Skip to content

Commit 98a859b

Browse files
shepazonford-at-aws
authored andcommitted
Add new SSO identity resolver example
NOT YET WORKING. This is a checkpoint save before making more changes.
1 parent 1f7d3ef commit 98a859b

File tree

4 files changed

+471
-0
lines changed

4 files changed

+471
-0
lines changed
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
//
4+
/// A simple example that shows how to use the AWS SDK for Swift to
5+
/// authenticate using optional static credentials and an AWS IAM role ARN.
6+
7+
// snippet-start:[swift.identity.sso.imports]
8+
import ArgumentParser
9+
import AWSClientRuntime
10+
import AWSS3
11+
import AWSSDKIdentity
12+
import AWSSTS
13+
import Foundation
14+
import SmithyIdentity
15+
// snippet-end:[swift.identity.sso.imports]
16+
17+
struct ExampleCommand: ParsableCommand {
18+
@Option(help: "AWS profile name (default: 'default')")
19+
var profile: String? = nil
20+
@Option(help: "AWS configuration file path (default: '~/.aws/config')")
21+
var config: String? = nil
22+
@Option(help: "AWS credentials file path (default: '~/.aws/credentials')")
23+
var credentials: String? = nil
24+
25+
static var configuration = CommandConfiguration(
26+
commandName: "sso-resolver",
27+
abstract: """
28+
Demonstrates how to use an SSO credential identity resolver with the
29+
AWS SDK for Swift.
30+
""",
31+
discussion: """
32+
"""
33+
)
34+
35+
/// Called by ``main()`` to do the actual running of the AWS
36+
/// example.
37+
// snippet-start:[swift.identity.sso.command.runasync]
38+
func runAsync() async throws {
39+
// If credentials are specified, create a credential identity
40+
// resolver that uses them to authenticate. This identity will be used
41+
// to ask for permission to use the specified role.
42+
43+
print("Creating resolver...")
44+
do {
45+
let identityResolver = try SSOAWSCredentialIdentityResolver(
46+
profileName: profile,
47+
configFilePath: config,
48+
credentialsFilePath: credentials
49+
)
50+
dump(identityResolver, name: "Identity resolver:")
51+
52+
// Use the credential identity resolver to access AWS S3.
53+
54+
print("Listing bucket names...")
55+
let names = try await getBucketNames(identityResolver: identityResolver)
56+
57+
print("Found \(names.count) buckets:")
58+
for name in names {
59+
print(" \(name)")
60+
}
61+
} catch {
62+
print("ERROR: Error getting bucket names in runAsync:", dump(error))
63+
throw error
64+
}
65+
}
66+
// snippet-end:[swift.identity.sso.command.runasync]
67+
}
68+
69+
/// An `Error` type used to return errors from the
70+
/// `assumeRole(identityResolver: roleArn:)` function.
71+
enum AssumeRoleExampleError: Error {
72+
/// An error indicating that the STS `AssumeRole` request failed.
73+
case assumeRoleFailed
74+
/// An error indicating that the returned credentials were missing
75+
/// required information.
76+
case incompleteCredentials
77+
/// An error indicating that no credentials were returned by `AssumeRole`.
78+
case missingCredentials
79+
80+
/// Return a human-readable explanation of the error.
81+
var errorDescription: String? {
82+
switch self {
83+
case .assumeRoleFailed:
84+
return "Unable to assume the specified role."
85+
case .incompleteCredentials:
86+
return "AWS STS returned incomplete credentials."
87+
case .missingCredentials:
88+
return "AWS STS did not return any credentials for the specified role."
89+
}
90+
}
91+
}
92+
93+
// snippet-start:[swift.identity.sso.assumeRole-function]
94+
/// Assume the specified role. If any kind of credential identity resolver is
95+
/// specified, that identity is adopted before assuming the role.
96+
///
97+
/// - Parameters:
98+
/// - identityResolver: Any kind of `AWSCredentialIdentityResolver`. If
99+
/// provided, this identity is adopted before attempting to assume the
100+
/// specified role.
101+
/// - roleArn: The ARN of the AWS role to assume.
102+
///
103+
/// - Throws: Re-throws STS errors. Also can throw any
104+
/// `AssumeRoleExampleError`.
105+
/// - Returns: An `AWSCredentialIdentity` containing the temporary credentials
106+
/// assigned.
107+
func assumeRole(identityResolver: (any AWSCredentialIdentityResolver)?,
108+
roleArn: String) async throws -> AWSCredentialIdentity {
109+
print("ASSUMEROLE")
110+
let stsConfiguration = try await STSClient.STSClientConfiguration(
111+
awsCredentialIdentityResolver: identityResolver
112+
)
113+
let stsClient = STSClient(config: stsConfiguration)
114+
115+
// Assume the role and return the assigned credentials.
116+
117+
// snippet-start: [swift.sts.sts.AssumeRole]
118+
let input = AssumeRoleInput(
119+
roleArn: roleArn,
120+
roleSessionName: "AssumeRole-Example"
121+
)
122+
123+
let output = try await stsClient.assumeRole(input: input)
124+
125+
guard let credentials = output.credentials else {
126+
throw AssumeRoleExampleError.missingCredentials
127+
}
128+
129+
guard let accessKey = credentials.accessKeyId,
130+
let secretKey = credentials.secretAccessKey,
131+
let sessionToken = credentials.sessionToken else {
132+
throw AssumeRoleExampleError.incompleteCredentials
133+
}
134+
// snippet-end: [swift.sts.sts.AssumeRole]
135+
136+
// Return an `AWSCredentialIdentity` object with the temporary
137+
// credentials.
138+
139+
let awsCredentials = AWSCredentialIdentity(
140+
accessKey: accessKey,
141+
secret: secretKey,
142+
sessionToken: sessionToken
143+
)
144+
return awsCredentials
145+
}
146+
// snippet-end:[swift.identity.sso.assumeRole-function]
147+
148+
/// Return an array containing the names of all available buckets using
149+
/// the specified credential identity resolver to authenticate.
150+
///
151+
/// - Parameter identityResolver: Any type of `AWSCredentialIdentityResolver`,
152+
/// used to authenticate and authorize the user for access to the bucket
153+
/// names.
154+
///
155+
/// - Throws: Re-throws errors from `ListBucketsPaginated`.
156+
///
157+
/// - Returns: An array of strings listing the buckets.
158+
func getBucketNames(identityResolver: (any AWSCredentialIdentityResolver)?)
159+
async throws -> [String] {
160+
do {
161+
// Get an S3Client with which to access Amazon S3.
162+
// snippet-start:[swift.identity.sso.use-resolver]
163+
let configuration = try await S3Client.S3ClientConfiguration(
164+
awsCredentialIdentityResolver: identityResolver
165+
)
166+
let client = S3Client(config: configuration)
167+
168+
// Use "Paginated" to get all the buckets. This lets the SDK handle
169+
// the 'continuationToken' in "ListBucketsOutput".
170+
let pages = client.listBucketsPaginated(
171+
input: ListBucketsInput( maxBuckets: 10)
172+
)
173+
// snippet-end:[swift.identity.sso.use-resolver]
174+
175+
// Get the bucket names.
176+
var bucketNames: [String] = []
177+
178+
do {
179+
for try await page in pages {
180+
guard let buckets = page.buckets else {
181+
print("Error: page is empty.")
182+
continue
183+
}
184+
185+
for bucket in buckets {
186+
bucketNames.append(bucket.name ?? "<unknown>")
187+
}
188+
}
189+
190+
return bucketNames
191+
} catch {
192+
print("ERROR: listBuckets:", dump(error))
193+
throw error
194+
}
195+
}
196+
}
197+
198+
/// The program's asynchronous entry point.
199+
@main
200+
struct Main {
201+
static func main() async {
202+
print("STARTING")
203+
let args = Array(CommandLine.arguments.dropFirst())
204+
205+
do {
206+
await SDKLoggingSystem().initialize(logLevel: .trace)
207+
SDKDefaultIO.shared.setLogLevel(level: .trace)
208+
let command = try ExampleCommand.parse(args)
209+
print("Calling runAsync")
210+
//try await command.runAsync()
211+
} catch {
212+
ExampleCommand.exit(withError: error)
213+
}
214+
}
215+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// swift-tools-version:5.9
2+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// The swift-tools-version declares the minimum version of Swift required to
6+
// build this package.
7+
8+
import PackageDescription
9+
10+
let package = Package(
11+
name: "sso-resolver",
12+
// Let Xcode know the minimum Apple platforms supported.
13+
platforms: [
14+
.macOS(.v11),
15+
.iOS(.v13)
16+
],
17+
dependencies: [
18+
// Dependencies declare other packages that this package depends on.
19+
.package(
20+
url: "https://github.com/awslabs/aws-sdk-swift",
21+
from: "1.0.0"
22+
),
23+
.package(
24+
url: "https://github.com/apple/swift-argument-parser.git",
25+
branch: "main"
26+
),
27+
],
28+
targets: [
29+
// Targets are the basic building blocks of a package, defining a module or a test suite.
30+
// Targets can depend on other targets in this package and products from dependencies.
31+
.executableTarget(
32+
name: "sso-resolver",
33+
dependencies: [
34+
.product(name: "AWSSTS", package: "aws-sdk-swift"),
35+
.product(name: "AWSS3", package: "aws-sdk-swift"),
36+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
37+
],
38+
path: "Sources"),
39+
]
40+
)

0 commit comments

Comments
 (0)