Skip to main content

Secrets and Encryption

Protect sensitive data in your integrations, including API keys, database passwords, certificates, and tokens.

Secrets in Config.toml

Store secrets in Config.toml and never commit this file to version control:

# Config.toml
dbPassword = "s3cur3-p@ssw0rd"
apiKey = "sk-abc123..."
oauthClientSecret = "client-secret-value"

Add Config.toml to .gitignore:

# .gitignore
Config.toml
**/Config.toml

Environment variables

Pass secrets via environment variables instead of files:

configurable string dbPassword = ?;
configurable string apiKey = ?;
export BAL_CONFIG_VAR_DB_PASSWORD="s3cur3-p@ssw0rd"
export BAL_CONFIG_VAR_API_KEY="sk-abc123"
bal run

Kubernetes secrets

Mount Kubernetes Secrets as files or environment variables:

apiVersion: v1
kind: Secret
metadata:
name: integration-secrets
type: Opaque
data:
dbPassword: czNjdXIzLXBAc3N3MHJk # base64 encoded
apiKey: c2stYWJjMTIz

---
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: integration
envFrom:
- secretRef:
name: integration-secrets
# Or mount as a Config.toml file:
volumeMounts:
- name: config-volume
mountPath: /app/Config.toml
subPath: Config.toml
volumes:
- name: config-volume
secret:
secretName: integration-config

HashiCorp Vault integration

There are two patterns for consuming Vault secrets in a Ballerina service on Kubernetes.

The Vault Secrets Operator (VSO) syncs Vault secrets into native Kubernetes Secret objects on a schedule. The Ballerina service reads them as environment variables — zero Vault coupling in application code.

1. Install VSO

helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

helm install vault-secrets-operator hashicorp/vault-secrets-operator \
--namespace vault-secrets-operator-system \
--create-namespace \
--set "defaultVaultConnection.enabled=true" \
--set "defaultVaultConnection.address=https://vault.vault.svc.cluster.local:8200"

If Vault uses a custom or self-signed CA, supply the CA cert via --set defaultVaultConnection.caCertSecretRef=<secret-name> where the secret contains the PEM-encoded CA certificate.

2. Create VSO resources

# VaultAuth — authenticates VSO to Vault using the app's ServiceAccount via Kubernetes auth
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: my-integration-auth
namespace: default
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: my-integration
serviceAccount: my-integration
---
# VaultStaticSecret — syncs the Vault KV secret to a Kubernetes Secret every 30s
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: my-integration-credentials
namespace: default
spec:
vaultAuthRef: my-integration-auth
type: kv-v2
mount: secret
path: myapp/credentials
destination:
name: my-integration-secret
create: true
transformation:
excludeRaw: true
templates:
DB_PASSWORD:
text: "{{ .Secrets.db_password }}"
API_KEY:
text: "{{ .Secrets.api_key }}"
refreshAfter: 30s

3. Reference the synced secret in Cloud.toml

Use [[cloud.secret.envs]] to generate valueFrom.secretKeyRef entries in the deployment — no manual YAML patching needed:

[[cloud.secret.envs]]
name = "DB_PASSWORD"
key_ref = "DB_PASSWORD"
secret_name = "my-integration-secret"

[[cloud.secret.envs]]
name = "API_KEY"
key_ref = "API_KEY"
secret_name = "my-integration-secret"

4. Read secrets in Ballerina via os:getEnv

import ballerina/os;

string dbPassword = os:getEnv("DB_PASSWORD");
string apiKey = os:getEnv("API_KEY");

Vault Agent Injector

The Vault Agent Injector (installed alongside Vault) mutates pods at admission time, injecting a sidecar that writes secrets to /vault/secrets/ as files. The Ballerina service reads the file on each request.

Add these annotations to the pod template in your deployment YAML:

annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "my-integration"
vault.hashicorp.com/agent-inject-secret-credentials: "secret/data/myapp/credentials"
vault.hashicorp.com/agent-inject-template-credentials: |
{{- with secret "secret/data/myapp/credentials" -}}
db_password={{ .Data.data.db_password }}
api_key={{ .Data.data.api_key }}
{{- end }}

Read the injected file in Ballerina:

import ballerina/io;

function readInjectedSecret(string filePath) returns map<string>|error {
string[] lines = check io:fileReadLines(filePath);
map<string> props = {};
foreach string line in lines {
int? eq = line.indexOf("=");
if eq is int {
props[line.substring(0, eq).trim()] = line.substring(eq + 1);
}
}
return props;
}
Vault Secrets OperatorVault Agent Injector
Pod containers1 (app only)3 (app + init + sidecar)
Secret deliveryEnv vars via secretKeyRefFile at /vault/secrets/
App reads secretos:getEnv()File I/O
Vault coupling in appNoneFile path + parse logic
K8s Secret createdYes (auditable)No

AWS Secrets Manager

import ballerinax/aws.secretsmanager;

secretsmanager:Client smClient = check new ({
region: "us-east-1",
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey
});

string dbPassword = check smClient->getSecretValue("prod/db/password");

TLS configuration

For a detailed guide on creating keystores and truststores, see Keystores and truststores.

Server TLS

listener http:Listener secureListener = new (9443, {
secureSocket: {
key: {
certFile: "/path/to/server.crt",
keyFile: "/path/to/server.key"
}
}
});

Client TLS (Trust custom CA)

final http:Client secureClient = check new ("https://internal-api.example.com", {
secureSocket: {
cert: "/certs/internal-ca.crt"
}
});

Mutual TLS

listener http:Listener mtlsListener = new (9443, {
secureSocket: {
key: {
certFile: "/path/to/server.crt",
keyFile: "/path/to/server.key"
},
mutualSsl: {
verifyClient: http:REQUIRE,
cert: "/path/to/ca.crt"
}
}
});

Encryption at rest

For database encryption, configure at the database level:

  • MySQL: Enable InnoDB tablespace encryption
  • PostgreSQL: Use pgcrypto extension or Transparent Data Encryption
  • MongoDB: Enable encryption at rest with WiredTiger
  • AWS RDS: Enable storage encryption in RDS settings

Best practices

  1. Never hardcode secrets in source code. Always use configurable variables.
  2. Never commit Config.toml to version control.
  3. Use a secrets manager (Vault, AWS Secrets Manager, Azure Key Vault) in production.
  4. Rotate secrets regularly. Use short-lived tokens where possible.
  5. Enable TLS everywhere. All service-to-service communication should be encrypted.
  6. Use mTLS for sensitive internal service communication.

What's next