2014-08-27 15:17:06 +00:00
|
|
|
from __future__ import unicode_literals
|
2016-10-17 22:09:46 +00:00
|
|
|
import base64
|
2019-11-16 23:20:33 +00:00
|
|
|
import hashlib
|
2019-10-20 20:39:57 +00:00
|
|
|
import os
|
|
|
|
import random
|
|
|
|
import string
|
2018-10-25 01:00:52 +00:00
|
|
|
import sys
|
2016-10-17 22:09:46 +00:00
|
|
|
from datetime import datetime
|
2017-09-19 21:01:08 +00:00
|
|
|
import json
|
2019-01-30 02:09:31 +00:00
|
|
|
import re
|
2014-03-27 23:12:53 +00:00
|
|
|
|
2018-10-25 01:00:52 +00:00
|
|
|
from cryptography import x509
|
|
|
|
from cryptography.hazmat.backends import default_backend
|
2019-10-18 15:29:15 +00:00
|
|
|
from six.moves.urllib.parse import urlparse
|
2018-11-07 21:58:26 +00:00
|
|
|
from uuid import uuid4
|
2018-10-25 01:00:52 +00:00
|
|
|
|
2019-05-21 16:44:06 +00:00
|
|
|
from moto.core.exceptions import RESTError
|
2019-12-17 02:05:29 +00:00
|
|
|
from moto.core import BaseBackend, BaseModel, ACCOUNT_ID
|
2019-10-31 15:44:26 +00:00
|
|
|
from moto.core.utils import (
|
|
|
|
iso_8601_datetime_without_milliseconds,
|
|
|
|
iso_8601_datetime_with_milliseconds,
|
|
|
|
)
|
2019-06-30 15:09:55 +00:00
|
|
|
from moto.iam.policy_validation import IAMPolicyDocumentValidator
|
2016-10-17 22:09:46 +00:00
|
|
|
|
2017-09-19 21:01:08 +00:00
|
|
|
from .aws_managed_policies import aws_managed_policies_data
|
2019-10-31 15:44:26 +00:00
|
|
|
from .exceptions import (
|
|
|
|
IAMNotFoundException,
|
|
|
|
IAMConflictException,
|
|
|
|
IAMReportNotPresentException,
|
|
|
|
IAMLimitExceededException,
|
|
|
|
MalformedCertificate,
|
|
|
|
DuplicateTags,
|
|
|
|
TagKeyTooBig,
|
|
|
|
InvalidTagCharacters,
|
|
|
|
TooManyTags,
|
|
|
|
TagValueTooBig,
|
|
|
|
EntityAlreadyExists,
|
|
|
|
ValidationError,
|
|
|
|
InvalidInput,
|
2019-10-28 22:50:17 +00:00
|
|
|
NoSuchEntity,
|
2019-10-31 15:44:26 +00:00
|
|
|
)
|
|
|
|
from .utils import (
|
|
|
|
random_access_key,
|
|
|
|
random_alphanumeric,
|
|
|
|
random_resource_id,
|
|
|
|
random_policy_id,
|
|
|
|
)
|
2016-10-17 22:09:46 +00:00
|
|
|
|
|
|
|
|
2017-03-27 18:08:57 +00:00
|
|
|
class MFADevice(object):
|
|
|
|
"""MFA Device class."""
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
def __init__(self, serial_number, authentication_code_1, authentication_code_2):
|
2019-06-02 18:18:50 +00:00
|
|
|
self.enable_date = datetime.utcnow()
|
2017-03-27 18:08:57 +00:00
|
|
|
self.serial_number = serial_number
|
|
|
|
self.authentication_code_1 = authentication_code_1
|
|
|
|
self.authentication_code_2 = authentication_code_2
|
|
|
|
|
2019-06-02 18:18:50 +00:00
|
|
|
@property
|
|
|
|
def enabled_iso_8601(self):
|
|
|
|
return iso_8601_datetime_without_milliseconds(self.enable_date)
|
|
|
|
|
2017-03-27 18:08:57 +00:00
|
|
|
|
2019-10-20 20:39:57 +00:00
|
|
|
class VirtualMfaDevice(object):
|
|
|
|
def __init__(self, device_name):
|
2019-10-31 15:44:26 +00:00
|
|
|
self.serial_number = "arn:aws:iam::{0}:mfa{1}".format(ACCOUNT_ID, device_name)
|
2019-10-20 20:39:57 +00:00
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
random_base32_string = "".join(
|
|
|
|
random.choice(string.ascii_uppercase + "234567") for _ in range(64)
|
|
|
|
)
|
|
|
|
self.base32_string_seed = base64.b64encode(
|
|
|
|
random_base32_string.encode("ascii")
|
|
|
|
).decode("ascii")
|
|
|
|
self.qr_code_png = base64.b64encode(
|
|
|
|
os.urandom(64)
|
|
|
|
) # this would be a generated PNG
|
2019-10-20 20:39:57 +00:00
|
|
|
|
|
|
|
self.enable_date = None
|
|
|
|
self.user_attribute = None
|
2019-10-21 19:48:50 +00:00
|
|
|
self.user = None
|
2019-10-20 20:39:57 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def enabled_iso_8601(self):
|
|
|
|
return iso_8601_datetime_without_milliseconds(self.enable_date)
|
|
|
|
|
|
|
|
|
2017-03-12 04:41:12 +00:00
|
|
|
class Policy(BaseModel):
|
2016-10-17 22:09:46 +00:00
|
|
|
is_attachable = False
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
name,
|
|
|
|
default_version_id=None,
|
|
|
|
description=None,
|
|
|
|
document=None,
|
|
|
|
path=None,
|
|
|
|
create_date=None,
|
|
|
|
update_date=None,
|
|
|
|
):
|
2016-10-17 22:09:46 +00:00
|
|
|
self.name = name
|
|
|
|
|
|
|
|
self.attachment_count = 0
|
2019-10-31 15:44:26 +00:00
|
|
|
self.description = description or ""
|
2016-10-17 22:09:46 +00:00
|
|
|
self.id = random_policy_id()
|
2019-10-31 15:44:26 +00:00
|
|
|
self.path = path or "/"
|
2019-04-16 19:29:48 +00:00
|
|
|
|
|
|
|
if default_version_id:
|
|
|
|
self.default_version_id = default_version_id
|
2019-10-31 15:44:26 +00:00
|
|
|
self.next_version_num = int(default_version_id.lstrip("v")) + 1
|
2019-04-16 19:29:48 +00:00
|
|
|
else:
|
2019-10-31 15:44:26 +00:00
|
|
|
self.default_version_id = "v1"
|
2019-04-16 19:29:48 +00:00
|
|
|
self.next_version_num = 2
|
2019-10-31 15:44:26 +00:00
|
|
|
self.versions = [
|
|
|
|
PolicyVersion(
|
|
|
|
self.arn, document, True, self.default_version_id, update_date
|
|
|
|
)
|
|
|
|
]
|
2016-10-17 22:09:46 +00:00
|
|
|
|
2019-06-06 12:36:39 +00:00
|
|
|
self.create_date = create_date if create_date is not None else datetime.utcnow()
|
|
|
|
self.update_date = update_date if update_date is not None else datetime.utcnow()
|
2019-06-02 18:18:50 +00:00
|
|
|
|
2019-06-29 16:55:19 +00:00
|
|
|
def update_default_version(self, new_default_version_id):
|
|
|
|
for version in self.versions:
|
|
|
|
if version.version_id == self.default_version_id:
|
|
|
|
version.is_default = False
|
|
|
|
break
|
|
|
|
self.default_version_id = new_default_version_id
|
|
|
|
|
2019-06-02 18:18:50 +00:00
|
|
|
@property
|
|
|
|
def created_iso_8601(self):
|
|
|
|
return iso_8601_datetime_with_milliseconds(self.create_date)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def updated_iso_8601(self):
|
|
|
|
return iso_8601_datetime_with_milliseconds(self.update_date)
|
2016-10-17 22:09:46 +00:00
|
|
|
|
|
|
|
|
2018-11-19 23:47:21 +00:00
|
|
|
class SAMLProvider(BaseModel):
|
|
|
|
def __init__(self, name, saml_metadata_document=None):
|
|
|
|
self.name = name
|
|
|
|
self.saml_metadata_document = saml_metadata_document
|
|
|
|
|
|
|
|
@property
|
|
|
|
def arn(self):
|
|
|
|
return "arn:aws:iam::{0}:saml-provider/{1}".format(ACCOUNT_ID, self.name)
|
|
|
|
|
|
|
|
|
2019-10-18 15:29:15 +00:00
|
|
|
class OpenIDConnectProvider(BaseModel):
|
|
|
|
def __init__(self, url, thumbprint_list, client_id_list=None):
|
|
|
|
self._errors = []
|
|
|
|
self._validate(url, thumbprint_list, client_id_list)
|
|
|
|
|
|
|
|
parsed_url = urlparse(url)
|
|
|
|
self.url = parsed_url.netloc + parsed_url.path
|
|
|
|
self.thumbprint_list = thumbprint_list
|
|
|
|
self.client_id_list = client_id_list
|
2019-10-18 18:37:35 +00:00
|
|
|
self.create_date = datetime.utcnow()
|
2019-10-18 15:29:15 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def arn(self):
|
2019-10-31 15:44:26 +00:00
|
|
|
return "arn:aws:iam::{0}:oidc-provider/{1}".format(ACCOUNT_ID, self.url)
|
2019-10-18 15:29:15 +00:00
|
|
|
|
2019-10-18 18:37:35 +00:00
|
|
|
@property
|
|
|
|
def created_iso_8601(self):
|
|
|
|
return iso_8601_datetime_without_milliseconds(self.create_date)
|
|
|
|
|
2019-10-18 15:29:15 +00:00
|
|
|
def _validate(self, url, thumbprint_list, client_id_list):
|
|
|
|
if any(len(client_id) > 255 for client_id in client_id_list):
|
2019-10-31 15:44:26 +00:00
|
|
|
self._errors.append(
|
|
|
|
self._format_error(
|
|
|
|
key="clientIDList",
|
|
|
|
value=client_id_list,
|
|
|
|
constraint="Member must satisfy constraint: "
|
|
|
|
"[Member must have length less than or equal to 255, "
|
|
|
|
"Member must have length greater than or equal to 1]",
|
|
|
|
)
|
|
|
|
)
|
2019-10-18 15:29:15 +00:00
|
|
|
|
|
|
|
if any(len(thumbprint) > 40 for thumbprint in thumbprint_list):
|
2019-10-31 15:44:26 +00:00
|
|
|
self._errors.append(
|
|
|
|
self._format_error(
|
|
|
|
key="thumbprintList",
|
|
|
|
value=thumbprint_list,
|
|
|
|
constraint="Member must satisfy constraint: "
|
|
|
|
"[Member must have length less than or equal to 40, "
|
|
|
|
"Member must have length greater than or equal to 40]",
|
|
|
|
)
|
|
|
|
)
|
2019-10-18 15:29:15 +00:00
|
|
|
|
|
|
|
if len(url) > 255:
|
2019-10-31 15:44:26 +00:00
|
|
|
self._errors.append(
|
|
|
|
self._format_error(
|
|
|
|
key="url",
|
|
|
|
value=url,
|
|
|
|
constraint="Member must have length less than or equal to 255",
|
|
|
|
)
|
|
|
|
)
|
2019-10-18 15:29:15 +00:00
|
|
|
|
|
|
|
self._raise_errors()
|
|
|
|
|
|
|
|
parsed_url = urlparse(url)
|
|
|
|
if not parsed_url.scheme or not parsed_url.netloc:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise ValidationError("Invalid Open ID Connect Provider URL")
|
2019-10-18 15:29:15 +00:00
|
|
|
|
|
|
|
if len(thumbprint_list) > 5:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise InvalidInput("Thumbprint list must contain fewer than 5 entries.")
|
2019-10-18 15:29:15 +00:00
|
|
|
|
|
|
|
if len(client_id_list) > 100:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMLimitExceededException(
|
|
|
|
"Cannot exceed quota for ClientIdsPerOpenIdConnectProvider: 100"
|
|
|
|
)
|
2019-10-18 15:29:15 +00:00
|
|
|
|
|
|
|
def _format_error(self, key, value, constraint):
|
|
|
|
return 'Value "{value}" at "{key}" failed to satisfy constraint: {constraint}'.format(
|
2019-10-31 15:44:26 +00:00
|
|
|
constraint=constraint, key=key, value=value
|
2019-10-18 15:29:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def _raise_errors(self):
|
|
|
|
if self._errors:
|
|
|
|
count = len(self._errors)
|
|
|
|
plural = "s" if len(self._errors) > 1 else ""
|
|
|
|
errors = "; ".join(self._errors)
|
|
|
|
self._errors = [] # reset collected errors
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
raise ValidationError(
|
|
|
|
"{count} validation error{plural} detected: {errors}".format(
|
|
|
|
count=count, plural=plural, errors=errors
|
|
|
|
)
|
|
|
|
)
|
2019-10-18 15:29:15 +00:00
|
|
|
|
|
|
|
|
2017-05-15 21:56:30 +00:00
|
|
|
class PolicyVersion(object):
|
2019-10-31 15:44:26 +00:00
|
|
|
def __init__(
|
|
|
|
self, policy_arn, document, is_default=False, version_id="v1", create_date=None
|
|
|
|
):
|
2017-05-15 21:56:28 +00:00
|
|
|
self.policy_arn = policy_arn
|
|
|
|
self.document = document or {}
|
2017-05-15 21:56:30 +00:00
|
|
|
self.is_default = is_default
|
2019-06-06 12:36:39 +00:00
|
|
|
self.version_id = version_id
|
2017-05-15 21:56:28 +00:00
|
|
|
|
2019-06-06 12:36:39 +00:00
|
|
|
self.create_date = create_date if create_date is not None else datetime.utcnow()
|
2019-06-02 18:18:50 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def created_iso_8601(self):
|
|
|
|
return iso_8601_datetime_with_milliseconds(self.create_date)
|
2017-05-15 21:56:28 +00:00
|
|
|
|
|
|
|
|
2016-10-17 22:09:46 +00:00
|
|
|
class ManagedPolicy(Policy):
|
|
|
|
"""Managed policy."""
|
|
|
|
|
|
|
|
is_attachable = True
|
|
|
|
|
2017-10-01 22:04:59 +00:00
|
|
|
def attach_to(self, obj):
|
2016-10-17 22:09:46 +00:00
|
|
|
self.attachment_count += 1
|
2018-08-07 20:59:15 +00:00
|
|
|
obj.managed_policies[self.arn] = self
|
2016-10-17 22:09:46 +00:00
|
|
|
|
2017-10-01 22:04:59 +00:00
|
|
|
def detach_from(self, obj):
|
2017-08-14 04:58:11 +00:00
|
|
|
self.attachment_count -= 1
|
2018-08-07 20:59:15 +00:00
|
|
|
del obj.managed_policies[self.arn]
|
2017-08-12 00:57:06 +00:00
|
|
|
|
2018-07-13 14:41:22 +00:00
|
|
|
@property
|
|
|
|
def arn(self):
|
|
|
|
return "arn:aws:iam::{0}:policy{1}{2}".format(ACCOUNT_ID, self.path, self.name)
|
|
|
|
|
2016-10-17 22:09:46 +00:00
|
|
|
|
|
|
|
class AWSManagedPolicy(ManagedPolicy):
|
|
|
|
"""AWS-managed policy."""
|
|
|
|
|
2017-09-19 21:01:08 +00:00
|
|
|
@classmethod
|
|
|
|
def from_data(cls, name, data):
|
2019-10-31 15:44:26 +00:00
|
|
|
return cls(
|
|
|
|
name,
|
|
|
|
default_version_id=data.get("DefaultVersionId"),
|
|
|
|
path=data.get("Path"),
|
|
|
|
document=json.dumps(data.get("Document")),
|
|
|
|
create_date=datetime.strptime(
|
|
|
|
data.get("CreateDate"), "%Y-%m-%dT%H:%M:%S+00:00"
|
|
|
|
),
|
|
|
|
update_date=datetime.strptime(
|
|
|
|
data.get("UpdateDate"), "%Y-%m-%dT%H:%M:%S+00:00"
|
|
|
|
),
|
|
|
|
)
|
2017-09-19 21:01:08 +00:00
|
|
|
|
2018-07-13 14:41:22 +00:00
|
|
|
@property
|
|
|
|
def arn(self):
|
2019-10-31 15:44:26 +00:00
|
|
|
return "arn:aws:iam::aws:policy{0}{1}".format(self.path, self.name)
|
2018-07-13 14:41:22 +00:00
|
|
|
|
2017-09-19 21:19:29 +00:00
|
|
|
|
2017-09-19 21:01:08 +00:00
|
|
|
# AWS defines some of its own managed policies and we periodically
|
|
|
|
# import them via `make aws_managed_policies`
|
|
|
|
aws_managed_policies = [
|
2019-10-31 15:44:26 +00:00
|
|
|
AWSManagedPolicy.from_data(name, d)
|
|
|
|
for name, d in json.loads(aws_managed_policies_data).items()
|
|
|
|
]
|
2017-09-19 21:19:29 +00:00
|
|
|
|
2016-10-17 22:09:46 +00:00
|
|
|
|
|
|
|
class InlinePolicy(Policy):
|
|
|
|
"""TODO: is this needed?"""
|
2014-03-27 23:12:53 +00:00
|
|
|
|
2015-04-30 23:32:53 +00:00
|
|
|
|
2017-03-12 04:41:12 +00:00
|
|
|
class Role(BaseModel):
|
2019-10-31 15:44:26 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
role_id,
|
|
|
|
name,
|
|
|
|
assume_role_policy_document,
|
|
|
|
path,
|
|
|
|
permissions_boundary,
|
|
|
|
description,
|
|
|
|
tags,
|
2019-11-24 18:19:09 +00:00
|
|
|
max_session_duration,
|
2019-10-31 15:44:26 +00:00
|
|
|
):
|
2014-03-27 23:12:53 +00:00
|
|
|
self.id = role_id
|
|
|
|
self.name = name
|
|
|
|
self.assume_role_policy_document = assume_role_policy_document
|
2019-10-31 15:44:26 +00:00
|
|
|
self.path = path or "/"
|
2014-11-30 19:16:29 +00:00
|
|
|
self.policies = {}
|
2016-10-17 22:09:46 +00:00
|
|
|
self.managed_policies = {}
|
2019-06-02 18:18:50 +00:00
|
|
|
self.create_date = datetime.utcnow()
|
2019-08-21 19:24:23 +00:00
|
|
|
self.tags = tags
|
|
|
|
self.description = description
|
2019-05-21 16:44:06 +00:00
|
|
|
self.permissions_boundary = permissions_boundary
|
2019-11-24 18:19:09 +00:00
|
|
|
self.max_session_duration = max_session_duration
|
2014-03-27 23:12:53 +00:00
|
|
|
|
2019-06-02 18:18:50 +00:00
|
|
|
@property
|
|
|
|
def created_iso_8601(self):
|
|
|
|
return iso_8601_datetime_with_milliseconds(self.create_date)
|
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
@classmethod
|
2019-10-31 15:44:26 +00:00
|
|
|
def create_from_cloudformation_json(
|
|
|
|
cls, resource_name, cloudformation_json, region_name
|
|
|
|
):
|
|
|
|
properties = cloudformation_json["Properties"]
|
2020-03-11 13:19:40 +00:00
|
|
|
role_name = (
|
|
|
|
properties["RoleName"] if "RoleName" in properties else str(uuid4())[0:5]
|
|
|
|
)
|
2014-03-27 23:12:53 +00:00
|
|
|
|
2014-12-07 00:37:10 +00:00
|
|
|
role = iam_backend.create_role(
|
2018-11-07 21:58:26 +00:00
|
|
|
role_name=role_name,
|
2019-10-31 15:44:26 +00:00
|
|
|
assume_role_policy_document=properties["AssumeRolePolicyDocument"],
|
|
|
|
path=properties.get("Path", "/"),
|
|
|
|
permissions_boundary=properties.get("PermissionsBoundary", ""),
|
|
|
|
description=properties.get("Description", ""),
|
|
|
|
tags=properties.get("Tags", {}),
|
2019-11-24 19:07:53 +00:00
|
|
|
max_session_duration=properties.get("MaxSessionDuration", 3600),
|
2014-03-27 23:12:53 +00:00
|
|
|
)
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
policies = properties.get("Policies", [])
|
2014-12-07 00:37:10 +00:00
|
|
|
for policy in policies:
|
2019-10-31 15:44:26 +00:00
|
|
|
policy_name = policy["PolicyName"]
|
|
|
|
policy_json = policy["PolicyDocument"]
|
2014-12-07 00:37:10 +00:00
|
|
|
role.put_policy(policy_name, policy_json)
|
|
|
|
|
|
|
|
return role
|
|
|
|
|
2017-03-19 15:58:24 +00:00
|
|
|
@property
|
|
|
|
def arn(self):
|
|
|
|
return "arn:aws:iam::{0}:role{1}{2}".format(ACCOUNT_ID, self.path, self.name)
|
|
|
|
|
2014-12-01 04:11:13 +00:00
|
|
|
def put_policy(self, policy_name, policy_json):
|
|
|
|
self.policies[policy_name] = policy_json
|
|
|
|
|
2017-08-14 04:58:11 +00:00
|
|
|
def delete_policy(self, policy_name):
|
|
|
|
try:
|
|
|
|
del self.policies[policy_name]
|
|
|
|
except KeyError:
|
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"The role policy with name {0} cannot be found.".format(policy_name)
|
|
|
|
)
|
2017-08-14 04:58:11 +00:00
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
@property
|
|
|
|
def physical_resource_id(self):
|
|
|
|
return self.id
|
|
|
|
|
2014-10-21 16:45:03 +00:00
|
|
|
def get_cfn_attribute(self, attribute_name):
|
2014-10-21 18:51:26 +00:00
|
|
|
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
2019-10-31 15:44:26 +00:00
|
|
|
|
|
|
|
if attribute_name == "Arn":
|
2019-11-07 19:18:03 +00:00
|
|
|
return self.arn
|
2014-10-21 18:51:26 +00:00
|
|
|
raise UnformattedGetAttTemplateException()
|
2014-10-21 16:45:03 +00:00
|
|
|
|
2019-01-30 02:09:31 +00:00
|
|
|
def get_tags(self):
|
|
|
|
return [self.tags[tag] for tag in self.tags]
|
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
|
2017-03-12 04:41:12 +00:00
|
|
|
class InstanceProfile(BaseModel):
|
2014-03-27 23:12:53 +00:00
|
|
|
def __init__(self, instance_profile_id, name, path, roles):
|
|
|
|
self.id = instance_profile_id
|
|
|
|
self.name = name
|
2019-10-31 15:44:26 +00:00
|
|
|
self.path = path or "/"
|
2014-03-27 23:12:53 +00:00
|
|
|
self.roles = roles if roles else []
|
2019-06-02 18:18:50 +00:00
|
|
|
self.create_date = datetime.utcnow()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def created_iso_8601(self):
|
|
|
|
return iso_8601_datetime_with_milliseconds(self.create_date)
|
2014-03-27 23:12:53 +00:00
|
|
|
|
|
|
|
@classmethod
|
2019-10-31 15:44:26 +00:00
|
|
|
def create_from_cloudformation_json(
|
|
|
|
cls, resource_name, cloudformation_json, region_name
|
|
|
|
):
|
|
|
|
properties = cloudformation_json["Properties"]
|
2014-03-27 23:12:53 +00:00
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
role_ids = properties["Roles"]
|
2014-03-27 23:12:53 +00:00
|
|
|
return iam_backend.create_instance_profile(
|
2019-10-31 15:44:26 +00:00
|
|
|
name=resource_name, path=properties.get("Path", "/"), role_ids=role_ids
|
2014-03-27 23:12:53 +00:00
|
|
|
)
|
|
|
|
|
2017-03-19 15:58:24 +00:00
|
|
|
@property
|
|
|
|
def arn(self):
|
2019-10-31 15:44:26 +00:00
|
|
|
return "arn:aws:iam::{0}:instance-profile{1}{2}".format(
|
|
|
|
ACCOUNT_ID, self.path, self.name
|
|
|
|
)
|
2017-03-19 15:58:24 +00:00
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
@property
|
|
|
|
def physical_resource_id(self):
|
|
|
|
return self.name
|
|
|
|
|
2014-10-21 16:45:03 +00:00
|
|
|
def get_cfn_attribute(self, attribute_name):
|
2014-10-21 18:51:26 +00:00
|
|
|
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
2019-10-31 15:44:26 +00:00
|
|
|
|
|
|
|
if attribute_name == "Arn":
|
2019-03-26 18:36:31 +00:00
|
|
|
return self.arn
|
2014-10-21 18:51:26 +00:00
|
|
|
raise UnformattedGetAttTemplateException()
|
2014-10-21 16:45:03 +00:00
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
|
2017-03-12 04:41:12 +00:00
|
|
|
class Certificate(BaseModel):
|
2014-05-29 21:25:26 +00:00
|
|
|
def __init__(self, cert_name, cert_body, private_key, cert_chain=None, path=None):
|
|
|
|
self.cert_name = cert_name
|
|
|
|
self.cert_body = cert_body
|
|
|
|
self.private_key = private_key
|
2017-03-19 15:58:24 +00:00
|
|
|
self.path = path if path else "/"
|
2014-05-29 21:25:26 +00:00
|
|
|
self.cert_chain = cert_chain
|
|
|
|
|
|
|
|
@property
|
|
|
|
def physical_resource_id(self):
|
|
|
|
return self.name
|
|
|
|
|
2017-03-19 15:58:24 +00:00
|
|
|
@property
|
|
|
|
def arn(self):
|
2019-10-31 15:44:26 +00:00
|
|
|
return "arn:aws:iam::{0}:server-certificate{1}{2}".format(
|
|
|
|
ACCOUNT_ID, self.path, self.cert_name
|
|
|
|
)
|
2017-03-19 15:58:24 +00:00
|
|
|
|
2014-05-29 21:25:26 +00:00
|
|
|
|
2018-10-25 01:00:52 +00:00
|
|
|
class SigningCertificate(BaseModel):
|
|
|
|
def __init__(self, id, user_name, body):
|
|
|
|
self.id = id
|
|
|
|
self.user_name = user_name
|
|
|
|
self.body = body
|
2019-06-02 18:18:50 +00:00
|
|
|
self.upload_date = datetime.utcnow()
|
2019-10-31 15:44:26 +00:00
|
|
|
self.status = "Active"
|
2018-10-25 01:00:52 +00:00
|
|
|
|
2019-06-02 18:18:50 +00:00
|
|
|
@property
|
|
|
|
def uploaded_iso_8601(self):
|
|
|
|
return iso_8601_datetime_without_milliseconds(self.upload_date)
|
|
|
|
|
2018-10-25 01:00:52 +00:00
|
|
|
|
2017-03-12 04:41:12 +00:00
|
|
|
class AccessKey(BaseModel):
|
2014-08-19 21:30:11 +00:00
|
|
|
def __init__(self, user_name):
|
|
|
|
self.user_name = user_name
|
2019-07-04 18:20:08 +00:00
|
|
|
self.access_key_id = "AKIA" + random_access_key()
|
|
|
|
self.secret_access_key = random_alphanumeric(40)
|
2019-10-31 15:44:26 +00:00
|
|
|
self.status = "Active"
|
2019-06-02 18:18:50 +00:00
|
|
|
self.create_date = datetime.utcnow()
|
|
|
|
self.last_used = datetime.utcnow()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def created_iso_8601(self):
|
|
|
|
return iso_8601_datetime_without_milliseconds(self.create_date)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def last_used_iso_8601(self):
|
|
|
|
return iso_8601_datetime_without_milliseconds(self.last_used)
|
2014-08-19 21:30:11 +00:00
|
|
|
|
2014-10-21 16:45:03 +00:00
|
|
|
def get_cfn_attribute(self, attribute_name):
|
2014-10-21 18:51:26 +00:00
|
|
|
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
2019-10-31 15:44:26 +00:00
|
|
|
|
|
|
|
if attribute_name == "SecretAccessKey":
|
2014-10-21 16:45:03 +00:00
|
|
|
return self.secret_access_key
|
2014-10-21 18:51:26 +00:00
|
|
|
raise UnformattedGetAttTemplateException()
|
2014-10-21 16:45:03 +00:00
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
|
2019-11-16 23:20:33 +00:00
|
|
|
class SshPublicKey(BaseModel):
|
|
|
|
def __init__(self, user_name, ssh_public_key_body):
|
|
|
|
self.user_name = user_name
|
|
|
|
self.ssh_public_key_body = ssh_public_key_body
|
|
|
|
self.ssh_public_key_id = "APKA" + random_access_key()
|
|
|
|
self.fingerprint = hashlib.md5(ssh_public_key_body.encode()).hexdigest()
|
|
|
|
self.status = "Active"
|
|
|
|
self.upload_date = datetime.utcnow()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def uploaded_iso_8601(self):
|
|
|
|
return iso_8601_datetime_without_milliseconds(self.upload_date)
|
|
|
|
|
|
|
|
|
2017-03-12 04:41:12 +00:00
|
|
|
class Group(BaseModel):
|
2019-10-31 15:44:26 +00:00
|
|
|
def __init__(self, name, path="/"):
|
2014-08-19 21:30:11 +00:00
|
|
|
self.name = name
|
|
|
|
self.id = random_resource_id()
|
|
|
|
self.path = path
|
2019-06-02 18:18:50 +00:00
|
|
|
self.create_date = datetime.utcnow()
|
2014-08-19 21:30:11 +00:00
|
|
|
|
|
|
|
self.users = []
|
2017-10-01 22:01:33 +00:00
|
|
|
self.managed_policies = {}
|
2017-03-05 03:56:36 +00:00
|
|
|
self.policies = {}
|
2014-08-19 21:30:11 +00:00
|
|
|
|
2019-06-02 18:18:50 +00:00
|
|
|
@property
|
|
|
|
def created_iso_8601(self):
|
|
|
|
return iso_8601_datetime_with_milliseconds(self.create_date)
|
|
|
|
|
2014-10-21 16:45:03 +00:00
|
|
|
def get_cfn_attribute(self, attribute_name):
|
2014-10-21 18:51:26 +00:00
|
|
|
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
2019-10-31 15:44:26 +00:00
|
|
|
|
|
|
|
if attribute_name == "Arn":
|
2014-10-21 16:45:03 +00:00
|
|
|
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "Arn" ]"')
|
2014-10-21 18:51:26 +00:00
|
|
|
raise UnformattedGetAttTemplateException()
|
2014-10-21 16:45:03 +00:00
|
|
|
|
2017-03-19 15:58:24 +00:00
|
|
|
@property
|
|
|
|
def arn(self):
|
2019-10-31 15:44:26 +00:00
|
|
|
if self.path == "/":
|
2018-10-17 00:14:23 +00:00
|
|
|
return "arn:aws:iam::{0}:group/{1}".format(ACCOUNT_ID, self.name)
|
|
|
|
|
|
|
|
else:
|
2019-10-31 15:44:26 +00:00
|
|
|
return "arn:aws:iam::{0}:group/{1}/{2}".format(
|
|
|
|
ACCOUNT_ID, self.path, self.name
|
|
|
|
)
|
2018-10-17 00:14:23 +00:00
|
|
|
|
2017-03-05 03:56:36 +00:00
|
|
|
def get_policy(self, policy_name):
|
|
|
|
try:
|
|
|
|
policy_json = self.policies[policy_name]
|
|
|
|
except KeyError:
|
|
|
|
raise IAMNotFoundException("Policy {0} not found".format(policy_name))
|
|
|
|
|
|
|
|
return {
|
2019-10-31 15:44:26 +00:00
|
|
|
"policy_name": policy_name,
|
|
|
|
"policy_document": policy_json,
|
|
|
|
"group_name": self.name,
|
2017-03-05 03:56:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def put_policy(self, policy_name, policy_json):
|
|
|
|
self.policies[policy_name] = policy_json
|
|
|
|
|
|
|
|
def list_policies(self):
|
|
|
|
return self.policies.keys()
|
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
|
2017-03-12 04:41:12 +00:00
|
|
|
class User(BaseModel):
|
2019-12-20 02:30:43 +00:00
|
|
|
def __init__(self, name, path=None, tags=None):
|
2014-08-19 21:30:11 +00:00
|
|
|
self.name = name
|
|
|
|
self.id = random_resource_id()
|
2017-01-12 01:54:37 +00:00
|
|
|
self.path = path if path else "/"
|
2019-06-02 18:18:50 +00:00
|
|
|
self.create_date = datetime.utcnow()
|
2017-03-27 18:08:57 +00:00
|
|
|
self.mfa_devices = {}
|
2014-08-19 21:30:11 +00:00
|
|
|
self.policies = {}
|
2017-08-12 00:57:06 +00:00
|
|
|
self.managed_policies = {}
|
2014-08-19 21:30:11 +00:00
|
|
|
self.access_keys = []
|
2019-11-16 23:20:33 +00:00
|
|
|
self.ssh_public_keys = []
|
2014-10-29 19:31:49 +00:00
|
|
|
self.password = None
|
2017-07-24 05:31:58 +00:00
|
|
|
self.password_reset_required = False
|
2018-10-25 01:00:52 +00:00
|
|
|
self.signing_certificates = {}
|
2019-12-20 02:30:43 +00:00
|
|
|
self.tags = tags
|
2014-08-19 21:30:11 +00:00
|
|
|
|
2017-03-19 15:58:24 +00:00
|
|
|
@property
|
|
|
|
def arn(self):
|
|
|
|
return "arn:aws:iam::{0}:user{1}{2}".format(ACCOUNT_ID, self.path, self.name)
|
|
|
|
|
2017-04-14 01:39:00 +00:00
|
|
|
@property
|
|
|
|
def created_iso_8601(self):
|
2019-06-02 18:18:50 +00:00
|
|
|
return iso_8601_datetime_with_milliseconds(self.create_date)
|
2017-04-14 01:39:00 +00:00
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
def get_policy(self, policy_name):
|
|
|
|
policy_json = None
|
|
|
|
try:
|
|
|
|
policy_json = self.policies[policy_name]
|
2015-12-04 01:56:28 +00:00
|
|
|
except KeyError:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMNotFoundException("Policy {0} not found".format(policy_name))
|
2014-08-19 21:30:11 +00:00
|
|
|
|
|
|
|
return {
|
2019-10-31 15:44:26 +00:00
|
|
|
"policy_name": policy_name,
|
|
|
|
"policy_document": policy_json,
|
|
|
|
"user_name": self.name,
|
2014-08-19 21:30:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def put_policy(self, policy_name, policy_json):
|
|
|
|
self.policies[policy_name] = policy_json
|
|
|
|
|
2017-03-27 18:08:57 +00:00
|
|
|
def deactivate_mfa_device(self, serial_number):
|
|
|
|
self.mfa_devices.pop(serial_number)
|
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
def delete_policy(self, policy_name):
|
|
|
|
if policy_name not in self.policies:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMNotFoundException("Policy {0} not found".format(policy_name))
|
2014-08-19 21:30:11 +00:00
|
|
|
|
|
|
|
del self.policies[policy_name]
|
|
|
|
|
|
|
|
def create_access_key(self):
|
|
|
|
access_key = AccessKey(self.name)
|
|
|
|
self.access_keys.append(access_key)
|
|
|
|
return access_key
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
def enable_mfa_device(
|
|
|
|
self, serial_number, authentication_code_1, authentication_code_2
|
|
|
|
):
|
2017-03-27 18:08:57 +00:00
|
|
|
self.mfa_devices[serial_number] = MFADevice(
|
2019-10-31 15:44:26 +00:00
|
|
|
serial_number, authentication_code_1, authentication_code_2
|
2017-03-27 18:08:57 +00:00
|
|
|
)
|
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
def get_all_access_keys(self):
|
|
|
|
return self.access_keys
|
|
|
|
|
2014-08-20 18:56:30 +00:00
|
|
|
def delete_access_key(self, access_key_id):
|
2018-11-27 11:28:09 +00:00
|
|
|
key = self.get_access_key_by_id(access_key_id)
|
|
|
|
self.access_keys.remove(key)
|
2014-08-20 18:56:30 +00:00
|
|
|
|
2018-01-10 23:29:08 +00:00
|
|
|
def update_access_key(self, access_key_id, status):
|
2018-11-27 11:28:09 +00:00
|
|
|
key = self.get_access_key_by_id(access_key_id)
|
|
|
|
key.status = status
|
|
|
|
|
|
|
|
def get_access_key_by_id(self, access_key_id):
|
2018-01-10 23:29:08 +00:00
|
|
|
for key in self.access_keys:
|
|
|
|
if key.access_key_id == access_key_id:
|
2018-11-27 11:28:09 +00:00
|
|
|
return key
|
2018-01-10 23:29:08 +00:00
|
|
|
else:
|
2018-11-27 11:28:09 +00:00
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"The Access Key with id {0} cannot be found".format(access_key_id)
|
|
|
|
)
|
2018-01-10 23:29:08 +00:00
|
|
|
|
2019-11-16 23:20:33 +00:00
|
|
|
def upload_ssh_public_key(self, ssh_public_key_body):
|
|
|
|
pubkey = SshPublicKey(self.name, ssh_public_key_body)
|
|
|
|
self.ssh_public_keys.append(pubkey)
|
|
|
|
return pubkey
|
|
|
|
|
|
|
|
def get_ssh_public_key(self, ssh_public_key_id):
|
|
|
|
for key in self.ssh_public_keys:
|
|
|
|
if key.ssh_public_key_id == ssh_public_key_id:
|
|
|
|
return key
|
|
|
|
else:
|
|
|
|
raise IAMNotFoundException(
|
|
|
|
"The SSH Public Key with id {0} cannot be found".format(
|
|
|
|
ssh_public_key_id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_all_ssh_public_keys(self):
|
|
|
|
return self.ssh_public_keys
|
|
|
|
|
|
|
|
def update_ssh_public_key(self, ssh_public_key_id, status):
|
|
|
|
key = self.get_ssh_public_key(ssh_public_key_id)
|
|
|
|
key.status = status
|
|
|
|
|
|
|
|
def delete_ssh_public_key(self, ssh_public_key_id):
|
|
|
|
key = self.get_ssh_public_key(ssh_public_key_id)
|
|
|
|
self.ssh_public_keys.remove(key)
|
|
|
|
|
2014-10-21 16:45:03 +00:00
|
|
|
def get_cfn_attribute(self, attribute_name):
|
2014-10-21 18:51:26 +00:00
|
|
|
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
2019-10-31 15:44:26 +00:00
|
|
|
|
|
|
|
if attribute_name == "Arn":
|
2014-10-21 16:45:03 +00:00
|
|
|
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "Arn" ]"')
|
2014-10-21 18:51:26 +00:00
|
|
|
raise UnformattedGetAttTemplateException()
|
2014-10-21 16:45:03 +00:00
|
|
|
|
2015-02-02 22:42:57 +00:00
|
|
|
def to_csv(self):
|
2019-10-31 15:44:26 +00:00
|
|
|
date_format = "%Y-%m-%dT%H:%M:%S+00:00"
|
2019-06-02 18:18:50 +00:00
|
|
|
date_created = self.create_date
|
2015-02-02 22:42:57 +00:00
|
|
|
# aagrawal,arn:aws:iam::509284790694:user/aagrawal,2014-09-01T22:28:48+00:00,true,2014-11-12T23:36:49+00:00,2014-09-03T18:59:00+00:00,N/A,false,true,2014-09-01T22:28:48+00:00,false,N/A,false,N/A,false,N/A
|
|
|
|
if not self.password:
|
2019-10-31 15:44:26 +00:00
|
|
|
password_enabled = "false"
|
|
|
|
password_last_used = "not_supported"
|
2015-02-02 22:42:57 +00:00
|
|
|
else:
|
2019-10-31 15:44:26 +00:00
|
|
|
password_enabled = "true"
|
|
|
|
password_last_used = "no_information"
|
2015-02-02 22:42:57 +00:00
|
|
|
|
|
|
|
if len(self.access_keys) == 0:
|
2019-10-31 15:44:26 +00:00
|
|
|
access_key_1_active = "false"
|
|
|
|
access_key_1_last_rotated = "N/A"
|
|
|
|
access_key_2_active = "false"
|
|
|
|
access_key_2_last_rotated = "N/A"
|
2015-02-02 22:42:57 +00:00
|
|
|
elif len(self.access_keys) == 1:
|
2019-10-31 15:44:26 +00:00
|
|
|
access_key_1_active = "true"
|
2015-02-02 22:42:57 +00:00
|
|
|
access_key_1_last_rotated = date_created.strftime(date_format)
|
2019-10-31 15:44:26 +00:00
|
|
|
access_key_2_active = "false"
|
|
|
|
access_key_2_last_rotated = "N/A"
|
2015-02-02 22:42:57 +00:00
|
|
|
else:
|
2019-10-31 15:44:26 +00:00
|
|
|
access_key_1_active = "true"
|
2015-02-02 22:42:57 +00:00
|
|
|
access_key_1_last_rotated = date_created.strftime(date_format)
|
2019-10-31 15:44:26 +00:00
|
|
|
access_key_2_active = "true"
|
2015-02-02 22:42:57 +00:00
|
|
|
access_key_2_last_rotated = date_created.strftime(date_format)
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
return "{0},{1},{2},{3},{4},{5},not_supported,false,{6},{7},{8},{9},false,N/A,false,N/A".format(
|
|
|
|
self.name,
|
|
|
|
self.arn,
|
|
|
|
date_created.strftime(date_format),
|
|
|
|
password_enabled,
|
|
|
|
password_last_used,
|
|
|
|
date_created.strftime(date_format),
|
|
|
|
access_key_1_active,
|
|
|
|
access_key_1_last_rotated,
|
|
|
|
access_key_2_active,
|
|
|
|
access_key_2_last_rotated,
|
|
|
|
)
|
2015-02-02 22:42:57 +00:00
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
|
2019-10-28 22:16:19 +00:00
|
|
|
class AccountPasswordPolicy(BaseModel):
|
2019-11-01 06:14:03 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
allow_change_password,
|
|
|
|
hard_expiry,
|
|
|
|
max_password_age,
|
|
|
|
minimum_password_length,
|
|
|
|
password_reuse_prevention,
|
|
|
|
require_lowercase_characters,
|
|
|
|
require_numbers,
|
|
|
|
require_symbols,
|
|
|
|
require_uppercase_characters,
|
|
|
|
):
|
2019-10-28 22:16:19 +00:00
|
|
|
self._errors = []
|
2019-11-01 06:14:03 +00:00
|
|
|
self._validate(
|
|
|
|
max_password_age, minimum_password_length, password_reuse_prevention
|
|
|
|
)
|
2019-10-28 22:16:19 +00:00
|
|
|
|
|
|
|
self.allow_users_to_change_password = allow_change_password
|
|
|
|
self.hard_expiry = hard_expiry
|
|
|
|
self.max_password_age = max_password_age
|
|
|
|
self.minimum_password_length = minimum_password_length
|
|
|
|
self.password_reuse_prevention = password_reuse_prevention
|
|
|
|
self.require_lowercase_characters = require_lowercase_characters
|
|
|
|
self.require_numbers = require_numbers
|
|
|
|
self.require_symbols = require_symbols
|
|
|
|
self.require_uppercase_characters = require_uppercase_characters
|
|
|
|
|
|
|
|
@property
|
|
|
|
def expire_passwords(self):
|
|
|
|
return True if self.max_password_age and self.max_password_age > 0 else False
|
|
|
|
|
2019-11-01 06:14:03 +00:00
|
|
|
def _validate(
|
|
|
|
self, max_password_age, minimum_password_length, password_reuse_prevention
|
|
|
|
):
|
2019-10-28 22:16:19 +00:00
|
|
|
if minimum_password_length > 128:
|
2019-11-01 06:14:03 +00:00
|
|
|
self._errors.append(
|
|
|
|
self._format_error(
|
|
|
|
key="minimumPasswordLength",
|
|
|
|
value=minimum_password_length,
|
|
|
|
constraint="Member must have value less than or equal to 128",
|
|
|
|
)
|
|
|
|
)
|
2019-10-28 22:16:19 +00:00
|
|
|
|
|
|
|
if password_reuse_prevention and password_reuse_prevention > 24:
|
2019-11-01 06:14:03 +00:00
|
|
|
self._errors.append(
|
|
|
|
self._format_error(
|
|
|
|
key="passwordReusePrevention",
|
|
|
|
value=password_reuse_prevention,
|
|
|
|
constraint="Member must have value less than or equal to 24",
|
|
|
|
)
|
|
|
|
)
|
2019-10-28 22:16:19 +00:00
|
|
|
|
|
|
|
if max_password_age and max_password_age > 1095:
|
2019-11-01 06:14:03 +00:00
|
|
|
self._errors.append(
|
|
|
|
self._format_error(
|
|
|
|
key="maxPasswordAge",
|
|
|
|
value=max_password_age,
|
|
|
|
constraint="Member must have value less than or equal to 1095",
|
|
|
|
)
|
|
|
|
)
|
2019-10-28 22:16:19 +00:00
|
|
|
|
|
|
|
self._raise_errors()
|
|
|
|
|
|
|
|
def _format_error(self, key, value, constraint):
|
|
|
|
return 'Value "{value}" at "{key}" failed to satisfy constraint: {constraint}'.format(
|
2019-11-14 22:07:04 +00:00
|
|
|
constraint=constraint, key=key, value=value
|
2019-10-28 22:16:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def _raise_errors(self):
|
|
|
|
if self._errors:
|
|
|
|
count = len(self._errors)
|
|
|
|
plural = "s" if len(self._errors) > 1 else ""
|
|
|
|
errors = "; ".join(self._errors)
|
|
|
|
self._errors = [] # reset collected errors
|
|
|
|
|
2019-11-01 06:14:03 +00:00
|
|
|
raise ValidationError(
|
|
|
|
"{count} validation error{plural} detected: {errors}".format(
|
2019-11-14 22:07:04 +00:00
|
|
|
count=count, plural=plural, errors=errors
|
2019-11-01 06:14:03 +00:00
|
|
|
)
|
|
|
|
)
|
2019-10-28 22:16:19 +00:00
|
|
|
|
|
|
|
|
2019-11-17 12:47:19 +00:00
|
|
|
class AccountSummary(BaseModel):
|
|
|
|
def __init__(self, iam_backend):
|
|
|
|
self._iam_backend = iam_backend
|
|
|
|
|
|
|
|
self._group_policy_size_quota = 5120
|
|
|
|
self._instance_profiles_quota = 1000
|
|
|
|
self._groups_per_user_quota = 10
|
|
|
|
self._attached_policies_per_user_quota = 10
|
|
|
|
self._policies_quota = 1500
|
2019-11-22 06:31:13 +00:00
|
|
|
self._account_mfa_enabled = 0 # Haven't found any information being able to activate MFA for the root account programmatically
|
2019-11-17 12:47:19 +00:00
|
|
|
self._access_keys_per_user_quota = 2
|
|
|
|
self._assume_role_policy_size_quota = 2048
|
|
|
|
self._policy_versions_in_use_quota = 10000
|
|
|
|
self._global_endpoint_token_version = (
|
|
|
|
1 # ToDo: Implement set_security_token_service_preferences()
|
|
|
|
)
|
|
|
|
self._versions_per_policy_quota = 5
|
|
|
|
self._attached_policies_per_group_quota = 10
|
|
|
|
self._policy_size_quota = 6144
|
|
|
|
self._account_signing_certificates_present = 0 # valid values: 0 | 1
|
|
|
|
self._users_quota = 5000
|
|
|
|
self._server_certificates_quota = 20
|
|
|
|
self._user_policy_size_quota = 2048
|
|
|
|
self._roles_quota = 1000
|
|
|
|
self._signing_certificates_per_user_quota = 2
|
|
|
|
self._role_policy_size_quota = 10240
|
|
|
|
self._attached_policies_per_role_quota = 10
|
|
|
|
self._account_access_keys_present = 0 # valid values: 0 | 1
|
|
|
|
self._groups_quota = 300
|
|
|
|
|
|
|
|
@property
|
|
|
|
def summary_map(self):
|
|
|
|
return {
|
|
|
|
"GroupPolicySizeQuota": self._group_policy_size_quota,
|
|
|
|
"InstanceProfilesQuota": self._instance_profiles_quota,
|
|
|
|
"Policies": self._policies,
|
|
|
|
"GroupsPerUserQuota": self._groups_per_user_quota,
|
|
|
|
"InstanceProfiles": self._instance_profiles,
|
|
|
|
"AttachedPoliciesPerUserQuota": self._attached_policies_per_user_quota,
|
|
|
|
"Users": self._users,
|
|
|
|
"PoliciesQuota": self._policies_quota,
|
|
|
|
"Providers": self._providers,
|
|
|
|
"AccountMFAEnabled": self._account_mfa_enabled,
|
|
|
|
"AccessKeysPerUserQuota": self._access_keys_per_user_quota,
|
|
|
|
"AssumeRolePolicySizeQuota": self._assume_role_policy_size_quota,
|
|
|
|
"PolicyVersionsInUseQuota": self._policy_versions_in_use_quota,
|
|
|
|
"GlobalEndpointTokenVersion": self._global_endpoint_token_version,
|
|
|
|
"VersionsPerPolicyQuota": self._versions_per_policy_quota,
|
|
|
|
"AttachedPoliciesPerGroupQuota": self._attached_policies_per_group_quota,
|
|
|
|
"PolicySizeQuota": self._policy_size_quota,
|
|
|
|
"Groups": self._groups,
|
|
|
|
"AccountSigningCertificatesPresent": self._account_signing_certificates_present,
|
|
|
|
"UsersQuota": self._users_quota,
|
|
|
|
"ServerCertificatesQuota": self._server_certificates_quota,
|
|
|
|
"MFADevices": self._mfa_devices,
|
|
|
|
"UserPolicySizeQuota": self._user_policy_size_quota,
|
|
|
|
"PolicyVersionsInUse": self._policy_versions_in_use,
|
|
|
|
"ServerCertificates": self._server_certificates,
|
|
|
|
"Roles": self._roles,
|
|
|
|
"RolesQuota": self._roles_quota,
|
|
|
|
"SigningCertificatesPerUserQuota": self._signing_certificates_per_user_quota,
|
|
|
|
"MFADevicesInUse": self._mfa_devices_in_use,
|
|
|
|
"RolePolicySizeQuota": self._role_policy_size_quota,
|
|
|
|
"AttachedPoliciesPerRoleQuota": self._attached_policies_per_role_quota,
|
|
|
|
"AccountAccessKeysPresent": self._account_access_keys_present,
|
|
|
|
"GroupsQuota": self._groups_quota,
|
|
|
|
}
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _groups(self):
|
|
|
|
return len(self._iam_backend.groups)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _instance_profiles(self):
|
|
|
|
return len(self._iam_backend.instance_profiles)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _mfa_devices(self):
|
|
|
|
# Don't know, if hardware devices are also counted here
|
|
|
|
return len(self._iam_backend.virtual_mfa_devices)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _mfa_devices_in_use(self):
|
|
|
|
devices = 0
|
|
|
|
|
|
|
|
for user in self._iam_backend.users.values():
|
|
|
|
devices += len(user.mfa_devices)
|
|
|
|
|
|
|
|
return devices
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _policies(self):
|
|
|
|
customer_policies = [
|
|
|
|
policy
|
|
|
|
for policy in self._iam_backend.managed_policies
|
|
|
|
if not policy.startswith("arn:aws:iam::aws:policy")
|
|
|
|
]
|
|
|
|
return len(customer_policies)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _policy_versions_in_use(self):
|
|
|
|
attachments = 0
|
|
|
|
|
|
|
|
for policy in self._iam_backend.managed_policies.values():
|
|
|
|
attachments += policy.attachment_count
|
|
|
|
|
|
|
|
return attachments
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _providers(self):
|
|
|
|
providers = len(self._iam_backend.saml_providers) + len(
|
|
|
|
self._iam_backend.open_id_providers
|
|
|
|
)
|
|
|
|
return providers
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _roles(self):
|
|
|
|
return len(self._iam_backend.roles)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _server_certificates(self):
|
|
|
|
return len(self._iam_backend.certificates)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _users(self):
|
|
|
|
return len(self._iam_backend.users)
|
|
|
|
|
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
class IAMBackend(BaseBackend):
|
|
|
|
def __init__(self):
|
|
|
|
self.instance_profiles = {}
|
|
|
|
self.roles = {}
|
2014-05-29 21:25:26 +00:00
|
|
|
self.certificates = {}
|
2014-08-19 21:30:11 +00:00
|
|
|
self.groups = {}
|
|
|
|
self.users = {}
|
2015-02-02 22:42:57 +00:00
|
|
|
self.credential_report = None
|
2016-10-17 22:09:46 +00:00
|
|
|
self.managed_policies = self._init_managed_policies()
|
2017-09-07 18:21:44 +00:00
|
|
|
self.account_aliases = []
|
2018-11-19 23:47:21 +00:00
|
|
|
self.saml_providers = {}
|
2019-10-18 15:29:15 +00:00
|
|
|
self.open_id_providers = {}
|
2019-10-31 15:44:26 +00:00
|
|
|
self.policy_arn_regex = re.compile(r"^arn:aws:iam::[0-9]*:policy/.*$")
|
2019-10-20 20:39:57 +00:00
|
|
|
self.virtual_mfa_devices = {}
|
2019-10-28 22:16:19 +00:00
|
|
|
self.account_password_policy = None
|
2019-11-17 12:47:19 +00:00
|
|
|
self.account_summary = AccountSummary(self)
|
2014-03-27 23:12:53 +00:00
|
|
|
super(IAMBackend, self).__init__()
|
|
|
|
|
2016-10-17 22:09:46 +00:00
|
|
|
def _init_managed_policies(self):
|
2019-06-06 12:36:39 +00:00
|
|
|
return dict((p.arn, p) for p in aws_managed_policies)
|
2016-10-17 22:09:46 +00:00
|
|
|
|
|
|
|
def attach_role_policy(self, policy_arn, role_name):
|
|
|
|
arns = dict((p.arn, p) for p in self.managed_policies.values())
|
|
|
|
policy = arns[policy_arn]
|
2017-10-01 22:04:59 +00:00
|
|
|
policy.attach_to(self.get_role(role_name))
|
2016-10-17 22:09:46 +00:00
|
|
|
|
2019-02-17 20:30:43 +00:00
|
|
|
def update_role_description(self, role_name, role_description):
|
|
|
|
role = self.get_role(role_name)
|
|
|
|
role.description = role_description
|
2019-02-17 21:35:49 +00:00
|
|
|
return role
|
2019-02-17 20:30:43 +00:00
|
|
|
|
2019-11-24 18:19:09 +00:00
|
|
|
def update_role(self, role_name, role_description, max_session_duration):
|
2019-02-18 03:37:33 +00:00
|
|
|
role = self.get_role(role_name)
|
|
|
|
role.description = role_description
|
2019-11-24 18:19:09 +00:00
|
|
|
role.max_session_duration = max_session_duration
|
2019-02-18 03:37:33 +00:00
|
|
|
return role
|
|
|
|
|
2017-08-14 04:58:11 +00:00
|
|
|
def detach_role_policy(self, policy_arn, role_name):
|
|
|
|
arns = dict((p.arn, p) for p in self.managed_policies.values())
|
|
|
|
try:
|
|
|
|
policy = arns[policy_arn]
|
2017-10-01 22:04:59 +00:00
|
|
|
policy.detach_from(self.get_role(role_name))
|
2017-08-14 04:58:11 +00:00
|
|
|
except KeyError:
|
|
|
|
raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn))
|
|
|
|
|
2017-10-01 22:01:33 +00:00
|
|
|
def attach_group_policy(self, policy_arn, group_name):
|
|
|
|
arns = dict((p.arn, p) for p in self.managed_policies.values())
|
|
|
|
try:
|
|
|
|
policy = arns[policy_arn]
|
|
|
|
except KeyError:
|
|
|
|
raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn))
|
2017-10-01 22:04:59 +00:00
|
|
|
policy.attach_to(self.get_group(group_name))
|
2017-10-01 22:01:33 +00:00
|
|
|
|
|
|
|
def detach_group_policy(self, policy_arn, group_name):
|
|
|
|
arns = dict((p.arn, p) for p in self.managed_policies.values())
|
|
|
|
try:
|
|
|
|
policy = arns[policy_arn]
|
|
|
|
except KeyError:
|
|
|
|
raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn))
|
2017-10-01 22:04:59 +00:00
|
|
|
policy.detach_from(self.get_group(group_name))
|
2017-10-01 22:01:33 +00:00
|
|
|
|
2017-08-12 00:57:06 +00:00
|
|
|
def attach_user_policy(self, policy_arn, user_name):
|
|
|
|
arns = dict((p.arn, p) for p in self.managed_policies.values())
|
2017-10-01 22:01:33 +00:00
|
|
|
try:
|
|
|
|
policy = arns[policy_arn]
|
|
|
|
except KeyError:
|
|
|
|
raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn))
|
2017-10-01 22:04:59 +00:00
|
|
|
policy.attach_to(self.get_user(user_name))
|
2017-08-12 00:57:06 +00:00
|
|
|
|
|
|
|
def detach_user_policy(self, policy_arn, user_name):
|
|
|
|
arns = dict((p.arn, p) for p in self.managed_policies.values())
|
2017-10-01 22:01:33 +00:00
|
|
|
try:
|
|
|
|
policy = arns[policy_arn]
|
|
|
|
except KeyError:
|
|
|
|
raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn))
|
2017-10-01 22:04:59 +00:00
|
|
|
policy.detach_from(self.get_user(user_name))
|
2017-08-12 00:57:06 +00:00
|
|
|
|
2016-10-17 22:09:46 +00:00
|
|
|
def create_policy(self, description, path, policy_document, policy_name):
|
2019-06-30 15:09:55 +00:00
|
|
|
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_document)
|
|
|
|
iam_policy_document_validator.validate()
|
|
|
|
|
2016-10-17 22:09:46 +00:00
|
|
|
policy = ManagedPolicy(
|
2019-10-31 15:44:26 +00:00
|
|
|
policy_name, description=description, document=policy_document, path=path
|
2016-10-17 22:09:46 +00:00
|
|
|
)
|
2019-11-05 18:57:38 +00:00
|
|
|
if policy.arn in self.managed_policies:
|
|
|
|
raise EntityAlreadyExists(
|
2019-11-11 09:14:22 +00:00
|
|
|
"A policy called {0} already exists. Duplicate names are not allowed.".format(
|
2019-11-05 22:29:28 +00:00
|
|
|
policy_name
|
|
|
|
)
|
2019-11-05 18:57:38 +00:00
|
|
|
)
|
2018-08-07 20:59:15 +00:00
|
|
|
self.managed_policies[policy.arn] = policy
|
2016-10-17 22:09:46 +00:00
|
|
|
return policy
|
|
|
|
|
2018-08-07 20:59:15 +00:00
|
|
|
def get_policy(self, policy_arn):
|
|
|
|
if policy_arn not in self.managed_policies:
|
|
|
|
raise IAMNotFoundException("Policy {0} not found".format(policy_arn))
|
|
|
|
return self.managed_policies.get(policy_arn)
|
2017-05-15 21:56:30 +00:00
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
def list_attached_role_policies(
|
|
|
|
self, role_name, marker=None, max_items=100, path_prefix="/"
|
|
|
|
):
|
2016-10-17 22:09:46 +00:00
|
|
|
policies = self.get_role(role_name).managed_policies.values()
|
2017-10-01 22:01:33 +00:00
|
|
|
return self._filter_attached_policies(policies, marker, max_items, path_prefix)
|
2016-10-17 22:09:46 +00:00
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
def list_attached_group_policies(
|
|
|
|
self, group_name, marker=None, max_items=100, path_prefix="/"
|
|
|
|
):
|
2017-10-01 22:01:33 +00:00
|
|
|
policies = self.get_group(group_name).managed_policies.values()
|
|
|
|
return self._filter_attached_policies(policies, marker, max_items, path_prefix)
|
2016-10-17 22:09:46 +00:00
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
def list_attached_user_policies(
|
|
|
|
self, user_name, marker=None, max_items=100, path_prefix="/"
|
|
|
|
):
|
2017-08-12 00:57:06 +00:00
|
|
|
policies = self.get_user(user_name).managed_policies.values()
|
2017-10-01 22:01:33 +00:00
|
|
|
return self._filter_attached_policies(policies, marker, max_items, path_prefix)
|
2017-08-12 00:57:06 +00:00
|
|
|
|
2016-10-17 22:09:46 +00:00
|
|
|
def list_policies(self, marker, max_items, only_attached, path_prefix, scope):
|
|
|
|
policies = self.managed_policies.values()
|
|
|
|
|
|
|
|
if only_attached:
|
|
|
|
policies = [p for p in policies if p.attachment_count > 0]
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
if scope == "AWS":
|
2016-10-17 22:09:46 +00:00
|
|
|
policies = [p for p in policies if isinstance(p, AWSManagedPolicy)]
|
2019-10-31 15:44:26 +00:00
|
|
|
elif scope == "Local":
|
|
|
|
policies = [p for p in policies if not isinstance(p, AWSManagedPolicy)]
|
2016-10-17 22:09:46 +00:00
|
|
|
|
2017-10-01 22:01:33 +00:00
|
|
|
return self._filter_attached_policies(policies, marker, max_items, path_prefix)
|
|
|
|
|
|
|
|
def _filter_attached_policies(self, policies, marker, max_items, path_prefix):
|
2016-10-17 22:09:46 +00:00
|
|
|
if path_prefix:
|
|
|
|
policies = [p for p in policies if p.path.startswith(path_prefix)]
|
|
|
|
|
2016-11-06 15:57:01 +00:00
|
|
|
policies = sorted(policies, key=lambda policy: policy.name)
|
2016-10-17 22:09:46 +00:00
|
|
|
start_idx = int(marker) if marker else 0
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
policies = policies[start_idx : start_idx + max_items]
|
2016-10-17 22:09:46 +00:00
|
|
|
|
|
|
|
if len(policies) < max_items:
|
|
|
|
marker = None
|
|
|
|
else:
|
|
|
|
marker = str(start_idx + max_items)
|
|
|
|
|
|
|
|
return policies, marker
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
def create_role(
|
|
|
|
self,
|
|
|
|
role_name,
|
|
|
|
assume_role_policy_document,
|
|
|
|
path,
|
|
|
|
permissions_boundary,
|
|
|
|
description,
|
|
|
|
tags,
|
2019-11-24 18:19:09 +00:00
|
|
|
max_session_duration,
|
2019-10-31 15:44:26 +00:00
|
|
|
):
|
2014-03-27 23:12:53 +00:00
|
|
|
role_id = random_resource_id()
|
2019-10-31 15:44:26 +00:00
|
|
|
if permissions_boundary and not self.policy_arn_regex.match(
|
|
|
|
permissions_boundary
|
|
|
|
):
|
|
|
|
raise RESTError(
|
|
|
|
"InvalidParameterValue",
|
|
|
|
"Value ({}) for parameter PermissionsBoundary is invalid.".format(
|
|
|
|
permissions_boundary
|
|
|
|
),
|
|
|
|
)
|
2019-11-11 08:21:42 +00:00
|
|
|
if [role for role in self.get_roles() if role.name == role_name]:
|
|
|
|
raise EntityAlreadyExists(
|
|
|
|
"Role with name {0} already exists.".format(role_name)
|
|
|
|
)
|
2019-05-21 16:44:06 +00:00
|
|
|
|
2019-08-21 19:24:23 +00:00
|
|
|
clean_tags = self._tag_verification(tags)
|
2019-10-31 15:44:26 +00:00
|
|
|
role = Role(
|
|
|
|
role_id,
|
|
|
|
role_name,
|
|
|
|
assume_role_policy_document,
|
|
|
|
path,
|
|
|
|
permissions_boundary,
|
|
|
|
description,
|
|
|
|
clean_tags,
|
2019-11-24 18:19:09 +00:00
|
|
|
max_session_duration,
|
2019-10-31 15:44:26 +00:00
|
|
|
)
|
2014-03-27 23:12:53 +00:00
|
|
|
self.roles[role_id] = role
|
|
|
|
return role
|
|
|
|
|
|
|
|
def get_role_by_id(self, role_id):
|
|
|
|
return self.roles.get(role_id)
|
|
|
|
|
|
|
|
def get_role(self, role_name):
|
|
|
|
for role in self.get_roles():
|
|
|
|
if role.name == role_name:
|
|
|
|
return role
|
2015-12-04 01:56:28 +00:00
|
|
|
raise IAMNotFoundException("Role {0} not found".format(role_name))
|
2014-03-27 23:12:53 +00:00
|
|
|
|
2017-09-26 21:22:59 +00:00
|
|
|
def get_role_by_arn(self, arn):
|
|
|
|
for role in self.get_roles():
|
|
|
|
if role.arn == arn:
|
|
|
|
return role
|
|
|
|
raise IAMNotFoundException("Role {0} not found".format(arn))
|
|
|
|
|
2017-05-18 17:37:00 +00:00
|
|
|
def delete_role(self, role_name):
|
2019-10-22 13:27:49 +00:00
|
|
|
role = self.get_role(role_name)
|
|
|
|
for instance_profile in self.get_instance_profiles():
|
|
|
|
for role in instance_profile.roles:
|
|
|
|
if role.name == role_name:
|
|
|
|
raise IAMConflictException(
|
|
|
|
code="DeleteConflict",
|
2019-10-31 15:44:26 +00:00
|
|
|
message="Cannot delete entity, must remove roles from instance profile first.",
|
2019-10-22 13:27:49 +00:00
|
|
|
)
|
|
|
|
if role.managed_policies:
|
|
|
|
raise IAMConflictException(
|
|
|
|
code="DeleteConflict",
|
2019-10-31 15:44:26 +00:00
|
|
|
message="Cannot delete entity, must detach all policies first.",
|
2019-10-22 13:27:49 +00:00
|
|
|
)
|
|
|
|
if role.policies:
|
|
|
|
raise IAMConflictException(
|
|
|
|
code="DeleteConflict",
|
2019-10-31 15:44:26 +00:00
|
|
|
message="Cannot delete entity, must delete policies first.",
|
2019-10-22 13:27:49 +00:00
|
|
|
)
|
|
|
|
del self.roles[role.id]
|
2017-05-18 17:37:00 +00:00
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
def get_roles(self):
|
|
|
|
return self.roles.values()
|
|
|
|
|
2014-12-01 04:11:13 +00:00
|
|
|
def put_role_policy(self, role_name, policy_name, policy_json):
|
|
|
|
role = self.get_role(role_name)
|
2019-06-30 15:35:26 +00:00
|
|
|
|
|
|
|
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_json)
|
|
|
|
iam_policy_document_validator.validate()
|
2015-06-29 15:04:40 +00:00
|
|
|
role.put_policy(policy_name, policy_json)
|
2014-12-01 04:11:13 +00:00
|
|
|
|
2017-08-14 04:58:11 +00:00
|
|
|
def delete_role_policy(self, role_name, policy_name):
|
|
|
|
role = self.get_role(role_name)
|
|
|
|
role.delete_policy(policy_name)
|
|
|
|
|
2014-12-01 04:11:13 +00:00
|
|
|
def get_role_policy(self, role_name, policy_name):
|
|
|
|
role = self.get_role(role_name)
|
2015-06-29 15:04:40 +00:00
|
|
|
for p, d in role.policies.items():
|
|
|
|
if p == policy_name:
|
|
|
|
return p, d
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMNotFoundException(
|
|
|
|
"Policy Document {0} not attached to role {1}".format(
|
|
|
|
policy_name, role_name
|
|
|
|
)
|
|
|
|
)
|
2014-12-01 04:11:13 +00:00
|
|
|
|
|
|
|
def list_role_policies(self, role_name):
|
|
|
|
role = self.get_role(role_name)
|
2015-06-29 15:04:40 +00:00
|
|
|
return role.policies.keys()
|
2014-12-01 04:11:13 +00:00
|
|
|
|
2019-08-21 19:24:23 +00:00
|
|
|
def _tag_verification(self, tags):
|
|
|
|
if len(tags) > 50:
|
|
|
|
raise TooManyTags(tags)
|
|
|
|
|
|
|
|
tag_keys = {}
|
|
|
|
for tag in tags:
|
|
|
|
# Need to index by the lowercase tag key since the keys are case insensitive, but their case is retained.
|
2019-10-31 15:44:26 +00:00
|
|
|
ref_key = tag["Key"].lower()
|
2019-08-21 19:24:23 +00:00
|
|
|
self._check_tag_duplicate(tag_keys, ref_key)
|
2019-10-31 15:44:26 +00:00
|
|
|
self._validate_tag_key(tag["Key"])
|
|
|
|
if len(tag["Value"]) > 256:
|
|
|
|
raise TagValueTooBig(tag["Value"])
|
2019-08-21 19:24:23 +00:00
|
|
|
|
|
|
|
tag_keys[ref_key] = tag
|
|
|
|
|
|
|
|
return tag_keys
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
def _validate_tag_key(self, tag_key, exception_param="tags.X.member.key"):
|
2019-01-30 02:09:31 +00:00
|
|
|
"""Validates the tag key.
|
|
|
|
|
|
|
|
:param tag_key: The tag key to check against.
|
|
|
|
:param exception_param: The exception parameter to send over to help format the message. This is to reflect
|
|
|
|
the difference between the tag and untag APIs.
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
# Validate that the key length is correct:
|
|
|
|
if len(tag_key) > 128:
|
|
|
|
raise TagKeyTooBig(tag_key, param=exception_param)
|
|
|
|
|
|
|
|
# Validate that the tag key fits the proper Regex:
|
|
|
|
# [\w\s_.:/=+\-@]+ SHOULD be the same as the Java regex on the AWS documentation: [\p{L}\p{Z}\p{N}_.:/=+\-@]+
|
2019-10-31 15:44:26 +00:00
|
|
|
match = re.findall(r"[\w\s_.:/=+\-@]+", tag_key)
|
2019-01-30 02:09:31 +00:00
|
|
|
# Kudos if you can come up with a better way of doing a global search :)
|
|
|
|
if not len(match) or len(match[0]) < len(tag_key):
|
|
|
|
raise InvalidTagCharacters(tag_key, param=exception_param)
|
|
|
|
|
|
|
|
def _check_tag_duplicate(self, all_tags, tag_key):
|
|
|
|
"""Validates that a tag key is not a duplicate
|
|
|
|
|
|
|
|
:param all_tags: Dict to check if there is a duplicate tag.
|
|
|
|
:param tag_key: The tag key to check against.
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
if tag_key in all_tags:
|
|
|
|
raise DuplicateTags()
|
|
|
|
|
|
|
|
def list_role_tags(self, role_name, marker, max_items=100):
|
|
|
|
role = self.get_role(role_name)
|
|
|
|
|
|
|
|
max_items = int(max_items)
|
|
|
|
tag_index = sorted(role.tags)
|
|
|
|
start_idx = int(marker) if marker else 0
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
tag_index = tag_index[start_idx : start_idx + max_items]
|
2019-01-30 02:09:31 +00:00
|
|
|
|
|
|
|
if len(role.tags) <= (start_idx + max_items):
|
|
|
|
marker = None
|
|
|
|
else:
|
|
|
|
marker = str(start_idx + max_items)
|
|
|
|
|
|
|
|
# Make the tag list of dict's:
|
|
|
|
tags = [role.tags[tag] for tag in tag_index]
|
|
|
|
|
|
|
|
return tags, marker
|
|
|
|
|
|
|
|
def tag_role(self, role_name, tags):
|
2019-08-21 19:24:23 +00:00
|
|
|
clean_tags = self._tag_verification(tags)
|
2019-01-30 02:09:31 +00:00
|
|
|
role = self.get_role(role_name)
|
2019-08-21 19:24:23 +00:00
|
|
|
role.tags.update(clean_tags)
|
2019-01-30 02:09:31 +00:00
|
|
|
|
|
|
|
def untag_role(self, role_name, tag_keys):
|
|
|
|
if len(tag_keys) > 50:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise TooManyTags(tag_keys, param="tagKeys")
|
2019-01-30 02:09:31 +00:00
|
|
|
|
|
|
|
role = self.get_role(role_name)
|
|
|
|
|
|
|
|
for key in tag_keys:
|
|
|
|
ref_key = key.lower()
|
2019-10-31 15:44:26 +00:00
|
|
|
self._validate_tag_key(key, exception_param="tagKeys")
|
2019-01-30 02:09:31 +00:00
|
|
|
|
|
|
|
role.tags.pop(ref_key, None)
|
|
|
|
|
2017-05-15 21:56:28 +00:00
|
|
|
def create_policy_version(self, policy_arn, policy_document, set_as_default):
|
2019-07-01 16:54:32 +00:00
|
|
|
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_document)
|
|
|
|
iam_policy_document_validator.validate()
|
|
|
|
|
2018-08-07 20:59:15 +00:00
|
|
|
policy = self.get_policy(policy_arn)
|
2017-05-15 21:56:30 +00:00
|
|
|
if not policy:
|
|
|
|
raise IAMNotFoundException("Policy not found")
|
2019-06-29 17:01:43 +00:00
|
|
|
if len(policy.versions) >= 5:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMLimitExceededException(
|
|
|
|
"A managed policy can have up to 5 versions. Before you create a new version, you must delete an existing version."
|
|
|
|
)
|
|
|
|
set_as_default = set_as_default == "true" # convert it to python bool
|
2017-05-15 21:56:30 +00:00
|
|
|
version = PolicyVersion(policy_arn, policy_document, set_as_default)
|
2017-05-15 21:56:28 +00:00
|
|
|
policy.versions.append(version)
|
2019-10-31 15:44:26 +00:00
|
|
|
version.version_id = "v{0}".format(policy.next_version_num)
|
2019-04-16 19:29:48 +00:00
|
|
|
policy.next_version_num += 1
|
2017-05-15 21:56:28 +00:00
|
|
|
if set_as_default:
|
2019-06-29 16:55:19 +00:00
|
|
|
policy.update_default_version(version.version_id)
|
2017-05-15 21:56:30 +00:00
|
|
|
return version
|
|
|
|
|
|
|
|
def get_policy_version(self, policy_arn, version_id):
|
2018-08-07 20:59:15 +00:00
|
|
|
policy = self.get_policy(policy_arn)
|
2017-05-15 21:56:30 +00:00
|
|
|
if not policy:
|
|
|
|
raise IAMNotFoundException("Policy not found")
|
|
|
|
for version in policy.versions:
|
|
|
|
if version.version_id == version_id:
|
|
|
|
return version
|
|
|
|
raise IAMNotFoundException("Policy version not found")
|
|
|
|
|
|
|
|
def list_policy_versions(self, policy_arn):
|
2018-08-07 20:59:15 +00:00
|
|
|
policy = self.get_policy(policy_arn)
|
2017-05-15 21:56:30 +00:00
|
|
|
if not policy:
|
|
|
|
raise IAMNotFoundException("Policy not found")
|
|
|
|
return policy.versions
|
2017-05-15 21:56:28 +00:00
|
|
|
|
|
|
|
def delete_policy_version(self, policy_arn, version_id):
|
2018-08-07 20:59:15 +00:00
|
|
|
policy = self.get_policy(policy_arn)
|
2017-05-15 21:56:30 +00:00
|
|
|
if not policy:
|
|
|
|
raise IAMNotFoundException("Policy not found")
|
2018-08-07 21:24:15 +00:00
|
|
|
if version_id == policy.default_version_id:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMConflictException(
|
|
|
|
code="DeleteConflict",
|
|
|
|
message="Cannot delete the default version of a policy.",
|
|
|
|
)
|
2017-05-15 21:56:28 +00:00
|
|
|
for i, v in enumerate(policy.versions):
|
|
|
|
if v.version_id == version_id:
|
|
|
|
del policy.versions[i]
|
|
|
|
return
|
2017-05-15 21:56:30 +00:00
|
|
|
raise IAMNotFoundException("Policy not found")
|
2017-05-15 21:56:28 +00:00
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
def create_instance_profile(self, name, path, role_ids):
|
2019-11-14 22:07:04 +00:00
|
|
|
if self.instance_profiles.get(name):
|
|
|
|
raise IAMConflictException(
|
|
|
|
code="EntityAlreadyExists",
|
|
|
|
message="Instance Profile {0} already exists.".format(name),
|
|
|
|
)
|
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
instance_profile_id = random_resource_id()
|
|
|
|
|
|
|
|
roles = [iam_backend.get_role_by_id(role_id) for role_id in role_ids]
|
2019-10-31 15:44:26 +00:00
|
|
|
instance_profile = InstanceProfile(instance_profile_id, name, path, roles)
|
2019-11-14 22:07:04 +00:00
|
|
|
self.instance_profiles[name] = instance_profile
|
2014-03-27 23:12:53 +00:00
|
|
|
return instance_profile
|
|
|
|
|
|
|
|
def get_instance_profile(self, profile_name):
|
|
|
|
for profile in self.get_instance_profiles():
|
|
|
|
if profile.name == profile_name:
|
|
|
|
return profile
|
|
|
|
|
2017-02-24 02:37:43 +00:00
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"Instance profile {0} not found".format(profile_name)
|
|
|
|
)
|
2017-01-19 02:36:50 +00:00
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
def get_instance_profiles(self):
|
|
|
|
return self.instance_profiles.values()
|
|
|
|
|
2015-02-27 00:08:54 +00:00
|
|
|
def get_instance_profiles_for_role(self, role_name):
|
|
|
|
found_profiles = []
|
|
|
|
|
|
|
|
for profile in self.get_instance_profiles():
|
|
|
|
if len(profile.roles) > 0:
|
|
|
|
if profile.roles[0].name == role_name:
|
|
|
|
found_profiles.append(profile)
|
|
|
|
|
|
|
|
return found_profiles
|
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
def add_role_to_instance_profile(self, profile_name, role_name):
|
|
|
|
profile = self.get_instance_profile(profile_name)
|
|
|
|
role = self.get_role(role_name)
|
|
|
|
profile.roles.append(role)
|
|
|
|
|
2016-05-05 02:25:46 +00:00
|
|
|
def remove_role_from_instance_profile(self, profile_name, role_name):
|
|
|
|
profile = self.get_instance_profile(profile_name)
|
|
|
|
role = self.get_role(role_name)
|
|
|
|
profile.roles.remove(role)
|
|
|
|
|
2014-05-29 21:25:26 +00:00
|
|
|
def get_all_server_certs(self, marker=None):
|
|
|
|
return self.certificates.values()
|
|
|
|
|
2019-11-17 12:47:19 +00:00
|
|
|
def upload_server_certificate(
|
2019-10-31 15:44:26 +00:00
|
|
|
self, cert_name, cert_body, private_key, cert_chain=None, path=None
|
|
|
|
):
|
2014-05-29 21:25:26 +00:00
|
|
|
certificate_id = random_resource_id()
|
|
|
|
cert = Certificate(cert_name, cert_body, private_key, cert_chain, path)
|
|
|
|
self.certificates[certificate_id] = cert
|
|
|
|
return cert
|
|
|
|
|
|
|
|
def get_server_certificate(self, name):
|
|
|
|
for key, cert in self.certificates.items():
|
|
|
|
if name == cert.cert_name:
|
|
|
|
return cert
|
|
|
|
|
2016-08-15 00:40:39 +00:00
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"The Server Certificate with name {0} cannot be " "found.".format(name)
|
|
|
|
)
|
2016-08-15 00:40:39 +00:00
|
|
|
|
2017-12-08 10:43:09 +00:00
|
|
|
def delete_server_certificate(self, name):
|
|
|
|
cert_id = None
|
|
|
|
for key, cert in self.certificates.items():
|
|
|
|
if name == cert.cert_name:
|
|
|
|
cert_id = key
|
|
|
|
break
|
|
|
|
|
|
|
|
if cert_id is None:
|
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"The Server Certificate with name {0} cannot be " "found.".format(name)
|
|
|
|
)
|
2017-12-08 10:43:09 +00:00
|
|
|
|
|
|
|
self.certificates.pop(cert_id, None)
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
def create_group(self, group_name, path="/"):
|
2014-08-19 21:30:11 +00:00
|
|
|
if group_name in self.groups:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMConflictException("Group {0} already exists".format(group_name))
|
2014-08-19 21:30:11 +00:00
|
|
|
|
|
|
|
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:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMNotFoundException("Group {0} not found".format(group_name))
|
2014-08-19 21:30:11 +00:00
|
|
|
|
|
|
|
return group
|
|
|
|
|
2015-04-30 23:32:53 +00:00
|
|
|
def list_groups(self):
|
|
|
|
return self.groups.values()
|
|
|
|
|
|
|
|
def get_groups_for_user(self, user_name):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
groups = []
|
|
|
|
for group in self.list_groups():
|
|
|
|
if user in group.users:
|
|
|
|
groups.append(group)
|
|
|
|
|
|
|
|
return groups
|
|
|
|
|
2017-03-05 03:56:36 +00:00
|
|
|
def put_group_policy(self, group_name, policy_name, policy_json):
|
|
|
|
group = self.get_group(group_name)
|
2019-06-30 15:35:26 +00:00
|
|
|
|
|
|
|
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_json)
|
|
|
|
iam_policy_document_validator.validate()
|
2017-03-05 03:56:36 +00:00
|
|
|
group.put_policy(policy_name, policy_json)
|
|
|
|
|
|
|
|
def list_group_policies(self, group_name, marker=None, max_items=None):
|
|
|
|
group = self.get_group(group_name)
|
|
|
|
return group.list_policies()
|
|
|
|
|
|
|
|
def get_group_policy(self, group_name, policy_name):
|
|
|
|
group = self.get_group(group_name)
|
|
|
|
return group.get_policy(policy_name)
|
|
|
|
|
2019-04-20 21:50:28 +00:00
|
|
|
def delete_group(self, group_name):
|
|
|
|
try:
|
|
|
|
del self.groups[group_name]
|
|
|
|
except KeyError:
|
2019-11-08 09:19:45 +00:00
|
|
|
raise IAMNotFoundException(
|
|
|
|
"The group with name {0} cannot be found.".format(group_name)
|
|
|
|
)
|
2019-04-20 21:50:28 +00:00
|
|
|
|
2019-12-20 02:30:43 +00:00
|
|
|
def create_user(self, user_name, path="/", tags=None):
|
2014-08-19 21:30:11 +00:00
|
|
|
if user_name in self.users:
|
2017-02-24 02:37:43 +00:00
|
|
|
raise IAMConflictException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"EntityAlreadyExists", "User {0} already exists".format(user_name)
|
|
|
|
)
|
2014-08-19 21:30:11 +00:00
|
|
|
|
2019-12-20 02:30:43 +00:00
|
|
|
user = User(user_name, path, tags)
|
2014-08-19 21:30:11 +00:00
|
|
|
self.users[user_name] = user
|
|
|
|
return user
|
|
|
|
|
2014-08-20 18:56:30 +00:00
|
|
|
def get_user(self, user_name):
|
|
|
|
user = None
|
|
|
|
try:
|
|
|
|
user = self.users[user_name]
|
|
|
|
except KeyError:
|
2015-12-04 01:56:28 +00:00
|
|
|
raise IAMNotFoundException("User {0} not found".format(user_name))
|
2014-08-20 18:56:30 +00:00
|
|
|
|
|
|
|
return user
|
|
|
|
|
2016-07-20 22:12:02 +00:00
|
|
|
def list_users(self, path_prefix, marker, max_items):
|
|
|
|
users = None
|
|
|
|
try:
|
2017-01-12 01:54:37 +00:00
|
|
|
users = self.users.values()
|
2016-07-20 22:12:02 +00:00
|
|
|
except KeyError:
|
2017-02-24 02:37:43 +00:00
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"Users {0}, {1}, {2} not found".format(path_prefix, marker, max_items)
|
|
|
|
)
|
2016-07-20 22:12:02 +00:00
|
|
|
|
|
|
|
return users
|
|
|
|
|
2019-03-12 16:27:37 +00:00
|
|
|
def update_user(self, user_name, new_path=None, new_user_name=None):
|
|
|
|
try:
|
|
|
|
user = self.users[user_name]
|
|
|
|
except KeyError:
|
|
|
|
raise IAMNotFoundException("User {0} not found".format(user_name))
|
|
|
|
|
|
|
|
if new_path:
|
|
|
|
user.path = new_path
|
|
|
|
if new_user_name:
|
|
|
|
user.name = new_user_name
|
|
|
|
self.users[new_user_name] = self.users.pop(user_name)
|
|
|
|
|
2019-02-19 03:20:29 +00:00
|
|
|
def list_roles(self, path_prefix, marker, max_items):
|
|
|
|
roles = None
|
|
|
|
try:
|
|
|
|
roles = self.roles.values()
|
|
|
|
except KeyError:
|
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"Users {0}, {1}, {2} not found".format(path_prefix, marker, max_items)
|
|
|
|
)
|
2019-02-19 03:20:29 +00:00
|
|
|
|
|
|
|
return roles
|
|
|
|
|
2018-10-25 01:00:52 +00:00
|
|
|
def upload_signing_certificate(self, user_name, body):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
cert_id = random_resource_id(size=32)
|
|
|
|
|
|
|
|
# Validate the signing cert:
|
|
|
|
try:
|
|
|
|
if sys.version_info < (3, 0):
|
|
|
|
data = bytes(body)
|
|
|
|
else:
|
2019-10-31 15:44:26 +00:00
|
|
|
data = bytes(body, "utf8")
|
2018-10-25 01:00:52 +00:00
|
|
|
|
|
|
|
x509.load_pem_x509_certificate(data, default_backend())
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
raise MalformedCertificate(body)
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
user.signing_certificates[cert_id] = SigningCertificate(
|
|
|
|
cert_id, user_name, body
|
|
|
|
)
|
2018-10-25 01:00:52 +00:00
|
|
|
|
|
|
|
return user.signing_certificates[cert_id]
|
|
|
|
|
|
|
|
def delete_signing_certificate(self, user_name, cert_id):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
|
|
|
|
try:
|
|
|
|
del user.signing_certificates[cert_id]
|
|
|
|
except KeyError:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMNotFoundException(
|
|
|
|
"The Certificate with id {id} cannot be found.".format(id=cert_id)
|
|
|
|
)
|
2018-10-25 01:00:52 +00:00
|
|
|
|
|
|
|
def list_signing_certificates(self, user_name):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
|
|
|
|
return list(user.signing_certificates.values())
|
|
|
|
|
|
|
|
def update_signing_certificate(self, user_name, cert_id, status):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
|
|
|
|
try:
|
|
|
|
user.signing_certificates[cert_id].status = status
|
|
|
|
|
|
|
|
except KeyError:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMNotFoundException(
|
|
|
|
"The Certificate with id {id} cannot be found.".format(id=cert_id)
|
|
|
|
)
|
2018-10-25 01:00:52 +00:00
|
|
|
|
2014-10-29 19:31:49 +00:00
|
|
|
def create_login_profile(self, user_name, password):
|
|
|
|
# This does not currently deal with PasswordPolicyViolation.
|
2015-12-04 01:56:28 +00:00
|
|
|
user = self.get_user(user_name)
|
2014-10-29 19:31:49 +00:00
|
|
|
if user.password:
|
2017-02-24 02:37:43 +00:00
|
|
|
raise IAMConflictException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"User {0} already has password".format(user_name)
|
|
|
|
)
|
2014-10-29 19:31:49 +00:00
|
|
|
user.password = password
|
2017-07-24 05:31:58 +00:00
|
|
|
return user
|
|
|
|
|
|
|
|
def get_login_profile(self, user_name):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
if not user.password:
|
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"Login profile for {0} not found".format(user_name)
|
|
|
|
)
|
2017-07-24 05:31:58 +00:00
|
|
|
return user
|
|
|
|
|
|
|
|
def update_login_profile(self, user_name, password, password_reset_required):
|
|
|
|
# This does not currently deal with PasswordPolicyViolation.
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
if not user.password:
|
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"Login profile for {0} not found".format(user_name)
|
|
|
|
)
|
2017-07-24 05:31:58 +00:00
|
|
|
user.password = password
|
|
|
|
user.password_reset_required = password_reset_required
|
|
|
|
return user
|
2014-10-29 19:31:49 +00:00
|
|
|
|
2016-11-11 22:05:02 +00:00
|
|
|
def delete_login_profile(self, user_name):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
if not user.password:
|
2017-02-24 02:37:43 +00:00
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"Login profile for {0} not found".format(user_name)
|
|
|
|
)
|
2016-11-11 22:05:02 +00:00
|
|
|
user.password = None
|
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
def add_user_to_group(self, group_name, user_name):
|
2015-12-04 01:56:28 +00:00
|
|
|
user = self.get_user(user_name)
|
|
|
|
group = self.get_group(group_name)
|
2014-08-19 21:30:11 +00:00
|
|
|
group.users.append(user)
|
|
|
|
|
|
|
|
def remove_user_from_group(self, group_name, user_name):
|
2015-12-04 01:56:28 +00:00
|
|
|
group = self.get_group(group_name)
|
|
|
|
user = self.get_user(user_name)
|
2014-08-19 21:30:11 +00:00
|
|
|
try:
|
|
|
|
group.users.remove(user)
|
2015-12-04 01:56:28 +00:00
|
|
|
except ValueError:
|
2017-02-24 02:37:43 +00:00
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"User {0} not in group {1}".format(user_name, group_name)
|
|
|
|
)
|
2014-08-19 21:30:11 +00:00
|
|
|
|
|
|
|
def get_user_policy(self, user_name, policy_name):
|
2015-12-04 01:56:28 +00:00
|
|
|
user = self.get_user(user_name)
|
|
|
|
policy = user.get_policy(policy_name)
|
2014-08-19 21:30:11 +00:00
|
|
|
return policy
|
|
|
|
|
2017-04-13 21:09:23 +00:00
|
|
|
def list_user_policies(self, user_name):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
return user.policies.keys()
|
|
|
|
|
2019-12-20 02:30:43 +00:00
|
|
|
def list_user_tags(self, user_name):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
return user.tags
|
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
def put_user_policy(self, user_name, policy_name, policy_json):
|
2015-12-04 01:56:28 +00:00
|
|
|
user = self.get_user(user_name)
|
2019-06-30 15:35:26 +00:00
|
|
|
|
|
|
|
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_json)
|
|
|
|
iam_policy_document_validator.validate()
|
2015-12-04 01:56:28 +00:00
|
|
|
user.put_policy(policy_name, policy_json)
|
2014-08-19 21:30:11 +00:00
|
|
|
|
|
|
|
def delete_user_policy(self, user_name, policy_name):
|
2015-12-04 01:56:28 +00:00
|
|
|
user = self.get_user(user_name)
|
|
|
|
user.delete_policy(policy_name)
|
2014-08-19 21:30:11 +00:00
|
|
|
|
2019-10-17 08:28:19 +00:00
|
|
|
def delete_policy(self, policy_arn):
|
|
|
|
del self.managed_policies[policy_arn]
|
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
def create_access_key(self, user_name=None):
|
2015-12-04 01:56:28 +00:00
|
|
|
user = self.get_user(user_name)
|
|
|
|
key = user.create_access_key()
|
2014-08-19 21:30:11 +00:00
|
|
|
return key
|
|
|
|
|
2018-01-10 23:29:08 +00:00
|
|
|
def update_access_key(self, user_name, access_key_id, status):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
user.update_access_key(access_key_id, status)
|
|
|
|
|
2018-11-27 16:12:41 +00:00
|
|
|
def get_access_key_last_used(self, access_key_id):
|
|
|
|
access_keys_list = self.get_all_access_keys_for_all_users()
|
|
|
|
for key in access_keys_list:
|
|
|
|
if key.access_key_id == access_key_id:
|
2019-10-31 15:44:26 +00:00
|
|
|
return {"user_name": key.user_name, "last_used": key.last_used_iso_8601}
|
2018-11-27 16:12:41 +00:00
|
|
|
else:
|
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"The Access Key with id {0} cannot be found".format(access_key_id)
|
|
|
|
)
|
2018-11-27 16:12:41 +00:00
|
|
|
|
|
|
|
def get_all_access_keys_for_all_users(self):
|
|
|
|
access_keys_list = []
|
|
|
|
for user_name in self.users:
|
|
|
|
access_keys_list += self.get_all_access_keys(user_name)
|
|
|
|
return access_keys_list
|
2018-11-27 11:28:09 +00:00
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
def get_all_access_keys(self, user_name, marker=None, max_items=None):
|
2015-12-04 01:56:28 +00:00
|
|
|
user = self.get_user(user_name)
|
|
|
|
keys = user.get_all_access_keys()
|
2014-08-19 21:30:11 +00:00
|
|
|
return keys
|
|
|
|
|
2014-08-20 18:56:30 +00:00
|
|
|
def delete_access_key(self, access_key_id, user_name):
|
2015-12-04 01:56:28 +00:00
|
|
|
user = self.get_user(user_name)
|
|
|
|
user.delete_access_key(access_key_id)
|
2014-08-20 18:56:30 +00:00
|
|
|
|
2019-11-16 23:20:33 +00:00
|
|
|
def upload_ssh_public_key(self, user_name, ssh_public_key_body):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
return user.upload_ssh_public_key(ssh_public_key_body)
|
|
|
|
|
|
|
|
def get_ssh_public_key(self, user_name, ssh_public_key_id):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
return user.get_ssh_public_key(ssh_public_key_id)
|
|
|
|
|
|
|
|
def get_all_ssh_public_keys(self, user_name):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
return user.get_all_ssh_public_keys()
|
|
|
|
|
|
|
|
def update_ssh_public_key(self, user_name, ssh_public_key_id, status):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
return user.update_ssh_public_key(ssh_public_key_id, status)
|
|
|
|
|
|
|
|
def delete_ssh_public_key(self, user_name, ssh_public_key_id):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
return user.delete_ssh_public_key(ssh_public_key_id)
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
def enable_mfa_device(
|
|
|
|
self, user_name, serial_number, authentication_code_1, authentication_code_2
|
|
|
|
):
|
2017-03-27 18:08:57 +00:00
|
|
|
"""Enable MFA Device for user."""
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
if serial_number in user.mfa_devices:
|
|
|
|
raise IAMConflictException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"EntityAlreadyExists", "Device {0} already exists".format(serial_number)
|
2017-03-27 18:08:57 +00:00
|
|
|
)
|
|
|
|
|
2019-10-21 20:51:00 +00:00
|
|
|
device = self.virtual_mfa_devices.get(serial_number, None)
|
|
|
|
if device:
|
|
|
|
device.enable_date = datetime.utcnow()
|
|
|
|
device.user = user
|
|
|
|
device.user_attribute = {
|
2019-10-31 15:44:26 +00:00
|
|
|
"Path": user.path,
|
|
|
|
"UserName": user.name,
|
|
|
|
"UserId": user.id,
|
|
|
|
"Arn": user.arn,
|
|
|
|
"CreateDate": user.created_iso_8601,
|
|
|
|
"PasswordLastUsed": None, # not supported
|
|
|
|
"PermissionsBoundary": {}, # ToDo: add put_user_permissions_boundary() functionality
|
|
|
|
"Tags": {}, # ToDo: add tag_user() functionality
|
2019-10-21 20:51:00 +00:00
|
|
|
}
|
|
|
|
|
2017-03-27 18:08:57 +00:00
|
|
|
user.enable_mfa_device(
|
2019-10-31 15:44:26 +00:00
|
|
|
serial_number, authentication_code_1, authentication_code_2
|
2017-03-27 18:08:57 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def deactivate_mfa_device(self, user_name, serial_number):
|
|
|
|
"""Deactivate and detach MFA Device from user if device exists."""
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
if serial_number not in user.mfa_devices:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMNotFoundException("Device {0} not found".format(serial_number))
|
2017-03-27 18:08:57 +00:00
|
|
|
|
2019-10-21 20:51:00 +00:00
|
|
|
device = self.virtual_mfa_devices.get(serial_number, None)
|
|
|
|
if device:
|
|
|
|
device.enable_date = None
|
|
|
|
device.user = None
|
|
|
|
device.user_attribute = None
|
|
|
|
|
2017-03-27 18:08:57 +00:00
|
|
|
user.deactivate_mfa_device(serial_number)
|
|
|
|
|
|
|
|
def list_mfa_devices(self, user_name):
|
|
|
|
user = self.get_user(user_name)
|
|
|
|
return user.mfa_devices.values()
|
|
|
|
|
2019-10-20 20:39:57 +00:00
|
|
|
def create_virtual_mfa_device(self, device_name, path):
|
|
|
|
if not path:
|
2019-10-31 15:44:26 +00:00
|
|
|
path = "/"
|
2019-10-20 20:39:57 +00:00
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
if not path.startswith("/") and not path.endswith("/"):
|
|
|
|
raise ValidationError(
|
|
|
|
"The specified value for path is invalid. "
|
|
|
|
"It must begin and end with / and contain only alphanumeric characters and/or / characters."
|
|
|
|
)
|
2019-10-20 20:39:57 +00:00
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
if any(not len(part) for part in path.split("/")[1:-1]):
|
|
|
|
raise ValidationError(
|
|
|
|
"The specified value for path is invalid. "
|
|
|
|
"It must begin and end with / and contain only alphanumeric characters and/or / characters."
|
|
|
|
)
|
2019-10-20 20:39:57 +00:00
|
|
|
|
|
|
|
if len(path) > 512:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise ValidationError(
|
|
|
|
"1 validation error detected: "
|
|
|
|
'Value "{}" at "path" failed to satisfy constraint: '
|
|
|
|
"Member must have length less than or equal to 512"
|
|
|
|
)
|
2019-10-20 20:39:57 +00:00
|
|
|
|
|
|
|
device = VirtualMfaDevice(path + device_name)
|
|
|
|
|
|
|
|
if device.serial_number in self.virtual_mfa_devices:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise EntityAlreadyExists(
|
|
|
|
"MFADevice entity at the same path and name already exists."
|
|
|
|
)
|
2019-10-20 20:39:57 +00:00
|
|
|
|
|
|
|
self.virtual_mfa_devices[device.serial_number] = device
|
|
|
|
return device
|
|
|
|
|
2019-10-20 21:03:20 +00:00
|
|
|
def delete_virtual_mfa_device(self, serial_number):
|
|
|
|
device = self.virtual_mfa_devices.pop(serial_number, None)
|
|
|
|
|
|
|
|
if not device:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMNotFoundException(
|
|
|
|
"VirtualMFADevice with serial number {0} doesn't exist.".format(
|
|
|
|
serial_number
|
|
|
|
)
|
|
|
|
)
|
2019-10-20 21:03:20 +00:00
|
|
|
|
2019-10-21 19:48:50 +00:00
|
|
|
def list_virtual_mfa_devices(self, assignment_status, marker, max_items):
|
|
|
|
devices = list(self.virtual_mfa_devices.values())
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
if assignment_status == "Assigned":
|
2019-10-21 19:48:50 +00:00
|
|
|
devices = [device for device in devices if device.enable_date]
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
if assignment_status == "Unassigned":
|
2019-10-21 19:48:50 +00:00
|
|
|
devices = [device for device in devices if not device.enable_date]
|
|
|
|
|
|
|
|
sorted(devices, key=lambda device: device.serial_number)
|
|
|
|
max_items = int(max_items)
|
|
|
|
start_idx = int(marker) if marker else 0
|
|
|
|
|
|
|
|
if start_idx > len(devices):
|
2019-10-31 15:44:26 +00:00
|
|
|
raise ValidationError("Invalid Marker.")
|
2019-10-21 19:48:50 +00:00
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
devices = devices[start_idx : start_idx + max_items]
|
2019-10-21 19:48:50 +00:00
|
|
|
|
|
|
|
if len(devices) < max_items:
|
|
|
|
marker = None
|
|
|
|
else:
|
|
|
|
marker = str(start_idx + max_items)
|
|
|
|
|
|
|
|
return devices, marker
|
|
|
|
|
2014-08-19 21:30:11 +00:00
|
|
|
def delete_user(self, user_name):
|
2019-10-22 13:27:49 +00:00
|
|
|
user = self.get_user(user_name)
|
|
|
|
if user.managed_policies:
|
|
|
|
raise IAMConflictException(
|
|
|
|
code="DeleteConflict",
|
2019-10-31 15:44:26 +00:00
|
|
|
message="Cannot delete entity, must detach all policies first.",
|
2019-10-22 13:27:49 +00:00
|
|
|
)
|
|
|
|
if user.policies:
|
|
|
|
raise IAMConflictException(
|
|
|
|
code="DeleteConflict",
|
2019-10-31 15:44:26 +00:00
|
|
|
message="Cannot delete entity, must delete policies first.",
|
2019-10-22 13:27:49 +00:00
|
|
|
)
|
|
|
|
del self.users[user_name]
|
2014-08-19 21:30:11 +00:00
|
|
|
|
2015-02-02 22:42:57 +00:00
|
|
|
def report_generated(self):
|
|
|
|
return self.credential_report
|
|
|
|
|
|
|
|
def generate_report(self):
|
|
|
|
self.credential_report = True
|
|
|
|
|
|
|
|
def get_credential_report(self):
|
|
|
|
if not self.credential_report:
|
2015-12-04 01:56:28 +00:00
|
|
|
raise IAMReportNotPresentException("Credential report not present")
|
2019-10-31 15:44:26 +00:00
|
|
|
report = "user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_2_active,access_key_2_last_rotated,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated\n"
|
2015-02-02 22:42:57 +00:00
|
|
|
for user in self.users:
|
|
|
|
report += self.users[user].to_csv()
|
2019-10-31 15:44:26 +00:00
|
|
|
return base64.b64encode(report.encode("ascii")).decode("ascii")
|
2014-08-19 21:30:11 +00:00
|
|
|
|
2017-09-07 18:21:44 +00:00
|
|
|
def list_account_aliases(self):
|
|
|
|
return self.account_aliases
|
|
|
|
|
|
|
|
def create_account_alias(self, alias):
|
|
|
|
# alias is force updated
|
|
|
|
self.account_aliases = [alias]
|
|
|
|
|
|
|
|
def delete_account_alias(self, alias):
|
|
|
|
self.account_aliases = []
|
|
|
|
|
2018-08-07 17:31:36 +00:00
|
|
|
def get_account_authorization_details(self, filter):
|
|
|
|
policies = self.managed_policies.values()
|
|
|
|
local_policies = set(policies) - set(aws_managed_policies)
|
|
|
|
returned_policies = []
|
|
|
|
|
|
|
|
if len(filter) == 0:
|
|
|
|
return {
|
2019-10-31 15:44:26 +00:00
|
|
|
"instance_profiles": self.instance_profiles.values(),
|
|
|
|
"roles": self.roles.values(),
|
|
|
|
"groups": self.groups.values(),
|
|
|
|
"users": self.users.values(),
|
|
|
|
"managed_policies": self.managed_policies.values(),
|
2018-08-07 17:31:36 +00:00
|
|
|
}
|
|
|
|
|
2019-10-31 15:44:26 +00:00
|
|
|
if "AWSManagedPolicy" in filter:
|
2018-08-07 17:31:36 +00:00
|
|
|
returned_policies = aws_managed_policies
|
2019-10-31 15:44:26 +00:00
|
|
|
if "LocalManagedPolicy" in filter:
|
2018-08-07 17:31:36 +00:00
|
|
|
returned_policies = returned_policies + list(local_policies)
|
|
|
|
|
|
|
|
return {
|
2019-10-31 15:44:26 +00:00
|
|
|
"instance_profiles": self.instance_profiles.values(),
|
|
|
|
"roles": self.roles.values() if "Role" in filter else [],
|
|
|
|
"groups": self.groups.values() if "Group" in filter else [],
|
|
|
|
"users": self.users.values() if "User" in filter else [],
|
|
|
|
"managed_policies": returned_policies,
|
2018-08-07 17:31:36 +00:00
|
|
|
}
|
|
|
|
|
2018-11-19 23:47:21 +00:00
|
|
|
def create_saml_provider(self, name, saml_metadata_document):
|
|
|
|
saml_provider = SAMLProvider(name, saml_metadata_document)
|
|
|
|
self.saml_providers[name] = saml_provider
|
|
|
|
return saml_provider
|
|
|
|
|
|
|
|
def update_saml_provider(self, saml_provider_arn, saml_metadata_document):
|
|
|
|
saml_provider = self.get_saml_provider(saml_provider_arn)
|
|
|
|
saml_provider.saml_metadata_document = saml_metadata_document
|
|
|
|
return saml_provider
|
|
|
|
|
|
|
|
def delete_saml_provider(self, saml_provider_arn):
|
|
|
|
try:
|
|
|
|
for saml_provider in list(self.list_saml_providers()):
|
|
|
|
if saml_provider.arn == saml_provider_arn:
|
|
|
|
del self.saml_providers[saml_provider.name]
|
|
|
|
except KeyError:
|
|
|
|
raise IAMNotFoundException(
|
2019-10-31 15:44:26 +00:00
|
|
|
"SAMLProvider {0} not found".format(saml_provider_arn)
|
|
|
|
)
|
2018-11-19 23:47:21 +00:00
|
|
|
|
|
|
|
def list_saml_providers(self):
|
|
|
|
return self.saml_providers.values()
|
|
|
|
|
|
|
|
def get_saml_provider(self, saml_provider_arn):
|
|
|
|
for saml_provider in self.list_saml_providers():
|
|
|
|
if saml_provider.arn == saml_provider_arn:
|
|
|
|
return saml_provider
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMNotFoundException(
|
|
|
|
"SamlProvider {0} not found".format(saml_provider_arn)
|
|
|
|
)
|
2018-11-19 23:47:21 +00:00
|
|
|
|
2019-07-13 13:58:34 +00:00
|
|
|
def get_user_from_access_key_id(self, access_key_id):
|
|
|
|
for user_name, user in self.users.items():
|
|
|
|
access_keys = self.get_all_access_keys(user_name)
|
|
|
|
for access_key in access_keys:
|
|
|
|
if access_key.access_key_id == access_key_id:
|
|
|
|
return user
|
|
|
|
return None
|
|
|
|
|
2019-10-18 15:29:15 +00:00
|
|
|
def create_open_id_connect_provider(self, url, thumbprint_list, client_id_list):
|
|
|
|
open_id_provider = OpenIDConnectProvider(url, thumbprint_list, client_id_list)
|
|
|
|
|
|
|
|
if open_id_provider.arn in self.open_id_providers:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise EntityAlreadyExists("Unknown")
|
2019-10-18 15:29:15 +00:00
|
|
|
|
|
|
|
self.open_id_providers[open_id_provider.arn] = open_id_provider
|
|
|
|
return open_id_provider
|
|
|
|
|
2019-10-18 18:51:22 +00:00
|
|
|
def delete_open_id_connect_provider(self, arn):
|
|
|
|
self.open_id_providers.pop(arn, None)
|
|
|
|
|
2019-10-18 18:37:35 +00:00
|
|
|
def get_open_id_connect_provider(self, arn):
|
|
|
|
open_id_provider = self.open_id_providers.get(arn)
|
|
|
|
|
|
|
|
if not open_id_provider:
|
2019-10-31 15:44:26 +00:00
|
|
|
raise IAMNotFoundException(
|
|
|
|
"OpenIDConnect Provider not found for arn {}".format(arn)
|
|
|
|
)
|
2019-10-18 18:37:35 +00:00
|
|
|
|
|
|
|
return open_id_provider
|
|
|
|
|
2019-10-18 19:12:44 +00:00
|
|
|
def list_open_id_connect_providers(self):
|
|
|
|
return list(self.open_id_providers.keys())
|
|
|
|
|
2019-11-01 06:14:03 +00:00
|
|
|
def update_account_password_policy(
|
|
|
|
self,
|
|
|
|
allow_change_password,
|
|
|
|
hard_expiry,
|
|
|
|
max_password_age,
|
|
|
|
minimum_password_length,
|
|
|
|
password_reuse_prevention,
|
|
|
|
require_lowercase_characters,
|
|
|
|
require_numbers,
|
|
|
|
require_symbols,
|
|
|
|
require_uppercase_characters,
|
|
|
|
):
|
2019-10-28 22:16:19 +00:00
|
|
|
self.account_password_policy = AccountPasswordPolicy(
|
|
|
|
allow_change_password,
|
|
|
|
hard_expiry,
|
|
|
|
max_password_age,
|
|
|
|
minimum_password_length,
|
|
|
|
password_reuse_prevention,
|
|
|
|
require_lowercase_characters,
|
|
|
|
require_numbers,
|
|
|
|
require_symbols,
|
2019-11-01 06:14:03 +00:00
|
|
|
require_uppercase_characters,
|
2019-10-28 22:16:19 +00:00
|
|
|
)
|
|
|
|
|
2019-10-28 22:50:17 +00:00
|
|
|
def get_account_password_policy(self):
|
|
|
|
if not self.account_password_policy:
|
2019-11-01 06:14:03 +00:00
|
|
|
raise NoSuchEntity(
|
|
|
|
"The Password Policy with domain name {} cannot be found.".format(
|
|
|
|
ACCOUNT_ID
|
|
|
|
)
|
|
|
|
)
|
2019-10-28 22:50:17 +00:00
|
|
|
|
|
|
|
return self.account_password_policy
|
|
|
|
|
2019-11-01 06:00:50 +00:00
|
|
|
def delete_account_password_policy(self):
|
|
|
|
if not self.account_password_policy:
|
2019-11-01 06:14:03 +00:00
|
|
|
raise NoSuchEntity(
|
|
|
|
"The account policy with name PasswordPolicy cannot be found."
|
|
|
|
)
|
2019-11-01 06:00:50 +00:00
|
|
|
|
|
|
|
self.account_password_policy = None
|
|
|
|
|
2019-11-17 12:47:19 +00:00
|
|
|
def get_account_summary(self):
|
|
|
|
return self.account_summary
|
|
|
|
|
2017-02-24 02:37:43 +00:00
|
|
|
|
2014-03-27 23:12:53 +00:00
|
|
|
iam_backend = IAMBackend()
|