From 63616be4a39a6f2f56f2b3fedba64e92a1153bf9 Mon Sep 17 00:00:00 2001 From: chrislu Date: Tue, 26 Aug 2025 08:32:43 -0700 Subject: [PATCH] setup keycloak --- test/s3/iam/s3_iam_framework.go | 17 ++- test/s3/iam/setup_all_tests.sh | 6 ++ test/s3/iam/setup_keycloak.sh | 176 ++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 3 deletions(-) diff --git a/test/s3/iam/s3_iam_framework.go b/test/s3/iam/s3_iam_framework.go index 9b3ae6366..241d51d30 100644 --- a/test/s3/iam/s3_iam_framework.go +++ b/test/s3/iam/s3_iam_framework.go @@ -117,15 +117,26 @@ func NewKeycloakClient(baseURL, realm, clientID, clientSecret string) *KeycloakC func (f *S3IAMTestFramework) isKeycloakAvailable(keycloakURL string) bool { client := &http.Client{Timeout: 5 * time.Second} // Use realms endpoint instead of health/ready for Keycloak v26+ - realmsURL := fmt.Sprintf("%s/realms/master", keycloakURL) + // First, verify master realm is reachable + masterURL := fmt.Sprintf("%s/realms/master", keycloakURL) - resp, err := client.Get(realmsURL) + resp, err := client.Get(masterURL) if err != nil { return false } defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return false + } - return resp.StatusCode == 200 + // Also ensure the specific test realm exists; otherwise fall back to mock + testRealmURL := fmt.Sprintf("%s/realms/%s", keycloakURL, KeycloakRealm) + resp2, err := client.Get(testRealmURL) + if err != nil { + return false + } + defer resp2.Body.Close() + return resp2.StatusCode == http.StatusOK } // AuthenticateUser authenticates a user with Keycloak and returns an access token diff --git a/test/s3/iam/setup_all_tests.sh b/test/s3/iam/setup_all_tests.sh index c7630b27f..49ea34dca 100755 --- a/test/s3/iam/setup_all_tests.sh +++ b/test/s3/iam/setup_all_tests.sh @@ -584,6 +584,12 @@ main() { # Setup Keycloak (optional) echo -e "\n${YELLOW}🔐 Setting up Keycloak...${NC}" if setup_keycloak; then + echo -e "${GREEN}✅ Keycloak container running${NC}" + # Configure realm, client, roles, and users idempotently + if [ -f "$TEST_DIR/setup_keycloak.sh" ]; then + echo -e "${YELLOW}🧩 Configuring Keycloak realm and test users...${NC}" + bash "$TEST_DIR/setup_keycloak.sh" + fi echo -e "${GREEN}✅ Keycloak setup successful${NC}" else echo -e "${YELLOW}⚠️ Keycloak setup failed or skipped${NC}" diff --git a/test/s3/iam/setup_keycloak.sh b/test/s3/iam/setup_keycloak.sh index c6bd8a297..f4ba48b41 100755 --- a/test/s3/iam/setup_keycloak.sh +++ b/test/s3/iam/setup_keycloak.sh @@ -1,3 +1,179 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +KEYCLOAK_IMAGE="quay.io/keycloak/keycloak:26.0.7" +CONTAINER_NAME="keycloak-iam-test" +KEYCLOAK_PORT="8080" +KEYCLOAK_URL="http://localhost:${KEYCLOAK_PORT}" + +# Realm and test fixtures expected by tests +REALM_NAME="seaweedfs-test" +CLIENT_ID="seaweedfs-s3" +CLIENT_SECRET="seaweedfs-s3-secret" +ROLE_ADMIN="s3-admin" +ROLE_READONLY="s3-read-only" +ROLE_READWRITE="s3-read-write" + +declare -A USERS +USERS=( + [admin-user]=admin123 + [read-user]=read123 + [write-user]=write123 +) + +echo -e "${BLUE}🔧 Setting up Keycloak realm and users for SeaweedFS S3 IAM testing...${NC}" +echo "Keycloak URL: ${KEYCLOAK_URL}" + +ensure_container() { + # Prefer any already running Keycloak container to avoid port conflicts + if docker ps --format '{{.Names}}' | grep -q '^keycloak$'; then + CONTAINER_NAME="keycloak" + echo -e "${GREEN}✅ Using existing container '${CONTAINER_NAME}'${NC}" + return 0 + fi + if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + echo -e "${GREEN}✅ Using existing container '${CONTAINER_NAME}'${NC}" + return 0 + fi + echo -e "${YELLOW}🐳 Starting Keycloak container (${KEYCLOAK_IMAGE})...${NC}" + docker rm -f "${CONTAINER_NAME}" >/dev/null 2>&1 || true + docker run -d --name "${CONTAINER_NAME}" -p "${KEYCLOAK_PORT}:8080" \ + -e KEYCLOAK_ADMIN=admin \ + -e KEYCLOAK_ADMIN_PASSWORD=admin \ + -e KC_HTTP_ENABLED=true \ + -e KC_HOSTNAME_STRICT=false \ + -e KC_HOSTNAME_STRICT_HTTPS=false \ + -e KC_HEALTH_ENABLED=true \ + "${KEYCLOAK_IMAGE}" start-dev >/dev/null +} + +wait_ready() { + echo -e "${YELLOW}⏳ Waiting for Keycloak to be ready...${NC}" + for i in $(seq 1 120); do + if curl -sf "${KEYCLOAK_URL}/health/ready" >/dev/null; then + echo -e "${GREEN}✅ Keycloak health check passed${NC}" + return 0 + fi + if curl -sf "${KEYCLOAK_URL}/realms/master" >/dev/null; then + echo -e "${GREEN}✅ Keycloak master realm accessible${NC}" + return 0 + fi + sleep 2 + done + echo -e "${RED}❌ Keycloak did not become ready in time${NC}" + exit 1 +} + +kcadm() { + docker exec -i "${CONTAINER_NAME}" /opt/keycloak/bin/kcadm.sh "$@" +} + +admin_login() { + kcadm config credentials --server "${KEYCLOAK_URL}" --realm master --user admin --password admin >/dev/null +} + +ensure_realm() { + if kcadm get realms | grep -q '"realm": *"'${REALM_NAME}'"'; then + echo -e "${GREEN}✅ Realm '${REALM_NAME}' already exists${NC}" + else + echo -e "${YELLOW}📝 Creating realm '${REALM_NAME}'...${NC}" + kcadm create realms -s realm="${REALM_NAME}" -s enabled=true >/dev/null + echo -e "${GREEN}✅ Realm created${NC}" + fi +} + +ensure_client() { + local id + id=$(kcadm get clients -r "${REALM_NAME}" -q clientId="${CLIENT_ID}" | jq -r '.[0].id // empty') + if [[ -n "${id}" ]]; then + echo -e "${GREEN}✅ Client '${CLIENT_ID}' already exists${NC}" + else + echo -e "${YELLOW}📝 Creating client '${CLIENT_ID}'...${NC}" + kcadm create clients -r "${REALM_NAME}" \ + -s clientId="${CLIENT_ID}" \ + -s protocol=openid-connect \ + -s publicClient=false \ + -s serviceAccountsEnabled=false \ + -s directAccessGrantsEnabled=true \ + -s standardFlowEnabled=false \ + -s implicitFlowEnabled=false \ + -s secret="${CLIENT_SECRET}" >/dev/null + echo -e "${GREEN}✅ Client created${NC}" + fi +} + +ensure_role() { + local role="$1" + if kcadm get roles -r "${REALM_NAME}" | jq -r '.[].name' | grep -qx "${role}"; then + echo -e "${GREEN}✅ Role '${role}' exists${NC}" + else + echo -e "${YELLOW}📝 Creating role '${role}'...${NC}" + kcadm create roles -r "${REALM_NAME}" -s name="${role}" >/dev/null + fi +} + +ensure_user() { + local username="$1" password="$2" + local uid + uid=$(kcadm get users -r "${REALM_NAME}" -q username="${username}" | jq -r '.[0].id // empty') + if [[ -z "${uid}" ]]; then + echo -e "${YELLOW}📝 Creating user '${username}'...${NC}" + uid=$(kcadm create users -r "${REALM_NAME}" -s username="${username}" -s enabled=true -i) + else + echo -e "${GREEN}✅ User '${username}' exists${NC}" + fi + echo -e "${YELLOW}🔑 Setting password for '${username}'...${NC}" + kcadm set-password -r "${REALM_NAME}" --userid "${uid}" --new-password "${password}" --temporary=false >/dev/null +} + +assign_role() { + local username="$1" role="$2" + local uid rid + uid=$(kcadm get users -r "${REALM_NAME}" -q username="${username}" | jq -r '.[0].id') + rid=$(kcadm get roles -r "${REALM_NAME}" | jq -r ".[] | select(.name==\"${role}\") | .id") + # Check if role already assigned + if kcadm get "users/${uid}/role-mappings/realm" -r "${REALM_NAME}" | jq -r '.[].name' | grep -qx "${role}"; then + echo -e "${GREEN}✅ User '${username}' already has role '${role}'${NC}" + return 0 + fi + echo -e "${YELLOW}➕ Assigning role '${role}' to '${username}'...${NC}" + kcadm add-roles -r "${REALM_NAME}" --uid "${uid}" --rolename "${role}" >/dev/null +} + +main() { + command -v docker >/dev/null || { echo -e "${RED}❌ Docker is required${NC}"; exit 1; } + command -v jq >/dev/null || { echo -e "${RED}❌ jq is required${NC}"; exit 1; } + + ensure_container + wait_ready + admin_login + ensure_realm + ensure_client + ensure_role "${ROLE_ADMIN}" + ensure_role "${ROLE_READONLY}" + ensure_role "${ROLE_READWRITE}" + + for u in "${!USERS[@]}"; do + ensure_user "$u" "${USERS[$u]}" + done + + assign_role admin-user "${ROLE_ADMIN}" + assign_role read-user "${ROLE_READONLY}" + assign_role write-user "${ROLE_READWRITE}" + + echo -e "${GREEN}✅ Keycloak test realm '${REALM_NAME}' configured${NC}" +} + +main "$@" + #!/bin/bash # Keycloak Setup Script for CI/CD