Skip to content

Commit

Permalink
feat: Add support for target_role_name and source_profile (#228)
Browse files Browse the repository at this point in the history
closes #225
  • Loading branch information
janstuemmel authored Jun 15, 2023
1 parent 8d11202 commit 06248fc
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 63 deletions.
11 changes: 10 additions & 1 deletion docs/example-config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,13 @@ group = Personal AWS Account
[personal-homepage-shared]
aws_account_id = 257355895763
role_name = AdminFullAccess
group = Personal AWS Account
group = Personal AWS Account

# Project with org
[my-org]
aws_account_id = myorg
target_role_name = AdminFullAccess

[child-profile]
aws_account_id = 123456789101
source_profile = my-org
15 changes: 15 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ group = My awesome account
region = eu-central-1
```

### Example config with `target_role_name`

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

[child-profile]
aws_account_id = 123456789101
source_profile = my-org
# uses target_role_name as role_name from above
```

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`.

## Usage

After entering your config, roles will show up in the popup window. You can filter roles via the search input.
Expand Down
4 changes: 1 addition & 3 deletions src/background/handlers/redirectListener.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { updateTabUrl } from '../../common/browser/tabs';
import { mapToSwitchForm } from '../../common/mappers';

const removeUndefinedEntries = (obj: object) => Object.fromEntries(
Object.entries(obj).filter(([_, v]) => v));
import { removeUndefinedEntries } from '../../common/util';

export const redirectListener = async (
msg: AWSConfigItem,
Expand Down
86 changes: 50 additions & 36 deletions src/common/config/mapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ it('should map to default group', () => {
role_name: 'bar',
region: 'us-east-1'
},
'baz': {
'baz profile': {
role_arn: 'arn:aws:iam::123456789012:role/MyRole'
}
} as StoredConfig;
Expand All @@ -20,29 +20,48 @@ it('should map to default group', () => {
[
{
"aws_account_id": "123456789012",
"color": undefined,
"region": undefined,
"role_name": "MyRole",
"title": "baz",
"title": "baz profile",
},
{
"aws_account_id": "foo",
"color": undefined,
"region": "us-east-1",
"role_name": "bar",
"title": "bar",
},
{
"aws_account_id": "foo",
"color": undefined,
"region": undefined,
"role_name": "bar",
"title": "foo",
},
]
`);
});

it('should map to default group', () => {
const stored = {
'source': {
aws_account_id: 'org',
target_role_name: 'UserRole'
},
'foo': {
aws_account_id: 'foo',
source_profile: 'source',
},
} as StoredConfig;
const config = mapConfig(stored);
expect(config).toMatchInlineSnapshot(`
[
{
"aws_account_id": "foo",
"role_name": "UserRole",
"source_profile": "source",
"title": "foo",
},
]
`);
});

it('should map to groups', () => {
const stored = {
'foo': {
Expand All @@ -60,16 +79,12 @@ it('should map to groups', () => {
[
{
"aws_account_id": "foo",
"color": undefined,
"region": undefined,
"role_name": "bar",
"title": "foo",
},
{
"aws_account_id": "foo",
"color": undefined,
"group": "baz",
"region": undefined,
"role_name": "bar",
"title": "bar",
},
Expand Down Expand Up @@ -99,35 +114,52 @@ it('should sort by groups', () => {
[
{
"aws_account_id": "foo",
"color": undefined,
"region": undefined,
"role_name": "ccc",
"title": "foo",
},
{
"aws_account_id": "foo",
"color": undefined,
"group": "aaa",
"region": undefined,
"role_name": "bar",
"title": "bar",
},
{
"aws_account_id": "foo",
"color": undefined,
"group": "bbb",
"region": undefined,
"role_name": "bar",
"title": "baz",
},
]
`);
});

it('should omit entries with invalid values', () => {
it('should omit entries with missing account_id', () => {
const stored = {
'foo': {
role_name: 'bar',
},
'bar': {
aws_account_id: 'foo',
role_name: 'bar',
},
'baz': 1337,
};
const config = mapConfig(stored as object as StoredConfig);
expect(config).toMatchInlineSnapshot(`
[
{
"aws_account_id": "foo",
"role_name": "bar",
"title": "bar",
},
]
`);
});

it('should omit entries with missing role_name', () => {
const stored = {
'foo': {
aws_account_id: 'bar',
},
'bar': {
aws_account_id: 'foo',
Expand All @@ -140,8 +172,6 @@ it('should omit entries with invalid values', () => {
[
{
"aws_account_id": "foo",
"color": undefined,
"region": undefined,
"role_name": "bar",
"title": "bar",
},
Expand All @@ -164,8 +194,6 @@ it('should omit entry with invalid role_arn', () => {
[
{
"aws_account_id": "foo",
"color": undefined,
"region": undefined,
"role_name": "bar",
"title": "bar",
},
Expand All @@ -184,8 +212,6 @@ it('should extract role_name and aws_account_id from role_arn', () => {
[
{
"aws_account_id": "123456789012",
"color": undefined,
"region": undefined,
"role_name": "MyRole",
"title": "foo",
},
Expand All @@ -206,7 +232,6 @@ it('should map region', () => {
[
{
"aws_account_id": "foo",
"color": undefined,
"region": "eu-central-1",
"role_name": "bar",
"title": "foo",
Expand All @@ -228,8 +253,6 @@ it('should not map invalid region', () => {
[
{
"aws_account_id": "foo",
"color": undefined,
"region": undefined,
"role_name": "bar",
"title": "foo",
},
Expand Down Expand Up @@ -265,33 +288,24 @@ it('should sort correctly', () => {
[
{
"aws_account_id": "foo",
"color": undefined,
"group": undefined,
"region": undefined,
"role_name": "bar",
"title": "bar2",
},
{
"aws_account_id": "foo",
"color": undefined,
"group": "b",
"region": undefined,
"role_name": "bar",
"title": "foo",
},
{
"aws_account_id": "foo",
"color": undefined,
"group": "b",
"region": undefined,
"role_name": "bar",
"title": "foo2",
},
{
"aws_account_id": "foo",
"color": undefined,
"group": "a",
"region": undefined,
"role_name": "bar",
"title": "bar",
},
Expand Down
34 changes: 14 additions & 20 deletions src/common/config/mapper.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { ColorTranslator } from 'colortranslator';

import { availableRegions } from './availableRegions';
import { removeUndefinedEntries } from '../util';

// Possible role_name syntax: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-quotas.html
const ROLE_ARN_REGEX = /^arn:aws:iam::(?<aws_account_id>\d{12}):role\/(?<role_name>[\w+=,.@-]+)/;

const isValidConfigEntry = ({ aws_account_id, role_name, role_arn }: AWSStoredConfigItem): boolean =>
!!aws_account_id && !!role_name || !!role_arn;
const HEX_COLOR_REGEX = /^(?<hex>[0-9A-Fa-f]{3,8})$/;

const isValidConfigItem = ({ aws_account_id, role_name }: Partial<AWSConfigItem>): boolean =>
!!aws_account_id && !!role_name;

function getColorHEX(color: string) {
const match = new RegExp(/^(?<hex>[0-9A-Fa-f]{3,8})$/).exec(color);
const match = new RegExp(HEX_COLOR_REGEX).exec(color);
const cssColor = match?.groups?.hex ? `#${match.groups.hex}` : color;

try {
Expand All @@ -22,10 +21,7 @@ function getColorHEX(color: string) {
}
}

const mapColor = ({ color = '', ...rest }: Partial<AWSConfigItem>): Partial<AWSConfigItem> =>
({ ...rest, color: getColorHEX(color) });

const trimTitle = (title: string) => title.replace('profile', '').trim();
const trimTitle = (title: string) => title.replace(/^profile/, '').trim();

// sorts the config by first appearance of a group
// ungrouped entries should still be on top
Expand Down Expand Up @@ -59,28 +55,26 @@ export const sortByGroupIndex = (config: AWSConfig) => {
};
};

const buildConfigItem = (
title: string,
{ role_arn = '', aws_account_id, role_name, region: regionTo, ...rest }: AWSStoredConfigItem
): Partial<AWSConfigItem> => {
const buildConfigItem = (config: StoredConfig) => (key: string): Partial<AWSConfigItem> => {
const { role_arn = '', aws_account_id, role_name, region: regionTo, color = '', ...rest } = config[key];
const region = availableRegions.includes(regionTo || '') ? regionTo : undefined;
const match = new RegExp(ROLE_ARN_REGEX).exec(role_arn);
const source = config[rest.source_profile ?? ''] ?? {};

return {
return removeUndefinedEntries({
...rest,
title: trimTitle(title),
title: trimTitle(key),
color: getColorHEX(color),
region,
aws_account_id: match?.groups?.aws_account_id ?? aws_account_id,
role_name: match?.groups?.role_name ?? role_name,
};
role_name: match?.groups?.role_name ?? role_name ?? source.target_role_name,
});
};

export function mapConfig(config: StoredConfig): AWSConfig {
const entries = Object.keys(config)
.filter((val) => isValidConfigEntry(config[val]))
.map((configEntry) => buildConfigItem(configEntry, config[configEntry]))
.filter(isValidConfigItem)
.map(mapColor) as AWSConfig;
.map(buildConfigItem(config))
.filter(isValidConfigItem) as AWSConfig;

return entries
.sort(sortByGroupIndex(entries));
Expand Down
1 change: 1 addition & 0 deletions src/common/util/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { groupBy } from './groupBy';
export { removeUndefinedEntries } from './removeUndefinedEntries';
2 changes: 2 additions & 0 deletions src/common/util/removeUndefinedEntries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const removeUndefinedEntries = (obj: object) => Object.fromEntries(
Object.entries(obj).filter(([_, v]) => v));
7 changes: 4 additions & 3 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ type AWSConfigItem = {
title: string,
aws_account_id: string,
role_name: string,

selected?: boolean,
} & AWSConfigOptionalData

Expand All @@ -28,15 +27,17 @@ type AWSConfigItemState = AWSConfigItem & { selected: boolean }
type AWSStoredConfigItem = {
aws_account_id?: string,
role_name?: string,
role_arn?: string
role_arn?: string,
source_profile?: string,
target_role_name?: string,
} & AWSConfigOptionalData

type StoredConfig = Record<string, AWSStoredConfigItem>

type SwitchRoleFormFromExtension = { _fromAWSRoleSwitchExtension?: 'true' }
type SwitchRoleForm = {
account: string,
roleName: string,
roleName?: string,
region?: string,
color?: string,
displayName?: string,
Expand Down

0 comments on commit 06248fc

Please sign in to comment.