Skip to content

Commit

Permalink
feat: include or exclude namespaces to watch based on their labels
Browse files Browse the repository at this point in the history
  • Loading branch information
plaffitt committed Jan 10, 2025
1 parent 5bc2da3 commit 2fc105c
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 106 deletions.
42 changes: 25 additions & 17 deletions cmd/x509-certificate-exporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ func main() {
kubeExcludeNamespaces := stringArrayFlag{}
getopt.FlagLong(&kubeExcludeNamespaces, "exclude-namespace", 0, "removes the given kube namespace from the watch list (applied after --include-namespace)")

kubeIncludeNamespaceLabels := stringArrayFlag{}
getopt.FlagLong(&kubeIncludeNamespaceLabels, "include-namespace-label", 0, "add the kube namespaces with the given label (or label value if specified) to the watch list (when used, all namespaces are excluded by default)")

kubeExcludeNamespaceLabels := stringArrayFlag{}
getopt.FlagLong(&kubeExcludeNamespaceLabels, "exclude-namespace-label", 0, "removes the kube namespaces with the given label (or label value if specified) from the watch list (applied after --include-namespace-label)")

kubeIncludeLabels := stringArrayFlag{}
getopt.FlagLong(&kubeIncludeLabels, "include-label", 0, "add the kube secrets with the given label (or label value if specified) to the watch list (when used, all secrets are excluded by default)")

Expand Down Expand Up @@ -128,23 +134,25 @@ func main() {
}

exporter := internal.Exporter{
ListenAddress: *listenAddress,
SystemdSocket: *systemdSocket,
ConfigFile: *configFile,
Files: files,
Directories: directories,
YAMLs: yamls,
YAMLPaths: internal.DefaultYamlPaths,
TrimPathComponents: *trimPathComponents,
MaxCacheDuration: time.Duration(maxCacheDuration),
ExposeRelativeMetrics: *exposeRelativeMetrics,
ExposeErrorMetrics: *exposeErrorMetrics,
KubeSecretTypes: kubeSecretTypes,
ConfigMapKeys: kubeConfigMapKeys,
KubeIncludeNamespaces: kubeIncludeNamespaces,
KubeExcludeNamespaces: kubeExcludeNamespaces,
KubeIncludeLabels: kubeIncludeLabels,
KubeExcludeLabels: kubeExcludeLabels,
ListenAddress: *listenAddress,
SystemdSocket: *systemdSocket,
ConfigFile: *configFile,
Files: files,
Directories: directories,
YAMLs: yamls,
YAMLPaths: internal.DefaultYamlPaths,
TrimPathComponents: *trimPathComponents,
MaxCacheDuration: time.Duration(maxCacheDuration),
ExposeRelativeMetrics: *exposeRelativeMetrics,
ExposeErrorMetrics: *exposeErrorMetrics,
KubeSecretTypes: kubeSecretTypes,
ConfigMapKeys: kubeConfigMapKeys,
KubeIncludeNamespaces: kubeIncludeNamespaces,
KubeExcludeNamespaces: kubeExcludeNamespaces,
KubeIncludeNamespaceLabels: kubeIncludeNamespaceLabels,
KubeExcludeNamespaceLabels: kubeExcludeNamespaceLabels,
KubeIncludeLabels: kubeIncludeLabels,
KubeExcludeLabels: kubeExcludeLabels,
}

if getopt.Lookup("expose-labels").Seen() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ spec:
{{- range .Values.secretsExporter.excludeNamespaces }}
- --exclude-namespace={{ . | trim }}
{{- end }}
{{- range .Values.secretsExporter.includeNamespaceLabels }}
- --include-namespace-label={{ . | trim }}
{{- end }}
{{- range .Values.secretsExporter.excludeNamespaceLabels }}
- --exclude-namespace-label={{ . | trim }}
{{- end }}
{{- range .Values.secretsExporter.includeLabels }}
- --include-label={{ . | trim }}
{{- end }}
Expand Down
4 changes: 4 additions & 0 deletions deploy/charts/x509-certificate-exporter/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ secretsExporter:
includeNamespaces: []
# -- Exclude namespaces from being scanned by the TLS Secrets exporter (evaluated after `includeNamespaces`)
excludeNamespaces: []
# -- Only watch namespaces having these labels (all namespaces if empty). Items can be keys such as `my-label` or also require a value with syntax `my-label=my-value`.
includeNamespaceLabels: []
# -- Exclude namespaces having these labels. Items can be keys such as `my-label` or also require a value with syntax `my-label=my-value`.
excludeNamespaceLabels: []
# -- Only watch TLS Secrets having these labels (all secrets if empty). Items can be keys such as `my-label` or also require a value with syntax `my-label=my-value`.
includeLabels: []
# -- Exclude TLS Secrets having these labels. Items can be keys such as `my-label` or also require a value with syntax `my-label=my-value`.
Expand Down
38 changes: 20 additions & 18 deletions internal/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,26 @@ import (

// Exporter : Configuration (from command-line)
type Exporter struct {
ListenAddress string
SystemdSocket bool
ConfigFile string
Files []string
Directories []string
YAMLs []string
YAMLPaths []YAMLCertRef
TrimPathComponents int
MaxCacheDuration time.Duration
ExposeRelativeMetrics bool
ExposeErrorMetrics bool
ExposeLabels []string
ConfigMapKeys []string
KubeSecretTypes []KubeSecretType
KubeIncludeNamespaces []string
KubeExcludeNamespaces []string
KubeIncludeLabels []string
KubeExcludeLabels []string
ListenAddress string
SystemdSocket bool
ConfigFile string
Files []string
Directories []string
YAMLs []string
YAMLPaths []YAMLCertRef
TrimPathComponents int
MaxCacheDuration time.Duration
ExposeRelativeMetrics bool
ExposeErrorMetrics bool
ExposeLabels []string
ConfigMapKeys []string
KubeSecretTypes []KubeSecretType
KubeIncludeNamespaces []string
KubeExcludeNamespaces []string
KubeIncludeNamespaceLabels []string
KubeExcludeNamespaceLabels []string
KubeIncludeLabels []string
KubeExcludeLabels []string

kubeClient *kubernetes.Clientset
listener net.Listener
Expand Down
177 changes: 106 additions & 71 deletions internal/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,36 +109,64 @@ func (exporter *Exporter) parseAllKubeObjects() ([]*certificateRef, []error) {
}

func (exporter *Exporter) listNamespacesToWatch() ([]string, error) {
includedNamespaces := exporter.KubeIncludeNamespaces
allNamespaces, err := exporter.kubeClient.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, err
}

if len(includedNamespaces) < 1 {
allNamespaces, err := exporter.kubeClient.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, err
namespaces := []*v1.Namespace{}
for _, namespace := range allNamespaces.Items {
found := false
for _, includeNs := range exporter.KubeIncludeNamespaces {
if namespace.Name == includeNs {
found = true
break
}
}

for _, ns := range allNamespaces.Items {
includedNamespaces = append(includedNamespaces, ns.Name)
if len(exporter.KubeIncludeNamespaces) > 0 && !found {
continue
}
}

namespaces := []string{}
for _, includeNs := range includedNamespaces {
found := false

found = false
for _, excludeNs := range exporter.KubeExcludeNamespaces {
if includeNs == excludeNs {
if namespace.Name == excludeNs {
found = true
break
}
}

if !found {
namespaces = append(namespaces, includeNs)
namespaces = append(namespaces, &namespace)
}
}

includedLabels, includedLabelsWithValue := exporter.prepareLabelFilters(exporter.KubeIncludeNamespaceLabels)
excludedLabels, excludedLabelsWithValue := exporter.prepareLabelFilters(exporter.KubeExcludeNamespaceLabels)
namespaces = filterObjects(namespaces, includedLabels, includedLabelsWithValue, excludedLabels, excludedLabelsWithValue)

namespacesNames := []string{}
for _, namespace := range namespaces {
namespacesNames = append(namespacesNames, namespace.Name)
}

return namespacesNames, nil
}

func (exporter *Exporter) prepareLabelFilters(labels []string) ([]string, map[string]string) {
labelsWithValue := map[string]string{}
labelsWithoutValue := []string{}

for _, label := range labels {
parts := strings.Split(label, "=")
if len(parts) < 2 {
labelsWithoutValue = append(labelsWithoutValue, label)

Check failure on line 163 in internal/kubernetes.go

View workflow job for this annotation

GitHub Actions / Static Analysis

SA4010: this result of append is never used, except maybe in other appends (staticcheck)
} else {
labelsWithValue[parts[0]] = parts[1]
}
}

return namespaces, nil
return labels, labelsWithValue
}

func (exporter *Exporter) getWatchedConfigMaps(namespace string) ([]v1.ConfigMap, error) {
Expand All @@ -163,27 +191,7 @@ func (exporter *Exporter) getWatchedSecrets(namespace string) ([]v1.Secret, erro
return cachedSecrets.([]v1.Secret), nil
}

includedLabelsWithValue := map[string]string{}
includedLabelsWithoutValue := []string{}
for _, label := range exporter.KubeIncludeLabels {
parts := strings.Split(label, "=")
if len(parts) < 2 {
includedLabelsWithoutValue = append(includedLabelsWithoutValue, label)
} else {
includedLabelsWithValue[parts[0]] = parts[1]
}
}

excludedLabelsWithValue := map[string]string{}
excludedLabelsWithoutValue := []string{}
for _, label := range exporter.KubeExcludeLabels {
parts := strings.Split(label, "=")
if len(parts) < 2 {
excludedLabelsWithoutValue = append(excludedLabelsWithoutValue, label)
} else {
excludedLabelsWithValue[parts[0]] = parts[1]
}
}
_, includedLabelsWithValue := exporter.prepareLabelFilters(exporter.KubeIncludeLabels)

labelSelector := metav1.LabelSelector{MatchLabels: includedLabelsWithValue}
secrets, err := exporter.kubeClient.CoreV1().Secrets(namespace).List(context.Background(), metav1.ListOptions{
Expand All @@ -193,7 +201,7 @@ func (exporter *Exporter) getWatchedSecrets(namespace string) ([]v1.Secret, erro
return nil, err
}

filteredSecrets, err := exporter.filterSecrets(secrets.Items, includedLabelsWithoutValue, excludedLabelsWithoutValue, excludedLabelsWithValue)
filteredSecrets, err := exporter.filterSecrets(secrets.Items)
if err != nil {
return nil, err
}
Expand All @@ -209,8 +217,11 @@ func (exporter *Exporter) getWatchedSecrets(namespace string) ([]v1.Secret, erro
return shrinkedSecrets, nil
}

func (exporter *Exporter) filterSecrets(secrets []v1.Secret, includedLabels, excludedLabels []string, excludedLabelsWithValue map[string]string) ([]v1.Secret, error) {
filteredSecrets := []v1.Secret{}
func (exporter *Exporter) filterSecrets(secrets []v1.Secret) ([]v1.Secret, error) {
filteredSecrets := []*v1.Secret{}

includedLabels, _ := exporter.prepareLabelFilters(exporter.KubeIncludeLabels)
excludedLabels, excludedLabelsWithValue := exporter.prepareLabelFilters(exporter.KubeExcludeLabels)

for _, secret := range secrets {
hasIncludedType, err := exporter.checkHasIncludedType(&secret)
Expand All @@ -222,41 +233,15 @@ func (exporter *Exporter) filterSecrets(secrets []v1.Secret, includedLabels, exc
continue
}

validKeyCount := 0
for _, expectedKey := range includedLabels {
for key := range secret.GetLabels() {
if key == expectedKey {
validKeyCount++
break
}
}
}

forbiddenKeyCount := 0
for _, forbiddenKey := range excludedLabels {
for key := range secret.GetLabels() {
if key == forbiddenKey {
forbiddenKeyCount++
break
}
}
}

for forbiddenKey, forbiddenValue := range excludedLabelsWithValue {
for key, value := range secret.GetLabels() {
if key == forbiddenKey && value == forbiddenValue {
forbiddenKeyCount++
break
}
}
}
filteredSecrets = append(filteredSecrets, &secret)
}

if validKeyCount >= len(includedLabels) && forbiddenKeyCount == 0 {
filteredSecrets = append(filteredSecrets, secret)
}
filteredSecrets = filterObjects(filteredSecrets, includedLabels, map[string]string{}, excludedLabels, excludedLabelsWithValue)
for i, filteredSecret := range filteredSecrets {
secrets[i] = *filteredSecret
}

return filteredSecrets, nil
return secrets[:len(filteredSecrets)], nil
}

func (exporter *Exporter) checkHasIncludedType(secret *v1.Secret) (bool, error) {
Expand Down Expand Up @@ -334,3 +319,53 @@ func getKubeClient(config *rest.Config) (*kubernetes.Clientset, error) {

return kubeClient, nil
}

func filterObjects[T metav1.Object](objects []T, includedLabels []string, includedLabelsWithValue map[string]string, excludedLabels []string, excludedLabelsWithValue map[string]string) []T {
filteredObjects := []T{}

for _, secret := range objects {
validKeyCount := 0
for _, expectedKey := range includedLabels {
for key := range secret.GetLabels() {
if key == expectedKey {
validKeyCount++
break
}
}
}

for expectedKey, expectedValue := range includedLabelsWithValue {
for key, value := range secret.GetLabels() {
if key == expectedKey && value == expectedValue {
validKeyCount++
break
}
}
}

forbiddenKeyCount := 0
for _, forbiddenKey := range excludedLabels {
for key := range secret.GetLabels() {
if key == forbiddenKey {
forbiddenKeyCount++
break
}
}
}

for forbiddenKey, forbiddenValue := range excludedLabelsWithValue {
for key, value := range secret.GetLabels() {
if key == forbiddenKey && value == forbiddenValue {
forbiddenKeyCount++
break
}
}
}

if validKeyCount >= len(includedLabels) && forbiddenKeyCount == 0 {
filteredObjects = append(filteredObjects, secret)
}
}

return filteredObjects
}

0 comments on commit 2fc105c

Please sign in to comment.