This repository has been archived by the owner on Jan 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Clean up inactive or orphaned records (#48)
* Add admin endpoint to clean up KMS keys + IAM roles
- Loading branch information
Showing
13 changed files
with
509 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,6 @@ | |
# limitations under the License. | ||
# | ||
|
||
version=0.20.3 | ||
version=0.21.0 | ||
groupId=com.nike.cerberus | ||
artifactId=cms |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
src/main/java/com/nike/cerberus/domain/CleanUpRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* Copyright (c) 2016 Nike, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.nike.cerberus.domain; | ||
|
||
/** | ||
* Clean up KMS key request. | ||
*/ | ||
public class CleanUpRequest { | ||
|
||
private Integer kmsExpirationPeriodInDays; | ||
|
||
public Integer getKmsExpirationPeriodInDays() { | ||
return kmsExpirationPeriodInDays; | ||
} | ||
|
||
public CleanUpRequest setKmsExpirationPeriodInDays(Integer kmsExpirationPeriodInDays) { | ||
this.kmsExpirationPeriodInDays = kmsExpirationPeriodInDays; | ||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
src/main/java/com/nike/cerberus/endpoints/admin/CleanUpInactiveOrOrphanedRecords.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Copyright (c) 2017 Nike, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.nike.cerberus.endpoints.admin; | ||
|
||
import com.google.inject.Inject; | ||
import com.nike.cerberus.domain.CleanUpRequest; | ||
import com.nike.cerberus.endpoints.AdminStandardEndpoint; | ||
import com.nike.cerberus.security.VaultAuthPrincipal; | ||
import com.nike.cerberus.service.CleanUpService; | ||
import com.nike.riposte.server.http.RequestInfo; | ||
import com.nike.riposte.server.http.ResponseInfo; | ||
import com.nike.riposte.server.http.impl.FullResponseInfo; | ||
import com.nike.riposte.util.AsyncNettyHelper; | ||
import com.nike.riposte.util.Matcher; | ||
import io.netty.channel.ChannelHandlerContext; | ||
import io.netty.handler.codec.http.HttpMethod; | ||
import io.netty.handler.codec.http.HttpResponseStatus; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import javax.ws.rs.core.SecurityContext; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.Executor; | ||
|
||
/** | ||
* Cleans up inactive or orphaned KMS keys and IAM roles. | ||
* | ||
* This is required because orphaned CMKs, KMS key DB records, and IAM role DB records are created when an SDB is deleted. | ||
* Thus this endpoint exists to clean up those already existing orphaned records in the DB, as well as orphaned records | ||
* made from future SDB deletions. | ||
* | ||
* The reason that this clean up is not done at the time of SDB deletion is to lessen code complexity and give control | ||
* to the administrators over when KMS keys are deleted. | ||
*/ | ||
public class CleanUpInactiveOrOrphanedRecords extends AdminStandardEndpoint<CleanUpRequest, Void> { | ||
|
||
private final Logger log = LoggerFactory.getLogger(getClass()); | ||
|
||
private static final int DEFAULT_KMS_KEY_INACTIVE_AFTER_N_DAYS = 30; | ||
|
||
private final CleanUpService cleanUpService; | ||
|
||
@Inject | ||
public CleanUpInactiveOrOrphanedRecords(CleanUpService cleanUpService) { | ||
this.cleanUpService = cleanUpService; | ||
} | ||
|
||
@Override | ||
public CompletableFuture<ResponseInfo<Void>> doExecute(final RequestInfo<CleanUpRequest> request, | ||
final Executor longRunningTaskExecutor, | ||
final ChannelHandlerContext ctx, | ||
final SecurityContext securityContext) { | ||
return CompletableFuture.supplyAsync( | ||
AsyncNettyHelper.supplierWithTracingAndMdc(() -> cleanUp(request, securityContext), ctx), | ||
longRunningTaskExecutor | ||
); | ||
} | ||
|
||
private FullResponseInfo<Void> cleanUp(final RequestInfo<CleanUpRequest> request, | ||
final SecurityContext securityContext) { | ||
|
||
final VaultAuthPrincipal vaultAuthPrincipal = (VaultAuthPrincipal) securityContext.getUserPrincipal(); | ||
final String principal = vaultAuthPrincipal.getName(); | ||
|
||
log.info("Clean Up Event: the principal {} is attempting to clean up kms keys", principal); | ||
|
||
Integer expirationPeriodInDays = request.getContent().getKmsExpirationPeriodInDays(); | ||
int kmsKeysInactiveAfterNDays = (expirationPeriodInDays == null) ? DEFAULT_KMS_KEY_INACTIVE_AFTER_N_DAYS : expirationPeriodInDays; | ||
|
||
cleanUpService.cleanUpInactiveAndOrphanedKmsKeys(kmsKeysInactiveAfterNDays); | ||
cleanUpService.cleanUpOrphanedIamRoles(); | ||
|
||
return ResponseInfo.<Void>newBuilder() | ||
.withHttpStatusCode(HttpResponseStatus.NO_CONTENT.code()) | ||
.build(); | ||
} | ||
|
||
@Override | ||
public Matcher requestMatcher() { | ||
return Matcher.match("/v1/cleanup", HttpMethod.PUT); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
130 changes: 130 additions & 0 deletions
130
src/main/java/com/nike/cerberus/service/CleanUpService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
* Copyright (c) 2017 Nike, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
*/ | ||
|
||
package com.nike.cerberus.service; | ||
|
||
import com.google.inject.Inject; | ||
import com.google.inject.Singleton; | ||
import com.nike.cerberus.dao.AwsIamRoleDao; | ||
import com.nike.cerberus.record.AwsIamRoleKmsKeyRecord; | ||
import com.nike.cerberus.record.AwsIamRoleRecord; | ||
import com.nike.cerberus.util.DateTimeSupplier; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.time.OffsetDateTime; | ||
import java.util.List; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import static com.nike.cerberus.service.KmsService.SOONEST_A_KMS_KEY_CAN_BE_DELETED; | ||
|
||
/** | ||
* Service to clean up inactive and orphaned KMS keys | ||
*/ | ||
@Singleton | ||
public class CleanUpService { | ||
|
||
private final Logger logger = LoggerFactory.getLogger(getClass()); | ||
|
||
private static final int DEFAULT_SLEEP_BETWEEN_KMS_CALLS = 10; // in seconds | ||
|
||
private final KmsService kmsService; | ||
|
||
private final AwsIamRoleDao awsIamRoleDao; | ||
|
||
private final DateTimeSupplier dateTimeSupplier; | ||
|
||
@Inject | ||
public CleanUpService(KmsService kmsService, | ||
AwsIamRoleDao awsIamRoleDao, | ||
DateTimeSupplier dateTimeSupplier) { | ||
this.kmsService = kmsService; | ||
this.awsIamRoleDao = awsIamRoleDao; | ||
this.dateTimeSupplier = dateTimeSupplier; | ||
} | ||
|
||
/** | ||
* Delete all AWS KMS keys and DB records for KMS keys that have not been used recently | ||
* or are no longer associated with an SDB. | ||
*/ | ||
public void cleanUpInactiveAndOrphanedKmsKeys(final int kmsKeysInactiveAfterNDays) { | ||
|
||
cleanUpInactiveAndOrphanedKmsKeys(kmsKeysInactiveAfterNDays, DEFAULT_SLEEP_BETWEEN_KMS_CALLS); | ||
} | ||
|
||
/** | ||
* Delete all AWS KMS keys and DB records for KMS keys that have not been used recently | ||
* or are no longer associated with an SDB. | ||
* @param kmsKeysInactiveAfterNDays - Consider KMS keys to be inactive after 'n' number of days | ||
* @param sleepInSeconds - Sleep for 'n' seconds between AWS calls, to keep from exceeding the API limit | ||
*/ | ||
protected void cleanUpInactiveAndOrphanedKmsKeys(final int kmsKeysInactiveAfterNDays, final int sleepInSeconds) { | ||
|
||
// get orphaned and inactive kms keys (not used in 'n' days) | ||
final OffsetDateTime inactiveDateTime = dateTimeSupplier.get().minusDays(kmsKeysInactiveAfterNDays); | ||
final List<AwsIamRoleKmsKeyRecord> inactiveAndOrphanedKmsKeys = awsIamRoleDao.getInactiveOrOrphanedKmsKeys(inactiveDateTime); | ||
|
||
if (inactiveAndOrphanedKmsKeys.isEmpty()) { | ||
logger.info("No keys to clean up."); | ||
} else { | ||
|
||
// delete inactive and orphaned kms key records from DB | ||
logger.info("Cleaning up orphaned or inactive KMS keys..."); | ||
inactiveAndOrphanedKmsKeys.forEach(kmsKeyRecord -> { | ||
try { | ||
logger.info("Deleting orphaned or inactive KMS key: id={}, region={}, lastValidated={}", | ||
kmsKeyRecord.getAwsKmsKeyId(), kmsKeyRecord.getAwsRegion(), kmsKeyRecord.getLastValidatedTs()); | ||
|
||
kmsService.scheduleKmsKeyDeletion(kmsKeyRecord.getAwsKmsKeyId(), kmsKeyRecord.getAwsRegion(), SOONEST_A_KMS_KEY_CAN_BE_DELETED); | ||
awsIamRoleDao.deleteKmsKeyById(kmsKeyRecord.getId()); | ||
|
||
TimeUnit.SECONDS.sleep(sleepInSeconds); | ||
} catch (InterruptedException ie) { | ||
logger.error("Timeout between KMS key deletion was interrupted"); | ||
Thread.currentThread().interrupt(); | ||
} catch (Exception e) { | ||
logger.error("There was a problem deleting KMS key with id: {}, region: {}", | ||
kmsKeyRecord.getAwsIamRoleId(), | ||
kmsKeyRecord.getAwsRegion()); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Delete all IAM role records that are no longer associated with an SDB. | ||
*/ | ||
public void cleanUpOrphanedIamRoles() { | ||
|
||
// get orphaned iam role ids | ||
final List<AwsIamRoleRecord> orphanedIamRoleIds = awsIamRoleDao.getOrphanedIamRoles(); | ||
|
||
// delete orphaned iam role records from DB | ||
orphanedIamRoleIds.forEach(awsIamRoleRecord -> { | ||
try { | ||
logger.info("Deleting orphaned IAM role: ARN={}, lastUpdated={}", | ||
awsIamRoleRecord.getAwsIamRoleArn(), | ||
awsIamRoleRecord.getLastUpdatedTs()); | ||
awsIamRoleDao.deleteIamRoleById(awsIamRoleRecord.getId()); | ||
} catch(Exception e) { | ||
logger.error("There was a problem deleting orphaned IAM role with ARN: {}", | ||
awsIamRoleRecord.getAwsIamRoleArn()); | ||
} | ||
}); | ||
} | ||
} | ||
|
Oops, something went wrong.