forgot about config overrides for a second there
This commit is contained in:
parent
0ffb04c1e7
commit
11320ba04a
|
@ -4,7 +4,7 @@ use std::net::{Ipv4Addr, SocketAddrV4};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::{Url};
|
use url::{Url};
|
||||||
use crate::api::APIErrorResponse;
|
use crate::api::APIErrorResponse;
|
||||||
use crate::{HostCommands};
|
use crate::{HostCommands, HostOverrideCommands};
|
||||||
|
|
||||||
pub async fn host_main(command: HostCommands, server: Url) -> Result<(), Box<dyn Error>> {
|
pub async fn host_main(command: HostCommands, server: Url) -> Result<(), Box<dyn Error>> {
|
||||||
match command {
|
match command {
|
||||||
|
@ -14,7 +14,12 @@ pub async fn host_main(command: HostCommands, server: Url) -> Result<(), Box<dyn
|
||||||
HostCommands::Delete { id } => delete_host(id, server).await,
|
HostCommands::Delete { id } => delete_host(id, server).await,
|
||||||
HostCommands::Update { id, listen_port, static_address, name, ip, role } => update_host(id, listen_port, static_address, name, ip, role, server).await,
|
HostCommands::Update { id, listen_port, static_address, name, ip, role } => update_host(id, listen_port, static_address, name, ip, role, server).await,
|
||||||
HostCommands::Block { id } => block_host(id, server).await,
|
HostCommands::Block { id } => block_host(id, server).await,
|
||||||
HostCommands::Enroll { id } => enroll_host(id, server).await
|
HostCommands::Enroll { id } => enroll_host(id, server).await,
|
||||||
|
HostCommands::Overrides { command } => match command {
|
||||||
|
HostOverrideCommands::List { id } => list_overrides(id, server).await,
|
||||||
|
HostOverrideCommands::Set { id, key, boolean, string, numeric } => set_override(id, key, boolean, numeric, string, server).await,
|
||||||
|
HostOverrideCommands::Unset { id, key } => unset_override(id, key, server).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,5 +417,224 @@ pub async fn block_host(id: String, server: Url) -> Result<(), Box<dyn Error>> {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct HostConfigOverrideResponse {
|
||||||
|
pub data: HostConfigOverrideData
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct HostConfigOverrideData {
|
||||||
|
pub overrides: Vec<HostConfigOverrideDataOverride>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct HostConfigOverrideDataOverride {
|
||||||
|
pub key: String,
|
||||||
|
pub value: HostConfigOverrideDataOverrideValue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum HostConfigOverrideDataOverrideValue {
|
||||||
|
Boolean(bool),
|
||||||
|
Numeric(i64),
|
||||||
|
Other(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_overrides(id: String, server: Url) -> Result<(), Box<dyn Error>> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// load session token
|
||||||
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
||||||
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
||||||
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
||||||
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
||||||
|
|
||||||
|
let token = format!("{} {}", session_token, auth_token);
|
||||||
|
|
||||||
|
let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token).send().await?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let resp: HostConfigOverrideResponse = res.json().await?;
|
||||||
|
|
||||||
|
for c_override in &resp.data.overrides {
|
||||||
|
println!(" Key: {}", c_override.key);
|
||||||
|
println!("Value: {}", match &c_override.value {
|
||||||
|
HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v),
|
||||||
|
HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v),
|
||||||
|
HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.data.overrides.is_empty() {
|
||||||
|
println!("No overrides found");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let resp: APIErrorResponse = res.json().await?;
|
||||||
|
|
||||||
|
eprintln!("[error] Error looking up config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SetOverrideRequest {
|
||||||
|
pub overrides: Vec<HostConfigOverrideDataOverride>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_override(id: String, key: String, boolean: Option<bool>, numeric: Option<i64>, other: Option<String>, server: Url) -> Result<(), Box<dyn Error>> {
|
||||||
|
if boolean.is_none() && numeric.is_none() && other.is_none() {
|
||||||
|
eprintln!("[error] no value provided: you must provide at least --boolean, --numeric, or --string");
|
||||||
|
std::process::exit(1);
|
||||||
|
} else if boolean.is_some() && numeric.is_some() || boolean.is_some() && other.is_some() || numeric.is_some() && other.is_some() {
|
||||||
|
eprintln!("[error] multiple values provided: you must provide only one of --boolean, --numeric, or --string");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let val;
|
||||||
|
|
||||||
|
if let Some(v) = boolean {
|
||||||
|
val = HostConfigOverrideDataOverrideValue::Boolean(v);
|
||||||
|
} else if let Some(v) = numeric {
|
||||||
|
val = HostConfigOverrideDataOverrideValue::Numeric(v);
|
||||||
|
} else if let Some(v) = other {
|
||||||
|
val = HostConfigOverrideDataOverrideValue::Other(v);
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// load session token
|
||||||
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
||||||
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
||||||
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
||||||
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
||||||
|
|
||||||
|
let token = format!("{} {}", session_token, auth_token);
|
||||||
|
|
||||||
|
let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).send().await?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let resp: HostConfigOverrideResponse = res.json().await?;
|
||||||
|
|
||||||
|
let mut others: Vec<HostConfigOverrideDataOverride> = vec![];
|
||||||
|
|
||||||
|
for c_override in resp.data.overrides {
|
||||||
|
if c_override.key != key {
|
||||||
|
others.push(c_override);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
others.push(HostConfigOverrideDataOverride {
|
||||||
|
key,
|
||||||
|
value: val,
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = client.put(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).json(&SetOverrideRequest {
|
||||||
|
overrides: others,
|
||||||
|
}).send().await?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let resp: HostConfigOverrideResponse = res.json().await?;
|
||||||
|
|
||||||
|
for c_override in &resp.data.overrides {
|
||||||
|
println!(" Key: {}", c_override.key);
|
||||||
|
println!("Value: {}", match &c_override.value {
|
||||||
|
HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v),
|
||||||
|
HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v),
|
||||||
|
HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.data.overrides.is_empty() {
|
||||||
|
println!("No overrides found");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Override set successfully");
|
||||||
|
} else {
|
||||||
|
let resp: APIErrorResponse = res.json().await?;
|
||||||
|
|
||||||
|
eprintln!("[error] Error setting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let resp: APIErrorResponse = res.json().await?;
|
||||||
|
|
||||||
|
eprintln!("[error] Error setting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn unset_override(id: String, key: String, server: Url) -> Result<(), Box<dyn Error>> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// load session token
|
||||||
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
||||||
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
||||||
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
||||||
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
||||||
|
|
||||||
|
let token = format!("{} {}", session_token, auth_token);
|
||||||
|
|
||||||
|
let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).send().await?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let resp: HostConfigOverrideResponse = res.json().await?;
|
||||||
|
|
||||||
|
let mut others: Vec<HostConfigOverrideDataOverride> = vec![];
|
||||||
|
|
||||||
|
for c_override in resp.data.overrides {
|
||||||
|
if c_override.key != key {
|
||||||
|
others.push(c_override);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = client.put(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).json(&SetOverrideRequest {
|
||||||
|
overrides: others,
|
||||||
|
}).send().await?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let resp: HostConfigOverrideResponse = res.json().await?;
|
||||||
|
|
||||||
|
for c_override in &resp.data.overrides {
|
||||||
|
println!(" Key: {}", c_override.key);
|
||||||
|
println!("Value: {}", match &c_override.value {
|
||||||
|
HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v),
|
||||||
|
HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v),
|
||||||
|
HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.data.overrides.is_empty() {
|
||||||
|
println!("No overrides found");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Override unset successfully");
|
||||||
|
} else {
|
||||||
|
let resp: APIErrorResponse = res.json().await?;
|
||||||
|
|
||||||
|
eprintln!("[error] Error unsetting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let resp: APIErrorResponse = res.json().await?;
|
||||||
|
|
||||||
|
eprintln!("[error] Error unsetting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
|
@ -203,6 +203,40 @@ pub enum HostCommands {
|
||||||
Enroll {
|
Enroll {
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
id: String
|
id: String
|
||||||
|
},
|
||||||
|
/// Manage config overrides set on the host
|
||||||
|
Overrides {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: HostOverrideCommands
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum HostOverrideCommands {
|
||||||
|
/// List the config overrides set on the host
|
||||||
|
List {
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: String
|
||||||
|
},
|
||||||
|
/// Set a config override on the host
|
||||||
|
Set {
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: String,
|
||||||
|
#[clap(short, long)]
|
||||||
|
key: String,
|
||||||
|
#[clap(short, long)]
|
||||||
|
boolean: Option<bool>,
|
||||||
|
#[clap(short, long)]
|
||||||
|
numeric: Option<i64>,
|
||||||
|
#[clap(short, long)]
|
||||||
|
string: Option<String>
|
||||||
|
},
|
||||||
|
/// Unset a config override on the host
|
||||||
|
Unset {
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: String,
|
||||||
|
#[clap(short, long)]
|
||||||
|
key: String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
.service(routes::v2::enroll::enroll)
|
.service(routes::v2::enroll::enroll)
|
||||||
.service(routes::v1::dnclient::dnclient)
|
.service(routes::v1::dnclient::dnclient)
|
||||||
.service(routes::v2::whoami::whoami)
|
.service(routes::v2::whoami::whoami)
|
||||||
|
.service(routes::v1::hosts::get_host_overrides)
|
||||||
|
.service(routes::v1::hosts::update_host_overrides)
|
||||||
})
|
})
|
||||||
.bind(CONFIG.server.bind)?
|
.bind(CONFIG.server.bind)?
|
||||||
.run()
|
.run()
|
||||||
|
|
|
@ -76,7 +76,8 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use trifid_api_entities::entity::{host, host_static_address, network, organization};
|
use trifid_api_entities::entity::{host, host_config_override, host_static_address, network, organization};
|
||||||
|
use trifid_api_entities::entity::prelude::HostConfigOverride;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct ListHostsRequestOpts {
|
pub struct ListHostsRequestOpts {
|
||||||
|
@ -2328,3 +2329,495 @@ pub async fn create_host_and_enrollment_code(
|
||||||
metadata: CreateHostAndCodeResponseMetadata {},
|
metadata: CreateHostAndCodeResponseMetadata {},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct HostConfigOverrideResponse {
|
||||||
|
pub data: HostConfigOverrideData
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct HostConfigOverrideData {
|
||||||
|
pub overrides: Vec<HostConfigOverrideDataOverride>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct HostConfigOverrideDataOverride {
|
||||||
|
pub key: String,
|
||||||
|
pub value: HostConfigOverrideDataOverrideValue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum HostConfigOverrideDataOverrideValue {
|
||||||
|
Boolean(bool),
|
||||||
|
Numeric(i64),
|
||||||
|
Other(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/v1/hosts/{host_id}/config-overrides")]
|
||||||
|
pub async fn get_host_overrides(id: Path<String>, req_info: HttpRequest, db: Data<AppState>) -> HttpResponse {
|
||||||
|
// For this endpoint, you either need to be a fully authenticated user OR a token with hosts:read
|
||||||
|
let session_info = enforce_2fa(&req_info, &db.conn)
|
||||||
|
.await
|
||||||
|
.unwrap_or(TokenInfo::NotPresent);
|
||||||
|
let api_token_info = enforce_api_token(&req_info, &["hosts:read"], &db.conn)
|
||||||
|
.await
|
||||||
|
.unwrap_or(TokenInfo::NotPresent);
|
||||||
|
|
||||||
|
// If neither are present, throw an error
|
||||||
|
if matches!(session_info, TokenInfo::NotPresent)
|
||||||
|
&& matches!(api_token_info, TokenInfo::NotPresent)
|
||||||
|
{
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_UNAUTHORIZED".to_string(),
|
||||||
|
message: "This endpoint requires either a fully authenticated user or a token with the hosts:read scope".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both are present, throw an error
|
||||||
|
if matches!(session_info, TokenInfo::AuthToken(_))
|
||||||
|
&& matches!(api_token_info, TokenInfo::ApiToken(_))
|
||||||
|
{
|
||||||
|
return HttpResponse::BadRequest().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(),
|
||||||
|
message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(),
|
||||||
|
path: None
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let org_id = match api_token_info {
|
||||||
|
TokenInfo::ApiToken(tkn) => tkn.organization,
|
||||||
|
_ => {
|
||||||
|
// we have a session token, which means we have to do a db request to get the organization that this user owns
|
||||||
|
let user = match session_info {
|
||||||
|
TokenInfo::AuthToken(tkn) => tkn.session_info.user,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let org = match organization::Entity::find()
|
||||||
|
.filter(organization::Column::Owner.eq(user.id))
|
||||||
|
.one(&db.conn)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
error!("database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error performing the database request, please try again later.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(org) = org {
|
||||||
|
org.id
|
||||||
|
} else {
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_NO_ORG".to_string(),
|
||||||
|
message: "This user does not own any organizations. Try using an API token instead.".to_string(),
|
||||||
|
path: None
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let net_id;
|
||||||
|
|
||||||
|
let net = match network::Entity::find()
|
||||||
|
.filter(network::Column::Organization.eq(&org_id))
|
||||||
|
.one(&db.conn)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
error!("database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error performing the database request, please try again later.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(net) = net {
|
||||||
|
net_id = net.id;
|
||||||
|
} else {
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_NO_NET".to_string(),
|
||||||
|
message: "This user does not own any networks. Try using an API token instead."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = match host::Entity::find()
|
||||||
|
.filter(host::Column::Id.eq(id.into_inner()))
|
||||||
|
.one(&db.conn)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(h) => h,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error with the database query. Please try again later."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let host = match host {
|
||||||
|
Some(h) => h,
|
||||||
|
None => {
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_UNAUTHORIZED".to_string(),
|
||||||
|
message:
|
||||||
|
"This resource does not exist or you do not have permission to access it."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if host.network != net_id {
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_UNAUTHORIZED".to_string(),
|
||||||
|
message: "This resource does not exist or you do not have permission to access it."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_overrides = match trifid_api_entities::entity::host_config_override::Entity::find().filter(host_config_override::Column::Host.eq(host.id)).all(&db.conn).await {
|
||||||
|
Ok(h) => h,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error with the database query. Please try again later."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let overrides: Vec<HostConfigOverrideDataOverride> = config_overrides.iter().map(|u| {
|
||||||
|
let val;
|
||||||
|
if u.value == "true" || u.value == "false" {
|
||||||
|
val = HostConfigOverrideDataOverrideValue::Boolean(u.value == "true");
|
||||||
|
} else if u.value.chars().all(|c| c.is_numeric()) {
|
||||||
|
val = HostConfigOverrideDataOverrideValue::Numeric(u.value.parse().unwrap());
|
||||||
|
} else {
|
||||||
|
val = HostConfigOverrideDataOverrideValue::Other(u.value.clone());
|
||||||
|
}
|
||||||
|
HostConfigOverrideDataOverride {
|
||||||
|
key: u.key.clone(),
|
||||||
|
value: val,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(HostConfigOverrideResponse {
|
||||||
|
data: HostConfigOverrideData {
|
||||||
|
overrides,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct UpdateOverridesRequest {
|
||||||
|
pub overrides: Vec<HostConfigOverrideDataOverride>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/v1/hosts/{host_id}/config-overrides")]
|
||||||
|
pub async fn update_host_overrides(id: Path<String>, req: Json<UpdateOverridesRequest>, req_info: HttpRequest, db: Data<AppState>) -> HttpResponse {
|
||||||
|
// For this endpoint, you either need to be a fully authenticated user OR a token with hosts:read
|
||||||
|
let session_info = enforce_2fa(&req_info, &db.conn)
|
||||||
|
.await
|
||||||
|
.unwrap_or(TokenInfo::NotPresent);
|
||||||
|
let api_token_info = enforce_api_token(&req_info, &["hosts:read"], &db.conn)
|
||||||
|
.await
|
||||||
|
.unwrap_or(TokenInfo::NotPresent);
|
||||||
|
|
||||||
|
// If neither are present, throw an error
|
||||||
|
if matches!(session_info, TokenInfo::NotPresent)
|
||||||
|
&& matches!(api_token_info, TokenInfo::NotPresent)
|
||||||
|
{
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_UNAUTHORIZED".to_string(),
|
||||||
|
message: "This endpoint requires either a fully authenticated user or a token with the hosts:read scope".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both are present, throw an error
|
||||||
|
if matches!(session_info, TokenInfo::AuthToken(_))
|
||||||
|
&& matches!(api_token_info, TokenInfo::ApiToken(_))
|
||||||
|
{
|
||||||
|
return HttpResponse::BadRequest().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(),
|
||||||
|
message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(),
|
||||||
|
path: None
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let org_id = match api_token_info {
|
||||||
|
TokenInfo::ApiToken(tkn) => tkn.organization,
|
||||||
|
_ => {
|
||||||
|
// we have a session token, which means we have to do a db request to get the organization that this user owns
|
||||||
|
let user = match session_info {
|
||||||
|
TokenInfo::AuthToken(tkn) => tkn.session_info.user,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let org = match organization::Entity::find()
|
||||||
|
.filter(organization::Column::Owner.eq(user.id))
|
||||||
|
.one(&db.conn)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
error!("database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error performing the database request, please try again later.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(org) = org {
|
||||||
|
org.id
|
||||||
|
} else {
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_NO_ORG".to_string(),
|
||||||
|
message: "This user does not own any organizations. Try using an API token instead.".to_string(),
|
||||||
|
path: None
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let net_id;
|
||||||
|
|
||||||
|
let net = match network::Entity::find()
|
||||||
|
.filter(network::Column::Organization.eq(&org_id))
|
||||||
|
.one(&db.conn)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
error!("database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error performing the database request, please try again later.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(net) = net {
|
||||||
|
net_id = net.id;
|
||||||
|
} else {
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_NO_NET".to_string(),
|
||||||
|
message: "This user does not own any networks. Try using an API token instead."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = match host::Entity::find()
|
||||||
|
.filter(host::Column::Id.eq(id.into_inner()))
|
||||||
|
.one(&db.conn)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(h) => h,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error with the database query. Please try again later."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let host = match host {
|
||||||
|
Some(h) => h,
|
||||||
|
None => {
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_UNAUTHORIZED".to_string(),
|
||||||
|
message:
|
||||||
|
"This resource does not exist or you do not have permission to access it."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if host.network != net_id {
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_UNAUTHORIZED".to_string(),
|
||||||
|
message: "This resource does not exist or you do not have permission to access it."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_overrides = match trifid_api_entities::entity::host_config_override::Entity::find().filter(host_config_override::Column::Host.eq(&host.id)).all(&db.conn).await {
|
||||||
|
Ok(h) => h,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error with the database query. Please try again later."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for c_override in config_overrides {
|
||||||
|
match c_override.delete(&db.conn).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error with the database query. Please try again later."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for c_override in &req.overrides {
|
||||||
|
let db_override = host_config_override::Model {
|
||||||
|
id: random_id("override"),
|
||||||
|
key: c_override.key.clone(),
|
||||||
|
value: match &c_override.value {
|
||||||
|
HostConfigOverrideDataOverrideValue::Boolean(v) => v.to_string(),
|
||||||
|
HostConfigOverrideDataOverrideValue::Numeric(v) => v.to_string(),
|
||||||
|
HostConfigOverrideDataOverrideValue::Other(v) => v.clone(),
|
||||||
|
},
|
||||||
|
host: host.id.clone(),
|
||||||
|
};
|
||||||
|
match db_override.into_active_model().insert(&db.conn).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error with the database query. Please try again later."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_overrides = match trifid_api_entities::entity::host_config_override::Entity::find().filter(host_config_override::Column::Host.eq(&host.id)).all(&db.conn).await {
|
||||||
|
Ok(h) => h,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error with the database query. Please try again later."
|
||||||
|
.to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let overrides: Vec<HostConfigOverrideDataOverride> = config_overrides.iter().map(|u| {
|
||||||
|
let val;
|
||||||
|
if u.value == "true" || u.value == "false" {
|
||||||
|
val = HostConfigOverrideDataOverrideValue::Boolean(u.value == "true");
|
||||||
|
} else if u.value.chars().all(|c| c.is_numeric()) || u.value.starts_with('-') && u.value.chars().collect::<Vec<_>>()[1..].iter().all(|c| c.is_numeric()) {
|
||||||
|
val = HostConfigOverrideDataOverrideValue::Numeric(u.value.parse().unwrap());
|
||||||
|
} else {
|
||||||
|
val = HostConfigOverrideDataOverrideValue::Other(u.value.clone());
|
||||||
|
}
|
||||||
|
HostConfigOverrideDataOverride {
|
||||||
|
key: u.key.clone(),
|
||||||
|
value: val,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(HostConfigOverrideResponse {
|
||||||
|
data: HostConfigOverrideData {
|
||||||
|
overrides,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue