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

Commit

Permalink
Feature/ci backup support (#62)
Browse files Browse the repository at this point in the history
* wip

* Fix reversed logic in can run method

* commands should exit status one if not runnable
  • Loading branch information
fieldju authored Sep 14, 2017
1 parent d9c16f2 commit cee08ec
Show file tree
Hide file tree
Showing 7 changed files with 245 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
4 changes: 4 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 @@ -120,6 +121,8 @@ public void run(String[] args) {

if (operation.isRunnable(command)) {
operation.run(command);
} else {
throw new RuntimeException("Command not runnable");
}
}
} catch (Throwable e) {
Expand Down Expand Up @@ -202,6 +205,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 cee08ec

Please sign in to comment.