Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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