Example #1
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
# Get the credentials from the user
if not email:
    print "Email: ",
    email = raw_input()
else:
    print "Using: %s" % email
password = getpass.getpass()
print "OTP Code (MFA): ",
otp_code = raw_input()
print ''

client = OneLoginClient(onelogin_client_id, onelogin_client_secret,
                        onelogin_region)

onelogin_response = client.get_saml_assertion(email, password, app_id,
                                              onelogin_subdomain)

saml = None

if onelogin_response is None:
    print('Failed logging in (password was incorrect)')
    exit(1)
elif onelogin_response and onelogin_response.type == "success":
    state_token = onelogin_response.mfa.state_token
    device_id = onelogin_response.mfa.devices[0].id
    mfa_response = client.get_saml_assertion_verifying(app_id,
                                                       device_id,
                                                       state_token,
                                                       otp_code,
                                                       do_not_notify=True)
Example #3
0
class OneloginAWS(object):
    def __init__(self, config, args):
        self.sts_client = boto3.client("sts")
        self.config = config
        self.args = args
        self.token = None
        self.account_id = None
        self.saml = None
        self.all_roles = None
        self.role_arn = None
        self.principal_arn = None
        self.credentials = None

        self.username = self.args.username
        self.password = None

        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):

        if not self.username:
            self.username = input("Onelogin Username: "******"Onelogin Password: "******"{}. {}".format(i + 1, device.type))
                device_num = input("Which OTP Device? ")
                device = devices[int(device_num) - 1]
            else:
                device = devices[0]

            otp_token = input("OTP Token: ")

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

        self.saml = saml_resp

    def get_arns(self):
        if not self.saml:
            self.get_saml_assertion()
        # Parse the returned assertion and extract the authorized roles
        aws_roles = []
        root = ET.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):
        if not self.all_roles:
            self.get_arns()

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

        selected_role = None

        # If I have more than one role, ask the user which one they want,
        # otherwise just proceed
        if len(self.all_roles) > 1:
            ind = 0
            for role, principal in self.all_roles:
                print("[{}] {}".format(ind, role))
                ind += 1
            while selected_role is None:
                choice = int(input("Role Number: "))
                if choice in range(len(self.all_roles)):
                    selected_role = choice
                else:
                    print("Invalid role index, please try again")
        else:
            selected_role = 0

        self.role_arn, self.principal_arn = self.all_roles[selected_role]

    def assume_role(self):
        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)

        self.credentials = res

    def save_credentials(self):
        if not self.credentials:
            self.assume_role()

        creds = self.credentials["Credentials"]

        cred_file = os.path.expanduser("~/.aws/credentials")
        cred_dir = os.path.expanduser("~/.aws/")
        if not os.path.exists(cred_dir):
            os.makedirs(cred_dir)
        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 self.config.get("profile"):
            name = self.config["profile"]
        elif self.args.profile != "":
            name = self.args.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.token = None
        self.credentials = None

    @staticmethod
    def generate_config():
        print("Configure Onelogin and AWS\n\n")
        config = configparser.ConfigParser()
        config.add_section("default")
        default = config["default"]

        default["base_uri"] = user_choice(
            "Pick a Onelogin API server:",
            ["https://api.us.onelogin.com/", "https://api.eu.onelogin.com/"])

        print("\nOnelogin API credentials. These can be found at:\n"
              "https://admin.us.onelogin.com/api_credentials")
        default["client_id"] = input("Onelogin API Client ID: ")
        default["client_secret"] = input("Onelogin API Client Secret: ")
        print("\nOnelogin AWS App ID. This can be found at:\n"
              "https://admin.us.onelogin.com/apps")
        default["aws_app_id"] = input("Onelogin App ID for AWS: ")
        print("\nOnelogin subdomain is 'company' for login domain of "
              "'comany.onelogin.com'")
        default["subdomain"] = input("Onelogin subdomain: ")

        config_fn = os.path.expanduser("~/{}".format(CONFIG_FILENAME))
        with open(config_fn, "w") as config_file:
            config.write(config_file)

        print("Configuration written to '{}'".format(config_fn))

    @staticmethod
    def load_config():
        try:
            config_fn = os.path.expanduser("~/{}".format(CONFIG_FILENAME))
            config = configparser.ConfigParser()
            config.read_file(open(config_fn))
            return config
        except FileNotFoundError:
            return None
Example #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(
            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