Skip to main content
Version: Next

Security configuration

Jadice flow supports different modes of web security: none, basic and oauth2. Lets first look at the components:

  • jf-controller: The base rest controller which handles JobRequest requests
  • jf-controller-ui: The vaadin UI. Sends request to controller via java client.
  • flow java client - for source scanner or other external services (and UI)

The different security configurations are applied via application.yml / values.yml. Lets now look at each component in detail.

jf-controller

security modes​

No security mode​

No security mode can be used in secure environments where no ports are exposed or for testing.

application.yml:

security.mode: none

HTTP Basic authentication​

Basic authentication can be enabled in the jf controller. It will use pre-defined users from configuration. To enable it, add this to the application.yml:

security.mode: basic

local-user-management:
users:
- name: admin
password: "{noop}password"
roles:
- ADMIN

This will add the "admin/password" user. More users can be added to this users list if desired. The password can be encoded. See Spring Password storage Documentation for more infos of supported algorithms.

OAuth2 Authentication​

See OAuth requirements section for configuring the users in your keycloak instance.

To enable OAuth in controller, activate it by providing:

security.mode: oauth

spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: jadice-flow-controller
authorization-grant-type: jwt-bearer
client-authentication-method: none
scope: openid, profile, email
provider:
keycloak:
# Attention: If using using an old keycloak, it might have "/auth/" in the path.
# In newer keycloak versions, the url starts at "/realms/"
issuer-uri: https://my-OldVersion-keycloak/auth/realms/jadice-flow
resourceserver:
jwt:
issuer-uri: ${spring.security.oauth2.client.provider.keycloak.issuer-uri}

Hint If any security mode like BASIC or OAUTH is enabled, the spring boot actuator might also be affected from spring security (so users have to log in or have a token to access it). In some cases, it is desired to have the actuator public available, or with basic auth (e.g. if it runs on management port anyway).

Separate Actuator security configuration​

The controller supports this by adding:

# none (=default), basic, oauth
jadice-flow.actuator.security.mode: none


# If basic auth or oauth is set on actuator, the following flag allows anonymous access to /actuator/prometheus only
prometheus.anonymousAccess: false

If prometheus.anonymousAccess is enabled, the prometheus endpoint is then accessible without authentication regardless of other endpoints of the actuator.

jf-controller-ui

security modes​

No security mode​

No security mode can be used in secure environments where no ports are exposed or for testing.

application.yml:

security.mode: none

HTTP Basic authentication​

Basic authentication can be enabled in the jf controller UI. It will use pre-defined users from configuration. To enable it, add this to the application.yml:

security.mode: basic-vaadin

local-user-management:
users:
- name: admin
password: "{noop}password"
roles:
- ADMIN

Users will login to the UI via an internal login page.

OAuth2 authentication​

See the Keycloak configuration section (jadice-flow-ui client) for configuring the client in your keycloak instance.

How the login works​

The controller UI is a server-side Vaadin Flow application. With security.mode: oauth-vaadin, the login is handled by Spring Security (oauth2Login) on the server, following the "backend for frontend" (BFF) pattern:

  1. The user opens the UI and is redirected to the keycloak login page (OpenID Connect authorization code flow).
  2. After login, keycloak redirects the browser back to the UI with a short-lived authorization code.
  3. The UI server exchanges this code for the tokens directly at the keycloak token endpoint (server-to-server).
  4. Access, refresh and ID token are kept on the server only (server-side session / memory of the UI).

The browser only holds the session cookie (JSESSIONID). Tokens are never handed out to the browser and there is no JavaScript OIDC library involved. The UI forwards the access token of the logged in user to the controller (token relay), so the controller sees the actual user and not a technical account.

Because the token exchange happens on the server, the UI can and should authenticate itself at keycloak with a client secret (confidential client). This is the recommended configuration:

security.mode: oauth-vaadin

spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: jadice-flow-ui
# Do not put the client secret into the application.yml / values.yaml (and do not set client-secret here).
# Spring binds it from the environment variable SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_CLIENTSECRET (see below)
client-authentication-method: client_secret_basic
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: openid, profile, email
provider:
keycloak:
# Attention: If an old keycloak with "/auth/" in the path is used.
# In newer keycloak versions, the url starts at "/realms/"
issuer-uri: https://<keycloak-host>/realms/jadice-flow

The client secret is read from the environment variable SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_CLIENTSECRET (Spring Boot relaxed binding of spring.security.oauth2.client.registration.keycloak.client-secret: dots become underscores, dashes are removed). In kubernetes, store the secret in a kubernetes Secret.

Since jf-controller-ui helm chart version <next version>, the chart handles the client secret and sets the environment variable above via secretKeyRef. There are two options:

  • Let the chart create the secret: set secrets.oauth.clientSecret. The chart creates the secret jf-controller-ui-oauth-secret with the key client-secret (as SealedSecret if secrets.useSealedSecrets: true). Pass the value at install time (e.g. --set secrets.oauth.clientSecret=<client-secret>) instead of storing it in a values file.
  • Use an existing secret: set auth.existingSecrets.oauth (or global.jadiceFlow.existingSecrets.oauth) to the name of a secret that contains the key client-secret, e.g. created with:
kubectl create secret generic jadice-flow-ui-oauth --from-literal=client-secret=<client-secret>

For compatibility reasons, the chart defaults register a public client (client-authentication-method: none). To use the confidential client, set client-authentication-method: client_secret_basic and provide the secret as described above. If a confidential client is configured and no secret is provided, helm install aborts with an error message.

security:
mode: oauth-vaadin

auth:
existingSecrets:
oauth: jadice-flow-ui-oauth

spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: jadice-flow-ui
client-authentication-method: client_secret_basic
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: openid, profile, email
provider:
keycloak:
issuer-uri: https://<keycloak-host>/realms/jadice-flow

When TLS is terminated at the ingress / load balancer, make sure the UI builds {baseUrl} with https, otherwise the redirect URI does not match the one registered in keycloak. This can be achieved with server.forward-headers-strategy: framework (requires the ingress to send X-Forwarded-* headers).

Public client (alternative)​

If a client secret cannot be used, the UI can also be registered as public client. In this case, the client does not authenticate itself at the token endpoint and the authorization code is only protected by PKCE (see below):

spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: jadice-flow-ui
client-authentication-method: none
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: openid, profile, email

Note that also with a public client, the code exchange is done by the UI server and the tokens stay in the server-side session. The confidential client is still preferred, because it binds the token exchange to a secret only the UI server knows.

PKCE​

Since jadice flow 1.28 (Spring Boot 4 / Spring Security 7), the UI always uses PKCE (Proof Key for Code Exchange) with the S256 code challenge method - for public and confidential clients. No configuration is required and there is no property to disable it.

It is recommended to also enforce PKCE in keycloak, so that authorization requests without a code challenge are rejected: In the jadice-flow-ui client, open the tab Advanced and set Proof Key for Code Exchange Code Challenge Method to S256.

Note When using "oauth-vaadin" mode, the current oauth token of the logged in user will also be used as token provider, if oauth is enabled in the client configuration.

Additional reverse proxy authentication (oauth2-proxy)​

Optionally, an authenticating reverse proxy like oauth2-proxy can be placed in front of the UI as an additional layer, e.g. to block unauthenticated requests before they reach the application. With ingress-nginx, this can be configured via defaultIngress.additionalAnnotations of the helm chart:

defaultIngress:
additionalAnnotations:
nginx.ingress.kubernetes.io/auth-url: "https://<oauth2-proxy-host>/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://<oauth2-proxy-host>/oauth2/start?rd=$scheme://$host$escaped_request_uri"

This does not replace security.mode: oauth-vaadin: the UI needs its own OAuth2 login to obtain the user token, which it forwards to the controller (token relay). Without it, requests to the controller cannot be made in the name of the logged in user.

flow java client (spring)

The flow java client is also used in the UI. So this part is also relevant for:

  • jf-controller-ui
  • jf-source-scanner

Java clients are mostly background services so they use a service account.

In flow, we can set these client configurations.

none​

No security mode configuration:

application.yml:

jadice-flow.client.security.mode=none

Basic auth​

Basic authentication will use the configured user to access the server.

application.yml:

jadice-flow.client.security.mode: basic
jadice-flow.client.basic-auth.username: admin
jadice-flow.client.basic-auth.password: password

in theory, it is possible to configure the UI to use oauth, and the controller to use basic auth. In such case, the above configuration can be used to authenticate via basic auth at the controller.

OAuth​

OAuth2 can be enabled with the following configuration:

application.yml:

# OAuth Client config to access controller:
# Usually the same. But you can also use oauth on client here and basic auth above for UI in theory for example
jadice-flow.client.security.mode: oauth

spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: jadice-flow-sa
authorization-grant-type: client_credentials
client-authentication-method: client_secret_basic
# Placeholder - provide the secret via environment variable / kubernetes secret, not in plain text
client-secret: ${SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_CLIENTSECRET}
scope: openid, profile, email
provider:
keycloak:
# Attention: If an old keycloak with "/auth/" in the path is used.
# In newer keycloak versions, the url starts at "/realms/"
issuer-uri: https://<keycloak-host>/realms/jadice-flow

# Token retrieval of the service account (used by the flow java client in non-UI components, e.g. source scanner)
OAuth2Config:
resourceName: ${spring.security.oauth2.client.registration.keycloak.client-id}
token-url: ${spring.security.oauth2.client.provider.keycloak.issuer-uri}/protocol/openid-connect/token
client-secret: ${spring.security.oauth2.client.registration.keycloak.client-secret}

This configuration will use the jadice-flow-sa (service account) user with the given client secret. The client_credentials grant does not involve a browser, so no redirect-uri is required.

In non-UI components, the service account token is requested by the flow java client using the OAuth2Config properties (client id and secret are sent as form parameters to the keycloak token endpoint). In the controller UI with security.mode: oauth-vaadin, the token of the logged in user is used instead (see OAuth2 authentication).

Hint In theory its possible to enable basic auth in the UI, but oauth on controller. In such case, the above config could be used to use the service account user for oauth authentication. Preferred is to use same authentication method on all sides.

Keycloak configuration

Lastly, lets look how to initially setup keycloak...

When using oauth, a keycloak server can be used to act as OAuth resource server. It can also be configured to proxy LDAP users in the background. See the keycloak documentation for details.

Here we will focus on the required / recommended default users.

Create a realm for jadice flow, e.g. "jadice-flow".

We generally need these 3 users:

jf-controller​

Controller uses bearer-only and requires token to be present.

  • client-id: jadice-flow-controller
  • client protocol: openid-connect
  • access type: bearer-only

jadice-flow-ui​

UI client for interactive login (recommended settings, confidential client):

  • client-id: jadice-flow-ui
  • client protocol / type: openid-connect
  • client authentication: ON (confidential client; older keycloak versions: access type confidential)
  • standard flow: ON
  • implicit flow: OFF
  • direct access grants: OFF
  • service accounts: OFF
  • Advanced → Proof Key for Code Exchange Code Challenge Method: S256 (see PKCE)

Credentials tab: Shows the client secret, which has to be provided to the UI (see Confidential client).

If the UI is configured as public client instead, set client authentication to OFF (access type public). All other settings stay the same.

Also important: Set the valid redirect URIs, otherwise the login cannot redirect back.

  • Valid redirect URIs: the exact redirect URI of the UI, e.g. https://<ui-host>/login/oauth2/code/keycloak. Do not use wildcards (*) in production. For local testing, e.g. http://localhost:8090/login/oauth2/code/keycloak.
  • Valid post logout redirect URIs: the base URL of the UI, e.g. https://<ui-host>/ (the UI redirects there after logout).
  • Web origins: the origin of the UI, e.g. https://<ui-host>.

Also keep an eye on HTTP vs HTTPS. If SSL is enabled (or TLS is terminated at the ingress), https has to be used in all of these URIs.

jadice-flow-sa​

For example this "jadice-flow-sa" (service account) user. It is also possible to give each background components (like source scanner etc) their own service account.

  • client-id: jadice-flow-sa
  • client protocol / type: openid-connect
  • client authentication: ON (older keycloak versions: access type confidential)
  • standard flow: OFF
  • implicit flow: OFF
  • direct access grants: OFF
  • service accounts: ON

Credentials tab: Shows client secret to use in configuration.

Security recommendations

  • Client secrets belong into a kubernetes Secret (or an external secret store), never into a ConfigMap, values.yaml or the git repository. Provide them to the application via environment variables.
  • Rotate client secrets regularly and after staff changes or suspected leaks (keycloak: Credentials tab → Regenerate), then update the kubernetes secret and restart the affected pods.
  • Use TLS for all redirect URIs, the keycloak issuer URI and the UI itself. Plain HTTP is only acceptable for local tests.
  • Grant each service account only the roles it needs, and preferably use one service account per component.

These measures also support typical requirements of information security management (e.g. ISO 27001) and NIS2 regarding access control, cryptography and secret handling.

SSL / TLS Configuration

This section describes how to setup SSL in a local / all-in-one environment. For kubernetes, this is usually already done by the load balancer. But if for example certbot shall be used, certificate+privKey File can be imported.

For test environments, a self-signed certificate can be generated.

Enable SSL with default SSL settings. Keystore will be created if not present.

The following SSL configuration can be enabled in controller and / or controller-ui.

UI config (will autoImport generated cert of controller):

# Default SSL settings
server:
ssl:
key-store-type: PKCS12
key-store: ${user.dir}/jf-keystore.p12
key-alias: jf-keystore
key-store-password: jf-keystore-pw-42

# Auto-cert SSL settings
# keystore-init can generate a self signed cert and / or import SSL certificates from trusted environments into the truststore.
#
keystore-init:
enabled: true
forceTruststoreImport: true
forceKeystoreImport: false
# You can provide an external cert and private key file here. If empty, a self-signed certificate will be generated
#certFile:
#privKeyFile:
generatedCertCNname: localhost
generatedCertDomainName: localhost
# Mainly for secure / trusted environments; e.g. we need to import the generated cert of controller into the UI
autoImportURL: https://localhost:8080

(Server/controller side config is the same, but without autoImport).

Keystore-init has these properties:

  • enabled : To generally enable / disable this functionality
  • forceTruststoreImport : Whether to force importing autoImportURLs on every start. If false (=default), cert will only be loaded if invalid / not existent. Otherwise loaded on each startup.
  • forceKeystoreImport : Whether to force importing certFile and privKeyFile on every start. If false (=default), cert will only be loaded if invalid / not existent. Otherwise loaded on each startup.
  • certFile : Certificate file to import (leave empty for self signed generation)
  • privKeyFile : Private key file to import (leave empty for self signed generation)
  • generatedCertCNname : common name (CN) of the certificate
  • generatedCertDomainName : domain name of the certificate
  • autoImportURL : Preferred is to provide a keystore externally. But in autoImportURL, we can trust any SSL connection and load the cert into the truststore. Add URLs to be trusted automatically. (dashed list below entry)