@codemowers/oidc-key-manager 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -45
- package/dist/commands/initialize.d.ts +1 -1
- package/dist/commands/initialize.js +3 -2
- package/dist/commands/rotate.d.ts +19 -0
- package/dist/commands/rotate.js +57 -0
- package/dist/helpers/common-flags.d.ts +0 -2
- package/dist/helpers/common-flags.js +0 -1
- package/dist/helpers/kube-api-service.d.ts +5 -1
- package/dist/helpers/kube-api-service.js +52 -7
- package/dist/helpers/secret.d.ts +8 -1
- package/dist/helpers/secret.js +36 -3
- package/oclif.manifest.json +100 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,9 +7,6 @@ CLI to manage secret keys required by oidc-gateway
|
|
|
7
7
|
* [Usage](#usage)
|
|
8
8
|
* [Commands](#commands)
|
|
9
9
|
<!-- tocstop -->
|
|
10
|
-
* [Usage](#usage)
|
|
11
|
-
* [Commands](#commands)
|
|
12
|
-
<!-- tocstop -->
|
|
13
10
|
# Usage
|
|
14
11
|
<!-- usage -->
|
|
15
12
|
```sh-session
|
|
@@ -17,28 +14,17 @@ $ npm install -g @codemowers/oidc-key-manager
|
|
|
17
14
|
$ key-manager COMMAND
|
|
18
15
|
running command...
|
|
19
16
|
$ key-manager (--version)
|
|
20
|
-
@codemowers/oidc-key-manager/0.
|
|
17
|
+
@codemowers/oidc-key-manager/0.2.0 linux-x64 node-v16.17.0
|
|
21
18
|
$ key-manager --help [COMMAND]
|
|
22
19
|
USAGE
|
|
23
20
|
$ key-manager COMMAND
|
|
24
21
|
...
|
|
25
22
|
```
|
|
26
23
|
<!-- usagestop -->
|
|
27
|
-
```sh-session
|
|
28
|
-
$ npm install -g oidc-key-manager
|
|
29
|
-
$ key-manager COMMAND
|
|
30
|
-
running command...
|
|
31
|
-
$ key-manager (--version)
|
|
32
|
-
oidc-key-manager/0.0.0 linux-x64 node-v18.15.0
|
|
33
|
-
$ key-manager --help [COMMAND]
|
|
34
|
-
USAGE
|
|
35
|
-
$ key-manager COMMAND
|
|
36
|
-
...
|
|
37
|
-
```
|
|
38
|
-
<!-- usagestop -->
|
|
39
24
|
# Commands
|
|
40
25
|
<!-- commands -->
|
|
41
26
|
* [`key-manager initialize`](#key-manager-initialize)
|
|
27
|
+
* [`key-manager rotate`](#key-manager-rotate)
|
|
42
28
|
|
|
43
29
|
## `key-manager initialize`
|
|
44
30
|
|
|
@@ -71,33 +57,37 @@ EXAMPLES
|
|
|
71
57
|
$ key-manager initialize --namespace <kube namespace> --secret <secret name> --recreate
|
|
72
58
|
```
|
|
73
59
|
|
|
74
|
-
_See code: [dist/commands/initialize.ts](https://github.com/codemowers/oidc-key-manager/blob/v0.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
FLAGS
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
--
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
60
|
+
_See code: [dist/commands/initialize.ts](https://github.com/codemowers/oidc-key-manager/blob/v0.2.0/dist/commands/initialize.ts)_
|
|
61
|
+
|
|
62
|
+
## `key-manager rotate`
|
|
63
|
+
|
|
64
|
+
Append new JWK|cookie key|both and rotate the array, optionally restarting the deployment
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
USAGE
|
|
68
|
+
$ key-manager rotate -c local|cluster [-n <value>] [-s <value>] [--both] [--jwks] [--cookie-keys]
|
|
69
|
+
[--max-number-of-jwks <value>] [--max-number-of-cookie-keys <value>] [--restart-deployment-backoff <value>
|
|
70
|
+
--restart-deployment <value>]
|
|
71
|
+
|
|
72
|
+
FLAGS
|
|
73
|
+
-c, --config=<option> (required) use local or in-cluster Kubernetes config
|
|
74
|
+
<options: local|cluster>
|
|
75
|
+
-n, --namespace=<value> namespace, defaults to current namespace if service account is used
|
|
76
|
+
-s, --secret=<value> [default: oidc-keys] secret name
|
|
77
|
+
--both rotate both JWKs and cookie keys
|
|
78
|
+
--cookie-keys rotate cookie keys
|
|
79
|
+
--jwks rotate JWKs
|
|
80
|
+
--max-number-of-cookie-keys=<value> [default: 3]
|
|
81
|
+
--max-number-of-jwks=<value> [default: 3]
|
|
82
|
+
--restart-deployment=<value> Kubernetes deployment name to restart while rotating
|
|
83
|
+
--restart-deployment-backoff=<value> [default: 60] Seconds to wait for deployment to restart
|
|
84
|
+
|
|
85
|
+
DESCRIPTION
|
|
86
|
+
Append new JWK|cookie key|both and rotate the array, optionally restarting the deployment
|
|
87
|
+
|
|
88
|
+
EXAMPLES
|
|
89
|
+
$ key-manager rotate
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
_See code: [dist/commands/rotate.ts](https://github.com/codemowers/oidc-key-manager/blob/v0.2.0/dist/commands/rotate.ts)_
|
|
103
93
|
<!-- commandsstop -->
|
|
@@ -4,10 +4,10 @@ export default class Initialize extends Command {
|
|
|
4
4
|
static enableJsonFlag: boolean;
|
|
5
5
|
static examples: string[];
|
|
6
6
|
static flags: {
|
|
7
|
+
recreate: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
8
|
namespace: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
8
9
|
secret: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
9
10
|
config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
|
-
recreate: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
};
|
|
12
12
|
static args: {};
|
|
13
13
|
run(): Promise<void>;
|
|
@@ -10,14 +10,14 @@ class Initialize extends core_1.Command {
|
|
|
10
10
|
const { flags } = await this.parse(Initialize);
|
|
11
11
|
const kubeApiService = new kube_api_service_1.KubeApiService(this, flags);
|
|
12
12
|
kubeApiService.printConfiguration();
|
|
13
|
-
const exists = await kubeApiService.
|
|
13
|
+
const exists = await kubeApiService.getSecret();
|
|
14
14
|
if (exists && !flags.recreate) {
|
|
15
15
|
this.exit(0);
|
|
16
16
|
}
|
|
17
17
|
if (exists) {
|
|
18
18
|
await kubeApiService.deleteSecret();
|
|
19
19
|
}
|
|
20
|
-
const secret = new secret_1.Secret();
|
|
20
|
+
const secret = new secret_1.Secret(this);
|
|
21
21
|
this.log('Generating secret');
|
|
22
22
|
secret.generateNew();
|
|
23
23
|
await kubeApiService.createSecret(secret);
|
|
@@ -34,5 +34,6 @@ Initialize.examples = [
|
|
|
34
34
|
];
|
|
35
35
|
Initialize.flags = {
|
|
36
36
|
...common_flags_1.default,
|
|
37
|
+
recreate: core_1.Flags.boolean({ description: 'recreate the secret if it exists', aliases: ['recreate'], required: false }),
|
|
37
38
|
};
|
|
38
39
|
Initialize.args = {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Rotate extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
both: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
jwks: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
'cookie-keys': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
'max-number-of-jwks': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
|
+
'max-number-of-cookie-keys': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
|
+
'restart-deployment': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
12
|
+
'restart-deployment-backoff': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
13
|
+
namespace: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
14
|
+
secret: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
15
|
+
config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
static args: {};
|
|
18
|
+
run(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const common_flags_1 = tslib_1.__importDefault(require("../helpers/common-flags"));
|
|
6
|
+
const kube_api_service_1 = require("../helpers/kube-api-service");
|
|
7
|
+
const secret_1 = require("../helpers/secret");
|
|
8
|
+
class Rotate extends core_1.Command {
|
|
9
|
+
async run() {
|
|
10
|
+
const { flags } = await this.parse(Rotate);
|
|
11
|
+
const kubeApiService = new kube_api_service_1.KubeApiService(this, flags);
|
|
12
|
+
kubeApiService.printConfiguration();
|
|
13
|
+
const kubeSecret = await kubeApiService.getSecret();
|
|
14
|
+
if (!kubeSecret) {
|
|
15
|
+
this.error('Secret does not exist');
|
|
16
|
+
this.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const secret = new secret_1.Secret(this);
|
|
19
|
+
secret.fromKubeSecret(kubeSecret);
|
|
20
|
+
if (flags.both || flags.jwks) {
|
|
21
|
+
secret.appendJWK(flags['max-number-of-jwks']);
|
|
22
|
+
}
|
|
23
|
+
if (flags.both || flags['cookie-keys']) {
|
|
24
|
+
secret.appendCookieKey(flags['max-number-of-cookie-keys']);
|
|
25
|
+
}
|
|
26
|
+
await kubeApiService.replaceSecret(secret);
|
|
27
|
+
if (flags['restart-deployment']) {
|
|
28
|
+
await kubeApiService.restartDeployment(flags['restart-deployment'], flags['restart-deployment-backoff']);
|
|
29
|
+
}
|
|
30
|
+
if (flags.both || flags.jwks) {
|
|
31
|
+
secret.rotateJWKs();
|
|
32
|
+
}
|
|
33
|
+
if (flags.both || flags.jwks) {
|
|
34
|
+
secret.rotateCookieKeys();
|
|
35
|
+
}
|
|
36
|
+
await kubeApiService.replaceSecret(secret);
|
|
37
|
+
if (flags['restart-deployment']) {
|
|
38
|
+
await kubeApiService.restartDeployment(flags['restart-deployment'], flags['restart-deployment-backoff']);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.default = Rotate;
|
|
43
|
+
Rotate.description = 'Append new JWK|cookie key|both and rotate the array, optionally restarting the deployment';
|
|
44
|
+
Rotate.examples = [
|
|
45
|
+
'<%= config.bin %> <%= command.id %>',
|
|
46
|
+
];
|
|
47
|
+
Rotate.flags = {
|
|
48
|
+
...common_flags_1.default,
|
|
49
|
+
both: core_1.Flags.boolean({ description: 'rotate both JWKs and cookie keys', exactlyOne: ['both', 'jwks', 'cookie-keys'] }),
|
|
50
|
+
jwks: core_1.Flags.boolean({ description: 'rotate JWKs' }),
|
|
51
|
+
'cookie-keys': core_1.Flags.boolean({ description: 'rotate cookie keys' }),
|
|
52
|
+
'max-number-of-jwks': core_1.Flags.integer({ default: 3 }),
|
|
53
|
+
'max-number-of-cookie-keys': core_1.Flags.integer({ default: 3 }),
|
|
54
|
+
'restart-deployment': core_1.Flags.string({ description: 'Kubernetes deployment name to restart while rotating' }),
|
|
55
|
+
'restart-deployment-backoff': core_1.Flags.integer({ description: 'Seconds to wait for deployment to restart', default: 60, dependsOn: ['restart-deployment'] }),
|
|
56
|
+
};
|
|
57
|
+
Rotate.args = {};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export interface CommonFlagsInterface {
|
|
2
2
|
namespace: string | undefined;
|
|
3
3
|
secret: string;
|
|
4
|
-
recreate: boolean;
|
|
5
4
|
config: string;
|
|
6
5
|
}
|
|
7
6
|
export declare enum ConfigType {
|
|
@@ -12,6 +11,5 @@ declare const _default: {
|
|
|
12
11
|
namespace: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
13
12
|
secret: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
14
13
|
config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
15
|
-
recreate: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
16
14
|
};
|
|
17
15
|
export default _default;
|
|
@@ -11,5 +11,4 @@ exports.default = {
|
|
|
11
11
|
namespace: core_1.Flags.string({ char: 'n', description: 'namespace, defaults to current namespace if service account is used', aliases: ['namespace'], required: false }),
|
|
12
12
|
secret: core_1.Flags.string({ char: 's', description: 'secret name', aliases: ['secret'], default: 'oidc-keys', required: false }),
|
|
13
13
|
config: core_1.Flags.string({ char: 'c', description: 'use local or in-cluster Kubernetes config', aliases: ['config'], required: true, options: [ConfigType.Local, ConfigType.InCluster] }),
|
|
14
|
-
recreate: core_1.Flags.boolean({ description: 'recreate the secret if it exists', aliases: ['recreate'], required: false }),
|
|
15
14
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { V1Secret } from '@kubernetes/client-node';
|
|
1
2
|
import { CommonFlagsInterface } from './common-flags';
|
|
2
3
|
import { Command } from '@oclif/core';
|
|
3
4
|
import { Secret } from './secret';
|
|
@@ -5,12 +6,15 @@ export declare class KubeApiService {
|
|
|
5
6
|
#private;
|
|
6
7
|
private kc;
|
|
7
8
|
private coreV1Api;
|
|
9
|
+
private appsV1Api;
|
|
8
10
|
private namespace;
|
|
9
11
|
private command;
|
|
10
12
|
private secretName;
|
|
11
13
|
constructor(command: Command, flags: CommonFlagsInterface);
|
|
12
14
|
printConfiguration(): void;
|
|
13
|
-
|
|
15
|
+
restartDeployment(deploymentName: string, timeoutInSeconds: number): Promise<any>;
|
|
16
|
+
getSecret(): Promise<V1Secret | undefined | null>;
|
|
14
17
|
deleteSecret(): Promise<void>;
|
|
15
18
|
createSecret(secret: Secret): Promise<void>;
|
|
19
|
+
replaceSecret(secret: Secret): Promise<void>;
|
|
16
20
|
}
|
|
@@ -15,6 +15,7 @@ class KubeApiService {
|
|
|
15
15
|
flags.config === common_flags_1.ConfigType.InCluster ? this.kc.loadFromCluster() : this.kc.loadFromDefault();
|
|
16
16
|
this.namespace = (_c = (_a = flags.namespace) !== null && _a !== void 0 ? _a : (_b = this.kc.getContextObject(this.kc.getCurrentContext())) === null || _b === void 0 ? void 0 : _b.namespace) !== null && _c !== void 0 ? _c : Undefined;
|
|
17
17
|
this.coreV1Api = this.kc.makeApiClient(client_node_1.CoreV1Api);
|
|
18
|
+
this.appsV1Api = this.kc.makeApiClient(client_node_1.AppsV1Api);
|
|
18
19
|
this.secretName = flags.secret;
|
|
19
20
|
tslib_1.__classPrivateFieldGet(this, _KubeApiService_instances, "m", _KubeApiService_validate).call(this);
|
|
20
21
|
}
|
|
@@ -26,17 +27,57 @@ class KubeApiService {
|
|
|
26
27
|
secretName: this.secretName,
|
|
27
28
|
});
|
|
28
29
|
}
|
|
29
|
-
async
|
|
30
|
+
async restartDeployment(deploymentName, timeoutInSeconds) {
|
|
31
|
+
this.command.log(`Restarting deployment ${deploymentName}`);
|
|
32
|
+
await this.appsV1Api.patchNamespacedDeployment(deploymentName, this.namespace, {
|
|
33
|
+
spec: {
|
|
34
|
+
template: {
|
|
35
|
+
metadata: {
|
|
36
|
+
annotations: {
|
|
37
|
+
'kubectl.kubernetes.io/restartedAt': String(Date.now()),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}, undefined, undefined, undefined, undefined, undefined, { headers: { 'Content-type': client_node_1.PatchUtils.PATCH_FORMAT_JSON_MERGE_PATCH } });
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const timeout = setTimeout(() => {
|
|
45
|
+
informer.stop();
|
|
46
|
+
reject(new Error(`Failed to observe new ReplicaSet before ${timeoutInSeconds} seconds`));
|
|
47
|
+
}, timeoutInSeconds * 1000);
|
|
48
|
+
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
49
|
+
const listFn = () => this.appsV1Api.listNamespacedDeployment(this.namespace);
|
|
50
|
+
const informer = (0, client_node_1.makeInformer)(this.kc, `/apis/apps/v1/namespaces/${this.namespace}/deployments/`, listFn);
|
|
51
|
+
informer.on('update', (obj) => {
|
|
52
|
+
var _a;
|
|
53
|
+
const conditions = (_a = obj === null || obj === void 0 ? void 0 : obj.status) === null || _a === void 0 ? void 0 : _a.conditions;
|
|
54
|
+
if (conditions) {
|
|
55
|
+
const progressingCondition = conditions.find((c) => c.type === 'Progressing');
|
|
56
|
+
if ((progressingCondition === null || progressingCondition === void 0 ? void 0 : progressingCondition.reason) === 'NewReplicaSetAvailable') {
|
|
57
|
+
this.command.log('Deployment finished restarting');
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
informer.stop();
|
|
60
|
+
resolve(obj);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
informer.start();
|
|
65
|
+
this.command.log('Waiting for deployment to restart');
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async getSecret() {
|
|
30
69
|
this.command.log(`Checking if secret ${this.secretName} exists`);
|
|
31
|
-
const
|
|
32
|
-
.then(
|
|
70
|
+
const secret = await this.coreV1Api.readNamespacedSecret(this.secretName, this.namespace)
|
|
71
|
+
.then(response => response.body)
|
|
33
72
|
.catch(error => {
|
|
34
|
-
if (error.
|
|
35
|
-
|
|
73
|
+
if (error.statusCode !== 404) {
|
|
74
|
+
this.command.error(error);
|
|
75
|
+
this.command.exit(1);
|
|
36
76
|
}
|
|
77
|
+
return null;
|
|
37
78
|
});
|
|
38
|
-
this.command.log(
|
|
39
|
-
return
|
|
79
|
+
this.command.log(secret ? `Secret ${this.secretName} exists` : `Secret ${this.secretName} does not exist`);
|
|
80
|
+
return secret;
|
|
40
81
|
}
|
|
41
82
|
async deleteSecret() {
|
|
42
83
|
this.command.log(`Deleting existing secret ${this.secretName}`);
|
|
@@ -48,6 +89,10 @@ class KubeApiService {
|
|
|
48
89
|
await this.coreV1Api.createNamespacedSecret(this.namespace, secret.toKubeSecret(this.secretName));
|
|
49
90
|
this.command.log(`Created secret ${this.secretName}`);
|
|
50
91
|
}
|
|
92
|
+
async replaceSecret(secret) {
|
|
93
|
+
this.command.log(`Replacing secret ${this.secretName}`);
|
|
94
|
+
await this.coreV1Api.replaceNamespacedSecret(this.secretName, this.namespace, secret.toKubeSecret(this.secretName));
|
|
95
|
+
}
|
|
51
96
|
}
|
|
52
97
|
exports.KubeApiService = KubeApiService;
|
|
53
98
|
_KubeApiService_instances = new WeakSet(), _KubeApiService_validate = function _KubeApiService_validate() {
|
package/dist/helpers/secret.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { V1Secret } from '@kubernetes/client-node';
|
|
2
|
+
import { Command } from '@oclif/core';
|
|
2
3
|
export declare class Secret {
|
|
3
4
|
#private;
|
|
4
5
|
private JWKs;
|
|
5
6
|
private CookieKeys;
|
|
6
|
-
|
|
7
|
+
private command;
|
|
8
|
+
constructor(command: Command);
|
|
7
9
|
generateNew(): void;
|
|
8
10
|
toKubeSecret(secretName: string): V1Secret;
|
|
11
|
+
fromKubeSecret(kubeSecret: V1Secret): void;
|
|
12
|
+
appendJWK(maxNumber: number): void;
|
|
13
|
+
appendCookieKey(maxNumber: number): void;
|
|
14
|
+
rotateJWKs(): void;
|
|
15
|
+
rotateCookieKeys(): void;
|
|
9
16
|
}
|
package/dist/helpers/secret.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var _Secret_instances, _Secret_generateRSAJwk, _Secret_generateCookieKey, _Secret_arrayToB64String, _Secret_getKubeSecretMetadata;
|
|
2
|
+
var _Secret_instances, _Secret_append, _Secret_rotate, _Secret_generateRSAJwk, _Secret_generateCookieKey, _Secret_arrayToB64String, _Secret_getKubeSecretMetadata;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.Secret = void 0;
|
|
5
5
|
const tslib_1 = require("tslib");
|
|
@@ -9,10 +9,11 @@ const node_crypto_1 = tslib_1.__importDefault(require("node:crypto"));
|
|
|
9
9
|
const JWKSKeyName = 'OIDC_JWKS';
|
|
10
10
|
const CookieKeysKeyName = 'OIDC_COOKIE_KEYS';
|
|
11
11
|
class Secret {
|
|
12
|
-
constructor() {
|
|
12
|
+
constructor(command) {
|
|
13
13
|
_Secret_instances.add(this);
|
|
14
14
|
this.JWKs = [];
|
|
15
15
|
this.CookieKeys = [];
|
|
16
|
+
this.command = command;
|
|
16
17
|
}
|
|
17
18
|
generateNew() {
|
|
18
19
|
this.JWKs = [tslib_1.__classPrivateFieldGet(this, _Secret_instances, "m", _Secret_generateRSAJwk).call(this, 4096)];
|
|
@@ -26,9 +27,41 @@ class Secret {
|
|
|
26
27
|
secret.data[CookieKeysKeyName] = tslib_1.__classPrivateFieldGet(this, _Secret_instances, "m", _Secret_arrayToB64String).call(this, this.CookieKeys);
|
|
27
28
|
return secret;
|
|
28
29
|
}
|
|
30
|
+
fromKubeSecret(kubeSecret) {
|
|
31
|
+
var _a;
|
|
32
|
+
const data = (_a = kubeSecret === null || kubeSecret === void 0 ? void 0 : kubeSecret.data) !== null && _a !== void 0 ? _a : {};
|
|
33
|
+
this.JWKs = JSON.parse(Buffer.from(data[JWKSKeyName], 'base64').toString());
|
|
34
|
+
this.CookieKeys = JSON.parse(Buffer.from(data[CookieKeysKeyName], 'base64').toString());
|
|
35
|
+
}
|
|
36
|
+
appendJWK(maxNumber) {
|
|
37
|
+
tslib_1.__classPrivateFieldGet(this, _Secret_instances, "m", _Secret_append).call(this, 'JWKs', maxNumber, () => tslib_1.__classPrivateFieldGet(this, _Secret_instances, "m", _Secret_generateRSAJwk).call(this, 4096));
|
|
38
|
+
}
|
|
39
|
+
appendCookieKey(maxNumber) {
|
|
40
|
+
tslib_1.__classPrivateFieldGet(this, _Secret_instances, "m", _Secret_append).call(this, 'CookieKeys', maxNumber, () => tslib_1.__classPrivateFieldGet(this, _Secret_instances, "m", _Secret_generateCookieKey).call(this, 32));
|
|
41
|
+
}
|
|
42
|
+
rotateJWKs() {
|
|
43
|
+
tslib_1.__classPrivateFieldGet(this, _Secret_instances, "m", _Secret_rotate).call(this, 'JWKs');
|
|
44
|
+
}
|
|
45
|
+
rotateCookieKeys() {
|
|
46
|
+
tslib_1.__classPrivateFieldGet(this, _Secret_instances, "m", _Secret_rotate).call(this, 'CookieKeys');
|
|
47
|
+
}
|
|
29
48
|
}
|
|
30
49
|
exports.Secret = Secret;
|
|
31
|
-
_Secret_instances = new WeakSet(),
|
|
50
|
+
_Secret_instances = new WeakSet(), _Secret_append = function _Secret_append(property, maxNumber, generatorFn) {
|
|
51
|
+
if (this[property].length + 1 > maxNumber) {
|
|
52
|
+
this.command.log(`Removing extra ${this[property].length + 1 - maxNumber} ${property}`);
|
|
53
|
+
this.JWKs.splice(maxNumber - 1);
|
|
54
|
+
}
|
|
55
|
+
this.command.log(`Appending new value to end of ${property}`);
|
|
56
|
+
this[property] = [...this[property], generatorFn()];
|
|
57
|
+
}, _Secret_rotate = function _Secret_rotate(property) {
|
|
58
|
+
this.command.log(`Rotating new value to the start of ${property}`);
|
|
59
|
+
const newValue = this[property].pop();
|
|
60
|
+
if (newValue) {
|
|
61
|
+
this[property] = [newValue, ...this[property]];
|
|
62
|
+
}
|
|
63
|
+
}, _Secret_generateRSAJwk = function _Secret_generateRSAJwk(len) {
|
|
64
|
+
this.command.log(`Generating ${len}bit RSA key pair`);
|
|
32
65
|
const keypair = jsrsasign_1.KEYUTIL.generateKeypair('RSA', len);
|
|
33
66
|
const jwk = jsrsasign_1.KEYUTIL.getJWK(keypair.prvKeyObj);
|
|
34
67
|
return {
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.2.0",
|
|
3
3
|
"commands": {
|
|
4
4
|
"initialize": {
|
|
5
5
|
"id": "initialize",
|
|
@@ -73,6 +73,105 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"args": {}
|
|
76
|
+
},
|
|
77
|
+
"rotate": {
|
|
78
|
+
"id": "rotate",
|
|
79
|
+
"description": "Append new JWK|cookie key|both and rotate the array, optionally restarting the deployment",
|
|
80
|
+
"strict": true,
|
|
81
|
+
"pluginName": "@codemowers/oidc-key-manager",
|
|
82
|
+
"pluginAlias": "@codemowers/oidc-key-manager",
|
|
83
|
+
"pluginType": "core",
|
|
84
|
+
"aliases": [],
|
|
85
|
+
"examples": [
|
|
86
|
+
"<%= config.bin %> <%= command.id %>"
|
|
87
|
+
],
|
|
88
|
+
"flags": {
|
|
89
|
+
"namespace": {
|
|
90
|
+
"name": "namespace",
|
|
91
|
+
"type": "option",
|
|
92
|
+
"char": "n",
|
|
93
|
+
"description": "namespace, defaults to current namespace if service account is used",
|
|
94
|
+
"required": false,
|
|
95
|
+
"multiple": false,
|
|
96
|
+
"aliases": [
|
|
97
|
+
"namespace"
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
"secret": {
|
|
101
|
+
"name": "secret",
|
|
102
|
+
"type": "option",
|
|
103
|
+
"char": "s",
|
|
104
|
+
"description": "secret name",
|
|
105
|
+
"required": false,
|
|
106
|
+
"multiple": false,
|
|
107
|
+
"default": "oidc-keys",
|
|
108
|
+
"aliases": [
|
|
109
|
+
"secret"
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"config": {
|
|
113
|
+
"name": "config",
|
|
114
|
+
"type": "option",
|
|
115
|
+
"char": "c",
|
|
116
|
+
"description": "use local or in-cluster Kubernetes config",
|
|
117
|
+
"required": true,
|
|
118
|
+
"multiple": false,
|
|
119
|
+
"options": [
|
|
120
|
+
"local",
|
|
121
|
+
"cluster"
|
|
122
|
+
],
|
|
123
|
+
"aliases": [
|
|
124
|
+
"config"
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
"both": {
|
|
128
|
+
"name": "both",
|
|
129
|
+
"type": "boolean",
|
|
130
|
+
"description": "rotate both JWKs and cookie keys",
|
|
131
|
+
"allowNo": false
|
|
132
|
+
},
|
|
133
|
+
"jwks": {
|
|
134
|
+
"name": "jwks",
|
|
135
|
+
"type": "boolean",
|
|
136
|
+
"description": "rotate JWKs",
|
|
137
|
+
"allowNo": false
|
|
138
|
+
},
|
|
139
|
+
"cookie-keys": {
|
|
140
|
+
"name": "cookie-keys",
|
|
141
|
+
"type": "boolean",
|
|
142
|
+
"description": "rotate cookie keys",
|
|
143
|
+
"allowNo": false
|
|
144
|
+
},
|
|
145
|
+
"max-number-of-jwks": {
|
|
146
|
+
"name": "max-number-of-jwks",
|
|
147
|
+
"type": "option",
|
|
148
|
+
"multiple": false,
|
|
149
|
+
"default": 3
|
|
150
|
+
},
|
|
151
|
+
"max-number-of-cookie-keys": {
|
|
152
|
+
"name": "max-number-of-cookie-keys",
|
|
153
|
+
"type": "option",
|
|
154
|
+
"multiple": false,
|
|
155
|
+
"default": 3
|
|
156
|
+
},
|
|
157
|
+
"restart-deployment": {
|
|
158
|
+
"name": "restart-deployment",
|
|
159
|
+
"type": "option",
|
|
160
|
+
"description": "Kubernetes deployment name to restart while rotating",
|
|
161
|
+
"multiple": false
|
|
162
|
+
},
|
|
163
|
+
"restart-deployment-backoff": {
|
|
164
|
+
"name": "restart-deployment-backoff",
|
|
165
|
+
"type": "option",
|
|
166
|
+
"description": "Seconds to wait for deployment to restart",
|
|
167
|
+
"multiple": false,
|
|
168
|
+
"dependsOn": [
|
|
169
|
+
"restart-deployment"
|
|
170
|
+
],
|
|
171
|
+
"default": 60
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"args": {}
|
|
76
175
|
}
|
|
77
176
|
}
|
|
78
177
|
}
|