Zalando Platform IAM Integration

Introductory Remark: After the learnings of two years of STUPS/IAM integration, we will integrate a more advanced and simpler solution. The integration will be Kubernetes native and will take complexity out of your application. Instead of providing you with client ID, client secret, username and password that you then have to use to generate tokens regularly, we will provide you a simple way to directly obtain the ready-to-go tokens instead of fiddling with the credentials. Technically speaking, this means you just need to read your current token from a text file in your filesystem and you are done - no complicated token libraries anymore.

The user flow for a new application to get OAuth credentials looks like:

Platform IAM Credentials

The PlatformCredentialsSet resource allows application owners to declare needed OAuth credentials.

apiVersion: "zalando.org/v1"
kind: PlatformCredentialsSet
metadata:
   name: my-app-credentials
spec:
   application: my-app
   tokens:
     full-access: # token name
       privileges: # privileges/scopes for the token.
         # All zalando-specific privileges start with namespace com.zalando, following pattern <namespace>::<privilege>
         # the privileges/scopes you define here should match those you define for your application in yourturn.
         - com.zalando::foobar.write
         - com.zalando::acme.full
     read-only: # token name
       privileges: # privileges/scopes for the token.
         - com.zalando::foobar.read
   clients:
     employee: # client name
       # the allowed grant type, see https://tools.ietf.org/html/rfc6749
       # options: authorization-code, implicit, resource-owner-password-credentials, client-credentials
       # (values directly reference RFC section titles)
       grant: authorization-code
       # the client's account realm
       # options: users, customers, services
       # ("services" realm should not be used for clients, use the "tokens" section instead!)
       realm: users
       # redirection URI as described in https://tools.ietf.org/html/rfc6749#section-2
       redirectUri: https://example.org/auth/callback

The declared credentials will automatically be provided as a secret with the same name.

Following this example you would get a token called full-access with the privileges com.zalando::foobar.write and com.zalando::acme.full, a token called read-only with privileges com.zalando::foobar.read and a client named employee which uses authorization-code grant under realm users.

Secrets

Automatically generated secrets provide the declared OAuth credentials in the following form:

apiVersion: v1
kind: Secret
metadata:
  name: my-app-credentials
type: Opaque
data:
  full-access-token-type: Bearer
  full-access-token-secret: JwAbc123.. # JWT token
  read-only-token-type: Bearer
  read-only-token-secret: JwBcd456.. # JWT token
  employee-client-id: 67b86a55-61e6-4862-aa14-70fe7be788f4
  employee-client-secret: 5585942c-ce79-44e4-aac2-8af565b51d3e

The secret can conveniently be mounted to read the tokens and client credentials from a volume:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  template:
    metadata:
      labels:
        application: my-app
    spec:
      containers:
      - name: my-app
        image: pierone.stups.zalan.do/myteam/my-app:cd53
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: my-app-credentials
          mountPath: /meta/credentials
          readOnly: true
      volumes:
      - name: my-app-credentials
        secret:
          secretName: my-app-credentials

The application can now simply read the declared tokens from text files, i.e. even a simple Bash script suffices to use OAuth tokens:

#!/bin/bash
type=$(cat /meta/credentials/read-only-token-type)
secret=$(cat /meta/credentials/read-only-token-secret)
curl -H "Authorization: $type $secret" https://resource-server.example.org/protected

Either use one of the supported token libraries or implement the file read on your own. How to read a token in different languages:

# Python
with open('/meta/credentials/{}-token-secret'.format(token_name)) as fd:
    access_token = fd.read().strip()
// JavaScript
const accessToken = String(fs.readFileSync(`/meta/credentials/${tokenName}-token-secret`)).trim()
// Java
String accessToken = new String(Files.readAllBytes(Paths.get("/meta/credentials/" + tokenName + "-token-secret"))).trim();

Note

Using the authorization type from the secret instead of hardcoding Bearer allows to transparently switch to HTTP Basic Auth in a different context (e.g. running the Open Source application in a non-Zalando environment). Users would simply need to provide an appropriate secret like:

apiVersion: v1
kind: Secret
metadata:
  name: my-app-credentials
type: Opaque
data:
  full-access-token-type: Basic
  full-access-token-secret: dXNlcjpwYXNzCg== # base64 encoded user:pass
  read-only-token-type: Basic
  read-only-token-secret: dXNlcjpwYXNzCg== # base64 encoded user:pass

Problem Feedback

Providing the requested credentials (tokens, clients) may fail for various reasons:

  • the PlatformCredentialsSet has syntactic errors
  • the application (application property) does not exist or is missing required configuration
  • the application is not allowed to obtain the requested credentials (e.g. missing privileges)
  • some other error occurred

All problems with credential distribution are written to the secret with the same name as the PlatformCredentialsSet:

apiVersion: v1
kind: Secret
metadata:
  name: my-app-credentials
  annotations:
    zalando.org/problems: |
      - type: https://credentials-provider.example.org/not-enough-privileges
        title: Forbidden: Not enough privileges
        status: 403
        instance: tokens/full-access
type: Opaque
data:
  # NOTE: the declared "full-access" token is missing as it was denied
  read-only-token-type: Bearer
  read-only-token-secret: JwBcd456.. # JWT token
  employee-client-id: 67b86a55-61e6-4862-aa14-70fe7be788f4
  employee-client-secret: 5585942c-ce79-44e4-aac2-8af565b51d3e

The zalando.org/problems annotation contains a list of “Problem JSON” objects as defined in RFC 7807 as YAML. At least the fields type, title and instance should be set by the component processing the PlatformCredentialsSet resource:

type
Machine-readable URI reference that identifies the problem type (e.g. https://example.org/invalid-grant)
title
Short, human-readable summary of the problem type (e.g. “Invalid client grant”)
instance
Relative path indicating the problem location, this should reference the token or client (e.g. clients/my-client)

See also the Problem OpenAPI schema YAML.