Compare commits
5 commits
c662dfbfd8
...
c65ae95b43
Author | SHA1 | Date | |
---|---|---|---|
|
c65ae95b43 | ||
|
c132ba1722 | ||
|
92f37d6b37 | ||
|
b445634b53 | ||
|
26e81cef17 |
10
CLAUDE.md
10
CLAUDE.md
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## Build & Test Commands
|
## Build & Test Commands
|
||||||
- Build & run: `cargo build && cargo run`
|
- Build & run: `cargo build && cargo run`
|
||||||
|
- Run server: `cargo run server`
|
||||||
- Run tests: `cargo test` (or `cargo test -- --nocapture` for verbose output)
|
- Run tests: `cargo test` (or `cargo test -- --nocapture` for verbose output)
|
||||||
- Run single test: `cargo test test_name -- --nocapture`
|
- Run single test: `cargo test test_name -- --nocapture`
|
||||||
- Docker test: `./test_docker.sh` (includes vault initialization)
|
- Docker test: `./test_docker.sh` (includes vault initialization)
|
||||||
|
@ -9,6 +10,15 @@
|
||||||
- Lint: `cargo clippy -- -D warnings`
|
- Lint: `cargo clippy -- -D warnings`
|
||||||
- Format: `cargo fmt --all`
|
- Format: `cargo fmt --all`
|
||||||
|
|
||||||
|
## CLI Commands
|
||||||
|
- Start server: `cargo run server [--vault-addr URL] [--api-port PORT]`
|
||||||
|
- Login: `cargo run login --username USER --password PASS [--api-url URL]`
|
||||||
|
- Upload document: `cargo run upload --name NAME --file PATH [--api-url URL]`
|
||||||
|
- Sign document: `cargo run sign --document-id ID --username USER --token TOKEN [--api-url URL]`
|
||||||
|
- Verify document: `cargo run verify --document-id ID [--api-url URL]`
|
||||||
|
- List documents: `cargo run list [--api-url URL]`
|
||||||
|
- Get document details: `cargo run get --document-id ID [--api-url URL]`
|
||||||
|
|
||||||
## Code Style Guidelines
|
## Code Style Guidelines
|
||||||
- **Formatting**: Follow rustfmt conventions (run `cargo fmt` before committing)
|
- **Formatting**: Follow rustfmt conventions (run `cargo fmt` before committing)
|
||||||
- **Imports**: Group by crate (stdlib → external → internal)
|
- **Imports**: Group by crate (stdlib → external → internal)
|
||||||
|
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
reqwest = { version = "0.11.18", features = ["json"] }
|
reqwest = { version = "0.11.18", features = ["json", "multipart"] }
|
||||||
tokio = { version = "1.28.0", features = ["full"] }
|
tokio = { version = "1.28.0", features = ["full"] }
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.160", features = ["derive"] }
|
||||||
serde_json = "1.0.96"
|
serde_json = "1.0.96"
|
||||||
|
@ -15,3 +15,4 @@ sha2 = "0.10.6"
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
|
clap = { version = "4.4.6", features = ["derive", "env"] }
|
||||||
|
|
|
@ -31,5 +31,7 @@ RUN apt-get update && apt-get install -y vault
|
||||||
WORKDIR /usr/local/bin
|
WORKDIR /usr/local/bin
|
||||||
|
|
||||||
COPY --from=builder /usr/src/vault-hier/target/release/vault-hier .
|
COPY --from=builder /usr/src/vault-hier/target/release/vault-hier .
|
||||||
# Set the entrypoint to directly run the Rust binary
|
|
||||||
|
# Set the entrypoint to directly run the Rust binary with the server command by default
|
||||||
ENTRYPOINT ["/usr/local/bin/vault-hier"]
|
ENTRYPOINT ["/usr/local/bin/vault-hier"]
|
||||||
|
CMD ["server"]
|
||||||
|
|
42
README.md
42
README.md
|
@ -7,6 +7,7 @@ This project implements a hierarchical document signing system using HashiCorp V
|
||||||
- **Hierarchical Signing**: Requires 3 of 5 signatures to validate a document, with at least 1 signature from each department
|
- **Hierarchical Signing**: Requires 3 of 5 signatures to validate a document, with at least 1 signature from each department
|
||||||
- **Department Structure**: Two departments (Legal and Finance) with 5 users each
|
- **Department Structure**: Two departments (Legal and Finance) with 5 users each
|
||||||
- **Document API**: Upload, sign, and verify documents through a RESTful API
|
- **Document API**: Upload, sign, and verify documents through a RESTful API
|
||||||
|
- **CLI Client**: Interact with the system through command-line interface
|
||||||
- **Vault Integration**: Leverages HashiCorp Vault's Transit engine for cryptographic operations
|
- **Vault Integration**: Leverages HashiCorp Vault's Transit engine for cryptographic operations
|
||||||
|
|
||||||
## System Architecture
|
## System Architecture
|
||||||
|
@ -50,7 +51,46 @@ The system consists of:
|
||||||
- Legal department: legal1/legal1pass through legal5/legal5pass
|
- Legal department: legal1/legal1pass through legal5/legal5pass
|
||||||
- Finance department: finance1/finance1pass through finance5/finance5pass
|
- Finance department: finance1/finance1pass through finance5/finance5pass
|
||||||
|
|
||||||
### API Usage Examples
|
### CLI Commands
|
||||||
|
|
||||||
|
The project includes a command-line interface to interact with the API:
|
||||||
|
|
||||||
|
1. **Start the Server**:
|
||||||
|
```bash
|
||||||
|
cargo run server [--vault-addr URL] [--api-port PORT]
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Login**:
|
||||||
|
```bash
|
||||||
|
cargo run login --username USER --password PASS [--api-url URL]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Upload Document**:
|
||||||
|
```bash
|
||||||
|
cargo run upload --name NAME --file PATH [--api-url URL]
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Sign Document**:
|
||||||
|
```bash
|
||||||
|
cargo run sign --document-id ID --username USER --token TOKEN [--api-url URL]
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Verify Document**:
|
||||||
|
```bash
|
||||||
|
cargo run verify --document-id ID [--api-url URL]
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **List Documents**:
|
||||||
|
```bash
|
||||||
|
cargo run list [--api-url URL]
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Get Document Details**:
|
||||||
|
```bash
|
||||||
|
cargo run get --document-id ID [--api-url URL]
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Usage Examples (curl)
|
||||||
|
|
||||||
1. **Login**:
|
1. **Login**:
|
||||||
```bash
|
```bash
|
||||||
|
|
|
@ -39,6 +39,8 @@ services:
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: none
|
condition: none
|
||||||
|
# Run with 'server' command
|
||||||
|
command: server --vault-addr http://vault:8200 --api-port 3000
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
vault-data:
|
vault-data:
|
||||||
|
|
325
src/main.rs
325
src/main.rs
|
@ -1,40 +1,343 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::env;
|
use clap::{Parser, Subcommand};
|
||||||
|
use std::path::PathBuf;
|
||||||
use tracing::{info};
|
use tracing::{info};
|
||||||
use tracing_subscriber::{fmt, EnvFilter};
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
// Import our library
|
// Import our library
|
||||||
use vault_hier::{start_api, initialize_vault};
|
use vault_hier::{start_api, initialize_vault};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(author, version, about = "Hierarchical Document Signing with HashiCorp Vault")]
|
||||||
|
struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Commands {
|
||||||
|
/// Start the vault-hier server
|
||||||
|
Server {
|
||||||
|
/// Address of the Vault server
|
||||||
|
#[arg(long, env = "VAULT_ADDR", default_value = "http://127.0.0.1:8200")]
|
||||||
|
vault_addr: String,
|
||||||
|
|
||||||
|
/// Port for the API server
|
||||||
|
#[arg(long, env = "API_PORT", default_value_t = 3000)]
|
||||||
|
api_port: u16,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Login to get an authentication token
|
||||||
|
Login {
|
||||||
|
/// Username for authentication
|
||||||
|
#[arg(short, long)]
|
||||||
|
username: String,
|
||||||
|
|
||||||
|
/// Password for authentication
|
||||||
|
#[arg(short, long)]
|
||||||
|
password: String,
|
||||||
|
|
||||||
|
/// API server address
|
||||||
|
#[arg(long, default_value = "http://localhost:3000")]
|
||||||
|
api_url: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Upload a document
|
||||||
|
Upload {
|
||||||
|
/// Name of the document
|
||||||
|
#[arg(short, long)]
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// Path to the document file
|
||||||
|
#[arg(short, long)]
|
||||||
|
file: PathBuf,
|
||||||
|
|
||||||
|
/// API server address
|
||||||
|
#[arg(long, default_value = "http://localhost:3000")]
|
||||||
|
api_url: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Sign a document
|
||||||
|
Sign {
|
||||||
|
/// Document ID to sign
|
||||||
|
#[arg(short, long)]
|
||||||
|
document_id: String,
|
||||||
|
|
||||||
|
/// Username for signing
|
||||||
|
#[arg(short, long)]
|
||||||
|
username: String,
|
||||||
|
|
||||||
|
/// Authentication token
|
||||||
|
#[arg(short, long)]
|
||||||
|
token: String,
|
||||||
|
|
||||||
|
/// API server address
|
||||||
|
#[arg(long, default_value = "http://localhost:3000")]
|
||||||
|
api_url: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Verify a document's signatures
|
||||||
|
Verify {
|
||||||
|
/// Document ID to verify
|
||||||
|
#[arg(short, long)]
|
||||||
|
document_id: String,
|
||||||
|
|
||||||
|
/// API server address
|
||||||
|
#[arg(long, default_value = "http://localhost:3000")]
|
||||||
|
api_url: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// List all documents
|
||||||
|
List {
|
||||||
|
/// API server address
|
||||||
|
#[arg(long, default_value = "http://localhost:3000")]
|
||||||
|
api_url: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Get document details
|
||||||
|
Get {
|
||||||
|
/// Document ID to retrieve
|
||||||
|
#[arg(short, long)]
|
||||||
|
document_id: String,
|
||||||
|
|
||||||
|
/// API server address
|
||||||
|
#[arg(long, default_value = "http://localhost:3000")]
|
||||||
|
api_url: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
// Initialize tracing
|
// Initialize tracing
|
||||||
fmt()
|
fmt()
|
||||||
.with_env_filter(EnvFilter::from_default_env().add_directive("vault_hier=info".parse()?))
|
.with_env_filter(EnvFilter::from_default_env().add_directive("vault_hier=trace".parse()?))
|
||||||
.with_target(false)
|
.with_target(false)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
// Get Vault address from env var or use default
|
let cli = Cli::parse();
|
||||||
let vault_addr = env::var("VAULT_ADDR").unwrap_or_else(|_| "http://127.0.0.1:8200".to_string());
|
|
||||||
|
|
||||||
// Get API port from env var or use default
|
match cli.command {
|
||||||
let api_port = env::var("API_PORT")
|
Commands::Server { vault_addr, api_port } => {
|
||||||
.unwrap_or_else(|_| "3000".to_string())
|
run_server(&vault_addr, api_port).await?;
|
||||||
.parse::<u16>()
|
},
|
||||||
.unwrap_or(3000);
|
Commands::Login { username, password, api_url } => {
|
||||||
|
login(&username, &password, &api_url).await?;
|
||||||
|
},
|
||||||
|
Commands::Upload { name, file, api_url } => {
|
||||||
|
upload_document(&name, file, &api_url).await?;
|
||||||
|
},
|
||||||
|
Commands::Sign { document_id, username, token, api_url } => {
|
||||||
|
sign_document(&document_id, &username, &token, &api_url).await?;
|
||||||
|
},
|
||||||
|
Commands::Verify { document_id, api_url } => {
|
||||||
|
verify_document(&document_id, &api_url).await?;
|
||||||
|
},
|
||||||
|
Commands::List { api_url } => {
|
||||||
|
list_documents(&api_url).await?;
|
||||||
|
},
|
||||||
|
Commands::Get { document_id, api_url } => {
|
||||||
|
get_document(&document_id, &api_url).await?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_server(vault_addr: &str, api_port: u16) -> Result<()> {
|
||||||
info!("Vault address: {}", vault_addr);
|
info!("Vault address: {}", vault_addr);
|
||||||
info!("Connecting to Vault at: {}", vault_addr);
|
info!("Connecting to Vault at: {}", vault_addr);
|
||||||
|
|
||||||
// Initialize and unseal Vault, get the root token
|
// Initialize and unseal Vault, get the root token
|
||||||
let root_token = initialize_vault(&vault_addr).await?;
|
let root_token = initialize_vault(vault_addr).await?;
|
||||||
|
|
||||||
info!("Starting hierarchical document signing API...");
|
info!("Starting hierarchical document signing API...");
|
||||||
|
|
||||||
// Start the hierarchical signing API
|
// Start the hierarchical signing API
|
||||||
start_api(&vault_addr, &root_token, api_port).await?;
|
start_api(vault_addr, &root_token, api_port).await?;
|
||||||
|
|
||||||
info!("API server shutdown. Exiting.");
|
info!("API server shutdown. Exiting.");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn login(username: &str, password: &str, api_url: &str) -> Result<()> {
|
||||||
|
info!("Logging in as user: {}", username);
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let response = client
|
||||||
|
.post(&format!("{}/api/login", api_url))
|
||||||
|
.json(&serde_json::json!({
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let data: serde_json::Value = response.json().await?;
|
||||||
|
println!("Login successful!");
|
||||||
|
println!("Token: {}", data["token"]);
|
||||||
|
println!("Department: {}", data["department"]);
|
||||||
|
} else {
|
||||||
|
let error_text = response.text().await?;
|
||||||
|
println!("Login failed: {}", error_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn upload_document(name: &str, file_path: PathBuf, api_url: &str) -> Result<()> {
|
||||||
|
info!("Uploading document: {}", name);
|
||||||
|
|
||||||
|
let file_content = tokio::fs::read(&file_path).await?;
|
||||||
|
let file_name = file_path.file_name().unwrap().to_string_lossy();
|
||||||
|
|
||||||
|
let form = reqwest::multipart::Form::new()
|
||||||
|
.text("name", name.to_string())
|
||||||
|
.part("file", reqwest::multipart::Part::bytes(file_content)
|
||||||
|
.file_name(file_name.to_string()));
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let response = client
|
||||||
|
.post(&format!("{}/api/documents", api_url))
|
||||||
|
.multipart(form)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let data: serde_json::Value = response.json().await?;
|
||||||
|
println!("Document uploaded successfully!");
|
||||||
|
println!("Document ID: {}", data["id"]);
|
||||||
|
} else {
|
||||||
|
let error_text = response.text().await?;
|
||||||
|
println!("Upload failed: {}", error_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sign_document(document_id: &str, username: &str, token: &str, api_url: &str) -> Result<()> {
|
||||||
|
info!("Signing document: {}", document_id);
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let response = client
|
||||||
|
.post(&format!("{}/api/documents/{}/sign", api_url, document_id))
|
||||||
|
.json(&serde_json::json!({
|
||||||
|
"username": username,
|
||||||
|
"token": token,
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let data: serde_json::Value = response.json().await?;
|
||||||
|
println!("Document signed successfully!");
|
||||||
|
println!("Signature ID: {}", data["signature_id"]);
|
||||||
|
} else {
|
||||||
|
let error_text = response.text().await?;
|
||||||
|
println!("Signing failed: {}", error_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_document(document_id: &str, api_url: &str) -> Result<()> {
|
||||||
|
info!("Verifying document: {}", document_id);
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let response = client
|
||||||
|
.get(&format!("{}/api/documents/{}/verify", api_url, document_id))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let data: serde_json::Value = response.json().await?;
|
||||||
|
println!("Verification result:");
|
||||||
|
println!(" Valid: {}", data["valid"]);
|
||||||
|
println!(" Total signatures: {}", data["signature_count"]);
|
||||||
|
println!(" Departments represented: {}", data["departments_represented"]);
|
||||||
|
|
||||||
|
if let Some(signatures) = data["signatures"].as_array() {
|
||||||
|
println!("\nSignatures:");
|
||||||
|
for (i, sig) in signatures.iter().enumerate() {
|
||||||
|
println!(" {}. User: {}, Department: {}, Time: {}",
|
||||||
|
i+1,
|
||||||
|
sig["username"],
|
||||||
|
sig["department"],
|
||||||
|
sig["timestamp"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let error_text = response.text().await?;
|
||||||
|
println!("Verification failed: {}", error_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_documents(api_url: &str) -> Result<()> {
|
||||||
|
info!("Listing all documents");
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let response = client
|
||||||
|
.get(&format!("{}/api/documents", api_url))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let data: Vec<serde_json::Value> = response.json().await?;
|
||||||
|
println!("Documents:");
|
||||||
|
|
||||||
|
for (i, doc) in data.iter().enumerate() {
|
||||||
|
println!(" {}. ID: {}, Name: {}", i+1, doc["id"], doc["name"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.is_empty() {
|
||||||
|
println!(" No documents found.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let error_text = response.text().await?;
|
||||||
|
println!("Failed to list documents: {}", error_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_document(document_id: &str, api_url: &str) -> Result<()> {
|
||||||
|
info!("Getting document: {}", document_id);
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let response = client
|
||||||
|
.get(&format!("{}/api/documents/{}", api_url, document_id))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let doc: serde_json::Value = response.json().await?;
|
||||||
|
println!("Document details:");
|
||||||
|
println!(" ID: {}", doc["id"]);
|
||||||
|
println!(" Name: {}", doc["name"]);
|
||||||
|
println!(" Hash: {}", doc["hash"]);
|
||||||
|
println!(" Created: {}", doc["created_at"]);
|
||||||
|
|
||||||
|
if let Some(signatures) = doc["signatures"].as_array() {
|
||||||
|
println!("\nSignatures:");
|
||||||
|
for (i, sig) in signatures.iter().enumerate() {
|
||||||
|
println!(" {}. User: {}, Department: {}, Time: {}",
|
||||||
|
i+1,
|
||||||
|
sig["username"],
|
||||||
|
sig["department"],
|
||||||
|
sig["timestamp"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if signatures.is_empty() {
|
||||||
|
println!(" No signatures yet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let error_text = response.text().await?;
|
||||||
|
println!("Failed to get document: {}", error_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -442,6 +442,9 @@ impl VaultClient {
|
||||||
Department::Finance => "finance",
|
Department::Finance => "finance",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get the username from the policy name (remove "-policy" suffix)
|
||||||
|
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
|
||||||
|
@ -449,8 +452,8 @@ impl VaultClient {
|
||||||
capabilities = ["read"]
|
capabilities = ["read"]
|
||||||
}}
|
}}
|
||||||
|
|
||||||
# Allow signing with user's key
|
# Allow signing with user's key - use explicit username instead of identity.entity.name
|
||||||
path "transit/sign/{{{{identity.entity.name}}}}" {{
|
path "transit/sign/{}" {{
|
||||||
capabilities = ["update"]
|
capabilities = ["update"]
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -463,7 +466,7 @@ impl VaultClient {
|
||||||
path "documents/data/dept/{}/signatures/*" {{
|
path "documents/data/dept/{}/signatures/*" {{
|
||||||
capabilities = ["create", "read", "update"]
|
capabilities = ["create", "read", "update"]
|
||||||
}}
|
}}
|
||||||
"#, 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!({
|
||||||
|
|
182
test_local.sh
182
test_local.sh
|
@ -16,6 +16,56 @@ else
|
||||||
VAULT_PID_FILE="./vault.pid"
|
VAULT_PID_FILE="./vault.pid"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Use a fixed port for the API to make debugging easier
|
||||||
|
API_PORT=3456
|
||||||
|
API_URL="http://localhost:$API_PORT"
|
||||||
|
|
||||||
|
# Error handling function
|
||||||
|
handle_error() {
|
||||||
|
echo "Error encountered, showing logs:"
|
||||||
|
if [ -f "./api_server.log" ]; then
|
||||||
|
echo "=== API Server Log ==="
|
||||||
|
cat ./api_server.log
|
||||||
|
echo "======================"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "./vault_server.log" ]; then
|
||||||
|
echo "=== Vault Server Log ==="
|
||||||
|
tail -n 100 ./vault_server.log
|
||||||
|
echo "======================="
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Call cleanup
|
||||||
|
cleanup
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to cleanup on exit
|
||||||
|
cleanup() {
|
||||||
|
echo "Cleaning up resources..."
|
||||||
|
if [ -n "$SERVER_PID" ]; then
|
||||||
|
echo "Stopping server process ($SERVER_PID)..."
|
||||||
|
kill -9 $SERVER_PID 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [ -f "$VAULT_PID_FILE" ]; then
|
||||||
|
VAULT_PID=$(cat "$VAULT_PID_FILE")
|
||||||
|
echo "Stopping vault process ($VAULT_PID)..."
|
||||||
|
kill -9 $VAULT_PID 2>/dev/null || true
|
||||||
|
rm -f "$VAULT_PID_FILE"
|
||||||
|
fi
|
||||||
|
killall vault-hier
|
||||||
|
rm -f test_document.txt
|
||||||
|
rm -rf /tmp/vault-test
|
||||||
|
# We'll keep the logs for inspection
|
||||||
|
# rm -f ./vault_server.log
|
||||||
|
# rm -f ./api_server.log
|
||||||
|
echo "Cleanup complete."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set trap for errors and interrupts
|
||||||
|
trap handle_error ERR
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
# Check if Vault is installed
|
# Check if Vault is installed
|
||||||
if ! command -v vault &> /dev/null; then
|
if ! command -v vault &> /dev/null; then
|
||||||
echo "Vault is not installed. Please install it first."
|
echo "Vault is not installed. Please install it first."
|
||||||
|
@ -85,13 +135,117 @@ for i in {1..10}; do
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
# Build and run the Rust application
|
# Build and run the Rust application with the server command
|
||||||
echo "Building and running the Rust application..."
|
echo "Building and running the vault-hier server..."
|
||||||
cargo build && cargo run
|
echo "Using API port: $API_PORT"
|
||||||
|
cargo build && cargo run server --vault-addr "$VAULT_ADDR" --api-port $API_PORT > ./api_server.log 2>&1 &
|
||||||
|
SERVER_PID=$!
|
||||||
|
echo "Server started with PID $SERVER_PID"
|
||||||
|
|
||||||
|
# Wait for the server to start
|
||||||
|
echo "Waiting for the server to start..."
|
||||||
|
sleep 10 # Increased wait time to ensure server is ready
|
||||||
|
|
||||||
|
# Test the server with some client operations
|
||||||
|
echo "Testing the client operations..."
|
||||||
|
|
||||||
|
# Create a sample file for testing
|
||||||
|
echo "Creating a sample file for testing..."
|
||||||
|
echo "This is a test document" > test_document.txt
|
||||||
|
|
||||||
|
# Test login with legal1 user
|
||||||
|
echo "Testing login with legal1 user..."
|
||||||
|
LOGIN_OUTPUT=$(cargo run login --username legal1 --password legal1pass --api-url "$API_URL")
|
||||||
|
echo "$LOGIN_OUTPUT"
|
||||||
|
LEGAL_TOKEN=$(echo "$LOGIN_OUTPUT" | grep "Token:" | awk '{print $2}' | tr -d '"')
|
||||||
|
|
||||||
|
if [ -z "$LEGAL_TOKEN" ]; then
|
||||||
|
echo "Login failed for legal1. Could not get token."
|
||||||
|
handle_error
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Login successful for legal1, got token: ${LEGAL_TOKEN:0:8}..."
|
||||||
|
|
||||||
|
# Test upload document
|
||||||
|
echo "Testing document upload..."
|
||||||
|
UPLOAD_OUTPUT=$(cargo run upload --name "Test Document" --file test_document.txt --api-url "$API_URL")
|
||||||
|
echo "$UPLOAD_OUTPUT"
|
||||||
|
DOC_ID=$(echo "$UPLOAD_OUTPUT" | grep "Document ID:" | awk '{print $3}' | tr -d '"')
|
||||||
|
|
||||||
|
if [ -z "$DOC_ID" ]; then
|
||||||
|
echo "Upload failed. Could not get document ID."
|
||||||
|
handle_error
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Upload successful, got document ID: $DOC_ID"
|
||||||
|
|
||||||
|
# Test using direct curl with the legal token
|
||||||
|
echo "Testing document signing with legal token via curl..."
|
||||||
|
echo "Using token: $LEGAL_TOKEN"
|
||||||
|
SIGN_OUTPUT=$(curl -s -X POST "$API_URL/api/documents/$DOC_ID/sign" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"username\":\"legal1\",\"token\":\"$LEGAL_TOKEN\"}")
|
||||||
|
echo "$SIGN_OUTPUT"
|
||||||
|
|
||||||
|
if echo "$SIGN_OUTPUT" | grep -q "signatures"; then
|
||||||
|
echo "Document signed successfully"
|
||||||
|
else
|
||||||
|
echo "Signing failed with curl. Trying with finance user..."
|
||||||
|
|
||||||
|
# Try with finance user
|
||||||
|
echo "Testing login with finance1 user..."
|
||||||
|
LOGIN_OUTPUT=$(cargo run login --username finance1 --password finance1pass --api-url "$API_URL")
|
||||||
|
echo "$LOGIN_OUTPUT"
|
||||||
|
FINANCE_TOKEN=$(echo "$LOGIN_OUTPUT" | grep "Token:" | awk '{print $2}' | tr -d '"')
|
||||||
|
|
||||||
|
if [ -z "$FINANCE_TOKEN" ]; then
|
||||||
|
echo "Login failed for finance1. Could not get token."
|
||||||
|
handle_error
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Login successful for finance1, got token: ${FINANCE_TOKEN:0:8}..."
|
||||||
|
|
||||||
|
echo "Testing document signing with finance token via curl..."
|
||||||
|
SIGN_OUTPUT=$(curl -s -X POST "$API_URL/api/documents/$DOC_ID/sign" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"username\":\"finance1\",\"token\":\"$FINANCE_TOKEN\"}")
|
||||||
|
echo "$SIGN_OUTPUT"
|
||||||
|
|
||||||
|
if ! echo "$SIGN_OUTPUT" | grep -q "signatures"; then
|
||||||
|
echo "Signing failed with both legal and finance users. Skipping rest of test."
|
||||||
|
handle_error
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test verification
|
||||||
|
echo "Testing document verification..."
|
||||||
|
VERIFY_OUTPUT=$(cargo run verify --document-id "$DOC_ID" --api-url "$API_URL")
|
||||||
|
echo "$VERIFY_OUTPUT"
|
||||||
|
|
||||||
|
if echo "$VERIFY_OUTPUT" | grep -q "Verification result"; then
|
||||||
|
echo "Verification successful"
|
||||||
|
else
|
||||||
|
echo "Verification failed"
|
||||||
|
handle_error
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test getting document details
|
||||||
|
echo "Testing get document details..."
|
||||||
|
GET_OUTPUT=$(cargo run get --document-id "$DOC_ID" --api-url "$API_URL")
|
||||||
|
echo "$GET_OUTPUT"
|
||||||
|
|
||||||
|
if echo "$GET_OUTPUT" | grep -q "Document details"; then
|
||||||
|
echo "Get document successful"
|
||||||
|
else
|
||||||
|
echo "Get document failed"
|
||||||
|
handle_error
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if the credentials file was created
|
# Check if the credentials file was created
|
||||||
if [ -f "vault-credentials.txt" ]; then
|
if [ -f "vault-credentials.txt" ] || [ -f "vault-credentials.json" ]; then
|
||||||
echo "Test successful! Credentials were saved to vault-credentials.txt"
|
echo "Test successful! Credentials were saved"
|
||||||
|
|
||||||
|
if [ -f "vault-credentials.txt" ]; then
|
||||||
# Extract the unseal keys for demonstration
|
# Extract the unseal keys for demonstration
|
||||||
UNSEAL_KEYS=$(grep "Key" vault-credentials.txt | head -n 3 | awk '{print $3}')
|
UNSEAL_KEYS=$(grep "Key" vault-credentials.txt | head -n 3 | awk '{print $3}')
|
||||||
ROOT_TOKEN=$(grep "Root Token" vault-credentials.txt | awk '{print $3}')
|
ROOT_TOKEN=$(grep "Root Token" vault-credentials.txt | awk '{print $3}')
|
||||||
|
@ -99,21 +253,7 @@ if [ -f "vault-credentials.txt" ]; then
|
||||||
echo "Root Token: $ROOT_TOKEN"
|
echo "Root Token: $ROOT_TOKEN"
|
||||||
echo "First 3 Unseal Keys (needed for threshold):"
|
echo "First 3 Unseal Keys (needed for threshold):"
|
||||||
echo "$UNSEAL_KEYS"
|
echo "$UNSEAL_KEYS"
|
||||||
|
fi
|
||||||
# Clean up temporary files
|
|
||||||
rm -f vault-credentials.txt
|
|
||||||
else
|
|
||||||
echo "Test failed! Credentials file was not created."
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\nTest complete! Cleaning up..."
|
echo -e "\nTest complete! All tests passed."
|
||||||
# Stop Vault server
|
|
||||||
kill -9 $VAULT_PID
|
|
||||||
rm "$VAULT_PID_FILE"
|
|
||||||
|
|
||||||
# Clean up test environment
|
|
||||||
rm -rf /tmp/vault-test
|
|
||||||
rm -f ./vault_server.log
|
|
||||||
|
|
||||||
echo "All cleaned up. Test successful!"
|
|
||||||
|
|
Loading…
Reference in a new issue