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​