Skip to content

feat: STS web identity creds resolver #933

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.traits.AuthTrait
import software.amazon.smithy.model.traits.OptionalAuthTrait
import software.amazon.smithy.model.traits.Trait
import software.amazon.smithy.model.traits.synthetic.NoAuthTrait
import software.amazon.smithy.rulesengine.language.EndpointRuleSet
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter
import software.amazon.smithy.rulesengine.language.syntax.parameters.ParameterType
Expand All @@ -26,7 +27,9 @@ import software.amazon.smithy.swift.codegen.utils.clientName
import software.amazon.smithy.swift.codegen.utils.toLowerCamelCase
import java.util.Locale

class AuthSchemeResolverGenerator {
class AuthSchemeResolverGenerator(
private val optionCustomization: ((String, SwiftWriter) -> SwiftWriter)? = null,
) {
fun render(ctx: ProtocolGenerator.GenerationContext) {
val serviceIndex = ServiceIndex(ctx.model)

Expand All @@ -47,7 +50,7 @@ class AuthSchemeResolverGenerator {
) {
writer.apply {
openBlock(
"public struct ${getSdkId(ctx)}${SmithyHTTPAuthAPITypes.AuthSchemeResolverParams.name}: \$N {",
"${ctx.settings.visibility} struct ${getSdkId(ctx)}${SmithyHTTPAuthAPITypes.AuthSchemeResolverParams.name}: \$N {",
"}",
SmithyHTTPAuthAPITypes.AuthSchemeResolverParams,
) {
Expand Down Expand Up @@ -96,7 +99,7 @@ class AuthSchemeResolverGenerator {
) {
writer.apply {
openBlock(
"public protocol ${getServiceSpecificAuthSchemeResolverName(ctx)}: \$N {",
"${ctx.settings.visibility} protocol ${getServiceSpecificAuthSchemeResolverName(ctx)}: \$N {",
"}",
SmithyHTTPAuthAPITypes.AuthSchemeResolver,
) {
Expand Down Expand Up @@ -127,13 +130,19 @@ class AuthSchemeResolverGenerator {

// Model-based auth scheme resolver should be private internal impl detail if service uses rules-based resolver.
val accessModifier = if (usesRulesBasedResolver) "private" else "public"
val resolvedAccessModifier =
if (accessModifier == "public" && ctx.settings.visibility == "internal") {
ctx.settings.visibility
} else {
accessModifier
}
val serviceSpecificAuthResolverProtocol = sdkId + AUTH_SCHEME_RESOLVER

writer.apply {
writer.openBlock(
"\$L struct \$L: \$L {",
"}",
accessModifier,
resolvedAccessModifier,
defaultResolverName,
serviceSpecificAuthResolverProtocol,
) {
Expand Down Expand Up @@ -240,35 +249,50 @@ class AuthSchemeResolverGenerator {
writer.apply {
indent()
schemes.forEach {
if (it.key == SigV4Trait.ID) {
renderSigV4AuthOption(it, writer)
} else {
if (it.key == NoAuthTrait.ID) {
write(
"validAuthOptions.append(\$N(schemeID: \$S))",
SmithyHTTPAuthAPITypes.AuthOption,
it.key,
)
} else {
renderAuthOption(it, writer)
}
}
dedent()
}
}

private fun renderSigV4AuthOption(
private fun renderAuthOption(
scheme: Map.Entry<ShapeId, Trait>,
writer: SwiftWriter,
) {
writer.apply {
val authOptionName = "${scheme.key.name}Option"
write("var $authOptionName = \$N(schemeID: \$S)", SmithyHTTPAuthAPITypes.AuthOption, scheme.key)
if (scheme.key == SigV4Trait.ID) renderSigV4AuthOptionCustomization(authOptionName, scheme, writer)
optionCustomization?.invoke(authOptionName, writer)
write("validAuthOptions.append($authOptionName)")
}
}

private fun renderSigV4AuthOptionCustomization(
authOptionName: String,
scheme: Map.Entry<ShapeId, Trait>,
writer: SwiftWriter,
) {
writer.apply {
write("var sigV4Option = \$N(schemeID: \$S)", SmithyHTTPAuthAPITypes.AuthOption, scheme.key)
write(
"sigV4Option.signingProperties.set(key: \$N.signingName, value: \"${(scheme.value as SigV4Trait).name}\")",
"$authOptionName.signingProperties.set(key: \$N.signingName, value: \"${(scheme.value as SigV4Trait).name}\")",
SmithyHTTPAuthAPITypes.SigningPropertyKeys,
)
openBlock("guard let region = serviceParams.region else {", "}") {
write("throw \$N.authError(\"Missing region in auth scheme parameters for SigV4 auth scheme.\")", SmithyTypes.ClientError)
}
write("sigV4Option.signingProperties.set(key: \$N.signingRegion, value: region)", SmithyHTTPAuthAPITypes.SigningPropertyKeys)
write("validAuthOptions.append(sigV4Option)")
write(
"$authOptionName.signingProperties.set(key: \$N.signingRegion, value: region)",
SmithyHTTPAuthAPITypes.SigningPropertyKeys,
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class EnumGenerator(
writer.writeShapeDocs(shape)
writer.writeAvailableAttribute(null, shape)
writer.openBlock(
"public enum \$enum.name:L: \$N, \$N, \$N, \$N, \$N {",
"${settings.visibility} enum \$enum.name:L: \$N, \$N, \$N, \$N, \$N {",
"}",
SwiftTypes.Protocols.Sendable,
SwiftTypes.Protocols.Equatable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class IntEnumGenerator(
writer.writeShapeDocs(shape)
writer.writeAvailableAttribute(null, shape)
writer.openBlock(
"public enum \$enum.name:L: \$N, \$N, \$N, \$N, \$N {",
"${settings.visibility} enum \$enum.name:L: \$N, \$N, \$N, \$N, \$N {",
"}",
SwiftTypes.Protocols.Sendable,
SwiftTypes.Protocols.Equatable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ServiceNamespaceIntegration : SwiftIntegration {
val namespaceName = service.nestedNamespaceType(ctx.symbolProvider).name
val filename = ModelFileUtils.filename(ctx.settings, namespaceName)
delegator.useFileWriter(filename) { writer ->
writer.write("public enum \$L {}", namespaceName)
writer.write("${ctx.settings.visibility} enum \$L {}", namespaceName)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class StructureGenerator(
val equatableConformance =
writer.format(", \$N", SwiftTypes.Protocols.Equatable).takeIf { shape.hasTrait<EquatableConformanceTrait>() } ?: ""
writer
.openBlock("public struct \$struct.name:L: \$N$equatableConformance {", SwiftTypes.Protocols.Sendable)
.openBlock("${settings.visibility} struct \$struct.name:L: \$N$equatableConformance {", SwiftTypes.Protocols.Sendable)
.call { generateStructMembers() }
.write("")
.call { generateInitializerForStructure(false) }
Expand Down Expand Up @@ -220,7 +220,7 @@ class StructureGenerator(
writer.writeAvailableAttribute(model, shape)
writer
.openBlock(
"public struct \$struct.name:L: \$N, \$error.protocol:N, \$N, \$N, \$N {",
"${settings.visibility} struct \$struct.name:L: \$N, \$error.protocol:N, \$N, \$N, \$N {",
ClientRuntimeTypes.Core.ModeledError,
ClientRuntimeTypes.Http.HttpError,
SwiftTypes.Error,
Expand All @@ -239,7 +239,7 @@ class StructureGenerator(
private fun generateErrorStructMembers() {
if (membersSortedByName.isNotEmpty()) {
writer.write("")
writer.openBlock("public struct Properties: \$N {", "}", SwiftTypes.Protocols.Sendable) {
writer.openBlock("${settings.visibility} struct Properties: \$N {", "}", SwiftTypes.Protocols.Sendable) {
membersSortedByName.forEach {
val (memberName, memberSymbol) = memberShapeDataContainer.getOrElse(it) { return@forEach }
writer.writeMemberDocs(model, it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ private const val GIT_REPO = "gitRepo"
private const val SWIFT_VERSION = "swiftVersion"
Copy link
Contributor Author

@sichanyoo sichanyoo Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The visibility setting in swift plugin codegen settings comes from the smithy-build.json generated at build time by aws-sdk-swift/codegen/sdk-codegen/build.gradle.kts, like all other values in the settings.

It's added to generate an internal STS client into AWSSDKIdentity module. For all public services, visibility is public.

private const val MERGE_MODELS = "mergeModels"
private const val COPYRIGHT_NOTICE = "copyrightNotice"
private const val VISIBILITY = "visibility"
private const val FOR_PROTOCOL_TESTS = "forProtocolTests"

// Prioritized list of protocols supported for code generation
private val DEFAULT_PROTOCOL_RESOLUTION_PRIORITY =
Expand All @@ -61,6 +63,8 @@ class SwiftSettings(
val swiftVersion: String,
val mergeModels: Boolean,
val copyrightNotice: String,
val visibility: String,
val forProtocolTests: Boolean,
) {
companion object {
private val LOGGER: Logger = Logger.getLogger(SwiftSettings::class.java.name)
Expand Down Expand Up @@ -90,6 +94,8 @@ class SwiftSettings(
SWIFT_VERSION,
MERGE_MODELS,
COPYRIGHT_NOTICE,
VISIBILITY,
FOR_PROTOCOL_TESTS,
),
)

Expand All @@ -113,6 +119,8 @@ class SwiftSettings(
COPYRIGHT_NOTICE,
"// Code generated by smithy-swift-codegen. DO NOT EDIT!\n\n",
)
val visibility = config.getStringMemberOrDefault(VISIBILITY, "public")
val forProtocolTests = config.getBooleanMemberOrDefault(FOR_PROTOCOL_TESTS, false)

return SwiftSettings(
serviceId,
Expand All @@ -126,6 +134,8 @@ class SwiftSettings(
swiftVersion,
mergeModels,
copyrightNotice,
visibility,
forProtocolTests,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class EndpointParamsGenerator(
writer: SwiftWriter,
endpointRuleSet: EndpointRuleSet?,
) {
writer.openBlock("public struct EndpointParams: Sendable {", "}") {
writer.openBlock("${ctx.settings.visibility} struct EndpointParams: Sendable {", "}") {
endpointRuleSet?.parameters?.toList()?.sortedBy { it.name.toString() }?.let { sortedParameters ->
renderMembers(writer, sortedParameters)
writer.write("")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@ class EndpointResolverGenerator(
val ruleSet = if (ruleSetNode != null) EndpointRuleSet.fromNode(ruleSetNode) else null

ctx.delegator.useFileWriter("Sources/${ctx.settings.moduleName}/Endpoints.swift") {
renderResolverProtocol(it)
renderResolverProtocol(it, ctx.settings.visibility)
it.write("")
renderResolver(it, ruleSet)
renderStaticResolver(it)
it.write("")
}
}

private fun renderResolverProtocol(writer: SwiftWriter) {
writer.openBlock("public protocol \$N {", "}", EndpointTypes.EndpointResolver) {
private fun renderResolverProtocol(
writer: SwiftWriter,
visibility: String,
) {
writer.openBlock("$visibility protocol \$N {", "}", EndpointTypes.EndpointResolver) {
writer.write("func resolve(params: EndpointParams) throws -> \$N", SmithyHTTPAPITypes.Endpoint)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ open class HttpProtocolServiceClient(

fun render(serviceSymbol: Symbol) {
writer.openBlock(
"public class \$L: \$N {",
"${ctx.settings.visibility} class \$L: \$N {",
"}",
serviceSymbol.name,
ClientRuntimeTypes.Core.Client,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ class SwiftSettingsTest {
swiftVersion = "5.7",
mergeModels = false,
copyrightNotice = "// Test copyright",
visibility = "public",
forProtocolTests = false,
)

private fun createServiceWithProtocols(protocols: Set<ShapeId>): ServiceShape {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,51 +47,61 @@ public struct DefaultExampleAuthSchemeResolver: ExampleAuthSchemeResolver {
}
switch serviceParams.operation {
case "onlyHttpApiKeyAuth":
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpApiKeyAuth"))
var httpApiKeyAuthOption = SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpApiKeyAuth")
validAuthOptions.append(httpApiKeyAuthOption)
case "onlyHttpApiKeyAuthOptional":
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpApiKeyAuth"))
var httpApiKeyAuthOption = SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpApiKeyAuth")
validAuthOptions.append(httpApiKeyAuthOption)
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#noAuth"))
case "onlyHttpBearerAuth":
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpBearerAuth"))
var httpBearerAuthOption = SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpBearerAuth")
validAuthOptions.append(httpBearerAuthOption)
case "onlyHttpBearerAuthOptional":
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpBearerAuth"))
var httpBearerAuthOption = SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpBearerAuth")
validAuthOptions.append(httpBearerAuthOption)
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#noAuth"))
case "onlyHttpApiKeyAndBearerAuth":
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpApiKeyAuth"))
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpBearerAuth"))
var httpApiKeyAuthOption = SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpApiKeyAuth")
validAuthOptions.append(httpApiKeyAuthOption)
var httpBearerAuthOption = SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpBearerAuth")
validAuthOptions.append(httpBearerAuthOption)
case "onlyHttpApiKeyAndBearerAuthReversed":
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpBearerAuth"))
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpApiKeyAuth"))
var httpBearerAuthOption = SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpBearerAuth")
validAuthOptions.append(httpBearerAuthOption)
var httpApiKeyAuthOption = SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#httpApiKeyAuth")
validAuthOptions.append(httpApiKeyAuthOption)
case "onlySigv4Auth":
var sigV4Option = SmithyHTTPAuthAPI.AuthOption(schemeID: "aws.auth#sigv4")
sigV4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingName, value: "weather")
var sigv4Option = SmithyHTTPAuthAPI.AuthOption(schemeID: "aws.auth#sigv4")
sigv4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingName, value: "weather")
guard let region = serviceParams.region else {
throw Smithy.ClientError.authError("Missing region in auth scheme parameters for SigV4 auth scheme.")
}
sigV4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingRegion, value: region)
validAuthOptions.append(sigV4Option)
sigv4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingRegion, value: region)
validAuthOptions.append(sigv4Option)
case "onlySigv4AuthOptional":
var sigV4Option = SmithyHTTPAuthAPI.AuthOption(schemeID: "aws.auth#sigv4")
sigV4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingName, value: "weather")
var sigv4Option = SmithyHTTPAuthAPI.AuthOption(schemeID: "aws.auth#sigv4")
sigv4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingName, value: "weather")
guard let region = serviceParams.region else {
throw Smithy.ClientError.authError("Missing region in auth scheme parameters for SigV4 auth scheme.")
}
sigV4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingRegion, value: region)
validAuthOptions.append(sigV4Option)
sigv4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingRegion, value: region)
validAuthOptions.append(sigv4Option)
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#noAuth"))
case "onlyCustomAuth":
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "com.test#customAuth"))
var customAuthOption = SmithyHTTPAuthAPI.AuthOption(schemeID: "com.test#customAuth")
validAuthOptions.append(customAuthOption)
case "onlyCustomAuthOptional":
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "com.test#customAuth"))
var customAuthOption = SmithyHTTPAuthAPI.AuthOption(schemeID: "com.test#customAuth")
validAuthOptions.append(customAuthOption)
validAuthOptions.append(SmithyHTTPAuthAPI.AuthOption(schemeID: "smithy.api#noAuth"))
default:
var sigV4Option = SmithyHTTPAuthAPI.AuthOption(schemeID: "aws.auth#sigv4")
sigV4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingName, value: "weather")
var sigv4Option = SmithyHTTPAuthAPI.AuthOption(schemeID: "aws.auth#sigv4")
sigv4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingName, value: "weather")
guard let region = serviceParams.region else {
throw Smithy.ClientError.authError("Missing region in auth scheme parameters for SigV4 auth scheme.")
}
sigV4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingRegion, value: region)
validAuthOptions.append(sigV4Option)
sigv4Option.signingProperties.set(key: SmithyHTTPAuthAPI.SigningPropertyKeys.signingRegion, value: region)
validAuthOptions.append(sigv4Option)
}
return self.reprioritizeAuthOptions(authSchemePreference: authSchemePreference, authOptions: validAuthOptions)
}
Expand Down
Loading