Skip to content

Architecture Overview

Voidkey implements a true zero-trust architecture for credential management, eliminating the need for long-lived secrets while maintaining strong security boundaries.

Voidkey’s architecture is built on zero-trust principles:

  1. No Shared Secrets: Clients and the broker authenticate independently with their own identity providers
  2. Verify Everything: Every request is authenticated and authorized
  3. Least Privilege: Credentials are scoped to minimum required permissions
  4. Time-Limited Access: All credentials have short expiration times

CLI (Go)

  • Obtains OIDC tokens from client IdP
  • Sends credential requests to broker
  • Formats and outputs credentials

Client Identity Providers

  • GitHub Actions OIDC
  • GitLab CI OIDC
  • Auth0, Okta, or any OIDC provider
  • Custom IdP implementations

Broker Server (NestJS)

  • Validates client OIDC tokens
  • Authenticates with broker IdP
  • Manages identity configurations
  • Orchestrates credential minting

Broker Core (TypeScript)

  • Token validation logic
  • Provider abstractions
  • Configuration management
  • Error handling and logging

Access Providers

  • AWS STS ✅
  • Google Cloud IAM 🚧 (Coming Soon)
  • Azure AD 🚧 (Coming Soon)
  • MinIO STS ✅
  • Custom provider implementations
sequenceDiagram
    participant CLI as Client
CLI participant ClientIdP as Client IdP
(GitHub, etc) participant Broker as Voidkey
Broker participant BrokerIdP as Broker IdP
(Auth0, etc) participant Cloud as Cloud
Provider CLI->>ClientIdP: 1. Auth (OIDC) ClientIdP->>CLI: 2. OIDC Token CLI->>Broker: 3. POST /credentials/mint
+ OIDC Token + Key Names Broker->>ClientIdP: 4. Fetch JWKS & Validate ClientIdP->>Broker: 5. Token Valid Broker->>BrokerIdP: 6. Client Creds Flow BrokerIdP->>Broker: 7. Access Token Broker->>Cloud: 8. Request Temp Credentials
(using Broker identity) Cloud->>Broker: 9. Scoped Credentials Broker->>CLI: 10. Formatted Credentials
(env vars, JSON, etc.) Note over CLI,Cloud: Zero-Trust Credential Flow

The broker validates client tokens by:

  1. Fetching JWKS from the client IdP
  2. Verifying token signature
  3. Validating token claims (issuer, audience, expiry)
  4. Checking subject against configured identities

Once validated, the broker:

  1. Determines allowed keys for the subject
  2. Authenticates with its own IdP
  3. Calls the appropriate cloud provider API
  4. Returns scoped, temporary credentials
clientIdentities:
- subject: "repo:myorg/myapp:ref:refs/heads/main"
idp: "github-actions"
keys:
AWS_PROD_DEPLOY:
provider: "aws-prod"
roleArn: "arn:aws:iam::123456789012:role/ProdDeploy"
duration: 3600
outputs:
AWS_ACCESS_KEY_ID: "AccessKeyId"
AWS_SECRET_ACCESS_KEY: "SecretAccessKey"
AWS_SESSION_TOKEN: "SessionToken"

Each identity can have multiple named keys:

  • Key Name: Logical name for the credential set
  • Provider: Which cloud provider to use
  • Configuration: Provider-specific settings
  • Outputs: How to map provider response to environment variables
  • Client ↔ Broker: HTTPS with OIDC bearer tokens
  • Broker ↔ IdPs: HTTPS with OAuth2/OIDC
  • Broker ↔ Cloud: HTTPS with provider-specific auth
  • Client Trust: Only trusts its own IdP
  • Broker Trust: Validates client tokens but uses own identity
  • Cloud Trust: Only trusts the broker’s authenticated identity
  • Scope: Credentials limited to specific resources
  • Time: Short expiration (typically 15-60 minutes)
  • Permissions: Least privilege based on use case

The broker server is completely stateless:

  • No session storage
  • No credential caching
  • Configuration loaded at startup
  • Horizontal scaling ready

Deploy multiple broker instances behind a load balancer:

graph TD
    LB[Load Balancer] --> B1[Broker
Instance] LB --> B2[Broker
Instance] LB --> B3[Broker
Instance]
  • JWKS Caching: Cache IdP public keys
  • Connection Pooling: Reuse HTTP connections
  • Async Operations: Non-blocking I/O throughout
  • Minimal Dependencies: Lightweight runtime
// Identity Provider Interface
interface IdpProvider {
name: string;
validateToken(token: string): Promise<TokenClaims>;
getPublicKeys(): Promise<JWKSet>;
}
// Access Provider Interface
interface AccessProvider {
name: string;
type: string;
mintCredentials(config: any): Promise<Credentials>;
}

Add new providers by implementing the interfaces:

  1. Create provider implementation
  2. Register with the broker
  3. Configure in config.yaml
  4. No core changes needed
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm ci --production
EXPOSE 3000
CMD ["npm", "start"]

Deploy as serverless functions:

  • AWS Lambda + API Gateway
  • Google Cloud Functions
  • Azure Functions
  • Minimal cold start time

Run closer to clients:

  • CloudFlare Workers
  • AWS Lambda@Edge
  • Reduced latency
  • Geographic distribution
GET /health
{
"status": "ok",
"timestamp": "2024-01-15T10:30:00Z",
"version": "0.8.0",
"uptime": 3600
}
  • Request count and latency
  • Token validation success/failure
  • Provider API calls
  • Error rates by type

Structured logging with:

  • Request correlation IDs
  • Security audit trail
  • Error details
  • Performance metrics