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

Commit 1430104

Browse files
committed
Create command to allow Cerberus operators to speficy allowed IAM principals that can use the CMK's associated with backups
2 parents d9c16f2 + 84549c0 commit 1430104

File tree

7 files changed

+243
-35
lines changed

7 files changed

+243
-35
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
1616

1717
group=com.nike
1818
artifactId=cerberus-lifecycle-cli
19-
version=3.1.1
19+
version=3.2.0

src/main/java/com/nike/cerberus/cli/CerberusRunner.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.nike.cerberus.command.core.RestoreCerberusBackupCommand;
4343
import com.nike.cerberus.command.core.UpdateStackCommand;
4444
import com.nike.cerberus.command.core.UploadCertFilesCommand;
45+
import com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand;
4546
import com.nike.cerberus.command.core.WhitelistCidrForVpcAccessCommand;
4647
import com.nike.cerberus.command.dashboard.PublishDashboardCommand;
4748
import com.nike.cerberus.command.gateway.CreateCloudFrontLogProcessingLambdaConfigCommand;
@@ -202,6 +203,7 @@ private void registerAllCommands() {
202203
registerCommand(new UpdateCmsConfigCommand());
203204
registerCommand(new RollingRebootWithHealthCheckCommand());
204205
registerCommand(new CreateCerberusBackupCommand());
206+
registerCommand(new SetBackupAdminPrincipalsCommand());
205207
}
206208

207209
/**
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2017 Nike, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.nike.cerberus.command.core;
18+
19+
import com.beust.jcommander.Parameter;
20+
import com.beust.jcommander.Parameters;
21+
import com.nike.cerberus.command.Command;
22+
import com.nike.cerberus.operation.Operation;
23+
import com.nike.cerberus.operation.core.SetBackupAdminPrincipalsOperation;
24+
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
28+
import static com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand.COMMAND_NAME;
29+
30+
/**
31+
* Command to update which principals besides for the root account will have permissions to use the backup cmk,
32+
* AKA create and restore backups.
33+
*/
34+
@Parameters(
35+
commandNames = COMMAND_NAME,
36+
commandDescription = "Update the IAM Principals that are allowed to create and restore backups. " +
37+
"This command automatically adds by default the root user and configured admin user arn, " +
38+
"but you can use this command to add iam principals such as CI systems and additional user principals " +
39+
"that will have access to encrypt and decrypt backup data"
40+
)
41+
public class SetBackupAdminPrincipalsCommand implements Command {
42+
43+
public static final String COMMAND_NAME = "set-backup-principals";
44+
45+
public static final String PRINCIPAL_LONG_ARG = "--principal";
46+
public static final String PRINCIPAL_SHORT_ARG = "-p";
47+
48+
@Parameter(
49+
names = {
50+
PRINCIPAL_LONG_ARG,
51+
PRINCIPAL_SHORT_ARG
52+
},
53+
description = "One or more additional principals to grant access to."
54+
)
55+
private List<String> additionalPrincipals = new ArrayList<>();
56+
57+
public List<String> getAdditionalPrincipals() {
58+
return additionalPrincipals;
59+
}
60+
61+
@Override
62+
public String getCommandName() {
63+
return COMMAND_NAME;
64+
}
65+
66+
@Override
67+
public Class<? extends Operation<?>> getOperationClass() {
68+
return SetBackupAdminPrincipalsOperation.class;
69+
}
70+
}

src/main/java/com/nike/cerberus/domain/environment/Environment.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package com.nike.cerberus.domain.environment;
1818

1919
import java.util.HashMap;
20+
import java.util.HashSet;
2021
import java.util.Map;
22+
import java.util.Set;
2123

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

3941
private Map<String, BackupRegionInfo> regionBackupBucketMap;
4042

43+
private Set<String> backupAdminIamPrincipals;
44+
4145
private String metricsTopicArn;
4246

4347
/**
@@ -129,6 +133,14 @@ public void setRegionBackupBucketMap(Map<String, BackupRegionInfo> regionBackupB
129133
this.regionBackupBucketMap = regionBackupBucketMap;
130134
}
131135

136+
public Set<String> getBackupAdminIamPrincipals() {
137+
return backupAdminIamPrincipals == null ? new HashSet<>() : backupAdminIamPrincipals;
138+
}
139+
140+
public void setBackupAdminIamPrincipals(Set<String> backupAdminIamPrincipals) {
141+
this.backupAdminIamPrincipals = backupAdminIamPrincipals;
142+
}
143+
132144
public String getMetricsTopicArn() {
133145
return metricsTopicArn;
134146
}

src/main/java/com/nike/cerberus/operation/core/CreateCerberusBackupOperation.java

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@
3535
import com.amazonaws.services.s3.model.Bucket;
3636
import com.amazonaws.services.s3.model.CryptoConfiguration;
3737
import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider;
38-
import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
39-
import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest;
40-
import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult;
4138
import com.fasterxml.jackson.core.JsonProcessingException;
4239
import com.fasterxml.jackson.databind.DeserializationFeature;
4340
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -47,6 +44,7 @@
4744
import com.google.common.collect.ImmutableMap;
4845
import com.nike.cerberus.client.CerberusAdminClient;
4946
import com.nike.cerberus.client.CerberusAdminClientFactory;
47+
import com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand;
5048
import com.nike.cerberus.domain.cms.SafeDepositBox;
5149
import com.nike.cerberus.command.core.CreateCerberusBackupCommand;
5250
import com.nike.cerberus.domain.EnvironmentMetadata;
@@ -67,6 +65,7 @@
6765
import java.text.SimpleDateFormat;
6866
import java.util.Date;
6967
import java.util.HashMap;
68+
import java.util.LinkedList;
7069
import java.util.List;
7170
import java.util.Map;
7271
import java.util.Optional;
@@ -87,15 +86,14 @@ public class CreateCerberusBackupOperation implements Operation<CreateCerberusBa
8786
private final CerberusAdminClient cerberusAdminClient;
8887
private final MetricsService metricsService;
8988
private final EnvironmentMetadata environmentMetadata;
90-
private final AWSSecurityTokenService sts;
89+
9190
private final Map<String, S3StoreService> regionToEncryptedStoreServiceMap = new HashMap<>();
9291

9392
@Inject
9493
public CreateCerberusBackupOperation(CerberusAdminClientFactory cerberusAdminClientFactory,
9594
ConfigStore configStore,
9695
MetricsService metricsService,
97-
EnvironmentMetadata environmentMetadata,
98-
AWSSecurityTokenService sts) {
96+
EnvironmentMetadata environmentMetadata) {
9997

10098
objectMapper = new ObjectMapper();
10199
objectMapper.findAndRegisterModules();
@@ -109,7 +107,6 @@ public CreateCerberusBackupOperation(CerberusAdminClientFactory cerberusAdminCli
109107
this.configStore = configStore;
110108
this.metricsService = metricsService;
111109
this.environmentMetadata = environmentMetadata;
112-
this.sts = sts;
113110

114111
cerberusAdminClient = cerberusAdminClientFactory.createCerberusAdminClient(
115112
configStore.getCerberusBaseUrl());
@@ -339,32 +336,19 @@ private String getBackupBucketName(String region) {
339336
}
340337

341338
private String provisionKmsCmkForBackupRegion(String region) {
342-
GetCallerIdentityResult identityResult = sts.getCallerIdentity(new GetCallerIdentityRequest());
343-
String accountId = identityResult.getAccount();
344-
String rootArn = String.format("arn:aws:iam::%s:root", accountId);
345-
346-
String adminRoleArn = configStore.getAccountAdminArn().get();
347-
348339
Policy kmsPolicy = new Policy();
340+
final List<Statement> statements = new LinkedList<>();
341+
// allow the configured admin iam principals all permissions
342+
configStore.getBackupAdminIamPrincipals().forEach( principal -> {
343+
log.debug("Adding principal: {} to the CMK Policy for region {}", principal, region);
344+
statements.add(new Statement(Statement.Effect.Allow)
345+
.withId("Principal " + principal + " Has All Actions")
346+
.withPrincipals(new Principal(AWS_PROVIDER, principal, false))
347+
.withActions(KMSActions.AllKMSActions)
348+
.withResources(new Resource("*")));
349+
});
349350

350-
// allow the root user all permissions
351-
Statement rootUserStatement = new Statement(Statement.Effect.Allow);
352-
rootUserStatement.withId("Root User Has All Actions");
353-
rootUserStatement.withPrincipals(new Principal(AWS_PROVIDER, rootArn, false));
354-
rootUserStatement.withActions(KMSActions.AllKMSActions);
355-
rootUserStatement.withResources(new Resource("*"));
356-
357-
// allow the configured admin user all permissions
358-
Statement adminUserStatement = new Statement(Statement.Effect.Allow);
359-
adminUserStatement.withId("Admin Role Has All Actions");
360-
adminUserStatement.withPrincipals(new Principal(AWS_PROVIDER, adminRoleArn, false));
361-
adminUserStatement.withActions(KMSActions.AllKMSActions);
362-
adminUserStatement.withResources(new Resource("*"));
363-
364-
kmsPolicy.withStatements(
365-
rootUserStatement,
366-
adminUserStatement
367-
);
351+
kmsPolicy.setStatements(statements);
368352

369353
String policyString = kmsPolicy.toJson();
370354

@@ -394,9 +378,8 @@ private String provisionKmsCmkForBackupRegion(String region) {
394378

395379
@Override
396380
public boolean isRunnable(CreateCerberusBackupCommand command) {
397-
Optional<String> adminIamPrincipalArn = configStore.getAccountAdminArn();
398-
if (! adminIamPrincipalArn.isPresent()) {
399-
log.error("The admin IAM principal must be set for this environment");
381+
if (!configStore.getBackupAdminIamPrincipals().isEmpty()) {
382+
log.error("Backup Admin Principals have not been set please run " + SetBackupAdminPrincipalsCommand.COMMAND_NAME);
400383
return false;
401384
}
402385
return true;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright (c) 2017 Nike, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.nike.cerberus.operation.core;
18+
19+
import com.amazonaws.auth.policy.Policy;
20+
import com.amazonaws.auth.policy.Principal;
21+
import com.amazonaws.auth.policy.Resource;
22+
import com.amazonaws.auth.policy.Statement;
23+
import com.amazonaws.auth.policy.actions.KMSActions;
24+
import com.amazonaws.services.kms.AWSKMS;
25+
import com.amazonaws.services.kms.AWSKMSClient;
26+
import com.amazonaws.services.kms.model.PutKeyPolicyRequest;
27+
import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
28+
import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest;
29+
import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult;
30+
import com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand;
31+
import com.nike.cerberus.operation.Operation;
32+
import com.nike.cerberus.store.ConfigStore;
33+
import org.slf4j.Logger;
34+
import org.slf4j.LoggerFactory;
35+
36+
import javax.inject.Inject;
37+
import java.util.*;
38+
39+
import static com.nike.cerberus.module.CerberusModule.getAWSCredentialsProviderChain;
40+
41+
/**
42+
* Operation to update which principals besides for the root account will have permissions to use the backup cmk,
43+
* AKA create and restore backups
44+
*/
45+
public class SetBackupAdminPrincipalsOperation implements Operation<SetBackupAdminPrincipalsCommand> {
46+
47+
private final Logger log = LoggerFactory.getLogger(getClass());
48+
49+
private static final String AWS_PROVIDER = "AWS";
50+
51+
private final ConfigStore configStore;
52+
private final AWSSecurityTokenService sts;
53+
54+
@Inject
55+
public SetBackupAdminPrincipalsOperation(ConfigStore configStore,
56+
AWSSecurityTokenService sts) {
57+
58+
this.configStore = configStore;
59+
this.sts = sts;
60+
}
61+
62+
@Override
63+
public void run(SetBackupAdminPrincipalsCommand command) {
64+
GetCallerIdentityResult identityResult = sts.getCallerIdentity(new GetCallerIdentityRequest());
65+
String accountId = identityResult.getAccount();
66+
String rootArn = String.format("arn:aws:iam::%s:root", accountId);
67+
String adminRoleArn = configStore.getAccountAdminArn().get();
68+
69+
Set<String> principals = new HashSet<>();
70+
principals.add(rootArn);
71+
principals.add(adminRoleArn);
72+
principals.addAll(command.getAdditionalPrincipals());
73+
74+
configStore.storeBackupAdminIamPrincipals(principals);
75+
76+
if (! configStore.getRegionBackupBucketMap().isEmpty()) {
77+
configStore.getRegionBackupBucketMap().forEach((region, backupRegionInfo) -> {
78+
final List<Statement> statements = new LinkedList<>();
79+
principals.forEach( principal -> {
80+
log.debug("Adding principal: {} to the CMK Policy for region {}", principal, region);
81+
statements.add(new Statement(Statement.Effect.Allow)
82+
.withId("Principal " + principal + " Has All Actions")
83+
.withPrincipals(new Principal(AWS_PROVIDER, principal, false))
84+
.withActions(KMSActions.AllKMSActions)
85+
.withResources(new Resource("*")));
86+
});
87+
88+
Policy kmsPolicy = new Policy();
89+
kmsPolicy.setStatements(statements);
90+
String policyString = kmsPolicy.toJson();
91+
92+
log.debug("Updating key {} for region {} with policy {}", backupRegionInfo.getKmsCmkId(), region, policyString);
93+
94+
AWSKMS kms = AWSKMSClient.builder().withCredentials(getAWSCredentialsProviderChain()).withRegion(region).build();
95+
PutKeyPolicyRequest request = new PutKeyPolicyRequest()
96+
.withKeyId(backupRegionInfo.getKmsCmkId())
97+
.withPolicyName("default")
98+
.withBypassPolicyLockoutSafetyCheck(true)
99+
.withPolicy(policyString);
100+
101+
kms.putKeyPolicy(request);
102+
103+
log.info("Successfully updated key {} in region {} to allow the following principals access {}",
104+
backupRegionInfo.getKmsCmkId(), region, String.join(", ", principals));
105+
});
106+
}
107+
}
108+
109+
@Override
110+
public boolean isRunnable(SetBackupAdminPrincipalsCommand command) {
111+
Optional<String> adminIamPrincipalArn = configStore.getAccountAdminArn();
112+
if (! adminIamPrincipalArn.isPresent()) {
113+
log.error("The admin IAM principal must be set for this environment");
114+
return false;
115+
}
116+
return true;
117+
}
118+
119+
}

src/main/java/com/nike/cerberus/store/ConfigStore.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,13 @@ public Optional<BackupRegionInfo> getBackupInfoForRegion(String region) {
947947
}
948948
}
949949

950+
public Map<String, BackupRegionInfo> getRegionBackupBucketMap() {
951+
synchronized (envDataLock) {
952+
final Environment environment = getEnvironmentData();
953+
return environment.getRegionBackupBucketMap();
954+
}
955+
}
956+
950957
public void storeBackupInfoForRegion(String region, String bucket, String kmsCmkId) {
951958
synchronized (envDataLock) {
952959
final Environment environment = getEnvironmentData();
@@ -955,6 +962,21 @@ public void storeBackupInfoForRegion(String region, String bucket, String kmsCmk
955962
}
956963
}
957964

965+
public Set<String> getBackupAdminIamPrincipals() {
966+
synchronized (envDataLock) {
967+
final Environment environment = getEnvironmentData();
968+
return environment.getBackupAdminIamPrincipals();
969+
}
970+
}
971+
972+
public void storeBackupAdminIamPrincipals(Set<String> principals) {
973+
synchronized (envDataLock) {
974+
final Environment environment = getEnvironmentData();
975+
environment.setBackupAdminIamPrincipals(principals);
976+
saveEnvironmentData(environment);
977+
}
978+
}
979+
958980
public Optional<String> getMetricsTopicArn() {
959981
synchronized (envDataLock) {
960982
final Environment environment = getEnvironmentData();

0 commit comments

Comments
 (0)