kaveo · Guides
Connect cloud accounts
Connect AWS through a read-only cross-account role with an ExternalId, then optionally Azure, GCP, Kubernetes and GitHub, and run your first real scan.
On this page
kaveo scans with read-only access that you grant. For AWS, the worker assumes a read-only IAM role that trusts only kaveo's identity and requires an ExternalId, so kaveo holds no access keys for your account. For Azure, GCP, Kubernetes and GitHub, kaveo stores a reference to a read-only credential that stays in your deployment's environment.
Start from a running stack (see Quick start). Registering and verifying accounts, setting schedules and the organizations helper need manage_accounts, which only admins have. API tokens default to view_findings and run_scan, so mint an onboarding token with kaveo auth token create --name onboarding --scope manage_accounts --scope run_scan --scope view_findings.
AWS#
AWS support is built into the default image. It has 44 read-only collectors and 93 detectors.
1. Give kaveo base credentials#
The worker assumes your role from kaveo's own AWS identity. Run kaveo on EC2 or ECS to use the instance or task role. Otherwise, set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and, for temporary credentials, AWS_SESSION_TOKEN in .env. Compose passes them to the api and worker. For the IMDSv2 hop limit on EC2 and for named profiles, see Deployment.
In production, set KAVEO_SCAN_MODE=aws. A missing credential then fails the scan instead of producing demo data. To check which ARN kaveo runs as:
docker compose --env-file .env -f infra/docker-compose.yml exec worker \
python -c "import boto3; print(boto3.client('sts').get_caller_identity()['Arn'])"
If you get an assumed-role session ARN back, use the role's IAM ARN as the principal instead: arn:aws:iam::<account-id>:role/<role-name>.
2. Deploy the read-only role#
Deploy the role in each account you want to scan. The role kaveo-readonly trusts only the principal you pass, requires a matching sts:ExternalId and attaches the AWS-managed read-only policies SecurityAudit and job-function/ViewOnlyAccess. Sessions last at most one hour and are named kaveo-scan in CloudTrail. Use an ExternalId of at least 8 characters, such as the output of openssl rand -hex 16.
aws cloudformation deploy \
--template-file infra/aws/kaveo-readonly-role.cfn.yaml \
--stack-name kaveo-readonly \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides \
KaveoPrincipalArn=arn:aws:iam::<kaveo-account-id>:role/<kaveo-identity> \
ExternalId=<a-strong-shared-secret>
aws cloudformation describe-stacks --stack-name kaveo-readonly \
--query "Stacks[0].Outputs" --output table
The RoleArn output is the ARN you register. To use Terraform instead, apply the file from its own directory. infra/aws also holds the remediation write-role module, and that module declares the same variables:
mkdir kaveo-readonly && cp infra/aws/kaveo-readonly-role.tf kaveo-readonly/
cd kaveo-readonly
terraform init
terraform apply \
-var 'kaveo_principal_arn=arn:aws:iam::<kaveo-account-id>:role/<kaveo-identity>' \
-var 'external_id=<a-strong-shared-secret>'
Its output is role_arn. The console's Connect account form can also create this role for you with a fresh ExternalId. With KAVEO_CFN_TEMPLATE_URL set, it shows Launch stack in AWS. Otherwise, it gives you one AWS CLI command to run in CloudShell. Under Advanced, it also shows the role as CloudFormation, Terraform, AWS CLI, CDK or JSON.
3. Register the account#
In the console, open Connect account and enter the 12-digit account id. kaveo derives the role ARN arn:aws:iam::<account-id>:role/kaveo-readonly. If you deployed the role yourself, enter your ARN and ExternalId under Advanced. The console registers the account, then checks that kaveo can assume the role, and shows the AWS error if it can't. The account stays registered when that check fails. Connecting it again updates the same record.
From the API:
curl -sX POST https://<host>/v1/accounts \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"provider": "aws",
"name": "prod",
"aws_account_id": "123456789012",
"role_arn": "arn:aws:iam::123456789012:role/kaveo-readonly",
"external_id": "<external-id>"
}'
curl -sX POST https://<host>/v1/accounts/<account-uuid>/verify \
-H "Authorization: Bearer $TOKEN"
<account-uuid> is the id in the register response, not the 12-digit AWS account number. kaveo accounts list shows it too.
Verify assumes the role and returns verified. If verification fails, the response also has an error field. With KAVEO_SCAN_MODE=synthetic, verify succeeds without calling AWS. The first successful verify of an account with no scans queues a scan and returns its scan_id.
If you register an AWS account id that already exists, kaveo replaces its role ARN, ExternalId, name and write-role fields with what you send. A field you leave out is cleared, so resend write_role_arn and write_external_id if you use them. Those two optional fields set up the separate role that approved fixes use. See Remediation and autonomous patrol.
Two helpers are available:
POST /v1/onboarding/policyasks the configured AI provider to draft the role from yourexternal_idand a plain-Englishdescription, in theformatyou choose:cloudformation,terraform,cliorjson(the default). Deterministic code then checks that the draft is read-only and keeps the ExternalId condition. If the check fails, which it always does with theofflineprovider, you get kaveo's standard read-only role as JSON instead, andnotessays which one you got. If the provider call itself errors, the endpoint returns503.GET /v1/onboarding/organizationsreturns a StackSet template for a read-only role namedKaveoReadOnlyRole, which attachesReadOnlyAccessandSecurityAuditand requires your ExternalId. Deploy it from the management account as a service-managed StackSet with automatic deployment on. Then register each member account with itsKaveoReadOnlyRoleARN and the same ExternalId, under Advanced in the console. If the first AWS account you connected can list the organization, as the management account or a delegated administrator can, the response also flags member accounts that aren't connected yet.
4. Run a scan#
Press Run scan on the dashboard, or call the API:
curl -sX POST https://<host>/v1/scans \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"account_id": "<account-uuid>", "regions": ["us-east-1"]}'
To scan every enabled region, leave out regions. With the CLI, point it at your stack with --url https://<host> or KAVEO_API_URL, then run:
kaveo scan start --account <account-uuid> --wait
Repeat --region to limit regions. Add --fail-on high to exit with code 1 when a finding is at or above that severity. When a scan finishes, its coverage.mode is aws if the data came from real collection and synthetic if it came from the fixture.
5. Schedule scans#
curl -sX PUT https://<host>/v1/accounts/<account-uuid>/schedule \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"interval_hours": 24}'
The interval can be 1 to 720 hours. null turns scheduling off. The first scheduled scan runs one interval after you set it. The dashboard's Auto-scan control sets the same value, with Off, every 6 hours, daily and weekly options.
Azure, GCP, Kubernetes and GitHub#
An account for one of these providers stores the provider's native id and a credential_ref of the form env:NAME. env: is the only scheme kaveo resolves today. At scan time the worker reads environment variable NAME and parses it as a JSON object. kaveo stores the reference, never the secret.
For Kubernetes, ssl_ca_cert is a file path, so the CA file must exist inside the worker container. verify_ssl applies only when you don't provide ssl_ca_cert. It defaults to true, and you should keep it on outside a lab.
For GitHub, the collector reads every organization the token can see, not only the one you register. If the token can't see an organization's 2FA setting, kaveo raises no finding for it.
To install an extra, add it to the package install in the api/worker Dockerfile (for example kaveo[azure,gcp]), then run docker compose --env-file .env -f infra/docker-compose.yml build api worker.
Compose passes only a fixed list of variables to the api and worker. Only the worker reads the credential, so add its variable to the worker in infra/docker-compose.override.yml:
services:
worker:
environment:
KAVEO_AZURE_PROD: ${KAVEO_AZURE_PROD:?set KAVEO_AZURE_PROD in .env}
In .env, wrap the value in single quotes so that Compose keeps the JSON exactly as written. For GCP, first flatten the key file onto one line, for example with jq -c . key.json.
KAVEO_AZURE_PROD='{"tenant_id":"<tenant-id>","client_id":"<app-id>","client_secret":"<secret>"}'
Start the stack with both compose files, then register the account:
docker compose --env-file .env \
-f infra/docker-compose.yml -f infra/docker-compose.override.yml up -d
curl -sX POST https://<host>/v1/accounts \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"provider": "azure",
"name": "azure-prod",
"native_account_id": "<subscription-id>",
"credential_ref": "env:KAVEO_AZURE_PROD"
}'
In the console, choose the provider on Connect account and fill in Credential reference. Verify applies only to AWS, so start a scan the same way as for AWS. A live scan reports the provider id, such as azure, as its coverage.mode. The shipped detectors:
azure.storage_public_access(high): a storage account allows public blob accessgcp.bucket_public(critical): a bucket's IAM policy grants a role toallUsersorallAuthenticatedUsersk8s.cluster_admin_binding(critical):cluster-adminis bound to a broad or anonymous groupsaas.github_mfa_not_enforced(high): the organization does not require 2FA
If the extra is missing, the credential doesn't resolve or the provider API errors, the scan fails with the reason in coverage.error. For these providers, kaveo never substitutes demo data for a real account. Only KAVEO_SCAN_MODE=synthetic gives you the per-provider fixture, labeled synthetic.
Troubleshooting#
Next steps#
- Detection and attack paths: what the detectors look for
- Compliance and reporting: map results to frameworks and export them
- Remediation and autonomous patrol: the write role and approved fixes
- Configuration: override files, AI providers and detection tuning