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 process of provisioning 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.

The configuration that you create in this tutorial can be the basis of additional work that you do with Windows Servers in Google Cloud. For example, when you've finished this tutorial, you can deploy ASP.NET applications with Windows Authentication in Windows containers.

If you're using Managed Microsoft AD and don't require automatic cleanup of stale computer accounts, consider joining the Windows VMs using the automated domain join feature. For more information, see Join a Windows VM automatically to a domain.

Objectives

  • Deploy a Cloud Run app that enables VM instances from selected projects to automatically join your Active Directory domain.
  • Create a Cloud Scheduler job that periodically scans your Active Directory domain for stale computer accounts and removes them.
  • Test the setup by creating an autoscaled managed instance group (MIGs) of domain-joined VM instances.

Costs

In this document, you use the following billable components of Google Cloud:

The instructions in this document are 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 you've already deployed Active Directory 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, groups, 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:

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, you use the following sysprep specialize scriptlet when you create a VM instance:

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

This 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 runs the sysprep specialize scriptlet. The specialize scriptlet invokes the register-computer Cloud Run app and downloads 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 securely identifies the VM instance.
  4. The script calls the register-computer app again, passing the ID token to authenticate itself.
  5. The app validates the ID token and extracts the name, zone, and Google Cloud project ID of the VM instance.
  6. The app 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 app 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 app verifies that the Google Cloud project is configured to allow VM instances to join Active Directory. To complete this verification, the app checks whether it can access the VM instance by using the Compute Engine API.
  8. If all checks pass successfully, the app prestages a computer account in Active Directory. The app 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 app 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 prestaged 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 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

You now create an OU that serves as a container for all project-specific OUs:

  1. Using an RDP client, log in to a machine that has administrative access to your Active Directory domain.
  2. Open an elevated PowerShell session.
  3. Create a new organizational unit:

    $ParentOrgUnitPath = (Get-ADDomain).ComputersContainer
    $ProjectsOrgUnitPath = New-ADOrganizationalUnit `
      -Name 'Projects' `
      -Path $ParentOrgUnitPath `
      -PassThru
    

Create an Active Directory user account

To access Active Directory and prestage computer accounts, the register-computer app needs an Active Directory user account:

  1. Create an Active Directory user account named register-computer, assign it a random password, and then place it in the Projects OU:

    # Generate a random password
    $Password = [Guid]::NewGuid().ToString()+"-"+[Guid]::NewGuid().ToString()
    
    # Create user
    $UpnSuffix = (Get-ADDomain).DNSRoot
    $RegisterComputerUser = New-ADUser `
        -Name "register-computer Cloud Run app" `
        -GivenName "Register" `
        -Surname "Computer" `
        -Path $ProjectsOrgUnitPath `
        -SamAccountName "register-computer" `
        -UserPrincipalName "register-computer@$UpnSuffix" `
        -AccountPassword (ConvertTo-SecureString "$Password" -AsPlainText -Force) `
        -PasswordNeverExpires $True `
        -Enabled $True `
        -PassThru
    
  2. Grant the register-computer account the minimum set of permissions needed to manage computer accounts and groups in the Projects OU and sub-OUs:

    $AcesForContainerAndDescendents = @(
        "CCDC;Computer",               # Create/delete computers
        "CCDC;Group"                   # Create/delete users
    )
    
    $AcesForDescendents = @(
        "LC;;Computer" ,               # List child objects
        "RC;;Computer" ,               # Read security information
        "WD;;Computer" ,               # Change security information
        "WP;;Computer" ,               # Write properties
        "RP;;Computer" ,               # Read properties
        "CA;Reset Password;Computer",  # ...
        "CA;Change Password;Computer", # ...
        "WS;Validated write to service principal name;Computer",
        "WS;Validated write to DNS host name;Computer",
    
        "LC;;Group",                   # List child objects
        "RC;;Group",                   # Read security information
        "WD;;Group",                   # Change security information
        "WP;;Group",                   # Write properties
        "RP;;Group"                    # Read properties
    )
    
    $AcesForContainerAndDescendents | % { dsacls.exe $ProjectsOrgUnitPath /G "${RegisterComputerUser}:${_}" /I:T | Out-Null }
    $AcesForDescendents | % { dsacls.exe $ProjectsOrgUnitPath /G "${RegisterComputerUser}:${_}" /I:S | Out-Null }
    

    The command might take a few minutes to complete.

  3. Reveal the Projects OU path and the generated password of the register-computer Active Directory user account. Note the values because you will need them later.

    Write-Host "Password: $Password"
    Write-Host "Projects OU: $ProjectsOrgUnitPath"
    

Preparing the Google Cloud project

You now 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:

  • Create a Secret Manager secret that contains the password of the register-computer Active Directory user account.
  • Deploy Serverless VPC Access to let Cloud Run access Active Directory.
  • Deploy the register-computer app.
  • 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.

Create a Secret Manager secret

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

    Open Cloud Shell

  2. Launch PowerShell:

    pwsh
    
  3. Initialize the following variable, replacing domain-project-id with the ID of your domain project:

    $DomainProjectId = "domain-project-id"
    
  4. Set the domain project as the default project:

    & gcloud config set project $DomainProjectId
    
  5. Enable the Secret Manager API:

    & gcloud services enable secretmanager.googleapis.com
    
  6. Enter the password of the register-computer Active Directory user account and store it in a Secret Manager secret:

    $RegisterComputerCredential = (Get-Credential -Credential 'register-computer')
    
    $TempFile = New-TemporaryFile
    Set-Content $TempFile $($RegisterComputerCredential.GetNetworkCredential().Password) -NoNewLine
    
    & gcloud secrets create ad-password --data-file $TempFile
    
    Remove-Item $TempFile
    

Deploy Serverless VPC Access

The register-computer app needs to communicate with your Active Directory domain controllers. To let Cloud Run access resources in a VPC, you now configure Serverless VPC Access:

  1. Initialize the following variables:

    $VpcName = "vpc-name"
    $ServerlessRegion = "serverless-region"
    $ServerlessIpRange = "serverless-ip-range"
    

    Replace the following:

    • vpc-name: The name of the VPC network that contains your Active Directory domain controllers.
    • serverless-region: The region to deploy your Cloud Run in. Choose a region that supports both Cloud Run 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 Run 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.

  2. Enable the Serverless VPC Access APIs:

    & gcloud services enable vpcaccess.googleapis.com
    
  3. Deploy Serverless VPC Access:

    & gcloud compute networks vpc-access connectors create serverless-connector `
        --network $VpcName `
        --region $ServerlessRegion `
        --range $ServerlessIpRange
    

Grant access to Kerberos and LDAP

Although Serverless VPC Access enables Cloud Run 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), LDAPS (TCP/636), 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,tcp:636,udp:464,tcp:464 `
        --source-ranges $ServerlessIpRange `
        --target-tags dc-tag `
        --network $VpcName `
        --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,tcp:636,udp:464,tcp:464 `
        --source-ranges $ServerlessIpRange `
        --target-service-accounts dc-sa `
        --network $VpcName `
        --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.

Deploy the Cloud Run app

You now set up Cloud Build to deploy the register-computer app to Cloud Run:

  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:

    $AdDomain = "dns-domain-name"
    $AdNetbiosDomain = "netbios-domain-name"
    $ProjectsOrgUnitPath = "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. Enable the Cloud Run and Cloud Build APIs:

    & gcloud services enable run.googleapis.com cloudbuild.googleapis.com
    
  4. Create a service account register-computer-app for the Cloud Run app:

    & gcloud iam service-accounts create register-computer-app `
      --display-name="register computer Cloud Run app"
    
  5. Allow the Cloud Run service account to read the secret that contains the Active Directory password:

    & gcloud secrets add-iam-policy-binding ad-password `
      --member "serviceAccount:register-computer-app@$DomainProjectId.iam.gserviceaccount.com" `
      --role "roles/secretmanager.secretAccessor"
    
  6. Grant Cloud Build the necessary permissions to deploy to Cloud Run:

    $DomainProjectNumber = (gcloud projects describe $DomainProjectId --format='value(projectNumber)')
    & gcloud iam service-accounts add-iam-policy-binding register-computer-app@$DomainProjectId.iam.gserviceaccount.com `
      --member "serviceAccount:$DomainProjectNumber@cloudbuild.gserviceaccount.com" `
      --role "roles/iam.serviceAccountUser"
    
    & gcloud projects add-iam-policy-binding $DomainProjectId `
      --member "serviceAccount:$DomainProjectNumber@cloudbuild.gserviceaccount.com" `
      --role roles/run.admin
    
  7. Use the file cloudbuild.yaml as a template to create a custom Cloud Run build config that matches your environment: