{"id":20186013,"url":"https://github.com/neosmart/securestore-rs","last_synced_at":"2025-04-06T06:12:21.576Z","repository":{"id":45056167,"uuid":"160423876","full_name":"neosmart/securestore-rs","owner":"neosmart","description":"A simple, encrypted, git-friendly, file-backed secrets manager for rust","archived":false,"fork":false,"pushed_at":"2024-02-28T16:55:27.000Z","size":136,"stargazers_count":145,"open_issues_count":1,"forks_count":12,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-30T05:06:57.442Z","etag":null,"topics":["cryptography","encryption","rust","secrets"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neosmart.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-12-04T21:55:26.000Z","updated_at":"2025-03-08T11:57:42.000Z","dependencies_parsed_at":"2024-12-31T14:47:29.738Z","dependency_job_id":null,"html_url":"https://github.com/neosmart/securestore-rs","commit_stats":{"total_commits":128,"total_committers":1,"mean_commits":128.0,"dds":0.0,"last_synced_commit":"54488de686ac5e17833379ad3cb7f90540f4fa74"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neosmart%2Fsecurestore-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neosmart%2Fsecurestore-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neosmart%2Fsecurestore-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neosmart%2Fsecurestore-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neosmart","download_url":"https://codeload.github.com/neosmart/securestore-rs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247441059,"owners_count":20939239,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cryptography","encryption","rust","secrets"],"created_at":"2024-11-14T03:15:37.236Z","updated_at":"2025-04-06T06:12:21.557Z","avatar_url":"https://github.com/neosmart.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SecureStore for Rust\n\n[![crates.io](https://img.shields.io/crates/v/securestore.svg)](https://crates.io/crates/securestore)\n[![docs.rs](https://docs.rs/securestore/badge.svg)](https://docs.rs/crate/securestore)\n\nThis repository houses [the `ssclient` command-line utility](./ssclient/) for creating and\nmanipulating [SecureStore secrets\nfiles](http://neosmart.net/blog/2020/securestore-open-secrets-format/) as well as [the `securestore`\nrust crate](./securestore/) providing an API for retrieving and decrypting secrets from a\nSecureStore at runtime.\n\n## Usage\n\nThis rust implementation of the SecureStore protocol ships in two separate, complementary parts: [a command line client](./ssclient/) (`ssclient`) and [a rust library/crate](./securestore/) (`securestore`). Both (mostly) expose the same functionality, but are intended to be used together for maximum productivity and ergonomics.\n\nThe typical workflow for deploying versioned secrets alongside a restricted-access binary (e.g. a web app, a kiosk, or similar where the application isn't distributed directly to end users and is running in what is considered to be a privileged environment) is demonstrated here.\n\n## Adding a SecureStore vault to your rust workspace\n\nStart by installing a copy of `ssclient`, the interactive SecureStore cli. Pre-built binaries are available (see releases), but the majority of rust developers will find the most convenient option for distribution to be direct installation via `cargo`:\n\n\u003cpre\u003e\n\u0026gt; cargo install ssclient\n\u003cb\u003e\u003cspan style=\"color:#0A0\"\u003e    Updating\u003c/span\u003e\u003c/b\u003e\u003cb\u003e\u003cspan style=\"color:#0A0\"\u003e\u003c/span\u003e\u003c/b\u003e crates.io index\n\u003cb\u003e\u003cspan style=\"color:#0A0\"\u003e  Installing\u003c/span\u003e\u003c/b\u003e ssclient v0.100.0\n\u003cb\u003e\u003cspan style=\"color:#0A0\"\u003e   Compiling\u003c/span\u003e\u003c/b\u003e securestore v0.100.0\n\u003cb\u003e\u003cspan style=\"color:#0A0\"\u003e   Compiling\u003c/span\u003e\u003c/b\u003e ssclient v0.100.0\n\u003cb\u003e\u003cspan style=\"color:#0A0\"\u003e    Finished\u003c/span\u003e\u003c/b\u003e release [optimized] target(s) in 36.14s\n\u003cb\u003e\u003cspan style=\"color:#0A0\"\u003e  Installing\u003c/span\u003e\u003c/b\u003e /home/mqudsi/.cargo/bin/ssclient\n\u003cb\u003e\u003cspan style=\"color:#0A0\"\u003e   Installed\u003c/span\u003e\u003c/b\u003e package `ssclient v0.100.0` (executable `ssclient`)\n\u003c/pre\u003e\n\n`ssclient` will be installed and added to the cargo binary directory in your `$PATH`, making it available in a terminal session merely by invoking `ssclient`.\n\nLet's imagine your worktree is a typical rust binary application with a layout matching a typical Cargo-based rust project:\n\n```sh\n\u003e tree\n.\n├── .git/\n├── Cargo.toml\n└── src\n    └─ main.rs\n```\n\nWe'll be storing our secrets in a folder called `secure` in the root of our cargo workspace. Open a terminal, `cd` into your rust project, and execute the following to create a new `secure` directory and use `ssclient` to create a new secrets vault called `secrets.json` (which is the SecureStore protocol default name):\n\n```sh\n\u003e cd my-rust-project\n\u003e mkdir secure\n\u003e cd secure\n\u003e ssclient create secrets.json --export-key secrets.key\nPassword: ************\nConfirm password: ************\nSaving newly generated key to secrets.key\nExcluding key file in newly-created VCS ignore file .gitignore\n```\n\n`ssclient` will prompt you to enter (then confirm) a password before it creates a new `secrets.json` file (currently a skeleton SecureStore vault without any secrets). The vault is symmetrically encrypted but can be decrypted either with the password you just entered or with the key file we just exported (`secrets.key`). When updating or interacting with the SecureStore vault via `ssclient` at the command line, we'll use password-based encryption/decryption, but when we deploy the app in dev or production, we'll be using key-based encryption/decryption instead.\n\nThe layout of our project folder now looks like this:\n\n```sh\n\u003e tree .\n.\n├── .git/\n├── Cargo.toml\n├── secure\n│   ├── .gitignore\n│   ├── secrets.json\n│   └── secrets.key\n└── src\n    └── main.rs\n```\n\nAs you can see, there are three new files under the `secrets` subdirectory: `secrets.json` (the human-readable SecureStore vault), `secrets.key` (the \u003cspan style=\"color: red; font-weight:bold;\"\u003esensitive \u0026amp; secure\u003c/span\u003e master key that lets us bypass password protection to decrypt secrets in production without manual intervention), and a `.gitignore` file that was helpfully created by `ssclient` and prevents `secrets.key` from being accidentally committed to the git repo.\n\nThe [SecureStore protocol](http://neosmart.net/blog/2020/securestore-open-secrets-format/) specifies that `secrets.json` _should_ be added to the git repository and versioned alongside the rest of the code that uses its secrets, and absolutely forbids `secrets.key` from being committed to a VCS.\n\nHere's what the \"empty\" `secrets.json` file looks like right now, before we've added any secrets:\n\n```json\n{\n  \"version\": 3,\n  \"iv\": \"eebJviP8bIC6XF6fp1g4Cw==\",\n  \"sentinel\": {\n    \"iv\": \"dVEOSg1OaM6LLF2fB7W7jg==\",\n    \"hmac\": \"mY1LP5gBDQaYFenC2oHHRb7LXSg=\",\n    \"payload\": \"7mFgSJSYclBBPo+Xbel0DA5y8e24QKqUh7m8EXy5+8bSagUoHGoIi2sJSKlSDP4X\"\n  },\n  \"secrets\": {}\n}\n```\n\nThis is the basic \"skeleton\" of a SecureStore (v3) vault, containing just enough info to let `ssclient` or the `securestore` crate verify that we're using the correct encryption/decryption key the next time we open the vault.\n\nThe generated `secure/.gitignore` file contains the following (one) rule:\n\n```\nsecrets.key\n```\n\nAll it does is stop `secure/secrets.key` from being accidentally added to the git repo.\n\nGo ahead and add the `secure` directory to git, then move on to the next section to see how we'll add secrets and then access them at run-time from our rust application:\n\n```sh\ngit add secure/\ngit commit -m \"Add empty SecureStore vault\"\ngit push\n```\n\n## Adding secrets to your SecureStore vault\n\nAt this point, the vault has been created and we're ready to add one or more secrets to the vault. The secrets will be stored encrypted in `secrets.json` and versioned in git alongside the same code using them, making it easy to roll back to earlier versions and make sure that managed secrets are kept in lock-step with the code that depends on them, across merges and PRs.\n\nStarting from the terminal where we left off, let's add a secret or two (say, the credentials for accessing PostgreSQL and the AWS IAM credentials to upload or sign S3 urls). We can either specify the secret value at the command line (`ssclient set secret_name secret_value`) or enter it at a prompt provided by `ssclient` for more security (e.g. to avoid the secret being stored in our bash history) or for convenience (e.g. entering special characters that would otherwise need to be escaped under bash) by only specifying the secret name (`ssclient set secret_name`). We can omit the store name/path because we're using the default (`secrets.json`) and we don't need to specify `-p` or `--password` because `ssclient` defaults to password-based decryption.\n\nLet's `cd` into the `secure` directory and set the first secret:\n\n```sh\n\u003e cd secure/\n\u003e ssclient set db:username\nPassword: ************\nValue: pgadmin\n```\n\nTo demonstrate how the SecureStore protocol allows interchangeable encryption/decryption with either a password or a key file, we'll set the next secret value using the key file we generated earlier (`secrets.key`) and we'll notice that the secret is added without `ssclient` prompting us to enter a password (but it's still encrypted, of course):\n\n```sh\n\u003e ssclient -k secrets.key set db:password pgsql123\nValue: pgsql123\n```\n\nLet's go ahead and set two more secrets, representing the AWS S3 IAM username and password:\n\n```sh\n\u003e ssclient -k secrets.key set aws:s3:access_id AKIAIOSFODNN7\n\u003e ssclient -k secrets.key set aws:s3:access_key wJalrXUtnFEMI/K7MDENG/bPxRfiCY\n```\n\nThe secrets vault (`secrets.json`) now contains four secrets (two not-actually-secret usernames and two actually-secret passwords). The only file that has changed in our git worktree is the secrets vault:\n\n```sh\n\u003e git status\nOn branch master\nChanges not staged for commit:\n  (use \"git add \u003cfile\u003e...\" to update what will be committed)\n    (use \"git restore \u003cfile\u003e...\" to discard changes in working directory)\n            modified:   secrets.json\n```\n\nLet's see what our `secrets.json` file looks like now:\n\n```json\n{\n  \"version\": 3,\n  \"iv\": \"eebJviP8bIC6XF6fp1g4Cw==\",\n  \"sentinel\": {\n    \"iv\": \"dVEOSg1OaM6LLF2fB7W7jg==\",\n    \"hmac\": \"mY1LP5gBDQaYFenC2oHHRb7LXSg=\",\n    \"payload\": \"7mFgSJSYclBBPo+Xbel0DA5y8e24QKqUh7m8EXy5+8bSagUoHGoIi2sJSKlSDP4X\"\n  },\n  \"secrets\": {\n    \"aws:s3:access_id\": {\n      \"iv\": \"4YEmTY9rwW7pq7/Iv6Vncg==\",\n      \"hmac\": \"tFO2cN/Fh/jO7ijbAXh98yrm2Nk=\",\n      \"payload\": \"ejKs4D2anovxS1OyX8e4Eg==\"\n    },\n    \"aws:s3:access_key\": {\n      \"iv\": \"czwulU+ejDXD0dryqA6aaA==\",\n      \"hmac\": \"w/egLShBDwu9L/Wagk/EKQVzpK0=\",\n      \"payload\": \"OfJM7ZDLOkGhD8OwjfOOQrHNgyeQqYPuDZKwARxN/nw=\"\n    },\n    \"db:password\": {\n      \"iv\": \"rN0a3GVuTkBctAbCO51VQA==\",\n      \"hmac\": \"ko8YB33XxhCEIge8Rxdyab1EA4Y=\",\n      \"payload\": \"uvq/NsjCfVkc6Upv8EWg8A==\"\n    },\n    \"db:username\": {\n      \"iv\": \"CkWYqyIWOTquFco/NZGiKA==\",\n      \"hmac\": \"ODcnI82hkzQ+9feyf/1WlV3yxt8=\",\n      \"payload\": \"oEmn2rQE9Hva97XwdNYkoA==\"\n    }\n  }\n}\n```\n\nWe can see there are four new named JSON objects under `secrets`, one for each secret. They're encrypted and encoded per the cross-platform SecureStore protocol in a way that's human-readable (plain-text, compact base64 representation of binary payloads, well-formatted), git-friendly (sorted by name, formatted with new lines between each secret and delimiting each secrets component, etc), and cross-platform compatible (standardized encryption and base64 encoding).\n\nIf you're following along in your own terminal, you'll hopefully notice that while the basic structure of your `secrets.json` file is the same as this one, the secret values themselves differ - this isn't just because we picked different passwords, though! This is part of what makes a SecureStore vault so secure, and prevents attackers from guessing your password or the encrypted secret contents if they get their hands on your `secrets.json` (which is safe enough to publish to GitHub or even the front page of _The New York Times_ without worry) -- as long as your `secrets.key` stays secure, of course!\n\nGo ahead and commit your updated secrets to git:\n\n```sh\n\u003e git add secrets.json\n\u003e git commit -m \"Add secrets for db and s3 access\"\n```\n\n## Retrieving secrets at runtime\n\nAt this point, we're going to set `ssclient` aside and focus on the rust side of things to see how secrets can be retrieved at runtime. First, let's add a dependency on the `securestore` crate to our `Cargo.toml`. We'll also add a dependency on `once_cell` to use `securestore::SecretsManager` as a singleton for demonstration purposes:\n\n```toml\n[package]\nname = \"sstemp\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nonce_cell = \"1.13.0\"\nsecurestore = \"0.100.0\"\n```\n\nAfter which we can open `src/main.rs` and add some code to open the secrets file and decrypt + retrieve one or more secrets at runtime. [The ssclient documentation](https://docs.rs/securestore/latest/securestore/) covers how the `securestore` crate and its primary `SecretsManager` type are used, but we'll demo the basics below.\n\nAt the top of `main.rs`, let's add some imports and a `once_cell::sync::Lazy` instance to hold our `SecretsManager`, since it's highly recommended to only have one `SecretsManager` instance servicing all secrets requests for the lifetime of the application:\n\n```rust\nuse securestore::{KeySource, SecretsManager};\nuse std::path::Path;\nuse once_cell::sync::Lazy;\n\nstatic SECRETS: Lazy\u003cSecretsManager\u003e = Lazy::new(|| {\n    let keyfile = Path::new(\"secure/secrets.key\");\n    SecretsManager::load(\"secure/secrets.json\", KeySource::File(keyfile))\n        .expect(\"Failed to load SecureStore vault!\")\n})\n```\n\nAs for accessing the secrets, let's add a helper function `get_db_credentials()` that will look up the secrets named `db:username` and `db:password` that we previously saved to the vault and return them as a `(String, String)` tuple:\n\n```rust\nfn get_db_credentials() -\u003e Result\u003c(String, String), securestore::Error\u003e {\n    let username = SECRETS.get(\"db:username\")?;\n    let password = SECRETS.get(\"db:password\")?;\n    Ok((username, password))\n}\n```\n\nand verify that the application builds without any issues:\n\n```sh\n\u003e cargo build\n   Compiling sstemp v0.1.0 (/tmp/sstemp)\n    Finished dev [unoptimized + debuginfo] target(s) in 0.74s\n```\n\nAnd that's all there is to it! We can demonstrate that it works by adding a test asserting the contents of the secrets (of course this would be a no-no in the real world!):\n\n```rust\n#[test]\nfn verify_db_credentials() -\u003e Result\u003c(), securestore::Error\u003e {\n    let expected = (\"pgadmin\".to_owned(), \"pgsql123\".to_owned());\n    let actual = get_db_credentials()?;\n\n    assert_eq!(expected, actual);\n    return Ok(());\n}\n```\n\nThen run the test:\n\n```sh\n\u003e cargo test\n    Finished test [unoptimized + debuginfo] target(s) in 0.06s\n         Running unittests src/main.rs (tar et/debug/deps/sstemp-b3b06ea1ff58c9da)\n\n         running 1 test\n         test verify_db_credentials ... ok\n```\n\n\nWe just need to make sure that when we deploy our code (via whatever method we normally deploy our app into production), we also send a copy of the `./secure` folder and store it alongside the executable (or in the CWD we execute our app under) -- this includes both the versioned and safe-to-share-with-the-world `secure/secrets.json` as well as the highly secret `secure/secrets.key` that mustn't ever be leaked to a non-secure environment.\n\n# Advanced secrets management topics\n\nYou'll notice that in the guide above, we stored two secrets (`db:password` and `aws:s3:access_key`) and two not-so-secret values (`db:username` and `aws:s3:access_id`) that could have been instead hard-coded into the application. What gives?\n\nThe answer lies in how SecureStore should be used in the real world, with teams of *n* \u003e 1 and where not everyone may have the commit bit. Ideally, your dev and production infrastructure should be completely separate, as should your secrets. The SecureStore protocol makes this easy because it's really just a glorified (but secure!) key-value database, versioned and stored alongside your code itself. In practice, you won't just have `secrets.key` and `secrets.json` - you'll actually have a vault/key pair for each of your dev/staging/production environments, with all the vaults saved under the `secure/` folder and committed to the repository, but with the corresponding `secrets.key` file for each environment's vault only shared with the people that should have access to it. Perhaps everyone has access to `secrets.dev.key` and can add/retrieve/remove secrets from `secrets.dev.json` but the production `secrets.prod.key` is only stored on the CI servers as a protected asset deployed to production but protected from being retrieved by any team members except for those with direct access to the secure CI environment or remote access to the production servers (your actual scenario will likely differ, but the basic idea stands).\n\nThe generic KV-nature of the SecureStore protocol and the SecureStore crate make it easy to abstract over the different dev/staging/prod configurations by simply loading a different store at startup depending on the environment (identified, say, by an environment variable), combined with storing any configuration-dependent non-secret values (usernames, connection strings, host addresses, etc) _as if_ they were secrets in the same SecureStore vault, all automatically resolved just by determining and loading the correct store just once. For example, your actual `SECRETS` declaration could look like this:\n\n```rust\nstatic SECRETS: Lazy\u003cSecretsManager\u003e = Lazy::new(|| {\n    let (store_path, key_path) = match std::env::var(\"MYWEBAPP_ENV\").as_ref().map(|s| s.as_str()) {\n        Ok(\"STAGING\") =\u003e (\"secure/secrets.staging.json\", Path::new(\"secure/secrets.staging.key\")),\n        Ok(\"PRODUCTION\") =\u003e (\"secure/secrets.prod.json\", Path::new(\"secure/secrets.prod.key\")),\n        _ =\u003e (\"secure/secrets.dev.json\", Path::new(\"secure/secrets.dev.key\")),\n    };\n\n    SecretsManager::load(store_path, KeySource::File(key_path))\n        .expect(\"Failed to load SecureStore vault!\")\n});\n```\n\nSo while we _could have_ hard-coded `db:username`, that would have meant also testing `MYWEBAPP_ENV` in `get_db_credentials()` to figure out what username to return in addition to the `MYWEBAPP_ENV` check in the `SECRETS` declaration that determined which secrets store to load. But if we store `db:username` as a secret the same way we store `db:password`, all that differentiation is done automatically for us. In addition, it makes sense to keep all the credentials info in one place (the secrets vault), wholly separate from the runtime logic - if you need to update the username and password in the future, you just need to update them in one place (the vault) rather than needing to patch both `secrets.json` and `src/main.rs` (in addition to remembering to do so).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneosmart%2Fsecurestore-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneosmart%2Fsecurestore-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneosmart%2Fsecurestore-rs/lists"}