From 8a8ce863e94334f2a80b592e2b1de033062d1d05 Mon Sep 17 00:00:00 2001 From: tim775 <52185+tim775@users.noreply.github.com> Date: Sat, 18 Dec 2021 15:10:24 -0500 Subject: [PATCH] feat: Add Sentinel example --- .github/workflows/examples_test.yml | 59 +++- README.md | 1 + examples/README.md | 1 + examples/sentinel/README.md | 97 ++++++ examples/sentinel/code/plan.json | 400 +++++++++++++++++++++++++ examples/sentinel/policy/policy.policy | 48 +++ 6 files changed, 605 insertions(+), 1 deletion(-) create mode 100644 examples/sentinel/README.md create mode 100644 examples/sentinel/code/plan.json create mode 100644 examples/sentinel/policy/policy.policy diff --git a/.github/workflows/examples_test.yml b/.github/workflows/examples_test.yml index 5fdd13a..68dd70b 100644 --- a/.github/workflows/examples_test.yml +++ b/.github/workflows/examples_test.yml @@ -152,6 +152,35 @@ jobs: cp /tmp/infracost_comment.md ./testdata/multi_terraform_workspace_config_file_comment_golden.md if: env.UPDATE_GOLDEN_FILES == 'true' + open-policy-agent: + name: Open Policy Agent + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Infracost + uses: ./setup + with: + api-key: ${{ secrets.INFRACOST_API_KEY }} + - name: Setup OPA + uses: infracost/setup-opa@v1 + - name: Run Infracost + run: >- + infracost breakdown --path=examples/conftest/code/plan.json + --format=json --out-file=/tmp/infracost.json + - name: Run OPA + run: >- + opa eval --input /tmp/infracost.json -d + examples/opa/policy/policy.rego --format pretty "data.infracost.deny" + | tee /tmp/opa.out + - name: Check Policies + run: | + denyReasons=$(- + infracost breakdown --path=examples/sentinel/code/plan.json + --format=json --out-file=/tmp/infracost.json + - name: Run Sentinel + run: >- + sentinel apply -global breakdown="$(cat /tmp/infracost.json)" + examples/sentinel/policy/policy.policy | tee /tmp/sentinel.out + - name: Check Policies + run: | + result=$( (BEGIN EXAMPLE) +```yml +name: Sentinel +on: [pull_request] + +jobs: + sentinel: + name: Sentinel + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Infracost + uses: infracost/actions/setup@v1 + with: + api-key: ${{ secrets.INFRACOST_API_KEY }} + + - name: Setup Sentinel + uses: innovationnorway/setup-sentinel@v1 + + - name: Run Infracost + run: infracost breakdown --path=examples/sentinel/code/plan.json --format=json --out-file=/tmp/infracost.json + + - name: Run Sentinel + run: sentinel apply -global breakdown="$(cat /tmp/infracost.json)" examples/sentinel/policy/policy.policy | tee /tmp/sentinel.out + + - name: Check Policies + run: | + result=$( (END EXAMPLE) diff --git a/examples/sentinel/code/plan.json b/examples/sentinel/code/plan.json new file mode 100644 index 0000000..5528dd9 --- /dev/null +++ b/examples/sentinel/code/plan.json @@ -0,0 +1,400 @@ +{ + "format_version": "0.2", + "terraform_version": "1.0.2", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_instance.web_app", + "mode": "managed", + "type": "aws_instance", + "name": "web_app", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "ami": "ami-674cbc1e", + "credit_specification": [], + "disable_api_termination": null, + "ebs_block_device": [ + { + "delete_on_termination": true, + "device_name": "my_data", + "iops": 800, + "tags": null, + "volume_size": 1000, + "volume_type": "io1" + } + ], + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "m5.4xlarge", + "monitoring": null, + "root_block_device": [ + { + "delete_on_termination": true, + "tags": null, + "volume_size": 50 + } + ], + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + }, + "sensitive_values": { + "capacity_reservation_specification": [], + "credit_specification": [], + "ebs_block_device": [ + {} + ], + "enclave_options": [], + "ephemeral_block_device": [], + "ipv6_addresses": [], + "metadata_options": [], + "network_interface": [], + "root_block_device": [ + {} + ], + "secondary_private_ips": [], + "security_groups": [], + "tags_all": {}, + "vpc_security_group_ids": [] + } + }, + { + "address": "aws_lambda_function.hello_world", + "mode": "managed", + "type": "aws_lambda_function", + "name": "hello_world", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": null, + "function_name": "hello_world", + "handler": "exports.test", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 1024, + "package_type": "Zip", + "publish": false, + "reserved_concurrent_executions": -1, + "role": "arn:aws:lambda:us-east-1:account-id:resource-id", + "runtime": "nodejs12.x", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "sensitive_values": { + "dead_letter_config": [], + "environment": [], + "file_system_config": [], + "image_config": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_instance.web_app", + "mode": "managed", + "type": "aws_instance", + "name": "web_app", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "ami-674cbc1e", + "credit_specification": [], + "disable_api_termination": null, + "ebs_block_device": [ + { + "delete_on_termination": true, + "device_name": "my_data", + "iops": 800, + "tags": null, + "volume_size": 1000, + "volume_type": "io1" + } + ], + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "m5.4xlarge", + "monitoring": null, + "root_block_device": [ + { + "delete_on_termination": true, + "tags": null, + "volume_size": 50 + } + ], + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + }, + "after_unknown": { + "arn": true, + "associate_public_ip_address": true, + "availability_zone": true, + "capacity_reservation_specification": true, + "cpu_core_count": true, + "cpu_threads_per_core": true, + "credit_specification": [], + "ebs_block_device": [ + { + "encrypted": true, + "kms_key_id": true, + "snapshot_id": true, + "throughput": true, + "volume_id": true + } + ], + "enclave_options": true, + "ephemeral_block_device": true, + "host_id": true, + "id": true, + "instance_initiated_shutdown_behavior": true, + "instance_state": true, + "ipv6_address_count": true, + "ipv6_addresses": true, + "key_name": true, + "metadata_options": true, + "network_interface": true, + "outpost_arn": true, + "password_data": true, + "placement_group": true, + "primary_network_interface_id": true, + "private_dns": true, + "private_ip": true, + "public_dns": true, + "public_ip": true, + "root_block_device": [ + { + "device_name": true, + "encrypted": true, + "iops": true, + "kms_key_id": true, + "throughput": true, + "volume_id": true, + "volume_type": true + } + ], + "secondary_private_ips": true, + "security_groups": true, + "subnet_id": true, + "tags_all": true, + "tenancy": true, + "vpc_security_group_ids": true + }, + "before_sensitive": false, + "after_sensitive": { + "capacity_reservation_specification": [], + "credit_specification": [], + "ebs_block_device": [ + {} + ], + "enclave_options": [], + "ephemeral_block_device": [], + "ipv6_addresses": [], + "metadata_options": [], + "network_interface": [], + "root_block_device": [ + {} + ], + "secondary_private_ips": [], + "security_groups": [], + "tags_all": {}, + "vpc_security_group_ids": [] + } + } + }, + { + "address": "aws_lambda_function.hello_world", + "mode": "managed", + "type": "aws_lambda_function", + "name": "hello_world", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "code_signing_config_arn": null, + "dead_letter_config": [], + "description": null, + "environment": [], + "file_system_config": [], + "filename": null, + "function_name": "hello_world", + "handler": "exports.test", + "image_config": [], + "image_uri": null, + "kms_key_arn": null, + "layers": null, + "memory_size": 1024, + "package_type": "Zip", + "publish": false, + "reserved_concurrent_executions": -1, + "role": "arn:aws:lambda:us-east-1:account-id:resource-id", + "runtime": "nodejs12.x", + "s3_bucket": null, + "s3_key": null, + "s3_object_version": null, + "tags": null, + "timeout": 3, + "timeouts": null, + "vpc_config": [] + }, + "after_unknown": { + "arn": true, + "dead_letter_config": [], + "environment": [], + "file_system_config": [], + "id": true, + "image_config": [], + "invoke_arn": true, + "last_modified": true, + "qualified_arn": true, + "signing_job_arn": true, + "signing_profile_version_arn": true, + "source_code_hash": true, + "source_code_size": true, + "tags_all": true, + "tracing_config": true, + "version": true, + "vpc_config": [] + }, + "before_sensitive": false, + "after_sensitive": { + "dead_letter_config": [], + "environment": [], + "file_system_config": [], + "image_config": [], + "tags_all": {}, + "tracing_config": [], + "vpc_config": [] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "version_constraint": "3.50.0", + "expressions": { + "access_key": { + "constant_value": "mock_access_key" + }, + "region": { + "constant_value": "us-east-1" + }, + "secret_key": { + "constant_value": "mock_secret_key" + }, + "skip_credentials_validation": { + "constant_value": true + }, + "skip_requesting_account_id": { + "constant_value": true + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_instance.web_app", + "mode": "managed", + "type": "aws_instance", + "name": "web_app", + "provider_config_key": "aws", + "expressions": { + "ami": { + "constant_value": "ami-674cbc1e" + }, + "ebs_block_device": [ + { + "device_name": { + "constant_value": "my_data" + }, + "iops": { + "constant_value": 800 + }, + "volume_size": { + "constant_value": 1000 + }, + "volume_type": { + "constant_value": "io1" + } + } + ], + "instance_type": { + "constant_value": "m5.4xlarge" + }, + "root_block_device": [ + { + "volume_size": { + "constant_value": 50 + } + } + ] + }, + "schema_version": 1 + }, + { + "address": "aws_lambda_function.hello_world", + "mode": "managed", + "type": "aws_lambda_function", + "name": "hello_world", + "provider_config_key": "aws", + "expressions": { + "function_name": { + "constant_value": "hello_world" + }, + "handler": { + "constant_value": "exports.test" + }, + "memory_size": { + "constant_value": 1024 + }, + "role": { + "constant_value": "arn:aws:lambda:us-east-1:account-id:resource-id" + }, + "runtime": { + "constant_value": "nodejs12.x" + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/examples/sentinel/policy/policy.policy b/examples/sentinel/policy/policy.policy new file mode 100644 index 0000000..ce0a333 --- /dev/null +++ b/examples/sentinel/policy/policy.policy @@ -0,0 +1,48 @@ +import "strings" + +limitTotalDiff = rule { + float(breakdown.totalMonthlyCost) < 1500 +} + +awsInstances = filter breakdown.projects[0].breakdown.resources as _, resource { + strings.split(resource.name, ".")[0] is "aws_instance" +} + +limitInstanceCost = rule { + all awsInstances as _, instance { + float(instance.hourlyCost) <= 2.00 + } +} + +instanceBaseCost = func(instance) { + cost = 0.0 + for instance.costComponents as cc { + cost += float(cc.hourlyCost) + } + return cost +} + +instanceIOPSCost = func(instance) { + cost = 0.0 + for instance.subresources as sr { + for sr.costComponents as cc { + if cc.name == "Provisioned IOPS" { + cost += float(cc.hourlyCost) + } + } + } + return cost +} + +limitInstanceIOPSCost = rule { + all awsInstances as _, instance { + instanceIOPSCost(instance) <= instanceBaseCost(instance) + } +} + +main = rule { + limitTotalDiff and + limitInstanceCost and + limitInstanceIOPSCost +} +