Skip to content

Commit

Permalink
Add missing permissions for sample AWS policy (#122)
Browse files Browse the repository at this point in the history
* Migrate AWS policy to autovalue object

This avoids embedding JSON in a Java constant, which is error prone.

* Add missing permissions

Added:
- kinesis:ListStreams
- logs:DescribeLogGroups

* Define JSON policy field order that matches AWS examples

* Return the policy JSON string to the UI as string

This is consistent with how this was done before. The UI can then format and display this to the user on the Available Services page. This is the policy that the user will create the AWS user for the AWS integration with.

* Specify the correct AWS policy version

* Change AWS Policy to a list

* Add missing permissions

Based on this [sample KCL code](aws/aws-sdk-java-v2#1214 (comment))

* Update version to 3.1.0-beta.2-SNAPSHOT

* Remove uneeded import

* Add Available Services API response error documentation

* Throw InternalServerErrorException instead of JSON exception

This is a bit cleaner from the API consumption side. A nice short error message is now returned instead of an obscure JSON error.
  • Loading branch information
Dan Torrey authored and bernd committed Jul 17, 2019
1 parent c6de790 commit 6bcdd8e
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 33 deletions.
31 changes: 31 additions & 0 deletions src/main/java/org/graylog/integrations/aws/AWSPolicy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.graylog.integrations.aws;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.google.auto.value.AutoValue;
import org.graylog.autovalue.WithBeanGetter;

import java.util.List;

@JsonAutoDetect
@AutoValue
@WithBeanGetter
// Define a JSON field order matching AWS examples. This improves readability.
@JsonPropertyOrder({AWSPolicy.VERSION, AWSPolicy.STATEMENT})
public abstract class AWSPolicy {

public static final String VERSION = "Version";
public static final String STATEMENT = "Statement";

@JsonProperty(VERSION)
public abstract String version();

@JsonProperty(STATEMENT)
public abstract List<AWSPolicyStatement> statement();

public static AWSPolicy create(@JsonProperty(VERSION) String version,
@JsonProperty(STATEMENT) List<AWSPolicyStatement> statement) {
return new AutoValue_AWSPolicy(version, statement);
}
}
41 changes: 41 additions & 0 deletions src/main/java/org/graylog/integrations/aws/AWSPolicyStatement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.graylog.integrations.aws;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.google.auto.value.AutoValue;
import org.graylog.autovalue.WithBeanGetter;

import java.util.List;

@JsonAutoDetect
@AutoValue
@WithBeanGetter
// Define a JSON field order matching AWS examples. This improves readability.
@JsonPropertyOrder({AWSPolicyStatement.SID, AWSPolicyStatement.EFFECT, AWSPolicyStatement.ACTION, AWSPolicyStatement.RESOURCE})
public abstract class AWSPolicyStatement {

static final String SID = "Sid";
static final String EFFECT = "Effect";
static final String ACTION = "Action";
static final String RESOURCE = "Resource";

@JsonProperty(SID)
public abstract String sid();

@JsonProperty(EFFECT)
public abstract String effect();

@JsonProperty(ACTION)
public abstract List<String> action();

@JsonProperty(RESOURCE)
public abstract String resource();

public static AWSPolicyStatement create(@JsonProperty(SID) String sid,
@JsonProperty(EFFECT) String effect,
@JsonProperty(ACTION) List<String> action,
@JsonProperty(RESOURCE) String resource) {
return new AutoValue_AWSPolicyStatement(sid, effect, action, resource);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package org.graylog.integrations.aws.resources;

import com.codahale.metrics.annotation.Timed;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.graylog.integrations.aws.AWSPermissions;
Expand Down Expand Up @@ -75,6 +78,9 @@ public RegionsResponse getAwsRegions() {
@GET
@Timed
@Path("/available_services")
@ApiResponses(value = {
@ApiResponse(code = 500, message = AWSService.POLICY_ENCODING_ERROR),
})
@ApiOperation(value = "Get all available AWS services")
@RequiresPermissions(AWSPermissions.AWS_READ)
public AvailableServiceResponse getAvailableServices() {
Expand Down
92 changes: 62 additions & 30 deletions src/main/java/org/graylog/integrations/aws/service/AWSService.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package org.graylog.integrations.aws.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.graylog.integrations.aws.AWSMessageType;
import org.graylog.integrations.aws.AWSPolicy;
import org.graylog.integrations.aws.AWSPolicyStatement;
import org.graylog.integrations.aws.inputs.AWSInput;
import org.graylog.integrations.aws.resources.requests.AWSInputCreateRequest;
import org.graylog.integrations.aws.resources.responses.AWSRegion;
Expand All @@ -31,7 +35,10 @@
import software.amazon.awssdk.regions.RegionMetadata;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.InternalServerErrorException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
Expand All @@ -45,16 +52,26 @@ public class AWSService {

private static final Logger LOG = LoggerFactory.getLogger(AWSService.class);

/**
* The only version supported is 2012-10-17
* @see <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html">IAM JSON Policy Elements: Version</a>
*/
private static final String AWS_POLICY_VERSION = "2012-10-17";
public static final String POLICY_ENCODING_ERROR = "An error occurred encoding the policy JSON";

private final InputService inputService;
private final MessageInputFactory messageInputFactory;
private final NodeId nodeId;
private final ObjectMapper objectMapper;

@Inject
public AWSService(InputService inputService, MessageInputFactory messageInputFactory, NodeId nodeId) {
public AWSService(InputService inputService, MessageInputFactory messageInputFactory, NodeId nodeId,
ObjectMapper objectMapper) {

this.inputService = inputService;
this.messageInputFactory = messageInputFactory;
this.nodeId = nodeId;
this.objectMapper = objectMapper;
}

/**
Expand Down Expand Up @@ -119,43 +136,58 @@ static StaticCredentialsProvider buildCredentialProvider(String accessKeyId, Str
*/
public AvailableServiceResponse getAvailableServices() {

// Create an AWSPolicy object that can be serialized into JSON.
// The user will use this as a guide to create a policy in their AWS account.
List<String> actions = Arrays.asList("cloudwatch:PutMetricData",
"dynamodb:CreateTable",
"dynamodb:DescribeTable",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Scan",
"dynamodb:UpdateItem",
"ec2:DescribeInstances",
"ec2:DescribeNetworkInterfaceAttribute",
"ec2:DescribeNetworkInterfaces",
"elasticloadbalancing:DescribeLoadBalancerAttributes",
"elasticloadbalancing:DescribeLoadBalancers",
"iam:CreateRole",
"iam:GetRole",
"iam:PutRolePolicy",
"kinesis:CreateStream",
"kinesis:DescribeStream",
"kinesis:GetRecords",
"kinesis:GetShardIterator",
"kinesis:ListShards",
"kinesis:ListStreams",
"logs:DescribeLogGroups",
"logs:PutSubscriptionFilter");

AWSPolicyStatement statement = AWSPolicyStatement.create("GraylogCloudWatchPolicy",
"Allow",
actions,
"*");
AWSPolicy awsPolicy = AWSPolicy.create(AWS_POLICY_VERSION, Collections.singletonList(statement));

ArrayList<AvailableService> services = new ArrayList<>();

// Deliberately provide the policy JSON as a string. The UI will format and display this to the user.
String policy;
try {
policy = objectMapper.writeValueAsString(awsPolicy);
} catch (JsonProcessingException e) {
// Return a more general internal server error if JSON encoding fails.
LOG.error(POLICY_ENCODING_ERROR, e);
throw new InternalServerErrorException(POLICY_ENCODING_ERROR, e);
}
AvailableService cloudWatchService =
AvailableService.create("CloudWatch",
"Retrieve CloudWatch logs via Kinesis. Kinesis allows streaming of the logs " +
"in real time. AWS CloudWatch is a monitoring and management service built " +
"for developers, system operators, site reliability engineers (SRE), " +
"and IT managers.",
"{\n" +
" \"Version\": \"2019-06-19\",\n" +
" \"Statement\": [\n" +
" {\n" +
" \"Sid\": \"GraylogCloudWatchPolicy\",\n" +
" \"Effect\": \"Allow\",\n" +
" \"Action\": [\n" +
" \"cloudwatch:PutMetricData\",\n" +
" \"dynamodb:CreateTable\",\n" +
" \"dynamodb:DescribeTable\",\n" +
" \"dynamodb:GetItem\",\n" +
" \"dynamodb:PutItem\",\n" +
" \"dynamodb:Scan\",\n" +
" \"dynamodb:UpdateItem\",\n" +
" \"ec2:DescribeInstances\",\n" +
" \"ec2:DescribeNetworkInterfaceAttribute\",\n" +
" \"ec2:DescribeNetworkInterfaces\",\n" +
" \"elasticloadbalancing:DescribeLoadBalancerAttributes\",\n" +
" \"elasticloadbalancing:DescribeLoadBalancers\",\n" +
" \"kinesis:GetRecords\",\n" +
" \"kinesis:GetShardIterator\",\n" +
" \"kinesis:ListShards\"\n" +
" ],\n" +
" \"Resource\": \"*\"\n" +
" }\n" +
" ]\n" +
"}",
policy,
"Requires Kinesis",
"https://aws.amazon.com/cloudwatch/"
);
"https://aws.amazon.com/cloudwatch/");
services.add(cloudWatchService);
return AvailableServiceResponse.create(services, services.size());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.graylog.integrations.aws.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.graylog.integrations.aws.AWSMessageType;
import org.graylog.integrations.aws.inputs.AWSInput;
import org.graylog.integrations.aws.resources.requests.AWSInputCreateRequest;
Expand All @@ -12,6 +14,7 @@
import org.graylog2.plugin.inputs.MessageInput;
import org.graylog2.plugin.system.NodeId;
import org.graylog2.rest.models.system.inputs.requests.InputCreateRequest;
import org.graylog2.shared.bindings.providers.ObjectMapperProvider;
import org.graylog2.shared.inputs.MessageInputFactory;
import org.junit.Before;
import org.junit.Rule;
Expand Down Expand Up @@ -59,7 +62,7 @@ public class AWSServiceTest {
@Before
public void setUp() {

awsService = new AWSService(inputService, messageInputFactory, nodeId);
awsService = new AWSService(inputService, messageInputFactory, nodeId, new ObjectMapperProvider().get());
}

@Test
Expand Down Expand Up @@ -124,13 +127,13 @@ public void regionTest() {
}
assertTrue(foundEuWestRegion);

// Use one liner presence checks.
// Use none liner presence checks.
assertTrue(regions.stream().anyMatch(r -> r.displayValue().equals("EU (Stockholm): eu-north-1")));
assertEquals("There should be 20 total regions. This will change in future versions of the AWS SDK", 20, regions.size());
}

@Test
public void testAvailableServices() {
public void testAvailableServices() throws JsonProcessingException {

AvailableServiceResponse services = awsService.getAvailableServices();

Expand All @@ -140,5 +143,13 @@ public void testAvailableServices() {

// CloudWatch should be in the list of available services.
assertTrue(services.services().stream().anyMatch(s -> s.name().equals("CloudWatch")));

// Verify that some of the needed actions are present.
String policy = services.services().get(0).policy();
assertTrue(policy.contains("cloudwatch"));
assertTrue(policy.contains("dynamodb"));
assertTrue(policy.contains("ec2"));
assertTrue(policy.contains("elasticloadbalancing"));
assertTrue(policy.contains("kinesis"));
}
}

0 comments on commit 6bcdd8e

Please sign in to comment.