22// SPDX-License-Identifier: Apache-2.0
33//
44/// A simple example that shows how to use the AWS SDK for Swift to
5- /// authenticate using an AWS IAM role ARN.
5+ /// authenticate using optional static credentials and an AWS IAM role ARN.
66
7- // snippet-start:[swift.static-resolver .imports]
7+ // snippet-start:[swift.AssumeRole .imports]
88import ArgumentParser
99import AWSClientRuntime
1010import AWSS3
1111import AWSSDKIdentity
1212import AWSSTS
1313import Foundation
1414import SmithyIdentity
15- // snippet-end:[swift.static-resolver .imports]
15+ // snippet-end:[swift.AssumeRole .imports]
1616
1717struct ExampleCommand : ParsableCommand {
1818 @Option ( help: " AWS access key ID " )
19- var accessKey : String
19+ var accessKey : String ? = nil
2020 @Option ( help: " AWS secret access key " )
21- var secretKey : String
21+ var secretKey : String ? = nil
2222 @Option ( help: " Session token " )
2323 var sessionToken : String ? = nil
2424 @Argument ( help: " ARN of the role to assume " )
2525 var roleArn : String
2626
2727 static var configuration = CommandConfiguration (
28- commandName: " static-resolver " ,
28+ commandName: " AssumeRole " ,
2929 abstract: """
30- Authenticate using the access key, secret access key, and role provided.
31- Then list the available buckets .
30+ Authenticate using the specified role, optionally using specified
31+ access key, secret access key, and session token first .
3232 """ ,
3333 discussion: """
34- This program uses the specified credentials when assuming the specified
35- role, then uses the credentials returned by the role to list the user's
36- buckets. This shows a couple of ways to use a
34+ This program uses the specified access key, secret access key, and
35+ optional session token, to request temporary credentials for the
36+ specified role Then it uses the credentials to list the user's
37+ Amazon S3 buckets. This shows a couple of ways to use a
3738 StaticAWSCredentialIdentityResolver object.
3839 """
3940 )
4041
4142 /// Called by ``main()`` to do the actual running of the AWS
4243 /// example.
43- // snippet-start:[swift.static-resolver .command.runasync]
44+ // snippet-start:[swift.AssumeRole .command.runasync]
4445 func runAsync( ) async throws {
45- // Authenticate using the command line inputs.
46+ // If credentials are specified, create a credential identity
47+ // resolver that uses them to authenticate. This identity will be used
48+ // to ask for permission to use the specified role.
49+
4650 var identityResolver : StaticAWSCredentialIdentityResolver ? = nil
47- /*
48- do {
49- identityResolver = try getIdentityResolver(accessKey: accessKey,
50- secretKey: secretKey, sessionToken: sessionToken)
51- } catch {
52- print("ERROR: Unable to get identity resolver in runAsync:",
53- dump(error))
54- throw error
51+
52+ if accessKey != nil && secretKey != nil {
53+ do {
54+ identityResolver = try getIdentityResolver ( accessKey: accessKey,
55+ secretKey: secretKey, sessionToken: sessionToken)
56+ } catch {
57+ print ( " ERROR: Unable to get identity resolver in runAsync: " ,
58+ dump ( error) )
59+ throw error
60+ }
5561 }
56- */
5762
58- // Assume the role.
63+ // Assume the role using the credentials provided on the command line,
64+ // or using the default credentials if none were specified.
5965
6066 do {
61- // snippet-start: [swift.static-resolver .use-role-credentials]
67+ // snippet-start: [swift.AssumeRole .use-role-credentials]
6268 let credentials = try await assumeRole ( identityResolver: identityResolver,
6369 roleArn: roleArn)
6470 do {
@@ -72,7 +78,7 @@ struct ExampleCommand: ParsableCommand {
7278 dump ( error) )
7379 throw error
7480 }
75- // snippet-end: [swift.static-resolver .use-role-credentials]
81+ // snippet-end: [swift.AssumeRole .use-role-credentials]
7682 } catch {
7783 print ( " ERROR: Error assuming role in runAsync: " , dump ( error) )
7884 throw AssumeRoleExampleError . assumeRoleFailed
@@ -92,14 +98,21 @@ struct ExampleCommand: ParsableCommand {
9298 throw error
9399 }
94100 }
95- // snippet-end:[swift.static-resolver .command.runasync]
101+ // snippet-end:[swift.AssumeRole .command.runasync]
96102}
97103
104+ /// An `Error` type used to return errors from the
105+ /// `assumeRole(identityResolver: roleArn:)` function.
98106enum AssumeRoleExampleError : Error {
107+ /// An error indicating that the STS `AssumeRole` request failed.
99108 case assumeRoleFailed
109+ /// An error indicating that the returned credentials were missing
110+ /// required information.
100111 case incompleteCredentials
112+ /// An error indicating that no credentials were returned by `AssumeRole`.
101113 case missingCredentials
102114
115+ /// Return a human-readable explanation of the error.
103116 var errorDescription : String ? {
104117 switch self {
105118 case . assumeRoleFailed:
@@ -112,78 +125,94 @@ enum AssumeRoleExampleError: Error {
112125 }
113126}
114127
115- // snippet-start:[swift.static-resolver.assumeRole-function]
128+ // snippet-start:[swift.AssumeRole.assumeRole-function]
129+ /// Assume the specified role. If any kind of credential identity resolver is
130+ /// specified, that identity is adopted before assuming the role.
131+ ///
132+ /// - Parameters:
133+ /// - identityResolver: Any kind of `AWSCredentialIdentityResolver`. If
134+ /// provided, this identity is adopted before attempting to assume the
135+ /// specified role.
136+ /// - roleArn: The ARN of the AWS role to assume.
137+ ///
138+ /// - Throws: Re-throws STS errors. Also can throw any
139+ /// `AssumeRoleExampleError`.
140+ /// - Returns: An `AWSCredentialIdentity` containing the temporary credentials
141+ /// assigned.
116142func assumeRole( identityResolver: ( any AWSCredentialIdentityResolver ) ? ,
117143 roleArn: String ) async throws -> AWSCredentialIdentity {
118144 let stsConfiguration = try await STSClient . STSClientConfiguration (
119145 awsCredentialIdentityResolver: identityResolver
120146 )
121147 let stsClient = STSClient ( config: stsConfiguration)
122148
149+ // Assume the role and return the assigned credentials.
150+
151+ // snippet-start: [swift.sts.AssumeRole]
123152 let input = AssumeRoleInput (
124153 roleArn: roleArn,
125- roleSessionName: " Static-Resolver -Example"
154+ roleSessionName: " AssumeRole -Example"
126155 )
127156
128- // Assume the role and return the assigned credentials.
157+ let output = try await stsClient . assumeRole ( input : input )
129158
130- do {
131- let output = try await stsClient. assumeRole ( input: input)
132-
133- guard let credentials = output. credentials else {
134- throw AssumeRoleExampleError . missingCredentials
135- }
159+ guard let credentials = output. credentials else {
160+ throw AssumeRoleExampleError . missingCredentials
161+ }
136162
137- guard let accessKey = credentials. accessKeyId,
138- let secretKey = credentials. secretAccessKey,
139- let sessionToken = credentials. sessionToken else {
140- throw AssumeRoleExampleError . incompleteCredentials
141- }
163+ guard let accessKey = credentials. accessKeyId,
164+ let secretKey = credentials. secretAccessKey,
165+ let sessionToken = credentials. sessionToken else {
166+ throw AssumeRoleExampleError . incompleteCredentials
167+ }
168+ // snippet-end: [swift.sts.AssumeRole]
142169
143- // Return an `AWSCredentialIdentity` object with the temporary
144- // credentials.
170+ // Return an `AWSCredentialIdentity` object with the temporary
171+ // credentials.
145172
146- let awsCredentials = AWSCredentialIdentity (
147- accessKey: accessKey,
148- secret: secretKey,
149- sessionToken: sessionToken
150- )
151- return awsCredentials
152- }
173+ let awsCredentials = AWSCredentialIdentity (
174+ accessKey: accessKey,
175+ secret: secretKey,
176+ sessionToken: sessionToken
177+ )
178+ return awsCredentials
153179}
154- // snippet-end:[swift.static-resolver .assumeRole-function]
180+ // snippet-end:[swift.AssumeRole .assumeRole-function]
155181
156- // snippet-start:[s3.swift.intro.getbucketnames]
157- // Return an array containing the names of all available buckets.
158- //
159- // - Returns: An array of strings listing the buckets.
182+ /// Return an array containing the names of all available buckets using
183+ /// the specified credential identity resolver to authenticate.
184+ ///
185+ /// - Parameter identityResolver: Any type of `AWSCredentialIdentityResolver`,
186+ /// used to authenticate and authorize the user for access to the bucket
187+ /// names.
188+ ///
189+ /// - Throws: Re-throws errors from `ListBucketsPaginated`.
190+ ///
191+ /// - Returns: An array of strings listing the buckets.
160192func getBucketNames( identityResolver: ( any AWSCredentialIdentityResolver ) ? )
161193 async throws -> [ String ] {
162194 do {
163195 // Get an S3Client with which to access Amazon S3.
164- // snippet-start:[s3. swift.intro.client-init ]
196+ // snippet-start:[swift.AssumeRole.use-resolver ]
165197 let configuration = try await S3Client . S3ClientConfiguration (
166198 awsCredentialIdentityResolver: identityResolver
167199 )
168- // configuration.region = "us-east-2" // Uncomment this to set the region programmatically.
169200 let client = S3Client ( config: configuration)
170- // snippet-end:[s3.swift.intro.client-init]
171201
172- // snippet-start:[s3.swift.intro.listbuckets]
173- // Use "Paginated" to get all the buckets.
174- // This lets the SDK handle the 'continuationToken' in "ListBucketsOutput".
202+ // Use "Paginated" to get all the buckets. This lets the SDK handle
203+ // the 'continuationToken' in "ListBucketsOutput".
175204 let pages = client. listBucketsPaginated (
176205 input: ListBucketsInput ( maxBuckets: 10 )
177206 )
178- // snippet-end:[s3. swift.intro.listbuckets ]
207+ // snippet-end:[swift.AssumeRole.use-resolver ]
179208
180209 // Get the bucket names.
181210 var bucketNames : [ String ] = [ ]
182211
183212 do {
184213 for try await page in pages {
185214 guard let buckets = page. buckets else {
186- print ( " Error: no buckets returned . " )
215+ print ( " Error: page is empty . " )
187216 continue
188217 }
189218
@@ -210,22 +239,28 @@ func getBucketNames(identityResolver: (any AWSCredentialIdentityResolver)?)
210239/// - Throws: Re-throws errors from AWSSDKIdentity.
211240/// - Returns: A `StaticAWSCredentialIdentityResolver` that can be used when
212241/// configuring service clients.
213- func getIdentityResolver( accessKey: String , secretKey: String ,
242+ func getIdentityResolver( accessKey: String ? , secretKey: String ? ,
214243 sessionToken: String ? )
215- throws -> StaticAWSCredentialIdentityResolver {
244+ throws -> StaticAWSCredentialIdentityResolver ? {
245+
246+ if accessKey == nil || secretKey == nil {
247+ return nil
248+ }
249+
250+ guard let accessKey = accessKey,
251+ let secretKey = secretKey else {
252+ return nil
253+ }
254+
216255 let credentials = AWSCredentialIdentity (
217256 accessKey: accessKey,
218257 secret: secretKey,
219- //expiration: cognitoCredentials.expiration,
220258 sessionToken: sessionToken
221259 )
222260
223261 return try StaticAWSCredentialIdentityResolver ( credentials)
224262}
225263
226- // snippet-end:[s3.swift.intro.getbucketnames]
227-
228- // snippet-start:[s3.swift.intro.main]
229264/// The program's asynchronous entry point.
230265@main
231266struct Main {
@@ -240,5 +275,3 @@ struct Main {
240275 }
241276 }
242277}
243-
244- // snippet-end:[s3.swift.intro.main]
0 commit comments