Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support _json_key when mutating docker credentials #91

Merged
Merged
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
100 changes: 69 additions & 31 deletions pkg/provider/bao/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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")
}
}

Expand All @@ -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")
}
69 changes: 66 additions & 3 deletions pkg/provider/common/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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{}
}
100 changes: 69 additions & 31 deletions pkg/provider/vault/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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")
}
}

Expand All @@ -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")
}
Loading