Skip to content

Commit

Permalink
feat(credentials): implement matchExpression-based credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewazores committed Jun 16, 2022
1 parent d13e504 commit 0cabc68
Show file tree
Hide file tree
Showing 25 changed files with 368 additions and 141 deletions.
1 change: 1 addition & 0 deletions src/main/java/io/cryostat/Cryostat.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public static void main(String[] args) throws Exception {
CompletableFuture<Void> future = new CompletableFuture<>();
client.httpServer().addShutdownListener(() -> future.complete(null));

client.credentialsManager().migrate();
client.credentialsManager().load();
client.ruleRegistry().loadRules();
client.vertx()
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/io/cryostat/MainModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
import javax.inject.Named;
import javax.inject.Singleton;
import javax.management.remote.JMXServiceURL;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

import io.cryostat.configuration.ConfigurationModule;
import io.cryostat.configuration.Variables;
Expand Down Expand Up @@ -155,4 +157,10 @@ static Path provideSavedRecordingsPath(Logger logger, Environment env) {
logger.info("Local save path for flight recordings set as {}", archivePath);
return Paths.get(archivePath);
}

@Provides
@Singleton
public static ScriptEngine provideScriptEngine() {
return new ScriptEngineManager().getEngineByName("nashorn");
}
}
11 changes: 10 additions & 1 deletion src/main/java/io/cryostat/configuration/ConfigurationModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import io.cryostat.core.sys.FileSystem;
import io.cryostat.messaging.notifications.NotificationFactory;
import io.cryostat.platform.PlatformClient;
import io.cryostat.rules.MatchExpressionEvaluator;

import com.google.gson.Gson;
import dagger.Module;
Expand All @@ -77,6 +78,7 @@ static Path provideConfigurationPath(Logger logger, Environment env) {
@Singleton
static CredentialsManager provideCredentialsManager(
@Named(CONFIGURATION_PATH) Path confDir,
MatchExpressionEvaluator matchExpressionEvaluator,
FileSystem fs,
PlatformClient platformClient,
NotificationFactory notificationFactory,
Expand All @@ -95,7 +97,14 @@ static CredentialsManager provideCredentialsManager(
PosixFilePermission.OWNER_EXECUTE)));
}
return new CredentialsManager(
credentialsDir, fs, platformClient, notificationFactory, gson, base32, logger);
credentialsDir,
matchExpressionEvaluator,
fs,
platformClient,
notificationFactory,
gson,
base32,
logger);
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down
125 changes: 103 additions & 22 deletions src/main/java/io/cryostat/configuration/CredentialsManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,31 +37,38 @@
*/
package io.cryostat.configuration;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
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.Set;
import java.util.stream.Collectors;

import javax.script.ScriptException;

import io.cryostat.core.log.Logger;
import io.cryostat.core.net.Credentials;
import io.cryostat.core.sys.FileSystem;
import io.cryostat.messaging.notifications.NotificationFactory;
import io.cryostat.platform.PlatformClient;
import io.cryostat.platform.ServiceRef;
import io.cryostat.rules.MatchExpressionEvaluator;

import com.google.gson.Gson;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.lang3.StringUtils;

public class CredentialsManager {

private final Path credentialsDir;
private final MatchExpressionEvaluator matchExpressionEvaluator;
private final FileSystem fs;
private final PlatformClient platformClient;
private final Gson gson;
Expand All @@ -72,13 +79,15 @@ public class CredentialsManager {

CredentialsManager(
Path credentialsDir,
MatchExpressionEvaluator matchExpressionEvaluator,
FileSystem fs,
PlatformClient platformClient,
NotificationFactory notificationFactory,
Gson gson,
Base32 base32,
Logger logger) {
this.credentialsDir = credentialsDir;
this.matchExpressionEvaluator = matchExpressionEvaluator;
this.fs = fs;
this.platformClient = platformClient;
this.gson = gson;
Expand All @@ -87,6 +96,34 @@ public class CredentialsManager {
this.credentialsMap = new HashMap<>();
}

public void migrate() throws Exception {
for (String file : this.fs.listDirectoryChildren(credentialsDir)) {
BufferedReader reader;
try {
Path path = credentialsDir.resolve(file);
reader = fs.readFile(path);
TargetSpecificStoredCredentials targetSpecificCredential =
gson.fromJson(reader, TargetSpecificStoredCredentials.class);

String targetId = targetSpecificCredential.getTargetId();
if (StringUtils.isNotBlank(targetId)) {
addCredentials(
targetIdToMatchExpression(targetSpecificCredential.getTargetId()),
targetSpecificCredential.getCredentials());
fs.deleteIfExists(path);
logger.info("Migrated {}", path);
}
} catch (IOException e) {
logger.warn(e);
continue;
}
}
}

public static String targetIdToMatchExpression(String targetId) {
return String.format("target.connectUrl == \"%s\"", targetId);
}

public void load() throws IOException {
this.fs.listDirectoryChildren(credentialsDir).stream()
.peek(n -> logger.trace("Credentials file: {}", n))
Expand All @@ -102,16 +139,17 @@ public void load() throws IOException {
})
.filter(Objects::nonNull)
.map(reader -> gson.fromJson(reader, StoredCredentials.class))
.forEach(sc -> credentialsMap.put(sc.getTargetId(), sc.getCredentials()));
.forEach(sc -> credentialsMap.put(sc.getMatchExpression(), sc.getCredentials()));
}

public boolean addCredentials(String targetId, Credentials credentials) throws IOException {
boolean replaced = credentialsMap.containsKey(targetId);
credentialsMap.put(targetId, credentials);
Path destination = getPersistedPath(targetId);
public boolean addCredentials(String matchExpression, Credentials credentials)
throws IOException {
boolean replaced = credentialsMap.containsKey(matchExpression);
credentialsMap.put(matchExpression, credentials);
Path destination = getPersistedPath(matchExpression);
fs.writeString(
destination,
gson.toJson(new StoredCredentials(targetId, credentials)),
gson.toJson(new StoredCredentials(matchExpression, credentials)),
StandardOpenOption.WRITE,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
Expand All @@ -121,47 +159,90 @@ public boolean addCredentials(String targetId, Credentials credentials) throws I
return replaced;
}

public boolean removeCredentials(String targetId) throws IOException {
Credentials deleted = this.credentialsMap.remove(targetId);
fs.deleteIfExists(getPersistedPath(targetId));
public boolean removeCredentials(String matchExpression) throws IOException {
Credentials deleted = this.credentialsMap.remove(matchExpression);
fs.deleteIfExists(getPersistedPath(matchExpression));
return deleted != null;
}

public Credentials getCredentials(String targetId) {
return this.credentialsMap.get(targetId);
for (ServiceRef service : this.platformClient.listDiscoverableServices()) {
if (Objects.equals(targetId, service.getServiceUri().toString())) {
return getCredentials(service);
}
}
return null;
}

public Credentials getCredentials(ServiceRef serviceRef) {
return getCredentials(serviceRef.getServiceUri().toString());
for (Map.Entry<String, Credentials> entry : credentialsMap.entrySet()) {
try {
if (matchExpressionEvaluator.applies(entry.getKey(), serviceRef)) {
return entry.getValue();
}
} catch (ScriptException e) {
logger.error(e);
continue;
}
}
return null;
}

public Collection<ServiceRef> getServiceRefsWithCredentials() {
List<ServiceRef> result = new ArrayList<>();
for (ServiceRef service : this.platformClient.listDiscoverableServices()) {
Credentials credentials = getCredentials(service);
if (credentials != null) {
result.add(service);
}
}
return result;
}

public List<ServiceRef> getCredentialKeys() {
return this.platformClient.listDiscoverableServices().stream()
.filter(target -> credentialsMap.containsKey(target.getServiceUri().toString()))
.collect(Collectors.toList());
public Collection<String> getMatchExpressions() {
return credentialsMap.keySet();
}

private Path getPersistedPath(String targetId) {
private Path getPersistedPath(String matchExpression) {
return credentialsDir.resolve(
String.format(
"%s.json",
base32.encodeAsString(targetId.getBytes(StandardCharsets.UTF_8))));
base32.encodeAsString(matchExpression.getBytes(StandardCharsets.UTF_8))));
}

static class StoredCredentials {
private final String matchExpression;
private final Credentials credentials;

StoredCredentials(String matchExpression, Credentials credentials) {
this.matchExpression = matchExpression;
this.credentials = credentials;
}

String getMatchExpression() {
return this.matchExpression;
}

Credentials getCredentials() {
return this.credentials;
}
}

public static class StoredCredentials {
@Deprecated(since = "2.2", forRemoval = true)
static class TargetSpecificStoredCredentials {
private final String targetId;
private final Credentials credentials;

StoredCredentials(String targetId, Credentials credentials) {
TargetSpecificStoredCredentials(String targetId, Credentials credentials) {
this.targetId = targetId;
this.credentials = credentials;
}

public String getTargetId() {
String getTargetId() {
return this.targetId;
}

public Credentials getCredentials() {
Credentials getCredentials() {
return this.credentials;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,10 @@
*/
package io.cryostat.net.web.http;

import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.rmi.ConnectIOException;
import java.util.Base64;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -170,35 +168,16 @@ private boolean isAuthFailure(ExecutionException e) {

private void handleConnectionException(RoutingContext ctx, ConnectionException e) {
Throwable cause = e.getCause();
try {
if (cause instanceof SecurityException || cause instanceof SaslException) {
ctx.response().putHeader(JMX_AUTHENTICATE_HEADER, "Basic");
throw new HttpException(427, "JMX Authentication Failure", e);
}
Throwable rootCause = ExceptionUtils.getRootCause(e);
if (rootCause instanceof ConnectIOException) {
throw new HttpException(502, "Target SSL Untrusted", e);
}
if (rootCause instanceof UnknownHostException) {
throw new HttpException(404, "Target Not Found", e);
}
} finally {
this.removeCredentialsIfPresent(ctx);
if (cause instanceof SecurityException || cause instanceof SaslException) {
ctx.response().putHeader(JMX_AUTHENTICATE_HEADER, "Basic");
throw new HttpException(427, "JMX Authentication Failure", e);
}
Throwable rootCause = ExceptionUtils.getRootCause(e);
if (rootCause instanceof ConnectIOException) {
throw new HttpException(502, "Target SSL Untrusted", e);
}
if (rootCause instanceof UnknownHostException) {
throw new HttpException(404, "Target Not Found", e);
}
}

private void removeCredentialsIfPresent(RoutingContext ctx) {
Optional<String> targetId = Optional.ofNullable(ctx.pathParam("targetId"));

targetId.ifPresent(
id -> {
if (credentialsManager.getCredentials(id) != null) {
try {
credentialsManager.removeCredentials(id);
} catch (IOException ioe) {
logger.error(ioe);
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
class AuthPostHandler extends AbstractAuthenticatedRequestHandler {

@Inject
AuthPostHandler(AuthManager auth, CredentialsManager credentialsManager, Logger logger) {
AuthPostHandler(
AuthManager auth, CredentialsManager credentialsManager, Logger logger) {
super(auth, credentialsManager, logger);
}

Expand Down
Loading

0 comments on commit 0cabc68

Please sign in to comment.