Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance configuration of Keycloak valid redirects list in the canned deployment #3503

Merged
merged 5 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion server/pbenchinacan/etc/pbench-server/pbench-server.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ uri = postgresql://pbenchcontainer:pbench@localhost:5432/pbenchcontainer
secret-key = "pbench-in-a-can secret shhh"

[openid]
server_url = https://localhost:8090
server_url = ##KEYCLOAK_SERVER_URL##
realm = ##KEYCLOAK_REALM##
client = ##KEYCLOAK_CLIENT##

# Provide a CA cert for the pbenchinacan Keycloak server connection.
tls_ca_file = /etc/pki/tls/certs/pbench_CA.crt
Expand Down
34 changes: 23 additions & 11 deletions server/pbenchinacan/load_keycloak.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@

# The script defaults to using master realm username/password as admin/admin
# unless specified otherwise by 'ADMIN_USERNAME' and 'ADMIN_PASSWORD' env
# variables. The script also defaults the keycloak redirect URI as
# "https://localhost:8443/*" unless specified otherwise by 'KEYCLOAK_REDIRECT_URI'
# env variable.
# variables. The script constructs the Keycloak redirect URI list as
# "https://<node>:8443/*" from the space-separated list of nodes in
# the 'KEYCLOAK_REDIRECT_HOSTS' env variable, defaulting to 'localhost' plus
# whatever we can glean from the 'hostname' command. It adds to that list the
# value of 'KEYCLOAK_DEV_REDIRECT' which defaults to "http://localhost:3000/*".

KEYCLOAK_HOST_PORT=${KEYCLOAK_HOST_PORT:-"https://localhost:8090"}
KEYCLOAK_REDIRECT_URI=${KEYCLOAK_REDIRECT_URI:-"https://localhost:8443/*"}
KEYCLOAK_REDIRECT_HOSTS=${KEYCLOAK_REDIRECT_HOSTS:-"localhost $(hostname -I) $(hostname -A) $(hostname -f)"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dang: so "localhost 10.0.0.104 <IPv6> dbutenho-thinkpadp1gen4i.bos.csb dbutenho-thinkpadp1gen4i.bos.csb dbutenho-thinkpadp1gen4i.bos.csb" (that's my local router IPv4 since I'm not on VPN, and the csb FQDNs aren't routable, so the IPv6 is the only one I actually ought to avoid revealing on GitHub 😆 )

But, seriously, using hostname -f when you're using hostname -A seems pointless as the former is "the" FQDN while the latter is all FQDNs and therefore will always be a superset. (And, since I don't think any of our systems have separate FQDNs across NICs, probably still always a duplicate.)

Also, I'm not sure the redirect will take an undelimited IPv6 address, so your https://<IPv6>:8443 probably won't work and might generate an error in Keycloak. I suspect it'd need to be https://[<IPv6>]:8443 ... all of which makes this IP stuff a lot more complicated than it might seem at first.

I just think you're potentially asking for trouble here, and I'm not sure your result is really going to help anyone anyway since we're not using this in a real deployment anymore.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using hostname -f when you're using hostname -A seems pointless as the former is "the" FQDN while the latter is all FQDNs and therefore will always be a superset.

I agree...but, I'm pretty sure that I one point I found myself in a situation where the output of -f was not in the list from -A, so I included it out of an abundance of caution. (I assume that I was inside a container at the time.... 🦐)

However, if your goal is to avoid duplication, I think you're out of luck -- I just got this:

# hostname -A
8e10ccf8e78e 8e10ccf8e78e 

🤣

since I don't think any of our systems have separate FQDNs across NICs, probably still always a duplicate.

intlab-002 has two NICs, but the second one doesn't seem to have a FQDN (i.e., the reverse-lookup on its IPv4 address fails), but hostname -A there reports a duplication of the n002 name (in addition to the intlab-002 name), too.

I just think you're potentially asking for trouble here, and I'm not sure your result is really going to help anyone anyway since we're not using this in a real deployment anymore.

I may indeed be asking for trouble, since I don't seem to have a system with IPv6 configured that I can test on. However, the result helps me -- running with the browser on my laptop and the canned server on my VM in the IntLab, the Keycloak redirect has to be something other than localhost. So, the question is, how to get that "something", and I was hoping that hostname could provide the answer...but there is no good way to choose among (or even recognize) what it offers. 🙁

I'm not sure the redirect will take an undelimited IPv6 address

I just tried a manual test...and it did! 🥴

"redirectUris": ["http://localhost:3000/*", "https://fd00::948d:1dff:fe6c:fefe:8443/*"]

So, I guess it just does a dumb-ish pattern match on the backside.

But, you're absolutely right, the IPv6 addresses should be wrapped in square-brackets. 😝

Copy link
Member Author

@webbnh webbnh Aug 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, they are now wrapped. Assuming the tests pass, I don't suppose you'd be willing to try the script on your machine? (My machines don't speak IPv6 outside of containers, and my attempt to run jenkins/runlocal using jenkins/run built the RPM but then couldn't find it to build the container.... 🍤)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, if your goal is to avoid duplication, I think you're out of luck -- I just got this:

# hostname -A
8e10ccf8e78e 8e10ccf8e78e 

Wait; so where did it get a "hostname" like that? Even inside a container, I'm seeing host.containers.internal -- which is stupid, but arguably less stupid than yours. 🤔

rofl

since I don't think any of our systems have separate FQDNs across NICs, probably still always a duplicate.

intlab-002 has two NICs, but the second one doesn't seem to have a FQDN (i.e., the reverse-lookup on its IPv4 address fails), but hostname -A there reports a duplication of the n002 name (in addition to the intlab-002 name), too.

Interesting. I know the BOS lab used to have a high-speed internal subnet; and presumably the "second" proxy NIC would be on the internal net, which I don't think had DNS. RDU2 may well do something similar.

I just think you're potentially asking for trouble here, and I'm not sure your result is really going to help anyone anyway since we're not using this in a real deployment anymore.

I may indeed be asking for trouble, since I don't seem to have a system with IPv6 configured that I can test on. However, the result helps me -- running with the browser on my laptop and the canned server on my VM in the IntLab, the Keycloak redirect has to be something other than localhost. So, the question is, how to get that "something", and I was hoping that hostname could provide the answer...but there is no good way to choose among (or even recognize) what it offers. slightly_frowning_face

I really don't understand the logic of the CSB configuration. They have DHCP addresses, but apparently no DDNS names, and hostname generates only non-routable blather. Then again, VMs and containers generally aren't any better.

(Then again, on the staging VM, hostname -f gives "rhel9-pbenchstaging" while hostname -A gives the actual routable DDNS DHCP FQDN, so that's something.)

I'm not sure the redirect will take an undelimited IPv6 address

I just tried a manual test...and it did! woozy_face

"redirectUris": ["http://localhost:3000/*", "https://fd00::948d:1dff:fe6c:fefe:8443/*"]

So, I guess it just does a dumb-ish pattern match on the backside.

Leaving the obvious question of which form, if either, would match on a redirect.

But, you're absolutely right, the IPv6 addresses should be wrapped in square-brackets.

I pulled your branch and did a runlocal. I verified that the Keycloak has the (delimited) IPv6 address. (Actually, it's got both the link-local address and the routable address ... and while my first thought was that the link-local was silly as it's not routable off the local subnet, well, my laptop curiously enough is on its own local subnet...) Anyway, I can open a web page on either delimited IPv6 address copied from the redirects list, and the page loads and I can log in!

And, interestingly, while the CSB hostname isn't actually routable, I'd never had occasion to notice before that it actually works as a "localhost" alias on my laptop, so https://dbutenho-thinkpadp1gen4i.bos.csb:8443/dashboard/ loads (and logs in) just fine!

The upshot here is that every one of the redirect URLs you're programming on my laptop actually work to launch the dashboard and to log in: even the ridiculously silly ones. 😆

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addendum: on a whim, I also tried the local dashboard dev-mode to exercise the http://localhost:3000 redirect, although there was no reason to think it wouldn't still work. It does... but, feeling in an IPv6-ish mood, I tried connecting to IPv6 localhost [::], and that's not mapped in your cert (or probably in the allowed redirects), so it didn't work. Hmm. But the link-local IPv6 (PBENCH_SERVER=https://\[fd10:22:16:1::10b9\]:8443 npm run dev) worked just fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, if your goal is to avoid duplication, I think you're out of luck -- I just got this:

# hostname -A
8e10ccf8e78e 8e10ccf8e78e 

Wait; so where did it get a "hostname" like that?

No clue...I'm just reporting what I'm seeing. 🙃

which is stupid, but arguably less stupid than yours. 🤔

😆

presumably the "second" proxy NIC would be on the internal net, which I don't think had DNS. RDU2 may well do something similar.

I expect that you're correct.

I really don't understand the logic of the CSB configuration. They have DHCP addresses, but apparently no DDNS names, and hostname generates only non-routable blather. Then again, VMs and containers generally aren't any better.

Yeah, and it's not just CSB. My home network has similarly awkward hostnames (which are not, generally, as far as I can tell, routable or even usable except in rare circumstances).

(Then again, on the staging VM, hostname -f gives "rhel9-pbenchstaging" while hostname -A gives the actual routable DDNS DHCP FQDN, so that's something.)

Hah! You found it!! That's a case of the return from -f not being in the list (of one...) returned by -A....

So, I guess it just does a dumb-ish pattern match on the backside.

Leaving the obvious question of which form, if either, would match on a redirect.

Well, the redirect is provided by the client (to wit, the Dashboard) and checked by the OIDC server and then invoked. So, if the provided value doesn't match the valid-redirect list, it doesn't really matter whether it is a valid URL format. 🙃 And, if it does match the list, but isn't a valid URL, then that's a problem for the client...so, I'm not sure that Keycloak has any motivation for enforcing anything other than fairly simple pattern matching.

I pulled your branch and did a runlocal. I verified that the Keycloak has the (delimited) IPv6 address. (Actually, it's got both the link-local address and the routable address ... and while my first thought was that the link-local was silly as it's not routable off the local subnet, well, my laptop curiously enough is on its own local subnet...) Anyway, I can open a web page on either delimited IPv6 address copied from the redirects list, and the page loads and I can log in!

Yay! 🎉 Thanks for checking that!!

And, interestingly, while the CSB hostname isn't actually routable, I'd never had occasion to notice before that it actually works as a "localhost" alias on my laptop, so https://dbutenho-thinkpadp1gen4i.bos.csb:8443/dashboard/ loads (and logs in) just fine!

I'm glad it's good for something.... 😉

The upshot here is that every one of the redirect URLs you're programming on my laptop actually work to launch the dashboard and to log in: even the ridiculously silly ones. 😆

Hurrah! (Thanks again for checking!!)

I also tried the local dashboard dev-mode to exercise the http://localhost:3000 redirect, although there was no reason to think it wouldn't still work. It does... but, feeling in an IPv6-ish mood, I tried connecting to IPv6 localhost [::], and that's not mapped in your cert (or probably in the allowed redirects), so it didn't work. Hmm. But the link-local IPv6 (PBENCH_SERVER=https://\[fd10:22:16:1::10b9\]:8443 npm run dev) worked just fine.

OK...if you're willing to test it again, I'll see if I can close that gap.... 🤔 But see my comment below.

KEYCLOAK_DEV_REDIRECT=${KEYCLOAK_DEV_REDIRECT:-"http://localhost:3000/*"}
ADMIN_USERNAME=${ADMIN_USERNAME:-"admin"}
ADMIN_PASSWORD=${ADMIN_PASSWORD:-"admin"}
Expand All @@ -34,6 +36,16 @@ export CURL_CA_BUNDLE=${CURL_CA_BUNDLE:-"${PWD}/server/pbenchinacan/etc/pki/tls/

end_in_epoch_secs=$(date --date "2 minutes" +%s)

keycloak_redirect_uris="\"${KEYCLOAK_DEV_REDIRECT}\""
for n in ${KEYCLOAK_REDIRECT_HOSTS}; do
if [[ $n =~ ^[0-9a-f]{0,4}(:[0-9a-f]{0,4}){1,8}$ ]]; then
n="[$n]" # IPv6 addresses must be wrapped in square-brackets
fi
keycloak_redirect_uris="${keycloak_redirect_uris}, \"https://${n}:8443/*\""
done

echo "Keycloak redirect URI list is <${keycloak_redirect_uris}>."

# Run the custom configuration

ADMIN_TOKEN=""
Expand Down Expand Up @@ -64,7 +76,7 @@ status_code=$(curl -f -s -o /dev/null -w "%{http_code}" -X POST \
"${KEYCLOAK_HOST_PORT}/admin/realms" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"realm": "'${REALM}'", "enabled": true}')
-d '{"realm": "'"${REALM}"'", "enabled": true}')

if [[ "${status_code}" != "201" ]]; then
echo "Realm creation failed with ${status_code}"
Expand Down Expand Up @@ -99,7 +111,7 @@ curl -si -f -X POST \
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.client.audience": "'${CLIENT}'",
"included.client.audience": "'"${CLIENT}"'",
"id.token.claim": "false",
"access.token.claim": "true"
}
Expand All @@ -111,16 +123,16 @@ CLIENT_CONF=$(curl -si -f -X POST \
"${KEYCLOAK_HOST_PORT}/admin/realms/${REALM}/clients" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"clientId": "'${CLIENT}'",
-d '{"clientId": "'"${CLIENT}"'",
"publicClient": true,
"defaultClientScopes": ["pbench", "openid", "profile", "email"],
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": true,
"enabled": true,
"attributes": {"post.logout.redirect.uris": "+"},
"redirectUris": ["'${KEYCLOAK_REDIRECT_URI}'", "'${KEYCLOAK_DEV_REDIRECT}'"]}')
"redirectUris": ['"${keycloak_redirect_uris}"']}')

CLIENT_ID=$(grep -o -e 'https://[^[:space:]]*' <<< ${CLIENT_CONF} | sed -e 's|.*/||')
CLIENT_ID=$(grep -o -e 'https://[^[:space:]]*' <<< "${CLIENT_CONF}" | sed -e 's|.*/||')
if [[ -z "${CLIENT_ID}" ]]; then
echo "${CLIENT} id is empty"
exit 1
Expand Down Expand Up @@ -155,7 +167,7 @@ USER=$(curl -si -f -X POST \
-H "Content-Type: application/json" \
-d '{"username": "admin", "enabled": true, "credentials": [{"type": "password", "value": "123", "temporary": false}]}')

USER_ID=$(grep -o -e 'https://[^[:space:]]*' <<< ${USER} | sed -e 's|.*/||')
USER_ID=$(grep -o -e 'https://[^[:space:]]*' <<< "${USER}" | sed -e 's|.*/||')

if [[ -z "${USER_ID}" ]]; then
echo "User id is empty"
Expand All @@ -168,7 +180,7 @@ status_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
"${KEYCLOAK_HOST_PORT}/admin/realms/${REALM}/users/${USER_ID}/role-mappings/clients/${CLIENT_ID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d '[{"id":"'${ROLE_ID}'","name":"ADMIN"}]')
-d '[{"id":"'"${ROLE_ID}"'","name":"ADMIN"}]')

if [[ "${status_code}" != "204" ]]; then
echo "Assigning 'ADMIN' client role to the user 'admin' failed with ${status_code}"
Expand Down
47 changes: 35 additions & 12 deletions server/pbenchinacan/run-pbench-in-a-can
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,41 @@ export PB_SERVER_IMAGE_PULL_POLICY="${PB_SERVER_IMAGE_PULL_POLICY:-${PB_COMMON_I
# Directory to use for the fully built dashboard code.
export PB_DASHBOARD_DIR="${PB_DASHBOARD_DIR:-${PWD}/dashboard/build/}"

# Keycloak realm and client IDs to be used by the load_keycloak.sh script and
# the pbench-server.cfg file.
export KEYCLOAK_REALM=${KEYCLOAK_REALM:-"pbench-server"}
export KEYCLOAK_CLIENT=${KEYCLOAK_CLIENT:-"pbench-client"}

# Note: the value of PB_HOST_IP will be used to generate the TLS certificate
# and so it (not `localhost`) must also be used to access the Pbench Server;
# otherwise, the TLS validation will fail due to a host mismatch.
if [[ -z "${PB_HOST_IP}" ]]; then
host_ip_list=$(hostname -I)
PB_HOST_IP=${host_ip_list%% *}
export PB_HOST_IP
fi
# Name or IP address to be used by the client to access the Pbench Server, to
# load the Dashboard, and to interface with the canned Keycloak server
host_name=${PB_HOST:-localhost}

host_name=${PB_HOST_NAME:-$(hostname --fqdn)}
# Set a value for the -addext "subjectAltName=..." option to the cert creation.
# Set the initial value "manually" and skip the "dummy" value to make the
# comma-separated concatenations work nicely.
#
# Notes about the use of `readarray`: in order for the output array to be
# visible to the rest of the commands, the `readarray` command must not be
# executed in a subprocess, and therefore it cannot be on the receiving end
# of a pipe. Instead we execute the `hostname` command in a "command
# expansion" inside a "here string". Unfortunately, this causes a newline
# to be appended to the output, so that if we divide the result on spaces
# we end up with an extra array entry containing just a newline. So, instead
# we translate the spaces into newlines ("squeezing" out any repeated
# delimiters) and use the `readarray` default delimiter which is the newline.
subj_alt_name="DNS.1:localhost"
readarray -t cert_hostnames <<< "$(echo "dummy $(hostname -A)" | tr -s ' ' '\n')"
for ((i=1; i < ${#cert_hostnames[*]}; i++)); do
subj_alt_name+=", DNS.$((i+1)):${cert_hostnames[i]}"
done
dbutenhof marked this conversation as resolved.
Show resolved Hide resolved
readarray -t cert_ipaddrs <<< "$(echo "127.0.0.1 $(hostname -I)" | tr -s ' ' '\n')"
for ((i=0; i < ${#cert_ipaddrs[*]}; i++)); do
subj_alt_name+=", IP.$((i+1)):${cert_ipaddrs[i]}"
done
echo "subjectAltName is <${subj_alt_name}>."

grep -q ${host_name} <<< ${subj_alt_name} \
|| echo "Warning: requested host name/addr (${host_name}) is not covered by the TLS cert." >&2

# Set up TMP_DIR, if it's not already defined, to point to WORKSPACE_TMP, if it
# is defined (e.g., by the CI), or to `/var/tmp/pbench` as a fallback.
Expand Down Expand Up @@ -65,8 +87,9 @@ cp ${pbiac_etc}/pbench-server/pbench-server.cfg ${PB_DEPLOY_FILES}/
# Customize the Pbench Server config file for canned operation
sed -Ei \
-e "/^ *realhost/ s/=.*/= $(hostname -f)/" \
-e "s/<keycloak_realm>/${KEYCLOAK_REALM}/" \
-e "s/<keycloak_client>/${KEYCLOAK_CLIENT}/" \
-e "s|##KEYCLOAK_SERVER_URL##|https://${host_name}:8090|" \
-e "s/##KEYCLOAK_REALM##/${KEYCLOAK_REALM}/" \
-e "s/##KEYCLOAK_CLIENT##/${KEYCLOAK_CLIENT}/" \
-e "s/##ADMIN_NAMES##/${PB_ADMIN_NAMES}/" \
${PB_DEPLOY_FILES}/pbench-server.cfg

Expand Down Expand Up @@ -132,7 +155,7 @@ podman run \
-addext "authorityKeyIdentifier = keyid,issuer" \
-addext "basicConstraints=CA:FALSE" \
-addext "keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment" \
-addext "subjectAltName = IP.2:${PB_HOST_IP}, DNS:localhost" \
-addext "subjectAltName = ${subj_alt_name}" \
2>&1 | sed -E -e '/^[.+*-]*$/ d'

chmod 0640 ${PB_DEPLOY_FILES}/pbench-server.key
Expand Down