diff --git a/.gitignore b/.gitignore
index bd0d916d0f70067196dc18173c227bd9ebd2696b..426a8c1a79d406b8d12c270caa7bbf4f354ffa3c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 example/
 .idea/
 build/terraform*
+**/*.swp
 
 # Binaries for programs and plugins
 *.exe
diff --git a/docs/r/vted_group.md b/docs/r/vted_group.md
index ec9fc88e340e742f1187814554fcc7457a28eaf8..15800a12f6d1c6d39ab3e81e9cb4afd717ebf6c2 100644
--- a/docs/r/vted_group.md
+++ b/docs/r/vted_group.md
@@ -12,9 +12,15 @@ resource "vted_group" "my_group" {
   suppress_display = true
   suppress_members = true
   
-  admins   = ["mikesir"]
+  admins {
+    people   = ["mikesir"]
+    services = ["gateway"]
+  }
   contacts = ["mikesir", "ceharris"]
-  managers = ["mikesir", "ceharris"]
+  managers {
+    people   = ["mikesir", "ceharris"]
+    services = ["gateway"]
+  }
   
   members {
     people = ["mikesir", "ceharris"]
@@ -29,9 +35,9 @@ The following arguments are supported:
 
 - `name` - (Required) The name of the group.
 - `expiration_date` - (Optional) The expiration date of the group. Supported formats are RFC3339 and `YYYY-MM-DD`. Note that when using `YYYY-MM-DD`, the time is set to 00:00 UTC, which might cause date display differences.
-- `admins` - (Required) A collection of PIDs that are allowed to administer the group.
+- `admins` - (Required) A map of the `people` (PIDs) and `services` (uusids) that are allowed to administer the group. **Note**: The TF provider service account is always a service admin for the group.
 - `contacts` - (Required) A collection of PIDs that are contacts for the group.
-- `managers` - (Optional) A collection of PIDs that are allowed to manage the group membership.
+- `managers` - (Optional) A map of the `people` (PIDs) and `services` (uusids) that are allowed to manage the group membership.
 - `members` - (Optional) A map of the `people` (PIDs) and `groups` (uusids) that are members of this group
 - `suppress_display` - (Optional) Boolean, defaults to true. Is this group visible in public listings?
 - `suppress_members` - (Optional) Boolean, defaults to true. Is this group's membership visible in public queries?
diff --git a/vted/data_source_vted_group.go b/vted/data_source_vted_group.go
index 758163aca818c9a011060c4fc6cc62ed88262154..a28105ac92f2164ea612e193e30d1c86c8187e8a 100644
--- a/vted/data_source_vted_group.go
+++ b/vted/data_source_vted_group.go
@@ -52,8 +52,8 @@ func dataSourceVtEdGroup() *schema.Resource {
 			},
 			"managers": {
 				Type:        schema.TypeList,
-				Description: "The contacts of the group",
-				MinItems:    1,
+				Description: "The managers of the group",
+				MinItems:    0,
 				Computed:    true,
 				Elem:        PersonResource(),
 			},
diff --git a/vted/resource_vted_group.go b/vted/resource_vted_group.go
index ca831daf52d1fd5b696e6b566c13c59f1ff81b24..75430738340ec5d9102465000908829cba2c9f47 100644
--- a/vted/resource_vted_group.go
+++ b/vted/resource_vted_group.go
@@ -58,12 +58,28 @@ func resourceVtEdGroup() *schema.Resource {
 				Elem:        &schema.Schema{Type: schema.TypeString},
 			},
 			"admins": &schema.Schema{
-				Type:        schema.TypeSet,
-				Description: "PIDs of administrators for the group",
+				Type:        schema.TypeList,
+				Description: "Administrators for the group",
 				Required:    true,
 				MinItems:    1,
-				Set:         schema.HashString,
-				Elem:        &schema.Schema{Type: schema.TypeString},
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"people": {
+							Type:        schema.TypeSet,
+							Description: "PIDs of the person administrators of the group",
+							Optional:    true,
+							Set:         schema.HashString,
+							Elem:        &schema.Schema{Type: schema.TypeString},
+						},
+						"services": {
+							Type:        schema.TypeSet,
+							Description: "UUSIDs of the service administrators of the group",
+							Optional:    true,
+							Set:         schema.HashString,
+							Elem:        &schema.Schema{Type: schema.TypeString},
+						},
+					},
+				},
 			},
 			"contacts": &schema.Schema{
 				Type:        schema.TypeSet,
@@ -74,11 +90,27 @@ func resourceVtEdGroup() *schema.Resource {
 				Elem:        &schema.Schema{Type: schema.TypeString},
 			},
 			"managers": &schema.Schema{
-				Type:        schema.TypeSet,
-				Description: "PIDs of managers for the group",
+				Type:        schema.TypeList,
+				Description: "Managers for the group",
 				Optional:    true,
-				Set:         schema.HashString,
-				Elem:        &schema.Schema{Type: schema.TypeString},
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"people": {
+							Type:        schema.TypeSet,
+							Description: "PIDs of the person managers of the group",
+							Optional:    true,
+							Set:         schema.HashString,
+							Elem:        &schema.Schema{Type: schema.TypeString},
+						},
+						"services": {
+							Type:        schema.TypeSet,
+							Description: "UUSIDs of the service managers of the group",
+							Optional:    true,
+							Set:         schema.HashString,
+							Elem:        &schema.Schema{Type: schema.TypeString},
+						},
+					},
+				},
 			},
 			"members": &schema.Schema{
 				Type:        schema.TypeList,
@@ -113,10 +145,12 @@ func resourceVtEdGroupCreate(d *schema.ResourceData, meta interface{}) error {
 
 	groupName := d.Get("name").(string)
 
-	err := validateAdminsAndContacts(d.Get("admins").(*schema.Set), d.Get("contacts").(*schema.Set))
+	err := validateAdminsAndContacts(d.Get("admins").([]interface{}), d.Get("contacts").(*schema.Set))
 	if err != nil {
 		return err
 	}
+	// provider service ID always gets added as a service admin for the group
+	d.Get("admins").([]interface{})[0].(map[string]interface{})["services"].(*schema.Set).Add(providerConfig.serviceId)
 
 	_, err = edClient.Post("/groups", groupConfigToFormBody(d))
 	if err != nil {
@@ -130,12 +164,12 @@ func resourceVtEdGroupCreate(d *schema.ResourceData, meta interface{}) error {
 		return err
 	}
 
-	err = syncReplicationTargets(groupName, d.Get("replication_targets").(*schema.Set), nil, edClient)
+	err = syncManagersAdmins(groupName, "managers", d.Get("managers").([]interface{}), nil, edClient)
 	if err != nil {
 		return err
 	}
 
-	err = syncRole(groupName, "managers", d.Get("managers").(*schema.Set), nil, edClient, "person")
+	err = syncManagersAdmins(groupName, "administrators", d.Get("admins").([]interface{}), nil, edClient)
 	if err != nil {
 		return err
 	}
@@ -145,9 +179,51 @@ func resourceVtEdGroupCreate(d *schema.ResourceData, meta interface{}) error {
 		return err
 	}
 
+	err = syncReplicationTargets(groupName, d.Get("replication_targets").(*schema.Set), nil, edClient)
+	if err != nil {
+		return err
+	}
+
 	return resourceVtEdGroupRead(d, meta)
 }
 
+func syncManagersAdmins(groupName string, roleName string, newCollection []interface{}, oldCollection []interface{}, client *EdClient) error {
+	var newPeopleSet *schema.Set
+	var oldPeopleSet *schema.Set
+	var newServicesSet *schema.Set
+	var oldServicesSet *schema.Set
+
+	if len(newCollection) > 0 && newCollection[0] != nil {
+		newMembers := newCollection[0].(map[string]interface{})
+		newPeopleSet = newMembers["people"].(*schema.Set)
+		newServicesSet = newMembers["services"].(*schema.Set)
+	} else {
+		newPeopleSet = schema.NewSet(schema.HashString, []interface{}{})
+		newServicesSet = schema.NewSet(schema.HashString, []interface{}{})
+	}
+
+	if len(oldCollection) > 0 {
+		oldMembers := oldCollection[0].(map[string]interface{})
+		oldPeopleSet = oldMembers["people"].(*schema.Set)
+		oldServicesSet = oldMembers["services"].(*schema.Set)
+	} else {
+		oldPeopleSet = schema.NewSet(schema.HashString, []interface{}{})
+		oldServicesSet = schema.NewSet(schema.HashString, []interface{}{})
+	}
+
+	err := syncRole(groupName, roleName, newPeopleSet, oldPeopleSet, client, "person")
+	if err != nil {
+		return err
+	}
+
+	err = syncRole(groupName, roleName, newServicesSet, oldServicesSet, client, "service")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func syncMembers(groupName string, newMemberCollection []interface{}, oldMemberCollection []interface{}, client *EdClient) error {
 	var newPeopleSet *schema.Set
 	var oldPeopleSet *schema.Set
@@ -198,10 +274,10 @@ func resourceVtEdGroupRead(d *schema.ResourceData, meta interface{}) error {
 	d.Set("creation_date", groupInfo.Get("creationDate").String())
 	d.Set("suppress_members", groupInfo.Get("suppressMembers").Bool())
 	d.Set("suppress_display", groupInfo.Get("suppressDisplay").Bool())
+	d.Set("admins", convertManagersAdmins(groupInfo.Get("administrators").Array()))
 	d.Set("replication_targets", groupInfo.Get("targets").Array())
-	d.Set("admins", convertFlattenedPeople(groupInfo.Get("administrators").Array()))
 	d.Set("contacts", convertFlattenedPeople(groupInfo.Get("contacts").Array()))
-	d.Set("managers", convertFlattenedPeople(groupInfo.Get("managers").Array()))
+	d.Set("managers", convertManagersAdmins(groupInfo.Get("managers").Array()))
 	d.Set("viewers", convertViewers(groupInfo.Get("viewers").Array()))
 	d.Set("members", convertMembers(groupInfo.Get("members").Array()))
 
@@ -227,9 +303,12 @@ func resourceVtEdGroupUpdate(d *schema.ResourceData, meta interface{}) error {
 		}
 	}
 
+	// provider service ID always gets added as a service admin for the group
+	d.Get("admins").([]interface{})[0].(map[string]interface{})["services"].(*schema.Set).Add(providerConfig.serviceId)
+
 	if d.HasChange("admins") {
 		oldValue, newValue := d.GetChange("admins")
-		err := syncRole(groupName, "administrators", newValue.(*schema.Set), oldValue.(*schema.Set), edClient, "person")
+		err := syncManagersAdmins(groupName, "administrators", newValue.([]interface{}), oldValue.([]interface{}), edClient)
 		if err != nil {
 			return err
 		}
@@ -245,7 +324,7 @@ func resourceVtEdGroupUpdate(d *schema.ResourceData, meta interface{}) error {
 
 	if d.HasChange("managers") {
 		oldValue, newValue := d.GetChange("managers")
-		err := syncRole(groupName, "managers", newValue.(*schema.Set), oldValue.(*schema.Set), edClient, "person")
+		err := syncManagersAdmins(groupName, "managers", newValue.([]interface{}), oldValue.([]interface{}), edClient)
 		if err != nil {
 			return err
 		}
@@ -301,18 +380,29 @@ func suppressTimeChange() func(k string, old string, new string, d *schema.Resou
 	}
 }
 
-func validateAdminsAndContacts(admins *schema.Set, contacts *schema.Set) error {
+func validateAdminsAndContacts(admins []interface{}, contacts *schema.Set) error {
+	var adminPeopleSet *schema.Set
+	var adminServicesSet *schema.Set
+
 	adminsAndContacts := make(map[string]bool)
 
-	for _, n := range admins.List() {
-		adminsAndContacts[n.(string)] = true
+	if len(admins) > 0 && admins[0] != nil {
+		adminMap := admins[0].(map[string]interface{})
+		adminPeopleSet = adminMap["people"].(*schema.Set)
+		adminServicesSet = adminMap["services"].(*schema.Set)
+		for _, n := range adminPeopleSet.List() {
+			adminsAndContacts[n.(string)] = true
+		}
+		for _, n := range adminServicesSet.List() {
+			adminsAndContacts[n.(string)] = true
+		}
 	}
 
 	for _, n := range contacts.List() {
 		adminsAndContacts[n.(string)] = true
 	}
 
-	if len(adminsAndContacts) == 1 {
+	if len(adminsAndContacts) <= 1 {
 		return errors.New("two distinct users are required for admins and contacts")
 	}
 
@@ -324,7 +414,9 @@ func groupConfigToFormBody(d *schema.ResourceData) string {
 	formDataObj := url.Values{}
 	formDataObj.Set("uugid", d.Get("name").(string))
 
-	for _, name := range d.Get("admins").(*schema.Set).List() {
+	// Creation only supports people admins
+	adminMap := d.Get("admins").([]interface{})[0].(map[string]interface{})
+	for _, name := range adminMap["people"].(*schema.Set).List() {
 		formDataObj.Add("administrator", name.(string))
 	}
 
@@ -469,6 +561,29 @@ func parseDateString(date string) (time.Time, error) {
 	return parsedDate, nil
 }
 
+func convertManagersAdmins(members []gjson.Result) []map[string]*schema.Set {
+	people := schema.NewSet(schema.HashString, []interface{}{})
+	services := schema.NewSet(schema.HashString, []interface{}{})
+
+	for _, member := range members {
+		memberKind := member.Get("kind").String()
+		if memberKind == "person" {
+			people.Add(member.Get("pid").String())
+		} else if memberKind == "service" {
+			services.Add(member.Get("uusid").String())
+		}
+	}
+
+	values := map[string]*schema.Set{
+		"people":   people,
+		"services": services,
+	}
+
+	return []map[string]*schema.Set{
+		values,
+	}
+}
+
 func convertMembers(members []gjson.Result) []map[string]*schema.Set {
 	people := schema.NewSet(schema.HashString, []interface{}{})
 	groups := schema.NewSet(schema.HashString, []interface{}{})