Asi configuring-aws-verified-access-for-ztna
Configure AWS Verified Access to provide VPN-less zero trust network access to internal applications using identity and device posture verification with Cedar policy language.
git clone https://github.com/plurigrid/asi
T=$(mktemp -d) && git clone --depth=1 https://github.com/plurigrid/asi "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/asi/skills/configuring-aws-verified-access-for-ztna" ~/.claude/skills/plurigrid-asi-configuring-aws-verified-access-for-ztna && rm -rf "$T"
plugins/asi/skills/configuring-aws-verified-access-for-ztna/SKILL.mdConfiguring AWS Verified Access for ZTNA
Overview
AWS Verified Access is a Zero Trust Network Access (ZTNA) service that provides secure, VPN-less access to corporate applications hosted in AWS. It evaluates each access request in real-time against granular conditional access policies written in the Cedar policy language, ensuring access is granted per-application only when specific security requirements such as user identity and device security posture are met and maintained. Verified Access integrates with AWS IAM Identity Center, third-party identity providers (Okta, CrowdStrike, JumpCloud, Jamf), and device management solutions. For multi-account deployments, AWS Resource Access Manager (RAM) enables sharing Verified Access groups across organizational units.
When to Use
- When deploying or configuring configuring aws verified access for ztna capabilities in your environment
- When establishing security controls aligned to compliance requirements
- When building or improving security architecture for this domain
- When conducting security assessments that require this implementation
Prerequisites
- AWS account with appropriate IAM permissions
- Identity provider (AWS IAM Identity Center, Okta, or OIDC-compatible)
- Device trust provider (CrowdStrike, Jamf, JumpCloud, or AWS Verified Access native)
- Internal Application Load Balancer (ALB) or network interface endpoint
- Understanding of Cedar policy language
- VPC with application workloads to protect
Architecture
End User (Browser) | | HTTPS v +------+--------+ | Verified | | Access | | Endpoint | | (Public DNS) | +------+--------+ | +------+--------+ | Verified | <-- Cedar Access Policies | Access | <-- Identity Provider Signals | Instance | <-- Device Trust Signals | (Policy | | Evaluation) | +------+--------+ | +------+--------+ | Verified | | Access Group | | (App Group) | +------+--------+ | +------+--------+ | Internal ALB | | or ENI Target | +------+--------+ | +------+--------+ | Application | | (Private VPC) | +--------------+
Core Components
Verified Access Instance
The regional entity that evaluates access requests against policies.
# Create Verified Access Instance via AWS CLI aws ec2 create-verified-access-instance \ --description "Production Zero Trust Instance" \ --tag-specifications 'ResourceType=verified-access-instance,Tags=[{Key=Environment,Value=production}]'
Trust Providers
Identity Trust Provider (AWS IAM Identity Center)
# Create identity trust provider aws ec2 create-verified-access-trust-provider \ --trust-provider-type user \ --user-trust-provider-type iam-identity-center \ --policy-reference-name "idc" \ --description "IAM Identity Center trust provider" \ --tag-specifications 'ResourceType=verified-access-trust-provider,Tags=[{Key=Type,Value=identity}]'
Identity Trust Provider (OIDC - Okta)
aws ec2 create-verified-access-trust-provider \ --trust-provider-type user \ --user-trust-provider-type oidc \ --oidc-options '{ "Issuer": "https://company.okta.com/oauth2/default", "AuthorizationEndpoint": "https://company.okta.com/oauth2/default/v1/authorize", "TokenEndpoint": "https://company.okta.com/oauth2/default/v1/token", "UserInfoEndpoint": "https://company.okta.com/oauth2/default/v1/userinfo", "ClientId": "0oa1234567890", "ClientSecret": "client-secret-here", "Scope": "openid profile groups" }' \ --policy-reference-name "okta" \ --description "Okta OIDC trust provider"
Device Trust Provider (CrowdStrike)
aws ec2 create-verified-access-trust-provider \ --trust-provider-type device \ --device-trust-provider-type crowdstrike \ --device-options '{ "TenantId": "crowdstrike-tenant-id", "PublicSigningKeyUrl": "https://api.crowdstrike.com/zero-trust/v2/certificates" }' \ --policy-reference-name "crowdstrike" \ --description "CrowdStrike device trust provider"
Attach Trust Providers to Instance
# Attach identity provider aws ec2 attach-verified-access-trust-provider \ --verified-access-instance-id vai-0123456789abcdef \ --verified-access-trust-provider-id vatp-0123456789abcdef # Attach device provider aws ec2 attach-verified-access-trust-provider \ --verified-access-instance-id vai-0123456789abcdef \ --verified-access-trust-provider-id vatp-device123456
Verified Access Groups
# Create a group for web applications aws ec2 create-verified-access-group \ --verified-access-instance-id vai-0123456789abcdef \ --description "Production Web Applications" \ --policy-document 'permit(principal, action, resource) when { context.okta.groups.contains("production-access") && context.crowdstrike.assessment.overall > 50 };' \ --tag-specifications 'ResourceType=verified-access-group,Tags=[{Key=Tier,Value=web}]'
Verified Access Endpoints
# Create endpoint for ALB-backed application aws ec2 create-verified-access-endpoint \ --verified-access-group-id vag-0123456789abcdef \ --endpoint-type load-balancer \ --attachment-type vpc \ --domain-certificate-arn arn:aws:acm:us-east-1:123456789012:certificate/xxxx \ --application-domain app.internal.company.com \ --endpoint-domain-prefix myapp \ --load-balancer-options '{ "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/internal-alb/xxxx", "Port": 443, "Protocol": "https", "SubnetIds": ["subnet-abc123", "subnet-def456"] }' \ --security-group-ids sg-0123456789abcdef \ --description "Internal HR Application"
Cedar Policy Language
Policy Basics
// Allow access for users in the engineering group with compliant devices permit(principal, action, resource) when { context.okta.groups.contains("engineering") && context.crowdstrike.assessment.overall > 70 && context.crowdstrike.assessment.sensor_config.status == "active" }; // Deny access from unmanaged devices forbid(principal, action, resource) when { !context.crowdstrike.assessment.sensor_config.status == "active" };
Advanced Policy Examples
// Time-based access - only during business hours (UTC) permit(principal, action, resource) when { context.okta.groups.contains("contractors") && context.http_request.http_method == "GET" && context.crowdstrike.assessment.overall > 80 }; // Restrict admin access to specific user group with high device trust permit(principal, action, resource) when { context.idc.groups.contains("admins") && context.crowdstrike.assessment.overall > 90 && context.crowdstrike.assessment.os_version.startswith("Windows 11") || context.crowdstrike.assessment.os_version.startswith("macOS 14") }; // Allow read-only access for lower trust levels permit(principal, action, resource) when { context.okta.groups.contains("read-only") && context.crowdstrike.assessment.overall > 30 && context.http_request.http_method == "GET" };
Group-Level vs Endpoint-Level Policies
// Group-level policy (applies to all endpoints in the group) // Set on the Verified Access Group permit(principal, action, resource) when { context.okta.groups.contains("employees") && context.crowdstrike.assessment.overall > 50 }; // Endpoint-level policy (additional restrictions for specific app) // Set on the Verified Access Endpoint permit(principal, action, resource) when { context.okta.groups.contains("hr-team") && context.okta.email.endsWith("@company.com") };
Terraform Configuration
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } # Verified Access Instance resource "aws_verifiedaccess_instance" "main" { description = "Production Zero Trust Access" tags = { Environment = "production" } } # Identity Trust Provider (OIDC) resource "aws_verifiedaccess_trust_provider" "okta" { policy_reference_name = "okta" trust_provider_type = "user" user_trust_provider_type = "oidc" description = "Okta identity provider" oidc_options { authorization_endpoint = "https://company.okta.com/oauth2/default/v1/authorize" client_id = var.okta_client_id client_secret = var.okta_client_secret issuer = "https://company.okta.com/oauth2/default" scope = "openid profile groups" token_endpoint = "https://company.okta.com/oauth2/default/v1/token" user_info_endpoint = "https://company.okta.com/oauth2/default/v1/userinfo" } } # Device Trust Provider (CrowdStrike) resource "aws_verifiedaccess_trust_provider" "crowdstrike" { policy_reference_name = "crowdstrike" trust_provider_type = "device" device_trust_provider_type = "crowdstrike" description = "CrowdStrike device trust" device_options { tenant_id = var.crowdstrike_tenant_id } } # Attach providers to instance resource "aws_verifiedaccess_instance_trust_provider_attachment" "okta" { verifiedaccess_instance_id = aws_verifiedaccess_instance.main.id verifiedaccess_trust_provider_id = aws_verifiedaccess_trust_provider.okta.id } resource "aws_verifiedaccess_instance_trust_provider_attachment" "crowdstrike" { verifiedaccess_instance_id = aws_verifiedaccess_instance.main.id verifiedaccess_trust_provider_id = aws_verifiedaccess_trust_provider.crowdstrike.id } # Verified Access Group resource "aws_verifiedaccess_group" "web_apps" { verifiedaccess_instance_id = aws_verifiedaccess_instance.main.id description = "Production Web Applications" policy_document = <<-CEDAR permit(principal, action, resource) when { context.okta.groups.contains("production-access") && context.crowdstrike.assessment.overall > 50 }; CEDAR tags = { Tier = "web" } } # Verified Access Endpoint resource "aws_verifiedaccess_endpoint" "internal_app" { verified_access_group_id = aws_verifiedaccess_group.web_apps.id endpoint_type = "load-balancer" attachment_type = "vpc" domain_certificate_arn = aws_acm_certificate.app.arn application_domain = "app.internal.company.com" endpoint_domain_prefix = "myapp" description = "Internal Application" load_balancer_options { load_balancer_arn = aws_lb.internal.arn port = 443 protocol = "https" subnet_ids = var.private_subnet_ids } security_group_ids = [aws_security_group.verified_access.id] policy_document = <<-CEDAR permit(principal, action, resource) when { context.okta.groups.contains("app-users") }; CEDAR } # Logging configuration resource "aws_verifiedaccess_instance_logging_configuration" "main" { verifiedaccess_instance_id = aws_verifiedaccess_instance.main.id access_logs { cloudwatch_logs { enabled = true log_group = aws_cloudwatch_log_group.verified_access.name } s3 { enabled = true bucket_name = aws_s3_bucket.access_logs.id prefix = "verified-access/" } } } resource "aws_cloudwatch_log_group" "verified_access" { name = "/aws/verified-access/production" retention_in_days = 90 }
Multi-Account Deployment with AWS RAM
# Share Verified Access Group across accounts via RAM resource "aws_ram_resource_share" "verified_access" { name = "verified-access-share" allow_external_principals = false } resource "aws_ram_resource_association" "group_share" { resource_arn = aws_verifiedaccess_group.web_apps.verified_access_group_arn resource_share_arn = aws_ram_resource_share.verified_access.arn } resource "aws_ram_principal_association" "workload_ou" { principal = "arn:aws:organizations::123456789012:ou/o-xxxx/ou-xxxx-xxxxxxxx" resource_share_arn = aws_ram_resource_share.verified_access.arn }
Monitoring and Logging
# Query access logs in CloudWatch aws logs filter-log-events \ --log-group-name /aws/verified-access/production \ --filter-pattern '{ $.status_code = "403" }' \ --start-time $(date -d '1 hour ago' +%s000) # CloudWatch alarm for access denials aws cloudwatch put-metric-alarm \ --alarm-name "VerifiedAccess-HighDenialRate" \ --metric-name "AccessDenied" \ --namespace "AWS/VerifiedAccess" \ --statistic Sum \ --period 300 \ --threshold 100 \ --comparison-operator GreaterThanThreshold \ --evaluation-periods 2 \ --alarm-actions arn:aws:sns:us-east-1:123456789012:security-alerts
Security Best Practices
- Layer policies: Use group-level policies for broad controls and endpoint-level for app-specific restrictions
- Require device trust: Always include device posture checks in Cedar policies
- Enable access logging: Send to both CloudWatch and S3 for real-time monitoring and long-term retention
- Use RAM for multi-account: Share groups across OUs instead of duplicating configuration
- Rotate OIDC secrets: Automate client secret rotation via Secrets Manager
- Test policies in non-production: Validate Cedar policies before production deployment
- Set high device trust thresholds: Require overall score above 70 for production access
- Monitor for policy drift: Use AWS Config rules to detect unauthorized changes