Deployment guide

OpenAPI Generator

Overview

OpenAPI Generator turns an OpenAPI specification into working code. Give it your API description and it returns a client SDK, a server stub or a documentation set, ready to unpack and use, across more than 150 language and framework targets.

This SecureAnyCloud image is the service form of it: the openapi-generator-online module. It gives you a REST API your build pipeline can call, and an interactive browser console for when you want a client right now. Running it yourself keeps your API description, which is design documentation for your system, inside your own network. Every team then gets the same generated clients from the same generator version in every pipeline.

What you get:

  • OpenAPI Generator Online 7.25.0, the unmodified upstream package from Maven Central at /opt/openapi-generator/, pinned by version and by the SHA-256 of the jar, and run by systemd as the unprivileged oagen service account
  • nginx terminating TLS on port 443 and requiring an HTTP basic credential on every request. The upstream service has no authentication of any kind because it is published as an open demo, so this front door is the entire access control, and it has no exemptions
  • No default credential. A unique password is generated on the first boot of your VM, stored only as a salted SHA-512 hash, and written to a file that only root can read
  • The generator bound to 127.0.0.1:8080, where nothing outside the instance can reach it. Off-box TCP is exactly 22 and 443, and there is deliberately no port 80
  • Generated output written to /var/lib/openapi-generator/work, a directory the service account owns and the service's systemd sandbox confines it to
  • A bounded cleanup on an hourly timer, so a long-running instance cannot fill its disk however heavily it is used
  • Confined specification fetching: generating from a URL keeps working, but the service cannot reach the Azure instance metadata service, the Azure wire server or anything on your private network while doing it
  • OpenJDK 21 (headless JRE) from Ubuntu main, so Ubuntu security updates patch the JVM like the rest of the image. No build toolchain ships: javac, mvn, gradle and jar are all absent
  • On-VM self-tests that prove the whole security posture, each one checked against a known-bad input first
  • The openapi-generator and nginx systemd services, enabled and active, plus the openapi-generator-reap.timer housekeeping timer

OpenAPI is a trademark of the Linux Foundation. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them. This image packages the unmodified open source software, which is distributed under the Apache License 2.0.

Prerequisites

You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.

Standard_B2s (2 vCPU, 4 GiB RAM) is the recommended size. One JVM with a 65% heap ceiling and nginx fit comfortably inside it, and generating a large SDK takes a few seconds of CPU rather than much memory. Choose a larger size if many pipelines will generate concurrently.

Configure the network security group (NSG) inbound rules as follows: 22/tcp from your management network, and 443/tcp from wherever your developers and build agents connect. There is deliberately no port 80. The only credential this service has is an HTTP basic credential, and a plaintext port would leak it.

1

Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for OpenAPI Generator by SecureAnyCloud, and select Create.

On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22). Then select Review + create and Create.

Open port 443 afterwards, as shown in the next step.

2

Deploy from the Azure CLI

Accept the image terms once per subscription, then deploy the VM:

az vm image terms accept --urn sac:openapi-generator-ubuntu-24-04:default:latest
az group create --name openapi-generator-rg --location eastus
az vm create \
  --resource-group openapi-generator-rg \
  --name openapi-generator-vm \
  --image sac1647283583153:openapi-generator-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard
az vm open-port --resource-group openapi-generator-rg --name openapi-generator-vm --port 443 --priority 1001

Restrict 443 to the networks your developers and build agents come from instead of leaving it open to the internet. The credential gate protects the service, and a smaller exposed surface adds a further layer.

3

Retrieve this instance's credential

The image ships no usable credential. On the first boot of your VM, a unique password is generated for that instance alone and written to /root/openapi-generator-credentials.txt, which only root can read. Print the file:

sudo cat /root/openapi-generator-credentials.txt

The file holds these values:

Key What it is

OAG_URL the console address for this VM

OAG_HOST this VM's customer-facing address, resolved at first boot

OAG_AUTH_USER the front-door username (apiuser)

OAG_AUTH_PASSWORD the front-door password, unique to this VM

OAG_TLS_CERT the certificate this VM serves, so on-VM tooling can verify it

The password is never stored in clear text anywhere else. nginx checks it against a salted SHA-512 hash in /etc/openapi-generator/htpasswd, which is 0640 root:www-data. Verify the file and its hash format:

sudo bash -c 'ls -l /etc/openapi-generator/htpasswd; grep -oE "^[^:]+:\\\$6\\\$" /etc/openapi-generator/htpasswd'
4

Open the console

Browse to https://<OAG_HOST>/ and sign in with OAG_AUTH_USER and OAG_AUTH_PASSWORD when the browser asks. The image ships a certificate generated for your VM's own address. Because it is self-signed, your browser warns once. Step 10 shows how to install your own.

The console lists every operation the service offers, grouped into clients and servers, with the API server set to your own instance.

Expand GET /api/gen/clients and press TRY to see the full catalog of client and documentation generators this version supports.

From the VM itself, make the same call over the API:

sudo bash -c 'CF=/root/openapi-generator-credentials.txt; H=$(grep "^OAG_HOST=" $CF | cut -d= -f2-); U=$(grep "^OAG_AUTH_USER=" $CF | cut -d= -f2-); P=$(grep "^OAG_AUTH_PASSWORD=" $CF | cut -d= -f2-); curl -s --cacert /etc/ssl/openapi-generator/openapi-generator.crt --resolve "$H:443:127.0.0.1" -u "$U:$P" "https://$H/api/gen/clients" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d), \"client generators\"); print(\", \".join(d[:12]), \"...\")"'

The --resolve flag is needed because an Azure VM cannot dial its own public IP. The Standard Load Balancer does not hairpin, so a request from the VM to its own public address times out. --resolve keeps your real address in the URL and in the TLS check while it sends the connection to loopback.

From your laptop, use the URL as it is.

5

Generate a client

Post an OpenAPI specification and the target language, and the service returns a one-time download link.

In the console, expand POST /api/gen/clients/{language}. Set language to python, paste your specification into the body as {"spec": { ... }} and press TRY.

From the API, run the whole flow end to end: generate the client, download it, and list what came back:

sudo bash -c 'set -e CF=/root/openapi-generator-credentials.txt CRT=/etc/ssl/openapi-generator/openapi-generator.crt H=$(grep "^OAG_HOST=" $CF | cut -d= -f2-) U=$(grep "^OAG_AUTH_USER=" $CF | cut -d= -f2-) P=$(grep "^OAG_AUTH_PASSWORD=" $CF | cut -d= -f2-) T=$(mktemp -d) /usr/local/sbin/openapi-generator-write-spec.sh $T/spec.json python3 -c "import json;json.dump({\"spec\":json.load(open(\"$T/spec.json\")),\"options\":{\"packageName\":\"acme_client\"}},open(\"$T/body.json\",\"w\"))" CODE=$(curl -s -m 600 --cacert $CRT --resolve "$H:443:127.0.0.1" -u "$U:$P" -X POST -H "Content-Type: application/json" --data @$T/body.json "https://$H/api/gen/clients/python" | python3 -c "import json,sys;print(json.load(sys.stdin)[\"code\"])") echo "download id: $CODE" curl -s -m 600 --cacert $CRT --resolve "$H:443:127.0.0.1" -u "$U:$P" -o $T/client.zip -w "download: HTTP %{http_code} %{size_download} bytes\n" "https://$H/api/gen/download/$CODE" unzip -l $T/client.zip | tail -n +4 | head -8 rm -rf $T'

Keep three things in mind about the download link:

  • It is one-time. The archive is deleted as it is read, so a second request for the same link fails. Generate again for another copy.
  • It is built from this instance's own address, taken from GENERATOR_HOST in /etc/openapi-generator/openapi-generator.env. It is deliberately not built from the incoming request. Otherwise any caller could choose the host their download link points at by setting a forwarded-host header.
  • It expires. Upstream holds generated archives for 24 hours, and the housekeeping in Step 11 clears what is left after that.

Every generator has its own option set, such as package names, library choices and serialization. To see what a particular generator accepts, ask it. The console shows the options under GET /api/gen/clients/{language}:

sudo bash -c 'CF=/root/openapi-generator-credentials.txt; H=$(grep "^OAG_HOST=" $CF | cut -d= -f2-); U=$(grep "^OAG_AUTH_USER=" $CF | cut -d= -f2-); P=$(grep "^OAG_AUTH_PASSWORD=" $CF | cut -d= -f2-); curl -s --cacert /etc/ssl/openapi-generator/openapi-generator.crt --resolve "$H:443:127.0.0.1" -u "$U:$P" "https://$H/api/gen/clients/typescript-axios" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d), \"options:\"); print(\", \".join(sorted(d)[:10]), \"...\")"'

Pass any of these options in the options object alongside spec.

6

Generate a server stub

Make the same call against /api/gen/servers/{framework}. First ask the instance which frameworks it supports:

sudo bash -c 'CF=/root/openapi-generator-credentials.txt; H=$(grep "^OAG_HOST=" $CF | cut -d= -f2-); U=$(grep "^OAG_AUTH_USER=" $CF | cut -d= -f2-); P=$(grep "^OAG_AUTH_PASSWORD=" $CF | cut -d= -f2-); curl -s --cacert /etc/ssl/openapi-generator/openapi-generator.crt --resolve "$H:443:127.0.0.1" -u "$U:$P" "https://$H/api/gen/servers" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d), \"server generators\"); print(\", \".join(d[:12]), \"...\")"'
7

Generating from a specification URL, and what this image will fetch

Instead of spec, you can send openAPIUrl, and the service fetches the specification itself. This makes generating a client for a published API a one-liner. It also means the service makes outbound requests to addresses a caller chooses.

This image therefore confines where those requests can go. The service's systemd unit carries an IP egress filter. The filter denies the Azure instance metadata service (169.254.0.0/16), the Azure wire server (168.63.129.16) and every RFC1918 private range, and it leaves public addresses and DNS alone.

Public specification URLs keep working. A caller cannot turn the generator into a probe for your instance's identity tokens or your private network.

Confirm the filter with its own check:

sudo /usr/local/sbin/openapi-generator-egress-check.sh

The check reports what happens inside the filter and, as a control, what happens without it. The control matters because a metadata service that is unreachable for an unrelated reason would otherwise make the filter look as if it were working when it was not.

If your specifications live on a host on your private network that you want the generator to reach, add that host's address to IPAddressAllow= in a drop-in instead of removing the deny list. Check the current setting:

sudo bash -c 'systemctl cat openapi-generator.service | grep -E "^IPAddress"'
8

Verify the security posture yourself

You can check every claim this image makes about its front door by running a script on the VM. Start with the credential gate:

sudo /usr/local/sbin/openapi-generator-verify-auth.sh

The script asserts that:

  • An unauthenticated request is refused with 401 and a WWW-Authenticate challenge on the API, the console, the API document, the download endpoint and the static assets.
  • A wrong password, a blank password, a wrong username, well-known weak guesses and a malformed authorization header are all refused.
  • This VM's own credential is accepted on a browser-shaped request and returns a populated generator catalog.
  • The generator's own port does not answer on the VM's external address, and nothing answers on port 80.
  • TLS verification refuses an address the certificate does not name.
  • A hostile forwarded-host header cannot move a download link.

Every one of those checks is itself tested against a known-bad input, because a gate that cannot fail proves nothing:

sudo /usr/local/sbin/openapi-generator-gate-selftest.sh

Next, check the exact off-box listening set:

sudo /usr/local/sbin/openapi-generator-port-check.sh

Finally, test the whole appliance in one command. It includes a real generate-and-download round trip, and the archive is checked file by file:

sudo /usr/local/sbin/openapi-generator-selftest.sh
9

Rotate the front-door password

Use the supplied helper to rotate the password. It reads the new password from standard input instead of the command line, so the password never lands in the process table, the journal or your shell history. It refuses empty, short, all-digit, colon-bearing and well-known weak passwords.

It also proves the rotation took before it returns: the new password must work and the old one must not.

printf '%s' '<new-password>' | sudo /usr/local/sbin/openapi-generator-set-password.sh

The helper rewrites both the hash nginx checks and the credentials file, so the two cannot drift apart. nginx re-reads the file on every request, so you need no restart and no connections drop.

10

Use your own certificate and hostname

Put your certificate and key at /etc/ssl/openapi-generator/openapi-generator.crt and .key, with the key set to 0600 root:root. Then tell the instance the address it should advertise in download links, and restart the services:

sudo bash -c 'echo "GENERATOR_HOST=https://<your-domain>" > /etc/openapi-generator/openapi-generator.env; systemctl restart openapi-generator nginx'

GENERATOR_HOST is what every download link is built from, so change it at the same time as the certificate. If you leave it pointing at the old address, your clients can still generate, but their download links name the wrong host.

11

Generated output and disk housekeeping

Generated archives live under /var/lib/openapi-generator/work and are removed as they are downloaded. Two mechanisms keep the disk bounded:

  • Upstream expires an archive's download link after 24 hours and sweeps the expired ones hourly.
  • This image adds its own hourly reaper for what that sweep cannot see: a generation that failed leaves a working directory behind with no link to expire. The reaper removes anything older than 25 hours, which is deliberately past upstream's own window so it can never destroy a link that is still valid. It also enforces a 2 GB ceiling and prunes oldest-first when the ceiling is exceeded.

Check the timer and the work directory size:

sudo bash -c 'systemctl list-timers openapi-generator-reap.timer --no-pager | head -3; du -sh /var/lib/openapi-generator/work'

Run the reaper by hand at any time, and check that the instance is holding nothing:

sudo /usr/local/sbin/openapi-generator-reap.sh
sudo /usr/local/sbin/openapi-generator-workdir-empty.sh

To change the ceiling, add --max-mb <N> to ExecStart= in /etc/systemd/system/openapi-generator-reap.service.

12

Upgrading

The generator is a single pinned jar, so an upgrade is a download, a checksum and a restart. First check what you are running:

sudo bash -c 'ls -1 /opt/openapi-generator/*.jar; unzip -p /opt/openapi-generator/openapi-generator-online-7.25.0.jar META-INF/MANIFEST.MF | grep -i "^implementation-version"'

New releases are published to Maven Central under org.openapitools:openapi-generator-online. The file https://repo1.maven.org/maven2/org/openapitools/openapi-generator-online/maven-metadata.xml is upstream's own statement of the current line. The project's GitHub releases attach only the command-line jar, so the online service's jar is on Maven Central and nowhere else.

To upgrade, follow these steps:

  • Download the new jar and verify its checksum against the .sha1 published beside it.
  • Place the jar in /opt/openapi-generator/.
  • Point ExecStart= at it in /etc/systemd/system/openapi-generator.service.
  • Run systemctl daemon-reload && systemctl restart openapi-generator.
  • Re-run openapi-generator-selftest.sh.

Upgrade note for this product. The published openapi-generator-online jar bundles commons-lang3 3.12.0, because its build inherits Spring Boot 2.5's dependency management. The generator core calls a class that exists only from 3.17.0. Without a correction, every Python-family generator fails with HTTP 500.

This image launches the unmodified jar through Spring Boot's own PropertiesLauncher with -Dloader.path=/opt/openapi-generator/ext. That puts the correct commons-lang3 ahead of the bundled one. Keep that flag when you upgrade, and check whether the new release still needs it:

sudo bash -c 'unzip -l /opt/openapi-generator/openapi-generator-online-7.25.0.jar | grep -o "commons-lang3-[0-9.]*jar"; ls -1 /opt/openapi-generator/ext/'

Troubleshooting

The console loads but shows nothing. The console is driven entirely by the API document at /api-docs, which becomes available a few seconds after the generation API does. The service's readiness gate waits for both before nginx is allowed to start, so this should not happen. If it does, check that the service came up cleanly:

sudo bash -c 'systemctl is-active openapi-generator nginx; journalctl -u openapi-generator -n 20 --no-pager | tail -5'

A generation returns HTTP 400. The service did not accept the specification. The most common cause is an anonymous inline schema where a generator expects a named one. Move the schema into components/schemas and reference it. A missing spec and a missing openAPIUrl also give 400, as does an unknown generator name.

A download returns HTTP 500 on the second try. This is expected, because archives are one-time. A link that was never issued returns 404.

Everything returns 401. Compare the credential you are sending with /root/openapi-generator-credentials.txt. There are no exempt paths: the console, the API document and the static assets are all behind the same credential.

The service will not start. It refuses to start if /etc/openapi-generator/openapi-generator.env is missing. This is deliberate, so the service can never run with download links built from whatever a caller asks for. Restore the file with a GENERATOR_HOST=https://<address> line, then check the services:

sudo bash -c 'systemctl is-enabled openapi-generator openapi-generator-firstboot nginx openapi-generator-reap.timer | tr "\n" " "; echo'

Support

This image is a repackaged open source product with 24/7 SecureAnyCloud support. Support covers deployment, upgrades, TLS certificates and custom domains, pipeline and CI integration, generator configuration and templates, and scaling. Contact support through the Azure Marketplace listing or at secureanycloud.com.

Deploy on Azure

Launch OpenAPI Generator on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure