Автоматизация управления разрешениями AWS Identity Center

Автоматизация управления разрешениями AWS Identity Center

31 октября 2022 г.

В сообщении было представлено решение для автоматизации групп AWS Identity Center для учетных записей AWS и

назначения набора разрешений для групп, созданных в AWS Identity Center от внешнего поставщика удостоверений.

Этот пост предполагает знакомство с сервисом AWS IAM и такими понятиями, как роли и политики.

Введение в Central Identity and Access Control на AWS

Среды AWS с несколькими учетными записями обычно настраиваются с централизованным контролем идентификации и доступа, а не для каждой отдельной учетной записи. AWS предлагает сервис IAM Identity Center (ранее — Single Sign On) для централизованного управления доступом, разрешениями и доступом к аккаунтам AWS.

Identity Center поддерживает перенос существующих пользователей и групп организации в среду AWS. В документации IAM это называется использованием внешнего поставщика удостоверений (IdP) для объединения пользователей и групп с помощью Identity Center.

В Identity Center разрешения управляются с помощью наборов разрешений, которые, в свою очередь, представляют собой набор политик IAM. После того как для пользователя или группы будет назначен набор разрешений для учетной записи, Identity Center автоматически создаст роли IAM в учетной записи. Политики роли настраиваются из набора разрешений. Кроме того, для этой роли настроена политика доверия, которая позволяет предположить роль только в том случае, если пользователь прошел проверку подлинности поставщиком федеративных удостоверений.

Управление контролем доступа и разрешениями AWS всегда должно соответствовать концепции разрешений с наименьшими привилегиями для любой задачи для пользователей среды. Среды AWS обычно настраиваются для управления доступом на основе ролей, где роли имеют достаточные настройки разрешений для выполнения необходимых задач. Роли в AWS Identity Center обычно сопоставляются с федеративными группами из IdP. В зависимости от требований к степени детализации это может привести к соотношению 1:1 между группами IdP и сочетанием учетной записи AWS и набора разрешений.

Давайте рассмотрим практический пример.

Доступ к аккаунту AWS «app1-prod» должен осуществляться операторами аккаунта с использованием трех разных разрешений в зависимости от выполняемых задач.

Для повседневных регулярных операций большинство задач должны быть доступны только для чтения и подпадать под категорию видимости, мониторинга и наблюдения. Можно использовать предоставленную AWS управляемую AWS политику ReadOnlyAccess, и разрешения, определенные политиками, выполнят свою работу.

Для экстренных изменений среды и управления службой IAM в рамках учетной записи можно использовать политики из управляемой AWS политики AdministratorAccess.

Для любых других задач, требующих интерактивных изменений разрешений среды, определенных политиками из управляемого AWS PowerUserAccess, можно использовать политику.

В примере сценария IdP должен содержать три группы, предоставляя членам группы доступ к учетной записи AWS app1-prod с тремя наборами разрешений ReadOnlyAccess, AdministratorAccess и < code>PowerUserAccess.

Создание групп в IdP и назначение наборов разрешений учетным записям AWS для группы должны быть выполнены до того, как пользователи из групп смогут войти в учетную запись AWS.

Многие организации автоматизировали создание учетных записей AWS, поскольку новые приложения и рабочие нагрузки необходимо часто размещать. Нередко рабочая нагрузка развертывается в нескольких аккаунтах AWS, для каждого из которых требуется настройка IAM Identity Center перед передачей пользователям аккаунтов.

AWS не предоставляет готового решения для автоматизации создания групп и наборов разрешений для назначений учетных записей AWS, поэтому часто этот шаг выполняется вручную, что приводит к задержкам и дополнительной работе.

Решение

В этом сообщении описывается решение, использующее бессерверные ресурсы AWS для федеративной группы AWS Identity Center для автоматизации назначения учетных записей и наборов разрешений. Это решение предполагает, что имя группы соответствует обычному шаблону, который содержит целевую учетную запись AWS и набор разрешений как часть имени группы.

В качестве примера мы будем использовать имя группы aws_accountname_a. Реализация решения построена со следующими предположениями, которые кодирует имя группы:

  • Постоянный префикс групп для доступа к аккаунту AWS (строка aws_)
  • Имя учетной записи, для которой должна быть назначена группа (строка имя_учетной_записи — любые непробельные символы между символами _ всего имени группы)
  • Короткое имя набора разрешений (строка _p для ссылки на набор разрешений AWSPowerUserAccess), которое группа должна быть назначена учетной записи.

Предпосылки

  • У пользователя есть аккаунт AWS, в котором включен IAM Identity Center.
  • У пользователя IAM Identity Center настроен на использование внешнего поставщика удостоверений для объединения пользователей и групп.
  • Внешний поставщик удостоверений настроен и настроен для автоматической подготовки, см. раздел ссылок в этом сообщении.
  • У пользователя есть необходимые разрешения для управления ресурсами в учетной записи (с помощью CloudFormation или других методов).

Архитектура высокого уровня

На следующей диаграмме показана высокоуровневая архитектура решения.

Image Description

Рабочий процесс решения

Основной рабочий процесс решения выделен в следующих шагах:

  • Внешний поставщик удостоверений создает группу в AWS IAM Identity Center, используя протокол SCIM между поставщиком удостоверений и AWS IAM Identity Center, инициируя событие AWS CloudTrail.
  • Правило AWS EventBridge отслеживает событие, возникающее при подготовке, и запускает функцию AWS Lambda, передавая ей сведения о событии.
  • Эта функция расшифровывает имя группы из сведений о событии, поскольку оно содержит нужное имя учетной записи и желаемое имя набора разрешений, которое будет назначено группе, и выполняет назначение в IAM Identity Center API.
  • Процесс регистрируется в группе журналов AWS CloudWatch для целей аудита и отладки.
  • В случае сбоя будет отправлено электронное письмо.

Реализация

Решение реализовано в AWS CloudFormation, но должно быть достаточно переносимым, чтобы его можно было реализовать в других платформах Infrastructure as Code, таких как Terraform и т. д.

Основными компонентами являются правило EventBridge для прослушивания желаемого события, функция Lambda и ее разрешения IAM, а также компонент SNS для отправки электронных писем в случае сбоя.

Шаблон CloudFormation

Решение поставляется в виде единого шаблона CloudFormation, который довольно легко развернуть в аккаунте AWS с включенным Identity Center.

Description: AWS SSO Automation Components

Parameters:
  ManagedResourcePrefix:
    Type: String
  InstanceArn:
    Type: String
  SMTPNotifyAddress:
    Type: String
  TopicName:
    Type: String
    Default: "AssigmentTopic"

Resources:
  NewSSOGroupEventRule:
    Type: AWS::Events::Rule
    Properties:
      Description: Trigger for when a new SSO Group is propagated from external IdP via SCIM
      EventPattern:
        source:
        - aws.sso-directory
        detail-type:
        - AWS API Call via CloudTrail
        detail:
          eventSource:
          - sso-directory.amazonaws.com
          eventName:
          - CreateGroup
      Targets:
        - Arn: !GetAtt SsoAssignGroupsFunction.Arn
          Id: sso-assign-group-function
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
      - PolicyName: SSOandOrgPermissions
        PolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Action:
            - sso-directory:Describe*
            - sso-directory:Get*
            - sso-directory:List*
            - sso-directory:Describe*
            - sso-directory:Search*
            - sso:Describe*
            - sso:Get*
            - sso:List*
            - sso:CreateAccountAssignment
            - sso:ProvisionPermissionSet
            - identitystore:List*
            - identitystore:Describe*
            - organizations:ListAccounts
            - sns:Publish
            Resource: '*'
  AssignmentTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: !Ref SMTPNotifyAddress
          Protocol: "email"
      TopicName: !Ref TopicName
  SsoAssignGroupsFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          """ This function is intended to be a standalone Lambda function for SSO Permission Set
          to AWS Account Assignment """

          import re
          import os
          import logging
          import json
          from time import sleep
          import boto3
          import traceback

          logger = logging.getLogger()
          logger.setLevel(logging.DEBUG)


          # This function assumes that groups are named like aws_<account-name>_<a|r|p>
          # see the documentation of this function for details.
          CONST_PREFIX = "aws"
          g_account_pattern = rf"^{CONST_PREFIX}_(S*)_(S*)"

          PSET_NAME_MAPPING_DICT = {
              "r": "AWSReadOnlyAccess",
              "a": "AWSAdministratorAccess",
              "p": "AWSPowerUserAccess",
          }

          sso_admin_client = boto3.client("sso-admin")
          org_client = boto3.client("organizations")
          sns_resource = boto3.resource("sns")


          def list_permission_sets(sso_instance_arn) -> dict:
              """Returns a dictionary of permissionssets for a given SSO Instance."""
              perm_set_dict = {}
              response = sso_admin_client.list_permission_sets(InstanceArn=sso_instance_arn)
              results = response["PermissionSets"]
              while "NextToken" in response:
                  response = sso_admin_client.list_permission_sets(
                      InstanceArn=sso_instance_arn, NextToken=response["NextToken"]
                  )
                  results.extend(response["PermissionSets"])

              for permission_set in results:
                  perm_description = sso_admin_client.describe_permission_set(
                      InstanceArn=sso_instance_arn, PermissionSetArn=permission_set
                  )
                  perm_set_dict[perm_description["PermissionSet"]["Name"]] = permission_set
              return perm_set_dict


          def list_aws_accounts() -> list:
              """Returns a list of account dictionaries containing  name id of each account"""
              account_list = []
              paginator = org_client.get_paginator("list_accounts")
              page_iterator = paginator.paginate()

              for page in page_iterator:
                  for acct in page["Accounts"]:
                      # only add active accounts
                      if acct["Status"] == "ACTIVE":
                          data = {"name": acct["Name"], "id": acct["Id"]}
                          account_list.append(data)
              #logger.debug("List of accounts: %s", account_list)
              return account_list


          def lambda_handler(event, context):
              """Main method for Lambda function, will handle the IAM Identity Center permission set to IAM Identity Center directiry group and AWS account mapping"""
              logger.debug("Invoked with event: %s", event)
              try:
                  group_display_name = event["detail"]["responseElements"]["group"]["displayName"]
                  if group_display_name == "":
                      logger.debug(
                          "Recieved SCIM CreateGroup event for roup name '%s'", group_display_name
                      )
                      raise Exception("Event did not contain the group display name property")

                  result = re.search(g_account_pattern, group_display_name)
                  print(result)
                  if not result:
                      logger.error(
                          "Security group: '%s' does not matching naming convention for account assignment. REGEX retourned matches: %s",
                          group_display_name, result,
                      )
                      raise Exception(
                          "Security group does not match convention for account assignment automation"
                      )

                  account_name = result.group(1)
                  short_name_pset = result.group(2)  # "a" "p" or "r"

                  if short_name_pset not in PSET_NAME_MAPPING_DICT:
                      logger.error(
                          "Short name '%s' for permission set is not known", short_name_pset
                      )
                      raise Exception("Short name for permission set is not known")

                  permission_set_name = PSET_NAME_MAPPING_DICT[short_name_pset]
                  logger.debug(
                      "Searching for account '%s' and permission set '%s'",
                      account_name,
                      permission_set_name,
                  )

                  accounts = list_aws_accounts()

                  instance_arn = os.getenv("INSTANCE_ARN")
                  logger.info("IAM Identity Center Instance ARN is configured '%s'", instance_arn)

                  if instance_arn is None:
                      raise Exception("No IAM Idenity Center Instance ARN is configured.")

                  logger.debug("Found IAM Idenity Center instance arn '%s'", instance_arn)
                  permission_sets = list_permission_sets(instance_arn)

                  logger.debug("Permission sets found '%s'", permission_sets)

                  account_id, permission_set_arn, account_name = None, None, None
                  for account in accounts:
                      logger.info("Id of desired account is '%s' ", account["id"])
                      account_id = account.get("id")
                      account_name = account.get("name")

                  if account_id is None:
                      logger.error("Can't find desired account '%s'", account_name)
                      raise Exception("Clould not find account")

                  for name, arn in permission_sets.items():
                      if name == permission_set_name:
                          logger.info("ARN of desired permission set is '%s'", arn)
                          permission_set_arn = arn

                  if permission_set_arn is None:
                      logger.error("Can't find desired permission set %s", permission_set_arn)
                      raise Exception("Can't find desired permission set")

                  principal_id = event["detail"]["responseElements"]["group"]["groupId"]
                  logger.info("PrincipalId of the group is: %s", principal_id)
                  if principal_id is None:
                      logger.error("Could not retrieve the princiapal id of group %s", principal_id)
                      raise Exception("Could not retrieve the princial id of the group")

                  request = {
                      "InstanceArn": instance_arn,
                      "TargetId": account_id,
                      "TargetType": "AWS_ACCOUNT",
                      "PermissionSetArn": permission_set_arn,
                      "PrincipalType": "GROUP",
                      "PrincipalId": principal_id,  #AWS IAM Identity Center group identifier
                  }

                  cracct_response = sso_admin_client.create_account_assignment(**request)
                  cracct_request_id = cracct_response["AccountAssignmentCreationStatus"][
                      "RequestId"
                  ]

                  for tries in range(5):
                      # The docs explain the following valid status states "IN_PROGRESS"|"FAILED"|"SUCCEEDED"
                      ps_prov_set_status = (
                          sso_admin_client.describe_account_assignment_creation_status(
                              InstanceArn=instance_arn,
                              AccountAssignmentCreationRequestId=cracct_request_id,
                          )
                      )
                      logger.info("Assignment attempt %s", tries)
                      status = ps_prov_set_status["AccountAssignmentCreationStatus"]["Status"]

                      if status == "IN_PROGRESS":
                          logger.info("Assignment is in progress")
                          logger.info("Sleeping for 5 seconds")
                          sleep(5.0)
                          continue
                      if status == "FAILED":
                          logger.error("Account assigned has failed")
                          raise Exception("Account assignment has failed")
                          return

                  logger.info(
                      "SUCCESS: Security Group: %s assigned to Account: %s with permission set: %s",
                      group_display_name,
                      account_name,
                      permission_set_name,
                  )
              except Exception as err:
                  message = {"Exception Details": str(err),
                                    "event": event}
                  return message
                  sns_topic = os.getenv("SNS_TOPIC")
                  topic = sns_resource.Topic(sns_topic)
                  topic.publish(
                      Message=json.dumps(message),
                      Subject="Account Association Operation Failed: SSO Automation",
                  )
                  logger.info("Error notification sent via SNS")

      Handler: 'assign_group_to_account.lambda_handler'
      Role: !GetAtt ExecutionRole.Arn
      Runtime: 'python3.9'
      MemorySize: 128
      Timeout: 900
      Environment:
        Variables:
          SNS_TOPIC: !Ref AssignmentTopic
          INSTANCE_ARN: !Ref InstanceArn
  EventsFunctionPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt SsoAssignGroupsFunction.Arn
      Principal: events.amazonaws.com
      SourceArn: !GetAtt NewSSOGroupEventRule.Arn

`

# References
See https://docs.aws.amazon.com/singlesignon/latest/userguide/provision-automatically.html for details on how to set a external Identity Provider configuration for provisioning.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE