Configuring Active Directory for VMs to automatically join a domain

This tutorial shows you how to configure Active Directory and Compute Engine so that Windows virtual machine (VM) instances can automatically join an Active Directory domain.

Automating the process of joining Windows VMs to Active Directory helps you simplify the provisioning of Windows servers. The approach also allows you to take advantage of autoscaling without sacrificing the benefits of using Active Directory to manage access and configuration.

This tutorial is intended for system admins and assumes that you are familiar with Active Directory and Google Cloud networking.

Objectives

  • Enable VM instances from selected projects to automatically join your Active Directory domain.
  • Set up domain-joined VM instances that are managed in autoscaled managed instance groups (MIGs).
  • Automate a process that detects and removes stale computer accounts in Active Directory.

Costs

This tutorial uses the following billable components of Google Cloud:

The tutorial is designed to keep your resource usage within the limits of Google Cloud's Always Free tier. To generate a cost estimate based on your projected usage, use the pricing calculator. New Google Cloud users might be eligible for a free trial.

Before you begin

This tutorial assumes that Active Directory is deployed on Google Cloud by using Managed Service for Microsoft Active Directory (Managed Microsoft AD) or by deploying self-managed domain controllers on Google Cloud.

To complete this tutorial, ensure that you have the following:

  • Administrative access to your Active Directory domain, including the ability to create users and organizational units (OUs).
  • An unused /28 CIDR IP range in the VPC that your Active Directory domain controllers are deployed in. You use this IP range to configure Serverless VPC Access.
  • A subnet into which you deploy Windows instances. The subnet must be configured to use Private Google Access.

If you use a self-managed domain controller, you also need the following:

  • A private DNS forwarding zone that forwards DNS queries to your Active Directory domain controllers.
  • Domain controllers that are deployed either into a standalone VPC or into the host project of a Shared VPC. Domain controllers that are deployed into a service project are not currently supported.

Implementing this approach

In an on-premises environment, you might rely on answer files (unattend.xml) and the JoinDomain customization to automatically join new computers to a domain. Although you can use the same process in Google Cloud, this approach has several limitations:

  • Using a customized unattend.xml file requires that you maintain a custom Compute Engine image. Keeping a custom image current using Windows Updates requires either ongoing maintenance or initial work to set up automation. Unless you need to maintain a custom image for other reasons, this extra effort might not be justified.
  • Using the JoinDomain customization ties an image to a single Active Directory domain because the domain name must be specified in unattend.xml. If you maintain multiple Active Directory domains or forests (for example, for separate testing and production environments), then you might need to maintain multiple custom images for each domain.
  • Joining a Windows computer to a domain requires user credentials that have permissions to create a computer object in the directory. If you use the JoinDomain customization in unattend.xml, you must embed these credentials as plaintext in unattend.xml. These embedded credentials can turn the image into a potential target for attackers. Although you can control access to the image by setting appropriate Identity and Access Management (IAM) permissions, managing access to a custom image adds unnecessary complexity.

The approach that this tutorial takes does not use answer files and therefore does not require specially prepared images. Instead, this approach relies on a sysprep specialize scriptlet. When you create a VM instance, you pass the scriptlet by using the following argument, where [DOMAIN] is the name of the Active Directory domain your VM instance is joining:

iex((New-Object System.Net.WebClient).DownloadString('https://[DOMAIN]/register-computer'))

The sysprep specialize scriptlet initiates a process that the following diagram illustrates.

Process that the sysprep specialize scriptlet initiates.

The process works as follows:

  1. After a VM instance is created, Windows boots for the first time. As part of the specialize configuration pass, Windows invokes the sysprep specialize script. The script invokes a Cloud Function named register-computer to download a PowerShell script that controls the domain joining process.
  2. Windows invokes the downloaded PowerShell script.
  3. The PowerShell script calls the metadata server to obtain an ID token that identifies the VM instance in a unique, security-enhanced way.
  4. The script calls the register-computer Cloud Function again, passing the ID token to authenticate itself.
  5. The function validates the ID token and extracts the name, zone, and Google Cloud project ID of the VM instance.
  6. The function verifies that the Active Directory domain is configured to permit VM instances from the given project to join the domain. To complete this verification, the function locates and connects to an Active Directory domain controller to check for an organizational unit (OU) whose name matches the Google Cloud project ID from the ID token. If a matching OU is found, then VM instances of the project are authorized to join the Active Directory domain in the given OU.
  7. The function verifies that the Google Cloud project is configured to allow VM instances to join Active Directory. To complete this verification, the function checks whether it can access the VM instance by using the Compute Engine API.
  8. If all checks pass successfully, the function creates a computer account in Active Directory. The function saves the VM instance's name, zone, and ID as attributes in the computer account object so that it can be associated with the VM instance.
  9. Using the Kerberos set password protocol, the function then assigns a random password to the computer account.
  10. The computer name and password are returned to the Windows instance over a TLS-secured channel.
  11. Using the pre-created computer account, the PowerShell script joins the computer to the domain.
  12. After the specialize configuration pass is complete, the machine reboots itself.

The remainder of this tutorial walks you through the steps that are required to set up automated domain joining.

Preparing the Active Directory domain

First, you prepare your Active Directory domain to let other computers automatically join it. To complete this step, you need a machine that has administrative access to your Active Directory domain.

Optional: Limit who can join computers to the domain

You might want to restrict who can join computers to the domain. By default, the Group Policy Object (GPO) configuration for the Default Domain Controller Policy grants the Add workstations to domain user right to all authenticated users. Anyone with that user right can join computers to the domain. Because you are automating the process of joining computers to your Active Directory domain, universally granting this level of access is an unnecessary security risk.

To limit who can join computers to your Active Directory domain, change the default configuration of the Default Domain Controller Policy GPO:

  1. Using an RDP client, log in to a machine that has administrative access to your Active Directory domain.
  2. Open the Group Policy Management Console (GPMC).
  3. Go to Forest > Domains > domain-name > Group Policy Objects, where domain-name is the name of your Active Directory domain.
  4. Right-click Default Domain Controller Policy and click Edit.
  5. In the Group Policy Management Editor console, go to Computer Configuration > Policies > Windows Settings > Security Settings > Local Policies > User Rights Assignment.
  6. Double-click Add workstations to domain.
  7. In Properties, remove Authenticated Users from the list.
  8. To let administrators join the domain manually (optional), click Add user or group, and then add an administrative group to the list.
  9. Click OK.

You can now close the Group Policy Management Editor console and GPMC.

Initialize a directory structure

We recommend that you create an OU that is named Projects. This OU serves as a base OU container for all project-specific OUs.

To create a dedicated OU for each Google Cloud project that contains domain-joined computers, do the following:

  1. On a machine that has administrative access to your Active Directory domain, open the Active Directory Users and Computers snap-in in the Microsoft Management Console (MMC).
  2. Right-click the domain container that you want to add an OU to.
  3. Select New > Organizational Unit.
  4. In the New Object dialog, enter Projects.
  5. Click OK.

Create an Active Directory user account

The register-computer Cloud Function needs to run as a Google Cloud service account, but it also needs to use an Active Directory user account to access your Active Directory domain.

  1. Get the distinguished name of the Projects OU:
    1. In the Active Directory Users and Computers MMC snap-in, select View > Advanced Features.
    2. In the navigation pane, right-click the Projects OU.
    3. In the Properties dialog, click the Attribute Editor tab.
    4. Double-click the distinguishedName attribute, and then from the dialog, copy the value of the attribute.
  2. On a machine that has administrative access to your Active Directory domain, open PowerShell.
  3. Initialize the $BaseOrgUnitPath variable:

    $BaseOrgUnitPath="distinguished-name"
    

    Replace distinguished-name with the distinguished name of the Projects OU.

  4. Create an Active Directory user account named CompRegistrar, assign it a random password, and then place it in the Projects OU:

    $Password=[Guid]::NewGuid().ToString()+"-"+[Guid]::NewGuid().ToString()
    $UpnSuffix=(Get-ADDomain).DNSRoot
    $CompRegistrar = New-ADUser `
        -Name "Computer Registrar" `
        -GivenName "Computer" `
        -Surname "Registrar" `
        -Path $BaseOrgUnitPath `
        -SamAccountName "CompRegistrar" `
        -UserPrincipalName "CompRegistrar@$UpnSuffix" `
        -AccountPassword (ConvertTo-SecureString "$Password" -AsPlainText -Force) `
        -PasswordNeverExpires $True `
        -PassThru
    $CompRegistrar | Enable-ADAccount
    
  5. Grant the CompRegistrar account the minimum set of permissions needed to manage computer accounts in the Projects OU and sub-OUs:

    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":CCDC;Computer" /I:T | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":LC;;Computer" /I:S  | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":RC;;Computer" /I:S  | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":WD;;Computer" /I:S  | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":WP;;Computer" /I:S  | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":RP;;Computer" /I:S  | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":CA;Reset Password;Computer" /I:S  | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":CA;Change Password;Computer" /I:S | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":WS;Validated write to service principal name;Computer" /I:S | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":WS;Validated write to DNS host name;Computer" /I:S | Out-Null
    
  6. Grant the CompRegistrar account the minimum set of permissions needed to manage group objects in the Projects OU and sub-OUs:

    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":CCDC;Group" /I:T | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":LC;;Group" /I:S  | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":RC;;Group" /I:S  | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":WD;;Group" /I:S  | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":WP;;Group" /I:S  | Out-Null
    & dsacls.exe $BaseOrgUnitPath /G $CompRegistrar":RP;;Group" /I:S  | Out-Null
    
  7. Reveal the generated password of the CompRegistrar Active Directory user account and note the value:

    Write-Host $Password
    

Your Active Directory domain is now ready to support automatic domain joins that use the CompRegistrar Active Directory user account.

Preparing the Google Cloud project

After you prepare the Active Directory domain, you can configure your domain project:

  • If you use Managed Microsoft AD, your domain project is the project in which you deployed Managed Microsoft AD.
  • If you use self-managed Active Directory, your domain project is the project that runs your Active Directory domain controllers. In the case of a Shared VPC, this project must be the same as the VPC host project.

You use this domain project to do the following:

  • Deploy the register-computer Cloud Function.
  • Deploy Serverless VPC Access to let Cloud Functions access Active Directory.
  • Manage a Cloud KMS key to help protect the password of the CompRegistrar Active Directory user account.
  • Configure Cloud Scheduler so that it triggers cleanups of stale computer accounts.

We recommend that you grant access to the domain project on a least-privilege basis.

Deploy Serverless VPC Access

The register-computer Cloud Function needs to communicate with your Active Directory domain controllers. To let Cloud Functions access resources in a VPC, you need to configure Serverless VPC Access.

To configure Serverless VPC Access, do the following:

  1. In the Google Cloud Console, open Cloud Shell.

    Open Cloud Shell

  2. Initialize the following variables:

    export DOMAIN_PROJECT_ID=domain-project-id
    export VPC_NAME=vpc-name
    export SERVERLESS_REGION=serverless-region
    export SERVERLESS_IP_RANGE=serverless-ip-range
    

    Replace the following:

    • domain-project-id: The project ID of your domain project.
    • vpc-name: The name of the VPC network that contains your Active Directory domain controllers.
    • serverless-region: The region to deploy your Cloud Function in. Choose a region that supports both Cloud Functions and Serverless VPC Access. The region does not have to be the same region as the one you plan to deploy VM instances in.

    • serverless-ip-range: Used by Serverless VPC Access to enable communication between Cloud Functions and resources in your VPC. Set to an unreserved /28 CIDR IP range. It must not overlap with any existing subnets in your VPC network.

  3. Enable the Cloud Functions API and Serverless VPC Access API:

    gcloud services enable cloudfunctions.googleapis.com cloudbuild.googleapis.com vpcaccess.googleapis.com \
        --project=$DOMAIN_PROJECT_ID
    
  4. Deploy Serverless VPC Access:

    gcloud compute networks vpc-access connectors create serverless-connector \
        --network=$VPC_NAME \
        --region=$SERVERLESS_REGION \
        --range=$SERVERLESS_IP_RANGE \
        --project=$DOMAIN_PROJECT_ID
    
  5. Because the Project Viewer and Network User roles are required for Cloud Functions to utilize Serverless VPC Access, grant these roles to the service account:

    MANAGEMENT_PROJECT_NUMBER=$(gcloud projects describe \
        $DOMAIN_PROJECT_ID --format="value(projectNumber)")
    
    gcloud projects add-iam-policy-binding $DOMAIN_PROJECT_ID \
        --member=serviceAccount:service-$MANAGEMENT_PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com \
        --role=roles/viewer \
        --project=$DOMAIN_PROJECT_ID
    
    gcloud projects add-iam-policy-binding $DOMAIN_PROJECT_ID \
        --member=serviceAccount:service-$MANAGEMENT_PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com \
        --role=roles/compute.networkUser \
        --project=$DOMAIN_PROJECT_ID
    
    gcloud projects add-iam-policy-binding $DOMAIN_PROJECT_ID \
        --member=serviceAccount:service-$MANAGEMENT_PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com \
        --role=roles/storage.objectCreator \
        --project=$DOMAIN_PROJECT_ID
    

Cloud Functions deployed in the domain project can now access resources in your VPC by using an address from the designated IP range as the source IP address.

Grant access to Kerberos and LDAP

Although Serverless VPC Access enables Cloud Functions to access resources in your VPC, connectivity to the Kerberos and LDAP endpoints of your domain controllers is still subject to firewall rules.

You need to create a firewall rule that permits serverless resources to access your domain controllers by using the following protocols: LDAP (TCP/389), Kerberos (UDP/88, TCP/88), or Kerberos password change (UDP/464, TCP/464). You can apply the rule based on a network tag that you have assigned to your domain controllers, or you can apply it by using a service account.

  • To apply the firewall rule, run one of the following commands in Cloud Shell:

    By network tag

    gcloud compute firewall-rules create allow-adkrb-from-serverless-to-dc \
        --direction=INGRESS \
        --action=allow \
        --rules=udp:88,tcp:88,tcp:389,udp:464,tcp:464 \
        --source-ranges=$SERVERLESS_IP_RANGE \
        --target-tags=dc-tag \
        --network=$VPC_NAME \
        --project=vpc-project-id \
        --priority 10000
    

    Replace the following:

    • dc-tag: The network tag assigned to your domain controller VMs.
    • vpc-project-id: The ID of the project the VPC is defined in. If you use a Shared VPC, use the VPC host project; otherwise, use the ID of the domain project.

    By service account

    gcloud compute firewall-rules create allow-adkrb-from-serverless-to-dc \
        --direction=INGRESS \
        --action=allow \
        --rules=udp:88,tcp:88,tcp:389,udp:464,tcp:464 \
        --source-ranges=$SERVERLESS_IP_RANGE \
        --target-service-accounts=dc-sa \
        --network=$VPC_NAME \
        --project=vpc-project-id \
        --priority 10000
    

    Replace the following:

    • dc-sa: The email address of the service account that your domain controller VMs use.
    • vpc-project-id: The ID of the project the VPC is defined in. If you use a Shared VPC, use the VPC host project; otherwise, use the ID of the domain project.

Create a Cloud KMS key

You need to create a Cloud KMS key for encrypting the password of the CompRegistrar Active Directory user account so that the password is not stored in plaintext.

To create a Cloud KMS key, do the following:

  1. In Cloud Shell, enable Cloud KMS in the domain project:

    gcloud services enable cloudkms.googleapis.com --project=$DOMAIN_PROJECT_ID
    
  2. Create a global Cloud KMS key ring and key:

    gcloud kms keyrings create computer-registrar-keyring \
        --location global \
        --project=$DOMAIN_PROJECT_ID
    
    gcloud kms keys create computer-registrar-key \
        --location=global \
        --purpose=encryption \
        --keyring=computer-registrar-keyring \
        --project=$DOMAIN_PROJECT_ID
    
  3. Create a service account that the Cloud Function uses:

    gcloud iam service-accounts create computer-registrar \
        --display-name="Registrar for Active Directory" \
        --project=$DOMAIN_PROJECT_ID
    
    export REGISTRAR_EMAIL=computer-registrar@$DOMAIN_PROJECT_ID.iam.gserviceaccount.com
    
  4. Grant the service account permission to use the Cloud KMS key for decryption:

    gcloud kms keys add-iam-policy-binding computer-registrar-key \
        --location=global \
        --keyring=computer-registrar-keyring \
        --member=serviceAccount:$REGISTRAR_EMAIL \
        --role=roles/cloudkms.cryptoKeyEncrypterDecrypter \
        --project=$DOMAIN_PROJECT_ID
    

Deploy the Cloud Function

To deploy the register-computer Cloud Function, do the following:

  1. In Cloud Shell, clone the GitHub repository:

    git clone https://github.com/GoogleCloudPlatform/gce-automated-ad-join.git && cd gce-automated-ad-join/ad-joining
    
  2. Initialize the following variables:

    export AD_DOMAIN=dns-domain-name
    export AD_NETBIOS_DOMAIN=netbios-domain-name
    export PROJECTS_DN="projects-ou-distinguished-name"
    

    Replace the following:

    • dns-domain-name: The DNS domain name of your Active Directory domain.
    • netbios-domain-name: The NetBIOS name of your Active Directory domain.
    • projects-ou-distinguished-name: The distinguished name of your Projects OU.
  3. Enter the password of the CompRegistrar Active Directory user account:

    read -p "AD password:" AD_PASSWORD && export AD_PASSWORD
    
  4. Build and deploy the Cloud Function:

    make
    

    When you are prompted, type Y to confirm that Cloud Functions allows unauthenticated invocations because the function implements a custom authentication and authorization mechanism.

    The deployment can take a couple of minutes to complete.

  5. Invoke the Cloud Function to verify that it deployed:

    curl https://$SERVERLESS_REGION-$DOMAIN_PROJECT_ID.cloudfunctions.net/register-computer
    

    A PowerShell script displays. The VM runs this script during the specialize phase that joins it to the domain.

Testing domain joining

To verify your setup, you create a Compute Engine instance, and then test whether it can automatically join Active Directory.

If you deployed Active Directory into a Shared VPC, we recommend that you create a project for testing purposes and attach the project to the Shared VPC.

Enable a project for automatic domain joining

The register-computer Cloud Function does not allow VM instances to join an Active Directory domain unless the VM's project is enabled for automatic domain joining. This security measure helps prevent VMs that are connected to unauthorized projects from accessing your domain.

To enable a project for automatic domain joining, you must do the following:

  • Create an OU in Active Directory whose name matches your Google Cloud project ID.
  • Grant the register-computer Cloud Function access to the Google Cloud project.

First, create the OU:

  1. Using an RDP client, log in to a machine that has administrative access to your Active Directory domain.
  2. In the Active Directory Users and Computers MMC snap-in, go to the Projects OU.
  3. Right-click the OU and select New > Organizational Unit.
  4. In the New Object dialog, enter the ID for the Google Cloud project to deploy your VMs in.
  5. Click OK.

Next, grant the register-computer Cloud Function access to the Google Cloud project:

  1. In Cloud Shell, initialize the TEST_PROJECT_ID variable:

    TEST_PROJECT_ID=project-id
    

    Replace project-id with the ID of the Google Cloud project into which you deploy VMs.

  2. Grant the computer-registrar service account the Compute Viewer role on the project:

    gcloud projects add-iam-policy-binding $TEST_PROJECT_ID \
        --member "serviceAccount:$REGISTRAR_EMAIL" \
        --role "roles/compute.viewer"
    

Your project is now ready to support automatic domain joining.

Create and join a VM instance

Next, you need to create a VM instance to verify that it can automatically join your Active Directory domain.

  1. In Cloud Shell, initialize the following variables:

    REGISTER_URL=https://$SERVERLESS_REGION-$DOMAIN_PROJECT_ID.cloudfunctions.net/register-computer
    VPC_REGION=vpc-region-to-deploy-vm
    VPC_SUBNET=vpc-subnet-to-deploy-vm
    ZONE=zone-to-deploy-vm
    

    Replace the following:

    • vpc-region-to-deploy-vm: The region to deploy the VM instance in.
    • vpc-subnet-to-deploy-vm: The subnet to deploy the VM instance in.
    • zone-to-deploy-vm: The zone to deploy the VM instance in.
  2. If you're using a standard hostname, follow the instructions appropriate for your VPC and hostname configuration to create a VM instance.

    Shared VPC

    1. In Cloud Shell, look up the project ID of your VPC host project:

      VPCHOST_PROJECT_ID=$(gcloud compute shared-vpc get-host-project --project=$TEST_PROJECT_ID --format=value\(name\))
      
    2. Create an instance by passing the specialize scriptlet that causes the VM to join the domain:

      gcloud compute instances create join-01 \
          --image-family=windows-2016-core \
          --image-project=windows-cloud \
          --machine-type=n1-standard-2 \
          --no-address \
          --zone=$ZONE \
          --subnet=projects/$VPCHOST_PROJECT_ID/regions/$VPC_REGION/subnetworks/$VPC_SUBNET \
          --project=$TEST_PROJECT_ID \
          "--metadata=sysprep-specialize-script-ps1=iex((New-Object System.Net.WebClient).DownloadString('$REGISTER_URL'))"
      

    Standalone VPC

    • In Cloud Shell, create an instance by passing the specialize scriptlet that causes the VM to join the domain:

      gcloud compute instances create join-01 \
          --image-family=windows-2016-core \
          --image-project=windows-cloud \
          --machine-type=n1-standard-2 \
          --no-address \
          --subnet=$VPC_SUBNET \
          --zone=$ZONE \
          "--metadata=sysprep-specialize-script-ps1=iex((New-Object System.Net.WebClient).DownloadString('$REGISTER_URL'))" \
          --project=$TEST_PROJECT_ID
      
  3. If you want to use a custom hostname, follow the instructions appropriate for your VPC and hostname configuration to create an instance.

    Shared VPC

    1. In Cloud Shell, look up the project ID of your VPC host project:

      VPCHOST_PROJECT_ID=$(gcloud compute shared-vpc get-host-project --project=$TEST_PROJECT_ID \
          --format=value\(name\))
      
    2. Create an instance by passing the specialize scriptlet that causes the VM to join the domain:

      gcloud compute instances create join-01 \
          --hostname=custom-01.example.com \
          --image-family=windows-2016-core \
          --image-project=windows-cloud \
          --machine-type=n1-standard-2 \
          --no-address \
          --zone=$ZONE \
          --subnet=projects/$VPCHOST_PROJECT_ID/regions/$VPC_REGION/subnetworks/$VPC_SUBNET \
          --project=$TEST_PROJECT_ID \
          "--metadata=sysprep-specialize-script-ps1=iex((New-Object System.Net.WebClient).DownloadString('$REGISTER_URL'))"
      

    Standalone VPC

    • In Cloud Shell, create an instance by passing the specialize scriptlet that causes the VM to join the domain:

      gcloud compute instances create join-01 \
          --hostname=custom-01.example.com \
          --image-family=windows-2016-core \
          --image-project=windows-cloud \
          --machine-type=n1-standard-2 \
          --no-address \
          --subnet=$VPC_SUBNET \
          --zone=$ZONE \
          --project=$TEST_PROJECT_ID \
          "--metadata=sysprep-specialize-script-ps1=iex((New-Object System.Net.WebClient).DownloadString('$REGISTER_URL'))"
      
  4. Monitor the boot process:

    gcloud compute instances tail-serial-port-output join-01 \
        --zone=$ZONE \
        --project=$TEST_PROJECT_ID
    

    After about one minute, the machine is joined to your Active Directory domain. The output is similar to the following:

    Domain           : corp.example.com
    DomainController : dc-01.corp.example.com.
    OrgUnitPath      : OU=test-project-123,OU=Projects,DC=corp,DC=example,DC=com
    
    WARNING: The changes will take effect after you restart the computer
    
    Computer successfully joined to domain
    

    To stop observing the boot process, press CTRL+C.

When you create additional VM instances, make sure to keep the instance name below this character limit; otherwise, joining a VM instance to your domain is likely to fail.

Verify that a VM is joined to Active Directory

  1. Using an RDP client, log in to a machine that has administrative access to your Active Directory domain.
  2. Open the Active Directory Users and Computers MMC snap-in.
  3. In the menu, ensure that View > Advanced Features is enabled.
  4. Go to the OU named after the Cloud project ID that you created a VM instance in.
  5. Double-click the join-01 account.
  6. In the Properties dialog, click the Attribute Editor tab.

    The computer account is annotated with additional LDAP attributes. These attributes let you track the association between the computer object and the Compute Engine instance.

    Verify that the list contains the following LDAP attributes and values.

    LDAP attribute Value
    msDS-cloudExtensionAttribute1 Google Cloud project ID
    msDS-cloudExtensionAttribute2 Compute Engine zone
    msDS-cloudExtensionAttribute3 Compute Engine instance name

    The msDS-cloudExtensionAttribute attributes are general-purpose attributes and are not used by Active Directory itself.

Delete the instance

After you verify that the VM instance is joined to the Active Directory domain, you delete the instance.

  • Delete the instance:

    gcloud compute instances delete join-01 \
        --zone=$ZONE \
        --project=$TEST_PROJECT_ID \
        --quiet
    

Create and join a managed instance group

You can also verify that instances from a MIG can automatically join your domain.

Shared VPC

  1. In Cloud Shell, look up the project ID of your VPC host project:

    VPCHOST_PROJECT_ID=$(gcloud compute shared-vpc get-host-project --project=$TEST_PROJECT_ID --format=value\(name\))
    
  2. Create an instance template by passing the specialize script that causes the VM to join the domain:

    gcloud compute instance-templates create ad-2016core-n1-std-2 \
        --image-family=windows-2016-core \
        --image-project=windows-cloud \
        --no-address \
        --machine-type=n1-standard-2 \
        "--metadata=sysprep-specialize-script-ps1=iex((New-Object System.Net.WebClient).DownloadString('$REGISTER_URL'))" \
        --subnet=projects/$VPCHOST_PROJECT_ID/regions/$VPC_REGION/subnetworks/$VPC_SUBNET \
        --project=$TEST_PROJECT_ID
    
  3. Create a managed instance group that uses the instance template:

    gcloud compute instance-groups managed create group-01 \
        --template=ad-2016core-n1-std-2 \
        --size=3 \
        --zones=$ZONE \
        --project=$TEST_PROJECT_ID
    

Standalone VPC

  1. Create an instance template by passing the specialize script that causes the VM to join the domain:

    gcloud compute instance-templates create ad-2016core-n1-std-2 \
        --image-family=windows-2016-core \
        --image-project=windows-cloud \
        --no-address \
        --machine-type=n1-standard-2 \
        "--metadata=sysprep-specialize-script-ps1=iex((New-Object System.Net.WebClient).DownloadString('$REGISTER_URL'))" \
        --subnet=$VPC_SUBNET \
        --project=$TEST_PROJECT_ID \
        --region=$VPC_REGION
    
  2. Create a managed instance group that uses the instance template:

    gcloud compute instance-groups managed create group-01 \
        --template=ad-2016core-n1-std-2 \
        --size=3 \
        --zones=$ZONE \
        --project=$TEST_PROJECT_ID
    

Wait a few minutes, and then use the Active Directory Users and Computers MMC snap-in to verify that Active Directory has three additional computer accounts, each with the prefix group-01.

After you verify that the VM instances from your MIGs can join your Active Directory domain, you can delete the managed group and instance template by following these steps:

  1. In Cloud Shell, delete the instance group:

    gcloud compute instance-groups managed delete group-01 \
        --region=$VPC_REGION \
        --project=$TEST_PROJECT_ID \
        --quiet
    
  2. Delete the instance template:

    gcloud compute instance-templates delete ad-2016core-n1-std-2 \
        --project=$TEST_PROJECT_ID \
        --quiet
    

Diagnose errors

If your VM instance failed to join the domain, check the log of the register-computer Cloud Function:

  1. In the Cloud Console, go to Cloud Functions.

    Go to Cloud Functions

  2. Click the register-computer Cloud Function.

  3. In the menu, click View logs.

Scheduling cleanup of stale computer accounts

Automating the process of joining computers to the domain reduces the effort to set up new servers and enables you to use domain-joined servers in managed instance groups. Over time, however, stale computer accounts can accumulate in the domain.

To prevent this accumulation, we recommend that you set up the register-computer Cloud Function to periodically scan your Active Directory domain to find and automatically remove stale accounts.

The register-computer Cloud Function can use the msDS-cloudExtensionAttribute attributes of computer accounts to identify which computer accounts are stale. These attributes contain the project, zone, and instance name of the corresponding VM instance in Compute Engine. For each computer account, the function can check if the corresponding VM instance is still available. If it is not, then the computer account is considered stale and removed.

To trigger a computer account cleanup, you invoke the /cleanup endpoint of the Cloud Function. To prevent unauthorized users from triggering a cleanup, this request must be authenticated by using the computer-registrar service account.

Configure Cloud Scheduler

The following steps show you how to set up Cloud Scheduler in conjunction with Pub/Sub to automatically trigger a cleanup once every 24 hours:

  1. In Cloud Shell, enable the Cloud Scheduler API in your domain project:

    gcloud services enable cloudscheduler.googleapis.com --project=$DOMAIN_PROJECT_ID
    
  2. Set APPENGINE_LOCATION to a valid App Engine location in which to deploy Cloud Scheduler:

    APPENGINE_LOCATION=location
    

    Replace location with the App Engine region that you selected for your VPC resources, for example, us-central. If that region is not available as an App Engine location, choose a location that is geographically close to you. For more information, see Regions and zones.

  3. Initialize App Engine:

    gcloud app create --region=$APPENGINE_LOCATION --project=$DOMAIN_PROJECT_ID
    
  4. Create a Cloud Scheduler job:

    gcloud scheduler jobs create http cleanup-computer-accounts \
        --schedule="every 24 hours" \
        --uri=$REGISTER_URL/cleanup \
        --oidc-service-account-email=$REGISTRAR_EMAIL \
        --oidc-token-audience=https://$SERVERLESS_REGION-$DOMAIN_PROJECT_ID.cloudfunctions.net/ \
        --project=$DOMAIN_PROJECT_ID
    

    This job calls the register-computer Cloud Function once every 24 hours and uses the computer-registrar service account for authentication.

Trigger a cleanup

To verify your configuration for cleaning up stale computer accounts, you can trigger the Cloud Scheduler job manually.

  1. In the Cloud Console, go to Cloud Scheduler.

    Go to Cloud Scheduler

  2. For the cleanup-computer-accounts job that you created, click Run Now.

    After a few seconds, the Result column displays Success, indicating that the cleanup completed successfully. If the result column does not update automatically within a few seconds, click the Refresh button.

For more details about which accounts were cleaned up, check the logs of the register-computer Cloud Function.

  1. In the Cloud Console, go to Cloud Functions.

    Go to Cloud Functions

  2. Click the register-computer Cloud Function.

  3. In the menu, click View logs.

    Log entries indicate that the computer accounts of the VM instances you used to test domain joining were identified as stale and removed.

Cleaning up

If you do not want to keep the Google Cloud setup used for this tutorial, you can revert this setup by doing the following:

  1. In Cloud Shell, delete the Cloud Scheduler job:

    gcloud scheduler jobs delete cleanup-computer-accounts \
        --project=$DOMAIN_PROJECT_ID \
        --quiet
    
  2. Delete the Cloud Function:

    gcloud functions delete register-computer \
        --project=$DOMAIN_PROJECT_ID \
        --region=$SERVERLESS_REGION \
        --quiet
    
  3. Destroy the Cloud KMS key:

    gcloud kms keys versions destroy 1 \
        --key=computer-registrar-key \
        --keyring=computer-registrar-keyring \
        --project=$DOMAIN_PROJECT_ID \
        --location=global
    
  4. Delete the firewall rule for LDAP and Kerberos access:

    gcloud compute firewall-rules delete allow-adkrb-from-serverless-to-dc \
        --project=vpc-project-id
    

    Replace vpc-project-id with the ID of the project the VPC is defined in. If you use a Shared VPC, use the VPC host project; otherwise, use the ID of the domain project.

  5. Delete Serverless VPC Access:

    gcloud beta compute networks vpc-access connectors delete serverless-connector \
        --region=$SERVERLESS_REGION \
        --quiet
    

Revert Active Directory changes

  1. Using an RDP client, log in to a machine that has administrative access to your Active Directory domain.
  2. In the Active Directory Users and Computers MMC snap-in, go to the Projects OU.
  3. Delete the CompRegistrar Active Directory user account.
  4. Delete the OU that you created for testing automated domain joining.

What's next