Skip to content

Commit a8aaf84

Browse files
authored
Merge pull request #614 from jetstack/VC-36950-add-exclude-annots-labels
VC-36950: It is now possible to exclude labels and annotations
2 parents d2fb4f0 + 8f99daa commit a8aaf84

File tree

8 files changed

+504
-66
lines changed

8 files changed

+504
-66
lines changed

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

+16
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,22 @@ Control Plane.
423423
> ```yaml
424424
> helm.sh/release.v1
425425
> ```
426+
#### **config.excludeAnnotationKeysRegex** ~ `array`
427+
> Default value:
428+
> ```yaml
429+
> []
430+
> ```
431+
432+
You can configure Venafi Kubernetes Agent to exclude some annotations or labels from being pushed to the Venafi Control Plane. All Kubernetes objects are affected. The objects are still pushed, but the specified annotations and labels are removed before being sent to the Venafi Control Plane.
433+
434+
Dots is the only character that needs to be escaped in the regex. Use either double quotes with escaped single quotes or unquoted strings for the regex to avoid YAML parsing issues with `\.`.
435+
436+
Example: excludeAnnotationKeysRegex: ['^kapp\.k14s\.io/original.*']
437+
#### **config.excludeLabelKeysRegex** ~ `array`
438+
> Default value:
439+
> ```yaml
440+
> []
441+
> ```
426442
#### **config.configmap.name** ~ `unknown`
427443
> Default value:
428444
> ```yaml

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

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ data:
1313
cluster_description: {{ .Values.config.clusterDescription | quote }}
1414
server: {{ .Values.config.server | quote }}
1515
period: {{ .Values.config.period | quote }}
16+
exclude-annotation-keys-regex:
17+
{{ .Values.config.excludeAnnotationKeysRegex | toYaml | nindent 6 }}
18+
exclude-label-keys-regex:
19+
{{ .Values.config.excludeLabelKeysRegex | toYaml | nindent 6 }}
1620
venafi-cloud:
1721
uploader_id: "no"
1822
upload_path: "/v1/tlspk/upload/clusterdata"

deploy/charts/venafi-kubernetes-agent/values.schema.json

+17
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@
165165
"configmap": {
166166
"$ref": "#/$defs/helm-values.config.configmap"
167167
},
168+
"excludeAnnotationKeysRegex": {
169+
"$ref": "#/$defs/helm-values.config.excludeAnnotationKeysRegex"
170+
},
171+
"excludeLabelKeysRegex": {
172+
"$ref": "#/$defs/helm-values.config.excludeLabelKeysRegex"
173+
},
168174
"ignoredSecretTypes": {
169175
"$ref": "#/$defs/helm-values.config.ignoredSecretTypes"
170176
},
@@ -206,6 +212,17 @@
206212
},
207213
"helm-values.config.configmap.key": {},
208214
"helm-values.config.configmap.name": {},
215+
"helm-values.config.excludeAnnotationKeysRegex": {
216+
"default": [],
217+
"description": "You can configure Venafi Kubernetes Agent to exclude some annotations or labels from being pushed to the Venafi Control Plane. All Kubernetes objects are affected. The objects are still pushed, but the specified annotations and labels are removed before being sent to the Venafi Control Plane.\n\nDots is the only character that needs to be escaped in the regex. Use either double quotes with escaped single quotes or unquoted strings for the regex to avoid YAML parsing issues with `\\.`.\n\nExample: excludeAnnotationKeysRegex: ['^kapp\\.k14s\\.io/original.*']",
218+
"items": {},
219+
"type": "array"
220+
},
221+
"helm-values.config.excludeLabelKeysRegex": {
222+
"default": [],
223+
"items": {},
224+
"type": "array"
225+
},
209226
"helm-values.config.ignoredSecretTypes": {
210227
"items": {
211228
"$ref": "#/$defs/helm-values.config.ignoredSecretTypes[0]"

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

+21-8
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ podSecurityContext: {}
114114
securityContext:
115115
capabilities:
116116
drop:
117-
- ALL
117+
- ALL
118118
readOnlyRootFilesystem: true
119119
runAsNonRoot: true
120120

@@ -230,13 +230,26 @@ config:
230230
# * https://kubernetes.io/docs/concepts/configuration/secret/#secret-types
231231
# * https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/#list-of-supported-fields
232232
ignoredSecretTypes:
233-
- kubernetes.io/service-account-token
234-
- kubernetes.io/dockercfg
235-
- kubernetes.io/dockerconfigjson
236-
- kubernetes.io/basic-auth
237-
- kubernetes.io/ssh-auth
238-
- bootstrap.kubernetes.io/token
239-
- helm.sh/release.v1
233+
- kubernetes.io/service-account-token
234+
- kubernetes.io/dockercfg
235+
- kubernetes.io/dockerconfigjson
236+
- kubernetes.io/basic-auth
237+
- kubernetes.io/ssh-auth
238+
- bootstrap.kubernetes.io/token
239+
- helm.sh/release.v1
240+
241+
# You can configure Venafi Kubernetes Agent to exclude some annotations or
242+
# labels from being pushed to the Venafi Control Plane. All Kubernetes objects
243+
# are affected. The objects are still pushed, but the specified annotations
244+
# and labels are removed before being sent to the Venafi Control Plane.
245+
#
246+
# Dots is the only character that needs to be escaped in the regex. Use either
247+
# double quotes with escaped single quotes or unquoted strings for the regex
248+
# to avoid YAML parsing issues with `\.`.
249+
#
250+
# Example: excludeAnnotationKeysRegex: ['^kapp\.k14s\.io/original.*']
251+
excludeAnnotationKeysRegex: []
252+
excludeLabelKeysRegex: []
240253

241254
# Specify ConfigMap details to load config from an existing resource.
242255
# This should be blank by default unless you have you own config.

pkg/agent/config.go

+31-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io"
66
"net/url"
77
"os"
8+
"regexp"
89
"time"
910

1011
"github.com/go-logr/logr"
@@ -54,6 +55,12 @@ type Config struct {
5455
InputPath string `yaml:"input-path"`
5556
// For testing purposes.
5657
OutputPath string `yaml:"output-path"`
58+
59+
// Skips annotation keys that match the given set of regular expressions.
60+
// Example: ".*someprivateannotation.*".
61+
ExcludeAnnotationKeysRegex []string `yaml:"exclude-annotation-keys-regex"`
62+
// Skips label keys that match the given set of regular expressions.
63+
ExcludeLabelKeysRegex []string `yaml:"exclude-label-keys-regex"`
5764
}
5865

5966
type Endpoint struct {
@@ -339,7 +346,9 @@ type CombinedConfig struct {
339346
VenConnNS string
340347

341348
// VenafiCloudKeypair and VenafiCloudVenafiConnection modes only.
342-
DisableCompression bool
349+
DisableCompression bool
350+
ExcludeAnnotationKeysRegex []*regexp.Regexp
351+
ExcludeLabelKeysRegex []*regexp.Regexp
343352

344353
// Only used for testing purposes.
345354
OutputPath string
@@ -585,6 +594,27 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
585594
res.DisableCompression = flags.DisableCompression
586595
}
587596

597+
// Validation of the config fields exclude_annotation_keys_regex and
598+
// exclude_label_keys_regex.
599+
{
600+
for i, regex := range cfg.ExcludeAnnotationKeysRegex {
601+
r, err := regexp.Compile(regex)
602+
if err != nil {
603+
errs = multierror.Append(errs, fmt.Errorf("invalid exclude_annotation_keys_regex[%d]: %w", i, err))
604+
continue
605+
}
606+
res.ExcludeAnnotationKeysRegex = append(res.ExcludeAnnotationKeysRegex, r)
607+
}
608+
for i, regex := range cfg.ExcludeLabelKeysRegex {
609+
r, err := regexp.Compile(regex)
610+
if err != nil {
611+
errs = multierror.Append(errs, fmt.Errorf("invalid exclude_label_keys_regex[%d]: %w", i, err))
612+
continue
613+
}
614+
res.ExcludeLabelKeysRegex = append(res.ExcludeLabelKeysRegex, r)
615+
}
616+
}
617+
588618
if errs != nil {
589619
return CombinedConfig{}, nil, errs
590620
}

pkg/agent/run.go

+7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/jetstack/preflight/api"
3535
"github.com/jetstack/preflight/pkg/client"
3636
"github.com/jetstack/preflight/pkg/datagatherer"
37+
"github.com/jetstack/preflight/pkg/datagatherer/k8s"
3738
"github.com/jetstack/preflight/pkg/kubeconfig"
3839
"github.com/jetstack/preflight/pkg/logs"
3940
"github.com/jetstack/preflight/pkg/version"
@@ -176,6 +177,12 @@ func Run(cmd *cobra.Command, args []string) (returnErr error) {
176177
return fmt.Errorf("failed to instantiate %q data gatherer %q: %v", kind, dgConfig.Name, err)
177178
}
178179

180+
dynDg, isDynamicGatherer := newDg.(*k8s.DataGathererDynamic)
181+
if isDynamicGatherer {
182+
dynDg.ExcludeAnnotKeys = config.ExcludeAnnotationKeysRegex
183+
dynDg.ExcludeLabelKeys = config.ExcludeLabelKeysRegex
184+
}
185+
179186
log.V(logs.Debug).Info("Starting DataGatherer", "name", dgConfig.Name)
180187

181188
// start the data gatherers and wait for the cache sync

pkg/datagatherer/k8s/dynamic.go

+85-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package k8s
33
import (
44
"context"
55
"fmt"
6+
"regexp"
67
"strings"
78
"time"
89

@@ -260,6 +261,9 @@ type DataGathererDynamic struct {
260261
// informer watches the events around the targeted resource and updates the cache
261262
informer k8scache.SharedIndexInformer
262263
registration k8scache.ResourceEventHandlerRegistration
264+
265+
ExcludeAnnotKeys []*regexp.Regexp
266+
ExcludeLabelKeys []*regexp.Regexp
263267
}
264268

265269
// Run starts the dynamic data gatherer's informers for resource collection.
@@ -338,7 +342,7 @@ func (g *DataGathererDynamic) Fetch() (interface{}, int, error) {
338342
}
339343

340344
// Redact Secret data
341-
err := redactList(items)
345+
err := redactList(items, g.ExcludeAnnotKeys, g.ExcludeLabelKeys)
342346
if err != nil {
343347
return nil, -1, errors.WithStack(err)
344348
}
@@ -349,7 +353,7 @@ func (g *DataGathererDynamic) Fetch() (interface{}, int, error) {
349353
return list, len(items), nil
350354
}
351355

352-
func redactList(list []*api.GatheredResource) error {
356+
func redactList(list []*api.GatheredResource, excludeAnnotKeys, excludeLabelKeys []*regexp.Regexp) error {
353357
for i := range list {
354358
if item, ok := list[i].Resource.(*unstructured.Unstructured); ok {
355359
// Determine the kind of items in case this is a generic 'mixed' list.
@@ -374,6 +378,10 @@ func redactList(list []*api.GatheredResource) error {
374378

375379
// remove managedFields from all resources
376380
Redact(RedactFields, resource)
381+
382+
RemoveUnstructuredKeys(excludeAnnotKeys, resource, "metadata", "annotations")
383+
RemoveUnstructuredKeys(excludeLabelKeys, resource, "metadata", "labels")
384+
377385
continue
378386
}
379387

@@ -386,6 +394,9 @@ func redactList(list []*api.GatheredResource) error {
386394
item.GetObjectMeta().SetManagedFields(nil)
387395
delete(item.GetObjectMeta().GetAnnotations(), "kubectl.kubernetes.io/last-applied-configuration")
388396

397+
RemoveTypedKeys(excludeAnnotKeys, item.GetObjectMeta().GetAnnotations())
398+
RemoveTypedKeys(excludeLabelKeys, item.GetObjectMeta().GetLabels())
399+
389400
resource := item.(runtime.Object)
390401
gvks, _, err := scheme.Scheme.ObjectKinds(resource)
391402
if err != nil {
@@ -411,6 +422,78 @@ func redactList(list []*api.GatheredResource) error {
411422
return nil
412423
}
413424

425+
// Meant for typed clientset objects.
426+
func RemoveTypedKeys(excludeAnnotKeys []*regexp.Regexp, m map[string]string) {
427+
for key := range m {
428+
for _, excludeAnnotKey := range excludeAnnotKeys {
429+
if excludeAnnotKey.MatchString(key) {
430+
delete(m, key)
431+
}
432+
}
433+
}
434+
}
435+
436+
// Meant for unstructured clientset objects. Removes the keys from the field
437+
// given as input. For example, let's say we have the following object:
438+
//
439+
// {
440+
// "metadata": {
441+
// "annotations": {
442+
// "key1": "value1",
443+
// "key2": "value2"
444+
// }
445+
// }
446+
// }
447+
//
448+
// Then, the following call:
449+
//
450+
// RemoveUnstructuredKeys("^key1$", obj, "metadata", "annotations")
451+
//
452+
// Will result in:
453+
//
454+
// {
455+
// "metadata": {
456+
// "annotations": {"key2": "value2"}
457+
// }
458+
// }
459+
//
460+
// If the given path doesn't exist or leads to a non-map object, nothing
461+
// happens. The leaf object must either be a map[string]interface{} (that's
462+
// what's returned by the unstructured clientset) or a map[string]string (that's
463+
// what's returned by the typed clientset).
464+
func RemoveUnstructuredKeys(excludeKeys []*regexp.Regexp, obj *unstructured.Unstructured, path ...string) {
465+
annotsRaw, ok, err := unstructured.NestedFieldNoCopy(obj.Object, path...)
466+
if err != nil {
467+
return
468+
}
469+
if !ok {
470+
return
471+
}
472+
473+
// The field may be nil since yaml.Unmarshal's omitempty might not be set on
474+
// on this struct field.
475+
if annotsRaw == nil {
476+
return
477+
}
478+
479+
// The only possible type in an unstructured.Unstructured object is
480+
// map[string]interface{}. That's because the yaml.Unmarshal func is used
481+
// with an empty map[string]interface{} object, which means all nested
482+
// objects will be unmarshalled to a map[string]interface{}.
483+
annots, ok := annotsRaw.(map[string]interface{})
484+
if !ok {
485+
return
486+
}
487+
488+
for key := range annots {
489+
for _, excludeAnnotKey := range excludeKeys {
490+
if excludeAnnotKey.MatchString(key) {
491+
delete(annots, key)
492+
}
493+
}
494+
}
495+
}
496+
414497
// generateExcludedNamespacesFieldSelector creates a field selector string from
415498
// a list of namespaces to exclude.
416499
func generateExcludedNamespacesFieldSelector(excludeNamespaces []string) fields.Selector {

0 commit comments

Comments
 (0)