diff --git a/.gitignore b/.gitignore index 7ce562ca0..dc1490564 100644 --- a/.gitignore +++ b/.gitignore @@ -52,10 +52,12 @@ docker-compose.override.yml helm/akhq/README.md helm/akhq/LICENSE - ## Docs docs/node_modules docs/.vuepress/.temp docs/.vuepress/.cache docs/.vuepress/public/contributors.html docs/.vuepress/dist + +/.factorypath +/client/.settings \ No newline at end of file diff --git a/build.gradle b/build.gradle index 852cc8c6c..8ba048ad5 100644 --- a/build.gradle +++ b/build.gradle @@ -145,6 +145,15 @@ dependencies { implementation group: "com.google.protobuf", name: "protobuf-java", version: '3.25.0' implementation group: "com.google.protobuf", name: "protobuf-java-util", version: '3.25.0' + //cenk is testing + implementation("io.micronaut:micronaut-http-client") + implementation("io.micronaut:micronaut-inject") + implementation("io.micronaut:micronaut-runtime") + implementation("jakarta.inject:jakarta.inject-api") + implementation("org.reactivestreams:reactive-streams") + + + // Password hashing implementation group: "org.mindrot", name: "jbcrypt", version: "0.4" diff --git a/docs/docs/configuration/authentifications/external.md b/docs/docs/configuration/authentifications/external.md index 475ce8aa9..be1ac5522 100644 --- a/docs/docs/configuration/authentifications/external.md +++ b/docs/docs/configuration/authentifications/external.md @@ -76,6 +76,19 @@ and expect the following JSON as response : } } ```` + +If you want to send a static authentication token to the external service where it might be public, you can extend the configuration for the rest interface as follows: +````yaml +akhq: + security: + rest: + enabled: true + url: https://external.service/get-roles-and-attributes + headers: + - name: Authorization + value: Bearer your-token +```` + ::: warning The response must contain the `Content-Type: application/json` header to prevent any issue when reading the response. ::: @@ -128,4 +141,4 @@ public class ClaimResponse { private List connectsFilterRegexp; private List consumerGroupsFilterRegexp; } -```` +```` \ No newline at end of file diff --git a/src/main/java/org/akhq/security/claim/RestApiClaimProvider.java b/src/main/java/org/akhq/security/claim/RestApiClaimProvider.java index 096b8b2c2..999740fd2 100644 --- a/src/main/java/org/akhq/security/claim/RestApiClaimProvider.java +++ b/src/main/java/org/akhq/security/claim/RestApiClaimProvider.java @@ -8,6 +8,7 @@ import io.micronaut.http.client.annotation.Client; import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.annotation.ExecuteOn; + import org.akhq.models.security.ClaimProvider; import org.akhq.models.security.ClaimRequest; import org.akhq.models.security.ClaimResponse; diff --git a/src/main/java/org/akhq/security/claim/RestApiClaimProviderFilter.java b/src/main/java/org/akhq/security/claim/RestApiClaimProviderFilter.java new file mode 100644 index 000000000..33267c668 --- /dev/null +++ b/src/main/java/org/akhq/security/claim/RestApiClaimProviderFilter.java @@ -0,0 +1,40 @@ +package org.akhq.security.claim; + +import io.micronaut.context.annotation.Primary; +import io.micronaut.context.annotation.Property; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.util.StringUtils; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.MutableHttpRequest; +import io.micronaut.http.annotation.ClientFilter; +import io.micronaut.http.filter.ClientFilterChain; +import io.micronaut.http.filter.HttpClientFilter; +import org.reactivestreams.Publisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; + +@Primary +@Requires(property = "akhq.security.rest.enabled", value = StringUtils.TRUE) +@Requires(property = "akhq.security.rest.headers") +@ClientFilter("${akhq.security.rest.url}") +public class RestApiClaimProviderFilter implements HttpClientFilter { + private static final Logger LOG = LoggerFactory.getLogger(RestApiClaimProviderFilter.class); + + private static final String HEADER_KEY = "name"; + private static final String HEADER_VALUE = "value"; + + @Property(name = "akhq.security.rest.headers") + private List> headers; + + @Override + public Publisher> doFilter(MutableHttpRequest request, ClientFilterChain chain) { + LOG.trace("Modifying outgoing authentication request."); + headers.forEach(header -> request.header(header.get(HEADER_KEY), header.get(HEADER_VALUE))); + + return Mono.from(chain.proceed(request)); + } +} \ No newline at end of file diff --git a/src/test/java/org/akhq/security/claim/RestApiClaimProviderTest.java b/src/test/java/org/akhq/security/claim/RestApiClaimProviderTest.java index 653ead84a..dfcfa0fcf 100644 --- a/src/test/java/org/akhq/security/claim/RestApiClaimProviderTest.java +++ b/src/test/java/org/akhq/security/claim/RestApiClaimProviderTest.java @@ -14,6 +14,7 @@ import io.micronaut.http.annotation.Body; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Filter; +import io.micronaut.http.annotation.Header; import io.micronaut.http.annotation.Post; import io.micronaut.http.client.annotation.Client; import io.micronaut.http.filter.HttpServerFilter; @@ -33,6 +34,7 @@ import java.util.Base64; import java.util.List; import java.util.Map; +import java.util.Optional; import jakarta.annotation.security.PermitAll; import static org.hamcrest.MatcherAssert.assertThat; @@ -90,7 +92,12 @@ public Publisher> doFilter(HttpRequest request, Server @Controller("/external-mock") static class RestApiExternalService { @Post - String generateClaim(@Body ClaimRequest request) { + String generateClaim(@Body ClaimRequest request, + @Header("X-Custom-Authentication") Optional authHeader) { + if (authHeader.isEmpty() || !authHeader.get().equals("Bearer custom-authentication")) { + throw new RuntimeException("Invalid custom authentication header."); + } + return "{\n" + " \"groups\" : {" + @@ -99,9 +106,8 @@ String generateClaim(@Body ClaimRequest request) { "\"patterns\": [\"user.*\"]," + "\"clusters\": [\"pub.*\"]" + "}]" + - "}"+ + "}" + "}"; } } } - diff --git a/src/test/resources/application-rest-api.yml b/src/test/resources/application-rest-api.yml index 439d698c5..e0026ff13 100644 --- a/src/test/resources/application-rest-api.yml +++ b/src/test/resources/application-rest-api.yml @@ -34,4 +34,7 @@ akhq: - admin rest: enabled: true - url: /external-mock \ No newline at end of file + url: /external-mock + headers: + - name: X-Custom-Authentication + value: Bearer custom-authentication