Skip to content

[EPIC repo-guard] Harden & Consolidate repo-guard Configuration — Validation Webhooks, Defaulting & Scoping #95

Description

@onuryilmaz

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:

  1. Validating Admission Webhooks — reject invalid or dangerous resources at the API boundary
  2. Mutating Admission Webhooks (Defaulting) — auto-populate sensible defaults to reduce boilerplate YAML
  3. 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

  • spec.github must be non-empty and reference an existing Github cluster resource
  • spec.organization must be non-empty
  • spec.installationID must be a positive integer (> 0)
  • Permission fields inside defaultPublicRepositoryTeams, defaultPrivateRepositoryTeams, and defaultInternalRepositoryTeams must be one of admin | push | pull | maintain | triage
  • Cross-namespace collision: reject creation/update if another GithubOrganization in a different namespace already targets the same (spec.github, spec.organization) tuple

GithubTeam

  • spec.github, spec.organization, spec.team must be non-empty
  • spec.greenhouseTeam must be non-empty when provided
  • When spec.externalMemberProvider is set, required sub-fields must be present (e.g., endpoint for GenericExternalMemberProvider)

GithubTeamRepository

  • spec.github, spec.organization, spec.team must be non-empty
  • spec.repository must contain at least one entry
  • spec.permission must be one of admin | push | pull | maintain | triage

Github (cluster-scoped)

  • spec.webURL and spec.v3APIURL must be valid URLs
  • spec.integrationID must be a positive integer
  • spec.secret must reference a non-empty secret name

LDAPGroupProvider / ClusterLDAPGroupProvider

  • spec.host must be a valid LDAP host or ldap(s):// URL
  • spec.baseDN must be non-empty
  • spec.secret must be non-empty

GenericExternalMemberProvider / ClusterGenericExternalMemberProvider

  • spec.endpoint must be a valid HTTP/HTTPS URL
  • When spec.paginated: true, spec.totalPagesField and spec.pageParam must be set
  • spec.idField must be non-empty
  • spec.testConnectionURL (if set) must be a valid URL

Pillar 2 — Mutating Admission Webhooks (Defaulting)

Implement a MutatingAdmissionWebhook to inject intelligent defaults and reduce required boilerplate YAML.

GithubOrganization defaults

  • Default spec.defaultPublicRepositoryTeams[*].permissionpush
  • Default spec.defaultPrivateRepositoryTeams[*].permissionpush
  • Default spec.defaultInternalRepositoryTeams[*].permissionpush

GithubTeamRepository defaults

  • Default spec.permissionpush

GithubTeam defaults

  • Default spec.syncInterval (if applicable) → operator-configured global default

General

  • Inject a repo-guard.cloudoperators.dev/managed-by annotation on all resources at creation time (audit trail)

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

  • Validating webhook rejects resources that violate any rule listed in Pillar 1
  • Mutating webhook injects defaults listed in Pillar 2
  • All CRD types have the kubebuilder markers from Pillar 3 applied
  • make manifests regenerates CRDs cleanly after marker changes
  • Webhook is registered in the operator manager and the Helm chart (charts/repo-guard/)
  • Unit tests cover each validation rule and each defaulting path
  • make lint, make vet, make controller-test all pass
  • No breaking changes to existing valid CRD resources (no field renames or removals)
  • The cluster-scoped vs namespace-scoped question for GithubOrganization is explicitly decided and documented

Related Issues

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backlogReady for sprint planning; triggers project additionfeatureNew functionality or enhancementkubernetesKubernetes specific features, CRDs, and controllerssecuritySecurity features and vulnerability management

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions