gRPC API¶
vfps's API is defined in Protocol Buffers, and every operation is served over gRPC as well as the
JSON-transcoded REST API. The definitions are in
src/Vfps/Protos/vfps/api/v1:
| Service | RPCs |
|---|---|
vfps.api.v1.NamespaceService |
Create, Get, GetAll, Delete, ListChildren |
vfps.api.v1.PseudonymService |
Create, Resolve, Get, List |
Plaintext gRPC is served on port 8081, which accepts HTTP/2 only - the HttpGrpc endpoint in
Kestrel__Endpoints, and the grpc port of the Helm chart's Service. Port 8080 serves the REST API
and the admin UI.
Server reflection¶
The server offers gRPC server reflection, so clients such as
grpcurl, ghz, grpcui or Postman need
no copy of the .proto files - and always see the API the server actually runs. To explore it:
grpcurl -plaintext 127.0.0.1:8081 list
grpcurl -plaintext 127.0.0.1:8081 describe vfps.api.v1.PseudonymService
grpcurl -plaintext 127.0.0.1:8081 describe vfps.api.v1.PseudonymServiceCreateRequest
And to call it:
grpcurl -plaintext \
-d '{"namespace": "test", "originalValue": "to be pseudonymized"}' \
127.0.0.1:8081 \
vfps.api.v1.PseudonymService/Create
Authenticating¶
With access control turned on, every call needs a bearer token - from the
identity provider, or a vfps access token created on the admin
UI's Access tokens page, which also shows these calls ready to copy. Reflection is part of the API
and takes the same token, so without one even list fails. grpcurl's -H sends the header with
reflection and the call alike:
export VFPS_TOKEN="vfps_pat_..."
export VFPS_NAMESPACE="..."
grpcurl -plaintext \
-H "authorization: Bearer $VFPS_TOKEN" \
-d "{\"namespace\": \"$VFPS_NAMESPACE\", \"originalValue\": \"to be pseudonymized\"}" \
127.0.0.1:8081 \
vfps.api.v1.PseudonymService/Create
A call without a valid token fails with Unauthenticated; one with a token that holds no grant on
the namespace, with PermissionDenied.
The gRPC utils image¶
ghcr.io/miracum/vfps/grpc-utils is a small image for talking to vfps where nothing is installed,
such as from inside a cluster. It contains:
- grpcurl, for making individual calls
- ghz, for load testing
curlandjq- vfps's
.protofiles, under/tmp/protos- for a tool that can't use reflection, pass-import-path /tmp/protos -proto vfps/api/v1/pseudonyms.proto
Every vfps release has a matching image tag, and latest follows the latest release and master
the default branch. The image runs as nobody (UID 65534). Releases up to v1.22.2 published it as
ghcr.io/miracum/vfps-grpc-utils.
From Docker¶
With vfps running locally - for example the getting started stack, which
binds port 8081 on 127.0.0.1 - share the host's network so localhost reaches it:
docker run --rm --network=host ghcr.io/miracum/vfps/grpc-utils:v1.22.4 \
grpcurl -plaintext \
-H "authorization: Bearer $VFPS_TOKEN" \
-d "{\"namespace\": \"$VFPS_NAMESPACE\", \"originalValue\": \"to be pseudonymized\"}" \
localhost:8081 \
vfps.api.v1.PseudonymService/Create
Your own shell expands $VFPS_TOKEN and $VFPS_NAMESPACE before Docker starts, so neither needs
passing into the container - leave out the -H line when access control is off. --network=host
is what lets the container reach a port the host only binds on 127.0.0.1; Docker Desktop supports
it only once host networking is enabled in its settings.
Inside Kubernetes¶
The plaintext gRPC port is usually reachable only from inside the cluster. Start a throwaway pod from the image in vfps's namespace and call the chart's Service, which is named after the release:
kubectl run --namespace=vfps --rm -i --tty --restart=Never \
--image=ghcr.io/miracum/vfps/grpc-utils:v1.22.4 \
vfps-grpc-utils -- bash
nobody@vfps-grpc-utils:/$ grpcurl -plaintext vfps:8081 list
If the chart's NetworkPolicy is enabled with networkPolicy.allowExternal: false, only pods
labelled <release>-client: "true" may reach vfps - add --labels=vfps-client=true to
kubectl run.
Load testing¶
ghz uses reflection too. For example, from the pod above, a minute of pseudonym creation against the chart's headless Service, which lets ghz spread the load across every replica:
ghz --duration=1m \
--connections=3 \
--lb-strategy=round_robin \
--insecure \
--call=vfps.api.v1.PseudonymService/Create \
-d '{"originalValue": "{{ randomString 32 }}", "namespace": "test"}' \
dns:///vfps-headless:8081
Unlike grpcurl, ghz keeps the metadata for its calls and for reflection apart - with access control on, pass the token to both:
--metadata="{\"authorization\": \"Bearer $VFPS_TOKEN\"}" \
--reflect-metadata="{\"authorization\": \"Bearer $VFPS_TOKEN\"}"
See Performance for measured numbers.