リクエストの署名

このトピックでは、Oracle Cloud Infrastructure APIリクエストに署名する方法について説明します。

次の署名サンプルが含まれています:

署名バージョン1

ここで説明する署名は、Oracle Cloud Infrastructure API署名のバージョン1です。今後、Oracleのリクエストの署名方法が変更された場合は、バージョン番号が増分され、顧客に通知されます。

必要な資格証明とOCID

正しいフォーマットのAPI署名キーが必要です。必要なキーとOCIDを参照してください。

注意

クライアント・クロック・スキュー

クライアントのクロックの誤差が5分を超えている場合、401 (NotAuthenticated) HTTPステータス・コードが返されます。これはAPIリクエストに影響します。詳細は、クライアント・クロック・スキューの最大許容値を参照してください。

また、テナンシとユーザーのOCIDが必要です。テナンシのOCIDとユーザーのOCIDを取得する場所を参照してください。

署名ステップのサマリー

通常、リクエストに署名するためには次のステップが必要です:

  1. HTTPSリクエストを形成します(SSLプロトコルTLS 1.2が必要です)。
  2. リクエストの一部に基づいて署名文字列を作成します。
  3. 秘密キーとRSA-SHA256アルゴリズムを使用して、署名文字列から署名を作成します。
  4. 作成した署名とその他の必須情報をリクエストのAuthorizationヘッダーに追加します。

これらのステップの詳細は、このトピックの残りの各項を参照してください。

理解する必要がある仕様

前述のプロセスのステップ2から4を実行する方法を学習するには、draft-cavage-http-signatures-08を参照してください。これは、Oracleでのリクエスト署名の処理方法の基礎となるドラフト仕様です。署名文字列の作成方法、署名の作成方法、および署名と必須情報をリクエストに追加する方法について一般的に説明しています。このトピックのこの後の項では、その内容を理解していることを想定しています。このリファレンスのOracle Cloud Infrastructure実装について、重要な詳細事項を次の項で説明します。

特別な実装の詳細

次の項では、仕様のOracle Cloud Infrastructure実装について注意する必要がある重要な項目について説明します。

Authorizationヘッダー

Oracle Cloud Infrastructureの署名では、Signature HTTPヘッダーではなく、Signature認証スキーム(Authorizationヘッダー)を使用します。

必要なヘッダー

この項では、署名文字列に含める必要があるヘッダーについて説明します。

ノート

必要なヘッダーがない場合はエラー

必要なヘッダーがない場合、クライアントは401「未認証」レスポンスを受け取ります。

GETおよびDELETEリクエスト(リクエスト本文にコンテンツがない)では、署名文字列には少なくとも次のヘッダーを含める必要があります:

PUTおよびPOSTリクエスト(リクエスト本文にコンテンツがある)では、署名文字列には少なくとも次のヘッダーを含める必要があります:

  • (request-target)
  • host
  • dateまたはx-date (両方が含まれる場合、Oracleではx-dateが使用)
  • x-content-sha256 (オブジェクト・ストレージのPUTリクエスト以外。次の項を参照)
  • content-type
  • content-length
注意

PUTおよびPOSTリクエストでは、本文が空の文字列であっても、クライアントがx-content-sha256をコンピュートして、それをリクエストと署名文字列に含める必要があります。また、本文が空の場合でも、content-lengthは常にリクエストと署名文字列で必要です。一部のHTTPクライアントでは、本文が空の場合にはcontent-lengthが送信されないため、クライアントが送信するように明示的に確認する必要があります。datex-dateが両方含まれている場合、Oracleではx-dateが使用されます。x-dateが使用されるのは、リクエストの署名部分の再利用(リプレイ攻撃)を防ぐためです。

1つの例外は、オブジェクトに対するオブジェクト・ストレージのPUTリクエストです(次の項を参照)。

オブジェクト・ストレージのPUTに関する特別な指示

オブジェクト・ストレージPutObjectおよびUploadPartのPUTリクエストの場合、署名文字列には少なくとも次のヘッダーを含める必要があります:

  • (request-target)
  • host
  • dateまたはx-date (両方が含まれる場合、Oracleではx-dateが使用)

PUTリクエストで通常必要なその他のヘッダー(前述のリストを参照)もリクエストに含まれる場合は、それらのヘッダーも署名文字列に含まれている必要があります。

ヘッダーの大/小文字の区別および順序

ヘッダーは、署名文字列ではすべて小文字にする必要があります。

署名文字列内でのヘッダーの順序は関係ありません。ただし、draft-cavage-http-signatures-05の説明に従って、Authorizationヘッダーのheadersパラメータには必ず順序を指定してください。

注意

(request-target)には、リクエストのパスと問合せ文字列が含まれます。リクエストに出現するのと同じ順序、問合せパラメータを使用して署名文字列を作成するとことが想定されています。署名が行われた後で、リクエストの問合せパラメータの順序が変わると、認証が失敗します。

パスと問合せ文字列のURLエンコーディング

署名文字列を構成する際には、RFC 3986に従って、パスと問合せ文字列(ヘッダーではない)内のすべてのパラメータをURLエンコーディングする必要があります。

キー識別子

リクエストに追加するAuthorizationヘッダーに、keyId="<TENANCY OCID>/<USER OCID>/<KEY FINGERPRINT>"を設定する必要があります。これらの値を取得するには、テナンシのOCIDとユーザーのOCIDを取得する場所を参照してください。keyIdの例を次に示します(ページ幅に合うように改行されています):

ocid1.tenancy.oc1..<unique_ID>/ocid1.user.oc1..<unique_ID>/<key_fingerprint>

署名アルゴリズム

署名アルゴリズムはRSA-SHA256である必要があり、Authorizationヘッダーにalgorithm="rsa-sha256"を設定する必要があります(引用符に注意してください)。

署名バージョン

Authorizationヘッダーにversion="1"を含める必要があります(引用符に注意してください)。そうしないと、現在のバージョン(現時点ではバージョン1)を使用しているとみなされます。

ヘッダーの例

次に、Authorizationヘッダー(本文にコンテンツが含まれるリクエストの場合)の一般的な構文の例を示します:

Authorization: Signature version="1",keyId="<tenancy_ocid>/<user_ocid>/<key_fingerprint>",algorithm="rsa-sha256",headers="(request-target) date x-content-sha256 content-type content-length",signature="Base64(RSA-SHA256(<signing_string>))"

テスト値

次に、キー・ペアの例、2つのリクエストの例、およびそれぞれに対応するAuthorizationヘッダーを示します。

注意

次の署名例ではRSA 2048ビット・キーが使用されています。これらのキーは署名コードのテストにのみ使用し、本番リクエストの送信には使用しないでください。

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
oYi+1hqp1fIekaxsyQIDAQAB
-----END PUBLIC KEY-----
						
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
-----END RSA PRIVATE KEY-----


The public key is stored under keyId:

ocid1.tenancy.oc1..<unique_ID>/ocid1.user.oc1..<unique_ID>/<key_fingerprint>



For the following GET request (line breaks inserted between query parameters for easier reading; also notice the URL encoding as mentioned earlier):
			
GET https://iaas.us-phoenix-1.oraclecloud.com/20160918/instances
?availabilityDomain=Pjwf%3A%20PHX-AD-1
&compartmentId=ocid1.compartment.oc1...<unique_ID>
&displayName=TeamXInstances
&volumeId=ocid1.volume.oc1.phx.<unique_ID>
Date: Thu, 05 Jan 2014 21:31:40 GMT
			
The signing string would be (line breaks inserted into the (request-target) header for easier reading):
			
date: Thu, 05 Jan 2014 21:31:40 GMT
(request-target): get /20160918/instances?availabilityDomain=Pjwf%3A%20PH
X-AD-1&compartmentId=ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2i
dnccdflvjsnog7mlr6rtdb25gilchfeyjxa&displayName=TeamXInstances&
volumeId=ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h
4lgvyndsdsnoiwr5q
host: iaas.us-phoenix-1.oraclecloud.com

The Authorization header would be:
Signature version="1",headers="date (request-target) host",keyId="ocid1.t
enancy.oc1..<unique_ID>/ocid1.user.oc1..<unique_ID>/<key_fingerprint>,algorithm="rsa-sha256
",signature="<your_signature>"

For the following POST request:
			
POST https://iaas.us-phoenix-1.oraclecloud.com/20160918/volumeAttachments
Date: Thu, 05 Jan 2014 21:31:40 GMT
{
   "compartmentId": "ocid1.compartment.oc1..<unique_id>",
   "instanceId": "ocid1.instance.oc1.phx.<unique_id>",
   "volumeId": "ocid1.volume.oc1.phx.<unique_id>"
}

The signing string would be:
			
date: Thu, 05 Jan 2014 21:31:40 GMT
(request-target): post /20160918/volumeAttachments
host: iaas.us-phoenix-1.oraclecloud.com
content-length: 316
content-type: application/json
x-content-sha256: V9Z20UJTvkvpJ50flBzKE32+6m2zJjweHpDMX/U4Uy0=
			
The Authorization header would be:	

Signature version="1",headers="date (request-target) host content-length c
ontent-type x-content-sha256",
keyId="ocid1.tenancy.oc1..<unique_id>/ocid1.user.oc1.<unique_id>/<your_fingerprint>",
algorithm="rsa-sha256",signature="<your_signature>"

サンプル・コード

この項では、APIリクエストの署名の基本的なコードを示します。

Java

/**
* Copyright (c) 2016, 2020, Oracle and/or its affiliates.  All rights reserved.
* This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl 
* or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
*/
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;

import com.google.common.net.UrlEscapers;

import com.oracle.bmc.http.signing.RequestSigningFilter;

public class RawRestCallExample {

public static void main(String[] args) throws Exception {
// TODO: fill this out
String instanceId = null;

String configurationFilePath = "~/.oci/config";
String profile = "DEFAULT";

// Pre-Requirement: Allow setting of restricted headers. This is required to allow the SigningFilter
// to set the host header that gets computed during signing of the request.
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");

// 1) Create a request signing filter instance
RequestSigningFilter requestSigningFilter =
RequestSigningFilter.fromConfigFile(configurationFilePath, profile);

// 2) Create a Jersey client and register the request signing filter
Client client = ClientBuilder.newBuilder().build().register(requestSigningFilter);

// 3) Target an endpoint. You must ensure that path arguments and query
// params are escaped correctly yourself
WebTarget target =
client.target("https://iaas.us-phoenix-1.oraclecloud.com")
.path("20160918")
.path("instances")
.path(UrlEscapers.urlPathSegmentEscaper().escape(instanceId));

// 4) Set the expected type and invoke the call
Invocation.Builder ib = target.request();
ib.accept(MediaType.APPLICATION_JSON);
Response response = ib.get();

// 5) Print the response headers and the body (JSON) as a string
MultivaluedMap<String, Object> responseHeaders = response.getHeaders();
System.out.println(responseHeaders);
InputStream responseBody = (InputStream) response.getEntity();
try (final BufferedReader reader =
new BufferedReader(new InputStreamReader(responseBody, StandardCharsets.UTF_8))) {
StringBuilder jsonBody = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
jsonBody.append(line);
}
System.out.println(jsonBody.toString());
}
}
		}

Python

重要

このPythonサンプル・コードではTLS 1.2が必要ですが、Mac OS XのデフォルトのPythonには含まれていません。
# coding: utf-8
# Copyright (c) 2016, 2020, Oracle and/or its affiliates.  All rights reserved.
# This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl 
# or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.

import requests
from oci.config import from_file
from oci.signer import Signer

config = from_file()
auth = Signer(
tenancy=config['tenancy'],
user=config['user'],
fingerprint=config['fingerprint'],
private_key_file_location=config['key_file'],
pass_phrase=config['pass_phrase']
)

endpoint = 'https://identity.us-phoenix-1.oraclecloud.com/20160918/users/'

body = {
'compartmentId': config['tenancy'],  # root compartment
'name': 'TestUser',
'description': 'Created with a raw request'
}

response = requests.post(endpoint, json=body, auth=auth)
response.raise_for_status()

print(response.json()['id'])

TypeScript

/**
 * Copyright (c) 2020, Oracle and/or its affiliates.  All rights reserved.
 * This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl 
 * or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
 */

import { DefaultRequestSigner, HttpRequest } from "oci-common";
import { provider } from "./authentication";
import * as promise from "es6-promise";
import "isomorphic-fetch";
promise.polyfill();

const userID = "Add User OCID here";
(async () => {
  // 1. Create Request Signing instance
  const signer = new DefaultRequestSigner(provider);

  // 2. Create HttpRequest to be signed
  const httpRequest: HttpRequest = {
    uri: `https://identity.us-phoenix-1.oraclecloud.com/20160918/users/${userID}`,
    headers: new Headers(),
    method: "GET"
  };

  // 3. sign request
  await signer.signHttpRequest(httpRequest);

  // 4. Make the call
  const response = await fetch(
    new Request(httpRequest.uri, {
      method: httpRequest.method,
      headers: httpRequest.headers,
      body: httpRequest.body
    })
  );
  // 5. Print response
  console.log(await response.json());
})();

JavaScript

/**
 * Copyright (c) 2020, Oracle and/or its affiliates.  All rights reserved.
 * This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl 
 * or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
 */

const common = require("oci-common");
const promise = require("es6-promise");
require("isomorphic-fetch");
promise.polyfill();

const configurationFilePath = "~/.oci/config";
const configProfile = "DEFAULT";

const provider = new common.ConfigFileAuthenticationDetailsProvider(
  configurationFilePath,
  configProfile
);

const userID = "<INSERT_SAMPLE_USER_OCID_HERE>";
(async () => {
  // 1. Create Request Signing instance
  const signer = new common.DefaultRequestSigner(provider);

  // 2. Create HttpRequest to be signed
  const httpRequest = {
    uri: `https://identity.us-phoenix-1.oraclecloud.com/20160918/users/${userID}`,
    headers: new Headers(),
    method: "GET"
  };

  // 3. sign request
  await signer.signHttpRequest(httpRequest);

  // 4. Make the call
  const response = await fetch(
    new Request(httpRequest.uri, {
      method: httpRequest.method,
      headers: httpRequest.headers,
      body: httpRequest.body
    })
  );
  // 5. Print response
  console.log(await response.json());
})();

Ruby


# Copyright (c) 2016, 2020, Oracle and/or its affiliates.  All rights reserved.
# This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or 
# Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.

require 'oci'
require 'net/http'

config = OCI::ConfigFileLoader.load_config(config_file_location:my_config_file_location)
endpoint = OCI::Regions.get_service_endpoint(config.region, :IdentityClient)

uri = URI(endpoint + '/20160918/users/' + config.user)
request = Net::HTTP::Get.new(uri)

signer = OCI::Signer.new(config.user, config.fingerprint, config.tenancy, config.key_file, pass_phrase:my_private_key_pass_phrase)
signer.sign(:get, uri.to_s, request, nil)

result = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) {|http|
http.request(request)
}

puts result.body

Go

次の例は、デフォルトの署名者を作成する方法を示しています。

ノート

SDK for Goは、カスタム・リクエストの署名に使用できるスタンドアロン署名者を公開しています。http_signer.goで関連するコードを確認できます。
// Copyright (c) 2016, 2018, 2020, Oracle and/or its affiliates.  All rights reserved.
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or 
// Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.

// Example code for sending raw request to  Service API

package example

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"time"

"github.com/oracle/oci-go-sdk/common"
"github.com/oracle/oci-go-sdk/example/helpers"
)

// ExampleRawRequest compose a request, sign it and send to server
func ExampleListUsers_RawRequest() {
// build the url
url := "https://identity.us-phoenix-1.oraclecloud.com/20160918/users/?compartmentId=" + *helpers.RootCompartmentID()

// create request
request, err := http.NewRequest("GET", url, nil)
helpers.FatalIfError(err)

// Set the Date header
request.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))

// And a provider of cryptographic keys
provider := common.DefaultConfigProvider()

// Build the signer
signer := common.DefaultRequestSigner(provider)

// Sign the request
signer.Sign(request)

client := http.Client{}

fmt.Println("send request")

// Execute the request
resp, err := client.Do(request)
helpers.FatalIfError(err)

defer resp.Body.Close()

log.Println("response Status:", resp.Status)
log.Println("response Headers:", resp.Header)

body, _ := ioutil.ReadAll(resp.Body)
log.Println("response Body:", string(body))

fmt.Println("receive response")

// Output:
// send request
// receive response
}

Bash

見やすいようにBashサンプルを全画面で表示します



#!/bin/bash
# Copyright (c) 2016, 2020, Oracle and/or its affiliates.  All rights reserved.
# This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.

set -e

if [[ -z "$COMPARTMENT_ID" ]];then
    echo "COMPARTMENT_ID must be defined in the environment. "
    exit 1
fi

USER_NAME="TestUser"
USER_DESCRIPTION="User created by raw request"
TARGET_URI='https://identity.us-phoenix-1.oraclecloud.com/20160918/users/'
HTTP_METHOD='POST'
PROFILE='ADMIN'
REQUEST_BODY="{\"compartmentId\": \"$COMPARTMENT_ID\", \"name\": \"$USER_NAME\", \"description\": \"$USER_DESCRIPTION\"}"


echo "oci raw-request --profile ${PROFILE} --target-uri ${TARGET_URI} --http-method ${HTTP_METHOD} --request-body "${REQUEST_BODY}" | jq -r '.data.id'"
USER_OCID=$(oci raw-request --profile ${PROFILE} --target-uri ${TARGET_URI} --http-method ${HTTP_METHOD} --request-body "${REQUEST_BODY}" | jq -r '.data.id')

echo "Created user OCID: $USER_OCID"

C#


/*
 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
 * This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or 
 * Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
 */

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Oci.Common.Http.Signing;

namespace Oci.Examples
{
    public class RawRestCallExample
    {
        private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

        public static async Task MainRaw()
        {
            var namespaceName = Environment.GetEnvironmentVariable("NAMESPACE_NAME");
            var compartmentId = Environment.GetEnvironmentVariable("COMPARTMENT_ID");

            var httpClientHandler = OciHttpClientHandler.FromConfigFile("~/.oci/config", "DEFAULT");
            var GET_BUCKETS_URL = $"https://objectstorage.us-phoenix-1.oraclecloud.com/n/{namespaceName}/b/?compartmentId={compartmentId}";
            var client = new HttpClient(httpClientHandler);
            var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(GET_BUCKETS_URL));

            var response = await client.SendAsync(requestMessage);

            logger.Info($"Is rest call successful: {response.IsSuccessStatusCode}");
            var responseJson = await response.Content.ReadAsStringAsync();
            logger.Info($"Parsed Response: {responseJson}");
        }
    }
}