gRPC で認証処理を実装する

## grpc で認証処理を実装する
- [interceptor](https://github.com/grpc/grpc-go/tree/master/examples/features/interceptor)を使って実装すると、良さそう
    - interceptor 自体は middleware 的な立ち回りをしてくれる

### 実装
- grpc-ecosystem にライブラリが用意されているのでそれを使っていく
  - [go-grpc-middleware/auth](https://godoc.org/github.com/grpc-ecosystem/go-grpc-middleware/auth)
  - 基本的には Client に PW を付与して、それをリクエスト時に投げてもらい、interceptor 内でチェックします
  - 実際に書くのは以下のようなコード

```go

grpcServer = grpc.NewServer(
			grpc_middleware.WithUnaryServerChain(
				grpcMetrics.UnaryServerInterceptor(),
				grpc_recovery.UnaryServerInterceptor(opts...),
                                // ここでChain
				grpc_auth.UnaryServerInterceptor(AuthFunc),
			),
		)

// AuthFunc は go-grpc-middleware/auth で使用する認証用の関数
// この関数自体は別のパッケージに切り出しても良い
func AuthFunc(ctx context.Context) (context.Context, error) {
	// ここでClientから投げられた認証ヘッダの値を取得
	key, err := grpc_auth.AuthFromMD(ctx, "bearer")
	if err != nil {
		return nil, err
	}

	// ここでは"password"ハードコーディングだけど、この手前でDBアクセスしたりして任意のパスを取得したりしても良い
	if key != "password" {
		return nil, grpc.Errorf(codes.Unauthenticated, "invalid api password")
	}

	// context に Client の情報を詰めて返す
	newCtx := context.WithValue(ctx, "result", "ok")
	return newCtx, nil
}
```

- `grpc_auth.UnaryServerInterceptor(AuthFunc)`に渡す `AuthFunc` は以下の型を返す関数なら中身はどうでもいいっぽい
    - `type AuthFunc func(ctx context.Context) (context.Context, error)`
        - https://godoc.org/github.com/grpc-ecosystem/go-grpc-middleware/auth#AuthFunc
- `AuthFunc`の中で、やりたいように認証処理を実装していく
    - `AuthFromMD`は指定した文字列をキーにリクエスト認証ヘッダからバリューを取得してくれる
        - MD は _MetaData_ かと思われる
    - 認証ヘッダを使わない場合は、普通にhttpリクエストヘッダを使ってバリューを取得する
    - よければ context を返し、ダメならエラーを返すとライブラリ側でうまいこと`UnaryServerInterceptor`かエラーを返してくれる

```github.com/grpc-ecosystem/go-grpc-middleware/blob/master/auth/auth.go
func UnaryServerInterceptor(authFunc AuthFunc) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		var newCtx context.Context
		var err error
		if overrideSrv, ok := info.Server.(ServiceAuthFuncOverride); ok {
			newCtx, err = overrideSrv.AuthFuncOverride(ctx, info.FullMethod)
		} else {
			newCtx, err = authFunc(ctx)
		}
		if err != nil {
			return nil, err
		}
		return handler(newCtx, req)
	}
}
```

### 使ってみる
- grpcurl してみて、ちゃんと認証処理が挟まるかチェック
    - `grpcurl -plaintext -import-path . -proto proto/hello.proto -d '{"name": "hoge"}' -H "Authorization: bearer password" localhost:5000 hello.HelloService/Hello`

### 所感
- `grpc.UnaryServerInterceptor` を返す部分と、認証ヘッダからバリューを取得する処理を自前で書かなくていい分楽かな
- とはいえ全部自分で実装してもそんなに手間でもない気がする

## 特定メソッドだけ認証をスキップしたい
- 例えばヘルスチェックは認証をスキップしたい、みたいな場合は[ServiceAuthFuncOverride](https://godoc.org/github.com/grpc-ecosystem/go-grpc-middleware/auth#ServiceAuthFuncOverride)が使える
    - 以下の interface の実装が grpc server に実装されていれば、`AuthFunc`より優先的にこちらがコールされる（オーバーライドと書いてあるが、実際には実装があるか否かの分岐が入っているっぽい）

```go

type ServiceAuthFuncOverride interface {
    AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error)
}
```

- `fullMethodName`が grpc のサービスとメソッドの情報を持っているので、少なくともメソッドの粒度で何かしらの分岐が可能

```go

func (s *Server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
	// HealthCheck がコールされた場合は認証のチェックを skip する
    if fullMethodName == "/hoge_proto.HogeService/HealthCheck" {
		return ctx, nil
	}
    
    // 認証チェック処理
    // ~~省略~~
    
    return ctx, nil
    }
```