README
ΒΆ
KubeUser
Lightweight Kubernetes-native user management operator that simplifies user authentication and authorization through declarative custom resources.
Overview
KubeUser automates Kubernetes user management through declarative custom resources. It handles certificate generation, RBAC binding, and kubeconfig creation automatically.
Why KubeUser?
Kubernetes-native user management - no certificate handling , no Keycloak required.
Architecture Overview
KubeUser follows the standard Kubernetes operator pattern:
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β User CRD βββββΆβ User Controller βββββΆβ RBAC Resources β
β (Custom Res.) β β (Reconciler) β β (Roles/Bindings)β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Certificate & β
β Kubeconfig Gen β
βββββββββββββββββββ
Quick installation
# Install cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.2/cert-manager.yaml
# Wait for cert-manager to be ready
kubectl wait --for=condition=ready pod -l app=cert-manager -n cert-manager --timeout=60s
helm repo add kubeuser https://openkube-hub.github.io/KubeUser
export KUBERNETES_API_SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
# Install with automatic namespace creation (recommended)
helm install kubeuser kubeuser/kubeuser --create-namespace -n kubeuser \
--set env.KUBERNETES_API_SERVER="$KUBERNETES_API_SERVER"
# verify installation
kubectl get pods -n kubeuser
Important: The controller requires a namespace for storing user certificates. Use --create-namespace or install into an existing namespace. The controller will NOT automatically create namespaces for GitOps compatibility.
Security Considerations
Deleting a User does NOT invalidate issued ceriticates.
When deleting a User:
- RBAC bindings removed immediately (no permissions)
- secrets deleted
- Certificated remain valid until expiry (~1 year)
π§ implemented Features
- Declarative User CRD with status tracking and finalizers
- Automatic client certificate generation via Kubernetes CSR API
- Stateful certificate rotation with Shadow Secret pattern
- Atomic secret updates with automatic rollback
- Resumable operations across controller restarts
- Certificate rotation 30 days before expiry
- Kubeconfig generation stored as Kubernetes secrets
- Namespace-scoped Role bindings (Role + RoleBinding)
- Namespace-scoped ClusterRole bindings (ClusterRole + RoleBinding)
- Dynamic RBAC reconciliation with automatic cleanup
- Admission webhook validation with TLS/HTTPS
- Role/ClusterRole existence validation
- High availability with leader election
- Prometheus metrics endpoint (HTTPS :8443) with RBAC protection
- Health probes (liveness and readiness)
- Helm chart and Kustomize deployment options
π§ Planned Features
- User Groups (UserGroup CRD)
- Predefined role templates library
- Certificate revocation mechanism
- OIDC, LDAP/AD, and SSO integration
- Enhanced metrics with Grafana dashboards and Prometheus alerts
- CLI tool and Web UI
π¦ Installation Instructions
Prerequisites
- Kubernetes cluster (v1.28+)
- kubectl configured to access your cluster with cluster-admin permissions
- cert-manager (required for webhook certificates)
- Docker (for building images locally)
- kind or minikube (for local testing)
Install cert-manager
KubeUser requires cert-manager for webhook certificate management:
# Install cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.2/cert-manager.yaml
# Wait for cert-manager to be ready
kubectl wait --for=condition=ready pod -l app=cert-manager -n cert-manager --timeout=60s
Deployment Options
Option 1: Using Helm (Recommended)
KubeUser publishes Helm charts via GitHub Pages. To install using Helm:
helm repo add kubeuser https://openkube-hub.github.io/KubeUser
helm repo update
```bash
# Install with automatic namespace creation (recommended)
helm upgrade --install kubeuser kubeuser/kubeuser \
--create-namespace \
--namespace kubeuser \
--version <version>
# Or install into existing namespace
helm upgrade --install kubeuser kubeuser/kubeuser \
--namespace existing-namespace \
--version <version>
# List available versions
helm search repo kubeuser --versions
# Upgrade to a new version later
# helm upgrade kubeuser kubeuser/kubeuser -n kubeuser --version <new-version>
# Uninstall
# helm uninstall kubeuser -n kubeuser
Notes:
- All resource names are prefixed by the Helm release name (e.g.,
kubeuser). - The chart defaults the image.tag to the chart version; override with
--set image.tag=<tag>if needed.
Option 2: Using Kustomize
# Clone the repository
git clone https://github.com/openkube-hub/KubeUser.git
cd KubeUser
# Create namespace first (required)
kubectl create namespace kubeuser
# Deploy using kustomize
kubectl apply -k config/default
# Wait for controller to be ready
kubectl wait --for=condition=ready pod -l control-plane=controller-manager -n kubeuser --timeout=120s
Option 3: Local Development with kind
For local testing and development:
# Build the Docker image
make docker-build
# Load image into kind cluster
kind load docker-image ghcr.io/openkube-hub/kubeuser-controller:latest --name <your-cluster-name>
# Deploy with local image
kubectl apply -k config/default
# Update deployment to use local image
kubectl patch deployment kubeuser-controller-manager -n kubeuser -p '{"spec":{"template":{"spec":{"containers":[{"name":"manager","imagePullPolicy":"Never"}]}}}}'
Verification
Verify the installation:
# Check controller status
kubectl get pods -n kubeuser
# Check webhook certificate
kubectl get certificates -n kubeuser
# Check CRDs
kubectl get crd users.auth.openkube.io
π Quick Start / Usage
How Defaults Work
KubeUser uses a mutating admission webhook to apply defaults at resource creation:
- You submit a minimal User spec with only required fields
- Webhook reads environment variables from Helm configuration
- Defaults applied for any omitted optional fields (ttl, autoRenew)
- Resource persisted to etcd with defaults written into the spec
- You can verify applied defaults:
kubectl get user <name> -o yaml
Example:
# You submit:
spec:
auth:
type: x509 # Only required field
# Webhook persists:
spec:
auth:
type: x509
ttl: "2160h" # Applied from KUBEUSER_DEFAULT_TTL
autoRenew: true # Applied from KUBEUSER_DEFAULT_AUTORENEW
Configuration: SREs can customize defaults via Helm:
helm upgrade --install kubeuser ./helm/kubeuser \
--set authDefaults.ttl=720h \
--set authDefaults.autoRenew=false
β οΈ Important: Changes to authDefaults only apply to NEW users created after the Helm upgrade. Existing users retain their original defaults (persisted in spec).
Basic User Creation
Create a user with namespace-scoped access:
apiVersion: auth.openkube.io/v1alpha1
kind: User
metadata:
name: alice
spec:
auth:
type: x509 # REQUIRED: must be 'x509' or 'oidc'
ttl: "72h" # Optional: 3 days (default: 2160h = 3 months)
autoRenew: false # Optional: disable auto-renewal (default: true)
roles:
- namespace: "development"
existingRole: "developer"
- namespace: "staging"
existingRole: "viewer"
User with Cluster-wide Access
apiVersion: auth.openkube.io/v1alpha1
kind: User
metadata:
name: bob-admin
spec:
auth:
type: x509
ttl: "2160h" # 3 months (default)
autoRenew: true # Enable automatic renewal
clusterRoles:
- existingClusterRole: "cluster-admin"
Mixed Permissions Example
apiVersion: auth.openkube.io/v1alpha1
kind: User
metadata:
name: contractor-jane
spec:
auth:
type: x509
ttl: "720h" # 30 days
autoRenew: true
renewBefore: "72h" # Renew 3 days before expiry
roles:
- namespace: "project-x"
existingRole: "developer"
- namespace: "testing"
existingRole: "tester"
- namespace: "monitoring"
existingClusterRole: "view" # Bind cluster role to specific namespace
clusterRoles:
- existingClusterRole: "view" # Read-only cluster access
Using Existing Cluster Roles in Namespace Scope
KubeUser supports binding existing cluster roles to specific namespaces, providing fine-grained access control:
apiVersion: auth.openkube.io/v1alpha1
kind: User
metadata:
name: namespace-admin
spec:
auth:
type: x509
ttl: "168h" # 1 week
autoRenew: true
renewBefore: "24h" # Renew 1 day before expiry
roles:
- namespace: "production"
existingClusterRole: "admin" # Full admin access to production namespace only
- namespace: "staging"
existingClusterRole: "edit" # Edit access to staging namespace only
- namespace: "development"
existingClusterRole: "view" # Read-only access to development namespace
This approach allows you to:
- Reuse well-defined cluster roles (admin, edit, view) in namespace-specific contexts
- Maintain consistent permission sets across different namespaces
- Avoid creating duplicate namespace-scoped roles
Auto-Renewal Examples
Basic Auto-Renewal (33% Rule)
apiVersion: auth.openkube.io/v1alpha1
kind: User
metadata:
name: alice
spec:
auth:
type: x509
ttl: "72h" # 3 day certificate
autoRenew: true # Renews after 48 hours (33% rule)
clusterRoles:
- existingClusterRole: "view"
Custom Renewal Timing
apiVersion: auth.openkube.io/v1alpha1
kind: User
metadata:
name: bob
spec:
auth:
type: x509
ttl: "168h" # 7 day certificate
autoRenew: true
renewBefore: "48h" # Renew 48 hours before expiry
roles:
- namespace: "development"
existingClusterRole: "edit"
Production Standard Certificate
apiVersion: auth.openkube.io/v1alpha1
kind: User
metadata:
name: charlie
spec:
auth:
type: x509
ttl: "2160h" # 90 days (production standard)
autoRenew: true
renewBefore: "720h" # Renew 30 days before expiry
roles:
- namespace: "production"
existingRole: "deployer"
Note: KubeUser enforces a 24-hour minimum TTL for production safety. Certificates shorter than 24h are rejected by the validating webhook to prevent Thundering Herd loops and API server exhaustion.
Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
spec.auth |
AuthSpec |
Yes | Authentication configuration (MANDATORY - cannot be omitted) |
spec.auth.type |
string |
Yes | Authentication method: x509 or oidc (MANDATORY - no default) |
spec.auth.ttl |
string |
No | Certificate lifetime (default: 2160h = 3 months). Default written by webhook at creation. |
spec.auth.autoRenew |
boolean |
No | Enable automatic certificate renewal (default: true). Default written by webhook at creation. |
spec.auth.renewBefore |
string |
No | Renew this duration before expiry (overrides 33% rule) |
spec.roles |
[]RoleSpec |
No | List of namespace-scoped role bindings |
spec.roles[].namespace |
string |
Yes | Target namespace for the role binding |
spec.roles[].existingRole |
string |
No* | Name of the existing Role in the namespace |
spec.roles[].existingClusterRole |
string |
No* | Name of the existing ClusterRole to bind to the namespace |
spec.clusterRoles |
[]ClusterRoleSpec |
No | List of cluster-wide role bindings |
spec.clusterRoles[].existingClusterRole |
string |
Yes | Name of the existing ClusterRole |
Note: Either existingRole or existingClusterRole must be specified for each role entry.
Managed Kubernetes Support
KubeUser supports managed Kubernetes environments with custom CSR signers:
AWS EKS:
helm install kubeuser ./helm/kubeuser \
--set signerName="beta.eks.amazonaws.com/app-client" \
--set rbac.signerResourceNames[0]="beta.eks.amazonaws.com/app-client"
GKE/AKS: Check your cluster's CSR signer name:
kubectl get csr -o jsonpath='{.items[0].spec.signerName}'
Then configure accordingly:
helm install kubeuser ./helm/kubeuser \
--set signerName="<your-signer-name>" \
--set rbac.signerResourceNames[0]="<your-signer-name>"
Note: The RBAC configuration must include the signer name in signerResourceNames to allow the controller to approve CSRs for that signer.
Observability and Monitoring
Check User Status:
# View all users with status
kubectl get users
# Detailed status for specific user
kubectl describe user alice
# JSON output for programmatic access
kubectl get user alice -o json | jq '.status'
Monitor Certificate Expiry:
# List all users with expiry times
kubectl get users -o custom-columns=NAME:.metadata.name,EXPIRY:.status.expiryTime,NEXT_RENEWAL:.status.nextRenewalAt
# Check if renewal is needed
kubectl get users -o json | jq '.items[] | select(.status.nextRenewalAt != null) | {name: .metadata.name, nextRenewal: .status.nextRenewalAt}'
View Renewal History:
# Last 10 renewal attempts
kubectl get user alice -o jsonpath='{.status.renewalHistory}' | jq
# Check for failed renewals
kubectl get users -o json | jq '.items[] | select(.status.renewalHistory[]?.success == false)'
Monitor Conditions:
# Check Ready condition
kubectl get user alice -o jsonpath='{.status.conditions[?(@.type=="Ready")]}'
# Check Renewing condition
kubectl get user alice -o jsonpath='{.status.conditions[?(@.type=="Renewing")]}'
Status Conditions: KubeUser provides standard Kubernetes conditions for monitoring:
- Ready: Indicates if the user's certificate is valid and ready for use
- Renewing: Shows if a certificate renewal is currently in progress
Status Fields:
phase: High-level status (Pending, Active, Expired, Error, Renewing)expiryTime: Certificate expiry timestamp (RFC3339)nextRenewalAt: When auto-renewal will trigger (only when autoRenew: true)renewalHistory: Last 10 renewal attempts with timestamps and outcomes
Managing Users
# Create sample namespace and role
kubectl create ns dev
kubectl create role developer --verb=get,list,watch --resource=pods -n dev
# Apply user configuration
kubectl apply -f test/test-user.yaml
# Check user status
kubectl get users
kubectl describe user jane
# Get the generated kubeconfig
kubectl get secret jane-kubeconfig -n kubeuser -o jsonpath='{.data.config}' | base64 -d > /tmp/kubeconfig
# Test user access
kubectl --kubeconfig /tmp/kubeconfig get pods -n dev
# Delete user (cleans up all associated resources)
kubectl delete user jane
Comprehensive Testing
For thorough testing of all features, use the provided test script:
# Run the comprehensive test suite
./test-kubeuser.sh
This script tests:
- Prerequisites validation
- Controller deployment health
- User creation and RBAC bindings
- Certificate generation and kubeconfig creation
- User access validation
- Certificate rotation
- Resource cleanup
Manual Testing Steps
-
Setup test environment:
kubectl apply -f test/test-setup.yaml -
Create a test user:
kubectl apply -f test/test-user-jane-1.yaml -
Verify user creation:
kubectl get users kubectl describe user jane -
Check generated resources:
# Check secrets kubectl get secrets -n kubeuser | grep jane # Check RBAC bindings kubectl get rolebindings -n dev | grep jane kubectl get clusterrolebindings | grep jane # Check CSR (if still present) kubectl get csr -l auth.openkube.io/user=jane -
Test user access:
# Extract kubeconfig kubectl get secret jane-kubeconfig -n kubeuser -o jsonpath='{.data.config}' | base64 -d > /tmp/jane.kubeconfig # Test authentication kubectl --kubeconfig /tmp/jane.kubeconfig auth can-i get pods -n dev # Test actual access kubectl --kubeconfig /tmp/jane.kubeconfig get pods -n dev
βοΈ Configuration
Certificate Duration Limits
Minimum TTL: 24 hours (enforced by validating webhook)
- Requests with TTL < 24h are rejected
- Prevents Thundering Herd loops and API server exhaustion
- Internal testing override:
KUBEUSER_MIN_DURATIONenvironment variable (not exposed in Helm)
Maximum TTL: 1 year (8760h)
- Based on Kubernetes default
--cluster-signing-durationflag - Configurable by cluster administrators
Default TTL: 90 days (2160h)
- Applied by mutating webhook when not specified
- Configurable via Helm
authDefaults.ttl
Environment Variables
The operator supports the following environment variables:
| Variable | Default | Description |
|---|---|---|
KUBERNETES_API_SERVER |
https://kubernetes.default.svc |
Kubernetes API server address |
CLUSTER_DOMAIN |
cluster.local |
Kubernetes cluster DNS domain (change if your cluster uses a custom domain) |
KUBEUSER_DEFAULT_TTL |
2160h |
Default certificate TTL (set via Helm authDefaults.ttl) |
KUBEUSER_DEFAULT_AUTORENEW |
true |
Default auto-renewal behavior (set via Helm authDefaults.autoRenew) |
KUBEUSER_SIGNER_NAME |
kubernetes.io/kube-apiserver-client |
CSR signer name (set via Helm signerName) |
π§ Troubleshooting
Common Issues
Controller Pod Not Starting
# Check pod status
kubectl get pods -n kubeuser
# Check pod logs
kubectl logs -n kubeuser deployment/kubeuser-controller-manager
# Check events
kubectl get events -n kubeuser --sort-by=.lastTimestamp
Common causes:
- Missing cert-manager installation
- Webhook certificate not ready
- Image pull issues (for local development)
Webhook Certificate Issues
# Check certificate status
kubectl get certificates -n kubeuser
kubectl describe certificate kubeuser-webhook-cert -n kubeuser
# Check cert-manager logs
kubectl logs -n cert-manager deployment/cert-manager
# Force certificate recreation
kubectl delete certificate kubeuser-webhook-cert -n kubeuser
kubectl apply -k config/default
User Creation Fails
# Check user status
kubectl describe user <username>
# Check controller logs
kubectl logs -n kubeuser deployment/kubeuser-controller-manager | grep -i error
# Check webhook validation
kubectl get validatingwebhookconfiguration kubeuser-validating-webhook-configuration -o yaml
Common causes:
- Referenced roles don't exist
- Target namespace doesn't exist (controller no longer auto-creates namespaces)
- RBAC permission issues
- Webhook validation failures
Namespace Issues: If you see errors about missing namespaces, ensure you:
- Used
--create-namespacewith Helm installation - Pre-created the namespace for Kustomize deployments
Certificate Generation Issues
# Check CSR status
kubectl get csr -l auth.openkube.io/user=<username>
# Check CSR details
kubectl describe csr <csr-name>
# Check controller RBAC permissions
kubectl auth can-i create certificatesigningrequests --as=system:serviceaccount:kubeuser:kubeuser-controller-manager
Getting Help
For additional support:
- Check the comprehensive documentation in
docs/ - Review logs for specific error messages
- Ensure all prerequisites are properly installed
- Verify RBAC permissions are correctly configured
π Documentation
- Certificate Management Guide - Comprehensive certificate management details
- Webhook Validation - Webhook validation and troubleshooting
- Test Script - Automated testing script
π Quick Reference
Essential Commands
# Deploy KubeUser
kubectl apply -k config/default
# Check deployment status
kubectl get pods -n kubeuser
kubectl get certificates -n kubeuser
# Create a user
kubectl apply -f test/test-user-jane-1.yaml
# Get user kubeconfig
kubectl get secret jane-kubeconfig -n kubeuser -o jsonpath='{.data.config}' | base64 -d > jane.kubeconfig
# Test user access
kubectl --kubeconfig jane.kubeconfig auth can-i get pods -n dev
# Clean up
kubectl delete user jane
kubectl delete -k config/default
Key Resources Created
- Namespace:
kubeuser - CRD:
users.auth.openkube.io - Controller:
kubeuser-controller-manager - Webhook:
kubeuser-validating-webhook-configuration - Certificates:
kubeuser-webhook-cert(managed by cert-manager)
User Resource Secrets
For each user, the controller creates:
<username>-key: Private key secret<username>-kubeconfig: Complete kubeconfig file- CSR:
<username>-csr(temporary, cleaned up after use)
π» Development Guide
Prerequisites
- Go: Version 1.24+ (as specified in go.mod)
- Docker: For building container images
- kubectl: Kubernetes command-line tool
- Kind: For local testing (optional but recommended)
- Kustomize: For manifest management
- Kubebuilder: v3.0+ (for code generation)
Local Development Setup
- Clone the repository:
git clone https://github.com/openkube-hub/KubeUser.git
cd KubeUser
- Install dependencies:
go mod tidy
- Generate code and manifests:
make generate
make manifests
- Run tests:
make test
Building and Running Locally
# Build the manager binary
make build
# Run against a Kubernetes cluster (requires kubeconfig)
make run
# Build and load Docker image (requires Docker)
make docker-build
Testing
Unit Tests
# Run all unit tests
make test
# Run tests with coverage
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out
End-to-End Tests
# Run e2e tests (creates Kind cluster)
make test-e2e
# Manual e2e testing
make setup-test-e2e # Creates Kind cluster
# ... run manual tests ...
make cleanup-test-e2e # Cleanup
Linting and Code Quality
# Run linter
make lint
# Fix linting issues automatically
make lint-fix
# Verify linting configuration
make lint-config
# Format code
make fmt
# Vet code
make vet
Development Workflow
- Make changes to the code
- Generate code:
make generate manifests - Run tests:
make test - Test locally:
make run - Build image:
make docker-build - Run e2e tests:
make test-e2e
π€ Contributing
We welcome contributions to KubeUser! Please follow these guidelines:
Submitting Pull Requests
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Follow the development setup above
- Make your changes with tests
- Ensure all tests pass:
make test lint - Commit with conventional commit format:
feat: add user group management - Implement UserGroup CRD - Add controller logic for group management - Include comprehensive tests Fixes #123 - Push to your fork:
git push origin feature/amazing-feature - Create a Pull Request
Code Style
- Follow standard Go conventions
- Use
gofmtfor formatting - Pass
golangci-lintchecks - Write comprehensive tests for new features
- Update documentation for user-facing changes
Commit Message Format
We use Conventional Commits:
feat:New featuresfix:Bug fixesdocs:Documentation changestest:Test-related changesrefactor:Code refactoringci:CI/CD changeschore:Maintenance tasks
If you find KubeUser useful, please consider giving it a β on GitHub!