opal-security 2.0.9 → 2.0.12

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
@@ -22,7 +22,7 @@ $ npm install -g opal-security
22
22
  $ opal COMMAND
23
23
  running command...
24
24
  $ opal (-v|--version|version)
25
- opal-security/2.0.9 darwin-x64 node-v14.16.1
25
+ opal-security/2.0.12 darwin-x64 node-v14.16.1
26
26
  $ opal --help [COMMAND]
27
27
  USAGE
28
28
  $ opal COMMAND
@@ -86,7 +86,7 @@ EXAMPLE
86
86
  opal aws:identity
87
87
  ```
88
88
 
89
- _See code: [src/commands/aws/identity.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/aws/identity.ts)_
89
+ _See code: [src/commands/aws/identity.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/aws/identity.ts)_
90
90
 
91
91
  ## `opal curl-example`
92
92
 
@@ -100,7 +100,7 @@ OPTIONS
100
100
  -h, --help show CLI help
101
101
  ```
102
102
 
103
- _See code: [src/commands/curl-example.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/curl-example.ts)_
103
+ _See code: [src/commands/curl-example.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/curl-example.ts)_
104
104
 
105
105
  ## `opal help [COMMAND]`
106
106
 
@@ -136,7 +136,7 @@ EXAMPLES
136
136
  opal iam-roles:start --id 51f7176b-0464-4a6f-8369-e951e187b398
137
137
  ```
138
138
 
139
- _See code: [src/commands/iam-roles/start.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/iam-roles/start.ts)_
139
+ _See code: [src/commands/iam-roles/start.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/iam-roles/start.ts)_
140
140
 
141
141
  ## `opal kube-roles:start`
142
142
 
@@ -158,7 +158,7 @@ EXAMPLES
158
158
  "arn:aws:iam::712234975475:role/acme-eks-cluster-admin-role"
159
159
  ```
160
160
 
161
- _See code: [src/commands/kube-roles/start.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/kube-roles/start.ts)_
161
+ _See code: [src/commands/kube-roles/start.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/kube-roles/start.ts)_
162
162
 
163
163
  ## `opal login`
164
164
 
@@ -175,7 +175,7 @@ EXAMPLE
175
175
  $ opal login
176
176
  ```
177
177
 
178
- _See code: [src/commands/login.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/login.ts)_
178
+ _See code: [src/commands/login.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/login.ts)_
179
179
 
180
180
  ## `opal logout`
181
181
 
@@ -192,7 +192,7 @@ EXAMPLE
192
192
  $ opal logout
193
193
  ```
194
194
 
195
- _See code: [src/commands/logout.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/logout.ts)_
195
+ _See code: [src/commands/logout.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/logout.ts)_
196
196
 
197
197
  ## `opal postgres-instances:start`
198
198
 
@@ -213,7 +213,7 @@ EXAMPLES
213
213
  opal postgres-instances:start --id 51f7176b-0464-4a6f-8369-e951e187b398 --accessLevelRemoteId "fullaccess"
214
214
  ```
215
215
 
216
- _See code: [src/commands/postgres-instances/start.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/postgres-instances/start.ts)_
216
+ _See code: [src/commands/postgres-instances/start.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/postgres-instances/start.ts)_
217
217
 
218
218
  ## `opal resources:get`
219
219
 
@@ -231,7 +231,7 @@ EXAMPLE
231
231
  opal resources:get --id 54052a3e-5375-4392-aeaf-0c6c44c131d4
232
232
  ```
233
233
 
234
- _See code: [src/commands/resources/get.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/resources/get.ts)_
234
+ _See code: [src/commands/resources/get.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/resources/get.ts)_
235
235
 
236
236
  ## `opal set-url`
237
237
 
@@ -249,12 +249,13 @@ OPTIONS
249
249
  --dev
250
250
  --devLocal
251
251
  --prod
252
+ --staging
252
253
 
253
254
  EXAMPLE
254
255
  $ opal set-host
255
256
  ```
256
257
 
257
- _See code: [src/commands/set-url.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/set-url.ts)_
258
+ _See code: [src/commands/set-url.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/set-url.ts)_
258
259
 
259
260
  ## `opal ssh:copyFrom`
260
261
 
@@ -280,7 +281,7 @@ EXAMPLES
280
281
  opal ssh:copyFrom --src instance/dir --dest my/dir --id 51f7176b-0464-4a6f-8369-e951e187b398
281
282
  ```
282
283
 
283
- _See code: [src/commands/ssh/copyFrom.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/ssh/copyFrom.ts)_
284
+ _See code: [src/commands/ssh/copyFrom.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/ssh/copyFrom.ts)_
284
285
 
285
286
  ## `opal ssh:copyTo`
286
287
 
@@ -306,7 +307,7 @@ EXAMPLES
306
307
  opal ssh:copyTo --src my/dir --dest instance/dir --id 51f7176b-0464-4a6f-8369-e951e187b398
307
308
  ```
308
309
 
309
- _See code: [src/commands/ssh/copyTo.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/ssh/copyTo.ts)_
310
+ _See code: [src/commands/ssh/copyTo.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/ssh/copyTo.ts)_
310
311
 
311
312
  ## `opal ssh:start`
312
313
 
@@ -325,5 +326,5 @@ EXAMPLES
325
326
  opal ssh:start --id 51f7176b-0464-4a6f-8369-e951e187b398
326
327
  ```
327
328
 
328
- _See code: [src/commands/ssh/start.ts](https://github.com/cli/opal/blob/v2.0.9/src/commands/ssh/start.ts)_
329
+ _See code: [src/commands/ssh/start.ts](https://github.com/opalsecurity/opal-cli/blob/v2.0.12/src/commands/ssh/start.ts)_
329
330
  <!-- commandsstop -->
@@ -6,13 +6,15 @@ const credentials_1 = require("../lib/credentials");
6
6
  class CurlExample extends command_1.Command {
7
7
  async run() {
8
8
  const accessToken = await credentials_1.cred.accessToken;
9
+ const organizationID = await credentials_1.cred.organizationID;
9
10
  const configData = config_1.getOrCreateConfigData(this.config.configDir);
10
11
  const url = configData[config_1.urlKey];
11
12
  this.log(`
12
13
  curl -v ${url}/query \\
13
14
  --data-binary '{"query":"query ListSSHSessions {resources(input: {serviceType: SSH, onlyMine: true}) {... on ResourcesResult { resources { name } } } }"}' \\
14
15
  --header "Content-Type: application/json" \\
15
- --header "Authorization: Bearer ${accessToken}"
16
+ --header "Authorization: Bearer ${accessToken}" \\
17
+ --header "X-Opal-Organization-ID: ${organizationID}"
16
18
  `);
17
19
  }
18
20
  }
@@ -6,16 +6,106 @@ const open = require("open");
6
6
  const openid_client_1 = require("openid-client");
7
7
  const apollo_1 = require("../lib/apollo");
8
8
  const credentials_1 = require("../lib/credentials");
9
- const ISSUER = 'https://auth.opal.dev';
9
+ const inquirer = require("inquirer");
10
+ const handler_1 = require("../handler");
11
+ const credentials_2 = require("../lib/credentials");
12
+ const config_1 = require("../lib/config");
13
+ const ISSUER_PROD = 'https://auth.opal.dev';
14
+ const ISSUER_DEV = 'https://authdev.opal.dev';
10
15
  const GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
11
- const CLIENT_ID = '42rm6E5v7o67LBpRfjdT9KhnjrQHr9UF';
16
+ const CLIENT_ID_PROD = '42rm6E5v7o67LBpRfjdT9KhnjrQHr9UF';
17
+ const CLIENT_ID_DEV = 'XYV8qoAvZG7dHnhRp2g5XMJ1zX9fBP6s';
18
+ const CLISignInMethodDocument = `
19
+ query CLISignInMethod($input: SignInMethodInput!) {
20
+ signInMethod(input: $input) {
21
+ __typename
22
+ ... on SignInMethodResult {
23
+ signInOrganizations {
24
+ organizationId
25
+ organizationName
26
+ }
27
+ }
28
+ }
29
+ }`;
30
+ const CLIAuthSessionCheckDocument = `
31
+ query CLIAuthSessionCheck {
32
+ organizationSettings {
33
+ ... on OrganizationSettingsResult {
34
+ settings {
35
+ id
36
+ }
37
+ }
38
+ }
39
+ }
40
+ `;
12
41
  class Login extends command_1.Command {
13
42
  async run() {
43
+ var _a, _b, _c, _d, _e;
14
44
  try {
15
- const issuer = await openid_client_1.Issuer.discover(ISSUER);
45
+ await apollo_1.initClient(this);
46
+ let email;
47
+ let organizationID;
48
+ if (await credentials_2.cred.accessToken) {
49
+ email = await credentials_2.cred.email;
50
+ organizationID = await credentials_2.cred.organizationID;
51
+ await credentials_2.cred.removeCredentials(-1);
52
+ }
53
+ const configDir = this.config.configDir;
54
+ const configData = config_1.getOrCreateConfigData(configDir);
55
+ if (email && organizationID) {
56
+ console.log('Signing in as ' + email + ' - to use a different account, run `opal logout`');
57
+ }
58
+ else {
59
+ this.log('Welcome to Opal! ⚡️\n');
60
+ this.log('Please confirm your Opal instance URL:', configData[config_1.urlKey]);
61
+ this.log('If this is not correct, please first use `opal set-url --help`\n');
62
+ const { email: promptEmail } = await inquirer.prompt([{
63
+ name: 'email',
64
+ message: 'Enter your email:',
65
+ type: 'input',
66
+ validate: email => Boolean(email),
67
+ }]);
68
+ email = promptEmail;
69
+ const { resp: signInOrganizationsResponse, error } = await handler_1.runQuery({
70
+ command: this,
71
+ query: CLISignInMethodDocument,
72
+ variables: { input: { email } },
73
+ });
74
+ if (error) {
75
+ this.log('Could not connect to Opal. Did you set the right URL? (`opal set-url --help`)');
76
+ }
77
+ 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;
78
+ if (signInOrganizations) {
79
+ if (signInOrganizations.length === 1) {
80
+ organizationID = signInOrganizations[0].organizationId;
81
+ }
82
+ else {
83
+ const responses = await inquirer.prompt([{
84
+ name: 'organizationID',
85
+ message: 'Select an organization:',
86
+ type: 'list',
87
+ choices: signInOrganizations.map(signInOrganization => ({
88
+ name: signInOrganization.organizationName,
89
+ value: signInOrganization.organizationId,
90
+ })),
91
+ }]);
92
+ organizationID = responses.organizationID;
93
+ }
94
+ }
95
+ }
96
+ let issuer;
97
+ let clientId;
98
+ if (config_1.isProduction(this.config.configDir)) {
99
+ issuer = await openid_client_1.Issuer.discover(ISSUER_PROD);
100
+ clientId = CLIENT_ID_PROD;
101
+ }
102
+ else {
103
+ issuer = await openid_client_1.Issuer.discover(ISSUER_DEV);
104
+ clientId = CLIENT_ID_DEV;
105
+ }
16
106
  const client = new issuer.Client({
17
107
  grant_types: [GRANT_TYPE],
18
- client_id: CLIENT_ID,
108
+ client_id: clientId,
19
109
  response_types: [],
20
110
  redirect_uris: [],
21
111
  token_endpoint_auth_method: 'none',
@@ -25,13 +115,29 @@ class Login extends command_1.Command {
25
115
  audience: 'https://opal.dev',
26
116
  scope: 'openid email profile',
27
117
  });
28
- this.log('You are being redirected to your browser to authenticate.\n');
118
+ this.log('\nYou are being redirected to your browser to authenticate.\n');
29
119
  this.log(` User Code: ${handle.user_code}\n`);
120
+ // Wait 2 seconds to show the user code clearly
121
+ const sleep = (ms) => {
122
+ return new Promise(resolve => {
123
+ setTimeout(resolve, ms);
124
+ });
125
+ };
126
+ await sleep(2000);
30
127
  await open(handle.verification_uri_complete, { wait: false });
31
128
  const tokenSet = await handle.poll();
32
129
  const userInfo = await client.userinfo(tokenSet);
33
- await keytar.setPassword(credentials_1.OPAL_CREDS_KEY, userInfo.email || 'unset-email', (tokenSet === null || tokenSet === void 0 ? void 0 : tokenSet.access_token) || '');
34
- await apollo_1.initClient(this);
130
+ await keytar.setPassword(credentials_1.OPAL_CREDS_KEY, (userInfo.email || 'unset-email') + '|' + organizationID, (tokenSet === null || tokenSet === void 0 ? void 0 : tokenSet.access_token) || '');
131
+ // "Representative" authenticated call to check the log-in worked as expected.
132
+ const { resp: authCheckResp, error: authCheckErr } = await handler_1.runQuery({
133
+ command: this,
134
+ query: CLIAuthSessionCheckDocument,
135
+ variables: {},
136
+ });
137
+ 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)) {
138
+ 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');
139
+ return;
140
+ }
35
141
  this.log('🎉 You have successfully authenticated with Opal! You can now run authenticated commands.\n');
36
142
  }
37
143
  catch (error) {
@@ -7,6 +7,7 @@ export default class SetUrl extends Command {
7
7
  custom: flags.IOptionFlag<string | undefined>;
8
8
  allowSelfSignedCerts: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
9
9
  prod: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
10
+ staging: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
10
11
  demo: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
11
12
  dev: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
12
13
  devLocal: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
@@ -14,6 +14,9 @@ class SetUrl extends command_1.Command {
14
14
  else if (flags.prod) {
15
15
  url = 'https://app.opal.dev';
16
16
  }
17
+ else if (flags.staging) {
18
+ url = 'https://staging.opal.dev';
19
+ }
17
20
  else if (flags.demo) {
18
21
  url = 'https://demo.opal.dev';
19
22
  }
@@ -46,6 +49,7 @@ SetUrl.flags = {
46
49
  }),
47
50
  allowSelfSignedCerts: command_1.flags.boolean(),
48
51
  prod: command_1.flags.boolean(),
52
+ staging: command_1.flags.boolean(),
49
53
  demo: command_1.flags.boolean(),
50
54
  dev: command_1.flags.boolean(),
51
55
  devLocal: command_1.flags.boolean(),
package/lib/handler.d.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import { Command } from '@oclif/command';
2
- interface QueryHandlerProps {
2
+ interface QueryHandlerProps<TVar = Record<string, any>> {
3
3
  command: Command;
4
4
  query: string;
5
- variables?: Record<string, any>;
5
+ variables?: TVar;
6
6
  }
7
7
  export declare const runMutation: ({ command, query, variables, }: QueryHandlerProps) => Promise<{
8
8
  resp: import("@apollo/client/core").FetchResult<any, Record<string, any>, Record<string, any>> | null;
9
9
  error: any;
10
10
  }>;
11
- export declare const runQuery: ({ command, query, variables, }: QueryHandlerProps) => Promise<{
12
- resp: import("@apollo/client/core").ApolloQueryResult<any> | null;
11
+ export declare const runQuery: <TResponse = any, TVar = Record<string, any>>({ command, query, variables, }: QueryHandlerProps<TVar>) => Promise<{
12
+ resp: import("@apollo/client/core").ApolloQueryResult<TResponse> | null;
13
13
  error: any;
14
14
  }>;
15
15
  declare const handler: ({ command, query, variables }: QueryHandlerProps) => void;
package/lib/lib/apollo.js CHANGED
@@ -21,6 +21,7 @@ exports.initClient = async (command) => {
21
21
  const currentCLIVersion = command.config.version;
22
22
  const configData = config_1.getOrCreateConfigData(configDir);
23
23
  const accessToken = await credentials_1.cred.accessToken;
24
+ const organizationID = await credentials_1.cred.organizationID;
24
25
  const httpsAgent = new https.Agent({
25
26
  rejectUnauthorized: !configData[config_1.allowSelfSignedCertsKey],
26
27
  });
@@ -36,7 +37,7 @@ exports.initClient = async (command) => {
36
37
  });
37
38
  const authLink = context_1.setContext((_, { headers }) => {
38
39
  return {
39
- headers: Object.assign(Object.assign({}, headers), { authorization: `Bearer ${accessToken}` }),
40
+ headers: Object.assign(Object.assign({}, headers), { authorization: `Bearer ${accessToken}`, 'X-Opal-Organization-ID': organizationID }),
40
41
  };
41
42
  });
42
43
  const checkCLIVersion = (operation) => {
@@ -4,3 +4,4 @@ export declare const allowSelfSignedCertsKey = "allowSelfSignedCerts";
4
4
  export declare const defaultAllowSelfSignedCerts = false;
5
5
  export declare const getOrCreateConfigData: (configDir: string) => Record<string, any>;
6
6
  export declare const writeConfigData: (configDir: string, newConfigData: Record<string, any>) => void;
7
+ export declare const isProduction: (configDir: string) => boolean;
package/lib/lib/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeConfigData = exports.getOrCreateConfigData = exports.defaultAllowSelfSignedCerts = exports.allowSelfSignedCertsKey = exports.defaultUrl = exports.urlKey = void 0;
3
+ exports.isProduction = exports.writeConfigData = exports.getOrCreateConfigData = exports.defaultAllowSelfSignedCerts = exports.allowSelfSignedCertsKey = exports.defaultUrl = exports.urlKey = void 0;
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
6
  exports.urlKey = 'url';
@@ -50,3 +50,11 @@ exports.writeConfigData = (configDir, newConfigData) => {
50
50
  mode: 0o0600,
51
51
  });
52
52
  };
53
+ exports.isProduction = (configDir) => {
54
+ const configData = exports.getOrCreateConfigData(configDir);
55
+ // Custom URLs are considered production since it includes on-prem
56
+ return configData[exports.urlKey] !== 'https://dev.opal.dev' &&
57
+ configData[exports.urlKey] !== 'http://localhost:3000' &&
58
+ configData[exports.urlKey] !== 'https://demo.opal.dev' &&
59
+ configData[exports.urlKey] !== 'https://staging.opal.dev';
60
+ };
@@ -1,6 +1,8 @@
1
1
  export declare const OPAL_CREDS_KEY = "opal";
2
2
  export declare const cred: {
3
3
  removeCredentials(after: number): Promise<void>;
4
- readonly accountId: Promise<string>;
5
- readonly accessToken: Promise<string>;
4
+ readonly accountId: Promise<string | undefined>;
5
+ readonly organizationID: Promise<string | undefined>;
6
+ readonly email: Promise<string | undefined>;
7
+ readonly accessToken: Promise<string | undefined>;
6
8
  };
@@ -20,6 +20,9 @@ exports.cred = {
20
20
  return (async () => {
21
21
  try {
22
22
  const keyContents = await keytar.findCredentials(exports.OPAL_CREDS_KEY);
23
+ if (!keyContents[0]) {
24
+ return undefined;
25
+ }
23
26
  const { account } = keyContents[0];
24
27
  return account;
25
28
  }
@@ -28,12 +31,50 @@ exports.cred = {
28
31
  }
29
32
  })();
30
33
  },
34
+ get organizationID() {
35
+ return (async () => {
36
+ try {
37
+ const keyContents = await keytar.findCredentials(exports.OPAL_CREDS_KEY);
38
+ if (!keyContents[0]) {
39
+ return undefined;
40
+ }
41
+ const { account } = keyContents[0];
42
+ const parts = account.split('|');
43
+ if (!parts || parts.length <= 1) {
44
+ return undefined;
45
+ }
46
+ return parts.pop();
47
+ }
48
+ catch (error) {
49
+ throw error;
50
+ }
51
+ })();
52
+ },
53
+ get email() {
54
+ return (async () => {
55
+ try {
56
+ const keyContents = await keytar.findCredentials(exports.OPAL_CREDS_KEY);
57
+ if (!keyContents[0]) {
58
+ return undefined;
59
+ }
60
+ const { account } = keyContents[0];
61
+ const parts = account.split('|');
62
+ if (!parts || parts.length <= 1) {
63
+ return undefined;
64
+ }
65
+ return parts[0];
66
+ }
67
+ catch (error) {
68
+ throw error;
69
+ }
70
+ })();
71
+ },
31
72
  get accessToken() {
32
73
  return (async () => {
33
74
  try {
34
75
  const keyContents = await keytar.findCredentials(exports.OPAL_CREDS_KEY);
35
76
  if (!keyContents[0]) {
36
- return 'unset-token';
77
+ return undefined;
37
78
  }
38
79
  const { password } = keyContents[0];
39
80
  return password;