1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
//! SecretKeeper for Hashicorp Vault //! Uses vault's 'transit' engine for encryption and decryption. //! //! SecretKeeper uris are of the form //! - `hashivault://MYKEY` (uses the default host:port == localhost:8200) //! - `hashivault://host:port/MYKEY` //! - this form uses http for localhost and https for all other hosts //! - `hashivault:https://host:port/MYKEY` //! - this form is only needed if the http/https scheme inferred above is not orrect //! //! If host and port are not set in the uri, the VAULT_ADDR is checked. //! VAULT_ADDR may be defined of the form 'https://127.0.0.1:8200/'. //! If VAULT_ADDR is not set, 'http://localhost:8200' is used. //! //! The REST API urls used to accesss the vault server are of the form: //! http(s)://host:port/v1/transit/(encrypt|decrypt|keys)/<KEY_NAME> //! //! In the first variation above, use crate::vault_client::{ create_key as vault_create_key, decrypt, encrypt, get_base_url, renew_token, ClientSpec, URL_SCHEME, }; use async_trait::async_trait; use bytes::Bytes; use secret_keeper::{ error::{Error, Result}, keepers::{Create, SecretKeeper}, util::form_get, WrappedKey, }; use serde_urlencoded; use std::env; /// HashivaultKeeper - hashicorp vault #[derive(Debug)] pub struct HashivaultKeeper {} /// Options for initializing HashivaultKeeper pub struct HashivaultOptions<'s> { /// If the vault token is periodic, the server can auto-renew the token /// at the time keeper is constructed. It does not auto-renew during runtime, /// so you must ensure that the token ttl is either unlimited, or at least /// as long as the expected runtime of the keeper. /// The token to be renewed is specified either with the "?token=" parameter /// of initial_uri, or in the environment variable VAULT_TOKEN pub renew_on_start: bool, /// The URI provided in opts is only currently used for initialization, /// and, only if renew_on_start is true. /// If rewnew_on_start is true, the vault server address must be provided /// either in the initial_uri OR in the environment variable VAULT_ADDR /// It is ignored for wrap and unwrap calls, in favor of the uri passed /// to those functions. pub initial_uri: &'s str, } impl<'o> HashivaultOptions<'o> { pub fn defaults() -> Self { HashivaultOptions { renew_on_start: false, initial_uri: "", } } } impl HashivaultKeeper { /// Constructs a new hashivault keeper with default options pub async fn new_default() -> Result<Self, Error> { Ok(Self::new(HashivaultOptions::defaults()).await?) } /// Constructs a new hashivault keeper pub async fn new(opt: HashivaultOptions<'_>) -> Result<Self, Error> { if opt.renew_on_start { let spec = ClientSpec::from_uri(opt.initial_uri)?; renew_token(&spec).await?; } Ok(HashivaultKeeper {}) } /// register with SecretKeeper so it can be discovered with SecretKeeper::for_uri pub async fn register(self) -> Result<(), Error> { Ok(SecretKeeper::register(Box::new(self)).await?) } } #[async_trait] impl SecretKeeper for HashivaultKeeper { /// Sends key to hashicorp vault to be encrypted. /// key-encryption-key never leavs the Hashicorp vault. /// Returned encrypted key is a string async fn wrap(&self, uri: &str, _nonce: &[u8], key: &[u8]) -> Result<WrappedKey, Error> { // nonce isn't used, but vault generates new nonce per key, // so same key encrypted for different files will still be different let spec = ClientSpec::from_uri(uri)?; let val = encrypt(&spec, key).await?; Ok(WrappedKey { key_enc: val, key_uri: String::from(uri), ident: None, }) } /// Sends key to hashicorp vault to be decrypted. /// key-encryption-key never leavs the Hashicorp vault. async fn unwrap(&self, _nonce: &[u8], wk: &WrappedKey) -> Result<Bytes, Error> { let spec = ClientSpec::from_uri(&wk.key_uri)?; let plaintext = decrypt(&spec, wk.key_enc.clone()).await?; Ok(Bytes::from(plaintext.to_owned())) } /// Returns the scheme 'hashivault' fn get_scheme(&self) -> &str { URL_SCHEME } /// Returns instance of Create fn as_create(&self) -> Result<&dyn Create, Error> { Ok(self) } } #[async_trait] impl Create for HashivaultKeeper { /// Creates the key. /// `key_name` is any valid key name /// `params` are url-encoded parameters that can be created with /// [`serde_urlencoded`](https://docs.rs/serde_urlencoded/0.6.1/serde_urlencoded/fn.to_string.html) /// /// Params: /// - 'key_type' type of key (see vault documentation). If not specified, uses "aes256-gcm96". /// - 'token' vault auth token. If not specified, uses value from env variable VAULT_TOKEN. /// - 'addr' vault address. If not specified, uses value from env variable VAULT_ADDR. /// /// For the code sample below to work, /// you need VAULT_ADDR and VAULT_TOKEN set in environment and vault server running. /// ``` /// use secret_keeper::keepers::Create; /// use secret_keeper_hashivault::{HashivaultKeeper, HashivaultOptions}; /// # use tokio_test; /// # tokio_test::block_on( async { /// // create 256-bit AES-GCM key in hashivault /// let params = [ ("key_type", "aes256-gcm96") ]; /// let key_name = "my-super-secret-key"; /// let keeper = HashivaultKeeper::new(HashivaultOptions::defaults()).await /// .expect("hashivault constructor"); /// let form = serde_urlencoded::to_string(¶ms).expect("invalid param syntax"); /// let _ = keeper.create_key(key_name, &form).await.expect("create key error"); /// # }); /// ``` async fn create_key(&self, key_name: &str, params: &str) -> Result<(), Error> { let fields = serde_urlencoded::from_str(params).map_err(|e| { Error::InvalidParameter(format!("'params' is not valid urlencoded: {}", e)) })?; let base_url = form_get(&fields, "addr") .unwrap_or(&get_base_url()) .to_string(); let token = form_get(&fields, "token") .map(|s| s.to_string()) .unwrap_or( match env::var("VAULT_TOKEN") { Ok(t) => t, Err(_) => { return Err(Error::InvalidParameter( "token required: either set 'token' in params, or set VAULT_TOKEN in environment".to_string())); } } ); let key_type = form_get(&fields, "key_type").unwrap_or("aes256-gcm96"); let spec = ClientSpec { uri: "".to_string(), // not used base_url, token, key_name: key_name.to_string(), }; let _ = vault_create_key(&spec, key_type).await?; Ok(()) } }