constellation-server 1.9.1

Pluggable authoritative DNS server. Entries can be added & removed from an HTTP REST API.
// Constellation
//
// Pluggable authoritative DNS server
// Copyright: 2018, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

use rocket::http::Status;
use rocket_contrib::json::Json;
use std::collections::HashMap;

use super::record_guard::RecordGuard;
use crate::dns::metrics::{MetricsStoreCountType, MetricsTimespan, MetricsType, METRICS_STORE};
use crate::dns::record::{RecordBlackhole, RecordName, RecordRegions, RecordType, RecordValues};
use crate::dns::zone::ZoneName;
use crate::store::store::StoreRecord;
use crate::APP_STORE;

#[derive(Deserialize)]
pub struct RecordData {
    ttl: Option<u32>,
    blackhole: Option<RecordBlackhole>,
    regions: Option<RecordRegions>,
    rescue: Option<RecordValues>,
    values: RecordValues,
}

#[derive(Serialize)]
pub struct RecordGetResponse {
    #[serde(rename = "type")]
    _type: RecordType,

    name: RecordName,
    ttl: Option<u32>,
    blackhole: Option<RecordBlackhole>,
    regions: Option<RecordRegions>,
    rescue: Option<RecordValues>,
    values: RecordValues,
}

type MetricsGenericGetResponse = HashMap<String, MetricsStoreCountType>;

#[head("/zone/<zone_name>/record/<record_name>/<record_type>")]
pub fn head_zone_record(
    _auth: RecordGuard,
    zone_name: ZoneName,
    record_name: RecordName,
    record_type: RecordType,
) -> Result<(), Status> {
    APP_STORE
        .check(&zone_name, &record_name, &record_type)
        .or(Err(Status::NotFound))
}

#[get("/zone/<zone_name>/record/<record_name>/<record_type>")]
pub fn get_zone_record(
    _auth: RecordGuard,
    zone_name: ZoneName,
    record_name: RecordName,
    record_type: RecordType,
) -> Result<Json<RecordGetResponse>, Status> {
    APP_STORE
        .get(&zone_name, &record_name, &record_type)
        .map(|record| {
            Json(RecordGetResponse {
                _type: record.kind,
                name: record.name,
                ttl: record.ttl,
                blackhole: record.blackhole,
                regions: record.regions,
                rescue: record.rescue,
                values: record.values,
            })
        })
        .or(Err(Status::NotFound))
}

#[put(
    "/zone/<zone_name>/record/<record_name>/<record_type>",
    data = "<data>",
    format = "application/json"
)]
pub fn put_zone_record(
    _auth: RecordGuard,
    zone_name: ZoneName,
    record_name: RecordName,
    record_type: RecordType,
    data: Json<RecordData>,
) -> Result<(), Status> {
    APP_STORE
        .set(
            &zone_name,
            StoreRecord {
                kind: record_type,
                name: record_name,
                ttl: data.ttl,
                blackhole: data.blackhole.to_owned(),
                regions: data.regions.to_owned(),
                rescue: data.rescue.to_owned(),
                values: data.values.to_owned(),
            },
        )
        .or(Err(Status::InternalServerError))
}

#[delete("/zone/<zone_name>/record/<record_name>/<record_type>")]
pub fn delete_zone_record(
    _auth: RecordGuard,
    zone_name: ZoneName,
    record_name: RecordName,
    record_type: RecordType,
) -> Result<(), Status> {
    APP_STORE
        .remove(&zone_name, &record_name, &record_type)
        .or(Err(Status::InternalServerError))
}

#[get("/zone/<zone_name>/metrics/<metrics_timespan>/query/types")]
pub fn get_metrics_query_types(
    _auth: RecordGuard,
    zone_name: ZoneName,
    metrics_timespan: MetricsTimespan,
) -> Result<Json<MetricsGenericGetResponse>, Status> {
    METRICS_STORE
        .aggregate(&zone_name, MetricsType::QueryType, metrics_timespan)
        .ok_or(Status::NotFound)
        .map(|aggregated| Json(aggregated))
}

#[get("/zone/<zone_name>/metrics/<metrics_timespan>/query/origins")]
pub fn get_metrics_query_origins(
    _auth: RecordGuard,
    zone_name: ZoneName,
    metrics_timespan: MetricsTimespan,
) -> Result<Json<MetricsGenericGetResponse>, Status> {
    METRICS_STORE
        .aggregate(&zone_name, MetricsType::QueryOrigin, metrics_timespan)
        .ok_or(Status::NotFound)
        .map(|aggregated| Json(aggregated))
}

#[get("/zone/<zone_name>/metrics/<metrics_timespan>/answer/codes")]
pub fn get_metrics_answer_codes(
    _auth: RecordGuard,
    zone_name: ZoneName,
    metrics_timespan: MetricsTimespan,
) -> Result<Json<MetricsGenericGetResponse>, Status> {
    METRICS_STORE
        .aggregate(&zone_name, MetricsType::AnswerCode, metrics_timespan)
        .ok_or(Status::NotFound)
        .map(|aggregated| Json(aggregated))
}