Overview
This issue tracks the work to harden and consolidate the repo-guard configuration layer. It merges and supersedes the original scoping discussion and the mutating webhook proposal (see #98), producing a single actionable epic covering three pillars:
- Validating Admission Webhooks — reject invalid or dangerous resources at the API boundary
- Mutating Admission Webhooks (Defaulting) — auto-populate sensible defaults to reduce boilerplate YAML
- CRD Field Validation — add
kubebuilder validation markers so the OpenAPI schema itself enforces constraints
Context & Motivation
Currently, repo-guard CRDs accept resources with:
- Empty or malformed required fields (
spec.organization, spec.github, spec.team)
- Invalid permission values (any string is accepted instead of
admin | push | pull)
- Malformed URLs for GitHub API endpoints and external provider endpoints
- No cross-namespace collision detection (multiple
GithubOrganization resources across namespaces can manage the same GitHub org simultaneously, causing conflicting reconciliations)
- No automatic defaults, forcing every user to provide identical boilerplate
These gaps lead to silent reconciliation failures, hard-to-debug operator errors, and accidental dual-management of the same GitHub organisation.
Pillar 1 — Validating Admission Webhooks
Implement a ValidatingAdmissionWebhook for each CRD to reject bad configuration before it is persisted.
GithubOrganization
GithubTeam
GithubTeamRepository
Github (cluster-scoped)
LDAPGroupProvider / ClusterLDAPGroupProvider
GenericExternalMemberProvider / ClusterGenericExternalMemberProvider
Pillar 2 — Mutating Admission Webhooks (Defaulting)
Implement a MutatingAdmissionWebhook to inject intelligent defaults and reduce required boilerplate YAML.
GithubOrganization defaults
GithubTeamRepository defaults
GithubTeam defaults
General
Pillar 3 — CRD Field Validation Markers
Add kubebuilder validation markers to all relevant _types.go files so the generated OpenAPI schema enforces constraints independently of the webhook layer. This provides defence-in-depth and enables kubectl client-side validation.
Required markers to add
| Type |
Field |
Marker |
GithubOrganization |
spec.github |
+kubebuilder:validation:MinLength=1 |
GithubOrganization |
spec.organization |
+kubebuilder:validation:MinLength=1 |
GithubOrganization |
spec.installationID |
+kubebuilder:validation:Minimum=1 |
GithubOrganization |
defaultXxxRepositoryTeams[*].permission |
+kubebuilder:validation:Enum=admin;push;pull;maintain;triage |
GithubTeam |
spec.github, spec.organization, spec.team |
+kubebuilder:validation:MinLength=1 |
GithubTeamRepository |
spec.github, spec.organization, spec.team |
+kubebuilder:validation:MinLength=1 |
GithubTeamRepository |
spec.permission |
+kubebuilder:validation:Enum=admin;push;pull;maintain;triage |
Github |
spec.webURL, spec.v3APIURL |
+kubebuilder:validation:Pattern=^https?:// |
Github |
spec.integrationID |
+kubebuilder:validation:Minimum=1 |
GenericExternalMemberProvider |
spec.endpoint |
+kubebuilder:validation:Pattern=^https?:// |
Scoping Question: Should GithubOrganization become Cluster-Scoped?
The original issue raised this as an open question. Current assessment:
Arguments for cluster-scoped GithubOrganization:
- A GitHub organisation is a global resource; managing the same org from multiple namespaces is inherently confusing
- Cluster-scope would make the cross-namespace collision problem structurally impossible
- Aligns with how the
Github resource (which GithubOrganization depends on) is already cluster-scoped
Arguments for keeping it namespace-scoped:
- Enables multi-tenant clusters where different teams manage their own organisations in isolated namespaces
- Avoids a breaking API change to the v1 types
- Collision prevention can be achieved via a validating webhook instead
Proposal: Keep GithubOrganization namespace-scoped but enforce uniqueness of the (github, organization) tuple cluster-wide via the validating webhook (see Pillar 1). Document the intended scoping model explicitly.
This question should be explicitly decided in the refinement session before implementation begins.
Out of Scope
- Changes to reconciliation logic beyond what is required to support webhook registration
- Moving existing string-based cross-resource references to typed
ObjectReference fields (separate issue)
- RBAC field-level restrictions
Acceptance Criteria
Related Issues
Overview
This issue tracks the work to harden and consolidate the repo-guard configuration layer. It merges and supersedes the original scoping discussion and the mutating webhook proposal (see #98), producing a single actionable epic covering three pillars:
kubebuildervalidation markers so the OpenAPI schema itself enforces constraintsContext & Motivation
Currently, repo-guard CRDs accept resources with:
spec.organization,spec.github,spec.team)admin | push | pull)GithubOrganizationresources across namespaces can manage the same GitHub org simultaneously, causing conflicting reconciliations)These gaps lead to silent reconciliation failures, hard-to-debug operator errors, and accidental dual-management of the same GitHub organisation.
Pillar 1 — Validating Admission Webhooks
Implement a
ValidatingAdmissionWebhookfor each CRD to reject bad configuration before it is persisted.GithubOrganization
spec.githubmust be non-empty and reference an existingGithubcluster resourcespec.organizationmust be non-emptyspec.installationIDmust be a positive integer (> 0)defaultPublicRepositoryTeams,defaultPrivateRepositoryTeams, anddefaultInternalRepositoryTeamsmust be one ofadmin | push | pull | maintain | triageGithubOrganizationin a different namespace already targets the same(spec.github, spec.organization)tupleGithubTeam
spec.github,spec.organization,spec.teammust be non-emptyspec.greenhouseTeammust be non-empty when providedspec.externalMemberProvideris set, required sub-fields must be present (e.g.,endpointforGenericExternalMemberProvider)GithubTeamRepository
spec.github,spec.organization,spec.teammust be non-emptyspec.repositorymust contain at least one entryspec.permissionmust be one ofadmin | push | pull | maintain | triageGithub (cluster-scoped)
spec.webURLandspec.v3APIURLmust be valid URLsspec.integrationIDmust be a positive integerspec.secretmust reference a non-empty secret nameLDAPGroupProvider / ClusterLDAPGroupProvider
spec.hostmust be a valid LDAP host orldap(s)://URLspec.baseDNmust be non-emptyspec.secretmust be non-emptyGenericExternalMemberProvider / ClusterGenericExternalMemberProvider
spec.endpointmust be a valid HTTP/HTTPS URLspec.paginated: true,spec.totalPagesFieldandspec.pageParammust be setspec.idFieldmust be non-emptyspec.testConnectionURL(if set) must be a valid URLPillar 2 — Mutating Admission Webhooks (Defaulting)
Implement a
MutatingAdmissionWebhookto inject intelligent defaults and reduce required boilerplate YAML.GithubOrganization defaults
spec.defaultPublicRepositoryTeams[*].permission→pushspec.defaultPrivateRepositoryTeams[*].permission→pushspec.defaultInternalRepositoryTeams[*].permission→pushGithubTeamRepository defaults
spec.permission→pushGithubTeam defaults
spec.syncInterval(if applicable) → operator-configured global defaultGeneral
repo-guard.cloudoperators.dev/managed-byannotation on all resources at creation time (audit trail)Pillar 3 — CRD Field Validation Markers
Add
kubebuildervalidation markers to all relevant_types.gofiles so the generated OpenAPI schema enforces constraints independently of the webhook layer. This provides defence-in-depth and enableskubectlclient-side validation.Required markers to add
GithubOrganizationspec.github+kubebuilder:validation:MinLength=1GithubOrganizationspec.organization+kubebuilder:validation:MinLength=1GithubOrganizationspec.installationID+kubebuilder:validation:Minimum=1GithubOrganizationdefaultXxxRepositoryTeams[*].permission+kubebuilder:validation:Enum=admin;push;pull;maintain;triageGithubTeamspec.github,spec.organization,spec.team+kubebuilder:validation:MinLength=1GithubTeamRepositoryspec.github,spec.organization,spec.team+kubebuilder:validation:MinLength=1GithubTeamRepositoryspec.permission+kubebuilder:validation:Enum=admin;push;pull;maintain;triageGithubspec.webURL,spec.v3APIURL+kubebuilder:validation:Pattern=^https?://Githubspec.integrationID+kubebuilder:validation:Minimum=1GenericExternalMemberProviderspec.endpoint+kubebuilder:validation:Pattern=^https?://Scoping Question: Should GithubOrganization become Cluster-Scoped?
The original issue raised this as an open question. Current assessment:
Arguments for cluster-scoped
GithubOrganization:Githubresource (whichGithubOrganizationdepends on) is already cluster-scopedArguments for keeping it namespace-scoped:
Proposal: Keep
GithubOrganizationnamespace-scoped but enforce uniqueness of the(github, organization)tuple cluster-wide via the validating webhook (see Pillar 1). Document the intended scoping model explicitly.Out of Scope
ObjectReferencefields (separate issue)Acceptance Criteria
kubebuildermarkers from Pillar 3 appliedmake manifestsregenerates CRDs cleanly after marker changescharts/repo-guard/)make lint,make vet,make controller-testall passGithubOrganizationis explicitly decided and documentedRelated Issues