1
0

More than 1 year has passed since last update.

Go Flow Levee で機密データのロギングを検出する

Last updated at Posted at 2022-01-25

はじめに

セキュリティ上問題となるプログラミングエラーの一つに、誤って機密データをログに記録してしまうというものがあります 1。データ構造やロジックが複雑になるほど、レビューでもこの種のエラーは見落としやすくなります。Kubernetes でも何度かこの問題が起きており、Go Flow Levee というツールを使った静的解析による対策が導入されています。

本記事では、Go 言語用の静的解析ツール「Go Flow Levee」と、Kubernetes での導入事例についてご紹介します。

Go Flow Levee とは

Go Flow Levee は、Go 言語用の静的解析ツールです。機密データなどの「ソース」が「サニタイザー」を通さずにログ関数などの「シンク」に到達していないかチェックできます。「ソース」をコピーした変数も「テイント」が付与されて伝搬が分析されることで、同様に「シンク」への到達を検出できます。2

認証情報などの機密データが誤ってログに記録されるようなケースを検出できるほか、ユーザからの入力に対してのテイントチェック(Taint Checking) としての使い方も可能です。

Go Flow Levee の基本的な使い方

Go Flow Levee の Quickstart のドキュメント をベースに、基本的な使い方をご紹介します。

検査するコード

以下の例 (quickstart.go) では、Authentication 構造体が Password というフィールドに機密データを保持しています。このコードでは log.Printf で、この構造体をログ出力しているため、機密データをログに記録してしまう問題があります。

quickstart.go
package quickstart

import "log"

type Authentication struct {
    Username string
    Password string // 機密データ
}

func authenticate(auth Authentication) (*AuthenticationResponse, error) {
    response, err := makeAuthenticationRequest(auth)
    if err != nil {
        // 機密データがロギングされている
        log.Printf("unable to make authenticated request: incorrect authentication? %v\n", auth)
        return nil, err
    }
    return response, nil
}

検査の設定

この例では、コードの検査に以下の 2 つの情報を設定します。この情報でテイント伝搬(taint propagation)の分析が行われ、機密データが指定した関数に渡されていないかチェックされます。

  • どの型が機密データを含むか (ソース)
    • 例では Authentication 構造体の Password フィールド
  • 機密データをそのまま渡されてはいけない関数 (シンク)
    • 例では log.Printf 関数

ソースの設定

機密データを含む型を表すソース(Source) の指定には、2 種類の方法があります。

  • 機密データを含む各フィールドに任意のタグを指定する
    • コードの変更が必要だが、タグで一括に指定できる
  • 機密データを含むフィールド名、パッケージ名、型名を記述する
    • コードの変更はないが、個別に指定が必要

タグで指定する

タグで指定する場合、まず機密データを表す任意のタグを定義し、設定ファイルに記載します。以下の例では、datapolicy のキーに、secret という値が設定されていたときに機密データとして扱われます。

analyzer_configuration.yaml
FieldTags:
  - Key: datapolicy
    Value: secret

機密データのフィールドにこのタグを設定します。

type Authentication struct {
  Username string
  Password string `datapolicy:"secret"`
}

フィールド名等で記述する

機密データを含むフィールド名、パッケージ名、型名を記述することもできます。外部パッケージなどタグを付与できない場合は、この方法を利用します。PackageRE, TypeRE, FieldRE を使って、正規表現を用いることもできます。

analyzer_configuration.yaml
Sources:
  - Package: github.com/google/go-flow-levee/guides/quickstart
    Type: Authentication
    Field: Password

この方法ではコードの変更は必要ありませんが、フィールド名などが変わった場合に忘れずこの設定も更新する必要があります。

シンクの設定

機密データが渡されてはいけない関数をシンク (Sink) として設定します。以下の例では log パッケージの Printf を対象としています。PackageREMethodRE を使うと正規表現を用いることもできます。

analyzer_configuration.yaml
Sinks:
  - Package: log
    Method: Printf

検査を実行する

まずは go-flow-levee (levee バイナリ)をインストールします。

go get github.com/google/go-flow-levee/cmd/levee

前述のサンプルコードが含まれる github.com/google/go-flow-levee/guides/quickstart を準備しておきます。

git clone git@github.com:google/go-flow-levee.git
cd go-flow-levee/guides/quickstart

go vetlevee バイナリを指定して実行します。設定は analyzer_configuration.yaml を参照してください。

go vet -vettool=$(which levee) -config=$(realpath analyzer_configuration.yaml) ./...

問題箇所が検出されました。:tada:

# github.com/google/go-flow-levee/guides/quickstart
./quickstart.go:14:13: a source has reached a sink
 source: ./quickstart.go:11:19

テイント伝搬の確認

quickstart.go を以下のように編集します。Password フィールドの情報を一度 data 変数に入れて、間接的に機密情報をログ出力するようにします。

-               log.Printf("unable to make authenticated request: incorrect authentication? %v", auth)
+               data := auth.Password
+               log.Printf("unable to make authenticated request: incorrect authentication? %v", data)

間接的な参照でも、正しく問題箇所が検出されました。:tada:

$ go vet -vettool=$(which levee) -config=$(realpath analyzer_configuration.yaml) ./...
# github.com/google/go-flow-levee/guides/quickstart
./quickstart.go:15:13: a source has reached a sink
 source: ./quickstart.go:11:19

以下のように、data 変数の代入を Username フィールドにすると、機密データがログに出力されなくなります。

-               data := auth.Password
+               data := auth.Username

問題がなくなったため、結果が表示されなくなりました。

$ go vet -vettool=$(which levee) -config=$(realpath analyzer_configuration.yaml) ./...
# 何も表示されない

Kubernetes での導入事例

Kubernetes での導入は KEP-1933: Defend Against Logging Secrets via Static Analysis という KEP で提案されました。現在は、PR 時のチェック (prow) の Job の一つ (pull-kubernetes-verify-govet-levee) として実行されています。

Kubernetes の設定

Go Flow Levee の設定ファイルは kubernetes/kuberneteshack/testdata/levee/levee-config.yaml に置かれています。以下のような設定が記述されています。

チェックを試してみる

levee のチェックを試すため、Kubernetes の pkg/kubelet/client/kubelet_client.goに、以下のログを追加してみます。ログに記録している KubeletClientConfig 構造体は BearerToken フィールドが機密情報を含みます

diff --git a/pkg/kubelet/client/kubelet_client.go b/pkg/kubelet/client/kubelet_client.go
index 20a4eb9df8b..d90eb82c7cc 100644
--- a/pkg/kubelet/client/kubelet_client.go
+++ b/pkg/kubelet/client/kubelet_client.go
@@ -30,6 +30,7 @@ import (
        "k8s.io/apiserver/pkg/server/egressselector"
        restclient "k8s.io/client-go/rest"
        "k8s.io/client-go/transport"
+       "k8s.io/klog/v2"
        nodeutil "k8s.io/kubernetes/pkg/util/node"
 )

@@ -173,6 +174,7 @@ type NodeConnectionInfoGetter struct {

 // NewNodeConnectionInfoGetter creates a new NodeConnectionInfoGetter.
 func NewNodeConnectionInfoGetter(nodes NodeGetter, config KubeletClientConfig) (ConnectionInfoGetter, error) {
+       klog.V(3).Infof("This log line leaks sensitive data: %v", config)
        transport, err := MakeTransport(&config)
        if err != nil {
                return nil, err

levee の実行スクリプト hack/verify-govet-levee.sh を使って Go Flow Levee を実行します。今回追加した、問題のあるログが検出されました。:tada:

$ hack/verify-govet-levee.sh
# ... 省略

# k8s.io/kubernetes/pkg/kubelet/client
pkg/kubelet/client/kubelet_client.go:177:17: a source has reached a sink
 source: pkg/kubelet/client/kubelet_client.go:176:52
make: *** [vet] Error 1

おわりに

誤って機密データをログに記録してしまう問題は、CWE-532: Insertion of Sensitive Information into Log File として共通脆弱性タイプ一覧にも分類されています。コードベースが大きくなるほど、こういった問題がないことを確認するのは難しくなります。不安があれば、一度 Go Flow Levee を試してみてはいかがでしょうか。


  1. 共通脆弱性タイプ一覧 CWE では、この問題を CWE-532: Insertion of Sensitive Information into Log File として分類しています。 

  2. Taint propagation analysis (テイント伝搬解析) と呼ばれる手法です。「テイント (taint)」「シンク (sink)」などの用語もこの分野の専門用語になります。 

1
0
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
0