SSTエンジニアブログ

SSTのエンジニアによるWebセキュリティの技術を中心としたエンジニアブログです。

Go言語とGinに触ってみた

はじめに

はじめまして!
セキュリティに強くなるつもりが、強くなったのは筋力だった!
新卒入社2年目の脆弱性診断員、秋本です。

 最近、診断業務でGo言語(以下Go)製のWebアプリに触れる機会がありました。Goについて全然知らなかったので調べてみたところ、Goのフレームワークは使いやすく整っておりコミュニティも活発みたいです。今後もGo製のWebアプリが出てくると思うので、脆弱性診断員としての知見を広めるという目的で、最近はGoの勉強をしています。
 ただ、Goは比較的新しい言語ゆえに、日本語のドキュメントがそれほど豊富でないのが難点です。そんなわけで、今回は私がGoに触れるにあたってつまずいた点とその解決策を共有できればなと思いブログにしました。初学者レベルですがお付き合いいただけると幸いです。

Goのインストール

今回は以下の環境で進めていきます。

構成 詳細
ホストOS Windows10
VM VirtualBox 6.1.6
Vagrant 2.2.7
ゲストOS Ubuntu18.04.4 LTS
ダンベル 10kg

 
まずはGoをインストールします。

$ sudo apt install golang

Goのインストールが無事終わったので、とりあえずバージョンを確認しておきます。

$ go version
go version go1.10.4 linux/amd64

Ginのインストール

 Goはgo get <パッケージ名>とすることで外部のパッケージを取得することができます。今回はGoのWebアプリケーションフレームワークであるGinに触れる予定なので、

$go get github.com/gin-gonic/gin

を実行します。 go getで取得したパッケージはGOPATH/src/配下にインストールされ、ソースコード内からimportで呼び出せるようになります。
めっちゃ簡単!

Webサーバを立ち上げてみる

公式のサンプルを参考に、アクセスするとJSONを返すコードを動かしてみます。
example.go

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run(":8000")
}

 Goはコンパイルが必要な言語ですが、go runコマンドを使うことでコンパイルから実行まで一気にやってくれます。ひとまず実行してみます。

$ go run example.go 
# github.com/gin-gonic/gin
go/src/github.com/gin-gonic/gin/context.go:77:11: undefined: http.SameSite

context.go:77:11:という、触った覚えのない箇所でundefined: http.SameSiteと怒られました。
 

解決策の調べ方

 以下、解決に至るまでに私が行った手順です。

  • エラーメッセージをそのまま検索する
  • 重要なワードを抜き取って検索する
  • 検索結果から別の言語についての記事を無視する
  • タイトルにメインの検索ワードが含まれている記事を片っ端から見る
  • 公式のドキュメントと照らし合わせる

 ↑の手順でいろいろと調べたところ、直接解決策になるようなものは見つかりませんでした。しかし、検索結果の一部でこんな記述を見つけました。

Samesite flag only support on Golang v1.11

Ginの公式のドキュメントを見に行くと、

The first need Go installed (version 1.11+ is required)

と記載されていました。バージョン1.11未満のGoで定義されているCookieはhttp.SameSite属性に対応していないみたいです。

バージョンを上げる

 今回apt installでインストールしたGoは1.10.4でした。現在インストール済みのGoとはおさらばして、こちらから最新版をダウンロードしてくることにします。  

$ wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz
$ tar -xzf go1.14.2.linux-amd64.tar.gz
$ export PATH=$PATH:<go/binのパス>
$ go version
go version go1.14.2 linux/amd64

バージョン1.14.2のGoをインストールすることができました。もう一度Ginをgo getしましょう。

$ go get github.com/gin-gonic/gin
package github.com/gin-gonic/gin: cannot download, $GOPATH must not be set to $GOROOT. For more details see: 'go help gopath'

ダメみたいですね。
罰としてダンベルの重量も10から14に上げておきます。  

GOPATHの沼

 先ほどのエラーメッセージを見ると、GOPATHなるものをGOROOTと同じものに設定しているせいでGinがダウンロードできないということがわかります。それぞれについて調べてみると、GOROOTはgoを配置したディレクトリ、GOPATHは外部パッケージのリソースを保存するディレクトリとして扱われるということが分かりました。そこで、インストール済みのgoを/usr/local/goへ移し、以下のパスを通しておくことにしました。

export GOROOT="/usr/local/go"
export GOPATH="/home/vagrant/go"
export PATH="$PATH:$GOROOT/bin"

その後、$go get github.com/gin-gonic/ginを実行すると…

$ ls $GOPATH/src/github.com/
gin-contrib  gin-gonic  go-playground  golang  leodido  mattn  ugorji

無事、Ginのインストールに成功しました。

Webサーバを立ち上げてみる(2回目)

 ようやくGinを使ったコードを実行するところまで来ました。先ほどのexample.goを改めて実行します。

$ go run example.go 
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8000
[GIN] 2020/04/13 - 04:55:57 | 404 |         708ns |    192.168.33.1 | GET      "/"
[GIN] 2020/04/13 - 04:56:01 | 200 |      87.586µs |    192.168.33.1 | GET      "/ping"

おっ…

f:id:sstakimoto:20200413135936p:plain
動いた!!

つまずきのまとめ

Goをインストールして実際にプログラムを動かすまでに以下のポイントがありました 。

  • Ginはバージョン1.11以上が必要
    (1.11未満はSameSite属性に対応していないようなので他のフレームワークも同じかも)
  • GOPATHとGOROOTの設定
    (パッケージ管理ツールでインストールした場合は不要っぽい)

公式のドキュメントをしっかり読んでおけば陥らなかった失敗ですが、おかげで日本語でまとめる機会ができました。同じようなポイントでつまずいたGo入門者の方のお役に立てれば幸いです。

おまけ

 過去の診断でCookieの属性不備を指摘する際、その対策方法について調査する機会がありました。Go (フレームワークなし)ではCookieの属性は明示的に宣言しない限り無効になっています。今のところセキュリティ会社っぽい要素がないので、おまけとしてCookieの操作方法を共有します。

ドキュメントより、"net/http"パッケージで

type Cookie struct {
    Name  string
    Value string

    Path       string    // optional
    Domain     string    // optional
    Expires    time.Time // optional
    RawExpires string    // for reading cookies only

    // MaxAge=0 means no 'Max-Age' attribute specified.
    // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
    // MaxAge>0 means Max-Age attribute present and given in seconds
    MaxAge   int
    Secure   bool
    HttpOnly bool
    SameSite SameSite // Go 1.11
    Raw      string
    Unparsed []string // Raw text of unparsed attribute-value pairs
}

が定義されているので、このCookieの構造体を宣言する際に以下のように初期化します。

test_cookie := &http.Cookie{
               Name:      "test",
               Value:     "hoge",
               Secure:    true,
               HttpOnly:  true,
}

 これにより、test_cookieという名前のCookieのSecure属性とHttpOnly属性が有効になります。それぞれの意味については割愛しますが、HttpOnly属性を有効にする意味については先日投稿されたXSSについてのブログが参考になるかと思います。
techblog.securesky-tech.com

参考

go-tour-jp.appspot.com
Goの基本を勉強できるWebサイトです。
オススメ。

Documentation - The Go Programming Language
公式のドキュメントが正解なので、かなりお世話になります。

ドキュメント - Go 言語
↑が日本語に翻訳されてました。
まだよく見ていないので未翻訳の箇所があるかもしれません。

おわりに

今回はGoをインストールして簡単なコードを実行するところで終わってしまいました。機会があれば、Goでこんなことできるよ!という記事も共有していけたらと思います。