DockerComposeYamlInstallationProviderTest.java
package org.keycloak.procotol.docker.installation;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.keycloak.protocol.docker.installation.DockerComposeYamlInstallationProvider.ROOT_DIR;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import jakarta.ws.rs.core.Response;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.KeyType;
import org.keycloak.protocol.docker.installation.DockerComposeYamlInstallationProvider;
import org.keycloak.rule.CryptoInitRule;
public class DockerComposeYamlInstallationProviderTest {
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
DockerComposeYamlInstallationProvider installationProvider;
static Certificate certificate;
@Before
public void setUp() throws Exception {
final KeyPairGenerator keyGen = CryptoIntegration.getProvider().getKeyPairGen(KeyType.RSA);
keyGen.initialize(2048, new SecureRandom());
final KeyPair keypair = keyGen.generateKeyPair();
certificate = CertificateUtils.generateV1SelfSignedCertificate(keypair, "test-realm");
installationProvider = new DockerComposeYamlInstallationProvider();
}
private Response fireInstallationProvider() throws IOException {
ByteArrayOutputStream byteStream = null;
ZipOutputStream zipOutput = null;
byteStream = new ByteArrayOutputStream();
zipOutput = new ZipOutputStream(byteStream);
return installationProvider.generateInstallation(zipOutput, byteStream, certificate, new URL("http://localhost:8080/auth/"), "docker-test", "docker-registry");
}
@Test
@Ignore // Used only for smoke testing
public void writeToRealZip() throws IOException {
final Response response = fireInstallationProvider();
final byte[] responseBytes = (byte[]) response.getEntity();
FileUtils.writeByteArrayToFile(new File("target/keycloak-docker-compose-yaml.zip"), responseBytes);
}
@Test
public void testAllTheZipThings() throws Exception {
final Response response = fireInstallationProvider();
assertThat("compose YAML returned non-ok response", response.getStatus(), equalTo(Response.Status.OK.getStatusCode()));
shouldIncludeDockerComposeYamlInZip(getZipResponseFromInstallProvider(response));
shouldIncludeReadmeInZip(getZipResponseFromInstallProvider(response));
shouldWriteBlankDataDirectoryInZip(getZipResponseFromInstallProvider(response));
shouldWriteCertDirectoryInZip(getZipResponseFromInstallProvider(response));
shouldWriteSslCertificateInZip(getZipResponseFromInstallProvider(response));
shouldWritePrivateKeyInZip(getZipResponseFromInstallProvider(response));
}
public void shouldIncludeDockerComposeYamlInZip(ZipInputStream zipInput) throws Exception {
final Optional<String> actualDockerComposeFileContents = getFileContents(zipInput, ROOT_DIR + "docker-compose.yaml");
assertThat("Could not find docker-compose.yaml file in zip archive response", actualDockerComposeFileContents.isPresent(), equalTo(true));
List<String> expectedDockerComposeAsStringLines = FileUtils.readLines(new File("src/test/resources/docker-compose-expected.yaml"), Charset.defaultCharset());
String[] actualDockerComposeAsStringLines = actualDockerComposeFileContents.get().split("\n");
String messageIfTestFails = "Invalid docker-compose file contents: \n" + actualDockerComposeFileContents.get();
for (int i = 0; i < expectedDockerComposeAsStringLines.size(); i++) {
assertEquals(messageIfTestFails, expectedDockerComposeAsStringLines.get(i), actualDockerComposeAsStringLines[i]);
}
}
public void shouldIncludeReadmeInZip(ZipInputStream zipInput) throws Exception {
final Optional<String> dockerComposeFileContents = getFileContents(zipInput, ROOT_DIR + "README.md");
assertThat("Could not find README.md file in zip archive response", dockerComposeFileContents.isPresent(), equalTo(true));
}
public void shouldWriteBlankDataDirectoryInZip(ZipInputStream zipInput) throws Exception {
ZipEntry zipEntry;
boolean dataDirFound = false;
while ((zipEntry = zipInput.getNextEntry()) != null) {
try {
if (zipEntry.getName().equals(ROOT_DIR + "data/")) {
dataDirFound = true;
assertThat("Zip entry for data directory is not the correct type", zipEntry.isDirectory(), equalTo(true));
}
} finally {
zipInput.closeEntry();
}
}
assertThat("Could not find data directory", dataDirFound, equalTo(true));
}
public void shouldWriteCertDirectoryInZip(ZipInputStream zipInput) throws Exception {
ZipEntry zipEntry;
boolean certsDirFound = false;
while ((zipEntry = zipInput.getNextEntry()) != null) {
try {
if (zipEntry.getName().equals(ROOT_DIR + "certs/")) {
certsDirFound = true;
assertThat("Zip entry for cert directory is not the correct type", zipEntry.isDirectory(), equalTo(true));
}
} finally {
zipInput.closeEntry();
}
}
assertThat("Could not find cert directory", certsDirFound, equalTo(true));
}
public void shouldWriteSslCertificateInZip(ZipInputStream zipInput) throws Exception {
final Optional<String> localhostCertificateFileContents = getFileContents(zipInput, ROOT_DIR + "certs/localhost.crt");
assertThat("Could not find localhost certificate", localhostCertificateFileContents.isPresent(), equalTo(true));
final X509Certificate x509Certificate = PemUtils.decodeCertificate(localhostCertificateFileContents.get());
assertThat("Invalid x509 given by docker-compose YAML", x509Certificate, notNullValue());
}
public void shouldWritePrivateKeyInZip(ZipInputStream zipInput) throws Exception {
final Optional<String> localhostPrivateKeyFileContents = getFileContents(zipInput, ROOT_DIR + "certs/localhost.key");
assertThat("Could not find localhost private key", localhostPrivateKeyFileContents.isPresent(), equalTo(true));
final PrivateKey privateKey = PemUtils.decodePrivateKey(localhostPrivateKeyFileContents.get());
assertThat("Invalid private Key given by docker-compose YAML", privateKey, notNullValue());
}
private ZipInputStream getZipResponseFromInstallProvider(Response response) throws IOException {
final Object responseEntity = response.getEntity();
if (!(responseEntity instanceof byte[])) {
fail("Recieved non-byte[] entity for docker-compose YAML installation response");
}
return new ZipInputStream(new ByteArrayInputStream((byte[]) responseEntity));
}
private static Optional<String> getFileContents(final ZipInputStream zipInputStream, final String fileName) throws IOException {
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
try {
if (zipEntry.getName().equals(fileName)) {
return Optional.of(readBytesToString(zipInputStream));
}
} finally {
zipInputStream.closeEntry();
}
}
// fall-through case if file name not found:
return Optional.empty();
}
private static String readBytesToString(final InputStream inputStream) throws IOException {
final ByteArrayOutputStream output = new ByteArrayOutputStream();
final byte[] buffer = new byte[4096];
int bytesRead;
try {
while ((bytesRead = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
} finally {
output.close();
}
return new String(output.toByteArray());
}
}