OpenShift 4 with Kerberos authentication (Request Header)

Why This Article

In An Enterprise Company

Authentication

Authorization

Audit

Kerberos

Apache httpd with Kerberos

request Header

The Architecture

WorkFlow Design

Getting Things Done

Creating a Project

# mkdir openshift-rh
# cd openshift-rh/
# oc create ns openshift-request-header
# oc label namespace openshift-request-header "openshift.io/cluster-monitoring=true"
# oc patch ns/openshift-request-header --type merge --patch '{"metadata":{"annotations":{"openshift.io/node-selector":"node-role.kubernetes.io/infra="}}}'
# oc project openshift-request-header

Service and Route

# mkdir YAML 
# cd YAML/
# cat > rh-service.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: httpd-rh-svc
labels:
app: httpd-rh-proxy
namespace: openshift-request-header
spec:
ports:
- name: login
port: 8443
selector:
app: httpd-rh-proxy
EOF
# cat > rh-route.yaml << EOF
apiVersion: route.openshift.io/v1
kind: Route
metadata:
labels:
app: httpd-rh-proxy
name: rh-proxy
namespace: openshift-request-header
spec:
port:
targetPort: login
to:
kind: Service
name: httpd-rh-svc
tls:
termination: passthrough
EOF
# oc apply -f rh-service.yaml -f rh-route.yaml

TLS Certificates

# cd ..
# mkdir TLS
# cd TLS
# cat > openssl_config.cnf << EOF
[ server_auth ]
basicConstraints=CA:FALSE
nsCertType = server
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
SUBJECT_ALTER_NAME=
[ client_auth ]
basicConstraints=CA:FALSE
nsCertType = client, email
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = cRLSign, keyCertSign
[ req ]
default_bits = 2048
prompt = no
default_md = sha512
distinguished_name = dn
attributes = req_attributes
x509_extensions = v3_ca
[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20
unstructuredName = An optional company name
[ dn ]
C=US
ST=New York
L=New York
O=MyOrg
OU=MyOU
emailAddress=me@working.me
CN = server.example.com
EOF
# openssl genrsa -out rootCA.key 4096
# openssl req -x509 -new -nodes -key rootCA.key -sha512 -days 3655 -out rootCA.crt -extensions v3_ca -config openssl_config.cnf

Server Auth

# ROUTER_FQDN=$(oc get route rh-proxy -o jsonpath='{.spec.host}')
# cat > server_csr.txt << EOF[req]
default_bits = 2048
prompt = no
default_md = sha512
req_extensions = req_ext
distinguished_name = dn
[ dn ]C=US
ST=New York
L=New York
O=MyOrg
OU=MyOrgUnit
emailAddress=me@working.me
CN = ${ROUTER_FQDN}
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = ${ROUTER_FQDN}
EOF
# openssl genrsa -out server.key 4096
# openssl req -new -key server.key -out server.csr -config <(cat server_csr.txt)
# export SAN="DNS:${ROUTER_FQDN}"
# sed -i 's/SUBJECT_ALTER_NAME=/subjectAltName=${ENV::SAN}/g' openssl_config.cnf
# openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 1024 -sha256 -extfile openssl_config.cnf -extensions server_auth
# openssl x509 -in server.crt -noout -text | grep DNS

Client Auth

# openssl genrsa -out client.key 4096
# openssl req -new -sha256 -key client.key \
-subj "/O=system:authenticated/CN=kube-proxy" \
-out client.csr
# openssl x509 -req -in client.csr -CA rootCA.crt \
-CAkey rootCA.key -CAcreateserial -out client.crt \
-days 1024 -sha256 \
-extfile openssl_config.cnf -extensions client_auth
# cat > router-ca.crt << EOF
< Organization CA >
EOF
# oc get cm -n openshift-config-managed default-ingress-cert -o template='{{ index .data "ca-bundle.crt"}}' >> router-ca.crt
# cat rootCA.crt router-ca.crt > ultimateca.crt
# cat client.{crt,key} > client.pem

Kerberos

Custom Image

# cd .. && mkdir image && cd image/
# cat > run-httpd.sh << EOF
#!/bin/bash
if [ -z ${PROXY_ROUTE} ]; then
echo "Environment variable SERVER_FQDN undefined"
exit 1
elif [[ -z ${OAUTH_ROUTE} ]]; then
echo "Environment variable OUATH_FQDN undefined"
exit 1
elif [[ -z ${SSL_CERT_FILE} ]]; then
echo "Environment variable SSL_CERT_FILE undefined"
exit 1
elif [[ -z ${SSL_KEY_FILE} ]]; then
echo "Environment variable SSL_KEY_FILE undefined"
exit 1
elif [[ -z ${SSL_CA_FILE} ]]; then
echo "Environment variable SSL_CA_FILE undefined"
exit 1
elif [[ -z ${PROXY_CA_FILE} ]]; then
echo "Environment variable PROXY_CA_FILE undefined"
exit 1
elif [[ -z ${PROXY_CERT_FILE} ]]; then
echo "Environment variable PROXY_CERT_FILE undefined"
exit 1
fi
echo "
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule request_module modules/mod_request.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule headers_module modules/mod_headers.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule unixd_module modules/mod_unixd.so
User apache
Group apache
LogLevel trace5
Listen 8443
ServerName ${PROXY_ROUTE}
<VirtualHost *:8443>
" > /tmp/reverse.conf
echo '
CustomLog "logs/ssl_request_log" "%t U=%{SSL_CLIENT_SAN_OTHER_msUPN_0}x %h \"%r\" %b"
' >> /tmp/reverse.conf
echo "
SSLEngine on
SSLCertificateFile ${SSL_CERT_FILE}
SSLCertificateKeyFile ${SSL_KEY_FILE}
SSLCACertificateFile ${SSL_CA_FILE}

SSLProxyEngine on
SSLProxyCACertificateFile ${PROXY_CA_FILE}
# It is critical to enforce client certificates. Otherwise, requests can
# spoof the X-Remote-User header by accessing the /oauth/authorize endpoint
# directly.
SSLProxyMachineCertificateFile ${PROXY_CERT_FILE}
SSLProxyVerify none
RewriteEngine on
RewriteRule ^/challenges/oauth/authorize(.*) https://${OAUTH_ROUTE}/oauth/authorize$1 [P,L]
RewriteRule ^/web-login/oauth/authorize(.*) https://${OAUTH_ROUTE}/oauth/authorize$1 [P,L]
<Location /challenges/oauth/authorize>
AuthType Kerberos
AuthName "Kerberos Login"
KrbMethodNegotiate On
KrbAuthRealms EXAMPLE1.COM EXAMPLE2.COM
Krb5KeyTab /opt/app-root/rh-proxy.keytab
KrbDelegateBasic on
KrbSaveCredentials On
Require valid-user
RequestHeader set \"X-Remote-User\" %{REMOTE_USER}s
</Location>
<Location /web-login/oauth/authorize>
AuthType Kerberos
AuthName "Kerberos Login"
KrbMethodNegotiate On
KrbAuthRealms EXAMPLE1.COM EXAMPLE2.COM
Krb5KeyTab /opt/app-root/rh-proxy.keytab
KrbDelegateBasic on
KrbSaveCredentials On
Require valid-user
RequestHeader set \"X-Remote-User\" %{REMOTE_USER}s
ErrorDocument 401 '<html><body>Client certificate authentication failed</body></html>'
</Location>
</VirtualHost>
RequestHeader unset X-Remote-User
" >> /tmp/reverse.conf
mv /tmp/reverse.conf /opt/app-root/reverse.conf/usr/sbin/httpd -DFOREGROUND
EOF
# chmod a+x run-httpd.sh
# cat > Dockerfile << EOF
FROM centos
MAINTAINER Oren Oichman <Back to Root>
RUN yum module enable mod_auth_openidc -y && \
yum install -y httpd mod_auth_gssapi mod_session \
apr-util-openssl mod_auth_gssapi mod_ssl \
mod_auth_openidc openssl && \
yum clean all
COPY run-httpd.sh /usr/sbin/run-httpd.shRUN echo "PidFile /tmp/http.pid" >> /etc/httpd/conf/httpd.conf
RUN echo 'IncludeOptional /opt/app-root/*.conf' >> /etc/httpd/conf/httpd.conf
RUN sed -i "s/Listen\ 80/Listen\ 8080/g" /etc/httpd/conf/httpd.conf
RUN sed -i "s/\"logs\/error_log\"/\/dev\/stderr/g" /etc/httpd/conf/httpd.conf
RUN sed -i "s/CustomLog \"logs\/access_log\"/CustomLog \/dev\/stdout/g" /etc/httpd/conf/httpd.conf
RUN rm -f /etc/httpd/conf.d/ssl.conf
RUN mkdir /opt/app-root/ && chown apache:apache /opt/app-root/ && chmod 777 /opt/app-root/
USER apacheEXPOSE 8080 8443
ENTRYPOINT ["/usr/sbin/run-httpd.sh"]
EOF
# buildah bud -f Dockerfile -t <registry>/httpd-request-header
#buildah push <registry>/httpd-request-header

Deployment

# oc create secret tls server-tls --key=server.key --cert=server.crt 
# OAUTH_ROUTE=$(oc get route -n openshift-authentication oauth-openshift -o template='{{ .spec.host }}')
# PROXY_ROUTE=$(oc get route -n openshift-request-header rh-proxy -o jsonpath='{.spec.host }')
# oc create cm routes --from-literal=oauth_route="$OAUTH_ROUTE" --from-literal=proxy_route="$PROXY_ROUTE"
# oc create cm custom-ca --from-file=ca.crt=ultimateca.crt
(for the CA file)
# oc create cm proxy-auth --from-file=proxy-auth.crt=client.pem
# oc create cm krb5-conf --from-file=krb5.conf=../krb/krb5.conf
# oc create cm krb5-keytab --from-file=krb5.keytab=../krb/krb5.keytab
# cd .. && mkdir YAML && cd YAML
# cat > rh-daemonset.yaml << EOF
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: rh-proxy
labels:
app: httpd-rh-proxy
spec:
selector:
matchLabels:
app: httpd-rh-proxy
replicas: 3
template:
metadata:
labels:
app: httpd-rh-proxy
spec:
containers:
- name: httpd-rh
image: <your registry>/httpd-request-header:latest
ports:
- containerPort: 8443
protocol: TCP
volumeMounts:
- name: server-tls
mountPath: /opt/app-root/server/
- name: custom-ca
mountPath: /opt/app-root/ca/ca.crt
subPath: ca.crt
- name: proxy-auth
mountPath: /opt/app-root/proxyauth/proxy-auth.crt
subPath: proxy-auth.crt
- name: krb5-keytab
mountPath: /etc/krb5.keytab
subPath: krb5.keytab
- name: krb5-conf
mountPath: /etc/krb5.conf
subPath: krb5.conf
env:
- name: OAUTH_ROUTE
valueFrom:
configMapKeyRef:
name: routes
key: oauth_route
- name: PROXY_ROUTE
valueFrom:
configMapKeyRef:
name: routes
key: proxy_route
- name: SSL_CERT_FILE
value: /opt/app-root/server/tls.crt
- name: SSL_KEY_FILE
value: /opt/app-root/server/tls.key
- name: SSL_CA_FILE
value: /opt/app-root/ca/ca.crt
- name: PROXY_CA_FILE
value: /opt/app-root/ca/ca.crt
- name: PROXY_CERT_FILE
value: /opt/app-root/proxyauth/proxy-auth.crt
volumes:
- name: server-tls
secret:
secretName: server-tls
- name: custom-ca
configMap:
name: custom-ca
- name: proxy-auth
configMap:
name: proxy-auth
- name: krb5-conf
configMap:
name: krb5-conf
- name: krb5-keytab
configMap:
name: krb5-keytab
EOF
# oc apply -f rh-daemonset.yaml
# cat > oauth-cluster.yaml << EOF
apiVersion: config.openshift.io/v1
kind: OAuth
metadata:
name: cluster
spec:
identityProviders:
- name: requestHeadersIdP
type: RequestHeader
requestHeader:
ca:
name: request-header-ca
headers: ["X-Remote-User"]
loginURL: "https://${PROXY_ROUTE}/web-login/oauth/authorize?${query}"
challengeURL: "https://${PROXY_ROUTE}/challenges/oauth/authorize?${query}"
EOF
# oc apply -f oauth-cluster.yaml

Testing

# klist -e
# oc login

--

--

Open Source contributer for the past 15 years

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store