consolidate user config paths,
move global config values to global key in localdb
This commit is contained in:
parent
8e476ea352
commit
4fbb64745b
@ -22,7 +22,7 @@ In Proxmox VE, follow the following steps:
|
||||
## Installation - API
|
||||
1. Clone this repo onto `Client Host`
|
||||
2. Run `npm install` to initiaze the package requirements
|
||||
3. Copy `localdb.json.template` as `localdb.json` and modify the following values under `pveAPIToken`:
|
||||
3. Copy `template.localdb.json` as `localdb.json` and modify the following values under `pveAPIToken`:
|
||||
- pveAPI - the URI to the Proxmox API, ie `<proxmoxhost>:8006/api2/json` or `<proxmox URL>/api2/json` if Proxmox VE is behind a reverse proxy.
|
||||
- hostname - the ProxmoxAAS-Client URL, ie `host.domain.tld`
|
||||
- domain - the base domain for the client and proxmox, ie `domain.tld`
|
||||
|
@ -1,158 +0,0 @@
|
||||
{
|
||||
"application": {
|
||||
"pveAPI": "https://pve.mydomain.example/api2/json",
|
||||
"pveAPIToken": {
|
||||
"user": "proxmoxaas-api",
|
||||
"realm": "pve",
|
||||
"id": "token",
|
||||
"uuid": "token-secret-value"
|
||||
},
|
||||
"pveroot": {
|
||||
"username": "root@pam",
|
||||
"password": "rootpassword"
|
||||
},
|
||||
"listenPort": 80,
|
||||
"hostname": "client.mydomain.example",
|
||||
"domain": "mydomain.example"
|
||||
},
|
||||
"resources": {
|
||||
"cpu": {
|
||||
"type": "list",
|
||||
"whitelist": true,
|
||||
"display": false
|
||||
},
|
||||
"cores": {
|
||||
"type": "numeric",
|
||||
"multiplier": 1,
|
||||
"base": 1024,
|
||||
"compact": false,
|
||||
"unit": "Cores",
|
||||
"display": true
|
||||
},
|
||||
"memory": {
|
||||
"type": "numeric",
|
||||
"multiplier": 1048576,
|
||||
"base": 1024,
|
||||
"compact": true,
|
||||
"unit": "B",
|
||||
"display": true
|
||||
},
|
||||
"swap": {
|
||||
"type": "numeric",
|
||||
"multiplier": 1048576,
|
||||
"base": 1024,
|
||||
"compact": true,
|
||||
"unit": "B",
|
||||
"display": true
|
||||
},
|
||||
"local": {
|
||||
"type": "storage",
|
||||
"multiplier": 1,
|
||||
"base": 1024,
|
||||
"compact": true,
|
||||
"unit": "B",
|
||||
"disks": [
|
||||
"rootfs",
|
||||
"mp",
|
||||
"sata",
|
||||
"unused"
|
||||
],
|
||||
"display": true
|
||||
},
|
||||
"cephpl": {
|
||||
"type": "storage",
|
||||
"multiplier": 1,
|
||||
"base": 1024,
|
||||
"compact": true,
|
||||
"unit": "B",
|
||||
"disks": [
|
||||
"rootfs",
|
||||
"mp",
|
||||
"sata",
|
||||
"unused"
|
||||
],
|
||||
"display": true
|
||||
},
|
||||
"network": {
|
||||
"type": "network",
|
||||
"multiplier": 1000000,
|
||||
"base": 1000,
|
||||
"compact": true,
|
||||
"unit": "B/s",
|
||||
"display": true
|
||||
},
|
||||
"pci": {
|
||||
"type": "list",
|
||||
"whitelist": true,
|
||||
"display": true
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"exampleuser@examplepool": {
|
||||
"resources": {
|
||||
"max": {
|
||||
"cpu": ["kvm64", "host"],
|
||||
"cores": 128,
|
||||
"memory": 131072,
|
||||
"swap": 131072,
|
||||
"local": 1099511627776,
|
||||
"cephpl": 1099511627776,
|
||||
"network": 100000,
|
||||
"pci": ["Device Name Matcher 1", "Device Name Matcher 2"]
|
||||
}
|
||||
},
|
||||
"nodes": [
|
||||
"examplenode1",
|
||||
"examplenode2"
|
||||
],
|
||||
"cluster": {
|
||||
"vmid": {
|
||||
"min": 200,
|
||||
"max": 299
|
||||
},
|
||||
"pool": "examplepool"
|
||||
},
|
||||
"templates": {
|
||||
"instances": {
|
||||
"lxc": {
|
||||
"net0": {
|
||||
"value": "name=eth0,bridge=vmbr0,ip=dhcp,ip6=dhcp,tag=10,type=veth,rate=1000",
|
||||
"resource": {
|
||||
"name": "network",
|
||||
"amount": 1000
|
||||
}
|
||||
}
|
||||
},
|
||||
"qemu": {
|
||||
"cpu": {
|
||||
"value": "host",
|
||||
"resource": null
|
||||
},
|
||||
"net0": {
|
||||
"value": "virtio,bridge=vmbr0,tag=10,rate=1000",
|
||||
"resource": {
|
||||
"name": "network",
|
||||
"amount": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"lxc": {
|
||||
"type": "veth",
|
||||
"bridge": "vmbr0",
|
||||
"vlan": 10,
|
||||
"ip": "dhcp",
|
||||
"ip6": "dhcp"
|
||||
},
|
||||
"qemu": {
|
||||
"type": "virtio",
|
||||
"bridge": "vmbr0",
|
||||
"vlan": 10
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
165
config/template.localdb.json
Normal file
165
config/template.localdb.json
Normal file
@ -0,0 +1,165 @@
|
||||
{
|
||||
"global": {
|
||||
"application": {
|
||||
"pveAPI": "https://pve.mydomain.example/api2/json",
|
||||
"pveAPIToken": {
|
||||
"user": "proxmoxaas-api",
|
||||
"realm": "pve",
|
||||
"id": "token",
|
||||
"uuid": "token-secret-value"
|
||||
},
|
||||
"pveroot": {
|
||||
"username": "root@pam",
|
||||
"password": "rootpassword"
|
||||
},
|
||||
"listenPort": 80,
|
||||
"hostname": "client.mydomain.example",
|
||||
"domain": "mydomain.example"
|
||||
},
|
||||
"resources": {
|
||||
"cpu": {
|
||||
"type": "list",
|
||||
"whitelist": true,
|
||||
"display": false
|
||||
},
|
||||
"cores": {
|
||||
"type": "numeric",
|
||||
"multiplier": 1,
|
||||
"base": 1024,
|
||||
"compact": false,
|
||||
"unit": "Cores",
|
||||
"display": true
|
||||
},
|
||||
"memory": {
|
||||
"type": "numeric",
|
||||
"multiplier": 1048576,
|
||||
"base": 1024,
|
||||
"compact": true,
|
||||
"unit": "B",
|
||||
"display": true
|
||||
},
|
||||
"swap": {
|
||||
"type": "numeric",
|
||||
"multiplier": 1048576,
|
||||
"base": 1024,
|
||||
"compact": true,
|
||||
"unit": "B",
|
||||
"display": true
|
||||
},
|
||||
"local": {
|
||||
"type": "storage",
|
||||
"multiplier": 1,
|
||||
"base": 1024,
|
||||
"compact": true,
|
||||
"unit": "B",
|
||||
"disks": [
|
||||
"rootfs",
|
||||
"mp",
|
||||
"sata",
|
||||
"unused"
|
||||
],
|
||||
"display": true
|
||||
},
|
||||
"cephpl": {
|
||||
"type": "storage",
|
||||
"multiplier": 1,
|
||||
"base": 1024,
|
||||
"compact": true,
|
||||
"unit": "B",
|
||||
"disks": [
|
||||
"rootfs",
|
||||
"mp",
|
||||
"sata",
|
||||
"unused"
|
||||
],
|
||||
"display": true
|
||||
},
|
||||
"network": {
|
||||
"type": "network",
|
||||
"multiplier": 1000000,
|
||||
"base": 1000,
|
||||
"compact": true,
|
||||
"unit": "B/s",
|
||||
"display": true
|
||||
},
|
||||
"pci": {
|
||||
"type": "list",
|
||||
"whitelist": true,
|
||||
"display": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"exampleuser@examplepool": {
|
||||
"resources": {
|
||||
"max": {
|
||||
"cpu": [
|
||||
"kvm64",
|
||||
"host"
|
||||
],
|
||||
"cores": 128,
|
||||
"memory": 131072,
|
||||
"swap": 131072,
|
||||
"local": 1099511627776,
|
||||
"cephpl": 1099511627776,
|
||||
"network": 100000,
|
||||
"pci": [
|
||||
"Device Name Matcher 1",
|
||||
"Device Name Matcher 2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"nodes": [
|
||||
"examplenode1",
|
||||
"examplenode2"
|
||||
],
|
||||
"cluster": {
|
||||
"vmid": {
|
||||
"min": 100,
|
||||
"max": 199
|
||||
},
|
||||
"pool": "examplepool"
|
||||
},
|
||||
"templates": {
|
||||
"instances": {
|
||||
"lxc": {
|
||||
"net0": {
|
||||
"value": "name=eth0,bridge=vmbr0,ip=dhcp,ip6=dhcp,tag=10,type=veth,rate=1000",
|
||||
"resource": {
|
||||
"name": "network",
|
||||
"amount": 1000
|
||||
}
|
||||
}
|
||||
},
|
||||
"qemu": {
|
||||
"cpu": {
|
||||
"value": "host",
|
||||
"resource": null
|
||||
},
|
||||
"net0": {
|
||||
"value": "virtio,bridge=vmbr0,tag=10,rate=1000",
|
||||
"resource": {
|
||||
"name": "network",
|
||||
"amount": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"lxc": {
|
||||
"type": "veth",
|
||||
"bridge": "vmbr0",
|
||||
"vlan": 10,
|
||||
"ip": "dhcp",
|
||||
"ip6": "dhcp"
|
||||
},
|
||||
"qemu": {
|
||||
"type": "virtio",
|
||||
"bridge": "vmbr0",
|
||||
"vlan": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
src/db.js
25
src/db.js
@ -22,27 +22,18 @@ class LocalDB {
|
||||
writeFileSync(path, JSON.stringify(this.#data));
|
||||
}
|
||||
|
||||
getApplicationConfig () {
|
||||
return this.#data.application;
|
||||
}
|
||||
|
||||
getResourceConfig () {
|
||||
return this.#data.resources;
|
||||
getGlobalConfig () {
|
||||
return this.#data.global;
|
||||
}
|
||||
|
||||
getUserConfig (username) {
|
||||
if (this.#data.users[username]) {
|
||||
return this.#data.users[username];
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
return this.#data.users[username];
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new LocalDB();
|
||||
export const pveAPI = db.getApplicationConfig().pveAPI;
|
||||
export const pveAPIToken = db.getApplicationConfig().pveAPIToken;
|
||||
export const listenPort = db.getApplicationConfig().listenPort;
|
||||
export const hostname = db.getApplicationConfig().hostname;
|
||||
export const domain = db.getApplicationConfig().domain;
|
||||
export const pveAPI = db.getGlobalConfig().application.pveAPI;
|
||||
export const pveAPIToken = db.getGlobalConfig().application.pveAPIToken;
|
||||
export const listenPort = db.getGlobalConfig().application.listenPort;
|
||||
export const hostname = db.getGlobalConfig().application.hostname;
|
||||
export const domain = db.getGlobalConfig().application.domain;
|
||||
|
123
src/main.js
123
src/main.js
@ -37,18 +37,6 @@ app.get("/api/echo", (req, res) => {
|
||||
res.status(200).send({ body: req.body, cookies: req.cookies });
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - check authentication
|
||||
* responses:
|
||||
* - 200: {auth: true, path: String}
|
||||
* - 401: {auth: false, path: String}
|
||||
*/
|
||||
app.get("/api/auth", async (req, res) => {
|
||||
let auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) { return; }
|
||||
res.status(200).send({ auth: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - proxy proxmox api without privilege elevation
|
||||
* request and responses passed through to/from proxmox
|
||||
@ -69,6 +57,18 @@ app.post("/api/proxmox/*", async (req, res) => { // proxy endpoint for POST prox
|
||||
res.status(result.status).send(result.data);
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - check authentication
|
||||
* responses:
|
||||
* - 200: {auth: true, path: String}
|
||||
* - 401: {auth: false, path: String}
|
||||
*/
|
||||
app.get("/api/auth", async (req, res) => {
|
||||
let auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) { return; }
|
||||
res.status(200).send({ auth: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* POST - safer ticket generation using proxmox authentication but adding HttpOnly
|
||||
* request:
|
||||
@ -78,7 +78,7 @@ app.post("/api/proxmox/*", async (req, res) => { // proxy endpoint for POST prox
|
||||
* - 200: {auth: true, path: String}
|
||||
* - 401: {auth: false, path: String}
|
||||
*/
|
||||
app.post("/api/ticket", async (req, res) => {
|
||||
app.post("/api/auth/ticket", async (req, res) => {
|
||||
let response = await requestPVE("/access/ticket", "POST", null, JSON.stringify(req.body));
|
||||
if (!(response.status === 200)) {
|
||||
res.status(response.status).send({ auth: false });
|
||||
@ -101,7 +101,7 @@ app.post("/api/ticket", async (req, res) => {
|
||||
* responses:
|
||||
* - 200: {auth: false, path: String}
|
||||
*/
|
||||
app.delete("/api/ticket", async (req, res) => {
|
||||
app.delete("/api/auth/ticket", async (req, res) => {
|
||||
let expire = new Date(0);
|
||||
res.cookie("PVEAuthCookie", "", { domain: domain, path: "/", httpOnly: true, secure: true, expires: expire });
|
||||
res.cookie("CSRFPreventionToken", "", { domain: domain, path: "/", httpOnly: true, secure: true, expires: expire });
|
||||
@ -110,13 +110,35 @@ app.delete("/api/ticket", async (req, res) => {
|
||||
res.status(200).send({ auth: false });
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get db global resource configuration
|
||||
* responses:
|
||||
* - 200: Object
|
||||
*/
|
||||
app.get("/api/global/config/:key", async (req, res) => {
|
||||
let params = {
|
||||
key: req.params.key
|
||||
}
|
||||
// check auth
|
||||
let auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) { return; }
|
||||
let allowKeys = ["resources"];
|
||||
if (allowKeys.includes(params.key)){
|
||||
let config = db.getGlobalConfig();
|
||||
res.status(200).send(config[params.key]);
|
||||
}
|
||||
else {
|
||||
res.status(401).send({auth: false, error: `User is not authorized to access /global/config/${params.key}.`});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get db user resource information including allocated, free, and maximum resource values along with resource metadata
|
||||
* responses:
|
||||
* - 200: {avail: Object, max: Object, used: Object, resources: Object}
|
||||
* - 401: {auth: false, path: String}
|
||||
*/
|
||||
app.get("/api/user/resources", async (req, res) => {
|
||||
app.get("/api/user/dynamic/resources", async (req, res) => {
|
||||
// check auth
|
||||
let auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) { return; }
|
||||
@ -125,60 +147,31 @@ app.get("/api/user/resources", async (req, res) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get db global resource configuration
|
||||
* responses:
|
||||
* - 200: Object
|
||||
*/
|
||||
app.get("/api/global/config/resources", async (req, res) => {
|
||||
// check auth
|
||||
let auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) { return; }
|
||||
let config = db.getResourceConfig();
|
||||
res.status(200).send(config);
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get db user resource configuration
|
||||
* GET - get db user configuration by key
|
||||
* request:
|
||||
* - key: User config key
|
||||
* responses:
|
||||
* - 200: Object
|
||||
* - 401: {auth: false, path: String}
|
||||
* - 401: {auth: false, error: String}
|
||||
*/
|
||||
app.get("/api/user/config/resources", async (req, res) => {
|
||||
app.get(`/api/user/config/:key`, async (req, res) => {
|
||||
let params = {
|
||||
key: req.params.key
|
||||
}
|
||||
// check auth
|
||||
let auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) { return; }
|
||||
let config = db.getUserConfig(req.cookies.username);
|
||||
res.status(200).send(config.resources);
|
||||
let allowKeys = ["resources", "cluster", "nodes"];
|
||||
if (allowKeys.includes(params.key)){
|
||||
let config = db.getUserConfig(req.cookies.username);
|
||||
res.status(200).send(config[params.key]);
|
||||
}
|
||||
else {
|
||||
res.status(401).send({auth: false, error: `User is not authorized to access /user/config/${params.key}.`});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get db user cluster configuration
|
||||
* responses:
|
||||
* - 200: {pool: String, templates: {lxc: Object, qemu: Object}, vmid: {min: Number, max: Number}}
|
||||
* - 401: {auth: false, path: String}
|
||||
*/
|
||||
app.get("/api/user/config/cluster", async (req, res) => {
|
||||
// check auth
|
||||
let auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) { return; }
|
||||
let config = db.getUserConfig(req.cookies.username);
|
||||
res.status(200).send(config.cluster)
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get db user node configuration
|
||||
* responses:
|
||||
* - 200: {nodes: String[]}
|
||||
* - 401: {auth: false, path: String}
|
||||
*/
|
||||
app.get("/api/user/config/nodes", async (req, res) => {
|
||||
// check auth
|
||||
let auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) { return; }
|
||||
let config = db.getUserConfig(req.cookies.username);
|
||||
res.status(200).send(config.nodes)
|
||||
})
|
||||
|
||||
/**
|
||||
* POST - detach mounted disk from instance
|
||||
* request:
|
||||
@ -259,7 +252,7 @@ app.post(`/api/:node(${nodeRegexP})/:type(${typeRegexP})/:vmid(${vmidRegexP})/di
|
||||
}
|
||||
// target disk must be allowed according to source disk's storage options
|
||||
let diskConfig = await getDiskInfo(params.node, params.type, params.vmid, `unused${params.source}`); // get target disk
|
||||
let resourceConfig = db.getResourceConfig();
|
||||
let resourceConfig = db.getGlobalConfig().resources;
|
||||
if (!resourceConfig[diskConfig.storage].disks.some(diskPrefix => params.disk.startsWith(diskPrefix))) {
|
||||
res.status(500).send({ error: `Requested target ${params.disk} is not in allowed list [${resourceConfig[diskConfig.storage].disks}].` });
|
||||
res.end();
|
||||
@ -486,7 +479,7 @@ app.post(`/api/:node(${nodeRegexP})/:type(${typeRegexP})/:vmid(${vmidRegexP})/di
|
||||
return;
|
||||
}
|
||||
// target disk must be allowed according to storage options
|
||||
let resourceConfig = db.getResourceConfig();
|
||||
let resourceConfig = db.getGlobalConfig().resources;
|
||||
if (!resourceConfig[params.storage].disks.some(diskPrefix => params.disk.startsWith(diskPrefix))) {
|
||||
res.status(500).send({ error: `Requested target ${params.disk} is not in allowed list [${resourceConfig[params.storage].disks}].` });
|
||||
res.end();
|
||||
@ -811,7 +804,7 @@ app.post(`/api/:node(${nodeRegexP})/:type(${typeRegexP})/:vmid(${vmidRegexP})/pc
|
||||
action[`hostpci${params.hostpci}`] = `${params.device},pcie=${params.pcie}`;
|
||||
action = JSON.stringify(action);
|
||||
// commit action
|
||||
let rootauth = await requestPVE("/access/ticket", "POST", null, JSON.stringify(db.getApplicationConfig().pveroot), null);
|
||||
let rootauth = await requestPVE("/access/ticket", "POST", null, JSON.stringify(db.getGlobalConfig().application.pveroot), null);
|
||||
if (!(rootauth.status === 200)) {
|
||||
res.status(rootauth.status).send({ auth: false, error: "API could not authenticate as root user." });
|
||||
res.end();
|
||||
@ -888,7 +881,7 @@ app.post(`/api/:node(${nodeRegexP})/:type(${typeRegexP})/:vmid(${vmidRegexP})/pc
|
||||
action[`hostpci${hostpci}`] = `${params.device},pcie=${params.pcie}`;
|
||||
action = JSON.stringify(action);
|
||||
// commit action
|
||||
let rootauth = await requestPVE("/access/ticket", "POST", null, JSON.stringify(db.getApplicationConfig().pveroot), null);
|
||||
let rootauth = await requestPVE("/access/ticket", "POST", null, JSON.stringify(db.getGlobalConfig().application.pveroot), null);
|
||||
if (!(rootauth.status === 200)) {
|
||||
res.status(rootauth.status).send({ auth: false, error: "API could not authenticate as root user." });
|
||||
res.end();
|
||||
@ -942,7 +935,7 @@ app.delete(`/api/:node(${nodeRegexP})/:type(${typeRegexP})/:vmid(${vmidRegexP})/
|
||||
// setup action
|
||||
let action = JSON.stringify({ delete: `hostpci${params.hostpci}` });
|
||||
// commit action, need to use root user here because proxmox api only allows root to modify hostpci for whatever reason
|
||||
let rootauth = await requestPVE("/access/ticket", "POST", null, JSON.stringify(db.getApplicationConfig().pveroot), null);
|
||||
let rootauth = await requestPVE("/access/ticket", "POST", null, JSON.stringify(db.getGlobalConfig().application.pveroot), null);
|
||||
if (!(rootauth.status === 200)) {
|
||||
res.status(response.status).send({ auth: false, error: "API could not authenticate as root user." });
|
||||
res.end();
|
||||
|
@ -28,7 +28,7 @@ export async function checkAuth (cookies, res, vmpath = null) {
|
||||
}
|
||||
|
||||
export async function getUserResources (req, username) {
|
||||
const dbResources = db.getResourceConfig();
|
||||
const dbResources = db.getGlobalConfig().resources;
|
||||
const used = await getUsedResources(req, dbResources);
|
||||
const max = db.getUserConfig(username).resources.max;
|
||||
const avail = {};
|
||||
|
Loading…
Reference in New Issue
Block a user