class APIClient:
    def __init__(self, connection, configuration):
        auth = configuration.get('auth')
        self.client = OneLoginClient(auth.get('clientId'),
                                     auth.get('clientSecret'),
                                     connection['region'])

    def generate_token(self):
        """To generate the Token"""
        self.client.get_access_token()
        return self.response_handler()

    def run_search(self, query_expr, range_end=None):
        """get the response from onelogin endpoints
        :param quary_expr: dict, filter parameters
        :param range_end: int,length value
        :return: response, json object"""
        token = self.client.get_access_token()
        events = []
        if token and self.client.error is None:
            events = self.client.get_events(query_expr, max_results=range_end)
        return self.response_handler(events)

    def response_handler(self, data=None):
        if data is None:
            data = []
        response = dict()
        if self.client.error: response["code"] = int(self.client.error)
        if self.client.error is None:
            response.update({"code": 200, "data": data})
        elif self.client.error == 500 and "local variable 'data' referenced before assignment" in self.client.error_description:
            response.update({"code": 200, "data": []})
        else:
            response["message"] = self.client.error_description
        return response
def get_client(options):
    client_id = client_secret = ip = None

    if options.client_id is not None and options.client_secret is not None:
        client_id = options.client_id
        client_secret = options.client_secret
        region = options.region
    else:
        if os.path.isfile('onelogin.sdk.json'):
            json_data = open('onelogin.sdk.json').read()
            data = json.loads(json_data)
            if 'client_id' in data.keys() and 'client_secret' in data.keys():
                client_id = data['client_id']
                client_secret = data['client_secret']
                region = data.get('region', 'us')
                ip = data.get('ip', None)

    if client_id is None or client_secret is None:
        raise Exception("OneLogin Client ID and Secret are required")

    client = OneLoginClient(client_id, client_secret, region)
    if ip:
        client.ip = ip
    client.prepare_token()
    if client.error == 401 or client.access_token is None:
        raise Exception(
            "Invalid client_id and client_secret. Access_token could not be retrieved"
        )
    return client
def one_tap_login(request):
    received_json_data = json.loads(request.body)
    credential = received_json_data['credential']
    decoded = jwt.decode(credential, verify=False)
    user_data = {
        "name": decoded['name'],
        "email": decoded['email'],
        "given_name": decoded['given_name'],
        "family_name": decoded['family_name']
    }
    
    json_user_data = json.dumps(user_data)
    request.session['user_data'] = json_user_data
    # run this in command shell:
    # python manage.py migrate

    client = OneLoginClient(
        mysecrets.ONELOGIN_CLIENT_ID, 
        mysecrets.ONELOGIN_CLIENT_SECRET,
        'us'
    )

    # 1. Make sure the user you want to create does not exist yet
    users = client.get_users({
        "email": decoded["email"]
    })

    # 2. Create the new user (explain the most interesting user parameters)
    if len(users) == 0:
        new_user_params = {
            "email": decoded["email"],
            "firstname": decoded["name"],
            "lastname": decoded["given_name"],
            "username": decoded["family_name"]
        }
        created_user = client.create_user(new_user_params)

        if created_user is not None:

            # 3. Assign the Default role to the user
            roles = client.get_roles({
                "name": "Default"
            })

            if  len(roles) == 1:
                role_ids = [
                    roles[0].id
                ]
                client.assign_role_to_user(created_user.id, role_ids)

            # 4. Set the user state
            USER_STATE_APPROVED = 1
            client.set_state_to_user(created_user.id, USER_STATE_APPROVED)

    return HttpResponse(json_user_data, content_type="application/json")
Beispiel #4
0
def onelogin_client() -> OneLoginClient:
    client = OneLoginClient(
        ArgumentParser.args.onelogin_client_id,
        ArgumentParser.args.onelogin_client_secret,
        ArgumentParser.args.onelogin_region
    )
    return client
Beispiel #5
0
 def testClientWithNoData(self):
     """
     Tests the constructor method of the OneLoginClient class
     Build a OneLoginClient object with no data
     """
     with self.assertRaises(Exception):
         client = OneLoginClient()
Beispiel #6
0
def onelogin_client() -> OneLoginClient:
    client = OneLoginClient(
        Config.onelogin.client_id,
        Config.onelogin.client_secret,
        Config.onelogin.region,
    )
    return client
Beispiel #7
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],
        )
def get_client(options):
    client_id = client_secret = ip = json_data = None
    region = 'us'
    client_file_name = 'onelogin.sdk.json'

    if options.client_id is not None and options.client_secret is not None:
        client_id = options.client_id
        client_secret = options.client_secret
        region = options.region
        ip = options.ip
    else:
        if options.config_file_path is not None and os.path.isfile(
                os.path.join(options.config_file_path, client_file_name)):
            json_data = open(
                os.path.join(options.config_file_path,
                             client_file_name)).read()
        elif os.path.isfile(client_file_name):
            json_data = open(client_file_name).read()
        elif os.path.isfile(
                os.path.expanduser('~') + '/.onelogin/' + client_file_name):
            json_data = open(
                os.path.expanduser('~') + '/.onelogin/' +
                client_file_name).read()
        if json_data is not None:
            data = json.loads(json_data)
            if 'client_id' in data.keys() and 'client_secret' in data.keys():
                client_id = data['client_id']
                client_secret = data['client_secret']
                if 'region' in data.keys() and data['region']:
                    region = data['region']
                if 'ip' in data.keys() and data['ip']:
                    ip = data['ip']

    if not client_id or not client_secret:
        raise Exception("OneLogin Client ID and Secret are required")
    client = OneLoginClient(client_id, client_secret, region)
    client.api_configuration["assertion"] = options.saml_api_version
    if ip:
        client.ip = ip
    client.prepare_token()
    if client.error == 401 or client.access_token is None:
        raise Exception(
            "Invalid client_id and client_secret. Access_token could not be retrieved"
        )
    return client
Beispiel #9
0
 def testClientDefaultErrorValues(self):
     """
     Tests the constructor method of the OneLoginClient class
     Build a OneLoginClient object and check if the error attributes exist and are None
     """
     client = OneLoginClient(client_id='test_client_id',
                             client_secret='test_client_secret',
                             region='eu')
     self.assertIsNone(client.error)
     self.assertIsNone(client.error_description)
Beispiel #10
0
 def testClientWithData(self):
     """
     Tests the constructor method of the OneLoginClient class
     Build a OneLoginClient object with a client_id and client_secret
     """
     client = OneLoginClient(client_id='test_client_id',
                             client_secret='test_client_secret')
     self.assertIsNot(client, None)
     self.assertEqual('test_client_id', client.client_id)
     self.assertEqual('test_client_secret', client.client_secret)
     self.assertEqual('us', client.url_builder.region)
Beispiel #11
0
    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],
        )
Beispiel #12
0
class OneLogin:
    def __init__(self):
        CLIENT_ID = os.environ["ONELOGIN_CLIENT_ID"]
        CLIENT_SECRET = os.environ["ONELOGIN_CLIENT_SECRET"]
        REGION = os.environ.get("ONELOGIN_REGION", "US").upper()
        self.client = OneLoginClient(CLIENT_ID, CLIENT_SECRET, REGION)

    def get_group_members(self, group_name=None):
        """
        This is technically not named well, since we're getting users assigned to a role, but
        because of the existing framework, the matching the function name keeps it reusable
        :param group_name:
        :return:
        """
        member_list = []
        role = self.client.get_roles(query_parameters={"name": group_name})
        users = self.client.get_users(query_parameters={"role_id": role[0].id})
        for user in users:
            member_list.append({"username": user.username, "email": user.email})

        return member_list
Beispiel #13
0
def get_client(options):
    client_id = client_secret = None

    if options.client_id is not None and options.client_secret is not None:
        client_id = options.client_id
        client_secret = options.client_secret
        region = options.region
    else:
        if os.path.isfile('onelogin.sdk.json'):
            json_data = open('onelogin.sdk.json').read()
            data = json.loads(json_data)
            if 'client_id' in data.keys() and 'client_secret' in data.keys():
                client_id = data['client_id']
                client_secret = data['client_secret']
                region = data.get('region', 'us')

    if client_id is None or client_secret is None:
        raise Exception("OneLogin Client ID and Secret are required")

    return OneLoginClient(client_id, client_secret, region)
Beispiel #14
0
 def __init__(self):
     CLIENT_ID = os.environ["ONELOGIN_CLIENT_ID"]
     CLIENT_SECRET = os.environ["ONELOGIN_CLIENT_SECRET"]
     REGION = os.environ.get("ONELOGIN_REGION", "US").upper()
     self.client = OneLoginClient(CLIENT_ID, CLIENT_SECRET, REGION)
Beispiel #15
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
 def __init__(self, connection, configuration):
     auth = configuration.get('auth')
     self.client = OneLoginClient(auth.get('clientId'),
                                  auth.get('clientSecret'),
                                  connection['region'])
# Uncomment to enable low level debugging
# logging.basicConfig(level=logging.DEBUG)
##########################################################################

# 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,
def pam_sm_authenticate(pamh, _flags, _argv):
    '''
    Authenticates a user via onelogin email/username and OTP
    '''
    # Load config file and build access token
    try:
        config_dpath = os.path.dirname(os.path.realpath(__file__))
        config_fpath = os.path.join(config_dpath, 'onepam.json')
        config_fd = open(config_fpath, 'r')
        config = config_fd.read()
        config_fd.close()
        config = json.loads(config)
    except Exception as error:
        logit('Error loading configuration: %s' % error)
        return pamh.PAM_AUTH_ERR

    # Create a client to OneLogin with the config details
    client = OneLoginClient(config['client_id'], config['client_secret'],
                            config['region'])
    if not client.get_access_token():
        logit('Error authenticating with onelogin')
        return pamh.PAM_AUTH_ERR

    # Prompt user for needed information
    try:
        # Unix user (aka, the onelogin role - usually passed via ssh, but may need to prompt)
        rolename = pamh.get_user(None)
        if rolename is None:
            return pamh.PAM_USER_UNKNOWN

        # OneLogin email/user
        email_or_user = pamh.conversation(
            pamh.Message(pamh.PAM_PROMPT_ECHO_ON,
                         'OneLogin email or user: '******'OneLogin password: '******'*') >= 0:
        logit('Invalid user "%s"' % email_or_user)
        return pamh.PAM_AUTH_ERR

    # Build uniform request object from config file
    uniform_timer = UniformTimer(config['request_duration_secs'])

    # Make all email/user/checks have a uniform duration (start)
    uniform_timer.start()

    # Query emails
    emails = client.get_users({'email': email_or_user})
    if emails is None:
        logit('Error querying email "%s"' % email_or_user)
        return pamh.PAM_AUTH_ERR

    # Query users
    users = client.get_users({'username': email_or_user})
    if users is None:
        logit('Error querying user "%s"' % email_or_user)
        return pamh.PAM_AUTH_ERR

    # Search emails first then users
    user = None
    for entry in emails:
        if entry.email == email_or_user:
            user = entry
            break
    if not user:
        for entry in users:
            if entry.username == email_or_user:
                user = entry
                break

    # Make all email/user/checks have a uniform duration (finish)
    uniform_timer.finish()

    # Check password (uncomment to add password auth)
    # uniform_timer.start()
    # token = client.create_session_login_token({'username_or_email': email_or_user,
    #                                            'password': password,
    #                                            'subdomain': config['subdomain']})
    # if token is None:
    #     logit('Invalid username or password "%s"' % email_or_user)
    #     user = None
    # uniform_timer.finish()

    # Valid user - query otp factors
    uniform_timer.start()
    device = None
    if user:
        factors = client.get_enrolled_factors(user.id)

        # Error querying devices - log and set user to None
        if factors is None:
            logit('Error querying enrolled factors for user "%s"' %
                  email_or_user)
            user = None

        # Find a factor for user (default is preferred, will use first listed otherwise)
        else:
            for factor in factors:
                # Only care about usable factors
                if not factor.active:
                    continue

                # Device is the default device - set and break
                if factor.default:
                    device = factor
                    break

                # Device isn't default, but some device is better than no device
                if device is None:
                    device = factor
    else:
        logit('Invalid email/user "%s"' % email_or_user)
    uniform_timer.finish()

    # User has a valid otp factor - activate it
    uniform_timer.start()
    state_token = None
    if user is not None and device is not None:
        # Grab state token
        state_token = device.state_token

        # Only trigger if device needs it
        if device.needs_trigger:
            activation = client.activate_factor(user.id, device.id)
            if not activation:
                logit('Error activating factor id %d for user "%s"' %
                      (device.id, email_or_user))
            else:
                state_token = activation.state_token

    # No active/default factor - log and set user to None
    else:
        logit('No valid otp factor found for user "%s"' % email_or_user)
    uniform_timer.finish()

    # Prompt for otp
    try:
        otp_token = pamh.conversation(
            pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, 'OTP: ')).resp
        if not otp_token:
            otp_token = None
    except pamh.exception as error:
        return error.pam_result

    # Verify otp
    uniform_timer.start()
    result = False
    if user is not None and device is not None:
        result = client.verify_factor(user.id,
                                      device.id,
                                      otp_token=otp_token,
                                      state_token=state_token)
    uniform_timer.finish()

    # Error verifying otp - log
    if result is None:
        logit('Error verifying factor id %d for user "%s"' %
              (device.id, email_or_user))
        return pamh.PAM_AUTH_ERR

    # Invalid otp - log
    if not result:
        logit('Invalid otp auth for user "%s"' % email_or_user)
        return pamh.PAM_AUTH_ERR

    # Check if user is authorized to login to provided role
    if config['user_roles'].get(rolename, None) not in user.get_role_ids():
        logit('User "%s" is not authorized to login as "%s"' %
              (email_or_user, rolename))
        return pamh.PAM_AUTH_ERR

    # Auth'd login
    return pamh.PAM_SUCCESS
Beispiel #19
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
Beispiel #20
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