@@ -117,13 +117,22 @@ suite("test_tls_cert_san_auth") {
117117 // === Cleanup function ===
118118 def cleanup = {
119119 logger. info(" Cleaning up test users and restoring config..." )
120+ // MySQL protocol test users
120121 try_sql(" DROP USER IF EXISTS '${ testUserBase} _1'@'%'" )
121122 try_sql(" DROP USER IF EXISTS '${ testUserBase} _2'@'%'" )
122123 try_sql(" DROP USER IF EXISTS '${ testUserBase} _3'@'%'" )
123124 try_sql(" DROP USER IF EXISTS '${ testUserBase} _4'@'%'" )
124125 try_sql(" DROP USER IF EXISTS '${ testUserBase} _5'@'%'" )
125126 try_sql(" DROP USER IF EXISTS '${ testUserBase} _6'@'%'" )
126127 try_sql(" DROP USER IF EXISTS '${ testUserBase} _7'@'%'" )
128+ // HTTPS test users
129+ try_sql(" DROP USER IF EXISTS '${ testUserBase} _http1'@'%'" )
130+ try_sql(" DROP USER IF EXISTS '${ testUserBase} _http2'@'%'" )
131+ try_sql(" DROP USER IF EXISTS '${ testUserBase} _http3'@'%'" )
132+ try_sql(" DROP USER IF EXISTS '${ testUserBase} _http4'@'%'" )
133+ try_sql(" DROP USER IF EXISTS '${ testUserBase} _http5'@'%'" )
134+ try_sql(" DROP USER IF EXISTS '${ testUserBase} _http6'@'%'" )
135+ try_sql(" DROP USER IF EXISTS '${ testUserBase} _http7'@'%'" )
127136 try {
128137 sql " ADMIN SET FRONTEND CONFIG ('tls_cert_based_auth_ignore_password' = '${ origIgnorePassword} ')"
129138 } catch (Exception e) {
@@ -239,7 +248,185 @@ suite("test_tls_cert_san_auth") {
239248 assertTrue (result7c, " Test 7c should succeed: URI SAN match" )
240249 logger. info(" Test 7c PASSED: URI SAN match works" )
241250
242- logger. info(" === All TLS SAN authentication tests PASSED ===" )
251+ logger. info(" === All MySQL protocol TLS SAN authentication tests PASSED ===" )
252+
253+ // ==================================================================================
254+ // HTTPS/HTTP Certificate-Based Authentication Tests
255+ // These tests verify that FE's HTTP endpoints also support cert-based auth
256+ // ==================================================================================
257+ logger. info(" === Starting HTTPS certificate-based auth tests ===" )
258+
259+ // Get FE HTTP connection info
260+ def feHost = mysqlHost // Same host as MySQL
261+ def httpPort = context. config. feHttpPort ?: " 8030"
262+ def httpEndpoint = " /api/bootstrap" // Simple endpoint that requires auth
263+
264+ logger. info(" FE HTTPS host: ${ feHost} , port: ${ httpPort} " )
265+ logger. info(" Test endpoint: ${ httpEndpoint} " )
266+
267+ // Helper: Execute curl command and check result
268+ def executeCurlCommand = { String command , boolean expectSuccess = true ->
269+ def cmds = [" /bin/bash" , " -c" , command]
270+ logger. info(" Execute curl: ${ command} " )
271+ Process p = cmds. execute()
272+ def errMsg = new StringBuilder ()
273+ def msg = new StringBuilder ()
274+ p. waitForProcessOutput(msg, errMsg)
275+
276+ def output = msg. toString(). trim()
277+ def error = errMsg. toString(). trim()
278+ logger. info(" stdout: ${ output} " )
279+ logger. info(" stderr: ${ error} " )
280+ logger. info(" exitValue: ${ p.exitValue()} " )
281+
282+ // For curl with -w '%{http_code}', success means HTTP 200
283+ // Authentication failure typically returns 401
284+ if (expectSuccess) {
285+ // Expect HTTP 200 (or 2xx)
286+ return output. startsWith(" 2" ) || output == " 200"
287+ } else {
288+ // Expect non-2xx (typically 401 Unauthorized)
289+ return ! output. startsWith(" 2" ) && output != " 200"
290+ }
291+ }
292+
293+ // Helper: Build curl command with SAN certificate
294+ def buildCurlWithCert = { String user , String password , String endpoint ->
295+ return " curl -s -o /dev/null -w '%{http_code}' " +
296+ " -u '${ user} :${ password} ' " +
297+ " --cacert ${ sanClientCa} " +
298+ " --cert ${ sanClientCert} --key ${ sanClientKey} " +
299+ " https://${ feHost} :${ httpPort}${ endpoint} 2>&1"
300+ }
301+
302+ // Helper: Build curl command without client certificate
303+ def buildCurlNoCert = { String user , String password , String endpoint ->
304+ return " curl -s -o /dev/null -w '%{http_code}' " +
305+ " -u '${ user} :${ password} ' " +
306+ " --cacert ${ sanClientCa} " +
307+ " https://${ feHost} :${ httpPort}${ endpoint} 2>&1"
308+ }
309+
310+ // Helper: Build curl command with no-SAN certificate
311+ def buildCurlWithNoSanCert = { String user , String password , String endpoint ->
312+ return " curl -s -o /dev/null -w '%{http_code}' " +
313+ " -u '${ user} :${ password} ' " +
314+ " --cacert ${ sanClientCa} " +
315+ " --cert ${ noSanClientCert} --key ${ noSanClientKey} " +
316+ " https://${ feHost} :${ httpPort}${ endpoint} 2>&1"
317+ }
318+
319+ // Helper: Build curl command with mismatched SAN certificate
320+ def buildCurlWithMismatchCert = { String user , String password , String endpoint ->
321+ return " curl -s -o /dev/null -w '%{http_code}' " +
322+ " -u '${ user} :${ password} ' " +
323+ " --cacert ${ sanClientCa} " +
324+ " --cert ${ sanClientCert} --key ${ sanClientKey} " +
325+ " https://${ feHost} :${ httpPort}${ endpoint} 2>&1"
326+ }
327+
328+ // Reset config for HTTPS tests
329+ sql " ADMIN SET FRONTEND CONFIG ('tls_cert_based_auth_ignore_password' = 'false')"
330+
331+ // === HTTP Test 1: REQUIRE SAN + matching cert + correct password -> success ===
332+ logger. info(" === HTTP Test 1: REQUIRE SAN + matching cert + correct password ===" )
333+ sql " CREATE USER '${ testUserBase} _http1'@'%' IDENTIFIED BY '${ testPassword} ' REQUIRE SAN '${ sanEmail} '"
334+ sql " GRANT ADMIN_PRIV ON *.* TO '${ testUserBase} _http1'@'%'"
335+
336+ def httpCmd1 = buildCurlWithCert(" ${ testUserBase} _http1" , testPassword, httpEndpoint)
337+ def httpResult1 = executeCurlCommand(httpCmd1, true )
338+ assertTrue (httpResult1, " HTTP Test 1 should succeed: matching SAN + correct password" )
339+ logger. info(" HTTP Test 1 PASSED: HTTPS request successful with matching SAN and password" )
340+
341+ // === HTTP Test 2: REQUIRE SAN + matching cert + wrong password -> failure ===
342+ logger. info(" === HTTP Test 2: REQUIRE SAN + matching cert + wrong password ===" )
343+ sql " CREATE USER '${ testUserBase} _http2'@'%' IDENTIFIED BY '${ testPassword} ' REQUIRE SAN '${ sanEmail} '"
344+ sql " GRANT ADMIN_PRIV ON *.* TO '${ testUserBase} _http2'@'%'"
345+
346+ def httpCmd2 = buildCurlWithCert(" ${ testUserBase} _http2" , " wrong_password" , httpEndpoint)
347+ def httpResult2 = executeCurlCommand(httpCmd2, false )
348+ assertTrue (httpResult2, " HTTP Test 2 should fail: wrong password even with matching SAN" )
349+ logger. info(" HTTP Test 2 PASSED: HTTPS request rejected with wrong password" )
350+
351+ // === HTTP Test 3: REQUIRE SAN + mismatched SAN cert -> failure ===
352+ logger. info(" === HTTP Test 3: REQUIRE SAN + mismatched SAN ===" )
353+ sql " CREATE USER '${ testUserBase} _http3'@'%' IDENTIFIED BY '${ testPassword} ' REQUIRE SAN '${ sanMismatch} '"
354+ sql " GRANT ADMIN_PRIV ON *.* TO '${ testUserBase} _http3'@'%'"
355+
356+ def httpCmd3 = buildCurlWithCert(" ${ testUserBase} _http3" , testPassword, httpEndpoint)
357+ def httpResult3 = executeCurlCommand(httpCmd3, false )
358+ assertTrue (httpResult3, " HTTP Test 3 should fail: SAN mismatch" )
359+ logger. info(" HTTP Test 3 PASSED: HTTPS request rejected with mismatched SAN" )
360+
361+ // === HTTP Test 4: REQUIRE SAN + no certificate -> failure ===
362+ // Note: This test depends on tls_verify_mode=verify_fail_if_no_peer_cert
363+ // If the server requires client cert, curl without --cert will fail at TLS handshake
364+ logger. info(" === HTTP Test 4: REQUIRE SAN + no certificate ===" )
365+ sql " CREATE USER '${ testUserBase} _http4'@'%' IDENTIFIED BY '${ testPassword} ' REQUIRE SAN '${ sanEmail} '"
366+ sql " GRANT ADMIN_PRIV ON *.* TO '${ testUserBase} _http4'@'%'"
367+
368+ def httpCmd4 = buildCurlNoCert(" ${ testUserBase} _http4" , testPassword, httpEndpoint)
369+ def httpResult4 = executeCurlCommand(httpCmd4, false )
370+ assertTrue (httpResult4, " HTTP Test 4 should fail: no certificate provided for user with REQUIRE SAN" )
371+ logger. info(" HTTP Test 4 PASSED: HTTPS request rejected without client certificate" )
372+
373+ // === HTTP Test 5: REQUIRE SAN + matching cert + ignore_password=true -> success ===
374+ logger. info(" === HTTP Test 5: REQUIRE SAN + ignore_password=true ===" )
375+ sql " ADMIN SET FRONTEND CONFIG ('tls_cert_based_auth_ignore_password' = 'true')"
376+ sql " CREATE USER '${ testUserBase} _http5'@'%' IDENTIFIED BY '${ testPassword} ' REQUIRE SAN '${ sanEmail} '"
377+ sql " GRANT ADMIN_PRIV ON *.* TO '${ testUserBase} _http5'@'%'"
378+
379+ // Use wrong password - should still succeed because ignore_password=true
380+ def httpCmd5 = buildCurlWithCert(" ${ testUserBase} _http5" , " any_wrong_password" , httpEndpoint)
381+ def httpResult5 = executeCurlCommand(httpCmd5, true )
382+ assertTrue (httpResult5, " HTTP Test 5 should succeed: ignore_password=true allows login with cert only" )
383+ logger. info(" HTTP Test 5 PASSED: HTTPS request successful with certificate only (password ignored)" )
384+
385+ // Reset config
386+ sql " ADMIN SET FRONTEND CONFIG ('tls_cert_based_auth_ignore_password' = 'false')"
387+
388+ // === HTTP Test 6: REQUIRE NONE + no-SAN cert + correct password -> success ===
389+ logger. info(" === HTTP Test 6: REQUIRE NONE + no-SAN cert ===" )
390+ sql " CREATE USER '${ testUserBase} _http6'@'%' IDENTIFIED BY '${ testPassword} '"
391+ sql " GRANT ADMIN_PRIV ON *.* TO '${ testUserBase} _http6'@'%'"
392+
393+ def httpCmd6 = buildCurlWithNoSanCert(" ${ testUserBase} _http6" , testPassword, httpEndpoint)
394+ def httpResult6 = executeCurlCommand(httpCmd6, true )
395+ assertTrue (httpResult6, " HTTP Test 6 should succeed: no TLS requirements, password auth works" )
396+ logger. info(" HTTP Test 6 PASSED: HTTPS request successful for user without TLS requirements" )
397+
398+ // === HTTP Test 7: ALTER USER add/remove REQUIRE SAN for HTTP ===
399+ logger. info(" === HTTP Test 7: ALTER USER add/remove REQUIRE SAN for HTTP ===" )
400+ sql " CREATE USER '${ testUserBase} _http7'@'%' IDENTIFIED BY '${ testPassword} ' REQUIRE SAN '${ sanEmail} '"
401+ sql " GRANT ADMIN_PRIV ON *.* TO '${ testUserBase} _http7'@'%'"
402+
403+ // First verify it works with matching cert
404+ def httpCmd7a = buildCurlWithCert(" ${ testUserBase} _http7" , testPassword, httpEndpoint)
405+ def httpResult7a = executeCurlCommand(httpCmd7a, true )
406+ assertTrue (httpResult7a, " HTTP Test 7a should succeed: initial REQUIRE SAN with matching cert" )
407+ logger. info(" HTTP Test 7a PASSED: REQUIRE SAN works with matching cert via HTTPS" )
408+
409+ // Remove REQUIRE SAN
410+ sql " ALTER USER '${ testUserBase} _http7'@'%' REQUIRE NONE"
411+
412+ // Now should work with no-SAN certificate
413+ def httpCmd7b = buildCurlWithNoSanCert(" ${ testUserBase} _http7" , testPassword, httpEndpoint)
414+ def httpResult7b = executeCurlCommand(httpCmd7b, true )
415+ assertTrue (httpResult7b, " HTTP Test 7b should succeed: REQUIRE NONE allows any cert" )
416+ logger. info(" HTTP Test 7b PASSED: REQUIRE NONE works with no-SAN certificate via HTTPS" )
417+
418+ // Add back REQUIRE SAN with different SAN type (DNS)
419+ sql " ALTER USER '${ testUserBase} _http7'@'%' REQUIRE SAN '${ sanDns} '"
420+
421+ // Now should work with DNS SAN from the same cert
422+ def httpCmd7c = buildCurlWithCert(" ${ testUserBase} _http7" , testPassword, httpEndpoint)
423+ def httpResult7c = executeCurlCommand(httpCmd7c, true )
424+ assertTrue (httpResult7c, " HTTP Test 7c should succeed: DNS SAN match" )
425+ logger. info(" HTTP Test 7c PASSED: REQUIRE SAN (DNS) works via HTTPS" )
426+
427+ logger. info(" === All HTTPS certificate-based auth tests PASSED ===" )
428+
429+ logger. info(" === All TLS SAN authentication tests (MySQL + HTTPS) PASSED ===" )
243430
244431 } finally {
245432 cleanup()
0 commit comments