opal-security 2.3.4 → 3.0.1-beta.4262451
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 +67 -38
- package/lib/commands/aws/identity.d.ts +1 -1
- package/lib/commands/aws/identity.js +2 -2
- package/lib/commands/{migrate-creds.d.ts → clear-auth-provider.d.ts} +3 -2
- package/lib/commands/clear-auth-provider.js +28 -0
- package/lib/commands/curl-example.d.ts +1 -1
- package/lib/commands/curl-example.js +10 -3
- package/lib/commands/iam-roles/start.d.ts +1 -1
- package/lib/commands/iam-roles/start.js +14 -14
- package/lib/commands/kube-roles/start.d.ts +1 -1
- package/lib/commands/kube-roles/start.js +10 -10
- package/lib/commands/login.d.ts +2 -1
- package/lib/commands/login.js +134 -74
- package/lib/commands/logout.d.ts +1 -1
- package/lib/commands/logout.js +3 -3
- package/lib/commands/postgres-instances/start.d.ts +1 -1
- package/lib/commands/postgres-instances/start.js +35 -34
- package/lib/commands/resources/get.d.ts +1 -1
- package/lib/commands/resources/get.js +6 -4
- package/lib/commands/set-auth-provider.d.ts +11 -0
- package/lib/commands/set-auth-provider.js +44 -0
- package/lib/commands/set-custom-header.d.ts +1 -1
- package/lib/commands/set-custom-header.js +5 -3
- package/lib/commands/set-token.d.ts +1 -1
- package/lib/commands/set-token.js +26 -19
- package/lib/commands/set-url.d.ts +1 -1
- package/lib/commands/set-url.js +13 -12
- package/lib/commands/ssh/copyFrom.d.ts +1 -1
- package/lib/commands/ssh/copyFrom.js +13 -13
- package/lib/commands/ssh/copyTo.d.ts +1 -1
- package/lib/commands/ssh/copyTo.js +13 -13
- package/lib/commands/ssh/start.d.ts +1 -1
- package/lib/commands/ssh/start.js +14 -15
- package/lib/graphql/fragment-masking.d.ts +19 -0
- package/lib/graphql/fragment-masking.js +21 -0
- package/lib/graphql/gql.d.ts +36 -0
- package/lib/graphql/gql.js +12 -0
- package/lib/graphql/graphql.d.ts +11413 -0
- package/lib/graphql/graphql.js +1491 -0
- package/lib/graphql/index.d.ts +2 -0
- package/lib/graphql/index.js +5 -0
- package/lib/handler.d.ts +5 -5
- package/lib/handler.js +7 -7
- package/lib/index.d.ts +1 -1
- package/lib/lib/apollo.d.ts +4 -2
- package/lib/lib/apollo.js +107 -55
- package/lib/lib/aws.js +15 -12
- package/lib/lib/cmd.d.ts +4 -6
- package/lib/lib/cmd.js +11 -11
- package/lib/lib/config.js +14 -15
- package/lib/lib/credentials/index.d.ts +9 -4
- package/lib/lib/credentials/index.js +26 -15
- package/lib/lib/credentials/keychain.d.ts +3 -3
- package/lib/lib/credentials/keychain.js +12 -12
- package/lib/lib/credentials/localEncryption.d.ts +3 -3
- package/lib/lib/credentials/localEncryption.js +40 -31
- package/lib/lib/flags.js +9 -9
- package/lib/lib/resources.d.ts +2 -2
- package/lib/lib/resources.js +29 -23
- package/lib/lib/sessions.d.ts +2 -2
- package/lib/lib/sessions.js +18 -17
- package/lib/lib/ssh.d.ts +1 -1
- package/lib/lib/ssh.js +8 -8
- package/lib/lib/util.d.ts +0 -1
- package/lib/lib/util.js +13 -13
- package/lib/types.d.ts +1787 -1787
- package/oclif.manifest.json +56 -7
- package/package.json +23 -29
- package/lib/commands/migrate-creds.js +0 -48
|
@@ -1,41 +1,52 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.removeOpalCredentials = exports.getOpalCredentials = exports.setOpalCredentials = void 0;
|
|
3
|
+
exports.removeOpalCredentials = exports.getOpalCredentials = exports.setOpalCredentials = exports.SecretType = void 0;
|
|
4
4
|
const config_1 = require("../config");
|
|
5
5
|
const keychain_1 = require("./keychain");
|
|
6
6
|
const localEncryption_1 = require("./localEncryption");
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
var SecretType;
|
|
8
|
+
(function (SecretType) {
|
|
9
|
+
SecretType["Cookie"] = "COOKIE";
|
|
10
|
+
SecretType["ApiToken"] = "API_TOKEN";
|
|
11
|
+
})(SecretType || (exports.SecretType = SecretType = {}));
|
|
12
|
+
const setOpalCredentials = async (command, email, organizationID, clientIDCandidate, secret, secretType) => {
|
|
13
|
+
const givenEmail = email || "email-unset";
|
|
9
14
|
const configData = (0, config_1.getOrCreateConfigData)(command.config.configDir);
|
|
10
15
|
configData.creds = {
|
|
11
16
|
clientIDCandidate,
|
|
12
17
|
email,
|
|
13
18
|
organizationID,
|
|
19
|
+
secretType,
|
|
14
20
|
};
|
|
15
21
|
(0, config_1.writeConfigData)(command.config.configDir, configData);
|
|
16
22
|
if (process.platform === "darwin") {
|
|
17
|
-
await (0, keychain_1.
|
|
23
|
+
await (0, keychain_1.setSecretInKeychain)(givenEmail, secret);
|
|
18
24
|
}
|
|
19
25
|
else {
|
|
20
|
-
await (0, localEncryption_1.
|
|
26
|
+
await (0, localEncryption_1.setSecretInConfig)(command, configData, secret);
|
|
21
27
|
}
|
|
22
28
|
};
|
|
23
29
|
exports.setOpalCredentials = setOpalCredentials;
|
|
24
|
-
const getOpalCredentials = async (command,
|
|
30
|
+
const getOpalCredentials = async (command, includeAuthSecret = true) => {
|
|
25
31
|
var _a, _b;
|
|
26
|
-
|
|
27
|
-
if (!
|
|
32
|
+
const creds = (_b = (_a = (0, config_1.getOrCreateConfigData)(command.config.configDir)) === null || _a === void 0 ? void 0 : _a.creds) !== null && _b !== void 0 ? _b : {};
|
|
33
|
+
if (!includeAuthSecret) {
|
|
28
34
|
return creds;
|
|
29
35
|
}
|
|
30
|
-
let
|
|
36
|
+
let secret = null;
|
|
31
37
|
if (process.platform === "darwin") {
|
|
32
|
-
|
|
38
|
+
secret = await (0, keychain_1.getSecretFromKeychain)((creds === null || creds === void 0 ? void 0 : creds.email) || "email-unset");
|
|
33
39
|
}
|
|
34
40
|
else {
|
|
35
|
-
|
|
41
|
+
secret = await (0, localEncryption_1.getSecretFromConfig)(creds);
|
|
36
42
|
}
|
|
37
|
-
if (
|
|
38
|
-
creds.
|
|
43
|
+
if (secret) {
|
|
44
|
+
creds.secret = secret;
|
|
45
|
+
// This is a fallback for users with stored credentials from before we converted to session auth with the CLITokenExchange mutation
|
|
46
|
+
// It will allow them to continue authenticating with an access token in an Authorization header, which will work until we remove support for that
|
|
47
|
+
if (!creds.secretType) {
|
|
48
|
+
creds.secretType = SecretType.ApiToken;
|
|
49
|
+
}
|
|
39
50
|
}
|
|
40
51
|
return creds;
|
|
41
52
|
};
|
|
@@ -43,13 +54,13 @@ exports.getOpalCredentials = getOpalCredentials;
|
|
|
43
54
|
const removeOpalCredentials = async (command) => {
|
|
44
55
|
var _a;
|
|
45
56
|
const configData = (0, config_1.getOrCreateConfigData)(command.config.configDir);
|
|
46
|
-
const email = ((_a = configData === null || configData === void 0 ? void 0 : configData.creds) === null || _a === void 0 ? void 0 : _a.email) ||
|
|
57
|
+
const email = ((_a = configData === null || configData === void 0 ? void 0 : configData.creds) === null || _a === void 0 ? void 0 : _a.email) || "email-unset";
|
|
47
58
|
// On linux, the access token is stored encrypted in configData.creds, so this effectively removes it
|
|
48
59
|
configData.creds = {};
|
|
49
60
|
(0, config_1.writeConfigData)(command.config.configDir, configData);
|
|
50
61
|
// but on OSX, we need an extra step to delete the token from the keychain
|
|
51
62
|
if (process.platform === "darwin") {
|
|
52
|
-
await (0, keychain_1.
|
|
63
|
+
await (0, keychain_1.deleteSecretFromKeychain)(email);
|
|
53
64
|
}
|
|
54
65
|
};
|
|
55
66
|
exports.removeOpalCredentials = removeOpalCredentials;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
1
|
+
export declare const setSecretInKeychain: (email: string, secret: string) => Promise<void>;
|
|
2
|
+
export declare const getSecretFromKeychain: (email: string) => Promise<string>;
|
|
3
|
+
export declare const deleteSecretFromKeychain: (email: string) => Promise<void>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.deleteSecretFromKeychain = exports.getSecretFromKeychain = exports.setSecretInKeychain = void 0;
|
|
4
4
|
const keychain = require("keychain");
|
|
5
|
-
const OPAL_KEYCHAIN_SERVICE =
|
|
5
|
+
const OPAL_KEYCHAIN_SERVICE = "opal-cli";
|
|
6
6
|
// On OSX, we can easily work with the system Keychain to store the access token.
|
|
7
|
-
const
|
|
7
|
+
const setSecretInKeychain = async (email, secret) => {
|
|
8
8
|
return new Promise((resolve, reject) => {
|
|
9
|
-
keychain.setPassword({ account: email, service: OPAL_KEYCHAIN_SERVICE, password:
|
|
9
|
+
keychain.setPassword({ account: email, service: OPAL_KEYCHAIN_SERVICE, password: secret }, (err) => {
|
|
10
10
|
if (err) {
|
|
11
11
|
reject(err);
|
|
12
12
|
}
|
|
@@ -16,11 +16,11 @@ const setTokenInKeychain = async (email, accessToken) => {
|
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
18
|
};
|
|
19
|
-
exports.
|
|
20
|
-
const
|
|
19
|
+
exports.setSecretInKeychain = setSecretInKeychain;
|
|
20
|
+
const getSecretFromKeychain = async (email) => {
|
|
21
21
|
return new Promise((resolve, reject) => {
|
|
22
|
-
keychain.getPassword({ account: email, service: OPAL_KEYCHAIN_SERVICE },
|
|
23
|
-
if (err && (err === null || err === void 0 ? void 0 : err.type) !==
|
|
22
|
+
keychain.getPassword({ account: email, service: OPAL_KEYCHAIN_SERVICE }, (err, password) => {
|
|
23
|
+
if (err && (err === null || err === void 0 ? void 0 : err.type) !== "PasswordNotFoundError") {
|
|
24
24
|
reject(err);
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
@@ -29,13 +29,13 @@ const getTokenFromKeychain = async (email) => {
|
|
|
29
29
|
});
|
|
30
30
|
});
|
|
31
31
|
};
|
|
32
|
-
exports.
|
|
33
|
-
const
|
|
32
|
+
exports.getSecretFromKeychain = getSecretFromKeychain;
|
|
33
|
+
const deleteSecretFromKeychain = async (email) => {
|
|
34
34
|
return new Promise((resolve) => {
|
|
35
|
-
keychain.deletePassword({ service: OPAL_KEYCHAIN_SERVICE, account: email },
|
|
35
|
+
keychain.deletePassword({ service: OPAL_KEYCHAIN_SERVICE, account: email }, () => {
|
|
36
36
|
// we might get errors here if the password doesn't exist, which we want to swallow
|
|
37
37
|
resolve();
|
|
38
38
|
});
|
|
39
39
|
});
|
|
40
40
|
};
|
|
41
|
-
exports.
|
|
41
|
+
exports.deleteSecretFromKeychain = deleteSecretFromKeychain;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { Command } from
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
1
|
+
import type { Command } from "@oclif/core";
|
|
2
|
+
export declare const setSecretInConfig: (command: Command, configData: Record<string, any>, secret: string) => Promise<void>;
|
|
3
|
+
export declare const getSecretFromConfig: (credsConfig: Record<string, any>) => Promise<string>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
const
|
|
3
|
+
exports.getSecretFromConfig = exports.setSecretInConfig = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const argon2 = require("argon2");
|
|
6
|
-
const
|
|
6
|
+
const inquirer = require("inquirer");
|
|
7
7
|
const config_1 = require("../config");
|
|
8
|
-
const ENCRYPTION_ALGORITHM =
|
|
8
|
+
const ENCRYPTION_ALGORITHM = "aes-256-gcm";
|
|
9
9
|
/**
|
|
10
10
|
* On linux, windows, and WSL, we can't easily work with the system keychain
|
|
11
11
|
* So instead, we store the token encrypted in an `accessTokenDetails` block in the CLI's config file
|
|
@@ -20,56 +20,65 @@ let password;
|
|
|
20
20
|
const promptForPassword = async (msg) => {
|
|
21
21
|
const { customKey } = await inquirer.prompt([
|
|
22
22
|
{
|
|
23
|
-
name:
|
|
23
|
+
name: "customKey",
|
|
24
24
|
message: msg,
|
|
25
|
-
type:
|
|
25
|
+
type: "password",
|
|
26
26
|
validate: (key) => key.length > 0,
|
|
27
27
|
},
|
|
28
28
|
]);
|
|
29
29
|
return customKey;
|
|
30
30
|
};
|
|
31
|
-
const
|
|
31
|
+
const setSecretInConfig = async (command, configData, secret) => {
|
|
32
32
|
if (!password) {
|
|
33
|
-
password = await promptForPassword(
|
|
33
|
+
password = await promptForPassword("Enter a password to encrypt your credentials: ");
|
|
34
34
|
}
|
|
35
|
-
const salt = (0,
|
|
36
|
-
const key = await argon2.hash(password, {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
const salt = (0, node_crypto_1.randomBytes)(24);
|
|
36
|
+
const key = await argon2.hash(password, {
|
|
37
|
+
hashLength: 32,
|
|
38
|
+
raw: true,
|
|
39
|
+
salt: salt,
|
|
40
|
+
});
|
|
41
|
+
const iv = (0, node_crypto_1.randomBytes)(12);
|
|
42
|
+
const cipher = (0, node_crypto_1.createCipheriv)(ENCRYPTION_ALGORITHM, key, iv);
|
|
43
|
+
let secretEncrypted = cipher.update(secret, "utf8", "base64");
|
|
44
|
+
secretEncrypted += cipher.final("base64");
|
|
41
45
|
// In addition to storing the encrypted text, we need to store the argon2 salt, and AES authTag + IV so we can decrypt
|
|
42
46
|
configData.creds.accessTokenDetails = {
|
|
43
|
-
encryptedText:
|
|
44
|
-
authTag: cipher.getAuthTag().toString(
|
|
45
|
-
salt: salt.toString(
|
|
46
|
-
iv: iv.toString(
|
|
47
|
+
encryptedText: secretEncrypted,
|
|
48
|
+
authTag: cipher.getAuthTag().toString("base64"),
|
|
49
|
+
salt: salt.toString("base64"),
|
|
50
|
+
iv: iv.toString("base64"),
|
|
47
51
|
};
|
|
48
52
|
(0, config_1.writeConfigData)(command.config.configDir, configData);
|
|
49
53
|
};
|
|
50
|
-
exports.
|
|
51
|
-
const
|
|
52
|
-
let
|
|
54
|
+
exports.setSecretInConfig = setSecretInConfig;
|
|
55
|
+
const getSecretFromConfig = async (credsConfig) => {
|
|
56
|
+
let secret = "";
|
|
53
57
|
if (credsConfig === null || credsConfig === void 0 ? void 0 : credsConfig.accessTokenDetails) {
|
|
54
58
|
if (!password) {
|
|
55
|
-
password = await promptForPassword(
|
|
59
|
+
password = await promptForPassword("Enter the password used to encrypt your credentials: ");
|
|
56
60
|
}
|
|
57
|
-
const salt = Buffer.from(credsConfig.accessTokenDetails.salt,
|
|
58
|
-
const iv = Buffer.from(credsConfig.accessTokenDetails.iv,
|
|
59
|
-
const key = await argon2.hash(password, {
|
|
61
|
+
const salt = Buffer.from(credsConfig.accessTokenDetails.salt, "base64");
|
|
62
|
+
const iv = Buffer.from(credsConfig.accessTokenDetails.iv, "base64");
|
|
63
|
+
const key = await argon2.hash(password, {
|
|
64
|
+
hashLength: 32,
|
|
65
|
+
raw: true,
|
|
66
|
+
salt: salt,
|
|
67
|
+
});
|
|
60
68
|
try {
|
|
61
|
-
const decipher = (0,
|
|
62
|
-
decipher.setAuthTag(Buffer.from(credsConfig.accessTokenDetails.authTag,
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
const decipher = (0, node_crypto_1.createDecipheriv)(ENCRYPTION_ALGORITHM, key, iv);
|
|
70
|
+
decipher.setAuthTag(Buffer.from(credsConfig.accessTokenDetails.authTag, "base64"));
|
|
71
|
+
secret = decipher.update(credsConfig.accessTokenDetails.encryptedText, "base64", "utf8");
|
|
72
|
+
secret += decipher.final("utf8");
|
|
65
73
|
}
|
|
66
74
|
catch (error) {
|
|
67
75
|
console.error("ERROR: Failed to decrypt local credentials.\n" +
|
|
68
76
|
" Check that you entered the correct password, or re-authenticate with `opal login`");
|
|
69
77
|
process.exit(1);
|
|
70
78
|
}
|
|
79
|
+
// biome-ignore lint/performance/noDelete: add tests for this function and remove this eventually
|
|
71
80
|
delete credsConfig.accessTokenDetails;
|
|
72
81
|
}
|
|
73
|
-
return
|
|
82
|
+
return secret;
|
|
74
83
|
};
|
|
75
|
-
exports.
|
|
84
|
+
exports.getSecretFromConfig = getSecretFromConfig;
|
package/lib/lib/flags.js
CHANGED
|
@@ -3,24 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SHARED_FLAGS = void 0;
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
5
|
exports.SHARED_FLAGS = {
|
|
6
|
-
help: core_1.Flags.help({ char:
|
|
6
|
+
help: core_1.Flags.help({ char: "h" }),
|
|
7
7
|
id: core_1.Flags.string({
|
|
8
8
|
multiple: false,
|
|
9
|
-
char:
|
|
10
|
-
description:
|
|
9
|
+
char: "i",
|
|
10
|
+
description: "The Opal ID of the resource. You can find this from the URL, e.g. https://opal.dev/resources/[ID]",
|
|
11
11
|
}),
|
|
12
12
|
accessLevelRemoteId: core_1.Flags.string({
|
|
13
13
|
multiple: false,
|
|
14
|
-
char:
|
|
15
|
-
description:
|
|
14
|
+
char: "a",
|
|
15
|
+
description: "The remote ID of the access level with which to access the resource.",
|
|
16
16
|
}),
|
|
17
17
|
sessionId: core_1.Flags.string({
|
|
18
18
|
multiple: false,
|
|
19
|
-
char:
|
|
20
|
-
description:
|
|
19
|
+
char: "s",
|
|
20
|
+
description: "The Opal ID of the session to connect to. Uses an existing session that was created via the web flow.",
|
|
21
21
|
}),
|
|
22
22
|
refresh: core_1.Flags.boolean({
|
|
23
|
-
char:
|
|
24
|
-
description:
|
|
23
|
+
char: "r",
|
|
24
|
+
description: "Starts a new session even if one already exists. Useful if a session is about to expire.",
|
|
25
25
|
}),
|
|
26
26
|
};
|
package/lib/lib/resources.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Command } from
|
|
2
|
-
import { ResourceAccessLevel, ResourceAccessLevelInput } from
|
|
1
|
+
import type { Command } from "@oclif/core";
|
|
2
|
+
import type { ResourceAccessLevel, ResourceAccessLevelInput } from "../types";
|
|
3
3
|
export type ResourceInfo = {
|
|
4
4
|
id: string;
|
|
5
5
|
name: string;
|
package/lib/lib/resources.js
CHANGED
|
@@ -5,16 +5,15 @@ const inquirer = require("inquirer");
|
|
|
5
5
|
const handler_1 = require("../handler");
|
|
6
6
|
const apollo_1 = require("./apollo");
|
|
7
7
|
exports.DEFAULT_ACCESS_LEVEL = {
|
|
8
|
-
accessLevelName:
|
|
9
|
-
accessLevelRemoteId:
|
|
8
|
+
accessLevelName: "",
|
|
9
|
+
accessLevelRemoteId: "",
|
|
10
10
|
};
|
|
11
11
|
// Returns a filtered array of items that match the input
|
|
12
12
|
const filterChoices = (input, choices) => {
|
|
13
|
-
|
|
14
|
-
if (input === '') {
|
|
13
|
+
if (!input) {
|
|
15
14
|
return choices;
|
|
16
15
|
}
|
|
17
|
-
return choices.filter(choice => choice.name.toLowerCase().includes(input.toLowerCase()));
|
|
16
|
+
return choices.filter((choice) => choice.name.toLowerCase().includes(input.toLowerCase()));
|
|
18
17
|
};
|
|
19
18
|
exports.filterChoices = filterChoices;
|
|
20
19
|
const ListResourcesDocumentTemplate = `
|
|
@@ -37,8 +36,8 @@ query ListResources {
|
|
|
37
36
|
}
|
|
38
37
|
}`;
|
|
39
38
|
const promptUserForResource = async (command, resourceType, message) => {
|
|
40
|
-
const listResourcesDocument = ListResourcesDocumentTemplate.replace(
|
|
41
|
-
const { resp, error } = await (0, handler_1.
|
|
39
|
+
const listResourcesDocument = ListResourcesDocumentTemplate.replace("RESOURCE_TYPE", resourceType);
|
|
40
|
+
const { resp, error } = await (0, handler_1.runQueryDeprecated)({
|
|
42
41
|
command: command,
|
|
43
42
|
query: listResourcesDocument,
|
|
44
43
|
variables: {},
|
|
@@ -60,19 +59,22 @@ const promptUserForResource = async (command, resourceType, message) => {
|
|
|
60
59
|
};
|
|
61
60
|
});
|
|
62
61
|
if (resourceInfos.length === 0) {
|
|
63
|
-
return (0, apollo_1.handleError)(command,
|
|
62
|
+
return (0, apollo_1.handleError)(command, "You don't have access to any resources of this type. Please go to Opal to request access.");
|
|
64
63
|
}
|
|
65
64
|
const resourceInfoByName = {};
|
|
66
|
-
|
|
65
|
+
// biome-ignore lint/complexity/noForEach: fix it when you get the chance
|
|
66
|
+
resourceInfos.forEach((resourceInfo) => {
|
|
67
67
|
resourceInfoByName[resourceInfo.name] = resourceInfo;
|
|
68
68
|
});
|
|
69
|
-
inquirer.registerPrompt(
|
|
70
|
-
const selectedResourceInfo = await inquirer.prompt([
|
|
71
|
-
|
|
69
|
+
inquirer.registerPrompt("autocomplete", require("inquirer-autocomplete-prompt"));
|
|
70
|
+
const selectedResourceInfo = await inquirer.prompt([
|
|
71
|
+
{
|
|
72
|
+
name: "resource",
|
|
72
73
|
message: message,
|
|
73
|
-
type:
|
|
74
|
+
type: "autocomplete",
|
|
74
75
|
source: (answers, input) => (0, exports.filterChoices)(input, resourceInfos),
|
|
75
|
-
}
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
76
78
|
return resourceInfoByName[selectedResourceInfo.resource];
|
|
77
79
|
};
|
|
78
80
|
exports.promptUserForResource = promptUserForResource;
|
|
@@ -90,7 +92,7 @@ query ListAccessLevelsForResource($resourceId: ResourceId!) {
|
|
|
90
92
|
}`;
|
|
91
93
|
const promptUserForAccessLevels = async (command, resourceId, instanceType, accessLevelRemoteId) => {
|
|
92
94
|
var _a, _b, _c;
|
|
93
|
-
const { resp, error } = await (0, handler_1.
|
|
95
|
+
const { resp, error } = await (0, handler_1.runQueryDeprecated)({
|
|
94
96
|
command: command,
|
|
95
97
|
query: ListAccessLevelsForResource,
|
|
96
98
|
variables: { resourceId },
|
|
@@ -103,11 +105,12 @@ const promptUserForAccessLevels = async (command, resourceId, instanceType, acce
|
|
|
103
105
|
name: resp.accessLevelName,
|
|
104
106
|
}));
|
|
105
107
|
if (!accessLevelInfos) {
|
|
106
|
-
return (0, apollo_1.handleError)(command,
|
|
108
|
+
return (0, apollo_1.handleError)(command, "This resource requires an access level, but none have been set up in Opal. Please contact your Opal admin.");
|
|
107
109
|
}
|
|
108
110
|
const accessLevelInfoByName = {};
|
|
109
111
|
const accessLevelInfoByRemoteId = {};
|
|
110
|
-
|
|
112
|
+
// biome-ignore lint/complexity/noForEach: please fix this
|
|
113
|
+
accessLevelInfos.forEach((accessLevelInfo) => {
|
|
111
114
|
accessLevelInfoByName[accessLevelInfo.name] = accessLevelInfo;
|
|
112
115
|
accessLevelInfoByRemoteId[accessLevelInfo.id] = accessLevelInfo;
|
|
113
116
|
});
|
|
@@ -117,14 +120,17 @@ const promptUserForAccessLevels = async (command, resourceId, instanceType, acce
|
|
|
117
120
|
}
|
|
118
121
|
// Prompt user to pick access levels available to them
|
|
119
122
|
if (!selectedAccessLevel) {
|
|
120
|
-
inquirer.registerPrompt(
|
|
121
|
-
const selectedAccessLevelInfo = await inquirer.prompt([
|
|
122
|
-
|
|
123
|
+
inquirer.registerPrompt("autocomplete", require("inquirer-autocomplete-prompt"));
|
|
124
|
+
const selectedAccessLevelInfo = await inquirer.prompt([
|
|
125
|
+
{
|
|
126
|
+
name: "accessLevel",
|
|
123
127
|
message: `Select an access level to the ${instanceType}`,
|
|
124
|
-
type:
|
|
128
|
+
type: "autocomplete",
|
|
125
129
|
source: (answers, input) => (0, exports.filterChoices)(input, accessLevelInfos),
|
|
126
|
-
}
|
|
127
|
-
|
|
130
|
+
},
|
|
131
|
+
]);
|
|
132
|
+
selectedAccessLevel =
|
|
133
|
+
accessLevelInfoByName[selectedAccessLevelInfo.accessLevel];
|
|
128
134
|
}
|
|
129
135
|
return {
|
|
130
136
|
accessLevelName: selectedAccessLevel.name,
|
package/lib/lib/sessions.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command } from
|
|
2
|
-
import { ResourceAccessLevelInput } from
|
|
1
|
+
import type { Command } from "@oclif/core";
|
|
2
|
+
import type { ResourceAccessLevelInput } from "../types";
|
|
3
3
|
export declare const getOrCreateSession: (command: Command, resourceId: string, accessLevel: ResourceAccessLevelInput, sessionId: string | undefined, metadataFragment: string, wantNewSession?: boolean) => Promise<any>;
|
|
4
4
|
export declare const getSessionExpirationMessage: (session: any) => string;
|
package/lib/lib/sessions.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getSessionExpirationMessage = exports.getOrCreateSession = void 0;
|
|
4
|
+
const moment = require("moment");
|
|
4
5
|
const open = require("open");
|
|
5
|
-
const config_1 = require("../lib/config");
|
|
6
6
|
const handler_1 = require("../handler");
|
|
7
|
-
const moment = require("moment");
|
|
8
7
|
const apollo_1 = require("../lib/apollo");
|
|
8
|
+
const config_1 = require("../lib/config");
|
|
9
9
|
const util_1 = require("./util");
|
|
10
10
|
const CreateSessionDocument = `
|
|
11
11
|
mutation CreateSession($id: ResourceId!, $accessLevel: ResourceAccessLevelInput!, $sessionId: SessionId) {
|
|
@@ -59,9 +59,9 @@ query ListSessions($id: ResourceId!) {
|
|
|
59
59
|
}
|
|
60
60
|
`;
|
|
61
61
|
const getSession = async (command, resourceId, accessLevelRemoteId, sessionId, metadataFragment, minCreatedAt) => {
|
|
62
|
-
const { resp, error } = await (0, handler_1.
|
|
62
|
+
const { resp, error } = await (0, handler_1.runQueryDeprecated)({
|
|
63
63
|
command: command,
|
|
64
|
-
query: ListSessionsDocument.replace(
|
|
64
|
+
query: ListSessionsDocument.replace("METADATA_FRAGMENT", metadataFragment),
|
|
65
65
|
variables: {
|
|
66
66
|
id: resourceId,
|
|
67
67
|
},
|
|
@@ -70,8 +70,9 @@ const getSession = async (command, resourceId, accessLevelRemoteId, sessionId, m
|
|
|
70
70
|
return (0, apollo_1.handleError)(command, error);
|
|
71
71
|
}
|
|
72
72
|
switch (resp === null || resp === void 0 ? void 0 : resp.data.sessions.__typename) {
|
|
73
|
-
case
|
|
73
|
+
case "SessionsResult": {
|
|
74
74
|
const sessions = resp.data.sessions.sessions;
|
|
75
|
+
// biome-ignore lint/suspicious/noImplicitAnyLet: please fix how we do queries in this cli
|
|
75
76
|
let selectedSession;
|
|
76
77
|
for (const session of sessions) {
|
|
77
78
|
if (sessionId && session.id !== sessionId) {
|
|
@@ -84,7 +85,8 @@ const getSession = async (command, resourceId, accessLevelRemoteId, sessionId, m
|
|
|
84
85
|
// This lets us wait until we get a session that's newly created
|
|
85
86
|
continue;
|
|
86
87
|
}
|
|
87
|
-
if (!selectedSession ||
|
|
88
|
+
if (!selectedSession ||
|
|
89
|
+
moment(session.endTime).diff(selectedSession.endTime) > 0) {
|
|
88
90
|
// Select the session with the latest end time
|
|
89
91
|
selectedSession = session;
|
|
90
92
|
}
|
|
@@ -105,13 +107,12 @@ const openBrowserAndPollForSession = async (command, message, resourceId, access
|
|
|
105
107
|
const configData = (0, config_1.getOrCreateConfigData)(command.config.configDir);
|
|
106
108
|
const url = configData[config_1.urlKey];
|
|
107
109
|
setTimeout(() => {
|
|
108
|
-
open(url
|
|
110
|
+
open(`${url}/resources/${resourceId}?showModal=true&refresh=true`);
|
|
109
111
|
}, 2000); // Wait before opening the browser to give the user time to read the message
|
|
112
|
+
// biome-ignore lint/suspicious/noImplicitAnyLet: please fix typing for queries
|
|
110
113
|
let session;
|
|
111
114
|
while (!session) {
|
|
112
|
-
// eslint-disable-next-line no-await-in-loop
|
|
113
115
|
await (0, util_1.sleep)(2000);
|
|
114
|
-
// eslint-disable-next-line no-await-in-loop
|
|
115
116
|
session = await getSession(command, resourceId, accessLevelRemoteId, sessionId, metadataFragment, minCreatedAt);
|
|
116
117
|
}
|
|
117
118
|
return session;
|
|
@@ -119,7 +120,7 @@ const openBrowserAndPollForSession = async (command, message, resourceId, access
|
|
|
119
120
|
const createSession = async (command, resourceId, accessLevel, sessionId, metadataFragment, wantNewSession) => {
|
|
120
121
|
const { resp, error } = await (0, handler_1.runMutation)({
|
|
121
122
|
command: command,
|
|
122
|
-
query: CreateSessionDocument.replace(
|
|
123
|
+
query: CreateSessionDocument.replace("METADATA_FRAGMENT", metadataFragment),
|
|
123
124
|
variables: {
|
|
124
125
|
id: resourceId,
|
|
125
126
|
accessLevel: accessLevel,
|
|
@@ -130,14 +131,14 @@ const createSession = async (command, resourceId, accessLevel, sessionId, metada
|
|
|
130
131
|
return (0, apollo_1.handleError)(command, error);
|
|
131
132
|
}
|
|
132
133
|
switch (resp === null || resp === void 0 ? void 0 : resp.data.createSession.__typename) {
|
|
133
|
-
case
|
|
134
|
+
case "CreateSessionResult": {
|
|
134
135
|
return resp.data.createSession.session;
|
|
135
136
|
}
|
|
136
|
-
case
|
|
137
|
-
return openBrowserAndPollForSession(command,
|
|
137
|
+
case "MfaInvalidError": {
|
|
138
|
+
return openBrowserAndPollForSession(command, "❗ MFA validation needed. Please connect via browser. Opening browser and awaiting validation...", resourceId, accessLevel.accessLevelRemoteId, sessionId, metadataFragment, wantNewSession);
|
|
138
139
|
}
|
|
139
|
-
case
|
|
140
|
-
return openBrowserAndPollForSession(command,
|
|
140
|
+
case "OidcIDTokenNotFoundError": {
|
|
141
|
+
return openBrowserAndPollForSession(command, "❗ OIDC authentication needed. Please connect via browser. Opening browser and awaiting authentication...", resourceId, accessLevel.accessLevelRemoteId, sessionId, metadataFragment, wantNewSession);
|
|
141
142
|
}
|
|
142
143
|
default:
|
|
143
144
|
return (0, apollo_1.handleError)(command, error, resp);
|
|
@@ -152,14 +153,14 @@ const getOrCreateSession = async (command, resourceId, accessLevel, sessionId, m
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
if (sessionId) {
|
|
155
|
-
return (0, apollo_1.handleError)(command,
|
|
156
|
+
return (0, apollo_1.handleError)(command, `Session not found for given id: ${sessionId}`);
|
|
156
157
|
}
|
|
157
158
|
// Create new session
|
|
158
159
|
return createSession(command, resourceId, accessLevel, sessionId, metadataFragment, wantNewSession);
|
|
159
160
|
};
|
|
160
161
|
exports.getOrCreateSession = getOrCreateSession;
|
|
161
162
|
const getSessionExpirationMessage = (session) => {
|
|
162
|
-
const diff = moment(session.endTime).diff(moment(),
|
|
163
|
+
const diff = moment(session.endTime).diff(moment(), "minutes");
|
|
163
164
|
const hours = Math.floor(diff / 60);
|
|
164
165
|
const minutes = diff % 60;
|
|
165
166
|
return `${hours}h ${minutes}m`;
|
package/lib/lib/ssh.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { Command } from
|
|
1
|
+
import type { Command } from "@oclif/core";
|
|
2
2
|
export declare const selectComputeInstance: (command: Command, action: string) => Promise<void | import("./resources").ResourceInfo>;
|
|
3
3
|
export declare const assertSessionManagerPluginExists: () => Promise<boolean>;
|
package/lib/lib/ssh.js
CHANGED
|
@@ -4,18 +4,18 @@ exports.assertSessionManagerPluginExists = exports.selectComputeInstance = void
|
|
|
4
4
|
const cmd_1 = require("../lib/cmd");
|
|
5
5
|
const resources_1 = require("./resources");
|
|
6
6
|
const selectComputeInstance = async (command, action) => {
|
|
7
|
-
return (0, resources_1.promptUserForResource)(command,
|
|
7
|
+
return (0, resources_1.promptUserForResource)(command, "AWS_EC2_INSTANCE", `Select an EC2 instance to ${action}`);
|
|
8
8
|
};
|
|
9
9
|
exports.selectComputeInstance = selectComputeInstance;
|
|
10
10
|
const assertSessionManagerPluginExists = async () => {
|
|
11
|
-
const errorMessage =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let checkPluginCommand =
|
|
15
|
-
if (process.platform ===
|
|
16
|
-
checkPluginCommand =
|
|
11
|
+
const errorMessage = "❗ AWS session manager plugin is required to use SSH functionality. " +
|
|
12
|
+
"Please install it then retry your command. Installation instructions can be found at " +
|
|
13
|
+
"https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html.";
|
|
14
|
+
let checkPluginCommand = "command -v session-manager-plugin";
|
|
15
|
+
if (process.platform === "win32")
|
|
16
|
+
checkPluginCommand = "where session-manager-plugin";
|
|
17
17
|
try {
|
|
18
|
-
const stdOut = await (0, cmd_1.runCommandExecWithCallback)(checkPluginCommand,
|
|
18
|
+
const stdOut = await (0, cmd_1.runCommandExecWithCallback)(checkPluginCommand, (error, stdOut, stdErr) => {
|
|
19
19
|
// Error if there's no output, meaning the plugin is not installed
|
|
20
20
|
if (!stdOut) {
|
|
21
21
|
console.log(errorMessage);
|
package/lib/lib/util.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
export declare const sleep: (ms: number) => Promise<unknown>;
|
|
3
2
|
export declare const copyToClipboard: (content: string) => import("child_process").ChildProcess;
|
|
4
3
|
export declare const displayContent: (content: string) => import("child_process").ChildProcess;
|
package/lib/lib/util.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.displayContent = exports.copyToClipboard = exports.sleep = void 0;
|
|
4
|
-
const
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
5
|
const sleep = (ms) => {
|
|
6
|
-
return new Promise(resolve => {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
7
|
setTimeout(resolve, ms);
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
exports.sleep = sleep;
|
|
11
11
|
const getCopyCommand = () => {
|
|
12
12
|
switch (process.platform) {
|
|
13
|
-
case
|
|
14
|
-
return
|
|
15
|
-
case
|
|
16
|
-
return
|
|
13
|
+
case "darwin":
|
|
14
|
+
return "pbcopy";
|
|
15
|
+
case "win32":
|
|
16
|
+
return "clip";
|
|
17
17
|
default:
|
|
18
|
-
return
|
|
18
|
+
return "xclip -selection clipboard";
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
const copyToClipboard = (content) => {
|
|
22
22
|
const copyCommand = getCopyCommand();
|
|
23
|
-
return (0,
|
|
23
|
+
return (0, node_child_process_1.exec)(`echo "${content}" | ${copyCommand}`);
|
|
24
24
|
};
|
|
25
25
|
exports.copyToClipboard = copyToClipboard;
|
|
26
26
|
const getDisplayCommand = () => {
|
|
27
27
|
switch (process.platform) {
|
|
28
|
-
case
|
|
29
|
-
return
|
|
28
|
+
case "win32":
|
|
29
|
+
return "more";
|
|
30
30
|
default:
|
|
31
|
-
return
|
|
31
|
+
return "less";
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
const displayContent = (content) => {
|
|
35
35
|
const displayCommand = getDisplayCommand();
|
|
36
|
-
return (0,
|
|
37
|
-
stdio:
|
|
36
|
+
return (0, node_child_process_1.spawn)(`cat <<< "${content}" | ${displayCommand}`, {
|
|
37
|
+
stdio: "inherit",
|
|
38
38
|
shell: true,
|
|
39
39
|
});
|
|
40
40
|
};
|