OPENSHIFT

Install RH-SSO on Openshift

#google , #rh-sso , #keycloak , #ansible , #authentication , #authorization

Install RH-SSO on Openshift

First create a new project to keep RH-SSO work clean and easily delete it if necessary.

oc new-project sso

I started by exporting the existing RH-SSO persistent depoyment template. This builds up one RH-SSO app with one postgres persistent database by default. You’ve got lots of other choices.

$ oc get template -n openshift | grep sso
eap64-sso-s2i                                   An example EAP 6 Single Sign-On application. For more information about using...   44 (19 blank)     8
eap70-sso-s2i                                   An example EAP 7 Single Sign-On application. For more information about using...   44 (19 blank)     8
eap71-sso-s2i                                   An example EAP 7 Single Sign-On application. For more information about using...   44 (19 blank)     8
sso71-https                                     An example SSO 7 application. For more information about using this template,...   26 (15 blank)     6
sso71-mysql                                     An example SSO 7 application with a MySQL database. For more information abou...   36 (20 blank)     8
sso71-mysql-persistent                          An example SSO 7 application with a MySQL database. For more information abou...   37 (20 blank)     9
sso71-postgresql                                An example SSO 7 application with a PostgreSQL database. For more information...   33 (17 blank)     8
sso71-postgresql-persistent                     An example SSO 7 application with a PostgreSQL database. For more information...   34 (17 blank)     9
sso72-https                                     An example SSO 7 application. For more information about using this template,...   26 (15 blank)     6
sso72-mysql                                     An example SSO 7 application with a MySQL database. For more information abou...   36 (20 blank)     8
sso72-mysql-persistent                          An example SSO 7 application with a MySQL database. For more information abou...   37 (20 blank)     9
sso72-postgresql                                An example SSO 7 application with a PostgreSQL database. For more information...   33 (17 blank)     8
sso72-postgresql-persistent                     An example SSO 7 application with a PostgreSQL database. For more information...   34 (17 blank)     9

These templates are all ready to use by just passing in variables, but I like to customize things as I work with them. As of this writing, you can see the template as below, with variables at the bottom:

$ oc export template sso72-postgresql-persistent -n openshift > base-template.yml
$ cat base-template.yml
apiVersion: v1
kind: Template
labels:
  template: sso72-postgresql-persistent
  xpaas: 1.4.9
message: 'A new persistent SSO service (using PostgreSQL) has been created in your
  project. The admin username/password for accessing the master realm via the SSO
  console is ${SSO_ADMIN_USERNAME}/${SSO_ADMIN_PASSWORD}. The username/password for
  accessing the PostgreSQL database "${DB_DATABASE}" is ${DB_USERNAME}/${DB_PASSWORD}.
  Please be sure to create the following secrets: "${HTTPS_SECRET}" containing the
  ${HTTPS_KEYSTORE} file used for serving secure content; "${JGROUPS_ENCRYPT_SECRET}"
  containing the ${JGROUPS_ENCRYPT_KEYSTORE} file used for securing JGroups communications;
  "${SSO_TRUSTSTORE_SECRET}" containing the ${SSO_TRUSTSTORE} file used for securing
  SSO requests.'
metadata:
  annotations:
    description: An example SSO 7 application with a PostgreSQL database. For more
      information about using this template, see https://github.com/jboss-openshift/application-templates.
    iconClass: icon-sso
    openshift.io/display-name: Single Sign-On 7.2 + PostgreSQL
    openshift.io/provider-display-name: Red Hat, Inc.
    tags: sso,keycloak,jboss
    template.openshift.io/documentation-url: https://access.redhat.com/documentation/en/red-hat-single-sign-on/
    template.openshift.io/long-description: This template defines resources needed
      to develop Red Hat Single Sign-On 7.2 server based deployment and deployment
      configuration for PostgreSQL using persistence.
    template.openshift.io/support-url: https://access.redhat.com
    version: 1.4.9
  creationTimestamp: null
  name: sso72-postgresql-persistent
objects:
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      description: The web server's http port.
      service.alpha.openshift.io/dependencies: '[{"name": "${APPLICATION_NAME}-postgresql",
        "kind": "Service"}]'
    labels:
      application: ${APPLICATION_NAME}
    name: ${APPLICATION_NAME}
  spec:
    ports:
    - port: 8080
      targetPort: 8080
    selector:
      deploymentConfig: ${APPLICATION_NAME}
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      description: The web server's https port.
      service.alpha.openshift.io/dependencies: '[{"name": "${APPLICATION_NAME}-postgresql",
        "kind": "Service"}]'
    labels:
      application: ${APPLICATION_NAME}
    name: secure-${APPLICATION_NAME}
  spec:
    ports:
    - port: 8443
      targetPort: 8443
    selector:
      deploymentConfig: ${APPLICATION_NAME}
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      description: The database server's port.
    labels:
      application: ${APPLICATION_NAME}
    name: ${APPLICATION_NAME}-postgresql
  spec:
    ports:
    - port: 5432
      targetPort: 5432
    selector:
      deploymentConfig: ${APPLICATION_NAME}-postgresql
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      description: The JGroups ping port for clustering.
      service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
    labels:
      application: ${APPLICATION_NAME}
    name: ${APPLICATION_NAME}-ping
  spec:
    clusterIP: None
    ports:
    - name: ping
      port: 8888
    selector:
      deploymentConfig: ${APPLICATION_NAME}
- apiVersion: v1
  id: ${APPLICATION_NAME}-http
  kind: Route
  metadata:
    annotations:
      description: Route for application's http service.
    labels:
      application: ${APPLICATION_NAME}
    name: ${APPLICATION_NAME}
  spec:
    host: ${HOSTNAME_HTTP}
    to:
      name: ${APPLICATION_NAME}
- apiVersion: v1
  id: ${APPLICATION_NAME}-https
  kind: Route
  metadata:
    annotations:
      description: Route for application's https service.
    labels:
      application: ${APPLICATION_NAME}
    name: secure-${APPLICATION_NAME}
  spec:
    host: ${HOSTNAME_HTTPS}
    tls:
      termination: passthrough
    to:
      name: secure-${APPLICATION_NAME}
- apiVersion: v1
  kind: DeploymentConfig
  metadata:
    labels:
      application: ${APPLICATION_NAME}
    name: ${APPLICATION_NAME}
  spec:
    replicas: 1
    selector:
      deploymentConfig: ${APPLICATION_NAME}
    strategy:
      type: Recreate
    template:
      metadata:
        labels:
          application: ${APPLICATION_NAME}
          deploymentConfig: ${APPLICATION_NAME}
        name: ${APPLICATION_NAME}
      spec:
        containers:
        - env:
          - name: DB_SERVICE_PREFIX_MAPPING
            value: ${APPLICATION_NAME}-postgresql=DB
          - name: DB_JNDI
            value: ${DB_JNDI}
          - name: DB_USERNAME
            value: ${DB_USERNAME}
          - name: DB_PASSWORD
            value: ${DB_PASSWORD}
          - name: DB_DATABASE
            value: ${DB_DATABASE}
          - name: TX_DATABASE_PREFIX_MAPPING
            value: ${APPLICATION_NAME}-postgresql=DB
          - name: DB_MIN_POOL_SIZE
            value: ${DB_MIN_POOL_SIZE}
          - name: DB_MAX_POOL_SIZE
            value: ${DB_MAX_POOL_SIZE}
          - name: DB_TX_ISOLATION
            value: ${DB_TX_ISOLATION}
          - name: JGROUPS_PING_PROTOCOL
            value: openshift.DNS_PING
          - name: OPENSHIFT_DNS_PING_SERVICE_NAME
            value: ${APPLICATION_NAME}-ping
          - name: OPENSHIFT_DNS_PING_SERVICE_PORT
            value: "8888"
          - name: HTTPS_KEYSTORE_DIR
            value: /etc/eap-secret-volume
          - name: HTTPS_KEYSTORE
            value: ${HTTPS_KEYSTORE}
          - name: HTTPS_KEYSTORE_TYPE
            value: ${HTTPS_KEYSTORE_TYPE}
          - name: HTTPS_NAME
            value: ${HTTPS_NAME}
          - name: HTTPS_PASSWORD
            value: ${HTTPS_PASSWORD}
          - name: JGROUPS_ENCRYPT_SECRET
            value: ${JGROUPS_ENCRYPT_SECRET}
          - name: JGROUPS_ENCRYPT_KEYSTORE_DIR
            value: /etc/jgroups-encrypt-secret-volume
          - name: JGROUPS_ENCRYPT_KEYSTORE
            value: ${JGROUPS_ENCRYPT_KEYSTORE}
          - name: JGROUPS_ENCRYPT_NAME
            value: ${JGROUPS_ENCRYPT_NAME}
          - name: JGROUPS_ENCRYPT_PASSWORD
            value: ${JGROUPS_ENCRYPT_PASSWORD}
          - name: JGROUPS_CLUSTER_PASSWORD
            value: ${JGROUPS_CLUSTER_PASSWORD}
          - name: SSO_ADMIN_USERNAME
            value: ${SSO_ADMIN_USERNAME}
          - name: SSO_ADMIN_PASSWORD
            value: ${SSO_ADMIN_PASSWORD}
          - name: SSO_REALM
            value: ${SSO_REALM}
          - name: SSO_SERVICE_USERNAME
            value: ${SSO_SERVICE_USERNAME}
          - name: SSO_SERVICE_PASSWORD
            value: ${SSO_SERVICE_PASSWORD}
          - name: SSO_TRUSTSTORE
            value: ${SSO_TRUSTSTORE}
          - name: SSO_TRUSTSTORE_DIR
            value: /etc/sso-secret-volume
          - name: SSO_TRUSTSTORE_PASSWORD
            value: ${SSO_TRUSTSTORE_PASSWORD}
          image: ${APPLICATION_NAME}
          imagePullPolicy: Always
          livenessProbe:
            exec:
              command:
              - /bin/bash
              - -c
              - /opt/eap/bin/livenessProbe.sh
            initialDelaySeconds: 60
          name: ${APPLICATION_NAME}
          ports:
          - containerPort: 8778
            name: jolokia
            protocol: TCP
          - containerPort: 8080
            name: http
            protocol: TCP
          - containerPort: 8443
            name: https
            protocol: TCP
          - containerPort: 8888
            name: ping
            protocol: TCP
          readinessProbe:
            exec:
              command:
              - /bin/bash
              - -c
              - /opt/eap/bin/readinessProbe.sh
          resources:
            limits:
              memory: ${MEMORY_LIMIT}
          volumeMounts:
          - mountPath: /etc/eap-secret-volume
            name: eap-keystore-volume
            readOnly: true
          - mountPath: /etc/jgroups-encrypt-secret-volume
            name: eap-jgroups-keystore-volume
            readOnly: true
          - mountPath: /etc/sso-secret-volume
            name: sso-truststore-volume
            readOnly: true
        terminationGracePeriodSeconds: 75
        volumes:
        - name: eap-keystore-volume
          secret:
            secretName: ${HTTPS_SECRET}
        - name: eap-jgroups-keystore-volume
          secret:
            secretName: ${JGROUPS_ENCRYPT_SECRET}
        - name: sso-truststore-volume
          secret:
            secretName: ${SSO_TRUSTSTORE_SECRET}
    triggers:
    - imageChangeParams:
        automatic: true
        containerNames:
        - ${APPLICATION_NAME}
        from:
          kind: ImageStreamTag
          name: redhat-sso72-openshift:1.0
          namespace: ${IMAGE_STREAM_NAMESPACE}
      type: ImageChange
    - type: ConfigChange
- apiVersion: v1
  kind: DeploymentConfig
  metadata:
    labels:
      application: ${APPLICATION_NAME}
    name: ${APPLICATION_NAME}-postgresql
  spec:
    replicas: 1
    selector:
      deploymentConfig: ${APPLICATION_NAME}-postgresql
    strategy:
      type: Recreate
    template:
      metadata:
        labels:
          application: ${APPLICATION_NAME}
          deploymentConfig: ${APPLICATION_NAME}-postgresql
        name: ${APPLICATION_NAME}-postgresql
      spec:
        containers:
        - env:
          - name: POSTGRESQL_USER
            value: ${DB_USERNAME}
          - name: POSTGRESQL_PASSWORD
            value: ${DB_PASSWORD}
          - name: POSTGRESQL_DATABASE
            value: ${DB_DATABASE}
          - name: POSTGRESQL_MAX_CONNECTIONS
            value: ${POSTGRESQL_MAX_CONNECTIONS}
          - name: POSTGRESQL_MAX_PREPARED_TRANSACTIONS
            value: ${POSTGRESQL_MAX_CONNECTIONS}
          - name: POSTGRESQL_SHARED_BUFFERS
            value: ${POSTGRESQL_SHARED_BUFFERS}
          image: postgresql
          imagePullPolicy: Always
          livenessProbe:
            initialDelaySeconds: 30
            tcpSocket:
              port: 5432
            timeoutSeconds: 1
          name: ${APPLICATION_NAME}-postgresql
          ports:
          - containerPort: 5432
            protocol: TCP
          readinessProbe:
            exec:
              command:
              - /bin/sh
              - -i
              - -c
              - psql -h 127.0.0.1 -U $POSTGRESQL_USER -q -d $POSTGRESQL_DATABASE -c
                'SELECT 1'
            initialDelaySeconds: 5
            timeoutSeconds: 1
          volumeMounts:
          - mountPath: /var/lib/pgsql/data
            name: ${APPLICATION_NAME}-postgresql-pvol
        terminationGracePeriodSeconds: 60
        volumes:
        - name: ${APPLICATION_NAME}-postgresql-pvol
          persistentVolumeClaim:
            claimName: ${APPLICATION_NAME}-postgresql-claim
    triggers:
    - imageChangeParams:
        automatic: true
        containerNames:
        - ${APPLICATION_NAME}-postgresql
        from:
          kind: ImageStreamTag
          name: postgresql:${POSTGRESQL_IMAGE_STREAM_TAG}
          namespace: ${IMAGE_STREAM_NAMESPACE}
      type: ImageChange
    - type: ConfigChange
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    labels:
      application: ${APPLICATION_NAME}
    name: ${APPLICATION_NAME}-postgresql-claim
  spec:
    accessModes:
    - ReadWriteOnce
    resources:
      requests:
        storage: ${VOLUME_CAPACITY}
parameters:
- description: The name for the application.
  displayName: Application Name
  name: APPLICATION_NAME
  required: true
  value: sso
- description: 'Custom hostname for http service route.  Leave blank for default hostname,
    e.g.: <application-name>.<project>.<default-domain-suffix>'
  displayName: Custom http Route Hostname
  name: HOSTNAME_HTTP
- description: 'Custom hostname for https service route.  Leave blank for default
    hostname, e.g.: <application-name>.<project>.<default-domain-suffix>'
  displayName: Custom https Route Hostname
  name: HOSTNAME_HTTPS
- description: Database JNDI name used by application to resolve the datasource, e.g.
    java:/jboss/datasources/postgresql
  displayName: Database JNDI Name
  name: DB_JNDI
  value: java:jboss/datasources/KeycloakDS
- description: Database name
  displayName: Database Name
  name: DB_DATABASE
  required: true
  value: root
- description: The name of the secret containing the keystore file
  displayName: Server Keystore Secret Name
  name: HTTPS_SECRET
  value: sso-app-secret
- description: The name of the keystore file within the secret
  displayName: Server Keystore Filename
  name: HTTPS_KEYSTORE
  value: keystore.jks
- description: The type of the keystore file (JKS or JCEKS)
  displayName: Server Keystore Type
  name: HTTPS_KEYSTORE_TYPE
- description: The name associated with the server certificate (e.g. jboss)
  displayName: Server Certificate Name
  name: HTTPS_NAME
- description: The password for the keystore and certificate (e.g. mykeystorepass)
  displayName: Server Keystore Password
  name: HTTPS_PASSWORD
- description: Sets xa-pool/min-pool-size for the configured datasource.
  displayName: Datasource Minimum Pool Size
  name: DB_MIN_POOL_SIZE
- description: Sets xa-pool/max-pool-size for the configured datasource.
  displayName: Datasource Maximum Pool Size
  name: DB_MAX_POOL_SIZE
- description: Sets transaction-isolation for the configured datasource.
  displayName: Datasource Transaction Isolation
  name: DB_TX_ISOLATION
- description: The maximum number of client connections allowed. This also sets the
    maximum number of prepared transactions.
  displayName: PostgreSQL Maximum number of connections
  name: POSTGRESQL_MAX_CONNECTIONS
- description: Configures how much memory is dedicated to PostgreSQL for caching data.
  displayName: PostgreSQL Shared Buffers
  name: POSTGRESQL_SHARED_BUFFERS
- description: Database user name
  displayName: Database Username
  from: user[a-zA-Z0-9]{3}
  generate: expression
  name: DB_USERNAME
  required: true
- description: Database user password
  displayName: Database Password
  from: '[a-zA-Z0-9]{8}'
  generate: expression
  name: DB_PASSWORD
  required: true
- description: Size of persistent storage for database volume.
  displayName: Database Volume Capacity
  name: VOLUME_CAPACITY
  required: true
  value: 1Gi
- description: The name of the secret containing the keystore file
  displayName: JGroups Secret Name
  name: JGROUPS_ENCRYPT_SECRET
  value: sso-app-secret
- description: The name of the keystore file within the secret
  displayName: JGroups Keystore Filename
  name: JGROUPS_ENCRYPT_KEYSTORE
  value: jgroups.jceks
- description: The name associated with the server certificate (e.g. secret-key)
  displayName: JGroups Certificate Name
  name: JGROUPS_ENCRYPT_NAME
- description: The password for the keystore and certificate (e.g. password)
  displayName: JGroups Keystore Password
  name: JGROUPS_ENCRYPT_PASSWORD
- description: JGroups cluster password
  displayName: JGroups Cluster Password
  from: '[a-zA-Z0-9]{8}'
  generate: expression
  name: JGROUPS_CLUSTER_PASSWORD
  required: true
- description: Namespace in which the ImageStreams for Red Hat Middleware images are
    installed. These ImageStreams are normally installed in the openshift namespace.
    You should only need to modify this if you've installed the ImageStreams in a
    different namespace/project.
  displayName: ImageStream Namespace
  name: IMAGE_STREAM_NAMESPACE
  required: true
  value: openshift
- description: SSO Server admin username
  displayName: SSO Admin Username
  from: '[a-zA-Z0-9]{8}'
  generate: expression
  name: SSO_ADMIN_USERNAME
  required: true
- description: SSO Server admin  password
  displayName: SSO Admin Password
  from: '[a-zA-Z0-9]{8}'
  generate: expression
  name: SSO_ADMIN_PASSWORD
  required: true
- description: Realm to be created in the SSO server (e.g. demo).
  displayName: SSO Realm
  name: SSO_REALM
- description: The username used to access the SSO service.  This is used by clients
    to create the appliction client(s) within the specified SSO realm.
  displayName: SSO Service Username
  name: SSO_SERVICE_USERNAME
- description: The password for the SSO service user.
  displayName: SSO Service Password
  name: SSO_SERVICE_PASSWORD
- description: The name of the truststore file within the secret (e.g. truststore.jks)
  displayName: SSO Trust Store
  name: SSO_TRUSTSTORE
- description: The password for the truststore and certificate (e.g. mykeystorepass)
  displayName: SSO Trust Store Password
  name: SSO_TRUSTSTORE_PASSWORD
- description: The name of the secret containing the truststore file (e.g. truststore-secret).
    Used for volume secretName
  displayName: SSO Trust Store Secret
  name: SSO_TRUSTSTORE_SECRET
  value: sso-app-secret
- description: The tag to use for the "postgresql" image stream.  Typically, this
    aligns with the major.minor version of PostgreSQL.
  displayName: PostgreSQL Image Stream Tag
  name: POSTGRESQL_IMAGE_STREAM_TAG
  required: true
  value: "9.5"
- description: Container memory limit
  name: MEMORY_LIMIT
  value: 1Gi

My final template is named sso-template.yml. Take a look at the differences. I ran into a problem having some of these postgres values blank. I plan to check on that with the development team. Postgres sets the defaults just fine, but if the variables are present with blank values, it has a problem. One further change I made was to remove the default non-SSL deployment options. I want SSL only and I want to enforce that by default. I tried leaving the basic HTTP variables blank, but that ended up causing failures.

$ diff base-template.yml sso-template.yml
36,51d35
<       description: The web server's http port.
<       service.alpha.openshift.io/dependencies: '[{"name": "${APPLICATION_NAME}-postgresql",
<         "kind": "Service"}]'
<     labels:
<       application: ${APPLICATION_NAME}
<     name: ${APPLICATION_NAME}
<   spec:
<     ports:
<     - port: 8080
<       targetPort: 8080
<     selector:
<       deploymentConfig: ${APPLICATION_NAME}
< - apiVersion: v1
<   kind: Service
<   metadata:
<     annotations:
95,107d78
<   id: ${APPLICATION_NAME}-http
<   kind: Route
<   metadata:
<     annotations:
<       description: Route for application's http service.
<     labels:
<       application: ${APPLICATION_NAME}
<     name: ${APPLICATION_NAME}
<   spec:
<     host: ${HOSTNAME_HTTP}
<     to:
<       name: ${APPLICATION_NAME}
< - apiVersion: v1
219,221d189
<           - containerPort: 8080
<             name: http
<             protocol: TCP
296,301d263
<           - name: POSTGRESQL_MAX_CONNECTIONS
<             value: ${POSTGRESQL_MAX_CONNECTIONS}
<           - name: POSTGRESQL_MAX_PREPARED_TRANSACTIONS
<             value: ${POSTGRESQL_MAX_CONNECTIONS}
<           - name: POSTGRESQL_SHARED_BUFFERS
<             value: ${POSTGRESQL_SHARED_BUFFERS}
360,363d321
< - description: 'Custom hostname for http service route.  Leave blank for default hostname,
<     e.g.: <application-name>.<project>.<default-domain-suffix>'
<   displayName: Custom http Route Hostname
<   name: HOSTNAME_HTTP

The magic now is in the parameter file. You could put all that junk on the command line, but why? This solution is so much more elegant:

$ cat sso-params
HOSTNAME_HTTPS=sso.apps.example.com
HTTPS_NAME=sso
HTTPS_PASSWORD=redacted
DB_USERNAME=ssoapp <!--(1)-->
DB_PASSWORD=redacted
DB_DATABASE=keycloak
JGROUPS_ENCRYPT_NAME=jgroups
JGROUPS_ENCRYPT_PASSWORD=redacted
JGROUPS_CLUSTER_PASSWORD=redacted
SSO_ADMIN_USERNAME=admin
SSO_ADMIN_PASSWORD=redacted
SSO_SERVICE_USERNAME=eap-mgmt-user
SSO_SERVICE_PASSWORD=redacted
SSO_REALM=ocp
  1. Note that I used a hyphen in the username originally as sso-app. Perhaps some of you may be wiser to postgres and already know that it does not like hyphens in usernames. Alas I wasted some time figuring this out.

Before creating everything in OpenShift with the template and parameters, you need to create the secrets it will use first. RH-SSO requires a keystore for the SSL cert and for the Jgroups cluster communication.

$ keytool -genkeypair -alias sso -keyalg RSA -keystore keystore.jks -storepass redacted -keypass redacted --dname "CN=sso.apps.example.com,OU=openshift,O=example.com,L=City,S=ST,C=US"

Submit your certificate to a Certificate Authority (CA) if you’d like to make it valid to the outside world (i.e. not self-signed). I skipped this step for the moment just to get things working. Import the signed certificate, along with any intermediate certificates.

$ keytool -certreq -keyalg RSA -alias sso -keystore keystore.jks -file sso.csr
$ keytool -import -keystore server.keystore -alias intermediateCA -file intermediate.ca
$ keytool -import -alias jboss -keystore server.keystore -file server.crt

Export a self-signed certificate from the keystore. You may need this later.

$ keytool -export -alias sso -keystore keystore.jks -file keystore.crt -storepass redacted

Convert the DER encoded certificate to PEM:

$ openssl x509 -in keystore.crt -inform der -outform pem -out keystore.pem

Convert that to a oneliner because it’s easier to add it to your OpenShift config that way:

$ awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' keystore.pem > keystore-one.pem

Create a keystore for your jgroups cluster communication. This is for protecting the communication between nodes when scaled beyond one. Note that this keystore format is JCEKS, a requirement of jgroups.

$ keytool -genseckey -alias jgroups -storetype JCEKS -keystore jgroups.jceks -storepass redacted -keypass redacted

Create an OpenShift secret from both your java keystores.

$ oc secret new sso-app-secret keystore.jks jgroups.jceks
secret/sso-app-secret

Now finally create everything else in OpenShift:

$ oc process -f sso-template.yml --param-file sso-params | oc apply -f-
service "secure-sso" created
service "sso-postgresql" created
service "sso-ping" created
route "secure-sso" created
deploymentconfig "sso" created
deploymentconfig "sso-postgresql" created
persistentvolumeclaim "sso-postgresql-claim" created

Be patient as the postgres database must come up before SSO initializes correctly. At this time, the template expects that the SSO deployment may fail and restart until the postgres database service is available. You may see the below error on the SSO node:

    Caused by: java.net.NoRouteToHostException: No route to host (Host unreachable)

Postgres had me concerned at first too, with regards to persistent volume claims. But after a few more minutes, it got things right.

$ oc get events | grep -i warn
1m         2m          9         sso-1-4xd92   Pod       spec.containers{sso}   Warning   Unhealthy               kubelet, infra1.example.internal   Readiness probe failed: {
1m        1m        3         sso-1-4xd92   Pod       spec.containers{sso}   Warning   Unhealthy   kubelet, infra1.example.internal   Liveness probe failed: {
35s       35s       1         sso-postgresql-1-b84rl   Pod                                                       Warning   FailedMount             kubelet, infra1.example.internal   Unable to mount volumes for pod "sso-postgresql-1-b84rl_sso-test(c4fb9a8c-5382-11e8-b8b5-001a4a16015f)": timeout expired waiting for volumes to attach/mount for pod "sso-test"/"sso-postgresql-1-b84rl". list of unattached/unmounted volumes=[sso-postgresql-pvol]
8s        8s        1         sso-postgresql-1-b84rl   Pod                     spec.containers{sso-postgresql}   Warning   Unhealthy               kubelet, infra1.example.internal   Readiness probe failed: sh: cannot set terminal process group (-1): Inappropriate ioctl for device
[bward@gauss sso]$ oc get pvc
NAME                   STATUS    VOLUME                                     CAPACITY   ACCESSMODES   STORAGECLASS   AGE
sso-postgresql-claim   Bound     pvc-c260f6eb-5382-11e8-9603-001a4a160161   1Gi        RWO           standard       3m
[bward@gauss sso]$ oc get pv
NAME                                       CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                                   STORAGECLASS   REASON    AGE
...
pvc-c260f6eb-5382-11e8-9603-001a4a160161   1Gi        RWO           Delete          Bound     sso-test/sso-postgresql-claim           standard                 3m
...

When everything is fine and dandy, you will see this on the SSO host:

18:09:03,656 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Red Hat Single Sign-On 7.2.1.GA (WildFly Core 3.0.12.Final-redhat-1) started in 81679ms - Started 661 of 1061 services (761 services are lazy, passive or on-demand)

You should also see similar output here:

$ oc get all
NAME                REVISION   DESIRED   CURRENT   TRIGGERED BY
dc/sso              1          1         1         config,image(redhat-sso72-openshift:1.0)
dc/sso-postgresql   1          1         1         config,image(postgresql:9.5)

NAME                  DESIRED   CURRENT   READY     AGE
rc/sso-1              1         1         1         7m
rc/sso-postgresql-1   1         1         1         7m

NAME                HOST/PORT                    PATH      SERVICES     PORT      TERMINATION   WILDCARD
routes/secure-sso   sso.apps.example.com             secure-sso   <all>     passthrough   None

NAME                                         CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
svc/glusterfs-dynamic-sso-postgresql-claim   172.30.232.47   <none>        1/TCP      7m
svc/secure-sso                               172.30.172.60   <none>        8443/TCP   7m
svc/sso-ping                                 None            <none>        8888/TCP   7m
svc/sso-postgresql                           172.30.16.62    <none>        5432/TCP   7m

NAME                        READY     STATUS    RESTARTS   AGE
po/sso-1-wm2wx              1/1       Running   3          7m
po/sso-postgresql-1-c26cm   1/1       Running   1          7m

Notice above that it took 3 restarts of SSO before the postgres pod came up correctly.

TODO: add more notes here

Debugging:

[standalone@localhost:9990 /] /subsystem=logging/logger=org.keycloak:add
{"outcome" => "success"}
[standalone@localhost:9990 /] /subsystem=logging/logger=org.keycloak:write-attribute(name=level,value=DEBUG)
{"outcome" => "success"}
[standalone@localhost:9990 /] /subsystem=logging/logger=org.keycloak:read-resource(recursive=true)
{
    "outcome" => "success",
    "result" => {
        "category" => "org.keycloak",
        "filter" => undefined,
        "filter-spec" => undefined,
        "handlers" => undefined,
        "level" => "DEBUG",
        "use-parent-handlers" => true
    }
}