nix
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
This commit is contained in:
parent
c65ae95b43
commit
d7b7a72444
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -2,7 +2,6 @@
|
||||||
/target/
|
/target/
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
*.pdb
|
*.pdb
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
.cargo/
|
.cargo/
|
||||||
|
@ -32,4 +31,4 @@ result
|
||||||
result-*
|
result-*
|
||||||
|
|
||||||
# macOS specific files
|
# macOS specific files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
2026
Cargo.lock
generated
Normal file
2026
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
28
flake.lock
28
flake.lock
|
@ -1,5 +1,20 @@
|
||||||
{
|
{
|
||||||
"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"
|
||||||
|
@ -20,11 +35,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1742268799,
|
"lastModified": 1742751704,
|
||||||
"narHash": "sha256-IhnK4LhkBlf14/F8THvUy3xi/TxSQkp9hikfDZRD4Ic=",
|
"narHash": "sha256-rBfc+H1dDBUQ2mgVITMGBPI1PGuCznf9rcWX/XIULyE=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "da044451c6a70518db5b730fe277b70f494188f1",
|
"rev": "f0946fa5f1fb876a9dc2e1850d9d3a4e3f914092",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -52,6 +67,7 @@
|
||||||
},
|
},
|
||||||
"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"
|
||||||
|
@ -62,11 +78,11 @@
|
||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1742437918,
|
"lastModified": 1742783666,
|
||||||
"narHash": "sha256-Vflb6KJVDikFcM9E231mRN88uk4+jo7BWtaaQMifthI=",
|
"narHash": "sha256-IwdSl51NL6V0f+mYXZR0UTKaGleOsk9zV3l6kt5SUWw=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "f03085549609e49c7bcbbee86a1949057d087199",
|
"rev": "60766d63c227d576510ecfb5edd3a687d56f6bc7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
142
flake.nix
142
flake.nix
|
@ -3,41 +3,117 @@
|
||||||
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 = { self, nixpkgs, flake-utils, rust-overlay }:
|
outputs =
|
||||||
flake-utils.lib.eachDefaultSystem
|
{ self
|
||||||
(system:
|
, nixpkgs
|
||||||
let
|
, flake-utils
|
||||||
overlays = [
|
, rust-overlay
|
||||||
rust-overlay.overlays.default
|
, crane
|
||||||
];
|
,
|
||||||
pkgs = import nixpkgs {
|
}:
|
||||||
inherit system overlays;
|
flake-utils.lib.eachDefaultSystem (
|
||||||
config = {
|
system:
|
||||||
allowUnfree = true;
|
let
|
||||||
};
|
overlays = [
|
||||||
|
rust-overlay.overlays.default
|
||||||
|
];
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system overlays;
|
||||||
|
config = {
|
||||||
|
allowUnfree = true;
|
||||||
};
|
};
|
||||||
in
|
};
|
||||||
with pkgs;
|
src = craneLib.cleanCargoSource ./.;
|
||||||
{
|
|
||||||
devShells.default = mkShell {
|
|
||||||
env = {
|
|
||||||
OPENSSL_NO_VENDOR = "1";
|
|
||||||
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
|
|
||||||
};
|
|
||||||
|
|
||||||
packages = [
|
commonArgs = {
|
||||||
pkg-config
|
inherit src;
|
||||||
vault
|
|
||||||
(rust-bin.stable.latest.default.override {
|
# Add any build inputs needed
|
||||||
extensions = [ "rust-src" ];
|
buildInputs = with pkgs; [
|
||||||
})
|
openssl
|
||||||
rustc
|
pkg-config
|
||||||
cargo
|
];
|
||||||
rustfmt
|
|
||||||
clippy
|
# Add any native build inputs
|
||||||
];
|
nativeBuildInputs = with pkgs; [
|
||||||
|
rustPlatform.bindgenHook
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
|
||||||
|
checkType = "debug";
|
||||||
|
env = {
|
||||||
|
OPENSSL_NO_VENDOR = "1";
|
||||||
|
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
);
|
|
||||||
|
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||||
|
individualCrateArgs = commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
inherit (craneLib.crateNameFromCargoToml { inherit src; }) version;
|
||||||
|
# NB: we disable tests since we'll run them all via cargo-nextest
|
||||||
|
doCheck = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Import rust setup
|
||||||
|
rustSetup = import ./nix/rust-setup.nix { inherit pkgs crane; };
|
||||||
|
inherit (rustSetup) rustVersion rustPlatform craneLib;
|
||||||
|
|
||||||
|
# Import vault-hier package
|
||||||
|
vault-hier = import ./nix/packages/vault-hier.nix {
|
||||||
|
inherit
|
||||||
|
pkgs
|
||||||
|
craneLib
|
||||||
|
individualCrateArgs
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Import devshell
|
||||||
|
devshell_with_src = import ./nix/devshell.nix {
|
||||||
|
inherit pkgs vault-hier rustVersion;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
checks = {
|
||||||
|
inherit vault-hier;
|
||||||
|
|
||||||
|
my-workspace-clippy = craneLib.cargoClippy (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
|
||||||
|
});
|
||||||
|
|
||||||
|
my-workspace-doc = craneLib.cargoDoc (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
});
|
||||||
|
|
||||||
|
# Check formatting
|
||||||
|
my-workspace-fmt = craneLib.cargoFmt {
|
||||||
|
inherit src;
|
||||||
|
};
|
||||||
|
|
||||||
|
my-workspace-toml-fmt = craneLib.taploFmt {
|
||||||
|
src = pkgs.lib.sources.sourceFilesBySuffices src [ ".toml" ];
|
||||||
|
# taplo arguments can be further customized below as needed
|
||||||
|
# taploExtraArgs = "--config ./taplo.toml";
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
26
nix/devshell.nix
Normal file
26
nix/devshell.nix
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
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
|
||||||
|
];
|
||||||
|
}
|
9
nix/packages/vault-hier.nix
Normal file
9
nix/packages/vault-hier.nix
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{ pkgs
|
||||||
|
, craneLib
|
||||||
|
, individualCrateArgs
|
||||||
|
,
|
||||||
|
}:
|
||||||
|
craneLib.buildPackage (individualCrateArgs // {
|
||||||
|
pname = "vault-hier";
|
||||||
|
cargoExtraArgs = "-p vault-hier";
|
||||||
|
})
|
13
nix/rust-setup.nix
Normal file
13
nix/rust-setup.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{ 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;
|
||||||
|
}
|
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "1.85.1"
|
||||||
|
components = ["rustfmt", "clippy", "rust-src"]
|
72
src/api.rs
72
src/api.rs
|
@ -1,15 +1,14 @@
|
||||||
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::{info, error, debug, instrument};
|
use tracing::{debug, error, info, 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;
|
||||||
|
@ -68,11 +67,7 @@ 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(
|
pub async fn start_api(vault_addr: &str, root_token: &str, api_port: u16) -> Result<()> {
|
||||||
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
|
||||||
|
@ -110,9 +105,7 @@ pub async fn start_api(
|
||||||
|
|
||||||
// Bind and serve
|
// Bind and serve
|
||||||
info!("Serving API at {}", addr);
|
info!("Serving API at {}", addr);
|
||||||
Server::bind(&addr)
|
Server::bind(&addr).serve(app.into_make_service()).await?;
|
||||||
.serve(app.into_make_service())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -132,7 +125,8 @@ 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.vault_client
|
let token = state
|
||||||
|
.vault_client
|
||||||
.login_user(&request.username, &request.password)
|
.login_user(&request.username, &request.password)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -160,7 +154,10 @@ 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!("Received document content: {} bytes", document_content.len());
|
debug!(
|
||||||
|
"Received document content: {} bytes",
|
||||||
|
document_content.len()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,14 +167,13 @@ async fn upload_document(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload document
|
// Upload document
|
||||||
let document_id = state.document_service
|
let document_id = state
|
||||||
|
.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
|
let document = state.document_service.get_document(&document_id).await?;
|
||||||
.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))
|
||||||
|
@ -191,12 +187,13 @@ 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
|
let document = state.document_service.get_document(&document_id).await?;
|
||||||
.get_document(&document_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
debug!("Retrieved document {} with {} signatures",
|
debug!(
|
||||||
document.id, document.signatures.len());
|
"Retrieved document {} with {} signatures",
|
||||||
|
document.id,
|
||||||
|
document.signatures.len()
|
||||||
|
);
|
||||||
|
|
||||||
Ok(Json(document))
|
Ok(Json(document))
|
||||||
}
|
}
|
||||||
|
@ -208,17 +205,22 @@ 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!("Signing request for document {} by user {}", document_id, request.username);
|
info!(
|
||||||
|
"Signing request for document {} by user {}",
|
||||||
|
document_id, request.username
|
||||||
|
);
|
||||||
|
|
||||||
state.document_service
|
state
|
||||||
|
.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
|
let document = state.document_service.get_document(&document_id).await?;
|
||||||
.get_document(&document_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!("Document {} successfully signed by {}", document_id, request.username);
|
info!(
|
||||||
|
"Document {} successfully signed by {}",
|
||||||
|
document_id, request.username
|
||||||
|
);
|
||||||
Ok(Json(document))
|
Ok(Json(document))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,12 +232,20 @@ 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.document_service
|
let verification = state
|
||||||
|
.document_service
|
||||||
.verify_document_signatures(&document_id)
|
.verify_document_signatures(&document_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
info!("Document {} verification result: {}",
|
info!(
|
||||||
document_id, if verification.is_verified { "VERIFIED" } else { "PENDING" });
|
"Document {} verification result: {}",
|
||||||
|
document_id,
|
||||||
|
if verification.is_verified {
|
||||||
|
"VERIFIED"
|
||||||
|
} else {
|
||||||
|
"PENDING"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
Ok(Json(verification))
|
Ok(Json(verification))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::{Sha256, Digest};
|
use sha2::{Digest, Sha256};
|
||||||
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,8 +88,10 @@ 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!("{}/v1/documents/data/docs/{}",
|
let url = format!(
|
||||||
self.vault_client.addr, document.id);
|
"{}/v1/documents/data/docs/{}",
|
||||||
|
self.vault_client.addr, document.id
|
||||||
|
);
|
||||||
|
|
||||||
let payload = json!({
|
let payload = json!({
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -104,7 +106,9 @@ impl DocumentService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.vault_client.client
|
let response = self
|
||||||
|
.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)
|
||||||
|
@ -118,8 +122,15 @@ impl DocumentService {
|
||||||
}
|
}
|
||||||
status => {
|
status => {
|
||||||
let error_text = response.text().await?;
|
let error_text = response.text().await?;
|
||||||
error!("Failed to store document metadata: {} - {}", status, error_text);
|
error!(
|
||||||
Err(anyhow::anyhow!("Failed to store document metadata: {} - {}", status, error_text))
|
"Failed to store document metadata: {} - {}",
|
||||||
|
status, error_text
|
||||||
|
);
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to store document metadata: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,10 +140,14 @@ 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!("{}/v1/documents/data/docs/{}",
|
let url = format!(
|
||||||
self.vault_client.addr, document_id);
|
"{}/v1/documents/data/docs/{}",
|
||||||
|
self.vault_client.addr, document_id
|
||||||
|
);
|
||||||
|
|
||||||
let response = self.vault_client.client
|
let response = self
|
||||||
|
.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()
|
||||||
|
@ -169,27 +184,49 @@ impl DocumentService {
|
||||||
}
|
}
|
||||||
|
|
||||||
let document = Document {
|
let document = Document {
|
||||||
id: json["data"]["data"]["id"].as_str().context("Missing id")?.to_string(),
|
id: json["data"]["data"]["id"]
|
||||||
name: json["data"]["data"]["name"].as_str().context("Missing name")?.to_string(),
|
.as_str()
|
||||||
hash: json["data"]["data"]["hash"].as_str().context("Missing hash")?.to_string(),
|
.context("Missing id")?
|
||||||
|
.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!("Retrieved document: {} with {} signatures", document.id, document.signatures.len());
|
debug!(
|
||||||
|
"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!("Failed to get document: {} - {}", status, error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"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(&self, document_id: &str, username: &str, user_token: &str) -> Result<()> {
|
pub async fn sign_document(
|
||||||
|
&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
|
||||||
|
@ -199,14 +236,15 @@ 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/{}",
|
let url = format!("{}/v1/transit/sign/{}", self.vault_client.addr, username);
|
||||||
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.vault_client.client
|
let response = self
|
||||||
|
.vault_client
|
||||||
|
.client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.header("X-Vault-Token", user_token)
|
.header("X-Vault-Token", user_token)
|
||||||
.json(&payload)
|
.json(&payload)
|
||||||
|
@ -224,7 +262,8 @@ 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).await?;
|
self.add_signature(document_id, username, &signature)
|
||||||
|
.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?;
|
||||||
|
@ -238,26 +277,43 @@ 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!("Failed to sign document: {} - {}", status, error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"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(&self, document_id: &str, username: &str, signature: &str) -> Result<()> {
|
async fn add_signature(
|
||||||
debug!("Adding signature from {} to document {}", username, document_id);
|
&self,
|
||||||
|
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.signatures.insert(username.to_string(), signature.to_string());
|
document
|
||||||
|
.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!("Added signature from {} to document {}", username, document_id);
|
debug!(
|
||||||
|
"Added signature from {} to document {}",
|
||||||
|
username, document_id
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,13 +325,20 @@ impl DocumentService {
|
||||||
Department::Finance => "finance",
|
Department::Finance => "finance",
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Recording {} department signature for document {}", dept_str, document_id);
|
debug!(
|
||||||
|
"Recording {} department signature for document {}",
|
||||||
|
dept_str, document_id
|
||||||
|
);
|
||||||
|
|
||||||
let url = format!("{}/v1/documents/data/dept/{}/signatures/{}",
|
let url = format!(
|
||||||
self.vault_client.addr, dept_str, document_id);
|
"{}/v1/documents/data/dept/{}/signatures/{}",
|
||||||
|
self.vault_client.addr, dept_str, document_id
|
||||||
|
);
|
||||||
|
|
||||||
// Check if department signatures already exist
|
// Check if department signatures already exist
|
||||||
let response = self.vault_client.client
|
let response = self
|
||||||
|
.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()
|
||||||
|
@ -309,7 +372,9 @@ impl DocumentService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.vault_client.client
|
let response = self
|
||||||
|
.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)
|
||||||
|
@ -318,13 +383,23 @@ impl DocumentService {
|
||||||
|
|
||||||
match response.status() {
|
match response.status() {
|
||||||
StatusCode::OK | StatusCode::NO_CONTENT => {
|
StatusCode::OK | StatusCode::NO_CONTENT => {
|
||||||
info!("Recorded signature for {} in {} department", user.username, dept_str);
|
info!(
|
||||||
|
"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!("Failed to record department signature: {} - {}", status, error_text);
|
error!(
|
||||||
Err(anyhow::anyhow!("Failed to record department signature: {} - {}", status, error_text))
|
"Failed to record department signature: {} - {}",
|
||||||
|
status, error_text
|
||||||
|
);
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to record department signature: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -364,17 +439,24 @@ impl DocumentService {
|
||||||
|
|
||||||
// Verify document signatures
|
// Verify document signatures
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
pub async fn verify_document_signatures(&self, document_id: &str) -> Result<SignatureVerification> {
|
pub async fn verify_document_signatures(
|
||||||
|
&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!("{}/v1/documents/data/config/signing_requirements",
|
let url = format!(
|
||||||
self.vault_client.addr);
|
"{}/v1/documents/data/config/signing_requirements",
|
||||||
|
self.vault_client.addr
|
||||||
|
);
|
||||||
|
|
||||||
let response = self.vault_client.client
|
let response = self
|
||||||
|
.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()
|
||||||
|
@ -384,8 +466,15 @@ 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!("Failed to get signing requirements: {} - {}", status, error_text);
|
error!(
|
||||||
return Err(anyhow::anyhow!("Failed to get signing requirements: {} - {}", status, error_text));
|
"Failed to get signing requirements: {} - {}",
|
||||||
|
status, error_text
|
||||||
|
);
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Failed to get signing requirements: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -413,13 +502,15 @@ 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.get_department_signatures(document_id, "finance").await?;
|
let finance_signatures = self
|
||||||
|
.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(),
|
||||||
|
@ -449,13 +540,24 @@ 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(&self, document_id: &str, department: &str) -> Result<Vec<String>> {
|
async fn get_department_signatures(
|
||||||
debug!("Getting {} department signatures for document {}", department, document_id);
|
&self,
|
||||||
|
document_id: &str,
|
||||||
|
department: &str,
|
||||||
|
) -> Result<Vec<String>> {
|
||||||
|
debug!(
|
||||||
|
"Getting {} department signatures for document {}",
|
||||||
|
department, document_id
|
||||||
|
);
|
||||||
|
|
||||||
let url = format!("{}/v1/documents/data/dept/{}/signatures/{}",
|
let url = format!(
|
||||||
self.vault_client.addr, department, document_id);
|
"{}/v1/documents/data/dept/{}/signatures/{}",
|
||||||
|
self.vault_client.addr, department, document_id
|
||||||
|
);
|
||||||
|
|
||||||
let response = self.vault_client.client
|
let response = self
|
||||||
|
.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()
|
||||||
|
@ -477,8 +579,12 @@ impl DocumentService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Found {} signatures for {} department on document {}",
|
debug!(
|
||||||
signatures.len(), department, document_id);
|
"Found {} signatures for {} department on document {}",
|
||||||
|
signatures.len(),
|
||||||
|
department,
|
||||||
|
document_id
|
||||||
|
);
|
||||||
|
|
||||||
Ok(signatures)
|
Ok(signatures)
|
||||||
}
|
}
|
||||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -1,11 +1,11 @@
|
||||||
// Modules that implement our hierarchical signing system
|
// Modules that implement our hierarchical signing system
|
||||||
pub mod vault_setup;
|
|
||||||
pub mod vault_init;
|
|
||||||
pub mod document_service;
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
|
pub mod document_service;
|
||||||
|
pub mod vault_init;
|
||||||
|
pub mod vault_setup;
|
||||||
|
|
||||||
// Re-export main components for easier access
|
// Re-export main components for easier access
|
||||||
pub use vault_setup::VaultClient;
|
|
||||||
pub use vault_init::initialize_vault;
|
|
||||||
pub use document_service::DocumentService;
|
|
||||||
pub use api::start_api;
|
pub use api::start_api;
|
||||||
|
pub use document_service::DocumentService;
|
||||||
|
pub use vault_init::initialize_vault;
|
||||||
|
pub use vault_setup::VaultClient;
|
||||||
|
|
96
src/main.rs
96
src/main.rs
|
@ -1,14 +1,18 @@
|
||||||
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::{fmt, EnvFilter};
|
use tracing_subscriber::{EnvFilter, fmt};
|
||||||
|
|
||||||
// Import our library
|
// Import our library
|
||||||
use vault_hier::{start_api, initialize_vault};
|
use vault_hier::{initialize_vault, start_api};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about = "Hierarchical Document Signing with HashiCorp Vault")]
|
#[command(
|
||||||
|
author,
|
||||||
|
version,
|
||||||
|
about = "Hierarchical Document Signing with HashiCorp Vault"
|
||||||
|
)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Commands,
|
command: Commands,
|
||||||
|
@ -117,27 +121,49 @@ async fn main() -> Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Server { vault_addr, api_port } => {
|
Commands::Server {
|
||||||
|
vault_addr,
|
||||||
|
api_port,
|
||||||
|
} => {
|
||||||
run_server(&vault_addr, api_port).await?;
|
run_server(&vault_addr, api_port).await?;
|
||||||
},
|
}
|
||||||
Commands::Login { username, password, api_url } => {
|
Commands::Login {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
api_url,
|
||||||
|
} => {
|
||||||
login(&username, &password, &api_url).await?;
|
login(&username, &password, &api_url).await?;
|
||||||
},
|
}
|
||||||
Commands::Upload { name, file, api_url } => {
|
Commands::Upload {
|
||||||
|
name,
|
||||||
|
file,
|
||||||
|
api_url,
|
||||||
|
} => {
|
||||||
upload_document(&name, file, &api_url).await?;
|
upload_document(&name, file, &api_url).await?;
|
||||||
},
|
}
|
||||||
Commands::Sign { document_id, username, token, api_url } => {
|
Commands::Sign {
|
||||||
|
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 { document_id, api_url } => {
|
Commands::Verify {
|
||||||
|
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 { document_id, api_url } => {
|
Commands::Get {
|
||||||
|
document_id,
|
||||||
|
api_url,
|
||||||
|
} => {
|
||||||
get_document(&document_id, &api_url).await?;
|
get_document(&document_id, &api_url).await?;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -194,8 +220,10 @@ 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("file", reqwest::multipart::Part::bytes(file_content)
|
.part(
|
||||||
.file_name(file_name.to_string()));
|
"file",
|
||||||
|
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
|
||||||
|
@ -216,7 +244,12 @@ async fn upload_document(name: &str, file_path: PathBuf, api_url: &str) -> Resul
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sign_document(document_id: &str, username: &str, token: &str, api_url: &str) -> Result<()> {
|
async fn sign_document(
|
||||||
|
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();
|
||||||
|
@ -255,16 +288,21 @@ 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!(" Departments represented: {}", data["departments_represented"]);
|
println!(
|
||||||
|
" 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!(" {}. User: {}, Department: {}, Time: {}",
|
println!(
|
||||||
i+1,
|
" {}. User: {}, Department: {}, Time: {}",
|
||||||
|
i + 1,
|
||||||
sig["username"],
|
sig["username"],
|
||||||
sig["department"],
|
sig["department"],
|
||||||
sig["timestamp"]);
|
sig["timestamp"]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -289,7 +327,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() {
|
||||||
|
@ -323,11 +361,13 @@ 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!(" {}. User: {}, Department: {}, Time: {}",
|
println!(
|
||||||
i+1,
|
" {}. User: {}, Department: {}, Time: {}",
|
||||||
|
i + 1,
|
||||||
sig["username"],
|
sig["username"],
|
||||||
sig["department"],
|
sig["department"],
|
||||||
sig["timestamp"]);
|
sig["timestamp"]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if signatures.is_empty() {
|
if signatures.is_empty() {
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use std::{
|
use std::{env, fs};
|
||||||
env,
|
use tracing::{debug, error, info, warn};
|
||||||
fs,
|
|
||||||
};
|
|
||||||
use tracing::{info, warn, error, debug};
|
|
||||||
|
|
||||||
use crate::vault_setup::VaultClient;
|
use crate::vault_setup::VaultClient;
|
||||||
|
|
||||||
|
@ -16,14 +13,17 @@ 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!("{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true", vault_addr);
|
let health_url = format!(
|
||||||
|
"{}/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,11 +56,16 @@ 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!("Found {} unseal keys. Attempting to unseal...", unseal_keys.len());
|
info!(
|
||||||
|
"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!("To unseal, set VAULT_UNSEAL_KEY_1, VAULT_UNSEAL_KEY_2, etc. environment variables.");
|
info!(
|
||||||
|
"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.");
|
||||||
|
@ -71,7 +76,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") {
|
||||||
|
@ -86,8 +91,12 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if root_token.is_empty() {
|
if root_token.is_empty() {
|
||||||
error!("Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file.");
|
error!(
|
||||||
anyhow::bail!("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."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Initialize Vault
|
// Initialize Vault
|
||||||
|
@ -112,11 +121,17 @@ 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!("Backup JSON credentials saved to Docker volume at: {}", docker_json_path);
|
info!(
|
||||||
|
"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!("Backup text credentials saved to Docker volume at: {}", docker_text_path);
|
info!(
|
||||||
|
"Backup text credentials saved to Docker volume at: {}",
|
||||||
|
docker_text_path
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("=========================================");
|
info!("=========================================");
|
||||||
|
@ -130,8 +145,10 @@ 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.keys_base64.iter()
|
let unseal_keys = init_response
|
||||||
.take(3) // We only need threshold number of keys (3)
|
.keys_base64
|
||||||
|
.iter()
|
||||||
|
.take(3) // We only need threshold number of keys (3)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,9 @@ 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::{
|
use std::{fs::File, io::Write, path::Path, time::Duration};
|
||||||
fs::File,
|
|
||||||
io::Write,
|
|
||||||
path::Path,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
use tracing::{info, error, debug, instrument};
|
use tracing::{debug, error, info, instrument};
|
||||||
|
|
||||||
// Vault API response structures
|
// Vault API response structures
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -77,9 +72,17 @@ impl VaultClient {
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
|
|
||||||
for i in 1..=30 {
|
for i in 1..=30 {
|
||||||
let health_url = format!("{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true", addr);
|
let health_url = format!(
|
||||||
|
"{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true",
|
||||||
|
addr
|
||||||
|
);
|
||||||
|
|
||||||
match client.get(&health_url).timeout(Duration::from_secs(1)).send().await {
|
match client
|
||||||
|
.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"
|
||||||
|
@ -89,7 +92,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);
|
||||||
}
|
}
|
||||||
|
@ -97,7 +100,9 @@ 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!("Timed out waiting for Vault to become available"));
|
return Err(anyhow::anyhow!(
|
||||||
|
"Timed out waiting for Vault to become available"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Vault is unavailable - sleeping (attempt {}/30)", i);
|
info!("Vault is unavailable - sleeping (attempt {}/30)", i);
|
||||||
|
@ -112,10 +117,7 @@ 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
|
let response = client.get(format!("{}/v1/sys/init", addr)).send().await?;
|
||||||
.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?;
|
||||||
|
@ -140,8 +142,10 @@ 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!("Seal status: sealed={}, threshold={}, shares={}, progress={}",
|
info!(
|
||||||
status.sealed, status.t, status.n, status.progress);
|
"Seal status: sealed={}, threshold={}, shares={}, 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?;
|
||||||
|
@ -224,9 +228,7 @@ 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 {
|
let unseal_req = UnsealRequest { key: key.clone() };
|
||||||
key: key.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = client
|
let response = client
|
||||||
.put(format!("{}/v1/sys/unseal", addr))
|
.put(format!("{}/v1/sys/unseal", addr))
|
||||||
|
@ -319,7 +321,8 @@ impl VaultClient {
|
||||||
"type": engine_type,
|
"type": engine_type,
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.client
|
let response = self
|
||||||
|
.client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.header("X-Vault-Token", &self.token)
|
.header("X-Vault-Token", &self.token)
|
||||||
.json(&payload)
|
.json(&payload)
|
||||||
|
@ -339,13 +342,23 @@ 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!("Failed to enable secrets engine: {}", error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to enable secrets engine: {}",
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
status => {
|
status => {
|
||||||
let error_text = response.text().await?;
|
let error_text = response.text().await?;
|
||||||
error!("Failed to enable secrets engine: {} - {}", status, error_text);
|
error!(
|
||||||
Err(anyhow::anyhow!("Failed to enable secrets engine: {} - {}", status, error_text))
|
"Failed to enable secrets engine: {} - {}",
|
||||||
|
status, error_text
|
||||||
|
);
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to enable secrets engine: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -360,7 +373,8 @@ impl VaultClient {
|
||||||
"type": method,
|
"type": method,
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.client
|
let response = self
|
||||||
|
.client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.header("X-Vault-Token", &self.token)
|
.header("X-Vault-Token", &self.token)
|
||||||
.json(&payload)
|
.json(&payload)
|
||||||
|
@ -380,25 +394,38 @@ 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!("Failed to enable auth method: {}", error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"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!("Failed to enable auth method: {} - {}", status, error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"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(&self, username: &str, password: &str, department: Department) -> Result<()> {
|
pub async fn create_user(
|
||||||
|
&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()).await?;
|
self.create_signing_policy(&policy_name, department.clone())
|
||||||
|
.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);
|
||||||
|
@ -407,7 +434,8 @@ impl VaultClient {
|
||||||
"policies": [policy_name],
|
"policies": [policy_name],
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.client
|
let response = self
|
||||||
|
.client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.header("X-Vault-Token", &self.token)
|
.header("X-Vault-Token", &self.token)
|
||||||
.json(&payload)
|
.json(&payload)
|
||||||
|
@ -429,7 +457,11 @@ 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!("Failed to create user: {} - {}", status, error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to create user: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,9 +476,10 @@ 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!(r#"
|
let policy = format!(
|
||||||
|
r#"
|
||||||
# Allow reading document metadata
|
# Allow reading document metadata
|
||||||
path "documents/data/docs/*" {{
|
path "documents/data/docs/*" {{
|
||||||
capabilities = ["read"]
|
capabilities = ["read"]
|
||||||
|
@ -466,14 +499,17 @@ 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.client
|
let response = self
|
||||||
|
.client
|
||||||
.put(&url)
|
.put(&url)
|
||||||
.header("X-Vault-Token", &self.token)
|
.header("X-Vault-Token", &self.token)
|
||||||
.json(&payload)
|
.json(&payload)
|
||||||
|
@ -488,7 +524,11 @@ 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!("Failed to create policy: {} - {}", status, error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to create policy: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -501,7 +541,8 @@ impl VaultClient {
|
||||||
"type": "ed25519",
|
"type": "ed25519",
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.client
|
let response = self
|
||||||
|
.client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.header("X-Vault-Token", &self.token)
|
.header("X-Vault-Token", &self.token)
|
||||||
.json(&payload)
|
.json(&payload)
|
||||||
|
@ -516,7 +557,11 @@ 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!("Failed to create signing key: {} - {}", status, error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to create signing key: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -537,7 +582,8 @@ impl VaultClient {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.client
|
let response = self
|
||||||
|
.client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.header("X-Vault-Token", &self.token)
|
.header("X-Vault-Token", &self.token)
|
||||||
.json(&payload)
|
.json(&payload)
|
||||||
|
@ -552,7 +598,11 @@ 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!("Failed to store user metadata: {} - {}", status, error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to store user metadata: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -567,7 +617,8 @@ 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).await?;
|
self.create_user(&username, &password, Department::Legal)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create 5 users in Finance department
|
// Create 5 users in Finance department
|
||||||
|
@ -575,7 +626,8 @@ 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).await?;
|
self.create_user(&username, &password, Department::Finance)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup document signing requirements
|
// Setup document signing requirements
|
||||||
|
@ -590,7 +642,10 @@ 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!("{}/v1/documents/data/config/signing_requirements", self.addr);
|
let url = format!(
|
||||||
|
"{}/v1/documents/data/config/signing_requirements",
|
||||||
|
self.addr
|
||||||
|
);
|
||||||
let payload = json!({
|
let payload = json!({
|
||||||
"data": {
|
"data": {
|
||||||
"total_required": 3,
|
"total_required": 3,
|
||||||
|
@ -610,7 +665,8 @@ impl VaultClient {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.client
|
let response = self
|
||||||
|
.client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.header("X-Vault-Token", &self.token)
|
.header("X-Vault-Token", &self.token)
|
||||||
.json(&payload)
|
.json(&payload)
|
||||||
|
@ -624,8 +680,15 @@ impl VaultClient {
|
||||||
}
|
}
|
||||||
status => {
|
status => {
|
||||||
let error_text = response.text().await?;
|
let error_text = response.text().await?;
|
||||||
error!("Failed to configure signing requirements: {} - {}", status, error_text);
|
error!(
|
||||||
Err(anyhow::anyhow!("Failed to configure signing requirements: {} - {}", status, error_text))
|
"Failed to configure signing requirements: {} - {}",
|
||||||
|
status, error_text
|
||||||
|
);
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to configure signing requirements: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -640,11 +703,7 @@ impl VaultClient {
|
||||||
"password": password,
|
"password": password,
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.client
|
let response = self.client.post(&url).json(&payload).send().await?;
|
||||||
.post(&url)
|
|
||||||
.json(&payload)
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
match response.status() {
|
match response.status() {
|
||||||
StatusCode::OK => {
|
StatusCode::OK => {
|
||||||
|
@ -660,7 +719,11 @@ 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!("Failed to login: {} - {}", status, error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to login: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -672,7 +735,8 @@ 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.client
|
let response = self
|
||||||
|
.client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
.header("X-Vault-Token", &self.token)
|
.header("X-Vault-Token", &self.token)
|
||||||
.send()
|
.send()
|
||||||
|
@ -694,7 +758,10 @@ impl VaultClient {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Retrieved user info for {} in {:?} department", username, department);
|
debug!(
|
||||||
|
"Retrieved user info for {} in {:?} department",
|
||||||
|
username, department
|
||||||
|
);
|
||||||
Ok(User {
|
Ok(User {
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
department,
|
department,
|
||||||
|
@ -703,7 +770,11 @@ 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!("Failed to get user info: {} - {}", status, error_text))
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to get user info: {} - {}",
|
||||||
|
status,
|
||||||
|
error_text
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue