Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,180 changes: 777 additions & 403 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ tokio = { version = "1", features = ["full"] }
serde_json = "1.0.127"
toml = "0.8.19"
uuid = { version = "1.10.0", features = ["v4"] }
fern = "0.6"
log = "0.4"
chrono = "0.4"
serde_yaml = "0.9"
hostname = "0.3"
113 changes: 113 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from flask import Flask, request, render_template, redirect, url_for, session, flash
import toml
import os

app = Flask(__name__)
app.secret_key = 'your_secret_key' # Change to something more secure!

ACL_PATH = './acl/acl.toml'

ADMIN_USERNAME = 'admin'
ADMIN_PASSWORD = 'password123'

def load_acl():
if not os.path.exists(ACL_PATH):
return {'users': []}
return toml.load(ACL_PATH)

def save_acl(data):
with open(ACL_PATH, 'w') as f:
toml.dump(data, f)

@app.route('/')
def home():
if 'logged_in' not in session:
return redirect(url_for('login'))
return redirect(url_for('dashboard'))

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
if request.form['username'] == ADMIN_USERNAME and request.form['password'] == ADMIN_PASSWORD:
session['logged_in'] = True
return redirect(url_for('dashboard'))
flash('Invalid credentials!')
return render_template('login.html')

@app.route('/dashboard')
def dashboard():
if 'logged_in' not in session:
return redirect(url_for('login'))

acl_data = load_acl()
return render_template('dashboard.html', users=acl_data['users'])

@app.route('/add-user', methods=['GET', 'POST'])
def add_user():
if 'logged_in' not in session:
return redirect(url_for('login'))

success = False # Flag to check if user was added

if request.method == 'POST':
name = request.form['name']
email = request.form['email']
hosts_allowed = request.form['hosts'].split(',')
validity = request.form['validity']

acl_data = load_acl()
acl_data['users'].append({
'name': name,
'email': email,
'hosts_allowed': hosts_allowed,
'validity': validity
})
save_acl(acl_data)

success = True # Set success flag to True

return render_template('add_user.html', success=success)

@app.route('/edit-user/<name>', methods=['GET', 'POST'])
def edit_user(name):
if 'logged_in' not in session:
return redirect(url_for('login'))

acl_data = load_acl()
user = next((user for user in acl_data['users'] if user['name'] == name), None)

if not user:
flash('User not found!')
return redirect(url_for('dashboard'))

success = False # Flag to indicate if the update was successful

if request.method == 'POST':
user['email'] = request.form['email']
user['hosts_allowed'] = request.form['hosts'].split(',')
user['validity'] = request.form['validity']
save_acl(acl_data)

success = True # Set success flag to True

return render_template('edit_user.html', user=user, success=success)

@app.route('/delete-user/<name>', methods=['POST'])
def delete_user(name):
if 'logged_in' not in session:
return redirect(url_for('login'))

acl_data = load_acl()
acl_data['users'] = [user for user in acl_data['users'] if user['name'] != name]
save_acl(acl_data)

flash(f'User {name} deleted successfully!')
return redirect(url_for('dashboard'))

@app.route('/logout')
def logout():
session.clear()
return redirect(url_for('login'))

if __name__ == '__main__':
app.run(debug=True, port=5000)
108 changes: 108 additions & 0 deletions certificates.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
[2025-02-05 18:31:36][INFO] 🔧 Configured for debug.
[2025-02-05 18:31:36][INFO] address: 127.0.0.1
[2025-02-05 18:31:36][INFO] port: 8000
[2025-02-05 18:31:36][INFO] workers: 8
[2025-02-05 18:31:36][INFO] max blocking threads: 512
[2025-02-05 18:31:36][INFO] ident: Rocket
[2025-02-05 18:31:36][INFO] IP header: X-Real-IP
[2025-02-05 18:31:36][INFO] limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
[2025-02-05 18:31:36][INFO] temp dir: /tmp
[2025-02-05 18:31:36][INFO] http/2: true
[2025-02-05 18:31:36][INFO] keep-alive: 5s
[2025-02-05 18:31:36][INFO] tls: disabled
[2025-02-05 18:31:36][INFO] shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
[2025-02-05 18:31:36][INFO] log level: normal
[2025-02-05 18:31:36][INFO] cli colors: true
[2025-02-05 18:31:36][INFO] 📬 Routes:
[2025-02-05 18:31:36][INFO] (handle_post) POST /handle-post
[2025-02-05 18:31:36][INFO] (options) OPTIONS /handle-post
[2025-02-05 18:31:36][INFO] (FileServer: keys) GET /public/<path..> [10]
[2025-02-05 18:31:36][INFO] 📡 Fairings:
[2025-02-05 18:31:36][INFO] Shield (liftoff, response, singleton)
[2025-02-05 18:31:36][INFO] Cross-Origin-Resource-Sharing Fairing (response)
[2025-02-05 18:31:36][INFO] 🛡️ Shield:
[2025-02-05 18:31:36][INFO] X-Content-Type-Options: nosniff
[2025-02-05 18:31:36][INFO] X-Frame-Options: SAMEORIGIN
[2025-02-05 18:31:36][INFO] Permissions-Policy: interest-cohort=()
[2025-02-05 18:31:36][WARN] 🚀 Rocket has launched from http://127.0.0.1:8000
[2025-02-05 18:32:05][INFO] OPTIONS /handle-post/:
[2025-02-05 18:32:05][INFO] Matched: (options) OPTIONS /handle-post
[2025-02-05 18:32:05][INFO] Outcome: Success(200 OK)
[2025-02-05 18:32:05][INFO] Response succeeded.
[2025-02-05 18:32:05][INFO] POST /handle-post/ application/json:
[2025-02-05 18:32:05][INFO] Matched: (handle_post) POST /handle-post
[2025-02-05 18:32:06][INFO] Certificate generated for nitingr.03@gmail.com: type=User, principals=["host1", "host2"]
[2025-02-05 18:32:06][INFO] Outcome: Success(200 OK)
[2025-02-05 18:32:06][INFO] Response succeeded.
[2025-02-05 18:32:37][WARN] Received SIGINT. Requesting shutdown.
[2025-02-05 18:32:37][INFO] Shutdown requested. Waiting for pending I/O...
[2025-02-05 18:32:37][INFO] Graceful shutdown completed successfully.
[2025-02-06 13:43:26][INFO] 🔧 Configured for debug.
[2025-02-06 13:43:26][INFO] address: 127.0.0.1
[2025-02-06 13:43:26][INFO] port: 8000
[2025-02-06 13:43:26][INFO] workers: 8
[2025-02-06 13:43:26][INFO] max blocking threads: 512
[2025-02-06 13:43:26][INFO] ident: Rocket
[2025-02-06 13:43:26][INFO] IP header: X-Real-IP
[2025-02-06 13:43:26][INFO] limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
[2025-02-06 13:43:26][INFO] temp dir: /tmp
[2025-02-06 13:43:26][INFO] http/2: true
[2025-02-06 13:43:26][INFO] keep-alive: 5s
[2025-02-06 13:43:26][INFO] tls: disabled
[2025-02-06 13:43:26][INFO] shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
[2025-02-06 13:43:26][INFO] log level: normal
[2025-02-06 13:43:26][INFO] cli colors: true
[2025-02-06 13:43:26][INFO] 📬 Routes:
[2025-02-06 13:43:26][INFO] (handle_post) POST /handle-post
[2025-02-06 13:43:26][INFO] (options) OPTIONS /handle-post
[2025-02-06 13:43:26][INFO] (FileServer: keys) GET /public/<path..> [10]
[2025-02-06 13:43:26][INFO] 📡 Fairings:
[2025-02-06 13:43:26][INFO] Shield (liftoff, response, singleton)
[2025-02-06 13:43:26][INFO] Cross-Origin-Resource-Sharing Fairing (response)
[2025-02-06 13:43:26][INFO] 🛡️ Shield:
[2025-02-06 13:43:26][INFO] X-Frame-Options: SAMEORIGIN
[2025-02-06 13:43:26][INFO] Permissions-Policy: interest-cohort=()
[2025-02-06 13:43:26][INFO] X-Content-Type-Options: nosniff
[2025-02-06 13:43:26][WARN] 🚀 Rocket has launched from http://127.0.0.1:8000
[2025-02-06 14:19:24][INFO] OPTIONS /handle-post/:
[2025-02-06 14:19:24][INFO] Matched: (options) OPTIONS /handle-post
[2025-02-06 14:19:24][INFO] Outcome: Success(200 OK)
[2025-02-06 14:19:24][INFO] Response succeeded.
[2025-02-06 14:19:24][INFO] POST /handle-post/ application/json:
[2025-02-06 14:19:24][INFO] Matched: (handle_post) POST /handle-post
[2025-02-06 14:19:25][INFO] Certificate generated for nitingr.03@gmail.com: type=User, principals=["host1", "host2"]
[2025-02-06 14:19:25][INFO] Outcome: Success(200 OK)
[2025-02-06 14:19:25][INFO] Response succeeded.
[2025-02-06 14:45:17][WARN] Received SIGINT. Requesting shutdown.
[2025-02-06 14:45:17][INFO] Shutdown requested. Waiting for pending I/O...
[2025-02-06 14:45:17][INFO] Graceful shutdown completed successfully.
[2025-02-06 14:46:29][INFO] Configured for debug.
[2025-02-06 14:46:29][INFO] address: 127.0.0.1
[2025-02-06 14:46:29][INFO] port: 8000
[2025-02-06 14:46:29][INFO] workers: 8
[2025-02-06 14:46:29][INFO] max blocking threads: 512
[2025-02-06 14:46:29][INFO] ident: Rocket
[2025-02-06 14:46:29][INFO] IP header: X-Real-IP
[2025-02-06 14:46:29][INFO] limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
[2025-02-06 14:46:29][INFO] temp dir: /tmp
[2025-02-06 14:46:29][INFO] http/2: true
[2025-02-06 14:46:29][INFO] keep-alive: 5s
[2025-02-06 14:46:29][INFO] tls: disabled
[2025-02-06 14:46:29][INFO] shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
[2025-02-06 14:46:29][INFO] log level: normal
[2025-02-06 14:46:29][INFO] cli colors: false
[2025-02-06 14:46:29][INFO] Routes:
[2025-02-06 14:46:29][INFO] (handle_post) POST /handle-post
[2025-02-06 14:46:29][INFO] (options) OPTIONS /handle-post
[2025-02-06 14:46:29][INFO] (FileServer: keys) GET /public/<path..> [10]
[2025-02-06 14:46:29][INFO] Fairings:
[2025-02-06 14:46:29][INFO] Cross-Origin-Resource-Sharing Fairing (response)
[2025-02-06 14:46:29][INFO] Shield (liftoff, response, singleton)
[2025-02-06 14:46:29][INFO] Shield:
[2025-02-06 14:46:29][INFO] X-Frame-Options: SAMEORIGIN
[2025-02-06 14:46:29][INFO] Permissions-Policy: interest-cohort=()
[2025-02-06 14:46:29][INFO] X-Content-Type-Options: nosniff
[2025-02-06 14:46:29][WARN] Rocket has launched from http://127.0.0.1:8000
[2025-02-06 14:54:43][WARN] Received SIGINT. Requesting shutdown.
[2025-02-06 14:54:43][INFO] Shutdown requested. Waiting for pending I/O...
[2025-02-06 14:54:43][INFO] Graceful shutdown completed successfully.
16 changes: 16 additions & 0 deletions certificates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
email: nitingr.03@gmail.com
cert_type: User
timestamp: 2025-02-05T13:02:06.549286981+00:00
principals:
- host1
- host2

---
email: nitingr.03@gmail.com
cert_type: User
timestamp: 2025-02-06T08:49:25.161753418+00:00
principals:
- host1
- host2

37 changes: 37 additions & 0 deletions src/key_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ use std::env;
use std::fs;
use std::time::{SystemTime, UNIX_EPOCH};
use uuid::Uuid;
use chrono::Utc;
use log::info;
use serde::Serialize;
use std::fs::OpenOptions;
use std::io::Write;

#[derive(Serialize)]
struct CertificateLog {
email: String,
cert_type: String,
timestamp: String,
principals: Vec<String>,
}

pub fn sign_key(
encoded_key: &str,
Expand Down Expand Up @@ -62,5 +75,29 @@ pub fn sign_key(
cert_builder.comment(email.to_string())?;

let cert: Certificate = cert_builder.sign(ca_key)?;
//println!("Certificate generated successfully:\n{:?}", cert);

let log_entry = CertificateLog {
email: email.clone(),
cert_type: if is_host { "Host".to_string() } else { "User".to_string() },
timestamp: Utc::now().to_rfc3339(),
principals: principals_permitted.clone(),
};

let yaml_entry = serde_yaml::to_string(&log_entry)?;

let mut file = OpenOptions::new()
.append(true)
.create(true)
.open("certificates.yml")
.expect("Failed to open or create certificates.yml");

writeln!(file, "---\n{}", yaml_entry).expect("Failed to write to certificates.yml");

info!(
"Certificate generated for {}: type={}, principals={:?}",
email, log_entry.cert_type, principals_permitted
);

Ok(cert.to_string())
}
27 changes: 27 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ use rocket::fs::FileServer;
#[macro_use]
extern crate rocket;

use fern::Dispatch;
use log::LevelFilter;
use chrono::Local;

#[launch]
fn rocket() -> _ {
setup_logger().expect("Failed to initialize logger");

config::load_env(); // Load environment variables

let project_location =
Expand All @@ -28,3 +34,24 @@ fn rocket() -> _ {
FileServer::from(format!("{project_location}/ca-server/keys/")),
)
}

fn setup_logger() -> Result<(), fern::InitError> {
// Create a log file handler
let log_file = fern::log_file("certificates.log").expect("Failed to create log file");

// Set up the logger with both console and file logging
Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{}][{}] {}",
Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
message
))
})
.level(LevelFilter::Info) // set desired logging level
.chain(std::io::stdout()) // log to console
.chain(log_file) // log to file
.apply()?; // apply the logger setup
Ok(())
}
5 changes: 2 additions & 3 deletions src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ use rocket::serde::json::Json;
pub async fn handle_post(token: Result<BearerToken, Status>, data: Json<SignRequest>) -> String {
match token {
// NOTE: Remove the hardcodedToken
Ok(token) => {
println!("{}", token.0);
println!("Reached line 14");
Ok(token) => {
let email: String = if token.0 == "hardcodedToken" {
data.identity.clone()
} else {
Expand Down Expand Up @@ -62,3 +60,4 @@ pub async fn handle_post(token: Result<BearerToken, Status>, data: Json<SignRequ
pub fn options() -> Status {
Status::Ok
}

Loading