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
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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 specified name.'
)
)
.addOption(
new Option(
'-r, --realm <realm>',
'Realm name; imports only the secret mappings of the specified realm.'
)
)
.action(async (host, realm, user, password, options, command) => {
command.handleDefaultArgsAndOpts(
host,
realm,
user,
password,
options,
command
);

if (options.name && !options.realm) {
printMessage(
'The -n/--policy-name option requires -r/--realm to be specified.',
'error'
);
program.help();
process.exitCode = 1;
return;
}

if (await getTokens(false, true, deploymentTypes)) {
Comment thread
dallinjsevy marked this conversation as resolved.
verboseMessage('importing config entity secret-mappings');
const outcome = await configManagerImportSecretMappings(
options.name,
options.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
Expand Up @@ -15,6 +15,7 @@ import OrgPrivileges from './config-manager-push-org-privileges';
import PasswordPolicy from './config-manager-push-password-policy';
import Schedules from './config-manager-push-schedules';
import ServiceObjects from './config-manager-push-service-objects';
import SecretMappings from './config-manager-push-secret-mappings';
import TermsAndConditions from './config-manager-push-terms-and-conditions';
import Themes from './config-manager-push-themes';
import UiConfig from './config-manager-push-ui-config';
Expand All @@ -23,7 +24,6 @@ export default function setup() {
const program = new FrodoStubCommand('push').description(
'Import configuration optimized for CI/CD pipelines (format compatible with fr-config-manager).'
);

program.addCommand(Themes().name('themes'));
program.addCommand(TermsAndConditions().name('terms-and-conditions'));
program.addCommand(PasswordPolicy().name('password-policy'));
Expand All @@ -43,6 +43,6 @@ export default function setup() {
program.addCommand(UiConfig().name('ui-config'));
program.addCommand(Authentication().name('authentication'));
program.addCommand(ConnectorDefinitions().name('connector-definitions'));

program.addCommand(SecretMappings().name('secret-mappings'));
return program;
}
96 changes: 90 additions & 6 deletions src/configManagerOps/FrConfigSecretMappingsOps.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
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, updateSecretStoreMapping } = frodo.secretStore;

const ESV_SECRET_STORE_ID = 'ESV';
const ESV_SECRET_STORE_TYPE = 'GoogleSecretManagerSecretStoreProvider';
export async function configManagerExportSecretMappings(
name?,
realm?
): Promise<boolean> {
try {
if (realm && realm !== '__default__realm__') {
const readData = await readSecretStoreMappings(
'ESV',
'GoogleSecretManagerSecretStoreProvider',
ESV_SECRET_STORE_ID,
ESV_SECRET_STORE_TYPE,
false
);
processSecretMappings(readData, `realms/${realm}/secret-mappings`, name);
} else {
for (const realm of await realmList()) {
state.setRealm(realm);
if (
realm === '/' &&
state.getDeploymentType() ===
frodo.utils.constants.CLOUD_DEPLOYMENT_TYPE_KEY
)
continue;
const readData = await readSecretStoreMappings(
'ESV',
'GoogleSecretManagerSecretStoreProvider',
ESV_SECRET_STORE_ID,
ESV_SECRET_STORE_TYPE,
false
);
processSecretMappings(
Expand Down Expand Up @@ -65,3 +73,79 @@ async function aliasSearch(object, name) {
return false;
}
}

/**
* Import all secrets to individual files in fr-config-manager format
* @param {string} name the name of the file to be imported
* @param {string} realm the name of teh specified realm to import to
* @returns {Promise<boolean>} true if successful, false otherwise
*/
export async function configManagerImportSecretMappings(
Comment thread
dallinjsevy marked this conversation as resolved.
name?: string,
realm?: string
): Promise<boolean> {
try {
if (realm && name) {
state.setRealm(realm);
const filePath = getFilePath(
`realms/${realm}/secret-mappings/${name}.json`
);
const readFile = fs.readFileSync(filePath, 'utf8');
const importData = JSON.parse(readFile);
await updateSecretStoreMapping(
ESV_SECRET_STORE_ID,
ESV_SECRET_STORE_TYPE,
importData,
false
);
saveJsonToFile(importData, 'name-realm-test.json');
} else if (realm && realm !== '/') {
state.setRealm(realm);
const filePath = getFilePath(`realms/${realm}/secret-mappings/`);
if (fs.existsSync(filePath)) {
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);
await updateSecretStoreMapping(
ESV_SECRET_STORE_ID,
ESV_SECRET_STORE_TYPE,
importData,
false
);
saveJsonToFile(importData, 'realm-test.json');
}
}
} else {
for (const realmName of await realmList()) {
if (
realmName === '/' &&
state.getDeploymentType() ===
frodo.utils.constants.CLOUD_DEPLOYMENT_TYPE_KEY
)
continue;
state.setRealm(realmName);
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);
await updateSecretStoreMapping(
ESV_SECRET_STORE_ID,
ESV_SECRET_STORE_TYPE,
importData,
false
);
saveJsonToFile(importData, 'test.json');
}
}
}
return true;
} catch (error) {
printError(error, `Error importing secret mappings`);
return false;
}
Comment thread
dallinjsevy marked this conversation as resolved.
}
60 changes: 60 additions & 0 deletions test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"meta": {
"exportDate": "2026-04-02T17:35:45.032Z",
"exportTool": "frodo",
"exportToolVersion": "v4.0.0-34 [v24.13.0]",
"exportedBy": "phales@trivir.com",
"origin": "https://openam-frodo-dev.forgeblocks.com/am",
"originAmVersion": "9.0.0"
},
"secretstore": {
"am.authentication.nodes.persistentcookie.encryption": {
"_id": "am.authentication.nodes.persistentcookie.encryption",
"_type": {
"_id": "mappings",
"collection": true,
"name": "Mappings"
},
"aliases": [
"Btest2"
],
"secretId": "am.authentication.nodes.persistentcookie.encryption"
},
"am.authentication.nodes.webauthn.fidometadataservice.rootcertificate": {
"_id": "am.authentication.nodes.webauthn.fidometadataservice.rootcertificate",
"_type": {
"_id": "mappings",
"collection": true,
"name": "Mappings"
},
"aliases": [
"Btest"
],
"secretId": "am.authn.authid.signing.HMAC"
},
"am.services.oauth2.oidc.signing.RSA": {
"_id": "am.services.oauth2.oidc.signing.RSA",
"_type": {
"_id": "mappings",
"collection": true,
"name": "Mappings"
},
"aliases": [
"Atest"
],
"secretId": "am.services.oauth2.oidc.signing.RSA"
},
"am.services.selfservice.token.signing": {
"_id": "am.services.selfservice.token.signing",
"_type": {
"_id": "mappings",
"collection": true,
"name": "Mappings"
},
"aliases": [
"Atest2"
],
"secretId": "am.services.selfservice.token.signing"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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]

[Experimental] 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.

Deployment: Cloud-only

Options:
-n, --name <name> Name of the secret mapping, It will only import secret
mapping with the specified name.
-r, --realm <realm> Realm name; imports only the secret mappings of the
specified realm.
-h, --help Help
-hh, --help-more Help with all options.
-hhh, --help-all Help with all options, environment variables, and usage
examples.
"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ Commands:
terms-and-conditions [Experimental] Import terms and conditions.
themes [Experimental] Import themes.
ui-config [Experimental] Import UI configuration.

(Cloud-only):
secret-mappings [Experimental] Import secret mappings.
"
`;
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
Expand Up @@ -52,6 +52,22 @@ exports[`frodo config-manager pulls "frodo config-manager pull secret-mappings -
}
`;

exports[`frodo config-manager pulls "frodo config-manager pull secret-mappings -D secretMappingTestDir": should export the secret-mappings in fr-config-manager style": secretMappingTestDir/realms/bravo/secret-mappings/am.services.httpclient.mtls.clientcert.testClientCert.secret.json 1`] = `
{
"_id": "am.services.httpclient.mtls.clientcert.testClientCert.secret",
"_rev": "243715290",
"_type": {
"_id": "mappings",
"collection": true,
"name": "Mappings",
},
"aliases": [
"esv-test-client-cert",
],
"secretId": "am.services.httpclient.mtls.clientcert.testClientCert.secret",
}
`;

exports[`frodo config-manager pulls "frodo config-manager pull secret-mappings -r alpha -D secretMappingTestDir": should export the secret-mappings in alpha realm in fr-config-manager style" 1`] = `0`;

exports[`frodo config-manager pulls "frodo config-manager pull secret-mappings -r alpha -D secretMappingTestDir": should export the secret-mappings in alpha realm in fr-config-manager style" 2`] = `""`;
Expand Down
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 ": should import the secret mapping into cloud" 1`] = `""`;

exports[`frodo config-manager push secret-mappings "frodo config-manager push secret-mappings -r alpha -n am.services.iot.cert.verification -D test/e2e/exports/fr-config-manager/cloud": should import a specific secret mapping by name into cloud" 1`] = `""`;

exports[`frodo config-manager push secret-mappings "frodo config-manager push secret-mappings -r bravo -D test/e2e/exports/fr-config-manager/cloud": should import a specific secret-mapping by name into cloud" 1`] = `""`;
Loading