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 placeholders in dockerconfigjson auth field #586

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
11 changes: 11 additions & 0 deletions fixtures/input/nonempty/secret_dockerconfigjson.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Secret
type: kubernetes.io/dockerconfigjson
metadata:
annotations:
avp.kubernetes.io/path: secret/testing
avp.kubernetes.io/kv-version: "1"
name: <name>-dockerconfigjson
namespace: <namespace>
data:
.dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL215LXNlcnZlci5sb2NhbCI6eyJ1c2VybmFtZSI6InVzZXIiLCJwYXNzd29yZCI6Ilx1MDAzY3BhdGg6c2VjcmV0L3Rlc3Rpbmcjc2VjcmV0LXZhci12YWx1ZVx1MDAzZSIsImF1dGgiOiJkWE5sY2pvOGNHRjBhRHB6WldOeVpYUXZkR1Z6ZEdsdVp5TnpaV055WlhRdGRtRnlMWFpoYkhWbFBnPT0ifX19
12 changes: 12 additions & 0 deletions fixtures/output/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,18 @@ metadata:
type: Opaque
---
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL215LXNlcnZlci5sb2NhbCI6eyJhdXRoIjoiZFhObGNqcGtSMVo2WkVNeGQxbFlUbnBrTWpsNVdrRTlQUT09IiwicGFzc3dvcmQiOiJkR1Z6ZEMxd1lYTnpkMjl5WkE9PSIsInVzZXJuYW1lIjoidXNlciJ9fX0=
kind: Secret
metadata:
annotations:
avp.kubernetes.io/kv-version: "1"
avp.kubernetes.io/path: secret/testing
name: test-name-dockerconfigjson
namespace: test-namespace
type: kubernetes.io/dockerconfigjson
---
apiVersion: v1
data:
secret.yaml: c29tZQ==dGVzdC1wYXNzd29yZA==dmFsdWU=
kind: Secret
Expand Down
8 changes: 7 additions & 1 deletion pkg/kube/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
// A Resource is the basis for all Templates
type Resource struct {
Kind string
Type string
TemplateData map[string]interface{} // The template as read from YAML
Backend types.Backend
replacementErrors []error // Any errors encountered in performing replacements
Expand Down Expand Up @@ -50,6 +51,7 @@ func NewTemplate(template unstructured.Unstructured, backend types.Backend, path
return &Template{
Resource{
Kind: template.GetKind(),
Type: getType(template),
TemplateData: template.Object,
Backend: backend,
Data: data,
Expand All @@ -71,7 +73,11 @@ func (t *Template) Replace() error {
case "ConfigMap":
replacerFunc = configReplacement
case "Secret":
replacerFunc = secretReplacement
if t.Type == "kubernetes.io/dockerconfigjson" {
replacerFunc = dockerSecretReplacement
} else {
replacerFunc = secretReplacement
}
default:
replacerFunc = genericReplacement
}
Expand Down
62 changes: 62 additions & 0 deletions pkg/kube/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ import (

"github.com/argoproj-labs/argocd-vault-plugin/pkg/types"
"github.com/argoproj-labs/argocd-vault-plugin/pkg/utils"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8yaml "k8s.io/apimachinery/pkg/util/yaml"
)

type missingKeyError struct {
s string
}

type dockerConfig struct {
Auths map[string](map[string]string) `json:"auths"`
}

func (e *missingKeyError) Error() string {
return e.s
}
Expand Down Expand Up @@ -226,6 +231,54 @@ func secretReplacement(key, value string, resource Resource) (interface{}, []err
return genericReplacement(key, value, resource)
}

func dockerSecretReplacement(key, value string, resource Resource) (interface{}, []error) {
reencode := true
bytes, err := base64.StdEncoding.DecodeString(value)
if err != nil {
reencode = false
bytes = []byte(value)
}

dc := dockerConfig{}
err = json.Unmarshal(bytes, &dc)
if err != nil {
return secretReplacement(key, value, resource)
}

errs := []error{}

// iterate through the auths map and run a secretReplacement
// on each value so we can replace secrets that have
// been base64 encoded twice or are HTML escaped
for repo, auth := range dc.Auths {
for k, v := range auth {
res, err := secretReplacement(key, v, resource)
if err != nil {
errs = append(errs, err...)
} else {
auth[k], _ = res.(string)
}
}
dc.Auths[repo] = auth
}

bytes, err = json.Marshal(dc)
if err != nil {
return nil, append(errs, err)
}

value = string(bytes)
if reencode {
// only base64 encode if the original value was encoded
value = base64.StdEncoding.EncodeToString(bytes)
}

// run a fallback genericReplacement to catch any placeholders
// that are not in .dockerconfigjson
res, fallbackErr := genericReplacement(key, value, resource)
return res, append(errs, fallbackErr...)
}

func stringify(input interface{}) string {
switch input.(type) {
case int:
Expand Down Expand Up @@ -270,3 +323,12 @@ func secretNamespaceName(input string) (string, string) {

return secretNamespace, secretName
}

func getType(template unstructured.Unstructured) string {
val := template.UnstructuredContent()["type"]
s, ok := val.(string)
if !ok {
return ""
}
return s
}
68 changes: 68 additions & 0 deletions pkg/kube/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,74 @@ func TestSecretReplacement_Base64Substrings(t *testing.T) {
assertSuccessfulReplacement(&dummyResource, &expected, t)
}

func TestDockerReplacement_Base64(t *testing.T) {
dummyResource := Resource{
TemplateData: map[string]interface{}{
"data": map[string]interface{}{
".dockerconfigjson": `eyJhdXRocyI6eyJodHRwczovL215LXNlcnZlci5sb2NhbCI6eyJ1c2VybmFtZSI6Ilx1MDAzY3VzZXJcdTAwM2UiLCJwYXNzd29yZCI6Ilx1MDAzY3Bhc3NcdTAwM2UiLCJhdXRoIjoiUEhWelpYSStPanh3WVhOelBnPT0ifX19`,
},
},
Data: map[string]interface{}{
"user": "testuser",
"pass": "testpass",
},
Annotations: map[string]string{
(types.AVPPathAnnotation): "",
},
}

replaceInner(&dummyResource, &dummyResource.TemplateData, dockerSecretReplacement)

expected := Resource{
TemplateData: map[string]interface{}{
"data": map[string]interface{}{
".dockerconfigjson": `eyJhdXRocyI6eyJodHRwczovL215LXNlcnZlci5sb2NhbCI6eyJhdXRoIjoiZEdWemRIVnpaWEk2ZEdWemRIQmhjM009IiwicGFzc3dvcmQiOiJ0ZXN0cGFzcyIsInVzZXJuYW1lIjoidGVzdHVzZXIifX19`,
},
},
Data: map[string]interface{}{
"user": "testuser",
"pass": "testpass",
},
replacementErrors: []error{},
}

assertSuccessfulReplacement(&dummyResource, &expected, t)
}

func TestDockerReplacement_Plain(t *testing.T) {
dummyResource := Resource{
TemplateData: map[string]interface{}{
"stringData": map[string]interface{}{
".dockerconfigjson": `{"auths":{"https://my-server.local":{"username":"\u003cuser\u003e","password":"\u003cpass\u003e","auth":"PHVzZXI+OjxwYXNzPg=="}}}`,
},
},
Data: map[string]interface{}{
"user": "testuser",
"pass": "testpass",
},
Annotations: map[string]string{
(types.AVPPathAnnotation): "",
},
}

replaceInner(&dummyResource, &dummyResource.TemplateData, dockerSecretReplacement)

expected := Resource{
TemplateData: map[string]interface{}{
"stringData": map[string]interface{}{
".dockerconfigjson": `{"auths":{"https://my-server.local":{"auth":"dGVzdHVzZXI6dGVzdHBhc3M=","password":"testpass","username":"testuser"}}}`,
},
},
Data: map[string]interface{}{
"user": "testuser",
"pass": "testpass",
},
replacementErrors: []error{},
}

assertSuccessfulReplacement(&dummyResource, &expected, t)
}

func TestStringify(t *testing.T) {
testCases := []struct {
input interface{}
Expand Down