Skip to content

Commit

Permalink
feat: Support for advanced config with source_profiles (#231)
Browse files Browse the repository at this point in the history
This extension now supports advanced configuration with `source_profile` and `target_role_name`. When using advanced configuration roles that does not match with root accounts alias are not shown anymore in popup. This enhanced the visablility of roles that are actually needed or can be assumed.
  • Loading branch information
janstuemmel authored Jun 17, 2023
1 parent 29f1bcb commit 0757562
Show file tree
Hide file tree
Showing 20 changed files with 537 additions and 51 deletions.
31 changes: 24 additions & 7 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Enter your roles in the editor via `ini` format:
### Example config

```ini

# A comment

[role-title]
Expand All @@ -35,20 +36,36 @@ group = My awesome account
region = eu-central-1
```

### Example config with `target_role_name`
### Advanced Config

It's possible to use `source_profile` in roles to point to a parent profile. Using parent profiles implicates two things:
* `target_role_name`: Defining a target role name will exclude the role from being displayed in popup window because it is a source profile. Every child profile will use `target_role_name` as `role_name` if it is not defined.
* `aws_account_id`: Setting this field to the root account alias or id will result in not showing roles that are not associated with `source_profile` in popup on current tab's aws-console.

```ini
[my-org]
aws_account_id = your_org_alias_or_id

[parent]
aws_account_id = my-org
target_role_name = MyRole

[child-profile]
# this profile will be shown on active aws-console tab in popup
[child-profile1]
aws_account_id = 123456789101
source_profile = my-org
# uses target_role_name as role_name from above
source_profile = parent

# this profile will not be shown on active aws-console tab in popup
[child-profile2]
aws_account_id = 123456789101
role_name = UserRole
```

When using a `source_profile` as parent and there is no `role_arn` specified in the child profile, the child profile uses `target_role_name` as `role_name`.
Example (see config above):
* You're logged in to aws-console with org alias or id `my-org`
* Your current tab is aws-console
* You open the extensions popup
* `child-profile1` is shown in popup as it is associated with `parent`
* `child-profile2` is **not** shown in popup as it is **not** associated with `parent`


## Usage

Expand Down
4 changes: 2 additions & 2 deletions src/background/background.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { addMessageListener } from '../common/browser';
import { getMappedConfig } from '../common/config';
import { getMappedConfig } from './handlers/getMappedConfig';
import { redirectListener } from './handlers/redirectListener';

// return true to indicate a asyncronous sendResponse
Expand All @@ -9,7 +9,7 @@ addMessageListener((msg, sender, sendResponse) => {
redirectListener(msg, sender);
return true;
case 'getConfig':
getMappedConfig().then((res) => {
getMappedConfig(msg.url).then((res) => {
sendResponse(res);
});
return true;
Expand Down
106 changes: 106 additions & 0 deletions src/background/handlers/getMappedConfig.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
jest.mock('../../common/config/storage');
jest.mock('../../common/browser/cookies');

import { getConfig } from '../../common/config/storage';
import { mock } from "../../test/helper";
import { getMappedConfig } from "./getMappedConfig";
import { getAccountAlias } from '../../common/browser/cookies';

const getConfigMock = mock(getConfig);
const getAccountAliasMock = mock(getAccountAlias);

beforeEach(() => {
getConfigMock.mockClear();
getAccountAliasMock.mockClear();
});

it('shows all roles when alias not matching any roles', async () => {
getAccountAliasMock.mockImplementationOnce(() => Promise.resolve('invalid'));
getConfigMock.mockImplementationOnce(() => Promise.resolve(`
[org1]
target_role_name = foo
aws_account_id = org1
[role1]
aws_account_id = bar1
source_profile = org1
[role2]
aws_account_id = bar2
role_name = foo
`));
expect(await getMappedConfig('')).toMatchInlineSnapshot(`
[
{
"aws_account_id": "bar2",
"role_name": "foo",
"title": "role2",
},
{
"aws_account_id": "bar1",
"role_name": "foo",
"source_profile_account_id": "org1",
"title": "role1",
},
]
`);
});

it('shows only role associated with source role when alias matching', async () => {
getAccountAliasMock.mockImplementationOnce(() => Promise.resolve('org1'));
getConfigMock.mockImplementationOnce(() => Promise.resolve(`
[org1]
target_role_name = foo
aws_account_id = org1
[role1]
aws_account_id = bar1
source_profile = org1
[role2]
aws_account_id = bar2
role_name = foo
`));
expect(await getMappedConfig('')).toMatchInlineSnapshot(`
[
{
"aws_account_id": "bar1",
"role_name": "foo",
"source_profile_account_id": "org1",
"title": "role1",
},
]
`);
});

it('shows all roles when getAccountAlias errors', async () => {
getAccountAliasMock.mockImplementationOnce(() => Promise.resolve(undefined));
getConfigMock.mockImplementationOnce(() => Promise.resolve(`
[org1]
target_role_name = foo
aws_account_id = org1
[role1]
aws_account_id = bar1
source_profile = org1
[role2]
aws_account_id = bar2
role_name = foo
`));
expect(await getMappedConfig('')).toMatchInlineSnapshot(`
[
{
"aws_account_id": "bar2",
"role_name": "foo",
"title": "role2",
},
{
"aws_account_id": "bar1",
"role_name": "foo",
"source_profile_account_id": "org1",
"title": "role1",
},
]
`);
});
15 changes: 15 additions & 0 deletions src/background/handlers/getMappedConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { getAccountAlias } from '../../common/browser/cookies';
import {
getConfig,
mapConfig,
parseConfig,
filterBySourceAccountAlias,
} from '../../common/config';

export const getMappedConfig = async (aliasUrl: string) => {
const alias = await getAccountAlias(aliasUrl) ?? '';
return await getConfig()
.then(parseConfig)
.then(mapConfig)
.then(filterBySourceAccountAlias(alias));
};
5 changes: 5 additions & 0 deletions src/common/browser/cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { browserOrChrome } from ".";

export const getAccountAlias = async (url: string) => browserOrChrome().cookies.get({url, name: 'aws-account-alias'})
.then(cookie => cookie?.value)
.catch(() => undefined);
4 changes: 4 additions & 0 deletions src/common/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ export {
MAX_ITEM_SIZE as STORAGE_MAX_ITEM_SIZE,
MAX_TOTAL_SIZE as STORAGE_MAX_TOTAL_SIZE
} from './storage';

// chrome and firefox api's are not fully compatible because chrome
// uses service workers and firefox background scripts
export const browserOrChrome = () => typeof browser !== 'undefined' ? browser : chrome;
98 changes: 98 additions & 0 deletions src/common/config/filterBySourceAccountAlias.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { filterBySourceAccountAlias } from "./filterBySourceAccountAlias";

it('should filter by source_profile_Account_id', () => {
const config = filterBySourceAccountAlias('org1')([
{ title: 'role1', aws_account_id: '1234', role_name: 'foo', source_profile_account_id: 'org1' },
{ title: 'role1', aws_account_id: '1234', role_name: 'foo', source_profile_account_id: 'org1' },
{ title: 'role2', aws_account_id: '1234', role_name: 'foo', source_profile_account_id: 'org2' },
{ title: 'role3', aws_account_id: '1234', role_name: 'foo' },
]);
expect(config).toMatchInlineSnapshot(`
[
{
"aws_account_id": "1234",
"role_name": "foo",
"source_profile_account_id": "org1",
"title": "role1",
},
{
"aws_account_id": "1234",
"role_name": "foo",
"source_profile_account_id": "org1",
"title": "role1",
},
]
`);
});

it('should return all roles when alias empty', () => {
const config = filterBySourceAccountAlias('')([
{ title: 'role1', aws_account_id: '1234', role_name: 'foo', source_profile_account_id: 'org1' },
{ title: 'role1', aws_account_id: '1234', role_name: 'foo', source_profile_account_id: 'org1' },
{ title: 'role2', aws_account_id: '1234', role_name: 'foo', source_profile_account_id: 'org2' },
{ title: 'role3', aws_account_id: '1234', role_name: 'foo' },
]);
expect(config).toMatchInlineSnapshot(`
[
{
"aws_account_id": "1234",
"role_name": "foo",
"source_profile_account_id": "org1",
"title": "role1",
},
{
"aws_account_id": "1234",
"role_name": "foo",
"source_profile_account_id": "org1",
"title": "role1",
},
{
"aws_account_id": "1234",
"role_name": "foo",
"source_profile_account_id": "org2",
"title": "role2",
},
{
"aws_account_id": "1234",
"role_name": "foo",
"title": "role3",
},
]
`);
});

it('should return all when no alias match source_profile_Account_id', () => {
const config = filterBySourceAccountAlias('')([
{ title: 'role1', aws_account_id: '1234', role_name: 'foo', source_profile_account_id: 'org1' },
{ title: 'role1', aws_account_id: '1234', role_name: 'foo', source_profile_account_id: 'org1' },
{ title: 'role2', aws_account_id: '1234', role_name: 'foo', source_profile_account_id: 'org2' },
{ title: 'role3', aws_account_id: '1234', role_name: 'foo' },
]);
expect(config).toMatchInlineSnapshot(`
[
{
"aws_account_id": "1234",
"role_name": "foo",
"source_profile_account_id": "org1",
"title": "role1",
},
{
"aws_account_id": "1234",
"role_name": "foo",
"source_profile_account_id": "org1",
"title": "role1",
},
{
"aws_account_id": "1234",
"role_name": "foo",
"source_profile_account_id": "org2",
"title": "role2",
},
{
"aws_account_id": "1234",
"role_name": "foo",
"title": "role3",
},
]
`);
});
10 changes: 10 additions & 0 deletions src/common/config/filterBySourceAccountAlias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const filterBySourceAccountAlias = (alias: string) => (config: AWSConfig) => {
const hasSourceAccountAlias = !!config.find((configItem) => configItem.source_profile_account_id === alias);
return config.filter((configItem: AWSConfigItem) => {
return (
!alias ||
!hasSourceAccountAlias ||
configItem.source_profile_account_id === alias
);
});
};
10 changes: 1 addition & 9 deletions src/common/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import { mapConfig } from './mapper';
import { parseConfig } from './parse';
import { getConfig } from './storage';

export {
setConfig,
getConfig,
Expand All @@ -16,8 +12,4 @@ export {
compressConfig,
} from './gzip';

export const getMappedConfig = async () => {
return getConfig()
.then(parseConfig)
.then(mapConfig);
};
export { filterBySourceAccountAlias } from './filterBySourceAccountAlias';
Loading

0 comments on commit 0757562

Please sign in to comment.