Skip to content

Commit 7fa7ee8

Browse files
committed
feat: add secret access scope configuration for stackit service account keys
1 parent 607be4b commit 7fa7ee8

5 files changed

Lines changed: 89 additions & 8 deletions

File tree

deploy/stackit/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ A Helm chart for stackitcloud/stackit-cert-manager-webhook
3535
| service | object | `{"port":443,"type":"ClusterIP"}` | Configuration for the webhook service. |
3636
| service.port | int | `443` | port of the service. |
3737
| service.type | string | `"ClusterIP"` | type of the service. |
38-
| stackitSaAuthentication | object | `{"enabled":false,"fileName":"sa.json","mountPath":"/var/run/secrets/stackit","secretName":"stackit-sa-authentication"}` | Configuration for the stackit service account keys. |
38+
| stackitSaAuthentication | object | `{"enabled":false,"fileName":"sa.json","mountPath":"/var/run/secrets/stackit","secretAccessScope":"webhook","secretName":"stackit-sa-authentication"}` | Configuration for the stackit service account keys. |
3939
| stackitSaAuthentication.enabled | bool | `false` | enabled flag for the stackit service account keys. |
4040
| stackitSaAuthentication.fileName | string | `"sa.json"` | key of the service account key in the secret. Which will be later be used to load in keys in the pod as well. |
4141
| stackitSaAuthentication.mountPath | string | `"/var/run/secrets/stackit"` | Path where the secret will be mounted in the pod. |
42+
| stackitSaAuthentication.secretAccessScope | string | `"webhook"` | secret access scope for auth token lookup. Use `webhook` for namespace-scoped access (recommended) or `issuer` to allow issuer-namespace secret lookup. |
4243
| stackitSaAuthentication.secretName | string | `"stackit-sa-authentication"` | secret where the service account key is stored. Should be in the same namespace as the webhook since it will be mounted into the pod. |
4344
| tolerations | list | `[]` | Tolerations for the webhook. |
4445

deploy/stackit/templates/deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ spec:
3232
env:
3333
- name: GROUP_NAME
3434
value: {{ .Values.groupName | quote }}
35+
- name: STACKIT_SECRET_ACCESS_SCOPE
36+
value: {{ .Values.stackitSaAuthentication.secretAccessScope | quote }}
3537
{{- if .Values.stackitSaAuthentication.enabled }}
3638
- name: STACKIT_SERVICE_ACCOUNT_KEY_PATH
3739
value: "{{ .Values.stackitSaAuthentication.mountPath}}/{{ .Values.stackitSaAuthentication.fileName}}"

deploy/stackit/templates/rbac.yaml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,17 @@ subjects:
9494
name: {{ .Values.certManager.serviceAccountName }}
9595
namespace: {{ .Values.certManager.namespace }}
9696
---
97+
{{- if eq (.Values.stackitSaAuthentication.secretAccessScope | default "webhook") "issuer" }}
98+
# Cluster-wide access to the webhook's authentication secret for issuer-scoped mode
9799
apiVersion: rbac.authorization.k8s.io/v1
98100
kind: ClusterRole
99101
metadata:
100102
name: {{ include "stackit-cert-manager-webhook.fullname" . }}:secret-reader
103+
labels:
104+
app: {{ include "stackit-cert-manager-webhook.name" . }}
105+
chart: {{ include "stackit-cert-manager-webhook.chart" . }}
106+
release: {{ .Release.Name }}
107+
heritage: {{ .Release.Service }}
101108
rules:
102109
- apiGroups:
103110
- ""
@@ -106,11 +113,41 @@ rules:
106113
verbs:
107114
- "get"
108115
- "watch"
116+
{{- else }}
117+
# Namespace-scoped Role for reading the webhook's authentication secret
118+
apiVersion: rbac.authorization.k8s.io/v1
119+
kind: Role
120+
metadata:
121+
name: {{ include "stackit-cert-manager-webhook.fullname" . }}:secret-reader
122+
namespace: {{ .Release.Namespace }}
123+
labels:
124+
app: {{ include "stackit-cert-manager-webhook.name" . }}
125+
chart: {{ include "stackit-cert-manager-webhook.chart" . }}
126+
release: {{ .Release.Name }}
127+
heritage: {{ .Release.Service }}
128+
rules:
129+
- apiGroups:
130+
- ""
131+
resources:
132+
- "secrets"
133+
verbs:
134+
- "get"
135+
- "watch"
136+
resourceNames:
137+
- {{ .Values.stackitSaAuthentication.secretName | default "stackit-cert-manager-webhook" }}
138+
{{- end }}
109139
---
140+
# Binding for the webhook secret reader
141+
{{- if eq (.Values.stackitSaAuthentication.secretAccessScope | default "webhook") "issuer" }}
110142
apiVersion: rbac.authorization.k8s.io/v1
111143
kind: ClusterRoleBinding
112144
metadata:
113145
name: {{ include "stackit-cert-manager-webhook.fullname" . }}:secret-reader
146+
labels:
147+
app: {{ include "stackit-cert-manager-webhook.name" . }}
148+
chart: {{ include "stackit-cert-manager-webhook.chart" . }}
149+
release: {{ .Release.Name }}
150+
heritage: {{ .Release.Service }}
114151
roleRef:
115152
apiGroup: rbac.authorization.k8s.io
116153
kind: ClusterRole
@@ -120,6 +157,27 @@ subjects:
120157
kind: ServiceAccount
121158
name: {{ include "stackit-cert-manager-webhook.fullname" . }}
122159
namespace: {{ .Release.Namespace }}
160+
{{- else }}
161+
apiVersion: rbac.authorization.k8s.io/v1
162+
kind: RoleBinding
163+
metadata:
164+
name: {{ include "stackit-cert-manager-webhook.fullname" . }}:secret-reader
165+
namespace: {{ .Release.Namespace }}
166+
labels:
167+
app: {{ include "stackit-cert-manager-webhook.name" . }}
168+
chart: {{ include "stackit-cert-manager-webhook.chart" . }}
169+
release: {{ .Release.Name }}
170+
heritage: {{ .Release.Service }}
171+
roleRef:
172+
apiGroup: rbac.authorization.k8s.io
173+
kind: Role
174+
name: {{ include "stackit-cert-manager-webhook.fullname" . }}:secret-reader
175+
subjects:
176+
- apiGroup: ""
177+
kind: ServiceAccount
178+
name: {{ include "stackit-cert-manager-webhook.fullname" . }}
179+
namespace: {{ .Release.Namespace }}
180+
{{- end }}
123181
---
124182
apiVersion: rbac.authorization.k8s.io/v1
125183
kind: ClusterRole

deploy/stackit/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ fullnameOverride: ""
3939
stackitSaAuthentication:
4040
# -- enabled flag for the stackit service account keys.
4141
enabled: false
42+
# -- controls whether the webhook secret is read only from the webhook namespace or may be read from the issuer namespace.
43+
# -- secret access scope for auth token lookup. Use "webhook" for namespace-scoped access (recommended) or "issuer" to allow issuer-namespace secret lookup.
44+
secretAccessScope: webhook
4245
# -- secret where the service account key is stored. Should be in the same namespace as the webhook since it will be mounted into the pod.
4346
secretName: stackit-sa-authentication
4447
# -- key of the service account key in the secret. Which will be later be used to load in keys in the pod as well.

internal/resolver/config.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,14 @@ type ConfigProvider interface {
1818

1919
type defaultConfigProvider struct {
2020
fileNamespaceName string
21+
secretAccessScope string
2122
}
2223

24+
const (
25+
secretAccessScopeWebhook = "webhook"
26+
secretAccessScopeIssuer = "issuer"
27+
)
28+
2329
type StackitDnsProviderConfig struct {
2430
ProjectId string `json:"projectId"`
2531
ApiBasePath string `json:"apiBasePath"`
@@ -49,11 +55,12 @@ func (d defaultConfigProvider) LoadConfig(cfgJSON *extapi.JSON) (StackitDnsProvi
4955
return cfg, err
5056
}
5157

52-
if cfg.AuthTokenSecretNamespace == "" {
58+
scope := d.secretAccessScope
59+
if scope == "" || scope == secretAccessScopeWebhook {
5360
cfg.AuthTokenSecretNamespace = webhookNamespace
5461
}
5562

56-
if err := validateSecretNamespace(cfg.AuthTokenSecretNamespace, webhookNamespace); err != nil {
63+
if err := validateSecretNamespace(cfg.AuthTokenSecretNamespace, webhookNamespace, scope); err != nil {
5764
return cfg, err
5865
}
5966

@@ -64,12 +71,21 @@ func (d defaultConfigProvider) LoadConfig(cfgJSON *extapi.JSON) (StackitDnsProvi
6471
return cfg, nil
6572
}
6673

67-
func validateSecretNamespace(configuredNamespace, webhookNamespace string) error {
68-
// Secrets must be in the same namespace as the webhook
69-
if configuredNamespace != webhookNamespace {
70-
return fmt.Errorf("authTokenSecretNamespace must be %s, got: %s", webhookNamespace, configuredNamespace)
74+
func validateSecretNamespace(configuredNamespace, webhookNamespace, accessScope string) error {
75+
switch accessScope {
76+
case "", secretAccessScopeWebhook:
77+
if configuredNamespace != webhookNamespace {
78+
return fmt.Errorf("authTokenSecretNamespace must be %s, got: %s", webhookNamespace, configuredNamespace)
79+
}
80+
return nil
81+
case secretAccessScopeIssuer:
82+
if configuredNamespace == "" {
83+
return fmt.Errorf("authTokenSecretNamespace must be specified when secretAccessScope=issuer")
84+
}
85+
return nil
86+
default:
87+
return fmt.Errorf("invalid secretAccessScope %q", accessScope)
7188
}
72-
return nil
7389
}
7490

7591
func unmarshalConfig(cfgJSON *extapi.JSON, cfg *StackitDnsProviderConfig) error {
@@ -152,5 +168,6 @@ func determineNamespace(fileNamespaceName string) (string, error) {
152168
func NewConfigProvider() ConfigProvider {
153169
return defaultConfigProvider{
154170
fileNamespaceName: "/var/run/secrets/kubernetes.io/serviceaccount/namespace",
171+
secretAccessScope: strings.ToLower(strings.TrimSpace(os.Getenv("STACKIT_SECRET_ACCESS_SCOPE"))),
155172
}
156173
}

0 commit comments

Comments
 (0)