Implemented finding credentials from already created IAM users and roles.

This commit is contained in:
acsbendi 2019-07-08 19:57:14 +02:00
parent 4ccf48e46b
commit 23957fe940
5 changed files with 206 additions and 59 deletions

View File

@ -6,19 +6,115 @@ from enum import Enum
from botocore.auth import SigV4Auth, S3SigV4Auth from botocore.auth import SigV4Auth, S3SigV4Auth
from botocore.awsrequest import AWSRequest from botocore.awsrequest import AWSRequest
from botocore.credentials import Credentials from botocore.credentials import Credentials
from moto.iam.models import ACCOUNT_ID, Policy from moto.iam.models import ACCOUNT_ID, Policy
from moto.iam import iam_backend from moto.iam import iam_backend
from moto.core.exceptions import SignatureDoesNotMatchError, AccessDeniedError, InvalidClientTokenIdError, InvalidAccessKeyIdError, AuthFailureError
from moto.s3.exceptions import BucketAccessDeniedError, S3AccessDeniedError, BucketInvalidTokenError, S3InvalidTokenError, S3InvalidAccessKeyIdError, BucketInvalidAccessKeyIdError
from moto.sts import sts_backend
from moto.core.exceptions import SignatureDoesNotMatchError, AccessDeniedError, InvalidClientTokenIdError
from moto.s3.exceptions import BucketAccessDeniedError, S3AccessDeniedError
ACCESS_KEY_STORE = { def create_access_key(access_key_id, headers):
"AKIAJDULPKHCC4KGTYVA": { if access_key_id.startswith("AKIA") or "X-Amz-Security-Token" not in headers:
"owner": "avatao-user", return IAMUserAccessKey(access_key_id, headers)
"secret_access_key": "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" else:
} return AssumedRoleAccessKey(access_key_id, headers)
}
class IAMUserAccessKey:
def __init__(self, access_key_id, headers):
iam_users = iam_backend.list_users('/', None, None)
for iam_user in iam_users:
for access_key in iam_user.access_keys:
if access_key.access_key_id == access_key_id:
self._owner_user_name = iam_user.name
self._access_key_id = access_key_id
self._secret_access_key = access_key.secret_access_key
if "X-Amz-Security-Token" in headers:
raise CreateAccessKeyFailure(reason="InvalidToken")
return
raise CreateAccessKeyFailure(reason="InvalidId")
@property
def arn(self):
return "arn:aws:iam::{account_id}:user/{iam_user_name}".format(
account_id=ACCOUNT_ID,
iam_user_name=self._owner_user_name
)
def create_credentials(self):
return Credentials(self._access_key_id, self._secret_access_key)
def collect_policies(self):
user_policies = []
inline_policy_names = iam_backend.list_user_policies(self._owner_user_name)
for inline_policy_name in inline_policy_names:
inline_policy = iam_backend.get_user_policy(self._owner_user_name, inline_policy_name)
user_policies.append(inline_policy)
attached_policies, _ = iam_backend.list_attached_user_policies(self._owner_user_name)
user_policies += attached_policies
user_groups = iam_backend.get_groups_for_user(self._owner_user_name)
for user_group in user_groups:
inline_group_policy_names = iam_backend.list_group_policies(user_group)
for inline_group_policy_name in inline_group_policy_names:
inline_user_group_policy = iam_backend.get_group_policy(user_group.name, inline_group_policy_name)
user_policies.append(inline_user_group_policy)
attached_group_policies = iam_backend.list_attached_group_policies(user_group.name)
user_policies += attached_group_policies
return user_policies
class AssumedRoleAccessKey:
def __init__(self, access_key_id, headers):
for assumed_role in sts_backend.assumed_roles:
if assumed_role.access_key_id == access_key_id:
self._access_key_id = access_key_id
self._secret_access_key = assumed_role.secret_access_key
self._session_token = assumed_role.session_token
self._owner_role_name = assumed_role.arn.split("/")[-1]
self._session_name = assumed_role.session_name
if headers["X-Amz-Security-Token"] != self._session_name:
raise CreateAccessKeyFailure(reason="InvalidToken")
return
raise CreateAccessKeyFailure(reason="InvalidId")
@property
def arn(self):
return "arn:aws:sts::{account_id}:assumed-role/{role_name}/{session_name}".format(
account_id=ACCOUNT_ID,
role_name=self._owner_role_name,
session_name=self._session_name
)
def create_credentials(self):
return Credentials(self._access_key_id, self._secret_access_key, self._session_token)
def collect_policies(self):
role_policies = []
inline_policy_names = iam_backend.list_role_policies(self._owner_role_name)
for inline_policy_name in inline_policy_names:
inline_policy = iam_backend.get_role_policy(self._owner_role_name, inline_policy_name)
role_policies.append(inline_policy)
attached_policies, _ = iam_backend.list_attached_role_policies(self._owner_role_name)
role_policies += attached_policies
return role_policies
class CreateAccessKeyFailure(Exception):
def __init__(self, reason, *args):
super().__init__(*args)
self.reason = reason
class IAMRequestBase(ABC): class IAMRequestBase(ABC):
@ -31,10 +127,13 @@ class IAMRequestBase(ABC):
self._headers = headers self._headers = headers
credential_scope = self._get_string_between('Credential=', ',', self._headers['Authorization']) credential_scope = self._get_string_between('Credential=', ',', self._headers['Authorization'])
credential_data = credential_scope.split('/') credential_data = credential_scope.split('/')
self._access_key = credential_data[0]
self._region = credential_data[2] self._region = credential_data[2]
self._service = credential_data[3] self._service = credential_data[3]
self._action = self._service + ":" + self._data["Action"][0] self._action = self._service + ":" + self._data["Action"][0]
try:
self._access_key = create_access_key(access_key_id=credential_data[0], headers=headers)
except CreateAccessKeyFailure as e:
self._raise_invalid_access_key(e.reason)
def check_signature(self): def check_signature(self):
original_signature = self._get_string_between('Signature=', ',', self._headers['Authorization']) original_signature = self._get_string_between('Signature=', ',', self._headers['Authorization'])
@ -43,48 +142,30 @@ class IAMRequestBase(ABC):
raise SignatureDoesNotMatchError() raise SignatureDoesNotMatchError()
def check_action_permitted(self): def check_action_permitted(self):
iam_user_name = ACCESS_KEY_STORE[self._access_key]["owner"] self._check_action_permitted_for_iam_user()
user_policies = self._collect_policies_for_iam_user(iam_user_name)
def _check_action_permitted_for_iam_user(self):
policies = self._access_key.collect_policies()
permitted = False permitted = False
for policy in user_policies: for policy in policies:
iam_policy = IAMPolicy(policy) iam_policy = IAMPolicy(policy)
permission_result = iam_policy.is_action_permitted(self._action) permission_result = iam_policy.is_action_permitted(self._action)
if permission_result == PermissionResult.DENIED: if permission_result == PermissionResult.DENIED:
self._raise_access_denied(iam_user_name) self._raise_access_denied()
elif permission_result == PermissionResult.PERMITTED: elif permission_result == PermissionResult.PERMITTED:
permitted = True permitted = True
if not permitted: if not permitted:
self._raise_access_denied(iam_user_name) self._raise_access_denied()
@abstractmethod @abstractmethod
def _raise_access_denied(self, iam_user_name): def _raise_access_denied(self):
raise NotImplementedError() raise NotImplementedError()
@staticmethod @abstractmethod
def _collect_policies_for_iam_user(iam_user_name): def _raise_invalid_access_key(self, reason):
user_policies = [] raise NotImplementedError()
inline_policy_names = iam_backend.list_user_policies(iam_user_name)
for inline_policy_name in inline_policy_names:
inline_policy = iam_backend.get_user_policy(iam_user_name, inline_policy_name)
user_policies.append(inline_policy)
attached_policies, _ = iam_backend.list_attached_user_policies(iam_user_name)
user_policies += attached_policies
user_groups = iam_backend.get_groups_for_user(iam_user_name)
for user_group in user_groups:
inline_group_policy_names = iam_backend.list_group_policies(user_group)
for inline_group_policy_name in inline_group_policy_names:
inline_user_group_policy = iam_backend.get_group_policy(user_group.name, inline_group_policy_name)
user_policies.append(inline_user_group_policy)
attached_group_policies = iam_backend.list_attached_group_policies(user_group.name)
user_policies += attached_group_policies
return user_policies
@abstractmethod @abstractmethod
def _create_auth(self, credentials): def _create_auth(self, credentials):
@ -107,11 +188,7 @@ class IAMRequestBase(ABC):
return request return request
def _calculate_signature(self): def _calculate_signature(self):
if self._access_key not in ACCESS_KEY_STORE: credentials = self._access_key.create_credentials()
raise InvalidClientTokenIdError()
secret_key = ACCESS_KEY_STORE[self._access_key]["secret_access_key"]
credentials = Credentials(self._access_key, secret_key)
auth = self._create_auth(credentials) auth = self._create_auth(credentials)
request = self._create_aws_request() request = self._create_aws_request()
canonical_request = auth.canonical_request(request) canonical_request = auth.canonical_request(request)
@ -125,23 +202,41 @@ class IAMRequestBase(ABC):
class IAMRequest(IAMRequestBase): class IAMRequest(IAMRequestBase):
def _raise_invalid_access_key(self, _):
if self._service == "ec2":
raise AuthFailureError()
else:
raise InvalidClientTokenIdError()
def _create_auth(self, credentials): def _create_auth(self, credentials):
return SigV4Auth(credentials, self._service, self._region) return SigV4Auth(credentials, self._service, self._region)
def _raise_access_denied(self, iam_user_name): def _raise_access_denied(self):
raise AccessDeniedError( raise AccessDeniedError(
account_id=ACCOUNT_ID, user_arn=self._access_key.arn,
iam_user_name=iam_user_name,
action=self._action action=self._action
) )
class S3IAMRequest(IAMRequestBase): class S3IAMRequest(IAMRequestBase):
def _raise_invalid_access_key(self, reason):
if reason == "InvalidToken":
if "BucketName" in self._data:
raise BucketInvalidTokenError(bucket=self._data["BucketName"])
else:
raise S3InvalidTokenError()
else:
if "BucketName" in self._data:
raise BucketInvalidAccessKeyIdError(bucket=self._data["BucketName"])
else:
raise S3InvalidAccessKeyIdError()
def _create_auth(self, credentials): def _create_auth(self, credentials):
return S3SigV4Auth(credentials, self._service, self._region) return S3SigV4Auth(credentials, self._service, self._region)
def _raise_access_denied(self, _): def _raise_access_denied(self):
if "BucketName" in self._data: if "BucketName" in self._data:
raise BucketAccessDeniedError(bucket=self._data["BucketName"]) raise BucketAccessDeniedError(bucket=self._data["BucketName"])
else: else:

View File

@ -88,11 +88,29 @@ class InvalidClientTokenIdError(RESTError):
class AccessDeniedError(RESTError): class AccessDeniedError(RESTError):
code = 403 code = 403
def __init__(self, account_id, iam_user_name, action): def __init__(self, user_arn, action):
super(AccessDeniedError, self).__init__( super(AccessDeniedError, self).__init__(
'AccessDenied', 'AccessDenied',
"User: arn:aws:iam::{account_id}:user/{iam_user_name} is not authorized to perform: {operation}".format( "User: {user_arn} is not authorized to perform: {operation}".format(
account_id=account_id, user_arn=user_arn,
iam_user_name=iam_user_name,
operation=action operation=action
)) ))
class InvalidAccessKeyIdError(RESTError):
code = 400
def __init__(self):
super(InvalidAccessKeyIdError, self).__init__(
'InvalidAccessKeyId',
"The AWS Access Key Id you provided does not exist in our records.")
class AuthFailureError(RESTError):
code = 400
def __init__(self):
super(AuthFailureError, self).__init__(
'AuthFailure',
"AWS was not able to validate the provided access credentials")

View File

@ -111,21 +111,19 @@ class ActionAuthenticatorMixin(object):
INITIAL_NO_AUTH_ACTION_COUNT = int(os.environ.get("INITIAL_NO_AUTH_ACTION_COUNT", 999999999)) INITIAL_NO_AUTH_ACTION_COUNT = int(os.environ.get("INITIAL_NO_AUTH_ACTION_COUNT", 999999999))
request_count = 0 request_count = 0
def _authenticate_action(self, iam_request): def _authenticate_action(self, iam_request_cls):
iam_request.check_signature()
if ActionAuthenticatorMixin.request_count >= ActionAuthenticatorMixin.INITIAL_NO_AUTH_ACTION_COUNT: if ActionAuthenticatorMixin.request_count >= ActionAuthenticatorMixin.INITIAL_NO_AUTH_ACTION_COUNT:
iam_request = iam_request_cls(method=self.method, path=self.path, data=self.data, headers=self.headers)
iam_request.check_signature()
iam_request.check_action_permitted() iam_request.check_action_permitted()
else: else:
ActionAuthenticatorMixin.request_count += 1 ActionAuthenticatorMixin.request_count += 1
def _authenticate_normal_action(self): def _authenticate_normal_action(self):
iam_request = IAMRequest(method=self.method, path=self.path, data=self.data, headers=self.headers) self._authenticate_action(IAMRequest)
self._authenticate_action(iam_request)
def _authenticate_s3_action(self): def _authenticate_s3_action(self):
iam_request = S3IAMRequest(method=self.method, path=self.path, data=self.data, headers=self.headers) self._authenticate_action(S3IAMRequest)
self._authenticate_action(iam_request)
class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):

View File

@ -213,3 +213,35 @@ class BucketAccessDeniedError(BucketError):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(BucketAccessDeniedError, self).__init__('AccessDenied', 'Access Denied', *args, **kwargs) super(BucketAccessDeniedError, self).__init__('AccessDenied', 'Access Denied', *args, **kwargs)
class S3InvalidTokenError(S3ClientError):
code = 400
def __init__(self, *args, **kwargs):
super(S3InvalidTokenError, self).__init__('InvalidToken', 'The provided token is malformed or otherwise invalid.', *args, **kwargs)
class BucketInvalidTokenError(BucketError):
code = 400
def __init__(self, *args, **kwargs):
super(BucketInvalidTokenError, self).__init__('InvalidToken', 'The provided token is malformed or otherwise invalid.', *args, **kwargs)
class S3InvalidAccessKeyIdError(S3ClientError):
code = 400
def __init__(self, *args, **kwargs):
super(S3InvalidAccessKeyIdError, self).__init__(
'InvalidAccessKeyId',
"The AWS Access Key Id you provided does not exist in our records.", *args, **kwargs)
class BucketInvalidAccessKeyIdError(S3ClientError):
code = 400
def __init__(self, *args, **kwargs):
super(BucketInvalidAccessKeyIdError, self).__init__(
'InvalidAccessKeyId',
"The AWS Access Key Id you provided does not exist in our records.", *args, **kwargs)

View File

@ -38,6 +38,9 @@ class AssumedRole(BaseModel):
class STSBackend(BaseBackend): class STSBackend(BaseBackend):
def __init__(self):
self.assumed_roles = []
def get_session_token(self, duration): def get_session_token(self, duration):
token = Token(duration=duration) token = Token(duration=duration)
return token return token
@ -48,6 +51,7 @@ class STSBackend(BaseBackend):
def assume_role(self, **kwargs): def assume_role(self, **kwargs):
role = AssumedRole(**kwargs) role = AssumedRole(**kwargs)
self.assumed_roles.append(role)
return role return role