opal-security 2.0.20 → 2.1.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 +24 -26
- package/lib/commands/iam-roles/start.js +30 -125
- package/lib/commands/kube-roles/start.js +23 -122
- package/lib/commands/login.d.ts +2 -1
- package/lib/commands/login.js +32 -23
- package/lib/commands/postgres-instances/start.js +59 -134
- package/lib/commands/resources/get.d.ts +1 -1
- package/lib/commands/resources/get.js +19 -1
- package/lib/commands/set-url.d.ts +6 -3
- package/lib/commands/set-url.js +36 -12
- package/lib/commands/ssh/copyFrom.js +18 -67
- package/lib/commands/ssh/copyTo.js +18 -67
- package/lib/commands/ssh/start.d.ts +1 -0
- package/lib/commands/ssh/start.js +33 -80
- package/lib/handler.d.ts +1 -2
- package/lib/handler.js +0 -27
- package/lib/lib/apollo.d.ts +2 -1
- package/lib/lib/apollo.js +57 -22
- package/lib/lib/aws.js +3 -2
- package/lib/lib/cmd.d.ts +0 -11
- package/lib/lib/cmd.js +3 -15
- package/lib/lib/common.d.ts +4 -3
- package/lib/lib/common.js +33 -15
- package/lib/lib/resources.d.ts +13 -5
- package/lib/lib/resources.js +83 -23
- package/lib/lib/sessions.d.ts +4 -0
- package/lib/lib/sessions.js +154 -0
- package/lib/lib/ssh.d.ts +1 -3
- package/lib/lib/ssh.js +3 -49
- package/lib/types.d.ts +1 -0
- package/oclif.manifest.json +1 -1
- package/package.json +2 -1
package/lib/commands/login.js
CHANGED
|
@@ -9,8 +9,8 @@ const apollo_1 = require("../lib/apollo");
|
|
|
9
9
|
const credentials_1 = require("../lib/credentials");
|
|
10
10
|
const inquirer = require("inquirer");
|
|
11
11
|
const handler_1 = require("../handler");
|
|
12
|
-
const credentials_2 = require("../lib/credentials");
|
|
13
12
|
const config_1 = require("../lib/config");
|
|
13
|
+
const common_1 = require("../lib/common");
|
|
14
14
|
const ISSUER_PROD = 'https://auth.opal.dev';
|
|
15
15
|
const ISSUER_DEV = 'https://authdev.opal.dev';
|
|
16
16
|
const GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
|
|
@@ -45,22 +45,26 @@ class Login extends command_1.Command {
|
|
|
45
45
|
var _a, _b, _c, _d, _e;
|
|
46
46
|
try {
|
|
47
47
|
await apollo_1.initClient(this);
|
|
48
|
-
|
|
49
|
-
let organizationID;
|
|
50
|
-
if (await credentials_2.cred.accessToken) {
|
|
51
|
-
email = await credentials_2.cred.email;
|
|
52
|
-
organizationID = await credentials_2.cred.organizationID;
|
|
53
|
-
await credentials_2.cred.removeCredentials(-1);
|
|
54
|
-
}
|
|
48
|
+
const { flags } = this.parse(Login);
|
|
55
49
|
const configDir = this.config.configDir;
|
|
56
50
|
const configData = config_1.getOrCreateConfigData(configDir);
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
let email = flags.email;
|
|
52
|
+
let organizationID;
|
|
53
|
+
if (await credentials_1.cred.accessToken) {
|
|
54
|
+
// Only use the previous email + organizationID if email isn't explicitly specified.
|
|
55
|
+
if (!email) {
|
|
56
|
+
email = await credentials_1.cred.email;
|
|
57
|
+
organizationID = await credentials_1.cred.organizationID;
|
|
58
|
+
}
|
|
59
|
+
await credentials_1.cred.removeCredentials(-1);
|
|
60
|
+
}
|
|
61
|
+
this.log('Welcome to Opal! ⚡️\n');
|
|
62
|
+
this.log('Please confirm your Opal instance URL:', configData[config_1.urlKey]);
|
|
63
|
+
this.log('If this is not correct, please first use `opal set-url --help`\n');
|
|
64
|
+
if (email) {
|
|
65
|
+
this.log('Signing in as: ' + email + ' - to use a different account, run `opal login --email [EMAIL]`');
|
|
59
66
|
}
|
|
60
67
|
else {
|
|
61
|
-
this.log('Welcome to Opal! ⚡️\n');
|
|
62
|
-
this.log('Please confirm your Opal instance URL:', configData[config_1.urlKey]);
|
|
63
|
-
this.log('If this is not correct, please first use `opal set-url --help`\n');
|
|
64
68
|
const { email: promptEmail } = await inquirer.prompt([{
|
|
65
69
|
name: 'email',
|
|
66
70
|
message: 'Enter your email:',
|
|
@@ -68,6 +72,8 @@ class Login extends command_1.Command {
|
|
|
68
72
|
validate: email => Boolean(email),
|
|
69
73
|
}]);
|
|
70
74
|
email = promptEmail;
|
|
75
|
+
}
|
|
76
|
+
if (!organizationID) {
|
|
71
77
|
const { resp: signInOrganizationsResponse, error } = await handler_1.runQuery({
|
|
72
78
|
command: this,
|
|
73
79
|
query: CLISignInMethodDocument,
|
|
@@ -77,7 +83,7 @@ class Login extends command_1.Command {
|
|
|
77
83
|
this.log('Could not connect to Opal. Did you set the right URL? (`opal set-url --help`)');
|
|
78
84
|
}
|
|
79
85
|
const signInOrganizations = (_b = (_a = signInOrganizationsResponse === null || signInOrganizationsResponse === void 0 ? void 0 : signInOrganizationsResponse.data) === null || _a === void 0 ? void 0 : _a.signInMethod) === null || _b === void 0 ? void 0 : _b.signInOrganizations;
|
|
80
|
-
if (signInOrganizations) {
|
|
86
|
+
if (signInOrganizations && signInOrganizations.length > 0) {
|
|
81
87
|
if (signInOrganizations.length === 1) {
|
|
82
88
|
organizationID = signInOrganizations[0].organizationId;
|
|
83
89
|
}
|
|
@@ -94,6 +100,10 @@ class Login extends command_1.Command {
|
|
|
94
100
|
organizationID = responses.organizationID;
|
|
95
101
|
}
|
|
96
102
|
}
|
|
103
|
+
else {
|
|
104
|
+
// If there are no organizations for the user, require the user to login before failing,
|
|
105
|
+
// which is parity with our web app.
|
|
106
|
+
}
|
|
97
107
|
}
|
|
98
108
|
let issuer;
|
|
99
109
|
let clientId;
|
|
@@ -121,14 +131,9 @@ class Login extends command_1.Command {
|
|
|
121
131
|
});
|
|
122
132
|
this.log('\nYou are being redirected to your browser to authenticate.\n');
|
|
123
133
|
this.log(` User Code: ${handle.user_code}\n`);
|
|
124
|
-
// Wait
|
|
134
|
+
// Wait before opening the browser window to ensure the user has time to
|
|
125
135
|
// see the User Code.
|
|
126
|
-
|
|
127
|
-
return new Promise(resolve => {
|
|
128
|
-
setTimeout(resolve, ms);
|
|
129
|
-
});
|
|
130
|
-
};
|
|
131
|
-
await sleep(1000);
|
|
136
|
+
await common_1.sleep(1000);
|
|
132
137
|
await open(handle.verification_uri_complete, { wait: false });
|
|
133
138
|
const tokenSet = await handle.poll();
|
|
134
139
|
const userInfo = await client.userinfo(tokenSet);
|
|
@@ -141,8 +146,8 @@ class Login extends command_1.Command {
|
|
|
141
146
|
});
|
|
142
147
|
if (authCheckErr || !((_e = (_d = (_c = authCheckResp === null || authCheckResp === void 0 ? void 0 : authCheckResp.data) === null || _c === void 0 ? void 0 : _c.organizationSettings) === null || _d === void 0 ? void 0 : _d.settings) === null || _e === void 0 ? void 0 : _e.id)) {
|
|
143
148
|
this.log('Error verifying log in. Authenticated commands may fail. Please double check your URL and use `opal logout; opal login` to try again.\n');
|
|
144
|
-
|
|
145
|
-
|
|
149
|
+
await credentials_1.cred.removeCredentials(-1);
|
|
150
|
+
apollo_1.handleError(this, authCheckErr);
|
|
146
151
|
}
|
|
147
152
|
this.log('🎉 You have successfully authenticated with Opal! You can now run authenticated commands.\n');
|
|
148
153
|
}
|
|
@@ -156,5 +161,9 @@ Login.description = 'Authenticates you with the Opal server.';
|
|
|
156
161
|
Login.examples = ['$ opal login'];
|
|
157
162
|
Login.flags = {
|
|
158
163
|
help: command_1.flags.help({ char: 'h' }),
|
|
164
|
+
email: command_1.flags.string({
|
|
165
|
+
multiple: false,
|
|
166
|
+
description: 'Email address to login with.',
|
|
167
|
+
}),
|
|
159
168
|
};
|
|
160
169
|
Login.args = [];
|
|
@@ -1,60 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const command_1 = require("@oclif/command");
|
|
4
|
-
const handler_1 = require("../../handler");
|
|
5
4
|
const cmd_1 = require("../../lib/cmd");
|
|
6
5
|
const apollo_1 = require("../../lib/apollo");
|
|
7
6
|
const inquirer = require("inquirer");
|
|
8
7
|
const resources_1 = require("../../lib/resources");
|
|
9
8
|
const common_1 = require("../../lib/common");
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
cursor
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}`;
|
|
23
|
-
const StartPostgresInstanceSessionDocument = `
|
|
24
|
-
mutation StartPostgresInstanceSession($id: ResourceId!, $accessLevel: ResourceAccessLevelInput!, $sessionId: SessionId) {
|
|
25
|
-
createSession(input: {resourceId: $id, accessLevel: $accessLevel, sessionId: $sessionId}) {
|
|
26
|
-
__typename
|
|
27
|
-
... on CreateSessionResult {
|
|
28
|
-
session {
|
|
29
|
-
id
|
|
30
|
-
endTime
|
|
31
|
-
metadata {
|
|
32
|
-
... on AwsIamFederatedRdsSession {
|
|
33
|
-
dbUser
|
|
34
|
-
dbPassword
|
|
35
|
-
dbHostname
|
|
36
|
-
dbPort
|
|
37
|
-
dbName
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
... on SessionNotFoundError {
|
|
43
|
-
message
|
|
44
|
-
}
|
|
45
|
-
... on MfaInvalidError {
|
|
46
|
-
message
|
|
47
|
-
}
|
|
48
|
-
... on OidcIDTokenNotFoundError {
|
|
49
|
-
message
|
|
50
|
-
}
|
|
51
|
-
... on ResourceNotFoundError {
|
|
52
|
-
message
|
|
53
|
-
}
|
|
54
|
-
... on EndSystemAuthorizationError {
|
|
55
|
-
message
|
|
56
|
-
}
|
|
57
|
-
}
|
|
9
|
+
const sessions_1 = require("../../lib/sessions");
|
|
10
|
+
const RdsSessionMetadataFragment = `
|
|
11
|
+
... on AwsIamFederatedRdsSession {
|
|
12
|
+
dbUser
|
|
13
|
+
dbPassword
|
|
14
|
+
dbHostname
|
|
15
|
+
dbPort
|
|
16
|
+
dbName
|
|
58
17
|
}`;
|
|
59
18
|
class StartPostgresInstanceSession extends command_1.Command {
|
|
60
19
|
async run() {
|
|
@@ -63,110 +22,76 @@ class StartPostgresInstanceSession extends command_1.Command {
|
|
|
63
22
|
let instanceId = flags.id;
|
|
64
23
|
let instanceName = null;
|
|
65
24
|
const sessionId = flags.sessionId;
|
|
66
|
-
// TODO: RESOURCES-1: How do we grant access to a perm using ID
|
|
67
25
|
if (!instanceId) {
|
|
68
|
-
const
|
|
69
|
-
command: this,
|
|
70
|
-
query: ListPostgresInstancesDocument,
|
|
71
|
-
variables: {},
|
|
72
|
-
});
|
|
73
|
-
if (error) {
|
|
74
|
-
apollo_1.printRequestOutput(this, postgresInstancesResp, error);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const resourceInfos = postgresInstancesResp === null || postgresInstancesResp === void 0 ? void 0 : postgresInstancesResp.data.resources.resources.map((resource) => {
|
|
78
|
-
return {
|
|
79
|
-
id: resource.id,
|
|
80
|
-
name: resource.name,
|
|
81
|
-
};
|
|
82
|
-
});
|
|
83
|
-
const noResourcesFound = resources_1.resourcesAreEmpty(this, resourceInfos);
|
|
84
|
-
if (noResourcesFound) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const resourceInfoByName = {};
|
|
88
|
-
resourceInfos.forEach(resourceInfo => {
|
|
89
|
-
resourceInfoByName[resourceInfo.name] = resourceInfo;
|
|
90
|
-
});
|
|
91
|
-
inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt'));
|
|
92
|
-
const selectedInstanceInfo = await inquirer.prompt([
|
|
93
|
-
{
|
|
94
|
-
name: 'instance',
|
|
95
|
-
message: 'Select a Postgres RDS instance to login to',
|
|
96
|
-
type: 'autocomplete',
|
|
97
|
-
source: (answers, input) => cmd_1.filterChoices(input, resourceInfos),
|
|
98
|
-
},
|
|
99
|
-
]);
|
|
100
|
-
const selectedInstance = resourceInfoByName[selectedInstanceInfo.instance];
|
|
26
|
+
const selectedInstance = await resources_1.promptUserForResource(this, 'AWS_RDS_POSTGRES_INSTANCE', 'Select an RDS Postgres instance to login to');
|
|
101
27
|
if (!selectedInstance) {
|
|
102
28
|
return;
|
|
103
29
|
}
|
|
104
30
|
instanceId = selectedInstance.id;
|
|
105
31
|
instanceName = selectedInstance.name;
|
|
106
|
-
// TODO: RESOURCES-3: Select the access level for the DB
|
|
107
32
|
}
|
|
108
|
-
// Fetch all access levels for resource
|
|
109
33
|
const accessLevel = await resources_1.promptUserForAccessLevels(this, instanceId, 'Postgres database', flags.accessLevelRemoteId);
|
|
110
34
|
if (!accessLevel) {
|
|
111
35
|
return;
|
|
112
36
|
}
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const selectedLaunchInfo = await inquirer.prompt([
|
|
37
|
+
const session = await sessions_1.getOrCreateSession(this, instanceId, accessLevel, sessionId, RdsSessionMetadataFragment);
|
|
38
|
+
const metadata = session.metadata;
|
|
39
|
+
switch (metadata === null || metadata === void 0 ? void 0 : metadata.__typename) {
|
|
40
|
+
case 'AwsIamFederatedRdsSession': {
|
|
41
|
+
// Don't inform the user about RDS session expiration time, since RDS works differently.
|
|
42
|
+
// Opal RDS sessions expire after 15min because after that time they can no longer be used to connect.
|
|
43
|
+
// However, once connected, RDS sessions can be used for up to 12h.
|
|
44
|
+
// Since there's many options for how the user can use RDS credentials, it's unclear which expiration
|
|
45
|
+
// we want to show the user.
|
|
46
|
+
const externalAppLaunchName = 'Launch external database app';
|
|
47
|
+
const psqlSessionLaunchName = 'Launch shell with psql session';
|
|
48
|
+
const viewDetailsName = 'View connection configuration';
|
|
49
|
+
const selectedLaunchInfo = await inquirer.prompt([{
|
|
50
|
+
name: 'launch',
|
|
51
|
+
message: 'Select how to access the database',
|
|
52
|
+
type: 'list',
|
|
53
|
+
choices: [
|
|
131
54
|
{
|
|
132
|
-
name:
|
|
133
|
-
message: 'Select how to access the database',
|
|
134
|
-
type: 'list',
|
|
135
|
-
choices: [
|
|
136
|
-
{
|
|
137
|
-
name: externalAppLaunchName,
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
name: psqlSessionLaunchName,
|
|
141
|
-
},
|
|
142
|
-
],
|
|
55
|
+
name: externalAppLaunchName,
|
|
143
56
|
},
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
57
|
+
{
|
|
58
|
+
name: psqlSessionLaunchName,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: viewDetailsName,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
}]);
|
|
65
|
+
const dbUrl = `postgresql://${metadata.dbUser}:${encodeURIComponent(metadata.dbPassword)}@${metadata.dbHostname}:${metadata.dbPort}/${metadata.dbName}`;
|
|
66
|
+
switch (selectedLaunchInfo.launch) {
|
|
67
|
+
case externalAppLaunchName: {
|
|
68
|
+
const startSessionCmd = `open ${dbUrl}`;
|
|
69
|
+
cmd_1.runCommandExec(startSessionCmd, `Opened external app for ${instanceName ? `"${instanceName}" instance` : 'instance'}`, `Failed to open external app for ${instanceName ? `"${instanceName}" instance` : 'instance'}`);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case viewDetailsName: {
|
|
73
|
+
const contentParts = [
|
|
74
|
+
`[Connection URL]\n${dbUrl}`,
|
|
75
|
+
`[Hostname]\n${metadata.dbHostname}`,
|
|
76
|
+
`[Port]\n${metadata.dbPort}`,
|
|
77
|
+
`[User]\n${metadata.dbUser}`,
|
|
78
|
+
`[Password]\n${metadata.dbPassword}`,
|
|
79
|
+
`[Database]\n${metadata.dbName}`,
|
|
80
|
+
];
|
|
81
|
+
const content = contentParts.join('\n\n');
|
|
82
|
+
common_1.displayContent(content);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case psqlSessionLaunchName: {
|
|
86
|
+
const startSessionCmd = `psql ${dbUrl}`;
|
|
87
|
+
cmd_1.startInteractiveShell(startSessionCmd, `shell with psql session for ${instanceName ? `"${instanceName}" instance` : 'instance'}`);
|
|
153
88
|
break;
|
|
154
89
|
}
|
|
155
|
-
default:
|
|
156
|
-
apollo_1.printRequestOutput(this, resp, error);
|
|
157
90
|
}
|
|
158
91
|
break;
|
|
159
92
|
}
|
|
160
|
-
case 'MfaInvalidError': {
|
|
161
|
-
common_1.handleMfaRedirect(this, instanceId);
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
case 'OidcIDTokenNotFoundError': {
|
|
165
|
-
common_1.handleOidcRedirect(this, instanceId);
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
93
|
default:
|
|
169
|
-
apollo_1.
|
|
94
|
+
return apollo_1.handleError(this, undefined, session);
|
|
170
95
|
}
|
|
171
96
|
}
|
|
172
97
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, flags } from '@oclif/command';
|
|
2
|
-
export declare const GetResourceDocument = "\nquery GetResource($id: ResourceId!) {\n resource(input: {id: $id}) {\n __typename\n ... on ResourceResult {\n resource {\n name\n id\n description\n connection {\n name\n id\n }\n resourceUsers {\n user {\n email\n }\n }\n }\n }\n ... on ResourceNotFoundError {\n message\n }\n }\n}";
|
|
2
|
+
export declare const GetResourceDocument = "\nquery GetResource($id: ResourceId!) {\n resource(input: {id: $id}) {\n __typename\n ... on ResourceResult {\n resource {\n name\n id\n description\n resourceType\n connection {\n name\n id\n connectionType\n }\n parentResource {\n name\n id\n resourceType\n }\n resourceUsers {\n user {\n fullName\n email\n id\n }\n }\n }\n }\n ... on ResourceNotFoundError {\n message\n }\n }\n}";
|
|
3
3
|
export default class GetResource extends Command {
|
|
4
4
|
static description: string;
|
|
5
5
|
static examples: string[];
|
|
@@ -4,6 +4,7 @@ exports.GetResourceDocument = void 0;
|
|
|
4
4
|
const command_1 = require("@oclif/command");
|
|
5
5
|
const handler_1 = require("../../handler");
|
|
6
6
|
const cmd_1 = require("../../lib/cmd");
|
|
7
|
+
const apollo_1 = require("../../lib/apollo");
|
|
7
8
|
exports.GetResourceDocument = `
|
|
8
9
|
query GetResource($id: ResourceId!) {
|
|
9
10
|
resource(input: {id: $id}) {
|
|
@@ -13,13 +14,22 @@ query GetResource($id: ResourceId!) {
|
|
|
13
14
|
name
|
|
14
15
|
id
|
|
15
16
|
description
|
|
17
|
+
resourceType
|
|
16
18
|
connection {
|
|
17
19
|
name
|
|
18
20
|
id
|
|
21
|
+
connectionType
|
|
22
|
+
}
|
|
23
|
+
parentResource {
|
|
24
|
+
name
|
|
25
|
+
id
|
|
26
|
+
resourceType
|
|
19
27
|
}
|
|
20
28
|
resourceUsers {
|
|
21
29
|
user {
|
|
30
|
+
fullName
|
|
22
31
|
email
|
|
32
|
+
id
|
|
23
33
|
}
|
|
24
34
|
}
|
|
25
35
|
}
|
|
@@ -33,7 +43,15 @@ class GetResource extends command_1.Command {
|
|
|
33
43
|
async run() {
|
|
34
44
|
cmd_1.setMostRecentCommand(this);
|
|
35
45
|
const { flags } = this.parse(GetResource);
|
|
36
|
-
|
|
46
|
+
const { resp, error } = await handler_1.runQuery({
|
|
47
|
+
command: this,
|
|
48
|
+
query: exports.GetResourceDocument,
|
|
49
|
+
variables: flags,
|
|
50
|
+
});
|
|
51
|
+
if (error) {
|
|
52
|
+
return apollo_1.handleError(this, error, resp);
|
|
53
|
+
}
|
|
54
|
+
apollo_1.printResponse(this, resp);
|
|
37
55
|
}
|
|
38
56
|
}
|
|
39
57
|
exports.default = GetResource;
|
|
@@ -4,14 +4,17 @@ export default class SetUrl extends Command {
|
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
6
|
help: import("@oclif/parser/lib/flags").IBooleanFlag<void>;
|
|
7
|
-
custom: flags.IOptionFlag<string | undefined>;
|
|
8
7
|
allowSelfSignedCerts: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
|
|
8
|
+
custom: flags.IOptionFlag<string | undefined>;
|
|
9
9
|
prod: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
|
|
10
|
-
staging: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
|
|
11
10
|
demo: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
|
|
12
11
|
dev: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
|
|
13
12
|
devLocal: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
|
|
14
13
|
};
|
|
15
|
-
static args:
|
|
14
|
+
static args: {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
required: boolean;
|
|
18
|
+
}[];
|
|
16
19
|
run(): Promise<void>;
|
|
17
20
|
}
|
package/lib/commands/set-url.js
CHANGED
|
@@ -7,17 +7,17 @@ const credentials_1 = require("../lib/credentials");
|
|
|
7
7
|
class SetUrl extends command_1.Command {
|
|
8
8
|
async run() {
|
|
9
9
|
try {
|
|
10
|
-
const { flags } = this.parse(SetUrl);
|
|
10
|
+
const { flags, args } = this.parse(SetUrl);
|
|
11
11
|
let url = config_1.defaultUrl;
|
|
12
|
-
if (
|
|
12
|
+
if (args.url) {
|
|
13
|
+
url = args.url;
|
|
14
|
+
}
|
|
15
|
+
else if (flags.custom) {
|
|
13
16
|
url = flags.custom;
|
|
14
17
|
}
|
|
15
18
|
else if (flags.prod) {
|
|
16
19
|
url = 'https://app.opal.dev';
|
|
17
20
|
}
|
|
18
|
-
else if (flags.staging) {
|
|
19
|
-
url = 'https://staging.opal.dev';
|
|
20
|
-
}
|
|
21
21
|
else if (flags.demo) {
|
|
22
22
|
url = 'https://demo.opal.dev';
|
|
23
23
|
}
|
|
@@ -27,6 +27,23 @@ class SetUrl extends command_1.Command {
|
|
|
27
27
|
else if (flags.devLocal) {
|
|
28
28
|
url = 'http://localhost:3000';
|
|
29
29
|
}
|
|
30
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
31
|
+
// Add protocol if not specified
|
|
32
|
+
if (url.startsWith('localhost')) {
|
|
33
|
+
url = 'http://' + url;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
url = 'https://' + url;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (url.match(/^https?:\/\/[^/]+\/$/)) {
|
|
40
|
+
// URL matches `https://x.y.z/`, so trim trailing slash
|
|
41
|
+
url = url.slice(0, -1);
|
|
42
|
+
}
|
|
43
|
+
else if (!url.match(/^https?:\/\/[^/]+$/)) {
|
|
44
|
+
// Error if URL doesn't match `https://x.y.z` or `http://x.y.z`
|
|
45
|
+
throw new Error('Invalid URL. Please provide only the protocol and domain (e.g. https://app.opal.dev).');
|
|
46
|
+
}
|
|
30
47
|
const configData = config_1.getOrCreateConfigData(this.config.configDir);
|
|
31
48
|
configData[config_1.urlKey] = url;
|
|
32
49
|
configData[config_1.allowSelfSignedCertsKey] = flags.allowSelfSignedCerts !== undefined;
|
|
@@ -46,14 +63,21 @@ SetUrl.description = `Sets the url of the Opal server. Defaults to ${config_1.de
|
|
|
46
63
|
SetUrl.examples = ['$ opal set-url'];
|
|
47
64
|
SetUrl.flags = {
|
|
48
65
|
help: command_1.flags.help({ char: 'h' }),
|
|
66
|
+
allowSelfSignedCerts: command_1.flags.boolean(),
|
|
67
|
+
// Legacy flags
|
|
49
68
|
custom: command_1.flags.string({
|
|
50
69
|
multiple: false,
|
|
70
|
+
hidden: true,
|
|
51
71
|
}),
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
dev: command_1.flags.boolean(),
|
|
57
|
-
devLocal: command_1.flags.boolean(),
|
|
72
|
+
prod: command_1.flags.boolean({ hidden: true }),
|
|
73
|
+
demo: command_1.flags.boolean({ hidden: true }),
|
|
74
|
+
dev: command_1.flags.boolean({ hidden: true }),
|
|
75
|
+
devLocal: command_1.flags.boolean({ hidden: true }),
|
|
58
76
|
};
|
|
59
|
-
SetUrl.args = [
|
|
77
|
+
SetUrl.args = [
|
|
78
|
+
{
|
|
79
|
+
name: 'url',
|
|
80
|
+
description: 'URL of the Opal server to use. If unspecified, defaults to https://app.opal.dev',
|
|
81
|
+
required: false,
|
|
82
|
+
},
|
|
83
|
+
];
|
|
@@ -1,46 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const command_1 = require("@oclif/command");
|
|
4
|
-
const handler_1 = require("../../handler");
|
|
5
4
|
const apollo_1 = require("../../lib/apollo");
|
|
6
5
|
const cmd_1 = require("../../lib/cmd");
|
|
7
6
|
const ssh_1 = require("../../lib/ssh");
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
createSession(input: {resourceId: $id, accessLevel: $accessLevel, sessionId: $sessionId}) {
|
|
12
|
-
__typename
|
|
13
|
-
... on CreateSessionResult {
|
|
14
|
-
session {
|
|
15
|
-
id
|
|
16
|
-
endTime
|
|
17
|
-
metadata {
|
|
18
|
-
... on AwsIamFederatedSSMSession {
|
|
19
|
-
awsAccessKeyId
|
|
20
|
-
awsSecretAccessKey
|
|
21
|
-
awsSessionToken
|
|
22
|
-
awsLoginUrl
|
|
23
|
-
federatedArn
|
|
24
|
-
ec2InstanceId
|
|
25
|
-
ec2Region
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
... on SessionNotFoundError {
|
|
31
|
-
message
|
|
32
|
-
}
|
|
33
|
-
... on MfaInvalidError {
|
|
34
|
-
message
|
|
35
|
-
}
|
|
36
|
-
... on ResourceNotFoundError {
|
|
37
|
-
message
|
|
38
|
-
}
|
|
39
|
-
... on EndSystemAuthorizationError {
|
|
40
|
-
message
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}`;
|
|
7
|
+
const resources_1 = require("../../lib/resources");
|
|
8
|
+
const sessions_1 = require("../../lib/sessions");
|
|
9
|
+
const start_1 = require("./start");
|
|
44
10
|
class StartSCPSession extends command_1.Command {
|
|
45
11
|
async run() {
|
|
46
12
|
cmd_1.setMostRecentCommand(this);
|
|
@@ -60,38 +26,23 @@ class StartSCPSession extends command_1.Command {
|
|
|
60
26
|
instanceId = selectedInstance.id;
|
|
61
27
|
instanceName = selectedInstance.name;
|
|
62
28
|
}
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
AWS_SESSION_TOKEN: metadata.awsSessionToken,
|
|
77
|
-
};
|
|
78
|
-
// Run SCP script
|
|
79
|
-
const scpCmd = `$SCRIPT_PATH/../../scripts/ssh_ssm_scp_from_server.sh ${metadata.ec2InstanceId} ${flags.user} ${flags.src} ${metadata.ec2Region} ${flags.dest}`;
|
|
80
|
-
const outputMessage = `from "${flags.src}" on ${instanceName ? `"${instanceName}" instance` : 'instance'} to "${flags.dest}" locally.`;
|
|
81
|
-
cmd_1.runCommandSpawn(scpCmd, `Copied ${outputMessage}`, `Failed to copy ${outputMessage}`, envVars);
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
default:
|
|
85
|
-
apollo_1.printRequestOutput(this, resp, error);
|
|
86
|
-
}
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
case 'MfaInvalidError': {
|
|
90
|
-
common_1.handleMfaRedirect(this, instanceId);
|
|
29
|
+
const session = await sessions_1.getOrCreateSession(this, instanceId, resources_1.DEFAULT_ACCESS_LEVEL, sessionId, start_1.Ec2SessionMetadataFragment);
|
|
30
|
+
const metadata = session.metadata;
|
|
31
|
+
switch (metadata === null || metadata === void 0 ? void 0 : metadata.__typename) {
|
|
32
|
+
case 'AwsIamFederatedSSMSession': {
|
|
33
|
+
const envVars = {
|
|
34
|
+
AWS_ACCESS_KEY_ID: metadata.awsAccessKeyId,
|
|
35
|
+
AWS_SECRET_ACCESS_KEY: metadata.awsSecretAccessKey,
|
|
36
|
+
AWS_SESSION_TOKEN: metadata.awsSessionToken,
|
|
37
|
+
};
|
|
38
|
+
// Run SCP script
|
|
39
|
+
const scpCmd = `$SCRIPT_PATH/../../scripts/ssh_ssm_scp_from_server.sh ${metadata.ec2InstanceId} ${flags.user} ${flags.src} ${metadata.ec2Region} ${flags.dest}`;
|
|
40
|
+
const outputMessage = `from "${flags.src}" on ${instanceName ? `"${instanceName}" instance` : 'instance'} to "${flags.dest}" locally.`;
|
|
41
|
+
cmd_1.runCommandSpawn(scpCmd, `Copied ${outputMessage}`, `Failed to copy ${outputMessage}`, envVars);
|
|
91
42
|
break;
|
|
92
43
|
}
|
|
93
44
|
default:
|
|
94
|
-
apollo_1.
|
|
45
|
+
return apollo_1.handleError(this, undefined, session);
|
|
95
46
|
}
|
|
96
47
|
}
|
|
97
48
|
}
|
|
@@ -118,7 +69,7 @@ StartSCPSession.flags = {
|
|
|
118
69
|
multiple: false,
|
|
119
70
|
required: false,
|
|
120
71
|
default: 'ssm-user',
|
|
121
|
-
description:
|
|
72
|
+
description: 'Pick which user you want to run SCP over. Keep in mind not all users will have access to each other\'s home directory.',
|
|
122
73
|
}),
|
|
123
74
|
id: command_1.flags.string({
|
|
124
75
|
multiple: false,
|