LoginSignup
8
8

More than 5 years have passed since last update.

AWSのAPIリクエストをGoから実行する

Last updated at Posted at 2014-10-31

GoからAWSの各種操作をしようとする際、対応する公式のSDKがないので自前でAPIを叩いて行う必要があります。
Goの勉強がてら東京リージョンのec2インスタンスの一覧を取得するコードを書いてみました。

基本的にAPIの公式ドキュメントを参照していますが、
それ以外にvagrantなどの作者としてもおなじみのmitchelhさん作成のawsライブラリの実装を参考にしました。
https://github.com/mitchellh/goamz

APIのリクエスト構造

エンドポイント

使用するリージョンにあわせて選択します。
今回はec2の東京リージョンなので、https://ec2.ap-northeast-1.amazonaws.com/を使用します。

エンドポイント一覧:http://docs.aws.amazon.com/general/latest/gr/rande.html#emr_region

クエリパラメータ

クエリパラメータには実行するすべてのリクエストで共通の必須パラメータと実施する操作固有のパラメータを指定します。

共通パラメータ

すべてのリクエストで共通のパラメータです。アクションの指定とクエリ認証が主です。
認証方式にはSignatureVersion2とSignatureVersion4があるらしいのですが、以降はクエリ認証にSignatureVersion2を使用する前提で進みます。
(SignatureVersion4はあまり調べてない。。)

以下、指定必須のパラメータです。

  • Action: AWSに対する操作を指定。今回はDescribeInstanceを使用。
    アクション一覧:http://docs.aws.amazon.com/AWSEC2/latest/APIReference/query-apis.html

  • Version: APIバージョン。

  • AWSAccessKeyId: AccessKeyIDを指定。

  • Expires: シグネチャの期限(たぶん・・)。ExpiresかTimestampのいずれかは必須。

  • Timestamp: シグネチャ作成日時(たぶん・・)。ExpiresかTimestampのいずれかは必須。

  • Signature: シグネチャ。作成方法は後述。

  • SignatureMethod: シグネチャ作成に使用するアルゴリズム。HmacSHA256かHmacSHA1を指定。

  • SignatureVersion: 認証に使うバージョン。2を指定。

参考:http://docs.aws.amazon.com/AWSEC2/latest/APIReference/Query-Common-Parameters.html

固有パラメータ

各操作ごとに必要なものがあれば指定します。

シグネチャの生成方法

HmacSHA256の場合、Canonical文字列を生成しシークレットキーと一緒にハッシュ値を計算し、base64エンコードします。

Canonical文字列は以下のルールで生成します。
リクエストメソッド + \n + ホスト名 + \n + パス + \n + クエリパラメータ
クエリパラメータはキーの辞書順で&で結合されている必要があり、かつ各パラメータがURLエンコードされていなければなりません。

canonical-string
GET\n
ec2.ap-northeast-1.amazonaws.com\n
/\n
AWSAccessKeyId=AccessKeyId&Action=DescribeInstances&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2014-11-01T15%3A19%3A30&Version=2014-09-01

コード

ここまでの流れを踏まえたサンプルコードとなります。

サンプル
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "time"
)

func main() {

    host := "https://ec2.ap-northeast-1.amazonaws.com/"

    accessKey := "Access Key ID"     // アクセスキー
    secretKey := "Secret Access Key" // シークレットキー

    // Signature以外の必須共通パラメータ
    params := map[string]string{
        "Action":           "DescribeInstances",
        "Version":          "2014-09-01",
        "AWSAccessKeyId":   accessKey,
        "Timestamp":        time.Now().UTC().Format(time.RFC3339),
        "SignatureVersion": "2",
        "SignatureMethod":  "HmacSHA256",
    }

    // Action固有のパラメータがある場合はシグネチャ生成前に設定
    params["MaxResults"] = "5"

    endpoint, err := url.Parse(host)
    if err != nil {
        log.Fatal(err)
    }

    // シグネチャ生成
    signature := getSignature(secretKey, endpoint.Host, params)

    params["Signature"] = signature

    // 
    endpoint.RawQuery = joinParams(params)

    // リクエスト
    resp, err := http.Get(endpoint.String())
    if err != nil {
        log.Fatal(err)
    }

    body, _ := ioutil.ReadAll(resp.Body)
    defer resp.Body.Close()

    // 結果を出力
    fmt.Println(string(body))

}

// URLエンコードされたパラメータを生成する
func joinParams(params map[string]string) string {
    p := make(url.Values, len(params))
    for k, v := range params {
        p[k] = []string{v}
    }
    return p.Encode()
}

// シグネチャを生成し取得する
func getSignature(secretKey string, host string, params map[string]string) string {

    payload := "GET\n" + host + "\n" + "/\n" + joinParams(params)

    hash := hmac.New(sha256.New, []byte(secretKey))
    hash.Write([]byte(payload))
    signature := make([]byte, base64.StdEncoding.EncodedLen(hash.Size()))
    base64.StdEncoding.Encode(signature, hash.Sum(nil))

    return string(signature)
}

上記を実行すると、以下のようなXML形式の結果が得られます。

結果(サンプル)
<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2014-09-01/">
  <requestId>fdcdcab1-ae5c-489e-9c33-4637c5dda355</requestId>
    <reservationSet>
      <item>
        <reservationId>r-1a2b3c4d</reservationId>
        <ownerId>111122223333</ownerId>
        <groupSet>
          <item>
            <groupId>sg-1a2b3c4d</groupId>
            <groupName>my-security-group</groupName>
          </item>
        </groupSet>
        <instancesSet>
          <item>
            <instanceId>i-1a2b3c4d</instanceId>
            <imageId>ami-1a2b3c4d</imageId>
            <instanceState>
              <code>16</code>
              <name>running</name>
            </instanceState>
            <privateDnsName/>
            <dnsName/>
            <reason/>
            <keyName>my-key-pair</keyName>
            <amiLaunchIndex>0</amiLaunchIndex>
            <productCodes/>
            <instanceType>c1.medium</instanceType>
            <launchTime>YYYY-MM-DDTHH:MM:SS+0000</launchTime>
            <placement>
              <availabilityZone>us-west-2a</availabilityZone>
              <groupName/>
              <tenancy>default</tenancy>
            </placement>
            <platform>windows</platform>
            <monitoring>
              <state>disabled</state>
            </monitoring>
            <subnetId>subnet-1a2b3c4d</subnetId>
            <vpcId>vpc-1a2b3c4d</vpcId>
            <privateIpAddress>10.0.0.12</privateIpAddress>
            <ipAddress>46.51.219.63</ipAddress>
            <sourceDestCheck>true</sourceDestCheck>
            <groupSet>
              <item>
                <groupId>sg-1a2b3c4d</groupId>
                <groupName>my-security-group</groupName>
              </item>
            </groupSet>
            <architecture>x86_64</architecture>
            <rootDeviceType>ebs</rootDeviceType>
            <rootDeviceName>/dev/sda1</rootDeviceName>
            <blockDeviceMapping>
              <item>
                <deviceName>/dev/sda1</deviceName>
                <ebs>
                  <volumeId>vol-1a2b3c4d</volumeId>
                  <status>attached</status>
                  <attachTime>YYYY-MM-DDTHH:MM:SS.SSSZ</attachTime>
                  <deleteOnTermination>true</deleteOnTermination>
                </ebs>
              </item>
            </blockDeviceMapping>
            <virtualizationType>hvm</virtualizationType>
            <clientToken>ABCDE1234567890123</clientToken>
            <tagSet>
              <item>
                <key>Name</key>
                <value>Windows Instance</value>
              </item>
            </tagSet>
            <hypervisor>xen</hypervisor>
            <networkInterfaceSet>
              <item>
                <networkInterfaceId>eni-1a2b3c4d</networkInterfaceId>
                <subnetId>subnet-1a2b3c4d</subnetId>
                <vpcId>vpc-1a2b3c4d</vpcId>
                <description>Primary network interface</description>
                <ownerId>111122223333</ownerId>
                <status>in-use</status>
                <macAddress>1b:2b:3c:4d:5e:6f</macAddress>
                <privateIpAddress>10.0.0.12</privateIpAddress>
                <sourceDestCheck>true</sourceDestCheck>
                <groupSet>
                  <item>
                    <groupId>sg-1a2b3c4d</groupId>
                    <groupName>my-security-group</groupName>
                  </item>
                </groupSet>
                <attachment>
                  <attachmentId>eni-attach-1a2b3c4d</attachmentId>
                  <deviceIndex>0</deviceIndex>
                  <status>attached</status>
                  <attachTime>YYYY-MM-DDTHH:MM:SS+0000</attachTime>
                  <deleteOnTermination>true</deleteOnTermination>
                </attachment>
                <association>
                  <publicIp>198.51.100.63</publicIp>
                  <ipOwnerId>111122223333</ipOwnerId>
                </association>
                <privateIpAddressesSet>
                  <item>
                    <privateIpAddress>10.0.0.12</privateIpAddress>
                    <primary>true</primary>
                    <association>
                      <publicIp>198.51.100.63</publicIp>
                      <ipOwnerId>111122223333</ipOwnerId>
                    </association>
                  </item>
                  <item>
                    <privateIpAddress>10.0.0.14</privateIpAddress>
                    <primary>false</primary>
                    <association>
                      <publicIp>198.51.100.177</publicIp>
                      <ipOwnerId>111122223333</ipOwnerId>
                    </association>
                  </item>
                </privateIpAddressesSet>
              </item>
            </networkInterfaceSet>
          </item>
        </instancesSet>
      </item>
    </reservationSet>  
</DescribeInstancesResponse>

以上です。
APIの仕様の詳細については正直理解があいまいなので、間違いがあるかもしれませんが、実行はできるかと思います。

結果はXMLで返ってくるのでパースして構造体にマッピングする必要がありますが、
ネストが深く構造体を作るのが大変だったので、XMLからGoの構造体を出力するツールを作成しました。
後日公開します。

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8