OPAL 0.9.9 on AWS User Guide
Overview
This guide covers launching and using the OPAL 0.9.9 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg identity, secrets and PKI listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
OPAL (Open Policy Administration Layer, by Permit.io) keeps Open Policy Agent instances up to date in real time. The OPAL server tracks a git repository of Rego policies and accepts data updates; every OPAL client subscribes to the server over a websocket and loads the new policy and data into its OPA the moment they change, so your services ask OPA for an authorisation decision on their own host and always get an answer from current rules. This image runs the server, one client and OPA together on one instance, ready to make decisions on the first boot and ready to serve more clients on other hosts.
Upstream OPAL starts with development defaults: no master token, a websocket token of THIS_IS_A_DEV_SECRET, no JWT signing key (so the server API checks no token at all), browser access allowed from every origin, and the client's built-in OPA listening on all addresses without authentication. The cloudimg image makes it safe to run on a public address:
- HTTPS from the first boot. nginx serves the OPAL server API on port 443 with a certificate generated on your instance's first boot (an end-entity certificate naming the instance's DNS names and addresses). Port 80 only redirects to HTTPS. The OPAL server itself listens on
127.0.0.1:7002. - Secrets unique to your instance. First boot generates the master token, the RS256 JWT signing key pair (its public half published at
/.well-known/jwks.json), the websocket token, the webhook secret and the local client's JWT. Nothing is baked into the image, and the services receive them as systemd credentials: they are never on a command line, in a service's environment or in a log. - Every API route needs a token. The master token only exchanges for JWTs at
POST /token; every other route needs a JWT signed by this instance's key, and a client JWT can read policy and data but cannot publish. Anything else answers401. nginx exposes only the OPAL server API (everything else is404), rate limits the token exchange, and refuses state-changing requests and websocket upgrades sent from a foreign web origin. - OPA is not exposed. OPA (
127.0.0.1:8181) and the client API (127.0.0.1:7000) listen on loopback only, and an owner firewall rule lets only root and the OPAL services reach them, so another local user cannot rewrite policies through OPA's API. You query OPA on the instance as root. - A local policy repository, ready to edit. Policies come from a git repository on the instance (
/var/lib/opal/policy-repo.git) with an example deny-by-default policy; a push reaches OPA within seconds. Point the server at your own repository whenever you like (Step 11). - Metadata service blocked. OPA, the OPAL server and client, nginx and the first boot service are blocked from the instance metadata service, so a data source URL or policy repository URL can never be used to read the instance's credentials. The AMI requires IMDSv2.
- Data sources cannot reach into your network. The client fetches data source URLs only through a cloudimg restricted fetcher:
GETonly, no request body, no redirects, and only to public internet addresses. Loopback, private (RFC 1918), CGNAT, link-local and IPv6 unique-local addresses and every address in this instance's VPC are refused, also when a DNS name resolves to one, so a datasource token cannot turn the client against OPA's API, the OPAL server or the other hosts in your VPC (Step 12). - Pinned and patched. The OPAL 0.9.9 wheels are installed from a sha256-pinned lock file with no compiler or package cache left on the image, OPA 1.22.2 is a cloudimg build of the upstream tag with its dependency security updates applied, and Ubuntu 24.04 LTS is fully patched at build time with unattended security upgrades switched on.
What is on the image: OPAL server and client 0.9.9 (/opt/opal/venv), OPA 1.22.2 (/usr/local/bin/opa), nginx, git, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. Licences and the exact pinned versions are in /usr/share/doc/cloudimg-opal.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: t3.medium (m5.large for many clients or large data sets)
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet.
- A security group that allows TCP 22 (SSH) from your administrators and TCP 443 (the OPAL server API and the client websocket) from the hosts that publish data or run OPAL clients. Port 80 is optional (it only redirects to HTTPS). Never open 7000, 7002 or 8181: they listen on loopback only and refuse connections from the network even if a security group allows them.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick OPAL 0.9.9 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose your instance type, key pair, subnet and a security group as above.
With the AWS CLI (replace the values in angle brackets):
aws ec2 create-security-group --region <region> --vpc-id <your-vpc> \
--group-name opal --description "OPAL: SSH from admins, HTTPS from publishers and OPAL clients"
aws ec2 authorize-security-group-ingress --region <region> --group-id <sg-id> --ip-permissions \
'IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges=[{CidrIp=<your-admin-cidr>}]' \
'IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=<your-client-cidr>}]'
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type t3.medium --key-name <your-key-pair> \
--security-group-ids <sg-id> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=opal}]'
First boot takes well under a minute. It names the server after the instance's public DNS name (or its private DNS name when it has no public one). To use your own name from the first boot, add user data that writes it before first boot runs, for example #!/bin/sh followed by mkdir -p /etc/cloudimg && echo opal.example.com > /etc/cloudimg/opal-domain.preset, or change it later with opal-set-domain (Step 10).
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| OPAL 0.9.9 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot generates the secrets and the certificate, seeds the policy repository, then starts OPA, the OPAL server, the OPAL client and nginx:
systemctl is-active opa opal-server opal-client nginx opal-guard
sudo test -f /var/lib/cloudimg/opal-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on the network; OPA (8181), the OPAL server (7002) and the OPAL client API (7000) are on loopback:
sudo ss -tlnH | awk '{print $4}' | sort -u
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
127.0.0.1:7000
127.0.0.1:7002
127.0.0.1:8181
127.0.0.53%lo:53
127.0.0.54:53
[::]:22
[::]:443
[::]:80
The self-test exchanges the master token for a JWT over HTTPS, publishes a data update, checks that OPA's decision follows it, and removes it again:
sudo opal-selftest
OPAL_SELFTEST_OK
Step 4: Read your instance's details
/root/opal-credentials.txt (root only) holds the server's URLs, the certificate fingerprint and the master token (OPAL_AUTH_MASTER_TOKEN, 64 hexadecimal characters). Show everything except the token:
sudo grep -v '^OPAL_AUTH_MASTER_TOKEN' /root/opal-credentials.txt
# OPAL 0.9.9 - written on first boot by opal-firstboot.service. Unique to this instance. Keep it secret.
OPAL_SERVER_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com
OPAL_JWKS_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/.well-known/jwks.json
OPAL_TLS_SHA256=AB:CD:...:EF
#
# The master token only exchanges for JWTs at POST /token ({"type": "client" | "datasource"}); every other OPAL server
# route needs a JWT this instance signed. Or mint one here without the master token: sudo opal-token client 365 > client.jwt
# OPA (the policy engine, 127.0.0.1:8181) is NOT exposed; query it on this instance as root, e.g.
# sudo curl -s http://127.0.0.1:8181/v1/data/cloudimg/authz/allow -d '{"input":{"user":"alice","action":"read"}}'
# Policies: the git repository /var/lib/opal/policy-repo.git (edit /var/lib/opal/policy-work as the opal user).
# The certificate is self-signed and unique to this instance: compare its SHA-256 fingerprint (above) with the one your
# client shows, or replace it with a CA-issued certificate for your own name.
# Your own name (point DNS at this instance first): sudo opal-set-domain opal.example.com
# Self-test: sudo opal-selftest User guide: https://www.cloudimg.co.uk/guides/opal-0-9-9-on-ubuntu-24-04-aws/
The master token is the key to the whole server: anyone holding it can mint JWTs that publish data. Keep it on the instance and hand out JWTs instead (Step 6); the commands there read it from the file without ever printing it.
The certificate is self-signed for this instance and is not a certificate authority. Compare its fingerprint with OPAL_TLS_SHA256 above before you trust it on another host. The curl below fetches the public signing keys through nginx on this instance (--connect-to sends it to 127.0.0.1, so it works however your security group and DNS are set up, and the certificate must still match the server's name):
openssl x509 -in /etc/ssl/opal/opal.crt -noout -fingerprint -sha256
curl -sS --cacert /etc/ssl/opal/opal.crt --connect-to ::127.0.0.1:443 <OPAL_JWKS_URL>
sha256 Fingerprint=AB:CD:...:EF
{"keys":[{"kty":"RSA","kid":"...","alg":"RS256","n":"...","e":"AQAB"}]}
Step 5: Ask OPA for a decision
The image ships an example policy, cloudimg/authz.rego: deny by default, and allow a user an action when the data lists that action for that user. With no data published yet, alice may not read:
sudo curl -s http://127.0.0.1:8181/v1/data/cloudimg/authz/allow -d '{"input":{"user":"alice","action":"read"}}'; echo
{"result":false}
Your applications ask the same question of the OPA next to them (on this instance, or on any host that runs an OPAL client, Step 9). OPA is never reachable from the network, and on this instance only root and the OPAL services may connect to it:
curl -s -o /dev/null -m 5 -w '%{http_code}\n' http://127.0.0.1:8181/v1/data || echo "refused for user $(id -un)"
000
refused for user ubuntu
Step 6: Exchange the master token for JWTs
Publishers use a datasource JWT and OPAL clients use a client JWT. Ask the server for them over HTTPS with the master token. The commands below keep every token in a file readable by you only, never on a command line. On the instance, --connect-to ::127.0.0.1:443 sends each request to nginx on this machine under the server's public name, so it does not depend on your security group or on how the name resolves inside the VPC (in the first minutes after launch it can still resolve to the public address):
umask 077
HOST=$(cat /etc/cloudimg/opal-domain)
sudo sed -n 's/^OPAL_AUTH_MASTER_TOKEN=/Authorization: Bearer /p' /root/opal-credentials.txt > ~/opal-master.h
curl -s --cacert /etc/ssl/opal/opal.crt --connect-to ::127.0.0.1:443 -H @"$HOME/opal-master.h" -H 'Content-Type: application/json' \
-d '{"type":"datasource"}' "https://$HOST/token" \
| python3 -c 'import json,sys; print("Authorization: Bearer " + json.load(sys.stdin)["token"])' > ~/opal-ds.h
curl -s --cacert /etc/ssl/opal/opal.crt --connect-to ::127.0.0.1:443 -H @"$HOME/opal-master.h" -H 'Content-Type: application/json' \
-d '{"type":"client","ttl":2592000}' "https://$HOST/token" \
| python3 -c 'import json,sys; print("Authorization: Bearer " + json.load(sys.stdin)["token"])' > ~/opal-client.h
wc -c ~/opal-ds.h ~/opal-client.h
test -s ~/opal-ds.h && test -s ~/opal-client.h && echo "both JWTs saved"
784 /home/ubuntu/opal-ds.h
779 /home/ubuntu/opal-client.h
1563 total
both JWTs saved
The response of POST /token is {"token": "<JWT>", "type": "bearer", "details": {...}}, where details names the peer type and the expiry (one year by default; ttl is in seconds). On the instance itself you can also mint a JWT without the master token: sudo opal-token client 365 > client.jwt (or datasource; the last argument is the lifetime in days).
Step 7: Publish data and watch the decision change
Grant alice the read action by publishing a data update to the OPAL server. The server sends it to every subscribed client, and the client writes it into OPA:
HOST=$(cat /etc/cloudimg/opal-domain)
curl -s --cacert /etc/ssl/opal/opal.crt --connect-to ::127.0.0.1:443 -H @"$HOME/opal-ds.h" -H 'Content-Type: application/json' \
-d '{"entries":[{"url":"","data":{"actions":["read"]},"dst_path":"/cloudimg/users/alice","topics":["policy_data"],"save_method":"PUT"}],"reason":"grant alice read"}' \
"https://$HOST/data/config"; echo
sleep 3
sudo curl -s http://127.0.0.1:8181/v1/data/cloudimg/authz/allow -d '{"input":{"user":"alice","action":"read"}}'; echo
sudo curl -s http://127.0.0.1:8181/v1/data/cloudimg/authz/allow -d '{"input":{"user":"alice","action":"write"}}'; echo
sudo curl -s http://127.0.0.1:8181/v1/data/cloudimg/users; echo
sudo curl -s http://127.0.0.1:8181/v1/data/cloudimg/users/alice | grep -q read && echo "alice's grant is in OPA"
{"status":"ok"}
{"result":true}
{"result":false}
{"result":{"alice":{"actions":["read"]}}}
alice's grant is in OPA
An entry can carry its data inline (as here) or a url the client fetches the data from (an HTTPS API of yours on a public address, for example), with config for headers. The client fetches with GET only and refuses private and loopback addresses (Step 12). A client JWT can read but not publish, and anything without a valid JWT is refused:
HOST=$(cat /etc/cloudimg/opal-domain)
curl -s -o /dev/null -w 'client JWT publishing: %{http_code}\n' --cacert /etc/ssl/opal/opal.crt --connect-to ::127.0.0.1:443 -H @"$HOME/opal-client.h" \
-H 'Content-Type: application/json' -d '{"entries":[],"reason":"x"}' "https://$HOST/data/config"
curl -s -o /dev/null -w 'no token: %{http_code}\n' --cacert /etc/ssl/opal/opal.crt --connect-to ::127.0.0.1:443 \
-H 'Content-Type: application/json' -d '{"entries":[],"reason":"x"}' "https://$HOST/data/config"
curl -s -o /dev/null -w 'wrong token: %{http_code}\n' --cacert /etc/ssl/opal/opal.crt --connect-to ::127.0.0.1:443 -H 'Authorization: Bearer not-a-real-token' \
"https://$HOST/policy?path=cloudimg"
curl -s -o /dev/null -w 'OPA through nginx: %{http_code}\n' --cacert /etc/ssl/opal/opal.crt --connect-to ::127.0.0.1:443 "https://$HOST/v1/data"
client JWT publishing: 401
no token: 401
wrong token: 401
OPA through nginx: 404
Step 8: Change a policy in git
The OPAL server tracks the main branch of /var/lib/opal/policy-repo.git and checks it every 10 seconds. Work in the checkout /var/lib/opal/policy-work as the opal user. Add a rule that lets a user granted * take any action, commit and push it, then grant bob *:
sudo -u opal -H tee -a /var/lib/opal/policy-work/cloudimg/authz.rego > /dev/null <<'EOF'
# A user granted "*" may take any action.
allow if {
"*" in data.cloudimg.users[input.user].actions
}
EOF
sudo -u opal -H git -C /var/lib/opal/policy-work commit -qam "Allow users granted * any action"
sudo -u opal -H git -C /var/lib/opal/policy-work push -q
HOST=$(cat /etc/cloudimg/opal-domain)
curl -s -o /dev/null -w 'grant bob *: %{http_code}\n' --cacert /etc/ssl/opal/opal.crt --connect-to ::127.0.0.1:443 -H @"$HOME/opal-ds.h" -H 'Content-Type: application/json' \
-d '{"entries":[{"url":"","data":{"actions":["*"]},"dst_path":"/cloudimg/users/bob","topics":["policy_data"],"save_method":"PUT"}],"reason":"grant bob everything"}' \
"https://$HOST/data/config"
for i in $(seq 1 30); do r=$(sudo curl -s http://127.0.0.1:8181/v1/data/cloudimg/authz/allow -d '{"input":{"user":"bob","action":"delete"}}'); [ "$r" = '{"result":true}' ] && break; sleep 2; done; echo "bob delete: $r"; [ "$r" = '{"result":true}' ]
grant bob *: 200
bob delete: {"result":true}
The new rule reached OPA a few seconds after the push, with no restart. The README in the repository repeats these commands.
Step 9: Connect from another host
Everything the server offers is on HTTPS port 443, so any host your security group allows can publish data and run an OPAL client. Copy the certificate to that host, compare its fingerprint with OPAL_TLS_SHA256, and give the host a JWT (Step 6) rather than the master token. From the other host, with the server's name in place of <opal-host>:
openssl x509 -in opal.crt -noout -fingerprint -sha256
curl -s --cacert opal.crt https://<opal-host>/healthcheck; echo
curl -s -o /dev/null -w 'policy with a client JWT: %{http_code}\n' --cacert opal.crt -H @opal-client.h "https://<opal-host>/policy?path=cloudimg"
sha256 Fingerprint=AB:CD:...:EF
{"status":"ok"}
policy with a client JWT: 200
An OPAL client on another host (the upstream opal-client 0.9.9 package or the permitio/opal-client container), with its own OPA next to it, needs these settings:
OPAL_SERVER_URL=https://<opal-host>
OPAL_CLIENT_TOKEN=<a client JWT from Step 6>
OPAL_DATA_TOPICS=policy_data
OPAL_POLICY_STORE_URL=http://127.0.0.1:8181
# only while the server keeps its self-signed certificate (Step 10 replaces it):
OPAL_CLIENT_SELF_SIGNED_CERTIFICATES_ALLOWED=true
OPAL_CLIENT_SSL_CONTEXT_TRUSTED_CA_FILE=/path/to/opal.crt
It subscribes to the server's websocket at wss://<opal-host>/ws (refused without a client JWT), downloads the policy bundle, and then receives every policy commit and data update, exactly as the client on this instance does: in our test a remote client had this instance's policy within 4 seconds of starting and a data update published here about a second later. The OPAL client ignores OPAL_CLIENT_SSL_CONTEXT_TRUSTED_CA_FILE unless OPAL_CLIENT_SELF_SIGNED_CERTIFICATES_ALLOWED=true is set too; with both it trusts exactly the certificate in that file. Upstream recommends a certificate from a public certificate authority for production, which needs neither setting (Step 10).
Step 10: Use your own name and certificate
Point a DNS name at the instance (an Elastic IP keeps the address stable), then make it the server's name. opal-set-domain renders nginx for the new name, makes a new self-signed certificate for it, and updates the URLs in /root/opal-credentials.txt:
sudo opal-set-domain opal.example.com
To use a certificate from a public certificate authority instead, run opal-set-domain first, then install your certificate and key as /etc/ssl/opal/opal.crt and /etc/ssl/opal/opal.key (root only) and run sudo systemctl reload nginx. Clients then no longer need the --cacert / trusted CA file setting.
Step 11: Use your own policy repository
To track your own repository instead of the local one, set OPAL_POLICY_REPO_URL (and OPAL_POLICY_REPO_MAIN_BRANCH if it is not main) in /etc/opal/server.env. The server keeps its working clone in /var/lib/opal/regoclone and refuses to start on a clone of a different repository, so remove the clone while the server is stopped:
sudo systemctl stop opal-server
sudoedit /etc/opal/server.env # OPAL_POLICY_REPO_URL=https://github.com/<your-org>/<your-policies>.git
sudo -u opal rm -rf /var/lib/opal/regoclone/opal_repo_clone
sudo systemctl start opal-server
The server clones the new repository and every client loads its policies within seconds (the clients reconnect by themselves after the restart). Only .rego and .json files are loaded. The instance metadata block stays in force for every URL the server or the client fetches. The image does not expose OPAL's /webhook route to the network, so the server polls the repository every OPAL_POLICY_REPO_POLLING_INTERVAL seconds (10 by default here).
Step 12: Operate and harden
Metadata service. Every OPAL service, OPA, nginx and the first boot service are blocked from the instance metadata service; a data source pointed at it simply fails:
systemctl show -p IPAddressDeny opa opal-server opal-client nginx
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 ::ffff:169.254.169.254/128
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 ::ffff:169.254.169.254/128
IPAddressDeny=0.0.0.0/8 168.63.129.16/32 fc00::/7 ::ffff:0.0.0.0/104 fe80::/10 10.0.0.0/8 169.254.0.0/16 ::ffff:100.64.0.0/106 ::ffff:172.16.0.0/108 100.64.0.0/10 ::ffff:169.254.0.0/112 172.16.0.0/12 192.168.0.0/16 ::ffff:192.168.0.0/112 ::ffff:10.0.0.0/104
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 ::ffff:169.254.169.254/128
The third entry is the OPAL client: it also drops every private, link-local and unique-local range (those cover the metadata addresses, so systemd folds them in), and loopback stays open so it can reach OPA and the OPAL server. The order of the entries varies between boots.
Owner guard. opal-guard.service loads the firewall rule that keeps other local users away from OPA and the OPAL loopback ports. OPA, the OPAL server and the client require it, so if the rule is removed they stop rather than run unguarded:
systemctl is-active opal-guard; sudo opal-selftest
active
OPAL_SELFTEST_OK
Data source URLs. The OPAL client fetches every data source url through cloudimg's restricted fetcher (/usr/local/lib/cloudimg/opal/pylib/cloudimg_opal_fetch.py, set by OPAL_FETCH_PROVIDER_MODULES in /etc/opal/client.env; the client refuses to start with any other fetcher). It sends GET requests only, with no body and no redirects, to http:// or https:// URLs whose every address is a public internet address. It refuses loopback, private (RFC 1918), CGNAT, link-local, IPv6 unique-local and documentation addresses, every address in this instance's VPC, and an entry that names another fetcher. It checks the address of a DNS name when it connects and connects only to the address it checked. The client's service is also firewalled from private addresses (systemctl show -p IPAddressDeny opal-client). Each refusal is logged without the URL's query string. To load data that lives on a private address, publish it inline (Step 7) from a host that can read it. This example tries to write a policy into OPA and to read a private address, and both are refused:
HOST=$(cat /etc/cloudimg/opal-domain)
SINCE=$(date '+%Y-%m-%d %H:%M:%S')
curl -s --cacert /etc/ssl/opal/opal.crt --connect-to ::127.0.0.1:443 -H @"$HOME/opal-ds.h" -H 'Content-Type: application/json' \
-d '{"entries":[{"url":"http://127.0.0.1:8181/v1/policies/demo","config":{"method":"put","data":"package demo"},"dst_path":"/cloudimg/demo","topics":["policy_data"],"save_method":"PUT"},{"url":"http://10.0.0.1/users.json","dst_path":"/cloudimg/demo","topics":["policy_data"],"save_method":"PUT"}],"reason":"refused sources"}' \
"https://$HOST/data/config"; echo
for i in $(seq 1 40); do [ "$(sudo journalctl -u opal-client --since "$SINCE" -o cat | grep -c 'cloudimg-fetch REFUSED')" -ge 2 ] && break; sleep 1; done
sudo journalctl -u opal-client --since "$SINCE" -o cat | grep -o 'cloudimg-fetch REFUSED.*'
echo "OPA policy demo: $(sudo curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8181/v1/policies/demo)"
sudo journalctl -u opal-client --since "$SINCE" -o cat | grep -q 'REFUSED rfc1918: http://10.0.0.1/users.json' && echo "both sources refused"
{"status":"ok"}
cloudimg-fetch REFUSED method: http://127.0.0.1:8181/v1/policies/demo
cloudimg-fetch REFUSED rfc1918: http://10.0.0.1/users.json
OPA policy demo: 404
both sources refused
Logs. sudo journalctl -u opal-server -u opal-client -u opa. A refused request is logged as Authentication failed with 401 without the token it carried. nginx keeps its access log in /var/log/nginx and logs errors only at crit, because request lines can carry tokens.
Tokens. Hand out client and datasource JWTs, never the master token, and give them a lifetime that suits the holder (ttl in Step 6, or the day count of opal-token). Delete the header files when you are done: shred -u ~/opal-master.h ~/opal-ds.h ~/opal-client.h.
Security group. Allow TCP 443 only from the hosts that publish data or run OPAL clients and TCP 22 only from your administrators.
Updates. Unattended security upgrades stay on for Ubuntu, nginx and git. OPAL and OPA are pinned: the exact versions and hashes are in /usr/share/doc/cloudimg-opal (requirements.lock.txt, pip-freeze.txt, opa/BUILD-INFO). New OPAL and OPA releases arrive as new versions of this listing.
Support and licensing
OPAL is developed by Permit.io and contributors and distributed under the Apache License 2.0; Open Policy Agent is a CNCF project under the Apache License 2.0. The licences and the third-party notices are in /usr/share/doc/cloudimg-opal:
cat /usr/share/doc/cloudimg-opal/README.txt
OPAL 0.9.9 on Ubuntu 24.04 - cloudimg AWS Marketplace image
opal-server / opal-client / opal-common 0.9.9: PyPI wheels, sha256-pinned (requirements.lock.txt), Apache-2.0.
pygit2 1.14.1: built from the PyPI sdist against Ubuntu libgit2 1.7 (GPL-2.0 with linking exception; see pygit2/).
OPA 1.22.2: a cloudimg build of the upstream tag with in-range dependency security updates (see opa/BUILD-INFO).
User guide: https://www.cloudimg.co.uk/guides/opal-0-9-9-on-ubuntu-24-04-aws/
cloudimg provides the packaging, the first boot automation, the HTTPS front end, the owner guard and the hardening, with 24/7 support by email and live chat at support@cloudimg.co.uk.
All product names are trademarks of their respective owners. cloudimg is not affiliated with or endorsed by Permit.io, the OPAL project or the Open Policy Agent project.