le-cert-server
GitHub

Authentication

le-cert-server supports two authentication methods:

  1. Pre-Shared Key (PSK) Authentication - Simple and secure for most use cases
  2. JWT/OIDC Authentication - Advanced scenarios with OAuth2 providers or platform identities

Pre-Shared Key (PSK) Authentication#

PSK authentication is a simpler and secure option for most deployments. Configure a single API key that clients use to authenticate:

auth:
  psk:
    key: "your-secure-random-key-here"

Generate a secure key:

# Generate a random 32-byte key (base64 encoded)
openssl rand -base64 32

Clients authenticate by passing the key in the Authorization header:

curl -X POST https://le-cert-server:8443/api/certificate \
  -H "Authorization: APIKey your-secure-random-key-here" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'

Security notice: When authenticating via PSK, all callers have access to all certificates. For fine-grained access control to specific certificates, use the JWT/OIDC authentication method.

JWT/OIDC Authentication (Advanced)#

For advanced scenarios, le-cert-server supports JWT token validation. This is useful when:

  • Using OAuth2/OIDC providers (Auth0, Okta, Keycloak, etc.)
  • Leveraging platform-managed identities (Azure Managed Identity, AWS IAM roles, Kubernetes service accounts, etc)
  • Implementing fine-grained access control with scopes

Basic JWT Configuration:

auth:
  issuerUrl: "https://your-auth-provider.com"
  audience: "your-api-audience"
  # Optional
  requiredScopes:
    - "read:certificates"
    - "write:certificates"

Example: Auth0 with Client Credentials Flow#

Ideal for machine-to-machine authentication:

1. Configure Auth0:

  • Create an API in Auth0 dashboard with identifier https://le-cert-server-api
  • Create a Machine-to-Machine application
  • Authorize the application to access your API
  • Define custom scopes (optional)

2. Configure le-cert-server:

auth:
  issuerUrl: "https://your-tenant.auth0.com"
  audience: "https://le-cert-server-api"
  requiredScopes:
    - "request:certificates"

3. Client obtains token and requests certificate:

# Get access token using client credentials
ACCESS_TOKEN=$(curl --request POST \
  --url 'https://your-tenant.auth0.com/oauth/token' \
  --header 'content-type: application/json' \
  --data '{
    "client_id":"YOUR_CLIENT_ID",
    "client_secret":"YOUR_CLIENT_SECRET",
    "audience":"https://le-cert-server-api",
    "grant_type":"client_credentials"
  }' | jq -r '.access_token')

# Request certificate
curl -X POST https://le-cert-server:8443/api/certificate \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'

Example: Azure Managed Identity#

Use Azure Managed Identities to authenticate from an Azure VM, App Service, or other Azure services, without managing credentials:

1. Configure Microsoft Entra ID:

  • Create an App Registration in Entra ID (Azure AD)
  • Note the Application (client) ID: this is your “audience” value
  • Assign Managed Identity to your Azure resources (VM, App Service, etc)
  • Grant the Managed Identity access to the App Registration

2. Configure le-cert-server:

auth:
  issuerUrl: "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0"
  audience: "YOUR_APP_CLIENT_ID"

3. Client uses Managed Identity to get token:

# From Azure VM or App Service with Managed Identity
ACCESS_TOKEN=$(curl -s 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=YOUR_APP_CLIENT_ID' \
  -H 'Metadata: true' | jq -r '.access_token')

# Request certificate
curl -X POST https://le-cert-server:8443/api/certificate \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'

Azure Managed Identity Benefits:

  • No credentials to manage or rotate
  • Automatic token refresh by Azure platform
  • Integrates with Azure RBAC and identity governance

Example: AWS IAM Roles with OIDC#

Use AWS IAM roles and OIDC federation for credential-free authentication:

1. Set up OIDC Provider in AWS IAM:

  • Configure an OIDC identity provider pointing to your auth server
  • Create an IAM role with trust policy for the OIDC provider
  • EC2 instances or ECS tasks assume this role

2. Configure le-cert-server:

auth:
  issuerUrl: "https://your-oidc-provider.com"
  audience: "sts.amazonaws.com"

3. Client uses AWS STS to exchange IAM credentials for OIDC token:

# Get OIDC token from AWS STS
ACCESS_TOKEN=$(aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::ACCOUNT:role/le-cert-server-role \
  --role-session-name cert-request \
  --web-identity-token $(cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token) \
  --query 'Credentials.SessionToken' \
  --output text)

# Request certificate
curl -X POST https://le--server:8443/api/certificate \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'

Example: GCP Service Account with Workload Identity#

Use GCP Workload Identity for GKE workloads or service accounts:

1. Set up Workload Identity:

  • Enable Workload Identity on your GKE cluster
  • Create a service account and bind it to Kubernetes service account
  • Grant the service account appropriate IAM roles

2. Configure le-cert-server:

auth:
  issuerUrl: "https://accounts.google.com"
  audience: "YOUR_CLIENT_ID.apps.googleusercontent.com"

3. Client uses GCP metadata service to get identity token:

# From GKE pod with Workload Identity
ACCESS_TOKEN=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=YOUR_CLIENT_ID.apps.googleusercontent.com" \
  -H "Metadata-Flavor: Google")

# Request certificate
curl -X POST https://le-cert-server:8443/api/certificate \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'

Example: tsiam (Tailscale workload identity)#

tsiam issues short-lived JWTs to machines based on their Tailscale identity (a Tailscale-native equivalent of AWS IAM Roles or Azure Managed Identity). Your nodes authenticate to le-cert-server and prove who they are simply by being on your tailnet.

Because tsiam tokens are validated over standard JWT/OIDC, le-cert-server can stay on a regular TCP listener and does not itself need to run on the tailnet: it only needs network access to tsiam’s OIDC discovery and JWKS endpoints (reachable over the tailnet, or publicly via Tailscale Funnel).

1. Allow the le-cert-server audience in tsiam (tsiam’s config.yaml):

tokens:
  allowedAudiences:
    - "https://le-cert-server"

2. Grant nodes access in your Tailscale ACL policy. This controls which machines may request a token for the le-cert-server audience:

{
  "grants": [
    {
      "src": ["tag:traefik"],
      "dst": ["tag:tsiam"],
      "app": {
        "italypaleale.me/tsiam": [
          { "allowedAudiences": ["https://le-cert-server"] }
        ]
      }
    }
  ]
}

3. Configure le-cert-server to trust tokens issued by tsiam:

auth:
  method: jwt
  jwt:
    # tsiam's OIDC issuer URL: its MagicDNS name on your tailnet, or a Tailscale Funnel URL
    # Must match the "iss" claim in the tokens tsiam issues
    # le-cert-server discovers the JWKS endpoint via {issuerUrl}/.well-known/openid-configuration
    issuerUrl: "https://tsiam.your-tailnet.ts.net"
    # Must match the audience the client requests from tsiam (and an entry in tsiam's allowedAudiences)
    audience: "https://le-cert-server"

4. Client fetches a token from tsiam, then requests a certificate:

# From any node on the tailnet, request a token scoped to the le-cert-server audience
ACCESS_TOKEN=$(curl -s -X POST "https://tsiam/token?resource=https://le-cert-server" \
  -H "X-Tsiam: 1" | jq -r '.access_token')

# Request certificate
curl -X POST https://le-cert-server:8443/api/certificate \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'
Edit this page on GitHub