Skip to content

fivexl/terraform-aws-sso-elevator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FivexL

Terraform module for implementing temporary elevated access via AWS IAM Identity Center (Successor to AWS Single Sign-On) and Slack

Introduction

Currently, AWS IAM Identity Center does not support the temporary assignment of permission sets to users. As a result, teams using AWS IAM Identity Center are forced to either create highly restricted permission sets or rely on AWS IAM role chaining. Both approaches have significant drawbacks and result in an overly complex security model. The desired solution is one where AWS operators are granted access only when necessary and for the exact duration needed, with a default state of no access or read-only access.

The terraform-aws-sso-elevator module addresses this issue by allowing the implementation of temporary elevated access to AWS accounts while avoiding permanently assigned permission sets, thereby achieving the principle of least privilege access.

For more information on temporary elevated access for AWS and the AWS-provided solution, visit Managing temporary elevated access to your AWS environment.

The key difference between the terraform-aws-sso-elevator module and the option described in the blog post above is that the module enables requesting access elevation via a Slack form. We hope that this implementation may inspire AWS to incorporate native support for temporary access elevation in AWS IAM Identity Center.

AWS announced that Customers of AWS IAM Identity Center (successor to AWS Single Sign-On) can use CyberArk Secure Cloud Access, Ermetic, and Okta Access Requests for temporary elevated access. So if you are already using one of those vendors we recommend checking their offering first.

Watch demo Demo

Functionality

sequenceDiagram
    Requester->>Slack: submits form in Slack - CMD+K, search access or /access command
    Slack->>AWS Lambda - Access Requester: sends request to access-requester
    AWS Lambda - Access Requester->>Slack: sends a message to Slack channel with approve/deny buttons and tags approvers
    Approver->>Slack: pressed approve button in Slack message
    Slack->>AWS Lambda - Access Requester: Send approved request to access-requester
    AWS Lambda - Access Requester->>AWS IAM Identity Center(SSO): creates user-level permission set assignment based on approved request
    AWS Lambda - Access Requester->>AWS EventBridge: creates revocation schedule
    AWS Lambda - Access Requester->>AWS S3: logs audit record
    AWS EventBridge->>AWS Lambda - Access Revoker: sends revocation event when times come
    AWS Lambda - Access Revoker->>AWS IAM Identity Center(SSO): revokes user-level permission set assignment
    AWS Lambda - Access Revoker->>AWS S3: logs audit record
    AWS Lambda - Access Revoker->>Slack:  send notification about revocation
Loading

The module deploys two AWS Lambda functions: access-requester and access-revoker. The access-requester handles requests from Slack, creating user-level permission set assignments and an Amazon EventBridge trigger that activates the access-revoker Lambda when it is time to revoke access. The access-revoker revokes user access when triggered by EventBridge and also runs daily to revoke any user-level permission set assignments without an associated EventBridge trigger. Group-level permission sets are not affected.

For auditing purposes, information about all access grants and revocations is stored in S3. See documentation here to find out how to configure AWS Athena to query audit logs.

Additionally, the Access-Revoker continuously reconciles the revocation schedule with all user-level permission set assignments and issues warnings if it detects assignments without a revocation schedule (presumably created by someone manually). By default, the Access-Revoker will automatically revoke all unknown user-level permission set assignments daily. However, you can configure it to operate more or less frequently.

Group Assignments Mode

Starting from version 2.0, Terraform AWS SSO Elevator introduces support for group access. SSO elevator now can add users to a groups, to do so, you will need to use /group-access command, which, instead of showing the form for account assignments, will present a Slack form where the user can select a group they want access to, specify a reason, and define the duration for which access is required.

The basic logic for access, configuration, and Slack integration remains the same as before. To enable the new Group Assignments Mode, you need to provide the module with a new group_config Terraform variable:

group_config = [
    {              
      "Resource" : ["99999999-8888-7777-6666-555555555555"], #ManagementAccountAdmins
      "Approvers" : [
        "[email protected]"
      ]
      "ApprovalIsNotRequired": true
    },
    {              
      "Resource" : ["11111111-2222-3333-4444-555555555555"], #prod read only
      "Approvers" : [
        "[email protected]"
      ]
      "AllowSelfApproval" : true,
    },
    {
      "Resource" : ["44445555-3333-2222-1111-555557777777"], #ProdAdminAccess
      "Approvers" : [
        "[email protected]"
      ]
    },
]

There are two key differences compared to the standard Elevator configuration:

  • ResourceType is not required for group access configurations.
  • In the Resource field, you must provide group IDs instead of account IDs.

The Elevator will only work with groups specified in the configuration.

If you were using Terraform AWS SSO Elevator before version 2.0.0, you need to update your Slack app manifest by adding a new shortcut to enable this functionality: { "name": "group-access", "type": "global", "callback_id": "request_for_group_membership", "description": "Request access to SSO Group" } To disable this functionality, simply remove the shortcut from the manifest.

Important Considerations and Assumptions

SSO elevator assumes that your Slack user email will match SSO user id otherwise it won't be able to match Slack user sending request to an AWS SSO user.

When onboarding your organization, be aware that the access-revoker will revoke all user-level Permission Set assignments in the AWS accounts you specified in the module configuration. If you specify Accounts: '*' in any of rules, it will remove user-level assignments from all accounts. Therefore, if you want to maintain some permanent SSO assignments (e.g., read-only in production and admin in development or test accounts), you should use group-level assignments. It is advisable to ensure your AWS admin has the necessary access level to your AWS SSO management account through group-level assignments so that you can experiment with the module's configuration.

Deployment and Usage

Note on dependencies

Lambdas are built using Python 3.10 and rely on Poetry for package management and dependency resolution. To run Terraform, both Python 3.10 and Poetry need to be installed on your system. If these tools are not available, you can opt to package the Lambdas using Docker by providing the appropriate flag to the module. We do recommend using Docker build where possible to avoid misconfigurations or missing packages.

The deployment process is divided into two main parts: deploying the Terraform module, which sets up the necessary infrastructure and resources for the Lambdas to function, and creating a Slack App, which will be the interface through which users can interact with the Lambdas. Detailed instructions on how to perform both of these steps, along with the Slack App manifest, can be found below.

Updated Build Process

In 1.4.0 release, the ability to build zip files locally without Docker has been removed due to issues with Python environments and version mismatches. Now, GitHub CI will pre-build the requester and revoker lambda Docker images and push them to FivexL's private ECR. Users can use these pre-built Docker images to build lambdas.

ECR is private for the following reasons:

  • AWS Lambda can't use any other source of images except ECR.
  • AWS Lambda can't use public ECR.
  • AWS Lambda doesn't support pulling container images from Amazon ECR using a pull-through cache rule (so we can't create a private repo from the user's side to pull images from the GHCR, for example).

Images and repositories are replicated in every region that AWS SSO supports except these:

- ap_east_1
- eu_south_1
- ap_southeast_3
- af_south_1
- me_south_1
- il_central_1
- me_central_1
- eu_south_2
- ap_south_2
- eu_central_2
- ap_southeast_4
- ca_west_1
- us_gov_east_1
- us_gov_west_1

Those regions are not enabled by default. If you need to use a region that is not supported by the module, please let us know by creating an issue, and we will add support for it.

Conclusion: Now there are only two ways to build an SSO elevator:

Using pre-created images pulled from ECR (Default) Using Docker build to build images locally (provide the variable use_pre_created_image = false) There is also an option to host ECR yourself by providing the following variables:

ecr_repo_name = "example_repo_name"
ecr_owner_account_id = "<example_account_id>"

API

To address the lambda-1 SecurityHub control alert triggered by the default creation of a FunctionURLAllowPublicAccess resource-based policy for lambda, in 1.4.0 release module will eventually migrate to the usage of API Gateway by default. You still can use lambda URL to seamlessly migrate to the API Gateway url, but it is deprecated and will be removed in future releases. You can use the following variables to control the behavior:

create_api_gateway = true # This will create an API Gateway for the requester lambda
create_lambda_url = false # This will delete lambda url

To fix the Security Hub issue when migrating to API Gateway, manually delete the FunctionURLAllowPublicAccess policy statement in the AWS Console. After updating the module, you can find the API URL in the output of the module. Please don't forget to update the Slack App manifest with the new URL.

Module configuration options and automatic approval

Configuration structure

The configuration is a list of dictionaries, where each dictionary represents a single configuration rule.

Each configuration rule specifies which resource(s) the rule applies to, which permission set(s) are being requested, who the approvers are, and any additional options for approving the request.

The fields in the configuration dictionary are:

  • ResourceType: This field specifies the type of resource being requested, such as "Account." As of now, the only supported value is "Account."
  • Resource: This field defines the specific resource(s) being requested. It accepts either a single string or a list of strings. Setting this field to "*" allows the rule to match all resources associated with the specified ResourceType.
  • PermissionSet: Here, you indicate the permission set(s) being requested. This can be either a single string or a list of strings. If set to "*", the rule matches all permission sets available for the defined Resource and ResourceType.
  • Approvers: This field lists the potential approvers for the request. It accepts either a single string or a list of strings representing different approvers.
  • AllowSelfApproval: This field can be a boolean, indicating whether the requester, if present in the Approvers list, is permitted to approve their own request. It defaults to None.
  • ApprovalIsNotRequired: This field can also be a boolean, signifying whether the approval can be granted automatically, bypassing the approvers entirely. The default value is None.

Explicit Deny

In the system, an explicit denial in any statement overrides any approvals. For instance, if one statement designates an individual as an approver for all accounts, but another statement specifies that the same individual is not allowed to self-approve or to bypass the approval process for a particular account and permission set (by setting "allow_self_approval" and "approval_is_not_required" to False), then that individual will not be able to approve requests for that specific account, thereby enforcing a stricter control.

Automatic Approval

Requests will be approved automatically if either of the following conditions are met:

  • AllowSelfApproval is set to true and the requester is in the Approvers list.
  • ApprovalIsNotRequired is set to true.

Aggregation of Rules

The approval decision and final list of reviewers will be calculated dynamically based on the aggregate of all rules. If you have a rule that specifies that someone is an approver for all accounts, then that person will be automatically added to all requests, even if there are more detailed rules for specific accounts or permission sets.

Single Approver

If there is only one approver and AllowSelfApproval is not set to true, nobody will be able to approve the request.

Diagram of processing a request:

Diagram of processing a request

Terraform deployment example

data "aws_ssoadmin_instances" "this" {}

# You will have to create /sso-elevator/slack-signing-secret AWS SSM Parameter
# and store Slack app signing secret there, if you have not created app yet then
# you can leave a dummy value there and update it after Slack app is ready
data "aws_ssm_parameter" "sso_elevator_slack_signing_secret" {
  name = "/sso-elevator/slack-signing-secret"
}

# You will have to create /sso-elevator/slack-bot-token AWS SSM Parameter
# and store Slack bot token there, if you have not created app yet then
# you can leave a dummy value there and update it after Slack app is ready
data "aws_ssm_parameter" "sso_elevator_slack_bot_token" {
  name = "/sso-elevator/slack-bot-token"
}

module "aws_sso_elevator" {
  source                           = "github.com/fivexl/terraform-aws-sso-elevator.git"
  aws_sns_topic_subscription_email = "[email protected]"

  slack_signing_secret = data.aws_ssm_parameter.sso_elevator_slack_signing_secret.value
  slack_bot_token      = data.aws_ssm_parameter.sso_elevator_slack_bot_token.value
  slack_channel_id     = "***********"
  schedule_expression  = "cron(0 23 * * ? *)" # revoke access schedule expression
  schedule_expression_for_check_on_inconsistency = "rate(1 hour)" 
  build_in_docker = true
  revoker_post_update_to_slack = true

  # The initial wait time before the first re-notification to the approver is sent.
  approver_renotification_initial_wait_time = 15
  # The multiplier applied to the wait time for each subsequent notification sent to the approver.
  # Default is 2, which means the wait time will double for each attempt.
  approver_renotification_backoff_multiplier = 2

  sso_instance_arn = one(data.aws_ssoadmin_instances.this.arns)

  # If you wish to use your own S3 bucket for audit_entry logs, 
  # specify its name here:
  s3_name_of_the_existing_bucket = "your-s3-bucket-name"

  # If you do not provide a value for s3_name_of_the_existing_bucket, 
  # the module will create a new bucket with the default name 'sso-elevator-audit-entry':
  s3_bucket_name_for_audit_entry = "fivexl-sso-elevator"

  # The default partition prefix is "logs/":
  s3_bucket_partition_prefix     = "some_prefix/"

  # MFA delete setting for the S3 bucket:
  s3_mfa_delete                  = false

  # Object lock setting for the S3 bucket:
  s3_object_lock                 = true

  # The default object lock configuration is as follows:
  # {
  #  rule = {
  #   default_retention = {
  #      mode  = "GOVERNANCE"
  #      years = 2
  #    }
  #  }
  #}
  # You can specify a different configuration here:
  s3_object_lock_configuration = {
    rule = {
      default_retention = {
        mode  = "GOVERNANCE"
        years = 1
      }
    }
  }

  # Here, you can specify the target_bucket and prefix for access logs of the sso_elevator bucket.
  # If s3_logging is not specified, logs will not be written:
  s3_logging = {
    target_bucket = "some_access_logging_bucket"
    target_prefix = "some_prefix_for_access_logs"
  }

  config = [
    # This could be a config for dev/stage account where developers can self-serve
    # permissions
    # Allows Bob and Alice to approve requests for all
    # PermissionSets in accounts dev_account_id and stage_account_id as
    # well as approve its own requests
    # You have to specify at AllowSelfApproval: true or specify two approvers
    # so you do not lock out approver
    {
      "ResourceType" : "Account",
      "Resource" : ["dev_account_id", "stage_account_id"],
      "PermissionSet" : "*",
      "Approvers" : ["[email protected]", "[email protected]"],
      "AllowSelfApproval" : true,
    },
    # This could be an option for a financial person
    # allows self approval for Billing PermissionSet
    # for account_id for user [email protected]
    {
      "ResourceType" : "Account",
      "Resource" : "account_id",
      "PermissionSet" : "Billing",
      "Approvers" : "[email protected]",
      "AllowSelfApproval" : true,
    },
    # Your typical CTO - can approve all accounts and all permissions
    # as well as his/hers own requests to avoid lock out
    # Careful withi Resource * since it will cause revocation of all
    # non-module-created user-level permission set assignments in all
    # accounts, add this one later when you are done with single account
    # testing
    {
      "ResourceType" : "Account",
      "Resource" : "*",
      "PermissionSet" : "*",
      "Approvers" : "[email protected]",
      "AllowSelfApproval" : true,
    },
    # Read only config for production accounts so developers
    # can check prod when needed
    {
      "ResourceType" : "Account",
      "Resource" : ["prod_account_id", "prod_account_id2"],
      "PermissionSet" : "ReadOnly",
      "AllowSelfApproval" : true,
    },
    # Prod access
    {
      "ResourceType" : "Account",
      "Resource" : ["prod_account_id", "prod_account_id2"],
      "PermissionSet" : "AdministratorAccess",
      "Approvers" : ["[email protected]", "[email protected]"],
      "ApprovalIsNotRequired" : false,
      "AllowSelfApproval" : false,
    },
    # example of list being used for permissions sets
    {
      "ResourceType" : "Account",
      "Resource" : "account_id",
      "PermissionSet" : ["ReadOnlyPlus", "AdministratorAccess"],
      "Approvers" : ["[email protected]"], 
      "AllowSelfApproval" : true,
    },

  ]
group_config = [
    {              
      "Resource" : ["99999999-8888-7777-6666-555555555555"], #ManagementAccountAdmins
      "Approvers" : [
        "[email protected]"
      ]
      "ApprovalIsNotRequired": true
    },
    {              
      "Resource" : ["11111111-2222-3333-4444-555555555555"], #prod read only
      "Approvers" : [
        "[email protected]"
      ]
      "AllowSelfApproval" : true,
    },
    {
      "Resource" : ["44445555-3333-2222-1111-555557777777"], #ProdAdminAccess
      "Approvers" : [
        "[email protected]"
      ]
    },
]
}

output "aws_sso_elevator_lambda_function_url" {
  value = module.aws_sso_elevator.lambda_function_url
}

Slack App creation

  1. Go to https://api.slack.com/
  2. Click create an app
  3. Click From an app manifest
  4. Select workspace, click next
  5. Choose yaml for app manifest format
  6. Update lambda url (from output aws_sso_elevator_lambda_function_url) to request_url field and paste the following into the text box:
display_information:
  name: AWS SSO Access Elevator
  description: Slack bot to temporary assign AWS SSO Permission set to a user
features:
  bot_user:
    display_name: AWS SSO Access Elevator
    always_online: false
  shortcuts:
    - name: access
      type: global
      callback_id: request_for_access
      description: Request access to Permission Set in AWS Account
    - name: group-access # Delete this shortcut if you want to prohibit access to the Group Assignments Mode
      type: global
      callback_id: request_for_group_membership
      description: Request access to SSO Group
oauth_config:
  scopes:
    bot:
      # 'commands': This permission adds shortcuts and/or slash commands that people can use.
      - commands
      # 'chat:write': This permission is required for the app to post messages to Slack.
      - chat:write
      # 'users:read' and 'users:read.email': These permissions are required for the app to find the user's email address, which is necessary for  creating AWS account assignments and including user mentions in requests.
      - users:read.email
      - users:read
      # 'channels:history': This permission is needed for the app to find old messages in order to handle "discard button" events.
      - channels:history
settings:
  interactivity:
    is_enabled: true
    request_url: <LAMBDA URL GOES HERE - CHECK LAMBDA CONFIGURATION IN AWS CONSOLE OR GET IT FORM TERRAFORM OUTPUT> 
  org_deploy_enabled: false
  socket_mode_enabled: false
  token_rotation_enabled: false
  1. Check permissions and click create
  2. Click install to workspace
  3. Copy Signing Secret # for slack_signing_secret module input
  4. Copy Bot User OAuth Token # for slack_bot_token module input

Terraform docs

Requirements

Name Version
terraform ~> 1.0
aws >= 4.64
external >= 1.0
local >= 1.0
null >= 2.0
random >= 3.0

Providers

Name Version
aws 5.65.0
random 3.6.2

Modules

Name Source Version
access_requester_slack_handler terraform-aws-modules/lambda/aws 4.16.0
access_revoker terraform-aws-modules/lambda/aws 4.16.0
audit_bucket fivexl/account-baseline/aws//modules/s3_baseline 1.3.2
http_api terraform-aws-modules/apigateway-v2/aws 5.0.0
sso_elevator_dependencies terraform-aws-modules/lambda/aws 4.16.0

Resources

Name Type
aws_cloudwatch_event_rule.sso_elevator_check_on_inconsistency resource
aws_cloudwatch_event_rule.sso_elevator_scheduled_revocation resource
aws_cloudwatch_event_target.check_inconsistency resource
aws_cloudwatch_event_target.sso_elevator_scheduled_revocation resource
aws_iam_role.eventbridge_role resource
aws_iam_role_policy.eventbridge_policy resource
aws_lambda_permission.eventbridge resource
aws_lambda_permission.url resource
aws_scheduler_schedule_group.one_time_schedule_group resource
aws_sns_topic.dlq resource
aws_sns_topic_subscription.dlq resource
random_string.random resource
aws_caller_identity.current data source
aws_iam_policy_document.revoker data source
aws_iam_policy_document.slack_handler data source
aws_region.current data source
aws_ssoadmin_instances.all data source

Inputs

Name Description Type Default Required
approver_renotification_backoff_multiplier The multiplier applied to the wait time for each subsequent notification sent to the approver. Default is 2, which means the wait time will double for each attempt. number 2 no
approver_renotification_initial_wait_time The initial wait time before the first re-notification to the approver is sent. This is measured in minutes. If set to 0, no re-notifications will be sent. number 15 no
aws_sns_topic_subscription_email value for the email address to subscribe to the SNS topic string "" no
config value for the SSO Elevator config any [] no
create_api_gateway If true, module will create & configure API Gateway for the Lambda function bool true no
create_lambda_url If true, the Lambda function will continue to use the Lambda URL, which will be deprecated in the future
If false, Lambda url will be deleted.
bool true no
ecr_owner_account_id In what account is the ECR repository located. string "222341826240" no
ecr_repo_name The name of the ECR repository. string "aws-sso-elevator" no
event_brige_check_on_inconsistency_rule_name value for the event bridge check on inconsistency rule name string "sso-elevator-check-on-inconsistency" no
event_brige_scheduled_revocation_rule_name value for the event bridge scheduled revocation rule name string "sso-elevator-scheduled-revocation" no
group_config value for the SSO Elevator group config any [] no
log_level value for the log level string "INFO" no
logs_retention_in_days The number of days you want to retain log events in the log group for both Lambda functions and API Gateway. number 365 no
max_permissions_duration_time Maximum duration of the permissions granted by the Elevator in hours. number 24 no
request_expiration_hours After how many hours should the request expire? If set to 0, the request will never expire. number 8 no
requester_lambda_name value for the requester lambda name string "access-requester" no
revoker_lambda_name value for the revoker lambda name string "access-revoker" no
revoker_post_update_to_slack Should revoker send a confirmation of the revocation to Slack? bool true no
s3_bucket_name_for_audit_entry Unique name of the S3 bucket string "sso-elevator-audit-entry" no
s3_bucket_partition_prefix The prefix for the S3 audit bucket object partitions.
Don't use slashes (/) in the prefix, as it will be added automatically, e.g. "logs" will be transformed to "logs/".
If you want to use the root of the bucket, leave this empty.
string "logs" no
s3_logging Map containing access bucket logging configuration. map(string) {} no
s3_mfa_delete Whether to enable MFA delete for the S3 bucket bool false no
s3_name_of_the_existing_bucket Specify the name of an existing S3 bucket to use. If not provided, a new bucket will be created. string "" no
s3_object_lock Enable object lock bool false no
s3_object_lock_configuration Object lock configuration any
{
"rule": {
"default_retention": {
"mode": "GOVERNANCE",
"years": 2
}
}
}
no
schedule_expression recovation schedule expression (will revoke all user-level assignments unknown to the Elevator) string "cron(0 23 * * ? *)" no
schedule_expression_for_check_on_inconsistency how often revoker should check for inconsistency (warn if found unknown user-level assignments) string "rate(2 hours)" no
schedule_group_name value for the schedule group name string "sso-elevator-scheduled-revocation" no
schedule_role_name value for the schedule role name string "sso-elevator-event-bridge-role" no
slack_bot_token value for the Slack bot token string n/a yes
slack_channel_id value for the Slack channel ID string n/a yes
slack_signing_secret value for the Slack signing secret string n/a yes
sso_instance_arn value for the SSO instance ARN string "" no
tags A map of tags to assign to resources. map(string) {} no
use_pre_created_image If true, the image will be pulled from the ECR repository. If false, the image will be built using Docker from the source code. bool true no

Outputs

Name Description
lambda_function_url value for the access_requester lambda function URL
requester_api_endpoint_url The full URL to invoke the API. Pass this URL into the Slack App manifest as the Request URL.
sso_elevator_bucket_id The name of the SSO elevator bucket.

More info

Development

Post review

  • Post review url
  • ToC generated with this