diff --git a/.gitignore b/.gitignore index 3f3673d..6bd7c37 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ # Environment .env +# Local config +config.mk + # Secrets /keytabs/ /volumes/ diff --git a/Dockerfile b/Dockerfile index ba0b6a3..f8721da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,27 @@ +# syntax=docker/dockerfile:1 + ARG utility_prefix=ghcr.io/amrc-factoryplus/utilities -ARG utility_ver=v1.0.6 +ARG utility_ver=v1.0.8 FROM ${utility_prefix}-build:${utility_ver} AS build # Install the node application on the build container where we can # compile the native modules. -RUN install -d -o node -g node /home/node/app +USER root +RUN <<'SHELL' + install -d -o node -g node /home/node/app + apk add git +SHELL WORKDIR /home/node/app USER node COPY package*.json ./ RUN npm install --save=false -COPY . . +COPY --chown=node . . +RUN <<'SHELL' + git describe --tags --dirty \ + | sed -re's/-[0-9]+-/-/;s/(.*)/export const GIT_VERSION="\1";/' \ + > lib/git-version.js +SHELL FROM ${utility_prefix}-run:${utility_ver} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d413fbd --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +# On Windows try https://frippery.org/busybox/ + +-include config.mk + +pkgver!=node -e 'console.log(JSON.parse(fs.readFileSync("package.json")).version)' + +version?=${pkgver} +suffix?= +registry?=ghcr.io/amrc-factoryplus +repo?=acs-auth + +tag=${registry}/${repo}:${version}${suffix} + +all: build push + +.PHONY: all build push check-committed + +check-committed: + [ -z "$$(git status --porcelain)" ] || (git status; exit 1) + +build: check-committed + docker build -t "${tag}" . + +push: + docker push "${tag}" + +ifdef deployment + +.PHONY: deploy restart logs + +kdeploy= deploy/"${deployment}" + +deploy: all restart logs + +restart: + kubectl rollout restart ${kdeploy} + kubectl rollout status ${kdeploy} + sleep 2 + +logs: + kubectl logs -f ${kdeploy} + +else + +deploy: + : Set $${deployment} for automatic k8s deployment + +endif diff --git a/bin/authn.js b/bin/authn.js index f46715d..9ca84ab 100644 --- a/bin/authn.js +++ b/bin/authn.js @@ -6,13 +6,16 @@ * Copyright 2022 AMRC */ -import { WebAPI, pkgVersion } from "@amrc-factoryplus/utilities"; +import { WebAPI, UUIDs } from "@amrc-factoryplus/utilities"; import AuthN from "../lib/authn.js"; import AuthZ from "../lib/authz.js"; import Editor from "../lib/editor.js"; -const Version = pkgVersion(import.meta); +import { GIT_VERSION } from "../lib/git-version.js"; + +/* This is the F+ service spec version */ +const Version = "1.0.0"; const authn = await new AuthN({ }).init(); const authz = await new AuthZ({ @@ -31,6 +34,12 @@ const editor = await new Editor({ const api = await new WebAPI({ ping: { version: Version, + service: UUIDs.Service.Authentication, + software: { + vendor: "AMRC", + application: "acs-auth", + revision: GIT_VERSION, + }, }, realm: process.env.REALM, hostname: process.env.HOSTNAME, diff --git a/editor/editor.js b/editor/editor.js index d5f8fea..c650b0b 100644 --- a/editor/editor.js +++ b/editor/editor.js @@ -14,6 +14,7 @@ const html = htm.bind(h); const Uuid = { General_Info: "64a8bfa9-7772-45c4-9d1a-9e6290690957", + Registration: "cb40bed5-49ad-4443-a7f5-08c75009da8f", }; let Services; @@ -83,9 +84,20 @@ async function fetch_json (service, path, method="GET", body=null) { return json; } -async function get_name (obj) { +async function _get_name (obj) { const gi = await fetch_json("configdb", `v1/app/${Uuid.General_Info}/object/${obj}`); - return gi ? gi.name : html`NO NAME`; + return gi + ? gi.deleted + ? html`${gi.name}` + : gi.name + : html`NO NAME`; +} + +async function get_name (obj) { + const reg = await fetch_json("configdb", `v1/app/${Uuid.Registration}/object/${obj}`); + const name = await _get_name(obj); + const klass = reg ? await _get_name(reg.class) : html`NO CLASS`; + return html`${name} (${klass})`; } function sort_acl (list) { diff --git a/lib/authz.js b/lib/authz.js index 00117a0..77bd01d 100644 --- a/lib/authz.js +++ b/lib/authz.js @@ -155,14 +155,17 @@ export default class AuthZ { if (!valid_uuid(uuid)) return res.status(400).end(); - const ok = await this.model.check_acl( - req.auth, Perm.Manage_Krb, uuid, true); + const ids = await this.model.principal_get(uuid); + + /* We can return 403 here as long as we don't return 404 until + * we've checked the permissions. */ + const ok = req.auth == ids.kerberos + || await this.model.check_acl(req.auth, Perm.Read_Krb, uuid, true); if (!ok) return res.status(403).end(); - const krb = await this.model.principal_get(uuid); - if (krb == null) + if (ids == null) return res.status(404).end(); - return res.status(200).json(krb); + return res.status(200).json(ids); } async principal_delete(req, res) { @@ -181,22 +184,22 @@ export default class AuthZ { /* This endpoint may change in future to allow searching for * principals by other criteria, e.g. Node address. */ async principal_find(req, res) { - const {kerberos} = req.query; + const kerberos = req.query?.kerberos ?? req.auth; if (!valid_krb(kerberos)) return res.status(400).end(); - /* XXX I'm not sure this is right: this means any client that - * wants to resolve Kerberos principals needs to be able to read - * them all. I would like to check ACLs against the resolved - * UUID, but then we have the problem of when to return 404, - * when 403, etc. */ - const ok = await this.model.check_acl( - req.auth, Perm.Read_Krb, UUIDs.Null, false); - if (!ok) return res.status(403).end(); - const uuid = await this.model.principal_find_by_krb(kerberos); if (uuid == null) return res.status(404).end(); + + /* We have to check permissions against the returned UUID. This + * means that we must return 404 instead of 403 if the check + * fails. Principals are always allowed to look up their own + * information. */ + const ok = req.auth == kerberos + || await this.model.check_acl(req.auth, Perm.Read_Krb, uuid, true) + if (!ok) return res.status(404).end(); + return res.status(200).json(uuid); } diff --git a/package.json b/package.json index 89c07e8..1b6e02e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "acs-auth", - "version": "1.0.0", + "version": "1.0.1", "description": "The Authorisation component of the AMRC Connectivity Stack", "type": "module", "main": "index.js", @@ -21,10 +21,10 @@ "author": "AMRC", "license": "MIT", "dependencies": { - "@amrc-factoryplus/utilities": "1.0.6", + "@amrc-factoryplus/utilities": "^1.0.8", "express": "^4.18.1", "express-basic-auth": "^1.2.1", "http-errors": "^2.0.0", "timers-promises": "^1.0.1" } -} \ No newline at end of file +}