Implemented finding credentials from already created IAM users and roles.
This commit is contained in:
parent
4ccf48e46b
commit
23957fe940
@ -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": "dfG1QfHkJvMrBLzm9D9GTPdzHxIFy/qe4ObbgylK"
|
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:
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user