[controller,api] Specify visible secrets

This patch carries the selectors for secrets to be shown to end users
over from the legacy dashboard-resourcemap roles into the new
CozystackResourceDefinition selectors. Also a {{ .namespace }} template
variable is added to the variables supported in the `resourceNames`
field in the selector.

```release-note
[controller,api] Support {{ .namespace }} in `resourceNames` resource
selectors, add whitelist of secrets to show to end-users.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
This commit is contained in:
Timofei Larkin
2025-10-01 14:02:21 +03:00
parent c16e37e079
commit 8d50dfb73f
30 changed files with 80 additions and 43 deletions

View File

@@ -103,6 +103,7 @@ type CozystackResourceDefinitionRelease struct {
// The resourceNames field supports Go templates with the following variables available:
// - {{ .name }}: The name of the managing application (from apps.cozystack.io/application.name)
// - {{ .kind }}: The lowercased kind of the managing application (from apps.cozystack.io/application.kind)
// - {{ .namespace }}: The namespace of the resource being processed
//
// Example YAML:
// secrets:

View File

@@ -2,32 +2,35 @@ package lineagecontrollerwebhook
import (
"bytes"
"context"
"text/template"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// matchName checks if the provided name matches any of the resource names in the array.
// Each entry in resourceNames is treated as a Go template that gets rendered using the passed context.
// A nil resourceNames array matches any string.
func matchName(name string, context map[string]string, resourceNames []string) bool {
func matchName(ctx context.Context, name string, templateContext map[string]string, resourceNames []string) bool {
if resourceNames == nil {
return true
}
logger := log.FromContext(ctx)
for _, templateStr := range resourceNames {
tmpl, err := template.New("resourceName").Parse(templateStr)
if err != nil {
// TODO: emit warning if error
logger.Error(err, "failed to parse resource name template", "template", templateStr)
continue
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, context)
err = tmpl.Execute(&buf, templateContext)
if err != nil {
// TODO: emit warning if error
logger.Error(err, "failed to execute resource name template", "template", templateStr, "context", templateContext)
continue
}
@@ -39,31 +42,31 @@ func matchName(name string, context map[string]string, resourceNames []string) b
return false
}
func matchResourceToSelector(name string, ctx, l map[string]string, s *cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool {
// TODO: emit warning if error
func matchResourceToSelector(ctx context.Context, name string, templateContext, l map[string]string, s *cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool {
sel, err := metav1.LabelSelectorAsSelector(&s.LabelSelector)
if err != nil {
log.FromContext(ctx).Error(err, "failed to convert label selector to selector")
return false
}
labelMatches := sel.Matches(labels.Set(l))
nameMatches := matchName(name, ctx, s.ResourceNames)
nameMatches := matchName(ctx, name, templateContext, s.ResourceNames)
return labelMatches && nameMatches
}
func matchResourceToSelectorArray(name string, ctx, l map[string]string, ss []*cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool {
func matchResourceToSelectorArray(ctx context.Context, name string, templateContext, l map[string]string, ss []*cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool {
for _, s := range ss {
if matchResourceToSelector(name, ctx, l, s) {
if matchResourceToSelector(ctx, name, templateContext, l, s) {
return true
}
}
return false
}
func matchResourceToExcludeInclude(name string, ctx, l map[string]string, ex, in []*cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool {
if matchResourceToSelectorArray(name, ctx, l, ex) {
func matchResourceToExcludeInclude(ctx context.Context, name string, templateContext, l map[string]string, ex, in []*cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool {
if matchResourceToSelectorArray(ctx, name, templateContext, l, ex) {
return false
}
if matchResourceToSelectorArray(name, ctx, l, in) {
if matchResourceToSelectorArray(ctx, name, templateContext, l, in) {
return true
}
return false

View File

@@ -134,8 +134,9 @@ func (h *LineageControllerWebhook) computeLabels(ctx context.Context, o *unstruc
"apps.cozystack.io/application.name": obj.GetName(),
}
templateLabels := map[string]string{
"kind": strings.ToLower(obj.GetKind()),
"name": obj.GetName(),
"kind": strings.ToLower(obj.GetKind()),
"name": obj.GetName(),
"namespace": o.GetNamespace(),
}
if o.GetAPIVersion() != "v1" || o.GetKind() != "Secret" {
return labels, err
@@ -149,7 +150,7 @@ func (h *LineageControllerWebhook) computeLabels(ctx context.Context, o *unstruc
return corev1alpha1.TenantResourceLabelValue
}
return "false"
}(matchResourceToExcludeInclude(o.GetName(), templateLabels, o.GetLabels(), crd.Spec.Secrets.Exclude, crd.Spec.Secrets.Include))
}(matchResourceToExcludeInclude(ctx, o.GetName(), templateLabels, o.GetLabels(), crd.Spec.Secrets.Exclude, crd.Spec.Secrets.Include))
return labels, err
}

View File

@@ -58,6 +58,8 @@ apiVersion: v1
kind: Secret
metadata:
name: {{ $.Release.Name }}-{{ kebabcase $user }}-credentials
labels:
apps.cozystack.io/user-secret: "true"
type: Opaque
stringData:
username: {{ $user }}

View File

@@ -10,11 +10,11 @@ rules:
resources:
- secrets
resourceNames:
- {{- if eq $oidcEnabled "true" -}}
kubeconfig-{{ .Release.Namespace }}
{{- else -}}
tenant-{{ .Release.Namespace }}
{{- end }}
{{- if eq $oidcEnabled "true" }}
- kubeconfig-{{ .Release.Namespace }}
{{- else }}
- {{ .Release.Namespace }}
{{- end }}
verbs: ["get", "list", "watch"]
---
kind: RoleBinding

View File

@@ -30,4 +30,4 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "whitelistHTTP"], ["spec", "whitelist"], ["spec", "machines"]]
secrets:
exclude: []
include: [{}]
include: []

View File

@@ -31,4 +31,7 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- bucket-{{ .name }}
- bucket-{{ .name }}-credentials

View File

@@ -29,4 +29,6 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "shards"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "logStorageSize"], ["spec", "logTTL"], ["spec", "users"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "s3Region"], ["spec", "backup", "s3Bucket"], ["spec", "backup", "schedule"], ["spec", "backup", "cleanupStrategy"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "backup", "resticPassword"], ["spec", "clickhouseKeeper"], ["spec", "clickhouseKeeper", "enabled"], ["spec", "clickhouseKeeper", "size"], ["spec", "clickhouseKeeper", "resourcesPreset"], ["spec", "clickhouseKeeper", "replicas"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- clickhouse-{{ .name }}-credentials

View File

@@ -31,4 +31,4 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "size"], ["spec", "storageClass"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resources", "cpu"], ["spec", "resources", "memory"]]
secrets:
exclude: []
include: [{}]
include: []

View File

@@ -30,4 +30,6 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "quorum"], ["spec", "quorum", "minSyncReplicas"], ["spec", "quorum", "maxSyncReplicas"], ["spec", "users"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "schedule"], ["spec", "backup", "retentionPolicy"], ["spec", "backup", "endpointURL"], ["spec", "backup", "destinationPath"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "bootstrap"], ["spec", "bootstrap", "enabled"], ["spec", "bootstrap", "recoveryTime"], ["spec", "bootstrap", "oldName"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- ferretdb-{{ .name }}-credentials

View File

@@ -31,4 +31,4 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "endpoints"], ["spec", "haproxy"], ["spec", "haproxy", "replicas"], ["spec", "haproxy", "resources"], ["spec", "haproxy", "resourcesPreset"], ["spec", "nginx"], ["spec", "nginx", "replicas"], ["spec", "nginx", "resources"], ["spec", "nginx", "resourcesPreset"]]
secrets:
exclude: []
include: [{}]
include: []

View File

@@ -31,4 +31,7 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- kubeconfig-{{ .namespace }}
- "{{ .namespace }}"

View File

@@ -31,4 +31,4 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "whitelist"], ["spec", "cloudflareProxy"], ["spec", "resources"], ["spec", "resourcesPreset"]]
secrets:
exclude: []
include: [{}]
include: []

View File

@@ -30,4 +30,6 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "topics"], ["spec", "kafka"], ["spec", "kafka", "replicas"], ["spec", "kafka", "resources"], ["spec", "kafka", "resourcesPreset"], ["spec", "kafka", "size"], ["spec", "kafka", "storageClass"], ["spec", "zookeeper"], ["spec", "zookeeper", "replicas"], ["spec", "zookeeper", "resources"], ["spec", "zookeeper", "resourcesPreset"], ["spec", "zookeeper", "size"], ["spec", "zookeeper", "storageClass"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- kafka-{{ .name }}-clients-ca

View File

@@ -31,4 +31,6 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "storageClass"], ["spec", "version"], ["spec", "host"], ["spec", "nodeGroups"], ["spec", "nodeGroups", "md0"], ["spec", "nodeGroups", "md0", "minReplicas"], ["spec", "nodeGroups", "md0", "maxReplicas"], ["spec", "nodeGroups", "md0", "instanceType"], ["spec", "nodeGroups", "md0", "ephemeralStorage"], ["spec", "nodeGroups", "md0", "roles"], ["spec", "nodeGroups", "md0", "resources"], ["spec", "nodeGroups", "md0", "gpus"], ["spec", "addons"], ["spec", "addons", "certManager"], ["spec", "addons", "certManager", "enabled"], ["spec", "addons", "certManager", "valuesOverride"], ["spec", "addons", "cilium"], ["spec", "addons", "cilium", "valuesOverride"], ["spec", "addons", "gatewayAPI"], ["spec", "addons", "gatewayAPI", "enabled"], ["spec", "addons", "ingressNginx"], ["spec", "addons", "ingressNginx", "enabled"], ["spec", "addons", "ingressNginx", "exposeMethod"], ["spec", "addons", "ingressNginx", "hosts"], ["spec", "addons", "ingressNginx", "valuesOverride"], ["spec", "addons", "gpuOperator"], ["spec", "addons", "gpuOperator", "enabled"], ["spec", "addons", "gpuOperator", "valuesOverride"], ["spec", "addons", "fluxcd"], ["spec", "addons", "fluxcd", "enabled"], ["spec", "addons", "fluxcd", "valuesOverride"], ["spec", "addons", "monitoringAgents"], ["spec", "addons", "monitoringAgents", "enabled"], ["spec", "addons", "monitoringAgents", "valuesOverride"], ["spec", "addons", "verticalPodAutoscaler"], ["spec", "addons", "verticalPodAutoscaler", "valuesOverride"], ["spec", "addons", "velero"], ["spec", "addons", "velero", "enabled"], ["spec", "addons", "velero", "valuesOverride"], ["spec", "addons", "coredns"], ["spec", "addons", "coredns", "valuesOverride"], ["spec", "controlPlane"], ["spec", "controlPlane", "replicas"], ["spec", "controlPlane", "apiServer"], ["spec", "controlPlane", "apiServer", "resources"], ["spec", "controlPlane", "apiServer", "resourcesPreset"], ["spec", "controlPlane", "controllerManager"], ["spec", "controlPlane", "controllerManager", "resourcesPreset"], ["spec", "controlPlane", "controllerManager", "resources"], ["spec", "controlPlane", "scheduler"], ["spec", "controlPlane", "scheduler", "resourcesPreset"], ["spec", "controlPlane", "scheduler", "resources"], ["spec", "controlPlane", "konnectivity"], ["spec", "controlPlane", "konnectivity", "server"], ["spec", "controlPlane", "konnectivity", "server", "resourcesPreset"], ["spec", "controlPlane", "konnectivity", "server", "resources"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- kubernetes-{{ .name }}-admin-kubeconfig

View File

@@ -30,4 +30,6 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "users"], ["spec", "databases"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "s3Region"], ["spec", "backup", "s3Bucket"], ["spec", "backup", "schedule"], ["spec", "backup", "cleanupStrategy"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "backup", "resticPassword"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- mysql-{{ .name }}-credentials

View File

@@ -30,4 +30,6 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "storageClass"], ["spec", "external"], ["spec", "users"], ["spec", "jetstream"], ["spec", "jetstream", "enabled"], ["spec", "jetstream", "size"], ["spec", "config"], ["spec", "config", "merge"], ["spec", "config", "resolver"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- nats-{{ .name }}-credentials

View File

@@ -40,5 +40,4 @@ spec:
exclude: []
include:
- resourceNames:
- postgres-{{ .name }}-app
- postgres-{{ .name }}-credentials

View File

@@ -30,4 +30,8 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "users"], ["spec", "vhosts"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- rabbitmq-{{ .name }}-default-user
- matchLabels:
apps.cozystack.io/user-secret: "true"

View File

@@ -30,4 +30,6 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "authEnabled"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- redis-{{ .name }}-auth

View File

@@ -31,4 +31,4 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "topology"], ["spec", "replicationFactor"], ["spec", "db"], ["spec", "db", "replicas"], ["spec", "db", "size"], ["spec", "db", "storageClass"], ["spec", "db", "resources"], ["spec", "db", "resourcesPreset"], ["spec", "master"], ["spec", "master", "replicas"], ["spec", "master", "resources"], ["spec", "master", "resourcesPreset"], ["spec", "filer"], ["spec", "filer", "replicas"], ["spec", "filer", "resources"], ["spec", "filer", "resourcesPreset"], ["spec", "filer", "grpcHost"], ["spec", "filer", "grpcPort"], ["spec", "filer", "whitelist"], ["spec", "volume"], ["spec", "volume", "replicas"], ["spec", "volume", "size"], ["spec", "volume", "storageClass"], ["spec", "volume", "resources"], ["spec", "volume", "resourcesPreset"], ["spec", "volume", "zones"], ["spec", "s3"], ["spec", "s3", "replicas"], ["spec", "s3", "resources"], ["spec", "s3", "resourcesPreset"]]
secrets:
exclude: []
include: [{}]
include: []

View File

@@ -30,4 +30,4 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resourcesPreset"], ["spec", "external"], ["spec", "httpAndHttps"], ["spec", "httpAndHttps", "mode"], ["spec", "httpAndHttps", "targetPorts"], ["spec", "httpAndHttps", "targetPorts", "http"], ["spec", "httpAndHttps", "targetPorts", "https"], ["spec", "httpAndHttps", "endpoints"], ["spec", "whitelistHTTP"], ["spec", "whitelist"]]
secrets:
exclude: []
include: [{}]
include: []

View File

@@ -28,4 +28,4 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "etcd"], ["spec", "monitoring"], ["spec", "ingress"], ["spec", "seaweedfs"], ["spec", "isolated"], ["spec", "resourceQuotas"]]
secrets:
exclude: []
include: [{}]
include: []

View File

@@ -31,4 +31,4 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "running"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "systemDisk"], ["spec", "systemDisk", "image"], ["spec", "systemDisk", "storage"], ["spec", "systemDisk", "storageClass"], ["spec", "gpus"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]]
secrets:
exclude: []
include: [{}]
include: []

View File

@@ -31,4 +31,4 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "source"], ["spec", "optical"], ["spec", "storage"], ["spec", "storageClass"]]
secrets:
exclude: []
include: [{}]
include: []

View File

@@ -31,4 +31,4 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "running"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "gpus"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]]
secrets:
exclude: []
include: [{}]
include: []

View File

@@ -30,4 +30,6 @@ spec:
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "external"], ["spec", "host"], ["spec", "users"], ["spec", "externalIPs"]]
secrets:
exclude: []
include: [{}]
include:
- resourceNames:
- vpn-{{ .name }}-urls

View File

@@ -188,6 +188,7 @@ spec:
The resourceNames field supports Go templates with the following variables available:
- {{ .name }}: The name of the managing application (from apps.cozystack.io/application.name)
- {{ .kind }}: The lowercased kind of the managing application (from apps.cozystack.io/application.kind)
- {{ .namespace }}: The namespace of the resource being processed
Example YAML:
secrets:
@@ -269,6 +270,7 @@ spec:
The resourceNames field supports Go templates with the following variables available:
- {{ .name }}: The name of the managing application (from apps.cozystack.io/application.name)
- {{ .kind }}: The lowercased kind of the managing application (from apps.cozystack.io/application.kind)
- {{ .namespace }}: The namespace of the resource being processed
Example YAML:
secrets:

View File

@@ -45,6 +45,8 @@ data:
---
{{- if .Capabilities.APIVersions.Has "v1.edp.epam.com/v1" }}
---
apiVersion: v1.edp.epam.com/v1
kind: KeycloakClient
metadata:
@@ -71,3 +73,4 @@ spec:
{{- range $i, $v := $extraRedirectUris }}
- "{{ $v }}"
{{- end }}
{{- end }}

View File

@@ -46,7 +46,7 @@ func TestWalkingOwnershipGraph(t *testing.T) {
if err != nil {
t.Fatal(err)
}
nodes := WalkOwnershipGraph(ctx, dynClient, mapper, obj)
nodes := WalkOwnershipGraph(ctx, dynClient, mapper, &stubMapper{}, obj)
for _, node := range nodes {
fmt.Printf("%#v\n", node)
}