Cloud KMS Setup - Nops
Configuration for AWS KMS, Google Cloud KMS, Azure Key Vault, and HashiCorp Vault Transit.
Overview
Nops supports decrypting data key shares encrypted under cloud KMS keys. Each provider ships as a separate NuGet package:
| Provider | Package | Key Type |
|---|---|---|
| AWS KMS | Nops.Core.AwsKms |
aws_kms |
| Google Cloud KMS | Nops.Core.GcpKms |
gcp_kms |
| Azure Key Vault | Nops.Core.Azkv |
azure_kv |
| HashiCorp Vault Transit | Nops.Core.Vault |
hc_vault |
Not supported: sops'
hckmskey type is Huawei Cloud KMS. Files encrypted only for it cannot be decrypted by Nops.
Using a provider
Create one master key per KMS key that can decrypt the file and pass them in
MasterKeys. You don't copy anything out of the file: the encrypted data key is
read from the file's sops metadata.
builder.Configuration.AddSops("secrets.sops.yaml", opts =>
{
opts.MasterKeys = new IMasterKeyProvider[]
{
new AwsKmsMasterKey("arn:aws:kms:us-east-1:123456789012:key/abcd-1234"),
};
});
AgeIdentities, PgpKeys and the sops environment keys are still tried
alongside MasterKeys.
AWS KMS
Installation
dotnet add package Nops.Core.AwsKms
Authentication
Uses AWS SDK default credential chain:
- Environment variables:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY - Shared credentials file:
~/.aws/credentials - IAM role (EC2, ECS, EKS, Lambda)
Usage
using Nops.Core.Keys;
var masterKey = new AwsKmsMasterKey(
arn: "arn:aws:kms:us-east-1:123456789012:key/abcd-1234"
);
IAM Policy
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:<region>:<account>:key/<key-id>"
}]
}
Encryption Context
If the SOPS file was encrypted with encryption context, pass it when creating the master key:
var masterKey = new AwsKmsMasterKey(
arn: "arn:aws:kms:us-east-1:123456789012:key/abcd-1234",
context: new[] {
new KeyValuePair<string, string>("Project", "myapp"),
new KeyValuePair<string, string>("Env", "prod")
}
);
LocalStack (Development)
docker run -p 4566:4566 localstack/localstack
var client = new AmazonKMSClient(
new BasicAWSCredentials("test", "test"),
RegionEndpoint.USEast1)
{
Config = new AmazonKMSConfig { ServiceURL = "http://localhost:4566" }
};
var masterKey = new AwsKmsMasterKey(
arn: "arn:aws:kms:us-east-1:000000000000:key/test",
client: client
);
Google Cloud KMS
Installation
dotnet add package Nops.Core.GcpKms
Authentication
Uses Application Default Credentials (ADC):
gcloud auth application-default login- Set
GOOGLE_APPLICATION_CREDENTIALSto service account JSON key path - Automatic on GCE/GKE/GAE
Usage
using Nops.Core.Keys;
var masterKey = new GcpKmsMasterKey(
resourceName: "projects/my-project/locations/global/keyRings/my-kr/cryptoKeys/my-key"
);
IAM Role
roles/cloudkms.cryptoKeyDecrypter
Or custom policy:
{
"permissions": [
"cloudkms.cryptoKeys.get",
"cloudkms.cryptoKeys.decrypt"
]
}
Azure Key Vault
Installation
dotnet add package Nops.Core.Azkv
Authentication
Uses DefaultAzureCredential:
- Environment variables:
AZURE_TENANT_ID,AZURE_CLIENT_ID,AZURE_CLIENT_SECRET az login(Azure CLI)- Managed Identity (on Azure)
Usage
using Nops.Core.Keys;
var masterKey = new AzkvMasterKey(
vaultUrl: "https://my-vault.vault.azure.net",
keyName: "sops-wrapping-key"
);
Key Vault Setup
az keyvault create --name my-vault --location eastus --resource-group my-rg
az keyvault key create --name sops-wrapping-key --vault-name my-vault --protection software
az keyvault set-policy --name my-vault --upn you@example.com --key-permissions unwrapKey wrapKey
HashiCorp Vault Transit
Installation
dotnet add package Nops.Core.Vault
Authentication
Pass a Vault token directly:
using Nops.Core.Keys;
var masterKey = new VaultTransitMasterKey(
address: "https://vault.example.com:8200",
mountPath: "transit",
keyName: "my-transit-key",
token: "s.vault-token-here"
);
Local Development
vault dev
# Token printed in output: s.abcd1234
export VAULT_TOKEN=s.abcd1234
export VAULT_ADDR=http://127.0.0.1:8200
vault secrets enable transit
vault write -f transit/keys/sops-key
Multi-Provider Support
Nops tries all master keys until one succeeds. Mix providers in a single file:
# secrets.sops.yaml
sops:
age:
- recipient: age1...
enc: age-encryption.org/v1...
kms:
- arn: arn:aws:kms:...
enc: BASE64...
gcp_kms:
- resource_name: projects/.../cryptoKeys/...
enc: BASE64...
Nops goes through the file's key entries in order and asks each matching
provider to decrypt; the first success wins. With several key groups (Shamir),
it stops as soon as shamir_threshold groups have been decrypted.
Testing
Each provider has integration tests gated by environment variables:
# AWS KMS (requires LocalStack)
AWS_LIVE_TEST=1 dotnet test --filter FullyQualifiedName~AwsKmsIntegrationTests
# GCP KMS (requires GCP credentials or emulator)
GCP_LIVE_TEST=1 dotnet test --filter FullyQualifiedName~GcpKmsIntegrationTests
# Azure Key Vault (requires Azure credentials)
AZURE_LIVE_TEST=1 dotnet test --filter FullyQualifiedName~AzkvIntegrationTests