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

Commit

Permalink
Merge pull request #281 from Netflix/develop
Browse files Browse the repository at this point in the history
Preparation for v0.4.1
  • Loading branch information
Patrick Kelley committed Dec 28, 2015
2 parents 1ab9a5f + 33079d3 commit e9ce881
Show file tree
Hide file tree
Showing 19 changed files with 791 additions and 222 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ before_script:
- python manage.py db upgrade

script:
- sh env_tests/test_dart.sh
- py.test security_monkey/tests || exit 1

notifications:
Expand Down
2 changes: 1 addition & 1 deletion dart/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: security_monkey
description: An AWS Policy Monitoring and Alerting Tool
version: 0.4.0
version: 0.4.1
dependencies:
angular: ">=1.1.0 <2.0.0"
angular_ui: '0.6.8'
Expand Down
2 changes: 1 addition & 1 deletion docs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ included, and users are free to add their own rules.

>>> import security_monkey
>>> security_monkey.__version__
u'0.4.0'
u'0.4.1'


Class and method level definitions and documentation
Expand Down
23 changes: 22 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,28 @@
Changelog
*********

v0.4.0 (2016-11-20)
v0.4.1 (2015-12-22)
===================
- PR #269 - mikegrima - TravisCI now ensures that dart builds.
- PR #270 - monkeysecurity - Refactored sts_connect to dynamically import boto resources.
- PR #271 - OllyTheNinja-Xero - Fixed indentation mistake in auditor.py
- PR #275 - AlexCline - Added elb logging to ELB watcher and auditor.
- PR #279 - mikegrima - Added ElasticSearch Watcher and Auditor (with tests).
- PR #280 - monkeysecurity - PolicyDiff better handling of changes to primitives (like ints) in dictionay values and added explicit escaping instead of relying on Angular.
- PR #282 - mikegrima - Documentation Fixes to configuration.rst and quickstart.rst adding es: permissions and other fixes.

Hotfixes:

- Added OSSMETADATA file to master/develop for internal Netflix tracking.

Contributors:

- @mikegrima
- @monkeysecurity
- @OllyTheNinja-Xero
- @AlexCline

v0.4.0 (2015-11-20)
===================
- PR #228 - jeremy-h - IAM check misses '*' when found within a list. (Issue #223)
- PR #230 - markofu - New error and echo functions to simplify code for scripts/secmonkey_auto_install.sh
Expand Down
4 changes: 3 additions & 1 deletion docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ SM-ReadOnly
"sqs:ListQueues",
"sqs:ReceiveMessage",
"storagegateway:List*",
"storagegateway:Describe*"
"storagegateway:Describe*",
"es:Describe*",
"es:List*"
],
"Effect": "Allow",
"Resource": "*"
Expand Down
11 changes: 7 additions & 4 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Paste in this JSON with the name "SecurityMonkeyLaunchPerms":
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "*"
"Resource": "arn:aws:iam::*:role/SecurityMonkey"
}
]
}
Expand Down Expand Up @@ -143,7 +143,9 @@ Paste in this JSON with the name "SecurityMonkeyReadOnly":
"sns:listtopics",
"sqs:getqueueattributes",
"sqs:listqueues",
"sqs:receivemessage"
"sqs:receivemessage",
"es:Describe*,
"es:List*"
],
"Effect": "Allow",
"Resource": "*"
Expand Down Expand Up @@ -231,9 +233,9 @@ Now may also be a good time to edit the "launch-wizard-1" security group to rest
Keypair
-------

You may be prompted to download a keypair. You should protect this keypair; it is used to provide ssh access to the new instance. Put it in a safe place. You will need to change the permissions on the keypair to 600::
You may be prompted to download a keypair. You should protect this keypair; it is used to provide ssh access to the new instance. Put it in a safe place. You will need to change the permissions on the keypair to 400::

$ chmod 600 SecurityMonkeyKeypair.pem
$ chmod 400 SecurityMonkeyKeypair.pem

Connecting to your new instance:
--------------------------------
Expand Down Expand Up @@ -315,6 +317,7 @@ Next we'll clone and install the package::

# Build the Web UI
$ cd /usr/local/src/security_monkey/dart
$ sudo /usr/lib/dart/bin/pub get
$ sudo /usr/lib/dart/bin/pub build

# Copy the compiled Web UI to the appropriate destination
Expand Down
37 changes: 37 additions & 0 deletions env_tests/test_dart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
##########################################
# Copyright 2015 Netflix, 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.
##########################################
#
# Script to test Dart installation for Security Monkey
#
##########################################

DART_DOWNLOAD_LOCATION="https://storage.googleapis.com/dart-archive/channels/stable/release/1.12.2/sdk/dartsdk-linux-x64-release.zip"

echo "Getting Dart..."
wget $DART_DOWNLOAD_LOCATION -O 'dartsdk-linux-x64-release.zip'

echo "Unzipping Dart..."
unzip "dartsdk-linux-x64-release.zip" > /dev/null

echo Setting up the environment variables
export DART_SDK="$PWD/dart-sdk"
export PATH="$DART_SDK/bin:$PATH"

echo "Building the dart deps..."
cd dart
pub get
pub build
3 changes: 1 addition & 2 deletions security_monkey/auditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ def __init__(self, accounts=None, debug=False):

for account in self.accounts:
users = User.query.filter(User.daily_audit_email==True).filter(User.accounts.any(name=account)).all()

self.emails.extend([user.email for user in users])
self.emails.extend([user.email for user in users])

def add_issue(self, score, issue, item, notes=None):
"""
Expand Down
181 changes: 181 additions & 0 deletions security_monkey/auditors/elasticsearch_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Copyright 2015 Netflix, 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.
"""
.. module: security_monkey.auditors.elb
:platform: Unix
.. version:: $$VERSION$$
.. moduleauthor:: Mike Grima <[email protected]>
"""
from security_monkey.auditor import Auditor
from security_monkey.common.arn import ARN
from security_monkey.datastore import NetworkWhitelistEntry
from security_monkey.watchers.elasticsearch_service import ElasticSearchService

import ipaddr


class ElasticSearchServiceAuditor(Auditor):
index = ElasticSearchService.index
i_am_singular = ElasticSearchService.i_am_singular
i_am_plural = ElasticSearchService.i_am_plural

def __init__(self, accounts=None, debug=False):
super(ElasticSearchServiceAuditor, self).__init__(accounts=accounts, debug=debug)

def prep_for_audit(self):
self.network_whitelist = NetworkWhitelistEntry.query.all()

def _parse_arn(self, arn_input, account_numbers, es_domain):
if arn_input == '*':
notes = "An ElasticSearch Service domain policy where { 'Principal': { 'AWS': '*' } } must also have"
notes += " a {'Condition': {'IpAddress': { 'AWS:SourceIp': '<ARN>' } } }"
notes += " or it is open to any AWS account."
self.add_issue(20, 'ES cluster open to all AWS accounts', es_domain, notes=notes)
return

arn = ARN(arn_input)
if arn.error:
self.add_issue(3, 'Auditor could not parse ARN', es_domain, notes=arn_input)
return

if arn.tech == 's3':
notes = "The ElasticSearch Service domain allows access from S3 bucket [{}]. ".format(arn.name)
notes += "Security Monkey does not yet have the capability to determine if this is "
notes += "a friendly S3 bucket. Please verify manually."
self.add_issue(3, 'ES cluster allows access from S3 bucket', es_domain, notes=notes)
else:
account_numbers.append(arn.account_number)

def check_es_access_policy(self, es_domain):
policy = es_domain.config["policy"]

for statement in policy.get("Statement", []):
effect = statement.get("Effect")
# We only care about "Allows"
if effect.lower() == "deny":
continue

account_numbers = []
princ = statement.get("Principal", {})
if isinstance(princ, dict):
princ_aws = princ.get("AWS", "error")
else:
princ_aws = princ

if princ_aws == "*":
condition = statement.get('Condition', {})

# Get the IpAddress subcondition:
ip_addr_condition = condition.get("IpAddress")

if ip_addr_condition:
source_ip_condition = ip_addr_condition.get("aws:SourceIp")

if not ip_addr_condition or not source_ip_condition:
tag = "ElasticSearch Service domain open to everyone"
notes = "An ElasticSearch Service domain policy where { 'Principal': { '*' } } OR"
notes += " { 'Principal': { 'AWS': '*' } } must also have a"
notes += " {'Condition': {'IpAddress': { 'AWS:SourceIp': '<ARN>' } } }"
notes += " or it is open to the world. In this case, anyone is allowed to perform "
notes += " this action(s): {}".format(statement.get("Action"))
self.add_issue(20, tag, es_domain, notes=notes)

else:
# Check for "aws:SourceIp" as a condition:
if isinstance(source_ip_condition, list):
for cidr in source_ip_condition:
self._check_proper_cidr(cidr, es_domain, statement.get("Action"))

else:
self._check_proper_cidr(source_ip_condition, es_domain, statement.get("Action"))

else:
if isinstance(princ_aws, list):
for entry in princ_aws:
arn = ARN(entry)
if arn.error:
self.add_issue(3, 'Auditor could not parse ARN', es_domain, notes=entry)
continue

if arn.root:
message = "ALL IAM Roles/users/groups in account can perform the following actions:\n"
message += "{}".format(statement.get("Action"))
self.add_issue(3, message, es_domain, notes="Root IAM ARN")

account_numbers.append(arn.account_number)
else:
arn = ARN(princ_aws)
if arn.error:
self.add_issue(3, 'Auditor could not parse ARN', es_domain, notes=princ_aws)
else:
if arn.root:
message = "ALL IAM Roles/users/groups in account can perform the following actions:\n"
message += "{}".format(statement.get("Action"))
self.add_issue(3, message, es_domain, notes="Root IAM ARN")

account_numbers.append(arn.account_number)

for account_number in account_numbers:
self._check_cross_account(account_number, es_domain, 'policy')

def _check_proper_cidr(self, cidr, es_domain, actions):
try:
any, ip_cidr = self._check_for_any_ip(cidr, es_domain, actions)
if any:
return

if not self._check_inclusion_in_network_whitelist(cidr):
message = "A CIDR that is not in the whitelist has access to this ElasticSearch Service domain:\n"
message += "CIDR: {}, Actions: {}".format(cidr, actions)
self.add_issue(5, message, es_domain, notes=cidr)

# Check if the CIDR is in a large subnet (and not whitelisted):
# Check if it's 10.0.0.0/8
if ip_cidr == ipaddr.IPNetwork("10.0.0.0/8"):
message = "aws:SourceIp Condition contains a very large IP range: 10.0.0.0/8"
self.add_issue(7, message, es_domain, notes=cidr)
else:
mask = int(ip_cidr.exploded.split('/')[1])
if 0 < mask < 24:
message = "aws:SourceIp contains a large IP Range: {}".format(cidr)
self.add_issue(3, message, es_domain, notes=cidr)


except ValueError as ve:
self.add_issue(3, 'Auditor could not parse CIDR', es_domain, notes=cidr)

def _check_for_any_ip(self, cidr, es_domain, actions):
if cidr == '*':
self.add_issue(20, 'Any IP can perform the following actions against this ElasticSearch Service '
'domain:\n{}'.format(actions),
es_domain, notes=cidr)
return True, None

zero = ipaddr.IPNetwork("0.0.0.0/0")
ip_cidr = ipaddr.IPNetwork(cidr)
if zero == ip_cidr:
self.add_issue(20, 'Any IP can perform the following actions against this ElasticSearch Service '
'domain:\n{}'.format(actions),
es_domain, notes=cidr)
return True, None

return False, ip_cidr

def _check_inclusion_in_network_whitelist(self, cidr):
for entry in self.network_whitelist:
if ipaddr.IPNetwork(cidr) in ipaddr.IPNetwork(str(entry.cidr)):
return True
return False
8 changes: 8 additions & 0 deletions security_monkey/auditors/elb.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ def check_listener_reference_policy(self, elb_item):
if not reference_policy:
self._process_custom_listener_policy(policy, listener['load_balancer_port'], elb_item)

def check_logging(self, elb_item):
"""
Alert when elb logging is not enabled
"""
logging = elb_item.config.get('is_logging')
if not logging:
self.add_issue(1, 'ELB is not configured for logging.', elb_item)

def _process_reference_policy(self, reference_policy, policy_name, port, elb_item):
notes = "Policy {0} on port {1}".format(policy_name, port)
if reference_policy is None:
Expand Down
Loading

0 comments on commit e9ce881

Please sign in to comment.