Skip to content

Commit

Permalink
Fix sealing secrets to edge clusters (#17)
Browse files Browse the repository at this point in the history
Rework the support for sealing secrets to edge clusters.
* The ConfigDB app containing the kubeseal public key has changed.
* Work around a bug in the git library when we change things too fast.
  • Loading branch information
amrc-benmorrow authored Feb 6, 2024
2 parents f5a104e + 25c8ebe commit e04e7dc
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 37 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1

ARG acs_build=ghcr.io/amrc-factoryplus/utilities-build:v1.3.1
ARG acs_run=ghcr.io/amrc-factoryplus/utilities-run:v1.3.1
ARG acs_build=ghcr.io/amrc-factoryplus/acs-base-js-build:v0.0.1
ARG acs_run=ghcr.io/amrc-factoryplus/acs-base-js-run:v0.0.1

FROM ${acs_build} AS build
ARG acs_npm=NO
Expand Down
5 changes: 5 additions & 0 deletions dumps/clusters-auth.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
"permission": "4a339562-cd57-408d-9d1a-6529a383ea4b",
"target": "f6c67e6f-e48e-4f69-b4bb-bfbddcc2a517"
},
{
"principal": "26d192cf-73c1-4c14-93cf-1e63743bab08",
"permission": "4a339562-cd57-408d-9d1a-6529a383ea4b",
"target": "747a62c9-1b66-4a2e-8dd9-0b70a91b6b75"
},
{
"principal": "26d192cf-73c1-4c14-93cf-1e63743bab08",
"permission": "4a339562-cd57-408d-9d1a-6529a383ea4b",
Expand Down
3 changes: 3 additions & 0 deletions dumps/clusters-auth.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ aces:
- principal: !u E.Requirement.ServiceAccount
permission: !u FP.Permission.ConfigDB.ReadConfig
target: !u E.App.Status
- principal: !u E.Requirement.ServiceAccount
permission: !u FP.Permission.ConfigDB.ReadConfig
target: !u E.App.EdgeStatus
- principal: !u E.Requirement.ServiceAccount
permission: !u FP.Permission.ConfigDB.ReadConfig
target: !u E.App.Flux
Expand Down
2 changes: 1 addition & 1 deletion lib/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class App {
version: "0.0.1",
software: {
vendor: "AMRC",
application: "acs-edge-deployment",
application: "acs-cluster-manager",
revision: GIT_VERSION,
},
},
Expand Down
31 changes: 21 additions & 10 deletions lib/checkout.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,28 @@ export class Checkout {

async write_file (file, content) {
const abs = path.resolve(this.dir, file);

/* Unlink the filehandle before we write, but hold it open to
* ensure the inode is not reused. This avoids an iso-git bug
* where it incorrectly decides a file hasn't changed because
* the lstat result hasn't changed. (It only looks at timestamps
* to the nearest second, which is fairly silly.) */
const fh = await fs_p.open(abs).catch(e => null);
if (fh) await fs_p.unlink(abs).catch(e => null);

await fs_p.mkdir(path.dirname(abs), { recursive: true });
await fs_p.writeFile(abs, content);

await fh?.close().catch(e => null);
this.log("Written file %s", abs);
}

async _handle_enoent (file, cb) {
const abs = path.resolve(this.dir, file);
path_for (...args) {
return path.resolve(this.dir, ...args);
}

async _handle_enoent (cb, args) {
const abs = this.path_for(...args);
try {
return await cb(abs);
}
Expand All @@ -173,16 +188,12 @@ export class Checkout {
}
}

read_file (file) {
return this._handle_enoent(file, f => fs_p.readFile(f, "utf-8"));
read_file (...args) {
return this._handle_enoent(f => fs_p.readFile(f, "utf-8"), args);
}

unlink_file (file) {
return this._handle_enoent(file, f => fs_p.unlink(f) ?? true);
}

path_for (...args) {
return path.join(this.dir, ...args);
unlink_file (...args) {
return this._handle_enoent(f => fs_p.unlink(f) ?? true, args);
}

async mkdir (...args) {
Expand Down
2 changes: 1 addition & 1 deletion lib/kubeseal.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function write_sealed_secret (opts) {
namespace, name, key);
}

function run_kubeseal (opts) {
export function run_kubeseal (opts) {
return tmpfile.withFile(async ({ path: cert }) => {
await fs_p.writeFile(cert, opts.x509);

Expand Down
55 changes: 34 additions & 21 deletions lib/secrets.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import { Debug } from "@amrc-factoryplus/utilities";

import { Checkout } from "./checkout.js";
import { write_sealed_secret } from "./kubeseal.js";
import { run_kubeseal } from "./kubeseal.js";
import * as manifests from "./manifests.js";
import { Edge } from "./uuids.js";

const debug = new Debug();
Expand All @@ -23,50 +24,62 @@ export class SealedSecrets {
}

async seal_secret (opts) {
return 500;

const info = await this.fplus.ConfigDB
.get_config(Edge.App.Cluster, opts.cluster);
.get_config(Edge.App.EdgeStatus, opts.cluster);
if (!info) return 404;

const x509 = info.kubeseal_cert;
if (!x509) return 503;

const co = await Checkout.clone({ fplus: this.fplus, url: info.flux });
await write_sealed_secret({
...opts, x509,
const sealed = await run_kubeseal({
...opts, x509,
tmpdir: this.cert_dir,
checkout: co,
log: this.log,
});

const co = await Checkout.clone({
fplus: this.fplus,
uuid: opts.cluster,
});
const dir = await co.mkdir("edo", "secrets", opts.namespace);
const file = `${opts.name}.yaml`;
const sss = await co.read_manifests(dir, file);
if (sss.length == 0)
sss[0] = manifests.sealed_secret(opts.namespace, opts.name);
sss[0].spec.encryptedData[opts.key] = sealed;
await co.write_manifests(dir, file, sss);
await co.commit("Updated sealed secret %s/%s/%s",
opts.namespace, opts.name, opts.key);

await (opts.dryrun ? co.dispose() : co.push());

return 204;
}

async delete_secret (opts) {
return 500;

const info = await this.fplus.ConfigDB
.get_config(Edge.App.Cluster, opts.cluster);
.get_config(Edge.App.EdgeStatus, opts.cluster);
if (!info) return 404;

const { namespace, name, key } = opts;
const mf = [namespace, "SealedSecret", name];
const co = await Checkout.clone({ fplus: this.fplus, url: info.flux });
const obj = await co.read_manifest(...mf);
if (obj) {
const enc = obj.spec.encryptedData;
delete enc[key];
const co = await Checkout.clone({
fplus: this.fplus,
uuid: opts.cluster,
});
const dir = await co.mkdir("edo", "secrets", opts.namespace);
const file = `${opts.name}.yaml`;
const sss = await co.read_manifests(dir, file);
if (sss.length) {
const enc = sss[0].spec.encryptedData;
delete enc[opts.key];
if (Object.keys(enc).length == 0)
await co.unlink_manifest(...mf);
await co.unlink_file(dir, file);
else
await co.write_manifest(obj);
await co.write_manifests(dir, file, sss);
if (opts.dryrun)
await co.dispose();
else
await co.push("Remove sealed secret %s/%s/%s.",
namespace, name, key);
opts.namespace, opts.name, opts.key);
}

return 204;
Expand Down
1 change: 1 addition & 0 deletions lib/uuids.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const Edge = {
App: {
Cluster: "bdb13634-0b3d-4e38-a065-9d88c12ee78d",
Status: "f6c67e6f-e48e-4f69-b4bb-bfbddcc2a517",
EdgeStatus: "747a62c9-1b66-4a2e-8dd9-0b70a91b6b75",
Flux: "72804a19-636b-4836-b62b-7ad1476f2b86",
HelmChart: "729fe070-5e67-4bc7-94b5-afd75cb42b03",
HelmRelease: "88436128-09a3-4c9c-b7f4-b0e495137265",
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "acs-cluster-manager",
"version": "0.0.3",
"version": "0.0.4",
"description": "",
"main": "index.js",
"type": "module",
Expand All @@ -18,7 +18,7 @@
"@kubernetes/client-node": "^0.18.1",
"concat-stream": "^2.0.0",
"immutable": "^5.0.0-beta.4",
"isomorphic-git": "^1.24.0",
"isomorphic-git": "^1.25.3",
"json-merge-patch": "^1.0.2",
"json-templates": "^5.0.0",
"tmp-promise": "^3.0.3",
Expand Down

0 comments on commit e04e7dc

Please sign in to comment.