swift-server / swift-aws-lambda-runtime

Swift implementation of AWS Lambda Runtime
Apache License 2.0
1.13k stars 102 forks source link

Support SSM ParameterStore / AWS Secret Manager #177

Closed DwayneCoussement closed 3 years ago

DwayneCoussement commented 3 years ago

Expected behavior

In node you can do the following:

const secretsManager = new AWS.SecretsManager();
    const secret = await secretsManager.getSecretValue({
        SecretId: event.db_secret_arn
    }).promise();

See also: https://chrisschuld.com/2020/07/aws-retrieving-secrets-in-parameter-store-with-node/

Actual behavior

Have a way to use the SecretsManager inside a Swift Lambda as well.

fabianfett commented 3 years ago

Hi @DwayneCoussement, thanks for bringing this up! Something like this is relatively easy possible with the Soto for AWS library.

The following code assumes that you are comfortable with Promises/Futures (Most node developers are 😉).


import SotoCore
import SotoSSM
import NIO
import AWSLambdaEvents
import AWSLambdaRuntime

class YourFancyLambdaHandler: EventLoopLambdaHandler {
    typealias In = YourEventIn
    typealias Out = YourEventOut

    let someSecret: String

    let httpClient: HTTPClient
    let awsClient: AWSClient

    enum StartUpError: Error {
        case couldNotFindStageEnvironmentVariable
        case couldNotReadSomeSecret
    }

    static func start(_ context: Lambda.InitializationContext) -> EventLoopFuture<ByteBufferLambdaHandler> {

        let httpClient = HTTPClient(eventLoopGroupProvider: .shared(context.eventLoop))
        let awsClient = AWSClient(httpClientProvider: .shared(httpClient))
        let ssm = SSM(client: awsClient, region: .eucentral1)

        return context.eventLoop.makeSucceededFuture(())
            .flatMapThrowing { _ -> String in
                guard let stage = Lambda.env("STAGE") else {
                     throw StartUpError.couldNotFindStageEnvironmentVariable
                }
                return stage
            }
            .flatMap { stage in
                return ssm.getParameters(.init(names: ["/service/\(stage)/your-secret"]))
            }
            .flatMapThrowing { (result) -> ByteBufferLambdaHandler in
                guard let value = result.parameters?.first?.value else {
                    throw StartUpError.couldNotReadSomeSecret
                }

                return YourFancyLambdaHandler(httpClient: httpClient, awsClient: awsClient, someSecret: value)
            }
    }

    func handle(context: Lambda.Context, event: YourEventIn) -> EventLoopFuture< YourEventOut> {
        // you can use your secret here. it's not even an optional
    }
}

Lambda.run(YourFancyLambdaHandler.start)

If you have any further questions please reach out.

DwayneCoussement commented 3 years ago

Hi @fabianfett,

First of all thanks for the quick response 🙏 Secondly I wanted to thank you for your great tutorials, they really helped me out a lot! I was aware of the Soto project, but never used it.

I do wonder if we shouldn't leverage this kind of functionality within this project, as we'd probably want to enable people to write them as secure as possible. Though I see as well a point that the AWS ecosystem is so big that there's plenty tons of alike features that can follow the same reasoning.

In any case, I'll take a look at Soto to figure this out, it seems pretty straightforward as well.

Cheers,

Dwayne