AWSTemplateFormatVersion: '2010-09-09'
Description: AWS Control Tower Integration with BlueCat Cloud Discovery & Visibility for IP Address Management

Parameters:
  BAMurl:
    Description: Provide the BlueCat Adress Manager (BAM) url
    Type: String
  Gatewayurl:
    Description: Provide the URL of the BlueCat Gateway
    Type: String
  Username:
    Description: Provide the Username for the BlueCat Admin
    Type: String
  Password:
    Description: Provide the Password for the BlueCat Admin
    Type: String
    NoEcho: true
  Region:
    Description: Provide the region where you want to run BlueCat Cloud Discovery & Visibility
    Type: String
    Default: us-east-1
  AccessKey:
    Description: Provide the the Access Key ID of the BlueCat IAM user
    Type: String
    NoEcho: true
  SecretKey:
    Description: Provide the the Secret Key ID of the BlueCat IAM user
    Type: String
    NoEcho: true
  BlueCatAccountId:
    Description: 12 digit AWS Account ID where Bluecat solution is deployed. Bluecat solution establishes trust with managed accounts to perform actions as defined by a permissions policy.
    Type: String
  BlueCatConfiguration:
    Description: A User friendly name for the configuration. This gets displayed in the CD&V Portal.
    Type: String    
  BluecatTemplateURL:
    Description:
      Base URL for Bluecat's CloudFormation template for managed accounts
    Type: String
    Default: 'https://bluecatresources.s3.amazonaws.com/BlueCat-managedaccount.yaml'
Metadata:
  'AWS::CloudFormation::Interface':
    ParameterGroups:
      - Label:
          default: BlueCat software portal URLs
        Parameters:
          - BAMurl
          - Gatewayurl
      - Label:
          default: Credentials of BlueCat Cloud Discovery & Visibility (CD&V)
        Parameters:
            - Username
            - Password
      - Label:
          default: Credentials of a IAM User used by BlueCat
        Parameters:
            - AccessKey
            - SecretKey
Resources:

  # ---------------------------------------------------------------------------------------------------
#  Create a Bluecat StackSet in the Control Tower Management Account
#      - The Bluecat StackSet is the basis for the Bluecat template to be provisioned in the managed accounts
# --------------------------------------------------------------------------------------------------

  BluecatStackSet:
    Type: AWS::CloudFormation::StackSet
    Properties:
      Description: StackSet for creating Bluecat Integration Role
      StackSetName: 'BluecatMemberRolev1'
      Parameters:
        - ParameterKey: BlueCatAccountId
          ParameterValue: !Ref BlueCatAccountId
      PermissionModel: SELF_MANAGED
      AdministrationRoleARN: !Join [':', ['arn:aws:iam:', !Ref 'AWS::AccountId', 'role/service-role/AWSControlTowerStackSetRole']]
      ExecutionRoleName: "AWSControlTowerExecution"
      Capabilities:
        - CAPABILITY_NAMED_IAM
        - CAPABILITY_IAM
        - CAPABILITY_AUTO_EXPAND
      TemplateURL: !Ref BluecatTemplateURL

  BlueCatCaptureControlTowerLifeCycleEvents:
    Type: AWS::Events::Rule
    Properties:
      Description: Capture Control Tower LifeCycle Events for BlueCat and Trigger an Action
      EventPattern:
        detail:
          eventName:
          - CreateManagedAccount
          - UpdateManagedAccount
          eventSource:
          - controltower.amazonaws.com
        detail-type:
        - AWS Service Event via CloudTrail
        source:
        - aws.controltower
      Name: BlueCatCaptureControlTowerLifeCycleEvents
      State: ENABLED
      Targets:
      - Arn: !GetAtt "BlueCatLambdaFunction.Arn"
        Id: IDCaptureControlTowerLifeCycleEvents


  #SUSE TriggerLifecyleEvent Lambda
  BlueCatLambdaFunction:
    Type: AWS::Lambda::Function
    # DependsOn: BlueCatTriggerLifecycleEventLambdaRole
    Properties:
      FunctionName: !Join [ "-", [ "BlueCatIPAM", !Ref AWS::Region, !Ref AWS::AccountId ]]
      Environment:
        Variables:
            # AMIlist: !Ref SUSEPrivateImageID
            bam_url: !Ref BAMurl
            gateway_url: !Ref Gatewayurl
            username: !Ref Username
            password: !Ref Password
            region_name: !Ref Region
            access_key_id: !Ref AccessKey
            secret_key_id: !Ref SecretKey
            bluecat_account_id: !Ref BlueCatAccountId
            bluecat_role_stackset: !Ref BluecatStackSet
            bluecat_configuration: !Ref BlueCatConfiguration
      Code:
        ZipFile: |
          import requests
          import json
          import os
          import boto3
          import logging
          import urllib.request
          import botocore.exceptions
          import time
          from botocore.exceptions import ClientError
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)
          stackset_list = ['BluecatMemberRolev1']

          ssl_verify = False
          import urllib3
          urllib3.disable_warnings()
          gateway_url = os.environ["gateway_url"]
          bam_url = os.environ["bam_url"]
          username = os.environ["username"]
          password = os.environ["password"]
          bluecat_configuration = os.environ["bluecat_configuration"]
          bluecat_role_stackset = os.environ["bluecat_role_stackset"]

  



          def lambda_handler(event, context):
              print('Starting the Function')
              masterAcct = event['detail']['userIdentity']['accountId']
              eventDetails = event['detail']
              regionName = eventDetails['awsRegion']
              eventName = eventDetails['eventName']
              srvEventDetails = eventDetails['serviceEventDetails']
              if eventName == 'CreateManagedAccount' or eventName == 'UpdateManagedAccount':
                  newAccInfo = {}
                  logger.info('Event Processed Sucessfully')
                  if eventName == 'CreateManagedAccount':
                      newAccInfo = srvEventDetails['createManagedAccountStatus']
                  if eventName == 'UpdateManagedAccount':
                      newAccInfo = srvEventDetails['updateManagedAccountStatus']
                  cmdStatus = newAccInfo['state']
                  if cmdStatus == 'SUCCEEDED':
                      '''Successful event recieved'''
                      accId = newAccInfo['account']['accountId']
                      print("AWS Account ID:", accId)
                      cloudformation = boto3.client('cloudformation')
                      try:
                          result = cloudformation.create_stack_instances(StackSetName=bluecat_role_stackset, Accounts=[accId], Regions=[regionName])
                          logger.info('Processed {} Sucessfully '.format(bluecat_role_stackset))
                      except Exception as e:
                          logger.error('Unable to launch in:{}, REASON: {}'.format(bluecat_role_stackset, e))

                      print ("Result = ", result);
                      print ("Operation id = ", result['OperationId'])
                      operationId = result['OperationId']

                      operationStatus = 'PENDING'
                      while operationStatus == 'PENDING' or operationStatus == 'RUNNING':
                          print("Calling list_stack_set_operation_results method")
                          time.sleep(10);
                          operationResult = cloudformation.list_stack_set_operation_results(StackSetName=bluecat_role_stackset,OperationId=operationId)
                          resultSummary = operationResult['Summaries'][0]
                          operationStatus = resultSummary['Status']
                          print("operationStatus = ", operationStatus )


                      print("Stackset Operation Status is ", operationStatus)
                      # Login to gateway
                      if operationStatus == 'SUCCEEDED':
                          gateway_header = gateway_login(gateway_url, bam_url, username, password)
                          print("Gateway Header:", gateway_header)

                          roleArn = "arn:aws:iam::" + accId + ":role/BlueCatRole"
                          aws_keys = [{
                              "region_name": os.environ["region_name"],
                              "access_key_id": os.environ["access_key_id"],
                              "secret_key_access": os.environ["secret_key_id"],
                              "role_arn": roleArn,
                              }]
                          discovery_options = {
                                  "options": {
                                      "private_networks": True,
                                      "public_networks": True,
                                      "bluecat_configuration": bluecat_configuration,
                                      "create_separate_configuration": True,
                                      "override_configuration": True,
                                      "virtual_machines": True,
                                      "load_balancers": True,
                                      "internal_name_resolution": True,
                                      "device_internal_view": "internal",
                                      "external_name_resolution": False,
                                      "device_external_view": "",
                                      "target_domain": "aws.example.com",
                                      "auto_create_subzone": True,
                                      "private_dns_zones": True,
                                      "dns_private_view": "private",
                                      "remove_deleted_resources": True,
                                      "public_dns_zones": True,
                                      "dns_public_view": "public"
                                  },
                                  "visibility": {
                                      "bam_username": username,
                                      "bam_password": password,
                                      "selective_deployment": False
                                  }
                              }
                          # Check AWS credentials and run discovery if valid
                          for aws_key in aws_keys:
                              is_valid = check_valid_aws_key_info(aws_key, gateway_url, gateway_header)
                              print("Key_is_valid:", is_valid)
                              if is_valid:
                                  run_discovery(aws_key, discovery_options, gateway_url, gateway_header)

          def gateway_login(gateway_url, bam_url, username, password):
              print('Logging into Gateway with - gateway_url: %s, bam_url: %s' % (gateway_url, bam_url))
              request_header = None
              # print("Request Header is:", request_header)
              login_url = f"{gateway_url}/rest_login"
              print("Login URL:", login_url)
              login_data = {
                  "username": username,
                  "password": password
              }

              try:
                  login_response = requests.request("POST", login_url, headers={}, data=login_data, verify=ssl_verify)
                  print("Login Response:", login_response)
                  if login_response.status_code != 200:
                      print("Failed logging into Gateway - %s" % login_response.text)

                  bam_token = login_response.json().get('access_token')
                  print("BAM TOKEN:", bam_token)
                  request_header = {'Auth': f'Basic {bam_token}'}
              except Exception as e:
                  print("Failed logging into Gateway - %s" % e)
              return request_header


          def check_valid_aws_key_info(aws_key, gateway_url, request_header):
              print('Checking AWS key - region_name: %s, access_key_id: %s' % (aws_key.get('region_name'), aws_key.get('access_key_id')))
              try:
                  url_check_aws_account = f"{gateway_url}/cloud-discovery/api/v1/provider/aws/authentication"
                  response = requests.request("POST", url_check_aws_account, json=aws_key, headers=request_header, verify=ssl_verify)
                  if response.status_code == 200:
                      print("AWS key - SUCCESS")
                      return True
                  else:
                      print("AWS key - Fail - %s" % response.json().get('message'))
                      return False
              except Exception as e:
                  print("AWS key - EXCEPTION - %s" % e)
                  return False


          def run_discovery(aws_key, discovery_options, gateway_url, request_header):
              print("Running discovery with key %s" % aws_key.get('access_key_id'))
              try:
                  url_request = f"{gateway_url}/cloud-discovery/api/v1/provider/aws/discovery"
                  discovery_options["authentication"] = aws_key
                  if "visibility" in discovery_options:
                      discovery_options["visibility"]["access_key_id"] = aws_key["access_key_id"]
                      discovery_options["visibility"]["secret_key_access"] = aws_key["secret_key_access"]
                      if aws_key.get("role_arn"):
                        discovery_options["visibility"]["role_arn"] = aws_key["role_arn"]
                  response = requests.request("POST", url_request, json=discovery_options, headers=request_header, verify=ssl_verify)
                  if response.status_code == 200:
                      print("Discovery run succesfully")
                  else:
                      raise Exception(f"[{response.status_code}] - {response.json()}")
              except Exception as e:
                  print("Discovery run failed - %s" % e)

      Handler: index.lambda_handler
      MemorySize: 256
      Role: !GetAtt "BlueCatTriggerLifecycleEventLambdaRole.Arn"
      Runtime: python3.7
      Timeout: 300


#SUSE Trigger LifecyleEvent Lambda Role
  BlueCatTriggerLifecycleEventLambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: AllowLambdaAssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: BlueCatIPAMLambdaPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Sid: '1'
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                  - 'logs:DescribeLogStreams'
                Effect: Allow
                Resource: !Join [':',['arn:aws:logs', !Ref 'AWS::Region', !Ref 'AWS::AccountId', 'log-group', '/aws/lambda/*']]
              - Sid: CloudFormationOperations
                Action:
                  - 'cloudformation:CreateStackInstances'
                  - 'cloudformation:DescribeStackInstance'
                  - 'cloudformation:DescribeStackSetOperation'
                  - 'cloudformation:CreateStackSet'
                  - 'cloudformation:DescribeStackSet'
                  - 'cloudformation:ListStackSetOperationResults'
                Effect: Allow
                Resource:
                  - !Join ['',['arn:aws:cloudformation:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':stackset/', !Ref BluecatStackSet]]
  PermissionForEventsToInvokeLambdachk:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt "BlueCatLambdaFunction.Arn"
      Principal: events.amazonaws.com
      SourceArn: !GetAtt "BlueCatCaptureControlTowerLifeCycleEvents.Arn"
