Skip to content

Commit

Permalink
last review comments handling
Browse files Browse the repository at this point in the history
  • Loading branch information
vgkowski committed Feb 27, 2024
1 parent c7836b8 commit 53fc97c
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 182 deletions.
222 changes: 114 additions & 108 deletions framework/API.md

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions framework/src/consumption/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ An Amazon OpenSearch Domain with SAML integration and access to OpenSearch REST

## Overview

The `OpensearchCluster` construct implements an OpenSeach Domain following best practises including:
The `OpenSearchCluster` construct implements an OpenSeach Domain following best practises including:
* private deployment in VPC
* SAML-authentication plugin to access OpenSearch Dashboards via a SAML2.0-compatible IdP
* access to the OpenSeach REST API to interact with OpenSearch objects like Roles, Indexes, Mappings...
Expand All @@ -170,11 +170,11 @@ Main steps are:
6. Use the content of the metadata file in the `samlMetadataContent` parameter of the construct
7. Provision the construct
8. Update the IAM Identity Center application attribute mappings by adding
1. `${user:email}` as the `Subject` with `emailAddress` format. `Subject` is the default subject key used in Opensearch construct, modify the mapping according to your configuration.
2. `${user:groups}`as the `Role` with `unspecified` format. `Role` is the default role key used in Opensearch construct, modify the mapping according to your configuration.
1. `${user:email}` as the `Subject` with `emailAddress` format. `Subject` is the default subject key used in OpenSearch construct, modify the mapping according to your configuration.
2. `${user:groups}`as the `Role` with `unspecified` format. `Role` is the default role key used in OpenSearch construct, modify the mapping according to your configuration.
9. Update the IAM Identity Center application configuration
1. Set the `Application ACS URL` to the `Opensearch SSO URL (IdP initiated)` from the Opensearch Domain security configuration
2. Set the `Application SAML audience` to the `Service provider entity ID` from the Opensearch Domain security configuration
1. Set the `Application ACS URL` to the `OpenSearch SSO URL (IdP initiated)` from the OpenSearch Domain security configuration
2. Set the `Application SAML audience` to the `Service provider entity ID` from the OpenSearch Domain security configuration

## Usage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Construct } from 'constructs';
import * as dsf from '../../index';


class ExampleDefaultOpensearchStack extends cdk.Stack {
class ExampleDefaultOpenSearchStack extends cdk.Stack {

constructor(scope: Construct, id: string , props:cdk.StackProps) {

Expand All @@ -17,7 +17,7 @@ class ExampleDefaultOpensearchStack extends cdk.Stack {
selfServicePortal:false
}
})
const osCluster = new dsf.consumption.OpensearchCluster(this, 'MyOpensearchCluster',{
const osCluster = new dsf.consumption.OpenSearchCluster(this, 'MyOpenSearchCluster',{
domainName:"mycluster",
samlEntityId:'<IdpIdentityId>',
samlMetadataContent:'<IdpOpenSearchApplicationMetadataXml>',
Expand All @@ -35,4 +35,4 @@ class ExampleDefaultOpensearchStack extends cdk.Stack {


const app = new cdk.App();
new ExampleDefaultOpensearchStack(app, 'ExampleDefaultDataLakeStorage', { env: {region:'us-east-1'} });
new ExampleDefaultOpenSearchStack(app, 'ExampleDefaultDataLakeStorage', { env: {region:'us-east-1'} });
6 changes: 3 additions & 3 deletions framework/src/consumption/examples/opensearch-saml.lit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { Construct } from 'constructs';
import * as dsf from '../../index';


class ExampleDefaultOpensearchStack extends cdk.Stack {
class ExampleDefaultOpenSearchStack extends cdk.Stack {

constructor(scope: Construct, id: string , props:cdk.StackProps) {

super(scope, id, props);
/// !show
const osCluster = new dsf.consumption.OpensearchCluster(this, 'MyOpensearchCluster',{
const osCluster = new dsf.consumption.OpenSearchCluster(this, 'MyOpenSearchCluster',{
domainName:"mycluster",
samlEntityId:'<IdpIdentityId>',
samlMetadataContent:'<IdpMetadataXml>',
Expand All @@ -27,4 +27,4 @@ class ExampleDefaultOpensearchStack extends cdk.Stack {


const app = new cdk.App();
new ExampleDefaultOpensearchStack(app, 'ExampleDefaultDataLakeStorage', { env: {region:'us-east-1'} });
new ExampleDefaultOpenSearchStack(app, 'ExampleDefaultDataLakeStorage', { env: {region:'us-east-1'} });
14 changes: 7 additions & 7 deletions framework/src/consumption/lib/opensearch/opensearch-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { EngineVersion } from 'aws-cdk-lib/aws-opensearchservice';


/**
* Simplified configuration for the Opensearch Cluster.
* Simplified configuration for the OpenSearch Cluster.
*/
export interface OpensearchProps {
export interface OpenSearchClusterProps {
/**
* The OpenSearch Domain name
*/
Expand All @@ -22,7 +22,7 @@ export interface OpensearchProps {
readonly version?: EngineVersion;
/**
* The EC2 Instance Type used for OpenSearch data nodes
* @default - @see OpensearchNodes.DATA_NODE_INSTANCE_DEFAULT
* @default - @see OpenSearchNodes.DATA_NODE_INSTANCE_DEFAULT
*/
readonly dataNodeInstanceType?: string;
/**
Expand All @@ -32,7 +32,7 @@ export interface OpensearchProps {
readonly dataNodeInstanceCount?: number;
/**
* The EC2 Instance Type for OpenSearch master nodes
* @default - @see OpensearchNodes.MASTER_NODE_INSTANCE_DEFAULT
* @default - @see OpenSearchNodes.MASTER_NODE_INSTANCE_DEFAULT
*/
readonly masterNodeInstanceType?: string;
/**
Expand All @@ -42,7 +42,7 @@ export interface OpensearchProps {
readonly masterNodeInstanceCount?: number;
/**
* The type of nodes for Ultra Warn nodes
* @default - @see OpensearchNodes.WARM_NODE_INSTANCE_DEFAULT
* @default - @see OpenSearchNodes.WARM_NODE_INSTANCE_DEFAULT
*/
readonly warmInstanceType?:number;
/**
Expand Down Expand Up @@ -132,9 +132,9 @@ export interface OpensearchProps {
}

/**
* Default Node Instances for Opensearch cluster
* Default Node Instances for OpenSearch cluster
*/
export enum OpensearchNodes {
export enum OpenSearchNodes {
DATA_NODE_INSTANCE_DEFAULT = 'r6g.xlarge.search',
MASTER_NODE_INSTANCE_DEFAULT = 'm6g.large.search',
WARM_NODE_INSTANCE_DEFAULT = 'ultrawarm1.medium.search',
Expand Down
51 changes: 24 additions & 27 deletions framework/src/consumption/lib/opensearch/opensearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IKey, Key } from 'aws-cdk-lib/aws-kms';
import { ILogGroup, LogGroup } from 'aws-cdk-lib/aws-logs';
import { Domain, DomainProps, IDomain, SAMLOptionsProperty } from 'aws-cdk-lib/aws-opensearchservice';
import { Construct } from 'constructs';
import { OpensearchProps, OpensearchNodes, OPENSEARCH_DEFAULT_VERSION } from './opensearch-props';
import { OpenSearchClusterProps, OpenSearchNodes, OPENSEARCH_DEFAULT_VERSION } from './opensearch-props';
import { Context, CreateServiceLinkedRole, DataVpc, TrackedConstruct, TrackedConstructProps } from '../../../utils';
import { DsfProvider } from '../../../utils/lib/dsf-provider';
import { ServiceLinkedRoleService } from '../../../utils/lib/service-linked-role-service';
Expand All @@ -21,32 +21,30 @@ import { ServiceLinkedRoleService } from '../../../utils/lib/service-linked-role
* ClientVPNEndpoint will be provisioned automatically for secure access to OpenSearch Dashboards.
*
* @example
* const osCluster = new dsf.consumption.OpensearchCluster(this, 'MyOpensearchCluster',{
* const osCluster = new dsf.consumption.OpenSearchCluster(this, 'MyOpenSearchCluster',{
* domainName:"mycluster2",
* samlEntityId:'<IdpIdentityId>',
* samlMetadataContent:'<IdpMetadataXml>',
* samlMasterBackendRole:'<IAMIdentityCenterAdminGroupId>',
* deployInVpc:true,
* removalPolicy:cdk.RemovalPolicy.DESTROY
* } as dsf.consumption.OpensearchProps );
* } as dsf.consumption.OpenSearchProps );
*
* osCluster.addRoleMapping('dashboards_user','<IAMIdentityCenterDashboardUsersGroupId>');
* osCluster.addRoleMapping('readall','<IAMIdentityCenterDashboardUsersGroupId>');
*
*
*/

export class OpensearchCluster extends TrackedConstruct {
export class OpenSearchCluster extends TrackedConstruct {

/**
* @public
* OpenSearchCluster domain
*/
public readonly domain: IDomain;

/**
* @public
* Cloudwatch log group to store OpenSearch cluster logs
* CloudWatch Logs Log Group to store OpenSearch cluster logs
*/
public readonly logGroup: ILogGroup;

Expand All @@ -56,7 +54,6 @@ export class OpensearchCluster extends TrackedConstruct {
public readonly encryptionKey: IKey;

/**
* @public
* VPC OpenSearch cluster is provisioned in.
*/
public readonly vpc:IVpc | undefined;
Expand Down Expand Up @@ -86,12 +83,12 @@ export class OpensearchCluster extends TrackedConstruct {
* Constructs a new instance of the OpenSearchCluster class
* @param {Construct} scope the Scope of the AWS CDK Construct
* @param {string} id the ID of the AWS CDK Construct
* @param {OpensearchClusterProps} props the OpenSearchCluster [properties]{@link OpensearchClusterProps}
* @param {OpenSearchClusterProps} props the OpenSearchCluster [properties]{@link OpenSearchClusterProps}
*/

constructor(scope: Construct, id: string, props: OpensearchProps) {
constructor(scope: Construct, id: string, props: OpenSearchClusterProps) {
const trackedConstructProps: TrackedConstructProps = {
trackingTag: id,
trackingTag: OpenSearchCluster.name,
};

super(scope, id, trackedConstructProps);
Expand Down Expand Up @@ -210,11 +207,11 @@ export class OpensearchCluster extends TrackedConstruct {
vpcSubnets: props.vpcSubnets ? vpcSubnetsSelection?.subnets : undefined,
capacity: {
masterNodes: props.masterNodeInstanceCount ?? 3,
masterNodeInstanceType: props.masterNodeInstanceType ?? OpensearchNodes.MASTER_NODE_INSTANCE_DEFAULT,
masterNodeInstanceType: props.masterNodeInstanceType ?? OpenSearchNodes.MASTER_NODE_INSTANCE_DEFAULT,
dataNodes: props.dataNodeInstanceCount ?? (vpcSubnetsSelection?.subnets?.length || defaultAzNumber),
dataNodeInstanceType: props.dataNodeInstanceType ?? OpensearchNodes.DATA_NODE_INSTANCE_DEFAULT,
dataNodeInstanceType: props.dataNodeInstanceType ?? OpenSearchNodes.DATA_NODE_INSTANCE_DEFAULT,
warmNodes: props.warmInstanceCount ?? 0,
warmInstanceType: props.warmInstanceType ?? OpensearchNodes.WARM_NODE_INSTANCE_DEFAULT,
warmInstanceType: props.warmInstanceType ?? OpenSearchNodes.WARM_NODE_INSTANCE_DEFAULT,
multiAzWithStandbyEnabled: props.multiAzWithStandbyEnabled ?? false,
},
encryptionAtRest: {
Expand Down Expand Up @@ -295,20 +292,19 @@ export class OpensearchCluster extends TrackedConstruct {

const samlAdminGroupId = props.samlMasterBackendRole;


//todo refactor to use new custom resource framework
this.addRoleMapping('all_access', samlAdminGroupId);
this.addRoleMapping('security_manager', samlAdminGroupId);
this.addRoleMapping('AllAccessOsRole', 'all_access', samlAdminGroupId);
this.addRoleMapping('SecurityManagerOsRole', 'security_manager', samlAdminGroupId);
}

/**
* Calls Opensearch API using custom resource.
* @param apiPath Opensearch API path
* @param body Opensearch API request body
* Calls OpenSearch API using custom resource.
* @param id The CDK resource ID
* @param apiPath OpenSearch API path
* @param body OpenSearch API request body
*/

private apiCustomResource(apiPath: string, body: any) {
const cr = new CustomResource(this, 'ApiCR-'+ Math.random().toFixed(8), {
public callOpenSearchApi(id: string, apiPath: string, body: any) {
const cr = new CustomResource(this, id, {
serviceToken: this.apiProvider.serviceToken,
resourceType: 'Custom::OpenSearchAPI',
properties: {
Expand All @@ -324,12 +320,13 @@ export class OpensearchCluster extends TrackedConstruct {
/**
* @public
* Add a new role mapping to the cluster.
* This method is used to add a role mapping to the Amazon Opensearch cluster
* @param name Opensearch role name @see https://opensearch.org/docs/2.9/security/access-control/users-roles/#predefined-roles
* This method is used to add a role mapping to the Amazon OpenSearch cluster
* @param id The CDK resource ID
* @param name OpenSearch role name @see https://opensearch.org/docs/2.9/security/access-control/users-roles/#predefined-roles
* @param role IAM Identity center SAML group Id
*/
public addRoleMapping(name: string, role: string) {
this.apiCustomResource('_plugins/_security/api/rolesmapping/' + name, {
public addRoleMapping(id: string, name: string, role: string) {
this.callOpenSearchApi(id, '_plugins/_security/api/rolesmapping/' + name, {
backend_roles: [role],
});
}
Expand Down
4 changes: 2 additions & 2 deletions framework/test/e2e/opensearch.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { App, RemovalPolicy, CfnOutput } from 'aws-cdk-lib';

import { TestStack } from './test-stack';
import { OpensearchCluster } from '../../src/consumption/index';
import { OpenSearchCluster } from '../../src/consumption/index';


jest.setTimeout(10000000);
Expand All @@ -23,7 +23,7 @@ const { stack } = testStack;
stack.node.setContext('@data-solutions-framework-on-aws/removeDataOnDestroy', true);

// Instantiate AccessLogsBucket Construct with default
const domain = new OpensearchCluster(stack, 'OpensearchVpc', {
const domain = new OpenSearchCluster(stack, 'OpenSearchVpc', {
domainName: 'e2e-tests-cluster',
samlEntityId: 'https://portal.sso.eu-west-1.amazonaws.com/saml/assertion/MTQ1Mzg4NjI1ODYwX2lucy02MmQ3Y2VlYWM0YWNkNjA1',
samlMetadataContent: `<?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://portal.sso.eu-west-1.amazonaws.com/saml/assertion/MTQ1Mzg4NjI1ODYwX2lucy02MmQ3Y2VlYWM0YWNkNjA1">
Expand Down
12 changes: 6 additions & 6 deletions framework/test/unit/consumption/opensearch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Stack, App, RemovalPolicy } from 'aws-cdk-lib';
import { Match, Template } from 'aws-cdk-lib/assertions';
import { SubnetType } from 'aws-cdk-lib/aws-ec2';
import { DataVpc } from '../../../lib/utils';
import { OpensearchCluster } from '../../../src/consumption';
import { OpenSearchCluster } from '../../../src/consumption';


describe('default configuration', () => {
Expand All @@ -23,7 +23,7 @@ describe('default configuration', () => {
stack.node.setContext('@data-solutions-framework-on-aws/removeDataOnDestroy', true);

// Instantiate AccessLogsBucket Construct with default
new OpensearchCluster(stack, 'OpensearchVpc', {
new OpenSearchCluster(stack, 'OpenSearchVpc', {
domainName: 'mycluster2',
samlEntityId: '<idpTest>',
samlMetadataContent: 'xmlContent',
Expand Down Expand Up @@ -103,7 +103,7 @@ describe('non vpc config', () => {
stack.node.setContext('@data-solutions-framework-on-aws/removeDataOnDestroy', true);

// Instantiate AccessLogsBucket Construct with default
new OpensearchCluster(stack, 'OpensearchPublic', {
new OpenSearchCluster(stack, 'OpenSearchPublic', {
domainName: 'mycluster2-public',
samlEntityId: '<idpTest>',
samlMetadataContent: 'xmlContent',
Expand Down Expand Up @@ -173,7 +173,7 @@ describe('custom vpc configuration', () => {
// Set context value for global data removal policy
stack.node.setContext('@data-solutions-framework-on-aws/removeDataOnDestroy', true);

const vpc = new DataVpc(stack, 'OpensearchDataVpc', {
const vpc = new DataVpc(stack, 'OpenSearchDataVpc', {
vpcCidr: '10.0.0.0/16',
clientVpnEndpointProps: {
serverCertificateArn: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
Expand All @@ -182,7 +182,7 @@ describe('custom vpc configuration', () => {
});
const vpcSubnetsSelection = vpc.vpc.selectSubnets({ onePerAz: true, subnetType: SubnetType.PRIVATE_WITH_EGRESS });

new OpensearchCluster(stack, 'OpensearchDomainVpc', {
new OpenSearchCluster(stack, 'OpenSearchDomainVpc', {
domainName: 'mycluster2',
samlEntityId: '<idpTest>',
samlMetadataContent: 'xmlContent',
Expand All @@ -202,7 +202,7 @@ describe('custom vpc configuration', () => {
SecurityGroupIds: [
{
'Fn::GetAtt': [
'OpensearchDomainVpcSecurityGroup019B7A8A',
Match.stringLikeRegexp('OpenSearchDomainVpcSecurityGroup.*'),
'GroupId',
],
},
Expand Down
Loading

0 comments on commit 53fc97c

Please sign in to comment.