Skip to content

Instantly share code, notes, and snippets.

@glennpratt
Created January 10, 2026 05:16
Show Gist options
  • Select an option

  • Save glennpratt/659767382f7d303c426474d6fe4d4e07 to your computer and use it in GitHub Desktop.

Select an option

Save glennpratt/659767382f7d303c426474d6fe4d4e07 to your computer and use it in GitHub Desktop.
#!/bin/bash
set -e
# Setup directories
TEST_DIR=$(pwd)/test-proxy-tls
mkdir -p "$TEST_DIR/certs"
cd "$TEST_DIR"
echo "### 1. Generating Certificates"
# CA
openssl genrsa -out certs/ca.key 2048
openssl req -new -x509 -days 365 -key certs/ca.key -out certs/ca.crt -subj "/CN=Test CA"
# Server (Registry A)
openssl genrsa -out certs/server.key 2048
openssl req -new -key certs/server.key -out certs/server.csr -subj "/CN=localhost"
echo "subjectAltName=DNS:localhost,IP:127.0.0.1" > certs/server.ext
openssl x509 -req -days 365 -in certs/server.csr -CA certs/ca.crt -CAkey certs/ca.key -CAcreateserial -out certs/server.crt -extfile certs/server.ext
# Client (Registry B)
openssl genrsa -out certs/client.key 2048
openssl req -new -key certs/client.key -out certs/client.csr -subj "/CN=client"
openssl x509 -req -days 365 -in certs/client.csr -CA certs/ca.crt -CAkey certs/ca.key -CAcreateserial -out certs/client.crt
echo "### 2. Creating Configuration Files"
cat <<EOF > config-a.yaml
version: 0.1
log:
level: debug
storage:
filesystem:
rootdirectory: $TEST_DIR/registry-a
http:
addr: :5001
tls:
certificate: certs/server.crt
key: certs/server.key
clientcas:
- certs/ca.crt
EOF
cat <<EOF > config-b.yaml
version: 0.1
log:
level: debug
storage:
filesystem:
rootdirectory: $TEST_DIR/registry-b
proxy:
remoteurl: https://localhost:5001
tls:
certificate: certs/client.crt
key: certs/client.key
rootcas:
- certs/ca.crt
http:
addr: :5002
EOF
echo "### 3. Building Registry"
cd ../
go build -o "$TEST_DIR/registry" ./cmd/registry
cd "$TEST_DIR"
echo "### 4. Starting Registry A (Upstream)"
OTEL_TRACES_EXPORTER=none ./registry serve config-a.yaml > registry-a.log 2>&1 &
PID_A=$!
echo "### Waiting for Registry A to be ready"
until curl --cert certs/client.crt --key certs/client.key --cacert certs/ca.crt -s https://localhost:5001/v2/ > /dev/null; do
echo "Registry A not ready, waiting..."
sleep 1
done
echo "### 5. Starting Registry B (Proxy)"
OTEL_TRACES_EXPORTER=none ./registry serve config-b.yaml > registry-b.log 2>&1 &
PID_B=$!
cleanup() {
echo "### Cleaning up"
kill $PID_A $PID_B 2>/dev/null || true
# rm -rf "$TEST_DIR"
}
trap cleanup EXIT
echo "### 5. Waiting for Registries to be ready"
sleep 5
echo "### 6. Verifying Proxy"
# Check if Registry B can talk to Registry A
# We use curl to Registry B which should trigger a proxy request to A
echo "Executing: curl -v http://localhost:5002/v2/"
RESPONSE=$(curl -s -w "\n\nHTTP Status: %{http_code}" http://localhost:5002/v2/)
echo "$RESPONSE"
HTTP_CODE=$(echo "$RESPONSE" | grep "HTTP Status:" | awk '{print $3}')
if [ "$HTTP_CODE" == "200" ]; then
echo ""
echo "SUCCESS: Proxy successfully authenticated and communicated with upstream."
else
echo ""
echo "FAILURE: Proxy returned HTTP $HTTP_CODE"
echo "--- Registry A Log ---"
cat registry-a.log
echo "--- Registry B Log ---"
cat registry-b.log
exit 1
fi
echo "### 7. Checking Logs for Errors"
# Check for errors in logs (excluding expected TLS handshake errors from health checks)
# Note: We exclude "TLS handshake error" because curl attempts without certs are expected
ERRORS_A=$(grep -i "level=error\|panic\|fatal" registry-a.log | grep -v "TLS handshake error" || true)
ERRORS_B=$(grep -i "level=error\|panic\|fatal" registry-b.log || true)
if [ -n "$ERRORS_A" ]; then
echo "FAILURE: Found errors in Registry A log:"
echo "$ERRORS_A"
exit 1
fi
if [ -n "$ERRORS_B" ]; then
echo "FAILURE: Found errors in Registry B log:"
echo "$ERRORS_B"
exit 1
fi
echo "SUCCESS: No errors found in logs"
echo ""
echo "### 8. Log Summary"
echo "--- Registry A (last 20 lines) ---"
tail -20 registry-a.log
echo ""
echo "--- Registry B (last 20 lines) ---"
tail -20 registry-b.log
echo ""
echo "### Test Passed Successfully"
@glennpratt
Copy link
Author

❯ ./scripts/test-proxy-tls.sh
### 1. Generating Certificates
Certificate request self-signature ok
subject=CN=localhost
Certificate request self-signature ok
subject=CN=client
### 2. Creating Configuration Files
### 3. Building Registry
### 4. Starting Registry A (Upstream)
### Waiting for Registry A to be ready
Registry A not ready, waiting...
### 5. Starting Registry B (Proxy)
### 5. Waiting for Registries to be ready
### 6. Verifying Proxy
Executing: curl -v http://localhost:5002/v2/
{}

HTTP Status: 200

SUCCESS: Proxy successfully authenticated and communicated with upstream.
### 7. Checking Logs for Errors
SUCCESS: No errors found in logs

### 8. Log Summary
--- Registry A (last 20 lines) ---
time="2026-01-09T21:04:05.250912-08:00" level=debug msg="using \"text\" logging formatter"
time="2026-01-09T21:04:05.251607-08:00" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.24.5 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
time="2026-01-09T21:04:05.251677-08:00" level=info msg="redis not configured" go.version=go1.24.5 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
time="2026-01-09T21:04:05.251887-08:00" level=info msg="Starting upload purge in 44m0s" go.version=go1.24.5 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
time="2026-01-09T21:04:05.252198-08:00" level=info msg="restricting TLS version to tls1.2 or higher" go.version=go1.24.5 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
time="2026-01-09T21:04:05.252224-08:00" level=info msg="restricting TLS cipher suites to: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_256_GCM_SHA384" go.version=go1.24.5 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
time="2026-01-09T21:04:05.252772-08:00" level=debug msg="CA Subject: 0\x121\x100\x0e\x06\x03U\x04\x03\f\aTest CA" go.version=go1.24.5 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
time="2026-01-09T21:04:05.252795-08:00" level=info msg="listening on [::]:5001, tls" go.version=go1.24.5 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
time="2026-01-09T21:04:05.325305-08:00" level=debug msg="authorizing request" go.version=go1.24.5 http.request.host="localhost:5001" http.request.id=f1134576-5d73-46a8-a4b0-80be706b717d http.request.method=GET http.request.remoteaddr="[::1]:60269" http.request.uri=/v2/ http.request.useragent=curl/7.71.1-DEV instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
time="2026-01-09T21:04:05.32537-08:00" level=info msg="response completed" go.version=go1.24.5 http.request.host="localhost:5001" http.request.id=f1134576-5d73-46a8-a4b0-80be706b717d http.request.method=GET http.request.remoteaddr="[::1]:60269" http.request.uri=/v2/ http.request.useragent=curl/7.71.1-DEV http.response.contenttype=application/json http.response.duration="720.584µs" http.response.status=200 http.response.written=2 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
::1 - - [09/Jan/2026:21:04:05 -0800] "GET /v2/ HTTP/2.0" 200 2 "" "curl/7.71.1-DEV"
time="2026-01-09T21:04:05.604566-08:00" level=debug msg="authorizing request" go.version=go1.24.5 http.request.host="localhost:5001" http.request.id=4de82be2-c312-4a79-bcd0-fcc902c4c6be http.request.method=GET http.request.remoteaddr="[::1]:60277" http.request.uri=/v2/ http.request.useragent=Go-http-client/1.1 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
time="2026-01-09T21:04:05.604624-08:00" level=info msg="response completed" go.version=go1.24.5 http.request.host="localhost:5001" http.request.id=4de82be2-c312-4a79-bcd0-fcc902c4c6be http.request.method=GET http.request.remoteaddr="[::1]:60277" http.request.uri=/v2/ http.request.useragent=Go-http-client/1.1 http.response.contenttype=application/json http.response.duration="74.959µs" http.response.status=200 http.response.written=2 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
::1 - - [09/Jan/2026:21:04:05 -0800] "GET /v2/ HTTP/1.1" 200 2 "" "Go-http-client/1.1"
time="2026-01-09T21:04:10.253542-08:00" level=debug msg="{\"Name\":\"GET /v2/\",\"SpanContext\":{\"TraceID\":\"d8ec7be58f59bc0d76310338ca66d81a\",\"SpanID\":\"f281c546aec7ba30\",\"TraceFlags\":\"01\",\"TraceState\":\"\",\"Remote\":false},\"Parent\":{\"TraceID\":\"00000000000000000000000000000000\",\"SpanID\":\"0000000000000000\",\"TraceFlags\":\"00\",\"TraceState\":\"\",\"Remote\":false},\"SpanKind\":2,\"StartTime\":\"2026-01-09T21:04:05.324605-08:00\",\"EndTime\":\"2026-01-09T21:04:05.325409834-08:00\",\"Attributes\":[{\"Key\":\"http.method\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"GET\"}},{\"Key\":\"http.scheme\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"https\"}},{\"Key\":\"net.host.name\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"localhost\"}},{\"Key\":\"net.host.port\",\"Value\":{\"Type\":\"INT64\",\"Value\":5001}},{\"Key\":\"net.sock.peer.addr\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"::1\"}},{\"Key\":\"net.sock.peer.port\",\"Value\":{\"Type\":\"INT64\",\"Value\":60269}},{\"Key\":\"user_agent.original\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"curl/7.71.1-DEV\"}},{\"Key\":\"http.target\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"/v2/\"}},{\"Key\":\"net.protocol.version\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"2.0\"}},{\"Key\":\"http.response_content_length\",\"Value\":{\"Type\":\"INT64\",\"Value\":2}},{\"Key\":\"http.status_code\",\"Value\":{\"Type\":\"INT64\",\"Value\":200}}],\"Events\":null,\"Links\":null,\"Status\":{\"Code\":\"Unset\",\"Description\":\"\"},\"DroppedAttributes\":0,\"DroppedEvents\":0,\"DroppedLinks\":0,\"ChildSpanCount\":0,\"Resource\":[{\"Key\":\"service.name\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"distribution\"}},{\"Key\":\"service.version\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"v3.0.0+unknown\"}}],\"InstrumentationScope\":{\"Name\":\"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\",\"Version\":\"0.57.0\",\"SchemaURL\":\"\",\"Attributes\":null},\"InstrumentationLibrary\":{\"Name\":\"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\",\"Version\":\"0.57.0\",\"SchemaURL\":\"\",\"Attributes\":null}}\n" go.version=go1.24.5 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown
time="2026-01-09T21:04:10.253824-08:00" level=debug msg="{\"Name\":\"GET /v2/\",\"SpanContext\":{\"TraceID\":\"b63904ac99f16c3fee3af8630a5ed3b5\",\"SpanID\":\"00d83da28b2a7da3\",\"TraceFlags\":\"01\",\"TraceState\":\"\",\"Remote\":false},\"Parent\":{\"TraceID\":\"00000000000000000000000000000000\",\"SpanID\":\"0000000000000000\",\"TraceFlags\":\"00\",\"TraceState\":\"\",\"Remote\":false},\"SpanKind\":2,\"StartTime\":\"2026-01-09T21:04:05.604526-08:00\",\"EndTime\":\"2026-01-09T21:04:05.604657875-08:00\",\"Attributes\":[{\"Key\":\"http.method\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"GET\"}},{\"Key\":\"http.scheme\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"https\"}},{\"Key\":\"net.host.name\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"localhost\"}},{\"Key\":\"net.host.port\",\"Value\":{\"Type\":\"INT64\",\"Value\":5001}},{\"Key\":\"net.sock.peer.addr\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"::1\"}},{\"Key\":\"net.sock.peer.port\",\"Value\":{\"Type\":\"INT64\",\"Value\":60277}},{\"Key\":\"user_agent.original\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"Go-http-client/1.1\"}},{\"Key\":\"http.target\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"/v2/\"}},{\"Key\":\"net.protocol.version\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"1.1\"}},{\"Key\":\"http.response_content_length\",\"Value\":{\"Type\":\"INT64\",\"Value\":2}},{\"Key\":\"http.status_code\",\"Value\":{\"Type\":\"INT64\",\"Value\":200}}],\"Events\":null,\"Links\":null,\"Status\":{\"Code\":\"Unset\",\"Description\":\"\"},\"DroppedAttributes\":0,\"DroppedEvents\":0,\"DroppedLinks\":0,\"ChildSpanCount\":0,\"Resource\":[{\"Key\":\"service.name\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"distribution\"}},{\"Key\":\"service.version\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"v3.0.0+unknown\"}}],\"InstrumentationScope\":{\"Name\":\"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\",\"Version\":\"0.57.0\",\"SchemaURL\":\"\",\"Attributes\":null},\"InstrumentationLibrary\":{\"Name\":\"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\",\"Version\":\"0.57.0\",\"SchemaURL\":\"\",\"Attributes\":null}}\n" go.version=go1.24.5 instance.id=31eb8f2a-322f-4258-8d3a-c76b589e5a8b version=v3.0.0+unknown

--- Registry B (last 20 lines) ---
time="2026-01-09T21:04:05.597238-08:00" level=debug msg="using \"text\" logging formatter"
time="2026-01-09T21:04:05.597711-08:00" level=info msg="redis not configured" go.version=go1.24.5 instance.id=65dc0cb6-5853-4eef-8a51-d305c36db0d7 version=v3.0.0+unknown
time="2026-01-09T21:04:05.597748-08:00" level=info msg="Starting upload purge in 54m0s" go.version=go1.24.5 instance.id=65dc0cb6-5853-4eef-8a51-d305c36db0d7 version=v3.0.0+unknown
time="2026-01-09T21:04:05.597815-08:00" level=info msg="Starting cached object TTL expiration scheduler..." go.version=go1.24.5 instance.id=65dc0cb6-5853-4eef-8a51-d305c36db0d7 version=v3.0.0+unknown
time="2026-01-09T21:04:05.604768-08:00" level=info msg="Registry configured as a proxy cache to https://localhost:5001" go.version=go1.24.5 instance.id=65dc0cb6-5853-4eef-8a51-d305c36db0d7 version=v3.0.0+unknown
time="2026-01-09T21:04:05.604795-08:00" level=warning msg="Registry does not implement RepositoryRemover. Will not be able to delete repos and tags" go.version=go1.24.5 instance.id=65dc0cb6-5853-4eef-8a51-d305c36db0d7 version=v3.0.0+unknown
time="2026-01-09T21:04:05.604963-08:00" level=info msg="listening on [::]:5002" go.version=go1.24.5 instance.id=65dc0cb6-5853-4eef-8a51-d305c36db0d7 version=v3.0.0+unknown
time="2026-01-09T21:04:10.384208-08:00" level=debug msg="authorizing request" go.version=go1.24.5 http.request.host="localhost:5002" http.request.id=8378a3f9-dc7c-41df-bbb3-ef7e5c6672b1 http.request.method=GET http.request.remoteaddr="[::1]:60309" http.request.uri=/v2/ http.request.useragent=curl/7.71.1-DEV instance.id=65dc0cb6-5853-4eef-8a51-d305c36db0d7 version=v3.0.0+unknown
time="2026-01-09T21:04:10.3844-08:00" level=info msg="response completed" go.version=go1.24.5 http.request.host="localhost:5002" http.request.id=8378a3f9-dc7c-41df-bbb3-ef7e5c6672b1 http.request.method=GET http.request.remoteaddr="[::1]:60309" http.request.uri=/v2/ http.request.useragent=curl/7.71.1-DEV http.response.contenttype=application/json http.response.duration=1.032959ms http.response.status=200 http.response.written=2 instance.id=65dc0cb6-5853-4eef-8a51-d305c36db0d7 version=v3.0.0+unknown
::1 - - [09/Jan/2026:21:04:10 -0800] "GET /v2/ HTTP/1.1" 200 2 "" "curl/7.71.1-DEV"

### Test Passed Successfully
### Cleaning up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment