commit 441b37187fed12cadc53f3579d156083e94a8215
parent 07f18ff7bf19595900a2921d47d6d57f1abdadf6
Author: Henrique Chan Carvalho Machado <henriqueccmachado@tecnico.ulisboa.pt>
Date: Mon, 19 Jan 2026 19:15:43 +0100
Unify config and disable webhook worker
Unify configuration by switching to a single [kych-oauth2-gateway] section with
uppercase keys and integrated client_* sections, disable compilation of the
webhook worker binary while keeping its source, and update the client management
CLI to match the new config format; additionally make UNIX socket permissions
configurable via UNIXPATH_MODE (default 0666), adjust related logging, and
replace the old example configs with a unified kych.conf.example.
Diffstat:
7 files changed, 123 insertions(+), 148 deletions(-)
diff --git a/kych_oauth2_gateway/Cargo.toml b/kych_oauth2_gateway/Cargo.toml
@@ -2,6 +2,7 @@
name = "kych"
version = "0.0.1"
edition = "2024"
+autobins = false
[lib]
name = "kych_oauth2_gateway_lib"
@@ -12,10 +13,6 @@ name = "kych-oauth2-gateway"
path = "src/main.rs"
[[bin]]
-name = "kych-oauth2-gateway-webhook-worker"
-path = "src/bin/webhook_worker.rs"
-
-[[bin]]
name = "kych-client-management"
path = "src/bin/client_management_cli.rs"
diff --git a/kych_oauth2_gateway/clients.conf.example b/kych_oauth2_gateway/clients.conf.example
@@ -1,22 +0,0 @@
-# OAuth2 Gateway Clients Configuration
-#
-# Use the client management CLI to sync this file to the database:
-# client-mgmt sync clients.conf
-
-[client_example]
-client_id = 1
-client_secret = 2
-webhook_url =
-verifier_url =
-verifier_management_api_path = /management/api/verifications
-redirect_uri =
-accepted_issuer_dids = did:tdw:QmPEZPhDFR4nEYSFK5bMnvECqdpf1tPTPJuWs9QrMjCumw:identifier-reg.trust-infra.swiyu-int.admin.ch:api:v1:did:9a5559f0-b81c-4368-a170-e7b4ae424527
-
-# [another_client]
-# client_id = client_staging_01
-# client_secret = another_secret
-# webhook_url = https://staging.example.com/webhook
-# verifier_url = https://verifier-staging.example.com
-# verifier_management_api_path = /api/v1/verifications
-# redirect_uri = https://staging.example.com/callback
-# accepted_issuer_dids = did:key:staging1
diff --git a/kych_oauth2_gateway/config.ini.example b/kych_oauth2_gateway/config.ini.example
@@ -1,19 +0,0 @@
-[server]
-host =
-port =
-#socket_path =
-
-[database]
-url =
-
-[crypto]
-nonce_bytes = 32
-token_bytes = 32
-authorization_code_bytes = 32
-
-[webhook_worker]
-retry_delay_server_error = 60
-retry_delay_forbidden = 60
-retry_delay_other = 3600
-fallback_poll_secs = 300
-batch_size = 100
diff --git a/kych_oauth2_gateway/src/bin/client_management_cli.rs b/kych_oauth2_gateway/src/bin/client_management_cli.rs
@@ -4,11 +4,12 @@
//!
//! Set DATABASE_URL environment variable to connect to the database.
//! Usage:
-//! client-mgmt client list
-//! client-mgmt client show <client_id>
-//! client-mgmt client create --client-id <id> --secret <secret> ...
-//! client-mgmt client update <client_id> --webhook-url <url>
-//! client-mgmt client delete <client_id>
+//! kych-client-management list
+//! kych-client-management show <client_id>
+//! kych-client-management create --client-id <id> --secret <secret> ...
+//! kych-client-management update <client_id> --webhook-url <url>
+//! kych-client-management sync kych.conf
+//! kych-client-management delete <client_id>
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
@@ -85,9 +86,9 @@ enum Commands {
accepted_issuer_dids: Option<String>,
},
- /// Sync clients from a configuration file
+ /// Sync clients from configuration file (reads [client_*] sections)
Sync {
- /// Path to clients.conf file
+ /// Path to kych.conf file
config_file: String,
/// Remove clients not in config file
@@ -337,24 +338,24 @@ async fn cmd_sync_clients(pool: &sqlx::PgPool, config_file: &str, prune: bool) -
for (section_name, properties) in ini.iter() {
let section_name = match section_name {
- Some(name) => name,
- None => continue,
+ Some(name) if name.starts_with("client_") => name,
+ _ => continue,
};
println!("\nProcessing section: [{}]", section_name);
- let client_id = properties.get("client_id")
- .ok_or_else(|| anyhow::anyhow!("Missing client_id in section [{}]", section_name))?;
- let client_secret = properties.get("client_secret")
- .ok_or_else(|| anyhow::anyhow!("Missing client_secret in section [{}]", section_name))?;
- let webhook_url = properties.get("webhook_url")
- .ok_or_else(|| anyhow::anyhow!("Missing webhook_url in section [{}]", section_name))?;
- let verifier_url = properties.get("verifier_url")
- .ok_or_else(|| anyhow::anyhow!("Missing verifier_url in section [{}]", section_name))?;
-
- let verifier_api_path = properties.get("verifier_management_api_path");
- let redirect_uri = properties.get("redirect_uri");
- let accepted_issuer_dids = properties.get("accepted_issuer_dids");
+ let client_id = properties.get("CLIENT_ID")
+ .ok_or_else(|| anyhow::anyhow!("Missing CLIENT_ID in section [{}]", section_name))?;
+ let client_secret = properties.get("CLIENT_SECRET")
+ .ok_or_else(|| anyhow::anyhow!("Missing CLIENT_SECRET in section [{}]", section_name))?;
+ let webhook_url = properties.get("WEBHOOK_URL")
+ .ok_or_else(|| anyhow::anyhow!("Missing WEBHOOK_URL in section [{}]", section_name))?;
+ let verifier_url = properties.get("VERIFIER_URL")
+ .ok_or_else(|| anyhow::anyhow!("Missing VERIFIER_URL in section [{}]", section_name))?;
+
+ let verifier_api_path = properties.get("VERIFIER_MANAGEMENT_API_PATH");
+ let redirect_uri = properties.get("REDIRECT_URI");
+ let accepted_issuer_dids = properties.get("ACCEPTED_ISSUER_DIDS");
synced_client_ids.insert(client_id.to_string());
diff --git a/kych_oauth2_gateway/src/config.rs b/kych_oauth2_gateway/src/config.rs
@@ -3,12 +3,14 @@ use serde::{Deserialize, Serialize};
use ini::Ini;
use std::path::Path;
+const MAIN_SECTION: &str = "kych-oauth2-gateway";
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub server: ServerConfig,
pub database: DatabaseConfig,
pub crypto: CryptoConfig,
- pub webhook_worker: WebhookWorkerConfig,
+ pub clients: Vec<ClientConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -16,6 +18,7 @@ pub struct ServerConfig {
pub host: Option<String>,
pub port: Option<u16>,
pub socket_path: Option<String>,
+ pub socket_mode: u32,
}
impl ServerConfig {
@@ -24,15 +27,15 @@ impl ServerConfig {
let has_unix = self.socket_path.is_some();
if has_tcp && has_unix {
- anyhow::bail!("Cannot specify both TCP (host/port) and Unix socket (socket_path)");
+ anyhow::bail!("Cannot specify both TCP (HOST/PORT) and Unix socket (UNIXPATH)");
}
if !has_tcp && !has_unix {
- anyhow::bail!("Must specify either TCP (host/port) or Unix socket (socket_path)");
+ anyhow::bail!("Must specify either TCP (HOST/PORT) or Unix socket (UNIXPATH)");
}
if has_tcp && (self.host.is_none() || self.port.is_none()) {
- anyhow::bail!("Host and port must be specified for TCP");
+ anyhow::bail!("HOST and PORT must both be specified for TCP");
}
Ok(())
@@ -56,12 +59,15 @@ pub struct CryptoConfig {
}
#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct WebhookWorkerConfig {
- pub retry_delay_server_error: i64,
- pub retry_delay_forbidden: i64,
- pub retry_delay_other: i64,
- pub fallback_poll_secs: u64,
- pub batch_size: i64,
+pub struct ClientConfig {
+ pub section_name: String,
+ pub client_id: String,
+ pub client_secret: String,
+ pub webhook_url: String,
+ pub verifier_url: String,
+ pub verifier_management_api_path: String,
+ pub redirect_uri: Option<String>,
+ pub accepted_issuer_dids: Option<String>,
}
impl Config {
@@ -69,97 +75,110 @@ impl Config {
let ini = Ini::load_from_file(path.as_ref())
.context("Failed to load config file")?;
- let server_section = ini
- .section(Some("server"))
- .context("Missing [server] section")?;
+ let main_section = ini
+ .section(Some(MAIN_SECTION))
+ .context(format!("Missing [{}] section", MAIN_SECTION))?;
- let host = server_section.get("host").map(|s| s.to_string());
- let port = server_section
- .get("port")
+ let host = main_section.get("HOST")
+ .filter(|s| !s.is_empty())
+ .map(|s| s.to_string());
+ let port = main_section
+ .get("PORT")
+ .filter(|s| !s.is_empty())
.map(|s| s.parse::<u16>())
.transpose()
- .context("Invalid port")?;
- let socket_path = server_section.get("socket_path").map(|s| s.to_string());
+ .context("Invalid PORT")?;
+ let socket_path = main_section.get("UNIXPATH")
+ .filter(|s| !s.is_empty())
+ .map(|s| s.to_string());
+ let socket_mode = main_section
+ .get("UNIXPATH_MODE")
+ .filter(|s| !s.is_empty())
+ .map(|s| u32::from_str_radix(s, 8))
+ .transpose()
+ .context("Invalid UNIXPATH_MODE (expected octal, e.g. 666)")?
+ .unwrap_or(0o666);
let server = ServerConfig {
host,
port,
socket_path,
+ socket_mode,
};
server.validate()?;
- let database_section = ini
- .section(Some("database"))
- .context("Missing [database] section")?;
-
let database = DatabaseConfig {
- url: database_section
- .get("url")
- .context("Missing database.url")?
+ url: main_section
+ .get("DATABASE")
+ .context("Missing DATABASE")?
.to_string(),
};
- let crypto_section = ini
- .section(Some("crypto"))
- .context("Missing [crypto] section")?;
-
let crypto = CryptoConfig {
- nonce_bytes: crypto_section
- .get("nonce_bytes")
- .context("Missing crypto.nonce_bytes")?
+ nonce_bytes: main_section
+ .get("NONCE_BYTES")
+ .context("Missing NONCE_BYTES")?
.parse()
- .context("Invalid crypto.nonce_bytes")?,
- token_bytes: crypto_section
- .get("token_bytes")
- .context("Missing crypto.token_bytes")?
+ .context("Invalid NONCE_BYTES")?,
+ token_bytes: main_section
+ .get("TOKEN_BYTES")
+ .context("Missing TOKEN_BYTES")?
.parse()
- .context("Invalid crypto.token_bytes")?,
- authorization_code_bytes: crypto_section
- .get("authorization_code_bytes")
- .context("Missing crypto.authorization_code_bytes")?
+ .context("Invalid TOKEN_BYTES")?,
+ authorization_code_bytes: main_section
+ .get("AUTH_CODE_BYTES")
+ .context("Missing AUTH_CODE_BYTES")?
.parse()
- .context("Invalid crypto.authorization_code_bytes")?,
+ .context("Invalid AUTH_CODE_BYTES")?,
};
- let webhook_worker_section = ini
- .section(Some("webhook_worker"))
- .context("Missing [webhook_worker] section")?;
-
- let webhook_worker = WebhookWorkerConfig {
- retry_delay_server_error: webhook_worker_section
- .get("retry_delay_server_error")
- .context("Missing webhook_worker.retry_delay_server_error")?
- .parse()
- .context("Invalid webhook_worker.retry_delay_server_error")?,
- retry_delay_forbidden: webhook_worker_section
- .get("retry_delay_forbidden")
- .context("Missing webhook_worker.retry_delay_forbidden")?
- .parse()
- .context("Invalid webhook_worker.retry_delay_forbidden")?,
- retry_delay_other: webhook_worker_section
- .get("retry_delay_other")
- .context("Missing webhook_worker.retry_delay_other")?
- .parse()
- .context("Invalid webhook_worker.retry_delay_other")?,
- fallback_poll_secs: webhook_worker_section
- .get("fallback_poll_secs")
- .context("Missing webhook_worker.fallback_poll_secs")?
- .parse()
- .context("Invalid webhook_worker.fallback_poll_secs")?,
- batch_size: webhook_worker_section
- .get("batch_size")
- .context("Missing webhook_worker.batch_size")?
- .parse()
- .context("Invalid webhook_worker.batch_size")?,
- };
+ let mut clients = Vec::new();
+ for (section_name, properties) in ini.iter() {
+ let section_name = match section_name {
+ Some(name) if name.starts_with("client_") => name,
+ _ => continue,
+ };
+
+ let client_id = properties.get("CLIENT_ID")
+ .context(format!("Missing CLIENT_ID in section [{}]", section_name))?
+ .to_string();
+ let client_secret = properties.get("CLIENT_SECRET")
+ .context(format!("Missing CLIENT_SECRET in section [{}]", section_name))?
+ .to_string();
+ let webhook_url = properties.get("WEBHOOK_URL")
+ .context(format!("Missing WEBHOOK_URL in section [{}]", section_name))?
+ .to_string();
+ let verifier_url = properties.get("VERIFIER_URL")
+ .context(format!("Missing VERIFIER_URL in section [{}]", section_name))?
+ .to_string();
+ let verifier_management_api_path = properties.get("VERIFIER_MANAGEMENT_API_PATH")
+ .unwrap_or("/management/api/verifications")
+ .to_string();
+ let redirect_uri = properties.get("REDIRECT_URI")
+ .filter(|s| !s.is_empty())
+ .map(|s| s.to_string());
+ let accepted_issuer_dids = properties.get("ACCEPTED_ISSUER_DIDS")
+ .filter(|s| !s.is_empty())
+ .map(|s| s.to_string());
+
+ clients.push(ClientConfig {
+ section_name: section_name.to_string(),
+ client_id,
+ client_secret,
+ webhook_url,
+ verifier_url,
+ verifier_management_api_path,
+ redirect_uri,
+ accepted_issuer_dids,
+ });
+ }
Ok(Config {
server,
database,
crypto,
- webhook_worker,
+ clients,
})
}
}
-
diff --git a/kych_oauth2_gateway/src/lib.rs b/kych_oauth2_gateway/src/lib.rs
@@ -3,5 +3,4 @@ pub mod handlers;
pub mod models;
pub mod state;
pub mod crypto;
-pub mod db;
-pub mod worker;
-\ No newline at end of file
+pub mod db;
+\ No newline at end of file
diff --git a/kych_oauth2_gateway/src/main.rs b/kych_oauth2_gateway/src/main.rs
@@ -67,17 +67,17 @@ async fn main() -> Result<()> {
if config.server.is_unix_socket() {
let socket_path = config.server.socket_path.as_ref().unwrap();
+ let socket_mode = config.server.socket_mode;
if std::path::Path::new(socket_path).exists() {
- tracing::warn!("Removing existing socket file: {}", socket_path);
+ tracing::warn!("Removing left-over `{}' from previous execution", socket_path);
std::fs::remove_file(socket_path)?;
}
let listener = tokio::net::UnixListener::bind(socket_path)?;
- let permissions = std::fs::Permissions::from_mode(0o766);
- let _ = fs::set_permissions(socket_path, permissions);
-
- tracing::info!("Server listening on Unix socket: {}", socket_path);
+ let permissions = std::fs::Permissions::from_mode(socket_mode);
+ fs::set_permissions(socket_path, permissions)?;
+ tracing::info!("set socket '{}' to mode {:o}", socket_path, socket_mode);
axum::serve(listener, app).await?;
} else {