diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e6edab..083a9a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and the project uses SemVer. ## [Unreleased] +### Security +- **Scoped Secret access**: the manager role no longer grants cluster-wide + `get/list/watch` on every Secret. The credential reconciler's Secret access is + now a namespaced `Role`/`RoleBinding` in the operator's own namespace (kustomize + + Helm), driven by a namespaced `+kubebuilder:rbac` marker. + ## [2026.06.1] - 2026-06-20 ### Changed diff --git a/config/rbac/rbac.yaml b/config/rbac/rbac.yaml index 4e7c674..e4fbe51 100644 --- a/config/rbac/rbac.yaml +++ b/config/rbac/rbac.yaml @@ -23,9 +23,6 @@ rules: - apiGroups: ["forail.forail-platform.io"] resources: ["jobtemplates/finalizers", "inventories/finalizers", "credentials/finalizers", "schedules/finalizers", "projects/finalizers", "organizations/finalizers", "teams/finalizers", "workflows/finalizers"] verbs: ["update"] - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["create", "patch"] @@ -46,3 +43,29 @@ roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: forail-operator +--- +# needtofix M12: Secret access is namespaced, not cluster-wide. The credential +# reconciler reads referenced Secrets only in the operator's namespace. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: forail-operator-secrets + namespace: forail-operator +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: forail-operator-secrets + namespace: forail-operator +subjects: + - kind: ServiceAccount + name: forail-operator + namespace: forail-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: forail-operator-secrets diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index b04ae6d..cfb6362 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -4,14 +4,6 @@ kind: ClusterRole metadata: name: manager-role rules: -- apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list - - watch - apiGroups: - forail.forail-platform.io resources: @@ -61,3 +53,20 @@ rules: - get - patch - update +--- +# needtofix M12: namespaced Secret access (generated from the namespace= rbac +# marker on CredentialReconciler). Regenerate with `make manifests`. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: manager-role + namespace: forail-operator +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch diff --git a/helm/templates/rbac.yaml b/helm/templates/rbac.yaml index 09597c8..887c133 100644 --- a/helm/templates/rbac.yaml +++ b/helm/templates/rbac.yaml @@ -15,11 +15,6 @@ rules: - apiGroups: ["forail.forail-platform.io"] resources: ["jobtemplates/finalizers", "inventories/finalizers", "credentials/finalizers", "schedules/finalizers", "projects/finalizers", "organizations/finalizers", "teams/finalizers", "workflows/finalizers"] verbs: ["update"] - # Credential reconciler reads referenced k8s Secrets for sensitive - # input fields (passwords, ssh keys, vault tokens). - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["create", "patch"] @@ -43,4 +38,37 @@ roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: {{ .Release.Name }}-forail-operator +--- +# needtofix M12: the credential reconciler reads referenced k8s Secrets for +# sensitive input fields (passwords, ssh keys, vault tokens). That access is +# a namespaced Role in the operator's own namespace — NOT a cluster-wide +# secrets grant. A multi-namespace setup adds one RoleBinding per namespace +# that holds Credential Secrets. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Release.Name }}-forail-operator-secrets + namespace: {{ .Release.Namespace }} + labels: + {{- include "forail-operator.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }}-forail-operator-secrets + namespace: {{ .Release.Namespace }} + labels: + {{- include "forail-operator.labels" . | nindent 4 }} +subjects: + - kind: ServiceAccount + name: {{ include "forail-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ .Release.Name }}-forail-operator-secrets {{- end }} diff --git a/internal/controller/credential_controller.go b/internal/controller/credential_controller.go index ffd8fa8..1988ff7 100644 --- a/internal/controller/credential_controller.go +++ b/internal/controller/credential_controller.go @@ -42,7 +42,10 @@ type CredentialReconciler struct { // +kubebuilder:rbac:groups=forail.forail-platform.io,resources=credentials,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=forail.forail-platform.io,resources=credentials/status,verbs=get;update;patch // +kubebuilder:rbac:groups=forail.forail-platform.io,resources=credentials/finalizers,verbs=update -// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch +// needtofix M12: namespaced secret access only — the reconciler must not be +// able to read every Secret in the cluster. The namespace marker makes +// controller-gen emit a namespaced Role instead of a ClusterRole rule. +// +kubebuilder:rbac:groups="",namespace=forail-operator,resources=secrets,verbs=get;list;watch func (r *CredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx)