Security Azure

GUAC 1.1 on Ubuntu 24.04 on Azure User Guide

| Product: GUAC 1.1 on Ubuntu 24.04 LTS on Azure

Overview

GUAC (Graph for Understanding Artifact Composition) is an OpenSSF project that turns the scattered metadata about your software supply chain into a single, connected, queryable graph. It ingests the documents your teams already produce — SBOMs in CycloneDX or SPDX form, SLSA build attestations, and vulnerability reports — and normalises them into a common model of packages, sources, builders and the dependencies between them. Instead of grepping across a pile of SBOM files, you ask questions of the graph over GraphQL: which artifacts depend on a given package, what versions are running where, which builds are affected by a newly disclosed vulnerability.

The cloudimg image installs the pinned upstream v1.1.0 release binaries (each verified by SHA-256), runs the GUAC GraphQL server and the collectsub service as hardened systemd units, and backs them with a local PostgreSQL 16 using GUAC's ent (SQL) backend — the light, self-contained backend that fits a 2 vCPU / 4 GiB VM, with no Neo4j to operate. The GraphQL API and its interactive GraphiQL Playground are reached through nginx enforcing HTTP Basic authentication on a single routable port; a deny-by-default host firewall keeps the GraphQL server, the collectsub service and PostgreSQL unreachable off-box. At first boot, guac-firstboot.service generates this VM's own PostgreSQL, database and web credentials, brings the services up, and ingests a bundled sample SBOM so the graph is already populated and queryable. There is no shared or default login in the image.

What is included:

  • GUAC v1.1.0 Go binaries — guacgql (GraphQL server), guaccsub (collectsub), guacone (ingest CLI), all pinned by SHA-256 (Apache-2.0)
  • PostgreSQL 16 from the official PGDG repository, bound to loopback, using GUAC's ent backend (auto-migrated on first start)
  • guacgql.service + guac-collectsub.service running as the guac user, gated on the first-boot marker
  • guac-firstboot.service generating the per-VM database + web credentials and ingesting the sample SBOM
  • GraphQL API + GraphiQL Playground on loopback (127.0.0.1:8080) fronted by nginx Basic auth on port 80
  • A ufw host firewall: deny-by-default, allowing only SSH (22) and the web port (80)
  • Ubuntu 24.04 LTS base, latest security patches applied at build time
  • 24/7 cloudimg support, 24h response SLA

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet + subnet. Recommended VM size: Standard_B2s (2 vCPU / 4 GiB) for evaluation and single-team use; scale up for larger graphs. You will reach the GraphQL API and Playground over HTTP on port 80, protected by per-VM Basic authentication.

Step 1: Deploy from the Azure Portal

Search the Azure Marketplace for GUAC 1.1 on Ubuntu 24.04. Create the VM with an NSG that allows:

  • TCP 22 (SSH) from your management network
  • TCP 80 (the GraphQL API + Playground) from your admin network

The GraphQL server (8080), the collectsub service (2782) and PostgreSQL (5432) are never exposed — the on-host ufw firewall blocks them and nginx is the sole entry point.

Step 2: Deploy from the Azure CLI

RG="guac-prod"; LOCATION="eastus"; VM_NAME="guac-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/guac-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name guac-vnet --address-prefix 10.106.0.0/16 --subnet-name guac-subnet --subnet-prefix 10.106.1.0/24
az network nsg create -g "$RG" --name guac-nsg
az network nsg rule create -g "$RG" --nsg-name guac-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name guac-nsg --name allow-web --priority 110 \
  --source-address-prefixes "<your-admin-cidr>" --destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name guac-vnet --subnet guac-subnet --nsg guac-nsg --public-ip-sku Standard

Step 3: Connect via SSH

ssh azureuser@<vm-ip>

Step 4: Verify the Services

Confirm first boot completed and the GraphQL server, collectsub service, PostgreSQL and nginx are all running:

systemctl is-active postgresql guacgql guac-collectsub nginx
test -f /var/lib/cloudimg/guac-firstboot.done && echo "first boot: complete"

postgresql, guacgql, guac-collectsub and nginx services all active, and the GUAC first-boot marker present

Step 5: Review the Security Posture

GUAC's GraphQL server and collectsub service bind all interfaces, and the GraphQL /query endpoint is unauthenticated and mutating, so the appliance is locked down two ways: a deny-by-default ufw firewall that opens only SSH and the web port, and nginx Basic auth in front of the loopback GraphQL server. Confirm both:

sudo ufw status | grep ALLOW
ss -tln | grep -E ':22 |:80 |:8080|:2782|:5432'

Only ports 22 and 80 are allowed inbound; PostgreSQL is bound to 127.0.0.1 only.

ufw allows only ports 22 and 80 inbound; guacgql on 8080 and guaccsub on 2782 are firewalled off-box and PostgreSQL binds 127.0.0.1

Step 6: Retrieve the Per-VM Credentials

Each VM mints its own PostgreSQL, database and web (Basic auth) credentials at first boot. Read them from the root-only file:

sudo stat -c '%a %U:%G' /root/guac-credentials.txt
sudo grep -E '^(web|postgres|guac_db)\.' /root/guac-credentials.txt
# --- The GUAC GraphQL Playground + API (via nginx Basic auth on port 80) ---
web.url=<GUAC_URL>
web.user=guac
web.password=<BASIC_PASSWORD>
# --- PostgreSQL superuser + guac DB role (LOOPBACK ONLY) ---
postgres.password=<POSTGRES_PASSWORD>
guac_db.password=<GUAC_DB_PASSWORD>
guac_db.database=guac

The per-VM credentials file at /root/guac-credentials.txt (0600 root:root) with the web, PostgreSQL and database secrets masked, showing the web URL, user and database

Step 7: Verify Authentication and the Graph End to End

The routable API enforces Basic auth: an anonymous request is refused, and the per-VM credential succeeds. The bundled guac-selfcheck proves the whole appliance — firewall, loopback binding, auth, and that a real query returns the ingested graph:

WEBPW=$(sudo grep '^web.password=' /root/guac-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'anonymous /query   -> HTTP %{http_code}\n' http://127.0.0.1/query -X POST -H 'content-type: application/json' --data '{"query":"{__typename}"}'
curl -s -o /dev/null -w 'authenticated /    -> HTTP %{http_code}\n' -u "guac:$WEBPW" http://127.0.0.1/
sudo guac-selfcheck | tail -1

Expected: anonymous /query -> 401, authenticated / -> 200, and a self-check line confirming the graph returned the ingested package nodes.

guac-selfcheck confirms loopback PostgreSQL, a {22,80} firewall, anonymous 401, and a live packages() query returning the sample SBOM package nodes

Step 8: Open the GraphQL Playground

Open http://<vm-ip>/ in your browser. Your browser prompts for the Basic credential — sign in as guac with the password from Step 6. You land in the interactive GraphiQL Playground, where you can write and run GraphQL queries against your supply-chain graph:

The GUAC GraphQL Playground (GraphiQL) loaded in the browser, ready to query the supply-chain graph

Step 9: Query the Supply-Chain Graph

The image ships with a sample SBOM already ingested, so the graph is populated the moment you open it. Run this query to list every package in the graph with its namespace and name:

{ packages(pkgSpec: {}) { type namespaces { namespace names { name } } } }

The result pane returns the real package nodes parsed from the sample SBOM — a generic application, and deb packages openssl and curl:

A live packages() query in the Playground returning the ingested SBOM graph: a generic package cloudimg-guac-demo and deb packages openssl and curl

Expand the query to include versions and qualifiers to see the full detail GUAC extracted from each package URL:

{ packages(pkgSpec: {}) { type namespaces { namespace names { name versions { version qualifiers { key value } } } } } }

A detailed packages() query showing each package name with its version and qualifiers from the SBOM

Step 10: Filter the Graph by Ecosystem

Every query accepts a pkgSpec filter, so you can scope questions to a package type, namespace, name or version. Filter to just the Debian packages:

{ packages(pkgSpec: {type: "deb"}) { type namespaces { namespace names { name } } } }

A filtered packages() query returning only the deb-ecosystem packages openssl and curl

Step 11: Ingest Your Own Documents

Add your own SBOM, SLSA or vulnerability documents to the graph with the guacone CLI, which parses and ingests directly through the GraphQL API — no message broker required. It auto-detects CycloneDX, SPDX and SLSA formats. Re-ingest the bundled sample (or point it at your own file or directory):

sudo -u guac env HOME=/var/lib/guac guacone collect files \
  --gql-addr http://localhost:8080/query /usr/share/guac/samples/

For continuous, watched collection across many sources, GUAC also ships the guaccollect + guacingest pipeline (with a NATS broker); those are out of scope for this single-node appliance but can be layered on a larger VM.

Step 12: Managing the Services

sudo systemctl status guacgql --no-pager
sudo systemctl restart guacgql
sudo journalctl -u guacgql -n 50 --no-pager

The GraphQL server reads /etc/guac/guac.yaml (which holds the per-VM database DSN); the graph lives in the local guac PostgreSQL database. Back up the database to preserve your ingested graph.

Step 13: Security Recommendations

  • Restrict the NSG. Expose port 80 only to your admin network and keep SSH (22) limited to management ranges. The on-host ufw firewall already denies everything else by default.
  • Front with TLS. For production, terminate HTTPS (for example with your own certificate in nginx, or an Azure Application Gateway / Front Door) so the Basic credential and query traffic are encrypted in transit.
  • Rotate the web credential and add further htpasswd users as needed (sudo htpasswd /etc/nginx/guac.htpasswd <name>), rather than sharing the guac login.
  • PostgreSQL and the GraphQL server are never exposed directly — they bind loopback / are firewalled, and the only off-box path to the graph is through the nginx Basic-auth proxy.

Step 14: Support and Licensing

GUAC is licensed under the Apache License 2.0. The complete source is available from the upstream project at github.com/guacsec/guac. The Apache-2.0 licence text ships in the image at /usr/share/doc/guac/LICENSE. PostgreSQL (PostgreSQL License) and nginx (2-clause BSD) are likewise permissive and ship unmodified.

Need Help?

cloudimg images come with 24/7 support and a 24-hour response SLA. Contact support through the Azure Marketplace listing or at cloudimg.co.uk.