ex-aws / ex_aws

A flexible, easy to use set of clients AWS APIs for Elixir
https://hex.pm/packages/ex_aws
MIT License
1.29k stars 527 forks source link

Creating a new service: "Credential should be scoped to correct service" #594

Closed dideler closed 6 years ago

dideler commented 6 years ago

Environment

Elixir 1.6.2 (compiled with OTP 20)


* ExAws version `mix deps |grep ex_aws`

Current behavior

I'm writing a service for SageMaker Runtime

defmodule ExAws.SageMakerRuntime do
  @actions %{invoke_endpoint: :post}

  @doc """
  https://docs.aws.amazon.com/sagemaker/latest/dg/API_runtime_InvokeEndpoint.html
  """
  def invoke_endpoint(endpoint_name, body) do
    request(:invoke_endpoint, body, "/endpoints/#{endpoint_name}/invocations")
  end

  defp request(action, data, path, params \\ [], headers \\ [], before_request \\ nil) do
    path = [path, "?", URI.encode_query(params)] |> IO.iodata_to_binary()
    http_method = Map.fetch!(@actions, action)

    ExAws.Operation.JSON.new(:"runtime.sagemaker", %{
      http_method: http_method,
      path: path,
      data: data,
      headers: [{"content-type", "application/json"} | headers],
      before_request: before_request
    })
  end
end

Configured as

config :ex_aws,
  debug_requests: true,
  host: "runtime.sagemaker.fake-region.amazonaws.com",
  region: "fake-region"

The request returns a 403 "Credential should be scoped to correct service: 'sagemaker'"

iex> ExAws.SageMakerRuntime.invoke_endpoint("my-endpoint", %{foo: "bar"}) |> ExAws.request()
# Request URL: "https://runtime.sagemaker.fake-region.amazonaws.com/endpoints/my-endpoint/invocations"
# Request HEADERS: [{"Authorization", "redacted"}, {"host", "runtime.sagemaker.fake-region.amazonaws.com"}, {"x-amz-date", "20181031T123525Z"}, {"x-amz-content-sha256", ""}, {"content-type", "application/json"}]
# Request BODY: "{\"foo\":\"bar\"}"
{:error,
 {:http_error, 403,
  %{
    body: "{\"message\":\"Credential should be scoped to correct service: 'sagemaker'. \"}",
    headers: [
      {"x-amzn-RequestId", "563155ad-ab44-40ac-8fd8-318a2b2f6f50"},
      {"x-amzn-ErrorType",
       "InvalidSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/"},
      {"Date", "Wed, 31 Oct 2018 12:35:25 GMT"},
      {"Content-Type", "application/json"},
      {"Content-Length", "75"}
    ],
    status_code: 403
  }}}

Expected behavior

The signed request returns a 200.

Credentials don't seem to be the problem as it works when using AWS CLI

aws --region 'fake-region' \
    sagemaker-runtime invoke-endpoint \
    --content-type 'application/json' \
    --endpoint-name 'my-endpoint' \
    --body '{"foo": "bar"}' \
    outfile

Found a similar issue, but it goes over my head: https://github.com/aws/aws-sdk-go/issues/1836

dideler commented 6 years ago

Running the AWS CLI command above with --debug shows a different authorisation value.

Elixir service

Credential=HASH/DATE/REGION/runtime.sagemaker/aws4_request

AWS CLI

Credential=HASH/DATE/REGION/sagemaker/aws4_request
dideler commented 6 years ago

Note that the service name in ExAws.SageMakerRuntime is :"runtime.sagemaker" instead of :sagemaker, as SageMaker Runtime is a separate service from SageMaker.

References that treat SageMaker and SageMaker Runtime as separate services

I tried with :sagemaker as the service name but then it makes the request to the wrong service, and returns an UnknownOperationException.

There's at least one other service with this condition: lex vs runtime.lex

dideler commented 6 years ago

We can set a default credential scope for the runtime.sagemaker service in priv/endpoints.ex.

        "runtime.sagemaker" => %{
+         "defaults" => %{"credentialScope" => %{"service" => "sagemaker"}},
          "endpoints" => %{
            "ap-northeast-1" => %{},
            "eu-west-1" => %{},
            "us-east-1" => %{},
            "us-east-2" => %{},
            "us-west-2" => %{}
          }
        },

I noticed some of the other services in this file do the same. But the value is currently being ignored.

I'll open some PRs to update runtime.sagemaker and for auth to consider a default credentialScope.