Skip to content
Permalink
Browse files
Merge pull request #4 from myrle-krantz/develop
Preparation work for making it possible to secure communication between services.
  • Loading branch information
myrle-krantz committed May 2, 2017
2 parents a68574b + b3f3132 commit 4192aa8198d93b1d8e709f011beee31589554843
Showing 20 changed files with 345 additions and 91 deletions.
@@ -24,6 +24,7 @@ public interface TokenConstants {
String PREFIX = "Bearer ";

String JWT_SIGNATURE_TIMESTAMP_CLAIM = "/mifos.io/signatureTimestamp";
String JWT_ENDPOINT_SET_CLAIM = "/mifos.io/endpointSet";
String JWT_CONTENT_CLAIM = "/mifos.io/tokenContent";

String REFRESH_TOKEN_COOKIE_NAME = "org.apache.fineract.refreshToken";
@@ -16,6 +16,7 @@

import io.mifos.anubis.api.v1.client.Anubis;
import io.mifos.anubis.api.v1.client.AnubisApiFactory;
import io.mifos.anubis.api.v1.domain.AllowedOperation;
import io.mifos.anubis.api.v1.domain.Signature;
import io.mifos.anubis.example.simple.Example;
import io.mifos.anubis.example.simple.ExampleConfiguration;
@@ -158,6 +159,20 @@ public void testNonExistentTenant() {
}
}

@Test(expected = InvalidTokenException.class)
public void testAuthenticateWithoutInitialize() {
try (final TenantDataStoreTestContext ignored = TenantDataStoreTestContext.forRandomTenantName(cassandraInitializer)) {

final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment
= new TenantApplicationSecurityEnvironmentTestRule(testEnvironment);
final String permissionToken = tenantApplicationSecurityEnvironment.getPermissionToken("bubba", "foo", AllowedOperation.READ);
try (final AutoUserContext ignored2 = new AutoUserContext("bubba", permissionToken)) {
Assert.assertFalse(example.foo());
Assert.fail("Not found exception should be thrown when authentication is attempted ");
}
}
}

private void initialize() {
final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment
= new TenantApplicationSecurityEnvironmentTestRule(testEnvironment);
@@ -53,7 +53,7 @@ public ResponseEntity<Void> initialize()

final ApplicationSignatureSet applicationSignatureSet = new ApplicationSignatureSet(identityManagerKeyPair.getTimestamp(), applicationSignature, identityManagerSignature);

this.specialTenantSignatureRepository.addSignatureSet(applicationSignatureSet);
this.specialTenantSignatureRepository.addSignatureSet(applicationSignatureSet, applicationKeyPair);
initialized = true;
return new ResponseEntity<>(HttpStatus.OK);
}
@@ -18,6 +18,7 @@
import io.mifos.anubis.api.v1.domain.ApplicationSignatureSet;
import io.mifos.anubis.api.v1.domain.Signature;
import io.mifos.anubis.config.TenantSignatureRepository;
import io.mifos.core.lang.security.RsaKeyPairFactory;
import org.springframework.stereotype.Component;

import java.util.HashMap;
@@ -31,15 +32,38 @@
*/
@Component
public class SpecialTenantSignatureRepository implements TenantSignatureRepository {
private final Map<String, ApplicationSignatureSet> applicationSignatureSetMap = new HashMap<>();
private static class AllTheKeyInfos {
final ApplicationSignatureSet applicationSignatureSet;
final RsaKeyPairFactory.KeyPairHolder applicationKeyPair;

void addSignatureSet(final ApplicationSignatureSet applicationSignatureSet) {
applicationSignatureSetMap.put(applicationSignatureSet.getTimestamp(), applicationSignatureSet);
private AllTheKeyInfos(
final ApplicationSignatureSet applicationSignatureSet,
final RsaKeyPairFactory.KeyPairHolder applicationKeyPair) {
this.applicationSignatureSet = applicationSignatureSet;
this.applicationKeyPair = applicationKeyPair;
}

ApplicationSignatureSet getApplicationSignatureSet() {
return applicationSignatureSet;
}

RsaKeyPairFactory.KeyPairHolder getApplicationKeyPair() {
return applicationKeyPair;
}
}
private final Map<String, AllTheKeyInfos> applicationSignatureSetMap = new HashMap<>();

void addSignatureSet(final ApplicationSignatureSet applicationSignatureSet,
final RsaKeyPairFactory.KeyPairHolder applicationKeyPair) {
applicationSignatureSetMap.put(applicationSignatureSet.getTimestamp(),
new AllTheKeyInfos(applicationSignatureSet, applicationKeyPair));
}

@Override
public Optional<Signature> getIdentityManagerSignature(final String timestamp) throws IllegalArgumentException {
final Optional<ApplicationSignatureSet> sigset = Optional.ofNullable(applicationSignatureSetMap.get(timestamp));
final Optional<ApplicationSignatureSet> sigset =
Optional.ofNullable(applicationSignatureSetMap.get(timestamp))
.map(AllTheKeyInfos::getApplicationSignatureSet);
return sigset.map(ApplicationSignatureSet::getIdentityManagerSignature);
}

@@ -50,7 +74,8 @@ public List<String> getAllSignatureSetKeyTimestamps() {

@Override
public Optional<ApplicationSignatureSet> getSignatureSet(final String timestamp) {
return Optional.ofNullable(applicationSignatureSetMap.get(timestamp));
return Optional.ofNullable(applicationSignatureSetMap.get(timestamp))
.map(AllTheKeyInfos::getApplicationSignatureSet);
}

@Override
@@ -60,24 +85,34 @@ public void deleteSignatureSet(final String timestamp) {

@Override
public Optional<Signature> getApplicationSignature(final String timestamp) {
final Optional<ApplicationSignatureSet> sigset = Optional.ofNullable(applicationSignatureSetMap.get(timestamp));
final Optional<ApplicationSignatureSet> sigset
= Optional.ofNullable(applicationSignatureSetMap.get(timestamp))
.map(AllTheKeyInfos::getApplicationSignatureSet);
return sigset.map(ApplicationSignatureSet::getApplicationSignature);
}

@Override
public Optional<ApplicationSignatureSet> getLatestSignatureSet() {
Optional<String> timestamp = getMostRecentTimestamp();
final Optional<String> timestamp = getMostRecentTimestamp();
return timestamp.flatMap(this::getSignatureSet);
}

@Override
public Optional<Signature> getLatestApplicationSignature() {
Optional<String> timestamp = getMostRecentTimestamp();
final Optional<String> timestamp = getMostRecentTimestamp();
return timestamp.flatMap(this::getApplicationSignature);
}

@Override
public Optional<RsaKeyPairFactory.KeyPairHolder> getLatestApplicationSigningKeyPair() {
final Optional<String> timestamp = getMostRecentTimestamp();
return timestamp
.flatMap(x -> Optional.ofNullable(applicationSignatureSetMap.get(x)))
.map(AllTheKeyInfos::getApplicationKeyPair);
}

private Optional<String> getMostRecentTimestamp() {
return getAllSignatureSetKeyTimestamps().stream()
.max(String::compareTo);
}
}
}
@@ -30,4 +30,7 @@ public interface Example {

@RequestMapping(value = "initialize", method = RequestMethod.DELETE)
void uninitialize();

@RequestMapping(value = "foo", method = RequestMethod.GET)
boolean foo();
}
@@ -54,4 +54,10 @@ public ResponseEntity<Void> uninitialize()
initialized = false;
return new ResponseEntity<>(HttpStatus.OK);
}

@RequestMapping(value = "/foo", method = RequestMethod.GET)
@Permittable(AcceptedTokenType.TENANT)
public ResponseEntity<Boolean> foo() {
return ResponseEntity.ok(false);
}
}
@@ -21,6 +21,7 @@
import io.mifos.anubis.security.IsisAuthenticatedAuthenticationProvider;
import io.mifos.anubis.security.UrlPermissionChecker;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -42,13 +43,20 @@
import java.util.ArrayList;
import java.util.List;

import static io.mifos.anubis.config.AnubisConstants.LOGGER_NAME;

/**
* @author Myrle Krantz
*/
@SuppressWarnings("WeakerAccess")
@Configuration
@EnableWebSecurity
public class AnubisSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
final private Logger logger;

public AnubisSecurityConfigurerAdapter(final @Qualifier(LOGGER_NAME) Logger logger) {
this.logger = logger;
}

@PostConstruct
public void configureSecurityContext()
@@ -83,7 +91,7 @@ public FilterRegistrationBean userContextFilter()

private AccessDecisionManager defaultAccessDecisionManager() {
final List<AccessDecisionVoter<?>> voters = new ArrayList<>();
voters.add(new UrlPermissionChecker());
voters.add(new UrlPermissionChecker(logger));
return new UnanimousBased(voters);
}

@@ -18,7 +18,9 @@

import io.mifos.anubis.api.v1.domain.ApplicationSignatureSet;
import io.mifos.anubis.api.v1.domain.Signature;
import io.mifos.core.lang.security.RsaKeyPairFactory;

import java.security.interfaces.RSAPrivateKey;
import java.util.List;
import java.util.Optional;

@@ -42,4 +44,6 @@ public interface TenantSignatureRepository {
Optional<Signature> getApplicationSignature(String timestamp);

Optional<Signature> getLatestApplicationSignature();

Optional<RsaKeyPairFactory.KeyPairHolder> getLatestApplicationSigningKeyPair();
}
@@ -16,6 +16,7 @@
package io.mifos.anubis.repository;

import com.datastax.driver.core.*;
import com.datastax.driver.core.exceptions.InvalidQueryException;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.querybuilder.Update;
@@ -27,6 +28,8 @@
import io.mifos.core.cassandra.core.CassandraSessionProvider;
import io.mifos.core.lang.ApplicationName;
import io.mifos.core.lang.security.RsaKeyPairFactory;
import io.mifos.core.lang.security.RsaPrivateKeyBuilder;
import io.mifos.core.lang.security.RsaPublicKeyBuilder;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -35,6 +38,10 @@

import javax.annotation.Nonnull;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -94,6 +101,9 @@ public Signature createSignatureSet(final String timestamp, final Signature iden
Assert.notNull(identityManagerSignature);

//TODO: add validation to make sure this timestamp is more recent than any already stored.
logger.info("Creating application signature set for timestamp '" + timestamp +
"'. Identity manager signature is: " + identityManagerSignature);

final RsaKeyPairFactory.KeyPairHolder applicationSignature = RsaKeyPairFactory.createKeyPair();

final Session session = cassandraSessionProvider.getTenantSession();
@@ -115,6 +125,7 @@ public void deleteSignatureSet(final String timestamp) {
Assert.notNull(timestamp);
//Don't actually delete, just invalidate, so that if someone starts coming at me with an older keyset, I'll
//know what's happening.
logger.info("Invalidationg signature set for timestamp '" + timestamp + "'.");
final Session session = cassandraSessionProvider.getTenantSession();
invalidateEntry(session, timestamp);
}
@@ -250,13 +261,18 @@ private Optional<Row> getRow(final @Nonnull String timestamp) {
final Session tenantSession = cassandraSessionProvider.getTenantSession();
final Select.Where query = timestampToSignatureQueryMap.computeIfAbsent(timestamp, timestampKey ->
QueryBuilder.select().from(tableName).where(QueryBuilder.eq(TIMESTAMP_COLUMN, timestampKey)));
final Row row = tenantSession.execute(query).one();
final Optional<Row> ret = Optional.ofNullable(row);
ret.map(TenantAuthorizationDataRepository::mapRowToValid).ifPresent(valid -> {
if (!valid)
logger.warn("Invalidated keyset for timestamp '" + timestamp + "' requested. Pretending no keyset exists.");
});
return ret.filter(TenantAuthorizationDataRepository::mapRowToValid);
try {
final Row row = tenantSession.execute(query).one();
final Optional<Row> ret = Optional.ofNullable(row);
ret.map(TenantAuthorizationDataRepository::mapRowToValid).ifPresent(valid -> {
if (!valid)
logger.warn("Invalidated keyset for timestamp '" + timestamp + "' requested. Pretending no keyset exists.");
});
return ret.filter(TenantAuthorizationDataRepository::mapRowToValid);
}
catch (final InvalidQueryException authorizationDataTableProbablyIsntConfiguredYet) {
throw new IllegalArgumentException("Tenant not found.");
}
}

private static Boolean mapRowToValid(final @Nonnull Row row) {
@@ -283,6 +299,24 @@ private static Signature mapRowToApplicationSignature(final @Nonnull Row row) {
return getSignature(row, APPLICATION_PUBLIC_KEY_MOD_COLUMN, APPLICATION_PUBLIC_KEY_EXP_COLUMN);
}

private static RsaKeyPairFactory.KeyPairHolder mapRowToKeyPairHolder(final @Nonnull Row row) {
final BigInteger publicKeyModulus = row.get(APPLICATION_PUBLIC_KEY_MOD_COLUMN, BigInteger.class);
final BigInteger publicKeyExponent = row.get(APPLICATION_PUBLIC_KEY_EXP_COLUMN, BigInteger.class);
final BigInteger privateKeyModulus = row.get(APPLICATION_PRIVATE_KEY_MOD_COLUMN, BigInteger.class);
final BigInteger privateKeyExponent = row.get(APPLICATION_PRIVATE_KEY_EXP_COLUMN, BigInteger.class);

final PublicKey publicKey = new RsaPublicKeyBuilder()
.setPublicKeyMod(publicKeyModulus)
.setPublicKeyExp(publicKeyExponent)
.build();
final PrivateKey privateKey = new RsaPrivateKeyBuilder()
.setPrivateKeyMod(privateKeyModulus)
.setPrivateKeyExp(privateKeyExponent)
.build();
final String timestamp = row.get(TIMESTAMP_COLUMN, String.class);
return new RsaKeyPairFactory.KeyPairHolder(timestamp, (RSAPublicKey)publicKey, (RSAPrivateKey)privateKey);
}

private static ApplicationSignatureSet mapRowToSignatureSet(final @Nonnull Row row) {
final String timestamp = row.get(TIMESTAMP_COLUMN, String.class);
final Signature identityManagerSignature = mapRowToIdentityManagerSignature(row);
@@ -316,6 +350,12 @@ public Optional<Signature> getLatestApplicationSignature() {
return timestamp.flatMap(this::getApplicationSignature);
}

@Override
public Optional<RsaKeyPairFactory.KeyPairHolder> getLatestApplicationSigningKeyPair() {
Optional<String> timestamp = getMostRecentTimestamp();
return timestamp.flatMap(this::getRow).map(TenantAuthorizationDataRepository::mapRowToKeyPairHolder);
}

private Optional<String> getMostRecentTimestamp() {
return getAllSignatureSetKeyTimestamps().stream()
.max(String::compareTo);
@@ -25,8 +25,10 @@
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.stream.IntStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* @author Myrle Krantz
@@ -71,21 +73,24 @@ private boolean matchesHelper(final String servletPath, final String method,
final boolean opMatches = allowedOperation.containsHttpMethod(method);
final String[] requestPathSegments = servletPath.split("/");

if (servletPathSegmentMatchers.size() == requestPathSegments.length + 1)
return lastSegmentIsStarSegment(servletPathSegmentMatchers);

if (servletPathSegmentMatchers.size() > requestPathSegments.length)
if (servletPathSegmentMatchers.size() > requestPathSegments.length + 1)
return false;

if (servletPathSegmentMatchers.size() == requestPathSegments.length + 1)
if (!lastSegmentIsStarSegment(servletPathSegmentMatchers))
return false;

if (servletPathSegmentMatchers.size() < requestPathSegments.length)
return lastSegmentIsStarSegment(servletPathSegmentMatchers);
if (!lastSegmentIsStarSegment(servletPathSegmentMatchers))
return false;

final boolean aNonMappableSegmentExistsInServletPath =
IntStream.range(0, servletPathSegmentMatchers.size())
.filter(i -> !segmentMatcher.test(servletPathSegmentMatchers.get(i), requestPathSegments[i]))
.findFirst().isPresent();
final Optional<Integer> indexOfFirstNonMappableSegment =
Stream.iterate(0, n -> n + 1)
.limit(Math.min(servletPathSegmentMatchers.size(), requestPathSegments.length))
.filter(i -> !segmentMatcher.test(servletPathSegmentMatchers.get(i), requestPathSegments[i]))
.findFirst();

return opMatches && !aNonMappableSegmentExistsInServletPath;
return opMatches && !indexOfFirstNonMappableSegment.isPresent();
}

private static boolean lastSegmentIsStarSegment(
@@ -106,4 +111,12 @@ private static boolean lastSegmentIsStarSegment(
@Override public int hashCode() {
return Objects.hash(servletPathSegmentMatchers, allowedOperation);
}

@Override
public String toString() {
return "ApplicationPermission{" +
"servletPathSegmentMatchers='" + servletPathSegmentMatchers.stream().map(PermissionSegmentMatcher::getPermissionSegment).collect(Collectors.joining("/")) +
"', allowedOperation=" + allowedOperation +
'}';
}
}

0 comments on commit 4192aa8

Please sign in to comment.