Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { frodo } from '@rockcarver/frodo-lib';
import { Option } from 'commander';

import { configManagerImportSecretMappings } from '../../../configManagerOps/FrConfigSecretMappingsOps';
import { getTokens } from '../../../ops/AuthenticateOps';
import { printMessage, verboseMessage } from '../../../utils/Console';
import { FrodoCommand } from '../../FrodoCommand';

const { CLOUD_DEPLOYMENT_TYPE_KEY } = frodo.utils.constants;

const deploymentTypes = [CLOUD_DEPLOYMENT_TYPE_KEY];

export default function setup() {
const program = new FrodoCommand(
'frodo config-manager push secret-mappings',
[],
deploymentTypes
);

program
.description('Import secret mappings.')
.addOption(
new Option(
'-n, --name <name>',
'Name of the secret mapping, It will only import secret mapping with the name. Works both with mapping._id or alias.'
)
)
.addOption(
new Option(
'-r, --realm <realm>',
'Specific realm to get secret mappings from (overrides environment)'
)
)
.action(async (host, realm, user, password, options, command) => {
command.handleDefaultArgsAndOpts(
host,
realm,
user,
password,
options,
command
);
if (options.realm) {
realm = options.realm;
}
if (await getTokens(false, true, deploymentTypes)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are going to want to check that when the -n, --name flag is provided that the -r, --realm flag is provided as well, since fr-config-manager requires both to be present when using -n, --name.

verboseMessage('Importing secret mappings...');
const outcome = await configManagerImportSecretMappings(
options.name,
realm
);
if (!outcome) process.exitCode = 1;
}
// unrecognized combination of options or no options
else {
printMessage(
'Unrecognized combination of options or no options...',
'error'
);
program.help();
process.exitCode = 1;
}
});

return program;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FrodoStubCommand } from '../../FrodoCommand';
import SecretMappings from './config-manager-push-secret-mappings';
import EmailProvider from './config-manager-push-email-provider';
import EmailTemplates from './config-manager-push-email-templates';
import Endpoints from './config-manager-push-endpoints';
Expand Down Expand Up @@ -27,6 +28,7 @@ export default function setup() {
program.addCommand(EmailTemplates().name('email-templates'));
program.addCommand(Schedules().name('schedules'));
program.addCommand(OrgPrivileges().name('org-privileges'));
program.addCommand(SecretMappings().name('secret-mappings'));

return program;
}
42 changes: 41 additions & 1 deletion src/configManagerOps/FrConfigSecretMappingsOps.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { frodo, state } from '@rockcarver/frodo-lib';
import fs from 'fs';

import { printError } from '../utils/Console';
import { realmList } from '../utils/FrConfig';

const { saveJsonToFile, getFilePath } = frodo.utils;
const { readSecretStoreMappings } = frodo.secretStore;
const { readSecretStoreMappings, importSecretStores } = frodo.secretStore;

export async function configManagerExportSecretMappings(
name?,
Expand All @@ -20,6 +21,8 @@ export async function configManagerExportSecretMappings(
processSecretMappings(readData, `realms/${realm}/secret-mappings`, name);
} else {
for (const realm of await realmList()) {
// fr-config-manager doesn't support root secret-mappings
if (realm === '/') continue;
state.setRealm(realm);
const readData = await readSecretStoreMappings(
'ESV',
Expand Down Expand Up @@ -65,3 +68,40 @@ async function aliasSearch(object, name) {
return false;
}
}

export async function configManagerImportSecretMappings(
name?: string,
realm?: string
): Promise<boolean> {
try {
if (realm && realm !== '__default__realm__' && name) {
const filePath = getFilePath(
`realms/${realm}/secret-mappings/${name}.json`
);
const readFile = fs.readFileSync(filePath, 'utf8');
const importData = JSON.parse(readFile);
const mappingData = { secretstore: { [importData._id]: importData } };
await importSecretStores(mappingData, false, importData._id);
} else {
for (const realmName of await realmList()) {
// fr-config-manager doesn't support root secret-mappingsgit
if (realmName === '/') continue;
const filePath = getFilePath(`realms/${realmName}/secret-mappings/`);
if (!fs.existsSync(filePath)) continue;
const readDir = fs.readdirSync(filePath, 'utf8');
for (const fileName of readDir) {
const fullPath = `${filePath}/${fileName}`;
const readFile = fs.readFileSync(fullPath, 'utf8');
const importData = JSON.parse(readFile);
const mappingData = { secretstore: { [importData._id]: importData } };

await importSecretStores(mappingData, false, importData._id);
}
}
}
return true;
} catch (error) {
printError(error, `Error importing secret mappings`);
return false;
}
Comment on lines +76 to +106
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have bugs in this function that prevent the secret mappings from being imported, using either the -n flag or not using it. You will want to fix those

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`CLI help interface for 'config-manager push secret-mappings' should be expected english 1`] = `
"Usage: frodo config-manager push secret-mappings [options] [host] [realm] [username] [password]

Import secret mappings.

Arguments:
host AM base URL, e.g.: https://cdk.iam.example.com/am. To use a connection profile, just specify a unique substring or alias.
realm Realm. Specify realm as '/' for the root realm or 'realm' or '/parent/child' otherwise. (default: "alpha" for Identity Cloud tenants, "/" otherwise.)
username Username to login with. Must be an admin user with appropriate rights to manage authentication journeys/trees.
password Password.

Options:
--curlirize Output all network calls in curl format.
-D, --directory <directory> Set the working directory.
--debug Debug output during command execution. If specified, may or may not produce additional output helpful for troubleshooting.
--flush-cache Flush token cache.
-h, --help Help
--idm-host <idm-host> IDM base URL, e.g.: https://cdk.idm.example.com/myidm. Use only if your IDM installation resides in a different domain and/or if the base path differs from the default "/openidm".
-k, --insecure Allow insecure connections when using SSL/TLS. Has no effect when using a network proxy for https (HTTPS_PROXY=http://<host>:<port>), in that case the proxy must provide this capability. (default: Don't allow insecure connections)
--login-client-id <client-id> Specify a custom OAuth2 client id to use a your own oauth2 client for IDM API calls in deployments of type "cloud" or "forgeops". Your custom client must be configured as a public client and allow the authorization code grant using the "openid fr:idm:*" scope. Use the "--redirect-uri" parameter if you have configured a custom redirect uri (default: "<host>/platform/appAuthHelperRedirect.html").
--login-redirect-uri <redirect-uri> Specify a custom redirect URI to use with your custom OAuth2 client (efault: "<host>/platform/appAuthHelperRedirect.html").
-m, --type <type> Override auto-detected deployment type. Valid values for type:
classic: A classic Access Management-only deployment with custom layout and configuration.
cloud: A ForgeRock Identity Cloud environment.
forgeops: A ForgeOps CDK or CDM deployment.
The detected or provided deployment type controls certain behavior like obtaining an Identity Management admin token or not and whether to export/import referenced email templates or how to walk through the tenant admin login flow of Identity Cloud and handle MFA (choices: "classic", "cloud", "forgeops")
-n, --name <name> Name of the secret mapping, It will only import secret mapping with the name. Works both with mapping._id or alias.
--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.
-r, --realm <realm> Specific realm to get secret mappings from (overrides environment)
--retry <strategy> Retry failed operations. Valid values for strategy:
everything: Retry all failed operations.
network: Retry only network-related failed operations.
nothing: Do not retry failed operations.
The selected retry strategy controls how the CLI handles failures. (choices: "nothing", "everything", "network", default: Do not retry failed operations.)
--sa-id <sa-id> Service account id.
--sa-jwk-file <file> File containing the JSON Web Key (JWK) associated with the the service account.
--use-realm-prefix-on-managed-objects Set to true if you want to use the realm name as a prefix on managed object configuration, e.g. managed/alpha_user, managed/alpha_application or managed/bravo_organization. When false, the default behaviour of using managed/user etc. is retained. This option is ignored when the deployment type is "cloud".
--verbose Verbose output during command execution. If specified, may or may not produce additional output.

Environment Variables:
FRODO_HOST: AM base URL. Overridden by 'host' argument.
FRODO_IDM_HOST: IDM base URL. Overridden by '--idm-host' option.
FRODO_REALM: Realm. Overridden by 'realm' argument.
FRODO_USERNAME: Username. Overridden by 'username' argument.
FRODO_PASSWORD: Password. Overridden by 'password' argument.
FRODO_LOGIN_CLIENT_ID: OAuth2 client id for IDM API calls. Overridden by '--login-client-id' option.
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. When using an Amster private key, specifies which journey to use for Amster authentication as opposed to the default 'amsterService' journey.
FRODO_AUTHENTICATION_HEADER_OVERRIDES: Map of headers: '{"host":"am.example.com:8081"}'. These headers are sent with all requests and can be used to override default behavior, for example to set a custom host header for Proxy Connect-protected PingOne Advanced Identity Cloud environments.
FRODO_CONFIGURATION_HEADER_OVERRIDES: Map of headers: '{"X-Configuration-Type":"mutable"}'. These headers are sent with all configuration requests and can be used to override default behavior, for example to set a custom configuration header for mutable PingOne Advanced Identity Cloud environments.
FRODO_DEBUG: Set to any value to enable debug output. Same as '--debug'.
FRODO_IGA: Set to "true" to enable IGA (Identity Governance) endpoints for cloud deployments, or "false" to disable them, overriding auto-detected value.
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.

"
`;
10 changes: 10 additions & 0 deletions test/client_cli/en/config-manager-push-secret-mappings.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import cp from 'child_process';
import { promisify } from 'util';

const exec = promisify(cp.exec);
const CMD = 'frodo config-manager push secret-mappings --help';
const { stdout } = await exec(CMD);

test("CLI help interface for 'config-manager push secret-mappings' should be expected english", async () => {
expect(stdout).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`frodo config-manager push secret-mappings "frodo config-manager push secret-mappings -D test/e2e/exports/fr-config-manager/cloud -m cloud": should import the password policy into cloud" 1`] = `""`;

exports[`frodo config-manager push secret-mappings "frodo config-manager push secret-mappings -n am.services.oauth2.stateless.signing.HMAC test/e2e/exports/fr-config-manager/cloud -m cloud": should import a specific password policy by name into cloud" 1`] = `""`;

exports[`frodo config-manager push secret-mappings "frodo config-manager push secret-mappings -r alpha test/e2e/exports/fr-config-manager/cloud -m cloud": should import a specific password policy by name into cloud" 1`] = `""`;
86 changes: 86 additions & 0 deletions test/e2e/config-manager-push-secret-mappings.e2e.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Follow this process to write e2e tests for the CLI project:
*
* 1. Test if all the necessary mocks for your tests already exist.
* In mock mode, run the command you want to test with the same arguments
* and parameters exactly as you want to test it, for example:
*
* $ FRODO_MOCK=1 frodo conn save https://openam-frodo-dev.forgeblocks.com/am volker.scheuber@forgerock.com Sup3rS3cr3t!
*
* If your command completes without errors and with the expected results,
* all the required mocks already exist and you are good to write your
* test and skip to step #4.
*
* If, however, your command fails and you see errors like the one below,
* you know you need to record the mock responses first:
*
* [Polly] [adapter:node-http] Recording for the following request is not found and `recordIfMissing` is `false`.
*
* 2. Record mock responses for your exact command.
* In mock record mode, run the command you want to test with the same arguments
* and parameters exactly as you want to test it, for example:
*
* $ FRODO_MOCK=record frodo conn save https://openam-frodo-dev.forgeblocks.com/am volker.scheuber@forgerock.com Sup3rS3cr3t!
*
* Wait until you see all the Polly instances (mock recording adapters) have
* shutdown before you try to run step #1 again.
* Messages like these indicate mock recording adapters shutting down:
*
* Polly instance 'conn/4' stopping in 3s...
* Polly instance 'conn/4' stopping in 2s...
* Polly instance 'conn/save/3' stopping in 3s...
* Polly instance 'conn/4' stopping in 1s...
* Polly instance 'conn/save/3' stopping in 2s...
* Polly instance 'conn/4' stopped.
* Polly instance 'conn/save/3' stopping in 1s...
* Polly instance 'conn/save/3' stopped.
*
* 3. Validate your freshly recorded mock responses are complete and working.
* Re-run the exact command you want to test in mock mode (see step #1).
*
* 4. Write your test.
* Make sure to use the exact command including number of arguments and params.
*
* 5. Commit both your test and your new recordings to the repository.
* Your tests are likely going to reside outside the frodo-lib project but
* the recordings must be committed to the frodo-lib project.
*/

/*
// ForgeOps
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should say Cloud

FRODO_MOCK=record FRODO_NO_CACHE=1 FRODO_HOST=https://nightly.gcp.forgeops.com/am frodo config-manager push secret-mappings -D test/e2e/exports/fr-config-manager/forgeops -m cloud
FRODO_MOCK=record FRODO_NO_CACHE=1 FRODO_HOST=https://nightly.gcp.forgeops.com/am frodo config-manager push secret-mappings -r alpha -D test/e2e/exports/fr-config-manager/forgeops -m cloud
FRODO_MOCK=record FRODO_NO_CACHE=1 FRODO_HOST=https://nightly.gcp.forgeops.com/am frodo config-manager push secret-mappings -n am.services.oauth2.stateless.signing.HMAC -D test/e2e/exports/fr-config-manager/forgeops -m cloud
Comment on lines +51 to +53
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FRODO_HOST is wrong, it should be the host of the frodo dev tenant (look at the other frodo tests to see what that looks like).

You also don't need to have the -m flag, that's optional here if you want to remove it.


*/

import cp from 'child_process';
import { promisify } from 'util';
import { getEnv, removeAnsiEscapeCodes } from './utils/TestUtils';
import { connection as c } from './utils/TestConfig';

const exec = promisify(cp.exec);

process.env['FRODO_MOCK'] = '1';
const cloudEnv = getEnv(c);

const allDirectory = "test/e2e/exports/fr-config-manager/cloud";

describe('frodo config-manager push secret-mappings', () => {
test(`"frodo config-manager push secret-mappings -D ${allDirectory} -m cloud": should import the password policy into cloud"`, async () => {
const CMD = `frodo config-manager push secret-mappings -D ${allDirectory} -m cloud`;
const { stdout } = await exec(CMD, cloudEnv);
expect(removeAnsiEscapeCodes(stdout)).toMatchSnapshot();
});

test(`"frodo config-manager push secret-mappings -r alpha ${allDirectory} -m cloud": should import a specific password policy by name into cloud"`, async () => {
const CMD = `frodo config-manager push secret-mappings -r alpha -D ${allDirectory} -m cloud`;
const { stdout } = await exec(CMD, cloudEnv);
expect(removeAnsiEscapeCodes(stdout)).toMatchSnapshot();
});
test(`"frodo config-manager push secret-mappings -n am.services.oauth2.stateless.signing.HMAC ${allDirectory} -m cloud": should import a specific password policy by name into cloud"`, async () => {
const CMD = `frodo config-manager push secret-mappings -n am.services.oauth2.stateless.signing.HMAC -D ${allDirectory} -m cloud`;
const { stdout } = await exec(CMD, cloudEnv);
expect(removeAnsiEscapeCodes(stdout)).toMatchSnapshot();
});
});
Comment on lines +69 to +86
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The descriptions on these tests say password policy, so they need to be updated

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add another 3 test mappings. One more for alpha realm, and two for bravo realm

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"_id": "am.services.oauth2.stateless.signing.HMAC",
"_rev": "2072507862",
"_type": {
"_id": "mappings",
"collection": true,
"name": "Mappings"
},
"aliases": [
"esv-oauth2-signing-key."
],
"secretId": "am.services.oauth2.stateless.signing.HMAC"
}
Loading