Skip to content
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

Lambda Caching Layer #87

Open
brignolij opened this issue Jun 1, 2023 · 4 comments
Open

Lambda Caching Layer #87

brignolij opened this issue Jun 1, 2023 · 4 comments

Comments

@brignolij
Copy link

Hello,

I'm using AWSSecretsManagerConfigurationExtensions with api running on Lambda. But i'm now facing the issue where the GetSecretValue limit of 10k read/seconds is reached.

I saw AWS provide a cache layer for secret and parameter that can be dirrectly link to a lambda function :
https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html

For example you can add this layer to your function with cloudformation like this :

      Layers:
        - arn:aws:lambda:eu-central-1:187925254637:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:4

Actually AWSSecretsManagerConfigurationExtensions is reading secret at each application start. in the case of .net api running on lambda, each invocations is reading secret.

Can we imagine an implementation of the cache layer of lambda ?

Regards

@Kralizek
Copy link
Owner

Kralizek commented Jun 1, 2023

I will have to think at the best way to support this scenario.

@brignolij
Copy link
Author

brignolij commented Jun 1, 2023

@Kralizek Hi again,

I spend time on this topic.

Using your Extension, we can "use" a custom implementation of GetSecret to call manually the secret layer.

I did it, but the layer seems not working very well.
I'm getting error 4xx, not read to serve traffic.

But here how i did it :

public class SecretManagerAdvanced : AmazonSecretsManagerClient
{
    private readonly ISecretsProvider _secretsProvider;
    public SecretManagerAdvanced(AmazonSecretsManagerConfig config) : base(config)
    {
        _secretsProvider = new SecretsProvider();
    }

    public SecretManagerAdvanced(AWSCredentials credentials, AmazonSecretsManagerConfig config) : base(credentials, config)
    {
        _secretsProvider = new SecretsProvider();
    }


    public override async Task<GetSecretValueResponse> GetSecretValueAsync(GetSecretValueRequest request,
        CancellationToken cancellationToken = new CancellationToken())
    {

        var result = _secretsProvider.GetSecretAsync(request.SecretId);

        if (result.Result is not null)
        {
            var response = new GetSecretValueResponse();
            response.Name = request.SecretId;
            response.SecretString = result.Result;
            return response;
        }

        return await base.GetSecretValueAsync(request, cancellationToken);
    }
}
public static class SecretMapper{
///
 private static IAmazonSecretsManager CreateClient()
    {
        var options = new SecretsManagerConfigurationProviderOptions();
        var region = RegionEndpoint.EUCentral1;

       // AWSCredentials? Credentials;
        
        if (options.CreateClient != null)
        {
            return options.CreateClient();
        }

        var clientConfig = new AmazonSecretsManagerConfig
        {
            RegionEndpoint = region
        };

        options.ConfigureSecretsManagerConfig(clientConfig);
            
        return Credentials switch
        {
            null => new SecretManagerAdvanced(clientConfig),
            _ => new SecretManagerAdvanced(Credentials, clientConfig)
        };
    }
}

internal interface ISecretsProvider
{
    Task<string?> GetSecretAsync(string secretName);
}

class SecretsProvider : ISecretsProvider
{
    private readonly HttpClient _httpClient;
    private readonly string _token;

    private readonly string GetSecretsEndpoint = "/secretsmanager/get?secretId=";

    public SecretsProvider()
    {
        _httpClient = new HttpClient() { BaseAddress = new Uri("http://localhost:2773") };
        _httpClient.DefaultRequestHeaders.Add("X-AWS-Parameters-Secrets-Token", Environment.GetEnvironmentVariable("AWS_SESSION_TOKEN"));
    }

    public async Task<string?> GetSecretAsync(string secretName)
    {
        var httpRequest = new HttpRequestMessage(
            HttpMethod.Get, 
            new Uri($"{GetSecretsEndpoint}{secretName}", UriKind.Relative));

        var response = await _httpClient
            .SendAsync(httpRequest)
            .ConfigureAwait(false);
        

        var responseAsJsonString = await response.Content
            .ReadAsStringAsync()
            .ConfigureAwait(false);

        if (response.IsSuccessStatusCode)
        {
            return responseAsJsonString;
        }
        return null;
    }
}

Did you already use lambda secret layer and make it work ?

Regards

@Kralizek
Copy link
Owner

Kralizek commented Jun 1, 2023

No I've never used the layer tbh

@brignolij
Copy link
Author

brignolij commented Jun 1, 2023

@Kralizek according this :
https://stackoverflow.com/questions/75624681/aws-secrets-and-parameters-lambda-extension-throw-not-ready-to-serve-traffic

the layer will be ready when the application has already started.

Is there a way to use your Extension after the application start ?

EDIT: I confirm, when calling HTTP GetSecret after application start, for example in a controller, the layer return the secret effectively.

For information, here how it works in lambda:

  1. LAMBDA INIT START is done
  2. dotnet app is started
  3. cache layer is ready
  4. LAMBDA START is done
  5. the http request is treated by dotnet app and return a response
  6. LAMBDA END is done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants