Policy as a code: Pulumi + AWS

Policy as a code: Pulumi + AWS

Hello LinkedIn :)

Introduction

Pulumi is an excellent tool for building and deploying cloud infrastructure using popular programming languages such as Python, TypeScript, and Go. One of the key features of Pulumi that sets it apart from other infrastructure-as-code tools is the Policy Pack. The Policy Pack allows you to define and enforce policies on your infrastructure as code, ensuring that your cloud resources are compliant with industry standards, regulations, and best practices.

In this blog post, we will explore the benefits of using Policy Pack to increase security and compliance in your cloud infrastructure. We will demonstrate how you can define policies as code using the Pulumi SDK and enforce them automatically during deployment. By treating policies as code, you can ensure consistency and automate policy enforcement, reducing the risk of human error and improving compliance, naming conventions, and security.

We will cover several use cases for Policy Pack, including enforcing naming conventions, security best practices, and cost optimization policies. By the end of this blog post, you will have a solid understanding of how Policy Pack can be used to improve the security and compliance of your cloud infrastructure, and how you can implement Policy Pack in your own Pulumi projects.

Policy as Code

Policy as code is a new paradigm for defining and enforcing policies. It treats policies as code, which means you can write policies using the same tools and techniques you use to write application code. This approach has many benefits, including:

  • Consistency: Policies can be defined and enforced consistently across all environments and projects.
  • Automation: Policies can be enforced automatically during deployment, eliminating the need for manual checks and reducing the risk of human error.
  • Collaboration: Policies can be shared and versioned like any other code, enabling collaboration and reuse across teams and projects.
  • Transparency: Policies are transparent and auditable, providing insight into how your infrastructure complies with regulations and best practices.

Pulumi's Policy Pack brings these benefits to cloud infrastructure by allowing you to define and enforce policies as code.

Benefits of Policy Pack

Policy Pack brings many benefits to cloud infrastructure development and deployment, including:

  • Compliance: Policy Pack can enforce compliance with industry standards, regulations, and best practices, ensuring that your infrastructure meets the necessary requirements.
  • Naming conventions: Policy Pack can enforce consistent naming conventions across all resources, making it easier to manage and understand your infrastructure.
  • Security: Policy Pack can enforce security best practices, such as resource isolation, encryption, and access control.
  • Cost optimization: Policy Pack can enforce cost optimization policies, such as the use of reserved instances or the enforcement of resource tags to track and manage costs.
  • Customization: Policy Pack can be customized to meet the specific needs of your organization, allowing you to enforce policies that are unique to your business.

Let's get started, and see how it works :)

Code: link

You can create policy packs based on your requirement, you can use policy packs to enforce:

  1. Tagging Policy
  2. Naming convention
  3. Security best practices
  4. Enforce instance (EC2, RDS) types
  5. And lots more

Policy Pack

The below policy pack checks:

  1. Standard resource naming convention
  2. S3 bucket naming convention
  3. If s3 is created with public read access
  4. Checks if the resource has required tags
  5. Checks if EC2 security group has open access on port 22 or 3389
  6. Checks if RDS security group has open access on port 3306
  7. Checks if RDS is made publicly accessible

from pulumi_policy import (
? ? EnforcementLevel,
? ? PolicyPack,
? ? ReportViolation,
? ? ResourceValidationArgs,
? ? ResourceValidationPolicy,
)


RDS_PORT = 3306
COMPANY_NAME = "abc"
PROJECT_NAME = "xyz"
ENV_LIST = ["dev", "test", "uat", "staging", "prod", "train", "sandbox"]


# policy to check the resource name



def check_resource_name(name):
? ? return (
? ? ? ? name is not None
? ? ? ? and name.startswith(f"{COMPANY_NAME}-{PROJECT_NAME}")
? ? ? ? # and name.endswith(f"{PROJECT_NAME}")
? ? ? ? and name.split("-")[-1] in ENV_LIST
? ? ? ? and name.count("-") > 2
? ? ? ? and "_" not in name
? ? )



def check_naming_convention(
? ? args: ResourceValidationArgs, report_violation: ReportViolation
):
? ? name = args.name
? ? if args.resource_type not in [
? ? ? ? "pulumi:pulumi:Stack",
? ? ? ? "pulumi:providers:aws",
? ? ? ? "pulumi:providers:random",
? ? ]:
? ? ? ? if "name" in args.props and not check_resource_name(args.props["name"]):
? ? ? ? ? ? violating_name = args.props["name"]
? ? ? ? ? ? report_violation(
? ? ? ? ? ? ? ? f"{violating_name} does not follow the standard naming policy."
? ? ? ? ? ? )



resource_naming_convention = ResourceValidationPolicy(
? ? name="resource-naming-convention",
? ? description="Prohibits creation of the resource if not following naming convention",
? ? validate=check_naming_convention,
)


##########################################################################################
##########################################################################################


# policy to check s3 naming convention



def check_s3_bucket_name(resource, report_violation):
? ? if resource.resource_type == "aws:s3/bucket:Bucket":
? ? ? ? bucketName = resource.props.get("bucket", "")
? ? ? ? if not (
? ? ? ? ? ? bucketName.startswith(f"{COMPANY_NAME}-{PROJECT_NAME}")
? ? ? ? ? ? and any(bucketName.endswith(env) for env in ENV_LIST)
? ? ? ? ):
? ? ? ? ? ? report_violation(
? ? ? ? ? ? ? ? "The S3 bucket naming does not follow standard naming convention"
? ? ? ? ? ? )



check_s3_bucket_naming_convention = ResourceValidationPolicy(
? ? name="check_s3_bucket_naming_convention",
? ? description="Prohibits creation of the resource if not following naming convention",
? ? validate=check_s3_bucket_name,
)


##########################################################################################
##########################################################################################



# policy to check is s3 is open to public



def s3_no_public_read_validator(
? ? args: ResourceValidationArgs, report_violation: ReportViolation
):
? ? if args.resource_type == "aws:s3/bucket:Bucket" and "acl" in args.props:
? ? ? ? acl = args.props["acl"]
? ? ? ? if acl == "public-read" or acl == "public-read-write":
? ? ? ? ? ? report_violation(
? ? ? ? ? ? ? ? "Please review the code. "
? ? ? ? ? ? ? ? + "You cannot set public-read or public-read-write on an S3 bucket. "
? ? ? ? ? ? ? ? + "Read more about ACLs here: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html"
? ? ? ? ? ? )



s3_no_public_read = ResourceValidationPolicy(
? ? name="s3-no-public-read",
? ? description="Prohibits setting the publicRead or publicReadWrite permission on AWS S3 buckets.",
? ? validate=s3_no_public_read_validator,
)


##########################################################################################
##########################################################################################



# policy to check tags



def tag_policy_func(args: ResourceValidationArgs, report_violation: ReportViolation):
? ? if args.resource_type not in [
? ? ? ? "pulumi:pulumi:Stack",
? ? ? ? "pulumi:providers:aws",
? ? ? ? "pulumi:providers:random",
? ? ]:


? ? ? ? if not "tags" in args.props:
? ? ? ? ? ? report_violation("Resource is missing tags")
? ? ? ? ? ? return


? ? ? ? if not "Project" in args.props["tags"]:
? ? ? ? ? ? report_violation('Resource is missing required "Project" tag')



tagging_policy = ResourceValidationPolicy(
? ? name="require-name-tag",
? ? description="Enforces a 'Project' tag on all resources",
? ? validate=tag_policy_func,
)


##########################################################################################
##########################################################################################



# policy to check security group on open to the internet for SSH



def sg_policy_func(args: ResourceValidationArgs, report_violation):
? ? if args.resource_type != "aws:ec2/securityGroup:SecurityGroup":
? ? ? ? return


? ? if "ingress" not in args.props:
? ? ? ? return


? ? for ingress in args.props["ingress"]:
? ? ? ? if (
? ? ? ? ? ? ingress["fromPort"] == 22
? ? ? ? ? ? and ingress["toPort"] == 22
? ? ? ? ? ? and "0.0.0.0/0" in ingress["cidrBlocks"]
? ? ? ? ):
        if (
? ? ? ? ? ? ingress["fromPort"] == 3389
? ? ? ? ? ? and ingress["toPort"] == 3389
? ? ? ? ? ? and "0.0.0.0/0" in ingress["cidrBlocks"]
? ? ? ? ):
    ? ? ? ? ? ? report_violation(
    ? ? ? ? ? ? ? ? "Ingress rule allowing traffic on port 22 from 0.0.0.0/0 is not allowed"
    ? ? ? ? ? ? )
    
    
    
    sg_policy = ResourceValidationPolicy(
    ? ? name="no-inbound-ssh-rule",
    ? ? description="Prevents creating a security group with an inbound rule of 0.0.0.0/0 on port 22",
    ? ? validate=sg_policy_func,
    )
    
    
    ##########################################################################################
    ##########################################################################################
    
    
    
    # policy to check security group on open to the internet for SSH
    
    
    
    def rds_sg_policy_func(args: ResourceValidationArgs, report_violation):
    ? ? if args.resource_type != "aws:ec2/securityGroup:SecurityGroup":
    ? ? ? ? return
    
    
    ? ? if "ingress" not in args.props:
    ? ? ? ? return
    
    
    ? ? for ingress in args.props["ingress"]:
    ? ? ? ? if (
    ? ? ? ? ? ? ingress["fromPort"] == RDS_PORT
    ? ? ? ? ? ? and ingress["toPort"] == RDS_PORT
    ? ? ? ? ? ? and "0.0.0.0/0" in ingress["cidrBlocks"]
    ? ? ? ? ):
    ? ? ? ? ? ? report_violation(
    ? ? ? ? ? ? ? ? f"Ingress rule allowing traffic on port {RDS_PORT} from 0.0.0.0/0 is not allowed"
    ? ? ? ? ? ? )
    
    
    
    rds_sg_policy = ResourceValidationPolicy(
    ? ? name="no-inbound-open--rds-access",
    ? ? description=f"Prevents creating a security group with an inbound rule of 0.0.0.0/0 on port {RDS_PORT}",
    ? ? validate=rds_sg_policy_func,
    )
    
    
    ##########################################################################################
    ##########################################################################################
    
    
    
    # policy to prevent RDS instance that are open to public
    
    
    
    def rds_policy_func(args: ResourceValidationArgs, report_violation):
  ? ? if args.resource_type != "aws:rds/instance:Instance":
  ? ? ? ? return
  
  
  ? ? if "publiclyAccessible" in args.props and args.props["publiclyAccessible"]:
? ? ? ? report_violation("Prevents creating RDS instances that are open to the public")



rds_privacy_policy = ResourceValidationPolicy(
? ? name="no-public-rds",
? ? description="RDS privacy policy",
? ? validate=rds_policy_func,
)


policy_pack = PolicyPack(
? ? name="aws-python",
? ? enforcement_level=EnforcementLevel.MANDATORY,
? ? policies=[
? ? ? ? sg_policy,
? ? ? ? s3_no_public_read,
? ? ? ? tagging_policy,
? ? ? ? rds_privacy_policy,
? ? ? ? rds_sg_policy,
? ? ? ? resource_naming_convention,
? ? ? ? check_s3_bucket_naming_convention,
? ? ],
)        


I have written a sample stack which creates, S3 bucket, couple of security groups and RDS instance, we will run our policy pack against this stack:

import pulumi
import pulumi_aws as aws


TEST_NAME = "abc-xyz-test-dev-bla"
bucket = aws.s3.Bucket(
? ? "bucket",
? ? acl="private",
? ? bucket="abc-xyz-test-dev",
? ? tags={"Environment": "Dev", "Name": "test"},
)



sg_desc = "Allow SSH access from all IP addresses"
sg = aws.ec2.SecurityGroup(
? ? TEST_NAME,
? ? name=TEST_NAME,
? ? description=sg_desc,
? ? ingress=[
? ? ? ? aws.ec2.SecurityGroupIngressArgs(
? ? ? ? ? ? protocol="tcp", from_port=22, to_port=22, cidr_blocks=["0.0.0.0/0"]
? ? ? ? )
? ? ],
? ? tags={"Environment": "Dev", "Name": "Test-sg"},
)


# Create a security group that allows inbound access on port 3306
rds_security_group = aws.ec2.SecurityGroup(
? ? "rds-security-group",
? ? vpc_id="vpc-123456",
? ? name=TEST_NAME,
? ? description="Allow inbound access on port 3306",
? ? ingress=[
? ? ? ? {
? ? ? ? ? ? "protocol": "tcp",
? ? ? ? ? ? "from_port": 3306,
? ? ? ? ? ? "to_port": 3306,
? ? ? ? ? ? "cidr_blocks": ["0.0.0.0/0"],
? ? ? ? }
? ? ],
? ? tags={"Environment": "Dev", "Name": "RDS-sg"},
)


# Create an RDS instance
rds_instance = aws.rds.Instance(
? ? "my-rds-instance",
? ? allocated_storage=20,
? ? engine="mysql",
? ? engine_version="5.7",
? ? instance_class="db.t2.micro",
? ? name=TEST_NAME,
? ? username="admin",
? ? password="password123",
? ? publicly_accessible=True, ?# Allow public access
? ? db_subnet_group_name="my-db-subnet-group",
? ? parameter_group_name="default.mysql5.7",
? ? vpc_security_group_ids=rds_security_group.id,
? ? tags={"Environment": "production", "Name": "my-rds-instance"},
)        

Command to preview the stack against the policy:

pulumi preview --policy-pack <policy_pack_path>
For eg:
pulumi preview --policy-pack ..\policypack\        

Please Note: The command needs to be run from the same directory where you stack is.

On running the policy pack against the stack I got the following output:

No alt text provided for this image

As you can see, I got whole heap of notifications from policy pack.

So, after correcting issues with my stack, i.e. adding the tags, changing the ingress CIDR range and fixing RDS public accessible property, I ran the preview again, this time I got the:

No alt text provided for this image

Conclusion

As you can see, this is a really easy and great way to fool proof your stack against some of the best practices. You could integrate the policy packs in your CICD pipelines and run it as a pre-checks before deploying any stack.

Policy Pack is a powerful tool for enforcing policies on your cloud infrastructure as code. By defining policies as code, you can ensure consistency, automate policy enforcement, and improve compliance, naming conventions, and security. With Pulumi's Policy Pack, you can enforce policies across all your environments and projects, ensuring that your infrastructure meets the necessary requirements and best practices.

要查看或添加评论,请登录

Dinesh Sharma的更多文章

  • GenAI Powered Chatbot Using Bedrock and Lex

    GenAI Powered Chatbot Using Bedrock and Lex

    Ever wondered how to build your own AI-powered chatbot without diving deep into the complexities of machine learning?…

  • From Bicycle to Spaceship: Navigating the Cloud Transformation Journey

    From Bicycle to Spaceship: Navigating the Cloud Transformation Journey

    Cloud transformation is like a journey one that takes you from the streets of your neighborhood to destinations far…

    6 条评论
  • re:Invent 2024: Day 4 Recap

    re:Invent 2024: Day 4 Recap

    Keynote Highlights from Werner Vogels: Lessons in "Simplexity" Werner Vogels’ keynote at AWS re:Invent 2024 was, as…

  • re:Invent 2024 Day 3 Recap

    re:Invent 2024 Day 3 Recap

    It’s Day 3 of AWS re:Invent, and the cloud conference rollercoaster is in full swing! Today was extra special because…

  • re:Invent 2024: Day 2 Recap

    re:Invent 2024: Day 2 Recap

    After the excitement of Day 1 at AWS re:Invent, which I recapped yesterday, the momentum only picked up on Day 2! Today…

    1 条评论
  • re:Invent 2024: Day 1 Recap

    re:Invent 2024: Day 1 Recap

    AWS re:Invent isn’t just an event, it’s a full-blown tech carnival where innovation meets collaboration, sprinkled with…

  • Farewell to AWS Services: A Nerdy Goodbye

    Farewell to AWS Services: A Nerdy Goodbye

    Well, folks, it looks like AWS has decided to play the role of the grim reaper for some of its services. In a move that…

    1 条评论
  • Drawing AWS with Python (No Art Skills Required)

    Drawing AWS with Python (No Art Skills Required)

    Gone are the days when updating architecture diagrams was a tedious task that often got pushed to the back burner In…

    5 条评论
  • Schedule Your Fargate Pods Organization-Wide Using a magic Lambda

    Schedule Your Fargate Pods Organization-Wide Using a magic Lambda

    Spoiler Alert! There are no magic Lambdas here, just a carefully crafted Lambda function designed to automate the…

  • Scaling Deployments with AWS Lambda

    Scaling Deployments with AWS Lambda

    Hello LinkedIn :) Recently, I've had the pleasure of diving deep into a fascinating use-case involving AWS Lambda and…

社区洞察

其他会员也浏览了