-
Notifications
You must be signed in to change notification settings - Fork 825
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for defining IAM Permissions Boundary for Project (#…
…7144) Adds a new advanced project configuration option to specify a permissions boundary that will be applied to all IAM roles in the project. This is broken into 3 components: 1. add a permissions boundary state manager to amplify-cli-core 2. add a prompt to amplify configure project that writes the boundary to the state manager 3. adds a permissions boundary template modifier to the pre-push cloudformation transformer that reads the value from the state manager and applies it to cfn templates
- Loading branch information
1 parent
2423e2e
commit acf031b
Showing
26 changed files
with
757 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
packages/amplify-cli-core/src/__tests__/permissionsBoundaryState.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { getPermissionsBoundaryArn, setPermissionsBoundaryArn } from '..'; | ||
import { stateManager } from '../state-manager'; | ||
|
||
jest.mock('../state-manager'); | ||
|
||
const testEnv = 'testEnv'; | ||
|
||
const objKey = 'PermissionsBoundaryPolicyArn'; | ||
|
||
const stateManager_mock = stateManager as jest.Mocked<typeof stateManager>; | ||
stateManager_mock.getLocalEnvInfo.mockReturnValue({ | ||
envName: testEnv, | ||
}); | ||
|
||
const testArn = 'testArn'; | ||
|
||
const tpi_stub = { | ||
[testEnv]: { | ||
awscloudformation: { | ||
[objKey]: testArn, | ||
}, | ||
}, | ||
}; | ||
|
||
describe('get permissions boundary arn', () => { | ||
beforeEach(jest.clearAllMocks); | ||
it('gets arn from team provider info file', () => { | ||
stateManager_mock.getTeamProviderInfo.mockReturnValueOnce(tpi_stub); | ||
expect(getPermissionsBoundaryArn()).toEqual(testArn); | ||
}); | ||
|
||
it('gets arn from preInitTeamProviderInfo', () => { | ||
// setup | ||
setPermissionsBoundaryArn(testArn, testEnv, tpi_stub); | ||
|
||
// test | ||
expect(getPermissionsBoundaryArn()).toEqual(testArn); | ||
|
||
// reset | ||
setPermissionsBoundaryArn(testArn, testEnv); | ||
}); | ||
|
||
it('returns undefined if no value found', () => { | ||
expect(getPermissionsBoundaryArn()).toBeUndefined(); | ||
}); | ||
}); | ||
|
||
describe('set permissions boundary arn', () => { | ||
beforeEach(jest.clearAllMocks); | ||
it('sets the ARN value in tpi file if specified', () => { | ||
stateManager_mock.getTeamProviderInfo.mockReturnValueOnce({}); | ||
setPermissionsBoundaryArn(testArn); | ||
expect(stateManager_mock.setTeamProviderInfo.mock.calls[0][1][testEnv].awscloudformation[objKey]).toEqual(testArn); | ||
}); | ||
|
||
it('sets the ARN for the specified env', () => { | ||
stateManager_mock.getTeamProviderInfo.mockReturnValueOnce({}); | ||
setPermissionsBoundaryArn(testArn, 'otherenv'); | ||
expect(stateManager_mock.setTeamProviderInfo.mock.calls[0][1].otherenv.awscloudformation[objKey]).toEqual(testArn); | ||
}); | ||
|
||
it('removes the ARN value if not specified', () => { | ||
stateManager_mock.getTeamProviderInfo.mockReturnValueOnce(tpi_stub); | ||
setPermissionsBoundaryArn(); | ||
expect(stateManager_mock.setTeamProviderInfo.mock.calls[0][1][testEnv].awscloudformation).toBeDefined(); | ||
expect(stateManager_mock.setTeamProviderInfo.mock.calls[0][1][testEnv].awscloudformation[objKey]).toBeUndefined(); | ||
}); | ||
|
||
it('if tpi object specified, sets arn in object and sets global preInitTeamProviderInfo', () => { | ||
const tpi: Record<string, any> = {}; | ||
setPermissionsBoundaryArn(testArn, undefined, tpi); | ||
expect(tpi[testEnv].awscloudformation[objKey]).toEqual(testArn); | ||
expect(getPermissionsBoundaryArn()).toEqual(testArn); | ||
delete (global as any).preInitTeamProviderInfo; | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { stateManager } from './state-manager'; | ||
import _ from 'lodash'; | ||
import { $TSObject } from '.'; | ||
|
||
let preInitTeamProviderInfo: any; | ||
|
||
export const getPermissionsBoundaryArn = (env?: string): string | undefined => { | ||
try { | ||
const tpi = preInitTeamProviderInfo ?? stateManager.getTeamProviderInfo(); | ||
// if the pre init team-provider-info only has one env (which should always be the case), default to that one | ||
if (preInitTeamProviderInfo && Object.keys(preInitTeamProviderInfo).length === 1 && !env) { | ||
env = Object.keys(preInitTeamProviderInfo)[0]; | ||
} | ||
return _.get(tpi, teamProviderInfoObjectPath(env)) as string | undefined; | ||
} catch { | ||
// uninitialized project | ||
return undefined; | ||
} | ||
}; | ||
|
||
/** | ||
* Stores the permissions boundary ARN in team-provider-info | ||
* If teamProviderInfo is not specified, the file is read, updated and written back to disk | ||
* If teamProviderInfo is specified, then this function assumes that the env is not initialized | ||
* In this case, the teamProviderInfo object is updated but not written to disk. Instead "preInitTeamProviderInfo" is set | ||
* so that subsequent calls to getPermissionsBoundaryArn will return the permissions boundary arn of the pre-initialized env | ||
* @param arn The permissions boundary arn. If undefined or empty, the permissions boundary is removed | ||
* @param env The Amplify env to update. If not specified, defaults to the current checked out environment | ||
* @param teamProviderInfo The team-provider-info object to update | ||
*/ | ||
export const setPermissionsBoundaryArn = (arn?: string, env?: string, teamProviderInfo?: $TSObject): void => { | ||
let tpiGetter = () => stateManager.getTeamProviderInfo(); | ||
let tpiSetter = (tpi: $TSObject) => { | ||
stateManager.setTeamProviderInfo(undefined, tpi); | ||
preInitTeamProviderInfo = undefined; | ||
}; | ||
if (teamProviderInfo) { | ||
tpiGetter = () => teamProviderInfo; | ||
tpiSetter = (tpi: $TSObject) => { | ||
preInitTeamProviderInfo = tpi; | ||
}; | ||
} | ||
const tpi = tpiGetter(); | ||
if (!arn) { | ||
_.unset(tpi, teamProviderInfoObjectPath(env)); | ||
} else { | ||
_.set(tpi, teamProviderInfoObjectPath(env), arn); | ||
} | ||
tpiSetter(tpi); | ||
}; | ||
|
||
const teamProviderInfoObjectPath = (env?: string) => [ | ||
env || (stateManager.getLocalEnvInfo().envName as string), | ||
'awscloudformation', | ||
'PermissionsBoundaryPolicyArn', | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { $TSContext } from 'amplify-cli-core'; | ||
import { executeProviderCommand } from '../../extensions/amplify-helpers/get-provider-plugins'; | ||
|
||
export const run = async (context: $TSContext) => { | ||
await executeProviderCommand(context, 'updateEnv'); | ||
}; |
Oops, something went wrong.