hashicorp / terraform-provider-aws

The AWS Provider enables Terraform to manage AWS resources.
https://registry.terraform.io/providers/hashicorp/aws
Mozilla Public License 2.0
9.82k stars 9.16k forks source link

[Enhancement]: added LOGS #28734

Closed kiran-113 closed 1 year ago

kiran-113 commented 1 year ago

Description

Hi, When ever i applied terraform apply command, the resources configured through cloud formation template are destroying and re creating, Please review and do enhancements .

Affected Resource(s) and/or Data Source(s)

CFT template

Potential Terraform Configuration

resource "aws_cloudformation_stack" "checkpoint_tgw_cloudformation_stack" {
  name = "aws-inspector2-cft"

  tags = {
      Environment = "Test"
      Name        = "Provider Tag"
   }

  parameters = {
    RemediateInspectorFindingCustomActionNoRBTArn = aws_securityhub_action_target.InspecorRemNoRBT.arn
    RemediateInspectorFindingCustomActionRBTArn =  aws_securityhub_action_target.InspecorRemRBT.arn

  }

  template_body      = "${file("${path.module}/resolveInspectorFindingsCFN.yaml")}"
  capabilities       = ["CAPABILITY_IAM"]
  disable_rollback   = true
  timeout_in_minutes = 50

  }

  output "stack_outputs" {
  description = "Outputs from the Stack execution"
  value       = aws_cloudformation_stack.checkpoint_tgw_cloudformation_stack.outputs
}

References

--- CFT TEMPLATE I USED
AWSTemplateFormatVersion: '2010-09-09'
Description: WARNING This template creates AWS Systems Manager Automation Runbooks, EventBridge rules, and a SecurityHub custom action to resolve Inspector findings. You will be billed for the AWS resources used if you create a stack from this template
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Optional parameter
        Parameters:
          - OrganizationId
      - Label:
          default: Parameters for creating SecurityHub Custom Actions
        Parameters:
          - RemediateInspectorFindingCustomActionNoRBTArn
          - RemediateInspectorFindingCustomActionRBTArn
Parameters:
  RemediateInspectorFindingCustomActionNoRBTArn:
    Type: String
    Default: arn:aws:securityhub:<Region>:<AWSAccountNumber>:action/custom/<CustomActionId>
    Description: ARN of the SecurityHub Custom Action for remediating Amazon Inspector Findings WITHOUT EC2 Instance reboot
  RemediateInspectorFindingCustomActionRBTArn:
    Type: String
    Default: arn:aws:securityhub:<Region>:<AWSAccountNumber>:action/custom/<CustomActionId>
    Description: ARN of the SecurityHub Custom Action for remediating Amazon Inspector Findings WITH EC2 Instance reboot
  OrganizationId:
    Description: (Leave it empty for standalone account) AWS Organization ID used for S3 bucket policy to allow target accounts to retrieve the install override list.
    Type: String
    Default: ""
Conditions:
  IsOrganizationIdEmpty:  !Equals [!Ref "OrganizationId", ""]
  IsOrganizationIdNotEmpty: !Not [Condition: IsOrganizationIdEmpty]

Resources:

  #-------------------------------------------------
  # EventBridge rule and IAM role to invoke the Automation runbook
  #-------------------------------------------------
  EventBridgeRolePolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action:
          - ssm:StartAutomationExecution
          Resource:
            - Fn::Sub: arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${ResolveInspectorFindingsRunbook}:$DEFAULT
        - Action: iam:PassRole
          Resource: !GetAtt AutomationIAMRole.Arn
          Condition:
            StringEquals:
              iam:PassedToService: ssm.amazonaws.com
          Effect: Allow
        Version: 2012-10-17
      PolicyName: !Sub EventBridge-Inspector-${AWS::StackName}
      Roles:
        - !Ref EventBridgeIAMRole

  EventBridgeIAMRole:
    Type: AWS::IAM::Role
    Properties:
      Description: Event Bridge role to invoke the Inspector Automation runbook
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - events.amazonaws.com
          Action: sts:AssumeRole

  EventBridgeRuleNoRBT:
    Type: AWS::Events::Rule
    Properties:
      EventBusName: default
      EventPattern:
        source:
        - aws.securityhub
        detail-type:
        - Security Hub Findings - Custom Action
        resources:
        - !Ref RemediateInspectorFindingCustomActionNoRBTArn
      Name: resolveInspectorFindings-withNoReboot
      State: ENABLED
      Targets:
      - Id: Idbb1a6e56-c229-4574-8e8f-9ea42afac0e6
        Arn: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${ResolveInspectorFindingsRunbook}:$DEFAULT
        RoleArn: !GetAtt EventBridgeIAMRole.Arn
        InputTransformer:
          InputPathsMap:
            inspectorFindingArn: "$.detail.findings[0].Id"
            instanceId: "$.detail.findings[0].Resources[0].Id"
            source: "$.source"
            sourceAwsAccountId: "$.detail.findings[0].AwsAccountId"
            sourceRegion: "$.detail.findings[0].Resources[0].Region"
          InputTemplate: !Sub
            - |
              {
                "AutomationAssumeRole": ["${AutomationIAMAssumeRole}"],
                "InspectorFindingArn":[<inspectorFindingArn>],
                "InstanceId":[<instanceId>],
                "RebootOption": ["NoReboot"],
                "Source":[<source>],
                "SourceAwsAccountId":[<sourceAwsAccountId>],
                "SourceRegion":[<sourceRegion>]
              }
            - AutomationIAMAssumeRole: !GetAtt AutomationIAMRole.Arn

  EventBridgeRuleRBT:
    Type: AWS::Events::Rule
    Properties:
      EventBusName: default
      EventPattern:
        source:
        - aws.securityhub
        detail-type:
        - Security Hub Findings - Custom Action
        resources:
        - !Ref RemediateInspectorFindingCustomActionRBTArn
      Name: resolveInspectorFindings-withReboot
      State: ENABLED
      Targets:
      - Id: Idbb1a6e56-c229-4574-8e8f-9ea42afac0e6
        Arn: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${ResolveInspectorFindingsRunbook}:$DEFAULT
        RoleArn: !GetAtt EventBridgeIAMRole.Arn
        InputTransformer:
          InputPathsMap:
            inspectorFindingArn: "$.detail.findings[0].Id"
            instanceId: "$.detail.findings[0].Resources[0].Id"
            source: "$.source"
            sourceAwsAccountId: "$.detail.findings[0].AwsAccountId"
            sourceRegion: "$.detail.findings[0].Resources[0].Region"
          InputTemplate: !Sub
            - |
              {
                "AutomationAssumeRole": ["${AutomationIAMAssumeRole}"],
                "InspectorFindingArn":[<inspectorFindingArn>],
                "InstanceId":[<instanceId>],
                "RebootOption": ["RebootIfNeeded"],
                "Source":[<source>],
                "SourceAwsAccountId":[<sourceAwsAccountId>],
                "SourceRegion":[<sourceRegion>]
              }
            - AutomationIAMAssumeRole: !GetAtt AutomationIAMRole.Arn
  #-------------------------------------------------
  # Automation IAM role to invoke the two Automation runbooks
  #-------------------------------------------------
  AutomationRolePolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action:
          - inspector2:ListFindings
          - securityHub:GetFindings
          - ssm:GetAutomationExecution
          Resource: '*'
        - Effect: Allow
          Action:
          - s3:PutObject
          Resource: !Join [ '', [!GetAtt InstallOverrideListBucket.Arn, '/*'] ]
        - Effect: Allow
          Action:
          - ssm:StartAutomationExecution
          Resource:
            - Fn::Sub: arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${AutomationRunPatchBaselineRunbook}:$DEFAULT
        - Action: iam:PassRole
          Resource:
            !GetAtt AutomationIAMRole.Arn
          Effect: Allow
        - Action: sts:AssumeRole
          Resource:
            Fn::Sub: arn:${AWS::Partition}:iam::*:role/resolveInspectorExecutionRole
          Effect: Allow
        - Action: organizations:ListAccountsForParent
          Resource: '*'
          Effect: Allow
        Version: 2012-10-17
      PolicyName: !Sub Automation-Inspector-${AWS::StackName}
      Roles:
        - !Ref AutomationIAMRole

  AutomationIAMRole:
    Type: AWS::IAM::Role
    Properties:
      Description: Automation IAM role to resolve Inspector findings
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ssm.amazonaws.com
          Action: sts:AssumeRole

  #-------------------------------------------------
  # Bucket used to store install override list
  #-------------------------------------------------
  InstallOverrideListBucket:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: BucketOwnerFullControl
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  #-------------------------------------------------
  # Bucket policy to allow retrieval of install override list from accounts within the AWS Organization
  #-------------------------------------------------
  InstallOverrideListBucketPolicyWithOrg:
    Type: AWS::S3::BucketPolicy
    Condition: IsOrganizationIdNotEmpty
    Properties:
      Bucket: !Ref InstallOverrideListBucket
      PolicyDocument:
        Statement:
        - Sid: SSMGetObject
          Effect: Allow
          Principal: "*"
          Action:
            - s3:GetObject
            - s3:PutObject
            - s3:PutObjectAcl
          Resource:
            - !Join [ '', [!GetAtt InstallOverrideListBucket.Arn, '/*'] ]
          Condition:
            StringEquals:
              aws:PrincipalOrgID: !Ref OrganizationId

  InstallOverrideListBucketPolicyWithoutOrg:
    Type: AWS::S3::BucketPolicy
    Condition: IsOrganizationIdEmpty
    Properties:
      Bucket: !Ref InstallOverrideListBucket
      PolicyDocument:
        Statement:
        - Sid: SSMGetObject
          Effect: Allow
          Principal: "*"
          Action:
            - s3:GetObject
            - s3:PutObject
            - s3:PutObjectAcl
          Resource:
            - !Join [ '', [!GetAtt InstallOverrideListBucket.Arn, '/*'] ]
          Condition:
            StringEquals:
              aws:PrincipalAccount: !Ref AWS::AccountId
  #-------------------------------------------------
  # Automation runbooks to create an install override list and patch target instance
  #-------------------------------------------------
  AutomationRunPatchBaselineRunbook:
    Type: AWS::SSM::Document
    Properties:
      DocumentType: Automation
      DocumentFormat: YAML
      TargetType: /AWS::EC2::Instance
      Content:
        description: |-
          # AWS Systems Manager Automation runbook to run a Patch Baseline operation with an Install Override List
          ## Summary
          This runbook installs specific updates on a target managed instance using an install override list. This runbook is intended to be used in conjunction with `ResolveInspectorFindingsRunbook` to resolve Inspector findings.
          The runbook includes four steps:
          1. Check for any in progress patching operations
          1. Initiate an Install operation for `AWS-RunPatchBaseline` using an `InstallOverrideList`
          1. Query the target managed instance's list of associations for `AWS-GatherSoftwareInventory`
          1. Immediately reapply the association for `AWS-GatherSoftwareInventory`
          ## Pre-requisites
          To use this runbook, provide an `InstallOverrideList` with the specific updates to be installed on the target managed node.
          Additionally, you must provide an Automation assume role that will be used by the runbook to resolve Inspector findings. This role must have permissions to list and send Run Command tasks and describe managed instance State Manager associations.
        schemaVersion: '0.3'
        assumeRole: '{{AutomationAssumeRole}}'
        parameters:
          AutomationAssumeRole:
            type: 'AWS::IAM::Role::Arn'
            description: (Required) The ARN of the role that allows Automation to perform the actions on your behalf.
            default: !GetAtt AutomationIAMRole.Arn
          IncludeKernelUpdates:
            type: Boolean
            description: (Required) Choose to include kernel updates for patch installation. Valid values are 'true' and 'false'.
            default: false
            allowedValues:
            - true
            - false
          InspectorFindingArn:
            type: String
            description: (Optional) The ARN of the Inspector finding.
            default: ''
          InstanceIds:
            type: 'StringList'
            description: (Optional) The instance IDs to target.
          Operation:
            type: String
            default: Install
            allowedValues:
            - Install
            description: >-
              (Required) The update or configuration to perform on the instance. The
              system checks if patches specified in the patch baseline are installed on
              the instance. The install operation installs patches missing from the
              baseline.
          RebootOption:
            type: String
            default: RebootIfNeeded
            allowedValues:
            - RebootIfNeeded
            - NoReboot
            description: >-
              (Optional) Reboot behavior after a patch Install operation. If you choose
              NoReboot and patches are installed, the instance is marked as non-compliant
              until a subsequent reboot and scan.
          S3BucketName:
            type: 'AWS::S3::Bucket::Name'
            description: (Required) The name of the S3 bucket to store the install override list.
            default: !Ref InstallOverrideListBucket
          S3BucketKey:
            type: String
            description: (Required) The S3 key path to store the install override list.
            default: inspector/installOverrideList/
          SeverityFilter:
            type: String
            description: (Optional) The severity to filter for Inspector findings.
            default: 'NONE'
            allowedValues:
              - NONE
              - UNTRIAGED
              - INFORMATIONAL
              - LOW
              - MEDIUM
              - HIGH
              - CRITICAL
          Source:
            type: String
            description: (Required) The source for the event.
            default: aws.ssm.automation
            allowedValues:
              - aws.ssm.automation
              - aws.securityhub
        mainSteps:
          - name: checkForInProgressPatchingTasks
            action: 'aws:executeScript'
            description: >-
              This action checks for any in-progress commands for ```AWS-RunPatchBaseline``` on the target managed node.
            inputs:
              Runtime: python3.8
              Handler: script_handler
              InputPayload:
                instanceId: '{{ InstanceIds }}'
              Script: |-
                import boto3
                import sys
                import logging
                logger = logging.getLogger()
                logger.setLevel(logging.INFO)
                ssm = boto3.client('ssm')
                def list_in_progress_commands(instanceId):
                  try:
                    logger.info('Checking for in progress patching operations on: {}'.format(instanceId))
                    #Check for in progress commands for AWS-RunPatchBaseline for this instance
                    inProgressCommandResponse = ssm.list_commands(
                      InstanceId=instanceId,
                      Filters=[
                        {
                          'key': 'DocumentName',
                          'value': 'AWS-RunPatchBaseline'
                        },
                        {
                          'key': 'Status',
                          'value': 'InProgress'
                        }
                      ]
                    )
                    return inProgressCommandResponse
                  except Exception as e:
                      logger.info('Failed to check for in progress patching operations on: {}'.format(instanceId))
                def script_handler(events, context):
                  #Ingest event details
                  instanceId = events['instanceId']
                  #Convert to string
                  instanceId = ''.join(instanceId)
                  #Check for in progress commands for AWS-RunPatchBaseline for this instance
                  inProgressCommandResponse=list_in_progress_commands(instanceId)
                  #No conflicting tasks, proceed with starting a new patch operation
                  if not inProgressCommandResponse['Commands']:
                    return("No conflicting AWS-RunPatchBaseline tasks, continuing...")
                  #Conflicting AWS-RunPatchBaseline already in progress, exit
                  else:
                    sys.exit('AWS-RunPatchBaseline already in progress, exiting...')
          - name: generateInstallOverrideList
            action: 'aws:executeScript'
            description: >-
              This action generates an install override list for the target managed node based on Inspector findings and stores the list in the specified S3 bucket.
            outputs:
              - Type: String
                Name: s3UriInstallOverrideList
                Selector: $.Payload.s3UriInstallOverrideList
              - Type: String
                Name: sourceAwsAccountId
                Selector: $.Payload.sourceAwsAccountId
              - Type: String
                Name: sourceRegion
                Selector: $.Payload.sourceRegion
              - Type: String
                Name: updatesMissing
                Selector: $.Payload.updatesMissing
            inputs:
              Runtime: python3.8
              Handler: script_handler
              InputPayload:
                includeKernelUpdates: '{{ IncludeKernelUpdates }}'
                inspectorFindingArn: '{{ InspectorFindingArn }}'
                instanceId: '{{ InstanceIds }}'
                s3BucketName: '{{ S3BucketName }}'
                s3BucketKey: '{{ S3BucketKey }}'
                severityFilter: '{{ SeverityFilter }}'
                source: '{{ Source }}'
              Script: |-
                import sys
                import boto3
                import json
                from datetime import datetime
                import logging
                logger = logging.getLogger()
                logger.setLevel(logging.INFO)
                inspector = boto3.client('inspector2')
                s3 = boto3.client('s3')
                def put_object_to_s3(s3BucketName,s3BucketKey,installOverrideList):
                  try:
                    logger.info('Put InstallOverrideList to S3 bucket: {}'.format(s3BucketName))
                    #Put object to S3
                    putObjectResponse = s3.put_object(
                            Bucket = s3BucketName,
                            Key = s3BucketKey,
                            Body = installOverrideList
                        )
                    return putObjectResponse
                  except Exception as e:
                      logger.info('Failed to put InstallOverrideList to S3 bucket: {}'.format(s3BucketName))
                def list_inspector_findings(instanceId,inspectorFindingArn='NONE',severityFilter='NONE'):
                  try:
                    logger.info('Retrieving Inspector findings for: {}'.format(instanceId))
                    if inspectorFindingArn!='NONE':
                      #Inspector finding ARN is provided, do not use severity filter
                      listFindingsResponse = inspector.list_findings(
                              filterCriteria={
                                #Filter for instance ID provided
                                'resourceId':[
                                  {
                                    'comparison':'EQUALS',
                                    'value':instanceId
                                  }
                                ],
                                #Filter for Inspector finding ARN
                                'findingArn': [
                                  {
                                      'comparison': 'EQUALS',
                                      'value': inspectorFindingArn
                                  },
                                ],
                                #Filter for package vulnerabilities, do not include network vulnerabilities
                                'findingType':[
                                  {
                                    'comparison':'EQUALS',
                                    'value':'PACKAGE_VULNERABILITY'
                                  }
                                ]
                              }
                            )
                    elif inspectorFindingArn=='NONE' and severityFilter=='NONE':
                      #No Inspector ARN or severity filter provided, get all active findings for the instance
                      listFindingsResponse = inspector.list_findings(
                        filterCriteria={
                          'resourceId':[
                            {
                              'comparison':'EQUALS',
                              'value':instanceId
                            }
                          ],
                          #Filter for package vulnerabilities, do not include network vulnerabilities
                          'findingType':[
                            {
                              'comparison':'EQUALS',
                              'value':'PACKAGE_VULNERABILITY'
                            }
                          ],
                          #Filter for active Inspector findings
                          'findingStatus':[
                            {
                              'comparison':'EQUALS',
                              'value':'ACTIVE'
                            }
                          ]
                        }
                      )
                    elif inspectorFindingArn=='NONE':
                      # No Inspector finding ARN provided but severity filter is provided, get actuve all findings for severity specified
                      listFindingsResponse = inspector.list_findings(
                        filterCriteria={
                          'resourceId':[
                            {
                              'comparison':'EQUALS',
                              'value':instanceId
                            }
                          ],
                          #Filter for package vulnerabilities, do not include network vulnerabilities
                          'findingType':[
                            {
                              'comparison':'EQUALS',
                              'value':'PACKAGE_VULNERABILITY'
                            }
                          ],
                          #Filter for active Inspector findings
                          'findingStatus':[
                            {
                              'comparison':'EQUALS',
                              'value':'ACTIVE'
                            }
                          ],
                          #Filter based on severity specified
                          'severity':[
                            {
                              'comparison':'EQUALS',
                              'value':severityFilter
                            }
                          ]
                        }
                      )
                    return listFindingsResponse
                  except Exception as e:
                    logger.info('Failed to retrieve Inspector findings for: {}'.format(instanceId))
                def get_unique_packages_to_update(instanceFindings,includeKernelUpdates=True):
                  #Create empty package list
                  packagesList = []
                  try:
                    logger.info('Retrieving list of unique packages to update')
                    #Loop through findings
                    for finding in instanceFindings:
                      vulnerablePackages = finding['packageVulnerabilityDetails']['vulnerablePackages']
                      #Loop through vulnerable pacakges
                      for package in vulnerablePackages:
                        #Create list of package names
                        packagesList.append(package['name'])
                    #Convert to set to get list of unique packages
                    packagesListSet = set(packagesList)
                    #Convert back to continue
                    packagesList = list(packagesListSet)
                    if not includeKernelUpdates:
                      #Remove kernel updates from package list
                      packagesList = [x for x in packagesList if not x.startswith("kernel")]
                      #packagesList.remove("kernel")
                    return packagesList
                  except Exception as e:
                      logger.info('Failed to create list of unique packages to update')
                def create_install_override_list(packagesList):
                  #Create empty package list to update
                  packagesToUpdate = []
                  try:
                    logger.info('Creating install override list')
                    #Loop through package list to create JSON formatted list expected by InstallOverrideList
                    for package in packagesList:
                      packagesToUpdate.append(
                        {
                          'id': package+'.*'
                        }
                      )
                    #Create JSON formatted InstallOverrideList
                    installOverrideList = json.dumps({'patches': packagesToUpdate})
                    return installOverrideList
                  except Exception as e:
                      logger.info('Failed to create install override list')
                def script_handler(events, context):
                  print('boto3 version')
                  print(boto3.__version__)
                  #Ingest event details
                  includeKernelUpdates = events['includeKernelUpdates']
                  inspectorFindingArn = events['inspectorFindingArn']
                  instanceId = events['instanceId']
                  s3BucketKey = events['s3BucketKey']
                  s3BucketName = events['s3BucketName']
                  severityFilter = events['severityFilter']
                  source = events['source']
                  #Convert instanceId to String type
                  instanceId = ''.join(instanceId)
                  #Get current date and time
                  now = datetime.now()
                  #Format date time to YY-mm-dd-H:M:S format
                  formattedDateTime = now.strftime("%Y-%m-%d-%H:%M:%S")
                  #Create empty package list to update
                  packagesToUpdate = []
                  #Set InstallOverrideList filename
                  fileName = instanceId+'-override-list-'+formattedDateTime+'.json'
                  #Custom action from Security Hub
                  if source=="aws.securityhub":
                    listFindingsResponse = list_inspector_findings(instanceId=instanceId,inspectorFindingArn=inspectorFindingArn)
                    instanceFindings = listFindingsResponse['findings']
                    #Loop through findings and get unique packages list to update
                    packagesList = get_unique_packages_to_update(instanceFindings)
                    #Check if package list contains vulnerable packages, exit if not
                    if not packagesList:
                      return {'updatesMissing':'false'}
                    else:
                      sourceAwsAccountId = instanceFindings[0]['awsAccountId']
                      sourceRegion = instanceFindings[0]['resources'][0]['region']
                      #Loop through package list to create JSON formatted list expected by InstallOverrideList
                      installOverrideList = create_install_override_list(packagesList)
                      #S3 bucket key default: inspector/installOverrideList/
                      #Build out bucket key with source account ID and region
                      s3BucketKeyModified = s3BucketKey+'accountid='+sourceAwsAccountId+"/"+'region='+sourceRegion+"/"+fileName
                      #Put InstallOverrideList in S3
                      put_object_to_s3(s3BucketName,s3BucketKeyModified,installOverrideList)
                      #Build out S3 URI
                      s3UriInstallOverrideList = 's3://'+s3BucketName+'/'+s3BucketKeyModified
                      return {'s3UriInstallOverrideList':s3UriInstallOverrideList, 'sourceAwsAccountId':sourceAwsAccountId,'sourceRegion':sourceRegion,'updatesMissing':'true'}
                  #Manual invocation
                  elif source=="aws.ssm.automation":
                    if severityFilter=='NONE':
                      #No severity filter provided, get all findings
                      listFindingsResponse = list_inspector_findings(instanceId=instanceId)
                    else:
                      # Severity filter provided, get all findings for specified severity
                      listFindingsResponse = list_inspector_findings(instanceId=instanceId,severityFilter=severityFilter)
                    instanceFindings = listFindingsResponse['findings']
                    #Loop through findings and get unique packages list to update
                    packagesList = get_unique_packages_to_update(instanceFindings,includeKernelUpdates)
                    #Check if package list contains vulnerable packages, exit if not
                    if not packagesList:
                      #No Inspector findings for package vulnerabilities
                      return {'updatesMissing':'false'}
                    else:
                      sourceAwsAccountId = instanceFindings[0]['awsAccountId']
                      sourceRegion = instanceFindings[0]['resources'][0]['region']
                      #Loop through package list to create JSON formatted list expected by InstallOverrideList
                      installOverrideList = create_install_override_list(packagesList)
                      #S3 bucket key default: inspector/installOverrideList/
                      #Build out bucket key with source account ID and region
                      s3BucketKeyModified = s3BucketKey+'accountid='+sourceAwsAccountId+"/"+'region='+sourceRegion+"/"+fileName
                      #Put InstallOverrideList in S3
                      put_object_to_s3(s3BucketName,s3BucketKeyModified,installOverrideList)
                      #Build out S3 URI
                      s3UriInstallOverrideList = 's3://'+s3BucketName+'/'+s3BucketKeyModified
                      return {'s3UriInstallOverrideList':s3UriInstallOverrideList, 'sourceAwsAccountId':sourceAwsAccountId,'sourceRegion':sourceRegion,'updatesMissing':'true'}
                  else:
                    sys.exit("No matching source, exiting...")
          - name: verifyUpdatesAreMissing
            action: aws:branch
            inputs:
              Choices:
              - NextStep: runPatchBaseline
                Variable: "{{generateInstallOverrideList.updatesMissing}}"
                StringEquals: 'true'
              Default:
                noUpdatesInstalled
          - name: runPatchBaseline
            action: 'aws:runCommand'
            timeoutSeconds: 7200
            onFailure: Abort
            description: >-
              This action runs the Command document ```AWS-RunPatchBaseline``` on the target managed node.
            inputs:
              DocumentName: AWS-RunPatchBaseline
              InstanceIds:
                - '{{ InstanceIds }}'
              Parameters:
                Operation: '{{ Operation }}'
                RebootOption: '{{ RebootOption }}'
                InstallOverrideList: '{{ generateInstallOverrideList.s3UriInstallOverrideList }}'
          - name: getInventoryAssociationId
            action: 'aws:executeScript'
            description: >-
              This action lists the current State Manager associations for the target managed node and determines the association ID for the document `AWS-GatherSoftwareInventory`.
            inputs:
              Runtime: python3.8
              Handler: script_handler
              InputPayload:
                instanceId: '{{ InstanceIds }}'
              Script: |-
                import boto3
                ssm = boto3.client('ssm')
                def script_handler(events, context):
                  #Ingest events
                  instanceId = events['instanceId']
                  #Convert instanceId to String type
                  instanceId = ''.join(instanceId)
                  #Describe associations for the instance ID
                  associationResponse = ssm.describe_instance_associations_status(
                      InstanceId = instanceId
                  )
                  for association in associationResponse['InstanceAssociationStatusInfos']:
                    if association['Name'] == 'AWS-GatherSoftwareInventory':
                      inventoryAssociationId = association['AssociationId']
                  return(inventoryAssociationId)
            outputs:
            - Name: inventoryAssociationId
              Selector: "$.Payload"
              Type: "String"
          - name: refreshInventoryAssociation
            action: 'aws:runCommand'
            isEnd: true
            description: >-
              This action runs the Command document ```AWS-RefreshAssociation``` on the specified target managed node.
            inputs:
              DocumentName: AWS-RefreshAssociation
              InstanceIds:
                - '{{ InstanceIds }}'
              Comment: "Refreshing Inventory association"
              Parameters:
                associationIds:
                  - '{{ getInventoryAssociationId.inventoryAssociationId }}'
          - name: noUpdatesInstalled
            action: aws:sleep
            isEnd: true
            inputs:
              Duration: PT30S

  ResolveInspectorFindingsRunbook:
    Type: AWS::SSM::Document
    Properties:
      DocumentType: Automation
      DocumentFormat: YAML
      TargetType: /AWS::EC2::Instance
      Content:
        description: |-
          # AWS Systems Manager Automation runbook to resolve Inspector findings
          ## Summary
          This runbook generates an install override list for a target managed instance based on Inspector findings. This runbook is intended to be used in conjunction with `AutomationRunPatchBaselineRunbook` to install missing updates.
          The runbook includes five steps:
          1. Generate an install override list based on vulnerable packages identified by Amazon Inspector
          1. Initiate a patching operation on the target managed node
          1. Wait for the patching operation to complete
          1. Review the status of the patching operation
          1. Review the status of the initial Security Hub finding
          ## Pre-requisites
          This runbook can be leveraged via a Security Hub custom action. To use this runbook manually, specify the target managed node and optionally specify an Inspector severity to filter findings.
          To initiate this Automation runbook, you must provide an Automation assume role that will be used by the runbook to resolve Inspector findings. This role must have permissions to list Inspector findings, get Security Hub findings, get Automation executions, put objects to S3, start Automation executions, and pass the Automation Administration role for multi-account/Region operations.
        schemaVersion: '0.3'
        assumeRole: '{{AutomationAssumeRole}}'
        parameters:
          AutomationAssumeRole:
            type: 'AWS::IAM::Role::Arn'
            description: (Required) The ARN of the role that allows Automation to perform the actions on your behalf.
            default: !GetAtt AutomationIAMRole.Arn
          AutomationPatchRunbook:
            type: String
            description: (Required) The name of the Automation runbook for patching.
            default: !Ref AutomationRunPatchBaselineRunbook
            allowedValues:
            - !Ref AutomationRunPatchBaselineRunbook
          InspectorFindingArn:
            type: String
            description: (Optional) The ARN of the Inspector finding.
          InstanceId:
            type: String
            description: (Optional) The instance ID to target.
          RebootOption:
            type: String
            description: (Optional) Reboot behavior after a patch Install operation. If you choose NoReboot and patches are installed, the instance is marked as non-compliant until a subsequent reboot and scan.
            default: RebootIfNeeded
            allowedValues:
            - RebootIfNeeded
            - NoReboot
          Source:
            type: String
            description: (Required) The source for the event.
            default: aws.ssm.automation
            allowedValues:
              - aws.ssm.automation
              - aws.securityhub
          SourceAwsAccountId:
            type: String
            description: (Required) The source AWS account ID for the target EC2 instance.
          SourceRegion:
            type: String
            description: (Required) The source region for the target EC2 instance.
        mainSteps:
          - name: patchInstance
            action: 'aws:executeScript'
            description: >-
              This action initiates a patch install operation on the target managed node using the install override list generated.
            outputs:
              - Name: automationExecutionId
                Selector: $.Payload
                Type: String
            inputs:
              Runtime: python3.8
              Handler: script_handler
              InputPayload:
                automationAdministrationRole: !GetAtt AutomationIAMRole.Arn
                automationPatchRunbook: '{{ AutomationPatchRunbook }}'
                inspectorFindingArn: '{{ InspectorFindingArn }}'
                instanceId: '{{ InstanceId }}'
                rebootOption: '{{ RebootOption }}'
                source: '{{ Source }}'
                sourceAwsAccountId: '{{ SourceAwsAccountId }}'
                sourceRegion: '{{ SourceRegion }}'
              Script: |-
                import boto3
                ssm = boto3.client('ssm')
                def script_handler(events, context):
                  #Ingest event details
                  automationAdministrationRole = events['automationAdministrationRole']
                  automationPatchRunbook = events['automationPatchRunbook']
                  inspectorFindingArn = events['inspectorFindingArn']
                  instanceId = events['instanceId']
                  rebootOption = events['rebootOption']
                  source = events['source']
                  sourceAwsAccountId = events['sourceAwsAccountId']
                  sourceRegion = events['sourceRegion']
                  #Split instance ID from resource ARN if needed
                  if ":" in instanceId:
                    instanceId = instanceId.split(":")[5]
                  #Initiate multi-account/Region Automation for AWS-RunPatchBaseline
                  automationExecutionResponse = ssm.start_automation_execution(
                    DocumentName=automationPatchRunbook,
                    Parameters={
                        'AutomationAssumeRole': [automationAdministrationRole],
                        'InspectorFindingArn': [inspectorFindingArn],
                        'InstanceIds': [instanceId],
                        'RebootOption': [rebootOption],
                        'Source': [source]
                    },
                    TargetLocations=[
                        {
                            'Accounts': [
                                sourceAwsAccountId,
                            ],
                            'Regions': [
                                sourceRegion,
                            ],
                            'ExecutionRoleName': 'resolveInspectorExecutionRole'
                        },
                    ]
                  )
                  #Return Automation Execution ID
                  return(automationExecutionResponse['AutomationExecutionId'])
          - name: waitForPatchToComplete
            action: aws:waitForAwsResourceProperty
            timeoutSeconds: 1800 #30 minutes
            description: >-
              This action waits for the patch install operation to complete.
            inputs:
              Service: ssm
              Api: GetAutomationExecution
              AutomationExecutionId: '{{ patchInstance.automationExecutionId }}'
              PropertySelector: "$.AutomationExecution.AutomationExecutionStatus"
              DesiredValues:
              - Success
              - Failed
              - TimedOut
              - Cancelled
          - name: reviewPatchStatus
            action: aws:assertAwsResourceProperty
            timeoutSeconds: 60 #1 minute
            description: >-
              This action asserts that the patch operation completed successfully.
            inputs:
              Service: ssm
              Api: GetAutomationExecution
              AutomationExecutionId: '{{ patchInstance.automationExecutionId }}'
              PropertySelector: "$.AutomationExecution.AutomationExecutionStatus"
              DesiredValues:
              - Success
          - name: reviewSecurityHubFindingStatus
            action: aws:waitForAwsResourceProperty
            timeoutSeconds: 600 #10 minutes
            description: >-
              This action reviews the status of the initial Security Hub finding.
            inputs:
              Service: securityhub
              Api: GetFindings
              Filters:
                Id:
                - Comparison: EQUALS
                  Value: '{{ InspectorFindingArn }}'
              PropertySelector: "$.Findings[0].RecordState"
              DesiredValues:
              - ARCHIVED
Outputs:
  InstallOverrideListS3BucketName:
    Description: The name of the S3 bucket used for hosting the install override list.
    Value: !Ref InstallOverrideListBucket
  AutomationRunbookName:
    Description: The name of the Automation runbook to resolve Inspector findings.
    Value: !Ref AutomationRunPatchBaselineRunbook
  AutomationAssumeRole:
    Description: The ARN of the IAM role for Automation.
    Value: !GetAtt AutomationIAMRole.Arn

Would you like to implement a fix?

None

github-actions[bot] commented 1 year ago

Community Note

Voting for Prioritization

Volunteering to Work on This Issue

justinretzolk commented 1 year ago

Hey @kiran-113 πŸ‘‹ Thank you for taking the time to raise this! So that we have the information needed to look into this, can you supply debug logs (redacted as needed) as well? That'll help give us an idea of why the resource is being marked for recreation on each run.

kiran-113 commented 1 year ago

Hey @kiran-113 πŸ‘‹ Thank you for taking the time to raise this! So that we have the information needed to look into this, can you supply debug logs (redacted as needed) as well? That'll help give us an idea of why the resource is being marked for recreation on each run. Hey Thanks for reply, could you please help me how to get DEBUG LOGS?

kiran-113 commented 1 year ago

Hey, @justinretzolk Could you please help how to get DEBUG LOGS?

justinretzolk commented 1 year ago

Hey @kiran-113 πŸ‘‹ You can do so by setting the TF_LOG environment variable to DEBUG.

kiran-113 commented 1 year ago

Hey @justinretzolk , please find the DEBUG LOGS COMMAND I USED : terraform apply -no-color 2>&1 | Tee-Object -FilePath apply.txt

apply.txt

kiran-113 commented 1 year ago

Hey @justinretzolk , can i know the cause for this issue

kiran-113 commented 1 year ago

Hey @kiran-113 πŸ‘‹ Thank you for taking the time to raise this! So that we have the information needed to look into this, can you supply debug logs (redacted as needed) as well? That'll help give us an idea of why the resource is being marked for recreation on each run. Hi, I attached logs, please let me know any further details required

justinretzolk commented 1 year ago

Hey @kiran-113 πŸ‘‹ Thank you for the debug logs. It looks like the replacement is being cased by the disable_rollback argument. Is that argument changing during these applies?

~ disable_rollback   = false -> true # forces replacement
kiran-113 commented 1 year ago

Hey @justinretzolk , Sorry for the delay, in my code disable_roolback is set to true, now i changed it to false, now is it working correctly, Thanks @justinretzolk , and i am closing the issue

github-actions[bot] commented 1 year ago

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.