From 63c5b20c476f5ac37aae1a31ff14beb5b07c3ac5 Mon Sep 17 00:00:00 2001 From: Allan Carter Date: Fri, 23 Aug 2024 15:27:06 +0000 Subject: [PATCH] Add res-demo template that allows CIDR and LDIF configuration The LDIF file creates admins, users, and groups in the regular USERS OU. It also creates a RES OU with admins, users, and groups in it. The RES OU users and groups are prefixed with "res". Resolves #251 Add install_vscode role and playbook Update custom-amis.md --- docs/custom-amis.md | 8 +- docs/res_integration.md | 99 +- res/.gitignore | 3 + res/create-ldif.py | 197 + res/download-res-templates.sh | 9 + res/requirements.txt | 2 + res/res-demo-with-cidr/bi.yaml | 639 +++ res/res-demo-with-cidr/res-demo-stack.yaml | 323 ++ res/res-demo-with-cidr/res.ldif | 4069 +++++++++++++++++ res/res-demo/bi.yaml | 581 +++ res/res-demo/networking.yaml | 606 +++ res/res-demo/res-demo-stack.yaml | 251 + res/res-demo/res.ldif | 136 + res/upload-res-templates.py | 103 + res/upload-res-templates.sh | 9 + source/resources/playbooks/install_vscode.yml | 8 + .../playbooks/roles/install_vscode/README.md | 14 + .../roles/install_vscode/tasks/main.yml | 43 + 18 files changed, 7080 insertions(+), 20 deletions(-) create mode 100644 res/.gitignore create mode 100755 res/create-ldif.py create mode 100755 res/download-res-templates.sh create mode 100644 res/requirements.txt create mode 100644 res/res-demo-with-cidr/bi.yaml create mode 100644 res/res-demo-with-cidr/res-demo-stack.yaml create mode 100644 res/res-demo-with-cidr/res.ldif create mode 100644 res/res-demo/bi.yaml create mode 100644 res/res-demo/networking.yaml create mode 100644 res/res-demo/res-demo-stack.yaml create mode 100644 res/res-demo/res.ldif create mode 100755 res/upload-res-templates.py create mode 100755 res/upload-res-templates.sh create mode 100644 source/resources/playbooks/install_vscode.yml create mode 100644 source/resources/playbooks/roles/install_vscode/README.md create mode 100644 source/resources/playbooks/roles/install_vscode/tasks/main.yml diff --git a/docs/custom-amis.md b/docs/custom-amis.md index de04010f..2104773c 100644 --- a/docs/custom-amis.md +++ b/docs/custom-amis.md @@ -5,8 +5,10 @@ By default, ParallelCluster will use pre-built AMIs for the OS that you select. The exception is Rocky 8 and 9, for which ParallelCluster does not provide pre-built AMIs. To use Rocky Linux, you must first build a custom AMI and specify it in your config file at **slurm/ParallelClusterConfig/Os/CustomAmi**. -The easiest way is to start an EC2 instance, update it with your changes, and create a new AMI from that instance. -You can then add the new AMI to your configuration file. +The easiest way is to start an EC2 instance with an existing ParallelCluster AMI, update it with your changes, and create a new AMI from that instance. +You can find the official ParallelCluster AMIs using the ParallelCluster UI. +Click on **Images** and the list of **Official Images** will be listed. +After you create the a new AMI, you can then add it to your configuration file. ParallelCluster can also automate this process for you using EC2 ImageBuilder. When you build your cluster, example ParallelCluster build configuration files @@ -50,6 +52,8 @@ charges when run on AWS EC2 instances to develop FPGA images that can be run on First subscribe to the FPGA developer AMI in the [AWS Marketplace](https://us-east-1.console.aws.amazon.com/marketplace/home?region=us-east-1#/landing). There are 2 versions, one for [CentOS 7](https://aws.amazon.com/marketplace/pp/prodview-gimv3gqbpe57k?ref=cns_1clkPro) and the other for [Amazon Linux 2](https://aws.amazon.com/marketplace/pp/prodview-iehshpgi7hcjg?ref=cns_1clkPro). +**Note**: The FPGA Developer AMI hasn't been ported to the latest OS versions, so it will not show up in the build file templates. + ## Deploy or update the Cluster After the AMI is built, add it to the config and create or update your cluster to use the AMI. diff --git a/docs/res_integration.md b/docs/res_integration.md index 4dfea5e7..38c4b51a 100644 --- a/docs/res_integration.md +++ b/docs/res_integration.md @@ -1,7 +1,58 @@ # RES Integration +First you will need to deploy RES. + + +## RES Setup + +After you've deployed RES, you need to configure it so that the remote desktops can be used as external login nodes and so that they have access to any file systems that you created. + +### Onboard your file systems + +RES natively supports EFS, FSx for NetApp Ontap, and FSx for Lustre file systems. +It can create them for you or you can onboard existing file systems. + +* Expand **Environment Management** +* Click **File Systems** +* Click **Onboard File System** or **Create File System** + +### Create RES Project + +* Expand **Environment Management** +* Click **Projects** +* Click **Create Project** +* Fill in the required fields. +* Add any file systems that you created so they will be automatically mounted on the desktops that belong to the project. +* Expand **Advanced Options** under **Resource Configurations** and add the **SlurmLoginNodeSG** so that it will be attached automatically to the remote desktop so they can access the external file systems and slurm clusters. +* Add the groups and users that can use the project. + +### Give the project access to software stacks + +Next, you'll need to give the project access to a Software Stack. +You can either create a new Software Stack or update an existing one. + +* Select **Software Stacks** under **Session Management**. +* Select an existing stack like the RHEL 8 stack +* Select **Actions**, **Edit Stack**. +* Select your project under Projects and enable it to use the stack. + +### Create virtual desktop + +Now you can create a virtual desktop using the project that you just created. + +* Select **My Virtual Desktops** under **Desktops**. +* Click **Launch New Virtual Desktop** +* Give it a descriptive name, select the project, operating system, and software stack. +* I suggest using a t3 instance for virtual desktops, such as a t3.large. If you need more cores or memory you will use your ParallelCluster compute nodes. +* I usually increase the storage size to 20GB so I can install additional packages. +* Click **Submit** and then wait for the desktop to be provisioned. You may need to refresh the page to update the desktop status. + +You can switch to the EC2 console to verify that the instance has been launched and that it has the required security group attached. + +## ParallelCluster Configurattion + Integration with [Research and Engineering Studion (RES)](https://docs.aws.amazon.com/res/latest/ug/overview.html) is straightforward. -You simply specify the **--RESEnvironmentName** option for the `install.sh` script or add the **RESEnvironmentName** configuration parameter +You simply specify the **--RESStackName** option for the `install.sh` script or add the **RESStackName** configuration parameter to your configuration file. The install script will set the following configuration parameters based on your RES environment or check them if you have them set to make sure they are consistent with your RES environment. @@ -11,15 +62,18 @@ The intention is to completely automate the deployment of ParallelCluster and se |-----------|-------------|------ | VpcId | VPC id for the RES cluster | vpc-xxxxxx | SubnetId | Subnet in the RES VPC. | subnet-xxxxx -| SubmitterInstanceTags | The tag of VDI instances. | 'res:EnvironmentName': *EnvironmentName*' -| ExtraMounts | The mount parameters for the /home directory. This is required for access to the home directory. | -| ExtraMountSecurityGroups | Security groups that give access to the ExtraMounts. These will be added to compute nodes so they can access the file systems. +| slurm/ExternalLoginNodes | Information of instances to be configured as external login nodes | +| slurm/DomainJoinedInstance | Tags of cluster-manager which will be used to create users_groups.json | +| slurm/storage/ExtraMounts | The mount parameters for the /home directory. This is required for access to the home directory. | +| slurm/SlurmCtl/AdditionalSecurityGroups | Security group that allows access to EFS /home | +| slurm/InstanceConfig/AdditionalSecurityGroups | Security group that allows access to EFS /home | -You must also create security groups as described in [Security Groups for Login Nodes](deployment-prerequisites.md#security-groups-for-login-nodes) and specify the SlurmHeadNodeSG in the `slurm/SlurmCtl/AdditionalSecurityGroups` parameter and the SlurmComputeNodeSG in the `slurm/InstanceConfig/AdditionalSecurityGroups` parameter. +You must also create security groups as described in [Security Groups for Login Nodes](deployment-prerequisites.md#security-groups-for-login-nodes). +You must either specify **AdditionalSecurityGroupsStackName** or specify the SlurmHeadNodeSG in the `slurm/SlurmCtl/AdditionalSecurityGroups` parameter and the SlurmComputeNodeSG in the `slurm/InstanceConfig/AdditionalSecurityGroups` parameter. -When you specify **RESEnvironmentName**, a lambda function will run SSM commands to create a cron job on a RES domain joined instance to update the users_groups.json file every hour. Another lambda function will also automatically configure all running VDI hosts to use the cluster. +When you specify **RESStackName**, a lambda function will run SSM commands to create a cron job on a RES domain joined instance to update the users_groups.json file every hour. Another lambda function will also automatically configure all running VDI hosts to use the cluster. -The following example shows the configuration parameters for a RES with the EnvironmentName=res-eda. +The following example shows the configuration parameters for a RES cluster with a stack named res-eda. ``` --- @@ -33,37 +87,46 @@ The following example shows the configuration parameters for a RES with the Envi StackName: res-eda-pc-3-9-1-rhel8-x86-config Region: + SshKeyPair: -RESEnvironmentName: res-eda +AdditionalSecurityGroupsStackName: res-eda-SlurmSecurityGroups + +RESStackName: res-eda ErrorSnsTopicArn: TimeZone: 'US/Central' slurm: - ClusterName: res-eda-pc-3-9-1-rhel8-x86 - ParallelClusterConfig: - Version: '3.9.1' + Version: '3.10.1' Image: Os: 'rhel8' Architecture: 'x86_64' - Database: - DatabaseStackName: pcluster-slurm-db-res + Slurmdbd: + SlurmdbdStackName: pcluster-slurm-dbd-res-eda-3-10-1 - SlurmCtl: - AdditionalSecurityGroups: - - sg-12345678 # SlurmHeadNodeSG + SlurmCtl: {} # Configure typical EDA instance types # A partition will be created for each combination of Base OS, Architecture, and Spot InstanceConfig: - AdditionalSecurityGroups: - - sg-23456789 # SlurmComputeNodeSG UseSpot: true NodeCounts: DefaultMaxCount: 10 ``` +## Connect to the virtual desktop + When the cluster deployment finishes you are ready to run jobs from your RES DCV desktop. + +## Create custom AMI for virtual desktops + +Connect to your virtual desktop and install packages, software, configure ParallelCluster clusters, mount file systems, and whatever else you need for your project. +You'll normally require root access to do this. +When you are done, remove the following files or else new virtual desktops created from the image will fail to provision. + +``` +rm /root/bootstrap/semaphore/*.lock +``` diff --git a/res/.gitignore b/res/.gitignore new file mode 100644 index 00000000..d6007917 --- /dev/null +++ b/res/.gitignore @@ -0,0 +1,3 @@ + +.venv +rendered_templates/ diff --git a/res/create-ldif.py b/res/create-ldif.py new file mode 100755 index 00000000..3ebde41d --- /dev/null +++ b/res/create-ldif.py @@ -0,0 +1,197 @@ +#!/usr/bin/python3 + +from textwrap import dedent + +config = { + 'OU': 'CORP', + 'DC': 'corp,dc=res,dc=com', + 'DirectoryDomain': 'corp.res.com', + 'groups': [ + { + 'prefix': 'group', + 'OU': None, + 'first_gid': 2000, + 'index0': 1, + 'number': 2, + 'description': 'Non-RES group' + }, + { + 'prefix': 'resgroup', + 'OU': 'RES', + 'first_gid': 2010, + 'index0': 1, + 'number': 2, + 'description': 'Represents a project group in RES' + }, + { + 'name': 'RESAdministrators', + 'OU': 'RES', + 'gid': 2020, + 'description': 'Represents the group of sudoers' + } + ], + 'users': [ + { + 'prefix': 'admin', + 'OU': None, + 'number': 10, + 'first_uid': 3000, + 'first_gid': 4000, + 'groups': [ + 'RESAdministrators', + 'group01' + ] + }, + { + 'prefix': 'resadmin', + 'OU': 'RES', + 'number': 10, + 'first_uid': 3100, + 'first_gid': 4200, + 'groups': [ + 'RESAdministrators', + 'resgroup01' + ] + }, + { + 'prefix': 'user', + 'OU': None, + 'number': 100, + 'first_uid': 5000, + 'first_gid': 6000, + 'groups': [ + 'group01' + ] + }, + { + 'prefix': 'user', + 'OU': None, + 'number': 100, + 'first_index': 101 + 'first_uid': 5100, + 'first_gid': 6100, + 'groups': [ + 'group02' + ] + }, + { + 'prefix': 'resuser', + 'OU': 'RES', + 'number': 100, + 'first_uid': 15000, + 'first_gid': 16000, + 'groups': [ + 'resgroup01' + ] + }, + { + 'prefix': 'resuser', + 'OU': 'RES', + 'number': 100, + 'first_index': 101 + 'first_uid': 15100, + 'first_gid': 16100, + 'groups': [ + 'resgroup02' + ] + }, + ] +} + +fh = open('res-demo-with-cidr/res.ldif', 'w') + +fh.write(dedent(""" + # Create a OU to be used by RES + dn: OU=RES,OU=${OU},DC=${DC} + changetype: add + objectClass: top + objectClass: organizationalUnit + ou: RES + description: The RES application will limit syncing groups and group-members in the RES OU + + # Create a OU to be used by RES to create computers + dn: OU=Computers,OU=RES,OU=${OU},DC=${DC} + changetype: add + objectClass: top + objectClass: organizationalUnit + ou: Computers + description: The RES application will limit creating computers to this OU + + # Create a OU to be used by RES to create groups and add users to + dn: OU=Users,OU=RES,OU=${OU},DC=${DC} + changetype: add + objectClass: top + objectClass: organizationalUnit + ou: Users + description: The RES application will limit syncing groups and group-members in the RES OU + """)) + +group_members = {} +for user_dict in config['users']: + user_prefix = user_dict['prefix'] + first_index = user_dict['first_index'] + for user_index in range(1, user_dict['number'] + 1): + userid = user_prefix + f"{user_index:04}" + ou = user_dict['OU'] + comment = f"Create a user: {userid}" + if ou: + comment += f" in {ou} OU" + dn = f"CN={userid},OU=Users," + if ou: + dn += f"OU={ou}," + dn += "OU=${OU},DC=${DC}" + fh.write(dedent(f""" + {comment} + dn: {dn} + changetype: add + objectClass: top + objectClass: person + objectClass: organizationalPerson + objectClass: user + cn: {userid} + sAMAccountName: {userid} + name: {userid} + userPrincipalName: {userid}@${{DirectoryDomain}} + mail: {userid}@${{DirectoryDomain}} + uidNumber: {user_dict['first_uid']+user_index} + gidNumber: {user_dict['first_gid']+user_index} + unixHomeDirectory: /home/{userid} + loginShell: /bin/bash + """)) + for group in user_dict['groups']: + if group not in group_members: + group_members[group] = [] + group_members[group].append(dn) + +for group_dict in config['groups']: + ou = group_dict['OU'] + for group_index in range(group_dict.get('index0', 1), group_dict.get('number', 1) + 1): + if 'index0' in group_dict: + group_name = f"{group_dict['prefix']}{group_index:02}" + gid = group_dict['first_gid'] + group_index + else: + group_name = group_dict['name'] + gid = group_dict['gid'] + comment = f"# Create a group: {group_name}" + if ou: + comment += f" in {ou} OU" + dn = f"CN={group_name},OU=Users," + if ou: + dn += "OU={ou}," + dn += "OU=${OU},DC=${DC}" + fh.write(dedent(f""" + {comment} + dn: {dn} + changetype: add + objectClass: top + objectClass: group + cn: {group_name} + description: {group_dict['description']} + distinguishedName: {dn} + name: {group_name} + sAMAccountName: {group_name} + objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=${{DC}} + gidNumber: {gid} + """)) + for member_dn in group_members.get(group_name, []): + fh.write(f"member: {member_dn}\n") diff --git a/res/download-res-templates.sh b/res/download-res-templates.sh new file mode 100755 index 00000000..82dd5035 --- /dev/null +++ b/res/download-res-templates.sh @@ -0,0 +1,9 @@ +#!/bin/bash -xe + +script_dir=$(dirname $(realpath $0)) +cd $script_dir +aws s3 cp s3://aws-hpc-recipes/main/recipes/res/res_demo_env/assets/res-demo-stack.yaml res-demo/. +aws s3 cp s3://aws-hpc-recipes/main/recipes/res/res_demo_env/assets/bi.yaml res-demo/. +aws s3 cp s3://aws-hpc-recipes/main/recipes/net/hpc_large_scale/assets/main.yaml res-demo/networking.yaml + +aws s3 cp s3://aws-hpc-recipes/main/recipes/res/res_demo_env/assets/res.ldif res-demo/. diff --git a/res/requirements.txt b/res/requirements.txt new file mode 100644 index 00000000..d5fc4dd8 --- /dev/null +++ b/res/requirements.txt @@ -0,0 +1,2 @@ +boto3 +jinja2 diff --git a/res/res-demo-with-cidr/bi.yaml b/res/res-demo-with-cidr/bi.yaml new file mode 100644 index 00000000..3ac9d78c --- /dev/null +++ b/res/res-demo-with-cidr/bi.yaml @@ -0,0 +1,639 @@ +Description: A set of external resources that can support a Research and Engineering Studio Environment deployment + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Vpc Configuration" + Parameters: + - VpcCidrBlock + - VpcCidrPublicSubnetA + - VpcCidrPublicSubnetB + - VpcCidrPublicSubnetC + - VpcCidrPrivateSubnetA + - VpcCidrPrivateSubnetB + - VpcCidrPrivateSubnetC + - Label: + default: "AD Configuration" + Parameters: + - DomainName + - SubDomain + - AdminPassword + - ServiceAccountPassword + - Label: + default: "AD Management Hosts Configuration" + Parameters: + - Keypair + - LDIFS3Path + - ClientIpCidr + - ClientPrefixList + - StopAdAdminInstances + +Parameters: + VpcCidrBlock: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.0.0/16 + Description: VPC CIDR Block (eg 10.3.0.0/16) + Type: String + + VpcCidrPublicSubnetA: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.0.0/20 + Description: VPC CIDR Block for the Public Subnet A + Type: String + + VpcCidrPublicSubnetB: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.16.0/20 + Description: VPC CIDR Block for the Public Subnet B + Type: String + + VpcCidrPublicSubnetC: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.32.0/20 + Description: VPC CIDR Block for the Public Subnet C + Type: String + + VpcCidrPrivateSubnetA: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.128.0/20 + Description: VPC CIDR Block for the Private Subnet A + Type: String + + VpcCidrPrivateSubnetB: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.144.0/20 + Description: VPC CIDR Block for the Private Subnet B + Type: String + + VpcCidrPrivateSubnetC: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.160.0/20 + Description: VPC CIDR Block for the Private Subnet C + Type: String + + DomainName: + Description: Active Directory Domain Name. The supplied LDIF file which provides bootstrap users uses this domain. A different LDIF file needs to be provided for a different domain. + Type: String + Default: corp.res.com + AllowedPattern: ^([a-zA-Z0-9]+[\\.-])+([a-zA-Z0-9])+$ + SubDomain: + Description: (Optional, but required for GovCloud regions) SubDomain for the Active Directory Domain Name. If provided, Active Directory Domain Name will be {SubDomain}.{DomainName} + Type: String + Default: "" + PortalDomainName: + Description: (Optional) Domain Name for web portal domain that lives in Route53 in account (may be different from the Active Directory domain). Used to generate certs, leave blank to skip certificate generation. + Type: String + Default: "" + EnvironmentName: + Description: (Optional) EnvironmentName must start with "res-"and should be less than or equal to 11 characters. Required to generate certificates. + Type: String + AllowedPattern: ^res-[A-Za-z\-\_0-9]{0,7}$ + Default: res-demo + AdminPassword: + Description: Provide the Active Directory Administrator Account Password Directly or Resource ARN to Secret Containing Password. + Type: String + MinLength: 8 + MaxLength: 2048 + AllowedPattern: (arn:(aws(-cn|-us-gov)?):secretsmanager:(us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d:\d{12}:secret:[a-zA-Z0-9/_+=.@-]+)|(?=^.{8,64}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9\s])(?=.*[a-z])|(?=.*[^A-Za-z0-9\s])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9\s]))^.* + NoEcho: true + ServiceAccountPassword: + Description: Provide the Active Directory Service Account Password Directly or Resource ARN to Secret Containing Password. + Type: String + MinLength: 8 + MaxLength: 2048 + AllowedPattern: (arn:(aws(-cn|-us-gov)?):secretsmanager:(us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d:\d{12}:secret:[a-zA-Z0-9/_+=.@-]+)|(?=^.{8,64}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9\s])(?=.*[a-z])|(?=.*[^A-Za-z0-9\s])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9\s]))^.* + NoEcho: true + LDIFS3Path: + Description: (Optional) An S3 Path (without the s3://) to an LDIF file that will be used during stack creation. + Type: String + Default: aws-hpc-recipes/main/recipes/res/res_demo_env/assets/res.ldif + StopAdAdminInstances: + Description: Automatically stop management instances to save costs. + Type: String + Default: "False" + AllowedValues: + - "True" + - "False" + Keypair: + Description: EC2 Keypair to access AD management instances. + Type: AWS::EC2::KeyPair::KeyName + ClientIpCidr: + Description: CIDR controlling incoming traffic to AD management instances. + Default: "" + Type: String + AllowedPattern: ^((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2}))?$ + ConstraintDescription: ClientIP must be a valid IP or network range of the form x.x.x.x/x. specify your IP/NETMASK (e.g x.x.x/32 or x.x.x.x/24 for subnet range) + ClientPrefixList: + Description: (Optional) VPC Prefix List ID controlling incoming traffic to AD management instances. + Default: "" + Type: String + AllowedPattern: ^(pl-[a-z0-9]{8,20})?$ + ConstraintDescription: Must be a valid VPC Prefix List ID, which begins with `pl-`. Prefix Lists can be configured at https://console.aws.amazon.com/vpcconsole/home#ManagedPrefixLists + EFSThroughputMode: + Description: (Optional) EFS filesystem throughput mode. + Type: String + Default: "bursting" + AllowedValues: + - "elastic" + - "bursting" + RetainStorageResources: + Description: (Optional) Retain the home file system and the RES VPC on RES deletion. Provide `True` to eliminate risk of accidentally deleting data. + Type: String + Default: "True" + AllowedValues: + - "True" + - "False" + +Conditions: + GenerateCerts: !Not [!Equals [!Ref PortalDomainName, ""]] + UseEnvironmentName: !Not [!Equals [!Ref EnvironmentName, ""]] + SubDomainNotProvided: !Equals [!Ref SubDomain, ""] + InGovCloud: !Equals [!Ref 'AWS::Partition', "aws-us-gov"] + RetainStorageAndNetworking: !Equals [!Ref RetainStorageResources, "True"] + +Resources: + +##### Nested stacks + + Networking: + Type: AWS::CloudFormation::Stack + DeletionPolicy: !If [ RetainStorageAndNetworking, Retain, Delete ] + Properties: + Parameters: + CidrBlock: !Ref VpcCidrBlock + CidrPublicSubnetA: !Ref VpcCidrPublicSubnetA + CidrPublicSubnetB: !Ref VpcCidrPublicSubnetB + CidrPublicSubnetC: !Ref VpcCidrPublicSubnetC + CidrPrivateSubnetA: !Ref VpcCidrPrivateSubnetA + CidrPrivateSubnetB: !Ref VpcCidrPrivateSubnetB + CidrPrivateSubnetC: !Ref VpcCidrPrivateSubnetC + ProvisionSubnetsC: "False" + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/net/hpc_large_scale/assets/main.yaml + + DirectoryService: + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + DomainName: !Ref DomainName + SubDomain: !Ref SubDomain + AdminPassword: !GetAtt PasswordResolver.AdminPassword + ServiceAccountPassword: !GetAtt PasswordResolver.ServiceAccountPassword + ServiceAccountName: ServiceAccount + LDIFS3Path: !Ref LDIFS3Path + Keypair: !Ref Keypair + UserName: "" + UserPassword: "" + AllowedIps: !Ref ClientIpCidr + ClientPrefixList: !Ref ClientPrefixList + # StopAdAdminInstance: !Ref StopAdAdminInstances + Vpc: !GetAtt [ Networking, Outputs.VPC ] + PrivateSubnetOne: {"Fn::Select": [0, { "Fn::Split" : [",", !GetAtt [ Networking, Outputs.PrivateSubnets ]] }]} + PrivateSubnetTwo: {"Fn::Select": [1, { "Fn::Split" : [",", !GetAtt [ Networking, Outputs.PrivateSubnets ]] }]} + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/dir/demo_managed_ad/assets/main.yaml + + WindowsManagementHost: + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + DomainName: !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName] ] ] + DelegationUser: Admin + DelegationPassword: !GetAtt PasswordResolver.AdminPassword + DirectoryId: !GetAtt [ DirectoryService, Outputs.DirectoryId ] + DnsIpAddress1: !GetAtt [ DirectoryService, Outputs.DnsIpAddress1 ] + DnsIpAddress2: !GetAtt [ DirectoryService, Outputs.DnsIpAddress2 ] + Keypair: !Ref Keypair + VpcId: !GetAtt [ Networking, Outputs.VPC ] + Subnet: {"Fn::Select": [0, { "Fn::Split" : [",", !GetAtt [ Networking, Outputs.PublicSubnets ]] }]} + StopAdAdminInstance: !Ref StopAdAdminInstances + ClientIpCidr: !Ref ClientIpCidr + ClientPrefixList: !Ref ClientPrefixList + PSS3Path: aws-hpc-recipes/main/recipes/res/res_demo_env/assets/service_account.ps1 + PSS3PathRegion: !If [InGovCloud, "us-gov-west-1", "us-east-1"] + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/dir/demo_managed_ad/assets/windows_management_host.yaml + + Storage: + Type: AWS::CloudFormation::Stack + DeletionPolicy: !If [ RetainStorageAndNetworking, Retain, Delete ] + Properties: + Parameters: + VpcId: !GetAtt [ Networking, Outputs.VPC ] + SubnetCount: 2 + SubnetIds: !GetAtt [ Networking, Outputs.PrivateSubnets ] + ThroughputMode: !Ref EFSThroughputMode + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/storage/efs_simple/assets/main.yaml + + Certs: + Condition: GenerateCerts + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + SubnetId: {"Fn::Select": [0, { "Fn::Split" : [",", !GetAtt [ Networking, Outputs.PrivateSubnets ]] }]} + DomainName: !Ref PortalDomainName + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/security/public_certs/assets/main.yaml + Tags: + - Key: "res:EnvironmentName" + Value: !Ref EnvironmentName + - Key: "res:ClusterName" + Value: !Ref EnvironmentName + - Key: "res:ModuleName" + Value: virtual-desktop-controller + - Key: "bi:Deployment" + Value: "true" + + ACMCertificate: + Condition: GenerateCerts + Type: AWS::CertificateManager::Certificate + Properties: + DomainName: !Ref PortalDomainName + ValidationMethod: DNS + SubjectAlternativeNames: + - !Sub ["*.${PortalDomainName}", {PortalDomainName: !Ref PortalDomainName }] + DomainValidationOptions: + - DomainName: !Ref PortalDomainName + HostedZoneId: !GetAtt HostedZoneResolver.HostedZoneId + +##### Custom Operations + + HostedZoneResolverRole: + Type: AWS::IAM::Role + Condition: GenerateCerts + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Policies: + - PolicyName: LogOutput + PolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Effect: Allow + # Resource: !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-Prep:* + Resource: '*' + - PolicyName: ListHostedZones + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - route53:ListHostedZones + Resource: '*' + + HostedZoneResolverLambda: + Type: AWS::Lambda::Function + Condition: GenerateCerts + Properties: + Description: !Sub "${AWS::StackName}: custom resource handler to resolve hosted zones." + Handler: index.handler + MemorySize: 128 + Role: !GetAtt HostedZoneResolverRole.Arn + Runtime: python3.9 + Timeout: 300 + TracingConfig: + Mode: Active + Code: + ZipFile: | + import cfnresponse + import boto3 + import logging + import random + import string + import re + logger = logging.getLogger() + logger.setLevel(logging.INFO) + route53 = boto3.client("route53") + + def create_physical_resource_id(): + alnum = string.ascii_uppercase + string.ascii_lowercase + string.digits + return "".join(random.choice(alnum) for _ in range(16)) + + def handler(event, context): + try: + print(event) + print( 'boto version {}'.format(boto3.__version__)) + + portal_domain_name = event['ResourceProperties']['PortalDomainName'] + + response_data = {} + reason = None + response_status = cfnresponse.SUCCESS + + if event['RequestType'] == 'Create': + response_data['Message'] = 'Resource creation successful!' + physical_resource_id = create_physical_resource_id() + + # use route53 to find the right hosted zone + zone_data = route53.list_hosted_zones() + zones = filter(lambda x: f"{portal_domain_name}.".endswith(x['Name']), zone_data['HostedZones']) + for zone in zones: + zone_id = re.match(r'\/hostedzone\/(.*)', zone['Id']).groups(0)[0] + logger.info(f"hosted zone = {zone} for portal_domain_name {portal_domain_name} with zone_id {zone_id}") + response_data['HostedZoneId'] = zone_id + else: + physical_resource_id = event['PhysicalResourceId'] + cfnresponse.send(event, context, response_status, response_data, physical_resource_id, reason) + except Exception as e: + cfnresponse.send(event, context, cfnresponse.FAILED, {"error": str(e)}) + + HostedZoneResolver: + Type: Custom::HostedZoneResolver + Condition: GenerateCerts + Properties: + ServiceToken: !GetAtt HostedZoneResolverLambda.Arn + PortalDomainName: !Ref PortalDomainName + + EFSSecurityGroupRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Policies: + - PolicyName: LogOutput + PolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Effect: Allow + Resource: '*' + - PolicyName: EC2DescribeVpcs + PolicyDocument: + Statement: + - Action: + - ec2:DescribeVpcs + Effect: Allow + Resource: '*' + # - !Sub ["arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${VpcID}", VpcID: !GetAtt [ Networking, Outputs.VPC ]] + - PolicyName: EFSRead + PolicyDocument: + Statement: + - Action: + - elasticfilesystem:DescribeFileSystems + - elasticfilesystem:DescribeMountTargets + - elasticfilesystem:DescribeMountTargetSecurityGroups + Effect: Allow + Resource: + - !Sub ["arn:${AWS::Partition}:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:file-system/${FSID}", FSID: !GetAtt [ Storage, Outputs.EFSFilesystemId ]] + - PolicyName: EFSAttribute + PolicyDocument: + Statement: + - Action: + - ec2:DescribeNetworkInterfaceAttribute + Effect: Allow + Resource: + - '*' + - PolicyName: EC2AddIngressRule + PolicyDocument: + Statement: + - Action: + - ec2:AuthorizeSecurityGroupIngress + - ec2:CreateTags + Effect: Allow + Resource: + - '*' + #- !Sub ["arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:security-group/${SGID}", SGID: !GetAtt [ Storage, Outputs.SecurityGroupId ]] + + EFSSecurityGroupLambda: + Type: AWS::Lambda::Function + Properties: + Description: !Sub "${AWS::StackName}: custom resource handler to finish setting up stack after other resources have been created." + Handler: index.handler + MemorySize: 128 + Role: !GetAtt EFSSecurityGroupRole.Arn + Runtime: python3.9 + Timeout: 300 + TracingConfig: + Mode: Active + Code: + ZipFile: | + import cfnresponse + import boto3 + import logging + import random + import string + import secrets + import json + logger = logging.getLogger() + logger.setLevel(logging.INFO) + ec2 = boto3.client("ec2") + efs = boto3.client("efs") + + def create_physical_resource_id(): + alnum = string.ascii_uppercase + string.ascii_lowercase + string.digits + return "".join(random.choice(alnum) for _ in range(16)) + + def handler(event, context): + print(event) + print( 'boto version {}'.format(boto3.__version__)) + + vpc_id = event['ResourceProperties']['VpcId'] + efs_id = event['ResourceProperties']['EfsId'] + + response_data = {} + reason = None + response_status = cfnresponse.SUCCESS + + if event['RequestType'] == 'Create': + response_data['Message'] = 'Resource creation successful!' + physical_resource_id = create_physical_resource_id() + + cidr = ec2.describe_vpcs(VpcIds=[vpc_id])["Vpcs"][0]['CidrBlock'] + fss = efs.describe_file_systems(FileSystemId=efs_id) + + mt = efs.describe_mount_targets(FileSystemId=efs_id)["MountTargets"] + mt_id = mt[0]['MountTargetId'] + + sg = efs.describe_mount_target_security_groups(MountTargetId=mt_id)["SecurityGroups"][0] + ec2.authorize_security_group_ingress(GroupId=sg, CidrIp=cidr, FromPort=2049, ToPort=2049, IpProtocol='tcp', ) + + else: + physical_resource_id = event['PhysicalResourceId'] + cfnresponse.send(event, context, response_status, response_data, physical_resource_id, reason) + + EFSSecurityGroup: + Type: Custom::EFSSecurityGroup + Properties: + ServiceToken: !GetAtt EFSSecurityGroupLambda.Arn + VpcId: !GetAtt [ Networking, Outputs.VPC ] + EfsId: !GetAtt [ Storage, Outputs.EFSFilesystemId ] + + PasswordResolverRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Policies: + - PolicyName: AdminServicePassword + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - secretsmanager:GetSecretValue + Resource: '*' + Condition: + StringEquals: + secretsmanager:ResourceTag/res:Deployment: + - "true" + + PasswordResolverLambda: + Type: AWS::Lambda::Function + Properties: + Description: !Sub "${AWS::StackName}: custom resource handler to resolve password config for Admin and Service Account." + Handler: index.handler + MemorySize: 128 + Role: !GetAtt PasswordResolverRole.Arn + Runtime: python3.9 + Timeout: 300 + TracingConfig: + Mode: Active + Code: + ZipFile: | + import re + import time + import json + import cfnresponse + import boto3 + import random + import string + secrets = boto3.client("secretsmanager") + + def create_physical_resource_id(): + alnum = string.ascii_uppercase + string.ascii_lowercase + string.digits + return "".join(random.choice(alnum) for _ in range(16)) + + def handler(event, context): + admin_password = event['ResourceProperties']['AdminPassword'] + service_account_password = event['ResourceProperties']['ServiceAccountPassword'] + + response_data = {} + reason = None + response_status = cfnresponse.SUCCESS + + if event['RequestType'] == 'Create': + try: + response_data['Message'] = 'Resource creation successful!' + physical_resource_id = create_physical_resource_id() + + secretsmanager_arn_regex_pattern = r"(arn:(aws(-cn|-us-gov)?):secretsmanager:(us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d:\d{12}:secret:[a-zA-Z0-9/_+=.@-]+)" + admin_arn_match = re.search(secretsmanager_arn_regex_pattern, admin_password) + service_account_arn_match = re.search(secretsmanager_arn_regex_pattern, service_account_password) + + resolved_admin_password = json.loads(secrets.get_secret_value(SecretId=admin_password)['SecretString'])["password"] if admin_arn_match else admin_password + resolved_service_account_password = json.loads(secrets.get_secret_value(SecretId=service_account_password)['SecretString'])["password"] if service_account_arn_match else service_account_password + + # provide outputs + response_data['AdminPassword'] = resolved_admin_password + response_data['ServiceAccountPassword'] = resolved_service_account_password + except: + response_status = cfnresponse.FAILED + else: + physical_resource_id = event['PhysicalResourceId'] + cfnresponse.send(event, context, response_status, response_data, physical_resource_id, reason) + + PasswordResolver: + Type: Custom::PasswordResolverLambda + Properties: + ServiceToken: !GetAtt PasswordResolverLambda.Arn + AdminPassword: !Ref AdminPassword + ServiceAccountPassword: !Ref ServiceAccountPassword + +Outputs: + VpcId: + Value: !GetAtt [ Networking, Outputs.VPC ] + PrivateSubnets: + Value: !GetAtt [ Networking, Outputs.PrivateSubnets ] + PublicSubnets: + Value: !GetAtt [ Networking, Outputs.PublicSubnets ] + CertificateSecretArn: + Description: ARN for a secret that contains the generated certificate. + Value: !GetAtt [ Certs, Outputs.CertificateArn ] + Condition: GenerateCerts + PrivateKeySecretArn: + Description: ARN for a secret that contains the generated certificate private key. + Value: !GetAtt [ Certs, Outputs.PrivateKeySecretArn ] + Condition: GenerateCerts + ACMCertificateARNforWebApp: + Description: ARN for an ACM Certificate that is generated for the PortalDomainName + Value: !Ref ACMCertificate + Condition: GenerateCerts + EnvironmentName: + Description: Name of Research and Engineering Studio environment. + Value: !Ref EnvironmentName + Condition: UseEnvironmentName + Keypair: + Description: Keypair used for management instances + Value: !Ref Keypair + ActiveDirectoryName: + Description: Fully Qualified Domain Name (FQDN) for your Active Directory + Value: !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName] ] ] + ADShortName: + Description: Please provide the short name in Active directory + Value: !GetAtt [ DirectoryService, Outputs.DomainShortName ] + LDAPConnectionURI: + Value: !Sub + - ldap://${DomainName} + - { DomainName: !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName] ] ] } + SudoersGroupName: + Value: RESAdministrators + LDAPBase: + Value: !Sub + - dc=${dc} + - { dc: !Join [",dc=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName] ] ] ]] } + ServiceAccountUsername: + Value: ServiceAccount + ServiceAccountPasswordSecretArn: + Value: !GetAtt [ DirectoryService, Outputs.PasswordSecretArn ] + ServiceAccountUserDN: + Description: The Distinguished Name (DN) of the ServiceAccount user in your Active Directory + Value: !Sub + - CN=ServiceAccount,OU=Users,OU=${ou},DC=${dc} + - {dc: !Join [",DC=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName]]]]], ou: !GetAtt [ DirectoryService, Outputs.DomainShortName ]} + SharedHomeFilesystemId: + Value: !GetAtt [ Storage, Outputs.EFSFilesystemId ] + UsersOU: + Description: The OU for all users who might join the system. The value provided here is based off of a supplied LDIF file. + Value: !Sub + - OU=Users,OU=RES,OU=${ou},DC=${dc} + - { dc: !Join [",DC=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName]]]]], ou: !GetAtt [ DirectoryService, Outputs.DomainShortName ]} + GroupsOU: + Description: The OU for groups that users belong to who might join the system. The value provided here is based off of a supplied LDIF file. + Value: !Sub + - OU=Users,OU=RES,OU=${ou},DC=${dc} + - { dc: !Join [",DC=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName]]]]], ou: !GetAtt [ DirectoryService, Outputs.DomainShortName ]} + SudoersOU: + Description: The OU for users who should have sudoers permission across all projects. The value provided here is based off of a supplied LDIF file. + Value: !Sub + - OU=Users,OU=RES,OU=${ou},DC=${dc} + - { dc: !Join [",DC=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName]]]]], ou: !GetAtt [ DirectoryService, Outputs.DomainShortName ]} + ComputersOU: + Description: The OU for computers that join the AD. The value provided here is based off of a supplied LDIF file. + Value: !Sub + - OU=Computers,OU=RES,OU=${ou},DC=${dc} + - { dc: !Join [",DC=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName]]]]], ou: !GetAtt [ DirectoryService, Outputs.DomainShortName ]} diff --git a/res/res-demo-with-cidr/res-demo-stack.yaml b/res/res-demo-with-cidr/res-demo-stack.yaml new file mode 100644 index 00000000..1d3b26c1 --- /dev/null +++ b/res/res-demo-with-cidr/res-demo-stack.yaml @@ -0,0 +1,323 @@ +Description: Research and Engineering Studio on AWS demo environment + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: RES Configuration + Parameters: + - EnvironmentName + - AdministratorEmail + - Label: + default: Access Management + Parameters: + - Keypair + - ClientIpCidr + - InboundPrefixList + - Label: + default: AD user and group configuration + Parameters: + - LDIFS3Path + - Label: + default: Optional network configuration + Parameters: + - VpcCidrBlock + - VpcCidrPublicSubnetA + - VpcCidrPublicSubnetB + - VpcCidrPublicSubnetC + - VpcCidrPrivateSubnetA + - VpcCidrPrivateSubnetB + - VpcCidrPrivateSubnetC + +Parameters: + + Keypair: + Description: EC2 Keypair to access management instance. + Type: AWS::EC2::KeyPair::KeyName + Default: "" + + EnvironmentName: + Description: Provide name of the RES Environment. Must be unique for your account and AWS Region. + Type: String + Default: res-demo + MinLength: 5 + MaxLength: 11 + AllowedPattern: ^res-[A-Za-z\-\_0-9]{0,7}$ + ConstraintDescription: EnvironmentName must start with "res-" and should be less than or equal to 11 characters. + + AdministratorEmail: + Type: String + AllowedPattern: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ + + ClientIpCidr: + Description: Default IP(s) allowed to directly access the Web UI, SSH into the bastion host, and access the Windows AD admin host. We recommend that you restrict it with your own IP/subnet (x.x.x.x/32 for your own ip or x.x.x.x/24 for range. Replace x.x.x.x with your own PUBLIC IP. You can get your public IP using tools such as https://ifconfig.co/) + Default: 0.0.0.0/0 + Type: String + AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2}) + ConstraintDescription: Value must be a valid IP or network range of the form x.x.x.x/x. + + InboundPrefixList: + Description: (Optional) VPC Prefix List controlling inbound access to Web UI, bastion host, and Windows AD admin host. + Default: "" + Type: String + AllowedPattern: ^(pl-[a-z0-9]{8,20})?$ + ConstraintDescription: Must be a valid VPC Prefix List ID, which begins with `pl-` or be empty. + + LDIFS3Path: + Description: An S3 Path (without the s3://) to an LDIF file that will be used during stack creation. + Type: String + Default: {{LDIFS3Path}} + + VpcCidrBlock: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.0.0/16 + Description: VPC CIDR Block (eg 10.3.0.0/16) + Type: String + + VpcCidrPublicSubnetA: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.0.0/20 + Description: VPC CIDR Block for the Public Subnet A + Type: String + + VpcCidrPublicSubnetB: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.16.0/20 + Description: VPC CIDR Block for the Public Subnet B + Type: String + + VpcCidrPublicSubnetC: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.32.0/20 + Description: VPC CIDR Block for the Public Subnet C + Type: String + + VpcCidrPrivateSubnetA: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.128.0/20 + Description: VPC CIDR Block for the Private Subnet A + Type: String + + VpcCidrPrivateSubnetB: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.144.0/20 + Description: VPC CIDR Block for the Private Subnet B + Type: String + + VpcCidrPrivateSubnetC: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.160.0/20 + Description: VPC CIDR Block for the Private Subnet C + Type: String + +Conditions: + UseEnvironmentName: !Not [!Equals [!Ref EnvironmentName, ""]] + +Resources: + + AdminPassword: + Type: AWS::SecretsManager::Secret + Properties: + Description: Active Directory Administrator Account Password. + Name: !Sub [ "res-AdminPassword-${StackName}-${StackId}", {StackName: !Select [1, !Split ['/', !Ref 'AWS::StackId']], StackId: !Select [2, !Split ['/', !Ref 'AWS::StackId']]}] + GenerateSecretString: + SecretStringTemplate: '{"username": "Admin"}' + GenerateStringKey: "password" + ExcludePunctuation: true + Tags: + - Key: res:Deployment + Value: "true" + - Key: res:EnvironmentName + Value: !Ref EnvironmentName + + ServiceAccountPassword: + Type: AWS::SecretsManager::Secret + Properties: + Description: Active Directory Service Account Password. + Name: !Sub [ "res-ServiceAccountPassword-${StackName}-${StackId}", {StackName: !Select [1, !Split ['/', !Ref 'AWS::StackId']], StackId: !Select [2, !Split ['/', !Ref 'AWS::StackId']]}] + GenerateSecretString: + SecretStringTemplate: '{"username": "ServiceAccount"}' + GenerateStringKey: "password" + ExcludePunctuation: true + Tags: + - Key: res:Deployment + Value: "true" + - Key: res:EnvironmentName + Value: !Ref EnvironmentName + + RESExternal: + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + LDIFS3Path : !Ref LDIFS3Path + VpcCidrBlock: !Ref VpcCidrBlock + VpcCidrPublicSubnetA: !Ref VpcCidrPublicSubnetA + VpcCidrPublicSubnetB: !Ref VpcCidrPublicSubnetB + VpcCidrPublicSubnetC: !Ref VpcCidrPublicSubnetC + VpcCidrPrivateSubnetA: !Ref VpcCidrPrivateSubnetA + VpcCidrPrivateSubnetB: !Ref VpcCidrPrivateSubnetB + VpcCidrPrivateSubnetC: !Ref VpcCidrPrivateSubnetC + PortalDomainName: "" + Keypair: !Ref Keypair + EnvironmentName: !If [UseEnvironmentName, !Ref EnvironmentName, ""] + AdminPassword: !Ref AdminPassword + ServiceAccountPassword: !Ref ServiceAccountPassword + ClientIpCidr: !Ref ClientIpCidr + ClientPrefixList: !Ref InboundPrefixList + RetainStorageResources: "False" + #TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/res/res_demo_env/assets/bi.yaml + TemplateURL: https://{{TemplateBucket}}.s3.amazonaws.com/{{TemplateBaseKey}}/bi.yaml + + RES: + Type: AWS::CloudFormation::Stack + DependsOn: InvokeDeleteSharedStorageSecurityGroup + Properties: + Parameters: + EnvironmentName: !Ref EnvironmentName + AdministratorEmail: !Ref AdministratorEmail + SSHKeyPair: !Ref Keypair + ClientIp: !Ref ClientIpCidr + ClientPrefixList: !Ref InboundPrefixList + CustomDomainNameforWebApp: "" + ACMCertificateARNforWebApp: "" + CustomDomainNameforVDI: "" + PrivateKeySecretARNforVDI: "" + CertificateSecretARNforVDI: "" + DomainTLSCertificateSecretArn: "" + VpcId: !GetAtt [ RESExternal, Outputs.VpcId ] + LoadBalancerSubnets: !GetAtt [ RESExternal, Outputs.PublicSubnets ] + InfrastructureHostSubnets: !GetAtt [ RESExternal, Outputs.PrivateSubnets ] + VdiSubnets: !GetAtt [ RESExternal, Outputs.PrivateSubnets ] + IsLoadBalancerInternetFacing: "true" + ActiveDirectoryName: !GetAtt [ RESExternal, Outputs.ActiveDirectoryName ] + ADShortName: !GetAtt [ RESExternal, Outputs.ADShortName ] + LDAPBase: !GetAtt [ RESExternal, Outputs.LDAPBase ] + LDAPConnectionURI: !GetAtt [ RESExternal, Outputs.LDAPConnectionURI ] + SudoersGroupName: RESAdministrators + ServiceAccountUsername: !GetAtt [ RESExternal, Outputs.ServiceAccountUsername ] + ServiceAccountPasswordSecretArn: !GetAtt [ RESExternal, Outputs.ServiceAccountPasswordSecretArn ] + UsersOU: !GetAtt [ RESExternal, Outputs.UsersOU ] + GroupsOU: !GetAtt [ RESExternal, Outputs.GroupsOU ] + SudoersOU: !GetAtt [ RESExternal, Outputs.SudoersOU ] + ComputersOU: !GetAtt [ RESExternal, Outputs.ComputersOU ] + SharedHomeFileSystemId: !GetAtt [ RESExternal, Outputs.SharedHomeFilesystemId ] + InfrastructureHostAMI: "" + EnableLdapIDMapping: "True" + IAMPermissionBoundary: "" + DisableADJoin: "False" + ServiceAccountUserDN: !GetAtt [ RESExternal, Outputs.ServiceAccountUserDN ] + TemplateURL: https://research-engineering-studio-us-east-1.s3.amazonaws.com/releases/latest/ResearchAndEngineeringStudio.template.json + + RESSsoKeycloak: + Type: AWS::CloudFormation::Stack + DependsOn: RES + Properties: + Parameters: + EnvironmentName: !Ref EnvironmentName + Keypair: !Ref Keypair + ServiceAccountPasswordSecretArn: !GetAtt [ RESExternal, Outputs.ServiceAccountPasswordSecretArn ] + VpcId: !GetAtt [ RESExternal, Outputs.VpcId ] + PublicSubnet: !Select [0, !Split [",", !GetAtt RESExternal.Outputs.PublicSubnets]] + ServiceAccountUserDN: !GetAtt [ RESExternal, Outputs.ServiceAccountUserDN ] + UsersDN: !GetAtt [ RESExternal, Outputs.LDAPBase ] + LDAPConnectionURI: !GetAtt [ RESExternal, Outputs.LDAPConnectionURI ] + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/res/res_demo_env/assets/res-sso-keycloak.yaml + + InvokeDeleteSharedStorageSecurityGroupRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: 'sts:AssumeRole' + Policies: + - PolicyName: InvokeConfigureSSOLambdaPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + Resource: + - !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${EnvironmentName}-delete_shared_storage_security_group + - Effect: Allow + Action: + - ec2:DescribeSecurityGroups + - ec2:DeleteSecurityGroup + - ec2:DescribeNetworkInterfaces + Resource: '*' + + InvokeDeleteSharedSecurityGroupHandlerFunction: + Type: 'AWS::Lambda::Function' + DependsOn: + - InvokeDeleteSharedStorageSecurityGroupRole + Properties: + Description: 'Deletes the shared storage security group when the stack is deleted.' + FunctionName: !Sub InvokeDeleteSharedSecurityGroupHandlerFunction-${AWS::StackName} + Timeout: 360 # 6 minutes + Role: !GetAtt InvokeDeleteSharedStorageSecurityGroupRole.Arn + Handler: index.handler + Runtime: python3.11 + Code: + ZipFile: | + import boto3 + import os + import logging + import cfnresponse + + logger = logging.getLogger() + logger.setLevel(logging.INFO) + + def handler(event, context): + logger.info(f"Received event: {event}") + response = {} + + if event["RequestType"] == "Delete": + try: + ec2 = boto3.client("ec2") + sgResponse = ec2.describe_security_groups( + Filters=[ + { + 'Name': 'group-name', + 'Values': [ + f"{os.environ['ENVIRONMENT_NAME']}-shared-storage-security-group", + ] + } + ] + ) + + if len(sgResponse['SecurityGroups']) == 0: + response['Output'] = "Shared storage security group not found." + else: + ec2.delete_security_group(GroupId=sgResponse['SecurityGroups'][0]['GroupId']) + response['Output'] = "Shared storage security group deleted." + + cfnresponse.send(event, context, cfnresponse.SUCCESS, response) + except Exception as e: + logger.error(f"Error: Unable to delete shared storage security group: {e}") + response['Output'] = f"Error: Unable to delete shared storage security group: {e}" + cfnresponse.send(event, context, cfnresponse.FAILED, response) + else: + cfnresponse.send(event, context, cfnresponse.SUCCESS, response) + Environment: + Variables: + ENVIRONMENT_NAME: !Ref EnvironmentName + + InvokeDeleteSharedStorageSecurityGroup: + Type: Custom::DeleteSharedStorageSecurityGroup + Properties: + ServiceToken: !GetAtt InvokeDeleteSharedSecurityGroupHandlerFunction.Arn + +Outputs: + KeycloakUrl: + Description: Keycloak Administrator Url + Value: !GetAtt [ RESSsoKeycloak, Outputs.KeycloakUrl ] + KeycloakAdminPasswordSecretArn: + Description: Keycloak password for admin user + Value: !GetAtt [ RESSsoKeycloak, Outputs.KeycloakAdminPasswordSecretArn ] + ApplicationUrl: + Description: RES application Url + Value: !GetAtt [ RESSsoKeycloak, Outputs.ApplicationUrl ] diff --git a/res/res-demo-with-cidr/res.ldif b/res/res-demo-with-cidr/res.ldif new file mode 100644 index 00000000..fc3cb15e --- /dev/null +++ b/res/res-demo-with-cidr/res.ldif @@ -0,0 +1,4069 @@ + +# Create a OU to be used by RES +dn: OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: organizationalUnit +ou: RES +description: The RES application will limit syncing groups and group-members in the RES OU + +# Create a OU to be used by RES to create computers +dn: OU=Computers,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: organizationalUnit +ou: Computers +description: The RES application will limit creating computers to this OU + +# Create a OU to be used by RES to create groups and add users to +dn: OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: organizationalUnit +ou: Users +description: The RES application will limit syncing groups and group-members in the RES OU + +Create a user: admin0001 +dn: CN=admin0001,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin0001 +sAMAccountName: admin0001 +name: admin0001 +userPrincipalName: admin0001@${DirectoryDomain} +mail: admin0001@${DirectoryDomain} +uidNumber: 3001 +gidNumber: 4001 +unixHomeDirectory: /home/admin0001 +loginShell: /bin/bash + +Create a user: admin0002 +dn: CN=admin0002,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin0002 +sAMAccountName: admin0002 +name: admin0002 +userPrincipalName: admin0002@${DirectoryDomain} +mail: admin0002@${DirectoryDomain} +uidNumber: 3002 +gidNumber: 4002 +unixHomeDirectory: /home/admin0002 +loginShell: /bin/bash + +Create a user: admin0003 +dn: CN=admin0003,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin0003 +sAMAccountName: admin0003 +name: admin0003 +userPrincipalName: admin0003@${DirectoryDomain} +mail: admin0003@${DirectoryDomain} +uidNumber: 3003 +gidNumber: 4003 +unixHomeDirectory: /home/admin0003 +loginShell: /bin/bash + +Create a user: admin0004 +dn: CN=admin0004,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin0004 +sAMAccountName: admin0004 +name: admin0004 +userPrincipalName: admin0004@${DirectoryDomain} +mail: admin0004@${DirectoryDomain} +uidNumber: 3004 +gidNumber: 4004 +unixHomeDirectory: /home/admin0004 +loginShell: /bin/bash + +Create a user: admin0005 +dn: CN=admin0005,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin0005 +sAMAccountName: admin0005 +name: admin0005 +userPrincipalName: admin0005@${DirectoryDomain} +mail: admin0005@${DirectoryDomain} +uidNumber: 3005 +gidNumber: 4005 +unixHomeDirectory: /home/admin0005 +loginShell: /bin/bash + +Create a user: admin0006 +dn: CN=admin0006,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin0006 +sAMAccountName: admin0006 +name: admin0006 +userPrincipalName: admin0006@${DirectoryDomain} +mail: admin0006@${DirectoryDomain} +uidNumber: 3006 +gidNumber: 4006 +unixHomeDirectory: /home/admin0006 +loginShell: /bin/bash + +Create a user: admin0007 +dn: CN=admin0007,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin0007 +sAMAccountName: admin0007 +name: admin0007 +userPrincipalName: admin0007@${DirectoryDomain} +mail: admin0007@${DirectoryDomain} +uidNumber: 3007 +gidNumber: 4007 +unixHomeDirectory: /home/admin0007 +loginShell: /bin/bash + +Create a user: admin0008 +dn: CN=admin0008,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin0008 +sAMAccountName: admin0008 +name: admin0008 +userPrincipalName: admin0008@${DirectoryDomain} +mail: admin0008@${DirectoryDomain} +uidNumber: 3008 +gidNumber: 4008 +unixHomeDirectory: /home/admin0008 +loginShell: /bin/bash + +Create a user: admin0009 +dn: CN=admin0009,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin0009 +sAMAccountName: admin0009 +name: admin0009 +userPrincipalName: admin0009@${DirectoryDomain} +mail: admin0009@${DirectoryDomain} +uidNumber: 3009 +gidNumber: 4009 +unixHomeDirectory: /home/admin0009 +loginShell: /bin/bash + +Create a user: admin0010 +dn: CN=admin0010,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin0010 +sAMAccountName: admin0010 +name: admin0010 +userPrincipalName: admin0010@${DirectoryDomain} +mail: admin0010@${DirectoryDomain} +uidNumber: 3010 +gidNumber: 4010 +unixHomeDirectory: /home/admin0010 +loginShell: /bin/bash + +Create a user: resadmin0001 in RES OU +dn: CN=resadmin0001,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resadmin0001 +sAMAccountName: resadmin0001 +name: resadmin0001 +userPrincipalName: resadmin0001@${DirectoryDomain} +mail: resadmin0001@${DirectoryDomain} +uidNumber: 3101 +gidNumber: 4201 +unixHomeDirectory: /home/resadmin0001 +loginShell: /bin/bash + +Create a user: resadmin0002 in RES OU +dn: CN=resadmin0002,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resadmin0002 +sAMAccountName: resadmin0002 +name: resadmin0002 +userPrincipalName: resadmin0002@${DirectoryDomain} +mail: resadmin0002@${DirectoryDomain} +uidNumber: 3102 +gidNumber: 4202 +unixHomeDirectory: /home/resadmin0002 +loginShell: /bin/bash + +Create a user: resadmin0003 in RES OU +dn: CN=resadmin0003,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resadmin0003 +sAMAccountName: resadmin0003 +name: resadmin0003 +userPrincipalName: resadmin0003@${DirectoryDomain} +mail: resadmin0003@${DirectoryDomain} +uidNumber: 3103 +gidNumber: 4203 +unixHomeDirectory: /home/resadmin0003 +loginShell: /bin/bash + +Create a user: resadmin0004 in RES OU +dn: CN=resadmin0004,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resadmin0004 +sAMAccountName: resadmin0004 +name: resadmin0004 +userPrincipalName: resadmin0004@${DirectoryDomain} +mail: resadmin0004@${DirectoryDomain} +uidNumber: 3104 +gidNumber: 4204 +unixHomeDirectory: /home/resadmin0004 +loginShell: /bin/bash + +Create a user: resadmin0005 in RES OU +dn: CN=resadmin0005,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resadmin0005 +sAMAccountName: resadmin0005 +name: resadmin0005 +userPrincipalName: resadmin0005@${DirectoryDomain} +mail: resadmin0005@${DirectoryDomain} +uidNumber: 3105 +gidNumber: 4205 +unixHomeDirectory: /home/resadmin0005 +loginShell: /bin/bash + +Create a user: resadmin0006 in RES OU +dn: CN=resadmin0006,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resadmin0006 +sAMAccountName: resadmin0006 +name: resadmin0006 +userPrincipalName: resadmin0006@${DirectoryDomain} +mail: resadmin0006@${DirectoryDomain} +uidNumber: 3106 +gidNumber: 4206 +unixHomeDirectory: /home/resadmin0006 +loginShell: /bin/bash + +Create a user: resadmin0007 in RES OU +dn: CN=resadmin0007,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resadmin0007 +sAMAccountName: resadmin0007 +name: resadmin0007 +userPrincipalName: resadmin0007@${DirectoryDomain} +mail: resadmin0007@${DirectoryDomain} +uidNumber: 3107 +gidNumber: 4207 +unixHomeDirectory: /home/resadmin0007 +loginShell: /bin/bash + +Create a user: resadmin0008 in RES OU +dn: CN=resadmin0008,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resadmin0008 +sAMAccountName: resadmin0008 +name: resadmin0008 +userPrincipalName: resadmin0008@${DirectoryDomain} +mail: resadmin0008@${DirectoryDomain} +uidNumber: 3108 +gidNumber: 4208 +unixHomeDirectory: /home/resadmin0008 +loginShell: /bin/bash + +Create a user: resadmin0009 in RES OU +dn: CN=resadmin0009,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resadmin0009 +sAMAccountName: resadmin0009 +name: resadmin0009 +userPrincipalName: resadmin0009@${DirectoryDomain} +mail: resadmin0009@${DirectoryDomain} +uidNumber: 3109 +gidNumber: 4209 +unixHomeDirectory: /home/resadmin0009 +loginShell: /bin/bash + +Create a user: resadmin0010 in RES OU +dn: CN=resadmin0010,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resadmin0010 +sAMAccountName: resadmin0010 +name: resadmin0010 +userPrincipalName: resadmin0010@${DirectoryDomain} +mail: resadmin0010@${DirectoryDomain} +uidNumber: 3110 +gidNumber: 4210 +unixHomeDirectory: /home/resadmin0010 +loginShell: /bin/bash + +Create a user: user0001 +dn: CN=user0001,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0001 +sAMAccountName: user0001 +name: user0001 +userPrincipalName: user0001@${DirectoryDomain} +mail: user0001@${DirectoryDomain} +uidNumber: 5001 +gidNumber: 6001 +unixHomeDirectory: /home/user0001 +loginShell: /bin/bash + +Create a user: user0002 +dn: CN=user0002,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0002 +sAMAccountName: user0002 +name: user0002 +userPrincipalName: user0002@${DirectoryDomain} +mail: user0002@${DirectoryDomain} +uidNumber: 5002 +gidNumber: 6002 +unixHomeDirectory: /home/user0002 +loginShell: /bin/bash + +Create a user: user0003 +dn: CN=user0003,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0003 +sAMAccountName: user0003 +name: user0003 +userPrincipalName: user0003@${DirectoryDomain} +mail: user0003@${DirectoryDomain} +uidNumber: 5003 +gidNumber: 6003 +unixHomeDirectory: /home/user0003 +loginShell: /bin/bash + +Create a user: user0004 +dn: CN=user0004,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0004 +sAMAccountName: user0004 +name: user0004 +userPrincipalName: user0004@${DirectoryDomain} +mail: user0004@${DirectoryDomain} +uidNumber: 5004 +gidNumber: 6004 +unixHomeDirectory: /home/user0004 +loginShell: /bin/bash + +Create a user: user0005 +dn: CN=user0005,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0005 +sAMAccountName: user0005 +name: user0005 +userPrincipalName: user0005@${DirectoryDomain} +mail: user0005@${DirectoryDomain} +uidNumber: 5005 +gidNumber: 6005 +unixHomeDirectory: /home/user0005 +loginShell: /bin/bash + +Create a user: user0006 +dn: CN=user0006,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0006 +sAMAccountName: user0006 +name: user0006 +userPrincipalName: user0006@${DirectoryDomain} +mail: user0006@${DirectoryDomain} +uidNumber: 5006 +gidNumber: 6006 +unixHomeDirectory: /home/user0006 +loginShell: /bin/bash + +Create a user: user0007 +dn: CN=user0007,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0007 +sAMAccountName: user0007 +name: user0007 +userPrincipalName: user0007@${DirectoryDomain} +mail: user0007@${DirectoryDomain} +uidNumber: 5007 +gidNumber: 6007 +unixHomeDirectory: /home/user0007 +loginShell: /bin/bash + +Create a user: user0008 +dn: CN=user0008,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0008 +sAMAccountName: user0008 +name: user0008 +userPrincipalName: user0008@${DirectoryDomain} +mail: user0008@${DirectoryDomain} +uidNumber: 5008 +gidNumber: 6008 +unixHomeDirectory: /home/user0008 +loginShell: /bin/bash + +Create a user: user0009 +dn: CN=user0009,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0009 +sAMAccountName: user0009 +name: user0009 +userPrincipalName: user0009@${DirectoryDomain} +mail: user0009@${DirectoryDomain} +uidNumber: 5009 +gidNumber: 6009 +unixHomeDirectory: /home/user0009 +loginShell: /bin/bash + +Create a user: user0010 +dn: CN=user0010,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0010 +sAMAccountName: user0010 +name: user0010 +userPrincipalName: user0010@${DirectoryDomain} +mail: user0010@${DirectoryDomain} +uidNumber: 5010 +gidNumber: 6010 +unixHomeDirectory: /home/user0010 +loginShell: /bin/bash + +Create a user: user0011 +dn: CN=user0011,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0011 +sAMAccountName: user0011 +name: user0011 +userPrincipalName: user0011@${DirectoryDomain} +mail: user0011@${DirectoryDomain} +uidNumber: 5011 +gidNumber: 6011 +unixHomeDirectory: /home/user0011 +loginShell: /bin/bash + +Create a user: user0012 +dn: CN=user0012,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0012 +sAMAccountName: user0012 +name: user0012 +userPrincipalName: user0012@${DirectoryDomain} +mail: user0012@${DirectoryDomain} +uidNumber: 5012 +gidNumber: 6012 +unixHomeDirectory: /home/user0012 +loginShell: /bin/bash + +Create a user: user0013 +dn: CN=user0013,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0013 +sAMAccountName: user0013 +name: user0013 +userPrincipalName: user0013@${DirectoryDomain} +mail: user0013@${DirectoryDomain} +uidNumber: 5013 +gidNumber: 6013 +unixHomeDirectory: /home/user0013 +loginShell: /bin/bash + +Create a user: user0014 +dn: CN=user0014,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0014 +sAMAccountName: user0014 +name: user0014 +userPrincipalName: user0014@${DirectoryDomain} +mail: user0014@${DirectoryDomain} +uidNumber: 5014 +gidNumber: 6014 +unixHomeDirectory: /home/user0014 +loginShell: /bin/bash + +Create a user: user0015 +dn: CN=user0015,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0015 +sAMAccountName: user0015 +name: user0015 +userPrincipalName: user0015@${DirectoryDomain} +mail: user0015@${DirectoryDomain} +uidNumber: 5015 +gidNumber: 6015 +unixHomeDirectory: /home/user0015 +loginShell: /bin/bash + +Create a user: user0016 +dn: CN=user0016,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0016 +sAMAccountName: user0016 +name: user0016 +userPrincipalName: user0016@${DirectoryDomain} +mail: user0016@${DirectoryDomain} +uidNumber: 5016 +gidNumber: 6016 +unixHomeDirectory: /home/user0016 +loginShell: /bin/bash + +Create a user: user0017 +dn: CN=user0017,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0017 +sAMAccountName: user0017 +name: user0017 +userPrincipalName: user0017@${DirectoryDomain} +mail: user0017@${DirectoryDomain} +uidNumber: 5017 +gidNumber: 6017 +unixHomeDirectory: /home/user0017 +loginShell: /bin/bash + +Create a user: user0018 +dn: CN=user0018,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0018 +sAMAccountName: user0018 +name: user0018 +userPrincipalName: user0018@${DirectoryDomain} +mail: user0018@${DirectoryDomain} +uidNumber: 5018 +gidNumber: 6018 +unixHomeDirectory: /home/user0018 +loginShell: /bin/bash + +Create a user: user0019 +dn: CN=user0019,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0019 +sAMAccountName: user0019 +name: user0019 +userPrincipalName: user0019@${DirectoryDomain} +mail: user0019@${DirectoryDomain} +uidNumber: 5019 +gidNumber: 6019 +unixHomeDirectory: /home/user0019 +loginShell: /bin/bash + +Create a user: user0020 +dn: CN=user0020,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0020 +sAMAccountName: user0020 +name: user0020 +userPrincipalName: user0020@${DirectoryDomain} +mail: user0020@${DirectoryDomain} +uidNumber: 5020 +gidNumber: 6020 +unixHomeDirectory: /home/user0020 +loginShell: /bin/bash + +Create a user: user0021 +dn: CN=user0021,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0021 +sAMAccountName: user0021 +name: user0021 +userPrincipalName: user0021@${DirectoryDomain} +mail: user0021@${DirectoryDomain} +uidNumber: 5021 +gidNumber: 6021 +unixHomeDirectory: /home/user0021 +loginShell: /bin/bash + +Create a user: user0022 +dn: CN=user0022,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0022 +sAMAccountName: user0022 +name: user0022 +userPrincipalName: user0022@${DirectoryDomain} +mail: user0022@${DirectoryDomain} +uidNumber: 5022 +gidNumber: 6022 +unixHomeDirectory: /home/user0022 +loginShell: /bin/bash + +Create a user: user0023 +dn: CN=user0023,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0023 +sAMAccountName: user0023 +name: user0023 +userPrincipalName: user0023@${DirectoryDomain} +mail: user0023@${DirectoryDomain} +uidNumber: 5023 +gidNumber: 6023 +unixHomeDirectory: /home/user0023 +loginShell: /bin/bash + +Create a user: user0024 +dn: CN=user0024,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0024 +sAMAccountName: user0024 +name: user0024 +userPrincipalName: user0024@${DirectoryDomain} +mail: user0024@${DirectoryDomain} +uidNumber: 5024 +gidNumber: 6024 +unixHomeDirectory: /home/user0024 +loginShell: /bin/bash + +Create a user: user0025 +dn: CN=user0025,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0025 +sAMAccountName: user0025 +name: user0025 +userPrincipalName: user0025@${DirectoryDomain} +mail: user0025@${DirectoryDomain} +uidNumber: 5025 +gidNumber: 6025 +unixHomeDirectory: /home/user0025 +loginShell: /bin/bash + +Create a user: user0026 +dn: CN=user0026,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0026 +sAMAccountName: user0026 +name: user0026 +userPrincipalName: user0026@${DirectoryDomain} +mail: user0026@${DirectoryDomain} +uidNumber: 5026 +gidNumber: 6026 +unixHomeDirectory: /home/user0026 +loginShell: /bin/bash + +Create a user: user0027 +dn: CN=user0027,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0027 +sAMAccountName: user0027 +name: user0027 +userPrincipalName: user0027@${DirectoryDomain} +mail: user0027@${DirectoryDomain} +uidNumber: 5027 +gidNumber: 6027 +unixHomeDirectory: /home/user0027 +loginShell: /bin/bash + +Create a user: user0028 +dn: CN=user0028,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0028 +sAMAccountName: user0028 +name: user0028 +userPrincipalName: user0028@${DirectoryDomain} +mail: user0028@${DirectoryDomain} +uidNumber: 5028 +gidNumber: 6028 +unixHomeDirectory: /home/user0028 +loginShell: /bin/bash + +Create a user: user0029 +dn: CN=user0029,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0029 +sAMAccountName: user0029 +name: user0029 +userPrincipalName: user0029@${DirectoryDomain} +mail: user0029@${DirectoryDomain} +uidNumber: 5029 +gidNumber: 6029 +unixHomeDirectory: /home/user0029 +loginShell: /bin/bash + +Create a user: user0030 +dn: CN=user0030,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0030 +sAMAccountName: user0030 +name: user0030 +userPrincipalName: user0030@${DirectoryDomain} +mail: user0030@${DirectoryDomain} +uidNumber: 5030 +gidNumber: 6030 +unixHomeDirectory: /home/user0030 +loginShell: /bin/bash + +Create a user: user0031 +dn: CN=user0031,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0031 +sAMAccountName: user0031 +name: user0031 +userPrincipalName: user0031@${DirectoryDomain} +mail: user0031@${DirectoryDomain} +uidNumber: 5031 +gidNumber: 6031 +unixHomeDirectory: /home/user0031 +loginShell: /bin/bash + +Create a user: user0032 +dn: CN=user0032,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0032 +sAMAccountName: user0032 +name: user0032 +userPrincipalName: user0032@${DirectoryDomain} +mail: user0032@${DirectoryDomain} +uidNumber: 5032 +gidNumber: 6032 +unixHomeDirectory: /home/user0032 +loginShell: /bin/bash + +Create a user: user0033 +dn: CN=user0033,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0033 +sAMAccountName: user0033 +name: user0033 +userPrincipalName: user0033@${DirectoryDomain} +mail: user0033@${DirectoryDomain} +uidNumber: 5033 +gidNumber: 6033 +unixHomeDirectory: /home/user0033 +loginShell: /bin/bash + +Create a user: user0034 +dn: CN=user0034,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0034 +sAMAccountName: user0034 +name: user0034 +userPrincipalName: user0034@${DirectoryDomain} +mail: user0034@${DirectoryDomain} +uidNumber: 5034 +gidNumber: 6034 +unixHomeDirectory: /home/user0034 +loginShell: /bin/bash + +Create a user: user0035 +dn: CN=user0035,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0035 +sAMAccountName: user0035 +name: user0035 +userPrincipalName: user0035@${DirectoryDomain} +mail: user0035@${DirectoryDomain} +uidNumber: 5035 +gidNumber: 6035 +unixHomeDirectory: /home/user0035 +loginShell: /bin/bash + +Create a user: user0036 +dn: CN=user0036,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0036 +sAMAccountName: user0036 +name: user0036 +userPrincipalName: user0036@${DirectoryDomain} +mail: user0036@${DirectoryDomain} +uidNumber: 5036 +gidNumber: 6036 +unixHomeDirectory: /home/user0036 +loginShell: /bin/bash + +Create a user: user0037 +dn: CN=user0037,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0037 +sAMAccountName: user0037 +name: user0037 +userPrincipalName: user0037@${DirectoryDomain} +mail: user0037@${DirectoryDomain} +uidNumber: 5037 +gidNumber: 6037 +unixHomeDirectory: /home/user0037 +loginShell: /bin/bash + +Create a user: user0038 +dn: CN=user0038,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0038 +sAMAccountName: user0038 +name: user0038 +userPrincipalName: user0038@${DirectoryDomain} +mail: user0038@${DirectoryDomain} +uidNumber: 5038 +gidNumber: 6038 +unixHomeDirectory: /home/user0038 +loginShell: /bin/bash + +Create a user: user0039 +dn: CN=user0039,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0039 +sAMAccountName: user0039 +name: user0039 +userPrincipalName: user0039@${DirectoryDomain} +mail: user0039@${DirectoryDomain} +uidNumber: 5039 +gidNumber: 6039 +unixHomeDirectory: /home/user0039 +loginShell: /bin/bash + +Create a user: user0040 +dn: CN=user0040,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0040 +sAMAccountName: user0040 +name: user0040 +userPrincipalName: user0040@${DirectoryDomain} +mail: user0040@${DirectoryDomain} +uidNumber: 5040 +gidNumber: 6040 +unixHomeDirectory: /home/user0040 +loginShell: /bin/bash + +Create a user: user0041 +dn: CN=user0041,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0041 +sAMAccountName: user0041 +name: user0041 +userPrincipalName: user0041@${DirectoryDomain} +mail: user0041@${DirectoryDomain} +uidNumber: 5041 +gidNumber: 6041 +unixHomeDirectory: /home/user0041 +loginShell: /bin/bash + +Create a user: user0042 +dn: CN=user0042,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0042 +sAMAccountName: user0042 +name: user0042 +userPrincipalName: user0042@${DirectoryDomain} +mail: user0042@${DirectoryDomain} +uidNumber: 5042 +gidNumber: 6042 +unixHomeDirectory: /home/user0042 +loginShell: /bin/bash + +Create a user: user0043 +dn: CN=user0043,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0043 +sAMAccountName: user0043 +name: user0043 +userPrincipalName: user0043@${DirectoryDomain} +mail: user0043@${DirectoryDomain} +uidNumber: 5043 +gidNumber: 6043 +unixHomeDirectory: /home/user0043 +loginShell: /bin/bash + +Create a user: user0044 +dn: CN=user0044,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0044 +sAMAccountName: user0044 +name: user0044 +userPrincipalName: user0044@${DirectoryDomain} +mail: user0044@${DirectoryDomain} +uidNumber: 5044 +gidNumber: 6044 +unixHomeDirectory: /home/user0044 +loginShell: /bin/bash + +Create a user: user0045 +dn: CN=user0045,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0045 +sAMAccountName: user0045 +name: user0045 +userPrincipalName: user0045@${DirectoryDomain} +mail: user0045@${DirectoryDomain} +uidNumber: 5045 +gidNumber: 6045 +unixHomeDirectory: /home/user0045 +loginShell: /bin/bash + +Create a user: user0046 +dn: CN=user0046,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0046 +sAMAccountName: user0046 +name: user0046 +userPrincipalName: user0046@${DirectoryDomain} +mail: user0046@${DirectoryDomain} +uidNumber: 5046 +gidNumber: 6046 +unixHomeDirectory: /home/user0046 +loginShell: /bin/bash + +Create a user: user0047 +dn: CN=user0047,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0047 +sAMAccountName: user0047 +name: user0047 +userPrincipalName: user0047@${DirectoryDomain} +mail: user0047@${DirectoryDomain} +uidNumber: 5047 +gidNumber: 6047 +unixHomeDirectory: /home/user0047 +loginShell: /bin/bash + +Create a user: user0048 +dn: CN=user0048,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0048 +sAMAccountName: user0048 +name: user0048 +userPrincipalName: user0048@${DirectoryDomain} +mail: user0048@${DirectoryDomain} +uidNumber: 5048 +gidNumber: 6048 +unixHomeDirectory: /home/user0048 +loginShell: /bin/bash + +Create a user: user0049 +dn: CN=user0049,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0049 +sAMAccountName: user0049 +name: user0049 +userPrincipalName: user0049@${DirectoryDomain} +mail: user0049@${DirectoryDomain} +uidNumber: 5049 +gidNumber: 6049 +unixHomeDirectory: /home/user0049 +loginShell: /bin/bash + +Create a user: user0050 +dn: CN=user0050,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0050 +sAMAccountName: user0050 +name: user0050 +userPrincipalName: user0050@${DirectoryDomain} +mail: user0050@${DirectoryDomain} +uidNumber: 5050 +gidNumber: 6050 +unixHomeDirectory: /home/user0050 +loginShell: /bin/bash + +Create a user: user0051 +dn: CN=user0051,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0051 +sAMAccountName: user0051 +name: user0051 +userPrincipalName: user0051@${DirectoryDomain} +mail: user0051@${DirectoryDomain} +uidNumber: 5051 +gidNumber: 6051 +unixHomeDirectory: /home/user0051 +loginShell: /bin/bash + +Create a user: user0052 +dn: CN=user0052,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0052 +sAMAccountName: user0052 +name: user0052 +userPrincipalName: user0052@${DirectoryDomain} +mail: user0052@${DirectoryDomain} +uidNumber: 5052 +gidNumber: 6052 +unixHomeDirectory: /home/user0052 +loginShell: /bin/bash + +Create a user: user0053 +dn: CN=user0053,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0053 +sAMAccountName: user0053 +name: user0053 +userPrincipalName: user0053@${DirectoryDomain} +mail: user0053@${DirectoryDomain} +uidNumber: 5053 +gidNumber: 6053 +unixHomeDirectory: /home/user0053 +loginShell: /bin/bash + +Create a user: user0054 +dn: CN=user0054,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0054 +sAMAccountName: user0054 +name: user0054 +userPrincipalName: user0054@${DirectoryDomain} +mail: user0054@${DirectoryDomain} +uidNumber: 5054 +gidNumber: 6054 +unixHomeDirectory: /home/user0054 +loginShell: /bin/bash + +Create a user: user0055 +dn: CN=user0055,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0055 +sAMAccountName: user0055 +name: user0055 +userPrincipalName: user0055@${DirectoryDomain} +mail: user0055@${DirectoryDomain} +uidNumber: 5055 +gidNumber: 6055 +unixHomeDirectory: /home/user0055 +loginShell: /bin/bash + +Create a user: user0056 +dn: CN=user0056,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0056 +sAMAccountName: user0056 +name: user0056 +userPrincipalName: user0056@${DirectoryDomain} +mail: user0056@${DirectoryDomain} +uidNumber: 5056 +gidNumber: 6056 +unixHomeDirectory: /home/user0056 +loginShell: /bin/bash + +Create a user: user0057 +dn: CN=user0057,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0057 +sAMAccountName: user0057 +name: user0057 +userPrincipalName: user0057@${DirectoryDomain} +mail: user0057@${DirectoryDomain} +uidNumber: 5057 +gidNumber: 6057 +unixHomeDirectory: /home/user0057 +loginShell: /bin/bash + +Create a user: user0058 +dn: CN=user0058,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0058 +sAMAccountName: user0058 +name: user0058 +userPrincipalName: user0058@${DirectoryDomain} +mail: user0058@${DirectoryDomain} +uidNumber: 5058 +gidNumber: 6058 +unixHomeDirectory: /home/user0058 +loginShell: /bin/bash + +Create a user: user0059 +dn: CN=user0059,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0059 +sAMAccountName: user0059 +name: user0059 +userPrincipalName: user0059@${DirectoryDomain} +mail: user0059@${DirectoryDomain} +uidNumber: 5059 +gidNumber: 6059 +unixHomeDirectory: /home/user0059 +loginShell: /bin/bash + +Create a user: user0060 +dn: CN=user0060,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0060 +sAMAccountName: user0060 +name: user0060 +userPrincipalName: user0060@${DirectoryDomain} +mail: user0060@${DirectoryDomain} +uidNumber: 5060 +gidNumber: 6060 +unixHomeDirectory: /home/user0060 +loginShell: /bin/bash + +Create a user: user0061 +dn: CN=user0061,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0061 +sAMAccountName: user0061 +name: user0061 +userPrincipalName: user0061@${DirectoryDomain} +mail: user0061@${DirectoryDomain} +uidNumber: 5061 +gidNumber: 6061 +unixHomeDirectory: /home/user0061 +loginShell: /bin/bash + +Create a user: user0062 +dn: CN=user0062,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0062 +sAMAccountName: user0062 +name: user0062 +userPrincipalName: user0062@${DirectoryDomain} +mail: user0062@${DirectoryDomain} +uidNumber: 5062 +gidNumber: 6062 +unixHomeDirectory: /home/user0062 +loginShell: /bin/bash + +Create a user: user0063 +dn: CN=user0063,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0063 +sAMAccountName: user0063 +name: user0063 +userPrincipalName: user0063@${DirectoryDomain} +mail: user0063@${DirectoryDomain} +uidNumber: 5063 +gidNumber: 6063 +unixHomeDirectory: /home/user0063 +loginShell: /bin/bash + +Create a user: user0064 +dn: CN=user0064,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0064 +sAMAccountName: user0064 +name: user0064 +userPrincipalName: user0064@${DirectoryDomain} +mail: user0064@${DirectoryDomain} +uidNumber: 5064 +gidNumber: 6064 +unixHomeDirectory: /home/user0064 +loginShell: /bin/bash + +Create a user: user0065 +dn: CN=user0065,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0065 +sAMAccountName: user0065 +name: user0065 +userPrincipalName: user0065@${DirectoryDomain} +mail: user0065@${DirectoryDomain} +uidNumber: 5065 +gidNumber: 6065 +unixHomeDirectory: /home/user0065 +loginShell: /bin/bash + +Create a user: user0066 +dn: CN=user0066,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0066 +sAMAccountName: user0066 +name: user0066 +userPrincipalName: user0066@${DirectoryDomain} +mail: user0066@${DirectoryDomain} +uidNumber: 5066 +gidNumber: 6066 +unixHomeDirectory: /home/user0066 +loginShell: /bin/bash + +Create a user: user0067 +dn: CN=user0067,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0067 +sAMAccountName: user0067 +name: user0067 +userPrincipalName: user0067@${DirectoryDomain} +mail: user0067@${DirectoryDomain} +uidNumber: 5067 +gidNumber: 6067 +unixHomeDirectory: /home/user0067 +loginShell: /bin/bash + +Create a user: user0068 +dn: CN=user0068,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0068 +sAMAccountName: user0068 +name: user0068 +userPrincipalName: user0068@${DirectoryDomain} +mail: user0068@${DirectoryDomain} +uidNumber: 5068 +gidNumber: 6068 +unixHomeDirectory: /home/user0068 +loginShell: /bin/bash + +Create a user: user0069 +dn: CN=user0069,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0069 +sAMAccountName: user0069 +name: user0069 +userPrincipalName: user0069@${DirectoryDomain} +mail: user0069@${DirectoryDomain} +uidNumber: 5069 +gidNumber: 6069 +unixHomeDirectory: /home/user0069 +loginShell: /bin/bash + +Create a user: user0070 +dn: CN=user0070,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0070 +sAMAccountName: user0070 +name: user0070 +userPrincipalName: user0070@${DirectoryDomain} +mail: user0070@${DirectoryDomain} +uidNumber: 5070 +gidNumber: 6070 +unixHomeDirectory: /home/user0070 +loginShell: /bin/bash + +Create a user: user0071 +dn: CN=user0071,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0071 +sAMAccountName: user0071 +name: user0071 +userPrincipalName: user0071@${DirectoryDomain} +mail: user0071@${DirectoryDomain} +uidNumber: 5071 +gidNumber: 6071 +unixHomeDirectory: /home/user0071 +loginShell: /bin/bash + +Create a user: user0072 +dn: CN=user0072,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0072 +sAMAccountName: user0072 +name: user0072 +userPrincipalName: user0072@${DirectoryDomain} +mail: user0072@${DirectoryDomain} +uidNumber: 5072 +gidNumber: 6072 +unixHomeDirectory: /home/user0072 +loginShell: /bin/bash + +Create a user: user0073 +dn: CN=user0073,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0073 +sAMAccountName: user0073 +name: user0073 +userPrincipalName: user0073@${DirectoryDomain} +mail: user0073@${DirectoryDomain} +uidNumber: 5073 +gidNumber: 6073 +unixHomeDirectory: /home/user0073 +loginShell: /bin/bash + +Create a user: user0074 +dn: CN=user0074,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0074 +sAMAccountName: user0074 +name: user0074 +userPrincipalName: user0074@${DirectoryDomain} +mail: user0074@${DirectoryDomain} +uidNumber: 5074 +gidNumber: 6074 +unixHomeDirectory: /home/user0074 +loginShell: /bin/bash + +Create a user: user0075 +dn: CN=user0075,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0075 +sAMAccountName: user0075 +name: user0075 +userPrincipalName: user0075@${DirectoryDomain} +mail: user0075@${DirectoryDomain} +uidNumber: 5075 +gidNumber: 6075 +unixHomeDirectory: /home/user0075 +loginShell: /bin/bash + +Create a user: user0076 +dn: CN=user0076,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0076 +sAMAccountName: user0076 +name: user0076 +userPrincipalName: user0076@${DirectoryDomain} +mail: user0076@${DirectoryDomain} +uidNumber: 5076 +gidNumber: 6076 +unixHomeDirectory: /home/user0076 +loginShell: /bin/bash + +Create a user: user0077 +dn: CN=user0077,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0077 +sAMAccountName: user0077 +name: user0077 +userPrincipalName: user0077@${DirectoryDomain} +mail: user0077@${DirectoryDomain} +uidNumber: 5077 +gidNumber: 6077 +unixHomeDirectory: /home/user0077 +loginShell: /bin/bash + +Create a user: user0078 +dn: CN=user0078,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0078 +sAMAccountName: user0078 +name: user0078 +userPrincipalName: user0078@${DirectoryDomain} +mail: user0078@${DirectoryDomain} +uidNumber: 5078 +gidNumber: 6078 +unixHomeDirectory: /home/user0078 +loginShell: /bin/bash + +Create a user: user0079 +dn: CN=user0079,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0079 +sAMAccountName: user0079 +name: user0079 +userPrincipalName: user0079@${DirectoryDomain} +mail: user0079@${DirectoryDomain} +uidNumber: 5079 +gidNumber: 6079 +unixHomeDirectory: /home/user0079 +loginShell: /bin/bash + +Create a user: user0080 +dn: CN=user0080,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0080 +sAMAccountName: user0080 +name: user0080 +userPrincipalName: user0080@${DirectoryDomain} +mail: user0080@${DirectoryDomain} +uidNumber: 5080 +gidNumber: 6080 +unixHomeDirectory: /home/user0080 +loginShell: /bin/bash + +Create a user: user0081 +dn: CN=user0081,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0081 +sAMAccountName: user0081 +name: user0081 +userPrincipalName: user0081@${DirectoryDomain} +mail: user0081@${DirectoryDomain} +uidNumber: 5081 +gidNumber: 6081 +unixHomeDirectory: /home/user0081 +loginShell: /bin/bash + +Create a user: user0082 +dn: CN=user0082,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0082 +sAMAccountName: user0082 +name: user0082 +userPrincipalName: user0082@${DirectoryDomain} +mail: user0082@${DirectoryDomain} +uidNumber: 5082 +gidNumber: 6082 +unixHomeDirectory: /home/user0082 +loginShell: /bin/bash + +Create a user: user0083 +dn: CN=user0083,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0083 +sAMAccountName: user0083 +name: user0083 +userPrincipalName: user0083@${DirectoryDomain} +mail: user0083@${DirectoryDomain} +uidNumber: 5083 +gidNumber: 6083 +unixHomeDirectory: /home/user0083 +loginShell: /bin/bash + +Create a user: user0084 +dn: CN=user0084,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0084 +sAMAccountName: user0084 +name: user0084 +userPrincipalName: user0084@${DirectoryDomain} +mail: user0084@${DirectoryDomain} +uidNumber: 5084 +gidNumber: 6084 +unixHomeDirectory: /home/user0084 +loginShell: /bin/bash + +Create a user: user0085 +dn: CN=user0085,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0085 +sAMAccountName: user0085 +name: user0085 +userPrincipalName: user0085@${DirectoryDomain} +mail: user0085@${DirectoryDomain} +uidNumber: 5085 +gidNumber: 6085 +unixHomeDirectory: /home/user0085 +loginShell: /bin/bash + +Create a user: user0086 +dn: CN=user0086,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0086 +sAMAccountName: user0086 +name: user0086 +userPrincipalName: user0086@${DirectoryDomain} +mail: user0086@${DirectoryDomain} +uidNumber: 5086 +gidNumber: 6086 +unixHomeDirectory: /home/user0086 +loginShell: /bin/bash + +Create a user: user0087 +dn: CN=user0087,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0087 +sAMAccountName: user0087 +name: user0087 +userPrincipalName: user0087@${DirectoryDomain} +mail: user0087@${DirectoryDomain} +uidNumber: 5087 +gidNumber: 6087 +unixHomeDirectory: /home/user0087 +loginShell: /bin/bash + +Create a user: user0088 +dn: CN=user0088,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0088 +sAMAccountName: user0088 +name: user0088 +userPrincipalName: user0088@${DirectoryDomain} +mail: user0088@${DirectoryDomain} +uidNumber: 5088 +gidNumber: 6088 +unixHomeDirectory: /home/user0088 +loginShell: /bin/bash + +Create a user: user0089 +dn: CN=user0089,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0089 +sAMAccountName: user0089 +name: user0089 +userPrincipalName: user0089@${DirectoryDomain} +mail: user0089@${DirectoryDomain} +uidNumber: 5089 +gidNumber: 6089 +unixHomeDirectory: /home/user0089 +loginShell: /bin/bash + +Create a user: user0090 +dn: CN=user0090,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0090 +sAMAccountName: user0090 +name: user0090 +userPrincipalName: user0090@${DirectoryDomain} +mail: user0090@${DirectoryDomain} +uidNumber: 5090 +gidNumber: 6090 +unixHomeDirectory: /home/user0090 +loginShell: /bin/bash + +Create a user: user0091 +dn: CN=user0091,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0091 +sAMAccountName: user0091 +name: user0091 +userPrincipalName: user0091@${DirectoryDomain} +mail: user0091@${DirectoryDomain} +uidNumber: 5091 +gidNumber: 6091 +unixHomeDirectory: /home/user0091 +loginShell: /bin/bash + +Create a user: user0092 +dn: CN=user0092,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0092 +sAMAccountName: user0092 +name: user0092 +userPrincipalName: user0092@${DirectoryDomain} +mail: user0092@${DirectoryDomain} +uidNumber: 5092 +gidNumber: 6092 +unixHomeDirectory: /home/user0092 +loginShell: /bin/bash + +Create a user: user0093 +dn: CN=user0093,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0093 +sAMAccountName: user0093 +name: user0093 +userPrincipalName: user0093@${DirectoryDomain} +mail: user0093@${DirectoryDomain} +uidNumber: 5093 +gidNumber: 6093 +unixHomeDirectory: /home/user0093 +loginShell: /bin/bash + +Create a user: user0094 +dn: CN=user0094,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0094 +sAMAccountName: user0094 +name: user0094 +userPrincipalName: user0094@${DirectoryDomain} +mail: user0094@${DirectoryDomain} +uidNumber: 5094 +gidNumber: 6094 +unixHomeDirectory: /home/user0094 +loginShell: /bin/bash + +Create a user: user0095 +dn: CN=user0095,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0095 +sAMAccountName: user0095 +name: user0095 +userPrincipalName: user0095@${DirectoryDomain} +mail: user0095@${DirectoryDomain} +uidNumber: 5095 +gidNumber: 6095 +unixHomeDirectory: /home/user0095 +loginShell: /bin/bash + +Create a user: user0096 +dn: CN=user0096,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0096 +sAMAccountName: user0096 +name: user0096 +userPrincipalName: user0096@${DirectoryDomain} +mail: user0096@${DirectoryDomain} +uidNumber: 5096 +gidNumber: 6096 +unixHomeDirectory: /home/user0096 +loginShell: /bin/bash + +Create a user: user0097 +dn: CN=user0097,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0097 +sAMAccountName: user0097 +name: user0097 +userPrincipalName: user0097@${DirectoryDomain} +mail: user0097@${DirectoryDomain} +uidNumber: 5097 +gidNumber: 6097 +unixHomeDirectory: /home/user0097 +loginShell: /bin/bash + +Create a user: user0098 +dn: CN=user0098,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0098 +sAMAccountName: user0098 +name: user0098 +userPrincipalName: user0098@${DirectoryDomain} +mail: user0098@${DirectoryDomain} +uidNumber: 5098 +gidNumber: 6098 +unixHomeDirectory: /home/user0098 +loginShell: /bin/bash + +Create a user: user0099 +dn: CN=user0099,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0099 +sAMAccountName: user0099 +name: user0099 +userPrincipalName: user0099@${DirectoryDomain} +mail: user0099@${DirectoryDomain} +uidNumber: 5099 +gidNumber: 6099 +unixHomeDirectory: /home/user0099 +loginShell: /bin/bash + +Create a user: user0100 +dn: CN=user0100,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user0100 +sAMAccountName: user0100 +name: user0100 +userPrincipalName: user0100@${DirectoryDomain} +mail: user0100@${DirectoryDomain} +uidNumber: 5100 +gidNumber: 6100 +unixHomeDirectory: /home/user0100 +loginShell: /bin/bash + +Create a user: resuser0001 in RES OU +dn: CN=resuser0001,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0001 +sAMAccountName: resuser0001 +name: resuser0001 +userPrincipalName: resuser0001@${DirectoryDomain} +mail: resuser0001@${DirectoryDomain} +uidNumber: 15001 +gidNumber: 16001 +unixHomeDirectory: /home/resuser0001 +loginShell: /bin/bash + +Create a user: resuser0002 in RES OU +dn: CN=resuser0002,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0002 +sAMAccountName: resuser0002 +name: resuser0002 +userPrincipalName: resuser0002@${DirectoryDomain} +mail: resuser0002@${DirectoryDomain} +uidNumber: 15002 +gidNumber: 16002 +unixHomeDirectory: /home/resuser0002 +loginShell: /bin/bash + +Create a user: resuser0003 in RES OU +dn: CN=resuser0003,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0003 +sAMAccountName: resuser0003 +name: resuser0003 +userPrincipalName: resuser0003@${DirectoryDomain} +mail: resuser0003@${DirectoryDomain} +uidNumber: 15003 +gidNumber: 16003 +unixHomeDirectory: /home/resuser0003 +loginShell: /bin/bash + +Create a user: resuser0004 in RES OU +dn: CN=resuser0004,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0004 +sAMAccountName: resuser0004 +name: resuser0004 +userPrincipalName: resuser0004@${DirectoryDomain} +mail: resuser0004@${DirectoryDomain} +uidNumber: 15004 +gidNumber: 16004 +unixHomeDirectory: /home/resuser0004 +loginShell: /bin/bash + +Create a user: resuser0005 in RES OU +dn: CN=resuser0005,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0005 +sAMAccountName: resuser0005 +name: resuser0005 +userPrincipalName: resuser0005@${DirectoryDomain} +mail: resuser0005@${DirectoryDomain} +uidNumber: 15005 +gidNumber: 16005 +unixHomeDirectory: /home/resuser0005 +loginShell: /bin/bash + +Create a user: resuser0006 in RES OU +dn: CN=resuser0006,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0006 +sAMAccountName: resuser0006 +name: resuser0006 +userPrincipalName: resuser0006@${DirectoryDomain} +mail: resuser0006@${DirectoryDomain} +uidNumber: 15006 +gidNumber: 16006 +unixHomeDirectory: /home/resuser0006 +loginShell: /bin/bash + +Create a user: resuser0007 in RES OU +dn: CN=resuser0007,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0007 +sAMAccountName: resuser0007 +name: resuser0007 +userPrincipalName: resuser0007@${DirectoryDomain} +mail: resuser0007@${DirectoryDomain} +uidNumber: 15007 +gidNumber: 16007 +unixHomeDirectory: /home/resuser0007 +loginShell: /bin/bash + +Create a user: resuser0008 in RES OU +dn: CN=resuser0008,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0008 +sAMAccountName: resuser0008 +name: resuser0008 +userPrincipalName: resuser0008@${DirectoryDomain} +mail: resuser0008@${DirectoryDomain} +uidNumber: 15008 +gidNumber: 16008 +unixHomeDirectory: /home/resuser0008 +loginShell: /bin/bash + +Create a user: resuser0009 in RES OU +dn: CN=resuser0009,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0009 +sAMAccountName: resuser0009 +name: resuser0009 +userPrincipalName: resuser0009@${DirectoryDomain} +mail: resuser0009@${DirectoryDomain} +uidNumber: 15009 +gidNumber: 16009 +unixHomeDirectory: /home/resuser0009 +loginShell: /bin/bash + +Create a user: resuser0010 in RES OU +dn: CN=resuser0010,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0010 +sAMAccountName: resuser0010 +name: resuser0010 +userPrincipalName: resuser0010@${DirectoryDomain} +mail: resuser0010@${DirectoryDomain} +uidNumber: 15010 +gidNumber: 16010 +unixHomeDirectory: /home/resuser0010 +loginShell: /bin/bash + +Create a user: resuser0011 in RES OU +dn: CN=resuser0011,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0011 +sAMAccountName: resuser0011 +name: resuser0011 +userPrincipalName: resuser0011@${DirectoryDomain} +mail: resuser0011@${DirectoryDomain} +uidNumber: 15011 +gidNumber: 16011 +unixHomeDirectory: /home/resuser0011 +loginShell: /bin/bash + +Create a user: resuser0012 in RES OU +dn: CN=resuser0012,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0012 +sAMAccountName: resuser0012 +name: resuser0012 +userPrincipalName: resuser0012@${DirectoryDomain} +mail: resuser0012@${DirectoryDomain} +uidNumber: 15012 +gidNumber: 16012 +unixHomeDirectory: /home/resuser0012 +loginShell: /bin/bash + +Create a user: resuser0013 in RES OU +dn: CN=resuser0013,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0013 +sAMAccountName: resuser0013 +name: resuser0013 +userPrincipalName: resuser0013@${DirectoryDomain} +mail: resuser0013@${DirectoryDomain} +uidNumber: 15013 +gidNumber: 16013 +unixHomeDirectory: /home/resuser0013 +loginShell: /bin/bash + +Create a user: resuser0014 in RES OU +dn: CN=resuser0014,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0014 +sAMAccountName: resuser0014 +name: resuser0014 +userPrincipalName: resuser0014@${DirectoryDomain} +mail: resuser0014@${DirectoryDomain} +uidNumber: 15014 +gidNumber: 16014 +unixHomeDirectory: /home/resuser0014 +loginShell: /bin/bash + +Create a user: resuser0015 in RES OU +dn: CN=resuser0015,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0015 +sAMAccountName: resuser0015 +name: resuser0015 +userPrincipalName: resuser0015@${DirectoryDomain} +mail: resuser0015@${DirectoryDomain} +uidNumber: 15015 +gidNumber: 16015 +unixHomeDirectory: /home/resuser0015 +loginShell: /bin/bash + +Create a user: resuser0016 in RES OU +dn: CN=resuser0016,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0016 +sAMAccountName: resuser0016 +name: resuser0016 +userPrincipalName: resuser0016@${DirectoryDomain} +mail: resuser0016@${DirectoryDomain} +uidNumber: 15016 +gidNumber: 16016 +unixHomeDirectory: /home/resuser0016 +loginShell: /bin/bash + +Create a user: resuser0017 in RES OU +dn: CN=resuser0017,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0017 +sAMAccountName: resuser0017 +name: resuser0017 +userPrincipalName: resuser0017@${DirectoryDomain} +mail: resuser0017@${DirectoryDomain} +uidNumber: 15017 +gidNumber: 16017 +unixHomeDirectory: /home/resuser0017 +loginShell: /bin/bash + +Create a user: resuser0018 in RES OU +dn: CN=resuser0018,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0018 +sAMAccountName: resuser0018 +name: resuser0018 +userPrincipalName: resuser0018@${DirectoryDomain} +mail: resuser0018@${DirectoryDomain} +uidNumber: 15018 +gidNumber: 16018 +unixHomeDirectory: /home/resuser0018 +loginShell: /bin/bash + +Create a user: resuser0019 in RES OU +dn: CN=resuser0019,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0019 +sAMAccountName: resuser0019 +name: resuser0019 +userPrincipalName: resuser0019@${DirectoryDomain} +mail: resuser0019@${DirectoryDomain} +uidNumber: 15019 +gidNumber: 16019 +unixHomeDirectory: /home/resuser0019 +loginShell: /bin/bash + +Create a user: resuser0020 in RES OU +dn: CN=resuser0020,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0020 +sAMAccountName: resuser0020 +name: resuser0020 +userPrincipalName: resuser0020@${DirectoryDomain} +mail: resuser0020@${DirectoryDomain} +uidNumber: 15020 +gidNumber: 16020 +unixHomeDirectory: /home/resuser0020 +loginShell: /bin/bash + +Create a user: resuser0021 in RES OU +dn: CN=resuser0021,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0021 +sAMAccountName: resuser0021 +name: resuser0021 +userPrincipalName: resuser0021@${DirectoryDomain} +mail: resuser0021@${DirectoryDomain} +uidNumber: 15021 +gidNumber: 16021 +unixHomeDirectory: /home/resuser0021 +loginShell: /bin/bash + +Create a user: resuser0022 in RES OU +dn: CN=resuser0022,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0022 +sAMAccountName: resuser0022 +name: resuser0022 +userPrincipalName: resuser0022@${DirectoryDomain} +mail: resuser0022@${DirectoryDomain} +uidNumber: 15022 +gidNumber: 16022 +unixHomeDirectory: /home/resuser0022 +loginShell: /bin/bash + +Create a user: resuser0023 in RES OU +dn: CN=resuser0023,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0023 +sAMAccountName: resuser0023 +name: resuser0023 +userPrincipalName: resuser0023@${DirectoryDomain} +mail: resuser0023@${DirectoryDomain} +uidNumber: 15023 +gidNumber: 16023 +unixHomeDirectory: /home/resuser0023 +loginShell: /bin/bash + +Create a user: resuser0024 in RES OU +dn: CN=resuser0024,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0024 +sAMAccountName: resuser0024 +name: resuser0024 +userPrincipalName: resuser0024@${DirectoryDomain} +mail: resuser0024@${DirectoryDomain} +uidNumber: 15024 +gidNumber: 16024 +unixHomeDirectory: /home/resuser0024 +loginShell: /bin/bash + +Create a user: resuser0025 in RES OU +dn: CN=resuser0025,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0025 +sAMAccountName: resuser0025 +name: resuser0025 +userPrincipalName: resuser0025@${DirectoryDomain} +mail: resuser0025@${DirectoryDomain} +uidNumber: 15025 +gidNumber: 16025 +unixHomeDirectory: /home/resuser0025 +loginShell: /bin/bash + +Create a user: resuser0026 in RES OU +dn: CN=resuser0026,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0026 +sAMAccountName: resuser0026 +name: resuser0026 +userPrincipalName: resuser0026@${DirectoryDomain} +mail: resuser0026@${DirectoryDomain} +uidNumber: 15026 +gidNumber: 16026 +unixHomeDirectory: /home/resuser0026 +loginShell: /bin/bash + +Create a user: resuser0027 in RES OU +dn: CN=resuser0027,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0027 +sAMAccountName: resuser0027 +name: resuser0027 +userPrincipalName: resuser0027@${DirectoryDomain} +mail: resuser0027@${DirectoryDomain} +uidNumber: 15027 +gidNumber: 16027 +unixHomeDirectory: /home/resuser0027 +loginShell: /bin/bash + +Create a user: resuser0028 in RES OU +dn: CN=resuser0028,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0028 +sAMAccountName: resuser0028 +name: resuser0028 +userPrincipalName: resuser0028@${DirectoryDomain} +mail: resuser0028@${DirectoryDomain} +uidNumber: 15028 +gidNumber: 16028 +unixHomeDirectory: /home/resuser0028 +loginShell: /bin/bash + +Create a user: resuser0029 in RES OU +dn: CN=resuser0029,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0029 +sAMAccountName: resuser0029 +name: resuser0029 +userPrincipalName: resuser0029@${DirectoryDomain} +mail: resuser0029@${DirectoryDomain} +uidNumber: 15029 +gidNumber: 16029 +unixHomeDirectory: /home/resuser0029 +loginShell: /bin/bash + +Create a user: resuser0030 in RES OU +dn: CN=resuser0030,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0030 +sAMAccountName: resuser0030 +name: resuser0030 +userPrincipalName: resuser0030@${DirectoryDomain} +mail: resuser0030@${DirectoryDomain} +uidNumber: 15030 +gidNumber: 16030 +unixHomeDirectory: /home/resuser0030 +loginShell: /bin/bash + +Create a user: resuser0031 in RES OU +dn: CN=resuser0031,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0031 +sAMAccountName: resuser0031 +name: resuser0031 +userPrincipalName: resuser0031@${DirectoryDomain} +mail: resuser0031@${DirectoryDomain} +uidNumber: 15031 +gidNumber: 16031 +unixHomeDirectory: /home/resuser0031 +loginShell: /bin/bash + +Create a user: resuser0032 in RES OU +dn: CN=resuser0032,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0032 +sAMAccountName: resuser0032 +name: resuser0032 +userPrincipalName: resuser0032@${DirectoryDomain} +mail: resuser0032@${DirectoryDomain} +uidNumber: 15032 +gidNumber: 16032 +unixHomeDirectory: /home/resuser0032 +loginShell: /bin/bash + +Create a user: resuser0033 in RES OU +dn: CN=resuser0033,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0033 +sAMAccountName: resuser0033 +name: resuser0033 +userPrincipalName: resuser0033@${DirectoryDomain} +mail: resuser0033@${DirectoryDomain} +uidNumber: 15033 +gidNumber: 16033 +unixHomeDirectory: /home/resuser0033 +loginShell: /bin/bash + +Create a user: resuser0034 in RES OU +dn: CN=resuser0034,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0034 +sAMAccountName: resuser0034 +name: resuser0034 +userPrincipalName: resuser0034@${DirectoryDomain} +mail: resuser0034@${DirectoryDomain} +uidNumber: 15034 +gidNumber: 16034 +unixHomeDirectory: /home/resuser0034 +loginShell: /bin/bash + +Create a user: resuser0035 in RES OU +dn: CN=resuser0035,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0035 +sAMAccountName: resuser0035 +name: resuser0035 +userPrincipalName: resuser0035@${DirectoryDomain} +mail: resuser0035@${DirectoryDomain} +uidNumber: 15035 +gidNumber: 16035 +unixHomeDirectory: /home/resuser0035 +loginShell: /bin/bash + +Create a user: resuser0036 in RES OU +dn: CN=resuser0036,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0036 +sAMAccountName: resuser0036 +name: resuser0036 +userPrincipalName: resuser0036@${DirectoryDomain} +mail: resuser0036@${DirectoryDomain} +uidNumber: 15036 +gidNumber: 16036 +unixHomeDirectory: /home/resuser0036 +loginShell: /bin/bash + +Create a user: resuser0037 in RES OU +dn: CN=resuser0037,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0037 +sAMAccountName: resuser0037 +name: resuser0037 +userPrincipalName: resuser0037@${DirectoryDomain} +mail: resuser0037@${DirectoryDomain} +uidNumber: 15037 +gidNumber: 16037 +unixHomeDirectory: /home/resuser0037 +loginShell: /bin/bash + +Create a user: resuser0038 in RES OU +dn: CN=resuser0038,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0038 +sAMAccountName: resuser0038 +name: resuser0038 +userPrincipalName: resuser0038@${DirectoryDomain} +mail: resuser0038@${DirectoryDomain} +uidNumber: 15038 +gidNumber: 16038 +unixHomeDirectory: /home/resuser0038 +loginShell: /bin/bash + +Create a user: resuser0039 in RES OU +dn: CN=resuser0039,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0039 +sAMAccountName: resuser0039 +name: resuser0039 +userPrincipalName: resuser0039@${DirectoryDomain} +mail: resuser0039@${DirectoryDomain} +uidNumber: 15039 +gidNumber: 16039 +unixHomeDirectory: /home/resuser0039 +loginShell: /bin/bash + +Create a user: resuser0040 in RES OU +dn: CN=resuser0040,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0040 +sAMAccountName: resuser0040 +name: resuser0040 +userPrincipalName: resuser0040@${DirectoryDomain} +mail: resuser0040@${DirectoryDomain} +uidNumber: 15040 +gidNumber: 16040 +unixHomeDirectory: /home/resuser0040 +loginShell: /bin/bash + +Create a user: resuser0041 in RES OU +dn: CN=resuser0041,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0041 +sAMAccountName: resuser0041 +name: resuser0041 +userPrincipalName: resuser0041@${DirectoryDomain} +mail: resuser0041@${DirectoryDomain} +uidNumber: 15041 +gidNumber: 16041 +unixHomeDirectory: /home/resuser0041 +loginShell: /bin/bash + +Create a user: resuser0042 in RES OU +dn: CN=resuser0042,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0042 +sAMAccountName: resuser0042 +name: resuser0042 +userPrincipalName: resuser0042@${DirectoryDomain} +mail: resuser0042@${DirectoryDomain} +uidNumber: 15042 +gidNumber: 16042 +unixHomeDirectory: /home/resuser0042 +loginShell: /bin/bash + +Create a user: resuser0043 in RES OU +dn: CN=resuser0043,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0043 +sAMAccountName: resuser0043 +name: resuser0043 +userPrincipalName: resuser0043@${DirectoryDomain} +mail: resuser0043@${DirectoryDomain} +uidNumber: 15043 +gidNumber: 16043 +unixHomeDirectory: /home/resuser0043 +loginShell: /bin/bash + +Create a user: resuser0044 in RES OU +dn: CN=resuser0044,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0044 +sAMAccountName: resuser0044 +name: resuser0044 +userPrincipalName: resuser0044@${DirectoryDomain} +mail: resuser0044@${DirectoryDomain} +uidNumber: 15044 +gidNumber: 16044 +unixHomeDirectory: /home/resuser0044 +loginShell: /bin/bash + +Create a user: resuser0045 in RES OU +dn: CN=resuser0045,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0045 +sAMAccountName: resuser0045 +name: resuser0045 +userPrincipalName: resuser0045@${DirectoryDomain} +mail: resuser0045@${DirectoryDomain} +uidNumber: 15045 +gidNumber: 16045 +unixHomeDirectory: /home/resuser0045 +loginShell: /bin/bash + +Create a user: resuser0046 in RES OU +dn: CN=resuser0046,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0046 +sAMAccountName: resuser0046 +name: resuser0046 +userPrincipalName: resuser0046@${DirectoryDomain} +mail: resuser0046@${DirectoryDomain} +uidNumber: 15046 +gidNumber: 16046 +unixHomeDirectory: /home/resuser0046 +loginShell: /bin/bash + +Create a user: resuser0047 in RES OU +dn: CN=resuser0047,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0047 +sAMAccountName: resuser0047 +name: resuser0047 +userPrincipalName: resuser0047@${DirectoryDomain} +mail: resuser0047@${DirectoryDomain} +uidNumber: 15047 +gidNumber: 16047 +unixHomeDirectory: /home/resuser0047 +loginShell: /bin/bash + +Create a user: resuser0048 in RES OU +dn: CN=resuser0048,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0048 +sAMAccountName: resuser0048 +name: resuser0048 +userPrincipalName: resuser0048@${DirectoryDomain} +mail: resuser0048@${DirectoryDomain} +uidNumber: 15048 +gidNumber: 16048 +unixHomeDirectory: /home/resuser0048 +loginShell: /bin/bash + +Create a user: resuser0049 in RES OU +dn: CN=resuser0049,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0049 +sAMAccountName: resuser0049 +name: resuser0049 +userPrincipalName: resuser0049@${DirectoryDomain} +mail: resuser0049@${DirectoryDomain} +uidNumber: 15049 +gidNumber: 16049 +unixHomeDirectory: /home/resuser0049 +loginShell: /bin/bash + +Create a user: resuser0050 in RES OU +dn: CN=resuser0050,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0050 +sAMAccountName: resuser0050 +name: resuser0050 +userPrincipalName: resuser0050@${DirectoryDomain} +mail: resuser0050@${DirectoryDomain} +uidNumber: 15050 +gidNumber: 16050 +unixHomeDirectory: /home/resuser0050 +loginShell: /bin/bash + +Create a user: resuser0051 in RES OU +dn: CN=resuser0051,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0051 +sAMAccountName: resuser0051 +name: resuser0051 +userPrincipalName: resuser0051@${DirectoryDomain} +mail: resuser0051@${DirectoryDomain} +uidNumber: 15051 +gidNumber: 16051 +unixHomeDirectory: /home/resuser0051 +loginShell: /bin/bash + +Create a user: resuser0052 in RES OU +dn: CN=resuser0052,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0052 +sAMAccountName: resuser0052 +name: resuser0052 +userPrincipalName: resuser0052@${DirectoryDomain} +mail: resuser0052@${DirectoryDomain} +uidNumber: 15052 +gidNumber: 16052 +unixHomeDirectory: /home/resuser0052 +loginShell: /bin/bash + +Create a user: resuser0053 in RES OU +dn: CN=resuser0053,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0053 +sAMAccountName: resuser0053 +name: resuser0053 +userPrincipalName: resuser0053@${DirectoryDomain} +mail: resuser0053@${DirectoryDomain} +uidNumber: 15053 +gidNumber: 16053 +unixHomeDirectory: /home/resuser0053 +loginShell: /bin/bash + +Create a user: resuser0054 in RES OU +dn: CN=resuser0054,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0054 +sAMAccountName: resuser0054 +name: resuser0054 +userPrincipalName: resuser0054@${DirectoryDomain} +mail: resuser0054@${DirectoryDomain} +uidNumber: 15054 +gidNumber: 16054 +unixHomeDirectory: /home/resuser0054 +loginShell: /bin/bash + +Create a user: resuser0055 in RES OU +dn: CN=resuser0055,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0055 +sAMAccountName: resuser0055 +name: resuser0055 +userPrincipalName: resuser0055@${DirectoryDomain} +mail: resuser0055@${DirectoryDomain} +uidNumber: 15055 +gidNumber: 16055 +unixHomeDirectory: /home/resuser0055 +loginShell: /bin/bash + +Create a user: resuser0056 in RES OU +dn: CN=resuser0056,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0056 +sAMAccountName: resuser0056 +name: resuser0056 +userPrincipalName: resuser0056@${DirectoryDomain} +mail: resuser0056@${DirectoryDomain} +uidNumber: 15056 +gidNumber: 16056 +unixHomeDirectory: /home/resuser0056 +loginShell: /bin/bash + +Create a user: resuser0057 in RES OU +dn: CN=resuser0057,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0057 +sAMAccountName: resuser0057 +name: resuser0057 +userPrincipalName: resuser0057@${DirectoryDomain} +mail: resuser0057@${DirectoryDomain} +uidNumber: 15057 +gidNumber: 16057 +unixHomeDirectory: /home/resuser0057 +loginShell: /bin/bash + +Create a user: resuser0058 in RES OU +dn: CN=resuser0058,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0058 +sAMAccountName: resuser0058 +name: resuser0058 +userPrincipalName: resuser0058@${DirectoryDomain} +mail: resuser0058@${DirectoryDomain} +uidNumber: 15058 +gidNumber: 16058 +unixHomeDirectory: /home/resuser0058 +loginShell: /bin/bash + +Create a user: resuser0059 in RES OU +dn: CN=resuser0059,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0059 +sAMAccountName: resuser0059 +name: resuser0059 +userPrincipalName: resuser0059@${DirectoryDomain} +mail: resuser0059@${DirectoryDomain} +uidNumber: 15059 +gidNumber: 16059 +unixHomeDirectory: /home/resuser0059 +loginShell: /bin/bash + +Create a user: resuser0060 in RES OU +dn: CN=resuser0060,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0060 +sAMAccountName: resuser0060 +name: resuser0060 +userPrincipalName: resuser0060@${DirectoryDomain} +mail: resuser0060@${DirectoryDomain} +uidNumber: 15060 +gidNumber: 16060 +unixHomeDirectory: /home/resuser0060 +loginShell: /bin/bash + +Create a user: resuser0061 in RES OU +dn: CN=resuser0061,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0061 +sAMAccountName: resuser0061 +name: resuser0061 +userPrincipalName: resuser0061@${DirectoryDomain} +mail: resuser0061@${DirectoryDomain} +uidNumber: 15061 +gidNumber: 16061 +unixHomeDirectory: /home/resuser0061 +loginShell: /bin/bash + +Create a user: resuser0062 in RES OU +dn: CN=resuser0062,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0062 +sAMAccountName: resuser0062 +name: resuser0062 +userPrincipalName: resuser0062@${DirectoryDomain} +mail: resuser0062@${DirectoryDomain} +uidNumber: 15062 +gidNumber: 16062 +unixHomeDirectory: /home/resuser0062 +loginShell: /bin/bash + +Create a user: resuser0063 in RES OU +dn: CN=resuser0063,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0063 +sAMAccountName: resuser0063 +name: resuser0063 +userPrincipalName: resuser0063@${DirectoryDomain} +mail: resuser0063@${DirectoryDomain} +uidNumber: 15063 +gidNumber: 16063 +unixHomeDirectory: /home/resuser0063 +loginShell: /bin/bash + +Create a user: resuser0064 in RES OU +dn: CN=resuser0064,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0064 +sAMAccountName: resuser0064 +name: resuser0064 +userPrincipalName: resuser0064@${DirectoryDomain} +mail: resuser0064@${DirectoryDomain} +uidNumber: 15064 +gidNumber: 16064 +unixHomeDirectory: /home/resuser0064 +loginShell: /bin/bash + +Create a user: resuser0065 in RES OU +dn: CN=resuser0065,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0065 +sAMAccountName: resuser0065 +name: resuser0065 +userPrincipalName: resuser0065@${DirectoryDomain} +mail: resuser0065@${DirectoryDomain} +uidNumber: 15065 +gidNumber: 16065 +unixHomeDirectory: /home/resuser0065 +loginShell: /bin/bash + +Create a user: resuser0066 in RES OU +dn: CN=resuser0066,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0066 +sAMAccountName: resuser0066 +name: resuser0066 +userPrincipalName: resuser0066@${DirectoryDomain} +mail: resuser0066@${DirectoryDomain} +uidNumber: 15066 +gidNumber: 16066 +unixHomeDirectory: /home/resuser0066 +loginShell: /bin/bash + +Create a user: resuser0067 in RES OU +dn: CN=resuser0067,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0067 +sAMAccountName: resuser0067 +name: resuser0067 +userPrincipalName: resuser0067@${DirectoryDomain} +mail: resuser0067@${DirectoryDomain} +uidNumber: 15067 +gidNumber: 16067 +unixHomeDirectory: /home/resuser0067 +loginShell: /bin/bash + +Create a user: resuser0068 in RES OU +dn: CN=resuser0068,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0068 +sAMAccountName: resuser0068 +name: resuser0068 +userPrincipalName: resuser0068@${DirectoryDomain} +mail: resuser0068@${DirectoryDomain} +uidNumber: 15068 +gidNumber: 16068 +unixHomeDirectory: /home/resuser0068 +loginShell: /bin/bash + +Create a user: resuser0069 in RES OU +dn: CN=resuser0069,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0069 +sAMAccountName: resuser0069 +name: resuser0069 +userPrincipalName: resuser0069@${DirectoryDomain} +mail: resuser0069@${DirectoryDomain} +uidNumber: 15069 +gidNumber: 16069 +unixHomeDirectory: /home/resuser0069 +loginShell: /bin/bash + +Create a user: resuser0070 in RES OU +dn: CN=resuser0070,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0070 +sAMAccountName: resuser0070 +name: resuser0070 +userPrincipalName: resuser0070@${DirectoryDomain} +mail: resuser0070@${DirectoryDomain} +uidNumber: 15070 +gidNumber: 16070 +unixHomeDirectory: /home/resuser0070 +loginShell: /bin/bash + +Create a user: resuser0071 in RES OU +dn: CN=resuser0071,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0071 +sAMAccountName: resuser0071 +name: resuser0071 +userPrincipalName: resuser0071@${DirectoryDomain} +mail: resuser0071@${DirectoryDomain} +uidNumber: 15071 +gidNumber: 16071 +unixHomeDirectory: /home/resuser0071 +loginShell: /bin/bash + +Create a user: resuser0072 in RES OU +dn: CN=resuser0072,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0072 +sAMAccountName: resuser0072 +name: resuser0072 +userPrincipalName: resuser0072@${DirectoryDomain} +mail: resuser0072@${DirectoryDomain} +uidNumber: 15072 +gidNumber: 16072 +unixHomeDirectory: /home/resuser0072 +loginShell: /bin/bash + +Create a user: resuser0073 in RES OU +dn: CN=resuser0073,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0073 +sAMAccountName: resuser0073 +name: resuser0073 +userPrincipalName: resuser0073@${DirectoryDomain} +mail: resuser0073@${DirectoryDomain} +uidNumber: 15073 +gidNumber: 16073 +unixHomeDirectory: /home/resuser0073 +loginShell: /bin/bash + +Create a user: resuser0074 in RES OU +dn: CN=resuser0074,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0074 +sAMAccountName: resuser0074 +name: resuser0074 +userPrincipalName: resuser0074@${DirectoryDomain} +mail: resuser0074@${DirectoryDomain} +uidNumber: 15074 +gidNumber: 16074 +unixHomeDirectory: /home/resuser0074 +loginShell: /bin/bash + +Create a user: resuser0075 in RES OU +dn: CN=resuser0075,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0075 +sAMAccountName: resuser0075 +name: resuser0075 +userPrincipalName: resuser0075@${DirectoryDomain} +mail: resuser0075@${DirectoryDomain} +uidNumber: 15075 +gidNumber: 16075 +unixHomeDirectory: /home/resuser0075 +loginShell: /bin/bash + +Create a user: resuser0076 in RES OU +dn: CN=resuser0076,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0076 +sAMAccountName: resuser0076 +name: resuser0076 +userPrincipalName: resuser0076@${DirectoryDomain} +mail: resuser0076@${DirectoryDomain} +uidNumber: 15076 +gidNumber: 16076 +unixHomeDirectory: /home/resuser0076 +loginShell: /bin/bash + +Create a user: resuser0077 in RES OU +dn: CN=resuser0077,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0077 +sAMAccountName: resuser0077 +name: resuser0077 +userPrincipalName: resuser0077@${DirectoryDomain} +mail: resuser0077@${DirectoryDomain} +uidNumber: 15077 +gidNumber: 16077 +unixHomeDirectory: /home/resuser0077 +loginShell: /bin/bash + +Create a user: resuser0078 in RES OU +dn: CN=resuser0078,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0078 +sAMAccountName: resuser0078 +name: resuser0078 +userPrincipalName: resuser0078@${DirectoryDomain} +mail: resuser0078@${DirectoryDomain} +uidNumber: 15078 +gidNumber: 16078 +unixHomeDirectory: /home/resuser0078 +loginShell: /bin/bash + +Create a user: resuser0079 in RES OU +dn: CN=resuser0079,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0079 +sAMAccountName: resuser0079 +name: resuser0079 +userPrincipalName: resuser0079@${DirectoryDomain} +mail: resuser0079@${DirectoryDomain} +uidNumber: 15079 +gidNumber: 16079 +unixHomeDirectory: /home/resuser0079 +loginShell: /bin/bash + +Create a user: resuser0080 in RES OU +dn: CN=resuser0080,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0080 +sAMAccountName: resuser0080 +name: resuser0080 +userPrincipalName: resuser0080@${DirectoryDomain} +mail: resuser0080@${DirectoryDomain} +uidNumber: 15080 +gidNumber: 16080 +unixHomeDirectory: /home/resuser0080 +loginShell: /bin/bash + +Create a user: resuser0081 in RES OU +dn: CN=resuser0081,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0081 +sAMAccountName: resuser0081 +name: resuser0081 +userPrincipalName: resuser0081@${DirectoryDomain} +mail: resuser0081@${DirectoryDomain} +uidNumber: 15081 +gidNumber: 16081 +unixHomeDirectory: /home/resuser0081 +loginShell: /bin/bash + +Create a user: resuser0082 in RES OU +dn: CN=resuser0082,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0082 +sAMAccountName: resuser0082 +name: resuser0082 +userPrincipalName: resuser0082@${DirectoryDomain} +mail: resuser0082@${DirectoryDomain} +uidNumber: 15082 +gidNumber: 16082 +unixHomeDirectory: /home/resuser0082 +loginShell: /bin/bash + +Create a user: resuser0083 in RES OU +dn: CN=resuser0083,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0083 +sAMAccountName: resuser0083 +name: resuser0083 +userPrincipalName: resuser0083@${DirectoryDomain} +mail: resuser0083@${DirectoryDomain} +uidNumber: 15083 +gidNumber: 16083 +unixHomeDirectory: /home/resuser0083 +loginShell: /bin/bash + +Create a user: resuser0084 in RES OU +dn: CN=resuser0084,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0084 +sAMAccountName: resuser0084 +name: resuser0084 +userPrincipalName: resuser0084@${DirectoryDomain} +mail: resuser0084@${DirectoryDomain} +uidNumber: 15084 +gidNumber: 16084 +unixHomeDirectory: /home/resuser0084 +loginShell: /bin/bash + +Create a user: resuser0085 in RES OU +dn: CN=resuser0085,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0085 +sAMAccountName: resuser0085 +name: resuser0085 +userPrincipalName: resuser0085@${DirectoryDomain} +mail: resuser0085@${DirectoryDomain} +uidNumber: 15085 +gidNumber: 16085 +unixHomeDirectory: /home/resuser0085 +loginShell: /bin/bash + +Create a user: resuser0086 in RES OU +dn: CN=resuser0086,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0086 +sAMAccountName: resuser0086 +name: resuser0086 +userPrincipalName: resuser0086@${DirectoryDomain} +mail: resuser0086@${DirectoryDomain} +uidNumber: 15086 +gidNumber: 16086 +unixHomeDirectory: /home/resuser0086 +loginShell: /bin/bash + +Create a user: resuser0087 in RES OU +dn: CN=resuser0087,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0087 +sAMAccountName: resuser0087 +name: resuser0087 +userPrincipalName: resuser0087@${DirectoryDomain} +mail: resuser0087@${DirectoryDomain} +uidNumber: 15087 +gidNumber: 16087 +unixHomeDirectory: /home/resuser0087 +loginShell: /bin/bash + +Create a user: resuser0088 in RES OU +dn: CN=resuser0088,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0088 +sAMAccountName: resuser0088 +name: resuser0088 +userPrincipalName: resuser0088@${DirectoryDomain} +mail: resuser0088@${DirectoryDomain} +uidNumber: 15088 +gidNumber: 16088 +unixHomeDirectory: /home/resuser0088 +loginShell: /bin/bash + +Create a user: resuser0089 in RES OU +dn: CN=resuser0089,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0089 +sAMAccountName: resuser0089 +name: resuser0089 +userPrincipalName: resuser0089@${DirectoryDomain} +mail: resuser0089@${DirectoryDomain} +uidNumber: 15089 +gidNumber: 16089 +unixHomeDirectory: /home/resuser0089 +loginShell: /bin/bash + +Create a user: resuser0090 in RES OU +dn: CN=resuser0090,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0090 +sAMAccountName: resuser0090 +name: resuser0090 +userPrincipalName: resuser0090@${DirectoryDomain} +mail: resuser0090@${DirectoryDomain} +uidNumber: 15090 +gidNumber: 16090 +unixHomeDirectory: /home/resuser0090 +loginShell: /bin/bash + +Create a user: resuser0091 in RES OU +dn: CN=resuser0091,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0091 +sAMAccountName: resuser0091 +name: resuser0091 +userPrincipalName: resuser0091@${DirectoryDomain} +mail: resuser0091@${DirectoryDomain} +uidNumber: 15091 +gidNumber: 16091 +unixHomeDirectory: /home/resuser0091 +loginShell: /bin/bash + +Create a user: resuser0092 in RES OU +dn: CN=resuser0092,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0092 +sAMAccountName: resuser0092 +name: resuser0092 +userPrincipalName: resuser0092@${DirectoryDomain} +mail: resuser0092@${DirectoryDomain} +uidNumber: 15092 +gidNumber: 16092 +unixHomeDirectory: /home/resuser0092 +loginShell: /bin/bash + +Create a user: resuser0093 in RES OU +dn: CN=resuser0093,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0093 +sAMAccountName: resuser0093 +name: resuser0093 +userPrincipalName: resuser0093@${DirectoryDomain} +mail: resuser0093@${DirectoryDomain} +uidNumber: 15093 +gidNumber: 16093 +unixHomeDirectory: /home/resuser0093 +loginShell: /bin/bash + +Create a user: resuser0094 in RES OU +dn: CN=resuser0094,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0094 +sAMAccountName: resuser0094 +name: resuser0094 +userPrincipalName: resuser0094@${DirectoryDomain} +mail: resuser0094@${DirectoryDomain} +uidNumber: 15094 +gidNumber: 16094 +unixHomeDirectory: /home/resuser0094 +loginShell: /bin/bash + +Create a user: resuser0095 in RES OU +dn: CN=resuser0095,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0095 +sAMAccountName: resuser0095 +name: resuser0095 +userPrincipalName: resuser0095@${DirectoryDomain} +mail: resuser0095@${DirectoryDomain} +uidNumber: 15095 +gidNumber: 16095 +unixHomeDirectory: /home/resuser0095 +loginShell: /bin/bash + +Create a user: resuser0096 in RES OU +dn: CN=resuser0096,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0096 +sAMAccountName: resuser0096 +name: resuser0096 +userPrincipalName: resuser0096@${DirectoryDomain} +mail: resuser0096@${DirectoryDomain} +uidNumber: 15096 +gidNumber: 16096 +unixHomeDirectory: /home/resuser0096 +loginShell: /bin/bash + +Create a user: resuser0097 in RES OU +dn: CN=resuser0097,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0097 +sAMAccountName: resuser0097 +name: resuser0097 +userPrincipalName: resuser0097@${DirectoryDomain} +mail: resuser0097@${DirectoryDomain} +uidNumber: 15097 +gidNumber: 16097 +unixHomeDirectory: /home/resuser0097 +loginShell: /bin/bash + +Create a user: resuser0098 in RES OU +dn: CN=resuser0098,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0098 +sAMAccountName: resuser0098 +name: resuser0098 +userPrincipalName: resuser0098@${DirectoryDomain} +mail: resuser0098@${DirectoryDomain} +uidNumber: 15098 +gidNumber: 16098 +unixHomeDirectory: /home/resuser0098 +loginShell: /bin/bash + +Create a user: resuser0099 in RES OU +dn: CN=resuser0099,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0099 +sAMAccountName: resuser0099 +name: resuser0099 +userPrincipalName: resuser0099@${DirectoryDomain} +mail: resuser0099@${DirectoryDomain} +uidNumber: 15099 +gidNumber: 16099 +unixHomeDirectory: /home/resuser0099 +loginShell: /bin/bash + +Create a user: resuser0100 in RES OU +dn: CN=resuser0100,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: resuser0100 +sAMAccountName: resuser0100 +name: resuser0100 +userPrincipalName: resuser0100@${DirectoryDomain} +mail: resuser0100@${DirectoryDomain} +uidNumber: 15100 +gidNumber: 16100 +unixHomeDirectory: /home/resuser0100 +loginShell: /bin/bash + +# Create a group: group01 +dn: CN=group01,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: group +cn: group01 +description: Non-RES group +distinguishedName: CN=group01,OU=Users,OU=${OU},DC=${DC} +name: group01 +sAMAccountName: group01 +objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=${DC} +gidNumber: 2001 +member: CN=admin0001,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0002,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0003,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0004,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0005,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0006,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0007,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0008,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0009,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0010,OU=Users,OU=${OU},DC=${DC} +member: CN=user0001,OU=Users,OU=${OU},DC=${DC} +member: CN=user0002,OU=Users,OU=${OU},DC=${DC} +member: CN=user0003,OU=Users,OU=${OU},DC=${DC} +member: CN=user0004,OU=Users,OU=${OU},DC=${DC} +member: CN=user0005,OU=Users,OU=${OU},DC=${DC} +member: CN=user0006,OU=Users,OU=${OU},DC=${DC} +member: CN=user0007,OU=Users,OU=${OU},DC=${DC} +member: CN=user0008,OU=Users,OU=${OU},DC=${DC} +member: CN=user0009,OU=Users,OU=${OU},DC=${DC} +member: CN=user0010,OU=Users,OU=${OU},DC=${DC} +member: CN=user0011,OU=Users,OU=${OU},DC=${DC} +member: CN=user0012,OU=Users,OU=${OU},DC=${DC} +member: CN=user0013,OU=Users,OU=${OU},DC=${DC} +member: CN=user0014,OU=Users,OU=${OU},DC=${DC} +member: CN=user0015,OU=Users,OU=${OU},DC=${DC} +member: CN=user0016,OU=Users,OU=${OU},DC=${DC} +member: CN=user0017,OU=Users,OU=${OU},DC=${DC} +member: CN=user0018,OU=Users,OU=${OU},DC=${DC} +member: CN=user0019,OU=Users,OU=${OU},DC=${DC} +member: CN=user0020,OU=Users,OU=${OU},DC=${DC} +member: CN=user0021,OU=Users,OU=${OU},DC=${DC} +member: CN=user0022,OU=Users,OU=${OU},DC=${DC} +member: CN=user0023,OU=Users,OU=${OU},DC=${DC} +member: CN=user0024,OU=Users,OU=${OU},DC=${DC} +member: CN=user0025,OU=Users,OU=${OU},DC=${DC} +member: CN=user0026,OU=Users,OU=${OU},DC=${DC} +member: CN=user0027,OU=Users,OU=${OU},DC=${DC} +member: CN=user0028,OU=Users,OU=${OU},DC=${DC} +member: CN=user0029,OU=Users,OU=${OU},DC=${DC} +member: CN=user0030,OU=Users,OU=${OU},DC=${DC} +member: CN=user0031,OU=Users,OU=${OU},DC=${DC} +member: CN=user0032,OU=Users,OU=${OU},DC=${DC} +member: CN=user0033,OU=Users,OU=${OU},DC=${DC} +member: CN=user0034,OU=Users,OU=${OU},DC=${DC} +member: CN=user0035,OU=Users,OU=${OU},DC=${DC} +member: CN=user0036,OU=Users,OU=${OU},DC=${DC} +member: CN=user0037,OU=Users,OU=${OU},DC=${DC} +member: CN=user0038,OU=Users,OU=${OU},DC=${DC} +member: CN=user0039,OU=Users,OU=${OU},DC=${DC} +member: CN=user0040,OU=Users,OU=${OU},DC=${DC} +member: CN=user0041,OU=Users,OU=${OU},DC=${DC} +member: CN=user0042,OU=Users,OU=${OU},DC=${DC} +member: CN=user0043,OU=Users,OU=${OU},DC=${DC} +member: CN=user0044,OU=Users,OU=${OU},DC=${DC} +member: CN=user0045,OU=Users,OU=${OU},DC=${DC} +member: CN=user0046,OU=Users,OU=${OU},DC=${DC} +member: CN=user0047,OU=Users,OU=${OU},DC=${DC} +member: CN=user0048,OU=Users,OU=${OU},DC=${DC} +member: CN=user0049,OU=Users,OU=${OU},DC=${DC} +member: CN=user0050,OU=Users,OU=${OU},DC=${DC} +member: CN=user0051,OU=Users,OU=${OU},DC=${DC} +member: CN=user0052,OU=Users,OU=${OU},DC=${DC} +member: CN=user0053,OU=Users,OU=${OU},DC=${DC} +member: CN=user0054,OU=Users,OU=${OU},DC=${DC} +member: CN=user0055,OU=Users,OU=${OU},DC=${DC} +member: CN=user0056,OU=Users,OU=${OU},DC=${DC} +member: CN=user0057,OU=Users,OU=${OU},DC=${DC} +member: CN=user0058,OU=Users,OU=${OU},DC=${DC} +member: CN=user0059,OU=Users,OU=${OU},DC=${DC} +member: CN=user0060,OU=Users,OU=${OU},DC=${DC} +member: CN=user0061,OU=Users,OU=${OU},DC=${DC} +member: CN=user0062,OU=Users,OU=${OU},DC=${DC} +member: CN=user0063,OU=Users,OU=${OU},DC=${DC} +member: CN=user0064,OU=Users,OU=${OU},DC=${DC} +member: CN=user0065,OU=Users,OU=${OU},DC=${DC} +member: CN=user0066,OU=Users,OU=${OU},DC=${DC} +member: CN=user0067,OU=Users,OU=${OU},DC=${DC} +member: CN=user0068,OU=Users,OU=${OU},DC=${DC} +member: CN=user0069,OU=Users,OU=${OU},DC=${DC} +member: CN=user0070,OU=Users,OU=${OU},DC=${DC} +member: CN=user0071,OU=Users,OU=${OU},DC=${DC} +member: CN=user0072,OU=Users,OU=${OU},DC=${DC} +member: CN=user0073,OU=Users,OU=${OU},DC=${DC} +member: CN=user0074,OU=Users,OU=${OU},DC=${DC} +member: CN=user0075,OU=Users,OU=${OU},DC=${DC} +member: CN=user0076,OU=Users,OU=${OU},DC=${DC} +member: CN=user0077,OU=Users,OU=${OU},DC=${DC} +member: CN=user0078,OU=Users,OU=${OU},DC=${DC} +member: CN=user0079,OU=Users,OU=${OU},DC=${DC} +member: CN=user0080,OU=Users,OU=${OU},DC=${DC} +member: CN=user0081,OU=Users,OU=${OU},DC=${DC} +member: CN=user0082,OU=Users,OU=${OU},DC=${DC} +member: CN=user0083,OU=Users,OU=${OU},DC=${DC} +member: CN=user0084,OU=Users,OU=${OU},DC=${DC} +member: CN=user0085,OU=Users,OU=${OU},DC=${DC} +member: CN=user0086,OU=Users,OU=${OU},DC=${DC} +member: CN=user0087,OU=Users,OU=${OU},DC=${DC} +member: CN=user0088,OU=Users,OU=${OU},DC=${DC} +member: CN=user0089,OU=Users,OU=${OU},DC=${DC} +member: CN=user0090,OU=Users,OU=${OU},DC=${DC} +member: CN=user0091,OU=Users,OU=${OU},DC=${DC} +member: CN=user0092,OU=Users,OU=${OU},DC=${DC} +member: CN=user0093,OU=Users,OU=${OU},DC=${DC} +member: CN=user0094,OU=Users,OU=${OU},DC=${DC} +member: CN=user0095,OU=Users,OU=${OU},DC=${DC} +member: CN=user0096,OU=Users,OU=${OU},DC=${DC} +member: CN=user0097,OU=Users,OU=${OU},DC=${DC} +member: CN=user0098,OU=Users,OU=${OU},DC=${DC} +member: CN=user0099,OU=Users,OU=${OU},DC=${DC} +member: CN=user0100,OU=Users,OU=${OU},DC=${DC} + +# Create a group: group02 +dn: CN=group02,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: group +cn: group02 +description: Non-RES group +distinguishedName: CN=group02,OU=Users,OU=${OU},DC=${DC} +name: group02 +sAMAccountName: group02 +objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=${DC} +gidNumber: 2002 + +# Create a group: resgroup01 in RES OU +dn: CN=resgroup01,OU=Users,OU={ou},OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: group +cn: resgroup01 +description: Represents a project group in RES +distinguishedName: CN=resgroup01,OU=Users,OU={ou},OU=${OU},DC=${DC} +name: resgroup01 +sAMAccountName: resgroup01 +objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=${DC} +gidNumber: 2011 +member: CN=resadmin0001,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0002,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0003,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0004,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0005,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0006,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0007,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0008,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0009,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0010,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0001,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0002,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0003,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0004,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0005,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0006,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0007,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0008,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0009,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0010,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0011,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0012,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0013,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0014,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0015,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0016,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0017,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0018,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0019,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0020,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0021,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0022,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0023,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0024,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0025,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0026,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0027,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0028,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0029,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0030,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0031,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0032,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0033,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0034,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0035,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0036,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0037,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0038,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0039,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0040,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0041,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0042,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0043,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0044,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0045,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0046,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0047,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0048,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0049,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0050,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0051,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0052,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0053,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0054,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0055,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0056,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0057,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0058,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0059,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0060,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0061,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0062,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0063,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0064,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0065,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0066,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0067,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0068,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0069,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0070,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0071,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0072,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0073,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0074,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0075,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0076,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0077,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0078,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0079,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0080,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0081,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0082,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0083,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0084,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0085,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0086,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0087,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0088,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0089,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0090,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0091,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0092,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0093,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0094,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0095,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0096,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0097,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0098,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0099,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resuser0100,OU=Users,OU=RES,OU=${OU},DC=${DC} + +# Create a group: resgroup02 in RES OU +dn: CN=resgroup02,OU=Users,OU={ou},OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: group +cn: resgroup02 +description: Represents a project group in RES +distinguishedName: CN=resgroup02,OU=Users,OU={ou},OU=${OU},DC=${DC} +name: resgroup02 +sAMAccountName: resgroup02 +objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=${DC} +gidNumber: 2012 + +# Create a group: RESAdministrators in RES OU +dn: CN=RESAdministrators,OU=Users,OU={ou},OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: group +cn: RESAdministrators +description: Represents the group of sudoers +distinguishedName: CN=RESAdministrators,OU=Users,OU={ou},OU=${OU},DC=${DC} +name: RESAdministrators +sAMAccountName: RESAdministrators +objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=${DC} +gidNumber: 2020 +member: CN=admin0001,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0002,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0003,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0004,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0005,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0006,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0007,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0008,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0009,OU=Users,OU=${OU},DC=${DC} +member: CN=admin0010,OU=Users,OU=${OU},DC=${DC} +member: CN=resadmin0001,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0002,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0003,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0004,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0005,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0006,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0007,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0008,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0009,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=resadmin0010,OU=Users,OU=RES,OU=${OU},DC=${DC} diff --git a/res/res-demo/bi.yaml b/res/res-demo/bi.yaml new file mode 100644 index 00000000..c945fdb3 --- /dev/null +++ b/res/res-demo/bi.yaml @@ -0,0 +1,581 @@ +Description: A set of external resources that can support a Research and Engineering Studio Environment deployment + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "AD Configuration" + Parameters: + - DomainName + - SubDomain + - AdminPassword + - ServiceAccountPassword + - Label: + default: "AD Management Hosts Configuration" + Parameters: + - Keypair + - LDIFS3Path + - ClientIpCidr + - ClientPrefixList + - StopAdAdminInstances + +Parameters: + DomainName: + Description: Active Directory Domain Name. The supplied LDIF file which provides bootstrap users uses this domain. A different LDIF file needs to be provided for a different domain. + Type: String + Default: corp.res.com + AllowedPattern: ^([a-zA-Z0-9]+[\\.-])+([a-zA-Z0-9])+$ + SubDomain: + Description: (Optional, but required for GovCloud regions) SubDomain for the Active Directory Domain Name. If provided, Active Directory Domain Name will be {SubDomain}.{DomainName} + Type: String + Default: "" + PortalDomainName: + Description: (Optional) Domain Name for web portal domain that lives in Route53 in account (may be different from the Active Directory domain). Used to generate certs, leave blank to skip certificate generation. + Type: String + Default: "" + EnvironmentName: + Description: (Optional) EnvironmentName must start with "res-"and should be less than or equal to 11 characters. Required to generate certificates. + Type: String + AllowedPattern: ^res-[A-Za-z\-\_0-9]{0,7}$ + Default: res-demo + AdminPassword: + Description: Provide the Active Directory Administrator Account Password Directly or Resource ARN to Secret Containing Password. + Type: String + MinLength: 8 + MaxLength: 2048 + AllowedPattern: (arn:(aws(-cn|-us-gov)?):secretsmanager:(us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d:\d{12}:secret:[a-zA-Z0-9/_+=.@-]+)|(?=^.{8,64}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9\s])(?=.*[a-z])|(?=.*[^A-Za-z0-9\s])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9\s]))^.* + NoEcho: true + ServiceAccountPassword: + Description: Provide the Active Directory Service Account Password Directly or Resource ARN to Secret Containing Password. + Type: String + MinLength: 8 + MaxLength: 2048 + AllowedPattern: (arn:(aws(-cn|-us-gov)?):secretsmanager:(us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d:\d{12}:secret:[a-zA-Z0-9/_+=.@-]+)|(?=^.{8,64}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9\s])(?=.*[a-z])|(?=.*[^A-Za-z0-9\s])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9\s]))^.* + NoEcho: true + LDIFS3Path: + Description: (Optional) An S3 Path (without the s3://) to an LDIF file that will be used during stack creation. + Type: String + Default: aws-hpc-recipes/main/recipes/res/res_demo_env/assets/res.ldif + StopAdAdminInstances: + Description: Automatically stop management instances to save costs. + Type: String + Default: "False" + AllowedValues: + - "True" + - "False" + Keypair: + Description: EC2 Keypair to access AD management instances. + Type: AWS::EC2::KeyPair::KeyName + ClientIpCidr: + Description: CIDR controlling incoming traffic to AD management instances. + Default: "" + Type: String + AllowedPattern: ^((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2}))?$ + ConstraintDescription: ClientIP must be a valid IP or network range of the form x.x.x.x/x. specify your IP/NETMASK (e.g x.x.x/32 or x.x.x.x/24 for subnet range) + ClientPrefixList: + Description: (Optional) VPC Prefix List ID controlling incoming traffic to AD management instances. + Default: "" + Type: String + AllowedPattern: ^(pl-[a-z0-9]{8,20})?$ + ConstraintDescription: Must be a valid VPC Prefix List ID, which begins with `pl-`. Prefix Lists can be configured at https://console.aws.amazon.com/vpcconsole/home#ManagedPrefixLists + EFSThroughputMode: + Description: (Optional) EFS filesystem throughput mode. + Type: String + Default: "bursting" + AllowedValues: + - "elastic" + - "bursting" + RetainStorageResources: + Description: (Optional) Retain the home file system and the RES VPC on RES deletion. Provide `True` to eliminate risk of accidentally deleting data. + Type: String + Default: "True" + AllowedValues: + - "True" + - "False" + +Conditions: + GenerateCerts: !Not [!Equals [!Ref PortalDomainName, ""]] + UseEnvironmentName: !Not [!Equals [!Ref EnvironmentName, ""]] + SubDomainNotProvided: !Equals [!Ref SubDomain, ""] + InGovCloud: !Equals [!Ref 'AWS::Partition', "aws-us-gov"] + RetainStorageAndNetworking: !Equals [!Ref RetainStorageResources, "True"] + +Resources: + +##### Nested stacks + + Networking: + Type: AWS::CloudFormation::Stack + DeletionPolicy: !If [ RetainStorageAndNetworking, Retain, Delete ] + Properties: + Parameters: + ProvisionSubnetsC: "False" + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/net/hpc_large_scale/assets/main.yaml + + DirectoryService: + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + DomainName: !Ref DomainName + SubDomain: !Ref SubDomain + AdminPassword: !GetAtt PasswordResolver.AdminPassword + ServiceAccountPassword: !GetAtt PasswordResolver.ServiceAccountPassword + ServiceAccountName: ServiceAccount + LDIFS3Path: !Ref LDIFS3Path + Keypair: !Ref Keypair + UserName: "" + UserPassword: "" + AllowedIps: !Ref ClientIpCidr + ClientPrefixList: !Ref ClientPrefixList + # StopAdAdminInstance: !Ref StopAdAdminInstances + Vpc: !GetAtt [ Networking, Outputs.VPC ] + PrivateSubnetOne: {"Fn::Select": [0, { "Fn::Split" : [",", !GetAtt [ Networking, Outputs.PrivateSubnets ]] }]} + PrivateSubnetTwo: {"Fn::Select": [1, { "Fn::Split" : [",", !GetAtt [ Networking, Outputs.PrivateSubnets ]] }]} + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/dir/demo_managed_ad/assets/main.yaml + + WindowsManagementHost: + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + DomainName: !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName] ] ] + DelegationUser: Admin + DelegationPassword: !GetAtt PasswordResolver.AdminPassword + DirectoryId: !GetAtt [ DirectoryService, Outputs.DirectoryId ] + DnsIpAddress1: !GetAtt [ DirectoryService, Outputs.DnsIpAddress1 ] + DnsIpAddress2: !GetAtt [ DirectoryService, Outputs.DnsIpAddress2 ] + Keypair: !Ref Keypair + VpcId: !GetAtt [ Networking, Outputs.VPC ] + Subnet: {"Fn::Select": [0, { "Fn::Split" : [",", !GetAtt [ Networking, Outputs.PublicSubnets ]] }]} + StopAdAdminInstance: !Ref StopAdAdminInstances + ClientIpCidr: !Ref ClientIpCidr + ClientPrefixList: !Ref ClientPrefixList + PSS3Path: aws-hpc-recipes/main/recipes/res/res_demo_env/assets/service_account.ps1 + PSS3PathRegion: !If [InGovCloud, "us-gov-west-1", "us-east-1"] + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/dir/demo_managed_ad/assets/windows_management_host.yaml + + Storage: + Type: AWS::CloudFormation::Stack + DeletionPolicy: !If [ RetainStorageAndNetworking, Retain, Delete ] + Properties: + Parameters: + VpcId: !GetAtt [ Networking, Outputs.VPC ] + SubnetCount: 2 + SubnetIds: !GetAtt [ Networking, Outputs.PrivateSubnets ] + ThroughputMode: !Ref EFSThroughputMode + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/storage/efs_simple/assets/main.yaml + + Certs: + Condition: GenerateCerts + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + SubnetId: {"Fn::Select": [0, { "Fn::Split" : [",", !GetAtt [ Networking, Outputs.PrivateSubnets ]] }]} + DomainName: !Ref PortalDomainName + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/security/public_certs/assets/main.yaml + Tags: + - Key: "res:EnvironmentName" + Value: !Ref EnvironmentName + - Key: "res:ClusterName" + Value: !Ref EnvironmentName + - Key: "res:ModuleName" + Value: virtual-desktop-controller + - Key: "bi:Deployment" + Value: "true" + + ACMCertificate: + Condition: GenerateCerts + Type: AWS::CertificateManager::Certificate + Properties: + DomainName: !Ref PortalDomainName + ValidationMethod: DNS + SubjectAlternativeNames: + - !Sub ["*.${PortalDomainName}", {PortalDomainName: !Ref PortalDomainName }] + DomainValidationOptions: + - DomainName: !Ref PortalDomainName + HostedZoneId: !GetAtt HostedZoneResolver.HostedZoneId + +##### Custom Operations + + HostedZoneResolverRole: + Type: AWS::IAM::Role + Condition: GenerateCerts + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Policies: + - PolicyName: LogOutput + PolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Effect: Allow + # Resource: !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-Prep:* + Resource: '*' + - PolicyName: ListHostedZones + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - route53:ListHostedZones + Resource: '*' + + HostedZoneResolverLambda: + Type: AWS::Lambda::Function + Condition: GenerateCerts + Properties: + Description: !Sub "${AWS::StackName}: custom resource handler to resolve hosted zones." + Handler: index.handler + MemorySize: 128 + Role: !GetAtt HostedZoneResolverRole.Arn + Runtime: python3.9 + Timeout: 300 + TracingConfig: + Mode: Active + Code: + ZipFile: | + import cfnresponse + import boto3 + import logging + import random + import string + import re + logger = logging.getLogger() + logger.setLevel(logging.INFO) + route53 = boto3.client("route53") + + def create_physical_resource_id(): + alnum = string.ascii_uppercase + string.ascii_lowercase + string.digits + return "".join(random.choice(alnum) for _ in range(16)) + + def handler(event, context): + try: + print(event) + print( 'boto version {}'.format(boto3.__version__)) + + portal_domain_name = event['ResourceProperties']['PortalDomainName'] + + response_data = {} + reason = None + response_status = cfnresponse.SUCCESS + + if event['RequestType'] == 'Create': + response_data['Message'] = 'Resource creation successful!' + physical_resource_id = create_physical_resource_id() + + # use route53 to find the right hosted zone + zone_data = route53.list_hosted_zones() + zones = filter(lambda x: f"{portal_domain_name}.".endswith(x['Name']), zone_data['HostedZones']) + for zone in zones: + zone_id = re.match(r'\/hostedzone\/(.*)', zone['Id']).groups(0)[0] + logger.info(f"hosted zone = {zone} for portal_domain_name {portal_domain_name} with zone_id {zone_id}") + response_data['HostedZoneId'] = zone_id + else: + physical_resource_id = event['PhysicalResourceId'] + cfnresponse.send(event, context, response_status, response_data, physical_resource_id, reason) + except Exception as e: + cfnresponse.send(event, context, cfnresponse.FAILED, {"error": str(e)}) + + HostedZoneResolver: + Type: Custom::HostedZoneResolver + Condition: GenerateCerts + Properties: + ServiceToken: !GetAtt HostedZoneResolverLambda.Arn + PortalDomainName: !Ref PortalDomainName + + EFSSecurityGroupRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Policies: + - PolicyName: LogOutput + PolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Effect: Allow + Resource: '*' + - PolicyName: EC2DescribeVpcs + PolicyDocument: + Statement: + - Action: + - ec2:DescribeVpcs + Effect: Allow + Resource: '*' + # - !Sub ["arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${VpcID}", VpcID: !GetAtt [ Networking, Outputs.VPC ]] + - PolicyName: EFSRead + PolicyDocument: + Statement: + - Action: + - elasticfilesystem:DescribeFileSystems + - elasticfilesystem:DescribeMountTargets + - elasticfilesystem:DescribeMountTargetSecurityGroups + Effect: Allow + Resource: + - !Sub ["arn:${AWS::Partition}:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:file-system/${FSID}", FSID: !GetAtt [ Storage, Outputs.EFSFilesystemId ]] + - PolicyName: EFSAttribute + PolicyDocument: + Statement: + - Action: + - ec2:DescribeNetworkInterfaceAttribute + Effect: Allow + Resource: + - '*' + - PolicyName: EC2AddIngressRule + PolicyDocument: + Statement: + - Action: + - ec2:AuthorizeSecurityGroupIngress + - ec2:CreateTags + Effect: Allow + Resource: + - '*' + #- !Sub ["arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:security-group/${SGID}", SGID: !GetAtt [ Storage, Outputs.SecurityGroupId ]] + + EFSSecurityGroupLambda: + Type: AWS::Lambda::Function + Properties: + Description: !Sub "${AWS::StackName}: custom resource handler to finish setting up stack after other resources have been created." + Handler: index.handler + MemorySize: 128 + Role: !GetAtt EFSSecurityGroupRole.Arn + Runtime: python3.9 + Timeout: 300 + TracingConfig: + Mode: Active + Code: + ZipFile: | + import cfnresponse + import boto3 + import logging + import random + import string + import secrets + import json + logger = logging.getLogger() + logger.setLevel(logging.INFO) + ec2 = boto3.client("ec2") + efs = boto3.client("efs") + + def create_physical_resource_id(): + alnum = string.ascii_uppercase + string.ascii_lowercase + string.digits + return "".join(random.choice(alnum) for _ in range(16)) + + def handler(event, context): + print(event) + print( 'boto version {}'.format(boto3.__version__)) + + vpc_id = event['ResourceProperties']['VpcId'] + efs_id = event['ResourceProperties']['EfsId'] + + response_data = {} + reason = None + response_status = cfnresponse.SUCCESS + + if event['RequestType'] == 'Create': + response_data['Message'] = 'Resource creation successful!' + physical_resource_id = create_physical_resource_id() + + cidr = ec2.describe_vpcs(VpcIds=[vpc_id])["Vpcs"][0]['CidrBlock'] + fss = efs.describe_file_systems(FileSystemId=efs_id) + + mt = efs.describe_mount_targets(FileSystemId=efs_id)["MountTargets"] + mt_id = mt[0]['MountTargetId'] + + sg = efs.describe_mount_target_security_groups(MountTargetId=mt_id)["SecurityGroups"][0] + ec2.authorize_security_group_ingress(GroupId=sg, CidrIp=cidr, FromPort=2049, ToPort=2049, IpProtocol='tcp', ) + + else: + physical_resource_id = event['PhysicalResourceId'] + cfnresponse.send(event, context, response_status, response_data, physical_resource_id, reason) + + EFSSecurityGroup: + Type: Custom::EFSSecurityGroup + Properties: + ServiceToken: !GetAtt EFSSecurityGroupLambda.Arn + VpcId: !GetAtt [ Networking, Outputs.VPC ] + EfsId: !GetAtt [ Storage, Outputs.EFSFilesystemId ] + + PasswordResolverRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Policies: + - PolicyName: AdminServicePassword + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - secretsmanager:GetSecretValue + Resource: '*' + Condition: + StringEquals: + secretsmanager:ResourceTag/res:Deployment: + - "true" + + PasswordResolverLambda: + Type: AWS::Lambda::Function + Properties: + Description: !Sub "${AWS::StackName}: custom resource handler to resolve password config for Admin and Service Account." + Handler: index.handler + MemorySize: 128 + Role: !GetAtt PasswordResolverRole.Arn + Runtime: python3.9 + Timeout: 300 + TracingConfig: + Mode: Active + Code: + ZipFile: | + import re + import time + import json + import cfnresponse + import boto3 + import random + import string + secrets = boto3.client("secretsmanager") + + def create_physical_resource_id(): + alnum = string.ascii_uppercase + string.ascii_lowercase + string.digits + return "".join(random.choice(alnum) for _ in range(16)) + + def handler(event, context): + admin_password = event['ResourceProperties']['AdminPassword'] + service_account_password = event['ResourceProperties']['ServiceAccountPassword'] + + response_data = {} + reason = None + response_status = cfnresponse.SUCCESS + + if event['RequestType'] == 'Create': + try: + response_data['Message'] = 'Resource creation successful!' + physical_resource_id = create_physical_resource_id() + + secretsmanager_arn_regex_pattern = r"(arn:(aws(-cn|-us-gov)?):secretsmanager:(us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d:\d{12}:secret:[a-zA-Z0-9/_+=.@-]+)" + admin_arn_match = re.search(secretsmanager_arn_regex_pattern, admin_password) + service_account_arn_match = re.search(secretsmanager_arn_regex_pattern, service_account_password) + + resolved_admin_password = json.loads(secrets.get_secret_value(SecretId=admin_password)['SecretString'])["password"] if admin_arn_match else admin_password + resolved_service_account_password = json.loads(secrets.get_secret_value(SecretId=service_account_password)['SecretString'])["password"] if service_account_arn_match else service_account_password + + # provide outputs + response_data['AdminPassword'] = resolved_admin_password + response_data['ServiceAccountPassword'] = resolved_service_account_password + except: + response_status = cfnresponse.FAILED + else: + physical_resource_id = event['PhysicalResourceId'] + cfnresponse.send(event, context, response_status, response_data, physical_resource_id, reason) + + PasswordResolver: + Type: Custom::PasswordResolverLambda + Properties: + ServiceToken: !GetAtt PasswordResolverLambda.Arn + AdminPassword: !Ref AdminPassword + ServiceAccountPassword: !Ref ServiceAccountPassword + +Outputs: + VpcId: + Value: !GetAtt [ Networking, Outputs.VPC ] + PrivateSubnets: + Value: !GetAtt [ Networking, Outputs.PrivateSubnets ] + PublicSubnets: + Value: !GetAtt [ Networking, Outputs.PublicSubnets ] + CertificateSecretArn: + Description: ARN for a secret that contains the generated certificate. + Value: !GetAtt [ Certs, Outputs.CertificateArn ] + Condition: GenerateCerts + PrivateKeySecretArn: + Description: ARN for a secret that contains the generated certificate private key. + Value: !GetAtt [ Certs, Outputs.PrivateKeySecretArn ] + Condition: GenerateCerts + ACMCertificateARNforWebApp: + Description: ARN for an ACM Certificate that is generated for the PortalDomainName + Value: !Ref ACMCertificate + Condition: GenerateCerts + EnvironmentName: + Description: Name of Research and Engineering Studio environment. + Value: !Ref EnvironmentName + Condition: UseEnvironmentName + Keypair: + Description: Keypair used for management instances + Value: !Ref Keypair + ActiveDirectoryName: + Description: Fully Qualified Domain Name (FQDN) for your Active Directory + Value: !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName] ] ] + ADShortName: + Description: Please provide the short name in Active directory + Value: !GetAtt [ DirectoryService, Outputs.DomainShortName ] + LDAPConnectionURI: + Value: !Sub + - ldap://${DomainName} + - { DomainName: !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName] ] ] } + SudoersGroupName: + Value: RESAdministrators + LDAPBase: + Value: !Sub + - dc=${dc} + - { dc: !Join [",dc=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName] ] ] ]] } + ServiceAccountUsername: + Value: ServiceAccount + ServiceAccountPasswordSecretArn: + Value: !GetAtt [ DirectoryService, Outputs.PasswordSecretArn ] + ServiceAccountUserDN: + Description: The Distinguished Name (DN) of the ServiceAccount user in your Active Directory + Value: !Sub + - CN=ServiceAccount,OU=Users,OU=${ou},DC=${dc} + - {dc: !Join [",DC=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName]]]]], ou: !GetAtt [ DirectoryService, Outputs.DomainShortName ]} + SharedHomeFilesystemId: + Value: !GetAtt [ Storage, Outputs.EFSFilesystemId ] + UsersOU: + Description: The OU for all users who might join the system. The value provided here is based off of a supplied LDIF file. + Value: !Sub + - OU=Users,OU=RES,OU=${ou},DC=${dc} + - { dc: !Join [",DC=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName]]]]], ou: !GetAtt [ DirectoryService, Outputs.DomainShortName ]} + GroupsOU: + Description: The OU for groups that users belong to who might join the system. The value provided here is based off of a supplied LDIF file. + Value: !Sub + - OU=Users,OU=RES,OU=${ou},DC=${dc} + - { dc: !Join [",DC=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName]]]]], ou: !GetAtt [ DirectoryService, Outputs.DomainShortName ]} + SudoersOU: + Description: The OU for users who should have sudoers permission across all projects. The value provided here is based off of a supplied LDIF file. + Value: !Sub + - OU=Users,OU=RES,OU=${ou},DC=${dc} + - { dc: !Join [",DC=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName]]]]], ou: !GetAtt [ DirectoryService, Outputs.DomainShortName ]} + ComputersOU: + Description: The OU for computers that join the AD. The value provided here is based off of a supplied LDIF file. + Value: !Sub + - OU=Computers,OU=RES,OU=${ou},DC=${dc} + - { dc: !Join [",DC=", !Split [".", !If [ SubDomainNotProvided, !Ref DomainName, !Join [ ".", [ !Ref SubDomain, !Ref DomainName]]]]], ou: !GetAtt [ DirectoryService, Outputs.DomainShortName ]} + diff --git a/res/res-demo/networking.yaml b/res/res-demo/networking.yaml new file mode 100644 index 00000000..e96d0bfe --- /dev/null +++ b/res/res-demo/networking.yaml @@ -0,0 +1,606 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Create public and private subnets in two or three AZs. Specified CIDR blocks allow 4096 IPs each. + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: VPC + Parameters: + - CidrBlock + - Label: + default: Subnets A + Parameters: + - CidrPublicSubnetA + - CidrPrivateSubnetA + - Label: + default: Subnets B + Parameters: + - CidrPublicSubnetB + - CidrPrivateSubnetB + - Label: + default: Subnets C + Parameters: + - ProvisionSubnetsC + - CidrPublicSubnetC + - CidrPrivateSubnetC + +Parameters: + CidrBlock: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.0.0/16 + Description: VPC CIDR Block (eg 10.3.0.0/16) + Type: String + CidrPublicSubnetA: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.0.0/20 + Description: VPC CIDR Block for the Public Subnet A + Type: String + CidrPublicSubnetB: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.16.0/20 + Description: VPC CIDR Block for the Public Subnet B + Type: String + CidrPublicSubnetC: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.32.0/20 + Description: VPC CIDR Block for the Public Subnet C + Type: String + CidrPrivateSubnetA: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.128.0/20 + Description: VPC CIDR Block for the Private Subnet A + Type: String + CidrPrivateSubnetB: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.144.0/20 + Description: VPC CIDR Block for the Private Subnet B + Type: String + CidrPrivateSubnetC: + AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' + Default: 10.3.160.0/20 + Description: VPC CIDR Block for the Private Subnet C + Type: String + ProvisionSubnetsC: + Type: String + Description: Provision optional 3rd set of subnets + Default: "True" + AllowedValues: + - "True" + - "False" + +Mappings: + RegionMap: + us-east-1: + ZoneId1: use1-az6 + ZoneId2: use1-az4 + ZoneId3: use1-az5 + us-east-2: + ZoneId1: use2-az2 + ZoneId2: use2-az3 + ZoneId3: use2-az1 + us-west-1: + ZoneId1: usw1-az1 + ZoneId2: usw1-az3 + ZoneId3: usw1-az2 + us-west-2: + ZoneId1: usw2-az1 + ZoneId2: usw2-az2 + ZoneId3: usw2-az3 + eu-central-1: + ZoneId1: euc1-az3 + ZoneId2: euc1-az2 + ZoneId3: euc1-az1 + eu-west-1: + ZoneId1: euw1-az1 + ZoneId2: euw1-az2 + ZoneId3: euw1-az3 + eu-west-2: + ZoneId1: euw2-az2 + ZoneId2: euw2-az3 + ZoneId3: euw2-az1 + eu-west-3: + ZoneId1: euw3-az1 + ZoneId2: euw3-az2 + ZoneId3: euw3-az3 + eu-north-1: + ZoneId1: eun1-az2 + ZoneId2: eun1-az1 + ZoneId3: eun1-az3 + ca-central-1: + ZoneId1: cac1-az2 + ZoneId2: cac1-az1 + ZoneId3: cac1-az3 + eu-south-1: + ZoneId1: eus1-az2 + ZoneId2: eus1-az1 + ZoneId3: eus1-az3 + ap-east-1: + ZoneId1: ape1-az3 + ZoneId2: ape1-az2 + ZoneId3: ape1-az1 + ap-northeast-1: + ZoneId1: apne1-az4 + ZoneId2: apne1-az1 + ZoneId3: apne1-az2 + ap-northeast-2: + ZoneId1: apne2-az1 + ZoneId2: apne2-az3 + ZoneId3: apne2-az2 + ap-south-1: + ZoneId1: aps1-az2 + ZoneId2: aps1-az3 + ZoneId3: aps1-az1 + ap-southeast-1: + ZoneId1: apse1-az1 + ZoneId2: apse1-az2 + ZoneId3: apse1-az3 + ap-southeast-2: + ZoneId1: apse2-az3 + ZoneId2: apse2-az1 + ZoneId3: apse2-az2 + us-gov-west-1: + ZoneId1: usgw1-az1 + ZoneId2: usgw1-az2 + ZoneId3: usgw1-az3 + ap-northeast-3: + ZoneId1: apne3-az3 + ZoneId2: apne3-az2 + ZoneId3: apne3-az1 + sa-east-1: + ZoneId1: sae1-az3 + ZoneId2: sae1-az2 + ZoneId3: sae1-az1 + af-south-1: + ZoneId1: afs1-az3 + ZoneId2: afs1-az2 + ZoneId3: afs1-az1 + ap-south-2: + ZoneId1: aps2-az3 + ZoneId2: aps2-az2 + ZoneId3: aps2-az1 + ap-southeast-3: + ZoneId1: apse3-az3 + ZoneId2: apse3-az2 + ZoneId3: apse3-az1 + ap-southeast-4: + ZoneId1: apse4-az3 + ZoneId2: apse4-az2 + ZoneId3: apse4-az1 + ca-west-1: + ZoneId1: caw1-az3 + ZoneId2: caw1-az2 + ZoneId3: caw1-az1 + eu-central-2: + ZoneId1: euc2-az3 + ZoneId2: euc2-az2 + ZoneId3: euc2-az1 + eu-south-2: + ZoneId1: eus2-az3 + ZoneId2: eus2-az2 + ZoneId3: eus2-az1 + il-central-1: + ZoneId1: ilc1-az3 + ZoneId2: ilc1-az2 + ZoneId3: ilc1-az1 + me-central-1: + ZoneId1: mec1-az3 + ZoneId2: mec1-az2 + ZoneId3: mec1-az1 + +Conditions: + DoProvisionSubnetsC: !Equals [!Ref ProvisionSubnetsC, "True"] + +Resources: + + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: !Ref CidrBlock + EnableDnsHostnames: true + EnableDnsSupport: true + Tags: + - Key: "Name" + Value: !Sub '${AWS::StackName}:Large-Scale-HPC' + + PublicSubnetA: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref CidrPublicSubnetA + AvailabilityZone: !GetAtt AvailabiltyZone1.ZoneName + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub + - '${StackName}:PublicSubnetA-${AvailabilityZone}' + - StackName: !Ref AWS::StackName + AvailabilityZone: !GetAtt AvailabiltyZone1.ZoneName + + PublicSubnetB: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref CidrPublicSubnetB + AvailabilityZone: !GetAtt AvailabiltyZone2.ZoneName + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub + - '${StackName}:PublicSubnetB-${AvailabilityZone}' + - StackName: !Ref AWS::StackName + AvailabilityZone: !GetAtt AvailabiltyZone2.ZoneName + + PublicSubnetC: + Type: AWS::EC2::Subnet + Condition: DoProvisionSubnetsC + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref CidrPublicSubnetC + AvailabilityZone: !GetAtt AvailabiltyZone3.ZoneName + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub + - '${StackName}:PublicSubnetC-${AvailabilityZone}' + - StackName: !Ref AWS::StackName + AvailabilityZone: !GetAtt AvailabiltyZone3.ZoneName + + InternetGateway: + Type: AWS::EC2::InternetGateway + + AttachGateway: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref VPC + InternetGatewayId: !Ref InternetGateway + + PublicRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}:PublicRoute' + PublicRoute1: + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + + PublicSubnetARouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnetA + RouteTableId: !Ref PublicRouteTable + + PublicSubnetBRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnetB + RouteTableId: !Ref PublicRouteTable + + PublicSubnetCRouteTableAssociation: + Condition: DoProvisionSubnetsC + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnetC + RouteTableId: !Ref PublicRouteTable + + PrivateSubnetA: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + AvailabilityZone: !GetAtt AvailabiltyZone1.ZoneName + CidrBlock: !Ref CidrPrivateSubnetA + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: !Sub + - '${StackName}:PrivateSubnetA-${AvailabilityZone}' + - StackName: !Ref AWS::StackName + AvailabilityZone: !GetAtt AvailabiltyZone1.ZoneName + + PrivateSubnetB: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + AvailabilityZone: !GetAtt AvailabiltyZone2.ZoneName + CidrBlock: !Ref CidrPrivateSubnetB + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: !Sub + - '${StackName}:PrivateSubnetB-${AvailabilityZone}' + - StackName: !Ref AWS::StackName + AvailabilityZone: !GetAtt AvailabiltyZone2.ZoneName + + PrivateSubnetC: + Type: AWS::EC2::Subnet + Condition: DoProvisionSubnetsC + Properties: + VpcId: !Ref VPC + AvailabilityZone: !GetAtt AvailabiltyZone3.ZoneName + CidrBlock: !Ref CidrPrivateSubnetC + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: !Sub + - '${StackName}:PrivateSubnetC-${AvailabilityZone}' + - StackName: !Ref AWS::StackName + AvailabilityZone: !GetAtt AvailabiltyZone3.ZoneName + + NatGatewayAEIP: + Type: AWS::EC2::EIP + DependsOn: AttachGateway + Properties: + Domain: vpc + + NatGatewayBEIP: + Type: AWS::EC2::EIP + DependsOn: AttachGateway + Properties: + Domain: vpc + + NatGatewayCEIP: + Condition: DoProvisionSubnetsC + Type: AWS::EC2::EIP + DependsOn: AttachGateway + Properties: + Domain: vpc + + NatGatewayA: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt NatGatewayAEIP.AllocationId + SubnetId: !Ref PublicSubnetA + + NatGatewayB: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt NatGatewayBEIP.AllocationId + SubnetId: !Ref PublicSubnetB + + NatGatewayC: + Type: AWS::EC2::NatGateway + Condition: DoProvisionSubnetsC + Properties: + AllocationId: !GetAtt NatGatewayCEIP.AllocationId + SubnetId: !Ref PublicSubnetC + + PrivateRouteTableA: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}:PrivateRouteA' + + PrivateRouteTableB: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}:PrivateRouteB' + + PrivateRouteTableC: + Type: AWS::EC2::RouteTable + Condition: DoProvisionSubnetsC + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}:PrivateRouteC' + + DefaultPrivateRouteA: + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRouteTableA + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGatewayA + + DefaultPrivateRouteB: + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRouteTableB + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGatewayB + + DefaultPrivateRouteC: + Type: AWS::EC2::Route + Condition: DoProvisionSubnetsC + Properties: + RouteTableId: !Ref PrivateRouteTableC + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGatewayC + + PrivateSubnetARouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: !Ref PrivateRouteTableA + SubnetId: !Ref PrivateSubnetA + + PrivateSubnetBRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: !Ref PrivateRouteTableB + SubnetId: !Ref PrivateSubnetB + + PrivateSubnetCRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: DoProvisionSubnetsC + Properties: + RouteTableId: !Ref PrivateRouteTableC + SubnetId: !Ref PrivateSubnetC + + AvailabiltyZone1: + Type: Custom::AvailabiltyZone + DependsOn: LogGroupGetAZLambdaFunction + Properties: + ServiceToken: !GetAtt GetAZLambdaFunction.Arn + ZoneId: !FindInMap [RegionMap, !Ref "AWS::Region", ZoneId1] + + AvailabiltyZone2: + Type: Custom::AvailabiltyZone + DependsOn: LogGroupGetAZLambdaFunction + Properties: + ServiceToken: !GetAtt GetAZLambdaFunction.Arn + ZoneId: !FindInMap [RegionMap, !Ref "AWS::Region", ZoneId2] + + AvailabiltyZone3: + Type: Custom::AvailabiltyZone + Condition: DoProvisionSubnetsC + DependsOn: LogGroupGetAZLambdaFunction + Properties: + ServiceToken: !GetAtt GetAZLambdaFunction.Arn + ZoneId: !FindInMap [RegionMap, !Ref "AWS::Region", ZoneId3] + + LogGroupGetAZLambdaFunction: + Type: AWS::Logs::LogGroup + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + LogGroupName: !Sub /aws/lambda/${GetAZLambdaFunction} + RetentionInDays: 7 + + GetAZLambdaFunction: + Type: AWS::Lambda::Function + Properties: + Description: GetAZLambdaFunction + Timeout: 60 + Runtime: python3.9 + Handler: index.handler + Role: !GetAtt GetAZLambdaRole.Arn + Code: + ZipFile: | + import cfnresponse + from json import dumps + from boto3 import client + EC2 = client('ec2') + def handler(event, context): + if event['RequestType'] in ('Create', 'Update'): + print(dumps(event, default=str)) + data = {} + try: + response = EC2.describe_availability_zones( + Filters=[{'Name': 'zone-id', 'Values': [event['ResourceProperties']['ZoneId']]}] + ) + print(dumps(response, default=str)) + data['ZoneName'] = response['AvailabilityZones'][0]['ZoneName'] + except Exception as error: + cfnresponse.send(event, context, cfnresponse.FAILED, {}, reason=error) + finally: + cfnresponse.send(event, context, cfnresponse.SUCCESS, data) + else: + cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}GetAZLambdaFunction + + GetAZLambdaRole: + Type: AWS::IAM::Role + Properties: + Path: / + Description: GetAZLambdaFunction + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - sts:AssumeRole + Principal: + Service: + - !Sub 'lambda.${AWS::URLSuffix}' + ManagedPolicyArns: + - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' + Policies: + - PolicyName: GetAZLambdaFunction + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: ec2 + Effect: Allow + Action: + - ec2:DescribeAvailabilityZones + Resource: + - '*' + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-GetAZLambdaFunction + + S3Endpoint: + Type: 'AWS::EC2::VPCEndpoint' + Properties: + VpcEndpointType: 'Gateway' + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3' + RouteTableIds: + - !Ref PublicRouteTable + - !Ref PrivateRouteTableA + - !Ref PrivateRouteTableB + VpcId: !Ref VPC + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Allow all traffic from resources in VPC + VpcId: + Ref: VPC + SecurityGroupIngress: + - IpProtocol: -1 + CidrIp: !Ref CidrBlock + SecurityGroupEgress: + - IpProtocol: -1 + CidrIp: !Ref CidrBlock + +Outputs: + VPC: + Value: !Ref VPC + Description: ID of the VPC + Export: + Name: !Sub ${AWS::StackName}-VPC + PublicSubnets: + Value: !Join + - ',' + - - !Ref PublicSubnetA + - !Ref PublicSubnetB + - !If + - DoProvisionSubnetsC + - !Ref PublicSubnetC + - !Ref AWS::NoValue + Description: ID of the public subnets + Export: + Name: !Sub ${AWS::StackName}-PublicSubnets + PrivateSubnets: + Value: !Join + - ',' + - - !Ref PrivateSubnetA + - !Ref PrivateSubnetB + - !If + - DoProvisionSubnetsC + - !Ref PrivateSubnetC + - !Ref AWS::NoValue + Description: ID of the private subnets + Export: + Name: !Sub ${AWS::StackName}-PrivateSubnets + DefaultPrivateSubnet: + Description: The ID of a default private subnet + Value: !Ref PrivateSubnetA + Export: + Name: !Sub "${AWS::StackName}-DefaultPrivateSubnet" + DefaultPublicSubnet: + Description: The ID of a default public subnet + Value: !Ref PublicSubnetA + Export: + Name: !Sub "${AWS::StackName}-DefaultPublicSubnet" + InternetGatewayId: + Description: The ID of the Internet Gateway + Value: !Ref InternetGateway + Export: + Name: !Sub "${AWS::StackName}-InternetGateway" + SecurityGroup: + Description: The ID of the local security group + Value: !Ref SecurityGroup + Export: + Name: !Sub "${AWS::StackName}-SecurityGroup" diff --git a/res/res-demo/res-demo-stack.yaml b/res/res-demo/res-demo-stack.yaml new file mode 100644 index 00000000..c091303b --- /dev/null +++ b/res/res-demo/res-demo-stack.yaml @@ -0,0 +1,251 @@ +Description: Research and Engineering Studio on AWS demo environment + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: RES Configuration + Parameters: + - EnvironmentName + - AdministratorEmail + - Label: + default: Access Management + Parameters: + - Keypair + - ClientIpCidr + - InboundPrefixList + +Parameters: + + Keypair: + Description: EC2 Keypair to access management instance. + Type: AWS::EC2::KeyPair::KeyName + Default: "" + + EnvironmentName: + Description: Provide name of the RES Environment. Must be unique for your account and AWS Region. + Type: String + Default: res-demo + AllowedPattern: ^res-[A-Za-z\-\_0-9]{0,7}$ + ConstraintDescription: EnvironmentName must start with "res-" and should be less than or equal to 11 characters. + + AdministratorEmail: + Type: String + AllowedPattern: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ + + ClientIpCidr: + Description: Default IP(s) allowed to directly access the Web UI, SSH into the bastion host, and access the Windows AD admin host. We recommend that you restrict it with your own IP/subnet (x.x.x.x/32 for your own ip or x.x.x.x/24 for range. Replace x.x.x.x with your own PUBLIC IP. You can get your public IP using tools such as https://ifconfig.co/) + Default: 0.0.0.0/0 + Type: String + AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2}) + ConstraintDescription: Value must be a valid IP or network range of the form x.x.x.x/x. + + InboundPrefixList: + Description: (Optional) VPC Prefix List controlling inbound access to Web UI, bastion host, and Windows AD admin host. + Default: "" + Type: String + AllowedPattern: ^(pl-[a-z0-9]{8,20})?$ + ConstraintDescription: Must be a valid VPC Prefix List ID, which begins with `pl-` or be empty. + +Conditions: + UseEnvironmentName: !Not [!Equals [!Ref EnvironmentName, ""]] + +Resources: + + AdminPassword: + Type: AWS::SecretsManager::Secret + Properties: + Description: Active Directory Administrator Account Password. + Name: !Sub [ "res-AdminPassword-${StackName}-${StackId}", {StackName: !Select [1, !Split ['/', !Ref 'AWS::StackId']], StackId: !Select [2, !Split ['/', !Ref 'AWS::StackId']]}] + GenerateSecretString: + SecretStringTemplate: '{"username": "Admin"}' + GenerateStringKey: "password" + ExcludePunctuation: true + Tags: + - Key: res:Deployment + Value: "true" + - Key: res:EnvironmentName + Value: !Ref EnvironmentName + + ServiceAccountPassword: + Type: AWS::SecretsManager::Secret + Properties: + Description: Active Directory Service Account Password. + Name: !Sub [ "res-ServiceAccountPassword-${StackName}-${StackId}", {StackName: !Select [1, !Split ['/', !Ref 'AWS::StackId']], StackId: !Select [2, !Split ['/', !Ref 'AWS::StackId']]}] + GenerateSecretString: + SecretStringTemplate: '{"username": "ServiceAccount"}' + GenerateStringKey: "password" + ExcludePunctuation: true + Tags: + - Key: res:Deployment + Value: "true" + - Key: res:EnvironmentName + Value: !Ref EnvironmentName + + RESExternal: + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + PortalDomainName: "" + Keypair: !Ref Keypair + EnvironmentName: !If [UseEnvironmentName, !Ref EnvironmentName, ""] + AdminPassword: !Ref AdminPassword + ServiceAccountPassword: !Ref ServiceAccountPassword + ClientIpCidr: !Ref ClientIpCidr + ClientPrefixList: !Ref InboundPrefixList + RetainStorageResources: "False" + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/res/res_demo_env/assets/bi.yaml + + RES: + Type: AWS::CloudFormation::Stack + DependsOn: InvokeDeleteSharedStorageSecurityGroup + Properties: + Parameters: + EnvironmentName: !Ref EnvironmentName + AdministratorEmail: !Ref AdministratorEmail + SSHKeyPair: !Ref Keypair + ClientIp: !Ref ClientIpCidr + ClientPrefixList: !Ref InboundPrefixList + CustomDomainNameforWebApp: "" + ACMCertificateARNforWebApp: "" + CustomDomainNameforVDI: "" + PrivateKeySecretARNforVDI: "" + CertificateSecretARNforVDI: "" + DomainTLSCertificateSecretArn: "" + VpcId: !GetAtt [ RESExternal, Outputs.VpcId ] + LoadBalancerSubnets: !GetAtt [ RESExternal, Outputs.PublicSubnets ] + InfrastructureHostSubnets: !GetAtt [ RESExternal, Outputs.PrivateSubnets ] + VdiSubnets: !GetAtt [ RESExternal, Outputs.PrivateSubnets ] + IsLoadBalancerInternetFacing: "true" + ActiveDirectoryName: !GetAtt [ RESExternal, Outputs.ActiveDirectoryName ] + ADShortName: !GetAtt [ RESExternal, Outputs.ADShortName ] + LDAPBase: !GetAtt [ RESExternal, Outputs.LDAPBase ] + LDAPConnectionURI: !GetAtt [ RESExternal, Outputs.LDAPConnectionURI ] + SudoersGroupName: RESAdministrators + ServiceAccountUsername: !GetAtt [ RESExternal, Outputs.ServiceAccountUsername ] + ServiceAccountPasswordSecretArn: !GetAtt [ RESExternal, Outputs.ServiceAccountPasswordSecretArn ] + UsersOU: !GetAtt [ RESExternal, Outputs.UsersOU ] + GroupsOU: !GetAtt [ RESExternal, Outputs.GroupsOU ] + SudoersOU: !GetAtt [ RESExternal, Outputs.SudoersOU ] + ComputersOU: !GetAtt [ RESExternal, Outputs.ComputersOU ] + SharedHomeFileSystemId: !GetAtt [ RESExternal, Outputs.SharedHomeFilesystemId ] + InfrastructureHostAMI: "" + EnableLdapIDMapping: "True" + IAMPermissionBoundary: "" + DisableADJoin: "False" + ServiceAccountUserDN: !GetAtt [ RESExternal, Outputs.ServiceAccountUserDN ] + TemplateURL: https://research-engineering-studio-us-east-1.s3.amazonaws.com/releases/latest/ResearchAndEngineeringStudio.template.json + + RESSsoKeycloak: + Type: AWS::CloudFormation::Stack + DependsOn: RES + Properties: + Parameters: + EnvironmentName: !Ref EnvironmentName + Keypair: !Ref Keypair + ServiceAccountPasswordSecretArn: !GetAtt [ RESExternal, Outputs.ServiceAccountPasswordSecretArn ] + VpcId: !GetAtt [ RESExternal, Outputs.VpcId ] + PublicSubnet: !Select [0, !Split [",", !GetAtt RESExternal.Outputs.PublicSubnets]] + ServiceAccountUserDN: !GetAtt [ RESExternal, Outputs.ServiceAccountUserDN ] + UsersDN: !GetAtt [ RESExternal, Outputs.LDAPBase ] + LDAPConnectionURI: !GetAtt [ RESExternal, Outputs.LDAPConnectionURI ] + TemplateURL: https://aws-hpc-recipes.s3.us-east-1.amazonaws.com/main/recipes/res/res_demo_env/assets/res-sso-keycloak.yaml + + InvokeDeleteSharedStorageSecurityGroupRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: 'sts:AssumeRole' + Policies: + - PolicyName: InvokeConfigureSSOLambdaPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + Resource: + - !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${EnvironmentName}-delete_shared_storage_security_group + - Effect: Allow + Action: + - ec2:DescribeSecurityGroups + - ec2:DeleteSecurityGroup + - ec2:DescribeNetworkInterfaces + Resource: '*' + + InvokeDeleteSharedSecurityGroupHandlerFunction: + Type: 'AWS::Lambda::Function' + DependsOn: + - InvokeDeleteSharedStorageSecurityGroupRole + Properties: + Description: 'Deletes the shared storage security group when the stack is deleted.' + FunctionName: !Sub InvokeDeleteSharedSecurityGroupHandlerFunction-${AWS::StackName} + Timeout: 360 # 6 minutes + Role: !GetAtt InvokeDeleteSharedStorageSecurityGroupRole.Arn + Handler: index.handler + Runtime: python3.11 + Code: + ZipFile: | + import boto3 + import os + import logging + import cfnresponse + + logger = logging.getLogger() + logger.setLevel(logging.INFO) + + def handler(event, context): + logger.info(f"Received event: {event}") + response = {} + + if event["RequestType"] == "Delete": + try: + ec2 = boto3.client("ec2") + sgResponse = ec2.describe_security_groups( + Filters=[ + { + 'Name': 'group-name', + 'Values': [ + f"{os.environ['ENVIRONMENT_NAME']}-shared-storage-security-group", + ] + } + ] + ) + + if len(sgResponse['SecurityGroups']) == 0: + response['Output'] = "Shared storage security group not found." + else: + ec2.delete_security_group(GroupId=sgResponse['SecurityGroups'][0]['GroupId']) + response['Output'] = "Shared storage security group deleted." + + cfnresponse.send(event, context, cfnresponse.SUCCESS, response) + except Exception as e: + logger.error(f"Error: Unable to delete shared storage security group: {e}") + response['Output'] = f"Error: Unable to delete shared storage security group: {e}" + cfnresponse.send(event, context, cfnresponse.FAILED, response) + else: + cfnresponse.send(event, context, cfnresponse.SUCCESS, response) + Environment: + Variables: + ENVIRONMENT_NAME: !Ref EnvironmentName + + InvokeDeleteSharedStorageSecurityGroup: + Type: Custom::DeleteSharedStorageSecurityGroup + Properties: + ServiceToken: !GetAtt InvokeDeleteSharedSecurityGroupHandlerFunction.Arn + +Outputs: + KeycloakUrl: + Description: Keycloak Administrator Url + Value: !GetAtt [ RESSsoKeycloak, Outputs.KeycloakUrl ] + KeycloakAdminPasswordSecretArn: + Description: Keycloak password for admin user + Value: !GetAtt [ RESSsoKeycloak, Outputs.KeycloakAdminPasswordSecretArn ] + ApplicationUrl: + Description: RES application Url + Value: !GetAtt [ RESSsoKeycloak, Outputs.ApplicationUrl ] diff --git a/res/res-demo/res.ldif b/res/res-demo/res.ldif new file mode 100644 index 00000000..57fc8cb4 --- /dev/null +++ b/res/res-demo/res.ldif @@ -0,0 +1,136 @@ +# Create a OU to be used by RES +dn: OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: organizationalUnit +ou: RES +description: The RES application will limit syncing groups and group-members in the RES OU + +# Create a OU to be used by RES to create computers +dn: OU=Computers,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: organizationalUnit +ou: Computers +description: The RES application will limit creating computers to this OU + +# Create a OU to be used by RES to create groups and add users to +dn: OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: organizationalUnit +ou: Users +description: The RES application will limit syncing groups and group-members in the RES OU + +# Create a user: user1 in RES OU +dn: CN=user1,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user1 +sAMAccountName: user1 +name: user1 +userPrincipalName: user1@${DirectoryDomain} +mail: user1@${DirectoryDomain} +uidNumber: 1011 +gidNumber: 1006 +unixHomeDirectory: /home/user1 +loginShell: /bin/bash + +# Create a user: admin1 in RES OU +dn: CN=admin1,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin1 +sAMAccountName: admin1 +name: admin1 +userPrincipalName: admin1@${DirectoryDomain} +mail: admin1@${DirectoryDomain} +uidNumber: 1012 +gidNumber: 1006 +unixHomeDirectory: /home/admin1 +loginShell: /bin/bash + +# Create a user: user2 outside RES OU +dn: CN=user2,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: user2 +sAMAccountName: user2 +name: user2 +userPrincipalName: user2@${DirectoryDomain} +mail: user2@${DirectoryDomain} +uidNumber: 1013 +gidNumber: 1007 +unixHomeDirectory: /home/user2 +loginShell: /bin/bash + +# Create a user: admin2 outside RES OU +dn: CN=admin2,OU=Users,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: user +cn: admin2 +sAMAccountName: admin2 +name: admin2 +userPrincipalName: admin2@${DirectoryDomain} +mail: admin2@${DirectoryDomain} +uidNumber: 1014 +gidNumber: 1007 +unixHomeDirectory: /home/admin2 +loginShell: /bin/bash + +# Create a group: RESAdministrators in RES OU +dn: CN=RESAdministrators,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: group +cn: RESAdministrators +description: Represents the group of sudoers and admins in RES +distinguishedName: CN=RESAdministrators,OU=Users,OU=RES,OU=${OU},DC=${DC} +name: RESAdministrators +sAMAccountName: RESAdministrators +objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=${DC} +gidNumber: 1005 +member: CN=admin1,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=admin2,OU=Users,OU=${OU},DC=${DC} + +# Create a group: group1 in RES OU +dn: CN=group_1,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: group +cn: group_1 +description: Represents a project group in RES +distinguishedName: CN=group_1,OU=Users,OU=RES,OU=${OU},DC=${DC} +name: group_1 +sAMAccountName: group_1 +objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=${DC} +gidNumber: 1006 +member: CN=user1,OU=Users,OU=RES,OU=${OU},DC=${DC} +member: CN=admin1,OU=Users,OU=RES,OU=${OU},DC=${DC} + +# Create a group: group2 in RES OU +dn: CN=group_2,OU=Users,OU=RES,OU=${OU},DC=${DC} +changetype: add +objectClass: top +objectClass: group +cn: group_2 +description: Represents a project group in RES +distinguishedName: CN=group_2,OU=Users,OU=RES,OU=${OU},DC=${DC} +name: group_2 +sAMAccountName: group_2 +objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=${DC} +gidNumber: 1007 +member: CN=user2,OU=Users,OU=${OU},DC=${DC} +member: CN=admin2,OU=Users,OU=${OU},DC=${DC} diff --git a/res/upload-res-templates.py b/res/upload-res-templates.py new file mode 100755 index 00000000..7e04c61e --- /dev/null +++ b/res/upload-res-templates.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +""" +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" + +""" +It's recommended to trigger this script via deploy-res.sh as python's virtual env and all required +libraries/dependencies will be automatically installed. + +If you run this script directly, make sure to have all the Python and CDK dependencies installed. +""" + +import argparse +import boto3 +from jinja2 import Template as JinjaTemplate +import logging +import os +import os.path +from os.path import dirname, realpath +from shutil import copy +import sys + +script_path = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(script_path) + +logger = logging.getLogger(__file__) +logger_formatter = logging.Formatter('%(levelname)s: %(message)s') +logger_streamHandler = logging.StreamHandler() +logger_streamHandler.setFormatter(logger_formatter) +logger.addHandler(logger_streamHandler) +logger.propagate = False +logger.setLevel(logging.INFO) + +class UploadRESTemplates(): + + def __init__(self): + self.stack_parameters = {} + + def main(self): + parser = argparse.ArgumentParser(description="Configure and upload RES templates to S3 so they can be deployed.") + parser.add_argument("--s3-bucket", type=str, required=True, help="S3 bucket for templates.") + parser.add_argument("--s3-base-key", type=str, required=True, help="Base S3 bucket key for templates.") + parser.add_argument("--region", '-r', type=str, default='us-east-1', help="AWS region to use for s3 commands.") + parser.add_argument("--debug", action='store_const', const=True, default=False, help="Enable debug messages.") + args = parser.parse_args() + + if args.debug: + logger.setLevel(logging.DEBUG) + + # Use script location as current working directory + script_directory = os.path.dirname(os.path.realpath(f"{__file__}")) + os.chdir(script_directory) + logger.info(f"Working directory: {script_directory}") + + template_vars = { + 'LDIFS3Path': f"{args.s3_bucket}/{args.s3_base_key}/res.ldif", + 'TemplateBucket': args.s3_bucket, + 'TemplateBaseKey': args.s3_base_key + } + src_dir = 'res-demo-with-cidr' + dst_dir = 'rendered_templates' + template_files = [ + 'res-demo-stack.yaml', + 'bi.yaml' + ] + os.makedirs(dst_dir, exist_ok=True) + for template_file in template_files: + jinja_template = JinjaTemplate(open(f"{src_dir}/{template_file}", 'r').read()) + fh = open(f"rendered_templates/{template_file}", 'w') + fh.write(jinja_template.render(**template_vars)) + fh.close() + copy(f"{src_dir}/res.ldif", dst_dir) + + s3_client = boto3.client('s3', region_name=args.region) + for root, dirs, files in os.walk(dst_dir): + for filename in files: + s3_client.put_object( + Bucket = args.s3_bucket, + Key = os.path.join(args.s3_base_key, filename), + Body = open(os.path.join(root, filename), 'r').read() + ) + os.path.join(root, filename) + + logger.info("Use the following link to deploy RES using CloudFormation.") + logger.info(f"https://console.aws.amazon.com/cloudformation/home?region={args.region}#/stacks/quickcreate?templateURL=https://{args.s3_bucket}.s3.amazonaws.com/{args.s3_base_key}/res-demo-stack.yaml") + +if __name__ == "__main__": + app = UploadRESTemplates() + app.main() diff --git a/res/upload-res-templates.sh b/res/upload-res-templates.sh new file mode 100755 index 00000000..41507eb7 --- /dev/null +++ b/res/upload-res-templates.sh @@ -0,0 +1,9 @@ +#!/bin/bash -xe + +script_dir=$(dirname $(realpath $0)) +cd $script_dir + +python3 -m venv .venv +source .venv/bin/activate +python3 -m pip install -r requirements.txt +./upload-res-templates.py "$@" diff --git a/source/resources/playbooks/install_vscode.yml b/source/resources/playbooks/install_vscode.yml new file mode 100644 index 00000000..d2f8a08b --- /dev/null +++ b/source/resources/playbooks/install_vscode.yml @@ -0,0 +1,8 @@ +--- +- name: Install VSCode + hosts: local + connection: local + become_user: root + become: yes + roles: + - install_vscode diff --git a/source/resources/playbooks/roles/install_vscode/README.md b/source/resources/playbooks/roles/install_vscode/README.md new file mode 100644 index 00000000..3f16efa6 --- /dev/null +++ b/source/resources/playbooks/roles/install_vscode/README.md @@ -0,0 +1,14 @@ +install_vscode +========= + +Install vscode + +Requirements +------------ + +Requires root permissions so that it can install the repo and package + +Role Variables +-------------- + +None diff --git a/source/resources/playbooks/roles/install_vscode/tasks/main.yml b/source/resources/playbooks/roles/install_vscode/tasks/main.yml new file mode 100644 index 00000000..8b6d7d18 --- /dev/null +++ b/source/resources/playbooks/roles/install_vscode/tasks/main.yml @@ -0,0 +1,43 @@ +--- +# tasks file for install_vscode + +- name: Show variables used by this role + debug: + msg: | + ansible_architecture: {{ ansible_architecture }} + architecture: {{ architecture }} + distribution: {{ distribution }} + distribution_major_version: {{ distribution_major_version }} + distribution_version: {{ distribution_version }} + + amazonlinux2: {{ amazonlinux2 }} + alma: {{ alma }} + alma8: {{ alma8 }} + centos: {{ centos }} + centos7: {{ centos7 }} + rhel: {{ rhel }} + rhel7: {{ rhel7 }} + rhel8: {{ rhel8 }} + rhel9: {{ rhel9 }} + rocky: {{ rocky }} + rocky8: {{ rocky8 }} + rocky9: {{ rocky9 }} + rhelclone: {{ rhelclone }} + rhel8clone: {{ rhel8clone }} + rhel9clone: {{ rhel9clone }} + +- name: Install vscode key and yum repository: + shell: + cmd: | + rpm --import https://packages.microsoft.com/keys/microsoft.asc + echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" | sudo tee /etc/yum.repos.d/vscode.repo > /dev/null + args: + creates: "/etc/yum.repos.d/vscode.repo" + +- name: Update package cache and install vscode + shell: + cmd: | + dnf check-update || yum check-update + dnf -y install code || yum -y install code + args: + creates: "/bin/code"