Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: runwhen.com/v1
kind: GenerationRules
spec:
platform: azure
generationRules:
- resourceTypes:
- azure_network_virtual_networks
matchRules:
- type: pattern
pattern: ".+"
properties: [name]
mode: substring
slxs:
- baseName: az-subnet-egress-validation
qualifiers: [resource, resource_group, subscription_id]
baseTemplateName: azure-subnet-egress-validation
levelOfDetail: basic
outputItems:
- type: slx
- type: runbook
templateName: azure-subnet-egress-validation-taskset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: runwhen.com/v1
kind: ServiceLevelX
metadata:
name: {{slx_name}}
labels:
{% include "common-labels.yaml" %}
annotations:
{% include "common-annotations.yaml" %}
spec:
imageURL: https://storage.googleapis.com/runwhen-nonprod-shared-images/icons/azure/networking/10064-icon-service-Virtual-Networks.svg
alias: "{{match_resource.resource.name}} Azure Subnet Egress Validation"
asMeasuredBy: NSG egress rules, UDR default routes vs Azure Firewall, and optional Network Watcher probes from a source VM.
owners:
- {{ workspace.owner_email }}
statement: Validates that subnet egress paths align with NSGs, route tables, and optional Azure Firewall policies.
additionalContext:
{% include "azure-hierarchy.yaml" ignore missing %}
qualified_name: "{{ match_resource.qualified_name }}"
tags:
{% include "azure-tags.yaml" ignore missing %}
- name: service
value: virtual-network
- name: access
value: read-only
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
apiVersion: runwhen.com/v1
kind: Runbook
metadata:
name: {{slx_name}}
labels:
{% include "common-labels.yaml" %}
annotations:
{% include "common-annotations.yaml" %}
spec:
location: {{default_location}}
description: Validates subnet egress using NSG rules, route tables, optional Azure Firewall alignment, and optional Network Watcher probes.
codeBundle:
{% if repo_url %}
repoUrl: {{repo_url}}
{% else %}
repoUrl: https://github.com/runwhen-contrib/rw-cli-codecollection.git
{% endif %}
{% if ref %}
ref: {{ref}}
{% else %}
ref: main
{% endif %}
pathToRobot: codebundles/azure-subnet-egress-validation/runbook.robot
configProvided:
- name: AZURE_SUBSCRIPTION_ID
value: "{{ subscription_id }}"
- name: AZURE_RESOURCE_GROUP
value: "{{ resource_group.name }}"
- name: VNET_NAME
value: "{{ match_resource.resource.name }}"
- name: PROBE_TARGETS
value: "{{ custom.probe_targets | default('https://example.com:443') }}"
- name: PROBE_MODE
value: "{{ custom.probe_mode | default('network-watcher') }}"
- name: SOURCE_VM_RESOURCE_ID
value: "{{ custom.source_vm_resource_id | default('') }}"
secretsProvided:
{% if wb_version %}
{% include "azure-auth.yaml" ignore missing %}
{% else %}
- name: azure_credentials
workspaceKey: AUTH DETAILS NOT FOUND
{% endif %}
16 changes: 16 additions & 0 deletions codebundles/azure-subnet-egress-validation/.test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Test infrastructure — Azure Subnet Egress Validation

This directory contains a Terraform stack and Taskfile tasks used to exercise the CodeBundle against a real Azure subscription.

## Prerequisites

- Azure CLI and Terraform installed
- `terraform/tf.secret` (not committed) with `ARM_SUBSCRIPTION_ID`, `AZ_TENANT_ID`, `AZ_CLIENT_ID`, `AZ_CLIENT_SECRET` exported for Terraform and `az`

## Usage

1. Copy `terraform/terraform.tfvars` values to match your subscription (resource group name, region, VNet name).
2. `cd terraform && terraform init && terraform apply`
3. From `.test`, run `task generate-rwl-config` (after sourcing `tf.secret`) to build `workspaceInfo.yaml` for RunWhen Local.

Tag test resources with `lifecycle: deleteme` for easy identification.
159 changes: 159 additions & 0 deletions codebundles/azure-subnet-egress-validation/.test/Taskfile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
version: "3"

tasks:
default:
desc: "Run/refresh config"
cmds:
- task: check-unpushed-commits
- task: generate-rwl-config
- task: run-rwl-discovery

clean:
desc: "Run cleanup tasks"
cmds:
- task: check-and-cleanup-terraform
- task: clean-rwl-discovery

build-infra:
desc: "Build test infrastructure"
cmds:
- task: build-terraform-infra

check-unpushed-commits:
desc: Check if outstanding commits or file updates need to be pushed before testing.
vars:
BASE_DIR: "../"
cmds:
- |
UNCOMMITTED_FILES=$(git diff --name-only HEAD | grep -E "^${BASE_DIR}(\.runwhen|[^/]+)" | grep -v "/\.test/" || true)
if [ -n "$UNCOMMITTED_FILES" ]; then
echo "Uncommitted changes found:"; echo "$UNCOMMITTED_FILES"; exit 1
fi
- |
git fetch origin
UNPUSHED_FILES=$(git diff --name-only origin/$(git rev-parse --abbrev-ref HEAD) HEAD | grep -E "^${BASE_DIR}(\.runwhen|[^/]+)" | grep -v "/\.test/" || true)
if [ -n "$UNPUSHED_FILES" ]; then
echo "Unpushed commits found:"; echo "$UNPUSHED_FILES"; exit 1
fi
silent: true

generate-rwl-config:
desc: "Generate RunWhen Local configuration (workspaceInfo.yaml)"
env:
ARM_SUBSCRIPTION_ID: "{{.ARM_SUBSCRIPTION_ID}}"
AZ_TENANT_ID: "{{.AZ_TENANT_ID}}"
AZ_CLIENT_SECRET: "{{.AZ_CLIENT_SECRET}}"
AZ_CLIENT_ID: "{{.AZ_CLIENT_ID}}"
RW_WORKSPACE: '{{.RW_WORKSPACE | default "my-workspace"}}'
cmds:
- |
source terraform/tf.secret
repo_url=$(git config --get remote.origin.url)
branch_name=$(git rev-parse --abbrev-ref HEAD)
codebundle=$(basename "$(dirname "$PWD")")
subscription_name=$(az account show --subscription "${ARM_SUBSCRIPTION_ID}" --query name -o tsv)
pushd terraform > /dev/null
resource_group=$(terraform show -json terraform.tfstate 2>/dev/null | jq -r '
.values.root_module.resources[]?
| select(.type == "azurerm_resource_group") | .values.name' | head -1)
popd > /dev/null
if [ -z "$resource_group" ]; then
echo "Error: Apply Terraform first or fix state so resource_group can be read."
exit 1
fi
cat <<EOF > workspaceInfo.yaml
workspaceName: "$RW_WORKSPACE"
workspaceOwnerEmail: authors@runwhen.com
defaultLocation: location-01-us-west1
defaultLOD: detailed
cloudConfig:
azure:
subscriptionId: "$ARM_SUBSCRIPTION_ID"
tenantId: "$AZ_TENANT_ID"
clientId: "$AZ_CLIENT_ID"
clientSecret: "$AZ_CLIENT_SECRET"
resourceGroupLevelOfDetails:
$resource_group: detailed
codeCollections:
- repoURL: "$repo_url"
branch: "$branch_name"
codeBundles: ["$codebundle"]
custom:
subscription_name: $subscription_name
EOF
silent: true

run-rwl-discovery:
desc: "Run RunWhen Local Discovery on test infrastructure"
cmds:
- |
source terraform/tf.secret
CONTAINER_NAME="RunWhenLocal"
docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
rm -rf output && mkdir -p output && chmod 777 output
docker run --name $CONTAINER_NAME -p 8081:8081 -v "$(pwd)":/shared -d ghcr.io/runwhen-contrib/runwhen-local:latest
docker exec -w /workspace-builder $CONTAINER_NAME ./run.sh $1 --verbose
echo "Review generated config under output/workspaces/"
silent: true

validate-generation-rules:
desc: "Validate YAML files in .runwhen/generation-rules"
cmds:
- |
for cmd in curl yq ajv; do
command -v $cmd >/dev/null || { echo "Missing $cmd"; exit 1; }
done
temp_dir=$(mktemp -d)
curl -s -o "$temp_dir/generation-rule-schema.json" \
https://raw.githubusercontent.com/runwhen-contrib/runwhen-local/refs/heads/main/src/generation-rule-schema.json
for yaml_file in ../.runwhen/generation-rules/*.yaml; do
json_file="$temp_dir/$(basename "${yaml_file%.*}.json")"
yq -o=json "$yaml_file" > "$json_file"
ajv validate -s "$temp_dir/generation-rule-schema.json" -d "$json_file" \
--spec=draft2020 --strict=false \
&& echo "$yaml_file OK" || echo "$yaml_file failed"
done
rm -rf "$temp_dir"
silent: true

check-terraform-infra:
desc: "Check if Terraform has deployed resources"
cmds:
- |
source terraform/tf.secret
cd terraform
[ -f terraform.tfstate ] || { echo "No state"; exit 0; }
terraform state list | head -20
silent: true

build-terraform-infra:
desc: "Run terraform apply"
cmds:
- |
source terraform/tf.secret
cd terraform
terraform init
terraform apply -auto-approve
silent: true

cleanup-terraform-infra:
desc: "Cleanup deployed Terraform infrastructure"
cmds:
- |
source terraform/tf.secret
cd terraform
[ -f terraform.tfstate ] && terraform destroy -auto-approve || true
silent: true

check-and-cleanup-terraform:
desc: "Destroy Terraform resources if present"
cmds:
- task: cleanup-terraform-infra

clean-rwl-discovery:
desc: "Remove RunWhen Local output"
cmds:
- |
rm -rf output
rm -f workspaceInfo.yaml
silent: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
terraform {
backend "local" {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
resource "azurerm_resource_group" "rg" {
name = var.resource_group
location = var.location
tags = var.tags
}

resource "azurerm_virtual_network" "vnet" {
name = var.vnet_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
address_space = ["10.42.0.0/16"]
tags = var.tags
}

resource "azurerm_subnet" "subnet_a" {
name = "subnet-a"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.42.1.0/24"]
}

resource "azurerm_subnet" "subnet_b" {
name = "subnet-b"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.42.2.0/24"]
}

resource "azurerm_network_security_group" "nsg" {
name = "rw-subnet-egress-nsg"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
tags = var.tags
}

resource "azurerm_subnet_network_security_group_association" "subnet_a_nsg" {
subnet_id = azurerm_subnet.subnet_a.id
network_security_group_id = azurerm_network_security_group.nsg.id
}

output "resource_group_name" {
value = azurerm_resource_group.rg.name
}

output "vnet_name" {
value = azurerm_virtual_network.vnet.name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.18"
}
}
}

provider "azurerm" {
features {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource_group = "azure-subnet-egress-test-rg"
location = "East US"
vnet_name = "rw-subnet-egress-test-vnet"
tags = {
"env" = "test"
"lifecycle" = "deleteme"
"product" = "runwhen"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
variable "resource_group" {
type = string
}

variable "location" {
type = string
default = "East US"
}

variable "vnet_name" {
type = string
}

variable "tags" {
type = map(string)
}
Loading