Domain and 2FA Authentication

Authentication with Active Directory

The AD configuration should be done in the /etc/logserver/properties.yml file.

Below is a list of settings to be made in the properties.yml file (the commented section in the file for the AD settings to start working, this fragment should be uncommented):

ldaps:
 - name: "example.com"  # domain that is configured
   host: "127.0.0.1,127.0.0.2"  # list of server for this domain
   #port: 389 # optional, default 389 for unencrypted session or 636 for encrypted sessions
   ssl_enabled: false  # optional, default true
   #ssl_trust_all_certs: true # optional, default false
   #ssl.keystore.file: "path" # path to the truststore store
   #ssl.keystore.password: "path" # password to the trusted certificate store
   bind_dn: "admin@example.com" # account name administrator
   bind_password: "password" # password for the administrator account
   search_user_base_DN: "OU=lab,DC=example,DC=com" # search for the DN user tree database
   #user_id_attribute: "uid # search for a user attribute optional, by default "uid"
   #search_groups_base_DN: "OU=lab,DC=example,DC=com" # group database search. This is a catalog main, after which the groups will be sought.
   #unique_member_attribute: "uniqueMember" # optional, default "uniqueMember"
   #max_connections: 10 # optional, default 10
   connection_timeout_in_sec: 10 # optional, default 1
   request_timeout_in_sec: 10 # optional, default 1
   cache_ttl_in_sec: 60 # optional, default 0 - cache disabled
   #authentication_only: true    # optional ignore role-mapping settings
   #default_authentication_roles: [ "roleName1", "roleName2" ]      # roles asigned to new users authenticating using this LDAP server, used when authentication_only = true

If we want to configure multiple domains, then in this configuration file we copy the # LDAP section below and configure it for the next domain.

Below is an example of how an entry for 2 domains should look like. (It is important to take the interpreter to read these values ​​correctly).

ldaps:
  - name: "example1.com" #DOMAIN 1
    host: "127.0.0.1,127.0.0.2"
    bind_dn: "esauth@example1.com"
    bind_password: "password"
    search_user_base_DN: "cn=Users,DC=example1,DC=com"
    ssl_enabled: false
  - name: "example2.com" #DOMAIN 2
    host: "127.0.0.1,127.0.0.2"
    bind_dn: "esauth@example2.com"
    bind_password: "password"
    search_user_base_DN: "cn=Users,DC=example2,DC=com"
    ssl_enabled: false

After completing the LDAP section entry in the properties.yml file, save the changes and reload the module with the below command:

curl -sS -uUSER:PASSWORD localhost:9200/_logserver/auth/reload -XPOST

Example:

curl -sS -ulogserver:logserver localhost:9200/_logserver/auth/reload -XPOST

Configure SSL support for AD authentication

Open the certificate manager on the AD server.

Select the certificate and open it

Select the option of copying to a file in the Details tab

Click the Next button

Keep the setting as shown below and click Next

Keep the setting as shown below and click Next.

Give the name a certificate

After the certificate is exported, this certificate should be imported into a trusted certificate file that will be used by the Data Node plugin.

To import a certificate into a trusted certificate file, a tool called „keytool.exe” is located in the JDK installation directory.

Use the following command to import a certificate file:

 keytool -import -alias adding_certificate_keystore -file certificate.cer -keystore certificatestore

The values certificate.cer and certificatestore should be changed accordingly.

By doing this, it will ask you to set a password for the trusted certificate store. Remember this password, because it must be set in the configuration of the Data Node plugin. The following settings must be set in the properties.yml configuration for SSL:

 ssl.keystore.file: "<path to the trust certificate store>"
 ssl.keystore.password: "<password to the trust certificate store>"

Role mapping

In the /etc/logserver/properties.yml configuration file you can find a section for configuring role mapping:

# LDAP ROLE MAPPING FILE
# rolemapping.file.path: /etc/logserver/role-mappings.yml

This variable points to the file /etc/logserver/role-mappings.yml Below is the sample content for this file:

# admin - superuser group
admin:
  - "CN=Admins,CN=Builtin,DC=example,DC=com"

gui-access:
  - "CN=Admins,CN=Builtin,DC=example,DC=com"

Attention. The role you define in the role-mapping.yml file must be created in the Energy Logserver.

How does the mapping mechanism work?
An AD user logs in to Energy Logserver. In the application, there is a gui-access role, which through the file role-mapping.yml binds to the name of an AD group of which the user is a member. Additionally, this AD group binds to the Energy Logserver admin role, which points to permission granted to the user in the application.

Similarly, the mechanism will work for any other role in the application. Important in this configuration is to add every AD group to gui-access which grants permission to log in and at least one more role which grants permission to some data.

The gui-access role is not visible in GUI, it’s only used to grant permission to log in.

If field authentication_only is true, user roles will not be mapped and they will be taken from the default_authentication_roles field. When the default_authentication_roles field is not added in properties.yml, a user without a role will be created.

Below is a screenshot of the console on which are marked accounts that were created by users logging in from AD

If you map roles from several domains, for example, dev.example1.com, and dev.example2.com then in the User List we will see which user from which domain with which role logged in Energy Logserver.

Password encryption

For security reasons, you can provide the encrypted password for Active Directory integration.
To do this use pass-encrypter.sh script that is located in the Utils directory in the installation folder.

  1. Installation of pass-encrypter

    cp -pr /installation_folder/logserver/pass-encrypter /usr/share/logserver/
    
  2. Use pass-encrypter

    /usr/share/logserver/utils/pass-encrypter/pass-encrypter.sh
    
    Enter the string for encryption :
    new_password
    Encrypted string : MTU1MTEwMDcxMzQzMg==1GEG8KUOgyJko0PuT2C4uw==
    

Authentication with Radius

To use the Radius protocol, install the latest available version of Energy Logserver.

Configuration

The default configuration file is located at /etc/logserver/properties.yml:

# Radius opts
#radius.host: "10.4.3.184"
#radius.secret: "querty1q2ww2q1"
#radius.port: 1812

Use appropriate secret based on config file in Radius server. The secret is configured on clients.conf in the Radius server.

In this case, since the plugin will try to do Radius auth, the client IP address should be the IP address where the Data Node is deployed.

Every user by default at present gets the admin role

Authentication with LDAP

To use OpenLDAP authorization, install or update Energy Logserver too at least 7.0.2.

LDAP Configuration

The default configuration file is located at /etc/logserver/properties.yml:

  • ldap_groups_search - Enable Open LDAP authorization. The ldap_groups_search switch with true/false values.

  • search filter - you can define search_filter for each domain. When polling the LDAP / AD server, the placeholder is changed to the userId (everything before @domain) of the user who is trying to log in. Sample search_filter:

    search_filter: "(&(objectClass=inetOrgPerson)(cn=%s))"
    

    If no search_filter is given, the default will be used:

    (&(&(objectCategory=Person)(objectClass=User))(samaccountname=%s))
    
  • max_connections - for each domain (must be> = 1), this is the maximum number of connections that will be created with the LDAP / AD server for a given domain. Initially, one connection is created, if necessary another, up to the maximum number of connections set. If max_connections is not given, the default value = 10 will be used.

  • ldap_groups_search - filter will be used to search groups on the AD / LDAP server to which the user is trying to log in. An example of groups_search_filter that works quite universally is:

    groups_search_filter: "(|(uniqueMember=%s)(member=%s))"
    

    Sample configuration:

    licenseFilePath: /usr/share/logserver/
    
    ldaps:
    
        - name: "dev.it.example.com"
          host: "192.168.0.1"
          port: 389                                                  # optional, default 389
          #ssl_enabled: false                                        # optional, default true
          #ssl_trust_all_certs: true                                 # optional, default false
          bind_dn: "Administrator@dev2.it.example.com"
          bind_password: "password"
          search_user_base_DN: "OU=lab,DC=dev,DC=it,DC=example,DC=pl"
          search_filter: "(&(objectClass=inetOrgperson)(cn=%s))"     # optional, default "(&(&(objectCategory=Person)(objectClass=User))(samaccountname=%s))"
          user_id_attribute: "uid"                                   # optional, default "uid"
          search_groups_base_DN: "OU=lab,DC=dev,DC=it,DC=example,DC=pl" # base DN, which will be used for searching user's groups in LDAP tree
          groups_search_filter: "(member=%s)"                        # optional, default (member=%s), if ldap_groups_search is set to true, this filter will be used for searching user's membership of LDAP groups
          ldap_groups_search: false                                  # optional, default false - user groups will be determined basing on user's memberOf attribute
          unique_member_attribute: "uniqueMember"                    # optional, default "uniqueMember"
          max_connections: 10                                        # optional, default 10
          connection_timeout_in_sec: 10                              # optional, default 1
          request_timeout_in_sec: 10                                 # optional, default 1
          cache_ttl_in_sec: 60                                       # optional, default 0 - cache disabled
    

    When the password is longer than 20 characters, we recommend using our pass-encrypter, otherwise, the backslash must be escaped with another backslash. Endpoint role-mapping/_reload has been changed to _role-mapping/reload. This is a unification of API conventions, following Data Node conventions.

Configuring Single Sign On (SSO)

To configure SSO, the system should be accessible by domain name URL, not IP address or localhost.

Correct: https://loggui.com:5601/login
Wrong: https://localhost:5601/login, https://10.0.10.120:5601/login

The configuration description described below is based on:

  • AD Domain / Realm: example.com

  • AD IP Address: 192.168.3.111

  • GUI Url: loggui.com

To enable SSO on your system, follow the steps below.

Configuration steps

  1. Create a User Account for Data Node auth plugin
    In this step, a Kerberos Principal representing the Data Node auth plugin is created on the Active Directory. The principal name would be name@EXAMPLE.COM, while the EXAMPLE.COM is the administrative name of the realm.

    Create a User in AD. Set “Account never expires” and enable support for Kerberos encryption as shown below.


  2. Define the Service Principal Name (SPN) and Create a keytab file for it

    Use the following command to create the keytab file identifying the SPN:

      C:> ktpass -out c:\Users\Administrator\esauth.keytab -princ HTTP/loggui.com@EXAMPLE.COM -mapUser esauth -mapOp set -pass 'Sprint$123' -crypto ALL -pType KRB5_NT_PRINCIPAL
    

    Details of the used switches:

    • -out - path to the keytab file

    • -mapUser - name of the previously created AD user. It might need to be preceded with pre-Windows 2000 logon if user cannot be found (e.g. EXAMPLE\esauth on the screenshot).

    • -princ - service principal name. Must start with uppercase HTTP/ and must end with uppercase domain after the @ sign. Will be used later to configure principal.

    • -pass - password that secures the keytab file itself (not connected user’s password!). Will be used later to configure principal’s password.

    For more details about the ktpass tool, please refer to the official documentation: ktpass details.

    The esauth.keytab file should be placed on your Data Node node - preferably /etc/logserver/ with read permissions for Data Node user:

    chmod 640 /etc/logserver/esauth.keytab
    chown user: /etc/logserver/esauth.keytab
    

  3. Create a file named krb5Login.conf:

      com.sun.security.jgss.initiate {
          com.sun.security.auth.module.Krb5LoginModule required
          principal="HTTP/loggui.com@EXAMPLE.COM"
          useKeyTab=true
          keyTab=/etc/logserver/esauth.keytab
          storeKey=true
          debug=true;
        };
      com.sun.security.jgss.krb5.accept {
        com.sun.security.auth.module.Krb5LoginModule required
        principal="HTTP/loggui.com@EXAMPLE.COM"
        useKeyTab=true
        keyTab=/etc/logserver/esauth.keytab
        storeKey=true
        debug=true;
      };
    

    The principal user and keyTab location should be changed as per the values created in Step 2. Make sure the domain is in UPPERCASE as shown above.
    The krb5Login.conf file should be placed on your Data Node node, for instance, /etc/logserver/ with read permissions for the Data Node user:

    sudo chmod 640 /etc/logserver/krb5Login.conf
    sudo chown user: /etc/logserver/krb5Login.conf
    

  4. Uncomment and edit JVM arguments, in /etc/logserver/jvm.options.d/single-sign-logon.options as shown below:

    -Dsun.security.krb5.debug=false \
    -Djava.security.krb5.realm=**EXAMPLE.COM** \
    -Djava.security.krb5.kdc=**192.168.3.111** \
    -Djava.security.auth.login.config=/etc/logserver/krb5Login.conf \
    -Djavax.security.auth.useSubjectCredsOnly=false
    

    Change the .krb5.realm and .krb5.kdc to the appropriate values. Realm is defined as used domain (must be in UPPERCASE) realm and .kdc is AD’s IP address. Those JVM arguments have to be set for the Logserver server.


  5. Authentication options if authentication_only: true is set

    If a user does not exist, Logserver will create the user without a role. Role in role-mapping.yml would be ignored and role gui-access from default_authentication_roles: ["gui-access"] will be assigned.

  6. Add the following additional (sso.domain, service_principal_name, service_principal_name_password) settings for LDAP in properties.yml file:

    sso.domain: "example.com"
    ldaps:
      - name: "example.com"
        host: "IP_address"
        port: 389                                      # optional, default 389
        ssl_enabled: false                             # optional, default true
        ssl_trust_all_certs: false                     # optional, default false
        bind_dn: "esauth@example.com"                  # optional, skip for anonymous bind
        bind_password: "password"                      # optional, skip for anonymous bind (AD admin's password)
        search_user_base_DN: "cn=Users,DC=example,DC=com"
        user_id_attribute: "uid"                       # optional, default "uid"
        unique_member_attribute: "uniqueMember"        # optional, default "uniqueMember"
        service_principal_name: "HTTP/loggui.com@EXAMPLE.COM" # principal name used while generating the keytab file
        service_principal_name_password: "Sprint$123"  # -pass used during keytab creation
    

    Note: At this moment, SSO works for only a single domain. So you have to mention for what domain SSO should work in the above property sso.domain - in this example it should be “example.com”.

  7. After completing the LDAP section entry in the properties.yml file, save the changes and send a request for reload authentication data with the command:

    curl -sS -u username:password localhost:9200/_logserver/auth/reload -XPOST
    

  8. Enable the SSO feature in the logserver-gui.yml file:

    login.sso_enabled: true
    

  9. After that Logserver GUI has to be restarted with systemctl restart command


Client (Browser) Configuration

Internet Explorer configuration

  1. Go to Internet Options from the Tools menu and click on Security Tab:

  2. Select Local intranet, click on Site -> Advanced -> Provide correct URL -> Click Add:

    After adding the site click close.

  3. Click on the custom level and select the option as shown below:

Chrome configuration

For Chrome, the settings are taken from the IE browser.

Firefox Configuration

Update the following config:

KDC error codes

KDC error codes Description
0 No error
1 Client entry is expired
2 Server entry is expired
3 Protocol version is not supported
4 Client key is encrypted in an old master key
5 Server key is encrypted in an old master key
6 Client is not defined in the security registry
7 Server is not defined in the security registry
8 Principal is not unique in the security registry
9 No key is available for the principal
10 Ticket is not eligible for postdating
11 Ticket is never valid
12 Request rejected due to KDC policy
13 Request option is not supported
14 Encryption type is not supported
15 Checksum type is not supported
16 Preauthentication type is not supported
17 Transited data type is not supported
18 Client account is revoked
19 Server account is revoked
20 TGT is revoked
21 Client account is not valid yet
22 Server account is not valid yet
23 Password is expired
24 Preauthentication failed
25 Preauthentication required
26 Supplied authentication ticket is not for the requested server
27 Server requires user-to-user protocol
31 Decryption integrity check failed
32 Ticket is expired
33 Ticket is not valid yet
34 Request is a replay of a previous request
35 Supplied authentication ticket is not for the current realm
36 Ticket and authenticator do not match
37 Clock skew is too great
38 Incorrect network address
39 Protocol version mismatch
40 Invalid message type
41 Message stream has been modified
42 Message is out of order
44 Key version is not available
45 Service key is not available
46 Mutual authentication failed
47 Incorrect message direction
48 Alternative authentication method required
49 Incorrect message sequence number
50 Inappropriate checksum type
60 Generic error detected
61 Field is too long
62 Client certificate is not acceptable
63 KDC certificate is not trusted or does not meet requirements
64 Certificate signature not valid
65 Client Diffie-Hellman key parameters not accepted
70 Client certificate could not be verified
71 Client certificate chain validation error occurred
72 Client certificate chain contains a revoked certificate
73 Revocation status for the certificate chain could not be determined
75 Kerberos client name does not match name bound to the client certificate
76 Kerberos KDC name does not match name bound to the KDC certificate
77 Key purpose restricts certificate usage
78 Certificate signature digest algorithm is not supported
79 PKAuthenticator is missing the required paChecksum
80 The signedData digest algorithm is not supported
81 The Public Key encryption delivery method is not supported

Two-Factor Authentication

Two-factor authentication (2FA) adds an additional verification layer on top of the standard Energy Logserver credentials. The two approaches below place NGINX in front of the Logserver-GUI (ELS Console, default port 5601) and delegate the second factor to an external mechanism, either an OAuth2 identity provider or a client X.509 certificate.

2FA with OAuth2 Proxy and Google (example)

This integration uses oauth2-proxy as an authentication gateway between NGINX and Google as the identity provider.

Software used:

NGINX configuration:

  1. Create /etc/nginx/conf.d/ng_oauth2_proxy.conf with the content below. The default server certificate and key generated by the Energy Logserver RPM are /etc/logserver-gui/ssl/logserver-gui.crt and /etc/logserver-gui/ssl/logserver-gui.key; replace logserver.org.crt/logserver.org.key accordingly or keep your organisation’s own certificate paths.

    server {
        listen 443 default ssl;
        server_name logserver.local;
        ssl_certificate /etc/logserver-gui/ssl/logserver-gui.crt;
        ssl_certificate_key /etc/logserver-gui/ssl/logserver-gui.key;
        ssl_session_cache   builtin:1000  shared:SSL:10m;
        add_header Strict-Transport-Security max-age=2592000;
    
      location /oauth2/ {
        proxy_pass       http://127.0.0.1:4180;
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
        proxy_set_header X-Auth-Request-Redirect $request_uri;
      }
      location = /oauth2/auth {
        proxy_pass       http://127.0.0.1:4180;
        proxy_set_header Host             $host;
        proxy_set_header X-Real-IP        $remote_addr;
        proxy_set_header X-Scheme         $scheme;
        proxy_set_header Content-Length   "";
        proxy_pass_request_body           off;
      }
    
      location / {
        auth_request /oauth2/auth;
        error_page 401 = /oauth2/sign_in;
    
        auth_request_set $user   $upstream_http_x_auth_request_user;
        auth_request_set $email  $upstream_http_x_auth_request_email;
        proxy_set_header X-User  $user;
        proxy_set_header X-Email $email;
    
        auth_request_set $token  $upstream_http_x_auth_request_access_token;
        proxy_set_header X-Access-Token $token;
    
        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header Set-Cookie $auth_cookie;
    
        auth_request_set $auth_cookie_name_upstream_1 $upstream_cookie_auth_cookie_name_1;
    
        if ($auth_cookie ~* "(; .*)") {
            set $auth_cookie_name_0 $auth_cookie;
            set $auth_cookie_name_1 "auth_cookie__oauth2_proxy_1=$auth_cookie_name_upstream_1$1";
        }
    
        if ($auth_cookie_name_upstream_1) {
            add_header Set-Cookie $auth_cookie_name_0;
            add_header Set-Cookie $auth_cookie_name_1;
        }
    
        proxy_pass https://127.0.0.1:5601;
      }
    }
    
  2. Set ssl_certificate and ssl_certificate_key to the certificate and key you intend to serve.

When SSL is terminated by NGINX, the Logserver-GUI can be started over HTTP. If encryption on the upstream is required, adjust proxy_pass accordingly.

oauth2-proxy configuration:

  1. Prepare oauth2_proxy.cfg.

  2. Create directories for the binary and configuration:

    mkdir -p /usr/share/oauth2_proxy/
    mkdir -p /etc/oauth2_proxy/
    
  3. Copy files into directories:

    cp oauth2_proxy /usr/share/oauth2_proxy/
    cp oauth2_proxy.cfg /etc/oauth2_proxy/
    
  4. Set directives according to the OAuth configuration created in the Google Cloud project:

    client_id =
    client_secret =
    # the following limits domains for authorization (* - all)
    email_domains = [
      "*"
    ]
    
  5. Set the public hostname:

    cookie_domain = "logserver-host.org"
    
  6. Optional, restrict login to a specific Google group:

Service start-up:

  • Start the NGINX service.

  • Start the oauth2-proxy service:

    /usr/share/oauth2_proxy/oauth2_proxy -config="/etc/oauth2_proxy/oauth2_proxy.cfg"
    

Open a browser and navigate to the Energy Logserver host address, the second factor is now enforced by Google before the request reaches the Console. The Console still presents its own login afterwards; the gateway adds a factor in front of it, it does not replace the Console login. See SSO gateway with Keycloak and oauth2-proxy for the same pattern with an OIDC provider and an explanation of that behaviour.

SSO gateway with Keycloak and oauth2-proxy

This integration places a gateway built from NGINX and oauth2-proxy in front of the Logserver-GUI (ELS Console) and enforces a Keycloak login, including any MFA the realm requires, before a request reaches the Console. It was built and verified end to end on a test server (Oracle Linux 9.7, ELS 8.x). The provider is OIDC, so the same steps apply to Entra, Okta, or any modern IdP after adjusting the issuer and client values.

What this gives you, and what it does not

The Console authenticates users only against its own backends: local accounts, LDAP/Active Directory, Radius, or Kerberos/SPNEGO. Its login plugin does not read an upstream identity from request headers, and it has no native OIDC or SAML support. There is no configuration switch to make it trust a proxy.

A gateway built from NGINX and oauth2-proxy therefore gives you:

  • A Keycloak login enforced before anyone reaches the Console. This is where MFA lives. Keycloak, or any IdP it federates, decides who passes.

  • One identity provider for the edge. oauth2-proxy speaks OIDC/OAuth2, so it links to Keycloak, Entra, Okta, and almost any modern IdP.

It does not give you a single login that flows into the Console session. After Keycloak authenticates the user, the Console still shows its own login screen, and the user authenticates a second time against the Console (the same Active Directory, but a separate prompt).

If the requirement is to force MFA in front of the Console, this is the right design. If the requirement is one login with no second prompt, use native Kerberos/SPNEGO instead, see Configuring Single Sign On (SSO). That path requires domain-joined workstations and an AD/KDC.

Architecture

Browser
  |
  |  https://els.example.com  (443)
  v
nginx  ----auth_request---->  oauth2-proxy (127.0.0.1:4180)
  |                                  |
  |                                  |  OIDC
  |                                  v
  |                            Keycloak  (login + MFA)
  |
  |  after the gateway authenticates, proxy_pass
  v
ELS Console (https://127.0.0.1:5601)  ->  still serves its own /login

NGINX owns port 443. Every request runs through an auth_request subrequest to oauth2-proxy. No valid session means a redirect to Keycloak; a valid session means NGINX forwards the request to the Console on 5601.

Prerequisites

  • A running node with the Console on port 5601 (HTTPS, self-signed by default).

  • Root access to that node.

  • Network reachability from the node to Keycloak, and from the user’s browser to both the node and Keycloak.

  • A DNS name or stable IP for the node. This procedure uses els.example.com; substitute your own.

  • From the Keycloak administrator: a realm and a confidential client (details in step 1).

1. Create the Keycloak client

Create a confidential client in the realm that holds the Console users.

  • Client ID: els-proxy (any name, keep it consistent below)

  • Client type: confidential (client authentication on)

  • Standard flow: enabled

  • Valid redirect URI: https://els.example.com/oauth2/callback

  • Web origin: https://els.example.com

Collect three values:

  • Issuer URL, for example https://keycloak.example.com/realms/corp. Confirm it by opening <issuer>/.well-known/openid-configuration and reading the issuer field; it must match exactly.

  • Client ID

  • Client secret

2. Free port 443 on the node

A default install publishes the Console on 443 with a firewalld forward that redirects 443 to 5601. That redirect runs in PREROUTING, so it captures external traffic before NGINX sees it. If you skip this step, the browser reaches the Console directly and the gateway never runs, while loopback tests still hit NGINX and look fine.

Check for the forward:

firewall-cmd --list-forward-ports

If you see port=443:proto=tcp:toport=5601:toaddr=, remove it:

firewall-cmd --permanent --remove-forward-port=port=443:proto=tcp:toport=5601
firewall-cmd --reload

The Console stays reachable directly on https://<node>:5601; port 443 now belongs to NGINX. To keep the Console on 443 untouched, run NGINX on another port (for example 8443) and adjust the redirect URI and redirect_url below accordingly. For background on this redirect, see GUI Port and HTTPS Redirect.

3. Install the packages

dnf install -y nginx

Install oauth2-proxy from its release page:

VER=v7.6.0
cd /tmp
curl -sL -o oauth2-proxy.tar.gz \
  https://github.com/oauth2-proxy/oauth2-proxy/releases/download/${VER}/oauth2-proxy-${VER}.linux-amd64.tar.gz
tar xzf oauth2-proxy.tar.gz
install oauth2-proxy-${VER}.linux-amd64/oauth2-proxy /usr/local/bin/
oauth2-proxy --version

4. TLS certificate for the gateway

Use the customer certificate for els.example.com in production. For a lab, generate a self-signed one:

mkdir -p /etc/nginx/els-proxy
openssl req -x509 -newkey rsa:2048 -nodes -days 825 \
  -keyout /etc/nginx/els-proxy/server.key \
  -out /etc/nginx/els-proxy/server.crt \
  -subj "/CN=els.example.com" \
  -addext "subjectAltName=DNS:els.example.com"

5. Configure oauth2-proxy

Generate a cookie secret:

head -c32 /dev/urandom | base64 | tr '+/' '-_' | cut -c1-43

Write /etc/oauth2-proxy/oauth2-proxy.cfg:

http_address = "127.0.0.1:4180"
reverse_proxy = true

provider        = "oidc"
oidc_issuer_url = "https://keycloak.example.com/realms/corp"
client_id       = "els-proxy"
client_secret   = "PASTE_CLIENT_SECRET"

redirect_url = "https://els.example.com/oauth2/callback"

email_domains = ["*"]
scope         = "openid email profile"

# expose identity headers to nginx
set_xauthrequest = true
pass_access_token = true
pass_user_headers = true

cookie_secret = "PASTE_COOKIE_SECRET"
cookie_secure = true
cookie_name   = "_els_oauth2"

# send the user straight to Keycloak instead of an intermediate button
skip_provider_button = true

Create the service /etc/systemd/system/oauth2-proxy.service:

[Unit]
Description=oauth2-proxy for the ELS Console
After=network.target

[Service]
ExecStart=/usr/local/bin/oauth2-proxy --config=/etc/oauth2-proxy/oauth2-proxy.cfg
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Start it:

systemctl daemon-reload
systemctl enable --now oauth2-proxy
journalctl -u oauth2-proxy -n 20 --no-pager

The log should report OAuthProxy configured for OpenID Connect Client ID: els-proxy. An error here usually means a wrong issuer URL or an unreachable Keycloak.

6. Configure NGINX

Write /etc/nginx/conf.d/els-proxy.conf:

server {
    listen 443 ssl;
    server_name els.example.com;
    access_log /var/log/nginx/els_access.log;

    ssl_certificate     /etc/nginx/els-proxy/server.crt;
    ssl_certificate_key /etc/nginx/els-proxy/server.key;

    # oauth2-proxy endpoints
    location /oauth2/ {
        proxy_pass       http://127.0.0.1:4180;
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
        proxy_set_header X-Auth-Request-Redirect $request_uri;
    }

    location = /oauth2/auth {
        proxy_pass       http://127.0.0.1:4180;
        proxy_set_header Host           $host;
        proxy_set_header X-Real-IP      $remote_addr;
        proxy_set_header X-Scheme       $scheme;
        proxy_set_header Content-Length "";
        proxy_pass_request_body         off;
    }

    # protect everything else, then forward to the Console
    location / {
        auth_request /oauth2/auth;
        error_page 401 = /oauth2/sign_in;

        auth_request_set $user  $upstream_http_x_auth_request_user;
        auth_request_set $email $upstream_http_x_auth_request_email;

        proxy_set_header X-User  $user;
        proxy_set_header X-Email $email;

        proxy_pass https://127.0.0.1:5601;
        proxy_ssl_verify off;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}

Allow NGINX to open outbound connections (SELinux blocks this by default), open the firewall, and start NGINX:

setsebool -P httpd_can_network_connect 1
firewall-cmd --add-port=443/tcp --permanent
firewall-cmd --reload
nginx -t
systemctl enable --now nginx

The X-User and X-Email headers are passed for completeness. The Console ignores them; leaving them in place does no harm and documents the intent.

Verification

Run these from a machine that can reach the node.

Confirm NGINX, not the Console, answers on 443. The certificate subject must be your gateway certificate, not CN=LOGSERVER:

echo | openssl s_client -connect els.example.com:443 -servername els.example.com 2>/dev/null \
  | openssl x509 -noout -subject

Confirm an unauthenticated request is sent to Keycloak:

curl -sk -D - -o /dev/null https://els.example.com/ | grep -i ^location
# expect: Location: https://keycloak.example.com/realms/corp/protocol/openid-connect/auth?...

Then open https://els.example.com/ in a browser. The expected sequence is the Keycloak login page (with MFA if the realm enforces it), and after login the Console login page. The second prompt is the documented limitation, not a fault. The NGINX access log shows the full chain:

GET /                       302   -> Keycloak
GET /oauth2/callback?code=  302   -> oauth2-proxy validated the token, set the session
GET /                       302   -> forwarded to the Console
GET /login                  200   -> Console serves its own login

Troubleshooting

  • The browser lands on the Console login directly and never sees Keycloak. The 443→5601 firewalld forward from step 2 is still active. Verify with firewall-cmd --list-forward-ports and remove it. A quick check: compare the TLS certificate from the browser side (CN=LOGSERVER means you are hitting the Console directly) against loopback on the node (CN=els.example.com means you reached NGINX).

  • oauth2-proxy fails to start with an issuer error. The oidc_issuer_url must match the issuer field in <issuer>/.well-known/openid-configuration character for character, including http versus https and any trailing path.

  • NGINX returns 500 on every request. The auth_request subrequest is failing. Check that oauth2-proxy is listening on 127.0.0.1:4180 and that setsebool -P httpd_can_network_connect 1 was applied.

  • Redirect loop after Keycloak login. The redirect URI in the Keycloak client does not match redirect_url in oauth2-proxy, or cookie_secure = true is set while the user reaches the site over plain HTTP. Align the URIs and keep the whole path on HTTPS.

2FA with Nginx and PKI client certificate

This integration places NGINX in front of the Logserver-GUI (ELS Console) and requires clients to present a valid X.509 certificate signed by a dedicated internal CA. On RHEL/Rocky/AlmaLinux the NGINX package from nginx.org runs under the nginx user and group.

1. Install NGINX

Follow the official NGINX installation guide: https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/

2. Create the client-certificate signing CA

Create a directory for the CA material:

cd /etc/nginx
mkdir CertificateAuthCA
cd CertificateAuthCA
chown root:nginx /etc/nginx/CertificateAuthCA/
chmod 770 /etc/nginx/CertificateAuthCA/

These permissions grant the user root (or the privileged user on your box) and the nginx group access. No one else should read this directory, it holds the root signing key.

Generate the CA private key. You will be prompted for a passphrase:

openssl genrsa -des3 -out myca.key 4096

Generate a CA certificate valid for 10 years (adjust as required; you will be prompted for CA attributes):

openssl req -new -x509 -days 3650 -key myca.key -out myca.crt

3. Create a client keypair

Repeat for each user: this can be scripted as part of user provisioning. You will be prompted for a passphrase, which will be distributed to the user together with the certificate.

Note

Never distribute the passphrase of the root CA private key from step 2.

openssl genrsa -des3 -out testuser.key 2048
openssl req -new -key testuser.key -out testuser.csr

Sign the client certificate with the CA. Valid for one year by default; increment the serial if you need to reissue:

openssl x509 -req -days 365 -in testuser.csr -CA myca.crt -CAkey myca.key -set_serial 01 -out testuser.crt

For Windows clients, combine the key material into a single PFX file (you will be prompted for the passphrase set above):

openssl pkcs12 -export -out testuser.pfx -inkey testuser.key -in testuser.crt -certfile myca.crt

Including the public portion of the CA in the PFX allows Windows to trust the internally signed CA.

4. Prepare the NGINX site certificate

Tighten directory permissions before generating site material:

chown -R root:nginx /etc/nginx/CertificateAuthCA
chmod 700 /etc/nginx/CertificateAuthCA

Generate the server key (you will be prompted for a passphrase and attributes):

openssl genrsa -out ./domain.com.key 2048

Create a CSR:

openssl req -new -sha256 -key ./domain.com.key -out ./domain.com.csr

Sign the server certificate:

openssl x509 -req -days 365 -in domain.com.csr -CA myca.crt -CAkey myca.key -set_serial 01 -out domain.com.crt

Remove the passphrase from the server key (you will be prompted for the passphrase set above):

openssl rsa -in domain.com.key -out domain.com.key.nopass

Create the NGINX sites-available directory and an empty configuration file:

cd /etc/nginx
mkdir sites-available
cd sites-available
touch proxy.conf

5. NGINX site configuration

Make sure firewalld is installed and enabled. Paste the following into proxy.conf:

server {
    listen 443;  ## The listen port must match the firewall rule.
    ssl on;
    server_name 192.168.3.87;  ## Your IP as server_name
    proxy_ssl_server_name on;
    ssl_certificate        /etc/nginx/CertificateAuthCA/domain.com.crt;        ## Server certificate
    ssl_certificate_key    /etc/nginx/CertificateAuthCA/domain.com.key.nopass; ## Server key (passphrase removed)
    ssl_client_certificate /etc/nginx/CertificateAuthCA/myca.crt;              ## CA certificate used to verify client certs

    ssl_verify_client on;

    ## Optionally capture error codes and redirect to a custom page:
    ## error_page 495 496 497 https://someerrorpage.yourdomain.com;

    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1.1 TLSv1.2;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK';

    keepalive_timeout 10;
    ssl_session_timeout 5m;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto https;
        proxy_pass http://localhost:5601/;   ## proxy_pass for Logserver-GUI (ELS Console)
    }
}

6. Enable the site in NGINX

nginx.conf loads modular configs from /etc/nginx/conf.d/*.conf. Create a symlink:

cd /etc/nginx
ln -s /etc/nginx/sites-available/proxy.conf /etc/nginx/conf.d/proxy.conf

7. Restart NGINX

systemctl restart nginx

8. Import the client certificate on a Windows machine

Double click the .pfx file and select Current User.

If the PFX has a passphrase, enter it here. Otherwise leave it blank and continue.

Add the target site to Trusted Sites in Internet Explorer. This allows the client certificate to be sent to the site (trusting it in Internet Explorer also trusts it in Chrome).

On the next visit the browser prompts to select a client certificate. Select the imported one and confirm.