setup_keycloak_docker.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. #!/bin/bash
  2. set -e
  3. # Keycloak configuration for Docker environment
  4. KEYCLOAK_URL="http://keycloak:8080"
  5. KEYCLOAK_ADMIN_USER="admin"
  6. KEYCLOAK_ADMIN_PASSWORD="admin"
  7. REALM_NAME="seaweedfs-test"
  8. CLIENT_ID="seaweedfs-s3"
  9. CLIENT_SECRET="seaweedfs-s3-secret"
  10. echo "🔧 Setting up Keycloak realm and users for SeaweedFS S3 IAM testing..."
  11. echo "Keycloak URL: $KEYCLOAK_URL"
  12. # Wait for Keycloak to be ready
  13. echo "⏳ Waiting for Keycloak to be ready..."
  14. timeout 120 bash -c '
  15. until curl -f "$0/health/ready" > /dev/null 2>&1; do
  16. echo "Waiting for Keycloak..."
  17. sleep 5
  18. done
  19. echo "✅ Keycloak health check passed"
  20. ' "$KEYCLOAK_URL"
  21. # Download kcadm.sh if not available
  22. if ! command -v kcadm.sh &> /dev/null; then
  23. echo "📥 Downloading Keycloak admin CLI..."
  24. wget -q https://github.com/keycloak/keycloak/releases/download/26.0.7/keycloak-26.0.7.tar.gz
  25. tar -xzf keycloak-26.0.7.tar.gz
  26. export PATH="$PWD/keycloak-26.0.7/bin:$PATH"
  27. fi
  28. # Wait a bit more for admin user initialization
  29. echo "⏳ Waiting for admin user to be fully initialized..."
  30. sleep 10
  31. # Function to execute kcadm commands with retry and multiple password attempts
  32. kcadm() {
  33. local max_retries=3
  34. local retry_count=0
  35. local passwords=("admin" "admin123" "password")
  36. while [ $retry_count -lt $max_retries ]; do
  37. for password in "${passwords[@]}"; do
  38. if kcadm.sh "$@" --server "$KEYCLOAK_URL" --realm master --user "$KEYCLOAK_ADMIN_USER" --password "$password" 2>/dev/null; then
  39. return 0
  40. fi
  41. done
  42. retry_count=$((retry_count + 1))
  43. echo "🔄 Retry $retry_count of $max_retries..."
  44. sleep 5
  45. done
  46. echo "❌ Failed to execute kcadm command after $max_retries retries"
  47. return 1
  48. }
  49. # Create realm
  50. echo "📝 Creating realm '$REALM_NAME'..."
  51. kcadm create realms -s realm="$REALM_NAME" -s enabled=true || echo "Realm may already exist"
  52. echo "✅ Realm created"
  53. # Create OIDC client
  54. echo "📝 Creating client '$CLIENT_ID'..."
  55. CLIENT_UUID=$(kcadm create clients -r "$REALM_NAME" \
  56. -s clientId="$CLIENT_ID" \
  57. -s secret="$CLIENT_SECRET" \
  58. -s enabled=true \
  59. -s serviceAccountsEnabled=true \
  60. -s standardFlowEnabled=true \
  61. -s directAccessGrantsEnabled=true \
  62. -s 'redirectUris=["*"]' \
  63. -s 'webOrigins=["*"]' \
  64. -i 2>/dev/null || echo "existing-client")
  65. if [ "$CLIENT_UUID" != "existing-client" ]; then
  66. echo "✅ Client created with ID: $CLIENT_UUID"
  67. else
  68. echo "✅ Using existing client"
  69. CLIENT_UUID=$(kcadm get clients -r "$REALM_NAME" -q clientId="$CLIENT_ID" --fields id --format csv --noquotes | tail -n +2)
  70. fi
  71. # Configure protocol mapper for roles
  72. echo "🔧 Configuring role mapper for client '$CLIENT_ID'..."
  73. MAPPER_CONFIG='{
  74. "protocol": "openid-connect",
  75. "protocolMapper": "oidc-usermodel-realm-role-mapper",
  76. "name": "realm-roles",
  77. "config": {
  78. "claim.name": "roles",
  79. "jsonType.label": "String",
  80. "multivalued": "true",
  81. "usermodel.realmRoleMapping.rolePrefix": ""
  82. }
  83. }'
  84. kcadm create clients/"$CLIENT_UUID"/protocol-mappers/models -r "$REALM_NAME" -b "$MAPPER_CONFIG" 2>/dev/null || echo "✅ Role mapper already exists"
  85. echo "✅ Realm roles mapper configured"
  86. # Configure audience mapper to ensure JWT tokens have correct audience claim
  87. echo "🔧 Configuring audience mapper for client '$CLIENT_ID'..."
  88. AUDIENCE_MAPPER_CONFIG='{
  89. "protocol": "openid-connect",
  90. "protocolMapper": "oidc-audience-mapper",
  91. "name": "audience-mapper",
  92. "config": {
  93. "included.client.audience": "'$CLIENT_ID'",
  94. "id.token.claim": "false",
  95. "access.token.claim": "true"
  96. }
  97. }'
  98. kcadm create clients/"$CLIENT_UUID"/protocol-mappers/models -r "$REALM_NAME" -b "$AUDIENCE_MAPPER_CONFIG" 2>/dev/null || echo "✅ Audience mapper already exists"
  99. echo "✅ Audience mapper configured"
  100. # Create realm roles
  101. echo "📝 Creating realm roles..."
  102. for role in "s3-admin" "s3-read-only" "s3-write-only" "s3-read-write"; do
  103. kcadm create roles -r "$REALM_NAME" -s name="$role" 2>/dev/null || echo "Role $role may already exist"
  104. done
  105. # Create users with roles
  106. declare -A USERS=(
  107. ["admin-user"]="s3-admin"
  108. ["read-user"]="s3-read-only"
  109. ["write-user"]="s3-read-write"
  110. ["write-only-user"]="s3-write-only"
  111. )
  112. for username in "${!USERS[@]}"; do
  113. role="${USERS[$username]}"
  114. password="${username//[^a-zA-Z]/}123" # e.g., "admin-user" -> "adminuser123"
  115. echo "📝 Creating user '$username'..."
  116. kcadm create users -r "$REALM_NAME" \
  117. -s username="$username" \
  118. -s enabled=true \
  119. -s firstName="Test" \
  120. -s lastName="User" \
  121. -s email="$username@test.com" 2>/dev/null || echo "User $username may already exist"
  122. echo "🔑 Setting password for '$username'..."
  123. kcadm set-password -r "$REALM_NAME" --username "$username" --new-password "$password"
  124. echo "➕ Assigning role '$role' to '$username'..."
  125. kcadm add-roles -r "$REALM_NAME" --uusername "$username" --rolename "$role"
  126. done
  127. # Create IAM configuration for Docker environment
  128. echo "🔧 Setting up IAM configuration for Docker environment..."
  129. cat > iam_config.json << 'EOF'
  130. {
  131. "sts": {
  132. "tokenDuration": "1h",
  133. "maxSessionLength": "12h",
  134. "issuer": "seaweedfs-sts",
  135. "signingKey": "dGVzdC1zaWduaW5nLWtleS0zMi1jaGFyYWN0ZXJzLWxvbmc="
  136. },
  137. "providers": [
  138. {
  139. "name": "keycloak",
  140. "type": "oidc",
  141. "enabled": true,
  142. "config": {
  143. "issuer": "http://keycloak:8080/realms/seaweedfs-test",
  144. "clientId": "seaweedfs-s3",
  145. "clientSecret": "seaweedfs-s3-secret",
  146. "jwksUri": "http://keycloak:8080/realms/seaweedfs-test/protocol/openid-connect/certs",
  147. "userInfoUri": "http://keycloak:8080/realms/seaweedfs-test/protocol/openid-connect/userinfo",
  148. "scopes": ["openid", "profile", "email"],
  149. "claimsMapping": {
  150. "username": "preferred_username",
  151. "email": "email",
  152. "name": "name"
  153. },
  154. "roleMapping": {
  155. "rules": [
  156. {
  157. "claim": "roles",
  158. "value": "s3-admin",
  159. "role": "arn:seaweed:iam::role/KeycloakAdminRole"
  160. },
  161. {
  162. "claim": "roles",
  163. "value": "s3-read-only",
  164. "role": "arn:seaweed:iam::role/KeycloakReadOnlyRole"
  165. },
  166. {
  167. "claim": "roles",
  168. "value": "s3-write-only",
  169. "role": "arn:seaweed:iam::role/KeycloakWriteOnlyRole"
  170. },
  171. {
  172. "claim": "roles",
  173. "value": "s3-read-write",
  174. "role": "arn:seaweed:iam::role/KeycloakReadWriteRole"
  175. }
  176. ],
  177. "defaultRole": "arn:seaweed:iam::role/KeycloakReadOnlyRole"
  178. }
  179. }
  180. }
  181. ],
  182. "policy": {
  183. "defaultEffect": "Deny"
  184. },
  185. "roles": [
  186. {
  187. "roleName": "KeycloakAdminRole",
  188. "roleArn": "arn:seaweed:iam::role/KeycloakAdminRole",
  189. "trustPolicy": {
  190. "Version": "2012-10-17",
  191. "Statement": [
  192. {
  193. "Effect": "Allow",
  194. "Principal": {
  195. "Federated": "keycloak"
  196. },
  197. "Action": ["sts:AssumeRoleWithWebIdentity"]
  198. }
  199. ]
  200. },
  201. "attachedPolicies": ["S3AdminPolicy"],
  202. "description": "Admin role for Keycloak users"
  203. },
  204. {
  205. "roleName": "KeycloakReadOnlyRole",
  206. "roleArn": "arn:seaweed:iam::role/KeycloakReadOnlyRole",
  207. "trustPolicy": {
  208. "Version": "2012-10-17",
  209. "Statement": [
  210. {
  211. "Effect": "Allow",
  212. "Principal": {
  213. "Federated": "keycloak"
  214. },
  215. "Action": ["sts:AssumeRoleWithWebIdentity"]
  216. }
  217. ]
  218. },
  219. "attachedPolicies": ["S3ReadOnlyPolicy"],
  220. "description": "Read-only role for Keycloak users"
  221. },
  222. {
  223. "roleName": "KeycloakWriteOnlyRole",
  224. "roleArn": "arn:seaweed:iam::role/KeycloakWriteOnlyRole",
  225. "trustPolicy": {
  226. "Version": "2012-10-17",
  227. "Statement": [
  228. {
  229. "Effect": "Allow",
  230. "Principal": {
  231. "Federated": "keycloak"
  232. },
  233. "Action": ["sts:AssumeRoleWithWebIdentity"]
  234. }
  235. ]
  236. },
  237. "attachedPolicies": ["S3WriteOnlyPolicy"],
  238. "description": "Write-only role for Keycloak users"
  239. },
  240. {
  241. "roleName": "KeycloakReadWriteRole",
  242. "roleArn": "arn:seaweed:iam::role/KeycloakReadWriteRole",
  243. "trustPolicy": {
  244. "Version": "2012-10-17",
  245. "Statement": [
  246. {
  247. "Effect": "Allow",
  248. "Principal": {
  249. "Federated": "keycloak"
  250. },
  251. "Action": ["sts:AssumeRoleWithWebIdentity"]
  252. }
  253. ]
  254. },
  255. "attachedPolicies": ["S3ReadWritePolicy"],
  256. "description": "Read-write role for Keycloak users"
  257. }
  258. ],
  259. "policies": [
  260. {
  261. "name": "S3AdminPolicy",
  262. "document": {
  263. "Version": "2012-10-17",
  264. "Statement": [
  265. {
  266. "Effect": "Allow",
  267. "Action": ["s3:*"],
  268. "Resource": ["*"]
  269. },
  270. {
  271. "Effect": "Allow",
  272. "Action": ["sts:ValidateSession"],
  273. "Resource": ["*"]
  274. }
  275. ]
  276. }
  277. },
  278. {
  279. "name": "S3ReadOnlyPolicy",
  280. "document": {
  281. "Version": "2012-10-17",
  282. "Statement": [
  283. {
  284. "Effect": "Allow",
  285. "Action": [
  286. "s3:GetObject",
  287. "s3:ListBucket"
  288. ],
  289. "Resource": [
  290. "arn:seaweed:s3:::*",
  291. "arn:seaweed:s3:::*/*"
  292. ]
  293. },
  294. {
  295. "Effect": "Allow",
  296. "Action": ["sts:ValidateSession"],
  297. "Resource": ["*"]
  298. }
  299. ]
  300. }
  301. },
  302. {
  303. "name": "S3WriteOnlyPolicy",
  304. "document": {
  305. "Version": "2012-10-17",
  306. "Statement": [
  307. {
  308. "Effect": "Allow",
  309. "Action": ["s3:*"],
  310. "Resource": [
  311. "arn:seaweed:s3:::*",
  312. "arn:seaweed:s3:::*/*"
  313. ]
  314. },
  315. {
  316. "Effect": "Deny",
  317. "Action": [
  318. "s3:GetObject",
  319. "s3:ListBucket"
  320. ],
  321. "Resource": [
  322. "arn:seaweed:s3:::*",
  323. "arn:seaweed:s3:::*/*"
  324. ]
  325. },
  326. {
  327. "Effect": "Allow",
  328. "Action": ["sts:ValidateSession"],
  329. "Resource": ["*"]
  330. }
  331. ]
  332. }
  333. },
  334. {
  335. "name": "S3ReadWritePolicy",
  336. "document": {
  337. "Version": "2012-10-17",
  338. "Statement": [
  339. {
  340. "Effect": "Allow",
  341. "Action": ["s3:*"],
  342. "Resource": [
  343. "arn:seaweed:s3:::*",
  344. "arn:seaweed:s3:::*/*"
  345. ]
  346. },
  347. {
  348. "Effect": "Allow",
  349. "Action": ["sts:ValidateSession"],
  350. "Resource": ["*"]
  351. }
  352. ]
  353. }
  354. }
  355. ]
  356. }
  357. EOF
  358. # Validate setup by testing authentication
  359. echo "🔍 Validating setup by testing admin-user authentication and role mapping..."
  360. KEYCLOAK_TOKEN_URL="http://keycloak:8080/realms/$REALM_NAME/protocol/openid-connect/token"
  361. # Get access token for admin-user
  362. ACCESS_TOKEN=$(curl -s -X POST "$KEYCLOAK_TOKEN_URL" \
  363. -H "Content-Type: application/x-www-form-urlencoded" \
  364. -d "grant_type=password" \
  365. -d "client_id=$CLIENT_ID" \
  366. -d "client_secret=$CLIENT_SECRET" \
  367. -d "username=admin-user" \
  368. -d "password=adminuser123" \
  369. -d "scope=openid profile email" | jq -r '.access_token')
  370. if [ "$ACCESS_TOKEN" = "null" ] || [ -z "$ACCESS_TOKEN" ]; then
  371. echo "❌ Failed to obtain access token"
  372. exit 1
  373. fi
  374. echo "✅ Authentication validation successful"
  375. # Decode and check JWT claims
  376. PAYLOAD=$(echo "$ACCESS_TOKEN" | cut -d'.' -f2)
  377. # Add padding for base64 decode
  378. while [ $((${#PAYLOAD} % 4)) -ne 0 ]; do
  379. PAYLOAD="${PAYLOAD}="
  380. done
  381. CLAIMS=$(echo "$PAYLOAD" | base64 -d 2>/dev/null | jq .)
  382. ROLES=$(echo "$CLAIMS" | jq -r '.roles[]?')
  383. if [ -n "$ROLES" ]; then
  384. echo "✅ JWT token includes roles: [$(echo "$ROLES" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')]"
  385. else
  386. echo "⚠️ No roles found in JWT token"
  387. fi
  388. echo "✅ Keycloak test realm '$REALM_NAME' configured for Docker environment"
  389. echo "🐳 Setup complete! You can now run: docker-compose up -d"