Deploy Eve Guard into your own AWS account with a single CloudFormation stack. No Kubernetes, no cluster to build, no servers to patch — ECS Fargate runs the containers and AWS manages the hosts.
Before you start: request these two secrets from the Eve Customer Success team and have them in hand before installing — both are required:
- Eve license key (
evk_...)- Registry token (image pull access)
The template itself needs no request. It is public — it contains no secrets, only infrastructure — so you can launch it directly; see Section 4.
Running Kubernetes instead? See the EKS guide.
Create the ACM certificate for your gateway domain, in the same region you will deploy into.
Open the AWS CloudFormation console with this release’s Eve Guard
template (new tab). Change the console region if you are not deploying
to us-west-2 — the certificate must be in that same
region:
Fill in the parameters — there are seven you must supply.
Tick the IAM acknowledgement and create the stack.
Roughly 10 minutes later the stack outputs a GatewayUrl.
Point your MCP clients at it.
There is nothing to download, no CLI to install, and no files to edit.
Nothing to do, if you accept the default. Eve Guard checks weekly for
new releases and emails the address you gave it. Set
AutoUpdateChannel to patch at install time and
it applies them for you, with automatic rollback if live traffic starts
failing.
See Section 6: Updates for all three postures.
Delete the CloudFormation stack. Some things are kept on purpose:
Both are listed in Section 9: Uninstall with the commands to remove them once you are sure. Neither blocks reinstalling.
Five containers, in your VPC, in your account. Traffic never leaves your network except to reach the Eve platform for policy configuration.
| Service | Purpose | Tasks |
|---|---|---|
kong |
The gateway itself. Terminates MCP traffic and enforces policy. | 1 |
orchestrator |
Policy decision point. Analyses every request. | 4–8, autoscaled |
redis-state |
OAuth session state and pending policy evaluations. In-memory (same
as EKS redis-auth). |
1 |
redis-cache |
Prompt analysis cache. Disposable. | 1 |
rule-improver |
Optional. Proposes policy improvements from observed traffic. | 0 or 1 |
Alongside them the stack creates an Application Load Balancer, one Secrets Manager secret, CloudWatch log groups, and two small Lambda functions — one that validates your environment before anything else is built, and one that checks weekly for new releases.
Clients reach the ALB over HTTPS on 443. TLS terminates there using your ACM certificate; inside the VPC, services talk to each other over ECS Service Connect.
Rough monthly estimate for the default sizing in
us-west-2, on-demand pricing, excluding data transfer:
| Item | Estimate |
|---|---|
| Orchestrator, 4 tasks × 1 vCPU / 2 GB | ~$120 |
| Kong, 1 task × 1 vCPU / 2 GB | ~$30 |
| Redis, 2 tasks × 0.5 vCPU / 1 GB | ~$30 |
| Application Load Balancer | ~$20 |
| CloudWatch Logs, Lambda, Secrets Manager | ~$5 |
| Total | ~$205/month |
A NAT gateway, if you do not already have one, adds roughly $32/month
per availability zone. rule-improver adds roughly $30/month
when enabled. These are estimates from public pricing, not a quote.
The stack does not create a VPC. It uses yours. You need:
This is the single most common install failure, so it is worth checking before you start. The containers must reach two internet endpoints:
ghcr.io, to pull the Eve Guard imagesBoth are on the public internet, so VPC endpoints alone are not sufficient. There are two supported shapes:
| Shape | AssignPublicIp |
Notes |
|---|---|---|
| Private subnets with a NAT gateway | DISABLED |
Recommended. Tasks have no public IP. |
| Public subnets | ENABLED |
No NAT cost, but tasks get public IPs. |
The stack checks this for you before creating anything, and fails in
about 20 seconds with a readable message rather than 15 minutes later
with CannotPullContainerError.
The load balancer needs a certificate for your gateway domain, and
ACM certificates for an ALB must live in the same region as the
load balancer. A certificate in us-east-1 will not
work for a load balancer in eu-west-1. This is also checked
before install.
If you do not have one yet:
aws acm request-certificate \
--domain-name gateway.example.com \
--validation-method DNS \
--region us-west-2Complete the DNS validation and wait for status ISSUED
before installing.
By default the load balancer is reachable from the internet. If your
clients are all inside your own network, set
LoadBalancerScheme to internal and the gateway
never gets a public address.
What this needs:
| Setting | Value |
|---|---|
LoadBalancerScheme |
internal |
LoadBalancerSubnetIds |
Private subnets, at least two, in different AZs |
LoadBalancerAllowedCidr |
The range your clients come from |
DNS for GatewayDomainName |
A private hosted zone |
Two things catch people out.
Private subnets alone are not enough. AWS decides where a load
balancer lives from the scheme, not from the subnets you hand it, so
passing private subnet IDs to an internet-facing load
balancer does not make it private — it fails to create instead.
LoadBalancerAllowedCidr is the network your clients
reach the load balancer from, which is not always this VPC. If
your users come in over a VPN that lives in another account and peers
into this one, the range you want is that VPN’s VPC, not the one hosting
the gateway. Getting this wrong locks out the people who need it, which
at least fails loudly.
Also note that DNS validation for your ACM certificate happens over the public internet even when the gateway itself is private. The certificate proves the name, it does not expose the endpoint.
Switching an existing stack between the two schemes replaces the load balancer and changes its DNS name, so plan a DNS cutover rather than treating it as a routine parameter change.
You can, and the stack does not need to know about it. The load balancer is a normal ALB, so attach a WAFv2 web ACL to it whenever you like:
aws wafv2 associate-web-acl \
--web-acl-arn "arn:aws:wafv2:us-west-2:111122223333:regional/webacl/my-acl/abc" \
--resource-arn "$(aws cloudformation describe-stack-resources \
--stack-name eve-guard --logical-resource-id LoadBalancer \
--query 'StackResources[0].PhysicalResourceId' --output text)"The ACL must be REGIONAL scope and in this region; a
CLOUDFRONT scope ACL cannot attach to a load balancer.
This is deliberately outside the template. Your web ACL is yours — your rules, your rate limits, your logging, on your own change cadence — and putting the association in the stack would only add a way for a stack operation to detach it. It is also not a prerequisite: install first, add the WAF when you are ready.
Worth adding alongside LoadBalancerAllowedCidr
rather than instead of it. The security group decides who can open a
connection; the WAF inspects the requests of those allowed through.
Neither substitutes for the other.
EnableDeletionProtection makes the load balancer refuse
to be deleted. It is off by default, and worth turning
on once the gateway is actually serving traffic, because the ALB is the
one resource in this stack whose loss is immediately visible to every
client.
It is off by default because it also blocks
rollback. If a create or update fails after the load
balancer exists, CloudFormation tries to remove it, cannot, and leaves
the stack in ROLLBACK_FAILED for you to clear by hand.
First installs are when failures are most likely, so the protection
costs more than it saves until the stack is known good.
The preflight check catches the usual first-install failures — a certificate in the wrong region, subnets with no egress, a bad license key — before any resource exists, so most failures never reach the load balancer. It cannot predict every one.
Turn it on with an ordinary stack update once you are serving:
aws cloudformation update-stack --stack-name eve-guard \
--use-previous-template --capabilities CAPABILITY_NAMED_IAM \
--parameters ParameterKey=EnableDeletionProtection,ParameterValue=true \
ParameterKey=EveLicenseKey,UsePreviousValue=true \
ParameterKey=GhcrToken,UsePreviousValue=trueRemember to clear it before an uninstall — Section 9 has the command.
The stack creates IAM roles, so whoever runs it needs
CAPABILITY_IAM/CAPABILITY_NAMED_IAM. If your
organisation does not allow that, see Section 8 — Eve can run the
install with a narrowly scoped role instead.
| Parameter | Description |
|---|---|
EveLicenseKey |
Your Eve license key (evk_...). Stored in Secrets
Manager, never logged. |
GhcrToken |
Registry token from Eve, for pulling the images. |
VpcId |
The VPC to deploy into. |
TaskSubnetIds |
Two or more subnets, in different AZs, for the containers. |
LoadBalancerSubnetIds |
Two or more subnets for the load balancer. Public ones unless you
set LoadBalancerScheme to internal. |
CertificateArn |
ACM certificate ARN, in this region. |
GatewayDomainName |
The DNS name clients will use,
e.g. gateway.example.com. |
| Parameter | Default | Description |
|---|---|---|
EveVersion |
2.2.37 |
Version to deploy. Drives all four image tags together. |
OpenAiApiKey |
empty | Your own OpenAI key. Recommended — see the note below. |
AutoUpdateChannel |
notify-only |
off, notify-only, patch, or
minor. See Section 6. |
NotificationEmail |
empty | Where update and error notifications go. Strongly recommended. |
AssignPublicIp |
DISABLED |
Set to ENABLED only when using public task subnets with
no NAT. |
LoadBalancerScheme |
internet-facing |
internal keeps the gateway off the public internet. See
Keeping the gateway
private. |
LoadBalancerAllowedCidr |
0.0.0.0/0 |
Narrows who may reach the load balancer on 80 and 443. |
EnableDeletionProtection |
false |
Blocks deletion of the load balancer. Turn on once the gateway is serving — see Protecting a live gateway. |
OrchestratorMinTasks |
4 |
Minimum orchestrator tasks. |
OrchestratorMaxTasks |
8 |
Maximum orchestrator tasks. Scales on CPU 70% / memory 80%. |
EnableRuleImprover |
false |
Deploy the optional policy improvement service. |
LogRetentionDays |
30 |
CloudWatch Logs retention. |
| Parameter | Default | Description |
|---|---|---|
KongDesiredCount |
1 |
Kong task count. Read the warning below before raising. |
ExistingRedisCacheEndpoint |
empty | Use an existing ElastiCache cluster for the prompt cache instead of an in-stack task. |
KongCustomServicesYaml |
empty | Extra MCP routes. See Section 7. |
EvePlatformUrl |
Eve’s endpoint | Override only if Eve tells you to. |
LogLevel |
INFO |
DEBUG is very verbose; use it only while
diagnosing. |
GhcrUsername |
eve-installer |
Override only if Eve tells you to. |
About
OpenAiApiKey. Supplying your own key is the setup we recommend: LLM usage is then billed to your OpenAI account, and the key is held in Secrets Manager in your account rather than anywhere else. Set it at install and you are done.It is optional because Eve Guard can instead resolve a key from your license payload, which is worth knowing about for two reasons. First, if Eve has provisioned a key for your guard, that key wins — the license payload is read before the environment, so this parameter has no effect and changing it will not appear to do anything. Second, if you leave this empty and Eve has not provisioned one, the gateway installs and starts normally and then fails on the first request that needs an LLM, because there is no key to use. If you are unsure which applies to your guard, ask Eve before leaving it empty.
About
KongDesiredCount. Kong’s rate limiting counts per task, so raising this multiplies your effective rate limit by the task count. One Kong task handles substantial throughput because it only proxies — the analysis work happens in the orchestrator, which is the service that autoscales. Talk to Eve before raising this.
Open the Launch Eve Guard in AWS CloudFormation button in Quick install. The template is public — nothing in it is a secret — and it pre-fills the template URL, so you land directly on the parameter form.
eve-guard. Keep the
eve-guard prefix if Eve will manage updates for you,
because their access is scoped by stack name.Watch the Events tab. Custom::Preflight
runs first and completes within a minute; if your environment has a
problem, this is where you find out, with a message naming the specific
subnet, certificate or license issue.
If you supplied a NotificationEmail, confirm the SNS
subscription email that arrives shortly after — unconfirmed
subscriptions receive nothing.
The template URL below is public — no credentials or account
registration needed to fetch it, only the secrets in
--parameters are yours to keep. Confirm the current version
at https://install.eve.security/versions.json first
(.ecs.template_url), since versioned keys are immutable and
there is no latest.
aws cloudformation create-stack \
--stack-name eve-guard \
--template-url https://eve-cfn-templates.s3.us-west-2.amazonaws.com/v2.2.37/ecs/eve-guard-ecs.yaml \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameters \
ParameterKey=EveLicenseKey,ParameterValue=evk_xxx \
ParameterKey=GhcrToken,ParameterValue=ghp_xxx \
ParameterKey=VpcId,ParameterValue=vpc-0abc \
ParameterKey=TaskSubnetIds,ParameterValue=\"subnet-0aaa,subnet-0bbb\" \
ParameterKey=LoadBalancerSubnetIds,ParameterValue=\"subnet-0ccc,subnet-0ddd\" \
ParameterKey=CertificateArn,ParameterValue=arn:aws:acm:us-west-2:111122223333:certificate/abc \
ParameterKey=GatewayDomainName,ParameterValue=gateway.example.com \
ParameterKey=NotificationEmail,ParameterValue=platform@example.com
aws cloudformation wait stack-create-complete --stack-name eve-guardNote the escaped quotes around the subnet lists — CloudFormation needs the comma-separated list as a single value.
Read the outputs:
aws cloudformation describe-stacks --stack-name eve-guard \
--query 'Stacks[0].Outputs' --output tablePoint your domain at the load balancer. Create a
CNAME (or a Route 53 alias) from your GatewayDomainName to
the LoadBalancerDnsName output. Until you do, HTTPS
requests will fail certificate validation, because the certificate is
for your domain and not for the ALB’s own hostname.
Verify the gateway is healthy:
curl -sS https://gateway.example.com/statusPoint your MCP clients at the
GatewayUrl output.
# All five services should show runningCount matching desiredCount.
aws ecs list-services --cluster eve-guard --query 'serviceArns' --output table
aws ecs describe-services --cluster eve-guard \
--services kong orchestrator redis-state redis-cache \
--query 'services[].{name:serviceName,desired:desiredCount,running:runningCount}' \
--output table
# The load balancer's view. Kong should be "healthy".
aws elbv2 describe-target-health \
--target-group-arn "$(aws elbv2 describe-target-groups \
--query "TargetGroups[?contains(TargetGroupName,'eve')].TargetGroupArn" \
--output text)" \
--query 'TargetHealthDescriptions[].TargetHealth.State'Logs, per service:
aws logs tail /ecs/eve-guard/kong --follow
aws logs tail /ecs/eve-guard/orchestrator --followA healthy Kong startup logs its resolved policy flags:
🔧 Eve Kong runtime flags: mcp_only=false interrogation_enabled=false
✅ Kong ready - starting in foreground mode
Those values come from your license. If you instead see a line about the config bootstrap failing, Kong could not reach the Eve platform — see Troubleshooting.
Eve ships new features every few weeks. You never need to uninstall and reinstall. The stack updates in place: the load balancer, its DNS name, your secret and your persisted state all survive.
Three postures, set by AutoUpdateChannel:
| Posture | What happens |
|---|---|
notify-only (default) |
Weekly check; you get an email when a release is available. You apply it when ready. |
patch |
Patch releases (2.2.0 → 2.2.1) apply
automatically. |
minor |
Minor and patch releases (2.2.0 → 2.3.0)
apply automatically. |
off |
No checks, no notifications. You track releases yourself. |
notify-only is the default because automatically
updating a security gateway is a change-management decision that is
yours to make, not ours. If your process allows it, patch
is the lowest-effort safe option.
A small Lambda in your stack runs weekly. It fetches
https://install.eve.security/versions.json, compares the
latest version to what you are running, and if an update is
due it calls CloudFormation’s UpdateStack on its own stack
with the new version.
Three properties worth knowing:
cloudformation.amazonaws.com can assume,
which the Lambda passes to UpdateStack and cannot use
itself. So invoking the function, on purpose or by accident, grants
nobody those permissions.That second role also cannot change any IAM role or policy, including its own. An update that only moves the version never needs to, and refusing it means a scheduled function can never mint or widen a role. If a future release does change a role, the automatic update fails and rolls back, and you apply that release manually — the same escalation you get for a release that needs a new template.
You are notified by email in every case: update available, update started, update failed.
One consequence to know about. Once an automatic update has run, CloudFormation associates that execution role with your stack and reuses it for later operations unless you say otherwise. Because the role cannot change IAM, a later manual upgrade that does change a role will fail with
AccessDenied. Pass your own role explicitly to override it:aws cloudformation update-stack --role-arn <your-admin-or-deployment-role> ..., or pick IAM role on the console’s update screen. The same applies todelete-stack.
Two ways. From the console: Update stack →
Use current template → change EveVersion →
next, next, submit.
Or from the CLI, which is the same operation:
aws cloudformation update-stack \
--stack-name eve-guard \
--use-previous-template \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameters \
ParameterKey=EveVersion,ParameterValue=2.2.37 \
ParameterKey=EveLicenseKey,UsePreviousValue=true \
ParameterKey=GhcrToken,UsePreviousValue=true \
ParameterKey=VpcId,UsePreviousValue=true \
ParameterKey=TaskSubnetIds,UsePreviousValue=true \
ParameterKey=LoadBalancerSubnetIds,UsePreviousValue=true \
ParameterKey=CertificateArn,UsePreviousValue=true \
ParameterKey=GatewayDomainName,UsePreviousValue=true
aws cloudformation wait stack-update-complete --stack-name eve-guard--use-previous-template matters: it changes only the
image tags and never touches your parameters or the template.
Updates are rolling. ECS starts new tasks, waits for them to pass health checks, then drains the old ones. The deployment circuit breaker rolls back automatically if the new tasks fail to become healthy.
Most releases only move image tags. Every so often a release changes the infrastructure itself, and then you need the new template rather than the one already in your stack. Eve tells you when this applies, and the automatic updater will email you rather than attempting it:
aws cloudformation update-stack \
--stack-name eve-guard \
--template-url https://eve-cfn-templates.s3.us-west-2.amazonaws.com/v2.2.37/ecs/eve-guard-ecs.yaml \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameters ParameterKey=EveVersion,ParameterValue=2.2.37 # ... plus UsePreviousValue for the restCreate a change set first if you want to see exactly what will change:
aws cloudformation create-change-set \
--stack-name eve-guard --change-set-name review-new-template \
--template-url https://eve-cfn-templates.s3.us-west-2.amazonaws.com/v2.2.37/ecs/eve-guard-ecs.yaml \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameters ParameterKey=EveVersion,ParameterValue=2.2.37
aws cloudformation describe-change-set \
--stack-name eve-guard --change-set-name review-new-template \
--query 'Changes[].ResourceChange.{Action:Action,Resource:LogicalResourceId,Replace:Replacement}' \
--output tableRotate through the stack, not through Secrets Manager. Editing the secret directly works until the next stack update, which will overwrite it from the stack parameters.
aws cloudformation update-stack \
--stack-name eve-guard --use-previous-template \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameters \
ParameterKey=EveLicenseKey,ParameterValue=evk_new_value \
ParameterKey=GhcrToken,UsePreviousValue=true \
ParameterKey=EveVersion,UsePreviousValue=true # ... plus the restThe tasks pick up the new value when they next restart. To apply it immediately:
aws ecs update-service --cluster eve-guard --service orchestrator --force-new-deployment
aws ecs update-service --cluster eve-guard --service kong --force-new-deploymentBy default Eve Guard proxies MCP traffic through its built-in routes.
To add your own upstreams, pass Kong declarative YAML in
KongCustomServicesYaml:
services:
- name: internal-jira
url: https://jira.internal.example.com
routes:
- name: internal-jira-route
paths:
- /jiraThe parameter is capped at 4096 characters. Multi-line values are awkward in the console form, so for anything non-trivial use a parameters file:
aws cloudformation update-stack \
--stack-name eve-guard --use-previous-template \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameters file://params.jsonYour services are merged with Eve’s built-in routes; they never replace them.
Optional, and only if you would rather Eve drove installs and
upgrades than have a Lambda in your account with
UpdateStack rights.
Eve provides a second, much smaller template that creates two IAM roles. It fits in the console paste box, so there is no bucket access involved and the full template is available for review before deployment.
Collect everything in the table below and share it with the Eve Customer Success team before deploying the roles stack. Eve needs these values to run the install on your behalf and cannot look them up without access to your account.
| What | Example | Where to find it |
|---|---|---|
| VPC ID | vpc-0aaabbbbccccdddd |
VPC console → Your VPCs |
| Task subnet IDs (≥ 2, private, different AZs) | subnet-0aaa,subnet-0bbb |
VPC console → Subnets |
| Task subnets have NAT gateway? | Yes / No |
VPC console → Route Tables — check if the route table for the task
subnets has a 0.0.0.0/0 → nat-… route. If not, containers
need public IPs (AssignPublicIp=ENABLED) |
| Load balancer subnet IDs (≥ 2, public, different AZs) | subnet-0ccc,subnet-0ddd |
VPC console → Subnets |
| ACM certificate ARN | arn:aws:acm:us-east-2:…:certificate/… |
ACM console → Certificates |
| Gateway domain name | gateway.example.com |
The DNS name you will point at the load balancer |
| External ID | (generated by you) | Run openssl rand -hex 16, store securely, share with
Eve CS over a verified channel — prevents the confused
deputy attack |
| Eve AWS Account ID | (provided by Eve) | Request from Eve Customer Success — verify out of band, do not rely on email alone |
Once you have the Eve AWS Account ID and your External ID ready, launch the roles stack:
Launch Eve Guard installer roles in AWS CloudFormation
You enter the External ID on the parameter form, acknowledge IAM capabilities, and create the stack.
Once the stack is in CREATE_COMPLETE, go to the
Outputs tab and send the following values to the Eve
Customer Success team. Without these, Eve cannot proceed with the
install.
| Output | Description | Required |
|---|---|---|
InstallerRoleArn |
The role Eve will assume to operate in your account | ✅ |
DeploymentRoleArn |
The role CloudFormation will use to deploy resources | ✅ |
StackNamePrefix |
The stack name prefix Eve’s access is scoped to | ✅ |
Share these over a verified channel — the same one you used to exchange the External ID.
CLI equivalent (replace placeholders):
EVE_ACCOUNT_ID=XXXXXXXXXXXX # verify with Eve Customer Success
EXTERNAL_ID='from-eve-cs'
aws cloudformation create-stack \
--stack-name eve-guard-roles \
--template-url "$(curl -s https://install.eve.security/versions.json | jq -r .ecs.roles_template_url)" \
--capabilities CAPABILITY_NAMED_IAM \
--parameters \
ParameterKey=EveAccountId,ParameterValue="$EVE_ACCOUNT_ID" \
ParameterKey=ExternalId,ParameterValue="$EXTERNAL_ID" \
ParameterKey=GrantScope,ParameterValue=UpdateOnly \
ParameterKey=StackNamePrefix,ParameterValue=eve-guardThe two roles exist separately for a specific reason. A role that can run CloudFormation with IAM capabilities is effectively account administrator, because it can create a role with any policy attached. So:
eve-guard-deployment-role holds the
actual permissions. Only the CloudFormation service can assume it. Eve
cannot.eve-guard-installer-role is what Eve
assumes. On its own it can do almost nothing: start CloudFormation
operations on stacks named eve-guard or
eve-guard-*, and read diagnostics.Three controls make that split real:
cloudformation:RoleArn — Eve may only run
CloudFormation as the deployment role, never as some more privileged
role already in the account.cloudformation:TemplateUrl — create/update/changeset
must use Eve’s published eve-guard-ecs.yaml on
eve-cfn-templates (no free-form TemplateBody).
That stops a compromised Eve identity from substituting a template that
mints eve-guard-backdoor with admin rights.iam:PermissionsBoundary — the deployment role may only
create or mutate stack IAM roles when they carry the stack’s
<stack>-permissions-boundary managed policy.
Prefix-scoped PutRolePolicy alone is not enough to mint
unbound admin roles.There is also an explicit Deny on
secretsmanager:GetSecretValue for the installer role, and
the deployment role may only read secrets named under the stack prefix,
so dynamic references cannot exfiltrate unrelated secrets.
| Parameter | Default | Description |
|---|---|---|
EveAccountId |
required | Eve’s AWS account. Verify this out of band before installing. |
ExternalId |
required | Secret Eve must present when assuming the role. Generate with
openssl rand -hex 16 and share with Eve Customer Success
over a verified channel. Minimum 16 characters. |
GrantScope |
UpdateOnly |
UpdateOnly lets Eve keep you current but not create the
stack. InstallAndUpdate allows the initial install
too. |
StackNamePrefix |
eve-guard |
Eve’s access is limited to the stack named exactly this and to
stacks named <prefix>-*. A separately named stack
that merely starts with the same letters — eve-guardian,
say — is out of reach. |
Send Eve the InstallerRoleArn stack
output. Most enterprises install themselves and use
UpdateOnly.
The template’s IAM policies use ${AWS::Region} wildcards
rather than a literal region, because the same template is published
once and installed by customers in different regions. That means the
template alone cannot promise that Eve only ever operates in one region
— it inherits whichever region the install runs in.
If you need a single region enforced, do it with a Service Control Policy. An SCP is strictly stronger than anything the template could assert, for three reasons: it applies to every principal in the account including your own administrators, it cannot be altered by a stack update, and Eve has no permission to detach or edit it.
Attach the following to the account (or the OU) that hosts Eve Guard,
replacing us-east-1 with your approved region:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRegionalOperationsOutsideApprovedRegion",
"Effect": "Deny",
"NotAction": [
"iam:*",
"sts:*",
"organizations:*",
"account:*",
"route53:*",
"cloudfront:*",
"globalaccelerator:*",
"shield:*",
"waf:*",
"wafv2:*",
"support:*",
"health:*",
"trustedadvisor:*",
"budgets:*",
"servicequotas:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}
]
}Two notes before you apply it:
route53:* must stay in
NotAction. Eve Guard creates an
AWS::ServiceDiscovery::PrivateDnsNamespace for Service
Connect, and Cloud Map implements that by creating a private hosted
zone. Route 53 is a global service whose requests always carry
aws:RequestedRegion: us-east-1. If you deploy outside
us-east-1 and this line is missing, the install fails at
ServiceNamespace with an AccessDenied on
route53:CreateHostedZone.Independently of the SCP, the stack’s preflight check validates that every task and load balancer subnet belongs to the VPC you selected, and that the ACM certificate resolves in the deployment region. Both fail the install before any resource is created.
If your organisation mandates a standard permissions boundary on
every role, note that IAM allows a role to carry exactly one. A boundary
you supply would replace the stack’s, not add to it — so the ceiling the
stack relies on would become yours to maintain, and any permission it
omits surfaces as an AccessDenied mid-install rather than
as a validation error. For that reason the template does not accept a
boundary ARN as a parameter.
Use an SCP here as well, for the same three reasons it is the right answer for regions: it applies to every principal including your own administrators, a stack update cannot alter it, and Eve cannot detach or edit it. An SCP also composes with the stack’s boundaries instead of displacing them, so you keep both ceilings rather than trading one for the other.
Every role the stack creates lives under the IAM path
/<stack>/ and carries a
<stack>-*permissions-boundary managed policy, both of
which an SCP can condition on. The boundaries themselves are in the
published template, so you can read exactly what each role is capped at
before you install.
The path also separates one install from another. If you run two Eve
Guard stacks in the same account — eve-guard and
eve-guard-prod, say — their roles sit under
/eve-guard/ and /eve-guard-prod/ respectively,
and each stack’s own grants address only its own path. Neither install
can pass or modify the other’s roles, which a shared
eve-guard-* name prefix would otherwise allow.
# Only needed if you turned EnableDeletionProtection on. It is off by default.
# Skipping it when protection is on leaves the stack in DELETE_FAILED; clear
# the attribute and delete again to recover.
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn "$(aws cloudformation describe-stack-resources \
--stack-name eve-guard --logical-resource-id LoadBalancer \
--query 'StackResources[0].PhysicalResourceId' --output text)" \
--attributes Key=deletion_protection.enabled,Value=false
aws cloudformation delete-stack --stack-name eve-guard
aws cloudformation wait stack-delete-complete --stack-name eve-guardDeliberately retained, so an accidental delete is not a license-key-loss event:
| Resource | Why | Remove with |
|---|---|---|
| Credentials secret | Holds your license key | aws secretsmanager delete-secret --secret-id eve-guard/credentials --force-delete-without-recovery |
| CloudWatch log groups | Audit trail | aws logs delete-log-group --log-group-name /ecs/eve-guard/kong |
Reinstalling with the same stack name while the old secret still exists will fail, because the secret name is already taken. Either delete it as above, or use a different stack name.
If automatic updates were ever enabled, the stack deletes cleanly but leaves two IAM resources in your account:
| Resource | Name |
|---|---|
| The updater’s CloudFormation execution role | eve-guard-UpdaterExecutionRole-<generated> |
| Its permissions boundary | eve-guard-<stack uid>-update-execution-permissions-boundary |
This is a CloudFormation limitation rather than a choice. When the
updater applies a release it calls UpdateStack with that
role, and CloudFormation then keeps the role associated with the stack
for every later operation, including the delete. The role therefore runs
its own deletion, and the moment it deletes itself its credentials stop
working — anything still in flight fails with “The security token
included in the request is invalid”. Retaining the two is what lets
everything else, including the stack’s other ten IAM resources, delete
cleanly.
Neither resource can do anything on its own: the role is only assumable by CloudFormation, and nothing references it once the stack is gone. Removing them is optional, and leaving them does not block reinstalling — the boundary name carries the stack uid and the role name is generated, so a new install under the same stack name will not collide.
To remove them, delete the role first. IAM refuses
to delete a managed policy while it is still attached to a role as a
permissions boundary, so the reverse order fails with
DeleteConflict.
STACK=eve-guard # the stack name you deleted
# 1. The role. Its name is generated, so find it by the stack's IAM path.
ROLE=$(aws iam list-roles --path-prefix "/${STACK}/" \
--query "Roles[?contains(RoleName,'UpdaterExecutionRole')].RoleName" \
--output text)
aws iam delete-role-policy --role-name "$ROLE" --policy-name apply-stack-update
aws iam delete-role --role-name "$ROLE"
# 2. Only now the boundary, which is free once no role carries it.
POLICY=$(aws iam list-policies --scope Local \
--query "Policies[?ends_with(PolicyName,'-update-execution-permissions-boundary')].Arn" \
--output text)
aws iam delete-policy --policy-arn "$POLICY"If you have installed Eve Guard more than once, each install leaves its own pair and the two queries above return several results. Match them up by the stack uid in the boundary name before deleting, or you will remove the boundary belonging to an install that is still running.
The same retention applies if you turn automatic updates
off without deleting the stack. Setting
AutoUpdateChannel to off removes both
resources from the stack, and Retain leaves the boundary in
your account. Because its name is derived from the stack id rather than
the install date, turning automatic updates back on later tries to
create the same name again and the update fails with
EntityAlreadyExists.
So if you are re-enabling automatic updates on a stack that had them turned off, delete the leftover boundary first. Nothing is attached to it at that point, so it deletes on its own:
aws iam delete-policy --policy-arn "$(aws iam list-policies --scope Local \
--query "Policies[?PolicyName=='${STACK}-<uid>-update-execution-permissions-boundary'].Arn" \
--output text)"The <uid> is the fifth dash-separated field of
your stack id, which you can read from the stack’s
Overview tab, or with
aws cloudformation describe-stacks --stack-name "$STACK" --query 'Stacks[0].StackId'.
CannotPullContainerError
during installThe task subnets have no route to the internet, or the registry token is wrong.
# Which is it? This shows the actual error.
aws ecs describe-tasks --cluster eve-guard \
--tasks "$(aws ecs list-tasks --cluster eve-guard --desired-status STOPPED \
--query 'taskArns[0]' --output text)" \
--query 'tasks[0].{stopped:stoppedReason,containers:containers[].reason}'0.0.0.0/0 to a NAT gateway, or set
AssignPublicIp=ENABLED if they are public subnets.This is the stack telling you what to fix, and the message names the specific problem. Common ones:
| Message contains | Fix |
|---|---|
| “has no route to a NAT gateway” | Add a NAT gateway, or use public subnets with
AssignPublicIp=ENABLED. |
| “span only 1 availability zone” | Choose task subnets in two different AZs. |
| “was not found in this region” | The ACM certificate is in a different region. Request one in the deployment region. |
| “rejected the license key” | Check for a typo; if it looks right, ask Eve whether the key is active. |
| “missing supabase_url, …” | Your license is valid but the guard is not fully configured on Eve’s side. Contact Eve. |
The orchestrator is deliberately fail-closed: it refuses to start rather than serve traffic without a valid policy configuration. Check its logs first.
aws logs tail /ecs/eve-guard/orchestrator --since 10mredis-state. On a first install this is usually a
startup race that resolves itself within a few minutes. If it persists,
check that redis-state is running.aws ecs describe-services --cluster eve-guard --services redis-state \
--query 'services[0].{running:runningCount,events:events[:3].message}'No healthy Kong tasks behind the load balancer.
aws logs tail /ecs/eve-guard/kong --since 10mThe most common cause is Kong failing to start because of a
configuration error, which shows in its logs as a
kong prepare failure. The second most common is simply that
the tasks have not finished starting yet — Kong gets a 120-second grace
period before the ALB starts failing it.
The important thing to know: Kong is fail-open by design. If it cannot reach the Eve platform it keeps serving traffic with default settings rather than going down. That is the right behaviour for an inline gateway, and it means this failure is quiet. Look for it explicitly:
aws logs tail /ecs/eve-guard/kong --since 30m | grep -E "Eve config|runtime flags"runtime flags line at all — Kong did not attempt the
fetch. This means EVE_LICENSE_KEY or
EvePlatformUrl is empty; both are required before Kong will
call the platform.Expected behaviour, and nothing is broken — the previous version is still serving. Find out why:
aws cloudformation describe-stack-events --stack-name eve-guard \
--query 'StackEvents[?ResourceStatus==`UPDATE_FAILED`].[LogicalResourceId,ResourceStatusReason]' \
--output tableA UPDATE_FAILED on an ECS service usually means the new
task version could not pass its health checks. Its logs will say
why:
aws logs tail /ecs/eve-guard/orchestrator --since 30mEnabled on Kong and the orchestrator for diagnostics:
aws ecs execute-command --cluster eve-guard \
--task "$(aws ecs list-tasks --cluster eve-guard --service-name kong \
--query 'taskArns[0]' --output text)" \
--container kong --interactive --command "/bin/bash"Requires the Session Manager plugin for the AWS CLI.
Contact Eve Customer Success with:
aws cloudformation describe-stack-events --stack-name eve-guard --max-items 25aws logs tail /ecs/eve-guard/<service> --since 30mPlease do not include your license key, registry token or any secret value.