├── .envrc ├── vaultrs-login ├── .gitignore ├── src │ ├── engines.rs │ ├── engines │ │ ├── approle.rs │ │ ├── userpass.rs │ │ ├── aws.rs │ │ └── oidc.rs │ ├── lib.rs │ └── method.rs ├── README.md └── Cargo.toml ├── src ├── api │ ├── aws.rs │ ├── kv1.rs │ ├── kv2.rs │ ├── pki.rs │ ├── ssh.rs │ ├── sys.rs │ ├── token.rs │ ├── auth │ │ ├── approle.rs │ │ ├── aws.rs │ │ ├── cert.rs │ │ ├── oidc.rs │ │ ├── kubernetes.rs │ │ ├── userpass.rs │ │ ├── userpass │ │ │ ├── responses.rs │ │ │ └── requests.rs │ │ ├── cert │ │ │ ├── responses.rs │ │ │ └── requests.rs │ │ ├── kubernetes │ │ │ ├── responses.rs │ │ │ └── requests.rs │ │ ├── oidc │ │ │ └── responses.rs │ │ └── approle │ │ │ └── responses.rs │ ├── cubbyhole.rs │ ├── database.rs │ ├── identity │ │ ├── entity.rs │ │ ├── group.rs │ │ ├── entity_alias.rs │ │ ├── group_alias.rs │ │ ├── group_alias │ │ │ ├── responses.rs │ │ │ └── requests.rs │ │ ├── entity_alias │ │ │ ├── responses.rs │ │ │ └── requests.rs │ │ ├── group │ │ │ └── responses.rs │ │ └── entity │ │ │ └── responses.rs │ ├── identity.rs │ ├── auth.rs │ ├── kv1 │ │ ├── responses.rs │ │ └── requests.rs │ ├── cubbyhole │ │ ├── responses.rs │ │ └── requests.rs │ ├── aws │ │ └── responses.rs │ ├── token │ │ └── responses.rs │ ├── kv2 │ │ └── responses.rs │ ├── database │ │ └── responses.rs │ ├── transit.rs │ ├── ssh │ │ └── responses.rs │ ├── transit │ │ └── responses.rs │ └── sys │ │ └── responses.rs ├── auth.rs ├── identity.rs ├── error.rs ├── identity │ ├── group_alias.rs │ ├── entity_alias.rs │ ├── group.rs │ └── entity.rs ├── cubbyhole.rs ├── kv1.rs └── auth │ ├── cert.rs │ ├── userpass.rs │ ├── kubernetes.rs │ └── oidc.rs ├── typos.toml ├── .gitignore ├── vaultrs-tests ├── tests │ ├── api_tests │ │ ├── common.rs │ │ ├── main.rs │ │ ├── client.rs │ │ ├── kv1.rs │ │ ├── cubbyhole.rs │ │ ├── oidc.rs │ │ ├── userpass.rs │ │ ├── cert.rs │ │ ├── token.rs │ │ ├── kubernetes.rs │ │ └── ssh.rs │ └── files │ │ ├── id_rsa.pub │ │ ├── csr.pem │ │ ├── aws.crt │ │ ├── root_ca.crt │ │ ├── kubernetes │ │ ├── ca.crt │ │ └── ca.key │ │ ├── id_rsa │ │ └── ca.pem └── Cargo.toml ├── .github └── workflows │ ├── typos.yml │ ├── publish.yml │ ├── audit.yml │ └── ci.yml ├── nix ├── lib │ └── toolchains.nix └── automation │ ├── devshells.nix │ └── configs.nix ├── flake.nix ├── LICENSE ├── Cargo.toml └── README.md /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /vaultrs-login/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /src/api/aws.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/kv1.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/kv2.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/pki.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/ssh.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/sys.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/token.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/auth/approle.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/auth/aws.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/auth/cert.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/auth/oidc.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/cubbyhole.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/database.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/auth/kubernetes.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/auth/userpass.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/identity/entity.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/identity/group.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/identity/entity_alias.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/identity/group_alias.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | -------------------------------------------------------------------------------- /src/api/identity.rs: -------------------------------------------------------------------------------- 1 | pub mod entity; 2 | pub mod entity_alias; 3 | pub mod group; 4 | pub mod group_alias; 5 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [default.extend-words] 2 | hashi = "hashi" 3 | 4 | [files] 5 | extend-exclude = ["vaultrs-tests/tests/files/*"] 6 | -------------------------------------------------------------------------------- /src/auth.rs: -------------------------------------------------------------------------------- 1 | pub mod approle; 2 | pub mod aws; 3 | pub mod cert; 4 | pub mod kubernetes; 5 | pub mod oidc; 6 | pub mod userpass; 7 | -------------------------------------------------------------------------------- /src/api/auth.rs: -------------------------------------------------------------------------------- 1 | pub mod approle; 2 | pub mod aws; 3 | pub mod cert; 4 | pub mod kubernetes; 5 | pub mod oidc; 6 | pub mod userpass; 7 | -------------------------------------------------------------------------------- /vaultrs-login/src/engines.rs: -------------------------------------------------------------------------------- 1 | pub mod approle; 2 | #[cfg(feature = "aws")] 3 | pub mod aws; 4 | #[cfg(feature = "oidc")] 5 | pub mod oidc; 6 | pub mod userpass; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Nix 2 | .direnv 3 | 4 | # Rust 5 | /target 6 | Cargo.lock 7 | 8 | # nixago: ignore-linked-files 9 | .prettierrc 10 | lefthook.yml 11 | treefmt.toml 12 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/common.rs: -------------------------------------------------------------------------------- 1 | mod images; 2 | mod setup; 3 | pub use images::{KUB_ACCOUNT_NAME, KUB_NAMESPACE}; 4 | pub use setup::{Test, POSTGRES_PASSWORD, POSTGRES_USER}; 5 | -------------------------------------------------------------------------------- /src/identity.rs: -------------------------------------------------------------------------------- 1 | //! Identity secrets engine 2 | //! 3 | //! The Identity secrets engine is the identity management solution for Vault. 4 | 5 | pub mod entity; 6 | pub mod entity_alias; 7 | pub mod group; 8 | pub mod group_alias; 9 | -------------------------------------------------------------------------------- /.github/workflows/typos.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | 7 | name: Typos 8 | 9 | jobs: 10 | spelling: 11 | name: Check for typos 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Actions Repository 15 | uses: actions/checkout@v2 16 | - name: Run spell check 17 | uses: crate-ci/typos@master -------------------------------------------------------------------------------- /nix/lib/toolchains.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | cell, 4 | }: let 5 | inherit (inputs) nixpkgs rust-overlay; 6 | in rec { 7 | rust-bin = 8 | (nixpkgs.appendOverlays [ 9 | (import rust-overlay) 10 | ]) 11 | .rust-bin; 12 | rustToolchain = rust-bin.selectLatestNightlyWith (toolchain: 13 | toolchain.default.override { 14 | extensions = ["rustfmt" "rust-src" "miri"]; 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/files/id_rsa.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwgnLpjB1h1IjIRytYfRmoPvCK3IcKsfZDz/0A+DADWvDWwfHao1fMlhbc0p7OT2cGFpOirkr6+hcO2WbYbLer/uJwfPazKcOCRh3E7Rlm5Ng0Uc+hL6YyDn7oQOdzqg7PKAfe6JjP7eNm5zyygZl6h9o2lUqh2EyuDbiv+OFqt7PesxmQU3ddY3X8bO6sHOFDbG8ZuIvbmIKAWIk1VayeV0IHe3CAyB+oitdAcoxegmGWrZd/M48y2ewZhhCw8e+f3DBg6n/WsW+JCbQzO7jfjOuvl1Ax0ZY3hSsiR5v93p8QQbBuoh/Z3PsebPzbaHGknpTyOtibCZTlRnkLkX5D josh@Joshuas-MacBook-Pro.local 2 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/main.rs: -------------------------------------------------------------------------------- 1 | mod approle; 2 | mod aws; 3 | mod cert; 4 | mod client; 5 | mod common; 6 | mod cubbyhole; 7 | mod database; 8 | mod identity; 9 | mod kubernetes; 10 | mod kv1; 11 | mod kv2; 12 | mod login; 13 | mod oidc; 14 | mod pki; 15 | mod ssh; 16 | mod sys; 17 | mod token; 18 | mod transit; 19 | mod userpass; 20 | 21 | // We use a single binary for integration tests because we want 22 | // them to run in parallel 23 | // https://users.rust-lang.org/t/how-to-execute-the-cargo-test-concurrently/92803/4 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: workflow_dispatch 2 | 3 | name: Manual Publish 4 | 5 | env: 6 | RUST_TOOLCHAIN: stable 7 | TOOLCHAIN_PROFILE: minimal 8 | 9 | jobs: 10 | publish: 11 | name: Publish to crates.io 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: ${{ env.TOOLCHAIN_PROFILE }} 18 | toolchain: ${{ env.RUST_TOOLCHAIN }} 19 | override: true 20 | - uses: katyo/publish-crates@v2 21 | with: 22 | registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | paths: 4 | - '**/Cargo.toml' 5 | - '**/Cargo.lock' 6 | push: 7 | branches: 8 | - master 9 | paths: 10 | - '**/Cargo.toml' 11 | - '**/Cargo.lock' 12 | schedule: 13 | - cron: '7 7 7 * *' 14 | 15 | name: Security audit 16 | 17 | jobs: 18 | audit: 19 | name: Security audit 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v2 24 | - name: Run security audit 25 | uses: actions-rs/audit-check@v1 26 | continue-on-error: true # Don't fail PRs due to potentially out-of-band fixes 27 | with: 28 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /src/api/auth/userpass/responses.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// Response from executing 4 | /// [ReadUserRequest][crate::api::auth::userpass::requests::ReadUserRequest] 5 | #[derive(Deserialize, Debug, Serialize)] 6 | pub struct ReadUserResponse { 7 | pub token_bound_cidrs: Vec, 8 | pub token_explicit_max_ttl: u64, 9 | pub token_no_default_policy: bool, 10 | pub token_num_uses: u64, 11 | pub token_ttl: u64, 12 | pub token_max_ttl: u64, 13 | pub token_period: u64, 14 | pub token_policies: Vec, 15 | pub token_type: String, 16 | } 17 | 18 | /// Response from executing 19 | /// [ListUsersRequest][crate::api::auth::userpass::requests::ListUsersRequest] 20 | #[derive(Deserialize, Debug, Serialize)] 21 | pub struct ListUsersResponse { 22 | pub keys: Vec, 23 | } 24 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "vaultrs"; 3 | inputs = { 4 | ## Nixpkgs ## 5 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 6 | 7 | ## Std ## 8 | std.url = "github:divnix/std"; 9 | std.inputs.nixpkgs.follows = "nixpkgs"; 10 | 11 | # Rust overlay 12 | rust-overlay.url = "github:oxalica/rust-overlay"; 13 | rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; 14 | }; 15 | 16 | outputs = {std, ...} @ inputs: 17 | std.growOn 18 | { 19 | inherit inputs; 20 | cellsFrom = ./nix; 21 | 22 | cellBlocks = [ 23 | (std.blockTypes.devshells "devshells") 24 | (std.blockTypes.functions "toolchains") 25 | (std.blockTypes.nixago "configs") 26 | ]; 27 | } 28 | { 29 | devShells = std.harvest inputs.self ["automation" "devshells"]; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /nix/automation/devshells.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | cell, 4 | }: let 5 | inherit (inputs) nixpkgs std; 6 | inherit (inputs.cells.lib.toolchains) rustToolchain; 7 | l = nixpkgs.lib // builtins; 8 | 9 | mkEnv = env: l.mapAttrsToList (name: value: {inherit name value;}) env; 10 | 11 | vaultrs = {...}: { 12 | name = nixpkgs.lib.mkForce "Vaultrs Devshell"; 13 | env = with nixpkgs; 14 | mkEnv { 15 | OPENSSL_NO_VENDOR = 1; 16 | OPENSSL_DIR = "${l.getDev openssl}"; 17 | OPENSSL_LIB_DIR = "${l.getLib openssl}/lib"; 18 | }; 19 | nixago = [ 20 | cell.configs.lefthook 21 | cell.configs.prettier 22 | cell.configs.treefmt 23 | ]; 24 | packages = with nixpkgs; [ 25 | gcc 26 | rustToolchain 27 | pkg-config 28 | ]; 29 | }; 30 | in 31 | l.mapAttrs (_: std.lib.dev.mkShell) rec { 32 | default = {...}: { 33 | imports = [ 34 | vaultrs 35 | ]; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/files/csr.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICWDCCAUACAQAwEzERMA8GA1UEAwwIdGVzdC5jb20wggEiMA0GCSqGSIb3DQEB 3 | AQUAA4IBDwAwggEKAoIBAQC5wP34OIilzG5rx8upXCDpNBpqTSd+OEF372hS4Uin 4 | wO9s5rdxTYeGAjLTasAUC2oEIi+6cwcHOdSXEWGvRLZQbJIWF987isEMExVAp/jm 5 | eQpmFFlv0ijoFPtB7hXBhiM8wX4QscMZR9eN7hEhTdAawN/JFaN3QoK2cVrVVLTQ 6 | jeK5upA98JBbnQeWmjP66PPbT14wQOyhP0kxspCvDHv5dEonAw7IuNgvDuF6C+4c 7 | ZvzNl2MV7Z/BkQ+AMi18MgHSW0FNU7zoLTr2uS2gPGIwW6pmwP4SQ202SYFPkLD9 8 | mZxO2z39VQZtMoFmF579IRhPE76q6vYoQUj0OgzVTWY5AgMBAAGgADANBgkqhkiG 9 | 9w0BAQsFAAOCAQEADlNK6hgrMlEqnT59KL17/EIlx7SnQfPzDpv6XpUgeI90YJUW 10 | GhD8flbQY8cp8XYEmQTKA4arePjxr3qRS5oS2UFbFZIQXKh4w3ioELUAnv1dqMlf 11 | NlhhNG50cXhxE4fKm+KWjMyB2xIQEpRCWFidqVwAZ+3QP49yMb7oXnwAJMaO/1lR 12 | ASu/+kAUrXbxM1cr2XpLgpFu46XOjsRtenZAoRrxb71dpYFP6BNb0Fj/X2xlp9O3 13 | aoEkVhrt/AypPAAVMrpb8cGbeBD1e5c/wIjn3sLbDk0WXa/uVzGdRW23gRHpxJrM 14 | jHyG8kTpCbyFwH6AV2MVDa/arXonq92pFZlr5A== 15 | -----END CERTIFICATE REQUEST----- 16 | -------------------------------------------------------------------------------- /vaultrs-login/src/engines/approle.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use vaultrs::{api::AuthInfo, client::Client, error::ClientError}; 3 | 4 | use crate::LoginMethod; 5 | 6 | /// A login method which uses AppRole credentials for obtaining a new token. 7 | #[derive(Debug)] 8 | pub struct AppRoleLogin { 9 | pub role_id: String, 10 | pub secret_id: String, 11 | } 12 | 13 | impl AppRoleLogin { 14 | pub fn new(role_id: &str, secret_id: &str) -> Self { 15 | AppRoleLogin { 16 | role_id: role_id.to_string(), 17 | secret_id: secret_id.to_string(), 18 | } 19 | } 20 | } 21 | 22 | #[async_trait] 23 | impl LoginMethod for AppRoleLogin { 24 | async fn login(&self, client: &impl Client, mount: &str) -> Result { 25 | vaultrs::auth::approle::login( 26 | client, 27 | mount, 28 | self.role_id.as_str(), 29 | self.secret_id.as_str(), 30 | ) 31 | .await 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vaultrs-login/src/engines/userpass.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use vaultrs::{api::AuthInfo, client::Client, error::ClientError}; 3 | 4 | use crate::LoginMethod; 5 | 6 | /// A login method which uses user/pass credentials for obtaining a new token. 7 | #[derive(Debug)] 8 | pub struct UserpassLogin { 9 | pub password: String, 10 | pub username: String, 11 | } 12 | 13 | impl UserpassLogin { 14 | pub fn new(username: &str, password: &str) -> Self { 15 | UserpassLogin { 16 | username: username.to_string(), 17 | password: password.to_string(), 18 | } 19 | } 20 | } 21 | 22 | #[async_trait] 23 | impl LoginMethod for UserpassLogin { 24 | async fn login(&self, client: &impl Client, mount: &str) -> Result { 25 | vaultrs::auth::userpass::login( 26 | client, 27 | mount, 28 | self.username.as_str(), 29 | self.password.as_str(), 30 | ) 31 | .await 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vaultrs-login/README.md: -------------------------------------------------------------------------------- 1 | # vaultrs-login 2 | 3 | > Adds login support for Vault clients from [vaultrs][1]. 4 | 5 | ## Installation 6 | 7 | Add `vaultrs-login` as a dependency to your cargo.toml: 8 | 9 | ```toml 10 | [dependencies] 11 | vaultrs-login = "0.2.3" 12 | ``` 13 | 14 | ## Usage 15 | 16 | ```rust 17 | use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; 18 | use vaultrs_login::LoginClient; 19 | use vaultrs_login::engines::approle::AppRoleLogin; 20 | 21 | // Create a client 22 | let mut client = VaultClient::new( 23 | VaultClientSettingsBuilder::default() 24 | .address("https://127.0.0.1:8200") 25 | .build() 26 | .unwrap() 27 | ).unwrap(); 28 | 29 | // Use one of the login flows to obtain a token for the client 30 | let role_id = String::from("my-role-id"); 31 | let secret_id = String::from("secret"); 32 | let login = AppRoleLogin { role_id, secret_id }; 33 | 34 | client.login("approle", &login).await; // Token is automatically set to client 35 | ``` 36 | 37 | ## Testing 38 | 39 | Run tests with cargo: 40 | 41 | ```bash 42 | cargo test 43 | ``` 44 | -------------------------------------------------------------------------------- /src/api/kv1/responses.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Value; 3 | 4 | /// Response from executing 5 | /// [GetSecretRequest][crate::api::kv1::requests::GetSecretRequest] 6 | #[derive(Deserialize, Debug, Serialize)] 7 | pub struct GetSecretResponse { 8 | pub data: Value, 9 | 10 | /// Auth is always null, official doc does not document this field 11 | pub auth: Option, 12 | pub lease_duration: i32, 13 | pub lease_id: String, 14 | pub renewable: bool, 15 | pub request_id: String, 16 | } 17 | 18 | /// Response from executing 19 | /// [ListSecretRequest][crate::api::kv1::requests::ListSecretRequest] 20 | #[derive(Deserialize, Debug, Serialize)] 21 | pub struct ListSecretResponse { 22 | pub data: ListSecretResponseKeys, 23 | 24 | /// Auth is always null, official doc does not document this field 25 | pub auth: Option, 26 | pub lease_duration: i32, 27 | pub lease_id: String, 28 | pub renewable: bool, 29 | } 30 | 31 | #[derive(Deserialize, Debug, Serialize)] 32 | pub struct ListSecretResponseKeys { 33 | pub keys: Vec, 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joshua Gilman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/api/cubbyhole/responses.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Value; 3 | 4 | /// Response from executing 5 | /// [GetSecretRequest][crate::api::cubbyhole::requests::GetSecretRequest] 6 | #[derive(Deserialize, Debug, Serialize)] 7 | pub struct GetSecretResponse { 8 | pub data: Value, 9 | 10 | /// Auth is always null, official doc does not document this field 11 | pub auth: Option, 12 | pub lease_duration: i32, 13 | pub lease_id: String, 14 | pub renewable: bool, 15 | pub request_id: String, 16 | } 17 | 18 | /// Response from executing 19 | /// [ListSecretRequest][crate::api::cubbyhole::requests::ListSecretRequest] 20 | #[derive(Deserialize, Debug, Serialize)] 21 | pub struct ListSecretResponse { 22 | pub data: ListSecretResponseKeys, 23 | 24 | /// Auth is always null, official doc does not document this field 25 | pub auth: Option, 26 | pub lease_duration: i32, 27 | pub lease_id: String, 28 | pub renewable: bool, 29 | } 30 | 31 | #[derive(Deserialize, Debug, Serialize)] 32 | pub struct ListSecretResponseKeys { 33 | pub keys: Vec, 34 | } 35 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/files/aws.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw 3 | FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD 4 | VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z 5 | ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u 6 | IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl 7 | cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e 8 | ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3 9 | VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P 10 | hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j 11 | k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U 12 | hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF 13 | lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf 14 | MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW 15 | MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw 16 | vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw 17 | 7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/files/root_ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC9TCCAd2gAwIBAgIQHh2t3qPz2hZjmmTQhzH+VjANBgkqhkiG9w0BAQsFADAT 3 | MREwDwYDVQQDEwh0ZXN0LmNvbTAeFw0yMTA4MTYyMzUzMjJaFw0yMTA5MTcyMzUz 4 | MjJaMBMxETAPBgNVBAMTCHRlc3QuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 5 | MIIBCgKCAQEAySFmVBpYD6tdr6TFXhop8GiZSMP3P7ebi1fbV/l7RzjYFmrHEraC 6 | jKV20cBlhvAZ/nP6s5opYDIRYA191A5YJkCVuNOsjEsR+WwcBae0hFgwuSaMIHFi 7 | +13f7MsHHfblOiqHPgfTkcrympR+c3787yoaKq46u0AlM/nuo25cmfKJ83w87C0e 8 | hILTOWDLEvJCDIRuN+uz8uvnFhmxbK8wvvBE65a/GLg3TjAikMqe9g9DhXYStDTp 9 | uLW/e+SuWaZ96Z/oj2GMPt+KiY04zT2LajrG5oaHLnrYlZMl1ZPWXxTxhneZ5al9 10 | IptlBWE1g1nqfPa1vJsH06FoYJIfBkF/SQIDAQABo0UwQzAOBgNVHQ8BAf8EBAMC 11 | AQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUdz9WcNPs+sUNv9XqfRS3 12 | XM5wQyswDQYJKoZIhvcNAQELBQADggEBABnC2JowooZmzL5b0Ir/KP+uj0YA3BhY 13 | hO8ARiAiUMYvxGb8IhSmRuISSfONRgRkLvrsURtfYj0stapVJn7k/QbNAVEBsUgl 14 | 5zJEYkQ4tKs+MyAioh8/4fO0Jt6U5bKGEFYge42mbq/7Zf/wT+2q7j7j+sSEFjo0 15 | 656EDjBMwje65y4StBuKqTxLpc4bmcdA1zwh7bYE97MzQjxGfU7t+nQ5LHMw/zYs 16 | g5QneVnJEwOQe4+kSogNLCkQwGwNvV6WL3sfqijuhaojI9oQUw2V028a4oO6Xvb1 17 | JXkbjEWChnHwZZ3FSdprhwFWsXD5hIumqbqXKxtLhTTqds4txqmb0fA= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /vaultrs-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vaultrs-tests" 3 | version = "0.0.0" 4 | edition = "2021" 5 | description = "Integration tests for vaultrs and vaultrs login" 6 | publish = false 7 | 8 | [dev-dependencies] 9 | aws-sdk-iam = { version = "1.13" } 10 | aws-sdk-sts = { version = "1.13" } 11 | aws-credential-types = { version = "1.1.5" } 12 | aws-types = { version = "1.1" } 13 | vaultrs = { path = ".."} 14 | vaultrs-login = { path = "../vaultrs-login", features = ["oidc", "aws"]} 15 | reqwest = { version = "0.12.2", default-features = false } 16 | base64 = "0.21" 17 | chrono = "0.4.38" 18 | serde_json = "1.0.94" 19 | data-encoding = "2.3.3" 20 | tracing-subscriber = { version = "0.3.16", default-features = false, features = ["env-filter", "fmt"] } 21 | testcontainers = { version = "0.23.1", features = ["http_wait"] } 22 | testcontainers-modules = { version = "0.11.3", features = ["localstack", "postgres"] } 23 | sha2 = "0.10.6" 24 | serial_test = "1.0.0" 25 | rsa = "0.9.8" 26 | rand = "0.8.0" 27 | aes-gcm = "0.10.3" 28 | aes-kw = { version = "0.2.1", features = ["alloc"] } 29 | rcgen = "0.13" 30 | tempfile = "3.10.1" 31 | tokio = { version = "1.40.0", features = ["full"] } 32 | tracing = "0.1.40" 33 | serde = "1.0.213" 34 | -------------------------------------------------------------------------------- /src/api/identity/group_alias/responses.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Response from executing 6 | /// [CreateGroupAliasRequest](crate::api::identity::group_alias::requests::CreateGroupAliasRequest) 7 | #[derive(Deserialize, Debug, Serialize)] 8 | pub struct CreateGroupAliasResponse { 9 | pub canonical_id: String, 10 | pub id: String, 11 | } 12 | 13 | /// Response from executing 14 | /// [ReadGroupAliasByIdRequest](crate::api::identity::group_alias::requests::ReadGroupAliasByIdRequest) 15 | #[derive(Deserialize, Debug, Serialize)] 16 | pub struct ReadGroupAliasByIdResponse { 17 | pub canonical_id: String, 18 | pub creation_time: String, 19 | pub id: String, 20 | pub last_update_time: String, 21 | pub metadata: Option>, 22 | pub mount_accessor: String, 23 | pub mount_path: String, 24 | pub mount_type: String, 25 | pub name: String, 26 | } 27 | 28 | /// Response from executing 29 | /// [ListGroupAliasesById](crate::api::identity::group_alias::requests::ListGroupAliasesByIdRequest) 30 | #[derive(Deserialize, Debug, Serialize)] 31 | pub struct ListGroupAliasesByIdResponse { 32 | pub keys: Vec, 33 | } 34 | -------------------------------------------------------------------------------- /src/api/auth/cert/responses.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// Response from executing 4 | /// [ReadCaCertificateRoleRequest][crate::api::auth::cert::requests::ReadCaCertificateRoleRequest] 5 | #[derive(Deserialize, Debug, Serialize)] 6 | pub struct ReadCaCertificateRoleResponse { 7 | pub allowed_common_names: Option>, 8 | pub allowed_dns_sans: Option>, 9 | pub allowed_email_sans: Option>, 10 | pub allowed_metadata_extensions: Option>, 11 | pub allowed_organizational_units: Option>, 12 | pub allowed_uri_sans: Option>, 13 | pub certificate: String, 14 | pub display_name: String, 15 | pub required_extensions: Option>, 16 | pub token_bound_cidrs: Vec, 17 | pub token_explicit_max_ttl: u64, 18 | pub token_max_ttl: u64, 19 | pub token_no_default_policy: bool, 20 | pub token_num_uses: u64, 21 | pub token_period: u64, 22 | pub token_policies: Vec, 23 | pub token_ttl: u64, 24 | pub token_type: String, 25 | } 26 | 27 | /// Response from executing 28 | /// [ListCaCertificateRoleRequest][crate::api::auth::cert::requests::ListCaCertificateRoleRequest] 29 | #[derive(Deserialize, Debug, Serialize)] 30 | pub struct ListCaCertificateRoleResponse { 31 | pub keys: Vec, 32 | } 33 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/files/kubernetes/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDezCCAmOgAwIBAgIUcLPiS5kp///dxHXs9ie6XHmzyBEwDQYJKoZIhvcNAQEL 3 | BQAwTDEkMCIGA1UECgwbZG9ja2VydGVzdC1zZXJ2ZXItd2Vic2VydmVyMSQwIgYD 4 | VQQDDBtkb2NrZXJ0ZXN0LXNlcnZlci13ZWJzZXJ2ZXIwIBcNMjExMjAyMTYwMjIy 5 | WhgPMjA1MTExMjUxNjAyMjJaMEwxJDAiBgNVBAoMG2RvY2tlcnRlc3Qtc2VydmVy 6 | LXdlYnNlcnZlcjEkMCIGA1UEAwwbZG9ja2VydGVzdC1zZXJ2ZXItd2Vic2VydmVy 7 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8UCQocmvS++G/TsuzJbx 8 | CT2P6Mnklp9D5AG92tg0VFbbKZg9wBYt7YlVRX/Iw5fXV+zQJjul+cymPMSpQ24j 9 | Jv48nDRkHGxguEGLu50Sap57fMCmek+Vr2Ph6tDS5FQgVVdz2041IjNBKT5Hci8i 10 | OEyYUlU7S7eKwBms8691FmG09NZXaoe4ccalxvVFHHHJLK+TME+XPJpy1tEB86O6 11 | ROevlD+XZ71YZPKqm8pG0k3RcS1nqxXQ9XS3X+J2G1kQrEXp9yPgYGSHDZs9G8CL 12 | Vrw8ELU9Yt2eDDQLpqsdtVbIhPgnTa7AZEGHYE6pmYIIZYYmrCitb2TH+qtcKHxk 13 | 0QIDAQABo1MwUTAdBgNVHQ4EFgQUlluteUFgRRmSnL8rAeqKauA2jz0wHwYDVR0j 14 | BBgwFoAUlluteUFgRRmSnL8rAeqKauA2jz0wDwYDVR0TAQH/BAUwAwEB/zANBgkq 15 | hkiG9w0BAQsFAAOCAQEA7XwmpLAS7/CRtjt7nbonkWFZdYv5qLAquxfgdrYPtTpU 16 | mLxSCW0qL33SpAtOB2meBeegBNAkeG5N3HTgCmvf3aPFgQXcjvIxkLmAFCIzVPQr 17 | ehKcklLFVsCyhMVkegMaaz3J4yr/gDFVfDfmBYr4gqzplFHCRBXmZa9zgERojdMX 18 | lKft/Fsu120BmChAPUIpVRJoQ0if4S5qSg/ewhln/DTFsZfhP7ofdc2rBtO9amiD 19 | SZaguCEaKvcLcGUzRmSionncnCLRln6891HvLjPWR41zo2krhLvw7CmH1OSRtbGM 20 | Vy0jQ+cmHcDjSKXY7wisCQ3SBmg1qlMA5c9wLBDEOQ== 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vaultrs" 3 | version = "0.7.4" 4 | authors = ["Joshua Gilman "] 5 | description = "An asynchronous Rust client library for the Hashicorp Vault API." 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/jmgilman/vaultrs" 9 | keywords = ["Vault", "API", "Client", "Hashicorp"] 10 | edition = "2021" 11 | 12 | [package.metadata.docs.rs] 13 | all-features = true 14 | rustdoc-args = ["--cfg", "docsrs"] 15 | 16 | [workspace] 17 | members = [ 18 | "vaultrs-login", "vaultrs-tests" 19 | ] 20 | 21 | [features] 22 | default = [ "rustls" ] 23 | rustls = [ "reqwest/rustls-tls", "rustify/rustls-tls" ] 24 | native-tls = [ "reqwest/default-tls", "reqwest/native-tls", "rustify/default" ] 25 | native-tls-vendored = [ "reqwest/native-tls-vendored", "rustify/default" ] 26 | 27 | [dependencies] 28 | async-trait = "0.1.68" 29 | bytes = "1.4.0" 30 | derive_builder = "0.12.0" 31 | http = "1" 32 | reqwest = { version = "0.12.2", default-features = false } 33 | rustify = { version = "0.6.0", default-features = false } 34 | rustify_derive = "0.5.2" 35 | serde = { version = "1.0.158", features = ["derive"] } 36 | serde_json = "1.0.94" 37 | thiserror = "1.0.40" 38 | url = "2.3.1" 39 | tracing = { version = "0.1.37", features = ["log"] } 40 | 41 | [dev-dependencies] 42 | tokio = { version = "1", default-features = false, features = ["macros", "rt-multi-thread"]} 43 | 44 | -------------------------------------------------------------------------------- /src/api/auth/kubernetes/responses.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// Response from executing 4 | /// [ReadKubernetesAuthConfigRequest][crate::api::auth::kubernetes::requests::ReadKubernetesAuthConfigRequest] 5 | #[derive(Deserialize, Debug, Serialize)] 6 | pub struct ReadKubernetesAuthConfigResponse { 7 | pub kubernetes_host: String, 8 | pub kubernetes_ca_cert: Option, 9 | pub pem_keys: Option>, 10 | pub issuer: Option, 11 | pub disable_iss_validation: bool, 12 | pub disable_local_ca_jwt: bool, 13 | } 14 | 15 | /// Response from executing 16 | /// [ListRolesRequest][crate::api::auth::kubernetes::requests::ListRolesRequest] 17 | #[derive(Deserialize, Debug, Serialize)] 18 | pub struct ListRolesResponse { 19 | pub keys: Vec, 20 | } 21 | 22 | /// Response from executing 23 | /// [ReadKubernetesRoleRequest][crate::api::auth::kubernetes::requests::ReadKubernetesRoleRequest] 24 | #[derive(Deserialize, Debug, Serialize)] 25 | pub struct ReadKubernetesRoleResponse { 26 | pub bound_service_account_names: Vec, 27 | pub bound_service_account_namespaces: Vec, 28 | pub token_ttl: u64, 29 | pub token_max_ttl: u64, 30 | pub token_policies: Vec, 31 | pub token_bound_cidrs: Vec, 32 | pub token_explicit_max_ttl: u64, 33 | pub token_no_default_policy: bool, 34 | pub token_num_uses: u64, 35 | pub token_period: u64, 36 | pub token_type: String, 37 | } 38 | -------------------------------------------------------------------------------- /vaultrs-login/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vaultrs-login" 3 | version = "0.2.3" 4 | authors = ["Joshua Gilman "] 5 | description = "Adds login support for Vault clients from vaultrs." 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/jmgilman/vaultrs" 9 | keywords = ["Vault", "API", "Client", "Hashicorp", "Login"] 10 | edition = "2018" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [features] 15 | aws = ["aws-sdk-sts", "aws-sigv4", "aws-types", "aws-credential-types", "aws-smithy-runtime-api", "base64", "http", "serde_json"] 16 | oidc = ["tiny_http", "tokio"] 17 | 18 | [dependencies] 19 | async-trait = "0.1.68" 20 | aws-credential-types = { version = "1.1.5", optional = true } 21 | aws-sdk-sts = { version = "1.13", optional = true } 22 | aws-sigv4 = { version = "1.1", optional = true } 23 | aws-smithy-runtime-api = { version = "1.1.5", optional = true } 24 | aws-types = { version = "1.1", optional = true } 25 | base64 = { version = "0.21", optional = true } 26 | http = { version = "0.2", optional = true } 27 | serde = "1.0.158" 28 | serde_json = { version = "1", optional = true } 29 | tiny_http = { version = "0.12.0", optional = true } 30 | tokio = { version = "1.26.0", optional = true } 31 | tracing = "0.1.37" 32 | url = "2.3.1" 33 | vaultrs = { version = "0.7.4", path = ".." } 34 | 35 | [dev-dependencies] 36 | reqwest = "0.11.15" 37 | tokio-test = "0.4.2" 38 | tracing-test = "0.2.4" 39 | -------------------------------------------------------------------------------- /src/api/identity/entity_alias/responses.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Response from executing 6 | /// [CreateEntityAliasRequest](crate::api::identity::entity_alias::requests::CreateEntityAliasRequest) 7 | #[derive(Deserialize, Debug, Serialize)] 8 | pub struct CreateEntityAliasResponse { 9 | pub canonical_id: String, 10 | pub id: String, 11 | } 12 | 13 | /// Response from executing 14 | /// [ReadEntityAliasByIdRequest](crate::api::identity::entity_alias::requests::ReadEntityAliasByIdRequest) 15 | #[derive(Deserialize, Debug, Serialize)] 16 | pub struct ReadEntityAliasByIdResponse { 17 | pub creation_time: String, 18 | pub canonical_id: String, 19 | pub custom_metadata: Option>, 20 | pub id: String, 21 | pub last_update_time: String, 22 | pub local: bool, 23 | pub metadata: Option>, 24 | pub mount_accessor: String, 25 | pub mount_path: String, 26 | pub mount_type: String, 27 | pub name: String, 28 | } 29 | 30 | /// Response from executing 31 | /// [ListEntityAliasesById](crate::api::identity::entity_alias::requests::ListEntityAliasesByIdRequest) 32 | #[derive(Deserialize, Debug, Serialize)] 33 | pub struct ListEntityAliasesByIdResponse { 34 | pub key_info: HashMap, 35 | pub keys: Vec, 36 | } 37 | 38 | #[derive(Deserialize, Debug, Serialize)] 39 | pub struct KeyInfo { 40 | pub canonical_id: String, 41 | pub custom_metadata: Option>, 42 | pub local: bool, 43 | pub mount_accessor: String, 44 | pub mount_path: String, 45 | pub mount_type: String, 46 | pub name: String, 47 | } 48 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | /// The common error type returned by this crate 4 | #[derive(Error, Debug)] 5 | pub enum ClientError { 6 | #[error("The Vault server returned an error (status code {code})")] 7 | APIError { code: u16, errors: Vec }, 8 | #[error("Failed to find file: {path}")] 9 | FileNotFoundError { path: String }, 10 | #[error("Error reading file: {path}")] 11 | FileReadError { 12 | source: std::io::Error, 13 | path: String, 14 | }, 15 | #[error("Error writing file: {path}")] 16 | FileWriteError { 17 | source: std::io::Error, 18 | path: String, 19 | }, 20 | #[error("Invalid login method")] 21 | InvalidLoginMethodError, 22 | #[error("Error parsing value into JSON")] 23 | JsonParseError { source: serde_json::error::Error }, 24 | #[error("Error parsing CA certificate as PEM encoded certificate: {path}")] 25 | ParseCertificateError { 26 | source: reqwest::Error, 27 | path: String, 28 | }, 29 | #[error("The request returned an empty response")] 30 | ResponseEmptyError, 31 | #[error("The result contained an empty data field")] 32 | ResponseDataEmptyError, 33 | #[error("Error parsing response wrapping result")] 34 | ResponseWrapError, 35 | #[error("Error configuring REST client")] 36 | RestClientBuildError { source: reqwest::Error }, 37 | #[error("An error occurred with the request")] 38 | RestClientError { 39 | #[from] 40 | source: rustify::errors::ClientError, 41 | }, 42 | #[error("The wrapped response doesn't exist or is not longer valid")] 43 | WrapInvalidError, 44 | #[error("The parameters given to the endpoint didn't update anything")] 45 | InvalidUpdateParameter, 46 | } 47 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/files/kubernetes/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA8UCQocmvS++G/TsuzJbxCT2P6Mnklp9D5AG92tg0VFbbKZg9 3 | wBYt7YlVRX/Iw5fXV+zQJjul+cymPMSpQ24jJv48nDRkHGxguEGLu50Sap57fMCm 4 | ek+Vr2Ph6tDS5FQgVVdz2041IjNBKT5Hci8iOEyYUlU7S7eKwBms8691FmG09NZX 5 | aoe4ccalxvVFHHHJLK+TME+XPJpy1tEB86O6ROevlD+XZ71YZPKqm8pG0k3RcS1n 6 | qxXQ9XS3X+J2G1kQrEXp9yPgYGSHDZs9G8CLVrw8ELU9Yt2eDDQLpqsdtVbIhPgn 7 | Ta7AZEGHYE6pmYIIZYYmrCitb2TH+qtcKHxk0QIDAQABAoIBAQCA2vdlMpAKgvka 8 | sE2iwMwlH1iKAauyN4bPdY6uIORyV6HfaY0x16bYRI3i/QmCTsuaDsZuZVWVQpVX 9 | x8KQXXcxEIHW/LSr6ccPIxM7Nf3sLJHQZCWHFpLghvzXqom5oqVSb89Vx0Ph7HZV 10 | TQpkFoC0iVSiRRvSqUnWqOATmBd1x9ihQGDqlBa0Zi5wWBYCKlQMsdGDewwxEEHG 11 | 0c2TItd8LVRxX3LPsFO17fRmMVki7AQ01dtXcdzJtBTl6XPvC7vAByOo8lFLRDf/ 12 | iUYbtAGPh3ZMYV4Yf3WY+3tx8PETjvfsfSItbk0VXEEHiPcv1lFScts3wrW8DHcN 13 | YsGUK7JBAoGBAPlCZLrey6ptqERiy56h8I6RXhQy5ebQ2FpZcliodEnCyaDIUkrS 14 | 588aChwX4e0bQK9CzTN5EdD0X7mQ/ssUg5qLhC6c0B5S5BNFmBl+trsio/PEwbPS 15 | cgG47RrQz2oZ0HdNMrCN2ZxfKdmel1fWTSx3VHLYOS6xolw4+rt3zI4lAoGBAPfG 16 | vRIJZsy/TqhfK8xCCuGQtv2XQSVRfCiHDkxY9fdCKaNE5GeWxWv+ONdiqZsf43QG 17 | 7TS2YVjhZM5YCw83obDmjMFoYkBjaLuZ5iL2c6Twxj3LtIGuZOVtZJJdTOvFanEy 18 | PBGKrMgE6D2oXiJHGdHzBQyJFxgCz8RpTQ4qbo49AoGAWte8uHJsjb+LXOkYxsbE 19 | UmFehUQLj/S7dSo2R2OVhjBspaF8hHKbM1qNJrH5kB2nlHhnKwRL3vjBTnMuuTrP 20 | v+prEUXrf7G8F938UgZheJBmanhiFDR1gnUwTN6fSU8BMAm9mKUKEziHmx12kPe0 21 | hqky6Owu4vqwbqYBk4NU1NUCgYANI1GVKkB0LNSr4tf8rafMDBNX4PRIWUi/EWI1 22 | tCXepXh1usptn7X6IvG6ofWiTw+NcGyVdfI1d0YUFuEHPojpS2A9RR6okVzVTbTB 23 | N9Yr8cRhNHLuyWN6MtG5XQ8eFUquk95Rg8vjkzcJResv4BtDYaJr5rz9vfBsVrUK 24 | Qj+4dQKBgQDzkxNeu7CqChqqqrcvly4+CB910cSsnnRJhbJNxKZMU92F4S18IAfw 25 | MtV6sXABpG5qGD26NOi5fh8JP6fUWCD17qjqobWtwUu81kMBolP+vokA4XtxhWhZ 26 | X0wLi1EH63l9psffefmbYbROuxFEYJ/lmCWOzE+MoDS3PW9FeGKc+w== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/api/aws/responses.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// Response from executing 4 | /// [GetConfigurationRequest][crate::api::aws::requests::GetConfigurationRequest] 5 | #[derive(Deserialize, Debug, Serialize)] 6 | pub struct GetConfigurationResponse { 7 | pub access_key: String, 8 | pub region: String, 9 | pub iam_endpoint: String, 10 | pub sts_endpoint: String, 11 | pub max_retries: u32, 12 | } 13 | 14 | /// Response from executing 15 | /// [RotateRootCredentialsRequest][crate::api::aws::requests::RotateRootCredentialsRequest] 16 | #[derive(Deserialize, Debug, Serialize)] 17 | pub struct RotateRootCredentialsResponse { 18 | pub access_key: String, 19 | } 20 | 21 | /// Response from executing 22 | /// [ReadLeaseRequest][crate::api::aws::requests::ReadLeaseRequest] 23 | 24 | #[derive(Deserialize, Debug, Serialize)] 25 | pub struct ReadLeaseResponse { 26 | pub lease: String, 27 | pub lease_max: String, 28 | } 29 | 30 | /// Response from executing 31 | /// [ReadRoleRequest][crate::api::aws::requests::ReadRoleRequest] 32 | #[derive(Deserialize, Debug, Serialize)] 33 | pub struct ReadRoleResponse { 34 | pub policy_document: Option, 35 | pub policy_arns: Option>, 36 | pub credential_type: String, 37 | pub role_arns: Option>, 38 | pub iam_groups: Option>, 39 | } 40 | 41 | /// Response from executing 42 | /// [ListRolesRequest][crate::api::aws::requests::ListRolesRequest] 43 | #[derive(Deserialize, Debug, Serialize)] 44 | pub struct ListRolesResponse { 45 | pub keys: Vec, 46 | } 47 | 48 | /// Response from executing 49 | /// [GenerateCredentialsRequest][crate::api::aws::requests::GenerateCredentialsRequest] 50 | #[derive(Deserialize, Debug, Serialize)] 51 | pub struct GenerateCredentialsResponse { 52 | pub access_key: String, 53 | pub secret_key: String, 54 | pub security_token: Option, 55 | pub arn: String, 56 | } 57 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/files/id_rsa: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn 3 | NhAAAAAwEAAQAAAQEAsIJy6YwdYdSIyEcrWH0ZqD7wityHCrH2Q8/9APgwA1rw1sHx2qNX 4 | zJYW3NKezk9nBhaToq5K+voXDtlm2Gy3q/7icHz2synDgkYdxO0ZZuTYNFHPoS+mMg5+6E 5 | Dnc6oOzygH3uiYz+3jZuc8soGZeofaNpVKodhMrg24r/jharez3rMZkFN3XWN1/GzurBzh 6 | Q2xvGbiL25iCgFiJNVWsnldCB3twgMgfqIrXQHKMXoJhlq2XfzOPMtnsGYYQsPHvn9wwYO 7 | p/1rFviQm0Mzu434zrr5dQMdGWN4UrIkeb/d6fEEGwbqIf2dz7Hmz822hxpJ6U8jrYmwmU 8 | 5UZ5C5F+QwAAA9iJaz3wiWs98AAAAAdzc2gtcnNhAAABAQCwgnLpjB1h1IjIRytYfRmoPv 9 | CK3IcKsfZDz/0A+DADWvDWwfHao1fMlhbc0p7OT2cGFpOirkr6+hcO2WbYbLer/uJwfPaz 10 | KcOCRh3E7Rlm5Ng0Uc+hL6YyDn7oQOdzqg7PKAfe6JjP7eNm5zyygZl6h9o2lUqh2EyuDb 11 | iv+OFqt7PesxmQU3ddY3X8bO6sHOFDbG8ZuIvbmIKAWIk1VayeV0IHe3CAyB+oitdAcoxe 12 | gmGWrZd/M48y2ewZhhCw8e+f3DBg6n/WsW+JCbQzO7jfjOuvl1Ax0ZY3hSsiR5v93p8QQb 13 | Buoh/Z3PsebPzbaHGknpTyOtibCZTlRnkLkX5DAAAAAwEAAQAAAQEAquLQj+2YMLPMbWIB 14 | xBzxryKoTTguAZvD2WlPABZInab4zmJcXZmQkkCpB8dd+k2DZ7CJ5JErhIqFslmmKVgwBn 15 | OxFMjXKyP+5WwuvA30btqVuymNg/cWxxWjpgXYTvHQfpy56gR0lbIxW9n5uLSiO6+1Au1M 16 | oR5BNq2QJcYlruNpv+2DgAk9hWDo3SUMSJceaQTxhZeCLPo9s7BQuXuyYDLjFkn2CP1T+R 17 | xyl03/0jijgGBBGliso8UL44Vxjg3D0QOjrdgY+KU2Qe0hEjMhCMkSfEi6TTLwKv88vl/D 18 | wFvGYUrgTVd0M9edKCmYV1QQU8yjnqzmzed5OVjEPVXyQQAAAIEAvBQq4A0YNDMUNTjPnf 19 | kbbNKkFKcBNhBgPT/ocHnNGKFQJsmGITZdPmbAKNprlqz1AK5ObeMYjrUNckIOS6rMRqKT 20 | HHXOFMFPJj3kJLnpwN0xqbXf7jhuHZLVlxhdwQBRmyvJ6tJC+3IUwygdHVDLahENb8vKec 21 | 8HlKf7Sf8zd/MAAACBANbCbJRQGNb6eL6nlhGOq02SRdoucrBUzFBfPqUYserGu4zuVRXs 22 | 7J1+b067zEmgBzdSZCGR3KBwT7jCahGUl6ezF1gUSaVb8qeS+5puJHCqoXeISZ0wlwhHW/ 23 | eC4gWyQUQypQNWmduLQpIcZpYAXep8Ig2uYgG5xalqJfqIT5AhAAAAgQDSZ6jQIy4Rcwac 24 | gAwzgyVIud1Dq1zO3AvAlYLjrlQKGMGNVc/X/lc72nfdigRuU48JjIvJJzoSfmkZ16zGUN 25 | fiARkepJxW3QXJ8AeXt4Z9lSTMYCoIqZ0Lf9gnJQ/Ceak2zpTi/0B+Ir8OxZ0oXa4jTppM 26 | i9Yz59Y2//Zs+CKR4wAAAB5qb3NoQEpvc2h1YXMtTWFjQm9vay1Qcm8ubG9jYWwBAgM= 27 | -----END OPENSSH PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/api/token/responses.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Response from executing 6 | /// [ListAccessorRequest][crate::api::token::requests::ListAccessorRequest] 7 | #[derive(Deserialize, Debug, Serialize)] 8 | pub struct ListAccessorResponse { 9 | pub keys: Vec, 10 | } 11 | 12 | /// Response from executing 13 | /// [LookupTokenRequest][crate::api::token::requests::LookupTokenRequest] 14 | #[derive(Deserialize, Debug, Serialize)] 15 | pub struct LookupTokenResponse { 16 | pub accessor: String, 17 | pub creation_time: u64, 18 | pub creation_ttl: u64, 19 | pub display_name: String, 20 | pub entity_id: String, 21 | pub expire_time: Option, 22 | pub explicit_max_ttl: u64, 23 | pub id: String, 24 | pub identity_policies: Option>, 25 | pub issue_time: Option, 26 | pub meta: Option>, 27 | pub num_uses: u64, 28 | pub orphan: bool, 29 | pub path: String, 30 | pub policies: Vec, 31 | pub renewable: Option, 32 | pub role: Option, 33 | pub ttl: u64, 34 | } 35 | 36 | /// Response from executing 37 | /// [ReadTokenRoleRequest][crate::api::token::requests::ReadTokenRoleRequest] 38 | #[derive(Deserialize, Debug, Serialize)] 39 | pub struct ReadTokenRoleResponse { 40 | pub allowed_entity_aliases: Option>, 41 | pub allowed_policies: Vec, 42 | pub disallowed_policies: Vec, 43 | pub explicit_max_ttl: u64, 44 | pub name: String, 45 | pub orphan: bool, 46 | pub path_suffix: String, 47 | pub period: u64, 48 | pub renewable: bool, 49 | pub token_explicit_max_ttl: u64, 50 | pub token_period: u64, 51 | pub token_type: String, 52 | } 53 | 54 | /// Response from executing 55 | /// [ListTokenRolesRequest][crate::api::token::requests::ListTokenRolesRequest] 56 | #[derive(Deserialize, Debug, Serialize)] 57 | pub struct ListTokenRolesResponse { 58 | pub keys: Vec, 59 | } 60 | -------------------------------------------------------------------------------- /src/api/kv2/responses.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use serde_json::Value; 5 | 6 | /// Response from executing 7 | /// [ReadConfigurationRequest][crate::api::kv2::requests::ReadConfigurationRequest] 8 | #[derive(Deserialize, Debug, Serialize)] 9 | pub struct ReadConfigurationResponse { 10 | pub cas_required: bool, 11 | pub delete_version_after: String, 12 | pub max_versions: u64, 13 | } 14 | 15 | /// Response from executing 16 | /// [ReadSecretRequest][crate::api::kv2::requests::ReadSecretRequest] 17 | #[derive(Deserialize, Debug, Serialize)] 18 | pub struct ReadSecretResponse { 19 | pub data: Value, 20 | pub metadata: SecretVersionMetadata, 21 | } 22 | 23 | /// Response from executing 24 | /// [ReadSecretRequest][crate::api::kv2::requests::ReadSecretRequest] 25 | #[derive(Deserialize, Debug, Serialize)] 26 | pub struct SecretVersionMetadata { 27 | pub created_time: String, 28 | pub deletion_time: String, 29 | pub custom_metadata: Option>, 30 | pub destroyed: bool, 31 | pub version: u64, 32 | } 33 | 34 | /// Response from executing 35 | /// [ListSecretsRequest][crate::api::kv2::requests::ListSecretsRequest] 36 | #[derive(Deserialize, Debug, Serialize)] 37 | pub struct ListSecretsResponse { 38 | pub keys: Vec, 39 | } 40 | 41 | /// Response from executing 42 | /// [ReadSecretMetadataRequest][crate::api::kv2::requests::ReadSecretMetadataRequest] 43 | #[derive(Deserialize, Debug, Serialize)] 44 | pub struct ReadSecretMetadataResponse { 45 | pub cas_required: bool, 46 | pub created_time: String, 47 | pub current_version: u64, 48 | pub delete_version_after: String, 49 | pub max_versions: u64, 50 | pub oldest_version: u64, 51 | pub updated_time: String, 52 | pub custom_metadata: Option>, 53 | pub versions: HashMap, 54 | } 55 | 56 | /// Response from executing 57 | /// [ReadSecretMetadataRequest][crate::api::kv2::requests::ReadSecretMetadataRequest] 58 | #[derive(Deserialize, Debug, Serialize)] 59 | pub struct SecretMetadata { 60 | pub created_time: String, 61 | pub deletion_time: String, 62 | pub destroyed: bool, 63 | } 64 | -------------------------------------------------------------------------------- /nix/automation/configs.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | cell, 4 | }: let 5 | inherit (inputs) nixpkgs std; 6 | l = nixpkgs.lib // builtins; 7 | inherit (inputs.cells.lib.toolchains) rustToolchain; 8 | in { 9 | # TODO: Potentially enable this 10 | conform = std.std.nixago.conform { 11 | data = { 12 | commit = { 13 | header = {length = 89;}; 14 | conventional = { 15 | types = [ 16 | "build" 17 | "chore" 18 | "ci" 19 | "docs" 20 | "feat" 21 | "fix" 22 | "perf" 23 | "refactor" 24 | "style" 25 | "test" 26 | ]; 27 | scopes = [ 28 | "devshell" 29 | "jormungandr" 30 | ]; 31 | }; 32 | }; 33 | }; 34 | }; 35 | lefthook = std.std.nixago.lefthook { 36 | data = { 37 | # TODO: Potentially enable this 38 | # commit-msg = { 39 | # commands = { 40 | # conform = { 41 | # run = "${nixpkgs.conform}/bin/conform enforce --commit-msg-file {1}"; 42 | # }; 43 | # }; 44 | # }; 45 | pre-commit = { 46 | commands = { 47 | treefmt = { 48 | run = "${nixpkgs.treefmt}/bin/treefmt --fail-on-change {staged_files}"; 49 | }; 50 | rustfmt = { 51 | run = "${rustToolchain}/bin/cargo fmt"; 52 | }; 53 | }; 54 | }; 55 | }; 56 | }; 57 | prettier = 58 | std.lib.dev.mkNixago 59 | { 60 | data = { 61 | printWidth = 80; 62 | proseWrap = "always"; 63 | }; 64 | output = ".prettierrc"; 65 | format = "json"; 66 | packages = with nixpkgs; [nodePackages.prettier]; 67 | }; 68 | treefmt = 69 | std.std.nixago.treefmt 70 | { 71 | data = { 72 | formatter = { 73 | nix = { 74 | command = "alejandra"; 75 | includes = ["*.nix"]; 76 | }; 77 | prettier = { 78 | command = "prettier"; 79 | options = ["--write"]; 80 | includes = [ 81 | "*.md" 82 | ]; 83 | }; 84 | }; 85 | }; 86 | packages = with nixpkgs; [alejandra]; 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /src/api/identity/group/responses.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Response from executing 6 | /// [CreateGroupRequest](crate::api::identity::group::requests::CreateGroupRequest) 7 | #[derive(Deserialize, Debug, Serialize)] 8 | pub struct CreateGroupResponse { 9 | pub id: String, 10 | pub name: String, 11 | } 12 | 13 | /// Response from executing 14 | /// [ReadGroupByIdRequest](crate::api::identity::group::requests::ReadGroupByIdRequest) 15 | #[derive(Deserialize, Debug, Serialize)] 16 | pub struct ReadGroupByIdResponse { 17 | // TODO What's the type of Alias? 18 | // pub alias: Option, 19 | pub creation_time: String, 20 | pub id: String, 21 | pub last_update_time: String, 22 | pub member_entity_ids: Option>, 23 | pub member_group_ids: Option>, 24 | pub parent_group_ids: Option>, 25 | pub metadata: Option>, 26 | pub modify_index: u64, 27 | pub namespace_id: String, 28 | pub name: String, 29 | pub policies: Option>, 30 | #[serde(rename = "type")] 31 | pub group_type: String, 32 | } 33 | 34 | /// Response from executing 35 | /// [ListGroupsById](crate::api::identity::group::requests::ListGroupsByIdRequest) 36 | #[derive(Deserialize, Debug, Serialize)] 37 | pub struct ListGroupsByIdResponse { 38 | pub keys: Vec, 39 | } 40 | 41 | /// Response from executing 42 | /// [ReadGroupByNameRequest](crate::api::identity::group::requests::ReadGroupByNameRequest) 43 | #[derive(Deserialize, Debug, Serialize)] 44 | pub struct ReadGroupByNameResponse { 45 | // TODO What's the type of Alias? 46 | // pub alias: Option, 47 | pub creation_time: String, 48 | pub id: String, 49 | pub last_update_time: String, 50 | pub member_entity_ids: Option>, 51 | pub member_group_ids: Option>, 52 | pub parent_group_ids: Option>, 53 | pub metadata: Option>, 54 | pub modify_index: u64, 55 | pub namespace_id: String, 56 | pub name: String, 57 | pub policies: Option>, 58 | #[serde(rename = "type")] 59 | pub group_type: String, 60 | } 61 | 62 | /// Response from executing 63 | /// [ListGroupsByName](crate::api::identity::group::requests::ListGroupsByNameRequest) 64 | #[derive(Deserialize, Debug, Serialize)] 65 | pub struct ListGroupsByNameResponse { 66 | pub keys: Vec, 67 | } 68 | -------------------------------------------------------------------------------- /src/api/auth/oidc/responses.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Response from executing 6 | /// [ReadConfigurationRequest][crate::api::auth::oidc::requests::ReadConfigurationRequest] 7 | #[derive(Deserialize, Debug, Serialize)] 8 | pub struct ReadConfigurationResponse { 9 | pub bound_issuer: Option, 10 | pub default_role: Option, 11 | pub jwks_ca_pem: Option, 12 | pub jwt_supported_algs: Option>, 13 | pub jwks_url: Option, 14 | pub jwt_validation_pubkeys: Option>, 15 | pub namespace_in_state: Option, 16 | pub oidc_discovery_ca_pem: Option, 17 | pub oidc_discovery_url: Option, 18 | pub oidc_client_id: Option, 19 | pub oidc_client_secret: Option, 20 | pub oidc_response_mode: Option, 21 | pub oidc_response_types: Option>, 22 | pub provider_config: Option>, 23 | } 24 | 25 | /// Response from executing 26 | /// [ReadRoleRequest][crate::api::auth::oidc::requests::ReadRoleRequest] 27 | #[derive(Deserialize, Debug, Serialize)] 28 | pub struct ReadRoleResponse { 29 | pub allowed_redirect_uris: Vec, 30 | pub user_claim: String, 31 | pub bound_subject: String, 32 | pub bound_claims: Option>, 33 | pub bound_claims_type: String, 34 | pub bound_audiences: Option>, 35 | pub claim_mappings: Option>, 36 | pub clock_skew_leeway: u64, 37 | pub expiration_leeway: u64, 38 | pub groups_claim: String, 39 | pub max_age: u64, 40 | pub not_before_leeway: u64, 41 | pub oidc_scopes: Option>, 42 | pub role_type: String, 43 | pub token_bound_cidrs: Vec, 44 | pub token_explicit_max_ttl: u64, 45 | pub token_no_default_policy: bool, 46 | pub token_num_uses: u64, 47 | pub token_period: u64, 48 | pub token_policies: Vec, 49 | pub token_ttl: u64, 50 | pub token_max_ttl: u64, 51 | pub token_type: String, 52 | pub verbose_oidc_logging: bool, 53 | } 54 | 55 | /// Response from executing 56 | /// [ListRolesRequest][crate::api::auth::oidc::requests::ListRolesRequest] 57 | #[derive(Deserialize, Debug, Serialize)] 58 | pub struct ListRolesResponse { 59 | pub keys: Vec, 60 | } 61 | 62 | /// Response from executing 63 | /// [OIDCAuthRequest][crate::api::auth::oidc::requests::OIDCAuthRequest] 64 | #[derive(Deserialize, Debug, Serialize)] 65 | pub struct OIDCAuthResponse { 66 | pub auth_url: String, 67 | } 68 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | paths-ignore: 6 | - '**.md' 7 | pull_request: 8 | paths-ignore: 9 | - '**.md' 10 | workflow_dispatch: 11 | 12 | name: CI 13 | 14 | env: 15 | RUST_TOOLCHAIN: stable 16 | TOOLCHAIN_PROFILE: minimal 17 | 18 | jobs: 19 | fmt: 20 | runs-on: ubuntu-latest 21 | name: stable / fmt 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Install stable 25 | uses: dtolnay/rust-toolchain@stable 26 | with: 27 | components: rustfmt 28 | - name: cargo fmt 29 | run: cargo fmt --check 30 | clippy: 31 | runs-on: ubuntu-latest 32 | name: stable / clippy 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Install stable 36 | uses: dtolnay/rust-toolchain@stable 37 | with: 38 | components: clippy 39 | - name: cargo clippy 40 | run: cargo clippy --all-targets --all-features --workspace -- -D warnings 41 | doc: 42 | # run docs generation on nightly rather than stable. This enables features like 43 | # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an 44 | # API be documented as only available in some specific platforms. 45 | runs-on: ubuntu-latest 46 | name: nightly / doc 47 | steps: 48 | - uses: actions/checkout@v4 49 | - name: Install nightly 50 | uses: dtolnay/rust-toolchain@nightly 51 | - name: Install cargo-docs-rs 52 | uses: dtolnay/install@cargo-docs-rs 53 | - name: cargo docs-rs 54 | run: cargo docs-rs 55 | test: 56 | runs-on: ubuntu-latest 57 | name: stable / test 58 | steps: 59 | - uses: actions/checkout@v4 60 | - name: Install stable 61 | uses: dtolnay/rust-toolchain@stable 62 | - name: cargo test 63 | run: cargo test --all-targets --all-features --workspace -- --include-ignored 64 | # https://github.com/rust-lang/cargo/issues/6669 65 | - name: cargo test --doc 66 | run: cargo test --all-features --workspace --doc 67 | 68 | publish: 69 | name: Publish to crates.io 70 | runs-on: ubuntu-latest 71 | if: startsWith(github.event.ref, 'refs/tags/v') 72 | needs: [fmt, clippy, test] 73 | steps: 74 | - uses: actions/checkout@v4 75 | - uses: actions-rs/toolchain@v1 76 | with: 77 | profile: ${{ env.TOOLCHAIN_PROFILE }} 78 | toolchain: ${{ env.RUST_TOOLCHAIN }} 79 | override: true 80 | - uses: katyo/publish-crates@v1 81 | with: 82 | registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 83 | -------------------------------------------------------------------------------- /src/api/auth/approle/responses.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Response from executing 6 | /// [ListRolesRequest][crate::api::auth::approle::requests::ListRolesRequest] 7 | #[derive(Deserialize, Debug, Serialize)] 8 | pub struct ListRolesResponse { 9 | pub keys: Vec, 10 | } 11 | 12 | /// Response from executing 13 | /// [ReadAppRoleRequest][crate::api::auth::approle::requests::ReadAppRoleRequest] 14 | #[derive(Deserialize, Debug, Serialize)] 15 | pub struct ReadAppRoleResponse { 16 | pub bind_secret_id: bool, 17 | pub secret_id_bound_cidrs: Option>, 18 | pub secret_id_num_uses: u64, 19 | pub secret_id_ttl: u64, 20 | pub token_ttl: u64, 21 | pub token_max_ttl: u64, 22 | pub token_policies: Vec, 23 | pub token_bound_cidrs: Vec, 24 | pub token_explicit_max_ttl: u64, 25 | pub token_no_default_policy: bool, 26 | pub token_num_uses: u64, 27 | pub token_period: u64, 28 | pub token_type: String, 29 | } 30 | 31 | /// Response from executing 32 | /// [ReadRoleIDRequest][crate::api::auth::approle::requests::ReadRoleIDRequest] 33 | #[derive(Deserialize, Debug, Serialize)] 34 | pub struct ReadRoleIDResponse { 35 | pub role_id: String, 36 | } 37 | 38 | /// Response from executing 39 | /// [GenerateNewSecretIDRequest][crate::api::auth::approle::requests::GenerateNewSecretIDRequest] 40 | #[derive(Deserialize, Debug, Serialize)] 41 | pub struct GenerateNewSecretIDResponse { 42 | pub secret_id_accessor: String, 43 | pub secret_id: String, 44 | pub secret_id_ttl: u64, 45 | } 46 | 47 | /// Response from executing 48 | /// [ListSecretIDRequest][crate::api::auth::approle::requests::ListSecretIDRequest] 49 | #[derive(Deserialize, Debug, Serialize)] 50 | pub struct ListSecretIDResponse { 51 | pub keys: Vec, 52 | } 53 | 54 | /// Response from executing 55 | /// [ReadSecretIDRequest][crate::api::auth::approle::requests::ReadSecretIDRequest] 56 | #[derive(Deserialize, Debug, Serialize)] 57 | pub struct ReadSecretIDResponse { 58 | pub cidr_list: Vec, 59 | pub creation_time: String, 60 | pub expiration_time: String, 61 | pub last_updated_time: String, 62 | pub metadata: Option>, 63 | pub secret_id_accessor: String, 64 | pub secret_id_num_uses: u64, 65 | pub secret_id_ttl: u64, 66 | pub token_bound_cidrs: Vec, 67 | } 68 | 69 | /// Response from executing 70 | /// [CreateCustomSecretIDRequest][crate::api::auth::approle::requests::CreateCustomSecretIDRequest] 71 | #[derive(Deserialize, Debug, Serialize)] 72 | pub struct CreateCustomSecretIDResponse { 73 | pub secret_id_accessor: String, 74 | pub secret_id: String, 75 | } 76 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/client.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use reqwest::Url; 4 | use vaultrs::client::VaultClient; 5 | use vaultrs::client::VaultClientSettingsBuilder; 6 | 7 | #[test] 8 | fn build_without_token() { 9 | let settings = VaultClientSettingsBuilder::default() 10 | .address("https://127.0.0.1:9999") 11 | .build() 12 | .unwrap(); 13 | 14 | assert_eq!("", settings.token); 15 | } 16 | 17 | #[test] 18 | #[should_panic] 19 | fn build_with_invalid_address_panics() { 20 | let _ = VaultClientSettingsBuilder::default().address("invalid_url"); 21 | } 22 | 23 | #[test] 24 | fn build_without_address() { 25 | let expected_address = "https://example.com:1234"; 26 | env::set_var("VAULT_ADDR", expected_address); 27 | 28 | let settings = VaultClientSettingsBuilder::default().build().unwrap(); 29 | assert_eq!(Url::parse(expected_address).unwrap(), settings.address); 30 | 31 | // What follows should, ideally, be a separate test case. 32 | // However, since we're using environment variables here 33 | // and those are a shared resource for the whole process, 34 | // (and tests are executed in parallel, in multiple threads), 35 | // this can lead to race conditions. 36 | // Since both cases test related behaviour, it's probably the simplest 37 | // solution to just test them this way. 38 | env::remove_var("VAULT_ADDR"); 39 | 40 | let settings = VaultClientSettingsBuilder::default().build().unwrap(); 41 | 42 | assert_eq!( 43 | Url::parse("http://127.0.0.1:8200").unwrap(), 44 | settings.address 45 | ); 46 | } 47 | 48 | const VAULT_SKIP_VERIFY: &str = "VAULT_SKIP_VERIFY"; 49 | 50 | fn build_client() -> VaultClient { 51 | VaultClient::new( 52 | VaultClientSettingsBuilder::default() 53 | .address("https://127.0.0.1:8200") 54 | .build() 55 | .unwrap(), 56 | ) 57 | .unwrap() 58 | } 59 | 60 | #[test] 61 | #[serial_test::serial] 62 | fn test_should_skip_tls_verification() { 63 | for value in ["", "1", "t", "T", "true", "True", "TRUE"] { 64 | env::set_var(VAULT_SKIP_VERIFY, value); 65 | let client = build_client(); 66 | assert!(!client.settings.verify); 67 | } 68 | } 69 | 70 | #[test] 71 | #[serial_test::serial] 72 | fn test_should_not_skip_tls_verification() { 73 | for value in ["0", "f", "F", "false", "False", "FALSE"] { 74 | env::set_var(VAULT_SKIP_VERIFY, value); 75 | let client = build_client(); 76 | assert!(client.settings.verify); 77 | } 78 | } 79 | 80 | #[test] 81 | #[serial_test::serial] 82 | fn test_should_verify_tls_if_variable_is_not_set() { 83 | env::remove_var(VAULT_SKIP_VERIFY); 84 | let client = build_client(); 85 | assert!(client.settings.verify); 86 | } 87 | -------------------------------------------------------------------------------- /src/api/database/responses.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// Response from executing 4 | /// [ReadConnectionRequest][crate::api::database::requests::ReadConnectionRequest] 5 | #[derive(Deserialize, Debug, Serialize)] 6 | pub struct ReadConnectionResponse { 7 | pub allowed_roles: Vec, 8 | pub connection_details: ConnectionDetails, 9 | pub plugin_name: String, 10 | } 11 | 12 | #[derive(Deserialize, Debug, Serialize)] 13 | pub struct ConnectionDetails { 14 | pub connection_url: String, 15 | pub username: String, 16 | } 17 | 18 | /// Response from executing 19 | /// [ListConnectionsRequest][crate::api::database::requests::ListConnectionsRequest] 20 | #[derive(Deserialize, Debug, Serialize)] 21 | pub struct ListConnectionsResponse { 22 | pub keys: Vec, 23 | } 24 | 25 | /// Response from executing 26 | /// [ReadRoleRequest][crate::api::database::requests::ReadRoleRequest] 27 | #[derive(Deserialize, Debug, Serialize)] 28 | pub struct ReadRoleResponse { 29 | pub creation_statements: Vec, 30 | pub db_name: String, 31 | pub default_ttl: u64, 32 | pub max_ttl: u64, 33 | pub renew_statements: Vec, 34 | pub revocation_statements: Vec, 35 | pub rollback_statements: Vec, 36 | } 37 | 38 | /// Response from executing 39 | /// [ListRolesRequest][crate::api::database::requests::ListRolesRequest] 40 | #[derive(Deserialize, Debug, Serialize)] 41 | pub struct ListRolesResponse { 42 | pub keys: Vec, 43 | } 44 | 45 | /// Response from executing 46 | /// [GenerateCredentialsRequest][crate::api::database::requests::GenerateCredentialsRequest] 47 | #[derive(Deserialize, Debug, Serialize)] 48 | pub struct GenerateCredentialsResponse { 49 | pub username: String, 50 | pub password: String, 51 | } 52 | 53 | /// Response from executing 54 | /// [ReadStaticRoleRequest][crate::api::database::requests::ReadStaticRoleRequest] 55 | #[derive(Deserialize, Debug, Serialize)] 56 | pub struct ReadStaticRoleResponse { 57 | pub db_name: String, 58 | pub username: String, 59 | pub rotation_period: u64, 60 | pub rotation_statements: Vec, 61 | } 62 | 63 | /// Response from executing 64 | /// [ListStaticRolesRequest][crate::api::database::requests::ListStaticRolesRequest] 65 | #[derive(Deserialize, Debug, Serialize)] 66 | pub struct ListStaticRolesResponse { 67 | pub keys: Vec, 68 | } 69 | 70 | /// Response from executing 71 | /// [GetStaticCredentialsRequest][crate::api::database::requests::GetStaticCredentialsRequest] 72 | #[derive(Deserialize, Debug, Serialize)] 73 | pub struct GetStaticCredentialsResponse { 74 | pub last_vault_rotation: String, 75 | pub password: String, 76 | pub rotation_period: u64, 77 | pub ttl: u64, 78 | pub username: String, 79 | } 80 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/kv1.rs: -------------------------------------------------------------------------------- 1 | use crate::common::Test; 2 | use std::collections::HashMap; 3 | use vaultrs::{api::kv1::responses::GetSecretResponse, error::ClientError, kv1, sys::mount}; 4 | 5 | #[tokio::test] 6 | async fn test_kv1() { 7 | let test = Test::builder().await; 8 | let client = test.client(); 9 | 10 | // Mount KV v1 secret engine 11 | let mount = "kv_v1"; 12 | let secret_path = "mysecret/foo"; 13 | mount::enable(client, mount, "kv", None).await.unwrap(); 14 | 15 | // Create test secrets 16 | let expected_secret = HashMap::from([("key1", "value1"), ("key2", "value2")]); 17 | kv1::set(client, mount, secret_path, &expected_secret) 18 | .await 19 | .unwrap(); 20 | 21 | // Read it 22 | let read_secret: HashMap = kv1::get(client, mount, secret_path).await.unwrap(); 23 | 24 | println!("{read_secret:?}"); 25 | 26 | assert_eq!(read_secret["key1"], expected_secret["key1"]); 27 | assert_eq!(read_secret["key2"], expected_secret["key2"]); 28 | 29 | // Read it as raw value 30 | let read_secret_raw: GetSecretResponse = 31 | kv1::get_raw(client, mount, secret_path).await.unwrap(); 32 | 33 | println!("{read_secret_raw:?}"); 34 | 35 | assert_eq!(read_secret_raw.data["key1"], expected_secret["key1"]); 36 | assert_eq!(read_secret_raw.data["key2"], expected_secret["key2"]); 37 | 38 | // List secret keys 39 | let list_secret = kv1::list(client, mount, "mysecret").await.unwrap(); 40 | 41 | println!("{list_secret:?}"); 42 | 43 | assert_eq!(list_secret.data.keys, vec!["foo"]); 44 | 45 | // Delete secret and read again and expect 404 to check deletion 46 | kv1::delete(client, mount, secret_path).await.unwrap(); 47 | 48 | let r = kv1::get_raw(client, mount, secret_path).await; 49 | 50 | match r.expect_err(&format!( 51 | "Expected error when reading {} after delete.", 52 | &secret_path 53 | )) { 54 | ClientError::APIError { code, .. } => { 55 | assert_eq!(code, 404, "Expected error code 404 for non-existing secret") 56 | } 57 | e => { 58 | panic!("Expected error to be APIError with code 404, got {e:?}") 59 | } 60 | }; 61 | 62 | let my_secrets = HashMap::from([("key1", "value1"), ("key2", "value2")]); 63 | 64 | kv1::set(client, mount, "my/secrets", &my_secrets) 65 | .await 66 | .unwrap(); 67 | 68 | let read_secrets: HashMap = 69 | kv1::get(client, mount, "my/secrets").await.unwrap(); 70 | 71 | println!("{:}", read_secrets["key1"]); // value1 72 | 73 | let list_secret = kv1::list(client, mount, "my").await.unwrap(); 74 | 75 | println!("{:?}", list_secret.data.keys); // [ "secrets" ] 76 | 77 | kv1::delete(client, mount, "my/secrets").await.unwrap(); 78 | } 79 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/cubbyhole.rs: -------------------------------------------------------------------------------- 1 | use crate::common::Test; 2 | use std::collections::HashMap; 3 | use vaultrs::{api::cubbyhole::responses::GetSecretResponse, cubbyhole, error::ClientError}; 4 | 5 | #[tokio::test] 6 | async fn test_cubbyhole() { 7 | let test = Test::builder().await; 8 | let client = test.client(); 9 | 10 | // Use pre-mounted cubbyhole secret engine 11 | let mount = "cubbyhole"; 12 | let secret_path = "mysecret/foo"; 13 | 14 | // Create test secrets 15 | let expected_secret = HashMap::from([("key1", "value1"), ("key2", "value2")]); 16 | cubbyhole::set(client, mount, secret_path, &expected_secret) 17 | .await 18 | .unwrap(); 19 | 20 | // Read it 21 | let read_secret: HashMap = 22 | cubbyhole::get(client, mount, secret_path).await.unwrap(); 23 | 24 | println!("{read_secret:?}"); 25 | 26 | assert_eq!(read_secret["key1"], expected_secret["key1"]); 27 | assert_eq!(read_secret["key2"], expected_secret["key2"]); 28 | 29 | // Read it as raw value 30 | let read_secret_raw: GetSecretResponse = cubbyhole::get_raw(client, mount, secret_path) 31 | .await 32 | .unwrap(); 33 | 34 | println!("{read_secret_raw:?}"); 35 | 36 | assert_eq!(read_secret_raw.data["key1"], expected_secret["key1"]); 37 | assert_eq!(read_secret_raw.data["key2"], expected_secret["key2"]); 38 | 39 | // List secret keys 40 | let list_secret = cubbyhole::list(client, mount, "mysecret").await.unwrap(); 41 | 42 | println!("{list_secret:?}"); 43 | 44 | assert_eq!(list_secret.data.keys, vec!["foo"]); 45 | 46 | // Delete secret and read again and expect 404 to check deletion 47 | cubbyhole::delete(client, mount, secret_path).await.unwrap(); 48 | 49 | let r = cubbyhole::get_raw(client, mount, secret_path).await; 50 | 51 | match r.expect_err(&format!( 52 | "Expected error when reading {} after delete.", 53 | &secret_path 54 | )) { 55 | ClientError::APIError { code, .. } => { 56 | assert_eq!(code, 404, "Expected error code 404 for non-existing secret") 57 | } 58 | e => { 59 | panic!("Expected error to be APIError with code 404, got {e:?}") 60 | } 61 | }; 62 | 63 | let my_secrets = HashMap::from([("key1", "value1"), ("key2", "value2")]); 64 | 65 | cubbyhole::set(client, mount, "my/secrets", &my_secrets) 66 | .await 67 | .unwrap(); 68 | 69 | let read_secrets: HashMap = 70 | cubbyhole::get(client, mount, "my/secrets").await.unwrap(); 71 | 72 | println!("{:}", read_secrets["key1"]); // value1 73 | 74 | let list_secret = cubbyhole::list(client, mount, "my").await.unwrap(); 75 | 76 | println!("{:?}", list_secret.data.keys); // [ "secrets" ] 77 | 78 | cubbyhole::delete(client, mount, "my/secrets") 79 | .await 80 | .unwrap(); 81 | } 82 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/files/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC9TCCAd2gAwIBAgIQHh2t3qPz2hZjmmTQhzH+VjANBgkqhkiG9w0BAQsFADAT 3 | MREwDwYDVQQDEwh0ZXN0LmNvbTAeFw0yMTA4MTYyMzUzMjJaFw0yMTA5MTcyMzUz 4 | MjJaMBMxETAPBgNVBAMTCHRlc3QuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 5 | MIIBCgKCAQEAySFmVBpYD6tdr6TFXhop8GiZSMP3P7ebi1fbV/l7RzjYFmrHEraC 6 | jKV20cBlhvAZ/nP6s5opYDIRYA191A5YJkCVuNOsjEsR+WwcBae0hFgwuSaMIHFi 7 | +13f7MsHHfblOiqHPgfTkcrympR+c3787yoaKq46u0AlM/nuo25cmfKJ83w87C0e 8 | hILTOWDLEvJCDIRuN+uz8uvnFhmxbK8wvvBE65a/GLg3TjAikMqe9g9DhXYStDTp 9 | uLW/e+SuWaZ96Z/oj2GMPt+KiY04zT2LajrG5oaHLnrYlZMl1ZPWXxTxhneZ5al9 10 | IptlBWE1g1nqfPa1vJsH06FoYJIfBkF/SQIDAQABo0UwQzAOBgNVHQ8BAf8EBAMC 11 | AQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUdz9WcNPs+sUNv9XqfRS3 12 | XM5wQyswDQYJKoZIhvcNAQELBQADggEBABnC2JowooZmzL5b0Ir/KP+uj0YA3BhY 13 | hO8ARiAiUMYvxGb8IhSmRuISSfONRgRkLvrsURtfYj0stapVJn7k/QbNAVEBsUgl 14 | 5zJEYkQ4tKs+MyAioh8/4fO0Jt6U5bKGEFYge42mbq/7Zf/wT+2q7j7j+sSEFjo0 15 | 656EDjBMwje65y4StBuKqTxLpc4bmcdA1zwh7bYE97MzQjxGfU7t+nQ5LHMw/zYs 16 | g5QneVnJEwOQe4+kSogNLCkQwGwNvV6WL3sfqijuhaojI9oQUw2V028a4oO6Xvb1 17 | JXkbjEWChnHwZZ3FSdprhwFWsXD5hIumqbqXKxtLhTTqds4txqmb0fA= 18 | -----END CERTIFICATE----- 19 | -----BEGIN RSA PRIVATE KEY----- 20 | MIIEpAIBAAKCAQEAySFmVBpYD6tdr6TFXhop8GiZSMP3P7ebi1fbV/l7RzjYFmrH 21 | EraCjKV20cBlhvAZ/nP6s5opYDIRYA191A5YJkCVuNOsjEsR+WwcBae0hFgwuSaM 22 | IHFi+13f7MsHHfblOiqHPgfTkcrympR+c3787yoaKq46u0AlM/nuo25cmfKJ83w8 23 | 7C0ehILTOWDLEvJCDIRuN+uz8uvnFhmxbK8wvvBE65a/GLg3TjAikMqe9g9DhXYS 24 | tDTpuLW/e+SuWaZ96Z/oj2GMPt+KiY04zT2LajrG5oaHLnrYlZMl1ZPWXxTxhneZ 25 | 5al9IptlBWE1g1nqfPa1vJsH06FoYJIfBkF/SQIDAQABAoIBAFhbwu+ZrywbFYpP 26 | MaG4jsoduZFc/ebRC5U4oJTSdhUA5PVcLV6MLWgKXNui0TdpjZHac72pMRoghL+U 27 | zb12G8WaQ+wxmbD8XcYcd2cImOY3NBhy1cZ+6YQSZohJXTQNvgBCpK1clNaMuO2t 28 | 5Ktqk0WiUmKDriuK740Y9Hx5xFagjbSv3HsxvoKIh4GyY29Z81Zy9ZaruMkEUgPG 29 | Ycpb/rvUnAvSkToLJgWofrnkW1OnvJZ0PAwxYmaO7c/+KuUXlVyEvQLSCMVn8lq/ 30 | NvjGMt/M2KMCo2GA7TdmmXvg6PfZEd1aABqyYSKKeQjukTI3/Ero8ght0MKxIK15 31 | /pFGqXECgYEA8ZF1/2LRp445olBRELRxNXyJp8oe2Z6hNnAAMZPtMB76Qi/zaQJ9 32 | snPHICLgoTm5oxGIOuNi9iZM4ZHE8QTg9mMXCb5UAK+bPWF9YYsxX+KYBFX9b+0H 33 | 7YbNPRkc2kpyffUfp/OvSQXwkMujAp1ypPGB60oIblPlzwnb6wUqkC0CgYEA1SV8 34 | H+Zu6dS/BeiFHVg5IIweMv2VqICv2apNRUMGJ/+q91I0PkvTAGeUHyw0MK9RBxHk 35 | IP2GCbr/Rma21jCfamnOBmpGErfMKw9Glt+fyKL/eDRxKzURp5yLIJkpTvZE6eTx 36 | tqrLlgcZf5vdtbJG2j/7S6q1BH8scuB7bceoAQ0CgYEApjvuUneDROWnnZ3dmzxA 37 | 54izBcceYa06zUZXq8ZbI5E9sExlGa9AJoxrzB6UK/6YU7Id6EBP+omywEtalm1A 38 | De10jhzum6Y8kwnCiVyK9qvs5pbyF5KqsPbhx5i/dSxkC2+igCY+SmcO++VWYNdO 39 | bFlRiUPh92I+s7HaJ6nfd2UCgYBkbLrk1M9hQKmYAQvm3ZBc82evIqeGsssnxQOP 40 | JJsb8Wv+By8yQstxn1u8rDkSzydgK1/O42Rp5T8tTmvK0zHpE1zMzPmjDBIYH6Rk 41 | pYxFHfc8DYlckC53SFyMyV0bJREnGoWASUyOrV2oMCnv18VmQ0sFRbLwIXMx1O6S 42 | tMVFaQKBgQCkEUfS79BjNaNr8OWBhMgobO9PLfkKr+gGT64LLtBhsbBv07qGXeqQ 43 | /+kDwnYdVn3yOJHGpf9pq0W/afi9A8FPoDpzvA3SzhKCUv6lPuhY+jK3MilKNKuK 44 | 62zRNV2pjYFgEHOLXtnyzmP2oCC5zyUmF2EQJUaR85UWeapeOb7emQ== 45 | -----END RSA PRIVATE KEY----- 46 | -------------------------------------------------------------------------------- /src/api/kv1/requests.rs: -------------------------------------------------------------------------------- 1 | use super::responses::{GetSecretResponse, ListSecretResponse}; 2 | 3 | use rustify_derive::Endpoint; 4 | use std::fmt::Debug; 5 | 6 | /// ## Read Secret 7 | /// This endpoint retrieves the secret at the specified location. 8 | /// 9 | /// * Path: {self.mount}/{self.path} 10 | /// * Method: GET 11 | /// * Response: GetSecretResponse 12 | /// * Reference: 13 | #[derive(Builder, Debug, Endpoint)] 14 | #[endpoint( 15 | path = "{self.mount}/{self.path}", 16 | response = "GetSecretResponse", 17 | builder = "true" 18 | )] 19 | #[builder(setter(into))] 20 | pub struct GetSecretRequest { 21 | #[endpoint(skip)] 22 | pub mount: String, 23 | #[endpoint(skip)] 24 | pub path: String, 25 | } 26 | 27 | /// ## Set Secret 28 | /// This endpoint set or update a secret at the specified location. 29 | /// 30 | /// * Path: {self.mount}/{self.path} 31 | /// * Method: POST 32 | /// * Response: N/A 33 | /// * Reference: 34 | #[derive(Builder, Debug, Endpoint)] 35 | #[endpoint(path = "{self.mount}/{self.path}", method = "POST", builder = "true")] 36 | #[builder(setter(into))] 37 | pub struct SetSecretRequest { 38 | #[endpoint(skip)] 39 | pub mount: String, 40 | #[endpoint(skip)] 41 | pub path: String, 42 | 43 | // key/value pairs to pass Vault 44 | // Must be as raw, otherwise payload would be sent as 45 | // { data: { key: value, key2: value2 } } rather than plain { key: value, key2: value2 } 46 | // Result in a secret with key "data" and erroneous value 47 | #[endpoint(raw)] 48 | pub data: Vec, 49 | } 50 | 51 | /// ## List secret keys 52 | /// This endpoint list secrets at given location 53 | /// 54 | /// * Path: {self.mount}/{self.path} 55 | /// * Method: LIST 56 | /// * Response: ListSecretResponse 57 | /// * Reference: 58 | #[derive(Builder, Debug, Endpoint)] 59 | #[endpoint( 60 | path = "{self.mount}/{self.path}", 61 | method = "LIST", 62 | builder = "true", 63 | response = "ListSecretResponse" 64 | )] 65 | #[builder(setter(into))] 66 | pub struct ListSecretRequest { 67 | #[endpoint(skip)] 68 | pub mount: String, 69 | #[endpoint(skip)] 70 | pub path: String, 71 | } 72 | 73 | /// ## Delete secret 74 | /// This endpoint delete a secret at given location 75 | /// 76 | /// * Path: {self.mount}/{self.path} 77 | /// * Method: DELETE 78 | /// * Response: N/A 79 | /// * Reference: 80 | #[derive(Builder, Debug, Endpoint)] 81 | #[endpoint(path = "{self.mount}/{self.path}", method = "DELETE", builder = "true")] 82 | #[builder(setter(into))] 83 | pub struct DeleteSecretRequest { 84 | #[endpoint(skip)] 85 | pub mount: String, 86 | #[endpoint(skip)] 87 | pub path: String, 88 | } 89 | -------------------------------------------------------------------------------- /src/api/cubbyhole/requests.rs: -------------------------------------------------------------------------------- 1 | use super::responses::{GetSecretResponse, ListSecretResponse}; 2 | 3 | use rustify_derive::Endpoint; 4 | use std::fmt::Debug; 5 | 6 | /// ## Read Secret 7 | /// This endpoint retrieves the secret at the specified location. 8 | /// 9 | /// * Path: {self.mount}/{self.path} 10 | /// * Method: GET 11 | /// * Response: GetSecretResponse 12 | /// * Reference: 13 | #[derive(Builder, Debug, Endpoint)] 14 | #[endpoint( 15 | path = "{self.mount}/{self.path}", 16 | response = "GetSecretResponse", 17 | builder = "true" 18 | )] 19 | #[builder(setter(into))] 20 | pub struct GetSecretRequest { 21 | #[endpoint(skip)] 22 | pub mount: String, 23 | #[endpoint(skip)] 24 | pub path: String, 25 | } 26 | 27 | /// ## Set Secret 28 | /// This endpoint set or update a secret at the specified location. 29 | /// 30 | /// * Path: {self.mount}/{self.path} 31 | /// * Method: POST 32 | /// * Response: N/A 33 | /// * Reference: 34 | #[derive(Builder, Debug, Endpoint)] 35 | #[endpoint(path = "{self.mount}/{self.path}", method = "POST", builder = "true")] 36 | #[builder(setter(into))] 37 | pub struct SetSecretRequest { 38 | #[endpoint(skip)] 39 | pub mount: String, 40 | #[endpoint(skip)] 41 | pub path: String, 42 | 43 | // key/value pairs to pass Vault 44 | // Must be as raw, otherwise payload would be sent as 45 | // { data: { key: value, key2: value2 } } rather than plain { key: value, key2: value2 } 46 | // Result in a secret with key "data" and erroneous value 47 | #[endpoint(raw)] 48 | pub data: Vec, 49 | } 50 | 51 | /// ## List secret keys 52 | /// This endpoint list secrets at given location 53 | /// 54 | /// * Path: {self.mount}/{self.path} 55 | /// * Method: LIST 56 | /// * Response: ListSecretResponse 57 | /// * Reference: 58 | #[derive(Builder, Debug, Endpoint)] 59 | #[endpoint( 60 | path = "{self.mount}/{self.path}", 61 | method = "LIST", 62 | builder = "true", 63 | response = "ListSecretResponse" 64 | )] 65 | #[builder(setter(into))] 66 | pub struct ListSecretRequest { 67 | #[endpoint(skip)] 68 | pub mount: String, 69 | #[endpoint(skip)] 70 | pub path: String, 71 | } 72 | 73 | /// ## Delete secret 74 | /// This endpoint delete a secret at given location 75 | /// 76 | /// * Path: {self.mount}/{self.path} 77 | /// * Method: DELETE 78 | /// * Response: N/A 79 | /// * Reference: 80 | #[derive(Builder, Debug, Endpoint)] 81 | #[endpoint(path = "{self.mount}/{self.path}", method = "DELETE", builder = "true")] 82 | #[builder(setter(into))] 83 | pub struct DeleteSecretRequest { 84 | #[endpoint(skip)] 85 | pub mount: String, 86 | #[endpoint(skip)] 87 | pub path: String, 88 | } 89 | -------------------------------------------------------------------------------- /src/api/transit.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod responses; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Copy, Debug, Serialize, Deserialize)] 7 | #[serde(rename_all = "kebab-case")] 8 | #[derive(Default)] 9 | pub enum KeyType { 10 | /// AES-128 wrapped with GCM using a 96-bit nonce size AEAD (symmetric, 11 | /// supports derivation and convergent encryption) 12 | Aes128Gcm96, 13 | /// AES-256 wrapped with GCM using a 96-bit nonce size AEAD (symmetric, 14 | /// supports derivation and convergent encryption, default) 15 | #[default] 16 | Aes256Gcm96, 17 | /// ChaCha20-Poly1305 AEAD (symmetric, supports derivation and convergent 18 | /// encryption) 19 | Chacha20Poly1305, 20 | /// ED25519 (asymmetric, supports derivation). When using derivation, a sign 21 | /// operation with the same context will derive the same key and signature; 22 | /// this is a signing analogue to convergent_encryption. 23 | Ed25519, 24 | /// ECDSA using the P-256 elliptic curve (asymmetric) 25 | EcdsaP256, 26 | /// ECDSA using the P-384 elliptic curve (asymmetric) 27 | EcdsaP384, 28 | /// ECDSA using the P-521 elliptic curve (asymmetric) 29 | EcdsaP521, 30 | /// RSA with bit size of 2048 (asymmetric) 31 | // kebab-case conversion doesn't work for words starting with a digit. 32 | #[serde(rename = "rsa-2048")] 33 | Rsa2048, 34 | /// RSA with bit size of 3072 (asymmetric) 35 | #[serde(rename = "rsa-3072")] 36 | Rsa3072, 37 | /// RSA with bit size of 4096 (asymmetric) 38 | #[serde(rename = "rsa-4096")] 39 | Rsa4096, 40 | } 41 | 42 | #[derive(Clone, Copy, Debug, Serialize, Deserialize)] 43 | #[serde(rename_all = "kebab-case")] 44 | #[derive(Default)] 45 | pub enum OutputFormat { 46 | #[default] 47 | Base64, 48 | Hex, 49 | } 50 | 51 | /// Note: In FIPS 140-2 mode, the following algorithms are not certified and 52 | /// thus should not be used: sha3-224, sha3-256, sha3-384, and sha3-512. 53 | #[derive(Clone, Copy, Debug, Serialize, Deserialize)] 54 | #[serde(rename_all = "kebab-case")] 55 | pub enum HashAlgorithm { 56 | Sha2_224, 57 | Sha2_256, 58 | Sha2_384, 59 | Sha2_512, 60 | Sha3_224, 61 | Sha3_256, 62 | Sha3_384, 63 | Sha3_512, 64 | } 65 | 66 | #[derive(Clone, Copy, Debug, Serialize, Deserialize)] 67 | #[serde(rename_all = "kebab-case")] 68 | pub enum SignatureAlgorithm { 69 | Pss, 70 | Pkcs1v15, 71 | } 72 | 73 | #[derive(Clone, Copy, Debug, Serialize, Deserialize)] 74 | #[serde(rename_all = "kebab-case")] 75 | pub enum MarshalingAlgorithm { 76 | /// The default, used by OpenSSL and X.509 77 | Asn1, 78 | /// The version used by JWS (and thus for JWTs). Selecting this will also 79 | /// change the output encoding to URL-safe Base64 encoding instead of 80 | /// standard Base64-encoding. 81 | Jws, 82 | } 83 | 84 | #[derive(Clone, Copy, Debug, Serialize, Deserialize)] 85 | #[serde(rename_all = "UPPERCASE")] 86 | pub enum HashFunction { 87 | Sha1, 88 | Sha224, 89 | Sha256, 90 | Sha384, 91 | Sha512, 92 | } 93 | -------------------------------------------------------------------------------- /src/api/identity/entity/responses.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Response from executing 6 | /// [CreateEntityRequest](crate::api::identity::entity::requests::CreateEntityRequest) 7 | #[derive(Deserialize, Debug, Serialize)] 8 | pub struct CreateEntityResponse { 9 | pub id: String, 10 | pub alias: Option>, 11 | } 12 | 13 | /// Response from executing 14 | /// [ReadEntityByIdRequest](crate::api::identity::entity::requests::ReadEntityByIdRequest) 15 | #[derive(Deserialize, Debug, Serialize)] 16 | pub struct ReadEntityByIdResponse { 17 | pub id: String, 18 | pub name: String, 19 | pub disabled: bool, 20 | pub policies: Vec, 21 | pub last_update_time: String, 22 | pub creation_time: String, 23 | pub metadata: Option>, 24 | pub aliases: Vec, 25 | pub direct_group_ids: Vec, 26 | pub group_ids: Vec, 27 | pub inherited_group_ids: Vec, 28 | pub merged_entity_ids: Option>, 29 | pub namespace_id: String, 30 | } 31 | 32 | /// Response from executing 33 | /// [ReadEntityByNameRequest](crate::api::identity::entity::requests::ReadEntityByNameRequest) 34 | #[derive(Deserialize, Debug, Serialize)] 35 | pub struct ReadEntityByNameResponse { 36 | pub id: String, 37 | pub name: String, 38 | pub disabled: bool, 39 | pub policies: Vec, 40 | pub last_update_time: String, 41 | pub creation_time: String, 42 | pub metadata: Option>, 43 | pub aliases: Vec, 44 | pub direct_group_ids: Vec, 45 | pub group_ids: Vec, 46 | pub inherited_group_ids: Vec, 47 | pub merged_entity_ids: Option>, 48 | pub namespace_id: String, 49 | } 50 | 51 | #[derive(Deserialize, Debug, Serialize)] 52 | pub struct Alias { 53 | pub id: String, 54 | pub canonical_id: String, 55 | pub mount_accessor: String, 56 | pub name: String, 57 | pub creation_time: String, 58 | pub last_update_time: String, 59 | pub local: bool, 60 | pub mount_type: String, 61 | pub mount_path: String, 62 | pub custom_metadata: Option>, 63 | pub metadata: Option>, 64 | pub merged_from_canonical_ids: Option>, 65 | } 66 | 67 | /// Response from executing 68 | /// [ListEntitiesById](crate::api::identity::entity::requests::ListEntitiesByIdRequest) 69 | #[derive(Deserialize, Debug, Serialize)] 70 | pub struct ListEntitiesByIdResponse { 71 | pub keys: Vec, 72 | } 73 | 74 | /// Response from executing 75 | /// [CreateEntityByNameRequest](crate::api::identity::entity::requests::CreateEntityByNameRequest) 76 | #[derive(Deserialize, Debug, Serialize)] 77 | pub struct CreateEntityByNameResponse { 78 | pub aliases: Option>, 79 | pub id: String, 80 | pub name: String, 81 | } 82 | 83 | /// Response from executing 84 | /// [ListEntitiesById](crate::api::identity::entity::requests::ListEntitiesByNameRequest) 85 | #[derive(Deserialize, Debug, Serialize)] 86 | pub struct ListEntitiesByNameResponse { 87 | pub keys: Vec, 88 | } 89 | -------------------------------------------------------------------------------- /src/identity/group_alias.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::{ 3 | self, 4 | identity::group_alias::{ 5 | requests::{ 6 | CreateGroupAliasRequest, CreateGroupAliasRequestBuilder, 7 | DeleteGroupAliasByIdRequest, ListGroupAliasesByIdRequest, 8 | ReadGroupAliasByIdRequest, UpdateGroupAliasByIdRequest, 9 | UpdateGroupAliasByIdRequestBuilder, 10 | }, 11 | responses::{ 12 | CreateGroupAliasResponse, ListGroupAliasesByIdResponse, ReadGroupAliasByIdResponse, 13 | }, 14 | }, 15 | }, 16 | client::Client, 17 | error::ClientError, 18 | }; 19 | 20 | pub async fn create( 21 | client: &impl Client, 22 | name: &str, 23 | mount_accessor: &str, 24 | opts: Option<&mut CreateGroupAliasRequestBuilder>, 25 | ) -> Result { 26 | let mut t = CreateGroupAliasRequest::builder(); 27 | let endpoint = opts 28 | .unwrap_or(&mut t) 29 | .name(name) 30 | .mount_accessor(mount_accessor) 31 | .build() 32 | .unwrap(); 33 | api::exec_with_result(client, endpoint) 34 | .await 35 | .map_err(|err| { 36 | // In the case the response as an empty HTTP Body 37 | if matches!( 38 | err, 39 | ClientError::RestClientError { 40 | source: rustify::errors::ClientError::ResponseParseError { .. } 41 | } 42 | ) { 43 | return ClientError::InvalidUpdateParameter; 44 | } 45 | err 46 | }) 47 | } 48 | 49 | /// Reads group alias by `id`. 50 | /// 51 | /// See [ReadGroupAliasByIdRequest] 52 | pub async fn read_by_id( 53 | client: &impl Client, 54 | id: &str, 55 | ) -> Result { 56 | let endpoint = ReadGroupAliasByIdRequest::builder().id(id).build().unwrap(); 57 | 58 | api::exec_with_result(client, endpoint).await 59 | } 60 | 61 | /// Update group alias by `id`. 62 | /// 63 | /// See [UpdateGroupAliasByIdRequest] 64 | pub async fn update_by_id( 65 | client: &impl Client, 66 | id: &str, 67 | mount_accessor: &str, 68 | opts: Option<&mut UpdateGroupAliasByIdRequestBuilder>, 69 | ) -> Result<(), ClientError> { 70 | let mut t = UpdateGroupAliasByIdRequest::builder(); 71 | let endpoint = opts 72 | .unwrap_or(&mut t) 73 | .id(id) 74 | .mount_accessor(mount_accessor) 75 | .build() 76 | .unwrap(); 77 | api::exec_with_empty(client, endpoint).await 78 | } 79 | 80 | /// Delete group alias by `id`. 81 | /// 82 | /// See [DeleteGroupAliasByIdRequest] 83 | pub async fn delete_by_id(client: &impl Client, id: &str) -> Result<(), ClientError> { 84 | let endpoint = DeleteGroupAliasByIdRequest::builder() 85 | .id(id) 86 | .build() 87 | .unwrap(); 88 | api::exec_with_empty(client, endpoint).await 89 | } 90 | 91 | /// List groups aliases by ID. 92 | /// 93 | /// See [ListGroupAliasesByIdRequest] 94 | pub async fn list_by_id(client: &impl Client) -> Result { 95 | let endpoint = ListGroupAliasesByIdRequest::builder().build().unwrap(); 96 | api::exec_with_result(client, endpoint).await 97 | } 98 | -------------------------------------------------------------------------------- /src/cubbyhole.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::{ 3 | self, 4 | cubbyhole::{ 5 | requests::{ 6 | DeleteSecretRequest, GetSecretRequest, ListSecretRequest, SetSecretRequest, 7 | }, 8 | responses::{GetSecretResponse, ListSecretResponse}, 9 | }, 10 | }, 11 | client::Client, 12 | error::ClientError, 13 | }; 14 | 15 | use serde::{de::DeserializeOwned, Serialize}; 16 | use std::collections::HashMap; 17 | 18 | /// Sets the value of the secret at the given path 19 | /// 20 | /// See [SetSecretRequest] 21 | pub async fn set( 22 | client: &impl Client, 23 | mount: &str, 24 | path: &str, 25 | data: &HashMap<&str, T>, 26 | ) -> Result<(), ClientError> { 27 | let data_value_json = data 28 | .serialize(serde_json::value::Serializer) 29 | .map_err(|e| ClientError::JsonParseError { source: e })?; 30 | 31 | // Convert our JSON values as bytes to be sent to Vault 32 | let data_u8 = serde_json::to_vec(&data_value_json) 33 | .map_err(|e| ClientError::JsonParseError { source: e })?; 34 | 35 | let endpoint = SetSecretRequest::builder() 36 | .mount(mount) 37 | .path(path) 38 | .data(data_u8) 39 | .build() 40 | .unwrap(); 41 | 42 | api::exec_with_empty(client, endpoint).await 43 | } 44 | 45 | /// Get value of the secret at given path. 46 | /// Return the deserialized HashMap of secret directly, 47 | /// if you need to access additional fields such as lead_duration, use [get_raw] 48 | pub async fn get( 49 | client: &impl Client, 50 | mount: &str, 51 | path: &str, 52 | ) -> Result { 53 | let res = get_raw(client, mount, path).await?; 54 | serde_json::value::from_value(res.data).map_err(|e| ClientError::JsonParseError { source: e }) 55 | } 56 | 57 | /// Get value of the secret at given path, returning the raw response without deserialization 58 | /// Additional fields are available on raw response, such as lease_duration 59 | pub async fn get_raw( 60 | client: &impl Client, 61 | mount: &str, 62 | path: &str, 63 | ) -> Result { 64 | let endpoint = GetSecretRequest::builder() 65 | .mount(mount) 66 | .path(path) 67 | .build() 68 | .unwrap(); 69 | 70 | api::exec_with_no_result(client, endpoint).await 71 | } 72 | 73 | /// List secret keys at given location, returning raw server response 74 | /// 75 | /// See [ListSecretRequest] 76 | pub async fn list( 77 | client: &impl Client, 78 | mount: &str, 79 | path: &str, 80 | ) -> Result { 81 | let endpoint = ListSecretRequest::builder() 82 | .mount(mount) 83 | .path(path) 84 | .build() 85 | .unwrap(); 86 | 87 | api::exec_with_no_result(client, endpoint).await 88 | } 89 | 90 | /// Delete secret at given location 91 | /// 92 | /// See [DeleteSecretRequest] 93 | pub async fn delete(client: &impl Client, mount: &str, path: &str) -> Result<(), ClientError> { 94 | let endpoint = DeleteSecretRequest::builder() 95 | .mount(mount) 96 | .path(path) 97 | .build() 98 | .unwrap(); 99 | 100 | api::exec_with_empty(client, endpoint).await 101 | } 102 | -------------------------------------------------------------------------------- /src/kv1.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::{ 3 | self, 4 | kv1::{ 5 | requests::{ 6 | DeleteSecretRequest, GetSecretRequest, ListSecretRequest, SetSecretRequest, 7 | }, 8 | responses::{GetSecretResponse, ListSecretResponse}, 9 | }, 10 | }, 11 | client::Client, 12 | error::ClientError, 13 | }; 14 | 15 | use serde::{de::DeserializeOwned, Serialize}; 16 | use std::collections::HashMap; 17 | 18 | /// Sets the value of the secret at the given path 19 | /// 20 | /// A key called ttl will trigger some special behavior. See the [Vault KV secrets engine documentation][] for details. 21 | /// See [SetSecretRequest] 22 | pub async fn set( 23 | client: &impl Client, 24 | mount: &str, 25 | path: &str, 26 | data: &HashMap<&str, T>, 27 | ) -> Result<(), ClientError> { 28 | let data_value_json = data 29 | .serialize(serde_json::value::Serializer) 30 | .map_err(|e| ClientError::JsonParseError { source: e })?; 31 | 32 | // Convert our JSON values as bytes to be sent to Vault 33 | let data_u8 = serde_json::to_vec(&data_value_json) 34 | .map_err(|e| ClientError::JsonParseError { source: e })?; 35 | 36 | let endpoint = SetSecretRequest::builder() 37 | .mount(mount) 38 | .path(path) 39 | .data(data_u8) 40 | .build() 41 | .unwrap(); 42 | 43 | api::exec_with_empty(client, endpoint).await 44 | } 45 | 46 | /// Get value of the secret at given path. 47 | /// Return the deserialized HashMap of secret directly, 48 | /// if you need to access additional fields such as lead_duration, use [get_raw] 49 | pub async fn get( 50 | client: &impl Client, 51 | mount: &str, 52 | path: &str, 53 | ) -> Result { 54 | let res = get_raw(client, mount, path).await?; 55 | serde_json::value::from_value(res.data).map_err(|e| ClientError::JsonParseError { source: e }) 56 | } 57 | 58 | /// Get value of the secret at given path, returning the raw response without deserialization 59 | /// Additional fields are available on raw response, such as lease_duration 60 | pub async fn get_raw( 61 | client: &impl Client, 62 | mount: &str, 63 | path: &str, 64 | ) -> Result { 65 | let endpoint = GetSecretRequest::builder() 66 | .mount(mount) 67 | .path(path) 68 | .build() 69 | .unwrap(); 70 | 71 | api::exec_with_no_result(client, endpoint).await 72 | } 73 | 74 | /// List secret keys at given location, returning raw server response 75 | /// 76 | /// See [ListSecretRequest] 77 | pub async fn list( 78 | client: &impl Client, 79 | mount: &str, 80 | path: &str, 81 | ) -> Result { 82 | let endpoint = ListSecretRequest::builder() 83 | .mount(mount) 84 | .path(path) 85 | .build() 86 | .unwrap(); 87 | 88 | api::exec_with_no_result(client, endpoint).await 89 | } 90 | 91 | /// Delete secret at given location 92 | /// 93 | /// See [DeleteSecretRequest] 94 | pub async fn delete(client: &impl Client, mount: &str, path: &str) -> Result<(), ClientError> { 95 | let endpoint = DeleteSecretRequest::builder() 96 | .mount(mount) 97 | .path(path) 98 | .build() 99 | .unwrap(); 100 | 101 | api::exec_with_empty(client, endpoint).await 102 | } 103 | -------------------------------------------------------------------------------- /src/identity/entity_alias.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::{ 3 | self, 4 | identity::entity_alias::{ 5 | requests::{ 6 | CreateEntityAliasRequest, CreateEntityAliasRequestBuilder, 7 | DeleteEntityAliasByIdRequest, ListEntityAliasesByIdRequest, 8 | ReadEntityAliasByIdRequest, UpdateEntityAliasByIdRequest, 9 | UpdateEntityAliasByIdRequestBuilder, 10 | }, 11 | responses::{ 12 | CreateEntityAliasResponse, ListEntityAliasesByIdResponse, 13 | ReadEntityAliasByIdResponse, 14 | }, 15 | }, 16 | }, 17 | client::Client, 18 | error::ClientError, 19 | }; 20 | 21 | /// Create or update an entity alias. 22 | /// 23 | /// See [ CreateEntityAliasRequest] 24 | pub async fn create( 25 | client: &impl Client, 26 | name: &str, 27 | canonical_id: &str, 28 | mount_accessor: &str, 29 | opts: Option<&mut CreateEntityAliasRequestBuilder>, 30 | ) -> Result { 31 | let mut t = CreateEntityAliasRequest::builder(); 32 | let endpoint = opts 33 | .unwrap_or(&mut t) 34 | .name(name) 35 | .canonical_id(canonical_id) 36 | .mount_accessor(mount_accessor) 37 | .build() 38 | .unwrap(); 39 | api::exec_with_result(client, endpoint) 40 | .await 41 | .map_err(|err| { 42 | // In the case the response as an empty HTTP Body 43 | if matches!( 44 | err, 45 | ClientError::RestClientError { 46 | source: rustify::errors::ClientError::ResponseParseError { .. } 47 | } 48 | ) { 49 | return ClientError::InvalidUpdateParameter; 50 | } 51 | err 52 | }) 53 | } 54 | 55 | /// Reads entity alias by `id`. 56 | /// 57 | /// See [ReadEntityAliasByIdRequest] 58 | pub async fn read_by_id( 59 | client: &impl Client, 60 | id: &str, 61 | ) -> Result { 62 | let endpoint = ReadEntityAliasByIdRequest::builder() 63 | .id(id) 64 | .build() 65 | .unwrap(); 66 | 67 | api::exec_with_result(client, endpoint).await 68 | } 69 | 70 | /// Update entity_alias by `id`. 71 | /// 72 | /// See [UpdateEntityAliasByIdRequest] 73 | pub async fn update_by_id( 74 | client: &impl Client, 75 | id: &str, 76 | opts: Option<&mut UpdateEntityAliasByIdRequestBuilder>, 77 | ) -> Result<(), ClientError> { 78 | let mut t = UpdateEntityAliasByIdRequest::builder(); 79 | let endpoint = opts.unwrap_or(&mut t).id(id).build().unwrap(); 80 | api::exec_with_empty(client, endpoint).await 81 | } 82 | 83 | /// Delete entity alias by `id`. 84 | /// 85 | /// See [DeleteEntityAliasByIdRequest] 86 | pub async fn delete_by_id(client: &impl Client, id: &str) -> Result<(), ClientError> { 87 | let endpoint = DeleteEntityAliasByIdRequest::builder() 88 | .id(id) 89 | .build() 90 | .unwrap(); 91 | api::exec_with_empty(client, endpoint).await 92 | } 93 | 94 | /// List entity aliases by ID. 95 | /// 96 | /// See [ListEntityAliasesByIdRequest] 97 | pub async fn list_by_id( 98 | client: &impl Client, 99 | ) -> Result { 100 | let endpoint = ListEntityAliasesByIdRequest::builder().build().unwrap(); 101 | api::exec_with_result(client, endpoint).await 102 | } 103 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/oidc.rs: -------------------------------------------------------------------------------- 1 | use tracing::debug; 2 | use vaultrs::client::Client; 3 | use vaultrs::error::ClientError; 4 | use vaultrs::sys::auth; 5 | 6 | use crate::common::Test; 7 | 8 | #[tokio::test] 9 | async fn test() { 10 | let test = Test::builder().await; 11 | let client = test.client(); 12 | let endpoint = setup(client).await.unwrap(); 13 | 14 | // Test config 15 | config::test_set(client, &endpoint).await; 16 | config::test_read(client, &endpoint).await; 17 | 18 | // Test roles 19 | role::test_set(client, &endpoint).await; 20 | role::test_read(client, &endpoint).await; 21 | role::test_list(client, &endpoint).await; 22 | 23 | role::test_delete(client, &endpoint).await; 24 | } 25 | 26 | mod config { 27 | use vaultrs::client::Client; 28 | 29 | use vaultrs::{api::auth::oidc::requests::SetConfigurationRequest, auth::oidc::config}; 30 | 31 | use super::OIDCEndpoint; 32 | 33 | pub async fn test_read(client: &impl Client, endpoint: &OIDCEndpoint) { 34 | config::read(client, endpoint.path.as_str()).await.unwrap(); 35 | } 36 | 37 | pub async fn test_set(client: &impl Client, endpoint: &OIDCEndpoint) { 38 | // TODO: This might not always work 39 | config::set( 40 | client, 41 | endpoint.path.as_str(), 42 | Some( 43 | SetConfigurationRequest::builder() 44 | .oidc_discovery_url("https://samples.auth0.com/") 45 | .oidc_client_id("kbyuFDidLLm280LIwVFiazOqjO3ty8KH") 46 | .oidc_client_secret( 47 | "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", 48 | ), 49 | ), 50 | ) 51 | .await 52 | .unwrap(); 53 | } 54 | } 55 | 56 | mod role { 57 | use super::{Client, OIDCEndpoint}; 58 | use vaultrs::{api::auth::oidc::requests::SetRoleRequest, auth::oidc::role}; 59 | 60 | pub async fn test_delete(client: &impl Client, endpoint: &OIDCEndpoint) { 61 | role::delete(client, endpoint.path.as_str(), endpoint.role.as_str()) 62 | .await 63 | .unwrap(); 64 | } 65 | 66 | pub async fn test_list(client: &impl Client, endpoint: &OIDCEndpoint) { 67 | role::list(client, endpoint.path.as_str()).await.unwrap(); 68 | } 69 | 70 | pub async fn test_read(client: &impl Client, endpoint: &OIDCEndpoint) { 71 | role::read(client, endpoint.path.as_str(), endpoint.role.as_str()) 72 | .await 73 | .unwrap(); 74 | } 75 | 76 | pub async fn test_set(client: &impl Client, endpoint: &OIDCEndpoint) { 77 | role::set( 78 | client, 79 | endpoint.path.as_str(), 80 | endpoint.role.as_str(), 81 | "claim", 82 | vec!["https://samples.auth0.com/authorize".to_string()], 83 | Some(&mut SetRoleRequest::builder()), 84 | ) 85 | .await 86 | .unwrap(); 87 | } 88 | } 89 | 90 | #[derive(Debug)] 91 | pub struct OIDCEndpoint { 92 | pub path: String, 93 | pub role: String, 94 | } 95 | 96 | async fn setup(client: &impl Client) -> Result { 97 | debug!("setting up OIDC auth engine"); 98 | 99 | let path = "oidc_test"; 100 | let role = "test"; 101 | 102 | // Mount the OIDC auth engine 103 | auth::enable(client, path, "oidc", None).await.unwrap(); 104 | Ok(OIDCEndpoint { 105 | path: path.to_string(), 106 | role: role.to_string(), 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/userpass.rs: -------------------------------------------------------------------------------- 1 | use crate::common::Test; 2 | use tracing::debug; 3 | use vaultrs::auth::userpass; 4 | use vaultrs::client::Client; 5 | use vaultrs::error::ClientError; 6 | use vaultrs::sys::auth; 7 | 8 | #[tokio::test] 9 | async fn test() { 10 | let test = Test::builder().await; 11 | let client = test.client(); 12 | let endpoint = setup(client).await.unwrap(); 13 | 14 | // Test user 15 | user::test_set(client, &endpoint).await; 16 | user::test_read(client, &endpoint).await; 17 | user::test_list(client, &endpoint).await; 18 | user::test_update_policies(client, &endpoint).await; 19 | 20 | // Test login 21 | test_login(client, &endpoint).await; 22 | 23 | // Test update password and delete 24 | user::test_update_password(client, &endpoint).await; 25 | user::test_delete(client, &endpoint).await; 26 | } 27 | 28 | pub async fn test_login(client: &impl Client, endpoint: &UserPassEndpoint) { 29 | userpass::login( 30 | client, 31 | endpoint.path.as_str(), 32 | endpoint.username.as_str(), 33 | endpoint.password.as_str(), 34 | ) 35 | .await 36 | .unwrap(); 37 | } 38 | 39 | pub mod user { 40 | use super::{Client, UserPassEndpoint}; 41 | use vaultrs::auth::userpass::user; 42 | 43 | pub async fn test_delete(client: &impl Client, endpoint: &UserPassEndpoint) { 44 | user::delete(client, endpoint.path.as_str(), endpoint.username.as_str()) 45 | .await 46 | .unwrap(); 47 | } 48 | 49 | pub async fn test_list(client: &impl Client, endpoint: &UserPassEndpoint) { 50 | user::list(client, endpoint.path.as_str()).await.unwrap(); 51 | } 52 | 53 | pub async fn test_read(client: &impl Client, endpoint: &UserPassEndpoint) { 54 | user::read(client, endpoint.path.as_str(), endpoint.username.as_str()) 55 | .await 56 | .unwrap(); 57 | } 58 | 59 | pub async fn test_set(client: &impl Client, endpoint: &UserPassEndpoint) { 60 | user::set( 61 | client, 62 | endpoint.path.as_str(), 63 | endpoint.username.as_str(), 64 | endpoint.password.as_str(), 65 | None, 66 | ) 67 | .await 68 | .unwrap(); 69 | } 70 | 71 | pub async fn test_update_password(client: &impl Client, endpoint: &UserPassEndpoint) { 72 | user::update_password( 73 | client, 74 | endpoint.path.as_str(), 75 | endpoint.username.as_str(), 76 | "This1sAT3st!!", 77 | ) 78 | .await 79 | .unwrap(); 80 | } 81 | 82 | pub async fn test_update_policies(client: &impl Client, endpoint: &UserPassEndpoint) { 83 | user::update_policies( 84 | client, 85 | endpoint.path.as_str(), 86 | endpoint.username.as_str(), 87 | "default", 88 | ) 89 | .await 90 | .unwrap(); 91 | } 92 | } 93 | 94 | #[derive(Debug)] 95 | pub struct UserPassEndpoint { 96 | pub path: String, 97 | pub username: String, 98 | pub password: String, 99 | } 100 | 101 | async fn setup(client: &impl Client) -> Result { 102 | debug!("setting up UserPass auth engine"); 103 | 104 | let path = "userpass_test"; 105 | let username = "test"; 106 | let password = "This1sAT3st!"; 107 | 108 | // Mount the UserPass auth engine 109 | auth::enable(client, path, "userpass", None).await.unwrap(); 110 | 111 | Ok(UserPassEndpoint { 112 | path: path.to_string(), 113 | username: username.to_string(), 114 | password: password.to_string(), 115 | }) 116 | } 117 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/cert.rs: -------------------------------------------------------------------------------- 1 | use tracing::debug; 2 | use vaultrs::api::auth::cert::requests::ConfigureTlsCertificateMethodBuilder; 3 | use vaultrs::auth::cert::{self}; 4 | use vaultrs::client::Client; 5 | use vaultrs::sys::auth; 6 | 7 | use crate::common::Test; 8 | 9 | #[tokio::test] 10 | async fn test() { 11 | let test = Test::new_tls().await; 12 | let client = test.client(); 13 | let ca_cert = test.ca_cert().unwrap(); 14 | 15 | let endpoint = setup(client).await; 16 | 17 | // Test CA cert role 18 | ca_cert_role::test_set(client, &endpoint, ca_cert).await; 19 | ca_cert_role::test_read(client, &endpoint).await; 20 | ca_cert_role::test_list(client, &endpoint).await; 21 | 22 | // Test login 23 | test_login(client, &endpoint).await; 24 | 25 | test_configure(client, &endpoint).await; 26 | 27 | // Test delete 28 | ca_cert_role::test_delete(client, &endpoint).await; 29 | } 30 | 31 | pub async fn test_login(client: &impl Client, endpoint: &CertEndpoint) { 32 | cert::login(client, endpoint.path.as_str(), endpoint.name.as_str()) 33 | .await 34 | .unwrap(); 35 | } 36 | 37 | pub async fn test_configure(client: &impl Client, endpoint: &CertEndpoint) { 38 | cert::configure_tls_certificate_method( 39 | client, 40 | endpoint.path.as_str(), 41 | Some( 42 | &mut ConfigureTlsCertificateMethodBuilder::default() 43 | .enable_identity_alias_metadata(true), 44 | ), 45 | ) 46 | .await 47 | .unwrap(); 48 | let login = cert::login(client, endpoint.path.as_str(), endpoint.name.as_str()) 49 | .await 50 | .unwrap(); 51 | let entity = vaultrs::identity::entity::read_by_id(client, &login.entity_id) 52 | .await 53 | .unwrap(); 54 | // FIXME: When we will bump the tested vault to a newer version, we will need to update this assert. 55 | assert!(entity.metadata.is_none()); 56 | } 57 | 58 | pub mod ca_cert_role { 59 | use std::{fs, path::Path}; 60 | 61 | use vaultrs::{auth::cert::ca_cert_role, client::Client}; 62 | 63 | use super::CertEndpoint; 64 | 65 | pub async fn test_delete(client: &impl Client, endpoint: &CertEndpoint) { 66 | ca_cert_role::delete(client, endpoint.path.as_str(), endpoint.name.as_str()) 67 | .await 68 | .unwrap(); 69 | } 70 | 71 | pub async fn test_list(client: &impl Client, endpoint: &CertEndpoint) { 72 | ca_cert_role::list(client, endpoint.path.as_str()) 73 | .await 74 | .unwrap(); 75 | } 76 | 77 | pub async fn test_read(client: &impl Client, endpoint: &CertEndpoint) { 78 | ca_cert_role::read(client, endpoint.path.as_str(), endpoint.name.as_str()) 79 | .await 80 | .unwrap(); 81 | } 82 | 83 | pub async fn test_set(client: &impl Client, endpoint: &CertEndpoint, ca_cert: &Path) { 84 | let client_crt = fs::read_to_string(ca_cert).unwrap(); 85 | 86 | ca_cert_role::set( 87 | client, 88 | endpoint.path.as_str(), 89 | endpoint.name.as_str(), 90 | &client_crt, 91 | None, 92 | ) 93 | .await 94 | .unwrap(); 95 | } 96 | } 97 | 98 | #[derive(Debug)] 99 | pub struct CertEndpoint { 100 | pub path: String, 101 | pub name: String, 102 | } 103 | 104 | async fn setup(client: &impl Client) -> CertEndpoint { 105 | debug!("setting up cert auth engine"); 106 | 107 | let path = "cert_test"; 108 | let name = "test"; 109 | 110 | // Mount the cert auth engine 111 | auth::enable(client, path, "cert", None).await.unwrap(); 112 | 113 | CertEndpoint { 114 | path: path.to_string(), 115 | name: name.to_string(), 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/api/ssh/responses.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Response from executing 6 | /// [ReadRoleRequest][crate::api::ssh::requests::ReadRoleRequest] 7 | #[derive(Deserialize, Debug, Serialize)] 8 | pub struct ReadRoleResponse { 9 | pub algorithm_signer: String, 10 | pub allow_bare_domains: bool, 11 | pub allow_host_certificates: bool, 12 | pub allow_subdomains: bool, 13 | pub allow_user_certificates: bool, 14 | pub allow_user_key_ids: bool, 15 | pub allowed_user_key_lengths: Option>, 16 | pub allowed_critical_options: Option, 17 | pub allowed_domains: String, 18 | pub allowed_extensions: String, 19 | pub allowed_users: String, 20 | pub allowed_users_template: bool, 21 | pub admin_user: Option, 22 | pub cidr_list: Option, 23 | pub default_critical_options: Option>, 24 | pub default_extensions: Option>, 25 | pub default_extensions_template: Option, 26 | pub default_user: String, 27 | pub key_id_format: String, 28 | pub key_type: String, 29 | pub max_ttl: u64, 30 | pub ttl: u64, 31 | } 32 | 33 | /// Response from executing 34 | /// [ListRolesRequest][crate::api::ssh::requests::ListRolesRequest] 35 | #[derive(Deserialize, Debug, Serialize)] 36 | pub struct ListRolesResponse { 37 | pub keys: Vec, 38 | pub key_info: HashMap, 39 | } 40 | 41 | /// Response from executing 42 | /// [ListRolesRequest][crate::api::ssh::requests::ListRolesRequest] 43 | #[derive(Deserialize, Debug, Serialize)] 44 | pub struct KeyInfo { 45 | pub key_type: String, 46 | } 47 | 48 | /// Response from executing 49 | /// [ListZeroAddressRolesRequest][crate::api::ssh::requests::ListZeroAddressRolesRequest] 50 | #[derive(Deserialize, Debug, Serialize)] 51 | pub struct ListZeroAddressRolesResponse { 52 | pub roles: Vec, 53 | } 54 | 55 | /// Response from executing 56 | /// [GenerateSSHCredsRequest][crate::api::ssh::requests::GenerateSSHCredsRequest] 57 | #[derive(Deserialize, Debug, Serialize)] 58 | pub struct GenerateSSHCredsResponse { 59 | pub allowed_users: Option, 60 | pub admin_user: Option, 61 | pub cidr_list: Option, 62 | pub default_user: Option, 63 | pub exclude_cidr_list: Option, 64 | pub install_script: Option, 65 | pub key: String, 66 | pub key_bits: Option, 67 | pub key_option_specs: Option, 68 | pub key_type: String, 69 | pub port: u64, 70 | } 71 | 72 | /// Response from executing 73 | /// [ListRolesByIPRequest][crate::api::ssh::requests::ListRolesByIPRequest] 74 | #[derive(Deserialize, Debug, Serialize)] 75 | pub struct ListRolesByIPResponse { 76 | pub roles: Vec, 77 | } 78 | 79 | /// Response from executing 80 | /// [VerifySSHOTPRequest][crate::api::ssh::requests::VerifySSHOTPRequest] 81 | #[derive(Deserialize, Debug, Serialize)] 82 | pub struct VerifySSHOTPResponse { 83 | pub ip: String, 84 | pub username: String, 85 | } 86 | 87 | /// Response from executing 88 | /// [SubmitCAInfoRequest][crate::api::ssh::requests::SubmitCAInfoRequest] 89 | #[derive(Deserialize, Debug, Serialize)] 90 | pub struct SubmitCAInfoResponse { 91 | pub public_key: String, 92 | } 93 | 94 | /// Response from executing 95 | /// [ReadPublicKeyRequest][crate::api::ssh::requests::ReadPublicKeyRequest] 96 | #[derive(Deserialize, Debug, Serialize)] 97 | pub struct ReadPublicKeyResponse { 98 | pub public_key: String, 99 | } 100 | 101 | /// Response from executing 102 | /// [SignSSHKeyRequest][crate::api::ssh::requests::SignSSHKeyRequest] 103 | #[derive(Deserialize, Debug, Serialize)] 104 | pub struct SignSSHKeyResponse { 105 | pub serial_number: String, 106 | pub signed_key: String, 107 | } 108 | -------------------------------------------------------------------------------- /src/auth/cert.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::{ 3 | self, 4 | auth::cert::requests::{ 5 | ConfigureTlsCertificateMethod, ConfigureTlsCertificateMethodBuilder, LoginRequest, 6 | }, 7 | AuthInfo, 8 | }, 9 | client::Client, 10 | error::ClientError, 11 | }; 12 | 13 | // Fetch a token with policies corresponding to the certificate. 14 | // 15 | // See [LoginRequest] 16 | pub async fn login( 17 | client: &impl Client, 18 | mount: &str, 19 | cert_name: &str, 20 | ) -> Result { 21 | let endpoint = LoginRequest::builder() 22 | .mount(mount) 23 | .cert_name(cert_name) 24 | .build() 25 | .unwrap(); 26 | api::auth(client, endpoint).await 27 | } 28 | 29 | /// ConfigureTlsCertificateMethod 30 | /// 31 | /// See [ConfigureTlsCertificateMethod] 32 | pub async fn configure_tls_certificate_method( 33 | client: &impl Client, 34 | mount: &str, 35 | opts: Option<&mut ConfigureTlsCertificateMethodBuilder>, 36 | ) -> Result<(), ClientError> { 37 | let mut t = ConfigureTlsCertificateMethod::builder(); 38 | let endpoint = opts.unwrap_or(&mut t).mount(mount).build().unwrap(); 39 | api::exec_with_empty(client, endpoint).await 40 | } 41 | 42 | pub mod ca_cert_role { 43 | use crate::{ 44 | api::{ 45 | self, 46 | auth::cert::{ 47 | requests::{ 48 | CreateCaCertificateRoleRequest, CreateCaCertificateRoleRequestBuilder, 49 | DeleteCaCertificateRoleRequest, ListCaCertificateRoleRequest, 50 | ReadCaCertificateRoleRequest, 51 | }, 52 | responses::{ListCaCertificateRoleResponse, ReadCaCertificateRoleResponse}, 53 | }, 54 | }, 55 | client::Client, 56 | error::ClientError, 57 | }; 58 | 59 | /// Deletes a CA certificate role. 60 | /// 61 | /// See [DeleteCaCertificateRoleRequest] 62 | pub async fn delete(client: &impl Client, mount: &str, name: &str) -> Result<(), ClientError> { 63 | let endpoint = DeleteCaCertificateRoleRequest::builder() 64 | .mount(mount) 65 | .name(name) 66 | .build() 67 | .unwrap(); 68 | api::exec_with_empty(client, endpoint).await 69 | } 70 | 71 | /// Lists CA certificate roles. 72 | /// 73 | /// See [ListCaCertificateRoleRequest] 74 | pub async fn list( 75 | client: &impl Client, 76 | mount: &str, 77 | ) -> Result { 78 | let endpoint = ListCaCertificateRoleRequest::builder() 79 | .mount(mount) 80 | .build() 81 | .unwrap(); 82 | api::exec_with_result(client, endpoint).await 83 | } 84 | 85 | /// Reads information about a CA certificate role. 86 | /// 87 | /// See [ReadCaCertificateRoleRequest] 88 | pub async fn read( 89 | client: &impl Client, 90 | mount: &str, 91 | username: &str, 92 | ) -> Result { 93 | let endpoint = ReadCaCertificateRoleRequest::builder() 94 | .mount(mount) 95 | .name(username) 96 | .build() 97 | .unwrap(); 98 | api::exec_with_result(client, endpoint).await 99 | } 100 | 101 | /// Creates a new CA certificate role 102 | /// 103 | /// See [CreateCaCertificateRoleRequest] 104 | pub async fn set( 105 | client: &impl Client, 106 | mount: &str, 107 | name: &str, 108 | certificate: &str, 109 | opts: Option<&mut CreateCaCertificateRoleRequestBuilder>, 110 | ) -> Result<(), ClientError> { 111 | let mut t = CreateCaCertificateRoleRequest::builder(); 112 | let endpoint = opts 113 | .unwrap_or(&mut t) 114 | .mount(mount) 115 | .name(name) 116 | .certificate(certificate) 117 | .build() 118 | .unwrap(); 119 | api::exec_with_empty(client, endpoint).await 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/identity/group.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::{ 3 | self, 4 | identity::group::{ 5 | requests::{ 6 | CreateGroupByNameRequest, CreateGroupByNameRequestBuilder, CreateGroupRequest, 7 | CreateGroupRequestBuilder, DeleteGroupByIdRequest, DeleteGroupByNameRequest, 8 | ListGroupsByIdRequest, ListGroupsByNameRequest, ReadGroupByIdRequest, 9 | ReadGroupByNameRequest, UpdateGroupByIdRequest, UpdateGroupByIdRequestBuilder, 10 | }, 11 | responses::{ 12 | CreateGroupResponse, ListGroupsByIdResponse, ListGroupsByNameResponse, 13 | ReadGroupByIdResponse, ReadGroupByNameResponse, 14 | }, 15 | }, 16 | }, 17 | client::Client, 18 | error::ClientError, 19 | }; 20 | 21 | /// Creates a group. 22 | /// 23 | /// See [CreateGroupRequest] 24 | pub async fn create( 25 | client: &impl Client, 26 | opts: Option<&mut CreateGroupRequestBuilder>, 27 | ) -> Result { 28 | let mut t = CreateGroupRequest::builder(); 29 | let endpoint = opts.unwrap_or(&mut t).build().unwrap(); 30 | api::exec_with_result(client, endpoint).await 31 | } 32 | 33 | /// Reads group by `id`. 34 | /// 35 | /// See [ReadGroupByIdRequest] 36 | pub async fn read_by_id( 37 | client: &impl Client, 38 | id: &str, 39 | ) -> Result { 40 | let endpoint = ReadGroupByIdRequest::builder().id(id).build().unwrap(); 41 | 42 | api::exec_with_result(client, endpoint).await 43 | } 44 | 45 | /// Reads group by `name`. 46 | /// 47 | /// See [ReadGroupByNameRequest] 48 | pub async fn read_by_name( 49 | client: &impl Client, 50 | name: &str, 51 | ) -> Result { 52 | let endpoint = ReadGroupByNameRequest::builder() 53 | .name(name) 54 | .build() 55 | .unwrap(); 56 | 57 | api::exec_with_result(client, endpoint).await 58 | } 59 | /// Update group by `id`. 60 | /// 61 | /// See [UpdateGroupByIdRequest] 62 | pub async fn update_by_id( 63 | client: &impl Client, 64 | id: &str, 65 | opts: Option<&mut UpdateGroupByIdRequestBuilder>, 66 | ) -> Result<(), ClientError> { 67 | let mut t = UpdateGroupByIdRequest::builder(); 68 | let endpoint = opts.unwrap_or(&mut t).id(id).build().unwrap(); 69 | api::exec_with_empty(client, endpoint).await 70 | } 71 | 72 | /// Delete group by `id`. 73 | /// 74 | /// See [DeleteGroupByIdRequest] 75 | pub async fn delete_by_id(client: &impl Client, id: &str) -> Result<(), ClientError> { 76 | let endpoint = DeleteGroupByIdRequest::builder().id(id).build().unwrap(); 77 | api::exec_with_empty(client, endpoint).await 78 | } 79 | 80 | /// List groups by ID. 81 | /// 82 | /// See [ListGroupsByIdRequest] 83 | pub async fn list_by_id(client: &impl Client) -> Result { 84 | let endpoint = ListGroupsByIdRequest::builder().build().unwrap(); 85 | api::exec_with_result(client, endpoint).await 86 | } 87 | /// Creates or update an group with the given `name`. 88 | /// 89 | /// See [CreateGroupByNameRequest] 90 | pub async fn create_or_update_by_name( 91 | client: &impl Client, 92 | name: &str, 93 | opts: Option<&mut CreateGroupByNameRequestBuilder>, 94 | ) -> Result<(), ClientError> { 95 | let mut t = CreateGroupByNameRequest::builder(); 96 | let endpoint = opts.unwrap_or(&mut t).name(name).build().unwrap(); 97 | api::exec_with_empty(client, endpoint).await 98 | } 99 | 100 | /// Delete group by `name`. 101 | /// 102 | /// See [DeleteGroupByIdRequest] 103 | pub async fn delete_by_name(client: &impl Client, name: &str) -> Result<(), ClientError> { 104 | let endpoint = DeleteGroupByNameRequest::builder() 105 | .name(name) 106 | .build() 107 | .unwrap(); 108 | api::exec_with_empty(client, endpoint).await 109 | } 110 | 111 | /// List entities by Name. 112 | /// 113 | /// See [ListGroupsByNameRequest] 114 | pub async fn list_by_name(client: &impl Client) -> Result { 115 | let endpoint = ListGroupsByNameRequest::builder().build().unwrap(); 116 | api::exec_with_result(client, endpoint).await 117 | } 118 | -------------------------------------------------------------------------------- /src/api/identity/group_alias/requests.rs: -------------------------------------------------------------------------------- 1 | use super::responses::{ 2 | CreateGroupAliasResponse, ListGroupAliasesByIdResponse, ReadGroupAliasByIdResponse, 3 | }; 4 | use rustify_derive::Endpoint; 5 | use serde::{Deserialize, Serialize}; 6 | use std::fmt::Debug; 7 | 8 | /// ## Create a group alias 9 | /// 10 | /// This endpoint creates or updates a group alias. 11 | /// 12 | /// * Path: identity/group-alias 13 | /// * Method: POST 14 | /// * Response: [CreateGroupAliasResponse] 15 | /// * Reference: 16 | #[derive(Builder, Debug, Default, Endpoint, Deserialize, Serialize)] 17 | #[endpoint( 18 | path = "identity/group-alias", 19 | method = "POST", 20 | builder = "true", 21 | response = "CreateGroupAliasResponse" 22 | )] 23 | #[builder(setter(into, strip_option), default)] 24 | pub struct CreateGroupAliasRequest { 25 | /// Name of the alias. 26 | pub name: String, 27 | /// ID of the group alias. If set, updates the corresponding group alias. 28 | pub id: Option, 29 | /// Mount accessor which this alias belongs to. 30 | pub mount_accessor: String, 31 | /// ID of the group to which this is an alias. 32 | pub canonical_id: Option, 33 | } 34 | 35 | /// ## Update group alias by ID 36 | /// 37 | /// This endpoint is used to update a existing group alias. 38 | /// 39 | /// * Path: identity/group-alias/id/{self.id} 40 | /// * Method: POST 41 | /// * Reference: 42 | #[derive(Builder, Debug, Default, Endpoint)] 43 | #[endpoint( 44 | path = "identity/group-alias/id/{self.id}", 45 | method = "POST", 46 | builder = "true" 47 | )] 48 | #[builder(setter(into, strip_option), default)] 49 | pub struct UpdateGroupAliasByIdRequest { 50 | /// Identifier of the group alias. 51 | #[endpoint(skip)] 52 | pub id: String, 53 | /// Name of the group alias. 54 | pub name: Option, 55 | /// Mount accessor which this alias belongs to. 56 | pub mount_accessor: String, 57 | /// ID of the group to which this is an alias. 58 | pub canonical_id: Option, 59 | } 60 | 61 | /// ## Read group alias by ID 62 | /// 63 | /// This endpoint queries the group alias by its identifier. 64 | /// 65 | /// * Path: identity/group-alias/id/{self.id} 66 | /// * Method: GET 67 | /// * Response: [ReadGroupAliasByIdResponse] 68 | /// * Reference: 69 | #[derive(Builder, Debug, Endpoint)] 70 | #[endpoint( 71 | path = "identity/group-alias/id/{self.id}", 72 | method = "GET", 73 | builder = "true", 74 | response = "ReadGroupAliasByIdResponse" 75 | )] 76 | #[builder(setter(into))] 77 | pub struct ReadGroupAliasByIdRequest { 78 | /// Identifier of the group alias. 79 | #[endpoint(skip)] 80 | pub id: String, 81 | } 82 | 83 | /// ## Delete group alias by ID 84 | /// 85 | /// This endpoint deletes a group alias. 86 | /// 87 | /// * Path: identity/group-alias/id/{self.id} 88 | /// * Method: DELETE 89 | /// * Reference: 90 | #[derive(Builder, Debug, Default, Endpoint)] 91 | #[endpoint( 92 | path = "identity/group-alias/id/{self.id}", 93 | method = "DELETE", 94 | builder = "true" 95 | )] 96 | #[builder(setter(into, strip_option), default)] 97 | pub struct DeleteGroupAliasByIdRequest { 98 | /// ID of the group alias. 99 | #[endpoint(skip)] 100 | pub id: String, 101 | } 102 | 103 | /// ## List group alias by ID 104 | /// 105 | /// This endpoint returns a list of available group aliases by their identifiers. 106 | /// 107 | /// * Path: identity/group-alias/id 108 | /// * Method: LIST 109 | /// * Response: [ListGroupAliasesByIdResponse] 110 | /// * Reference: 111 | #[derive(Builder, Debug, Endpoint, Default)] 112 | #[endpoint( 113 | path = "identity/group-alias/id", 114 | method = "LIST", 115 | builder = "true", 116 | response = "ListGroupAliasesByIdResponse" 117 | )] 118 | #[builder(setter(into, strip_option), default)] 119 | pub struct ListGroupAliasesByIdRequest {} 120 | -------------------------------------------------------------------------------- /src/auth/userpass.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::{self, auth::userpass::requests::LoginRequest, AuthInfo}, 3 | client::Client, 4 | error::ClientError, 5 | }; 6 | 7 | // Fetch a token with policies corresponding to the username. 8 | // 9 | // See [LoginRequest] 10 | pub async fn login( 11 | client: &impl Client, 12 | mount: &str, 13 | username: &str, 14 | password: &str, 15 | ) -> Result { 16 | let endpoint = LoginRequest::builder() 17 | .mount(mount) 18 | .username(username) 19 | .password(password) 20 | .build() 21 | .unwrap(); 22 | api::auth(client, endpoint).await 23 | } 24 | 25 | pub mod user { 26 | use crate::{ 27 | api::{ 28 | self, 29 | auth::userpass::{ 30 | requests::{ 31 | CreateUserRequest, CreateUserRequestBuilder, DeleteUserRequest, 32 | ListUsersRequest, ReadUserRequest, UpdatePasswordRequest, 33 | UpdatePoliciesRequest, 34 | }, 35 | responses::{ListUsersResponse, ReadUserResponse}, 36 | }, 37 | }, 38 | client::Client, 39 | error::ClientError, 40 | }; 41 | 42 | /// Deletes a user. 43 | /// 44 | /// See [DeleteUserRequest] 45 | pub async fn delete( 46 | client: &impl Client, 47 | mount: &str, 48 | username: &str, 49 | ) -> Result<(), ClientError> { 50 | let endpoint = DeleteUserRequest::builder() 51 | .mount(mount) 52 | .username(username) 53 | .build() 54 | .unwrap(); 55 | api::exec_with_empty(client, endpoint).await 56 | } 57 | 58 | /// Lists users. 59 | /// 60 | /// See [ListUsersRequest] 61 | pub async fn list(client: &impl Client, mount: &str) -> Result { 62 | let endpoint = ListUsersRequest::builder().mount(mount).build().unwrap(); 63 | api::exec_with_result(client, endpoint).await 64 | } 65 | 66 | /// Reads information about a user. 67 | /// 68 | /// See [ReadUserRequest] 69 | pub async fn read( 70 | client: &impl Client, 71 | mount: &str, 72 | username: &str, 73 | ) -> Result { 74 | let endpoint = ReadUserRequest::builder() 75 | .mount(mount) 76 | .username(username) 77 | .build() 78 | .unwrap(); 79 | api::exec_with_result(client, endpoint).await 80 | } 81 | 82 | /// Crates or updates a new user. 83 | /// 84 | /// See [CreateUserRequest] 85 | pub async fn set( 86 | client: &impl Client, 87 | mount: &str, 88 | username: &str, 89 | password: &str, 90 | opts: Option<&mut CreateUserRequestBuilder>, 91 | ) -> Result<(), ClientError> { 92 | let mut t = CreateUserRequest::builder(); 93 | let endpoint = opts 94 | .unwrap_or(&mut t) 95 | .mount(mount) 96 | .username(username) 97 | .password(password) 98 | .build() 99 | .unwrap(); 100 | api::exec_with_empty(client, endpoint).await 101 | } 102 | 103 | /// Updates a user's password. 104 | /// 105 | /// See [UpdatePasswordRequest] 106 | pub async fn update_password( 107 | client: &impl Client, 108 | mount: &str, 109 | username: &str, 110 | password: &str, 111 | ) -> Result<(), ClientError> { 112 | let endpoint = UpdatePasswordRequest::builder() 113 | .mount(mount) 114 | .username(username) 115 | .password(password) 116 | .build() 117 | .unwrap(); 118 | api::exec_with_empty(client, endpoint).await 119 | } 120 | 121 | /// Updates a user's policies. 122 | /// 123 | /// See [UpdatePoliciesRequest] 124 | pub async fn update_policies( 125 | client: &impl Client, 126 | mount: &str, 127 | username: &str, 128 | policies: &str, 129 | ) -> Result<(), ClientError> { 130 | let endpoint = UpdatePoliciesRequest::builder() 131 | .mount(mount) 132 | .username(username) 133 | .policies(policies) 134 | .build() 135 | .unwrap(); 136 | api::exec_with_empty(client, endpoint).await 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /vaultrs-login/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # vaultrs-login 2 | //! 3 | //! > Adds login support for Vault clients from [vaultrs]. 4 | //! 5 | //! ## Installation 6 | //! 7 | //! Add `vaultrs-login` as a dependency to your cargo.toml: 8 | //! 9 | //! ```toml 10 | //! [dependencies] 11 | //! vaultrs-login = "0.2.0" 12 | //! ``` 13 | //! 14 | //! ## Usage 15 | //! 16 | //! ```rust 17 | //! use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; 18 | //! use vaultrs_login::LoginClient; 19 | //! use vaultrs_login::engines::approle::AppRoleLogin; 20 | //! 21 | //! // Create a client 22 | //! let mut client = VaultClient::new( 23 | //! VaultClientSettingsBuilder::default() 24 | //! .address("https://127.0.0.1:8200") 25 | //! .build() 26 | //! .unwrap() 27 | //! ).unwrap(); 28 | //! 29 | //! // Use one of the login flows to obtain a token for the client 30 | //! let role_id = String::from("my-role-id"); 31 | //! let secret_id = String::from("secret"); 32 | //! let login = AppRoleLogin { role_id, secret_id }; 33 | //! 34 | //! # tokio_test::block_on(async { 35 | //! client.login("approle", &login).await; // Token is automatically set to client 36 | //! # }) 37 | //! ``` 38 | //! 39 | //! [vaultrs]: https://docs.rs/vaultrs/latest/vaultrs/ 40 | 41 | #[macro_use] 42 | extern crate tracing; 43 | 44 | use async_trait::async_trait; 45 | use vaultrs::{ 46 | api::AuthInfo, 47 | client::{Client, VaultClient}, 48 | error::ClientError, 49 | }; 50 | 51 | pub mod engines; 52 | pub mod method; 53 | 54 | /// Represents a method for logging into Vault which returns a new token. 55 | #[async_trait] 56 | pub trait LoginMethod: Sync + Send { 57 | async fn login(&self, client: &impl Client, mount: &str) -> Result; 58 | } 59 | 60 | /// Represents a method for logging into Vault which returns a new token but 61 | /// requires two separate steps to complete. 62 | #[async_trait] 63 | pub trait MultiLoginMethod: Sync + Send { 64 | type Callback: MultiLoginCallback; 65 | 66 | async fn login(&self, client: &impl Client, mount: &str) 67 | -> Result; 68 | } 69 | 70 | /// Represents the second step of a multi-step login method that returns the 71 | /// authentication info. 72 | #[async_trait] 73 | pub trait MultiLoginCallback: Sync + Send { 74 | async fn callback(self, client: &impl Client, mount: &str) -> Result; 75 | } 76 | 77 | /// Adds login behavior to [Client]s. 78 | #[async_trait] 79 | pub trait LoginClient: Client + Sized { 80 | /// Performs a login using the given method and sets the resulting token to 81 | /// this client. 82 | #[instrument(skip(self, method), err)] 83 | /// Workaround until is fixed 84 | #[allow(clippy::blocks_in_conditions)] 85 | async fn login( 86 | &mut self, 87 | mount: &str, 88 | method: &M, 89 | ) -> Result<(), ClientError> { 90 | let info = method.login(self, mount).await?; 91 | self.set_token(info.client_token.as_str()); 92 | Ok(()) 93 | } 94 | 95 | /// Performs the first step of a multi-step login, returning the resulting 96 | /// callback which must be passed back to the client to finish the login 97 | /// flow. 98 | #[instrument(skip(self, method), err)] 99 | /// Workaround until is fixed 100 | #[allow(clippy::blocks_in_conditions)] 101 | async fn login_multi( 102 | &self, 103 | mount: &str, 104 | method: M, 105 | ) -> Result { 106 | method.login(self, mount).await 107 | } 108 | 109 | /// Performs the second step of a multi-step login and sets the resulting 110 | /// token to this client. 111 | #[instrument(skip(self, callback), err)] 112 | /// Workaround until is fixed 113 | #[allow(clippy::blocks_in_conditions)] 114 | async fn login_multi_callback( 115 | &mut self, 116 | mount: &str, 117 | callback: C, 118 | ) -> Result<(), ClientError> { 119 | let info = callback.callback(self, mount).await?; 120 | self.set_token(info.client_token.as_str()); 121 | Ok(()) 122 | } 123 | } 124 | 125 | impl LoginClient for VaultClient {} 126 | -------------------------------------------------------------------------------- /src/auth/kubernetes.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::{ 3 | self, auth::kubernetes::requests::ConfigureKubernetesAuthRequest, 4 | auth::kubernetes::requests::ConfigureKubernetesAuthRequestBuilder, 5 | auth::kubernetes::requests::LoginWithKubernetesRequest, 6 | auth::kubernetes::requests::ReadKubernetesAuthConfigRequest, 7 | auth::kubernetes::responses::ReadKubernetesAuthConfigResponse, AuthInfo, 8 | }, 9 | client::Client, 10 | error::ClientError, 11 | }; 12 | 13 | // Configure Kubernetes auth backend. 14 | // 15 | // See [ConfigureKubernetesAuthRequest] 16 | pub async fn configure( 17 | client: &impl Client, 18 | mount: &str, 19 | kubernetes_host: &str, 20 | opts: Option<&mut ConfigureKubernetesAuthRequestBuilder>, 21 | ) -> Result<(), ClientError> { 22 | let mut t = ConfigureKubernetesAuthRequest::builder(); 23 | let endpoint = opts 24 | .unwrap_or(&mut t) 25 | .mount(mount) 26 | .kubernetes_host(kubernetes_host) 27 | .build() 28 | .unwrap(); 29 | 30 | api::exec_with_empty(client, endpoint).await 31 | } 32 | 33 | // Configure Kubernetes auth backend. 34 | // 35 | // See [ReadKubernetesAuthConfigResponse] 36 | pub async fn read_config( 37 | client: &impl Client, 38 | mount: &str, 39 | ) -> Result { 40 | let endpoint = ReadKubernetesAuthConfigRequest::builder() 41 | .mount(mount) 42 | .build() 43 | .unwrap(); 44 | 45 | api::exec_with_result(client, endpoint).await 46 | } 47 | 48 | // Fetch a Result { 57 | let endpoint = LoginWithKubernetesRequest::builder() 58 | .mount(mount) 59 | .role(role) 60 | .jwt(jwt) 61 | .build() 62 | .unwrap(); 63 | api::auth(client, endpoint).await 64 | } 65 | 66 | pub mod role { 67 | use crate::api; 68 | use crate::api::auth::kubernetes::{ 69 | requests::{ 70 | CreateKubernetesRoleRequest, CreateKubernetesRoleRequestBuilder, 71 | DeleteKubernetesRoleRequest, ListRolesRequest, ReadKubernetesRoleRequest, 72 | }, 73 | responses::{ListRolesResponse, ReadKubernetesRoleResponse}, 74 | }; 75 | use crate::client::Client; 76 | use crate::error::ClientError; 77 | 78 | /// Lists all Kubernetes roles. 79 | /// 80 | /// See [ListRolesRequest] 81 | /// 82 | pub async fn list(client: &impl Client, mount: &str) -> Result { 83 | let endpoint = ListRolesRequest::builder().mount(mount).build().unwrap(); 84 | api::exec_with_result(client, endpoint).await 85 | } 86 | 87 | /// Reads properties of a Kubernetes role. 88 | /// 89 | /// See [ReadKubernetesRoleResponse] 90 | pub async fn read( 91 | client: &impl Client, 92 | mount: &str, 93 | name: &str, 94 | ) -> Result { 95 | let endpoint = ReadKubernetesRoleRequest::builder() 96 | .mount(mount) 97 | .name(name) 98 | .build() 99 | .unwrap(); 100 | api::exec_with_result(client, endpoint).await 101 | } 102 | 103 | /// Creates a Kubernetes role. 104 | /// 105 | /// See [CreateKubernetesRoleRequest] 106 | pub async fn create( 107 | client: &impl Client, 108 | mount: &str, 109 | name: &str, 110 | opts: Option<&mut CreateKubernetesRoleRequestBuilder>, 111 | ) -> Result<(), ClientError> { 112 | let mut t = CreateKubernetesRoleRequest::builder(); 113 | let endpoint = opts 114 | .unwrap_or(&mut t) 115 | .mount(mount) 116 | .name(name) 117 | .build() 118 | .unwrap(); 119 | api::exec_with_empty(client, endpoint).await 120 | } 121 | 122 | /// Deletes an existing Kubernetes role. 123 | /// 124 | /// See [DeleteKubernetesRoleRequest] 125 | pub async fn delete(client: &impl Client, mount: &str, name: &str) -> Result<(), ClientError> { 126 | let endpoint = DeleteKubernetesRoleRequest::builder() 127 | .mount(mount) 128 | .name(name) 129 | .build() 130 | .unwrap(); 131 | api::exec_with_empty(client, endpoint).await 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /vaultrs-login/src/engines/aws.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use async_trait::async_trait; 4 | use aws_credential_types::Credentials; 5 | use aws_smithy_runtime_api::client::identity::Identity; 6 | use base64::{engine::general_purpose, Engine as _}; 7 | use std::time::SystemTime; 8 | use vaultrs::{api::AuthInfo, client::Client, error::ClientError}; 9 | 10 | use crate::LoginMethod; 11 | use aws_sigv4::http_request::{sign, SignableRequest, SigningSettings}; 12 | use aws_sigv4::sign::v4; 13 | 14 | /// A login method which uses AWS credentials for obtaining a new token. 15 | #[derive(Debug)] 16 | pub struct AwsIamLogin { 17 | pub access_key: String, 18 | pub secret_key: String, 19 | pub session_token: Option, 20 | pub region: String, 21 | pub role: Option, 22 | pub header_value: Option, 23 | } 24 | 25 | /// A login method which uses EC2 instance pkcs7 signature for obtaining a new token. 26 | #[derive(Debug)] 27 | pub struct AwsEc2Login { 28 | pub pkcs7: String, 29 | pub nonce: Option, 30 | pub role: Option, 31 | } 32 | 33 | #[async_trait] 34 | impl LoginMethod for AwsIamLogin { 35 | async fn login(&self, client: &impl Client, mount: &str) -> Result { 36 | let sts_endpoint = "https://sts.amazonaws.com"; 37 | 38 | let mut req_builder = http::Request::builder() 39 | .uri(sts_endpoint) 40 | .method("POST") 41 | .header( 42 | "Content-Type", 43 | "application/x-www-form-urlencoded;charset=utf-8", 44 | ); 45 | 46 | if let Some(header_value) = &self.header_value { 47 | req_builder = req_builder.header("X-Vault-AWS-IAM-Server-ID", header_value); 48 | } 49 | 50 | let mut request = req_builder 51 | .body("Action=GetCallerIdentity&Version=2011-06-15") 52 | .unwrap(); 53 | let identity = Identity::new( 54 | Credentials::new( 55 | &self.access_key, 56 | &self.secret_key, 57 | self.session_token.clone(), 58 | None, 59 | "hardcoded-credentials", 60 | ), 61 | None, 62 | ); 63 | 64 | let signing_params = v4::SigningParams::builder() 65 | .identity(&identity) 66 | .region(&self.region) 67 | .name("sts") 68 | .settings(SigningSettings::default()) 69 | .time(SystemTime::now()); 70 | 71 | let signable_request = SignableRequest::new( 72 | request.method().as_str(), 73 | request.uri().to_string(), 74 | request 75 | .headers() 76 | .into_iter() 77 | .map(|(name, value)| (name.as_str(), value.to_str().unwrap())), 78 | aws_sigv4::http_request::SignableBody::Bytes(request.body().as_bytes()), 79 | ) 80 | .unwrap(); 81 | let (out, _sig) = sign(signable_request, &signing_params.build().unwrap().into()) 82 | .unwrap() 83 | .into_parts(); 84 | 85 | out.apply_to_request_http0x(&mut request); 86 | 87 | let iam_http_request_method = request.method().as_str(); 88 | let iam_request_url = general_purpose::STANDARD.encode(request.uri().to_string()); 89 | let iam_request_headers = general_purpose::STANDARD.encode( 90 | serde_json::to_string( 91 | &request 92 | .headers() 93 | .iter() 94 | .map(|(k, v)| (k.as_str(), v.to_str().unwrap())) 95 | .collect::>(), 96 | ) 97 | .unwrap(), 98 | ); 99 | let iam_request_body = general_purpose::STANDARD.encode(request.body()); 100 | 101 | vaultrs::auth::aws::iam_login( 102 | client, 103 | mount, 104 | iam_http_request_method, 105 | &iam_request_url, 106 | &iam_request_headers, 107 | &iam_request_body, 108 | self.role.as_deref(), 109 | ) 110 | .await 111 | } 112 | } 113 | 114 | #[async_trait] 115 | impl LoginMethod for AwsEc2Login { 116 | async fn login(&self, client: &impl Client, mount: &str) -> Result { 117 | vaultrs::auth::aws::ec2_login( 118 | client, 119 | mount, 120 | self.pkcs7.as_str(), 121 | self.nonce.as_deref(), 122 | self.role.as_deref(), 123 | ) 124 | .await 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/api/transit/responses.rs: -------------------------------------------------------------------------------- 1 | use super::KeyType; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::HashMap; 4 | 5 | /// Response from executing 6 | /// [ReadKeyRequest][crate::api::transit::requests::ReadKeyRequest] 7 | #[derive(Debug, Serialize, Deserialize)] 8 | pub struct ReadKeyResponse { 9 | #[serde(rename = "type")] 10 | pub key_type: KeyType, 11 | pub deletion_allowed: bool, 12 | pub derived: bool, 13 | pub exportable: bool, 14 | pub allow_plaintext_backup: bool, 15 | /// If the key is asymmetric, the API returns the public keys 16 | pub keys: ReadKeyData, 17 | pub min_decryption_version: u64, 18 | pub min_encryption_version: u64, 19 | pub name: String, 20 | pub supports_encryption: bool, 21 | pub supports_decryption: bool, 22 | pub supports_derivation: bool, 23 | pub supports_signing: bool, 24 | pub imported: Option, 25 | } 26 | 27 | #[derive(Debug, Serialize, Deserialize)] 28 | #[serde(untagged)] 29 | pub enum ReadKeyData { 30 | /// A key ID integer (string) to unix timestamp. 31 | Symmetric(HashMap), 32 | /// A key ID integer (string) to public key mapping. 33 | Asymmetric(HashMap), 34 | } 35 | 36 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] 37 | pub struct ReadPublicKeyEntry { 38 | /// An ISO8601 timestamp 39 | pub creation_time: String, 40 | pub name: String, 41 | pub public_key: String, 42 | } 43 | 44 | /// Response from executing 45 | /// [ListKeysRequest][crate::api::transit::requests::ListKeysRequest] 46 | #[derive(Deserialize, Debug, Serialize)] 47 | pub struct ListKeysResponse { 48 | pub keys: Vec, 49 | } 50 | 51 | /// Response from executing 52 | /// [ExportKeyRequest][crate::api::transit::requests::ExportKeyRequest] 53 | #[derive(Debug, Serialize, Deserialize)] 54 | pub struct ExportKeyResponse { 55 | pub name: String, 56 | pub keys: HashMap, 57 | } 58 | 59 | /// Response from executing 60 | /// [EncryptDataRequest][crate::api::transit::requests::EncryptDataRequest] 61 | #[derive(Debug, Serialize, Deserialize)] 62 | pub struct EncryptDataResponse { 63 | pub ciphertext: String, 64 | } 65 | 66 | /// Response from executing 67 | /// [DecryptDataRequest][crate::api::transit::requests::DecryptDataRequest] 68 | #[derive(Debug, Serialize, Deserialize)] 69 | pub struct DecryptDataResponse { 70 | pub plaintext: String, 71 | } 72 | 73 | /// Response from executing 74 | /// [RewrapDataRequest][crate::api::transit::requests::RewrapDataRequest] 75 | #[derive(Debug, Serialize, Deserialize)] 76 | pub struct RewrapDataResponse { 77 | pub ciphertext: String, 78 | } 79 | 80 | /// Response from executing 81 | /// [GenerateDataKeyRequest][crate::api::transit::requests::GenerateDataKeyRequest] 82 | #[derive(Debug, Serialize, Deserialize)] 83 | pub struct GenerateDataKeyResponse { 84 | pub plaintext: Option, 85 | pub ciphertext: String, 86 | } 87 | 88 | /// Response from executing 89 | /// [GenerateRandomBytesRequest][crate::api::transit::requests::GenerateRandomBytesRequest] 90 | #[derive(Debug, Serialize, Deserialize)] 91 | pub struct GenerateRandomBytesResponse { 92 | pub random_bytes: String, 93 | } 94 | 95 | /// Response from executing 96 | /// [HashDataRequest][crate::api::transit::requests::HashDataRequest] 97 | #[derive(Debug, Serialize, Deserialize)] 98 | pub struct HashDataResponse { 99 | pub sum: String, 100 | } 101 | 102 | /// Response from executing 103 | /// [GenerateHmacRequest][crate::api::transit::requests::GenerateHmacRequest] 104 | #[derive(Debug, Serialize, Deserialize)] 105 | pub struct GenerateHmacResponse { 106 | pub hmac: String, 107 | } 108 | 109 | /// Response from executing 110 | /// [SignDataRequest][crate::api::transit::requests::SignDataRequest] 111 | #[derive(Debug, Serialize, Deserialize)] 112 | pub struct SignDataResponse { 113 | pub signature: String, 114 | } 115 | 116 | /// Response from executing 117 | /// [VerifySignedDataRequest][crate::api::transit::requests::VerifySignedDataRequest] 118 | #[derive(Debug, Serialize, Deserialize)] 119 | pub struct VerifySignedDataResponse { 120 | pub valid: bool, 121 | } 122 | 123 | /// Response from executing 124 | /// [BackupKeyRequest][crate::api::transit::requests::BackupKeyRequest] 125 | #[derive(Debug, Serialize, Deserialize)] 126 | pub struct BackupKeyResponse { 127 | pub backup: String, 128 | } 129 | 130 | /// Response from executing 131 | /// [ReadTransitCacheConfigurationRequest][crate::api::transit::requests::ReadTransitCacheConfigurationRequest] 132 | #[derive(Debug, Serialize, Deserialize)] 133 | pub struct ReadTransitCacheConfigurationResponse { 134 | pub size: u64, 135 | } 136 | 137 | /// Response from executing 138 | /// [GetWrappingKeyRequest][crate::api::transit::requests::GetWrappingKeyRequest] 139 | #[derive(Debug, Serialize, Deserialize)] 140 | pub struct GetWrappingKeyResponse { 141 | pub public_key: String, 142 | } 143 | -------------------------------------------------------------------------------- /vaultrs-login/src/engines/oidc.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use async_trait::async_trait; 4 | use tiny_http::{Response, Server}; 5 | use tokio::task::JoinHandle; 6 | use vaultrs::{api::AuthInfo, client::Client, error::ClientError}; 7 | 8 | use crate::{MultiLoginCallback, MultiLoginMethod}; 9 | 10 | /// A login method which uses OIDC credentials for obtaining a new token. 11 | #[derive(Debug)] 12 | pub struct OIDCLogin { 13 | pub port: Option, // Defaults to 8250 14 | pub role: Option, // Defaults to what's configured in the backend 15 | } 16 | 17 | /// The callback for the OIDC login method. 18 | #[derive(Debug)] 19 | pub struct OIDCCallback { 20 | pub handle: JoinHandle, 21 | pub url: String, 22 | } 23 | 24 | // The parameters returned by the OAuth authorization server after successful 25 | // authentication. 26 | #[derive(Debug, Default)] 27 | pub struct OIDCCallbackParams { 28 | pub code: String, 29 | pub nonce: String, 30 | pub state: String, 31 | } 32 | 33 | #[async_trait] 34 | impl MultiLoginMethod for OIDCLogin { 35 | type Callback = OIDCCallback; 36 | 37 | /// Runs a standalone HTTP server which listens for the OIDC callback. 38 | /// 39 | /// This method performs several things. It firsts constructs a redirect URL 40 | /// which points back to the HTTP address of the web server it starts. It 41 | /// then asks Vault for an authorization URL using the constructed redirect. 42 | /// Finally, it starts a small HTTP server that listens for the redirect 43 | /// from the OAuth authorization server, capturing the various parameters 44 | /// and returning them as a [OIDCCallbackParams]. 45 | /// 46 | /// The function returns an [OIDCCallback] which contains the authorization 47 | /// URL generated by Vault which an end-user must visit to complete the 48 | /// authorization flow. It also returns a handle to the task running the 49 | /// HTTP server. The `callback` method can be awaited on and will only 50 | /// return once the redirect has been received. 51 | async fn login( 52 | &self, 53 | client: &impl Client, 54 | mount: &str, 55 | ) -> Result { 56 | // The Vault CLI uses 57 | // we match that here to try and remain consistent 58 | let port = self.port.unwrap_or(8250); 59 | let ip = "127.0.0.1"; 60 | let hostname = "localhost"; 61 | 62 | let base = url::Url::parse(format!("http://{hostname}:{port}").as_str()).unwrap(); 63 | let redirect = base.join("oidc/callback").unwrap().to_string(); 64 | let response = 65 | vaultrs::auth::oidc::auth(client, mount, redirect.as_str(), self.role.clone()).await?; 66 | let server = Server::http(format!("{ip}:{port}")).unwrap(); 67 | 68 | let handle = tokio::task::spawn_blocking(move || { 69 | let mut result = OIDCCallbackParams::default(); 70 | for request in server.incoming_requests() { 71 | let url = base.join(request.url()).unwrap(); 72 | let query: HashMap<_, _> = url.query_pairs().into_owned().collect(); 73 | 74 | result.code = query 75 | .get("code") 76 | .cloned() 77 | .or_else(|| Some("".to_string())) 78 | .unwrap(); 79 | result.nonce = query 80 | .get("nonce") 81 | .cloned() 82 | .or_else(|| Some("".to_string())) 83 | .unwrap(); 84 | result.state = query 85 | .get("state") 86 | .cloned() 87 | .or_else(|| Some("".to_string())) 88 | .unwrap(); 89 | 90 | request 91 | .respond(Response::from_string("Success!")) 92 | .expect("Error responding!"); 93 | server.unblock(); 94 | } 95 | result 96 | }); 97 | 98 | Ok(OIDCCallback { 99 | handle, 100 | url: response.auth_url, 101 | }) 102 | } 103 | } 104 | 105 | #[async_trait] 106 | impl MultiLoginCallback for OIDCCallback { 107 | /// Exchanges OIDC callback parameters for a Vault token. 108 | /// 109 | /// This method will block until the underlying HTTP server receives a 110 | /// request from the OAuth authorization server at the redirect URL. It uses 111 | /// the resulting state, code, and nonce to retrieve a token from Vault. 112 | async fn callback(self, client: &impl Client, mount: &str) -> Result { 113 | let result = self.handle.await.unwrap(); 114 | vaultrs::auth::oidc::callback( 115 | client, 116 | mount, 117 | result.state.as_str(), 118 | result.nonce.as_str(), 119 | result.code.as_str(), 120 | ) 121 | .await 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/token.rs: -------------------------------------------------------------------------------- 1 | use crate::common::Test; 2 | use tracing::debug; 3 | use vaultrs::client::Client; 4 | use vaultrs::{api::token::requests::CreateTokenRequest, error::ClientError, token}; 5 | 6 | #[tokio::test] 7 | async fn test() { 8 | let test = Test::builder().await; 9 | let client = test.client(); 10 | let mut token = setup(client).await.unwrap(); 11 | 12 | // Test token roles 13 | role::test_set(client, "test").await; 14 | role::test_list(client).await; 15 | role::test_read(client, "test").await; 16 | role::test_delete(client, "test").await; 17 | 18 | // Test tidy 19 | test_tidy(client).await; 20 | 21 | // Test creating tokens 22 | test_new(client).await; 23 | test_new_orphan(client).await; 24 | 25 | // Test looking up tokens 26 | test_lookup(client, token.token.as_str()).await; 27 | test_lookup_self(client).await; 28 | test_lookup_accessor(client, token.accessor.as_str()).await; 29 | 30 | // Test renewing tokens 31 | test_renew(client, token.token.as_str()).await; 32 | test_renew_self(client).await; 33 | test_renew_accessor(client, token.accessor.as_str()).await; 34 | 35 | // Test revoking tokens 36 | test_revoke(client, token.token.as_str()).await; 37 | token = setup(client).await.unwrap(); 38 | test_revoke_accessor(client, token.accessor.as_str()).await; 39 | token = setup(client).await.unwrap(); 40 | test_revoke_orphan(client, token.token.as_str()).await; 41 | 42 | test_revoke_self(client).await; 43 | } 44 | 45 | pub async fn test_lookup(client: &impl Client, token: &str) { 46 | token::lookup(client, token).await.unwrap(); 47 | } 48 | 49 | pub async fn test_lookup_accessor(client: &impl Client, accessor: &str) { 50 | token::lookup_accessor(client, accessor).await.unwrap(); 51 | } 52 | 53 | pub async fn test_lookup_self(client: &impl Client) { 54 | token::lookup_self(client).await.unwrap(); 55 | } 56 | 57 | pub async fn test_new(client: &impl Client) { 58 | token::new(client, None).await.unwrap(); 59 | } 60 | 61 | pub async fn test_new_orphan(client: &impl Client) { 62 | token::new_orphan(client, None).await.unwrap(); 63 | } 64 | 65 | pub async fn test_renew(client: &impl Client, token: &str) { 66 | token::renew(client, token, Some("20m")).await.unwrap(); 67 | } 68 | 69 | pub async fn test_renew_accessor(client: &impl Client, accessor: &str) { 70 | token::renew_accessor(client, accessor, Some("20m")) 71 | .await 72 | .unwrap(); 73 | } 74 | 75 | pub async fn test_renew_self(client: &impl Client) { 76 | let resp = token::renew_self(client, Some("20m")).await; 77 | // Cannot renew the root token 78 | if let ClientError::APIError { code: _, errors } = resp.unwrap_err() { 79 | assert_eq!(errors[0], "lease is not renewable"); 80 | } 81 | } 82 | 83 | pub async fn test_revoke(client: &impl Client, token: &str) { 84 | token::revoke(client, token).await.unwrap(); 85 | } 86 | 87 | pub async fn test_revoke_accessor(client: &impl Client, accessor: &str) { 88 | token::revoke_accessor(client, accessor).await.unwrap(); 89 | } 90 | 91 | pub async fn test_revoke_orphan(client: &impl Client, token: &str) { 92 | token::revoke_orphan(client, token).await.unwrap(); 93 | } 94 | 95 | pub async fn test_revoke_self(client: &impl Client) { 96 | token::revoke_self(client).await.unwrap(); 97 | } 98 | 99 | pub async fn test_tidy(client: &impl Client) { 100 | token::tidy(client).await.unwrap(); 101 | } 102 | 103 | mod role { 104 | use vaultrs::api::token::requests::SetTokenRoleRequest; 105 | use vaultrs::token::role; 106 | 107 | use super::Client; 108 | 109 | pub async fn test_delete(client: &impl Client, role_name: &str) { 110 | role::delete(client, role_name).await.unwrap(); 111 | } 112 | 113 | pub async fn test_list(client: &impl Client) { 114 | role::list(client).await.unwrap(); 115 | } 116 | 117 | pub async fn test_read(client: &impl Client, role_name: &str) { 118 | role::read(client, role_name).await.unwrap(); 119 | } 120 | 121 | pub async fn test_set(client: &impl Client, role_name: &str) { 122 | role::set( 123 | client, 124 | role_name, 125 | Some( 126 | SetTokenRoleRequest::builder() 127 | .renewable(true) 128 | .token_explicit_max_ttl("1h"), 129 | ), 130 | ) 131 | .await 132 | .unwrap(); 133 | } 134 | } 135 | 136 | // TODO: Add test for create token with role 137 | 138 | struct Token { 139 | pub accessor: String, 140 | pub token: String, 141 | } 142 | 143 | async fn setup(client: &impl Client) -> Result { 144 | debug!("creating new token"); 145 | 146 | // Create a new token 147 | let resp = token::new( 148 | client, 149 | Some( 150 | CreateTokenRequest::builder() 151 | .ttl("10m") 152 | .renewable(true) 153 | .explicit_max_ttl("1h"), 154 | ), 155 | ) 156 | .await?; 157 | Ok(Token { 158 | accessor: resp.accessor, 159 | token: resp.client_token, 160 | }) 161 | } 162 | -------------------------------------------------------------------------------- /vaultrs-login/src/method.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, convert::TryFrom, str::FromStr}; 2 | 3 | use serde::Deserialize; 4 | use vaultrs::{client::Client, error::ClientError}; 5 | 6 | /// Contains the login methods currently supported by this crate 7 | pub const SUPPORTED_METHODS: [Method; 4] = 8 | [Method::APPROLE, Method::OIDC, Method::USERPASS, Method::AWS]; 9 | 10 | /// Represents all login methods. 11 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] 12 | #[serde(rename_all = "lowercase")] 13 | pub enum Method { 14 | ALICLOUD, 15 | APPROLE, 16 | AWS, 17 | AZURE, 18 | CERT, 19 | CF, 20 | GCP, 21 | GITHUB, 22 | KERBEROS, 23 | KUBERNETES, 24 | LDAP, 25 | OCI, 26 | OIDC, 27 | OKTA, 28 | RADIUS, 29 | TOKEN, 30 | USERPASS, 31 | } 32 | 33 | impl std::fmt::Display for Method { 34 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 35 | match *self { 36 | Method::ALICLOUD => write!(f, "AliCloud"), 37 | Method::APPROLE => write!(f, "AppRole"), 38 | Method::AWS => write!(f, "AWS"), 39 | Method::AZURE => write!(f, "Azure"), 40 | Method::CERT => write!(f, "TLS Certificates"), 41 | Method::CF => write!(f, "Cloud Foundry"), 42 | Method::GCP => write!(f, "GCP"), 43 | Method::GITHUB => write!(f, "Github"), 44 | Method::KERBEROS => write!(f, "Kerberos"), 45 | Method::KUBERNETES => write!(f, "Kubernetes"), 46 | Method::LDAP => write!(f, "LDAP"), 47 | Method::OCI => write!(f, "Oracle Cloud Infrastructure"), 48 | Method::OIDC => write!(f, "OpenID Connect"), 49 | Method::OKTA => write!(f, "Okta"), 50 | Method::RADIUS => write!(f, "RADIUS"), 51 | Method::TOKEN => write!(f, "Token"), 52 | Method::USERPASS => write!(f, "Username/Password"), 53 | } 54 | } 55 | } 56 | 57 | impl FromStr for Method { 58 | type Err = ClientError; 59 | 60 | fn from_str(s: &str) -> Result { 61 | Ok(match s.to_lowercase().as_str() { 62 | "alicloud" => Method::ALICLOUD, 63 | "approle" => Method::APPROLE, 64 | "aws" => Method::AWS, 65 | "azure" => Method::AZURE, 66 | "cert" => Method::CERT, 67 | "cf" => Method::CF, 68 | "gcp" => Method::GCP, 69 | "github" => Method::GITHUB, 70 | "kerberos" => Method::KERBEROS, 71 | "kubernetes" => Method::KUBERNETES, 72 | "ldap" => Method::LDAP, 73 | "oci" => Method::OCI, 74 | "oidc" => Method::OIDC, 75 | "okta" => Method::OKTA, 76 | "radius" => Method::RADIUS, 77 | "token" => Method::TOKEN, 78 | "userpass" => Method::USERPASS, 79 | _ => return Err(ClientError::InvalidLoginMethodError), 80 | }) 81 | } 82 | } 83 | 84 | impl TryFrom for Method { 85 | type Error = ClientError; 86 | 87 | fn try_from(source: String) -> Result { 88 | source.parse() 89 | } 90 | } 91 | 92 | impl<'a> TryFrom<&'a str> for Method { 93 | type Error = ClientError; 94 | 95 | fn try_from(source: &'a str) -> Result { 96 | source.parse() 97 | } 98 | } 99 | 100 | #[allow(clippy::from_over_into)] 101 | impl<'a> Into<&'a str> for Method { 102 | fn into(self) -> &'a str { 103 | match self { 104 | Method::ALICLOUD => "alicloud", 105 | Method::APPROLE => "approle", 106 | Method::AWS => "aws", 107 | Method::AZURE => "azure", 108 | Method::CERT => "cert", 109 | Method::CF => "cf", 110 | Method::GCP => "gcp", 111 | Method::GITHUB => "github", 112 | Method::KERBEROS => "kerberos", 113 | Method::KUBERNETES => "kubernetes", 114 | Method::LDAP => "ldap", 115 | Method::OCI => "oci", 116 | Method::OIDC => "oidc", 117 | Method::OKTA => "okta", 118 | Method::RADIUS => "radius", 119 | Method::TOKEN => "token", 120 | Method::USERPASS => "userpass", 121 | } 122 | } 123 | } 124 | 125 | impl From for String { 126 | fn from(m: Method) -> Self { 127 | let s: &str = m.into(); 128 | s.to_string() 129 | } 130 | } 131 | 132 | /// Returns the default mount point for the given auth method 133 | pub fn default_mount(method: &Method) -> String { 134 | method.clone().into() 135 | } 136 | 137 | /// Returns a list of login methods available on the Vault server 138 | pub async fn list(client: &impl Client) -> Result, ClientError> { 139 | let mounts = vaultrs::sys::auth::list(client).await?; 140 | let mut result = HashMap::new(); 141 | for (path, info) in mounts { 142 | result.insert(path, info.mount_type.parse::()?); 143 | } 144 | Ok(result) 145 | } 146 | 147 | /// Returns a list of login methods currently supported by this crate 148 | pub async fn list_supported(client: &impl Client) -> Result, ClientError> { 149 | let mut mounts = list(client).await?; 150 | mounts.retain(|_, v| SUPPORTED_METHODS.contains(v)); 151 | Ok(mounts) 152 | } 153 | -------------------------------------------------------------------------------- /src/api/identity/entity_alias/requests.rs: -------------------------------------------------------------------------------- 1 | use super::responses::{ 2 | CreateEntityAliasResponse, ListEntityAliasesByIdResponse, ReadEntityAliasByIdResponse, 3 | }; 4 | use rustify_derive::Endpoint; 5 | use serde::Serialize; 6 | use std::{collections::HashMap, fmt::Debug}; 7 | 8 | /// ## Create an entity alias 9 | /// 10 | /// This endpoint creates a new alias for an entity. 11 | /// 12 | /// * Path: identity/entity-alias 13 | /// * Method: POST 14 | /// * Response: [`Option`] 15 | /// * Reference: 16 | #[derive(Builder, Debug, Default, Endpoint)] 17 | #[endpoint( 18 | path = "identity/entity-alias", 19 | response = "CreateEntityAliasResponse", 20 | method = "POST", 21 | builder = "true" 22 | )] 23 | #[builder(setter(into, strip_option), default)] 24 | pub struct CreateEntityAliasRequest { 25 | /// Name of the alias. Name should be the identifier of the client in the authentication source. 26 | /// For example, if the alias belongs to userpass backend, the name should be a valid username within userpass auth method. 27 | /// If the alias belongs to GitHub, it should be the GitHub username. 28 | /// If the alias belongs to an approle auth method, the name should be a valid RoleID. 29 | pub name: String, 30 | /// Entity ID to which this alias belongs to. 31 | pub canonical_id: String, 32 | /// Accessor of the mount to which the alias should belong to. 33 | pub mount_accessor: String, 34 | /// ID of the entity alias. If set, updates the corresponding entity alias. 35 | pub id: Option, 36 | /// A map of arbitrary string to string valued user-provided metadata meant to describe the alias. 37 | pub custom_metadata: Option>, 38 | } 39 | 40 | /// ## Read entity alias by ID 41 | /// 42 | /// This endpoint queries the entity alias by its identifier. 43 | /// 44 | /// * Path: identity/entity-alias/id/{self.id} 45 | /// * Method: GET 46 | /// * Response: [ReadEntityAliasByIdResponse] 47 | /// * Reference: 48 | #[derive(Builder, Debug, Endpoint)] 49 | #[endpoint( 50 | path = "identity/entity-alias/id/{self.id}", 51 | method = "GET", 52 | builder = "true", 53 | response = "ReadEntityAliasByIdResponse" 54 | )] 55 | #[builder(setter(into))] 56 | pub struct ReadEntityAliasByIdRequest { 57 | /// Identifier of the entity alias. 58 | #[endpoint(skip)] 59 | pub id: String, 60 | } 61 | 62 | /// ## Update entity alias by ID 63 | /// 64 | /// This endpoint is used to update an existing entity alias. 65 | /// 66 | /// * Path: identity/entity-alias/id/{self.id} 67 | /// * Method: POST 68 | /// * Reference: 69 | #[derive(Builder, Debug, Default, Endpoint)] 70 | #[endpoint( 71 | path = "identity/entity-alias/id/{self.id}", 72 | method = "POST", 73 | builder = "true" 74 | )] 75 | #[builder(setter(into, strip_option), default)] 76 | pub struct UpdateEntityAliasByIdRequest { 77 | /// Identifier of the entity alias. 78 | #[endpoint(skip)] 79 | pub id: String, 80 | /// Name of the alias. Name should be the identifier of the client in the authentication source. 81 | /// For example, if the alias belongs to userpass backend, the name should be a valid username within userpass backend. 82 | /// If alias belongs to GitHub, it should be the GitHub username. 83 | pub name: Option, 84 | /// Entity ID to which this alias belongs to. 85 | pub canonical_id: Option, 86 | /// Accessor of the mount to which the alias should belong to. 87 | pub mount_accessor: Option, 88 | /// A map of arbitrary string to string valued user-provided metadata meant to describe the alias. 89 | pub custom_metadata: Option>, 90 | } 91 | 92 | /// ## Delete entity alias by ID 93 | /// 94 | /// This endpoint deletes an alias from its corresponding entity. 95 | /// 96 | /// * Path: identity/entity-alias/id/{self.id} 97 | /// * Method: DELETE 98 | /// * Reference: 99 | #[derive(Builder, Debug, Default, Endpoint)] 100 | #[endpoint( 101 | path = "identity/entity-alias/id/{self.id}", 102 | method = "DELETE", 103 | builder = "true" 104 | )] 105 | #[builder(setter(into, strip_option), default)] 106 | pub struct DeleteEntityAliasByIdRequest { 107 | /// Identifier of the entity alias. 108 | #[endpoint(skip)] 109 | pub id: String, 110 | } 111 | 112 | /// ## List entity alias by ID 113 | /// 114 | /// The list by ID endpoint returns the available entity aliases and key data by their identifiers. 115 | /// 116 | /// * Path: identity/entity-alias/id 117 | /// * Method: LIST 118 | /// * Response: [ListEntityAliasesByIdResponse] 119 | /// * Reference: 120 | #[derive(Builder, Debug, Endpoint, Default)] 121 | #[endpoint( 122 | path = "identity/entity-alias/id", 123 | method = "LIST", 124 | builder = "true", 125 | response = "ListEntityAliasesByIdResponse" 126 | )] 127 | #[builder(setter(into, strip_option), default)] 128 | pub struct ListEntityAliasesByIdRequest {} 129 | -------------------------------------------------------------------------------- /src/identity/entity.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::{ 4 | api::{ 5 | self, 6 | identity::entity::{ 7 | requests::{ 8 | BatchDeleteRequest, CreateEntityByNameRequest, CreateEntityByNameRequestBuilder, 9 | CreateEntityRequest, CreateEntityRequestBuilder, DeleteEntityByIdRequest, 10 | DeleteEntityByNameRequest, ListEntitiesByIdRequest, ListEntitiesByNameRequest, 11 | MergeEntitiesRequest, MergeEntitiesRequestBuilder, ReadEntityByIdRequest, 12 | ReadEntityByNameRequest, UpdateEntityByIdRequest, UpdateEntityByIdRequestBuilder, 13 | }, 14 | responses::{ 15 | CreateEntityResponse, ListEntitiesByIdResponse, ListEntitiesByNameResponse, 16 | ReadEntityByIdResponse, ReadEntityByNameResponse, 17 | }, 18 | }, 19 | }, 20 | client::Client, 21 | error::ClientError, 22 | }; 23 | 24 | /// Create an entity. 25 | /// 26 | /// See [CreateEntityRequest] 27 | pub async fn create( 28 | client: &impl Client, 29 | opts: Option<&mut CreateEntityRequestBuilder>, 30 | ) -> Result { 31 | let mut t = CreateEntityRequest::builder(); 32 | let endpoint = opts.unwrap_or(&mut t).build().unwrap(); 33 | api::exec_with_result(client, endpoint).await 34 | } 35 | 36 | /// Reads entity by `id`. 37 | /// 38 | /// See [ReadEntityByIdRequest] 39 | pub async fn read_by_id( 40 | client: &impl Client, 41 | id: &str, 42 | ) -> Result { 43 | let endpoint = ReadEntityByIdRequest::builder().id(id).build().unwrap(); 44 | 45 | api::exec_with_result(client, endpoint).await 46 | } 47 | 48 | /// Update entity by `id`. 49 | /// 50 | /// See [UpdateEntityByIdRequest] 51 | pub async fn update_by_id( 52 | client: &impl Client, 53 | id: &str, 54 | opts: Option<&mut UpdateEntityByIdRequestBuilder>, 55 | ) -> Result<(), ClientError> { 56 | let mut t = UpdateEntityByIdRequest::builder(); 57 | let endpoint = opts.unwrap_or(&mut t).id(id).build().unwrap(); 58 | api::exec_with_empty(client, endpoint).await 59 | } 60 | 61 | /// Delete entity by `id`. 62 | /// 63 | /// See [DeleteEntityByIdRequest] 64 | pub async fn delete_by_id(client: &impl Client, id: &str) -> Result<(), ClientError> { 65 | let endpoint = DeleteEntityByIdRequest::builder().id(id).build().unwrap(); 66 | api::exec_with_empty(client, endpoint).await 67 | } 68 | 69 | /// Batch delete entity. 70 | /// 71 | /// See [BatchDeleteRequest] 72 | pub async fn batch_delete>>( 73 | client: &impl Client, 74 | entity_ids: T, 75 | ) -> Result<(), ClientError> { 76 | let endpoint = BatchDeleteRequest::builder() 77 | .entity_ids(entity_ids) 78 | .build() 79 | .unwrap(); 80 | api::exec_with_empty(client, endpoint).await 81 | } 82 | 83 | /// List entities by ID. 84 | /// 85 | /// See [ListEntitiesByIdRequest] 86 | pub async fn list_by_id(client: &impl Client) -> Result { 87 | let endpoint = ListEntitiesByIdRequest::builder().build().unwrap(); 88 | api::exec_with_result(client, endpoint).await 89 | } 90 | 91 | /// Creates or update an entity with the given `name`. 92 | /// 93 | /// See [CreateEntityByNameRequest] 94 | pub async fn create_or_update_by_name( 95 | client: &impl Client, 96 | name: &str, 97 | opts: Option<&mut CreateEntityByNameRequestBuilder>, 98 | ) -> Result<(), ClientError> { 99 | let mut t = CreateEntityByNameRequest::builder(); 100 | let endpoint = opts.unwrap_or(&mut t).name(name).build().unwrap(); 101 | api::exec_with_empty(client, endpoint).await 102 | } 103 | 104 | /// Reads entity by `name`. 105 | /// 106 | /// See [ReadEntityByNameRequest] 107 | pub async fn read_by_name( 108 | client: &impl Client, 109 | name: &str, 110 | ) -> Result { 111 | let endpoint = ReadEntityByNameRequest::builder() 112 | .name(name) 113 | .build() 114 | .unwrap(); 115 | 116 | api::exec_with_result(client, endpoint).await 117 | } 118 | 119 | /// Delete entity by `name`. 120 | /// 121 | /// See [DeleteEntityByIdRequest] 122 | pub async fn delete_by_name(client: &impl Client, name: &str) -> Result<(), ClientError> { 123 | let endpoint = DeleteEntityByNameRequest::builder() 124 | .name(name) 125 | .build() 126 | .unwrap(); 127 | api::exec_with_empty(client, endpoint).await 128 | } 129 | 130 | /// List entities by Name. 131 | /// 132 | /// See [ListEntitiesByNameRequest] 133 | pub async fn list_by_name(client: &impl Client) -> Result { 134 | let endpoint = ListEntitiesByNameRequest::builder().build().unwrap(); 135 | api::exec_with_result(client, endpoint).await 136 | } 137 | 138 | /// Merge entities. 139 | /// 140 | /// See [MergeEntitiesRequest] 141 | pub async fn merge( 142 | client: &impl Client, 143 | from_entity_ids: Vec, 144 | to_entity_id: String, 145 | opts: Option<&mut MergeEntitiesRequestBuilder>, 146 | ) -> Result<(), ClientError> { 147 | let mut t = MergeEntitiesRequest::builder(); 148 | let endpoint = opts 149 | .unwrap_or(&mut t) 150 | .from_entity_ids(from_entity_ids) 151 | .to_entity_id(to_entity_id) 152 | .build() 153 | .unwrap(); 154 | api::exec_with_empty(client, endpoint).await 155 | } 156 | -------------------------------------------------------------------------------- /src/api/auth/userpass/requests.rs: -------------------------------------------------------------------------------- 1 | use super::responses::{ListUsersResponse, ReadUserResponse}; 2 | use rustify_derive::Endpoint; 3 | 4 | /// ## Create/Update User 5 | /// Create a new user or update an existing user. 6 | /// 7 | /// * Path: /auth/{self.mount}/users/{self.username} 8 | /// * Method: POST 9 | /// * Response: N/A 10 | /// * Reference: 11 | #[derive(Builder, Debug, Default, Endpoint)] 12 | #[endpoint( 13 | path = "/auth/{self.mount}/users/{self.username}", 14 | method = "POST", 15 | builder = "true" 16 | )] 17 | #[builder(setter(into, strip_option), default)] 18 | pub struct CreateUserRequest { 19 | #[endpoint(skip)] 20 | pub mount: String, 21 | #[endpoint(skip)] 22 | pub username: String, 23 | pub password: String, 24 | pub token_bound_cidrs: Option>, 25 | pub token_explicit_max_ttl: Option, 26 | pub token_no_default_policy: Option, 27 | pub token_num_uses: Option, 28 | pub token_ttl: Option, 29 | pub token_max_ttl: Option, 30 | pub token_period: Option, 31 | pub token_policies: Option>, 32 | pub token_type: Option, 33 | } 34 | 35 | /// ## Read User 36 | /// Reads the properties of an existing username. 37 | /// 38 | /// * Path: /auth/{self.mount}/users/{self.username} 39 | /// * Method: GET 40 | /// * Response: [ReadUserResponse] 41 | /// * Reference: 42 | #[derive(Builder, Debug, Default, Endpoint)] 43 | #[endpoint( 44 | path = "/auth/{self.mount}/users/{self.username}", 45 | response = "ReadUserResponse", 46 | builder = "true" 47 | )] 48 | #[builder(setter(into, strip_option), default)] 49 | pub struct ReadUserRequest { 50 | #[endpoint(skip)] 51 | pub mount: String, 52 | #[endpoint(skip)] 53 | pub username: String, 54 | } 55 | 56 | /// ## Delete User 57 | /// This endpoint deletes the user from the method. 58 | /// 59 | /// * Path: /auth/{self.mount}/users/{self.username} 60 | /// * Method: DELETE 61 | /// * Response: N/A 62 | /// * Reference: 63 | #[derive(Builder, Debug, Default, Endpoint)] 64 | #[endpoint( 65 | path = "/auth/{self.mount}/users/{self.username}", 66 | method = "DELETE", 67 | builder = "true" 68 | )] 69 | #[builder(setter(into, strip_option), default)] 70 | pub struct DeleteUserRequest { 71 | #[endpoint(skip)] 72 | pub mount: String, 73 | #[endpoint(skip)] 74 | pub username: String, 75 | } 76 | 77 | /// ## Update Password on User 78 | /// Update password for an existing user. 79 | /// 80 | /// * Path: /auth/{self.mount}/users/{self.username}/password 81 | /// * Method: POST 82 | /// * Response: N/A 83 | /// * Reference: 84 | #[derive(Builder, Debug, Default, Endpoint)] 85 | #[endpoint( 86 | path = "/auth/{self.mount}/users/{self.username}/password", 87 | method = "POST", 88 | builder = "true" 89 | )] 90 | #[builder(setter(into, strip_option), default)] 91 | pub struct UpdatePasswordRequest { 92 | #[endpoint(skip)] 93 | pub mount: String, 94 | #[endpoint(skip)] 95 | pub username: String, 96 | pub password: String, 97 | } 98 | 99 | /// ## Update Policies on User 100 | /// Update policies for an existing user. 101 | /// 102 | /// * Path: /auth/{self.mount}/users/{self.username}/policies 103 | /// * Method: POST 104 | /// * Response: N/A 105 | /// * Reference: 106 | #[derive(Builder, Debug, Default, Endpoint)] 107 | #[endpoint( 108 | path = "/auth/{self.mount}/users/{self.username}/policies", 109 | method = "POST", 110 | builder = "true" 111 | )] 112 | #[builder(setter(into, strip_option), default)] 113 | pub struct UpdatePoliciesRequest { 114 | #[endpoint(skip)] 115 | pub mount: String, 116 | #[endpoint(skip)] 117 | pub username: String, 118 | pub policies: String, 119 | } 120 | 121 | /// ## List Users 122 | /// List available userpass users. 123 | /// 124 | /// * Path: /auth/{self.mount}/users 125 | /// * Method: LIST 126 | /// * Response: [ListUsersResponse] 127 | /// * Reference: 128 | #[derive(Builder, Debug, Default, Endpoint)] 129 | #[endpoint( 130 | path = "/auth/{self.mount}/users", 131 | method = "LIST", 132 | response = "ListUsersResponse", 133 | builder = "true" 134 | )] 135 | #[builder(setter(into, strip_option), default)] 136 | pub struct ListUsersRequest { 137 | #[endpoint(skip)] 138 | pub mount: String, 139 | } 140 | 141 | /// ## Login 142 | /// Login with the username and password. 143 | /// 144 | /// * Path: /auth/{self.mount}/login/{self.username} 145 | /// * Method: POST 146 | /// * Response: N/A 147 | /// * Reference: 148 | #[derive(Builder, Debug, Default, Endpoint)] 149 | #[endpoint( 150 | path = "/auth/{self.mount}/login/{self.username}", 151 | method = "POST", 152 | builder = "true" 153 | )] 154 | #[builder(setter(into, strip_option), default)] 155 | pub struct LoginRequest { 156 | #[endpoint(skip)] 157 | pub mount: String, 158 | #[endpoint(skip)] 159 | pub username: String, 160 | pub password: String, 161 | } 162 | -------------------------------------------------------------------------------- /src/api/auth/kubernetes/requests.rs: -------------------------------------------------------------------------------- 1 | use super::responses::{ 2 | ListRolesResponse, ReadKubernetesAuthConfigResponse, ReadKubernetesRoleResponse, 3 | }; 4 | use rustify_derive::Endpoint; 5 | 6 | /// ## Configure Kubernetes Auth 7 | /// Sets backend configuration for the Kubernetes auth mount 8 | /// 9 | /// * Path: /auth/kubernetes/config 10 | /// * Method: POST 11 | /// * Response: N/A 12 | /// * Reference: 13 | #[derive(Builder, Debug, Default, Endpoint)] 14 | #[endpoint(path = "/auth/{self.mount}/config", method = "POST", builder = "true")] 15 | #[builder(setter(into, strip_option), default)] 16 | pub struct ConfigureKubernetesAuthRequest { 17 | #[endpoint(skip)] 18 | pub mount: String, 19 | pub kubernetes_host: String, 20 | pub kubernetes_ca_cert: Option, 21 | pub pem_keys: Option>, 22 | pub issuer: Option, 23 | pub disable_iss_validation: bool, 24 | pub disable_local_ca_jwt: bool, 25 | } 26 | 27 | /// ## Read Kubernetes Auth Config 28 | /// Gets backend configuration for the Kubernetes auth mount 29 | /// 30 | /// * Path: /auth/kubernetes/config 31 | /// * Method: GET 32 | /// * Response: [ReadKubernetesAuthConfigResponse] 33 | /// * Reference: 34 | #[derive(Builder, Debug, Default, Endpoint)] 35 | #[endpoint( 36 | path = "/auth/{self.mount}/config", 37 | method = "GET", 38 | response = "ReadKubernetesAuthConfigResponse", 39 | builder = "true" 40 | )] 41 | #[builder(setter(into))] 42 | pub struct ReadKubernetesAuthConfigRequest { 43 | #[endpoint(skip)] 44 | pub mount: String, 45 | } 46 | 47 | /// ## Login with Kubernetes 48 | /// Issues a Vault token based on the presented credentials. 49 | /// 50 | /// * Path: /auth/kubernetes/login 51 | /// * Method: POST 52 | /// * Response: N/A 53 | /// * Reference: 54 | #[derive(Builder, Debug, Endpoint)] 55 | #[endpoint(path = "/auth/{self.mount}/login", method = "POST", builder = "true")] 56 | #[builder(setter(into))] 57 | pub struct LoginWithKubernetesRequest { 58 | #[endpoint(skip)] 59 | pub mount: String, 60 | pub role: String, 61 | pub jwt: String, 62 | } 63 | 64 | /// ## List Roles 65 | /// Returns a list the existing Kubernetes roles. 66 | /// 67 | /// * Path: /auth/{self.mount}/role 68 | /// * Method: LIST 69 | /// * Response: [ListRolesResponse] 70 | /// * Reference: 71 | #[derive(Builder, Debug, Default, Endpoint)] 72 | #[endpoint( 73 | path = "/auth/{self.mount}/role", 74 | method = "LIST", 75 | response = "ListRolesResponse", 76 | builder = "true" 77 | )] 78 | #[builder(setter(into, strip_option), default)] 79 | pub struct ListRolesRequest { 80 | #[endpoint(skip)] 81 | pub mount: String, 82 | } 83 | 84 | /// ## Create Kubernetes role 85 | /// Creates a new Kubernetes Role. 86 | /// 87 | /// * Path: /auth/{self.mount}/role/{self.name} 88 | /// * Method: POST 89 | /// * Response: N/A 90 | /// * Reference: 91 | #[derive(Builder, Debug, Default, Endpoint)] 92 | #[endpoint( 93 | path = "/auth/{self.mount}/role/{self.name}", 94 | method = "POST", 95 | builder = "true" 96 | )] 97 | #[builder(setter(into, strip_option), default)] 98 | pub struct CreateKubernetesRoleRequest { 99 | #[endpoint(skip)] 100 | pub mount: String, 101 | #[endpoint(skip)] 102 | pub name: String, 103 | pub bound_service_account_names: Vec, 104 | pub bound_service_account_namespaces: Vec, 105 | pub audience: Option, 106 | pub token_ttl: Option, 107 | pub token_max_ttl: Option, 108 | pub token_policies: Option>, 109 | pub token_bound_cidrs: Option>, 110 | pub token_explicit_max_ttl: Option, 111 | pub token_no_default_policy: Option, 112 | pub token_num_uses: Option, 113 | pub token_period: Option, 114 | pub token_type: Option, 115 | } 116 | 117 | /// ## Read AppRole 118 | /// Reads the properties of an existing Kubernetes role. 119 | /// 120 | /// * Path: /auth/{self.mount}/role/{self.name} 121 | /// * Method: GET 122 | /// * Response: [ReadKubernetesRoleResponse] 123 | /// * Reference: 124 | #[derive(Builder, Debug, Default, Endpoint)] 125 | #[endpoint( 126 | path = "/auth/{self.mount}/role/{self.name}", 127 | response = "ReadKubernetesRoleResponse", 128 | builder = "true" 129 | )] 130 | #[builder(setter(into, strip_option), default)] 131 | pub struct ReadKubernetesRoleRequest { 132 | #[endpoint(skip)] 133 | pub mount: String, 134 | #[endpoint(skip)] 135 | pub name: String, 136 | } 137 | 138 | /// ## Delete AppRole 139 | /// Deletes an existing Kubernetes. 140 | /// 141 | /// * Path: /auth/{self.mount}/role/{self.name} 142 | /// * Method: DELETE 143 | /// * Response: N/A 144 | /// * Reference: 145 | #[derive(Builder, Debug, Default, Endpoint)] 146 | #[endpoint( 147 | path = "/auth/{self.mount}/role/{self.name}", 148 | method = "DELETE", 149 | builder = "true" 150 | )] 151 | #[builder(setter(into, strip_option), default)] 152 | pub struct DeleteKubernetesRoleRequest { 153 | #[endpoint(skip)] 154 | pub mount: String, 155 | #[endpoint(skip)] 156 | pub name: String, 157 | } 158 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/kubernetes.rs: -------------------------------------------------------------------------------- 1 | use tracing::debug; 2 | use vaultrs::api::auth::kubernetes::requests::ConfigureKubernetesAuthRequest; 3 | use vaultrs::client::Client; 4 | use vaultrs::error::ClientError; 5 | use vaultrs::sys::auth; 6 | 7 | use crate::common::{Test, KUB_ACCOUNT_NAME}; 8 | 9 | #[tokio::test] 10 | async fn test() { 11 | let test = Test::builder().with_nginx().await; 12 | let client = test.client(); 13 | let nginx_server_addr = test.nginx_url().unwrap(); 14 | let endpoint = setup(client, nginx_server_addr).await.unwrap(); 15 | 16 | // Test pre-configure auth backend 17 | test_configure(client, &endpoint).await; 18 | test_read_config(client, &endpoint).await; 19 | 20 | // Test roles 21 | role::test_create(client, &endpoint).await; 22 | role::test_read(client, &endpoint).await; 23 | role::test_list(client, &endpoint).await; 24 | 25 | // That's the only test failing 26 | test_login(client, &endpoint).await; 27 | 28 | role::test_delete(client, &endpoint).await; 29 | } 30 | 31 | pub async fn test_configure(client: &impl Client, endpoint: &KubernetesRoleEndpoint) { 32 | vaultrs::auth::kubernetes::configure( 33 | client, 34 | &endpoint.path, 35 | &endpoint.kubernetes_host, 36 | Some( 37 | &mut ConfigureKubernetesAuthRequest::builder() 38 | .kubernetes_host(format!("http://{}", &endpoint.kubernetes_host)) 39 | .kubernetes_ca_cert(include_str!("../files/kubernetes/ca.crt")), // .issuer(&endpoint.jtw_issuer), 40 | ), 41 | ) 42 | .await 43 | .unwrap(); 44 | } 45 | 46 | pub async fn test_read_config(client: &impl Client, endpoint: &KubernetesRoleEndpoint) { 47 | vaultrs::auth::kubernetes::read_config(client, endpoint.path.as_str()) 48 | .await 49 | .unwrap(); 50 | } 51 | 52 | pub async fn test_login(client: &impl Client, endpoint: &KubernetesRoleEndpoint) { 53 | // We use the same test vector than official vault client: 54 | vaultrs::auth::kubernetes::login(client, &endpoint.path, &endpoint.role_name, "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InZhdWx0LWF1dGgtdG9rZW4tdDVwY24iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoidmF1bHQtYXV0aCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImQ3N2Y4OWJjLTkwNTUtMTFlNy1hMDY4LTA4MDAyNzZkOTliZiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnZhdWx0LWF1dGgifQ.HKUcqgrvan5ZC_mnpaMEx4RW3KrhfyH_u8G_IA2vUfkLK8tH3T7fJuJaPr7W6K_BqCrbeM5y3owszOzb4NR0Lvw6GBt2cFcen2x1Ua4Wokr0bJjTT7xQOIOw7UvUDyVS17wAurlfUnmWMwMMMOebpqj5K1t6GnyqghH1wPdHYRGX-q5a6C323dBCgM5t6JY_zTTaBgM6EkFq0poBaifmSMiJRPrdUN_-IgyK8fgQRiFYYkgS6DMIU4k4nUOb_sUFf5xb8vMs3SMteKiuWFAIt4iszXTj5IyBUNqe0cXA3zSY3QiNCV6bJ2CWW0Qf9WDtniT79VAqcR4GYaTC_gxjNA") 55 | .await 56 | .unwrap(); 57 | } 58 | 59 | mod role { 60 | use crate::common::{KUB_ACCOUNT_NAME, KUB_NAMESPACE}; 61 | 62 | use super::{Client, KubernetesRoleEndpoint}; 63 | use vaultrs::api::auth::kubernetes::requests::CreateKubernetesRoleRequest; 64 | 65 | pub async fn test_delete(client: &impl Client, endpoint: &KubernetesRoleEndpoint) { 66 | vaultrs::auth::kubernetes::role::delete( 67 | client, 68 | endpoint.path.as_str(), 69 | endpoint.role_name.as_str(), 70 | ) 71 | .await 72 | .unwrap(); 73 | } 74 | 75 | pub async fn test_list(client: &impl Client, endpoint: &KubernetesRoleEndpoint) { 76 | vaultrs::auth::kubernetes::role::list(client, endpoint.path.as_str()) 77 | .await 78 | .unwrap(); 79 | } 80 | 81 | pub async fn test_read(client: &impl Client, endpoint: &KubernetesRoleEndpoint) { 82 | vaultrs::auth::kubernetes::role::read( 83 | client, 84 | endpoint.path.as_str(), 85 | endpoint.role_name.as_str(), 86 | ) 87 | .await 88 | .unwrap(); 89 | } 90 | 91 | pub async fn test_create(client: &impl Client, endpoint: &KubernetesRoleEndpoint) { 92 | vaultrs::auth::kubernetes::role::create( 93 | client, 94 | &endpoint.path, 95 | &endpoint.role_name, 96 | Some( 97 | &mut CreateKubernetesRoleRequest::builder() 98 | .bound_service_account_namespaces(vec![KUB_NAMESPACE.into()]) 99 | .bound_service_account_names(vec![KUB_ACCOUNT_NAME.into()]) 100 | .token_ttl("10m"), 101 | ), 102 | ) 103 | .await 104 | .unwrap(); 105 | } 106 | } 107 | 108 | #[derive(Clone, Debug)] 109 | pub struct KubernetesRoleEndpoint { 110 | pub path: String, 111 | pub role_name: String, 112 | pub kubernetes_host: String, 113 | } 114 | 115 | async fn setup( 116 | client: &impl Client, 117 | nginx_server_addr: &str, 118 | ) -> Result { 119 | debug!("setting up Kubernetes auth engine"); 120 | let path = "kubernetes_test"; 121 | 122 | // Mount the AppRole auth engine 123 | auth::enable(client, path, "kubernetes", None) 124 | .await 125 | .unwrap(); 126 | 127 | Ok(KubernetesRoleEndpoint { 128 | path: path.into(), 129 | role_name: KUB_ACCOUNT_NAME.into(), 130 | kubernetes_host: nginx_server_addr.into(), 131 | }) 132 | } 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vaultrs 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

17 | 18 | > An asynchronous Rust client library for the [Hashicorp Vault][1] API 19 | 20 | The following features are currently supported: 21 | 22 | - Auth 23 | - [AppRole](https://developer.hashicorp.com/vault/docs/auth/approle) 24 | - [AWS](https://developer.hashicorp.com/vault/docs/auth/aws) 25 | - [JWT/OIDC](https://developer.hashicorp.com/vault/api-docs/auth/jwt) 26 | - [Kubernetes](https://developer.hashicorp.com/vault/docs/auth/kubernetes) 27 | - [Token](https://developer.hashicorp.com/vault/docs/auth/token) 28 | - [Certificate](https://developer.hashicorp.com/vault/docs/auth/cert) 29 | - [Userpass](https://developer.hashicorp.com/vault/docs/auth/userpass) 30 | - Secrets 31 | - [AWS](https://developer.hashicorp.com/vault/docs/secrets/aws) 32 | - [Cubbyhole](https://developer.hashicorp.com/vault/docs/secrets/cubbyhole) 33 | - [Databases](https://developer.hashicorp.com/vault/api-docs/secret/databases) 34 | - [KV v1](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v1) 35 | - [KV v2](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v2) 36 | - [PKI](https://developer.hashicorp.com/vault/docs/secrets/pki) 37 | - [SSH](https://developer.hashicorp.com/vault/docs/secrets/ssh) 38 | - [Transit](https://developer.hashicorp.com/vault/api-docs/secret/transit) 39 | - Sys 40 | - [Health](https://developer.hashicorp.com/vault/api-docs/system/health) 41 | - [Policies](https://developer.hashicorp.com/vault/api-docs/system/policy) 42 | - [Sealing](https://developer.hashicorp.com/vault/api-docs/system/seal) 43 | - [Wrapping](https://developer.hashicorp.com/vault/docs/concepts/response-wrapping) 44 | 45 | See something missing? 46 | [Open an issue](https://github.com/jmgilman/vaultrs/issues/new). 47 | 48 | ## Installation 49 | 50 | First, choose one of the two TLS implementations for `vaultrs`' connection to 51 | Vault: 52 | 53 | - `rustls` (default) to use [Rustls](https://github.com/rustls/rustls) 54 | - `native-tls` to use 55 | [rust-native-tls](https://github.com/sfackler/rust-native-tls), which builds 56 | on your platform-specific TLS implementation. 57 | 58 | Then, add `vaultrs` as a dependency to your cargo.toml: 59 | 60 | 1. To use [Rustls](https://github.com/rustls/rustls), import as follows: 61 | 62 | ```toml 63 | [dependencies] 64 | vaultrs = "0.7.4" 65 | ``` 66 | 67 | 2. To use [rust-native-tls](https://github.com/sfackler/rust-native-tls), which 68 | builds on your platform-specific TLS implementation, specify: 69 | 70 | ```toml 71 | [dependencies] 72 | vaultrs = { version = "0.7.4", default-features = false, features = [ "native-tls" ] } 73 | ``` 74 | 75 | ## Usage 76 | 77 | ### Setup the client 78 | 79 | The client is used to configure the connection to Vault and is required to be 80 | passed to all API calls for execution. Behind the scenes it uses an asynchronous 81 | client from [Reqwest](https://docs.rs/reqwest/) for communicating to Vault. 82 | 83 | ```rust 84 | use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; 85 | 86 | // Create a client 87 | let client = VaultClient::new( 88 | VaultClientSettingsBuilder::default() 89 | .address("https://127.0.0.1:8200") 90 | .token("TOKEN") 91 | .build() 92 | .unwrap() 93 | ).unwrap(); 94 | ``` 95 | 96 | For more usages, take a look at [the documentation][6] 97 | 98 | ## Error Handling and Tracing 99 | 100 | All errors generated by this crate are wrapped in the `ClientError` enum 101 | provided by the crate. API warnings are automatically captured via `tracing` and 102 | API errors are captured and returned as their own variant. Connection related 103 | errors from `rustify` are wrapped and returned as a single variant. 104 | 105 | All top level API operations are instrumented with `tracing`'s `#[instrument]` 106 | attribute. 107 | 108 | ## Testing 109 | 110 | See the the [tests][3] directory for tests. Run tests with `cargo test`. 111 | 112 | **Note**: All tests rely on bringing up a local Vault development server using 113 | Docker. In order to run tests Docker must be running locally (Docker Desktop 114 | works). The first run will be longer than other because it will fetch images. 115 | 116 | Some long-running tests are ignored by default locally. To run them do: 117 | 118 | ```sh 119 | cargo test -- --include-ignored 120 | ``` 121 | 122 | ## Contributing 123 | 124 | Check out the [issues][2] for items needing attention or submit your own and 125 | then: 126 | 127 | 1. Fork the repo () 128 | 2. Create your feature branch (git checkout -b feature/fooBar) 129 | 3. Commit your changes (git commit -am 'Add some fooBar') 130 | 4. Push to the branch (git push origin feature/fooBar) 131 | 5. Create a new Pull Request 132 | 133 | See [CONTRIBUTING][5] for extensive documentation on the 134 | architecture of this library and how to add additional functionality to it. 135 | 136 | [1]: https://developer.hashicorp.com/vault/ 137 | [2]: https://github.com/jmgilman/vaultrs/issues 138 | [3]: https://github.com/jmgilman/vaultrs/tree/master/vaultrs-tests/tests/api_tests 139 | [5]: https://github.com/jmgilman/vaultrs/tree/master/CONTRIBUTING.md 140 | [6]: https://docs.rs/vaultrs 141 | -------------------------------------------------------------------------------- /src/auth/oidc.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | api::{ 3 | self, 4 | auth::oidc::{ 5 | requests::{JWTLoginRequest, OIDCAuthRequest, OIDCCallbackRequest}, 6 | responses::OIDCAuthResponse, 7 | }, 8 | AuthInfo, 9 | }, 10 | client::Client, 11 | error::ClientError, 12 | }; 13 | 14 | /// Obtain an authorization URL from Vault to start an OIDC login flow 15 | /// 16 | /// See [OIDCAuthRequest] 17 | pub async fn auth( 18 | client: &impl Client, 19 | mount: &str, 20 | redirect_uri: &str, 21 | role: Option, 22 | ) -> Result { 23 | let mut endpoint = OIDCAuthRequest::builder(); 24 | if let Some(r) = role { 25 | endpoint.role(r); 26 | } 27 | api::exec_with_result( 28 | client, 29 | endpoint 30 | .mount(mount) 31 | .redirect_uri(redirect_uri) 32 | .build() 33 | .unwrap(), 34 | ) 35 | .await 36 | } 37 | 38 | /// Exchange an authorization code for an OIDC ID Token 39 | /// 40 | /// See [OIDCCallbackRequest] 41 | pub async fn callback( 42 | client: &impl Client, 43 | mount: &str, 44 | state: &str, 45 | nonce: &str, 46 | code: &str, 47 | ) -> Result { 48 | let endpoint = OIDCCallbackRequest::builder() 49 | .mount(mount) 50 | .state(state) 51 | .nonce(nonce) 52 | .code(code) 53 | .build() 54 | .unwrap(); 55 | api::auth(client, endpoint).await 56 | } 57 | 58 | /// Fetch a token using a JWT token 59 | /// 60 | /// See [JWTLoginRequest] 61 | pub async fn login( 62 | client: &impl Client, 63 | mount: &str, 64 | jwt: &str, 65 | role: Option, 66 | ) -> Result { 67 | let mut endpoint = JWTLoginRequest::builder(); 68 | if let Some(r) = role { 69 | endpoint.role(r); 70 | } 71 | api::auth(client, endpoint.mount(mount).jwt(jwt).build().unwrap()).await 72 | } 73 | 74 | pub mod config { 75 | use crate::{ 76 | api::{ 77 | self, 78 | auth::oidc::{ 79 | requests::{ 80 | ReadConfigurationRequest, SetConfigurationRequest, 81 | SetConfigurationRequestBuilder, 82 | }, 83 | responses::ReadConfigurationResponse, 84 | }, 85 | }, 86 | client::Client, 87 | error::ClientError, 88 | }; 89 | 90 | /// Read the configuration of the mounted KV engine 91 | /// 92 | /// See [ReadConfigurationResponse] 93 | pub async fn read( 94 | client: &impl Client, 95 | mount: &str, 96 | ) -> Result { 97 | let endpoint = ReadConfigurationRequest::builder() 98 | .mount(mount) 99 | .build() 100 | .unwrap(); 101 | api::exec_with_result(client, endpoint).await 102 | } 103 | 104 | /// Update the configuration of the mounted KV engine 105 | /// 106 | /// See [SetConfigurationRequest] 107 | pub async fn set( 108 | client: &impl Client, 109 | mount: &str, 110 | opts: Option<&mut SetConfigurationRequestBuilder>, 111 | ) -> Result<(), ClientError> { 112 | let mut t = SetConfigurationRequest::builder(); 113 | let endpoint = opts.unwrap_or(&mut t).mount(mount).build().unwrap(); 114 | api::exec_with_empty(client, endpoint).await 115 | } 116 | } 117 | 118 | pub mod role { 119 | use crate::api; 120 | use crate::api::auth::oidc::{ 121 | requests::{ 122 | DeleteRoleRequest, ListRolesRequest, ReadRoleRequest, SetRoleRequest, 123 | SetRoleRequestBuilder, 124 | }, 125 | responses::{ListRolesResponse, ReadRoleResponse}, 126 | }; 127 | use crate::client::Client; 128 | use crate::error::ClientError; 129 | 130 | /// Deletes a role 131 | /// 132 | /// See [DeleteRoleRequest] 133 | pub async fn delete(client: &impl Client, mount: &str, name: &str) -> Result<(), ClientError> { 134 | let endpoint = DeleteRoleRequest::builder() 135 | .mount(mount) 136 | .name(name) 137 | .build() 138 | .unwrap(); 139 | api::exec_with_empty(client, endpoint).await 140 | } 141 | 142 | /// Lists all roles 143 | /// 144 | /// See [ListRolesRequest] 145 | pub async fn list(client: &impl Client, mount: &str) -> Result { 146 | let endpoint = ListRolesRequest::builder().mount(mount).build().unwrap(); 147 | api::exec_with_result(client, endpoint).await 148 | } 149 | 150 | /// Reads a role 151 | /// 152 | /// See [ReadRoleRequest] 153 | pub async fn read( 154 | client: &impl Client, 155 | mount: &str, 156 | name: &str, 157 | ) -> Result { 158 | let endpoint = ReadRoleRequest::builder() 159 | .mount(mount) 160 | .name(name) 161 | .build() 162 | .unwrap(); 163 | api::exec_with_result(client, endpoint).await 164 | } 165 | 166 | /// Creates or updates a role 167 | /// 168 | /// See [SetRoleRequest] 169 | pub async fn set( 170 | client: &impl Client, 171 | mount: &str, 172 | name: &str, 173 | user_claim: &str, 174 | allowed_redirect_uris: Vec, 175 | opts: Option<&mut SetRoleRequestBuilder>, 176 | ) -> Result<(), ClientError> { 177 | let mut t = SetRoleRequest::builder(); 178 | let endpoint = opts 179 | .unwrap_or(&mut t) 180 | .mount(mount) 181 | .name(name) 182 | .user_claim(user_claim) 183 | .allowed_redirect_uris(allowed_redirect_uris) 184 | .build() 185 | .unwrap(); 186 | api::exec_with_empty(client, endpoint).await 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/api/auth/cert/requests.rs: -------------------------------------------------------------------------------- 1 | use rustify_derive::Endpoint; 2 | 3 | use super::responses::{ListCaCertificateRoleResponse, ReadCaCertificateRoleResponse}; 4 | 5 | /// ## Create/Update CA certificate role 6 | /// Create or update a CA certificate role. 7 | /// 8 | /// * Path: /auth/{self.mount}/certs/{self.name} 9 | /// * Method: POST 10 | /// * Response: N/A 11 | /// * Reference: 12 | #[derive(Builder, Debug, Default, Endpoint)] 13 | #[endpoint( 14 | path = "/auth/{self.mount}/certs/{self.name}", 15 | method = "POST", 16 | builder = "true" 17 | )] 18 | #[builder(setter(into, strip_option), default)] 19 | pub struct CreateCaCertificateRoleRequest { 20 | #[endpoint(skip)] 21 | pub mount: String, 22 | #[endpoint(skip)] 23 | pub name: String, 24 | pub certificate: String, 25 | pub allowed_common_names: Option>, 26 | pub allowed_dns_sans: Option>, 27 | pub allowed_email_sans: Option>, 28 | pub allowed_uri_sans: Option>, 29 | pub allowed_organizational_units: Option>, 30 | pub required_extensions: Option>, 31 | pub allowed_metadata_extensions: Option>, 32 | pub ocsp_enabled: Option, 33 | pub ocsp_ca_certificates: Option, 34 | pub ocsp_servers_override: Option>, 35 | pub ocsp_fail_open: Option, 36 | pub ocsp_query_all_servers: Option, 37 | pub display_name: Option, 38 | pub token_ttl: Option, 39 | pub token_max_ttl: Option, 40 | pub token_policies: Option>, 41 | pub token_bound_cidrs: Option>, 42 | pub token_explicit_max_ttl: Option, 43 | pub token_no_default_policy: Option, 44 | pub token_num_uses: Option, 45 | pub token_period: Option, 46 | pub token_type: Option, 47 | } 48 | 49 | /// ## Read CA certificate role 50 | /// Reads the properties of an existing CA certificate role. 51 | /// 52 | /// * Path: /auth/{self.mount}/certs/{self.name} 53 | /// * Method: GET 54 | /// * Response: [ReadCaCertificateRoleResponse] 55 | /// * Reference: 56 | #[derive(Builder, Debug, Default, Endpoint)] 57 | #[endpoint( 58 | path = "/auth/{self.mount}/certs/{self.name}", 59 | response = "ReadCaCertificateRoleResponse", 60 | builder = "true" 61 | )] 62 | #[builder(setter(into, strip_option), default)] 63 | pub struct ReadCaCertificateRoleRequest { 64 | #[endpoint(skip)] 65 | pub mount: String, 66 | #[endpoint(skip)] 67 | pub name: String, 68 | } 69 | 70 | /// ## Delete CA certificate role 71 | /// This endpoint deletes the CA certificate role. 72 | /// 73 | /// * Path: /auth/{self.mount}/certs/{self.name} 74 | /// * Method: DELETE 75 | /// * Response: N/A 76 | /// * Reference: 77 | #[derive(Builder, Debug, Default, Endpoint)] 78 | #[endpoint( 79 | path = "/auth/{self.mount}/certs/{self.name}", 80 | method = "DELETE", 81 | builder = "true" 82 | )] 83 | #[builder(setter(into, strip_option), default)] 84 | pub struct DeleteCaCertificateRoleRequest { 85 | #[endpoint(skip)] 86 | pub mount: String, 87 | #[endpoint(skip)] 88 | pub name: String, 89 | } 90 | 91 | /// ## List CA certificate role 92 | /// List available CA certificate roles. 93 | /// 94 | /// * Path: /auth/{self.mount}/certs 95 | /// * Method: LIST 96 | /// * Response: [ListCaCertificateRoleResponse] 97 | /// * Reference: 98 | #[derive(Builder, Debug, Default, Endpoint)] 99 | #[endpoint( 100 | path = "/auth/{self.mount}/certs", 101 | method = "LIST", 102 | response = "ListCaCertificateRoleResponse", 103 | builder = "true" 104 | )] 105 | #[builder(setter(into, strip_option), default)] 106 | pub struct ListCaCertificateRoleRequest { 107 | #[endpoint(skip)] 108 | pub mount: String, 109 | } 110 | 111 | /// ## Configure TLS certificate method 112 | /// Configuration options for the method. 113 | /// 114 | /// * Path: /auth/{self.mount}/config 115 | /// * Method: POST 116 | /// * Response: N/A 117 | /// * Reference: 118 | #[derive(Builder, Debug, Default, Endpoint)] 119 | #[endpoint(path = "/auth/{self.mount}/config", method = "POST", builder = "true")] 120 | #[builder(setter(into, strip_option), default)] 121 | pub struct ConfigureTlsCertificateMethod { 122 | #[endpoint(skip)] 123 | pub mount: String, 124 | /// If set, during renewal, skips the matching of presented client identity with the client identity used during login. 125 | disable_binding: Option, 126 | /// If set, metadata of the certificate including the metadata corresponding to allowed_metadata_extensions will be stored in the alias. 127 | enable_identity_alias_metadata: Option, 128 | /// The size of the OCSP response LRU cache. Note that this cache is used for all configured certificates. 129 | ocsp_cache_size: Option, 130 | /// The size of the role cache. Use -1 to disable role caching. 131 | role_cache_size: Option, 132 | } 133 | 134 | /// ## Login 135 | /// Login with the TLS certificate method and authenticate against only the named 136 | /// certificate role. 137 | /// 138 | /// * Path: /auth/{self.mount}/login 139 | /// * Method: POST 140 | /// * Response: N/A 141 | /// * Reference: 142 | #[derive(Builder, Debug, Default, Endpoint)] 143 | #[endpoint(path = "/auth/{self.mount}/login", method = "POST", builder = "true")] 144 | #[builder(setter(into, strip_option), default)] 145 | pub struct LoginRequest { 146 | #[endpoint(skip)] 147 | pub mount: String, 148 | pub cert_name: String, 149 | } 150 | -------------------------------------------------------------------------------- /src/api/sys/responses.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Response from executing 6 | /// [ListMountsRequest][crate::api::sys::requests::ListMountsRequest] 7 | #[derive(Deserialize, Debug, Serialize)] 8 | pub struct MountResponse { 9 | pub accessor: String, 10 | pub config: MountConfigResponse, 11 | pub description: String, 12 | pub external_entropy_access: bool, 13 | pub local: bool, 14 | pub options: Option>, 15 | pub seal_wrap: bool, 16 | #[serde(rename = "type")] 17 | pub mount_type: String, 18 | pub uuid: String, 19 | } 20 | 21 | /// Response from executing 22 | /// [ListMountsRequest][crate::api::sys::requests::ListMountsRequest] 23 | #[derive(Deserialize, Debug, Serialize)] 24 | pub struct MountConfigResponse { 25 | pub default_lease_ttl: u64, 26 | pub force_no_cache: bool, 27 | pub max_lease_ttl: u64, 28 | } 29 | 30 | /// Response from executing 31 | /// [GetConfigurationOfTheSecretEngineRequest][crate::api::sys::requests::GetConfigurationOfTheSecretEngineRequest ] 32 | #[derive(Deserialize, Debug, Serialize)] 33 | pub struct GetConfigurationOfTheSecretEngineResponse { 34 | pub accessor: String, 35 | pub config: MountConfigResponse, 36 | pub description: String, 37 | pub external_entropy_access: bool, 38 | pub local: bool, 39 | pub options: Option>, 40 | pub plugin_version: Option, 41 | pub running_plugin_version: Option, 42 | pub running_sha256: Option, 43 | pub seal_wrap: bool, 44 | #[serde(rename = "type")] 45 | pub mount_type: String, 46 | pub uuid: String, 47 | } 48 | 49 | /// Response from executing 50 | /// [ListAuthsRequest][crate::api::sys::requests::ListAuthsRequest] 51 | #[derive(Deserialize, Debug, Serialize)] 52 | pub struct AuthResponse { 53 | pub accessor: String, 54 | pub config: AuthConfigResponse, 55 | pub description: String, 56 | pub external_entropy_access: bool, 57 | pub local: bool, 58 | pub options: Option>, 59 | pub seal_wrap: bool, 60 | #[serde(rename = "type")] 61 | pub mount_type: String, 62 | pub uuid: String, 63 | } 64 | 65 | /// Response from executing 66 | /// [ListAuthsRequest][crate::api::sys::requests::ListAuthsRequest] 67 | #[derive(Deserialize, Debug, Serialize)] 68 | pub struct AuthConfigResponse { 69 | pub default_lease_ttl: u64, 70 | pub force_no_cache: bool, 71 | pub max_lease_ttl: u64, 72 | pub token_type: String, 73 | } 74 | 75 | /// Response from executing 76 | /// [reMountRequest][crate::api::sys::requests::ReMountRequest] 77 | #[derive(Deserialize, Debug, Serialize)] 78 | pub struct RemountResponse { 79 | pub migration_id: String, 80 | } 81 | 82 | /// Response from executing 83 | /// [reMountRequest][crate::api::sys::requests::ReMountRequest] 84 | #[derive(Deserialize, Debug, Serialize)] 85 | pub struct RemountStatusResponse { 86 | pub migration_id: String, 87 | pub migration_info: MigrationInfo, 88 | } 89 | 90 | #[derive(Deserialize, Debug, Serialize)] 91 | pub struct MigrationInfo { 92 | pub source_mount: String, 93 | pub target_mount: String, 94 | pub status: String, 95 | } 96 | 97 | /// Response from executing 98 | /// [WrappingLookupRequest][crate::api::sys::requests::WrappingLookupRequest] 99 | #[derive(Deserialize, Debug, Serialize)] 100 | pub struct WrappingLookupResponse { 101 | pub creation_path: String, 102 | pub creation_time: String, 103 | pub creation_ttl: u64, 104 | } 105 | 106 | /// Response from executing 107 | /// [ReadHealthRequest][crate::api::sys::requests::ReadHealthRequest] 108 | #[derive(Deserialize, Debug, Serialize)] 109 | pub struct ReadHealthResponse { 110 | pub initialized: bool, 111 | pub sealed: bool, 112 | pub standby: bool, 113 | pub performance_standby: bool, 114 | pub replication_performance_mode: String, 115 | pub replication_dr_mode: String, 116 | pub server_time_utc: i64, 117 | pub version: String, 118 | pub cluster_name: Option, 119 | pub cluster_id: Option, 120 | pub last_wal: Option, 121 | /// Returned only by vault >= 1.16 122 | pub enterprise: Option, 123 | /// Returned only by vault >= 1.16 124 | pub echo_duration_ms: Option, 125 | /// Returned only by vault >= 1.16 126 | pub clock_skew_ms: Option, 127 | pub replication_primary_canary_age_ms: Option, 128 | pub removed_from_cluster: Option, 129 | pub ha_connection_healthy: Option, 130 | pub last_request_forwarding_heartbeat_ms: Option, 131 | } 132 | 133 | /// Response from executing 134 | /// [StartInitializationRequest][crate::api::sys::requests::StartInitializationRequest] 135 | #[derive(Deserialize, Debug, Serialize)] 136 | pub struct StartInitializationResponse { 137 | pub keys: Vec, 138 | pub keys_base64: Vec, 139 | pub root_token: String, 140 | } 141 | 142 | /// Response from executing 143 | /// [UnsealRequest][crate::api::sys::requests::UnsealRequest] 144 | #[derive(Deserialize, Debug, Serialize)] 145 | pub struct UnsealResponse { 146 | pub sealed: bool, 147 | #[serde(rename = "t")] 148 | pub threshold: u64, 149 | #[serde(rename = "n")] 150 | pub n_shares: u64, 151 | pub progress: u64, 152 | pub version: String, 153 | pub cluster_name: Option, 154 | pub cluster_id: Option, 155 | } 156 | 157 | /// Response from executing 158 | /// [ListPoliciesRequest][crate::api::sys::requests::ListPoliciesRequest] 159 | #[derive(Deserialize, Debug, Serialize)] 160 | pub struct ListPoliciesResponse { 161 | pub policies: Vec, 162 | } 163 | 164 | /// Response from executing 165 | /// [ReadPolicyRequest][crate::api::sys::requests::ReadPolicyRequest] 166 | #[derive(Deserialize, Debug, Serialize)] 167 | pub struct ReadPolicyResponse { 168 | pub name: String, 169 | pub rules: String, 170 | } 171 | 172 | /// Response from executing 173 | /// [RandomRequest][crate::api::sys::requests::RandomRequest] 174 | #[derive(Deserialize, Debug, Serialize)] 175 | pub struct RandomResponse { 176 | pub random_bytes: String, 177 | } 178 | -------------------------------------------------------------------------------- /vaultrs-tests/tests/api_tests/ssh.rs: -------------------------------------------------------------------------------- 1 | use tracing::debug; 2 | use vaultrs::api::ssh::requests::SetRoleRequest; 3 | use vaultrs::client::Client; 4 | use vaultrs::sys::mount; 5 | 6 | use crate::common::Test; 7 | 8 | #[tokio::test] 9 | async fn test() { 10 | let test = Test::builder().await; 11 | let client = test.client(); 12 | let endpoint = setup(client).await; 13 | 14 | // Test roles 15 | role::test_set(client, &endpoint).await; 16 | role::test_read(client, &endpoint).await; 17 | role::test_list(client, &endpoint).await; 18 | 19 | // Test zero addresses 20 | zero::test_set(client, &endpoint).await; 21 | zero::test_list(client, &endpoint).await; 22 | zero::test_delete(client, &endpoint).await; 23 | 24 | // Test CA 25 | ca::test_submit(client, &endpoint).await; 26 | ca::test_read(client, &endpoint).await; 27 | ca::test_delete(client, &endpoint).await; 28 | ca::test_generate(client, &endpoint).await; 29 | ca::test_sign(client, &endpoint).await; 30 | 31 | // Test generate 32 | let key = test_generate_otp(client, &endpoint).await; 33 | test_verify_otp(client, &endpoint, key).await; 34 | 35 | role::test_delete(client, &endpoint).await; 36 | } 37 | 38 | pub async fn test_generate_otp(client: &impl Client, endpoint: &SSHEndpoint) -> String { 39 | vaultrs::ssh::generate( 40 | client, 41 | endpoint.path.as_str(), 42 | endpoint.otp_role.as_str(), 43 | "192.168.1.1", 44 | Some("admin".to_string()), 45 | ) 46 | .await 47 | .unwrap() 48 | .key 49 | } 50 | 51 | pub async fn test_verify_otp(client: &impl Client, endpoint: &SSHEndpoint, otp: String) { 52 | vaultrs::ssh::verify_otp(client, endpoint.path.as_str(), otp.as_str()) 53 | .await 54 | .unwrap(); 55 | } 56 | 57 | pub mod ca { 58 | use super::{Client, SSHEndpoint}; 59 | use std::fs; 60 | use vaultrs::ssh::ca; 61 | 62 | pub async fn test_delete(client: &impl Client, endpoint: &SSHEndpoint) { 63 | ca::delete(client, endpoint.path.as_str()).await.unwrap(); 64 | } 65 | 66 | pub async fn test_generate(client: &impl Client, endpoint: &SSHEndpoint) { 67 | ca::generate(client, endpoint.path.as_str()).await.unwrap(); 68 | } 69 | 70 | pub async fn test_read(client: &impl Client, endpoint: &SSHEndpoint) { 71 | ca::read(client, endpoint.path.as_str()).await.unwrap(); 72 | } 73 | 74 | pub async fn test_sign(client: &impl Client, endpoint: &SSHEndpoint) { 75 | let public_key = fs::read_to_string("tests/files/id_rsa.pub").unwrap(); 76 | ca::sign( 77 | client, 78 | endpoint.path.as_str(), 79 | endpoint.role.as_str(), 80 | public_key.as_str(), 81 | None, 82 | ) 83 | .await 84 | .unwrap(); 85 | } 86 | 87 | pub async fn test_submit(client: &impl Client, endpoint: &SSHEndpoint) { 88 | let private_key = fs::read_to_string("tests/files/id_rsa").unwrap(); 89 | let public_key = fs::read_to_string("tests/files/id_rsa.pub").unwrap(); 90 | ca::set( 91 | client, 92 | endpoint.path.as_str(), 93 | private_key.as_str(), 94 | public_key.as_str(), 95 | ) 96 | .await 97 | .unwrap(); 98 | } 99 | } 100 | 101 | mod role { 102 | use super::{Client, SSHEndpoint}; 103 | use vaultrs::{api::ssh::requests::SetRoleRequest, ssh::role}; 104 | 105 | pub async fn test_delete(client: &impl Client, endpoint: &SSHEndpoint) { 106 | role::delete(client, endpoint.path.as_str(), endpoint.role.as_str()) 107 | .await 108 | .unwrap(); 109 | } 110 | 111 | pub async fn test_list(client: &impl Client, endpoint: &SSHEndpoint) { 112 | role::list(client, endpoint.path.as_str()).await.unwrap(); 113 | } 114 | 115 | pub async fn test_read(client: &impl Client, endpoint: &SSHEndpoint) { 116 | role::read(client, endpoint.path.as_str(), endpoint.role.as_str()) 117 | .await 118 | .unwrap(); 119 | } 120 | 121 | pub async fn test_set(client: &impl Client, endpoint: &SSHEndpoint) { 122 | role::set( 123 | client, 124 | endpoint.path.as_str(), 125 | endpoint.role.as_str(), 126 | Some( 127 | &mut SetRoleRequest::builder() 128 | .key_type("ca") 129 | .allowed_users("*") 130 | .allow_user_certificates(true), 131 | ), 132 | ) 133 | .await 134 | .unwrap(); 135 | } 136 | } 137 | 138 | pub mod zero { 139 | use super::{Client, SSHEndpoint}; 140 | use vaultrs::ssh::zero; 141 | 142 | pub async fn test_set(client: &impl Client, endpoint: &SSHEndpoint) { 143 | zero::set(client, endpoint.path.as_str(), vec![endpoint.role.clone()]) 144 | .await 145 | .unwrap(); 146 | } 147 | 148 | pub async fn test_list(client: &impl Client, endpoint: &SSHEndpoint) { 149 | zero::list(client, endpoint.path.as_str()).await.unwrap(); 150 | } 151 | 152 | pub async fn test_delete(client: &impl Client, endpoint: &SSHEndpoint) { 153 | zero::delete(client, endpoint.path.as_str()).await.unwrap(); 154 | } 155 | } 156 | 157 | #[derive(Debug)] 158 | pub struct SSHEndpoint { 159 | pub path: String, 160 | pub role: String, 161 | pub otp_role: String, 162 | } 163 | 164 | async fn setup(client: &impl Client) -> SSHEndpoint { 165 | debug!("setting up SSH auth engine"); 166 | 167 | let path = "ssh_test"; 168 | let role = "test"; 169 | let otp_role = "test_otp"; 170 | 171 | // Mount the SSH auth engine 172 | mount::enable(client, path, "ssh", None).await.unwrap(); 173 | 174 | // Create OTP role 175 | vaultrs::ssh::role::set( 176 | client, 177 | path, 178 | otp_role, 179 | Some( 180 | &mut SetRoleRequest::builder() 181 | .key_type("otp") 182 | .default_user("admin") 183 | .cidr_list("192.168.0.0/16"), 184 | ), 185 | ) 186 | .await 187 | .unwrap(); 188 | 189 | SSHEndpoint { 190 | path: path.to_string(), 191 | role: role.to_string(), 192 | otp_role: otp_role.to_string(), 193 | } 194 | } 195 | --------------------------------------------------------------------------------