コード例 #1
0
    def __init__(self, config: Section):
        self.sts_client = boto3.client("sts")
        self.config = config
        self.saml = None
        self.all_roles = None
        self.role_arn = None
        self.credentials = None
        self.duration_seconds = int(config['duration_seconds'])
        self.user_credentials = UserCredentials(config)
        self.mfa = MFACredentials(config)

        base_uri_parts = self.config['base_uri'].split('.')
        self.ol_client = OneLoginClient(
            self.config['client_id'],
            self.config['client_secret'],
            base_uri_parts[1],
        )
コード例 #2
0
    def test_load_password_promptuser(self):
        cfg = helper.build_config("""[test-section]
username = mock_user
""")
        sec = cfg.section("test-section")

        creds = UserCredentials(sec)
        creds._load_password_from_keychain = MagicMock()
        creds._prompt_user_password = MagicMock()
        creds._save_password_to_keychain = MagicMock()

        creds.load_password()

        creds._load_password_from_keychain.assert_not_called()
        creds._save_password_to_keychain.assert_not_called()

        creds._prompt_user_password.assert_called_once_with()
コード例 #3
0
    def test_save_password_success(self):
        cfg = helper.build_config("""[test-section]
username = mock_user
save_password = true
""")
        sec = cfg.section("test-section")

        creds = UserCredentials(sec)

        def create_password():
            creds.password = "******"

        creds._load_password_from_keychain = MagicMock()
        creds._prompt_user_password = MagicMock(side_effect=create_password)
        creds._save_password_to_keychain = MagicMock()

        creds.load_password()

        creds._load_password_from_keychain.assert_called_once_with()
        creds._prompt_user_password.assert_called_once_with()
        creds._save_password_to_keychain.assert_called_once_with()
コード例 #4
0
class OneloginAWS(object):
    """
    Handles the authentication between OneLogin SAML Assertion and the AWS
    identity federation
    """

    def __init__(self, config: Section):
        self.sts_client = boto3.client("sts")
        self.config = config
        self.saml = None
        self.all_roles = None
        self.role_arn = None
        self.credentials = None
        self.duration_seconds = int(config['duration_seconds'])
        self.user_credentials = UserCredentials(config)
        self.mfa = MFACredentials(config)

        base_uri_parts = self.config['base_uri'].split('.')
        self.ol_client = OneLoginClient(
            self.config['client_id'],
            self.config['client_secret'],
            base_uri_parts[1],
        )

    def get_saml_assertion(self):
        """
        Retrieve users credentials and get the SAML assertion from Onelogin,
        based on the users choice of AWS account to log into
        """

        self.user_credentials.load_credentials()

        saml_resp = self.ol_client.get_saml_assertion(
            username_or_email=self.user_credentials.username,
            password=self.user_credentials.password,
            app_id=self.config['aws_app_id'],
            subdomain=self.config['subdomain'],
            ip_address=self.get_ip_address(),
        )

        if saml_resp is None:
            raise Exception("Onelogin Error: '{error}' '{desc}'".format(
                error=self.ol_client.error,
                desc=self.ol_client.error_description
            ))

        if saml_resp.mfa:
            if not self.mfa.ready():
                self.mfa.select_device(saml_resp.mfa.devices)
                if not self.mfa.has_otp:
                    self.mfa.prompt_token()

            saml_resp = self.ol_client.get_saml_assertion_verifying(
                self.config['aws_app_id'],
                self.mfa.device.id,
                saml_resp.mfa.state_token,
                self.mfa.otp
            )

        self.saml = saml_resp

    def get_ip_address(self) -> Optional[str]:
        """
        Get the client IP address.
        Uses either the `ip_address` in config,
        or if `auto_determine_ip_address` is specified in config,
        the ipify service is used to dynamically lookup the IP address.
        """

        # if ip address has been hard coded in config file, use that
        ip_address = self.config.get('ip_address')
        if ip_address is not None:
            return ip_address

        # if auto determine is enabled, use ipify to lookup the ip
        if self.config.auto_determine_ip_address:
            ip_address = ipify.get_ip()
            return ip_address

    def get_arns(self):
        """Extract the IAM Role ARNs from the SAML Assertion"""

        if not self.saml:
            self.get_saml_assertion()
        # Parse the returned assertion and extract the authorized roles
        aws_roles = []
        root = ElementTree.fromstring(
            base64.b64decode(self.saml.saml_response))

        namespace = "{urn:oasis:names:tc:SAML:2.0:assertion}"
        role_name = "https://aws.amazon.com/SAML/Attributes/Role"
        for attr in root.iter(namespace + "Attribute"):
            if attr.get("Name") == role_name:
                for val in attr.iter(namespace + "AttributeValue"):
                    aws_roles.append(val.text)

        # 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
        aws_roles = [role.split(",") for role in aws_roles]
        aws_roles = [(role, principal) for role, principal in aws_roles]
        self.all_roles = aws_roles

    def get_role(self):
        """
        Prompt the user to choose a Role ARN if more than one is available
        """

        if not self.all_roles:
            self.get_arns()

        if not self.all_roles:
            raise Exception("No roles found")

        # If I have more than one role, ask the user which one they want,
        # otherwise just proceed

        self.role_arn, self.principal_arn = user_role_prompt(
            self.all_roles,
            saved_choice=self.config.get("role_arn"),
        )

    def assume_role(self):
        """Perform an AWS SAML role assumption"""

        if not self.role_arn:
            self.get_role()
        if self.config['region']:
            self.sts_client = boto3.client("sts", region_name=self.config["region"])
        res = self.sts_client.assume_role_with_saml(
            RoleArn=self.role_arn,
            PrincipalArn=self.principal_arn,
            SAMLAssertion=self.saml.saml_response,
            DurationSeconds=self.duration_seconds
        )

        self.credentials = res

    def save_credentials(self):
        """Save the AWS Federation credentials to disk"""

        if not self.credentials:
            self.assume_role()

        creds = self.credentials["Credentials"]

        cred_file = self._initialize_credentials()

        cred_config = configparser.ConfigParser()
        cred_config.read(cred_file)

        # Update with new credentials
        name = self.credentials["AssumedRoleUser"]["Arn"]
        m = re.search('(arn\:aws([\w-]*)\:sts\:\:)(.*)', name)

        if m is not None:
            name = m.group(3)
        name = name.replace(":assumed-role", "")
        if "profile" in self.config:
            name = self.config["profile"]

        # Initialize the profile block if it is undefined
        if name not in cred_config:
            cred_config[name] = {}

        # Set each value specifically instead of overwriting the entire
        # profile block in case they have other parameters defined
        cred_config[name]['aws_access_key_id'] = creds["AccessKeyId"]
        cred_config[name]['aws_secret_access_key'] = creds["SecretAccessKey"]
        cred_config[name]['aws_session_token'] = creds["SessionToken"]

        # Set region for this profile if passed in via configuration
        if self.config['region']:
            cred_config[name]['region'] = self.config['region']

        with open(cred_file, "w") as cred_config_file:
            cred_config.write(cred_config_file)

        print("Credentials cached in '{}'".format(cred_file))
        print("Expires at {}".format(creds["Expiration"]))
        print("Use aws cli with --profile " + name)

        # Reset state in the case of another transaction
        self.credentials = None

    def _initialize_credentials(self):
        cred_file = os.environ.get('AWS_SHARED_CREDENTIALS_FILE', None)

        if cred_file is None:
            cred_file = os.path.expanduser("~/.aws/credentials")
            cred_dir = os.path.expanduser("~/.aws/")
            if not os.path.exists(cred_dir):
                os.makedirs(cred_dir)

        return cred_file
コード例 #5
0
class OneloginAWS(object):
    """
    Handles the authentication between OneLogin SAML Assertion and the AWS
    identity federation
    """
    def __init__(self, config: Section):
        self.sts_client = boto3.client("sts")
        self.config = config
        self.saml = None
        self.all_roles = None
        self.role_arn = None
        self.credentials = None
        self.duration_seconds = int(config['duration_seconds'])
        self.user_credentials = UserCredentials(config)
        self.mfa = MFACredentials(config)

        base_uri_parts = self.config['base_uri'].split('.')
        self.ol_client = OneLoginClient(
            self.config['client_id'],
            self.config['client_secret'],
            base_uri_parts[1],
        )

    def get_saml_assertion(self):
        """
        Retrieve users credentials and get the SAML assertion from Onelogin,
        based on the users choice of AWS account to log into
        """

        self.user_credentials.load_credentials()

        saml_resp = self.ol_client.get_saml_assertion(
            self.user_credentials.username, self.user_credentials.password,
            self.config['aws_app_id'], self.config['subdomain'])

        if saml_resp is None:
            raise Exception("Onelogin Error: '{error}' '{desc}'".format(
                error=self.ol_client.error,
                desc=self.ol_client.error_description))
        if saml_resp.mfa:
            if not self.mfa.ready():
                self.mfa.select_device(saml_resp.mfa.devices)
                if not self.mfa.has_otp:
                    self.mfa.prompt_token()

            saml_resp = self.ol_client.get_saml_assertion_verifying(
                self.config['aws_app_id'], self.mfa.device.id,
                saml_resp.mfa.state_token, self.mfa.otp)

        self.saml = saml_resp

    def get_arns(self):
        """Extract the IAM Role ARNs from the SAML Assertion"""

        if not self.saml:
            self.get_saml_assertion()
        # Parse the returned assertion and extract the authorized roles
        aws_roles = []
        root = ElementTree.fromstring(base64.b64decode(
            self.saml.saml_response))

        namespace = "{urn:oasis:names:tc:SAML:2.0:assertion}"
        role_name = "https://aws.amazon.com/SAML/Attributes/Role"
        for attr in root.iter(namespace + "Attribute"):
            if attr.get("Name") == role_name:
                for val in attr.iter(namespace + "AttributeValue"):
                    aws_roles.append(val.text)

        # 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
        aws_roles = [role.split(",") for role in aws_roles]
        aws_roles = [(role, principal) for role, principal in aws_roles]
        self.all_roles = aws_roles

    def get_role(self):
        """
        Prompt the user to choose a Role ARN if more than one is available
        """

        if not self.all_roles:
            self.get_arns()

        if not self.all_roles:
            raise Exception("No roles found")

        # If I have more than one role, ask the user which one they want,
        # otherwise just proceed

        self.role_arn, self.principal_arn = user_role_prompt(
            self.all_roles,
            saved_choice=self.config.get("role_arn"),
        )

    def assume_role(self):
        """Perform an AWS SAML role assumption"""

        if not self.role_arn:
            self.get_role()
        res = self.sts_client.assume_role_with_saml(
            RoleArn=self.role_arn,
            PrincipalArn=self.principal_arn,
            SAMLAssertion=self.saml.saml_response,
            DurationSeconds=self.duration_seconds)

        self.credentials = res

    def save_credentials(self):
        """Save the AWS Federation credentials to disk"""

        if not self.credentials:
            self.assume_role()

        creds = self.credentials["Credentials"]

        cred_file = self._initialize_credentials()

        cred_config = configparser.ConfigParser()
        cred_config.read(cred_file)

        # Update with new credentials
        name = self.credentials["AssumedRoleUser"]["Arn"]
        if name.startswith("arn:aws:sts::"):
            name = name[13:]
        name = name.replace(":assumed-role", "")
        if "profile" in self.config:
            name = self.config["profile"]

        cred_config[name] = {
            "aws_access_key_id": creds["AccessKeyId"],
            "aws_secret_access_key": creds["SecretAccessKey"],
            "aws_session_token": creds["SessionToken"]
        }

        with open(cred_file, "w") as cred_config_file:
            cred_config.write(cred_config_file)

        print("Credentials cached in '{}'".format(cred_file))
        print("Expires at {}".format(creds["Expiration"]))
        print("Use aws cli with --profile " + name)

        # Reset state in the case of another transaction
        self.credentials = None

    def _initialize_credentials(self):
        cred_file = os.environ.get('AWS_SHARED_CREDENTIALS_FILE', None)

        if cred_file is None:
            cred_file = os.path.expanduser("~/.aws/credentials")
            cred_dir = os.path.expanduser("~/.aws/")
            if not os.path.exists(cred_dir):
                os.makedirs(cred_dir)

        return cred_file