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

Commit

Permalink
Create command to allow Cerberus operators to speficy allowed IAM pri…
Browse files Browse the repository at this point in the history
…ncipals that can use the CMK's associated with backups
  • Loading branch information
fieldju committed Sep 12, 2017
2 parents d9c16f2 + 84549c0 commit 1430104
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 35 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@

group=com.nike
artifactId=cerberus-lifecycle-cli
version=3.1.1
version=3.2.0
2 changes: 2 additions & 0 deletions src/main/java/com/nike/cerberus/cli/CerberusRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import com.nike.cerberus.command.core.RestoreCerberusBackupCommand;
import com.nike.cerberus.command.core.UpdateStackCommand;
import com.nike.cerberus.command.core.UploadCertFilesCommand;
import com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand;
import com.nike.cerberus.command.core.WhitelistCidrForVpcAccessCommand;
import com.nike.cerberus.command.dashboard.PublishDashboardCommand;
import com.nike.cerberus.command.gateway.CreateCloudFrontLogProcessingLambdaConfigCommand;
Expand Down Expand Up @@ -202,6 +203,7 @@ private void registerAllCommands() {
registerCommand(new UpdateCmsConfigCommand());
registerCommand(new RollingRebootWithHealthCheckCommand());
registerCommand(new CreateCerberusBackupCommand());
registerCommand(new SetBackupAdminPrincipalsCommand());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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.command.core;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.nike.cerberus.command.Command;
import com.nike.cerberus.operation.Operation;
import com.nike.cerberus.operation.core.SetBackupAdminPrincipalsOperation;

import java.util.ArrayList;
import java.util.List;

import static com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand.COMMAND_NAME;

/**
* Command to update which principals besides for the root account will have permissions to use the backup cmk,
* AKA create and restore backups.
*/
@Parameters(
commandNames = COMMAND_NAME,
commandDescription = "Update the IAM Principals that are allowed to create and restore backups. " +
"This command automatically adds by default the root user and configured admin user arn, " +
"but you can use this command to add iam principals such as CI systems and additional user principals " +
"that will have access to encrypt and decrypt backup data"
)
public class SetBackupAdminPrincipalsCommand implements Command {

public static final String COMMAND_NAME = "set-backup-principals";

public static final String PRINCIPAL_LONG_ARG = "--principal";
public static final String PRINCIPAL_SHORT_ARG = "-p";

@Parameter(
names = {
PRINCIPAL_LONG_ARG,
PRINCIPAL_SHORT_ARG
},
description = "One or more additional principals to grant access to."
)
private List<String> additionalPrincipals = new ArrayList<>();

public List<String> getAdditionalPrincipals() {
return additionalPrincipals;
}

@Override
public String getCommandName() {
return COMMAND_NAME;
}

@Override
public Class<? extends Operation<?>> getOperationClass() {
return SetBackupAdminPrincipalsOperation.class;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package com.nike.cerberus.domain.environment;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* General purpose environment data that isn't sensitive.
Expand All @@ -38,6 +40,8 @@ public class Environment {

private Map<String, BackupRegionInfo> regionBackupBucketMap;

private Set<String> backupAdminIamPrincipals;

private String metricsTopicArn;

/**
Expand Down Expand Up @@ -129,6 +133,14 @@ public void setRegionBackupBucketMap(Map<String, BackupRegionInfo> regionBackupB
this.regionBackupBucketMap = regionBackupBucketMap;
}

public Set<String> getBackupAdminIamPrincipals() {
return backupAdminIamPrincipals == null ? new HashSet<>() : backupAdminIamPrincipals;
}

public void setBackupAdminIamPrincipals(Set<String> backupAdminIamPrincipals) {
this.backupAdminIamPrincipals = backupAdminIamPrincipals;
}

public String getMetricsTopicArn() {
return metricsTopicArn;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.CryptoConfiguration;
import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider;
import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest;
import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -47,6 +44,7 @@
import com.google.common.collect.ImmutableMap;
import com.nike.cerberus.client.CerberusAdminClient;
import com.nike.cerberus.client.CerberusAdminClientFactory;
import com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand;
import com.nike.cerberus.domain.cms.SafeDepositBox;
import com.nike.cerberus.command.core.CreateCerberusBackupCommand;
import com.nike.cerberus.domain.EnvironmentMetadata;
Expand All @@ -67,6 +65,7 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -87,15 +86,14 @@ public class CreateCerberusBackupOperation implements Operation<CreateCerberusBa
private final CerberusAdminClient cerberusAdminClient;
private final MetricsService metricsService;
private final EnvironmentMetadata environmentMetadata;
private final AWSSecurityTokenService sts;

private final Map<String, S3StoreService> regionToEncryptedStoreServiceMap = new HashMap<>();

@Inject
public CreateCerberusBackupOperation(CerberusAdminClientFactory cerberusAdminClientFactory,
ConfigStore configStore,
MetricsService metricsService,
EnvironmentMetadata environmentMetadata,
AWSSecurityTokenService sts) {
EnvironmentMetadata environmentMetadata) {

objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
Expand All @@ -109,7 +107,6 @@ public CreateCerberusBackupOperation(CerberusAdminClientFactory cerberusAdminCli
this.configStore = configStore;
this.metricsService = metricsService;
this.environmentMetadata = environmentMetadata;
this.sts = sts;

cerberusAdminClient = cerberusAdminClientFactory.createCerberusAdminClient(
configStore.getCerberusBaseUrl());
Expand Down Expand Up @@ -339,32 +336,19 @@ private String getBackupBucketName(String region) {
}

private String provisionKmsCmkForBackupRegion(String region) {
GetCallerIdentityResult identityResult = sts.getCallerIdentity(new GetCallerIdentityRequest());
String accountId = identityResult.getAccount();
String rootArn = String.format("arn:aws:iam::%s:root", accountId);

String adminRoleArn = configStore.getAccountAdminArn().get();

Policy kmsPolicy = new Policy();
final List<Statement> statements = new LinkedList<>();
// allow the configured admin iam principals all permissions
configStore.getBackupAdminIamPrincipals().forEach( principal -> {
log.debug("Adding principal: {} to the CMK Policy for region {}", principal, region);
statements.add(new Statement(Statement.Effect.Allow)
.withId("Principal " + principal + " Has All Actions")
.withPrincipals(new Principal(AWS_PROVIDER, principal, false))
.withActions(KMSActions.AllKMSActions)
.withResources(new Resource("*")));
});

// allow the root user all permissions
Statement rootUserStatement = new Statement(Statement.Effect.Allow);
rootUserStatement.withId("Root User Has All Actions");
rootUserStatement.withPrincipals(new Principal(AWS_PROVIDER, rootArn, false));
rootUserStatement.withActions(KMSActions.AllKMSActions);
rootUserStatement.withResources(new Resource("*"));

// allow the configured admin user all permissions
Statement adminUserStatement = new Statement(Statement.Effect.Allow);
adminUserStatement.withId("Admin Role Has All Actions");
adminUserStatement.withPrincipals(new Principal(AWS_PROVIDER, adminRoleArn, false));
adminUserStatement.withActions(KMSActions.AllKMSActions);
adminUserStatement.withResources(new Resource("*"));

kmsPolicy.withStatements(
rootUserStatement,
adminUserStatement
);
kmsPolicy.setStatements(statements);

String policyString = kmsPolicy.toJson();

Expand Down Expand Up @@ -394,9 +378,8 @@ private String provisionKmsCmkForBackupRegion(String region) {

@Override
public boolean isRunnable(CreateCerberusBackupCommand command) {
Optional<String> adminIamPrincipalArn = configStore.getAccountAdminArn();
if (! adminIamPrincipalArn.isPresent()) {
log.error("The admin IAM principal must be set for this environment");
if (!configStore.getBackupAdminIamPrincipals().isEmpty()) {
log.error("Backup Admin Principals have not been set please run " + SetBackupAdminPrincipalsCommand.COMMAND_NAME);
return false;
}
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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.operation.core;

import com.amazonaws.auth.policy.Policy;
import com.amazonaws.auth.policy.Principal;
import com.amazonaws.auth.policy.Resource;
import com.amazonaws.auth.policy.Statement;
import com.amazonaws.auth.policy.actions.KMSActions;
import com.amazonaws.services.kms.AWSKMS;
import com.amazonaws.services.kms.AWSKMSClient;
import com.amazonaws.services.kms.model.PutKeyPolicyRequest;
import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest;
import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult;
import com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand;
import com.nike.cerberus.operation.Operation;
import com.nike.cerberus.store.ConfigStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.util.*;

import static com.nike.cerberus.module.CerberusModule.getAWSCredentialsProviderChain;

/**
* Operation to update which principals besides for the root account will have permissions to use the backup cmk,
* AKA create and restore backups
*/
public class SetBackupAdminPrincipalsOperation implements Operation<SetBackupAdminPrincipalsCommand> {

private final Logger log = LoggerFactory.getLogger(getClass());

private static final String AWS_PROVIDER = "AWS";

private final ConfigStore configStore;
private final AWSSecurityTokenService sts;

@Inject
public SetBackupAdminPrincipalsOperation(ConfigStore configStore,
AWSSecurityTokenService sts) {

this.configStore = configStore;
this.sts = sts;
}

@Override
public void run(SetBackupAdminPrincipalsCommand command) {
GetCallerIdentityResult identityResult = sts.getCallerIdentity(new GetCallerIdentityRequest());
String accountId = identityResult.getAccount();
String rootArn = String.format("arn:aws:iam::%s:root", accountId);
String adminRoleArn = configStore.getAccountAdminArn().get();

Set<String> principals = new HashSet<>();
principals.add(rootArn);
principals.add(adminRoleArn);
principals.addAll(command.getAdditionalPrincipals());

configStore.storeBackupAdminIamPrincipals(principals);

if (! configStore.getRegionBackupBucketMap().isEmpty()) {
configStore.getRegionBackupBucketMap().forEach((region, backupRegionInfo) -> {
final List<Statement> statements = new LinkedList<>();
principals.forEach( principal -> {
log.debug("Adding principal: {} to the CMK Policy for region {}", principal, region);
statements.add(new Statement(Statement.Effect.Allow)
.withId("Principal " + principal + " Has All Actions")
.withPrincipals(new Principal(AWS_PROVIDER, principal, false))
.withActions(KMSActions.AllKMSActions)
.withResources(new Resource("*")));
});

Policy kmsPolicy = new Policy();
kmsPolicy.setStatements(statements);
String policyString = kmsPolicy.toJson();

log.debug("Updating key {} for region {} with policy {}", backupRegionInfo.getKmsCmkId(), region, policyString);

AWSKMS kms = AWSKMSClient.builder().withCredentials(getAWSCredentialsProviderChain()).withRegion(region).build();
PutKeyPolicyRequest request = new PutKeyPolicyRequest()
.withKeyId(backupRegionInfo.getKmsCmkId())
.withPolicyName("default")
.withBypassPolicyLockoutSafetyCheck(true)
.withPolicy(policyString);

kms.putKeyPolicy(request);

log.info("Successfully updated key {} in region {} to allow the following principals access {}",
backupRegionInfo.getKmsCmkId(), region, String.join(", ", principals));
});
}
}

@Override
public boolean isRunnable(SetBackupAdminPrincipalsCommand command) {
Optional<String> adminIamPrincipalArn = configStore.getAccountAdminArn();
if (! adminIamPrincipalArn.isPresent()) {
log.error("The admin IAM principal must be set for this environment");
return false;
}
return true;
}

}
22 changes: 22 additions & 0 deletions src/main/java/com/nike/cerberus/store/ConfigStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,13 @@ public Optional<BackupRegionInfo> getBackupInfoForRegion(String region) {
}
}

public Map<String, BackupRegionInfo> getRegionBackupBucketMap() {
synchronized (envDataLock) {
final Environment environment = getEnvironmentData();
return environment.getRegionBackupBucketMap();
}
}

public void storeBackupInfoForRegion(String region, String bucket, String kmsCmkId) {
synchronized (envDataLock) {
final Environment environment = getEnvironmentData();
Expand All @@ -955,6 +962,21 @@ public void storeBackupInfoForRegion(String region, String bucket, String kmsCmk
}
}

public Set<String> getBackupAdminIamPrincipals() {
synchronized (envDataLock) {
final Environment environment = getEnvironmentData();
return environment.getBackupAdminIamPrincipals();
}
}

public void storeBackupAdminIamPrincipals(Set<String> principals) {
synchronized (envDataLock) {
final Environment environment = getEnvironmentData();
environment.setBackupAdminIamPrincipals(principals);
saveEnvironmentData(environment);
}
}

public Optional<String> getMetricsTopicArn() {
synchronized (envDataLock) {
final Environment environment = getEnvironmentData();
Expand Down

0 comments on commit 1430104

Please sign in to comment.