#secrets-manager #secret #google #aws #env-var #aws-kms #api-bindings

secret-vault

Library provides a secure vault to store securely application secrets in memory from Google/AWS/K8S and environment variables

33 releases (stable)

1.12.1 Apr 13, 2024
1.12.0 Jan 2, 2024
1.10.1 Oct 1, 2023
1.8.0 Apr 13, 2023
0.10.0 Jul 31, 2022

#83 in Cryptography

Download history 54/week @ 2024-01-01 24/week @ 2024-01-08 16/week @ 2024-01-15 15/week @ 2024-01-22 32/week @ 2024-02-05 71/week @ 2024-02-12 149/week @ 2024-02-19 122/week @ 2024-02-26 162/week @ 2024-03-04 590/week @ 2024-03-11 61/week @ 2024-03-18 68/week @ 2024-03-25 111/week @ 2024-04-01 256/week @ 2024-04-08 71/week @ 2024-04-15

508 downloads per month

Apache-2.0

135KB
3K SLoC

Cargo Cargo tests and formatting security audit unsafe license

Secret Vault for Rust

Library provides the following crates:

  • General secret value type - a simple implementation of a secure and serializable (serde and proto) type of any kind of secrets. Documentation is here.
  • Secret vault - a library provides a memory-backed storage for the application secrets integrated with external source of secrets. Documentation is below.

Secret Vault

Library provides the support for the secrets coming to your application from the following sources:

  • Google Cloud Secret Manager
  • Amazon Secrets Manager
  • Environment variables
  • Files source (mostly designed to read K8S secrets mounted as files)
  • Temporarily available secret generator generated by cryptographic pseudo-random number generator

Features

  • Reading/caching registered secrets and their metadata in memory from defined sources;
  • Memory encryption using AEAD cryptography (optional);
  • Automatic refresh secrets from the sources support (optional);
  • Extensible and strongly typed API to be able to implement any kind of sources;
  • Memory encryption using Google/AWS KMS envelope encryption (optional);
  • Multi-sources support;
  • Snapshots for performance-critical secrets;

Quick start

Cargo.toml:

[dependencies]
secret-vault = { version = "1.8", features=["..."] }
secret-vault-type = { version = "0.3" }

See security consideration below about versioning.

Available optional features for Secret Vault:

  • gcp-secretmanager for Google Secret Manager support
  • aws-secretmanager for Amazon Secret Manager support
  • ring-aead-encryption for encryption support using Ring AEAD
  • gcp-kms-encryption for Google KMS envelope encryption support
  • aws-kms-encryption for Amazon KMS envelope encryption support
  • serde for serde serialization support
  • ahash for maps and snapshots based on AHashMap

Example for GCP with AEAD encryption:


// Describing secrets and marking them non-required
// since this is only example and they don't exist in your project
let secret1 = SecretVaultRef::new("test-secret1".into()).with_required(false);
let secret2 = SecretVaultRef::new("test-secret2".into())
    .with_secret_version("1".into())
    .with_required(false);

// Building the vault
let vault = SecretVaultBuilder::with_source(
    gcp::GcpSecretManagerSource::new(&config_env_var("PROJECT_ID")?).await?,
)
    .with_encryption(ring_encryption::SecretVaultRingAeadEncryption::new()?)
    .with_secret_refs(vec![&secret1, &secret2])
    .build()?;

// Load secrets from the source
vault.refresh().await?;

// Reading the secret values
let secret: Option<Secret> = vault.get_secret_by_ref(&secret1).await?;

// Or if you require it available
let secret: Secret = vault.require_secret_by_ref(&secret1).await?;
println!("Received secret: {:?}", secret);

// Using the Viewer API to share only methods able to read secrets
let vault_viewer = vault.viewer();
vault_viewer.get_secret_by_ref(&secret2).await?;

To run this example use with environment variables:

# PROJECT_ID=<your-google-project-id> cargo run --example gcloud_secret_manager_vault

All examples available at secret-vault/examples directory.

Making SecretVaultRef available globally

It is convenient to make those references globally available inside your apps since they don't contain any sensitive information. To make it easy consider using crates such as lazy_static or once_cell:

use once_cell::sync::Lazy;

pub static MY_SECRET_REF: Lazy<SecretVaultRef> = Lazy::new(|| {
   SecretVaultRef::new("my-secret".into())
});

Multiple sources

The library supports reading from multiple sources simultaneously using the concept of namespaces:


let secret_aws_namespace: SecretNamespace = "aws".into();
let secret_env_namespace: SecretNamespace = "env".into();

SecretVaultBuilder::with_source(
        MultipleSecretsSources::new()
            .add_source(&secret_env_namespace, InsecureEnvSource::new())
            .add_source(&secret_aws_namespace,
                aws::AwsSecretManagerSource::new(&config_env_var("ACCOUNT_ID")?).await?
            )
)

let secret_ref_aws = SecretVaultRef::new("test-secret-xRnpry".into()).with_namespace(secret_aws_namespace.clone());
let secret_ref_env = SecretVaultRef::new("user".into()).with_namespace(secret_env_namespace.clone());

vault.register_secrets_refs(vec![&secret_ref_aws, &secret_ref_env]).refresh().await?;

Reading secret metadata from GCP/AWS secret managers

By default reading metadata (such as labels and expiration dates) from secrets is disabled since it requires more permissions. To enable it use options (GCP example):

// Building the vault
let vault = SecretVaultBuilder::with_source(
    gcp::GcpSecretManagerSource::with_options(
        gcp::GcpSecretManagerSourceOptions::new(config_env_var("PROJECT_ID")?)
            .with_read_metadata(true),
    )
    .await?,
)

Security considerations and risks

OSS

Open source code is created through voluntary collaboration of software developers. The original authors license the code so that anyone can see it, modify it, and distribute new versions of it. You should manage all OSS using the same procedures and tools that you use for commercial products. As always, train your employees on cyber security best practices that can help them securely use and manage software products. You should not solely rely on individuals, especially on the projects like this reading sensitive information.

Versioning

Please don't use broad version dependency management not to include a new version of dependency automatically without auditing the changes.

Protect your secrets in GCP/AWS using IAM and service accounts

Don't expose all of your secrets to the apps. Use IAM and different service accounts to give access only on as-needed basis.

Zeroing, protecting memory and encryption don't provide 100% safety

There are still allocations on the protocol layers (such as the official Amazon SDK, for instance), there is a session secret key available in memory without KMS, etc.

So don't consider this is a completely safe solution for all possible attacks. The mitigation some of the attacks is not possible without implementing additional support on hardware/OS level (such as Intel SGX project, for instance).

In general, consider this as one small additional effort to mitigate some risks, but keep in mind this is not the only solution you should rely on.

The most secure setup/config at the moment available is:

  • GCP Secret Manager + KMS enveloper encryption and AEAD

because in case of GCP there are additional effort in Google Cloud SDK provided integration with this library. One of the unexpected side-effects of not having the official SDK for Rust from Google.

Snapshots

For performance critical secrets there are snapshots support reducing overheads from:

  • encryption, so snapshots are decrypted all the time
  • async runtime and possible network calls (such for KMS, etc)
  • RwLock synchronization

To make secret available for snapshotting you need to enable it explicitly using with_allow_in_snapshots for secret refs. For complete example look at hashmap_snapshot.rs and verify the difference in performance below.

Performance

Test config:

  • Up to 1000 generated secrets
  • CPU: Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz

The comparison between reading performance of encrypted, non-encrypted vault, and snapshots:

read-secrets-perf-simple-vault
time:   [143.53 ns 144.16 ns 144.77 ns]

read-secrets-perf-encrypted-vault
time:   [338.62 ns 339.29 ns 340.22 ns]

read-secrets-perf-std-hash-snapshot
time:   [89.188 ns 89.221 ns 89.266 ns]

read-secrets-perf-ahash-snapshot
time:   [68.096 ns 68.202 ns 68.339 ns]

Rotating application secrets strategy without downtime

This is mostly application specific area, but general idea is to have at least two version of secrets:

  • The current/latest version of secret which will be used for the new transactions/requests/data in your application.
  • Previous version which still need to be valid to interact.

Then you have two options for configuration/version management:

  • Use some configuration in your app that contains those versions and redeploy your app when you need to rotate. That means it will trigger refreshing all secrets at the start. Recommended for most of the cases, since this is more auditable and declarative.
  • Updating automatically secrets and their versions using SecretVaultAutoRefresher (or your own implementation) without redeploys.

Licence

Apache Software License (ASL)

Author

Abdulla Abdurakhmanov

Dependencies

~4–40MB
~602K SLoC