def test_with_well_formed_string(self): role = AWSRole( 'arn:aws:iam::123456789012:role/path/role-name,provider') self.assertEqual(role.account, '123456789012') self.assertEqual(role.arn, 'arn:aws:iam::123456789012:role/path/role-name') self.assertEqual(role.name, 'role-name') self.assertEqual(role.profile, '123456789012.role-name') self.assertEqual(role.provider, 'provider')
def test_with_role_as_none(self): is_valid = AWSRole.is_valid(None) self.assertFalse(is_valid)
def test_with_valid_strings(self): role = AWSRole('arn/role,provider') self.assertEqual(role.name, 'role')
def test_parsed_role(self): role = AWSRole('arn/role,provider') self.assertEqual(role.arn, 'arn/role') self.assertEqual(role.provider, 'provider')
def test_with_valid_strings(self): is_valid = AWSRole.is_valid('arn/role,provider') self.assertTrue(is_valid)
def test_with_missing_provider_string(self): is_valid = AWSRole.is_valid('arn/role,') self.assertFalse(is_valid)
def test_with_missing_arn_string(self): is_valid = AWSRole.is_valid(',provider') self.assertFalse(is_valid)
def test_with_malformed_role_string(self): is_valid = AWSRole.is_valid('arn_noseparator_provider') self.assertFalse(is_valid)
def test_with_empty_string(self): is_valid = AWSRole.is_valid('') self.assertFalse(is_valid)
def _handle_sts_from_response(self, response, region, config_filename, default_role, list_only): """ Takes a successful SAML response, parses it for valid AWS IAM roles, and then reaches out to AWS and requests temporary tokens for each of the IAM roles. :param response: The SAML response from a previous request to ADFS :param region: The AWS region tokens are being requested for :param config_filename: Where shoujld the tokens be written to :param default_role: Which IAM role should be as as the default in the config file :param list_only: If set, the IAM roles available will just be printed instead of assumed """ soup = BeautifulSoup(response.text, 'html.parser') # Look for the SAMLResponse attribute of the input tag (determined by # analyzing the debug print lines above) assertion = None for inputtag in soup.find_all('input'): if inputtag.get('name') == 'SAMLResponse': assertion = inputtag.get('value') if not assertion: raise Exception( "did not get a valid SAML response. response was:\n%s" % response.text) # Parse the returned assertion and extract the authorized roles aws_roles = [] root = ET.fromstring(base64.b64decode(assertion)) for saml2attribute in root.iter( '{urn:oasis:names:tc:SAML:2.0:assertion}Attribute'): if saml2attribute.get( 'Name') == 'https://aws.amazon.com/SAML/Attributes/Role': for saml2attributevalue in saml2attribute.iter( '{urn:oasis:names:tc:SAML:2.0:assertion}AttributeValue' ): aws_roles.append(saml2attributevalue.text) if not aws_roles: raise Exception("user does not have any valid aws roles.") # Note the format of the attribute value should be role_arn,principal_arn # but lots of blogs list it as principal_arn,role_arn so let's reverse # them if needed for aws_role in aws_roles: chunks = aws_role.split(',') if 'saml-provider' in chunks[0]: new_aws_role = chunks[1] + ',' + chunks[0] index = aws_roles.index(aws_role) aws_roles.insert(index, new_aws_role) aws_roles.remove(aws_role) # If the user supplied to a default role make sure # that role is available to them. if default_role is not None: found_default_role = False for aws_role in aws_roles: name = AWSRole(aws_role).name if name == default_role: found_default_role = True break if not found_default_role: raise Exception( "provided default role not found in list of available roles" ) # Go through each of the available roles and # attempt to get temporary tokens for each for aws_role in aws_roles: profile = AWSRole(aws_role).name if list_only: logging.info("role: {}".format(profile)) else: token = self._bind_assertion_to_role(assertion, aws_role, profile, region, config_filename, default_role) if not token: raise Exception("did not receive a valid token from aws.") expires_utc = token.credentials.expiration if default_role == profile: logging.info("default role: {} until {}".format( profile, expires_utc)) else: logging.info("role: {} until {}".format( profile, expires_utc))