AuthServer API for AppSSO

In Application Single Sign-On (commonly called AppSSO), AuthServer represents the request for an OIDC authorization server. It causes the deployment of an authorization server backed by Redis over mutual TLS if no storage is defined.

An AuthServer should have labels which allow to uniquely match it amongst others. ClientRegistration selects an AuthServer by label selector and needs a unique match to be successful.

To allow ClientRegistrations only from a restricted set of Namespaces, you must set the annotation sso.apps.tanzu.vmware.com/allow-client-namespaces. Its value is a comma-separated list of allowed Namespaces, for example, "app-team-red,app-team-green". If the annotation is missing, the default value is *, denoting that all client namespaces are allowed.

The issuer URI, which is the point of entry for clients and end-users, is constructed through the package’s domain_template. You can view the issuer URI by running kubectl get authserver -n authservers.

See Issuer URI & TLS for more information.

Token signature keys are configured by using spec.tokenSignature. This is a required field. See Token signatures for more context.

You can configure identity providers under spec.identityProviders. If there is none, end-users can not log in. For more information about configuring identity providers, see Identity providers.

The deployment can be further customized by configuring replicas, resources, http server and logging properties.

Spec

apiVersion: sso.apps.tanzu.vmware.com/v1alpha1
kind: AuthServer
metadata:
  name: ""
  namespace: ""
  labels: { } # required, must uniquely identify this AuthServer
  annotations:
    sso.apps.tanzu.vmware.com/allow-client-namespaces: "" # optional, a comma-separated list of allowed client namespaces
    sso.apps.tanzu.vmware.com/allow-unsafe-issuer-uri: "" # optional
    sso.apps.tanzu.vmware.com/allow-unsafe-identity-provider: "" # optional
    sso.apps.tanzu.vmware.com/allow-unsafe-cors: "" # optional
spec:
  # .tls is optional if a default issuer is set
  tls:
    # must be one and only one of issuerRef, certificateRef or secretRef, unless deactivated
    issuerRef:
      name: ""
      kind: ""
      group: cert-manager.io
    certificateRef:
      name: ""
    secretRef:
      name: ""
    deactivated: false # If true, requires annotation `sso.apps.tanzu.vmware.com/allow-unsafe-issuer-uri: ""`.
  cors:
    allowOrigins: # optional, cannot be combined with 'allowAllOrigins'.
      - ""
    allowAllOrigins: false # optional
                           # If true, requires annotation `sso.apps.tanzu.vmware.com/allow-unsafe-cors: ""`.
                           # Cannot be combined with 'allowOrigins'.

    allowMethods: # optional, defaults to ["GET", "POST", "OPTIONS"]
      - ""
    allowHeaders: # optional, defaults to ["Authorization"]
      - ""
    exposeHeaders: # optional, defaults to "[]"
      - ""
    allowCredentials: false # optional, defaults to false
  token:             # optional
    accessToken:     # optional
      expiry: "12h"  # optional, default expiry is 12 hours
    refreshToken:    # optional
      expiry: "720h" # optional, default expiry is 720 hours (30 days)
    idToken:         # optional
      expiry: "12h"  # optional, default expiry is 12 hours
  tokenSignature: # required
    signAndVerifyKeyRef:
      name: "" # Must be a secret that contains an RSA private key with a minimum length of 2048 bits.
    extraVerifyKeyRefs:
      - name: "" # Must be a secret that contains an RSA private key with a minimum length of 2048 bits.
  storage: # optional
    redis: # required if 'storage' is defined
      serviceRef: # Reference to a provisioned service within the same namespace as this AuthServer. Only supports Secret reference.
        apiVersion: "v1"
        kind: "Secret"
        name: ""
  caCerts: # optional
    - secretRef: # Reference to Secret resource within the same namespace as this AuthServer.
        name: ""
  identityProviders: # optional
    # each must be one and only one of internalUnsafe, ldap, openID or saml
    - name: "" # must be unique
               # must follow the DNS Subdomain formatting (RFC 1123):
               # https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
               # must not start with 'client' or 'unknown'
      internalUnsafe: # requires annotation `sso.apps.tanzu.vmware.com/allow-unsafe-identity-provider: ""`
        users:
          - username: ""
            password: ""
            claims: # optional
              custom_claim: ""
              another_custom_claim: ""
            roles:
              - ""
        accessToken: # optional
          scope:
            defaults: # optional
            - ""
            rolesToScopes: # optional
              - fromRole: ""
                toScopes:
                  - ""
    - name: "" # must be unique
               # must follow the DNS Subdomain formatting (RFC 1123):
               # https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
               # > must not start with 'client' or 'unknown'
      ldap:
        server:
          scheme: ""
          host: ""
          port: 0
          base: ""
        bind:
          dn: ""
          passwordRef:
            name: ldap-password
        user:
          searchFilter: ""
          searchBase: ""
        roles: # optional
          fromUpstream:
            attribute: "" # required
            search:
              filter: ""
              base: ""
              subTree: false
              depth: 0
          filterBy: # optional
            - exactMatch: ""
            - regex: "" # must be valid regular expression
        idToken: # optional
          claims:
            - fromUpstream: ""
              toClaim: ""
        accessToken: # optional
          scope:
            defaults: # optional
            - ""
            rolesToScopes: # optional
              - fromRole: ""
                toScopes:
                - ""
        group:    # deprecated, use 'ldap.roles.fromUpstream' instead.
          search: # deprecated, use 'ldap.roles.fromUpstream.search' instead.
            filter: ""
            base: ""
            subTree: false
            depth: 0
          roleAttribute: "" # deprecated, use 'ldap.roles.fromUpstream.attribute' instead.
    - name: "" # must be unique
               # must follow the DNS Subdomain formatting (RFC 1123):
               # https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
               # must not start with 'client' or 'unknown'
      openID:
        issuerURI: ""
        displayName: "" # optional, must be between 2 and 32 characters in length
        clientID: ""
        clientSecretRef:
          name: ""
        scopes:
          - ""
        roles: # optional
          fromUpstream:
            claim: "" # required
          filterBy: # optional
            - exactMatch: ""
            - regex: "" # must be valid regular expression
        idToken: # optional
          claims:
            - fromUpstream: ""
              toClaim: ""
        accessToken: # optional
          scope:
            defaults: # optional
            - ""
            rolesToScopes: # optional
              - fromRole: ""
                toScopes:
                - ""
    - name: "" # must be unique
               # must follow the DNS Subdomain formatting (RFC 1123):
               # https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
               # must not start with 'client' or 'unknown'
      saml:
        metadataURI: ""
        displayName: "" # optional, must be between 2 and 32 characters in length
        roles: # optional
          fromUpstream:
            attribute: "" # required
          filterBy: # optional
            - exactMatch: ""
            - regex: "" # must be valid regular expressions
        idToken: # optional
          claims:
            - fromUpstream: ""
              toClaim: ""
        accessToken: # optional
          scope:
            defaults: # optional
              - ""
            rolesToScopes: # optional
              - fromRole: ""
                toScopes:
                  - ""
  replicas: 1 # optional, default 2
  logging: "" # optional, must be valid YAML
  server: "" # optional, must be valid YAML
  resources: # optional, default {requests: {cpu: "256m", memory: "300Mi"}, limits: {cpu: "2", memory: "768Mi"}}
    requests:
      cpu: ""
      mem: ""
    limits:
      cpu: ""
      mem: ""
  redisResources: # optional, default {requests: {cpu: "50m", memory: "100Mi"}, limits: {cpu: "100m", memory: "256Mi"}}
    requests:
      cpu: ""
      mem: ""
    limits:
      cpu: ""
      mem: ""
status:
  observedGeneration: 0
  issuerURI: ""
  clientRegistrationCount: 1
  tokenSignatureKeyCount: 0
  deployments:
    authServer:
      configHash: ""
      image: ""
      replicas: 0
    redis: # left empty if storage is configured by the service operator
      image: ""
  storage:
    redis:
      host: "" # the hostname of the configured Redis
      port: "" # the port of the configured Redis
  tls:
    deactivated: false
    issuerRef:
      name: ""
      kind: ""
      group: cert-manager.io
  conditions:
    - lastTransitionTime:
      message: ""
      reason: ""
      status: "True" # or "False"
      type: ""

Alternatively, you can interactively discover the spec with:

kubectl explain authservers.sso.apps.tanzu.vmware.com

Status & conditions

The .status subresource helps you to learn the AuthServer’s readiness, resulting deployments, attached clients and to troubleshoot issues.

.status.issuerURI is the templated issuer URI. This is the entry point for any traffic.

.status.tls is the actual TLS configuration.

.status.tokenSignatureKeyCount is the number of currently configured token signature keys.

.status.clientRegistrationCount is the number of currently registered clients.

.status.deployments.authServer describes the current authorization server deployment by listing the running image, its replicas, the hash of the current configuration and the generation which has last resulted in a restart.

.status.deployments.redis describes the current provided Redis deployment by listing its running image. This field is nil if storage is defined explicitly by using .spec.storage.

.status.storage.redis describes the configured Redis storage such as host name and port number.

.status.conditions documents each step in the reconciliation:

  • Valid: Is the spec valid?
  • ImagePullSecretApplied: Has the image pull secret been applied?
  • SignAndVerifyKeyResolved: Has the single sign-and-verify key been resolved?
  • ExtraVerifyKeysResolved: Have the single extra verify keys been resolved?
  • IdentityProvidersResolved: Has all identity provider configuration been resolved?
  • ConfigResolved: Has the complete configuration for the authorization server been resolved?
  • AuthServerConfigured: Has the complete configuration for the authorization server been applied?
  • IssuerURIReady: Is the authorization server yet responding to {.status.issuerURI}/.well-known/openid-configuration?
  • Ready: whether all the previous conditions are “True”

The super condition Ready denotes a fully successful reconciliation of a given ClientRegistration.

If everything goes well you will see something like this:

issuerURI: "https://..."
observedGeneration: 1
tokenSignatureKeyCount: 0
clientRegistrationCount: 0
caCerts:
  - cert:
      subject: ""
    source:
      secretEntry: ""
deployments:
  authServer:
    LastParentGenerationWithRestart: 1
    configHash: "11216479096262796218"
    image: "..."
    replicas: 1
  redis: # leave empty if external storage is defined
    image: "..."
storage:
  redis:
   host: "" # the host name of the configured Redis
   port: "" # the port of the configured Redis
tls:
  deactivated: false
  # One of issuerRef, certificateRef or secretRef is set if TLS is enabled
  issuerRef:
    name: ""
    kind: ""
    group: ""
  certificateRef:
    name: ""
  secretRef:
    name: ""
conditions:
  - lastTransitionTime: "2022-08-24T09:58:10Z"
    message: ""
    reason: KeysConfigSecretUpdated
    status: "True"
    type: AuthServerConfigured
  - lastTransitionTime: "2022-08-24T09:58:10Z"
    message: ""
    reason: Resolved
    status: "True"
    type: ConfigResolved
  - lastTransitionTime: "2022-08-24T09:58:10Z"
    message: ""
    reason: ExtraVerifyKeysResolved
    status: "True"
    type: ExtraVerifyKeysResolved
  - lastTransitionTime: "2022-08-24T09:58:10Z"
    message: ""
    reason: Resolved
    status: "True"
    type: IdentityProvidersResolved
  - lastTransitionTime: "2022-08-24T09:58:10Z"
    message: ""
    reason: ImagePullSecretApplied
    status: "True"
    type: ImagePullSecretApplied
  - lastTransitionTime: "2022-08-24T09:58:28Z"
    message: ""
    reason: Ready
    status: "True"
    type: IssuerURIReady
  - lastTransitionTime: "2022-08-24T09:58:28Z"
    message: ""
    reason: Ready
    status: "True"
    type: Ready
  - lastTransitionTime: "2022-08-24T09:58:10Z"
    message: ""
    reason: SignAndVerifyKeyResolved
    status: "True"
    type: SignAndVerifyKeyResolved
  - lastTransitionTime: "2022-08-24T09:58:10Z"
    message: ""
    reason: Valid
    status: "True"
    type: Valid

RBAC

The ServiceAccount of the authorization server has a Role with the following permissions:

- apiGroups:
    - ""
  resources:
    - secrets
  verbs:
    - get
    - list
    - watch
  resourceNames:
    - { name }-auth-server-keys
    - { name }-auth-server-clients

Example

This example requests an authorization server with two token signature keys and two identity providers.

Note

The label used for matching to ClientRegistrations must be unique across namespaces.

---
apiVersion: sso.apps.tanzu.vmware.com/v1alpha1
kind: AuthServer
metadata:
  name: authserver-sample
  namespace: default
  labels:
    identifier: authserver-identifier
    sample: "true"
  annotations:
    sso.apps.tanzu.vmware.com/allow-unsafe-identity-provider: ""
    sso.apps.tanzu.vmware.com/allow-unsafe-cors: ""
spec:
  replicas: 1
  tls:
    issuerRef:
      name: my-cluster-issuer
      kind: ClusterIssuer
  tokenSignature:
    signAndVerifyKeyRef:
      name: sample-token-signing-key
    extraVerifyKeyRefs:
      - name: sample-token-verification-key
  cors:
    allowAllOrigins: true
  identityProviders:
    - name: internal
      internalUnsafe:
        users:
          - username: user
            password: password
            claims:
              alt_address: "123 Alternate Street"
            roles:
              - message.write
    - name: okta
      openID:
        issuerURI: https://dev-xxxxxx.okta.com
        displayName: "Okta"
        clientID: xxxxxxxxxxxxx
        clientSecretRef:
          name: okta-client-secret
        authorizationUri: https://dev-xxxxxx.okta.com/oauth2/v1/authorize
        tokenUri: https://dev-xxxxxx.okta.com/oauth2/v1/token
        jwksUri: https://dev-xxxxxx.okta.com/oauth2/v1/keys
        userinfoUri: https://dev-xxxxxx.okta.com/oauth2/v1/userinfo
        scopes:
          - openid
        roles:
          fromUpstream:
            claim: my_custom_okta_roles_claim
        idToken:
          claims:
            - fromUpstream: "alternate_address"
              toClaim: "alt_address"
        accessToken:
          scope:
            defaults:
              - "developer.read"
              - "developer.write"
            rolesToScopes:
              - fromRole: "finance"
                toScopes:
                  - "finance.read"
                  - "finance.write"

---
apiVersion: secretgen.k14s.io/v1alpha1
kind: RSAKey
metadata:
  name: sample-token-signing-key
  namespace: default
spec:
  secretTemplate:
    type: Opaque
    stringData:
      key.pem: $(privateKey)
      pub.pem: $(publicKey)

---
apiVersion: secretgen.k14s.io/v1alpha1
kind: RSAKey
metadata:
  name: sample-token-verification-key
  namespace: default
spec:
  secretTemplate:
    type: Opaque
    stringData:
      key.pem: $(privateKey)
      pub.pem: $(publicKey)

---
apiVersion: v1
kind: Secret
metadata:
  name: okta-client-secret
  namespace: default
stringData:
  clientSecret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
check-circle-line exclamation-circle-line close-line
Scroll to top icon