- 背景
- 事業部に散々脅かされた
- 「もし、ハックされたら研開(というか江端一人)で責任取れよ」と念を押された
- AWSコンソールは、江端のみにログイン権限があるので、ここからいじるのは難しいそう
- 江端予測
- EC2への直接の攻撃・または侵入
- 一応SSHアクセスはできる状態
- SSHによる攻撃はかなり難しいのではないか(秘密鍵がある限り)
- EBLへの攻撃・または侵入
- 無闇なアタックはできるだろうが、内部に入ることはできないのでは?
- 取られる情報
- 個人情報はないので、個人情報の流出は不可能だが、ユーザ利用情報やら運行情報が取られる可能性もある
- EC2への直接の攻撃・または侵入
- 江端が調べた範囲のこと
- SQLインジェクション
- WebからSQLに触れないので意味なし
- XSS(クロスサイトスクリプティング)
- Webサイトのリンクは限定的であり、外部から改竄される余地がない
- バッファオーバーフロー
- ELBでEC2を保護しているから、サービスは落せない(?)
- WebDAV
- 本サービスでは使っていないので関係なし
- FTPS(FTP over SSL)やSFTP(SSH File Transfer Protocol)等)を採用しているか
- FTPサーバ上げる予定がないので、自動的にこれを採用することになる
- メールサーバ
- メールサーバがない
- CMS
- 使用しない
- 意図しないサーバへのゾーン転送の防止
- 発生しえない
- 安全なHTTPS通信
- TLSを利用したhttps通信を使用
- 社外公開サーバ セキュリティ対策リスト_20191007.xlsxにて確認済み
- SQLインジェクション
- 江端不明点
- EC2からIAMを書き換えることって可能なのか?
AWSのセキュリティ対策
MarkdownをWord形式(docx)に変換する方法 Pandoc
MarkdownをWord形式(docx)に変換する を参考にして、Pandoc をインストールしましたが、とてもキレイに表示されて便利です。
# PruneMobileとは
複数の人間や自動車等の移動体のリアルタイムの位置情報を、地図上に表示する、PruneClusterのアプリーケーションです。
PruneMobileのサーバに対して、任意のタイミングで位置情報を送り込むだけで、地図上にマーカーが表示されます。
## 使用環境
- golang(Go言語)のインストールされていれば良いです。私(江端智一)の環境では以下のようになっています。
```
$ PruneMobile\server>go version
$ go version go1.14 windows/amd64
```
- 実際に動かせば、コンパイラから、あれこれ言われますので、それに対応して下さい。基本的には、
```
$ go get github.com/gorilla/websocket
```
は必要になると思います。
## サンプルプログラムの環境
- Webブラウザで表示される地図は、東京都江東区の豊洲駅を中心にして作ってあります。
- PruneMobile\server\serverX.go (Xは数字) の中にある、
```
var map = L.map("map", {
attributionControl: false,
zoomControl: false
}).setView(new L.LatLng(35.654543, 139.795534), 18);
```
の"35.654543, 139.795534"の座標を変更すれば、地図の中心位置が変わります。
- クライアントプログラムでは、豊洲駅を中心にランダムウォークさせています
- PruneMobile\server\clientX.go (Xは数字)を起動すると、10秒間程、マーカーが移動して、その後消滅します。
- クライアントプログラム(clientX.go)は複数同時に起動させることができます。
## 現時点で確認している問題点で、いずれ直すもの
- マーカーの消滅のタイミングが、同時になってしまう
- Webブラウザの表示が、最初の1つめしか、正常に動作しない
- ローカルのjs(javascript)のローディングに失敗した為、江端のプライベートサーバ(kobore.net)からローディングしている。PruneMobile\server\serverX.goの以下を参照
```
<script src="http://kobore.net/PruneCluster.js"></script>
<link rel="stylesheet" href="http://kobore.net/examples.css"/>
```
# サンプルプログラムの動作方法
## Step 1 サーバの起動
適当なシェルを立ち上げて
```
$ cd PruneMobile\server
$ go run serverX.go (Xは数字)
```
とすると、「Windowsセキュリティの重要な警告(windows10の場合)」が出てくるので、「アクセスを許可する」ボタンを押下して下さい。
## Step 2 ブラウザの起動
Chromoブラウザ(他のブラウザのことは知らん)から、
```
http://localhost:8080/
```
と入力して下さい。豊洲地区の地図が表示されます。
## Step 3 クライアントの起動
適当なシェルを立ち上げて
```
$ cd PruneMobile\client
$ go run clientX.go (Xは数字)
```
とすると、マーカが0.5秒単位でランダムに動きます。
# クライアントプログラムで使うI/F(データ形式)
## 前提
- サーバとwebsocketのコネクションを確立して下さい。方法は問いません。golangでの記述方法はclient/clientX.goを参考にして下さい。
- データ形式はJSONを用います。golangでの記載サンプルは以下の通りです。
```
// GetLoc GetLoc
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
//Address string `json:"address"`
}
```
## Step.1 マーカーの登録
IDを"0"にして、最初のマーカーの座標を入力したJSONを、サーバに送付して下さい。golangでの送信方法はは以下の通りです。
```
gl := GetLoc{
ID: 0,
Lat: 35.653976,
Lng: 139.796821,
}
err = c.WriteJSON(gl)
if err != nil {
log.Println("write:", err)
}
```
返り値に入ってきたIDが、これからそのマーカで使うID番号となります。golangでの受信方法はは以下の通りです。
```
gl2 := new(GetLoc)
err = c.ReadJSON(gl2)
log.Printf("after ID:%d", gl2.ID)
log.Printf("after Lat:%f", gl2.Lat)
log.Printf("after Lng:%f", gl2.Lng)
```
以後、このID番号(整数)を使用して下さい。この番号と異なる番号を使用した場合、動作は保証されません。
## Step.2 マーカーの移動
指定されたIDを使って、移動後の座標を送付して下さい。
```
gl := GetLoc{
ID: 5, // IDが"5"の場合
Lat: 35.653923,
Lng: 139.796852,
}
err = c.WriteJSON(gl)
if err != nil {
log.Println("write:", err)
}
```
返り値は、入力と同じJSONが戻ってきますが、必ず受信して下さい。
```
gl2 := new(GetLoc)
err = c.ReadJSON(gl2)
log.Printf("after ID:%d", gl2.ID)
log.Printf("after Lat:%f", gl2.Lat)
log.Printf("after Lng:%f", gl2.Lng)
```
## Step.3 マーカーの抹消
指定されたIDを使って、地球上の緯度経度の数値で現わせない座標を入力して下さい。具体的に、latに90.0より大きな値、またはlngに180より大きな値を入力することで、マーカが消去されます。
```
gl := GetLoc{
ID: 5, // IDが"5"の場合
Lat: 999.9,
Lng: 999.9,
}
err = c.WriteJSON(gl)
if err != nil {
log.Println("write:", err)
}
```
返り値は、入力と同じJSONが戻ってきますが、必ず受信して下さい。
```
gl2 := new(GetLoc)
err = c.ReadJSON(gl2)
log.Printf("after ID:%d", gl2.ID)
log.Printf("after Lat:%f", gl2.Lat)
log.Printf("after Lng:%f", gl2.Lng)
```
以上
wsarecv: An existing connection was forcibly closed by the remote host. がどうにも分からない
PruneMobile というソフトウェアを作っています。
クライアント側のプログラムで、Golang(Go言語)の 並列(通信)処理を始めて本格的に実装したのですが、サーバ側にエラーが乱発しています。
Golangの通信プログラムの仕様は今一つ分かなくて(C言語なら「私がマニュアルだ」と傲慢に言い放ちますが)、試行錯誤の最中です。
"Go"は"C"と、ポインタや配列も違って、勉強し直し状態です。
それはさておき、クライアント側のプログラムは、以下の通り。ポイントは、
go passenger(1)
go passenger(2)
go passenger(3)
go passenger(4)
でスレッド処理をしてみたところです。
// client6.go ペアは server12.go
package main
import (
"flag"
"log"
"net/url"
"os"
"github.com/gorilla/websocket"
"math/rand"
"time"
)
// GetLoc GetLoc
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
//Address string `json:"address"`
}
var gl [100]GetLoc
var gl2 [100]GetLoc
var c [100](*websocket.Conn)
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
//var addr = flag.String("addr", "localhost:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options
func random(min, max float64) float64 {
return rand.Float64()*(max-min) + min
}
func main() {
rand.Seed(time.Now().UnixNano())
flag.Parse()
log.SetFlags(0)
go passenger(1)
go passenger(2)
go passenger(3)
go passenger(4)
time.Sleep(25 * time.Second)
}
func passenger(i int) {
/*
gl[i] = GetLoc{
ID: 0,
Lat: 35.653976,
Lng: 139.796821,
}
*/
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
log.Printf("connecting to %s", u.String())
var err error
c[i], _, err = websocket.DefaultDialer.Dial(u.String(), nil)
//c[i] = cc
if err != nil {
log.Fatal("dial:", err)
}
defer c[i].Close()
gl[i].ID = 0
gl[i].Lat = 35.653976
gl[i].Lng = 139.796821
if i != -100 {
log.Printf("before1 %d ID:%d", i, gl[i].ID)
log.Printf("before1 %d Lat:%f", i, gl[i].Lat)
log.Printf("before1 %d Lng:%f", i, gl[i].Lng)
}
//err = c.WriteJSON(mt, gl)
err = c[i].WriteJSON(gl[i])
if err != nil {
log.Println("write:", err)
}
//&(gl2[i]) = new(GetLoc)
err = c[i].ReadJSON(&(gl2[i]))
if i != -100 {
log.Printf("after1 %d ID:%d", i, gl2[i].ID)
log.Printf("after1 %d Lat:%f", i, gl2[i].Lat)
log.Printf("after1 %d Lng:%f", i, gl2[i].Lng)
}
if gl2[i].ID == 0 {
// 強制停止
os.Exit(1)
}
gl[i].ID = gl2[i].ID
for k := 0; k < 20; k++ {
gl[i].Lat += random(0.5, -0.5) * 0.00001 * 10 * 5
gl[i].Lng += random(0.5, -0.5) * 0.00002 * 10 * 5
if i != -100 {
log.Printf("before2 %d ID:%d", i, gl[i].ID)
log.Printf("before2 %d Lat:%f", i, gl[i].Lat)
log.Printf("before2 %d Lng:%f", i, gl[i].Lng)
}
err = c[i].WriteJSON(gl[i])
if err != nil {
log.Println("write:", err)
}
//gl2[i] := new(GetLoc)
err = c[i].ReadJSON(&(gl2[i]))
if i != -100 {
log.Printf("after2 %d ID:%d", i, gl2[i].ID)
log.Printf("after2 %d Lat:%f", i, gl2[i].Lat)
log.Printf("after2 %d Lng:%f", i, gl2[i].Lng)
}
time.Sleep(1 * time.Second) // 1秒休む
}
gl[i].ID = gl2[i].ID
gl[i].Lat = 999.9
gl[i].Lng = 999.9
log.Printf("before3 %d ID:%d", i, gl[i].ID)
log.Printf("before3 %d Lat:%f", i, gl[i].Lat)
log.Printf("before3 %d Lng:%f", i, gl[i].Lng)
err = c[i].WriteJSON(gl[i])
err = c[i].ReadJSON(&(gl2[i]))
log.Printf("after3 %d ID:%d", i, gl2[i].ID)
log.Printf("after3 %d Lat:%f", i, gl2[i].Lat)
log.Printf("after3 %d Lng:%f", i, gl2[i].Lng)
}
でもって、サーバ側に、
wsarecv: An existing connection was forcibly closed by the remote host.
がでてくるのですが、これがどうにも分からない。
サーバ側の出力コンソールは、こんな感じ
first ID:0
117 after gl = <-chan2_1
first Lat:0.000000
94
first Lng:0.000000
126
read: read tcp 127.0.0.1:8080->127.0.0.1:50368: wsarecv: An existing connection was forcibly closed by the remote host.
138
write: write tcp 127.0.0.1:8080->127.0.0.1:50366: wsasend: An existing connection was forcibly closed by the remote host.
after gl2.id = 3
after gl2.lat = 35.653909
after gl2.lng= 139.796333
145
148
113 before gl = <-chan2_1
86
final ID:3
final Lat:35.653909
final Lng:139.796333
94
write: write tcp 127.0.0.1:8080->127.0.0.1:50365: wsasend: An existing connection was forcibly closed by the remote host
とりあえず、忘れそうなので、自分の為にメモを残しておきます。
websocket.DefaultDialer.Dialもスレッド単位ごとに発行しているから分離しているから、良さそうなんだけどなぁ。Websocketの複数のエンドポイントを作って並列処理につっこむには、もう一頑張り必要、ということでしょう。
ちなみに「誰か、私を助けてくれたっていいんだからね!」と申し上げておきます。
golangにサーバ2つを搭載、と、一方通行の通信を試してみた件
取り敢えず、週末の成果(ちゃんと動くものではないので、そのまま使うことはお勧めしません)

"client1.go"の内容
package main
import (
"flag"
"log"
"net/url"
"github.com/gorilla/websocket"
)
// GetLoc GetLoc
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
//Address string `json:"address"`
}
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
//var addr = flag.String("addr", "localhost:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options
func main() {
flag.Parse()
log.SetFlags(0)
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
gl := GetLoc{
ID: 1,
Lat: 35.653976,
Lng: 139.796841,
}
log.Printf("ID:%d", gl.ID)
log.Printf("Lat:%f", gl.Lat)
log.Printf("Lng:%f", gl.Lng)
//err = c.WriteJSON(mt, gl)
err = c.WriteJSON(gl)
if err != nil {
log.Println("write:", err)
}
}
"server8.go"の内容
/*
// server7.go
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// 使い方
// go run server7.go (適当なシェルから)
// http://localhost:8080 (ブラウザ起動)
*/
package main
import (
"flag"
"html/template"
"log"
"net/http"
"github.com/gorilla/websocket"
)
// GetLoc GetLoc
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
//Address string `json:"address"`
}
//var addr = flag.String("addr", "localhost:8080", "http service address")
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options
func echo2(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
//_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
gl := new(GetLoc)
err := c.ReadJSON(&gl) // クライアントからのメッセージの受信(mtはクライアント識別子)
log.Printf("ID:%d", gl.ID)
log.Printf("Lat:%f", gl.Lat)
log.Printf("Lng:%f", gl.Lng)
if err != nil {
log.Println("read:", err)
break
}
}
}
func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
/*
for {
//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
if err != nil {
log.Println("read:", err)
break
}
// JSON(位置情報)を無理やり入れてみた
gl := GetLoc{
ID: 1,
Lat: 35.653976,
Lng: 139.796842,
}
//log.Printf("recv_serv: %s", gl)
//err = c.WriteJSON(mt, gl)
err = c.WriteJSON(gl)
if err != nil {
log.Println("write:", err)
break
}
}
*/
gl := GetLoc{
ID: 1,
Lat: 35.653976,
Lng: 139.796842,
}
//log.Printf("recv_serv: %s", gl)
//err = c.WriteJSON(mt, gl)
err = c.WriteJSON(gl)
if err != nil {
log.Println("write:", err)
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/echo2", echo2) // echo関数を登録 (サーバとして必要)
http.HandleFunc("/echo", echo) // echo関数を登録 (サーバとして必要)
http.HandleFunc("/", home) // home関数を登録
log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>PruneCluster - Realworld 50k</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>
<script src="http://kobore.net/PruneCluster.js"></script> <!-- これ、いずれローカルホストから取れるように換える -->
<link rel="stylesheet" href="http://kobore.net/examples.css"/> <!-- これも、いずれローカルホストから取れるように換える -->
<!-- goのテンプレートのローカルって、どこになるんだろう? -->
</head>
<body>
<div id="map"></div>
<script>
ws = new WebSocket("{{.}}"); // websocketの確立
/*
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
};
*/
//引数にはミリ秒を指定。(例:5秒の場合は5000)
function sleep(a){
var dt1 = new Date().getTime();
var dt2 = new Date().getTime();
while (dt2 < dt1 + a){
dt2 = new Date().getTime();
}
return;
}
var map = L.map("map", {
attributionControl: false,
zoomControl: false
}).setView(new L.LatLng(35.654543, 139.795534), 18);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
detectRetina: true,
maxNativeZoom: 18
}).addTo(map);
var leafletView = new PruneClusterForLeaflet(1,1); // (120,20)がデフォルト
/*
// 送信5発(3秒単位)を送信
ws.onopen = function (event) {
for (let i = 0; i < 1; i++){
ws.send("Ebata is great");
//print("send: Ebata is great");
//print("Start sleep");
sleep(1000);
//print("End sleep");
}
}
*/
var markers = [];
// 受信すると、勝手にここに飛んでくる
ws.onmessage = function (event) {
// データをJSON形式に変更
var obj = JSON.parse(event.data);
// データをマーカーとして登録
var marker = new PruneCluster.Marker(obj.lat, obj.lng);
markers.push(marker);
leafletView.RegisterMarker(marker);
// leafletView.ProcessView(); // 変更が行われたときに呼び出されなければなりません。
}
/*
// 100人分を登録する
var size = 1;
var markers = [];
for (var i = 0; i < size; ++i) {
var marker = new PruneCluster.Marker(35.654543 + (Math.random() - 0.5) * 0.00001 * size, 139.795534 + (Math.random() - 0.5) * 0.00002 * size);
markers.push(marker);
leafletView.RegisterMarker(marker);
}
*/
// ランダムウォークさせる
window.setInterval(function () {
for (i = 0; i < 1; ++i) {
//var coef = i < size / 8 ? 10 : 1;
var coef = 10;
var ll = markers[i].position;
ll.lat += (Math.random() - 0.5) * 0.00001 * coef;
ll.lng += (Math.random() - 0.5) * 0.00002 * coef;
}
leafletView.ProcessView(); // 変更が行われたときに呼び出されれなければならない
}, 500);
// サーバを止めると、ここに飛んでくる
ws.onclose = function(event) {
//print("CLOSE");
ws = null;
}
map.addLayer(leafletView);
</script>
</body>
</html>
`))
golang の templateを使って、PrumeClusterと取り敢えず繋いでみた
ようやく動いた。疲れた。JSONを送り込むのは、明日以降ね。
忘備録
カレントディレクトリの中にdistというディレクトリを作って、LeafletStyleSheet.css PruneCluster.d.ts PruneCluster.js を放り込んだカレントディレクトリの中にLeafletStyleSheet.css,PruneCluster.d.ts,PruneCluster.js,examples.cssを放り込んでおいた。- 上記はあまり意味なかったらしい(プログラムの下を御参照)。
使い方は、
>go run server5.go
http://localhost:8080
で起動する。
/*
// server5.go
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// 使い方
// go run server5.go (適当なシェルから)
// http://localhost:8080 (ブラウザ起動)
*/
package main
import (
"flag"
"html/template"
"log"
"net/http"
"github.com/gorilla/websocket"
)
// GetLoc GetLoc
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
//Address string `json:"address"`
}
//var addr = flag.String("addr", "localhost:8080", "http service address")
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options
func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
if err != nil {
log.Println("read:", err)
break
}
// JSONを無理やり入れてみた
gl := GetLoc{
ID: 1,
Lat: 35.653976,
Lng: 139.796842,
}
//log.Printf("recv_serv: %s", gl)
//err = c.WriteJSON(mt, gl)
err = c.WriteJSON(gl)
if err != nil {
log.Println("write:", err)
break
}
/*
log.Printf("recv_serv: %s", message) // 受信したメッセージの表示
err = c.WriteMessage(mt, message) // 受信したメッセージの返送
if err != nil {
log.Println("write:", err)
break
}
*/
}
}
func home(w http.ResponseWriter, r *http.Request) {
//homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
homeTemplate.Execute(w, "")
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/echo", echo) // echo関数を登録 (サーバとして必要)
http.HandleFunc("/", home) // home関数を登録
log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>PruneCluster - Realworld 50k</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>
<script src="PruneCluster.js"></script>
<link rel="stylesheet" href="examples.css"/>
</head>
<body>
<div id="map"></div>
<script>
var map = L.map("map", {
attributionControl: false,
zoomControl: false
}).setView(new L.LatLng(35.654543, 139.795534), 18);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
detectRetina: true,
maxNativeZoom: 18
}).addTo(map);
var leafletView = new PruneClusterForLeaflet(1,1); // (120,20)がデフォルト
var size = 100;
var markers = [];
for (var i = 0; i < size; ++i) {
var marker = new PruneCluster.Marker(35.654543 + (Math.random() - 0.5) * 0.00001 * size, 139.795534 + (Math.random() - 0.5) * 0.00002 * size);
markers.push(marker);
leafletView.RegisterMarker(marker);
}
window.setInterval(function () {
for (i = 0; i < size / 2; ++i) {
var coef = i < size / 8 ? 10 : 1;
var ll = markers[i].position;
ll.lat += (Math.random() - 0.5) * 0.00001 * coef;
ll.lng += (Math.random() - 0.5) * 0.00002 * coef;
}
leafletView.ProcessView();
}, 500);
map.addLayer(leafletView);
</script>
</body>
</html>
`))
ところが、今朝、PC断ち上げて再起動させてみたところ、昨日の状況に戻ってしまいました。
試しに、自分のサーバ(kobore.net)に、PruneCluster.jsとexamples.cssをアップして、さらにdistというディレクトリ掘って、LeafletStyleSheet.cssを放りこんでみたら動きました。
<script src="PruneCluster.js"></script>
→ <script src="http://kobore.net/PruneCluster.js"></script>
<link rel="stylesheet" href="examples.css"/>
→ <link rel="stylesheet" href="http://kobore.net/examples.css"/>
まあ、いずれ修正するとして、動くならなんでも良いので、このまま進めます。
JSON.parse()で、大文字が全部小文字にされるなんて、聞いてないんですけど
なんで、JSON.parse()で、パースができないのだろう? と3時間くらい悩んだあげく、大文字を小文字変換されていることに、ようやく気がつきました。
ひどくない?
気がつかなかった私が悪いの?
/*
// server4.go
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// 使い方
// go run server2.go (適当なシェルから)
// http://localhost:8080 (ブラウザ起動)
*/
package main
import (
"flag"
"html/template"
"log"
"net/http"
"github.com/gorilla/websocket"
)
// GetLoc GetLoc
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
//Address string `json:"address"`
}
//var addr = flag.String("addr", "localhost:8080", "http service address")
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options
func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
if err != nil {
log.Println("read:", err)
break
}
// JSONを無理やり入れてみた
gl := GetLoc{
ID: 1,
Lat: 35.653976,
Lng: 139.796842,
}
//log.Printf("recv_serv: %s", gl)
//err = c.WriteJSON(mt, gl)
err = c.WriteJSON(gl)
if err != nil {
log.Println("write:", err)
break
}
/*
log.Printf("recv_serv: %s", message) // 受信したメッセージの表示
err = c.WriteMessage(mt, message) // 受信したメッセージの返送
if err != nil {
log.Println("write:", err)
break
}
*/
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/echo", echo) // echo関数を登録 (サーバとして必要)
http.HandleFunc("/", home) // home関数を登録
log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
};
//引数にはミリ秒を指定します。(例:5秒の場合は5000)
function sleep(a){
var dt1 = new Date().getTime();
var dt2 = new Date().getTime();
while (dt2 < dt1 + a){
dt2 = new Date().getTime();
}
return;
}
ws = new WebSocket("{{.}}");
// 送信5発(3秒単位)を送信
ws.onopen = function (event) {
for (let i = 0; i < 5; i++){
ws.send("Ebata is great");
print("send: Ebata is great");
print("Start sleep");
sleep(1000);
print("End sleep");
}
}
// 受信すると、勝手にここに飛んでくる
ws.onmessage = function (event) {
//console.log(event.data);
print("RESPONSE: " + event.data);
var obj = JSON.parse(event.data);
// test code (JSON.parse()が大文字を無視することを確認するテスト)
//const json = '{"ID":1, "Lat":35.653976, "Lng":139.796842}';
//print("json:" + json);
//var obj = JSON.parse(json);
print("obj:" + obj);
print("ID:" + obj.id); // obj.IDなら読み取れない
print("Lat:" + obj.lat); // obj.Latなら読み取れない
print("Lng:" + obj.lng); // obj.Lngなら読み取れない
}
// サーバを止めると、ここに飛んでくる
ws.onclose = function(event) {
print("CLOSE");
ws = null;
}
</script>
</head>
<body>
<div id="output"></div>
</body>
</html>
`))
golang の templateを使って、5回のみのechoをやってみた
golangのtemplateを試してみたくて(javascriptに疲れてきて)、Webブラウザから一回だけメッセージを送信するプログラム(server2.go)を書いてみました。
# Echoプログラムはいままで使ってきたプログラムを使わせて貰っています。
「GolangでCUIでWebsocketを試したい」にドンピシャのソースコードはこちら
やっていることは、サーバを立ち上げて、ブラウザからhttp://localhost:8080を起動して、(1)WebSocketをオープンして、(2)テキストメッセージを"5つ"送付して、(3)サーバから戻ってきたメッセージを受信・表示して、(4)クローズしているだけ
/*
// server3.go
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// 使い方
// go run server2.go (適当なシェルから)
// http://localhost:8080 (ブラウザ起動)
*/
package main
import (
"flag"
"html/template"
"log"
"net/http"
"github.com/gorilla/websocket"
)
//var addr = flag.String("addr", "localhost:8080", "http service address")
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options
func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv_serv: %s", message) // 受信したメッセージの表示
err = c.WriteMessage(mt, message) // 受信したメッセージの返送
if err != nil {
log.Println("write:", err)
break
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/echo", echo) // echo関数を登録 (サーバとして必要)
http.HandleFunc("/", home) // home関数を登録
log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
};
//引数にはミリ秒を指定します。(例:5秒の場合は5000)
function sleep(a){
var dt1 = new Date().getTime();
var dt2 = new Date().getTime();
while (dt2 < dt1 + a){
dt2 = new Date().getTime();
}
return;
}
ws = new WebSocket("{{.}}");
// 送信5発(3秒単位)を送信
ws.onopen = function (event) {
for (let i = 0; i < 5; i++){
ws.send("Ebata is great");
print("send: Ebata is great");
print("Start sleep");
sleep(3000);
print("End sleep");
}
}
// 受信すると、勝手にここに飛んでくる
ws.onmessage = function (event) {
//console.log(event.data);
print("RESPONSE: " + event.data);
}
// サーバを止めると、ここに飛んでくる
ws.onclose = function(event) {
print("CLOSE");
ws = null;
}
</script>
</head>
<body>
<div id="output"></div>
</body>
</html>
`))
JSONのデータを直接受けるhtmlファイルを作成している途中の件
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さんから、以下の情報を頂きました。
多分、今日あたりからぶつかりそうな問題でした。
で、今ぶつかっている問題ですが、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>
From HTTP to HTTPS with Go の簡単翻訳
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("", ""))
この設定で注意すべき点がいくつかあります。
- http.ListenAndServeTLS() の代わりに server.ListenAndServerTLS() を使用します。
- サーバ証明書と鍵を tls.Config.GetCertificate 関数の中にロードします。
- サーバが信頼すべきクライアント CA 証明書のプールを作成します。
- 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
},
},
},
}
サーバと比較した場合の設定の違いに注目してください。
- tls.Configでは、サーバー上のClientCAの設定に対して証明書プールをロードするためにRootCAを使用しています。
- tls.Config.GetClientCertificate を使用して、サーバー上の tls.Config.GetCertificate に対してクライアント証明書をロードしています。
- 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のこの記事。
なるべくGoのパッケージを使わないで、RESTサーバとクライアントを作って、JSONのデータ送受信してみる
サーバ側 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