Created
October 5, 2022 17:17
-
-
Save benkehoe/d6c02be0c58fc65864502e9efc80af05 to your computer and use it in GitHub Desktop.
Revisions
-
Ben Kehoe created this gist
Oct 5, 2022 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,314 @@ # Copyright 2022 Ben Kehoe # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. # For launching the web console with a specific IAM Identity Center # account and role, including packaging that up as a shareable, # non-credential-based token, see: # https://github.com/benkehoe/aws-sso-util/blob/master/docs/console.md import sys import json import webbrowser import urllib.parse import os import argparse from typing import Optional import requests import boto3 def get_logout_url(region: Optional[str] = None): redirect = urllib.parse.quote_plus( "https://aws.amazon.com/premiumsupport/knowledge-center/sign-out-account/?from_aws_sso_util_logout" ) if not region or region == "us-east-1": return ( f"https://signin.aws.amazon.com/oauth?Action=logout&redirect_uri={redirect}" ) if region == "us-gov-east-1": return "https://us-gov-east-1.signin.amazonaws-us-gov.com/oauth?Action=logout" if region == "us-gov-west-1": return "https://signin.amazonaws-us-gov.com/oauth?Action=logout" return f"https://{region}.signin.aws.amazon.com/oauth?Action=logout&redirect_uri={redirect}" def get_federation_endpoint(region: Optional[str] = None): if not region or region == "us-east-1": return "https://signin.aws.amazon.com/federation" if region == "us-gov-east-1": return "https://us-gov-east-1.signin.amazonaws-us-gov.com/federation" if region == "us-gov-west-1": return "https://signin.amazonaws-us-gov.com/federation" return f"https://{region}.signin.aws.amazon.com/federation" def get_destination_base_url(region: Optional[str] = None): if region and region.startswith("us-gov-"): # TODO: regional? return "https://console.amazonaws-us-gov.com" if region: return f"https://{region}.console.aws.amazon.com/" else: return "https://console.aws.amazon.com/" def get_destination( path: Optional[str] = None, region: Optional[str] = None, override_region_in_destination: bool = False, ): base = get_destination_base_url(region=region) if path: stripped_path_parts = urllib.parse.urlsplit(path)[2:] path = urllib.parse.urlunsplit(("", "") + stripped_path_parts) url = urllib.parse.urljoin(base, path) else: # url = urllib.parse.urljoin(base, "/console/home") url = base if not region: return url parts = list(urllib.parse.urlsplit(url)) query_params = urllib.parse.parse_qsl(parts[3]) if override_region_in_destination: query_params = [(k, v) for k, v in query_params if k != "region"] query_params.append(("region", region)) elif not any(k == "region" for k, _ in query_params): query_params.append(("region", region)) query_str = urllib.parse.urlencode(query_params) parts[3] = query_str url = urllib.parse.urlunsplit(parts) return url def DurationType(value): value = int(value) if 15 < value < 720: raise ValueError("Duration must be between 15 and 720 minutes (inclusive)") return value def main(): parser = argparse.ArgumentParser(description="Launch the AWS console") parser.add_argument( "--profile", metavar="PROFILE_NAME", help="A config profile to use" ) parser.add_argument("--region", metavar="REGION", help="The AWS region") parser.add_argument( "--destination", dest="destination_path", metavar="PATH", help="Console URL path to go to", ) override_region_group = parser.add_mutually_exclusive_group() override_region_group.add_argument( "--override-region-in-destination", action="store_true" ) override_region_group.add_argument( "--keep-region-in-destination", dest="override_region_in_destination", action="store_false", ) open_group = parser.add_mutually_exclusive_group() open_group.add_argument( "--open", dest="open_url", action="store_true", default=None, help="Open the login URL in a browser (the default)", ) open_group.add_argument( "--no-open", dest="open_url", action="store_false", help="Do not open the login URL", ) print_group = parser.add_mutually_exclusive_group() print_group.add_argument( "--print", dest="print_url", action="store_true", default=None, help="Print the login URL", ) print_group.add_argument( "--no-print", dest="print_url", action="store_false", help="Do not print the login URL", ) parser.add_argument( "--duration", metavar="MINUTES", type=DurationType, help="The session duration in minutes", ) logout_first_group = parser.add_mutually_exclusive_group() logout_first_group.add_argument( "--logout-first", "-l", action="store_true", default=None, help="Open a logout page first", ) logout_first_group.add_argument( "--no-logout-first", dest="logout_first", action="store_false", help="Do not open a logout page first", ) args = parser.parse_args() if args.open_url is None: args.open_url = True logout_first_from_env = False if args.logout_first is None: args.logout_first = os.environ.get("AWS_CONSOLE_LOGOUT_FIRST", "").lower() in [ "true", "1", ] logout_first_from_env = True if args.logout_first and not args.open_url: if logout_first_from_env: logout_first_value = os.environ["AWS_CONSOLE_LOGOUT_FIRST"] raise parser.exit( f"AWS_CONSOLE_LOGOUT_FIRST={logout_first_value} requires --open" ) else: raise parser.exit("--logout-first requires --open") session = boto3.Session(profile_name=args.profile) if not args.region: args.region = session.region_name or os.environ.get( "AWS_CONSOLE_DEFAULT_REGION" ) if not args.destination_path: args.destination_path = session._session.get_scoped_config().get( "web_console_destination" ) or os.environ.get("AWS_CONSOLE_DEFAULT_DESTINATION") credentials = session.get_credentials() if not credentials: parser.exit("Could not find credentials") federation_endpoint = get_federation_endpoint(region=args.region) issuer = os.environ.get("AWS_CONSOLE_DEFAULT_ISSUER") destination = get_destination( path=args.destination_path, region=args.region, override_region_in_destination=args.override_region_in_destination, ) launch_console( session=session, federation_endpoint=federation_endpoint, destination=destination, region=args.region, open_url=args.open_url, print_url=args.print_url, duration=args.duration, logout_first=args.logout_first, issuer=issuer, ) def launch_console( *, session: boto3.Session, federation_endpoint: str, destination: str, region: Optional[str] = None, open_url: Optional[bool] = None, print_url: Optional[bool] = None, duration: Optional[int] = None, logout_first: Optional[bool] = None, issuer: Optional[str] = None, ): if not issuer: issuer = "aws_console_launcher.py" read_only_credentials = session.get_credentials().get_frozen_credentials() session_data = { "sessionId": read_only_credentials.access_key, "sessionKey": read_only_credentials.secret_key, "sessionToken": read_only_credentials.token, } get_signin_token_payload = { "Action": "getSigninToken", "Session": json.dumps(session_data), } if duration is not None: get_signin_token_payload["SessionDuration"] = duration * 60 response = requests.post(federation_endpoint, data=get_signin_token_payload) if response.status_code != 200: print("Could not get signin token", file=sys.stderr) print(response.status_code + "\n" + response.text, file=sys.stderr) sys.exit(2) token = response.json()["SigninToken"] get_login_url_params = { "Action": "login", "Issuer": issuer, "Destination": destination, "SigninToken": token, } request = requests.Request( method="GET", url=federation_endpoint, params=get_login_url_params ) prepared_request = request.prepare() login_url = prepared_request.url if print_url: print(login_url) if open_url: if logout_first: logout_url = get_logout_url(region=region) webbrowser.open( logout_url, autoraise=False ) # &redirect_uri=https://aws.amazon.com webbrowser.open(login_url) if __name__ == "__main__": main()