diff --git a/pkg/filter/validator/basicauth.go b/pkg/filter/validator/basicauth.go index 181e9e14de..1bdbab63b5 100644 --- a/pkg/filter/validator/basicauth.go +++ b/pkg/filter/validator/basicauth.go @@ -54,7 +54,10 @@ type ( // key: /custom-data/{etcdPrefix}/{$key} // value: // key: "$key" + // username: "$username" # optional // password: "$password" + // Username and password are used for Basic Authentication. If "username" is empty, the value of "key" + // entry is used as username for Basic Auth. EtcdPrefix string `yaml:"etcdPrefix" jsonschema:"omitempty"` } @@ -89,9 +92,11 @@ type ( authorizedUsersCache AuthorizedUsersCache } - credentials struct { - Key string `yaml:"key" jsonschema:"omitempty"` - Password string `yaml:"password" jsonschema:"omitempty"` + // etcdCredentials defines the format for credentials in etcd + etcdCredentials struct { + Key string `yaml:"key" jsonschema:"omitempty"` + User string `yaml:"username" jsonschema:"omitempty"` + Pass string `yaml:"password" jsonschema:"required"` } ) @@ -99,6 +104,19 @@ const ( customDataPrefix = "/custom-data/" ) +// Username uses username if present, otherwise key +func (cred *etcdCredentials) Username() string { + if cred.User != "" { + return cred.User + } + return cred.Key +} + +// Password returns password. +func (cred *etcdCredentials) Password() string { + return cred.Pass +} + func parseCredentials(creds string) (string, string, error) { parts := strings.Split(creds, ":") if len(parts) < 2 { @@ -213,19 +231,20 @@ func newEtcdUserCache(cluster cluster.Cluster, etcdPrefix string) *etcdUserCache func kvsToReader(kvs map[string]string) io.Reader { pwStrSlice := make([]string, 0, len(kvs)) for _, item := range kvs { - creds := &credentials{} + creds := &etcdCredentials{} err := yaml.Unmarshal([]byte(item), creds) if err != nil { logger.Errorf(err.Error()) continue } - if creds.Key == "" || creds.Password == "" { + if creds.Username() == "" || creds.Password() == "" { logger.Errorf( - "Parsing credential updates failed. Make sure that credentials contains 'key' and 'password' entries.", + "Parsing credential updates failed. " + + "Make sure that credentials contains 'key' or 'password' entry for password and 'password' entries.", ) continue } - pwStrSlice = append(pwStrSlice, creds.Key+":"+creds.Password) + pwStrSlice = append(pwStrSlice, creds.Username()+":"+creds.Password()) } if len(pwStrSlice) == 0 { // no credentials found, let's return empty reader diff --git a/pkg/filter/validator/validator_test.go b/pkg/filter/validator/validator_test.go index 0d569b082e..6a844e8206 100644 --- a/pkg/filter/validator/validator_test.go +++ b/pkg/filter/validator/validator_test.go @@ -377,6 +377,17 @@ basicAuth: expectedValid := []bool{true, true, false} v = createValidator(yamlSpec, nil, nil) + + t.Run("invalid headers", func(t *testing.T) { + ctx, header := prepareCtxAndHeader() + b64creds := base64.StdEncoding.EncodeToString([]byte(userIds[0])) // missing : and pw + header.Set("Authorization", "Basic "+b64creds) + result := v.Handle(ctx) + if result != resultInvalid { + t.Errorf("should be bad format") + } + }) + for i := 0; i < 3; i++ { ctx, header := prepareCtxAndHeader() b64creds := base64.StdEncoding.EncodeToString([]byte(userIds[i] + ":" + passwords[i])) @@ -428,6 +439,15 @@ basicAuth: } }) + t.Run("dummy etcd", func(t *testing.T) { + userCache := &etcdUserCache{prefix: ""} // should now skip cluster ops + userCache.WatchChanges() + if userCache.Match("doge", "dogepw") { + t.Errorf("should not match") + } + userCache.Close() + }) + t.Run("credentials from etcd", func(t *testing.T) { etcdDirName, err := ioutil.TempDir("", "etcd-validator-test") check(err) @@ -442,11 +462,14 @@ basicAuth: t.Errorf("newEtcdUserCache failed") } - pwToYaml := func(user string, pw string) string { - return fmt.Sprintf("key: %s\npassword: %s", user, pw) + pwToYaml := func(key string, user string, pw string) string { + if user != "" { + return fmt.Sprintf("username: %s\npassword: %s", user, pw) + } + return fmt.Sprintf("key: %s\npassword: %s", key, pw) } - clusterInstance.Put("/custom-data/credentials/1", pwToYaml(userIds[0], encryptedPasswords[0])) - clusterInstance.Put("/custom-data/credentials/2", pwToYaml(userIds[2], encryptedPasswords[2])) + clusterInstance.Put("/custom-data/credentials/1", pwToYaml(userIds[0], "", encryptedPasswords[0])) + clusterInstance.Put("/custom-data/credentials/2", pwToYaml("", userIds[2], encryptedPasswords[2])) var mockMap sync.Map supervisor := supervisor.NewMock(