The Aphex Pipeline Infrastructure provides a Kubernetes-native API for repository onboarding through Custom Resource Definitions (CRDs). Users interact with the platform by creating RepoBinding resources, which trigger automated provisioning of tenant infrastructure with complete isolation and security boundaries.
The platform provides OIDC authentication for the Kubernetes API server, enabling kubectl and other Kubernetes clients to authenticate using the centralized Authentik identity provider via Dex.
API Server OIDC Settings (configured in Kind cluster):
- Issuer URL:
https://dex.home.local - Client ID:
kubernetes - Username Claim:
email - Groups Claim:
groups
Dex Client Configuration:
- Client ID:
kubernetes - Redirect URIs:
http://localhost:8000/callback,http://localhost:18000/callback - Authentication Flow: Authentik → Dex → Kubernetes API
Users authenticate to the Kubernetes API using the kubectl OIDC plugin:
# Install kubectl OIDC plugin
kubectl krew install oidc-login
# Configure OIDC authentication
kubectl oidc-login setup \
--oidc-issuer-url=https://dex.home.local \
--oidc-client-id=kubernetes
# Authenticate and access cluster
kubectl get pods --user=oidcAuthentication Flow:
- User runs kubectl command with OIDC authentication
- kubectl opens browser to Dex login page
- Dex redirects to Authentik for authentication
- User authenticates with Authentik credentials
- Authentik returns to Dex with user info and groups
- Dex issues JWT token to kubectl
- kubectl uses JWT for Kubernetes API requests
- API server validates JWT and maps groups to RBAC roles
Source: platform/bootstrap/kind-cluster-config.yaml, platform/auth/dex/configmap.yaml
The primary API for creating multi-tenant organizations with isolated namespaces, public webhook endpoints, and Cloudflare tunnel integration.
API Group: aphex.io
API Version: v1alpha1
Kind: Organization
Scope: Namespaced (must be created in platform-system)
apiVersion: aphex.io/v1alpha1
kind: Organization
metadata:
name: acme-corp
namespace: platform-system
spec:
displayName: "ACME Corporation"
adminUsers:
- admin@acme-corp.com
webhookSecret: "" # Auto-generated if emptyBehavior:
- Creates namespace:
org-acme-corp - Creates Cloudflare tunnel via API
- Creates DNS CNAME:
acme-corp.arbiter-dev.com → {tunnel-id}.cfargotunnel.com - Deploys cloudflared tunnel pod
- Creates EventListener with dedicated ServiceAccount and ClusterRoleBinding
- Creates organization admin RBAC
- Updates status with webhook URL
Status Fields:
namespace: Organization namespace namewebhookURL: Public webhook endpoint (e.g.,https://acme-corp.arbiter-dev.com)phase:Pending,Active, orFailed
Source: platform/crds/organization-crd.yaml, platform/platform-controller/controller/controllers/organization_controller.go
kubectl delete organization acme-corp -n platform-systemBehavior:
- Deletes DNS CNAME record from Cloudflare
- Cleans up tunnel connections via Cloudflare API
- Deletes tunnel from Cloudflare
- Deletes ClusterRoleBinding for EventListener
- Deletes organization namespace (cascades all resources)
Source: platform/platform-controller/controller/controllers/organization_controller.go
The primary API for onboarding repositories to the platform with automated tenant provisioning.
API Group: aphex.io
API Version: v1alpha1
Kind: RepoBinding
Scope: Namespaced (must be created in platform-system namespace)
apiVersion: aphex.io/v1alpha1
kind: RepoBinding
metadata:
name: <binding-name>
namespace: platform-system
spec:
aphexOrg: <string> # Required: Organization name (maps to org-{aphexOrg} namespace)
repoOrg: <string> # Required: GitHub organization
repoName: <string> # Required: Repository name
pipelineName: <string> # Required: Tekton Pipeline name to trigger
templateRef: <string> # Required: Dispatcher template (e.g., run-pipeline-v1)
pipelineSpec: <string> # Required: Raw YAML content of Tekton Pipeline to createField Descriptions:
| Field | Type | Required | Description |
|---|---|---|---|
aphexOrg |
string | Yes | Organization name (maps to org-{aphexOrg} namespace) |
repoOrg |
string | Yes | GitHub organization name |
repoName |
string | Yes | Repository name |
pipelineName |
string | Yes | Tekton Pipeline name to trigger |
templateRef |
string | Yes | Dispatcher template name (e.g., run-pipeline-v1) |
pipelineSpec |
string | Yes | Raw YAML content of Tekton Pipeline resource to create |
Validation Rules:
aphexOrgmust reference an existing Organization resourcepipelineSpecmust contain valid Tekton Pipeline YAML (kind: Pipeline, apiVersion: tekton.dev/v1 or tekton.dev/v1beta1)pipelineNamemust reference an existing Tekton PipelinetemplateRefmust reference an existing dispatcher template (e.g.,run-pipeline-v1)
For data model details, see data-models.md.
Source: platform/crds/repobinding-crd.yaml, platform/platform-controller/controller/controllers/repobinding_controller.go
The onboarding controller updates the status to reflect provisioning progress and provide GitHub webhook configuration details.
status:
phase: <string> # "Pending" | "Provisioning" | "Ready" | "Failed"
message: <string> # Human-readable status message
conditions: # Detailed condition tracking
- type: <string> # Condition type
status: <string> # "True" | "False" | "Unknown"
reason: <string> # Machine-readable reason
message: <string> # Human-readable message
lastTransitionTime: <timestamp> # When condition last changed
webhookConfiguration: # GitHub webhook setup information
url: <string> # Webhook URL for GitHub configuration
secret: <string> # Webhook secret for GitHub configuration
events: <[]string> # Supported webhook events
provisionedResources: # Status of provisioned resources
namespace: <boolean> # Tenant namespace created
serviceAccount: <boolean> # Service account created
rbac: <boolean> # RBAC policies created
resourceQuota: <boolean> # Resource quotas created
networkPolicy: <boolean> # Network policies created
eventListener: <boolean> # Tekton EventListener created
pipelineResources: <boolean> # Pipeline resources createdPhase Values:
Pending: RepoBinding created, validation in progressProvisioning: Controller is creating tenant resourcesReady: All resources provisioned successfully, webhook readyFailed: Provisioning failed (check conditions for details)
Condition Types:
NamespaceReady: Tenant namespace created and configuredRBACReady: Service account and RBAC policies configuredNetworkPolicyReady: Network isolation policies appliedEventListenerReady: Tekton webhook handler configuredWebhookReady: GitHub webhook configuration available
Example 1: Standard Repository Onboarding
apiVersion: aphex.io/v1alpha1
kind: RepoBinding
metadata:
name: my-app-binding
namespace: platform-system
spec:
repoOrg: "acme-corp"
repoName: "my-application"
tenantName: "my-app"
permissionProfile: "standard"Example 2: Infrastructure Repository with Elevated Permissions
apiVersion: aphex.io/v1alpha1
kind: RepoBinding
metadata:
name: infrastructure-binding
namespace: platform-system
spec:
repoOrg: "acme-corp"
repoName: "infrastructure-as-code"
tenantName: "infrastructure"
permissionProfile: "elevated"Standard Profile:
- Namespace-scoped RBAC permissions
- Standard resource quotas (CPU: 2 cores, Memory: 4Gi, Storage: 10Gi)
- Network policies allowing ingress from ingress-system only
- Access to shared pipeline catalog
- EventListener for webhook handling
Elevated Profile:
- Additional cluster-scoped read permissions for infrastructure resources
- Higher resource quotas (CPU: 4 cores, Memory: 8Gi, Storage: 20Gi)
- Additional network policy exceptions for infrastructure access
- Access to infrastructure-specific pipeline tasks
After RepoBinding reaches Ready phase, configure the GitHub webhook:
Step 1: Get Webhook Configuration
kubectl get repobinding my-app-binding -n platform-system -o yamlStep 2: Configure in GitHub Repository
- Navigate to repository Settings → Webhooks → Add webhook
- Payload URL: Use
status.webhookConfiguration.url - Content type:
application/json - Secret: Use
status.webhookConfiguration.secret - Events: Select "Push events" (or use
status.webhookConfiguration.events) - Active: ✓ Enabled
Step 3: Verify Webhook
# Check EventListener logs
kubectl logs -n <tenant-name> -l app.kubernetes.io/component=eventlistener
# Test webhook delivery in GitHub repository settingsThe API for managing Archon knowledge bases that track documentation across multiple repositories.
API Group: aphex.io
API Version: v1alpha1
Kind: KnowledgeBase
Scope: Namespaced (typically created in platform-system namespace)
apiVersion: aphex.io/v1alpha1
kind: KnowledgeBase
metadata:
name: <knowledge-base-name>
namespace: platform-system
spec:
displayName: <string> # Required: Human-readable knowledge base name
description: <string> # Optional: Context about this knowledge base
repositories: # Required: List of repositories to track (minimum 1)
- url: <string> # Required: GitHub repository URL (https://github.com/org/repo)
branch: <string> # Optional: Git branch to track (default: "main")
paths: # Optional: Documentation paths (default: [".kiro/docs"])
- <string>Field Descriptions:
| Field | Type | Required | Description |
|---|---|---|---|
displayName |
string | Yes | Human-readable knowledge base name |
description |
string | No | Optional context about this knowledge base |
repositories |
array | Yes | List of repositories to track (minimum 1 repository) |
repositories[].url |
string | Yes | GitHub repository URL (must start with https://github.com/) |
repositories[].branch |
string | No | Git branch to track (default: main) |
repositories[].paths |
array | No | Documentation paths to track (default: [".kiro/docs"]) |
Validation Rules:
displayNameis required and must be non-emptyrepositoriesarray must contain at least one repositoryrepositories[].urlmust start withhttps://github.com/repositories[].branchmust be a valid Git branch name if providedrepositories[].pathsmust start with.kiro/docsif provided
For data model details, see data-models.md.
Source: platform/crds/aphex_knowledgebases.yaml, platform/platform-controller/controller/controllers/knowledgebase_controller.go
The controller updates the status to reflect validation and tracking state.
status:
phase: <string> # "Pending" | "Ready" | "Failed"
message: <string> # Human-readable status information
lastReconcileTime: <timestamp> # ISO 8601 timestamp of last reconciliationPhase Values:
Pending: KnowledgeBase created, validation in progressReady: Specification validated, tracking repositoriesFailed: Validation failed (check message for details)
Phase Transitions:
Pending → Ready
↓
Failed
Example 1: Platform Documentation Knowledge Base
apiVersion: aphex.io/v1alpha1
kind: KnowledgeBase
metadata:
name: platform-docs
namespace: platform-system
spec:
displayName: "Platform Documentation"
description: "Archon knowledge base for platform infrastructure and tooling"
repositories:
- url: "https://github.com/bdchatham/AphexPlatformInfrastructure"
branch: "main"
paths:
- ".kiro/docs"
- url: "https://github.com/bdchatham/AphexCLI"
branch: "main"
paths:
- ".kiro/docs"
- url: "https://github.com/bdchatham/ArchonAgent"
branch: "main"
paths:
- ".kiro/docs"Example 2: Application Documentation Knowledge Base
apiVersion: aphex.io/v1alpha1
kind: KnowledgeBase
metadata:
name: app-docs
namespace: platform-system
spec:
displayName: "Application Documentation"
description: "Documentation for production applications"
repositories:
- url: "https://github.com/acme-corp/frontend-app"
branch: "main"
- url: "https://github.com/acme-corp/backend-api"
branch: "main"
- url: "https://github.com/acme-corp/data-pipeline"
branch: "develop"
paths:
- ".kiro/docs"
- ".kiro/docs/runbooks"The KnowledgeBase controller validates specifications and maintains tracking state.
Reconciliation Process:
- Validation: Validates KnowledgeBase spec (repository URLs, branch names, paths)
- Status Update: Updates phase to
Readyif validation passes,Failedif validation fails - Periodic Reconciliation: Requeues every 5 minutes to maintain tracking state
Validation Checks:
- Repository URLs must start with
https://github.com/ - Branch names must be valid Git branch names (if provided)
- Paths must start with
.kiro/docs(if provided) - Repositories array must contain at least one repository
Error Handling:
- Validation errors result in
Failedphase with descriptive error messages - Invalid specifications do not trigger requeue (user must fix the spec)
- Transient errors trigger exponential backoff retry
Source
platform/crds/aphex_knowledgebases.yaml- KnowledgeBase CRD definitionplatform/platform-controller/controller/api/v1alpha1/knowledgebase_types.go- KnowledgeBase Go typesplatform/platform-controller/controller/controllers/knowledgebase_controller.go- Controller reconciliation logic
Purpose: Enables customers to create secrets in their organization namespace and reference them across their systems.
Resource: ClusterSecretStore (cluster-scoped)
Created By: Organization controller during organization provisioning
API Version: external-secrets.io/v1
Example:
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: org-acme-corp-store
labels:
platform.aphex/organization: acme-corp
platform.aphex/managed-by: organization-controller
spec:
conditions:
- namespaceSelector:
matchLabels:
aphex.dev/org: acme-corp
provider:
kubernetes:
remoteNamespace: org-acme-corp
server:
caProvider:
type: ConfigMap
name: kube-root-ca.crt
key: ca.crt
namespace: org-acme-corp
auth:
serviceAccount:
name: eso-secrets-reader
namespace: org-acme-corpFields:
metadata.name:org-{organizationName}-storespec.conditions[].namespaceSelector: Restricts access to namespaces labeled with organizationspec.provider.kubernetes.remoteNamespace: Organization namespace where secrets are storedspec.provider.kubernetes.auth.serviceAccount: ServiceAccount with read access toorg-secrets
Namespace Selector: Only namespaces with label aphex.dev/org: {organizationName} can use this store
RBAC Resources (created per organization):
- ServiceAccount:
eso-secrets-readerin organization namespace - Role: Read access to Secret
org-secrets - RoleBinding: Binds ServiceAccount to Role
Purpose: Synchronize secrets from organization namespace to target namespaces
Resource: ExternalSecret (namespace-scoped)
Created By: Customers in their application namespaces
API Version: external-secrets.io/v1
Example (from ArchonKnowledgeBaseInfrastructure):
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: knowledge-base-secrets
namespace: archon-knowledge-base
spec:
refreshInterval: 1h
secretStoreRef:
name: org-archon-store
kind: ClusterSecretStore
target:
name: knowledge-base-secrets
creationPolicy: Owner
data:
- secretKey: github_token
remoteRef:
key: org-secrets
property: github-token
- secretKey: postgres_user
remoteRef:
key: org-secrets
property: postgres-user
- secretKey: postgres_password
remoteRef:
key: org-secrets
property: postgres-passwordFields:
spec.refreshInterval: How often to sync secrets (default: 1h)spec.secretStoreRef.name: ClusterSecretStore name (org-{organizationName}-store)spec.target.name: Name of Kubernetes Secret to createspec.target.creationPolicy:Owner(ExternalSecret owns the Secret)spec.data[]: Array of secret mappingssecretKey: Key in target SecretremoteRef.key: Source Secret name (alwaysorg-secrets)remoteRef.property: Property within source Secret
Customer Workflow:
-
Create Secret
org-secretsin organization namespace with all secrets:kubectl create secret generic org-secrets \ -n org-acme-corp \ --from-literal=github-token=ghp_xxx \ --from-literal=postgres-user=myuser \ --from-literal=postgres-password=mypass
-
Label application namespace with organization:
kubectl label namespace my-app aphex.dev/org=acme-corp
-
Create ExternalSecret in application namespace referencing ClusterSecretStore
-
External Secrets Operator syncs secrets automatically
Benefits:
- Centralized secret management per organization
- Secrets can be referenced across multiple namespaces
- No need to duplicate secrets
- Automatic synchronization and rotation support
- Namespace isolation via label selectors
Source
platform/base/external-secrets/kustomization.yaml- External Secrets Operator installationplatform/platform-controller/controller/controllers/organization_controller.go- ClusterSecretStore provisioning (provisionSecretStore)- External Secrets Operator documentation: https://external-secrets.io/
The onboarding controller watches RepoBinding resources and provisions tenant infrastructure automatically.
Reconciliation Process:
- Validation: Validates RepoBinding spec and checks Organization exists
- Namespace Creation: Creates pipeline namespace (
{pipelineName}) with proper labels - Pipeline Creation: Parses
pipelineSpecusing Kubernetes YAML decoder and creates typed Tekton Pipeline resource in pipeline namespace. The controller usestektonv1.Pipelinestructs for compile-time type safety and proper handling of Tekton's custom JSON unmarshaling (e.g.,ParamValuetypes). - RBAC Setup: Creates ServiceAccount, Role, RoleBinding, ClusterRole, and ClusterRoleBinding
- Resource Quotas: Applies resource limits (CPU, memory, pods, PVCs)
- Network Policies: Configures network isolation with egress to internet and DNS
- Terraform Secret: Creates Terraform backend configuration secret
- EventListener Update: Adds pipeline namespace to organization EventListener's namespace selector
- TriggerTemplate: Materializes dispatcher template from catalog in organization namespace
- Trigger: Creates Trigger resource in organization namespace linking webhook to pipeline
- Webhook Configuration: Updates status with webhook URL and secret
- Status Updates: Updates RepoBinding status with provisioning progress
Error Handling:
- Validation errors result in
Failedphase with descriptive error messages - Transient errors trigger exponential backoff retry
- Resource conflicts are detected and resolved automatically
- Orphaned resources are cleaned up on RepoBinding deletion
Source
platform/crds/repobinding-crd.yaml- RepoBinding CRD definitionplatform/platform-controller/controller/api/v1alpha1/repobinding_types.go- RepoBinding Go typesplatform/platform-controller/controller/controllers/repobinding_controller.go- Controller reconciliation logicplatform/platform-controller/controller/controllers/repobinding_provisioners.go- Resource provisioning functions
OIDC Authentication:
- Issuer URL:
https://dex.home.local - Client ID:
argocd - Scopes:
openid profile email groups - Group Claims: Maps Authentik groups to ArgoCD roles
Access Patterns:
- Admin Group: Full access to all ArgoCD applications and settings
- Engineering Group: Read-only access to applications, no admin functions
- Tenant-specific: Future enhancement for tenant-scoped access
OIDC Authentication:
- Issuer URL:
https://dex.home.local - Client ID:
tekton-dashboard - Scopes:
openid profile email groups - Group Claims: Maps Authentik groups to Kubernetes RBAC
Access Patterns:
- Admin Group: Full access to all pipelines and resources
- Engineering Group: Read-only access to pipeline runs and logs
- Tenant-scoped: Access limited to tenant namespace resources
Admin Access:
- URL:
https://auth.home.local - Admin User:
admin(password from bootstrap) - API Access: Token-based API for automation
User Management:
- Groups:
admins,engineeringwith different platform permissions - External Providers: GitHub OAuth, LDAP, SAML integration support
- Self-Service: Users can update profiles and passwords
Source
platform/auth/authentik/- Authentik configuration and blueprintsplatform/auth/dex/- Dex OIDC connector configurationplatform/auth/ingress/- Ingress resources for authentication services repoOrg: "your-github-org" repoName: "infrastructure-repo" tenantName: "infrastructure" permissionProfile: "elevated" ingressHost: "webhooks.example.com"
**Example 3: Check Status**
```bash
# Get RepoBinding status
kubectl get repobinding archon-binding -n platform-system
# Get detailed status
kubectl describe repobinding archon-binding -n platform-system
# Get status as YAML
kubectl get repobinding archon-binding -n platform-system -o yaml
Example Status Output:
status:
phase: Ready
message: "All resources provisioned successfully"
webhookURL: "https://webhooks.example.com/archon"
webhookSecret: "whsec_abc123xyz456"
namespaceCreated: true
serviceAccountCreated: true
rbacCreated: true
quotasCreated: true
networkPolicyCreated: true
terraformSecretCreated: true
eventListenerCreated: true
ingressCreated: trueEach tenant gets a dedicated Tekton EventListener that receives GitHub webhooks.
URL Format: https://<ingressHost>/<tenantName>
Example: https://webhooks.example.com/archon
Method: POST
Headers:
Content-Type: application/jsonX-GitHub-Event: pushX-Hub-Signature-256: sha256=<signature>
Body (GitHub Push Event):
{
"ref": "refs/heads/main",
"after": "abc123def456...",
"repository": {
"clone_url": "https://github.com/org/repo.git",
"name": "repo",
"full_name": "org/repo"
},
"pusher": {
"name": "username"
}
}Success (200 OK):
{
"eventListener": "github-listener",
"namespace": "archon",
"eventID": "abc123"
}Unauthorized (401 Unauthorized):
{
"error": "Invalid webhook signature"
}Bad Request (400 Bad Request):
{
"error": "Invalid webhook payload"
}EventListeners validate webhook signatures using HMAC-SHA256:
signature = HMAC-SHA256(secret, payload)
X-Hub-Signature-256 = "sha256=" + hex(signature)
The webhook secret is stored in a Kubernetes Secret and referenced by the EventListener.
When a RepoBinding is created, the onboarding controller provisions these Kubernetes resources:
apiVersion: v1
kind: Namespace
metadata:
name: ${TENANT_NAME}
labels:
platform.aphex/tenant: "${TENANT_NAME}"
platform.aphex/repo: "${REPO_ORG}/${REPO_NAME}"
platform.aphex/managed-by: "platform-controller"apiVersion: v1
kind: ServiceAccount
metadata:
name: pipeline-runner
namespace: ${TENANT_NAME}apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pipeline-runner
namespace: ${TENANT_NAME}
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "configmaps", "secrets"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: ["tekton.dev"]
resources: ["pipelineruns", "taskruns"]
verbs: ["get", "list", "create"]apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pipeline-runner
namespace: ${TENANT_NAME}
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "configmaps", "secrets", "services", "persistentvolumeclaims"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: ["tekton.dev"]
resources: ["pipelineruns", "taskruns", "pipelines", "tasks"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "create", "update", "delete"]apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pipeline-runner
namespace: ${TENANT_NAME}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pipeline-runner
subjects:
- kind: ServiceAccount
name: pipeline-runner
namespace: ${TENANT_NAME}apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pipeline-runner-${TENANT_NAME}
labels:
platform.aphex/tenant: "${TENANT_NAME}"
platform.aphex/managed-by: "platform-controller"
rules:
- apiGroups: ["triggers.tekton.dev"]
resources: ["clusterinterceptors", "clustertriggerbindings"]
verbs: ["get", "list", "watch"]Purpose: Grants read-only access to cluster-scoped Tekton Triggers resources. Required for EventListener pods to validate webhooks and create PipelineRuns.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: pipeline-runner-${TENANT_NAME}
labels:
platform.aphex/tenant: "${TENANT_NAME}"
platform.aphex/managed-by: "platform-controller"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: pipeline-runner-${TENANT_NAME}
subjects:
- kind: ServiceAccount
name: pipeline-runner
namespace: ${TENANT_NAME}Purpose: Binds the ClusterRole to the tenant's pipeline-runner ServiceAccount, granting cluster-scoped read permissions for Tekton Triggers resources.
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-quota
namespace: ${TENANT_NAME}
spec:
hard:
requests.cpu: "4"
requests.memory: "8Gi"
limits.cpu: "8"
limits.memory: "16Gi"
persistentvolumeclaims: "5"
pods: "20"apiVersion: v1
kind: LimitRange
metadata:
name: tenant-limits
namespace: ${TENANT_NAME}
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: tenant-isolation
namespace: ${TENANT_NAME}
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector: {}
egress:
- to:
- podSelector: {}
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- to:
- ipBlock:
cidr: 0.0.0.0/0apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: github-listener
namespace: ${TENANT_NAME}
spec:
serviceAccountName: pipeline-runner
triggers:
- name: github-push
interceptors:
- ref:
name: github
params:
- name: secretRef
value:
secretName: webhook-${TENANT_NAME}
secretKey: secret
- name: eventTypes
value:
- push
- ref:
name: cel
params:
- name: filter
value: "body.ref == 'refs/heads/main'"
bindings:
- ref: github-push-binding
template:
ref: cdktf-deploy-trigger-templateapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: github-webhook
namespace: ${TENANT_NAME}
spec:
rules:
- host: ${INGRESS_HOST}
http:
paths:
- path: /${TENANT_NAME}
pathType: Prefix
backend:
service:
name: el-github-listener
port:
number: 8080The platform uses ArgoCD Applications to manage components via GitOps.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: platform-root
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/bdchatham/AphexPlatformInfrastructure
targetRevision: main
path: platform/argocd/apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3mstatus:
sync:
status: Synced # Synced | OutOfSync | Unknown
revision: abc123def456
health:
status: Healthy # Healthy | Progressing | Degraded | Suspended | Missing | Unknown
conditions:
- type: ComparisonError
status: "False"
message: ""Purpose: Enforce logical security boundaries for customer team resources in ArgoCD.
Resource: AppProject (namespace-scoped in argocd namespace)
Created By: RepoBinding controller during pipeline provisioning
API Version: argoproj.io/v1alpha1
Example:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: my-pipeline
namespace: argocd
labels:
platform.aphex/pipeline: my-pipeline
platform.aphex/managed-by: platform-controller
spec:
description: "Project for my-pipeline pipeline"
destinations:
- server: https://kubernetes.default.svc
namespace: my-pipeline
- server: https://kubernetes.default.svc
namespace: my-pipeline-*
sourceRepos:
- https://github.com/my-org/my-repo
- https://github.com/my-org/my-repo.git
clusterResourceWhitelist: []
namespaceResourceWhitelist:
- group: '*'
kind: '*'Fields:
metadata.name: Pipeline name (matches RepoBindingpipelineName)metadata.labels["platform.aphex/pipeline"]: Pipeline identifier for cleanupspec.destinations[]: Allowed deployment targets- Exact namespace:
{pipelineName} - Wildcard namespaces:
{pipelineName}-*
- Exact namespace:
spec.sourceRepos[]: Allowed Git repositories (only the specific repo)spec.clusterResourceWhitelist: Empty (no cluster-scoped resources allowed)spec.namespaceResourceWhitelist: All resources within scoped namespaces
Security Benefits:
- Prevents cross-pipeline Application deployments
- Enforces namespace boundaries
- Restricts source repositories
- Prevents cluster-scoped resource creation
- Logical isolation for customer teams
Lifecycle:
- Created: During RepoBinding RBAC provisioning step
- Updated: If RepoBinding spec changes (repo URL, pipeline name)
- Deleted: When RepoBinding is deleted (finalizer cleanup)
Deletion Behavior: When RepoBinding is deleted, the controller:
- Deletes the AppProject
- Deletes all Applications with label
platform.aphex/pipeline: {pipelineName} - Removes finalizer from RepoBinding
Source
platform/platform-controller/controller/controllers/repobinding_provisioners.go- provisionArgoCDAppProject functionplatform/platform-controller/controller/controllers/repobinding_controller.go- Deletion logic
Invalid Namespace Pattern:
status:
phase: Failed
message: "Namespace name must match pattern ^[a-z0-9-]+$"
namespaceCreated: false
serviceAccountCreated: false
rbacCreated: false
quotasCreated: false
networkPolicyCreated: false
terraformSecretCreated: false
eventListenerCreated: false
ingressCreated: falsePrivileged Namespace:
status:
phase: Failed
message: "Cannot create namespace with privileged name"
namespaceCreated: false
serviceAccountCreated: false
rbacCreated: false
quotasCreated: false
networkPolicyCreated: false
terraformSecretCreated: false
eventListenerCreated: false
ingressCreated: falseInvalid Webhook Signature:
{
"level": "error",
"msg": "Invalid webhook signature",
"eventListener": "github-listener",
"namespace": "archon",
"timestamp": "2024-12-31T10:00:00Z"
}Invalid Event Type:
{
"level": "warn",
"msg": "Event type not supported",
"eventType": "pull_request",
"eventListener": "github-listener",
"namespace": "archon",
"timestamp": "2024-12-31T10:00:00Z"
}Branch Filter Not Matched:
{
"level": "info",
"msg": "Branch filter not matched",
"ref": "refs/heads/feature-branch",
"eventListener": "github-listener",
"namespace": "archon",
"timestamp": "2024-12-31T10:00:00Z"
}The authentication system provides OIDC-based authentication for platform services through Authentik (IdP) and Dex (OIDC connector).
Authentik provides a REST API for managing users, groups, and OIDC providers.
Internal (pod-to-pod): http://authentik.auth-system.svc.cluster.local:9000
External (browser): https://auth.home.local
All API requests require authentication via Bearer token:
Authorization: Bearer <api-token>
API tokens are created via Authentik UI or API and stored in Kubernetes Secrets.
Health Check
GET /api/v3/root/config/
Returns Authentik configuration and health status.
Response (200 OK):
{
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
"branding_title": "authentik",
"ui_footer_links": [],
"ui_theme": "automatic",
"cache_timeout": 300,
"cache_timeout_flows": 300,
"cache_timeout_policies": 300,
"cache_timeout_reputation": 300
}List Users
GET /api/v3/core/users/
Returns list of all users.
Response (200 OK):
{
"pagination": {
"next": 0,
"previous": 0,
"count": 2,
"current": 1,
"total_pages": 1,
"start_index": 1,
"end_index": 2
},
"results": [
{
"pk": 1,
"username": "admin",
"name": "Admin User",
"is_active": true,
"last_login": "2024-01-05T10:00:00Z",
"email": "admin@example.com",
"groups": ["admins"]
}
]
}List Groups
GET /api/v3/core/groups/
Returns list of all groups.
Response (200 OK):
{
"pagination": {
"next": 0,
"previous": 0,
"count": 2,
"current": 1,
"total_pages": 1,
"start_index": 1,
"end_index": 2
},
"results": [
{
"pk": "abc123",
"name": "admins",
"is_superuser": false,
"parent": null,
"users": [1],
"attributes": {}
},
{
"pk": "def456",
"name": "engineering",
"is_superuser": false,
"parent": null,
"users": [],
"attributes": {}
}
]
}List OAuth2 Providers
GET /api/v3/providers/oauth2/
Returns list of all OAuth2/OIDC providers.
Response (200 OK):
{
"pagination": {
"next": 0,
"previous": 0,
"count": 1,
"current": 1,
"total_pages": 1,
"start_index": 1,
"end_index": 1
},
"results": [
{
"pk": 1,
"name": "Dex OIDC Provider",
"authorization_flow": "abc123",
"client_type": "confidential",
"client_id": "dex-client",
"client_secret": "***",
"redirect_uris": "https://dex.home.local/callback",
"signing_key": "def456"
}
]
}Update OAuth2 Provider
PATCH /api/v3/providers/oauth2/{id}/
Content-Type: application/json
Request Body:
{
"client_secret": "new-secret-value"
}Response (200 OK):
{
"pk": 1,
"name": "Dex OIDC Provider",
"authorization_flow": "abc123",
"client_type": "confidential",
"client_id": "dex-client",
"client_secret": "***",
"redirect_uris": "https://dex.home.local/callback",
"signing_key": "def456"
}Authentik OIDC Discovery
GET /application/o/dex/.well-known/openid-configuration
Returns OIDC discovery metadata for the Dex application.
Response (200 OK):
{
"issuer": "https://auth.home.local/application/o/dex/",
"authorization_endpoint": "https://auth.home.local/application/o/authorize/",
"token_endpoint": "https://auth.home.local/application/o/token/",
"userinfo_endpoint": "https://auth.home.local/application/o/userinfo/",
"end_session_endpoint": "https://auth.home.local/application/o/dex/end-session/",
"jwks_uri": "https://auth.home.local/application/o/dex/jwks/",
"response_types_supported": ["code", "id_token", "id_token token", "code token"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "profile", "email", "groups"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
"claims_supported": ["sub", "iss", "aud", "exp", "iat", "name", "email", "groups"]
}Dex provides OIDC connector functionality between Authentik and platform services.
Internal (pod-to-pod): http://dex.auth-system.svc.cluster.local:5556
External (browser): https://dex.home.local
GET /healthz
Returns Dex health status.
Response (200 OK):
OK
GET /.well-known/openid-configuration
Returns OIDC discovery metadata for Dex.
Response (200 OK):
{
"issuer": "https://dex.home.local",
"authorization_endpoint": "https://dex.home.local/auth",
"token_endpoint": "https://dex.home.local/token",
"userinfo_endpoint": "https://dex.home.local/userinfo",
"jwks_uri": "https://dex.home.local/keys",
"response_types_supported": ["code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "profile", "email", "groups", "offline_access"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
"claims_supported": ["sub", "iss", "aud", "exp", "iat", "name", "email", "groups"]
}GET /auth?client_id=<client_id>&redirect_uri=<redirect_uri>&response_type=code&scope=<scopes>&state=<state>
Initiates OIDC authorization flow. Redirects to Authentik for authentication.
Query Parameters:
client_id: Client ID (e.g., "argocd", "tekton-dashboard")redirect_uri: Callback URL (e.g., "https://argocd.home.local/auth/callback")response_type: Must be "code"scope: Space-separated scopes (e.g., "openid profile email groups")state: Random state value for CSRF protection
Response: HTTP 302 redirect to Authentik login page
POST /token
Content-Type: application/x-www-form-urlencoded
Exchanges authorization code for access token and ID token.
Request Body:
grant_type=authorization_code
&code=<authorization_code>
&redirect_uri=<redirect_uri>
&client_id=<client_id>
&client_secret=<client_secret>
Response (200 OK):
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEyMyJ9...",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjQ1NiJ9...",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc4OSJ9..."
}GET /userinfo
Authorization: Bearer <access_token>
Returns user information for the authenticated user.
Response (200 OK):
{
"sub": "abc123",
"name": "John Doe",
"email": "john.doe@example.com",
"groups": ["admins"]
}ArgoCD is configured to use Dex as its OIDC provider.
Issuer: https://dex.home.local
Client ID: argocd
Client Secret: Stored in argocd-secret Secret
Redirect URI: https://argocd.home.local/auth/callback
Scopes: openid, profile, email, groups
ArgoCD maps OIDC groups to ArgoCD roles:
adminsgroup →role:admin(full access)engineeringgroup →role:readonly(read-only access)
- User accesses ArgoCD UI at
https://argocd.home.local - User clicks "Login via Dex"
- ArgoCD redirects to Dex authorization endpoint
- Dex redirects to Authentik login page
- User logs in to Authentik
- Authentik redirects back to Dex with authorization code
- Dex exchanges code for tokens
- Dex redirects back to ArgoCD with authorization code
- ArgoCD exchanges code for tokens
- ArgoCD validates ID token and extracts user info and groups
- User is logged in to ArgoCD with appropriate role
Tekton Dashboard is configured to use Dex as its OIDC provider.
Issuer: https://dex.home.local
Client ID: tekton-dashboard
Client Secret: Stored in tekton-dashboard-oidc Secret
Redirect URI: https://tekton.home.local/auth/callback
Scopes: openid, profile, email, groups
Tekton Dashboard uses Kubernetes RBAC for authorization:
adminsgroup → ClusterRole with full accessengineeringgroup → ClusterRole with read-only access
- User accesses Tekton Dashboard at
https://tekton.home.local - Tekton Dashboard redirects to Dex authorization endpoint
- Dex redirects to Authentik login page (if not already logged in)
- User logs in to Authentik
- Authentik redirects back to Dex with authorization code
- Dex exchanges code for tokens
- Dex redirects back to Tekton Dashboard with authorization code
- Tekton Dashboard exchanges code for tokens
- Tekton Dashboard validates ID token and extracts user info and groups
- User is logged in to Tekton Dashboard with appropriate permissions
ID tokens issued by Dex contain the following claims:
{
"iss": "https://dex.home.local",
"sub": "abc123",
"aud": "argocd",
"exp": 1704542400,
"iat": 1704456000,
"name": "John Doe",
"email": "john.doe@example.com",
"groups": ["admins"],
"email_verified": true
}Claim Descriptions:
| Claim | Type | Description |
|---|---|---|
iss |
string | Issuer (Dex URL) |
sub |
string | Subject (unique user ID) |
aud |
string | Audience (client ID) |
exp |
number | Expiration time (Unix timestamp) |
iat |
number | Issued at time (Unix timestamp) |
name |
string | User's display name |
email |
string | User's email address |
groups |
array | User's group memberships |
email_verified |
boolean | Whether email is verified |
Unauthorized (401):
{
"detail": "Authentication credentials were not provided."
}Forbidden (403):
{
"detail": "You do not have permission to perform this action."
}Not Found (404):
{
"detail": "Not found."
}Invalid Client (401):
{
"error": "invalid_client",
"error_description": "Invalid client credentials."
}Invalid Grant (400):
{
"error": "invalid_grant",
"error_description": "The provided authorization grant is invalid, expired, or revoked."
}Invalid Scope (400):
{
"error": "invalid_scope",
"error_description": "The requested scope is invalid, unknown, or malformed."
}Source
platform/auth/authentik/README.mdplatform/auth/dex/README.mdplatform/integrations/argocd-oidc-config.yamlplatform/integrations/tekton-dashboard-oidc.yamlplatform/auth/dex/configmap.yaml
The AphexCLI provides OIDC-authenticated access to platform CRDs via the Kubernetes API.
After aphex login, kubeconfig contains:
users:
- name: aphex
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubectl
args:
- oidc-login
- get-token
- --oidc-issuer-url=https://dex.home.local
- --oidc-client-id=kubernetes
contexts:
- name: aphex
context:
cluster: aphex
user: aphex| Command | Resource | Verb | Namespace | Required Group |
|---|---|---|---|---|
aphex pipeline create |
pipelines.platform.dev | create | user-, team- | platform-engineering |
aphex pipeline list |
pipelines.platform.dev | list | user-, team- | platform-engineering |
aphex pipeline get |
pipelines.platform.dev | get | user-, team- | platform-engineering |
aphex pipeline delete |
pipelines.platform.dev | delete | user-, team- | platform-operators |
Source
platform/auth/dex/configmap.yamlplatform/rbac/platform-rbac.yaml
Source
.kiro/specs/argocd-tekton-platform/design.md.kiro/specs/argocd-tekton-platform/requirements.mdplatform/crds/repobinding-crd.yamlplatform/platform-controller/controller/platform/tenancy/templates/platform/argocd/apps/