kazokmr's Blog

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

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を使って続けることができた。

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

mkcertで生成した証明書をHAProxyにインポートしローカルでSSLオフロードする

前回、ローカル環境でHAProxyを使った分散環境が用意できたので、HTTPS接続にも対応させてみる

kazokmr.hatenablog.com

mkcert で自己証明書を生成する

mkcert というツールを使い、ローカルCA証明書を作成・インポートして、サーバー証明書を作成します。 手順は、mkcert のGitHubページに従って進めます。

GitHub - FiloSottile/mkcert: A simple zero-config tool to make locally trusted development certificates with any names you'd like.

mkcert のインストール

brewでインストールします。nssはFireforx からアクセスする場合に必要です。

brew install mkcert
brew install nss

ローカル CA 証明書を作り 端末にインポートする

下記のコマンドを実行することでインポートまで行われます。途中でrootパスワードを求められたら、端末の管理者パスワードを入力します。

mkcert -install
Created a new local CA 💥
Sudo password:
The local CA is now installed in the system trust store! ⚡️
The local CA is now installed in the Firefox trust store (requires browser restart)! 🦊
The local CA is now installed in Java's trust store! ☕️

証明書を確認します。Macの場合 「Keychain Access」を開き左側のメニューで「System」を選び、「Certificates」タブを選ぶと、"mkcert ....." というルートCA証明書がインポートされました。

証明書を生成する

localhost に対する証明書を作成します。mkcert localhost とコマンド実行すると、localhost.pem と localhost-key.pem が生成されます。

mkcert localhost

Created a new certificate valid for the following names 📜
 - "localhost"

The certificate is at "./localhost.pem" and the key at "./localhost-key.pem" ✅

It will expire on 23 April 2023 🗓

他にも、ワイルドカード (*.example.com) や ループバックアドレス (127.0.0.1) などでも作成できる。

HAProxyにSSLオフロード設定を行う

証明書をHAProxyにインポートする

HAProxyにインポートする証明書ファイル(.pem)は、1つのファイルにまとめる必要があるため、先程作成したキーペアを1つにまとめておきます。

HAProxy version 2.3.19 - Configuration Manual

cat localhost.pem localhost-key.pem > cert.localhost.pem

作成したPemファイルをDockerコンテナで利用するようにDockerfileを修正します

FROM haproxy:2.3
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
COPY cert.localhost.pem /usr/local/etc/haproxy/cert.localhost.pem

設定ファイルを編集する

haproxy.cfg に 証明書のパス、httpヘッダーへのx-forwardedの追加、httpからhttpsへのリダイレクトを追加します。

...
frontend http-in
    bind *:80
    default_backend servers

    # サーバー証明書のパスを指定
    bind :443 ssl crt /usr/local/etc/haproxy/cert.localhost.pem

    # x-forwarded-for を有効にし、プロトコルとポートもheader にセットする
    option forwardfor
    http-request set-header x-forwarded-proto https
    http-request set-header x-forwarded-port 443

    # httpからのアクセスをhttpsにリダイレクトする
    redirect scheme https if !{ ssl_fc }
...

最後にHAProxyのコンテナイメージを再ビルドして実行します。 またホスト側から443ポートでアクセスできるようにすること。

...
services:
  haproxy:
    build: ./haproxy
    depends_on:
      - app01
      - app02
      - app03
    ports:
      - 80:80
      - 443:443
...

動作確認

ブラウザから https でアクセスできました。httpでアクセスした場合もhttpsにリダイレクトします。 またSpringBootアプリのセッション管理も正常でした。

httpsでアクセス

ローカルでSSLやLBの動作確認できるの便利。 今日の設定内容は証明書を除いてGitHubリポジトリにあります。

github.com

参考記事

多機能なロードバランサとして使える多機能プロクシサーバー「HAProxy」入門 | さくらのナレッジ

多機能プロクシサーバー「HAProxy」のさまざまな設定例 | さくらのナレッジ

HAProxy version 2.3.19 - Configuration Manual