שאילתות לגבי חברות בקבוצות

במדריך הזה מוסבר איך לבצע שאילתות טרנזיטיביות לגבי חברות בקבוצות ולאחזר את גרף החברות של חבר.

בנוסף להצגת החברים הישירים בקבוצה, אפשר לחפש באופן טרנזיטיבי חברים ישירים ועקיפים, ולראות את גרף החברות של חבר ספציפי. היכולות האלה מתאימות לתרחישי השימוש הבאים:

  • בעלי משאבים יכולים לקבל החלטות מושכלות יותר לגבי שינויים ברשימות בקרת הגישה למשאבים, אם הם מבינים אילו קבוצות וחברים מושפעים מהשינויים.
  • בעלי קבוצות יכולים להעריך את ההשפעה של הוספה או הסרה של קבוצה מקבוצה שקשורה לבקרת ACL, ולפתור בקלות רבה יותר בעיות שקשורות לחברות בקבוצה.
  • גורמי ביקורת של אבטחה יכולים לבצע ביקורת של מדיניות הגישה בצורה יעילה יותר כי הם יכולים לראות את מבנה החברות המורחב של כל הארגון.
  • בודקי אבטחה יכולים להעריך את סיכון האבטחה של חבר בקבוצה על ידי צפייה בכל החברויות הישירות והעקיפות שלו בקבוצות, או על ידי בדיקה אם חבר שייך לקבוצה מסוימת.

חברות בקבוצה יכולה להיות של אדם פרטי, של חשבון שירות או של קבוצה אחרת.

למשתמש או לחשבון השירות שמבצעים את השאילתה צריכה להיות הרשאה לצפייה בחברות בכל הקבוצות שכלולות בשאילתה, אחרת הבקשה תיכשל. אם השאילתה מחזירה את השגיאה PERMISSION_DENIED, סביר להניח שאין לכם את ההרשאות הנכונות לאחת מהקבוצות המקוננות, במיוחד אם אחת מהן היא קבוצה שבבעלות ארגון אחר.

לפני שמתחילים

מפעילים את Cloud Identity API.

תפקידים שנדרשים להפעלת ממשקי API

כדי להפעיל ממשקי API, נדרשת ההרשאה serviceusage.services.enable. אם יצרתם את הפרויקט, סביר להניח שכבר יש לכם את ההרשאה הזו דרך התפקיד 'בעלים' (roles/owner). אחרת, תוכלו לקבל את ההרשאה הזו דרך התפקיד 'אדמין בממשק 'שימוש בשירות'' (roles/serviceusage.serviceUsageAdmin). איך מקצים תפקידים

להפעלת ה-API

חיפוש של כל החברים בקבוצה

הקוד הזה מחזיר את כל החברויות בקבוצה. התשובה כוללת את סוג המינוי (ישיר, עקיף או שניהם) לכל מינוי.

REST

כדי לקבל רשימה של כל החברים בקבוצה, מתקשרים אל groups.memberships.searchTransitiveMemberships() עם המזהה של קבוצת ההורה.

Python

כדי לבצע אימות ב-Cloud Identity, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

import googleapiclient.discovery
from urllib.parse import urlencode

def search_transitive_memberships(service, parent, page_size):
  try:
    memberships = []
    next_page_token = ''
    while True:
      query_params = urlencode(
        {
          "page_size": page_size,
          "page_token": next_page_token
        }
      )
      request = service.groups().memberships().searchTransitiveMemberships(parent=parent)
      request.uri += "&" + query_params
      response = request.execute()

      if 'memberships' in response:
        memberships += response['memberships']

      if 'nextPageToken' in response:
        next_page_token = response['nextPageToken']
      else:
        next_page_token = ''

      if len(next_page_token) == 0:
        break;

    print(memberships)
  except Exception as e:
    print(e)

def main():

  service = googleapiclient.discovery.build('cloudidentity', 'v1')

  # Return results with a page size of 50
  search_transitive_memberships(service, 'groups/GROUP_ID', 50)

if __name__ == '__main__':
    main()

חיפוש של כל החברויות של משתמש בקבוצות

REST

כדי למצוא את כל הקבוצות שהמשתמש שייך אליהן, קוראים לפונקציה groups.memberships.searchTransitiveGroups() עם מפתח החבר (לדוגמה, כתובת האימייל של החבר).

Python

כדי לבצע אימות ב-Cloud Identity, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

הקוד הזה מחזיר את כל הקבוצות שהמשתמש שייך אליהן (חוץ מקבוצות עם מיפוי זהויות), ישירות ועקיפות.

import googleapiclient.discovery
from urllib.parse import urlencode

def search_transitive_groups(service, member, page_size):
  try:
    groups = []
    next_page_token = ''
    while True:
      query_params = urlencode(
        {
          "query": "member_key_id == '{}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels".format(member),
          "page_size": page_size,
          "page_token": next_page_token
        }
      )
      request = service.groups().memberships().searchTransitiveGroups(parent='groups/-')
      request.uri += "&" + query_params
      response = request.execute()

      if 'memberships' in response:
        groups += response['memberships']

      if 'nextPageToken' in response:
        next_page_token = response['nextPageToken']
      else:
        next_page_token = ''

      if len(next_page_token) == 0:
        break;

    print(groups)
  except Exception as e:
    print(e)

def main():

  service = googleapiclient.discovery.build('cloudidentity', 'v1')

  # Return results with a page size of 50
  search_transitive_groups(service, 'MEMBER_EMAIL_ADDRESS', 50)

if __name__ == '__main__':
    main()

בדיקת החברות בקבוצה

REST

כדי לבדוק אם חבר שייך לקבוצה מסוימת (ישירות או בעקיפין), מתקשרים אל checkTransitiveMembership() עם מזהה קבוצת האם ומפתח החבר (לדוגמה, כתובת האימייל של החבר).

Python

כדי לבצע אימות ב-Cloud Identity, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

הקוד הבא קובע אם החבר שייך לקבוצה ספציפית:

import googleapiclient.discovery
from urllib.parse import urlencode

def check_transitive_membership(service, parent, member):
  try:
    query_params = urlencode(
      {
        "query": "member_key_id == '{}'".format(member)
      }
    )
    request = service.groups().memberships().checkTransitiveMembership(parent=parent)
    request.uri += "&" + query_params
    response = request.execute()
    print(response['hasMembership'])
  except Exception as e:
    print(e)

def main():

  service = googleapiclient.discovery.build('cloudidentity', 'v1')

  check_transitive_membership(service, 'groups/GROUP_ID', 'MEMBER_EMAIL_ADDRESS')

if __name__ == '__main__':
    main()

אחזור גרף החברות של חבר

.

REST

כדי לקבל את גרף החברות של משתמש (כל הקבוצות שהמשתמש שייך אליהן, יחד עם פרטי הנתיב), צריך להתקשר אל groups.memberships.getMembershipGraph() עם מזהה קבוצת ההורה ומפתח החברות (לדוגמה, כתובת האימייל של המשתמש). הגרף מוחזר כרשימת סמיכויות.

Python

כדי לבצע אימות ב-Cloud Identity, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

הקוד הבא מחזיר את גרף החברות של חבר ספציפי בקבוצת Google (השאילתה הזו מסוננת לפי סוג הקבוצה באמצעות התווית):

import googleapiclient.discovery
from urllib.parse import urlencode

def get_membership_graph(service, parent, member):
  try:
    query_params = urlencode(
      {
        "query": "member_key_id == '{}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels".format(member)
      }
    )
    request = service.groups().memberships().getMembershipGraph(parent=parent)
    request.uri += "&" + query_params
    response = request.execute()
    print(response['response'])
  except Exception as e:
    print(e)

def main()

  service = googleapiclient.discovery.build('cloudidentity', 'v1')

  # Specify parent group as 'groups/-' to get ALL the groups of a member
  # along with path information
  get_membership_graph(service, 'groups/GROUP_ID', 'MEMBER_KEY')

if __name__ == '__main__':
    main()

יצירת ייצוג חזותי של גרף החברות

בהמשך מופיעה תשובה לדוגמה מקוד Python שלמעלה. בדוגמה הזו, הקבוצות 000,‏ 111 ו-222 מקושרות באופן הבא (החצים הם מהורה לצאצא): 000 -> 111 -> 222. קריאה לקוד לדוגמה כדי לאחזר את הגרף המלא של קבוצה 222:

get_membership_graph(service, 'groups/-', 'group-2@example.com')

התוצאה היא התגובה הבאה:

{
  "@type": "type.googleapis.com/google.apps.cloudidentity.groups.v1.GetMembershipGraphResponse",
  "adjacencyList": [
    {
      "edges": [
        {
          "name": "groups/000/memberships/111",
          "preferredMemberKey": {
            "id": "group-1@example.com"
          },
          "roles": [
            {
              "name": "MEMBER"
            }
          ]
        }
      ],
      "group": "groups/000"
    },
    {
      "edges": [
        {
          "name": "groups/111/memberships/222",
          "preferredMemberKey": {
            "id": "group-2@example.com"
          },
          "roles": [
            {
              "name": "MEMBER"
            }
          ]
        }
      ],
      "group": "groups/111"
    }
  ],
  "groups": [
    {
      "name": "groups/000",
      "groupKey": {
        "id": "group-0@example.com"
      },
      "displayName": "Group - 0",
      "description": "Group - 0",
      "labels": {
        "cloudidentity.googleapis.com/groups.discussion_forum": ""
      }
    },
    {
      "name": "groups/111",
      "groupKey": {
        "id": "group-1@example.com"
      },
      "displayName": "Group - 1",
      "description": "Group - 1",
      "labels": {
        "cloudidentity.googleapis.com/groups.discussion_forum": ""
      }
    },
    {
      "name": "groups/222",
      "groupKey": {
        "id": "group-2@example.com"
      },
      "displayName": "Group - 2",
      "description": "Group - 2",
      "labels": {
        "cloudidentity.googleapis.com/groups.discussion_forum": ""
      }
    }
  ]
}

כל פריט ברשימת הסמיכות מייצג קבוצה ואת החברים הישירים שלה (קצוות), והתגובה כוללת גם פרטים של כל הקבוצות בתרשים החברות. אפשר לנתח אותו כדי ליצור ייצוגים חלופיים (למשל, תרשים DOT) שאפשר להשתמש בהם כדי להמחיש את תרשים החברות.

אפשר להשתמש בסקריפט לדוגמה הזה כדי להמיר את התגובה לגרף DOT:

#
# Generates output in a dot format. Invoke this method using
# response['response'] from get_membership_graph()
#
# Save the output to a .dot file (say graph.dot)
# Use the dot tool to generate a visualization of the graph
# Example:
# dot -Tpng -o graph.png graph.dot
#
# Generates output like below:
#
# digraph {
#   'group0' [label='groups/000 (GROUP 0)'];
#   'group1' [label='groups/111 (GROUP 1)'];
#   'group2' [label='groups/222 (GROUP 2)'];
#   'group3' [label='groups/333 (GROUP 3)'];
#   'group4' [label='groups/444 (GROUP 4)'];
#
#   'group0' -> 'group1' [label='group-1@example.com (MEMBER)'];
#   'group0' -> 'group2' [label='group-2@example.com (MEMBER)'];
#   'group1' -> 'group3' [label='group-3@example.com (MEMBER)'];
#   'group3' -> 'group4' [label='group-4@example.com (MEMBER)'];
#   'group2' -> 'group3' [label='group-3@example.com (MEMBER)'];
# }
#
def convert_to_dot_format(graph):
  output = "digraph {\n"
  try:
    # Generate labels for the group nodes
    for group in graph['groups']:
      if 'displayName' in group:
        label = '{} ({})'.format(group['name'], group['displayName'])
      else:
        label = group['name']
      output += '  "{}" [label="{}"];\n'.format(group['name'].split('/')[1], label)

    output += '\n'

    # Generate edges
    for item in graph['adjacencyList']:
      group_id = item['group'].split('/')[1]
      for edge in item['edges']:
        edge_to = edge['name'].split('/')[3]
        edge_key = edge['preferredMemberKey']['id']
        # Collect the roles
        roles = []
        for role in edge['roles']:
          roles.append(role['name'])
        output += '  "{}" -> "{}" [label="{} ({})"];\n'.format(group_id,
                                                               edge_to,
                                                               edge_key,
                                                               ','.join(roles))

    output += "}\n"
    print(output)
  except Exception as e:
    print(e)

ההיררכיה החזותית שמתקבלת עבור התשובה לדוגמה:

דוגמה לתרשים חברות מהמרת DOT