OPA deployment¶
Open Policy Agent runs in two distinct modes at Golem Trust. The first is OPA Gatekeeper, which operates as a Kubernetes admission webhook and enforces policies on every resource created or updated in any cluster. The second is a standalone OPA server, which handles policy queries from Terraform validation pipelines, the StrongDM database access integration, and the API gateway. Dr. Crucible and Ludmilla designed the two-mode architecture because Gatekeeper is tightly coupled to Kubernetes and cannot serve the non-Kubernetes use cases, while the standalone server can serve both. This runbook covers the standalone OPA server deployment; Gatekeeper installation is covered in its own runbook.
Standalone OPA: binary installation¶
The standalone OPA server runs as a systemd service on a dedicated VM in each Hetzner region. The binary is downloaded from the Golem Trust internal mirror rather than directly from GitHub:
curl -fsSL https://artifacts.golems.internal/opa/v0.68.0/opa_linux_amd64 \
-o /usr/local/bin/opa
chmod 755 /usr/local/bin/opa
opa version
Create the runtime directories and a dedicated system user:
useradd --system --no-create-home --shell /usr/sbin/nologin opa
mkdir -p /etc/opa /var/lib/opa/bundles /var/log/opa
chown -R opa:opa /var/lib/opa /var/log/opa
OPA configuration file¶
Create /etc/opa/config.yaml. This configures the bundle server pointing to the GitLab repository, Vault AppRole authentication for fetching policies, decision logging, and the Prometheus metrics endpoint:
services:
gitlab-bundles:
url: https://gitlab.golems.internal/golem-trust/opa-policies/-/raw/main
credentials:
bearer:
token_path: /var/run/opa/gitlab-token
bundles:
golem-trust:
service: gitlab-bundles
resource: "/bundles/golem-trust-bundle.tar.gz"
polling:
min_delay_seconds: 60
max_delay_seconds: 120
signing:
keyid: golem-trust-bundle-key
keys:
golem-trust-bundle-key:
key: /etc/opa/bundle-signing-key.pem
algorithm: RS256
scope: read
decision_logs:
console: false
service: graylog-decisions
reporting:
min_delay_seconds: 5
max_delay_seconds: 30
services:
graylog-decisions:
url: https://graylog.golems.internal:12900/api/inputs/opa-decisions
plugins:
envoy_ext_authz_grpc:
addr: :9191
enable_reflection: false
server:
encoding:
gzip:
min_length: 1024
systemd unit¶
Create /etc/systemd/system/opa.service:
[Unit]
Description=Open Policy Agent
Documentation=https://www.openpolicyagent.org/
After=network-online.target vault-agent.service
Wants=network-online.target
[Service]
Type=simple
User=opa
Group=opa
ExecStart=/usr/local/bin/opa run \
--server \
--addr=0.0.0.0:8181 \
--diagnostic-addr=0.0.0.0:8282 \
--config-file=/etc/opa/config.yaml \
--log-level=info \
--log-format=json
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
LimitNOFILE=65536
StandardOutput=journal
StandardError=journal
SyslogIdentifier=opa
[Install]
WantedBy=multi-user.target
Enable and start:
systemctl daemon-reload
systemctl enable opa
systemctl start opa
Health and metrics endpoints¶
OPA exposes a health endpoint on the main port and a diagnostics endpoint on 8282. Check health:
curl -s http://localhost:8181/health | python3 -m json.tool
A healthy response looks like:
{
"bundles": {
"golem-trust": {
"last_successful_activation": "2025-11-14T09:00:12.441Z",
"last_successful_download": "2025-11-14T09:00:11.883Z"
}
}
}
The Prometheus metrics endpoint is on port 8282:
curl -s http://localhost:8282/metrics | grep opa_
Key metrics to monitor: opa_request_duration_seconds, opa_bundle_last_success_time_seconds, and opa_decision_log_error_count.
Vault AppRole authentication¶
OPA fetches its GitLab token from Vault using AppRole. A Vault agent sidecar runs on the same VM and writes the token to /var/run/opa/gitlab-token. The agent configuration is in /etc/vault-agent/opa-agent.hcl:
auto_auth {
method "approle" {
config = {
role_id_file_path = "/etc/vault-agent/role-id"
secret_id_file_path = "/var/run/vault-agent/secret-id"
}
}
sink "file" {
config = {
path = "/var/run/opa/gitlab-token"
mode = 0600
}
}
}
template {
destination = "/var/run/opa/gitlab-token"
contents = "{{ with secret \"secret/data/opa/gitlab-token\" }}{{ .Data.data.token }}{{ end }}"
}
The Vault role is configured by Cheery with a 1-hour token TTL; the agent renews it automatically. If OPA logs bundle fetch failures, check the token file is present and non-empty:
ls -lh /var/run/opa/gitlab-token
Data document loading¶
Static data documents (such as approved registry lists and country code allow-lists) are included in the signed bundle from GitLab. They are not loaded separately. To inspect what data OPA currently holds:
curl -s http://localhost:8181/v1/data | python3 -m json.tool | head -60