YubiSwitch を使って YubiKey の誤作動を防ぐ方法
皆さん YubiKey 使ってますか?
私は YubiKey 5C Nano を使っています。
Yubico YubiKey 5C Nano Two Factor Authentication Security Key - Black - USB-C[cb]
- ジャンル: パソコン・周辺機器 > パソコンパーツ > その他記憶装置(内蔵型) > その他
- ショップ: コンビニマート
- 価格: 10,560円
この YubiKey 5C Nano はちょっと触れただけで One Time Password (以下 OTP) を発行するという物でサクッと入力できて便利な反面、困ったこともあります。

具体的には
- Slack に OTP を投稿してしまう。
- Jira のキーボードショートカットに引っかかり、新しいチケットを作成してしまう。
- Twitter のキーボードショートカットに引っかかり、通知に飛んでしまう。
などです。
今回はそれを解決します。
続きを読むAWS CloudFormation の dry-run を cli でやるコマンド
AWS CloudFormation で dry-run して差分が見たい場合、以下のように create-change-set と describe-change-set のコマンドで見れる。
$ aws cloudformation create-change-set --stack-name test-stack --change-set-name test-stack-dry-run --template-body file://test-stack.yml $ aws cloudformation describe-change-set --stack-name test-stack --change-set-name test-stack-dry-run
以下に例を示す。
続きを読むgRPC(Go)でタイムアウト時にio.EOFエラーになる件を調査した
WEB+DB PRESS Vol.110のgRPC特集がおもしろくて手を動かしながら読んでいる。
タイムアウト設定の事も書かれていて、そこに
設定した期限を過ぎた場合、クライアントスタブは応答を待ち受けるのをやめてステータスコードDeadlineExceeded(4)として処理を終了します。
とあったので試したところ、想定と異なった挙動になったのでメモしておく。
現象
一部省略するが、メインとなるコードは下記である。
c := pb.NewFileServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
stream, err := c.Upload(ctx)
if err != nil {
log.Fatalf("Could not upload file: %v.", err)
}
buf := make([]byte, 1000*1024)
for {
n, err := fs.Read(buf)
if err == io.EOF {
break
} else if err != nil {
log.Fatalf("Could not read file: %v.", err)
}
if err := stream.Send(&pb.FileRequest{Name: filepath.Base(name), Data: buf[:n]}); err != nil {
log.Fatalf("Could not send file: %v.", err)
}
}
res, err := stream.CloseAndRecv()
if err != nil {
log.Fatalf("Could not receive response file: %v.", err)
}
この時、私は stream.Send の部分で DeadlineExceeded エラーが返ると期待していたが実際は io.EOF エラーだった。
調査
気になったのでgrpc-goのコードを辿って調査した。
長くなるので重要だと思った箇所を記載する。
また、私の解釈としてStreaming RPCには接続保持側とストリーミング通信側があると解釈しているので2つに分けて記載する。
例えば上記コードで言うと Upload() が接続保持側で Send() がストリーミング通信側である。
接続保持側
Contextのハンドリングをしているのはこちら側
ctx.Done()をハンドリングしている所。- stream.go#L323
- まずここで
contextからタイムアウトの通知を受け取る。 - そして
cs.finish(toRPCErr(ctx.Err()))が実行される。 - ここで渡されているエラーは
DeadlineExceededである。
- まずここで
- stream.go#L323
CloseStream()- stream.go#L928
- ここで
ClientTransportのStreamを閉じている。 - 実装は
internal/transport/http2_client.goにある。
- ここで
- stream.go#L928
s.swapState(streamDone)- internal/transport/http2_client.go#L698
Streamはstateを持っている。- その
Streamを Doneに書き換えている。 stateはWriteが開始される前に見るだけなのでDoneに書き換えてもWrite中の処理を止めることはできない。
- internal/transport/http2_client.go#L698
close(s.done)- internal/transport/http2_client.go#L751
- Write中の処理を止めるため
close(s.done)を行う。
- Write中の処理を止めるため
- internal/transport/http2_client.go#L751
ここまでで接続保持側の終了処理が完了。
ストリーミング通信側
SendMsg()- stream.go#L663
Sendが呼んでいる関数。
- stream.go#L663
Write()- stream.go#L828
- ここから
internal/transport/http2_client.goに飛ぶ。
- ここから
- stream.go#L828
if s.getState() != streamActive- internal/transport/http2_client.go#L822
stateがActiveでない場合はここに入ってerrStreamDoneが返る。- ここに入るのはWrite開始前の場合。
- internal/transport/http2_client.go#L822
get- internal/transport/http2_client.go#L840
- Write中の場合はここに来る。
- ここから
internal/transport/flowcontrol.goに飛ぶ。
- internal/transport/http2_client.go#L840
case <-w.done- internal/transport/flowcontrol.go#L62
- 接続保持側で
close(s.done)するとここに入ってerrStreamDoneが返る。
- 接続保持側で
- internal/transport/flowcontrol.go#L62
上記のように DeadlineExceeded ではないエラーが返り終了する。
ストリーミング通信側はContextを見るのではなく、接続保持側からの操作によって終了する。
解決策
Send でエラーが返ってきた場合、 Fatal 等で終了させずに break させて CloseAndRecv() まで実行してエラーハンドリングを行うと良い。
Send のエラーは警告くらいの気持ちで Print だけしておけば良いと思う。
for { n, err := fs.Read(buf) if err == io.EOF { break } else if err != nil { log.Fatalf("Could not read file: %v.", err) } if err := stream.Send(&pb.FileRequest{Name: filepath.Base(name), Data: buf[:n]}); err != nil { log.Printf("Could not send file: %v.", err) break } } res, err := stream.CloseAndRecv() if err != nil { log.Fatalf("Could not receive response file: %v.", err) }
変更箇所は2行。
if err := stream.Send(&pb.FileRequest{Name: filepath.Base(name), Data: buf[:n]}); err != nil {
- log.Fatalf("Could not send file: %v.", err)
+ log.Printf("Could not send file: %v.", err)
+ break
}
するとこんな感じでログが出る。
2019/04/29 08:18:57 Could not send file: EOF. 2019/04/29 08:18:57 Could not receive response file: rpc error: code = DeadlineExceeded desc = context deadline exceeded. exit status 1
これでタイムアウトのときに DeadlineExceeded が返るようになった。
まとめ
- タイムアウトだった場合でも
Send()のエラーはio.EOFが返る。 Send()でエラーが出てもそこで終了せずにCloseAndRecv()まで実行してエラーを確認。- タイムアウトの場合は
CloseAndRecv()のエラーでDeadlineExceededエラーが取れる。
私はgRPCを仕事でも使ったのだが、protoファイルを書くだけでサーバのインタフェースとクライアントのAPIコール関数を生成してくれる。
サーバはインタフェースの実装を書くだけだし、クライアントは関数を呼び出す感覚でAPIコールができる。
これはすごく楽なのではないだろうか。
特にマイクロサービスアーキテクチャを採用している場合にはおすすめです!
スプレッドシートのデータを取得する
slack botでスプレッドシートからデータ取得する場面があったので調査して検証がてらgolangで実装してみた。
気をつける所とかメモ的に書いておく。
作ったのはスプレッドシートで重み付け抽選を行うアプリ。
認証
認証方法はAPIキー、OAuth、サービスアカウントの3つがある。

APIキー
公開されたスプレッドシートしか読み込めないようだったので今回はパス。
OAuthクライアントID
一度ユーザがブラウザを開いて認証する必要がある。
bot用には使えないなーと思ったのでパス。
サービスアカウント
新しくGoogleアカウントを発行するイメージ。
スプレッドシートに招待して使う。
招待してしまえばずっと使えるためbotには合っていると思い、今回はサービスアカウントを使用することにした。
使用方法
発行したjsonキーファイルの中から client_email を取り出す。
$ cat /path/to/key.json | jq -r .client_email test@quickstart-1551059800000.iam.gserviceaccount.com
データを取得したいスプレッドシートに移動して右上の共有ボタンをクリック。
『他のユーザと共有』ダイアログを開き、先程取得した client_email を追加する。

実装
実装側の認証には google.JWTConfigFromJSON を使う。
キーjsonファイルの中身とスコープ(今回は取得なのでreadonly)を渡す。
conf, err := google.JWTConfigFromJSON(
jsonKeyBytes,
"https://www.googleapis.com/auth/spreadsheets.readonly"
)
返ってきた conf からhttpクライアントを取得し、 sheets.New に渡して Service を取得する。
srv, err := sheets.New(conf.Client(ctx))
あとは取得したサービスを使ってGetすればいい。
スプレッドシートIDと取得するデータのレンジ(下記の例では A2:B )を指定する。
( A2:B はA2セルからB列の最後のセルまでという意味)
resp, err := srv.Spreadsheets.Values.Get(spreadsheetID, "A2:B").Context(ctx).Do() var items []lotteryItem for _, row := range resp.Values { name := row[0].(string) weight, err := strconv.Atoi(row[1].(string)) items = append(items, lotteryItem{name: name, weight: weight}) }
※エラーハンドリングは省略してあります。
列方向への取得
デフォルトでは行方向の取得になるが、列方向に取得したい場合は MajorDimension に COLUMNS を指定すればよい。
resp, err := srv.Spreadsheets.Values.Get(spreadsheetID, "A2:B").Context(ctx).MajorDimension("COLUMNS").Do()
参考
Nuxt.js×Express使用時のhost指定でハマった
Nuxt.js×ExpressのDockerfileを書いている時に、コンテナ外からアクセスを受け付けようと思って server/index.js の host の値を 127.0.0.1 を 0.0.0.0 に書き換えた。
host = process.env.HOST || '0.0.0.0',
しかしコンテナ外からアクセスできなかった。
よく見ると nuxt.options.server で上書きされているようだった。
const {
host = process.env.HOST || '0.0.0.0',
port = process.env.PORT || 3000
} = nuxt.options.server
(最近のjsに慣れてなくて、この記法の意味調べるのに手間取った...)
nuxt.options.server のデフォルト値
環境変数 HOST に 0.0.0.0 を指定するとコンテナ外からのアクセスは通った。
まとめ
ENV HOST 0.0.0.0
追記
結局上書きされるなら server/index.js で代入してる所無駄じゃない?と思ってPR出してみた。
追記2
PRマージされてた😊