Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(permissions): API permissions model using client token #599

Merged
merged 11 commits into from Aug 9, 2021
14 changes: 13 additions & 1 deletion pom.xml
Expand Up @@ -67,7 +67,7 @@
<org.apache.commons.validator.version>1.7</org.apache.commons.validator.version>
<org.apache.commons.io.version>2.8.0</org.apache.commons.io.version>
<org.apache.httpcomponents.version>4.5.13</org.apache.httpcomponents.version>
<io.fabric8.client.version>5.3.0</io.fabric8.client.version>
<io.fabric8.client.version>5.4.1</io.fabric8.client.version>
<io.vertx.web.version>3.9.7</io.vertx.web.version>
<org.slf4j.version>1.7.30</org.slf4j.version>
<com.google.code.gson.version>2.8.6</com.google.code.gson.version>
Expand Down Expand Up @@ -201,6 +201,18 @@
<version>${com.github.spotbugs.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-server-mock</artifactId>
<version>${io.fabric8.client.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>openshift-server-mock</artifactId>
<version>${io.fabric8.client.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/io/cryostat/messaging/MessagingServer.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
12 changes: 9 additions & 3 deletions src/main/java/io/cryostat/net/AuthManager.java
Expand Up @@ -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<Boolean> validateToken(Supplier<String> tokenProvider);
Future<Boolean> validateToken(
Supplier<String> tokenProvider, Set<ResourceAction> resourceActions);

Future<Boolean> validateHttpHeader(Supplier<String> headerProvider);
Future<Boolean> validateHttpHeader(
Supplier<String> headerProvider, Set<ResourceAction> resourceActions);

Future<Boolean> validateWebSocketSubProtocol(Supplier<String> subProtocolProvider);
Future<Boolean> validateWebSocketSubProtocol(
Supplier<String> subProtocolProvider, Set<ResourceAction> resourceActions);

AuthenticatedAction doAuthenticated(
Supplier<String> provider, Function<Supplier<String>, Future<Boolean>> validator);
Expand Down
27 changes: 20 additions & 7 deletions src/main/java/io/cryostat/net/BasicAuthManager.java
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -77,7 +79,8 @@ public AuthenticationScheme getScheme() {
}

@Override
public Future<Boolean> validateToken(Supplier<String> tokenProvider) {
public Future<Boolean> validateToken(
Supplier<String> tokenProvider, Set<ResourceAction> resourceActions) {
if (!configLoaded) {
this.loadConfig();
}
Expand All @@ -90,12 +93,21 @@ public Future<Boolean> validateToken(Supplier<String> 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<Boolean> validateHttpHeader(Supplier<String> headerProvider) {
public Future<Boolean> validateHttpHeader(
Supplier<String> headerProvider, Set<ResourceAction> resourceActions) {
String authorization = headerProvider.get();
if (StringUtils.isBlank(authorization)) {
return CompletableFuture.completedFuture(false);
Expand All @@ -109,14 +121,15 @@ public Future<Boolean> validateHttpHeader(Supplier<String> 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<Boolean> validateWebSocketSubProtocol(Supplier<String> subProtocolProvider) {
public Future<Boolean> validateWebSocketSubProtocol(
Supplier<String> subProtocolProvider, Set<ResourceAction> resourceActions) {
String subprotocol = subProtocolProvider.get();
if (StringUtils.isBlank(subprotocol)) {
return CompletableFuture.completedFuture(false);
Expand All @@ -132,7 +145,7 @@ public Future<Boolean> validateWebSocketSubProtocol(Supplier<String> 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);
}
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/io/cryostat/net/NetworkModule.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/io/cryostat/net/NoopAuthManager.java
Expand Up @@ -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 {

Expand All @@ -56,17 +58,20 @@ public AuthenticationScheme getScheme() {
}

@Override
public Future<Boolean> validateToken(Supplier<String> tokenProvider) {
public Future<Boolean> validateToken(
Supplier<String> tokenProvider, Set<ResourceAction> resourceActions) {
return CompletableFuture.completedFuture(true);
}

@Override
public Future<Boolean> validateHttpHeader(Supplier<String> headerProvider) {
public Future<Boolean> validateHttpHeader(
Supplier<String> headerProvider, Set<ResourceAction> resourceActions) {
return CompletableFuture.completedFuture(true);
}

@Override
public Future<Boolean> validateWebSocketSubProtocol(Supplier<String> subProtocolProvider) {
public Future<Boolean> validateWebSocketSubProtocol(
Supplier<String> subProtocolProvider, Set<ResourceAction> resourceActions) {
return CompletableFuture.completedFuture(true);
}
}