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

OpenSearch Serverless #72

Open
kellypleahy opened this issue Feb 23, 2023 · 2 comments
Open

OpenSearch Serverless #72

kellypleahy opened this issue Feb 23, 2023 · 2 comments

Comments

@kellypleahy
Copy link

Amazon has recently introduced a new serverless option for opensearch. In their infinite wisdom, they decided to change the service name for the purposes of signing HTTP requests from 'es' to 'aoss' for this new service, which means all clients break (fun)...

I forked this project in hopes of fixing the library, but it turns out I was able to replace it with very simple amount of code. You might want to see how much of this can be reused for 4.8.1 and just replace what you have (no more need for reflection, etc.).

I'll push a commit with this version to my fork, but I turned off building for 4.8.1 since I don't need that and don't have time right now to make it work and test.

(depends on the nuget packages AWSCRT and AWSCRT-HTTP, and was based on the code posted here: https://github.com/aws-samples/sigv4a-signing-examples/tree/main/dotnet)

Mostly just posting this here so it helps someone and they can take up the mantle if desired since I don't have the bandwidth myself at the moment and it took a while for me to get this working! Some differences from the link above - we needed a region in the signature, so you need to use the SIGV4 instead of SIGV4A as in the sample code.

    private class AwsConnection : HttpConnection
    {
        private readonly Credentials _credentials;
        private readonly string _serviceName;
        private readonly string _regionName;

        public AwsConnection(string endpointUri)
        {
            var creds = new EnvironmentVariablesAWSCredentials().GetCredentials();
            _credentials = new Credentials(creds.AccessKey, creds.SecretKey, creds.Token);
            var endpointParts = endpointUri.Split('.');
            _serviceName = endpointParts[^3];
            _regionName = endpointParts[^4];
        }

        protected override HttpRequestMessage CreateHttpRequestMessage(RequestData requestData)
        {
            var request = base.CreateHttpRequestMessage(requestData);
            _SignRequest(request);
            return request;
        }

        private void _SignRequest(HttpRequestMessage request)
        {
            if (request.RequestUri == null)
                throw new ArgumentException("Missing request URI on request object");
            var awsRequest = new HttpRequest
            {
                Method = request.Method.ToString(),
                Headers = new[] { new HttpHeader("host", request.RequestUri.Host) },
                Uri = request.RequestUri.PathAndQuery,
            };
            var awsSigningConfig = new AwsSigningConfig
            {
                Service = _serviceName,
                Region = _regionName,
                Algorithm = AwsSigningAlgorithm.SIGV4,
                SignatureType = AwsSignatureType.HTTP_REQUEST_VIA_HEADERS,
                SignedBodyHeader = AwsSignedBodyHeaderType.X_AMZ_CONTENT_SHA256,
                Credentials = _credentials,
            };

            var result = AwsSigner.SignHttpRequest(awsRequest, awsSigningConfig);
            var signingResult = result.Get();
            var signedRequest = signingResult.SignedRequest;
            foreach (var header in signedRequest.Headers)
            {
                if (request.Headers.Contains(header.Name))
                    request.Headers.Remove(header.Name);
                request.Headers.TryAddWithoutValidation(header.Name, header.Value);
            }
        }
    }

Also, FYI - I found out while looking into this that the "right" way to do what you had to do with reflection (your comments in the code) is to use a DelegatingHandler as your base class for your http message handler, and then just create is as a wrapper around the other handler. You create it and set InnerHandler to the inner handler (or use the protected constructor), then in your override of SendAsync you just call base.SendAsync when you want to trigger the inner handler. Not sure if this existed at the time you did the other work, or exists in 4.8.1, but it seemed like a nice way to avoid the reflection.

@bcuff
Copy link
Owner

bcuff commented Feb 27, 2023

Hey Kelly thanks for raising the issue. Any chance you can share your fork? No big deal if not. I have enough to go on but I might not get around to it for a little while.

Also - I'll look into that reflection bit. That always annoyed me and I never figured out a way around using reflection. Might not have been possible in the 4.8.1 version but I think we can deprecate support for .NET Framework at this point.

@kellypleahy
Copy link
Author

Hi Brandon,

It turns out I didn't end up doing the changes in the fork after all, as I was able to replace the whole library with a small class as above. That said, I've put the same code into the library and I think it will work as-is. I've pushed it as a wip commit on my fork so you can see the new code. Sorry for not cleaning up, I just have way too many things going on to give it the attention it deserves, so I figured I'd just share what I could with the time I have.

With the changes I made, you don't need the reflection or the other class (http handler) at all. Though just for your information, the fix to the http message handler to remove the reflection is there anyway (DelgatingHandler is the way to do that). That said, you don't need it anymore if you do everything in the connection object.

Kelly

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