LoginSignup
7
2

More than 5 years have passed since last update.

chromedpでChromeの特定のリクエストをinterceptする(Chrome DevTools Protocol)

Posted at

Chromeを起動して特定のURLに対するリクエストだけinterceptしてSAMLレスポンスを取り出すCLIをgolangで作りたかったのですが、割と苦戦したのでメモ。

概要

golangからChromeを操作しようとすると agoutichromedp が出てくるのですが、これらはChromeの操作を全部自動化しようという話がほとんどです。
今回自分は特定のページに飛ばしたあとユーザにある程度操作させて、特定のリクエストのときだけ処理したかったのですがググっても簡単には出来なかったので書いておきます。

詳細

具体的には以下のツールでAzure ADでのログインをユーザにやらせて、AWSにSAMLレスポンスを投げるときにそれだけ取り出したかったです。
https://github.com/knqyf263/azaws

記事はこれ
https://qiita.com/knqyf263/items/acca73ca4316b41d901f

結論から言うとchromedpを使いました。chromedpはChrome DevTools Protocolを使ってChromeを色々操作できるgolangのライブラリです。examplesもあるのでスクリーンショットを撮りたい、とかは簡単にできます。
ですが、chromedpでは何故かnetwork eventに関するcallback等が設定できません(2018/01/08現在)。
https://github.com/chromedp/chromedp/issues/180

Chrome DevTools Protocolのドキュメントを見ると getRequestPostData とかいかにも欲しいメソッドが定義されていて、これこれ!と思ったのですがchromedpでは使えません。
https://chromedevtools.github.io/devtools-protocol/tot/Network

それで困っていたのですが、リポジトリを眺めていたらcdprotoというリポジトリの方にtypesやeventsは全て定義されていました。これは自動生成されているので全てあるっぽいです。
じゃあchromedpの拡張をすればすぐ出来そうだな、と思ってchromedpのソースコードを読んでいたのですが、冷静に考えて同じこと思ってる人いるでしょという気分になったのでNetworhkのevents名などでググったらIssueが見つかりました。
https://github.com/chromedp/chromedp/issues/252

自分が見つけた時点では2週間ほど前にそのまま使えるexampleを貼っていてくれた人がいて、それを丸ごとパクりました。12月に作ろうと思ったら途中でやめてたかもしれないのでラッキーでした。

あとはそれを改修して作ったコードが以下です。抜粋して必要な箇所のみ書いてるのでそのままでは動かないです。


func devToolHandler(s string, is ...interface{}) {
    go func() {
        for _, elem := range is {
            var msg cdproto.Message
            // The CDP messages are sent as strings so we need to convert them back
            json.Unmarshal([]byte(fmt.Sprintf("%s", elem)), &msg)
            msgChann <- msg
        }
    }()
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // create chrome instance
    c, err := chromedp.New(ctx,
        chromedp.WithRunnerOptions(
            runner.Flag("user-data-dir", userDataDir),
            runner.Flag("disable-infobars", true)),
        chromedp.WithLog(devToolHandler))
    if err != nil {
        return err
    }

    err = c.Run(ctx, network.Enable())
    if err != nil {
        return err
    }

    loginURL := `https://login.microsoftonline.com/dummy/saml2?SAMLRequest=dummy`

    err = c.Run(ctx, chromedp.Navigate(loginURL))
    if err != nil {
        return err
    }

    err = c.Run(ctx, chromedp.ActionFunc(func(_ context.Context, h cdp.Executor) error {
        for {
            var msg cdproto.Message
            select {
            case <-ctx.Done():
                return ctx.Err()
            case msg = <-msgChann:
            }

            switch msg.Method.String() {
            case "Network.requestWillBeSent":
                var reqWillSend network.EventRequestWillBeSent
                json.Unmarshal(msg.Params, &reqWillSend)
                if reqWillSend.Request.URL != "https://signin.aws.amazon.com/saml" {
                    continue
                }
                form, err := url.ParseQuery(reqWillSend.Request.PostData)
                if err != nil {
                    return errors.Wrap(err, "Failed to parse query")
                }
                samlResponse, ok := form["SAMLResponse"]
                if !ok || len(samlResponse) == 0 {
                    return errors.Wrap(err, "No such key: SAMLResponse")
                }
                                // Use samlResponse
                return nil
            }
        }
    }))
    if err != nil {
        return errors.Wrap(err, "Faled to handle events")
    }

    // shutdown chrome
    if err = c.Shutdown(ctx); err != nil {
        return errors.Wrap(err, "Faled to shutdown Chrome")
    }

    // wait for chrome to finish the shutdown
    if err = c.Wait(); err != nil {
        return err
    }
    return nil
}

上の例ではAzure ADのログインURLをChromeで開いて、ユーザが手動でログインしたあとにAWSにSAMLレスポンスを投げるタイミングで処理しています。
Azureからのレスポンスを取っても良かったのですが、AWSにSAMLレスポンスを投げるところの方がパースが楽だったのでそっちを使っています。
同じ感じでやれば他のNetwork系のeventも取れますしSAML連携しているようなサイトの自動化なども簡単にできると思います。

Chromeの操作するだけならnode.jsとかの方が簡単だったと思いますが、配りやすさを考えるとやはりgolangでやりたかったのでchromedpで頑張りました。

まとめ

Chromeの全自動化じゃなくて半自動化みたいなのをしたい時に逆に面倒という話でした。
Issueにそのまま動くexample書いてくれる人は神(ちなみに上のexampleは動かない)。

7
2
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
7
2