Skip to content

Commit

Permalink
feat(servicecatalogappregistry): add sharing of applications and attr…
Browse files Browse the repository at this point in the history
…ibute groups (#20850)

This PR adds sharing capability to the Application and Attribute Group constructs for Service Catalog AppRegistry. Users who have enabled AWS Organizations in their AWS account can now share their AppRegistry Application and Attribute Groups with accounts in their organization, organizational units (OUs), IAM roles, and IAM users. This provides CDK parity with the support of cross-account sharing of Applications and Attribute Groups which was [released as an AppRegistry feature](https://aws.amazon.com/about-aws/whats-new/2022/06/aws-service-catalogs-application-registry-cross-account-applications/).

----

### All Submissions:

* [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*

---

Co-authored by: Aidan Crank
  • Loading branch information
mackalex authored Aug 22, 2022
1 parent cf5d115 commit cf3bb6e
Show file tree
Hide file tree
Showing 20 changed files with 901 additions and 24 deletions.
59 changes: 59 additions & 0 deletions packages/@aws-cdk/aws-servicecatalogappregistry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ enables organizations to create and manage repositores of applications and assoc
- [Associations](#associations)
- [Associating application with an attribute group](#attribute-group-association)
- [Associating application with a stack](#resource-association)
- [Sharing](#sharing)
- [Sharing an application](#sharing-an-application)
- [Sharing an attribute group](#sharing-an-attribute-group)

The `@aws-cdk/aws-servicecatalogappregistry` package contains resources that enable users to automate governance and management of their AWS resources at scale.

Expand Down Expand Up @@ -124,3 +127,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

```ts
import * as iam from '@aws-cdk/aws-iam';
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,
});
```
58 changes: 48 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.
*/
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, {
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 Down Expand Up @@ -156,12 +203,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 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;
}
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

0 comments on commit cf3bb6e

Please sign in to comment.