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,22 @@
apiVersion: runwhen.com/v1
kind: GenerationRules
spec:
platform: azure
generationRules:
- resourceTypes:
- azure_storage_accounts
matchRules:
- type: pattern
pattern: ".+"
properties: [name]
mode: substring
slxs:
- baseName: azure-storage-investigation
qualifiers: ["resource", "subscription_id", "resource_group"]
baseTemplateName: azure-storage-account-investigation
levelOfDetail: basic
outputItems:
- type: slx
- type: sli
- type: runbook
templateName: azure-storage-account-investigation-taskset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
apiVersion: runwhen.com/v1
kind: ServiceLevelIndicator
metadata:
name: {{slx_name}}
labels:
{% include "common-labels.yaml" %}
annotations:
{% include "common-annotations.yaml" %}
spec:
displayUnitsLong: Investigation Score
displayUnitsShort: score
locations:
- {{default_location}}
description: Measures investigation completeness for Azure Storage Account RBAC, metrics, and diagnostic log coverage.
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-storage-account-investigation/sli.robot
intervalStrategy: intermezzo
intervalSeconds: 600
configProvided:
- name: AZURE_SUBSCRIPTION_ID
value: "{{ subscription_id }}"
- name: AZURE_RESOURCE_GROUP
value: "{{ resource_group.name }}"
- name: AZURE_STORAGE_ACCOUNT_NAME
value: "{{ match_resource.resource.name }}"
secretsProvided:
{% if wb_version %}
{% include "azure-auth.yaml" ignore missing %}
{% else %}
- name: azure_credentials
workspaceKey: AUTH DETAILS NOT FOUND
{% endif %}
alertConfig:
tasks:
persona: eager-edgar
sessionTTL: 10m
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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/storage/10086-icon-service-Storage-Accounts.svg
alias: {{ match_resource.resource.name }} Azure Storage Account Investigation
asMeasuredBy: Investigation completeness score covering RBAC, dependencies, metrics, and access logs.
configProvided:
- name: SLX_PLACEHOLDER
value: SLX_PLACEHOLDER
owners:
- {{ workspace.owner_email }}
statement: Investigate Azure Storage Account utilization, ownership, dependencies, and access patterns to support safe remediation of public blob access and shared key authentication.
additionalContext:
{% include "azure-hierarchy.yaml" ignore missing %}
qualified_name: "{{ match_resource.qualified_name }}"
tags:
{% include "azure-tags.yaml" ignore missing %}
- name: cloud
value: azure
- name: service
value: storage
- name: scope
value: resource
- 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: Investigate Azure Storage Account RBAC, dependencies, transaction metrics, and access logs for safe remediation planning.
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-storage-account-investigation/runbook.robot
configProvided:
- name: AZURE_SUBSCRIPTION_ID
value: "{{ subscription_id }}"
- name: AZURE_RESOURCE_GROUP
value: "{{ resource_group.name }}"
- name: AZURE_STORAGE_ACCOUNT_NAME
value: "{{ match_resource.resource.name }}"
- name: LOOKBACK_DAYS
value: "{{ custom.lookback_days | default('7') }}"
- name: ADDITIONAL_SUBSCRIPTION_IDS
value: "{{ custom.additional_subscription_ids | default('') }}"
- name: LOG_ANALYTICS_WORKSPACE_ID
value: "{{ custom.log_analytics_workspace_id | default('') }}"
secretsProvided:
{% if wb_version %}
{% include "azure-auth.yaml" ignore missing %}
{% else %}
- name: azure_credentials
workspaceKey: AUTH DETAILS NOT FOUND
{% endif %}
17 changes: 17 additions & 0 deletions codebundles/azure-storage-account-investigation/.test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Test Infrastructure

Terraform provisions a storage account with public blob access enabled, diagnostic settings forwarding StorageBlobLogs to Log Analytics, and sample RBAC role assignments for integration testing.

## Prerequisites

- Azure CLI authenticated with permissions to create storage accounts and Log Analytics workspaces
- Copy `terraform/tf.secret.example` to `terraform/tf.secret` with service principal credentials (see azure-acr-health bundle)

## Usage

```bash
task build-infra # terraform apply
task clean # terraform destroy
```

Outputs include `storage_account_name`, `resource_group_name`, and `log_analytics_workspace_id` for runbook configuration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
version: "3"

tasks:
default:
desc: "Run complete test suite"
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 for uncommitted/unpushed changes
vars:
BASE_DIR: "../"
cmds:
- |
UNCOMMITTED=$(git diff --name-only HEAD | grep -E "^${BASE_DIR}" | grep -v "/\.test/" || true)
if [ -n "$UNCOMMITTED" ]; then
echo "Uncommitted changes found. Commit and push before testing."
exit 1
fi
silent: true

generate-rwl-config:
desc: "Generate RunWhen Local configuration (workspaceInfo.yaml)"
cmds:
- |
if [ ! -f terraform/tf.secret ]; then
echo "Create terraform/tf.secret with Azure credentials before testing."
exit 1
fi
source terraform/tf.secret
repo_url=$(git config --get remote.origin.url)
branch_name=$(git rev-parse --abbrev-ref HEAD)
codebundle=$(basename "$(dirname "$PWD")")
cat <<EOF > workspaceInfo.yaml
workspaceName: "${RW_WORKSPACE:-my-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}"
codeCollections:
- repoURL: "${repo_url}"
branch: "${branch_name}"
codeBundles: ["${codebundle}"]
EOF
silent: true

run-rwl-discovery:
desc: "Run RunWhen Local Discovery"
cmds:
- echo "RunWhen Local discovery requires docker and tf.secret; see README.md"

build-terraform-infra:
desc: "Run terraform apply"
dir: terraform
cmds:
- |
if [ -f tf.secret ]; then source tf.secret; fi
terraform init
terraform apply -auto-approve

check-and-cleanup-terraform:
desc: "Destroy Terraform resources if state exists"
dir: terraform
cmds:
- |
if [ -f "terraform.tfstate" ]; then
if [ -f tf.secret ]; then source tf.secret; fi
terraform destroy -auto-approve
fi

clean-rwl-discovery:
desc: "Clean RunWhen Local output"
cmds:
- rm -rf output workspaceInfo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
terraform {
backend "local" {
path = "terraform.tfstate"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
terraform {
required_version = ">= 1.3.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
}

provider "azurerm" {
features {}
}

resource "random_string" "suffix" {
length = 8
special = false
upper = false
}

data "azurerm_client_config" "current" {}

resource "azurerm_resource_group" "test" {
name = var.resource_group
location = var.location
tags = var.tags
}

resource "azurerm_log_analytics_workspace" "logs" {
name = "${var.codebundle}-logs-${random_string.suffix.result}"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
sku = "PerGB2018"
retention_in_days = 30
tags = var.tags
}

resource "azurerm_storage_account" "investigation" {
name = "${var.codebundle}${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
account_tier = "Standard"
account_replication_type = "LRS"
allow_nested_items_to_be_public = true
min_tls_version = "TLS1_2"
tags = var.tags
}

resource "azurerm_monitor_diagnostic_setting" "blob_logs" {
name = "blob-logs-to-law"
target_resource_id = "${azurerm_storage_account.investigation.id}/blobServices/default"
log_analytics_workspace_id = azurerm_log_analytics_workspace.logs.id

enabled_log {
category = "StorageRead"
}
enabled_log {
category = "StorageWrite"
}
enabled_log {
category = "StorageDelete"
}

metric {
category = "Transaction"
enabled = true
}
}

resource "azurerm_role_assignment" "sp_reader" {
count = var.sp_principal_id != "" ? 1 : 0
scope = azurerm_storage_account.investigation.id
role_definition_name = "Storage Blob Data Reader"
principal_id = var.sp_principal_id
}

resource "azurerm_role_assignment" "sp_contributor_rg" {
count = var.sp_principal_id != "" ? 1 : 0
scope = azurerm_resource_group.test.id
role_definition_name = "Reader"
principal_id = var.sp_principal_id
}

resource "azurerm_storage_container" "public_test" {
name = "public-test"
storage_account_name = azurerm_storage_account.investigation.name
container_access_type = "blob"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
output "storage_account_name" {
value = azurerm_storage_account.investigation.name
description = "Test storage account name"
}

output "resource_group_name" {
value = azurerm_resource_group.test.name
description = "Test resource group name"
}

output "subscription_id" {
value = data.azurerm_client_config.current.subscription_id
description = "Subscription ID"
}

output "log_analytics_workspace_id" {
value = azurerm_log_analytics_workspace.logs.id
description = "Log Analytics workspace resource ID"
}

output "storage_account_id" {
value = azurerm_storage_account.investigation.id
description = "Storage account resource ID"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Example values for local testing (no secrets)
codebundle = "storinv"
resource_group = "rg-storage-account-investigation-test"
location = "eastus"
Loading
Loading