Compare commits
21 Commits
2f48449c47
...
237d6156eb
Author | SHA1 | Date |
---|---|---|
core | 237d6156eb | |
core | 2f1452087e | |
core | 866a1dc4a5 | |
core | 309c21682c | |
core | c97670e3c1 | |
core | 224a86ffbe | |
core | 564dbc3a6b | |
core | 7b3df2a641 | |
core | c4cce4a638 | |
core | 96e5229de5 | |
core | a439b9da90 | |
core | 90176995d3 | |
core | 5533f2853d | |
core | 7d217b92c9 | |
core | d99ca39c65 | |
core | 11a2059107 | |
core | a9b4de2731 | |
core | 61c6b65f9b | |
core | 409cc76afd | |
core | 0f112ae15a | |
core | 9bdee4f793 |
|
@ -0,0 +1,57 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
master
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
jobs:
|
||||||
|
build_x64:
|
||||||
|
runs_on: docker
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Go toolchain
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: '1.20'
|
||||||
|
- name: Setup Rust toolchain
|
||||||
|
uses: https://github.com/actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
- name: Install additional dependencies
|
||||||
|
run: apt update && apt-get install -y libclang-dev clang sshpass rsync
|
||||||
|
- name: Enable Rust dependency caching
|
||||||
|
uses: https://github.com/Swatinem/rust-cache@v2
|
||||||
|
- name: Compile release binary
|
||||||
|
uses: https://github.com/actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --release --bin tfclient
|
||||||
|
- name: Upload binary
|
||||||
|
run: sshpass -p "${{ secrets.TRIFID_DLCDN_PASSWORD }}" rsync --mkpath -e 'ssh -p ${{ secrets.TRIFID_DLCDN_PORT }} -o StrictHostKeyChecking=no' target/release/tfclient ${{ secrets.TRIFID_DLCDN_USER }}@${{ secrets.TRIFID_DLCDN_IP }}:${{ secrets.TRIFID_DLCDN_PATH }}/tfclient/amd64/$GITHUB_SHA/tfclient
|
||||||
|
build_arm64:
|
||||||
|
runs_on: docker-arm64
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Go toolchain
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: '1.20'
|
||||||
|
- name: Setup Rust toolchain
|
||||||
|
uses: https://github.com/actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
- name: Install additional dependencies
|
||||||
|
run: apt update && apt-get install -y libclang-dev clang sshpass rsync
|
||||||
|
- name: Compile release binary
|
||||||
|
uses: https://github.com/actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --release --bin tfclient
|
||||||
|
- name: Upload binary
|
||||||
|
run: sshpass -p "${{ secrets.TRIFID_DLCDN_PASSWORD }}" rsync --mkpath -e 'ssh -p ${{ secrets.TRIFID_DLCDN_PORT }} -o StrictHostKeyChecking=no' target/release/tfclient ${{ secrets.TRIFID_DLCDN_USER }}@${{ secrets.TRIFID_DLCDN_IP }}:${{ secrets.TRIFID_DLCDN_PATH }}/tfclient/arm64/$GITHUB_SHA/tfclient
|
|
@ -92,7 +92,20 @@
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"enroll": "Enroll",
|
"enroll": "Enroll",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"config": "Configuration"
|
"config": "Configuration",
|
||||||
|
"add": "Add",
|
||||||
|
"create": {
|
||||||
|
"name": "Host name",
|
||||||
|
"btn": "Add host",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"role": "Role",
|
||||||
|
"ip": "IP Address",
|
||||||
|
"error": {
|
||||||
|
"needsname": "Host name is required.",
|
||||||
|
"needsrole": "Role is required.",
|
||||||
|
"invalidip": "Invalid IP address"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"delete": {
|
"delete": {
|
||||||
|
@ -184,7 +197,8 @@
|
||||||
"hosts": "Hosts",
|
"hosts": "Hosts",
|
||||||
"roles": "Roles",
|
"roles": "Roles",
|
||||||
"lighthouses": "Lighthouses",
|
"lighthouses": "Lighthouses",
|
||||||
"relays": "Relays"
|
"relays": "Relays",
|
||||||
|
"addhost": "Add Host"
|
||||||
},
|
},
|
||||||
"logout": "Log out",
|
"logout": "Log out",
|
||||||
"loading": "Dashboard is loading"
|
"loading": "Dashboard is loading"
|
||||||
|
|
|
@ -98,7 +98,8 @@
|
||||||
|
|
||||||
<LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}>
|
<LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}>
|
||||||
<AdminLayout selected="hosts">
|
<AdminLayout selected="hosts">
|
||||||
<h3>{$t("common.page.hosts")}</h3>
|
|
||||||
|
<h3>{$t("common.page.hosts")} <a href="/hosts/add" class="btn btn-primary float-end">{$t("hosts.add")} <i class="fas fa-plus ms-1"></i></a></h3>
|
||||||
|
|
||||||
<table class="table table-dark table-hover">
|
<table class="table table-dark table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -114,7 +115,7 @@
|
||||||
{#each hosts as host}
|
{#each hosts as host}
|
||||||
{#if !(host.isLighthouse || host.isRelay)}
|
{#if !(host.isLighthouse || host.isRelay)}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{host.name}</td>
|
<td><a href="/hosts/{host.id}/edit">{host.name}</a></td>
|
||||||
<td>{host.metadata?.lastSeenAt}</td>
|
<td>{host.metadata?.lastSeenAt}</td>
|
||||||
<td>{host.ipAddress}</td>
|
<td>{host.ipAddress}</td>
|
||||||
<td><a href="/roles/{host.roleID}/edit">{getRoleName(host.roleID)}</a></td>
|
<td><a href="/roles/{host.roleID}/edit">{getRoleName(host.roleID)}</a></td>
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {isLoading, t} from "svelte-i18n";
|
||||||
|
import LoadingWrapper from "$components/LoadingWrapper.svelte";
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
import {APIResult, isAuthedMFA, isAuthedSession} from "$lib/auth.ts";
|
||||||
|
import {Logger, logSetup} from "$lib/logger";
|
||||||
|
import type {APIError} from "$lib/auth.ts";
|
||||||
|
import {PUBLIC_BASE_URL} from "$env/static/public";
|
||||||
|
import {Configuration, HostsApi, NetworksApi, ResponseError, RolesApi} from "$lib/api";
|
||||||
|
import type {Host} from "$lib/api/models/Host.ts";
|
||||||
|
import type {Role} from "$lib/api/models/Role.ts";
|
||||||
|
import AdminLayout from "$components/AdminLayout.svelte";
|
||||||
|
import {page} from "$app/stores";
|
||||||
|
|
||||||
|
let loading = true;
|
||||||
|
let isError = false;
|
||||||
|
let error = '';
|
||||||
|
$: currentlyLoading = $isLoading;
|
||||||
|
|
||||||
|
let hosts: Host[] = [];
|
||||||
|
let roles: Role[] = [];
|
||||||
|
|
||||||
|
let network = "";
|
||||||
|
|
||||||
|
logSetup();
|
||||||
|
let logger = new Logger("hosts/add/+page.svelte");
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
let session_load_info = await isAuthedSession();
|
||||||
|
if (session_load_info[0] == APIResult.Failed) {
|
||||||
|
let err = session_load_info[1] as APIError;
|
||||||
|
logger.error(`session load failed: ${err.code} ${err.message}`);
|
||||||
|
window.location.href = '/login';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mfa_load_info = await isAuthedMFA();
|
||||||
|
if (mfa_load_info[0] == APIResult.Failed) {
|
||||||
|
let err = mfa_load_info[1] as APIError;
|
||||||
|
logger.error(`mfa load failed: ${err.code} ${err.message}`);
|
||||||
|
window.location.href = '/2fa';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pull networks
|
||||||
|
const configuration = new Configuration({
|
||||||
|
basePath: PUBLIC_BASE_URL,
|
||||||
|
accessToken: window.localStorage.getItem("session") + " " + window.localStorage.getItem("mfa")
|
||||||
|
});
|
||||||
|
|
||||||
|
const networksApi = new NetworksApi(configuration);
|
||||||
|
let networks;
|
||||||
|
try {
|
||||||
|
networks = await networksApi.networksList();
|
||||||
|
} catch (e) {
|
||||||
|
let resp_json = await e.response.json();
|
||||||
|
if (resp_json.errors[0].code == "ERR_NO_ORG") {
|
||||||
|
window.location.href = "/networkcreate";
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
isError = true;
|
||||||
|
error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}});
|
||||||
|
loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(networks);
|
||||||
|
|
||||||
|
if (networks.data?.length == 0) {
|
||||||
|
window.location.href = '/networkcreate';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
network = networks.data![0].id;
|
||||||
|
|
||||||
|
const hostsApi = new HostsApi(configuration);
|
||||||
|
hosts = (await hostsApi.hostsList({
|
||||||
|
filterIsLighthouse: false,
|
||||||
|
filterIsRelay: false
|
||||||
|
})).data!;
|
||||||
|
|
||||||
|
console.log(hosts);
|
||||||
|
|
||||||
|
const rolesApi = new RolesApi(configuration);
|
||||||
|
roles = (await rolesApi.rolesList()).data!;
|
||||||
|
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
function getRoleName(byId: string): string {
|
||||||
|
for (let i = 0; i < roles.length; i++) {
|
||||||
|
if (roles[i].id == byId) {
|
||||||
|
return roles[i].name!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
let hostName = "";
|
||||||
|
let roleId = "";
|
||||||
|
let ipAddr = "";
|
||||||
|
|
||||||
|
let hasFormErr = false;
|
||||||
|
let formErr = "";
|
||||||
|
|
||||||
|
async function addHost() {
|
||||||
|
hasFormErr = false;
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
if (hostName == "") {
|
||||||
|
formErr = $t("hosts.create.error.needsname");
|
||||||
|
hasFormErr = true;
|
||||||
|
loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (roleId == "") {
|
||||||
|
formErr = $t("hosts.create.error.needsrole");
|
||||||
|
hasFormErr = true;
|
||||||
|
loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const re = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
|
||||||
|
|
||||||
|
// validate IP addr
|
||||||
|
if (!ipAddr.match(re)) {
|
||||||
|
formErr = $t("hosts.create.error.invalidip");
|
||||||
|
hasFormErr = true;
|
||||||
|
loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all is good, actually create the host
|
||||||
|
const configuration = new Configuration({
|
||||||
|
basePath: PUBLIC_BASE_URL,
|
||||||
|
accessToken: window.localStorage.getItem("session") + " " + window.localStorage.getItem("mfa")
|
||||||
|
});
|
||||||
|
const hostsApi = new HostsApi(configuration);
|
||||||
|
try {
|
||||||
|
await hostsApi.hostCreate({
|
||||||
|
hostCreateRequest: {
|
||||||
|
name: hostName,
|
||||||
|
networkID: network,
|
||||||
|
ipAddress: ipAddr,
|
||||||
|
roleID: roleId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
let body = await (<ResponseError>e).response.json();
|
||||||
|
|
||||||
|
console.log(body);
|
||||||
|
|
||||||
|
formErr = $t("hosts.create.error." + body.errors[0].code);
|
||||||
|
hasFormErr = true;
|
||||||
|
loading = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.location.href = "/hosts";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{$t("common.title", {values: {title: $t("common.page.hosts")}})}</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}>
|
||||||
|
<AdminLayout selected="hosts">
|
||||||
|
<h3>{$t("common.page.addhost")}</h3>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={addHost}>
|
||||||
|
<label for="name" class="form-label">{$t("hosts.create.name")}</label>
|
||||||
|
<input type="text" id="name" bind:value={hostName} class="form-control" />
|
||||||
|
|
||||||
|
<label for="role" class="form-label">{$t("hosts.create.role")}</label>
|
||||||
|
<select id="role" bind:value={roleId} class="form-select">
|
||||||
|
{#each roles as role}
|
||||||
|
<option value={role.id}>{role.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="ip" class="form-label">{$t("hosts.create.ip")}</label>
|
||||||
|
<input type="text" id="name" bind:value={ipAddr} class="form-control" />
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<button disabled class="mt-2 btn btn-primary"><i class="fas fa-gear fa-spin"></i></button>
|
||||||
|
{:else}
|
||||||
|
<button class="mt-2 btn btn-primary">{$t("hosts.create.btn")}</button>
|
||||||
|
{/if}
|
||||||
|
<button class="mt-2 btn btn-outline-info">{$t("hosts.create.cancel")}</button>
|
||||||
|
{#if hasFormErr}
|
||||||
|
<p class="text-danger">{formErr}</p>
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
</AdminLayout>
|
||||||
|
</LoadingWrapper>
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "trifid-api"
|
name = "trifid-api"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Pure-rust Defined Networking compatible management server"
|
description = "Pure-rust Defined Networking compatible management server"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
|
|
|
@ -57,7 +57,7 @@ pub async fn generate_config(
|
||||||
.unwrap()],
|
.unwrap()],
|
||||||
subnets: vec![],
|
subnets: vec![],
|
||||||
groups: vec![format!("role:{}", info.host.role)],
|
groups: vec![format!("role:{}", info.host.role)],
|
||||||
not_before: SystemTime::now(),
|
not_before: SystemTime::now() - Duration::from_secs(3600), // make certs that have already been valid for an hour. if your system clock is more than an hour behind, it is no longer my problem
|
||||||
not_after: SystemTime::now() + Duration::from_secs(CONFIG.crypto.certs_expiry_time),
|
not_after: SystemTime::now() + Duration::from_secs(CONFIG.crypto.certs_expiry_time),
|
||||||
public_key: info.dh_pubkey.clone().try_into().unwrap(),
|
public_key: info.dh_pubkey.clone().try_into().unwrap(),
|
||||||
is_ca: false,
|
is_ca: false,
|
||||||
|
|
|
@ -13,13 +13,14 @@ use log::{error, warn};
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use sea_orm::{ActiveModelTrait, EntityTrait};
|
use sea_orm::{ActiveModelTrait, EntityTrait};
|
||||||
use trifid_pki::cert::{deserialize_ed25519_public, deserialize_x25519_public};
|
use trifid_pki::cert::{deserialize_ed25519_public, deserialize_nebula_certificate_from_pem, deserialize_x25519_public};
|
||||||
|
|
||||||
use trifid_api_entities::entity::{host, keystore_entry, keystore_host};
|
use trifid_api_entities::entity::{host, keystore_entry, keystore_host};
|
||||||
use crate::error::APIErrorsResponse;
|
use crate::error::APIErrorsResponse;
|
||||||
use sea_orm::{ColumnTrait, QueryFilter, IntoActiveModel};
|
use sea_orm::{ColumnTrait, QueryFilter, IntoActiveModel};
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
use crate::config::NebulaConfig;
|
||||||
use crate::tokens::random_id;
|
use crate::tokens::random_id;
|
||||||
|
|
||||||
#[post("/v1/dnclient")]
|
#[post("/v1/dnclient")]
|
||||||
|
@ -217,9 +218,42 @@ pub async fn dnclient(
|
||||||
};
|
};
|
||||||
|
|
||||||
let current_cfg = keystore_data.config;
|
let current_cfg = keystore_data.config;
|
||||||
let generated_config_string = serde_yaml::to_string(&cfg).unwrap();
|
let current_cfg: NebulaConfig = match serde_yaml::from_str(¤t_cfg) {
|
||||||
|
Ok(cfg) => cfg,
|
||||||
|
Err(e) => {
|
||||||
|
error!("error loading old configuration: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(EnrollResponse::Error {
|
||||||
|
errors: vec![APIError {
|
||||||
|
code: "ERR_CFG_GENERATION_ERROR".to_string(),
|
||||||
|
message: "There was an error generating the host configuration.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let config_update_avail = current_cfg != generated_config_string || req.counter < keystore_header.counter as u32;
|
let mut config_is_different = current_cfg == cfg;
|
||||||
|
|
||||||
|
if config_is_different {
|
||||||
|
// check if it is a certificate issue
|
||||||
|
let c0 = current_cfg.clone();
|
||||||
|
let mut c1 = cfg.clone();
|
||||||
|
c1.pki.cert = c0.pki.cert.clone();
|
||||||
|
if c0 == c1 {
|
||||||
|
// its just the cert. deserialize both and check if any details have changed
|
||||||
|
let cert0 = deserialize_nebula_certificate_from_pem(c0.pki.cert.as_bytes()).expect("generated an invalid certificate");
|
||||||
|
let mut cert1 = deserialize_nebula_certificate_from_pem(c1.pki.cert.as_bytes()).expect("generated an invalid certificate");
|
||||||
|
cert1.details.not_before = cert0.details.not_before;
|
||||||
|
cert1.details.not_after = cert0.details.not_after;
|
||||||
|
if cert0.serialize_to_pem().expect("generated invalid cert") == cert1.serialize_to_pem().expect("generated invalid cert") {
|
||||||
|
// fake news! its fine actually
|
||||||
|
config_is_different = false;
|
||||||
|
}
|
||||||
|
// if anything else changed, we still want to issue the update
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_update_avail = config_is_different || req.counter < keystore_header.counter as u32;
|
||||||
|
|
||||||
host_am.last_out_of_date = Set(config_update_avail);
|
host_am.last_out_of_date = Set(config_update_avail);
|
||||||
host_am.last_seen_at = Set(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64);
|
host_am.last_seen_at = Set(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64);
|
||||||
|
|
Loading…
Reference in New Issue