Skip to content

Commit

Permalink
feat: (ESPSTUDIO-8123 ESPSTUDIO-8124)
Browse files Browse the repository at this point in the history
feat: (ESPSTUDIO-8123 ESPSTUDIO-8124)
Deployment script - support Viya
Deployment script - support Keycloak

Update the deployment script to set up oath on viya and keycloak

put in separate code paths to set the appropriate oauth and URLs in the ini file

add removal scripts these may come in handy for testing.
  • Loading branch information
mtlljm authored Nov 22, 2023
1 parent e4dd2da commit d8933fb
Show file tree
Hide file tree
Showing 9 changed files with 497 additions and 89 deletions.
18 changes: 11 additions & 7 deletions install/config-map.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ metadata:
data:
grafana-uaa.ini: |
[server]
domain = TEMPLATE_ESP_DOMAIN
root_url = https://TEMPLATE_ESP_DOMAIN/grafana/
domain = TEMPLATE_GRAFANA_DOMAIN
root_url = https://TEMPLATE_GRAFANA_DOMAIN/grafana/
serve_from_sub_path = true
[plugins]
Expand All @@ -16,6 +16,7 @@ data:
accessTokenExpirationCheck = true
[security]
disable_initial_admin_creation = true
cookie_secure = true
cookie_samesite = lax
Expand All @@ -25,12 +26,12 @@ data:
[auth]
disable_login_form = true
signout_redirect_url = https://TEMPLATE_ESP_DOMAIN/oauth2/sign_out?rd=https://TEMPLATE_ESP_DOMAIN/uaa/logout.do?redirect=https://TEMPLATE_ESP_DOMAIN/uaa/login
signout_redirect_url = TEMPLATE_SIGNOUT_REDIRECT_URL
[auth.generic_oauth]
tls_skip_verify_insecure = true
enabled = true
name = UAA
name = OAuth
use_pkce = true
auto_login = true
client_id = TEMPLATE_OAUTH_CLIENT_ID
Expand All @@ -39,6 +40,9 @@ data:
email_attribute_path = email
name_attribute_path = user_name
login_attribute_path = user_name
auth_url = https://TEMPLATE_ESP_DOMAIN/uaa/oauth/authorize
token_url = https://TEMPLATE_ESP_DOMAIN/uaa/oauth/token?token_format=jwt
api_url = https://TEMPLATE_ESP_DOMAIN/uaa/userinfo
auth_url = TEMPLATE_AUTH_URL
token_url = TEMPLATE_TOKEN_URL
api_url = TEMPLATE_API_URL
allow_assign_grafana_admin = true
role_attribute_path = contains(grafana_roles[*], 'grafana-admin') && 'GrafanaAdmin' || contains(grafana_roles[*], 'admin') && 'Admin' || contains(grafana_roles[*], 'editor') && 'Editor' || 'Viewer'
178 changes: 97 additions & 81 deletions install/configure-grafana.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,118 +2,134 @@

set -e -o pipefail -o nounset

OAUTH_CLIENT_ID="${OAUTH_CLIENT_ID:-sv_client}"
export OAUTH_CLIENT_ID

OAUTH_CLIENT_SECRET="${OAUTH_CLIENT_SECRET:-secret}"
export OAUTH_CLIENT_SECRET

ESP_NAMESPACE="${1}"

#input variables
ESP_NAMESPACE="${1}"; export ESP_NAMESPACE
ESP_PLUGIN_SOURCE="${2}"
export ESP_PLUGIN_SOURCE
OAUTH_TYPE="${3:-uaa}"

#optional environment variables - exported for use in other scripts
OAUTH_CLIENT_ID="${OAUTH_CLIENT_ID:-sv_client}"; export OAUTH_CLIENT_ID
OAUTH_CLIENT_SECRET="${OAUTH_CLIENT_SECRET:-secret}"; export OAUTH_CLIENT_SECRET
KEYCLOAK_SUBPATH="${KEYCLOAK_SUBPATH:-auth}"; export KEYCLOAK_SUBPATH

#optional environment variables
DRY_RUN="${DRY_RUN:-false}"
INSTALL_GRAFANA="${INSTALL_GRAFANA:-false}"
GRAFANA_VERSION="${GRAFANA_VERSION:-'9.5.13'}"
GRAFANA_VERSION="${GRAFANA_VERSION:-9.5.13}"
GRAFANA_NAMESPACE="${GRAFANA_NAMESPACE:-${ESP_NAMESPACE}}"

function check_requirements() {
[ -z "$KUBECONFIG" ] && {
echo "KUBECONFIG environment variable unset." >&2
exit 1
}

[ -z "${ESP_NAMESPACE}" ] && {
echo "Usage: ${0} <namespace> <plugin-zip-url> <oauth-type>" >&2
exit 1
}

[ -z "${ESP_PLUGIN_SOURCE}" ] && {
echo "Usage: ${0} <namespace> <plugin-zip-url> <oauth-type>" >&2
exit 1
}

if ! kubectl get namespace "${ESP_NAMESPACE}" 2>/dev/null 1>&2; then
echo >&2 "ERROR: Namespace ${ESP_NAMESPACE} not found."
exit 1
fi
}

# Fetch access token to perform admin tasks:
function fetch_uaa_admin_token() {
_resp=$(curl "https://${ESP_DOMAIN}/uaa/oauth/token" -s -k -X POST \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-d "client_id=${UAA_ADMIN}&client_secret=${UAA_SECRET}&grant_type=client_credentials&response_type=token")
function generate_manifests() {
if [ -d "./manifests" ]; then
echo "Existing manifest directory found." >&2
echo "Removing manifests..."
rm -r ./manifests/
fi

[ -d "./manifests" ] || mkdir "manifests"
cp -r *.yaml manifests/

for file in `find ./manifests/ -name "*.y*ml"` ; do

sed -i 's|TEMPLATE_AUTH_URL|'$TEMPLATE_AUTH_URL'|g' $file
sed -i 's|TEMPLATE_TOKEN_URL|'$TEMPLATE_TOKEN_URL'|g' $file
sed -i 's|TEMPLATE_API_URL|'$TEMPLATE_API_URL'|g' $file
sed -i 's|TEMPLATE_SIGNOUT_REDIRECT_URL|'$TEMPLATE_SIGNOUT_REDIRECT_URL'|g' $file

sed -i 's|TEMPLATE_GRAFANA_DOMAIN|'$GRAFANA_DOMAIN'|g' $file
sed -i 's|TEMPLATE_ESP_DOMAIN|'$ESP_DOMAIN'|g' $file
sed -i 's|TEMPLATE_OAUTH_CLIENT_ID|'$OAUTH_CLIENT_ID'|g' $file
sed -i 's|TEMPLATE_OAUTH_CLIENT_SECRET|'$OAUTH_CLIENT_SECRET'|g' $file
sed -i 's|TEMPLATE_ESP_PLUGIN_SOURCE|'$ESP_PLUGIN_SOURCE'|g' $file
sed -i 's|TEMPLATE_GRAFANA_VERSION|'$GRAFANA_VERSION'|g' $file

if [[ "${DRY_RUN}" == true ]]; then
echo $file
cat $file
fi

echo "${_resp}" | jq -r '.access_token'
done
}

# Add Grafana generic OAuth to allowed auth redirects:
function add_grafana_auth_redirect() {
_token="$(fetch_uaa_admin_token)"
_redirect="https://${ESP_DOMAIN}/grafana/login/generic_oauth"
check_requirements

_config=$(curl -s -k -X GET "https://${ESP_DOMAIN}/uaa/oauth/clients/${OAUTH_CLIENT_ID}" -H "Authorization: Bearer ${_token}")
echo "Fetching required deployment information..."
ESP_DOMAIN=$(kubectl -n "${ESP_NAMESPACE}" get ingress --output json | jq -r '.items[0].spec.rules[0].host')
export ESP_DOMAIN

_update_body=$(echo "${_config}" | jq -c -r --arg redirect "${_redirect}" \
'.redirect_uri += [$redirect] | {client_id: .client_id, redirect_uri: .redirect_uri}')
GRAFANA_DOMAIN=$(kubectl -n "${GRAFANA_NAMESPACE}" get ingress --output json | jq -r '.items[0].spec.rules[0].host')

_resp=$(curl "https://${ESP_DOMAIN}/uaa/oauth/clients/${OAUTH_CLIENT_ID}" -s -k -X PUT \
-o /dev/null -w "%{http_code}" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ${_token}" \
-H 'Accept: application/json' \
-d "${_update_body}")
echo "Adding Grafana to allowed OAuth client redirects..."
if [ "${OAUTH_TYPE}" == "viya" ]; then

if [ "${_resp}" == '200' ]; then
echo " Grafana OAuth redirect added."
else
echo >&2 "ERROR: OAuth client redirect update failed with status code ${_resp}."
exit 1
fi
}
if [[ "${DRY_RUN}" == false ]]; then
bash register-oauth-client-viya.sh
fi

[ -z "$KUBECONFIG" ] && {
echo "KUBECONFIG environment variable unset." >&2
exit 1
}
TEMPLATE_AUTH_URL="https://${ESP_DOMAIN}/SASLogon/oauth/authorize"
TEMPLATE_TOKEN_URL="https://${ESP_DOMAIN}/SASLogon/oauth/token"
TEMPLATE_API_URL="https://${ESP_DOMAIN}/SASLogon/userinfo"
TEMPLATE_SIGNOUT_REDIRECT_URL="https://${ESP_DOMAIN}/SASLogon/logout.do"

[ -z "${ESP_NAMESPACE}" ] && {
echo "Usage: ${0} <namespace> <plugin-zip-url>" >&2
exit 1
}
elif [ "${OAUTH_TYPE}" == "keycloak" ]; then

[ -z "${ESP_PLUGIN_SOURCE}" ] && {
echo "Usage: ${0} <namespace> <plugin-zip-url>" >&2
exit 1
}
if [[ "${DRY_RUN}" == false ]]; then
bash register-oauth-client-keycloak.sh
fi

if [ -d "./manifests" ]; then
echo "Existing manifest directory found." >&2
echo "Removing manifests..."
rm -r ./manifests/
fi
TEMPLATE_AUTH_URL="https://${ESP_DOMAIN}/${KEYCLOAK_SUBPATH}/realms/sas-esp/protocol/openid-connect/auth"
TEMPLATE_TOKEN_URL="https://${ESP_DOMAIN}/${KEYCLOAK_SUBPATH}/realms/sas-esp/protocol/openid-connect/token"
TEMPLATE_API_URL="https://${ESP_DOMAIN}/${KEYCLOAK_SUBPATH}/realms/sas-esp/protocol/openid-connect/userinfo"
TEMPLATE_SIGNOUT_REDIRECT_URL="https://${ESP_DOMAIN}/${KEYCLOAK_SUBPATH}/realms/sas-esp/protocol/openid-connect/logout?client_id=${OAUTH_CLIENT_ID}\&post_logout_redirect_uri=https://${ESP_DOMAIN}/grafana/login"

echo "Fetching required deployment information..."
ESP_DOMAIN=$(kubectl -n "${ESP_NAMESPACE}" get ingress --output json | jq -r '.items[0].spec.rules[0].host')
export ESP_DOMAIN
else

_uaa_secret_data=$(kubectl -n "${ESP_NAMESPACE}" get secret uaa-secret --output json)
UAA_ADMIN=$(echo "${_uaa_secret_data}" | jq -r '.data.username | @base64d')
export UAA_ADMIN
UAA_SECRET=$(echo "${_uaa_secret_data}" | jq -r '.data.password | @base64d')
export UAA_SECRET
if [[ "${DRY_RUN}" == false ]]; then
bash register-oauth-client-uaa.sh
fi

TEMPLATE_AUTH_URL="https://${ESP_DOMAIN}/uaa/oauth/authorize"
TEMPLATE_TOKEN_URL="https://${ESP_DOMAIN}/uaa/oauth/token?token_format=jwt"
TEMPLATE_API_URL="https://${ESP_DOMAIN}/uaa/userinfo"
TEMPLATE_SIGNOUT_REDIRECT_URL="https://${ESP_DOMAIN}/oauth2/sign_out?rd=https://${ESP_DOMAIN}/uaa/logout.do?redirect=https://${ESP_DOMAIN}/uaa/login"

fi

cat <<EOF
Deployment details:
ESP domain: ${ESP_DOMAIN}
UAA admin user: ${UAA_ADMIN}
UAA admin secret: ****
OAuth client ID: ${OAUTH_CLIENT_ID}
OAuth client secret: ****
Deploying Grafana with values:
ESP plugin source: ${ESP_PLUGIN_SOURCE}
EOF

echo "Adding Grafana to allowed OAuth client redirects..."
add_grafana_auth_redirect

echo "Generating manifests..."
[ -d "./manifests" ] || mkdir "manifests"
cp -r *.yaml manifests/

find ./manifests/ -type f -name "*.yaml" -exec perl -pi -e 's/\QTEMPLATE_ESP_DOMAIN/$ENV{"ESP_DOMAIN"}/g' '{}' +
find ./manifests/ -type f -name "*.yaml" -exec perl -pi -e 's/\QTEMPLATE_OAUTH_CLIENT_ID/$ENV{"OAUTH_CLIENT_ID"}/g' '{}' +
find ./manifests/ -type f -name "*.yaml" -exec perl -pi -e 's/\QTEMPLATE_OAUTH_CLIENT_SECRET/$ENV{"OAUTH_CLIENT_SECRET"}/g' '{}' +
find ./manifests/ -type f -name "*.yaml" -exec perl -pi -e 's/\QTEMPLATE_ESP_PLUGIN_SOURCE/$ENV{"ESP_PLUGIN_SOURCE"}/g' '{}' +
find ./manifests/ -type f -name "*.yaml" -exec perl -pi -e 's/\QTEMPLATE_GRAFANA_VERSION/$ENV{"GRAFANA_VERSION"}/g' '{}' +
generate_manifests

if [[ "${DRY_RUN}" == true ]]; then
echo "Dry run specified. Printing manifests to be applied:"
echo "./manifests/config-map.yaml"
cat ./manifests/config-map.yaml
echo "./manifests/patch-grafana.yaml"
cat ./manifests/patch-grafana.yaml
exit 0
exit 0
fi

if [[ "${INSTALL_GRAFANA}" == true ]]; then
Expand Down
2 changes: 1 addition & 1 deletion install/grafana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,4 @@ spec:
tls:
- hosts:
- "TEMPLATE_ESP_DOMAIN"
secretName: foo
secretName: TEMPLATE_OAUTH_CLIENT_SECRET
109 changes: 109 additions & 0 deletions install/register-oauth-client-keycloak.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env bash

set -e -o pipefail -o nounset

function check_keycloak_deployment() {
if ! kubectl -n "${ESP_NAMESPACE}" get deployment keycloak-deployment 2>/dev/null 1>&2; then
echo >&2 "ERROR: No Keycloak deployment found under namespace ${ESP_NAMESPACE}."
exit 1
fi

_kc_pod=$(kubectl -n "${ESP_NAMESPACE}" get pods -o json |
jq -r '.items[] | select(.metadata.name | test("^keycloak-deployment-")) | .metadata.name')
[ -n "${_kc_pod}" ] || {
echo >&2 "ERROR: No keycloak-deployment-* pod found under namespace ${ESP_NAMESPACE}."
exit 1
}

_kc_ready=$(kubectl -n "${ESP_NAMESPACE}" get pod "${_kc_pod}" -o json |
jq -r '.status.conditions[] | select(.type == "Ready") | .status')
[ "${_kc_ready}" == 'True' ] || {
echo >&2 "ERROR: Keycloak deployment exists but is not ready. Try again later."
exit 1
}
}

function check_requirements() {

if ! kubectl -n "${ESP_NAMESPACE}" get secret keycloak-admin-secret 2>/dev/null 1>&2; then
echo >&2 "ERROR: No Keycloak admin secret found under namespace ${ESP_NAMESPACE}."
exit 1
fi

if ! kubectl -n "${ESP_NAMESPACE}" get secret oauth2-proxy-client-secret 2>/dev/null 1>&2; then
echo >&2 "ERROR: No OAuth2 Proxy client secret found under namespace ${ESP_NAMESPACE}."
exit 1
fi

check_keycloak_deployment
}

# Fetch access token to perform admin tasks:
function fetch_keycloak_admin_token() {
_resp=$(curl "https://${ESP_DOMAIN}/${KEYCLOAK_SUBPATH}/realms/master/protocol/openid-connect/token" -s -k -X POST \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-d "client_id=admin-cli&grant_type=password&username=${KEYCLOAK_ADMIN}&password=${KEYCLOAK_SECRET}")

echo "${_resp}" | jq -r '.access_token'
}

function create_role() {
_role_name="${1}"
_role_repr="{\"name\": \"${_role_name}\", \"clientRole\": true}"
curl "https://${ESP_DOMAIN}/${KEYCLOAK_SUBPATH}/admin/realms/sas-esp/clients/${_client_id}/roles" -s -k -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${_token}" \
-d "${_role_repr}"
}

function add_protocol_mapper() {
_mapper_repr=$(echo -e "
{
\"name\": \"GrafanaRoles\",
\"protocol\": \"openid-connect\",
\"protocolMapper\": \"oidc-usermodel-client-role-mapper\",
\"consentRequired\": false,
\"config\": {
\"claim.name\": \"grafana_roles\",
\"usermodel.clientRoleMapping.clientId\": \"${OAUTH_CLIENT_ID}\",
\"jsonType.label\": \"String\",
\"multivalued\": \"true\",
\"access.token.claim\": \"true\",
\"userinfo.token.claim\": \"false\",
\"id.token.claim\": \"true\"
}
}")
_mapper_body=$(echo "${_mapper_repr}" | jq -r -c)
curl -s -k -X POST \
"https://${ESP_DOMAIN}/${KEYCLOAK_SUBPATH}/admin/realms/sas-esp/clients/${_client_id}/protocol-mappers/models" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${_token}" \
-d "${_mapper_body}"
}

function prepare_keycloak_roles() {
_token="$(fetch_keycloak_admin_token)"
# Get sas-esp realm clients:
_kc_clients=$(curl -s -k -X GET "https://${ESP_DOMAIN}/${KEYCLOAK_SUBPATH}/admin/realms/sas-esp/clients" -H "Authorization: Bearer ${_token}")
# Get OAuth2 Proxy client ID:
_client_id=$(echo "${_kc_clients}" | jq -r --arg opid "${OAUTH_CLIENT_ID}" '.[] | select(.clientId == $opid) | .id')
# Create Grafana roles:
create_role "grafana-admin"
create_role "admin"
create_role "editor"
# Create Grafana role protocol mapper:
add_protocol_mapper
}

_keycloak_admin_secret=$(kubectl -n "${ESP_NAMESPACE}" get secret keycloak-admin-secret --output json)
KEYCLOAK_ADMIN=$(echo "${_keycloak_admin_secret}" | jq -r '.data.username | @base64d')
KEYCLOAK_SECRET=$(echo "${_keycloak_admin_secret}" | jq -r '.data.password | @base64d')

_oauth2_proxy_secret=$(kubectl -n "${ESP_NAMESPACE}" get secret oauth2-proxy-client-secret --output json)
OAUTH_CLIENT_ID=$(echo "${_oauth2_proxy_secret}" | jq -r '.data.OAUTH2_PROXY_CLIENT_ID | @base64d')
export OAUTH_CLIENT_ID
OAUTH_CLIENT_SECRET=$(echo "${_oauth2_proxy_secret}" | jq -r '.data.OAUTH2_PROXY_CLIENT_SECRET | @base64d')
export OAUTH_CLIENT_SECRET

prepare_keycloak_roles
Loading

0 comments on commit d8933fb

Please sign in to comment.