@cloud-copilot/iam-simulate
Version:
Simulate evaluation of AWS IAM policies
163 lines • 6.07 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isActualContextKey = isActualContextKey;
exports.contextKeyParts = contextKeyParts;
exports.normalizeContextKeyCase = normalizeContextKeyCase;
exports.typeForContextKey = typeForContextKey;
const iam_data_1 = require("@cloud-copilot/iam-data");
const globalConditionKeys_js_1 = require("../global_conditions/globalConditionKeys.js");
const oidcKeys = new Set(['aud', 'sub', 'email', 'oaud', 'sub']);
const oidcProviderPattern = /^[0-9a-zA-Z\._\-]+$/;
/**
* Check if a context key actually exists
*
* @param key The context key to check
* @returns true if the context key is valid, false otherwise
*/
async function isActualContextKey(key) {
if (key.includes('/')) {
return isActualContextKeyWithVariable(key);
}
if ((0, globalConditionKeys_js_1.globalConditionKeyExists)(key)) {
return true;
}
if (isOidcConditionKey(key)) {
return true;
}
const parts = key.split(':');
if (parts.length !== 2) {
return false;
}
const [service, action] = parts;
const serviceExists = await (0, iam_data_1.iamServiceExists)(service);
if (!serviceExists) {
return false;
}
const actionExists = await (0, iam_data_1.iamConditionKeyExists)(service, key);
return actionExists;
}
/**
* Checks if a context key with a variable in it is a valid context key
*/
async function isActualContextKeyWithVariable(key) {
const slashLocation = key.indexOf('/');
const prefix = key.slice(0, slashLocation);
const rest = key.slice(slashLocation + 1);
if (rest.length === 0) {
return false;
}
const globalKey = (0, globalConditionKeys_js_1.getVariableGlobalConditionKeyByPrefix)(prefix);
if (globalKey) {
return true;
}
const serviceKey = await serviceContextKeyDetails(key);
return !!serviceKey;
}
/**
* Takes context key and returns the details for it, accounting for any variables in the key
*
* @param contextKey The context key to get the details for
* @returns The details for the context key if it exists. if the key has variables in it it will return the details for the variable key
*/
async function serviceContextKeyDetails(contextKey) {
const [service, key] = contextKeyParts(contextKey.toLowerCase());
const serviceExists = await (0, iam_data_1.iamServiceExists)(service);
if (!serviceExists) {
return undefined;
}
if (key.includes('/')) {
const prefix = service + ':' + key.slice(0, key.indexOf('/') + 1);
const allConditionsKeys = await (0, iam_data_1.iamConditionKeysForService)(service);
const matchingKey = allConditionsKeys.find((k) => k.toLowerCase().startsWith(prefix));
if (matchingKey) {
return await (0, iam_data_1.iamConditionKeyDetails)(service, matchingKey);
}
return undefined;
}
const exists = await (0, iam_data_1.iamConditionKeyExists)(service, contextKey);
if (!exists) {
return undefined;
}
return (0, iam_data_1.iamConditionKeyDetails)(service, contextKey);
}
/**
* Split a context key into the service and the rest of the key. This has to be a special
* method because context keys with variables may have a colon in the variable section,
* because of course they can.
*
* @param contextKey The context key to split
* @returns A tuple with the service and the rest of the key
*/
function contextKeyParts(contextKey) {
const colonIndex = contextKey.indexOf(':');
return [contextKey.slice(0, colonIndex), contextKey.slice(colonIndex + 1)];
}
/**
* Check the capitalization of a context key and return the correct capitalization
*
* @param contextKey the condition key to check
* @returns if the condition key is an array type
*/
async function normalizeContextKeyCase(contextKey) {
const serviceKey = await serviceContextKeyDetails(contextKey);
if (serviceKey) {
return replaceVariableInContextKey(serviceKey.key, contextKey);
}
const globalConditionKey = (0, globalConditionKeys_js_1.getGlobalConditionKeyWithOrWithoutPrefix)(contextKey);
if (globalConditionKey) {
return replaceVariableInContextKey(globalConditionKey.key, contextKey);
}
if (isOidcConditionKey(contextKey)) {
return contextKey;
}
throw new Error(`Context key ${contextKey} not found`);
}
/**
* Replaces a variable in a context key with the actual value from a policy
*
* @param specKey the string value of the condition key spec
* @param actualKey the string value of the condition key in the policy
* @returns the spec condition key with the variable portion replaced with the actual value
*/
function replaceVariableInContextKey(specKey, actualKey) {
const slashIndex = specKey.indexOf('/');
if (slashIndex === -1) {
return specKey;
}
const prefix = specKey.slice(0, slashIndex);
const suffix = actualKey.slice(slashIndex);
return prefix + suffix;
}
/**
* Get the type of a context key
*
* @param contextKey - The string condition key to get the type for
* @returns The type of the condition key
* @throws an error if the condition key is not found
*/
async function typeForContextKey(contextKey) {
const globalConditionKey = (0, globalConditionKeys_js_1.getGlobalConditionKeyWithOrWithoutPrefix)(contextKey);
if (globalConditionKey) {
return globalConditionKey.dataType;
}
const keyDetails = await serviceContextKeyDetails(contextKey);
if (keyDetails) {
return keyDetails.type;
}
throw new Error(`Condition key ${contextKey} not found`);
}
/**
* Checks if a string is a valid OIDC condition key
*
* @param key the key to check
* @returns true if the key is a valid OIDC condition key
*/
function isOidcConditionKey(key) {
const parts = key.split(':');
if (parts.length !== 2) {
return false;
}
const [service, action] = parts;
return oidcKeys.has(action) && oidcProviderPattern.test(service);
}
//# sourceMappingURL=contextKeys.js.map