Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [3.0.7] - 2025-07-24

### Fixed

- log fetch now repects the log API request limit (1 per second)
Expand Down Expand Up @@ -2080,7 +2082,8 @@ Frodo CLI 2.x automatically refreshes session and access tokens before they expi
- Fixed problem with adding connection profiles
- Miscellaneous bug fixes

[unreleased]: https://github.com/rockcarver/frodo-cli/compare/v3.0.6...HEAD
[unreleased]: https://github.com/rockcarver/frodo-cli/compare/v3.0.7...HEAD
[3.0.7]: https://github.com/rockcarver/frodo-cli/compare/v3.0.6...v3.0.7
[3.0.6]: https://github.com/rockcarver/frodo-cli/compare/v3.0.5...v3.0.6
[3.0.5]: https://github.com/rockcarver/frodo-cli/compare/v3.0.4...v3.0.5
[3.0.4]: https://github.com/rockcarver/frodo-cli/compare/v3.0.4-1...v3.0.4
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rockcarver/frodo-cli",
"version": "3.0.6",
"version": "3.0.7",
"type": "module",
"description": "A command line interface to manage ForgeRock Identity Cloud tenants, ForgeOps deployments, and classic deployments.",
"keywords": [
Expand Down
48 changes: 46 additions & 2 deletions src/cli/FrodoCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
} from '../utils/Console.js';

const { DEFAULT_REALM_KEY, DEPLOYMENT_TYPES } = frodo.utils.constants;
const { convertPrivateKeyToPem } = frodo.utils.crypto;

Check failure on line 18 in src/cli/FrodoCommand.ts

View workflow job for this annotation

GitHub Actions / Build

Property 'crypto' does not exist on type 'FRUtils & ScriptValidation & ExportImport & Base64 & { constants: Constants; jose: Jose; json: Json; version: Version; }'.

const hostArgument = new Argument(
'[host]',
Expand Down Expand Up @@ -62,6 +63,16 @@
'File containing the JSON Web Key (JWK) associated with the the service account.'
);

const amsterPrivateKeyPassphraseOption = new Option(
'--passphrase <passphrase>',
'The passphrase for the Amster private key if it is encrypted.'
);

const amsterPrivateKeyFileOption = new Option(
'--private-key <file>',
'File containing the private key for authenticating with Amster. Supported formats include PEM (both PKCS#1 and PKCS#8 variants), OpenSSH, DNSSEC, and JWK.'
);

const deploymentOption = new Option(
'-m, --type <type>',
'Override auto-detected deployment type. Valid values for type: \n\
Expand Down Expand Up @@ -118,6 +129,8 @@
loginRedirectUri,
serviceAccountIdOption,
serviceAccountJwkFileOption,
amsterPrivateKeyPassphraseOption,
amsterPrivateKeyFileOption,
deploymentOption,
directoryOption,
insecureOption,
Expand Down Expand Up @@ -153,6 +166,35 @@
);
}
},
[amsterPrivateKeyPassphraseOption.attributeName()]: (passphrase: string) => {
// This is needed in the case the passphrase is an option, but the private key is an environment variable.
process.env.FRODO_AMSTER_PASSPHRASE = passphrase;
},
[amsterPrivateKeyFileOption.attributeName()]: (
file: string,
options: Record<string, string | boolean>
) => {
const passphrase =
(options[amsterPrivateKeyPassphraseOption.attributeName()] as string) ||
process.env.FRODO_AMSTER_PASSPHRASE;
try {
// Store as PEM format (PKCS#8 variant specifically) since Jose supports PEM and since PKCS#8 supports more algorithms than PKCS#1
state.setAmsterPrivateKey(

Check failure on line 182 in src/cli/FrodoCommand.ts

View workflow job for this annotation

GitHub Actions / Build

Property 'setAmsterPrivateKey' does not exist on type 'State'.
convertPrivateKeyToPem(
fs.readFileSync(file, 'utf8'),
passphrase,
file
.replaceAll('\\', '/')
.substring(file.replaceAll('\\', '/').lastIndexOf('/') + 1)
)
);
} catch (error) {
printMessage(
`Error parsing private key from file ${file}: ${error.message}`,
'error'
);
}
},
[deploymentOption.attributeName()]: (type: string) =>
state.setDeploymentType(type),
[directoryOption.attributeName()]: (directory: string) =>
Expand Down Expand Up @@ -277,6 +319,8 @@
` FRODO_LOGIN_REDIRECT_URI: Redirect Uri for custom OAuth2 client id. Overridden by '--login-redirect-uri' option.\n` +
` FRODO_SA_ID: Service account uuid. Overridden by '--sa-id' option.\n` +
` FRODO_SA_JWK: Service account JWK. Overridden by '--sa-jwk-file' option but takes the actual JWK as a value, not a file name.\n` +
` FRODO_AMSTER_PASSPHRASE: Passphrase for the Amster private key if it is encrypted. Overridden by '--passphrase' option.\n` +
` FRODO_AMSTER_PRIVATE_KEY: Amster private key. Overridden by '--private-key' option but takes the actual private key as a value (i.e. the file contents), not a file name. Supported formats include PEM (both PKCS#1 and PKCS#8 variants), OpenSSH, DNSSEC, and JWK.\n` +
` FRODO_NO_CACHE: Disable token cache. Same as '--no-cache' option.\n` +
` FRODO_TOKEN_CACHE_PATH: Use this token cache file instead of '~/.frodo/TokenCache.json'.\n` +
('frodo conn save' === this.name()
Expand All @@ -288,7 +332,7 @@
` FRODO_LOG_SECRET: Log API secret. Overridden by 'password' argument.\n`
: ``) +
` FRODO_CONNECTION_PROFILES_PATH: Use this connection profiles file instead of '~/.frodo/Connections.json'.\n` +
` FRODO_AUTHENTICATION_SERVICE: Name of a login journey to use.\n` +
` FRODO_AUTHENTICATION_SERVICE: Name of a login journey to use. When using an Amster private key, specifies which journey to use for Amster authentication as opposed to the default 'amsterService' journey.\n` +
` FRODO_DEBUG: Set to any value to enable debug output. Same as '--debug'.\n` +
` FRODO_MASTER_KEY_PATH: Use this master key file instead of '~/.frodo/masterkey.key' file.\n` +
` FRODO_MASTER_KEY: Use this master key instead of what's in '~/.frodo/masterkey.key'. Takes precedence over FRODO_MASTER_KEY_PATH.\n`
Expand Down Expand Up @@ -338,7 +382,7 @@
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handler: any = stateMap[k];
handler(v);
handler(v, options);
} else {
debugMessage(
`FrodoCommand.handleDefaultArgsAndOpts: Ignoring non-default option '${k}'.`
Expand Down
11 changes: 10 additions & 1 deletion src/cli/conn/conn-save.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,21 @@ export default function setup() {
` $ frodo conn save ${s.amBaseUrl} ${s.username} '${s.password}'\n`[
'brightCyan'
] +
` Create a connection profile using Amster private key credentials (PingAM classic deployments only):\n` +
` $ frodo conn save --private-key ${s.amsterPrivateKey} ${s.amClassicBaseUrl}\n`[
'brightCyan'
] +
` Save an existing service account to an existing or new connection profile:\n` +
` $ frodo conn save --sa-id ${s.saId} --sa-jwk-file ${s.saJwkFile} ${s.amBaseUrl}\n`[
'brightCyan'
] +
` Save an existing service account to an existing connection profile (partial host URL only updates an existing profile):\n` +
` $ frodo conn save --sa-id ${s.saId} --sa-jwk-file ${s.saJwkFile} ${s.connId}\n`[
'brightCyan'
] +
` Update an existing connection profile to use Amster private key credentials with a custom Amster journey (PingAM classic deployments only):\n` +
` $ frodo conn save --private-key ${s.amsterPrivateKey} --authentication-service ${s.customAmsterService} ${s.classicConnId}\n`[
'brightCyan'
]
)
.action(
Expand All @@ -86,6 +94,7 @@ export default function setup() {
JSON.parse(options.authenticationHeaderOverrides)
);
}
const needAmsterLogin = !!options.privateKey;
const needSa =
options.sa &&
!state.getServiceAccountId() &&
Expand All @@ -95,7 +104,7 @@ export default function setup() {
!state.getLogApiKey() &&
!state.getLogApiSecret() &&
needSa;
const forceLoginAsUser = needSa || needLogApiKey;
const forceLoginAsUser = !needAmsterLogin && (needSa || needLogApiKey);
if (
(options.validate && (await getTokens(forceLoginAsUser))) ||
!options.validate
Expand Down
5 changes: 5 additions & 0 deletions src/help/SampleData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ export const connId2 = 'zion';
export const username2 = 'neo@nebuchadnezzar.zion.com';
export const password2 = 'R3dP!ll3d';
export const realm = '/alpha';
export const amClassicBaseUrl = 'https://am.example.com:8443/am';
export const classicConnId = 'am.example';
export const amsterPrivateKey =
'/home/trinity/am/security/keys/amster/amster_rsa';
export const customAmsterService = 'AmsterLogin';
14 changes: 14 additions & 0 deletions src/ops/ConnectionProfileOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@
'Service Account',
'Username',
'Log API Key',
'Authentication Service',
]);
Object.keys(connectionsData).forEach((c) => {
table.push([
c,
connectionsData[c].svcacctName || connectionsData[c].svcacctId,
connectionsData[c].username,
connectionsData[c].logApiKey,
connectionsData[c].authenticationService,
]);
});
printMessage(table.toString(), 'data');
Expand Down Expand Up @@ -76,10 +78,12 @@
debugMessage(profile);
const present = '[present]';
const jwk = profile.svcacctJwk;
const privateKey = profile.amsterPrivateKey;

Check failure on line 81 in src/ops/ConnectionProfileOps.ts

View workflow job for this annotation

GitHub Actions / Build

Property 'amsterPrivateKey' does not exist on type 'ConnectionProfileInterface'.
if (!showSecrets) {
if (profile.password) profile.password = present;
if (profile.logApiSecret) profile.logApiSecret = present;
if (profile.svcacctJwk) (profile as unknown)['svcacctJwk'] = present;
if (profile.amsterPrivateKey) profile.amsterPrivateKey = present;

Check failure on line 86 in src/ops/ConnectionProfileOps.ts

View workflow job for this annotation

GitHub Actions / Build

Property 'amsterPrivateKey' does not exist on type 'ConnectionProfileInterface'.

Check failure on line 86 in src/ops/ConnectionProfileOps.ts

View workflow job for this annotation

GitHub Actions / Build

Property 'amsterPrivateKey' does not exist on type 'ConnectionProfileInterface'.
}
if (!profile.idmHost) {
delete profile.idmHost;
Expand Down Expand Up @@ -122,9 +126,15 @@
// do nothing
}
}
if (!profile.amsterPrivateKey) {

Check failure on line 129 in src/ops/ConnectionProfileOps.ts

View workflow job for this annotation

GitHub Actions / Build

Property 'amsterPrivateKey' does not exist on type 'ConnectionProfileInterface'.
delete profile.amsterPrivateKey;

Check failure on line 130 in src/ops/ConnectionProfileOps.ts

View workflow job for this annotation

GitHub Actions / Build

Property 'amsterPrivateKey' does not exist on type 'ConnectionProfileInterface'.
}
if (showSecrets && jwk) {
(profile as unknown)['svcacctJwk'] = 'see below';
}
if (showSecrets && privateKey) {
profile.amsterPrivateKey = 'see below';

Check failure on line 136 in src/ops/ConnectionProfileOps.ts

View workflow job for this annotation

GitHub Actions / Build

Property 'amsterPrivateKey' does not exist on type 'ConnectionProfileInterface'.
}
if (!profile.authenticationService) {
delete profile.authenticationService;
}
Expand All @@ -141,12 +151,16 @@
svcacctId: 'Service Account Id',
svcacctJwk: 'Service Account JWK',
svcacctScope: 'Service Account Scope',
amsterPrivateKey: 'Amster Private Key',
};
const table = createObjectTable(profile, keyMap);
printMessage(table.toString(), 'data');
if (showSecrets && jwk) {
printMessage(JSON.stringify(jwk), 'data');
}
if (showSecrets && privateKey) {
printMessage(privateKey, 'data');
}
} else {
printMessage(`No connection profile ${host} found`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ Options:
(choices: "classic", "cloud",
"forgeops")
--no-cache Disable token cache for this operation.
--passphrase <passphrase> The passphrase for the Amster private
key if it is encrypted.
--private-key <file> File containing the private key for
authenticating with Amster. Supported
formats include PEM (both PKCS#1 and
PKCS#8 variants), OpenSSH, DNSSEC, and
JWK.
--sa-id <sa-id> Service account id.
--sa-jwk-file <file> File containing the JSON Web Key (JWK)
associated with the the service account.
Expand All @@ -88,10 +95,12 @@ Environment Variables:
FRODO_LOGIN_REDIRECT_URI: Redirect Uri for custom OAuth2 client id. Overridden by '--login-redirect-uri' option.
FRODO_SA_ID: Service account uuid. Overridden by '--sa-id' option.
FRODO_SA_JWK: Service account JWK. Overridden by '--sa-jwk-file' option but takes the actual JWK as a value, not a file name.
FRODO_AMSTER_PASSPHRASE: Passphrase for the Amster private key if it is encrypted. Overridden by '--passphrase' option.
FRODO_AMSTER_PRIVATE_KEY: Amster private key. Overridden by '--private-key' option but takes the actual private key as a value (i.e. the file contents), not a file name. Supported formats include PEM (both PKCS#1 and PKCS#8 variants), OpenSSH, DNSSEC, and JWK.
FRODO_NO_CACHE: Disable token cache. Same as '--no-cache' option.
FRODO_TOKEN_CACHE_PATH: Use this token cache file instead of '~/.frodo/TokenCache.json'.
FRODO_CONNECTION_PROFILES_PATH: Use this connection profiles file instead of '~/.frodo/Connections.json'.
FRODO_AUTHENTICATION_SERVICE: Name of a login journey to use.
FRODO_AUTHENTICATION_SERVICE: Name of a login journey to use. When using an Amster private key, specifies which journey to use for Amster authentication as opposed to the default 'amsterService' journey.
FRODO_DEBUG: Set to any value to enable debug output. Same as '--debug'.
FRODO_MASTER_KEY_PATH: Use this master key file instead of '~/.frodo/masterkey.key' file.
FRODO_MASTER_KEY: Use this master key instead of what's in '~/.frodo/masterkey.key'. Takes precedence over FRODO_MASTER_KEY_PATH.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ Options:
output to console instead. This option
only applies if used with the --llt
option.
--passphrase <passphrase> The passphrase for the Amster private
key if it is encrypted.
--private-key <file> File containing the private key for
authenticating with Amster. Supported
formats include PEM (both PKCS#1 and
PKCS#8 variants), OpenSSH, DNSSEC, and
JWK.
--sa-id <sa-id> Service account id.
--sa-jwk-file <file> File containing the JSON Web Key (JWK)
associated with the the service account.
Expand All @@ -114,10 +121,12 @@ Environment Variables:
FRODO_LOGIN_REDIRECT_URI: Redirect Uri for custom OAuth2 client id. Overridden by '--login-redirect-uri' option.
FRODO_SA_ID: Service account uuid. Overridden by '--sa-id' option.
FRODO_SA_JWK: Service account JWK. Overridden by '--sa-jwk-file' option but takes the actual JWK as a value, not a file name.
FRODO_AMSTER_PASSPHRASE: Passphrase for the Amster private key if it is encrypted. Overridden by '--passphrase' option.
FRODO_AMSTER_PRIVATE_KEY: Amster private key. Overridden by '--private-key' option but takes the actual private key as a value (i.e. the file contents), not a file name. Supported formats include PEM (both PKCS#1 and PKCS#8 variants), OpenSSH, DNSSEC, and JWK.
FRODO_NO_CACHE: Disable token cache. Same as '--no-cache' option.
FRODO_TOKEN_CACHE_PATH: Use this token cache file instead of '~/.frodo/TokenCache.json'.
FRODO_CONNECTION_PROFILES_PATH: Use this connection profiles file instead of '~/.frodo/Connections.json'.
FRODO_AUTHENTICATION_SERVICE: Name of a login journey to use.
FRODO_AUTHENTICATION_SERVICE: Name of a login journey to use. When using an Amster private key, specifies which journey to use for Amster authentication as opposed to the default 'amsterService' journey.
FRODO_DEBUG: Set to any value to enable debug output. Same as '--debug'.
FRODO_MASTER_KEY_PATH: Use this master key file instead of '~/.frodo/masterkey.key' file.
FRODO_MASTER_KEY: Use this master key instead of what's in '~/.frodo/masterkey.key'. Takes precedence over FRODO_MASTER_KEY_PATH.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ Options:
(choices: "classic", "cloud",
"forgeops")
--no-cache Disable token cache for this operation.
--passphrase <passphrase> The passphrase for the Amster private
key if it is encrypted.
--private-key <file> File containing the private key for
authenticating with Amster. Supported
formats include PEM (both PKCS#1 and
PKCS#8 variants), OpenSSH, DNSSEC, and
JWK.
-s, --client-secret [secret] Client secret.
--sa-id <sa-id> Service account id.
--sa-jwk-file <file> File containing the JSON Web Key (JWK)
Expand All @@ -95,10 +102,12 @@ Environment Variables:
FRODO_LOGIN_REDIRECT_URI: Redirect Uri for custom OAuth2 client id. Overridden by '--login-redirect-uri' option.
FRODO_SA_ID: Service account uuid. Overridden by '--sa-id' option.
FRODO_SA_JWK: Service account JWK. Overridden by '--sa-jwk-file' option but takes the actual JWK as a value, not a file name.
FRODO_AMSTER_PASSPHRASE: Passphrase for the Amster private key if it is encrypted. Overridden by '--passphrase' option.
FRODO_AMSTER_PRIVATE_KEY: Amster private key. Overridden by '--private-key' option but takes the actual private key as a value (i.e. the file contents), not a file name. Supported formats include PEM (both PKCS#1 and PKCS#8 variants), OpenSSH, DNSSEC, and JWK.
FRODO_NO_CACHE: Disable token cache. Same as '--no-cache' option.
FRODO_TOKEN_CACHE_PATH: Use this token cache file instead of '~/.frodo/TokenCache.json'.
FRODO_CONNECTION_PROFILES_PATH: Use this connection profiles file instead of '~/.frodo/Connections.json'.
FRODO_AUTHENTICATION_SERVICE: Name of a login journey to use.
FRODO_AUTHENTICATION_SERVICE: Name of a login journey to use. When using an Amster private key, specifies which journey to use for Amster authentication as opposed to the default 'amsterService' journey.
FRODO_DEBUG: Set to any value to enable debug output. Same as '--debug'.
FRODO_MASTER_KEY_PATH: Use this master key file instead of '~/.frodo/masterkey.key' file.
FRODO_MASTER_KEY: Use this master key instead of what's in '~/.frodo/masterkey.key'. Takes precedence over FRODO_MASTER_KEY_PATH.
Expand Down
Loading
Loading