Access management within your cloud environment can be one of the most demanding challenges within AWS, with the constant addition of new resources and services that your business may wish to utilize. AWS Service Control Policies (SCPs) can be a powerful tool as part of a layered approach to enforcing least privilege.
Whether you are leveraging generic AWS-managed IAM policies for one account or creating custom policies to grant least privilege across hundreds of accounts, SCPs can be applied to your AWS Organization to define the maximum allowable privileges for any identity and to ensure specific conditions are met when a particular action is utilized.
As the name suggests, Service Control Policies allow you to define the list of AWS Services and IAM actions permissible within your AWS environments. If your organization is mandated to adhere to a regulatory standard or compliance requirements, entire AWS services can be implicitly or explicitly denied to ensure your policies are not violated, regardless of the IAM policies attached to an identity. Beyond regulatory or compliance mandates, SCPs are also often leveraged to “opt-in” to which AWS services can be consumed, restrict the AWS regions you allow within your accounts, and to ensure that specific actions which do not align with your security policies are disallowed.
AWS SCPs require the usage of AWS Organizations, specifically the ‘All Features’ feature set; it is not possible with only Consolidated Billing enabled.
SCPs can be applied to any of 3 levels within your AWS Organization:
There are two overarching ways to implement SCPs: Implicit Allow with Explicit Deny (Deny List), or Implicit Deny with Explicit Allow (Allow List). The default SCP applied to an AWS Organization grants FullAWSAccess and does not perform any restrictions. To remedy this and restrict access, you can either apply your custom Allow-List SCP and detach the FullAWSAccess policy, or retain the FullAWSAccess policy and apply a separate policy to deny specific services/actions.
The proper direction for your organization will depend on whether you are beholden to regulatory or compliance requirements, as well as your risk appetite.
Explicit Deny, Implicit Allow
FullAWSAccess + Deny Action
This is best leveraged only when you wish to disallow a limited subset of actions, and leave the majority of privilege management to your AWS IAM policies. One risk you are allowing your Users and Roles to leverage AWS Managed Policies, when a new service/action is added to that policy, your SCP may not automatically deny the new action.
FullAWSAccess Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
Deny List Policy Example:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDisallowedActions",
"Effect": "Deny",
"Action": [
"cloudtrail:DeleteTrail",
"cloudtrail:PutEventSelectors",
"cloudtrail:StopLogging",
"cloudtrail:UpdateTrail",
"config:DeleteConfigRule",
"config:DeleteConfigurationAggregator",
"config:DeleteConfigurationRecorder",
"config:DeleteDeliveryChannel",
"config:DeleteEvaluationResults",
"config:DeleteRetentionConfiguration",
"config:StopConfigurationRecorder",
"ec2:DeleteFlowLogs",
"guardduty:DisassociateFromMasterAccount",
"logs:DeleteLogGroup",
"logs:DeleteLogStream",
"organizations:LeaveOrganization"
],
"Resource": "*"
}
]
}
Explicit Allow, Implicit Deny
Create and apply a Service Control Policy that grants the specific actions you wish to permit, and ensure the FullAWSAccess policy is removed
Only the AWS Services and actions you specify will be permitted.
This pattern avoids consuming an additional SCP per Organization hierarchy level, allowing for 5 custom SCPs per level before reaching the service limit.
Allow List Example:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ExplicitAllow",
"Effect": "Allow",
"Action": [
"access-analyzer:*",
"account:*",
"backup:*",
"billing:*",
"billingconductor:*",
"budgets:*",
"cloudformation:*",
"cloudshell:*",
"cloudtrail:*",
"codebuild:*",
"codecommit:*",
"config:*",
"ec2:*",
"elasticloadbalancing:*",
"events:*",
"health:*",
"iam:*",
"kms:*",
"lambda:*",
"logs:*",
"organizations:*",
"pricing:*",
"s3:*",
"secretsmanager:*",
"servicecatalog:*",
"sns:*",
"sqs:*",
"sso-directory:*",
"sso:*",
"sts:*",
"support:*",
"sustainability:*",
"tax:*"
],
"Resource": "*"
}
]
}
Deny NotAction
If retaining the FullAWSAccess policy is desired, but you wish to ensure only actions you explicitly specify are permitted, a Deny policy paired with the NotAction element can meet your needs.
Deny NotAction example:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNotAction",
"Effect": "Deny",
"NotAction": [
"access-analyzer:*",
"account:*",
"backup:*",
"billing:*",
"billingconductor:*",
"budgets:*",
"cloudformation:*",
"cloudshell:*",
"cloudtrail:*",
"codebuild:*",
"codecommit:*",
"config:*",
"ec2:*",
"elasticloadbalancing:*",
"events:*",
"health:*",
"iam:*",
"kms:*",
"lambda:*",
"logs:*",
"organizations:*",
"pricing:*",
"s3:*",
"secretsmanager:*",
"servicecatalog:*",
"sns:*",
"sqs:*",
"sso-directory:*",
"sso:*",
"sts:*",
"support:*",
"sustainability:*",
"tax:*"
],
"Resource": "*"
}
]
}
As AWS Organizations themselves are rarely static, your footprint is likely to continue to increase both from an Account and an Organizational Unit perspective as you onboard additional teams, create additional workloads, and build out further segmentation and security boundaries within your environment.
To increase the manageability and success of your Service Control Policies, consider leveraging HashiCorp Terraform to define and provision them via Infrastructure as Code (IaC). When access is restricted to and implemented through your CI/CD pipeline, you can ensure that unauthorized changes are not made, and that your developers are empowered to understand the specific conditions that must be met when they are contributing infrastructure and resources to your AWS environments.
Example SCP attached at the Root level, defined in Terraform HCL:
data "aws_organizations_organization" "org" {}
data "aws_organizations_organizational_units" "ou" {
parent_id = data.aws_organizations_organization.org.roots[0].id
}
resource "aws_organizations_policy_attachment" "root_allow_list" {
policy_id = aws_organizations_policy.root_allow_list.id
target_id = data.aws_organizations_organization.org.roots[0].id
}
resource "aws_organizations_policy" "root_allow_list" {
name = "root_allow_list"
type = "SERVICE_CONTROL_POLICY"
content = data.aws_iam_policy_document.root_allow_list.json
}
data "aws_iam_policy_document" "root_allow_list" {
statement {
sid = "AllowedActions"
effect = "Allow"
resources = ["*"]
actions = [
"s3:*",
"ec2:*",
"iam:*".
]
}
}
When exploring SCPs, there are a few important limitations to be aware of and design compensating controls around.
When your Service Control Policy statement is an “Allow”, not a “Deny”, you are unable to utilize the following Policy elements:
This means that in order to permit an action with a specific condition or resource in place, or permit all actions other than a specific subset of actions, you must utilize a Deny statement and adjust your logic accordingly.
{
"Sid": "RestrictRegionExample",
"Resource": "*",
"Effect": "Deny",
"Action": ["ec2:RunInstances"],
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": [
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
]
}
}
}
{
"Sid": "DenyAllRootAccess",
"Effect": "Deny",
"Action": ["*"],
"Resource": "*",
"Condition": {
"StringLike": {
"aws:PrincipalArn": ["arn:aws:iam::*:root"]
}
}
}
{
"Sid": "IPRestrictDeleteBucket",
"Resource": "*",
"Effect": "Deny",
"Action": ["s3:DeleteBucket"],
"Condition": {
"NotIpAddress": {
"aws:SourceIp": ["1.2.3.4/32"]
}
}
}
{
"Sid": "EnforceGoldenImageSourceAccounts",
"Resource": "*",
"Effect": "Deny",
"Action": ["ec2:RunInstances"],
"Condition": {
"StringNotEquals": {
"ec2:Owner": ["123456789012"]
}
}
}
{
"Sid": "DenyAccessWithException",
"Effect": "Deny",
"Action": ["ec2:CreateVPC", "ec2:DeleteVPC"],
"Resource": "*",
"Condition": {
"StringNotLike": {
"aws:PrincipalARN": "arn:aws:iam::123456789012:role/terraform_role"
}
}
}
When a request is denied by a Service Control Policy, the error message will explicitly denote it was a Service Control Policy that denied the action, not an IAM policy, resource-based policy, trust policy, Permissions Boundary, etc.
{ARN} is not authorized to perform: {service:action} with an explicit deny in a service control policy
If you are receiving an Explicit Deny error message, ensure that each level of your AWS Organization applicable to the AWS Account has a statement that will allow the action, and that there is not a statement present which would explicitly deny the action.
As SCPs can have an impact on your operations when not properly tested, it is of utmost importance to develop a testing capability for your SCPs. If your existing AWS account architecture has separate logical environments for development vs production, ensure changes are applied to only your lower environments, tested, and then promoted. Otherwise, a “policy staging” AWS account can be provisioned in your AWS organization to allow for testing the effects of your SCPs without affecting any running workloads.
AWS Service Control Policies are a powerful tool to limit your organization’s exposure to new, intentionally disallowed, or simply unfamiliar AWS Services and Resources at any organizational maturity level. Particularly where your organization may have several mechanisms to create and manage AWS IAM policies, SCPs can help ensure overly permissive access is not granted to services, resources, and even specific configuration conditions that could expose your organization to security risks if they are not already addressed via other means or explicitly denied on every single IAM Policy you utilize.