Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lethal Trifecta Mitigation Demo

Important

This is a demonstration, not a fully production-ready solution for isolating sensitive data. Ensure that you do a threat-model for your particular circumstances and environment.

This demo shows how to use Tailscale device posture attributes and Aperture to help mitigate the lethal trifecta.

Its web interface creates user-owned EC2 machines in one of two network classes:

  • noegress machines receive custom:hasEgress=false when their single-use device auth key is created.
  • open machines receive the custom:hasEgress=true attribute.

The example tailnet policy grants members access to devices tagged tag:aperture, with the Aperture connectors gated by device posture: a device with the explicit custom:hasEgress=false value may use both label:noCustomerData and label:hasCustomerData connectors, while a device with custom:hasEgress=true is limited to label:noCustomerData.

Layout

cmd/egressd/          web service that allows users to create EC2 instances
cmd/allowlist/        discovers the Tailscale control and DERP endpoints for Terraform
terraform/            creates the egress-restricted infrastructure, and an optional jump host
tool/terraform        downloads, verifies, and runs a pinned Terraform release
policy.hujson         example posture-gated tailnet policy

The restricted VPC allows TCP 443 only to the Tailscale control and DERP IPv4 and IPv6 addresses discovered during each Terraform plan or apply, plus the regional S3 IPv4 and IPv6 prefix lists. A default-deny Route 53 Resolver DNS Firewall permits only the corresponding Tailscale hostnames and the four S3 names: the regional and dual-stack endpoints, each plain and bucket-qualified.

The Tailscale .deb package is staged in an endpoint-scoped S3 bucket because restricted machines cannot reach the package repository.

Setup

The operator host needs Go, AWS credentials, Bash, curl, jq, unzip, and either sha256sum or shasum.

  1. Create the infrastructure:

    tool/terraform -chdir=terraform init
    tool/terraform -chdir=terraform apply

    To include the isolated SSH jump host, also set jump_host_enable=true, an existing aws_key_name, and at least one of jump_host_ssh_cidrs or jump_host_ssh_ipv6_cidrs.

    Terraform downloads a pinned Tailscale package, verifies its hash, and stages it in the S3 bucket during the apply. To upgrade the package, update the version, URL, and checksum in terraform/bootstrap_s3.tf.

  2. Apply policy.hujson to a test tailnet, and add Aperture to the tailnet with the tag:aperture tag.

  3. Apply the hasCustomerData or noCustomerData labels to connectors in Aperture.

  4. Get a Tailscale API access token from the console: https://console.tailscale.com/admin/settings/keys

  5. Create a device-provisioning OAuth app with the callback URL and allowed node attributes:

    curl -X POST "https://api.tailscale.com/api/v2/tailnet/-/oauth-apps" \
      -u "<api-access-token>:" \
      -H "Content-Type: application/json" \
      -o oauth-credentials.json \
      -d '{
        "name": "lethal-trifecta-mitigation-demo",
        "redirectUris": ["http://localhost:8080/oauth/callback"],
        "scopes": ["auth_keys:create:once"],
        "allowedNodeAttributes": ["custom:hasEgress"]
      }'

    Custom posture attributes require a supported Tailscale plan.

  6. Run the service:

    export TS_OAUTH_APP_CLIENT_SECRET=tskey-app-...
    go run ./cmd/egressd \
      --region us-east-2 \
      --deb-url "$(tool/terraform -chdir=terraform output -raw bootstrap_deb_url)"

    Also pass the --aws-key-name flag when using the jump host.

  7. Open http://localhost:8080, choose a machine class, and complete the Tailscale consent screen.

  8. Wait a minute or two for it to boot, then use the displayed SSH command to connect to the VM via Tailscale SSH.

What to observe

Create one machine of each class. In the Tailscale admin console, the restricted device has the posture attribute custom:hasEgress=false; the open device has custom:hasEgress=true instead.

If you configure Aperture as a MCP proxy in your agent of choice, you can see that:

  • From the restricted machine, the agent is able to use any proxied MCP server with either the label hasCustomerData or noCustomerData, but cannot reach the internet.
  • From an open machine, only MCP servers tagged with noCustomerData can be reached, and the machine is able to reach the internet.

Future work

The restricted VPC currently allows communication with Tailscale's public DERP servers. A root-level agent could join another tailnet -- using, for example, a reusable auth key supplied through prompt injection -- and regain internet access through an exit node on a different tailnet.

Deployments concerned about this attack can run a custom DERP server with --verify-clients, admit only devices from the intended tailnet, and allowlist only that DERP server's IP addresses instead of the IPs for the public DERP servers. This ensures that even if a prompt-injected device is able to join an arbitrary tailnet, it will not be able to establish connectivity with anyone.

In order to allow full DERP functionality, ports other than TCP 443 should be allowed through the firewall; at the very least, UDP port 3478 (STUN) and TCP port 80 (HTTP) should be allowed. While the demo works without them, this is not a future guarantee.

About

No description, website, or topics provided.

Resources

Code of conduct

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages