Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ go_library(
deps = [
"//cache/disk:go_default_library",
"//config:go_default_library",
"//ldap:go_default_library",
"//server:go_default_library",
"//utils/flags:go_default_library",
"//utils/idle:go_default_library",
Expand Down
71 changes: 67 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,10 @@ OPTIONS:
[$BAZEL_REMOTE_TLS_KEY_FILE]

--allow_unauthenticated_reads If authentication is enabled
(--htpasswd_file or --tls_ca_file), allow unauthenticated clients read
access. (default: false, ie if authentication is required, read-only
requests must also be authenticated) [$BAZEL_REMOTE_UNAUTHENTICATED_READS]
(--htpasswd_file, --tls_ca_file or --ldap.url), allow unauthenticated
clients read access. (default: false, i.e. if authentication is required,
read-only requests must also be authenticated)
[$BAZEL_REMOTE_UNAUTHENTICATED_READS]

--idle_timeout value The maximum period of having received no request
after which the server will shut itself down. (default: 0s, ie disabled)
Expand Down Expand Up @@ -292,6 +293,34 @@ OPTIONS:
Google credentials for the Google Cloud Storage proxy backend.
[$BAZEL_REMOTE_GCS_JSON_CREDENTIALS_FILE]

--ldap.url value The LDAP URL which may include a port. LDAP over SSL
(LDAPs) is supported.
[$BAZEL_REMOTE_LDAP_URL]

--ldap.base_dn value The distinguished name of the search base.
[$BAZEL_REMOTE_LDAP_BASE_DN]

--ldap.bind_user value The user who is allowed to perform a search within
the base DN. If none is specified the connection and the search is
performed without an authentication. It is recommended to use a read-only
account.
[$BAZEL_REMOTE_LDAP_BIND_USER]

--ldap.bind_password value The password of the bind user.
[$BAZEL_REMOTE_LDAP_BIND_PASSWORD]

--ldap.username_attribute value The user attribute of a connecting user.
(default: "uid")
[$BAZEL_REMOTE_LDAP_USER_ATTRIBUTE]

--ldap.groups value Filter clause for searching groups. This option can be
given multiple times and the groups are OR connected in the search query.
[$BAZEL_REMOTE_LDAP_GROUPS]

--ldap.cache_time value The amount of time to cache a successful
authentication in seconds. (default 3600)
[$BAZEL_REMOTE_LDAP_CACHE_TIME]

--s3.endpoint value The S3/minio endpoint to use when using S3 proxy
backend. [$BAZEL_REMOTE_S3_ENDPOINT]

Expand Down Expand Up @@ -469,6 +498,17 @@ http_address: 0.0.0.0:8080
# Alternatively, you can use simple authentication:
#htpasswd_file: path/to/.htpasswd

# At most one authentication mechanism can be used
#ldap:
# url: ldaps://ldap.example.com:636
# base_dn: OU=My Users,DC=example,DC=com
# username_attribute: sAMAccountName # defaults to "uid"
# bind_user: ldapuser
# bind_password: ldappassword
# cache_time: 3600 # in seconds (default 1 hour)
# groups:
# - CN=bazel-users,OU=Groups,OU=My Users,DC=example,DC=com



# If tls_ca_file or htpasswd_file are specified, you can choose
Expand Down Expand Up @@ -679,7 +719,10 @@ $ bazel build :bazel-remote
### Authentication

bazel-remote defaults to allow unauthenticated access, but basic `.htpasswd`
style authentication and mutual TLS authentication are also supported.
style authentication, mutual TLS authentication and LDAP are also supported.
Please note that only one authentication mechanism can be used at a time.

#### htpasswd

In order to pass a `.htpasswd` and/or server key file(s) to the cache
inside a docker container, you first need to mount the file in the
Expand All @@ -698,6 +741,8 @@ $ docker run -v /path/to/cache/dir:/data \
--htpasswd_file /etc/bazel-remote/htpasswd --max_size 5
```

#### mTLS

If you prefer not using `.htpasswd` files it is also possible to
authenticate with mTLS (also can be known as "authenticating client
certificates"). You can do this by passing in the the cert/key the
Expand All @@ -716,6 +761,24 @@ $ docker run -v /path/to/cache/dir:/data \
--max_size 5
```

#### LDAP

LDAP is an additional authentication method for the cache. It can be used via
command line args, the config file or env variables.

```bash
$ docker run -v /path/to/cache/dir:/data \
-p 9090:8080 -p 9092:9092 buchgr/bazel-remote-cache \
--ldap.url="ldaps://ldap.example.com:636" \
--ldap.base_dn="OU=My Users,DC=example,DC=com" \
--ldap.groups="CN=bazel-users,OU=Groups,OU=My Users,DC=example,DC=com" \
--ldap.groups="CN=bazel-testers,OU=Groups,OU=My Users,DC=example,DC=com" \
--ldap.cache_time=100 \
--ldap.bind_user="cn=readonly.username,ou=readonly,OU=Other Users,DC=example,DC=com" \
--ldap.bind_password="secret4Sure" \
--max_size 5
```

### Using bazel-remote with AWS Credential file authentication for S3 inside a docker container

The following demonstrates how to configure a docker instance of bazel-remote to use an AWS S3
Expand Down
7 changes: 7 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ go_repository(
version = "v1.3.5",
)

go_repository(
name = "in_gopkg_asn1_ber_v1",
importpath = "gopkg.in/asn1-ber.v1",
sum = "h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=",
version = "v1.0.0-20181015200546-f715ec2f112d",
)

gazelle_dependencies()

http_archive(
Expand Down
52 changes: 51 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ type URLBackendConfig struct {
CaFile string `yaml:"ca_file"`
}

type LDAPConfig struct {
URL string `yaml:"url"`
BaseDN string `yaml:"base_dn"`
BindUser string `yaml:"bind_user"`
BindPassword string `yaml:"bind_password"`
UsernameAttribute string `yaml:"username_attribute"`
Groups []string `yaml:"groups,flow"`
CacheTime time.Duration `yaml:"cache_time"`
}

func (c *URLBackendConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type Aux URLBackendConfig
aux := &struct {
Expand Down Expand Up @@ -89,6 +99,7 @@ type Config struct {
StorageMode string `yaml:"storage_mode"`
ZstdImplementation string `yaml:"zstd_implementation"`
HtpasswdFile string `yaml:"htpasswd_file"`
LDAP *LDAPConfig `yaml:"ldap,omitempty"`
MinTLSVersion string `yaml:"min_tls_version"`
TLSCaFile string `yaml:"tls_ca_file"`
TLSCertFile string `yaml:"tls_cert_file"`
Expand Down Expand Up @@ -157,6 +168,7 @@ func newFromArgs(dir string, maxSize int, storageMode string, zstdImplementation
hc *URLBackendConfig,
grpcb *URLBackendConfig,
gcs *GoogleCloudStorageConfig,
ldap *LDAPConfig,
s3 *S3CloudStorageConfig,
azblob *AzBlobStorageConfig,
disableHTTPACValidation bool,
Expand Down Expand Up @@ -192,6 +204,7 @@ func newFromArgs(dir string, maxSize int, storageMode string, zstdImplementation
GoogleCloudStorage: gcs,
HTTPBackend: hc,
GRPCBackend: grpcb,
LDAP: ldap,
Comment thread
JonasScharpf marked this conversation as resolved.
IdleTimeout: idleTimeout,
DisableHTTPACValidation: disableHTTPACValidation,
DisableGRPCACDepsCheck: disableGRPCACDepsCheck,
Expand Down Expand Up @@ -278,6 +291,10 @@ func newFromYaml(data []byte) (*Config, error) {
return &c, nil
}

func NewConfigFromYaml(data []byte) (*Config, error) {
return newFromYaml(data)
}

func validateConfig(c *Config) error {
if c.Dir == "" {
return errors.New("The 'dir' flag/key is required")
Expand Down Expand Up @@ -368,7 +385,7 @@ func validateConfig(c *Config) error {
"and 'tls_cert_file' specified.")
}

if c.AllowUnauthenticatedReads && c.TLSCaFile == "" && c.HtpasswdFile == "" {
if c.AllowUnauthenticatedReads && c.TLSCaFile == "" && c.HtpasswdFile == "" && c.LDAP == nil {
return errors.New("AllowUnauthenticatedReads setting is only available when authentication is enabled")
}

Expand Down Expand Up @@ -450,6 +467,25 @@ func validateConfig(c *Config) error {
}
}

if c.HtpasswdFile != "" && c.TLSCaFile != "" && c.LDAP != nil {
return errors.New("One can specify at most one authentication mechanism")
}

if c.LDAP != nil {
if c.LDAP.URL == "" {
return errors.New("The 'url' field is required for 'ldap'")
}
if c.LDAP.BaseDN == "" {
return errors.New("The 'base_dn' field is required for 'ldap'")
}
if c.LDAP.UsernameAttribute == "" {
c.LDAP.UsernameAttribute = "uid"
}
if c.LDAP.CacheTime <= 0 {
c.LDAP.CacheTime = 3600
}
}

switch c.AccessLogLevel {
case "none", "all":
default:
Expand Down Expand Up @@ -590,6 +626,19 @@ func get(ctx *cli.Context) (*Config, error) {
}
}

var ldap *LDAPConfig
if ctx.String("ldap.url") != "" {
ldap = &LDAPConfig{
URL: ctx.String("ldap.url"),
BaseDN: ctx.String("ldap.base_dn"),
BindUser: ctx.String("ldap.bind_user"),
BindPassword: ctx.String("ldap.bind_password"),
UsernameAttribute: ctx.String("ldap.username_attribute"),
Groups: ctx.StringSlice("ldap.groups"),
CacheTime: ctx.Duration("ldap.cache_time"),
}
}

return newFromArgs(
ctx.String("dir"),
ctx.Int("max_size"),
Expand All @@ -610,6 +659,7 @@ func get(ctx *cli.Context) (*Config, error) {
hc,
grpcb,
gcs,
ldap,
s3,
azblob,
ctx.Bool("disable_http_ac_validation"),
Expand Down
51 changes: 51 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,57 @@ s3_proxy:
}
}

func TestValidLDAPConfig(t *testing.T) {
yaml := `host: localhost
port: 8080
dir: /opt/cache-dir
max_size: 100
ldap:
url: ldap://ldap.example.com
base_dn: OU=My Users,DC=example,DC=com
username_attribute: sAMAccountName
bind_user: ldapuser
bind_password: ldappassword
cache_time: 3600s
groups:
- CN=bazel-users,OU=Groups,OU=My Users,DC=example,DC=com
- CN=other-users,OU=Groups2,OU=Alien Users,DC=foo,DC=org
`
config, err := newFromYaml([]byte(yaml))
if err != nil {
t.Fatal(err)
}

expectedConfig := &Config{
HTTPAddress: "localhost:8080",
Dir: "/opt/cache-dir",
MaxSize: 100,
StorageMode: "zstd",
ZstdImplementation: "go",
LDAP: &LDAPConfig{
URL: "ldap://ldap.example.com",
BaseDN: "OU=My Users,DC=example,DC=com",
BindUser: "ldapuser",
BindPassword: "ldappassword",
UsernameAttribute: "sAMAccountName",
Groups: []string{"CN=bazel-users,OU=Groups,OU=My Users,DC=example,DC=com", "CN=other-users,OU=Groups2,OU=Alien Users,DC=foo,DC=org"},
CacheTime: 3600 * time.Second,
},
NumUploaders: 100,
MinTLSVersion: "1.0",
MaxQueuedUploads: 1000000,
MaxBlobSize: math.MaxInt64,
MaxProxyBlobSize: math.MaxInt64,
MetricsDurationBuckets: []float64{.5, 1, 2.5, 5, 10, 20, 40, 80, 160, 320},
AccessLogLevel: "all",
LogTimezone: "UTC",
}

if !cmp.Equal(config, expectedConfig) {
t.Fatalf("Expected '%+v' but got '%+v'", expectedConfig, config)
}
}

func TestValidProfiling(t *testing.T) {
yaml := `host: localhost
port: 1234
Expand Down
Loading