Unverified Commit cf55bcab authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Add SigningService & SigningConfig during operation construction (#202)

* Add SigningService & SigningConfig during operation construction

This commit adds:
- SigV4SigningFeature during operation construction
- A `signing_service()` function to config objects to define a default signing service

These are both gated on the `aws.auth#sigV4` trait being present on the service

* Fix sig-auth missed changes

* Fix bad import
parent 71c97a13
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -81,7 +81,7 @@ fn signing_config(
            .get::<SystemTime>()
            .copied()
            .unwrap_or_else(SystemTime::now),
        region: region.into(),
        region,
        service: signing_service,
    };
    Ok((operation_config, request_config, creds))
+0 −8
Original line number Diff line number Diff line
@@ -122,11 +122,3 @@ impl SigV4Signer {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -10,7 +10,8 @@ import software.amazon.smithy.rust.codegen.smithy.customize.CombinedCodegenDecor
val DECORATORS = listOf(
    CredentialsProviderDecorator(),
    RegionDecorator(),
    AwsEndpointDecorator()
    AwsEndpointDecorator(),
    SigV4SigningDecorator()
)

class AwsCodegenDecorator : CombinedCodegenDecorator(DECORATORS) {
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

package software.amazon.smithy.rustsdk

import software.amazon.smithy.aws.traits.auth.SigV4Trait
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.rustlang.Local
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.asType
import software.amazon.smithy.rust.codegen.rustlang.rust
import software.amazon.smithy.rust.codegen.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.rustlang.writable
import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator
import software.amazon.smithy.rust.codegen.smithy.generators.OperationCustomization
import software.amazon.smithy.rust.codegen.smithy.generators.OperationSection
import software.amazon.smithy.rust.codegen.smithy.generators.ProtocolConfig
import software.amazon.smithy.rust.codegen.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.smithy.generators.config.ServiceConfig
import software.amazon.smithy.rust.codegen.smithy.letIf
import software.amazon.smithy.rust.codegen.util.dq

/**
 * The SigV4SigningDecorator:
 * - adds a `signing_service()` method to `config` to return the default signing service
 * - sets the `SigningService` during operation construction
 * - sets a default `OperationSigningConfig` A future enhancement will customize this for specific services that need
 *   different behavior.
 */
class SigV4SigningDecorator : RustCodegenDecorator {
    override val name: String = "SigV4Signing"
    override val order: Byte = 0

    private fun applies(protocolConfig: ProtocolConfig): Boolean = protocolConfig.serviceShape.hasTrait(SigV4Trait::class.java)

    override fun configCustomizations(
        protocolConfig: ProtocolConfig,
        baseCustomizations: List<ConfigCustomization>
    ): List<ConfigCustomization> {
        return baseCustomizations.letIf(applies(protocolConfig)) {
            it + SigV4SigningConfig(protocolConfig.serviceShape.expectTrait(SigV4Trait::class.java))
        }
    }

    override fun operationCustomizations(
        protocolConfig: ProtocolConfig,
        operation: OperationShape,
        baseCustomizations: List<OperationCustomization>
    ): List<OperationCustomization> {
        return baseCustomizations.letIf(applies(protocolConfig)) {
            it + SigV4SigningFeature(protocolConfig.runtimeConfig)
        }
    }
}

class SigV4SigningConfig(private val sigV4Trait: SigV4Trait) : ConfigCustomization() {
    override fun section(section: ServiceConfig): Writable {
        return when (section) {
            is ServiceConfig.ConfigImpl -> writable {
                rust(
                    """
                    /// The signature version 4 service signing name to use in the credential scope when signing requests.
                    ///
                    /// The signing service may be overidden by the `Endpoint`, or by specifying a custom [`SigningService`](aws_types::SigningService) during
                    /// operation construction
                    pub fn signing_service(&self) -> &'static str {
                        ${sigV4Trait.name.dq()}
                    }
                    """
                )
            }
            else -> emptySection
        }
    }
}

class SigV4SigningFeature(private val runtimeConfig: RuntimeConfig) :
    OperationCustomization() {
    override fun section(section: OperationSection): Writable {
        return when (section) {
            is OperationSection.Feature -> writable {
                // TODO: this needs to be customized for individual operations, not just `default_config()`
                rustTemplate(
                    """
                ${section.request}.config_mut().insert(
                    #{sig_auth}::signer::OperationSigningConfig::default_config()
                );
                ${section.request}.config_mut().insert(#{aws_types}::SigningService::from_static(${section.config}.signing_service()));
                """,
                    "sig_auth" to runtimeConfig.sigAuth().asType(),
                    "aws_types" to awsTypes(runtimeConfig).asType()
                )
            }
            else -> emptySection
        }
    }
}

fun RuntimeConfig.sigAuth() = CargoDependency("aws-sig-auth", Local(this.relativePath))
+28 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

package software.amazon.smithy.rustsdk

import org.junit.jupiter.api.Test
import software.amazon.smithy.aws.traits.auth.SigV4Trait
import software.amazon.smithy.rust.codegen.testutil.compileAndTest
import software.amazon.smithy.rust.codegen.testutil.stubConfigProject
import software.amazon.smithy.rust.codegen.testutil.unitTest

internal class SigV4SigningCustomizationTest {
    @Test
    fun `generates a valid config`() {
        val project = stubConfigProject(SigV4SigningConfig(SigV4Trait.builder().name("test-service").build()))
        project.useFileWriter("src/lib.rs", "crate") {
            it.unitTest(
                """
            let conf = crate::config::Config::builder().build();
            assert_eq!(conf.signing_service(), "test-service");
            """
            )
        }
        project.compileAndTest()
    }
}