Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to Add an Authentication Header for External REST API Authentication #1759

Merged
merged 14 commits into from
Jun 3, 2024
Merged
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
15 changes: 14 additions & 1 deletion docs/docs/configuration/authentifications/external.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
:::
Expand Down Expand Up @@ -128,4 +141,4 @@ public class ClaimResponse {
private List<String> connectsFilterRegexp;
private List<String> consumerGroupsFilterRegexp;
}
````
````
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Map<String, String>> headers;

@Override
public Publisher<? extends HttpResponse<?>> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -90,7 +92,12 @@ public Publisher<MutableHttpResponse<?>> 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<String> authHeader) {
if (authHeader.isEmpty() || !authHeader.get().equals("Bearer custom-authentication")) {
throw new RuntimeException("Invalid custom authentication header.");
}

return
"{\n" +
" \"groups\" : {" +
Expand All @@ -99,9 +106,8 @@ String generateClaim(@Body ClaimRequest request) {
"\"patterns\": [\"user.*\"]," +
"\"clusters\": [\"pub.*\"]" +
"}]" +
"}"+
"}" +
"}";
}
}
}

5 changes: 4 additions & 1 deletion src/test/resources/application-rest-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ akhq:
- admin
rest:
enabled: true
url: /external-mock
url: /external-mock
headers:
- name: X-Custom-Authentication
value: Bearer custom-authentication
Loading