feat: initial commit
This commit is contained in:
commit
4bb3305e84
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
1739
Cargo.lock
generated
Normal file
1739
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "pdns-oidc-tsigkey-manager"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
config = "0.13.1"
|
||||||
|
axum = "0.6.18"
|
||||||
|
serde = { version = "1.0.164", features = ["derive"] }
|
||||||
|
serde_with = "3.0.0"
|
||||||
|
serde_json = "1.0.99"
|
||||||
|
reqwest = { version = "0.11.18", features = ["json"] }
|
||||||
|
url = { version = "2.4.0", features = ["serde"] }
|
||||||
|
tokio = { version = "1.28.2", features = ["full"] }
|
63
src/api.rs
Normal file
63
src/api.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use axum::{
|
||||||
|
extract::{State},
|
||||||
|
};
|
||||||
|
use axum::Json;
|
||||||
|
use serde::{Deserialize,Serialize};
|
||||||
|
use crate::*;
|
||||||
|
use crate::PowerDnsOidcTsigkeyError;
|
||||||
|
|
||||||
|
pub async fn list_keys(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> Result<Json<Vec<TsigKey>>, PowerDnsOidcTsigkeyError> {
|
||||||
|
let req = state.http_client.get::<String>((config_cell.get().unwrap().powerdns.url.to_string() + "/servers/localhost/tsigkeys").into())
|
||||||
|
.header("X-API-Key", config_cell.get().unwrap().powerdns.api_token.clone());
|
||||||
|
let response = req
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
match response.status().is_success() {
|
||||||
|
true => {
|
||||||
|
let powerdns_tsig_keys: Vec<PowerDnsTsigKey> = response.json().await?;
|
||||||
|
Ok(Json(powerdns_tsig_keys.into_iter().map(|key| key.into()).collect()))
|
||||||
|
},
|
||||||
|
false => Err(PowerDnsOidcTsigkeyError::from_response(response).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//pub async fn list_key(Path(key_id): Path<String>) -> Json<TsigKey> {}
|
||||||
|
//pub async fn create_key() -> Json<TsigKey> {}
|
||||||
|
//pub async fn delete_key(Path(key_id): Path<String>) {}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct TsigKeyList {
|
||||||
|
pub keys: Vec<TsigKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct TsigKey {
|
||||||
|
pub name: String,
|
||||||
|
pub algorithm: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub key: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct PowerDnsTsigKey {
|
||||||
|
pub name: String,
|
||||||
|
pub id: String,
|
||||||
|
pub algorithm: String,
|
||||||
|
pub key: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub object_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PowerDnsTsigKey> for TsigKey {
|
||||||
|
fn from(tsigkey: PowerDnsTsigKey) -> Self {
|
||||||
|
Self {
|
||||||
|
name: tsigkey.name,
|
||||||
|
algorithm: tsigkey.algorithm,
|
||||||
|
key: Some(tsigkey.key).filter(|s| !s.is_empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
159
src/main.rs
Normal file
159
src/main.rs
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
mod api;
|
||||||
|
pub(crate) mod settings;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::Arc,
|
||||||
|
fmt::Display,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
routing::{get},
|
||||||
|
Router,
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::settings::PowerDnsOidcTsigkeyConfig;
|
||||||
|
use reqwest::Response as ReqwestResponse;
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
//use serde_with::{serde_as, DisplayFromStr};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PowerDnsOidcTsigkeyError {
|
||||||
|
ApiResponse(String),
|
||||||
|
PowerDnsApi(StatusCode, PowerDnsApiError),
|
||||||
|
Reqwest(reqwest::Error),
|
||||||
|
Config(config::ConfigError),
|
||||||
|
Serde(serde_json::Error),
|
||||||
|
Io(std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerDnsOidcTsigkeyError {
|
||||||
|
pub async fn from_response(res: ReqwestResponse) -> Self {
|
||||||
|
match res
|
||||||
|
.headers()
|
||||||
|
.get(reqwest::header::CONTENT_TYPE)
|
||||||
|
.map(reqwest::header::HeaderValue::to_str)
|
||||||
|
{
|
||||||
|
Some(Ok(content_type)) if content_type.starts_with("application/json") => {
|
||||||
|
let status_code = res.status();
|
||||||
|
match res.json().await.map_err(reqwest::Error::from) {
|
||||||
|
Ok(json_result) => Self::PowerDnsApi(status_code, json_result),
|
||||||
|
Err(err) => Self::Reqwest(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => match res.text().await {
|
||||||
|
Ok(text) => Self::ApiResponse(text),
|
||||||
|
Err(err) => Self::Reqwest(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for PowerDnsOidcTsigkeyError {
|
||||||
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
let sc = match self {
|
||||||
|
Self::PowerDnsApi(sc, _) => sc,
|
||||||
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
};
|
||||||
|
(sc, self.to_string()).into_response()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PowerDnsOidcTsigkeyError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
PowerDnsOidcTsigkeyError::ApiResponse(err) => f.write_fmt(format_args!("API Error: {:#?}", err)),
|
||||||
|
PowerDnsOidcTsigkeyError::PowerDnsApi(sc, err) => f.write_fmt(format_args!("API Error {}: {:#?}", sc, err)),
|
||||||
|
PowerDnsOidcTsigkeyError::Reqwest(err) => f.write_fmt(format_args!("Reqwest Error: {:#?}", err)),
|
||||||
|
PowerDnsOidcTsigkeyError::Config(err) => f.write_fmt(format_args!("Config Error: {:#?}", err)),
|
||||||
|
PowerDnsOidcTsigkeyError::Serde(err) => f.write_fmt(format_args!("JSON Error: {:#?}", err)),
|
||||||
|
PowerDnsOidcTsigkeyError::Io(err) => f.write_fmt(format_args!("IO Error: {:#?}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for PowerDnsOidcTsigkeyError {}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for PowerDnsOidcTsigkeyError {
|
||||||
|
fn from(e: reqwest::Error) -> Self {
|
||||||
|
Self::Reqwest(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<config::ConfigError> for PowerDnsOidcTsigkeyError {
|
||||||
|
fn from(e: config::ConfigError) -> Self {
|
||||||
|
Self::Config(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for PowerDnsOidcTsigkeyError {
|
||||||
|
fn from(e: serde_json::Error) -> Self {
|
||||||
|
Self::Serde(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for PowerDnsOidcTsigkeyError {
|
||||||
|
fn from(e: std::io::Error) -> Self {
|
||||||
|
Self::Io(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct PowerDnsApiError {
|
||||||
|
pub error: String,
|
||||||
|
pub errors: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) type PowerDnsOidcTsigkeyResult<T> = Result<T, PowerDnsOidcTsigkeyError>;
|
||||||
|
|
||||||
|
async fn parse_json<Out: DeserializeOwned>(res: ReqwestResponse) -> PowerDnsOidcTsigkeyResult<Out> {
|
||||||
|
match res.status().is_success() {
|
||||||
|
true => Ok(res.json().await?),
|
||||||
|
false => Err(PowerDnsOidcTsigkeyError::from_response(res).await),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AppState {
|
||||||
|
http_client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
static config_cell: OnceCell<PowerDnsOidcTsigkeyConfig> = OnceCell::const_new();
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
match settings::PowerDnsOidcTsigkeyConfig::load("config.yaml") {
|
||||||
|
Ok(config) => {
|
||||||
|
config_cell.set(config).unwrap();
|
||||||
|
run().await;
|
||||||
|
},
|
||||||
|
Err(e) => println!("Failed to load config.yaml: {:?}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run() {
|
||||||
|
let addr: SocketAddr = (config_cell.get().unwrap().server.bind_address, config_cell.get().unwrap().server.port).into();
|
||||||
|
let state = AppState { http_client: reqwest::Client::new() };
|
||||||
|
// let router = create_router(state);
|
||||||
|
let router = Router::new()
|
||||||
|
.route("/api/v1/tsigkeys", get(api::list_keys))
|
||||||
|
// .route("/api/v1/tsigkeys/create", post(api::create_key))
|
||||||
|
// .route("/api/v1/tsigkeys/:keyid",
|
||||||
|
// put(api::create_key).delete(api::delete_key).get(api::list_key))
|
||||||
|
.with_state(Arc::new(state));
|
||||||
|
axum::Server::bind(&addr)
|
||||||
|
.serve(router.into_make_service())
|
||||||
|
.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
//fn create_router(state: AppState) -> Result<Router, Box<Error>> {
|
||||||
|
// Ok(router)
|
||||||
|
//}
|
69
src/settings.rs
Normal file
69
src/settings.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use url::Url;
|
||||||
|
use serde::{Deserialize};
|
||||||
|
use config::{Config, ConfigError, Environment, File};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PowerDnsOidcTsigkeyConfig {
|
||||||
|
/// OIDC Provider
|
||||||
|
pub oidc: OidcConfig,
|
||||||
|
/// PowerDNS config
|
||||||
|
pub powerdns: PowerDnsConfig,
|
||||||
|
/// Logging config
|
||||||
|
pub log: LogConfig,
|
||||||
|
/// HTTP(s) config
|
||||||
|
pub server: ServerConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PowerDnsConfig {
|
||||||
|
/// URL where PowerDNS API can be reached
|
||||||
|
pub url: Url,
|
||||||
|
/// Api Token to use for authentication
|
||||||
|
pub api_token: String,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct LogConfig {
|
||||||
|
/// The log level
|
||||||
|
pub level: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
/// IpAddress to listen on
|
||||||
|
pub bind_address: IpAddr,
|
||||||
|
/// Port to listen on
|
||||||
|
pub port: u16,
|
||||||
|
/// TLS config
|
||||||
|
pub tls: ServerTlsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ServerTlsConfig {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct OidcConfig {
|
||||||
|
pub issuer: Url,
|
||||||
|
pub client_id: String,
|
||||||
|
pub client_secret: String,
|
||||||
|
pub scopes: Vec<String>,
|
||||||
|
pub username_claim: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerDnsOidcTsigkeyConfig {
|
||||||
|
pub fn load(filename: &str) -> Result<Self, ConfigError> {
|
||||||
|
Config::builder()
|
||||||
|
.add_source(File::with_name(filename))
|
||||||
|
.add_source(Environment::with_prefix("POTK").separator("_"))
|
||||||
|
.set_default("log.level", "INFO")?
|
||||||
|
.set_default("server.bind_address", "::")?
|
||||||
|
.set_default("server.port", 8080)?
|
||||||
|
.build()?
|
||||||
|
.try_deserialize()
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user