Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c9fd187
Start building dumps for the new auth setup
amrc-benmorrow Dec 10, 2024
8d6705a
Remove _Principal group_ from the main dump
amrc-benmorrow Dec 10, 2024
bef6a09
Start working on ConfigDB dump format version 2
amrc-benmorrow Dec 10, 2024
903cf8f
Convert dump validation to a JSON schema
amrc-benmorrow Dec 13, 2024
7ef9118
Start reworking the v2 dump format
amrc-benmorrow Dec 13, 2024
80922e2
Load dumps via the new endpoint
amrc-benmorrow Dec 13, 2024
d809bbb
Promote _Permission group_ to a rank 2 class
amrc-benmorrow Dec 13, 2024
0589836
Don't create _Permission group_ in the main dump
amrc-benmorrow Dec 13, 2024
46b0de5
Move over to `service-client`
amrc-benmorrow Dec 13, 2024
144d93b
Check ConfigDB dumps against the schema
amrc-benmorrow Dec 13, 2024
0a999ad
Fix dump schema problems
amrc-benmorrow Dec 13, 2024
84a7d73
Update eslint
amrc-benmorrow Dec 13, 2024
8366130
Move the Git Auth entries back to dumps.yaml
amrc-benmorrow Dec 16, 2024
e244bc4
Remove Helm annotation on service-setup Job
amrc-benmorrow Dec 16, 2024
629c8b9
Set service-setup backoff limit very high
amrc-benmorrow Dec 16, 2024
9597282
Notes on the translation from groups to classes
amrc-benmorrow Dec 18, 2024
d826e0a
Try using symbols to indicate subclass vs member
amrc-benmorrow Dec 18, 2024
19608a1
Maintain rank invariants
amrc-benmorrow Dec 18, 2024
acc31c7
Update GROUPS based on ConfigDB structure
amrc-benmorrow Dec 19, 2024
682f3eb
Start working on a full dump for the auth classes
amrc-benmorrow Dec 19, 2024
3e4e9f0
Database functions to load object from a dump
amrc-benmorrow Dec 19, 2024
79180e7
Allow primary membership to be indirect
amrc-benmorrow Jan 6, 2025
62e2d16
Load v2 dumps via SQL function
amrc-benmorrow Jan 7, 2025
0bf7c5a
Implement the new auth class structure
amrc-benmorrow Jan 8, 2025
b277fd8
Sort dumps using REQUIRE comments
amrc-benmorrow Jan 8, 2025
107b124
Handle existing objects when we re-rank
amrc-benmorrow Jan 8, 2025
c648e63
Refactor load_dumps SQL function
amrc-benmorrow Jan 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions acs-auth/GROUPS
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Comment
* New object
% Dynamic (per-install) object
[] Renamed object
() Primary class

R1Class [Class definition]
# This is now effectively ₡Principal.
⊃ *Principal group
⊃ *Principal type
∋ Human user
∋ Central service
∋ Edge service
∋ Edge Agent
∋ Application account
∋ Special account
⊃ Role [User group]
⊃ Service role [Service requirement]
∋ Central monitor
∋ Cluster manager
∋ Git server
∋ UNS Sparkplug ingester
⊃ *Edge role
⊃ Clusters-managed group
∋ %Edge flux
∋ %Edge krbkeys
⊃ Edge-managed group
∋ %Active Edge Agent
∋ %Edge monitor
∋ %Edge sync
⊃ *Client role
∋ Administrator
∋ Sparkplug Node
∋ Sparkplug global reader
∋ UNS global reader
# Ditto, this is now ₡Permission.
⊃ Permission group
⊃ *Service permission set
∋ Auth
∋ MQTT
∋ ⋮
⊃ Composite permission [Client role]
∋ Edge Node consumer
∋ Edge monitor for agent
∋ Warehouse
∋ Edge Node
∋ Global Primary Application
∋ Command Escalation service

Individual
⊃ *Principal
⊃ Human user [User account]
⊃ Central service [Service account]
⊃ Cluster manager
∋ %sv1clustermanager
⊃ UNS Sparkplug ingester
∋ %sv1sparkplugingester
⊃ Central monitor
∋ %sv1monitor
⊃ Git servers
∋ %sv1git
⊃ UNS historian
∋ %sv1historianuns
∋ sv1auth
∋ sv1cmdesc
∋ sv1configdb
∋ ⋮
⊃ Edge service [Edge cluster account]
⊃ %Edge flux
⊃ %Edge krbkeys
⊃ %Edge sync
⊃ %Edge monitor
⊃ Edge Agent [Cell Gateway] (Principal type)
⊃ %Edge Agent (Edge role)
⊃ *Application account
⊃ *Special account
∋ %Admin account
∋ *%Root account
⊃ Sparkplug Node
⊃ Edge Agent
⊃ Edge monitor
∋ sv1cmdesc
∋ sv1configdb
∋ sv1directory
∋ sv1git
⊃ Sparkplug global reader [MQTT global debugger]
⊃ Administrator
∋ Admin account
⊃ Central monitor
⊃ Edge monitor
⊃ UNS Sparkplug ingester
⊃ *UNS ingester
⊃ UNS Sparkplug ingester
⊃ *UNS global reader
⊃ Administrator
⊃ UNS historian
⊃ Permission
⊃ Auth
⊃ MQTT
⊃ ⋮
⊃ Edge Node consumer
∋ Subscribe and read Node
∋ Rebirth
⊃ Edge monitor for agent
∋ Read Node
∋ Reload Edge Agent config
∋ Rebirth
# These are not composite permissions
⊃ Warehouse
∋ Subscribe and read whole namespace
∋ Rebirth
⊃ Command Escalation service
∋ Issue global commands
∋ Subscribe and read whole namespace
⊃ Global debugger
∋ Subscribe and read whole namespace
# These are obsolete and unused
⊃ Edge Node
∋ Subscribe and read STATE
∋ Participate as Node
∋ Represent Devices
⊃ Global Primary Application
58 changes: 33 additions & 25 deletions acs-configdb/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ function patch_json (path, patch) {
}

async function post_json(path, json) {
const rsp = await service_fetch(`/v1/${path}`, {
const rsp = await service_fetch(`/${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Expand Down Expand Up @@ -338,7 +338,7 @@ function Apps(props) {
uuid: new_uuid.current.value,
name: new_name.current.value,
};
if (await post_json(`app`, body)) {
if (await post_json(`v1/app`, body)) {
set_msg("App created");
} else {
set_msg("Error");
Expand Down Expand Up @@ -383,6 +383,7 @@ function Objs(props) {
<${Ranks.Provider} value=${ranks}>
<${NewObj}/>
<h2>Ranks of object</h2>
<p><b>⊃</b> indicates a subclass, <b>∋</b> indicates a class member.<//>
<dl>${hranks}</dl>
<//>
`;
Expand Down Expand Up @@ -428,24 +429,29 @@ function Klass(props) {
["Set primary class", "PATCH", `${reg}/${m}`, { "class": klass }],
];

const notyet = html`<b>...</b><br/>`;
const notyet = html`<dt><b>...</b><//>`;
const hsubs = subs?.map(s =>
html`<${Obj} obj=${s} key=${s} menu=${s_menu(s)}/>`);
html`<${Obj} obj=${s} key=${s} menu=${s_menu(s)} pfx="⊃"/>`);
const hobjs = objs?.map(o =>
html`<${Obj} obj=${o} key=${o} menu=${m_menu(o)}/>`);
html`<${Obj} obj=${o} key=${o} menu=${m_menu(o)} pfx="∋"/>`);
const hstat = status && html`
<dt><p onClick=${() => set_status()}>${status}<//><//>`;

//<h3>Subclasses ${""}
//<h3>Members ${""}
return html`
${status && html`<p onClick=${() => set_status()}>${status}<//>`}
<h3>Subclasses ${""}
<input type=text size=40 ref=${new_s}/>
<button onClick=${add_rel("subclass", new_s)}>Add subclass<//>
<//>
<dl>${hsubs ?? notyet}<//>
<h3>Members ${""}
<input type=text size=40 ref=${new_m}/>
<button onClick=${add_rel("member", new_m)}>Add member<//>
<//>
<dl>${hobjs ?? notyet}<//>
<dl>
${hstat}
<dt>
<b>⊃</b> <input type=text size=40 ref=${new_s}/>
<button onClick=${add_rel("subclass", new_s)}>Add subclass<//>
<//><dt>
<b>∋</b> <input type=text size=40 ref=${new_m}/>
<button onClick=${add_rel("member", new_m)}>Add member<//>
<//>
${hsubs ?? notyet}
${hobjs ?? notyet}
</dl>
`;
}

Expand All @@ -459,13 +465,15 @@ function ObjFetchInfo (props) {
}

function ObjDisplay (props) {
const { menu } = props;
const { menu, pfx } = props;
const info = useContext(ObjInfo);

const obj = info.uuid;

const mbutt = menu ? html`<${ObjMenu} menu=${menu}/>` : "";
const title = html`${mbutt} <${ObjTitleCtx} with_class=${true}/>`
const title = html`
${mbutt} <b>${pfx}</b>
<${ObjTitleCtx} with_class=${true}/>`;

if (info?.reg?.rank == 0)
return html`<dt>${title}<//>`;
Expand All @@ -478,8 +486,11 @@ function ObjDisplay (props) {
}

function Obj (props) {
const {obj, menu} = props;
return html`<${ObjFetchInfo} obj=${obj}><${ObjDisplay} menu=${menu}/><//>`;
const {obj, menu, pfx} = props;
return html`
<${ObjFetchInfo} obj=${obj}>
<${ObjDisplay} menu=${menu} pfx=${pfx}/>
<//>`;
}

function NewObj(props) {
Expand All @@ -494,7 +505,7 @@ function NewObj(props) {
uuid: new_obj.current.value || undefined,
"class": new_class.current.value,
};
const rsp = await post_json("object", spec);
const rsp = await post_json("v2/object", spec);

if (rsp) {
set_msg(html`Created
Expand Down Expand Up @@ -639,17 +650,15 @@ function Dumps(props) {

const dump_r = useRef(null);
const file_r = useRef(null);
const ovrw_r = useRef(null);

const load = async () => {
const dump = JSON.parse(dump_r.current?.value);
if (dump == null) {
set_msg("Error reading dump from textbox");
return;
}
const ovrw = !!ovrw_r.current?.checked;

const ok = await post_json(`load?overwrite=${ovrw}`, dump);
const ok = await post_json(`load`, dump);
set_msg(ok ? "Loaded dump" : "Failed");
if (ok) dump_r.current.value = "";
};
Expand All @@ -670,7 +679,6 @@ function Dumps(props) {
</p>
<p><textarea cols=80 rows=24 ref=${dump_r}></textarea></p>
<p><input type=file ref=${file_r} onChange=${read_file}/></p>
<p><label><input type=checkbox ref=${ovrw_r}/> Overwrite existing entries</label></p>
<p>
<button onClick=${load}>Load JSON dump</button>
${msg}
Expand Down
47 changes: 45 additions & 2 deletions acs-configdb/lib/api-v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ export class APIv1 {

["/object", "/v2/object"],
["/object/:object", "/v2/object/:object"],

["/load", "/load"],
];

for (const [source, dest] of forwards) {
Expand All @@ -86,6 +84,7 @@ export class APIv1 {
api.get("/app/:app/search", this.config_search.bind(this));
api.get("/app/:app/class/:class/search", this.config_search.bind(this));

api.post("/load", this.dump_load.bind(this));
api.get("/save", this.dump_save.bind(this));
}

Expand Down Expand Up @@ -171,6 +170,50 @@ export class APIv1 {
return res.status(200).json(json);
}

/* This is different from /load in that it accepts an overwrite
* query parameter. */
async dump_load(req, res) {
const dump = req.body;
const booleans = {
"true": true, "false": false,
"1": true, "0": false,
on: true, off: false,
yes: true, no: false,
};

const overwrite = booleans[req.query.overwrite];
if (overwrite == undefined)
return res.status(400).end();

if (!this.model.dump_validate(dump)) {
this.log("Dump failed to validate: %o", this.model.dump_validate.errors);
return res.status(400).end();
}
if (dump.version != 1) {
this.log("/v1/load can only load version 1 dumps");
return res.status(400).end();
}

const perms = {
classes: Perm.Manage_Obj,
objects: Perm.Manage_Obj,
configs: Perm.Write_App,
};
for (const [key, perm] of Object.entries(perms)) {
if (key in dump) {
const ok = await this.auth.check_acl(
req.auth, perm, UUIDs.Null, false);
if (!ok) {
this.log("Refusing dump (%s)", key);
return res.status(403).end();
}
}
}

const st = await this.model.dump_load(dump, !!overwrite);
res.status(st).end();
}

async dump_save(req, res) {
const ok = await this.auth.check_acl(req.auth, Perm.Manage_Obj, UUIDs.Null)
&& await this.auth.check_acl(req.auth, Perm.Read_App, UUIDs.Null);
Expand Down
Loading