コード例 #1
0
ファイル: lookup.py プロジェクト: OlafConijn/aws-sso-util
def lookup_user_by_id(session: boto3.Session,
                      ids: Ids,
                      user_id,
                      *,
                      cache=None):
    if cache is None:
        cache = {}

    cache_key_id = f"{_CACHE_KEY_PREFIX_USER_ID}{user_id}"
    if cache_key_id in cache:
        LOGGER.debug(f"Found user {user_id} in cache")
        user = cache[cache_key_id]
        if isinstance(user, LookupError):
            raise user
        return user

    LOGGER.debug(f"Looking up user {user_id}")

    identity_store = session.client('identitystore')
    try:
        user = identity_store.describe_user(
            IdentityStoreId=ids.identity_store_id, UserId=user_id)
        user.pop("ResponseMetadata", None)
    except aws_error_utils.catch_aws_error("ResourceNotFoundException") as e:
        err = LookupError(e)
        cache[cache_key_id] = err
        raise err

    user_name = user["UserName"]

    cache_key_name = f"{_CACHE_KEY_PREFIX_USER_NAME}{user_name}"
    cache[cache_key_id] = user
    cache[cache_key_name] = user

    return user
コード例 #2
0
ファイル: lookup.py プロジェクト: OlafConijn/aws-sso-util
def lookup_account_by_id(session, account_id, *, cache=None):
    if cache is None:
        cache = {}

    account_id = _format.format_account_id(account_id)

    cache_key_id = f"{_CACHE_KEY_PREFIX_ACCOUNT_ID}{account_id}"

    if cache_key_id in cache:
        LOGGER.debug(f"Found account {account_id} in cache")
        account = cache[cache_key_id]
        if isinstance(account, LookupError):
            raise account
        return account

    LOGGER.debug(f"Looking up account {account_id}")

    organizations = session.client("organizations")
    try:
        account = organizations.describe_account(
            AccountId=account_id)["Account"]
    except aws_error_utils.catch_aws_error("AccountNotFoundException") as e:
        err = LookupError(e)
        cache[cache_key_id] = err
        raise err

    account_name = account["Name"]

    cache_key_name = f"{_CACHE_KEY_PREFIX_ACCOUNT_NAME}{account_name}"

    cache[cache_key_id] = account
    cache[cache_key_name] = account

    return account
コード例 #3
0
ファイル: lookup.py プロジェクト: OlafConijn/aws-sso-util
def lookup_group_by_id(session: boto3.Session,
                       ids: Ids,
                       group_id,
                       *,
                       cache=None):
    if cache is None:
        cache = {}

    cache_key_id = f"{_CACHE_KEY_PREFIX_GROUP_ID}{group_id}"
    if cache_key_id in cache:
        LOGGER.debug(f"Found group {group_id} in cache")
        group = cache[cache_key_id]
        if isinstance(group, LookupError):
            raise group
        return group

    LOGGER.debug(f"Looking up group {group_id}")

    identity_store = session.client('identitystore')
    try:
        group = identity_store.describe_group(
            IdentityStoreId=ids.identity_store_id, GroupId=group_id)
        group.pop("ResponseMetadata", None)
    except aws_error_utils.catch_aws_error("ResourceNotFoundException") as e:
        err = LookupError(e)
        cache[cache_key_id] = err
        raise err

    group_name = group["DisplayName"]

    cache_key_name = f"{_CACHE_KEY_PREFIX_GROUP_NAME}{group_name}"
    cache[cache_key_id] = group
    cache[cache_key_name] = group

    return group
コード例 #4
0
def test_catch():
    error = make_error('AssumeRole', 'RegionDisabled')
    try:
        raise error
    except catch_aws_error('RegionDisabled') as e:
        assert e is error

    with pytest.raises(ClientError, match=re.escape(str(error))):
        try:
            raise error
        except catch_aws_error('OtherCode') as e:
            assert False

    def matcher(client_error):
        return client_error is error

    try:
        raise error
    except catch_aws_error(matcher) as e:
        assert e is error

    def nonmatcher(client_error):
        return False

    with pytest.raises(ClientError, match=re.escape(str(error))):
        try:
            raise error
        except catch_aws_error(nonmatcher) as e:
            assert False

    class OtherError(Exception):
        pass

    try:
        raise OtherError('test')
    except catch_aws_error(ALL_CODES) as e:
        assert False
    except OtherError:
        assert True
コード例 #5
0
ファイル: lookup.py プロジェクト: OlafConijn/aws-sso-util
def lookup_permission_set_by_id(session: boto3.Session,
                                ids: Ids,
                                permission_set_id,
                                *,
                                cache=None):
    if cache is None:
        cache = {}

    permission_set_arn = _format.format_permission_set_arn(
        ids, permission_set_id, raise_on_unknown=True)

    cache_key_arn = f"{_CACHE_KEY_PREFIX_PERMISSION_SET_ARN}{permission_set_arn}"

    if cache_key_arn in cache:
        LOGGER.debug(f"Found permission set {permission_set_id} in cache")
        ps = cache[cache_key_arn]
        if isinstance(ps, LookupError):
            raise ps
        return ps

    LOGGER.debug(f"Looking up permission set {permission_set_id}")

    sso = session.client("sso-admin")

    try:
        ps = sso.describe_permission_set(
            InstanceArn=ids.instance_arn,
            PermissionSetArn=permission_set_arn)["PermissionSet"]
    except aws_error_utils.catch_aws_error("ResourceNotFoundException") as e:
        err = LookupError(e)
        cache[cache_key_arn] = err
        raise err

    cache_key_name = f"{_CACHE_KEY_PREFIX_PERMISSION_SET_NAME}{ps['Name']}"

    cache[cache_key_arn] = ps
    cache[cache_key_name] = ps

    return ps
コード例 #6
0
    def principal_iterator(
            target_type, target_id, target_name,
            permission_set_arn, permission_set_id, permission_set_name):
        if target_type != "AWS_ACCOUNT":
            raise TypeError(f"Unsupported target type {target_type}")

        sso_admin_client = context.session.client("sso-admin")
        identity_store_client = context.session.client("identitystore")

        assignments_paginator = sso_admin_client.get_paginator("list_account_assignments")
        for response in assignments_paginator.paginate(
                InstanceArn=context.ids.instance_arn,
                AccountId=target_id,
                PermissionSetArn=permission_set_arn):
            LOGGER.debug(f"ListAccountAssignments for {target_id} {permission_set_arn.split('/')[-1]} page: {response}")

            if not response["AccountAssignments"] and not "NextToken" in response:
                LOGGER.debug(f"No assignments for {target_id} {permission_set_arn.split('/')[-1]}")

            for assignment in response["AccountAssignments"]:
                principal_type = assignment["PrincipalType"]
                principal_id = assignment["PrincipalId"]
                LOGGER.debug(f"Visiting principal {principal_type}:{principal_id}")

                if context.principal:
                    for principal in context.principal:
                        type_matches = (principal[0] == "UNKNOWN" or principal[0] != principal_type)
                        if type_matches and principal[1] == principal_id:
                            LOGGER.debug(f"Found principal {principal_type}:{principal_id}")
                            break
                    else:
                        LOGGER.debug(f"Principal {principal_type}:{principal_id} does not match principals")
                        continue

                principal_key = (principal_type, principal_id)
                if not context.get_principal_names:
                    principal_name = "UNKNOWN"
                else:
                    if principal_key not in context.cache:
                        if principal_type == "GROUP":
                            try:
                                response = identity_store_client.describe_group(
                                    IdentityStoreId=context.ids.identity_store_id,
                                    GroupId=principal_id
                                )
                                LOGGER.debug(f"DescribeGroup response: {response}")
                                context.cache[principal_key] = response["DisplayName"]
                            except aws_error_utils.catch_aws_error("ResourceNotFoundException"):
                                context.cache[principal_key] = "UNKNOWN"
                        elif principal_type == "USER":
                            try:
                                response = identity_store_client.describe_user(
                                    IdentityStoreId=context.ids.identity_store_id,
                                    UserId=principal_id
                                )
                                LOGGER.debug(f"DescribeUser response: {response}")
                                context.cache[principal_key] = response["UserName"]
                            except aws_error_utils.catch_aws_error("ResourceNotFoundException"):
                                context.cache[principal_key] = "UNKNOWN"
                        else:
                            raise ValueError(f"Unknown principal type {principal_type}")
                    principal_name = context.cache[principal_key]

                if not _filter(context.filter_cache, principal_key, context.principal_filter, (principal_type, principal_id, principal_name)):
                    if context.principal:
                        LOGGER.debug(f"Principal is filtered: {principal_type}:{principal_id}")
                    else:
                        LOGGER.debug(f"Principal is filtered: {principal_type}:{principal_id}")
                    continue

                LOGGER.debug(f"Visiting principal: {principal_type}:{principal_id}")
                yield principal_type, principal_id, principal_name
コード例 #7
0
def login(
        sso_start_url,
        sso_region,
        profile,
        login_all,
        force,
        headless,
        verbose):
    """Log in to an AWS SSO instance.

    Note this only needs to be done once for a given SSO instance (i.e., start URL),
    as all profiles sharing the same start URL will share the same login.

    If only one SSO instance/start URL exists in your AWS config file,
    or you've set the environment variables AWS_DEFAULT_SSO_START_URL and AWS_DEFAULT_SSO_REGION,
    you don't need to provide a start URL or region.

    Otherwise, you can provide a full start URL, or a regex for the start URL (usually a substring will work),
    and if this uniquely identifies a start URL in your config, that will suffice.

    You can also provide a profile name with --profile to use the SSO instance from a specific profile.
    """

    if login_all is None:
        login_all = os.environ.get(LOGIN_ALL_VAR, "").lower() in ["true", "1"]

    configure_logging(LOGGER, verbose)

    instances, specifier, all_instances = find_instances(
        profile_name=profile,
        profile_source="--profile",
        start_url=sso_start_url,
        start_url_source="CLI input",
        region=sso_region,
        region_source="CLI input",
        start_url_vars=LOGIN_DEFAULT_START_URL_VARS,
        region_vars=LOGIN_DEFAULT_SSO_REGION_VARS,
    )

    if not instances:
        if all_instances:
            LOGGER.fatal((
                f"No AWS SSO config matched {specifier.to_str(region=True)} " +
                f"from {SSOInstance.to_strs(all_instances)}"))
        else:
            LOGGER.fatal("No AWS SSO config found")
        sys.exit(1)

    if len(instances) > 1 and not login_all:
        LOGGER.fatal(f"Found {len(instances)} SSO configs, please specify one or use --all: {SSOInstance.to_strs(instances)}")
        sys.exit(1)

    LOGGER.debug(f"Instances: {SSOInstance.to_strs(instances)}")

    session = botocore.session.Session()

    regions = [i.region for i in instances]
    token_fetchers = {}
    for region in regions:
        token_fetchers[region] = get_token_fetcher(session, region, interactive=True, disable_browser=headless)

    if len(instances) > 1:
        print(f"Logging in {len(instances)} AWS SSO instances")
    for instance in instances:
        print(f"Logging in {instance.start_url}")
        token_fetcher = token_fetchers[instance.region]
        try:
            token = token_fetcher.fetch_token(instance.start_url, force_refresh=force)
            LOGGER.debug(f"Token: {token}")
            expiration = token['expiresAt']
            if isinstance(expiration, str):
                expiration = parse(expiration)
            expiration_utc = expiration.astimezone(tzutc())
            expiration_str = expiration_utc.strftime(UTC_TIME_FORMAT)
            try:
                local_expiration = expiration_utc.astimezone(tzlocal())
                expiration_str = local_expiration.strftime(LOCAL_TIME_FORMAT)
                # TODO: locale-friendly string
            except:
                pass
            print(f"Login succeeded, valid until {expiration_str}")
        except PendingAuthorizationExpiredError:
            print(f"Login window expired", file=sys.stderr)
            sys.exit(2)
        except aws_error_utils.catch_aws_error("InvalidGrantException") as e:
            LOGGER.debug("Login failed; the login window may have expired", exc_info=True)
            err_info = aws_error_utils.get_aws_error_info(e)
            msg_str = f" ({err_info.message})" if err_info.message else ""
            print(f"Login failed; the login window may have expired: {err_info.code}{msg_str}", file=sys.stderr)
            sys.exit(3)
        except botocore.exceptions.ClientError as e:
            LOGGER.debug("Login failed", exc_info=True)
            err_info = aws_error_utils.get_aws_error_info(e)
            msg_str = f" ({err_info.message})" if err_info.message else ""
            print(f"Login failed: {err_info.code}{msg_str}", file=sys.stderr)
            sys.exit(4)
        except Exception as e:
            LOGGER.debug("Login failed", exc_info=True)
            print(f"Login failed: {e}", file=sys.stderr)
            sys.exit(4)