sarankk commented on code in PR #247:
URL: https://github.com/apache/cassandra-sidecar/pull/247#discussion_r2350207278


##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authentication/ReloadingJwtAuthenticationHandler.java:
##########
@@ -72,14 +80,24 @@ public ReloadingJwtAuthenticationHandler(Vertx vertx,
         this.vertx = vertx;
         this.jwtParameters = jwtParameters;
         this.roleProcessor = roleProcessor;
-
-        periodicTaskExecutor.schedule(new OAuth2AuthHandlerGenerateTask());
+        if 
(jwtParameters.jwtAuthType().equals(JwtParameters.AuthType.STATELESS))
+        {
+            periodicTaskExecutor.schedule(new 
PeriodicStatelessJwtRefreshTask());
+        }
+        else if 
(jwtParameters.jwtAuthType().equals(JwtParameters.AuthType.OAUTH))
+        {
+            periodicTaskExecutor.schedule(new OAuth2AuthHandlerGenerateTask());
+        }
+        else
+        {
+            throw new IllegalStateException("Unsupported JWT Auth Type: " + 
jwtParameters.jwtAuthType());
+        }
     }
 
     @Override
     public void authenticate(RoutingContext context, 
Handler<AsyncResult<User>> handler)
     {
-        OAuth2AuthHandlerImpl oAuth2AuthHandler = delegateHandler.get();
+        AuthenticationHandlerInternal oAuth2AuthHandler = 
delegateHandler.get();

Review Comment:
   Nit: Rename to more generic variable name
   
   ```suggestion
           AuthenticationHandlerInternal authHandler = delegateHandler.get();
   ```



##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authentication/ReloadingJwtAuthenticationHandler.java:
##########
@@ -205,4 +229,55 @@ public void execute(Promise<Void> promise)
                              });
         }
     }
+
+    private class PeriodicStatelessJwtRefreshTask implements PeriodicTask
+    {
+        private final String taskName = 
String.format("PeriodicStatelessJwtRefreshTask_%s", jwtParameters.site());
+        @Override
+        public DurationSpec delay()
+        {
+            return jwtParameters.configDiscoverInterval();
+        }
+
+        @Override
+        public DurationSpec initialDelay()
+        {
+            return SecondBoundConfiguration.ZERO;
+        }
+
+
+        @Override
+        public void execute(Promise<Void> promise)
+        {
+            WebClient webClient = WebClient.create(vertx, new 
WebClientOptions().setSsl(true));
+            if (!jwtParameters.enabled())
+            {
+                delegateHandler.set(null);
+                promise.complete();
+                return;
+            }
+            String jwtPemUri = jwtParameters.site();
+            webClient.getAbs(jwtPemUri).send()
+                     .onSuccess(ar -> {
+                        String pem = ar.bodyAsString();
+                        JWTAuthOptions jwtAuthOptions = new JWTAuthOptions()
+                                                        .addPubSecKey(new 
PubSecKeyOptions()
+                                                                      
.setAlgorithm("RS256")
+                                                                      
.setBuffer(pem));
+                        JWTAuth auth = JWTAuth.create(vertx, jwtAuthOptions);
+                        AuthenticationHandlerInternal jwtAuthHandlerDelegate = 
new JWTAuthHandlerImpl(auth, null);
+                        delegateHandler.set(jwtAuthHandlerDelegate);
+                        promise.complete();
+                    }).onFailure(cause -> {
+                        LOGGER.error("Error encountered when refreshing 
stateless JWT PEM material.", cause);

Review Comment:
   Will be good to add metrics around successful and failed refreshes



##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authentication/ReloadingJwtAuthenticationHandler.java:
##########
@@ -205,4 +229,55 @@ public void execute(Promise<Void> promise)
                              });
         }
     }
+
+    private class PeriodicStatelessJwtRefreshTask implements PeriodicTask
+    {
+        private final String taskName = 
String.format("PeriodicStatelessJwtRefreshTask_%s", jwtParameters.site());
+        @Override
+        public DurationSpec delay()
+        {
+            return jwtParameters.configDiscoverInterval();
+        }
+
+        @Override
+        public DurationSpec initialDelay()
+        {
+            return SecondBoundConfiguration.ZERO;
+        }
+
+
+        @Override
+        public void execute(Promise<Void> promise)
+        {
+            WebClient webClient = WebClient.create(vertx, new 
WebClientOptions().setSsl(true));

Review Comment:
   Shall we create client at Periodic task level? Also do we need certs for 
`SSL` connection? 



##########
server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/ReloadingJwtAuthenticationHandlerTest.java:
##########
@@ -75,4 +95,102 @@ void testDelegateHandlerNotCreatedWhenJWTDisabled()
             assertThat(result.cause()).hasMessage("Service Unavailable");
         });
     }
+
+    @Test
+    void testStatelessJwtAuthenticationWithValidToken() throws Exception
+    {
+        // Generate a test RSA key pair and PEM
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+        keyGen.initialize(2048);
+        KeyPair keyPair = keyGen.generateKeyPair();
+
+        String publicKeyPem = "-----BEGIN PUBLIC KEY-----\n" +
+                              
Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()) +
+                              "\n-----END PUBLIC KEY-----";
+
+        Vertx vertx = Vertx.vertx();
+        try
+        {
+            // Mock HTTP server to serve the PEM
+            HttpServer mockServer = vertx.createHttpServer();
+            CountDownLatch serverLatch = new CountDownLatch(1);
+
+            mockServer.requestHandler(request -> {
+                request.response()
+                       .putHeader("Content-Type", "text/plain")
+                       .end(publicKeyPem);
+            }).listen(8080, result -> serverLatch.countDown());

Review Comment:
   Can we get port dynamically or use port `0` here? 



##########
server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/ReloadingJwtAuthenticationHandlerTest.java:
##########
@@ -75,4 +95,102 @@ void testDelegateHandlerNotCreatedWhenJWTDisabled()
             assertThat(result.cause()).hasMessage("Service Unavailable");
         });
     }
+
+    @Test
+    void testStatelessJwtAuthenticationWithValidToken() throws Exception
+    {
+        // Generate a test RSA key pair and PEM
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+        keyGen.initialize(2048);
+        KeyPair keyPair = keyGen.generateKeyPair();
+
+        String publicKeyPem = "-----BEGIN PUBLIC KEY-----\n" +
+                              
Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()) +
+                              "\n-----END PUBLIC KEY-----";
+
+        Vertx vertx = Vertx.vertx();
+        try
+        {
+            // Mock HTTP server to serve the PEM
+            HttpServer mockServer = vertx.createHttpServer();
+            CountDownLatch serverLatch = new CountDownLatch(1);
+
+            mockServer.requestHandler(request -> {
+                request.response()
+                       .putHeader("Content-Type", "text/plain")
+                       .end(publicKeyPem);
+            }).listen(8080, result -> serverLatch.countDown());
+
+            serverLatch.await(5, TimeUnit.SECONDS);
+
+            // Configure for stateless authentication
+            JwtParameterExtractor parameterExtractor = new 
JwtParameterExtractor(Map.of("enabled", "true",
+                                                                               
         "site", "http://localhost:8080/jwks";,
+                                                                               
         "jwt_auth_type", 
JwtParameters.AuthType.STATELESS.toString().toLowerCase()));
+            JwtRoleProcessor mockRoleProcessor = mock(JwtRoleProcessor.class);
+            
when(mockRoleProcessor.processRoles(any())).thenReturn(List.of("test_role"));
+            ReloadingJwtAuthenticationHandler handler = 
getReloadingJwtAuthenticationHandler(vertx, parameterExtractor, 
mockRoleProcessor);
+
+            // Wait a bit for the handler to be set
+            Thread.sleep(1000);

Review Comment:
   Nit: To avoid wait, we could add a loopAssert here
   
   ```suggestion
               loopAssert(1, () -> 
assertNotNull(handler.delegateHandler.get()));
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to