feat: initial commit
This commit is contained in:
		
							
								
								
									
										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