constructor()

in lib/search-stack.ts [16:183]


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

    super(scope, id, props);

    const applicationPrefix = new CfnParameter(this, 'applicationPrefix', {
      default: this.node.tryGetContext('applicationPrefix'),
      description: "Prefix for the Amazon Cognito domain and the Amazon Elasticsearch Service domain",
      type: "String",
      allowedPattern: "^[a-z0-9]*$",
      minLength: 3,
      maxLength: 20
    }).valueAsString;

    const userPool = new CfnUserPool(this, "userPool", {
      adminCreateUserConfig: {
        allowAdminCreateUserOnly: true
      },
      usernameAttributes: ["email"],
      autoVerifiedAttributes: ["email"],
    });

    // get a unique suffix from the last element of the stackId, e.g. 06b321d6b6e2
    const suffix = Fn.select(4, Fn.split("-", Fn.select(2, Fn.split("/", this.stackId))));

    new CfnUserPoolDomain(this, "cognitoDomain", {
      domain: applicationPrefix + "-" + suffix,
      userPoolId: userPool.ref
    });

    const idPool = new CfnIdentityPool(this, "identityPool", {
      allowUnauthenticatedIdentities: false,
      cognitoIdentityProviders: []
    });

    const authRole = new Role(this, "authRole", {
      assumedBy: new FederatedPrincipal('cognito-identity.amazonaws.com', {
        "StringEquals": { "cognito-identity.amazonaws.com:aud": idPool.ref },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "authenticated"
        }
      }, "sts:AssumeRoleWithWebIdentity")
    });

    const esRole = new Role(this, "esRole", {
      assumedBy: new ServicePrincipal('es.amazonaws.com'),
      managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName("AmazonESCognitoAccess")]
    });

    const esDomain = new CfnDomain(this, "searchDomain", {
      elasticsearchClusterConfig: { instanceType: "t3.small.elasticsearch" },
      ebsOptions: { volumeSize: 10, ebsEnabled: true },
      elasticsearchVersion: "7.10",
      domainName: applicationPrefix,
      nodeToNodeEncryptionOptions: { enabled: true },
      encryptionAtRestOptions: { enabled: true },
      domainEndpointOptions: {
        enforceHttps: true
      },
      cognitoOptions: {
        enabled: true,
        identityPoolId: idPool.ref,
        roleArn: esRole.roleArn,
        userPoolId: userPool.ref
      },

      // Trust the cognito authenticated Role
      accessPolicies: {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Principal": {
              "AWS": authRole.roleArn
            },
            "Action": [
              "es:ESHttpGet",
              "es:ESHttpPut",
              "es:ESHttpPost",
              "es:ESHttpDelete"
            ],
            "Resource": "arn:aws:es:" + this.region + ":" + this.account + ":domain/" + applicationPrefix + "/*"
          }
        ]
      }
    });

    new CfnIdentityPoolRoleAttachment(this, 'userPoolRoleAttachment', {
      identityPoolId: idPool.ref,
      roles: {
        'authenticated': authRole.roleArn
      }
    });

    const ElasticsearchHttpPostPolicyStatement = new PolicyStatement({
      effect: Effect.ALLOW,
      resources: [`arn:${this.partition}:es:${this.region}:${this.account}:domain/${esDomain.domainName}/*`],
      actions: [
        "es:ESHttpPost",
        "es:ESHttpPut"
      ],
    });

    /**
     * Function implementing the requests to Amazon Elasticsearch Service
     * for the custom resource.
     */
    const esRequestsFn = new Function(this, 'esRequestsFn', {
      runtime: Runtime.NODEJS_14_X,
      handler: 'es-requests.handler',
      code: Code.fromAsset(path.join(__dirname, '..', 'functions/es-requests')),
      timeout: Duration.seconds(30),
      environment: {
        "DOMAIN": esDomain.attrDomainEndpoint,
        "REGION": this.region
      }
    });
    esRequestsFn.addToRolePolicy(ElasticsearchHttpPostPolicyStatement);

    const streamLogsFn = new Function(this, 'streamLogs', {
      runtime: Runtime.NODEJS_14_X,
      handler: 'stream-logs.handler',
      code: Code.fromAsset(path.join(__dirname, '..', 'functions/stream-logs')),
      environment: {
        "DOMAIN": esDomain.attrDomainEndpoint,
        "REGION": this.region
      },
    });
    streamLogsFn.addToRolePolicy(ElasticsearchHttpPostPolicyStatement);

    const esRequestProvider = new Provider(this, 'esRequestProvider', {
      onEventHandler: esRequestsFn
    });

    /**
     * You can import files exported via Kibana's
     * Stack Management -> Save Objects as done with the
     * dashboard.ndjson below.
     */
    new CustomResource(this, 'esRequestsResource', {
      serviceToken: esRequestProvider.serviceToken,
      properties: {
        requests: [
          {
            "method": "PUT",
            "path": "_template/example-index-template",
            "body": fs.readFileSync(path.join(__dirname, "index-template.json")).toString()
          },
          {
            "method": "POST",
            "path": "_plugin/kibana/api/saved_objects/_import?overwrite=true",
            "body": fs.readFileSync(path.join(__dirname, "dashboard.ndjson")).toString(),
            "filename": "dashboard.ndjson"
          },
        ]
      }
    });

    new CfnOutput(this, 'createUserUrl', {
      description: "Create a new user in the user pool here.",
      value: "https://" + this.region + ".console.aws.amazon.com/cognito/users?region=" + this.region + "#/pool/" + userPool.ref + "/users"
    });

    new CfnOutput(this, 'kibanaUrl', {
      description: "Access Kibana via this URL.",
      value: "https://" + esDomain.attrDomainEndpoint + "/_plugin/kibana/"
    });

  }