Skip to content

Commit 803b953

Browse files
authored
Merge pull request #554 from wallrj/memory-optimization
[VC-35377] Reduce memory by excluding Helm release Secrets and some standard Secret types
2 parents 8a765ef + 72450b9 commit 803b953

File tree

7 files changed

+162
-22
lines changed

7 files changed

+162
-22
lines changed

deploy/charts/venafi-kubernetes-agent/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ If you do not have one, you can sign up for a free trial now at:
2525
| authentication.venafiConnection.name | string | `"venafi-components"` | The name of a VenafiConnection resource which contains the configuration for authenticating to Venafi. |
2626
| authentication.venafiConnection.namespace | string | `"venafi"` | The namespace of a VenafiConnection resource which contains the configuration for authenticating to Venafi. |
2727
| command | list | `[]` | Specify the command to run overriding default binary. |
28-
| config | object | `{"clientId":"","clusterDescription":"","clusterName":"","configmap":{"key":null,"name":null},"period":"0h1m0s","server":"https://api.venafi.cloud/"}` | Configuration section for the Venafi Kubernetes Agent itself |
28+
| config | object | `{"clientId":"","clusterDescription":"","clusterName":"","configmap":{"key":null,"name":null},"ignoredSecretTypes":["kubernetes.io/service-account-token","kubernetes.io/dockercfg","kubernetes.io/dockerconfigjson","kubernetes.io/basic-auth","kubernetes.io/ssh-auth","bootstrap.kubernetes.io/token","helm.sh/release.v1"],"period":"0h1m0s","server":"https://api.venafi.cloud/"}` | Configuration section for the Venafi Kubernetes Agent itself |
2929
| config.clientId | string | `""` | The client-id returned from the Venafi Control Plane |
3030
| config.clusterDescription | string | `""` | Description for the cluster resource if it needs to be created in Venafi Control Plane |
3131
| config.clusterName | string | `""` | Name for the cluster resource if it needs to be created in Venafi Control Plane |
3232
| config.configmap | object | `{"key":null,"name":null}` | Specify ConfigMap details to load config from an existing resource. This should be blank by default unless you have you own config. |
33+
| config.ignoredSecretTypes | list | `["kubernetes.io/service-account-token","kubernetes.io/dockercfg","kubernetes.io/dockerconfigjson","kubernetes.io/basic-auth","kubernetes.io/ssh-auth","bootstrap.kubernetes.io/token","helm.sh/release.v1"]` | Reduce the memory usage of the agent and reduce the load on the Kubernetes API server by omitting various common Secret types when listing Secrets. These Secret types will be added to a "type!=<type>" field selector in the agent config. * https://docs.venafi.cloud/vaas/k8s-components/t-cfg-tlspk-agent/#configuration * https://kubernetes.io/docs/concepts/configuration/secret/#secret-types * https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/#list-of-supported-fields |
3334
| config.period | string | `"0h1m0s"` | Send data back to the platform every minute unless changed |
3435
| config.server | string | `"https://api.venafi.cloud/"` | Overrides the server if using a proxy in your environment For the EU variant use: https://api.venafi.eu/ |
3536
| crds.forceRemoveValidationAnnotations | bool | `false` | The 'x-kubernetes-validations' annotation is not supported in Kubernetes 1.22 and below. This annotation is used by CEL, which is a feature introduced in Kubernetes 1.25 that improves how validation is performed. This option allows to force the 'x-kubernetes-validations' annotation to be excluded, even on Kubernetes 1.25+ clusters. |

deploy/charts/venafi-kubernetes-agent/templates/configmap.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ data:
8989
resource-type:
9090
version: v1
9191
resource: secrets
92+
{{- with .Values.config.ignoredSecretTypes }}
93+
field-selectors:
94+
{{- range . }}
95+
- type!={{ . }}
96+
{{- end }}
97+
{{- end }}
9298
- kind: "k8s-dynamic"
9399
name: "k8s/certificates"
94100
config:
@@ -202,5 +208,3 @@ data:
202208
version: v1
203209
resource: issuers
204210
{{- end }}
205-
206-

deploy/charts/venafi-kubernetes-agent/values.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,22 @@ config:
203203
# -- Description for the cluster resource if it needs to be created in Venafi Control Plane
204204
clusterDescription: ""
205205

206+
# -- Reduce the memory usage of the agent and reduce the load on the Kubernetes
207+
# API server by omitting various common Secret types when listing Secrets.
208+
# These Secret types will be added to a "type!=<type>" field selector in the
209+
# agent config.
210+
# * https://docs.venafi.cloud/vaas/k8s-components/t-cfg-tlspk-agent/#configuration
211+
# * https://kubernetes.io/docs/concepts/configuration/secret/#secret-types
212+
# * https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/#list-of-supported-fields
213+
ignoredSecretTypes:
214+
- kubernetes.io/service-account-token
215+
- kubernetes.io/dockercfg
216+
- kubernetes.io/dockerconfigjson
217+
- kubernetes.io/basic-auth
218+
- kubernetes.io/ssh-auth
219+
- bootstrap.kubernetes.io/token
220+
- helm.sh/release.v1
221+
206222
# -- Specify ConfigMap details to load config from an existing resource.
207223
# This should be blank by default unless you have you own config.
208224
configmap:

docs/datagatherers/k8s-dynamic.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ resource referenced in the `kind` for that datagatherer.
6464
There is an example `ClusterRole` and `ClusterRoleBinding` which can be found in
6565
[`./deployment/kubernetes/base/00-rbac.yaml`](./deployment/kubernetes/base/00-rbac.yaml).
6666

67-
# Secrets
67+
## Secrets
6868

6969
Secrets can be gathered using the following config:
7070

@@ -79,4 +79,30 @@ Secrets can be gathered using the following config:
7979

8080
Before Secrets are sent to the Preflight backend, they are redacted so no secret data is transmitted. See [`fieldfilter.go`](./../../pkg/datagatherer/k8s/fieldfilter.go) to see the details of which fields are filteres and which ones are redacted.
8181

82-
> **All resource other than Kubernetes Secrets are sent in full, so make sure that you don't store secret information on arbitrary resources.**
82+
> **All resource other than Kubernetes Secrets are sent in full, so make sure that you don't store secret information on arbitrary resources.**
83+
84+
85+
## Field Selectors
86+
87+
You can use [field selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/#list-of-supported-fields)
88+
to include or exclude certain resources.
89+
For example, you can reduce the memory usage of the agent and reduce the load on the Kubernetes
90+
API server by omitting various common [Secret types](https://kubernetes.io/docs/concepts/configuration/secret/#secret-types)
91+
when listing Secrets.
92+
93+
```yaml
94+
- kind: "k8s-dynamic"
95+
name: "k8s/secrets"
96+
config:
97+
resource-type:
98+
version: v1
99+
resource: secrets
100+
field-selectors:
101+
- type!=kubernetes.io/service-account-token
102+
- type!=kubernetes.io/dockercfg
103+
- type!=kubernetes.io/dockerconfigjson
104+
- type!=kubernetes.io/basic-auth
105+
- type!=kubernetes.io/ssh-auth,
106+
- type!=bootstrap.kubernetes.io/token
107+
- type!=helm.sh/release.v1
108+
```

examples/one-shot-secret.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# one-shot-secret.yaml
2+
#
3+
# An example configuration file which can be used for local testing.
4+
# It gathers only secrets and it does not attempt to upload to Venafi.
5+
# For example:
6+
#
7+
# builds/preflight agent \
8+
# --agent-config-file examples/one-shot-secret.yaml \
9+
# --one-shot \
10+
# --output-path output.json
11+
#
12+
organization_id: "my-organization"
13+
cluster_id: "my_cluster"
14+
period: 1m
15+
data-gatherers:
16+
- kind: "k8s-dynamic"
17+
name: "k8s/secrets"
18+
config:
19+
resource-type:
20+
version: v1
21+
resource: secrets
22+
field-selectors:
23+
- type!=kubernetes.io/service-account-token
24+
- type!=kubernetes.io/dockercfg
25+
- type!=kubernetes.io/dockerconfigjson
26+
- type!=kubernetes.io/basic-auth
27+
- type!=kubernetes.io/ssh-auth,
28+
- type!=bootstrap.kubernetes.io/token
29+
- type!=helm.sh/release.v1

pkg/datagatherer/k8s/dynamic.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ type ConfigDynamic struct {
3939
ExcludeNamespaces []string `yaml:"exclude-namespaces"`
4040
// IncludeNamespaces is a list of namespaces to include.
4141
IncludeNamespaces []string `yaml:"include-namespaces"`
42+
// FieldSelectors is a list of field selectors to use when listing this resource
43+
FieldSelectors []string `yaml:"field-selectors"`
4244
}
4345

4446
// UnmarshalYAML unmarshals the ConfigDynamic resolving GroupVersionResource.
@@ -52,6 +54,7 @@ func (c *ConfigDynamic) UnmarshalYAML(unmarshal func(interface{}) error) error {
5254
} `yaml:"resource-type"`
5355
ExcludeNamespaces []string `yaml:"exclude-namespaces"`
5456
IncludeNamespaces []string `yaml:"include-namespaces"`
57+
FieldSelectors []string `yaml:"field-selectors"`
5558
}{}
5659
err := unmarshal(&aux)
5760
if err != nil {
@@ -64,6 +67,7 @@ func (c *ConfigDynamic) UnmarshalYAML(unmarshal func(interface{}) error) error {
6467
c.GroupVersionResource.Resource = aux.ResourceType.Resource
6568
c.ExcludeNamespaces = aux.ExcludeNamespaces
6669
c.IncludeNamespaces = aux.IncludeNamespaces
70+
c.FieldSelectors = aux.FieldSelectors
6771

6872
return nil
6973
}
@@ -79,6 +83,16 @@ func (c *ConfigDynamic) validate() error {
7983
errors = append(errors, "invalid configuration: GroupVersionResource.Resource cannot be empty")
8084
}
8185

86+
for i, selectorString := range c.FieldSelectors {
87+
if selectorString == "" {
88+
errors = append(errors, fmt.Sprintf("invalid field selector %d: must not be empty", i))
89+
}
90+
_, err := fields.ParseSelector(selectorString)
91+
if err != nil {
92+
errors = append(errors, fmt.Sprintf("invalid field selector %d: %s", i, err))
93+
}
94+
}
95+
8296
if len(errors) > 0 {
8397
return fmt.Errorf(strings.Join(errors, ", "))
8498
}
@@ -150,7 +164,15 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami
150164
return nil, err
151165
}
152166
// init shared informer for selected namespaces
153-
fieldSelector := generateFieldSelector(c.ExcludeNamespaces)
167+
fieldSelector := generateExcludedNamespacesFieldSelector(c.ExcludeNamespaces)
168+
169+
// Add any custom field selectors to the excluded namespaces selector
170+
// The selectors have already been validated, so it is safe to use
171+
// ParseSelectorOrDie here.
172+
for _, selectorString := range c.FieldSelectors {
173+
fieldSelector = fields.AndSelectors(fieldSelector, fields.ParseSelectorOrDie(selectorString))
174+
}
175+
154176
// init cache to store gathered resources
155177
dgCache := cache.New(5*time.Minute, 30*time.Second)
156178

@@ -159,7 +181,7 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami
159181
cl: cl,
160182
k8sClientSet: clientset,
161183
groupVersionResource: c.GroupVersionResource,
162-
fieldSelector: fieldSelector,
184+
fieldSelector: fieldSelector.String(),
163185
namespaces: c.IncludeNamespaces,
164186
cache: dgCache,
165187
}
@@ -177,7 +199,7 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami
177199
60*time.Second,
178200
informers.WithNamespace(metav1.NamespaceAll),
179201
informers.WithTweakListOptions(func(options *metav1.ListOptions) {
180-
options.FieldSelector = fieldSelector
202+
options.FieldSelector = fieldSelector.String()
181203
}))
182204
newDataGatherer.nativeSharedInformer = factory
183205
informer := informerFunc(factory)
@@ -200,7 +222,7 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami
200222
cl,
201223
60*time.Second,
202224
metav1.NamespaceAll,
203-
func(options *metav1.ListOptions) { options.FieldSelector = fieldSelector },
225+
func(options *metav1.ListOptions) { options.FieldSelector = fieldSelector.String() },
204226
)
205227
resourceInformer := factory.ForResource(c.GroupVersionResource)
206228
informer := resourceInformer.Informer()
@@ -420,17 +442,17 @@ func namespaceResourceInterface(iface dynamic.NamespaceableResourceInterface, na
420442
return iface.Namespace(namespace)
421443
}
422444

423-
// generateFieldSelector creates a field selector string from a list of
424-
// namespaces to exclude.
425-
func generateFieldSelector(excludeNamespaces []string) string {
426-
fieldSelector := fields.Nothing()
445+
// generateExcludedNamespacesFieldSelector creates a field selector string from
446+
// a list of namespaces to exclude.
447+
func generateExcludedNamespacesFieldSelector(excludeNamespaces []string) fields.Selector {
448+
var selectors []fields.Selector
427449
for _, excludeNamespace := range excludeNamespaces {
428450
if excludeNamespace == "" {
429451
continue
430452
}
431-
fieldSelector = fields.AndSelectors(fields.OneTermNotEqualSelector("metadata.namespace", excludeNamespace), fieldSelector)
453+
selectors = append(selectors, fields.OneTermNotEqualSelector("metadata.namespace", excludeNamespace))
432454
}
433-
return fieldSelector.String()
455+
return fields.AndSelectors(selectors...)
434456
}
435457

436458
func isIncludedNamespace(namespace string, namespaces []string) bool {

pkg/datagatherer/k8s/dynamic_test.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ func sortGatheredResources(list []*api.GatheredResource) {
105105
func TestNewDataGathererWithClientAndDynamicInformer(t *testing.T) {
106106
ctx := context.Background()
107107
config := ConfigDynamic{
108-
IncludeNamespaces: []string{"a"},
108+
ExcludeNamespaces: []string{"kube-system"},
109109
GroupVersionResource: schema.GroupVersionResource{Group: "foobar", Version: "v1", Resource: "foos"},
110+
FieldSelectors: []string{
111+
"type!=kubernetes.io/service-account-token",
112+
"type!=kubernetes.io/dockercfg",
113+
},
110114
}
111115
cl := fake.NewSimpleDynamicClient(runtime.NewScheme())
112116
dg, err := config.newDataGathererWithClient(ctx, cl, nil)
@@ -121,7 +125,8 @@ func TestNewDataGathererWithClientAndDynamicInformer(t *testing.T) {
121125
groupVersionResource: config.GroupVersionResource,
122126
// it's important that the namespaces are set as the IncludeNamespaces
123127
// during initialization
124-
namespaces: config.IncludeNamespaces,
128+
namespaces: config.IncludeNamespaces,
129+
fieldSelector: "metadata.namespace!=kube-system,type!=kubernetes.io/service-account-token,type!=kubernetes.io/dockercfg",
125130
}
126131

127132
gatherer := dg.(*DataGathererDynamic)
@@ -150,6 +155,9 @@ func TestNewDataGathererWithClientAndDynamicInformer(t *testing.T) {
150155
if gatherer.nativeSharedInformer != nil {
151156
t.Errorf("unexpected nativeSharedInformer value: %v. should be nil", gatherer.nativeSharedInformer)
152157
}
158+
if !reflect.DeepEqual(gatherer.fieldSelector, expected.fieldSelector) {
159+
t.Errorf("expected %v, got %v", expected.fieldSelector, gatherer.fieldSelector)
160+
}
153161
}
154162

155163
func TestNewDataGathererWithClientAndSharedIndexInformer(t *testing.T) {
@@ -216,6 +224,8 @@ exclude-namespaces:
216224
# from the config file
217225
include-namespaces:
218226
- default
227+
field-selectors:
228+
- type!=kubernetes.io/service-account-token
219229
`
220230

221231
expectedGVR := schema.GroupVersionResource{
@@ -231,6 +241,10 @@ include-namespaces:
231241

232242
expectedIncludeNamespaces := []string{"default"}
233243

244+
expectedFieldSelectors := []string{
245+
"type!=kubernetes.io/service-account-token",
246+
}
247+
234248
cfg := ConfigDynamic{}
235249
err := yaml.Unmarshal([]byte(textCfg), &cfg)
236250
if err != nil {
@@ -251,6 +265,9 @@ include-namespaces:
251265
if got, want := cfg.IncludeNamespaces, expectedIncludeNamespaces; !reflect.DeepEqual(got, want) {
252266
t.Errorf("IncludeNamespaces does not match: got=%+v want=%+v", got, want)
253267
}
268+
if got, want := cfg.FieldSelectors, expectedFieldSelectors; !reflect.DeepEqual(got, want) {
269+
t.Errorf("FieldSelectors does not match: got=%+v want=%+v", got, want)
270+
}
254271
}
255272

256273
func TestConfigDynamicValidate(t *testing.T) {
@@ -275,17 +292,42 @@ func TestConfigDynamicValidate(t *testing.T) {
275292
},
276293
ExpectedError: "cannot set excluded and included namespaces",
277294
},
295+
{
296+
Config: ConfigDynamic{
297+
GroupVersionResource: schema.GroupVersionResource{
298+
Group: "",
299+
Version: "v1",
300+
Resource: "secrets",
301+
},
302+
FieldSelectors: []string{""},
303+
},
304+
ExpectedError: "invalid field selector 0: must not be empty",
305+
},
306+
{
307+
Config: ConfigDynamic{
308+
GroupVersionResource: schema.GroupVersionResource{
309+
Group: "",
310+
Version: "v1",
311+
Resource: "secrets",
312+
},
313+
FieldSelectors: []string{"foo"},
314+
},
315+
ExpectedError: "invalid field selector 0: invalid selector: 'foo'; can't understand 'foo'",
316+
},
278317
}
279318

280319
for _, test := range tests {
281320
err := test.Config.validate()
282-
if !strings.Contains(err.Error(), test.ExpectedError) {
321+
if err == nil && test.ExpectedError != "" {
322+
t.Errorf("expected error: %q, got: nil", test.ExpectedError)
323+
}
324+
if err != nil && !strings.Contains(err.Error(), test.ExpectedError) {
283325
t.Errorf("expected %s, got %s", test.ExpectedError, err.Error())
284326
}
285327
}
286328
}
287329

288-
func TestGenerateFieldSelector(t *testing.T) {
330+
func TestGenerateExcludedNamespacesFieldSelector(t *testing.T) {
289331
tests := []struct {
290332
ExcludeNamespaces []string
291333
ExpectedFieldSelector string
@@ -300,19 +342,19 @@ func TestGenerateFieldSelector(t *testing.T) {
300342
ExcludeNamespaces: []string{
301343
"kube-system",
302344
},
303-
ExpectedFieldSelector: "metadata.namespace!=kube-system,",
345+
ExpectedFieldSelector: "metadata.namespace!=kube-system",
304346
},
305347
{
306348
ExcludeNamespaces: []string{
307349
"kube-system",
308350
"my-namespace",
309351
},
310-
ExpectedFieldSelector: "metadata.namespace!=my-namespace,metadata.namespace!=kube-system,",
352+
ExpectedFieldSelector: "metadata.namespace!=kube-system,metadata.namespace!=my-namespace",
311353
},
312354
}
313355

314356
for _, test := range tests {
315-
fieldSelector := generateFieldSelector(test.ExcludeNamespaces)
357+
fieldSelector := generateExcludedNamespacesFieldSelector(test.ExcludeNamespaces).String()
316358
if fieldSelector != test.ExpectedFieldSelector {
317359
t.Errorf("ExpectedFieldSelector does not match: got=%+v want=%+v", fieldSelector, test.ExpectedFieldSelector)
318360
}

0 commit comments

Comments
 (0)