Project Objective

ThreadCraft, our e-commerce website, has been scaling fast, and so has its AWS environment. Hence, my role, as a Cloud Engineer is to ensure that they have a well organized environment setup to deploy and manage AWS resources safely and effectively.

The goal of this project is to bring structure and governance to ThreadCraft's AWS Organization by using Service Control Policies and Resource Control Policies. These policies act as guardrails, helping us enforce security, compliance, and operational consistency across all AWS accounts in the organization.


Services used:

  1. AWS Organizations helps manage and govern an environment as it grows and scales, when not only are more resources deployed but also more accounts are added. We create accounts, group them into organizational units and allocate resources, apply policies for governance, consolidate billing under a single payment method for all the accounts.

  2. Organizational Units (OUs) are used to group the accounts together and administer as a single unit, simplifying the management of companies accounts. When we attach policies to an OU, all accounts within that OU will inherit the policy. 

  3. Service Control Policies (SCPs) are a type of organizational policy that define the maximum available permissions for identities (IAM roles and users) in an account. They do not grant permissions, instead, they act as guardrails, that limit what actions identities can perform, even if those actions are allowed by IAM policies.

  4. Resource Control Policies (RCPs) are a type of organizational policy that define the maximum permissions for resources. Like SCPs, RCPs do not grant permissions, and do not grant access but restrict what actions can be performed on resources by identities from within the organization.

AWS Services supporting RCPs:

  • Amazon S3
  • AWS Security Token Service
  • AWS Key Management Service
  • Amazon Simple Queue Service
  • AWS Secrets Manager

The effective permissions for a resource are determined by the logical intersection between what is allowed by the RCPs, SCPs, the identity-based policies and resource-based policies.

What SCPs and RCPs have in common:

  • Do not grant permissions, they restrict what IAM policies can allow.
  • Do not apply to the Management Account.
  • If an action is denied in SCP or RCP, no IAM policy can override it.
  • Used for governance, security and compliance.

How are SCPs and RCPs different:

  • SCPs apply to IAM identities, RCPs apply to AWS resources.
  • SCPs restrict what IAM users and roles can do, RCPs restrict what actions can be done with the supported AWS resources.

What do you need for this project:
Following best practices, AWS recommends 3 AWS accounts to be set up in AWS Organizations: one account for the management account, one to deploy the main resources, and in the ThreadCraft project, the third one will be later used for backup purposes.


I. AWS Organizations

  1. First, we need to set up an organization. We need to log in in the main account, which is called the Management Account. From this account we will manage the other accounts. It is not affected by the organizational policies. Create an Organization from the AWS Organizations console. Choose Create an organization. 
  2. Go to the Organizations dashboard > select AWS accounts > Add a new account > Invite the existing AWS account. An email will be sent which needs to be accepted.

Image description

  1. Then, let's create the account for backup purposes. From the AWS Organizations dashboard, select AWS accounts > Add a new account > Create an AWS account > Name the account, add the email address and either you change the IAM role or leave it as it is, OrganizationAccountAccessRole, which will be used to access resources in the member account.

Image description

  1. When you need to switch the role, navigate to the top right corner of the console. Click on your account / user name. If you use multi-session support, Switch role is under the drop down arrow:

Image description

II. Create organizational units

  1. In the AWS Organizations console, navigate to the AWS accounts. Tick the Root container and, from Actions, Create New. For the organizational unit name, enter "Production" and then choose Create organizational unit.

Image description

  1. We will create the OU for the backup account. Tick the Production OU. On the Create organizational unit in Production page, for the second OU, enter "Backup" and choose Create organizational unit.

Image description

  1. Move the member accounts into these OUs: In the AWS accounts page, expand the tree under Production OU. Select the account you want to move under Production. Under Actions, AWS account, choose Move. Click on the triangle to expand the Production, tick the account and then choose Move from Actions. Select the account which is for backup and from Actions, choose Move. Expand Production, then expand and select Backup. Next, click on Move AWS Account.

III. Enable Service Control Policies (SCP) and Resource Control Policies (RCP)

To be able to attach organizational policies on the root or any OUs, we need to enable the policy types we want to use: we choose Service control policies and Resource control policies.
From AWS Organizations console, select Policies page, choose Service Control Policies, then the click on Enable service control policies. 

Image description


We chose five SCPs to enforce security, compliance, and governance at the account level:

1. Protect CloudTrail Logs: Denies deletion of CloudTrail logs, ensuring that all API activity is logged. By keeping the logs intact, we ensure compliance and support auditing.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "cloudtrail:DeleteTrail",
        "cloudtrail:StopLogging"
      ],
      "Resource": "*"
    }
  ]
}

2. Enforce Region Restriction: Resources will be deployed only in the specified regions: "us-east-1", "us-west-1". Improves security and compliance by restricting where AWS resources can be created. This guardrail is particularly useful in preventing scenarios where, for example, a compromised identity (such as a bot) gains permissions and attempts to deploy resources in unauthorized regions for malicious purposes (such as cryptocurrency mining).

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": ["us-east-1", "us-west-1"]
        }
      }
    }
  ]
}

3. Block VPC Internet access: This SCP prevents any VPC that does not already have direct Internet access from obtaining it. Since ThreadCraft's VPCs are isolated and do not have direct access to Internet, we used this SCP to prevent any user or role to change network configuration and give EC2 instances direct access to Internet, minimizing the attack surface.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "ec2:AttachInternetGateway",
        "ec2:CreateInternetGateway",
        "ec2:CreateEgressOnlyInternetGateway",
        "ec2:CreateVpcPeeringConnection",
        "ec2:AcceptVpcPeeringConnection",
        "globalaccelerator:Create*",
        "globalaccelerator:Update*"
      ],
      "Resource": "*"
    }
  ]
}

4. Protect GuardDuty Operations: Prevents users or roles in the accounts to disable or modify Guard Duty configuration. It enables read-only access to all GuardDuty information and resources, ensuring GuardDuty remains active and tamper-proof, maintaining continuous threat detection.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Action": [ 
                "guardduty:AcceptInvitation",
                "guardduty:ArchiveFindings",
                "guardduty:CreateDetector",
                "guardduty:CreateFilter",
                "guardduty:CreateIPSet",
                "guardduty:CreateMembers",
                "guardduty:CreatePublishingDestination",
                "guardduty:CreateSampleFindings",
                "guardduty:CreateThreatIntelSet",
                "guardduty:DeclineInvitations",
                "guardduty:DeleteDetector",
                "guardduty:DeleteFilter",
                "guardduty:DeleteInvitations",
                "guardduty:DeleteIPSet",
                "guardduty:DeleteMembers",
                "guardduty:DeletePublishingDestination",
                "guardduty:DeleteThreatIntelSet",
                "guardduty:DisassociateFromMasterAccount",
                "guardduty:DisassociateMembers",
                "guardduty:InviteMembers",
                "guardduty:StartMonitoringMembers",
                "guardduty:StopMonitoringMembers",
                "guardduty:TagResource",
                "guardduty:UnarchiveFindings",
                "guardduty:UntagResource",
                "guardduty:UpdateDetector",
                "guardduty:UpdateFilter",
                "guardduty:UpdateFindingsFeedback",
                "guardduty:UpdateIPSet",
                "guardduty:UpdatePublishingDestination",
                "guardduty:UpdateThreatIntelSet"
            ],      
            "Resource": "*"
        }
    ]
}

5. Protect ARC Routing Control: Prevents users from updating ARC routing control state. For our disaster recovery plan, ARC (AWS Route 53 Application Recovery Controller) is a critical service to be continuously monitored. However, it is utterly important that only the authorized personnel responsible for failover to the secondary site have permissions to switch traffic. Hence, the below policy ensures that the IAM roles and users are not allowed to update the ARC route controls, except the employees who are designated in the Disaster Recovery Plan.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DenyAll",
            "Effect": "Deny",
            "Action": [
                "route53-recovery-cluster:UpdateRoutingControlState",
                "route53-recovery-cluster:UpdateRoutingControlStates"
            ],
            "Resource": "*",
            "Condition": {
                "ArnNotLike": {
                    "aws:PrincipalARN": [
                       "arn:aws:iam::*:role/Role1AllowedToBypassThisSCP",
                       "arn:aws:iam::*:role/Role2AllowedToBypassThisSCP"
                    ]
                }
            }
        }
    ]
}

We chose five RCPs to enforce security, compliance, and governance at the account level:
1. Restrict Cross-Account Access. The below data perimeter RCP prevents that other entities can assume IAM roles into our accounts. Only the accounts from our organization and the specified partners accounts can assume IAM roles within our organization. The policy ensures a secure, controlled access to IAM roles and protects against unauthorized cross-account activity, strengthening our overall identity perimeter.

{  
    "Effect": "Deny",  
    "Principal": "*",  
    "Action": "sts:AssumeRole",  
    "Resource": "*",  
    "Condition": {  
        "StringNotEqualsIfExists": {  
            "aws:PrincipalOrgID": "",  
            "aws:PrincipalAccount": [  
                "",  
                "" 
            ] 
        },
        "BoolIfExists": {
            "aws:PrincipalIsAWSService": "false"
        }
    }  
}

2. Cross-Service Confused Deputy Protection. For example, when a threat actor that doesn't have access to a service, such as S3, but somehow succeeds to manipulate the trust of another service (such as Lambda or CloudWatch) that interacts with S3, and this way, it gets access to S3, it is called cross-service confused deputy problem. Using the below RCP we ensure that only services from our organization can interact with the supported resources on our behalf.

{
    "Version": "2012-10-17",
    "Statement": [
        {            
            "Sid": "EnforceConfusedDeputyProtection",
            "Effect": "Deny",
            "Principal": "*",
            "Action": [
                "s3:*",
                "sqs:*",
                "kms:*",
                "secretsmanager:*",
                "sts:*"
            ],
            "Resource": "*",
            "Condition": {
                "StringNotEqualsIfExists": {
                    "aws:SourceOrgID": "my-org-id",
                    "aws:SourceAccount": [
                        "third-party-account-a",
                        "third-party-account-b"
                    ]
                },  
                "Bool": {
                    "aws:PrincipalIsAWSService": "true"
                },
                 "Null": {
                    "aws:SourceArn": "false"
                }
            }
        }
    ]
}

3. Deny users from deleting Amazon S3 buckets or objects. To protect our S3 buckets and their contents from unintentional deletion we use the below resource based policy. This way, we ensure data availability and reduce risk of data loss.

{
    "Version":"2012-10-17",
    "Statement":[
       {
          "Effect":"Deny",
          "Principal":"*",
          "Action":[
             "s3:DeleteBucket",
             "s3:DeleteBucketPolicy",
             "s3:DeleteObject",
             "s3:DeleteObjectVersion",
             "s3:DeleteObjectTagging",
             "s3:DeleteObjectVersionTagging"
          ],
          "Resource":[
             "arn:aws:s3:::[BUCKET_TO_PROTECT]",
             "arn:aws:s3:::[BUCKET_TO_PROTECT]/*"
          ]
       }
    ]
 }

4. Deny Built-In Web Identity Providers. When we log in to a website using our Google account, Google acts as a federated identity provider. It allows us to authenticate using their credentials, without creating a new account on the website. With the below RCP we ensure that even if an unauthorized actor manages to get access to a federated identity provider, they cannot assume an IAM role across our accounts. This prevents unauthorized access and reinforces the desired security posture.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:federatedProvider": [
                        "accounts.google.com",
                        "graph.facebook.com", 
                        "www.amazon.com"
                    ]
                }
            }
        }
    ]
}

5. Protect KMS Keys from Deletion. Deny deletion of a KMS key and only allow specific roles to delete KMS keys. With this RCP we deny all principals the permissions to delete KMS keys, with the Privileged_role exception. In such manner, we prevent intentional or accidental deletion of the KMS keys, we keep data encrypted, guaranteeing its confidentiality.

{
    "Version":"2012-10-17",
    "Statement":[
       {
          "Effect":"Deny",
          "Principal":"*",
          "Action":[
             "kms:ScheduleKeyDeletion",
             "kms:DeleteAlias",
             "kms:DeleteCustomKeyStore",
             "kms:DeleteImportedKeyMaterial"
          ],
          "Resource":"*",
          "Condition":{
             "ArnNotLike":{
      "aws:PrincipalArn":"arn:aws:iam::${Account}:role/[PRIVILEGED_ROLE]"
             }
          }
       }
    ]
 }

Validating the Resource Control Policy (RCP)
To verify the effectiveness of the implemented guardrails, we tested the RCP which denies the deletion of S3 buckets and objects.

An S3 object deletion attempt was made, and as expected, the action failed with an explicit Deny enforced by the Resource Control Policy:

Image description

Image description

This confirms that the RCP is working as intended, enforcing protection and preventing unauthorized or accidental data deletion across S3 buckets.

By combining Service Control Policies and Resource Control Policies, ThreadCraft has established a solid foundation for scalable, secure, and compliant cloud operations. From protecting CloudTrail logs to enforcing data perimeters, these guardrails strengthen the overall security posture  and ensure protection against unauthorized activities.

If this was helpful or sparked ideas, let’s connect!
Got tips, tricks, or favorite guardrails?
👇 Share them in the comments!