Compare commits

..

6 Commits

Author SHA1 Message Date
6894e80a8f docs: Simplified sequence diagram 2026-06-28 21:24:20 +05:30
920e4d11c1 docs: Added credits
All checks were successful
Deployment Pipeline / test (push) Successful in 2m33s
Deployment Pipeline / deploy (push) Successful in 33s
2026-06-28 21:20:56 +05:30
e38b8cbee3 Merge branch 'master' of git.adityagupta.dev:sortedcord/bootstrap-auth-server
Some checks failed
Deployment Pipeline / deploy (push) Has been cancelled
Deployment Pipeline / test (push) Has been cancelled
2026-06-25 17:03:35 +05:30
64925a0946 docs: Add installation and usage instructions 2026-06-25 17:03:18 +05:30
b2ade25246 ci: Added github workflows for tests 2026-06-25 16:58:16 +05:30
ba482aab1d ci: Prevent workflow runs on trivial file changes 2026-06-25 16:56:02 +05:30
5 changed files with 164 additions and 43 deletions

View File

@@ -4,6 +4,10 @@ on:
push:
branches:
- master
paths-ignore:
- '**.md'
- '.gitignore'
- 'docs/**'
jobs:
test:

View File

@@ -4,9 +4,17 @@ on:
push:
branches-ignore:
- master
paths-ignore:
- '**.md'
- '.gitignore'
- 'docs/**'
pull_request:
branches:
- master
paths-ignore:
- '**.md'
- '.gitignore'
- 'docs/**'
jobs:
test:

43
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Continuous Integration
on:
push:
branches-ignore:
- master
paths-ignore:
- '**.md'
- '.gitignore'
- 'docs/**'
pull_request:
branches:
- master
paths-ignore:
- '**.md'
- '.gitignore'
- 'docs/**'
jobs:
test:
# Use GitHub's official hosted runners instead of your self-hosted ones
runs-on: ubuntu-latest
steps:
- name: Checkout Code
# We can use the official checkout action here since GitHub's runners have Node.js installed natively
uses: actions/checkout@v4
- name: Install Rust Toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Install Dependencies
run: sudo apt-get update && sudo apt-get install -y sqlite3
- name: Cache Cargo Dependencies
uses: Swatinem/rust-cache@v2
- name: Check Formatting
run: cargo fmt -- --check || echo "Formatting issues found, but proceeding anyway for now"
- name: Run Tests
run: cargo test

119
README.md
View File

@@ -1,8 +1,6 @@
# Bootstrap Authentication - Asymmetric Crypto (RSA) Auth & E2E Secrets
## Introduction
This project is a centralized authentication and secrets provisioning server. It is designed to allow arbitrary client machines to securely request access and retrieve sensitive environment variables or secrets during their initialization phase. It relies on strict cryptographic verification using Ed25519 asymmetric keys to ensure that only explicitly authorized clients receive secrets.
This project is a centralized authentication and secrets provisioning server for [**Bootstrap**](https://github.com/sortedcord/bootstrap) (*Not to be confused the with CSS framework*). It is designed to allow arbitrary client machines to securely request access and retrieve sensitive environment variables or secrets during their initialization phase. It relies on strict cryptographic verification using Ed25519 asymmetric keys to ensure that only explicitly authorized clients receive secrets.
By utilizing modern encryption protocols, this system provides a highly secure mechanism to bootstrap new servers, workstations, or deployment targets without ever transmitting plain text secrets over the network or storing them insecurely.
@@ -16,16 +14,88 @@ Once the device is approved, the new client must prove it actually possesses the
This strict protocol guarantees that even if the network transport is entirely compromised, the payload remains mathematically inaccessible to anyone except the exact client device that generated the initial request.
```mermaid
sequenceDiagram
autonumber
actor Admin
participant Requester as Requester Client (b me)
participant Server as Auth Server
participant Approver as Approver Client (b trust)
Requester->>Server: POST /api/register (Host, OS, Public Key)
Server-->>Requester: 200 OK (user_code, challenge_nonce)
Note over Requester: Displays user_code & begins polling...
Admin->>Approver: Exec: b trust <user_code>
Approver->>Server: GET /api/pending/<user_code>
Server-->>Approver: 200 OK (Requester Public Key)
Note over Approver: Admin confirms public key fingerprint
Approver->>Server: POST /api/approve (user_code, Admin Signature & Fingerprint)
Note over Server: Server verifies signature against admin list
Requester->>Server: POST /api/challenge/poll (user_code, Signature of Nonce)
Server-->>Requester: 200 OK (Encrypted Secrets Payload)
Note over Requester: Decrypts payload using local Ed25519 private key
```
## Installation
To compile and install the server directly from the source code, you will need the Rust toolchain installed on your system.
Execute the following command in your terminal to build the release binary:
```bash
cargo build --release
```
The compiled executable will be located in the target release directory. You can copy this binary to any location in your execution path.
Alternatively, you can utilize the included Docker configuration to build a container image without needing the Rust compiler installed locally:
```bash
docker build -t bootstrap_auth_server:latest .
```
## Deployment and Configuration
The server requires minimal setup. It compiles to a single binary and utilizes SQLite for its persistent storage, eliminating the need for complex database infrastructure.
The application is configured using standard environment variables:
* `SERVER_MASTER_KEY` : A highly secure string used to derive the AES encryption key. This is required for encrypting and decrypting the database secrets.
* `SERVER_MASTER_KEY` : A highly secure string used to derive the AES encryption key. This is required for encrypting and decrypting the database secrets.
> [!NOTE]
> You can easily generate a cryptographically secure string directly in your terminal using the following utility:
> ```bash
> openssl rand -base64 32
> ```
* `DATABASE_URL` : The connection string for the SQLite database. Defaults to a local file named data.db.
* SERVER_PORT : The network port the Axum server will listen on. Defaults to 3000.
## Usage
Before starting the application, you must define the required environment variables. Create a file named secrets.json in the same directory where you plan to execute the server if you wish to provision initial data.
Start the server by passing the master key as an environment variable:
```bash
SERVER_MASTER_KEY="your_highly_secure_random_string_here" cargo run --release
```
If you are using Docker, you can start the container in detached mode and map the necessary ports and volumes:
```bash
docker run -d \
-p 3000:3000 \
-e SERVER_MASTER_KEY="your_highly_secure_random_string_here" \
-v /absolute/path/to/data:/app/data \
bootstrap_auth_server:latest
```
Once the server is running, you can monitor the logs to verify that the database migrations have completed successfully and that the server is actively listening for incoming authentication requests on the specified port.
## Initial Secrets Provisioning
When the server starts, it automatically looks for a file named secrets.json in the current working directory. If this file is found, the server parses the key and value pairs, encrypts the values securely using the master key, and stores them persistently in the database.
@@ -75,45 +145,8 @@ A JSON object containing:
A JSON object containing:
* `encrypted_secrets` (base64 encoded string containing the age encrypted payload)
## Authentication Sequence
The complete cryptographic handshake and provisioning flow is illustrated below:
## Credits
```mermaid
sequenceDiagram
autonumber
participant C as New Client
participant S as Authentication Server
participant DB as SQLite Database
participant A as Administrator Device
* [David Wong](https://cryptologie.net/posts/eddsa-ed25519-ed25519-ietf-ed25519ph-ed25519ctx-hasheddsa-pureeddsa-wtf/) for his brief intro to EdDSA and Ed25519 on his blog post.
Note over C: Generates Ed25519 Key Pair
C->>S: POST /api/register (Public Key, System Info)
S->>DB: Insert pending device record
S-->>C: Returns User Code and Challenge Nonce
Note over C, A: Out of band communication
C->>A: Displays User Code and Public Key Fingerprint
Note over A: Signs New Client Public Key
A->>S: POST /api/approve (User Code, Cryptographic Signature)
S->>DB: Validates Administrator Key
S->>S: Verifies Signature mathematically
S->>DB: Updates pending device to approved status
S-->>A: Acknowledges approval
loop Polling mechanism
Note over C: Signs Challenge Nonce
C->>S: POST /api/challenge/poll (User Code, Nonce Signature)
S->>S: Verifies Client Signature mathematically
S->>DB: Checks approval status
end
S->>DB: Retrieves AES encrypted secrets
S->>S: Decrypts secrets in memory using Master Key
S->>S: Encrypts secrets using age protocol for Client Public Key
S->>DB: Purges pending request record
S-->>C: Returns age encrypted payload
Note over C: Decrypts payload using local Private Key
```

View File

@@ -132,6 +132,39 @@ pub async fn approve(
Ok(Json("Approved!").into_response())
}
#[derive(Serialize)]
pub struct PendingDeviceRes {
pub hostname: String,
pub os: String,
pub public_key: String,
}
pub async fn get_pending_device(
State(state): State<AppState>,
axum::extract::Path(user_code): axum::extract::Path<String>,
) -> Result<Json<PendingDeviceRes>, StatusCode> {
let record = sqlx::query_as::<_, (String, String, String)>(
"SELECT hostname, os, public_key FROM devices WHERE user_code = ? AND approved_at IS NULL",
)
.bind(&user_code)
.fetch_optional(&state.pool)
.await
.map_err(|err| {
tracing::error!("Database error fetching pending device: {:?}", err);
StatusCode::INTERNAL_SERVER_ERROR
})?;
if let Some((hostname, os, public_key)) = record {
Ok(Json(PendingDeviceRes {
hostname,
os,
public_key,
}))
} else {
Err(StatusCode::NOT_FOUND)
}
}
#[derive(Deserialize)]
pub struct PollReq {
pub user_code: String,