VPC verified access using Pulumi
Dinesh Sharma
AWS Regional Practice Lead | AWS Ambassador | Puluminary | AWS Community Builder | Mentor | IaC Ninja | Desi | PB13 | RJ19
As cloud environments continue to evolve, ensuring secure and streamlined network connectivity becomes paramount. Amazon Web Services (AWS) has introduced a new feature called VPC Verified Access, built on Zero Trust guiding principles, offering enhanced security and simplified access management for Virtual Private Cloud (VPC) resources. In this blog post, we will explore what VPC Verified Access is, its key features, and the use cases where it can be beneficial.
What is VPC Verified Access?
VPC Verified Access is a feature provided by AWS that enables secure access to resources within a VPC by leveraging client-side TLS certificates. It is built on Zero Trust principles, which assume that no network or user can be inherently trusted. With VPC Verified Access, organizations adopt a Zero Trust approach to network security, ensuring that every access request is verified and authorized, regardless of its origin or location.
Key Features of VPC Verified Access
Client-Side TLS Certificates
VPC Verified Access utilizes client-side TLS certificates for authentication and encryption. Clients are required to present a valid TLS certificate during the connection establishment process, ensuring that only authorized clients can access VPC resources. This approach aligns with the Zero Trust principle of authenticating and verifying every access attempt.
Secure Connectivity
By leveraging TLS encryption, VPC Verified Access ensures secure communication between clients and VPC resources. The encryption protects sensitive data in transit, guarding against eavesdropping or tampering attempts. This encryption layer adds an additional level of security, reducing the risk of data breaches and unauthorized access.
Simplified Access Management
With VPC Verified Access, managing access to VPC resources becomes more streamlined. Instead of managing complex firewall rules or IP whitelists, administrators can focus on managing client certificates and associating them with the appropriate resources. This simplifies access control and reduces the administrative overhead, while adhering to the Zero Trust principle of least privilege.
Granular Access Control
VPC Verified Access provides fine-grained control over resource-level access. By associating specific client certificates with individual resources or groups of resources, administrators can enforce access policies based on the Zero Trust principle of granting access only when necessary. This ensures that each client has access only to the resources they require, minimizing the attack surface and enhancing overall security.
Use Cases for VPC Verified Access
Application-to-Application Communication
VPC Verified Access is well-suited for secure communication between applications running in different VPCs or across different AWS accounts. By authenticating and encrypting communication using client-side TLS certificates, organizations can establish secure connections between their distributed applications without the need for complex VPN setups. This approach aligns with the Zero Trust principle of verifying and securing all communication channels.
Third-Party Integration:
When integrating with external partners or vendors, organizations can leverage VPC Verified Access to establish secure connections. By exchanging client-side TLS certificates with trusted partners, organizations can ensure that only authorized entities can access their VPC resources, minimizing the risk of unauthorized access. This use case follows the Zero Trust principle of verifying the identity and authorization of all entities, including third parties.
Secure API Gateway Access
VPC Verified Access can be used to secure access to API Gateway endpoints deployed within a VPC. By requiring client-side TLS certificates, organizations can authenticate and authorize incoming requests to their APIs, mitigating the risks associated with unauthorized access or API abuse. This approach aligns with the Zero Trust principle of securing all access points and validating each request.
Architecture Overview
We will explore the power of VPC Verified Access by demonstrating a simple architecture using an EC2 instance fronted by an Application Load Balancer (ALB). We will leverage the capabilities of Pulumi, an infrastructure as code platform, to create this stack. Furthermore, we will highlight the significance of VPC Verified Access, emphasizing its availability on Pulumi while other popular platforms like Terraform are yet to release an official version.
Code
The code is structured into four main parts, each serving a specific purpose:
By dividing the code into these distinct parts, it becomes easier to manage and understand each component's functionality and purpose within the overall deployment architecture.
领英推荐
Declaring environment variables and creating cedar policy
# defining config file
with open("./config.json") as config_file:
? ? data = json.load(config_file)
# defining userdata file
with open("userdata.sh", "r") as file:
? ? user_data_script = file.read()
# decalaring env variables
STACK_NAME = data["STACK_NAME"]
PRIVATE_SUBNET_IDS = data["PRIVATE_SUBNET_IDS"]
VPC_ID = data["VPC_ID"]
INSTANCE_TYPE = data["INSTANCE_TYPE"]
CLOUDWATCH_LOGS = data["CLOUDWATCH_LOGS"]
AMI = data["AMI"]
DOMAIN = data["DOMAIN"]
APPLICATION_DOMAIN = data["APPLICATION_DOMAIN"]
POLICY_REF_NAME = data["POLICY_REF_NAME"]
EMAIL_DOMAIN = data["EMAIL_DOMAIN"]
GROUP_ID = data["GROUP_ID"]
HTTPS_PORT = data["HTTPS_PORT"]
HTTP_PORT = data["HTTP_PORT"]
PROTOCOL = data["PROTOCOL"]
# getting acm cert
ssl_cert = aws.acm.get_certificate(domain=DOMAIN, statuses=["ISSUED"])
# cedar policy
cedar_policy = """
? ? ? ? permit(principal, action, resource)
? ? ? ? when {
? ? ? ? ? ? context.{POLICY_REF_NAME}.groups has "{GROUP_ID}" &&
? ? ? ? ? ? context.{POLICY_REF_NAME}.user.email.address like "*@{EMAIL_DOMAIN}" &&
? ? ? ? ? ? context.{POLICY_REF_NAME}.user.email.verified == true
? ? ? ? };
? ? ? ? """
cedar_policy_env_vars = {
? ? "{POLICY_REF_NAME}": POLICY_REF_NAME,
? ? "{GROUP_ID}": GROUP_ID,
? ? "{EMAIL_DOMAIN}": EMAIL_DOMAIN,
}
for env_var, var_name in cedar_policy_env_vars.items():
? ? var_value = var_name
? ? if var_value:
? ? ? ? cedar_policy = re.sub(re.escape(env_var), var_value, cedar_policy)
You must be wondering what is cedar policy now?
It ?is a authorization policy language used by customers of the Amazon Verified Permissions and AWS Verified Access managed services.?(link)
Creating ec2 stack
# function to create a stack for ec2
def ec2_stack():
? ? # creating a security group for ec2 instance
? ? ec2_sg = aws.ec2.SecurityGroup(
? ? ? ? f"{STACK_NAME}-ec2-sg",
? ? ? ? vpc_id=VPC_ID,
? ? ? ? ingress=[
? ? ? ? ? ? aws.ec2.SecurityGroupIngressArgs(
? ? ? ? ? ? ? ? from_port=HTTP_PORT,
? ? ? ? ? ? ? ? to_port=HTTP_PORT,
? ? ? ? ? ? ? ? protocol=PROTOCOL,
? ? ? ? ? ? ? ? cidr_blocks=["0.0.0.0/0"],
? ? ? ? ? ? )
? ? ? ? ],
? ? ? ? egress=[
? ? ? ? ? ? aws.ec2.SecurityGroupEgressArgs(
? ? ? ? ? ? ? ? from_port=0,
? ? ? ? ? ? ? ? to_port=0,
? ? ? ? ? ? ? ? protocol="-1",
? ? ? ? ? ? ? ? cidr_blocks=["0.0.0.0/0"],
? ? ? ? ? ? )
? ? ? ? ],
? ? ? ? tags={
? ? ? ? ? ? "Name": f"{STACK_NAME}-ec2-sg",
? ? ? ? },
? ? )
? ? # creating a sample ec2 instance which wil be fronted by alb
? ? ec2_instance = aws.ec2.Instance(
? ? ? ? f"{STACK_NAME}-ec2",
? ? ? ? instance_type=INSTANCE_TYPE,
? ? ? ? subnet_id=PRIVATE_SUBNET_IDS[0],
? ? ? ? vpc_security_group_ids=[ec2_sg.id],
? ? ? ? ami=AMI,
? ? ? ? user_data=user_data_script,
? ? ? ? tags={
? ? ? ? ? ? "Name": f"{STACK_NAME}-ec2",
? ? ? ? },
? ? )
? ? return (ec2_sg, ec2_instance)
Creating ALB stack
# function to create a stack for alb
def alb_stack(ec2_sg_id, ec2_instance_id):
? ? # creating a security group for alb instance
? ? alb_sg = aws.ec2.SecurityGroup(
? ? ? ? f"{STACK_NAME}-alb-sg",
? ? ? ? vpc_id=VPC_ID,
? ? ? ? ingress=[
? ? ? ? ? ? aws.ec2.SecurityGroupIngressArgs(
? ? ? ? ? ? ? ? from_port=HTTPS_PORT,
? ? ? ? ? ? ? ? to_port=HTTPS_PORT,
? ? ? ? ? ? ? ? protocol=PROTOCOL,
? ? ? ? ? ? ? ? cidr_blocks=["0.0.0.0/0"],
? ? ? ? ? ? )
? ? ? ? ],
? ? ? ? egress=[
? ? ? ? ? ? aws.ec2.SecurityGroupEgressArgs(
? ? ? ? ? ? ? ? from_port=0,
? ? ? ? ? ? ? ? to_port=0,
? ? ? ? ? ? ? ? protocol="-1",
? ? ? ? ? ? ? ? cidr_blocks=["0.0.0.0/0"],
? ? ? ? ? ? )
? ? ? ? ],
? ? ? ? tags={
? ? ? ? ? ? "Name": f"{STACK_NAME}-alb-sg",
? ? ? ? },
? ? )
? ? ec2_sg_rule = aws.ec2.SecurityGroupRule(
? ? ? ? f"{STACK_NAME}-ec2-sg-rule",
? ? ? ? type="ingress",
? ? ? ? from_port=HTTP_PORT,
? ? ? ? to_port=HTTP_PORT,
? ? ? ? protocol=PROTOCOL,
? ? ? ? source_security_group_id=alb_sg,
? ? ? ? security_group_id=ec2_sg_id,
? ? )
? ? # creating an internal ALB
? ? load_balancer = aws.alb.LoadBalancer(
? ? ? ? f"{STACK_NAME}-alb",
? ? ? ? name=f"{STACK_NAME}-alb",
? ? ? ? internal=True,
? ? ? ? load_balancer_type="application",
? ? ? ? security_groups=[alb_sg.id],
? ? ? ? subnets=PRIVATE_SUBNET_IDS,
? ? ? ? tags={
? ? ? ? ? ? "Name": f"{STACK_NAME}-alb",
? ? ? ? },
? ? )
? ? # creating a target group with target type 'instance'
? ? target_group = aws.alb.TargetGroup(
? ? ? ? f"{STACK_NAME}-tg",
? ? ? ? port=HTTP_PORT,
? ? ? ? protocol="HTTP",
? ? ? ? target_type="instance",
? ? ? ? vpc_id=VPC_ID,
? ? ? ? tags={
? ? ? ? ? ? "Name": f"{STACK_NAME}-tg",
? ? ? ? },
? ? )
? ? # attaching the EC2 instance to the target group
? ? target_group_attachment = aws.alb.TargetGroupAttachment(
? ? ? ? f"{STACK_NAME}-tg-attachment",
? ? ? ? target_group_arn=target_group.arn,
? ? ? ? target_id=ec2_instance_id,
? ? ? ? port=HTTP_PORT,
? ? )
? ? # creating a listener
? ? listener = aws.alb.Listener(
? ? ? ? f"{STACK_NAME}-listener",
? ? ? ? load_balancer_arn=load_balancer.arn,
? ? ? ? port=HTTPS_PORT,
? ? ? ? protocol="HTTPS",
? ? ? ? ssl_policy="ELBSecurityPolicy-TLS13-1-2-2021-06",
? ? ? ? certificate_arn=ssl_cert.arn,
? ? ? ? default_actions=[
? ? ? ? ? ? {
? ? ? ? ? ? ? ? "type": "forward",
? ? ? ? ? ? ? ? "target_group_arn": target_group.arn,
? ? ? ? ? ? }
? ? ? ? ],
? ? )
? ? return load_balancer
Creating VPC verified stack
Finally, we create the VPC Verified stack, which primarily focuses on enabling VPC Verified Access. However, there are a few important considerations to keep in mind:
Please Note: It's crucial to ensure that the "POLICY_REF_NAME" environment variable matches the "policy_reference_name" when creating the Cedar policy and configuring the trust provider. This is an important detail that can sometimes be overlooked and may result in unexpected behavior. I personally encountered this issue and spent a significant amount of time troubleshooting before realizing the mismatched names.
# function to create a stack for voc verified access
def vpc_verified_access(load_balancer_arn):
? ? # creating a security group for vpc verified access
? ? vpc_verified_access_sg = aws.ec2.SecurityGroup(
? ? ? ? f"{STACK_NAME}-sg",
? ? ? ? vpc_id=VPC_ID,
? ? ? ? ingress=[
? ? ? ? ? ? aws.ec2.SecurityGroupIngressArgs(
? ? ? ? ? ? ? ? from_port=HTTPS_PORT,
? ? ? ? ? ? ? ? to_port=HTTPS_PORT,
? ? ? ? ? ? ? ? protocol=PROTOCOL,
? ? ? ? ? ? ? ? cidr_blocks=["0.0.0.0/0"],
? ? ? ? ? ? )
? ? ? ? ],
? ? ? ? egress=[
? ? ? ? ? ? aws.ec2.SecurityGroupEgressArgs(
? ? ? ? ? ? ? ? from_port=0,
? ? ? ? ? ? ? ? to_port=0,
? ? ? ? ? ? ? ? protocol="-1",
? ? ? ? ? ? ? ? cidr_blocks=["0.0.0.0/0"],
? ? ? ? ? ? )
? ? ? ? ],
? ? ? ? tags={
? ? ? ? ? ? "Name": f"{STACK_NAME}-sg",
? ? ? ? },
? ? )
? ? # creating a cloudwatch log group for vvpc verified access
? ? cloudwatch_log_group = aws.cloudwatch.LogGroup(
? ? ? ? f"{STACK_NAME}-log-group",
? ? ? ? tags={
? ? ? ? ? ? "Name": f"{STACK_NAME}-log-group",
? ? ? ? },
? ? )
? ? # creating vpc verified access stack
? ? trust_provider = aws_native.ec2.VerifiedAccessTrustProvider(
? ? ? ? f"{STACK_NAME}-trust-provider",
? ? ? ? policy_reference_name=POLICY_REF_NAME,
? ? ? ? trust_provider_type="user",
? ? ? ? user_trust_provider_type="iam-identity-center",
? ? ? ? tags=[
? ? ? ? ? ? aws_native.ec2.VerifiedAccessTrustProviderTagArgs(
? ? ? ? ? ? ? ? key="Name",
? ? ? ? ? ? ? ? value=f"{STACK_NAME}-trust-provider",
? ? ? ? ? ? )
? ? ? ? ],
? ? )
? ? verified_access_instance = aws_native.ec2.VerifiedAccessInstance(
? ? ? ? f"{STACK_NAME}-instance",
? ? ? ? verified_access_trust_provider_ids=[trust_provider],
? ? ? ? logging_configurations=aws_native.ec2.VerifiedAccessInstanceVerifiedAccessLogsArgs(
? ? ? ? ? ? cloud_watch_logs=aws_native.ec2.VerifiedAccessInstanceVerifiedAccessLogsCloudWatchLogsPropertiesArgs(
? ? ? ? ? ? ? ? enabled=CLOUDWATCH_LOGS, log_group=cloudwatch_log_group
? ? ? ? ? ? )
? ? ? ? ),
? ? ? ? tags=[
? ? ? ? ? ? aws_native.ec2.VerifiedAccessInstanceTagArgs(
? ? ? ? ? ? ? ? key="Name",
? ? ? ? ? ? ? ? value=f"{STACK_NAME}-instance",
? ? ? ? ? ? )
? ? ? ? ],
? ? )
? ? verified_access_group = aws_native.ec2.VerifiedAccessGroup(
? ? ? ? f"{STACK_NAME}-group",
? ? ? ? verified_access_instance_id=verified_access_instance,
? ? ? ? policy_document=cedar_policy,
? ? ? ? tags=[
? ? ? ? ? ? aws_native.ec2.VerifiedAccessGroupTagArgs(
? ? ? ? ? ? ? ? key="Name",
? ? ? ? ? ? ? ? value=f"{STACK_NAME}-group",
? ? ? ? ? ? )
? ? ? ? ],
? ? )
? ? verified_access_endpoint = aws_native.ec2.VerifiedAccessEndpoint(
? ? ? ? f"{STACK_NAME}-endpoint",
? ? ? ? application_domain=APPLICATION_DOMAIN,
? ? ? ? domain_certificate_arn=ssl_cert.arn,
? ? ? ? endpoint_domain_prefix=STACK_NAME,
? ? ? ? verified_access_group_id=verified_access_group,
? ? ? ? attachment_type="vpc",
? ? ? ? endpoint_type="load-balancer",
? ? ? ? security_group_ids=[vpc_verified_access_sg],
? ? ? ? load_balancer_options=aws_native.ec2.VerifiedAccessEndpointLoadBalancerOptionsArgs(
? ? ? ? ? ? load_balancer_arn=load_balancer_arn,
? ? ? ? ? ? port=HTTPS_PORT,
? ? ? ? ? ? protocol="https",
? ? ? ? ? ? subnet_ids=PRIVATE_SUBNET_IDS,
? ? ? ? ),
? ? ? ? tags=[
? ? ? ? ? ? aws_native.ec2.VerifiedAccessEndpointTagArgs(
? ? ? ? ? ? ? ? key="Name",
? ? ? ? ? ? ? ? value=f"{STACK_NAME}-endpoint",
? ? ? ? ? ? )
? ? ? ? ],
? ? )
Finally executing the 3 functions
ec2 = ec2_stack()
alb = alb_stack(ec2[0], ec2[1])
vpc_verified_access(alb.arn)
Testing
One noticeable aspect is that when attempting to access the application, you will encounter an AWS SSO sign-in window.
Utilize your AWS SSO credentials to log in. After successful authentication, you will gain access to your application. If you are using the provided code, you can expect to see a result similar to the example below:
Overall, I found VPC Verified Access to be a remarkable solution that greatly simplifies the setup process and alleviates many challenges. It streamlines the configuration steps and eliminates unnecessary complexities, making it a hassle-free experience. With VPC Verified Access, I was able to focus more on building and securing my applications, rather than getting bogged down by intricate authentication and authorization mechanisms.