Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for login via certificate #76

Merged
merged 2 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
components: rustfmt, clippy
- name: Use cache
- name: Use cache
uses: Swatinem/rust-cache@v1
with:
working-directory: vaultrs-login/
Expand Down Expand Up @@ -87,13 +87,19 @@ jobs:
with:
command: test
args: --no-run
- name: Run cargo test
- 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
runs-on: ubuntu-latest
Expand Down Expand Up @@ -136,4 +142,4 @@ jobs:
override: true
- uses: katyo/publish-crates@v1
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ tracing-subscriber = { version = "0.3.16", default-features = false, features =
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"
1 change: 1 addition & 0 deletions src/api/auth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod approle;
pub mod aws;
pub mod cert;
pub mod kubernetes;
pub mod oidc;
pub mod userpass;
2 changes: 2 additions & 0 deletions src/api/auth/cert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod requests;
pub mod responses;
126 changes: 126 additions & 0 deletions src/api/auth/cert/requests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use rustify_derive::Endpoint;

use super::responses::{ListCaCertificateRoleResponse, ReadCaCertificateRoleResponse};

/// ## Create/Update CA certificate role
/// Create or update a CA certificate role.
///
/// * Path: /auth/{self.mount}/certs/{self.name}
/// * Method: POST
/// * Response: N/A
/// * Reference: https://developer.hashicorp.com/vault/api-docs/auth/cert#create-ca-certificate-role
#[derive(Builder, Debug, Default, Endpoint)]
#[endpoint(
path = "/auth/{self.mount}/certs/{self.name}",
method = "POST",
builder = "true"
)]
#[builder(setter(into, strip_option), default)]
pub struct CreateCaCertificateRoleRequest {
#[endpoint(skip)]
pub mount: String,
#[endpoint(skip)]
pub name: String,
pub certificate: String,
pub allowed_common_names: Option<Vec<String>>,
pub allowed_dns_sans: Option<Vec<String>>,
pub allowed_email_sans: Option<Vec<String>>,
pub allowed_uri_sans: Option<Vec<String>>,
pub allowed_organizational_units: Option<Vec<String>>,
pub required_extensions: Option<Vec<String>>,
pub allowed_metadata_extensions: Option<Vec<String>>,
pub ocsp_enabled: Option<bool>,
pub ocsp_ca_certificates: Option<String>,
pub ocsp_servers_override: Option<Vec<String>>,
pub ocsp_fail_open: Option<bool>,
pub ocsp_query_all_servers: Option<bool>,
pub display_name: Option<String>,
pub token_ttl: Option<String>,
pub token_max_ttl: Option<String>,
pub token_policies: Option<Vec<String>>,
pub token_bound_cidrs: Option<Vec<String>>,
pub token_explicit_max_ttl: Option<String>,
pub token_no_default_policy: Option<bool>,
pub token_num_uses: Option<u64>,
pub token_period: Option<String>,
pub token_type: Option<String>,
}

/// ## Read CA certificate role
/// Reads the properties of an existing CA certificate role.
///
/// * Path: /auth/{self.mount}/certs/{self.name}
/// * Method: GET
/// * Response: [ReadCaCertificateRoleResponse]
/// * Reference: https://developer.hashicorp.com/vault/api-docs/auth/cert#read-ca-certificate-role
#[derive(Builder, Debug, Default, Endpoint)]
#[endpoint(
path = "/auth/{self.mount}/certs/{self.name}",
response = "ReadCaCertificateRoleResponse",
builder = "true"
)]
#[builder(setter(into, strip_option), default)]
pub struct ReadCaCertificateRoleRequest {
#[endpoint(skip)]
pub mount: String,
#[endpoint(skip)]
pub name: String,
}

/// ## Delete CA certificate role
/// This endpoint deletes the CA certificate role.
///
/// * Path: /auth/{self.mount}/certs/{self.name}
/// * Method: DELETE
/// * Response: N/A
/// * Reference: https://developer.hashicorp.com/vault/api-docs/auth/cert#delete-certificate-role
#[derive(Builder, Debug, Default, Endpoint)]
#[endpoint(
path = "/auth/{self.mount}/certs/{self.name}",
method = "DELETE",
builder = "true"
)]
#[builder(setter(into, strip_option), default)]
pub struct DeleteCaCertificateRoleRequest {
#[endpoint(skip)]
pub mount: String,
#[endpoint(skip)]
pub name: String,
}

/// ## List CA certificate role
/// List available CA certificate roles.
///
/// * Path: /auth/{self.mount}/certs
/// * Method: LIST
/// * Response: [ListCaCertificateRoleResponse]
/// * Reference: https://developer.hashicorp.com/vault/api-docs/auth/cert#list-certificate-roles
#[derive(Builder, Debug, Default, Endpoint)]
#[endpoint(
path = "/auth/{self.mount}/certs",
method = "LIST",
response = "ListCaCertificateRoleResponse",
builder = "true"
)]
#[builder(setter(into, strip_option), default)]
pub struct ListCaCertificateRoleRequest {
#[endpoint(skip)]
pub mount: String,
}

/// ## Login
/// Login with the TLS certificate method and authenticate against only the named
/// certificate role.
///
/// * Path: /auth/{self.mount}/login
/// * Method: POST
/// * Response: N/A
/// * Reference: https://developer.hashicorp.com/vault/api-docs/auth/cert#login-with-tls-certificate-method
#[derive(Builder, Debug, Default, Endpoint)]
#[endpoint(path = "/auth/{self.mount}/login", method = "POST", builder = "true")]
#[builder(setter(into, strip_option), default)]
pub struct LoginRequest {
#[endpoint(skip)]
pub mount: String,
pub cert_name: String,
}
32 changes: 32 additions & 0 deletions src/api/auth/cert/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use serde::{Deserialize, Serialize};

/// Response from executing
/// [ReadCaCertificateRoleRequest][crate::api::auth::cert::requests::ReadCaCertificateRoleRequest]
#[derive(Deserialize, Debug, Serialize)]
pub struct ReadCaCertificateRoleResponse {
pub allowed_common_names: Option<Vec<String>>,
pub allowed_dns_sans: Option<Vec<String>>,
pub allowed_email_sans: Option<Vec<String>>,
pub allowed_metadata_extensions: Option<Vec<String>>,
pub allowed_organizational_units: Option<Vec<String>>,
pub allowed_uri_sans: Option<Vec<String>>,
pub certificate: String,
pub display_name: String,
pub required_extensions: Option<Vec<String>>,
pub token_bound_cidrs: Vec<String>,
pub token_explicit_max_ttl: u64,
pub token_max_ttl: u64,
pub token_no_default_policy: bool,
pub token_num_uses: u64,
pub token_period: u64,
pub token_policies: Vec<String>,
pub token_ttl: u64,
pub token_type: String,
}

/// Response from executing
/// [ListCaCertificateRoleRequest][crate::api::auth::cert::requests::ListCaCertificateRoleRequest]
#[derive(Deserialize, Debug, Serialize)]
pub struct ListCaCertificateRoleResponse {
pub keys: Vec<String>,
}
1 change: 1 addition & 0 deletions src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod approle;
pub mod aws;
pub mod cert;
pub mod kubernetes;
pub mod oidc;
pub mod userpass;
107 changes: 107 additions & 0 deletions src/auth/cert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use crate::{
api::{self, auth::cert::requests::LoginRequest, AuthInfo},
client::Client,
error::ClientError,
};

// Fetch a token with policies corresponding to the certificate.
//
// See [LoginRequest]
#[instrument(skip(client), err)]
pub async fn login(
client: &impl Client,
mount: &str,
cert_name: &str,
) -> Result<AuthInfo, ClientError> {
let endpoint = LoginRequest::builder()
.mount(mount)
.cert_name(cert_name)
.build()
.unwrap();
api::auth(client, endpoint).await
}

pub mod ca_cert_role {
use crate::{
api::{
self,
auth::cert::{
requests::{
CreateCaCertificateRoleRequest, CreateCaCertificateRoleRequestBuilder,
DeleteCaCertificateRoleRequest, ListCaCertificateRoleRequest,
ReadCaCertificateRoleRequest,
},
responses::{ListCaCertificateRoleResponse, ReadCaCertificateRoleResponse},
},
},
client::Client,
error::ClientError,
};

/// Deletes a CA certificate role.
///
/// See [DeleteCaCertificateRoleRequest]
#[instrument(skip(client), err)]
pub async fn delete(client: &impl Client, mount: &str, name: &str) -> Result<(), ClientError> {
let endpoint = DeleteCaCertificateRoleRequest::builder()
.mount(mount)
.name(name)
.build()
.unwrap();
api::exec_with_empty(client, endpoint).await
}

/// Lists CA certificate roles.
///
/// See [ListCaCertificateRoleRequest]
#[instrument(skip(client), err)]
pub async fn list(
client: &impl Client,
mount: &str,
) -> Result<ListCaCertificateRoleResponse, ClientError> {
let endpoint = ListCaCertificateRoleRequest::builder()
.mount(mount)
.build()
.unwrap();
api::exec_with_result(client, endpoint).await
}

/// Reads information about a CA certificate role.
///
/// See [ReadCaCertificateRoleRequest]
#[instrument(skip(client), err)]
pub async fn read(
client: &impl Client,
mount: &str,
username: &str,
) -> Result<ReadCaCertificateRoleResponse, ClientError> {
let endpoint = ReadCaCertificateRoleRequest::builder()
.mount(mount)
.name(username)
.build()
.unwrap();
api::exec_with_result(client, endpoint).await
}

/// Creates a new CA certificate role
///
/// See [CreateCaCertificateRoleRequest]
#[instrument(skip(client, opts), err)]
pub async fn set(
client: &impl Client,
mount: &str,
name: &str,
certificate: &str,
opts: Option<&mut CreateCaCertificateRoleRequestBuilder>,
) -> Result<(), ClientError> {
let mut t = CreateCaCertificateRoleRequest::builder();
let endpoint = opts
.unwrap_or(&mut t)
.mount(mount)
.name(name)
.certificate(certificate)
.build()
.unwrap();
api::exec_with_empty(client, endpoint).await
}
}
Loading
Loading