2020/09,江端さんの技術メモ

Access to XMLHttpRequest at 'http://localhost:8080/api/loc/2' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
test.html:30 GET http://localhost:8080/api/loc/2 net::ERR_FAILED

が取れなくて困っていたので、以下のアドインを入れて強制的に解決した(デフォルトで、"なんでもOK")としておけばよさそう)。

ちなみに、これを使うと、Twitterで通信障害「"問題が発生しました" "やりなおす"」が発生するようなので、実験が終ったら、解除しておいた方が良いです。

ちなみに、上記の問題はjavascriptで発生している問題ですが、Go連携でなんとかしようかと思っていたところで、Sさんから、以下の情報を頂きました。

[Golang] gorilla/muxのCORS対処法

多分、今日あたりからぶつかりそうな問題でした。

で、今ぶつかっている問題ですが、JSON.parseから、情報が取れないんですよね。なんでかなー、色々試しているんだけど、もう疲れてきたなぁ。VSCでデバッグしているんですけど、dataの中身がスカスカなんですよねー。うん困った。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0.1//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html lang="ja">
<head>
<meta http-equiv="Content-Type" Content="text/html;charset=Shift_JIS">
<meta http-equiv="Content-Script-Type" content="text/javascript">

<title>同期通信テスト</title>

<script>
    // (1)XMLHttpRequestオブジェクトを作成
    var xmlHttp = new XMLHttpRequest();


    // (2)onreadystatechangeイベントで処理の状況変化を監視
    //xmlHttp.onreadystatechange = function(){
    //    if(this.readyState == 4 && this.status == 200){
    //        //console.log(this.responseText);
    //        data = this.response
    //    }
    //}

    //var data;

    // (3)HTTPのGETメソッドとアクセスする場所を指定
    xmlHttp.open("GET", "http://localhost:8080/api/loc/2", true);
    //xmlHttp.onload = function(){
    //    if (xmlHttp.status >= 200 && xmlHttp.status < 200){
    //        data = JSON.parse(xmlHttp.responseText);
    //    } else {
    //        console.log("error");
    //    }
    //}

    //xmlHttp.responseType = 'json'

    // (4)HTTPリクエストを送信 
    xmlHttp.send();  

    var data = JSON.parse(xmlHttp.responseText);

    //alert(xmlHttp.responseText);
    console.log(xmlHttp.response);
    console.log(xmlHttp.responseText);
    JSON.parse(data.responseText);
    //console.log(data);
    //var user = JSON.parse(this.responseText);   
    //var user = JSON.parse(data);   
    //var user = JSON.parse(this.responseText);
    //alert(user);
</script>

2020/09,江端さんの技術メモ

サーバ側 loc_rest_server.go というファイル名で保存して、
>go run loc_rest_server.go
で起動


package main

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

	"github.com/gorilla/mux"
)

/*

type GetLoc struct {
	Message string `json:"message"`
	Name    string `json:"name"`
}
*/

/*
// GetLoc GetLoc
type GetLoc struct {
	ID  int64   `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}
*/

// GetLoc GetLoc
type GetLoc struct {
	ID  string `json:"id"`
	Lat string `json:"lat"`
	Lng string `json:"lng"`
	//Address string  `json:"address"`
}

// ErrorResponse error response
type ErrorResponse struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

// locService loc service
func locService(ctx context.Context, number string, tm time.Time) (*GetLoc, error) {

	if number == "1" {
		return &GetLoc{
			ID:  number,
			Lat: "35.653976",
			Lng: "139.796842",
		}, nil
	}

	if number == "2" {
		return &GetLoc{
			ID:  number,
			Lat: "35.653758",
			Lng: "139.794192",
		}, nil
	}

	return nil, nil
}

// AppHandler application handler adaptor
type AppHandler struct {
	h func(http.ResponseWriter, *http.Request) (int, interface{}, error)
}

func (a AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	encoder := json.NewEncoder(w)
	status, res, err := a.h(w, r)
	if err != nil {
		log.Printf("error: %s", err)
		w.WriteHeader(status)
		encoder.Encode(res)
		return
	}
	w.WriteHeader(status)
	encoder.Encode(res)
	return
}

/*
// GetLoc GetLoc
func (app *App) GetLoc(w http.ResponseWriter, r *http.Request) (int, interface{}, error) {
	res, err := locService(r.Context(), "", time.Now())
	if err != nil {
		app.Logger.Printf("error: %s", err)
		e := ErrorResponse{
			Code:    http.StatusInternalServerError,
			Message: "something went wrong",
		}
		return http.StatusInternalServerError, e, err
	}
	app.Logger.Printf("ok: %v", res)
	return http.StatusOK, res, nil
}
*/

// GetLocWithNumber GetLoc with number
func (app *App) GetLocWithNumber(w http.ResponseWriter, r *http.Request) (int, interface{}, error) {
	val := mux.Vars(r)
	//res, err := locService(r.Context(), val["id"], time.Now())
	res, err := locService(r.Context(), val["id"], time.Now())
	if err != nil {
		app.Logger.Printf("error: %s", err)
		e := ErrorResponse{
			Code:    http.StatusInternalServerError,
			Message: "something went wrong",
		}
		return http.StatusInternalServerError, e, err
	}
	app.Logger.Printf("ok: %v", res)
	return http.StatusOK, res, nil
}

// App application
type App struct {
	Host   string
	Name   string
	Logger *log.Logger
}

func main() {
	host, err := os.Hostname()
	if err != nil {
		log.Fatal(err)
	}

	app := App{
		Name:   "my-service",
		Host:   host,
		Logger: log.New(os.Stdout, fmt.Sprintf("[host=%s] ", host), log.LstdFlags),
	}
	// for gorilla/mux
	router := mux.NewRouter()
	r := router.PathPrefix("/api").Subrouter()
	//r.Methods("GET").Path("/loc").Handler(AppHandler{h: app.GetLoc})
	//r.Methods("GET").Path("/loc/staticName").Handler(AppHandler{h: app.GetLoc})
	r.Methods("GET").Path("/loc/{id}").Handler(AppHandler{h: app.GetLocWithNumber})

	if err := http.ListenAndServe(":8080", router); err != nil {
		log.Fatal(err)
	}
}

クライアント側
main.go で保存して、
>go run main.go
で起動

参考とさせて頂いたページ「Goでhttpリクエストを送信する方法

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	//url := "http://google.co.jp"
	url := "http://localhost:8080/api/loc/2"

	resp, _ := http.Get(url)
	defer resp.Body.Close()

	byteArray, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(byteArray)) // htmlをstringで取得
}
動作結果
>go run main.go
 {"id":"2","lat":"35.653758","lng":"139.794192"}

JSONで展開するにはどうしたらいいかな?

参考にさせて頂いたのは「goでjson apiを叩く

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  string `json:"id"`
	Lat string `json:"lat"`
	Lng string `json:"lng"`
	//Address string  `json:"address"`
}

func main() {
	//url := "http://google.co.jp"
	url := "http://localhost:8080/api/loc/2"

	resp, _ := http.Get(url)
	defer resp.Body.Close()

	/*
		byteArray, _ := ioutil.ReadAll(resp.Body)
		fmt.Println(string(byteArray)) // htmlをstringで取得
	*/

	var d GetLoc

	fmt.Printf("======Body (use json.Unmarshal)======\n")
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	err = json.Unmarshal(body, &d)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%v\n", d)

	fmt.Printf("ID:%v\n", d.ID)
	fmt.Printf("Lat:%v\n", d.Lat)
	fmt.Printf("Lng:%v\n", d.Lng)
}

出力結果

>go run main.go
======Body (use json.Unmarshal)======
{2 35.653758 139.794192}
ID:2
Lat:35.653758
Lng:139.794192

2020/09,未分類,江端さんの技術メモ

WiMAX 2+ は、有線(USB)で使えないと思っていたけど、USBで表示されたドライブ(D:)をクリックしたら使えるようになった。本体の方で特別な設定はしなかった。とりあえずメモで。

2020/09,江端さんの忘備録,江端さんの技術メモ

私が今使っているWordPressは、現時点での最新バージョンなのですが、どうしても、Prism For WPのメニューが出てこなくて、ずっと悩んでいました。

で、色々しらべた結果、

をインストールする必要があるらしく、これを入れることでメニューがでるようになりました。

が今度は、画像イメージのコピペができなくなりました。

で、さらに、

をインストールして、この画面のようにコピペを張りつけるように戻すことができるようになりました。

なんか、やりたいことを実現していくと、どんどんWordPressのダウングレードをしているような気になってきました(プラグインもどんどん入れることになって、気持ち悪いです(経験上、プラグインの入れすぎは、トラブルの元になります))。

 

2020/09,江端さんの技術メモ

常日頃から御指導頂いているSさんから、Bad Elf 2300の位置情報をキャプチャするhtmlファイルの内容を教えて頂いた。忘れないように、残しておく。
Bad ElfをBTでリンクしたiPadで稼働を確認済み(iPhoneでは稼働確認できなかった)

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>geolocation-sample</title>
</head>
<body>
  <div id="output"></div>
 
<script>
    var output = document.getElementById('output');
 
    // 位置情報の取得に成功した際のコールバック
    const successCallback = (position) => {
        console.log(position);
		output.innerHTML += "<P>==========";
		output.innerHTML += "<P>time:" + position.timestamp;
		output.innerHTML += "<P>latitude:" + position.coords.latitude;
		output.innerHTML += "<P>longitude:" + position.coords.longitude;
		output.innerHTML += "<P>altitude:" + position.coords.altitude;
		output.innerHTML += "<P>accuracy:" + position.coords.accuracy;
		output.innerHTML += "<P>altitudeAccuracy:" + position.coords.altitudeAccuracy;
		output.innerHTML += "<P>heading:" + position.coords.heading;	
		output.innerHTML += "<P>speed:" + position.coords.speeed;	
    };
 
    // 位置情報の取得に失敗した際のコールバック
    const errorCallback = (err) => {
        console.log(err);
		output.innerHTML += "Error\n";		
    };
 
    // 位置を監視する構成オプション
    // オプションの内容は次のリンクに書かれています。
    // https://developer.mozilla.org/ja/docs/Web/API/PositionOptions
    const options = {
        enableHighAccuracy: true,
        timeout: 5000,
        maximumAge: 0
    };
 
    let watchPositionID;
 
    window.onload = () => {
        // navigator.geolocation.watchPositionについては次のURLにかかれています。
        // https://developer.mozilla.org/ja/docs/Web/API/Geolocation/watchPosition
        watchPositionID = navigator.geolocation.watchPosition(successCallback, errorCallback, options);
    };
 
    // ブラウザーを閉じる前に位置の監視を止めます
    window.onbeforeunload = () => {
        navigator.geolocation.clearWatch(watchPositionID);
    }
</script>
</body>
</html>

2020/09,江端さんの忘備録,江端さんの技術メモ

https://www.prakharsrivastav.com/posts/from-http-to-https-using-go/ が原典

Goを使ってHTTPからHTTPSへ
2019-08-02 :: プラカール・スリバスタフ

序章
この記事では、Go で TLS 暗号化を設定する方法を学びます。さらに、相互にTLS暗号化を設定する方法を探っていきます。このブログ記事で紹介されているコードはこちらからご覧いただけます。この記事では、関連するスニペットを表示しています。興味のある読者は、リポジトリをクローンしてそれに従ってください。

まず、シンプルな Http サーバとクライアントを Go で書くことから始めます。次に、サーバで TLS を設定することで、両者間のトラフィックを暗号化します。この記事の最後に、両者間の相互 TLS を設定します。

シンプルなhttpサーバー
まず、Go で Http クライアント・サーバの実装を作成してみましょう。localhost:8080 に到達可能な Http エンドポイント /server を公開します。そして、http.Clientを使ってエンドポイントを呼び出し、その結果を表示します。

完全な実装はこちらを参照してください。

// Server code
mux := http.NewServeMux()
mux.HandleFunc("/server", func(w http.ResponseWriter, r *http.Request) {
  fmt.Fprint(w, "Protect Me...")
})
log.Fatal(http.ListenAndServe(":8080", mux))


// Client code
if r, err = http.NewRequest(http.MethodGet, "http://localhost:8080/server", nil); err != nil {
	log.Fatalf("request failed : %v", err)
}

c := http.Client{
	Timeout:   time.Second * 5,
	Transport: &http.Transport{IdleConnTimeout: 10 * time.Second},
}

if data, err = callServer(c, r); err != nil {
	log.Fatal(err)
}
log.Println(data) // Should print "Protect Me..."

次のセクションでは、TLS を使用してクライアントとサーバ間のトラフィックを暗号化します。その前に、公開鍵インフラストラクチャ (PKI) をセットアップする必要があります。

PKI のセットアップ
ミニ PKI インフラストラクチャをセットアップするために、minica という Go ユーティリティを使用して、ルート、サーバ、クライアントの鍵ペアと証明書を作成します。実際には、認証局 (CA) またはドメイン管理者 (組織内) が鍵ペアと署名付き証明書を提供してくれます。私たちの場合は、minicaを使ってこれをプロビジョニングしてもらうことにします。

鍵ペアと証明書の生成
注: これらを生成するのが面倒に思える場合は、Github リポジトリでコミットされた証明書を再利用することができます。

以下の手順で証明書を生成します。

minicaをインストールする: github.com/jsha/minicaを取得してください。
minica --domains server-certを実行してサーバ証明書を作成します。
初めて実行すると4つのファイルが生成されます。
minica.pem(ルート証明書
minica-key.pem (root 用の秘密鍵)
server-cert/cert.pem (ドメイン「server-cert」の証明書、ルートの公開鍵で署名されています)
server-cert/key.pem (ドメイン「server-cert」の秘密鍵)
minica --domains client-certを実行してクライアント証明書を作成します。2つの新しいファイルが生成されます。
client-cert/cert.pem (ドメイン "client-cert "の証明書)
client-cert/key.pem (ドメイン "client-cert "の秘密鍵)
また、minicaでドメインの代わりにIPを使用して鍵ペアや証明書を生成することもできます。

etc/hosts にエイリアスを設定する
上記で生成したクライアント証明書とサーバ証明書は、それぞれドメイン server-cert と client-cert で有効です。これらのドメインは存在しないので、localhost(127.0.0.1)のエイリアスを作成します。これを設定すると、localhost の代わりに server-cert を使用して Http サーバにアクセスできるようになります。

Linux以外のプラットフォームを使っている場合は、OSに合わせた設定方法をググってみてください。私はLinuxマシンを使っていますが、ドメインエイリアスの設定はとても簡単です。etc/hostsファイルを開き、以下の項目を追加します。

127.0.0.1       server-cert
127.0.0.1       client-cert

この時点で、インフラストラクチャの設定は完了です。次のセクションでは、クライアントとサーバ間のトラフィックを暗号化するために、これらの証明書を使ってサーバを設定します。

サーバーでTLSを設定する
サーバ-certドメインに生成された鍵と証明書を使って、サーバにTLSを設定してみましょう。クライアントは先ほどと同じです。唯一の違いは、3つの異なるURLでサーバを呼び出すことで、何が起こっているのかを理解することです。

完全な実装はこちら

// Server configuration
mux := http.NewServeMux()
mux.HandleFunc("/server", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "i am protected")
})
log.Println("starting server")
// Here we use ListenAndServerTLS() instead of ListenAndServe()
// CertPath and KeyPath are location for certificate and key for server-cer
log.Fatal(http.ListenAndServeTLS(":8080", CertPath, KeyPath, mux))

// Server configuration
c := http.Client{
    Timeout:   5 * time.Second,
    Transport: &http.Transport{IdleConnTimeout: 10 * time.Second,},
}

if r, err = http.NewRequest(http.MethodGet, "http://localhost:8080/server", nil); err != nil { // 1
//if r, err = http.NewRequest(http.MethodGet, "https://localhost:8080/server", nil); err != nil { // 2
//if r, err = http.NewRequest(http.MethodGet, "https://server-cert:8080/server", nil); err != nil { // 3
    log.Fatalf("request failed : %v", err)
}

if data, err = callServer(c, r); err != nil {
    log.Fatal(err)
}
log.Println(data)

http.ListenAndServeTLS()を使用してサーバを起動します。これにはポート、公開証明書へのパス、秘密鍵へのパス、そしてHttp-handlerの4つの引数が必要です。サーバからのレスポンスを見てみましょう。私たちは失敗しますが、私たちはどのようにHttp暗号化が動作するかについてのより多くの洞察を与える3つの異なる要求を送信します。

Attepmt 1 http://localhost:8080/server に送信すると、応答があります。

Client Error. Get http://localhost:8080/server: net/http: HTTP/1.x transport connection broken: malformed HTTP response "\x15\x03x01x00x02\x02"

サーバエラー: http: 127.0.0.1:35694 からの TLS ハンドシェイクエラー: tls: 最初のレコードが TLS ハンドシェイクのように見えません。

これは、サーバーが暗号化されたデータを送信していることを意味する良いニュースです。Http経由では誰も意味をなさないでしょう。

Attempt 2 to https://localhost:8080/server、レスポンスは以下の通りです。

クライアントエラーです。Get https://localhost:8080/server: x509: certificate is valid for server-cert, not localhost

サーバエラー: http: 127.0.0.1:35698 からの TLS ハンドシェイクエラー: リモートエラー: tls: 不正な証明書

これはまたしても朗報です。これは、ドメインサーバ証明書に発行された証明書を他のドメイン(ローカルホスト)で使用することができないことを意味します。

Attempt 3 to https://server-cert:8080/server、応答があります。

クライアントエラーです。Get https://server-cert:8080/server: x509: certificate signed by unknown authority

サーバエラー: http: 127.0.0.1:35700 からの TLS ハンドシェイクエラー: リモートエラー: tls: 不正な証明書

このエラーは、クライアントがその証明書に署名したことを信頼していないことを示しています。クライアントは証明書に署名した CA を認識していなければなりません。

このセクションの全体的な考えは、TLS が保証する 3 つの保証を実証することでした。

メッセージは常に暗号化されている。
サーバが実際に言っている通りのものであること。
クライアントはサーバの証明書を盲目的に信じてはいけない。クライアントは、CA を通じてサーバの身元を確認できるようにしなければなりません。

クライアントでCA証明書を設定する
クライアント側のCA証明書を設定して、ルートCAの証明書とサーバの身元を照合できるようにします。サーバ証明書はルートCAの公開鍵を使って署名されているので、TLSハンドシェイクが有効になり、通信が暗号化されます。

完全な実装はこちらにあります。

// create a Certificate pool to hold one or more CA certificates
rootCAPool := x509.NewCertPool()

// read minica certificate (which is CA in our case) and add to the Certificate Pool
rootCA, err := ioutil.ReadFile(RootCertificatePath)
if err != nil {
    log.Fatalf("reading cert failed : %v", err)
}
rootCAPool.AppendCertsFromPEM(rootCA)
log.Println("RootCA loaded")

// in the http client configuration, add TLS configuration and add the RootCAs
c := http.Client{
    Timeout: 5 * time.Second,
    Transport: &http.Transport{
        IdleConnTimeout: 10 * time.Second,
        TLSClientConfig: &tls.Config{RootCAs: rootCAPool},
    },
}

if r, err = http.NewRequest(http.MethodGet, "https://server-cert:8080/server", nil); err != nil {
    log.Fatalf("request failed : %v", err)
}

if data, err = callServer(c, r); err != nil {
    log.Fatal(err)
}
log.Println(data)

// server response
prakhar@tardis (master)? % go run client.go  
RootCA loaded
i am protected # response from server

これにより、先ほど説明した3つの保証がすべて保証されます。

相互TLSの設定
サーバーにクライアントの信頼を確立しています。しかし、多くのユースケースでは、サーバーがクライアントを信頼する必要があります。例えば、金融、医療、公共サービス業界などです。これらのシナリオのために、クライアントとサーバーの間で相互にTLSを設定して、双方がお互いを信頼できるようにします。

TLSプロトコルは、最初からこれをサポートしています。相互TLS認証を設定するために必要な手順は以下の通りです。

1.サーバはCA(CA-1)から証明書を取得します。クライアントは、サーバの証明書に署名したCA-1の公開証明書を持っている必要があります。
2.クライアントは CA (CA-2) から証明書を取得します。サーバは、クライアントの証明書に署名したCA-2の公開証明書を持っていなければなりません。簡単にするために、クライアント証明書とサーバ証明書の両方に署名するために同じ CA (CA-1 == CA-2) を使用します。
3.サーバは、すべてのクライアントを検証するためにCA証明書プールを作成します。この時点で、サーバはCA-2の公開証明書を含む。
4.同様に、クライアントは独自のCA証明書プールを作成し、CA-1の公開証明書を含む。
5.両者は、CA 証明書プールに対して受信要求を検証します。どちらか一方に検証エラーがあった場合、接続は中断されます。
実際に動作を見てみましょう。この機能の完全な実装はこちらを参照してください。

サーバーの設定

mux := http.NewServeMux()
mux.HandleFunc("/server", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "i am protected")
})

clientCA, err := ioutil.ReadFile(RootCertificatePath)
if err != nil {
    log.Fatalf("reading cert failed : %v", err)
}
clientCAPool := x509.NewCertPool()
clientCAPool.AppendCertsFromPEM(clientCA)
log.Println("ClientCA loaded")

s := &http.Server{
    Handler: mux,
    Addr:    ":8080",
    TLSConfig: &tls.Config{
        ClientCAs:  clientCAPool,
        ClientAuth: tls.RequireAndVerifyClientCert,
        GetCertificate: func(info *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
            c, err := tls.LoadX509KeyPair(CertPath, KeyPath)
            if err != nil {
                fmt.Printf("Error loading key pair: %v\n", err)
                return nil, err
            }
            return &c, nil
        },
    },
}
log.Fatal(s.ListenAndServeTLS("", ""))

この設定で注意すべき点がいくつかあります。

  1. http.ListenAndServeTLS() の代わりに server.ListenAndServerTLS() を使用します。
  2. サーバ証明書と鍵を tls.Config.GetCertificate 関数の中にロードします。
  3. サーバが信頼すべきクライアント CA 証明書のプールを作成します。
  4. tls.Config.ClientAuth = tls.RequireAndVerifyClientCertを設定し、接続しようとするすべてのクライアントの証明書を常に検証します。検証されたクライアントのみが会話を続けることができます。

クライアント設定
http.Clientの設定は、クライアントの設定も少し変わります。

rootCA, err := ioutil.ReadFile(RootCertificatePath)
if err != nil {
    log.Fatalf("reading cert failed : %v", err)
}
rootCAPool := x509.NewCertPool()
rootCAPool.AppendCertsFromPEM(rootCA)
log.Println("RootCA loaded")

c := http.Client{
    Timeout: 5 * time.Second,
    Transport: &http.Transport{
        IdleConnTimeout: 10 * time.Second,
        TLSClientConfig: &tls.Config{
            RootCAs: rootCAPool,
            GetClientCertificate: func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
                c, err := tls.LoadX509KeyPair(ClientCertPath, ClientKeyPath)
                if err != nil {
                    fmt.Printf("Error loading key pair: %v\n", err)
                    return nil, err
                }
                return &c, nil
            },
        },
    },
}

サーバと比較した場合の設定の違いに注目してください。

  1. tls.Configでは、サーバー上のClientCAの設定に対して証明書プールをロードするためにRootCAを使用しています。
  2. tls.Config.GetClientCertificate を使用して、サーバー上の tls.Config.GetCertificate に対してクライアント証明書をロードしています。
  3. GitHub の実際のコードにはいくつかのコールバックがあり、これを使って証明書の情報を見ることもできます。

クライアントとサーバの相互TLS認証の実行

# Server logs
2019/08/01 20:00:50 starting server
2019/08/01 20:00:50 ClientCA loaded
2019/08/01 20:01:01 client requested certificate
Verified certificate chain from peer:
  Cert 0:
    Subject [client-cert] # Server shows the client certificate details
    Usage [1 2]
    Issued by minica root ca 5b4bc5 
    Issued by 
  Cert 1:
    Self-signed certificate minica root ca 5b4bc5

# Client logs
2019/08/01 20:01:01 RootCA loaded
Verified certificate chain from peer:
  Cert 0:
    Subject [server-cert] # Client knows the server certificate details
    Usage [1 2]
    Issued by minica root ca 5b4bc5
    Issued by 
  Cert 1:
    Self-signed certificate minica root ca 5b4bc5
2019/08/01 20:01:01 request from server
2019/08/01 20:01:01 i am protected

結論
TLS の設定は、実装の問題というよりも証明書の管理の問題が常にあります。TLS 設定における典型的な混乱は、実装というよりも正しい証明書の使用に関連していることが多いです。TLS プロトコルとハンドシェイクを正しく理解していれば、Go は箱から出してすぐに必要なものをすべて提供してくれます。

また、理論的な観点からTLSの暗号化とセキュリティを探求した以前の記事もチェックしてみてください。

参考文献
この記事は、Gophercon-2018でのLiz Riceの素晴らしいトークに大きく影響されていますので、ぜひチェックしてみてください。その他の参考文献は以下の通りです。

secure-connections: gophercon のためのレポ
minica 認証局
Eric Chiangによるこの驚くべき記事。必読です。
step-by-step-guide-to-mtls-in-go.
mediumのこの記事。

2020/09,江端さんの忘備録,江端さんの技術メモ

ChromeにAdd-onを追加

ChromウェブストアのMarkdown Preview Plusのページを開きます
https://chrome.google.com/webstore/detail/markdown-preview-plus/febilkbfcbhebfnokafefeacimjdckgl?hl=ja

[Chromeに追加]ボタンをクリックします

と、ここまではいいんだけど、次をやらないと表示しない

chrome://extensions/

から、

から「詳細」を選び

をアクティブにするのが重要。

さらに、WordPressでは、エディタ「Gutenberg」を入れると、最初からMarkdownが使える

なかなか便利です。

以上

2020/09,江端さんの技術メモ

ここのところ、Websocket通信のセキュア化のコーディングで、かなり嵌ってしまっている私に、師匠のSさんから、

GoのサーバーでWSSやHTTPSのプロトコルを直接扱うことをせず、かわりに、リバースプロキシサーバーでHTTPSやWSSプロトコルを扱い、GoのサーバーではHTTPやWSプロトコルを扱うように構成を変更する、というのはいかがでしょうか?

とのご提言を頂きました。

あ、そりゃそうだ ―― と思いました。私は、Websocket通信のアタックさえ回避できれば、なんでも良いわけでして、何がなんでもwssをやりたい訳ではありません。

インターネットの中でセキュアが担保できれば、内部システムなんぞスカスカでも構いません(そもそも、システム内部に侵入されたら、その時点で"システムとしては、"The END"です)。

とりあえずPC(Windows10 Box)の中でnginx(Webサーバ)を立てようと思いました。

いや、驚くほど簡単。「インストールから実行まで10秒を切ったWebサーバ」というのは生まれて始めてかもしれません。

http://nginx.org/en/download.html から、Stable versionをダウンロードして、

解凍して、"nginx.exe"を叩くだけ。(私の場合、ダウンロードの場所は、C:\の直下にしておきました)

これで、ブラウザから、http://localhost と入力して稼動確認

ちなみに、トップページは、htmlフォルダの中にあるので、適当に変更すれば、普通のWebサーバにもなります。

それはさておき。

今回の場合、クライアントからnginxまでのセキュアが確保できれば足り、その後ろは、普通のwebsocketの通信ができれば良い、とします(golangでwebsocketのセキュア通信って、もう気が狂いそうになるほど面倒)。

Sさんからは、nginx.confを送付して貰ったのですが、まあ、当然ですが、一発では動きませんでしたので、先ず、セキュア通信なしのリバースプロキシを動かしてみます。

nginxでポート8000で受けて、それを、エコーサーバ(8080)に転送するだけのものですが、NginxのリバースプロキシでWebソケットを通す際の設定 とSさんから送って頂いた設定ファイルを参考にさせて頂いて、以下のようにconf/nginx.confの内容を書き換えました。

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    map $http_upgrade $connection_upgrade { 
        default upgrade;    
        ''      close;
    } 

    server {
        listen 8000;
        server_name localhost;

        location / {
            proxy_pass   http://localhost:8080;

            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Frame-Options SAMEORIGIN;

            proxy_read_timeout 86400;

        }
    }
}

ちなみにエコーサーバは、以下のものをそのまま使っています。

でもって、こんな感じでちゃんと動いています。もちろんlocalhost:8080でも動きます。

ーーーー

では、ここから、ngnixまでのセキュア化の検討を開始します。

ここでしばしスタック中。TLSがちゃんと動いているのか不安になったので、普通のWebでできるか、以下のようなnginx.confを使って、index.htmlで試してみました。

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    server {
        listen       443;
        server_name  localhost;

        ssl                  on;
        ssl_certificate      algo.crt; # https://wp.kobore.net/2020/09/post-1124/で作ったもの
        ssl_certificate_key  algo.key; # 同上

        location / {
            root html;
            index index.html index.htm;
        }
    }
}  

https://localhost/ でも、http://localhost/ でも動いていますので、algo.crt, algo.keyについては(多分)大丈夫だと思われます。

では、元の問題に戻りましょう。(4時間格闘)

どうやら、外向けをセキュアにするには、nginx.confを3箇所だけ変更追加すれば良い、と記載されていたので、以下のようなnginx.confに改造した "#change"と、"#add"のところだけ。

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    map $http_upgrade $connection_upgrade { 
        default upgrade;    
        ''      close;
    } 

    server {
        #listen 8000;
        listen 443 ssl;  # change

        server_name localhost;

        ssl_certificate algo.crt; #add
        ssl_certificate_key algo.key; #add



        location / {
            proxy_pass   http://localhost:8080;

            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Frame-Options SAMEORIGIN;

            proxy_read_timeout 86400;

        }
    }
}

ところが、https://localhost を実施しても、サーバ側から、以下のエラーがでてきて、どうにもならない。

upgrade:websocket: the client is not using the websocket protocol: 'websocket' token not found in 'Upgrade' header

Visual Studio Codeにかけて、サーバでトレースしても読めないし(htmlの部分だから無理ない)、正直もうどうして良いか分からなくなってきたとき、「あ、chromoのディベロッパーツールを使えばいいか」と気が付きました。

そんでもって、

これで気がつきました。ここって"ws:"ではなくて、"wss:"にしないとアカンのじゃないかな、と。

で、ここを修正して、ECHOサーバを立ち上げ直したら、無事稼動することが確認できました。

まあ、まだまだ課題山積ですが、とりあえず、家(システム)の外はセキュアにする目処が立ちましたので、wssの通信プログラムで苦労するのは「やめる方向」で決定しました。

nginxをリバースプロキシにして、そこまではガチガチにしておき、家の中はユルユルに作るで、進めていくことにします。

以上

2020/09,江端さんの技術メモ

$ git clone https://github.com/GuyBrand/WssSample

で取ってきた、コードで、色々調べている。

サーバは、以下で起動。

クアイアントは、

で起動(に失敗)

以下、色々ごそごそやってみる。

ちなみに、getCrtホルダは、証明鍵を作るプログラムのようす
C:\Users\ebata\WssSample\genCrt>ls
genCrt.go new.crt new.key

C:\Users\ebata\WssSample\goClient>curl localhost:9200
Client sent an HTTP request to an HTTPS server.

C:\Users\ebata\WssSample\goClient>curl https://localhost:9200
curl: (77) schannel: next InitializeSecurityContext failed: SEC_E_UNTRUSTED_ROOT (0x80090325) - 信頼されていない機関によって証明書チェーンが発行されました。

「信頼されていない機関」って「私」か。そりゃそうだが。

うーん、では、curlコマンドを使ってHTTPSのAPIを叩く と 、SSL通信時発生する証明書エラーとその仕組みを理解する を参考にして、

C:\Users\ebata\WssSample\goClient>curl -k https://localhost:9200

を実行。

色々でてきた。ということは、サーバとしては機能しているようです。

とは言え、

は、解決されていないです。

そもそも、私、SSLの仕組み、きちんと理解できていると思っていません(だからAWSのhttps化で地獄を見ることになった)。今回、イケメンとラブレターで学ぶSSLの仕組み で、なんとか分かったような気になりました。

C:\Users\ebata\WssSample\server の中に入っている、algo.crt を叩いてみたら、

が出てきました。インストールして良いものかどうか迷いましたが、(有効期間内だったようだし)「証明証のインストール」のボタンを押してみました。

よく分かりませんが、言われるがまま(にボタンを押していきました。

そんでもって、サーバ側からは、C:\Users\ebata\WssSample\server>go run main.go hub.go conn.go クライアント側には、C:\Users\ebata\WssSample\goClient>go run goClient.go conn.go hub.go を入力しましたが ―― うん、症状改善なし、でした。

一応ブラウザから https://localhost:9200 を押下したみたのですが、

こんな感じですねえ。サーバのエラー表示はこんな感じです。

「詳細設定」ボタンを押して、「localhost にアクセスする(安全ではありません)」を押下すると、一応動きます。

この画面2枚上げると、表示が連動しているのが分かります。

Windows10の環境設定画面から、以下のような設定もやってみたけどダメでした(ので消しました(別の問題を引き起こしそうだったので))

(一時休憩)

現在勉強中のページ

MacでGolangでhttpdでTLSでLet's Encryptを使ってみた。

ここで困っていたら、普段からご教示して頂いているSさんから「mkcertを使ってみたら」というアドバイスを頂きました。で、get clone https://github.com/FiloSottile/mkcert.git などもやってみたのですが、ソースコードしか入っていなくて、このコンパイル方法も良く分かりませんでした。

https://github.com/FiloSottile/mkcertを除いてみたら、バイナリがダウンロードできそうことが分かりました。

でもって、ここから、Windows10で使えそうなバイナリをダウンロードしました。

ダウンロードしたところから、直接叩いてみたら、C:\Users\Ebata\AppData\Local\mkcert の中に、鍵ができていましたが、最初に、mkcer -installしろ、と言われています。

本当はmkcertにリネームした方が良いのでしょうが、面倒なので、そのまま mkcert-v1.4.1-windows-amd64.exe -install でセットしました。

その後、mkcert-v1.4.1-windows-amd64.exe localhost 127.0.0.1 と入力すると、"localhost" と "127.0.0.1"を含む鍵が、カレントディレクトリにできるようです。

で、"localhost+1-key.pem"を "algo.key"とリネームして、"localhost+1.pem"を"algo.crt"とリネームして、C:\Users\ebata\WssSample\serverに放り込む。

C:\Users\ebata\WssSample\server>go run main.go conn.go hub.go
file algo.crt found switching to https

とこの状態で、ブラウザから、https://localhost:9200を入力

おお! 警告が出なくなった!!! サーバはちゃんと動いているみたい。

では、クライアントのプログラムも・・・

画像に alt 属性が指定されていません。ファイル名: image-28.png

うん、こっちはダメだな。

    ws, _, err := websocket.NewClient(c, u, wsHeaders, 1024, 1024)
    if err != nil {
            fmt.Println(err.Error())
            return
    }

トレースで、このNewClient()でエラーが出ているのは分かっているんですが、この原因は分かりません。

クライアント側のコードでは、鍵の読み込みをしていないようです。

ちなみに、WssSample/gemCrt に鍵を生成するプログラムが入っていますが、これで作った鍵では、httpsの認証問題をクリアできませんでしたので、素直に「mkcertを使ってみたら」と申し上げます。

(もう一度チャレンジしますが今日は▽ここまで)。