Skip to content

Commit

Permalink
Mask dockerconfigjson secret types with a json object
Browse files Browse the repository at this point in the history
If implemented, flux diff kustomization will managed correctly sops
managed dockerconfigjson secrets.
A secret containing stringData has been added to the test cases as well.

Signed-off-by: Soule BA <[email protected]>
  • Loading branch information
souleb committed Feb 5, 2022
1 parent 4b4e6b1 commit 49cb272
Show file tree
Hide file tree
Showing 15 changed files with 160 additions and 19 deletions.
6 changes: 6 additions & 0 deletions cmd/flux/diff_kustomization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ func TestDiffKustomization(t *testing.T) {
objectFile: "./testdata/diff-kustomization/value-sops-secret.yaml",
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden"),
},
{
name: "diff with a sops dockerconfigjson secret object",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo",
objectFile: "./testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml",
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden"),
},
}

tmpl := map[string]string{
Expand Down
25 changes: 25 additions & 0 deletions cmd/flux/testdata/build-kustomization/podinfo-result.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,31 @@ spec:
type: ClusterIP
---
apiVersion: v1
data:
.dockerconfigjson: eyJtYXNrIjoiKipTT1BTKioifQ==
kind: Secret
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: docker-secret
namespace: default
type: kubernetes.io/dockerconfigjson
---
apiVersion: v1
kind: Secret
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: secret-basic-auth-stringdata
namespace: default
stringData:
password: t0p-Secret
username: admin
type: kubernetes.io/basic-auth
---
apiVersion: v1
data:
token: KipTT1BTKio=
kind: Secret
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apiVersion: v1
data:
.dockerconfigjson: ENC[AES256_GCM,data:KHCFH3hNnc+PMfWLFEPjebf3W4z4WXbGFAANRZyZC+07z7wlrTALJM6rn8YslW4tMAWCoAYxblC5WRCszTy0h9rw0U/RGOv5H0qCgnNg/FILFUqhwo9pNfrUH+MEP4M9qxxbLKZwObpHUE7DUsKx1JYAxsI=,iv:q48lqUbUQD+0cbYcjNMZMJLRdGHi78ZmDhNAT2th9tg=,tag:QRI2SZZXQrAcdql3R5AH2g==,type:str]
kind: Secret
metadata:
name: docker-secret
type: kubernetes.io/dockerconfigjson
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3eU1CTEJhVXZ4eEVYYkVV
OU90TEcrR2pYckttN0pBanJoSUZWSW1RQXlRCkUydFJ3V1NZUTBuVFF0aC9GUEcw
bUdhNjJWTkoyL1FUVi9Dc1dxUDBkM0UKLS0tIE1sQXkwcWdGaEFuY0RHQTVXM0J6
dWpJcThEbW15V3dXYXpPZklBdW1Hd1kKoIAdmGNPrEctV8h1w8KuvQ5S+BGmgqN9
MgpNmUhJjWhgcQpb5BRYpQesBOgU5TBGK7j58A6DMDKlSiYZsdQchQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2022-02-03T16:03:17Z"
mac: ENC[AES256_GCM,data:AHdYSawajwgAFwlmDN1IPNmT9vWaYKzyVIra2d6sPcjTbZ8/p+VRSRpVm4XZFFsaNnW5AUJaouwXnKYDTmJDXKlr/rQcu9kXqsssQgdzcXaA6l5uJlgsnml8ba7J3OK+iEKMax23mwQEx2EUskCd9ENOwFDkunP02sxqDNOz20k=,iv:8F5OamHt3fAVorf6p+SoIrWoqkcATSGWVoM0EK87S4M=,tag:E1mxXnc7wWkEX5BxhpLtng==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.7.1
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ resources:
- ./deployment.yaml
- ./hpa.yaml
- ./service.yaml
- ./dockerconfigjson-sops-secret.yaml
- ./stringdata-secret.yaml
secretGenerator:
- files:
- token=token.encrypted
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-basic-auth-stringdata
type: kubernetes.io/basic-auth
stringData:
username: admin
password: t0p-Secret
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/docker-secret created
► Secret/default/secret-basic-auth-stringdata created
► Secret/default/podinfo-token-77t89m9b67 created
► Secret/default/db-user-pass-bkbd782d2c created
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
► Deployment/default/podinfo created
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/secret-basic-auth-stringdata created
► Secret/default/podinfo-token-77t89m9b67 created
► Secret/default/db-user-pass-bkbd782d2c created
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
► Deployment/default/podinfo created
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/docker-secret created
► Secret/default/secret-basic-auth-stringdata created
► Secret/default/podinfo-token-77t89m9b67 drifted

data
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
► Deployment/default/podinfo created
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/docker-secret created
► Secret/default/secret-basic-auth-stringdata created
► Secret/default/podinfo-token-77t89m9b67 created
► Secret/default/db-user-pass-bkbd782d2c drifted

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ spec.ports.http.port
- 9899
+ 9898

► Secret/default/docker-secret created
► Secret/default/secret-basic-auth-stringdata created
► Secret/default/podinfo-token-77t89m9b67 created
► Secret/default/db-user-pass-bkbd782d2c created
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
► Deployment/default/podinfo created
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/docker-secret created
► Secret/default/secret-basic-auth-stringdata created
► Secret/default/db-user-pass-bkbd782d2c created
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
data:
.dockerconfigjson: eyJtYXNrIjoiKipTT1BTKioifQ==
kind: Secret
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: docker-secret
namespace: default
type: kubernetes.io/dockerconfigjson
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
► Deployment/default/podinfo created
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/docker-secret created
► Secret/default/secret-basic-auth-stringdata created
► Secret/default/podinfo-token-77t89m9b67 created
► Secret/default/db-user-pass-bkbd782d2c created
76 changes: 60 additions & 16 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"sync"
"time"
Expand All @@ -40,9 +41,11 @@ import (
)

const (
controllerName = "kustomize-controller"
controllerGroup = "kustomize.toolkit.fluxcd.io"
mask = "**SOPS**"
controllerName = "kustomize-controller"
controllerGroup = "kustomize.toolkit.fluxcd.io"
mask = "**SOPS**"
dockercfgSecretType = "kubernetes.io/dockerconfigjson"
typeField = "type"
)

var defaultTimeout = 80 * time.Second
Expand Down Expand Up @@ -183,7 +186,7 @@ func (b *Builder) build() (m resmap.ResMap, err error) {
}

// make sure secrets are masked
err = trimSopsData(res)
err = maskSopsData(res)
if err != nil {
return
}
Expand Down Expand Up @@ -257,37 +260,42 @@ func (b *Builder) setOwnerLabels(res *resource.Resource) error {
return nil
}

func trimSopsData(res *resource.Resource) error {
func maskSopsData(res *resource.Resource) error {
// sopsMess is the base64 encoded mask
sopsMess := base64.StdEncoding.EncodeToString([]byte(mask))

if res.GetKind() == "Secret" {
dataMap := res.GetDataMap()
asYaml, err := res.AsYAML()
if err != nil {
return fmt.Errorf("failed to decode secret %s data: %w", res.GetName(), err)
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
}

//delete any sops data as we don't want to expose it
if bytes.Contains(asYaml, []byte("sops:")) && bytes.Contains(asYaml, []byte("mac: ENC[")) {
// delete the sops object
res.PipeE(yaml.FieldClearer{Name: "sops"})
for k := range dataMap {
dataMap[k] = sopsMess
secretType, err := res.GetFieldValue(typeField)
if err != nil {
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
}

} else {
for k, v := range dataMap {
data, err := base64.StdEncoding.DecodeString(v)
if v, ok := secretType.(string); ok && v == dockercfgSecretType {
// if the secret is a json docker config secret, we need to mask the data with a json object
err := maskDockerconfigjsonSopsData(dataMap)
if err != nil {
if _, ok := err.(base64.CorruptInputError); ok {
return fmt.Errorf("failed to decode secret %s data: %w", res.GetName(), err)
}
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
}

if bytes.Contains(data, []byte("sops")) && bytes.Contains(data, []byte("ENC[")) {
} else {
for k := range dataMap {
dataMap[k] = sopsMess
}
}
} else {
err := maskBase64EncryptedSopsData(dataMap, sopsMess)
if err != nil {
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
}
}

res.SetDataMap(dataMap)
Expand All @@ -296,6 +304,42 @@ func trimSopsData(res *resource.Resource) error {
return nil
}

func maskDockerconfigjsonSopsData(dataMap map[string]string) error {
sopsMess := struct {
Mask string `json:"mask"`
}{
Mask: mask,
}

maskJson, err := json.Marshal(sopsMess)
if err != nil {
return err
}

for k := range dataMap {
dataMap[k] = base64.StdEncoding.EncodeToString(maskJson)
}

return nil
}

func maskBase64EncryptedSopsData(dataMap map[string]string, mask string) error {
for k, v := range dataMap {
data, err := base64.StdEncoding.DecodeString(v)
if err != nil {
if _, ok := err.(base64.CorruptInputError); ok {
return err
}
}

if bytes.Contains(data, []byte("sops")) && bytes.Contains(data, []byte("ENC[")) {
dataMap[k] = mask
}
}

return nil
}

// Cancel cancels the build
// It restores a clean reprository
func (b *Builder) Cancel() error {
Expand Down
6 changes: 3 additions & 3 deletions internal/build/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type: kubernetes.io/basic-auth
name: "secret sops secret",
yamlStr: `apiVersion: v1
data:
.dockercfg: ENC[AES256_GCM,data:KHCFH3hNnc+PMfWLFEPjebf3W4z4WXbGFAANRZyZC+07z7wlrTALJM6rn8YslW4tMAWCoAYxblC5WRCszTy0h9rw0U/RGOv5H0qCgnNg/FILFUqhwo9pNfrUH+MEP4M9qxxbLKZwObpHUE7DUsKx1JYAxsI=,iv:q48lqUbUQD+0cbYcjNMZMJLRdGHi78ZmDhNAT2th9tg=,tag:QRI2SZZXQrAcdql3R5AH2g==,type:str]
.dockerconfigjson: ENC[AES256_GCM,data:KHCFH3hNnc+PMfWLFEPjebf3W4z4WXbGFAANRZyZC+07z7wlrTALJM6rn8YslW4tMAWCoAYxblC5WRCszTy0h9rw0U/RGOv5H0qCgnNg/FILFUqhwo9pNfrUH+MEP4M9qxxbLKZwObpHUE7DUsKx1JYAxsI=,iv:q48lqUbUQD+0cbYcjNMZMJLRdGHi78ZmDhNAT2th9tg=,tag:QRI2SZZXQrAcdql3R5AH2g==,type:str]
kind: Secret
metadata:
name: secret
Expand Down Expand Up @@ -125,7 +125,7 @@ sops:
`,
expected: `apiVersion: v1
data:
.dockercfg: KipTT1BTKio=
.dockerconfigjson: eyJtYXNrIjoiKipTT1BTKioifQ==
kind: Secret
metadata:
name: secret
Expand All @@ -142,7 +142,7 @@ type: kubernetes.io/dockerconfigjson
}

resource := &resource.Resource{RNode: *r}
err = trimSopsData(resource)
err = maskSopsData(resource)
if err != nil {
t.Fatalf("unable to trim sops data: %v", err)
}
Expand Down

0 comments on commit 49cb272

Please sign in to comment.