Skip to content
This repository has been archived by the owner on May 13, 2021. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dlahn committed Oct 8, 2019
0 parents commit f1bd11c
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Dockerfile
README.mdown
27 changes: 27 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM ruby:2.6-alpine3.10 as build
MAINTAINER [email protected]

RUN mkdir -p /usr/src/garrison-agent
WORKDIR /usr/src/garrison-agent

RUN gem install bundler -v "~> 2.0"
COPY Gemfile Gemfile.lock /usr/src/garrison-agent/
RUN bundle install --jobs "$(getconf _NPROCESSORS_ONLN)" --retry 5 --without development

COPY . /usr/src/garrison-agent

RUN rm /usr/local/bundle/cache/*.gem
RUN find /usr/local/bundle -iname '*.o' -exec rm {} \;
RUN find /usr/local/bundle -iname '*.a' -exec rm {} \;


# RUNTIME CONTAINER
FROM ruby:2.6-alpine3.10

RUN apk upgrade --no-cache

WORKDIR /usr/src/garrison-agent
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /usr/src/garrison-agent /usr/src/garrison-agent

ENV PATH "$PATH:/usr/src/garrison-agent/bin"
8 changes: 8 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
source 'https://rubygems.org'

gem 'aws-sdk-s3', '~> 1', require: 'aws-sdk-s3'
gem 'garrison-api', '~> 2'

group :development do
gem 'pry', require: 'pry'
end
45 changes: 45 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
GEM
remote: https://rubygems.org/
specs:
aws-eventstream (1.0.3)
aws-partitions (1.220.0)
aws-sdk-core (3.68.1)
aws-eventstream (~> 1.0, >= 1.0.2)
aws-partitions (~> 1.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.24.0)
aws-sdk-core (~> 3, >= 3.61.1)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.48.0)
aws-sdk-core (~> 3, >= 3.61.1)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.1.0)
aws-eventstream (~> 1.0, >= 1.0.2)
coderay (1.1.2)
garrison-api (2.0.0)
httparty (>= 0.16.2, < 0.18.0)
httparty (0.17.1)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
jmespath (1.4.0)
method_source (0.9.2)
mime-types (3.3)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.0904)
multi_xml (0.6.0)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)

PLATFORMS
ruby

DEPENDENCIES
aws-sdk-s3 (~> 1)
garrison-api (~> 2)
pry

BUNDLED WITH
2.0.2
68 changes: 68 additions & 0 deletions README.mdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
Garrison Agent - AWS S3
--

This is a part of the [Garrison](https://github.com/forward3d/garrison) security project. This agent provides various AWS S3 compliance checks.

### Checks Provided

| Function Name | Description |
| ------------- | ------------- |
| `check_encryption` | Alerts if encryption is not enabled for an RDS instance. |
| `check_public_access_block` | Alerts if there are any buckets defined without a public access block. |

### Installation & Example

Docker Hub - https://hub.docker.com/r/forward3d/garrison-agent-aws-rds/

docker pull forward3d/garrison-agent-aws-rds
docker run --rm -e "GARRISON_URL=https://garrison.internal.acme.com" forward3d/garrison-agent-aws-s3 check_encryption
docker run --rm -e "GARRISON_URL=https://garrison.internal.acme.com" -e "GARRISON_AWS_REGIONS=eu-west-1,us-west-2" forward3d/garrison-agent-aws-s3 check_public_access

### Agent Specific Configuration

These are additional specific configuration options for this agent. [Global agent configurations](https://github.com/forward3d/garrison#global-configuration-options) still apply.

| Environmental Variable | Default | Expects |
| ------------- | ------------- | ------------- |
| `GARRISON_AWS_REGIONS` | `all` [[1]](#f1) | Comma Separated Strings eg. `eu-west-1,us-west-2` |

#### AWS Authentication

As this requires access to the AWS API you will need this IAM policy as a minimum for it to operate correctly.

{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:GetBucketEncryption",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketLocation",
"s3:ListAllMyBuckets"
],
"Resource": "*",
"Effect": "Allow"
}
]
}

We recommend using EC2/ECS Task roles so that you don't need to send credentials into the container, however if you can't use those or want to send in specific Access Keys and Secret keys, please see the [AWS Documentation](https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html#aws-ruby-sdk-credentials-environment) as to how you do that.

##### Cross-Account Authentication (`STS AssumeRole`)

If you run Garrison agents in one account, and want to reach into other AWS accounts you need to send in extra environmental variables to support that.

| Environmental Variable | Value |
| ------------- | ------------- |
| `AWS_ACCOUNT_ID` | Not used as part of authentication, but to override the tag set on any alerts |
| `AWS_ASSUME_ROLE_CREDENTIALS_ARN` | Arn of the role (in the other account) you wish to assume |

### Check Specific Configuration

Some checks provided by this agent have extra configuration options.

#### `check_public_access`

| Environmental Variable | Default |
| ------------- | ------------- |
| `GARRISON_S3_ALLOWED_PUBLIC_BUCKETS` | |
10 changes: 10 additions & 0 deletions bin/check_encryption
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env ruby

require_relative '../environment'

module Garrison
module Checks
check = CheckEncryption.new(@options)
check.run
end
end
11 changes: 11 additions & 0 deletions bin/check_public_access
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env ruby

require_relative '../environment'

module Garrison
module Checks
@options[:excluded_buckets] = ENV['GARRISON_S3_ALLOWED_PUBLIC_BUCKETS'].split(',')
check = CheckPublicAccessBlock.new(@options)
check.run
end
end
25 changes: 25 additions & 0 deletions environment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'bundler'
Bundler.require(:default)
ROOT = File.dirname(__FILE__)

Dir[File.join(ROOT, 'garrison/lib/*.rb')].each do |file|
require file
end

Dir[File.join(ROOT, 'garrison/checks/*.rb')].each do |file|
require file
end

Garrison::Api.configure do |config|
config.url = ENV['GARRISON_URL']
config.uuid = ENV['GARRISON_AGENT_UUID']
end

Garrison::Logging.info('Garrison Agent - AWS S3')

module Garrison
module Checks
@options = {}
@options[:regions] = ENV['GARRISON_AWS_REGIONS'] ? ENV['GARRISON_AWS_REGIONS'].split(',') : nil
end
end
83 changes: 83 additions & 0 deletions garrison/checks/check_encryption.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
module Garrison
module Checks
class CheckEncryption < Check

def settings
self.source ||= 'aws-s3'
self.severity ||= 'critical'
self.family ||= 'infrastructure'
self.type ||= 'compliance'
self.options[:regions] ||= 'all'
self.options[:engines] ||= 'all'
end

def key_values
[
{ key: 'datacenter', value: 'aws' },
{ key: 'aws-service', value: 'rds' },
{ key: 'aws-account', value: AwsHelper.whoami }
]
end

def perform
options[:regions] = AwsHelper.all_regions if options[:regions] == 'all'
options[:regions].each do |region|
Logging.info "Checking region #{region}"
not_encrypted = unecrypted_s3(region)

not_encrypted.each do |instance|
alert(
name: 'Encryption Violation',
target: instance,
detail: 'bucket_encrypted: false',
finding: instance,
finding_id: "aws-s3-#{instance}-encryption",
urls: [
{
name: 'AWS Dashboard',
url: "https://console.aws.amazon.com/s3/buckets/#{instance}?region=#{region}"
}
],
key_values: [
{
key: 'aws-region',
value: region
}
]
)
end
end
end

private

def unecrypted_s3(region)
if ENV['AWS_ASSUME_ROLE_CREDENTIALS_ARN']
role_credentials = Aws::AssumeRoleCredentials.new(
client: Aws::STS::Client.new(region: region),
role_arn: ENV['AWS_ASSUME_ROLE_CREDENTIALS_ARN'],
role_session_name: 'garrison-agent-s3'
)

s3 = Aws::S3::Resource.new(credentials: role_credentials)
else
s3 = Aws::S3::Resource.new(region: region)
end

unencrypted_buckets = []

s3.buckets.each do |bucket|
if s3.client.get_bucket_location(bucket: bucket.name).location_constraint == region
begin
s3.client.get_bucket_encryption(bucket: bucket.name)
rescue Aws::S3::Errors::ServerSideEncryptionConfigurationNotFoundError
unencrypted_buckets << bucket.name
end
end
end
puts unencrypted_buckets.count
unencrypted_buckets
end
end
end
end
83 changes: 83 additions & 0 deletions garrison/checks/check_public_access_block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
module Garrison
module Checks
class CheckPublicAccessBlock < Check

def settings
self.source ||= 'aws-s3'
self.severity ||= 'critical'
self.family ||= 'infrastructure'
self.type ||= 'compliance'
self.options[:regions] ||= 'all'
self.options[:engines] ||= 'all'
end

def key_values
[
{ key: 'datacenter', value: 'aws' },
{ key: 'aws-service', value: 'rds' },
{ key: 'aws-account', value: AwsHelper.whoami }
]
end

def perform
options[:regions] = AwsHelper.all_regions if options[:regions] == 'all'
options[:regions].each do |region|
Logging.info "Checking region #{region}"
buckets_without_public_block = public_block_s3(region)

buckets_without_public_block.each do |instance|
alert(
name: 'Public Access Block Violation',
target: instance,
detail: 'bucket_public_access_blocked: false',
finding: instance,
finding_id: "aws-s3-#{instance}-public_access",
urls: [
{
name: 'AWS Dashboard',
url: "https://console.aws.amazon.com/s3/buckets/#{instance}?region=#{region}"
}
],
key_values: [
{
key: 'aws-region',
value: region
}
]
) unless options[:excluded_buckets].include? instance
end
end
end

private

def public_block_s3(region)
if ENV['AWS_ASSUME_ROLE_CREDENTIALS_ARN']
role_credentials = Aws::AssumeRoleCredentials.new(
client: Aws::STS::Client.new(region: region),
role_arn: ENV['AWS_ASSUME_ROLE_CREDENTIALS_ARN'],
role_session_name: 'garrison-agent-s3'
)

s3 = Aws::S3::Resource.new(credentials: role_credentials)
else
s3 = Aws::S3::Resource.new(region: region)
end

buckets_without_public_block = []

s3.buckets.each do |bucket|
if s3.client.get_bucket_location(bucket: bucket.name).location_constraint == region
begin
s3.client.get_public_access_block(bucket: bucket.name)
rescue => e
buckets_without_public_block << bucket.name
end
end
end

buckets_without_public_block
end
end
end
end
13 changes: 13 additions & 0 deletions garrison/lib/aws_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Garrison
class AwsHelper

def self.whoami
@whoami ||= ENV['AWS_ACCOUNT_ID'] || Aws::STS::Client.new(region: 'us-east-1').get_caller_identity.account
end

def self.all_regions
Aws::Partitions.partition('aws').service('S3').regions
end

end
end

0 comments on commit f1bd11c

Please sign in to comment.