SSO integration with Keycloak (vanilla)
This is an extension of the base setup presented in the step by step guide. Make sure you have it running before reading further (unless you just want to look at how to integrate Keycloak with ACS component without trying it on your local machine).
Architecture of the deployment
The following components are deployed by the example chart:
flowchart TB
classDef alf fill:#0c0
classDef thrdP fill:#ffa
subgraph legend
t[3rd party component]:::thrdP
a[Alfresco component]:::alf
end
repodb[(Repository\nDatabase)]:::thrdP
amq{{Message\nBroker}}:::thrdP
repo[Alfresco\nRepository]:::alf
keycloak[Identity\nProvider]:::thrdP
share(Alfreso\nShare UI):::alf
adf(Alfreso\nContent App):::alf
repo ==> amq
repo ==> repodb
share --> repo
adf --> repo
repo --> keycloak
share --> keycloak
adf --> keycloak
Keycloak
As of Alfresco 23.1 Alfresco Identity Service is not required anymore. It is possible to use a vanilla Keyloak distribution. In this document we will use the Codecentric Keyloak chart so we can reuse similar patterns of integration we’ve been using earlier.
Declaring the dependency
Add below lines in Chart.yaml
dependencies:
- name: keycloakx
repository: https://codecentric.github.io/helm-charts
version: 2.3.0
Configure Keycloak
If you want to apply custom configuration as you build your own charts, refer to the chart README.
In this example we’ll start by simply adding the basic configuration in the values.yaml
file:
keycloakx:
nameOverride: keycloak
command:
- /opt/keycloak/bin/kc.sh
- start
- --http-enabled=true
- --http-port=8080
- --hostname-strict=false
- --hostname-strict-https=false
- --import-realm
http:
relativePath: /auth # keycloak http api will be available under this path
ingress:
enabled: true # enabled external access the keycloak
tls: [] # disable https for this example
rules:
- host: >-
{{ template "alfresco-common.external.host" $ }} # external hostname
paths:
- path: "{{ .Values.http.relativePath }}"
pathType: Prefix
extraEnvFrom: |
- configMapRef:
name: keycloak
extraEnv: |
- name: JAVA_OPTS_APPEND
value: >-
-Djgroups.dns.query={{ include "keycloak.fullname" . }}-headless
And create a configmap for basic configuration:
apiVersion: v1
kind: ConfigMap
metadata:
name: keycloak
labels: {{- include "acs-sso-example.labels" . | nindent 4 }}
data:
KC_HOSTNAME: {{ template "alfresco-common.external.host" . }}
Again here some named templates provided in the alfresco-common chart might come handy to avoid duplicating values.
This will start a keycloak instance with basic parameters and only a master realm. To make it usable we need to give it an admin username & password and also a realm Alfresco applications will be configured in as client(s).
Keycloak admin
Admin credentials can be passed as a kubernetes secret. Here we will create this secret from the umbrella chart values which we’ll add as shown below in the same values.yaml
file:
keycloakx:
nameOverride: keycloak
admin:
# -- Keycloak admin username
username: admin
# -- Keycloak admin password.
password: null # autogenerate
Now, let’s create the secret we’ll use to pass to the keycloakx chart in templates/secret-idp.yaml
:
{{- if empty (lookup "v1" "Secret" $.Release.Namespace "keycloak") }}
apiVersion: v1
kind: Secret
metadata:
name: keycloak
labels: {{- include "acs-sso-example.labels" . | nindent 4 }}
annotations:
"helm.sh/resource-policy": keep
data:
{{- with .Values.keycloakx }}
KEYCLOAK_ADMIN: {{ .admin.username | default "admin" | b64enc | quote }}
KEYCLOAK_ADMIN_PASSWORD: >-
{{ (.admin.password | default (randAscii 16)) | b64enc }}
{{- end }}
{{- end }}
Alfresco realm
In order to create a realm we need to:
- provide the json definition of the realm
- mount it in the
/opt/keycloak/data/import
folder in the pod - ensure keycloak is started with the
--import
switch.
Again, in order to provide the realm definition we’ll use values in the umbrella chart values.yaml
:
keycloakx:
extraEnvFrom: |
- secretRef:
name: keycloak
realm:
# -- Alfresco Realm definition
- id: alfresco
realm: alfresco
enabled: true
sslRequired: none
loginTheme: alfresco
clients:
- clientId: alfresco
enabled: true
standardFlowEnabled: true
implicitFlowEnabled: true
publicClient: true
redirectUris: >-
{{- $redirectUris := list }}
{{- range (index (include "alfresco-common.known.urls" $ | mustFromJson) "known_urls") }}
{{- $redirectUris = append $redirectUris (printf "%s/*" .) }}
{{- end }}
{{- $redirectUris }}
webOrigins: >-
{{ index (include "alfresco-common.known.urls" $ | mustFromJson) "known_urls" }}
users:
- # -- default Alfresco admin user
username: admin
enabled: true
credentials:
- type: password
# -- default Alfresco admin password
value: secret
internationalizationEnabled: true
defaultLocale: en
supportedLocales:
- "ca"
- "de"
- "en"
- "es"
- "fr"
- "it"
- "ja"
- "lt"
- "nl"
- "no"
- "pt-BR"
- "ru"
- "sv"
- "zh-CN"
And a new secret in templates/secret-idp-realm.yaml
:
apiVersion: v1
kind: Secret
metadata:
name: keycloak-realm
labels: {{- include "acs-sso-example.labels" . | nindent 4 }}
data:
{{- with .Values.keycloakx.admin.realm }}
{{- range . }}
{{- range .clients }}
{{- $_ := set . "redirectUris" (tpl .redirectUris $ | list) }}
{{- $_ := set . "webOrigins" (tpl .webOrigins $ | list) }}
{{- end }}
{{- printf "%s.json" .id | nindent 2 }}: {{ mustToJson . | b64enc | quote }}
{{- end }}
{{- end }}
Alfresco theme
Importing a theme requires adding the theme’s code to the /opt/keycloak/themes
directory. Configmap or Secrets are not well suited for that as this is a whole directory structure we need to mount and also because such resources are also limited in size. A good alternative is to create an ephemeral volume and use an init container and use it to populate the ephemeral volume the main container of the keycloak will use later on in the pod’s lifecycle. All of this is done using values in the values.yaml
file as shown below:
# Ephemeral volume
extraVolumes: |
- name: theme
emptyDir: {}
# volume mount for the main container
extraVolumeMounts: |
- name: theme
mountPath: /opt/keycloak/themes
extraInitContainers: |
- image: busybox:1.36
imagePullPolicy: IfNotPresent
name: theme-fetcher
command: [sh]
args:
- -c
- |
wget https://github.com/Alfresco/alfresco-keycloak-theme/releases/download/0.3.5/alfresco-keycloak-theme-0.3.5.zip -O alfresco.zip
unzip -d /themes alfresco.zip
volumeMounts:
- name: theme
mountPath: /themes
Alfresco repository SSO configuration
Now let’s amend the ACS config to enable SSO. Well do it using a feature of the alfresco-repository chart which allow us use a configmap as the alfresco-global.properties
file.
The configmap needs to contains what you put in the alfresco-global.properties
file and you can use templating to populate it. E.g. in templates/configmap-repo.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: repository-properties
labels: {{- include "acs-sso-example.labels" . | nindent 4 }}
data:
alfresco-global.properties: |
authentication.chain = identity-service1:identity-service,alfrescoNtlm1:alfrescoNtlm
identity-service.authentication.enabled = true
identity-service.realm = alfresco
{{- $kCtx := dict "Values" .Values.keycloakx "Chart" .Chart "Release" .Release }}
identity-service.auth-server-url = http://{{ include "keycloak.fullname" $kCtx }}-http{{ .Values.keycloakx.http.relativePath }}
identity-service.enable-basic-auth = true
identity-service.realm
needs to match the name of the realm defined earlier. To get the right identity-service.auth-server-url
we are computing the context of the keycloakx subcharts in $kCtx
(using nameOverride
) and pass that context to the same templating code used in the subchart to give the service a name. This is because here you here we’re using localhost as a domain, but if you use a true DNS domain the repo could point tpo this instead (which you can set in known_urls
and use alfresco-common.external.url
).
Then in the values.yaml
file add below configuration to alfresco-repository
:
alfresco-repository:
configuration:
repository:
existingConfigMap: repository-properties
Alfresco Share
For Share the approach is very similar.
Declaring Share dependency
In Chart.yaml
- name: alfresco-share
repository: https://alfresco.github.io/alfresco-helm-charts/
version: 0.3.0
Configuring Alfresco Share
In values .yaml
we add the required config as per the share chart doc:
alfresco-share:
nameOverride: alfresco-share
image:
repository: alfresco/alfresco-share
tag: 23.2.0-A13
repository:
existingConfigMap:
name: share-repository
extraVolumes:
- name: share-properties
configMap:
name: share-properties
extraVolumeMounts:
- name: share-properties
mountPath: >-
/usr/local/tomcat/webapps/share/WEB-INF/classes/share-config.properties
subPath: share.properties
ingress:
hosts:
- host: localhost
paths:
- path: /share
pathType: Prefix
Then we proceed in creating the few configmaps we need.
The first one to tell Share where the repo is in templates/configmap-share.yaml
:
apiVersion: v1
kind: ConfigMap
metadata:
name: share-repository
labels: {{- include "acs-sso-example.labels" . | nindent 4 }}
data:
{{- with (index .Values "alfresco-repository") }}
{{- $repoCtx := dict "Values" . "Chart" $.Chart "Release" $.Release }}
{{- $reposvc := .service | default dict }}
REPO_HOST: {{ template "alfresco-repository.fullname" $repoCtx }}
REPO_PORT: {{ $reposvc.port | default 80 | quote }}
{{- end }}
And the second one to hold the SSO config in templates/configmap-share-properties.yaml
:
apiVersion: v1
kind: ConfigMap
metadata:
name: share-properties
labels: {{- include "acs-sso-example.labels" . | nindent 4 }}
data:
share.properties: |
aims.enabled = true
{{- with .Values.keycloakx }}
{{- $kCtx := dict "Values" . "Chart" $.Chart "Release" $.Release }}
aims.realm = {{ index .admin.realm 0 "realm" }}
aims.resource = {{ index .admin.realm 0 "clients" 0 "clientId" }}
aims.publicClient=true
aims.principalAttribute=sub
aims.authServerUrl = {{ printf "http://%s-http%s" (include "keycloak.fullname" $kCtx) .http.relativePath }}
{{- end }}
Alfresco Content App
Declaring Content dependency
In `Chart.yaml:
- name: common
alias: alfresco-content-app
repository: https://activiti.github.io/activiti-cloud-helm-charts
version: 8.2.0
Configuring Alfresco Content App
The content-app basic and SSO configuration sits only in the values.yaml
file. Pay special attention to providing OAUTH2 urls that match your realm configuration (realm name & client id).
alfresco-content-app:
nameOverride: alfresco-content-app
enabled: true
service:
envType: frontend
ingress:
ingressClassName: nginx
hostName: localhost
path: /workspace
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 5g
nginx.ingress.kubernetes.io/proxy-buffer-size: 8k
tls: []
image:
repository: alfresco/alfresco-content-app
tag: 4.3.0
pullPolicy: IfNotPresent
env:
APP_CONFIG_PROVIDER: ECM
APP_CONFIG_AUTH_TYPE: OAUTH
APP_CONFIG_OAUTH2_HOST: "{protocol}//{hostname}{:port}/auth/realms/alfresco"
APP_CONFIG_OAUTH2_CLIENTID: alfresco
APP_CONFIG_OAUTH2_REDIRECT_SILENT_IFRAME_URI: "{protocol}//{hostname}{:port}/assets/silent-refresh.html"
securityContext:
runAsNonRoot: true
runAsUser: 101
capabilities:
drop:
- NET_RAW
- ALL
resources:
requests:
cpu: "0.25"
memory: "256Mi"
limits:
cpu: "1"
memory: "1024Mi"
Deployment
We can now build and deploy the chart:
helm dep build # pull dependencies
helm install --generate-name --atomic .