Skip to content

Commit

Permalink
Merge pull request #76 from IBM/keycloak
Browse files Browse the repository at this point in the history
PoC: Enable Keycloak for Identity Managment
  • Loading branch information
mrsabath authored Nov 26, 2020
2 parents 39335a0 + eed1da1 commit b2e66aa
Show file tree
Hide file tree
Showing 25 changed files with 3,814 additions and 49 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ spec:
create ingress:
```console
$ kk create -f ingress-IKS.yaml
$ kubectl -n tsi-vault create -f ingress-IKS.yaml
```
</details>

Expand Down
Binary file added charts/ti-key-release-1-v1.8.2.tgz
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ data:
value: {{ .Values.vaultAddress }}
- name: SECRET_REFRESH_SEC
value: {{ .Values.secrets.refreshSec }}
- name: IDENTITY_REFRESH_SEC
value: {{ .Values.identities.refreshSec }}
volumeMounts:
- name: tsi-secrets
mountPath: /usr/share/secrets/tsi-secrets
Expand Down Expand Up @@ -55,6 +57,8 @@ data:
value: {{ .Values.vaultAddress }}
- name: SECRET_REFRESH_SEC
value: {{ .Values.secrets.refreshSec }}
- name: IDENTITY_REFRESH_SEC
value: {{ .Values.secrets.refreshIdent }}
volumeMounts:
- name: tsi-secrets
mountPath: /usr/share/secrets/tsi-secrets
Expand Down Expand Up @@ -87,6 +91,9 @@ data:
- path: "tsi-secrets"
fieldRef:
fieldPath: metadata.annotations['tsi.secrets']
- path: "tsi-identities"
fieldRef:
fieldPath: metadata.annotations['tsi.identities']
- path: "ti-identity"
fieldRef:
fieldPath: metadata.annotations['admission.trusted.identity/ti-identity']
Expand Down
4 changes: 4 additions & 0 deletions charts/ti-key-release-1/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ secrets:
# the TSI secrets to be retrieved by the sidecar
vaultAddress: http://vault

identities:
# how often identities should be retrieved from Keycloak
refreshSec: 600

# Not recommended for user to configure this. Hyperkube image to use when executing
# kubectl commands
hyperkube:
Expand Down
Binary file added charts/ti-key-release-2-v1.8.2.tgz
Binary file not shown.
3 changes: 3 additions & 0 deletions charts/ti-key-release-2/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ ti-key-release-1:
secrets:
# how often secrets should be retrieved from Vault
refreshSec: 600
identities:
# how often identities should be retrieved from Keycloak
refreshSec: 600
# Cluster Information
cluster:
name: cluster-name
Expand Down
Binary file added charts/tsi-node-setup-v1.8.2.tgz
Binary file not shown.
5 changes: 5 additions & 0 deletions components/jwt-sidecar/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ RUN wget https://releases.hashicorp.com/vault/1.4.2/vault_1.4.2_linux_amd64.zip
COPY run-sidecar.sh execute-get-token.sh execute-get-vault-secrets.sh \
get-vault-secrets.sh /usr/local/bin/

# adding Keycloak identities:
COPY execute-get-keycloak-identities.sh get-keycloak-identities.sh /usr/local/bin/

COPY test-vault-cli.sh /test-vault-cli.sh

# Default value for NEW_JWT_WAIT_SEC must be a little shorter than TTL_SEC
Expand All @@ -31,9 +34,11 @@ ENV JWT_TTL_SEC=${DEFAULT_JWT_TTL_SEC}
# Default values for vault client setup
ARG DEFAULT_VAULT_ADDR="http://vault:8200"
ARG DEFAULT_SECRET_REFRESH_SEC=600
ARG DEFAULT_IDENTITY_REFRESH_SEC=600
ARG DEFAULT_IS_SIDECAR=true
ENV VAULT_ADDR=${DEFAULT_VAULT_ADDR}
ENV SECRET_REFRESH_SEC=${DEFAULT_SECRET_REFRESH_SEC}
ENV IDENTITY_REFRESH_SEC=${DEFAULT_IDENTITY_REFRESH_SEC}
ENV IS_SIDECAR=${DEFAULT_IS_SIDECAR}

CMD ["/bin/bash", "-c", "/usr/local/bin/run-sidecar.sh"]
66 changes: 66 additions & 0 deletions components/jwt-sidecar/execute-get-keycloak-identities.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/bin/bash

JWTFILE="/jwt/token"
IDSREQFILE="/pod-metadata/tsi-identities"

# set the initial wait to 10 seconds
# once the vault secret is retrieved successfully, switch to
# provided parameter
WAIT_SEC=10

# when script is running in init initContainer, attempt obtaining secrets only few times
MAX_EXEC=5
COUNTER=0

while true
do
# first we should wait for the token to be available
if [ ! -s "$JWTFILE" ]; then
echo "$JWTFILE does not exist yet. Let's wait for it. Please make sure the JSS in initalized."
while [ ! -s "$JWTFILE" ]; do
sleep 5
done
fi

# get the identity definitions
if [ ! -s "$IDSREQFILE" ]; then
echo "$IDSREQFILE does not exist or empty."

if $IS_SIDECAR; then
echo "Nothing to do. Waiting..."
while [ ! -s "$IDSREQFILE" ]; do
sleep 5
done
else
# if the script is running in initContainer, there is no need to block
# when no secrets are needed
echo "Nothing to do. Exiting ..."
exit 0
fi

fi

/usr/local/bin/get-keycloak-identities.sh
RT=$?
# When script is running as sidecar, run it forever
if $IS_SIDECAR; then
if [ "$RT" == "0" ]; then
# introduce the random wait value from 1 to 30 seconds
RAND_WAIT=$((1 + RANDOM % 30))
WAIT_SEC=$((${IDENTITY_REFRESH_SEC} + RAND_WAIT))
echo "Waiting $WAIT_SEC seconds ..."
fi
else
# when it's running as initContainer, exit after successful transaction
if [ "$RT" == "0" ]; then
echo "Keycloak identities successfully executed!"
exit 0
fi
if [[ "$COUNTER" -gt "$MAX_EXEC" ]]; then
echo "$COUNTER unsuccessful attempts to get Keycloak identities. Exiting..."
exit 1
fi
((COUNTER++))
fi
sleep "${WAIT_SEC}"
done
34 changes: 23 additions & 11 deletions components/jwt-sidecar/execute-get-vault-secrets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,42 @@ COUNTER=0

while true
do
if [ ! -s "$SECREQFILE" ]; then
echo "$SECREQFILE does not exist or empty. Nothing to do. Waiting..."
while [ ! -s "$SECREQFILE" ]; do
sleep 5
done
fi

# first we should wait for the token to be available
if [ ! -s "$JWTFILE" ]; then
echo "$JWTFILE does not exist yet. Let's wait for it. Please make sure the JSS in initalized."
while [ ! -s "$JWTFILE" ]; do
sleep 5
done
fi

# get the secret definitions
if [ ! -s "$SECREQFILE" ]; then
echo "$SECREQFILE does not exist or empty."

if $IS_SIDECAR; then
echo "Nothing to do. Waiting..."
while [ ! -s "$SECREQFILE" ]; do
sleep 5
done
else
# if the script is running in initContainer, there is no need to block
# when no secrets are needed
echo "Nothing to do. Exiting ..."
exit 0
fi

fi

/usr/local/bin/get-vault-secrets.sh
RT=$?
# When sript is running as sidecar, run it forever
if $IS_SIDECAR; then
# When script is running as sidecar, run it forever
if $IS_SIDECAR; then
if [ "$RT" == "0" ]; then
# introduce the random wait value from 1 to 30 seconds
RAND_WAIT=$((1 + RANDOM % 30))
WAIT_SEC=$((${SECRET_REFRESH_SEC} + RAND_WAIT))
echo "Waiting $WAIT_SEC seconds ..."
fi
sleep "${WAIT_SEC}"
else
# when it's running as initContainer, exit after successful transaction
if [ "$RT" == "0" ]; then
Expand All @@ -60,6 +72,6 @@ do
exit 1
fi
((COUNTER++))
sleep "${WAIT_SEC}"
fi
sleep "${WAIT_SEC}"
done
147 changes: 147 additions & 0 deletions components/jwt-sidecar/get-keycloak-identities.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/bin/bash

IDSOUTDIR="/usr/share/secrets"
JWTFILE="/jwt/token"
# IDSREQFILE - Identities request file from Pod Annotation
IDSREQFILE="/pod-metadata/tsi-identities"

# when we decide to pass the Keycloak address on cluster level:
# validate if KEYCLOAK_ADDR env. variable is set
# if [ "$KEYCLOAK_ADDR" == "" ]; then
# echo "KEYCLOAK_ADDR must be set"
# exit 1
# fi

# make sure that JWT file exists
if [ ! -s "$JWTFILE" ]; then
echo "$JWTFILE does not exist. Make sure Trusted Identity is setup correctly"
exit 1
fi

# since annotations are provided in YAML format,
# convert YAML to JSON for easier manipulations
if [ ! -s "$IDSREQFILE" ]; then
echo "$IDSREQFILE contains no data. Nothing to do"
exit 1
fi
JSON=$(yq r -j "$IDSREQFILE")
if [ "$?" != "0" ]; then
echo "Error parsing $IDSREQFILE file. Incorrect format"
exit 1
fi

# the return values from this function are ignored
# we only use the echoed values
run()
{
# example of the realm token URL:
# "${KEYCLOAK_ADDR}/auth/realms/hello-world-authz/protocol/openid-connect/token"
local KEYCLOAK_TOKEN_URL=$1
local K_LOCAL=$2
local LOCPATH=${K_LOCAL:-"tsi-secrets/identities"}
local AUD=$3
local JWTFILE="/jwt/token"
local TOKEN_RESP=$(mktemp /tmp/token-resp.XXX)
local FILENAME="access_token.$3.$COUNT"

# local-path must start with "tsi-secrets"
if [[ ${LOCPATH} != "tsi-secrets" ]] && [[ ${LOCPATH} != "/tsi-secrets" ]] && [[ ${LOCPATH} != /tsi-secrets/* ]] && [[ ${LOCPATH} != tsi-secrets/* ]]; then
echo "ERROR: invalid local-path requested: $LOCPATH"
echo "Local path must start with /tsi-secrets"
return 1
fi
local IDSOUTDIR=${IDSOUTDIR}/${LOCPATH}

# Sample format for requesting the access token:
# curl --location --request POST 'http://<keycloak-server>/auth/realms/tsi-realm/protocol/openid-connect/token' \
# --header 'Content-Type: application/x-www-form-urlencoded' \
# --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:uma-ticket' \
# --data-urlencode 'audience=tsi-client' \
# --data-urlencode 'client_id=tsi-client' \
# --data-urlencode "tsi_token=$(cat /jwt/token)"

# Sample format for requesting the public key from Keycloak:
# curl --location --request GET 'http://<keycloak-server>/auth/realms/tsi-realm/protocol/openid-connect/certs' \
# --header 'Content-Type: application/x-www-form-urlencoded' \
# --data-urlencode --data-urlencode "tsi_token=$(cat /jwt/token)"

SC=$(curl --max-time 10 -s -w "%{http_code}" -o $TOKEN_RESP --location --request POST \
${KEYCLOAK_TOKEN_URL} --header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=tsi-client' --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:uma-ticket' \
--data-urlencode "tsi_token=$(cat $JWTFILE)" --data-urlencode "audience=$AUD" 2> /dev/null)
local RT=$?

# return value for curl timeout is 28
if [[ "$RT" == "28" ]]; then
echo "Timout while getting Keycloak token"
rm $TOKEN_RESP
return 1
fi

if [[ "$RT" != "0" ]]; then
echo "Unknown error getting Keycloak token"
cat $TOKEN_RESP
rm $TOKEN_RESP
return 1
fi

if [ "$SC" != "200" ]; then
echo "Error getting Keycloak token"
cat $TOKEN_RESP
rm $TOKEN_RESP
return 1
fi

RESP=$(cat $TOKEN_RESP)
# rm -f $TOKEN_RESP
mkdir -p ${IDSOUTDIR}
mv $TOKEN_RESP ${IDSOUTDIR}/${FILENAME}
# REF_TOK=$(echo $RESP | jq -r '.refresh_token')
# ACCESS_TOK=$(echo $RESP | jq -r '.access_token')
# echo $ACCESS_TOK | cut -d"." -f2 | sed 's/\./\n/g' | base64 --decode | jq
echo $RESP | jq -r '.access_token' | cut -d"." -f2 | base64 --decode | jq '.' > ${IDSOUTDIR}/${FILENAME}.txt
}

## create help menu:
helpme()
{
cat <<HELPMEHELPME
Syntax: $0
HELPMEHELPME
}

ERR=0
COUNT=0
for row in $(echo "${JSON}" | jq -c '.[]' ); do
# for each requested identities parse its attributes
KEYCLOAK_ADDR=$(echo "$row" | jq -r '."tsi.keycloak/token-url"')
KEYCLOAK_PATH=$(echo "$row" | jq -r '."tsi.keycloak/local-path"')
KEYCLOAK_AUDS=$(echo "$row" | jq -r '."tsi.keycloak/audiences"')
if [ "$KEYCLOAK_PATH" == "null" ]; then
KEYCLOAK_PATH=""
fi
if [ "$KEYCLOAK_AUDS" == "null" ]; then
KEYCLOAK_AUDS="tsi-client"
fi

# audiences can be separated with comma
auds=$(echo $KEYCLOAK_AUDS | tr "," "\n")
for aud in $auds; do

# then run identity retrieval from Keycloak
run $KEYCLOAK_ADDR $KEYCLOAK_PATH $aud
RT=$?
if [ "$RT" != "0" ]; then
echo "Error processing identities token-url=${KEYCLOAK_ADDR}, audiance=$aud, local-path=$KEYCLOAK_PATH"
# if we want to end the init process in case of the failed attempt,
# uncomment all the way to the end
# ERR=1
fi
# increase the counter
COUNT=$((COUNT+1))
done
# if [ "$ERR" -ne 0 ]; then
# exit 1
# fi
done
17 changes: 13 additions & 4 deletions components/jwt-sidecar/run-sidecar.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
#!/usr/bin/env bash

/usr/local/bin/execute-get-token.sh &

/usr/local/bin/execute-get-vault-secrets.sh
RT=$?

if ! $IS_SIDECAR; then
if [ "$RT" == "0" ]; then
echo "All good!"
exit 0
echo "All good with secrets"
else
echo "Unsuccessful Vault retrieve"
exit 1
fi
fi
/usr/local/bin/execute-get-keycloak-identities.sh
RT=$?
if ! $IS_SIDECAR; then
if [ "$RT" == "0" ]; then
echo "All good with identities"
else
echo "Unsuccessful retrieve"
echo "Unsuccessful Keycloak retrieve"
exit 1
fi
fi
Loading

0 comments on commit b2e66aa

Please sign in to comment.