From 8406863d8c97d7de216311e8639393b6c1edbdb9 Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Tue, 28 Jul 2026 15:51:11 +0530 Subject: [PATCH] Implement pam gateway remove and set-max-instnance commands --- KeeperSdk/src/index.ts | 11 ++++ KeeperSdk/src/pam/PamManager.ts | 20 ++++++ KeeperSdk/src/pam/gateway/GatewayManager.ts | 22 +++++++ KeeperSdk/src/pam/gateway/createGateway.ts | 4 +- KeeperSdk/src/pam/gateway/editGateway.ts | 32 +++------- KeeperSdk/src/pam/gateway/gatewayHelpers.ts | 51 +++++++++++---- KeeperSdk/src/pam/gateway/gatewayTypes.ts | 33 ++++++++++ KeeperSdk/src/pam/gateway/index.ts | 9 +++ KeeperSdk/src/pam/gateway/listGateways.ts | 17 +---- KeeperSdk/src/pam/gateway/removeGateway.ts | 62 +++++++++++++++++++ .../src/pam/gateway/setGatewayMaxInstances.ts | 57 +++++++++++++++++ KeeperSdk/src/pam/index.ts | 11 ++++ KeeperSdk/src/utils/constants.ts | 6 ++ KeeperSdk/src/vault/KeeperVault.ts | 20 ++++++ examples/sdk_example/package.json | 2 + .../src/pam/gateway/remove_gateway.ts | 44 +++++++++++++ .../src/pam/gateway/set_max_instances.ts | 50 +++++++++++++++ keeperapi/src/restMessages.ts | 10 +++ 18 files changed, 411 insertions(+), 50 deletions(-) create mode 100644 KeeperSdk/src/pam/gateway/removeGateway.ts create mode 100644 KeeperSdk/src/pam/gateway/setGatewayMaxInstances.ts create mode 100644 examples/sdk_example/src/pam/gateway/remove_gateway.ts create mode 100644 examples/sdk_example/src/pam/gateway/set_max_instances.ts diff --git a/KeeperSdk/src/index.ts b/KeeperSdk/src/index.ts index b62df02..0de80d3 100644 --- a/KeeperSdk/src/index.ts +++ b/KeeperSdk/src/index.ts @@ -665,6 +665,10 @@ export { formatCreateGatewayOutput, editGateway, formatEditGatewayOutput, + removeGateway, + formatRemoveGatewayOutput, + setGatewayMaxInstances, + formatSetGatewayMaxInstancesOutput, GatewayListFormat, GatewayStatus, GatewayConfigInitFormat, @@ -678,6 +682,7 @@ export { GATEWAY_LIST_VERBOSE_HEADERS, getKeeperRouterBaseUrl, webSafeUidFromBytes, + controllerUidsEqual, toFiniteNumber, formatTimestampMs, parseGatewayVersionString, @@ -686,6 +691,8 @@ export { getKeeperRegionAbbreviation, formatGatewayOneTimeToken, findEnterpriseGatewayByUidOrName, + requireEnterpriseGatewayByUidOrName, + fetchEnterprisePamControllers, groupOnlineGatewaysByControllerUid, isKeeperRouterConnectionError, } from './pam' @@ -709,6 +716,10 @@ export type { CreateGatewayResult, EditGatewayInput, EditGatewayResult, + RemoveGatewayInput, + RemoveGatewayResult, + SetGatewayMaxInstancesInput, + SetGatewayMaxInstancesResult, GatewayJsonPoolInstance, GatewayJsonEntry, GatewaysJsonPayload, diff --git a/KeeperSdk/src/pam/PamManager.ts b/KeeperSdk/src/pam/PamManager.ts index bdc4202..330d50e 100644 --- a/KeeperSdk/src/pam/PamManager.ts +++ b/KeeperSdk/src/pam/PamManager.ts @@ -10,7 +10,11 @@ import type { FormattedGatewaysTable, ListGatewaysOptions, ListGatewaysResult, + RemoveGatewayInput, + RemoveGatewayResult, RenderGatewaysAsciiTableOptions, + SetGatewayMaxInstancesInput, + SetGatewayMaxInstancesResult, } from './gateway/gatewayTypes' export type AuthProvider = () => Auth @@ -49,6 +53,22 @@ export class PamManager { return this.gatewayManager.formatEditGatewayOutput(result) } + public async removeGateway(input: RemoveGatewayInput): Promise { + return this.gatewayManager.removeGateway(input) + } + + public formatRemoveGatewayOutput(result: RemoveGatewayResult): string { + return this.gatewayManager.formatRemoveGatewayOutput(result) + } + + public async setGatewayMaxInstances(input: SetGatewayMaxInstancesInput): Promise { + return this.gatewayManager.setGatewayMaxInstances(input) + } + + public formatSetGatewayMaxInstancesOutput(result: SetGatewayMaxInstancesResult): string { + return this.gatewayManager.formatSetGatewayMaxInstancesOutput(result) + } + public formatGatewaysTable( result: ListGatewaysResult, options: FormatGatewaysTableOptions = {} diff --git a/KeeperSdk/src/pam/gateway/GatewayManager.ts b/KeeperSdk/src/pam/gateway/GatewayManager.ts index a4568a1..e699949 100644 --- a/KeeperSdk/src/pam/gateway/GatewayManager.ts +++ b/KeeperSdk/src/pam/gateway/GatewayManager.ts @@ -3,6 +3,8 @@ import type { InMemoryStorage } from '../../storage/InMemoryStorage' import { KeeperSdkError, ResultCodes } from '../../utils' import { createGateway, formatCreateGatewayOutput } from './createGateway' import { editGateway, formatEditGatewayOutput } from './editGateway' +import { removeGateway, formatRemoveGatewayOutput } from './removeGateway' +import { formatSetGatewayMaxInstancesOutput, setGatewayMaxInstances } from './setGatewayMaxInstances' import { formatGatewaysJson, formatGatewaysOutput, @@ -19,7 +21,11 @@ import type { FormattedGatewaysTable, ListGatewaysOptions, ListGatewaysResult, + RemoveGatewayInput, + RemoveGatewayResult, RenderGatewaysAsciiTableOptions, + SetGatewayMaxInstancesInput, + SetGatewayMaxInstancesResult, } from './gatewayTypes' export type AuthProvider = () => Auth @@ -64,6 +70,22 @@ export class GatewayManager { return formatEditGatewayOutput(result) } + public async removeGateway(input: RemoveGatewayInput): Promise { + return removeGateway(this.requireAuth(), input) + } + + public formatRemoveGatewayOutput(result: RemoveGatewayResult): string { + return formatRemoveGatewayOutput(result) + } + + public async setGatewayMaxInstances(input: SetGatewayMaxInstancesInput): Promise { + return setGatewayMaxInstances(this.requireAuth(), input) + } + + public formatSetGatewayMaxInstancesOutput(result: SetGatewayMaxInstancesResult): string { + return formatSetGatewayMaxInstancesOutput(result) + } + public formatGatewaysTable( result: ListGatewaysResult, options: FormatGatewaysTableOptions = {} diff --git a/KeeperSdk/src/pam/gateway/createGateway.ts b/KeeperSdk/src/pam/gateway/createGateway.ts index 8655302..45a3212 100644 --- a/KeeperSdk/src/pam/gateway/createGateway.ts +++ b/KeeperSdk/src/pam/gateway/createGateway.ts @@ -149,11 +149,10 @@ function buildCreateGatewayMessage( tokenExpiresInMin: number, isInitializedConfig: boolean ): string { - const base = `The one-time token was created in application [${appLabel}]. The new Gateway named ${gatewayName} will show up in the gateway list once it is initialized.` if (isInitializedConfig) { return `The one-time token was created in application [${appLabel}]. Use the initialized config in the Gateway. The new Gateway named ${gatewayName} will show up in the gateway list once it is initialized.` } - return `${base} Token expires in ${tokenExpiresInMin} minutes.` + return `The one-time token was created in application [${appLabel}]. The new Gateway named ${gatewayName} will show up in the gateway list once it is initialized. Token expires in ${tokenExpiresInMin} minutes.` } export async function createGateway( @@ -221,7 +220,6 @@ export async function createGateway( ? await initKsmConfigFromToken(oneTimeToken, host, configInit) : oneTimeToken - // Automation: return only the OTT / initialized config string (Commander -r). if (returnValue) { return tokenOrConfig } diff --git a/KeeperSdk/src/pam/gateway/editGateway.ts b/KeeperSdk/src/pam/gateway/editGateway.ts index 9dfd81f..036e69a 100644 --- a/KeeperSdk/src/pam/gateway/editGateway.ts +++ b/KeeperSdk/src/pam/gateway/editGateway.ts @@ -1,15 +1,16 @@ -import type { Auth, PAM } from '@keeper-security/keeperapi' -import { createInMessage, getControllers, normal64Bytes, PAM as PamProto } from '@keeper-security/keeperapi' +import type { Auth } from '@keeper-security/keeperapi' +import { modifyControllerMessage, normal64Bytes } from '@keeper-security/keeperapi' import { EnterpriseDataInclude, EnterpriseDataManager } from '../../teams/enterpriseData' import { applyDecryptedNodeNames, resolveParentNode } from '../../teams/teamUtils' import { extractErrorMessage, KeeperSdkError, ResultCodes } from '../../utils' -import { findEnterpriseGatewayByUidOrName, toFiniteNumber, webSafeUidFromBytes } from './gatewayHelpers' +import { + fetchEnterprisePamControllers, + requireEnterpriseGatewayByUidOrName, + toFiniteNumber, + webSafeUidFromBytes, +} from './gatewayHelpers' import type { EditGatewayInput, EditGatewayResult } from './gatewayTypes' -function modifyControllerMessage(data: PAM.IPAMController) { - return createInMessage(data, 'pam/modify_controller', PamProto.PAMController) -} - function hasNodeArgument(nodeIdOrName: EditGatewayInput['nodeIdOrName']): boolean { return ( nodeIdOrName !== undefined && @@ -73,21 +74,8 @@ export async function editGateway(auth: Auth, input: EditGatewayInput): Promise< ) } - let controllers: PAM.IPAMController[] - try { - const response = await auth.executeRest(getControllers()) - controllers = response.controllers ?? [] - } catch (err) { - throw new KeeperSdkError( - `Failed to list enterprise gateways: ${extractErrorMessage(err)}`, - ResultCodes.PAM_GATEWAY_EDIT_FAILED - ) - } - - const gateway = findEnterpriseGatewayByUidOrName(controllers, gatewayUidOrName) - if (!gateway?.controllerUid?.length) { - throw new KeeperSdkError(`Gateway "${gatewayUidOrName}" not found.`, ResultCodes.PAM_GATEWAY_NOT_FOUND) - } + const controllers = await fetchEnterprisePamControllers(auth, ResultCodes.PAM_GATEWAY_EDIT_FAILED) + const gateway = requireEnterpriseGatewayByUidOrName(controllers, gatewayUidOrName) const gatewayUid = webSafeUidFromBytes(gateway.controllerUid) const previousName = gateway.controllerName || '' diff --git a/KeeperSdk/src/pam/gateway/gatewayHelpers.ts b/KeeperSdk/src/pam/gateway/gatewayHelpers.ts index 98165bc..c927ea6 100644 --- a/KeeperSdk/src/pam/gateway/gatewayHelpers.ts +++ b/KeeperSdk/src/pam/gateway/gatewayHelpers.ts @@ -1,23 +1,21 @@ -import type { DRecord, PAM } from '@keeper-security/keeperapi' -import { getKeeperRouterUrl, webSafe64FromBytes } from '@keeper-security/keeperapi' +import type { Auth, DRecord, PAM } from '@keeper-security/keeperapi' +import { getControllers, getKeeperRouterUrl, webSafe64FromBytes } from '@keeper-security/keeperapi' import type { InMemoryStorage } from '../../storage/InMemoryStorage' import { VaultObjectKind } from '../../folders/folderHelpers' import { getRecordTitle } from '../../records/RecordUtils' -import { KEEPER_PUBLIC_HOSTS, KeeperSdkError, ResultCodes } from '../../utils' +import { KEEPER_PUBLIC_HOSTS, KeeperSdkError, ResultCodes, extractErrorMessage } from '../../utils' import { APP_NOT_ACCESSIBLE_LABEL, KSM_APP_RECORD_VERSION, ROUTER_CONNECTION_ERROR_CODES, type RouterConnectionErrorCode, } from './gatewayConstants' -import type { GatewayVersionParts, KsmApplicationDisplayInfo, ResolvedKsmApplication } from './gatewayTypes' - -type NetworkErrorLike = { - code?: string - errno?: string - message?: string - cause?: { code?: string } -} +import type { + GatewayVersionParts, + KsmApplicationDisplayInfo, + NetworkErrorLike, + ResolvedKsmApplication, +} from './gatewayTypes' export function getKeeperRouterBaseUrl(host: string): string { return getKeeperRouterUrl(host, '').replace(/\/$/, '') @@ -28,6 +26,14 @@ export function webSafeUidFromBytes(bytes: Uint8Array | null | undefined): strin return webSafe64FromBytes(bytes) } +export function controllerUidsEqual(a: Uint8Array | null | undefined, b: Uint8Array | null | undefined): boolean { + if (!a || !b || a.length !== b.length) return false + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false + } + return true +} + export function toFiniteNumber(value: unknown): number { if (value == null) return 0 if (typeof value === 'number') return Number.isFinite(value) ? value : 0 @@ -194,6 +200,29 @@ export function findEnterpriseGatewayByUidOrName( }) } +export function requireEnterpriseGatewayByUidOrName( + controllers: readonly PAM.IPAMController[], + gatewayUidOrName: string +): PAM.IPAMController { + const gateway = findEnterpriseGatewayByUidOrName(controllers, gatewayUidOrName) + if (!gateway?.controllerUid?.length) { + throw new KeeperSdkError(`Gateway "${gatewayUidOrName}" not found.`, ResultCodes.PAM_GATEWAY_NOT_FOUND) + } + return gateway +} + +export async function fetchEnterprisePamControllers( + auth: Auth, + failureResultCode: string +): Promise { + try { + const response = await auth.executeRest(getControllers()) + return response.controllers ?? [] + } catch (err) { + throw new KeeperSdkError(`Failed to list enterprise gateways: ${extractErrorMessage(err)}`, failureResultCode) + } +} + export function groupOnlineGatewaysByControllerUid( controllers: readonly PAM.IPAMOnlineController[] ): Map { diff --git a/KeeperSdk/src/pam/gateway/gatewayTypes.ts b/KeeperSdk/src/pam/gateway/gatewayTypes.ts index beec60a..ced8083 100644 --- a/KeeperSdk/src/pam/gateway/gatewayTypes.ts +++ b/KeeperSdk/src/pam/gateway/gatewayTypes.ts @@ -120,6 +120,7 @@ export type CreateGatewayInput = { application: string tokenExpiresInMin?: number configInit?: GatewayConfigInitFormatInput + /** When true, return only the OTT/config string (automation / -r). */ returnValue?: boolean } @@ -155,6 +156,31 @@ export type EditGatewayResult = { message: string } +export type RemoveGatewayInput = { + gatewayUidOrName: string +} + +export type RemoveGatewayResult = { + success: boolean + found: boolean + gatewayUid?: string + gatewayName?: string + message: string +} + +export type SetGatewayMaxInstancesInput = { + gatewayUidOrName: string + maxInstances: number +} + +export type SetGatewayMaxInstancesResult = { + success: boolean + gatewayUid: string + gatewayName: string + maxInstances: number + message: string +} + export type GatewayJsonPoolInstance = { instance_number: number status: typeof GatewayStatus.Online @@ -193,3 +219,10 @@ export type GatewaysJsonPayload = { gateway_counts?: GatewayCounts message?: string } + +export type NetworkErrorLike = { + code?: string + errno?: string + message?: string + cause?: { code?: string } +} diff --git a/KeeperSdk/src/pam/gateway/index.ts b/KeeperSdk/src/pam/gateway/index.ts index dc71b9a..42ea501 100644 --- a/KeeperSdk/src/pam/gateway/index.ts +++ b/KeeperSdk/src/pam/gateway/index.ts @@ -11,6 +11,8 @@ export { export { createGateway, formatCreateGatewayOutput } from './createGateway' export { editGateway, formatEditGatewayOutput } from './editGateway' +export { removeGateway, formatRemoveGatewayOutput } from './removeGateway' +export { setGatewayMaxInstances, formatSetGatewayMaxInstancesOutput } from './setGatewayMaxInstances' export { GatewayListFormat, GatewayStatus, GatewayConfigInitFormat } from './gatewayTypes' export type { @@ -33,6 +35,10 @@ export type { CreateGatewayResult, EditGatewayInput, EditGatewayResult, + RemoveGatewayInput, + RemoveGatewayResult, + SetGatewayMaxInstancesInput, + SetGatewayMaxInstancesResult, GatewayJsonPoolInstance, GatewayJsonEntry, GatewaysJsonPayload, @@ -52,6 +58,7 @@ export { export { getKeeperRouterBaseUrl, webSafeUidFromBytes, + controllerUidsEqual, toFiniteNumber, formatTimestampMs, parseGatewayVersionString, @@ -60,6 +67,8 @@ export { getKeeperRegionAbbreviation, formatGatewayOneTimeToken, findEnterpriseGatewayByUidOrName, + requireEnterpriseGatewayByUidOrName, + fetchEnterprisePamControllers, groupOnlineGatewaysByControllerUid, isKeeperRouterConnectionError, } from './gatewayHelpers' diff --git a/KeeperSdk/src/pam/gateway/listGateways.ts b/KeeperSdk/src/pam/gateway/listGateways.ts index 11868b5..fdb8c60 100644 --- a/KeeperSdk/src/pam/gateway/listGateways.ts +++ b/KeeperSdk/src/pam/gateway/listGateways.ts @@ -1,9 +1,10 @@ import type { Auth, PAM } from '@keeper-security/keeperapi' -import { getControllers, pamGetOnlineControllersMessage } from '@keeper-security/keeperapi' +import { pamGetOnlineControllersMessage } from '@keeper-security/keeperapi' import type { InMemoryStorage } from '../../storage/InMemoryStorage' import { extractErrorMessage, KeeperSdkError, ResultCodes } from '../../utils' import { EMPTY_GATEWAYS_MESSAGE, GATEWAY_LIST_DEFAULT_HEADERS, GATEWAY_LIST_VERBOSE_HEADERS } from './gatewayConstants' import { + fetchEnterprisePamControllers, formatTimestampMs, getKeeperRouterBaseUrl, getKsmApplicationDisplayInfo, @@ -101,18 +102,6 @@ async function loadOnlineControllers( } } -async function loadEnterpriseControllers(auth: Auth): Promise { - try { - const response = await auth.executeRest(getControllers()) - return response.controllers ?? [] - } catch (err) { - throw new KeeperSdkError( - `Failed to list enterprise gateways: ${extractErrorMessage(err)}`, - ResultCodes.PAM_GATEWAY_LIST_FAILED - ) - } -} - function resolveConnectivityStatus(routerDown: boolean, connectedCount: number): GatewayListRow['status'] { if (routerDown) return GatewayStatus.Unknown if (connectedCount === 0) return GatewayStatus.Offline @@ -243,7 +232,7 @@ export async function listGateways( const online = await loadOnlineControllers(auth, force, routerHost) if (online.abort) return online.abort - const enterpriseControllers = await loadEnterpriseControllers(auth) + const enterpriseControllers = await fetchEnterprisePamControllers(auth, ResultCodes.PAM_GATEWAY_LIST_FAILED) if (!enterpriseControllers.length) { return emptyListResult({ routerDown: online.routerDown, diff --git a/KeeperSdk/src/pam/gateway/removeGateway.ts b/KeeperSdk/src/pam/gateway/removeGateway.ts new file mode 100644 index 0000000..aa8ea35 --- /dev/null +++ b/KeeperSdk/src/pam/gateway/removeGateway.ts @@ -0,0 +1,62 @@ +import type { Auth } from '@keeper-security/keeperapi' +import { removeControllerMessage } from '@keeper-security/keeperapi' +import { extractErrorMessage, KeeperSdkError, ResultCodes } from '../../utils' +import { + controllerUidsEqual, + fetchEnterprisePamControllers, + findEnterpriseGatewayByUidOrName, + webSafeUidFromBytes, +} from './gatewayHelpers' +import type { RemoveGatewayInput, RemoveGatewayResult } from './gatewayTypes' + +export async function removeGateway(auth: Auth, input: RemoveGatewayInput): Promise { + const gatewayUidOrName = input.gatewayUidOrName?.trim() || '' + if (!gatewayUidOrName) { + throw new KeeperSdkError('Gateway UID or name is required.', ResultCodes.PAM_GATEWAY_REQUIRED) + } + + const controllers = await fetchEnterprisePamControllers(auth, ResultCodes.PAM_GATEWAY_REMOVE_FAILED) + const gateway = findEnterpriseGatewayByUidOrName(controllers, gatewayUidOrName) + if (!gateway?.controllerUid?.length) { + return { + success: false, + found: false, + message: `Gateway ${gatewayUidOrName} not found`, + } + } + + const gatewayUid = webSafeUidFromBytes(gateway.controllerUid) + const gatewayName = gateway.controllerName || gatewayUid + const controllerUidBytes = gateway.controllerUid + + try { + const response = await auth.executeRest(removeControllerMessage({ uid: controllerUidBytes })) + const errorEntry = (response.controllers ?? []).find((entry) => + controllerUidsEqual(entry.controllerUid, controllerUidBytes) + ) + if (errorEntry) { + throw new KeeperSdkError( + errorEntry.message || `Failed to remove gateway ${gatewayName}.`, + ResultCodes.PAM_GATEWAY_REMOVE_FAILED + ) + } + } catch (err) { + if (err instanceof KeeperSdkError) throw err + throw new KeeperSdkError( + `Failed to remove gateway: ${extractErrorMessage(err)}`, + ResultCodes.PAM_GATEWAY_REMOVE_FAILED + ) + } + + return { + success: true, + found: true, + gatewayUid, + gatewayName, + message: `Gateway ${gatewayName} has been removed.`, + } +} + +export function formatRemoveGatewayOutput(result: RemoveGatewayResult): string { + return result.message +} diff --git a/KeeperSdk/src/pam/gateway/setGatewayMaxInstances.ts b/KeeperSdk/src/pam/gateway/setGatewayMaxInstances.ts new file mode 100644 index 0000000..1110b6a --- /dev/null +++ b/KeeperSdk/src/pam/gateway/setGatewayMaxInstances.ts @@ -0,0 +1,57 @@ +import type { Auth } from '@keeper-security/keeperapi' +import { setControllerMaxInstanceCountMessage } from '@keeper-security/keeperapi' +import { extractErrorMessage, KeeperSdkError, ResultCodes } from '../../utils' +import { + fetchEnterprisePamControllers, + requireEnterpriseGatewayByUidOrName, + webSafeUidFromBytes, +} from './gatewayHelpers' +import type { SetGatewayMaxInstancesInput, SetGatewayMaxInstancesResult } from './gatewayTypes' + +export async function setGatewayMaxInstances( + auth: Auth, + input: SetGatewayMaxInstancesInput +): Promise { + const gatewayUidOrName = input.gatewayUidOrName?.trim() || '' + if (!gatewayUidOrName) { + throw new KeeperSdkError('Gateway UID or name is required.', ResultCodes.PAM_GATEWAY_REQUIRED) + } + + const maxInstances = Number(input.maxInstances) + if (!Number.isFinite(maxInstances) || maxInstances < 1) { + throw new KeeperSdkError('--max-instances must be at least 1', ResultCodes.PAM_GATEWAY_INVALID_MAX_INSTANCES) + } + const maxInstanceCount = Math.floor(maxInstances) + + const controllers = await fetchEnterprisePamControllers(auth, ResultCodes.PAM_GATEWAY_SET_MAX_INSTANCES_FAILED) + const gateway = requireEnterpriseGatewayByUidOrName(controllers, gatewayUidOrName) + const gatewayUid = webSafeUidFromBytes(gateway.controllerUid) + const gatewayName = gateway.controllerName || gatewayUid + + try { + await auth.executeRestAction( + setControllerMaxInstanceCountMessage({ + controllerUid: gateway.controllerUid, + maxInstanceCount, + }) + ) + } catch (err) { + if (err instanceof KeeperSdkError) throw err + throw new KeeperSdkError( + `Failed to set max instances: ${extractErrorMessage(err)}`, + ResultCodes.PAM_GATEWAY_SET_MAX_INSTANCES_FAILED + ) + } + + return { + success: true, + gatewayUid, + gatewayName, + maxInstances: maxInstanceCount, + message: `${gatewayName}: max instance count set to ${maxInstanceCount}`, + } +} + +export function formatSetGatewayMaxInstancesOutput(result: SetGatewayMaxInstancesResult): string { + return result.message +} diff --git a/KeeperSdk/src/pam/index.ts b/KeeperSdk/src/pam/index.ts index f6adf65..ccb981f 100644 --- a/KeeperSdk/src/pam/index.ts +++ b/KeeperSdk/src/pam/index.ts @@ -12,6 +12,10 @@ export { formatCreateGatewayOutput, editGateway, formatEditGatewayOutput, + removeGateway, + formatRemoveGatewayOutput, + setGatewayMaxInstances, + formatSetGatewayMaxInstancesOutput, GatewayListFormat, GatewayStatus, GatewayConfigInitFormat, @@ -25,6 +29,7 @@ export { GATEWAY_LIST_VERBOSE_HEADERS, getKeeperRouterBaseUrl, webSafeUidFromBytes, + controllerUidsEqual, toFiniteNumber, formatTimestampMs, parseGatewayVersionString, @@ -33,6 +38,8 @@ export { getKeeperRegionAbbreviation, formatGatewayOneTimeToken, findEnterpriseGatewayByUidOrName, + requireEnterpriseGatewayByUidOrName, + fetchEnterprisePamControllers, groupOnlineGatewaysByControllerUid, isKeeperRouterConnectionError, } from './gateway' @@ -57,6 +64,10 @@ export type { CreateGatewayResult, EditGatewayInput, EditGatewayResult, + RemoveGatewayInput, + RemoveGatewayResult, + SetGatewayMaxInstancesInput, + SetGatewayMaxInstancesResult, GatewayJsonPoolInstance, GatewayJsonEntry, GatewaysJsonPayload, diff --git a/KeeperSdk/src/utils/constants.ts b/KeeperSdk/src/utils/constants.ts index 2aeffe2..2e9146d 100644 --- a/KeeperSdk/src/utils/constants.ts +++ b/KeeperSdk/src/utils/constants.ts @@ -159,6 +159,9 @@ export enum PamErrorCode { GatewayEditFailed = 'pam_gateway_edit_failed', GatewayNodeNotFound = 'pam_gateway_node_not_found', MultipleGatewayNodeMatches = 'pam_multiple_gateway_node_matches', + GatewayRemoveFailed = 'pam_gateway_remove_failed', + GatewayInvalidMaxInstances = 'pam_gateway_invalid_max_instances', + GatewaySetMaxInstancesFailed = 'pam_gateway_set_max_instances_failed', } export const ResultCodes = { @@ -256,6 +259,9 @@ export const ResultCodes = { PAM_GATEWAY_EDIT_FAILED: PamErrorCode.GatewayEditFailed, PAM_GATEWAY_NODE_NOT_FOUND: PamErrorCode.GatewayNodeNotFound, PAM_MULTIPLE_GATEWAY_NODE_MATCHES: PamErrorCode.MultipleGatewayNodeMatches, + PAM_GATEWAY_REMOVE_FAILED: PamErrorCode.GatewayRemoveFailed, + PAM_GATEWAY_INVALID_MAX_INSTANCES: PamErrorCode.GatewayInvalidMaxInstances, + PAM_GATEWAY_SET_MAX_INSTANCES_FAILED: PamErrorCode.GatewaySetMaxInstancesFailed, AUDIT_INVALID_REPORT_TYPE: AuditReportErrorCode.InvalidReportType, AUDIT_INVALID_CREATED_FILTER: AuditReportErrorCode.InvalidCreatedFilter, AUDIT_INVALID_FILTER: AuditReportErrorCode.InvalidFilter, diff --git a/KeeperSdk/src/vault/KeeperVault.ts b/KeeperSdk/src/vault/KeeperVault.ts index f3782a4..c87e89f 100644 --- a/KeeperSdk/src/vault/KeeperVault.ts +++ b/KeeperSdk/src/vault/KeeperVault.ts @@ -153,6 +153,10 @@ import type { CreateGatewayResult, EditGatewayInput, EditGatewayResult, + RemoveGatewayInput, + RemoveGatewayResult, + SetGatewayMaxInstancesInput, + SetGatewayMaxInstancesResult, } from '../pam/gateway/gatewayTypes' import type { ListUserRow, @@ -1024,6 +1028,22 @@ export class KeeperVault { return this.pamManager.formatEditGatewayOutput(result) } + public async removeGateway(input: RemoveGatewayInput): Promise { + return this.pamManager.removeGateway(input) + } + + public formatRemoveGatewayOutput(result: RemoveGatewayResult): string { + return this.pamManager.formatRemoveGatewayOutput(result) + } + + public async setGatewayMaxInstances(input: SetGatewayMaxInstancesInput): Promise { + return this.pamManager.setGatewayMaxInstances(input) + } + + public formatSetGatewayMaxInstancesOutput(result: SetGatewayMaxInstancesResult): string { + return this.pamManager.formatSetGatewayMaxInstancesOutput(result) + } + public formatGatewaysTable( result: ListGatewaysResult, options?: FormatGatewaysTableOptions diff --git a/examples/sdk_example/package.json b/examples/sdk_example/package.json index 0502df9..31ae84c 100644 --- a/examples/sdk_example/package.json +++ b/examples/sdk_example/package.json @@ -63,6 +63,8 @@ "pam:gateway:list": "ts-node src/pam/gateway/list_gateways.ts", "pam:gateway:new": "ts-node src/pam/gateway/create_gateway.ts", "pam:gateway:edit": "ts-node src/pam/gateway/edit_gateway.ts", + "pam:gateway:remove": "ts-node src/pam/gateway/remove_gateway.ts", + "pam:gateway:set-max-instances": "ts-node src/pam/gateway/set_max_instances.ts", "link-local": "cd ../../KeeperSdk && npm link ../keeperapi && cd ../examples/sdk_example && npm link ../../keeperapi", "types": "tsc --watch", "types:ci": "tsc" diff --git a/examples/sdk_example/src/pam/gateway/remove_gateway.ts b/examples/sdk_example/src/pam/gateway/remove_gateway.ts new file mode 100644 index 0000000..f542178 --- /dev/null +++ b/examples/sdk_example/src/pam/gateway/remove_gateway.ts @@ -0,0 +1,44 @@ +import { + cleanup, + extractErrorMessage, + login, + logger, + prompt, + suppressLogs, +} from '@keeper-security/keeper-sdk-javascript' +import { runExample } from '../../utils/runner' + +async function removeGatewayExample() { + const vault = await login() + + try { + const gatewayUidOrName = (await prompt('Gateway UID or name: ')).trim() + if (!gatewayUidOrName) { + logger.info('Gateway UID or name is required.') + return + } + + let result + const restore = suppressLogs() + try { + result = await vault.removeGateway({ gatewayUidOrName }) + } finally { + restore() + } + + logger.info('') + if (!result.found) { + logger.warn(vault.formatRemoveGatewayOutput(result)) + } else { + logger.info(vault.formatRemoveGatewayOutput(result)) + } + logger.info('') + } catch (err) { + logger.error(`Operation failed: ${extractErrorMessage(err)}`) + process.exitCode = 1 + } finally { + cleanup(vault) + } +} + +runExample(removeGatewayExample) diff --git a/examples/sdk_example/src/pam/gateway/set_max_instances.ts b/examples/sdk_example/src/pam/gateway/set_max_instances.ts new file mode 100644 index 0000000..475486e --- /dev/null +++ b/examples/sdk_example/src/pam/gateway/set_max_instances.ts @@ -0,0 +1,50 @@ +import { + cleanup, + extractErrorMessage, + login, + logger, + prompt, + suppressLogs, +} from '@keeper-security/keeper-sdk-javascript' +import { runExample } from '../../utils/runner' + +async function setGatewayMaxInstancesExample() { + const vault = await login() + + try { + const gatewayUidOrName = (await prompt('Gateway UID or name: ')).trim() + if (!gatewayUidOrName) { + logger.info('Gateway UID or name is required.') + return + } + + const maxRaw = (await prompt('Maximum instances (>= 1): ')).trim() + const maxInstances = Number(maxRaw) + if (!Number.isFinite(maxInstances) || maxInstances < 1) { + logger.info('Maximum instances must be an integer >= 1.') + return + } + + let result + const restore = suppressLogs() + try { + result = await vault.setGatewayMaxInstances({ + gatewayUidOrName, + maxInstances: Math.floor(maxInstances), + }) + } finally { + restore() + } + + logger.info('') + logger.info(vault.formatSetGatewayMaxInstancesOutput(result)) + logger.info('') + } catch (err) { + logger.error(`Operation failed: ${extractErrorMessage(err)}`) + process.exitCode = 1 + } finally { + cleanup(vault) + } +} + +runExample(setGatewayMaxInstancesExample) diff --git a/keeperapi/src/restMessages.ts b/keeperapi/src/restMessages.ts index ccaa8f4..1190e25 100644 --- a/keeperapi/src/restMessages.ts +++ b/keeperapi/src/restMessages.ts @@ -964,6 +964,16 @@ export const getControllers = (): RestOutMessage => export const modifyControllerMessage = (data: PAM.IPAMController): RestInMessage => createInMessage(data, 'pam/modify_controller', PAM.PAMController) +export const removeControllerMessage = ( + data: PAM.IPAMGenericUidRequest +): RestMessage => + createMessage(data, 'pam/remove_controller', PAM.PAMGenericUidRequest, PAM.PAMRemoveControllerResponse) + +export const setControllerMaxInstanceCountMessage = ( + data: PAM.IPAMSetMaxInstanceCountRequest +): RestInMessage => + createInMessage(data, 'pam/set_controller_max_instance_count', PAM.PAMSetMaxInstanceCountRequest) + export const getConfigurationControllerMessage = ( data: PAM.IPAMGenericUidRequest ): RestMessage =>