Skip to content

Commit eb10b70

Browse files
authoredOct 30, 2024··
feat: Add ability to hide certain annotations on secret resources (#18216)
Signed-off-by: Siddhesh Ghadi <sghadi1203@gmail.com>
1 parent 83eb0b1 commit eb10b70

File tree

10 files changed

+138
-11
lines changed

10 files changed

+138
-11
lines changed
 

‎controller/appcontroller.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ func (ctrl *ApplicationController) hideSecretData(app *appv1.Application, compar
759759
resDiff := res.Diff
760760
if res.Kind == kube.SecretKind && res.Group == "" {
761761
var err error
762-
target, live, err = diff.HideSecretData(res.Target, res.Live)
762+
target, live, err = diff.HideSecretData(res.Target, res.Live, ctrl.settingsMgr.GetSensitiveAnnotations())
763763
if err != nil {
764764
return nil, fmt.Errorf("error hiding secret data: %w", err)
765765
}

‎docs/operator-manual/argocd-cm.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ data:
222222
clusters:
223223
- "*.local"
224224
225+
# An optional comma-separated list of annotation keys to mask in UI/CLI on secrets
226+
resource.sensitive.mask.annotations: openshift.io/token-secret.value,api-key
227+
225228
# An optional comma-separated list of metadata.labels to observe in the UI.
226229
resource.customLabels: tier
227230

‎docs/operator-manual/declarative-setup.md

+8
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,14 @@ Notes:
12251225
* Invalid globs result in the whole rule being ignored.
12261226
* If you add a rule that matches existing resources, these will appear in the interface as `OutOfSync`.
12271227

1228+
## Mask sensitive Annotations on Secrets
1229+
1230+
An optional comma-separated list of `metadata.annotations` keys can be configured with `resource.sensitive.mask.annotations` to mask their values in UI/CLI on Secrets.
1231+
1232+
```yaml
1233+
resource.sensitive.mask.annotations: openshift.io/token-secret.value, api-key
1234+
```
1235+
12281236
## Auto respect RBAC for controller
12291237

12301238
Argocd controller can be restricted from discovering/syncing specific resources using just controller rbac, without having to manually configure resource exclusions.

‎go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
1111
github.com/alicebob/miniredis/v2 v2.33.0
1212
github.com/antonmedv/expr v1.15.1
13-
github.com/argoproj/gitops-engine v0.7.1-0.20241023134423-09e5225f8472
13+
github.com/argoproj/gitops-engine v0.7.1-0.20241029102952-9ab0b2ecae96
1414
github.com/argoproj/notifications-engine v0.4.1-0.20241007194503-2fef5c9049fd
1515
github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1
1616
github.com/aws/aws-sdk-go v1.55.5

‎go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ github.com/antonmedv/expr v1.15.1/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4J
8484
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
8585
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
8686
github.com/appscode/go v0.0.0-20191119085241-0887d8ec2ecc/go.mod h1:OawnOmAL4ZX3YaPdN+8HTNwBveT1jMsqP74moa9XUbE=
87-
github.com/argoproj/gitops-engine v0.7.1-0.20241023134423-09e5225f8472 h1:NSUzj5CWkOR6xrbGBT4dhZ7WsHhT/pbud+fsvQuUe7k=
88-
github.com/argoproj/gitops-engine v0.7.1-0.20241023134423-09e5225f8472/go.mod h1:b1vuwkyMUszyUK+USUJqC8vJijnQsEPNDpC+sDdDLtM=
87+
github.com/argoproj/gitops-engine v0.7.1-0.20241029102952-9ab0b2ecae96 h1:7Guh0VsAHmccy0c55XfzVMT5Y/t76N3j/O0CXk22/A4=
88+
github.com/argoproj/gitops-engine v0.7.1-0.20241029102952-9ab0b2ecae96/go.mod h1:b1vuwkyMUszyUK+USUJqC8vJijnQsEPNDpC+sDdDLtM=
8989
github.com/argoproj/notifications-engine v0.4.1-0.20241007194503-2fef5c9049fd h1:lOVVoK89j9Nd4+JYJiKAaMNYC1402C0jICROOfUPWn0=
9090
github.com/argoproj/notifications-engine v0.4.1-0.20241007194503-2fef5c9049fd/go.mod h1:N0A4sEws2soZjEpY4hgZpQS8mRIEw6otzwfkgc3g9uQ=
9191
github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 h1:qsHwwOJ21K2Ao0xPju1sNuqphyMnMYkyB3ZLoLtxWpo=

‎server/application/application.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
557557
return nil, fmt.Errorf("error unmarshaling manifest into unstructured: %w", err)
558558
}
559559
if obj.GetKind() == kube.SecretKind && obj.GroupVersionKind().Group == "" {
560-
obj, _, err = diff.HideSecretData(obj, nil)
560+
obj, _, err = diff.HideSecretData(obj, nil, s.settingsMgr.GetSensitiveAnnotations())
561561
if err != nil {
562562
return nil, fmt.Errorf("error hiding secret data: %w", err)
563563
}
@@ -684,7 +684,7 @@ func (s *Server) GetManifestsWithFiles(stream application.ApplicationService_Get
684684
return fmt.Errorf("error unmarshaling manifest into unstructured: %w", err)
685685
}
686686
if obj.GetKind() == kube.SecretKind && obj.GroupVersionKind().Group == "" {
687-
obj, _, err = diff.HideSecretData(obj, nil)
687+
obj, _, err = diff.HideSecretData(obj, nil, s.settingsMgr.GetSensitiveAnnotations())
688688
if err != nil {
689689
return fmt.Errorf("error hiding secret data: %w", err)
690690
}
@@ -1373,7 +1373,7 @@ func (s *Server) GetResource(ctx context.Context, q *application.ApplicationReso
13731373
if err != nil {
13741374
return nil, fmt.Errorf("error getting resource: %w", err)
13751375
}
1376-
obj, err = replaceSecretValues(obj)
1376+
obj, err = s.replaceSecretValues(obj)
13771377
if err != nil {
13781378
return nil, fmt.Errorf("error replacing secret values: %w", err)
13791379
}
@@ -1385,9 +1385,9 @@ func (s *Server) GetResource(ctx context.Context, q *application.ApplicationReso
13851385
return &application.ApplicationResourceResponse{Manifest: &manifest}, nil
13861386
}
13871387

1388-
func replaceSecretValues(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
1388+
func (s *Server) replaceSecretValues(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
13891389
if obj.GetKind() == kube.SecretKind && obj.GroupVersionKind().Group == "" {
1390-
_, obj, err := diff.HideSecretData(nil, obj)
1390+
_, obj, err := diff.HideSecretData(nil, obj, s.settingsMgr.GetSensitiveAnnotations())
13911391
if err != nil {
13921392
return nil, err
13931393
}
@@ -1424,7 +1424,7 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
14241424
if manifest == nil {
14251425
return nil, fmt.Errorf("failed to patch resource: manifest was nil")
14261426
}
1427-
manifest, err = replaceSecretValues(manifest)
1427+
manifest, err = s.replaceSecretValues(manifest)
14281428
if err != nil {
14291429
return nil, fmt.Errorf("error replacing secret values: %w", err)
14301430
}
@@ -2184,7 +2184,7 @@ func (s *Server) ListResourceLinks(ctx context.Context, req *application.Applica
21842184
return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err)
21852185
}
21862186

2187-
obj, err = replaceSecretValues(obj)
2187+
obj, err = s.replaceSecretValues(obj)
21882188
if err != nil {
21892189
return nil, fmt.Errorf("error replacing secret values: %w", err)
21902190
}

‎test/e2e/mask_secret_values_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package e2e
2+
3+
import (
4+
"regexp"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/argoproj/gitops-engine/pkg/health"
10+
11+
. "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
12+
. "github.com/argoproj/argo-cd/v2/test/e2e/fixture"
13+
. "github.com/argoproj/argo-cd/v2/test/e2e/fixture/app"
14+
)
15+
16+
// Values of `.data` & `.stringData“ fields in Secret resources are masked in UI/CLI
17+
// Optionally, values of `.metadata.annotations` can also be masked, if needed.
18+
func TestMaskSecretValues(t *testing.T) {
19+
sensitiveData := regexp.MustCompile(`SECRETVAL|NEWSECRETVAL|U0VDUkVUVkFM`)
20+
21+
Given(t).
22+
Path("empty-dir").
23+
When().
24+
SetParamInSettingConfigMap("resource.sensitive.mask.annotations", "token"). // hide sensitive annotation
25+
AddFile("secrets.yaml", `apiVersion: v1
26+
kind: Secret
27+
metadata:
28+
name: secret
29+
annotations:
30+
token: SECRETVAL
31+
app: test
32+
stringData:
33+
username: SECRETVAL
34+
data:
35+
password: U0VDUkVUVkFM
36+
`).
37+
CreateApp().
38+
Sync().
39+
Then().
40+
Expect(SyncStatusIs(SyncStatusCodeSynced)).
41+
Expect(HealthIs(health.HealthStatusHealthy)).
42+
// sensitive data should be masked in manifests output
43+
And(func(app *Application) {
44+
mnfs, _ := RunCli("app", "manifests", app.Name)
45+
assert.False(t, sensitiveData.MatchString(mnfs))
46+
}).
47+
When().
48+
PatchFile("secrets.yaml", `[{"op": "replace", "path": "/stringData/username", "value": "NEWSECRETVAL"}]`).
49+
PatchFile("secrets.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"token": "NEWSECRETVAL"}}]`).
50+
Refresh(RefreshTypeHard).
51+
Then().
52+
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
53+
// sensitive data should be masked in diff output
54+
And(func(app *Application) {
55+
diff, _ := RunCli("app", "diff", app.Name)
56+
assert.False(t, sensitiveData.MatchString(diff))
57+
})
58+
}

‎test/e2e/testdata/empty-dir/.gitignore

Whitespace-only changes.

‎util/settings/settings.go

+24
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ const (
461461
resourceInclusionsKey = "resource.inclusions"
462462
// resourceIgnoreResourceUpdatesEnabledKey is the key to a boolean determining whether the resourceIgnoreUpdates feature is enabled
463463
resourceIgnoreResourceUpdatesEnabledKey = "resource.ignoreResourceUpdatesEnabled"
464+
// resourceSensitiveAnnotationsKey is the key to list of annotations to mask in secret resource
465+
resourceSensitiveAnnotationsKey = "resource.sensitive.mask.annotations"
464466
// resourceCustomLabelKey is the key to a custom label to show in node info, if present
465467
resourceCustomLabelsKey = "resource.customLabels"
466468
// resourceIncludeEventLabelKeys is the key to labels to be added onto Application k8s events if present on an Application or it's AppProject. Supports wildcard.
@@ -2341,6 +2343,28 @@ func (mgr *SettingsManager) GetExcludeEventLabelKeys() []string {
23412343
return labelKeys
23422344
}
23432345

2346+
func (mgr *SettingsManager) GetSensitiveAnnotations() map[string]bool {
2347+
annotationKeys := make(map[string]bool)
2348+
2349+
argoCDCM, err := mgr.getConfigMap()
2350+
if err != nil {
2351+
log.Error(fmt.Errorf("failed getting configmap: %w", err))
2352+
return annotationKeys
2353+
}
2354+
2355+
value, ok := argoCDCM.Data[resourceSensitiveAnnotationsKey]
2356+
if !ok || value == "" {
2357+
return annotationKeys
2358+
}
2359+
2360+
value = strings.ReplaceAll(value, " ", "")
2361+
keys := strings.Split(value, ",")
2362+
for _, k := range keys {
2363+
annotationKeys[k] = true
2364+
}
2365+
return annotationKeys
2366+
}
2367+
23442368
func (mgr *SettingsManager) GetMaxWebhookPayloadSize() int64 {
23452369
argoCDCM, err := mgr.getConfigMap()
23462370
if err != nil {

‎util/settings/settings_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -1820,3 +1820,37 @@ func TestIsImpersonationEnabled(t *testing.T) {
18201820
require.NoError(t, err,
18211821
"when user enables the flag in argocd-cm config map, IsImpersonationEnabled() must not return any error")
18221822
}
1823+
1824+
func TestSettingsManager_GetHideSecretAnnotations(t *testing.T) {
1825+
tests := []struct {
1826+
name string
1827+
input string
1828+
output map[string]bool
1829+
}{
1830+
{
1831+
name: "Empty input",
1832+
input: "",
1833+
output: map[string]bool{},
1834+
},
1835+
{
1836+
name: "Comma separated data",
1837+
input: "example.com/token-secret.value,token,key",
1838+
output: map[string]bool{"example.com/token-secret.value": true, "token": true, "key": true},
1839+
},
1840+
{
1841+
name: "Comma separated data with space",
1842+
input: "example.com/token-secret.value, token, key",
1843+
output: map[string]bool{"example.com/token-secret.value": true, "token": true, "key": true},
1844+
},
1845+
}
1846+
for _, tt := range tests {
1847+
t.Run(tt.name, func(t *testing.T) {
1848+
_, settingsManager := fixtures(map[string]string{
1849+
resourceSensitiveAnnotationsKey: tt.input,
1850+
})
1851+
keys := settingsManager.GetSensitiveAnnotations()
1852+
assert.Equal(t, len(tt.output), len(keys))
1853+
assert.Equal(t, tt.output, keys)
1854+
})
1855+
}
1856+
}

0 commit comments

Comments
 (0)
Please sign in to comment.