diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5509140 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.DS_Store diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 387f04a..0000000 --- a/LICENSE +++ /dev/null @@ -1,14 +0,0 @@ -Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 903128f..0000000 --- a/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# Pipeline To Service Catalog - -This architecture and sample demonstrates how to simplify management of the -[AWS Service Catalogs](https://aws.amazon.com/servicecatalog/),related products and sharing of portfolios with other -accounts truly by maintaining them in version control, thereby adopting “Infrastructure as Code” -practices. This solution also delivers the updates to the products using a Continuous Delivery -[AWS CodePipeline](https://aws.amazon.com/codepipeline/) - -For more details, refer the blogpost - -[![](images/architecture.jpeg)][architecture] - -## Specifics on the mapping.yaml file - -mapping.yaml is the core configuration component of this solution, which defines how your portfolio , permissions and products associated with the portfolio are defined. -This configuration file is how your portfolio will look like in Service Catalog, when deployed by this solution. - -### __Description of properties__ - -* __name (string)__ - - Display name of the portfolio. This is the unique identifier and must match any already existing portfolio. - __Length Constraints:__ Minimum length of 1. Maximum length of 100. - __Example:__ Infrastructure - -* __description (string)__ - - Description of the portfolio. This field in the mapping.json overrides the description of the portfolio in service catalog during the sync operation, so that the mapping.json is the ultimate source of truth. - __Length Constraints:__ Maximum length of 2000. - __Example:__ Includes all the products related to infrastructure - -* __owner (string)__ - Owner of the portfolio. This field in the mapping.json overrides the owner of the portfolio in service catalog during the sync operation, so that the mapping.json is the ultimate source of truth. - __Length Constraints:__ Minimum length of 1. Maximum length of 20. - __Example:__ operations - -* __products (list)__ - * __name (string)__ - Display name of the product. This is the unique identifier and must match any already existing product. A new product will be created and associated with the portfolio, if the name does not matches with any product already associated in the portfolio. If a match is found, a new version of the product will be created. - __Example:__ VPC - - * __template (string)__ - Name of the CloudFormation template on file system - __Example:__ product-vpc.yaml - * __owner(string)__ - Owner of the template, who has authored this template - __Example:__ team1@example.com - * __description(string)__ - Description of the product - __Example:__ VPC and 4 subnet architecture - -* __accounts (list)__ - List of accounts, with which you wish to share the portfolio with. - If you add new accounts in the list, the portfolio will be shared with that account. If you remove any account from the list, the portfolio will be un-shared with that account. - Must be a valid AWS Account. - * __identifier (string)__ - Friendly name with which you wish to identify the account number - __Example:__ Test Account - * __number (string)__ - AWS Account number, with which you wish to share the portfolio with - __Example:__ 123456789012 - -* __tags (list)__ - Key value pair to tag the portfolio with - * __Key(string)__ - The Key name - * __Value (string)__ - The value of the Key - -* __principals (list)__ - List of IAM Users, Roles or Groups to allow using the products in the portfolio in the current account. - Must be either IAM Role (__role/SOME_ROLE__) or IAM User (__user/SOME_USR__) - or IAM Group (__group/SOME_GROUP__) - __Example:__ - - role/Admin - - user/name - - group/AdminGroup diff --git a/bin/deploy.sh b/bin/deploy.sh deleted file mode 100755 index 196b0dd..0000000 --- a/bin/deploy.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -IFS= read -r -p "Enter S3Bucket to use: " S3Bucket -IFS= read -r -p "Enter CodeCommit Repository to use: " CodeCommit - -cp pipeline-to-service-catalog.yaml scripts/pipeline-to-service-catalog.yaml -cd scripts -pip install -r requirements.txt -t "$PWD" --upgrade -aws cloudformation package --template-file pipeline-to-service-catalog.yaml \ ---s3-bucket "$S3Bucket" --s3-prefix cfn-packages --output-template-file transformed-pipeline-to-service-catalog.yaml -rm pipeline-to-service-catalog.yaml -aws cloudformation deploy --template-file transformed-pipeline-to-service-catalog.yaml --capabilities CAPABILITY_NAMED_IAM \ ---parameter-overrides S3Bucket="$S3Bucket" CodeCommit="$CodeCommit" --stack-name blog-sc-pipeline -cd .. \ No newline at end of file diff --git a/images/architecture b/images/architecture deleted file mode 100644 index c3a69cb..0000000 Binary files a/images/architecture and /dev/null differ diff --git a/images/architecture.jpeg b/images/architecture.jpeg deleted file mode 100644 index 54173c7..0000000 Binary files a/images/architecture.jpeg and /dev/null differ diff --git a/images/architecture.xml b/images/architecture.xml deleted file mode 100644 index bd0cc1f..0000000 --- a/images/architecture.xml +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/images/sync-sc-pipeline.jpeg b/images/sync-sc-pipeline.jpeg deleted file mode 100644 index a187a27..0000000 Binary files a/images/sync-sc-pipeline.jpeg and /dev/null differ diff --git a/lambda-cloudformation.yaml b/lambda-cloudformation.yaml index 07b256c..8a5c04d 100644 --- a/lambda-cloudformation.yaml +++ b/lambda-cloudformation.yaml @@ -1,66 +1,70 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -AWSTemplateFormatVersion: '2010-09-09' -Description: service-catalog-sync-lambda +AWSTemplateFormatVersion: '2010-09-09' +Description: custom organization-config-rules Resources: - LAMBDA: + rVPCFlowLogS3EnforcementLambda2: Type: AWS::Lambda::Function + DependsOn: + - rVPCFlowLogS3EnforcementLambdaRole2 Properties: - FunctionName: service-catalog-sync-lambda - Handler: sync-catalog.handler + FunctionName: VPCFlowLogS3EnforcementLambda2 + Handler: VPCFlowLogS3EnforcementLambda.lambda_handler Runtime: python2.7 - Description: Function to sync service catalog with a configuration file - Code: ./ + Description: Ensure that VPC FlowLogs to centralized S3 bucket. + Code: ./rules/VPC_FLOW_LOGS_TO_S3_CUSTOM/VPCFlowLogS3EnforcementLambda.py MemorySize: 128 - Timeout: 300 - Role: !GetAtt LAMBDAROLE.Arn - LAMBDAROLE: - Type: AWS::IAM::Role - Properties: - RoleName: !Sub service-catalog-sync-lambda-role-${AWS::Region} - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - - Effect: Allow - Principal: - Service: - - lambda.amazonaws.com - Action: - - sts:AssumeRole - Path: / - LAMBDAPOLICY: - Type: AWS::IAM::Policy + Timeout: 180 + Role: !GetAtt rVPCFlowLogS3EnforcementLambdaRole2.Arn + rVPCFlowLogS3EnforcementLambdaRole2: + Type: 'AWS::IAM::Role' Properties: - PolicyName: !Sub service-catalog-sync-lambda-policy-${AWS::Region} - PolicyDocument: - Version: 2012-10-17 + RoleName: VPCFlowLogS3EnforcementLambdaRole2 + AssumeRolePolicyDocument: + Version: '2012-10-17' Statement: - - - Effect: Allow - Action: - - servicecatalog:* - - s3:* - - codepipeline:PutJobFailureResult - - codepipeline:PutJobSuccessResult - - cloudformation:ValidateTemplate - - iam:GetRole - Resource: - - "*" - - - Effect: Allow - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:PutLogEvents - Resource: arn:aws:logs:*:*:* - Roles: - - - !Ref LAMBDAROLE -Outputs: - LambdaArn: - Description: ARN of the Lambda Function, which syncs up configuration files with yaml file - Value: !GetAtt LAMBDA.Arn - Export: - Name: service-catalog-sync-lambda \ No newline at end of file + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: 'sts:AssumeRole' + Policies: + - PolicyName: VPCFlowLogS3EnforcementLambdaPolicy2 + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'logs:CreateLogGroup' + - 'logs:CreateLogStream' + - 'logs:PutLogEvents' + - 'logs:DescribeLogStreams' + - 'logs:DescribeLogGroups' + - 'logs:CreateLogDelivery' + - 'logs:DeleteLogDelivery' + - 'ec2:CreateFlowLogs' + - 'ec2:DescribeFlowLogs' + - 'config:PutEvaluations' + - 'config:StartConfigRulesEvaluation' + Resource: + - '*' + - PolicyName: OrgConfigRuleAssumeEvalsRole2Policy2 + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: "sts:AssumeRole" + Resource: "arn:aws:iam::*:role/OrgConfigRuleEvalsRole2" + - PolicyName: VPCFlowLogS3EnforcementAssumeLambdaRole2Policy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: "sts:AssumeRole" + Resource: "arn:aws:iam::*:role/VPCFlowLogS3EnforcementLambdaRole2" + rConfigPermissionToCallLambda: + Type: "AWS::Lambda::Permission" + DependsOn: + - rVPCFlowLogS3EnforcementLambda2 + Properties: + FunctionName: !GetAtt rVPCFlowLogS3EnforcementLambda2.Arn + Action: "lambda:InvokeFunction" + Principal: "config.amazonaws.com" diff --git a/org-config-rules.yaml b/org-config-rules.yaml new file mode 100644 index 0000000..020e8b9 --- /dev/null +++ b/org-config-rules.yaml @@ -0,0 +1,147 @@ +--- +# +# Filename : org-managed-config-rules.yaml +# Date : 6 Dec 2019 +# Author : Tommy Hunt (tahv@pge.com) +# Description : AWS managed Organization Config Rules for all accounts. +# +AWSTemplateFormatVersion: '2010-09-09' +Description: Managed Organization AWS:Config Rules +Parameters: + ComplianceAccount: + Description: AWS AccountNumber of the centralized-lambda to be invoked. + Type: Number + Default: 567207295412 + pLambdaExecutionRoleARN: + Type: String + Description: ARN of Role allowing lambda to determine compliance + Default: 'arn:aws:iam::*:role/VPCFlowLogS3EnforcementLambdaRole2' + pOrgConfigRuleEvalsRoleARN: + Type: String + Description: ARN of Role allowing PutEvaluations to account's OrganizationConfigRules + Default: 'arn:aws:iam::*:role/OrgConfigRuleEvalsRole' +Mappings: + AcctType: + nonprod: + XAccounts: + - "919568423267" #Master + # - "782391863272" #Tools + # - "123133550781" #Dev + # - "567207295412" #Test + # - "930856341568" #Production + +Resources: + rConfigRuleForCloudTrail: + Type: AWS::Config::OrganizationConfigRule + Properties: + ExcludedAccounts: + !FindInMap [AcctType, nonprod, XAccounts] + OrganizationConfigRuleName: org-is-cloudtrail-enabled + OrganizationManagedRuleMetadata: + Description: Is Cloudtrail enabled for this account? + RuleIdentifier: CLOUD_TRAIL_ENABLED + rConfigRuleForSSH: + Type: AWS::Config::OrganizationConfigRule + Properties: + ExcludedAccounts: + !FindInMap [AcctType, nonprod, XAccounts] + OrganizationConfigRuleName: org-ec2-sg-unrestricted-ssh-access + OrganizationManagedRuleMetadata: + Description: Do EC2 security groups restrict inbound ssh access? + ResourceTypesScope: + - AWS::EC2::SecurityGroup + RuleIdentifier: INCOMING_SSH_DISABLED + rConfigRuleForRequiredTags: + Type: AWS::Config::OrganizationConfigRule + Properties: + ExcludedAccounts: + !FindInMap [AcctType, nonprod, XAccounts] + OrganizationConfigRuleName: ec2-required-tags + OrganizationManagedRuleMetadata: + Description: Do EC2 instances and volumes comply with required tags? + InputParameters: '{"tag1Key": "Name", "tag2Key": "AppID"}' + ResourceTypesScope: + - AWS::EC2::Volume + - AWS::EC2::Instance + RuleIdentifier: REQUIRED_TAGS + rConfigRuleForUnrestrictedPorts: + Type: AWS::Config::OrganizationConfigRule + Properties: + ExcludedAccounts: + !FindInMap [AcctType, nonprod, XAccounts] + OrganizationConfigRuleName: org-ec2-sg-unrestricted-ports + OrganizationManagedRuleMetadata: + Description: Do security groups restrict inbound TCP traffic ? + InputParameters: '{"blockedPort1": "3389", "blockedPort2": "22"}' + ResourceTypesScope: + - AWS::EC2::SecurityGroup + RuleIdentifier: RESTRICTED_INCOMING_TRAFFIC + rConfigRuleForRdsPublic: + Type: AWS::Config::OrganizationConfigRule + Properties: + ExcludedAccounts: + !FindInMap [AcctType, nonprod, XAccounts] + OrganizationConfigRuleName: org-rds-public-access + OrganizationManagedRuleMetadata: + Description: Are RDS instances publicly accessible? + ResourceTypesScope: + - AWS::RDS::DBInstance + RuleIdentifier: RDS_INSTANCE_PUBLIC_ACCESS_CHECK + rConfigRuleForRdsEncryption: + Type: AWS::Config::OrganizationConfigRule + Properties: + ExcludedAccounts: + !FindInMap [AcctType, nonprod, XAccounts] + OrganizationConfigRuleName: org-rds-encryption + OrganizationManagedRuleMetadata: + Description: Are RDS instances encrypted? + ResourceTypesScope: + - AWS::RDS::DBInstance + RuleIdentifier: RDS_STORAGE_ENCRYPTED + rConfigRuleForRdsBackup: + Type: AWS::Config::OrganizationConfigRule + Properties: + ExcludedAccounts: + !FindInMap [AcctType, nonprod, XAccounts] + OrganizationConfigRuleName: org-rds-backup + OrganizationManagedRuleMetadata: + Description: Are RDS instances being backed-up? + ResourceTypesScope: + - AWS::RDS::DBInstance + RuleIdentifier: DB_INSTANCE_BACKUP_ENABLED + rConfigRuleForEncryptedBuckets: + Type: AWS::Config::OrganizationConfigRule + Properties: + ExcludedAccounts: + !FindInMap [AcctType, nonprod, XAccounts] + OrganizationConfigRuleName: s3-bucket-server-side-encryption-enabled + OrganizationManagedRuleMetadata: + Description: Does S3 bucket have either S3 default encryption enabled or an S3 bucket policy explicitly denies put-object requests without server side encryption? + ResourceTypesScope: + - AWS::S3::Bucket + RuleIdentifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED + rConfigRuleForEncryptedVolumes: + Type: AWS::Config::OrganizationConfigRule + Properties: + ExcludedAccounts: + !FindInMap [AcctType, nonprod, XAccounts] + OrganizationConfigRuleName: encrypted-volumes + OrganizationManagedRuleMetadata: + Description: Are EBS volumes that are in an attached state encrypted? + ResourceTypesScope: + - AWS::EC2::Volume + RuleIdentifier: ENCRYPTED_VOLUMES + # rConfigVPCFlowLogRule: + # Type: AWS::Config::OrganizationConfigRule + # Properties: + # ExcludedAccounts: + # !FindInMap [AcctType, nonprod, XAccounts] + # OrganizationConfigRuleName: VPCFlowLogS3Rule2 + # OrganizationCustomRuleMetadata: + # Description: Are VPCFlowLogs logging to S3? + # InputParameters: !Sub '{"executionRole":"${pLambdaExecutionRoleARN}","evaluationRole":"${pOrgConfigRuleEvalsRoleARN}"}' + # LambdaFunctionArn: !Sub arn:aws:lambda:us-east-2:${ComplianceAccount}:function:VPCFlowLogS3EnforcementLambda2 + # OrganizationConfigRuleTriggerTypes: + # - 'ConfigurationItemChangeNotification' + # ResourceTypesScope: + # - AWS::EC2::VPC diff --git a/pipeline-to-service-catalog.yaml b/pipeline-to-service-catalog.yaml deleted file mode 100644 index 22f1526..0000000 --- a/pipeline-to-service-catalog.yaml +++ /dev/null @@ -1,293 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: pipeline to service catalog -Parameters: - RepositoryName: - Description: > - Name of the AWS CodeCommit that you wish to create, where you can push the code related to Service Catalog - Type: String -Resources: - ServiceCatalogRepo: - Type: AWS::CodeCommit::Repository - DeletionPolicy: Retain - Properties: - RepositoryDescription: Holds Service Catalog code - RepositoryName: !Ref RepositoryName - ArtifactBucket: - Type: AWS::S3::Bucket - DeletionPolicy: Retain - PipeLineRole: - Type: AWS::IAM::Role - Properties: - RoleName: !Sub ${AWS::StackName}-role-${AWS::Region} - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - - Effect: Allow - Principal: - Service: - - codepipeline.amazonaws.com - Action: - - sts:AssumeRole - Path: / - Policies: - - - PolicyDocument: - Version: 2012-10-17 - Statement: - - - Effect: Allow - Action: - - codepipeline:* - - iam:ListRoles - - cloudformation:* - - codecommit:List* - - codecommit:Get* - - codecommit:GitPull - - codecommit:UploadArchive - - codecommit:CancelUploadArchive - - codebuild:BatchGetBuilds - - codebuild:StartBuild - - iam:PassRole - - s3:ListAllMyBuckets - - s3:GetBucketLocation - - lambda:InvokeFunction - - lambda:ListFunctions - - lambda:GetFunctionConfiguration - Resource: - - "*" - - - Effect: Allow - Action: - - s3:PutObject - - s3:GetBucketPolicy - - s3:GetObject - - s3:ListBucket - Resource: - - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket, '/*']] - - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket]] - PolicyName: !Sub ${AWS::StackName}-policy-${AWS::Region} - BuildProjectRole: - Type: AWS::IAM::Role - Properties: - #RoleName: !Sub ${AWS::StackName}-CodeBuildRole - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - - Effect: Allow - Principal: - Service: - - codebuild.amazonaws.com - Action: - - sts:AssumeRole - Path: / - BuildProjectPolicy: - Type: AWS::IAM::Policy - DependsOn: ArtifactBucket - Properties: - PolicyName: !Sub ${AWS::StackName}-CodeBuildPolicy - PolicyDocument: - Version: 2012-10-17 - Statement: - - - Effect: Allow - Action: - - s3:PutObject - - s3:GetBucketPolicy - - s3:GetObject - - s3:ListBucket - Resource: - - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket, '/*']] - - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket]] - - - Effect: Allow - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:PutLogEvents - Resource: arn:aws:logs:*:*:* - Roles: - - - !Ref BuildProjectRole - BuildProject: - Type: AWS::CodeBuild::Project - Properties: - Name: !Ref AWS::StackName - Description: !Sub ${AWS::StackName}-buildproject - ServiceRole: !GetAtt BuildProjectRole.Arn - Artifacts: - Type: CODEPIPELINE - Environment: - Type: linuxContainer - ComputeType: BUILD_GENERAL1_SMALL - Image: aws/codebuild/python:2.7.12 - EnvironmentVariables: - - Name: S3Bucket - Value: !Ref ArtifactBucket - Source: - Type: CODEPIPELINE - BuildSpec: | - version: 0.1 - phases: - install: - commands: - - printenv - - ls -R - - cd scripts && pip install -r requirements.txt -t "$PWD" - build: - commands: - - cp lambda-cloudformation.yaml scripts/lambda-cloudformation.yaml - - cd scripts && aws cloudformation package --template-file lambda-cloudformation.yaml --s3-bucket $S3Bucket --s3-prefix catalog-sync-lambda/codebuild --output-template-file samtemplate.yaml - artifacts: - files: scripts/samtemplate.yaml - discard-paths: yes - - TimeoutInMinutes: 10 - Tags: - - Key: Name - Value: !Ref AWS::StackName - CFDeployerRole: - Type: AWS::IAM::Role - Properties: - #RoleName: !Sub ${AWS::StackName}-cfdeployer-role-${AWS::Region} - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - - Effect: Allow - Principal: - Service: - - cloudformation.amazonaws.com - Action: - - sts:AssumeRole - Path: / - CFDeployerPolicy: - Type: AWS::IAM::Policy - Properties: - PolicyName: !Sub ${AWS::StackName}-cfdeployer-policy-${AWS::Region} - PolicyDocument: - Version: 2012-10-17 - Statement: - - - Effect: Allow - Action: - - lambda:AddPermission - - lambda:CreateFunction - - lambda:DeleteFunction - - lambda:InvokeFunction - - lambda:RemovePermission - - lambda:UpdateFunctionCode - - lambda:GetFunctionConfiguration - - lambda:GetFunction - - lambda:UpdateFunctionConfiguration - - iam:CreateRole - - iam:CreatePolicy - - iam:GetRole - - iam:DeleteRole - - iam:PutRolePolicy - - iam:PassRole - - iam:DeleteRolePolicy - - cloudformation:* - Resource: "*" - - - Effect: Allow - Action: - - s3:PutObject - - s3:GetBucketPolicy - - s3:GetObject - - s3:ListBucket - Resource: - - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket, '/*']] - - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket]] - Roles: - - - !Ref CFDeployerRole - Pipeline: - Type: AWS::CodePipeline::Pipeline - DependsOn: PipeLineRole - Properties: - RoleArn: !GetAtt PipeLineRole.Arn - Name: !Ref AWS::StackName - Stages: - - Name: source-code-checkout - Actions: - - Name: App - ActionTypeId: - Category: Source - Owner: AWS - Version: 1 - Provider: CodeCommit - Configuration: - RepositoryName: !GetAtt ServiceCatalogRepo.Name - BranchName: master - OutputArtifacts: - - Name: SCCheckoutArtifact - RunOrder: 1 - - - Name: Build - Actions: - - - Name: build-lambda-function - ActionTypeId: - Category: Build - Owner: AWS - Version: 1 - Provider: CodeBuild - Configuration: - ProjectName: !Ref BuildProject - RunOrder: 1 - InputArtifacts: - - Name: SCCheckoutArtifact - OutputArtifacts: - - Name: BuildOutput - - Name: Deploy - Actions: - - Name: deploy-lambda-function - ActionTypeId: - Category: Deploy - Owner: AWS - Version: 1 - Provider: CloudFormation - Configuration: - ChangeSetName: Deploy - ActionMode: CREATE_UPDATE - StackName: !Sub service-catalog-sync-lambda - Capabilities: CAPABILITY_NAMED_IAM - TemplatePath: BuildOutput::samtemplate.yaml - RoleArn: !GetAtt CFDeployerRole.Arn - InputArtifacts: - - Name: BuildOutput - RunOrder: 1 - - - Name: Invoke - Actions: - - - Name: call-function - ActionTypeId: - Category: Invoke - Owner: AWS - Version: 1 - Provider: Lambda - Configuration: - FunctionName: service-catalog-sync-lambda - InputArtifacts: - - Name: SCCheckoutArtifact - RunOrder: 1 - ArtifactStore: - Type: S3 - Location: !Ref ArtifactBucket -Outputs: - ArtifactBucket: - Description: ArtifactBucket to be Used - Value: !Ref ArtifactBucket - RepositoryHttpUrl: - Description: CodeCommit Repository HTTP URL to push Service Catalog Related Artifacts - Value: !GetAtt ServiceCatalogRepo.CloneUrlHttp - RepositorySSHUrl: - Description: CodeCommit Repository SSH URL to push Service Catalog Related Artifacts - Value: !GetAtt ServiceCatalogRepo.CloneUrlSsh \ No newline at end of file diff --git a/portfolio-infrastructure/mapping.yaml b/portfolio-infrastructure/mapping.yaml deleted file mode 100644 index d55864c..0000000 --- a/portfolio-infrastructure/mapping.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: NAME_OF_THE_PORTFOLIO -description: DESCRIPTION_OF_THE_PORTFOLIO -owner: EMAIL@EXAMPLE.COM -products: -- name: vpc-product - template: product-vpc.yaml - owner: EMAIL@EXAMPLE.COM - description: creates a vpc and all related components -- name: sample-dynamodb - template: product-sample-dynamodb.yaml - owner: EMAIL@EXAMPLE.COM - description: creates a sample dynamodb table -accounts: -- identifier: Sample AWS Account - number: 123456789012 -- identifier: Another - number: 123456789013 -tags: -- Key: keynam1 - Value: value1 -- Key: keyname2 - Value: value2 -principals: -- role/Admin \ No newline at end of file diff --git a/portfolio-infrastructure/product-sample-dynamodb.yaml b/portfolio-infrastructure/product-sample-dynamodb.yaml deleted file mode 100644 index b850ae4..0000000 --- a/portfolio-infrastructure/product-sample-dynamodb.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 -AWSTemplateFormatVersion: 2010-09-09 -Resources: - DynamoDBTable: - Type: AWS::DynamoDB::Table - Properties: - AttributeDefinitions: - - - AttributeName: ArtistId - AttributeType: S - - - AttributeName: Concert - AttributeType: S - - - AttributeName: TicketSales - AttributeType: S - KeySchema: - - - AttributeName: ArtistId - KeyType: HASH - - - AttributeName: Concert - KeyType: RANGE - ProvisionedThroughput: - ReadCapacityUnits: 1 - WriteCapacityUnits: 1 - TableName: sample-table \ No newline at end of file diff --git a/portfolio-infrastructure/product-vpc.yaml b/portfolio-infrastructure/product-vpc.yaml deleted file mode 100644 index a2b2e4b..0000000 --- a/portfolio-infrastructure/product-vpc.yaml +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -AWSTemplateFormatVersion: '2010-09-09' -Description: 'Launches VPC, NATGateways, Endpoints, 4 Subnet Architecture' -Parameters: - EnvironmentName: - Description: Name of the VPC - AllowedPattern: "[a-z0-9]*" - Type: String - ConstraintDescription : Must begin with a letter. Lower case letters.Can include numbers. - MaxLength: 5 - Default: dev -Mappings: - VpcMap: - dev: - VpcCIDR: 10.0.0.0/16 - Subnet1CIDR: 10.0.32.0/27 - Subnet2CIDR: 10.0.64.0/27 -Resources: - VPC: - Type: AWS::EC2::VPC - Properties: - CidrBlock: !FindInMap [VpcMap,!Ref EnvironmentName,VpcCIDR] - Tags: - - Key: Name - Value: !Ref EnvironmentName - - InternetGateway: - Type: AWS::EC2::InternetGateway - Properties: - Tags: - - Key: Name - Value: !Ref EnvironmentName - - InternetGatewayAttachment: - Type: AWS::EC2::VPCGatewayAttachment - Properties: - InternetGatewayId: !Ref InternetGateway - VpcId: !Ref VPC - - Subnet1: - Type: AWS::EC2::Subnet - Properties: - VpcId: !Ref VPC - AvailabilityZone: !Select [ 0, !GetAZs ''] - MapPublicIpOnLaunch: true - CidrBlock: !FindInMap [VpcMap,!Ref EnvironmentName,Subnet1CIDR] - Tags: - - Key: Name - Value: !Sub ${EnvironmentName} (Public) - - Subnet2: - Type: AWS::EC2::Subnet - Properties: - VpcId: !Ref VPC - AvailabilityZone: !Select [ 1, !GetAZs ''] - MapPublicIpOnLaunch: true - CidrBlock: !FindInMap [VpcMap,!Ref EnvironmentName,Subnet2CIDR] - Tags: - - Key: Name - Value: !Sub ${EnvironmentName} (Public) - - RouteTable: - Type: AWS::EC2::RouteTable - Properties: - VpcId: !Ref VPC - Tags: - - Key: Name - Value: !Ref EnvironmentName - - DefaultRoute: - Type: AWS::EC2::Route - Properties: - RouteTableId: !Ref RouteTable - DestinationCidrBlock: 0.0.0.0/0 - GatewayId: !Ref InternetGateway - - Subnet1RouteTableAssociation: - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - RouteTableId: !Ref RouteTable - SubnetId: !Ref Subnet1 - - Subnet2RouteTableAssociation: - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - RouteTableId: !Ref RouteTable - SubnetId: !Ref Subnet2 - -Outputs: - Subnet1: - Value: !Ref Subnet1 - Subnet2: - Value: !Ref Subnet2 - VpcId: - Value: !Ref VPC diff --git a/rules/VPC_FLOW_LOGS_TO_S3_CUSTOM/README.md b/rules/VPC_FLOW_LOGS_TO_S3_CUSTOM/README.md new file mode 100644 index 0000000..fb4f1ec --- /dev/null +++ b/rules/VPC_FLOW_LOGS_TO_S3_CUSTOM/README.md @@ -0,0 +1,77 @@ +# aws-vpc-flowlogs + +**NOTE: aws-vpc-flowlogs are no longer deployed using these templages.** +**VPC Flow Log enablement is done in the base-vpc-from-parameter Stackset.** +**AWS Config rules to enforce enablement is done in the BaseConfigRules Stackset.** +**AWS Config is enabled in Enable-Config Stackset.** + +## Overview +Cloudformation templates: + * CentralLoggingSetup: Initial creation of Kinesis Stream, IAM policy, S3 bucket, KMS keys (currently not being used as QRadar was not able to decrypt the files), and Log Destionation PolicyName +StackSet Templates: + * AccountLoggingSetup: Run against LoBs to setup a Logs Subscription Filter + * VPCFlowLogChecker: Run against LoBs that setup the lambda_function, Config Rule and IAM Permissions + * Config: Sets up AWS Config for accounts with a few basic rules. +Lambda: + * cloudwatchParser: Unzips and decodes cloudwatch logs that come in. + * lambda_function: Checks to see if VPC Flow logs are enabled, if not, creates them. + * VPCFlowLog-Checker.zip: Zipped lambba_function that is stored in S3 and pulled down by LoBs + +## How to use +#### If this is the first time any StackSets have been run, or the first time AWS Config has been used do the following: + * Line of Business Acount: Run StackSetExecutionRole.yml in the region you will be working with (US-WEST-2) + * StackSet Account: (pge-it-stackset) Add the new account number to it to the existing Config-Setup StackSet and run it. This will do the first time configuration of AWS Config and setup its S3 bucket. **WARNING: If you skip this step the vpc-flow-logs log group will not be created and the EnableLogSubscriptionFilters stackset will fail! + * StackSet Account: Edit bucket policy for pge-central-lambda (under Permissions) and add: + Example: + + ``` + { + "Sid": "Allow Account: ", + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::############:role/AWSCloudFormationStackSetExecutionRole" + }, + "Action": [ + "s3:Get*", + "s3:List*" + ], + "Resource": [ + "arn:aws:s3:::pge-central-lambda", + "arn:aws:s3:::pge-central-lambda/*" + ] + } +``` +This allows the new LoB to pull down Lambda zip files to set up in their personal acocunt. + +#### If those steps have been completed already, please continue below: +1. StackSet Account: Edit the CentralLoggingSetup.yaml file and update the following resource: + * VPCFlowLogsDestination: + Type: 'AWS::Logs::Destination' + + The following will need to be added to the policy to allow Log Subscriptions: + + ``` + - '{"Effect" : "Allow", "Principal" : {"AWS" : "' + - '############' + - '" }, "Action" : "logs:PutSubscriptionFilter", "Resource" : "arn:aws:logs:' + - !Ref AWS::Region + - ':' + - !Ref AWS::AccountId + - ':destination:VPCFlowLogsDestination"},' + ``` + +The ######## is the account number for the new LoB. + +2. StackSet Account: Run an update on CentralLoggingSetup.yaml (this is a normal CloudFormation template not a StackSet) + +3. StackSet Account: Open the existing VPCFlowLogEnforcement + * Open Manage StackSets + * Create StackSet + * Add LoB Account Number and Region and go through the remainder of the prompts + + 4. This will begin the enforcement of VPC Flows Logs in the new account. VPC Flow Logs groups are automatically generated, but can take 3-7 minutes. You can either wait or log in and check that vpc-flow-log log group has been created in CloudWatch. Note, an EC2 instance that can generate traffic will help speed the creation of the vpc-flow-logs log group. + + 5. StackSet Account: Open the existing EnableLogSubscriptionFilters + * Open Manage StackSets + * Create StackSet + * Add LoB Account Number and Region and go through the remainder of the prompts diff --git a/rules/VPC_FLOW_LOGS_TO_S3_CUSTOM/VPCFlowLogS3EnforcementLambda.json b/rules/VPC_FLOW_LOGS_TO_S3_CUSTOM/VPCFlowLogS3EnforcementLambda.json new file mode 100644 index 0000000..05a06d8 --- /dev/null +++ b/rules/VPC_FLOW_LOGS_TO_S3_CUSTOM/VPCFlowLogS3EnforcementLambda.json @@ -0,0 +1,171 @@ +{ + "configRuleId": "config-rule-lr9as8", + "version": "1.0", + "configRuleName": "OrgConfigRule-VPCFlowLogS3Rule2-uzq6tknq", + "configRuleArn": "arn:aws:config:us-west-2:514712703977:config-rule/aws-service-rule/config-multiaccountsetup.amazonaws.com/config-rule-lr9as8", + "invokingEvent": { + "configurationItemDiff": null, + "configurationItem": { + "relatedEvents": [], + "relationships": [ + { + "resourceId": "acl-03f8b26a1e2c6764a", + "resourceName": null, + "resourceType": "AWS::EC2::NetworkAcl", + "name": "Contains NetworkAcl" + }, + { + "resourceId": "acl-0fe9828fbea8ab09a", + "resourceName": null, + "resourceType": "AWS::EC2::NetworkAcl", + "name": "Contains NetworkAcl" + }, + { + "resourceId": "eni-0058f547080cea5a6", + "resourceName": null, + "resourceType": "AWS::EC2::NetworkInterface", + "name": "Contains NetworkInterface" + }, + { + "resourceId": "eni-07ddf1b4fd741e784", + "resourceName": null, + "resourceType": "AWS::EC2::NetworkInterface", + "name": "Contains NetworkInterface" + }, + { + "resourceId": "rtb-0167cf4ae3d163418", + "resourceName": null, + "resourceType": "AWS::EC2::RouteTable", + "name": "Contains RouteTable" + }, + { + "resourceId": "rtb-05a226c7e57a73128", + "resourceName": null, + "resourceType": "AWS::EC2::RouteTable", + "name": "Contains RouteTable" + }, + { + "resourceId": "sg-01df04091f6d66432", + "resourceName": null, + "resourceType": "AWS::EC2::SecurityGroup", + "name": "Contains SecurityGroup" + }, + { + "resourceId": "sg-09cd2f17d3ad16cb5", + "resourceName": null, + "resourceType": "AWS::EC2::SecurityGroup", + "name": "Contains SecurityGroup" + }, + { + "resourceId": "sg-0fabbfa23226e6a62", + "resourceName": null, + "resourceType": "AWS::EC2::SecurityGroup", + "name": "Contains SecurityGroup" + }, + { + "resourceId": "subnet-01e6eb361d99a4b7f", + "resourceName": null, + "resourceType": "AWS::EC2::Subnet", + "name": "Contains Subnet" + }, + { + "resourceId": "subnet-046aee08d92882bdc", + "resourceName": null, + "resourceType": "AWS::EC2::Subnet", + "name": "Contains Subnet" + }, + { + "resourceId": "subnet-0509503c4ae031423", + "resourceName": null, + "resourceType": "AWS::EC2::Subnet", + "name": "Contains Subnet" + }, + { + "resourceId": "subnet-0b355a4852f60bc32", + "resourceName": null, + "resourceType": "AWS::EC2::Subnet", + "name": "Contains Subnet" + } + ], + "configuration": { + "cidrBlock": "10.45.0.0/16", + "dhcpOptionsId": "dopt-ab3b18cd", + "state": "available", + "vpcId": "vpc-0917d52ba4f3a6755", + "ownerId": "514712703977", + "instanceTenancy": "default", + "ipv6CidrBlockAssociationSet": [], + "cidrBlockAssociationSet": [ + { + "associationId": "vpc-cidr-assoc-0c82010c41a6e8208", + "cidrBlock": "10.45.0.0/16", + "cidrBlockState": { + "state": "associated", + "statusMessage": null + } + } + ], + "isDefault": false, + "tags": [ + { + "key": "Environment", + "value": "Demo" + }, + { + "key": "aws:cloudformation:stack-id", + "value": "arn:aws:cloudformation:us-west-2:514712703977:stack/PrivateVPCEndpointNetworkResources/43f43660-5181-11e9-80f8-0650fec6e554" + }, + { + "key": "Name", + "value": "Demo-VPC" + }, + { + "key": "aws:cloudformation:logical-id", + "value": "MyVPC" + }, + { + "key": "aws:cloudformation:stack-name", + "value": "PrivateVPCEndpointNetworkResources" + }, + { + "key": "Owner", + "value": "n1s3" + } + ] + }, + "supplementaryConfiguration": {}, + "tags": { + "aws:cloudformation:stack-name": "PrivateVPCEndpointNetworkResources", + "Owner": "n1s3", + "aws:cloudformation:stack-id": "arn:aws:cloudformation:us-west-2:514712703977:stack/PrivateVPCEndpointNetworkResources/43f43660-5181-11e9-80f8-0650fec6e554", + "Environment": "Demo", + "aws:cloudformation:logical-id": "MyVPC", + "Name": "Demo-VPC" + }, + "configurationItemVersion": "1.3", + "configurationItemCaptureTime": "2019-07-24T23:32:50.591Z", + "configurationStateId": 1564011170591, + "awsAccountId": "514712703977", + "configurationItemStatus": "OK", + "resourceType": "AWS::EC2::VPC", + "resourceId": "vpc-0917d52ba4f3a6755", + "resourceName": null, + "ARN": "arn:aws:ec2:us-west-2:514712703977:vpc/vpc-0917d52ba4f3a6755", + "awsRegion": "us-west-2", + "availabilityZone": "Multiple Availability Zones", + "configurationStateMd5Hash": "", + "resourceCreationTime": null + }, + "notificationCreationTime": "2019-12-09T18:46:24.499Z", + "messageType": "ConfigurationItemChangeNotification", + "recordVersion": "1.3" + }, + "resultToken": "eyJlbmNyeXB0ZWREYXRhIjpbLTQ2LDExOCw2MCwtMTI4LDYyLDg2LDUwLDEyMSwtMzUsNDMsLTY3LC03OSwtNzIsLTEwNywtODUsNTIsMTAsLTcyLC00MiwtMjYsNzksMzMsLTU5LDgwLDU2LC0yNiw5OSwtNDAsLTEyLC0xLC03MCwxOCwxOCwxMjUsLTQxLDc2LDMyLDU2LC0yNSw4OSw1NywtMTE5LC0xMDEsLTg0LC0zMCwtMzYsOTcsLTY2LDEwOSwtMTAyLC04NywtMTQsLTg5LDgwLC04MSwtMzEsNDMsLTYwLDI2LDk2LC02Miw5MSw3NSwtNDksLTE5LDQ3LC04MSwtMzcsNTAsLTQzLC0xMDEsLTUyLDQ4LC0xMjUsLTUxLC00OSwtOTIsODgsLTEwNiwtMTA5LC0zMSwtMTYsLTk1LDQwLC05NiwtODgsMjksLTcsLTM0LDM0LDU1LDU3LC04MCw0MCwtMjMsNjUsMzMsOTYsLTc1LDMsLTUzLC00NywtOTIsNjUsLTYyLC04NSw4NywtMTExLDUsLTM5LC00MywxMDgsLTk1LC0yNyw5NCw4NiwtOTgsMTI0LC0xOSw0OCw1LC0xNywtMTA1LC0xMDMsNTAsNTMsOTMsLTkzLDEwLC04OCwtMTExLC0xMjgsLTg2LC0xOCwtMTE0LDc4LDg2LDgyLDc4LC00NywxMCwtMTE0LDEsLTgwLC00Myw1MCwtNDQsNTEsMTIsOTQsNDEsLTU2LDEwMSwxNyw2MSwzMiwtMzgsLTg5LDM2LC0xMDIsMTgsLTUsLTg4LC0xMjUsNTksNTYsMTIwLC0xMiwtOCwyMCwtMjgsLTkzLC0xMTQsMTIxLC0xMDIsMTA3LDEyNSwtMyw3NywxMTIsLTc4LDM2LC05NCwtOTksMjEsNzcsNjAsLTIwLDk1LC0xMTcsNzcsMTIsNDMsLTEyMCw5MCw3Miw2OCw4MSwyMiw0MSw2MSw4OSwtOTIsNDMsLTc3LC01NiwtNiwtNjksLTkxLC0xNCwtODgsLTM1LDYwLC02NCwtMzIsLTMxLC03OSwtOTUsNTEsMjIsLTc0LC05MCw0OSwtNjQsLTc3LDExOCw1LDc0LDExNywtMTksMTEwLDEwMiwxMDcsLTQyLC0xMjgsNCwtODIsLTk1LC0xMTEsLTc5LC0zOCwtOTksLTYwLC0xMTgsLTEwMywtODUsLTc5LDUxLDk4LDYsNjksLTE5LC01NSwtNDAsLTYyLDgwLDMxLC0xMDUsLTMxLC0xMjAsMTQsNDMsLTEyNCwxMTUsMTIwLC02OCwxMTgsLTE1LC0xMSwtNjAsNzQsLTY2LDc5LDg5LDMyLDIyLC0xMCwxMjcsLTg0LC0xMjAsLTQ1LDE5LC03NiwtMTUsLTc4LC0xMTQsLTYyLC0xMDYsLTY2LDg4LC04MSwxLC00OSwyMCwxNCwxMTIsNDUsLTg0LDg1LDMyLDMzLDYsLTExOCw0NSwtNTcsNzAsMTIsNjIsOTIsLTYzLDMwLDkyLDQ4LC0xMTMsNDcsOTAsLTEyNywtNzgsLTc3LC0zMSwtNCwtMSwtNTEsLTI4LC0xNCwyMCw4NSwtMTYsLTg1LDM0LC04Miw3MCwtNjQsMTIwLDM1LDExNCwxMDksMTksMzksNDMsLTU5LC0xMjIsLTQ4LDgzLC04MiwxMTksLTQ5LDEwNiwtMTA5LC0zMiwtNDAsODMsLTE1LC0xMDgsMTUsMTcsMTA3LDQ4LC0yOSwxMjYsLTM3LC00Nyw0NCw4MCw1MCw3MywtMzgsLTIsLTExMCwxMDQsMTI2LDg5LC0zMSwtNTMsLTQ3LDI2LC0xMjcsLTEyNCwxMDIsMTA2LC05OSwtMTA0LC00MCwxMTksNCw2NywtODcsMTAzLDE1LC00OCwzNiwtMTAsMTExLC01NSwtOCw1MywtNDIsNzIsMzksLTExNSwtODUsMzUsLTg3LDU3LDUxLDExMiwtNzYsLTM4LDI5LDE4LC0zNiwtMTE0LC00OSwtNDUsLTk2LC0zMywyMywzNiwtNSw4MCwxMjQsLTExMSwtNDIsLTEyMiwzLDc4LDk5LC04NiwtNDQsLTEwMywtNzYsNDAsMzUsODksNDcsMTEyLC0zOSwtNzcsNzgsLTgxLDE0LC05MCw0MCw4OSw2MSw0Myw3MSwzMSwyMSwxMDAsMTA4LDEyMSwtMTE1LDEyNiw0Nyw4OCwtMSw5MCw0OSwzOCwtMTIyLC03NiwxMjAsLTYzLC00NCw0NCwtNTksOTMsOTksMTgsLTEyOCwtNTIsLTk0LDEwNiw5Myw3OCwtNjksLTI1LC05MSwxMTcsLTE1LC02MywxMjQsNjIsNjUsLTM4LDI1LDQ4LDQ4LC01OSwxMTgsLTM1LC0zNSw5LDExMywtNjYsOTAsMTAyLC01NywtMTE0LC00NywxMjMsLTIsLTUsMTA4LDkyLC05LC01NywtMTI2LC04LDQ2LDg0LC05MywxMjMsLTQ0LDgyLC0xMTEsLTY4LDksLTExNiwxMywtOTMsLTExMSw4OSwxMTIsLTYwLDQ0LDQ3LDExNiwxNCwtMTIsMTE3LC0xMTAsNzEsLTU3LDEwMSwtMjQsMywxMSwzNSwxOSwtNjksODksMTA3LC03OCwtMywtMTI3LC0xMDEsNTMsLTUzLC03NSw2LC0yNSw4Nyw5NSwtMTIsLTUyLDQsLTk5LDI4LC03NCwtMTIwLDk0LDk1LC0xMDcsLTQ0LC01MiwxMTgsNjgsLTQzLDEyMiw3Miw4OSwtNjEsLTEwNiw4NiwtMTA2LC04MSwxMDksLTExMCwtOTIsLTk2LDk3LDExNSwxMjcsOTMsLTExMywtNCw2NCw2MywtMTksLTEwOCwtOSwtNzQsMTIzLDc4LDExMSwtMTE4LC03MCw4LDI5LDkzLDk5LC02MiwtMzgsMCw3LDEwNywyNCw5MCw2NiwtNiwtNjUsNzgsLTIwLDczLC00NSwtNzgsLTQ5LDY0LC0zNSw5OSwtNDFdLCJtYXRlcmlhbFNldFNlcmlhbE51bWJlciI6MSwiaXZQYXJhbWV0ZXJTcGVjIjp7Iml2IjpbLTg3LC03Niw1Miw5MiwtNTEsLTU4LC0xMjgsLTE2LDQ5LC03Myw1NSw5NSwtOCwtOTksODcsLTEwMV19fQ==", + "eventLeftScope": "FALSE", + "ruleParameters": { + "executionRole": "arn:aws:iam::514712703977:role/VPCFlowLogS3EnforcementLambdaRole2", + "evaluationRole": "arn:aws:iam::514712703977:role/OrgConfigRuleEvalsRole2" + }, + "executionRoleArn": "arn:aws:iam::514712703977:role/StackSet-Enable-Config-893a20c2-6-rCfgRecorderRole-LBIOFOHCV5EK", + "accountId": "514712703977" +} \ No newline at end of file diff --git a/rules/VPC_FLOW_LOGS_TO_S3_CUSTOM/VPCFlowLogS3EnforcementLambda.py b/rules/VPC_FLOW_LOGS_TO_S3_CUSTOM/VPCFlowLogS3EnforcementLambda.py new file mode 100644 index 0000000..df90dd9 --- /dev/null +++ b/rules/VPC_FLOW_LOGS_TO_S3_CUSTOM/VPCFlowLogS3EnforcementLambda.py @@ -0,0 +1,139 @@ +# +# Custom AWS Config Rule - VPC Flow Logs +# + +import boto3, json + +def evaluate_compliance(config_item, r_id, creds): + """ + Assuming the given role-credentials... + Evaluates the VPC FlowLog compliance of the given resource id. + It's assumed the given resource id is a VPC. + Returns COMPLIANT if VPC FlowLogging correct. + Returns NON_COMPLIANT if VPC lowLogging incorrect and could not be corrected. + Otherwise, returns IT_BROKE. + """ + if (config_item['resourceType'] != 'AWS::EC2::VPC'): + return 'NOT_APPLICABLE' + ec2 = boto3.client('ec2', + aws_access_key_id=creds['AccessKeyId'], + aws_secret_access_key=creds['SecretAccessKey'], + aws_session_token=creds['SessionToken'], + ) + if is_flow_logs_enabled(r_id, ec2): + return 'COMPLIANT' + elif create_flow_logs(r_id, ec2): + return 'NON_COMPLIANT' + else: + return 'IT_BROKE' + +def is_flow_logs_enabled(vpc_id, ec2): + """ + Detects if the given VPC is FlowLogging into centralized S3 bucket. + Returns TRUE if LogDestination correct, otherwise, returns FALSE. + """ + response = ec2.describe_flow_logs( + Filter=[ + { + 'Name': 'resource-id', + 'Values': [ + vpc_id, + ], + }, + { + 'Name': 'log-destination-type', + 'Values': [ + 's3' + ], + }, + ], + ) + if len(response[u'FlowLogs']) > 0: + for i in response[u'FlowLogs']: + if i[u'LogDestination'] == 'arn:aws:s3:::pge-central-flowlogs': + return True + return False + +def create_flow_logs(vpc_id, ec2): + """ + Configures the given VPC to FlowLog into centralized S3 bucket. + Returns FALSE wth unsuccessful response, otherwise, returns TRUE. + """ + response = ec2.create_flow_logs( + ResourceIds=[ + vpc_id + ], + ResourceType='VPC', + TrafficType='ALL', + LogDestinationType='s3', + LogDestination='arn:aws:s3:::pge-central-flowlogs' + ) + return not bool(response[u'Unsuccessful']) + +def assume_role(role_to_assume_arn,role_session_name='vpcflowlog2S3session'): + """ + Fetches sts credentials for the given role arn. + Optionally, client can name the boto3 session. + Returns sts credentials. + """ + print "assume_role("+role_to_assume_arn+','+role_session_name+')' + # Create IAM client + sts_default_provider_chain = boto3.client('sts') + + print('Default Provider Identity: : ' + sts_default_provider_chain.get_caller_identity()['Arn']) + + response=sts_default_provider_chain.assume_role( + RoleArn=role_to_assume_arn, + RoleSessionName=role_session_name + ) + + creds=response['Credentials'] + # Test the credentials + sts_assumed_role = boto3.client('sts', + aws_access_key_id=creds['AccessKeyId'], + aws_secret_access_key=creds['SecretAccessKey'], + aws_session_token=creds['SessionToken'], + ) + print('AssumedRole Identity: ' + sts_assumed_role.get_caller_identity()['Arn']) + return creds + + +def lambda_handler(event, context): + """ + The infamous entry point to this function, lambda_handler. + This funk is intended to run from a centralized account. + This funk assumes executionRole to evaluate VPC FlowLog configuration compliance. + It puts evaluation into configservice of the given evaluationRole. + :param event: Assumes triggering event to be a dict, ConfigurationItemChangeNotification. + :param context: AWS Lambda uses this parameter to provide runtime information. + :return: nothing. + """ + print(event) + invoking_event = json.loads(event['invokingEvent']) + compliance_value = 'NOT_APPLICABLE' + account_id = json.loads(event['accountId']) + resource_id = invoking_event['configurationItem']['resourceId'] + print "resource id="+resource_id + ruleParameters = json.loads(event['ruleParameters']) + # Assume the role passed from the managed-account + executionCredentials = assume_role('arn:aws:iam::'+account_id+':role/VPCFlowLogS3EnforcementLambdaRole2') + compliance_value = evaluate_compliance(invoking_event['configurationItem'], resource_id, executionCredentials) + print "compliance-result="+compliance_value + # Assume the role passed from the managed-account + evaluationCredentials = assume_role('arn:aws:iam::'+account_id+':role/OrgConfigRuleEvalsRole2') + # Create AWS SDK clients & initialize custom rule parameters + config = boto3.client('config', + aws_access_key_id=executionCredentials['AccessKeyId'], + aws_secret_access_key=executionCredentials['SecretAccessKey'], + aws_session_token=executionCredentials['SessionToken'], + ) + response = config.put_evaluations( + Evaluations=[ + { + 'ComplianceResourceType': invoking_event['configurationItem']['resourceType'], + 'ComplianceResourceId': resource_id, + 'ComplianceType': compliance_value, + 'OrderingTimestamp': invoking_event['notificationCreationTime'] + }, + ], + ResultToken=event['resultToken']) diff --git a/scripts/requirements.txt b/rules/requirements.txt similarity index 100% rename from scripts/requirements.txt rename to rules/requirements.txt diff --git a/scripts/sync-catalog.py b/scripts/sync-catalog.py deleted file mode 100755 index 418c419..0000000 --- a/scripts/sync-catalog.py +++ /dev/null @@ -1,602 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -from __future__ import print_function -import yaml -import json -import boto3 -import traceback -from boto3.session import Session -import zipfile -import botocore -import uuid -import os -import datetime - -code_pipeline = boto3.client('codepipeline') -sts_client = boto3.client('sts') -accountid = sts_client.get_caller_identity()["Account"] -client = boto3.client('servicecatalog') - - -def handler(event, context): - """ Main method controlling the sequence of actions as follows - 1. Get JobID from input - 2. Get Job Data from input - 3. Get Artifact data from input - 4. Setup S3 Client - 5. Sync Codebase with Service Catalog - :param event: Input json from code pipeline, containing job id and input artifacts - :param context: Not used, but required for Lambda function - :return: None - :exception: Any exception - """ - print(event) - try: - job_id = event['CodePipeline.job']['id'] - job_data = event['CodePipeline.job']['data'] - artifact_data = job_data['inputArtifacts'][0] - s3 = setup_s3_client() - sync_service_catalog(s3, artifact_data) - put_job_success(job_id, "Success") - - - except Exception as e: - print('Function failed due to exception.') - print(e) - traceback.print_exc() - put_job_failure(job_id, 'Function exception: ' + str(e)) - - -def sync_service_catalog(s3, artifact): - """ Pseudo logic as follows - 1. Extract S3 Zip file - 2. Iterate through all the folders starting with portfolio- - 3. Read mapping.yaml in each such folder. Refer Readme for more details on syntax - 4. If portfolio name matches, update the products by creating a new version - 5. If portfolio name does not matches, create a new one. - 6. Share the portfolio with list of accounts mentioned in the mapping.yaml - 7. Give access to the principals mentioned in the mapping.yaml - 8. Tag Portfolio as mentioned in mapping.yaml - - :param s3: S3 Boto3 client - :param artifact: Artifact object sent by codepipeline - :return: None - """ - bucket = artifact['location']['s3Location']['bucketName'] - key = artifact['location']['s3Location']['objectKey'] - tmp_file = '/tmp/' + str(uuid.uuid4()) - s3.download_file(bucket, key, tmp_file) - with zipfile.ZipFile(tmp_file, 'r') as zip: - zip.extractall('/tmp/') - print('Extract Complete') - for folder in os.listdir('/tmp'): - print('Checking object = ' + folder) - if os.path.isdir("/tmp/" + folder): - print('Found ' + folder + ' as folder') - if str(folder).startswith('portfolio-'): - print('Found ' + folder + ' as folder starting with portfolio') - for mappingfile in os.listdir("/tmp/" + folder): - print('Found ' + mappingfile + ' inside folder ' + folder) - if str(mappingfile).endswith('mapping.yaml'): - print('Working with ' + mappingfile + ' inside folder ' + folder) - with open(("/tmp/" + str(folder) + "/" + str(mappingfile)), 'r') as stream: - objfile = yaml.load(stream) - # objfile = json.loads("/tmp/"+folder+"/"+mappingfile) - print('Loaded JSON=' + str(objfile)) - lst_portfolio = list_portfolios() - lst_portfolio_name = [] - obj_portfolio = {} - for portfolio in lst_portfolio: - if portfolio['DisplayName'] not in lst_portfolio_name: - lst_portfolio_name.append(portfolio['DisplayName']) - if objfile['name'] in lst_portfolio_name: - print('PORTFOLIO Match found.Checking Products now.') - for item in lst_portfolio: - if item['DisplayName'] == objfile['name']: - portfolio_id = item['Id'] - obj_portfolio = item - update_portfolio(obj_portfolio, objfile, bucket) - remove_principal_with_portfolio(obj_portfolio['Id']) - associate_principal_with_portfolio(obj_portfolio, objfile) - lst_products = list_products_for_portfolio(portfolio_id) - lst_products_name = [] - for products in lst_products: - lst_products_name.append(products['Name']) - for productsInFile in objfile['products']: - if productsInFile['name'] in lst_products_name: - s3key = 'sc-templates/' + productsInFile['name'] + '/templates/' + str( - uuid.uuid4()) + '.yaml' - for ids in lst_products: - if ids['Name'] == productsInFile['name']: - productid = ids['ProductId'] - s3.upload_file( - '/tmp/' + str(folder) + "/" + productsInFile['template'], - bucket, s3key) - create_provisioning_artifact(productsInFile, productid, bucket + "/" + s3key) - else: - s3key = 'sc-templates/' + productsInFile['name'] + '/templates/' + str( - uuid.uuid4()) + '.yaml' - s3.upload_file( - '/tmp/' + str(folder) + "/" + productsInFile['template'], - bucket, - s3key) - create_product(productsInFile, portfolio_id, bucket + "/" + s3key) - else: - print('NO PORTFOLIO Match found.Creating one...') - create_portfolio_response = create_portfolio(objfile, bucket) - portfolioid = create_portfolio_response['PortfolioDetail']['Id'] - associate_principal_with_portfolio(create_portfolio_response['PortfolioDetail'], objfile) - for productsInFile in objfile['products']: - s3key = 'sc-templates/' + productsInFile['name'] + '/templates/' + str( - uuid.uuid4()) + '.yaml' - s3.upload_file( - '/tmp/' + str(folder) + "/" + productsInFile['template'], bucket, - s3key) - create_product(productsInFile, portfolioid, bucket + "/" + s3key) - - -def update_portfolio(objPortfolio, objMappingFile, bucket): - """ Pseudo code as - 1. Make describe_portfolio call - 2. Call update_portfolio to remove all tags and sync Description and ProviderName as mentioned in mapping file object - 3. Call update_portfolio to add the tags as mentioned in mapping file object - 4. Modify Bucket policy to sync access with the accountnumber - 5. Share portfolio with the accounts mentioned in the mapping file object - 6. Remove portfolio access from the accounts NOT mentioned in the mapping file object - - :param objPortfolio: Portfolio Object as retrieved from boto3 call - :param objMappingFile: mapping.yaml file object - :param bucket: S3 Bucket - :return: - """ - describe_portfolio = client.describe_portfolio(Id=objPortfolio['Id']) - objTags = [] - for tag in describe_portfolio['Tags']: - if tag['Key'] not in objTags: - objTags.append(tag['Key']) - - client.update_portfolio( - Id=objPortfolio['Id'], - Description=objMappingFile['description'], - ProviderName=objMappingFile['owner'], - RemoveTags=objTags - ) - client.update_portfolio( - Id=objPortfolio['Id'], - AddTags=objMappingFile['tags'] - ) - bucket_policy = get_bucket_policy(bucket) - policy = json.loads(bucket_policy['Policy']) - statements = policy['Statement'] - objAccounts = [] - for account in objMappingFile['accounts']: - if check_if_account_is_integer(account['number']) and str(account['number']) != accountid: - objAccounts.append(str(account['number'])) - accounts_to_add = get_accounts_to_append(statements, objAccounts, bucket) - if accounts_to_add: - statements.append(create_policy(accounts_to_add, bucket)) - policy['Statement'] = statements - put_bucket_policy(json.dumps(policy), bucket) - share_portfolio(objAccounts, objPortfolio['Id']) - remove_portfolio_share(objAccounts, objPortfolio['Id']) - - -def associate_principal_with_portfolio(objPortfolio, objMappingFile): - """ Grants access to the Roles/Users as mentioned in the mappings object - - :param objPortfolio: Portfolio Object as retrieved from boto3 call - :param objMappingFile: mapping.yaml file object - :return: None - """ - if len(objMappingFile['principals']) > 0: - for principalarn in objMappingFile['principals']: - client.associate_principal_with_portfolio( - PortfolioId=objPortfolio['Id'], - PrincipalARN="arn:aws:iam::"+accountid+":"+str(principalarn), - PrincipalType='IAM' - ) - - - -def remove_principal_with_portfolio(id): - - """ Removes access to the Roles/Users as mentioned in the mappings object - - :param objPortfolio: Portfolio Object as retrieved from boto3 call - :param objMappingFile: mapping.yaml file object - :return: None - """ - - list_principals = client.list_principals_for_portfolio( - PortfolioId=id - ) - for principal in list_principals['Principals']: - client.disassociate_principal_from_portfolio( - PortfolioId=id, - PrincipalARN=principal['PrincipalARN'] - ) - - -def list_portfolios(): - """ - :return: List of Portfolios in the account - """ - nextmarker = None - done = False - client = boto3.client('servicecatalog') - lst_portfolio = [] - - while not done: - if nextmarker: - portfolio_response = client.list_portfolios(nextmarker=nextmarker) - else: - portfolio_response = client.list_portfolios() - - for portfolio in portfolio_response['PortfolioDetails']: - lst_portfolio.append(portfolio) - - if 'NextPageToken' in portfolio_response: - nextmarker = portfolio_response['NextPageToken'] - else: - break - return lst_portfolio - - -def list_products_for_portfolio(id): - """ - - :param id: portfolio id - :return: List of products associated with the portfolio - """ - nextmarker = None - done = False - client = boto3.client('servicecatalog') - lst_products = [] - - while not done: - if nextmarker: - product_response = client.search_products_as_admin(nextmarker=nextmarker, PortfolioId=id) - else: - product_response = client.search_products_as_admin(PortfolioId=id) - - for product in product_response['ProductViewDetails']: - lst_products.append(product['ProductViewSummary']) - - if 'NextPageToken' in product_response: - nextmarker = product_response['NextPageToken'] - else: - break - return lst_products - - -def create_product(objProduct, portfolioid, s3objectkey): - """ - - :param objProduct: Product object to be created. has all the mandatory details for product creation - :param portfolioid: Portfolio ID with which the newly created product would be associated with - :param s3objectkey: S3Object Key, which has the cloudformation template for the product - :return: None - """ - client = boto3.client('servicecatalog') - create_product_response = client.create_product( - Name=objProduct['name'], - Owner=objProduct['owner'], - Description=objProduct['description'], - SupportEmail=objProduct['owner'], - ProductType='CLOUD_FORMATION_TEMPLATE', - ProvisioningArtifactParameters={ - 'Name': 'InitialCreation', - 'Description': 'InitialCreation', - 'Info': { - 'LoadTemplateFromURL': 'https://s3.amazonaws.com/' + s3objectkey - }, - 'Type': 'CLOUD_FORMATION_TEMPLATE' - }, - IdempotencyToken=str(uuid.uuid4()) - ) - - response = client.associate_product_with_portfolio( - ProductId=create_product_response['ProductViewDetail']['ProductViewSummary']['ProductId'], - PortfolioId=portfolioid - ) - - -def create_provisioning_artifact(objProduct, productid, s3objectkey): - """ - - :param objProduct: Product object for which the provisioning artifact (version of the product) will be created. has all the mandatory details for product. - :param productid: Product ID - :param s3objectkey: S3Object Key, which has the cloudformation template for the product - :return: None - """ - client = boto3.client('servicecatalog') - response = client.create_provisioning_artifact( - ProductId=productid, - Parameters={ - 'Name': str(uuid.uuid4()), - 'Description': str(datetime.datetime.now()), - 'Info': { - 'LoadTemplateFromURL': 'https://s3.amazonaws.com/' + s3objectkey - }, - 'Type': 'CLOUD_FORMATION_TEMPLATE' - }, - IdempotencyToken=str(uuid.uuid4()) - ) - - -def create_portfolio(objMappingFile, bucket): - """ - - :param objMappingFile: Object of the mapping file - :param bucket: BucketName which holds the cloudformation templates for the products - :return: Response of Create portfolio share API call - """ - client = boto3.client('servicecatalog') - response = client.create_portfolio( - DisplayName=objMappingFile['name'], - Description=objMappingFile['description'], - ProviderName=objMappingFile['owner'], - IdempotencyToken=str(uuid.uuid4()), - Tags=objMappingFile['tags'] - ) - - bucket_policy = get_bucket_policy(bucket) - policy = json.loads(bucket_policy['Policy']) - statements = policy['Statement'] - objAccounts = [] - for account in objMappingFile['accounts']: - if check_if_account_is_integer(account['number']) and str(account['number']) != accountid: - objAccounts.append(str(account['number'])) - accounts_to_add = get_accounts_to_append(statements, objAccounts, bucket) - if accounts_to_add: - statements.append(create_policy(accounts_to_add, bucket)) - policy['Statement'] = statements - put_bucket_policy(json.dumps(policy), bucket) - share_portfolio(objAccounts, response['PortfolioDetail']['Id']) - remove_portfolio_share(objAccounts, response['PortfolioDetail']['Id']) - return response - - -def remove_portfolio_share(lst_accounts, portfolioid): - """ Removes the portfolio share - :param lst_accounts: list of accounts to remove - :param portfolioid: portfolio id from which to remove the share - :return: None - """ - lst_privledged_accounts = list_portfolio_shares(portfolioid) - for account in lst_privledged_accounts: - if account not in lst_accounts: - client.delete_portfolio_share( - PortfolioId=portfolioid, - AccountId=account - ) - if not lst_privledged_accounts: - for account in lst_accounts: - client.delete_portfolio_share( - PortfolioId=portfolioid, - AccountId=account - ) - - -def list_portfolio_shares(portfolioid): - """ Lists the shares for the specified portfolio - :param portfolioid: portfolio id to list the shares - :return: List of accounts with which the portfolio is already shared with - """ - nextmarker = None - done = False - lst_privledged_accounts = [] - client = boto3.client('servicecatalog') - - while not done: - if nextmarker: - lst_portfolio_access = client.list_portfolio_access(nextmarker=nextmarker, PortfolioId=portfolioid) - else: - lst_portfolio_access = client.list_portfolio_access(PortfolioId=portfolioid) - - for accounts in lst_portfolio_access['AccountIds']: - lst_privledged_accounts.append(accounts) - - if 'NextPageToken' in lst_portfolio_access: - nextmarker = lst_portfolio_access['NextPageToken'] - else: - break - print(lst_privledged_accounts) - return lst_privledged_accounts - - -def share_portfolio(lst_accounts, portfolioid): - """ Shares the portfolio with the specified account ids - :param lst_accounts: list of accounts to share the portfolio with - :param portfolioid: portfolio id - :return: None - """ - lst_privledged_accounts = list_portfolio_shares(portfolioid) - if lst_accounts: - for account in lst_accounts: - if account not in lst_privledged_accounts: - client.create_portfolio_share( - PortfolioId=portfolioid, - AccountId=str(account) - ) - - -def get_bucket_policy(s3bucket): - """ Gets S3 Bucket policy - :param s3bucket: S3 bucket to get the policy - :return: Bucket Policy Object - """ - s3_client = boto3.client('s3') - try: - bucket_policy = s3_client.get_bucket_policy(Bucket=s3bucket) - except: - bucket_policy = {u'Policy': u'{"Version":"2012-10-17","Id":"Default-Policy",' - u'"Statement":[{"Sid":"' + str(uuid.uuid4()) + - u'","Effect":"Allow",' - u'"Principal":{"AWS":"arn:aws:iam::' + accountid + - u':root"},"Action":"s3:GetObject",' - u'"Resource":"arn:aws:s3:::' + s3bucket + - u'/*"}]}' - } - return bucket_policy - - -def create_policy(accountid, s3bucket): - """ Creates the S3 Bucket policy and grants access to the specified accounts with which the portfolio is being shared - :param accountid: AWS account id - :param s3bucket: S3 Bucket - :return: Built S3 Policy Object - """ - policy = { - "Sid": str(uuid.uuid1()), - "Effect": "Allow", - "Principal": { - "AWS": accountid - }, - "Action": "s3:GetObject", - "Resource": [ - "arn:aws:s3:::" + s3bucket + "/sc-templates/*" - ] - } - return policy - - -def get_accounts_to_append(statements, lstaccounts, s3bucket): - """ Gets the accounts to append to the S3 Bucket policy - :param statements: Existing statements - :param lstaccounts: List of AWS accounts - :param s3bucket: S3 Bucket - :return: Object of principals to append in S3 policy - """ - objPrincipals = [] - boolmatchfolder = False - - for statement in statements: - if type(statement['Resource']) is unicode: - if statement['Resource'] == "arn:aws:s3:::" + s3bucket + "/sc-templates/*": - boolmatchfolder = True - else: - for folder in statement['Resource']: - if folder == "arn:aws:s3:::" + s3bucket + "/sc-templates/*": - boolmatchfolder = True - - if boolmatchfolder: - objexistingprincipals = [] - - for statement in statements: - if type(statement['Resource']) is unicode: - if statement['Resource'] == "arn:aws:s3:::" + s3bucket + "/sc-templates/*": - if type(statement['Principal']['AWS']) is unicode: - if statement['Principal']['AWS'] not in objexistingprincipals: - objexistingprincipals.append(statement['Principal']['AWS']) - else: - for arn in statement['Principal']['AWS']: - if arn not in objexistingprincipals: - objexistingprincipals.append(arn) - - else: - for folder in statement['Resource']: - if folder == "arn:aws:s3:::" + s3bucket + "/sc-templates/*": - for arn in statement['Principal']['AWS']: - if arn not in objexistingprincipals: - objexistingprincipals.append(arn) - for account in lstaccounts: - if ("arn:aws:iam::" + str(account) + ":root") not in objexistingprincipals: - if "arn:aws:iam::" + str(account) + ":root" not in objPrincipals: - objPrincipals.append("arn:aws:iam::" + str(account) + ":root") - else: - for account in lstaccounts: - if "arn:aws:iam::" + str(account) + ":root" not in objPrincipals: - objPrincipals.append("arn:aws:iam::" + str(account) + ":root") - - return objPrincipals - - -def put_bucket_policy(policy, s3bucket): - """ Puts the created S3 Bucket policy - :param policy: Input Policy - :param s3bucket: S3 Bucket - :return: None - """ - s3_client = boto3.client('s3') - s3_client.put_bucket_policy( - Bucket=s3bucket, - Policy=policy - ) - - -def check_if_account_is_integer(string): - """ Checks if the account number is an integer - - :param string: input string - :return: Boolean, if the input is integer - """ - - try: - int(string) - return True - except ValueError: - return False - - -def setup_s3_client(): - """ - :return: Boto3 S3 session. Uses IAM credentials - """ - session = Session() - return session.client('s3', config=botocore.client.Config(signature_version='s3v4')) - - -def put_job_success(job, message): - """Notify CodePipeline of a successful job - - Args: - job: The CodePipeline job ID - message: A message to be logged relating to the job status - - Raises: - Exception: Any exception thrown by .put_job_success_result() - - """ - print('Putting job success') - print(message) - code_pipeline.put_job_success_result(jobId=job) - - -def put_job_failure(job, message): - """Notify CodePipeline of a failed job - - Args: - job: The CodePipeline job ID - message: A message to be logged relating to the job status - - Raises: - Exception: Any exception thrown by .put_job_failure_result() - - """ - print('Putting job failure') - print(message) - code_pipeline.put_job_failure_result(jobId=job, failureDetails={'message': message, 'type': 'JobFailed'}) - - -def get_user_params(job_id, job_data): - """ Gets User parameters from the input job id and data , sent from codepipeline - :param job_id: Job ID - :param job_data: Job data sent from codepipeline - :return: Parameters sent from codepipeline - :exception: Call put_job_failure to send failure to codepipeline - """ - try: - user_parameters = job_data['actionConfiguration']['configuration']['UserParameters'] - decoded_parameters = json.loads(user_parameters) - print(decoded_parameters) - except Exception as e: - put_job_failure(job_id, e) - raise Exception('UserParameters could not be decoded as JSON') - - return decoded_parameters - - -if __name__ == "__main__": - handler(None, None)