diff --git a/compose/cryostat.yml b/compose/cryostat.yml index 24e509f92..27304b4cb 100644 --- a/compose/cryostat.yml +++ b/compose/cryostat.yml @@ -53,3 +53,5 @@ volumes: external: true probes: external: true + credentials: + external: true diff --git a/smoketest.bash b/smoketest.bash index 264ce03b1..a384e0320 100755 --- a/smoketest.bash +++ b/smoketest.bash @@ -354,6 +354,15 @@ createProbeTemplateVolume() { } createProbeTemplateVolume +createCredentialVolume() { + "${container_engine}" volume create credentials + "${container_engine}" container create --name credentials_helper -v credentials:/credentials registry.access.redhat.com/ubi9/ubi-micro + if [ -d "${DIR}/credentials" ]; then + "${container_engine}" cp "${DIR}/credentials" credentials_helper:/credentials + fi +} +createCredentialVolume + setupUserHosts() { # This requires https://github.com/figiel/hosts to work. See README. truncate -s 0 "${HOSTSFILE}" diff --git a/src/main/java/io/cryostat/ConfigProperties.java b/src/main/java/io/cryostat/ConfigProperties.java index b49c3cb5c..07a1db5aa 100644 --- a/src/main/java/io/cryostat/ConfigProperties.java +++ b/src/main/java/io/cryostat/ConfigProperties.java @@ -55,6 +55,7 @@ public class ConfigProperties { public static final String PROBE_TEMPLATES_DIR = "probe-templates-dir"; public static final String SSL_TRUSTSTORE_DIR = "ssl.truststore.dir"; public static final String RULES_DIR = "rules-dir"; + public static final String CREDENTIALS_DIR = "credentials-dir"; public static final String URI_RANGE = "cryostat.target.uri-range"; diff --git a/src/main/java/io/cryostat/credentials/Credentials.java b/src/main/java/io/cryostat/credentials/Credentials.java index 2eee81ed7..984408015 100644 --- a/src/main/java/io/cryostat/credentials/Credentials.java +++ b/src/main/java/io/cryostat/credentials/Credentials.java @@ -15,21 +15,30 @@ */ package io.cryostat.credentials; +import java.io.BufferedInputStream; +import java.io.IOException; import java.net.URISyntaxException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import io.cryostat.ConfigProperties; import io.cryostat.expressions.MatchExpression; import io.cryostat.expressions.MatchExpression.TargetMatcher; import io.cryostat.targets.Target; import io.cryostat.targets.TargetConnectionManager; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.runtime.StartupEvent; import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Uni; import jakarta.annotation.security.RolesAllowed; +import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.ws.rs.DELETE; @@ -39,6 +48,7 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.UriInfo; +import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestForm; import org.jboss.resteasy.reactive.RestPath; @@ -49,9 +59,59 @@ @Path("/api/v4/credentials") public class Credentials { + @ConfigProperty(name = ConfigProperties.CREDENTIALS_DIR) + java.nio.file.Path dir; + @Inject TargetConnectionManager connectionManager; @Inject TargetMatcher targetMatcher; @Inject Logger logger; + @Inject ObjectMapper mapper; + + @Transactional + void onStart(@Observes StartupEvent evt) { + if (!checkDir()) { + return; + } + try { + Files.walk(dir) + .filter(Files::isRegularFile) + .filter(Files::isReadable) + .forEach(this::createFromFile); + } catch (IOException e) { + logger.error(e); + } + } + + private boolean checkDir() { + return Files.exists(dir) + && Files.isReadable(dir) + && Files.isExecutable(dir) + && Files.isDirectory(dir); + } + + private void createFromFile(java.nio.file.Path path) { + try (var is = new BufferedInputStream(Files.newInputStream(path))) { + var credential = mapper.readValue(is, Credential.class); + Map params = new HashMap(); + params.put("username", credential.username); + params.put("password", credential.password); + params.put("matchExpression", credential.matchExpression); + var exists = + Credential.find( + "username = :username" + + " and password = :password" + + " and matchExpression = :matchExpression", + params) + .count() + != 0; + if (exists) { + return; + } + credential.persist(); + } catch (Exception e) { + logger.error("Failed to create credentials from file", e); + } + } @POST @Blocking diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2b7be4521..45f55a90b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -52,6 +52,7 @@ cryostat.target.uri-range=PUBLIC cryostat.agent.tls.required=true conf-dir=/opt/cryostat.d +credentials-dir=${conf-dir}/credentials.d rules-dir=${conf-dir}/rules.d templates-dir=${conf-dir}/templates.d probe-templates-dir=${conf-dir}/probes.d