Skip to content

Commit

Permalink
Basic Auth etcd mode to use username by default (#472)
Browse files Browse the repository at this point in the history
* basic auth etcd mode to use username if present

* rename DefaultUsername() to Username()
  • Loading branch information
Samu Tamminen authored Jan 24, 2022
1 parent 94da1f0 commit 3c21251
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 11 deletions.
33 changes: 26 additions & 7 deletions pkg/filter/validator/basicauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Expand Down Expand Up @@ -89,16 +92,31 @@ 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"`
}
)

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 {
Expand Down Expand Up @@ -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
Expand Down
31 changes: 27 additions & 4 deletions pkg/filter/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand Down Expand Up @@ -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)
Expand All @@ -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(
Expand Down

0 comments on commit 3c21251

Please sign in to comment.