持続的な運用開発のために社
内基盤を整えている話
〜auditのCI組み込みやlintの
社内PKG化〜
2019/8/1 bitbank LT Night #5 ~Angular~
@studioTeaTwo
ToshiyaTanaka@studioTeaTwo
🏢個人事業主(屋号:studioTeaTwo)/ OverPage, inc.
👨‍💻 Angular/react/typescript/c#
個人事業主(フロントエンドエンジニア)として bitbankさんのお手伝いさ
せていただいているので今日はその話をします
今日話すこと
1. 基盤を再整備している背景
2. auditのCI組み込み
3. lintの社内PKG化
基盤を再整備している
背景
背景(1)
● 全社的にtypescriptへシフト中
○ フロントエンド typescript(Angular)
○ バックエンド Java→typescriptへ移行中
● リポジトリ数
○ typescript 50 / 全社 400
背景(2)
● ソース管理
○ bitbucket/github → gitlabへ移行
○ オンプレで構築してCIランナーなども社内PCで実施
○ NPME(npm enterprise)で社内PKG化して再利用
背景(3)
● リリースサイクル
○ 定期は週一
● セキュリティは業界的に意識高く
○ gitlabにしてオンプレにしてるのもその一環
● 攻めに転じていくために色々基盤を整えたい時期
auditのCI組み込み
auditをCIに組み込みたい、しかし...
● レポート量が多い...
● アップデートもそう簡単ではない...重大度とメンテナンス難易度は比例しない。
重大度が低くてもアップデートが難しいケースがある
● CIでは参考値としてallow_failerしても良いが、いつも黄色になってるのも気持
ち悪い....
auditの結果を任意にフィルタリングしてCIを
落としたい!
やること
● yarn audit --jsonの出力をnodeスクリプトで加工する
● コマンド引数で対象にする報告を指定する
○ 対象にする重大度を指定する
○ 特定のレポートNOやPKG名を除外する
● 重大度毎の件数だけでなくレポートNO毎のPKG件数も出す
○ インシデントが発生している根っこのPKGだけ知りたい
実施内容
● yarn audit --jsonの出力データ構造を解析する
● nodeスクリプトを作成する
● NPMEにする
yarn audit --jsonの出力データ構造
● JSONL(JSON Lines)形式。区切り記号無し
● summary(1個)とadvisory(たくさん)で構成される
● advisoryはデータ量が多い
○ 1件あたりnpm auditの数倍ある
○ 出力結果が20MB超えて驚いた
{
"type": "auditSummary",
"data": {
"vulnerabilities": {
"info": 0,
"low": 63,
"moderate": 2,
"high": 4,
"critical": 0
},
"dependencies": 72361,
"devDependencies": 0,
"optionalDependencies": 0,
"totalDependencies": 72361
}
}
{
"type": "auditAdvisory",
"data": {
"resolution": {
"id": 786,
"path": "jest>jest-cli>jest-config>babel-jest>babel-plugin-istanbul>test-exclude>micromatch>braces",
"dev": false,
"optional": false,
"bundled": false
},
"advisory": {
"findings": [
{
"version": "1.8.5",
"paths": [
"jest>jest-cli>jest-config>babel-jest>babel-plugin-istanbul>test-exclude>micromatch>braces",
<省略>
],
"dev": false,
"optional": false,
"bundled": false
}
],
"id": 786,
"created": "2019-02-15T21:44:30.680Z",
"updated": "2019-04-02T18:18:29.356Z",
"deleted": null,
"title": "Regular Expression Denial of Service",
"found_by": {
nodeスクリプトによるフィルタリング
● コマンド引数を解析する
● yarn audit --jsonを実行して結果取得する
● advisoryを必要な項目だけ抽出する
● コマンド引数の指定内容でフィルタリングする
● 小計を算出する
● CIにログとexitCodeを伝える
function normalizeAuditFormat(data: string): AuditAdvisory {
const dataJSON = JSON.parse(data);
// 出力を`yarn audit`相当にする
return {
severity: dataJSON.data.advisory.severity,
reportNo: dataJSON.data.advisory.id,
title: dataJSON.data.advisory.title,
module_name: dataJSON.data.advisory.module_name,
patched_versions: dataJSON.data.advisory.patched_versions,
path: dataJSON.data.resolution.path,
url: dataJSON.data.advisory.url,
};
}
advisoryを必要な項目だけ抽出する
auditAdvisories
// 脆弱性プライオリティで絞り込む(一番最初に判定すること)
.filter((advisory) => targetVulnerabilities.some((targetVulnerability) =>
targetVulnerability === advisory.severity))
// NPM報告レポートNOで除外する
.filter((advisory) => !ignoreReportNumbers.some((ignoreReportNumber) =>
ignoreReportNumber === advisory.reportNo))
// NPMパッケージ名で除外する
.filter((advisory) => !ignorePackages.some((ignorePackage) => ignorePackage ===
advisory.module_name))
フィルタリング
function subtotalPerReportNo(outputAdvisories: AuditAdvisory[]): AuditSubtotal {
const knownReportNo = new Set(); // 重大度を跨いで同じレポートNoが存在することは無いため、1個で使い回せる
const initialValue: AuditSubtotal = { low: 0, moderate: 0, high: 0, critical: 0 };
return outputAdvisories.reduce((accumulator, currentValue) => {
switch (currentValue.severity) {
case 'low': {
if (knownReportNo.has(currentValue.reportNo)) {
break;
}
accumulator.low += 1;
knownReportNo.add(currentValue.reportNo);
break;
}
...
}
return accumulator;
}, initialValue);
}
レポートNO毎の正味件数
パッケージ依存の自動アップデート
● 現在調査中
● gitlabかつコードを社外に出したくない
● 実質renovate一択
lintの社内PKG化
tslintを社内PKG化したい理由
● 属人性を下げPJ間の移動をしやすくする
● 社内標準ルールとリポジトリ固有ルールを明確にする
● 変更やリプレースしやすくする
○ tslintからeslintへ移行問題
アプローチ
● 属人性を下げPJ間の移動をしやすくする
⇨ 社内標準ルールセットを定める
● 社内標準ルールとリポジトリ固有ルールを明確にする
⇨ extendsを活用して、社内標準ルールをPKG化し各リポジトリにはリポジトリ
固有ルールだけを記述する
● 変更やリプレースしやすくする
⇨ NPMEにて社内PKG化して一元管理する
実施内容
● ドキュメントベースでルール標準素案を起こす
● NPMEの作成する
● 各リポジトリに組み込んでいく
ルール標準セットの種類
● 共通
○ tslint:allをベースに策定する
○ tslint-microsoft-contrib/vrsource-tslint-rulesからピックアップ
● Angular
○ Angular-CLIのlintを外部PKGしたtslint-angularをベースに策定する
● サーバサイド(未了)
● rxjs(未了)
● テスト(未了)
ドキュメント
原則tslint:allにしたので、falseにする理由と拡張ルールからピックアップする理由
を記録した
NPMEの作成
● js/tsでソースやテストを記述して、jsonファイルをアウトプットする
○ コメントが書ける
● 活用する拡張ルールセット系もこのPKGに内包する
○ 各リポジトリではこのNPMEだけインストールすればいいように
NPME PKG構成
ソースファイル
ビルドスクリプト
node_modulesパス問題
● jsonファイルは静的ファイルのためjsファイルのようにpackage.jsonを利用した
node_modules解決はできない
● tslintはextendsやrulesDirectoryに指定されたnpm名の解決のために
browserify/resolve を用いて自力でnodeのrequire.resolve()同様の探索を行って
いる(babelも同様)
● ベースパスからnode_modulesを探して探索する
● しかし、npmもyarnもフラットインストールがデフォルトになるため、利用先にインス
トールされるとベースパスから上の階層に依存PKGが存在することになる
● そのためnpm名だと解決できない(上位ディレクトリに再帰掛けるのは厄介)
{
"rulesDirectory": [
"../../tslint-microsoft-contrib",
"../../vrsource-tslint-rules/rules"
],
}
{
"rulesDirectory": [
"tslint-microsoft-contrib",
"vrsource-tslint-rules/rules"
],
}
ソースファイル
ビルドファイル
×物理で殴る ○相対パス参照に置き換える
各リポジトリでやること
● package.jsonにNPMEの社内標準ルールセットPKGを追加する
● 特段事情が無ければdevDependenciesからtslint絡みは消す
● tslint.jsonのextendsで社内標準ルールの選択をする
● arrayに並べる順番は、基本的なものを最初に置き徐々に特有のものにしてい
く。同じルールが定義されている場合に後のもので上書きされるため
● tslint.jsonのrulesに各リポジトリの固有ルールを上書き・追加する
tslint.json例
{
"extends": ["@bitbank/coding-rules/tslintBase", "@bitbank/coding-rules/tslintAngular",
"tslint-config-prettier"],
"rules": {
"component-selector": [
true,
"element",
"baw",
"kebab-case"
],
...
}
・左から基本となるものを並べて上書きしていく。
・一番右にtslint-config-prettierを置く。フォーマッタ系
ルールをリセットして prettierに一任する。
・リポジトリ固有のルールだけを記述する
・例ではAngularのHTMLタグ名のprefixを定めている
課題
● 標準ルール策定は割れ窓になりやすい
○ 基本は緩める方向に
○ それでも統一することは難しい。採用フレームワーク毎の事情、tscと
babel-preset-typescriptの文化違いなど
○ NPME化して変更・リプレースしやすくすることを第一目的にするのがいい
かも > eslint問題
END

持続的な運用開発のために社内基盤を整えている話 〜auditのCI組み込みやlintの社内PKG化〜