May 30, 2023
IAM
Curtis Castrapel

SCPs: Protecting Your AWS Environment (and your job)

IAMbic, the open-source tool for managing distributed IAM permissions, has expanded its support to include AWS compliance guardrails through Service Control Policies (SCPs). SCPs provide policy governance in AWS, acting as a protective barrier for AWS resources. Users can now efficiently track changes, rollback between different IAM versions, and automatically correct out-of-band changes for important resources. Additionally, IAMbic supports a GitOps workflow, allowing you to implement IAM and SCP changes via PR reviews for improved governance. Continue reading for examples of SCPs, and practical guidance on applying them safely.

AWS Service Control Policies (SCPs) are a key layer within the six-tier policy framework, providing policy governance across all of your accounts, and functioning as a “permissions ceiling”. They serve as the protective shield for your AWS accounts, kind of like the "protective" dome in The Simpsons Movie.

Source

SCPs help enforce security best practices, restrict access to resources, and prevent privilege escalation across all of your AWS accounts. But managing them exclusively in the AWS Console is challenging. 

The main issues we see with SCPs are that they’re difficult to manage with the context and awareness of how IAM is being used at an organization, it’s difficult to track changes to policies, they are prone to drift over time, and it’s often all too easy to misconfigure an SCP and take down production services.

In this post, we’re going to blatantly steal a few of the SCP Best Practices recommended by Scott Piper in his SCP Best Practices blog post (Thank you, Scott). We’re then going to provide you with a practical way to determine which SCP’s are “safe” to apply (But please, use your best judgment here), then we’re going to shamelessly and repeatedly plug IAMbic as a better way to manage your SCPs.

Let’s start with the plugging!

Source

IAMbic is an open-source tool that handles the complex task of managing distributed IAM permissions and compliance guardrails across multiple cloud environments. IAMbic watches the current state of your IAM, and keeps a Git repo updated with reality. Since IAMbic templates are as-code and bi-directional, you can use them in a Git-style workflow to write changes back to the cloud. Some key benefits of using IAMbic to monitor and manage IAM are:

  • Version control - Easily track changes and rollback changes between different versions of your IAM, without worrying about state
  • Drift Prevention - Automatically correct out-of-band changes for your important resources
  • Bi-directional - Imports your existing IAM setup without requiring you to write a single line of code. Make changes, run iambic apply, and write them back to the cloud. When you’re ready to move to a production GitOps flow, we provide all of the Github Action workflows you’ll need.
  • GitOps workflow - Implement IAM and SCP changes via GitOps workflows and PR reviews for governance (See our IAMOps Philosophy docs for examples)
  • Common format - Manage SCPs alongside the rest of your IAM (Including your human identities in your identity provider) in a consistent format
  • Optional Management Control - IAMbic keeps Git updated with the state of your IAM, regardless of whether you’re managing any resources with IAMbic. You can choose which resources (if any) you want IAMbic to manage.

Check out our Example Repository, Website, and GitHub Repository for more information. If you have any questions, we would love to hear from you in our Slack Community.

Example SCPs in IAMbic

Implementing SCPs without careful consideration can have disastrous consequences for your organization. It’s crucial to have a clear understanding of the actions being used within your organization before applying restrictions. Analyzing CloudTrail logs can provide valuable insights into which actions are safe to restrict, and identify legitimate use cases that can serve as conditional exceptions in your policies.

To aid you in the analysis of your CloudTrail logs, we provide SQL queries that can be used with Cloudtrail Lake or Athena. We recommend using Cloudtrail Lake for simplicity. However, if that’s not feasible, you can create an Athena table to query your CloudTrail S3 bucket. A prerequisite to both options is that you have enabled Organizational Cloudtrail, and all trails across all of your accounts are logging to a single S3 bucket. This script can help you create an Athena table on top of that bucket. Follow the instructions on the top of the script to install and use it, and feel free to reach out if you encounter any issues.

In the following sections, we'll explore different SCPs that enhance security within your AWS Organization, we’ll provide SQL queries to determine if the SCPs are “safe” to apply (please exercise your best judgment), and we’ll also provide the IAMbic templates for implementation.

To confirm the effectiveness of each SCP, we suggest performing a "safe" action that triggers an event. Wait for approximately 30 minutes and then execute the corresponding SQL query to verify if the action was recorded as expected in your CloudTrail logs. Remember to thoroughly examine your CloudTrail S3 bucket to ensure that all of your accounts and regions are actively logged.

When running the SQL queries, consider adjusting the search period based on your requirements. The provided queries are configured to examine the past 90 days of usage by default. If you identify legitimate usage of certain events, you may need to add conditional statements to the SCPs to allow the intended identities or avoid attaching the SCPs to specific OUs or accounts.

An example conditional statement that can be added to an SCP, which allows any role with the name admin to bypass a Deny statement specified in an SCP.

condition:
  StringNotLike:
    aws:PrincipalArn:
      - 'arn:aws:iam::*:role/admin'

If you’re copying and pasting the SCP templates below verbatim, you will need to replace the org_id and account_id values to match your AWS Organizations management account.

Lastly, if you want to convert back-and-forth between IAMbic and AWS IAM Policy Language, check out the iambic convert command, or the Policy Converter Web UI.

Give it a go, and let us know in Slack if you run into any issues, or have additional recommendations for the queries/templates.

How to assign SCPs to targets in IAMbic

To assign SCPs to targets in IAMbic, you can either attach targets to a customer-managed SCP in the AWS console and run iambic import, or append targets to your SCP IAMbic template and apply them to the cloud using iambic apply. Here are some examples:

  • Apply to Specific accounts
properties: 
  ...
  targets: 
    accounts: 
      - Account Name 1 
      - Account Name 2
  • Apply to Specific OUs
properties: 
  …
  targets: 
    organizational_units:  
      - ou-12ab-1abcdef2
      - ou-12ab-2abcdef3 
  • Apply to the Root OU of the Organization (affects all accounts)
properties: 
  ... 
  targets: 
    roots:     
      - r-12ab

Deny Root Access

Using the root user for everyday tasks exposes AWS resources to potential compromise. It's a best practice to use temporary credentials provided by AWS Identity Center (SSO) or IAM roles for everyday interaction with AWS. This SCP helps you pull the root of the weed to keep your AWS garden healthy and secure.

SQL Queries

Athena Query

Run this query to look for root user usage over the past 90 days.

SELECT distinct userIdentity.arn, eventname
FROM cloudtrail_logs
WHERE date_parse(date, '%Y/%m/%d') > current_timestamp - interval '90' day
AND userIdentity IS NOT NULL
AND userIdentity.arn IS NOT NULL
AND userIdentity.arn LIKE '%:root'
group by userIdentity.arn, eventname

Your results should resemble the following screenshot:

CloudTrail Lake Query

In CloudTrail Lake, this query would look like the following:

SELECT distinct userIdentity.arn, eventname
FROM replace_with_your_event_data_store_id
WHERE eventTime >= date_add('day',-90,current_timestamp)
AND userIdentity.arn LIKE '%:root'
group by userIdentity.arn, eventname

Your results should resemble the following screenshot:

IAMbic Template

This IAMbic template will manage an SCP that blocks root usage by default. Temporarily disable this SCP on the target account when there is a legitimate (and peer approved) use case for using root.

template_type: NOQ::AWS::Organizations::SCP  
account_id: '123456789012' 
identifier: DenyRoot
iambic_managed: enforced
org_id: o-12345678
properties:
  policy_document: 
    statement:  
      - action: 
          - '*' 
        effect: Deny
        resource: 
          - '*' 
        condition: 
          StringLike: 
            aws:PrincipalArn: 
              - arn:aws:iam::*:root     
  policy_name: DenyRoot

Prevent Disrupting Essential Security Services

Security services, like GuardDuty, Access Analyzer, CloudTrail, S3 Public Block Access, etc. are important for maintaining the security posture of your AWS environment. Unauthorized changes to these services can compromise your security setup.

Since there may be legitimate use cases to perform certain actions, such as updating GuardDuty detectors, you may wish to modify part of this SCP with conditional exceptions.

SQL Queries

Athena:
SELECT distinct userIdentity.arn, eventname
FROM cloudtrail_logs
WHERE date_parse(date, '%Y/%m/%d') > current_timestamp - interval '90' day
AND eventname IN ('DeleteDetector', 'DisassociateFromMasterAccount', 'UpdateDetector', 'CreateFilter', 'CreateIPSet', 'DeleteRule', 'DisableRule', 'RemoveTargets', 'DeleteAnalyzer', 'DisableEbsEncryptionByDefault', 'PutAccountPublicAccessBlock')
GROUP BY userIdentity.arn, eventname
CloudTrail Lake:
SELECT distinct userIdentity.arn, eventname
FROM replace_with_your_event_data_store_id
WHERE eventTime >= date_add('day',-90,current_timestamp)
AND eventname IN ('DeleteDetector', 'DisassociateFromMasterAccount', 'UpdateDetector', 'CreateFilter', 'CreateIPSet', 'DeleteRule', 'DisableRule', 'RemoveTargets', 'DeleteAnalyzer', 'DisableEbsEncryptionByDefault', 'PutAccountPublicAccessBlock')
GROUP BY userIdentity.arn, eventname

IAMbic Template

If you frequently modify EventBridge or GuardDuty rules, this template may not be suitable for you in its current form. To minimize the maintenance effort for legitimate updates, you might need to modify this example to exclude specific identities from making changes.

template_type: NOQ::AWS::Organizations::SCP
account_id: '123456789012'
iambic_managed: enforced
identifier: PreventDisableSecurityServices
org_id: o-1234567890
properties:
  policy_document:
    statement:
      - action:
        - access-analyzer:DeleteAnalyzer
        - ec2:DisableEbsEncryptionByDefault
        - events:DeleteRule
        - events:DisableRule
        - events:RemoveTargets
        - guardduty:CreateFilter
        - guardduty:CreateIPSet
        - guardduty:DeleteDetector
        - guardduty:DisassociateFromMasterAccount
        - guardduty:UpdateDetector
        - s3:PutAccountPublicAccessBlock
       effect: Deny
       resource: '*'
    version: '2012-10-17'
  policy_name: PreventDisableSecurityServices

Require Usage of IMDSv2 for EC2 Instances

The Instance Metadata Service (IMDSv2) comes with additional defenses against server side request forgery (SSRF) vulnerabilities compared to IMDSv1. This policy ensures that all EC2 instances use IMDSv2.

SQL Queries

Athena RunInstances query:
SELECT DISTINCT userIdentity.arn, eventName
FROM cloudtrail_logs
WHERE date_parse(date, '%Y/%m/%d') > current_timestamp - interval '90' day
AND eventName = 'RunInstances'
AND json_extract_scalar(requestParameters, '$.metadataOptions.httpTokens') = 'optional'
GROUP BY userIdentity.arn, eventName;
CloudTrail Lake RunInstances query:
SELECT DISTINCT useridentity.arn, eventname
FROM replace_with_your_event_data_store_id
WHERE eventTime >= date_add('day',-90,current_timestamp)
AND eventname = 'RunInstances'
AND json_extract_scalar(requestparameters['metadataOptions'], '$.httpTokens') = 'optional'
GROUP BY useridentity.arn, eventname;
Athena ModifyInstanceMetadata query:
SELECT DISTINCT userIdentity.arn,
	json_extract_scalar(
		requestParameters,
		'$.ModifyInstanceMetadataOptionsRequest.InstanceId'
	)
FROM cloudtrail_logs
WHERE date_parse(date, '%Y/%m/%d') > current_timestamp - interval '5' hour
	AND eventName = 'ModifyInstanceMetadataOptions'
	AND json_extract_scalar(
		requestParameters,
		'$.ModifyInstanceMetadataOptionsRequest.HttpTokens'
	) = 'optional'
GROUP BY userIdentity.arn,
	json_extract_scalar(
		requestParameters,
		'$.ModifyInstanceMetadataOptionsRequest.InstanceId'
	);
Cloudtrail Lake ModifyInstanceMetadata query
SELECT DISTINCT useridentity.arn,
    json_extract_scalar(requestparameters['ModifyInstanceMetadataOptionsRequest'], '$.InstanceId')
FROM replace_with_your_event_data_store_id
WHERE eventTime >= date_add('day',-90,current_timestamp)
AND eventname = 'ModifyInstanceMetadataOptions'
AND json_extract_scalar(requestparameters['ModifyInstanceMetadataOptionsRequest'], '$.HttpTokens') = 'optional'
GROUP BY useridentity.arn,
    json_extract_scalar(requestparameters['ModifyInstanceMetadataOptionsRequest'], '$.InstanceId');
Cloudtrail Lake ec2RoleDelivery query:
SELECT DISTINCT useridentity.arn, eventSource, eventName
FROM replace_with_your_event_data_store_id
WHERE eventTime >= date_add('day',-90,current_timestamp)
AND useridentity.sessionContext.ec2RoleDelivery = '1.0'

IAMbic Template

template_type: NOQ::AWS::Organizations::SCP
account_id: '123456789012'
identifier: PreventIMDSV1
org_id: o-1234567890
properties:
  policy_document:
    statement:
      - action:
          - '*'
        condition:
          NumericLessThan:
            Ec2RoleDelivery: '2.0'
        effect: Deny
        resource:
          - '*'
      - action:
          - ec2:RunInstances
        condition:
          StringNotEquals:
            Ec2MetadataHttpTokens: required
        effect: Deny
        resource:
          - arn:aws:ec2:*:*:instance/*
      - action:
          - ec2:ModifyInstanceMetadataOptions
        effect: Deny
        resource:
          - '*'
      - action:
          - ec2:RunInstances
        condition:
          NumericGreaterThan:
            Ec2MetadataHttpPutResponseHopLimit: '1'
        effect: Deny
        resource:
          - arn:aws:ec2:*:*:instance/*
    version: '2012-10-17'
  policy_name: PreventIMDSV1

Deny Ability to Leave AWS Organization

This policy is used to prevent users or roles from unintentionally or maliciously removing an AWS account from your organization.

IAMbic Template

template_type: NOQ::AWS::Organizations::SCP
account_id: '123456789012'
identifier: PreventLeaveOrg
org_id: o-1234567890
properties:
  policy_document:
    statement:
      - action:
          - organizations:LeaveOrganization
        effect: Deny
        resource:
          - '*'
    version: '2012-10-17'
  policy_id: p-21od23cu
  policy_name: PreventLeaveOrg

Region Enforcement

If your organization only permits the usage of specific regions or if you have regulatory and compliance requirements to store data in particular regions, this SCP effectively limits unauthorized regions and should be considered.

Prior to applying this SCP, it is important to review the AWS-provided caveats and refer to Scott Piper's blog post for further guidance and considerations.

IAMbic template

template_type: NOQ::AWS::Organizations::SCP
account_id: '123456789012'
identifier: RestrictRegions
org_id: o-12345678
iambic_managed: enforced
properties:
  policy_document:
    statement:
      - condition:
          StringNotEquals:
            aws:RequestedRegion:
              - us-east-1
              - us-west-2
        effect: Deny
        not_action:
          - 'a4b:*'
          - 'acm:*'
          - 'aws-marketplace-management:*'
          - 'aws-marketplace:*'
          - 'aws-portal:*'
          - 'budgets:*'
          - 'ce:*'
          - 'chime:*'
          - 'cloudfront:*'
          - 'config:*'
          - 'cur:*'
          - 'directconnect:*'
          - 'ec2:DescribeRegions'
          - 'ec2:DescribeTransitGateways'
          - 'ec2:DescribeVpnGateways'
          - 'fms:*'
          - 'globalaccelerator:*'
          - 'health:*'
          - 'iam:*'
          - 'importexport:*'
          - 'kms:*'
          - 'mobileanalytics:*'
          - 'networkmanager:*'
          - 'organizations:*'
          - 'pricing:*'
          - 'route53:*'
          - 'route53domains:*'
          - 'route53-recovery-cluster:*'
          - 'route53-recovery-control-config:*'
          - 'route53-recovery-readiness:*'
          - 's3:GetAccountPublic*'
          - 's3:ListAllMyBuckets'
          - 's3:ListMultiRegionAccessPoints'
          - 's3:PutAccountPublic*'
          - 'shield:*'
          - 'sts:*'
          - 'support:*'
          - 'trustedadvisor:*'
          - 'waf-regional:*'
          - 'waf:*'
          - 'wafv2:*'
          - 'wellarchitected:*'
        resource:
          - '*'
  policy_name: RestrictRegions

SQL Query

Athena

Update the regions in the query to match approved regions in your environment.

SELECT DISTINCT eventSource, eventName, awsRegion 
FROM cloudtrail_logs 
WHERE date_parse(date, '%Y/%m/%d') >= current_timestamp - interval '90' day
AND awsRegion NOT IN ( 'us-east-1', 'us-west-2' )
AND eventSource NOT IN ('a4b.amazonaws.com', 'budgets.amazonaws.com', 'ce.amazonaws.com', 'chime.amazonaws.com', 'cloudfront.amazonaws.com', 'cur.amazonaws.com', 'globalaccelerator.amazonaws.com', 'health.amazonaws.com', 'iam.amazonaws.com', 'importexport.amazonaws.com', 'mobileanalytics.amazonaws.com', 'organizations.amazonaws.com', 'route53.amazonaws.com', 'route53domains.amazonaws.com', 'shield.amazonaws.com', 'support.amazonaws.com', 'trustedadvisor.amazonaws.com', 'waf.amazonaws.com', 'wellarchitected.amazonaws.com');

This query retrieves the event name, region, and source of actions performed outside the ‘us-east-1’ and ‘us-west-2’ regions within the specified timeframe.

Cloudtrail Lake
SELECT DISTINCT eventSource, eventName, awsRegion 
FROM replace_with_your_event_data_store_id
WHERE eventTime >= date_add('day',-90,current_timestamp)
AND awsRegion NOT IN ( 'us-east-1', 'us-west-2' )
AND eventSource NOT IN ('a4b.amazonaws.com', 'budgets.amazonaws.com', 'ce.amazonaws.com', 'chime.amazonaws.com', 'cloudfront.amazonaws.com', 'cur.amazonaws.com', 'globalaccelerator.amazonaws.com', 'health.amazonaws.com', 'iam.amazonaws.com', 'importexport.amazonaws.com', 'mobileanalytics.amazonaws.com', 'organizations.amazonaws.com', 'route53.amazonaws.com', 'route53domains.amazonaws.com', 'shield.amazonaws.com', 'support.amazonaws.com', 'trustedadvisor.amazonaws.com', 'waf.amazonaws.com', 'wellarchitected.amazonaws.com');

Deny ability to make a VPC accessible from the Internet that isn’t already for specific accounts.

This SCP is useful to protect your AWS resources and data from exposure to the Internet. By denying the creation and attachment of internet gateways and the creation of VPC peering connections, you can ensure that a VPC that wasn't originally internet-accessible remains that way. This is particularly useful in sandbox or development accounts where you might want to limit exposure of your infrastructure to minimize the potential attack surface and keep development activities isolated from the public internet.

IAMbic Template

template_type: NOQ::AWS::Organizations::SCP
account_id: '123456789012'
Identifier: PreventExposeVPC
org_id: o-1234567890
properties:
  policy_document:
    statement:
      - action:
          - ec2:AcceptVpcPeeringConnection
          - ec2:AttachInternetGateway
          - ec2:CreateEgressOnlyInternetGateway
          - ec2:CreateInternetGateway
          - ec2:CreateVpcPeeringConnection
          - globalaccelerator:Create*
          - globalaccelerator:Update*
        effect: Deny
        resource:
          - '*'
    version: '2012-10-17'
  policy_name: PreventExposeVPC

SQL Query

Athena Query
SELECT DISTINCT
    eventsource,
    eventname,
    COALESCE(
        useridentity.sessioncontext.sessionissuer.arn,
        useridentity.arn,
        useridentity.accountId
    ) AS identity
FROM
    cloudtrail_logs
WHERE
    (
        (eventsource = 'ec2.amazonaws.com' AND
        (
            eventname = 'AttachInternetGateway' OR
            eventname = 'CreateInternetGateway' OR
            eventname = 'CreateEgressOnlyInternetGateway' OR
            eventname = 'CreateVpcPeeringConnection' OR
            eventname = 'AcceptVpcPeeringConnection'
        ))
        OR 
        (eventsource = 'globalaccelerator.amazonaws.com' AND
        (
            eventname LIKE 'Create%' OR
            eventname LIKE 'Update%'
        ))
    )
    AND date_parse(eventTime, '%Y-%m-%dT%T') > current_timestamp - interval '90' day
GROUP BY 
    eventsource, eventname, COALESCE(
        useridentity.sessioncontext.sessionissuer.arn,
        useridentity.arn,
        useridentity.accountId
    );
Cloudtrail Lake Query
SELECT DISTINCT
    eventsource,
    eventname,
    COALESCE(
        useridentity.sessioncontext.sessionissuer.arn,
        useridentity.arn,
        useridentity.accountId
    ) AS identity
FROM
    replace_with_your_event_data_store_id
WHERE
    (
        (eventsource = 'ec2.amazonaws.com' AND
        (
            eventname = 'AttachInternetGateway' OR
            eventname = 'CreateInternetGateway' OR
            eventname = 'CreateEgressOnlyInternetGateway' OR
            eventname = 'CreateVpcPeeringConnection' OR
            eventname = 'AcceptVpcPeeringConnection'
        ))
        OR 
        (eventsource = 'globalaccelerator.amazonaws.com' AND
        (
            eventname LIKE 'Create%' OR
            eventname LIKE 'Update%'
        ))
    )
    AND eventTime >= date_add('day',-90,current_timestamp)
GROUP BY 
    eventsource, eventname, COALESCE(
        useridentity.sessioncontext.sessionissuer.arn,
        useridentity.arn,
        useridentity.accountId
    );

Next Steps

We highly recommend performing a deeper, and more customized assessment of your environment, and creating SCPs that work for your unique situation. Rhino Security Labs released a handy list of IAM Privilege Escalations that serves as a useful reference for locking down permissions.

During my time at Netflix, our team conducted an analysis of CloudTrail using this list as a reference. We used the findings to create SCPs that aimed to prevent privilege escalations and unauthorized changes to IAM permissions.

These SCPs were designed to allow only the necessary identities to have those permissions while adding additional layers of security through tagging, identity, and organizational boundaries. As we gained more experience, we continuously refined and enhanced these SCPs to ensure better protection for our AWS resources.

Once you’re ready to migrate to a production IAMbic deployment, you can start leveraging GitOps workflows with your IAM changes. See IAMbic’s IAMOps Philosophy for more information and example workflows.

Conclusion

That wraps up our deep dive into SCPs! We've examined their importance, shared some IAMbic templates, and guided you through the corresponding SQL queries. Your feedback is crucial in shaping our next steps, so join us on our Slack channel and share your thoughts. Thanks for joining us on this journey, stay secure, and keep in touch!

Special Thanks to Richard Julian, Phil Hadviger, and David Behroozi for help with content and for reviewing this blog post.

Curtis Castrapel

Noq Founder
linkedin
twittergithub

The First IAM Ops Platform for AWS

Learn More