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

BE: RBAC: Add integration test for Active Directory auth #726

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions .github/workflows/backend_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
BASE_REF: ${{ github.base_ref }}
SKIP_SONAR: "true" # TODO remove when public
run: |
./mvnw clean install -DskipTests -Pprod
wernerdv marked this conversation as resolved.
Show resolved Hide resolved
./mvnw -B -ntp versions:set -DnewVersion=${{ github.event.pull_request.head.sha }}
./mvnw -B -V -ntp verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
-Dsonar.skip=${SKIP_SONAR} \
Expand All @@ -63,6 +64,7 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_BACKEND }}
SKIP_SONAR: "true" # TODO remove when public
run: |
./mvnw clean install -DskipTests -Pprod
./mvnw -B -ntp versions:set -DnewVersion=$GITHUB_SHA
./mvnw -B -V -ntp verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
-Dsonar.skip=${SKIP_SONAR} \
Expand Down
120 changes: 120 additions & 0 deletions api/src/test/java/io/kafbat/ui/ActiveDirectoryIntegrationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package io.kafbat.ui;

import static io.kafbat.ui.AbstractIntegrationTest.LOCAL;
import static io.kafbat.ui.container.ActiveDirectoryContainer.DOMAIN;
import static io.kafbat.ui.container.ActiveDirectoryContainer.EMPTY_PERMISSIONS_USER;
import static io.kafbat.ui.container.ActiveDirectoryContainer.FIRST_USER_WITH_GROUP;
import static io.kafbat.ui.container.ActiveDirectoryContainer.PASSWORD;
import static io.kafbat.ui.container.ActiveDirectoryContainer.SECOND_USER_WITH_GROUP;
import static io.kafbat.ui.container.ActiveDirectoryContainer.USER_WITHOUT_GROUP;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import io.kafbat.ui.container.ActiveDirectoryContainer;
import io.kafbat.ui.model.AuthenticationInfoDTO;
import io.kafbat.ui.model.ResourceTypeDTO;
import io.kafbat.ui.model.UserPermissionDTO;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.BodyInserters;

@SpringBootTest
@ActiveProfiles("rbac-ad")
@AutoConfigureWebTestClient(timeout = "60000")
@ContextConfiguration(initializers = {ActiveDirectoryIntegrationTest.Initializer.class})
public class ActiveDirectoryIntegrationTest {
private static final String SESSION = "SESSION";

private static final ActiveDirectoryContainer ACTIVE_DIRECTORY = new ActiveDirectoryContainer();

@Autowired
private WebTestClient webTestClient;

@BeforeAll
public static void setup() {
ACTIVE_DIRECTORY.start();
}

@AfterAll
public static void shutdown() {
ACTIVE_DIRECTORY.stop();
}

@Test
public void testUserPermissions() {
AuthenticationInfoDTO info = authenticationInfo(FIRST_USER_WITH_GROUP);

assertNotNull(info);
assertTrue(info.getRbacEnabled());

List<UserPermissionDTO> permissions = info.getUserInfo().getPermissions();

assertFalse(permissions.isEmpty());
assertTrue(permissions.stream().anyMatch(permission ->
permission.getClusters().contains(LOCAL) && permission.getResource() == ResourceTypeDTO.TOPIC));
assertEquals(permissions, authenticationInfo(SECOND_USER_WITH_GROUP).getUserInfo().getPermissions());
assertEquals(permissions, authenticationInfo(USER_WITHOUT_GROUP).getUserInfo().getPermissions());
}

@Test
public void testEmptyPermissions() {
assertTrue(Objects.requireNonNull(authenticationInfo(EMPTY_PERMISSIONS_USER))
.getUserInfo()
.getPermissions()
.isEmpty()
);
}

private String session(String name) {
return Objects.requireNonNull(
webTestClient
.post()
.uri("/login")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData("username", name).with("password", PASSWORD))
.exchange()
.expectStatus()
.isFound()
.returnResult(String.class)
.getResponseCookies()
.getFirst(SESSION))
.getValue();
}

private AuthenticationInfoDTO authenticationInfo(String name) {
return webTestClient
.get()
.uri("/api/authorization")
.cookie(SESSION, session(name))
.exchange()
.expectStatus()
.isOk()
.returnResult(AuthenticationInfoDTO.class)
.getResponseBody()
.blockFirst();
}

public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(@NotNull ConfigurableApplicationContext context) {
System.setProperty("spring.ldap.urls", ACTIVE_DIRECTORY.getLdapUrl());
System.setProperty("oauth2.ldap.activeDirectory", "true");
System.setProperty("oauth2.ldap.activeDirectory.domain", DOMAIN);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package io.kafbat.ui.container;

import com.github.dockerjava.api.command.InspectContainerResponse;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;

@Slf4j
public class ActiveDirectoryContainer extends GenericContainer<ActiveDirectoryContainer> {
public static final String DOMAIN = "corp.kafbat.io";
public static final String PASSWORD = "StrongPassword123";
public static final String FIRST_USER_WITH_GROUP = "JohnDoe";
public static final String SECOND_USER_WITH_GROUP = "JohnWick";
public static final String USER_WITHOUT_GROUP = "JackSmith";
public static final String EMPTY_PERMISSIONS_USER = "JohnJames";

private static final String DOMAIN_DC = "dc=corp,dc=kafbat,dc=io";
private static final String GROUP = "group";
private static final String FIRST_GROUP = "firstGroup";
private static final String SECOND_GROUP = "secondGroup";
private static final String DOMAIN_EMAIL = "kafbat.io";
private static final String SAMBA_TOOL = "samba-tool";
private static final int LDAP_PORT = 389;
private static final DockerImageName IMAGE_NAME = DockerImageName.parse("nowsci/samba-domain:latest");

public ActiveDirectoryContainer() {
super(IMAGE_NAME);

withExposedPorts(LDAP_PORT);

withEnv("DOMAIN", DOMAIN);
withEnv("DOMAIN_DC", DOMAIN_DC);
withEnv("DOMAIN_EMAIL", DOMAIN_EMAIL);
withEnv("DOMAINPASS", PASSWORD);
withEnv("NOCOMPLEXITY", "true");
withEnv("INSECURELDAP", "true");

withPrivilegedMode(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this?

Copy link
Contributor Author

@wernerdv wernerdv Jan 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, otherwise there will be an error when starting the container:

com.github.dockerjava.api.exception.ConflictException: Status 409: {"message":"container <> is not running"}

in the documentation, --privileged flag is also used in the examples
https://nowsci.com/samba-domain/#examples-with-docker-run

}

protected void containerIsStarted(InspectContainerResponse containerInfo) {
createUser(EMPTY_PERMISSIONS_USER);
createUser(USER_WITHOUT_GROUP);
createUser(FIRST_USER_WITH_GROUP);
createUser(SECOND_USER_WITH_GROUP);

exec(SAMBA_TOOL, GROUP, "add", FIRST_GROUP);
exec(SAMBA_TOOL, GROUP, "add", SECOND_GROUP);
exec(SAMBA_TOOL, GROUP, "addmembers", FIRST_GROUP, FIRST_USER_WITH_GROUP);
exec(SAMBA_TOOL, GROUP, "addmembers", SECOND_GROUP, SECOND_USER_WITH_GROUP);
}

public String getLdapUrl() {
return String.format("ldap://%s:%s", getHost(), getMappedPort(LDAP_PORT));
}

private void createUser(String name) {
exec(SAMBA_TOOL, "user", "create", name, PASSWORD, "--mail-address", name + '@' + DOMAIN_EMAIL);
exec(SAMBA_TOOL, "user", "setexpiry", name, "--noexpiry");
}

private void exec(String... cmd) {
ExecResult result;
try {
result = execInContainer(cmd);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}

if (result.getStdout() != null && !result.getStdout().isEmpty()) {
log.info("Output: {}", result.getStdout());
}

if (result.getExitCode() != 0) {
throw new IllegalStateException(result.toString());
}
}
}
23 changes: 23 additions & 0 deletions api/src/test/resources/application-rbac-ad.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
auth:
type: LDAP
rbac:
roles:
- name: "roleName"
clusters:
- local
subjects:
- provider: ldap_ad
type: group
value: firstGroup
- provider: ldap_ad
type: group
value: secondGroup
- provider: ldap_ad
type: user
value: JackSmith
permissions:
- resource: applicationconfig
actions: all
- resource: topic
value: ".*"
actions: all
Loading