Vectasec · Guides
Scan AWS with a read-only role
Connect an AWS account to Vectasec through a cross-account read-only role and scan SQS, SNS, EventBridge, MSK and API Gateway across regions.
On this page
This guide connects one AWS account to Vectasec through a cross-account, read-only IAM role. You create the role, save a connection that assumes it, run a scan and read the findings. It assumes you already have a working engine and schema from the Quick start.
What gets scanned#
One aws connection covers one account. When role_arn is set, the connector first assumes that role. It then calls sts:GetCallerIdentity to prove the credential works, and fans out over every region you list in regions to read:
- SQS: queues and their attributes (resource policy, encryption, dead-letter queue)
- SNS: topics, topic attributes and subscriptions
- EventBridge: event buses and their policies, rules and rule targets
- MSK: clusters (client authentication, encryption, public access, monitoring level)
- API Gateway: v1 REST APIs and v2 HTTP/WebSocket APIs (routes, authorizers, stages)
Apart from the STS calls (AssumeRole and GetCallerIdentity), every call is a List*, Get* or Describe* read. No call sends, receives or reads a message. EventBridge target input payloads are not stored, because they can embed message bodies.
The scan produces these resource kinds: aws_account, aws_sqs_queue, aws_sns_topic, aws_sns_subscription, aws_event_bus, aws_event_rule, aws_event_target, aws_msk_cluster, aws_api and aws_api_stage. Principals named in the Allow statements of SQS, SNS and EventBridge resource policies become non-human identities with grants. For more on those, see Non-human identities and attack paths.
1. Create a read-only role in the target account#
Create a role in the account you want to scan. Its trust policy should allow only the principal that runs your scans, and it should require an external ID. Save the policy as trust-policy.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/vectasec-engine" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "your-external-id" }
}
}
]
}
Replace 111122223333:role/vectasec-engine with the IAM principal your scans run as. The connector makes the AssumeRole call using the standard AWS credential chain of whichever process runs it:
- the engine API when you click Test in the console
vectasec workerwhen you click Scan in the console, and for scheduled rescans (for a connection in the default cloud mode)- your own shell when you run the CLI
If these processes run as different IAM principals, trust each one in the policy. If the connection also sets profile or a key pair, that identity makes the call instead. When the calling principal lives in a different AWS account from the role, its own identity policy must also allow sts:AssumeRole on the new role.
Generate the external ID once, for example with openssl rand -hex 16, and keep it for step 2. It guards against the confused-deputy problem; it is not a password. Vectasec stores it with the connection's regular config.
Create the role and attach the AWS managed SecurityAudit policy:
aws iam create-role \
--role-name vectasec-readonly \
--assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy \
--role-name vectasec-readonly \
--policy-arn arn:aws:iam::aws:policy/SecurityAudit
SecurityAudit covers every call the connector makes. ReadOnlyAccess also works, but it grants much broader read access than the connector needs. If you write a custom policy instead, the connector calls only these actions (sts:GetCallerIdentity needs no permission):
- SQS:
sqs:ListQueues,sqs:GetQueueAttributes - SNS:
sns:ListTopics,sns:GetTopicAttributes,sns:ListSubscriptionsByTopic - EventBridge:
events:ListEventBuses,events:DescribeEventBus,events:ListRules,events:ListTargetsByRule - MSK:
kafka:ListClustersV2 - API Gateway (v1 and v2):
apigateway:GET
2. Add the connection#
From the engine directory of the release, save the connection and test it:
uv run vectasec connections add --type aws --name prod-aws \
--set regions=us-east-1,eu-west-1 \
--set role_arn=arn:aws:iam::123456789012:role/vectasec-readonly \
--set external_id=your-external-id
uv run vectasec connections test prod-aws
The AWS fields have no dedicated CLI flags, so each one goes through --set key=value. connections test runs the full collection with the same connector code a scan uses and prints the resource count by kind. It stores nothing except the connection's status.
In the console, open Systems, choose Connect a system, then Amazon Web Services. Connecting a system needs the editor or admin role. The console shows the same fields, and the secret ones use password inputs.
The base identity is chosen in this order: the key pair (when both access_key_id and secret_access_key are set), then the named profile, then the default chain. If role_arn is set, the connector assumes the role on top of that identity.
Prefer the role. It leaves no AWS secret in Vectasec at all, and its temporary credentials are held only in memory while the collection runs. If you must use static keys, enter them in the console rather than with --set, so they stay out of your shell history. Secret fields are kept in one Vault secret per connection and merged into memory only at scan or test time.
In CloudTrail, calls made through the role appear under the session name vectasec-readonly-scan.
3. Scan and review#
uv run vectasec scan --connection prod-aws
uv run vectasec findings
uv run vectasec identities
scan prints resource, edge, finding and observation counts. findings lists every finding for the tenant, worst first, with evidence counts. In the console, Scan queues the same scan for vectasec worker. You can also set the connection to rescan automatically every 30 minutes, 1 hour, 6 hours, 24 hours or 7 days. Scheduled rescans need pg_cron in the database, and they skip a connection whose last test failed until a test passes again.
The 21 AWS rules:
A wildcard principal scoped by a Condition, such as aws:SourceArn or aws:PrincipalOrgID, is not reported as public. Each finding cites the policy statement, field or route it read. For how findings are recorded, see Findings, evidence and compliance.
Permissions and coverage gaps#
A denied read never counts as a pass. It becomes a not_assessable finding that names what could not be read:
vectasec posture counts these findings as coverage gaps. To close one, widen the role with the read actions the finding names and rescan. AWS rules are not mapped to compliance framework controls today, so AWS findings, including these gaps, do not change a control's status.
A failed AssumeRole or GetCallerIdentity call is an authentication failure and fails the whole scan. A service call that fails for another reason, such as an unreachable regional endpoint, is recorded in the collection field of the aws_account resource. Check that field if a region returns no resources for a service.
Not covered today#
- Step Functions, Lambda and Kinesis
- ElastiCache and MemoryDB
- IAM inventory beyond the principals that resource policies name. The connector makes no
iam:List*calls. - AWS Organizations enumeration. The account and regions are explicit config, so you add one connection per account.
- CloudTrail or usage telemetry, so no last-used or staleness data
For MSK, this connector reads the managed cluster properties. Broker-level Kafka posture comes from the Kafka connector. That connector opens its admin client with no SASL or TLS settings, so today it can reach only a plaintext listener that accepts unauthenticated clients. It cannot scan MSK listeners that require TLS, IAM or SCRAM.