Policy as a code: Pulumi + AWS
Dinesh Sharma
Mentor | AWS Ambassador | Puluminary | AWS Community Builder | AWS Regional Practice Lead | IaC Ninja | Desi | PB13 | RJ19
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:
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:
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:
领英推荐
Policy Pack
The below policy pack checks:
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:
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:
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.