Active Directory Group Membership

I’ve written some simple terraform code to assist with creating group membership for active directory groups. This is an on-prem deployment of Active Directory and we are using the hashicorp/ad provider.

Expected Behavior
When apply is run, the code will be checked against the state file and add, change or destroy objects determined, and actioned, accordingly. In addition, if the code matches the state file, it will report as such with no prompt to confirm an action.

Actual Behavior
Each time the apply is run, group membership is changed. When I add a member to the group, all existing members are removed and the new member added. If I make no changes to membership, all members are removed and, if I apply again without change, all members are added.

Has anyone else experienced this behavior?
Can anyone explain this behavior, is something in my code causing this?

terraform {
  required_version = ">= 0.14.0"
  required_providers {
    ad = {
      source = "hashicorp/ad"
      version = "0.4.4"
    }
  }
}

provider "ad" {
  winrm_hostname = "hostname"
  winrm_username = "username"
  winrm_password = "password"
}

variable group_memberships {
  type = map(object({
    user_list     = list(string)
    group_list    = list(string)
    computer_list = list(string)
  }))
}

group_memberships = {
  AppAccessGroup = {
    user_list     = [
                    "roger.ramjet",
                    "wylie.coyote"]
    group_list    = [
                    "ReadOnlyUsers",
                    "TemporaryUsers"]
    computer_list = [
                    "computer01$",
                    "computer02$"]
  }
}

resource ad_group_membership "gm" {
  for_each = var.group_memberships 
    group_id = each.key
    group_members  = concat(each.value.user_list, each.value.group_list, each.value.computer_list)
}

I did some more investigation and found the answer.

The ad_group_membership resource is very flexible when specifying a list of member AD Principals. From the provider documentation:
“Each principal can be identified by its GUID, SID, Distinguished Name, or SAM Account Name. Only one is required”

This makes it easy to create the membership resource, as you can see in the code I posted.

The problem is, the provider records the list of principals in the state file using each principal’s ID (GUID):

    {
      "mode": "managed",
      "type": "ad_group_membership",
      "name": "gm",
      "provider": "provider[\"registry.terraform.io/hashicorp/ad\"]",
      "instances": [
        {
          "index_key": "AppAccessGroup",
          "schema_version": 0,
          "attributes": {
            "group_id": "AppAccessGroup",
            "group_members": [
              "071792bd-1e5b-4ff5-a57a-969bee712b08",
              "22ee22ba-c562-4645-b03a-1ce451c925f1",
              "2cd6fbcc-5ce2-4e77-95c4-d4a56eb2c974",
              "db274f67-0360-4d6f-9daf-027065dea943",
              "f6655ffc-97d8-4243-851f-7b709d671ec3"
            ],
            "id": "AppAccessGroup_9efccf37-c6cf-f1fd-9f99-9797b23d8cd9"
          },
          "sensitive_attributes": [],
          "private": "bnVsbA==",
          "dependencies": [
            "data.ad_computer.c",
            "data.ad_group.g",
            "data.ad_user.u"
          ]
        }
      ]
    }

So every time the code is applied, the resource is destroyed because it does not have any matching members.

To avoid this problem, the principals should be specified with their GUID, rather than Distinguished Name or SAM Account Name. So, the code gets more complex to keep descriptive names (for readability) and fetch the respective IDs for the resource block.