mirror of
https://github.com/fluxcd/flux2.git
synced 2026-02-24 16:41:47 +00:00
Mask dockerconfigjson secret types and support StringData secrets
If implemented, flux diff kustomization will managed correctly sops managed dockerconfigjson secrets. Sops encrypted secret with stringData maps are supported too. Signed-off-by: Soule BA <soule@weave.works>
This commit is contained in:
parent
cf3f729f98
commit
2e9fd33ce5
19 changed files with 281 additions and 24 deletions
|
|
@ -79,6 +79,18 @@ func TestDiffKustomization(t *testing.T) {
|
||||||
objectFile: "./testdata/diff-kustomization/value-sops-secret.yaml",
|
objectFile: "./testdata/diff-kustomization/value-sops-secret.yaml",
|
||||||
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden"),
|
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"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff with a sops stringdata secret object",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo",
|
||||||
|
objectFile: "./testdata/diff-kustomization/stringdata-sops-secret.yaml",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-stringdata-sops-secret.golden"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl := map[string]string{
|
tmpl := map[string]string{
|
||||||
|
|
|
||||||
|
|
@ -153,12 +153,18 @@ func NewRootFlags() rootFlags {
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
logger.Failuref("%v", err)
|
|
||||||
|
|
||||||
if err, ok := err.(*RequestError); ok {
|
if err, ok := err.(*RequestError); ok {
|
||||||
|
if err.StatusCode == 1 {
|
||||||
|
logger.Warningf("%v", err)
|
||||||
|
} else {
|
||||||
|
logger.Failuref("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
os.Exit(err.StatusCode)
|
os.Exit(err.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Failuref("%v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,31 @@ spec:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
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: KipTT1BTKio=
|
||||||
|
username: KipTT1BTKio=
|
||||||
|
type: kubernetes.io/basic-auth
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
data:
|
data:
|
||||||
token: KipTT1BTKio=
|
token: KipTT1BTKio=
|
||||||
kind: Secret
|
kind: Secret
|
||||||
|
|
|
||||||
27
cmd/flux/testdata/build-kustomization/podinfo/dockerconfigjson-sops-secret.yaml
vendored
Normal file
27
cmd/flux/testdata/build-kustomization/podinfo/dockerconfigjson-sops-secret.yaml
vendored
Normal file
|
|
@ -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
|
||||||
|
|
@ -4,6 +4,8 @@ resources:
|
||||||
- ./deployment.yaml
|
- ./deployment.yaml
|
||||||
- ./hpa.yaml
|
- ./hpa.yaml
|
||||||
- ./service.yaml
|
- ./service.yaml
|
||||||
|
- ./dockerconfigjson-sops-secret.yaml
|
||||||
|
- ./stringdata-secret.yaml
|
||||||
secretGenerator:
|
secretGenerator:
|
||||||
- files:
|
- files:
|
||||||
- token=token.encrypted
|
- token=token.encrypted
|
||||||
|
|
|
||||||
28
cmd/flux/testdata/build-kustomization/podinfo/stringdata-secret.yaml
vendored
Normal file
28
cmd/flux/testdata/build-kustomization/podinfo/stringdata-secret.yaml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secret-basic-auth-stringdata
|
||||||
|
type: kubernetes.io/basic-auth
|
||||||
|
stringData:
|
||||||
|
username: ENC[AES256_GCM,data:uKiQR48=,iv:jh2lgyAVu7igJAgoJsnOGhjxFyvUAa9lvT21u3hhqpU=,tag:zXM2JEpk3ZEH7WfkcWXXkw==,type:str]
|
||||||
|
password: ENC[AES256_GCM,data:PyhZmNhy929JGQ==,iv:PBqPaJmSw21+kn4gIlg5VdjLNZyf613z5RUTCesBoVw=,tag:Hjc7DsuUrtsz7PYPdNkL3g==,type:str]
|
||||||
|
sops:
|
||||||
|
kms: []
|
||||||
|
gcp_kms: []
|
||||||
|
azure_kv: []
|
||||||
|
hc_vault: []
|
||||||
|
age:
|
||||||
|
- recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJd0xxbDZhYjVoZzY4YWhK
|
||||||
|
d2NvMVgrSGRVUGhHRGg3R1FpVURnbmh1TDBzCjcwby85M3JaK09QVk0yZFNMb2NL
|
||||||
|
c2NQZW5hS1FhYlBHU0VoUzBVYzZYUUUKLS0tIEdaNEw2Y0VjVHpZc3pyYUtLVmJk
|
||||||
|
NmN3K2VLU0NiZ1d0VHBYbGlCM1lrNmMKeWz3yfFbMNE+ly21oLfc1XnDSPRmnlPP
|
||||||
|
wIs8lk/qrzVZ45C9GdWnnPeGZZiia46Yop9TxseUS8gCjJ6KCxJCAg==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
lastmodified: "2022-02-06T12:51:07Z"
|
||||||
|
mac: ENC[AES256_GCM,data:jtdzwj19uxdxvnmXg1HkAkDA6XlKMJOYFy7uLI5t/t11LwGop5Yeo7a4nQEEELehRx9J7B6U6NiySxAxBxWx5uW5vI5c8+069VV6dkiCIefnYSzuoIhQafjlFl1/KvH7VEjIWfHYuXF09v9PEKXkxEHUYDpS3QqQ3ymHRRI08pU=, iv:xX3E7F+AM29Pm8G5oqxRfYu9E7tEBGIaHeCJYgrtFmc=,tag:MJPGusNvu05z939jg8PAwQ==,type:str]
|
||||||
|
pgp: []
|
||||||
|
encrypted_regex: ^(data|stringData)$
|
||||||
|
version: 3.7.1
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
► HorizontalPodAutoscaler/default/podinfo created
|
► HorizontalPodAutoscaler/default/podinfo created
|
||||||
► Service/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/podinfo-token-77t89m9b67 created
|
||||||
► Secret/default/db-user-pass-bkbd782d2c created
|
► Secret/default/db-user-pass-bkbd782d2c created
|
||||||
|
|
|
||||||
6
cmd/flux/testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden
vendored
Normal file
6
cmd/flux/testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden
vendored
Normal file
|
|
@ -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
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
► Deployment/default/podinfo created
|
► Deployment/default/podinfo created
|
||||||
► HorizontalPodAutoscaler/default/podinfo created
|
► HorizontalPodAutoscaler/default/podinfo created
|
||||||
► Service/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
|
► Secret/default/podinfo-token-77t89m9b67 drifted
|
||||||
|
|
||||||
data
|
data
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
► Deployment/default/podinfo created
|
► Deployment/default/podinfo created
|
||||||
► HorizontalPodAutoscaler/default/podinfo created
|
► HorizontalPodAutoscaler/default/podinfo created
|
||||||
► Service/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/podinfo-token-77t89m9b67 created
|
||||||
► Secret/default/db-user-pass-bkbd782d2c drifted
|
► Secret/default/db-user-pass-bkbd782d2c drifted
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,7 @@ spec.ports.http.port
|
||||||
- 9899
|
- 9899
|
||||||
+ 9898
|
+ 9898
|
||||||
|
|
||||||
|
► Secret/default/docker-secret created
|
||||||
|
► Secret/default/secret-basic-auth-stringdata created
|
||||||
► Secret/default/podinfo-token-77t89m9b67 created
|
► Secret/default/podinfo-token-77t89m9b67 created
|
||||||
► Secret/default/db-user-pass-bkbd782d2c created
|
► Secret/default/db-user-pass-bkbd782d2c created
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
► Deployment/default/podinfo created
|
► Deployment/default/podinfo created
|
||||||
► HorizontalPodAutoscaler/default/podinfo created
|
► HorizontalPodAutoscaler/default/podinfo created
|
||||||
► Service/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
|
► Secret/default/db-user-pass-bkbd782d2c created
|
||||||
|
|
|
||||||
6
cmd/flux/testdata/diff-kustomization/diff-with-stringdata-sops-secret.golden
vendored
Normal file
6
cmd/flux/testdata/diff-kustomization/diff-with-stringdata-sops-secret.golden
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
► Deployment/default/podinfo created
|
||||||
|
► HorizontalPodAutoscaler/default/podinfo created
|
||||||
|
► Service/default/podinfo created
|
||||||
|
► Secret/default/docker-secret created
|
||||||
|
► Secret/default/podinfo-token-77t89m9b67 created
|
||||||
|
► Secret/default/db-user-pass-bkbd782d2c created
|
||||||
11
cmd/flux/testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml
vendored
Normal file
11
cmd/flux/testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml
vendored
Normal file
|
|
@ -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
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
► Deployment/default/podinfo created
|
► Deployment/default/podinfo created
|
||||||
► HorizontalPodAutoscaler/default/podinfo created
|
► HorizontalPodAutoscaler/default/podinfo created
|
||||||
► Service/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/podinfo-token-77t89m9b67 created
|
||||||
► Secret/default/db-user-pass-bkbd782d2c created
|
► Secret/default/db-user-pass-bkbd782d2c created
|
||||||
|
|
|
||||||
12
cmd/flux/testdata/diff-kustomization/stringdata-sops-secret.yaml
vendored
Normal file
12
cmd/flux/testdata/diff-kustomization/stringdata-sops-secret.yaml
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
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: KipTT1BTKio=
|
||||||
|
username: KipTT1BTKio=
|
||||||
|
type: kubernetes.io/basic-auth
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -40,9 +41,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
controllerName = "kustomize-controller"
|
controllerName = "kustomize-controller"
|
||||||
controllerGroup = "kustomize.toolkit.fluxcd.io"
|
controllerGroup = "kustomize.toolkit.fluxcd.io"
|
||||||
mask = "**SOPS**"
|
mask = "**SOPS**"
|
||||||
|
dockercfgSecretType = "kubernetes.io/dockerconfigjson"
|
||||||
|
typeField = "type"
|
||||||
|
dataField = "data"
|
||||||
|
stringDataField = "stringData"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultTimeout = 80 * time.Second
|
var defaultTimeout = 80 * time.Second
|
||||||
|
|
@ -183,7 +188,7 @@ func (b *Builder) build() (m resmap.ResMap, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure secrets are masked
|
// make sure secrets are masked
|
||||||
err = trimSopsData(res)
|
err = maskSopsData(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -257,40 +262,131 @@ func (b *Builder) setOwnerLabels(res *resource.Resource) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimSopsData(res *resource.Resource) error {
|
func maskSopsData(res *resource.Resource) error {
|
||||||
// sopsMess is the base64 encoded mask
|
// sopsMess is the base64 encoded mask
|
||||||
sopsMess := base64.StdEncoding.EncodeToString([]byte(mask))
|
sopsMess := base64.StdEncoding.EncodeToString([]byte(mask))
|
||||||
|
|
||||||
if res.GetKind() == "Secret" {
|
if res.GetKind() == "Secret" {
|
||||||
|
// get both data and stringdata maps as a secret can have both
|
||||||
dataMap := res.GetDataMap()
|
dataMap := res.GetDataMap()
|
||||||
|
stringDataMap := getStringDataMap(res)
|
||||||
asYaml, err := res.AsYAML()
|
asYaml, err := res.AsYAML()
|
||||||
if err != nil {
|
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
|
// delete any sops data as we don't want to expose it
|
||||||
|
// assume that both data and stringdata are encrypted
|
||||||
if bytes.Contains(asYaml, []byte("sops:")) && bytes.Contains(asYaml, []byte("mac: ENC[")) {
|
if bytes.Contains(asYaml, []byte("sops:")) && bytes.Contains(asYaml, []byte("mac: ENC[")) {
|
||||||
|
// delete the sops object
|
||||||
res.PipeE(yaml.FieldClearer{Name: "sops"})
|
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 {
|
if v, ok := secretType.(string); ok && v == dockercfgSecretType {
|
||||||
for k, v := range dataMap {
|
// if the secret is a json docker config secret, we need to mask the data with a json object
|
||||||
data, err := base64.StdEncoding.DecodeString(v)
|
err := maskDockerconfigjsonSopsData(dataMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(base64.CorruptInputError); ok {
|
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
|
||||||
return fmt.Errorf("failed to decode secret %s data: %w", res.GetName(), err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Contains(data, []byte("sops")) && bytes.Contains(data, []byte("ENC[")) {
|
err = maskDockerconfigjsonSopsData(stringDataMap)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for k := range dataMap {
|
||||||
dataMap[k] = sopsMess
|
dataMap[k] = sopsMess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for k := range stringDataMap {
|
||||||
|
stringDataMap[k] = sopsMess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := maskBase64EncryptedSopsData(dataMap, sopsMess)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = maskSopsDataInStringDataSecret(stringDataMap, sopsMess)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set the data and stringdata maps
|
||||||
res.SetDataMap(dataMap)
|
res.SetDataMap(dataMap)
|
||||||
|
|
||||||
|
if len(stringDataMap) > 0 {
|
||||||
|
err = res.SetMapField(yaml.NewMapRNode(&stringDataMap), stringDataField)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStringDataMap(rn *resource.Resource) map[string]string {
|
||||||
|
n, err := rn.Pipe(yaml.Lookup(stringDataField))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := map[string]string{}
|
||||||
|
_ = n.VisitFields(func(node *yaml.MapNode) error {
|
||||||
|
result[yaml.GetValue(node.Key)] = yaml.GetValue(node.Value)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func maskSopsDataInStringDataSecret(stringDataMap map[string]string, mask string) error {
|
||||||
|
for k, v := range stringDataMap {
|
||||||
|
if bytes.Contains([]byte(v), []byte("sops")) && bytes.Contains([]byte(v), []byte("ENC[")) {
|
||||||
|
stringDataMap[k] = mask
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ type: kubernetes.io/basic-auth
|
||||||
name: "secret sops secret",
|
name: "secret sops secret",
|
||||||
yamlStr: `apiVersion: v1
|
yamlStr: `apiVersion: v1
|
||||||
data:
|
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
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
name: secret
|
name: secret
|
||||||
|
|
@ -125,7 +125,7 @@ sops:
|
||||||
`,
|
`,
|
||||||
expected: `apiVersion: v1
|
expected: `apiVersion: v1
|
||||||
data:
|
data:
|
||||||
.dockercfg: KipTT1BTKio=
|
.dockerconfigjson: eyJtYXNrIjoiKipTT1BTKioifQ==
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
name: secret
|
name: secret
|
||||||
|
|
@ -142,7 +142,7 @@ type: kubernetes.io/dockerconfigjson
|
||||||
}
|
}
|
||||||
|
|
||||||
resource := &resource.Resource{RNode: *r}
|
resource := &resource.Resource{RNode: *r}
|
||||||
err = trimSopsData(resource)
|
err = maskSopsData(resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to trim sops data: %v", err)
|
t.Fatalf("unable to trim sops data: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,17 +199,31 @@ func diff(liveFile, mergedFile string, output io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func diffSopsSecret(obj, liveObject, mergedObject *unstructured.Unstructured, change *ssa.ChangeSetEntry) {
|
func diffSopsSecret(obj, liveObject, mergedObject *unstructured.Unstructured, change *ssa.ChangeSetEntry) {
|
||||||
data := obj.Object["data"]
|
// get both data and stringdata maps
|
||||||
for _, v := range data.(map[string]interface{}) {
|
data := obj.Object[dataField]
|
||||||
|
stringData := obj.Object[stringDataField]
|
||||||
|
|
||||||
|
if m, ok := data.(map[string]interface{}); ok && m != nil {
|
||||||
|
applySopsDiff(m, liveObject, mergedObject, change)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, ok := stringData.(map[string]interface{}); ok && m != nil {
|
||||||
|
applySopsDiff(m, liveObject, mergedObject, change)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applySopsDiff(data map[string]interface{}, liveObject, mergedObject *unstructured.Unstructured, change *ssa.ChangeSetEntry) {
|
||||||
|
for _, v := range data {
|
||||||
v, err := base64.StdEncoding.DecodeString(v.(string))
|
v, err := base64.StdEncoding.DecodeString(v.(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Contains(v, []byte(mask)) {
|
if bytes.Contains(v, []byte(mask)) {
|
||||||
if liveObject != nil && mergedObject != nil {
|
if liveObject != nil && mergedObject != nil {
|
||||||
change.Action = string(ssa.UnchangedAction)
|
change.Action = string(ssa.UnchangedAction)
|
||||||
dataLive := liveObject.Object["data"].(map[string]interface{})
|
dataLive := liveObject.Object[dataField].(map[string]interface{})
|
||||||
dataMerged := mergedObject.Object["data"].(map[string]interface{})
|
dataMerged := mergedObject.Object[dataField].(map[string]interface{})
|
||||||
if cmp.Diff(keys(dataLive), keys(dataMerged)) != "" {
|
if cmp.Diff(keys(dataLive), keys(dataMerged)) != "" {
|
||||||
change.Action = string(ssa.ConfiguredAction)
|
change.Action = string(ssa.ConfiguredAction)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue