diff --git a/README.md b/README.md index ddf0378..b3b3aa3 100644 --- a/README.md +++ b/README.md @@ -81,13 +81,11 @@ This section is relevant only to internal users at SAS. ### Install a Released Version of the Plug-in -An installation script is provided to install the plug-in and configure Grafana. The installation script performs the following tasks: - * Modifies the Grafana deployment by adding the GF_INSTALL_PLUGINS environment variable to enable Grafana to install the plug-in. - * Configures a new `grafana.ini` file to enable OAuth authentication. - * Configures Grafana as an OAuth client with the chosen OAuth provider. Users of Grafana are directed to use the OAuth login page. - * Optionally installs Grafana for you. - -Use the installation script to install the plug-in: +Installation scripts are provided to install the plug-in and configure Grafana. These scripts perform the following tasks: + * Modify the Grafana deployment by adding the GF_INSTALL_PLUGINS environment variable to enable Grafana to install the plug-in. + * Configure a new `grafana.ini` file to enable OAuth authentication. + * Configure Grafana as an OAuth client with the chosen OAuth provider. Users of Grafana are directed to use the OAuth login page. + * Optionally install Grafana for you. 1. Set the correct Kubernetes configuration file for your environment. ``` @@ -105,16 +103,39 @@ Use the installation script to install the plug-in: ``` export GRAFANA_NAMESPACE=grafana ``` -5. Run the installation script, adjusting the command to specify the following variables: +5. Run `configure-grafana.sh`, adjusting the command to specify the following variables: - The Kubernetes _namespace_ in which SAS Event Stream Processing is installed. - The _version_ of the plug-in that you want to install. Ensure that you specify a version of the plug-in that is compatible with your version of Grafana. - - The _oauth-provider_ of the environment. Select one of the following options: **uaa**, **keycloak** or **viya**. + - The _oauth-provider_ of the environment. Select one of the following options: `uaa`, `keycloak` or `viya`. > **Caution**: Running the installation script might overwrite any existing Grafana configuration. ``` cd ./install - bash configure-grafana.sh https://github.com/sassoftware/grafana-esp-plugin/download//sasesp-plugin-.zip + bash configure-grafana.sh + ``` +6. Run one of the following three scripts, depending on your chosen OAuth provider. Adjust the command to specify the following variables. + - The Kubernetes namespace in which SAS Event Stream Processing is installed, _esp-namespace_. + - (Optional) The Kubernetes namespace in which Grafana is installed, _grafana-namespace_ if this differs from the namespace in which SAS Event Stream Processing is installed. + ``` + bash register-oauth-client-keycloak.sh + ``` + ``` + bash register-oauth-client-uaa.sh + ``` ``` + bash register-oauth-client-viya.sh + ``` +7. If your OAuth provider is the SAS Viya platform and Grafana is not running in the same namespace as the SAS Viya platform, you must update the Content Security Policy (CSP) for SAS Logon to allow the Grafana host name to be used as a target of form submission. + If you do not update the CSP, the browser blocks the redirect. You can update the CSP in one of the following two ways: + - Use SAS Environment Manager to update the _content-security-policy_ value under the _sas.commons.web.security_ section. + - Update the _sas-logon-app_ deployment to add the _SAS_COMMONS_WEB_SECURITY_CONTENTSECURITYPOLICY_ environment variable. + + Update either SAS Environment Manager or the _sas-logon-app_ deployment with the following value, substituting the Grafana host name: + ``` + default-src 'self'; style-src 'self'; font-src 'self' data:; + frame-ancestors 'self'; form-action 'self' ; + ``` + ### (Optional) Build and Install a Privately Signed Version of the Plug-in diff --git a/install/configure-grafana.sh b/install/configure-grafana.sh index 1609200..6d576c0 100644 --- a/install/configure-grafana.sh +++ b/install/configure-grafana.sh @@ -4,7 +4,7 @@ set -e -o pipefail -o nounset #input variables ESP_NAMESPACE="${1}"; export ESP_NAMESPACE -ESP_PLUGIN_SOURCE="${2}" +ESP_PLUGIN_VERSION="${2}" OAUTH_TYPE="${3:-uaa}" #optional environment variables - exported for use in other scripts @@ -25,12 +25,12 @@ function check_requirements() { } [ -z "${ESP_NAMESPACE}" ] && { - echo "Usage: ${0} " >&2 + echo "Usage: ${0} " >&2 exit 1 } - [ -z "${ESP_PLUGIN_SOURCE}" ] && { - echo "Usage: ${0} " >&2 + [ -z "${ESP_PLUGIN_VERSION}" ] && { + echo "Usage: ${0} " >&2 exit 1 } @@ -75,18 +75,15 @@ function generate_manifests() { check_requirements 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 +#duplicate domain code +ESP_DOMAIN=$(kubectl -n "${ESP_NAMESPACE}" get ingress --output json | jq -r '.items[0].spec.rules[0].host') GRAFANA_DOMAIN=$(kubectl -n "${GRAFANA_NAMESPACE}" get ingress --output json | jq -r '.items[0].spec.rules[0].host') +ESP_PLUGIN_SOURCE="https://github.com/sassoftware/grafana-esp-plugin/download/$ESP_PLUGIN_VERSION/sasesp-plugin-$ESP_PLUGIN_VERSION.zip" echo "Adding Grafana to allowed OAuth client redirects..." if [ "${OAUTH_TYPE}" == "viya" ]; then - if [[ "${DRY_RUN}" == false ]]; then - bash register-oauth-client-viya.sh - fi - 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" @@ -94,10 +91,6 @@ if [ "${OAUTH_TYPE}" == "viya" ]; then elif [ "${OAUTH_TYPE}" == "keycloak" ]; then - if [[ "${DRY_RUN}" == false ]]; then - bash register-oauth-client-keycloak.sh - 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" @@ -105,10 +98,6 @@ elif [ "${OAUTH_TYPE}" == "keycloak" ]; then else - 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" @@ -129,12 +118,13 @@ echo "Generating manifests..." generate_manifests if [[ "${DRY_RUN}" == true ]]; then + #GF_INSTALL_PLUGINS_VALUE=$(kubectl -n "${ESP_NAMESPACE}" get deployment/grafana --output json | jq -c '.spec.template.spec.containers[0].env[] | select(.name | contains("GF_INSTALL_PLUGINS")) | .value') exit 0 fi if [[ "${INSTALL_GRAFANA}" == true ]]; then echo "Installing grafana" - kubectl -n "${ESP_NAMESPACE}" apply -f ./manifests/grafana.yaml + kubectl -n "${GRAFANA_NAMESPACE}" apply -f ./manifests/grafana.yaml fi echo "Applying config-map.yaml" diff --git a/install/register-oauth-client-keycloak.sh b/install/register-oauth-client-keycloak.sh index fa4bc04..6606d9a 100644 --- a/install/register-oauth-client-keycloak.sh +++ b/install/register-oauth-client-keycloak.sh @@ -2,6 +2,25 @@ set -e -o pipefail -o nounset +ESP_NAMESPACE="${1}" +KEYCLOAK_SUBPATH="${KEYCLOAK_SUBPATH:-auth}" + +function usage () { + echo "Usage: ${0} " >&2 + exit 1 +} + +[ -z "$KUBECONFIG" ] && { + echo "KUBECONFIG environment variable unset." >&2 + exit 1 +} + +[ -z "${ESP_NAMESPACE}" ] && { + usage +} + +ESP_DOMAIN=$(kubectl -n "${ESP_NAMESPACE}" get ingress --output json | jq -r '.items[0].spec.rules[0].host') + 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}." @@ -40,7 +59,7 @@ function check_requirements() { # 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 \ + _resp=$(curl "https://${ESP_DOMAIN}/${KEYCLOAK_SUBPATH}/realms/master/protocol/openid-connect/token" -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}") @@ -51,7 +70,7 @@ function fetch_keycloak_admin_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 \ + curl "https://${ESP_DOMAIN}/${KEYCLOAK_SUBPATH}/admin/realms/sas-esp/clients/${_client_id}/roles" -k -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${_token}" \ -d "${_role_repr}" @@ -75,7 +94,7 @@ function add_protocol_mapper() { } }") _mapper_body=$(echo "${_mapper_repr}" | jq -r -c) - curl -s -k -X POST \ + curl -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}" \ @@ -85,7 +104,7 @@ function add_protocol_mapper() { 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}") + _kc_clients=$(curl -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: @@ -106,4 +125,11 @@ export OAUTH_CLIENT_ID OAUTH_CLIENT_SECRET=$(echo "${_oauth2_proxy_secret}" | jq -r '.data.OAUTH2_PROXY_CLIENT_SECRET | @base64d') export OAUTH_CLIENT_SECRET +cat < " >&2 + exit 1 +} + +[ -z "$KUBECONFIG" ] && { + echo "KUBECONFIG environment variable unset." >&2 + exit 1 +} + +[ -z "${ESP_NAMESPACE}" ] && { + usage +} + +ESP_DOMAIN=$(kubectl -n "${ESP_NAMESPACE}" get ingress --output json | jq -r '.items[0].spec.rules[0].host') +GRAFANA_DOMAIN=$(kubectl -n "${GRAFANA_NAMESPACE}" get ingress --output json | jq -r '.items[0].spec.rules[0].host') + # Fetch access token to perform admin tasks: function fetch_uaa_admin_token() { - _resp=$(curl "https://${ESP_DOMAIN}/uaa/oauth/token" -s -k -X POST \ + _resp=$(curl "https://${ESP_DOMAIN}/uaa/oauth/token" -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") @@ -15,14 +37,14 @@ function fetch_uaa_admin_token() { # Add Grafana generic OAuth to allowed auth redirects: function add_grafana_auth_redirect_uaa() { _token="$(fetch_uaa_admin_token)" - _redirect="https://${ESP_DOMAIN}/grafana/login/generic_oauth" + _redirect="https://${GRAFANA_DOMAIN}/grafana/login/generic_oauth" - _config=$(curl -s -k -X GET "https://${ESP_DOMAIN}/uaa/oauth/clients/${OAUTH_CLIENT_ID}" -H "Authorization: Bearer ${_token}") + _config=$(curl -k -X GET "https://${ESP_DOMAIN}/uaa/oauth/clients/${OAUTH_CLIENT_ID}" -H "Authorization: Bearer ${_token}") _update_body=$(echo "${_config}" | jq -c -r --arg redirect "${_redirect}" \ '.redirect_uri += [$redirect] | {client_id: .client_id, redirect_uri: .redirect_uri}') - _resp=$(curl "https://${ESP_DOMAIN}/uaa/oauth/clients/${OAUTH_CLIENT_ID}" -s -k -X PUT \ + _resp=$(curl "https://${ESP_DOMAIN}/uaa/oauth/clients/${OAUTH_CLIENT_ID}" -k -X PUT \ -o /dev/null -w "%{http_code}" \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer ${_token}" \ @@ -43,4 +65,14 @@ export UAA_ADMIN UAA_SECRET=$(echo "${_uaa_secret_data}" | jq -r '.data.password | @base64d') export UAA_SECRET +cat < " >&2 + exit 1 +} + +[ -z "$KUBECONFIG" ] && { + echo "KUBECONFIG environment variable unset." >&2 + exit 1 +} + +[ -z "${ESP_NAMESPACE}" ] && { + usage +} + +ESP_DOMAIN=$(kubectl -n "${ESP_NAMESPACE}" get ingress --output json | jq -r '.items[0].spec.rules[0].host') +GRAFANA_DOMAIN=$(kubectl -n "${GRAFANA_NAMESPACE}" get ingress --output json | jq -r '.items[0].spec.rules[0].host') + function fetch_consul_token () { _token=$(kubectl -n "${ESP_NAMESPACE}" get secret sas-consul-client -o go-template='{{ .data.CONSUL_TOKEN | base64decode}}') @@ -48,4 +70,12 @@ function register_oauth_client () { } +cat <