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,136 @@
import { frodo, state } from '@rockcarver/frodo-lib';
import { Option } from 'commander';

import {
configManagerImportAuthzPoliciesAll,
configManagerImportAuthzPolicySet,
configManagerImportAuthzPolicySetsRealm,
} from '../../../configManagerOps/FrConfigAuthzPoliciesOps';
import { getTokens } from '../../../ops/AuthenticateOps';
import { printMessage } from '../../../utils/Console';
import { FrodoCommand } from '../../FrodoCommand';

const { CLOUD_DEPLOYMENT_TYPE_KEY, FORGEOPS_DEPLOYMENT_TYPE_KEY } =
frodo.utils.constants;

const deploymentTypes = [
CLOUD_DEPLOYMENT_TYPE_KEY,
FORGEOPS_DEPLOYMENT_TYPE_KEY,
];
const { constants } = frodo.utils;
const { readRealms } = frodo.realm;

export default function setup() {
const program = new FrodoCommand(
'frodo config-manager push authz-policies',
deploymentTypes
);

program
.description('Import authorization policies from realm.')
.addOption(
new Option(
'-r, --realm <realm>',
'Specifies the realm to import from. Only policy sets from this realm will be imported.'
)
)
.addOption(
new Option(
'-n, --policy-name <policy-set-name>',
'Import only a specific policy set with the name.'
)
)

.action(async (host, realm, user, password, options, command) => {
command.handleDefaultArgsAndOpts(
host,
realm,
user,
password,
options,
command
);

// -r/--realm flag has precedence over [realm] arguement
if (options.realm) {
realm = options.realm;
}

if (await getTokens(false, true, deploymentTypes)) {
let outcome: boolean;

// -p/--p-set
if (options.policyName) {
printMessage(
`importing the policy set "${options.policyName}" in the ${state.getRealm()} realm.`
);

// try and find script in current realm
outcome = await configManagerImportAuthzPolicySet(options.policyName);

// check other realms for the script but only if there is no config file specified
if (!outcome && !options.file) {
const checkedRealms: string[] = [state.getRealm()];
for (const realm of await readRealms()) {
if (outcome) {
break;
}
if (!checkedRealms.includes(realm.name)) {
printMessage(
`importing the policy set "${options.policyName}" from the ${checkedRealms[checkedRealms.length - 1]} realm failed.`
);
state.setRealm(realm.name);
checkedRealms.push(state.getRealm());
printMessage(
`Looking for the policy set "${options.policyName}" in the ${state.getRealm()} realm now.`
);
outcome = await configManagerImportAuthzPolicySet(
options.policyName
);
}
}
if (!outcome) {
printMessage(
`Did not find the policy set "${options.policyName}" anywhere.`
);
}
}
}

// -r/--realm
else if (realm !== constants.DEFAULT_REALM_KEY) {
printMessage(
`importing all the policy sets in the ${state.getRealm()} realm.`
);
outcome = await configManagerImportAuthzPolicySetsRealm(
options.realm
);
}

// import all policy sets from all realms, the default when no options are provided
else {
printMessage('importing all the policy sets in the host tenant.');
outcome = await configManagerImportAuthzPoliciesAll();
}

if (!outcome) {
printMessage(
`Failed to import one or more authorization policy sets. ${options.verbose ? '' : 'Check --verbose for me details.'}`
);
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 AuthzPolicies from './config-manager-push-authz-policies';
import EmailProvider from './config-manager-push-email-provider';
import Endpoints from './config-manager-push-endpoints';
import Kba from './config-manager-push-kba';
Expand All @@ -19,6 +20,6 @@ export default function setup() {
program.addCommand(EmailProvider().name('email-provider'));
program.addCommand(Endpoints().name('endpoints'));
program.addCommand(Kba().name('kba'));

program.addCommand(AuthzPolicies().name('authz-policies'));
return program;
}
152 changes: 152 additions & 0 deletions src/configManagerOps/FrConfigAuthzPoliciesOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { frodo, state } from '@rockcarver/frodo-lib';
import { PolicySkeleton } from '@rockcarver/frodo-lib/types/api/PoliciesApi';
import { PolicySetSkeleton } from '@rockcarver/frodo-lib/types/api/PolicySetApi';
import { ResourceTypeSkeleton } from '@rockcarver/frodo-lib/types/api/ResourceTypesApi';
import { PolicySetExportInterface } from '@rockcarver/frodo-lib/types/ops/PolicySetOps';
import fs from 'fs';
import { readFile } from 'fs/promises';

import { printError, verboseMessage } from '../utils/Console';
Expand Down Expand Up @@ -228,3 +230,153 @@ export async function configManagerExportAuthzPoliciesAll(): Promise<boolean> {
return false;
}
}

/**
* Import all policy sets (with their policies and resource types) for the current realm
* @param realm the specified realm the user wants to improt to
* @returns {Promise<boolean>} true if import was successful
*/
export async function configManagerImportAuthzPolicySetsRealm(
realm: string
): Promise<boolean> {
try {
if (realm === '/') {
return true;
}

state.setRealm(realm);
const realmAuthzDir = `realms/${state.getRealm()}/authorization`;
const resourcetype: Record<string, any> = {};
const resourceTypesDir = getFilePath(`${realmAuthzDir}/resource-types`);

if (fs.existsSync(resourceTypesDir)) {
const readReadTypesFiles = fs.readdirSync(resourceTypesDir);
for (const file of readReadTypesFiles) {
if (file.endsWith('.json')) {
const data = JSON.parse(
fs.readFileSync(`${resourceTypesDir}/${file}`, 'utf8')
);
resourcetype[data.uuid] = data;
}
}
}

const policyset: Record<string, any> = {};
const policyMap: Record<string, any> = {};
const policySetsDir = getFilePath(`${realmAuthzDir}/policy-sets`);

if (fs.existsSync(policySetsDir)) {
const readPolicySetDir = fs.readdirSync(policySetsDir);

for (const psDir of readPolicySetDir) {
const psFilePath = `${policySetsDir}/${psDir}/${psDir}.json`;
const psData = JSON.parse(fs.readFileSync(psFilePath, 'utf8'));
policyset[psData.name] = psData;
const policiesDir = `${policySetsDir}/${psDir}/policies`;

for (const file of fs.readdirSync(policiesDir)) {
if (file.endsWith('.json')) {
const pData = JSON.parse(
fs.readFileSync(`${policiesDir}/${file}`, 'utf8')
);
policyMap[pData.name] = pData;
}
}
}
}

const importData: PolicySetExportInterface = {
script: {},
resourcetype,
policy: policyMap,
policyset,
};

await policySet.importPolicySets(importData);
return true;
} catch (error) {
printError(error);
return false;
}
}

export async function configManagerImportAuthzPolicySet(
policySetName: string
): Promise<boolean> {
try {
if (state.getRealm() === '/') {
return false;
}
const psDir = getFilePath(
`realms/${state.getRealm()}/authorization/policy-sets/${policySetName}`
);
const psFilePath = `${psDir}/${policySetName}.json`;
const psData: PolicySetSkeleton = JSON.parse(
fs.readFileSync(psFilePath, 'utf8')
);

const policyMap: Record<string, PolicySkeleton> = {};
const policiesDir = `${psDir}/policies`;
const policyFiles = fs.readdirSync(policiesDir);

for (const file of policyFiles) {
if (file.endsWith('.json')) {
const pData: PolicySkeleton = JSON.parse(
fs.readFileSync(`${policiesDir}/${file}`, 'utf8')
);
policyMap[pData.name] = pData;
}
}

const resourcetype: Record<string, ResourceTypeSkeleton> = {};
const resourceDir = getFilePath(
`realms/${state.getRealm()}/authorization/resource-types`
);

if (fs.existsSync(resourceDir)) {
const readResourceDir = fs.readdirSync(resourceDir);
for (const file of readResourceDir) {
if (file.endsWith('.json')) {
const rtData: ResourceTypeSkeleton = JSON.parse(
fs.readFileSync(`${resourceDir}/${file}`, 'utf8')
);
if (psData.resourceTypeUuids.includes(rtData.uuid)) {
resourcetype[rtData.uuid] = rtData;
}
}
}
}

const importData: PolicySetExportInterface = {
script: {},
resourcetype,
policy: policyMap,
policyset: { [psData.name]: psData },
};

await policySet.importPolicySet(policySetName, importData);
return true;
} catch (error) {
printError(error);
return false;
}
}

/**
* Import all policy sets from all realms
* @returns {Promise<boolean>} true if all imports were successful
*/
export async function configManagerImportAuthzPoliciesAll(): Promise<boolean> {
try {
for (const realm of await readRealms()) {
state.setRealm(realm.name);
if (!(await configManagerImportAuthzPolicySetsRealm(realm.name))) {
return false;
}
}
return true;
} catch (error) {
printError(error);
return false;
}
}
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 authz-policies' should be expected english 1`] = `
"Usage: frodo config-manager push authz-policies [options] [host] [realm] [username] [password]

Import authorization policies from realm.

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, --policy-name <policy-set-name> Import only a specific policy set with the name.
--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> Specifies the realm to import from. Only policy sets from this realm will be imported.
--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.

"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Options:
-h, --help Help

Commands:
authz-policies Import authorization policies from realm.
email-provider Import email provider configuration.
endpoints Import custom endpoints objects.
help display help for command
Expand Down
Loading