@ -20,6 +20,7 @@ import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"sync"
"time"
@ -40,9 +41,13 @@ 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"
dataField = "data"
stringDataField = "stringData"
)
var defaultTimeout = 80 * time . Second
@ -183,7 +188,7 @@ func (b *Builder) build() (m resmap.ResMap, err error) {
}
// make sure secrets are masked
err = tri mSopsData( res )
err = mask SopsData( res )
if err != nil {
return
}
@ -257,40 +262,131 @@ func (b *Builder) setOwnerLabels(res *resource.Resource) error {
return nil
}
func tri mSopsData( res * resource . Resource ) error {
func mask SopsData( res * resource . Resource ) error {
// sopsMess is the base64 encoded mask
sopsMess := base64 . StdEncoding . EncodeToString ( [ ] byte ( mask ) )
if res . GetKind ( ) == "Secret" {
// get both data and stringdata maps as a secret can have both
dataMap := res . GetDataMap ( )
stringDataMap := getStringDataMap ( res )
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 sop s 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[" ) ) {
// 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 )
}
} els e {
for k , v := range dataMap {
data, err := base64 . StdEncoding . DecodeString ( v )
if v , ok := secretType . ( string ) ; ok && v == dockercfgSecretTyp e {
// 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[" ) ) {
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
}
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 )
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