Deployment guide

grpcUI 1.5.4

Overview

grpcUI is an open source web UI for gRPC services, a browser-based Postman for gRPC. It reads the services and message types of a gRPC server through server reflection, or from your .proto / protoset files, and draws a form for every method. When you press Invoke, it makes the real gRPC call from the server and shows the response, headers, trailers and errors.

Unary, server streaming, client streaming and bidirectional methods all work, and every call has a ready grpcurl command line.

This guide covers launching and using the grpcUI 1.5.4 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud developer and API tools listing on AWS Marketplace. Pick it as the software version when you subscribe and launch.

grpcUI has no login of its own, and its documentation says to put it behind authentication. Anyone who can reach it can call your services. The SecureAnyCloud image turns it into a private gRPC console for your team.

What you get:

  • HTTPS from the first boot. nginx serves grpcUI on port 443 with a certificate generated on the first boot of your instance. Port 80 redirects to HTTPS.
  • A password unique to your instance, on every path. HTTP Basic Auth is required for the user admin on every page, asset and endpoint (the web form, method schemas, examples and the invoke endpoint). The password is generated on first boot and written to /var/lib/sac/data/grpcui-credentials.txt (root only). nginx stores only a bcrypt hash of it, and no password is baked into the image.
  • Ready to try. A small demo gRPC server (grpcbin, with server reflection on) runs on the instance, so grpcUI lists callable services as soon as you sign in. Pointing grpcUI at your own service takes one systemd drop-in (Step 7).
  • The metadata service is out of reach. grpcUI makes gRPC calls from the server. Its target is fixed in its service configuration, so no request can change the address it dials. grpcUI, the demo server and nginx are all blocked from the EC2 instance metadata service at the kernel level, even if grpcUI is pointed at the metadata address.
  • Built from source with patched modules. The upstream release binaries were built with older Go and gRPC versions that carry known advisories. This image builds grpcUI 1.5.4 and the demo server from their release tags with Go 1.27.3 and fixed golang.org/x/net, golang.org/x/crypto and google.golang.org/grpc modules. govulncheck reports no vulnerability that the code reaches. No Go toolchain or compiler ships.
  • Hardened defaults. grpcUI and the demo server listen on 127.0.0.1 only. They run as their own unprivileged users, with no capabilities and a read-only view of the system. nginx refuses to start until first boot has generated the certificate and the password. It drops requests for host names the instance does not serve and refuses cross-site requests that call methods. The AMI requires IMDSv2.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: grpcUI 1.5.4 (/usr/local/bin/grpcui), the grpcbin 1.0.8 demo gRPC server (/usr/local/bin/grpcbin), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu | Architecture: x86_64 | Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH), TCP 443 (grpcUI) and TCP 80 (redirects to HTTPS) from your own networks only.
  • To call your own gRPC services, a subnet and security groups that let the instance reach them.
1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick grpcUI 1.5.4 on Ubuntu 24.04 as the software version, select your Region, and choose Launch through EC2.

Choose m5.large, your key pair, a security group as described above, and a subnet that can reach the gRPC services you want to explore. grpcUI itself needs little, so a smaller instance works for a few users.

To launch with the AWS CLI instead, replace the values in angle brackets:

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=grpcui}]'
2

Connect to your instance

The SSH user for grpcUI 1.5.4 on Ubuntu 24.04 is ubuntu:

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

First boot generates the certificate and the password, then nginx starts in front of grpcUI and the demo server. Check the units and the first boot marker:

systemctl is-active grpc-demo-server grpcui nginx grpcui-firstboot
sudo test -f /var/lib/sac/data/grpcui-firstboot.done && echo FIRSTBOOT_DONE

All four units are active and the first boot marker is present:

active active active active FIRSTBOOT_DONE

Only SSH and nginx listen on the network. grpcUI (port 8080) and the demo server (9000, plus its TLS listener on 9001) listen on 127.0.0.1 only. List the listeners:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd")) 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) 127.0.0.1:8080 users:(("grpcui")) 127.0.0.1:9000 users:(("grpcbin")) 127.0.0.1:9001 users:(("grpcbin")) [::]:22 users:(("sshd"),("systemd")) [::]:443 users:(("nginx"),("nginx"),("nginx")) [::]:80 users:(("nginx"),("nginx"),("nginx"))
4

Get your URL and password

Read the credentials file:

sudo cat /var/lib/sac/data/grpcui-credentials.txt

Only root can read the file. It holds the URL, the user name and the password for this instance:

GRPCUI_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ GRPCUI_USERNAME=admin GRPCUI_PASSWORD=<GRPCUI_PASSWORD>

The URL uses the public DNS name of the instance, taken from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To serve grpcUI under your own DNS name, see Step 9.

5

Sign in and call a method

Open the GRPCUI_URL in your browser. The certificate is self-signed, so your browser asks you to confirm it the first time. You can also install your own certificate (Step 9).

Sign in as admin with the password from Step 4. The browser remembers the login for the session.

grpcUI opens connected to the bundled demo server (127.0.0.1:9000). Pick a service and a method, fill in the request fields and press Invoke. To set an optional field, tick the box next to it. The example here uses hello.HelloService / SayHello with the greeting sac.

The Response tab shows the response headers, the response message and the trailers.

The Raw Request tab takes the request as JSON instead, and shows the equivalent grpcurl command. For example, addsvc.Add / Sum with a = 2 and b = 40 returns 42. In JSON, 64-bit integers are strings.

The demo server also has streaming methods (hello.HelloService / LotsOfReplies, LotsOfGreetings, BidiHello) and the grpcbin.GRPCBin methods for headers, errors and empty messages. It also has grpc.gateway.examples.examplepb.ABitOfEverythingService (from the gRPC gateway examples), whose messages use most protobuf field types.

The History tab keeps your recent requests in your browser.

6

Call grpcUI from the command line

The HTTP endpoints of grpcUI are behind the same login. The invoke endpoint takes a JSON body and the CSRF token that grpcUI sets as a cookie. The script reads the password from the credentials file and passes it to curl on standard input, so it never appears in the process list:

sudo bash -c ' F=/var/lib/sac/data/grpcui-credentials.txt P=$(grep "^GRPCUI_PASSWORD=" $F | cut -d= -f2-) H=$(grep "^GRPCUI_URL=" $F | sed -E "s#^GRPCUI_URL=https://([^/]+)/.*#\1#") auth() { printf "user = \"admin:%s\"\n" "$P"; } R="--resolve $H:443:127.0.0.1"; J=$(mktemp) auth | curl -K - -sk $R -c "$J" -o /dev/null "https://$H/" T=$(awk "/_grpcui_csrf_token/{print \$NF}" "$J") auth | curl -K - -sk $R -b "$J" -H "Content-Type: application/json" -H "x-grpcui-csrf-token: $T" \
  -H "Origin: https://$H" -d "{\"data\":[{\"greeting\":\"sac\"}]}" \
  "https://$H/invoke/hello.HelloService.SayHello" | grep -E "\"(reply|error)\"" auth | curl -K - -sk $R "https://$H/metadata?method=hello.HelloService.SayHello" | grep -E "requestType" rm -f "$J" '
"error": null, "reply": "hello sac" "requestType": "hello.HelloRequest",
7

Point grpcUI at your own gRPC service

grpcUI talks to one gRPC server, which is set on its command line in grpcui.service. To explore your own service, override the command with a systemd drop-in.

Keep -bind 127.0.0.1 -port 8080, because nginx is the only part that faces the network. Replace the target with the address of your service, a host name or IP and a port (here grpc.<your-domain>:50051). The empty ExecStart= line clears the original command first.

Then restart grpcUI and check that it connected, because it reads the service list at start-up. Stop the demo server, which you no longer need:

sudo mkdir -p /etc/systemd/system/grpcui.service.d
sudo tee /etc/systemd/system/grpcui.service.d/override.conf >/dev/null <<'EOF2' [Service] ExecStart= ExecStart=/usr/local/bin/grpcui -bind 127.0.0.1 -port 8080 -connect-fail-fast=false -connect-timeout 60 -plaintext grpc.<your-domain>:50051 EOF2
sudo systemctl daemon-reload
sudo systemctl restart grpcui systemctl is-active grpcui journalctl -u grpcui -n 5 --no-pager sudo systemctl disable --now grpc-demo-server

If you prefer an editor, sudo systemctl edit grpcui opens the same drop-in.

Reload the page in your browser. The header shows Connected to your service.

These options are useful on the ExecStart line (see grpcui -help):

  • Plain text (no TLS): -plaintext
  • TLS with a public certificate: leave out -plaintext
  • TLS with a private CA: -cacert /etc/grpcui/ca.pem (and -servername <name> if the certificate names another host)
  • Mutual TLS: -cacert /etc/grpcui/ca.pem -cert /etc/grpcui/client.pem -key /etc/grpcui/client.key
  • Self-signed, for testing only: -insecure (skips certificate checks)
  • No server reflection: -import-path /etc/grpcui/protos -proto service.proto or -protoset /etc/grpcui/service.protoset (reflection is then off unless you also pass -use-reflection)
  • Only some services: -service <package.Service> (repeat it)

Put certificate, key and .proto files in a directory that grpcUI can read but other users cannot. For example, run sudo install -d -o root -g grpcui -m 0750 /etc/grpcui and give the files owner root:grpcui and mode 0640. The grpcUI service runs as the grpcui user with a read-only view of the system, so it can read the files but never change them.

Enter request metadata such as authorization tokens per call, in the Request Metadata table of the web form. A token given with -H or -rpc-header on the command line is stored in the unit file and is visible in the process list to local users.

To go back to the demo server, remove the drop-in with sudo systemctl revert grpcui. Then run sudo systemctl enable --now grpc-demo-server and sudo systemctl restart grpcui.

8

Check the security posture from the command line

Without a login, grpcUI refuses requests, including those to its schema and invoke endpoints. Port 80 redirects to HTTPS. The health check stays open for load balancers. A request for a host name the instance does not serve is dropped without an answer. Run these checks:

H=$(sudo grep '^GRPCUI_URL=' /var/lib/sac/data/grpcui-credentials.txt | sed -E 's#^GRPCUI_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'http:// -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'metadata, no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/metadata?method=*"
curl -sk -o /dev/null -w 'invoke, no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST -H 'Content-Type: application/json' -d '{}' "https://$H/invoke/hello.HelloService.SayHello"
curl -sk -o /dev/null -w 'health -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/health"
curl -sk -o /dev/null -w 'unknown host -> %{http_code}\n' --resolve "evil.example:443:127.0.0.1" "https://evil.example/" || true
http:// -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ no login -> HTTP 401 metadata, no login -> HTTP 401 invoke, no login -> HTTP 401 health -> HTTP 200 unknown host -> 000

000 means nginx closed the connection without answering, which is how it treats any host name it does not serve.

With the password, grpcUI is served and calls work from its own pages. A call whose browser Origin is another web site is refused (HTTP 403), so another page cannot use your signed-in browser to call your services. The invoke endpoint never makes a call on a plain GET (HTTP 405):

sudo bash -c ' F=/var/lib/sac/data/grpcui-credentials.txt P=$(grep "^GRPCUI_PASSWORD=" $F | cut -d= -f2-) H=$(grep "^GRPCUI_URL=" $F | sed -E "s#^GRPCUI_URL=https://([^/]+)/.*#\1#") auth() { printf "user = \"admin:%s\"\n" "$P"; } R="--resolve $H:443:127.0.0.1"; J=$(mktemp) auth | curl -K - -sk $R -c "$J" -o /dev/null -w "with login -> HTTP %{http_code}\n" "https://$H/" T=$(awk "/_grpcui_csrf_token/{print \$NF}" "$J") inv() { auth | curl -K - -sk $R -b "$J" -o /dev/null -w "$1 -> HTTP %{http_code}\n" -H "Content-Type: application/json" \
  -H "x-grpcui-csrf-token: $T" -H "Origin: $2" -d "{\"data\":[{\"greeting\":\"x\"}]}" "https://$H/invoke/hello.HelloService.SayHello"; } inv "invoke, same origin" "https://$H" inv "invoke, other origin" "https://evil.example" auth | curl -K - -sk $R -b "$J" -o /dev/null -w "invoke as GET -> HTTP %{http_code}\n" -H "x-grpcui-csrf-token: $T" "https://$H/invoke/hello.HelloService.SayHello" rm -f "$J" '
with login -> HTTP 200 invoke, same origin -> HTTP 200 invoke, other origin -> HTTP 403 invoke as GET -> HTTP 405

nginx keeps only a bcrypt hash of the password. The plain text is in the root-only credentials file:

sudo sed -E 's/\$2y\$12\$.*/$2y$12$<bcrypt-hash>/' /etc/nginx/.grpcui-htpasswd
sudo stat -c '%a %U:%G %n' /etc/nginx/.grpcui-htpasswd /var/lib/sac/data/grpcui-credentials.txt
admin:$2y$12$<bcrypt-hash> 640 root:www-data /etc/nginx/.grpcui-htpasswd 600 root:root /var/lib/sac/data/grpcui-credentials.txt

The certificate was generated on this instance and names its own addresses. Inspect it:

H=$(sudo grep '^GRPCUI_URL=' /var/lib/sac/data/grpcui-credentials.txt | sed -E 's#^GRPCUI_URL=https://([^/]+)/.*#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = sac X509v3 Subject Alternative Name: DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost

grpcUI, the demo server and nginx cannot reach the instance metadata service. grpcUI and the demo server run as their own users with no capabilities and a read-only system. The target of grpcUI is set in its unit. Confirm all three:

systemctl show grpcui grpc-demo-server nginx -p IPAddressDeny
systemctl show grpcui grpc-demo-server -p User -p ProtectSystem -p CapabilityBoundingSet -p NoNewPrivileges
systemctl cat grpcui | grep -E '^ExecStart='
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 CapabilityBoundingSet= User=grpcui ProtectSystem=strict NoNewPrivileges=yes CapabilityBoundingSet= User=grpcbin ProtectSystem=strict NoNewPrivileges=yes ExecStart=/usr/local/bin/grpcui -bind 127.0.0.1 -port 8080 -connect-fail-fast=false -connect-timeout 60 -plaintext 127.0.0.1:9000

The demo server always opens a second, TLS listener, which needs a key. The key pair is generated every time the demo server starts. It lives in memory-backed storage (/run/grpcbin, tmpfs) that only the grpcbin user can read. It never reaches the disk, and no two instances share it:

sudo ls -l /run/grpcbin/ findmnt -no FSTYPE -T /run/grpcbin
total 8 -rw------- 1 grpcbin grpcbin 615 <date> loopback.crt -rw------- 1 grpcbin grpcbin 241 <date> loopback.key tmpfs
9

Your own DNS name and certificate

Point a DNS record at the instance, then set the name that grpcUI is served as. This command mints a new self-signed certificate for the name. It updates nginx, the cross-site protection and the URL in the credentials file, and it keeps the name across reboots:

sudo grpcui-set-hostname <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx. Running grpcui-set-hostname again replaces it with a self-signed one:

sudo install -m 0644 <your-domain>.crt /etc/ssl/grpcui/grpcui.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/grpcui/grpcui.key
sudo nginx -t && sudo systemctl reload nginx
10

Change the password or add users

The login is an nginx Basic Auth file. To set a new password for admin, hand it to htpasswd on standard input, then reload nginx. printf is a shell builtin, so the password never appears in the process list. Start the line with a space so most shells keep it out of your history.

printf '%s\n' '<new-password>' | sudo htpasswd -i -B -C 12 /etc/nginx/.grpcui-htpasswd admin
sudo systemctl reload nginx

Running sudo htpasswd -B -C 12 /etc/nginx/.grpcui-htpasswd <user> without -i prompts for the password instead. Use it with another user name to give a colleague their own login. Use sudo htpasswd -D /etc/nginx/.grpcui-htpasswd <user> to remove a login.

Then update or delete GRPCUI_PASSWORD in /var/lib/sac/data/grpcui-credentials.txt, so the file does not hold an old password.

nginx checks the login and does not pass it on to grpcUI or to your gRPC service.

11

Server components

Key components and paths on the instance:

  • grpcUI binary: /usr/local/bin/grpcui
  • Demo gRPC server (grpcbin): /usr/local/bin/grpcbin
  • Build record, licenses, module list, govulncheck reports: /usr/share/doc/sac-grpcui/
  • grpcUI service: grpcui.service (user grpcui, 127.0.0.1:8080)
  • Demo server service: grpc-demo-server.service (user grpcbin, 127.0.0.1:9000 and 9001)
  • nginx site: /etc/nginx/sites-available/sac-grpcui
  • Basic Auth file: /etc/nginx/.grpcui-htpasswd (bcrypt)
  • Certificate and key: /etc/ssl/grpcui/
  • First boot script: /usr/local/sbin/grpcui-firstboot.sh
  • Host name helper: /usr/local/sbin/grpcui-set-hostname
  • Credentials: /var/lib/sac/data/grpcui-credentials.txt (mode 0600)

Verify the installed versions and the audit results:

grpcui -version ls /usr/local/bin command -v go || echo "no Go toolchain" nginx -v 2>&1 grep -h '^Your code' /usr/share/doc/sac-grpcui/govulncheck-*-binary.txt
grpcui v1.5.4 grpcbin grpcui no Go toolchain nginx version: nginx/1.24.0 (Ubuntu) Your code is affected by 0 vulnerabilities. Your code is affected by 0 vulnerabilities.

/usr/share/doc/sac-grpcui/BUILD-INFO.txt records the exact source archives and commits, the Go toolchain, the module versions raised to fixed releases and the binary checksums. The govulncheck-*.txt files hold the full binary and source mode reports.

12

Security notes and recommendations

  • Allow ports 22, 80 and 443 in the security group from your own networks only.
  • Keep the per-instance password secret, and rotate it if it may have been shared (Step 10).
  • Use your own DNS name and a certificate your clients trust (Step 9).
  • grpcUI calls whatever service you point it at, with the requests your users send. Give the instance a security group and subnet that reach only the gRPC services it should explore, and use a test or staging service where you can.
  • Known items with no fixed upstream release, reviewed for this image: govulncheck lists GO-2026-5932 (the deprecated golang.org/x/crypto/openpgp package, which has no fixed version) as present in a required module, but neither program imports or contains that package. The grpcUI web page bundles jQuery 3.4.1 and jQuery UI 1.12.1, which have published cross-site scripting advisories (CVE-2020-11022, CVE-2020-11023, CVE-2021-41182, CVE-2021-41183, CVE-2021-41184) for pages that pass untrusted HTML or options to them. grpcUI inserts service names, field names and response values as text, its date picker options are fixed, and the page is only served behind your login. SecureAnyCloud rechecks both items at each grpcUI release.
  • Keep the operating system patched. Unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.

Support and licensing

grpcUI is open source under the MIT license (Fullstory). The bundled demo server grpcbin is open source under the MIT license (Manfred Touron). Both license texts are in /usr/share/doc/sac-grpcui/. There is no per-user or per-CPU license fee. The AMI price covers the SecureAnyCloud build, hardening, first boot setup and support.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24 hour response SLA

Deploy on AWS

Launch grpcUI 1.5.4 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS