kazokmr's Blog

試したこと、読んだこと、見たこと、聴いたことを書きたくなったら書くブログ

OWASP ZAP でペネトレーションテストを学ぶ(後編)

これは何の記録か?

ペネトレーションテストを理解するために、実際にOWASP ZAP (Ver.2.11.1)を使って検証したことのまとめ

前編では、OWASP ZAPのドキュメントを読んで理解したことをまとめた。

kazokmr.hatenablog.com

試したこと

  • OWASP ZAPの基本機能を使ったペネトレーションテストの実施手順
  • GraphQL と OpenAPI のURLを取得するためのスキーマ定義のインポート
  • リクエストとレスポンスのデータを動的変更するためのScriptの作成

試していないこと(いずれ試したい)

  • OWASP ZAP を Dockerコンテナから実行する
  • CI/CDパイプラインからOWASP ZAPでぺネトレーションテストを実行する
  • OWASP ZAP を 活用した手動ペネトレーションテストの実施

OWASP ZAP を試してみた感想

この後の記録が長いので感想を先に書いておく

  • ペネトレーションテストが導入しやすい
    • 基本的な脆弱性の検査を一通り行えるので、脆弱性の知見に乏しくても最低限の検証は行える
    • 検査対象のURLリクエストの探索を網羅的に収集したいときに便利
    • 開発段階で最低限の脆弱性検査が頻繁に行えそう
  • このツールに頼り切るだけでは必要な検査はカバー出来ない
    • SPA(CSR)のようなサーバーとの通信が発生しないフロントエンドアプリは検査しづらい
    • 検証結果・探索結果が適切か判断は人が確認する必要がある
  • 検査する脆弱性と検査しない脆弱性を把握しコントロールしなければならない
    • 初期設定のままだと必要以上に検査時間が掛かったりする
    • 探索したい脆弱性の検出にデフォルトでは対応していないこともある
    • 何の検査をどの程度の強度で実施するかコントロールする
    • 自分達でスクリプトを作ることも時には必要
  • 日頃からぺネトレーションテストを意識した準備と対応が大事
    • 検査が必要な脆弱性の抽出とその検証方法を予め定義する
    • 検査対象のURLとリクエスト/レスポンス情報の正しさを確認できるようにする
    • 検証範囲を「PRが発行される都度」「日次のスモークテスト」「リリース前」などに分割しないと必要以上に検査に時間を取られる
    • セキュリティ専門チームを置くと良さそう

準備

インストールは前編に書いているので省略。

テスト対象Webアプリ

OWASPが提供している WebGoat をローカルPCのDockerから起動して利用した。 認証機能を持つため、予めユーザーアカウントを登録しておいた。

owasp.org

クライアント環境のプロキシ設定

ブラウザをOWASP ZAPから起動させる場合、事前に設定することは無かった。

通常、ブラウザとサーバー間の通信(リクエストとレスポンス)を傍受したり書き換えられるようにするには、クライアント端末でOWASP ZAPをプロキシとする設定が必要。 OWASP ZAP がWebdriverを使ってブラウザを制御することでこの設定が無くても通信の探索が出来ていると考えている。

アプリケーションを探索する (Explore)

テスト対象アプリケーションにアクセスする URL とその通信で送受信した リクエストレスポンス を探索し収集していく。

手動で探索する(Manual Explore)

ブラウザを手動で操作することで、その時の通信情報をOWASP ZAPにキャプチャさせる。

  1. Workspaceの『Quick Start』タブから『Manual Explore』を選ぶ
  2. URL to explore に、探索するアプリのルートURL (e.g. http://localhost:8081/WebGoat)を入力する
  3. Enable HUD を利用しない場合はチェックを外す *1 *2
  4. Explore your applicationから、探索に使うブラウザを選択する
  5. Launch Browseを押すとブラウザが起動される
  6. 起動したブラウザでアプリを操作すると通信情報がキャプチャされていく
  7. 一通り操作を終えたらブラウザを閉じるとOWASP ZAPの探索も終了する

Manual Explore

通信の取得が目的なので以下のようなリクエスト送信が発生する操作を行う

  • リンクをクリックする
  • ボタンをクリックする
  • フォームに入力する
  • フォームをSubmitする

またこの後の自動探索を円滑に進めるための情報や操作も取得する

  • 想定通りのレスポンスが返るような操作
  • リクエストのqueryとbodyに含めるパラメータの組み合わせパターンを網羅する
  • 認証・認可に必要な情報が取得できる操作
  • ログイン状態(ログイン中orログアウト中)が把握できる情報の取得
  • ユーザーロールごとの探索

URLをContextで管理する

探索した通信情報(送信したリクエストと受信したレスポンス)は、URLと共に記録される。 SiteツリーではURLをパスの階層構造で表示し、Historyタブは探索した時系列で表示する。

探索されたURLには検査対象外の外部サイトが含まれたりするので、脆弱性検査したいURLをContextに追加する。

Contextで特定の関係にURLをまとめて管理することで、SpiderやActive Scanの検査対象を絞ったり(スコープ)、探索不要な外部サイトに誤って攻撃することを防ぐことができる。 また認証方法やセッション管理、ユーザーロールの登録などはContextで設定することになる。

Contextは検査範囲ごとに複数用意し管理することができる。

Siteツリー

認証方法・セッション管理方法を設定する

アプリケーションの認証情報を設定しておくことで、認証後の画面と機能を探索できるようにする。認証設定はContext をダブルクリックするか Session Properties メニューから行う

Authentication

Authentication Method

Form認証やHTTP認証などを指定する。他にも手動で認証操作を行ったり、カスタムスクリプトで認証を実行できたりする。

Form認証を例にすると、ログイン認証を行うリクエストURL と 必要ならページURLを用意し、POSTで渡す認証情報(credential)を定義する。これらの情報は探索済みのリクエストから Flag as Context で取り込むことができる。

UsernameとPasswordに渡す値は、{%username%}{%password%} と変数で指定することで、Userごとに認証情報の値が設定できる

Authentication Verification

送受信するリクエストとレスポンスの情報から、ログイン中またはログアウト中かを判断するAuthentication Stratagy とパターンを設定する

  • Check every Response: レスポンスに含まれる情報で判断する。例えばhtmlの中にログイン中であることが識別できる リンク・アイコン・メッセージの有無などを検索する
  • Check every Request: リクエストに含まれる情報で判断する。例えば、ヘッダに認証トークンの含まれているかどうかを検索する
  • Check every Request of Response: リクエストとレスポンスの両方で判断する
  • Poll the Specified URL: 指定した間隔で認証有無が判断できるURLにリクエストを送信した際のレスポンス情報で現在がログイン中かどうかを判断する。

Users

OWASP ZAP からWebアプリケーションにアクセスするときのUserが作成できる。UserはAuthentication Method で作成した username と password パラメータへの値を登録できるので、ロール別に複数Userを用意して使い分けることで、テストの網羅性を上げることができる。

Forced User

ログイン認証が必要なリクエストで利用するユーザーをUsersから指定する。この機能を利用するには探索やスキャンの前に ツールバーの "Forced User Mode" (鍵アイコン) をEnabledにする必要がある。

これとは別にSpiderやActive Scan実行時にUserが選択できるが、検証ではここでUserを選択しただけだと認証情報を利用してくれなかったので、Forced User Modeは必ず有効にする必要がありそうだった。

セッション管理

Webアプリのセッション管理方法を指定することができる。

  • Cookie-Based: CookieにセッションID(トークン)を管理する方法。別途、トークンを管理するCookie名を設定する
  • HTTP Authentication: リクエストヘッダで "Authorization" パラメータと値を渡す方法
  • Script-Based: 上記以外のセッション管理方式は、スクリプトを作成し管理することができる。

URLを自動探索する

Spider で探索する

SpiderはWebアプリをクローリングしてURLを自動探索する機能。 受信したレスポンスBodyに含まれる情報から、検索したURLにリクエストを送信することを繰り返して、URLと通信情報を取得する

Ajax Spider で探索する

アドオンとしてOWASP ZAPにプリインストールされているもう一つのSpider。

前述のSpider *3は、静的なURLリンクが検出できるがレスポンスから読み取れないURLは検出できない。 Ajax Spiderはブラウザを起動して実際にボタンやリンクを操作することで動的に生成されるリクエストURLを検出することができる。*4

Forced Browse で更にURLを探索する

Webアプリ用のミドルウェアフレームワークによって生成されるディレクトリなどは、開発チームの意図に関係なく公開されてしまうことがある。このようなディレクトリはWebアプリ内のページ操作では探索できず、直接URLをリクエストすることで探索できる。 このようなディレクトリ名からURLを生成して探索を行う機能がForced Browserアドオンでプリインストールされている。

ディレクトリ名のリストファイルは、プリセットされているもの、アドオンで追加するもの、独自にカスタマイズして追加するものが使える。

自動探索を行う際の注意事項

それぞれの機能の役割に従ってURLを網羅的に探索できるが以下のようなことに注意する

  • 検索対象の範囲指定を適切にする
    • 必要以上にURLを探索することで、外部サイトへアクセスしてしまう可能性がある
    • 検索の深さが大きいため再帰的に何度も探索してしまい探索が終わらない。

これらは実行する前にOptionで設定ができるので、検査仕様を決めた上で実施するとよい

  • 検査結果が正しいか確認し、必要であれば修正して再実行する
    • 認証情報や認可のためのトークンが不正のためエラーレスポンスしか返っていない。
    • 必要なリクエストパラメータの値が間違っている。または不足している。

適切な値で送信しないと適切なレスポンスが受信できない場合もあるので手動で修正する必要がある。

他にも後述する リクエストURLの「パスパラメータ」や「クエリパラメータ」といったURLの構造の調整 (Structural Modifiers)も必要に応じて設定する

データ駆動コンテンツ (Data Driven Content)

標準ではURLが異なれば機能も異なると認識して整理されるが、パスの一部をパラメータとして利用するケースがある。
例: ユーザーの詳細情報を取得するURLに /user/{userId}/detail のような ユーザーIDをパスに含める場合。

この場合はContextのStructure設定で "Data Drive Node" として動作するURLパスを正規表現で定義する。

構造パラメータ (Structural Parameters)

先ほどのData Driven とは逆で、URLは同じだがクエリパラメータの値によって機能が別れるケースもある。
例: ユーザーの詳細情報を取得するURL /user/detail?userId={userId}&userType={userType} があり、ユーザーの種別(userType)によって返される画面が異なる場合。*5

この場合はContextのStructure設定で "Structural Parameter" として動作するクエリパラメータを正規表現で定義することで、クエリパラメータの値によってノードを分けて管理させる。

アプリケーションを攻撃する (Attack)

探索したURLや通信情報を基にWebアプリケーションの脆弱性を検査する。これを "スキャン" と呼ぶ。
スキャン方法は"Passive Scan" と "Active Scan" の2種類があり、特定できる脆弱性が異なる。

スキャンする脆弱性は "スキャンルール" として定義されている。

スキャンルール

スキャンルールは脆弱性を特定するための方法を定義したもので、定義内容に基づいてスキャンを実行する。

スキャンルールは定期的に更新されており、OWASP ZAP にプリセットされている "Release"版だけでなく、検証段階に応じて "Beta" "Alpha" とされているルールもアドオンで追加したり、独自のカスタムルールを作成してスキャンさせることもできる。
どのようなルールが定義されているかは公式マニュアルを参照。次のリンクは "Rlease"版で定義されているスキャンルール。

OWASP ZAP – Passive Scan Rules

OWASP ZAP – Active Scan Rules

スキャンポリシー

スキャンポリシー(Scan Policy)は、スキャンルールに対する実行レベルが定義できる。*6

Threshold

どのレベルで潜在的脆弱性を検査・報告するかをが設定できる。スキャンルールごとに Low~High および OFF を選択する

  • High: そのスキャンルールの最低限の検査だけに限定する。検査時間は早くなるが、潜在的脆弱性が検索されにくくなる。
  • Low: そのスキャンルールで定義されているすべての検査を実施する。潜在的脆弱性が特定されやすいが、偽陽性は増え検索時間も長くなる
  • Medium: High と Low の中間。デフォルト設定は大体これ。 *OFF: そのスキャンルールで定義されている脆弱性は検査しない(スキップする)

Strength

脆弱性を特定するための攻撃数を制御する。これはActive Scanのみの設定。スキャンルールごとに Low~High または Insane を選択する

  • High:より多くの攻撃を行うので発見する脆弱性の数も増える。その分、検索時間が長い。
  • Low: 攻撃の数が少なくなるので検索時間は短い。その分、脆弱性の特定が漏れるので偽陰性となる可能性がある。
  • Medium: High と Low の中間。デフォルト設定は大体これ。
  • Insane: 非常に多くの検査を実施するので時間がかかる。アプリ全体ではなく特定の機能に対して行うのがおすすめ。*7

Passive Scan 結果を確認する

Passive Scanは "送信リクエスト" と "受信レスポンス" の内容を基にスキャンする。出力された情報から脆弱性を診断するだけで意図的な攻撃は行わない。

Passive Scanは これまでの探索の中で自動的に行われており、検出された脆弱性はAlertに登録される。同じリクエストで再スキャンしたい場合は、リクエストを再送信したりSpiderを実行する。

Active Scan を実施する

これまで探索したURL(リクエスト情報)に対して、既知の攻撃を行うことで潜在的脆弱性を検出する。スキャン対象アプリケーションへ実際に攻撃を行うことになるので、外部サイトなどが対象に含まれないように注意が必要。

実行手順

  1. ツールバーの一番左にあるModeプルダウンを ”Protected Mode” にする。Protecte ModeはSocpe対象のURLだけを攻撃対象とする
  2. テスト対象アプリが認証機能(Authentication)を持つなら、ツールバーの "Forced User Mode"(鍵アイコン)を有効にする
  3. テストするContextを右クリックし Active Scan を選択する
  4. ダイアログが表示されるので、利用するUserやScanPolicy、Technologyの設定を確認する。
  5. スキャンを開始する。

対象外アプリケーションを攻撃しないように設定する

テスト対象外のアプリへの攻撃を防ぐために必ず次の設定を行う。

  • "Protect Mode" にすることでScope内のURLに対してだけ攻撃を行う。
  • "Scope"は、Contextに含めることで対象となる。

つまり探索したURLのうち、テストするURLだけをContextに指定するようにし、Protect Modeで実行すること。

スキャンルール "Cross Site Scripting (DOM Based) "

Active Scanのルールの中に標準インストールされているアドオン "Cross Site Scripting (DOM Based)" は、DOM Based XSS を検出するルールであり、サーバーとの通信を行わず、クライアントサイド(フロントエンドアプリ)だけで発生する脆弱性を検出することができる。
そのため、ブラウザを起動してテストを行うので実行にとても時間を有する*8

このためこのスキャンルールを利用する際は、適切な設定や単独で実行するなどの対応が必要そうだと感じた。

Manual Scan を 実施する

最後に Active Scan では検出できない脆弱性を手動操作で検出する。
セキュリティテスト以外のテストでも同じことが言えるが、全てのテストを自動化することは難しい。 特にセキュリティテストは手法が決まっているわけでは無く攻撃者がどんな方法で攻撃を仕掛けるかはわからないし、開発プロセスや技術スタックによってアプリケーション特有のセキュリティホールが存在したりするので、自動化による汎用的なテストだけでは不十分なことが多い。

OWASP ZAPのマニュアルでも「ここまでの操作で基本的な脆弱性は発見できるが、より多くの脆弱性を発見するにはアプリケーションの手動テストも必要になる。」と説明するのと同時に、OWASP Testing Guideが紹介されている。

owasp.org

今回はツールを利用することが目的の一つだったので、手動テストは省略しているが、公式マニュアルでも手動テストを支援する方法を今後紹介する予定としている。

報告する (Report)

アラートの確認

画面下部の Alertタブで検出された脆弱性の詳細が確認できる。
Alert はリスクによって "High" > "Medium" > "Low" > "Informational" > "False Positive" の5段階のレベルがあり、脆弱性ごとにデフォルト値があるがカスタマイズもできる。

レポートを出力する

メニューバーの[Report] > [Generate Rerport] からレポートの出力が行える。 複数のレポートテンプレートが用意されているので、必要なものを選択して出力を行う。

GraphQLとOpenAPI に対するテスト例

公開されているAPIに対するテストを行う場合、API側が公開しているスキーマ定義を取り込んでURLを探索することができる。

OpenAPI (REST API) の API定義を取り込む

メニューバー の [Import] > [Import a OpenAPI definition from...] から 2種類のどちらかの方法で取り込む。

  • ローカルファイルから探索する: OpenAPI仕様に準拠した定義ファイル(.json/.yaml)をインポートする
  • URLから探索する: 公開されているOpenAPIドキュメント(Swagger UIなど)のURLを指定して取り込む

上記とは別にテスト対象アプリケーションのエンドポイントURLを指定し探索を行う。 探索したAPIはこれまで通り、URLが登録され自動的にPassive Scanまで行われるので、あとはこれまで記載した通り適切な送受信が行えるように手動で設定を行う。

GraphQL の API定義を取り込む

メニューバー の [Import] > [Import a GraphQL schema from...] から 2種類のどちらかの方法で取り込む。

  • ローカルファイルから探索する: GraphQLのスキーマ定義ファイル(*.graphql)を指定して取り込む
  • GraphQLエンドポイントURLから探索する: WebアプリケーションのGraphQL API用のエンドポイントURL (例: http://localhost:4000/graphql) を指定する。

上記とは別にテスト対象アプリケーションのエンドポイントURLを指定し探索を行う。
探索した後は、OpenAPIと同様に手動で補完を行う。

Scriptの使い方

通信の際に、受信したレスポンスに含まれる動的なパラメータを次のリクエストに渡して取得したい場合など、OWASP ZAP の標準機能やアドオンでは実現できない処理をスクリプトを用意して実行させることができる。

今回REST APIの検証で以下のアプリを利用したが、このアプリはCSRF対策として、Spring Secruityの利用した二重送信Cookie(Double-Submit-Cookie)を採用しており、サーバーはログイン認証が成功するとcsrfトークンを生成してクライアントのCookieにセットする。 クライアントではセットされたCookieからトークンの値を取得しリクエストヘッダーで渡して送信するようにしている。

github.com

このプロセスはOWASP ZAP では対応していないので、Scripts の "HTTPSender"を利用して、レスポンスを受信した時とリクエストを送信する時に動的なcsrfトークンをセットするようにしている。

var regexUrl = /\/login$/i;
var antiCsrfTokenName = "XSRF-TOKEN";
var reqHeaderCsrfTokenName = "X-XSRF-TOKEN";

// Response受信時の処理
function responseReceived(msg, initiator, helper) {
    // print('responseReceived called for url=' + msg.getRequestHeader().getURI().toString());
    // ログイン認証のレスポンスの場合に処理を行う *リクエストURIの最後が "/login"が該当する
    if (msg.getRequestHeader().getURI().toString().match(regexUrl) != null) {
        // レスポンスヘッダに含まれる Set-Cookieを検索する
        var cookies = msg.getResponseHeader().getCookieParams();
        var iterator = cookies.iterator();
        while(iterator.hasNext()){
            var cookie = iterator.next();
            // セットするCookieが、CSRF対策トークンで、値が渡される場合
            if(cookie.getName().equals(antiCsrfTokenName) && cookie.getValue() != ""){
                // print('Latest CSRF Token value: ' + cookie.getValue());
                // Global変数にcsrf対策トークンの値をセットする
                    org.zaproxy.zap.extension.script.ScriptVars.setGlobalVar("anti.csrf.token.value", cookie.getValue());
                }
        }
    }
}

// リクエスト送信時
function sendingRequest(msg, initiator, helper) {
    // 送信メソッドが"GET"以外、かつ ログイン認証ではない場合
    if(msg.getRequestHeader().getMethod() != "GET" && msg.getRequestHeader().getURI().toString().match(regexUrl) == null){
        // Global変数から csrfトークンを取得する
        var antiCsrfTokenValue = org.zaproxy.zap.extension.script.ScriptVars.getGlobalVar("anti.csrf.token.value");
        // print("antiCsrfTokenValue:" + antiCsrfTokenValue);
        // HttpRequestHeadeにcsrfトークンを追加する
        msg.getRequestHeader().setHeader(reqHeaderCsrfTokenName, antiCsrfTokenValue);
        // Cokieを取得する
        var cookies = msg.getCookieParams();
        var iterator = cookies.iterator();
        while(iterator.hasNext()){
            var cookie = iterator.next();
            // cookieがcsrf対策トークンの場合
            if(cookie.getName().equals(antiCsrfTokenName)){
                var secureTokenValue = cookie.getValue();
                // print("secureTokenValue:" + secureTokenValue);
                // csrfトークンの値がGlobal変数とCookieで異なればCookieの値をグローバル変数に更新する
                if (antiCsrfTokenValue != null && !secureTokenValue.equals(antiCsrfTokenValue)) {
                    cookie.setValue(antiCsrfTokenValue);
                    }
                return;
            }
        }
    }else{
        // HTTPリクエストヘッダーをクリアする
        msg.getRequestHeader().setHeader(reqHeaderCsrfTokenName, null);
    }
}

このスクリプトを有効にすると、探索やスキャンが行われるたびに実行されるようになる。

最後に

かなり長くなったが、実際に試行錯誤しながら使ってみることで、OWASP ZAP の仕組みがわかったし、ペネトレーションテストの勘所も掴めた気がする。

後は脆弱性への理解を深めて「どんな検証をするべきか」「どこまで検証するべきか」などの知識を深掘りして使いこなせるようになりたい。

*1:HUD(Heads Up Display)はOWASP ZAPをブラウザに重ねて表示できる機能。ブラウザとOWASP ZAPを往復しなくてもスキャン結果の確認や簡単な設定が行えるので便利。

*2:OFFにしたのはデュアルディスプレイだったのと、たまに機能しなくなることがあったため。

*3:マニュアルなどでは(Traditional) Spiderと書かれている

*4:SPAのようなクライアントサイドで画面を更新する操作はサーバーとの通信が発生しないため検出できない。

*5:実際はuserTypeなんて指定しないで、userIdを基にサーバー側でuserTypeを特定するとは思う

*6:Scan Policyの設定はActive Scanに対して行うが、ThresholdはPassive Scanでも有効なので便宜的ではあるがここでまとめて記載した

*7:Insaneという用語を使っているがそれ自体に差別的な意味は無い旨の説明が公式マニュアルに記載されている。

*8:検証でも数ページの探索を1日以上かけても進捗が50%にならずに途中でやめた。

OWASP ZAP でペネトレーションテストを学ぶ(前編)

これは何?

前職では、アプリケーションセキュリティテスト(AST)ツールの販売に関わっていて、SASTやSCAは詳しくなったが、DASTに関してはツールはもちろんテスト自体の知見もあまり身につけられていなかった。

そんな中、現職の年間目標の一つにDASTを取り入れることを挙げていたのと、開発中のWebアプリケーションの脆弱性診断を委託することになり、ペネトレーションテストを学びたいモチベーションが出てきたので、夏休みの宿題としてOWASP ZAPの使いながらベネトレーションテストの基本を学ぶことにした。

前編では座学としてZAPを使う前にペネトレーションテストについてOWASP ZAPの公式マニュアルを中心に学んだことを整理する。
後編では実際にZAPを使ったペネトレーションテストを実行して理解したことをまとめる。

なお、本来の目標は「ZAPによるペネトレーションテストをCI/CDパイプラインに組み込んで自動プロセス化する」だったが、予定通り進まなかったので、宿題事項は引き続き検証を進める予定。

ペネトレーションテストとは

悪意ある攻撃者のように振る舞ってシステムに侵入し、データを盗んだりサービスが正常に動作出来なくなるような攻撃を目的として実施するテストのこと

目的

脆弱性を探し出しその脆弱性に対処できるようにすること。また、特定の攻撃に対してシステムが脆弱でないことを検証したり、検出した脆弱性を修正して再検証すること。

メリット

動作しているシステムに対して実際にテストして脆弱性を検出するため、誤検知(存在しない脆弱性が報告されるなど)が少なく正確な結果が得られる。 他にも、防御の仕組みの検証、対応策の検証、セキュリティポリシーの遵守の確認などにも利用される。

その反面、検証用環境を構築し必要なデータを投入するなどの準備を行なったり、実際にシステムとの通信を行って検証するので、テストの実施に時間が掛かる。

ペネトレーションテストの基本手順

  • 探索する(Explore): テスト対象のシステムを調べる
    • システムが持つ全ての機能やエンドポイントを特定する
    • パッチはインストールされているかなどシステムの情報を調べる
    • システムに隠されているコンテンツや既知の脆弱性、弱点の兆候などを探索する
  • 攻撃する(Attack): 探索した情報を元に脆弱性を検査する
    • クライアントとシステム間の通信内容に既知の脆弱性が無いかを検査する
    • 既知の脆弱性や考えられる脆弱性で攻撃し、その脆弱性の存在を証明する
  • 報告する(Report): 検査結果を報告する
    • 脆弱性の有無
    • 脆弱性とその脆弱性がどのように悪用されるか
    • 悪用されることの難しさ
    • 悪用されることの重大性

ペネトレーションテストで意識すること

  • アプリケーションが提供する全ての機能を検査する
    • 機能を利用するための全てのURLやエンドポイントをリストアップする
    • 全ての機能にアクセスするための最低限のユーザー情報やデータを予め登録する
  • 認証や外部のWAFに守られている機能も検査する
    • 悪意あるユーザーが認証を突破したら?
    • 一般ユーザーを装ってサービスが利用していたら?
    • 公開していなくても機能やエンドポイントが存在するなら攻撃対象になり得る
  • 未知の脆弱性の検出は難しい
  • 自動テストと手動テストを使い分ける
    • 自動テストは基本的な脆弱性検査を回帰的に検査し
    • 手動テストは操作が複雑など自動では対応できないテストに集中する
    • クライアントサイドで処理が完結するSPAだとZAPのようなサーバーとの通信内容を検証するツールでは検知できない。

OWASP ZAP とは

OWASP について

Open Web Application Security Project(OWASP) は、ソフトウェアのセキュリティ改善を目的とした非営利な団体

owasp.org

owasp.org

OWASPでは、ソフトウェアのセキュリティに関する様々なプロジェクトが存在する。中でも OWASP Top Ten はリスクの高い脆弱性や攻撃されることが多い脆弱性などを統計・分析した結果を公開したものでソフトウェア開発において手始めに対処するべき脆弱性を理解することに役立っている。

owasp.org

OWASP ZAP の主な特徴や機能

OWASP ZAP (Zed Attack Proxy) は OWASPのフラッグシップ・プロジェクトの一つで、オープンソースで公開されており、無料で利用することができるWebアプリケーション向けの脆弱性スキャナである。

www.zaproxy.org

  • 中間プロキシとして動作する
    • クライアント(ブラウザ)とサーバー(Webアプリ)間の通信を傍受し、リクエストとレスポンスをスキャンする。
    • 傍受したリクエストやレスポンスを意図的に改ざんすることでブラウザやWebアプリの挙動を検査する。
  • Spider
    • Webアプリが提供するURLリンクを検出するためのツール。Traditional spider と Ajax Spider の2種類がある。
    • Traditional Spider は、レスポンスに含まれるHTMLを探索して formタグやaタグ, src属性などからURLリンクを検出する。
    • Ajax Spider は、ブラウザから実際にボタン動作やClick動作を試行することで遷移先のページなどのURLを検出する。Traditional Spiderでは検出できない動的に生成されるリンクの探索に役立つ。
  • Passive Scan (受動的スキャン)
    • 検出したURLに対して送信したリクエストと受信したレスポンスを傍受し、これらのヘッダやボディを解析して既知の脆弱性を特定する。
    • 傍受したリクエストとレスポンスを書き換えたりしないので、アプリケーションの破壊や停止を引き起こすことなく脆弱性を特定することができる。
  • Active Scan (能動的スキャン)
    • 検出したURLに対して既知の脆弱性を突く攻撃を実際に行い、その際のアプリケーションの挙動から脆弱性を特定する。
    • 攻撃は傍受したリクエストやレスポンスの内容を改ざんすることで行ったりする
    • テストの結果、アプリケーションが停止したり破壊する可能性があるので、本番環境やテスト対象以外のWebアプリに対してActive Scanを絶対に実行しないように注意する。
  • ユーザー管理と認証管理およびセッション管理
    • テスト実行に必要な認証情報やセッション情報の方式を設定したり管理が行える
    • テストを実行するユーザーも選択できる。ユーザーは複数管理が行え、ユーザーごとに認証情報が設定できる。
    • アプリケーションにユーザーロールがある場合など、全ロールのユーザーを作成することで全ての機能とエンドポイントがカバーできる。
  • アドオン

OWASP ZAP で検出できる脆弱性

  • 既知の脆弱性脆弱性ごとにScan Rule として定義され公開されている。
  • ZAPのデフォルトで検査できるScan Rule はステータスが Releaseとなっているもの。
  • その他のステータスには、Alpha, Beta があり、別途アドオンで追加すれば検査が可能になる。
  • 独自ルールをScriptで作成して脆弱性を検査することもできる

www.zaproxy.org

OWASP ZAPをインストールし、ペネトレーションテストを行う

インストール

  • インストーラは下記からダウンロードできる
  • Windows版とLinux版は別途、Java 8(JRE 8) 以上をインストールする必要がある
  • Mac版はJava 11がバンドルされている
  • Docker版もある。基本はCLI設定になるが、ブラウザから操作できるGUIも提供されている模様。(動作未確認です。)

今回の検証では、MacのHomebrew caskを使ってインストールした Version 2.1.1 を利用した

検証に利用するWebアプリ

今回は同じくOWASPのフラッグシップ・プロジェクトである Juice Shop をローカルのDockerで起動し利用した。

owasp.org

Juice Shopはアプリに含まれている脆弱性を攻撃することでソフトウェアのセキュリティを学ぶことができる体験型のWebアプリケーションである。

Juice Shop以外にも同様のWebアプリケーションは VWADとして公開されており、プログラミング言語フレームワーク、実行環境に対応している。

owasp.org

基本の操作手順

前述のペネトレーションテストの基本手順をZAPで行う。 これは下記のマニュアルを参考にしており、実際に実施したことは 後編にまとめる。

www.zaproxy.org

Explorer - 探索する

  • Webアプリが提供する全ての機能とエンドポイントURLを探索する。
  • ブラウザで実際に全てのリンクやボタンを押したり、全てのフォームを埋めてsubmitすることで、アプリケーションとの通信(リクエストとレスポンス)をキャプチャする

Contextへの登録

  • 探索したSite(URL)からテスト対象とするエンドポイントをContextに追加する。
  • Contextに追加したサイトは、テストスコープの対象となり、認証情報を指定したユーザーでテストが実施できたり、セッション管理が行えるようになる。

Spider でさらに探索する

  • Spiderを利用することで手動では探索できなかったり隠れていたURLを探索する
  • 先に手動で探索したサイトをContextに設定しておくことで、ContextベースでSpiderを実行することができるので、認証情報を使ったり、探索が必要なサイトだけを対象にすることができる。
  • 手動探索とTraditional Spiderの探索結果は重複するURLも多いので先に認証処理に関するエンドポイントを中心に手動探索してContext管理を行い、Traditional Spiderを使うと効率的かもしれない。
  • Ajax Spiderも実行することで動的に生成されるリンクも探索する。
  • Ajax Spiderの実行にはブラウザが必要になる。

Forced Browse - 強制ブラウズ

  • 参照されていないファイルやディレクトリを発見するための探索機能。 “Forced Browse“アドオンのインストールが必要
  • 一般的にURLディレクトリ(パス)の名称として使われるキーワードリストを用意して、URLを生成して探索を行うことで、元に該当するURLが無いか探索する。
  • 存在するディレクトリ名を発見したら更に再帰的にパスを生成して探索する。このため探索に時間もシステムへの負荷も掛かる。
  • デフォルトでは、'directory-list-1.0.txt' ファイルがインストールされている。
  • 他のディレクトリリストもマーケットプレイスに公開されていたり、独自に用意してインポートすることもできる。

Passive Scan の実施

  • ここまでの手動探索やSpider探索などで、探索と同時にScanを実施していた。
  • つまり、Passive Scanは明示的に独立して実行する必要がない。

Active Scan の実施

  • 探索したサイトに対して基本的な脆弱性を特定する検査を実施する。
  • 実際のシステムに攻撃を仕掛けるので、スコープ対象以外のサイトURLへ攻撃しないように、スコープ指定を行い、Protectモードで実行することがお勧め。
  • エンドポイントの数が少なくても全てを完了するまでに5−6時間掛かった。実際のアプリへのテストなら並列実行などを検討した方が良さそう。

レポートを作成する

  • 検査結果をファイルに出力することができる。
  • ファイル出力しなくても、ZAPのセッションを保存すれば ZAPからも確認できる。

手動テスト

Active Scanによる自動テストでは発見できない脆弱性を検証するために、手動テストを実行する。

詳細は WSTG (Web Security Testing Guide)を参照

owasp.org

公式ドキュメントでは、将来的にZAPが手動テストをどのように支援できるかを紹介する予定があるそうだが現時点では非公開。

例えば、探索したリクエスト/レスポンスにBreakpointを打ち、通信内容を書き換えてテストする操作なども手動テストの一種だと考える。

今回はZAPを使ってペネトレーションテストを自動実行することが目的でもあるため省略する。

前編はここまで。

実際にZAP DesktopUI から Juice Shopへのペネトレーションテストを実施した結果は、後編にまとめる。

追記:

Active Scanで、Cross Site Scripting (DOM Based) の検査が行われるようになっていて、SPAこれならクライアントサイドで発生するXSSの検証ができるのかもしれない。

ただこのルールはまだ Beta版で、下記の説明の通り ヘッドレスモードのブラウザで検証しているせいか、他のルールと比較して圧倒的に時間を消費している*1ので、現時点で有効なルールではない気がする。

www.zaproxy.org

*1:他のRuleが長くても23分で完了しているのに、プログレスバーが2~3%で3時間近く経過している

フロントエンドアプリ(React)で使うライブラリを最新にしたらテストでエラーになったので色々直した話

モチベーション

自分のフロンエンド技術を維持するため、サンプルアプリを作って試していたり、依存関係のバージョンも最新で動かすようにしている。 だけど最近テスト実行時にこれまで遭遇しなかったエラーが出るようになって対応が難しかった。 一応、テストは全て通るようにはなったけど、結構大掛かりな対応になりそうなので、まとめておく。

多分、内容は更新していくかm

エラーになったpackage.json

サンプルアプリケーションとエラーとなった時点のpackage.json を基点として記載しておく

github.com

{
  "name": "project-frontend-dev-process",
  "version": "0.1.0",
  "private": true,
  "license": "MIT",
  "engines": {
    "node": "16.x"
  },
  "packageManager": "yarn@1.22.19",
  "dependencies": {
    "@emotion/react": "^11.9.3",
    "@emotion/styled": "^11.9.3",
    "@fontsource/roboto": "^4.5.7",
    "@mui/icons-material": "^5.8.4",
    "@mui/material": "^5.9.1",
    "@tanstack/react-query": "^4.0.10",
    "@tanstack/react-query-devtools": "^4.0.10",
    "axios": "^0.27.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "recoil": "^0.7.4",
    "ulid": "^2.3.0",
    "web-vitals": "^2.1.4"
  },
  "devDependencies": {
    "@storybook/addon-a11y": "^6.5.9",
    "@storybook/addon-actions": "^6.5.9",
    "@storybook/addon-essentials": "^6.5.9",
    "@storybook/addon-interactions": "^6.5.9",
    "@storybook/addon-links": "^6.5.9",
    "@storybook/addon-storyshots": "^6.5.9",
    "@storybook/builder-webpack5": "^6.5.9",
    "@storybook/jest": "^0.0.10",
    "@storybook/manager-webpack5": "^6.5.9",
    "@storybook/node-logger": "^6.5.9",
    "@storybook/preset-create-react-app": "^4.1.2",
    "@storybook/react": "^6.5.9",
    "@storybook/testing-library": "^0.0.13",
    "@storybook/testing-react": "^1.3.0",
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.3.0",
    "@testing-library/react-hooks": "^8.0.1",
    "@testing-library/user-event": "^14.2.6",
    "@types/jest": "^28.1.6",
    "@types/node": "^18.0.6",
    "@types/react": "^18.0.15",
    "@types/react-dom": "^18.0.6",
    "chromatic": "^6.7.1",
    "jest": "^28.1.3",
    "jest-environment-jsdom": "^28.1.3",
    "msw": "^0.44.2",
    "msw-storybook-addon": "^1.6.3",
    "prettier": "^2.6.2",
    "react-scripts": "^5.0.1",
    "ts-jest": "^28.0.7",
    "typescript": "^4.7.4",
    "webpack": "^5.73.0"
  },
  "resolutions": {
    "react-test-renderer": "^18.2.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ],
    "overrides": [
      {
        "files": [
          "**/*.stories.*"
        ],
        "rules": {
          "import/no-anonymous-default-export": "off"
        }
      }
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "jest": {
    "testMatch": [
      "**/__tests__/**/*.test.[tj]s?(x)"
    ],
    "transform": {
      "^.+\\.(ts|tsx)$": "ts-jest"
    }
  },
  "msw": {
    "workerDirectory": "public"
  },
  "homepage": "."
}

起きたこと

Serverアクセスする所でNetworkエラーが発生した

MSWを0.42.1 までだと発生しない。 だけど設定してあるhandlerに対しても、handleしていないと出てはいた

ServerのリクエストURLにホストをつけた

https://example.com を付けるようにした mswのHandlerもこのホストで受け付けるようにした

結果、http://localhost CrossOrigin エラーが発生した

Jestの設定に testEnvironmentを指定しようとした

react-scriptではこの変数に対応していないことを知る

ここでreact-scriptsのJestがV27なことに気づく

v28を使いたいのとこの辺がのversion違いがエラーの原因な気がしたので、scriptsのtestコマンドを react-scripts を介さずに "test": "jest" に変えた

そうしたら他のテストでもエラーが発生し始め、全体的にテストが通らなくなってきたので、見直すことにした

jest.config.ts と jest.setup.ts を作る

jest.setup.ts は CreateReactAppが作成していた、src/setupTests.js を 名前を変えただけ。 rootに持ってきたのは jest.config.tsと並べて管理したかったから

ちなみにjest.setup.jsだとimport moduleのエラーが出てしまい、 import "@testing-library/jest-dom" が利かず、jest-dom のライブラリを利用している テストファイルごとに付けなければいけなくなった。 これを.tsに変えると直るのだが、モジュールシステムがわからない。

jest.configの方も以下を参考に .ts に変えた。 この過程で ts-node をインストールしている

kulshekhar.github.io

IntelliJでテストを単独実行すると 前述の jest.setup.js をみてくれない

CLIではなく、IDE上で特定のケースのテストを実行しようとする以下のようなエラーが出る

expect(...).toBeInTheDocument is not a function
TypeError: expect(...).toBeInTheDocument is not a function

toBeInTheDocument() は、jest-domに含まれているので、setupTests.ts が参照されていないのではと考えてConfigurationを見たら Jest packageが、react-scripts を参照していたので、これを jest に変えたら通った。 Documentの内容から、react-scripts を使って実行するのが最優先になっているのだと思った。 なので react-scripts をremove を考え始めている

www.jetbrains.com

Mockの状態をクリアする必要が出てきた

Mockを使って toHaveBeenCalled() で検証していた箇所が軒並みFailedになり始めた。 テストを繰り返し行う場合や実行順で影響が出るので、テストごとに状態がクリアされていないと判断し、クリア処理を追加した

beforeEach(() => mockedMutate.mockClear());

jestjs.io

今までこれが無くてもテストが通っていたのは、 react-scripts が何かをおこなっていたからだと予測できるが、深く追ってはいない。

Storyshots.test が失敗する

Storybookのアドオンを利用したSnapshotテストがエラーを返していた。

 FAIL  src/__tests__/Storyshots.test.ts
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /Users/kazokmr/IdeaProjects/project-frontend-dev-process/.storybook/preview.js:3
    import "../src/index.css";
    ^^^^^^

    SyntaxError: Cannot use import statement outside a module

jest.setup.js の import "@testing-library/jest-dom" で出力されたエラーと同じだったので、 ./.storybook/preview.js を .ts に変えたらエラーが変わった

Details:

    /Users/kazokmr/IdeaProjects/project-frontend-dev-process/src/index.css:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){body {
                                                                                           ^

    SyntaxError: Unexpected token '{'

zenn.dev

こちらの記事を参考に ./.jest/style.ts 作って以下のように1行追記し、 jest.config.ts の moduleNameMapperプロパティで cssファイルをこのファイルにマッピングするようにした

export default {};
...

  moduleNameMapper: {
    "\\.(css|less)$": "<rootDir>/.jest/style.ts"
  }
...

一応これでエラーはなくなり、Snapshotも取得できるように戻った。

ただし、以下のようなエラーメッセージは出ているので、react-scriptsを介さない場合の設定に見直す必要はあると思う。 .mdxファイルに対しても同じ対処が必要だった。

 ● Console

    console.error
      Unexpected error while loading ./stories/Introduction.stories.mdx: /Users/kazokmr/IdeaProjects/project-frontend-dev-process/src/stories/Introduction.stories.mdx: Support for the experimental syntax 'jsx' isn't currently enabled (11:1):
      
         9 | import StackAlt from './assets/stackalt.svg';
        10 |
      > 11 | <Meta title="Example/Introduction" />
           | ^
        12 |
        13 | <style>{`
        14 |   .subheading {
      
      Add @babel/preset-react (https://github.com/babel/babel/tree/main/packages/babel-preset-react) to the 'presets' section of your Babel config to enable transformation.
      If you want to leave it as-is, add @babel/plugin-syntax-jsx (https://github.com/babel/babel/tree/main/packages/babel-plugin-syntax-jsx) to the 'plugins' section to enable parsing.
       Jest encountered an unexpected token
      
      Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
      
      Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
      
      By default "node_modules" folder is ignored by transformers.
      
      Here's what you can do:
       • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
       • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
       • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
       • If you need a custom transformation specify a "transform" option in your config.
       • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
      
      You'll find more details and examples of these config options in the docs:
      https://jestjs.io/docs/configuration
      For information about custom transformations, see:
      https://jestjs.io/docs/code-transformation
      
      Details:
      
      SyntaxError: /Users/kazokmr/IdeaProjects/project-frontend-dev-process/src/stories/Introduction.stories.mdx: Support for the experimental syntax 'jsx' isn't currently enabled (11:1):
      
         9 | import StackAlt from './assets/stackalt.svg';
        10 |
      > 11 | <Meta title="Example/Introduction" />
           | ^
        12 |
        13 | <style>{`
        14 |   .subheading {
      
      Add @babel/preset-react (https://github.com/babel/babel/tree/main/packages/babel-preset-react) to the 'presets' section of your Babel config to enable transformation.
      If you want to leave it as-is, add @babel/plugin-syntax-jsx (https://github.com/babel/babel/tree/main/packages/babel-plugin-syntax-jsx) to the 'plugins' section to enable parsing.
          at instantiate (/Users/kazokmr/IdeaProjects/project-frontend-dev-process/node_modules/@babel/parser/src/parse-error/credentials.js:61:22)
          at instantiate (/Users/kazokmr/IdeaProjects/project-frontend-dev-process/node_modules/@babel/parser/src/parse-error.js:58:12)
          at Parser.toParseError [as raise] (/Users/kazokmr/IdeaProjects/project-frontend-dev-process/node_modules/@babel/parser/src/tokenizer/index.js:1736:19)
          at Parser.raise [as expectOnePlugin] (/Users/kazokmr/IdeaProjects/project-frontend-dev-process/node_modules/@babel/parser/src/tokenizer/index.js:1800:18)
          at Parser.expectOnePlugin [as parseExprAtom] (/Users/kazokmr/IdeaProjects/project-frontend-dev-process/node_modules/@babel/parser/src/parser/expression.js:1239:16)
          at Parser.parseExprAtom [as parseExprSubscripts] (/Users/kazokmr/IdeaProjects/project-frontend-dev-process/node_modules/@babel/parser/src/parser/expression.js:684:23)
          at Parser.parseExprSubscripts [as parseUpdate] (/Users/kazokmr/IdeaProjects/project-frontend-dev-process/node_modules/@babel/parser/src/parser/expression.js:663:21)
          at Parser.parseUpdate [as parseMaybeUnary] (/Users/kazokmr/IdeaProjects/project-frontend-dev-process/node_modules/@babel/parser/src/parser/expression.js:632:23)
          at Parser.parseMaybeUnary [as parseMaybeUnaryOrPrivate] (/Users/kazokmr/IdeaProjects/project-frontend-dev-process/node_modules/@babel/parser/src/parser/expression.js:384:14)
          at Parser.parseMaybeUnaryOrPrivate [as parseExprOps] (/Users/kazokmr/IdeaProjects/project-frontend-dev-process/node_modules/@babel/parser/src/parser/expression.js:394:23)

      at Object.error (node_modules/@storybook/client-logger/dist/cjs/index.js:74:67)
      at node_modules/@storybook/core-client/dist/cjs/preview/executeLoadable.js:84:32
          at Array.forEach (<anonymous>)
      at node_modules/@storybook/core-client/dist/cjs/preview/executeLoadable.js:77:18
          at Array.forEach (<anonymous>)
      at executeLoadable (node_modules/@storybook/core-client/dist/cjs/preview/executeLoadable.js:76:10)
      at executeLoadableForChanges (node_modules/@storybook/core-client/dist/cjs/preview/executeLoadable.js:127:20)
      at Object.getProjectAnnotations [as nextFn] (node_modules/@storybook/core-client/dist/cjs/preview/start.js:161:84)

モジュールシステム も react-scripts も tsc も何にもわかっていないなぁ自分。

(7.26 追記) 上記の問題は Storyshotsでスナップショットテストを実行する際に、.cssや .mdx ファイルの解析に失敗していることが問題だった。 なので対応としては大きく2つの方法があり、今回はテスト自体への支障が無いと判断して、2の対応をしたことになる

  1. Jest実行時に解析できるようにファイルの中身を変換する (jest.config の transformオプションを使う)
  2. Jest実行時にファイルの参照先をモックファイルに変換して無視させる (jest.config の moduleNameMapperオプションを使う)

1は変換するためのモジュールを予めインストールしておき、そのモジュールを指定すると良さそう。 例えば、CSS なら jest-css-modules-transform, MDXなら @storybook/addon-docs/jest-transform-mdx などを使うのが良さそう。と思ったけど、CSSの方はそれでも行けたけど、MDXは追加したモジュールでエラーが出た。

このあと調べること

テストは通っているので、これでコミットはするが大きく2つやるべきことが残っている

snapshotsテストの設定見直し

実行ログではエラーが出ていたり、一部 react-scriptsに依存した設定になっているように見受けられるので調査・対応する。

多分だけど、Storybook全体で設定内容を見直した方が良さそう

(7.26 追記) 前述の通りMDXファイルの解析エラーが問題だったので、今回はCSSの時と同様にモック化したMDXファイルに置き換える対応で解決した。

Viteに移行する

jestなど react-scripts でバンドルしているものは、最新バージョンに追従するのが難しいので、react-scripts (CreateReactApp)からの脱却を考えている。できればライブラリへの依存度が少ない薄いビルドツールを使いたい。

となると vite が候補に挙げられるのでこれを試してみたい

JJUG CCC 2021 Spring 参加レポート

前回の参加が、2019 Spring だったので2年振りです。
簡単に聴いたセッションの感想を書きます。

ccc2021spring.java-users.jp

フロントエンド・バックエンドの分離の道のり

fortee.jp

既存システムで JSP + カスタムタグ + JavaScript で開発していたフロントエンドを React に変更して、フロントエンドとバックエンドの開発を明確に分離するまでのお話。

JSPで書いていたものを全てReactで書くのは難しく「画面の共通フレームの部分はJSPのまま、コンテンツ部分をReactで書く。」「初回のリクエストはJSPで返し、以後はReactで非同期で対応する。」などの過渡期の部分の話が聞けたのが面白かった。

フロントエンドの方が採用技術が大きく変わったのでこちらよりの話が多い感じがしたので、バックエンド側で行った話ももう少し聞いてみたかった。

今どき?の Java における例外処理についての考察

fortee.jp

Javaの例外の基本から始まり、従来の例外処理とコーディングでの課題を挙げ、他の言語だとどのような書き方ができるかを紹介して、最後は Java16 そして Java17 になるとどのように例外処理が書けるかというお話。

最近Kotlinを勉強したというのもあったので関数型プログラミングでの書き方についても大体理解しながら聴くことができた。

次のLTSになる Java17 だと Switch式, record, sealed class などを駆使すると、どのように例外処理が書けるかを見たら、早く使ってみたいって気持ちになった。

OpenID Connect 1.0 with Spring Security

fortee.jp

ちょっと前に自社で、SAML, OAuth2.0, OIDC を調べていて、Spring Securityのリファレンスページ や Auth屋さんの本を読んでいたので、答え合わせをするようなつもりで聴いてみました。

アクセストークンを「切符」で例えて説明されたり、重要な言葉を説明する時に強弱をつけて説明されるので多田さんの話はいつも聴きやすいですね。(この辺は自分も参考にしたいです。)

(OAuth認証の問題の部分については、私自身も他人に説明できるほど理解できていないですが、
本来は認証が成功することでアクセスが許可されるログインユーザーの認証情報に、OAuthの認可の仕組みでアクセスできるようにすることで認証と同等の結果が得られるのが、OAuth認証かなと思っています。
この使い方には明確な規約が無いので、正しく取り扱わないとなりすましログインなどの脆弱性を引き起こすので、OAuthに認証情報を取り扱うときのルールを定めたのがOIDCなのかなと雑に理解しています。)

またSpring Securityだと、OAuth 2.0 Login の機能に OIDCも実装されているので、リファレンスを読んでもOIDC自体のことが分かりにくいんですよね。逆にいえばSpring Security だとOAuth認証とOIDCの違いを意識しなくても使いやすくなっているということかもしれませんが。

サーバーレスAPIをKotlinで開発してみよう

fortee.jp

サーバーレスアプリケーションを構築する機会は無いのですが、Kotlinを勉強したばかりというのもあり聴いてみました。

SAMはAWSのサーバーレスアプリケーション開発をサポートするツールなのでAWSでサーバレスアプリをデプロイするまでを簡単に行える感じなのが面白そうでした。

また普段はそこまで気をつけていない、リソース量や初期化時間を考えて設計が大事なこと、テストもLambdaに依存する所と依存しない部分に分けて行うなどの説明がわかりやすく、機会があれば触ってみたいなと感じるセッション内容でした。

JFR などのツールを用いて FullGC や OOME の原因を特定する流れ

fortee.jp

JFR や JMC、Memory Analyzerを使った問題解析・原因分析を行う手順を紹介するセッション

前職だと、JConsole、Memory Analyzer は使ったことあったのだけれど、JFR、JMCはやっぱりいいなと思いました。 今の仕事でもここまで原因調査を行う機会が無いのだけれども、いつか必要になるときに触れておきたいですね。

あとJFRは、本番稼働中は常時有効にしておくものなのかがわからなかったので、その辺を聞けばよかったです。

モダンな技術をエンタープライズ開発の現場にインストールした話

fortee.jp

エンタープライズ開発の現場で、DDD、CQS、CI/CD を取り入れた話。 自分が行っていることに近い所も多かったので興味深く聴きました。

最初のDDDについてはアーキテクチャ寄りの話が多く、せっかくのドメイン駆動なのでもっと顧客の業務に対してどのように寄り添ってコードに落とし込んだのかということをもっと聴いてみたかった。

次のjOOQにしても、知識の幅を広げるという意味で「Mybatisは使ったことあるからjOOQを採用した。」という考えはわかるのだけども、顧客の業務フローに対してどのようにマッチしているのかみたいなことも聞けたらよかったと思いました。

最後のCI/CDは、Bitbucket Pipelines を使った事例の話でした。Bitbucket Pipelinesは仕事上よく触るし今回のようなECR、ECSと連携する処理も使ったことがあるけど、どうしてもGitHubGitHub Actionsと比べるとシェアが少ないので情報が少なかったのでこのような事例を発表していたけのがとても良かったです。
ちなみにUIテスト(E2Eテスト)は自動化しようとしたか?は個人的に聞いてみたかったです。(Selenium GridをBitbucket Pipelinesで動かそうとして上手く実行できなかったこともあったので。)

Plug-in Architectures with the Java Module System

fortee.jp

最後はAndresさんの英語セッション。セッション名は変わったとの話がありましたがタイトルは忘れてしまいました。

Javaのモジュールシステムのことはあまり知らなかったので聴いてみましたが、やはり英語を聞き取ることが中々に難しかったのと、Layrryの紹介がメインだったこともあり、正直ついていくことができませんでした。

ただ、Layrryに触れてみれば分かることもありそうな感じはしたので、今度機会があれば使ってみたい。

あとQAの時に進行役の杉山さんが「グローバルだと、モジュールシステムを意識して開発していることが多いのか?」みたいなことを質問されていたと思いますが(間違っていたらすみません。)、ちょうど Andres さんが回答した時に家族に声を掛けられて聞けなかったのが悔しかった。

全体として

まずは運営の方々やスタッフの皆様、ありがとうございました。去年の秋は参加できずオンライン参加は今回が初めてでしたが、大きなトラブルもなくセッションを聴くことができました。

ここ最近のCCCは土曜日開催だったと思うのですが、土曜日は家族のイベントが多くオンラインだと集中して見れないので難しかったので、日曜開催だったのは個人的にとても助かりました。

スポンサーブース(Spatial Chat)についてですが、本当なら参加してお話ししたりするべきだとは思ったのですが、セッションを聴いた後の合間の10分の休憩で家のことをやったりしていたので、参加できませんでした。すいません。

次回はオンとオフの両方で参加ができるようになるのかもしれないですが、多分オンラインでの参加になるかなと思います。

BDD in Action 2nd Edition 第8章メモ

第8章を読みました。この本の目次を見ると付録を除いて全15章となっているので、折り返し地点です。 現時点で9章はまだ公開されていませんが。

www.manning.com

kazokmr.hatenablog.com

8章もCucumberでテストを書くときのテクニックの説明です。
読みやすく、メンテナンスしやすく、信頼性が高いテストコードを書くために、レイヤー構造の概念を用いています。

この章のボリュームは他と比べると多くはありませんが、Cucumberを用いた 振る舞い の一部がよくわかるような内容だと思います。

レイヤー構造でテストを書く

ここで説明しているレイヤーは大きく次の3つに分かれ、上からピラミッド型で表されていました。

  1. ビジネスルール・レイヤー : フィーチャーファイルやシナリオ

  2. ビジネスフロー・レイヤー : グルーコード

  3. テクニカル・レイヤー : 実際にテスト対象アプリのUIを操作したり、REST API を送信するコード

これは「テストピラミッド」の最上位に当たる UIテスト の三角形の中を分解しているように思えた。
(図を見せて説明できないので、イメージしづらいですが。)

ビジネスルール・レイヤー

これまでの章で見てきた、Gherkin形式で書かれたシナリオを指す。
何をテストするのか、あるいは、何を期待しているのかを ビジネス側のユーザーがわかる用語や表現で記述した物。

シナリオの内容は、ここまでに行ったアクションで、ビジネス側と合意が取れているはずなので、Cucumberのテスト結果から、アプリケーションがシナリオを満たしているかどうかが把握できる。

ビジネスフロー・レイヤー

前の7章で出てきた、シナリオとCucumberでのテストをつなぐ、ステップ定義ファイルを指す。
ここで Given, When, Then の各ステップごとに定義メソッドで、受け入れテストのための準備、実行、検証を行う。

このレイヤーのポイントは ユースケース図のように アクターやアクターが操作するモノをオブジェクト化して、振る舞いを表現すること。 だと感じた。

どういうことかというと、テストコード上に アクターそのもの、そのアクターが操作するクライアントツール(WebアプリのUIならブラウザ)などを テスト専用のクラスとして定義しオブジェクトにすることで、グルーコードの中で、実際に振る舞いを表現して、シナリオを実演することができる。

つまり、テスト対象アプリケーションを利用する現実世界をモデリングして、グルーコードでシナリオを表現できるようにする。

  1. 「アクターは、ブラウザで、○○ページを開く」

  2. 「アクターは、ブラウザで、テキストに ***と入力する」

  3. 「アクターは、ブラウザで、送信ボタンをクリックする」

  4. 「ブラウザに・・・・・と出力される。」
    -> この出力内容がシナリオで期待値と合っているか検証する

グルーコードで表現するオブジェクトは、抽象的に書くことが望ましい。
そうすることで、オブジェクトの汎用性、再利用性を高め、アプリケーションを変更した時の影響を小さくすることができる。

テクニカル・レイヤー

上位のビジネスフロー・レイヤー (グルーコード)で、定義した振る舞いから、テスト対象アプリケーションのUI、あるいはREST APIなどの操作を行う。

本の中ではこのレイヤーの中も2つのレイヤーに分かれていて、上位の方は ページ、テキスト、ボタンのような抽象的なオブジェクトを用意して表現し、下位のレイヤーでそれらのオブジェクトがUI上で、どこに配置され、id属性やname属性などのプロパティを具象化する。

レイヤー化のメリット

このようにCucumberのテストコードをレイヤー構造にすることで次のようなメリットがあると思った。

変更に強い

アプリケーションの変更がどのような内容かによって、修正が必要なテストコードの影響範囲を小さくできる。

例えば、ビジネスの要件は変わらず、UI のレイアウト や REST APIのパラメータ名称だけが 変わるだけなら、最下層のレイヤーだけを修正すればいいかもしれない。

または、アクターとして表現するべき登場人物が増えるのなら、アクターのオブジェクトの種類を増やし、その分だけきぞんのテストの実行を追加するだけで良いかもしれない。

再利用しやすい

新しいビジネス要件を実装する際に、作成済みのアクターなどのオブジェクトを流用して、シナリオを具現化しやすくなる。

アプリケーションのUIが作られる前に、テストコードが書き始められる

シナリオはもちろんグルーコードやテクニカルレイヤーの一部は、アプリケーションのUIの詳細情報と疎結になるので、UIを作る前からテストを書き始めることができる。
これは、受け入れテストやエンドツーエンドテストの早期実行に繋げることができると思う。

ペルソナを使う

前述のアクターについて、TypeSafe Config ライブラリ を用いて、HOCON形式で作成した属性情報を取り込み、ペルソナとして具象化する方法が紹介されていた。

TypeSafe Config や HOCONファイルは使ったことないけど、本文中のサンプルを見る限り、単純なプロパティを羅列化したオブジェクトを用意したい場合に便利そうだと思った。

GitHub - lightbend/config: configuration library for JVM languages using HOCON files

所感

BDDやCucumberがカバーしているのは、シナリオを用いた受け入れテストやエンドツーエンドテストになるからか、テストコードを手続き的に書いてしまうことが多い。
これだと、コードが長くなって読みづらくなったり、テスト対象アプリケーションへの依存度が高くなりがちなので、レイヤー構造を採用することで、そういった欠点が解消できそうだと思った。

テストコードで書くことが、テスト対象システムの外側、つまり実際にシステムを使うユーザー自身やユーザーの振る舞い、システムのインターフェースに対する操作である点に「振る舞い駆動開発」と呼ばれる理由の1つを見た気がした。

テストコードの中で、アプリケーションを利用する現実世界を表現して書くのは、正にユースケース図の振る舞いのイメージに重なると感じた。

またこの方法だとプロダクトコードと並行してテストコードが書けることが多いので、受け入れテストの早期実行が実現できる。という点にとても共感できた。

このようなユーザー(文章中だとアクター)中心のコーディングスタイルを「スクリーンプレイ」と呼ぶそうで、これは著者が開発している、Serenityにも取り入れられている。スクリーンプレイの詳細は今後公開される章で説明があるようなので楽しみ。だけど、いつ公開されるのかな??

BDD in Action, 2nd Edition 第7章 を読んだ感想メモ

この本はまだMEAPで全部が公開されていないわけだけど、 6章の公開が去年の6月末で、7章は去年の大晦日でした。
まぁ自分の感想は、そこから更に5ヶ月近くかかっていますが。

www.manning.com

6章までを軽くおさらいすると、このような流れでシナリオをfeatureファイルに書き出しました。

  • ステークホルダーやQAエンジニアと協力して、

  • 要件を深掘りして詳細化し、

  • 実例を出して話し合いながら、

  • (テスト)実行可能なシナリオを作る。

  • シナリオはfeatureファイルに、Gherkinフォーマット(Given, When, Then) で書く。

そしてこの7章から少しずつ テストコード (受け入れテストコードと言った方がいいかも。)を書き始めるので、これまでよりもCucumberの使うためのテクニックの説明に入ります。
ですが7章と8章は、テストコードを書き方というよりは、準備やテストコード設計のための原則を説明しています。
中でも7章は、6章までで見てきたシナリオ と ステップ定義を紐づけるときのテクニック、ステップ定義の文法、Gherkinの主なキーワードの説明のような、Cucumberの基本的な使い方の説明が主な内容となっている。

尚、本文ではJavaJavaScript の2種類のパターンで説明していますが、本ブログは基本的にJavaで書くことを前提とした感想になっている。

ステップ定義 (グルーコード)

ステップ定義 (StepDefinition)は、featureファイルに書いたシナリオから、ステップ別にメソッドを定義する。
ステップとは Gherkinの Given, When, Then を指し、ステップをAnd や But で繋げている場合は、それらも1つのステップ定義としてメソッドにする。

ステップ定義メソッドには、シナリオのどのステップに対応しているかを明示するために、 アノテーション(@Given, @When, @Then) を付け、値にシナリオのステップ文を入力する。

ステップ定義は、Givenメソッドがテストの前提条件、Whenメソッドがテストの入力条件、そして Thenメソッドが結果の出力と検証(assertion) を書く。
Cucumberが シナリオのテストを実行する時は、書かれているステップの順番でメソッドを実行するみたい。

ステップ定義は、Cucumberに対して、(featureファイルの)シナリオと対応するテストコードを結びつける役割を持つので、グルーコード (glue code: glue は接着剤)と呼ばれる。

Step Definitions - Cucumber Documentation

Cucumber Test Runner クラス

Cucumber は専用のランナークラスを作成して、テストを実行する。 JavaJUnitを利用する場合は、JUnitの @RunWithアノテーションを利用して、ランナークラスを定義する。

またこのランナークラスの内部では、featureファイルのルートディレクトリ、ステップ定義のルートパッケージなどの設定も行える。

Cucumber Reference - Cucumber Documentation

ステップ定義パラメータ

シナリオのステップ文に書かれた値は、ステップ定義メソッドのパラメータに渡すことができる。

パラメータとして渡す値は、アノテーションの値に記載したステップ文で {} で囲むことでメソッドが参照することができる。{} の中は定数だけでなく、Cucumber Expression(Cucumber独自の正規表現)と 一般的な正規表現が使えるのでテストデータの値を変えたい場合はステップの文が変わらない限り、featureファイル側のシナリオの値を変えるだけで対応できる。
そのため、featureファイルのシナリオ側は、値をリストやテーブル形式で定義することができ、ステップ定義のパラメータはList型オブジェクトで受け取ることも可能。

ステップ定義のパラメータは、@ParameterTypeアノテーションを付けたカスタムクラスを利用することもできる。
シナリオの値を内包した、アクター(ペルソナ)や ビジネスの関心事(ドメイン)として、テスト用のオブジェクト用意することもできます。
Cucumberのテストに、この ペルソナやドメイン などの概念を取り入れて、テストコードをレイヤー化する話は次の8章で詳しく説明することになる。

Cucumber Expressions - Cucumber Documentation

Cucumber Reference - Cucumber Documentation

Background と Hook

Background と Hook(@Befor, @After, など) は、複数のシナリオ、あるいは フィーチャーを横断して、前述のオブジェクトを共有したり、テスト実行前の前処理あるいは後処理の工程を定義することができる。

Backgroundは、主にFeatureファイル内の複数のシナリオで共用するデータテーブルのことを指す。 このデータテーブルをステップ定義で、@DataTableTypeアノテーションを付与したメソッド内でデータオブジェクト生成し、ステップ定義のメソッドで参照できるようになる。

Backgroudは featureファイルで宣言して記載することから、このデータテーブル自体もビジネス側の関心事の1つであることを意識する。

Gherkin Reference - Cucumber Documentation

Hookは、開発/テストチームがテスト実行のために必要な前処理及び後処理を定義するときに使う。
使用事例としては、「テスト開始前にブラウザを開き、終了後にブラウザを閉じる。」といった操作、「テストの整合性のためにテスト実行時にデータ初期化する。あるいはテスト終了後に破棄する」など。

要するに「適切にテストするためには必要な工程だが、ビジネス側の関心事ではない。」ような、シナリオには現れない工程に対して宣言するのが主な使い方になる。

尚、どのシナリオでどのHookを使うかは、シナリオに任意のタグを設定し、ステップ定義メソッドのHook側のアノテーション内にタグを指定すれば、タグに紐づくシナリオを実行する時だけHook処理を行う。といった条件が設定できる。

Cucumber Reference - Cucumber Documentation

テスト環境の準備

章の最後は、テスト環境の構築についての話。

BDDが対象としているテストは、主に統合テスト、受け入れテスト、およびエンドツーエンドテストとなる。このためテストを行う際はテスト対象アプリと連携する他のシステムの構築が必要になることが多い。

データベースであれば、H2などのインメモリータイプを利用すれば実現しやすいが、本番で使うデータベースと異なったり、データベース以外にも連携するコンポーネントがあると、本番と同等環境でテストを行うことが大事になってくる。

そういった点で、テスト環境としてできる限り仮想化環境、特に最近ならDockerでコンテナ化したコンポーネントでテストを実行しようというお話でした。

この章の最後のセクションでは、テスト用のDockerコンテナを管理しやすくする、TestConteinersというオープンソースライブラリが紹介されています。

www.testcontainers.org

TestContainersは私自身も初めて聞いた気がしますが、PostgreSQLなどのRDBMS以外にも、NoSQL、Kafka、Elasticsearch といったコンポーネントを Dockerコンテナで起動し、更にそれらをJavaオブジェクトとしてテストコードで扱うことができるようです。

TestContainersを使えば、テストコードで連携するコンポーネントやテストデータを管理できるので、便利な気がします。

所感

  • ステップ定義メソッドはできるだけシンプルにしたいが、冗長化を避けて汎用的にしようとすると Cucumber Expressionなり、正規表現を多用してしまいそうな気がした。

  • 逆にステップ定義のglue code が複雑になりそうなら、シナリオを見直すことも考えておいた方が良さそう。

  • TestContainersは、連携サービスをJavaオブジェクトで一括管理できそうな気がしていて、個別にdockerfileを用意しなくても良さそうなのが便利な気がするので、今度使ってみたい。

次の8章も公開されているので、続けて読んでいきます。

JetBrains Academyの実施記録

去年の8月頃から流れでJetBrains Academyを取り組んでいた。

kazokmr.hatenablog.com

無料期間を過ぎてもキリが悪かったのでそのまま続けていて、満足いく所まで終えられたので2021年5月末でサブスクを一旦解除するので、やったことの記録をまとめておく。

注)内容が宣伝っぽくなってしまったり、未経験者の勉強のまとめみたいになっているが、今の会社に転職してからはコードを書くことが減ってしまったが、前職で10年ほど開発でガリガリ書いていました。

Jetbrains Academyは主に、未経験者向けのサービスだと思うので、正直場違いな感じで続けていましたけど、受けてみて初めて知ることも多かったので、毎日楽しく学ぶことができました。

プロフィール

hyperskill.org

去年のお盆の頃に始めて、毎日最低1日は解いていた。(毎日1問ずつ問題が出るのでそれは必ずやるようにした。)

トラック

トラックは専攻とかコースのようなもの。それぞれに決められたトピックとプロジェクトを進めていき達成していく。

今回自分がクリアしたものは、Java Developer, Kotlin Developer, Frontend Developer, Preparing for the AP Computer Science (Java), Java Desktop Application Developer の5つ

最後の2つのトラックは特に意識していなくて、Javaのトピックを全て進めていたら達成していた。

f:id:kazokmr:20210513052102p:plain
Tracks

プロジェクト

達成したプロジェクトはJava, Kotlin, Frontend(HTML, CSS, Javascript)合わせて33
難易度の内訳は、Easy : 7, Medium : 5, Hard : 9, Challenging : 12

達成したプロジェクトのコードはポートフォリオとして公開しても問題無いみたいなので、GitHubで公開している。

GitHub - kazokmr/Jetbrains-academy-Java: Jetbrains Academy Java Developerコースでクリアしたプロジェクトです

GitHub - kazokmr/Jetbrains-Academy-Kotlin: Jetbrains Academy でクリアしたKotlinプロジェクトです

GitHub - kazokmr/Jetbrains-Academy-Frontend: Jetbrains Academy の Frontendjj Developerコースでクリアしたプロジェクトです

トピック (ナレッジマップ)

トピックは1つの学習テーマで、セオリーと呼ぶ説明ページを読んだ後、プラクティスとして幾つかの問題に正解することで進めていく。

ラクティスは、選択式もあれば、実際にコードを書いてテストするタイプもある。間違っても特にペナルティは無いが、プロジェクトを達成することでもらえるGemを使ってヒントを得たり、答えを知ることもできる。

コードを書く問題の場合は、正解後に自分のコードを公開したり、他の人がアップしたコードを見ることができる。

さらには受講者同士でコメント欄で、意見や質問、ヒントを出し合ったりもできる。

自分も実際にコードをアップして、イイねといったリアクションをもらったり、「何でこんな書き方をしているの?」って質問をもらって説明したりした。

以下は、達成したトピックをカテゴリ別にマッピングしたもの

Java

Java11 の内容がベースで体系的に200近くのトピックがある。現在の

f:id:kazokmr:20210513060406p:plain
Java

Kotlin

まだβ版扱いだったのでJavaほど網羅されてはいない。スコープ関数も無かったけど進めていくうちに他のサイトを見て覚えた。
Kotlinを本格的に触るのはこれが初めてだったけど、Javaより使いやすい点もあると思ったし好きになった。

f:id:kazokmr:20210513060559p:plain
Kotlin

JavaScript

Javaコースに含まれている課題もあったので他にも行ってみた。 JavaScriptはここまで体系的に学んだことが無かったのでやってみてよかった。

f:id:kazokmr:20210513060735p:plain
JavaScript

Scala

Scalaもβ版でトピックも8つしか無かったので理解したとは言い難いが、前述のKotlinをクリアした後だったので、Kotlinの雰囲気で学ぶことはできた。

f:id:kazokmr:20210513060823p:plain
Scala

Backend / Android / Java Swing

どれも前職で関わったプロジェクトで1回以上は触ったことがあったけど、JavaScriptと同じで体系的に学んだことが無かったので良かった。これらを理解した上でもう一度あの時にプロジェクトを進められたら面白そうだと思った。

f:id:kazokmr:20210513060925p:plain

Frontend

利用期間が終了するまでに日数があったので最後にプロジェクトも含め進めた。前職の時からそうだったのだけど、CSS使って、画面のレイアウトを考えたりするのは苦手だなと改めて感じた。(大きさや位置を調整したり、どんな色がいいか考えたりする所)

HTML4 や CSS2 の頃のことしか知らなかったので、その頃より書きやすくなった気はするし、Javascriptと合わせて動的な制御を行うのは面白い。

f:id:kazokmr:20210513061005p:plain

Fundamentals

情報工学の基礎やアルゴリズムが学べ、その他にもビルドツールやGit、IDEなどの開発系ツール、データベース・SQLについて学習できる。

アルゴリズムは、大学の専攻が情報系では無かった(電気工学)だったので、一から学ぶことができて知識の底上げになった。

f:id:kazokmr:20210513061106p:plain

Math

プロジェクトを達成するために必要なものトピックだけを実施した。

英語で書かれているのもあるが、数学は大学以降はほとんど勉強していなかったから結構難しかった。
学生時代は得意科目の一つだったと思っていたけど、かなり衰えてしまっていた。

全部のトピックを取り組みたい気持ちもあったけど、それはまた別の機会にする。

f:id:kazokmr:20210513061409p:plain

まとめ

プログラミングだけでなくて、情報工学の基礎的なことも体系的に学べるので、個人的にも学生時代の復習ができた気がしてチャレンジして良かった。

セオリーも問題も全て英語なので、説明がわからないことや問題文の意味を取り違えてしまうことも多かったけど、DeepLを使って続けることができた。

今回、大体の内容をクリアできたし他に学びたいこともあるので、一旦やめるが、トピックが増えて復習したいと思ったら再開すると思う。