Compare commits

..

No commits in common. "c1f76f4c8b0c11ab0fef2a553a0717f450ce9761" and "c65ae95b4365ce3eace88dffb679b261e8de7ff6" have entirely different histories.

19 changed files with 235 additions and 2709 deletions

3
.gitignore vendored
View file

@ -2,6 +2,7 @@
/target/ /target/
**/*.rs.bk **/*.rs.bk
*.pdb *.pdb
Cargo.lock
# Generated by Cargo # Generated by Cargo
.cargo/ .cargo/
@ -31,4 +32,4 @@ result
result-* result-*
# macOS specific files # macOS specific files
.DS_Store .DS_Store

2026
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,5 @@
{ {
"nodes": { "nodes": {
"crane": {
"locked": {
"lastModified": 1742394900,
"narHash": "sha256-vVOAp9ahvnU+fQoKd4SEXB2JG2wbENkpqcwlkIXgUC0=",
"owner": "ipetkov",
"repo": "crane",
"rev": "70947c1908108c0c551ddfd73d4f750ff2ea67cd",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"
@ -35,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1742751704, "lastModified": 1742268799,
"narHash": "sha256-rBfc+H1dDBUQ2mgVITMGBPI1PGuCznf9rcWX/XIULyE=", "narHash": "sha256-IhnK4LhkBlf14/F8THvUy3xi/TxSQkp9hikfDZRD4Ic=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "f0946fa5f1fb876a9dc2e1850d9d3a4e3f914092", "rev": "da044451c6a70518db5b730fe277b70f494188f1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -67,7 +52,6 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"crane": "crane",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
@ -78,11 +62,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1742783666, "lastModified": 1742437918,
"narHash": "sha256-IwdSl51NL6V0f+mYXZR0UTKaGleOsk9zV3l6kt5SUWw=", "narHash": "sha256-Vflb6KJVDikFcM9E231mRN88uk4+jo7BWtaaQMifthI=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "60766d63c227d576510ecfb5edd3a687d56f6bc7", "rev": "f03085549609e49c7bcbbee86a1949057d087199",
"type": "github" "type": "github"
}, },
"original": { "original": {

130
flake.nix
View file

@ -3,105 +3,41 @@
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.url = "github:oxalica/rust-overlay";
crane.url = "github:ipetkov/crane";
}; };
outputs = outputs = { self, nixpkgs, flake-utils, rust-overlay }:
{ flake-utils.lib.eachDefaultSystem
self, (system:
nixpkgs, let
flake-utils, overlays = [
rust-overlay, rust-overlay.overlays.default
crane,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
overlays = [
rust-overlay.overlays.default
];
pkgs = import nixpkgs {
inherit system overlays;
config = {
allowUnfree = true;
};
};
# Import rust setup
rustSetup = import ./nix/rust-setup.nix { inherit pkgs crane; };
inherit (rustSetup) rustVersion rustPlatform craneLib;
src = craneLib.cleanCargoSource ./.;
commonArgs = {
inherit src;
# Add any build inputs needed
buildInputs = with pkgs; [
openssl
pkg-config
]; ];
pkgs = import nixpkgs {
# Add any native build inputs inherit system overlays;
nativeBuildInputs = with pkgs; [ config = {
rustPlatform.bindgenHook allowUnfree = true;
pkg-config };
];
checkType = "debug";
env = {
OPENSSL_NO_VENDOR = "1";
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
}; };
}; in
with pkgs;
{
devShells.default = mkShell {
env = {
OPENSSL_NO_VENDOR = "1";
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
};
cargoArtifacts = craneLib.buildDepsOnly commonArgs; packages = [
individualCrateArgs = commonArgs // { pkg-config
inherit cargoArtifacts; vault
inherit (craneLib.crateNameFromCargoToml { inherit src; }) version; (rust-bin.stable.latest.default.override {
# NB: we disable tests since we'll run them all via cargo-nextest extensions = [ "rust-src" ];
doCheck = false; })
}; rustc
cargo
# Import vault-hier package rustfmt
vault-hier = import ./nix/packages/vault-hier.nix { clippy
inherit ];
pkgs };
craneLib }
individualCrateArgs );
;
};
# Import devshell
devshell_with_src = import ./nix/devshell.nix {
inherit pkgs vault-hier rustVersion;
};
in
{
# Import checks
checks = import ./nix/checks {
inherit
craneLib
src
commonArgs
cargoArtifacts
vault-hier
pkgs
;
};
# Add packages output
packages = {
inherit vault-hier;
default = vault-hier;
};
devShells = {
inherit devshell_with_src;
default = devshell_with_src;
};
# Add formatter for `nix fmt`
formatter = pkgs.nixfmt-rfc-style;
}
);
} }

View file

@ -1,13 +0,0 @@
{
craneLib,
commonArgs,
cargoArtifacts,
}:
craneLib.cargoClippy (
commonArgs
// {
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
}
)

View file

@ -1,30 +0,0 @@
{
craneLib,
src,
commonArgs,
cargoArtifacts,
vault-hier,
pkgs,
}:
{
inherit vault-hier;
my-workspace-clippy = import ./clippy.nix {
inherit craneLib commonArgs cargoArtifacts;
};
my-workspace-doc = import ./doc.nix {
inherit craneLib commonArgs cargoArtifacts;
};
# Check formatting
my-workspace-fmt = import ./fmt.nix {
inherit craneLib src;
};
my-workspace-toml-fmt = import ./toml-fmt.nix {
inherit craneLib src;
lib = pkgs.lib;
};
}

View file

@ -1,12 +0,0 @@
{
craneLib,
commonArgs,
cargoArtifacts,
}:
craneLib.cargoDoc (
commonArgs
// {
inherit cargoArtifacts;
}
)

View file

@ -1,5 +0,0 @@
{ craneLib, src }:
craneLib.cargoFmt {
inherit src;
}

View file

@ -1,11 +0,0 @@
{
craneLib,
src,
lib,
}:
craneLib.taploFmt {
src = lib.sources.sourceFilesBySuffices src [ ".toml" ];
# taplo arguments can be further customized below as needed
# taploExtraArgs = "--config ./taplo.toml";
}

View file

@ -1,26 +0,0 @@
{
pkgs,
vault-hier,
rustVersion,
}:
let
toolchain_with_src = (
rustVersion.override {
extensions = [
"rustfmt"
"clippy"
"rust-src"
];
}
);
in
pkgs.mkShell {
packages = with pkgs; [
vault-hier # Add the vault-hier package to the dev shell
toolchain_with_src # Add the custom Rust toolchain with source code to the dev shell
];
nativeBuildInputs = [
vault-hier
];
}

View file

@ -1,12 +0,0 @@
{
pkgs,
craneLib,
individualCrateArgs,
}:
craneLib.buildPackage (
individualCrateArgs
// {
pname = "vault-hier";
cargoExtraArgs = "-p vault-hier";
}
)

View file

@ -1,13 +0,0 @@
{ pkgs, crane }:
let
rustVersion = pkgs.rust-bin.fromRustupToolchainFile (../rust-toolchain.toml);
rustPlatform = pkgs.makeRustPlatform {
cargo = rustVersion;
rustc = rustVersion;
};
craneLib = (crane.mkLib pkgs).overrideToolchain rustVersion;
in
{
inherit rustVersion rustPlatform craneLib;
}

View file

@ -1,3 +0,0 @@
[toolchain]
channel = "1.85.1"
components = ["rustfmt", "clippy", "rust-src"]

View file

@ -1,14 +1,15 @@
use anyhow::Result; use anyhow::Result;
use axum::{ use axum::{
Json, Router, Server,
extract::{Multipart, Path, State}, extract::{Multipart, Path, State},
http::StatusCode, http::StatusCode,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
routing::{get, post}, routing::{get, post},
Json, Router,
Server,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
use tracing::{debug, error, info, instrument}; use tracing::{info, error, debug, instrument};
use crate::document_service::{Document, DocumentService, SignatureVerification}; use crate::document_service::{Document, DocumentService, SignatureVerification};
use crate::vault_setup::VaultClient; use crate::vault_setup::VaultClient;
@ -67,7 +68,11 @@ where
// Start the API server // Start the API server
#[instrument(skip(vault_addr, root_token))] #[instrument(skip(vault_addr, root_token))]
pub async fn start_api(vault_addr: &str, root_token: &str, api_port: u16) -> Result<()> { pub async fn start_api(
vault_addr: &str,
root_token: &str,
api_port: u16,
) -> Result<()> {
info!("Starting API server on port {}...", api_port); info!("Starting API server on port {}...", api_port);
// Initialize Vault client // Initialize Vault client
@ -105,7 +110,9 @@ pub async fn start_api(vault_addr: &str, root_token: &str, api_port: u16) -> Res
// Bind and serve // Bind and serve
info!("Serving API at {}", addr); info!("Serving API at {}", addr);
Server::bind(&addr).serve(app.into_make_service()).await?; Server::bind(&addr)
.serve(app.into_make_service())
.await?;
Ok(()) Ok(())
} }
@ -125,8 +132,7 @@ async fn login(
) -> Result<Json<LoginResponse>, ApiError> { ) -> Result<Json<LoginResponse>, ApiError> {
info!("Login attempt for user: {}", request.username); info!("Login attempt for user: {}", request.username);
let token = state let token = state.vault_client
.vault_client
.login_user(&request.username, &request.password) .login_user(&request.username, &request.password)
.await?; .await?;
@ -154,10 +160,7 @@ async fn upload_document(
debug!("Received document name: {}", document_name); debug!("Received document name: {}", document_name);
} else if name == "file" { } else if name == "file" {
document_content = field.bytes().await?.to_vec(); document_content = field.bytes().await?.to_vec();
debug!( debug!("Received document content: {} bytes", document_content.len());
"Received document content: {} bytes",
document_content.len()
);
} }
} }
@ -167,13 +170,14 @@ async fn upload_document(
} }
// Upload document // Upload document
let document_id = state let document_id = state.document_service
.document_service
.upload_document(&document_name, &document_content) .upload_document(&document_name, &document_content)
.await?; .await?;
// Return document metadata // Return document metadata
let document = state.document_service.get_document(&document_id).await?; let document = state.document_service
.get_document(&document_id)
.await?;
info!("Document uploaded successfully with ID: {}", document_id); info!("Document uploaded successfully with ID: {}", document_id);
Ok(Json(document)) Ok(Json(document))
@ -187,13 +191,12 @@ async fn get_document(
) -> Result<Json<Document>, ApiError> { ) -> Result<Json<Document>, ApiError> {
info!("Fetching document: {}", document_id); info!("Fetching document: {}", document_id);
let document = state.document_service.get_document(&document_id).await?; let document = state.document_service
.get_document(&document_id)
.await?;
debug!( debug!("Retrieved document {} with {} signatures",
"Retrieved document {} with {} signatures", document.id, document.signatures.len());
document.id,
document.signatures.len()
);
Ok(Json(document)) Ok(Json(document))
} }
@ -205,22 +208,17 @@ async fn sign_document(
Path(document_id): Path<String>, Path(document_id): Path<String>,
Json(request): Json<SignDocumentRequest>, Json(request): Json<SignDocumentRequest>,
) -> Result<Json<Document>, ApiError> { ) -> Result<Json<Document>, ApiError> {
info!( info!("Signing request for document {} by user {}", document_id, request.username);
"Signing request for document {} by user {}",
document_id, request.username
);
state state.document_service
.document_service
.sign_document(&document_id, &request.username, &request.token) .sign_document(&document_id, &request.username, &request.token)
.await?; .await?;
let document = state.document_service.get_document(&document_id).await?; let document = state.document_service
.get_document(&document_id)
.await?;
info!( info!("Document {} successfully signed by {}", document_id, request.username);
"Document {} successfully signed by {}",
document_id, request.username
);
Ok(Json(document)) Ok(Json(document))
} }
@ -232,20 +230,12 @@ async fn verify_document(
) -> Result<Json<SignatureVerification>, ApiError> { ) -> Result<Json<SignatureVerification>, ApiError> {
info!("Verifying document signatures: {}", document_id); info!("Verifying document signatures: {}", document_id);
let verification = state let verification = state.document_service
.document_service
.verify_document_signatures(&document_id) .verify_document_signatures(&document_id)
.await?; .await?;
info!( info!("Document {} verification result: {}",
"Document {} verification result: {}", document_id, if verification.is_verified { "VERIFIED" } else { "PENDING" });
document_id,
if verification.is_verified {
"VERIFIED"
} else {
"PENDING"
}
);
Ok(Json(verification)) Ok(Json(verification))
} }

View file

@ -1,12 +1,12 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use reqwest::StatusCode; use reqwest::StatusCode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use sha2::{Digest, Sha256}; use sha2::{Sha256, Digest};
use std::collections::HashMap; use std::collections::HashMap;
use tracing::{debug, error, info, instrument};
use uuid::Uuid; use uuid::Uuid;
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use tracing::{info, error, debug, instrument};
use crate::vault_setup::{Department, User, VaultClient}; use crate::vault_setup::{Department, User, VaultClient};
@ -88,10 +88,8 @@ impl DocumentService {
async fn store_document_metadata(&self, document: &Document) -> Result<()> { async fn store_document_metadata(&self, document: &Document) -> Result<()> {
debug!("Storing document metadata for {}", document.id); debug!("Storing document metadata for {}", document.id);
let url = format!( let url = format!("{}/v1/documents/data/docs/{}",
"{}/v1/documents/data/docs/{}", self.vault_client.addr, document.id);
self.vault_client.addr, document.id
);
let payload = json!({ let payload = json!({
"data": { "data": {
@ -106,9 +104,7 @@ impl DocumentService {
} }
}); });
let response = self let response = self.vault_client.client
.vault_client
.client
.post(&url) .post(&url)
.header("X-Vault-Token", &self.vault_client.token) .header("X-Vault-Token", &self.vault_client.token)
.json(&payload) .json(&payload)
@ -122,15 +118,8 @@ impl DocumentService {
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!( error!("Failed to store document metadata: {} - {}", status, error_text);
"Failed to store document metadata: {} - {}", Err(anyhow::anyhow!("Failed to store document metadata: {} - {}", status, error_text))
status, error_text
);
Err(anyhow::anyhow!(
"Failed to store document metadata: {} - {}",
status,
error_text
))
} }
} }
} }
@ -140,14 +129,10 @@ impl DocumentService {
pub async fn get_document(&self, document_id: &str) -> Result<Document> { pub async fn get_document(&self, document_id: &str) -> Result<Document> {
debug!("Getting document metadata for {}", document_id); debug!("Getting document metadata for {}", document_id);
let url = format!( let url = format!("{}/v1/documents/data/docs/{}",
"{}/v1/documents/data/docs/{}", self.vault_client.addr, document_id);
self.vault_client.addr, document_id
);
let response = self let response = self.vault_client.client
.vault_client
.client
.get(&url) .get(&url)
.header("X-Vault-Token", &self.vault_client.token) .header("X-Vault-Token", &self.vault_client.token)
.send() .send()
@ -184,49 +169,27 @@ impl DocumentService {
} }
let document = Document { let document = Document {
id: json["data"]["data"]["id"] id: json["data"]["data"]["id"].as_str().context("Missing id")?.to_string(),
.as_str() name: json["data"]["data"]["name"].as_str().context("Missing name")?.to_string(),
.context("Missing id")? hash: json["data"]["data"]["hash"].as_str().context("Missing hash")?.to_string(),
.to_string(),
name: json["data"]["data"]["name"]
.as_str()
.context("Missing name")?
.to_string(),
hash: json["data"]["data"]["hash"]
.as_str()
.context("Missing hash")?
.to_string(),
status, status,
signatures, signatures,
}; };
debug!( debug!("Retrieved document: {} with {} signatures", document.id, document.signatures.len());
"Retrieved document: {} with {} signatures",
document.id,
document.signatures.len()
);
Ok(document) Ok(document)
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to get document: {} - {}", status, error_text); error!("Failed to get document: {} - {}", status, error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to get document: {} - {}", status, error_text))
"Failed to get document: {} - {}",
status,
error_text
))
} }
} }
} }
// Sign a document with the user's key // Sign a document with the user's key
#[instrument(skip(self, user_token), fields(document_id = %document_id, username = %username))] #[instrument(skip(self, user_token), fields(document_id = %document_id, username = %username))]
pub async fn sign_document( pub async fn sign_document(&self, document_id: &str, username: &str, user_token: &str) -> Result<()> {
&self,
document_id: &str,
username: &str,
user_token: &str,
) -> Result<()> {
info!("Signing document {} by user {}", document_id, username); info!("Signing document {} by user {}", document_id, username);
// Get document metadata // Get document metadata
@ -236,15 +199,14 @@ impl DocumentService {
let user = self.vault_client.get_user_info(username).await?; let user = self.vault_client.get_user_info(username).await?;
// Sign the document hash with user's key // Sign the document hash with user's key
let url = format!("{}/v1/transit/sign/{}", self.vault_client.addr, username); let url = format!("{}/v1/transit/sign/{}",
self.vault_client.addr, username);
let payload = json!({ let payload = json!({
"input": BASE64.encode(document.hash.as_bytes()), "input": BASE64.encode(document.hash.as_bytes()),
}); });
let response = self let response = self.vault_client.client
.vault_client
.client
.post(&url) .post(&url)
.header("X-Vault-Token", user_token) .header("X-Vault-Token", user_token)
.json(&payload) .json(&payload)
@ -262,8 +224,7 @@ impl DocumentService {
debug!("Generated signature for document {}", document_id); debug!("Generated signature for document {}", document_id);
// Update document with signature // Update document with signature
self.add_signature(document_id, username, &signature) self.add_signature(document_id, username, &signature).await?;
.await?;
// Update department signature record // Update department signature record
self.record_department_signature(document_id, &user).await?; self.record_department_signature(document_id, &user).await?;
@ -277,43 +238,26 @@ impl DocumentService {
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to sign document: {} - {}", status, error_text); error!("Failed to sign document: {} - {}", status, error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to sign document: {} - {}", status, error_text))
"Failed to sign document: {} - {}",
status,
error_text
))
} }
} }
} }
// Add a signature to a document // Add a signature to a document
#[instrument(skip(self, signature), fields(document_id = %document_id, username = %username))] #[instrument(skip(self, signature), fields(document_id = %document_id, username = %username))]
async fn add_signature( async fn add_signature(&self, document_id: &str, username: &str, signature: &str) -> Result<()> {
&self, debug!("Adding signature from {} to document {}", username, document_id);
document_id: &str,
username: &str,
signature: &str,
) -> Result<()> {
debug!(
"Adding signature from {} to document {}",
username, document_id
);
// Get current document // Get current document
let mut document = self.get_document(document_id).await?; let mut document = self.get_document(document_id).await?;
// Add signature // Add signature
document document.signatures.insert(username.to_string(), signature.to_string());
.signatures
.insert(username.to_string(), signature.to_string());
// Store updated document // Store updated document
self.store_document_metadata(&document).await?; self.store_document_metadata(&document).await?;
debug!( debug!("Added signature from {} to document {}", username, document_id);
"Added signature from {} to document {}",
username, document_id
);
Ok(()) Ok(())
} }
@ -325,20 +269,13 @@ impl DocumentService {
Department::Finance => "finance", Department::Finance => "finance",
}; };
debug!( debug!("Recording {} department signature for document {}", dept_str, document_id);
"Recording {} department signature for document {}",
dept_str, document_id
);
let url = format!( let url = format!("{}/v1/documents/data/dept/{}/signatures/{}",
"{}/v1/documents/data/dept/{}/signatures/{}", self.vault_client.addr, dept_str, document_id);
self.vault_client.addr, dept_str, document_id
);
// Check if department signatures already exist // Check if department signatures already exist
let response = self let response = self.vault_client.client
.vault_client
.client
.get(&url) .get(&url)
.header("X-Vault-Token", &self.vault_client.token) .header("X-Vault-Token", &self.vault_client.token)
.send() .send()
@ -372,9 +309,7 @@ impl DocumentService {
} }
}); });
let response = self let response = self.vault_client.client
.vault_client
.client
.post(&url) .post(&url)
.header("X-Vault-Token", &self.vault_client.token) .header("X-Vault-Token", &self.vault_client.token)
.json(&payload) .json(&payload)
@ -383,23 +318,13 @@ impl DocumentService {
match response.status() { match response.status() {
StatusCode::OK | StatusCode::NO_CONTENT => { StatusCode::OK | StatusCode::NO_CONTENT => {
info!( info!("Recorded signature for {} in {} department", user.username, dept_str);
"Recorded signature for {} in {} department",
user.username, dept_str
);
Ok(()) Ok(())
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!( error!("Failed to record department signature: {} - {}", status, error_text);
"Failed to record department signature: {} - {}", Err(anyhow::anyhow!("Failed to record department signature: {} - {}", status, error_text))
status, error_text
);
Err(anyhow::anyhow!(
"Failed to record department signature: {} - {}",
status,
error_text
))
} }
} }
} }
@ -439,24 +364,17 @@ impl DocumentService {
// Verify document signatures // Verify document signatures
#[instrument(skip(self))] #[instrument(skip(self))]
pub async fn verify_document_signatures( pub async fn verify_document_signatures(&self, document_id: &str) -> Result<SignatureVerification> {
&self,
document_id: &str,
) -> Result<SignatureVerification> {
info!("Verifying signatures for document {}", document_id); info!("Verifying signatures for document {}", document_id);
// Get document // Get document
let document = self.get_document(document_id).await?; let document = self.get_document(document_id).await?;
// Get signing requirements // Get signing requirements
let url = format!( let url = format!("{}/v1/documents/data/config/signing_requirements",
"{}/v1/documents/data/config/signing_requirements", self.vault_client.addr);
self.vault_client.addr
);
let response = self let response = self.vault_client.client
.vault_client
.client
.get(&url) .get(&url)
.header("X-Vault-Token", &self.vault_client.token) .header("X-Vault-Token", &self.vault_client.token)
.send() .send()
@ -466,15 +384,8 @@ impl DocumentService {
StatusCode::OK => response.json::<serde_json::Value>().await?, StatusCode::OK => response.json::<serde_json::Value>().await?,
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!( error!("Failed to get signing requirements: {} - {}", status, error_text);
"Failed to get signing requirements: {} - {}", return Err(anyhow::anyhow!("Failed to get signing requirements: {} - {}", status, error_text));
status, error_text
);
return Err(anyhow::anyhow!(
"Failed to get signing requirements: {} - {}",
status,
error_text
));
} }
}; };
@ -502,15 +413,13 @@ impl DocumentService {
// Get department signatures // Get department signatures
let legal_signatures = self.get_department_signatures(document_id, "legal").await?; let legal_signatures = self.get_department_signatures(document_id, "legal").await?;
let finance_signatures = self let finance_signatures = self.get_department_signatures(document_id, "finance").await?;
.get_department_signatures(document_id, "finance")
.await?;
// Check if requirements are met // Check if requirements are met
let total_signatures = document.signatures.len(); let total_signatures = document.signatures.len();
let is_verified = total_signatures >= required_signatures let is_verified = total_signatures >= required_signatures &&
&& legal_signatures.len() >= required_legal legal_signatures.len() >= required_legal &&
&& finance_signatures.len() >= required_finance; finance_signatures.len() >= required_finance;
let verification = SignatureVerification { let verification = SignatureVerification {
document_id: document_id.to_string(), document_id: document_id.to_string(),
@ -540,24 +449,13 @@ impl DocumentService {
// Get department signatures for a document // Get department signatures for a document
#[instrument(skip(self))] #[instrument(skip(self))]
async fn get_department_signatures( async fn get_department_signatures(&self, document_id: &str, department: &str) -> Result<Vec<String>> {
&self, debug!("Getting {} department signatures for document {}", department, document_id);
document_id: &str,
department: &str,
) -> Result<Vec<String>> {
debug!(
"Getting {} department signatures for document {}",
department, document_id
);
let url = format!( let url = format!("{}/v1/documents/data/dept/{}/signatures/{}",
"{}/v1/documents/data/dept/{}/signatures/{}", self.vault_client.addr, department, document_id);
self.vault_client.addr, department, document_id
);
let response = self let response = self.vault_client.client
.vault_client
.client
.get(&url) .get(&url)
.header("X-Vault-Token", &self.vault_client.token) .header("X-Vault-Token", &self.vault_client.token)
.send() .send()
@ -579,12 +477,8 @@ impl DocumentService {
} }
} }
debug!( debug!("Found {} signatures for {} department on document {}",
"Found {} signatures for {} department on document {}", signatures.len(), department, document_id);
signatures.len(),
department,
document_id
);
Ok(signatures) Ok(signatures)
} }

View file

@ -1,11 +1,11 @@
// Modules that implement our hierarchical signing system // Modules that implement our hierarchical signing system
pub mod api;
pub mod document_service;
pub mod vault_init;
pub mod vault_setup; pub mod vault_setup;
pub mod vault_init;
pub mod document_service;
pub mod api;
// Re-export main components for easier access // Re-export main components for easier access
pub use api::start_api;
pub use document_service::DocumentService;
pub use vault_init::initialize_vault;
pub use vault_setup::VaultClient; pub use vault_setup::VaultClient;
pub use vault_init::initialize_vault;
pub use document_service::DocumentService;
pub use api::start_api;

View file

@ -1,18 +1,14 @@
use anyhow::Result; use anyhow::Result;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::path::PathBuf; use std::path::PathBuf;
use tracing::info; use tracing::{info};
use tracing_subscriber::{EnvFilter, fmt}; use tracing_subscriber::{fmt, EnvFilter};
// Import our library // Import our library
use vault_hier::{initialize_vault, start_api}; use vault_hier::{start_api, initialize_vault};
#[derive(Parser)] #[derive(Parser)]
#[command( #[command(author, version, about = "Hierarchical Document Signing with HashiCorp Vault")]
author,
version,
about = "Hierarchical Document Signing with HashiCorp Vault"
)]
struct Cli { struct Cli {
#[command(subcommand)] #[command(subcommand)]
command: Commands, command: Commands,
@ -121,49 +117,27 @@ async fn main() -> Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
match cli.command { match cli.command {
Commands::Server { Commands::Server { vault_addr, api_port } => {
vault_addr,
api_port,
} => {
run_server(&vault_addr, api_port).await?; run_server(&vault_addr, api_port).await?;
} },
Commands::Login { Commands::Login { username, password, api_url } => {
username,
password,
api_url,
} => {
login(&username, &password, &api_url).await?; login(&username, &password, &api_url).await?;
} },
Commands::Upload { Commands::Upload { name, file, api_url } => {
name,
file,
api_url,
} => {
upload_document(&name, file, &api_url).await?; upload_document(&name, file, &api_url).await?;
} },
Commands::Sign { Commands::Sign { document_id, username, token, api_url } => {
document_id,
username,
token,
api_url,
} => {
sign_document(&document_id, &username, &token, &api_url).await?; sign_document(&document_id, &username, &token, &api_url).await?;
} },
Commands::Verify { Commands::Verify { document_id, api_url } => {
document_id,
api_url,
} => {
verify_document(&document_id, &api_url).await?; verify_document(&document_id, &api_url).await?;
} },
Commands::List { api_url } => { Commands::List { api_url } => {
list_documents(&api_url).await?; list_documents(&api_url).await?;
} },
Commands::Get { Commands::Get { document_id, api_url } => {
document_id,
api_url,
} => {
get_document(&document_id, &api_url).await?; get_document(&document_id, &api_url).await?;
} },
} }
Ok(()) Ok(())
@ -191,7 +165,7 @@ async fn login(username: &str, password: &str, api_url: &str) -> Result<()> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let response = client let response = client
.post(format!("{}/api/login", api_url)) .post(&format!("{}/api/login", api_url))
.json(&serde_json::json!({ .json(&serde_json::json!({
"username": username, "username": username,
"password": password, "password": password,
@ -220,14 +194,12 @@ async fn upload_document(name: &str, file_path: PathBuf, api_url: &str) -> Resul
let form = reqwest::multipart::Form::new() let form = reqwest::multipart::Form::new()
.text("name", name.to_string()) .text("name", name.to_string())
.part( .part("file", reqwest::multipart::Part::bytes(file_content)
"file", .file_name(file_name.to_string()));
reqwest::multipart::Part::bytes(file_content).file_name(file_name.to_string()),
);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let response = client let response = client
.post(format!("{}/api/documents", api_url)) .post(&format!("{}/api/documents", api_url))
.multipart(form) .multipart(form)
.send() .send()
.await?; .await?;
@ -244,17 +216,12 @@ async fn upload_document(name: &str, file_path: PathBuf, api_url: &str) -> Resul
Ok(()) Ok(())
} }
async fn sign_document( async fn sign_document(document_id: &str, username: &str, token: &str, api_url: &str) -> Result<()> {
document_id: &str,
username: &str,
token: &str,
api_url: &str,
) -> Result<()> {
info!("Signing document: {}", document_id); info!("Signing document: {}", document_id);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let response = client let response = client
.post(format!("{}/api/documents/{}/sign", api_url, document_id)) .post(&format!("{}/api/documents/{}/sign", api_url, document_id))
.json(&serde_json::json!({ .json(&serde_json::json!({
"username": username, "username": username,
"token": token, "token": token,
@ -279,7 +246,7 @@ async fn verify_document(document_id: &str, api_url: &str) -> Result<()> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let response = client let response = client
.get(format!("{}/api/documents/{}/verify", api_url, document_id)) .get(&format!("{}/api/documents/{}/verify", api_url, document_id))
.send() .send()
.await?; .await?;
@ -288,21 +255,16 @@ async fn verify_document(document_id: &str, api_url: &str) -> Result<()> {
println!("Verification result:"); println!("Verification result:");
println!(" Valid: {}", data["valid"]); println!(" Valid: {}", data["valid"]);
println!(" Total signatures: {}", data["signature_count"]); println!(" Total signatures: {}", data["signature_count"]);
println!( println!(" Departments represented: {}", data["departments_represented"]);
" Departments represented: {}",
data["departments_represented"]
);
if let Some(signatures) = data["signatures"].as_array() { if let Some(signatures) = data["signatures"].as_array() {
println!("\nSignatures:"); println!("\nSignatures:");
for (i, sig) in signatures.iter().enumerate() { for (i, sig) in signatures.iter().enumerate() {
println!( println!(" {}. User: {}, Department: {}, Time: {}",
" {}. User: {}, Department: {}, Time: {}", i+1,
i + 1,
sig["username"], sig["username"],
sig["department"], sig["department"],
sig["timestamp"] sig["timestamp"]);
);
} }
} }
} else { } else {
@ -318,7 +280,7 @@ async fn list_documents(api_url: &str) -> Result<()> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let response = client let response = client
.get(format!("{}/api/documents", api_url)) .get(&format!("{}/api/documents", api_url))
.send() .send()
.await?; .await?;
@ -327,7 +289,7 @@ async fn list_documents(api_url: &str) -> Result<()> {
println!("Documents:"); println!("Documents:");
for (i, doc) in data.iter().enumerate() { for (i, doc) in data.iter().enumerate() {
println!(" {}. ID: {}, Name: {}", i + 1, doc["id"], doc["name"]); println!(" {}. ID: {}, Name: {}", i+1, doc["id"], doc["name"]);
} }
if data.is_empty() { if data.is_empty() {
@ -346,7 +308,7 @@ async fn get_document(document_id: &str, api_url: &str) -> Result<()> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let response = client let response = client
.get(format!("{}/api/documents/{}", api_url, document_id)) .get(&format!("{}/api/documents/{}", api_url, document_id))
.send() .send()
.await?; .await?;
@ -361,13 +323,11 @@ async fn get_document(document_id: &str, api_url: &str) -> Result<()> {
if let Some(signatures) = doc["signatures"].as_array() { if let Some(signatures) = doc["signatures"].as_array() {
println!("\nSignatures:"); println!("\nSignatures:");
for (i, sig) in signatures.iter().enumerate() { for (i, sig) in signatures.iter().enumerate() {
println!( println!(" {}. User: {}, Department: {}, Time: {}",
" {}. User: {}, Department: {}, Time: {}", i+1,
i + 1,
sig["username"], sig["username"],
sig["department"], sig["department"],
sig["timestamp"] sig["timestamp"]);
);
} }
if signatures.is_empty() { if signatures.is_empty() {

View file

@ -1,7 +1,10 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use reqwest::Client; use reqwest::Client;
use std::{env, fs}; use std::{
use tracing::{debug, error, info, warn}; env,
fs,
};
use tracing::{info, warn, error, debug};
use crate::vault_setup::VaultClient; use crate::vault_setup::VaultClient;
@ -13,17 +16,14 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
VaultClient::wait_for_vault(vault_addr).await?; VaultClient::wait_for_vault(vault_addr).await?;
// Display Vault health status // Display Vault health status
let health_url = format!( let health_url = format!("{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true", vault_addr);
"{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true",
vault_addr
);
match client.get(&health_url).send().await { match client.get(&health_url).send().await {
Ok(response) => { Ok(response) => {
if response.status().is_success() { if response.status().is_success() {
let status_text = response.text().await?; let status_text = response.text().await?;
info!("Vault status: {}", status_text); info!("Vault status: {}", status_text);
} }
} },
Err(e) => warn!("Error getting Vault status: {}", e), Err(e) => warn!("Error getting Vault status: {}", e),
} }
@ -47,7 +47,7 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
Ok(key) => { Ok(key) => {
info!("Found unseal key {} from environment", i); info!("Found unseal key {} from environment", i);
unseal_keys.push(key); unseal_keys.push(key);
} },
Err(_) => { Err(_) => {
debug!("Unseal key {} not found in environment", i); debug!("Unseal key {} not found in environment", i);
} }
@ -56,16 +56,11 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
// If we have unseal keys, try to unseal // If we have unseal keys, try to unseal
if !unseal_keys.is_empty() { if !unseal_keys.is_empty() {
info!( info!("Found {} unseal keys. Attempting to unseal...", unseal_keys.len());
"Found {} unseal keys. Attempting to unseal...",
unseal_keys.len()
);
VaultClient::unseal_vault(&client, vault_addr, &unseal_keys).await?; VaultClient::unseal_vault(&client, vault_addr, &unseal_keys).await?;
} else { } else {
warn!("No unseal keys found. Vault remains sealed."); warn!("No unseal keys found. Vault remains sealed.");
info!( info!("To unseal, set VAULT_UNSEAL_KEY_1, VAULT_UNSEAL_KEY_2, etc. environment variables.");
"To unseal, set VAULT_UNSEAL_KEY_1, VAULT_UNSEAL_KEY_2, etc. environment variables."
);
} }
} else { } else {
info!("Vault is already unsealed."); info!("Vault is already unsealed.");
@ -76,7 +71,7 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
Ok(token) => { Ok(token) => {
info!("Found root token from environment"); info!("Found root token from environment");
root_token = token; root_token = token;
} },
Err(_) => { Err(_) => {
// Try to load from credentials file // Try to load from credentials file
if let Ok(contents) = fs::read_to_string("vault-credentials.json") { if let Ok(contents) = fs::read_to_string("vault-credentials.json") {
@ -91,12 +86,8 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
} }
if root_token.is_empty() { if root_token.is_empty() {
error!( error!("Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file.");
"Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file." anyhow::bail!("Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file.");
);
anyhow::bail!(
"Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file."
);
} }
} else { } else {
// Initialize Vault // Initialize Vault
@ -121,17 +112,11 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
if let Ok(()) = std::fs::create_dir_all("/app/data") { if let Ok(()) = std::fs::create_dir_all("/app/data") {
let docker_json_path = "/app/data/vault-credentials.json"; let docker_json_path = "/app/data/vault-credentials.json";
VaultClient::save_credentials(&init_response, docker_json_path)?; VaultClient::save_credentials(&init_response, docker_json_path)?;
info!( info!("Backup JSON credentials saved to Docker volume at: {}", docker_json_path);
"Backup JSON credentials saved to Docker volume at: {}",
docker_json_path
);
let docker_text_path = "/app/data/vault-credentials.txt"; let docker_text_path = "/app/data/vault-credentials.txt";
VaultClient::save_credentials(&init_response, docker_text_path)?; VaultClient::save_credentials(&init_response, docker_text_path)?;
info!( info!("Backup text credentials saved to Docker volume at: {}", docker_text_path);
"Backup text credentials saved to Docker volume at: {}",
docker_text_path
);
} }
info!("========================================="); info!("=========================================");
@ -145,10 +130,8 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
info!("========================================="); info!("=========================================");
// Unseal Vault using the first three keys // Unseal Vault using the first three keys
let unseal_keys = init_response let unseal_keys = init_response.keys_base64.iter()
.keys_base64 .take(3) // We only need threshold number of keys (3)
.iter()
.take(3) // We only need threshold number of keys (3)
.cloned() .cloned()
.collect::<Vec<String>>(); .collect::<Vec<String>>();

View file

@ -2,9 +2,14 @@ use anyhow::{Context, Result};
use reqwest::{Client, StatusCode}; use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use std::{fs::File, io::Write, path::Path, time::Duration}; use std::{
fs::File,
io::Write,
path::Path,
time::Duration,
};
use tokio::time::sleep; use tokio::time::sleep;
use tracing::{debug, error, info, instrument}; use tracing::{info, error, debug, instrument};
// Vault API response structures // Vault API response structures
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -72,17 +77,9 @@ impl VaultClient {
let client = Client::new(); let client = Client::new();
for i in 1..=30 { for i in 1..=30 {
let health_url = format!( let health_url = format!("{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true", addr);
"{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true",
addr
);
match client match client.get(&health_url).timeout(Duration::from_secs(1)).send().await {
.get(&health_url)
.timeout(Duration::from_secs(1))
.send()
.await
{
Ok(response) => { Ok(response) => {
let status = response.status().as_u16(); let status = response.status().as_u16();
// Accept any of these status codes as "available" // Accept any of these status codes as "available"
@ -92,7 +89,7 @@ impl VaultClient {
} }
debug!("Vault returned unexpected status code: {}", status); debug!("Vault returned unexpected status code: {}", status);
} },
Err(e) => { Err(e) => {
debug!("Error connecting to Vault: {}", e); debug!("Error connecting to Vault: {}", e);
} }
@ -100,9 +97,7 @@ impl VaultClient {
if i == 30 { if i == 30 {
error!("Timed out waiting for Vault to become available"); error!("Timed out waiting for Vault to become available");
return Err(anyhow::anyhow!( return Err(anyhow::anyhow!("Timed out waiting for Vault to become available"));
"Timed out waiting for Vault to become available"
));
} }
info!("Vault is unavailable - sleeping (attempt {}/30)", i); info!("Vault is unavailable - sleeping (attempt {}/30)", i);
@ -117,7 +112,10 @@ impl VaultClient {
pub async fn check_init_status(client: &Client, addr: &str) -> Result<bool> { pub async fn check_init_status(client: &Client, addr: &str) -> Result<bool> {
info!("Checking if Vault is already initialized..."); info!("Checking if Vault is already initialized...");
let response = client.get(format!("{}/v1/sys/init", addr)).send().await?; let response = client
.get(format!("{}/v1/sys/init", addr))
.send()
.await?;
if response.status().is_success() { if response.status().is_success() {
let status = response.json::<serde_json::Value>().await?; let status = response.json::<serde_json::Value>().await?;
@ -142,10 +140,8 @@ impl VaultClient {
if response.status().is_success() { if response.status().is_success() {
let status = response.json::<SealStatusResponse>().await?; let status = response.json::<SealStatusResponse>().await?;
info!( info!("Seal status: sealed={}, threshold={}, shares={}, progress={}",
"Seal status: sealed={}, threshold={}, shares={}, progress={}", status.sealed, status.t, status.n, status.progress);
status.sealed, status.t, status.n, status.progress
);
return Ok(status); return Ok(status);
} else { } else {
let error_text = response.text().await?; let error_text = response.text().await?;
@ -228,7 +224,9 @@ impl VaultClient {
for (i, key) in unseal_keys.iter().take(required_keys).enumerate() { for (i, key) in unseal_keys.iter().take(required_keys).enumerate() {
info!("Applying unseal key {}/{}...", i + 1, required_keys); info!("Applying unseal key {}/{}...", i + 1, required_keys);
let unseal_req = UnsealRequest { key: key.clone() }; let unseal_req = UnsealRequest {
key: key.clone(),
};
let response = client let response = client
.put(format!("{}/v1/sys/unseal", addr)) .put(format!("{}/v1/sys/unseal", addr))
@ -321,8 +319,7 @@ impl VaultClient {
"type": engine_type, "type": engine_type,
}); });
let response = self let response = self.client
.client
.post(&url) .post(&url)
.header("X-Vault-Token", &self.token) .header("X-Vault-Token", &self.token)
.json(&payload) .json(&payload)
@ -342,23 +339,13 @@ impl VaultClient {
Ok(()) Ok(())
} else { } else {
error!("Failed to enable secrets engine: {}", error_text); error!("Failed to enable secrets engine: {}", error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to enable secrets engine: {}", error_text))
"Failed to enable secrets engine: {}",
error_text
))
} }
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!( error!("Failed to enable secrets engine: {} - {}", status, error_text);
"Failed to enable secrets engine: {} - {}", Err(anyhow::anyhow!("Failed to enable secrets engine: {} - {}", status, error_text))
status, error_text
);
Err(anyhow::anyhow!(
"Failed to enable secrets engine: {} - {}",
status,
error_text
))
} }
} }
} }
@ -373,8 +360,7 @@ impl VaultClient {
"type": method, "type": method,
}); });
let response = self let response = self.client
.client
.post(&url) .post(&url)
.header("X-Vault-Token", &self.token) .header("X-Vault-Token", &self.token)
.json(&payload) .json(&payload)
@ -394,38 +380,25 @@ impl VaultClient {
Ok(()) Ok(())
} else { } else {
error!("Failed to enable auth method: {}", error_text); error!("Failed to enable auth method: {}", error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to enable auth method: {}", error_text))
"Failed to enable auth method: {}",
error_text
))
} }
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to enable auth method: {} - {}", status, error_text); error!("Failed to enable auth method: {} - {}", status, error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to enable auth method: {} - {}", status, error_text))
"Failed to enable auth method: {} - {}",
status,
error_text
))
} }
} }
} }
// Create a new user in Vault and associate with department // Create a new user in Vault and associate with department
#[instrument(skip(self, password))] #[instrument(skip(self, password))]
pub async fn create_user( pub async fn create_user(&self, username: &str, password: &str, department: Department) -> Result<()> {
&self,
username: &str,
password: &str,
department: Department,
) -> Result<()> {
info!("Creating user {} in department {:?}", username, department); info!("Creating user {} in department {:?}", username, department);
// Step 1: Create a policy for the user // Step 1: Create a policy for the user
let policy_name = format!("{}-policy", username); let policy_name = format!("{}-policy", username);
self.create_signing_policy(&policy_name, department.clone()) self.create_signing_policy(&policy_name, department.clone()).await?;
.await?;
// Step 2: Create the user with userpass auth // Step 2: Create the user with userpass auth
let url = format!("{}/v1/auth/userpass/users/{}", self.addr, username); let url = format!("{}/v1/auth/userpass/users/{}", self.addr, username);
@ -434,8 +407,7 @@ impl VaultClient {
"policies": [policy_name], "policies": [policy_name],
}); });
let response = self let response = self.client
.client
.post(&url) .post(&url)
.header("X-Vault-Token", &self.token) .header("X-Vault-Token", &self.token)
.json(&payload) .json(&payload)
@ -457,11 +429,7 @@ impl VaultClient {
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to create user: {} - {}", status, error_text); error!("Failed to create user: {} - {}", status, error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to create user: {} - {}", status, error_text))
"Failed to create user: {} - {}",
status,
error_text
))
} }
} }
} }
@ -476,10 +444,9 @@ impl VaultClient {
// Get the username from the policy name (remove "-policy" suffix) // Get the username from the policy name (remove "-policy" suffix)
let username = policy_name.trim_end_matches("-policy"); let username = policy_name.trim_end_matches("-policy");
// Policy content with specific paths for the department // Policy content with specific paths for the department
let policy = format!( let policy = format!(r#"
r#"
# Allow reading document metadata # Allow reading document metadata
path "documents/data/docs/*" {{ path "documents/data/docs/*" {{
capabilities = ["read"] capabilities = ["read"]
@ -499,17 +466,14 @@ impl VaultClient {
path "documents/data/dept/{}/signatures/*" {{ path "documents/data/dept/{}/signatures/*" {{
capabilities = ["create", "read", "update"] capabilities = ["create", "read", "update"]
}} }}
"#, "#, username, dept_name);
username, dept_name
);
let url = format!("{}/v1/sys/policies/acl/{}", self.addr, policy_name); let url = format!("{}/v1/sys/policies/acl/{}", self.addr, policy_name);
let payload = json!({ let payload = json!({
"policy": policy, "policy": policy,
}); });
let response = self let response = self.client
.client
.put(&url) .put(&url)
.header("X-Vault-Token", &self.token) .header("X-Vault-Token", &self.token)
.json(&payload) .json(&payload)
@ -524,11 +488,7 @@ impl VaultClient {
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to create policy: {} - {}", status, error_text); error!("Failed to create policy: {} - {}", status, error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to create policy: {} - {}", status, error_text))
"Failed to create policy: {} - {}",
status,
error_text
))
} }
} }
} }
@ -541,8 +501,7 @@ impl VaultClient {
"type": "ed25519", "type": "ed25519",
}); });
let response = self let response = self.client
.client
.post(&url) .post(&url)
.header("X-Vault-Token", &self.token) .header("X-Vault-Token", &self.token)
.json(&payload) .json(&payload)
@ -557,11 +516,7 @@ impl VaultClient {
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to create signing key: {} - {}", status, error_text); error!("Failed to create signing key: {} - {}", status, error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to create signing key: {} - {}", status, error_text))
"Failed to create signing key: {} - {}",
status,
error_text
))
} }
} }
} }
@ -582,8 +537,7 @@ impl VaultClient {
} }
}); });
let response = self let response = self.client
.client
.post(&url) .post(&url)
.header("X-Vault-Token", &self.token) .header("X-Vault-Token", &self.token)
.json(&payload) .json(&payload)
@ -598,11 +552,7 @@ impl VaultClient {
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to store user metadata: {} - {}", status, error_text); error!("Failed to store user metadata: {} - {}", status, error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to store user metadata: {} - {}", status, error_text))
"Failed to store user metadata: {} - {}",
status,
error_text
))
} }
} }
} }
@ -617,8 +567,7 @@ impl VaultClient {
let username = format!("legal{}", i); let username = format!("legal{}", i);
let password = format!("legal{}pass", i); let password = format!("legal{}pass", i);
debug!(username, "Creating Legal department user"); debug!(username, "Creating Legal department user");
self.create_user(&username, &password, Department::Legal) self.create_user(&username, &password, Department::Legal).await?;
.await?;
} }
// Create 5 users in Finance department // Create 5 users in Finance department
@ -626,8 +575,7 @@ impl VaultClient {
let username = format!("finance{}", i); let username = format!("finance{}", i);
let password = format!("finance{}pass", i); let password = format!("finance{}pass", i);
debug!(username, "Creating Finance department user"); debug!(username, "Creating Finance department user");
self.create_user(&username, &password, Department::Finance) self.create_user(&username, &password, Department::Finance).await?;
.await?;
} }
// Setup document signing requirements // Setup document signing requirements
@ -642,10 +590,7 @@ impl VaultClient {
async fn setup_signing_requirements(&self) -> Result<()> { async fn setup_signing_requirements(&self) -> Result<()> {
info!("Setting up document signing requirements"); info!("Setting up document signing requirements");
let url = format!( let url = format!("{}/v1/documents/data/config/signing_requirements", self.addr);
"{}/v1/documents/data/config/signing_requirements",
self.addr
);
let payload = json!({ let payload = json!({
"data": { "data": {
"total_required": 3, "total_required": 3,
@ -665,8 +610,7 @@ impl VaultClient {
} }
}); });
let response = self let response = self.client
.client
.post(&url) .post(&url)
.header("X-Vault-Token", &self.token) .header("X-Vault-Token", &self.token)
.json(&payload) .json(&payload)
@ -680,15 +624,8 @@ impl VaultClient {
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!( error!("Failed to configure signing requirements: {} - {}", status, error_text);
"Failed to configure signing requirements: {} - {}", Err(anyhow::anyhow!("Failed to configure signing requirements: {} - {}", status, error_text))
status, error_text
);
Err(anyhow::anyhow!(
"Failed to configure signing requirements: {} - {}",
status,
error_text
))
} }
} }
} }
@ -703,7 +640,11 @@ impl VaultClient {
"password": password, "password": password,
}); });
let response = self.client.post(&url).json(&payload).send().await?; let response = self.client
.post(&url)
.json(&payload)
.send()
.await?;
match response.status() { match response.status() {
StatusCode::OK => { StatusCode::OK => {
@ -719,11 +660,7 @@ impl VaultClient {
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to login: {} - {}", status, error_text); error!("Failed to login: {} - {}", status, error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to login: {} - {}", status, error_text))
"Failed to login: {} - {}",
status,
error_text
))
} }
} }
} }
@ -735,8 +672,7 @@ impl VaultClient {
let url = format!("{}/v1/documents/data/users/{}", self.addr, username); let url = format!("{}/v1/documents/data/users/{}", self.addr, username);
let response = self let response = self.client
.client
.get(&url) .get(&url)
.header("X-Vault-Token", &self.token) .header("X-Vault-Token", &self.token)
.send() .send()
@ -758,10 +694,7 @@ impl VaultClient {
} }
}; };
debug!( debug!("Retrieved user info for {} in {:?} department", username, department);
"Retrieved user info for {} in {:?} department",
username, department
);
Ok(User { Ok(User {
username: username.to_string(), username: username.to_string(),
department, department,
@ -770,11 +703,7 @@ impl VaultClient {
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to get user info: {} - {}", status, error_text); error!("Failed to get user info: {} - {}", status, error_text);
Err(anyhow::anyhow!( Err(anyhow::anyhow!("Failed to get user info: {} - {}", status, error_text))
"Failed to get user info: {} - {}",
status,
error_text
))
} }
} }
} }