Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

Commit

Permalink
Bugfix/handle large policy and metadata collections (#44)
Browse files Browse the repository at this point in the history
* Attempt to handle tokens that have large enough policy and metadata collections that it causes KMS to fail to encrypt.
  • Loading branch information
fieldju authored May 22, 2017
1 parent 30c683f commit c82b116
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import com.amazonaws.services.kms.model.EncryptResult;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
Expand Down Expand Up @@ -87,6 +89,7 @@ public class AuthenticationService {
public static final String IAM_TOKEN_TTL_OVERRIDE = "cms.iam.token.ttl.override";
public static final String LOOKUP_SELF_POLICY = "lookup-self";
public static final String DEFAULT_TOKEN_TTL = "1h";
public static final int KMS_SIZE_LIMIT = 4096;

private final SafeDepositBoxDao safeDepositBoxDao;
private final AwsIamRoleDao awsIamRoleDao;
Expand Down Expand Up @@ -235,7 +238,7 @@ private IamRoleAuthResponse authenticate(IamPrincipalCredentials credentials, Ma
.setTtl(iamTokenTTL)
.setNoDefaultPolicy(true);

final VaultAuthResponse authResponse = vaultAdminClient.createOrphanToken(tokenAuthRequest);
VaultAuthResponse authResponse = vaultAdminClient.createOrphanToken(tokenAuthRequest);

byte[] authResponseJson;
try {
Expand All @@ -247,13 +250,70 @@ private IamRoleAuthResponse authenticate(IamPrincipalCredentials credentials, Ma
.withExceptionMessage("Failed to write IAM role authentication response as JSON for encrypting.")
.build();
}

authResponseJson = validateAuthPayloadSizeAndTruncateIfLargerThanMaxKmsSupportedSize(authResponseJson,
authResponse, credentials.getIamPrincipalArn());

final byte[] encryptedAuthResponse = encrypt(credentials.getRegion(), keyId, authResponseJson);

IamRoleAuthResponse iamRoleAuthResponse = new IamRoleAuthResponse();
iamRoleAuthResponse.setAuthData(Base64.encodeBase64String(encryptedAuthResponse));
return iamRoleAuthResponse;
}

/**
* if the metadata and policies make the token too big to encrypt with KMS we can as a stop gap trim the metadata
* and policies from the token.
*
* This information is stored in Vault and can be fetched by the client with a look-up self call
*
* @param authResponseJson The current serialized auth payload
* @param authResponse The response object, with the original policies and metadata
* @param iamPrincipal The calling iam principal
* @return a serialized auth payload that KMS can encrypt
*/
protected byte[] validateAuthPayloadSizeAndTruncateIfLargerThanMaxKmsSupportedSize(byte[] authResponseJson,
VaultAuthResponse authResponse,
String iamPrincipal) {

if (authResponseJson.length <= KMS_SIZE_LIMIT) {
return authResponseJson;
}

String originalMetadata = "unknown";
String originalPolicies = "unknown";
try {
originalMetadata = objectMapper.writeValueAsString(authResponse.getMetadata());
originalPolicies = objectMapper.writeValueAsString(authResponse.getPolicies());
} catch (JsonProcessingException e) {
logger.warn("Failed to serialize original metadata or policies for token generated for IAM Principal: {}", iamPrincipal, e);
}

authResponse.setMetadata(ImmutableMap.of("_truncated", "true"));
authResponse.setPolicies(ImmutableSet.of("_truncated"));

logger.warn(
"The auth token has length: {} which is > {} KMS cannot encrypt it, truncating auth payload by removing policies and metadata " +
"original metadata: {} " +
"original policies: {}",
authResponseJson.length,
KMS_SIZE_LIMIT,
originalMetadata,
originalPolicies
);

try {
return objectMapper.writeValueAsBytes(authResponse);
} catch (JsonProcessingException e) {
throw ApiException.newBuilder()
.withApiErrors(DefaultApiError.INTERNAL_SERVER_ERROR)
.withExceptionCause(e)
.withExceptionMessage("Failed to write IAM role authentication response as JSON for encrypting.")
.build();
}

}

/**
* Since tokens are immutable, there are certain situations where refreshing the token used by a user is
* necessary. Anytime permissions change, this is required to reflect that to the user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package com.nike.cerberus.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nike.cerberus.auth.connector.AuthConnector;
import com.nike.cerberus.aws.KmsClientFactory;
Expand All @@ -26,25 +27,29 @@
import com.nike.cerberus.record.AwsIamRoleKmsKeyRecord;
import com.nike.cerberus.record.AwsIamRoleRecord;
import com.nike.cerberus.security.VaultAuthPrincipal;
import com.nike.cerberus.server.config.CmsConfig;
import com.nike.cerberus.util.AwsIamRoleArnParser;
import com.nike.cerberus.util.DateTimeSupplier;
import com.nike.vault.client.VaultAdminClient;
import org.joda.time.DateTime;
import com.nike.vault.client.model.VaultAuthResponse;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import static org.junit.Assert.assertEquals;

import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -76,24 +81,24 @@ public class AuthenticationServiceTest {
@Mock
private VaultPolicyService vaultPolicyService;

@Mock
private ObjectMapper objectMapper;

// @Named(ADMIN_GROUP_PROPERTY) final String adminGroup,

@Mock
private DateTimeSupplier dateTimeSupplier;

@Mock
private AwsIamRoleArnParser awsIamRoleArnParser;

@InjectMocks
private AuthenticationService authenticationService;

@Before
public void setup() {

initMocks(this);
objectMapper = CmsConfig.configureObjectMapper();
authenticationService = new AuthenticationService(safeDepositBoxDao,
awsIamRoleDao, authConnector, kmsService, kmsClientFactory,
vaultAdminClient, vaultPolicyService, objectMapper, "foo",
dateTimeSupplier, awsIamRoleArnParser);
}

@Test
Expand Down Expand Up @@ -153,4 +158,43 @@ public void tests_that_getKeyId_only_validates_kms_policy_one_time_within_interv
verify(kmsService, times(1)).validatePolicy(awsIamRoleKmsKeyRecord, principalArn);
}

@Test
public void tests_that_validateAuthPayloadSizeAndTruncateIfLargerThanMaxKmsSupportedSize_returns_the_original_payload_if_the_size_can_be_encrypted_by_kms() throws JsonProcessingException {
VaultAuthResponse response = new VaultAuthResponse()
.setClientToken(UUID.randomUUID().toString())
.setLeaseDuration(3600)
.setMetadata(new HashMap<>())
.setPolicies(new HashSet<>())
.setRenewable(false);

byte[] serializedAuth = new ObjectMapper().writeValueAsBytes(response);

byte[] actual = authenticationService.validateAuthPayloadSizeAndTruncateIfLargerThanMaxKmsSupportedSize(serializedAuth, response, "foo");

assertEquals(serializedAuth, actual);
}

@Test
public void tests_that_validateAuthPayloadSizeAndTruncateIfLargerThanMaxKmsSupportedSize_returns_a_truncated_payload_if_the_size_cannot_be_encrypted_by_kms() throws JsonProcessingException {
Map<String, String> meta = new HashMap<>();
Set<String> policies = new HashSet<>();
for (int i = 0; i < 100; i++) {
policies.add(RandomStringUtils.random(25));
}

VaultAuthResponse response = new VaultAuthResponse()
.setClientToken(UUID.randomUUID().toString())
.setLeaseDuration(3600)
.setMetadata(meta)
.setPolicies(policies)
.setRenewable(false);

byte[] serializedAuth = new ObjectMapper().writeValueAsBytes(response);
assertTrue(serializedAuth.length > AuthenticationService.KMS_SIZE_LIMIT);

byte[] actual = authenticationService.validateAuthPayloadSizeAndTruncateIfLargerThanMaxKmsSupportedSize(serializedAuth, response, "foo");

assertNotEquals(serializedAuth, actual);
assertTrue(actual.length < AuthenticationService.KMS_SIZE_LIMIT);
}
}

0 comments on commit c82b116

Please sign in to comment.