Skip to content

Commit b6fb719

Browse files
author
Arnas Navašinskas
committed
Add aks example
1 parent fb0459b commit b6fb719

File tree

10 files changed

+427
-0
lines changed

10 files changed

+427
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Example of AKS cluster connected to CAST AI with enabled Kvisor security agent and customized runtime security rules
2+
Following example creates AKS cluster and its supporting resources.\
3+
After AKS 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 Virtual network - `vnet.tf`
9+
2. Create AKS cluster - `aks.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 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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# 2. Create AKS cluster.
2+
3+
resource "azurerm_kubernetes_cluster" "this" {
4+
name = var.cluster_name
5+
resource_group_name = azurerm_resource_group.this.name
6+
location = azurerm_resource_group.this.location
7+
dns_prefix = var.cluster_name
8+
node_resource_group = "${var.cluster_name}-ng"
9+
10+
default_node_pool {
11+
name = "default"
12+
# Node count has to be > 2 to successfully deploy CAST AI controller.
13+
node_count = 2
14+
vm_size = "Standard_D2_v2"
15+
vnet_subnet_id = azurerm_subnet.internal.id
16+
}
17+
18+
identity {
19+
type = "SystemAssigned"
20+
}
21+
22+
tags = {
23+
Environment = "Test"
24+
}
25+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# 3. Connect AKS cluster to CAST AI with enabled Kvisor security agent.
2+
3+
# Configure Data sources and providers required for CAST AI connection.
4+
data "azurerm_subscription" "current" {}
5+
6+
# Configure AKS cluster connection to CAST AI using CAST AI aks-cluster module with enabled Kvisor security agent.
7+
module "castai-aks-cluster" {
8+
source = "castai/aks/castai"
9+
10+
kvisor_grpc_addr = var.kvisor_grpc_addr
11+
12+
# Kvisor is an open-source security agent from CAST AI.
13+
# install_security_agent by default installs Kvisor controller (k8s: deployment)
14+
# https://docs.cast.ai/docs/kvisor
15+
install_security_agent = true
16+
17+
# Kvisor configuration examples, enable certain features:
18+
kvisor_values = [
19+
yamlencode({
20+
controller = {
21+
extraArgs = {
22+
# UI: Vulnerability management configuration = API: IMAGE_SCANNING
23+
"image-scan-enabled" = true
24+
# UI: Compliance configuration = API: CONFIGURATION_SCANNING
25+
"kube-bench-enabled" = true
26+
"kube-linter-enabled" = true
27+
}
28+
}
29+
30+
# UI: Runtime Security = API: RUNTIME_SECURITY
31+
agent = {
32+
# In order to enable Runtime security set agent.enabled to true.
33+
# This will install Kvisor agent (k8s: daemonset)
34+
# https://docs.cast.ai/docs/sec-runtime-security
35+
"enabled" = true
36+
37+
extraArgs = {
38+
# Runtime security configuration examples:
39+
# By default, most users enable the eBPF events and file hash enricher.
40+
# For all flag explanations and code, see: https://github.com/castai/kvisor/blob/main/cmd/agent/daemon/daemon.go
41+
"ebpf-events-enabled" = true
42+
"file-hash-enricher-enabled" = true
43+
# other examples
44+
"netflow-enabled" = false
45+
"netflow-export-interval" = "30s"
46+
"ebpf-program-metrics-enabled" = false
47+
"prom-metrics-export-enabled" = false
48+
"prom-metrics-export-interval" = "30s"
49+
"process-tree-enabled" = false
50+
}
51+
}
52+
})
53+
]
54+
55+
# Deprecated, leave this empty, to prevent setting defaults.
56+
kvisor_controller_extra_args = {}
57+
58+
# Everything else...
59+
60+
wait_for_cluster_ready = false
61+
62+
install_workload_autoscaler = false
63+
install_pod_mutator = false
64+
delete_nodes_on_disconnect = var.delete_nodes_on_disconnect
65+
66+
api_url = var.castai_api_url
67+
castai_api_token = var.castai_api_token
68+
grpc_url = var.castai_grpc_url
69+
70+
aks_cluster_name = var.cluster_name
71+
aks_cluster_region = var.cluster_region
72+
node_resource_group = azurerm_kubernetes_cluster.this.node_resource_group
73+
resource_group = azurerm_kubernetes_cluster.this.resource_group_name
74+
75+
subscription_id = data.azurerm_subscription.current.subscription_id
76+
tenant_id = data.azurerm_subscription.current.tenant_id
77+
78+
default_node_configuration = module.castai-aks-cluster.castai_node_configurations["default"]
79+
80+
node_configurations = {
81+
default = {
82+
disk_cpu_ratio = 25
83+
subnets = [azurerm_subnet.internal.id]
84+
tags = var.tags
85+
}
86+
}
87+
}
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 AKS: 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-aks-cluster]
21+
}
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-aks-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
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Following providers required by AKS and Vnet resources.
2+
provider "azurerm" {
3+
features {}
4+
subscription_id = var.subscription_id
5+
}
6+
7+
provider "castai" {
8+
api_token = var.castai_api_token
9+
api_url = var.castai_api_url
10+
}
11+
12+
provider "azuread" {
13+
tenant_id = data.azurerm_subscription.current.tenant_id
14+
}
15+
16+
provider "helm" {
17+
kubernetes {
18+
host = azurerm_kubernetes_cluster.this.kube_config.0.host
19+
client_certificate = base64decode(azurerm_kubernetes_cluster.this.kube_config.0.client_certificate)
20+
client_key = base64decode(azurerm_kubernetes_cluster.this.kube_config.0.client_key)
21+
cluster_ca_certificate = base64decode(azurerm_kubernetes_cluster.this.kube_config.0.cluster_ca_certificate)
22+
}
23+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
cluster_name = "<place-holder>"
2+
cluster_region = "<place-holder>"
3+
castai_api_token = "<place-holder>"
4+
subscription_id = "<place-holder>"

0 commit comments

Comments
 (0)