Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(servicecatalogappregistry): add sharing of applications and attribute groups #20850

Merged
merged 29 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ec8709a
example RAM share start for appreg
May 6, 2022
4e5fcf4
update spacing
May 6, 2022
b2ad0eb
Add unit tests, fix typos
Jun 18, 2022
17438c5
Documentation additions for sharing
Jun 22, 2022
ea7b52d
Added integ tests for sharing
Jun 23, 2022
a986534
Fix newlines and documentation
Jun 23, 2022
c785e58
Revised hashing strategy for resource shares, updated tests, revised …
Jun 28, 2022
9300e03
Merge branch 'main' into appreg_ram_share
mackalex Jun 28, 2022
14c7f81
AWS RAM constructs to stable in order to fix dependency issue
Jun 29, 2022
3120c49
Upgrade constructs dependency
Jun 29, 2022
73d0041
Merge branch 'main' into appreg_ram_share
mackalex Jun 29, 2022
ecb00ec
Revised docstring, using Names methods for naming
Jul 1, 2022
904f751
Merge branch 'main' into appreg_ram_share
mackalex Jul 15, 2022
b63da2e
Merge branch 'main' into appreg_ram_share
mackalex Jul 19, 2022
8f951ee
Merge branch 'main' into appreg_ram_share
mackalex Jul 28, 2022
1d62528
Add share permission option and additional tests
Jul 23, 2022
c4358c5
Remove option to allow external principals, should always be false fo…
Jul 23, 2022
db7e438
Rename organizations to organizationArns, update README
Jul 27, 2022
e3e4599
Change shareResource to shareApplication and shareAttributeGroup resp…
Jul 28, 2022
5ce92cd
Allow for multiple shares per Application or AttributeGroup
Jul 30, 2022
6a32574
Revert RAM constructs to experimental per (#21208)
Jul 30, 2022
bebfc13
Added extra blank line above each @param
Jul 30, 2022
6d0fcb2
Update packages/@aws-cdk/aws-servicecatalogappregistry/lib/attribute-…
mackalex Aug 3, 2022
2c8034c
Update packages/@aws-cdk/aws-servicecatalogappregistry/lib/applicatio…
mackalex Aug 3, 2022
80b7b5c
Merge branch 'main' into appreg_ram_share
mackalex Aug 3, 2022
a422beb
Merge branch 'main' into appreg_ram_share
mackalex Aug 5, 2022
34bbfdb
Update table of contents in README
Aug 6, 2022
0570d3c
Merge branch 'main' into appreg_ram_share
mackalex Aug 10, 2022
19a87dc
Merge branch 'main' into appreg_ram_share
mergify[bot] Aug 22, 2022
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
56 changes: 56 additions & 0 deletions packages/@aws-cdk/aws-servicecatalogappregistry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,59 @@ const myStack = new Stack(app, 'MyStack');
declare const application: appreg.Application;
application.associateStack(myStack);
```

## Sharing

You can share your AppRegistry applications and attribute groups with AWS Organizations, Organizational Units (OUs), AWS accounts within an organization, as well as IAM roles and users. AppRegistry requires that AWS Organizations is enabled in an account before deploying a share of an application or attribute group.

### Sharing an application

```ts
import * as iam from '@aws-cdk/aws-iam';
declare const application: appreg.Application;
declare const myRole: iam.IRole;
declare const myUser: iam.IUser;
application.shareApplication({
accounts: ['123456789012'],
organizationArns: ['arn:aws:organizations::123456789012:organization/o-my-org-id'],
roles: [myRole],
users: [myUser],
});
```

E.g., sharing an application with multiple accounts and allowing the accounts to associate resources to the application.

```ts
import * as iam from '@aws-cdk/aws-iam';
declare const application: appreg.Application;
application.shareApplication({
accounts: ['123456789012', '234567890123'],
sharePermission: appreg.SharePermission.ALLOW_ACCESS,
});
```

### Sharing an attribute group
mackalex marked this conversation as resolved.
Show resolved Hide resolved

```ts
import * as iam from '@aws-cdk/aws-iam';
mackalex marked this conversation as resolved.
Show resolved Hide resolved
declare const attributeGroup: appreg.AttributeGroup;
declare const myRole: iam.IRole;
declare const myUser: iam.IUser;
attributeGroup.shareAttributeGroup({
accounts: ['123456789012'],
organizationArns: ['arn:aws:organizations::123456789012:organization/o-my-org-id'],
roles: [myRole],
users: [myUser],
});
```

E.g., sharing an application with multiple accounts and allowing the accounts to associate applications to the attribute group.

```ts
import * as iam from '@aws-cdk/aws-iam';
declare const attributeGroup: appreg.AttributeGroup;
attributeGroup.shareAttributeGroup({
accounts: ['123456789012', '234567890123'],
sharePermission: appreg.SharePermission.ALLOW_ACCESS,
});
```
60 changes: 50 additions & 10 deletions packages/@aws-cdk/aws-servicecatalogappregistry/lib/application.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import * as crypto from 'crypto';
import { CfnResourceShare } from '@aws-cdk/aws-ram';
import * as cdk from '@aws-cdk/core';
import { Names } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { IAttributeGroup } from './attribute-group';
import { getPrincipalsforSharing, hashValues, ShareOptions, SharePermission } from './common';
import { InputValidator } from './private/validation';
import { CfnApplication, CfnAttributeGroupAssociation, CfnResourceAssociation } from './servicecatalogappregistry.generated';

const APPLICATION_READ_ONLY_RAM_PERMISSION_ARN = 'arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryApplicationReadOnly';
const APPLICATION_ALLOW_ACCESS_RAM_PERMISSION_ARN = 'arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryApplicationAllowAssociation';

/**
* A Service Catalog AppRegistry Application.
*/
Expand All @@ -23,15 +28,24 @@ export interface IApplication extends cdk.IResource {

/**
* Associate thisapplication with an attribute group.
*
* @param attributeGroup AppRegistry attribute group
*/
associateAttributeGroup(attributeGroup: IAttributeGroup): void;

/**
* Associate this application with a CloudFormation stack.
*
* @param stack a CFN stack
*/
associateStack(stack: cdk.Stack): void;

/**
* Share this application with other IAM entities, accounts, or OUs.
*
* @param shareOptions The options for the share.
*/
shareApplication(shareOptions: ShareOptions): void;
}

/**
Expand Down Expand Up @@ -88,10 +102,43 @@ abstract class ApplicationBase extends cdk.Resource implements IApplication {
}
}

/**
* Share an application with accounts, organizations and OUs, and IAM roles and users.
* The application will become available to end users within those principals.
*
* @param shareOptions The options for the share.
mackalex marked this conversation as resolved.
Show resolved Hide resolved
*/
public shareApplication(shareOptions: ShareOptions): void {
const principals = getPrincipalsforSharing(shareOptions);
const shareName = `RAMShare${hashValues(Names.nodeUniqueId(this.node), this.node.children.length.toString())}`;
new CfnResourceShare(this, shareName, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider since we have hash for resource name we can either throw an error if someone tries to add same share twice or ignore it, like we do in other SC resources.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will think about this one a bit more. I think some logic to ignore it may be sufficient.

name: shareName,
allowExternalPrincipals: false,
principals: principals,
resourceArns: [this.applicationArn],
permissionArns: [this.getApplicationSharePermissionARN(shareOptions)],
});
}

/**
* Create a unique id
*/
protected abstract generateUniqueHash(resourceAddress: string): string;

/**
* Get the correct permission ARN based on the SharePermission
*/
private getApplicationSharePermissionARN(shareOptions: ShareOptions): string {
switch (shareOptions.sharePermission) {
case SharePermission.ALLOW_ACCESS:
return APPLICATION_ALLOW_ACCESS_RAM_PERMISSION_ARN;
case SharePermission.READ_ONLY:
return APPLICATION_READ_ONLY_RAM_PERMISSION_ARN;

default:
return shareOptions.sharePermission ?? APPLICATION_READ_ONLY_RAM_PERMISSION_ARN;
}
}
}

/**
Expand All @@ -102,7 +149,9 @@ export class Application extends ApplicationBase {
* Imports an Application construct that represents an external application.
*
* @param scope The parent creating construct (usually `this`).
*
* @param id The construct's name.
*
* @param applicationArn the Amazon Resource Name of the existing AppRegistry Application
mackalex marked this conversation as resolved.
Show resolved Hide resolved
*/
public static fromApplicationArn(scope: Construct, id: string, applicationArn: string): IApplication {
Expand Down Expand Up @@ -156,12 +205,3 @@ export class Application extends ApplicationBase {
InputValidator.validateLength(this.node.path, 'application description', 0, 1024, props.description);
}
}

/**
* Generates a unique hash identfifer using SHA256 encryption algorithm
*/
function hashValues(...values: string[]): string {
const sha256 = crypto.createHash('sha256');
values.forEach(val => sha256.update(val));
return sha256.digest('hex').slice(0, 12);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { CfnResourceShare } from '@aws-cdk/aws-ram';
import * as cdk from '@aws-cdk/core';
import { getPrincipalsforSharing, hashValues, ShareOptions, SharePermission } from './common';
import { Construct } from 'constructs';
import { InputValidator } from './private/validation';
import { CfnAttributeGroup } from './servicecatalogappregistry.generated';
import { Names } from '@aws-cdk/core';

const ATTRIBUTE_GROUP_READ_ONLY_RAM_PERMISSION_ARN = 'arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryAttributeGroupReadOnly';
const ATTRIBUTE_GROUP_ALLOW_ACCESS_RAM_PERMISSION_ARN = 'arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryAttributeGroupAllowAssociation';

/**
* A Service Catalog AppRegistry Attribute Group.
Expand All @@ -18,6 +24,13 @@ export interface IAttributeGroup extends cdk.IResource {
* @attribute
*/
readonly attributeGroupId: string;

/**
* Share the attribute group resource with other IAM entities, accounts, or OUs.
*
* @param shareOptions The options for the share.
*/
shareAttributeGroup(shareOptions: ShareOptions): void;
}

/**
Expand Down Expand Up @@ -45,6 +58,33 @@ export interface AttributeGroupProps {
abstract class AttributeGroupBase extends cdk.Resource implements IAttributeGroup {
public abstract readonly attributeGroupArn: string;
public abstract readonly attributeGroupId: string;

public shareAttributeGroup(shareOptions: ShareOptions): void {
const principals = getPrincipalsforSharing(shareOptions);
const shareName = `RAMShare${hashValues(Names.nodeUniqueId(this.node), this.node.children.length.toString())}`;
new CfnResourceShare(this, shareName, {
name: shareName,
allowExternalPrincipals: false,
principals: principals,
resourceArns: [this.attributeGroupArn],
permissionArns: [this.getAttributeGroupSharePermissionARN(shareOptions)],
});
}

/**
* Get the correct permission ARN based on the SharePermission
*/
protected getAttributeGroupSharePermissionARN(shareOptions: ShareOptions): string {
switch (shareOptions.sharePermission) {
case SharePermission.ALLOW_ACCESS:
return ATTRIBUTE_GROUP_ALLOW_ACCESS_RAM_PERMISSION_ARN;
case SharePermission.READ_ONLY:
return ATTRIBUTE_GROUP_READ_ONLY_RAM_PERMISSION_ARN;

default:
return shareOptions.sharePermission ?? ATTRIBUTE_GROUP_READ_ONLY_RAM_PERMISSION_ARN;
}
}
}

/**
Expand All @@ -55,7 +95,9 @@ export class AttributeGroup extends AttributeGroupBase implements IAttributeGrou
* Imports an attribute group construct that represents an external attribute group.
*
* @param scope The parent creating construct (usually `this`).
*
* @param id The construct's name.
*
* @param attributeGroupArn the Amazon Resource Name of the existing AppRegistry attribute group
mackalex marked this conversation as resolved.
Show resolved Hide resolved
*/
public static fromAttributeGroupArn(scope: Construct, id: string, attributeGroupArn: string): IAttributeGroup {
Expand Down
87 changes: 87 additions & 0 deletions packages/@aws-cdk/aws-servicecatalogappregistry/lib/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as crypto from 'crypto';
import * as iam from '@aws-cdk/aws-iam';

/**
* Supported permissions for sharing applications or attribute groups with principals using AWS RAM.
*/
export enum SharePermission {
/**
* Allows principals in the share to only view the application or attribute group.
*/
READ_ONLY,

/**
* Allows principals in the share to associate resources and attribute groups with applications.
*/
ALLOW_ACCESS,
};

/**
* The options that are passed into a share of an Application or Attribute Group.
*/
export interface ShareOptions {
/**
* A list of AWS accounts that the application will be shared with.
*
* @default - No accounts specified for share
*/
readonly accounts?: string[];

/**
* A list of AWS Organization or Organizational Units (OUs) ARNs that the application will be shared with.
*
* @default - No AWS Organizations or OUs specified for share
*/
readonly organizationArns?: string[];

/**
* A list of AWS IAM roles that the application will be shared with.
*
* @default - No IAM roles specified for share
*/
readonly roles?: iam.IRole[];

/**
* An option to manage access to the application or attribute group.
*
* @default - Principals will be assigned read only permissions on the application or attribute group.
*/
readonly sharePermission?: SharePermission | string;

/**
* A list of AWS IAM users that the application will be shared with.
*
* @default - No IAM Users specified for share
*/
readonly users?: iam.IUser[];
}

/**
* Generates a unique hash identfifer using SHA256 encryption algorithm.
*/
export function hashValues(...values: string[]): string {
const sha256 = crypto.createHash('sha256');
values.forEach(val => sha256.update(val));
return sha256.digest('hex').slice(0, 12);
}

/**
* Reformats share targets into a collapsed list necessary for handler.
*
* @param options The share target options
* @returns flat list of target ARNs
*/
export function getPrincipalsforSharing(options: ShareOptions): string[] {
const principals = [
...options.accounts ?? [],
...options.organizationArns ?? [],
...options.users ? options.users.map(user => user.userArn) : [],
...options.roles ? options.roles.map(role => role.roleArn) : [],
];

if (principals.length == 0) {
throw new Error('An entity must be provided for the share');
}

return principals;
mackalex marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './application';
export * from './attribute-group';
export * from './common';

// AWS::ServiceCatalogAppRegistry CloudFormation Resources:
export * from './servicecatalogappregistry.generated';
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-servicecatalogappregistry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,14 @@
},
"dependencies": {
"@aws-cdk/core": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-ram": "0.0.0",
"constructs": "^10.0.0"
},
"peerDependencies": {
"@aws-cdk/core": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-ram": "0.0.0",
"constructs": "^10.0.0"
},
"engines": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"17.0.0"}
{"version":"20.0.0"}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"version": "17.0.0",
"version": "20.0.0",
"files": {
"d561cf6d9aa2d98689712d70accb1c3f56f2a54d6cbb1268d35bd72e05675791": {
"d03aa6239eb3b20f4b72fb3dd44a4082d06d7a5451d0ac3855bd1aa78aecfbe9": {
"source": {
"path": "integ-servicecatalogappregistry-application.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "d561cf6d9aa2d98689712d70accb1c3f56f2a54d6cbb1268d35bd72e05675791.json",
"objectKey": "d03aa6239eb3b20f4b72fb3dd44a4082d06d7a5451d0ac3855bd1aa78aecfbe9.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Loading