diff --git a/pom.xml b/pom.xml
index 3b7740c869..868a9456d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,7 +67,7 @@
1.7
2.8.0
4.5.13
- 5.3.0
+ 5.4.1
3.9.7
1.7.30
2.8.6
@@ -201,6 +201,18 @@
${com.github.spotbugs.version}
provided
+
+ io.fabric8
+ kubernetes-server-mock
+ ${io.fabric8.client.version}
+ test
+
+
+ io.fabric8
+ openshift-server-mock
+ ${io.fabric8.client.version}
+ test
+
diff --git a/src/main/java/io/cryostat/messaging/MessagingServer.java b/src/main/java/io/cryostat/messaging/MessagingServer.java
index b8ac612108..799e985ba2 100644
--- a/src/main/java/io/cryostat/messaging/MessagingServer.java
+++ b/src/main/java/io/cryostat/messaging/MessagingServer.java
@@ -56,6 +56,7 @@
import io.cryostat.messaging.notifications.NotificationFactory;
import io.cryostat.net.AuthManager;
import io.cryostat.net.HttpServer;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import com.google.gson.Gson;
@@ -147,7 +148,12 @@ public void start() throws SocketException, UnknownHostException {
authManager
.doAuthenticated(
sws::subProtocol,
- authManager::validateWebSocketSubProtocol)
+ p ->
+ authManager
+ .validateWebSocketSubProtocol(
+ p,
+ ResourceAction
+ .READ_ALL))
.onSuccess(
() -> {
logger.info(
diff --git a/src/main/java/io/cryostat/net/AuthManager.java b/src/main/java/io/cryostat/net/AuthManager.java
index f173fce192..7883ba1ca7 100644
--- a/src/main/java/io/cryostat/net/AuthManager.java
+++ b/src/main/java/io/cryostat/net/AuthManager.java
@@ -37,18 +37,24 @@
*/
package io.cryostat.net;
+import java.util.Set;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.function.Supplier;
+import io.cryostat.net.security.ResourceAction;
+
public interface AuthManager {
AuthenticationScheme getScheme();
- Future validateToken(Supplier tokenProvider);
+ Future validateToken(
+ Supplier tokenProvider, Set resourceActions);
- Future validateHttpHeader(Supplier headerProvider);
+ Future validateHttpHeader(
+ Supplier headerProvider, Set resourceActions);
- Future validateWebSocketSubProtocol(Supplier subProtocolProvider);
+ Future validateWebSocketSubProtocol(
+ Supplier subProtocolProvider, Set resourceActions);
AuthenticatedAction doAuthenticated(
Supplier provider, Function, Future> validator);
diff --git a/src/main/java/io/cryostat/net/BasicAuthManager.java b/src/main/java/io/cryostat/net/BasicAuthManager.java
index 7ef6cb45f8..5b83f55b94 100644
--- a/src/main/java/io/cryostat/net/BasicAuthManager.java
+++ b/src/main/java/io/cryostat/net/BasicAuthManager.java
@@ -44,6 +44,7 @@
import java.util.Base64;
import java.util.Objects;
import java.util.Properties;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Supplier;
@@ -52,6 +53,7 @@
import io.cryostat.core.log.Logger;
import io.cryostat.core.sys.FileSystem;
+import io.cryostat.net.security.ResourceAction;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
@@ -77,7 +79,8 @@ public AuthenticationScheme getScheme() {
}
@Override
- public Future validateToken(Supplier tokenProvider) {
+ public Future validateToken(
+ Supplier tokenProvider, Set resourceActions) {
if (!configLoaded) {
this.loadConfig();
}
@@ -90,12 +93,21 @@ public Future validateToken(Supplier tokenProvider) {
String user = matcher.group(1);
String pass = matcher.group(2);
String passHashHex = DigestUtils.sha256Hex(pass);
- return CompletableFuture.completedFuture(
- Objects.equals(users.getProperty(user), passHashHex));
+ boolean granted = Objects.equals(users.getProperty(user), passHashHex);
+ // FIXME actually implement this
+ resourceActions.forEach(
+ action ->
+ logger.trace(
+ "user {} granted {} {}",
+ user,
+ action.getVerb(),
+ action.getResource()));
+ return CompletableFuture.completedFuture(granted);
}
@Override
- public Future validateHttpHeader(Supplier headerProvider) {
+ public Future validateHttpHeader(
+ Supplier headerProvider, Set resourceActions) {
String authorization = headerProvider.get();
if (StringUtils.isBlank(authorization)) {
return CompletableFuture.completedFuture(false);
@@ -109,14 +121,15 @@ public Future validateHttpHeader(Supplier headerProvider) {
try {
String decoded =
new String(Base64.getUrlDecoder().decode(b64), StandardCharsets.UTF_8).trim();
- return validateToken(() -> decoded);
+ return validateToken(() -> decoded, resourceActions);
} catch (IllegalArgumentException e) {
return CompletableFuture.completedFuture(false);
}
}
@Override
- public Future validateWebSocketSubProtocol(Supplier subProtocolProvider) {
+ public Future validateWebSocketSubProtocol(
+ Supplier subProtocolProvider, Set resourceActions) {
String subprotocol = subProtocolProvider.get();
if (StringUtils.isBlank(subprotocol)) {
return CompletableFuture.completedFuture(false);
@@ -132,7 +145,7 @@ public Future validateWebSocketSubProtocol(Supplier subProtocol
try {
String decoded =
new String(Base64.getUrlDecoder().decode(b64), StandardCharsets.UTF_8).trim();
- return validateToken(() -> decoded);
+ return validateToken(() -> decoded, resourceActions);
} catch (IllegalArgumentException e) {
return CompletableFuture.completedFuture(false);
}
diff --git a/src/main/java/io/cryostat/net/NetworkModule.java b/src/main/java/io/cryostat/net/NetworkModule.java
index 3f8a3b7abd..48ed6f9db0 100644
--- a/src/main/java/io/cryostat/net/NetworkModule.java
+++ b/src/main/java/io/cryostat/net/NetworkModule.java
@@ -55,6 +55,8 @@
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
+import io.fabric8.openshift.client.DefaultOpenShiftClient;
+import io.fabric8.openshift.client.OpenShiftConfigBuilder;
import io.vertx.core.Vertx;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
@@ -159,7 +161,12 @@ static BasicAuthManager provideBasicAuthManager(Logger logger, FileSystem fs) {
@Provides
@Singleton
static OpenShiftAuthManager provideOpenShiftAuthManager(Logger logger, FileSystem fs) {
- return new OpenShiftAuthManager(logger, fs);
+ return new OpenShiftAuthManager(
+ logger,
+ fs,
+ token ->
+ new DefaultOpenShiftClient(
+ new OpenShiftConfigBuilder().withOauthToken(token).build()));
}
@Binds
diff --git a/src/main/java/io/cryostat/net/NoopAuthManager.java b/src/main/java/io/cryostat/net/NoopAuthManager.java
index ce7e3f3870..7295ffea2f 100644
--- a/src/main/java/io/cryostat/net/NoopAuthManager.java
+++ b/src/main/java/io/cryostat/net/NoopAuthManager.java
@@ -37,11 +37,13 @@
*/
package io.cryostat.net;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import io.cryostat.core.log.Logger;
+import io.cryostat.net.security.ResourceAction;
public class NoopAuthManager extends AbstractAuthManager {
@@ -56,17 +58,20 @@ public AuthenticationScheme getScheme() {
}
@Override
- public Future validateToken(Supplier tokenProvider) {
+ public Future validateToken(
+ Supplier tokenProvider, Set resourceActions) {
return CompletableFuture.completedFuture(true);
}
@Override
- public Future validateHttpHeader(Supplier headerProvider) {
+ public Future validateHttpHeader(
+ Supplier headerProvider, Set resourceActions) {
return CompletableFuture.completedFuture(true);
}
@Override
- public Future validateWebSocketSubProtocol(Supplier subProtocolProvider) {
+ public Future validateWebSocketSubProtocol(
+ Supplier subProtocolProvider, Set resourceActions) {
return CompletableFuture.completedFuture(true);
}
}
diff --git a/src/main/java/io/cryostat/net/OpenShiftAuthManager.java b/src/main/java/io/cryostat/net/OpenShiftAuthManager.java
index 5e9d50daef..72033b99a0 100644
--- a/src/main/java/io/cryostat/net/OpenShiftAuthManager.java
+++ b/src/main/java/io/cryostat/net/OpenShiftAuthManager.java
@@ -41,22 +41,33 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.Base64;
+import java.util.List;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import io.cryostat.core.log.Logger;
import io.cryostat.core.sys.FileSystem;
+import io.cryostat.net.security.ResourceAction;
+import io.cryostat.net.security.ResourceType;
+import io.cryostat.net.security.ResourceVerb;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import io.fabric8.kubernetes.api.model.authentication.TokenReview;
+import io.fabric8.kubernetes.api.model.authentication.TokenReviewBuilder;
+import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReview;
+import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReviewBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClientException;
-import io.fabric8.openshift.client.DefaultOpenShiftClient;
import io.fabric8.openshift.client.OpenShiftClient;
-import io.fabric8.openshift.client.OpenShiftConfigBuilder;
import jdk.jfr.Category;
import jdk.jfr.Event;
import jdk.jfr.Label;
@@ -65,11 +76,16 @@
public class OpenShiftAuthManager extends AbstractAuthManager {
+ private static final Set PERMISSION_NOT_REQUIRED = Set.of("PERMISSION_NOT_REQUIRED");
+
private final FileSystem fs;
+ private final Function clientProvider;
- public OpenShiftAuthManager(Logger logger, FileSystem fs) {
+ OpenShiftAuthManager(
+ Logger logger, FileSystem fs, Function clientProvider) {
super(logger);
this.fs = fs;
+ this.clientProvider = clientProvider;
}
@Override
@@ -78,65 +94,113 @@ public AuthenticationScheme getScheme() {
}
@Override
- public Future validateToken(Supplier tokenProvider) {
+ public Future validateToken(
+ Supplier tokenProvider, Set resourceActions) {
String token = tokenProvider.get();
if (StringUtils.isBlank(token)) {
return CompletableFuture.completedFuture(false);
}
- return CompletableFuture.supplyAsync(
- () -> {
- AuthRequest evt = new AuthRequest();
- evt.begin();
+ if (resourceActions.isEmpty()) {
+ return reviewToken(token);
+ }
+
+ try (OpenShiftClient client = clientProvider.apply(token)) {
+ String namespace = getNamespace();
+ List> results =
+ resourceActions
+ .parallelStream()
+ .flatMap(
+ resourceAction ->
+ validateAction(client, namespace, resourceAction))
+ .collect(Collectors.toList());
- try (OpenShiftClient authClient =
- new DefaultOpenShiftClient(
- new OpenShiftConfigBuilder()
- .withOauthToken(token)
- .build())) {
- // only an authenticated user should be allowed to list routes
- // in the namespace
- // TODO find a better way to authenticate tokens
- authClient.routes().inNamespace(getNamespace()).list();
+ CompletableFuture.allOf(results.toArray(new CompletableFuture[0]))
+ .get(15, TimeUnit.SECONDS);
+ // if we get here then all requests were successful and granted, otherwise an exception
+ // was thrown on allOf().get() above
+ return CompletableFuture.completedFuture(true);
+ } catch (KubernetesClientException | ExecutionException e) {
+ logger.info(e);
+ return CompletableFuture.failedFuture(e);
+ } catch (Exception e) {
+ logger.error(e);
+ return CompletableFuture.failedFuture(e);
+ }
+ }
+
+ Future reviewToken(String token) {
+ try (OpenShiftClient client = clientProvider.apply(getServiceAccountToken())) {
+ TokenReview review =
+ new TokenReviewBuilder().withNewSpec().withToken(token).endSpec().build();
+ review = client.tokenReviews().create(review);
+ Boolean authenticated = review.getStatus().getAuthenticated();
+ return CompletableFuture.completedFuture(authenticated != null && authenticated);
+ } catch (KubernetesClientException e) {
+ logger.info(e);
+ return CompletableFuture.failedFuture(e);
+ } catch (Exception e) {
+ logger.error(e);
+ return CompletableFuture.failedFuture(e);
+ }
+ }
+ private Stream> validateAction(
+ OpenShiftClient client, String namespace, ResourceAction resourceAction) {
+ Set resources = map(resourceAction.getResource());
+ if (PERMISSION_NOT_REQUIRED.equals(resources) || resources.isEmpty()) {
+ return Stream.of(CompletableFuture.completedFuture(null));
+ }
+ String group = "operator.cryostat.io";
+ String verb = map(resourceAction.getVerb());
+ return resources
+ .parallelStream()
+ .map(
+ resource -> {
+ AuthRequest evt = new AuthRequest();
+ evt.begin();
+ try {
+ SelfSubjectAccessReview accessReview =
+ new SelfSubjectAccessReviewBuilder()
+ .withNewSpec()
+ .withNewResourceAttributes()
+ .withNamespace(namespace)
+ .withGroup(group)
+ .withResource(resource)
+ .withVerb(verb)
+ .endResourceAttributes()
+ .endSpec()
+ .build();
+ accessReview =
+ client.authorization()
+ .v1()
+ .selfSubjectAccessReview()
+ .create(accessReview);
evt.setRequestSuccessful(true);
- return true;
- } catch (KubernetesClientException e) {
- logger.info(e);
+ if (!accessReview.getStatus().getAllowed()) {
+ return CompletableFuture.failedFuture(
+ new PermissionDeniedException(
+ namespace,
+ group,
+ resource,
+ verb,
+ accessReview.getStatus().getReason()));
+ } else {
+ return CompletableFuture.completedFuture(null);
+ }
} catch (Exception e) {
- logger.error(e);
+ return CompletableFuture.failedFuture(e);
} finally {
if (evt.shouldCommit()) {
evt.end();
evt.commit();
}
}
-
- return false;
- })
- .orTimeout(15, TimeUnit.SECONDS);
- }
-
- @Name("io.cryostat.net.OpenShiftAuthManager.AuthRequest")
- @Label("AuthRequest")
- @Category("Cryostat")
- @SuppressFBWarnings(
- value = "URF_UNREAD_FIELD",
- justification = "Event fields are recorded with JFR instead of accessed directly")
- public static class AuthRequest extends Event {
-
- boolean requestSuccessful;
-
- public AuthRequest() {
- this.requestSuccessful = false;
- }
-
- public void setRequestSuccessful(boolean requestSuccessful) {
- this.requestSuccessful = requestSuccessful;
- }
+ });
}
@Override
- public Future validateHttpHeader(Supplier headerProvider) {
+ public Future validateHttpHeader(
+ Supplier headerProvider, Set resourceActions) {
String authorization = headerProvider.get();
if (StringUtils.isBlank(authorization)) {
return CompletableFuture.completedFuture(false);
@@ -146,11 +210,12 @@ public Future validateHttpHeader(Supplier headerProvider) {
if (!matcher.matches()) {
return CompletableFuture.completedFuture(false);
}
- return validateToken(() -> matcher.group(1));
+ return validateToken(() -> matcher.group(1), resourceActions);
}
@Override
- public Future validateWebSocketSubProtocol(Supplier subProtocolProvider) {
+ public Future validateWebSocketSubProtocol(
+ Supplier subProtocolProvider, Set resourceActions) {
String subprotocol = subProtocolProvider.get();
if (StringUtils.isBlank(subprotocol)) {
return CompletableFuture.completedFuture(false);
@@ -167,7 +232,7 @@ public Future validateWebSocketSubProtocol(Supplier subProtocol
try {
String decoded =
new String(Base64.getUrlDecoder().decode(b64), StandardCharsets.UTF_8).trim();
- return validateToken(() -> decoded);
+ return validateToken(() -> decoded, resourceActions);
} catch (IllegalArgumentException e) {
return CompletableFuture.completedFuture(false);
}
@@ -183,4 +248,98 @@ private String getNamespace() throws IOException {
.findFirst()
.get();
}
+
+ @SuppressFBWarnings(
+ value = "DMI_HARDCODED_ABSOLUTE_FILENAME",
+ justification = "Kubernetes serviceaccount file path is well-known and absolute")
+ private String getServiceAccountToken() throws IOException {
+ return fs.readFile(Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH))
+ .lines()
+ .filter(StringUtils::isNotBlank)
+ .findFirst()
+ .get();
+ }
+
+ private static Set map(ResourceType resource) {
+ switch (resource) {
+ case TARGET:
+ return Set.of("flightrecorders");
+ case RECORDING:
+ return Set.of("recordings");
+ case CERTIFICATE:
+ return Set.of("deployments", "pods", "cryostats");
+ case CREDENTIALS:
+ return Set.of("cryostats");
+ case TEMPLATE:
+ case REPORT:
+ case RULE:
+ default:
+ return PERMISSION_NOT_REQUIRED;
+ }
+ }
+
+ private static String map(ResourceVerb verb) {
+ switch (verb) {
+ case CREATE:
+ return "create";
+ case READ:
+ return "get";
+ case UPDATE:
+ return "patch";
+ case DELETE:
+ return "delete";
+ default:
+ throw new IllegalArgumentException(
+ String.format("Unknown resource verb \"%s\"", verb));
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static class PermissionDeniedException extends Exception {
+ private final String namespace;
+ private final String resource;
+ private final String verb;
+
+ public PermissionDeniedException(
+ String namespace, String group, String resource, String verb, String reason) {
+ super(
+ String.format(
+ "Requesting client in namespace \"%s\" cannot %s %s.%s: %s",
+ namespace, verb, resource, group, reason));
+ this.namespace = namespace;
+ this.resource = resource;
+ this.verb = verb;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String getResourceType() {
+ return resource;
+ }
+
+ public String getVerb() {
+ return verb;
+ }
+ }
+
+ @Name("io.cryostat.net.OpenShiftAuthManager.AuthRequest")
+ @Label("AuthRequest")
+ @Category("Cryostat")
+ @SuppressFBWarnings(
+ value = "URF_UNREAD_FIELD",
+ justification = "Event fields are recorded with JFR instead of accessed directly")
+ public static class AuthRequest extends Event {
+
+ boolean requestSuccessful;
+
+ public AuthRequest() {
+ this.requestSuccessful = false;
+ }
+
+ public void setRequestSuccessful(boolean requestSuccessful) {
+ this.requestSuccessful = requestSuccessful;
+ }
+ }
}
diff --git a/src/main/java/io/cryostat/net/security/ResourceAction.java b/src/main/java/io/cryostat/net/security/ResourceAction.java
new file mode 100644
index 0000000000..427c1fbce2
--- /dev/null
+++ b/src/main/java/io/cryostat/net/security/ResourceAction.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright The Cryostat Authors
+ *
+ * The Universal Permissive License (UPL), Version 1.0
+ *
+ * Subject to the condition set forth below, permission is hereby granted to any
+ * person obtaining a copy of this software, associated documentation and/or data
+ * (collectively the "Software"), free of charge and under any and all copyright
+ * rights in the Software, and any and all patent rights owned or freely
+ * licensable by each licensor hereunder covering either (i) the unmodified
+ * Software as contributed to or provided by such licensor, or (ii) the Larger
+ * Works (as defined below), to deal in both
+ *
+ * (a) the Software, and
+ * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
+ * one is included with the Software (each a "Larger Work" to which the Software
+ * is contributed by such licensors),
+ *
+ * without restriction, including without limitation the rights to copy, create
+ * derivative works of, display, perform, and distribute the Software and make,
+ * use, sell, offer for sale, import, export, have made, and have sold the
+ * Software and the Larger Work(s), and to sublicense the foregoing rights on
+ * either these or other terms.
+ *
+ * This license is subject to the following condition:
+ * The above copyright notice and either this complete permission notice or at
+ * a minimum a reference to the UPL must be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package io.cryostat.net.security;
+
+import static io.cryostat.net.security.ResourceType.CERTIFICATE;
+import static io.cryostat.net.security.ResourceType.CREDENTIALS;
+import static io.cryostat.net.security.ResourceType.RECORDING;
+import static io.cryostat.net.security.ResourceType.REPORT;
+import static io.cryostat.net.security.ResourceType.RULE;
+import static io.cryostat.net.security.ResourceType.TARGET;
+import static io.cryostat.net.security.ResourceType.TEMPLATE;
+import static io.cryostat.net.security.ResourceVerb.CREATE;
+import static io.cryostat.net.security.ResourceVerb.DELETE;
+import static io.cryostat.net.security.ResourceVerb.READ;
+import static io.cryostat.net.security.ResourceVerb.UPDATE;
+
+import java.util.EnumSet;
+import java.util.stream.Collectors;
+
+/**
+ * API action types(), at a high level, which are mapped to underlying platform permissions and
+ * checked by AuthManagers.
+ */
+public enum ResourceAction {
+ CREATE_TARGET(CREATE, TARGET),
+ READ_TARGET(READ, TARGET),
+ UPDATE_TARGET(UPDATE, TARGET),
+ DELETE_TARGET(DELETE, TARGET),
+
+ CREATE_RECORDING(CREATE, RECORDING),
+ READ_RECORDING(READ, RECORDING),
+ UPDATE_RECORDING(UPDATE, RECORDING),
+ DELETE_RECORDING(DELETE, RECORDING),
+
+ CREATE_TEMPLATE(CREATE, TEMPLATE),
+ READ_TEMPLATE(READ, TEMPLATE),
+ UPDATE_TEMPLATE(UPDATE, TEMPLATE),
+ DELETE_TEMPLATE(DELETE, TEMPLATE),
+
+ CREATE_REPORT(CREATE, REPORT),
+ READ_REPORT(READ, REPORT),
+ UPDATE_REPORT(UPDATE, REPORT),
+ DELETE_REPORT(DELETE, REPORT),
+
+ CREATE_CREDENTIALS(CREATE, CREDENTIALS),
+ READ_CREDENTIALS(READ, CREDENTIALS),
+ UPDATE_CREDENTIALS(UPDATE, CREDENTIALS),
+ DELETE_CREDENTIALS(DELETE, CREDENTIALS),
+
+ CREATE_RULE(CREATE, RULE),
+ READ_RULE(READ, RULE),
+ UPDATE_RULE(UPDATE, RULE),
+ DELETE_RULE(DELETE, RULE),
+
+ CREATE_CERTIFICATE(CREATE, CERTIFICATE),
+ READ_CERTIFICATE(READ, CERTIFICATE),
+ UPDATE_CERTIFICATE(UPDATE, CERTIFICATE),
+ DELETE_CERTIFICATE(DELETE, CERTIFICATE),
+ ;
+
+ public static final EnumSet ALL = EnumSet.allOf(ResourceAction.class);
+ public static final EnumSet NONE = EnumSet.noneOf(ResourceAction.class);
+ public static final EnumSet READ_ALL =
+ EnumSet.copyOf(
+ ALL.stream()
+ .filter(p -> ResourceVerb.READ == p.verb)
+ .collect(Collectors.toSet()));
+ public static final EnumSet WRITE_ALL = EnumSet.complementOf(READ_ALL);
+
+ private final ResourceVerb verb;
+ private final ResourceType resource;
+
+ ResourceAction(ResourceVerb verb, ResourceType resource) {
+ this.verb = verb;
+ this.resource = resource;
+ }
+
+ public ResourceVerb getVerb() {
+ return verb;
+ }
+
+ public ResourceType getResource() {
+ return resource;
+ }
+}
diff --git a/src/main/java/io/cryostat/net/security/ResourceType.java b/src/main/java/io/cryostat/net/security/ResourceType.java
new file mode 100644
index 0000000000..3d53bbbd59
--- /dev/null
+++ b/src/main/java/io/cryostat/net/security/ResourceType.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright The Cryostat Authors
+ *
+ * The Universal Permissive License (UPL), Version 1.0
+ *
+ * Subject to the condition set forth below, permission is hereby granted to any
+ * person obtaining a copy of this software, associated documentation and/or data
+ * (collectively the "Software"), free of charge and under any and all copyright
+ * rights in the Software, and any and all patent rights owned or freely
+ * licensable by each licensor hereunder covering either (i) the unmodified
+ * Software as contributed to or provided by such licensor, or (ii) the Larger
+ * Works (as defined below), to deal in both
+ *
+ * (a) the Software, and
+ * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
+ * one is included with the Software (each a "Larger Work" to which the Software
+ * is contributed by such licensors),
+ *
+ * without restriction, including without limitation the rights to copy, create
+ * derivative works of, display, perform, and distribute the Software and make,
+ * use, sell, offer for sale, import, export, have made, and have sold the
+ * Software and the Larger Work(s), and to sublicense the foregoing rights on
+ * either these or other terms.
+ *
+ * This license is subject to the following condition:
+ * The above copyright notice and either this complete permission notice or at
+ * a minimum a reference to the UPL must be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package io.cryostat.net.security;
+
+public enum ResourceType {
+ TARGET,
+ RECORDING,
+ TEMPLATE,
+ REPORT,
+ CREDENTIALS,
+ RULE,
+ CERTIFICATE,
+ ;
+}
diff --git a/src/main/java/io/cryostat/net/security/ResourceVerb.java b/src/main/java/io/cryostat/net/security/ResourceVerb.java
new file mode 100644
index 0000000000..4d20d555ae
--- /dev/null
+++ b/src/main/java/io/cryostat/net/security/ResourceVerb.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright The Cryostat Authors
+ *
+ * The Universal Permissive License (UPL), Version 1.0
+ *
+ * Subject to the condition set forth below, permission is hereby granted to any
+ * person obtaining a copy of this software, associated documentation and/or data
+ * (collectively the "Software"), free of charge and under any and all copyright
+ * rights in the Software, and any and all patent rights owned or freely
+ * licensable by each licensor hereunder covering either (i) the unmodified
+ * Software as contributed to or provided by such licensor, or (ii) the Larger
+ * Works (as defined below), to deal in both
+ *
+ * (a) the Software, and
+ * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
+ * one is included with the Software (each a "Larger Work" to which the Software
+ * is contributed by such licensors),
+ *
+ * without restriction, including without limitation the rights to copy, create
+ * derivative works of, display, perform, and distribute the Software and make,
+ * use, sell, offer for sale, import, export, have made, and have sold the
+ * Software and the Larger Work(s), and to sublicense the foregoing rights on
+ * either these or other terms.
+ *
+ * This license is subject to the following condition:
+ * The above copyright notice and either this complete permission notice or at
+ * a minimum a reference to the UPL must be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package io.cryostat.net.security;
+
+public enum ResourceVerb {
+ CREATE,
+ READ,
+ UPDATE,
+ DELETE,
+ ;
+}
diff --git a/src/main/java/io/cryostat/net/web/http/AbstractAuthenticatedRequestHandler.java b/src/main/java/io/cryostat/net/web/http/AbstractAuthenticatedRequestHandler.java
index 1a9c8443a3..3cbbefc1c2 100644
--- a/src/main/java/io/cryostat/net/web/http/AbstractAuthenticatedRequestHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/AbstractAuthenticatedRequestHandler.java
@@ -41,6 +41,7 @@
import java.nio.charset.StandardCharsets;
import java.rmi.ConnectIOException;
import java.util.Base64;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -52,7 +53,9 @@
import io.cryostat.core.net.Credentials;
import io.cryostat.net.AuthManager;
import io.cryostat.net.ConnectionDescriptor;
+import io.cryostat.net.OpenShiftAuthManager.PermissionDeniedException;
+import io.fabric8.kubernetes.client.KubernetesClientException;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.RoutingContext;
@@ -77,12 +80,21 @@ protected AbstractAuthenticatedRequestHandler(AuthManager auth) {
@Override
public void handle(RoutingContext ctx) {
try {
- if (!validateRequestAuthorization(ctx.request()).get()) {
- throw new HttpStatusException(401);
+ boolean permissionGranted = validateRequestAuthorization(ctx.request()).get();
+ if (!permissionGranted) {
+ // expected to go into catch clause below
+ throw new HttpStatusException(401, "HTTP Authorization Failure");
}
// set Content-Type: text/plain by default. Handler implementations may replace this.
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, HttpMimeType.PLAINTEXT.mime());
handleAuthenticated(ctx);
+ } catch (ExecutionException ee) {
+ Throwable cause = ee.getCause();
+ if (cause instanceof PermissionDeniedException
+ || cause instanceof KubernetesClientException) {
+ throw new HttpStatusException(401, "HTTP Authorization Failure", ee);
+ }
+ throw new HttpStatusException(500, ee.getMessage(), ee);
} catch (HttpStatusException e) {
throw e;
} catch (ConnectionException e) {
@@ -105,7 +117,8 @@ public void handle(RoutingContext ctx) {
}
protected Future validateRequestAuthorization(HttpServerRequest req) throws Exception {
- return auth.validateHttpHeader(() -> req.getHeader(HttpHeaders.AUTHORIZATION));
+ return auth.validateHttpHeader(
+ () -> req.getHeader(HttpHeaders.AUTHORIZATION), resourceActions());
}
protected ConnectionDescriptor getConnectionDescriptorFromContext(RoutingContext ctx) {
diff --git a/src/main/java/io/cryostat/net/web/http/RequestHandler.java b/src/main/java/io/cryostat/net/web/http/RequestHandler.java
index 2b68058d83..3eb32b175d 100644
--- a/src/main/java/io/cryostat/net/web/http/RequestHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/RequestHandler.java
@@ -37,6 +37,9 @@
*/
package io.cryostat.net.web.http;
+import java.util.Set;
+
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.api.ApiVersion;
import io.vertx.core.Handler;
@@ -68,6 +71,8 @@ default String basePath() {
HttpMethod httpMethod();
+ Set resourceActions();
+
default boolean isAvailable() {
return true;
}
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/AuthPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/AuthPostHandler.java
index 59c5e213b7..50fd415645 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/AuthPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/AuthPostHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -68,6 +71,11 @@ public String path() {
return basePath() + "auth";
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
// This handler is not async, but it's simple enough that it doesn't need
// to be run in a seperate worker thread.
@Override
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/GrafanaDashboardUrlGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/GrafanaDashboardUrlGetHandler.java
index 0d99e8a2f7..850e4b368c 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/GrafanaDashboardUrlGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/GrafanaDashboardUrlGetHandler.java
@@ -38,10 +38,12 @@
package io.cryostat.net.web.http.api.v1;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
import io.cryostat.core.sys.Environment;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.RequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -80,6 +82,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
// This handler is not async, but it's simple enough that it doesn't need
// to be run in a seperate worker thread.
@Override
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/GrafanaDatasourceUrlGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/GrafanaDatasourceUrlGetHandler.java
index 42b89f2944..efa0a532dd 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/GrafanaDatasourceUrlGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/GrafanaDatasourceUrlGetHandler.java
@@ -38,10 +38,12 @@
package io.cryostat.net.web.http.api.v1;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
import io.cryostat.core.sys.Environment;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.RequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -75,6 +77,11 @@ public String path() {
return basePath() + "grafana_datasource_url";
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public HttpMethod httpMethod() {
return HttpMethod.GET;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/NotificationsUrlGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/NotificationsUrlGetHandler.java
index 31a751ff4f..cde8b0ea7e 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/NotificationsUrlGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/NotificationsUrlGetHandler.java
@@ -40,11 +40,13 @@
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
import io.cryostat.net.HttpServer;
import io.cryostat.net.NetworkConfiguration;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.RequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -78,6 +80,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + "notifications_url";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingDeleteHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingDeleteHandler.java
index 665d9bfb19..a11d3d612f 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingDeleteHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingDeleteHandler.java
@@ -39,7 +39,9 @@
import java.io.IOException;
import java.nio.file.Path;
+import java.util.EnumSet;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
@@ -49,6 +51,7 @@
import io.cryostat.messaging.notifications.NotificationFactory;
import io.cryostat.net.AuthManager;
import io.cryostat.net.reports.ReportService;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -89,6 +92,11 @@ public HttpMethod httpMethod() {
return HttpMethod.DELETE;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.DELETE_RECORDING);
+ }
+
@Override
public String path() {
return basePath() + "recordings/:recordingName";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingGetHandler.java
index c90959e3ef..d1e884e050 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingGetHandler.java
@@ -38,12 +38,15 @@
package io.cryostat.net.web.http.api.v1;
import java.nio.file.Path;
+import java.util.EnumSet;
+import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import io.cryostat.MainModule;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -73,6 +76,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_RECORDING);
+ }
+
@Override
public String path() {
return basePath() + "recordings/:recordingName";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingUploadPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingUploadPostHandler.java
index 619061bbb2..6c4dcdaf4d 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingUploadPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingUploadPostHandler.java
@@ -41,7 +41,9 @@
import java.net.URL;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
+import java.util.EnumSet;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
@@ -51,6 +53,7 @@
import io.cryostat.core.sys.Environment;
import io.cryostat.core.sys.FileSystem;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -97,6 +100,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_RECORDING);
+ }
+
@Override
public String path() {
return basePath() + "recordings/:recordingName/upload";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsGetHandler.java
index ba0b7711ad..7be11739d8 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsGetHandler.java
@@ -41,9 +41,11 @@
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.nio.file.Path;
+import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -54,6 +56,7 @@
import io.cryostat.core.log.Logger;
import io.cryostat.core.sys.FileSystem;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.WebServer;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
@@ -99,6 +102,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_RECORDING);
+ }
+
@Override
public String path() {
return basePath() + "recordings";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsPostBodyHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsPostBodyHandler.java
index 39771f0b43..899b316f69 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsPostBodyHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsPostBodyHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -67,6 +70,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + RecordingsPostHandler.PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsPostHandler.java
index 16be53f11f..72f5c910fe 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/RecordingsPostHandler.java
@@ -40,7 +40,9 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.EnumSet;
import java.util.Map;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -56,6 +58,7 @@
import io.cryostat.messaging.notifications.NotificationFactory;
import io.cryostat.net.AuthManager;
import io.cryostat.net.HttpServer;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -118,6 +121,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.CREATE_RECORDING);
+ }
+
@Override
public String path() {
return basePath() + PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/ReportGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/ReportGetHandler.java
index 1d7a918781..19f9260a27 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/ReportGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/ReportGetHandler.java
@@ -38,6 +38,8 @@
package io.cryostat.net.web.http.api.v1;
import java.nio.file.Path;
+import java.util.EnumSet;
+import java.util.Set;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
@@ -46,6 +48,7 @@
import io.cryostat.core.log.Logger;
import io.cryostat.net.AuthManager;
import io.cryostat.net.reports.ReportService;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -76,6 +79,14 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(
+ ResourceAction.READ_RECORDING,
+ ResourceAction.CREATE_REPORT,
+ ResourceAction.READ_REPORT);
+ }
+
@Override
public String path() {
return basePath() + "reports/:recordingName";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetEventsGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetEventsGetHandler.java
index 8534ab0bc4..7b3cd741a8 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetEventsGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetEventsGetHandler.java
@@ -39,7 +39,9 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.EnumSet;
import java.util.List;
+import java.util.Set;
import javax.inject.Inject;
@@ -48,6 +50,7 @@
import io.cryostat.jmc.serialization.SerializableEventTypeInfo;
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -79,6 +82,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET);
+ }
+
@Override
public String path() {
return basePath() + "targets/:targetId/events";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingDeleteHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingDeleteHandler.java
index dba4db4135..9c7527218d 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingDeleteHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingDeleteHandler.java
@@ -37,13 +37,16 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.EnumSet;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
import io.cryostat.messaging.notifications.NotificationFactory;
import io.cryostat.net.AuthManager;
import io.cryostat.net.ConnectionDescriptor;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -80,6 +83,11 @@ public HttpMethod httpMethod() {
return HttpMethod.DELETE;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.DELETE_RECORDING, ResourceAction.READ_TARGET);
+ }
+
@Override
public String path() {
return basePath() + "targets/:targetId/recordings/:recordingName";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingGetHandler.java
index 073c28d795..47ccf304b0 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingGetHandler.java
@@ -39,8 +39,10 @@
import java.io.IOException;
import java.io.InputStream;
+import java.util.EnumSet;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -48,6 +50,7 @@
import io.cryostat.net.AuthManager;
import io.cryostat.net.ConnectionDescriptor;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -82,6 +85,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET, ResourceAction.READ_RECORDING);
+ }
+
@Override
public String path() {
return basePath() + "targets/:targetId/recordings/:recordingName";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsGetHandler.java
index 10107ecff6..cab103a1fb 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsGetHandler.java
@@ -37,8 +37,10 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
@@ -49,6 +51,7 @@
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -93,6 +96,11 @@ public String path() {
return basePath() + PATH;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchBodyHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchBodyHandler.java
index a663dcd844..a5e168f957 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchBodyHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchBodyHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -71,6 +74,11 @@ public HttpMethod httpMethod() {
return HttpMethod.PATCH;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + TargetRecordingOptionsPatchHandler.PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchHandler.java
index 4ac8484978..6ba1c0d8fb 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchHandler.java
@@ -38,7 +38,9 @@
package io.cryostat.net.web.http.api.v1;
import java.util.Arrays;
+import java.util.EnumSet;
import java.util.Map;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -50,6 +52,7 @@
import io.cryostat.core.RecordingOptionsCustomizer.OptionKey;
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -99,6 +102,11 @@ public String path() {
return basePath() + PATH;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET, ResourceAction.UPDATE_TARGET);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingPatchBodyHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingPatchBodyHandler.java
index 320c82ef4d..b98995ef78 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingPatchBodyHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingPatchBodyHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -71,6 +74,11 @@ public HttpMethod httpMethod() {
return HttpMethod.PATCH;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + TargetRecordingPatchHandler.PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingPatchHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingPatchHandler.java
index cd559140db..1daa52e47c 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingPatchHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingPatchHandler.java
@@ -37,9 +37,13 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.EnumSet;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -79,6 +83,14 @@ public String path() {
return basePath() + PATH;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(
+ ResourceAction.READ_TARGET,
+ ResourceAction.READ_RECORDING,
+ ResourceAction.UPDATE_RECORDING);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingUploadPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingUploadPostHandler.java
index 9b10b9faf5..0ddda75c4e 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingUploadPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingUploadPostHandler.java
@@ -43,7 +43,9 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import java.util.EnumSet;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
@@ -54,6 +56,7 @@
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
import io.cryostat.net.reports.ReportService;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -106,6 +109,11 @@ public String path() {
return basePath() + "targets/:targetId/recordings/:recordingName/upload";
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET, ResourceAction.READ_RECORDING);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsGetHandler.java
index 68cee7e912..69a4e1bd4d 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsGetHandler.java
@@ -38,7 +38,9 @@
package io.cryostat.net.web.http.api.v1;
import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.List;
+import java.util.Set;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -48,6 +50,7 @@
import io.cryostat.jmc.serialization.HyperlinkedSerializableRecordingDescriptor;
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.WebServer;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
@@ -91,6 +94,11 @@ public String path() {
return basePath() + "targets/:targetId/recordings";
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET, ResourceAction.READ_RECORDING);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsPostBodyHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsPostBodyHandler.java
index b70a07612e..b2693594b0 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsPostBodyHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsPostBodyHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -72,6 +75,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + TargetRecordingsPostHandler.PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsPostHandler.java
index 3f48dc2214..97466f298b 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetRecordingsPostHandler.java
@@ -39,7 +39,9 @@
import java.io.IOException;
import java.net.URISyntaxException;
+import java.util.EnumSet;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -57,6 +59,7 @@
import io.cryostat.net.AuthManager;
import io.cryostat.net.ConnectionDescriptor;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.WebServer;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
@@ -113,6 +116,16 @@ public String path() {
return basePath() + PATH;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(
+ ResourceAction.READ_TARGET,
+ ResourceAction.UPDATE_TARGET,
+ ResourceAction.CREATE_RECORDING,
+ ResourceAction.READ_RECORDING,
+ ResourceAction.READ_TEMPLATE);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetReportGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetReportGetHandler.java
index 3abf289e9a..3667dec74d 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetReportGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetReportGetHandler.java
@@ -37,6 +37,8 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.EnumSet;
+import java.util.Set;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -47,6 +49,7 @@
import io.cryostat.net.AuthManager;
import io.cryostat.net.reports.ReportService;
import io.cryostat.net.reports.SubprocessReportGenerator;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -85,6 +88,15 @@ public String path() {
return basePath() + "targets/:targetId/reports/:recordingName";
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(
+ ResourceAction.READ_TARGET,
+ ResourceAction.READ_RECORDING,
+ ResourceAction.CREATE_REPORT,
+ ResourceAction.READ_REPORT);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetSnapshotPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetSnapshotPostHandler.java
index d48145bd73..20c79744c4 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetSnapshotPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetSnapshotPostHandler.java
@@ -37,6 +37,9 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.EnumSet;
+import java.util.Set;
+
import javax.inject.Inject;
import org.openjdk.jmc.flightrecorder.configuration.recording.RecordingOptionsBuilder;
@@ -44,6 +47,7 @@
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
import io.cryostat.recordings.RecordingOptionsBuilderFactory;
@@ -76,6 +80,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET, ResourceAction.UPDATE_RECORDING);
+ }
+
@Override
public String path() {
return basePath() + "targets/:targetId/snapshot";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetTemplateGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetTemplateGetHandler.java
index 35027a9c98..7e9128ae94 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetTemplateGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetTemplateGetHandler.java
@@ -37,11 +37,15 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.EnumSet;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.core.templates.TemplateType;
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -76,6 +80,11 @@ public String path() {
return basePath() + "targets/:targetId/templates/:templateName/type/:templateType";
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET, ResourceAction.READ_TEMPLATE);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetTemplatesGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetTemplatesGetHandler.java
index 9d84c760b6..79d3c68dac 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetTemplatesGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetTemplatesGetHandler.java
@@ -38,7 +38,9 @@
package io.cryostat.net.web.http.api.v1;
import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.List;
+import java.util.Set;
import javax.inject.Inject;
@@ -46,6 +48,7 @@
import io.cryostat.core.templates.TemplateType;
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -90,6 +93,11 @@ public String path() {
return basePath() + "targets/:targetId/templates";
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET, ResourceAction.READ_TEMPLATE);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TargetsGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TargetsGetHandler.java
index b74affe297..70307cf718 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TargetsGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TargetsGetHandler.java
@@ -37,9 +37,13 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.EnumSet;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -77,6 +81,11 @@ public String path() {
return basePath() + "targets";
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TemplateDeleteHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TemplateDeleteHandler.java
index f5b995133a..4601407b85 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TemplateDeleteHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TemplateDeleteHandler.java
@@ -37,7 +37,9 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.EnumSet;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
@@ -45,6 +47,7 @@
import io.cryostat.core.templates.MutableTemplateService.InvalidEventTemplateException;
import io.cryostat.messaging.notifications.NotificationFactory;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -84,6 +87,11 @@ public String path() {
return basePath() + "templates/:templateName";
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.DELETE_TEMPLATE);
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TemplatesPostBodyHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TemplatesPostBodyHandler.java
index b49789a5eb..592f7e9bcc 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TemplatesPostBodyHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TemplatesPostBodyHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.api.v1;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -76,6 +79,11 @@ public String path() {
return basePath() + TemplatesPostHandler.PATH;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public void handleAuthenticated(RoutingContext ctx) {
BODY_HANDLER.handle(ctx);
diff --git a/src/main/java/io/cryostat/net/web/http/api/v1/TemplatesPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v1/TemplatesPostHandler.java
index 90fbb5b630..1e8bcb9a7f 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v1/TemplatesPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v1/TemplatesPostHandler.java
@@ -39,7 +39,9 @@
import java.io.InputStream;
import java.nio.file.Path;
+import java.util.EnumSet;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
@@ -50,6 +52,7 @@
import io.cryostat.core.templates.MutableTemplateService.InvalidXmlException;
import io.cryostat.messaging.notifications.NotificationFactory;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -93,6 +96,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.CREATE_TEMPLATE);
+ }
+
@Override
public String path() {
return basePath() + PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/AbstractV2RequestHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/AbstractV2RequestHandler.java
index 35c8e4345c..02a11ea5a0 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/AbstractV2RequestHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/AbstractV2RequestHandler.java
@@ -41,6 +41,7 @@
import java.nio.charset.StandardCharsets;
import java.rmi.ConnectIOException;
import java.util.Base64;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -50,6 +51,7 @@
import io.cryostat.core.net.Credentials;
import io.cryostat.net.AuthManager;
import io.cryostat.net.ConnectionDescriptor;
+import io.cryostat.net.OpenShiftAuthManager.PermissionDeniedException;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.RequestHandler;
import io.cryostat.net.web.http.api.ApiMeta;
@@ -57,6 +59,7 @@
import io.cryostat.net.web.http.api.ApiResultData;
import com.google.gson.Gson;
+import io.fabric8.kubernetes.client.KubernetesClientException;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;
@@ -87,11 +90,26 @@ protected AbstractV2RequestHandler(AuthManager auth, Gson gson) {
public final void handle(RoutingContext ctx) {
RequestParameters requestParams = RequestParameters.from(ctx);
try {
- if (requiresAuthentication()
- && !validateRequestAuthorization(
- requestParams.getHeaders().get(HttpHeaders.AUTHORIZATION))
- .get()) {
- throw new ApiException(401, "HTTP Authorization Failure");
+ if (requiresAuthentication()) {
+ try {
+ boolean permissionGranted =
+ validateRequestAuthorization(
+ requestParams
+ .getHeaders()
+ .get(HttpHeaders.AUTHORIZATION))
+ .get();
+ if (!permissionGranted) {
+ // expected to go into catch clause below
+ throw new ApiException(401, "HTTP Authorization Failure");
+ }
+ } catch (ExecutionException ee) {
+ Throwable cause = ee.getCause();
+ if (cause instanceof PermissionDeniedException
+ || cause instanceof KubernetesClientException) {
+ throw new ApiException(401, "HTTP Authorization Failure", ee);
+ }
+ throw new ApiException(500, ee);
+ }
}
writeResponse(ctx, handle(requestParams));
} catch (ApiException e) {
@@ -117,7 +135,7 @@ public final void handle(RoutingContext ctx) {
}
protected Future validateRequestAuthorization(String authHeader) throws Exception {
- return auth.validateHttpHeader(() -> authHeader);
+ return auth.validateHttpHeader(() -> authHeader, resourceActions());
}
protected ConnectionDescriptor getConnectionDescriptorFromParams(RequestParameters params) {
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/ApiGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/ApiGetHandler.java
index 24be80630c..0e7eecfd70 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/ApiGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/ApiGetHandler.java
@@ -47,6 +47,7 @@
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.WebServer;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.RequestHandler;
@@ -90,6 +91,11 @@ public String path() {
return basePath() + "api";
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
HttpMimeType mimeType() {
return HttpMimeType.JSON;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/CertificatePostBodyHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/CertificatePostBodyHandler.java
index 5a24beb104..9bb214c11a 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/CertificatePostBodyHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/CertificatePostBodyHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.api.v2;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -71,6 +74,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + CertificatePostHandler.PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/CertificatePostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/CertificatePostHandler.java
index b251fee83c..52d4a360ee 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/CertificatePostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/CertificatePostHandler.java
@@ -46,6 +46,8 @@
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Set;
import java.util.function.Function;
import javax.inject.Inject;
@@ -55,6 +57,7 @@
import io.cryostat.core.sys.FileSystem;
import io.cryostat.net.AuthManager;
import io.cryostat.net.security.CertificateValidator;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -104,6 +107,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.CREATE_CERTIFICATE);
+ }
+
@Override
public String path() {
return basePath() + PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/RuleDeleteHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/RuleDeleteHandler.java
index f76995d021..7baa39227a 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/RuleDeleteHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/RuleDeleteHandler.java
@@ -38,11 +38,14 @@
package io.cryostat.net.web.http.api.v2;
import java.io.IOException;
+import java.util.EnumSet;
+import java.util.Set;
import javax.inject.Inject;
import io.cryostat.core.log.Logger;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
import io.cryostat.rules.Rule;
@@ -80,6 +83,11 @@ public HttpMethod httpMethod() {
return HttpMethod.DELETE;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.DELETE_RULE);
+ }
+
@Override
public String path() {
return basePath() + PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/RuleGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/RuleGetHandler.java
index a8603c1cff..fdd37b0487 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/RuleGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/RuleGetHandler.java
@@ -37,10 +37,14 @@
*/
package io.cryostat.net.web.http.api.v2;
+import java.util.EnumSet;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.core.log.Logger;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
import io.cryostat.rules.Rule;
@@ -78,6 +82,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_RULE);
+ }
+
@Override
public String path() {
return basePath() + PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/RulesGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/RulesGetHandler.java
index 0f40004e3a..ff546437ba 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/RulesGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/RulesGetHandler.java
@@ -37,12 +37,14 @@
*/
package io.cryostat.net.web.http.api.v2;
+import java.util.EnumSet;
import java.util.Set;
import javax.inject.Inject;
import io.cryostat.core.log.Logger;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
import io.cryostat.rules.Rule;
@@ -80,6 +82,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_RULE);
+ }
+
@Override
public String path() {
return basePath() + PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/RulesPostBodyHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/RulesPostBodyHandler.java
index 32a82aef78..a72b593b4d 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/RulesPostBodyHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/RulesPostBodyHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.api.v2;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -71,6 +74,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + RulesPostHandler.PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/RulesPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/RulesPostHandler.java
index bd8bcec7cf..4751526d0f 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/RulesPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/RulesPostHandler.java
@@ -38,11 +38,14 @@
package io.cryostat.net.web.http.api.v2;
import java.io.IOException;
+import java.util.EnumSet;
+import java.util.Set;
import javax.inject.Inject;
import io.cryostat.core.log.Logger;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
import io.cryostat.rules.MatchExpressionValidationException;
@@ -89,6 +92,16 @@ public String path() {
return basePath() + PATH;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(
+ ResourceAction.CREATE_RULE,
+ ResourceAction.READ_TARGET,
+ ResourceAction.CREATE_RECORDING,
+ ResourceAction.UPDATE_RECORDING,
+ ResourceAction.READ_TEMPLATE);
+ }
+
@Override
public HttpMimeType mimeType() {
return HttpMimeType.PLAINTEXT;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsDeleteHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsDeleteHandler.java
index aaa455c2f7..223a163aa6 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsDeleteHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsDeleteHandler.java
@@ -38,11 +38,14 @@
package io.cryostat.net.web.http.api.v2;
import java.io.IOException;
+import java.util.EnumSet;
+import java.util.Set;
import javax.inject.Inject;
import io.cryostat.configuration.CredentialsManager;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -77,6 +80,11 @@ public HttpMethod httpMethod() {
return HttpMethod.DELETE;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.DELETE_CREDENTIALS);
+ }
+
@Override
public String path() {
return basePath() + PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsPostBodyHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsPostBodyHandler.java
index 49f6ef4fa1..6f88b83fe6 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsPostBodyHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsPostBodyHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.api.v2;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -71,6 +74,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + TargetCredentialsPostHandler.PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsPostHandler.java
index 3193550db7..5fbccab4e2 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/TargetCredentialsPostHandler.java
@@ -38,6 +38,8 @@
package io.cryostat.net.web.http.api.v2;
import java.io.IOException;
+import java.util.EnumSet;
+import java.util.Set;
import javax.inject.Inject;
@@ -45,6 +47,7 @@
import io.cryostat.core.log.Logger;
import io.cryostat.core.net.Credentials;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -82,6 +85,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.CREATE_CREDENTIALS);
+ }
+
@Override
public String path() {
return basePath() + PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/TargetDeleteHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/TargetDeleteHandler.java
index 09268b0a1e..a7cda66d0d 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/TargetDeleteHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/TargetDeleteHandler.java
@@ -40,10 +40,13 @@
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.EnumSet;
+import java.util.Set;
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
import io.cryostat.platform.internal.CustomTargetPlatformClient;
@@ -79,6 +82,11 @@ public HttpMethod httpMethod() {
return HttpMethod.DELETE;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.DELETE_TARGET);
+ }
+
@Override
public String path() {
return basePath() + "targets/:targetId";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/TargetEventsGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/TargetEventsGetHandler.java
index 9bbba64520..e72eeb8757 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/TargetEventsGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/TargetEventsGetHandler.java
@@ -38,6 +38,7 @@
package io.cryostat.net.web.http.api.v2;
import java.util.Arrays;
+import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -50,6 +51,7 @@
import io.cryostat.jmc.serialization.SerializableEventTypeInfo;
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -83,6 +85,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET);
+ }
+
@Override
public String path() {
return basePath() + "targets/:targetId/events";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/TargetRecordingOptionsListGetHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/TargetRecordingOptionsListGetHandler.java
index 91b971eb99..b22386950c 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/TargetRecordingOptionsListGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/TargetRecordingOptionsListGetHandler.java
@@ -38,8 +38,10 @@
package io.cryostat.net.web.http.api.v2;
import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
@@ -48,6 +50,7 @@
import io.cryostat.jmc.serialization.SerializableOptionDescriptor;
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -81,6 +84,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET);
+ }
+
@Override
public String path() {
return basePath() + "targets/:targetId/recordingOptionsList";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/TargetSnapshotPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/TargetSnapshotPostHandler.java
index c80b75c883..e8957e7adc 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/TargetSnapshotPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/TargetSnapshotPostHandler.java
@@ -37,6 +37,9 @@
*/
package io.cryostat.net.web.http.api.v2;
+import java.util.EnumSet;
+import java.util.Set;
+
import javax.inject.Inject;
import org.openjdk.jmc.common.unit.QuantityConversionException;
@@ -46,6 +49,7 @@
import io.cryostat.jmc.serialization.HyperlinkedSerializableRecordingDescriptor;
import io.cryostat.net.AuthManager;
import io.cryostat.net.TargetConnectionManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.WebServer;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -91,6 +95,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.READ_TARGET, ResourceAction.UPDATE_RECORDING);
+ }
+
@Override
public String path() {
return basePath() + "targets/:targetId/snapshot";
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostBodyHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostBodyHandler.java
index 6e44692ee2..b9254889da 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostBodyHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostBodyHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.api.v2;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -71,6 +74,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + TargetsPostHandler.PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostHandler.java
index e3c5917803..5c364c86de 100644
--- a/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostHandler.java
@@ -40,13 +40,16 @@
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import javax.inject.Inject;
import io.cryostat.net.AuthManager;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.api.ApiVersion;
import io.cryostat.platform.PlatformClient;
@@ -93,6 +96,11 @@ public HttpMethod httpMethod() {
return HttpMethod.POST;
}
+ @Override
+ public Set resourceActions() {
+ return EnumSet.of(ResourceAction.CREATE_TARGET);
+ }
+
@Override
public String path() {
return basePath() + PATH;
diff --git a/src/main/java/io/cryostat/net/web/http/generic/CorsEnablingHandler.java b/src/main/java/io/cryostat/net/web/http/generic/CorsEnablingHandler.java
index 52e882898a..bc7ff58dd4 100644
--- a/src/main/java/io/cryostat/net/web/http/generic/CorsEnablingHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/generic/CorsEnablingHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.generic;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.core.sys.Environment;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.WebServer;
import io.cryostat.net.web.http.AbstractAuthenticatedRequestHandler;
import io.cryostat.net.web.http.RequestHandler;
@@ -93,6 +96,11 @@ public HttpMethod httpMethod() {
return HttpMethod.OTHER; // unused for ALL_PATHS handlers
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return ALL_PATHS;
diff --git a/src/main/java/io/cryostat/net/web/http/generic/CorsOptionsHandler.java b/src/main/java/io/cryostat/net/web/http/generic/CorsOptionsHandler.java
index 3ab89b38be..e7a56c711d 100644
--- a/src/main/java/io/cryostat/net/web/http/generic/CorsOptionsHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/generic/CorsOptionsHandler.java
@@ -37,9 +37,12 @@
*/
package io.cryostat.net.web.http.generic;
+import java.util.Set;
+
import javax.inject.Inject;
import io.cryostat.core.sys.Environment;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.api.ApiVersion;
import io.vertx.core.http.HttpMethod;
@@ -66,6 +69,11 @@ public HttpMethod httpMethod() {
return HttpMethod.OPTIONS;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + "*";
diff --git a/src/main/java/io/cryostat/net/web/http/generic/HealthGetHandler.java b/src/main/java/io/cryostat/net/web/http/generic/HealthGetHandler.java
index f8c3701b55..eab6486904 100644
--- a/src/main/java/io/cryostat/net/web/http/generic/HealthGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/generic/HealthGetHandler.java
@@ -41,12 +41,14 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import io.cryostat.core.log.Logger;
import io.cryostat.core.sys.Environment;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.RequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -92,6 +94,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public boolean isAsync() {
return false;
diff --git a/src/main/java/io/cryostat/net/web/http/generic/StaticAssetsGetHandler.java b/src/main/java/io/cryostat/net/web/http/generic/StaticAssetsGetHandler.java
index c3fcc03026..a945fa9e06 100644
--- a/src/main/java/io/cryostat/net/web/http/generic/StaticAssetsGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/generic/StaticAssetsGetHandler.java
@@ -37,8 +37,11 @@
*/
package io.cryostat.net.web.http.generic;
+import java.util.Set;
+
import javax.inject.Inject;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.RequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -65,6 +68,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + "*";
diff --git a/src/main/java/io/cryostat/net/web/http/generic/TimeoutHandler.java b/src/main/java/io/cryostat/net/web/http/generic/TimeoutHandler.java
index e12c3d7f2c..ee5b683e5e 100644
--- a/src/main/java/io/cryostat/net/web/http/generic/TimeoutHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/generic/TimeoutHandler.java
@@ -37,8 +37,11 @@
*/
package io.cryostat.net.web.http.generic;
+import java.util.Set;
+
import javax.inject.Inject;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.RequestHandler;
import io.cryostat.net.web.http.api.ApiVersion;
@@ -70,6 +73,11 @@ public HttpMethod httpMethod() {
return HttpMethod.OTHER;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return ALL_PATHS;
diff --git a/src/main/java/io/cryostat/net/web/http/generic/WebClientAssetsGetHandler.java b/src/main/java/io/cryostat/net/web/http/generic/WebClientAssetsGetHandler.java
index 9f4705ad43..a8be9cac28 100644
--- a/src/main/java/io/cryostat/net/web/http/generic/WebClientAssetsGetHandler.java
+++ b/src/main/java/io/cryostat/net/web/http/generic/WebClientAssetsGetHandler.java
@@ -38,9 +38,11 @@
package io.cryostat.net.web.http.generic;
import java.nio.file.Path;
+import java.util.Set;
import javax.inject.Inject;
+import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.WebServer;
import io.cryostat.net.web.http.HttpMimeType;
import io.cryostat.net.web.http.RequestHandler;
@@ -81,6 +83,11 @@ public HttpMethod httpMethod() {
return HttpMethod.GET;
}
+ @Override
+ public Set resourceActions() {
+ return ResourceAction.NONE;
+ }
+
@Override
public String path() {
return basePath() + "*";
diff --git a/src/test/java/io/cryostat/net/BasicAuthManagerTest.java b/src/test/java/io/cryostat/net/BasicAuthManagerTest.java
index 476d271405..a2d838fd4e 100644
--- a/src/test/java/io/cryostat/net/BasicAuthManagerTest.java
+++ b/src/test/java/io/cryostat/net/BasicAuthManagerTest.java
@@ -44,6 +44,7 @@
import io.cryostat.core.log.Logger;
import io.cryostat.core.sys.FileSystem;
+import io.cryostat.net.security.ResourceAction;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
@@ -153,7 +154,7 @@ void shouldLogFileReadErrors() throws Exception {
class TokenValidationTest {
@Test
void shouldFailAuthenticationWhenCredentialsMalformed() throws Exception {
- Assertions.assertFalse(mgr.validateToken(() -> "user").get());
+ Assertions.assertFalse(mgr.validateToken(() -> "user", ResourceAction.NONE).get());
ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor