Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

keycloak user authentication #33

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export SCENARIO ?= baseline-test
# Used to set --host option of locust CLI (base URL to load test). See https://docs.locust.io/en/stable/configuration.html#command-line-options for details
export HOST ?= http://localhost

export KEYCLOAK_USER_PASS ?= $(shell mktemp -u XXXXXXXXXX)
# Used to set --users option of locust CLI (Peak number of concurrent Locust users.). See https://docs.locust.io/en/stable/configuration.html#command-line-options for details
export USERS ?= 100

Expand Down Expand Up @@ -37,6 +38,8 @@ export RHDH_RESOURCES_CPU_LIMITS ?=
export RHDH_RESOURCES_MEMORY_REQUESTS ?=
export RHDH_RESOURCES_MEMORY_LIMITS ?=
export RHDH_KEYCLOAK_REPLICAS ?= 1
export LOCUST_EXTRA_CMD ?=
export AUTH_PROVIDER ?=

# python's venv base dir relative to the root of the repository
PYTHON_VENV=.venv
Expand Down Expand Up @@ -138,10 +141,22 @@ clean:
.PHONY: test
test: $(TMP_DIR) $(ARTIFACT_DIR)
echo $(SCENARIO)>$(TMP_DIR)/benchmark-scenario
ifneq ($(shell test $(AUTH_PROVIDER) == 'keycloak' && echo 1 || echo 0),0)
$(eval key_pass := $(shell oc -n rhdh-performance get secret perf-test-secrets -o template --template='{{.data.keycloak_user_pass}}' | base64 -d))
$(eval key_host := $(shell oc -n rhdh-performance get routes/keycloak -o template --template='{{.spec.host}}' ))
$(eval LOCUST_EXTRA_CMD := --keycloak-host $(key_host) --keycloak-password $(key_pass) )
ifneq ($(shell test $(USERS) -gt $(WORKERS) && echo 1 || echo 0),0)
@echo "users greater than workers "
else
$(eval WORKERS := $(USERS))
endif
else
@echo "no changes"
endif
cat locust-test-template.yaml | envsubst | kubectl apply --namespace $(LOCUST_NAMESPACE) -f -
kubectl create --namespace $(LOCUST_NAMESPACE) configmap locust.$(SCENARIO) --from-file scenarios/$(SCENARIO).py --dry-run=client -o yaml | kubectl apply --namespace $(LOCUST_NAMESPACE) -f -
date --utc -Ins>$(TMP_DIR)/benchmark-before
timeout=$$(date -d "30 seconds" "+%s"); while [ -z "$$(kubectl get --namespace $(LOCUST_NAMESPACE) pod -l performance-test-pod-name=$(SCENARIO)-test-master -o name)" ]; do if [ "$$(date "+%s")" -gt "$$timeout" ]; then echo "ERROR: Timeout waiting for locust master pod to start"; exit 1; else echo "Waiting for locust master pod to start..."; sleep 5s; fi; done
timeout=$$(date -d "480 seconds" "+%s"); while [ -z "$$(kubectl get --namespace $(LOCUST_NAMESPACE) pod -l performance-test-pod-name=$(SCENARIO)-test-master -o name)" ]; do if [ "$$(date "+%s")" -gt "$$timeout" ]; then echo "ERROR: Timeout waiting for locust master pod to start"; exit 1; else echo "Waiting for locust master pod to start..."; sleep 5s; fi; done
kubectl wait --namespace $(LOCUST_NAMESPACE) --for=condition=Ready=true $$(kubectl get --namespace $(LOCUST_NAMESPACE) pod -l performance-test-pod-name=$(SCENARIO)-test-master -o name)
@echo "Getting locust master log:"
kubectl logs --namespace $(LOCUST_NAMESPACE) -f -l performance-test-pod-name=$(SCENARIO)-test-master | tee load-test.log
Expand Down
2 changes: 1 addition & 1 deletion ci-scripts/rhdh-setup/create_resource.sh
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ create_user() {
curl -s -k --location --request POST "$(keycloak_url)/auth/admin/realms/backstage/users" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer '"$token" \
--data-raw '{"firstName":"'"${username}"'","lastName":"tester", "email":"'"${username}"'@test.com", "enabled":"true", "username":"'"${username}"'","groups":["/'"${groupname}"'"]}' |& tee -a "$TMP_DIR/create_user.log"
--data-raw '{"firstName":"'"${username}"'","lastName":"tester", "email":"'"${username}"'@test.com","emailVerified":"true", "enabled":"true", "username":"'"${username}"'","groups":["/'"${groupname}"'"],"credentials":[{"type":"password","value":"'"${KEYCLOAK_USER_PASS}"'","temporary":false}]}' |& tee -a "$TMP_DIR/create_user.log"
echo "[INFO][$(date --utc -Ins)] User $username ($groupname) created" >>"$TMP_DIR/create_user.log"
}

Expand Down
30 changes: 25 additions & 5 deletions ci-scripts/rhdh-setup/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export BACKSTAGE_USER_COUNT="${BACKSTAGE_USER_COUNT:-1}"
export GROUP_COUNT="${GROUP_COUNT:-1}"
export API_COUNT="${API_COUNT:-1}"
export COMPONENT_COUNT="${COMPONENT_COUNT:-1}"
export KEYCLOAK_USER_PASS=${KEYCLOAK_USER_PASS:-$(mktemp -u XXXXXXXXXX)}
export AUTH_PROVIDER="${AUTH_PROVIDER:-''}"


TMP_DIR=$(readlink -m "${TMP_DIR:-.tmp}")
mkdir -p "${TMP_DIR}"
Expand Down Expand Up @@ -84,8 +87,11 @@ delete() {
keycloak_install() {
$cli create namespace "${RHDH_NAMESPACE}" --dry-run=client -o yaml | $cli apply -f -
export KEYCLOAK_CLIENT_SECRET
export COOKIE_SECRET
KEYCLOAK_CLIENT_SECRET=$(mktemp -u XXXXXXXXXX)
COOKIE_SECRET=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d -- '\n' | tr -- '+/' '-_'; echo)
envsubst <template/keycloak/keycloak-op.yaml | $clin apply -f -
envsubst <template/backstage/perf-test-secrets.yaml | $clin apply -f -
grep -m 1 "rhsso-operator" <($clin get pods -w)
wait_to_start deployment rhsso-operator 300 300
envsubst <template/keycloak/keycloak.yaml | $clin apply -f -
Expand All @@ -97,8 +103,11 @@ keycloak_install() {

# shellcheck disable=SC2016,SC1004
backstage_install() {
cp "template/backstage/app-config.yaml" "$TMP_DIR/app-config.yaml"
if [ "${AUTH_PROVIDER}" == "keycloak" ]; then yq -i '. |= . + {"signInPage":"oauth2Proxy"}' "$TMP_DIR/app-config.yaml"; fi
if [ "${AUTH_PROVIDER}" == "keycloak" ]; then yq -i '. |= . + {"auth":{"environment":"production","providers":{"oauth2Proxy":{}}}}' "$TMP_DIR/app-config.yaml"; fi
until envsubst <template/backstage/secret-rhdh-pull-secret.yaml | $clin apply -f -; do $clin delete secret rhdh-pull-secret; done
until $clin create configmap app-config-rhdh --from-file "app-config-rhdh.yaml=template/backstage/app-config.yaml"; do $clin delete configmap app-config-rhdh; done
until $clin create configmap app-config-rhdh --from-file "app-config-rhdh.yaml=$TMP_DIR/app-config.yaml"; do $clin delete configmap app-config-rhdh; done
envsubst <template/backstage/plugin-secrets.yaml | $clin apply -f -
helm repo remove "${repo_name}" || true
helm repo add "${repo_name}" "${RHDH_HELM_REPO}"
Expand All @@ -115,6 +124,8 @@ backstage_install() {
chart_origin="$chart_origin@$RHDH_HELM_CHART_VERSION"
fi
echo "Installing RHDH Helm chart $RHDH_HELM_RELEASE_NAME from $chart_origin in $RHDH_NAMESPACE namespace"
cp "$chart_values" "$TMP_DIR/chart-values.temp.yaml"
if [ "${AUTH_PROVIDER}" == "keycloak" ]; then yq -i '.upstream.backstage |= . + load("template/backstage/oauth2-container-patch.yaml")' "$TMP_DIR/chart-values.temp.yaml"; fi
envsubst \
'${OPENSHIFT_APP_DOMAIN} \
${RHDH_HELM_RELEASE_NAME} \
Expand All @@ -125,11 +136,14 @@ backstage_install() {
${RHDH_IMAGE_REPO} \
${RHDH_IMAGE_TAG} \
${RHDH_NAMESPACE} \
' <"$chart_values" >"$TMP_DIR/chart-values.yaml"
${COOKIE_SECRET} \
' <"$TMP_DIR/chart-values.temp.yaml" >"$TMP_DIR/chart-values.yaml"
if [ -n "${RHDH_RESOURCES_CPU_REQUESTS}" ]; then yq -i '.upstream.backstage.resources.requests.cpu = "'"${RHDH_RESOURCES_CPU_REQUESTS}"'"' "$TMP_DIR/chart-values.yaml"; fi
if [ -n "${RHDH_RESOURCES_CPU_LIMITS}" ]; then yq -i '.upstream.backstage.resources.limits.cpu = "'"${RHDH_RESOURCES_CPU_LIMITS}"'"' "$TMP_DIR/chart-values.yaml"; fi
if [ -n "${RHDH_RESOURCES_MEMORY_REQUESTS}" ]; then yq -i '.upstream.backstage.resources.requests.memory = "'"${RHDH_RESOURCES_MEMORY_REQUESTS}"'"' "$TMP_DIR/chart-values.yaml"; fi
if [ -n "${RHDH_RESOURCES_MEMORY_LIMITS}" ]; then yq -i '.upstream.backstage.resources.limits.memory = "'"${RHDH_RESOURCES_MEMORY_LIMITS}"'"' "$TMP_DIR/chart-values.yaml"; fi
if [ "${AUTH_PROVIDER}" == "keycloak" ]; then yq -i '.upstream.service.ports.targetPort = "oauth2-proxy"' "$TMP_DIR/chart-values.yaml"; fi
if [ "${AUTH_PROVIDER}" == "keycloak" ]; then yq -i '.upstream.service.ports.backend = 4180' "$TMP_DIR/chart-values.yaml"; fi
#shellcheck disable=SC2086
helm upgrade --install "${RHDH_HELM_RELEASE_NAME}" --devel "${repo_name}/${RHDH_HELM_CHART}" ${version_arg} -n "${RHDH_NAMESPACE}" --values "$TMP_DIR/chart-values.yaml"
wait_to_start statefulset "${RHDH_HELM_RELEASE_NAME}-postgresql-read" 300 300
Expand Down Expand Up @@ -232,24 +246,30 @@ install() {
setup_monitoring
}

while getopts "crdi" flag; do
while getopts ":i:crd" flag; do
case "${flag}" in
c)
create_objs
exit 0
;;
r)
delete
install
exit 0
;;
d)
delete
exit 0
;;
i)
AUTH_PROVIDER="$OPTARG"
install
exit 0
;;
*)
\?)
echo "WARNING: Invalid option: ${flag} - defaulting to -i (install)"
install
;;
esac
done

install
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
extraContainers:
- name: oauth2-proxy
env:
- name: OAUTH2_PROXY_CLIENT_ID
valueFrom:
secretKeyRef:
key: CLIENT_ID
name: keycloak-client-secret-backstage
- name: OAUTH2_PROXY_CLIENT_SECRET
valueFrom:
secretKeyRef:
key: CLIENT_SECRET
name: keycloak-client-secret-backstage
- name: OAUTH2_PROXY_COOKIE_SECRET
valueFrom:
secretKeyRef:
key: keycloak_cookie_secret
name: perf-test-secrets
- name: OAUTH2_PROXY_OIDC_ISSUER_URL
value: https://keycloak-${RHDH_NAMESPACE}.${OPENSHIFT_APP_DOMAIN}/auth/realms/backstage
- name: OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY
value: 'true'
ports:
- name: oauth2-proxy
containerPort: 4180
protocol: TCP
imagePullPolicy: IfNotPresent
image: quay.io/oauth2-proxy/oauth2-proxy:latest
args:
- "--provider=oidc"
- "--email-domain=*"
- "--upstream=http://localhost:7007"
- "--http-address=0.0.0.0:4180"
- "--skip-provider-button"

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
stringData:
keycloak_user_pass: ${KEYCLOAK_USER_PASS}
keycloak_cookie_secret: ${COOKIE_SECRET}
metadata:
name: perf-test-secrets
1 change: 1 addition & 0 deletions locust-test-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ spec:
--users ${USERS}
--spawn-rate ${SPAWN_RATE}
--run-time ${DURATION}
${LOCUST_EXTRA_CMD}
workerCommandSeed: --locustfile /lotest/src/${SCENARIO}.py
workerReplicas: ${WORKERS}
configMap: locust.${SCENARIO}
88 changes: 87 additions & 1 deletion scenarios/mvp.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from locust import HttpUser, task
from locust import HttpUser, events, task
from locust.runners import MasterRunner, WorkerRunner
from urllib3.exceptions import InsecureRequestWarning
import urllib.parse
import json
import re
import urllib3

urllib3.disable_warnings(InsecureRequestWarning)

__version__ = "1"

usernames = []

entity_facets_params = {}

entity_facets_params["kind"] = {
Expand Down Expand Up @@ -87,19 +93,99 @@
base_path_entities = "/api/catalog/entities"



def setup_test_users(environment, msg, **kwargs):
# Fired when the worker receives a message of type 'test_users'
usernames.extend(map(lambda u: u, msg.data))

@events.init.add_listener
def on_locust_init(environment, **_kwargs):
if not isinstance(environment.runner, MasterRunner):
environment.runner.register_message("test_users", setup_test_users)

@events.test_start.add_listener
def on_test_start(environment, **_kwargs):
# When the test is started, evenly divides list between
# worker nodes to ensure unique data across threads
if not isinstance(environment.runner, WorkerRunner):
users = []
for i in range(1,int(environment.runner.target_user_count)+1):
users.append(f"test{i}")

worker_count = environment.runner.worker_count
chunk_size = int(len(users) / worker_count)

for i, worker in enumerate(environment.runner.clients):
start_index = i * chunk_size

if i + 1 < worker_count:
end_index = start_index + chunk_size
else:
end_index = len(users)

data = users[start_index:end_index]
environment.runner.send_message("test_users", data, worker)

@events.init_command_line_parser.add_listener
def _(parser):
parser.add_argument("--keycloak-host", type=str, default="")
parser.add_argument("--keycloak-password", is_secret=True, default="")



class MVPTest(HttpUser):

def on_start(self):
self.client.verify = False
if self.environment.parsed_options.keycloak_host:
r = self.client.get('/api/auth/oauth2Proxy/refresh', verify=False)
qs_str=urllib.parse.parse_qs(r.url)
STATE=qs_str['state']

param = {'client_id': self.CLIENTID, 'state': STATE, 'redirect_uri': self.REDIRECT_URL,'scope': 'openid email profile','response_type': 'code'}
r = self.client.get(self.KEYCLOAK_URL+"/realms/backstage/protocol/openid-connect/auth", verify=False, params=param)

login_cookies=r.cookies
pattern = r'action="([^"]*)"'
LOGIN_URL_tmp=re.findall(pattern, str(r.content))[0]
LOGIN_URL=LOGIN_URL_tmp.replace("&amp;", "&")
qs_str=urllib.parse.parse_qs(LOGIN_URL)
TAB_ID=qs_str['tab_id']
EXECUTION=qs_str['execution']

param = {'client_id': self.CLIENTID, 'tab_id': TAB_ID, 'execution': EXECUTION}
form = {'username': self.USERNAME, 'password': self.PASSWORD, 'credentialId': ''}
r = self.client.post(LOGIN_URL, verify=False, data=form, params=param)

r = self.client.get(self.REFRESH_URL, verify=False)
json_dict = json.loads(r.content)
TOKEN=json_dict['backstageIdentity']['token']

self.HEADER = {'Authorization': 'Bearer ' + TOKEN}

def __init__(self, parent):
super().__init__(parent)
self.HEADER=''
if self.environment.parsed_options.keycloak_host:
self.USERNAME = usernames.pop()
self.KEYCLOAK_URL=f'https://{self.environment.parsed_options.keycloak_host}/auth'
self.REDIRECT_URL=f'{self.environment.host}/oauth2/callback'
self.REFRESH_URL=f'{self.environment.host}/api/auth/oauth2Proxy/refresh'

self.PASSWORD=self.environment.parsed_options.keycloak_password
self.REALM="backstage"
self.CLIENTID="backstage"

def entitiy_facets(self, query) -> None:
self.client.get(base_path_facets,
verify=False,
headers=self.HEADER,
params=entity_facets_params[query])

def entities(self, query) -> None:
self.client.get(base_path_entities,
verify=False,
headers=self.HEADER,
params=entities_params[query])

@task
Expand Down
Loading