@@ -3,6 +3,7 @@ package integration_testing
33import (
44 "bytes"
55 "context"
6+ "crypto/sha1"
67 "encoding/json"
78 "fmt"
89 "net"
@@ -140,6 +141,174 @@ FileBackend = ""
140141 t .Errorf ("Unexpected output format: %s" , output )
141142 }
142143 })
144+
145+ t .Run ("AssumeProfileWithSSO" , func (t * testing.T ) {
146+ // Create AWS config with SSO profile
147+ ssoConfig := fmt .Sprintf (`[profile test-sso]
148+ sso_account_id = 123456789012
149+ sso_role_name = TestRole
150+ sso_region = us-east-1
151+ sso_start_url = %s
152+ region = us-east-1
153+ ` , mockServer .URL )
154+
155+ // Update AWS config file with SSO profile
156+ err := os .WriteFile (awsConfigPath , []byte (awsConfig + "\n " + ssoConfig ), 0644 )
157+ require .NoError (t , err )
158+
159+ // Create SSO cache directory and token
160+ ssoCacheDir := filepath .Join (awsDir , "sso" , "cache" )
161+ err = os .MkdirAll (ssoCacheDir , 0755 )
162+ require .NoError (t , err )
163+
164+ // Create a cached SSO token
165+ tokenData := map [string ]interface {}{
166+ "accessToken" : "cached-test-token" ,
167+ "expiresAt" : time .Now ().Add (1 * time .Hour ).Format (time .RFC3339 ),
168+ "region" : "us-east-1" ,
169+ "startUrl" : mockServer .URL ,
170+ }
171+ tokenBytes , err := json .Marshal (tokenData )
172+ require .NoError (t , err )
173+
174+ // The cache filename is a SHA1 hash of the session name
175+ // For AWS SSO, the session name is derived from the start URL
176+ h := sha1 .New ()
177+ h .Write ([]byte (mockServer .URL ))
178+ cacheFile := filepath .Join (ssoCacheDir , fmt .Sprintf ("%x.json" , h .Sum (nil )))
179+ err = os .WriteFile (cacheFile , tokenBytes , 0600 )
180+ require .NoError (t , err )
181+
182+ // Set up environment
183+ env := []string {
184+ fmt .Sprintf ("HOME=%s" , homeDir ),
185+ fmt .Sprintf ("AWS_CONFIG_FILE=%s" , awsConfigPath ),
186+ fmt .Sprintf ("XDG_CONFIG_HOME=%s" , xdgConfigHome ),
187+ "GRANTED_QUIET=true" , // Suppress output messages
188+ "FORCE_NO_ALIAS=true" , // Skip alias configuration
189+ "FORCE_ASSUME_CLI=true" , // Force assume mode
190+ "PATH=" + os .Getenv ("PATH" ), // Preserve PATH
191+ }
192+
193+ // Run assume command with SSO profile
194+ cmd := exec .Command (grantedBinary , "test-sso" )
195+ cmd .Env = env
196+
197+ var stdout , stderr bytes.Buffer
198+ cmd .Stdout = & stdout
199+ cmd .Stderr = & stderr
200+
201+ err = cmd .Run ()
202+ if err != nil {
203+ t .Fatalf ("Assume command failed: %v\n Stdout: %s\n Stderr: %s" , err , stdout .String (), stderr .String ())
204+ }
205+
206+ // Parse output
207+ output := stdout .String ()
208+ t .Logf ("Assume output: %s" , output )
209+
210+ // The assume command outputs credentials in a specific format
211+ assert .Contains (t , output , "GrantedAssume" )
212+
213+ // Extract credentials from output
214+ parts := strings .Fields (output )
215+ if len (parts ) >= 4 {
216+ accessKey := parts [1 ]
217+ secretKey := parts [2 ]
218+ sessionToken := parts [3 ]
219+
220+ // For SSO profiles, we expect temporary credentials from the mock server
221+ assert .Equal (t , "ASIAMOCKEXAMPLE" , accessKey )
222+ assert .Equal (t , "mock-secret-key" , secretKey )
223+ assert .Equal (t , "mock-session-token" , sessionToken )
224+ } else {
225+ t .Errorf ("Unexpected output format: %s" , output )
226+ }
227+ })
228+
229+ t .Run ("AssumeProfileWithGrantedSSO" , func (t * testing.T ) {
230+ // Create AWS config with granted_sso_ profile configuration
231+ grantedSSOConfig := fmt .Sprintf (`[profile test-granted-sso]
232+ granted_sso_account_id = 123456789012
233+ granted_sso_role_name = TestRole
234+ granted_sso_region = us-east-1
235+ granted_sso_start_url = %s
236+ credential_process = %s credential-process --profile test-granted-sso
237+ region = us-east-1
238+ ` , mockServer .URL , grantedBinary )
239+
240+ // Update AWS config file with granted SSO profile
241+ err := os .WriteFile (awsConfigPath , []byte (awsConfig + "\n " + grantedSSOConfig ), 0644 )
242+ require .NoError (t , err )
243+
244+ // Create SSO cache directory and token for the granted credential process
245+ ssoCacheDir := filepath .Join (awsDir , "sso" , "cache" )
246+ err = os .MkdirAll (ssoCacheDir , 0755 )
247+ require .NoError (t , err )
248+
249+ // Create a cached SSO token
250+ tokenData := map [string ]interface {}{
251+ "accessToken" : "cached-test-token" ,
252+ "expiresAt" : time .Now ().Add (1 * time .Hour ).Format (time .RFC3339 ),
253+ "region" : "us-east-1" ,
254+ "startUrl" : mockServer .URL ,
255+ }
256+ tokenBytes , err := json .Marshal (tokenData )
257+ require .NoError (t , err )
258+
259+ // The cache filename is a SHA1 hash of the start URL
260+ h := sha1 .New ()
261+ h .Write ([]byte (mockServer .URL ))
262+ cacheFile := filepath .Join (ssoCacheDir , fmt .Sprintf ("%x.json" , h .Sum (nil )))
263+ err = os .WriteFile (cacheFile , tokenBytes , 0600 )
264+ require .NoError (t , err )
265+
266+ // Set up environment
267+ env := []string {
268+ fmt .Sprintf ("HOME=%s" , homeDir ),
269+ fmt .Sprintf ("AWS_CONFIG_FILE=%s" , awsConfigPath ),
270+ fmt .Sprintf ("XDG_CONFIG_HOME=%s" , xdgConfigHome ),
271+ "GRANTED_QUIET=true" , // Suppress output messages
272+ "FORCE_NO_ALIAS=true" , // Skip alias configuration
273+ "FORCE_ASSUME_CLI=true" , // Force assume mode
274+ "PATH=" + os .Getenv ("PATH" ), // Preserve PATH
275+ }
276+
277+ // Run assume command with granted SSO profile
278+ cmd := exec .Command (grantedBinary , "test-granted-sso" )
279+ cmd .Env = env
280+
281+ var stdout , stderr bytes.Buffer
282+ cmd .Stdout = & stdout
283+ cmd .Stderr = & stderr
284+
285+ err = cmd .Run ()
286+ if err != nil {
287+ t .Fatalf ("Assume command failed: %v\n Stdout: %s\n Stderr: %s" , err , stdout .String (), stderr .String ())
288+ }
289+
290+ // Parse output
291+ output := stdout .String ()
292+ t .Logf ("Assume output: %s" , output )
293+
294+ // The assume command outputs credentials in a specific format
295+ assert .Contains (t , output , "GrantedAssume" )
296+
297+ // Extract credentials from output
298+ parts := strings .Fields (output )
299+ if len (parts ) >= 4 {
300+ accessKey := parts [1 ]
301+ secretKey := parts [2 ]
302+ sessionToken := parts [3 ]
303+
304+ // For granted SSO profiles with credential process, we expect temporary credentials
305+ assert .Equal (t , "ASIAMOCKEXAMPLE" , accessKey )
306+ assert .Equal (t , "mock-secret-key" , secretKey )
307+ assert .Equal (t , "mock-session-token" , sessionToken )
308+ } else {
309+ t .Errorf ("Unexpected output format: %s" , output )
310+ }
311+ })
143312}
144313
145314// AssumeE2EMockServer is a specialized mock server for assume command testing
@@ -171,6 +340,8 @@ func NewAssumeE2EMockServer() *AssumeE2EMockServer {
171340 server .handleGetRoleCredentials (w , r )
172341 case "AWSSSSOPortalService.ListAccounts" :
173342 server .handleListAccounts (w , r )
343+ case "AWSSSSOPortalService.ListAccountRoles" :
344+ server .handleListAccountRoles (w , r )
174345 case "SSOOIDCService.CreateToken" :
175346 server .handleCreateToken (w , r )
176347 default :
@@ -250,3 +421,17 @@ func (s *AssumeE2EMockServer) handleCreateToken(w http.ResponseWriter, r *http.R
250421 w .Header ().Set ("Content-Type" , "application/x-amz-json-1.1" )
251422 json .NewEncoder (w ).Encode (response )
252423}
424+
425+ func (s * AssumeE2EMockServer ) handleListAccountRoles (w http.ResponseWriter , r * http.Request ) {
426+ response := map [string ]interface {}{
427+ "roleList" : []map [string ]interface {}{
428+ {
429+ "roleName" : "TestRole" ,
430+ "accountId" : "123456789012" ,
431+ },
432+ },
433+ }
434+
435+ w .Header ().Set ("Content-Type" , "application/x-amz-json-1.1" )
436+ json .NewEncoder (w ).Encode (response )
437+ }
0 commit comments