AWSTemplateFormatVersion: "2010-09-09"
Description: "Netography IAM Roles and Lambdas - Deploy to the centralized logging account"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Netography Account Configuration"
        Parameters:
          - NetographyAccountId
          - NetographyExternalId
      - Label:
          default: "AWS Organization Configuration"
        Parameters:
          - OrganizationId
          - RootId
      - Label:
          default: "Deployment Configuration"
        Parameters:
          - DeploymentRegions
          - DeployFlowAndDNSLambda
          - DeployContextLambda
      - Label:
          default: "DNS Logging Configuration"
        Parameters:
          - ResolverQueryLogConfigBucketName
Parameters:
  # Netography Account Configuration
  NetographyAccountId:
    Type: String
    Default: "336133252600"
    Description: Netography AWS Account ID for cross-account access. Found in the Netography console under Settings > AWS Custom Trust Policy.

  NetographyExternalId:
    Type: String
    Description: External ID for additional security when assuming the role. Found in the Netography console under Settings > AWS Custom Trust Policy.
    AllowedPattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"

  # AWS Organization Configuration
  OrganizationId:
    Type: String
    Description: AWS Organization ID (o-example1234567). Used for setting permissions on Lambda functions to allow invocation from the organization.
    AllowedPattern: "^o-[a-z0-9]{10,32}$"

  RootId:
    Type: String
    Description: AWS Organizations Root ID (r-example). Used for deploying context roles across all organization accounts.
    AllowedPattern: "^r-[a-z0-9]{4,32}$"

  # Deployment Configuration
  DeploymentRegions:
    Type: CommaDelimitedList
    Description: Comma-separated list of AWS regions where Lambda functions and DNS logging should be deployed.
    Default: "us-east-1,us-east-2,us-west-1,us-west-2"

  DeployFlowAndDNSLambda:
    Type: String
    Default: "true"
    AllowedValues:
      - "true"
      - "false"
    Description: Deploy Lambda functions for ingesting VPC flow logs and DNS query logs. Choose 'false' if you only want IAM roles without Lambda automation.

  DeployContextLambda:
    Type: String
    Default: "true"
    AllowedValues:
      - "true"
      - "false"
    Description: Deploy Lambda function for automatically creating context integrations. This enables Netography to automatically discover and monitor EC2 instances across your organization.

  # DNS Logging Configuration
  ResolverQueryLogConfigBucketName:
    Type: String
    Description: S3 bucket name for Route53 DNS query logs. Leave empty to disable DNS logging. If specified, DNS logging will be configured across all deployment regions.
    Default: ""
Conditions:
  ShouldDeployFlowAndDNSLambda: !Equals [!Ref DeployFlowAndDNSLambda, "true"]
  ShouldDeployContextLambda: !Equals [!Ref DeployContextLambda, "true"]
  ShouldDeployDNSLogging:
    !Not [!Equals [!Ref ResolverQueryLogConfigBucketName, ""]]

Resources:
  NetographyRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: NetographyRole
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub "arn:aws:iam::${NetographyAccountId}:root"
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                sts:ExternalId: !Ref NetographyExternalId
      Policies:
        - PolicyName: NetographyS3Access
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:ListBucket
                  - s3:GetBucketLocation
                Resource: "*"
      Tags:
        - Key: Name
          Value: NetographyRole
        - Key: Purpose
          Value: NetographyVPCFlowLogAccess

  NetoFlowlogIngestFunctionRole:
    Type: AWS::IAM::Role
    Condition: ShouldDeployFlowAndDNSLambda
    Properties:
      RoleName: NetoFlowlogIngestFunctionRole
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: SecretsManagerAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                  - secretsmanager:DescribeSecret
                Resource:
                  Fn::Sub: arn:aws:secretsmanager:*:${AWS::AccountId}:secret:NETOSECRET*
      Tags:
        - Key: Name
          Value: NetoFlowlogIngestFunctionRole
        - Key: Purpose
          Value: LambdaExecution

  NetoContextFunctionRole:
    Type: AWS::IAM::Role
    Condition: ShouldDeployContextLambda
    Properties:
      RoleName: NetoContextFunctionRole
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: SecretsManagerAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                  - secretsmanager:DescribeSecret
                Resource:
                  Fn::Sub: arn:aws:secretsmanager:*:${AWS::AccountId}:secret:NETOSECRET*
      Tags:
        - Key: Name
          Value: NetoContextFunctionRole
        - Key: Purpose
          Value: LambdaExecution

  # StackSet to deploy Lambda functions to all regions
  LambdaFunctionsStackSet:
    Type: AWS::CloudFormation::StackSet
    Condition: ShouldDeployFlowAndDNSLambda
    DependsOn: NetoFlowlogIngestFunctionRole
    Properties:
      StackSetName: !Sub "${AWS::StackName}-LambdaFunctions"
      PermissionModel: SELF_MANAGED
      StackInstancesGroup:
        - DeploymentTargets:
            Accounts:
              - !Ref AWS::AccountId
          Regions: !Ref DeploymentRegions
      TemplateBody: |
        AWSTemplateFormatVersion: "2010-09-09"
        Description: "Lambda functions for VPC Flow Log and Context processing - deployed per region"
        Parameters:
          OrganizationId:
            Type: String
          DeployFlowAndDNSLambda:
            Type: String
          DeployContextLambda:
            Type: String
        Conditions:
          ShouldDeployFlowAndDNSLambda: !Equals [!Ref DeployFlowAndDNSLambda, "true"]
          ShouldDeployContextLambda: !Equals [!Ref DeployContextLambda, "true"]
        Resources:
          NetoFlowlogIngestFunction:
            Type: AWS::Lambda::Function
            Condition: ShouldDeployFlowAndDNSLambda
            Properties:
              FunctionName: "NetoFlowlogIngestFunction"
              Runtime: python3.11
              Handler: index.handler
              Role: !Sub "arn:aws:iam::${AWS::AccountId}:role/NetoFlowlogIngestFunctionRole"
              Timeout: 300
              Code:
                ZipFile: |
                  import base64
                  import json
                  import logging
                  import random
                  import time
                  import hashlib
                  import hmac
                  import urllib3
                  import boto3
                  from urllib.parse import urlencode
                  
                  logging.getLogger().setLevel(logging.INFO)
                  
                  def send_response(event, context, status, data, physical_resource_id=None):
                      response_body = {
                          'Status': status,
                          'Reason': f'See CloudWatch Log Stream: {context.log_stream_name}',
                          'PhysicalResourceId': physical_resource_id or context.log_stream_name,
                          'StackId': event['StackId'],
                          'RequestId': event['RequestId'],
                          'LogicalResourceId': event['LogicalResourceId'],
                          'Data': data
                      }
                      
                      http = urllib3.PoolManager()
                      response = http.request('PUT', event['ResponseURL'], 
                                            body=json.dumps(response_body),
                                            headers={'Content-Type': ''})
                  
                  class NetoSecret:
                      def __init__(self, api_key_base64):
                          decoded_str = base64.b64decode(api_key_base64).decode()
                          netosecret_json = json.loads(decoded_str)
                          self.app_name = netosecret_json.get("appname")
                          self.app_key = netosecret_json.get("appkey")
                          self.shared_secret = netosecret_json.get("sharedsecret")
                          self.short_name = netosecret_json.get("shortname")
                          self.url = netosecret_json.get("url")
                  
                  def create_jwt(payload, secret):
                      # Simple JWT implementation using only built-in libraries
                      header = {"alg": "HS256", "typ": "JWT"}
                      
                      # Base64 encode header and payload
                      header_b64 = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip('=')
                      payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')
                      
                      # Create signature
                      message = f"{header_b64}.{payload_b64}"
                      signature = hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()
                      signature_b64 = base64.urlsafe_b64encode(signature).decode().rstrip('=')
                      
                      return f"{message}.{signature_b64}"
                  
                  def get_netography_access_token(netosecret):
                      payload = {
                          "iat": int(time.time()),
                          "jti": random.randint(0, 10000000),
                          "appname": netosecret.app_name,
                          "appkey": netosecret.app_key,
                          "shortname": netosecret.short_name,
                      }
                      jwt_token = create_jwt(payload, netosecret.shared_secret)
                      
                      http = urllib3.PoolManager()
                      headers = {
                          "accept": "application/json",
                          "content-type": "application/json",
                          "authorization": f"Bearer {jwt_token}",
                      }
                      
                      url = f"{netosecret.url}/auth/token"
                      response = http.request('POST', url, 
                                            body=json.dumps({"jwt": jwt_token}),
                                            headers=headers)
                      
                      if response.status != 200:
                          logging.error(f"Failed to authenticate: {response.status}, {response.data}")
                          return None
                      
                      response_data = json.loads(response.data)
                      return response_data["access_token"]
                  
                  def get_secret_from_aws(secret_name, region_name):
                      client = boto3.client('secretsmanager', region_name=region_name)
                      try:
                          response = client.get_secret_value(SecretId=secret_name)
                          return json.loads(response['SecretString'])["NETOSECRET"]
                      except Exception as e:
                          logging.error(f"Error retrieving secret {secret_name}: {str(e)}")
                          return None
                  
                  def create_flow_source(netosecret, role_arn, log_bucket, vpc_region, log_bucket_region, log_bucket_prefix, vpc_id, account_id):
                      token = get_netography_access_token(netosecret)
                      if not token:
                          raise Exception("Failed to get access token")
                      
                      payload = {
                          "awsauthtype": "RoleARN",
                          "role": {"arn": role_arn},
                          "bucket": log_bucket,
                          "bucketregion": log_bucket_region,
                          "prefix": log_bucket_prefix,
                          "region": vpc_region,
                          "sqsurl": "",
                          "name": f"{account_id}-{vpc_id}",
                          "enabled": True,
                          "hiveprefix": False,
                          "flowtype": "aws",
                          "flowresource": "s3",
                          "accountid": account_id,
                          "traffictype": "flow",
                          "deleteobjects": False,
                          "samplerate": 100,
                          "tags": [vpc_region, vpc_id],
                      }
                      
                      http = urllib3.PoolManager()
                      headers = {
                          "accept": "application/json",
                          "content-type": "application/json",
                          "authorization": f"Bearer {token}",
                      }
                      
                      response = http.request('POST', f"{netosecret.url}/vpc",
                                            body=json.dumps(payload),
                                            headers=headers)
                      
                      if response.status != 200:
                          logging.error(f"Failed to create flow source: {response.status}, {response.data}")
                          raise Exception(f"Failed to create flow source: {response.status}")
                  
                  def create_dns_source(netosecret, role_arn, log_bucket, vpc_region, log_bucket_region, log_bucket_prefix, vpc_id, account_id):
                      token = get_netography_access_token(netosecret)
                      if not token:
                          raise Exception("Failed to get access token")
                      
                      payload = {
                          "enabled": True,
                          "flowtype": "aws",
                          "flowresource": "s3",
                          "samplerate": 100,
                          "traffictype": "dns",
                          "name": f"{account_id}-{vpc_id}-dns",
                          "vpcid": vpc_id,
                          "accountid": account_id,
                          "region": log_bucket_region,
                          "bucketregion": log_bucket_region,
                          "prefix": log_bucket_prefix,
                          "bucket": log_bucket,
                          "role": {"arn": role_arn},
                          "awsauthtype": "RoleARN",
                          "deleteobjects": False,
                      }
                      
                      http = urllib3.PoolManager()
                      headers = {
                          "accept": "application/json",
                          "content-type": "application/json",
                          "authorization": f"Bearer {token}",
                      }
                      
                      response = http.request('POST', f"{netosecret.url}/vpc",
                                            body=json.dumps(payload),
                                            headers=headers)
                      
                      if response.status != 200:
                          logging.error(f"Failed to create DNS source: {response.status}, {response.data}")
                          raise Exception(f"Failed to create DNS source: {response.status}")
                  
                  def handler(event, context):
                      try:
                          logging.info(f"Received event: {json.dumps(event)}")
                          
                          if event["RequestType"] == "Create":
                              vpc_id = event["ResourceProperties"].get("VpcId")
                              s3_bucket = event["ResourceProperties"].get("S3Bucket")
                              
                              # Get secret from current region
                              current_region = context.invoked_function_arn.split(':')[3]
                              netosecret_b64 = get_secret_from_aws("NETOSECRET", current_region)
                              if not netosecret_b64:
                                  # Fallback to us-east-2 if not found in current region
                                  netosecret_b64 = get_secret_from_aws("NETOSECRET", "us-east-2")
                              
                              if not netosecret_b64:
                                  raise Exception("Could not retrieve NETOSECRET from any region")
                              
                              netosecret = NetoSecret(netosecret_b64)
                              
                              create_flow_source(
                                  netosecret=netosecret,
                                  role_arn=event["ResourceProperties"].get("RoleArn"),
                                  log_bucket=s3_bucket,
                                  vpc_region=event["ResourceProperties"].get("VpcRegion"),
                                  log_bucket_region=event["ResourceProperties"].get("LogBucketRegion"),
                                  log_bucket_prefix=event["ResourceProperties"].get("LogBucketPrefix"),
                                  vpc_id=vpc_id,
                                  account_id=event["ResourceProperties"].get("AccountId"),
                              )
                              
                              if event["ResourceProperties"].get("DnsLogsEnabled") == "true":
                                  create_dns_source(
                                      netosecret=netosecret,
                                      role_arn=event["ResourceProperties"].get("RoleArn"),
                                      log_bucket=event["ResourceProperties"].get("DnsBucket"),
                                      vpc_region=event["ResourceProperties"].get("VpcRegion"),
                                      log_bucket_region=event["ResourceProperties"].get("DnsBucketRegion"),
                                      log_bucket_prefix=event["ResourceProperties"].get("DnsBucketPrefix"),
                                      vpc_id=vpc_id,
                                      account_id=event["ResourceProperties"].get("AccountId"),
                                  )
                          
                          send_response(event, context, "SUCCESS", {})
                      except Exception as e:
                          logging.error(f"Error: {str(e)}")
                          send_response(event, context, "FAILED", {}, str(e))
          
          NetoFlowlogIngestFunctionPermission:
            Type: AWS::Lambda::Permission
            Condition: ShouldDeployFlowAndDNSLambda
            Properties:
              FunctionName: !Ref NetoFlowlogIngestFunction
              Action: lambda:InvokeFunction
              Principal: "*"
              PrincipalOrgID: !Ref OrganizationId

      Parameters:
        - ParameterKey: OrganizationId
          ParameterValue: !Ref OrganizationId
        - ParameterKey: DeployFlowAndDNSLambda
          ParameterValue: !Ref DeployFlowAndDNSLambda
        - ParameterKey: DeployContextLambda
          ParameterValue: !Ref DeployContextLambda
      Capabilities:
        - CAPABILITY_NAMED_IAM

  # Context Lambda Function - deployed once in the centralized account
  NetoContextFunction:
    Type: AWS::Lambda::Function
    Condition: ShouldDeployContextLambda
    Properties:
      FunctionName: "NetoContextFunction"
      Runtime: python3.11
      Handler: index.handler
      Role: !GetAtt NetoContextFunctionRole.Arn
      Timeout: 300
      Code:
        ZipFile: |
          import base64
          import random
          import time
          import hashlib
          import hmac
          import logging
          import json
          import boto3
          import urllib3

          logging.getLogger().setLevel(logging.INFO)

          def send_response(event, context, status, data, physical_resource_id=None):
              response_body = {
                  'Status': status,
                  'Reason': f'See CloudWatch Log Stream: {context.log_stream_name}',
                  'PhysicalResourceId': physical_resource_id or context.log_stream_name,
                  'StackId': event['StackId'],
                  'RequestId': event['RequestId'],
                  'LogicalResourceId': event['LogicalResourceId'],
                  'Data': data
              }
              
              http = urllib3.PoolManager()
              response = http.request('PUT', event['ResponseURL'], 
                                    body=json.dumps(response_body),
                                    headers={'Content-Type': ''})

          class NetoSecret:
              def __init__(self, api_key_base64):
                  decoded_str = base64.b64decode(api_key_base64).decode()
                  netosecret_json = json.loads(decoded_str)
                  self.app_name = netosecret_json.get("appname")
                  self.app_key = netosecret_json.get("appkey")
                  self.shared_secret = netosecret_json.get("sharedsecret")
                  self.short_name = netosecret_json.get("shortname")
                  self.url = netosecret_json.get("url")

          def create_jwt(payload, secret):
              # Simple JWT implementation using only built-in libraries
              header = {"alg": "HS256", "typ": "JWT"}
              
              # Base64 encode header and payload
              header_b64 = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip('=')
              payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')
              
              # Create signature
              message = f"{header_b64}.{payload_b64}"
              signature = hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()
              signature_b64 = base64.urlsafe_b64encode(signature).decode().rstrip('=')
              
              return f"{message}.{signature_b64}"

          def get_netography_access_token(netosecret):
              payload = {
                  "iat": int(time.time()),
                  "jti": random.randint(0, 10000000),
                  "appname": netosecret.app_name,
                  "appkey": netosecret.app_key,
                  "shortname": netosecret.short_name,
              }
              jwt_token = create_jwt(payload, netosecret.shared_secret)
              
              http = urllib3.PoolManager()
              headers = {
                  "accept": "application/json",
                  "content-type": "application/json",
                  "authorization": f"Bearer {jwt_token}",
              }
              
              url = f"{netosecret.url}/auth/token"
              response = http.request('POST', url, 
                                    body=json.dumps({"jwt": jwt_token}),
                                    headers=headers)
              
              if response.status != 200:
                  logging.error(f"Failed to authenticate: {response.status}, {response.data}")
                  return None
              
              response_data = json.loads(response.data)
              return response_data["access_token"]

          def get_secret_from_aws(secret_name, region_name):
              client = boto3.client('secretsmanager', region_name=region_name)
              try:
                  response = client.get_secret_value(SecretId=secret_name)
                  return json.loads(response['SecretString'])["NETOSECRET"]
              except Exception as e:
                  logging.error(f"Error retrieving secret {secret_name}: {str(e)}")
                  return None

          def create_context_integration(netosecret, role_arn, account_id, tags=None):
              token = get_netography_access_token(netosecret)
              if not token:
                  return {"success": False, "error": "Failed to get access token"}
              
              payload = {
                  "name": account_id,
                  "type": "aws",
                  "enabled": True,
                  "updateinterval": 3600,
                  "aws": {
                      "awsauthtype": "RoleARN",
                      "role": {
                          "arn": role_arn
                      }
                  }
              }
              if tags:
                  payload["tags"] = tags
              
              http = urllib3.PoolManager()
              headers = {
                  "accept": "application/json",
                  "content-type": "application/json",
                  "authorization": f"Bearer {token}",
              }
              
              url = f"{netosecret.url}/integrations/context"
              response = http.request('POST', url,
                                    body=json.dumps(payload),
                                    headers=headers)
              
              if response.status != 200:
                  if response.status == 400 and b"already exists" in response.data:
                      logging.info(f"Context integration for account {account_id} already exists, skipping creation")
                      return {"success": True, "message": "Integration already exists"}
                  else:
                      logging.error(f"Failed to create context integration: {response.status}, {response.data}")
                      return {"success": False, "error": f"Failed to create context integration: {response.status}"}
              
              response_data = json.loads(response.data)
              logging.info(f"Context integration created successfully: {response_data}")
              return {"success": True, "data": response_data}

          def handler(event, context):
              try:
                  logging.info(f"Received event: {json.dumps(event)}")
                  
                  if event["RequestType"] == "Create":
                      # Get secret from current region
                      current_region = context.invoked_function_arn.split(':')[3]
                      netosecret_b64 = get_secret_from_aws("NETOSECRET", current_region)
                      if not netosecret_b64:
                          # Fallback to us-east-2 if not found in current region
                          netosecret_b64 = get_secret_from_aws("NETOSECRET", "us-east-2")
                      
                      if not netosecret_b64:
                          send_response(event, context, "FAILED", {}, "Could not retrieve NETOSECRET from any region")
                          return
                      
                      netosecret = NetoSecret(netosecret_b64)
                      tags = event["ResourceProperties"].get("Tags")
                      tags_list = tags.split(",") if tags else None
                      
                      result = create_context_integration(
                          netosecret=netosecret,
                          role_arn=event["ResourceProperties"].get("RoleArn"),
                          account_id=event["ResourceProperties"].get("AccountId"),
                          tags=tags_list
                      )
                      
                      if not result["success"]:
                          send_response(event, context, "FAILED", {}, result["error"])
                          return
                  
                  send_response(event, context, "SUCCESS", {})
              except Exception as e:
                  logging.error(f"Error: {str(e)}")
                  send_response(event, context, "FAILED", {}, str(e))

  NetoContextFunctionInvokePermission:
    Type: AWS::Lambda::Permission
    Condition: ShouldDeployContextLambda
    Properties:
      FunctionName: !Ref NetoContextFunction
      Action: lambda:InvokeFunction
      Principal: "*"
      PrincipalOrgID: !Ref OrganizationId

  ResolverQueryLogConfigStackSet:
    Type: AWS::CloudFormation::StackSet
    Condition: ShouldDeployDNSLogging
    Properties:
      StackSetName: !Sub "${AWS::StackName}-ResolverQueryLogConfig"
      PermissionModel: SELF_MANAGED
      StackInstancesGroup:
        - DeploymentTargets:
            Accounts:
              - !Ref AWS::AccountId # Deploy to the current account
          Regions: !Ref DeploymentRegions
        # For this simple option, we just use your existing DNSRegions list.
      TemplateBody: |
        AWSTemplateFormatVersion: "2010-09-09"
        Description: "Template to create a DNS Query Log Config and share it via RAM."
        Parameters:
          ResolverQueryLogConfigBucketName:
            Type: String
          OrganizationId:
            Type: String
        Resources:
          Route53ResolverQueryLoggingConfig:
            Type: AWS::Route53Resolver::ResolverQueryLoggingConfig
            Properties:
              Name: !Sub "${AWS::AccountId}-${AWS::Region}-dns-query-logs"
              DestinationArn: !Sub "arn:aws:s3:::${ResolverQueryLogConfigBucketName}/vpc-dns-logs"
          
          # This resource share will be created in EACH region from the DNSRegions list
          QueryLogConfigShare:
            Type: AWS::RAM::ResourceShare
            Properties:
              Name: !Sub "RAM-Share-${AWS::Region}-DNS-Query-Log-Config"
              AllowExternalPrincipals: false
              ResourceArns:
                - !GetAtt Route53ResolverQueryLoggingConfig.Arn
              Principals:
                - !Sub "arn:aws:organizations::${AWS::AccountId}:organization/${OrganizationId}" # Sharing with the Org ID from the parent stack
        Outputs:
          NetoRoute53ResolverQueryLoggingConfigId:
            Description: ID of the Route53 Resolver Query Logging Config for this region
            Value: !Ref Route53ResolverQueryLoggingConfig
            Export:
              Name: NetoRoute53ResolverQueryLoggingConfigId

      Parameters:
        - ParameterKey: ResolverQueryLogConfigBucketName
          ParameterValue: !Ref ResolverQueryLogConfigBucketName
        - ParameterKey: OrganizationId
          ParameterValue: !Ref OrganizationId # Pass the Org ID into the stack instances
      Capabilities:
        - CAPABILITY_NAMED_IAM

  # Context StackSet - deploying roles to member accounts
  NetographyContextStackSet:
    Type: AWS::CloudFormation::StackSet
    Condition: ShouldDeployContextLambda
    DependsOn:
      - NetoContextFunction
      - NetoContextFunctionInvokePermission
    Properties:
      StackSetName: !Sub "${AWS::StackName}-ContextStackSet"
      Description: Netography StackSet - This template will deploy an IAM role to every account with the name NetographyContextRole
      PermissionModel: SERVICE_MANAGED
      AutoDeployment:
        Enabled: true
        RetainStacksOnAccountRemoval: false
      OperationPreferences:
        RegionConcurrencyType: PARALLEL
        MaxConcurrentPercentage: 100
      StackInstancesGroup:
        - DeploymentTargets:
            OrganizationalUnitIds:
              - !Ref RootId # Deploy to the root of the organization
          Regions:
            - !Ref AWS::Region
      TemplateBody: |
        AWSTemplateFormatVersion: 2010-09-09
        Description: >-
          Netography StackSet - This template will deploy an IAM role to every account with the name NetographyContextRole. This role allows assuming from the NetographyAccountID parameter with an external ID of the NetographyExternalID parameter. It has AmazonEC2ReadOnlyAccess attached.
        Parameters:
          NetographyAccountID:
            Type: String
            Description: The Netography AWS Account ID to grant access to the S3 bucket.
          NetographyExternalID:
            Type: String
            Description: The External ID provided by Netography for secure access.
          NetoContextFunctionARN:
            Type: String
            Description: The ARN of the NetoContext Lambda function from the centralized account.
          Tags:
            Type: CommaDelimitedList
            Description: Only add contexts for EC2 instances with these tags.
            Default: ""
        Resources:
          NetographyContextRole:
            Type: AWS::IAM::Role
            Properties:
              RoleName: NetographyContextRole
              AssumeRolePolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Effect: Allow
                    Principal:
                      AWS: !Sub "arn:aws:iam::${NetographyAccountID}:root"
                    Action: sts:AssumeRole
                    Condition:
                      StringEquals:
                        sts:ExternalId: !Ref NetographyExternalID
              ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess
          RoleCreationTrigger:
            Type: AWS::CloudFormation::CustomResource
            Version: "1.0"
            Properties:
              ServiceToken: !Ref NetoContextFunctionARN
              RoleArn: !GetAtt NetographyContextRole.Arn
              AccountId: !Ref AWS::AccountId
              Tags: !Join [",", !Ref Tags]
      Parameters:
        - ParameterKey: NetographyAccountID
          ParameterValue: !Ref NetographyAccountId
        - ParameterKey: NetographyExternalID
          ParameterValue: !Ref NetographyExternalId
        - ParameterKey: NetoContextFunctionARN
          ParameterValue: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:NetoContextFunction"
        - ParameterKey: Tags
          ParameterValue: ""
      Capabilities:
        - CAPABILITY_NAMED_IAM

Outputs:
  NetographyRoleArn:
    Description: ARN of the IAM role for Netography to access S3 buckets
    Value:
      Fn::GetAtt:
        - NetographyRole
        - Arn
    Export:
      Name: NetographyRoleARN
  LambdaFunctionsStackSetId:
    Description: ID of the Lambda Functions StackSet
    Condition: ShouldDeployFlowAndDNSLambda
    Value: !Ref LambdaFunctionsStackSet
    Export:
      Name: LambdaFunctionsStackSetId
  NetoFlowlogIngestFunctionName:
    Description: Name of the Neto Flow Log Ingest Lambda Function (deployed in each region)
    Condition: ShouldDeployFlowAndDNSLambda
    Value: "NetoFlowlogIngestFunction"
    Export:
      Name: NetoFlowlogIngestFunctionName
  NetoContextFunctionName:
    Description: Name of the Neto Context Lambda Function (deployed in each region)
    Condition: ShouldDeployContextLambda
    Value: "NetoContextFunction"
    Export:
      Name: NetoContextFunctionName
