full role functionality

This commit is contained in:
core 2023-07-27 17:09:27 -04:00
parent e44f170c6e
commit 186c2633d8
Signed by: core
GPG Key ID: FDBF740DADDCEECF
4 changed files with 471 additions and 4 deletions

View File

@ -82,6 +82,9 @@
"name": "Name",
"rules": "Rule count",
"description": "Description",
"actions": "Actions",
"editbtn": "Edit",
"delete": "Delete",
"add": {
"any": "Any",
"name": "Role name",
@ -105,7 +108,34 @@
"role": "Allowed role",
"desc": "Description",
"add": "Add rule",
"edit": "Edit rule",
"edit": "Save edit",
"cancel": "Cancel"
}
},
"edit": {
"any": "Any",
"name": "Role name",
"desc": "Role description",
"button": "Save",
"cancel": "Cancel",
"rules": "Inbound firewall rules",
"rulesexplainer": "Inbound traffic is denied by default. Add rules to allow traffic from hosts belonging to specific roles.",
"rulescols": {
"description": "Description",
"protocol": "Protocol",
"portrange": "Port range",
"allowedrole": "Allowed role"
},
"ruleremove": "Remove rule",
"ruleedit": "Edit rule",
"rulesadd": "Add rule",
"editrule": {
"protocol": "Protocol",
"range": "Port or port range",
"role": "Allowed role",
"desc": "Description",
"add": "Add rule",
"edit": "Save edit",
"cancel": "Cancel"
}
}

View File

@ -76,6 +76,23 @@
async function roleAdd() {
window.location.href = "/roles/add";
}
async function roleDelete(id: string) {
const configuration = new Configuration({
basePath: PUBLIC_BASE_URL,
accessToken: window.localStorage.getItem("session") + " " + window.localStorage.getItem("mfa")
});
const rolesApi = new RolesApi(configuration);
await rolesApi.roleDelete(
{
roleID: id
}
);
window.location.reload();
}
</script>
<svelte:head>
@ -98,12 +115,21 @@
<th>{$t("roles.name")}</th>
<th>{$t("roles.rules")}</th>
<th>{$t("roles.description")}</th>
<th>{$t("roles.actions")}</th>
</tr>
{#each roles.data as role}
<tr>
<td>{role.name}</td>
<td>
<a href="/roles/{role.id}/edit">
{role.name}
</a>
</td>
<td>{role.firewallRules.length}</td>
<td>{role.description}</td>
<td>
<button on:click={() => {window.location.href = `/roles/${role.id}/edit`}}>{$t("roles.editbtn")}</button>
<button on:click={() => {roleDelete(role.id)}}>{$t("roles.delete")}</button>
</td>
</tr>
{/each}
</table>

View File

@ -0,0 +1,411 @@
<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, NetworksApi, RolesApi, FirewallRuleProtocolEnum} from "$lib/api";
import type {FirewallRule} from "$lib/api";
import AdminBar from "$components/AdminBar.svelte";
import {page} from "$app/stores";
let loading = true;
let isError = false;
let error = '';
$: currentlyLoading = $isLoading || loading;
logSetup();
let logger = new Logger("roles/edit/+page.svelte");
let roles;
let role;
let roleName = '';
let roleDescription = '';
enum RuleProtocol {
ANY = 0,
TCP = 1,
UDP = 2,
ICMP = 3
}
function protoToStringName(proto: RuleProtocol): string {
console.log(`proto: ${proto}`);
if (proto == RuleProtocol.ANY) {
return $t("roles.edit.any");
} else if (proto == RuleProtocol.TCP) {
return "TCP";
} else if (proto == RuleProtocol.UDP) {
return "UDP";
} else if (proto == RuleProtocol.ICMP) {
return "ICMP";
}
}
interface Rule {
description: string,
protocol: RuleProtocol,
portRange: null | number | [number, number]
allowedRole: string | null
}
function prettyPortRange(range: null | number | [number, number]): string {
if (range === null) {
return $t("roles.edit.any");
} else if (typeof range === 'number') {
return String(range);
} else {
return String(range[0]) + "-" + String(range[1])
}
}
function findRole(role: string): string {
if (role === null) {
return $t("roles.edit.any");
}
for (let i = 0; i < roles.data.length; i++) {
if (roles.data[i].id == role) {
return roles.data[i].name;
}
}
return role;
}
let rules: Rule[] = [];
function removeRule(rule: Rule) {
let newRules: Rule[] = [];
for (let i = 0; i < rules.length; i++) {
if (rules[i] != rule) {
newRules.push(rules[i]);
}
}
rules = newRules;
}
let isEditingRule = false;
let editingRuleDesc = "";
let editingRuleProtocol = RuleProtocol.ANY;
let editingRulePortRange = "any";
let editingRuleAllowedRole = "any";
let editingExistingRule = false;
let editingExistingTheRule = null;
$: allowClickingOtherStuff = isEditingRule;
function editRule(rule: Rule) {
if (isEditingRule) return;
isEditingRule = true;
editingRuleDesc = rule.description;
editingRuleProtocol = rule.protocol;
editingRulePortRange = prettyPortRange(rule.portRange);
editingRuleAllowedRole = (rule.allowedRole === null) ? "any" : rule.allowedRole;
editingExistingRule = true;
editingExistingTheRule = rule;
}
function addRule() {
if (isEditingRule) return;
isEditingRule = true;
editingRuleDesc = "";
editingRuleProtocol = RuleProtocol.ANY;
editingRulePortRange = "any";
editingRuleAllowedRole = "any";
editingExistingRule = false;
editingExistingTheRule = null;
}
function finishEdit() {
if (!isEditingRule) return;
// parse port range
let portRange;
if (editingRulePortRange == "any" || editingRulePortRange == "Any") {
portRange = null;
} else {
let split = editingRulePortRange.split("-");
if (split.length == 1) {
portRange = parseInt(split[0]);
} else {
portRange = [parseInt(split[0]), parseInt(split[1])];
}
}
rules.push({
allowedRole: editingRuleAllowedRole,
description: editingRuleDesc,
portRange: portRange,
protocol: editingRuleProtocol
});
if (editingExistingRule) {
removeRule(editingExistingTheRule);
}
isEditingRule = false;
editingRuleDesc = "";
editingRuleProtocol = RuleProtocol.ANY;
editingRulePortRange = "any";
editingRuleAllowedRole = "any";
editingExistingRule = false;
editingExistingTheRule = null;
}
function cancelEdit() {
if (!isEditingRule) return;
isEditingRule = false;
editingRuleDesc = "";
editingRuleProtocol = RuleProtocol.ANY;
editingRulePortRange = "any";
editingRuleAllowedRole = "any";
editingExistingRule = false;
editingExistingTheRule = null;
}
function convertRule(rule: Rule): FirewallRule {
let protocol;
if (rule.protocol == RuleProtocol.ANY) {
protocol = FirewallRuleProtocolEnum.Any;
} else if (rule.protocol == RuleProtocol.TCP) {
protocol = FirewallRuleProtocolEnum.Tcp;
} else if (rule.protocol == RuleProtocol.UDP) {
protocol = FirewallRuleProtocolEnum.Udp;
} else if (rule.protocol == RuleProtocol.ICMP) {
protocol = FirewallRuleProtocolEnum.Icmp;
}
let allowedRole = undefined;
if (rule.allowedRole == null) {
allowedRole = undefined;
} else {
allowedRole = rule.allowedRole;
}
let portRange;
if (typeof rule.portRange === "number") {
portRange = {
from: rule.portRange,
to: rule.portRange
}
} else if (rule.portRange === null) {
portRange = undefined;
} else {
portRange = {
from: rule.portRange[0],
to: rule.portRange[1]
}
}
return {
protocol: protocol,
description: rule.description,
allowedRoleID: allowedRole,
portRange: portRange
};
}
async function roleEdit() {
const configuration = new Configuration({
basePath: PUBLIC_BASE_URL,
accessToken: window.localStorage.getItem("session") + " " + window.localStorage.getItem("mfa")
});
const rolesApi = new RolesApi(configuration);
let apirules = [];
for (let i = 0; i < rules.length; i++) {
apirules.push(convertRule(rules[i]));
}
await rolesApi.roleEdit(
{
roleID: $page.params.role_id,
roleEditRequest: {
name: roleName,
description: roleDescription,
firewallRules: apirules
}
}
);
window.location.href = "/roles"
}
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;
}
const rolesApi = new RolesApi(configuration);
roles = await rolesApi.rolesList();
// pull our role
role = await rolesApi.roleGet({
roleID: $page.params.role_id
});
roleName = role.data.name;
roleDescription = role.data.description;
for (let i = 0; i < role.data.firewallRules.length; i++) {
let rule = role.data.firewallRules[i];
let portRange;
if (rule.portRange === undefined) {
portRange = null;
} else if (rule.portRange.from == rule.portRange.to) {
portRange = rule.portRange.from;
} else {
portRange = [rule.portRange.from, rule.portRange.to];
}
let protocol;
if (rule.protocol === "ANY") {
protocol = RuleProtocol.ANY;
} else if (rule.protocol === "TCP") {
protocol = RuleProtocol.TCP;
} else if (rule.protocol === "UDP") {
protocol = RuleProtocol.UDP;
} else if (rule.protocol === "ICMP") {
protocol = RuleProtocol.ICMP;
}
let allowedRole;
if (rule.allowedRoleID === undefined) {
allowedRole = null;
} else {
allowedRole = rule.allowedRoleID;
}
rules.push({
description: rule.description,
protocol: protocol,
portRange: portRange,
allowedRole: allowedRole
});
}
console.log(role);
loading = false;
});
</script>
<svelte:head>
<title>{$t("common.title", {values: {title: $t("common.page.roles")}})}</title>
</svelte:head>
<LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}>
<AdminBar selected="roles" />
<form on:submit|preventDefault={roleEdit}>
<label for="roleName">{$t("roles.edit.name")}</label>
<input disabled bind:value={roleName} type="text" id="roleName" />
<label for="roleDesc">{$t("roles.edit.desc")}</label>
<input bind:value={roleDescription} type="text" id="roleDesc" />
<h3>{$t("roles.edit.rules")}</h3>
<p>{$t("roles.edit.rulesexplainer")}</p>
<!-- firewall rules -->
<table>
<tr>
<th>{$t("roles.edit.rulescols.description")}</th>
<th>{$t("roles.edit.rulescols.protocol")}</th>
<th>{$t("roles.edit.rulescols.portrange")}</th>
<th>{$t("roles.edit.rulescols.allowedrole")}</th>
</tr>
{#each rules as rule}
<tr>
<td>{rule.description}</td>
<td>{protoToStringName(rule.protocol)}</td>
<td>{prettyPortRange(rule.portRange)}</td>
<td>{findRole(rule.allowedRole)}</td>
<button disabled="{allowClickingOtherStuff}" on:click={() => {editRule(rule)}}>{$t("roles.edit.ruleedit")}</button>
<button disabled="{allowClickingOtherStuff}" on:click={() => {removeRule(rule)}}>{$t("roles.edit.ruleremove")}</button>
</tr>
{/each}
</table>
<button disabled="{allowClickingOtherStuff}" on:click={() => {addRule()}}>{$t("roles.edit.rulesadd")}</button>
{#if isEditingRule}
<form on:submit|preventDefault>
<label for="ruleProtocol">{$t("roles.edit.editrule.protocol")}</label>
<select id="ruleProtocol" bind:value="{editingRuleProtocol}">
<option value="{RuleProtocol.ANY}">{$t("roles.edit.any")}</option>
<option value="{RuleProtocol.TCP}">TCP</option>
<option value="{RuleProtocol.UDP}">UDP</option>
<option value="{RuleProtocol.ICMP}">ICMP</option>
</select>
<label for="rulePortRange">{$t("roles.edit.editrule.range")}</label>
<input type="text" bind:value="{editingRulePortRange}" id="rulePortRange"/>
<label for="ruleAllowedRole">{$t("roles.edit.editrule.role")}</label>
<select id="ruleAllowedRole" bind:value="{editingRuleAllowedRole}">
<option value="any">{$t("roles.edit.any")}</option>
{#each roles.data as role}
<option value="{role.id}">{role.name}</option>
{/each}
</select>
<label for="ruleDescription">{$t("roles.edit.editrule.desc")}</label>
<input type="text" bind:value="{editingRuleDesc}" id="ruleDescription"/>
<button on:click|preventDefault={finishEdit}>{editingExistingRule ? $t("roles.edit.editrule.edit") : $t("roles.edit.editrule.add")}</button>
<button on:click|preventDefault={cancelEdit}>{$t("roles.edit.editrule.cancel")}</button>
</form>
{/if}
<br/>
<button disabled="{allowClickingOtherStuff}">{$t("roles.edit.button")}</button>
<button disabled="{allowClickingOtherStuff}" on:click|preventDefault={() => {window.location.href = "/roles";}}>{$t("roles.edit.cancel")}</button>
</form>
</LoadingWrapper>

View File

@ -301,7 +301,7 @@
<LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}>
<AdminBar selected="roles" />
<form on:submit|preventDefault={roleAdd}>
<form>
<label for="roleName">{$t("roles.add.name")}</label>
<input bind:value={roleName} type="text" id="roleName" />
<label for="roleDesc">{$t("roles.add.desc")}</label>
@ -358,7 +358,7 @@
<br/>
<button disabled="{allowClickingOtherStuff}">{$t("roles.add.button")}</button>
<button on:click={roleAdd} disabled="{allowClickingOtherStuff}">{$t("roles.add.button")}</button>
<button disabled="{allowClickingOtherStuff}" on:click|preventDefault={() => {window.location.href = "/roles";}}>{$t("roles.add.cancel")}</button>
</form>
</LoadingWrapper>