From a8d7f5ce63986bb01d1ea9b6aeff47a89ac218cc Mon Sep 17 00:00:00 2001 From: Bence Csati Date: Tue, 11 Jun 2024 15:15:16 +0200 Subject: [PATCH] feat: support _json_key when mutating docker credentials Signed-off-by: Bence Csati --- pkg/provider/bao/secret.go | 100 +++++++++++++++++++++++----------- pkg/provider/common/secret.go | 69 ++++++++++++++++++++++- pkg/provider/vault/secret.go | 100 +++++++++++++++++++++++----------- 3 files changed, 204 insertions(+), 65 deletions(-) diff --git a/pkg/provider/bao/secret.go b/pkg/provider/bao/secret.go index a947863..9aab08a 100644 --- a/pkg/provider/bao/secret.go +++ b/pkg/provider/bao/secret.go @@ -78,41 +78,28 @@ func mutateDockerCreds(secret *corev1.Secret, dc *common.DockerCredentials, inje assembled := common.DockerCredentials{Auths: map[string]common.DockerAuthConfig{}} for key, creds := range dc.Auths { - authBytes, err := base64.StdEncoding.DecodeString(creds.Auth) + authBytes, err := base64.StdEncoding.DecodeString(creds.Auth.(string)) if err != nil { return errors.Wrap(err, "auth base64 decoding failed") } - auth := string(authBytes) - if isValidPrefix(auth) { - split := strings.Split(auth, ":") - if len(split) != 4 { - return errors.New("splitting auth credentials failed") + if isValidPrefix(string(authBytes)) { + authCreds, err := determineAuthType(authBytes) + if err != nil { + return errors.Wrap(err, "handling auth failed") } - username := fmt.Sprintf("%s:%s", split[0], split[1]) - password := fmt.Sprintf("%s:%s", split[2], split[3]) - credentialData := map[string]string{ - "username": username, - "password": password, + credentialData, err := common.AssembleCredentialData(authCreds) + if err != nil { + return errors.Wrap(err, "assembling credential data failed") } dcCreds, err := injector.GetDataFromBao(credentialData) if err != nil { - return err + return errors.Wrap(err, "retrieving data from bao failed") } - auth = fmt.Sprintf("%s:%s", dcCreds["username"], dcCreds["password"]) - dockerAuth := common.DockerAuthConfig{ - Auth: base64.StdEncoding.EncodeToString([]byte(auth)), - } - - if creds.Username != "" && creds.Password != "" { - dockerAuth.Username = dcCreds["username"] - dockerAuth.Password = dcCreds["password"] - } - - assembled.Auths[key] = dockerAuth + assembled.Auths[key] = common.AssembleDockerAuthConfig(dcCreds, creds) } } @@ -154,14 +141,35 @@ func secretNeedsMutation(secret *corev1.Secret) (bool, error) { } for _, creds := range dc.Auths { - authBytes, err := base64.StdEncoding.DecodeString(creds.Auth) - if err != nil { - return false, errors.Wrap(err, "auth base64 decoding failed") - } - - auth := string(authBytes) - if isValidPrefix(auth) { - return true, nil + switch creds.Auth.(type) { + case string: + authBytes, err := base64.StdEncoding.DecodeString(creds.Auth.(string)) + if err != nil { + return false, errors.Wrap(err, "auth base64 decoding failed") + } + + auth := string(authBytes) + if isValidPrefix(auth) { + return true, nil + } + + case map[string]interface{}: + // get sub-keys from the auth field + authMap, ok := creds.Auth.(map[string]interface{}) + if !ok { + return false, errors.New("invalid auth type") + } + + // check if any of the sub-keys have a vault prefix + for _, v := range authMap { + if isValidPrefix(v.(string)) { + return true, nil + } + } + return false, nil + + default: + return false, errors.New("invalid auth type") } } @@ -174,3 +182,33 @@ func secretNeedsMutation(secret *corev1.Secret) (bool, error) { return false, nil } + +// determineAuthType takes a byte slice of authentication data and determines its type. +// It supports three formats: "username:usr:password:pass", JSON keys, and valid vault paths. +func determineAuthType(auth []byte) (map[string]string, error) { + creds := make(map[string]string) + + // if the auth string is formatted as "username:usr:password:pass", + // split the string into username and password + split := strings.Split(string(auth), ":") + if len(split) == 4 { + creds["username"] = fmt.Sprintf("%s:%s", split[0], split[1]) + creds["password"] = fmt.Sprintf("%s:%s", split[2], split[3]) + + return creds, nil + } + + // if the auth string is a JSON key, don't split and use it as is + if json.Valid(auth) { + creds["auth"] = string(auth) + return creds, nil + } + + // if none of the above, the auth string can still be a valid vault path + if isValidPrefix(string(auth)) { + creds["auth"] = string(auth) + return creds, nil + } + + return nil, errors.New("invalid auth string") +} diff --git a/pkg/provider/common/secret.go b/pkg/provider/common/secret.go index a125b8f..ad618f5 100644 --- a/pkg/provider/common/secret.go +++ b/pkg/provider/common/secret.go @@ -14,15 +14,20 @@ package common +import ( + "encoding/base64" + "fmt" +) + type DockerCredentials struct { Auths map[string]DockerAuthConfig `json:"auths"` } // DockerAuthConfig contains authorization information for connecting to a Registry type DockerAuthConfig struct { - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Auth string `json:"auth,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth interface{} `json:"auth,omitempty"` // Email is an optional value associated with the username. // This field is deprecated and will be removed in a later @@ -38,3 +43,61 @@ type DockerAuthConfig struct { // RegistryToken is a bearer token to be sent to a registry RegistryToken string `json:"registrytoken,omitempty"` } + +// assembleCredentialData assembles the credential data that will be retrieved from Vault +func AssembleCredentialData(authCreds map[string]string) (map[string]string, error) { + if username, ok := authCreds["username"]; ok { + if password, ok := authCreds["password"]; ok { + credentialData := map[string]string{ + "username": username, + "password": password, + } + + return credentialData, nil + } + } + + if auth, ok := authCreds["auth"]; ok { + credentialData := map[string]string{ + "auth": auth, + } + + return credentialData, nil + } + + return nil, fmt.Errorf("no valid credentials found") +} + +// assembleDockerAuthConfig assembles the DockerAuthConfig from the retrieved data from Vault +func AssembleDockerAuthConfig(dcCreds map[string]string, creds DockerAuthConfig) DockerAuthConfig { + if username, ok := dcCreds["username"]; ok { + if password, ok := dcCreds["password"]; ok { + auth := fmt.Sprintf("%s:%s", username, password) + + dockerAuth := DockerAuthConfig{ + Auth: base64.StdEncoding.EncodeToString([]byte(auth)), + } + + if creds.Username != "" && creds.Password != "" { + dockerAuth.Username = dcCreds["username"] + dockerAuth.Password = dcCreds["password"] + } + + return dockerAuth + } + } + + if auth, ok := dcCreds["auth"]; ok { + dockerAuth := DockerAuthConfig{ + Auth: base64.StdEncoding.EncodeToString([]byte(auth)), + } + + if creds.Auth != "" { + dockerAuth.Auth = auth + } + + return dockerAuth + } + + return DockerAuthConfig{} +} diff --git a/pkg/provider/vault/secret.go b/pkg/provider/vault/secret.go index dbc5e03..eb5fce8 100644 --- a/pkg/provider/vault/secret.go +++ b/pkg/provider/vault/secret.go @@ -78,41 +78,28 @@ func mutateDockerCreds(secret *corev1.Secret, dc *common.DockerCredentials, inje assembled := common.DockerCredentials{Auths: map[string]common.DockerAuthConfig{}} for key, creds := range dc.Auths { - authBytes, err := base64.StdEncoding.DecodeString(creds.Auth) + authBytes, err := base64.StdEncoding.DecodeString(creds.Auth.(string)) if err != nil { return errors.Wrap(err, "auth base64 decoding failed") } - auth := string(authBytes) - if isValidPrefix(auth) { - split := strings.Split(auth, ":") - if len(split) != 4 { - return errors.New("splitting auth credentials failed") + if isValidPrefix(string(authBytes)) { + authCreds, err := determineAuthType(authBytes) + if err != nil { + return errors.Wrap(err, "handling auth failed") } - username := fmt.Sprintf("%s:%s", split[0], split[1]) - password := fmt.Sprintf("%s:%s", split[2], split[3]) - credentialData := map[string]string{ - "username": username, - "password": password, + credentialData, err := common.AssembleCredentialData(authCreds) + if err != nil { + return errors.Wrap(err, "assembling credential data failed") } dcCreds, err := injector.GetDataFromVault(credentialData) if err != nil { - return err + return errors.Wrap(err, "retrieving data from vault failed") } - auth = fmt.Sprintf("%s:%s", dcCreds["username"], dcCreds["password"]) - dockerAuth := common.DockerAuthConfig{ - Auth: base64.StdEncoding.EncodeToString([]byte(auth)), - } - - if creds.Username != "" && creds.Password != "" { - dockerAuth.Username = dcCreds["username"] - dockerAuth.Password = dcCreds["password"] - } - - assembled.Auths[key] = dockerAuth + assembled.Auths[key] = common.AssembleDockerAuthConfig(dcCreds, creds) } } @@ -154,14 +141,35 @@ func secretNeedsMutation(secret *corev1.Secret) (bool, error) { } for _, creds := range dc.Auths { - authBytes, err := base64.StdEncoding.DecodeString(creds.Auth) - if err != nil { - return false, errors.Wrap(err, "auth base64 decoding failed") - } - - auth := string(authBytes) - if isValidPrefix(auth) { - return true, nil + switch creds.Auth.(type) { + case string: + authBytes, err := base64.StdEncoding.DecodeString(creds.Auth.(string)) + if err != nil { + return false, errors.Wrap(err, "auth base64 decoding failed") + } + + auth := string(authBytes) + if isValidPrefix(auth) { + return true, nil + } + + case map[string]interface{}: + // get sub-keys from the auth field + authMap, ok := creds.Auth.(map[string]interface{}) + if !ok { + return false, errors.New("invalid auth type") + } + + // check if any of the sub-keys have a vault prefix + for _, v := range authMap { + if isValidPrefix(v.(string)) { + return true, nil + } + } + return false, nil + + default: + return false, errors.New("invalid auth type") } } @@ -174,3 +182,33 @@ func secretNeedsMutation(secret *corev1.Secret) (bool, error) { return false, nil } + +// determineAuthType takes a byte slice of authentication data and determines its type. +// It supports three formats: "username:usr:password:pass", JSON keys, and valid vault paths. +func determineAuthType(auth []byte) (map[string]string, error) { + creds := make(map[string]string) + + // if the auth string is formatted as "username:usr:password:pass", + // split the string into username and password + split := strings.Split(string(auth), ":") + if len(split) == 4 { + creds["username"] = fmt.Sprintf("%s:%s", split[0], split[1]) + creds["password"] = fmt.Sprintf("%s:%s", split[2], split[3]) + + return creds, nil + } + + // if the auth string is a JSON key, don't split and use it as is + if json.Valid(auth) { + creds["auth"] = string(auth) + return creds, nil + } + + // if none of the above, the auth string can still be a valid vault path + if isValidPrefix(string(auth)) { + creds["auth"] = string(auth) + return creds, nil + } + + return nil, errors.New("invalid auth string") +}