@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 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.1.0 linux-x64 node-v16.17.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.1.0/dist/commands/initialize.ts)_
75
- <!-- commandsstop -->
76
- * [`key-manager initialize`](#key-manager-initialize)
77
-
78
- ## `key-manager initialize`
79
-
80
- Initialize the secret with initial keys
81
-
82
- ```
83
- USAGE
84
- $ key-manager initialize [-n <value>] [-s <value>] [--recreate]
85
-
86
- FLAGS
87
- -n, --namespace=<value> namespace, defaults to current namespace if service account is used
88
- -s, --secret=<value> [default: oidc-keys] secret name
89
- --recreate recreate the secret if it exists
90
-
91
- DESCRIPTION
92
- Initialize the secret with initial keys
93
-
94
- EXAMPLES
95
- $ key-manager initialize
96
-
97
- $ key-manager initialize -n <kube namespace> -s <secret name>
98
-
99
- $ key-manager initialize --namespace <kube namespace> --secret <secret name> --recreate
100
- ```
101
-
102
- _See code: [dist/commands/initialize.ts](https://github.com/codemowers/oidc-key-manager/blob/v0.0.0/dist/commands/initialize.ts)_
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.checkSecretExistence();
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
- checkSecretExistence(): Promise<boolean>;
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 checkSecretExistence() {
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 exists = await this.coreV1Api.readNamespacedSecret(this.secretName, this.namespace)
32
- .then(() => true)
70
+ const secret = await this.coreV1Api.readNamespacedSecret(this.secretName, this.namespace)
71
+ .then(response => response.body)
33
72
  .catch(error => {
34
- if (error.status === 404) {
35
- return false;
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(exists ? `Secret ${this.secretName} already exists` : `Secret ${this.secretName} does not exist`);
39
- return Boolean(exists);
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() {
@@ -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
- constructor();
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
  }
@@ -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(), _Secret_generateRSAJwk = function _Secret_generateRSAJwk(len) {
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 {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemowers/oidc-key-manager",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "CLI to manage secret keys required by oidc-gateway",
5
5
  "author": "Erki Aas",
6
6
  "bin": {