Skip to content

TLS & Certificate Management

Problem Statement

"I need to manage TLS certificates for my Kubernetes services without installing cert-manager, or I want to consolidate multiple certificate sources (ACME, Vault, manual) under a single, unified API."

NovaEdge provides built-in certificate lifecycle management through the ProxyCertificate CRD. It supports four certificate sources -- ACME (Let's Encrypt), cert-manager integration, HashiCorp Vault PKI, and manual TLS secrets -- all with automatic renewal, OCSP stapling, and mTLS client verification.


Architecture

graph TB
    subgraph Certificate_Sources["Certificate Sources"]
        ACME["Let's Encrypt<br/>(ACME)"]
        CM["cert-manager<br/>(ClusterIssuer)"]
        VAULT["HashiCorp Vault<br/>(PKI Engine)"]
        MANUAL["Manual TLS<br/>(K8s Secrets)"]
        style ACME fill:#FFE4B5,stroke:#333
        style CM fill:#FFE4B5,stroke:#333
        style VAULT fill:#FFE4B5,stroke:#333
        style MANUAL fill:#FFE4B5,stroke:#333
    end

    subgraph NovaEdge_Controller["NovaEdge Controller"]
        CERTMGR["Certificate Manager<br/>ProxyCertificate CRD"]
        STORE["Kubernetes Secrets<br/>(tls.crt + tls.key)"]
        style CERTMGR fill:#90EE90,stroke:#333
        style STORE fill:#90EE90,stroke:#333
    end

    subgraph NovaEdge_Agents["NovaEdge Agents (per node)"]
        TLS_TERM["TLS Termination<br/>SNI Routing"]
        OCSP["OCSP Stapling"]
        MTLS["mTLS Client Auth"]
        style TLS_TERM fill:#90EE90,stroke:#333
        style OCSP fill:#90EE90,stroke:#333
        style MTLS fill:#90EE90,stroke:#333
    end

    ACME -- "HTTP-01 / DNS-01 /<br/>TLS-ALPN-01" --> CERTMGR
    CM -- "annotations" --> CERTMGR
    VAULT -- "PKI secrets engine" --> CERTMGR
    MANUAL -- "secretRef" --> CERTMGR

    CERTMGR -- "store & renew" --> STORE
    STORE -- "push via ConfigSnapshot" --> TLS_TERM
    TLS_TERM --> OCSP
    TLS_TERM --> MTLS

Source 1: Built-in ACME (Let's Encrypt)

NovaEdge includes a native ACME client that supports HTTP-01, DNS-01, and TLS-ALPN-01 challenge types. No external controller is needed.

HTTP-01 Challenge

The simplest approach. NovaEdge automatically responds to HTTP-01 challenges on port 80.

apiVersion: novaedge.io/v1alpha1
kind: ProxyCertificate
metadata:
  name: app-example-com
  namespace: production
spec:
  domains:
    - app.example.com
    - www.example.com
  issuer:
    type: acme
    acme:
      email: admin@example.com
      challengeType: http-01
      server: "https://acme-v02.api.letsencrypt.org/directory"
      acceptTOS: true
  keyType: EC256
  renewBefore: "720h"
  secretName: app-example-com-tls

DNS-01 Challenge (Cloudflare)

Required for wildcard certificates. NovaEdge supports Cloudflare, Route53, and Google DNS providers.

apiVersion: novaedge.io/v1alpha1
kind: ProxyCertificate
metadata:
  name: wildcard-example-com
  namespace: production
spec:
  domains:
    - "*.example.com"
    - example.com
  issuer:
    type: acme
    acme:
      email: admin@example.com
      challengeType: dns-01
      dns01:
        provider: cloudflare
        credentialsRef:
          name: cloudflare-api-token
        propagationTimeout: "120s"
        pollingInterval: "5s"
  keyType: EC256
  renewBefore: "720h"
  secretName: wildcard-example-com-tls
---
# Create the Cloudflare API token secret first
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token
  namespace: production
type: Opaque
stringData:
  CF_API_TOKEN: "<your-cloudflare-api-token>"

DNS-01 Challenge (Route53)

apiVersion: novaedge.io/v1alpha1
kind: ProxyCertificate
metadata:
  name: wildcard-example-com-aws
  namespace: production
spec:
  domains:
    - "*.example.com"
  issuer:
    type: acme
    acme:
      email: admin@example.com
      challengeType: dns-01
      dns01:
        provider: route53
        credentialsRef:
          name: aws-route53-credentials
        propagationTimeout: "180s"
  keyType: EC256
  secretName: wildcard-example-com-tls
---
apiVersion: v1
kind: Secret
metadata:
  name: aws-route53-credentials
  namespace: production
type: Opaque
stringData:
  AWS_ACCESS_KEY_ID: "<your-access-key>"
  AWS_SECRET_ACCESS_KEY: "<your-secret-key>"
  AWS_REGION: "us-east-1"
  AWS_HOSTED_ZONE_ID: "Z1234567890ABC"

DNS-01 Challenge (Google Cloud DNS)

apiVersion: novaedge.io/v1alpha1
kind: ProxyCertificate
metadata:
  name: wildcard-example-com-gcp
  namespace: production
spec:
  domains:
    - "*.example.com"
  issuer:
    type: acme
    acme:
      email: admin@example.com
      challengeType: dns-01
      dns01:
        provider: googledns
        credentialsRef:
          name: gcp-dns-credentials
  keyType: EC256
  secretName: wildcard-example-com-tls
---
apiVersion: v1
kind: Secret
metadata:
  name: gcp-dns-credentials
  namespace: production
type: Opaque
stringData:
  GCE_PROJECT: "my-gcp-project"
  GCE_SERVICE_ACCOUNT_FILE: |
    {
      "type": "service_account",
      "project_id": "my-gcp-project",
      ...
    }

TLS-ALPN-01 Challenge

Useful when port 80 is not available. The challenge runs over TLS on port 443.

apiVersion: novaedge.io/v1alpha1
kind: ProxyCertificate
metadata:
  name: app-example-com-alpn
  namespace: production
spec:
  domains:
    - app.example.com
  issuer:
    type: acme
    acme:
      email: admin@example.com
      challengeType: tls-alpn-01
      tlsAlpn01:
        port: 443
  keyType: EC256
  secretName: app-example-com-tls

Let's Encrypt Staging (Testing)

Always test with the staging server before using production to avoid rate limits:

apiVersion: novaedge.io/v1alpha1
kind: ProxyCertificate
metadata:
  name: app-example-com-staging
  namespace: production
spec:
  domains:
    - app.example.com
  issuer:
    type: acme
    acme:
      email: admin@example.com
      challengeType: http-01
      server: "https://acme-staging-v02.api.letsencrypt.org/directory"
  keyType: EC256
  secretName: app-example-com-tls-staging

Source 2: cert-manager Integration

If you already run cert-manager, NovaEdge can consume its issuers through annotations on the ProxyGateway or through a dedicated ProxyCertificate referencing a cert-manager Issuer.

Via ProxyCertificate CRD

apiVersion: novaedge.io/v1alpha1
kind: ProxyCertificate
metadata:
  name: app-certmanager
  namespace: production
spec:
  domains:
    - app.example.com
    - api.example.com
  issuer:
    type: cert-manager
    certManager:
      issuerRef:
        name: letsencrypt-prod
        kind: ClusterIssuer
  keyType: EC256
  secretName: app-certmanager-tls

Via ProxyGateway Annotations

Add cert-manager annotations directly to your ProxyGateway. The controller detects these and creates cert-manager Certificate resources automatically:

apiVersion: novaedge.io/v1alpha1
kind: ProxyGateway
metadata:
  name: main-gateway
  namespace: production
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  vipRef: main-vip
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      hostnames:
        - app.example.com
      tls:
        secretRef:
          name: app-certmanager-tls
          namespace: production
        minVersion: "TLS1.2"

Source 3: HashiCorp Vault PKI

NovaEdge integrates with Vault's PKI secrets engine for enterprise certificate management.

Using ProxyCertificate with Vault PKI

apiVersion: novaedge.io/v1alpha1
kind: ProxyCertificate
metadata:
  name: app-vault-cert
  namespace: production
spec:
  domains:
    - app.example.com
    - api.example.com
  issuer:
    type: vault-pki
    vaultPKI:
      path: pki-int
      role: novaedge-issuer
      ttl: "720h"
  keyType: EC256
  secretName: app-vault-tls

Using Inline Vault Reference on ProxyGateway

For tighter integration, reference Vault directly in the gateway listener:

apiVersion: novaedge.io/v1alpha1
kind: ProxyGateway
metadata:
  name: vault-gateway
  namespace: production
spec:
  vipRef: main-vip
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      hostnames:
        - app.example.com
      tls:
        vaultCertRef:
          path: pki-int
          role: novaedge-issuer
          ttl: "720h"
          cacheSecretName: app-vault-cached-tls
        minVersion: "TLS1.3"

The controller requests a certificate from Vault, caches it in the Kubernetes Secret specified by cacheSecretName, and renews it before expiry.

Vault Configuration Prerequisites

Ensure your Vault PKI secrets engine is configured:

# Enable PKI secrets engine
vault secrets enable -path=pki-int pki

# Configure the role
vault write pki-int/roles/novaedge-issuer \
  allowed_domains="example.com" \
  allow_subdomains=true \
  max_ttl="8760h" \
  key_type="ec" \
  key_bits=256

# Grant the NovaEdge controller service account access
vault policy write novaedge-pki - <<EOF
path "pki-int/issue/novaedge-issuer" {
  capabilities = ["create", "update"]
}
path "pki-int/sign/novaedge-issuer" {
  capabilities = ["create", "update"]
}
EOF

Source 4: Manual TLS Secrets

For certificates managed externally (purchased from a CA, generated by your own PKI, etc.), point to an existing Kubernetes TLS Secret.

apiVersion: novaedge.io/v1alpha1
kind: ProxyCertificate
metadata:
  name: app-manual-cert
  namespace: production
spec:
  domains:
    - app.example.com
  issuer:
    type: manual
    manual:
      secretRef:
        name: app-manual-tls
        namespace: production
  secretName: app-manual-tls
---
# Create the TLS secret from your certificate files
apiVersion: v1
kind: Secret
metadata:
  name: app-manual-tls
  namespace: production
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded-certificate-chain>
  tls.key: <base64-encoded-private-key>

Or create it with kubectl:

kubectl create secret tls app-manual-tls \
  --namespace production \
  --cert=./fullchain.pem \
  --key=./privkey.pem

Configuring HTTPS Listeners with Certificates

Regardless of the certificate source, the ProxyGateway references the resulting TLS Secret:

apiVersion: novaedge.io/v1alpha1
kind: ProxyVIP
metadata:
  name: web-vip
spec:
  address: "10.0.100.50/32"
  mode: L2ARP
  ports:
    - 80
    - 443
---
apiVersion: novaedge.io/v1alpha1
kind: ProxyGateway
metadata:
  name: web-gateway
  namespace: production
spec:
  vipRef: web-vip
  listeners:
    - name: http
      port: 80
      protocol: HTTP
      sslRedirect: true
    - name: https
      port: 443
      protocol: HTTPS
      hostnames:
        - app.example.com
        - api.example.com
      tls:
        certificateRef:
          name: app-example-com
          kind: ProxyCertificate
        minVersion: "TLS1.2"
      ocspStapling: true
  redirectScheme:
    enabled: true
    scheme: https
    statusCode: 301

SNI Support with Multiple Certificates

Serve different certificates per hostname on the same listener:

apiVersion: novaedge.io/v1alpha1
kind: ProxyGateway
metadata:
  name: multi-domain-gateway
  namespace: production
spec:
  vipRef: web-vip
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      tlsCertificates:
        "app.example.com":
          secretRef:
            name: app-example-com-tls
            namespace: production
          minVersion: "TLS1.3"
        "api.partner.io":
          secretRef:
            name: api-partner-io-tls
            namespace: production
          minVersion: "TLS1.2"
        "*.internal.corp":
          secretRef:
            name: wildcard-internal-tls
            namespace: production
      ocspStapling: true

mTLS: Client Certificate Verification

Require clients to present a valid certificate signed by a trusted CA. This is useful for API-to-API communication, IoT device authentication, or zero-trust architectures.

apiVersion: novaedge.io/v1alpha1
kind: ProxyGateway
metadata:
  name: mtls-gateway
  namespace: production
spec:
  vipRef: web-vip
  listeners:
    - name: https-mtls
      port: 443
      protocol: HTTPS
      hostnames:
        - api.example.com
      tls:
        secretRef:
          name: api-server-tls
          namespace: production
        minVersion: "TLS1.3"
      clientAuth:
        mode: require
        caCertRef:
          name: client-ca-bundle
          namespace: production
        requiredCNPatterns:
          - "^service-.*\\.example\\.com$"
        requiredSANs:
          - "*.api-clients.example.com"
      ocspStapling: true
---
# CA bundle for verifying client certificates
apiVersion: v1
kind: Secret
metadata:
  name: client-ca-bundle
  namespace: production
type: Opaque
data:
  ca.crt: <base64-encoded-ca-certificate>

Optional mTLS (Passthrough if No Client Cert)

For endpoints that should work with or without client certificates:

apiVersion: novaedge.io/v1alpha1
kind: ProxyGateway
metadata:
  name: optional-mtls-gateway
  namespace: production
spec:
  vipRef: web-vip
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      hostnames:
        - portal.example.com
      tls:
        secretRef:
          name: portal-tls
          namespace: production
        minVersion: "TLS1.2"
      clientAuth:
        mode: optional
        caCertRef:
          name: client-ca-bundle
          namespace: production

OCSP Stapling

OCSP stapling improves TLS handshake performance by embedding the certificate revocation status directly in the TLS handshake, avoiding the client needing to contact the CA's OCSP responder.

Enable it per-listener:

listeners:
  - name: https
    port: 443
    protocol: HTTPS
    tls:
      secretRef:
        name: app-tls
        namespace: production
    ocspStapling: true

For maximum security, request the OCSP Must-Staple extension in the certificate itself:

apiVersion: novaedge.io/v1alpha1
kind: ProxyCertificate
metadata:
  name: app-must-staple
  namespace: production
spec:
  domains:
    - app.example.com
  issuer:
    type: acme
    acme:
      email: admin@example.com
      challengeType: http-01
  keyType: EC256
  mustStaple: true
  secretName: app-must-staple-tls

Verification Steps

Check Certificate Status

# List all ProxyCertificate resources
kubectl get proxycertificates -n production

# Expected output:
# NAME                    DOMAINS            ISSUER       STATE   EXPIRES                AGE
# app-example-com         app.example.com    acme         Ready   2026-05-16T00:00:00Z   1d
# wildcard-example-com    *.example.com      acme         Ready   2026-05-16T00:00:00Z   1d
# app-vault-cert          app.example.com    vault-pki    Ready   2026-03-17T00:00:00Z   1d
# Inspect a specific certificate's full status
kubectl describe proxycertificate app-example-com -n production

Verify the TLS Secret Was Created

kubectl get secret app-example-com-tls -n production -o jsonpath='{.type}'
# Expected: kubernetes.io/tls

Test TLS Termination

# Verify the certificate served by the gateway
openssl s_client -connect 10.0.100.50:443 -servername app.example.com < /dev/null 2>/dev/null | \
  openssl x509 -noout -subject -issuer -dates

Verify OCSP Stapling

openssl s_client -connect 10.0.100.50:443 -servername app.example.com -status < /dev/null 2>&1 | \
  grep -A 5 "OCSP Response"

Expected output includes OCSP Response Status: successful.

Monitor Certificate Metrics

kubectl exec -n novaedge-system daemonset/novaedge-agent -- \
  curl -s localhost:9090/metrics | grep novaedge_certificate

Key metrics:

Metric Description
novaedge_certificate_expiry_seconds Seconds until certificate expires
novaedge_certificate_renewals_total Number of successful renewals
novaedge_acme_challenges_total ACME challenge attempts by type
novaedge_certificate_errors_total Certificate-related errors