diff --git a/api/v1/gitrepository_types.go b/api/v1/gitrepository_types.go index 590f1a38e..b5db38dcf 100644 --- a/api/v1/gitrepository_types.go +++ b/api/v1/gitrepository_types.go @@ -218,6 +218,23 @@ type GitRepositoryRef struct { Commit string `json:"commit,omitempty"` } +// CrossNamespaceObjectReference allows referencing a Secret or ConfigMap +// in another namespace. +type CrossNamespaceObjectReference struct { + // Name of the Secret or ConfigMap. + // +kubebuilder:validation:MinLength=1 + Name string `json:"name"` + + // Namespace of the Secret or ConfigMap. + // +kubebuilder:validation:MinLength=1 + Namespace string `json:"namespace"` + + // Kind of the object being referenced: Secret or ConfigMap. + // +kubebuilder:validation:Enum=Secret;ConfigMap + Kind string `json:"kind"` +} + + // GitRepositoryVerification specifies the Git commit signature verification // strategy. type GitRepositoryVerification struct { @@ -235,6 +252,11 @@ type GitRepositoryVerification struct { // authors. // +required SecretRef meta.LocalObjectReference `json:"secretRef"` + + // PublicKeyRef allows referencing a Secret or ConfigMap in another namespace + // that contains the public key(s) for verification. + // +optional + PublicKeyRef *CrossNamespaceObjectReference `json:"publicKeyRef,omitempty"` } // GitRepositoryStatus records the observed state of a Git repository. diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 0a8fb3583..a6f71c52e 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -218,6 +218,21 @@ func (in *BucketStatus) DeepCopy() *BucketStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CrossNamespaceObjectReference) DeepCopyInto(out *CrossNamespaceObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceObjectReference. +func (in *CrossNamespaceObjectReference) DeepCopy() *CrossNamespaceObjectReference { + if in == nil { + return nil + } + out := new(CrossNamespaceObjectReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GitRepository) DeepCopyInto(out *GitRepository) { *out = *in @@ -330,7 +345,7 @@ func (in *GitRepositorySpec) DeepCopyInto(out *GitRepositorySpec) { if in.Verification != nil { in, out := &in.Verification, &out.Verification *out = new(GitRepositoryVerification) - **out = **in + (*in).DeepCopyInto(*out) } if in.ProxySecretRef != nil { in, out := &in.ProxySecretRef, &out.ProxySecretRef @@ -427,6 +442,11 @@ func (in *GitRepositoryStatus) DeepCopy() *GitRepositoryStatus { func (in *GitRepositoryVerification) DeepCopyInto(out *GitRepositoryVerification) { *out = *in out.SecretRef = in.SecretRef + if in.PublicKeyRef != nil { + in, out := &in.PublicKeyRef, &out.PublicKeyRef + *out = new(CrossNamespaceObjectReference) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositoryVerification. diff --git a/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml index 10cf1162e..bab47bb01 100644 --- a/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml +++ b/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml @@ -217,6 +217,31 @@ spec: - Tag - TagAndHEAD type: string + publicKeyRef: + description: |- + PublicKeyRef allows referencing a Secret or ConfigMap in another namespace + that contains the public key(s) for verification. + properties: + kind: + description: 'Kind of the object being referenced: Secret + or ConfigMap.' + enum: + - Secret + - ConfigMap + type: string + name: + description: Name of the Secret or ConfigMap. + minLength: 1 + type: string + namespace: + description: Namespace of the Secret or ConfigMap. + minLength: 1 + type: string + required: + - kind + - name + - namespace + type: object secretRef: description: |- SecretRef specifies the Secret containing the public keys of trusted Git diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index be1010e97..482c8846e 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -7,18 +7,19 @@ rules: - apiGroups: - "" resources: - - events - verbs: - - create - - patch -- apiGroups: - - "" - resources: + - configmaps - secrets verbs: - get - list - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch - apiGroups: - "" resources: diff --git a/internal/controller/gitrepository_controller.go b/internal/controller/gitrepository_controller.go index 379bf8a1f..215b31bf3 100644 --- a/internal/controller/gitrepository_controller.go +++ b/internal/controller/gitrepository_controller.go @@ -124,6 +124,7 @@ func getPatchOptions(ownedConditions []string, controllerName string) []patch.Op // +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=gitrepositories/status,verbs=get;update;patch // +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=gitrepositories/finalizers,verbs=get;create;update;patch;delete // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch +// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch // GitRepositoryReconciler reconciles a v1.GitRepository object. type GitRepositoryReconciler struct { @@ -1073,25 +1074,66 @@ func (r *GitRepositoryReconciler) verifySignature(ctx context.Context, obj *sour return sreconcile.ResultSuccess, nil } - // Get secret with GPG data - publicKeySecret := types.NamespacedName{ - Namespace: obj.Namespace, - Name: obj.Spec.Verification.SecretRef.Name, - } - secret := &corev1.Secret{} - if err := r.Client.Get(ctx, publicKeySecret, secret); err != nil { - e := serror.NewGeneric( - fmt.Errorf("PGP public keys secret error: %w", err), - "VerificationError", - ) - conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, "%s", e) - return sreconcile.ResultEmpty, e - } + var keyRings []string + + if obj.Spec.Verification.PublicKeyRef != nil { + // new cross-namespace logic + ref := obj.Spec.Verification.PublicKeyRef + switch ref.Kind { + case "Secret": + var secret corev1.Secret + if err := r.Client.Get(ctx, types.NamespacedName{ + Namespace: ref.Namespace, + Name: ref.Name, + }, &secret); err != nil { + e := serror.NewGeneric( + fmt.Errorf("PGP public keys secret error: %w", err), + "VerificationError", + ) + conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, "%s", e) + return sreconcile.ResultEmpty, e + } + for _, v := range secret.Data { + keyRings = append(keyRings, string(v)) + } + + case "ConfigMap": + var cm corev1.ConfigMap + if err := r.Client.Get(ctx, types.NamespacedName{ + Namespace: ref.Namespace, + Name: ref.Name, + }, &cm); err != nil { + e := serror.NewGeneric( + fmt.Errorf("PGP public keys configmap error: %w", err), + "VerificationError", + ) + conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, "%s", e) + return sreconcile.ResultEmpty, e + } + for _, v := range cm.Data { + keyRings = append(keyRings, v) + } + } + } else { + // fallback to same-namespace SecretRef + publicKeySecret := types.NamespacedName{ + Namespace: obj.Namespace, + Name: obj.Spec.Verification.SecretRef.Name, + } + secret := &corev1.Secret{} + if err := r.Client.Get(ctx, publicKeySecret, secret); err != nil { + e := serror.NewGeneric( + fmt.Errorf("PGP public keys secret error: %w", err), + "VerificationError", + ) + conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, "%s", e) + return sreconcile.ResultEmpty, e + } + for _, v := range secret.Data { + keyRings = append(keyRings, string(v)) + } +} - var keyRings []string - for _, v := range secret.Data { - keyRings = append(keyRings, string(v)) - } var message strings.Builder if obj.Spec.Verification.VerifyTag() {