Token Vendor
The token vendor provides authentication for requests from the robots to our cloud environment.
The robots identity is generated during setup via a public-private key pair.
The token vendor provides APIs for registering robots through their public key and OAuth2 workflows for authenticating the signed requests from robots to cloud resources, for example to write logs to GCP Logging.
The token vendor itself is stateless and all data is stored in GCP.
The following workflows are covered by the token vendor:
- Register a robot by its public key and a unique device identifier. The public key is stored in a cloud backend.
- Retrieve a robot's public key through the device identifier
- Generate an scoped and time-limited IAM access token for access to GCP resources
- Validate a given IAM access token
Public Key Backends
The token vendor supports multiple backends for storage of public keys for registered devices.
Kubernetes Configmaps
The Kubernetes backend uses configmaps to store and lookup public keys.
The configmaps are stored in a configured namespace with the device identifier as name.
The public key is stored under a key in the configmap.
Devices can be removed by deleting the configmap.
In-Memory
Stores public keys in-memory for testing.
Example:
# Run with memory backend
bazel run //src/go/cmd/token-vendor -- -verbose --project testproject --accepted_audience test --key-store IN_MEMORY
# Store test key
curl --data-binary "@api/v1/testdata/rsa_cert.pem" -H "Content-type: application/x-pem-file" -D - http://127.0.0.1:9090/apis/core.token-vendor/v1/public-key.publish?device-id=robot-dev-testuser
# Retrieve key
curl -D - http://127.0.0.1:9090/apis/core.token-vendor/v1/public-key.read?device-id=robot-dev-testuser
API
/public-key.publish: Robot registration
New robots get registered by a human administrator (authorized by an access
token on the request). The method add the provided public key to the configured
key store. Write access to the public key registry needs to be restricted to
refuse eg. robots to register other robots.
- URL: /apis/core.token-vendor/v1/public-key.publish
- Method: POST
- URL Params:
- device-id: unique device name (by default robot-)
- Body: application/x-pem-file
- Response: only http status code
/public-key.read: Public key retrieval
To verify messages send by a robot one can fetch the public key from the
keystore using this method.
- URL: /apis/core.token-vendor/v1/public-key.read
- Method: GET
- URL Params:
- device-id: unique device name (by default robot-)
- Response: application/x-pem-file
/token.oauth2: OAuth2 access token requests by robots
Robots sign JWTs with their local private keys. These get verified against the
public keys from the keystore. If the key is present and enabled, the token
vendor will hand out an OAuth access token for robot-service@ service account.
- URL: /apis/core.token-vendor/v1/token.oauth2
- Method: POST
- Body: JWT query (TokenSource)
- Response: application/json
/token.verify: AuthN/Z verification
Browsers or robots can query endpoints like the ws-proxy with authorization
headers or a ?token=
query parameter. They are already authenticated, and the
token vendor just checks that IAM authorizes the request.
- URL: /apis/core.token-vendor/v1/token.verify
- Method: GET
- URL Params:
- robots: boolean to indicate if robot-service account tookens are allowed
- Response: only http status code
Results are backed by a cache with a 5 minute lifetime to ease the load on the
IAM backend.
Interactive AuthN & AuthZ (with oauth2-proxy)
We use the token vendor together with oauth2-proxy as an authentication and
authorization helper for nginx. This is essentially a poor man's IAP, used
because the GCE Ingress controller does not support IAP annotations on the GCE
objects it creates. Ingresses can be protected by it with an auth-url
annotation:
nginx.ingress.kubernetes.io/auth-url: "http://oauth2-proxy.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify"
nginx.ingress.kubernetes.io/auth-signin: "https://{{ .Values.domain }}/oauth2/start?rd=$escaped_request_uri"
nginx is set up to use the oauth2-proxy as an authentication proxy, and the
oauth2-proxy has its upstream set to the token vendor.
The request for unauthenticated users flows like this:
- nginx-ingress receives the request.
- nginx-ingress queries the auth-url.
- oauth2-proxy sees that there's no cookie attached, redirects the user to the
Google sign-in page.
- When the user is signed in, oauth2-proxy's callback page sets the encrypted
auth cookie and redirects the user back to the original URL.
- nginx-ingress again queries the auth-url.
- oauth2-proxy sees the auth cookie, decrypts it and forwards the access token
to the token vendor.
- The token vendor sees the access token and checks the authorization with
IAM.
- When authorization is fine, token vendor returns 200.
- nginx-ingress forwards the request to the backend.
curl API Example Workflow
First set the name of your project:
PROJECT=testproject
Publish a key for the device robot-dev-testuser
:
curl -D - --max-time 3 --data-binary "@api/v1/testdata/rsa_cert.pem" -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-type: application/x-pem-file" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/public-key.publish?device-id=robot-dev-testuser
Read the key again:
curl -D - --max-time 3 -H "Authorization: Bearer $(gcloud auth print-access-token)" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/public-key.read?device-id=robot-dev-testuser
Verify if your local user account has access to the human and robot ACL:
curl -D - --max-time 3 -H "Authorization: Bearer $(gcloud auth print-access-token)" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.verify
and
curl -D - --max-time 3 -H "Authorization: Bearer $(gcloud auth print-access-token)" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.verify?robots=true
Request a cloud access token for the robot. First generate a valid JWT using the intstructions at testdata/README.md. Afterwards use it to request the cloud token:
JWT=$(cat api/v1/testdata/jwt.bin)
curl -D - --max-time 3 --data-binary "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${JWT}" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.oauth2
You can capture the token in $TOKEN
with:
TOKEN=$(curl -s --max-time 3 --data-binary "grant_type=urn:ietf:params:oauth:grant-typ
e:jwt-bearer&assertion=${JWT}" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.oauth2 | jq -r .access_token)
Verify if the token has access to the robots ACL (it should respond 200):
curl -D - --max-time 3 -H "Authorization: Bearer ${TOKEN}" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.verify?robots=true
Verify if the token does not have access to the human ACL (it should respond 403):
curl -D - --max-time 3 -H "Authorization: Bearer ${TOKEN}" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.verify