opal-security 5.0.1 → 5.1.1
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 +26 -28
- package/build/commands/login.d.ts +0 -1
- package/build/commands/login.js +36 -89
- package/build/commands/whoami.js +2 -2
- package/build/graphql/gql.d.ts +4 -4
- package/build/graphql/gql.js +2 -2
- package/build/graphql/graphql.d.ts +6661 -59
- package/build/graphql/graphql.js +701 -3
- package/build/hooks/init/version-check.d.ts +3 -0
- package/build/hooks/init/version-check.js +110 -0
- package/build/labels.d.ts +6 -2
- package/build/labels.js +133 -2
- package/build/lib/request/api/queries/apps.js +5 -1
- package/build/lib/request/api/queries/assets.js +10 -3
- package/build/lib/request/api/queries/roles.js +11 -5
- package/build/lib/request/displays.js +13 -8
- package/build/lib/request/prompts/asset-prompt.js +5 -2
- package/build/lib/request/prompts/role-prompt.js +1 -1
- package/build/lib/request/request-utils.d.ts +2 -2
- package/build/lib/request/request-utils.js +20 -20
- package/build/lib/request/types.d.ts +5 -3
- package/build/lib/resources.d.ts +2 -2
- package/oclif.manifest.json +22 -29
- package/package.json +5 -2
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import https from "node:https";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { allowSelfSignedCertsKey, getOrCreateConfigData, urlKey, } from "../../lib/config.js";
|
|
5
|
+
// Commands that should skip the version check (e.g., configuration commands)
|
|
6
|
+
const SKIP_VERSION_CHECK_COMMANDS = [
|
|
7
|
+
"set-url",
|
|
8
|
+
"set-token",
|
|
9
|
+
"set-auth-config",
|
|
10
|
+
"version",
|
|
11
|
+
"help",
|
|
12
|
+
"autocomplete",
|
|
13
|
+
];
|
|
14
|
+
const hook = async (opts) => {
|
|
15
|
+
// Skip version check for certain commands
|
|
16
|
+
if (opts.id && SKIP_VERSION_CHECK_COMMANDS.includes(opts.id)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const configData = getOrCreateConfigData(opts.config.configDir);
|
|
21
|
+
const baseUrl = configData[urlKey];
|
|
22
|
+
const allowSelfSignedCerts = configData[allowSelfSignedCertsKey];
|
|
23
|
+
if (!baseUrl) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const cliVersion = opts.config.version;
|
|
27
|
+
const url = new URL("/api/cli/version-check", baseUrl);
|
|
28
|
+
url.searchParams.set("version", cliVersion);
|
|
29
|
+
const response = await fetchWithTimeout(url.toString(), {
|
|
30
|
+
method: "GET",
|
|
31
|
+
headers: {
|
|
32
|
+
"User-Agent": `Opal CLI v${cliVersion}`,
|
|
33
|
+
},
|
|
34
|
+
}, allowSelfSignedCerts, 5000);
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
// Silently fail on non-2xx responses - don't block the CLI
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const data = JSON.parse(response.body);
|
|
40
|
+
if (data.status === "ok") {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Display message based on status
|
|
44
|
+
const formattedMessage = formatMessage(data.status, data.message);
|
|
45
|
+
if (formattedMessage) {
|
|
46
|
+
process.stderr.write(`${formattedMessage}\n`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (_a) {
|
|
50
|
+
// Silently fail on any errors (network issues, etc.) - don't block the CLI
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function formatMessage(status, message) {
|
|
54
|
+
if (!message) {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
switch (status) {
|
|
58
|
+
case "error":
|
|
59
|
+
return chalk.red(`❗ ${message}`);
|
|
60
|
+
case "warn":
|
|
61
|
+
return chalk.yellow(`⚠️ ${message}`);
|
|
62
|
+
case "info":
|
|
63
|
+
return chalk.blue(`ℹ️ ${message}`);
|
|
64
|
+
default:
|
|
65
|
+
return message;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function fetchWithTimeout(url, options, allowSelfSignedCerts, timeoutMs) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const parsedUrl = new URL(url);
|
|
71
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
72
|
+
const lib = isHttps ? https : http;
|
|
73
|
+
const requestOptions = {
|
|
74
|
+
hostname: parsedUrl.hostname,
|
|
75
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
76
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
77
|
+
method: options.method,
|
|
78
|
+
headers: options.headers,
|
|
79
|
+
timeout: timeoutMs,
|
|
80
|
+
};
|
|
81
|
+
if (isHttps) {
|
|
82
|
+
requestOptions.rejectUnauthorized =
|
|
83
|
+
!allowSelfSignedCerts;
|
|
84
|
+
}
|
|
85
|
+
const req = lib.request(requestOptions, (res) => {
|
|
86
|
+
let body = "";
|
|
87
|
+
res.on("data", (chunk) => {
|
|
88
|
+
body += chunk;
|
|
89
|
+
});
|
|
90
|
+
res.on("end", () => {
|
|
91
|
+
resolve({
|
|
92
|
+
ok: res.statusCode !== undefined &&
|
|
93
|
+
res.statusCode >= 200 &&
|
|
94
|
+
res.statusCode !== undefined &&
|
|
95
|
+
res.statusCode >= 200 &&
|
|
96
|
+
res.statusCode < 300,
|
|
97
|
+
status: res.statusCode || 0,
|
|
98
|
+
body,
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
req.on("error", reject);
|
|
103
|
+
req.on("timeout", () => {
|
|
104
|
+
req.destroy();
|
|
105
|
+
reject(new Error("Request timed out"));
|
|
106
|
+
});
|
|
107
|
+
req.end();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
export default hook;
|
package/build/labels.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
import { ConnectionType, EntityType } from "./graphql/graphql.js";
|
|
1
|
+
import { ConnectionType, EntityType, GroupType, ResourceType } from "./graphql/graphql.js";
|
|
2
2
|
export declare const connectionTypeLabelByType: Record<ConnectionType, string>;
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const EntityTypeLabels: Partial<Record<EntityType, string>>;
|
|
4
|
+
export declare const ResourceTypeLabels: Record<ResourceType, string>;
|
|
5
|
+
export declare const GroupTypeLabels: Record<GroupType, string>;
|
|
6
|
+
export declare function getAssetTypeLabel(entityType: EntityType, assetType: ResourceType | GroupType | undefined): string;
|
|
7
|
+
export declare function getAppTypeLabel(appType: ConnectionType | ResourceType): string;
|
package/build/labels.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConnectionType, EntityType } from "./graphql/graphql.js";
|
|
1
|
+
import { ConnectionType, EntityType, GroupType, ResourceType, } from "./graphql/graphql.js";
|
|
2
2
|
export const connectionTypeLabelByType = {
|
|
3
3
|
[ConnectionType.ActiveDirectory]: "Active Directory",
|
|
4
4
|
[ConnectionType.Aws]: "Amazon Web Services (Legacy)",
|
|
@@ -34,8 +34,139 @@ export const connectionTypeLabelByType = {
|
|
|
34
34
|
[ConnectionType.Cursor]: "Cursor",
|
|
35
35
|
[ConnectionType.OpenaiPlatform]: "OpenAI Platform",
|
|
36
36
|
[ConnectionType.OracleFusion]: "Oracle Fusion",
|
|
37
|
+
[ConnectionType.Devin]: "Devin",
|
|
38
|
+
[ConnectionType.Incidentio]: "incident.io",
|
|
39
|
+
[ConnectionType.OktaCiam]: "Okta CIAM",
|
|
40
|
+
[ConnectionType.Vault]: "HashiCorp Vault",
|
|
37
41
|
};
|
|
38
|
-
export const
|
|
42
|
+
export const EntityTypeLabels = {
|
|
39
43
|
[EntityType.Resource]: "Resource",
|
|
40
44
|
[EntityType.Group]: "Group",
|
|
41
45
|
};
|
|
46
|
+
// ResourceTypeLabels maps readable labels from model resource types
|
|
47
|
+
export const ResourceTypeLabels = {
|
|
48
|
+
[ResourceType.AwsEc2Instance]: "AWS EC2",
|
|
49
|
+
[ResourceType.AwsEksCluster]: "AWS EKS",
|
|
50
|
+
[ResourceType.AwsIamRole]: "AWS IAM Role",
|
|
51
|
+
[ResourceType.AwsAccount]: "AWS Account",
|
|
52
|
+
[ResourceType.AwsOrganizationalUnit]: "AWS Organizational Unit",
|
|
53
|
+
[ResourceType.AwsSsoPermissionSet]: "AWS Identity Center Role",
|
|
54
|
+
[ResourceType.AwsRdsMysqlInstance]: "AWS RDS MySQL Instance",
|
|
55
|
+
[ResourceType.AwsRdsMysqlCluster]: "AWS RDS MySQL Cluster",
|
|
56
|
+
[ResourceType.AwsRdsPostgresInstance]: "AWS RDS Postgres Instance",
|
|
57
|
+
[ResourceType.AwsRdsPostgresCluster]: "AWS RDS Postgres Cluster",
|
|
58
|
+
[ResourceType.Custom]: "Custom",
|
|
59
|
+
[ResourceType.CustomConnector]: "Connector Resource",
|
|
60
|
+
[ResourceType.GcpOrganization]: "GCP Organization",
|
|
61
|
+
[ResourceType.GcpBucket]: "GCP Bucket",
|
|
62
|
+
[ResourceType.GcpFolder]: "GCP Folder",
|
|
63
|
+
[ResourceType.GcpComputeInstance]: "GCP Compute",
|
|
64
|
+
[ResourceType.GcpCloudSqlMysqlInstance]: "GCP Cloud SQL MySQL",
|
|
65
|
+
[ResourceType.GcpCloudSqlPostgresInstance]: "GCP Cloud SQL Postgres",
|
|
66
|
+
[ResourceType.GcpGkeCluster]: "GCP GKE",
|
|
67
|
+
[ResourceType.GcpProject]: "GCP Project",
|
|
68
|
+
[ResourceType.GcpBigQueryDataset]: "GCP BigQuery Dataset",
|
|
69
|
+
[ResourceType.GcpBigQueryTable]: "GCP BigQuery Table",
|
|
70
|
+
[ResourceType.GcpServiceAccount]: "GCP Service Account",
|
|
71
|
+
[ResourceType.GoogleWorkspaceRole]: "Google Workspace Role",
|
|
72
|
+
[ResourceType.GitHubRepo]: "GitHub Repo",
|
|
73
|
+
[ResourceType.GitHubOrg]: "GitHub Organization",
|
|
74
|
+
[ResourceType.GitHubOrgRole]: "GitHub Organization Role",
|
|
75
|
+
[ResourceType.GitLabProject]: "GitLab Project",
|
|
76
|
+
[ResourceType.MongoInstance]: "Mongo",
|
|
77
|
+
[ResourceType.MongoAtlasInstance]: "Mongo Atlas",
|
|
78
|
+
[ResourceType.OktaApp]: "Okta App",
|
|
79
|
+
[ResourceType.OktaRole]: "Okta Role",
|
|
80
|
+
[ResourceType.OpalRole]: "Opal Role",
|
|
81
|
+
[ResourceType.OpalScopedRole]: "Opal Custom Role",
|
|
82
|
+
[ResourceType.PagerdutyRole]: "PagerDuty Role",
|
|
83
|
+
[ResourceType.TailscaleSsh]: "Tailscale SSH",
|
|
84
|
+
[ResourceType.SalesforcePermissionSet]: "SFDC Permission Set",
|
|
85
|
+
[ResourceType.SalesforceProfile]: "SFDC Profile",
|
|
86
|
+
[ResourceType.SalesforceRole]: "SFDC Role",
|
|
87
|
+
[ResourceType.SnowflakeDatabase]: "Snowflake Database",
|
|
88
|
+
[ResourceType.SnowflakeSchema]: "Snowflake Schema",
|
|
89
|
+
[ResourceType.SnowflakeTable]: "Snowflake Table",
|
|
90
|
+
[ResourceType.WorkdayRole]: "Workday Organization Role",
|
|
91
|
+
[ResourceType.MysqlInstance]: "MySQL",
|
|
92
|
+
[ResourceType.MariadbInstance]: "MariaDB",
|
|
93
|
+
[ResourceType.PostgresInstance]: "Postgres",
|
|
94
|
+
[ResourceType.TeleportRole]: "Teleport Role",
|
|
95
|
+
[ResourceType.AzureManagementGroup]: "Azure Management Group",
|
|
96
|
+
[ResourceType.AzureResourceGroup]: "Azure Resource Group",
|
|
97
|
+
[ResourceType.AzureSubscription]: "Azure Subscription",
|
|
98
|
+
[ResourceType.AzureVirtualMachine]: "Azure Virtual Machine",
|
|
99
|
+
[ResourceType.AzureStorageAccount]: "Azure Storage Account",
|
|
100
|
+
[ResourceType.AzureStorageContainer]: "Azure Storage Container",
|
|
101
|
+
[ResourceType.AzureSqlServer]: "Azure SQL Server",
|
|
102
|
+
[ResourceType.AzureSqlManagedInstance]: "Azure SQL Managed Instance",
|
|
103
|
+
[ResourceType.AzureSqlDatabase]: "Azure SQL Database",
|
|
104
|
+
[ResourceType.AzureSqlManagedDatabase]: "Azure SQL Managed Database",
|
|
105
|
+
[ResourceType.AzureUserAssignedManagedIdentity]: "Azure User-Assigned Managed Identity",
|
|
106
|
+
[ResourceType.AzureEntraIdRole]: "Microsoft Entra ID Role",
|
|
107
|
+
[ResourceType.AzureEnterpriseApp]: "Azure Enterprise Application",
|
|
108
|
+
[ResourceType.DatabricksAccountServicePrincipal]: "Databricks Account-Level Service Principal",
|
|
109
|
+
[ResourceType.IlevelAdvancedRole]: "iLEVEL Advanced Role",
|
|
110
|
+
[ResourceType.DatastaxAstraRole]: "Astra Role",
|
|
111
|
+
[ResourceType.CoupaRole]: "Coupa Role",
|
|
112
|
+
[ResourceType.CursorOrganization]: "Cursor Organization",
|
|
113
|
+
[ResourceType.OpenaiPlatformProject]: "OpenAI Platform Project",
|
|
114
|
+
[ResourceType.OpenaiPlatformServiceAccount]: "OpenAI Platform Service Account",
|
|
115
|
+
[ResourceType.AnthropicWorkspace]: "Anthropic Workspace",
|
|
116
|
+
[ResourceType.OracleFusionRole]: "Oracle Fusion Role",
|
|
117
|
+
[ResourceType.DevinOrganization]: "Devin Organization",
|
|
118
|
+
[ResourceType.DevinRole]: "Devin Role",
|
|
119
|
+
[ResourceType.VaultOidcRole]: "Vault OIDC Role",
|
|
120
|
+
[ResourceType.VaultPolicy]: "Vault Policy",
|
|
121
|
+
[ResourceType.VaultSecret]: "Vault Secret",
|
|
122
|
+
};
|
|
123
|
+
// GroupTypeLabels maps readable labels from model group types
|
|
124
|
+
export const GroupTypeLabels = {
|
|
125
|
+
[GroupType.ActiveDirectoryGroup]: "AD Group",
|
|
126
|
+
[GroupType.AwsSsoGroup]: "AWS IAM Group",
|
|
127
|
+
[GroupType.DuoGroup]: "Duo Group",
|
|
128
|
+
[GroupType.GitHubTeam]: "GitHub Team",
|
|
129
|
+
[GroupType.GitLabGroup]: "GitLab Group",
|
|
130
|
+
[GroupType.GoogleGroupsGroup]: "Google Group",
|
|
131
|
+
[GroupType.GoogleGroupsGkeGroup]: "GKE Google Group",
|
|
132
|
+
[GroupType.LdapGroup]: "LDAP Group",
|
|
133
|
+
[GroupType.OktaGroup]: "Okta Group",
|
|
134
|
+
[GroupType.OpalGroup]: "Opal Group",
|
|
135
|
+
[GroupType.AzureAdMicrosoft_365Group]: "Microsoft 365 Group",
|
|
136
|
+
[GroupType.AzureAdSecurityGroup]: "Microsoft Entra ID Security Group",
|
|
137
|
+
[GroupType.ConnectorGroup]: "Connector Group",
|
|
138
|
+
[GroupType.SnowflakeRole]: "Snowflake Role",
|
|
139
|
+
[GroupType.TailscaleGroup]: "Tailscale Group",
|
|
140
|
+
[GroupType.WorkdayUserSecurityGroup]: "Workday User Security Group",
|
|
141
|
+
[GroupType.OpalAccessRule]: "Access Rule",
|
|
142
|
+
[GroupType.OktaGroupRule]: "Okta Group Rule",
|
|
143
|
+
[GroupType.DatabricksAccountGroup]: "Databricks Account-Level Group",
|
|
144
|
+
[GroupType.DevinGroup]: "Devin Group",
|
|
145
|
+
[GroupType.IncidentioOnCallSchedule]: "incident.io On-Call Schedule",
|
|
146
|
+
[GroupType.PagerdutyOnCallSchedule]: "PagerDuty On-Call Schedule",
|
|
147
|
+
};
|
|
148
|
+
export function getAssetTypeLabel(entityType, assetType) {
|
|
149
|
+
var _a;
|
|
150
|
+
if (assetType === undefined) {
|
|
151
|
+
// If asset type is not defined (e.g., Okta/Azure group), return empty string
|
|
152
|
+
return "";
|
|
153
|
+
}
|
|
154
|
+
let assetTypeLabel = (_a = EntityTypeLabels[entityType]) !== null && _a !== void 0 ? _a : entityType;
|
|
155
|
+
if (entityType === EntityType.Resource) {
|
|
156
|
+
assetTypeLabel =
|
|
157
|
+
ResourceTypeLabels[assetType] || assetTypeLabel;
|
|
158
|
+
}
|
|
159
|
+
else if (entityType === EntityType.Group) {
|
|
160
|
+
assetTypeLabel = GroupTypeLabels[assetType] || assetTypeLabel;
|
|
161
|
+
}
|
|
162
|
+
return `[${assetTypeLabel}]`;
|
|
163
|
+
}
|
|
164
|
+
export function getAppTypeLabel(appType) {
|
|
165
|
+
if (appType in connectionTypeLabelByType) {
|
|
166
|
+
return connectionTypeLabelByType[appType];
|
|
167
|
+
}
|
|
168
|
+
if (appType in ResourceTypeLabels) {
|
|
169
|
+
return ResourceTypeLabels[appType];
|
|
170
|
+
}
|
|
171
|
+
return "App";
|
|
172
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { graphql } from "../../../../graphql/index.js";
|
|
2
|
+
import { ResourceTypeLabels, connectionTypeLabelByType, } from "../../../../labels.js";
|
|
2
3
|
// TODO: add pagination ability from CLI. (Load more...) option
|
|
3
4
|
const GET_REQUESTABLE_APPS_QUERY = graphql(`
|
|
4
5
|
query GetRequestableAppsQuery($searchQuery: String) {
|
|
@@ -41,18 +42,21 @@ export async function queryRequestableApps(cmd, client, input) {
|
|
|
41
42
|
});
|
|
42
43
|
return (_b = (_a = resp === null || resp === void 0 ? void 0 : resp.data) === null || _a === void 0 ? void 0 : _a.appsV2) === null || _b === void 0 ? void 0 : _b.edges.map((edge) => {
|
|
43
44
|
let type = undefined;
|
|
45
|
+
let message = "";
|
|
44
46
|
switch (edge.node.__typename) {
|
|
45
47
|
case "Resource":
|
|
46
48
|
type = edge.node.resourceType;
|
|
49
|
+
message = `${edge.node.displayName} [${ResourceTypeLabels[type]}]`;
|
|
47
50
|
break;
|
|
48
51
|
case "Connection":
|
|
49
52
|
type = edge.node.connectionType;
|
|
53
|
+
message = `${edge.node.displayName} [${connectionTypeLabelByType[type]}]`;
|
|
50
54
|
break;
|
|
51
55
|
default:
|
|
52
56
|
type = edge.node.__typename;
|
|
53
57
|
}
|
|
54
58
|
return {
|
|
55
|
-
message:
|
|
59
|
+
message: message,
|
|
56
60
|
value: {
|
|
57
61
|
id: edge.node.id,
|
|
58
62
|
name: edge.node.displayName,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { graphql } from "../../../../graphql/index.js";
|
|
2
|
+
import { getAssetTypeLabel } from "../../../../labels.js";
|
|
2
3
|
import { entityTypeFromString } from "../../types.js";
|
|
3
4
|
const GET_ASSETS_QUERY = graphql(`
|
|
4
5
|
query PaginatedEntityDropdown(
|
|
@@ -21,10 +22,12 @@ const GET_ASSETS_QUERY = graphql(`
|
|
|
21
22
|
resource {
|
|
22
23
|
id
|
|
23
24
|
name
|
|
25
|
+
resourceType
|
|
24
26
|
}
|
|
25
27
|
group {
|
|
26
28
|
id
|
|
27
29
|
name
|
|
30
|
+
groupType
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
cursor
|
|
@@ -55,13 +58,17 @@ export async function queryRequestableAssets(cmd, client, appId, input) {
|
|
|
55
58
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
56
59
|
const name = ((_a = item.resource) === null || _a === void 0 ? void 0 : _a.name) || ((_b = item.group) === null || _b === void 0 ? void 0 : _b.name);
|
|
57
60
|
const id = ((_c = item.resource) === null || _c === void 0 ? void 0 : _c.id) || ((_d = item.group) === null || _d === void 0 ? void 0 : _d.id);
|
|
58
|
-
const
|
|
61
|
+
const entityType = entityTypeFromString(((_e = item.resource) === null || _e === void 0 ? void 0 : _e.__typename) || ((_f = item.group) === null || _f === void 0 ? void 0 : _f.__typename));
|
|
62
|
+
const assetType = ((_g = item.resource) === null || _g === void 0 ? void 0 : _g.resourceType) || ((_h = item.group) === null || _h === void 0 ? void 0 : _h.groupType);
|
|
63
|
+
const assetTypeLabel = getAssetTypeLabel(entityType, assetType);
|
|
59
64
|
return {
|
|
60
|
-
message: `${name}
|
|
65
|
+
message: `${name} ${assetTypeLabel}`,
|
|
61
66
|
value: {
|
|
62
67
|
name: name || "",
|
|
63
68
|
id: id || "",
|
|
64
|
-
|
|
69
|
+
entityType: entityType,
|
|
70
|
+
assetType: assetType,
|
|
71
|
+
toString: () => name || "",
|
|
65
72
|
},
|
|
66
73
|
};
|
|
67
74
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { graphql } from "../../../../graphql/index.js";
|
|
2
2
|
import { EntityType, } from "../../../../graphql/graphql.js";
|
|
3
|
-
import {
|
|
3
|
+
import { EntityTypeLabels, GroupTypeLabels } from "../../../../labels.js";
|
|
4
4
|
const RESOURCE_ROLES_QUERY = graphql(`
|
|
5
5
|
query ResourceAccessLevels($resourceId: ResourceId!) {
|
|
6
6
|
accessLevels(input: {
|
|
@@ -61,6 +61,7 @@ export async function queryAssetRoles(cmd, client, assetType, assetId) {
|
|
|
61
61
|
value: {
|
|
62
62
|
name: role.accessLevelName || "",
|
|
63
63
|
id: role.accessLevelRemoteId || "",
|
|
64
|
+
entityType: EntityType.Resource,
|
|
64
65
|
},
|
|
65
66
|
};
|
|
66
67
|
});
|
|
@@ -90,6 +91,7 @@ export async function queryAssetRoles(cmd, client, assetType, assetId) {
|
|
|
90
91
|
value: {
|
|
91
92
|
name: role.accessLevelName,
|
|
92
93
|
id: role.accessLevelRemoteId,
|
|
94
|
+
entityType: EntityType.Group,
|
|
93
95
|
},
|
|
94
96
|
};
|
|
95
97
|
});
|
|
@@ -136,7 +138,11 @@ const ASSOCIATED_ITEMS_QUERY = graphql(`
|
|
|
136
138
|
__typename
|
|
137
139
|
id
|
|
138
140
|
name
|
|
141
|
+
... on Group {
|
|
142
|
+
groupType
|
|
143
|
+
}
|
|
139
144
|
... on Resource {
|
|
145
|
+
resourceType
|
|
140
146
|
accessLevels(
|
|
141
147
|
filters: {
|
|
142
148
|
skipRemoteAccessLevels: false # azure app roles are remote
|
|
@@ -169,7 +175,6 @@ function appRolesFromEdge(edge) {
|
|
|
169
175
|
value: {
|
|
170
176
|
id: edge.node.id + accessLevel.accessLevelRemoteId,
|
|
171
177
|
name: accessLevel.accessLevelName,
|
|
172
|
-
type: DisplayLabels[EntityType.Resource],
|
|
173
178
|
toString: () => accessLevel.accessLevelName,
|
|
174
179
|
},
|
|
175
180
|
}));
|
|
@@ -180,7 +185,7 @@ function appRolesFromEdge(edge) {
|
|
|
180
185
|
value: {
|
|
181
186
|
id: edge.node.id,
|
|
182
187
|
name: (_b = edge.alias) !== null && _b !== void 0 ? _b : edge.node.name,
|
|
183
|
-
type:
|
|
188
|
+
type: EntityTypeLabels[EntityType.Resource],
|
|
184
189
|
toString: () => { var _a; return (_a = edge.alias) !== null && _a !== void 0 ? _a : edge.node.name; },
|
|
185
190
|
},
|
|
186
191
|
},
|
|
@@ -189,11 +194,12 @@ function appRolesFromEdge(edge) {
|
|
|
189
194
|
case "Group":
|
|
190
195
|
return [
|
|
191
196
|
{
|
|
192
|
-
message: `${(_c = edge.alias) !== null && _c !== void 0 ? _c : edge.node.name} ${
|
|
197
|
+
message: `${(_c = edge.alias) !== null && _c !== void 0 ? _c : edge.node.name} [${GroupTypeLabels[edge.node.groupType]}]`,
|
|
193
198
|
value: {
|
|
194
199
|
id: edge.node.id,
|
|
195
200
|
name: (_d = edge.alias) !== null && _d !== void 0 ? _d : edge.node.name,
|
|
196
|
-
type:
|
|
201
|
+
type: EntityTypeLabels[EntityType.Group],
|
|
202
|
+
assetType: edge.node.groupType,
|
|
197
203
|
toString: () => { var _a; return (_a = edge.alias) !== null && _a !== void 0 ? _a : edge.node.name; },
|
|
198
204
|
},
|
|
199
205
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import Table from "cli-table3";
|
|
3
|
-
import {
|
|
3
|
+
import { getAppTypeLabel, getAssetTypeLabel } from "../../labels.js";
|
|
4
4
|
export function headerMessage(cmd) {
|
|
5
5
|
console.clear();
|
|
6
6
|
cmd.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
@@ -10,21 +10,26 @@ export function headerMessage(cmd) {
|
|
|
10
10
|
/*
|
|
11
11
|
Example treeified output:
|
|
12
12
|
|
|
13
|
-
Opal [
|
|
14
|
-
└── Infosec Group [
|
|
15
|
-
└── Infosec Prod Databases [
|
|
16
|
-
MongoDB Atlas Test [
|
|
17
|
-
└── my-mongo-db-atlas [
|
|
13
|
+
Opal [AppType]
|
|
14
|
+
└── Infosec Group [GroupType]
|
|
15
|
+
└── Infosec Prod Databases [GroupType]
|
|
16
|
+
MongoDB Atlas Test [AppType]
|
|
17
|
+
└── my-mongo-db-atlas [ResourceType]
|
|
18
18
|
└── admin:test [Role]
|
|
19
19
|
*/
|
|
20
20
|
export function treeifyRequestMap(cmd, requestMap) {
|
|
21
|
+
headerMessage(cmd);
|
|
21
22
|
for (const [_appId, appNode] of Object.entries(requestMap)) {
|
|
22
23
|
// Print App title first (without tree lines)
|
|
23
|
-
|
|
24
|
+
const appTypeLabel = appNode.appType
|
|
25
|
+
? getAppTypeLabel(appNode.appType)
|
|
26
|
+
: "App";
|
|
27
|
+
cmd.log(`${chalk.bold(appNode.appName)} ${chalk.dim(`[${appTypeLabel}]`)}`);
|
|
24
28
|
for (const [_assetId, assetNode] of Object.entries(appNode.assets)) {
|
|
25
29
|
// If okta/azure asset with no role, change asset name
|
|
26
30
|
const assetName = assetNode.assetName || "No Role (Direct access)";
|
|
27
|
-
|
|
31
|
+
const assetTypeLabel = getAssetTypeLabel(assetNode.entityType, assetNode.assetType);
|
|
32
|
+
cmd.log(`└── ${assetName} ${chalk.dim(`${assetTypeLabel}`)}`);
|
|
28
33
|
if (assetNode.roles !== undefined) {
|
|
29
34
|
for (const [_roleId, roleNode] of Object.entries(assetNode.roles)) {
|
|
30
35
|
const roleName = roleNode.roleName;
|
|
@@ -5,6 +5,7 @@ import enquirer from "enquirer-esm";
|
|
|
5
5
|
// @ts-expect-error - enquirer-esm doesn't export autocomplete in types but it exists at runtime
|
|
6
6
|
const { autocomplete } = enquirer;
|
|
7
7
|
export async function chooseOktaAzureRoles(cmd, client, app, requestMap) {
|
|
8
|
+
var _a;
|
|
8
9
|
const associatedItems = (await queryAssociatedItems(cmd, client, app.id, "")) || [];
|
|
9
10
|
const selectedRole = await autocomplete({
|
|
10
11
|
name: "Roles",
|
|
@@ -24,7 +25,8 @@ export async function chooseOktaAzureRoles(cmd, client, app, requestMap) {
|
|
|
24
25
|
entry.assets[selectedRole.id] = {
|
|
25
26
|
assetId: selectedRole.id,
|
|
26
27
|
assetName: selectedRole.name,
|
|
27
|
-
|
|
28
|
+
entityType: entityTypeFromString(selectedRole.type),
|
|
29
|
+
assetType: (_a = selectedRole.assetType) !== null && _a !== void 0 ? _a : undefined,
|
|
28
30
|
roles: {},
|
|
29
31
|
};
|
|
30
32
|
}
|
|
@@ -53,7 +55,8 @@ export async function chooseAssets(cmd, client, appId, requestMap) {
|
|
|
53
55
|
entry.assets[selectedAsset.id] = {
|
|
54
56
|
assetId: selectedAsset.id,
|
|
55
57
|
assetName: selectedAsset.name,
|
|
56
|
-
|
|
58
|
+
entityType: selectedAsset.entityType,
|
|
59
|
+
assetType: selectedAsset.assetType,
|
|
57
60
|
roles: {},
|
|
58
61
|
};
|
|
59
62
|
}
|
|
@@ -10,7 +10,7 @@ export async function chooseRoles(cmd, client, appId, assetId, requestMap) {
|
|
|
10
10
|
if (entry === undefined || assetEntry === undefined) {
|
|
11
11
|
throw new Error(`App ${appId} or Asset ${assetId} not found in requestMap`);
|
|
12
12
|
}
|
|
13
|
-
const assetRoles = (_a = (await queryAssetRoles(cmd, client, assetEntry.
|
|
13
|
+
const assetRoles = (_a = (await queryAssetRoles(cmd, client, assetEntry.entityType, assetId))) !== null && _a !== void 0 ? _a : [];
|
|
14
14
|
if (assetRoles !== undefined &&
|
|
15
15
|
(assetRoles.length === 0 ||
|
|
16
16
|
(assetRoles.length === 1 && assetRoles[0].value.name === ""))) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApolloClient } from "@apollo/client";
|
|
2
2
|
import type { Command } from "@oclif/core";
|
|
3
|
-
import { type ConnectionType, RequestMessageCode } from "../../graphql/graphql.js";
|
|
3
|
+
import { type ConnectionType, RequestMessageCode, type ResourceType } from "../../graphql/graphql.js";
|
|
4
4
|
import { type RequestMap, type RequestMetadata } from "./types.js";
|
|
5
5
|
export declare function initEmptyRequestMetadata(): RequestMetadata;
|
|
6
6
|
export declare function setRequestDefaults(cmd: Command, client: ApolloClient, metadata: RequestMetadata): Promise<void>;
|
|
@@ -9,6 +9,6 @@ export declare function getRequestLink(cmd: Command, id: string): string;
|
|
|
9
9
|
export declare function generateRequestLink(cmd: Command, defaultDurationInMinutes: number): string;
|
|
10
10
|
export declare function bypassRequestSelection(cmd: Command, client: ApolloClient, flagValue: string[], metadata: RequestMetadata): Promise<void>;
|
|
11
11
|
export declare function bypassDuration(cmd: Command, duration: number, metadata: RequestMetadata): void;
|
|
12
|
-
export declare function getRequestMessageFromCode(cmd: Command, code: RequestMessageCode,
|
|
12
|
+
export declare function getRequestMessageFromCode(cmd: Command, code: RequestMessageCode, appName: string | undefined, appType: ConnectionType | ResourceType | undefined, extraParams?: string, sourceGroupRedirect?: () => void): string;
|
|
13
13
|
export declare function duplicateRequestTemplate(cmd: Command, client: ApolloClient, requestId: string, metadata: RequestMetadata): Promise<void>;
|
|
14
14
|
export declare function copyBundleAssets(cmd: Command, client: ApolloClient, bundleId: string, requestMap: RequestMap): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { EntityType, RequestMessageCode, RequestStatus, ThirdPartyProvider, } from "../../graphql/graphql.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getAppTypeLabel } from "../../labels.js";
|
|
4
4
|
import { getOrCreateConfigData, urlKey } from "../../lib/config.js";
|
|
5
5
|
import { createRequest, queryCatalogItems, queryRequest, queryRequestDefaults, } from "./api/index.js";
|
|
6
6
|
import { queryBundle } from "./api/queries/requests.js";
|
|
@@ -49,7 +49,7 @@ export async function setRequestDefaults(cmd, client, metadata) {
|
|
|
49
49
|
});
|
|
50
50
|
const roleIds = mappedRoles.length ? mappedRoles : [""];
|
|
51
51
|
for (const roleId of roleIds) {
|
|
52
|
-
switch (assetNode.
|
|
52
|
+
switch (assetNode.entityType) {
|
|
53
53
|
case EntityType.Resource: {
|
|
54
54
|
requestedResources.push({
|
|
55
55
|
resourceId: assetId,
|
|
@@ -86,22 +86,22 @@ export async function setRequestDefaults(cmd, client, metadata) {
|
|
|
86
86
|
metadata.requestDefaults.requesterIsAdmin =
|
|
87
87
|
requestDefaults.requesterIsAdmin;
|
|
88
88
|
for (const message of requestDefaults.messages || []) {
|
|
89
|
-
let
|
|
90
|
-
let
|
|
89
|
+
let appName = undefined;
|
|
90
|
+
let appType = undefined;
|
|
91
91
|
// If the message has an entityId, retrieve the connection name and type
|
|
92
92
|
if (message.entityId) {
|
|
93
93
|
for (const appNode of Object.values(requestMap)) {
|
|
94
94
|
for (const assetNode of Object.values(appNode.assets)) {
|
|
95
95
|
if (assetNode.assetId === message.entityId) {
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
appName = appNode.appName;
|
|
97
|
+
appType = appNode.appType;
|
|
98
98
|
break;
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
// Log the request message based on the code
|
|
104
|
-
cmd.log(chalk.dim(`\n${chalk.italic(message.level)}:`), getRequestMessageFromCode(cmd, message.code,
|
|
104
|
+
cmd.log(chalk.dim(`\n${chalk.italic(message.level)}:`), getRequestMessageFromCode(cmd, message.code, appName, appType));
|
|
105
105
|
if (message.level === "ERROR") {
|
|
106
106
|
process.exit(1);
|
|
107
107
|
}
|
|
@@ -134,7 +134,7 @@ export async function submitFinalRequest(cmd, client, metadata) {
|
|
|
134
134
|
});
|
|
135
135
|
const roleIds = mappedRoles.length > 0 ? mappedRoles : [""];
|
|
136
136
|
for (const roleId of roleIds) {
|
|
137
|
-
switch (assetNode.
|
|
137
|
+
switch (assetNode.entityType) {
|
|
138
138
|
case EntityType.Resource: {
|
|
139
139
|
requestedResources.push({
|
|
140
140
|
resourceId: assetId,
|
|
@@ -216,7 +216,7 @@ export async function bypassRequestSelection(cmd, client, flagValue, metadata) {
|
|
|
216
216
|
metadata.requestMap[appId].assets[assetId] = {
|
|
217
217
|
assetId: assetId,
|
|
218
218
|
assetName: assetName,
|
|
219
|
-
|
|
219
|
+
entityType: entityTypeFromString(item.__typename),
|
|
220
220
|
roles: {},
|
|
221
221
|
};
|
|
222
222
|
}
|
|
@@ -264,23 +264,23 @@ export function bypassDuration(cmd, duration, metadata) {
|
|
|
264
264
|
metadata.durationInMinutes = duration;
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
|
-
export function getRequestMessageFromCode(cmd, code,
|
|
267
|
+
export function getRequestMessageFromCode(cmd, code, appName, appType, extraParams,
|
|
268
268
|
// sourceGroup?: PropsFor<typeof GroupBindingGroupLabel>["group"];
|
|
269
269
|
sourceGroupRedirect) {
|
|
270
270
|
switch (code) {
|
|
271
271
|
case RequestMessageCode.RequireUserAuthToken: {
|
|
272
|
-
if (
|
|
273
|
-
const
|
|
272
|
+
if (appType) {
|
|
273
|
+
const appLabel = getAppTypeLabel(appType);
|
|
274
274
|
// This case the connection has a third party provider such as GitLab,
|
|
275
275
|
// GitHub that requires the use to create a manual step into the end
|
|
276
276
|
// system.
|
|
277
|
-
const isThirdPartyProvider = Object.values(ThirdPartyProvider).find((v) => v ===
|
|
277
|
+
const isThirdPartyProvider = Object.values(ThirdPartyProvider).find((v) => v === appType);
|
|
278
278
|
if (isThirdPartyProvider) {
|
|
279
279
|
const configData = getOrCreateConfigData(cmd.config.configDir);
|
|
280
|
-
return `This item requires you to link your ${
|
|
281
|
-
You can link your ${
|
|
280
|
+
return `This item requires you to link your ${appLabel} account to Opal before requesting access.
|
|
281
|
+
You can link your ${appLabel} account at ${`${configData[urlKey]}/user/settings/identities`}`;
|
|
282
282
|
}
|
|
283
|
-
const name =
|
|
283
|
+
const name = appName !== null && appName !== void 0 ? appName : appLabel;
|
|
284
284
|
// This case would be hit in case the user is not provisioned onto the end system, such as AWS, GCP, Okta, etc.
|
|
285
285
|
return `This item requires you to be provisioned on ${name} before requesting access.\
|
|
286
286
|
Please contact your Opal admin to provision your user on ${name}.`;
|
|
@@ -302,7 +302,7 @@ function addRequestedResourcesToMetadata(requestedResources, requestMap) {
|
|
|
302
302
|
if (!resource)
|
|
303
303
|
continue;
|
|
304
304
|
const { id: assetId, displayName: assetName, connection, connectionId, __typename, } = resource;
|
|
305
|
-
const
|
|
305
|
+
const entityType = entityTypeFromString(__typename);
|
|
306
306
|
const roleId = accessLevel.accessLevelRemoteId;
|
|
307
307
|
const roleName = accessLevel.accessLevelName;
|
|
308
308
|
if (!requestMap[connectionId]) {
|
|
@@ -318,7 +318,7 @@ function addRequestedResourcesToMetadata(requestedResources, requestMap) {
|
|
|
318
318
|
appAssets[assetId] = {
|
|
319
319
|
assetId,
|
|
320
320
|
assetName,
|
|
321
|
-
|
|
321
|
+
entityType,
|
|
322
322
|
roles: {},
|
|
323
323
|
};
|
|
324
324
|
}
|
|
@@ -334,7 +334,7 @@ function addRequestedGroupsToMetadata(requestedGroups, requestMap) {
|
|
|
334
334
|
if (!group)
|
|
335
335
|
continue;
|
|
336
336
|
const { id: assetId, name: assetName, connection, connectionId, __typename, } = group;
|
|
337
|
-
const
|
|
337
|
+
const entityType = entityTypeFromString(__typename);
|
|
338
338
|
const roleId = accessLevel === null || accessLevel === void 0 ? void 0 : accessLevel.accessLevelRemoteId;
|
|
339
339
|
const roleName = accessLevel === null || accessLevel === void 0 ? void 0 : accessLevel.accessLevelName;
|
|
340
340
|
if (!requestMap[connectionId]) {
|
|
@@ -350,7 +350,7 @@ function addRequestedGroupsToMetadata(requestedGroups, requestMap) {
|
|
|
350
350
|
appAssets[assetId] = {
|
|
351
351
|
assetId,
|
|
352
352
|
assetName,
|
|
353
|
-
|
|
353
|
+
entityType,
|
|
354
354
|
roles: {},
|
|
355
355
|
};
|
|
356
356
|
}
|