diff --git a/docs/examples/policies/_testutils.sh b/docs/examples/policies/_testutils.sh index f6ae44f7a..86e079001 100644 --- a/docs/examples/policies/_testutils.sh +++ b/docs/examples/policies/_testutils.sh @@ -151,23 +151,35 @@ test_policy_eval() { actual_result="pass" # No violations = test should pass fi else - actual_result="fail" # Command failed + actual_result="failed_eval" # Command failed to execute fi # Compare with expected result if [ "$actual_result" = "$expected_result" ]; then - if [ "$expected_result" = "pass" ]; then - echo -e "${GREEN}✓ PASSED${NC}" - else - echo -e "${GREEN}✓ FAILED (as expected)${NC}" - fi + case "$expected_result" in + "pass") + echo -e "${GREEN}✓ PASSED${NC}" + ;; + "fail") + echo -e "${GREEN}✓ FAILED (as expected)${NC}" + ;; + "failed_eval") + echo -e "${GREEN}✓ EVAL FAILED (as expected)${NC}" + ;; + esac ((TESTS_PASSED++)) else - if [ "$expected_result" = "pass" ]; then - echo -e "${RED}✗ FAILED (expected to pass but failed)${NC}" - else - echo -e "${RED}✗ PASSED (expected to fail but passed)${NC}" - fi + case "$expected_result" in + "pass") + echo -e "${RED}✗ FAILED (expected to pass but $actual_result)${NC}" + ;; + "fail") + echo -e "${RED}✗ PASSED (expected to fail but $actual_result)${NC}" + ;; + "failed_eval") + echo -e "${RED}✗ PASSED (expected eval failure but $actual_result)${NC}" + ;; + esac # Show skip reason if policy wasn't executed if [ -n "${skip_reason:-}" ]; then diff --git a/docs/examples/policies/http-hostname-validation/README.md b/docs/examples/policies/http-hostname-validation/README.md new file mode 100644 index 000000000..307db0b84 --- /dev/null +++ b/docs/examples/policies/http-hostname-validation/README.md @@ -0,0 +1,66 @@ +# HTTP Hostname Validation Example + +Demonstrates how to make HTTP requests to external APIs from Chainloop policies while maintaining security through hostname allowlisting. + +## What This Policy Does + +This policy is just an example which validates the Chainloop platform version by making HTTP requests to its external info API. + +It demonstrates: + +- **HTTP API Integration** - Makes requests to `https://app.chainloop.dev/api/info` +- **Response validation** - Compares API response against expected version (configurable) +- **Hostname Security** - Requires explicit hostname allowlisting for HTTP requests +- **Error Handling** - Gracefully handles network failures and API errors + +## Policy Parameters + +| Parameter | Description | Required | Default | Example | +|-----------|-------------|----------|---------|---------| +| `expected_version` | Expected platform version to validate against | ❌ No | `1.2.3` | `v0.256.0`, `2.0.0` | + +## The HTTP Security Challenge + +By default, Chainloop policies **block all HTTP requests** for security reasons. This policy will fail with: + +``` +ERR evaluating policy: unallowed host: app.chainloop.dev +``` + +## Solution: Hostname Allowlisting + +Use the `--allowed-hostnames` flag to explicitly allow specific hostnames: + +```bash +chainloop policy develop eval \ + --policy policy.yaml \ + --material testdata/empty.json \ + --kind EVIDENCE \ + --allowed-hostnames app.chainloop.dev +``` + +## Using in Workflow Contracts + +Add this policy to your workflow contract: + +```yaml +apiVersion: workflowcontract.chainloop.dev/v1 +kind: WorkflowContract +metadata: + name: platform-validation-workflow +spec: + materials: + - type: EVIDENCE + name: platform-check + + policies: + - ref: ./http-hostname-validation/policy.yaml + with: + expected_version: "2.0.0" # Optional, defaults to "1.2.3" +``` + +**Note**: When running in production, the Control Plane manages hostname allowlisting through organization settings. The `--allowed-hostnames` flag is only for local development and testing. + +## Development & Testing + +See [test.sh](test.sh) for the test cases. \ No newline at end of file diff --git a/docs/examples/policies/http-hostname-validation/policy.yaml b/docs/examples/policies/http-hostname-validation/policy.yaml new file mode 100644 index 000000000..d21baf1db --- /dev/null +++ b/docs/examples/policies/http-hostname-validation/policy.yaml @@ -0,0 +1,66 @@ +apiVersion: workflowcontract.chainloop.dev/v1 +kind: Policy +metadata: + name: http-hostname-validation + description: Validates Chainloop platform version by making HTTP requests to external APIs +spec: + inputs: + - name: expected_version + description: Expected platform version to validate against + default: "1.2.3" + policies: + - kind: EVIDENCE + embedded: | + package main + + import rego.v1 + + ################################ + # Common section do NOT change # + ################################ + + result := { + "skipped": skipped, + "violations": violations, + "skip_reason": skip_reason, + "ignore": ignore, + } + + default skip_reason := "" + + skip_reason := m if { + not valid_input + m := "invalid input" + } + + default skipped := true + + skipped := false if valid_input + + default ignore := false + + ######################################## + # EO Common section, custom code below # + ######################################## + # Validates if the input is valid and can be understood by this policy + valid_input := true + + # Make HTTP request to Chainloop API + api_response := http.send({ + "method": "GET", + "url": "https://app.chainloop.dev/api/info", + "cache": true, + }) + + # Extract platform version from API response + platform_version := api_response.body.platform.version + + # Get expected version from input (default handled by engine) + expected_version := input.args.expected_version + + violations contains msg if { + valid_input + api_response.status_code == 200 + platform_version != expected_version + msg := sprintf("Platform version violation: expected '%s', got '%s'", [expected_version, platform_version]) + } diff --git a/docs/examples/policies/http-hostname-validation/test.sh b/docs/examples/policies/http-hostname-validation/test.sh new file mode 100755 index 000000000..92c2901da --- /dev/null +++ b/docs/examples/policies/http-hostname-validation/test.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# HTTP Hostname Validation Policy Tests +# This file demonstrates the --allowed-hostnames flag functionality +source ../_testutils.sh + +# Initialize test framework +init_tests + +# Verify required files exist +verify_files "policy.yaml" "testdata/empty.json" + +# Get current platform version from the API to perform the test passing the right version +get_platform_version() { + if command -v curl &> /dev/null; then + curl -s https://app.chainloop.dev/api/info | grep -o '"version":"[^"]*"' | cut -d'"' -f4 + elif command -v wget &> /dev/null; then + wget -qO- https://app.chainloop.dev/api/info | grep -o '"version":"[^"]*"' | cut -d'"' -f4 + else + echo "v0.256.0" # fallback version + fi +} + +test_section "Policy Validation" +test_policy_lint "policy.yaml" + +test_policy_eval "No Allowed Hostnames - Should Fail the evaluation" "failed_eval" \ + --kind EVIDENCE \ + --material testdata/empty.json + +test_policy_eval "With Wrong Allowed Hostname - Should Fail" "failed_eval" \ + --kind EVIDENCE \ + --material testdata/empty.json \ + --allowed-hostnames example.com + +test_policy_eval "With Correct Allowed Hostname should run evaluation but fail because of version mismatch" "fail" \ + --kind EVIDENCE \ + --material testdata/empty.json \ + --allowed-hostnames app.chainloop.dev + +echo "Fetching current platform version..." +CURRENT_VERSION=$(get_platform_version) +echo "Current platform version: $CURRENT_VERSION" +echo "" + +test_policy_eval "Custom Expected Version (matching current platform)" "pass" \ + --kind EVIDENCE \ + --material testdata/empty.json \ + --allowed-hostnames app.chainloop.dev \ + --input expected_version=$CURRENT_VERSION + +# Print test summary and exit +test_summary \ No newline at end of file diff --git a/docs/examples/policies/http-hostname-validation/testdata/empty.json b/docs/examples/policies/http-hostname-validation/testdata/empty.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/docs/examples/policies/http-hostname-validation/testdata/empty.json @@ -0,0 +1 @@ +{} \ No newline at end of file