Skip to content

Commit fb0459b

Browse files
author
Arnas Navašinskas
committed
Add eks example
1 parent a1267c0 commit fb0459b

File tree

11 files changed

+526
-1
lines changed

11 files changed

+526
-1
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Example of EKS cluster connected to CAST AI with enabled Kvisor security agent and customized runtime security rules
2+
Following this example creates EKS cluster and its supporting resources using AWS community modules.\
3+
After EKS cluster is created it is onboarded to CAST AI.\
4+
[Kvisor security agent](https://docs.cast.ai/docs/kvisor) is deployed to the cluster and security policies are enabled.\
5+
See `install_security_agent` and `kvisor_values` variables in `castai.tf` file enabling Kvisor.\
6+
See `castai_security_runtime_rule` resource defining example [Runtime security rule](https://docs.cast.ai/docs/anomaly-rules-engine) in `castai_runtime_rules.tf` file.\
7+
Example configuration should be analysed in the following order:
8+
1. Create VPC - `vpc.tf`
9+
2. Create EKS cluster - `eks.tf`
10+
3. Create CAST AI related resources to connect GKE cluster to CAST AI with [Kvisor security agent](https://docs.cast.ai/docs/kvisor) enabled - `castai.tf`
11+
4. Create CAST AI Runtime security rules - `castai_runtime_rules.tf`
12+
5. If needed - import existing CAST AI runtime rules with `fetch_castai_runtime_rules.sh` script.\
13+
Script will generate terraform resources and will print terraform import commands to the console.\
14+
\
15+
In order to import - set your API key and optionally the base URL:
16+
```
17+
export CASTAI_API_KEY="your-api-key-here"
18+
export CASTAI_BASE_URL="https://api.cast.ai" # Optional, default is https://api.cast.ai
19+
```
20+
Run the script (it requires jq installed):
21+
```
22+
# Fetch all rules
23+
./fetch_castai_runtime_rules.sh
24+
25+
# Fetch only already enabled rules
26+
./fetch_castai_runtime_rules.sh --enabled-only
27+
```
28+
Output:\
29+
Terraform resources are saved to `castai_runtime_rules.tf.example`\
30+
Copy wanted runtime rules from `castai_runtime_rules.tf.example` to `castai_runtime_rules.tf` file.\
31+
Terraform import commands are printed to console, you need to import newly added resources to terraform state before running any other commands.
32+
33+
# Terraform Usage
34+
1. Rename `tf.vars.example` to `tf.vars`
35+
2. Update `tf.vars` file with your project name, cluster name, cluster region and CAST AI API token.
36+
3. Initialize Terraform. Under example root folder run:
37+
```
38+
terraform init
39+
```
40+
4. Run Terraform apply:
41+
```
42+
terraform apply -var-file=tf.vars
43+
```
44+
5. To destroy resources created by this example:
45+
```
46+
terraform destroy -var-file=tf.vars
47+
```
48+
49+
Please refer to this guide if you run into any issues https://docs.cast.ai/docs/terraform-troubleshooting
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# 3. Connect EKS cluster to CAST AI with enabled Kvisor security agent.
2+
3+
# Configure Data sources and providers required for CAST AI connection.
4+
data "aws_caller_identity" "current" {}
5+
6+
# Configure EKS cluster connection using CAST AI eks-cluster module.
7+
resource "castai_eks_clusterid" "cluster_id" {
8+
account_id = data.aws_caller_identity.current.account_id
9+
region = var.cluster_region
10+
cluster_name = var.cluster_name
11+
}
12+
13+
resource "castai_eks_user_arn" "castai_user_arn" {
14+
cluster_id = castai_eks_clusterid.cluster_id.id
15+
}
16+
17+
# Create AWS IAM policies and a user to connect to CAST AI.
18+
module "castai-eks-role-iam" {
19+
source = "castai/eks-role-iam/castai"
20+
21+
aws_account_id = data.aws_caller_identity.current.account_id
22+
aws_cluster_region = var.cluster_region
23+
aws_cluster_name = var.cluster_name
24+
aws_cluster_vpc_id = module.vpc.vpc_id
25+
26+
castai_user_arn = castai_eks_user_arn.castai_user_arn.arn
27+
28+
create_iam_resources_per_cluster = true
29+
}
30+
31+
# Install CAST AI with enabled Kvisor security agent.
32+
module "castai-eks-cluster" {
33+
source = "castai/eks-cluster/castai"
34+
35+
kvisor_grpc_addr = var.kvisor_grpc_addr
36+
37+
# Kvisor is an open-source security agent from CAST AI.
38+
# install_security_agent by default installs Kvisor controller (k8s: deployment)
39+
# https://docs.cast.ai/docs/kvisor
40+
install_security_agent = true
41+
42+
# Kvisor configuration examples, enable certain features:
43+
kvisor_values = [
44+
yamlencode({
45+
controller = {
46+
extraArgs = {
47+
# UI: Vulnerability management configuration = API: IMAGE_SCANNING
48+
"image-scan-enabled" = true
49+
# UI: Compliance configuration = API: CONFIGURATION_SCANNING
50+
"kube-bench-enabled" = true
51+
"kube-linter-enabled" = true
52+
}
53+
}
54+
55+
# UI: Runtime Security = API: RUNTIME_SECURITY
56+
agent = {
57+
# In order to enable Runtime security set agent.enabled to true.
58+
# This will install Kvisor agent (k8s: daemonset)
59+
# https://docs.cast.ai/docs/sec-runtime-security
60+
"enabled" = true
61+
62+
extraArgs = {
63+
# Runtime security configuration examples:
64+
# By default, most users enable the eBPF events and file hash enricher.
65+
# For all flag explanations and code, see: https://github.com/castai/kvisor/blob/main/cmd/agent/daemon/daemon.go
66+
"ebpf-events-enabled" = true
67+
"file-hash-enricher-enabled" = true
68+
# other examples
69+
"netflow-enabled" = false
70+
"netflow-export-interval" = "30s"
71+
"ebpf-program-metrics-enabled" = false
72+
"prom-metrics-export-enabled" = false
73+
"prom-metrics-export-interval" = "30s"
74+
"process-tree-enabled" = false
75+
}
76+
}
77+
})
78+
]
79+
80+
# Deprecated, leave this empty, to prevent setting defaults.
81+
kvisor_controller_extra_args = {}
82+
83+
# Everything else...
84+
85+
wait_for_cluster_ready = false
86+
87+
install_egressd = false
88+
install_workload_autoscaler = false
89+
install_pod_mutator = false
90+
delete_nodes_on_disconnect = false
91+
92+
api_url = var.castai_api_url
93+
castai_api_token = var.castai_api_token
94+
grpc_url = var.castai_grpc_url
95+
96+
aws_account_id = data.aws_caller_identity.current.account_id
97+
aws_cluster_region = var.cluster_region
98+
aws_cluster_name = var.cluster_name
99+
100+
aws_assume_role_arn = module.castai-eks-role-iam.role_arn
101+
102+
default_node_configuration = module.castai-eks-cluster.castai_node_configurations["default"]
103+
node_configurations = {
104+
default = {
105+
subnets = module.vpc.private_subnets
106+
tags = {}
107+
security_groups = [
108+
module.eks.cluster_security_group_id,
109+
module.eks.node_security_group_id,
110+
]
111+
instance_profile_arn = module.castai-eks-role-iam.instance_profile_arn
112+
}
113+
}
114+
115+
node_templates = {
116+
default_by_castai = {
117+
name = "default-by-castai"
118+
configuration_id = module.castai-eks-cluster.castai_node_configurations["default"]
119+
is_default = true
120+
is_enabled = true
121+
should_taint = false
122+
123+
constraints = {
124+
on_demand = true
125+
spot = false
126+
use_spot_fallbacks = false
127+
128+
enable_spot_diversity = false
129+
spot_diversity_price_increase_limit_percent = 20
130+
131+
spot_interruption_predictions_enabled = false
132+
spot_interruption_predictions_type = "aws-rebalance-recommendations"
133+
}
134+
}
135+
}
136+
137+
# module "castai-eks-cluster" has to be destroyed before module "castai-eks-role-iam".
138+
depends_on = [module.castai-eks-role-iam, module.eks, module.vpc]
139+
}
140+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# 4. Import Runtime security rules.
2+
# Below is example security runtime rule, you can import existing rules from cast ai using fetch_castai_runtime_rules.sh script
3+
4+
resource "castai_security_runtime_rule" "example_rule__dns_to_crypto_mining_" {
5+
name = "Example rule: DNS to crypto mining"
6+
category = "event"
7+
severity = "SEVERITY_LOW"
8+
enabled = false
9+
rule_text = <<EOT
10+
event.type == event_dns && event.dns.network_details.category == category_crypto
11+
EOT
12+
rule_engine_type = "RULE_ENGINE_TYPE_CEL"
13+
resource_selector = <<EOT
14+
resource.namespace == "default"
15+
EOT
16+
labels = {
17+
environment = "dev"
18+
team = "security"
19+
}
20+
depends_on = [module.castai-eks-cluster]
21+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# 2. Create EKS cluster.
2+
3+
module "eks" {
4+
source = "terraform-aws-modules/eks/aws"
5+
version = "19.4.2"
6+
putin_khuylo = true
7+
8+
cluster_name = var.cluster_name
9+
cluster_version = var.cluster_version
10+
cluster_endpoint_public_access = true
11+
12+
cluster_addons = {
13+
coredns = {
14+
most_recent = true
15+
}
16+
kube-proxy = {
17+
most_recent = true
18+
}
19+
vpc-cni = {
20+
most_recent = true
21+
}
22+
}
23+
24+
vpc_id = module.vpc.vpc_id
25+
subnet_ids = module.vpc.private_subnets
26+
27+
eks_managed_node_groups = {
28+
node_group_1 = {
29+
name = "${var.cluster_name}-ng-1"
30+
instance_types = ["m5.large", "m5.xlarge", "t3.large"]
31+
desired_size = 2
32+
}
33+
}
34+
35+
manage_aws_auth_configmap = true
36+
37+
aws_auth_roles = [
38+
# Add the CAST AI IAM role which required for CAST AI nodes to join the cluster.
39+
{
40+
rolearn = module.castai-eks-role-iam.instance_profile_role_arn
41+
username = "system:node:{{EC2PrivateDNSName}}"
42+
groups = [
43+
"system:bootstrappers",
44+
"system:nodes",
45+
]
46+
}
47+
]
48+
49+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
if ! command -v jq &> /dev/null; then
6+
echo "❌ 'jq' is required but not installed."
7+
exit 1
8+
fi
9+
10+
# Configuration
11+
BASE_URL=${CASTAI_BASE_URL:-"https://api.cast.ai"}
12+
API_KEY=${CASTAI_API_KEY:-""}
13+
TF_OUTPUT_FILE="castai_runtime_rules.tf.example"
14+
ENABLED_ONLY=false
15+
DEPENDENCY_MODULE="module.castai-eks-cluster"
16+
17+
# Parse optional arguments
18+
while [[ $# -gt 0 ]]; do
19+
case "$1" in
20+
--enabled-only)
21+
ENABLED_ONLY=true
22+
shift
23+
;;
24+
*)
25+
echo "❌ Unknown option: $1"
26+
echo "Usage: $0 [--enabled-only]"
27+
exit 1
28+
;;
29+
esac
30+
done
31+
32+
# Check for API key
33+
if [ -z "$API_KEY" ]; then
34+
echo "❌ Error: CASTAI_API_KEY environment variable not set."
35+
exit 1
36+
fi
37+
38+
# Full API endpoint
39+
ENDPOINT="$BASE_URL/v1/security/runtime/rules?search=&page.limit=5000&sort.field=severity&sort.order=desc"
40+
41+
echo "🔍 Fetching CAST AI Runtime Rules from: $ENDPOINT"
42+
43+
# Fetch data
44+
response=$(curl -s "$ENDPOINT" \
45+
-H "Accept: application/json" \
46+
-H "X-API-Key: $API_KEY" \
47+
--fail -v)
48+
49+
if [ -z "$response" ]; then
50+
echo "❌ Failed to fetch data from CAST AI."
51+
exit 1
52+
fi
53+
54+
if [ "$ENABLED_ONLY" = true ]; then
55+
rules=$(echo "$response" | jq '.rules | map(select(.enabled == true))')
56+
else
57+
rules=$(echo "$response" | jq '.rules')
58+
fi
59+
60+
# Count and print number of enabled rules
61+
RULE_COUNT=$(echo "$rules" | jq 'length')
62+
echo "✅ Found $RULE_COUNT rule(s)."
63+
64+
# Start new TF file
65+
echo "# This file is autogenerated by fetch_castai_runtime_rules.sh" > "$TF_OUTPUT_FILE"
66+
67+
# Escape double quotes
68+
escape_quotes() {
69+
echo "$1" | sed 's/"/\\"/g'
70+
}
71+
72+
echo "$rules" | jq -c '.[]' | while read -r rule_json; do
73+
name=$(echo "$rule_json" | jq -r '.name')
74+
name_escaped=$(escape_quotes "$name")
75+
safe_name=$(echo "$name" | iconv -c -t ascii | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9_' '_' | sed 's/^_*//')
76+
77+
category=$(echo "$rule_json" | jq -r '.category')
78+
severity=$(echo "$rule_json" | jq -r '.severity')
79+
enabled=$(echo "$rule_json" | jq -r '.enabled')
80+
rule_text=$(echo "$rule_json" | jq -r '.ruleText // ""')
81+
resource_selector=$(echo "$rule_json" | jq -r '.resourceSelector // ""' | sed 's/[[:space:]]*$//')
82+
rule_engine_type=$(echo "$rule_json" | jq -r '.ruleEngineType')
83+
labels=$(echo "$rule_json" | jq -c '.labels // {}')
84+
85+
cat >> "$TF_OUTPUT_FILE" <<EOF
86+
87+
resource "castai_security_runtime_rule" "$safe_name" {
88+
name = "$name_escaped"
89+
category = "$category"
90+
severity = "$severity"
91+
enabled = $enabled
92+
rule_text = <<EOT
93+
$rule_text
94+
EOT
95+
rule_engine_type = "$rule_engine_type"
96+
EOF
97+
98+
if [ -n "$resource_selector" ]; then
99+
cat >> "$TF_OUTPUT_FILE" <<EOF
100+
resource_selector = <<EOT
101+
$resource_selector
102+
EOT
103+
EOF
104+
fi
105+
106+
if [ "$labels" != "{}" ]; then
107+
echo " labels = {" >> "$TF_OUTPUT_FILE"
108+
echo "$labels" | jq -r 'to_entries[] | " \(.key) = \"\(.value | gsub("\""; "\\\""))\""' >> "$TF_OUTPUT_FILE"
109+
echo " }" >> "$TF_OUTPUT_FILE"
110+
fi
111+
112+
echo " depends_on = [${DEPENDENCY_MODULE}]" >> "$TF_OUTPUT_FILE"
113+
echo "}" >> "$TF_OUTPUT_FILE"
114+
done
115+
116+
# Print import commands
117+
echo ""
118+
echo "📝 Terraform resources written to: $TF_OUTPUT_FILE"
119+
echo ""
120+
echo "📦 Suggested Terraform import commands:"
121+
122+
rules_list=$(echo "$rules" | jq -r '.[].name')
123+
count=$(echo "$rules_list" | wc -l | tr -d ' ')
124+
current=0
125+
126+
echo "$rules_list" | while read -r name; do
127+
current=$((current + 1))
128+
safe_name=$(echo "$name" | iconv -c -t ascii | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9_' '_' | sed 's/^_*//')
129+
if [ "$current" -lt "$count" ]; then
130+
echo "terraform import -var-file=tf.vars castai_security_runtime_rule.$safe_name \"$name\" &&"
131+
else
132+
echo "terraform import -var-file=tf.vars castai_security_runtime_rule.$safe_name \"$name\""
133+
fi
134+
done

0 commit comments

Comments
 (0)