Skip to content

Commit

Permalink
fix(permissions): API permissions model using client token (#599)
Browse files Browse the repository at this point in the history
* feat(security): AuthManagers validate permissions

* test(security): add tests for required permissions

* style(basicauthmanager): deduplicate

* test(openshift): add OpenShiftAuthManagerTest

* feat(openshift): ResourceTypes may map to multiple k8s resource types

* feat(openshift): add pods permission

* test(openshift): complete OpenShiftAuthManagerTest

* test(report-get): rebase fix test compile

* chore(openshift): use fabric8-client 5.4.1

* test(openshift): rename test method

* fix(openshift): validate user tokens even without required resource actions
  • Loading branch information
andrewazores committed Aug 9, 2021
1 parent 7f73beb commit e8c60af
Show file tree
Hide file tree
Showing 110 changed files with 1,885 additions and 139 deletions.
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);
}
}

0 comments on commit e8c60af

Please sign in to comment.