DontShaveTheYak / cf2tf

Convert Cloudformation templates to Terraform.
GNU General Public License v3.0
474 stars 78 forks source link

AvailabilityZone in CloudFormation is not supported #157

Closed aaditunni-unityinfotech closed 1 year ago

aaditunni-unityinfotech commented 1 year ago

I was trying to convert a CloudFormation template to terraform and it gave me this error: ValueError: Could not convert Cloudformation property "AvailabilityZone" to Terraform attribute of ['id'].

Traceback (most recent call last): File "", line 198, in _run_module_as_main File "", line 88, in _run_code File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Scripts\cf2tf.exe__main.py", line 7, in File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\click\core.py", line 1130, in call return self.main(*args, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\click\core.py", line 1055, in main rv = self.invoke(ctx) ^^^^^^^^^^^^^^^^ File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\click\core.py", line 1404, in invoke return ctx.invoke(self.callback, ctx.params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\click\core.py", line 760, in invoke return callback(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\cf2tf\app.py", line 44, in cli config = TemplateConverter(tmpl_path.stem, cf_template, search_manger).convert() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\cf2tf\convert.py", line 92, in convert tf_resources = self.convert_to_tf(self.manifest) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\cf2tf\convert.py", line 139, in convert_to_tf tf_resources.extend(converter(resources)) ^^^^^^^^^^^^^^^^^^^^ File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\cf2tf\convert.py", line 316, in convert_resources resolved_values = self.resolve_values( ^^^^^^^^^^^^^^^^^^^^ File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\cf2tf\convert.py", line 172, in resolve_values data[key] = self.resolve_values( ^^^^^^^^^^^^^^^^^^^^ File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\cf2tf\convert.py", line 184, in resolve_values return allowed_func[key](self, value) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\aadit.unni\AppData\Local\Programs\Python\Python311\Lib\site-packages\cf2tf\conversion\expressions.py", line 421, in get_att raise ValueError( ValueError: Could not convert Cloudformation property "AvailabilityZone" to Terraform attribute of ['id'].

Here is the link to the template: https://github.com/aws-samples/aws-securityhub-remediations/blob/main/aws-ecr-continuouscompliance/cft/aws-ecr-continuouscompliance-v1.yaml

Link to the official blog: https://aws.amazon.com/blogs/containers/automating-image-compliance-for-amazon-eks-using-amazon-elastic-container-registry-and-aws-security-hub/

shadycuz commented 1 year ago

I will take a look at this, seems like it has matched the wrong resource in Terraform.

shadycuz commented 1 year ago

This has been fixed locally and will be released in the next version soon. Until now here is your template:

$ cf2tf aws-ecr-continuouscompliance-v1.yaml 
// Converting aws-ecr-continuouscompliance-v1.yaml to Terraform!
// Existing Terraform src code found at /tmp/terraform_src.

data "aws_region" "current" {}

data "aws_caller_identity" "current" {}

resource "aws_cloudwatch_event_rule" "capture_ecr_image_scan_events" {
  description = "Capture ECR Scan Events and Trigger an Action"
  event_pattern = {
    detail-type = [
      "ECR Image Scan"
    ]
    source = [
      "aws.ecr"
    ]
  }
  name = "CaptureECRScanEvent"
  // CF Property(State) = "ENABLED"
  // CF Property(Targets) = [
  //   {
  //     Arn = aws_lambda_function.ecr_to_sec_hub_send_findings_lambda.arn
  //     Id = "IDCaptureECRImageScanEvents"
  //   }
  // ]
}

resource "aws_lambda_function" "ecr_to_sec_hub_send_findings_lambda" {
  function_name = "ECR2SecurityHubSendFindingsLambda"
  description = "Maps ECR Scan Finding into ASFF before importing to Security Hub"
  handler = "index.lambda_handler"
  memory_size = 384
  role = aws_iam_role.ecr_to_sec_hub_send_findings_lambda_role.arn
  runtime = "python3.7"
  timeout = 70
  environment {
    variables = {
      account_num = data.aws_caller_identity.current.account_id
      region = data.aws_region.current.name
    }
  }
  code_signing_config_arn = {
    ZipFile = "import json
import boto3
import datetime
import uuid
import os
def lambda_handler(event, context):
    # import Lambda ENV VARs
    accountId = os.environ['account_num']
    awsRegion = os.environ['region']
    # Get ECR event details
    eventDetails = event['detail']
    repoName = eventDetails['repository-name']
    findingsevcounts = eventDetails['finding-severity-counts']
    numCritical = 0
    numMedium = 0
    numHigh = 0
    if findingsevcounts.get('CRITICAL'):
        numCritical = findingsevcounts['CRITICAL']
    if findingsevcounts.get('MEDIUM'):
        numMedium = findingsevcounts['MEDIUM']
    if findingsevcounts.get('HIGH'):
        numHigh = findingsevcounts['HIGH']

    # send finding to Security Hub
    severity = "LOW"
    title = "ECR Finding"
    ECRComplianceRating = 'PASSED'
    if numMedium:
        severity = "MEDIUM"
        title = "Medium ECR Vulnerability"
        ECRComplianceRating = 'FAILED'
    if numHigh:
        severity = "HIGH"
        title = "High ECR Vulnerability"
        ECRComplianceRating = 'FAILED'
    if numCritical:
        severity = "CRITICAL"
        title = "Critical ECR Vulnerability"
        ECRComplianceRating = 'FAILED'

    # ISO Time
    iso8061Time = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat()
    # ASFF BIF Id
    asffID = str(uuid.uuid4())
    # import security hub boto3 client
    sechub = boto3.client('securityhub')
    # call BIF
    try:
        response = sechub.batch_import_findings(
            Findings=[
                {
                    'SchemaVersion': '2018-10-08',
                    'Id': asffID,
                    'ProductArn': 'arn:aws:securityhub:' + awsRegion + ':' + accountId + ':product/' + accountId + '/default',
                    'ProductFields': {
                        'ECRRepoName': repoName,
                    },
                    'GeneratorId': asffID,
                    'AwsAccountId': accountId,
                    'Types': [ 'Software and Configuration Checks' ],
                    'FirstObservedAt': iso8061Time,
                    'UpdatedAt': iso8061Time,
                    'CreatedAt': iso8061Time,
                    'Severity': {
                        'Label': severity
                    },
                    'Title': title,
                    'Description': title,
                    'Resources': [
                        {
                            'Type': 'AwsEcr',
                            'Id': 'AWS::::Account:' + accountId,
                            'Partition': 'aws',
                            'Region': awsRegion,
                        }
                    ],
                    'WorkflowState': 'NEW',
                    'Compliance': {'Status': ECRComplianceRating},
                    'RecordState': 'ACTIVE'
                }
            ]
        )
        print(response)
    except Exception as e:
        print(e)
        print("Submitting finding to Security Hub failed, please troubleshoot further")
        raise
"
  }
}

resource "aws_iam_role" "ecr_to_sec_hub_send_findings_lambda_role" {
  force_detach_policies = [
    {
      PolicyName = "ECRToSecHubSendFindingsLambda-Policy"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "cloudwatch:PutMetricData",
              "securityhub:BatchImportFindings"
            ]
            Resource = "*"
          },
          {
            Effect = "Allow"
            Action = [
              "logs:CreateLogGroup",
              "logs:CreateLogStream",
              "logs:PutLogEvents"
            ]
            Resource = "*"
          }
        ]
      }
    }
  ]
  assume_role_policy = {
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
        Action = [
          "sts:AssumeRole"
        ]
      }
    ]
  }
}

resource "aws_lambda_permission" "permission_for_events_to_invoke_lambdachk" {
  action = "lambda:InvokeFunction"
  function_name = aws_lambda_function.ecr_to_sec_hub_send_findings_lambda.arn
  principal = "events.amazonaws.com"
  source_arn = aws_cloudwatch_event_rule.capture_ecr_image_scan_events.arn
}

resource "aws_lambda_function" "create_security_hub_custom_action_target_lambda" {
  function_name = "CreateSecurityHubCustomActionTargetLambda-ECR"
  description = "Custom resource to create an action target in Security Hub"
  handler = "index.lambda_handler"
  memory_size = 256
  role = aws_iam_role.create_security_hub_custom_action_target_lambda_role.arn
  runtime = "python3.7"
  timeout = 60
  environment {
    variables = {
      Region = data.aws_region.current.name
    }
  }
  code_signing_config_arn = {
    ZipFile = "import boto3
import cfnresponse
import os
def lambda_handler(event, context):
    try:
        properties = event['ResourceProperties']
        region = os.environ['Region']
        client = boto3.client('securityhub', region_name=region)
        responseData = {}
        if event['RequestType'] == 'Create':
            response = client.create_action_target(
                Name=properties['Name'],
                Description=properties['Description'],
                Id=properties['Id']
            )
            responseData['Arn'] = response['ActionTargetArn']
        elif event['RequestType'] == 'Delete':
            account_id = context.invoked_function_arn.split(":")[4]
            client.delete_action_target(
                ActionTargetArn=f"arn:aws:securityhub:{region}:{account_id}:action/custom/{properties['Id']}"
            )
        cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
    except Exception as e:
        print(e)
        cfnresponse.send(event, context, cfnresponse.FAILED, {})
"
  }
}

resource "aws_iam_role" "create_security_hub_custom_action_target_lambda_role" {
  force_detach_policies = [
    {
      PolicyName = "CreateActionTarget-LambdaPolicy-ECR"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "cloudwatch:PutMetricData"
            ]
            Resource = "*"
          },
          {
            Effect = "Allow"
            Action = [
              "logs:CreateLogGroup",
              "logs:CreateLogStream",
              "logs:PutLogEvents"
            ]
            Resource = "*"
          },
          {
            Effect = "Allow"
            Action = [
              "securityhub:CreateActionTarget",
              "securityhub:DeleteActionTarget"
            ]
            Resource = "*"
          }
        ]
      }
    }
  ]
  assume_role_policy = {
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
        Action = [
          "sts:AssumeRole"
        ]
      }
    ]
  }
}

resource "aws_cloudwatch_event_rule" "ecr_access_prohibited_rule" {
  name = "ECRAccessProhibitedRule"
  description = "ECR1 - Deny Access to ECR due to vulnerability assesment"
  event_pattern = {
    source = [
      "aws.securityhub"
    ]
    detail-type = [
      "Security Hub Findings - Custom Action"
    ]
    resources = [
      aws_securityhub_action_target.ecr_action_target.arn
    ]
  }
  // CF Property(State) = "ENABLED"
  // CF Property(Targets) = [
  //   {
  //     Arn = aws_lambda_function.ecr_access_prohibited_lambda.arn
  //     Id = "ECR1"
  //   }
  // ]
}

resource "aws_securityhub_action_target" "ecr_action_target" {
  // CF Property(ServiceToken) = aws_lambda_function.create_security_hub_custom_action_target_lambda.arn
  name = "ECR1"
  description = "Deny Access to ECR"
  identifier = "ECR11"
}

resource "aws_lambda_permission" "ecr_access_prohibited_lambda_permission" {
  function_name = aws_lambda_function.ecr_access_prohibited_lambda.arn
  action = "lambda:InvokeFunction"
  principal = "events.amazonaws.com"
  source_arn = aws_cloudwatch_event_rule.ecr_access_prohibited_rule.arn
}

resource "aws_lambda_function" "ecr_access_prohibited_lambda" {
  function_name = "ECRAccessProhibitedLambda"
  description = "ECR1 - Deny Access to ECR due to vulnerability assesment"
  handler = "index.lambda_handler"
  memory_size = 256
  role = aws_iam_role.ecr_access_prohibited_lambda_role.arn
  runtime = "python3.7"
  timeout = 60
  code_signing_config_arn = {
    ZipFile = "import boto3
import json
import os
def lambda_handler(event, context):

    repoName = str(event['detail']['findings'][0]['ProductFields']['ECRRepoName'])
    ecr = boto3.client('ecr')
    try:
        policyText = '{\n  "Version" : "2008-10-17",\n  "Statement" : [ {\n    "Sid" : "deny all",\n    "Effect" : "Deny",\n    "Principal" : "*",\n "Action" : [ "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "ecr:BatchCheckLayerAvailability" ]\n } ]\n}'
        response = ecr.set_repository_policy(
            repositoryName=repoName,
            policyText=policyText
        )

    except Exception as e:
        print(e)
        print("SSM automation execution error")
        raise
"
  }
}

resource "aws_iam_role" "ecr_access_prohibited_lambda_role" {
  force_detach_policies = [
    {
      PolicyName = "ECRAccessProhibitedLambdaPolicy"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "cloudwatch:PutMetricData"
            ]
            Resource = "*"
          },
          {
            Effect = "Allow"
            Action = [
              "logs:CreateLogGroup",
              "logs:CreateLogStream",
              "logs:PutLogEvents"
            ]
            Resource = "*"
          },
          {
            Effect = "Allow"
            Action = [
              "ssm:StartAutomationExecution",
              "ecr:*",
              "iam:PassRole",
              "securityhub:UpdateFindings"
            ]
            Resource = "*"
          }
        ]
      }
    }
  ]
  assume_role_policy = {
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
        Action = [
          "sts:AssumeRole"
        ]
      }
    ]
  }
}

There will likely be things that need manually fixed, feel free to open up new issues =)

Thanks