diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16375d0..1e70589 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,125 +16,48 @@ env: TOOLCHAIN_PROFILE: minimal jobs: - lint-vaultrs: - name: Run cargo fmt and cargo clippy for vaultrs + fmt: runs-on: ubuntu-latest + name: stable / fmt steps: - - name: Checkout sources - uses: actions/checkout@v2 - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: ${{ env.TOOLCHAIN_PROFILE }} - toolchain: ${{ env.RUST_TOOLCHAIN }} - override: true - components: rustfmt, clippy - - name: Use cache - uses: Swatinem/rust-cache@v1 - - name: Run cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - name: Run cargo clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: -- -D warnings - lint-vaultrs-login: - name: Run cargo fmt and cargo clippy for vaultrs-login - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: ${{ env.TOOLCHAIN_PROFILE }} - toolchain: ${{ env.RUST_TOOLCHAIN }} - override: true - components: rustfmt, clippy - - name: Use cache - uses: Swatinem/rust-cache@v1 - with: - working-directory: vaultrs-login/ - - name: Run cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all --package vaultr-login -- --check - - name: Run cargo clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --package vaultrs-login -- -D warnings - test-vaultrs: - name: Run cargo test for vaultrs + - uses: actions/checkout@v4 + - name: Install stable + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: cargo fmt + run: cargo fmt --check + clippy: runs-on: ubuntu-latest + name: stable / clippy steps: - - name: Checkout sources - uses: actions/checkout@v2 - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: ${{ env.TOOLCHAIN_PROFILE }} - toolchain: ${{ env.RUST_TOOLCHAIN }} - override: true - - name: Use cache - uses: Swatinem/rust-cache@v1 - - name: Run cargo test with -no-run (compile tests) - uses: actions-rs/cargo@v1 - with: - command: test - args: --no-run - - name: Run cargo test --all-features - uses: actions-rs/cargo@v1 - env: - RUST_TEST_THREADS: 1 - with: - command: test - args: --all-features - - name: Run cargo test - uses: actions-rs/cargo@v1 - env: - RUST_TEST_THREADS: 1 - with: - command: test - test-vaultrs-login: - name: Run cargo test for vaultrs-login + - uses: actions/checkout@v4 + - name: Install stable + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: cargo clippy + run: cargo clippy --all-targets --all-features --workspace -- -D warnings + test: runs-on: ubuntu-latest + name: stable / test steps: - - name: Checkout sources - uses: actions/checkout@v2 - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: ${{ env.TOOLCHAIN_PROFILE }} - toolchain: ${{ env.RUST_TOOLCHAIN }} - override: true - - name: Use cache - uses: Swatinem/rust-cache@v1 - with: - working-directory: vaultrs-login/ - - name: Run cargo test with -no-run (compile tests) - uses: actions-rs/cargo@v1 - with: - command: test - args: --package vaultrs-login --all-features --no-run - - name: Run cargo test - uses: actions-rs/cargo@v1 - env: - RUST_TEST_THREADS: 1 - with: - command: test - args: --package vaultrs-login --all-features + - uses: actions/checkout@v4 + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: cargo test + run: cargo test --all-targets --all-features --workspace + # https://github.com/rust-lang/cargo/issues/6669 + - name: cargo test --doc + run: cargo test --all-features --workspace --doc + publish: name: Publish to crates.io runs-on: ubuntu-latest if: startsWith(github.event.ref, 'refs/tags/v') - needs: [lint-vaultrs, lint-vaultrs-login, test-vaultrs, test-vaultrs-login] + needs: [fmt, clippy, test] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: ${{ env.TOOLCHAIN_PROFILE }} diff --git a/Cargo.toml b/Cargo.toml index 6a2644a..9e7ddbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,15 +7,17 @@ license = "MIT" readme = "README.md" repository = "https://github.com/jmgilman/vaultrs" keywords = ["Vault", "API", "Client", "Hashicorp"] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] -members = [ - "vaultrs-login", +members = [ + "vaultrs-login", "vaultrs-tests", "." ] +default-members = ["vaultrs-login", "vaultrs-tests", "."] + [features] default = [ "rustls" ] rustls = [ "reqwest/rustls-tls", "rustify/rustls-tls" ] @@ -36,20 +38,3 @@ thiserror = "1.0.40" url = "2.3.1" tracing = { version = "0.1.37", features = ["log"] } -[dev-dependencies] -base64 = "0.21" -chrono = "0.4.38" -data-encoding = "2.3.3" -tokio-test = "0.4.2" -tracing-subscriber = { version = "0.3.16", default-features = false, features = ["env-filter", "fmt"] } -tracing-test = "0.2.4" -test-log = { version = "0.2.11", features = ["trace"] } -env_logger = "0.10.0" -dockertest = "0.3.0" -dockertest-server = { version = "0.1.7", features = ["hashi", "database", "webserver", "cloud"] } -jwt = "0.16.0" -sha2 = "0.10.6" -hmac = "0.12.1" -serial_test = "1.0.0" -rcgen = "0.12.1" -tempfile = "3.10.1" diff --git a/src/client.rs b/src/client.rs index 7ad88d1..6b56b99 100644 --- a/src/client.rs +++ b/src/client.rs @@ -82,8 +82,12 @@ impl VaultClient { /// Creates a new [VaultClient] using the given [VaultClientSettings]. #[instrument(skip(settings), err)] pub fn new(settings: VaultClientSettings) -> Result { + #[cfg(not(feature = "rustls"))] let mut http_client = reqwest::ClientBuilder::new(); + #[cfg(feature = "rustls")] + let mut http_client = reqwest::ClientBuilder::new().use_rustls_tls(); + // Optionally set timeout on client http_client = if let Some(timeout) = settings.timeout { http_client.timeout(timeout) diff --git a/tests/cert.rs b/tests/cert.rs deleted file mode 100644 index 692ad81..0000000 --- a/tests/cert.rs +++ /dev/null @@ -1,263 +0,0 @@ -#[macro_use] -extern crate tracing; - -mod common; -mod vault_bind_mounts_container; - -use std::collections::HashMap; -use std::error::Error as _; -use std::fs; - -use dockertest_server::Test; -use rcgen::{BasicConstraints, Certificate, CertificateParams, IsCa}; -use tempfile::TempDir; -use test_log::test; -use vault_bind_mounts_container::{VaultServer, VaultServerConfig}; -use vaultrs::api::auth::cert::requests::{ - ConfigureTlsCertificateMethodBuilder, CreateCaCertificateRoleRequestBuilder, -}; -use vaultrs::auth::cert::{self}; -use vaultrs::client::{Client, VaultClient, VaultClientSettingsBuilder}; -use vaultrs::error::ClientError; -use vaultrs::sys::auth; - -use crate::common::{PORT, VERSION}; - -#[test] -fn test() { - let certs = generate_certs(); - let test = new_tls_test(&certs.serialized_cert_dir); - test.run(|instance| async move { - let server: VaultServer = instance.server(); - - let ca_cert_path = certs - .serialized_cert_dir - .path() - .to_path_buf() - .join("ca_cert.pem") - .to_str() - .unwrap() - .to_string(); - let client_cert_str = certs - .client_cert - .serialize_pem_with_signer(&certs.ca_cert) - .unwrap(); - let mut data = client_cert_str.as_bytes().to_vec(); - let mut data2 = certs - .client_cert - .serialize_private_key_pem() - .as_bytes() - .to_vec(); - data.append(&mut data2); - let identity = reqwest::Identity::from_pem(&data).unwrap(); - - let client = match VaultClient::new( - VaultClientSettingsBuilder::default() - .address(format!("https://localhost:{PORT}")) - .token(server.token.clone()) - .ca_certs(vec![ca_cert_path]) - .identity(Some(identity)) - .build() - .unwrap(), - ) { - Ok(c) => c, - Err(err) => { - assert!(err - .source() - .unwrap() - .source() - .unwrap() - .to_string() - .eq("incompatible TLS identity type")); - assert!(cfg!(feature = "native-tls").eq(&true)); - return; - } - }; - let endpoint = setup(&client).await.unwrap(); - - // Test CA cert role - ca_cert_role::test_set(&client, &endpoint, client_cert_str.clone()).await; - ca_cert_role::test_read(&client, &endpoint).await; - ca_cert_role::test_list(&client, &endpoint).await; - - // Test login - test_login(&client, &endpoint).await; - - test_configure(&client, &endpoint).await; - - // Test delete - ca_cert_role::test_delete(&client, &endpoint).await; - }); -} - -pub async fn test_login(client: &impl Client, endpoint: &CertEndpoint) { - let res = cert::login(client, endpoint.path.as_str(), endpoint.name.as_str()).await; - assert!(res.is_ok()); -} - -pub async fn test_configure(client: &impl Client, endpoint: &CertEndpoint) { - cert::configure_tls_certificate_method( - client, - endpoint.path.as_str(), - Some( - &mut ConfigureTlsCertificateMethodBuilder::default() - .enable_identity_alias_metadata(true), - ), - ) - .await - .unwrap(); - let login = cert::login(client, endpoint.path.as_str(), endpoint.name.as_str()) - .await - .unwrap(); - let entity = vaultrs::identity::entity::read_by_id(client, &login.entity_id) - .await - .unwrap(); - // FIXME: When we will bump the tested vault to a newer version, we will need to update this assert. - assert!(entity.metadata.is_none()); -} - -pub mod ca_cert_role { - use vaultrs::{auth::cert::ca_cert_role, client::Client}; - - use crate::CertEndpoint; - - pub async fn test_delete(client: &impl Client, endpoint: &CertEndpoint) { - let res = - ca_cert_role::delete(client, endpoint.path.as_str(), endpoint.name.as_str()).await; - assert!(res.is_ok()); - } - - pub async fn test_list(client: &impl Client, endpoint: &CertEndpoint) { - let res = ca_cert_role::list(client, endpoint.path.as_str()).await; - assert!(res.is_ok()); - } - - pub async fn test_read(client: &impl Client, endpoint: &CertEndpoint) { - let res = ca_cert_role::read(client, endpoint.path.as_str(), endpoint.name.as_str()).await; - assert!(res.is_ok()); - } - - pub async fn test_set(client: &impl Client, endpoint: &CertEndpoint, certificate: String) { - let res = ca_cert_role::set( - client, - endpoint.path.as_str(), - endpoint.name.as_str(), - certificate.as_str(), - None, - ) - .await; - assert!(res.is_ok()); - } -} - -#[derive(Debug)] -pub struct CertEndpoint { - pub path: String, - pub name: String, -} - -async fn setup(client: &impl Client) -> Result { - debug!("setting up cert auth engine"); - - let path = "cert_test"; - let name = "test"; - - // Mount the cert auth engine - auth::enable(client, path, "cert", None).await?; - - Ok(CertEndpoint { - path: path.to_string(), - name: name.to_string(), - }) -} - -struct Certificates { - ca_cert: Certificate, - client_cert: Certificate, - serialized_cert_dir: TempDir, -} - -fn generate_certs() -> Certificates { - let mut ca_cert_params = CertificateParams::new([]); - ca_cert_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(ca_cert_params).unwrap(); - - let client_cert_params = CertificateParams::new([]); - let client_cert = Certificate::from_params(client_cert_params).unwrap(); - - let server_cert_params = CertificateParams::new(["localhost".to_string()]); - let server_cert = Certificate::from_params(server_cert_params).unwrap(); - - // We need to serialize the ca and server certs so that we can mount them within the vault container - let serialized_cert_dir = tempfile::tempdir().unwrap(); - - let ca_cert_path = serialized_cert_dir.path().to_path_buf().join("ca_cert.pem"); - fs::write(ca_cert_path, ca_cert.serialize_pem().unwrap()).unwrap(); - - let server_cert_path = serialized_cert_dir - .path() - .to_path_buf() - .join("server_cert.pem"); - fs::write( - server_cert_path, - server_cert.serialize_pem_with_signer(&ca_cert).unwrap(), - ) - .unwrap(); - - let server_key_path = serialized_cert_dir - .path() - .to_path_buf() - .join("server_key.pem"); - fs::write(server_key_path, server_cert.serialize_private_key_pem()).unwrap(); - - Certificates { - ca_cert, - client_cert, - serialized_cert_dir, - } -} - -fn new_tls_test(server_certs_dir: &TempDir) -> Test { - let mut test = Test::default(); - let certs_mount_dir = "/etc/vault/certs".to_string(); - let ca_cert_mount_path = format!("{certs_mount_dir}/ca_cert.pem"); - let server_cert_mount_path = format!("{certs_mount_dir}/server_cert.pem"); - let server_key_mount_path = format!("{certs_mount_dir}/server_key.pem"); - let vault_config = HashMap::from([( - "listener", - vec![HashMap::from([( - "tcp", - HashMap::from([ - ("address", "0.0.0.0:8200".to_string()), // 8200 is hardcoded as internal port in VaultServerConfig::into_composition - ("tls_cert_file", server_cert_mount_path), - ("tls_key_file", server_key_mount_path), - ("tls_client_ca_file", ca_cert_mount_path), - ("tls_min_version", "tls12".to_string()), - ]), - )])], - )]); - - let env = HashMap::from([ - ( - "VAULT_DEV_LISTEN_ADDRESS".to_string(), - "0.0.0.0:9999".to_string(), // Setting 9999 to leave 8200 available for the listener configured in VAULT_LOCAL_CONFIG - ), - ( - "VAULT_LOCAL_CONFIG".to_string(), - serde_json::to_string(&vault_config).unwrap(), - ), - ]); - - let config = VaultServerConfig::builder() - .port(PORT) - .version(VERSION.into()) - .env(env) - .bind_mounts(HashMap::from([( - certs_mount_dir, - server_certs_dir.path().to_str().unwrap().to_string(), - )])) - .build() - .unwrap(); - test.register(config); - test -} diff --git a/tests/common.rs b/tests/common.rs deleted file mode 100644 index 512125c..0000000 --- a/tests/common.rs +++ /dev/null @@ -1,242 +0,0 @@ -use async_trait::async_trait; -pub use dockertest_server::servers::cloud::localstack::LocalStackServerConfig; -pub use dockertest_server::servers::database::postgres::PostgresServerConfig; -pub use dockertest_server::servers::hashi::{VaultServer, VaultServerConfig}; -use dockertest_server::servers::webserver::nginx::{ - ManagedContent, NginxServerConfig, WebserverContent, -}; -use dockertest_server::Test; -use std::collections::HashMap; -use tracing::trace; -use vaultrs::{ - api::sys::requests::{ - EnableAuthDataConfig, EnableAuthRequest, EnableEngineDataConfig, EnableEngineRequest, - }, - client::{Client, VaultClient, VaultClientSettingsBuilder}, - error::ClientError, - sys::{auth, mount}, -}; -//use vaultrs_test::VaultServer; - -pub const PORT: u32 = 8300; -pub const VERSION: &str = "1.10.3"; -pub const NGINX_PORT: u32 = 8888; -pub const NGINX_VERSION: &str = "1.21"; -pub const LOCALSTACK_VERSION: &str = "2.0.2"; - -#[async_trait] -pub trait VaultServerHelper { - /// Mounts a new instance of the requested secret engine at the given path. - async fn mount_secret( - &self, - client: &impl Client, - path: &str, - engine: &str, - ) -> Result<(), ClientError>; - - /// Mounts a new instance of the requested secret engine at the given path - /// using a configuration. - async fn mount_secret_with_config( - &self, - client: &impl Client, - path: &str, - engine: &str, - config: EnableEngineDataConfig, - ) -> Result<(), ClientError>; - - /// Mounts a new instance of the requested auth engine at the given path. - async fn mount_auth( - &self, - client: &impl Client, - path: &str, - engine: &str, - ) -> Result<(), ClientError>; - - /// Mounts a new instance of the requested auth engine at the given path - /// using a configuration. - async fn mount_auth_with_config( - &self, - client: &impl Client, - path: &str, - engine: &str, - config: EnableAuthDataConfig, - ) -> Result<(), ClientError>; - - fn client(&self) -> VaultClient; -} - -#[async_trait] -impl VaultServerHelper for VaultServer { - /// Mounts a new instance of the requested secret engine at the given path. - async fn mount_secret( - &self, - client: &impl Client, - path: &str, - engine: &str, - ) -> Result<(), ClientError> { - trace!(?path, ?engine, "mounting secret engine"); - mount::enable(client, path, engine, None).await - } - - /// Mounts a new instance of the requested secret engine at the given path - /// using a configuration. - async fn mount_secret_with_config( - &self, - client: &impl Client, - path: &str, - engine: &str, - config: EnableEngineDataConfig, - ) -> Result<(), ClientError> { - trace!(?path, ?engine, ?config, "mounting secret engine"); - mount::enable( - client, - path, - engine, - Some(EnableEngineRequest::builder().config(config)), - ) - .await - } - - /// Mounts a new instance of the requested auth engine at the given path. - async fn mount_auth( - &self, - client: &impl Client, - path: &str, - engine: &str, - ) -> Result<(), ClientError> { - trace!(?path, ?engine, "mounting auth engine"); - auth::enable(client, path, engine, None).await - } - - /// Mounts a new instance of the requested auth engine at the given path - /// using a configuration. - async fn mount_auth_with_config( - &self, - client: &impl Client, - path: &str, - engine: &str, - config: EnableAuthDataConfig, - ) -> Result<(), ClientError> { - trace!(?path, ?engine, ?config, "mounting auth engine"); - auth::enable( - client, - path, - engine, - Some(EnableAuthRequest::builder().config(config)), - ) - .await - } - - fn client(&self) -> VaultClient { - VaultClient::new( - VaultClientSettingsBuilder::default() - .address(self.external_url()) - .token(self.token.clone()) - .build() - .unwrap(), - ) - .unwrap() - } -} - -// Sets up a new test. -#[allow(dead_code)] -pub fn new_test() -> Test { - let mut test = Test::default(); - let config = VaultServerConfig::builder() - .port(PORT) - .version(VERSION.into()) - .build() - .unwrap(); - test.register(config); - test -} - -// Sets up a new database test. -#[allow(dead_code)] -pub fn new_db_test() -> Test { - let mut test = new_test(); - let db_config = PostgresServerConfig::builder().port(6432).build().unwrap(); - test.register(db_config); - test -} - -// Sets up a new AWS test. -#[allow(dead_code)] -pub fn new_aws_test() -> Test { - let mut test = new_test(); - let localstack_config = LocalStackServerConfig::builder() - .env( - vec![(String::from("SERVICES"), String::from("iam,sts"))] - .into_iter() - .collect::>(), - ) - .version(LOCALSTACK_VERSION.to_string()) - .build() - .unwrap(); - test.register(localstack_config); - test -} - -// Sets up a new webserver test. -#[allow(dead_code)] -pub fn new_webserver_test() -> (Test, ManagedContent) { - let mut test = Test::default(); - let vault_config = VaultServerConfig::builder() - .port(PORT) - .version(VERSION.into()) - .build() - .unwrap(); - let (nginx_config, content) = configure_nginx_for_kubernetes_auth(); - - test.register(vault_config); - test.register(nginx_config); - (test, content) -} - -fn configure_nginx_for_kubernetes_auth() -> (NginxServerConfig, ManagedContent) { - let mut nginx_config = NginxServerConfig::builder() - .port(NGINX_PORT) - .version(NGINX_VERSION.into()) - .build() - .unwrap(); - - let mut content = nginx_config - .tls_from_ca_bytes( - include_bytes!("./files/kubernetes/ca.crt"), - include_bytes!("./files/kubernetes/ca.key"), - ) - .unwrap(); - - content.append( - &mut nginx_config - .add_web_content( - WebserverContent::builder() - .name("tokenapi") - .content(kuberneter_mock_token_response().as_bytes().to_vec()) - .content_type("application/json") - .serve_path("/apis/authentication.k8s.io/v1/tokenreviews") - .build() - .unwrap(), - ) - .unwrap(), - ); - - (nginx_config, content) -} - -fn kuberneter_mock_token_response() -> String { - serde_json::json!({ - "apiVersion": "authentication.k8s.io/v1", - "kind": "TokenReview", - "status": { - "authenticated": true, - "user": { - "uid": "testuid", - "username": "system:serviceaccount:testns:test", - }, - "audiences": ["vaultrs-test"] - } - }) - .to_string() -} diff --git a/tests/kv1.rs b/tests/kv1.rs deleted file mode 100644 index 3b4c98f..0000000 --- a/tests/kv1.rs +++ /dev/null @@ -1,102 +0,0 @@ -extern crate tracing; - -mod common; - -use common::{VaultServer, VaultServerHelper}; -use std::collections::HashMap; -use test_log::test; -use vaultrs::kv1; - -use vaultrs::api::kv1::responses::GetSecretResponse; -use vaultrs::error::ClientError; - -#[test] -fn test_kv1() { - let test = common::new_test(); - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let client = server.client(); - - // Mount KV v1 secret engine - let mount = "kv_v1"; - let secret_path = "mysecret/foo"; - server.mount_secret(&client, mount, "kv").await.unwrap(); - - // Create test secrets - let expected_secret = HashMap::from([("key1", "value1"), ("key2", "value2")]); - kv1::set(&client, mount, secret_path, &expected_secret) - .await - .unwrap(); - - // Read it - let read_secret: HashMap = - kv1::get(&client, mount, secret_path).await.unwrap(); - - println!("{:?}", read_secret); - - assert_eq!( - read_secret.get("key1").unwrap(), - expected_secret.get("key1").unwrap() - ); - assert_eq!( - read_secret.get("key2").unwrap(), - expected_secret.get("key2").unwrap() - ); - - // Read it as raw value - let read_secret_raw: GetSecretResponse = - kv1::get_raw(&client, mount, secret_path).await.unwrap(); - - println!("{:?}", read_secret_raw); - - assert_eq!( - read_secret_raw.data.get("key1").unwrap(), - expected_secret.get("key1").unwrap() - ); - assert_eq!( - read_secret_raw.data.get("key2").unwrap(), - expected_secret.get("key2").unwrap() - ); - - // List secret keys - let list_secret = kv1::list(&client, mount, "mysecret").await.unwrap(); - - println!("{:?}", list_secret); - - assert_eq!(list_secret.data.keys, vec!["foo"]); - - // Delete secret and read again and expect 404 to check deletion - kv1::delete(&client, mount, secret_path).await.unwrap(); - - let r = kv1::get_raw(&client, mount, secret_path).await; - - match r.expect_err(&format!( - "Expected error when reading {} after delete.", - &secret_path - )) { - ClientError::APIError { code, .. } => { - assert_eq!(code, 404, "Expected error code 404 for non-existing secret") - } - e => { - panic!("Expected error to be APIError with code 404, got {:?}", e) - } - }; - - let my_secrets = HashMap::from([("key1", "value1"), ("key2", "value2")]); - - kv1::set(&client, mount, "my/secrets", &my_secrets) - .await - .unwrap(); - - let read_secrets: HashMap = - kv1::get(&client, mount, "my/secrets").await.unwrap(); - - println!("{:}", read_secrets.get("key1").unwrap()); // value1 - - let list_secret = kv1::list(&client, mount, "my").await.unwrap(); - - println!("{:?}", list_secret.data.keys); // [ "secrets" ] - - kv1::delete(&client, mount, "my/secrets").await.unwrap(); - }); -} diff --git a/tests/vault_bind_mounts_container.rs b/tests/vault_bind_mounts_container.rs deleted file mode 100644 index 309912c..0000000 --- a/tests/vault_bind_mounts_container.rs +++ /dev/null @@ -1,109 +0,0 @@ -// This file is copied from https://github.com/jmgilman/dockertest-server/blob/master/src/servers/hashi/vault.rs -// because of https://github.com/jmgilman/dockertest-server/pull/15 - -use derive_builder::Builder; -use dockertest::{waitfor, Source}; -use dockertest_server::{common::rand_string, Config, ContainerConfig, Server}; -use std::collections::HashMap; - -const IMAGE: &str = "vault"; -const PORT: u32 = 8200; -const LOG_MSG: &str = "Development mode should NOT be used in production installations!"; -const SOURCE: Source = Source::DockerHub; - -/// Configuration for creating a Hashicorp Vault server. -/// -/// A token with root permissions will automatically be generated using the -/// `token` field. If it's omitted the token will automatically be generated. -/// -/// By default the Vault server listens on port 8200 for HTTP requests. This -/// is exposed on the container by default, but the exposed port can be -/// controlled by setting the `port` field. -/// -/// See the [Dockerhub](https://hub.docker.com/_/vault) page for more -/// information on the arguments and environment variables that can be used to -/// configure the server. -#[derive(Clone, Default, Builder)] -#[builder(default)] -pub struct VaultServerConfig { - #[builder(default = "Vec::new()")] - pub args: Vec, - #[builder(default = "HashMap::new()")] - pub env: HashMap, - #[builder(default = "dockertest_server::server::new_handle(IMAGE)")] - pub handle: String, - #[builder(default = "8200")] - pub port: u32, - #[builder(default = "15")] - pub timeout: u16, - #[builder(default = "rand_string(16)")] - pub token: String, - #[builder(default = "String::from(\"latest\")")] - pub version: String, - #[builder(default = "HashMap::new()")] - pub bind_mounts: HashMap, -} - -impl VaultServerConfig { - pub fn builder() -> VaultServerConfigBuilder { - VaultServerConfigBuilder::default() - } -} - -impl Config for VaultServerConfig { - fn into_composition(self) -> dockertest::Composition { - let ports = vec![(PORT, self.port)]; - let mut env = self.env.clone(); - env.insert(String::from("VAULT_DEV_ROOT_TOKEN_ID"), self.token.clone()); - - let timeout = self.timeout; - let wait = Box::new(waitfor::MessageWait { - message: LOG_MSG.into(), - source: waitfor::MessageSource::Stdout, - timeout, - }); - - ContainerConfig { - args: self.args, - env, - handle: self.handle, - name: IMAGE.into(), - source: SOURCE, - version: self.version, - ports: Some(ports), - wait: Some(wait), - bind_mounts: self.bind_mounts, - } - .into() - } - - fn handle(&self) -> &str { - self.handle.as_str() - } -} - -/// A running instance of a Vault server. -/// -/// The `token` field contains the root Vault token for the server. The server -/// URL which is accessible from the local host can be found in `local_address`. -/// Other running containers which need access to this server should use the -/// `address` field instead. -pub struct VaultServer { - pub external_port: u32, - pub internal_port: u32, - pub ip: String, - pub token: String, -} - -impl Server for VaultServer { - type Config = VaultServerConfig; - - fn new(config: &Self::Config, container: &dockertest::RunningContainer) -> Self { - VaultServer { - external_port: config.port, - internal_port: PORT, - ip: container.ip().to_string(), - token: config.token.clone(), - } - } -} diff --git a/tests/vault_prod_container.rs b/tests/vault_prod_container.rs deleted file mode 100644 index 6a8aefc..0000000 --- a/tests/vault_prod_container.rs +++ /dev/null @@ -1,115 +0,0 @@ -// This file is copied from https://github.com/jmgilman/dockertest-server/blob/master/src/servers/hashi/vault.rs -// because of the LOG_MSG condition is only applicable to vault dev server. - -use derive_builder::Builder; -use dockertest::{waitfor, Source}; -use dockertest_server::{Config, ContainerConfig, Server}; -use std::collections::HashMap; - -const IMAGE: &str = "vault"; -const PORT: u32 = 8300; -const LOG_MSG: &str = "Vault server started!"; -const SOURCE: Source = Source::DockerHub; - -/// Configuration for creating a Hashicorp Vault server. -/// -/// A token with root permissions will automatically be generated using the -/// `token` field. If it's omitted the token will automatically be generated. -/// -/// By default the Vault server listens on port 8200 for HTTP requests. This -/// is exposed on the container by default, but the exposed port can be -/// controlled by setting the `port` field. -/// -/// See the [Dockerhub](https://hub.docker.com/_/vault) page for more -/// information on the arguments and environment variables that can be used to -/// configure the server. -#[derive(Clone, Default, Builder)] -#[builder(default)] -pub struct VaultServerConfig { - #[builder(default = "vec![String::from(\"server\")]")] - pub args: Vec, - #[builder(default = "HashMap::new()")] - pub env: HashMap, - #[builder(default = "dockertest_server::server::new_handle(IMAGE)")] - pub handle: String, - #[builder(default = "8200")] - pub port: u32, - #[builder(default = "15")] - pub timeout: u16, - #[builder(default = "String::from(\"latest\")")] - pub version: String, -} - -impl VaultServerConfig { - pub fn builder() -> VaultServerConfigBuilder { - VaultServerConfigBuilder::default() - } -} - -impl Config for VaultServerConfig { - fn into_composition(self) -> dockertest::Composition { - let ports = vec![(PORT, self.port)]; - - let timeout = self.timeout; - let wait = Box::new(waitfor::MessageWait { - message: LOG_MSG.into(), - source: waitfor::MessageSource::Stdout, - timeout, - }); - - ContainerConfig { - args: self.args, - env: self.env, - handle: self.handle, - name: IMAGE.into(), - source: SOURCE, - version: self.version, - ports: Some(ports), - wait: Some(wait), - bind_mounts: HashMap::new(), - } - .into() - } - - fn handle(&self) -> &str { - self.handle.as_str() - } -} - -/// A running instance of a Vault server. -/// -/// The server URL which is accessible from the local host can be found in `local_address`. -/// Other running containers which need access to this server should use the -/// `address` field instead. -pub struct VaultServer { - pub external_port: u32, - pub internal_port: u32, - pub ip: String, -} - -impl VaultServer { - fn format_address(&self, host: &str, port: u32) -> String { - format!("{}:{}", host, port) - } - - fn format_url(&self, host: &str, port: u32) -> String { - format!("http://{}", self.format_address(host, port)) - } - - /// The external HTTP address - pub fn external_url(&self) -> String { - self.format_url("localhost", self.external_port) - } -} - -impl Server for VaultServer { - type Config = VaultServerConfig; - - fn new(config: &Self::Config, container: &dockertest::RunningContainer) -> Self { - VaultServer { - external_port: config.port, - internal_port: PORT, - ip: container.ip().to_string(), - } - } -} diff --git a/typos.toml b/typos.toml index bb2b054..9eacbbe 100644 --- a/typos.toml +++ b/typos.toml @@ -2,4 +2,4 @@ hashi = "hashi" [files] -extend-exclude = ["tests/files/*"] \ No newline at end of file +extend-exclude = ["vaultrs-tests/tests/files/*"] diff --git a/vaultrs-login/Cargo.toml b/vaultrs-login/Cargo.toml index ac63d4b..5d47f38 100644 --- a/vaultrs-login/Cargo.toml +++ b/vaultrs-login/Cargo.toml @@ -37,4 +37,3 @@ vaultrs = { version = "0.7.3", path = ".." } reqwest = "0.11.15" tokio-test = "0.4.2" tracing-test = "0.2.4" -dockertest-server = { version = "0.1.7", features = ["hashi", "auth", "cloud"] } diff --git a/vaultrs-login/tests/common.rs b/vaultrs-login/tests/common.rs deleted file mode 100644 index 61c2220..0000000 --- a/vaultrs-login/tests/common.rs +++ /dev/null @@ -1,156 +0,0 @@ -use async_trait::async_trait; -pub use dockertest_server::servers::auth::{OIDCServer, OIDCServerConfig}; -pub use dockertest_server::servers::cloud::{LocalStackServer, LocalStackServerConfig}; -pub use dockertest_server::servers::hashi::{VaultServer, VaultServerConfig}; -use dockertest_server::Test; -use vaultrs::{ - api::sys::requests::{ - EnableAuthDataConfig, EnableAuthRequest, EnableEngineDataConfig, EnableEngineRequest, - }, - client::{Client, VaultClient, VaultClientSettingsBuilder}, - error::ClientError, - sys::{auth, mount}, -}; -//use vaultrs_test::VaultServer; - -pub const OIDC_PORT: u32 = 9080; -pub const OIDC_VERSION: &str = "0.3.5"; -pub const VAULT_PORT: u32 = 8300; -pub const VAULT_VERSION: &str = "1.8.2"; -pub const LOCALSTACK_PORT: u32 = 8765; -pub const LOCALSTACK_VERSION: &str = "0.13.1"; - -#[async_trait] -pub trait VaultServerHelper { - /// Mounts a new instance of the requested secret engine at the given path. - async fn mount_secret( - &self, - client: &impl Client, - path: &str, - engine: &str, - ) -> Result<(), ClientError>; - - /// Mounts a new instance of the requested secret engine at the given path - /// using a configuration. - async fn mount_secret_with_config( - &self, - client: &impl Client, - path: &str, - engine: &str, - config: EnableEngineDataConfig, - ) -> Result<(), ClientError>; - - /// Mounts a new instance of the requested auth engine at the given path. - async fn mount_auth( - &self, - client: &impl Client, - path: &str, - engine: &str, - ) -> Result<(), ClientError>; - - /// Mounts a new instance of the requested auth engine at the given path - /// using a configuration. - async fn mount_auth_with_config( - &self, - client: &impl Client, - path: &str, - engine: &str, - config: EnableAuthDataConfig, - ) -> Result<(), ClientError>; - - fn client(&self) -> VaultClient; -} - -#[async_trait] -impl VaultServerHelper for VaultServer { - /// Mounts a new instance of the requested secret engine at the given path. - async fn mount_secret( - &self, - client: &impl Client, - path: &str, - engine: &str, - ) -> Result<(), ClientError> { - mount::enable(client, path, engine, None).await - } - - /// Mounts a new instance of the requested secret engine at the given path - /// using a configuration. - async fn mount_secret_with_config( - &self, - client: &impl Client, - path: &str, - engine: &str, - config: EnableEngineDataConfig, - ) -> Result<(), ClientError> { - mount::enable( - client, - path, - engine, - Some(EnableEngineRequest::builder().config(config)), - ) - .await - } - - /// Mounts a new instance of the requested auth engine at the given path. - async fn mount_auth( - &self, - client: &impl Client, - path: &str, - engine: &str, - ) -> Result<(), ClientError> { - auth::enable(client, path, engine, None).await - } - - /// Mounts a new instance of the requested auth engine at the given path - /// using a configuration. - async fn mount_auth_with_config( - &self, - client: &impl Client, - path: &str, - engine: &str, - config: EnableAuthDataConfig, - ) -> Result<(), ClientError> { - auth::enable( - client, - path, - engine, - Some(EnableAuthRequest::builder().config(config)), - ) - .await - } - - fn client(&self) -> VaultClient { - VaultClient::new( - VaultClientSettingsBuilder::default() - .address(self.external_url()) - .token(self.token.clone()) - .build() - .unwrap(), - ) - .unwrap() - } -} - -// Sets up a new Vault test with OIDC and LocalStack support. -pub fn new_test() -> Test { - let mut test = Test::default(); - let vault_config = VaultServerConfig::builder() - .port(VAULT_PORT) - .version(VAULT_VERSION.into()) - .build() - .unwrap(); - let oidc_config = OIDCServerConfig::builder() - .port(OIDC_PORT) - .version(OIDC_VERSION.into()) - .build() - .unwrap(); - let localstack_config = LocalStackServerConfig::builder() - .port(LOCALSTACK_PORT) - .version(LOCALSTACK_VERSION.into()) - .build() - .unwrap(); - test.register(vault_config); - test.register(oidc_config); - test.register(localstack_config); - test -} diff --git a/vaultrs-tests/Cargo.toml b/vaultrs-tests/Cargo.toml new file mode 100644 index 0000000..5811cbc --- /dev/null +++ b/vaultrs-tests/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "vaultrs-tests" +version = "0.0.0" +edition = "2021" +description = "Integration tests for vaultrs and vaultrs login" +publish = false + +[dev-dependencies] +aws-sdk-iam = { version = "1.13" } +aws-sdk-sts = { version = "1.13" } +aws-credential-types = { version = "1.1.5" } +aws-types = { version = "1.1" } +vaultrs = { path = ".."} +vaultrs-login = { path = "../vaultrs-login", features = ["oidc", "aws"]} +reqwest = { version = "0.12.2", default-features = false } +base64 = "0.21" +chrono = "0.4.38" +serde_json = "1.0.94" +data-encoding = "2.3.3" +tracing-subscriber = { version = "0.3.16", default-features = false, features = ["env-filter", "fmt"] } +test-log = { version = "0.2.11", features = ["trace"] } +env_logger = "0.10.0" +testcontainers = { version = "0.23.1", features = ["http_wait"] } +testcontainers-modules = { version = "0.11.3", features = ["localstack", "postgres"] } +jwt = "0.16.0" +sha2 = "0.10.6" +hmac = "0.12.1" +serial_test = "1.0.0" +rcgen = "0.13" +tempfile = "3.10.1" +tokio = { version = "1.40.0", features = ["full"] } +tracing = "0.1.40" +serde = "1.0.213" diff --git a/tests/approle.rs b/vaultrs-tests/tests/api_tests/approle.rs similarity index 77% rename from tests/approle.rs rename to vaultrs-tests/tests/api_tests/approle.rs index 2ac64d2..ab3e254 100644 --- a/tests/approle.rs +++ b/vaultrs-tests/tests/api_tests/approle.rs @@ -1,43 +1,35 @@ -#[macro_use] -extern crate tracing; - -mod common; - -use common::{VaultServer, VaultServerHelper}; -use test_log::test; +use crate::common::Test; +use tracing::debug; use vaultrs::auth::approle; use vaultrs::client::Client; -use vaultrs::error::ClientError; - -#[test] -fn test() { - let test = common::new_test(); - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let client = server.client(); - let endpoint = setup(&server, &client).await.unwrap(); - - // Test roles - crate::role::test_set(&client, &endpoint).await; - crate::role::test_read(&client, &endpoint).await; - crate::role::test_list(&client, &endpoint).await; - crate::role::test_read_id(&client, &endpoint).await; - crate::role::test_update_id(&client, &endpoint).await; - - // Test secret IDs - let (id, accessor) = crate::role::secret::test_generate(&client, &endpoint).await; - crate::role::secret::test_read(&client, &endpoint, id.as_str()).await; - crate::role::secret::test_read_accessor(&client, &endpoint, accessor.as_str()).await; - crate::role::secret::test_list(&client, &endpoint).await; - crate::role::secret::test_delete_accessor(&client, &endpoint, accessor.as_str()).await; - crate::role::secret::test_custom(&client, &endpoint).await; - crate::role::secret::test_delete(&client, &endpoint, "test").await; - - // Test auth - test_login(&client, &endpoint).await; - - crate::role::test_delete(&client, &endpoint).await; - }) +use vaultrs::sys::auth; + +#[tokio::test] +async fn test() { + let test = Test::builder().await; + let client = test.client(); + let endpoint = setup(client).await; + + // Test roles + role::test_set(client, &endpoint).await; + role::test_read(client, &endpoint).await; + role::test_list(client, &endpoint).await; + role::test_read_id(client, &endpoint).await; + role::test_update_id(client, &endpoint).await; + + // Test secret IDs + let (id, accessor) = role::secret::test_generate(client, &endpoint).await; + role::secret::test_read(client, &endpoint, id.as_str()).await; + role::secret::test_read_accessor(client, &endpoint, accessor.as_str()).await; + role::secret::test_list(client, &endpoint).await; + role::secret::test_delete_accessor(client, &endpoint, accessor.as_str()).await; + role::secret::test_custom(client, &endpoint).await; + role::secret::test_delete(client, &endpoint, "test").await; + + // Test auth + test_login(client, &endpoint).await; + + role::test_delete(client, &endpoint).await; } pub async fn test_login(client: &impl Client, endpoint: &AppRoleEndpoint) { @@ -219,16 +211,15 @@ pub struct AppRoleEndpoint { pub role_name: String, } -async fn setup(server: &VaultServer, client: &impl Client) -> Result { +async fn setup(client: &impl Client) -> AppRoleEndpoint { debug!("setting up AppRole auth engine"); let path = "approle_test"; let role_name = "test"; // Mount the AppRole auth engine - server.mount_auth(client, path, "approle").await?; - - Ok(AppRoleEndpoint { + auth::enable(client, path, "approle", None).await.unwrap(); + AppRoleEndpoint { path: path.to_string(), role_name: role_name.to_string(), - }) + } } diff --git a/tests/aws.rs b/vaultrs-tests/tests/api_tests/aws.rs similarity index 75% rename from tests/aws.rs rename to vaultrs-tests/tests/api_tests/aws.rs index 6241c2d..4fdbd09 100644 --- a/tests/aws.rs +++ b/vaultrs-tests/tests/api_tests/aws.rs @@ -1,133 +1,108 @@ -#[macro_use] -extern crate tracing; - -mod common; - -use common::{VaultServer, VaultServerHelper}; -use dockertest_server::servers::cloud::localstack::LocalStackServer; -use test_log::test; +use crate::common::Test; +use tracing::debug; use vaultrs::client::Client; use vaultrs::error::ClientError; - -#[test] -fn test_auth() { - let test = common::new_aws_test(); - - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let localstack: LocalStackServer = instance.server(); - let client = server.client(); - let endpoint = setup_auth_engine(&server, &client).await.unwrap(); - - crate::config::client::test_set(&localstack, &client, &endpoint).await; - crate::config::client::test_read(&client, &endpoint).await; - crate::config::client::test_delete(&client, &endpoint).await; - - crate::config::identity::test_set(&client, &endpoint).await; - crate::config::identity::test_read(&client, &endpoint).await; - - crate::config::certificate::test_create(&client, &endpoint).await; - crate::config::certificate::test_read(&client, &endpoint).await; - crate::config::certificate::test_list(&client, &endpoint).await; - crate::config::certificate::test_delete(&client, &endpoint).await; - - crate::config::sts::test_create(&client, &endpoint).await; - crate::config::sts::test_read(&client, &endpoint).await; - crate::config::sts::test_list(&client, &endpoint).await; - crate::config::sts::test_delete(&client, &endpoint).await; - - crate::config::tidy::identity_access_list::test_set(&client, &endpoint).await; - crate::config::tidy::identity_access_list::test_read(&client, &endpoint).await; - crate::config::tidy::identity_access_list::test_delete(&client, &endpoint).await; - - crate::config::tidy::role_tag_deny_list::test_set(&client, &endpoint).await; - crate::config::tidy::role_tag_deny_list::test_read(&client, &endpoint).await; - crate::config::tidy::role_tag_deny_list::test_delete(&client, &endpoint).await; - - crate::role::test_create_iam(&client, &endpoint).await; - crate::role::test_create_ec2(&client, &endpoint).await; - crate::role::test_read(&client, &endpoint).await; - crate::role::test_list(&client, &endpoint).await; - - let role_tag = crate::role::test_create_tag(&client, &endpoint).await; - crate::role_tag_deny_list::test_create(&client, &endpoint, &role_tag).await; - crate::role_tag_deny_list::test_read(&client, &endpoint, &role_tag).await; - crate::role_tag_deny_list::test_list(&client, &endpoint, &role_tag).await; - crate::role_tag_deny_list::test_tidy(&client, &endpoint).await; - crate::role_tag_deny_list::test_delete(&client, &endpoint, &role_tag).await; - - // role is needed for role_tag_deny_list operations - crate::role::test_delete(&client, &endpoint).await; - - crate::identity_access_list::test_list(&client, &endpoint).await; - crate::identity_access_list::test_tidy(&client, &endpoint).await; - }); +use vaultrs::sys::{auth, mount}; + +#[tokio::test] +async fn test_auth() { + let test = Test::builder().with_localstack(["iam", "sts"]).await; + let client = test.client(); + let endpoint = setup_auth_engine(client).await.unwrap(); + + config::client::test_set(test.localstack_url().unwrap(), client, &endpoint).await; + config::client::test_read(client, &endpoint).await; + config::client::test_delete(client, &endpoint).await; + + config::identity::test_set(client, &endpoint).await; + config::identity::test_read(client, &endpoint).await; + + config::certificate::test_create(client, &endpoint).await; + config::certificate::test_read(client, &endpoint).await; + config::certificate::test_list(client, &endpoint).await; + config::certificate::test_delete(client, &endpoint).await; + + config::sts::test_create(client, &endpoint).await; + config::sts::test_read(client, &endpoint).await; + config::sts::test_list(client, &endpoint).await; + config::sts::test_delete(client, &endpoint).await; + + config::tidy::identity_access_list::test_set(client, &endpoint).await; + config::tidy::identity_access_list::test_read(client, &endpoint).await; + config::tidy::identity_access_list::test_delete(client, &endpoint).await; + + config::tidy::role_tag_deny_list::test_set(client, &endpoint).await; + config::tidy::role_tag_deny_list::test_read(client, &endpoint).await; + config::tidy::role_tag_deny_list::test_delete(client, &endpoint).await; + + role::test_create_iam(client, &endpoint).await; + role::test_create_ec2(client, &endpoint).await; + role::test_read(client, &endpoint).await; + role::test_list(client, &endpoint).await; + + let role_tag = role::test_create_tag(client, &endpoint).await; + role_tag_deny_list::test_create(client, &endpoint, &role_tag).await; + role_tag_deny_list::test_read(client, &endpoint, &role_tag).await; + role_tag_deny_list::test_list(client, &endpoint, &role_tag).await; + role_tag_deny_list::test_tidy(client, &endpoint).await; + role_tag_deny_list::test_delete(client, &endpoint, &role_tag).await; + + // role is needed for role_tag_deny_list operations + role::test_delete(client, &endpoint).await; + + identity_access_list::test_list(client, &endpoint).await; + identity_access_list::test_tidy(client, &endpoint).await; } -#[test] -fn test_secret_engine() { - let test = common::new_aws_test(); - - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let localstack: LocalStackServer = instance.server(); - let client = server.client(); - let endpoint = setup_secret_engine(&server, &client).await.unwrap(); - - crate::secretengine::config::test_set(&localstack, &client, &endpoint).await; - crate::secretengine::config::test_get(&client, &endpoint).await; - crate::secretengine::config::test_rotate(&client, &endpoint).await; - crate::secretengine::config::test_set_lease(&client, &endpoint).await; - crate::secretengine::config::test_read_lease(&client, &endpoint).await; - - crate::secretengine::roles::test_create_update(&client, &endpoint).await; - crate::secretengine::roles::test_read(&client, &endpoint).await; - crate::secretengine::roles::test_list(&client, &endpoint).await; - crate::secretengine::roles::test_credentials(&client, &endpoint).await; - crate::secretengine::roles::test_credentials_sts(&client, &endpoint).await; - crate::secretengine::roles::test_delete(&client, &endpoint).await; - }); +#[tokio::test] +async fn test_secret_engine() { + let test = Test::builder().with_localstack(["iam", "sts"]).await; + + let client = test.client(); + let endpoint = setup_secret_engine(client).await.unwrap(); + + secretengine::config::test_set(test.localstack_url().unwrap(), client, &endpoint).await; + secretengine::config::test_get(client, &endpoint).await; + secretengine::config::test_rotate(client, &endpoint).await; + secretengine::config::test_set_lease(client, &endpoint).await; + secretengine::config::test_read_lease(client, &endpoint).await; + + secretengine::roles::test_create_update(client, &endpoint).await; + secretengine::roles::test_read(client, &endpoint).await; + secretengine::roles::test_list(client, &endpoint).await; + secretengine::roles::test_credentials(client, &endpoint).await; + secretengine::roles::test_credentials_sts(client, &endpoint).await; + secretengine::roles::test_delete(client, &endpoint).await; } #[derive(Debug)] pub struct AwsAuthEndpoint { pub path: String, - pub role_name: String, } pub struct AwsSecretEngineEndpoint { pub path: String, } -async fn setup_auth_engine( - server: &VaultServer, - client: &impl Client, -) -> Result { +async fn setup_auth_engine(client: &impl Client) -> Result { debug!("setting up AWS auth engine"); let path = "aws_test"; - let role_name = "test"; // Mount the AppRole auth engine - server.mount_auth(client, path, "aws").await?; + auth::enable(client, path, "aws", None).await.unwrap(); // configure aws client Ok(AwsAuthEndpoint { path: path.to_string(), - role_name: role_name.to_string(), }) } -async fn setup_secret_engine( - server: &VaultServer, - client: &impl Client, -) -> Result { +async fn setup_secret_engine(client: &impl Client) -> Result { debug!("setting up AWS secret engine"); let path = "aws_test"; - - server.mount_secret(client, path, "aws").await?; - + mount::enable(client, path, "aws", None).await.unwrap(); Ok(AwsSecretEngineEndpoint { path: path.to_string(), }) @@ -135,17 +110,16 @@ async fn setup_secret_engine( mod config { pub mod client { - use dockertest_server::servers::cloud::localstack::LocalStackServer; use vaultrs::{api::auth::aws::requests::ConfigureClientRequest, auth::aws}; - use crate::{AwsAuthEndpoint, Client}; + use super::super::{AwsAuthEndpoint, Client}; pub async fn test_set( - localstack: &LocalStackServer, + localstack_url: &str, client: &impl Client, endpoint: &AwsAuthEndpoint, ) { - let res = aws::config::client::set( + aws::config::client::set( client, &endpoint.path, Some( @@ -153,14 +127,13 @@ mod config { .access_key("test") .secret_key("test") .sts_region("local") - .endpoint(localstack.internal_url()) - .sts_endpoint(localstack.internal_url()) - .iam_endpoint(localstack.internal_url()), + .endpoint(localstack_url) + .sts_endpoint(localstack_url) + .iam_endpoint(localstack_url), ), ) - .await; - - assert!(res.is_ok()); + .await + .unwrap(); } pub async fn test_read(client: &impl Client, endpoint: &AwsAuthEndpoint) { @@ -178,7 +151,7 @@ mod config { pub mod identity { use vaultrs::{api::auth::aws::requests::ConfigureIdentityRequest, auth::aws}; - use crate::{AwsAuthEndpoint, Client}; + use super::super::{AwsAuthEndpoint, Client}; pub async fn test_set(client: &impl Client, endpoint: &AwsAuthEndpoint) { let res = aws::config::identity::set( @@ -208,10 +181,10 @@ mod config { use base64::{engine::general_purpose, Engine as _}; use vaultrs::auth::aws; - use crate::{AwsAuthEndpoint, Client}; + use super::super::{AwsAuthEndpoint, Client}; const CERT_NAME: &str = "test_cert"; - const CERT: &str = include_str!("files/aws.crt"); + const CERT: &str = include_str!("../files/aws.crt"); pub async fn test_create(client: &impl Client, endpoint: &AwsAuthEndpoint) { let res = aws::config::certificate::create( @@ -246,7 +219,7 @@ mod config { pub mod sts { use vaultrs::auth::aws; - use crate::{AwsAuthEndpoint, Client}; + use super::super::{AwsAuthEndpoint, Client}; const SATELLITE_ACCOUNT_ID: &str = "000000000001"; const ROLE_NAME: &str = "SomeRole"; @@ -283,7 +256,7 @@ mod config { auth::aws, }; - use crate::{AwsAuthEndpoint, Client}; + use super::super::super::{AwsAuthEndpoint, Client}; pub async fn test_set(client: &impl Client, endpoint: &AwsAuthEndpoint) { let res = aws::config::tidy::identity_access_list::set( @@ -322,7 +295,7 @@ mod config { api::auth::aws::requests::ConfigureRoleTagDenyListTidyOperationRequest, auth::aws, }; - use crate::{AwsAuthEndpoint, Client}; + use super::super::super::{AwsAuthEndpoint, Client}; pub async fn test_set(client: &impl Client, endpoint: &AwsAuthEndpoint) { let res = aws::config::tidy::role_tag_deny_list::set( @@ -364,7 +337,7 @@ mod role { auth::aws, }; - use crate::{AwsAuthEndpoint, Client}; + use super::{AwsAuthEndpoint, Client}; const ROLE_NAME_IAM: &str = "test_role_iam"; const ROLE_NAME_EC2: &str = "test_role_ec2"; @@ -451,7 +424,7 @@ mod role { mod identity_access_list { use vaultrs::{api::auth::aws::requests::TidyIdentityAccessListEntriesRequest, auth::aws}; - use crate::{AwsAuthEndpoint, Client, ClientError}; + use super::{AwsAuthEndpoint, Client, ClientError}; pub async fn test_list(client: &impl Client, endpoint: &AwsAuthEndpoint) { let res = aws::identity_access_list::list(client, &endpoint.path).await; @@ -481,7 +454,7 @@ mod role_tag_deny_list { auth::aws, }; - use crate::{AwsAuthEndpoint, Client}; + use super::{AwsAuthEndpoint, Client}; pub async fn test_create( client: &impl Client, @@ -537,13 +510,12 @@ mod role_tag_deny_list { pub mod secretengine { pub mod config { - use dockertest_server::servers::cloud::localstack::LocalStackServer; use vaultrs::{api::aws::requests::SetConfigurationRequest, aws}; - use crate::{AwsSecretEngineEndpoint, Client}; + use super::super::{AwsSecretEngineEndpoint, Client}; pub async fn test_set( - localstack: &LocalStackServer, + localstack_url: &str, client: &impl Client, endpoint: &AwsSecretEngineEndpoint, ) { @@ -556,8 +528,8 @@ pub mod secretengine { SetConfigurationRequest::builder() .max_retries(3) .region("eu-central-1") - .sts_endpoint(localstack.internal_url()) - .iam_endpoint(localstack.internal_url()), + .sts_endpoint(localstack_url) + .iam_endpoint(localstack_url), ), ) .await; @@ -613,7 +585,7 @@ pub mod secretengine { aws, }; - use crate::{AwsSecretEngineEndpoint, Client}; + use super::super::{AwsSecretEngineEndpoint, Client}; pub const TEST_ROLE: &str = "test_role"; pub const TEST_ARN: &str = "arn:aws:iam::123456789012:role/test_role"; @@ -650,23 +622,31 @@ pub mod secretengine { assert!(res.is_ok()); let data = res.unwrap(); + dbg!(&data); assert!(data.keys[0] == TEST_ROLE); assert!(data.keys.len() == 1); } pub async fn test_credentials(client: &impl Client, endpoint: &AwsSecretEngineEndpoint) { - let res = aws::roles::credentials( + let role = aws::roles::read(client, &endpoint.path, TEST_ROLE) + .await + .unwrap(); + dbg!(role); + let data = aws::roles::credentials( client, &endpoint.path, TEST_ROLE, - Some(GenerateCredentialsRequest::builder().ttl("3h")), + Some( + GenerateCredentialsRequest::builder() + .ttl("3h") + .role_arn(TEST_ARN.to_string()), + ), ) - .await; + .await + .unwrap(); + dbg!(&data); - assert!(res.is_ok()); - - let data = res.unwrap(); - assert!(data.access_key.starts_with("ASIA")); + assert!(data.access_key.starts_with("LSIA")); assert!(!data.secret_key.is_empty()); assert!(!data.security_token.unwrap().is_empty()); } @@ -686,7 +666,7 @@ pub mod secretengine { assert!(res.is_ok()); let data = res.unwrap(); - assert!(data.access_key.starts_with("ASIA")); + assert!(data.access_key.starts_with("LSIA")); assert!(!data.secret_key.is_empty()); assert!(!data.security_token.unwrap().is_empty()); } diff --git a/vaultrs-tests/tests/api_tests/cert.rs b/vaultrs-tests/tests/api_tests/cert.rs new file mode 100644 index 0000000..33a7feb --- /dev/null +++ b/vaultrs-tests/tests/api_tests/cert.rs @@ -0,0 +1,115 @@ +use tracing::debug; +use vaultrs::api::auth::cert::requests::ConfigureTlsCertificateMethodBuilder; +use vaultrs::auth::cert::{self}; +use vaultrs::client::Client; +use vaultrs::sys::auth; + +use crate::common::Test; + +#[tokio::test] +async fn test() { + let test = Test::new_tls().await; + let client = test.client(); + let ca_cert = test.ca_cert().unwrap(); + + let endpoint = setup(client).await; + + // Test CA cert role + ca_cert_role::test_set(client, &endpoint, ca_cert).await; + ca_cert_role::test_read(client, &endpoint).await; + ca_cert_role::test_list(client, &endpoint).await; + + // Test login + test_login(client, &endpoint).await; + + test_configure(client, &endpoint).await; + + // Test delete + ca_cert_role::test_delete(client, &endpoint).await; +} + +pub async fn test_login(client: &impl Client, endpoint: &CertEndpoint) { + cert::login(client, endpoint.path.as_str(), endpoint.name.as_str()) + .await + .unwrap(); +} + +pub async fn test_configure(client: &impl Client, endpoint: &CertEndpoint) { + cert::configure_tls_certificate_method( + client, + endpoint.path.as_str(), + Some( + &mut ConfigureTlsCertificateMethodBuilder::default() + .enable_identity_alias_metadata(true), + ), + ) + .await + .unwrap(); + let login = cert::login(client, endpoint.path.as_str(), endpoint.name.as_str()) + .await + .unwrap(); + let entity = vaultrs::identity::entity::read_by_id(client, &login.entity_id) + .await + .unwrap(); + // FIXME: When we will bump the tested vault to a newer version, we will need to update this assert. + assert!(entity.metadata.is_none()); +} + +pub mod ca_cert_role { + use std::{fs, path::Path}; + + use vaultrs::{auth::cert::ca_cert_role, client::Client}; + + use super::CertEndpoint; + + pub async fn test_delete(client: &impl Client, endpoint: &CertEndpoint) { + let res = + ca_cert_role::delete(client, endpoint.path.as_str(), endpoint.name.as_str()).await; + assert!(res.is_ok()); + } + + pub async fn test_list(client: &impl Client, endpoint: &CertEndpoint) { + let res = ca_cert_role::list(client, endpoint.path.as_str()).await; + assert!(res.is_ok()); + } + + pub async fn test_read(client: &impl Client, endpoint: &CertEndpoint) { + let res = ca_cert_role::read(client, endpoint.path.as_str(), endpoint.name.as_str()).await; + assert!(res.is_ok()); + } + + pub async fn test_set(client: &impl Client, endpoint: &CertEndpoint, ca_cert: &Path) { + let client_crt = fs::read_to_string(ca_cert).unwrap(); + + ca_cert_role::set( + client, + endpoint.path.as_str(), + endpoint.name.as_str(), + &client_crt, + None, + ) + .await + .unwrap(); + } +} + +#[derive(Debug)] +pub struct CertEndpoint { + pub path: String, + pub name: String, +} + +async fn setup(client: &impl Client) -> CertEndpoint { + debug!("setting up cert auth engine"); + + let path = "cert_test"; + let name = "test"; + + // Mount the cert auth engine + auth::enable(client, path, "cert", None).await.unwrap(); + + CertEndpoint { + path: path.to_string(), + name: name.to_string(), + } +} diff --git a/tests/client.rs b/vaultrs-tests/tests/api_tests/client.rs similarity index 100% rename from tests/client.rs rename to vaultrs-tests/tests/api_tests/client.rs diff --git a/vaultrs-tests/tests/api_tests/common.rs b/vaultrs-tests/tests/api_tests/common.rs new file mode 100644 index 0000000..4161d9f --- /dev/null +++ b/vaultrs-tests/tests/api_tests/common.rs @@ -0,0 +1,3 @@ +mod images; +mod setup; +pub use setup::{Test, POSTGRES_PASSWORD, POSTGRES_USER}; diff --git a/vaultrs-tests/tests/api_tests/common/images.rs b/vaultrs-tests/tests/api_tests/common/images.rs new file mode 100644 index 0000000..0523452 --- /dev/null +++ b/vaultrs-tests/tests/api_tests/common/images.rs @@ -0,0 +1,324 @@ +use reqwest::StatusCode; +use std::{borrow::Cow, collections::HashMap, fs, io::Write, path::PathBuf}; +use testcontainers::{ + core::{wait::HttpWaitStrategy, ContainerPort, Mount, WaitFor}, + Image, +}; + +pub struct Vault { + env_vars: HashMap, +} + +impl Default for Vault { + fn default() -> Self { + Self { + env_vars: HashMap::from([("VAULT_DEV_ROOT_TOKEN_ID".to_owned(), "root".to_owned())]), + } + } +} + +impl Image for Vault { + fn name(&self) -> &str { + VAULT_NAME + } + + fn tag(&self) -> &str { + VAULT_TAG + } + + fn ready_conditions(&self) -> Vec { + vec![WaitFor::http( + HttpWaitStrategy::new("/v1/sys/health").with_expected_status_code(StatusCode::OK), + )] + } + + fn expose_ports(&self) -> &[ContainerPort] { + &[ContainerPort::Tcp(8200)] + } + + fn env_vars( + &self, + ) -> impl IntoIterator>, impl Into>)> { + Box::new(self.env_vars.iter()) + } +} + +pub struct TlsVault { + env_vars: HashMap, + _binded_dir: tempfile::TempDir, + volumes: Vec, +} + +impl TlsVault { + pub fn new(vault_key: &str, vault_cert: &str, ca_cert: &str) -> Self { + let binded_dir = tempfile::tempdir().unwrap(); + fs::write(binded_dir.path().join("ca_cert.crt"), ca_cert).unwrap(); + fs::write(binded_dir.path().join("vault_server.crt"), vault_cert).unwrap(); + fs::write(binded_dir.path().join("vault_server.key"), vault_key).unwrap(); + Self { + env_vars: HashMap::from([ + ( + "VAULT_LOCAL_CONFIG".to_owned(), + serde_json::json!({ + "listener": [ + { + "tcp": { + "address": "0.0.0.0:8200", + "tls_cert_file" : "/vault/config/vault_server.crt", + "tls_key_file" : "/vault/config/vault_server.key", + "tls_client_ca_file" : "/vault/config/ca_cert.crt", + "tls_min_version" : "tls13", + } + } + ], + "storage": [ + { + "inmem": {} + } + ], + "disable_mlock": true, + "log_level": "trace" + }) + .to_string(), + ), + ("VAULT_DEV_ROOT_TOKEN_ID".to_owned(), "root".to_owned()), + // Setting 9999 to leave 8200 available for the listener configured config.hcl + ( + "VAULT_DEV_LISTEN_ADDRESS".to_owned(), + "0.0.0.0:9999".to_owned(), + ), + ]), + volumes: vec![Mount::bind_mount( + binded_dir.path().to_str().unwrap(), + "/vault/config", + )], + _binded_dir: binded_dir, + } + } + + pub fn ca_cert(&self) -> PathBuf { + self._binded_dir.path().join("ca_cert.crt") + } +} + +impl Image for TlsVault { + fn name(&self) -> &str { + VAULT_NAME + } + + fn tag(&self) -> &str { + VAULT_TAG + } + + fn ready_conditions(&self) -> Vec { + vec![WaitFor::http( + HttpWaitStrategy::new("/v1/sys/health") + .with_expected_status_code(StatusCode::OK) + .with_client( + reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build() + .unwrap(), + ) + .with_tls(), + )] + } + + fn expose_ports(&self) -> &[ContainerPort] { + &[ContainerPort::Tcp(8200)] + } + + fn env_vars( + &self, + ) -> impl IntoIterator>, impl Into>)> { + Box::new(self.env_vars.iter()) + } + + fn mounts(&self) -> impl IntoIterator { + Box::new(self.volumes.iter()) + } +} + +/// A vault that is not in a dev mod. +/// Can be useful to test unseal and initialization workflows. +pub struct ProdVault { + env_vars: HashMap, +} + +impl Default for ProdVault { + fn default() -> Self { + Self { + env_vars: HashMap::from([( + "VAULT_LOCAL_CONFIG".to_owned(), + serde_json::json!({ + "listener": [ + { + "tcp": { + "address": "0.0.0.0:8200", + "tls_disable": "true" + } + } + ], + "storage": [ + { + "inmem": {} + } + ], + "disable_mlock": true + }) + .to_string(), + )]), + } + } +} + +impl Image for ProdVault { + fn name(&self) -> &str { + VAULT_NAME + } + + fn tag(&self) -> &str { + VAULT_TAG + } + + fn ready_conditions(&self) -> Vec { + vec![WaitFor::http( + HttpWaitStrategy::new("/v1/sys/health") + .with_expected_status_code(StatusCode::NOT_IMPLEMENTED), + )] + } + + fn expose_ports(&self) -> &[ContainerPort] { + &[ContainerPort::Tcp(8200)] + } + + fn env_vars( + &self, + ) -> impl IntoIterator>, impl Into>)> { + Box::new(self.env_vars.iter()) + } + + fn cmd(&self) -> impl IntoIterator>> { + // By default the Vault server will read the config file inside `/vault/config` + vec!["server"].into_iter() + } +} + +/// Nginx is used as web server to mock kubernetes API +pub(super) struct Nginx { + volumes: Vec, + _binded_dirs: Vec, +} + +impl Nginx { + pub(super) fn new() -> Self { + // Default conf, with a tweak to allow all http methods on static resources + let nginx_conf = r#" +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + # hack to allow all http methods on static resources + error_page 405 =200 $uri; +} +"# + .to_string(); + let binded_conf = tempfile::tempdir().unwrap(); + let mut conf = fs::File::create_new(binded_conf.path().join("nginx.conf")).unwrap(); + conf.write_all(nginx_conf.as_bytes()).unwrap(); + + let binded_base = tempfile::tempdir().unwrap(); + let request_dir = binded_base + .path() + .join("apis") + .join("authentication.k8s.io") + .join("v1"); + fs::create_dir_all(&request_dir).unwrap(); + let mut index_html = fs::File::create_new(binded_base.path().join("index.html")).unwrap(); + index_html + .write_all(b"Hello World!") + .unwrap(); + let mut api_endpoint = fs::File::create_new(request_dir.join("tokenreviews")).unwrap(); + api_endpoint + .write_all( + serde_json::json!({ + "apiVersion": "authentication.k8s.io/v1", + "kind": "TokenReview", + "status": { + "authenticated": true, + "user": { + "uid": "testuid", + "username": "system:serviceaccount:testns:test", + }, + "audiences": ["vaultrs-test"] + } + }) + .to_string() + .as_bytes(), + ) + .unwrap(); + + Self { + volumes: vec![ + Mount::bind_mount( + binded_base.path().to_str().unwrap(), + "/usr/share/nginx/html", + ), + Mount::bind_mount(binded_conf.path().to_str().unwrap(), "/etc/nginx/conf.d"), + ], + _binded_dirs: vec![binded_base, binded_conf], + } + } +} + +impl Image for Nginx { + fn name(&self) -> &str { + NGINX_NAME + } + + fn tag(&self) -> &str { + NGINX_VERSION + } + + fn ready_conditions(&self) -> Vec { + vec![WaitFor::http( + HttpWaitStrategy::new("/").with_expected_status_code(StatusCode::OK), + )] + } + + fn expose_ports(&self) -> &[ContainerPort] { + &[ContainerPort::Tcp(80)] + } + + fn mounts(&self) -> impl IntoIterator { + Box::new(self.volumes.iter()) + } +} + +/// Mock an identity provider, something that implements OpenID Connect. +pub(super) struct Oidc; + +impl Image for Oidc { + fn name(&self) -> &str { + OIDC_NAME + } + + fn tag(&self) -> &str { + OIDC_VERSION + } + + fn ready_conditions(&self) -> Vec { + vec![WaitFor::message_on_stdout(b"started server on address")] + } +} + +const VAULT_NAME: &str = "hashicorp/vault"; +const VAULT_TAG: &str = "1.10.3"; +const NGINX_NAME: &str = "nginx"; +const NGINX_VERSION: &str = "1.21"; +const OIDC_NAME: &str = "ghcr.io/navikt/mock-oauth2-server"; +const OIDC_VERSION: &str = "0.3.5"; diff --git a/vaultrs-tests/tests/api_tests/common/setup.rs b/vaultrs-tests/tests/api_tests/common/setup.rs new file mode 100644 index 0000000..4bec105 --- /dev/null +++ b/vaultrs-tests/tests/api_tests/common/setup.rs @@ -0,0 +1,343 @@ +use super::images::{Nginx, Oidc, ProdVault, TlsVault, Vault}; +use rcgen::{CertificateParams, KeyPair}; +use std::{ + future::{Future, IntoFuture}, + path::{Path, PathBuf}, + pin::Pin, +}; +use testcontainers::{runners::AsyncRunner, ContainerAsync, Image, ImageExt}; +use testcontainers_modules::{localstack::LocalStack, postgres::Postgres}; +use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; + +pub struct Test +where + I: Image, +{ + client: VaultClient, + /// Vault handle, remove the container on drop. + _vault: ContainerAsync, + /// If Vault use TLS, the CA that signed its certificate. + ca_cert: Option, + localstack: Option, + postgres: Option, + nginx: Option, + oidc: Option, +} + +impl Test +where + T: Image, +{ + pub fn client(&self) -> &VaultClient { + &self.client + } + + pub fn client_mut(&mut self) -> &mut VaultClient { + &mut self.client + } + + pub fn localstack_url(&self) -> Option<&str> { + self.localstack + .as_ref() + .map(|localstack| localstack.url.as_str()) + } + + pub fn postgres_url(&self) -> Option<&str> { + self.postgres.as_ref().map(|postgres| postgres.url.as_str()) + } + + pub fn nginx_url(&self) -> Option<&str> { + self.nginx.as_ref().map(|nginx| nginx.url.as_str()) + } + + pub fn oidc_url(&self) -> Option<&str> { + self.oidc.as_ref().map(|oidc| oidc.url.as_str()) + } + + pub fn ca_cert(&self) -> Option<&Path> { + self.ca_cert.as_deref() + } +} + +#[derive(Default)] +pub struct TestBuilder { + localstack: Option, + nginx: bool, + postgres: bool, + oidc: bool, +} + +impl TestBuilder { + pub fn with_postgres(mut self) -> Self { + self.postgres = true; + self + } + + pub fn with_nginx(mut self) -> Self { + self.nginx = true; + self + } + + pub fn with_oidc(mut self) -> Self { + self.oidc = true; + self + } + + pub fn with_localstack( + mut self, + services: impl IntoIterator>, + ) -> Self { + // TODO: when Iterator::intersperse is stable use it. + // https://docs.localstack.cloud/references/configuration// + let mut services_env = String::new(); + for service in services { + let service: String = service.into(); + services_env.push_str(&service); + services_env.push(','); + } + let services_env = services_env.strip_suffix(',').unwrap(); + self.localstack = Some(services_env.to_string()); + self + } + + // Don't use this directly, just use `.await` the `TestBuilder`. + async fn build(self) -> Test { + let _ = tracing_subscriber::FmtSubscriber::builder() + .with_test_writer() + .try_init(); + + let nginx = if self.nginx { + let nginx = Nginx::new().start().await.unwrap(); + let bridge_ip = nginx.get_bridge_ip_address().await.unwrap(); + let url = format!("http://{bridge_ip}:80"); + Some(RunningNginx { _nginx: nginx, url }) + } else { + None + }; + + let postgres = if self.postgres { + let postgres = Postgres::default() + .with_user(POSTGRES_USER) + .with_password(POSTGRES_PASSWORD) + .start() + .await + .unwrap(); + let bridge_ip = postgres.get_bridge_ip_address().await.unwrap(); + let url = format!("{bridge_ip}:5432"); + Some(RunningPostgres { + _postgres: postgres, + url, + }) + } else { + None + }; + + let localstack = if let Some(services) = self.localstack { + let localstack = LocalStack::default() + .with_env_var("SERVICES", services) + .start() + .await + .unwrap(); + let bridge_ip = localstack.get_bridge_ip_address().await.unwrap(); + let url = format!("http://{bridge_ip}:4566"); + Some(RunningLocalStack { + _localstack: localstack, + url, + }) + } else { + None + }; + + let oidc = if self.oidc { + let oidc = Oidc.start().await.unwrap(); + let host = oidc.get_bridge_ip_address().await.unwrap(); + let url = format!("http://{host}:8080"); + Some(RunningOidc { _oidc: oidc, url }) + } else { + None + }; + + let vault = Vault::default().start().await.unwrap(); + let host_port = vault.get_host_port_ipv4(8200).await.unwrap(); + let addr = format!("http://localhost:{host_port}"); + + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(addr) + .token("root") + .build() + .unwrap(), + ) + .unwrap(); + + Test { + localstack, + nginx, + postgres, + client, + oidc, + _vault: vault, + ca_cert: None, + } + } +} + +impl IntoFuture for TestBuilder { + type Output = Test; + + // TODO: update when impl_trait_in_assoc_type is stabilized. + type IntoFuture = Pin + Send + 'static>>; + + fn into_future(self) -> Self::IntoFuture { + Box::pin(self.build()) + } +} + +impl Test { + pub fn builder() -> TestBuilder { + TestBuilder::default() + } +} + +impl Test { + pub async fn new_tls() -> Self { + let _ = tracing_subscriber::FmtSubscriber::builder() + .with_test_writer() + .try_init(); + let TlsSetup { + vault_key, + vault_cert, + client_bundle, + ca_cert, + } = generate_certs(); + let vault = TlsVault::new(&vault_key, &vault_cert, &ca_cert) + .start() + .await + .unwrap(); + let ca_cert = vault.image().ca_cert(); + let host_port = vault.get_host_port_ipv4(8200).await.unwrap(); + let addr = format!("https://localhost:{host_port}"); + + let identity = reqwest::Identity::from_pem(&client_bundle).unwrap(); + + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(addr) + .token("root") + .identity(Some(identity)) + .ca_certs(vec![ca_cert.to_str().unwrap().to_string()]) + .build() + .unwrap(), + ) + .unwrap(); + + Self { + client, + _vault: vault, + localstack: None, + postgres: None, + nginx: None, + oidc: None, + ca_cert: Some(ca_cert), + } + } +} + +impl Test { + pub async fn new_prod() -> Self { + let (client, vault) = Self::new_vault_prod().await; + + Self { + client, + _vault: vault, + localstack: None, + postgres: None, + nginx: None, + oidc: None, + ca_cert: None, + } + } + + async fn new_vault_prod() -> (VaultClient, ContainerAsync) { + let _ = tracing_subscriber::FmtSubscriber::builder() + .with_test_writer() + .try_init(); + + let vault = ProdVault::default().start().await.unwrap(); + let host_port = vault.get_host_port_ipv4(8200).await.unwrap(); + let addr = format!("http://localhost:{host_port}"); + + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(addr) + .build() + .unwrap(), + ) + .unwrap(); + (client, vault) + } +} + +pub const POSTGRES_USER: &str = "postgres"; +pub const POSTGRES_PASSWORD: &str = "postgres"; + +struct RunningOidc { + /// OIDC handle, remove the container on drop. + _oidc: ContainerAsync, + url: String, +} + +struct RunningLocalStack { + /// Localstack handle, remove the container on drop. + _localstack: ContainerAsync, + url: String, +} + +struct RunningPostgres { + /// Postgres handle, remove the container on drop. + _postgres: ContainerAsync, + url: String, +} + +struct RunningNginx { + /// Nginx handle, remove the container on drop. + _nginx: ContainerAsync, + url: String, +} + +fn generate_certs() -> TlsSetup { + let mut params = CertificateParams::new([]).unwrap(); + params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); + let ca_key_pair = KeyPair::generate().unwrap(); + let ca_cert = params.self_signed(&ca_key_pair).unwrap(); + + let vault_key_pair = KeyPair::generate().unwrap(); + let params = CertificateParams::new(["localhost".to_string()]).unwrap(); + let vault_cert = params + .signed_by(&vault_key_pair, &ca_cert, &ca_key_pair) + .unwrap(); + + let client_key_pair = KeyPair::generate().unwrap(); + let client_cert = CertificateParams::new([]) + .unwrap() + .signed_by(&client_key_pair, &ca_cert, &ca_key_pair) + .unwrap(); + + let mut client_bundle = client_cert.pem().into_bytes(); + let mut client_key = client_key_pair.serialize_pem().into_bytes(); + client_bundle.append(&mut client_key); + + TlsSetup { + vault_key: vault_key_pair.serialize_pem(), + vault_cert: vault_cert.pem(), + client_bundle, + ca_cert: ca_cert.pem(), + } +} + +struct TlsSetup { + ca_cert: String, + vault_key: String, + vault_cert: String, + client_bundle: Vec, +} diff --git a/tests/database.rs b/vaultrs-tests/tests/api_tests/database.rs similarity index 73% rename from tests/database.rs rename to vaultrs-tests/tests/api_tests/database.rs index 107f446..ca92ac8 100644 --- a/tests/database.rs +++ b/vaultrs-tests/tests/api_tests/database.rs @@ -1,48 +1,41 @@ -#[macro_use] -extern crate tracing; - -mod common; - -use common::{VaultServer, VaultServerHelper}; -use dockertest_server::servers::database::postgres::PostgresServer; -use test_log::test; +use tracing::debug; use vaultrs::api::database::requests::PostgreSQLConnectionRequest; use vaultrs::client::Client; use vaultrs::error::ClientError; - -#[test] -fn test() { - let test = common::new_db_test(); - test.run(|instance| async move { - let db_server: PostgresServer = instance.server(); - let vault_server: VaultServer = instance.server(); - let client = vault_server.client(); - let endpoint = setup(&db_server, &vault_server, &client).await.unwrap(); - - // Test reset/rotate - crate::connection::test_reset(&client, &endpoint).await; - crate::connection::test_rotate(&client, &endpoint).await; - - // Test roles - crate::role::test_set(&client, &endpoint).await; - crate::role::test_read(&client, &endpoint).await; - crate::role::test_creds(&client, &endpoint).await; - crate::role::test_list(&client, &endpoint).await; - crate::role::test_delete(&client, &endpoint).await; - - // Test static roles - crate::static_role::test_set(&client, &endpoint).await; - crate::static_role::test_read(&client, &endpoint).await; - crate::static_role::test_creds(&client, &endpoint).await; - crate::static_role::test_list(&client, &endpoint).await; - crate::static_role::test_rotate(&client, &endpoint).await; - crate::static_role::test_delete(&client, &endpoint).await; - - // Test connection - crate::connection::test_read(&client, &endpoint).await; - crate::connection::test_list(&client, &endpoint).await; - crate::connection::test_delete(&client, &endpoint).await; - }); +use vaultrs::sys::mount; + +use crate::common::{Test, POSTGRES_PASSWORD, POSTGRES_USER}; + +#[tokio::test] +async fn test() { + let test = Test::builder().with_postgres().await; + let client = test.client(); + let db_url = test.postgres_url().unwrap(); + let endpoint = setup(db_url, client).await.unwrap(); + + // Test reset/rotate + connection::test_reset(client, &endpoint).await; + connection::test_rotate(client, &endpoint).await; + + // Test roles + role::test_set(client, &endpoint).await; + role::test_read(client, &endpoint).await; + role::test_creds(client, &endpoint).await; + role::test_list(client, &endpoint).await; + role::test_delete(client, &endpoint).await; + + // Test static roles + static_role::test_set(client, &endpoint).await; + static_role::test_read(client, &endpoint).await; + static_role::test_creds(client, &endpoint).await; + static_role::test_list(client, &endpoint).await; + static_role::test_rotate(client, &endpoint).await; + static_role::test_delete(client, &endpoint).await; + + // Test connection + connection::test_read(client, &endpoint).await; + connection::test_list(client, &endpoint).await; + connection::test_delete(client, &endpoint).await; } mod connection { @@ -68,9 +61,10 @@ mod connection { } pub async fn test_reset(client: &impl Client, endpoint: &DatabaseEndpoint) { - let res = - connection::reset(client, endpoint.path.as_str(), endpoint.connection.as_str()).await; - assert!(res.is_ok()); + dbg!(&endpoint); + connection::reset(client, endpoint.path.as_str(), endpoint.connection.as_str()) + .await + .unwrap(); } pub async fn test_rotate(client: &impl Client, endpoint: &DatabaseEndpoint) { @@ -198,26 +192,22 @@ pub struct DatabaseEndpoint { pub username: String, } -async fn setup( - db_server: &PostgresServer, - vault_server: &VaultServer, - client: &impl Client, -) -> Result { +async fn setup(db_url: &str, client: &impl Client) -> Result { debug!("setting up database secret engine"); let path = "db_test"; let connection = "postgres"; let role = "test"; let static_role = "static_test"; - // Mount the database secret engine - vault_server.mount_secret(client, path, "database").await?; + mount::enable(client, path, "database", None).await.unwrap(); // Configure let url = format!( "postgresql://{{{{username}}}}:{{{{password}}}}@{}/postgres?sslmode=disable", - db_server.internal_address() + db_url ); + dbg!(&url); vaultrs::database::connection::postgres( client, path, @@ -226,8 +216,8 @@ async fn setup( PostgreSQLConnectionRequest::builder() .plugin_name("postgresql-database-plugin") .connection_url(url) - .username(&db_server.username) - .password(&db_server.password) + .username(POSTGRES_USER) + .password(POSTGRES_PASSWORD) .verify_connection(false) .allowed_roles(vec!["*".into()]), ), @@ -239,6 +229,6 @@ async fn setup( path: path.to_string(), role: role.to_string(), static_role: static_role.to_string(), - username: db_server.username.clone(), + username: "postgres".to_string(), }) } diff --git a/tests/identity.rs b/vaultrs-tests/tests/api_tests/identity.rs similarity index 87% rename from tests/identity.rs rename to vaultrs-tests/tests/api_tests/identity.rs index fcf6d1a..9c0baf1 100644 --- a/tests/identity.rs +++ b/vaultrs-tests/tests/api_tests/identity.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use dockertest_server::servers::hashi::VaultServer; use vaultrs::{ api::identity::{ entity::requests::{ @@ -23,9 +22,7 @@ use vaultrs::{ identity, sys, }; -use crate::common::VaultServerHelper; - -mod common; +use crate::common::Test; const ENTITY_NAME: &str = "test-entity"; const ENTITY_NEW_NAME: &str = "new-test-entity"; @@ -35,60 +32,53 @@ const POLICY: &str = "default"; const GROUP_NAME: &str = "test-group"; const GROUP_ALIAS_NAME: &str = "test-group-alias"; -#[test] -fn test_entity_and_entity_alias() { - let test = common::new_test(); - - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let client = server.client(); - - let entity_id = test_create_entity(&client).await; - let alias_id = test_create_entity_alias(&client, &entity_id).await; - create_anonymous_entity(&client).await; - test_list_entity_by_id(&client, &entity_id).await; - test_read_entity_by_id(&client, &entity_id).await; - test_update_entity_by_id(&client, &entity_id).await; - - test_list_entity_by_name(&client).await; - test_read_entity_by_name(&client, &entity_id).await; - test_create_or_update_entity_by_name(&client).await; - test_delete_entity_by_name(&client).await; - - test_batch_delete_entity(&client).await; - test_merge_entity(&client).await; - - test_read_entity_alias_id(&client, &alias_id).await; - test_update_entity_alias_by_id(&client, &alias_id).await; - test_list_entity_alias_by_id(&client, &alias_id, &entity_id).await; - test_delete_entity_alias_by_id(&client, &alias_id).await; - }); +#[tokio::test] +async fn test_entity_and_entity_alias() { + let test = Test::builder().await; + + let client = test.client(); + + let entity_id = test_create_entity(client).await; + let alias_id = test_create_entity_alias(client, &entity_id).await; + create_anonymous_entity(client).await; + test_list_entity_by_id(client, &entity_id).await; + test_read_entity_by_id(client, &entity_id).await; + test_update_entity_by_id(client, &entity_id).await; + + test_list_entity_by_name(client).await; + test_read_entity_by_name(client, &entity_id).await; + test_create_or_update_entity_by_name(client).await; + test_delete_entity_by_name(client).await; + + test_batch_delete_entity(client).await; + test_merge_entity(client).await; + + test_read_entity_alias_id(client, &alias_id).await; + test_update_entity_alias_by_id(client, &alias_id).await; + test_list_entity_alias_by_id(client, &alias_id, &entity_id).await; + test_delete_entity_alias_by_id(client, &alias_id).await; } -#[test] -fn test_group_and_group_alias() { - let test = common::new_test(); - - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let client = server.client(); - - let group_id = test_create_group(&client).await; - test_read_group_by_id(&client, &group_id).await; - test_update_group_by_id(&client, &group_id).await; - test_list_groups_by_id(&client, &group_id).await; - test_delete_group_by_id(&client, &group_id).await; - - test_create_group_by_name(&client).await; - test_read_group_by_name(&client).await; - test_list_groups_by_name(&client).await; - test_delete_group_by_name(&client).await; - - let group_alias_id = test_group_alias(&client).await; - test_update_group_alias_by_id(&client, &group_alias_id).await; - test_list_group_aliases_by_id(&client, &group_alias_id).await; - test_delete_group_alias_by_id(&client, &group_alias_id).await; - }); +#[tokio::test] +async fn test_group_and_group_alias() { + let test = Test::builder().await; + let client = test.client(); + + let group_id = test_create_group(client).await; + test_read_group_by_id(client, &group_id).await; + test_update_group_by_id(client, &group_id).await; + test_list_groups_by_id(client, &group_id).await; + test_delete_group_by_id(client, &group_id).await; + + test_create_group_by_name(client).await; + test_read_group_by_name(client).await; + test_list_groups_by_name(client).await; + test_delete_group_by_name(client).await; + + let group_alias_id = test_group_alias(client).await; + test_update_group_alias_by_id(client, &group_alias_id).await; + test_list_group_aliases_by_id(client, &group_alias_id).await; + test_delete_group_alias_by_id(client, &group_alias_id).await; } async fn test_create_entity(client: &VaultClient) -> String { @@ -308,7 +298,7 @@ async fn test_create_entity_alias(client: &VaultClient, entity_id: &str) -> Stri token_auth_accessor, Some( &mut CreateEntityAliasRequestBuilder::default() - .id(&entity_alias.id.clone()) + .id(entity_alias.id.clone()) .custom_metadata(metadata.clone()), ), ) @@ -326,7 +316,7 @@ async fn test_create_entity_alias(client: &VaultClient, entity_id: &str) -> Stri ENTITY_ALIAS_NAME, entity_id.to_string().as_str(), token_auth_accessor, - Some(&mut CreateEntityAliasRequestBuilder::default().id(&entity_alias.id.clone())), + Some(&mut CreateEntityAliasRequestBuilder::default().id(entity_alias.id.clone())), ) .await .err() diff --git a/tests/kubernetes.rs b/vaultrs-tests/tests/api_tests/kubernetes.rs similarity index 72% rename from tests/kubernetes.rs rename to vaultrs-tests/tests/api_tests/kubernetes.rs index 2d433a8..a9c3bd0 100644 --- a/tests/kubernetes.rs +++ b/vaultrs-tests/tests/api_tests/kubernetes.rs @@ -1,37 +1,31 @@ -#[macro_use] -extern crate tracing; - -mod common; - -use common::{VaultServer, VaultServerHelper}; -use dockertest_server::servers::webserver::nginx::NginxServer; -use test_log::test; +use tracing::debug; use vaultrs::api::auth::kubernetes::requests::ConfigureKubernetesAuthRequest; use vaultrs::client::Client; use vaultrs::error::ClientError; +use vaultrs::sys::auth; -#[test] -fn test() { - let (test, _content) = common::new_webserver_test(); - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let webserver: NginxServer = instance.server(); - let client = server.client(); - let endpoint = setup(&server, &client, &webserver).await.unwrap(); +use crate::common::Test; - // Test pre-configure auth backend - test_configure(&client, &endpoint).await; - test_read_config(&client, &endpoint).await; +#[tokio::test] +async fn test() { + let test = Test::builder().with_nginx().await; + let client = test.client(); + let nginx_server_addr = test.nginx_url().unwrap(); + let endpoint = setup(client, nginx_server_addr).await.unwrap(); - // Test roles - crate::role::test_create(&client, &endpoint).await; - crate::role::test_read(&client, &endpoint).await; - crate::role::test_list(&client, &endpoint).await; + // Test pre-configure auth backend + test_configure(client, &endpoint).await; + test_read_config(client, &endpoint).await; - crate::test_login(&client, &endpoint).await; + // Test roles + role::test_create(client, &endpoint).await; + role::test_read(client, &endpoint).await; + role::test_list(client, &endpoint).await; - crate::role::test_delete(&client, &endpoint).await; - }) + // That's the only test failing + test_login(client, &endpoint).await; + + role::test_delete(client, &endpoint).await; } pub async fn test_configure(client: &impl Client, endpoint: &KubernetesRoleEndpoint) { @@ -41,8 +35,8 @@ pub async fn test_configure(client: &impl Client, endpoint: &KubernetesRoleEndpo &endpoint.kubernetes_host, Some( &mut ConfigureKubernetesAuthRequest::builder() - .kubernetes_host(&format!("https://{}", &endpoint.kubernetes_host)) - .kubernetes_ca_cert(include_str!("files/kubernetes/ca.crt")) + .kubernetes_host(format!("http://{}", &endpoint.kubernetes_host)) + .kubernetes_ca_cert(include_str!("../files/kubernetes/ca.crt")) .issuer(&endpoint.jtw_issuer), ), ) @@ -82,11 +76,9 @@ pub async fn test_login(client: &impl Client, endpoint: &KubernetesRoleEndpoint) let token_str = claims.sign_with_key(&key).unwrap(); - let resp = - vaultrs::auth::kubernetes::login(client, &endpoint.path, &endpoint.role_name, &token_str) - .await; - - assert!(resp.is_ok()); + vaultrs::auth::kubernetes::login(client, &endpoint.path, &endpoint.role_name, &token_str) + .await + .unwrap(); } mod role { @@ -145,21 +137,22 @@ pub struct KubernetesRoleEndpoint { } async fn setup( - server: &VaultServer, client: &impl Client, - webserver: &NginxServer, + nginx_server_addr: &str, ) -> Result { debug!("setting up Kubernetes auth engine"); let path = "kubernetes_test"; let role_name = "test"; // Mount the AppRole auth engine - server.mount_auth(client, path, "kubernetes").await?; + auth::enable(client, path, "kubernetes", None) + .await + .unwrap(); Ok(KubernetesRoleEndpoint { path: path.to_string(), role_name: role_name.to_string(), - kubernetes_host: webserver.internal_url().to_string(), + kubernetes_host: nginx_server_addr.to_string(), jtw_issuer: "vaultrs/test".to_string(), kubernetes_namespace: "testns".to_string(), }) diff --git a/vaultrs-tests/tests/api_tests/kv1.rs b/vaultrs-tests/tests/api_tests/kv1.rs new file mode 100644 index 0000000..6de554c --- /dev/null +++ b/vaultrs-tests/tests/api_tests/kv1.rs @@ -0,0 +1,90 @@ +use crate::common::Test; +use std::collections::HashMap; +use vaultrs::{api::kv1::responses::GetSecretResponse, error::ClientError, kv1, sys::mount}; + +#[tokio::test] +async fn test_kv1() { + let test = Test::builder().await; + let client = test.client(); + + // Mount KV v1 secret engine + let mount = "kv_v1"; + let secret_path = "mysecret/foo"; + mount::enable(client, mount, "kv", None).await.unwrap(); + + // Create test secrets + let expected_secret = HashMap::from([("key1", "value1"), ("key2", "value2")]); + kv1::set(client, mount, secret_path, &expected_secret) + .await + .unwrap(); + + // Read it + let read_secret: HashMap = kv1::get(client, mount, secret_path).await.unwrap(); + + println!("{:?}", read_secret); + + assert_eq!( + read_secret.get("key1").unwrap(), + expected_secret.get("key1").unwrap() + ); + assert_eq!( + read_secret.get("key2").unwrap(), + expected_secret.get("key2").unwrap() + ); + + // Read it as raw value + let read_secret_raw: GetSecretResponse = + kv1::get_raw(client, mount, secret_path).await.unwrap(); + + println!("{:?}", read_secret_raw); + + assert_eq!( + read_secret_raw.data.get("key1").unwrap(), + expected_secret.get("key1").unwrap() + ); + assert_eq!( + read_secret_raw.data.get("key2").unwrap(), + expected_secret.get("key2").unwrap() + ); + + // List secret keys + let list_secret = kv1::list(client, mount, "mysecret").await.unwrap(); + + println!("{:?}", list_secret); + + assert_eq!(list_secret.data.keys, vec!["foo"]); + + // Delete secret and read again and expect 404 to check deletion + kv1::delete(client, mount, secret_path).await.unwrap(); + + let r = kv1::get_raw(client, mount, secret_path).await; + + match r.expect_err(&format!( + "Expected error when reading {} after delete.", + &secret_path + )) { + ClientError::APIError { code, .. } => { + assert_eq!(code, 404, "Expected error code 404 for non-existing secret") + } + e => { + panic!("Expected error to be APIError with code 404, got {:?}", e) + } + }; + + let my_secrets = HashMap::from([("key1", "value1"), ("key2", "value2")]); + + kv1::set(client, mount, "my/secrets", &my_secrets) + .await + .unwrap(); + + let read_secrets: HashMap = + kv1::get(client, mount, "my/secrets").await.unwrap(); + + println!("{:}", read_secrets.get("key1").unwrap()); // value1 + + let list_secret = kv1::list(client, mount, "my").await.unwrap(); + + println!("{:?}", list_secret.data.keys); // [ "secrets" ] + + kv1::delete(client, mount, "my/secrets").await.unwrap(); +} diff --git a/tests/kv2.rs b/vaultrs-tests/tests/api_tests/kv2.rs similarity index 74% rename from tests/kv2.rs rename to vaultrs-tests/tests/api_tests/kv2.rs index 36b99ae..8c7efa8 100644 --- a/tests/kv2.rs +++ b/vaultrs-tests/tests/api_tests/kv2.rs @@ -1,59 +1,51 @@ -#[macro_use] -extern crate tracing; +use crate::common::Test; -mod common; - -use common::{VaultServer, VaultServerHelper}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use test_log::test; +use tracing::{debug, trace}; use vaultrs::api::kv2::requests::{SetSecretMetadataRequest, SetSecretRequestOptions}; use vaultrs::client::Client; use vaultrs::error::ClientError; use vaultrs::kv2; - -#[test] -fn test() { - let test = common::new_test(); - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let client = server.client(); - let endpoint = setup(&server, &client).await.unwrap(); - - // Test set / read - test_list(&client, &endpoint).await; - test_read(&client, &endpoint).await; - test_read_version(&client, &endpoint).await; - test_set(&client, &endpoint).await; - test_set_with_compare_and_swap(&client, &endpoint).await; - test_set_metadata(&client, &endpoint).await; - test_read_metadata(&client, &endpoint).await; - - // Test delete - test_delete_latest(&client, &endpoint).await; - test_undelete_versions(&client, &endpoint).await; - - test_delete_versions(&client, &endpoint).await; - create(&client, &endpoint).await.unwrap(); - - test_destroy_versions(&client, &endpoint).await; - create(&client, &endpoint).await.unwrap(); - - test_delete_metadata(&client, &endpoint).await; - create(&client, &endpoint).await.unwrap(); - - // Test config - crate::config::test_set(&client, &endpoint).await; - crate::config::test_read(&client, &endpoint).await; - - // Test URL encoding works as expected - test_kv2_url_encoding(&server).await; - }); +use vaultrs::sys::mount; + +#[tokio::test] +async fn test() { + let test = Test::builder().await; + let client = test.client(); + let endpoint = setup(client).await.unwrap(); + + // Test set / read + test_list(client, &endpoint).await; + test_read(client, &endpoint).await; + test_read_version(client, &endpoint).await; + test_set(client, &endpoint).await; + test_set_with_compare_and_swap(client, &endpoint).await; + test_set_metadata(client, &endpoint).await; + test_read_metadata(client, &endpoint).await; + + // Test delete + test_delete_latest(client, &endpoint).await; + test_undelete_versions(client, &endpoint).await; + + test_delete_versions(client, &endpoint).await; + create(client, &endpoint).await.unwrap(); + + test_destroy_versions(client, &endpoint).await; + create(client, &endpoint).await.unwrap(); + + test_delete_metadata(client, &endpoint).await; + create(client, &endpoint).await.unwrap(); + + // Test config + config::test_set(client, &endpoint).await; + config::test_read(client, &endpoint).await; + + // Test URL encoding works as expected + test_kv2_url_encoding(client).await; } -async fn test_kv2_url_encoding(server: &VaultServer) { - let client = server.client(); - +async fn test_kv2_url_encoding(client: &impl Client) { debug!("setting up kv2 auth engine"); let path = "path/to/secret engine"; let name = "path/to/some secret/password name with whitespace"; @@ -68,18 +60,18 @@ async fn test_kv2_url_encoding(server: &VaultServer) { }; // Mount the KV2 engine - server.mount_secret(&client, path, "kv-v2").await.unwrap(); + mount::enable(client, path, "kv-v2", None).await.unwrap(); // Create a test secret - create(&client, &endpoint).await.unwrap(); + create(client, &endpoint).await.unwrap(); - let secrets = kv2::list(&client, path, "path/to/some secret/") + let secrets = kv2::list(client, path, "path/to/some secret/") .await .unwrap(); assert_eq!(secrets.len(), 1); assert_eq!(secrets.first().unwrap(), "password name with whitespace"); - let res: Result = kv2::read(&client, path, name).await; + let res: Result = kv2::read(client, path, name).await; assert!(res.is_ok()); assert_eq!(res.unwrap().key, endpoint.secret.key); } @@ -199,8 +191,9 @@ async fn test_undelete_versions(client: &impl Client, endpoint: &SecretEndpoint) } mod config { - use crate::{Client, SecretEndpoint}; - use vaultrs::{api::kv2::requests::SetConfigurationRequest, kv2::config}; + use vaultrs::{api::kv2::requests::SetConfigurationRequest, client::Client, kv2::config}; + + use super::SecretEndpoint; pub async fn test_read(client: &impl Client, endpoint: &SecretEndpoint) { let resp = config::read(client, endpoint.path.as_str()).await; @@ -250,7 +243,7 @@ async fn create(client: &impl Client, endpoint: &SecretEndpoint) -> Result<(), C Ok(()) } -async fn setup(server: &VaultServer, client: &impl Client) -> Result { +async fn setup(client: &impl Client) -> Result { debug!("setting up kv2 auth engine"); let path = "secret_test"; let name = "test"; @@ -265,7 +258,7 @@ async fn setup(server: &VaultServer, client: &impl Client) -> Result::new(); - expected.insert("approle_test/".to_string(), Method::APPROLE); - expected.insert("token/".to_string(), Method::TOKEN); - expected.insert("userpass_test/".to_string(), Method::USERPASS); - expected.insert("aws_test/".to_string(), Method::AWS); - - let res = method::list(client).await; - assert!(res.is_ok()); + let auths = method::list(client).await.unwrap(); + let expected_auths = HashMap::from([ + ("approle_test/".into(), Method::APPROLE), + ("oidc_test/".to_string(), Method::OIDC), + ("token/".to_string(), Method::TOKEN), + ("userpass_test/".to_string(), Method::USERPASS), + ("aws_test/".to_string(), Method::AWS), + ]); - let res = res.unwrap(); - assert_eq!(res["approle_test/"], expected["approle_test/"]); - assert_eq!(res["token/"], expected["token/"]); - assert_eq!(res["userpass_test/"], expected["userpass_test/"]); - assert_eq!(res["aws_test/"], expected["aws_test/"]); + assert_eq!(auths, expected_auths); } #[instrument(skip(client))] @@ -89,7 +74,7 @@ async fn test_list_supported(client: &VaultClient) { assert!(res.is_ok()); let res = res.unwrap(); - assert_eq!(res.keys().len(), 3); + assert_eq!(res.keys().len(), 4); } #[instrument(skip(client))] @@ -116,32 +101,25 @@ async fn test_approle(client: &mut VaultClient) { let secret_id = res.unwrap().secret_id; // Test login - let res = client + client .login("approle_test", &AppRoleLogin { role_id, secret_id }) - .await; - assert!(res.is_ok()); - //assert!(client.lookup().await.is_ok()); + .await + .unwrap(); + client.lookup().await.unwrap(); } -#[cfg(feature = "oidc")] -#[instrument(skip(client, oidc_server, vault_server))] -async fn test_oidc(oidc_server: &OIDCServer, vault_server: &VaultServer, client: &mut VaultClient) { +#[instrument(skip(client, oidc_url))] +async fn test_oidc(oidc_url: &str, client: &mut VaultClient) { debug!("running test..."); use vaultrs::api::auth::oidc::requests::{SetConfigurationRequest, SetRoleRequest}; let mount = "oidc_test"; let role = "test"; - let port = 8350; - - // Mount OIDC engine - vault_server - .mount_auth(client, mount, "oidc") - .await - .unwrap(); + const PORT: u16 = 8350; // Configure OIDC engine - let auth_url = format!("{}/default", oidc_server.internal_url()); + let auth_url = format!("{}/default", oidc_url); vaultrs::auth::oidc::config::set( client, mount, @@ -156,8 +134,9 @@ async fn test_oidc(oidc_server: &OIDCServer, vault_server: &VaultServer, client: .await .unwrap(); + // This url is the call back of the server that vault login will spawn. + let redirect = format!("http://localhost:{PORT}/oidc/callback"); // Create OIDC test role - let redirect = format!("http://localhost:{}/oidc/callback", port); vaultrs::auth::oidc::role::set( client, mount, @@ -171,28 +150,21 @@ async fn test_oidc(oidc_server: &OIDCServer, vault_server: &VaultServer, client: // Create OIDC login request let login = vaultrs_login::engines::oidc::OIDCLogin { - port: Some(port), + port: Some(PORT), role: Some(role.to_string()), }; + // The callback contains the aurorize url and the parameters let callback = client.login_multi(mount, login).await.unwrap(); - // Perform a mock login - // Vault is configured to use the DNS name and port of the test OAuth server - // so it can communicate with it on the Docker network. Our local test - // client won't be able to resolve the DNS name or reach the port since it's - // forwarded to a random OS port. So we must replace it with the version - // that our test client can resolve. - let url = callback.url.replace( - oidc_server.internal_url().as_str(), - oidc_server.external_url().as_str(), - ); + let url = callback.url.as_str(); let rclient = reqwest::Client::default(); let params = [("username", "default"), ("acr", "default")]; + rclient.post(url).form(¶ms).send().await.unwrap(); // The callback should be successful now client.login_multi_callback(mount, callback).await.unwrap(); - //assert!(vault_server.client.lookup().await.is_ok()); + client.lookup().await.unwrap(); } #[instrument(skip(client))] @@ -200,18 +172,18 @@ async fn test_userpass(client: &mut VaultClient) { debug!("running test..."); // Create a user - let res = userpass::user::set( + userpass::user::set( client, "userpass_test", "test", "test", Some(CreateUserRequest::builder().token_policies(vec!["default".to_string()])), ) - .await; - assert!(res.is_ok()); + .await + .unwrap(); // Test login - let res = client + client .login( "userpass_test", &UserpassLogin { @@ -219,17 +191,17 @@ async fn test_userpass(client: &mut VaultClient) { password: "test".to_string(), }, ) - .await; - assert!(res.is_ok()); - //assert!(server.client.lookup().await.is_ok()); + .await + .unwrap(); + client.lookup().await.unwrap(); } -#[cfg(feature = "aws")] -#[instrument(skip(localstack, client))] -async fn test_aws(localstack: &LocalStackServer, client: &mut VaultClient) { +#[instrument(skip(localstack_url, client))] +async fn test_aws(localstack_url: &str, client: &mut VaultClient) { debug!("running test..."); use vaultrs::api::auth::aws::requests::{ConfigureClientRequest, CreateRoleRequest}; + use vaultrs::auth::aws; let mount = "aws_test"; @@ -240,9 +212,9 @@ async fn test_aws(localstack: &LocalStackServer, client: &mut VaultClient) { &mut ConfigureClientRequest::builder() .access_key("test") .secret_key("test") - .endpoint(localstack.internal_url()) - .iam_endpoint(localstack.internal_url()) - .sts_endpoint(localstack.internal_url()) + .endpoint(localstack_url) + .iam_endpoint(localstack_url) + .sts_endpoint(localstack_url) .sts_region("local"), ), ) @@ -261,7 +233,7 @@ async fn test_aws(localstack: &LocalStackServer, client: &mut VaultClient) { .build(); let iam_config = aws_sdk_iam::config::Builder::from(&aws_config) - .endpoint_url(localstack.internal_url()) + .endpoint_url(localstack_url) .behavior_version_latest() .build(); @@ -303,7 +275,7 @@ async fn test_aws(localstack: &LocalStackServer, client: &mut VaultClient) { .unwrap(); let sts_config = aws_sdk_sts::config::Builder::from(&aws_config) - .endpoint_url(localstack.internal_url()) + .endpoint_url(localstack_url) .behavior_version_latest() .build(); let sts_client = aws_sdk_sts::Client::from_conf(sts_config); @@ -330,5 +302,5 @@ async fn test_aws(localstack: &LocalStackServer, client: &mut VaultClient) { let res = client.login(mount, &login).await; assert!(res.is_ok()); - //assert!(vault_server.client.lookup().await.is_ok()); + client.lookup().await.unwrap(); } diff --git a/vaultrs-tests/tests/api_tests/main.rs b/vaultrs-tests/tests/api_tests/main.rs new file mode 100644 index 0000000..5baa0a5 --- /dev/null +++ b/vaultrs-tests/tests/api_tests/main.rs @@ -0,0 +1,22 @@ +mod approle; +mod aws; +mod cert; +mod client; +mod common; +mod database; +mod identity; +mod kubernetes; +mod kv1; +mod kv2; +mod login; +mod oidc; +mod pki; +mod ssh; +mod sys; +mod token; +mod transit; +mod userpass; + +// We use a single binary for integration tests because we want +// them to run in parallel +// https://users.rust-lang.org/t/how-to-execute-the-cargo-test-concurrently/92803/4 diff --git a/tests/oidc.rs b/vaultrs-tests/tests/api_tests/oidc.rs similarity index 72% rename from tests/oidc.rs rename to vaultrs-tests/tests/api_tests/oidc.rs index 04c8c05..da0a908 100644 --- a/tests/oidc.rs +++ b/vaultrs-tests/tests/api_tests/oidc.rs @@ -1,38 +1,35 @@ -#[macro_use] -extern crate tracing; - -mod common; - -use common::{VaultServer, VaultServerHelper}; -use test_log::test; +use tracing::debug; use vaultrs::client::Client; use vaultrs::error::ClientError; +use vaultrs::sys::auth; + +use crate::common::Test; + +#[tokio::test] +async fn test() { + let test = Test::builder().await; + let client = test.client(); + let endpoint = setup(client).await.unwrap(); + + // Test config + config::test_set(client, &endpoint).await; + config::test_read(client, &endpoint).await; -#[test] -fn test() { - let test = common::new_test(); - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let client = server.client(); - let endpoint = setup(&server, &client).await.unwrap(); - - // Test config - crate::config::test_set(&client, &endpoint).await; - crate::config::test_read(&client, &endpoint).await; - - // Test roles - crate::role::test_set(&client, &endpoint).await; - crate::role::test_read(&client, &endpoint).await; - crate::role::test_list(&client, &endpoint).await; - - crate::role::test_delete(&client, &endpoint).await; - }); + // Test roles + role::test_set(client, &endpoint).await; + role::test_read(client, &endpoint).await; + role::test_list(client, &endpoint).await; + + role::test_delete(client, &endpoint).await; } mod config { - use crate::{Client, OIDCEndpoint}; + use vaultrs::client::Client; + use vaultrs::{api::auth::oidc::requests::SetConfigurationRequest, auth::oidc::config}; + use super::OIDCEndpoint; + pub async fn test_read(client: &impl Client, endpoint: &OIDCEndpoint) { let resp = config::read(client, endpoint.path.as_str()).await; @@ -97,15 +94,14 @@ pub struct OIDCEndpoint { pub role: String, } -async fn setup(server: &VaultServer, client: &impl Client) -> Result { +async fn setup(client: &impl Client) -> Result { debug!("setting up OIDC auth engine"); let path = "oidc_test"; let role = "test"; // Mount the OIDC auth engine - server.mount_auth(client, path, "oidc").await?; - + auth::enable(client, path, "oidc", None).await.unwrap(); Ok(OIDCEndpoint { path: path.to_string(), role: role.to_string(), diff --git a/tests/pki.rs b/vaultrs-tests/tests/api_tests/pki.rs similarity index 75% rename from tests/pki.rs rename to vaultrs-tests/tests/api_tests/pki.rs index 9bb915a..df29796 100644 --- a/tests/pki.rs +++ b/vaultrs-tests/tests/api_tests/pki.rs @@ -1,60 +1,53 @@ -#[macro_use] -extern crate tracing; - -mod common; - -use common::{VaultServer, VaultServerHelper}; -use test_log::test; -use vaultrs::api::sys::requests::EnableEngineDataConfigBuilder; +use crate::common::Test; +use tracing::debug; +use vaultrs::api::sys::requests::{EnableEngineDataConfigBuilder, EnableEngineRequest}; use vaultrs::client::Client; use vaultrs::error::ClientError; - -#[test] -fn test() { - let test = common::new_test(); - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let client = server.client(); - let endpoint = setup(&server, &client).await.unwrap(); - - // Test roles - crate::role::test_set(&client, &endpoint).await; - crate::role::test_read(&client, &endpoint).await; - crate::role::test_list(&client, &endpoint).await; - crate::role::test_delete(&client, &endpoint).await; - - // Test CA - crate::role::test_set(&client, &endpoint).await; - crate::cert::ca::test_generate(&client, &endpoint).await; - crate::cert::ca::test_sign(&client, &endpoint).await; - crate::cert::ca::test_sign_intermediate(&client, &endpoint).await; - - crate::cert::ca::test_sign_self_issued(&client, &endpoint).await; - crate::cert::ca::test_delete(&client, &endpoint).await; - crate::cert::ca::test_submit(&client, &endpoint).await; - crate::cert::ca::test_delete(&client, &endpoint).await; - crate::cert::ca::test_generate(&client, &endpoint).await; - - // Test intermediate CA - crate::cert::ca::int::test_generate(&client, &endpoint, &server).await; - crate::cert::ca::int::test_set_signed(&client, &endpoint).await; - - // Test certs - crate::cert::test_generate(&client, &endpoint).await; - crate::cert::test_read(&client, &endpoint).await; - crate::cert::test_list(&client, &endpoint).await; - crate::cert::test_revoke(&client, &endpoint).await; - crate::cert::test_tidy(&client, &endpoint).await; - - // Test CRLs - crate::cert::crl::test_set_config(&client, &endpoint).await; - crate::cert::crl::test_read_config(&client, &endpoint).await; - crate::cert::crl::test_rotate(&client, &endpoint).await; - - // Test URLs - crate::cert::urls::test_set(&client, &endpoint, &server).await; - crate::cert::urls::test_read(&client, &endpoint).await; - }); +use vaultrs::sys::mount; + +#[tokio::test] +async fn test() { + let test = Test::builder().await; + let client = test.client(); + let endpoint = setup(client).await.unwrap(); + + // Test roles + role::test_set(client, &endpoint).await; + role::test_read(client, &endpoint).await; + role::test_list(client, &endpoint).await; + role::test_delete(client, &endpoint).await; + + // Test CA + role::test_set(client, &endpoint).await; + cert::ca::test_generate(client, &endpoint).await; + cert::ca::test_sign(client, &endpoint).await; + cert::ca::test_sign_intermediate(client, &endpoint).await; + + cert::ca::test_sign_self_issued(client, &endpoint).await; + cert::ca::test_delete(client, &endpoint).await; + cert::ca::test_submit(client, &endpoint).await; + cert::ca::test_delete(client, &endpoint).await; + cert::ca::test_generate(client, &endpoint).await; + + // Test intermediate CA + cert::ca::int::test_generate(client, &endpoint).await; + cert::ca::int::test_set_signed(client, &endpoint).await; + + // Test certs + cert::test_generate(client, &endpoint).await; + cert::test_read(client, &endpoint).await; + cert::test_list(client, &endpoint).await; + cert::test_revoke(client, &endpoint).await; + cert::test_tidy(client, &endpoint).await; + + // Test CRLs + cert::crl::test_set_config(client, &endpoint).await; + cert::crl::test_read_config(client, &endpoint).await; + cert::crl::test_rotate(client, &endpoint).await; + + // Test URLs + cert::urls::test_set(client, &endpoint).await; + cert::urls::test_read(client, &endpoint).await; } mod cert { @@ -191,18 +184,13 @@ mod cert { } pub mod int { - use super::super::super::{VaultServer, VaultServerHelper}; use super::{Client, PKIEndpoint}; use vaultrs::pki::cert::ca; use vaultrs::pki::cert::ca::int; + use vaultrs::sys::mount; - pub async fn test_generate( - client: &impl Client, - _: &PKIEndpoint, - server: &VaultServer, - ) { - let resp = server.mount_secret(client, "pki_int", "pki").await; - assert!(resp.is_ok()); + pub async fn test_generate(client: &impl Client, _: &PKIEndpoint) { + mount::enable(client, "pki_int", "pki", None).await.unwrap(); let resp = int::generate(client, "pki_int", "internal", "test-int.com", None).await; @@ -267,7 +255,6 @@ mod cert { } pub mod urls { - use super::super::VaultServer; use super::{Client, PKIEndpoint}; use vaultrs::{api::pki::requests::SetURLsRequest, pki::cert::urls}; @@ -277,9 +264,9 @@ mod cert { assert!(!res.unwrap().issuing_certificates.is_empty()) } - pub async fn test_set(client: &impl Client, endpoint: &PKIEndpoint, server: &VaultServer) { - let issue = format!("{}/v1/{}/ca", server.internal_url(), endpoint.path); - let dist = format!("{}/v1/{}/crl", server.internal_url(), endpoint.path); + pub async fn test_set(client: &impl Client, endpoint: &PKIEndpoint) { + let issue = format!("{}/v1/{}/ca", client.http().base, endpoint.path); + let dist = format!("{}/v1/{}/crl", client.http().base, endpoint.path); let res = urls::set( client, @@ -335,7 +322,7 @@ pub struct PKIEndpoint { pub role: String, } -async fn setup(server: &VaultServer, client: &impl Client) -> Result { +async fn setup(client: &impl Client) -> Result { debug!("setting up PKI auth engine"); let path = "pki_test"; @@ -346,9 +333,14 @@ async fn setup(server: &VaultServer, client: &impl Client) -> Result Result { +async fn setup(client: &impl Client) -> Result { debug!("setting up SSH auth engine"); let path = "ssh_test"; @@ -225,8 +219,7 @@ async fn setup(server: &VaultServer, client: &impl Client) -> Result Test { - let mut test = Test::default(); - let vault_config = serde_json::json!({ - "listener": [ - { - "tcp": { - "address": "0.0.0.0:8300", - "tls_disable": "true" - } - } - ], - "storage": [ - { - "inmem": {} - } - ], - "disable_mlock": true - - }); - let env = HashMap::from([("VAULT_LOCAL_CONFIG".to_string(), vault_config.to_string())]); - let config = vault_prod_container::VaultServerConfig::builder() - .port(PORT) - .version(VERSION.into()) - .env(env) - .build() - .unwrap(); - test.register(config); - test -} - mod tools { use super::Client; use vaultrs::{api::sys::requests::RandomRequestBuilder, sys::tools}; diff --git a/tests/token.rs b/vaultrs-tests/tests/api_tests/token.rs similarity index 70% rename from tests/token.rs rename to vaultrs-tests/tests/api_tests/token.rs index ad4a2f7..9060fa7 100644 --- a/tests/token.rs +++ b/vaultrs-tests/tests/api_tests/token.rs @@ -1,53 +1,45 @@ -#[macro_use] -extern crate tracing; - -mod common; - -use common::{VaultServer, VaultServerHelper}; -use test_log::test; +use crate::common::Test; +use tracing::debug; use vaultrs::client::Client; use vaultrs::{api::token::requests::CreateTokenRequest, error::ClientError, token}; -#[test] -fn test() { - let test = common::new_test(); - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let client = server.client(); - let mut token = setup(&client).await.unwrap(); +#[tokio::test] +async fn test() { + let test = Test::builder().await; + let client = test.client(); + let mut token = setup(client).await.unwrap(); - // Test token roles - crate::role::test_set(&client, "test").await; - crate::role::test_list(&client).await; - crate::role::test_read(&client, "test").await; - crate::role::test_delete(&client, "test").await; + // Test token roles + role::test_set(client, "test").await; + role::test_list(client).await; + role::test_read(client, "test").await; + role::test_delete(client, "test").await; - // Test tidy - test_tidy(&client).await; + // Test tidy + test_tidy(client).await; - // Test creating tokens - test_new(&client).await; - test_new_orphan(&client).await; + // Test creating tokens + test_new(client).await; + test_new_orphan(client).await; - // Test looking up tokens - test_lookup(&client, token.token.as_str()).await; - test_lookup_self(&client).await; - test_lookup_accessor(&client, token.accessor.as_str()).await; + // Test looking up tokens + test_lookup(client, token.token.as_str()).await; + test_lookup_self(client).await; + test_lookup_accessor(client, token.accessor.as_str()).await; - // Test renewing tokens - test_renew(&client, token.token.as_str()).await; - test_renew_self(&client).await; - test_renew_accessor(&client, token.accessor.as_str()).await; + // Test renewing tokens + test_renew(client, token.token.as_str()).await; + test_renew_self(client).await; + test_renew_accessor(client, token.accessor.as_str()).await; - // Test revoking tokens - test_revoke(&client, token.token.as_str()).await; - token = setup(&client).await.unwrap(); - test_revoke_accessor(&client, token.accessor.as_str()).await; - token = setup(&client).await.unwrap(); - test_revoke_orphan(&client, token.token.as_str()).await; + // Test revoking tokens + test_revoke(client, token.token.as_str()).await; + token = setup(client).await.unwrap(); + test_revoke_accessor(client, token.accessor.as_str()).await; + token = setup(client).await.unwrap(); + test_revoke_orphan(client, token.token.as_str()).await; - test_revoke_self(&client).await; - }); + test_revoke_self(client).await; } pub async fn test_lookup(client: &impl Client, token: &str) { @@ -120,9 +112,9 @@ pub async fn test_tidy(client: &impl Client) { mod role { use vaultrs::api::token::requests::SetTokenRoleRequest; + use vaultrs::token::role; use super::Client; - use crate::token::role; pub async fn test_delete(client: &impl Client, role_name: &str) { let resp = role::delete(client, role_name).await; diff --git a/tests/transit.rs b/vaultrs-tests/tests/api_tests/transit.rs similarity index 70% rename from tests/transit.rs rename to vaultrs-tests/tests/api_tests/transit.rs index e2e0870..ad91e3e 100644 --- a/tests/transit.rs +++ b/vaultrs-tests/tests/api_tests/transit.rs @@ -1,42 +1,36 @@ -#[macro_use] -extern crate tracing; - -mod common; - use base64::{engine::general_purpose, Engine as _}; -use common::{VaultServer, VaultServerHelper}; use data_encoding::HEXLOWER; use sha2::{Digest, Sha256}; -use test_log::test; -use vaultrs::{client::VaultClient, error::ClientError}; - -#[test] -fn test() { - let test = common::new_test(); - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let endpoint = TransitEndpoint::setup(&server).await.unwrap(); - - key::test_create(&endpoint).await; - key::test_read(&endpoint).await; - key::test_list(&endpoint).await; - key::test_rotate(&endpoint).await; - key::test_update(&endpoint).await; - key::test_delete(&endpoint).await; - key::test_export(&endpoint).await; - key::test_backup_and_restore(&endpoint).await; - key::test_trim(&endpoint).await; - - data::test_encrypt_and_rewrap_and_decrypt(&endpoint).await; - data::test_sign_and_verify(&endpoint).await; - - generate::test_data_key(&endpoint).await; - generate::test_random_bytes(&endpoint).await; - generate::test_hash(&endpoint).await; - generate::test_hmac(&endpoint).await; - - cache::test_configure_and_read(&endpoint).await - }); +use tracing::debug; +use vaultrs::{client::VaultClient, error::ClientError, sys::mount}; + +use crate::common::Test; + +#[tokio::test] +async fn test() { + let test = Test::builder().await; + let client = test.client(); + let endpoint = TransitEndpoint::setup(client).await.unwrap(); + + key::test_create(&endpoint).await; + key::test_read(&endpoint).await; + key::test_list(&endpoint).await; + key::test_rotate(&endpoint).await; + key::test_update(&endpoint).await; + key::test_delete(&endpoint).await; + key::test_export(&endpoint).await; + key::test_backup_and_restore(&endpoint).await; + key::test_trim(&endpoint).await; + + data::test_encrypt_and_rewrap_and_decrypt(&endpoint).await; + data::test_sign_and_verify(&endpoint).await; + + generate::test_data_key(&endpoint).await; + generate::test_random_bytes(&endpoint).await; + generate::test_hash(&endpoint).await; + generate::test_hmac(&endpoint).await; + + cache::test_configure_and_read(&endpoint).await } mod key { @@ -48,12 +42,12 @@ mod key { use vaultrs::api::transit::KeyType; use vaultrs::transit::key; - pub async fn test_create(endpoint: &TransitEndpoint) { - let resp = key::create(&endpoint.client, &endpoint.path, &endpoint.keys.basic, None).await; + pub async fn test_create(endpoint: &TransitEndpoint<'_>) { + let resp = key::create(endpoint.client, &endpoint.path, &endpoint.keys.basic, None).await; assert!(resp.is_ok()); let resp = key::create( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.export, Some( @@ -68,17 +62,11 @@ mod key { .await; assert!(resp.is_ok()); - let resp = key::create( - &endpoint.client, - &endpoint.path, - &endpoint.keys.delete, - None, - ) - .await; + let resp = key::create(endpoint.client, &endpoint.path, &endpoint.keys.delete, None).await; assert!(resp.is_ok()); let resp = key::create( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.signing, Some( @@ -91,7 +79,7 @@ mod key { assert!(resp.is_ok()); let resp = key::create( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.asymmetric, Some( @@ -105,24 +93,24 @@ mod key { assert!(resp.is_ok()); } - pub async fn test_read(endpoint: &TransitEndpoint) { - let resp = key::read(&endpoint.client, &endpoint.path, &endpoint.keys.basic) + pub async fn test_read(endpoint: &TransitEndpoint<'_>) { + let resp = key::read(endpoint.client, &endpoint.path, &endpoint.keys.basic) .await .unwrap(); assert_eq!(&resp.name, &endpoint.keys.basic); - let resp = key::read(&endpoint.client, &endpoint.path, &endpoint.keys.export) + let resp = key::read(endpoint.client, &endpoint.path, &endpoint.keys.export) .await .unwrap(); assert!(&resp.exportable); - let resp = key::read(&endpoint.client, &endpoint.path, &endpoint.keys.delete) + let resp = key::read(endpoint.client, &endpoint.path, &endpoint.keys.delete) .await .unwrap(); // requires config update first assert!(!&resp.deletion_allowed); - let resp = key::read(&endpoint.client, &endpoint.path, &endpoint.keys.asymmetric) + let resp = key::read(endpoint.client, &endpoint.path, &endpoint.keys.asymmetric) .await .unwrap(); assert_eq!(&resp.name, &endpoint.keys.asymmetric); @@ -132,7 +120,7 @@ mod key { panic!("Key must be asymmetric") } vaultrs::api::transit::responses::ReadKeyData::Asymmetric(keys) => { - for (_key_id, key_metadata) in keys { + for key_metadata in keys.values() { let _datetime: chrono::DateTime = key_metadata .creation_time .parse() @@ -148,25 +136,25 @@ mod key { } } - pub async fn test_list(endpoint: &TransitEndpoint) { - let resp = key::list(&endpoint.client, &endpoint.path).await.unwrap(); + pub async fn test_list(endpoint: &TransitEndpoint<'_>) { + let resp = key::list(endpoint.client, &endpoint.path).await.unwrap(); assert!(&resp.keys.contains(&endpoint.keys.basic)); assert!(&resp.keys.contains(&endpoint.keys.export)); } - pub async fn test_rotate(endpoint: &TransitEndpoint) { + pub async fn test_rotate(endpoint: &TransitEndpoint<'_>) { // key version 2 - let resp = key::rotate(&endpoint.client, &endpoint.path, &endpoint.keys.export).await; + let resp = key::rotate(endpoint.client, &endpoint.path, &endpoint.keys.export).await; assert!(resp.is_ok()); // key version 3 - let resp = key::rotate(&endpoint.client, &endpoint.path, &endpoint.keys.export).await; + let resp = key::rotate(endpoint.client, &endpoint.path, &endpoint.keys.export).await; assert!(resp.is_ok()); } - pub async fn test_update(endpoint: &TransitEndpoint) { + pub async fn test_update(endpoint: &TransitEndpoint<'_>) { let resp = key::update( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.export, Some( @@ -179,7 +167,7 @@ mod key { assert!(resp.is_ok()); let resp = key::update( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.delete, Some(UpdateKeyConfigurationRequest::builder().deletion_allowed(true)), @@ -188,17 +176,17 @@ mod key { assert!(resp.is_ok()); } - pub async fn test_delete(endpoint: &TransitEndpoint) { - let resp = key::delete(&endpoint.client, &endpoint.path, &endpoint.keys.basic).await; + pub async fn test_delete(endpoint: &TransitEndpoint<'_>) { + let resp = key::delete(endpoint.client, &endpoint.path, &endpoint.keys.basic).await; assert!(resp.is_err()); - let resp = key::delete(&endpoint.client, &endpoint.path, &endpoint.keys.delete).await; + let resp = key::delete(endpoint.client, &endpoint.path, &endpoint.keys.delete).await; assert!(resp.is_ok()); } - pub async fn test_export(endpoint: &TransitEndpoint) { + pub async fn test_export(endpoint: &TransitEndpoint<'_>) { let resp = key::export( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.basic, ExportKeyType::EncryptionKey, @@ -208,7 +196,7 @@ mod key { assert!(resp.is_err()); let latest = key::export( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.export, ExportKeyType::EncryptionKey, @@ -218,7 +206,7 @@ mod key { .unwrap(); let resp = key::export( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.export, ExportKeyType::EncryptionKey, @@ -231,7 +219,7 @@ mod key { assert_eq!(&resp.keys, &latest.keys); let resp = key::export( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.export, ExportKeyType::EncryptionKey, @@ -244,26 +232,20 @@ mod key { assert!(resp.keys.contains_key("3")); } - pub async fn test_backup_and_restore(endpoint: &TransitEndpoint) { - let resp = key::backup(&endpoint.client, &endpoint.path, &endpoint.keys.basic).await; + pub async fn test_backup_and_restore(endpoint: &TransitEndpoint<'_>) { + let resp = key::backup(endpoint.client, &endpoint.path, &endpoint.keys.basic).await; assert!(resp.is_err()); - let backup = key::backup(&endpoint.client, &endpoint.path, &endpoint.keys.export) + let backup = key::backup(endpoint.client, &endpoint.path, &endpoint.keys.export) .await .unwrap() .backup; - let resp = key::restore( - &endpoint.client, - &endpoint.path, - &endpoint.keys.export, - None, - ) - .await; + let resp = key::restore(endpoint.client, &endpoint.path, &endpoint.keys.export, None).await; assert!(resp.is_err()); let resp = key::restore( - &endpoint.client, + endpoint.client, &endpoint.path, &backup, Some(RestoreKeyRequest::builder().force(true)), @@ -272,8 +254,8 @@ mod key { assert!(resp.is_ok()); } - pub async fn test_trim(endpoint: &TransitEndpoint) { - let resp = key::trim(&endpoint.client, &endpoint.path, &endpoint.keys.export, 2).await; + pub async fn test_trim(endpoint: &TransitEndpoint<'_>) { + let resp = key::trim(endpoint.client, &endpoint.path, &endpoint.keys.export, 2).await; assert!(resp.is_ok()); } } @@ -287,9 +269,9 @@ mod data { use vaultrs::api::transit::SignatureAlgorithm; use vaultrs::transit::{data, key}; - pub async fn test_encrypt_and_rewrap_and_decrypt(endpoint: &TransitEndpoint) { + pub async fn test_encrypt_and_rewrap_and_decrypt(endpoint: &TransitEndpoint<'_>) { let encrypted = data::encrypt( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.export, &endpoint.data.secret, @@ -299,11 +281,11 @@ mod data { .unwrap(); // key version 4 - let resp = key::rotate(&endpoint.client, &endpoint.path, &endpoint.keys.export).await; + let resp = key::rotate(endpoint.client, &endpoint.path, &endpoint.keys.export).await; assert!(resp.is_ok()); let rewrapped = data::rewrap( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.export, &encrypted.ciphertext, @@ -314,7 +296,7 @@ mod data { assert!(encrypted.ciphertext != rewrapped.ciphertext); let decrypted = data::decrypt( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.export, &encrypted.ciphertext, @@ -325,7 +307,7 @@ mod data { assert_eq!(&decrypted.plaintext, &endpoint.data.secret); let decrypted = data::decrypt( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.export, &rewrapped.ciphertext, @@ -336,9 +318,9 @@ mod data { assert_eq!(&decrypted.plaintext, &endpoint.data.secret); } - pub async fn test_sign_and_verify(endpoint: &TransitEndpoint) { + pub async fn test_sign_and_verify(endpoint: &TransitEndpoint<'_>) { let signed = data::sign( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.signing, &endpoint.data.secret, @@ -352,7 +334,7 @@ mod data { .unwrap(); let verified = data::verify( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.signing, &endpoint.data.secret, @@ -378,9 +360,9 @@ mod generate { use vaultrs::api::transit::{HashAlgorithm, OutputFormat}; use vaultrs::transit::generate; - pub async fn test_data_key(endpoint: &TransitEndpoint) { + pub async fn test_data_key(endpoint: &TransitEndpoint<'_>) { let resp = generate::data_key( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.basic, DataKeyType::Plaintext, @@ -391,9 +373,9 @@ mod generate { assert!(&resp.plaintext.is_some()) } - pub async fn test_random_bytes(endpoint: &TransitEndpoint) { + pub async fn test_random_bytes(endpoint: &TransitEndpoint<'_>) { let resp = generate::random_bytes( - &endpoint.client, + endpoint.client, &endpoint.path, OutputFormat::Hex, RandomBytesSource::Platform, @@ -404,9 +386,9 @@ mod generate { assert_eq!(resp.random_bytes.len(), 20) } - pub async fn test_hash(endpoint: &TransitEndpoint) { + pub async fn test_hash(endpoint: &TransitEndpoint<'_>) { let resp = generate::hash( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.data.context, Some( @@ -420,9 +402,9 @@ mod generate { assert_eq!(resp.sum, endpoint.data.context_shasum_hex); } - pub async fn test_hmac(endpoint: &TransitEndpoint) { + pub async fn test_hmac(endpoint: &TransitEndpoint<'_>) { let resp = generate::hmac( - &endpoint.client, + endpoint.client, &endpoint.path, &endpoint.keys.basic, &endpoint.data.context, @@ -438,16 +420,16 @@ mod cache { use vaultrs::api::transit::requests::ConfigureCacheRequest; use vaultrs::transit::cache; - pub async fn test_configure_and_read(endpoint: &TransitEndpoint) { + pub async fn test_configure_and_read(endpoint: &TransitEndpoint<'_>) { let resp = cache::configure( - &endpoint.client, + endpoint.client, &endpoint.path, Some(ConfigureCacheRequest::builder().size(123u64)), ) .await; assert!(resp.is_ok()); - let resp = cache::read(&endpoint.client, &endpoint.path).await.unwrap(); + let resp = cache::read(endpoint.client, &endpoint.path).await.unwrap(); assert_eq!(resp.size, 123); } } @@ -479,19 +461,19 @@ impl TestData { } } -pub struct TransitEndpoint { - pub client: VaultClient, +pub struct TransitEndpoint<'a> { + pub client: &'a VaultClient, pub path: String, pub keys: TestKeys, pub data: TestData, } -impl TransitEndpoint { - async fn setup(server: &VaultServer) -> Result { +impl<'a> TransitEndpoint<'a> { + async fn setup(client: &'a VaultClient) -> Result { debug!("setting up transit secrets engine"); let endpoint = TransitEndpoint { - client: server.client(), + client, path: "transit-test".into(), keys: TestKeys { basic: "basic-key".into(), @@ -503,9 +485,9 @@ impl TransitEndpoint { data: TestData::new("test-context", "super secret data"), }; - server - .mount_secret(&endpoint.client, &endpoint.path, "transit") - .await?; + mount::enable(endpoint.client, &endpoint.path, "transit", None) + .await + .unwrap(); Ok(endpoint) } diff --git a/tests/userpass.rs b/vaultrs-tests/tests/api_tests/userpass.rs similarity index 71% rename from tests/userpass.rs rename to vaultrs-tests/tests/api_tests/userpass.rs index 33b283d..03bf6f3 100644 --- a/tests/userpass.rs +++ b/vaultrs-tests/tests/api_tests/userpass.rs @@ -1,35 +1,28 @@ -#[macro_use] -extern crate tracing; - -mod common; - -use common::{VaultServer, VaultServerHelper}; -use test_log::test; +use crate::common::Test; +use tracing::debug; use vaultrs::auth::userpass; use vaultrs::client::Client; use vaultrs::error::ClientError; - -#[test] -fn test() { - let test = common::new_test(); - test.run(|instance| async move { - let server: VaultServer = instance.server(); - let client = server.client(); - let endpoint = setup(&server, &client).await.unwrap(); - - // Test user - user::test_set(&client, &endpoint).await; - user::test_read(&client, &endpoint).await; - user::test_list(&client, &endpoint).await; - user::test_update_policies(&client, &endpoint).await; - - // Test login - test_login(&client, &endpoint).await; - - // Test update password and delete - user::test_update_password(&client, &endpoint).await; - user::test_delete(&client, &endpoint).await; - }); +use vaultrs::sys::auth; + +#[tokio::test] +async fn test() { + let test = Test::builder().await; + let client = test.client(); + let endpoint = setup(client).await.unwrap(); + + // Test user + user::test_set(client, &endpoint).await; + user::test_read(client, &endpoint).await; + user::test_list(client, &endpoint).await; + user::test_update_policies(client, &endpoint).await; + + // Test login + test_login(client, &endpoint).await; + + // Test update password and delete + user::test_update_password(client, &endpoint).await; + user::test_delete(client, &endpoint).await; } pub async fn test_login(client: &impl Client, endpoint: &UserPassEndpoint) { @@ -104,10 +97,7 @@ pub struct UserPassEndpoint { pub password: String, } -async fn setup( - server: &VaultServer, - client: &impl Client, -) -> Result { +async fn setup(client: &impl Client) -> Result { debug!("setting up UserPass auth engine"); let path = "userpass_test"; @@ -115,7 +105,7 @@ async fn setup( let password = "This1sAT3st!"; // Mount the UserPass auth engine - server.mount_auth(client, path, "userpass").await?; + auth::enable(client, path, "userpass", None).await.unwrap(); Ok(UserPassEndpoint { path: path.to_string(), diff --git a/tests/files/aws.crt b/vaultrs-tests/tests/files/aws.crt similarity index 100% rename from tests/files/aws.crt rename to vaultrs-tests/tests/files/aws.crt diff --git a/tests/files/ca.pem b/vaultrs-tests/tests/files/ca.pem similarity index 100% rename from tests/files/ca.pem rename to vaultrs-tests/tests/files/ca.pem diff --git a/tests/files/csr.pem b/vaultrs-tests/tests/files/csr.pem similarity index 100% rename from tests/files/csr.pem rename to vaultrs-tests/tests/files/csr.pem diff --git a/tests/files/id_rsa b/vaultrs-tests/tests/files/id_rsa similarity index 100% rename from tests/files/id_rsa rename to vaultrs-tests/tests/files/id_rsa diff --git a/tests/files/id_rsa.pub b/vaultrs-tests/tests/files/id_rsa.pub similarity index 100% rename from tests/files/id_rsa.pub rename to vaultrs-tests/tests/files/id_rsa.pub diff --git a/tests/files/kubernetes/ca.crt b/vaultrs-tests/tests/files/kubernetes/ca.crt similarity index 100% rename from tests/files/kubernetes/ca.crt rename to vaultrs-tests/tests/files/kubernetes/ca.crt diff --git a/tests/files/kubernetes/ca.key b/vaultrs-tests/tests/files/kubernetes/ca.key similarity index 100% rename from tests/files/kubernetes/ca.key rename to vaultrs-tests/tests/files/kubernetes/ca.key diff --git a/tests/files/root_ca.crt b/vaultrs-tests/tests/files/root_ca.crt similarity index 100% rename from tests/files/root_ca.crt rename to vaultrs-tests/tests/files/root_ca.crt