{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Click AD Connector and Cross-Account Role for managing Workspaces",
  "Resources": {
    "IamRoleLambdaExecution": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": ["lambda.amazonaws.com"]
              },
              "Action": ["sts:AssumeRole"]
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": "SsmReadPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": ["ssm:GetParametersByPath"],
                  "Resource": [
                    {
                      "Fn::Join": [
                        "",
                        [
                          "arn:aws:ssm:",
                          {
                            "Ref": "AWS::Region"
                          },
                          ":",
                          {
                            "Ref": "AWS::AccountId"
                          },
                          ":",
                          "parameter",
                          {
                            "Ref": "SSMPrefix"
                          },
                          "*"
                        ]
                      ]
                    }
                  ]
                }
              ]
            }
          },
          {
            "PolicyName": "IAMIdentityCenterReadPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Sid": "AllowSSOAndIdentityStoreReadAccess",
                  "Effect": "Allow",
                  "Action": ["sso:ListInstances"],
                  "Resource": "*"
                },
                {
                  "Sid": "AllowIdentityStoreAccess",
                  "Effect": "Allow",
                  "Action": [
                    "identitystore:DescribeUser",
                    "identitystore:ListGroups",
                    "identitystore:ListGroupMemberships",
                    "identitystore:ListGroupMembershipsForMember",
                    "identitystore:ListUsers"
                  ],
                  "Resource": "*"
                }
              ]
            }
          }
        ],
        "Path": "/",
        "ManagedPolicyArns": [
          {
            "Fn::Join": [
              "",
              [
                "arn:",
                {
                  "Ref": "AWS::Partition"
                },
                ":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
              ]
            ]
          }
        ]
      }
    },
    "AdConnectorLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": { "Fn::Sub": "click-ad-gateway-${AWS::Region}" },
          "S3Key": "click-ad-connector-v2.6.1.zip"
        },
        "Handler": "ad_connector.lambda_handler",
        "Runtime": "python3.12",
        "MemorySize": 1024,
        "Timeout": 60,
        "Environment": {
          "Variables": {
            "ad_extra_attributes": {
              "Ref": "AdExtraAttributes"
            },
            "ad_groups_ous": {
              "Ref": "AdGroupsOUs"
            },
            "ad_groups_query": {
              "Ref": "AdGroupsQuery"
            },
            "ad_host": {
              "Ref": "AdHost"
            },
            "ad_people_ous": {
              "Ref": "AdPeopleOUs"
            },
            "ad_people_query": {
              "Ref": "AdPeopleQuery"
            },
            "ad_port": {
              "Ref": "AdPort"
            },
            "ad_user": {
              "Ref": "AdUser"
            },
            "ad_use_secure_ldap": {
              "Ref": "UseSecureLdap"
            },
            "endpoint": {
              "Ref": "Endpoint"
            },
            "entra_enabled":{
              "Ref": "EntraEnabled"
            },
            "entra_group_match_terms": {
              "Ref": "EntraGroupMatchTerms"
            },
            "force_update": {
              "Ref": "UpdateForced"
            },
            "ssm_prefix": {
              "Ref": "SSMPrefix"
            }
          }
        },
        "Role": {
          "Fn::GetAtt": ["IamRoleLambdaExecution", "Arn"]
        },
        "VpcConfig": {
          "SecurityGroupIds": {
            "Ref": "SecurityGroupIds"
          },
          "SubnetIds": {
            "Ref": "SubnetIds"
          }
        }
      },
      "DependsOn": []
    },
    "AdConnectorLambdaVersion": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "AdConnectorLambdaFunction"
        }
      }
    },
    "AdConnectorEventsRuleSchedule1": {
      "Type": "AWS::Events::Rule",
      "Properties": {
        "ScheduleExpression": {
          "Ref": "ScheduleRate"
        },
        "State": "ENABLED",
        "Targets": [
          {
            "Arn": {
              "Fn::GetAtt": ["AdConnectorLambdaFunction", "Arn"]
            },
            "Id": "AdConnectorSchedule"
          }
        ]
      }
    },
    "AdConnectorLambdaPermissionEventsRuleSchedule1": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": ["AdConnectorLambdaFunction", "Arn"]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "events.amazonaws.com",
        "SourceArn": {
          "Fn::GetAtt": ["AdConnectorEventsRuleSchedule1", "Arn"]
        }
      }
    },
    "ClickSSMKey": {
      "Type": "AWS::KMS::Key",
      "Properties": {
        "Description": "Encryption Key to encrypt SSM parameters for Click",
        "KeyPolicy": {
          "Version": "2012-10-17",
          "Id": "key-clickpolicy-1",
          "Statement": [
            {
              "Sid": "Allow access for Key Administrators",
              "Effect": "Allow",
              "Principal": {
                "AWS": [
                  {
                    "Ref": "ClickKMSKeyAdminArn"
                  }
                ]
              },
              "Action": [
                "kms:Create*",
                "kms:Describe*",
                "kms:Enable*",
                "kms:List*",
                "kms:Put*",
                "kms:Update*",
                "kms:Revoke*",
                "kms:Disable*",
                "kms:Get*",
                "kms:Delete*",
                "kms:TagResource",
                "kms:UntagResource",
                "kms:ScheduleKeyDeletion",
                "kms:CancelKeyDeletion"
              ],
              "Resource": "*"
            },
            {
              "Sid": "Allow use of the key",
              "Effect": "Allow",
              "Principal": {
                "AWS": [
                  {
                    "Fn::GetAtt": ["IamRoleLambdaExecution", "Arn"]
                  },
                  {
                    "Fn::If": [
                      "ShouldIncludeEncryptersArn",
                      {
                        "Ref": "ClickKMSKeyEncrypterArn"
                      },
                      {
                        "Ref": "AWS::NoValue"
                      }
                    ]
                  }
                ]
              },
              "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
              ],
              "Resource": "*"
            }
          ]
        }
      }
    },
    "ClickKMSSNSTopic": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "DisplayName": "ClickKMSSNSTopic"
      }
    },
    "ClickKMSSNSTopicSubscription": {
      "Type": "AWS::SNS::Subscription",
      "Condition": "ShouldCreateSubscription",
      "Properties": {
        "Endpoint": {
          "Ref": "KMSPolicySubscriptionEmail"
        },
        "Protocol": "email",
        "TopicArn": {
          "Ref": "ClickKMSSNSTopic"
        }
      }
    },
    "ClickKMSSNSTopicCloudtrailPolicy": {
      "Type": "AWS::SNS::TopicPolicy",
      "Properties": {
        "PolicyDocument": {
          "Id": "ClickKMSCloudwatchEventPolicy",
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "Sid1",
              "Effect": "Allow",
              "Principal": {
                "Service": "events.amazonaws.com"
              },
              "Action": "sns:Publish",
              "Resource": {
                "Ref": "ClickKMSSNSTopic"
              }
            }
          ]
        },
        "Topics": [
          {
            "Ref": "ClickKMSSNSTopic"
          }
        ]
      }
    },
    "ClickKMSCloudwatchEvent": {
      "Type": "AWS::Events::Rule",
      "Properties": {
        "Description": "Key policy change event.",
        "EventPattern": {
          "source": ["aws.kms"],
          "detail-type": ["AWS API Call via CloudTrail"],
          "detail": {
            "eventSource": ["kms.amazonaws.com"],
            "eventName": ["PutKeyPolicy"],
            "requestParameters": {
              "keyId": [
                {
                  "Ref": "ClickSSMKey"
                }
              ]
            }
          }
        },
        "State": "ENABLED",
        "Targets": [
          {
            "Arn": {
              "Ref": "ClickKMSSNSTopic"
            },
            "Id": "ClickKMSSNSTopic",
            "InputTransformer": {
              "InputPathsMap": {
                "agent": "$.detail.userAgent",
                "detail": "$.detail.eventName",
                "source": "$.detail.sourceIPAddress",
                "user": "$.detail.userIdentity.arn"
              },
              "InputTemplate": "\"Click Security Alert:\"\n\"Somebody has made a change on the Key Policy for the KMS Key that is used to encrypt SSM parameters that contain the credentials to Active Directory.\"\n\"User: <user>\"\n\"IP Address: <source>\"\n\"System: <agent>\"\n"
            }
          }
        ]
      }
    },
    "CrossAccountRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "Policies": [
          {
            "PolicyName": "InvokeLambda",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": {
                "Effect": "Allow",
                "Action": ["lambda:InvokeFunction", "lambda:InvokeAsync"],
                "Resource": {
                  "Fn::GetAtt": ["AdConnectorLambdaFunction", "Arn"]
                }
              }
            }
          }
        ],
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": {
            "Effect": "Allow",
            "Principal": {
              "AWS": {
                "Ref": "TrustRoleArn"
              }
            },
            "Action": ["sts:AssumeRole"],
            "Condition": {
              "StringEquals": {
                "sts:ExternalId": {
                  "Ref": "TrustExternalId"
                }
              }
            }
          }
        }
      }
    }
  },
  "Outputs": {
    "LambdaFunctionName": {
      "Description": "Name of lambda function",
      "Value": {
        "Ref": "AdConnectorLambdaFunction"
      }
    },
    "CrossAccountRoleArn": {
      "Description": "ARN of cross-account role",
      "Value": {
        "Fn::GetAtt": ["CrossAccountRole", "Arn"]
      }
    },
    "ClickKMSKeyId": {
      "Description": "ID of the KMS Key created by Click to store credentials.",
      "Value": {
        "Ref": "ClickSSMKey"
      }
    }
  },
  "Conditions": {
    "ShouldIncludeEncryptersArn": {
      "Fn::Not": [
        {
          "Fn::Equals": [
            "",
            {
              "Ref": "ClickKMSKeyEncrypterArn"
            }
          ]
        }
      ]
    },
    "ShouldCreateSubscription": {
      "Fn::Not": [
        {
          "Fn::Equals": [
            "",
            {
              "Ref": "KMSPolicySubscriptionEmail"
            }
          ]
        }
      ]
    }
  },
  "Parameters": {
    "SecurityGroupIds": {
      "Type": "List<AWS::EC2::SecurityGroup::Id>",
      "Description": "AD Connector Lambda will be placed in these Security Groups"
    },
    "SubnetIds": {
      "Type": "List<AWS::EC2::Subnet::Id>",
      "Description": "AD Connector Lambda will be placed in these subnets"
    },
    "SSMPrefix": {
      "Type": "String",
      "Description": "Prefix where config is stored in SSM -- include leading and trailing /. Requires ad/password and shared_secret",
      "AllowedPattern": "/.+/",
      "ConstraintDescription": "must begin and end with a forward slash (/)"
    },
    "ClickKMSKeyAdminArn": {
      "Type": "String",
      "Description": "ARN of IAM role or user that will get ADMIN permissions to the Click KMS Key."
    },
    "ClickKMSKeyEncrypterArn": {
      "Type": "String",
      "Description": "ARN of IAM role or user that is deploying this CloudFormation Stack. This user will get USE/encrypt permissions to the Click KMS Key."
    },
    "KMSPolicySubscriptionEmail": {
      "Type": "String",
      "Description": "Enter an email address if a subscription to the KMS Key Policy Change topic is desired."
    },
    "AdUser": {
      "Type": "String",
      "Description": "AD User to login with eg (user@ad.acmedev.sync-demo.com). Password goes in SSM <prefix>/ad/password"
    },
    "AdHost": {
      "Type": "String",
      "Description": "LDAP hostname (comma separated to include more than one)"
    },
    "AdPort": {
      "Type": "Number",
      "Description": "LDAP Port",
      "Default": 389
    },
    "UseSecureLdap": {
      "Description": "Use Secure LDAP?",
      "Default": false,
      "Type": "String",
      "AllowedValues": [true, false]
    },
    "AdPeopleOUs": {
      "Type": "String",
      "Description": "OU bases for users eg (dc=acme,dc=com) (pipe| separated to include more than one)"
    },
    "AdGroupsOUs": {
      "Type": "String",
      "Description": "OU bases for groups eg (dc=acme,dc=com) (pipe| separated to include more than one)"
    },
    "AdPeopleQuery": {
      "Type": "String",
      "Default": "",
      "Description": "LDAP query for users (relative to the AdPeopleOUs parameter). Leave empty for default"
    },
    "AdGroupsQuery": {
      "Type": "String",
      "Default": "",
      "Description": "LDAP query for groups (relative to the AdGroupOUs parameter). Leave empty for default"
    },
    "AdExtraAttributes": {
      "Type": "String",
      "Default": "",
      "Description": "Comma-separated list of extra LDAP attributes to include for users."
    },
    "Endpoint": {
      "Type": "String",
      "Description": "URL of where to send POST-back"
    },
    "TrustRoleArn": {
      "Type": "String",
      "Description": "Full ARN of trusted role in Synchronet's account"
    },
    "TrustExternalId": {
      "Type": "String",
      "Description": "Trust external id"
    },
    "ScheduleRate": {
      "Type": "String",
      "Description": "Schedule to run AD Connector",
      "Default": "rate(1 hour)"
    },
    "CodeDeployBucket": {
      "Type": "String",
      "Default": "click-ad-gateway",
      "Description": "Bucket name where code is hosted"
    },
    "UpdateForced": {
      "Type": "String",
      "Default": "false",
      "Description": "Trying to force an update"
    },
    "EntraEnabled": {
      "Type": "String",
      "Default": false,
      "AllowedValues": [true, false],
      "Description": "Control if data from Entra/IAM Identity Center should be gathered"
    },
    "EntraGroupMatchTerms": {
      "Type": "String",
      "Default": "Click",
      "Description": "Terms that will be used to gather Entra Groups. For example if you have Click,HR,Test we will gather all groups that have those strings in the name"
    }
  }
}
