From: BjornMagnussonXA Date: Tue, 22 Mar 2022 09:35:10 +0000 (+0100) Subject: ICS tests with istio and JWTs X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=145762b23535cdffece56c661d9fc0c073cc911e;p=nonrtric.git ICS tests with istio and JWTs Added support to create JWT in keycloak Added support to configure ICS with JWT handling Added support to configure auth with isto Adapted test case to test ICS with auth Issue-ID: NONRTRIC-738 Signed-off-by: BjornMagnussonXA Change-Id: Ifc540778b84e5f4291876a90be78c838652ef8b3 --- diff --git a/test/auto-test/FTC1100.sh b/test/auto-test/FTC1100.sh index ce95a774..0ff083c1 100755 --- a/test/auto-test/FTC1100.sh +++ b/test/auto-test/FTC1100.sh @@ -18,13 +18,19 @@ # -TC_ONELINE_DESCR="ICS full interfaces walkthrough" +TC_ONELINE_DESCR="ICS full interfaces walkthrough - with or without istio enabled" + +USE_ISTIO=1 #App names to include in the test when running docker, space separated list DOCKER_INCLUDED_IMAGES="ICS PRODSTUB CR RICSIM CP HTTPPROXY NGW KUBEPROXY" #App names to include in the test when running kubernetes, space separated list -KUBE_INCLUDED_IMAGES="PRODSTUB CR ICS RICSIM CP HTTPPROXY KUBEPROXY NGW" +if [ $USE_ISTIO -eq 0 ]; then + KUBE_INCLUDED_IMAGES="PRODSTUB CR ICS RICSIM CP HTTPPROXY KUBEPROXY NGW" +else + KUBE_INCLUDED_IMAGES="PRODSTUB CR ICS RICSIM CP HTTPPROXY KUBEPROXY NGW KEYCLOAK ISTIO AUTHSIDECAR" +fi #Prestarted app (not started by script) to include in the test when running kubernetes, space separated list KUBE_PRESTARTED_IMAGES="" @@ -48,18 +54,77 @@ FLAT_A1_EI="1" clean_environment +if [ $USE_ISTIO -eq 1 ]; then + echo -e $RED"#########################################"$ERED + echo -e $RED"# Work around istio jwks cache"$ERED + echo -e $RED"# Cycle istiod down and up to clear cache"$ERED + echo "" + __kube_scale deployment istiod istio-system 0 + __kube_scale deployment istiod istio-system 1 + echo -e $RED"# Cycle istiod done" + echo -e $RED"#########################################"$ERED + + istio_enable_istio_namespace $KUBE_SIM_NAMESPACE + istio_enable_istio_namespace $KUBE_NONRTRIC_NAMESPACE +fi + start_kube_proxy +set_kubeproxy_debug -use_ics_rest_https +if [ $USE_ISTIO -eq 1 ]; then + use_ics_rest_http -use_prod_stub_https + use_prod_stub_http -use_simulator_https + use_simulator_http -use_cr_https + use_cr_http +else + use_ics_rest_https + + use_prod_stub_https + + use_simulator_https + + use_cr_https +fi start_http_proxy +if [ $USE_ISTIO -eq 1 ]; then + start_keycloak + + keycloak_api_obtain_admin_token + + keycloak_api_create_realm nrtrealm true 60 + keycloak_api_create_confidential_client nrtrealm icsc + keycloak_api_generate_client_secret nrtrealm icsc + keycloak_api_get_client_secret nrtrealm icsc + + keycloak_api_get_client_token nrtrealm icsc + + CLIENT_TOKEN=$(keycloak_api_read_client_token nrtrealm icsc) + echo "CLIENT_TOKEN: "$CLIENT_TOKEN + + ICS_SEC=$(keycloak_api_read_client_secret nrtrealm icsc) + echo "ICS_SEC: "$ICS_SEC + + istio_req_auth_by_jwks $PROD_STUB_APP_NAME $KUBE_SIM_NAMESPACE KUBEPROXY "$KUBE_PROXY_ISTIO_JWKS_KEYS" + istio_auth_policy_by_issuer $PROD_STUB_APP_NAME $KUBE_SIM_NAMESPACE KUBEPROXY + + istio_req_auth_by_jwksuri $PROD_STUB_APP_NAME $KUBE_SIM_NAMESPACE nrtrealm + istio_auth_policy_by_realm $PROD_STUB_APP_NAME $KUBE_SIM_NAMESPACE nrtrealm + + istio_req_auth_by_jwks $CR_APP_NAME $KUBE_SIM_NAMESPACE KUBEPROXY "$KUBE_PROXY_ISTIO_JWKS_KEYS" + istio_auth_policy_by_issuer $CR_APP_NAME $KUBE_SIM_NAMESPACE KUBEPROXY + + istio_req_auth_by_jwksuri $CR_APP_NAME $KUBE_SIM_NAMESPACE nrtrealm + istio_auth_policy_by_realm $CR_APP_NAME $KUBE_SIM_NAMESPACE nrtrealm + + ics_configure_sec nrtrealm icsc $ICS_SEC + +fi + start_ics NOPROXY $SIM_GROUP/$ICS_COMPOSE_DIR/$ICS_CONFIG_FILE #Change NOPROXY to PROXY to run with http proxy if [ $RUNMODE == "KUBE" ]; then @@ -85,11 +150,19 @@ start_cr 1 CB_JOB="$PROD_STUB_SERVICE_PATH$PROD_STUB_JOB_CALLBACK" CB_SV="$PROD_STUB_SERVICE_PATH$PROD_STUB_SUPERVISION_CALLBACK" #Targets for ei jobs -TARGET1="$RIC_SIM_HTTPX://ricsim_g3_1:$RIC_SIM_PORT/datadelivery" -TARGET2="$RIC_SIM_HTTPX://ricsim_g3_2:$RIC_SIM_PORT/datadelivery" -TARGET3="$RIC_SIM_HTTPX://ricsim_g3_3:$RIC_SIM_PORT/datadelivery" -TARGET8="$RIC_SIM_HTTPX://ricsim_g3_4:$RIC_SIM_PORT/datadelivery" -TARGET10="$RIC_SIM_HTTPX://ricsim_g3_4:$RIC_SIM_PORT/datadelivery" +if [ $RUNMODE == "KUBE" ]; then + TARGET1="$RIC_SIM_HTTPX://ricsim-g3-1.ricsim-g3.$KUBE_A1SIM_NAMESPACE:$RIC_SIM_PORT/datadelivery" + TARGET2="$RIC_SIM_HTTPX://ricsim-g3-2.ricsim-g3.$KUBE_A1SIM_NAMESPACE:$RIC_SIM_PORT/datadelivery" + TARGET3="$RIC_SIM_HTTPX://ricsim-g3-3.ricsim-g3.$KUBE_A1SIM_NAMESPACE:$RIC_SIM_PORT/datadelivery" + TARGET8="$RIC_SIM_HTTPX://ricsim-g3-4.ricsim-g3.$KUBE_A1SIM_NAMESPACE:$RIC_SIM_PORT/datadelivery" + TARGET10="$RIC_SIM_HTTPX://ricsim-g3-4.ricsim-g3.$KUBE_A1SIM_NAMESPACE:$RIC_SIM_PORT/datadelivery" +else + TARGET1="$RIC_SIM_HTTPX://ricsim_g3_1:$RIC_SIM_PORT/datadelivery" + TARGET2="$RIC_SIM_HTTPX://ricsim_g3_2:$RIC_SIM_PORT/datadelivery" + TARGET3="$RIC_SIM_HTTPX://ricsim_g3_3:$RIC_SIM_PORT/datadelivery" + TARGET8="$RIC_SIM_HTTPX://ricsim_g3_4:$RIC_SIM_PORT/datadelivery" + TARGET10="$RIC_SIM_HTTPX://ricsim_g3_4:$RIC_SIM_PORT/datadelivery" +fi #Targets for info jobs TARGET101="http://localhost:80/target" # Dummy target, no target for info data in this env... diff --git a/test/common/api_curl.sh b/test/common/api_curl.sh index e99f5791..6058e083 100644 --- a/test/common/api_curl.sh +++ b/test/common/api_curl.sh @@ -159,12 +159,15 @@ __do_curl_to_api() { echo "-000" return 1 fi - + jwt="" + if [ ! -z "$KUBE_PROXY_CURL_JWT" ]; then + jwt=" -H "\""Authorization: Bearer $KUBE_PROXY_CURL_JWT"\" + fi if [ $__ADAPTER_TYPE == "REST" ]; then url=" "${__ADAPTER}${input_url} oper=" -X "$oper curlString="curl -k $proxyflag "${oper}${timeout}${httpcode}${accept}${content}${url}${file} - echo " CMD: "$curlString >> $HTTPLOG + echo " CMD: $curlString $jwt" >> $HTTPLOG if [ $# -gt 3 ]; then echo " FILE: $(<$fname)" >> $HTTPLOG fi @@ -174,7 +177,11 @@ __do_curl_to_api() { while [ $maxretries -ge 0 ]; do let maxretries=maxretries-1 - res=$($curlString) + if [ ! -z "$KUBE_PROXY_CURL_JWT" ]; then + res=$($curlString -H "Authorization: Bearer $KUBE_PROXY_CURL_JWT") + else + res=$($curlString) + fi retcode=$? if [ $retcode -ne 0 ]; then echo " RETCODE: "$retcode >> $HTTPLOG diff --git a/test/common/authsidecar_api_functions.sh b/test/common/authsidecar_api_functions.sh new file mode 100644 index 00000000..594aa31c --- /dev/null +++ b/test/common/authsidecar_api_functions.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +# ============LICENSE_START=============================================== +# Copyright (C) 2020 Nordix Foundation. All rights reserved. +# ======================================================================== +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============LICENSE_END================================================= +# + +# This is a script that contains container/service management functions +# for AUTHSIDECAR + +################ Test engine functions ################ + +# Create the image var used during the test +# arg: (selects staging, snapshot, release etc) +# is present only for images with staging, snapshot,release tags +__AUTHSIDECAR_imagesetup() { + __check_and_create_image_var AUTHSIDECAR "AUTHSIDECAR_IMAGE" "AUTHSIDECAR_IMAGE_BASE" "AUTHSIDECAR_IMAGE_TAG" $1 "$AUTHSIDECAR_DISPLAY_NAME" +} + +# Pull image from remote repo or use locally built image +# arg: +# Shall be used for images allowing overriding. For example use a local image when test is started to use released images +# Shall be used for images that does not allow overriding +# Both var may contain: 'remote', 'remote-remove' or 'local' +__AUTHSIDECAR_imagepull() { + __check_and_pull_image $1 "$AUTHSIDECAR_DISPLAY_NAME" $AUTHSIDECAR_APP_NAME AUTHSIDECAR_IMAGE +} + +# Build image (only for simulator or interfaces stubs owned by the test environment) +# arg: (selects staging, snapshot, release etc) +# is present only for images with staging, snapshot,release tags +__AUTHSIDECAR_imagebuild() { + echo -e $RED"Image for app AUTHSIDECAR shall never be built"$ERED +} + +# Generate a string for each included image using the app display name and a docker images format string +# If a custom image repo is used then also the source image from the local repo is listed +# arg: +__AUTHSIDECAR_image_data() { + echo -e "$AUTHSIDECAR_DISPLAY_NAME\t$(docker images --format $1 $AUTHSIDECAR_IMAGE)" >> $2 + if [ ! -z "$AUTHSIDECAR_IMAGE_SOURCE" ]; then + echo -e "-- source image --\t$(docker images --format $1 $AUTHSIDECAR_IMAGE_SOURCE)" >> $2 + fi +} + +# Scale kubernetes resources to zero +# All resources shall be ordered to be scaled to 0, if relevant. If not relevant to scale, then do no action. +# This function is called for apps fully managed by the test script +__AUTHSIDECAR_kube_scale_zero() { + : +} + +# Scale kubernetes resources to zero and wait until this has been accomplished, if relevant. If not relevant to scale, then do no action. +# This function is called for prestarted apps not managed by the test script. +__AUTHSIDECAR_kube_scale_zero_and_wait() { + : +} + +# Delete all kube resouces for the app +# This function is called for apps managed by the test script. +__AUTHSIDECAR_kube_delete_all() { + : +} + +# Store docker logs +# This function is called for apps managed by the test script. +# args: +__AUTHSIDECAR_store_docker_logs() { + if [ $RUNMODE == "KUBE" ]; then + kubectl $KUBECONF logs -l "autotest=AUTHSIDECAR" -A --tail=-1 > $1$2_authsidecar.log 2>&1 + fi +} + +# Initial setup of protocol, host and ports +# This function is called for apps managed by the test script. +# args: - +__AUTHSIDECAR_initial_setup() { + : +} + +# Set app short-name, app name and namespace for logging runtime statistics of kubernets pods or docker containers +# For docker, the namespace shall be excluded +# This function is called for apps managed by the test script as well as for prestarted apps. +# args: - +__AUTHSIDECAR_statisics_setup() { + echo "" +} + +# Check application requirements, e.g. helm, the the test needs. Exit 1 if req not satisfied +# args: - +__AUTHSIDECAR_test_requirements() { + : +} + +####################################################### + +# This app is intended as a sidecard container - no specific test functions \ No newline at end of file diff --git a/test/common/chartmus_api_functions.sh b/test/common/chartmus_api_functions.sh index f7e268ed..7c7a365c 100644 --- a/test/common/chartmus_api_functions.sh +++ b/test/common/chartmus_api_functions.sh @@ -201,7 +201,7 @@ start_chart_museum() { if [ $retcode_p -eq 0 ]; then echo -e " Using existing $CHART_MUS_APP_NAME deployment and service" - echo " Setting RC replicas=1" + echo " Setting CHARTMUS replicas=1" __kube_scale deployment $CHART_MUS_APP_NAME $KUBE_SIM_NAMESPACE 1 fi diff --git a/test/common/clean_docker.sh b/test/common/clean_docker.sh index 70e42da7..7fe6c39f 100755 --- a/test/common/clean_docker.sh +++ b/test/common/clean_docker.sh @@ -18,7 +18,7 @@ # Script to clean all docker containers having the label 'nrttest_app', i.e started by autotest -echo "Will stop and remove all docker containers with label 'nrttest_app'" +echo "Stopping and removing all docker containers with label 'nrttest_app'" echo " Stopping containers..." docker stop $(docker ps -qa --filter "label=nrttest_app") 2> /dev/null echo " Removing stopped containers..." diff --git a/test/common/clean_kube.sh b/test/common/clean_kube.sh index f25d52c5..5997abc3 100755 --- a/test/common/clean_kube.sh +++ b/test/common/clean_kube.sh @@ -86,7 +86,7 @@ __kube_wait_for_zero_count() { __kube_delete_all_resources() { echo " Delete all in namespace $1 ..." namespace=$1 - resources="deployments replicaset statefulset services pods configmaps pvc serviceaccounts secrets" + resources="deployments replicaset statefulset services pods configmaps pvc serviceaccounts secrets authorizationpolicies requestauthentications" for restype in $resources; do result=$(kubectl $KUBECONF get $restype -n $namespace -o jsonpath='{.items[?(@.metadata.labels.autotest)].metadata.name}') if [ $? -eq 0 ] && [ ! -z "$result" ]; then diff --git a/test/common/ics_api_functions.sh b/test/common/ics_api_functions.sh index 0df2ae41..2152de9e 100644 --- a/test/common/ics_api_functions.sh +++ b/test/common/ics_api_functions.sh @@ -89,6 +89,7 @@ __ICS_store_docker_logs() { # args: - __ICS_initial_setup() { use_ics_rest_http + export ICS_SIDECAR_JWT_FILE="" } # Set app short-name, app name and namespace for logging runtime statistics of kubernets pods or docker containers @@ -278,6 +279,10 @@ start_ics() { # Create app input_yaml=$SIM_GROUP"/"$ICS_COMPOSE_DIR"/"app.yaml output_yaml=$PWD/tmp/ics_app.yaml + if [ -z "$ICS_SIDECAR_JWT_FILE" ]; then + cat $input_yaml | sed '/#ICS_JWT_START/,/#ICS_JWT_STOP/d' > $PWD/tmp/ics_app_tmp.yaml + input_yaml=$PWD/tmp/ics_app_tmp.yaml + fi __kube_create_instance app $ICS_APP_NAME $input_yaml $output_yaml fi @@ -448,7 +453,7 @@ set_ics_debug() { # (Function for test scripts) set_ics_trace() { echo -e $BOLD"Setting ics trace logging"$EBOLD - curlString="$ICS_SERVICE_PATH/actuator/loggers/org.oransc.information -X POST -H Content-Type:application/json -d {\"configuredLevel\":\"trace\"}" + curlString="$ICS_SERVICE_PATH$ICS_ACTUATOR -X POST -H Content-Type:application/json -d {\"configuredLevel\":\"trace\"}" result=$(__do_curl "$curlString") if [ $? -ne 0 ]; then __print_err "Could not set trace mode" $@ @@ -2461,4 +2466,17 @@ ics_kube_pvc_reset() { __log_test_pass return 0 +} + +# args: +ics_configure_sec() { + export ICS_CREDS_GRANT_TYPE="client_credentials" + export ICS_CREDS_CLIENT_SECRET=$3 + export ICS_CREDS_CLIENT_ID=$2 + export ICS_AUTH_SERVICE_URL=$KEYCLOAK_SERVICE_PATH$KEYCLOAK_TOKEN_URL_PREFIX/$1/protocol/openid-connect/token + export ICS_SIDECAR_MOUNT="/token-cache" + export ICS_SIDECAR_JWT_FILE=$ICS_SIDECAR_MOUNT"/jwt.txt" + + export AUTHSIDECAR_APP_NAME + export AUTHSIDECAR_DISPLAY_NAME } \ No newline at end of file diff --git a/test/common/istio_api_functions.sh b/test/common/istio_api_functions.sh new file mode 100644 index 00000000..2afc3f9c --- /dev/null +++ b/test/common/istio_api_functions.sh @@ -0,0 +1,249 @@ +#!/bin/bash + +# ============LICENSE_START=============================================== +# Copyright (C) 2021 Nordix Foundation. All rights reserved. +# ======================================================================== +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============LICENSE_END================================================= +# + +# This is a script that contains function to handle helm on localhost + + +################ Test engine functions ################ + +# Create the image var used during the test +# arg: (selects staging, snapshot, release etc) +# is present only for images with staging, snapshot,release tags +__ISTIO_imagesetup() { + : +} + +# Pull image from remote repo or use locally built image +# arg: +# Shall be used for images allowing overriding. For example use a local image when test is started to use released images +# Shall be used for images that does not allow overriding +# Both var may contain: 'remote', 'remote-remove' or 'local' +__ISTIO_imagepull() { + : +} + +# Build image (only for simulator or interfaces stubs owned by the test environment) +# arg: (selects staging, snapshot, release etc) +# is present only for images with staging, snapshot,release tags +__ISTIO_imagebuild() { + : +} + +# Generate a string for each included image using the app display name and a docker images format string +# If a custom image repo is used then also the source image from the local repo is listed +# arg: +__ISTIO_image_data() { + : +} + +# Scale kubernetes resources to zero +# All resources shall be ordered to be scaled to 0, if relevant. If not relevant to scale, then do no action. +# This function is called for apps fully managed by the test script +__ISTIO_kube_scale_zero() { + : +} + +# Scale kubernetes resources to zero and wait until this has been accomplished, if relevant. If not relevant to scale, then do no action. +# This function is called for prestarted apps not managed by the test script. +__ISTIO_kube_scale_zero_and_wait() { + : +} + +# Delete all kube resouces for the app +# This function is called for apps managed by the test script. +__ISTIO_kube_delete_all() { + __kube_delete_all_resources $KUBE_NONRTRIC_NAMESPACE autotest ISTIO +} + +# Store docker logs +# This function is called for apps managed by the test script. +# args: +__ISTIO_store_docker_logs() { + : +} + +# Initial setup of protocol, host and ports +# This function is called for apps managed by the test script. +# args: - +__ISTIO_initial_setup() { + # See jwt-info.txt in simulator-group/kubeproxy for detailed info + KUBE_PROXY_CURL_JWT=$ISTIO_GENERIC_JWT + KUBE_PROXY_ISTIO_JWKS_KEYS=$ISTIO_GENERIC_JWKS_KEY +} + +# Set app short-name, app name and namespace for logging runtime statistics of kubernets pods or docker containers +# For docker, the namespace shall be excluded +# This function is called for apps managed by the test script as well as for prestarted apps. +# args: - +__ISTIO_statisics_setup() { + : +} + +# Check application requirements, e.g. helm, the the test needs. Exit 1 if req not satisfied +# args: - +__ISTIO_test_requirements() { + + kubectl $KUBECONF get requestauthentications -A &> /dev/null + if [ $? -ne 0 ]; then + echo $RED" Istio api: kubectl get requestauthentications is not installed" + exit 1 + fi + kubectl $KUBECONF get authorizationpolicies -A &> /dev/null + if [ $? -ne 0 ]; then + echo $RED" Istio api: kubectl get authorizationpolicies is not installed" + exit 1 + fi +} + +####################################################### + + +# Enable istio on namespace +# arg: +istio_enable_istio_namespace() { + __log_conf_start $@ + if [ $# -ne 1 ]; then + __print_err "" $@ + return 1 + fi + __kube_create_namespace $1 + __kube_label_non_ns_instance ns $1 "istio-injection=enabled" + __log_conf_ok + return 0 +} + +# Request authorization by jwksuri +# args: +istio_req_auth_by_jwksuri() { + __log_conf_start $@ + if [ $# -ne 3 ]; then + __print_err " " $@ + return 1 + fi + name="ra-jwksuri-"$3"-"$1"-"$2 + export ISTIO_TEMPLATE_REPLACE_RA_NAME=$(echo $name | tr '[:upper:]' '[:lower:]') + export ISTIO_TEMPLATE_REPLACE_RA_NS=$2 + export ISTIO_TEMPLATE_REPLACE_RA_APP_NAME=$1 + export ISTIO_TEMPLATE_REPLACE_RA_ISSUER=$KEYCLOAK_ISSUER_PATH$KEYCLOAK_TOKEN_URL_PREFIX/$3 + export ISTIO_TEMPLATE_REPLACE_RA_JWKSURI=$KEYCLOAK_SERVICE_PATH$KEYCLOAK_TOKEN_URL_PREFIX/$3/protocol/openid-connect/certs + inputfile=$SIM_GROUP/$ISTIO_COMPOSE_DIR/ra-jwksuri-template.yaml + outputfile=tmp/$ISTIO_TEMPLATE_REPLACE_RA_NAME".yaml" + envsubst < $inputfile > $outputfile + if [ $? -ne 0 ]; then + __log_conf_fail_general "Cannot substitute yaml: $inputfile" + return 1 + fi + kubectl $KUBECONF apply -f $outputfile &> tmp/kubeerr + if [ $? -ne 0 ]; then + __log_conf_fail_general "Cannot apply yaml: $outputfile" + return 1 + fi + __log_conf_ok + return 0 +} + +# Request authorization by jwks (inline keys) +# args: +istio_req_auth_by_jwks() { + __log_conf_start $@ + if [ $# -ne 4 ]; then + __print_err " " $@ + return 1 + fi + name="ra-jwks-"$3"-"$1"-"$2 + export ISTIO_TEMPLATE_REPLACE_RA_NAME=$(echo $name | tr '[:upper:]' '[:lower:]') + export ISTIO_TEMPLATE_REPLACE_RA_NS=$2 + export ISTIO_TEMPLATE_REPLACE_RA_APP_NAME=$1 + export ISTIO_TEMPLATE_REPLACE_RA_ISSUER=$3 + export ISTIO_TEMPLATE_REPLACE_RA_JWKS=$4 + inputfile=$SIM_GROUP/$ISTIO_COMPOSE_DIR/ra-jwks-template.yaml + outputfile=tmp/$ISTIO_TEMPLATE_REPLACE_RA_NAME".yaml" + envsubst < $inputfile > $outputfile + if [ $? -ne 0 ]; then + __log_conf_fail_general "Cannot substitute yaml: $inputfile" + return 1 + fi + kubectl $KUBECONF apply -f $outputfile &> tmp/kubeerr + if [ $? -ne 0 ]; then + __log_conf_fail_general "Cannot apply yaml: $outputfile" + return 1 + fi + __log_conf_ok + return 0 +} + +# Authorization policy - by realm +# args: +istio_auth_policy_by_realm() { + __log_conf_start $@ + if [ $# -ne 3 ]; then + __print_err " " $@ + return 1 + fi + name="ap-realm-"$3"-"$1"-"$2 + export ISTIO_TEMPLATE_REPLACE_AP_NAME=$(echo $name | tr '[:upper:]' '[:lower:]') + export ISTIO_TEMPLATE_REPLACE_AP_NS=$2 + export ISTIO_TEMPLATE_REPLACE_AP_APP_NAME=$1 + export ISTIO_TEMPLATE_REPLACE_AP_PRINCIPAL="$KEYCLOAK_ISSUER_PATH$KEYCLOAK_TOKEN_URL_PREFIX/$3/*" + inputfile=$SIM_GROUP/$ISTIO_COMPOSE_DIR/ap-principal-template.yaml + outputfile=tmp/$ISTIO_TEMPLATE_REPLACE_AP_NAME".yaml" + envsubst < $inputfile > $outputfile + if [ $? -ne 0 ]; then + __log_conf_fail_general "Cannot substitute yaml: $inputfile" + return 1 + fi + kubectl $KUBECONF apply -f $outputfile &> tmp/kubeerr + if [ $? -ne 0 ]; then + __log_conf_fail_general "Cannot apply yaml: $outputfile" + return 1 + fi + __log_conf_ok + return 0 +} + +# Authorization policy - by issuer +# args: +istio_auth_policy_by_issuer() { + __log_conf_start $@ + if [ $# -ne 3 ]; then + __print_err " " $@ + return 1 + fi + name="ap-iss-"$3"-"$1"-"$2 + export ISTIO_TEMPLATE_REPLACE_AP_NAME=$(echo $name | tr '[:upper:]' '[:lower:]') + export ISTIO_TEMPLATE_REPLACE_AP_NS=$2 + export ISTIO_TEMPLATE_REPLACE_AP_APP_NAME=$1 + export ISTIO_TEMPLATE_REPLACE_AP_PRINCIPAL="$3/*" + inputfile=$SIM_GROUP/$ISTIO_COMPOSE_DIR/ap-principal-template.yaml + outputfile=tmp/$ISTIO_TEMPLATE_REPLACE_AP_NAME".yaml" + envsubst < $inputfile > $outputfile + if [ $? -ne 0 ]; then + __log_conf_fail_general "Cannot substitute yaml: $inputfile" + return 1 + fi + kubectl $KUBECONF apply -f $outputfile &> tmp/kubeerr + if [ $? -ne 0 ]; then + __log_conf_fail_general "Cannot apply yaml: $outputfile" + return 1 + fi + __log_conf_ok + return 0 +} + + diff --git a/test/common/keycloak_api_functions.sh b/test/common/keycloak_api_functions.sh new file mode 100644 index 00000000..828dcb39 --- /dev/null +++ b/test/common/keycloak_api_functions.sh @@ -0,0 +1,616 @@ +#!/bin/bash + +# ============LICENSE_START=============================================== +# Copyright (C) 2021 Nordix Foundation. All rights reserved. +# ======================================================================== +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============LICENSE_END================================================= +# + +# This is a script that contains container/service management functions and test functions for Keycloak + + +################ Test engine functions ################ + +# Create the image var used during the test +# arg: (selects staging, snapshot, release etc) +# is present only for images with staging, snapshot,release tags +__KEYCLOAK_imagesetup() { + __check_and_create_image_var KEYCLOAK "KEYCLOAK_IMAGE" "KEYCLOAK_IMAGE_BASE" "KEYCLOAK_IMAGE_TAG" REMOTE_OTHER "$KEYCLOAK_DISPLAY_NAME" +} + +# Pull image from remote repo or use locally built image +# arg: +# Shall be used for images allowing overriding. For example use a local image when test is started to use released images +# Shall be used for images that does not allow overriding +# Both var may contain: 'remote', 'remote-remove' or 'local' +__KEYCLOAK_imagepull() { + __check_and_pull_image $2 "$KEYCLOAK_DISPLAY_NAME" $KEYCLOAK_APP_NAME KEYCLOAK_IMAGE +} + +# Build image (only for simulator or interfaces stubs owned by the test environment) +# arg: (selects staging, snapshot, release etc) +# is present only for images with staging, snapshot,release tags +__KEYCLOAK_imagebuild() { + echo -e $RED" Image for app KEYCLOAK shall never be built"$ERED +} + +# Generate a string for each included image using the app display name and a docker images format string +# If a custom image repo is used then also the source image from the local repo is listed +# arg: +__KEYCLOAK_image_data() { + echo -e "$KEYCLOAK_DISPLAY_NAME\t$(docker images --format $1 $KEYCLOAK_IMAGE)" >> $2 + if [ ! -z "$KEYCLOAK_IMAGE_SOURCE" ]; then + echo -e "-- source image --\t$(docker images --format $1 $KEYCLOAK_IMAGE_SOURCE)" >> $2 + fi +} + +# Scale kubernetes resources to zero +# All resources shall be ordered to be scaled to 0, if relevant. If not relevant to scale, then do no action. +# This function is called for apps fully managed by the test script +__KEYCLOAK_kube_scale_zero() { + __kube_scale_all_resources $KUBE_KEYCLOAK_NAMESPACE autotest KEYCLOAK +} + +# Scale kubernetes resources to zero and wait until this has been accomplished, if relevant. If not relevant to scale, then do no action. +# This function is called for prestarted apps not managed by the test script. +__KEYCLOAK_kube_scale_zero_and_wait() { + echo -e $RED" KEYCLOAK app is not scaled in this state"$ERED +} + +# Delete all kube resouces for the app +# This function is called for apps managed by the test script. +__KEYCLOAK_kube_delete_all() { + __kube_delete_all_resources $KUBE_KEYCLOAK_NAMESPACE autotest KEYCLOAK +} + +# Store docker logs +# This function is called for apps managed by the test script. +# args: +__KEYCLOAK_store_docker_logs() { + if [ $RUNMODE == "KUBE" ]; then + kubectl $KUBECONF logs -l "autotest=KEYCLOAK" -n $KUBE_KEYCLOAK_NAMESPACE --tail=-1 > $1$2_keycloak.log 2>&1 + else + docker logs $KEYCLOAK_APP_NAME > $1$2_keycloak.log 2>&1 + fi +} + +# Initial setup of protocol, host and ports +# This function is called for apps managed by the test script. +# args: - +__KEYCLOAK_initial_setup() { + use_keycloak_http +} + +# Set app short-name, app name and namespace for logging runtime statistics of kubernets pods or docker containers +# For docker, the namespace shall be excluded +# This function is called for apps managed by the test script as well as for prestarted apps. +# args: - +__KEYCLOAK_statisics_setup() { + if [ $RUNMODE == "KUBE" ]; then + echo "KEYCLOAK $KEYCLOAK_APP_NAME $KUBE_KEYCLOAK_NAMESPACE" + else + echo "KEYCLOAK $KEYCLOAK_APP_NAME" + fi +} + +# Check application requirements, e.g. helm, the the test needs. Exit 1 if req not satisfied +# args: - +__KEYCLOAK_test_requirements() { + which jq > /dev/null + if [ $? -ne 0 ]; then + echo $RED" 'jq' is required to run tests. Pls install 'jq'" + return 1 + fi +} + +####################################################### + +# Set http as the protocol to use for all communication to the Keycloak +# args: - +# (Function for test scripts) +use_keycloak_http() { + __keycloak_set_protocoll "http" $KEYCLOAK_INTERNAL_PORT $KEYCLOAK_EXTERNAL_PORT +} + +# Set https as the protocol to use for all communication to the Keycloak +# args: - +# (Function for test scripts) +use_keycloak_https() { + __keycloak_set_protocoll "https" $KEYCLOAK_INTERNAL_SECURE_PORT $KEYCLOAK_EXTERNAL_SECURE_PORT +} + +# Setup paths to svc/container for internal and external access +# args: +__keycloak_set_protocoll() { + echo -e $BOLD"$KEYCLOAK_DISPLAY_NAME protocol setting"$EBOLD + echo -e " Using $BOLD $1 $EBOLD towards $KEYCLOAK_DISPLAY_NAME" + + ## Access to Keycloak + + KEYCLOAK_SERVICE_PATH=$1"://"$KEYCLOAK_APP_NAME":"$2 # docker access, container->container and script->container via proxy + KEYCLOAK_SERVICE_PORT=$2 + KEYCLOAK_SERVICE_HOST=$KEYCLOAK_APP_NAME + KEYCLOAK_ISSUER_PATH=$1"://"$KEYCLOAK_APP_NAME + if [ $RUNMODE == "KUBE" ]; then + KEYCLOAK_SERVICE_PATH=$1"://"$KEYCLOAK_APP_NAME.$KUBE_KEYCLOAK_NAMESPACE":"$3 # kube access, pod->svc and script->svc via proxy + KEYCLOAK_SERVICE_PORT=$3 + KEYCLOAK_SERVICE_HOST=$KEYCLOAK_APP_NAME.$KUBE_KEYCLOAK_NAMESPACE + KEYCLOAK_ISSUER_PATH=$1"://"$KEYCLOAK_APP_NAME.$KUBE_KEYCLOAK_NAMESPACE + fi + KEYCLOAK_SERVICE_HTTPX=$1 + + echo "" +} + +### Admin API functions Keycloak + +########################### +### Keycloak functions +########################### + +# Export env vars for config files, docker compose and kube resources +# args: +__keycloak_export_vars() { + export KEYCLOAK_APP_NAME + export KEYCLOAK_DISPLAY_NAME + + export DOCKER_SIM_NWNAME + export KUBE_KEYCLOAK_NAMESPACE + + export KEYCLOAK_IMAGE + export KEYCLOAK_INTERNAL_PORT + export KEYCLOAK_EXTERNAL_PORT + + export KEYCLOAK_ADMIN_USER + export KEYCLOAK_ADMIN_PWD + export KEYCLOAK_KC_PROXY +} + + +# Start the Keycloak in the simulator group +# args: - +# (Function for test scripts) +start_keycloak() { + + echo -e $BOLD"Starting $KEYCLOAK_DISPLAY_NAME"$EBOLD + + if [ $RUNMODE == "KUBE" ]; then + + # Check if app shall be fully managed by the test script + __check_included_image "KEYCLOAK" + retcode_i=$? + + # Check if app shall only be used by the testscipt + __check_prestarted_image "KEYCLOAK" + retcode_p=$? + + if [ $retcode_i -ne 0 ] && [ $retcode_p -ne 0 ]; then + echo -e $RED"The $KEYCLOAK_NAME app is not included as managed nor prestarted in this test script"$ERED + echo -e $RED"The $KEYCLOAK_APP_NAME will not be started"$ERED + exit + fi + if [ $retcode_i -eq 0 ] && [ $retcode_p -eq 0 ]; then + echo -e $RED"The $KEYCLOAK_APP_NAME app is included both as managed and prestarted in this test script"$ERED + echo -e $RED"The $KEYCLOAK_APP_NAME will not be started"$ERED + exit + fi + + if [ $retcode_p -eq 0 ]; then + echo -e " Using existing $KEYCLOAK_APP_NAME deployment and service" + echo " Setting keycloak replicas=1" + __kube_scale deployment $KEYCLOAK_APP_NAME $KUBE_KEYCLOAK_NAMESPACE 1 + fi + + if [ $retcode_i -eq 0 ]; then + echo -e " Creating $KEYCLOAK_APP_NAME deployment and service" + + __kube_create_namespace $KUBE_KEYCLOAK_NAMESPACE + + __keycloak_export_vars + + # Create service and app + input_yaml=$SIM_GROUP"/"$KEYCLOAK_COMPOSE_DIR"/"svc_app.yaml + output_yaml=$PWD/tmp/keycloak_svc_app.yaml + __kube_create_instance "service/app" $KEYCLOAK_APP_NAME $input_yaml $output_yaml + + fi + + __check_service_start $KEYCLOAK_APP_NAME $KEYCLOAK_SERVICE_PATH$KEYCLOAK_ALIVE_URL + else + + # Check if docker app shall be fully managed by the test script + __check_included_image 'KEYCLOAK' + if [ $? -eq 1 ]; then + echo -e $RED"The Keycloak app is not included as managed in this test script"$ERED + echo -e $RED"The Keycloak will not be started"$ERED + exit + fi + + __keycloak_export_vars + + __start_container $KEYCLOAK_COMPOSE_DIR "" NODOCKERARGS 1 $KEYCLOAK_APP_NAME + + __check_service_start $KEYCLOAK_APP_NAME $KEYCLOAK_SERVICE_PATH$KEYCLOAK_ALIVE_URL + fi + echo "" + return 0 +} + +# Excute a curl cmd towards the keycloak and check the response code is 2XX. +# args: +# resp: if return code is 0 otherwise +__execute_curl_to_keycloak() { + + proxyflag="" + if [ ! -z "$KUBE_PROXY_PATH" ]; then + if [ $KUBE_PROXY_HTTPX == "http" ]; then + proxyflag=" --proxy $KUBE_PROXY_PATH" + else + proxyflag=" --proxy-insecure --proxy $KUBE_PROXY_PATH" + fi + fi + __cmd="curl -skw %{http_code} $proxyflag $1" + echo " CMD: $__cmd" >> $HTTPLOG + res=$($__cmd) + echo " RESP: $res" >> $HTTPLOG + retcode=$? + if [ $retcode -ne 0 ]; then + __log_conf_fail_general " Fatal error when executing curl, response: "$retcode + echo "$res" + return 1 + fi + status=${res:${#res}-3} + if [ $status -lt 200 ] && [ $status -gt 299 ]; then + __log_conf_fail_status_code "2XX" $status + echo "$res" + return 1 + fi + echo ${res:0:${#res}-3} + return 0 +} + +# # Excute a curl cmd towards the keycloak and check the response code is 2XX. +# # args: +# # resp: if return code is 0 otherwise +# __execute_curl_to_keycloak2() { + +# # TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S") +# # echo "(${BASH_LINENO[0]}) - ${TIMESTAMP}: ${FUNCNAME[0]}" $@ >> $HTTPLOG +# proxyflag="" +# if [ ! -z "$KUBE_PROXY_PATH" ]; then +# if [ $KUBE_PROXY_HTTPX == "http" ]; then +# proxyflag=" --proxy $KUBE_PROXY_PATH" +# else +# proxyflag=" --proxy-insecure --proxy $KUBE_PROXY_PATH" +# fi +# fi +# __cmd="curl -skw %{http_code} $proxyflag "$(< $1) +# echo " CMD: $__cmd" >> $HTTPLOG +# res=$(curl -skw %{http_code} $proxyflag $(< $1)) +# echo " RESP: $res" >> $HTTPLOG +# retcode=$? +# if [ $retcode -ne 0 ]; then +# __log_conf_fail_general " Fatal error when executing curl, response: "$retcode +# echo "$res" +# return 1 +# fi +# status=${res:${#res}-3} +# if [ $status -lt 200 ] && [ $status -gt 299 ]; then +# __log_conf_fail_status_code "2XX" $status +# echo "$res" +# return 1 +# fi +# echo ${res:0:${#res}-3} +# return 0 +# } + +# Excute a curl cmd towards the keycloak and check the response code is 2XX. +# args: +# resp: if return code is 0 otherwise +__execute_curl_to_keycloak2() { + proxyflag="" + if [ ! -z "$KUBE_PROXY_PATH" ]; then + if [ $KUBE_PROXY_HTTPX == "http" ]; then + proxyflag=" --proxy $KUBE_PROXY_PATH" + else + proxyflag=" --proxy-insecure --proxy $KUBE_PROXY_PATH" + fi + fi + if [ $1 == "POST" ]; then + if [ $# -eq 3 ]; then + echo curl -X POST -skw %{http_code} $proxyflag "$2" -H "Authorization: Bearer $3" >> $HTTPLOG + res=$(curl -X POST -skw %{http_code} $proxyflag "$2" -H "Authorization: Bearer $3") + retcode=$? + else + echo curl -X POST -skw %{http_code} $proxyflag "$2" -H "Content-Type: application/json" -H "Authorization: Bearer $3" --data-binary "$4" >> $HTTPLOG + res=$(curl -X POST -skw %{http_code} $proxyflag "$2" -H "Content-Type: application/json" -H "Authorization: Bearer $3" --data-binary "$4") + retcode=$? + fi + elif [ $1 == "PUT" ]; then + if [ $# -eq 3 ]; then + echo curl -X PUT -skw %{http_code} $proxyflag "$2" -H "Authorization: Bearer $3" >> $HTTPLOG + res=$(curl -X PUT -skw %{http_code} $proxyflag "$2" -H "Authorization: Bearer $3") + retcode=$? + else + echo curl -X PUT -skw %{http_code} $proxyflag "$2" -H "Content-Type: application/json" -H "Authorization: Bearer $3" --data-binary "$4" >> $HTTPLOG + res=$(curl -X PUT -skw %{http_code} $proxyflag "$2" -H "Content-Type: application/json" -H "Authorization: Bearer $3" --data-binary "$4") + retcode=$? + fi + elif [ $1 == "GET" ]; then + echo curl -X GET -skw %{http_code} $proxyflag "$2" -H "Authorization: Bearer $3" >> $HTTPLOG + res=$(curl -X GET -skw %{http_code} $proxyflag "$2" -H "Authorization: Bearer $3") + retcode=$? + fi + echo " RESP: $res" >> $HTTPLOG + if [ $retcode -ne 0 ]; then + __log_conf_fail_general " Fatal error when executing curl, response: "$retcode + echo "$res" + return 1 + fi + status=${res:${#res}-3} + if [ $status -lt 200 ] && [ $status -gt 299 ]; then + __log_conf_fail_status_code "2XX" $status + echo "$res" + return 1 + fi + echo ${res:0:${#res}-3} + return 0 +} + +# Extract JWT access token from json structure +# args: +__keycloak_decode_jwt() { + echo $1 | jq -r .access_token | jq -R 'split(".") | .[1] | @base64d | fromjson' + return 0 +} + +# Get the admin token to use for subsequent rest calls to keycloak +# args: - +keycloak_api_obtain_admin_token() { + __log_conf_start $@ + __curl_string="-X POST $KEYCLOAK_SERVICE_PATH$KEYCLOAK_ADMIN_URL_PREFIX/protocol/openid-connect/token -H Content-Type:application/x-www-form-urlencoded -d username="$KEYCLOAK_ADMIN_USER" -d password="$KEYCLOAK_ADMIN_PWD" -d grant_type=password -d client_id="$KEYCLOAK_ADMIN_CLIENT + __TMP_TOKEN=$(__execute_curl_to_keycloak "$__curl_string") + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when executing curl, response: "$? + return 1 + fi + + __KEYCLOAK_ADMIN_TOKEN=$(echo "$__TMP_TOKEN" | jq -r '.access_token') + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when extracting token, response: "$? + return 1 + fi + + echo "Decoded token:" >> $HTTPLOG + __keycloak_decode_jwt "$__TMP_TOKEN" >> $HTTPLOG + + __KEYCLOAK_ADMIN_TOKEN_EXP=$(echo "$__TMP_TOKEN" | jq -r '.expires_in') + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when extracting expiry time, response: "$? + return 1 + fi + echo " Admin token obtained. Expires in $__KEYCLOAK_ADMIN_TOKEN_EXP seconds" + + __log_conf_ok + return 0 +} + +# Create a realm, name, enabled, expiry-time +# args: true|false +keycloak_api_create_realm() { + __log_conf_start $@ + __json='{"realm":"'$1'","enabled":'$2',"accessTokenLifespan":'$3'}' + res=$(__execute_curl_to_keycloak2 POST "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX" "$__KEYCLOAK_ADMIN_TOKEN" "$__json") + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when creating realm, response: "$? + return 1 + fi + __log_conf_ok + return 0 +} + +# Update a realm, name, enabled, expiry-time +# args: true|false +keycloak_api_update_realm() { + __log_conf_start $@ + __json='{"realm":"'$1'","enabled":'$2',"accessTokenLifespan":'$3'}' + res=$(__execute_curl_to_keycloak2 PUT "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$1" "$__KEYCLOAK_ADMIN_TOKEN" "$__json") + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when updating realm, response: "$? + return 1 + fi + __log_conf_ok + return 0 +} + +# Create a client +# args: +keycloak_api_create_confidential_client() { + __log_conf_start $@ + __json='{"clientId":"'$2'","publicClient":false,"serviceAccountsEnabled": true,"rootUrl":"https://example.com/example/","adminUrl":"https://example.com/example/"}' + res=$(__execute_curl_to_keycloak2 POST "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$1/clients" "$__KEYCLOAK_ADMIN_TOKEN" "$__json") + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when ucreating client, response: "$? + return 1 + fi + __log_conf_ok + return 0 +} + +__keycloak_api_get_client_id() { + TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S") + echo "(${BASH_LINENO[0]}) - ${TIMESTAMP}: ${FUNCNAME[0]}" $@ >> $HTTPLOG + + res=$(__execute_curl_to_keycloak2 GET "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$1/clients?clientId=$2" "$__KEYCLOAK_ADMIN_TOKEN") + if [ $? -ne 0 ]; then + return 1 + fi + echo $res | jq -r '.[0].id' + return 0 +} + +__keycloak_api_get_service_account_id() { + TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S") + echo "(${BASH_LINENO[0]}) - ${TIMESTAMP}: ${FUNCNAME[0]}" $@ >> $HTTPLOG + + res=$(__execute_curl_to_keycloak2 GET "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$1/clients$2/service-account-user" "$__KEYCLOAK_ADMIN_TOKEN") + if [ $? -ne 0 ]; then + return 1 + fi + echo $res | jq -r '.[0].id' + return 0 +} + +# Generate secret for client +# args: +keycloak_api_generate_client_secret() { + __log_conf_start $@ + __c_id=$(__keycloak_api_get_client_id $1 $2) + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when getting client id, response: "$? + return 1 + fi + res=$(__execute_curl_to_keycloak2 POST "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$1/clients/$__c_id/client-secret" "$__KEYCLOAK_ADMIN_TOKEN") + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when generating client secret, response: "$? + return 1 + fi + __c_sec=$(__execute_curl_to_keycloak2 GET "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$1/clients/$__c_id/client-secret" "$__KEYCLOAK_ADMIN_TOKEN") + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when getting client secret, response: "$? + return 1 + fi + __c_sec=$(echo $__c_sec | jq -r .value) + echo " Client id : $__c_id" + echo " Client secret: $__c_sec" + __log_conf_ok + return 0 +} + +# Get secret for client +# args: +keycloak_api_get_client_secret() { + __log_conf_start $@ + __c_id=$(__keycloak_api_get_client_id $1 $2) + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when getting client id, response: "$? + return 1 + fi + __c_sec=$(__execute_curl_to_keycloak2 GET "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$1/clients/$__c_id/client-secret" "$__KEYCLOAK_ADMIN_TOKEN") + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when getting client secret, response: "$? + return 1 + fi + __c_sec=$(echo $__c_sec | jq -r .value) + echo " Client id : $__c_id" + echo " Client secret: $__c_sec" + __log_conf_ok + return 0 +} + +# Create client roles +# args: + +keycloak_api_create_client_roles() { + __log_conf_start $@ + __c_id=$(__keycloak_api_get_client_id $1 $2) + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when getting client id, response: "$? + return 1 + fi + __realm=$1 + shift; shift; + while [ $# -gt 0 ]; do + __json='{"name":"'$1'"}' + res=$(__execute_curl_to_keycloak2 POST "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$__realm/clients/$__c_id/roles" "$__KEYCLOAK_ADMIN_TOKEN" "$__json") + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when creating client role, response: "$? + return 1 + fi + shift + done + __log_conf_ok + return 0 +} + +# Get a client token +# args: +keycloak_api_get_client_token() { + __log_conf_start $@ + __c_id=$(__keycloak_api_get_client_id $1 $2) + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when getting client id, response: "$? + return 1 + fi + __c_sec=$(__execute_curl_to_keycloak2 GET "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$1/clients/$__c_id/client-secret" "$__KEYCLOAK_ADMIN_TOKEN") + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when getting client secret, response: "$? + return 1 + fi + __c_sec=$(echo $__c_sec | jq -r .value) + __curl_string="-X POST $KEYCLOAK_SERVICE_PATH$KEYCLOAK_TOKEN_URL_PREFIX/$1/protocol/openid-connect/token -H Content-Type:application/x-www-form-urlencoded -d client_id="$2" -d client_secret="$__c_sec" -d grant_type=client_credentials" + __TMP_TOKEN=$(__execute_curl_to_keycloak "$__curl_string") + if [ $? -ne 0 ]; then + __log_conf_fail_general " Fatal error when getting client token, response: "$? + return 1 + fi + echo $__TMP_TOKEN| jq -r .access_token + __log_conf_ok + return 0 +} + +# Read a client token +# args: +keycloak_api_read_client_token() { + TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S") + echo "(${BASH_LINENO[0]}) - ${TIMESTAMP}: ${FUNCNAME[0]}" $@ >> $HTTPLOG + __c_id=$(__keycloak_api_get_client_id $1 $2) + if [ $? -ne 0 ]; then + echo "" + return 1 + fi + __c_sec=$(__execute_curl_to_keycloak2 GET "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$1/clients/$__c_id/client-secret" "$__KEYCLOAK_ADMIN_TOKEN") + if [ $? -ne 0 ]; then + echo "" + return 1 + fi + __c_sec=$(echo $__c_sec | jq -r .value) + __curl_string="-X POST $KEYCLOAK_SERVICE_PATH$KEYCLOAK_TOKEN_URL_PREFIX/$1/protocol/openid-connect/token -H Content-Type:application/x-www-form-urlencoded -d client_id="$2" -d client_secret="$__c_sec" -d grant_type=client_credentials" + __TMP_TOKEN=$(__execute_curl_to_keycloak "$__curl_string") + if [ $? -ne 0 ]; then + echo "" + return 1 + fi + echo $__TMP_TOKEN| jq -r .access_token + return 0 +} + +# Read secret for client +# args: +keycloak_api_read_client_secret() { + TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S") + echo "(${BASH_LINENO[0]}) - ${TIMESTAMP}: ${FUNCNAME[0]}" $@ >> $HTTPLOG + __c_id=$(__keycloak_api_get_client_id $1 $2) + if [ $? -ne 0 ]; then + echo "" + return 1 + fi + __c_sec=$(__execute_curl_to_keycloak2 GET "$KEYCLOAK_SERVICE_PATH$KEYCLOAK_REALM_URL_PREFIX/$1/clients/$__c_id/client-secret" "$__KEYCLOAK_ADMIN_TOKEN") + if [ $? -ne 0 ]; then + echo "" + return 1 + fi + __c_sec=$(echo $__c_sec | jq -r .value) + echo $__c_sec + return 0 +} \ No newline at end of file diff --git a/test/common/kubeproxy_api_functions.sh b/test/common/kubeproxy_api_functions.sh index 120fbb6a..c66e63c8 100644 --- a/test/common/kubeproxy_api_functions.sh +++ b/test/common/kubeproxy_api_functions.sh @@ -393,3 +393,4 @@ set_kubeproxy_debug() { echo "" return 0 } + diff --git a/test/common/prodstub_api_functions.sh b/test/common/prodstub_api_functions.sh index 9af00d8d..7dbef777 100644 --- a/test/common/prodstub_api_functions.sh +++ b/test/common/prodstub_api_functions.sh @@ -274,8 +274,14 @@ __execute_curl_to_prodstub() { proxyflag=" --proxy-insecure --proxy $KUBE_PROXY_PATH" fi fi - echo " CMD: $3 $proxyflag" >> $HTTPLOG - res="$($3 $proxyflag)" + if [ ! -z "$KUBE_PROXY_CURL_JWT" ]; then + jwt="-H "\""Authorization: Bearer $KUBE_PROXY_CURL_JWT"\" + echo " CMD: $3 $proxyflag $jwt" >> $HTTPLOG + res=$($3 $proxyflag -H "Authorization: Bearer $KUBE_PROXY_CURL_JWT") + else + echo " CMD: $3 $proxyflag" >> $HTTPLOG + res="$($3 $proxyflag)" + fi echo " RESP: $res" >> $HTTPLOG retcode=$? if [ $retcode -ne 0 ]; then diff --git a/test/common/pvccleaner_api_functions.sh b/test/common/pvccleaner_api_functions.sh index 26bee7b5..4ffb5229 100644 --- a/test/common/pvccleaner_api_functions.sh +++ b/test/common/pvccleaner_api_functions.sh @@ -79,7 +79,7 @@ __PVCCLEANER_kube_delete_all() { # args: __PVCCLEANER_store_docker_logs() { if [ $RUNMODE == "KUBE" ]; then - kubectl $KUBECONF logs -l "autotest=PRODSTUB" -A --tail=-1 > $1$2_pvs_cleaner.log 2>&1 + kubectl $KUBECONF logs -l "autotest=PVCCLEANER" -A --tail=-1 > $1$2_pvs_cleaner.log 2>&1 fi } diff --git a/test/common/ricsim_api_functions.sh b/test/common/ricsim_api_functions.sh index 46235634..b8af5323 100644 --- a/test/common/ricsim_api_functions.sh +++ b/test/common/ricsim_api_functions.sh @@ -241,7 +241,7 @@ start_ric_simulators() { export RIC_SIM_INTERNAL_PORT export RIC_SIM_INTERNAL_SECURE_PORT - echo -e " Creating $A1PMS_APP_NAME app and expose service" + echo -e " Creating $RIC_SIM_PREFIX app and expose service" #Check if nonrtric namespace exists, if not create it __kube_create_namespace $KUBE_A1SIM_NAMESPACE diff --git a/test/common/test_env-onap-jakarta.sh b/test/common/test_env-onap-jakarta.sh index 0824cc17..33ac6ac2 100644 --- a/test/common/test_env-onap-jakarta.sh +++ b/test/common/test_env-onap-jakarta.sh @@ -215,7 +215,7 @@ ICS_LOGPATH="/var/log/information-coordinator-service/application.log" # Path th ICS_APP_NAME_ALIAS="information-service-container" # Alias name, name used by the control panel ICS_HOST_MNT_DIR="./mnt" # Mounted dir, relative to compose file, on the host ICS_CONTAINER_MNT_DIR="/var/information-coordinator-service" # Mounted dir in the container -ICS_ACTUATOR="/actuator/loggers/org.oransc.information" # Url for trace/debug +ICS_ACTUATOR="/actuator/loggers/org.oransc.ics" # Url for trace/debug ICS_CERT_MOUNT_DIR="./cert" ICS_ALIVE_URL="/status" # Base path for alive check ICS_COMPOSE_DIR="ics" # Dir in simulator_group for docker-compose diff --git a/test/common/test_env-oran-e-release.sh b/test/common/test_env-oran-e-release.sh index dbddca1f..4e57ac5c 100755 --- a/test/common/test_env-oran-e-release.sh +++ b/test/common/test_env-oran-e-release.sh @@ -214,7 +214,7 @@ KAFKAPC_IMAGE_BASE="kafka-procon" KAFKAPC_IMAGE_TAG_LOCAL="latest" #No local image for pvc cleaner, remote image always used -#PVC Cleaner remote image and tag +#Chartmusem remote image and tag CHART_MUS_IMAGE_BASE="ghcr.io/helm/chartmuseum" CHART_MUS_IMAGE_TAG_REMOTE_OTHER="v0.13.1" #No local image for chart museum, remote image always used @@ -290,7 +290,7 @@ ICS_LOGPATH="/var/log/information-coordinator-service/application.log" # Path th ICS_APP_NAME_ALIAS="information-service-container" # Alias name, name used by the control panel ICS_HOST_MNT_DIR="./mnt" # Mounted db dir, relative to compose file, on the host ICS_CONTAINER_MNT_DIR="/var/information-coordinator-service" # Mounted dir in the container -ICS_ACTUATOR="/actuator/loggers/org.oransc.information" # Url for trace/debug +ICS_ACTUATOR="/actuator/loggers/org.oransc.ics" # Url for trace/debug ICS_CERT_MOUNT_DIR="./cert" ICS_ALIVE_URL="/status" # Base path for alive check ICS_COMPOSE_DIR="ics" # Dir in simulator_group for docker-compose diff --git a/test/common/test_env-oran-f-release.sh b/test/common/test_env-oran-f-release.sh index eb266344..4beddd15 100755 --- a/test/common/test_env-oran-f-release.sh +++ b/test/common/test_env-oran-f-release.sh @@ -150,6 +150,13 @@ HELM_MANAGER_IMAGE_TAG_REMOTE_SNAPSHOT="1.2.0-SNAPSHOT" HELM_MANAGER_IMAGE_TAG_REMOTE="1.2.0" HELM_MANAGER_IMAGE_TAG_REMOTE_RELEASE="1.2.0" +# Auth sidecar +AUTHSIDECAR_IMAGE_BASE="o-ran-sc/nonrtric-auth-token-fetch" +AUTHSIDECAR_IMAGE_TAG_LOCAL="1.0.0-SNAPSHOT" +AUTHSIDECAR_IMAGE_TAG_REMOTE_SNAPSHOT="1.0.0-SNAPSHOT" +AUTHSIDECAR_IMAGE_TAG_REMOTE="1.0.0" +AUTHSIDECAR_IMAGE_TAG_REMOTE_RELEASE="1.0.0" + #MR stub image and tag MRSTUB_IMAGE_BASE="mrstub" MRSTUB_IMAGE_TAG_LOCAL="latest" @@ -202,13 +209,18 @@ KAFKAPC_IMAGE_BASE="kafka-procon" KAFKAPC_IMAGE_TAG_LOCAL="latest" #No local image for pvc cleaner, remote image always used -#PVC Cleaner remote image and tag +#Chartmusem remote image and tag CHART_MUS_IMAGE_BASE="ghcr.io/helm/chartmuseum" CHART_MUS_IMAGE_TAG_REMOTE_OTHER="v0.13.1" #No local image for chart museum, remote image always used +#Keycloak remote image and tag +KEYCLOAK_IMAGE_BASE="quay.io/keycloak/keycloak" +KEYCLOAK_IMAGE_TAG_REMOTE_OTHER="17.0.0" +#No local image for chart museum, remote image always used + # List of app short names produced by the project -PROJECT_IMAGES_APP_NAMES="A1PMS ICS CP RC RICSIM NGW DMAAPADP DMAAPMED HELMMANAGER" # Add SDNC here if oran image is used +PROJECT_IMAGES_APP_NAMES="A1PMS ICS CP RC RICSIM NGW DMAAPADP DMAAPMED HELMMANAGER AUTHSIDECAR" # Add SDNC here if oran image is used # List of app short names which images pulled from ORAN ORAN_IMAGES_APP_NAMES="" # Not used @@ -241,6 +253,7 @@ KUBE_SIM_NAMESPACE="nonrtric-ft" # Namespace for simulat KUBE_A1SIM_NAMESPACE="a1-sim" # Namespace for a1-p simulators (RICSIM) KUBE_ONAP_NAMESPACE="onap" # Namespace for onap (only message router) KUBE_SDNC_NAMESPACE="onap" # Namespace for sdnc +KUBE_KEYCLOAK_NAMESPACE="keycloak" # Namespace for keycloak A1PMS_EXTERNAL_PORT=8081 # A1PMS container external port (host -> container) A1PMS_INTERNAL_PORT=8081 # A1PMS container internal port (container -> container) @@ -278,7 +291,7 @@ ICS_LOGPATH="/var/log/information-coordinator-service/application.log" # Path th ICS_APP_NAME_ALIAS="information-service-container" # Alias name, name used by the control panel ICS_HOST_MNT_DIR="./mnt" # Mounted db dir, relative to compose file, on the host ICS_CONTAINER_MNT_DIR="/var/information-coordinator-service" # Mounted dir in the container -ICS_ACTUATOR="/actuator/loggers/org.oransc.information" # Url for trace/debug +ICS_ACTUATOR="/actuator/loggers/org.oransc.ics" # Url for trace/debug ICS_CERT_MOUNT_DIR="./cert" ICS_ALIVE_URL="/status" # Base path for alive check ICS_COMPOSE_DIR="ics" # Dir in simulator_group for docker-compose @@ -547,6 +560,29 @@ HELM_MANAGER_ALIVE_URL="/helm/charts" # Base path for alive c HELM_MANAGER_COMPOSE_DIR="helmmanager" # Dir in simulator_group for docker-compose HELM_MANAGER_USER="helmadmin" HELM_MANAGER_PWD="itisasecret" + +KEYCLOAK_APP_NAME="keycloak" # Name for the keycloak app +KEYCLOAK_DISPLAY_NAME="Keycloak" +KEYCLOAK_EXTERNAL_PORT=80 # keycloak container external port (host -> container) +KEYCLOAK_INTERNAL_PORT=8080 # keycloak container internal port (container -> container) +KEYCLOAK_ADMIN_URL_PREFIX="/realms/master" +KEYCLOAK_REALM_URL_PREFIX="/admin/realms" +KEYCLOAK_TOKEN_URL_PREFIX="/realms" +KEYCLOAK_ALIVE_URL="/realms/master" # Base path for alive check +KEYCLOAK_COMPOSE_DIR="keycloak" +KEYCLOAK_ADMIN_USER="admin" +KEYCLOAK_ADMIN_PWD="admin" +KEYCLOAK_ADMIN_CLIENT="admin-cli" +KEYCLOAK_KC_PROXY="edge" + +ISTIO_COMPOSE_DIR="istio" + +# See jwt-info.txt in simulator-group/kubeproxy for detailed info +ISTIO_GENERIC_JWKS_KEY='{ "keys":[{"kty":"RSA","e":"AQAB","kid":"dc1b272d-124e-417f-b6e3-eda9c0e29509","n":"u1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0_IzW7yWR7QkrmBL7jTKEn5u-qKhbwKfBstIs-bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW_VDL5AaWTg0nLVkjRo9z-40RQzuVaE8AkAFmxZzow3x-VJYKdjykkJ0iT9wCS0DRTXu269V264Vf_3jvredZiKRkgwlL9xNAwxXFg0x_XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC-9aGVd-Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmw"}]}' +ISTIO_GENERIC_JWT="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJkb21haW4iLCJpc3MiOiJLVUJFUFJPWFkifQ.T5p9ip8yBRAYpArajFGhUlpfnV0HAbA7dPsSojYx1BNo6nwt_cpt6xJ8x66XwV-KqHud_S8hlnixLBYRtUiU8v7lWdk8RhBwW7w4CJs6n8ByvKsjJU8se18RWbSqsi-IQRsdkMiHz5fKosfCGVj6hI214S_yY988ICV7kl9anQhaD8zUPQQvso2zaAkT1qTgC5pxpZc3lB5526DvzsmYr_gaeE-GcbKW9hFoYppOhItL74IRVqRBs_pbaAauUg-9v_bRaJc5yOo3UMFDNiI2HCB6mdgJTLNb8bsT5qExgcbCRpUnOCF0I6PrvVlGft4zZkvz7I0I-8emVn4m-PV-BA" + +AUTHSIDECAR_APP_NAME="authsidecar" +AUTHSIDECAR_DISPLAY_NAME="Authentication Token Fetcher" ######################################## # Setting for common curl-base function ######################################## diff --git a/test/common/testcase_common.sh b/test/common/testcase_common.sh index 5ebc9d3a..1c77edab 100755 --- a/test/common/testcase_common.sh +++ b/test/common/testcase_common.sh @@ -30,7 +30,7 @@ __print_args() { echo " [--repo-policy local|remote] [--cluster-timeout ] [--print-stats]" echo " [--override ] [--pre-clean] [--gen-stats] [--delete-namespaces]" echo " [--delete-containers] [--endpoint-stats] [--kubeconfig ] [--host-path-dir ]" - echo " [--kubecontext ]" + echo " [--kubecontext ] [--docker-host ] [--docker-proxy ]" } if [ $# -eq 1 ] && [ "$1" == "help" ]; then @@ -68,6 +68,8 @@ if [ $# -eq 1 ] && [ "$1" == "help" ]; then echo "--kubeconfig - Configure kubectl to use cluster specific cluster config file" echo "--host-path-dir - (Base-)path on local-hostmounted to all VMs (nodes), for hostpath volumes in kube" echo "--kubecontext - Configure kubectl to use a certain context, e.g 'minikube'" + echo "--docker-host - Configure docker to use docker in e.g. a VM" + echo "--docker-proxy - Configure ip/host to docker when docker is running in a VM" echo "" echo "List of app short names supported: "$APP_SHORT_NAMES exit 0 @@ -268,6 +270,14 @@ DELETE_CONTAINERS=0 #Var to configure kubectl from a config file or context KUBECONF="" +#Localhost, may be set to another host/ip by cmd parameter +LOCALHOST_NAME="localhost" + +#Reseting vars related to token/keys used by kubeproxy when istio is enabled +#The vars are populated if istio is used in the testcase +KUBE_PROXY_CURL_JWT="" +KUBE_PROXY_ISTIO_JWKS_KEYS="" + #Var pointing to dir mounted to each kubernetes node (master and workers) #Persistent volumes using "hostpath" are allocated beneath the point. #Typically it is a dir on local host mounted to each VM running the master and worker. @@ -883,6 +893,55 @@ while [ $paramerror -eq 0 ] && [ $foundparm -eq 0 ]; do fi else HOST_PATH_BASE_DIR=$1 + echo "Option set - Host path for kube set to: "$1 + shift + foundparm=0 + fi + fi + fi + if [ $paramerror -eq 0 ]; then + if [ "$1" == "--docker-host" ]; then + shift; + if [ -z "$1" ]; then + paramerror=1 + if [ -z "$paramerror_str" ]; then + paramerror_str="No url found for : '--docker-host'" + fi + else + export DOCKER_HOST="$1" + echo "Option set - DOCKER_HOST set to: "$1 + shift + foundparm=0 + fi + fi + fi + if [ $paramerror -eq 0 ]; then + if [ "$1" == "--docker-host" ]; then + shift; + if [ -z "$1" ]; then + paramerror=1 + if [ -z "$paramerror_str" ]; then + paramerror_str="No url found for : '--docker-host'" + fi + else + export DOCKER_HOST="$1" + echo "Option set - DOCKER_HOST set to: "$1 + shift + foundparm=0 + fi + fi + fi + if [ $paramerror -eq 0 ]; then + if [ "$1" == "--docker-proxy" ]; then + shift; + if [ -z "$1" ]; then + paramerror=1 + if [ -z "$paramerror_str" ]; then + paramerror_str="No ip/host found for : '--docker-proxy'" + fi + else + export LOCALHOST_NAME=$1 + echo "Option set - docker proxy set to: "$1 shift foundparm=0 fi @@ -905,11 +964,6 @@ if [ $paramerror -eq 1 ]; then exit 1 fi -#Localhost constants -LOCALHOST_NAME="localhost" -# if [ ! -z "$DOCKER_HOST" ]; then -# LOCALHOST_NAME=$(echo $DOCKER_HOST | awk -F[/:] '{print $4}' ) -# fi LOCALHOST_HTTP="http://$LOCALHOST_NAME" LOCALHOST_HTTPS="https://$LOCALHOST_NAME" @@ -2293,7 +2347,7 @@ __kube_delete_all_resources() { namespace=$1 labelname=$2 labelid=$3 - resources="deployments replicaset statefulset services pods configmaps persistentvolumeclaims persistentvolumes serviceaccounts clusterrolebindings secrets" + resources="deployments replicaset statefulset services pods configmaps persistentvolumeclaims persistentvolumes serviceaccounts clusterrolebindings secrets authorizationpolicies requestauthentications" deleted_resourcetypes="" for restype in $resources; do ns_flag="-n $namespace" @@ -2409,7 +2463,7 @@ __kube_delete_namespace() { return 0 } -# Removes a namespace +# Removes and re-create a namespace # args: # (Not for test scripts) clean_and_create_namespace() { @@ -2427,7 +2481,22 @@ clean_and_create_namespace() { if [ $? -ne 0 ]; then return 1 fi +} + +# Add/remove label on non-namespaced kube object +# args: