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

Upgrade keycloak to version 18.0.0 #498

Merged
merged 2 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@

import io.quarkus.test.bootstrap.KeycloakService;
import io.quarkus.test.bootstrap.RestService;
import io.quarkus.test.services.Container;
import io.quarkus.test.services.KeycloakContainer;

public abstract class BaseSecurityResourceIT {

static final String REALM_BASE_PATH = "realms";
static final String REALM_DEFAULT = "test-realm";
static final String CLIENT_ID_DEFAULT = "test-application-client";
static final String CLIENT_SECRET_DEFAULT = "test-application-client-secret";
static final String NORMAL_USER = "test-normal-user";

@Container(image = "quay.io/keycloak/keycloak:16.1.0", expectedLog = "Admin console listening", port = 8080)
static final KeycloakService keycloak = new KeycloakService("/keycloak-realm.json", REALM_DEFAULT);
@KeycloakContainer(command = { "start-dev --import-realm" })
static KeycloakService keycloak = new KeycloakService("/keycloak-realm.json", REALM_DEFAULT, REALM_BASE_PATH);

private AuthzClient authzClient;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.quarkus.qe;

import static org.hamcrest.Matchers.equalTo;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.keycloak.authorization.client.AuthzClient;

import io.quarkus.test.bootstrap.KeycloakService;
import io.quarkus.test.bootstrap.RestService;
import io.quarkus.test.scenarios.QuarkusScenario;
import io.quarkus.test.services.Container;
import io.quarkus.test.services.QuarkusApplication;

@QuarkusScenario
public class LegacyKeycloakIT {

static final String REALM_DEFAULT = "test-realm";
static final String CLIENT_ID_DEFAULT = "test-application-client";
static final String CLIENT_SECRET_DEFAULT = "test-application-client-secret";
static final String NORMAL_USER = "test-normal-user";

@Container(image = "quay.io/keycloak/keycloak:14.0.0", expectedLog = "Admin console listening", port = 8080)
static KeycloakService keycloak = new KeycloakService("/keycloak-realm.json", REALM_DEFAULT, "/auth/realms/");

private AuthzClient authzClient;

@QuarkusApplication
static final RestService app = new RestService()
.withProperty("quarkus.oidc.auth-server-url", () -> keycloak.getRealmUrl())
.withProperty("quarkus.oidc.client-id", CLIENT_ID_DEFAULT)
.withProperty("quarkus.oidc.credentials.secret", CLIENT_SECRET_DEFAULT);

@BeforeEach
public void setup() {
authzClient = keycloak.createAuthzClient(CLIENT_ID_DEFAULT, CLIENT_SECRET_DEFAULT);
}

@Test
public void checkUserResourceByNormalUser() {
app.given()
.auth().oauth2(getTokenByTestNormalUser())
.get("/user")
.then()
.statusCode(200)
.body(equalTo("Hello, user test-normal-user"));
}

private String getTokenByTestNormalUser() {
return authzClient.obtainAccessToken(NORMAL_USER, NORMAL_USER).getToken();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class OpenShiftUsingCustomTemplateResourceIT {
private static final String CLIENT_ID_DEFAULT = "test-application-client";
private static final String CLIENT_SECRET_DEFAULT = "test-application-client-secret";

@Container(image = "quay.io/keycloak/keycloak:16.1.0", expectedLog = "Admin console listening", port = 8080)
@Container(image = "quay.io/keycloak/keycloak:18.0.0", expectedLog = "started", port = 8080)
static final KeycloakService customkeycloak = new KeycloakService("/keycloak-realm.json", REALM_DEFAULT);

@QuarkusApplication
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.qe;

import io.quarkus.test.scenarios.OpenShiftDeploymentStrategy;
import io.quarkus.test.scenarios.OpenShiftScenario;

@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingOpenShiftExtension)
public class OpenShiftUsingExtensionLegacyKeycloakIT extends LegacyKeycloakIT {
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

@QuarkusScenario
public class SecurityResourceIT extends BaseSecurityResourceIT {

@QuarkusApplication
static final RestService app = new RestService()
.withProperty("quarkus.oidc.auth-server-url", () -> keycloak.getRealmUrl())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ items:
- name: latest
from:
kind: DockerImage
name: quay.io/keycloak/keycloak:16.1.0
name: quay.io/keycloak/keycloak:18.0.0
- apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
Expand All @@ -31,6 +31,7 @@ items:
spec:
containers:
- name: keycloak
args: [ "start-dev", "--import-realm"]
env:
- name: X509_CA_BUNDLE
value: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
Expand Down
33 changes: 32 additions & 1 deletion examples/keycloak/src/test/resources/keycloak-realm.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,38 @@
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "test-application-client-secret"
"secret": "test-application-client-secret",
"protocolMappers": [
{
"id": "f17f8d5f-2327-4e0b-8001-48a7706be252",
"name": "realm roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-realm-role-mapper",
"consentRequired": false,
"config": {
"user.attribute": "foo",
"access.token.claim": "true",
"claim.name": "realm_access.roles",
"jsonType.label": "String",
"multivalued": "true"
}
},
{
"id": "0e0f1e8d-60f9-4435-b753-136d70e56af8",
"name": "username",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "username",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "preferred_username",
"jsonType.label": "String"
}
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.test.services.containers;

import java.lang.annotation.Annotation;
import java.util.Optional;
import java.util.ServiceLoader;

import io.quarkus.test.bootstrap.ManagedResource;
Expand Down Expand Up @@ -29,7 +30,7 @@ protected String getExpectedLog() {
}

protected String[] getCommand() {
return command;
return Optional.ofNullable(command).orElse(new String[] {});
}

protected Integer getPort() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import static io.quarkus.test.bootstrap.BaseService.SERVICE_STARTUP_TIMEOUT;
import static io.quarkus.test.bootstrap.BaseService.SERVICE_STARTUP_TIMEOUT_DEFAULT;
import static io.quarkus.test.utils.PropertiesUtils.RESOURCE_PREFIX;
import static io.quarkus.test.utils.PropertiesUtils.RESOURCE_WITH_DESTINATION_PREFIX;
import static io.quarkus.test.utils.PropertiesUtils.RESOURCE_WITH_DESTINATION_PREFIX_MATCHER;
import static io.quarkus.test.utils.PropertiesUtils.RESOURCE_WITH_DESTINATION_SPLIT_CHAR;
import static io.quarkus.test.utils.PropertiesUtils.SECRET_PREFIX;

import java.nio.file.Files;
Expand Down Expand Up @@ -119,6 +122,17 @@ private Map<String, String> resolveProperties() {
if (isResource(entry.getValue())) {
value = entry.getValue().replace(RESOURCE_PREFIX, StringUtils.EMPTY);
addFileToContainer(value);
} else if (isResourceWithDestinationPath(entry.getValue())) {
value = entry.getValue().replace(RESOURCE_WITH_DESTINATION_PREFIX, StringUtils.EMPTY);
if (!value.matches(RESOURCE_WITH_DESTINATION_PREFIX_MATCHER)) {
String errorMsg = String.format("Unexpected %s format. Expected destinationPath|fileName but found %s",
RESOURCE_WITH_DESTINATION_PREFIX, value);
throw new RuntimeException(errorMsg);
}

String destinationPath = value.split(RESOURCE_WITH_DESTINATION_SPLIT_CHAR)[0];
String fileName = value.split(RESOURCE_WITH_DESTINATION_SPLIT_CHAR)[1];
addFileToContainer(destinationPath, fileName);
} else if (isSecret(entry.getValue())) {
value = entry.getValue().replace(SECRET_PREFIX, StringUtils.EMPTY);
addFileToContainer(value);
Expand All @@ -139,6 +153,15 @@ private void addFileToContainer(String filePath) {
}
}

private void addFileToContainer(String destinationPath, String hostFilePath) {
String containerFullPath = destinationPath + hostFilePath;
innerContainer.withClasspathResourceMapping(hostFilePath, containerFullPath, BindMode.READ_ONLY);
}

private boolean isResourceWithDestinationPath(String key) {
return key.startsWith(RESOURCE_WITH_DESTINATION_PREFIX);
}

private boolean isResource(String key) {
return key.startsWith(RESOURCE_PREFIX);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
public final class PropertiesUtils {

public static final String RESOURCE_PREFIX = "resource::/";
public static final String RESOURCE_WITH_DESTINATION_PREFIX = "resource_with_destination::";
public static final String RESOURCE_WITH_DESTINATION_SPLIT_CHAR = "\\|";
public static final String RESOURCE_WITH_DESTINATION_PREFIX_MATCHER = ".*" + RESOURCE_WITH_DESTINATION_SPLIT_CHAR + ".*";
public static final String SECRET_PREFIX = "secret::/";
public static final Path TARGET = Path.of("target");
public static final String SLASH = "/";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package io.quarkus.test.bootstrap.inject;

import static io.quarkus.test.model.CustomVolume.VolumeType.CONFIG_MAP;
import static io.quarkus.test.model.CustomVolume.VolumeType.SECRET;
import static io.quarkus.test.utils.PropertiesUtils.RESOURCE_PREFIX;
import static io.quarkus.test.utils.PropertiesUtils.RESOURCE_WITH_DESTINATION_PREFIX;
import static io.quarkus.test.utils.PropertiesUtils.RESOURCE_WITH_DESTINATION_PREFIX_MATCHER;
import static io.quarkus.test.utils.PropertiesUtils.RESOURCE_WITH_DESTINATION_SPLIT_CHAR;
import static io.quarkus.test.utils.PropertiesUtils.SECRET_PREFIX;
import static io.quarkus.test.utils.PropertiesUtils.SLASH;
import static io.quarkus.test.utils.PropertiesUtils.TARGET;
Expand All @@ -26,16 +31,13 @@
import org.apache.commons.lang3.StringUtils;

import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesList;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeBuilder;
import io.fabric8.kubernetes.api.model.VolumeMount;
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.client.utils.Serialization;
Expand All @@ -46,6 +48,7 @@
import io.quarkus.test.bootstrap.Service;
import io.quarkus.test.configuration.PropertyLookup;
import io.quarkus.test.logging.Log;
import io.quarkus.test.model.CustomVolume;
import io.quarkus.test.utils.Command;
import io.quarkus.test.utils.FileUtils;

Expand Down Expand Up @@ -365,67 +368,88 @@ private EnvVar getEnvVarByKey(String key, Container container) {

private Map<String, String> enrichProperties(Map<String, String> properties, Deployment deployment) {
// mount path x volume
Map<String, Volume> volumes = new HashMap<>();
Map<String, CustomVolume> volumes = new HashMap<>();

Map<String, String> output = new HashMap<>();
for (Entry<String, String> entry : properties.entrySet()) {
String value = entry.getValue();
String propertyValue = entry.getValue();
if (isResource(entry.getValue())) {
String path = entry.getValue().replace(RESOURCE_PREFIX, StringUtils.EMPTY);
String mountPath = getMountPath(path);
String filename = getFileName(path);
String configMapName = normalizeName(mountPath);
String configMapName = normalizeConfigMapName(mountPath);

// Update config map
createOrUpdateConfigMap(configMapName, filename, getFileContent(path));

// Add the volume
if (!volumes.containsKey(mountPath)) {
Volume volume = new VolumeBuilder()
.withName(configMapName)
.withConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName).build())
.build();
volumes.put(mountPath, volume);
volumes.put(mountPath, new CustomVolume(configMapName, "", CONFIG_MAP));
}

value = mountPath + SLASH + filename;
propertyValue = mountPath + SLASH + filename;
} else if (isResourceWithDestinationPath(propertyValue)) {
String path = propertyValue.replace(RESOURCE_WITH_DESTINATION_PREFIX, StringUtils.EMPTY);
if (!propertyValue.matches(RESOURCE_WITH_DESTINATION_PREFIX_MATCHER)) {
String errorMsg = String.format("Unexpected %s format. Expected destinationPath|fileName but found %s",
RESOURCE_WITH_DESTINATION_PREFIX, propertyValue);
throw new RuntimeException(errorMsg);
}

String mountPath = path.split(RESOURCE_WITH_DESTINATION_SPLIT_CHAR)[0];
String fileName = path.split(RESOURCE_WITH_DESTINATION_SPLIT_CHAR)[1];
String fileNameNormalized = getFileName(fileName);
String configMapName = normalizeConfigMapName(mountPath);

// Update config map
createOrUpdateConfigMap(configMapName, fileNameNormalized, getFileContent(fileName));
propertyValue = mountPath + SLASH + fileNameNormalized;
// Add the volume
if (!volumes.containsKey(mountPath)) {
volumes.put(propertyValue, new CustomVolume(configMapName, fileNameNormalized, CONFIG_MAP));
}
} else if (isSecret(entry.getValue())) {
String path = entry.getValue().replace(SECRET_PREFIX, StringUtils.EMPTY);
String mountPath = getMountPath(path);
String filename = getFileName(path);
String secretName = normalizeName(path);
String secretName = normalizeConfigMapName(path);

// Push secret file
doCreateSecretFromFile(secretName, getFilePath(path));

// Add the volume
Volume volume = new VolumeBuilder()
.withName(secretName)
.withSecret(new SecretVolumeSourceBuilder()
.withSecretName(secretName)
.build())
.build();
volumes.put(mountPath, volume);

value = mountPath + SLASH + filename;
volumes.put(mountPath, volumes.put(mountPath, new CustomVolume(secretName, "", SECRET)));
propertyValue = mountPath + SLASH + filename;
}

output.put(entry.getKey(), value);
output.put(entry.getKey(), propertyValue);
}

for (Entry<String, Volume> volume : volumes.entrySet()) {
deployment.getSpec().getTemplate().getSpec().getVolumes().add(volume.getValue());
for (Entry<String, CustomVolume> volume : volumes.entrySet()) {
deployment.getSpec().getTemplate().getSpec().getVolumes().add(volume.getValue().getVolume());

// Configure all the containers to map the volume
deployment.getSpec().getTemplate().getSpec().getContainers()
.forEach(container -> container.getVolumeMounts()
.add(new VolumeMountBuilder().withName(volume.getValue().getName())
.withReadOnly(true).withMountPath(volume.getKey()).build()));
.add(createVolumeMount(volume)));
}

return output;
}

private VolumeMount createVolumeMount(Entry<String, CustomVolume> volume) {
VolumeMountBuilder volumeMountBuilder = new VolumeMountBuilder().withName(volume.getValue().getName())
.withReadOnly(true).withMountPath(volume.getKey());

if (!volume.getValue().getSubFolderRegExp().isEmpty()) {
volumeMountBuilder.withSubPathExpr(volume.getValue().getSubFolderRegExp());
}

return volumeMountBuilder.build();
}

private boolean isResourceWithDestinationPath(String key) {
return key.startsWith(RESOURCE_WITH_DESTINATION_PREFIX);
}

private void createOrUpdateConfigMap(String configMapName, String key, String value) {
if (client.configMaps().withName(configMapName).get() != null) {
// update existing config map by adding new file
Expand Down Expand Up @@ -498,7 +522,7 @@ private String getFilePath(String path) {
return path;
}

private String normalizeName(String name) {
private String normalizeConfigMapName(String name) {
return StringUtils.removeStart(name, SLASH)
.replaceAll(Pattern.quote("."), "-")
.replaceAll(SLASH, "-");
Expand Down
Loading