constructor()

in lib/edge-analytics-stack.ts [32:670]


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

    const greengrass_service_role = new iam.Role(
      this,
      "GreengrassServiceRole",
      {
        roleName: "Greengrass_ServiceRole",
        assumedBy: new iam.ServicePrincipal("greengrass.amazonaws.com")
      }
    );

    greengrass_service_role.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName(
        "service-role/AWSGreengrassResourceAccessRolePolicy"
      )
    );

    greengrass_service_role.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLogsFullAccess")
    );

    const custom_greengrass_service_role_modifier = new CustomGreengrassServiceRoleResource(
      this,
      "CustomGreengrassServiceRoleModifier",
      {
        roleArn: greengrass_service_role.roleArn,
        account: this.account,
        stackName: this.stackName
      }
    );

    /**
     * Create Greengrass Core Thing.
     */

    const core = new iot.CfnThing(this, "Core", {
      thingName: "Greengrass-Core"
    });

    const core_definition = new greengrass.CfnCoreDefinition(
      this,
      "CoreDefinition",
      { name: `${core.thingName}-Definition` }
    );

    const core_credentials = new CustomCertificateResource(
      this,
      "CoreCredentials",
      {
        account: this.account,
        stackName: this.stackName,
        thingName: core.thingName!
      }
    );

    const core_definition_version = new greengrass.CfnCoreDefinitionVersion(
      this,
      "CoreDefinitionVersion",
      {
        coreDefinitionId: core_definition.ref,
        cores: [
          {
            id: core.ref,
            thingArn: `arn:aws:iot:${this.region}:${this.account}:thing/${core.thingName}`,
            certificateArn: core_credentials.certificateArn
          }
        ]
      }
    );

    const core_policy = new iot.CfnPolicy(this, "CorePolicy", {
      policyName: `${core.thingName}_Policy`,
      policyDocument: {
        Version: "2012-10-17",
        Statement: [
          {
            Effect: "Allow",
            Action: "iot:*",
            Resource: "*"
          },
          {
            Effect: "Allow",
            Action: "greengrass:*",
            Resource: "*"
          }
        ]
      }
    });

    const core_policy_principal_attachment = new iot.CfnPolicyPrincipalAttachment(
      this,
      "CorePolicyPrincipalAttachment",
      {
        principal: core_credentials.certificateArn,
        policyName: core_policy.ref
      }
    );

    const core_principal_attachment = new iot.CfnThingPrincipalAttachment(
      this,
      "CorePrincipalAttachment",
      { principal: core_credentials.certificateArn, thingName: core.ref }
    );

    core.addDependsOn(core_policy_principal_attachment);
    core_principal_attachment.addDependsOn(core);

    /**
     * Create Greengrass Device.
     * In this case node-red will emulate an IoT Thing.
     */

    const device = new iot.CfnThing(this, "IoTDevice", {
      thingName: "Node-Red-Thing"
    });

    const device_definition = new greengrass.CfnDeviceDefinition(
      this,
      "DeviceDefinition",
      { name: `${device.thingName}-Definition` }
    );

    const device_credentials = new CustomCertificateResource(
      this,
      "DeviceCredentials",
      {
        account: this.account,
        stackName: this.stackName,
        thingName: device.thingName!
      }
    );

    const device_definition_version = new greengrass.CfnDeviceDefinitionVersion(
      this,
      "DeviceDefinitionVersion",
      {
        deviceDefinitionId: device_definition.ref,
        devices: [
          {
            id: device.ref,
            certificateArn: device_credentials.certificateArn,
            thingArn: `arn:aws:iot:${this.region}:${this.account}:thing/${device.thingName}`,
            syncShadow: false
          }
        ]
      }
    );

    const device_policy = new iot.CfnPolicy(this, "DevicePolicy", {
      policyName: "GreengrassGroupDevicePolicy",
      policyDocument: {
        Version: "2012-10-17",
        Statement: [
          {
            Effect: "Allow",
            Action: ["iot:Connect", "iot:Publish", "greengrass:Discover"],
            Resource: ["*"]
          }
        ]
      }
    });

    const device_policy_principal_attachment = new iot.CfnPolicyPrincipalAttachment(
      this,
      "DevicePolicyPrincipalAttachment",
      {
        policyName: device_policy.ref,
        principal: device_credentials.certificateArn
      }
    );

    const device_principal_attachment = new iot.CfnThingPrincipalAttachment(
      this,
      "DevicePrincipalAttachment",
      {
        principal: device_credentials.certificateArn,
        thingName: device.ref
      }
    );

    /**
     * Create Greengrass Lambdas.
     */

    const greengrass_group_lambda_role = new iam.Role(
      this,
      "GreengrassGroupLambdaRole",
      {
        assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com")
      }
    );

    greengrass_group_lambda_role.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName("AWSLambdaFullAccess")
    );

    greengrass_group_lambda_role.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLogsFullAccess")
    );

    greengrass_group_lambda_role.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName("AWSIoTFullAccess")
    );

    greengrass_group_lambda_role.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName("AWSGreengrassFullAccess")
    );

    const receiver_lambda = new lambda.Function(this, "ReceiverLambda", {
      runtime: lambda.Runtime.PYTHON_3_7,
      code: lambda.Code.fromAsset(path.join(__dirname, "receiver-lambda")),
      handler: "receiver-lambda.handler",
      memorySize: 1024,
      role: greengrass_group_lambda_role,
      timeout: cdk.Duration.seconds(60)
    });

    const receiver_lambda_version = receiver_lambda.addVersion("1");

    const receiver_lambda_alias = new lambda.Alias(
      this,
      "ReceiverLambdaAlias",
      {
        aliasName: "ReceiverLambdaAlias",
        version: receiver_lambda_version
      }
    );

    const analyzer_lambda = new lambda.Function(this, "AnalyzerLambda", {
      runtime: lambda.Runtime.PYTHON_3_7,
      code: lambda.Code.fromAsset(path.join(__dirname, "analyzer-lambda")),
      handler: "analyzer-lambda.handler",
      memorySize: 1024,
      role: greengrass_group_lambda_role,
      timeout: cdk.Duration.seconds(60)
    });

    const analyzer_lambda_version = analyzer_lambda.addVersion(
      Guid.create().toString()
    );

    const analyzer_lambda_alias = new lambda.Alias(
      this,
      "AnalyzerLambdaAlias",
      {
        aliasName: "AnalyzerLambdaAlias",
        version: analyzer_lambda_version
      }
    );

    const function_definition = new greengrass.CfnFunctionDefinition(
      this,
      "FunctionDefinition",
      { name: "FunctionDefinition" }
    );

    const function_definition_version = new greengrass.CfnFunctionDefinitionVersion(
      this,
      "FunctionDefinitionVersion",
      {
        functionDefinitionId: function_definition.getAtt("Id").toString(),
        defaultConfig: {
          execution: {
            isolationMode: "NoContainer"
          }
        },
        functions: [
          {
            functionArn: receiver_lambda_alias.functionArn,
            id: Guid.create().toString(),
            functionConfiguration: {
              encodingType: "json",
              pinned: false,
              timeout: 30
            }
          },
          {
            functionArn: analyzer_lambda_alias.functionArn,
            id: Guid.create().toString(),
            functionConfiguration: {
              encodingType: "json",
              pinned: false,
              timeout: 30
            }
          }
        ]
      }
    );

    /**
     * Create Greengrass Loggers.
     */

    const logger_definition = new greengrass.CfnLoggerDefinition(
      this,
      "LoggerDefinition",
      {
        name: "LoggerDefinition"
      }
    );

    const logger_definition_version = new greengrass.CfnLoggerDefinitionVersion(
      this,
      "LoggerDefinitionVersion",
      {
        loggerDefinitionId: logger_definition.ref,
        loggers: [
          {
            component: "GreengrassSystem",
            id: "GreengrassSystem_Logger_CW_1",
            level: "INFO",
            type: "AWSCloudWatch"
          },
          {
            component: "Lambda",
            id: "Lambda_Logger_CW_1",
            level: "INFO",
            type: "AWSCloudWatch"
          },
          {
            component: "GreengrassSystem",
            id: "GreengrassSystem_Logger_Local_1",
            level: "DEBUG",
            space: 1024,
            type: "FileSystem"
          },
          {
            component: "Lambda",
            id: "Lambda_Logger_Local_1",
            level: "DEBUG",
            space: 1024,
            type: "FileSystem"
          }
        ]
      }
    );

    /**
     * Create Greengrass Subscriptions.
     */

    const subscription_definition = new greengrass.CfnSubscriptionDefinition(
      this,
      "SubscriptionDefinition",
      {
        name: "SubscriptionDefinition"
      }
    );

    const subscription_definition_version = new greengrass.CfnSubscriptionDefinitionVersion(
      this,
      "SubscriptionDefinitionVersion",
      {
        subscriptionDefinitionId: subscription_definition.ref,
        subscriptions: [
          {
            id: "DeviceToReceiver",
            source: `arn:aws:iot:${this.region}:${this.account}:thing/${device.thingName}`,
            subject: "metrics/raw/#",
            target: receiver_lambda_alias.functionArn
          },
          {
            id: "ReceiverToAnalyzer",
            source: receiver_lambda_alias.functionArn,
            subject: "metrics/stored/#",
            target: analyzer_lambda_alias.functionArn
          },
          {
            id: "AnalyzerToCloud",
            source: analyzer_lambda_alias.functionArn,
            subject: "metrics/filled/#",
            target: "cloud"
          },
          {
            id: "AnalyzerToDevice",
            source: analyzer_lambda_alias.functionArn,
            subject: "metrics/filled/#",
            target: `arn:aws:iot:${this.region}:${this.account}:thing/${device.thingName}`
          }
        ]
      }
    );

    /**
     * Create Greengrass Group.
     */

    const greengrass_group = new greengrass.CfnGroup(this, "GreengrassGroup", {
      name: "Greengrass-Group",
      roleArn: greengrass_service_role.roleArn
    });

    const greengrass_group_version = new greengrass.CfnGroupVersion(
      this,
      "GreengrassGroupVersion",
      {
        groupId: greengrass_group.ref,
        coreDefinitionVersionArn: core_definition_version.ref,
        deviceDefinitionVersionArn: device_definition_version.ref,
        subscriptionDefinitionVersionArn: subscription_definition_version.ref,
        loggerDefinitionVersionArn: logger_definition_version.ref,
        functionDefinitionVersionArn: function_definition_version.ref
      }
    );

    greengrass_group.addDependsOn(core_definition);
    greengrass_group.addDependsOn(device_definition);
    greengrass_group.addDependsOn(subscription_definition);
    greengrass_group.addDependsOn(logger_definition);
    greengrass_group.addDependsOn(function_definition);

    const vpc = ec2.Vpc.fromLookup(this, "VPC", {
      isDefault: true
    });

    const security_group = new ec2.SecurityGroup(this, "SecurityGroup", {
      vpc,
      description: "Allow ssh access to ec2 instances",
      allowAllOutbound: true
    });

    security_group.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(22),
      "Allow ssh access from the world"
    );

    security_group.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(80),
      "Allow http access from the world"
    );

    security_group.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(443),
      "Allow https access from the world"
    );

    security_group.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(8883),
      "Allow MQTT access from the world"
    );

    const ubuntu = new ec2.GenericLinuxImage({
      "us-east-1": "ami-04763b3055de4860b",
      "us-west-2": "ami-0994c095691a46fb5"
    });

    const instance_type = ec2.InstanceType.of(
      ec2.InstanceClass.T3,
      ec2.InstanceSize.SMALL
    );

    const instance_role = new iam.Role(this, "Ec2InstanceRole", {
      assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com")
    });

    instance_role.addToPolicy(
      new iam.PolicyStatement({
        resources: [core_credentials.secretArn, device_credentials.secretArn],
        actions: [
          "secretsmanager:GetSecretValue",
          "secretsmanager:ListSecrets",
          "secretsmanager:DescribeSecret"
        ]
      })
    );

    instance_role.addToPolicy(
      new iam.PolicyStatement({
        resources: [
          greengrass_group.attrArn,
          `${greengrass_group.attrArn}/certificateauthorities/*`
        ],
        actions: [
          "greengrass:ListGroupCertificateAuthorities",
          "greengrass:GetGroupCertificateAuthority",
          "greengrass:CreateDeployment"
        ]
      })
    );

    const core_instance_user_data = ec2.UserData.forLinux({
      shebang: "#!/bin/bash -xe"
    });
    core_instance_user_data.addCommands(
      "exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1",
      "apt-get update -y",
      "adduser --system ggc_user",
      "groupadd --system ggc_group",
      "apt-get install jq software-properties-common redis-server nginx -y",
      "echo | add-apt-repository ppa:deadsnakes/ppa",
      "apt-get update -y",
      "apt-get install python3.7 -y",
      "update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.5 1",
      "update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 2",
      "echo | update-alternatives --config python3",
      "curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py",
      "python3 get-pip.py",
      "systemctl enable redis-server.service",
      "systemctl enable nginx",
      "printf 'y' | bash <(curl -Ss https://my-netdata.io/kickstart-static64.sh)",
      // "wget https://d1onfpft10uf5o.cloudfront.net/greengrass-core/downloads/1.9.3/greengrass-linux-x86-64-1.9.3.tar.gz",
      // "tar -xzf greengrass-linux-x86-64-1.9.3.tar.gz -C /",
      "wget https://d1onfpft10uf5o.cloudfront.net/greengrass-core/downloads/1.10.0/greengrass-linux-x86-64-1.10.0.tar.gz",
      "tar -xzf greengrass-linux-x86-64-1.10.0.tar.gz -C /",
      "pip3 install awscli pandas redis==3.3.4 greengrasssdk==1.5.0 --upgrade",
      cdk.Fn.sub("${command}", {
        command: `aws secretsmanager get-secret-value --secret-id Greengrass-Core-Credentials --region  ${this.region} | jq --raw-output '.SecretString' | jq -r '.[0].certificatePem' > /greengrass/certs/${core.thingName}.certificate.pem`
      }),
      cdk.Fn.sub("${command}", {
        command: `aws secretsmanager get-secret-value --secret-id Greengrass-Core-Credentials --region  ${this.region} | jq --raw-output '.SecretString' | jq -r '.[1].privateKey' > /greengrass/certs/${core.thingName}.private.key`
      }),
      cdk.Fn.sub("${command}", {
        command: `aws secretsmanager get-secret-value --secret-id Greengrass-Core-Credentials --region  ${this.region} | jq --raw-output '.SecretString' | jq -r '.[2].publicKey' > /greengrass/certs/${core.thingName}.public.key`
      }),
      "curl -o /greengrass/certs/root.ca.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem",
      "curl -o /etc/systemd/system/greengrass.service https://gist.githubusercontent.com/sudhirjena/281cb8e27705a0273e6d11dccac05a93/raw/fa2979a3f313fde4699450a94e41dc550c879aba/greengrass.service",
      "systemctl enable greengrass.service",
      "cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.disabled",
      "curl -o /etc/nginx/sites-available/default https://gist.githubusercontent.com/sudhirjena/95f0a7bf5f4c3e4100f11512416eae6c/raw/f08cac27d0811cf27e34dd338fa9685c80ed4a8e/netdata.proxy.conf",
      "service nginx reload",
      "cd /greengrass/config",
      cdk.Fn.sub("echo '${command}' | jq '.' > config.json", {
        command: `{"coreThing":{"caPath":"root.ca.pem","certPath":"${core.thingName}.certificate.pem","keyPath":"${core.thingName}.private.key","thingArn":"arn:aws:iot:${this.region}:${this.account}:thing/${core.thingName}","iotHost":"${core_credentials.iotEndpoint}","ggHost":"greengrass-ats.iot.${this.region}.amazonaws.com","keepAlive":600},"runtime":{"cgroup":{"useSystemd":"yes"}},"managedRespawn":false,"crypto":{"principals":{"SecretsManager":{"privateKeyPath":"file:///greengrass/certs/${core.thingName}.private.key"},"IoTCertificate":{"privateKeyPath":"file:///greengrass/certs/${core.thingName}.private.key","certificatePath":"file:///greengrass/certs/${core.thingName}.certificate.pem"}},"caPath":"file:///greengrass/certs/root.ca.pem"}}`
      }),
      "service greengrass start",
      "curl -o /tmp/userdata.sh http://169.254.169.254/latest/user-data"
    );

    const core_instance = new ec2.Instance(this, "GreengrassCoreInstance", {
      instanceType: instance_type,
      machineImage: ubuntu,
      vpc: vpc,
      securityGroup: security_group,
      instanceName: "Greengrass-Core-Instance",
      keyName: "ee-default-keypair",
      role: instance_role,
      userData: core_instance_user_data
    });

    const node_red_instance_user_data = ec2.UserData.forLinux({
      shebang: "#!/bin/bash -xe"
    });
    node_red_instance_user_data.addCommands(
      "exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1",
      "su ubuntu",
      "echo 'Installing pre-reqs' ",
      "sudo apt-get update -y",
      "curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -",
      "sudo apt-get install nodejs jq python3-pip nginx -y",
      "sudo systemctl enable nginx",
      "pip3 install awscli --upgrade",
      "sudo npm install pm2 -g",
      "sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu",
      "sudo npm install -g --unsafe-perm node-red@0.19.5",
      "su ubuntu -c 'pm2 start /usr/bin/node-red' ",
      "mkdir /home/ubuntu/.awscerts",
      cdk.Fn.sub("${command}", {
        command: `aws secretsmanager get-secret-value --secret-id Node-Red-Thing-Credentials --region  ${this.region} | jq --raw-output '.SecretString' | jq -r '.[0].certificatePem' > /home/ubuntu/.awscerts/${device.thingName}.cert.pem`
      }),
      cdk.Fn.sub("${command}", {
        command: `aws secretsmanager get-secret-value --secret-id Node-Red-Thing-Credentials --region  ${this.region} | jq --raw-output '.SecretString' | jq -r '.[1].privateKey' > /home/ubuntu/.awscerts/${device.thingName}.private.key`
      }),
      cdk.Fn.sub("${command}", {
        command: `aws secretsmanager get-secret-value --secret-id Node-Red-Thing-Credentials --region  ${this.region} | jq --raw-output '.SecretString' | jq -r '.[2].publicKey' > /home/ubuntu/.awscerts/${device.thingName}.public.key`
      }),
      cdk.Fn.sub("${command}", {
        command: `# CERTIFICATE_AUTHORITY_ID=$(aws greengrass list-group-certificate-authorities --group-id ${greengrass_group.attrId} --region  ${this.region} | jq -r '.GroupCertificateAuthorities[0].GroupCertificateAuthorityId')`
      }),
      cdk.Fn.sub("${command}", {
        command: `# aws greengrass get-group-certificate-authority --certificate-authority-id $CERTIFICATE_AUTHORITY_ID  --group-id ${greengrass_group.attrId} --region  ${this.region} | jq -r '.PemEncodedCertificate' > /home/ubuntu/.awscerts/group-CA.crt`
      }),
      cdk.Fn.sub("${command}", {
        command: `while [ ! -f /home/ubuntu/.node-red/settings.js ];
      do
        sleep 2;
      done;
      sleep 1;`
      }),
      "cd /home/ubuntu/.node-red",
      "mv settings.js settings.js.backup",
      "curl -o settings.js https://gist.githubusercontent.com/sudhirjena/501728beb60a3f00fc85feb9966b81b4/raw/120896c1eecf5a0560bdd79f8e7764e2fd6a8391/settings.js",
      "npm install node-red-contrib-mqtt-broker hub node-red-dashboard",
      cdk.Fn.sub("${command}", {
        command: `curl https://gist.githubusercontent.com/sudhirjena/2efacfdd980a43ea0e6267a75adef077/raw/99af57950b98ae85aab51f122553052ea2e1d14d/node-red-flow.json | jq '.[15].broker = "${core_instance.instancePrivateIp}" '> flows_$HOSTNAME.json`
      }),
      "cd /home/ubuntu",
      "chown -R ubuntu:ubuntu .awscerts",
      "su ubuntu -c 'pm2 restart node-red' ",
      "sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.disabled",
      "sudo curl -o /etc/nginx/sites-available/default https://gist.githubusercontent.com/sudhirjena/7bc0eb86fded30bb8bbff961de611553/raw/c2341cbb68dc28ea52735c254a3cd0e5e01329a2/node-red.proxy.conf",
      "sudo service nginx reload",
      "curl -o /tmp/userdata.sh http://169.254.169.254/latest/user-data"
    );

    const node_red_instance = new ec2.Instance(this, "NodeRedInstance", {
      instanceType: instance_type,
      machineImage: ubuntu,
      vpc: vpc,
      securityGroup: security_group,
      instanceName: "Node-Red-Instance",
      keyName: "ee-default-keypair",
      role: instance_role,
      userData: node_red_instance_user_data
    });

    new cdk.CfnOutput(this, "Region", {
      description: "The AWS Region you are operating in.",
      value: this.region
    });

    new cdk.CfnOutput(this, "Greengrass Group Id", {
      description: "Identifier for your Greengrass Group",
      value: greengrass_group.attrId
    });

    new cdk.CfnOutput(this, "Greengrass Core EC2 IP Address", {
      description: "The public Ip Address of your Greengrass Core EC2 Instance",
      value: core_instance.instancePublicIp
    });

    new cdk.CfnOutput(this, "Netdata URL", {
      description:
        "The URL of Netdata to monitor performance of your Greengrass Core",
      value: `http://${core_instance.instancePublicIp}/netdata/`
    });

    new cdk.CfnOutput(this, "NodeRed URL", {
      value: `http://${node_red_instance.instancePublicIp}/node-red`
    });

    new cdk.CfnOutput(this, "NodeRed Dashboard URL", {
      value: `http://${node_red_instance.instancePublicIp}/node-red/ui`
    });
  }