Skip to content

AWS Aurora Key Store

Tayra.KeyStore.AwsAurora stores encryption keys in Amazon Aurora PostgreSQL authenticated with AWS IAM database authentication - no static password is ever stored in config or transmitted in plaintext. Tokens are short-lived (15 min) and refreshed automatically by the underlying NpgsqlDataSource on a 13-minute cadence.

Under the hood this is the same PostgreSqlKeyStore you already know - Aurora's wire protocol is PostgreSQL - wrapped with an NpgsqlDataSource whose password provider is the AWS SDK's RDSAuthTokenGenerator. You get the existing Postgres keystore's transactional semantics, schema/table customisation, retry behaviour, and crypto-shred guarantees.

When to use

  • Your keystore must run on AWS Aurora (compliance, infra mandate, or because Aurora is your operational standard).
  • You can't or don't want to manage a static database password.
  • You want IAM-driven access control (rotate access by changing IAM policies, not database credentials).
  • You want the same keystore to scale up to millions of subjects in envelope encryption mode.

If you're happy with password authentication on a vanilla PostgreSQL connection, use UsePostgreSqlKeyStore(...) - Aurora is wire-compatible and works fine with the existing Postgres keystore. Reach for the Aurora package only when you specifically want IAM auth.

Prerequisites in your AWS account

Aurora IAM authentication is not enabled by default. You need:

  1. IAM auth enabled on the cluster. Set EnableIAMDatabaseAuthentication = true (Console: Modify cluster → Database authentication → Password and IAM database authentication).
  2. A database user with the rds_iam role. Run once in the cluster:
    sql
    CREATE USER tayra_app;
    GRANT rds_iam TO tayra_app;
    GRANT CONNECT ON DATABASE tayra TO tayra_app;
    GRANT USAGE, CREATE ON SCHEMA public TO tayra_app;
  3. An IAM policy allowing your app's IAM principal to connect:
    json
    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Action": "rds-db:connect",
        "Resource": "arn:aws:rds-db:us-east-1:123456789012:dbuser:cluster-resource-id/tayra_app"
      }]
    }
  4. SSL is mandatory. Aurora rejects IAM connections without SSL; the keystore defaults to VerifyFull.

Configuration

csharp
using Tayra.KeyStore.AwsAurora;

builder.Services
    .AddTayra(o => o.LicenseKey = config["Tayra:License"]!)
    .UseAwsAuroraKeyStore(opts =>
    {
        opts.ClusterEndpoint = "my-cluster.cluster-xyz.us-east-1.rds.amazonaws.com";
        opts.Database = "tayra";
        opts.IamUser = "tayra_app";
        opts.Region = "us-east-1";

        // Optional - sensible defaults shown.
        opts.Port = 5432;
        opts.SslMode = AwsAuroraSslMode.VerifyFull;
        opts.TokenRefreshInterval = TimeSpan.FromMinutes(13);   // tokens valid 15 min
        opts.TokenFailureRetryInterval = TimeSpan.FromSeconds(30);
        opts.MaxPoolSize = 100;

        // Schema/table customisation (inherited from PostgreSqlKeyStoreOptions)
        opts.Schema = "public";
        opts.TableName = "tayra_encryption_keys";
        opts.AutoMigrate = true;
    });

The AWS credentials chain (env vars, profile, IRSA / IAM role for service account, EC2 instance role, etc.) is what signs the auth token - Tayra does not handle credentials directly.

As envelope-encryption master

Aurora pairs cleanly with the envelope encryption model - IAM auth ensures no static credentials touch the master keystore:

csharp
builder.Services
    .AddTayra(o => o.LicenseKey = config["Tayra:License"]!)

    // Data role: cheap Postgres for 15M wrapped DEKs
    .UsePostgreSqlKeyStore(config.GetConnectionString("DataKeyStore")!)

    // Master role: hardened Aurora with IAM auth, 1 master per org
    .WithAwsAuroraMasterKey(opts =>
    {
        opts.ClusterEndpoint = "master.cluster-xyz.us-east-1.rds.amazonaws.com";
        opts.Database = "tayra_master";
        opts.IamUser = "tayra_master_app";
        opts.Region = "us-east-1";

        opts.KeyIdTemplate = "tayra:master:{tenantId}";
        opts.CacheDuration = TimeSpan.FromMinutes(30);
    });

The default {tenantId} template covers per-tenant masters. To key by org id, region, environment, or any composite - set opts.MasterKeyIdResolver = sp => ... (a function with access to DI) or register your own IMasterKeyIdResolver. See Customizing the master key id.

How the IAM token is refreshed

The keystore uses NpgsqlDataSourceBuilder.UsePeriodicPasswordProvider. Behaviour:

  • On startup, AWS SDK generates the first auth token (signed with your AWS credentials, no STS call).
  • Every TokenRefreshInterval (default 13 min, AWS-issued tokens are valid for 15 min), Npgsql calls the provider again. New connections from the pool use the fresh token; existing pooled connections continue to work.
  • If token generation fails (transient AWS SDK error), Npgsql retries every TokenFailureRetryInterval (default 30 s).

You don't need to think about the token lifecycle - it's transparent to the keystore code.

Ports and security groups

Allow inbound TCP/5432 (or your Port) from your application's security group / VPC subnet. Aurora's private network is the default and recommended setup.

Cost notes

  • IAM auth itself is free.
  • Standard Aurora pricing applies (cluster instances, I/O, storage).
  • Per-subject keys at consumer scale (millions of subjects) → use envelope encryption so the Aurora cluster only holds one master per org, not 15M rows. This converts a multi-thousand-dollar Aurora bill into a baseline cluster cost.

Crypto-shredding

Identical to PostgreSQL: DELETE removes the key row from the table immediately. Aurora's storage layer reclaims pages on its normal cadence; the row is gone from any active query plane the moment the transaction commits.

See also