All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.neptune.auth.NeptuneNettyHttpSigV4Signer Maven / Gradle / Ivy

Go to download

A library for Amazon Neptune that enables AWS Signature Version 4 signing for HTTP using Netty.

The newest version!
/*
 *   Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License").
 *   You may not use this file except in compliance with the License.
 *   A copy of the License is located at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   or in the "license" file accompanying this file. This file is distributed
 *   on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 *   express or implied. See the License for the specific language governing
 *   permissions and limitations under the License.
 */

package com.amazonaws.neptune.auth;

import com.amazonaws.auth.AWSCredentialsProvider;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.utils.StringUtils;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import org.apache.http.entity.StringEntity;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;

import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.AUTHORIZATION;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.HOST;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_DATE;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_SECURITY_TOKEN;

/**
 * Signer for HTTP requests made via Netty clients {@link FullHttpRequest}s.
 */
public class NeptuneNettyHttpSigV4Signer extends NeptuneSigV4SignerBase {

    /**
     * Create a V4 Signer for Netty HTTP requests.
     *
     * @param regionName             name of the region for which the request is signed
     * @param v1AwsCredentialProvider the provider offering access to the credentials used for signing the request
     * @throws NeptuneSigV4SignerException in case initialization fails
     */
    public NeptuneNettyHttpSigV4Signer(final String regionName,
                                       final AWSCredentialsProvider v1AwsCredentialProvider) throws NeptuneSigV4SignerException {
        super(regionName, v1AwsCredentialProvider);
    }

    /**
     * Create a V4 Signer for Netty HTTP requests.
     *
     * @param regionName             name of the region for which the request is signed
     * @param v1AwsCredentialProvider the provider offering access to the credentials used for signing the request
     * @param serviceName            name of the service name used to sign the requests. Defaults to neptune-db
     * @throws NeptuneSigV4SignerException in case initialization fails
     */
    public NeptuneNettyHttpSigV4Signer(final String regionName,
                                       final AWSCredentialsProvider v1AwsCredentialProvider,
                                       final String serviceName) throws NeptuneSigV4SignerException {
        super(regionName, v1AwsCredentialProvider, serviceName);
    }

    /**
     * Create a V4 Signer for Netty HTTP requests.
     *
     * @param regionName             name of the region for which the request is signed
     * @param awsCredentialsProvider the provider offering access to the credentials used for signing the request
     * @throws NeptuneSigV4SignerException in case initialization fails
     */
    public NeptuneNettyHttpSigV4Signer(final String regionName,
                                       final AwsCredentialsProvider awsCredentialsProvider) throws NeptuneSigV4SignerException {
        super(regionName, awsCredentialsProvider);
    }

    /**
     * Create a V4 Signer for Netty HTTP requests.
     *
     * @param regionName             name of the region for which the request is signed
     * @param awsCredentialsProvider the provider offering access to the credentials used for signing the request
     * @param serviceName            name of the service name used to sign the requests. Defaults to neptune-db
     * @throws NeptuneSigV4SignerException in case initialization fails
     */
    public NeptuneNettyHttpSigV4Signer(final String regionName,
                                       final AwsCredentialsProvider awsCredentialsProvider,
                                       final String serviceName) throws NeptuneSigV4SignerException {
        super(regionName, awsCredentialsProvider, serviceName);
    }

    @Override
    protected SdkHttpFullRequest toSignableRequest(final FullHttpRequest request)
            throws NeptuneSigV4SignerException {

        // make sure the request is not null and contains the minimal required set of information
        checkNotNull(request, "The request must not be null");
        checkNotNull(request.uri(), "The request URI must not be null");
        checkNotNull(request.method(), "The request method must not be null");

        // convert the headers to the internal API format
        final HttpHeaders headers = request.headers();
        final Map> headersInternal = new HashMap<>();

        String hostName = "";

        // we don't want to add the Host header as the Signer always adds the host header.
        for (String header : headers.names()) {
            // Skip adding the Host header as the signing process will add one.
            if (!header.equalsIgnoreCase(HOST)) {
                headersInternal.put(header, Arrays.asList(headers.get(header)));
            } else {
                hostName = headers.get(header);
            }
        }

        // convert the parameters to the internal API format
        final URI uri = URI.create(request.uri());

        final String queryStr = uri.getQuery();
        final Map> parametersInternal = new HashMap<>(extractParametersFromQueryString(queryStr));

        // carry over the entity (or an empty entity, if no entity is provided)
        final InputStream content;
        final ByteBuf contentBuffer = request.content();
        boolean hasContent = false;
        try {
            if (contentBuffer != null && contentBuffer.isReadable()) {
                hasContent = true;
                contentBuffer.retain();
                byte[] bytes = new byte[contentBuffer.readableBytes()];
                contentBuffer.getBytes(contentBuffer.readerIndex(), bytes);
                content = new ByteArrayInputStream(bytes);
            } else {
                content = new StringEntity("").getContent();
            }
        } catch (UnsupportedEncodingException e) {
            throw new NeptuneSigV4SignerException("Encoding of the input string failed", e);
        } catch (IOException e) {
            throw new NeptuneSigV4SignerException("IOException while accessing entity content", e);
        } finally {
            if (hasContent) {
                contentBuffer.release();
            }
        }

        if (StringUtils.isEmpty(hostName)) {
            // try to extract hostname from the uri since hostname was not provided in the header.
            final String authority = uri.getAuthority();
            if (authority == null) {
                throw new NeptuneSigV4SignerException("Unable to identify host information,"
                        + " either hostname should be provided in the uri or should be passed as a header");
            }

            hostName = authority;
        }

        // Gremlin websocket requests don't contain protocol information. Here, http:// doesn't have any consequence
        // other than letting the signer work with a full valid uri. The protocol is not used anywhere in signing.
        final URI endpointUri = URI.create("http://" + hostName);

        return convertToSignableRequest(
                request.method().name(),
                endpointUri,
                uri.getPath(),
                headersInternal,
                parametersInternal,
                content);
    }

    @Override
    protected void attachSignature(final FullHttpRequest request, final NeptuneSigV4Signature signature)
            throws NeptuneSigV4SignerException {

        // make sure the request is not null and contains the minimal required set of information
        checkNotNull(signature, "The signature must not be null");
        checkNotNull(signature.getHostHeader(), "The signed Host header must not be null");
        checkNotNull(signature.getXAmzDateHeader(), "The signed X-AMZ-DATE header must not be null");
        checkNotNull(signature.getAuthorizationHeader(), "The signed Authorization header must not be null");

        final HttpHeaders headers = request.headers();
        Optional hostHeaderName = Optional.empty();
        // Check if host header is present in the request headers.
        for (String name: headers.names()) {
            if (name.equalsIgnoreCase(HOST)) {
                hostHeaderName = Optional.of(name);
                break;
            }
        }

        // Remove the host header from the request as we are going to add the host header from the signed request.
        // This also ensures that the right header name is used.
        hostHeaderName.ifPresent(name -> headers.remove(name));
        request.headers().add(HOST, signature.getHostHeader());
        request.headers().add(X_AMZ_DATE, signature.getXAmzDateHeader());
        request.headers().add(AUTHORIZATION, signature.getAuthorizationHeader());

        // https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
        // For temporary security credentials, it requires an additional HTTP header
        // or query string parameter for the security token. The name of the header
        // or query string parameter is X-Amz-Security-Token, and the value is the session token.
        if (!signature.getSessionToken().isEmpty()) {
            request.headers().add(X_AMZ_SECURITY_TOKEN, signature.getSessionToken());
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy