feat: initial commit
This commit is contained in:
commit
436dbe1393
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
config.yaml
|
||||||
|
/target
|
3073
Cargo.lock
generated
Normal file
3073
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
Cargo.toml
Normal file
23
Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "pdns-tsig-key-manager"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
askama = "0.12.1"
|
||||||
|
axum = { version = "0.7.5", features = ["tracing", "multipart", "macros", "http2"] }
|
||||||
|
axum-core = { version = "0.4.3", features = ["tracing"] }
|
||||||
|
axum-extra = { version = "0.9.3", features = ["form"] }
|
||||||
|
config = "0.14.0"
|
||||||
|
http = "1.1.0"
|
||||||
|
openidconnect = "3.5.0"
|
||||||
|
reqwest = { version = "0.12.7", features = ["json"] }
|
||||||
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
|
serde_json = "1.0.128"
|
||||||
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
|
strum_macros = "0.26.4"
|
||||||
|
time = { version = "0.3.36", features = ["local-offset", "parsing", "serde", "serde-human-readable"] }
|
||||||
|
tokio = { version = "1.40.0", features = ["full"] }
|
||||||
|
url = { version = "2.5.2", features = ["serde"] }
|
13
config.example.yml
Normal file
13
config.example.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
server:
|
||||||
|
bind_address: 127.0.0.1
|
||||||
|
port: 3000
|
||||||
|
powerdns:
|
||||||
|
server_url: https://my.powerdns.api.server:3000
|
||||||
|
server_api_key: my_power_dns_server_api_key
|
||||||
|
server_id: localhost
|
||||||
|
oidc:
|
||||||
|
issuer: https://openidconnect.provider.tld/realms/if-you-use-keycloak
|
||||||
|
client_id: client-id-you-generated
|
||||||
|
client_secret: client-secret-that-you-generated-or-copied
|
||||||
|
callback: https://the-public-domain-of-this.service.tld/openid/callback
|
46
src/config.rs
Normal file
46
src/config.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use url::Url;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use config::{
|
||||||
|
Config,
|
||||||
|
ConfigError,
|
||||||
|
File,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
pub bind_address: IpAddr,
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct PowerDnsConfig {
|
||||||
|
pub server_url: Url,
|
||||||
|
pub server_api_key: String,
|
||||||
|
pub server_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct OidcConfig {
|
||||||
|
pub issuer: Url,
|
||||||
|
pub client_id: String,
|
||||||
|
pub client_secret: String,
|
||||||
|
pub callback: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct PowerDnsTsigKeyManagerConfig {
|
||||||
|
pub oidc: OidcConfig,
|
||||||
|
pub powerdns: PowerDnsConfig,
|
||||||
|
pub server: ServerConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerDnsTsigKeyManagerConfig {
|
||||||
|
pub fn load(filename: &str) -> Result<Self, ConfigError> {
|
||||||
|
Config::builder()
|
||||||
|
.add_source(File::with_name(filename))
|
||||||
|
.build()?
|
||||||
|
.try_deserialize()
|
||||||
|
}
|
||||||
|
}
|
1
src/error/mod.rs
Normal file
1
src/error/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod powerdns;
|
70
src/error/powerdns.rs
Normal file
70
src/error/powerdns.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use std::vec::Vec;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde::{
|
||||||
|
Deserialize,
|
||||||
|
Serialize,
|
||||||
|
};
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct PowerDnsApiError {
|
||||||
|
pub error: String,
|
||||||
|
pub errors: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PowerDnsError {
|
||||||
|
Reqwest(reqwest::Error),
|
||||||
|
Serde(serde_json::Error),
|
||||||
|
Api(PowerDnsApiError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PowerDnsError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
PowerDnsError::Reqwest(r) => f.write_fmt(format_args!("Request error on {:?}: {:?}", r.url(), r)),
|
||||||
|
PowerDnsError::Serde(s) => f.write_fmt(format_args!("SerDe error: {:?}", s)),
|
||||||
|
PowerDnsError::Api(a) => f.write_fmt(format_args!("API error: {:?}", a)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for PowerDnsError {}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for PowerDnsError {
|
||||||
|
fn from(e: reqwest::Error) -> Self {
|
||||||
|
Self::Reqwest(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<serde_json::Error> for PowerDnsError {
|
||||||
|
fn from(e: serde_json::Error) -> Self {
|
||||||
|
Self::Serde(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PowerDnsApiError> for PowerDnsError {
|
||||||
|
fn from(e: PowerDnsApiError) -> Self {
|
||||||
|
Self::Api(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub type PowerDnsResult<T> = Result<T, PowerDnsError>;
|
||||||
|
|
||||||
|
impl IntoResponse for PowerDnsError {
|
||||||
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("Error processing request: {}", self),
|
||||||
|
).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for PowerDnsApiError {
|
||||||
|
fn from(err: reqwest::Error) -> Self {
|
||||||
|
Self {
|
||||||
|
error: format!("{:?}", err),
|
||||||
|
errors: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
src/handlers/api.rs
Normal file
37
src/handlers/api.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use axum::extract::{
|
||||||
|
Path,
|
||||||
|
State,
|
||||||
|
};
|
||||||
|
use axum::response::{
|
||||||
|
IntoResponse,
|
||||||
|
Json,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
use crate::model::tsigkey::{
|
||||||
|
TsigKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn list(State(app_state): State<AppState>) -> impl IntoResponse {
|
||||||
|
app_state.pdns_client.list_tsig_keys()
|
||||||
|
.await
|
||||||
|
.and_then(|keys| Ok(Json(keys)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(State(app_state): State<AppState>, Path(tsig_key_name): Path<String>) -> impl IntoResponse {
|
||||||
|
app_state.pdns_client.get_tsig_key(tsig_key_name)
|
||||||
|
.await
|
||||||
|
.and_then(|key| Ok(Json(key)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add(State(app_state): State<AppState>, Json(tsig_key): Json<TsigKey>) -> impl IntoResponse {
|
||||||
|
app_state.pdns_client.add_tsig_key(tsig_key)
|
||||||
|
.await
|
||||||
|
.and_then(|key| Ok(Json(key)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(State(app_state): State<AppState>, Path(tsig_key_name): Path<String>) -> impl IntoResponse {
|
||||||
|
app_state.pdns_client.delete_tsig_key(tsig_key_name)
|
||||||
|
.await
|
||||||
|
.and_then(|_| Ok(Json(())))
|
||||||
|
}
|
12
src/handlers/app.rs
Normal file
12
src/handlers/app.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use askama::Template;
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
|
||||||
|
use crate::util::askama::HtmlTemplate;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "app_home.html")]
|
||||||
|
struct AppHomeTemplate {}
|
||||||
|
|
||||||
|
pub async fn home() -> impl IntoResponse {
|
||||||
|
HtmlTemplate(AppHomeTemplate {})
|
||||||
|
}
|
4
src/handlers/mod.rs
Normal file
4
src/handlers/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub(crate) mod api;
|
||||||
|
pub(crate) mod app;
|
||||||
|
pub(crate) mod user;
|
||||||
|
pub(crate) mod openid;
|
65
src/handlers/openid.rs
Normal file
65
src/handlers/openid.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
use axum::response::{
|
||||||
|
IntoResponse,
|
||||||
|
Redirect,
|
||||||
|
};
|
||||||
|
use axum::extract::{
|
||||||
|
State,
|
||||||
|
Query,
|
||||||
|
};
|
||||||
|
use openidconnect::{
|
||||||
|
AuthenticationFlow,
|
||||||
|
AuthorizationCode,
|
||||||
|
CsrfToken,
|
||||||
|
Nonce,
|
||||||
|
OAuth2TokenResponse,
|
||||||
|
Scope,
|
||||||
|
};
|
||||||
|
use openidconnect::core::CoreResponseType;
|
||||||
|
use openidconnect::reqwest::async_http_client;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct OidcCallback {
|
||||||
|
code: String,
|
||||||
|
state: String
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login(State(app_state): State<AppState>) -> impl IntoResponse {
|
||||||
|
let (auth_url, csrf_state, nonce) = app_state.oidc_client.authorize_url(
|
||||||
|
AuthenticationFlow::<CoreResponseType>::AuthorizationCode,
|
||||||
|
CsrfToken::new_random,
|
||||||
|
Nonce::new_random,
|
||||||
|
)
|
||||||
|
.add_scope(Scope::new("openid".to_string()))
|
||||||
|
.add_scope(Scope::new("profile".to_string()))
|
||||||
|
.url();
|
||||||
|
let mut lock = app_state.state.lock().await;
|
||||||
|
lock.insert(
|
||||||
|
nonce.secret().to_owned(),
|
||||||
|
csrf_state.secret().to_owned(),
|
||||||
|
);
|
||||||
|
Redirect::to(auth_url.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn callback(
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Query(callback): Query<OidcCallback>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
println!("Received auth code: {}", callback.code.clone());
|
||||||
|
let token_response = app_state.oidc_client
|
||||||
|
.exchange_code(AuthorizationCode::new(callback.code.to_owned()))
|
||||||
|
.request_async(async_http_client)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
println!("Exchanged for access token: {}", token_response.access_token().secret());
|
||||||
|
println!("\tvalid for scopes {:?}", token_response.scopes());
|
||||||
|
Redirect::to("/user/home")
|
||||||
|
// let id_token_verifier: CoreIdTokenVerifier = app_state.oidc_client.id_token_verifier();
|
||||||
|
// let id_token_claims: &CoreIdTokenClaims = token_response
|
||||||
|
// .extra_fields()
|
||||||
|
// .id_token()
|
||||||
|
// .expect("IdP did not return an ID token")
|
||||||
|
// .claims(&id_token_verifier, &nonce)
|
||||||
|
}
|
12
src/handlers/user.rs
Normal file
12
src/handlers/user.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use askama::Template;
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
|
||||||
|
use crate::util::askama::HtmlTemplate;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "user_home.html")]
|
||||||
|
struct UserHomeTemplate {}
|
||||||
|
|
||||||
|
pub async fn home() -> impl IntoResponse {
|
||||||
|
HtmlTemplate(UserHomeTemplate {})
|
||||||
|
}
|
61
src/main.rs
Normal file
61
src/main.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::{Arc};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use axum::Router;
|
||||||
|
use axum::routing::{get, post};
|
||||||
|
use openidconnect::core::CoreClient;
|
||||||
|
|
||||||
|
pub mod model;
|
||||||
|
mod config;
|
||||||
|
mod error;
|
||||||
|
mod handlers;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use crate::util::powerdns::PowerDnsApi;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AppState {
|
||||||
|
oidc_client: CoreClient,
|
||||||
|
pdns_client: PowerDnsApi,
|
||||||
|
state: Arc<Mutex<HashMap<String, String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let config = config::PowerDnsTsigKeyManagerConfig::load("config.yaml")
|
||||||
|
.expect("Unable to load and parse config.yaml");
|
||||||
|
|
||||||
|
let oidc_client = util::openid::create_client(
|
||||||
|
config.oidc.issuer,
|
||||||
|
config.oidc.client_id,
|
||||||
|
config.oidc.client_secret,
|
||||||
|
config.oidc.callback,
|
||||||
|
).await;
|
||||||
|
|
||||||
|
let pdns_client = util::powerdns::PowerDnsApi::new(
|
||||||
|
config.powerdns.server_url,
|
||||||
|
config.powerdns.server_api_key,
|
||||||
|
config.powerdns.server_id,
|
||||||
|
).await;
|
||||||
|
|
||||||
|
let state: HashMap<String, String> = HashMap::new();
|
||||||
|
let app_state = AppState {
|
||||||
|
oidc_client,
|
||||||
|
pdns_client,
|
||||||
|
state: Arc::new(Mutex::new(state)),
|
||||||
|
};
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/user/home", get(handlers::user::home))
|
||||||
|
.route("/openid/login", get(handlers::openid::login))
|
||||||
|
.route("/openid/callback", get(handlers::openid::callback))
|
||||||
|
.route("/api/tsigkey/list", get(handlers::api::list))
|
||||||
|
.route("/api/tsigkey/:tsig_key_name", get(handlers::api::get).delete(handlers::api::delete))
|
||||||
|
.route("/api/tsigkey/", post(handlers::api::add).put(handlers::api::add))
|
||||||
|
.route("/", get(handlers::app::home))
|
||||||
|
.with_state(app_state);
|
||||||
|
let socket_addr: SocketAddr = (config.server.bind_address, config.server.port).into();
|
||||||
|
let listener = tokio::net::TcpListener::bind(socket_addr).await.unwrap();
|
||||||
|
axum::serve::serve(listener, app).await.unwrap();
|
||||||
|
}
|
1
src/model/mod.rs
Normal file
1
src/model/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod tsigkey;
|
29
src/model/tsigkey.rs
Normal file
29
src/model/tsigkey.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
use serde::{
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
};
|
||||||
|
use strum_macros::{
|
||||||
|
EnumString,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, EnumString, Serialize, Deserialize)]
|
||||||
|
#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
|
||||||
|
pub enum TsigKeyAlgorithm {
|
||||||
|
#[serde(rename = "hmac-md5")]
|
||||||
|
#[strum(to_string = "hmac-md5")]
|
||||||
|
HmacMd5,
|
||||||
|
#[serde(rename = "hmac-sha256")]
|
||||||
|
#[strum(to_string = "hmac-sha256")]
|
||||||
|
HmacSha256,
|
||||||
|
#[serde(rename = "hmac-sha512")]
|
||||||
|
#[strum(to_string = "hmac-sha512")]
|
||||||
|
HmacSha512,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TsigKey {
|
||||||
|
pub algorithm: TsigKeyAlgorithm,
|
||||||
|
pub name: String,
|
||||||
|
#[serde(rename = "key")]
|
||||||
|
pub secret: Option<String>,
|
||||||
|
}
|
20
src/util/askama.rs
Normal file
20
src/util/askama.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use askama::Template;
|
||||||
|
use axum::response::{Html,Response};
|
||||||
|
use axum::{
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct HtmlTemplate<T>(pub T);
|
||||||
|
|
||||||
|
impl<T> IntoResponse for HtmlTemplate<T>
|
||||||
|
where
|
||||||
|
T: Template,
|
||||||
|
{
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
match self.0.render() {
|
||||||
|
Ok(html) => Html(html).into_response(),
|
||||||
|
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to render html template: {err}")).into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
src/util/mod.rs
Normal file
3
src/util/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub(crate) mod askama;
|
||||||
|
pub(crate) mod openid;
|
||||||
|
pub(crate) mod powerdns;
|
28
src/util/openid.rs
Normal file
28
src/util/openid.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use openidconnect::reqwest::async_http_client;
|
||||||
|
use openidconnect::core::{
|
||||||
|
CoreClient,
|
||||||
|
CoreProviderMetadata,
|
||||||
|
};
|
||||||
|
use openidconnect::{
|
||||||
|
ClientId,
|
||||||
|
ClientSecret,
|
||||||
|
IssuerUrl,
|
||||||
|
RedirectUrl,
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
pub async fn create_client(issuer: Url, id: String, secret: String, redirect_url: Url) -> CoreClient {
|
||||||
|
let issuer_url = IssuerUrl::from_url(issuer);
|
||||||
|
let metadata = CoreProviderMetadata::discover_async(issuer_url, async_http_client)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
println!("Unable to discover provider metadata: {}", err);
|
||||||
|
unreachable!();
|
||||||
|
});
|
||||||
|
CoreClient::from_provider_metadata(
|
||||||
|
metadata,
|
||||||
|
ClientId::new(id),
|
||||||
|
Some(ClientSecret::new(secret)),
|
||||||
|
)
|
||||||
|
.set_redirect_uri(RedirectUrl::from_url(redirect_url))
|
||||||
|
}
|
109
src/util/powerdns.rs
Normal file
109
src/util/powerdns.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
use url::Url;
|
||||||
|
use serde::{
|
||||||
|
de::DeserializeOwned,
|
||||||
|
};
|
||||||
|
use reqwest::{
|
||||||
|
Client,
|
||||||
|
Method,
|
||||||
|
RequestBuilder,
|
||||||
|
StatusCode,
|
||||||
|
Response,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::model::tsigkey::TsigKey;
|
||||||
|
use crate::error::powerdns::{
|
||||||
|
PowerDnsError,
|
||||||
|
PowerDnsResult,
|
||||||
|
PowerDnsApiError,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PowerDnsApi {
|
||||||
|
api_url: Url,
|
||||||
|
api_key: String,
|
||||||
|
server_id: String,
|
||||||
|
http_client: Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// some comment
|
||||||
|
impl PowerDnsApi {
|
||||||
|
pub async fn new(api_url: Url, api_key: String, server_id: String) -> Self {
|
||||||
|
PowerDnsApi {
|
||||||
|
api_url,
|
||||||
|
api_key,
|
||||||
|
server_id,
|
||||||
|
http_client: Client::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_tsig_keys(self) -> PowerDnsResult<Vec<TsigKey>> {
|
||||||
|
Self::transform_response::<Vec<TsigKey>>(
|
||||||
|
self.get_request_for(Method::GET, "tsigkeys".to_string()).send().await?,
|
||||||
|
StatusCode::OK,
|
||||||
|
).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_tsig_key(self, name: String) -> PowerDnsResult<TsigKey> {
|
||||||
|
Self::transform_response::<TsigKey>(
|
||||||
|
self.get_request_for(Method::GET, format!("tsigkeys/{}", name)).send().await?,
|
||||||
|
StatusCode::OK,
|
||||||
|
).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_tsig_key(self, key: TsigKey) -> PowerDnsResult<TsigKey> {
|
||||||
|
match key.secret {
|
||||||
|
Some(ref _secret) => Self::transform_response::<TsigKey>(
|
||||||
|
self.get_request_for(
|
||||||
|
Method::PUT,
|
||||||
|
format!("tsigkeys/{}", key.name),
|
||||||
|
).json(&key).send().await?,
|
||||||
|
StatusCode::OK,
|
||||||
|
).await,
|
||||||
|
None => Self::transform_response::<TsigKey>(
|
||||||
|
self.get_request_for(Method::POST, "tsigkeys".to_string())
|
||||||
|
.json(&key).send().await?,
|
||||||
|
StatusCode::CREATED,
|
||||||
|
).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_tsig_key(self, name: String) -> PowerDnsResult<()> {
|
||||||
|
Self::transform_response::<()>(
|
||||||
|
self.get_request_for(Method::DELETE, format!("tsigkeys/{}", name)).send().await?,
|
||||||
|
StatusCode::NO_CONTENT,
|
||||||
|
).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_for(self, method: Method, endpoint: String) -> RequestBuilder {
|
||||||
|
let url = format!("{}api/v1/servers/{}/{}", self.api_url, self.server_id, endpoint);
|
||||||
|
println!("Requesting {} {}", method, url);
|
||||||
|
self.http_client.request(
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
)
|
||||||
|
.header("X-API-Key", self.api_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn transform_response<T: DeserializeOwned>(res: Response, expected: StatusCode) -> PowerDnsResult<T> {
|
||||||
|
println!("Received response with statuscode={}", res.status());
|
||||||
|
let unknown_status_error = |code: StatusCode| -> PowerDnsError {PowerDnsApiError {
|
||||||
|
error: format!("Unexpected server status code: {}", code),
|
||||||
|
errors: None,
|
||||||
|
}.into()};
|
||||||
|
// TODO: Check if status_code is the 'correct' one for the endpoint
|
||||||
|
match res.status() {
|
||||||
|
StatusCode::OK |
|
||||||
|
StatusCode::CREATED |
|
||||||
|
StatusCode::NO_CONTENT => {
|
||||||
|
if res.status() == expected { Ok(res.json().await?) } else { Err(unknown_status_error(res.status())) }
|
||||||
|
},
|
||||||
|
StatusCode::BAD_REQUEST |
|
||||||
|
StatusCode::NOT_FOUND |
|
||||||
|
StatusCode::UNPROCESSABLE_ENTITY |
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR => Err(PowerDnsError::Api(res.json::<PowerDnsApiError>().await?)),
|
||||||
|
_ => Err(unknown_status_error(res.status()).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
templates/app_home.html
Normal file
7
templates/app_home.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1><code>pdns-tsig-key-manager</code></h1>
|
||||||
|
|
||||||
|
<small><code>v0.0.1</code></small>
|
||||||
|
{% endblock content %}
|
23
templates/base.html
Normal file
23
templates/base.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>pdns-tsig-key-manager</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-bs-theme="dark">
|
||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="nav-link" href="/">Home</a>
|
||||||
|
<a class="nav-link" href="/user/home">User Home</a>
|
||||||
|
<a class="nav-link" href="/openid/login">User Login</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container p-4">
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% endblock content%}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
5
templates/user_home.html
Normal file
5
templates/user_home.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1> User Home</h1>
|
||||||
|
{% endblock content %}
|
Loading…
Reference in New Issue
Block a user