Cloud network context

Enrich network flow data with cloud provider region, zone, and subnet information.

Cloud network context enriches your network flow data with cloud provider context — VPC/subnet CIDRs, regions, availability zones, peering connections, and Transit Gateway topology. This lets you attribute network flows to specific cloud regions and availability zones for cost analysis and security visibility.

Overview

When enabled, Kvisor's cloud state controller:

  1. Fetches VPC network state from your cloud provider API on startup
  2. Caches network state and refreshes it periodically (default: every hour)
  3. Fetches cloud service IP ranges (e.g. ip-ranges.amazonaws.com) separately — no auth needed, refreshed daily
  4. Enriches network flow events with VPC context (region, zone, subnet info)

IP lookup uses a three-tier priority: static mappings > cloud-discovered > public cloud service ranges.

⚠️

Warning

Zone information is not available for GCP. GCP subnets are regional, not zonal, so enriched flows will include region but not availability zone.

Before you start

  • Kvisor installed and running (Installation guide)
  • Network traffic monitoring enabled (--set agent.extraArgs.netflow-enabled=true) — see Configuring Kvisor features
  • Your cloud provider VPC ID (AWS) or VPC network name (GCP)
  • IAM permissions configured (see below)

Configure cloud provider authentication

IAM policy

Create an IAM policy with the following permissions. This covers VPC, peering, and Transit Gateway discovery. If you don't use Transit Gateway, the TransitGateway* and SearchTransitGatewayRoutes actions are unused but harmless.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeVpcs",
        "ec2:DescribeSubnets",
        "ec2:DescribeVpcPeeringConnections",
        "ec2:DescribeTransitGatewayAttachments",
        "ec2:DescribeTransitGatewayPeeringAttachments",
        "ec2:DescribeTransitGatewayRouteTables",
        "ec2:SearchTransitGatewayRoutes"
      ],
      "Resource": "*"
    }
  ]
}

Option 1: IRSA — IAM roles for service accounts (recommended)

Step 1: Create IAM policy

export AWS_REGION="us-east-1"
export POLICY_NAME="KvisorVPCReaderPolicy"

cat > kvisor-vpc-policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeVpcs",
        "ec2:DescribeSubnets",
        "ec2:DescribeVpcPeeringConnections",
        "ec2:DescribeTransitGatewayAttachments",
        "ec2:DescribeTransitGatewayPeeringAttachments",
        "ec2:DescribeTransitGatewayRouteTables",
        "ec2:SearchTransitGatewayRoutes"
      ],
      "Resource": "*"
    }
  ]
}
EOF

aws iam create-policy \
  --policy-name ${POLICY_NAME} \
  --policy-document file://kvisor-vpc-policy.json \
  --region ${AWS_REGION}

Step 2: Create IAM role for service account

Option A: Use existing Kvisor service account (recommended)
export CLUSTER_NAME="your-eks-cluster"
export NAMESPACE="castai-agent"
export SERVICE_ACCOUNT_NAME="castai-kvisor-controller"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

eksctl utils associate-iam-oidc-provider \
  --cluster ${CLUSTER_NAME} --region ${AWS_REGION} --approve

export OIDC_PROVIDER=$(aws eks describe-cluster --name ${CLUSTER_NAME} --region ${AWS_REGION} \
  --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")

cat > trust-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "${OIDC_PROVIDER}:sub": "system:serviceaccount:${NAMESPACE}:${SERVICE_ACCOUNT_NAME}",
          "${OIDC_PROVIDER}:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}
EOF

aws iam create-role \
  --role-name KvisorVPCReaderRole \
  --assume-role-policy-document file://trust-policy.json

aws iam attach-role-policy \
  --role-name KvisorVPCReaderRole \
  --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/${POLICY_NAME}

export ROLE_ARN=$(aws iam get-role --role-name KvisorVPCReaderRole --query 'Role.Arn' --output text)
echo "Role ARN: ${ROLE_ARN}"
Option B: Let eksctl create a new service account
export CLUSTER_NAME="your-eks-cluster"
export NAMESPACE="castai-agent"
export SERVICE_ACCOUNT_NAME="castai-kvisor-controller"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

eksctl utils associate-iam-oidc-provider \
  --cluster ${CLUSTER_NAME} --region ${AWS_REGION} --approve

eksctl create iamserviceaccount \
  --cluster=${CLUSTER_NAME} \
  --namespace=${NAMESPACE} \
  --name=${SERVICE_ACCOUNT_NAME} \
  --attach-policy-arn=arn:aws:iam::${AWS_ACCOUNT_ID}:policy/${POLICY_NAME} \
  --region=${AWS_REGION} \
  --approve

Step 3: Configure Helm

For Option A (existing service account):

controller:
  extraArgs:
    cloud-provider: aws
    cloud-provider-vpc-sync-enabled: true
    cloud-provider-vpc-name: "vpc-0123456789abcdef0"

  serviceAccount:
    create: true
    annotations:
      eks.amazonaws.com/role-arn: "arn:aws:iam::YOUR_ACCOUNT_ID:role/KvisorVPCReaderRole"

For Option B (eksctl-created service account), set serviceAccount.create: false.

Option 2: IAM user with access keys

Step 1: Create IAM user and policy

export USER_NAME="kvisor-vpc-reader"
export POLICY_NAME="KvisorVPCReaderPolicy"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

cat > kvisor-vpc-policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeVpcs",
        "ec2:DescribeSubnets",
        "ec2:DescribeVpcPeeringConnections",
        "ec2:DescribeTransitGatewayAttachments",
        "ec2:DescribeTransitGatewayPeeringAttachments",
        "ec2:DescribeTransitGatewayRouteTables",
        "ec2:SearchTransitGatewayRoutes"
      ],
      "Resource": "*"
    }
  ]
}
EOF

aws iam create-policy --policy-name ${POLICY_NAME} --policy-document file://kvisor-vpc-policy.json
aws iam create-user --user-name ${USER_NAME}
aws iam attach-user-policy --user-name ${USER_NAME} \
  --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/${POLICY_NAME}
aws iam create-access-key --user-name ${USER_NAME} > access-key.json

Step 2: Create Kubernetes secret

export AWS_ACCESS_KEY_ID=$(jq -r '.AccessKey.AccessKeyId' access-key.json)
export AWS_SECRET_ACCESS_KEY=$(jq -r '.AccessKey.SecretAccessKey' access-key.json)

kubectl create secret generic aws-credentials \
  --from-literal=AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
  --from-literal=AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
  --namespace castai-agent

rm access-key.json

Step 3: Configure Helm

controller:
  extraArgs:
    cloud-provider: aws
    cloud-provider-vpc-sync-enabled: true
    cloud-provider-vpc-name: "vpc-0123456789abcdef0"

  envFrom:
    - secretRef:
        name: aws-credentials
📘

AWS zone IDs

By default, Kvisor uses zone names (e.g. us-east-1a). AWS also exposes zone IDs (e.g. use1-az1) which are consistent across accounts — useful for correct cross-account traffic attribution. Enable with --set controller.extraArgs.cloud-provider-aws-use-zone-id=true.

When this flag is enabled, the zone field in any static CIDR mappings must also use zone IDs (e.g. use1-az1), not zone names.

📘

Azure support

Azure cloud network context is coming soon.

Enable VPC state discovery

After configuring authentication, enable VPC state discovery with Helm:

helm upgrade castai-kvisor castai-helm/castai-kvisor -n castai-agent \
  --reset-then-reuse-values \
  --set controller.extraArgs.cloud-provider=aws \
  --set controller.extraArgs.cloud-provider-aws-region=<your-aws-region> \
  --set controller.extraArgs.cloud-provider-vpc-sync-enabled=true \
  --set controller.extraArgs.cloud-provider-vpc-name=<your-vpc-id>

Helm flags reference

FlagDescriptionDefault
cloud-providerCloud provider: aws or gcp
cloud-provider-vpc-sync-enabledEnable VPC state discoveryfalse
cloud-provider-vpc-nameVPC ID (AWS) or network name (GCP)
cloud-provider-vpc-sync-intervalHow often to refresh VPC state1h
cloud-provider-vpc-cache-sizeLRU cache size for IP lookups10000
cloud-provider-gcp-project-idGCP project ID (GCP only)
cloud-provider-aws-regionAWS region of the cluster. Recommended; required for correct TGW region attribution (AWS only)
cloud-provider-aws-use-zone-idUse zone IDs (use1-az1) instead of zone names (us-east-1a) (AWS only)false
cloud-provider-aws-cross-account-roleCross-account role ARN template (see Transit Gateway discovery)
cloud-provider-public-cidrs-sync-intervalHow often to refresh public cloud service IP ranges24h
cloud-provider-static-cidrs-filePath to static CIDR YAML (see Static CIDR mappings)

All flags are set under controller.extraArgs in the Helm values.

Advanced configuration

AWS Transit Gateway discovery (AWS only)

When your cluster VPC is connected to other VPCs via AWS Transit Gateway, Kvisor can automatically discover remote VPCs and their CIDR ranges — removing the need for manual static CIDR mappings for TGW-connected networks.

How it works

  1. Kvisor discovers Transit Gateway attachments for the cluster VPC
  2. For each TGW, it enumerates all VPC and peering attachments (including remote accounts)
  3. It queries TGW route tables to discover destination CIDRs per attachment
  4. If a cross-account role ARN template is configured, Kvisor assumes that role via STS to fetch subnet-level detail (zone, zone ID) from remote accounts
  5. For TGW peering attachments, it resolves the peer TGW's region and discovers VPCs behind it
                        ┌──────────────────────────────────────────────────────┐
                        │              Same Region (e.g. us-east-1)           │
                        │                                                      │
┌─────────────────┐     │     ┌──────────────────┐     ┌─────────────────┐    │
│  Cluster VPC    │     │     │  Transit Gateway  │     │  Remote VPC A   │    │
│  Account 111    │─────┼────▶│                   │◀────│  Account 222    │    │
│  10.0.0.0/16    │     │     │  TGW route table  │     │  10.1.0.0/16    │    │
└─────────────────┘     │     │  has CIDR routes  │     └─────────────────┘    │
                        │     │  for all attached  │                            │
                        │     │  VPCs              │     ┌─────────────────┐    │
                        │     │                   │◀────│  Remote VPC B   │    │
                        │     └──────────────────┘     │  Account 333    │    │
                        │                               │  10.2.0.0/16    │    │
                        │                               └─────────────────┘    │
                        └──────────────────────────────────────────────────────┘

Basic setup (route-level CIDRs only)

TGW discovery works automatically when VPC sync is enabled — no extra flags needed. Kvisor discovers TGW-attached VPCs and indexes their route-table CIDRs (region attribution, no per-AZ detail).

Requires the IAM permissions from the AWS authentication section (the DescribeTransitGateway* and SearchTransitGatewayRoutes actions).

controller:
  extraArgs:
    cloud-provider: aws
    cloud-provider-aws-region: "us-east-1"
    cloud-provider-vpc-sync-enabled: true
    cloud-provider-vpc-name: "vpc-0123456789abcdef0"
⚠️

Important

Set cloud-provider-aws-region to your cluster's AWS region. This is used as the region for discovered TGW VPCs and for targeting cross-account API calls. Without it, discovered VPCs may have no region attribution in netflow records.

Cross-account subnet discovery (optional)

For full subnet-level detail (zone names and zone IDs), configure a cross-account IAM role that Kvisor can assume in each remote account.

Step 1: Create IAM role in each remote account

Permissions policy (minimal — only needs to read subnets and TGW attachments):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeSubnets",
        "ec2:DescribeTransitGatewayAttachments"
      ],
      "Resource": "*"
    }
  ]
}

Trust policy (allows the Kvisor role in the cluster account to assume this role):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::CLUSTER_ACCOUNT_ID:role/KvisorVPCReaderRole"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Create the role using the AWS CLI (run once per remote account):

export CLUSTER_ACCOUNT_ID="123456789012"

cat > trust-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::${CLUSTER_ACCOUNT_ID}:role/KvisorVPCReaderRole"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

cat > permissions-policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeSubnets",
        "ec2:DescribeTransitGatewayAttachments"
      ],
      "Resource": "*"
    }
  ]
}
EOF

aws iam create-role \
  --role-name KvisorCrossAccountReader \
  --assume-role-policy-document file://trust-policy.json \
  --description "Allows kvisor in cluster account to read subnet info for TGW discovery"

REMOTE_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

aws iam create-policy \
  --policy-name KvisorCrossAccountReaderPolicy \
  --policy-document file://permissions-policy.json

aws iam attach-role-policy \
  --role-name KvisorCrossAccountReader \
  --policy-arn arn:aws:iam::${REMOTE_ACCOUNT_ID}:policy/KvisorCrossAccountReaderPolicy

rm trust-policy.json permissions-policy.json
Step 2: Add sts:AssumeRole to the cluster account role

The Kvisor role in the cluster account needs permission to assume the remote roles:

{
  "Effect": "Allow",
  "Action": "sts:AssumeRole",
  "Resource": "arn:aws:iam::*:role/KvisorCrossAccountReader"
}

Using the AWS CLI (run in the cluster account):

cat > assume-policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::*:role/KvisorCrossAccountReader"
    }
  ]
}
EOF

aws iam put-role-policy \
  --role-name KvisorVPCReaderRole \
  --policy-name KvisorCrossAccountAssumePolicy \
  --policy-document file://assume-policy.json

rm assume-policy.json

Step 3: Configure the cross-account role ARN template

Add the cloud-provider-aws-cross-account-role flag. Kvisor replaces {account-id} with the actual AWS account ID of each remote VPC discovered via the Transit Gateway.

controller:
  extraArgs:
    cloud-provider: aws
    cloud-provider-aws-region: "us-east-1"
    cloud-provider-vpc-sync-enabled: true
    cloud-provider-vpc-name: "vpc-0123456789abcdef0"
    cloud-provider-aws-cross-account-role: "arn:aws:iam::{account-id}:role/KvisorCrossAccountReader"

Fallback behavior

ConfigurationWhat you get
VPC sync only (no cross-account role)TGW route CIDRs, region only. Auto-discovers connected VPCs but no AZ detail.
VPC sync + cross-account roleSubnet CIDRs, zone name + zone ID. Full zone-aware network mapping.
Role configured but STS failsFalls back to TGW route CIDRs (logged as warning, non-fatal).

Static CIDR mappings

Static CIDR mappings let you manually annotate IP ranges that are not automatically discovered, such as on-premises networks, VPN endpoints, or cross-account VPCs without Transit Gateway connectivity.

Static entries take highest priority in IP lookups — a static /32 will override a broader cloud-discovered subnet.

When to use

  • The destination is in another account or cloud and not reachable via the cloud API
  • You use Direct Connect, Site-to-Site VPN, or other connectivity not automatically discovered
  • You have managed services (RDS, Cloud SQL, etc.) with known private IPs
  • Cloud provider API discovery is not enabled

Configuration via Helm values

Add staticCIDRs under controller.netflow in your values.yaml. Helm creates a ConfigMap and mounts it automatically when the list is non-empty:

controller:
  netflow:
    staticCIDRs:
      mappings:
        - cidr: "10.1.0.0/24"
          zone: "us-east-1a"
          region: "us-east-1"
          name: "production-vpc"
          kind: "VPC"
          connectivityMethod: "TransitGateway"

        - cidr: "10.0.0.13/32"
          zone: "us-east-1a"
          region: "us-east-1"
          name: "production-rds"
          kind: "RDS"
          connectivityMethod: "PrivateLink"

        - cidr: "10.2.0.0/16"
          region: "us-east-1"
          name: "peered-vpc"
          kind: "VPC"
          connectivityMethod: "VPCPeering"

Mapping fields

FieldRequiredDescription
cidrYesIP range in CIDR notation (e.g. 10.1.0.0/24) or single IP (10.0.0.1/32)
regionYesCloud region (e.g. us-east-1 for AWS, us-central1 for GCP)
zoneNoAvailability zone (e.g. us-east-1a for AWS, us-central1-a for GCP). Leave empty for regional ranges. For AWS, when cloud-provider-aws-use-zone-id: true is set, use zone IDs here (e.g. use1-az1)
nameNoHuman-readable name (e.g. production-rds, cloud-sql-instance)
kindNoResource type (e.g. VPC, RDS, CloudSQL, OnPrem)
connectivityMethodNoConnectivity label — see values below

connectivityMethod values

This field is optional and free-form. Recommended values for common networking paths:

ValueDescriptionProvider
VPCPeeringVPC Peering / VPC Network PeeringAWS, GCP
TransitGatewayAWS Transit GatewayAWS
PrivateLinkAWS PrivateLink / VPC EndpointsAWS
DirectConnectAWS Direct ConnectAWS
SiteToSiteVPNAWS Site-to-Site VPNAWS
CloudVPNGCP Cloud VPNGCP
CloudInterconnectGCP Cloud InterconnectGCP
NATGatewayNAT Gateway / Cloud NATAWS, GCP
IntraVPCSame VPC / same VPC networkAWS, GCP

Custom values (e.g. "on-prem-mpls", "ExpressRoute") are passed through as-is in netflow records.

📘

Note

Kvisor does not perform any cost calculations. The connectivityMethod field is purely a label attached to netflow records so that downstream systems (e.g. Cast AI cost attribution) can distinguish traffic by connectivity type.

Using a pre-existing ConfigMap

If you manage your own ConfigMap (e.g. via GitOps):

controller:
  extraArgs:
    cloud-provider-static-cidrs-file: /etc/kvisor/my-cidrs/static-cidrs.yaml

  extraVolumes:
    - name: my-static-cidrs
      configMap:
        name: my-existing-configmap

  extraVolumeMounts:
    - name: my-static-cidrs
      mountPath: /etc/kvisor/my-cidrs
      readOnly: true

The ConfigMap file must be valid YAML:

staticCIDRMappings:
  - cidr: "10.1.0.0/24"
    zone: "us-east-1a"
    region: "us-east-1"
    name: "production-vpc"
    kind: "VPC"
    connectivityMethod: "TransitGateway"
  - cidr: "10.128.0.0/20"
    region: "us-central1"
    name: "gcp-subnet"
    kind: "VPC"
    connectivityMethod: "VPCPeering"

Combining with cloud discovery

Static mappings and cloud provider discovery work together. Static entries always win for the same IP:

controller:
  extraArgs:
    cloud-provider: aws
    cloud-provider-vpc-sync-enabled: true
    cloud-provider-vpc-name: "vpc-0123456789abcdef0"

  netflow:
    staticCIDRs:
      mappings:
        # This /32 overrides whatever the cloud API says about 10.0.0.13
        - cidr: "10.0.0.13/32"
          region: "us-east-1"
          name: "cross-account-rds"
          connectivityMethod: "TransitGateway"

Verify the configuration

Check that VPC state sync is working:

kubectl logs -l app.kubernetes.io/name=castai-kvisor-controller -n castai-agent | grep -i "vpc\|cloud"

Look for log messages indicating successful VPC state sync, such as:

  • VPC state loaded with subnet/CIDR counts
  • Cloud service IP ranges fetched successfully

If Transit Gateway discovery is enabled, you should also see logs about TGW attachments and route table entries being indexed.

Troubleshooting

VPC sync not starting

Verify the cloud provider flags are set:

kubectl get deployment castai-kvisor-controller -n castai-agent -o yaml | grep -A 20 "args"

Ensure cloud-provider-vpc-sync-enabled is true and cloud-provider-vpc-name is set.

Authentication errors

Check controller logs for credential errors:

kubectl logs -l app.kubernetes.io/name=castai-kvisor-controller -n castai-agent | grep -i "error\|auth\|credential\|forbidden"

Common causes:

  • IRSA: Service account annotation missing or OIDC provider not configured
  • Access keys: Secret not created in the correct namespace (castai-agent)
  • Insufficient permissions: IAM policy missing required ec2:Describe* actions

Cross-account role assumption failing (AWS only)

If TGW cross-account discovery falls back to route-level CIDRs, check for STS errors:

kubectl logs -l app.kubernetes.io/name=castai-kvisor-controller -n castai-agent | grep -i "assume\|sts"

Verify the trust policy in the remote account allows the Kvisor role ARN and that the cluster account role has sts:AssumeRole permission.