Skip to content

Kubernetes Secrets with OCI Vault - External Secrets Operator

This cluster uses OCI Vault (Always Free tier) to securely store all sensitive configuration. This approach ensures that secrets are never committed to the repository and can be retrieved for cluster recreation.

ResourceFree LimitOur Usage
Vault Secrets150~10
Master Key Versions201
HSM-protected Keys

All secrets are stored in the k3s-secrets-vault OCI Vault:

Secret NamePurposeUsed By
cloudflare-api-tokenDNS automationExternal DNS, Cert Manager
cloudflare-zone-idCloudflare zoneExternal DNS
domain-nameBase domainManifests
github-patRepository accessArgoCD, GHCR
github-usernameGitHub authenticationArgoCD, GHCR
git-repo-urlRepository URLArgoCD
k3s-tokenCluster join tokenK3s nodes
acme-emailLet’s Encrypt contactCert Manager
argocd-admin-passwordArgoCD UI loginArgoCD
ssh-public-keyInstance accessOCI Compute

Install and configure the OCI CLI:

Terminal window
brew install oci-cli
oci setup config
Terminal window
oci vault secret list \
--compartment-id <your-compartment-ocid> \
--query 'data[].{"name":"secret-name","id":id}' \
--output table
Terminal window
oci secrets secret-bundle get \
--secret-id <secret-ocid> \
--query 'data."secret-bundle-content".content' \
--raw-output | base64 -d

The ArgoCD admin password is automatically synced from Vault to the cluster via External Secrets Operator.

Terminal window
# From cluster (password is synced from Vault)
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath='{.data.password}' | base64 -d
# Or directly from Vault
ARGOCD_SECRET_ID=$(terraform -chdir=tf-k3s output -json secret_ocids | jq -r '.argocd_admin_password')
oci secrets secret-bundle get \
--secret-id "$ARGOCD_SECRET_ID" \
--query 'data."secret-bundle-content".content' \
--raw-output | base64 -d

To recreate terraform.tfvars from OCI Vault:

  1. Get all secret OCIDs

    Terminal window
    terraform -chdir=tf-k3s output -json secret_ocids
  2. Create the tfvars file

    Terminal window
    cat > tf-k3s/terraform.tfvars << 'EOF'
    tenancy_ocid = "<from OCI Console>"
    user_ocid = "<from OCI Console>"
    fingerprint = "<from OCI Console>"
    private_key_path = "/path/to/oci_api_key.pem"
    region = "<your-region>"
    compartment_ocid = "<from OCI Console>"
    EOF
  3. Add secrets from Vault

    Terminal window
    # Example for one secret
    echo "cloudflare_api_token = \"$(oci secrets secret-bundle get \
    --secret-id <cloudflare-api-token-ocid> \
    --query 'data."secret-bundle-content".content' \
    --raw-output | base64 -d)\"" >> tf-k3s/terraform.tfvars

The cluster runs External Secrets Operator (ESO) to sync OCI Vault secrets to Kubernetes Secrets. This enables GitOps-friendly secret management where:

  1. Secrets are stored in OCI Vault
  2. ESO reads secrets from Vault
  3. ESO creates/updates Kubernetes Secrets
  4. Applications reference standard Kubernetes Secrets

The cluster uses Instance Principals to authenticate with OCI Vault. This eliminates the need for managing long-lived API keys for the cluster itself.

  1. Dynamic Group: k3s-nodes-dg groups all instances in the compartment (instance.compartment.id = '<compartment_ocid>').
  2. Policy: k3s-secrets-read-policy allows the dynamic group to read secret-family and use vaults in the compartment.
  3. ClusterSecretStore: Configured with principalType: InstancePrincipal.
flowchart LR
    subgraph OCI["OCI Cloud"]
        Vault[(Vault)]
        IAM[IAM Policy]
    end
    
    subgraph K8s["K3s Cluster"]
        Node[Worker Node]
        ESO[External Secrets<br/>Operator]
        css[ClusterSecretStore]
        es[ExternalSecret]
        Secret[K8s Secret]
    end
    
    Node --"Identity"--> IAM
    IAM --"Allow Read"--> Vault
    Vault --"Secret Bundle"--> ESO
    css --"Config"--> ESO
    es --"Ref"--> ESO
    ESO --"Sync"--> Secret
  1. Never commit secrets - All .tfvars files are gitignored
  2. Use HSM protection - OCI Vault uses HSM-backed master keys
  3. Enable versioning - Secret versions are retained for rollback
  4. Least privilege - Use scoped API tokens (Cloudflare Zone.DNS only)
  5. Rotate regularly - Update secrets and create new versions

Terraform state is stored in OCI Object Storage:

SettingValue
Bucketk3s-tfstate
VersioningEnabled
AccessPrivate (NoPublicAccess)
EncryptionServer-side (default)