Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SPARK-49090][CORE] Support
JWSFilter
### What changes were proposed in this pull request? This PR aims to support `JWSFilter` which is a servlet filter that requires `JWS`, a cryptographically signed JSON Web Token, in the header via `spark.ui.filters` configuration. - spark.ui.filters=org.apache.spark.ui.JWSFilter - spark.org.apache.spark.ui.JWSFilter.param.key=YOUR-BASE64URL-ENCODED-KEY To simply put, `JWSFilter` will check the following for all requests. - The HTTP request should have `Authorization: Bearer <jws>` header. - `<jws>` is a string with three fields, `<header>.<payload>.<signature>`. - `<header>` is supposed to be a base64url-encoded string of `{"alg":"HS256","typ":"JWT"}`. - `<payload>` is a base64url-encoded string of fully-user-defined content. - `<signature>` is a signature based on `<header>.<payload>` and a user-provided key parameter. For example, the value of `<header>` will be `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9` always and the value of `payload` can be `e30` if the payload is empty, `{}`. The `<signature>` part is changed by the shared value of `spark.org.apache.spark.ui.JWSFilter.param.key` between the server and client. ``` jshell> java.util.Base64.getUrlEncoder().encodeToString("{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes()) $2 ==> "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" jshell> java.util.Base64.getUrlEncoder().encodeToString("{}".getBytes()) $3 ==> "e30=" ``` ### Why are the changes needed? To provide a little better security on WebUI consistently including Spark Standalone Clusters. For example, **SETTING** ``` $ jshell | Welcome to JShell -- Version 17.0.12 | For an introduction type: /help intro jshell> java.util.Base64.getUrlEncoder().encodeToString("Visit https://spark.apache.org to download Apache Spark.".getBytes()) $1 ==> "VmlzaXQgaHR0cHM6Ly9zcGFyay5hcGFjaGUub3JnIHRvIGRvd25sb2FkIEFwYWNoZSBTcGFyay4=" ``` ``` $ cat conf/spark-defaults.conf spark.ui.filters org.apache.spark.ui.JWSFilter spark.org.apache.spark.ui.JWSFilter.param.key VmlzaXQgaHR0cHM6Ly9zcGFyay5hcGFjaGUub3JnIHRvIGRvd25sb2FkIEFwYWNoZSBTcGFyay4= ``` **SPARK-SHELL** ``` $ build/sbt package $ cp jjwt-impl-0.12.6.jar assembly/target/scala-2.13/jars $ cp jjwt-jackson-0.12.6.jar assembly/target/scala-2.13/jars $ bin/spark-shell ``` Without JWS (ErrorCode: 403 Forbidden) ``` $ curl -v http://localhost:4040/ * Host localhost:4040 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:4040... * connect to ::1 port 4040 from ::1 port 61313 failed: Connection refused * Trying 127.0.0.1:4040... * Connected to localhost (127.0.0.1) port 4040 > GET / HTTP/1.1 > Host: localhost:4040 > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/1.1 403 Forbidden < Date: Fri, 02 Aug 2024 01:27:23 GMT < Cache-Control: must-revalidate,no-cache,no-store < Content-Type: text/html;charset=iso-8859-1 < Content-Length: 472 < <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/> <title>Error 403 Authorization header is missing.</title> </head> <body><h2>HTTP ERROR 403 Authorization header is missing.</h2> <table> <tr><th>URI:</th><td>/</td></tr> <tr><th>STATUS:</th><td>403</td></tr> <tr><th>MESSAGE:</th><td>Authorization header is missing.</td></tr> <tr><th>SERVLET:</th><td>org.apache.spark.ui.JettyUtils$$anon$2-3b39bee2</td></tr> </table> </body> </html> * Connection #0 to host localhost left intact ``` With JWS, ``` $ curl -v -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.4EKWlOkobpaAPR0J4BE0cPQ-ZD1tRQKLZp1vtE7upPw" http://localhost:4040/ * Host localhost:4040 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:4040... * connect to ::1 port 4040 from ::1 port 61311 failed: Connection refused * Trying 127.0.0.1:4040... * Connected to localhost (127.0.0.1) port 4040 > GET / HTTP/1.1 > Host: localhost:4040 > User-Agent: curl/8.7.1 > Accept: */* > Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.4EKWlOkobpaAPR0J4BE0cPQ-ZD1tRQKLZp1vtE7upPw > * Request completely sent off < HTTP/1.1 302 Found < Date: Fri, 02 Aug 2024 01:27:01 GMT < Cache-Control: no-cache, no-store, must-revalidate < X-Frame-Options: SAMEORIGIN < X-XSS-Protection: 1; mode=block < X-Content-Type-Options: nosniff < Location: http://localhost:4040/jobs/ < Content-Length: 0 < * Connection #0 to host localhost left intact ``` **SPARK MASTER** Without JWS (ErrorCode: 403 Forbidden) ``` $ curl -v http://localhost:8080/json/ * Host localhost:8080 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:8080... * connect to ::1 port 8080 from ::1 port 61331 failed: Connection refused * Trying 127.0.0.1:8080... * Connected to localhost (127.0.0.1) port 8080 > GET /json/ HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/1.1 403 Forbidden < Date: Fri, 02 Aug 2024 01:34:03 GMT < Cache-Control: must-revalidate,no-cache,no-store < Content-Type: text/html;charset=iso-8859-1 < Content-Length: 477 < <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/> <title>Error 403 Authorization header is missing.</title> </head> <body><h2>HTTP ERROR 403 Authorization header is missing.</h2> <table> <tr><th>URI:</th><td>/json/</td></tr> <tr><th>STATUS:</th><td>403</td></tr> <tr><th>MESSAGE:</th><td>Authorization header is missing.</td></tr> <tr><th>SERVLET:</th><td>org.apache.spark.ui.JettyUtils$$anon$1-6c52101f</td></tr> </table> </body> </html> * Connection #0 to host localhost left intact ``` With JWS ``` $ curl -v -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.4EKWlOkobpaAPR0J4BE0cPQ-ZD1tRQKLZp1vtE7upPw" http://localhost:8080/json/ * Host localhost:8080 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:8080... * connect to ::1 port 8080 from ::1 port 61329 failed: Connection refused * Trying 127.0.0.1:8080... * Connected to localhost (127.0.0.1) port 8080 > GET /json/ HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/8.7.1 > Accept: */* > Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.4EKWlOkobpaAPR0J4BE0cPQ-ZD1tRQKLZp1vtE7upPw > * Request completely sent off < HTTP/1.1 200 OK < Date: Fri, 02 Aug 2024 01:33:10 GMT < Cache-Control: no-cache, no-store, must-revalidate < X-Frame-Options: SAMEORIGIN < X-XSS-Protection: 1; mode=block < X-Content-Type-Options: nosniff < Content-Type: text/json;charset=utf-8 < Vary: Accept-Encoding < Content-Length: 320 < { "url" : "spark://M3-Max.local:7077", "workers" : [ ], "aliveworkers" : 0, "cores" : 0, "coresused" : 0, "memory" : 0, "memoryused" : 0, "resources" : [ ], "resourcesused" : [ ], "activeapps" : [ ], "completedapps" : [ ], "activedrivers" : [ ], "completeddrivers" : [ ], "status" : "ALIVE" * Connection #0 to host localhost left intact }% ``` ### Does this PR introduce _any_ user-facing change? No, this is a new filter. ### How was this patch tested? Pass the CIs. ### Was this patch authored or co-authored using generative AI tooling? No. Closes #47575 from dongjoon-hyun/SPARK-49090. Lead-authored-by: Dongjoon Hyun <[email protected]> Co-authored-by: Dongjoon Hyun <[email protected]> Signed-off-by: Dongjoon Hyun <[email protected]>
- Loading branch information