awslabs / aws-api-gateway-developer-portal

A Serverless Developer Portal for easily publishing and cataloging APIs
Apache License 2.0
929 stars 402 forks source link

Unable to Override Request Header "Host" to Support AWS API Gateway Private APIs : Access Denied #75

Closed MiguelGervassi closed 5 years ago

MiguelGervassi commented 6 years ago

Hi,

Issue Summary:

In order to support AWS API Gateway Private APIs, I need to invoke APIs using VPC endpoints. The method requests need to have VPC endpoint as URL and the request header host needs to have API ID of the Private API. Example using CURL: curl -v -X GET https://vpce-xxxxxxxxxxxxxxxxx-xxxxxxxx.execute-api.us-east-1.vpce.amazonaws.com/prod/catalog?end=undefined&start=undefined -H'Host:xxxxxxxxxx.execute-api.us-east-1.amazonaws.com'

apigClientModified.zip

Through code changes, I have tried overriding Request Header Host value pro-grammatically in apigClient.js file but request Header remains unchanged even after making the changes.

Are there any recommendations in how I override request header host value?

See Debugging information below.

Request Header seen on IE Developer Console Networking Tab:

Accept: / Accept-Encoding: gzip, deflate Access-Control-Request-Headers: accept, x-amz-date, authorization, x-amz-security-token Access-Control-Request-Method: GET Cache-Control: no-cache Connection: Keep-Alive Content-Length: 0 DNT: 1 Host: vpce-xxxxxxxxxxxxxxxxx-xxxxxxxx.execute-api.us-east-1.vpce.amazonaws.com Origin: https://xxxxxxxxxx.cloudfront.net User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko

File: apigClient.js

`/ eslint-disable / /*

var apigClientFactory = {}; apigClientFactory.newClient = function (config) { var apigClient = { }; if(config === undefined) { config = { accessKey: '', secretKey: '', sessionToken: '', region: '', apiKey: undefined, defaultContentType: 'application/json', defaultAcceptType: 'application/json' }; } if(config.accessKey === undefined) { config.accessKey = ''; } if(config.secretKey === undefined) { config.secretKey = ''; } if(config.apiKey === undefined) { config.apiKey = ''; } if(config.sessionToken === undefined) { config.sessionToken = ''; } if(config.region === undefined) { config.region = 'us-east-1'; } //If defaultContentType is not defined then default to application/json if(config.defaultContentType === undefined) { config.defaultContentType = 'application/json'; } //If defaultAcceptType is not defined then default to application/json if(config.defaultAcceptType === undefined) { config.defaultAcceptType = 'application/json'; }

// extract endpoint and path from url
var invokeUrl = 'https://vpce-xxxxxxxxxxxxxxxxx-xxxxxxxx.execute-api.us-east-1.vpce.amazonaws.com/prod';
var endpoint = /(^https?:\/\/[^\/]+)/g.exec(invokeUrl)[1];
var pathComponent = invokeUrl.substring(endpoint.length);

var sigV4ClientConfig = {
    accessKey: config.accessKey,
    secretKey: config.secretKey,
    sessionToken: config.sessionToken,
    serviceName: 'execute-api',
    region: config.region,
    endpoint: endpoint,
    defaultContentType: config.defaultContentType,
    defaultAcceptType: config.defaultAcceptType
};

var authType = 'NONE';
if (sigV4ClientConfig.accessKey !== undefined && sigV4ClientConfig.accessKey !== '' && sigV4ClientConfig.secretKey !== undefined && sigV4ClientConfig.secretKey !== '') {
    authType = 'AWS_IAM';
}

var simpleHttpClientConfig = {
    endpoint: endpoint,
    defaultContentType: config.defaultContentType,
    defaultAcceptType: config.defaultAcceptType
};

var apiGatewayClient = apiGateway.core.apiGatewayClientFactory.newClient(simpleHttpClientConfig, sigV4ClientConfig);

apigClient.rootOptions = function (params, body, additionalParams) {
    // if(additionalParams === undefined) { additionalParams = {}; }
    // params.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";

    params = {
        //This is where any header, path, or querystring request params go. The key is the parameter named as defined in the API
        Host: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"
    };
    // var body = {
    //     //This is where you define the body of the request
    // };
    additionalParams = {
        //If there are any unmodeled query parameters or headers that need to be sent with the request you can add them here
        headers: {
            Host: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"
        }
    };

    apiGateway.core.utils.assertParametersDefined(params, [], ['body']);

    var rootOptionsRequest = {
        verb: 'options'.toUpperCase(),
        path: pathComponent + uritemplate('/').expand(apiGateway.core.utils.parseParametersToObject(params, [])),
        headers: apiGateway.core.utils.parseParametersToObject(params, []),
        queryParams: apiGateway.core.utils.parseParametersToObject(params, []),
        body: body
    };
    rootOptionsRequest.headers.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";
    rootOptionsRequest.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";
    console.log("Root Options Request: " + JSON.stringify(rootOptionsRequest));
    console.log("Additional Params: " + JSON.stringify(additionalParams));
    return apiGatewayClient.makeRequest(rootOptionsRequest, authType, additionalParams, config.apiKey);
};

apigClient.get = function (path, params, body, additionalParams) {
  // if(additionalParams === undefined) { additionalParams = {}; }
  // params.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";

  params = {
      //This is where any header, path, or querystring request params go. The key is the parameter named as defined in the API
      Host: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"
  };
  // var body = {
  //     //This is where you define the body of the request
  // };
  additionalParams = {
      //If there are any unmodeled query parameters or headers that need to be sent with the request you can add them here
      headers: {
          Host: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"
      }
  };

    apiGateway.core.utils.assertParametersDefined(params, [], ['body']);

    var proxyOptionsRequest = {
        verb: 'GET',
        path: pathComponent + path,
        headers: apiGateway.core.utils.parseParametersToObject(params, []),
        queryParams: apiGateway.core.utils.parseParametersToObject(params, ['start', 'end']),
        body: body
    };
    proxyOptionsRequest.headers.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";
    proxyOptionsRequest.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";
    console.log("Proxy Options Request: " + JSON.stringify(proxyOptionsRequest));
    console.log("Additional Params: " + JSON.stringify(additionalParams));
    return apiGatewayClient.makeRequest(proxyOptionsRequest, authType, additionalParams, config.apiKey);
};

apigClient.post = function (path, params, body, additionalParams) {
  // if(additionalParams === undefined) { additionalParams = {}; }
  // params.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";

  params = {
      //This is where any header, path, or querystring request params go. The key is the parameter named as defined in the API
      Host: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"
  };
  // var body = {
  //     //This is where you define the body of the request
  // };
  additionalParams = {
      //If there are any unmodeled query parameters or headers that need to be sent with the request you can add them here
      headers: {
          Host: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"
      }
  };

    apiGateway.core.utils.assertParametersDefined(params, [], ['body']);

    var proxyOptionsRequest = {
        verb: 'POST',
        path: pathComponent + path,
        headers: apiGateway.core.utils.parseParametersToObject(params, []),
        queryParams: apiGateway.core.utils.parseParametersToObject(params, []),
        body: body
    };
    proxyOptionsRequest.headers.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";
    proxyOptionsRequest.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";
    console.log("Proxy Options Request: " + JSON.stringify(proxyOptionsRequest));
    console.log("Additional Params: " + JSON.stringify(additionalParams));
    return apiGatewayClient.makeRequest(proxyOptionsRequest, authType, additionalParams, config.apiKey);
};

apigClient.put = function (path, params, body, additionalParams) {
  // if(additionalParams === undefined) { additionalParams = {}; }
  // params.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";

  params = {
      //This is where any header, path, or querystring request params go. The key is the parameter named as defined in the API
      Host: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"
  };
  // var body = {
  //     //This is where you define the body of the request
  // };
  additionalParams = {
      //If there are any unmodeled query parameters or headers that need to be sent with the request you can add them here
      headers: {
          Host: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"
      }
  };

    apiGateway.core.utils.assertParametersDefined(params, [], ['body']);

    var proxyOptionsRequest = {
        verb: 'PUT',
        path: pathComponent + path,
        headers: apiGateway.core.utils.parseParametersToObject(params, []),
        queryParams: apiGateway.core.utils.parseParametersToObject(params, []),
        body: body
    };
    proxyOptionsRequest.headers.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";
    proxyOptionsRequest.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";
    console.log("Proxy Options Request: " + JSON.stringify(proxyOptionsRequest));
    console.log("Additional Params: " + JSON.stringify(additionalParams));
    return apiGatewayClient.makeRequest(proxyOptionsRequest, authType, additionalParams, config.apiKey);
};

apigClient.delete = function (path, params, body, additionalParams) {
  // if(additionalParams === undefined) { additionalParams = {}; }
  // params.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";

  params = {
      //This is where any header, path, or querystring request params go. The key is the parameter named as defined in the API
      Host: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"
  };
  // var body = {
  //     //This is where you define the body of the request
  // };
  additionalParams = {
      //If there are any unmodeled query parameters or headers that need to be sent with the request you can add them here
      headers: {
          Host: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"
      }
  };

    apiGateway.core.utils.assertParametersDefined(params, [], ['body']);

    var proxyOptionsRequest = {
        verb: 'DELETE',
        path: pathComponent + path,
        headers: apiGateway.core.utils.parseParametersToObject(params, []),
        queryParams: apiGateway.core.utils.parseParametersToObject(params, []),
        body: body
    };
    proxyOptionsRequest.headers.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";
    proxyOptionsRequest.Host = "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com";
    console.log("Proxy Options Request: " + JSON.stringify(proxyOptionsRequest));
    console.log("Additional Params: " + JSON.stringify(additionalParams));
    return apiGatewayClient.makeRequest(proxyOptionsRequest, authType, additionalParams, config.apiKey);
};

return apigClient;

};`

Thanks, Miguel Gervassi

MynockSpit commented 5 years ago

Yeah, as a security measure, browser's don't allow you to change the Host header.

You should be able to get set the x-apigw-api-id header and achieve the same result. (The comparison curl would be curl -v https://{vpce-id}.execute-api.{region}.vpce.amazonaws.com/test/get -H'x-apigw-api-id:{api-id}'.)

Here's an example for how you might change it for the get requests.

// apigClient.js

- let invokeUrl = `https://${window.config.restApiId}.execute-api.${window.config.region}.amazonaws.com/prod`,
+ let invokeUrl = 'https://vpce-xxxxxxxxxxxxxxxxx-xxxxxxxx.execute-api.${window.config.region}.vpce.amazonaws.com/prod';
    endpoint = /(^https?:\/\/[^\/]+)/g.exec(invokeUrl)[1],
    pathComponent = invokeUrl.substring(endpoint.length)
var invokeUrl = 'https://vpce-xxxxxxxxxxxxxxxxx-xxxxxxxx.execute-api.us-east-1.vpce.amazonaws.com/prod';

// ...

apigClient.get = function (path, params, body, additionalParams) {
    if(additionalParams === undefined) { additionalParams = {}; }

    apiGateway.core.utils.assertParametersDefined(params, [], ['body']);

+   // default params if it doesn't exist
+   if (!params) {
+       params = { headers: {} }
+   } 
+   // default headers if it doesn't exist
+   else if (typeof params === 'object' && !params.headers) {
+       params.headers = {}
+   }
+ 
+   // set the api id header
+   params.headers['x-apigw-api-id'] = window.config.restApiId

    var proxyOptionsRequest = {
        verb: 'GET',
        path: pathComponent + path,
        headers: apiGateway.core.utils.parseParametersToObject(params, []),
        queryParams: apiGateway.core.utils.parseParametersToObject(params, ['start', 'end']),
        body: body
    };

    return apiGatewayClient.makeRequest(proxyOptionsRequest, authType, additionalParams, config.apiKey);
};
MynockSpit commented 5 years ago

Closing this. Please reopen if you that doesn’t fix it and/or you’re still stuck!