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

Commit

Permalink
Avoid hitting API limit on KMS policy validation
Browse files Browse the repository at this point in the history
  • Loading branch information
sdford committed Apr 27, 2017
1 parent 6aa07ab commit e62de48
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 26 deletions.
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,20 @@ That will setup the default policy and generate a token for CMS and output:

There are a few parameters that need to be configured for CMS to run properly, they are defined in this table.

property | required | notes
--------------------------- | -------- | ----------
JDBC.url | Yes | The JDBC url for the mysql db
JDBC.username | Yes | The JDBC user name for the mysql db
JDBC.password | Yes | The JDBC JDBC.password for the mysql db
root.user.arn | Yes | The arn for the root AWS user, needed to make the KMS keys deletable.
admin.role.arn | Yes | The arn for an AWS user, needed to make the KMS keys deletable.
cms.role.arn | Yes | The arn for the Instance profile for CMS instances, so they can admin KMS keys that they create.
cms.admin.group | Yes | Group that user can be identified by to get admin privileges, currently this just enables users to access `/v1/metadata` see API.md
cms.admin.roles | No | Comma seperated list of ARNs that can auth and access admin endpoints.
cms.auth.connector | Yes | The user authentication connector implementation to use for user auth.
cms.user.token.ttl.override | No | By default user tokens are created with a TTL of 1h, you can override that with this param
cms.iam.token.ttl.override | No | By default IAM tokens are created with a TTL of 1h, you can override that with this param
property | required | notes
--------------------------- | -------- | ----------
JDBC.url | Yes | The JDBC url for the mysql db
JDBC.username | Yes | The JDBC user name for the mysql db
JDBC.password | Yes | The JDBC JDBC.password for the mysql db
root.user.arn | Yes | The arn for the root AWS user, needed to make the KMS keys deletable.
admin.role.arn | Yes | The arn for an AWS user, needed to make the KMS keys deletable.
cms.role.arn | Yes | The arn for the Instance profile for CMS instances, so they can admin KMS keys that they create.
cms.admin.group | Yes | Group that user can be identified by to get admin privileges, currently this just enables users to access `/v1/metadata` see API.md
cms.admin.roles | No | Comma seperated list of ARNs that can auth and access admin endpoints.
cms.auth.connector | Yes | The user authentication connector implementation to use for user auth.
cms.user.token.ttl.override | No | By default user tokens are created with a TTL of 1h, you can override that with this param
cms.iam.token.ttl.override | No | By default IAM tokens are created with a TTL of 1h, you can override that with this param
cms.kms.policy.validation.interval.millis.override | No | By default CMS validates KMS key policies no more than once per minute, you can override that with this param

For local dev see `Running CMS Locally`.

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
# limitations under the License.
#

version=0.17.0
version=0.18.0
groupId=com.nike.cerberus
artifactId=cms
4 changes: 2 additions & 2 deletions gradle/develop.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import org.apache.tools.ant.taskdefs.condition.Os
import groovyx.net.http.RESTClient
import static groovyx.net.http.ContentType.*

def dashboardRelease = 'v0.12.0'
def vaultVersion = "0.6.4"
def dashboardRelease = 'v1.0.0'
def vaultVersion = "0.7.0"

buildscript {
apply from: file('gradle/buildscript.gradle'), to: buildscript
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/nike/cerberus/dao/AwsIamRoleDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ public Optional<AwsIamRoleKmsKeyRecord> getKmsKey(final String awsIamRoleId, fin
public int createIamRoleKmsKey(final AwsIamRoleKmsKeyRecord record) {
return awsIamRoleMapper.createIamRoleKmsKey(record);
}

public int updateIamRoleKmsKey(final AwsIamRoleKmsKeyRecord record) {
return awsIamRoleMapper.updateIamRoleKmsKey(record);
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/nike/cerberus/mapper/AwsIamRoleMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ int deleteIamRolePermission(@Param("safeDepositBoxId") String safeDepositBoxId,
List<AwsIamRolePermissionRecord> getIamRolePermissions(@Param("safeDepositBoxId") String safeDepositBoxId);

int deleteIamRolePermissions(@Param("safeDepositBoxId") String safeDepositBoxId);

int updateIamRoleKmsKey(@Param("record") AwsIamRoleKmsKeyRecord record);
}
16 changes: 14 additions & 2 deletions src/main/java/com/nike/cerberus/record/AwsIamRoleKmsKeyRecord.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class AwsIamRoleKmsKeyRecord {

private OffsetDateTime lastUpdatedTs;

private OffsetDateTime lastValidatedTs;

public String getId() {
return id;
}
Expand Down Expand Up @@ -112,6 +114,15 @@ public AwsIamRoleKmsKeyRecord setLastUpdatedTs(OffsetDateTime lastUpdatedTs) {
return this;
}

public OffsetDateTime getLastValidatedTs() {
return lastValidatedTs;
}

public AwsIamRoleKmsKeyRecord setLastValidatedTs(OffsetDateTime lastValidatedTs) {
this.lastValidatedTs = lastValidatedTs;
return this;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -124,11 +135,12 @@ public boolean equals(Object o) {
Objects.equals(createdBy, that.createdBy) &&
Objects.equals(lastUpdatedBy, that.lastUpdatedBy) &&
Objects.equals(createdTs, that.createdTs) &&
Objects.equals(lastUpdatedTs, that.lastUpdatedTs);
Objects.equals(lastUpdatedTs, that.lastUpdatedTs) &&
Objects.equals(lastValidatedTs, that.lastValidatedTs);
}

@Override
public int hashCode() {
return Objects.hash(id, awsIamRoleId, awsRegion, awsKmsKeyId, createdBy, lastUpdatedBy, createdTs, lastUpdatedTs);
return Objects.hash(id, awsIamRoleId, awsRegion, awsKmsKeyId, createdBy, lastUpdatedBy, createdTs, lastUpdatedTs, lastValidatedTs);
}
}
33 changes: 28 additions & 5 deletions src/main/java/com/nike/cerberus/service/AuthenticationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,14 @@
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.joda.time.Interval;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
Expand All @@ -73,13 +78,17 @@
@Singleton
public class AuthenticationService {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

public static final String SYSTEM_USER = "system";
public static final String ADMIN_GROUP_PROPERTY = "cms.admin.group";
public static final String ADMIN_IAM_ROLES_PROPERTY = "cms.admin.roles";
public static final String USER_TOKEN_TTL_OVERRIDE = "cms.user.token.ttl.override";
public static final String IAM_TOKEN_TTL_OVERRIDE = "cms.iam.token.ttl.override";
public static final String KMS_POLICY_VALIDATION_INTERVAL_OVERRIDE = "cms.kms.policy.validation.interval.millis.override";
public static final String LOOKUP_SELF_POLICY = "lookup-self";
public static final String DEFAULT_TOKEN_TTL = "1h";
public static final Integer DEFAULT_KMS_VALIDATION_INTERVAL = 6000; // in milliseconds

private final SafeDepositBoxDao safeDepositBoxDao;
private final AwsIamRoleDao awsIamRoleDao;
Expand Down Expand Up @@ -107,6 +116,10 @@ public class AuthenticationService {
@Named(IAM_TOKEN_TTL_OVERRIDE)
String iamTokenTTL = DEFAULT_TOKEN_TTL;

@Inject(optional=true)
@Named(KMS_POLICY_VALIDATION_INTERVAL_OVERRIDE)
Integer kmsKeyPolicyValidationInterval = DEFAULT_KMS_VALIDATION_INTERVAL;

@Inject
public AuthenticationService(final SafeDepositBoxDao safeDepositBoxDao,
final AwsIamRoleDao awsIamRoleDao,
Expand Down Expand Up @@ -203,7 +216,7 @@ public IamRoleAuthResponse authenticate(IamPrincipalCredentials credentials) {
return authenticate(credentials, vaultAuthPrincipalMetadata);
}

public IamRoleAuthResponse authenticate(IamPrincipalCredentials credentials, Map<String, String> vaultAuthPrincipalMetadata) {
private IamRoleAuthResponse authenticate(IamPrincipalCredentials credentials, Map<String, String> vaultAuthPrincipalMetadata) {
final String keyId;
try {
keyId = getKeyId(credentials);
Expand Down Expand Up @@ -360,7 +373,7 @@ private Set<String> buildPolicySet(final String iamRoleArn) {
* @param credentials IAM role credentials
* @return KMS Key id
*/
private String getKeyId(IamPrincipalCredentials credentials) {
protected String getKeyId(IamPrincipalCredentials credentials) {
final Optional<AwsIamRoleRecord> iamRole = awsIamRoleDao.getIamRole(credentials.getIamPrincipalArn());

if (!iamRole.isPresent()) {
Expand All @@ -374,14 +387,24 @@ private String getKeyId(IamPrincipalCredentials credentials) {
final Optional<AwsIamRoleKmsKeyRecord> kmsKey = awsIamRoleDao.getKmsKey(iamRole.get().getId(), credentials.getRegion());

final String kmsKeyId;
final AwsIamRoleKmsKeyRecord kmsKeyRecord;
final OffsetDateTime now = dateTimeSupplier.get();

if (!kmsKey.isPresent()) {
kmsKeyId = kmsService.provisionKmsKey(iamRole.get().getId(), credentials.getIamPrincipalArn(),
credentials.getRegion(), SYSTEM_USER, dateTimeSupplier.get());
credentials.getRegion(), SYSTEM_USER, now);
} else {
kmsKeyId = kmsKey.get().getAwsKmsKeyId();
kmsKeyRecord = kmsKey.get();
kmsKeyId = kmsKeyRecord.getAwsKmsKeyId();
String keyRegion = credentials.getRegion();
kmsService.validatePolicy(kmsKeyId, credentials.getIamPrincipalArn(), keyRegion);
if (ChronoUnit.MILLIS.between(kmsKeyRecord.getLastValidatedTs(), now) >= kmsKeyPolicyValidationInterval) {
try {
kmsService.validatePolicy(kmsKeyId, credentials.getIamPrincipalArn(), keyRegion);
kmsService.updateKmsKey(kmsKeyRecord, SYSTEM_USER, now, now);
} catch (ApiException ae) {
logger.warn("Could not validate KMS policy. API limit may have been reached for validate call.");
}
}
}

return kmsKeyId;
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/nike/cerberus/service/KmsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import javax.inject.Inject;
import javax.inject.Singleton;
import java.time.OffsetDateTime;
import java.util.Optional;

/**
* Abstracts interactions with the AWS KMS service.
Expand Down Expand Up @@ -111,12 +112,39 @@ public String provisionKmsKey(final String iamRoleId,
awsIamRoleKmsKeyRecord.setLastUpdatedBy(user);
awsIamRoleKmsKeyRecord.setCreatedTs(dateTime);
awsIamRoleKmsKeyRecord.setLastUpdatedTs(dateTime);
awsIamRoleKmsKeyRecord.setLastValidatedTs(dateTime);

awsIamRoleDao.createIamRoleKmsKey(awsIamRoleKmsKeyRecord);

return result.getKeyMetadata().getArn();
}

@Transactional
public void updateKmsKey(final AwsIamRoleKmsKeyRecord awsIamRoleKmsKeyRecord,
final String user,
final OffsetDateTime dateTime,
final OffsetDateTime lastValidatedTs) {
final Optional<AwsIamRoleKmsKeyRecord> kmsKey =
awsIamRoleDao.getKmsKey(awsIamRoleKmsKeyRecord.getAwsIamRoleId(), awsIamRoleKmsKeyRecord.getAwsRegion());

if (!kmsKey.isPresent()) {
throw ApiException.newBuilder()
.withApiErrors(DefaultApiError.ENTITY_NOT_FOUND)
.withExceptionMessage("Unable to update a KMS key that does not exist.")
.build();
}

AwsIamRoleKmsKeyRecord kmsKeyRecord = kmsKey.get();

AwsIamRoleKmsKeyRecord updatedKmsKeyRecord = new AwsIamRoleKmsKeyRecord();
updatedKmsKeyRecord.setAwsIamRoleId(kmsKeyRecord.getAwsIamRoleId());
updatedKmsKeyRecord.setLastUpdatedBy(user);
updatedKmsKeyRecord.setLastUpdatedTs(dateTime);
updatedKmsKeyRecord.setLastValidatedTs(lastValidatedTs);
updatedKmsKeyRecord.setAwsRegion(kmsKeyRecord.getAwsRegion());
awsIamRoleDao.updateIamRoleKmsKey(updatedKmsKeyRecord);
}

protected String getAliasName(String awsIamRoleKmsKeyId) {
return String.format(KMS_ALIAS_FORMAT, awsIamRoleKmsKeyId);
}
Expand Down
22 changes: 19 additions & 3 deletions src/main/resources/com/nike/cerberus/mapper/AwsIamRoleMapper.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
CREATED_BY,
LAST_UPDATED_BY,
CREATED_TS,
LAST_UPDATED_TS
LAST_UPDATED_TS,
LAST_VALIDATED_TS
FROM
AWS_IAM_ROLE_KMS_KEY
WHERE
Expand All @@ -75,7 +76,8 @@
CREATED_BY,
LAST_UPDATED_BY,
CREATED_TS,
LAST_UPDATED_TS
LAST_UPDATED_TS,
LAST_VALIDATED_TS
)
VALUES (
#{record.id},
Expand All @@ -85,7 +87,8 @@
#{record.createdBy},
#{record.lastUpdatedBy},
#{record.createdTs},
#{record.lastUpdatedTs}
#{record.lastUpdatedTs},
#{record.lastValidatedTs}
)
</insert>

Expand Down Expand Up @@ -160,6 +163,19 @@
AWS_IAM_ROLE_ID = #{record.awsIamRoleId}
</update>

<update id="updateIamRoleKmsKey" parameterType="AwsIamRoleKmsKeyRecord">
UPDATE
AWS_IAM_ROLE_KMS_KEY
SET
LAST_UPDATED_BY = #{record.lastUpdatedBy},
LAST_UPDATED_TS = #{record.lastUpdatedTs},
LAST_VALIDATED_TS = #{record.lastValidatedTs}
WHERE
AWS_IAM_ROLE_ID = #{record.awsIamRoleId}
AND
AWS_REGION = #{record.awsRegion}
</update>

<delete id="deleteIamRolePermission">
DELETE FROM
AWS_IAM_ROLE_PERMISSIONS
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE AWS_IAM_ROLE_KMS_KEY
ADD COLUMN LAST_VALIDATED_TS DATETIME NOT NULL;

UPDATE AWS_IAM_ROLE_KMS_KEY
SET LAST_VALIDATED_TS = LAST_UPDATED_TS;
10 changes: 10 additions & 0 deletions src/test/java/com/nike/cerberus/dao/AwsIamRoleDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,14 @@ public void createIamRoleKmsKey_returns_record_count() {

assertThat(actualCount).isEqualTo(recordCount);
}

@Test
public void updateIamRoleKmsKey_returns_record_count() {
final int recordCount = 1;
when(awsIamRoleMapper.updateIamRoleKmsKey(awsIamRoleKmsKeyRecord)).thenReturn(recordCount);

final int actualCount = subject.updateIamRoleKmsKey(awsIamRoleKmsKeyRecord);

assertThat(actualCount).isEqualTo(recordCount);
}
}
Loading

0 comments on commit e62de48

Please sign in to comment.