diff --git a/collector/open-policy-agent/examples/compose/Dockerfile b/collector/open-policy-agent/examples/compose/Dockerfile new file mode 100644 index 0000000..431887d --- /dev/null +++ b/collector/open-policy-agent/examples/compose/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.9.4-slim-buster + +COPY requirements.txt / +RUN pip install -r /requirements.txt + +COPY echo_server.py / + +ENV FLASK_APP=./echo_server.py + +CMD ["flask", "run", "--host=0.0.0.0"] + +EXPOSE 5000 diff --git a/collector/open-policy-agent/examples/compose/README.md b/collector/open-policy-agent/examples/compose/README.md new file mode 100644 index 0000000..db10a15 --- /dev/null +++ b/collector/open-policy-agent/examples/compose/README.md @@ -0,0 +1,41 @@ +# Monitor Open Policy Agent with the OpenTelemetry Collector + +## Overview +Open Policy Agent (OPA) is a powerful open-source policy engine that enables fine-grained policy enforcement for cloud-native applications. Integrating OPA with Cloud Observability allows you to monitor and gain insights into how policies are enforced within your application, providing visibility into policy decisions and their impact. This README will guide you through the process of setting up OPA integration with Cloud Observability. + +## Prerequisites + +* Docker +* Docker Compose +* OPA CLI (optional) - [Installation instructions](https://www.openpolicyagent.org/docs/latest/#1-download-opa) +* A Cloud Observability account +* Cloud Observability [access token][ls-docs-access-token] + +## How to set it up + +1. **Export your Cloud Observability access token**: + ```bash + export LS_ACCESS_TOKEN= + ``` + +2. **Run the docker compose example to spin up Gitea and the OpenTelemetry Collector**: + ```bash + docker-compose up -d + ``` +Notice that for the example purposes the policy is already built and bundled. + +3. **Access OPA's web interface**: Visit http://localhost:8181. + +4. **Monitor OPA Metrics in Cloud Observability**: After setting things up, OPA metrics should start populating in your Cloud Observability dashboard. + +5. **Shutting down the monitoring setup**: + ```bash + docker-compose down + ``` + +## Configuring OPA for Enhanced Monitoring + +For detailed configurations and best practices, always consult [Open Policy Agent's official documentation][opa-docs]. + +[ls-docs-access-token]: https://docs.lightstep.com/docs/create-and-manage-access-tokens +[opa-docs]: https://www.openpolicyagent.org/docs/latest/ diff --git a/collector/open-policy-agent/examples/compose/bundle.tar.gz b/collector/open-policy-agent/examples/compose/bundle.tar.gz new file mode 100644 index 0000000..2f80fd5 Binary files /dev/null and b/collector/open-policy-agent/examples/compose/bundle.tar.gz differ diff --git a/collector/open-policy-agent/examples/compose/collector.yaml b/collector/open-policy-agent/examples/compose/collector.yaml new file mode 100644 index 0000000..e760e16 --- /dev/null +++ b/collector/open-policy-agent/examples/compose/collector.yaml @@ -0,0 +1,26 @@ +receivers: + prometheus/opa: + config: + scrape_configs: + - job_name: 'opa' + scrape_interval: 15s + static_configs: + - targets: ['opa:8181'] + +processors: + batch: + +exporters: + logging: + loglevel: debug + otlp: + endpoint: ingest.lightstep.com:443 + headers: + - lightstep-access-token: ${LS_ACCESS_TOKEN} + +service: + pipelines: + metrics: + receivers: [prometheus/opa] + processors: [batch] + exporters: [logging,otlp] diff --git a/collector/open-policy-agent/examples/compose/docker-compose.yaml b/collector/open-policy-agent/examples/compose/docker-compose.yaml new file mode 100644 index 0000000..629eb77 --- /dev/null +++ b/collector/open-policy-agent/examples/compose/docker-compose.yaml @@ -0,0 +1,54 @@ +services: + opa: + image: openpolicyagent/opa:0.40.0-rootless + ports: + - "8181:8181" + command: + - "run" + - "--server" + - "--log-format=json-pretty" + - "--set=decision_logs.console=true" + - "--set=services.nginx.url=http://bundle_server" + - "--set=bundles.nginx.service=nginx" + - "--set=bundles.nginx.resource=bundle.tar.gz" + - "--set=bundles.nginx.polling.min_delay_seconds=10" + - "--set=bundles.nginx.polling.max_delay_seconds=30" + depends_on: + - bundle_server + networks: + - integrations + + api_server: + image: openpolicyagent/demo-restful-api:0.2 + ports: + - "5000:5000" + environment: + - OPA_ADDR=http://opa:8181 + - POLICY_PATH=/v1/data/httpapi/authz + depends_on: + - opa + + bundle_server: + image: nginx:1.20.0-alpine + ports: + - 8888:80 + volumes: + - ./bundle.tar.gz:/usr/share/nginx/html/bundle.tar.gz:ro + + otel-collector: + container_name: otel-collector + image: otel/opentelemetry-collector-contrib:0.81.0 + hostname: otel-collector + restart: always + command: [ "--config=/conf/collector.yaml" ] + volumes: + - ./collector.yaml:/conf/collector.yaml:ro + environment: + LS_ACCESS_TOKEN: "${LS_ACCESS_TOKEN}" + depends_on: + - opa + networks: + - integrations + +networks: + integrations: diff --git a/collector/open-policy-agent/examples/compose/echo_server.py b/collector/open-policy-agent/examples/compose/echo_server.py new file mode 100755 index 0000000..33c0321 --- /dev/null +++ b/collector/open-policy-agent/examples/compose/echo_server.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +import base64 +import os +import logging +import sys +import json + +from flask import Flask +from flask import request +import requests + +logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) + +app = Flask(__name__) + +opa_url = os.environ.get("OPA_ADDR", "http://localhost:8181") +policy_path = os.environ.get("POLICY_PATH", "/v1/data/httpapi/authz") + +def check_auth(url, user, method, url_as_array, token): + input_dict = {"input": { + "user": user, + "path": url_as_array, + "method": method, + }} + if token is not None: + input_dict["input"]["token"] = token + + logging.info("Checking auth...") + logging.info(json.dumps(input_dict, indent=2)) + try: + rsp = requests.post(url, data=json.dumps(input_dict)) + except Exception as err: + logging.info(err) + return {} + j = rsp.json() + if rsp.status_code >= 300: + logging.info("Error checking auth, got status %s and message: %s", j.status_code, j.text) + return {} + logging.info("Auth response:") + logging.info(json.dumps(j, indent=2)) + return j + +@app.route('/', defaults={'path': ''}) +@app.route('/') +def root(path): + user_encoded = request.headers.get( + "Authorization", + "Basic " + str(base64.b64encode("Anonymous:none".encode("utf-8")), "utf-8") + ) + if user_encoded: + user_encoded = user_encoded.split("Basic ")[1] + user, _ = base64.b64decode(user_encoded).decode("utf-8").split(":") + url = opa_url + policy_path + path_as_array = path.split("/") + token = request.args["token"] if "token" in request.args else None + j = check_auth(url, user, request.method, path_as_array, token).get("result", {}) + if j.get("allow", False): + return "Success: user %s is authorized \n" % user + return "Error: user %s is not authorized to %s url /%s \n" % (user, request.method, path) + +if __name__ == "__main__": + app.run() diff --git a/collector/open-policy-agent/examples/compose/policy/example-hr.rego b/collector/open-policy-agent/examples/compose/policy/example-hr.rego new file mode 100644 index 0000000..497564a --- /dev/null +++ b/collector/open-policy-agent/examples/compose/policy/example-hr.rego @@ -0,0 +1,13 @@ +package httpapi.authz + +# Allow HR members to get anyone's salary. +allow { + input.method == "GET" + input.path = ["finance", "salary", _] + input.user == hr[_] +} + +# David is the only member of HR. +hr = [ + "david", +] diff --git a/collector/open-policy-agent/examples/compose/policy/example-jwt.rego b/collector/open-policy-agent/examples/compose/policy/example-jwt.rego new file mode 100644 index 0000000..2283908 --- /dev/null +++ b/collector/open-policy-agent/examples/compose/policy/example-jwt.rego @@ -0,0 +1,37 @@ +package httpapi.authz + +default allow = false + +# Allow users to get their own salaries. +allow { + some username + input.method == "GET" + input.path = ["finance", "salary", username] + token.payload.user == username + user_owns_token +} + +# Allow managers to get their subordinate' salaries. +allow { + some username + input.method == "GET" + input.path = ["finance", "salary", username] + token.payload.subordinates[_] == username + user_owns_token +} + +# Allow HR members to get anyone's salary. +allow { + input.method == "GET" + input.path = ["finance", "salary", _] + token.payload.hr == true + user_owns_token +} + +# Ensure that the token was issued to the user supplying it. +user_owns_token { input.user == token.payload.azp } + +# Helper to get the token payload. +token = {"payload": payload} { + [_, payload, _] := io.jwt.decode(input.token) +} diff --git a/collector/open-policy-agent/examples/compose/policy/example.rego b/collector/open-policy-agent/examples/compose/policy/example.rego new file mode 100644 index 0000000..97c0681 --- /dev/null +++ b/collector/open-policy-agent/examples/compose/policy/example.rego @@ -0,0 +1,29 @@ +package httpapi.authz + +# bob is alice's manager, and betty is charlie's. +subordinates = {"alice": [], "charlie": [], "bob": ["alice"], "betty": ["charlie"]} + +# HTTP API request +# input = { +# "path": ["finance", "salary", "alice"], +# "user": "alice", +# "method": "GET" +# } + +default allow = false + +# Allow users to get their own salaries. +allow { + some username + input.method == "GET" + input.path = ["finance", "salary", username] + input.user == username +} + +# Allow managers to get their subordinates' salaries. +allow { + some username + input.method == "GET" + input.path = ["finance", "salary", username] + subordinates[input.user][_] == username +} diff --git a/collector/open-policy-agent/examples/compose/requirements.txt b/collector/open-policy-agent/examples/compose/requirements.txt new file mode 100644 index 0000000..5eaf725 --- /dev/null +++ b/collector/open-policy-agent/examples/compose/requirements.txt @@ -0,0 +1,2 @@ +flask +requests \ No newline at end of file diff --git a/collector/open-policy-agent/metrics.csv b/collector/open-policy-agent/metrics.csv new file mode 100644 index 0000000..e85c993 --- /dev/null +++ b/collector/open-policy-agent/metrics.csv @@ -0,0 +1,60 @@ +Name,Description,Unit,DataType,Attributes +go_gc_cycles_automatic_gc_cycles_total,Count of completed GC cycles generated by the Go runtime.,,counter, +go_gc_cycles_forced_gc_cycles_total,Count of completed GC cycles forced by the application.,,counter, +go_gc_cycles_total_gc_cycles_total,Count of all completed GC cycles.,,counter, +go_gc_duration_seconds,A summary of the pause duration of garbage collection cycles.,seconds,summary, +go_gc_heap_allocs_by_size_bytes_total,"Distribution of heap allocations by approximate size. Note that this does not include tiny objects as defined by /gc/heap/tiny/allocs:objects, only tiny blocks.",bytes,histogram, +go_gc_heap_allocs_bytes_total,Cumulative sum of memory allocated to the heap by the application.,bytes,counter, +go_gc_heap_allocs_objects_total,"Cumulative count of heap allocations triggered by the application. Note that this does not include tiny objects as defined by /gc/heap/tiny/allocs:objects, only tiny blocks.",,counter, +go_gc_heap_frees_by_size_bytes_total,"Distribution of freed heap allocations by approximate size. Note that this does not include tiny objects as defined by /gc/heap/tiny/allocs:objects, only tiny blocks.",bytes,histogram, +go_gc_heap_frees_bytes_total,Cumulative sum of heap memory freed by the garbage collector.,bytes,counter, +go_gc_heap_frees_objects_total,"Cumulative count of heap allocations whose storage was freed by the garbage collector. Note that this does not include tiny objects as defined by /gc/heap/tiny/allocs:objects, only tiny blocks.",,counter, +go_gc_heap_goal_bytes,Heap size target for the end of the GC cycle.,bytes,gauge, +go_gc_heap_objects_objects,"Number of objects, live or unswept, occupying heap memory.",,gauge, +go_gc_heap_tiny_allocs_objects_total,"Count of small allocations that are packed together into blocks. These allocations are counted separately from other allocations because each individual allocation is not tracked by the runtime, only their block. Each block is already accounted for in allocs-by-size and frees-by-size.",,counter, +go_gc_pauses_seconds_total,Distribution individual GC-related stop-the-world pause latencies.,seconds,histogram, +go_goroutines,Number of goroutines that currently exist.,,gauge, +go_info,Information about the Go environment.,,gauge, +go_memory_classes_heap_free_bytes,"Memory that is completely free and eligible to be returned to the underlying system, but has not been. This metric is the runtime's estimate of free address space that is backed by physical memory.",bytes,gauge, +go_memory_classes_heap_objects_bytes,Memory occupied by live objects and dead objects that have not yet been marked free by the garbage collector.,bytes,gauge, +go_memory_classes_heap_released_bytes,"Memory that is completely free and has been returned to the underlying system. This metric is the runtime's estimate of free address space that is still mapped into the process, but is not backed by physical memory.",bytes,gauge, +go_memory_classes_heap_stacks_bytes,"Memory allocated from the heap that is reserved for stack space, whether or not it is currently in-use.",bytes,gauge, +go_memory_classes_heap_unused_bytes,Memory that is reserved for heap objects but is not currently used to hold heap objects.,bytes,gauge, +go_memory_classes_metadata_mcache_free_bytes,"Memory that is reserved for runtime mcache structures, but not in-use.",bytes,gauge, +go_memory_classes_metadata_mcache_inuse_bytes,Memory that is occupied by runtime mcache structures that are currently being used.,bytes,gauge, +go_memory_classes_metadata_mspan_free_bytes,"Memory that is reserved for runtime mspan structures, but not in-use.",bytes,gauge, +go_memory_classes_metadata_mspan_inuse_bytes,Memory that is occupied by runtime mspan structures that are currently being used.,bytes,gauge, +go_memory_classes_metadata_other_bytes,Memory that is reserved for or used to hold runtime metadata.,bytes,gauge, +go_memory_classes_os_stacks_bytes,Stack memory allocated by the underlying operating system.,bytes,gauge, +go_memory_classes_other_bytes,"Memory used by execution trace buffers, structures for debugging the runtime, finalizer and profiler specials, and more.",bytes,gauge, +go_memory_classes_profiling_buckets_bytes,Memory that is used by the stack trace hash map used for profiling.,bytes,gauge, +go_memory_classes_total_bytes,All memory mapped by the Go runtime into the current process as read-write. Note that this does not include memory mapped by code called via cgo or via the syscall package. Sum of all metrics in /memory/classes.,bytes,gauge, +go_memstats_alloc_bytes,Number of bytes allocated and still in use.,bytes,gauge, +go_memstats_alloc_bytes_total,"Total number of bytes allocated, even if freed.",bytes,counter, +go_memstats_buck_hash_sys_bytes,Number of bytes used by the profiling bucket hash table.,bytes,gauge, +go_memstats_frees_total,Total number of frees.,,counter, +go_memstats_gc_cpu_fraction,The fraction of this program's available CPU time used by the GC since the program started.,,gauge, +go_memstats_gc_sys_bytes,Number of bytes used for garbage collection system metadata.,bytes,gauge, +go_memstats_heap_alloc_bytes,Number of heap bytes allocated and still in use.,bytes,gauge, +go_memstats_heap_idle_bytes,Number of heap bytes waiting to be used.,bytes,gauge, +go_memstats_heap_inuse_bytes,Number of heap bytes that are in use.,bytes,gauge, +go_memstats_heap_objects,Number of allocated objects.,,gauge, +go_memstats_heap_released_bytes,Number of heap bytes released to OS.,bytes,gauge, +go_memstats_heap_sys_bytes,Number of heap bytes obtained from system.,bytes,gauge, +go_memstats_last_gc_time_seconds,Number of seconds since 1970 of last garbage collection.,seconds,gauge, +go_memstats_lookups_total,Total number of pointer lookups.,,counter, +go_memstats_mallocs_total,Total number of mallocs.,,counter, +go_memstats_mcache_inuse_bytes,Number of bytes in use by mcache structures.,bytes,gauge, +go_memstats_mcache_sys_bytes,Number of bytes used for mcache structures obtained from system.,bytes,gauge, +go_memstats_mspan_inuse_bytes,Number of bytes in use by mspan structures.,bytes,gauge, +go_memstats_mspan_sys_bytes,Number of bytes used for mspan structures obtained from system.,bytes,gauge, +go_memstats_next_gc_bytes,Number of heap bytes when next garbage collection will take place.,bytes,gauge, +go_memstats_other_sys_bytes,Number of bytes used for other system allocations.,bytes,gauge, +go_memstats_stack_inuse_bytes,Number of bytes in use by the stack allocator.,bytes,gauge, +go_memstats_stack_sys_bytes,Number of bytes obtained from system for stack allocator.,bytes,gauge, +go_memstats_sys_bytes,Number of bytes obtained from system.,bytes,gauge, +go_sched_goroutines_goroutines,Count of live goroutines.,,gauge, +go_sched_latencies_seconds,Distribution of the time goroutines have spent in the scheduler in a runnable state before actually running.,seconds,histogram, +http_request_duration_seconds_sum,The HTTP request latencies in seconds.,seconds,summary,"handler,method,code" +http_request_duration_seconds_count,The HTTP request latencies in seconds.,,summary,"handler,method,code" +http_request_duration_seconds_bucket,The HTTP request latencies in seconds.,seconds,histogram,"handler,method,code" \ No newline at end of file