Skip to content

Commit 9ea990b

Browse files
authored
Pam Gateway and config sdk examples (#252)
1 parent 343dbf0 commit 9ea990b

12 files changed

Lines changed: 1085 additions & 22 deletions

examples/sdk_example/package.json

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,6 @@
3636
"roles:role-users": "ts-node src/roles/roleUsers.ts",
3737
"roles:manage-node": "ts-node src/roles/manageNode.ts",
3838
"roles:privileges": "ts-node src/roles/rolePrivileges.ts",
39-
"nsf:list": "ts-node src/nestedShareFolders/list_nsf.ts",
40-
"nsf:get": "ts-node src/nestedShareFolders/get_nsf.ts",
41-
"nsf:ln": "ts-node src/nestedShareFolders/link_nsf.ts",
42-
"nsf:rm": "ts-node src/nestedShareFolders/remove_nsf.ts",
43-
"nsf:record-details": "ts-node src/nestedShareFolders/get_nsf_record_details.ts",
44-
"nsf:record-update": "ts-node src/nestedShareFolders/update_nsf_record.ts",
45-
"nsf:record-add": "ts-node src/nestedShareFolders/add_nsf_record.ts",
46-
"nsf:mkdir": "ts-node src/nestedShareFolders/mkdir_nsf.ts",
47-
"nsf:rmdir": "ts-node src/nestedShareFolders/rmdir_nsf.ts",
48-
"nsf:share-folder": "ts-node src/nestedShareFolders/share_nsf_folder.ts",
49-
"nsf:share-record": "ts-node src/nestedShareFolders/share_nsf_record.ts",
50-
"nsf:record-permission": "ts-node src/nestedShareFolders/nsf_record_permission.ts",
51-
"nsf:shortcut": "ts-node src/nestedShareFolders/shortcut_nsf.ts",
52-
"nsf:transfer-record": "ts-node src/nestedShareFolders/transfer_nsf_record.ts",
53-
"nsf:rndir": "ts-node src/nestedShareFolders/rndir_nsf.ts",
5439
"teams:list": "ts-node src/teams/list_teams.ts",
5540
"teams:view": "ts-node src/teams/view_team.ts",
5641
"teams:add": "ts-node src/teams/add_team.ts",
@@ -68,15 +53,34 @@
6853
"reports:audit-report": "ts-node src/enterpriseReport/audit_report.ts",
6954
"reports:action-report": "ts-node src/enterpriseReport/action_report.ts",
7055
"reports:password-report": "ts-node src/enterpriseReport/password_report.ts",
71-
"link-local": "cd ../../KeeperSdk && npm link ../keeperapi && cd ../examples/sdk_example && npm link ../../keeperapi",
72-
"types": "tsc --watch",
73-
"types:ci": "tsc",
74-
"nsf:folder": "ts-node src/nestedShareFolders/nsf_folder.ts",
75-
"nsf:rndir": "ts-node src/nestedShareFolders/rndir_nsf.ts",
76-
"nsf:share": "ts-node src/nestedShareFolders/share_nsf.ts",
56+
"nsf:list": "ts-node src/nestedShareFolders/list_nsf.ts",
57+
"nsf:get": "ts-node src/nestedShareFolders/get_nsf.ts",
58+
"nsf:ln": "ts-node src/nestedShareFolders/link_nsf.ts",
59+
"nsf:rm": "ts-node src/nestedShareFolders/remove_nsf.ts",
60+
"nsf:record-details": "ts-node src/nestedShareFolders/get_nsf_record_details.ts",
61+
"nsf:record-update": "ts-node src/nestedShareFolders/update_nsf_record.ts",
62+
"nsf:record-add": "ts-node src/nestedShareFolders/add_nsf_record.ts",
63+
"nsf:mkdir": "ts-node src/nestedShareFolders/mkdir_nsf.ts",
64+
"nsf:rmdir": "ts-node src/nestedShareFolders/rmdir_nsf.ts",
65+
"nsf:share-folder": "ts-node src/nestedShareFolders/share_nsf_folder.ts",
66+
"nsf:share-record": "ts-node src/nestedShareFolders/share_nsf_record.ts",
67+
"nsf:record-permission": "ts-node src/nestedShareFolders/nsf_record_permission.ts",
7768
"nsf:shortcut": "ts-node src/nestedShareFolders/shortcut_nsf.ts",
7869
"nsf:transfer-record": "ts-node src/nestedShareFolders/transfer_nsf_record.ts",
79-
"nsf:record-permission": "ts-node src/nestedShareFolders/nsf_record_permission.ts"
70+
"nsf:rndir": "ts-node src/nestedShareFolders/rndir_nsf.ts",
71+
"nsf:folder": "ts-node src/nestedShareFolders/nsf_folder.ts",
72+
"pam:gateway:list": "ts-node src/pam/gateway/list_gateways.ts",
73+
"pam:gateway:new": "ts-node src/pam/gateway/create_gateway.ts",
74+
"pam:gateway:edit": "ts-node src/pam/gateway/edit_gateway.ts",
75+
"pam:gateway:remove": "ts-node src/pam/gateway/remove_gateway.ts",
76+
"pam:gateway:set-max-instances": "ts-node src/pam/gateway/set_max_instances.ts",
77+
"pam:config:list": "ts-node src/pam/config/list_configs.ts",
78+
"pam:config:new": "ts-node src/pam/config/create_config.ts",
79+
"pam:config:edit": "ts-node src/pam/config/edit_config.ts",
80+
"pam:config:remove": "ts-node src/pam/config/remove_config.ts",
81+
"link-local": "cd ../../KeeperSdk && npm link ../keeperapi && cd ../examples/sdk_example && npm link ../../keeperapi",
82+
"types": "tsc --watch",
83+
"types:ci": "tsc"
8084
},
8185
"dependencies": {
8286
"@keeper-security/keeperapi": "17.1.0",
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
import {
2+
logger,
3+
prompt,
4+
PAM_CONFIG_ENVIRONMENTS,
5+
type PamConfigEnvironment,
6+
type PamConfigurationPermissionValue,
7+
type PamConfigurationPermissionsInput,
8+
type PamConfigurationRecordFieldInput,
9+
} from '@keeper-security/keeper-sdk-javascript'
10+
import { isYes } from '../../utils/format'
11+
12+
export { PAM_CONFIG_ENVIRONMENTS }
13+
export type { PamConfigEnvironment }
14+
15+
export type PamConfigFieldsPromptResult = {
16+
fields: PamConfigurationRecordFieldInput[]
17+
adminCredentialUid?: string
18+
}
19+
20+
export type PamConfigFieldsPromptOptions = {
21+
includeSchedulePrompt?: boolean
22+
}
23+
24+
type PamConfigLabeledFieldType = 'text' | 'secret' | 'multiline' | 'json' | 'email' | 'checkbox'
25+
26+
type PermissionPromptDefinition = {
27+
key: keyof PamConfigurationPermissionsInput
28+
label: string
29+
}
30+
31+
const PERMISSION_PROMPT_DEFINITIONS: readonly PermissionPromptDefinition[] = [
32+
{ key: 'connections', label: 'Connections (-c)' },
33+
{ key: 'tunneling', label: 'Tunneling (-u)' },
34+
{ key: 'rotation', label: 'Rotation (-r)' },
35+
{ key: 'remoteBrowserIsolation', label: 'Remote browser isolation (-rbi)' },
36+
{ key: 'connectionsRecording', label: 'Connections recording (-cr)' },
37+
{ key: 'typescriptRecording', label: 'Typescript recording (-tr)' },
38+
{ key: 'aiThreatDetection', label: 'AI threat detection' },
39+
{ key: 'aiTerminateSessionOnDetection', label: 'AI terminate session on detection' },
40+
]
41+
42+
async function promptOptionalText(label: string): Promise<string> {
43+
return (await prompt(`${label} (optional): `)).trim()
44+
}
45+
46+
async function promptOptionalBoolean(label: string): Promise<boolean | undefined> {
47+
const raw = (await prompt(`${label} [y/N, Enter to skip]: `)).trim()
48+
if (!raw) return undefined
49+
return isYes(raw)
50+
}
51+
52+
function splitCommaSeparatedList(raw: string): string[] {
53+
return raw
54+
.split(',')
55+
.map((entry) => entry.trim())
56+
.filter(Boolean)
57+
}
58+
59+
function appendLabeledField(
60+
fields: PamConfigurationRecordFieldInput[],
61+
type: PamConfigLabeledFieldType,
62+
label: string,
63+
value: string | boolean | undefined
64+
): void {
65+
if (value == null) return
66+
if (typeof value === 'string' && value.length === 0) return
67+
fields.push({ type, label, value: [value] })
68+
}
69+
70+
function appendMultilineField(
71+
fields: PamConfigurationRecordFieldInput[],
72+
label: string,
73+
values: string[]
74+
): void {
75+
if (values.length === 0) return
76+
fields.push({ type: 'multiline', label, value: [values.join('\n')] })
77+
}
78+
79+
async function promptPermissionFlagValue(label: string): Promise<PamConfigurationPermissionValue | undefined> {
80+
const raw = (await prompt(`${label} [on|off|default, Enter to skip]: `)).trim().toLowerCase()
81+
if (!raw) return undefined
82+
if (raw === 'on' || raw === 'off' || raw === 'default') return raw
83+
logger.info(` Invalid "${raw}". Skipping (use on, off, or default).`)
84+
return undefined
85+
}
86+
87+
export async function promptPamConfigurationPermissions(): Promise<
88+
PamConfigurationPermissionsInput | undefined
89+
> {
90+
const wantPermissions = isYes(await prompt('Set additional permissions? [y/N]: '))
91+
if (!wantPermissions) return undefined
92+
93+
const permissions: PamConfigurationPermissionsInput = {}
94+
let anySet = false
95+
for (const entry of PERMISSION_PROMPT_DEFINITIONS) {
96+
const value = await promptPermissionFlagValue(entry.label)
97+
if (value) {
98+
permissions[entry.key] = value
99+
anySet = true
100+
}
101+
}
102+
return anySet ? permissions : undefined
103+
}
104+
105+
async function promptAwsConfigurationFields(fields: PamConfigurationRecordFieldInput[]): Promise<void> {
106+
appendLabeledField(fields, 'text', 'awsId', await promptOptionalText('AWS ID (--aws-id)'))
107+
appendLabeledField(fields, 'secret', 'accessKeyId', await promptOptionalText('Access Key ID (--access-key-id)'))
108+
appendLabeledField(
109+
fields,
110+
'secret',
111+
'accessSecretKey',
112+
await promptOptionalText('Access Secret Key (--access-secret-key)')
113+
)
114+
appendMultilineField(
115+
fields,
116+
'regionNames',
117+
splitCommaSeparatedList(await promptOptionalText('Region names, comma-separated (--region-name)'))
118+
)
119+
}
120+
121+
async function promptAzureConfigurationFields(fields: PamConfigurationRecordFieldInput[]): Promise<void> {
122+
appendLabeledField(fields, 'text', 'azureId', await promptOptionalText('Azure ID (--azure-id)'))
123+
appendLabeledField(fields, 'secret', 'clientId', await promptOptionalText('Client ID (--client-id)'))
124+
appendLabeledField(fields, 'secret', 'clientSecret', await promptOptionalText('Client Secret (--client-secret)'))
125+
appendLabeledField(
126+
fields,
127+
'secret',
128+
'subscriptionId',
129+
await promptOptionalText('Subscription ID (--subscription_id)')
130+
)
131+
appendLabeledField(fields, 'secret', 'tenantId', await promptOptionalText('Tenant ID (--tenant-id)'))
132+
appendMultilineField(
133+
fields,
134+
'resourceGroups',
135+
splitCommaSeparatedList(await promptOptionalText('Resource groups, comma-separated (--resource-group)'))
136+
)
137+
}
138+
139+
async function promptGcpConfigurationFields(fields: PamConfigurationRecordFieldInput[]): Promise<void> {
140+
appendLabeledField(fields, 'text', 'pamGcpId', await promptOptionalText('GCP ID (--gcp-id)'))
141+
appendLabeledField(
142+
fields,
143+
'json',
144+
'pamServiceAccountKey',
145+
await promptOptionalText('Service Account Key JSON (--service-account-key)')
146+
)
147+
appendLabeledField(
148+
fields,
149+
'email',
150+
'pamGoogleAdminEmail',
151+
await promptOptionalText('Google Admin Email (--google-admin-email)')
152+
)
153+
appendMultilineField(
154+
fields,
155+
'pamGcpRegionName',
156+
splitCommaSeparatedList(await promptOptionalText('GCP regions, comma-separated (--gcp-region)'))
157+
)
158+
}
159+
160+
async function promptGitHubConfigurationFields(fields: PamConfigurationRecordFieldInput[]): Promise<void> {
161+
appendLabeledField(fields, 'text', 'pamGitHubId', await promptOptionalText('GitHub ID (--github-id)'))
162+
appendLabeledField(
163+
fields,
164+
'secret',
165+
'personalAccessToken',
166+
await promptOptionalText('Personal Access Token (--personal-access-token)')
167+
)
168+
appendLabeledField(
169+
fields,
170+
'text',
171+
'pamGitHubBaseUrl',
172+
await promptOptionalText('GitHub Base URL (--github-base-url)')
173+
)
174+
}
175+
176+
async function promptDomainConfigurationFields(
177+
fields: PamConfigurationRecordFieldInput[]
178+
): Promise<string | undefined> {
179+
appendLabeledField(fields, 'text', 'pamDomainId', await promptOptionalText('Domain ID (--domain-id)'))
180+
const hostname = await promptOptionalText('Domain hostname (--domain-hostname)')
181+
const port = await promptOptionalText('Domain port (--domain-port)')
182+
if (hostname || port) {
183+
fields.push({
184+
type: 'pamHostname',
185+
value: [{ hostName: hostname || '', port: port || '' }],
186+
})
187+
}
188+
const useSsl = await promptOptionalBoolean('Use SSL (--domain-use-ssl)')
189+
if (useSsl != null) appendLabeledField(fields, 'checkbox', 'useSSL', useSsl)
190+
const scanDcCidr = await promptOptionalBoolean('Scan DC CIDR (--domain-scan-dc-cidr)')
191+
if (scanDcCidr != null) appendLabeledField(fields, 'checkbox', 'scanDCCIDR', scanDcCidr)
192+
appendLabeledField(
193+
fields,
194+
'text',
195+
'networkCIDR',
196+
await promptOptionalText('Domain network CIDR (--domain-network-cidr)')
197+
)
198+
appendLabeledField(
199+
fields,
200+
'text',
201+
'userMatch',
202+
await promptOptionalText('Domain user match (--domain-user-match)')
203+
)
204+
const domainAdmin = await promptOptionalText('Domain admin pamUser UID/title (--domain-admin)')
205+
return domainAdmin || undefined
206+
}
207+
208+
async function promptOciConfigurationFields(fields: PamConfigurationRecordFieldInput[]): Promise<void> {
209+
appendLabeledField(fields, 'text', 'pamOciId', await promptOptionalText('OCI ID (--oci-id)'))
210+
appendLabeledField(fields, 'secret', 'adminOcid', await promptOptionalText('OCI Admin OCID (--oci-admin-id)'))
211+
appendLabeledField(
212+
fields,
213+
'secret',
214+
'adminPublicKey',
215+
await promptOptionalText('OCI Admin Public Key (--oci-admin-public-key)')
216+
)
217+
appendLabeledField(
218+
fields,
219+
'secret',
220+
'adminPrivateKey',
221+
await promptOptionalText('OCI Admin Private Key (--oci-admin-private-key)')
222+
)
223+
appendLabeledField(fields, 'text', 'tenancyOci', await promptOptionalText('OCI Tenancy (--oci-tenancy)'))
224+
appendLabeledField(fields, 'text', 'regionOci', await promptOptionalText('OCI Region (--oci-region)'))
225+
}
226+
227+
async function promptLocalConfigurationFields(fields: PamConfigurationRecordFieldInput[]): Promise<void> {
228+
appendLabeledField(fields, 'text', 'networkId', await promptOptionalText('Network ID (--network-id)'))
229+
appendLabeledField(fields, 'text', 'networkCIDR', await promptOptionalText('Network CIDR (--network-cidr)'))
230+
}
231+
232+
async function promptCommonOptionalFields(
233+
fields: PamConfigurationRecordFieldInput[],
234+
options: PamConfigFieldsPromptOptions
235+
): Promise<void> {
236+
appendLabeledField(
237+
fields,
238+
'text',
239+
'identityProviderUid',
240+
await promptOptionalText('Identity Provider UID (--identity-provider)')
241+
)
242+
243+
if (options.includeSchedulePrompt !== false) {
244+
const scheduleCron = (await prompt('Default rotation CRON (--schedule, Enter to skip): ')).trim()
245+
if (scheduleCron) {
246+
fields.push({
247+
type: 'schedule',
248+
label: 'defaultRotationSchedule',
249+
value: [{ type: 'CRON', cron: scheduleCron, tz: 'Etc/UTC' }],
250+
})
251+
}
252+
}
253+
254+
const portMappingsRaw = await promptOptionalText(
255+
'Port mappings, comma-separated port=protocol (--port-mapping)'
256+
)
257+
if (!portMappingsRaw) return
258+
259+
const portMappingLines = portMappingsRaw
260+
.split(',')
261+
.map((entry) => entry.trim())
262+
.filter(Boolean)
263+
.map((entry) => {
264+
if (entry.includes('=')) return entry
265+
const [port, protocol] = entry.split(':')
266+
return protocol ? `${port.trim()}=${protocol.trim()}` : port.trim()
267+
})
268+
.filter(Boolean)
269+
appendMultilineField(fields, 'portMapping', portMappingLines)
270+
}
271+
272+
export async function promptPamConfigurationFields(
273+
environment: string,
274+
options: PamConfigFieldsPromptOptions = {}
275+
): Promise<PamConfigFieldsPromptResult> {
276+
const fields: PamConfigurationRecordFieldInput[] = []
277+
let adminCredentialUid: string | undefined
278+
279+
switch (environment) {
280+
case 'aws':
281+
await promptAwsConfigurationFields(fields)
282+
break
283+
case 'azure':
284+
await promptAzureConfigurationFields(fields)
285+
break
286+
case 'gcp':
287+
await promptGcpConfigurationFields(fields)
288+
break
289+
case 'github':
290+
await promptGitHubConfigurationFields(fields)
291+
break
292+
case 'domain':
293+
adminCredentialUid = await promptDomainConfigurationFields(fields)
294+
break
295+
case 'oci':
296+
await promptOciConfigurationFields(fields)
297+
break
298+
case 'local':
299+
default:
300+
await promptLocalConfigurationFields(fields)
301+
break
302+
}
303+
304+
await promptCommonOptionalFields(fields, options)
305+
return { fields, adminCredentialUid }
306+
}

0 commit comments

Comments
 (0)