Setting the file. One moment.
Launch Config · Launch With AWS · aws/agent-toolkit-for-aws · Skills Docs
ContentsBack to the top of the page 69
Creating Amazon Aurora Db Cluster With Instances
92
Routing Traffic With Route53 And CloudFront
Resilience Program Design
Creating API Gateway Stage
Number 45.6
Position 6 of 7
Type Python
Size 7 KB
Lines 201 scripts/ launch_config.py
Python · 201 lines · 7 KB
from
urllib.parse
import
urlparse
14
15 # ── Constants ────────────────────────────────────────────────────────────
16
17 DEFAULT_BASE_URL = "https://launch-with-aws.us-east-1.api.aws"
18
19 ENV_BASE_URL = "LAUNCH_WITH_AWS_BASE_URL"
20 ENV_IDC_ISSUER_URL = "LAUNCH_WITH_AWS_IDC_ISSUER_URL"
21 ENV_SCOPES = "LAUNCH_WITH_AWS_SCOPES"
22
23 SSO_OIDC_REGION = "us-east-1"
24 IDC_ISSUER_URL = "https://view.awsapps.com/start"
25
26 CLIENT_NAME = "Launch with AWS Agent Skill"
27 SCOPES = [ "launch:access" ]
28 TOKEN_EXPIRY_BUFFER_SECS = 300
29 CALLBACK_TIMEOUT_SECS = 600.0
30 DEFAULT_TOKEN_LIFETIME_SECS = 28800
31
32 # Maximum local session lifetime; re-authentication is required after this.
33 MAX_SESSION_LIFETIME_SECS = 90 * 24 * 3600 # 90 days
34
35 SESSION_DIR = "~/.launch-with-aws"
36 SESSION_FILE_NAME = "session.json"
37
38 AUTH_WAIT_POLL_INTERVAL_SECS = 1.0
39
40 REQUEST_TIMEOUT_SECS = 120.0
41 UPLOAD_TIMEOUT_SECS = 300.0
42 GITHUB_ZIPBALL_TIMEOUT_SECS = 120.0
43
44 DEFAULT_COST_ESTIMATE_REGION = "us-east-1"
45
46 # Archive limits, checked against ZIP central-directory metadata (no
47 # decompression) plus a streamed compressed-size cap.
48 MAX_ARCHIVE_BYTES = 500 * 1024 * 1024 # 500 MiB compressed
49 MAX_ARCHIVE_ENTRIES = 100_000
50 MAX_UNCOMPRESSED_BYTES = 2 * 1024 * 1024 * 1024 # 2 GiB total decompressed
51 MAX_ENTRY_UNCOMPRESSED_BYTES = 100 * 1024 * 1024 # 100 MiB per entry
52 MAX_COMPRESSION_RATIO = 100 # decompressed/compressed per entry
53
54 # ── Models ───────────────────────────────────────────────────────────────
55
56
57 @dataclass
58 class ClientCredentials :
59 """OIDC client registration plus the derived authorize/token endpoints."""
60
61 client_id: str
62 client_secret: str
63 client_expires_at: int
64 authorize_endpoint: str
65 token_endpoint: str
66 scopes: List[ str ]
67
68
69 @dataclass
70 class StoredSession ( ClientCredentials ):
71 """A persisted session: client registration plus the current token pair."""
72
73 access_token: str = ""
74 refresh_token: str = ""
75 token_expires_at: int = 0
76
77 def to_json (self, indent: int = 2 ) -> str :
78 return json.dumps(asdict( self ), indent = indent)
79
80 @ classmethod
81 def from_json (cls, text: str ) -> "StoredSession" :
82 return cls ( ** json.loads(text))
83
84
85 # ── Runtime config ───────────────────────────────────────────────────────
86
87 logger = logging.getLogger( __name__ )
88
89 _ALLOWED_HOST_SUFFIXES = ( ".api.aws" , ".amazonaws.com" )
90
91
92 class ConfigError ( Exception ):
93 """Raised when configuration is invalid or unsafe."""
94
95
96 def _validate_base_url (url: str ) -> str :
97 """Validate and return a sanitized base URL.
98
99 Rejects non-HTTPS schemes, hosts outside the AWS domain allowlist,
100 and hosts that resolve to private/loopback/link-local addresses.
101 """
102 parsed = urlparse(url)
103
104 if parsed.scheme != "https" :
105 raise ConfigError(
106 f "Invalid { ENV_BASE_URL } : scheme must be https, got { parsed.scheme !r} . "
107 "Refusing to send credentials over a non-HTTPS connection."
108 )
109
110 hostname = parsed.hostname
111 if not hostname:
112 raise ConfigError( f "Invalid { ENV_BASE_URL } : no hostname in { url !r} ." )
113
114 if not any (
115 hostname == suffix.lstrip( "." ) or hostname.endswith(suffix)
116 for suffix in _ALLOWED_HOST_SUFFIXES
117 ):
118 raise ConfigError(
119 f "Invalid { ENV_BASE_URL } : host { hostname !r} is not in the allowed "
120 f 'domains ( { ", " .join( _ALLOWED_HOST_SUFFIXES ) } ). '
121 "Only official AWS endpoints are permitted."
122 )
123
124 try :
125 resolved = socket.getaddrinfo(hostname, None )
126 except socket.gaierror as err:
127 raise ConfigError(
128 f "Invalid { ENV_BASE_URL } : DNS resolution failed for { hostname !r} : { err } . "
129 "Refusing to proceed with an unresolvable host."
130 ) from err
131
132 for _family, _type, _proto, _canonname, sockaddr in resolved:
133 ip = ipaddress.ip_address(sockaddr[ 0 ])
134 if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
135 raise ConfigError(
136 f "Invalid { ENV_BASE_URL } : host { hostname !r} resolves to "
137 f "private/loopback/link-local address { ip } . "
138 "Refusing to send credentials to a non-public address."
139 )
140
141 return url.rstrip( "/" )
142
143
144 # IAM Identity Center issuer hosts accepted for the issuer URL override.
145 _ALLOWED_ISSUER_HOST_SUFFIXES = ( ".amazonaws.com" , ".awsapps.com" )
146
147
148 def validate_issuer_url (url: str ) -> str :
149 """Validate an IdC issuer URL override, returning it unchanged if allowed.
150
151 Only https URLs whose host is on an allowed issuer domain are accepted.
152 """
153 parsed = urlparse(url)
154
155 if parsed.scheme != "https" :
156 raise ConfigError(
157 f "Invalid { ENV_IDC_ISSUER_URL } : scheme must be https, got { parsed.scheme !r} . "
158 "Refusing to anchor the authentication flow on a non-HTTPS issuer."
159 )
160
161 hostname = parsed.hostname
162 if not hostname:
163 raise ConfigError( f "Invalid { ENV_IDC_ISSUER_URL } : no hostname in { url !r} ." )
164
165 if not any (
166 hostname == suffix.lstrip( "." ) or hostname.endswith(suffix)
167 for suffix in _ALLOWED_ISSUER_HOST_SUFFIXES
168 ):
169 raise ConfigError(
170 f "Invalid { ENV_IDC_ISSUER_URL } : host { hostname !r} is not in the allowed "
171 f 'issuer domains ( { ", " .join( _ALLOWED_ISSUER_HOST_SUFFIXES ) } ). '
172 "Only official AWS IAM Identity Center endpoints are permitted."
173 )
174
175 return url
176
177
178 def resolve_issuer_url () -> str :
179 """Return the IdC issuer URL, honoring a validated env-var override."""
180 override = os.environ.get( ENV_IDC_ISSUER_URL )
181 if override:
182 validated = validate_issuer_url(override)
183 logger.warning( "Using non-default IdC issuer URL: %s " , validated)
184 return validated
185 return IDC_ISSUER_URL
186
187
188 class Config :
189 """Configuration loaded from environment variables."""
190
191 def __init__ (self) -> None :
192 env_url = os.environ.get( ENV_BASE_URL )
193 if env_url:
194 self .base_url = _validate_base_url(env_url)
195 logger.info( "Using non-default base URL: %s " , self .base_url)
196 else :
197 self .base_url = DEFAULT_BASE_URL
198
199
200 def load_config () -> Config:
201 return Config()