Browse Source

Adding initial cloudflare client interaction

* address lookup for defined zone
develop
Drew Short 6 years ago
parent
commit
9466cae494
  1. 13
      src/main.rs
  2. 48
      src/server/api.rs
  3. 48
      src/server/middleware/api_auth.rs
  4. 1
      src/server/mod.rs
  5. 8
      src/server/router.rs
  6. 52
      src/server/util.rs

13
src/main.rs

@ -1,6 +1,7 @@
extern crate actix_web; extern crate actix_web;
extern crate bytes; extern crate bytes;
extern crate clap; extern crate clap;
extern crate cloudflare;
extern crate env_logger; extern crate env_logger;
extern crate futures; extern crate futures;
#[macro_use] #[macro_use]
@ -16,6 +17,8 @@ extern crate serde_yaml;
use std::process; use std::process;
use std::sync::Arc; use std::sync::Arc;
use cloudflare::Cloudflare;
mod args; mod args;
mod config; mod config;
mod server; mod server;
@ -71,7 +74,15 @@ fn main() {
); );
let shared_config = Arc::new(config); let shared_config = Arc::new(config);
let actix_server = actix_web::server::new(move || server::router::create(shared_config.clone()))
let shared_cloudflare = match Cloudflare::new(
&shared_config.cloudflare.key,
&shared_config.cloudflare.email,
"https://api.cloudflare.com/client/v4/") {
Ok(api) => Arc::new(api),
Err(_e) => process::exit(-1)
};
let actix_server = actix_web::server::new(move || server::router::create(shared_config.clone(), shared_cloudflare.clone()))
.workers(workers) .workers(workers)
.bind(bind); .bind(bind);

48
src/server/api.rs

@ -6,15 +6,18 @@ use actix_web::{AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpRe
use actix_web::http::Method; use actix_web::http::Method;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use bytes::{Buf, Bytes, IntoBuf}; use bytes::{Buf, Bytes, IntoBuf};
use cloudflare::{Cloudflare, zones};
use futures::future::Future; use futures::future::Future;
use crate::config::model::Config;
use crate::server::error::APIError; use crate::server::error::APIError;
use crate::server::middleware::api_auth::APIAuthRootAndZone; use crate::server::middleware::api_auth::APIAuthRootAndZone;
use crate::server::router::AppState; use crate::server::router::AppState;
use crate::server::util;
pub fn route(scope: Scope<AppState>) -> Scope<AppState> { pub fn route(scope: Scope<AppState>) -> Scope<AppState> {
scope scope
.resource("address", |r| r.method(Method::GET).f(get_address))
.resource("address", |r| r.method(Method::GET).f(get_request_address))
.nested("{root}/{zone}", |zone_scope| { .nested("{root}/{zone}", |zone_scope| {
zone_scope zone_scope
.middleware(APIAuthRootAndZone) .middleware(APIAuthRootAndZone)
@ -48,7 +51,7 @@ fn parse_remote_info(remote_info: &str) -> Result<IpAddr, APIError> {
} }
} }
fn get_address(req: &HttpRequest<AppState>) -> HttpResponse {
fn get_request_address(req: &HttpRequest<AppState>) -> HttpResponse {
match req.connection_info().remote() { match req.connection_info().remote() {
Some(remote_info) => { Some(remote_info) => {
match parse_remote_info(remote_info) { match parse_remote_info(remote_info) {
@ -65,6 +68,47 @@ fn get_address(req: &HttpRequest<AppState>) -> HttpResponse {
} }
} }
fn get_address(req: &HttpRequest<AppState>) -> HttpResponse {
let root_match = util::get_match_value(req, "root");
let zone_match = util::get_match_value(req, "zone");
let config: &Config = &req.state().config;
let cloudflare: &Cloudflare = &req.state().cloudflare;
if root_match.is_none() || zone_match.is_none() {
HttpResponse::BadRequest().into()
} else {
let root = root_match.unwrap();
let zone = zone_match.unwrap();
match util::get_domain_from_root(&root) {
Some(domain) => match zones::get_zoneid(cloudflare, &domain) {
Ok(zone_id) => match zones::dns::list_dns_of_type(cloudflare, &zone_id, zones::dns::RecordType::A) {
Ok(dns_entries) => {
let record_name = format!("{}.{}", &zone, &root);
debug!("Looking for dns record: {}", record_name);
for dns_entry in dns_entries {
debug!("record {:?}", dns_entry);
if dns_entry.name == record_name {
return HttpResponse::build(StatusCode::OK)
.content_type("text/plain")
.body(format!("{}", dns_entry.content));
}
}
HttpResponse::NotFound().into()
}
Err(e) => {
error!("{:?}", e);
HttpResponse::InternalServerError().into()
}
},
Err(e) => {
error!("{:?}", e);
HttpResponse::InternalServerError().into()
}
},
None => HttpResponse::BadRequest().into()
}
}
}
fn update_address_automatically(req: &HttpRequest<AppState>) -> HttpResponse { fn update_address_automatically(req: &HttpRequest<AppState>) -> HttpResponse {
match req.connection_info().remote() { match req.connection_info().remote() {
Some(remote_info) => { Some(remote_info) => {

48
src/server/middleware/api_auth.rs

@ -5,41 +5,7 @@ use actix_web::middleware::{Middleware, Started};
use crate::config::model::Config; use crate::config::model::Config;
use crate::config::model::UserConfig; use crate::config::model::UserConfig;
use crate::server::router::AppState; use crate::server::router::AppState;
fn get_match_value<S>(req: &HttpRequest<S>, key: &str) -> Option<String> {
let match_info = req.resource().match_info();
match match_info.get(key) {
Some(value) => Some(String::from(value)),
None => None
}
}
fn get_header_value<S>(req: &HttpRequest<S>, key: &str) -> Option<String> {
match req.headers().get(key) {
Some(header) => match header.to_str() {
Ok(header_value) => Some(String::from(header_value)),
Err(_e) => None
},
None => None
}
}
fn get_user_from_request(req: &HttpRequest<AppState>) -> Option<&UserConfig> {
let config: &Config = &req.state().config;
let username = get_username_from_request(req);
match username {
Some(username) => config.get_user(&username),
None => None
}
}
fn get_username_from_request<S>(req: &HttpRequest<S>) -> Option<String> {
get_header_value(req, "X-AUTH-USERNAME")
}
fn get_token_from_request<S>(req: &HttpRequest<S>) -> Option<String> {
get_header_value(req, "X-AUTH-TOKEN")
}
use crate::server::util;
fn valid_username_and_token_in_vec(username: &str, token: &str, users: Vec<&UserConfig>) -> bool { fn valid_username_and_token_in_vec(username: &str, token: &str, users: Vec<&UserConfig>) -> bool {
for user in users { for user in users {
@ -57,8 +23,8 @@ pub struct APIAuthRootAndZone;
impl Middleware<AppState> for APIAuthUser { impl Middleware<AppState> for APIAuthUser {
fn start(&self, req: &HttpRequest<AppState>) -> Result<Started> { fn start(&self, req: &HttpRequest<AppState>) -> Result<Started> {
let config: &Config = &req.state().config; let config: &Config = &req.state().config;
let username = get_username_from_request(req);
let token = get_token_from_request(req);
let username = util::get_username_from_request(req);
let token = util::get_token_from_request(req);
if username.is_none() || token.is_none() { if username.is_none() || token.is_none() {
Ok(Started::Response(HttpResponse::Unauthorized().into())) Ok(Started::Response(HttpResponse::Unauthorized().into()))
} else if config.is_valid_username_and_token(&username.unwrap(), &token.unwrap()) { } else if config.is_valid_username_and_token(&username.unwrap(), &token.unwrap()) {
@ -72,15 +38,15 @@ impl Middleware<AppState> for APIAuthUser {
impl Middleware<AppState> for APIAuthRootAndZone { impl Middleware<AppState> for APIAuthRootAndZone {
fn start(&self, req: &HttpRequest<AppState>) -> Result<Started> { fn start(&self, req: &HttpRequest<AppState>) -> Result<Started> {
let config: &Config = &req.state().config; let config: &Config = &req.state().config;
let root = get_match_value(req, "root");
let zone = get_match_value(req, "zone");
let root = util::get_match_value(req, "root");
let zone = util::get_match_value(req, "zone");
if root.is_none() || zone.is_none() { if root.is_none() || zone.is_none() {
Ok(Started::Response(HttpResponse::BadRequest().into())) Ok(Started::Response(HttpResponse::BadRequest().into()))
} else { } else {
match config.get_users_for_root_and_zone(&root.unwrap(), &zone.unwrap()) { match config.get_users_for_root_and_zone(&root.unwrap(), &zone.unwrap()) {
Some(users) => { Some(users) => {
let username = get_username_from_request(req);
let token = get_token_from_request(req);
let username = util::get_username_from_request(req);
let token = util::get_token_from_request(req);
if username.is_none() || token.is_none() { if username.is_none() || token.is_none() {
Ok(Started::Response(HttpResponse::BadRequest().into())) Ok(Started::Response(HttpResponse::BadRequest().into()))
} else if valid_username_and_token_in_vec(&username.unwrap(), &token.unwrap(), users) { } else if valid_username_and_token_in_vec(&username.unwrap(), &token.unwrap(), users) {

1
src/server/mod.rs

@ -7,6 +7,7 @@ pub mod api;
pub mod error; pub mod error;
pub mod middleware; pub mod middleware;
pub mod router; pub mod router;
pub mod util;
#[derive(Serialize)] #[derive(Serialize)]
pub struct Health { pub struct Health {

8
src/server/router.rs

@ -4,16 +4,18 @@ use std::sync::Arc;
use actix_web::{App, http}; use actix_web::{App, http};
use actix_web::middleware::Logger; use actix_web::middleware::Logger;
use cloudflare::Cloudflare;
use crate::config::model::Config; use crate::config::model::Config;
use crate::server; use crate::server;
pub struct AppState { pub struct AppState {
pub config: Arc<Config>
pub config: Arc<Config>,
pub cloudflare: Arc<Cloudflare>
} }
pub fn create(config: Arc<Config>) -> App<AppState> {
actix_web::App::with_state(AppState { config })
pub fn create(config: Arc<Config>, cloudflare: Arc<Cloudflare>) -> App<AppState> {
actix_web::App::with_state(AppState { config, cloudflare })
.middleware(Logger::default()) .middleware(Logger::default())
.scope("api/", server::api::route) .scope("api/", server::api::route)
.resource("/health", |r| { .resource("/health", |r| {

52
src/server/util.rs

@ -0,0 +1,52 @@
use actix_web::HttpRequest;
use crate::config::model::Config;
use crate::config::model::UserConfig;
use crate::server::router::AppState;
pub fn get_match_value<S>(req: &HttpRequest<S>, key: &str) -> Option<String> {
let match_info = req.resource().match_info();
match match_info.get(key) {
Some(value) => Some(String::from(value)),
None => None
}
}
pub fn get_header_value<S>(req: &HttpRequest<S>, key: &str) -> Option<String> {
match req.headers().get(key) {
Some(header) => match header.to_str() {
Ok(header_value) => Some(String::from(header_value)),
Err(_e) => None
},
None => None
}
}
pub fn get_user_from_request(req: &HttpRequest<AppState>) -> Option<&UserConfig> {
let config: &Config = &req.state().config;
let username = get_username_from_request(req);
match username {
Some(username) => config.get_user(&username),
None => None
}
}
pub fn get_username_from_request<S>(req: &HttpRequest<S>) -> Option<String> {
get_header_value(req, "X-AUTH-USERNAME")
}
pub fn get_token_from_request<S>(req: &HttpRequest<S>) -> Option<String> {
get_header_value(req, "X-AUTH-TOKEN")
}
pub fn get_domain_from_root(root: &str) -> Option<String> {
let mut parts: Vec<&str> = root.split('.').collect();
let length = parts.len();
if length <= 1 {
None
} else if length == 2 {
Some(String::from(root))
} else {
Some(String::from(parts.split_off(length - 2).join(".")))
}
}
Loading…
Cancel
Save