/** * Validate that the IAM principal for the CMS has permissions to schedule and cancel deletion of the KMS key. * @param policyJson - The KMS key policy as a String */ protected boolean cmsHasKeyDeletePermissions(String policyJson) { try { Policy policy = policyReader.createPolicyFromJsonString(policyJson); return policy.getStatements() .stream() .anyMatch(statement -> StringUtils.equals(statement.getId(), CERBERUS_MANAGEMENT_SERVICE_SID) && statementAppliesToPrincipal(statement, cmsRoleArn) && statement.getEffect() == Statement.Effect.Allow && statementIncludesAction(statement, KMSActions.ScheduleKeyDeletion) && statementIncludesAction(statement, KMSActions.CancelKeyDeletion)); } catch (Exception e) { logger.error("Failed to validate that CMS can delete KMS key, there may be something wrong with the policy", e); } return false; }
/** * Generates the standard KMS key policy statement for the Cerberus Management Service */ protected Statement generateStandardCMSPolicyStatement() { Statement cmsStatement = new Statement(Statement.Effect.Allow); cmsStatement.withId(CERBERUS_MANAGEMENT_SERVICE_SID); cmsStatement.withPrincipals(new Principal(AWS_PROVIDER, cmsRoleArn, false)); cmsStatement.withActions( KMSActions.Encrypt, KMSActions.Decrypt, KMSActions.ReEncryptFrom, KMSActions.ReEncryptTo, KMSActions.GenerateDataKey, KMSActions.GenerateDataKeyWithoutPlaintext, KMSActions.GenerateRandom, KMSActions.DescribeKey, KMSActions.ScheduleKeyDeletion, KMSActions.CancelKeyDeletion); cmsStatement.withResources(new Resource("*")); return cmsStatement; }
public String subscribeQueueToTopic(String snsTopicArn, String sqsQueueUrl){ Map<String, String> queueAttributes = sqsClient.getQueueAttributes(new GetQueueAttributesRequest(sqsQueueUrl) .withAttributeNames(QueueAttributeName.QueueArn.toString())).getAttributes(); String sqsQueueArn = queueAttributes.get(QueueAttributeName.QueueArn.toString()); Policy policy = new Policy().withStatements( new Statement(Effect.Allow) .withId("topic-subscription-" + snsTopicArn) .withPrincipals(Principal.AllUsers) .withActions(SQSActions.SendMessage) .withResources(new Resource(sqsQueueArn)) .withConditions(ConditionFactory.newSourceArnCondition(snsTopicArn))); logger.debug("Policy: " + policy.toJson()); queueAttributes = new HashMap<String, String>(); queueAttributes.put(QueueAttributeName.Policy.toString(), policy.toJson()); sqsClient.setQueueAttributes(new SetQueueAttributesRequest(sqsQueueUrl, queueAttributes)); SubscribeResult subscribeResult = snsClient.subscribe(new SubscribeRequest() .withEndpoint(sqsQueueArn) .withProtocol("sqs") .withTopicArn(snsTopicArn)); return subscribeResult.getSubscriptionArn(); }
/** * Converts the specified JSON string to an AWS policy object. * * For more information see, @see * http://docs.aws.amazon.com/AWSSdkDocsJava/latest * /DeveloperGuide/java-dg-access-control.html * * @param jsonString * the specified JSON string representation of this AWS access * control policy. * * @return An AWS policy object. * * @throws IllegalArgumentException * If the specified JSON string is null or invalid and cannot be * converted to an AWS policy object. */ public Policy createPolicyFromJsonString(String jsonString) { if (jsonString == null) { throw new IllegalArgumentException("JSON string cannot be null"); } JsonNode policyNode; JsonNode idNode; JsonNode statementNodes; Policy policy = new Policy(); List<Statement> statements = new LinkedList<Statement>(); try { policyNode = Jackson.jsonNodeOf(jsonString); idNode = policyNode.get(JsonDocumentFields.POLICY_ID); if (isNotNull(idNode)) { policy.setId(idNode.asText()); } statementNodes = policyNode.get(JsonDocumentFields.STATEMENT); if (isNotNull(statementNodes)) { for (JsonNode node : statementNodes) { statements.add(statementOf(node)); } } } catch (Exception e) { String message = "Unable to generate policy object fron JSON string " + e.getMessage(); throw new IllegalArgumentException(message, e); } policy.setStatements(statements); return policy; }
/** * Creates a <code>Statement<code> instance from the statement node. * * A statement consists of an Effect, id (optional), principal, action, resource, * and conditions. * <p> * principal is the AWS account that is making a request to access or modify one of your AWS resources. * <p> * action is the way in which your AWS resource is being accessed or modified, such as sending a message to an Amazon SQS queue, or storing an object in an Amazon S3 bucket. * <p> * resource is the AWS entity that the principal wants to access, such as an Amazon SQS queue, or an object stored in Amazon S3. * <p> * conditions are the optional constraints that specify when to allow or deny access for the principal to access your resource. Many expressive conditions are available, some specific to each service. For example, you can use date conditions to allow access to your resources only after or before a specific time. * * @param jStatement * JsonNode representing the statement. * @return a reference to the statement instance created. */ private Statement statementOf(JsonNode jStatement) { JsonNode effectNode = jStatement.get(JsonDocumentFields.STATEMENT_EFFECT); final Effect effect = isNotNull(effectNode) ? Effect.valueOf(effectNode.asText()) : Effect.Deny ; Statement statement = new Statement(effect); JsonNode id = jStatement.get(JsonDocumentFields.STATEMENT_ID); if (isNotNull(id)) { statement.setId(id.asText()); } JsonNode actionNodes = jStatement.get(JsonDocumentFields.ACTION); if (isNotNull(actionNodes)) statement.setActions(actionsOf(actionNodes)); JsonNode resourceNodes = jStatement.get(JsonDocumentFields.RESOURCE); if (isNotNull(resourceNodes)) statement.setResources(resourcesOf(resourceNodes)); JsonNode conditionNodes = jStatement.get(JsonDocumentFields.CONDITION); if (isNotNull(conditionNodes)) statement.setConditions(conditionsOf(conditionNodes)); JsonNode principalNodes = jStatement.get(JsonDocumentFields.PRINCIPAL); if (isNotNull(principalNodes)) statement.setPrincipals(principalOf(principalNodes)); return statement; }
static Policy getForUser(String bucket, String userName) { Statement creatingObjectsStatement = getObjectCreatingStatement(bucket, userName); Statement multipartUploadStatement = getMultipartUploadStatement(bucket, userName); Statement listBucketStatement = getListBucketStatement(bucket, userName); return new Policy("PerUserFileUploadingPolicy", Arrays.asList(multipartUploadStatement, creatingObjectsStatement, listBucketStatement)); }
private static Statement getObjectCreatingStatement(String bucket, String userName) { return new Statement(Statement.Effect.Allow) .withActions( () -> "s3:PutObject", () -> "s3:GetObject" ) .withResources(new Resource("arn:aws:s3:::" + bucket + "/" + userName + "/*")); }
private static Statement getListBucketStatement(String bucket, String userName) { return new Statement(Statement.Effect.Allow) .withActions( () -> "s3:ListBucket" ) .withResources(new Resource("arn:aws:s3:::" + bucket)) .withConditions( new Condition() .withType("StringEquals") .withConditionKey("s3:prefix") .withValues(userName+"/") ); }
private String getPolicy(List<String> accountIds) { Policy policy = new Policy("AuthorizedWorkerAccessPolicy"); Statement stmt = new Statement(Effect.Allow); Action action = SQSActions.SendMessage; stmt.getActions().add(action); stmt.setResources(new LinkedList<>()); for(String accountId : accountIds) { Principal principal = new Principal(accountId); stmt.getPrincipals().add(principal); } stmt.getResources().add(new Resource(getQueueARN())); policy.getStatements().add(stmt); return policy.toJson(); }
/** * Overwrite the policy statement for CMS with the standard statement. Add the standard statement for CMS * to the policy if it did not already exist. * * @param policyJson - The KMS key policy in JSON format * @return - The updated JSON KMS policy containing a regenerated statement for CMS */ protected String overwriteCMSPolicy(String policyJson) { Policy policy = policyReader.createPolicyFromJsonString(policyJson); removeStatementFromPolicy(policy, CERBERUS_MANAGEMENT_SERVICE_SID); Collection<Statement> statements = policy.getStatements(); statements.add(generateStandardCMSPolicyStatement()); return policy.toJson(); }
protected void removeStatementFromPolicy(Policy policy, String statementId) { Collection<Statement> existingStatements = policy.getStatements(); List<Statement> policyStatementsExcludingConsumer = existingStatements.stream() .filter(statement -> ! StringUtils.equals(statement.getId(), statementId)) .collect(Collectors.toList()); policyStatementsExcludingConsumer.add(generateStandardCMSPolicyStatement()); policy.setStatements(policyStatementsExcludingConsumer); }
/** * Validates that the given KMS key policy statement applies to the given principal */ protected boolean statementAppliesToPrincipal(Statement statement, String principalArn) { return statement.getPrincipals() .stream() .anyMatch(principal -> StringUtils.equals(principal.getId(), principalArn)); }
/** * Validates that the given KMS key policy statement includes the given action */ protected boolean statementIncludesAction(Statement statement, Action action) { return statement.getActions() .stream() .anyMatch(statementAction -> StringUtils.equals(statementAction.getActionName(), action.getActionName())); }
public String generateStandardKmsPolicy(String iamRoleArn) { Policy kmsPolicy = new Policy(); Statement rootUserStatement = new Statement(Statement.Effect.Allow); rootUserStatement.withId("Root User Has All Actions"); rootUserStatement.withPrincipals(new Principal(AWS_PROVIDER, rootUserArn, false)); rootUserStatement.withActions(KMSActions.AllKMSActions); rootUserStatement.withResources(new Resource("*")); Statement keyAdministratorStatement = new Statement(Statement.Effect.Allow); keyAdministratorStatement.withId("Admin Role Has All Actions"); keyAdministratorStatement.withPrincipals(new Principal(AWS_PROVIDER, adminRoleArn, false)); keyAdministratorStatement.withActions(KMSActions.AllKMSActions); keyAdministratorStatement.withResources(new Resource("*")); Statement instanceUsageStatement = generateStandardCMSPolicyStatement(); Statement iamRoleUsageStatement = new Statement(Statement.Effect.Allow); iamRoleUsageStatement.withId(CERBERUS_CONSUMER_SID); iamRoleUsageStatement.withPrincipals( new Principal(AWS_PROVIDER, iamRoleArn, false)); iamRoleUsageStatement.withActions(KMSActions.Decrypt); iamRoleUsageStatement.withResources(new Resource("*")); kmsPolicy.withStatements(rootUserStatement, keyAdministratorStatement, instanceUsageStatement, iamRoleUsageStatement); return kmsPolicy.toJson(); }
@Test public void test_that_generateStandardCMSPolicyStatement_returns_a_valid_statement() { Statement result = kmsPolicyService.generateStandardCMSPolicyStatement(); assertEquals(KmsPolicyService.CERBERUS_MANAGEMENT_SERVICE_SID, result.getId()); assertEquals(Statement.Effect.Allow, result.getEffect()); assertTrue(kmsPolicyService.cmsHasKeyDeletePermissions(new Policy().withStatements(result).toJson())); }
@Test public void test_that_removePolicyFromStatement_removes_the_given_statement() { String removeId = "remove id"; String keepId = "keep id"; Statement statementToRemove = new Statement(Statement.Effect.Allow).withId(removeId).withActions(KMSActions.AllKMSActions); Statement statementToKeep = new Statement(Statement.Effect.Deny).withId(keepId).withActions(KMSActions.AllKMSActions); Policy policy = new Policy("policy", Lists.newArrayList(statementToKeep, statementToRemove)); kmsPolicyService.removeStatementFromPolicy(policy, removeId); assertTrue(policy.getStatements().contains(statementToKeep)); assertFalse(policy.getStatements().contains(statementToRemove)); }
public static String getPublicReadPolicy(String bucket_name) { Policy bucket_policy = new Policy().withStatements( new Statement(Statement.Effect.Allow) .withPrincipals(Principal.AllUsers) .withActions(S3Actions.GetObject) .withResources(new Resource( "arn:aws:s3:::" + bucket_name + "/*"))); return bucket_policy.toJson(); }
private String provisionKmsCmkForBackupRegion(String region) { 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("*"))); }); kmsPolicy.setStatements(statements); String policyString = kmsPolicy.toJson(); log.debug("Creating key for region {} with policy {}", region, policyString); AWSKMS kms = AWSKMSClient.builder().withCredentials(getAWSCredentialsProviderChain()).withRegion(region).build(); CreateKeyResult createKeyResult = kms.createKey( new CreateKeyRequest() .withPolicy(policyString) .withBypassPolicyLockoutSafetyCheck(true) .withDescription(String.format("Cerberus Backup Encryption key for env: %S region: %s", environmentMetadata.getName(), region)) .withTags( new Tag().withTagKey("env").withTagValue(environmentMetadata.getName()), new Tag().withTagKey("region").withTagValue(region), new Tag().withTagKey("cerberus-backup-key").withTagValue("true") ) ); String keyId = createKeyResult.getKeyMetadata().getKeyId(); log.info("Created new backup KMS CMK with id: {} for region: {}", keyId, region); return keyId; }
/** * Adds a permission to allow the specified actions to the given KMS key id. * * @param kmsKeyId Full ARN to the kms key * @param actions List of actions * * @return This builder */ @SuppressWarnings("PMD.CloseResource") public AwsPolicyBuilder withKms(String kmsKeyId, KmsActions... actions) { Statement statement = new Statement(Effect.Allow); statement.setActions(Arrays.asList(actions)); statement.setResources(Arrays.asList(new Resource(kmsKeyId))); policy.getStatements().add(statement); return this; }
/** * Adds a permission to allow the specified actions to the given bucket and s3 object key. The permission will allow the given actions only to the specified * object key. If object key is null, the permission is applied to the bucket itself. * * @param bucketName S3 bucket name * @param objectKey S3 object key * @param actions List of actions to allow * * @return This builder */ @SuppressWarnings("PMD.CloseResource") public AwsPolicyBuilder withS3(String bucketName, String objectKey, S3Actions... actions) { Statement statement = new Statement(Effect.Allow); statement.setActions(Arrays.asList(actions)); String resource = "arn:aws:s3:::" + bucketName; if (objectKey != null) { resource += "/" + objectKey; } statement.setResources(Arrays.asList(new Resource(resource))); policy.getStatements().add(statement); return this; }
void validatePolicyDocument(String policyJSON) { Policy policy = Policy.fromJson(policyJSON); Asserts.isFalse(policy.getStatements().isEmpty(), "statement is required"); for (Statement statement : policy.getStatements()) { Asserts.isFalse(statement.getActions().isEmpty(), "action is required"); } }
boolean policyChanged(Policy policy1, Policy policy2) { Collection<Statement> statements1 = policy1.getStatements(); Collection<Statement> statements2 = policy2.getStatements(); if (statements1.size() != statements2.size()) return true; for (Statement statement1 : statements1) { if (!containStatement(statements2, statement1)) return true; } return false; }
private Boolean statementEquals(Statement statement1, Statement statement2) { List<Action> actions1 = statement1.getActions(); List<Action> actions2 = statement2.getActions(); boolean actionMatches = actions1.size() == actions2.size() && actions1.stream().allMatch(action1 -> actions2.stream().anyMatch(action2 -> action1.getActionName().equals(action2.getActionName()))); if (!actionMatches) return false; boolean effectMatches = statement1.getEffect().equals(statement2.getEffect()); if (!effectMatches) return false; List<Resource> resources1 = statement1.getResources(); List<Resource> resources2 = statement2.getResources(); boolean resourceMatches = resources1.size() == resources2.size() && resources1.stream().allMatch(resource1 -> resources2.stream().anyMatch(resource2 -> resource1.getId().equals(resource2.getId()))); if (!resourceMatches) return false; List<Condition> conditions1 = statement1.getConditions(); List<Condition> conditions2 = statement2.getConditions(); boolean conditionMatches = conditions1.size() == conditions2.size() && conditions1.stream().allMatch(condition1 -> conditions2.stream().anyMatch(condition2 -> conditionEquals(condition1, condition2))); if (!conditionMatches) return false; List<Principal> principals1 = statement1.getPrincipals(); List<Principal> principals2 = statement2.getPrincipals(); boolean principleMatches = principals1.size() == principals2.size() && principals1.stream().allMatch(principle1 -> principals2.stream().anyMatch(principal2 -> principleEquals(principle1, principal2))); if (!principleMatches) return false; return true; }
/** * Converts the given <code>Policy</code> into a JSON String. * * @param policy * the policy to be converted. * @return a JSON String of the specified policy object. */ private String jsonStringOf(Policy policy) throws JsonGenerationException, IOException { generator.writeStartObject(); writeJsonKeyValue(JsonDocumentFields.VERSION, policy.getVersion()); if (isNotNull(policy.getId())) writeJsonKeyValue(JsonDocumentFields.POLICY_ID, policy.getId()); writeJsonArrayStart(JsonDocumentFields.STATEMENT); for (Statement statement : policy.getStatements()) { generator.writeStartObject(); if (isNotNull(statement.getId())) { writeJsonKeyValue(JsonDocumentFields.STATEMENT_ID, statement.getId()); } writeJsonKeyValue(JsonDocumentFields.STATEMENT_EFFECT, statement .getEffect().toString()); List<Principal> principals = statement.getPrincipals(); if (isNotNull(principals) && !principals.isEmpty()) writePrincipals(principals); List<Action> actions = statement.getActions(); if (isNotNull(actions) && !actions.isEmpty()) writeActions(actions); List<Resource> resources = statement.getResources(); if (isNotNull(resources) && !resources.isEmpty()) writeResources(resources); List<Condition> conditions = statement.getConditions(); if (isNotNull(conditions) && !conditions.isEmpty()) writeConditions(conditions); generator.writeEndObject(); } writeJsonArrayEnd(); generator.writeEndObject(); generator.flush(); return writer.toString(); }
private static Statement getMultipartUploadStatement(String bucket, String userName) { return new Statement(Statement.Effect.Allow) .withActions(() -> "s3:ListBucketMultipartUploads") .withResources(new Resource("arn:aws:s3:::" + bucket)); }
@Test public void test_that_statementAllowsAction_returns_true_when_action_in_statement() { Action action = KMSActions.CancelKeyDeletion; Statement statement = new Statement(Statement.Effect.Allow).withActions(action); assertTrue(kmsPolicyService.statementIncludesAction(statement, action)); }
@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)); }); } }
private boolean containStatement(Collection<Statement> statements, Statement statement) { return statements.stream().anyMatch(statement1 -> statementEquals(statement1, statement)); }
private static void allowSQSQueueToReceiveMessagesFromSNSTopic( AmazonSQS amazonSQS, String queueURL, String queueARN, String topicARN ) { GetQueueAttributesResult queueAttributesResult = amazonSQS.getQueueAttributes( new GetQueueAttributesRequest().withQueueUrl(queueURL).withAttributeNames( QueueAttributeName.Policy ) ); String policyJson = queueAttributesResult.getAttributes().get(QueueAttributeName.Policy.name()); final List<Statement> statements; if (policyJson != null) { statements = new ArrayList<>(Policy.fromJson(policyJson).getStatements()); } else { // no policies yet exist statements = new ArrayList<>(); } statements.add( new Statement(Statement.Effect.Allow) .withPrincipals(Principal.AllUsers) .withResources(new Resource(queueARN)) .withActions(SQSActions.SendMessage) .withConditions(ConditionFactory.newSourceArnCondition(topicARN)) ); Policy policy = new Policy(); policy.setStatements(statements); Map<String, String> queueAttributes = new HashMap<>(); queueAttributes.put(QueueAttributeName.Policy.name(), policy.toJson()); // Note that if the queue already has this policy, this will do nothing. amazonSQS.setQueueAttributes( new SetQueueAttributesRequest() .withQueueUrl(queueURL) .withAttributes(queueAttributes) ); }