RBAC with Fine Grained Access - Apigee X - Using Groups

My previous article RBAC with Fine Grained Access - Apigee X was a quick solution at the project level to show the necessary configuration. This article provides a more robust solution using organizational groups, plus it shows how to configure using Google APIs and Terraform.

User Story 

As a security stakeholder, I want to restrict access to Apigee resources so that I can assign them to separate business units or projects.

  • Business unit leads should be able to grant user access to their own environment(s).
  • Business unit members should only be able to access Apigee resources that belong to the business unit. These resources should include:
    • API Proxy 
    • Shared Flow
    • Deployments
    • Debug Sessions
    • Flow Hooks
    • KVMs 
    • Resource files
    • TargetServers
    • References
    • KeyStores

Overview and Background

Supported Resource Types

Apigee supports adding resource conditions in IAM policies to these supported resources types using conditional role bindings during role assignment. Conditions can be based on time (e.g. schedule, expiry) or a named resource (e.g. type and name starts with “bu1-”). Roles can be assigned to individual users or to a group, the later being a better practice. Assigning conditional access to a group simplifies management as once the conditions have been specified for the group, individual users are simply added to the group. This avoids updating the conditions on every user assignment. 

Environment Access

Environments do not support conditional role assignment directly. Rather, access to an environment and its resources is granted via “Access” in the UI or via the Apigee API Environments Set IAM Policy API. Assigning a user as an “Apigee Environment Admin” to an environment (e.g. bu1-test) controls access to environment specific resources such as KVMs, Resource files, TargetServers, References, and KeyStores. It also controls access to deployments and debug sessions for the environment.

Unsupported Resource Types

These resource types do not support “conditional” role based assignment, access to these resources can be managed via built in roles or custom roles.

  • Analytics Dashboards
  • Custom Reports
  • Advanced API Security
  • Advanced API Ops
  • Integrated Developer Portals
  • Integrations
  • Connectors

Limitations and Caveats

  1. Rate Plans
    1. Rate Plans are associated with an API Product, therefore access can be protected by the API Product name.
    2. Rate Plans are identified using a UUID; it’s not demonstrated how to apply conditional role assignment.
  2. App Developers
    1. Developers are identified using either an email or a UUID; it’s not demonstrated how to apply conditional role assignment.
  3. Applications
    1. Although Applications can be restricted by name, The “Apps” list page in the UI performs a “GET /apps?expand=true” API call which returns all the details for an App thereby allowing the user to see any App’s keys.

Overview of Steps

Prerequisites

  1. Apigee X organization with 2 environments (e.g. bu1-test, bu2-test).
  2. Users in the organization and Service Accounts in project to exist.

Steps

  1. Create groups (e.g. business-unit-1, business-unit-2)
  2. Create a custom role “Custom Role Apigee Deploy and Debug” with get and list permissions for environments and deployments to users without conditions. This fixes UI errors preventing deployments and debug sessions.
  3. Assign Apigee API Admin and Apigee Developer Admin roles to group with conditions on role assignment.
  4. Assign a custom role “Custom Role Apigee Deploy and Debug” to group without conditions. 
  5. Assign Apigee Environment Admin “Access” for the user to specific Environments (e.g. bu1-test, bu2-test).

Solution Details - Using the UI

Enable API

kurtkanaskie_0-1663701901845.png

Apigee supports adding resource conditions in IAM policies on specific resource types (proxies, shared flows) used in the Apigee builtin rules through GCP IAM. Not all resource types are “conditional”, in particular Environments. Access to environments is done via “Access” in the UI or via the Apigee API Environments Set IAM Policy API. Assigning a user or group as an “Apigee Environment Admin” to an environment (e.g. bu1-test, bu2-test) controls access to environment specific resources such as KVMs, Resource files, TargetServers, References, and KeyStores.

Create Groups and Add Members

In IAM and Admin for the organization, select Groups and create the context specific groups (e.g. bu1, bu2, bu3).

NOTE: this assumes individual users are already created in the organization.

 

kurtkanaskie_0-1663790878226.png

The resulting details:

 

kurtkanaskie_1-1663791066748.png

Repeat for other groups.

NOTES: 

  • Adding a Service Account to a group is generally not a best practice.
  • The Service Account was created in the project without any roles.

See: Service Accounts and Google Groups and Avoid using groups for granting service accounts access to resources.

Create Custom Role at Organization Level

This is required to allow the user to manage deployments and debug sessions in the Apigee UI. It also enables configuring Flow Hooks, for the assigned environment.

Create a custom role (e.g. Custom Role Apigee Deploy and Debug) that allows get and list permissions on deployments and environments.

apigee.deployments.get
apigee.deployments.list
apigee.entitlements.get
apigee.envgroupattachments.get
apigee.envgroupattachments.list
apigee.envgroups.get
apigee.envgroups.list
apigee.environments.get
apigee.environments.getStats
apigee.environments.list
apigee.operations.get
apigee.operations.list
apigee.projectorganizations.get
kurtkanaskie_0-1680011503565.png

 


Assign Roles with Conditions to Groups

The Apigee built in roles for Apigee API Admin and Apigee Developer Admin are required for an “API proxy developer” to create and test.

Apigee API Admin role is required to create API proxies, shared flows and related artifacts such as API Products. 

Apigee Developer Admin role is required to create Developers and Apps for testing. 

The custom role Custom Role Apigee Deploy and Debug avoids errors in the UI, it is assigned without conditions.

Assign Conditions to Assigned Roles

Add the following conditions to both the Apigee API Admin and Apigee Developer Admin role assignments.

  • The use of (resource.type == 'apigee.googleapis.com/Developer') allows users to list Developers and their Apps.
  • The use of (resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu1-')) allows access to named Developer Apps.

NOTE: the resource.name.startsWith() condition uses the name of the Apigee X project (e.g. apigeex-exp), replace with your value.

resource.name.startsWith('organizations/apigeex-exp/apis/bu1-') ||
resource.name.startsWith('organizations/apigeex-exp/sharedflows/bu1-') ||
resource.name.startsWith('organizations/apigeex-exp/apiproducts/bu1-') ||
(resource.type == 'apigee.googleapis.com/Developer') ||
(resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu1-')) ||
resource.type == 'cloudresourcemanager.googleapis.com/Project'

Steps:

  1. Navigate to IAM & Admin → IAM in the GCP Console
  2. Add a principal and assign Apigee API Admin role.
  3. Click the pencil to “Edit principal”
  4. Click the pencil to add the Condition

 

kurtkanaskie_4-1663701901847.png

Select “CONDITION EDITOR” and paste the condition from above, adjusting “bu1-” to be your resource prefix.

kurtkanaskie_6-1663791590728.png

Repeat for the Apigee Developer Admin role.

Then repeat for other groups, changing the prefix value accordingly.

kurtkanaskie_5-1663791512095.png 

Assign Access to Environment for Groups

Access for users to specific environments is done in Apigee UI or via APIs on the environment.

Access to environments is required to allow management of environment specific resources such as Target Servers, KVMs and PropertySets, since these are not named resources and cannot be used in conditional role assignments.

Access to environments is also required to manage deployments for the specific environment.

kurtkanaskie_3-1663791394664.png

 Role assignment in the Apigee Management UI

kurtkanaskie_7-1663791633627.png

 

 

Solution Details - Using APIs

TIP: Create an alias to simplify curl commands to GCP and Apigee APIs.

alias curlx='curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)"'

Create Groups

Enable API

kurtkanaskie_9-1663701901776.png

You’ll need to use the customer ID (e.g. customers/C03rq1yqp) which you can find in the Admin console under Account → Account Settings at: https://admin.google.com/ac/accountsettings/profile.

curlx -X POST 'https://cloudidentity.googleapis.com/v1/groups?initialGroupConfig=WITH_INITIAL_OWNER' \
--header 'X-Goog-User-Project: apigeex-exp' \
--header 'Content-Type: application/json' \
--data-raw '{
    "groupKey": {
        "id": "bu1@kurtkanaskie.altostrat.com"
    },
    "parent": "customers/C03rq1yqp",
    "displayName": "Business Unit 1",
    "description": "Business Unit 1 Group",
    "labels": {
        "cloudidentity.googleapis.com/groups.discussion_forum": ""
    }
}'

# response
{
    "done": true,
    "response": {
        "@type": "type.googleapis.com/google.apps.cloudidentity.groups.v1.Group",
        "name": "groups/02ce457m220civc",
        "groupKey": {
            "id": "bu3@kurtkanaskie.altostrat.com"
        },
        "parent": "customers/C03rq1yqp",
        "displayName": "Business Unit 1",
        "description": "Business Unit 1 Group",
        "createTime": "2022-09-13T14:12:40.425818Z",
        "updateTime": "2022-09-13T14:12:40.425818Z",
        "labels": {
            "cloudidentity.googleapis.com/groups.discussion_forum": ""
        }
    }
}

Add Users to Group

NOTE: This assumes users are already onboarded in the organization.

curlx -X POST 'https://cloudidentity.googleapis.com/v1/groups/02ce457m220civc/memberships' \
--header 'X-Goog-User-Project: apigeex-exp' \
--header 'Content-Type: application/json' \
--data-raw '{
    "preferredMemberKey": {
        "id": "dev1.bu1@kurtkanaskie.altostrat.com"
    },
    "roles": [
        {
            "name": "MEMBER"
        }
    ]
}'

# response
{
    "done": true,
    "response": {
        "@type": "type.googleapis.com/google.apps.cloudidentity.groups.v1.Membership",
        "name": "groups/02ce457m220civc/memberships/109834938926743824778",
        "preferredMemberKey": {
            "id": "dev1.bu1@kurtkanaskie.altostrat.com"
        },
        "roles": [
            {
                "name": "MEMBER"
            }
        ]
    }
}​

Create Custom Role

See: Creating and managing custom roles

NOTES:

  • ORG_ID is the unique identifier of your organization (e.g. 785287799950) which you can find from the GCP Console project selector dropdown.
  • Ensure the user performing these steps has the “Role Administrator” role assigned.
curlx -X POST https://iam.googleapis.com/v1/organization/$ORG_ID/roles \
--header 'Content-Type: application/json' \
--data-raw '{
    "roleId": "CustomRoleApigeeDeployDebug",
    "role": {
        "title": "Custom Role Apigee Deploy and Debug",
        "description": "Custom role for fine grained access with deployment and debug",
        "includedPermissions": [
            "apigee.deployments.get", 
            "apigee.deployments.list", 
            "apigee.entitlements.get", 
            "apigee.envgroupattachments.get", 
            "apigee.envgroupattachments.list", 
            "apigee.envgroups.get", 
            "apigee.envgroups.list", 
            "apigee.environments.get", 
            "apigee.environments.getStats", 
            "apigee.environments.list", 
            "apigee.operations.get", 
            "apigee.operations.list", 
            "apigee.projectorganizations.get"
        ],
        "stage": "ALPHA"
    }
}'

# response
{
  "name": "organizations/785287799950/roles/CustomRoleApigeeDeployDebug",
  "title": "Custom Role Apigee Deploy and Debug",
  "description": "Custom role for fine grained access with deployment and debug",
  "includedPermissions": [
    "apigee.deployments.get", 
    "apigee.deployments.list", 
    "apigee.entitlements.get", 
    "apigee.envgroupattachments.get", 
    "apigee.envgroupattachments.list", 
    "apigee.envgroups.get", 
    "apigee.envgroups.list", 
    "apigee.environments.get", 
    "apigee.environments.getStats", 
    "apigee.environments.list", 
    "apigee.operations.get", 
    "apigee.operations.list", 
    "apigee.projectorganizations.get"
  ],
  "etag": "BwXoB+k8g6E="
}

Assign Roles

See: Manage conditional role bindings and Grant or revoke multiple roles.

Assign Group to Roles with Conditions

NOTE: Updating the policy for users requires a complete update of all bindings.

Get the current policy and save it in a file. 

curlx -X POST https://cloudresourcemanager.googleapis.com/v1/organizations/$ORG_ID:getIamPolicy --header 'Content-Type: application/json' --data-raw '{
  "options": {
    "requestedPolicyVersion": 3
  }
}'  > current_allow_policy_org.json

cat current_allow_policy_org.json
{
  "version": 3,
  "etag": "BwXoG4rcZMQ=",
  "bindings": [
... content clipped ...
  ]
}

Copy the policy to a new file (e.g. update_allow_policy_org.json) and edit to add the outer “policy” element

cp current_allow_policy_org.json update_allow_policy_org.json

{
  "policy":
      content from current_allow_policy_org.json
}

Add in the new role assignments

curlx -X POST -H "Content-Type: application/json; charset=utf-8" -d @update_allow_policy.json "https://cloudresourcemanager.googleapis.com/v1/organizations/$ORG_ID:setIamPolicy"

cat update_allow_policy.json
{
  "policy": {
    "version": 3,
    "etag": "BwXofUXVZlY=",
    "bindings": [{
        "role": "organizations/785287799950/roles/CustomRoleApigeeDeployDebug",
        "members": [
          "group:business-unit-1@kurtkanaskie.altostrat.com",
          "group:business-unit-2@kurtkanaskie.altostrat.com"
        ]
      },
      {
        "role": "roles/apigee.apiAdminV2",
        "members": [
          "group:business-unit-1@kurtkanaskie.altostrat.com"
        ],
        "condition": {
          "expression": "resource.name.startsWith('organizations/apigeex-exp/apis/bu1-') ||\nresource.name.startsWith('organizations/apigeex-exp/sharedflows/bu1-') ||\nresource.name.startsWith('organizations/apigeex-exp/apiproducts/bu1-') ||\n(resource.type == 'apigee.googleapis.com/Developer') ||\n(resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu1-')) ||\nresource.type == 'cloudresourcemanager.googleapis.com/Project'\n",
          "title": "business-unit-1"
        }
      },
      {
        "role": "roles/apigee.developerAdmin",
        "members": [
          "group:business-unit-1@kurtkanaskie.altostrat.com"
        ],
        "condition": {
          "expression": "resource.name.startsWith('organizations/apigeex-exp/apis/bu1-') ||\nresource.name.startsWith('organizations/apigeex-exp/sharedflows/bu1-') ||\nresource.name.startsWith('organizations/apigeex-exp/apiproducts/bu1-') ||\n(resource.type == 'apigee.googleapis.com/Developer') ||\n(resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu1-')) ||\nresource.type == 'cloudresourcemanager.googleapis.com/Project'",
          "title": "business-unit-1"
        }
      },
      {
        "role": "roles/apigee.apiAdminV2",
        "members": [
          "group:business-unit-2@kurtkanaskie.altostrat.com"
        ],
        "condition": {
          "expression": "resource.name.startsWith('organizations/apigeex-exp/apis/bu2-') ||\nresource.name.startsWith('organizations/apigeex-exp/sharedflows/bu2-') ||\nresource.name.startsWith('organizations/apigeex-exp/apiproducts/bu2-') ||\n(resource.type == 'apigee.googleapis.com/Developer') ||\n(resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu2-')) ||\nresource.type == 'cloudresourcemanager.googleapis.com/Project'",
          "title": "business-unit-2"
        }
      },
      {
        "role": "roles/apigee.developerAdmin",
        "members": [
          "group:business-unit-2@kurtkanaskie.altostrat.com"
        ],
        "condition": {
          "expression": "resource.name.startsWith('organizations/apigeex-exp/apis/bu2-') ||\nresource.name.startsWith('organizations/apigeex-exp/sharedflows/bu2-') ||\nresource.name.startsWith('organizations/apigeex-exp/apiproducts/bu2-') ||\n(resource.type == 'apigee.googleapis.com/Developer') ||\n(resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu2-')) ||\nresource.type == 'cloudresourcemanager.googleapis.com/Project'",
          "title": "business-unit-2"
        }
      }
... content clipped ...
  ]
}

Assign Group Environment Access via Apigee API

This is a two step process:

  1. Environments Get IAM Policy API - Gets the IAM policy on an environment.
  2. Environments Set IAM Policy API - Sets the IAM policy on an environment. If the policy already exists, it will be replaced.

Get and set policy for bu1-test environment.

Get the current policy

curlx 'https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu1-test:getIamPolicy?options.requestedPolicyVersion=3'

# response
{
    "etag": "ACAB"
}

Update policy for group business-unit-1

curlx -X POST 'https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu1-test:setIamPolicy' \
--header 'Content-Type: application/json' \
--data-raw '{
    "policy": {
        "bindings": [
            {
                "role": "roles/apigee.environmentAdmin",
                "members": [
                    "group:business-unit-1@kurtkanaskie.altostrat.com"
                ]
            }
        ],
        "etag": "ACAB",
        "version": 1
    }
}'

# response
{
    "version": 1,
    "etag": "BwXoflPFvoc=",
    "bindings": [
        {
            "role": "roles/apigee.environmentAdmin",
            "members": [
                "group:business-unit-1@kurtkanaskie.altostrat.com"
            ]
        }
    ]
}

Get and set policy for bu2-test environment.

Get the current policy

curlx 'https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu2-test:getIamPolicy?options.requestedPolicyVersion=3'

# response
{
    "etag": "ACAB"
}

Update policy for group business-unit-2

curlx -X POST 'https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu2-test:setIamPolicy' \
--header 'Content-Type: application/json' \
--data-raw '{
    "policy": {
        "bindings": [
            {
                "role": "roles/apigee.environmentAdmin",
                "members": [
                    "group:business-unit-2@kurtkanaskie.altostrat.com"
                ]
            }
        ],
        "etag": "ACAB",
        "version": 1
    }
}'

# response
{
    "version": 1,
    "etag": "BwXoflPFvoc=",
    "bindings": [
        {
            "role": "roles/apigee.environmentAdmin",
            "members": [
                "group:business-unit-2@kurtkanaskie.altostrat.com"
            ]
        }
    ]
}

Terraform Organization and Groups

Overview

  1. Create GCP custom role, groups, group role assignment with conditions and memberships.
  2. Assign Apigee Environment Access

NOTE: Expects users in organization and Service Accounts in project to exist.

Terraform Providers (terraform.tf)

terraform {
  required_providers {
    google = {
      source = "hashicorp/google"
    }
  }
}

Variables (variables.tf)

variable "domain" {
  type = string
  default = "kurtkanaskie.altostrat.com"
}

variable "customer_id" {
  type = string
  default = "C03rq1yqp"
}

variable "org_id" {
  type = string
  default = "785287799950"
}

variable "project_id" {
  type = string
  default = "apigeex-exp"
}

Custom Role, Conditions and Bindings (main.tf)

provider "google" {
  user_project_override = true
  
  # How do I set permissions at org level? Organization Admin role - nope
  # credentials = file("sa-apigeex-exp-terraform.json")

  billing_project = var.project_id
  project = var.project_id
}

#######################################################################
### Custom Apigee X Deploy and Debug Role
#######################################################################
resource "google_organization_iam_custom_role" "CustomRoleApigeeXDeployDebug" {
  org_id      = var.org_id
  role_id     = "CustomRoleApigeeXDeployDebug"
  title       = "Custom Role Apigee X Deploy and Debug"
  description = "Custom role for Apigee X fine grained access with deployment and debug"
  permissions = [
    "apigee.deployments.get",
    "apigee.deployments.list",
    "apigee.entitlements.get",
    "apigee.envgroupattachments.get",
    "apigee.envgroupattachments.list",
    "apigee.envgroups.get",
    "apigee.envgroups.list",
    "apigee.environments.get",
    "apigee.environments.getStats",
    "apigee.environments.list",
    "apigee.operations.get",
    "apigee.operations.list",
    "apigee.projectorganizations.get"
  ]
}

#######################################################################
### BU 1
### Groups and memberships BU 1
#######################################################################
resource "google_cloud_identity_group" "business_unit_1" {
  display_name         = "Business Unit One"
  description          = "Business Unit One Group"
  initial_group_config = "WITH_INITIAL_OWNER"

  parent = "customers/${var.customer_id}"

  group_key {
      id = "business-unit-1@${var.domain}"
  }

  labels = {
    "cloudidentity.googleapis.com/groups.discussion_forum" = ""
  }
}

resource "google_cloud_identity_group_membership" "business_unit_1_membership_1" {
  group    = google_cloud_identity_group.business_unit_1.id

  preferred_member_key {
    id = "dev1.bu1@${var.domain}"
  }

  roles {
    name = "MEMBER"
  }
}

resource "google_cloud_identity_group_membership" "business_unit_1_membership_2" {
  group    = google_cloud_identity_group.business_unit_1.id

  preferred_member_key {
    id = "dev2.bu1@${var.domain}"
  }

  roles {
    name = "MEMBER"
  }
}

resource "google_cloud_identity_group_membership" "business_unit_1_membership_3" {
  group    = google_cloud_identity_group.business_unit_1.id

  preferred_member_key {
    id = "cicd-test-bu1@${var.project_id}.iam.gserviceaccount.com"
  }

  roles {
    name = "MEMBER"
  }
}

#######################################################################
### Role assignment to groups with conditions BU 1
#######################################################################
resource "google_organization_iam_member" "business_unit_1_custom" {
  org_id  = var.org_id
  role    = "organizations/${var.org_id}/roles/CustomRoleApigeeXDeployDebug"
  member  = "group:business-unit-1@${var.domain}"
}

resource "google_organization_iam_member" "business_unit_1_api_admin" {
  org_id  = var.org_id
  role    = "roles/apigee.apiAdminV2"
  member  = "group:business-unit-1@${var.domain}"

  condition {
    title       = "business-unit-1"
    description = "Conditions for business unit to edit bu1- prefixed proxies, shared flows and test"
    expression  = "resource.name.startsWith('organizations/apigeex-exp/apis/bu1-') ||\nresource.name.startsWith('organizations/apigeex-exp/sharedflows/bu1-') ||\nresource.name.startsWith('organizations/apigeex-exp/apiproducts/bu1-') ||\n(resource.type == 'apigee.googleapis.com/Developer') ||\n(resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu1-')) ||\nresource.type == 'cloudresourcemanager.googleapis.com/Project'"
  }
}

resource "google_organization_iam_member" "business_unit_1_developer_admin" {
  org_id  = var.org_id
  role    = "roles/apigee.developerAdmin"
  member  = "group:business-unit-1@${var.domain}"

  condition {
    title       = "business-unit-1"
    description = "Conditions for business unit to edit bu1- prefixed proxies, shared flows and test"
    expression  = "resource.name.startsWith('organizations/apigeex-exp/apis/bu1-') ||\nresource.name.startsWith('organizations/apigeex-exp/sharedflows/bu1-') ||\nresource.name.startsWith('organizations/apigeex-exp/apiproducts/bu1-') ||\n(resource.type == 'apigee.googleapis.com/Developer') ||\n(resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu1-')) ||\nresource.type == 'cloudresourcemanager.googleapis.com/Project'"
  }
}

#######################################################################
### Environment Access for BU 1
#######################################################################
resource "google_apigee_environment_iam_member" "member-bu1-test" {
  org_id = "organizations/${var.project_id}"
  env_id = "bu1-test"
  role = "roles/apigee.environmentAdmin"
  member = "group:business-unit-1@${var.domain}"
}

#######################################################################
### BU 2
### Groups and memberships BU 2
#######################################################################
resource "google_cloud_identity_group" "business_unit_2" {
  display_name         = "Business Unit Two"
  description          = "Business Unit Two Group"
  initial_group_config = "WITH_INITIAL_OWNER"

  parent = "customers/${var.customer_id}"

  group_key {
      id = "business-unit-2@${var.domain}"
  }

  labels = {
    "cloudidentity.googleapis.com/groups.discussion_forum" = ""
  }
}

resource "google_cloud_identity_group_membership" "business_unit_2_membership_1" {
  group    = google_cloud_identity_group.business_unit_2.id

  preferred_member_key {
    id = "dev1.bu2@${var.domain}"
  }

  roles {
    name = "MEMBER"
  }
}

resource "google_cloud_identity_group_membership" "business_unit_2_membership_2" {
  group    = google_cloud_identity_group.business_unit_2.id

  preferred_member_key {
    id = "dev2.bu2@${var.domain}"
  }

  roles {
    name = "MEMBER"
  }
}

resource "google_cloud_identity_group_membership" "business_unit_2_membership_3" {
  group    = google_cloud_identity_group.business_unit_2.id

  preferred_member_key {
    id = "cicd-test-bu2@${var.project_id}.iam.gserviceaccount.com"
  }

  roles {
    name = "MEMBER"
  }
}

#######################################################################
### Role assignment to groups with conditions BU 2
#######################################################################
resource "google_organization_iam_member" "business_unit_2_custom" {
  org_id  = var.org_id
  role    = "organizations/${var.org_id}/roles/CustomRoleApigeeXDeployDebug"
  member  = "group:business-unit-2@${var.domain}"
}

resource "google_organization_iam_member" "business_unit_2_api_admin" {
  org_id  = var.org_id
  role    = "roles/apigee.apiAdminV2"
  member  = "group:business-unit-2@${var.domain}"

  condition {
    title       = "business-unit-2"
    description = "Conditions for business unit to edit bu2- prefixed proxies, shared flows and test"
    expression  = "resource.name.startsWith('organizations/apigeex-exp/apis/bu2-') ||\nresource.name.startsWith('organizations/apigeex-exp/sharedflows/bu2-') ||\nresource.name.startsWith('organizations/apigeex-exp/apiproducts/bu2-') ||\n(resource.type == 'apigee.googleapis.com/Developer') ||\n(resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu2-')) ||\nresource.type == 'cloudresourcemanager.googleapis.com/Project'"
  }
}

resource "google_organization_iam_member" "business_unit_2_developer_admin" {
  org_id  = var.org_id
  role    = "roles/apigee.developerAdmin"
  member  = "group:business-unit-2@${var.domain}"

  condition {
    title       = "business-unit-2"
    description = "Conditions for business unit to edit bu2- prefixed proxies, shared flows and test"
    expression  = "resource.name.startsWith('organizations/apigeex-exp/apis/bu2-') ||\nresource.name.startsWith('organizations/apigeex-exp/sharedflows/bu2-') ||\nresource.name.startsWith('organizations/apigeex-exp/apiproducts/bu2-') ||\n(resource.type == 'apigee.googleapis.com/Developer') ||\n(resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu2-')) ||\nresource.type == 'cloudresourcemanager.googleapis.com/Project'"
  }
}

#######################################################################
### Environment Access for BU 2
#######################################################################
resource "google_apigee_environment_iam_member" "member-bu2-test" {
  org_id = "organizations/${var.project_id}"
  env_id = "bu2-test"
  role = "roles/apigee.environmentAdmin"
  member = "group:business-unit-2@${var.domain}"
}

Test

Users and service accounts created for role assignment: 

dev1-bu1@kurtkanaskie.altostrat.com
dev2-bu1@kurtkanaskie.altostrat.com
dev1-bu2@kurtkanaskie.altostrat.com
dev2-bu2@kurtkanaskie.altostrat.com
cicd-test-bu1@apigeex-exp.iam.gserviceaccount.com
cicd-test-bu2@apigeex-exp.iam.gserviceaccount.com

Set up gcloud configuration (optional) 

gcloud config configurations create $YOUR_PROJECT
gcloud config set project $YOUR_PROJECT
gcloud config set account dev1.bu1@kurtkanaskie.altostrat.com​

Activate a Service Account

gcloud config set account cicd-test-bu1@apigeex-exp.iam.gserviceaccount.com​

Sample API calls

# KVMs, Target Servers, Key Stores, References and Properties (OK)
200 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu1-test/targetservers
403 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu2-test/targetservers

# Environments and deployments (OK)
200 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/deployments
200 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu1-test/deployments
200 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu2-test/deployments (expected)

# Conditional proxies, revisions, deployments (OK)
200 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/apis/bu1-proxy-1/revisions
200 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/apis/bu1-proxy-1/deployments
403 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/apis/bu2-proxy-1/revisions (expected)
403 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/apis/bu2-proxy-1/deployments (expected)

200 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/apis/bu1-proxy-1/revisions/7
200 - curlx -X DELETE https://apigee.googleapis.com/v1/organizations/apigeex-exp/apis/bu1-proxy-1/revisions/7
200 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/apis/bu1-proxy-1/revisions/7/deployments
200 - curlx https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu1-test/apis/bu1-proxy-1/revisions/7/deployments
200 - curlx -X DELETE https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu1-test/apis/bu1-proxy-1/revisions/7/deployments
200 - curlx -X POST https://apigee.googleapis.com/v1/organizations/apigeex-exp/environments/bu1-test/apis/bu1-proxy-1/revisions/6/deployments

References

GCP IAM and Roles

Apigee Users and Roles

Terraform

Contributors
Comments
kurtkanaskie
Staff

I just realized that using this prevented me from seeing Environments and Environment Groups.

I was able to "fix" this by changing the custom role to be:

apigee.deployments.get
apigee.deployments.list
apigee.envgroupattachments.get
apigee.envgroupattachments.list
apigee.envgroups.get
apigee.envgroups.list
apigee.environments.get
apigee.environments.list
apigee.operations.get
apigee.operations.list
paul-wright
Staff

Public Preview of Apigee UI in the Cloud Console stating Apigee requires setup

If you have seen the Public preview release of the Apigee UI in the Google Cloud console and are using a custom role as described above, you may have seen the Console ask for Apigee to be set up again. This behavior is the result of a new permission added as part of the Preview which may be missing from the custom roles being used.

To correct this behavior, simply add apigee.projectorganizations.get to the custom role Kurt describes above.

kurtkanaskie
Staff

Thanks Paul,

I've updated the article to reflect the permissions for the custom role when using the Apigee UI in the Cloud Console. Here are the permissions:

apigee.deployments.get
apigee.deployments.list
apigee.entitlements.get
apigee.envgroupattachments.get
apigee.envgroupattachments.list
apigee.envgroups.get
apigee.envgroups.list
apigee.environments.get
apigee.environments.getStats
apigee.environments.list
apigee.operations.get
apigee.operations.list
apigee.projectorganizations.get

 

ghagemans
Bronze 1
Bronze 1

thank you for this article, it's great!

when filtering on the resources (e.g. 

resource.name.startsWith('organizations/apigeex-exp/apis/bu1-')

is it possible to filter on other fields, such as the creator of the proxy or the description? 

kurtkanaskie
Staff

@ghagemans wrote:

is it possible to filter on other fields, such as the creator of the proxy or the description? 


Generally you can using the pattern and extract function: 

(resource.type == 'apigee.googleapis.com/DeveloperApp' && resource.name.extract('/apps/{name}').startsWith('bu1-'))

However, there is limited metadata on the API itself, e.g.

curlx https://apigee.googleapis.com/v1/organizations/$ORG/apis/catch-all-v1
{
  "metaData": {
    "createdAt": "1619614207213",
    "lastModifiedAt": "1628261685569",
    "subType": "Proxy"
  },
  "name": "catch-all-v1",
  "revision": [
    "1",
    "2",
    "3",
    "4"
  ],
  "apiProxyType": "PROGRAMMABLE"
}

 

paul-wright
Staff

Kurt, can you confirm... I believe any values evaluated with the .startsWith() function need to be on the management API's URL path. An Entity's metadata cannot be used.

kurtkanaskie
Staff

Hey Paul,

The use of extracts function work for Apps, because they use a UUID in the path, so extracting the name and using starts with is valid. For example my "roletester" cannot edit/save the App with name bu2-app-1. This works in the Classic UI, but sadly, not in the new UI.

kurtkanaskie_0-1689093250764.png

 

ghagemans
Bronze 1
Bronze 1

Thanks you so much for the reply! 

igallardo
Bronze 2
Bronze 2

Hi @kurtkanaskie

I've posted a question about something relationated with this topic, is this one: https://www.googlecloudcommunity.com/gc/Apigee/Apigee-hide-resources-using-IAM-Condition-Policies/td...

Can you answer about how to hide or block environments in Apigee for some group of people using IAM policies and IAM conditions?

I need this option because we are sharing the same Apigee project and for security aspect we must hide some options for some group of people.

Thanks.

kurtkanaskie
Staff

Hi @igallardo 

An Environment is not a conditional resource type as per docs: https://cloud.google.com/apigee/docs/api-platform/system-administration/add-iam-conditions#adding-re...

Access to environments is done via “Access” in the UI or via the Apigee API Environments Set IAM Policy API. Assigning a user or group as an “Apigee Environment Admin” to an environment (e.g. bu1-test, bu2-test) controls access to environment specific resources such as KVMs, Resource files, TargetServers, References, KeyStores and Deployments.

For example, in the environment "bu1-test":

kurtkanaskie_1-1698674444953.png

This is detailed in the article.

Version history
Last update:
‎10-25-2023 08:26 AM
Updated by: