diff --git a/moto/ec2/models.py b/moto/ec2/models.py
index bab604890..540d98744 100644
--- a/moto/ec2/models.py
+++ b/moto/ec2/models.py
@@ -544,6 +544,43 @@ class SecurityGroup(object):
def physical_resource_id(self):
return self.id
+ def matches_filters(self, filters):
+ result = True
+
+ def to_attr(filter_name):
+ attr = None
+
+ if attr == 'group-name':
+ attr = 'name'
+ elif attr == 'group-id':
+ attr = 'id'
+ else:
+ attr = filter_name.replace('-', '_')
+
+ return attr
+
+ for key, value in filters.items():
+ ret = False
+
+ if key.startswith('ip-permission'):
+ match = re.search(r"ip-permission.(*)", key)
+ ingress_attr = to_attr(match.groups()[0])
+
+ for ingress in self.ingress_rules:
+ if getattr(ingress, ingress_attr) in filters[key]:
+ ret = True
+ break
+ else:
+ attr_name = to_attr(key)
+ ret = getattr(self, attr_name) in filters[key]
+
+ if not ret:
+ break
+ else:
+ result = False
+
+ return result
+
class SecurityGroupBackend(object):
@@ -566,8 +603,20 @@ class SecurityGroupBackend(object):
self.groups[vpc_id][group_id] = group
return group
- def describe_security_groups(self):
- return itertools.chain(*[x.values() for x in self.groups.values()])
+ def describe_security_groups(self, group_ids=None, groupnames=None, filters=None):
+ all_groups = itertools.chain(*[x.values() for x in self.groups.values()])
+ groups = []
+
+ if group_ids or groupnames or filters:
+ for group in all_groups:
+ if ((group_ids and group.id in group_ids) or
+ (groupnames and group.name in groupnames) or
+ (filters and group.matches_filters(filters))):
+ groups.append(group)
+ else:
+ groups = all_groups
+
+ return groups
def delete_security_group(self, name=None, group_id=None):
if group_id:
diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py
index d0ebe69a6..37d9bf35d 100644
--- a/moto/ec2/responses/security_groups.py
+++ b/moto/ec2/responses/security_groups.py
@@ -3,6 +3,7 @@ from jinja2 import Template
from moto.core.responses import BaseResponse
from moto.ec2.models import ec2_backend
+from moto.ec2.utils import filters_from_querystring
def process_rules_from_querystring(querystring):
@@ -35,6 +36,22 @@ def process_rules_from_querystring(querystring):
return (name, group_id, ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids)
+def process_group_ids_from_querystring(querystring):
+ group_ids = []
+ for key, value in querystring.items():
+ if 'GroupId' in key:
+ group_ids.append(value[0])
+ return group_ids
+
+
+def process_groupnames_from_querystring(querystring):
+ groupnames = []
+ for key, value in querystring.items():
+ if 'GroupName' in key:
+ groupnames.append(value[0])
+ return groupnames
+
+
class SecurityGroups(BaseResponse):
def authorize_security_group_egress(self):
raise NotImplementedError('SecurityGroups.authorize_security_group_egress is not yet implemented')
@@ -65,7 +82,16 @@ class SecurityGroups(BaseResponse):
return DELETE_GROUP_RESPONSE
def describe_security_groups(self):
- groups = ec2_backend.describe_security_groups()
+ groupnames = process_groupnames_from_querystring(self.querystring)
+ group_ids = process_group_ids_from_querystring(self.querystring)
+ filters = filters_from_querystring(self.querystring)
+
+ groups = ec2_backend.describe_security_groups(
+ group_ids=group_ids,
+ groupnames=groupnames,
+ filters=filters
+ )
+
template = Template(DESCRIBE_SECURITY_GROUPS_RESPONSE)
return template.render(groups=groups)
diff --git a/moto/iam/models.py b/moto/iam/models.py
index e949cf173..8e072f20a 100644
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -1,7 +1,9 @@
from __future__ import unicode_literals
-from moto.core import BaseBackend
-from .utils import random_resource_id
+from boto.exception import BotoServerError
+from moto.core import BaseBackend
+from .utils import random_access_key, random_alphanumeric, random_resource_id
+from datetime import datetime
class Role(object):
@@ -65,12 +67,91 @@ class Certificate(object):
return self.name
+class AccessKey(object):
+ def __init__(self, user_name):
+ self.user_name = user_name
+ self.access_key_id = random_access_key()
+ self.secret_access_key = random_alphanumeric(32)
+ self.status = 'Active'
+ self.create_date = datetime.strftime(
+ datetime.utcnow(),
+ "%Y-%m-%d-%H-%M-%S"
+ )
+
+
+class Group(object):
+ def __init__(self, name, path='/'):
+ self.name = name
+ self.id = random_resource_id()
+ self.path = path
+ self.created = datetime.strftime(
+ datetime.utcnow(),
+ "%Y-%m-%d-%H-%M-%S"
+ )
+
+ self.users = []
+
+
+class User(object):
+ def __init__(self, name, path='/'):
+ self.name = name
+ self.id = random_resource_id()
+ self.path = path
+ self.created = datetime.strftime(
+ datetime.utcnow(),
+ "%Y-%m-%d-%H-%M-%S"
+ )
+
+ self.policies = {}
+ self.access_keys = []
+
+ def get_policy(self, policy_name):
+ policy_json = None
+ try:
+ policy_json = self.policies[policy_name]
+ except:
+ raise BotoServerError(404, 'Not Found')
+
+ return {
+ 'policy_name': policy_name,
+ 'policy_document': policy_json,
+ 'user_name': self.name,
+ }
+
+ def put_policy(self, policy_name, policy_json):
+ self.policies[policy_name] = policy_json
+
+ def delete_policy(self, policy_name):
+ if policy_name not in self.policies:
+ raise BotoServerError(404, 'Not Found')
+
+ del self.policies[policy_name]
+
+ def create_access_key(self):
+ access_key = AccessKey(self.name)
+ self.access_keys.append(access_key)
+ return access_key
+
+ def get_all_access_keys(self):
+ return self.access_keys
+
+ def delete_access_key(self, access_key_id):
+ for key in self.access_keys:
+ if key.access_key_id == access_key_id:
+ self.access_keys.remove(key)
+ break
+ else:
+ raise BotoServerError(404, 'Not Found')
+
+
class IAMBackend(BaseBackend):
def __init__(self):
self.instance_profiles = {}
self.roles = {}
self.certificates = {}
+ self.groups = {}
+ self.users = {}
super(IAMBackend, self).__init__()
def create_role(self, role_name, assume_role_policy_document, path, policies):
@@ -125,4 +206,119 @@ class IAMBackend(BaseBackend):
if name == cert.cert_name:
return cert
+ def create_group(self, group_name, path='/'):
+ if group_name in self.groups:
+ raise BotoServerError(409, 'Conflict')
+
+ group = Group(group_name, path)
+ self.groups[group_name] = group
+ return group
+
+ def get_group(self, group_name, marker=None, max_items=None):
+ group = None
+ try:
+ group = self.groups[group_name]
+ except KeyError:
+ raise BotoServerError(404, 'Not Found')
+
+ return group
+
+ def create_user(self, user_name, path='/'):
+ if user_name in self.users:
+ raise BotoServerError(409, 'Conflict')
+
+ user = User(user_name, path)
+ self.users[user_name] = user
+ return user
+
+ def get_user(self, user_name):
+ user = None
+ try:
+ user = self.users[user_name]
+ except KeyError:
+ raise BotoServerError(404, 'Not Found')
+
+ return user
+
+ def add_user_to_group(self, group_name, user_name):
+ group = None
+ user = None
+
+ try:
+ group = self.groups[group_name]
+ user = self.users[user_name]
+ except KeyError:
+ raise BotoServerError(404, 'Not Found')
+
+ group.users.append(user)
+
+ def remove_user_from_group(self, group_name, user_name):
+ group = None
+ user = None
+
+ try:
+ group = self.groups[group_name]
+ user = self.users[user_name]
+ group.users.remove(user)
+ except (KeyError, ValueError):
+ raise BotoServerError(404, 'Not Found')
+
+ def get_user_policy(self, user_name, policy_name):
+ policy = None
+ try:
+ user = self.users[user_name]
+ policy = user.get_policy(policy_name)
+ except KeyError:
+ raise BotoServerError(404, 'Not Found')
+
+ return policy
+
+ def put_user_policy(self, user_name, policy_name, policy_json):
+ try:
+ user = self.users[user_name]
+ user.put_policy(policy_name, policy_json)
+ except KeyError:
+ raise BotoServerError(404, 'Not Found')
+
+ def delete_user_policy(self, user_name, policy_name):
+ try:
+ user = self.users[user_name]
+ user.delete_policy(policy_name)
+ except KeyError:
+ raise BotoServerError(404, 'Not Found')
+
+ def create_access_key(self, user_name=None):
+ key = None
+ try:
+ user = self.users[user_name]
+ key = user.create_access_key()
+ except KeyError:
+ raise BotoServerError(404, 'Not Found')
+
+ return key
+
+ def get_all_access_keys(self, user_name, marker=None, max_items=None):
+ keys = None
+ try:
+ user = self.users[user_name]
+ keys = user.get_all_access_keys()
+ except KeyError:
+ raise BotoServerError(404, 'Not Found')
+
+ return keys
+
+ def delete_access_key(self, access_key_id, user_name):
+ try:
+ user = self.users[user_name]
+ user.delete_access_key(access_key_id)
+ except KeyError:
+ raise BotoServerError(404, 'Not Found')
+
+ def delete_user(self, user_name):
+ try:
+ del self.users[user_name]
+ except KeyError:
+ raise BotoServerError(404, 'Not Found')
+
+
iam_backend = IAMBackend()
diff --git a/moto/iam/responses.py b/moto/iam/responses.py
index 35f1ad09b..3d6cf3c20 100644
--- a/moto/iam/responses.py
+++ b/moto/iam/responses.py
@@ -83,6 +83,114 @@ class IamResponse(BaseResponse):
template = Template(GET_SERVER_CERTIFICATE_TEMPLATE)
return template.render(certificate=cert)
+ def create_group(self):
+ group_name = self._get_param('GroupName')
+ path = self._get_param('Path')
+
+ group = iam_backend.create_group(group_name, path)
+ template = Template(CREATE_GROUP_TEMPLATE)
+ return template.render(group=group)
+
+ def get_group(self):
+ group_name = self._get_param('GroupName')
+
+ group = iam_backend.get_group(group_name)
+ template = Template(GET_GROUP_TEMPLATE)
+ return template.render(group=group)
+
+ def create_user(self):
+ user_name = self._get_param('UserName')
+ path = self._get_param('Path')
+
+ user = iam_backend.create_user(user_name, path)
+ template = Template(USER_TEMPLATE)
+ return template.render(action='Create', user=user)
+
+ def get_user(self):
+ user_name = self._get_param('UserName')
+ user = iam_backend.get_user(user_name)
+ template = Template(USER_TEMPLATE)
+ return template.render(action='Get', user=user)
+
+ def add_user_to_group(self):
+ group_name = self._get_param('GroupName')
+ user_name = self._get_param('UserName')
+
+ iam_backend.add_user_to_group(group_name, user_name)
+ template = Template(GENERIC_EMPTY_TEMPLATE)
+ return template.render(name='AddUserToGroup')
+
+ def remove_user_from_group(self):
+ group_name = self._get_param('GroupName')
+ user_name = self._get_param('UserName')
+
+ iam_backend.remove_user_from_group(group_name, user_name)
+ template = Template(GENERIC_EMPTY_TEMPLATE)
+ return template.render(name='RemoveUserFromGroup')
+
+ def get_user_policy(self):
+ user_name = self._get_param('UserName')
+ policy_name = self._get_param('PolicyName')
+
+ policy_document = iam_backend.get_user_policy(user_name, policy_name)
+ template = Template(GET_USER_POLICY_TEMPLATE)
+ return template.render(
+ user_name=user_name,
+ policy_name=policy_name,
+ policy_document=policy_document
+ )
+
+ def put_user_policy(self):
+ user_name = self._get_param('UserName')
+ policy_name = self._get_param('PolicyName')
+ policy_document = self._get_param('PolicyDocument')
+
+ iam_backend.put_user_policy(user_name, policy_name, policy_document)
+ template = Template(GENERIC_EMPTY_TEMPLATE)
+ return template.render(name='PutUserPolicy')
+
+ def delete_user_policy(self):
+ user_name = self._get_param('UserName')
+ policy_name = self._get_param('PolicyName')
+
+ iam_backend.delete_user_policy(user_name, policy_name)
+ template = Template(GENERIC_EMPTY_TEMPLATE)
+ return template.render(name='DeleteUserPolicy')
+
+ def create_access_key(self):
+ user_name = self._get_param('UserName')
+
+ key = iam_backend.create_access_key(user_name)
+ template = Template(CREATE_ACCESS_KEY_TEMPLATE)
+ return template.render(key=key)
+
+ def list_access_keys(self):
+ user_name = self._get_param('UserName')
+
+ keys = iam_backend.get_all_access_keys(user_name)
+ template = Template(LIST_ACCESS_KEYS_TEMPLATE)
+ return template.render(user_name=user_name, keys=keys)
+
+ def delete_access_key(self):
+ user_name = self._get_param('UserName')
+ access_key_id = self._get_param('AccessKeyId')
+
+ iam_backend.delete_access_key(access_key_id, user_name)
+ template = Template(GENERIC_EMPTY_TEMPLATE)
+ return template.render(name='DeleteAccessKey')
+
+ def delete_user(self):
+ user_name = self._get_param('UserName')
+ iam_backend.delete_user(user_name)
+ template = Template(GENERIC_EMPTY_TEMPLATE)
+ return template.render(name='DeleteUser')
+
+
+GENERIC_EMPTY_TEMPLATE = """<{{ name }}Response>
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+{{ name }}Response>"""
CREATE_INSTANCE_PROFILE_TEMPLATE = """
@@ -275,3 +383,106 @@ GET_SERVER_CERTIFICATE_TEMPLATE = """
"""
+CREATE_GROUP_TEMPLATE = """
+
+
+ {{ group.path }}
+ {{ group.name }}
+ {{ group.id }}
+ arn:aws:iam::123456789012:group/{{ group.path }}
+
+
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+"""
+
+GET_GROUP_TEMPLATE = """
+
+
+ {{ group.path }}
+ {{ group.name }}
+ {{ group.id }}
+ arn:aws:iam::123456789012:group/{{ group.path }}
+
+
+ {% for user in group.users %}
+
+ {{ user.path }}
+ {{ user.name }}
+ {{ user.id }}
+
+ arn:aws:iam::123456789012:user/{{ user.path }}/{{ user.name}}
+
+
+ {% endfor %}
+
+ false
+
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+"""
+
+USER_TEMPLATE = """<{{ action }}UserResponse>
+ <{{ action }}UserResult>
+
+ {{ user.path }}
+ {{ user.name }}
+ {{ user.id }}
+ arn:aws:iam::123456789012:user/{{ user.path }}/{{ user.name }}
+
+
+ {{ action }}UserResult>
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+{{ action }}UserResponse>"""
+
+GET_USER_POLICY_TEMPLATE = """
+
+ {{ user_name }}
+ {{ policy_name }}
+
+ {{ policy_document }}
+
+
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+"""
+
+CREATE_ACCESS_KEY_TEMPLATE = """
+
+
+ {{ key.user_name }}
+ {{ key.access_key_id }}
+ {{ key.status }}
+
+ {{ key.secret_access_key }}
+
+
+
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+"""
+
+LIST_ACCESS_KEYS_TEMPLATE = """
+
+ {{ user_name }}
+
+ {% for key in keys %}
+
+ {{ user_name }}
+ {{ key.access_key_id }}
+ {{ key.status }}
+
+ {% endfor %}
+
+ false
+
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+"""
diff --git a/moto/iam/utils.py b/moto/iam/utils.py
index 684548f60..14038b5fc 100644
--- a/moto/iam/utils.py
+++ b/moto/iam/utils.py
@@ -4,8 +4,24 @@ import string
import six
+def random_alphanumeric(length):
+ return ''.join(six.text_type(
+ random.choice(
+ string.ascii_letters + string.digits
+ )) for _ in range(length)
+ )
+
+
def random_resource_id():
size = 20
chars = list(range(10)) + list(string.ascii_lowercase)
return ''.join(six.text_type(random.choice(chars)) for x in range(size))
+
+
+def random_access_key():
+ return ''.join(six.text_type(
+ random.choice(
+ string.ascii_uppercase + string.digits
+ )) for _ in range(16)
+ )
diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py
index c624922ca..a2f90aff6 100644
--- a/tests/test_ec2/test_security_groups.py
+++ b/tests/test_ec2/test_security_groups.py
@@ -195,3 +195,22 @@ def test_authorize_group_in_vpc():
# And check that it gets revoked
security_group = [group for group in conn.get_all_security_groups() if group.name == 'test1'][0]
security_group.rules.should.have.length_of(0)
+
+
+@mock_ec2
+def test_get_all_security_groups():
+ conn = boto.connect_ec2()
+ conn.create_security_group(name='test1', description='test1', vpc_id='vpc-mjm05d27')
+ conn.create_security_group(name='test2', description='test2')
+
+ resp = conn.get_all_security_groups(groupnames=['test1'])
+ resp.should.have.length_of(1)
+
+ resp = conn.get_all_security_groups(filters={'vpc_id': ['vpc-mjm05d27']})
+ resp.should.have.length_of(1)
+
+ resp = conn.get_all_security_groups(filters={'description': ['test1']})
+ resp.should.have.length_of(1)
+
+ resp = conn.get_all_security_groups()
+ resp.should.have.length_of(2)
diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py
index c2d8c2480..b5f75f578 100644
--- a/tests/test_iam/test_iam.py
+++ b/tests/test_iam/test_iam.py
@@ -1,8 +1,10 @@
from __future__ import unicode_literals
import boto
-
import sure # noqa
+from nose.tools import assert_raises, assert_equals, assert_not_equals
+from boto.exception import BotoServerError
+
from moto import mock_iam
@@ -58,3 +60,105 @@ def test_create_role_and_instance_profile():
conn.list_roles().roles[0].role_name.should.equal('my-role')
conn.list_instance_profiles().instance_profiles[0].instance_profile_name.should.equal("my-profile")
+
+
+@mock_iam()
+def test_create_group():
+ conn = boto.connect_iam()
+ conn.create_group('my-group')
+ with assert_raises(BotoServerError):
+ conn.create_group('my-group')
+
+
+@mock_iam()
+def test_get_group():
+ conn = boto.connect_iam()
+ conn.create_group('my-group')
+ conn.get_group('my-group')
+ with assert_raises(BotoServerError):
+ conn.get_group('not-group')
+
+
+@mock_iam()
+def test_create_user():
+ conn = boto.connect_iam()
+ conn.create_user('my-user')
+ with assert_raises(BotoServerError):
+ conn.create_user('my-user')
+
+
+@mock_iam()
+def test_get_user():
+ conn = boto.connect_iam()
+ with assert_raises(BotoServerError):
+ conn.get_user('my-user')
+ conn.create_user('my-user')
+ conn.get_user('my-user')
+
+
+@mock_iam()
+def test_add_user_to_group():
+ conn = boto.connect_iam()
+ with assert_raises(BotoServerError):
+ conn.add_user_to_group('my-group', 'my-user')
+ conn.create_group('my-group')
+ with assert_raises(BotoServerError):
+ conn.add_user_to_group('my-group', 'my-user')
+ conn.create_user('my-user')
+ conn.add_user_to_group('my-group', 'my-user')
+
+
+@mock_iam()
+def test_remove_user_from_group():
+ conn = boto.connect_iam()
+ with assert_raises(BotoServerError):
+ conn.remove_user_from_group('my-group', 'my-user')
+ conn.create_group('my-group')
+ conn.create_user('my-user')
+ with assert_raises(BotoServerError):
+ conn.remove_user_from_group('my-group', 'my-user')
+ conn.add_user_to_group('my-group', 'my-user')
+ conn.remove_user_from_group('my-group', 'my-user')
+
+
+@mock_iam()
+def test_create_access_key():
+ conn = boto.connect_iam()
+ with assert_raises(BotoServerError):
+ conn.create_access_key('my-user')
+ conn.create_user('my-user')
+ conn.create_access_key('my-user')
+
+
+@mock_iam()
+def test_get_all_access_keys():
+ conn = boto.connect_iam()
+ conn.create_user('my-user')
+ response = conn.get_all_access_keys('my-user')
+ assert_equals(
+ response['list_access_keys_response']['list_access_keys_result']['access_key_metadata'],
+ []
+ )
+ conn.create_access_key('my-user')
+ response = conn.get_all_access_keys('my-user')
+ assert_not_equals(
+ response['list_access_keys_response']['list_access_keys_result']['access_key_metadata'],
+ []
+ )
+
+
+@mock_iam()
+def test_delete_access_key():
+ conn = boto.connect_iam()
+ conn.create_user('my-user')
+ access_key_id = conn.create_access_key('my-user')['create_access_key_response']['create_access_key_result']['access_key']['access_key_id']
+ conn.delete_access_key(access_key_id, 'my-user')
+
+
+@mock_iam()
+def test_delete_user():
+ conn = boto.connect_iam()
+ with assert_raises(BotoServerError):
+ conn.delete_user('my-user')
+ conn.create_user('my-user')
+ conn.delete_user('my-user')