LoginSignup
1
1

More than 3 years have passed since last update.

【Go】踏み台サーバー経由 (ssh) で VPC エンドポイントの Elasticsearch Service にローカルから接続する【AWS】

Posted at

やりたいこと

踏み台サーバー経由 (ssh) でプライベートサブネット内の RDS (MySQL) へ接続するのと同様にして、VPC エンドポイントの Elasticsearch Service にも踏み台サーバー経由で接続することが目標です。

ssh コマンドと curl コマンドを利用すれば次のように簡単にローカルから接続できますが、今回は Go プログラムで接続することを目標とします。

ssh -i <path/to/private-key> <username>@<hostname> curl -s '<ES_ENDPOINT>/_cat/indices?format=json&pretty'

前提

次のようなアーキテクチャを想定します。セキュリティグループはいい感じに設定されているものとします。

AWS (2019年) フレームワーク.jpeg

実装

Elasticsearch のクライアント用ライブラリとしては Elastic 社公式の elastic/go-elasticsearch を利用します。

実装のポイントは http.RoundTripper (http.Transport) の Dial に SSH Client の Dial を利用することです。

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net"
    "net/http"
    "os"
    "time"

    "github.com/elastic/go-elasticsearch/v8"
    "github.com/elastic/go-elasticsearch/v8/esapi"
    "golang.org/x/crypto/ssh"
)

func main() {
    log.SetFlags(log.LstdFlags | log.Lshortfile)

    var (
        sshUser       = os.Getenv("SSH_USER")
        sshHost       = os.Getenv("SSH_HOST")
        sshPort       = os.Getenv("SSH_PORT")
        sshPrivateKey = os.Getenv("SSH_PRIVATE_KEY")
        esEndpoint    = os.Getenv("ES_ENDPOINT")
    )

    // ------------------------------
    // 秘密鍵ファイルの読み込み
    // ------------------------------
    b, err := ioutil.ReadFile(sshPrivateKey)
    if err != nil {
        log.Fatal(err)
    }

    signer, err := ssh.ParsePrivateKey(b)
    if err != nil {
        log.Fatal(err)
    }

    // ------------------------------
    // SSH クライアントの生成
    // ------------------------------
    sshConf := ssh.ClientConfig{
        User: sshUser,
        Auth: []ssh.AuthMethod{
            ssh.PublicKeys(signer),
        },
        HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
            return nil
        },
        Timeout: 10 * time.Second,
    }

    sshClient, err := ssh.Dial("tcp", net.JoinHostPort(sshHost, sshPort), &sshConf)
    if err != nil {
        log.Fatal(err)
    }
    defer sshClient.Close()

    // ------------------------------
    // Elasticsearch クライアントの生成
    // ------------------------------
    esConf := elasticsearch.Config{
        Addresses: []string{esEndpoint},
        Transport: &http.Transport{
            Proxy:               http.ProxyFromEnvironment,
            Dial:                sshClient.Dial, // ここで SSH Client を利用
            TLSHandshakeTimeout: 10 * time.Second,
        },
    }

    es, err := elasticsearch.NewClient(esConf)
    if err != nil {
        log.Fatal(err)
    }

    // ------------------------------
    // リクエストを実行 (/_cat/indices)
    // ------------------------------
    req := esapi.CatIndicesRequest{
        Format: "json",
        Pretty: true,
    }

    ctx := context.Background()

    resp, err := req.Do(ctx, es)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    // ------------------------------
    // レスポンスを解析
    // ------------------------------
    if resp.IsError() {
        log.Fatal(resp.String())
    }

    body := io.TeeReader(resp.Body, os.Stdout) // debug

    var r []map[string]interface{}
    if err := json.NewDecoder(body).Decode(&r); err != nil {
        log.Fatal(err)
    }

    for i, obj := range r {
        fmt.Printf("\n[#%d]\n", i)
        for k, v := range obj {
            fmt.Println(k, v)
        }
    }
}
1
1
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
1
1