HOWTO: CMK with OIDC authentication

Disclaimer
This How-To is a “Work-in-Progress”, and as far as i am now its Single-Site, configured with CRE v.2.1.0p20 (current now is 2.1.0p24, configuration has not broken after i upgraded to it) on a RockyLinux 9.1 box.

Update
In my PoC env i have added a 2nd site, with a different set of OIDC client&secret.
I have not met issues, but have not finished testing it.

This is a proof of concept, providing a basic way of federative authentication via OpenID-Connect, and should (for now) not be used on production sites !

Base condition:

  • A working / configured site on the box.
  • wget command available on the box.

Prerequisites:

  • Get mod_auth_oidc rpm (current available version)onto the box (in a directory you prefer) , as root
    wget https://github.com/zmartzone/mod_auth_openidc/releases/download/v2.4.12.3/mod_auth_openidc-2.4.12.3-1.el9.x86_64.rpm
  • install the rpm (as root)
    rpm -Uvh mod_auth_openidc-2.4.12.3-1.el9.x86_64.rpm

Configuration:

  • Change to / su to your omd site.
    omd su yoursitename

  • Edit etc/apache/conf.d/auth.conf
    – Add/edit the following settings, just below the ServerName directive, the complete auth.conf example is below.

# Set this to the Name of your Checkmk site, e.g.# Define SITE mysite
# Define SITE mysite

Define SITE my-cmk-monitoring-site-name

# ServerName from listen-ports.conf needs to be overwritten here
# and being set to the URL of the real server.

ServerName https://my-monitoring-host.my-domain-name.tld

# Load the OIDC module.
<IfModule !mod_auth_openidc.c>

        LoadModule auth_openidc_module /usr/lib64/httpd/modules/mod_auth_openidc.so

</IfModule>

# (Mandatory)
# URL where OpenID Connect Provider metadata can be found (e.g. https://accounts.google.com/.well-known/openid-configuration)
# The obtained metadata will be cached and refreshed every 24 hours.
# If set, individual entries below will not have to be configured but can be used to add
# extra entries/endpoints to settings obtained from the metadata.
# If OIDCProviderMetadataURL is not set, the entries below it will have to be configured for a single
# static OP configuration or OIDCMetadataDir will have to be set for configuration of multiple OPs.
#OIDCProviderMetadataURL <url>
# if your IDP does not have a configuration-endpoint then you will have to configure them manually as 
# described in the module's configuration-example
# https://github.com/zmartzone/mod_auth_openidc/blob/master/auth_openidc.conf

OIDCProviderMetadataURL https://your.IDP.domain.tld/.well-known/openid-configuration-endpoint

# Your OIDC Client ID - as provided by your IDP-Administrator
OIDCClientID Your_OIDC_Client_ID_pasted_HERE

# Your OIDC Client Secret - as provided by your IDP-Administrator
OIDCClientSecret Your_OIDC_Client_Secret_pasted_HERE

# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content
OIDCRedirectURI https://your-cmk-host.domain.tld/${SITE}/secure/redirect_uri

# (Mandatory)
# Set a password for crypto purposes, this is used for:
# - encryption of the (temporary) state cookie
# - encryption of cache entries, that may include the session cookie, see: OIDCCacheEncrypt and OIDCSessionType
# Note that an encrypted cache mechanism can be shared between servers if they use the same OIDCCryptoPassphrase
# If the value begins with exec: the resulting command will be executed and the
# first line returned to standard output by the program will be used as the password, e.g:
#    OIDCCryptoPassphrase "exec:/bin/bash -c \"head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32\""
# (notice that the above typically only works in non-clustered environments)
# The command may be absolute or relative to the web server root.
#OIDCCryptoPassphrase [ [passphrase] | "exec:/path/to/otherProgram arg1" ]
OIDCCryptoPassphrase "exec:/bin/bash -c \"head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32\"
"
# Define the OpenID Connect scope that is requested from the OP (eg. "openid email profile").
# When not defined, the bare minimal scope "openid" is used.
# NB: multiple scope values must be enclosed in a single pair of double quotes
# NB: this can be overridden on a per-OP basis in the .conf file using the key: scope
#OIDCScope "<scope(s)-separated-by-spaces-and-enclosed-in-double-quotes>"
#
# CMK-specific - as CMK in this configuration is only looking for the username, and this is available thru the profile scope ( mapped from either nickname or preferred_username).
OIDCScope "openid profile"

# Set REMOTE_USER to the IDP-sent attribute nickname (part of the profile scope).
# this is the value that mod_authnz_ldap leverages as the first parameter after basedn.
# in the example below, REMOTE_USER = nickname = username attribute in LDAP
# transmitted in the profile scope as nickname.
# Valid other attribute would be preferred_username, depending on your IDP's config.
OIDCRemoteUserClaim nickname

# Define the X-Forwarded-* or Forwarded headers that will be taken into account as set by a reverse proxy
# in front of mod_auth_openidc. Must be one or more of:
#  X-Forwarded-Host
#  X-Forwarded-Port
#  X-Forwarded-Proto
#  Forwarded
# When not defined, such headers will be ignored.
#OIDCXForwardedHeaders <header>+
#
# CMK-specific, if you forget to add this, then you will end up redirected at port 5000 , which will produce an error, stripping the port off the url you *will* however be logged in and see your dashboard without additional authentication.
OIDCXForwardedHeaders X-Forwarded-Host

<Location /${SITE}>

        # Use OpenID-Connect auth only in case there is no Checkmk authentication
        # Cookie provided by the user and whitelist also some other required URLs.

    <If "! %{HTTP_COOKIE} =~ /^auth_${SITE}/ && \
        ! %{REQUEST_URI} = '/${SITE}/check_mk/register_agent.py' && \
        ! %{REQUEST_URI} = '/${SITE}/check_mk/run_cron.py' && \
        ! %{REQUEST_URI} = '/${SITE}/check_mk/deploy_agent.py' && \
        ! %{REQUEST_URI} = '/${SITE}/check_mk/restapi.py' && \
        ! %{REQUEST_URI} -strmatch '/${SITE}/check_mk/api/*' && \
        ! %{QUERY_STRING} =~ /(_secret=|auth_|register_agent)/ && \
                ! %{REQUEST_URI} =~ m#^/${SITE}/(omd/|check_mk/((images|themes)/.*\.(png|svg)|login\.py|.*\.(css|js)))# ">

                Order allow,deny
                Allow from all

                AuthType openid-connect
                require valid-user

              RequestHeader set X-Remote-User "expr=%{REMOTE_USER}"

        # When OpenID-Connect auth fails, show the login page to the user. This should only happen,
        # if e.g. the mellon cookie is lost/rejected or if the IDP is misconfigured.
        # A failed login at the IDP will not return you here at all.
            ErrorDocument 401 '<html> \
              <head> \
                <meta http-equiv="refresh" content="1; URL=/${SITE}/check_mk/login.py"> \
              </head> \
              <body> \
                OIDC authentication failed, redirecting to login page. \
                <a href="/${SITE}/check_mk/login.py">Click here</a>. \
              </body> \
            </html>'
            </if>
    # This header is also needed after authentication (outside of the If clause)
    RequestHeader set X-Remote-User "expr=%{REMOTE_USER}"

</Location>

CMK web interface config:

  • You now have to activate under Setup > General > Global Settings > User Interface > Authenticate users by incoming HTTP requests at Current settings the Activate HTTP header authentication option.

Restart your CMK site after making these configuration-changes

  • omd restart

Remember, to make this work the user authentication needs to be an existing Local user in CMK.

2 Likes

Additional to add to the config(if needed):

  • if you need to pull a specific (named) authentication-contract from your IDP add this.
# (Optional)
# Extra parameters that will be sent along with the Authorization Request.
# These must be URL-query-encoded as in: "display=popup&prompt=consent" or
# specific for Google's implementation: "approval_prompt=force".
# This is used against a statically configured (single) OP or serves as the default for discovered OPs.
# As an alternative to this option, one may choose to add the parameters as
# part of the URL set in OIDCProviderAuthorizationEndpoint or "authorization_endpoint"
# in the .provider metadata (though that would not work with Discovery OPs).
#
# Since version 2.3.11rc1 one can pass on query parameters from the request to the authorization
# request by adding e.g. "foo=#" which which will dynamically pull in the query parameter value
# from the request query parameter and add it to the authentication request to the OP.
#
# The default is to not add extra parameters.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: auth_request_params

# For this PoC the configuration-item is used to request a specific authentication-contract.
# In sending a/the acr-property, with the value of a/the URI of a configured authentication-contract
# on the IDP the specifics of this authentication-contract will be honored.
# NB: if this is required the specifics will be provided by your IDP-Administrator.
OIDCAuthRequestParams acr=Your_defined contact-URI or name on the IDP

This will request the specified authentication-contract to be used.
If it does not exist, then the IDP will fall back to the default contract which is specified on the IDP.
(usually this is a username/password contract)

This is the equivalent in OIDC of the SAML parameter to specify the Authentication-context.

  • Glowsome
1 Like

Hi Glowsome,

Seems that i have a working Azure AD OpenID connection to my host/apache.

The problem i am currently facing seems to be with the internal redirects, when i write on the browser:
https://example.com/monitoring/check_mk/ i will be automatically redirected to https://example.com/monitoring/check_mk/login.py?_origtarget=index.py without being asked for the login mask.

In Case i write any location after /check_mk/123example i will be redirected to the login mask and the whole token process finishes correctly and I get the not found error.

currently using the following location directive:

<Location /${SITE}>

        # Use OpenID-Connect auth only in case there is no Checkmk authentication
        # Cookie provided by the user and whitelist also some other required URLs.

    <If "! %{HTTP_COOKIE} =~ /^auth_${SITE}/ && \
        ! %{REQUEST_URI} = '/${SITE}/check_mk/register_agent.py' && \
        ! %{REQUEST_URI} = '/${SITE}/check_mk/run_cron.py' && \
        ! %{REQUEST_URI} = '/${SITE}/check_mk/deploy_agent.py' && \
        ! %{REQUEST_URI} = '/${SITE}/check_mk/restapi.py' && \
        ! %{REQUEST_URI} -strmatch '/${SITE}/check_mk/api/*' && \
        ! %{QUERY_STRING} =~ /(_secret=|auth_|register_agent)/ && \
        ! %{REQUEST_URI} =~ m#^/${SITE}/(omd/|check_mk/((images|themes)/.*\.(png|svg)|login\.py|.*\.(css|js)))# ">

                Order allow,deny
                Allow from all

                AuthType openid-connect
                require valid-user
</If>
</Location>

Any idea what needs to be adapted?

Thanks,
Fred

@techtuga

Seems i cut the confoguration-block short in my initial conf … so i posted it completely.

apart from the idp-data it should be a 1:1 usable copy.

Also when trying … always use an incognito/private browser session to avoid strange behaviour with cookies etc.

  • Glowsome

@Glowsome

perfectly working as mentioned. what a journey to get here :smiley:
many many thanks :beer:

Fred

Hi Glowsome,

If it happens that you are logged out you get to the normal login window.
Would be good to have a second login button with the option to login with SSO and maybe disabling the option for the normal login completely.

Br,
Fred

Hi @techtuga

As this whole way of setting/making OIDC happens is like a shell around CMK, and CMK is by itself not aware of the solution it will by default falback to its native login.

Untested:
In the auth.conf there is a redirect when authentication fails, or the login token expires.

 # When OpenID-Connect auth fails, show the login page to the user. This should only happen,
        # if e.g. the cookie is lost/rejected or if the IDP is misconfigured.
        # A failed login at the IDP will not return you here at all.
            ErrorDocument 401 '<html> \
              <head> \
                <meta http-equiv="refresh" content="1; URL=/${SITE}/check_mk/login.py"> \
              </head> \
              <body> \
                OIDC authentication failed, redirecting to login page. \
                <a href="/${SITE}/check_mk/login.py">Click here</a>. \
              </body> \
            </html>'

Maybe by changing that url to point again to the ${SITE}/Check_mk instead of login.py we close the loop.

  • Glowsome