- 投稿日:2019-05-21T23:30:48+09:00
Reactの最速チュートリアル
前文
まともなReactのセットアップがなかったので書きます。
Macの場合だけ Windowsもnodeのインストール以外はだいたい同じなはず
環境構築
nodeの環境構築
$ brew install node
$ npm install -g n
$ sudo n stable
Reactの雛形作成
$ npm install -g create-react-app
$ npx create-react-app "プロジェクト名"
これでReactの雛形のプロジェクトのフォルダが生成されます。
プロジェクト名は小文字英数字が望ましい というか大文字ではプロジェクト作れないとりまブラウザで確認
$ cd "プロジェクト名"
$ npm start
後は勝手にビルドされてブラウザが開くはず
ディレクトリ構造の解説
これを解説しないとこの辺のファイルを妙に弄ってプロジェクトを破損させたりする輩がいるので解説
- node_modules/
- npm install ... とかで入るライブラリ等の実体がある所
- いじるな
- public/
- index.htmlとかfaviconとかを入れておく場所
- src/
- Reactのソースコードを書く場所
- build/
- index.htmlとかfaviconとかが入る場所
- package.json
- npm等で操作したデータが入っている ここに依存パッケージとかビルドするスクリプトとかが入っている
- 消すな
- yarn.lock
- パッケージ管理にyarnを使っている場合関連パッケージが記載されている
- 消すな
- README.md
- 読め
ReactというかWebpackの仕組み
正確な説明はしないけどイメージ程度に
Reactの独自の言語であるJSXをコンパイルして、普通のブラウザで見られるhtml,css,javascriptの形式にする。
これがnpm run buildのコマンドのやっていることnpm startはnpm run buildをやってくれ、ブラウザに対してリンクを開いてくれる。
この状態でソースコードを編集すると自動的にビルドしなおしてくれるWebpackはいろいろなライブラリを導入したり、ReactやVueのソースコードをひとまとめにしてくれるやつのこと
基本的にReactはこのWebpackとかを通して、まとめてビルドしない動かない詳しい人が見たら怒りそうな解説であるが、イメージが大事...
チュートリアル
- 投稿日:2019-05-21T23:30:48+09:00
Re:0から始めるReactのチュートリアル
前文
まともなReactのセットアップや説明がなかったので書きます。
周り見てた感じいろんなQiitaの記事を片っ端から試して凄まじい構成になっているものをよく見るので、環境構築から基礎まで書けたらよいと思います。ちょいちょい更新予定環境構築はMacの場合だけ Windowsもnodeのインストール以外はだいたい同じなはず
環境構築
nodeの環境構築
$ brew install node
$ npm install -g n
$ sudo n stable
Reactの雛形作成
$ npm install -g create-react-app
$ npx create-react-app "プロジェクト名"
これでReactの雛形のプロジェクトのフォルダが生成されます。
プロジェクト名は小文字英数字が望ましい というか大文字ではプロジェクト作れないとりまブラウザで確認
$ cd "プロジェクト名"
$ npm start
後は勝手にビルドされてブラウザが開くはず
ディレクトリ構造の解説
これを解説しないとこの辺のファイルを妙に弄ってプロジェクトを破損させたりする輩がいるので解説
- node_modules/
- npm install ... とかで入るライブラリ等の実体がある所
- いじるな
- public/
- index.htmlとかfaviconとかを入れておく場所
- src/
- Reactのソースコードを書く場所
- build/
- index.htmlとかfaviconとかが入る場所
- package.json
- npm等で操作したデータが入っている ここに依存パッケージとかビルドするスクリプトとかが入っている
- 消すな
- yarn.lock
- パッケージ管理にyarnを使っている場合関連パッケージが記載されている
- 消すな
- README.md
- 読め
ReactというかWebpackの仕組み
正確な説明はしないけどイメージ程度に
Reactの独自の言語であるJSXをコンパイルして、普通のブラウザで見られるhtml,css,javascriptの形式にする。
これがnpm run buildのコマンドのやっていることnpm startはnpm run buildをやってくれ、ブラウザに対してリンクを開いてくれる。
この状態でソースコードを編集すると自動的にビルドしなおしてくれるWebpackはいろいろなライブラリを導入したり、ReactやVueのソースコードをひとまとめにしてくれるやつのこと
基本的にReactはこのWebpackとかを通して、まとめてビルドしない動かない詳しい人が見たら怒りそうな解説であるが、イメージが大事...
src/ にあるコードの解説
ここは脳死でそういうもんなんやと考えてもいい
大事なのはせいぜいAppがpublic/index.htmlのrootにRenderされるという情報
後はのちのち考えればよいindex.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; ReactDOM.render(<App />, document.getElementById('root')); serviceWorker.unregister();App.js
index.jsのrenderから呼ばれたコンポーネントはこちら
これはFunctional Componentと言ってちょっと新しい書き方なのでややこしい
要はfunction Appがコンポーネントのように振る舞う
returnの中身が描画して欲しいデータということApp.jsimport React from 'react'; import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App;しかしこれはわかりずらいので従来のコンポーネントに戻したコードがこちら
割とこっちの方が馴染みのあるコードなはず
ネットに転がってるチュートリアルもこっちのほうがまだ多い気がするApp.jsimport React from 'react'; import logo from './logo.svg'; import './App.css'; class App extends React.Component { render() { return( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ) } } export default Appチュートリアル
- 投稿日:2019-05-21T23:30:48+09:00
完全に心が折れた人向けのReactチュートリアル
前文
まともなReactのセットアップや説明がなかったので書きます。
周り見てた感じいろんなQiitaの記事を片っ端から試して凄まじい構成になっているものをよく見るので、環境構築から基礎まで書けたらよいと思います。ちょいちょい更新予定環境構築はMacの場合だけ Windowsもnodeのインストール以外はだいたい同じなはず
そもそもReactって
React(リアクト)は、Facebookとコミュニティによって開発されているユーザインタフェースを構築するためのJavaScriptライブラリである[3]。React.jsまたはReactJSの名称でも知られている。wikipediaより引用
要はフロントを作るためのライブラリです。htmlとjsとcssの凝ったバージョンだと思えばよし
npmとかを使うから、Node.jsと混同したりサーバーサイドも関係あるんちゃう?って考える人も多いですが、関係ないです。ただのフロントエンドを構築するためのライブラリです。環境構築
nodeの環境構築
$ brew install node
$ npm install -g n
$ sudo n stable
Reactの雛形作成
$ npm install -g create-react-app
$ npx create-react-app "プロジェクト名"
これでReactの雛形のプロジェクトのフォルダが生成されます。
プロジェクト名は小文字英数字が望ましい というか大文字ではプロジェクト作れないとりまブラウザで確認
$ cd "プロジェクト名"
$ npm start
後は勝手にビルドされてブラウザが開くはず
ディレクトリ構造の解説
これを解説しないとこの辺のファイルを妙に弄ってプロジェクトを破損させたりする輩がいるので解説
- node_modules/
- npm install ... とかで入るライブラリ等の実体がある所
- いじるな
- public/
- index.htmlとかfaviconとかを入れておく場所
- src/
- Reactのソースコードを書く場所
- build/
- index.htmlとかfaviconとかが入る場所
- package.json
- npm等で操作したデータが入っている ここに依存パッケージとかビルドするスクリプトとかが入っている
- 消すな
- yarn.lock
- パッケージ管理にyarnを使っている場合関連パッケージが記載されている
- 消すな
- README.md
- 読め
ReactというかWebpackの仕組み
正確な説明はしないけどイメージ程度に
Reactの独自の言語であるJSXをコンパイルして、普通のブラウザで見られるhtml,css,javascriptの形式にする。
これがnpm run buildのコマンドのやっていることnpm startはnpm run buildをやってくれ、ブラウザに対してリンクを開いてくれる。
この状態でソースコードを編集すると自動的にビルドしなおしてくれるWebpackはいろいろなライブラリを導入したり、ReactやVueのソースコードをひとまとめにしてくれるやつのこと
基本的にReactはこのWebpackとかを通して、まとめてビルドしない動かない詳しい人が見たら怒りそうな解説であるが、イメージが大事...
src/ にあるコードの解説
ここは脳死でそういうもんなんやと考えてもいい
大事なのはせいぜいAppがpublic/index.htmlのrootにRenderされるという情報
後はのちのち考えればよいindex.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; ReactDOM.render(<App />, document.getElementById('root')); serviceWorker.unregister();index.jsのrenderから呼ばれたコンポーネントはこちら
これはFunctional Componentと言ってちょっと新しい書き方なのでややこしい
要はfunction Appがコンポーネントのように振る舞う
returnの中身が描画して欲しいデータということApp.jsimport React from 'react'; import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App;しかしこれはわかりずらいので従来のコンポーネントに戻したコードが以下のコード
割とこっちの方が馴染みのあるコードなはず
ネットに転がってるチュートリアルもこっちのほうがまだ多い気がする
もしこっちのほうがいいという人は、コピペすればよいApp.jsimport React from 'react'; import logo from './logo.svg'; import './App.css'; class App extends React.Component { render() { return( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ) } } export default Appチュートリアル
- 投稿日:2019-05-21T23:17:33+09:00
Failed to execute 'createElement' on 'Document': The tag name provided ('static/media/icon.ac7a4370.svg') is not a valid name.
react-svg-loaderを使い表題のエラーが出た場合はwebpack.configに以下の設定を追加してください
config.resolve.extensions.push('.svg') config.module.rules = config.module.rules.map(data => { if (/svg\|/.test( String(data.test))) data.test = /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/ return data }) config.module.rules.push({ test: /\.svg$/, use: [ { loader: require.resolve('babel-loader') }, { loader: require.resolve('react-svg-loader') } ] })
- 投稿日:2019-05-21T21:34:30+09:00
Reduxアーキテクチャのサイクルをコードリーディングで一通り確認する
この文書では、React JS Tutorialsのソースコードをコードリーディングすることで、Reduxアーキテクチャのサイクルを一通り確認します。
事前知識
- reactについては公式チュートリアルは完了しているものとします。
準備
node.jsのインストールは完了しているものとします。
React JS Tutorialsのソースコードをダウンロードしてください(※バージョン違いが生じないようにフォークしてあります)。
ダウンロードしたらプロジェクトのルートディレクトリで、
cd 5-redux-react npm i npm run dev
します。
そして、localhost:8080をブラウザで開いてください。
コードリーディング
まず
src/js/client.js
を見てください。ここがアプリケーションのエントリポイントになります。普通のReactアプリケーションと同様に、ReactDOM.render()
を呼んでいます。import React from "react" import ReactDOM from "react-dom" import { Provider } from "react-redux" import Layout from "./components/Layout" import store from "./store" const app = document.getElementById('app') ReactDOM.render(<Provider store={store}> <Layout /> </Provider>, app);
Layout
コンポーネントを確認します。src/js/components/Layout.js
を見てください。import React from "react" import { connect } from "react-redux" import { fetchUser } from "../actions/userActions" import { fetchTweets } from "../actions/tweetsActions" @connect((store) => { return { user: store.user.user, userFetched: store.user.fetched, tweets: store.tweets.tweets, }; }) export default class Layout extends React.Component { componentWillMount() { this.props.dispatch(fetchUser()) } fetchTweets() { this.props.dispatch(fetchTweets()) } render() { const { user, tweets } = this.props; if (!tweets.length) { return <button onClick={this.fetchTweets.bind(this)}>load tweets</button> } const mappedTweets = tweets.map(tweet => <li key={tweet.id}>{tweet.text}</li>) return <div> <h1>{user.name}</h1> <ul>{mappedTweets}</ul> </div> } }画面が表示される時(componentWillMount)にユーザーを取得し、画面に表示しています。この動作に沿って説明をしていきます。
src/js/components/Layout.js
の15行目を見てください。componentWillMount() { this.props.dispatch(fetchUser()) }propsのdispatchを呼んでいますね。
ここでpropsにdispatchがあると言うのは、このコンポーネントへ
connect
しているからです。connect
は、7行目にあります。@connect((store) => { return { user: store.user.user, userFetched: store.user.fetched, tweets: store.tweets.tweets, }; }) export default class Layout extends React.Component {
@
はDecoratorといって、コード上「次」にある要素に対して、影響を及ぼすことができる機能です。今回のコード例では、これは「次」にあるLayout
コンポーネントに対するconnect
の記述です。connect
することで、Reactコンポーネントにthis.props.dispatchが生えて、dispatchできるようになります。ここでは、
fetchUser()
というActionをdispatchしていると言うことになります。このdispatchの結果、ユーザーの情報が読み込まれて画面が更新されることになるわけです。ここではその流れを追っていきます。
src/js/actions/userActoins.js
を見てください。1行目に
fetchUser()
があります。ここはFETCH_USER_FULFILLED
というタイプのアクションを作成しています。またpayloadにユーザーのデータが入っています。これがdispatchされているわけです。dipatchされると、それをreducerが処理し、storeを変更します。
src/js/reducers/userReducer.js
を見てください。19行目がFETCH_USER_FULFILLEDを処理するreducerです。
case "FETCH_USER_FULFILLED": { return { ...state, fetching: false, fetched: true, user: action.payload, } }reducerが何をやっているかと言うと、
state
を変更します。
...state,
で現在のstateを展開し、fetching
,fetched
,user
を変更して、returnします。returnされた結果が新しいstateになります。ここでいう
state
というのはアプリケーション全体におけるただ一つのstate
です。アプリケーション内のstate
を全てまとめて一元管理するのがreduxの考え方です。reactの感覚でいうと
state
は個々のコンポーネントにあるもののように思われますが、それとは違うので考え方の切り替えが必要です。さて、こうして変更された
state
は画面に反映されなければなりませんが、それはどのようにされるのでしょうか。再度、Layout
コンポーネントを確認します。src/js/components/Layout.js
の7行目を見てください。@connect((store) => { return { user: store.user.user, userFetched: store.user.fetched, tweets: store.tweets.tweets, }; }) export default class Layout extends React.Component {
connect
が、reduxのstate
と画面のprops
の紐付けを行います。returnされた要素が画面のprops
として渡されます。
state
が更新されると、reduxは以前のstateと比較を行い、更新されている部分のデータを使っている画面コンポーネントについて再描画を行います。ここまででアクションの発行から画面の更新までの流れを一通り確認できました。
- 投稿日:2019-05-21T20:41:48+09:00
ブロックチェーン上に読書感想文を書けるサービス作った
TL;DR
ブロックチェーンを利用したDappsの開発を勉強中です。
一度サンプルアプリケーションを作ってみたかったので作りました。
とりあえずざっくり作りたかったのでいろいろ適当です。サービス概要
https://iread-decent.herokuapp.com/
サービス名は「IRead」としました。
いろいろ勉強で本を読むのですが、読むだけで終わってしまい、読んで得た知見・学んだことを共有できずにいました。
そこでIReadを使って本を読んだ感想文をブロックチェーン上に記録することで、学びの証跡を残せ、それを自分の公開鍵で自分の学びであることを証明できます。解説
GitHub
https://github.com/theMistletoe/IRead
使用した技術
- Truffle
- solidity
- react(Next.js)
工夫した点
solidity
https://github.com/theMistletoe/IRead/blob/master/contracts/BookReportToken.sol
肝となるコントラクトコードについてはERC721の規格を継承しています。
OpenZeppelinのコードを継承する形ですね。このERC721に準拠したsolidityのコードを書く正解がちょっと分かりませんでした。
同じように他の方のERC721トークンの実装を見ると継承しているものの、コントラクトの属性を継承した子のフィールドに直接もってやり取りしているようだったので、それだとonlyOwner制約などの恩恵を受けられないのではと思いました。なので今回はアプリケーションの属性についてはトークンのメタ情報(tokenURI)にJSON形式で保持することにしました。
実装方針が正しいかどうかは不明なので詳しい方がいれば教えてください。
react
https://github.com/theMistletoe/IRead/blob/master/client/comps/AllReports.js
reactについてはてんで素人で書いたので間違い・不正があれば突っ込んでください笑
一点だけ、全トークンを表示するときに、トークンがburnされているとトークンlengthが合わなくなって全件取得できなかったので、
burnされたトークン含め過去発行されたトークンの総数の取得をTrensferイベントから取得しています。
(burnされるとtotalSupply関数で取得されるトークン数も-1される)参考
追記します。
- 投稿日:2019-05-21T18:32:51+09:00
React.Fragmentの使いどころ
はじめに
Reactを触ることになって、初めて知ることだらけ。
技術用語がまだスッと入って来ず、先輩から何度か説明してもらい、手を動かし、記事を読んでやっとふわっと理解したので一応記事にしておく。
ほぼほぼ自分用のメモなので、説明が足りないところがあったり間違ってるところがあったらごめんなさい。React.Fragmentってなに
Reactではrenderメソッドのreturnの中身は一つの要素でないといけないらしい。
要素が並列で並んでしまう場合に<React.Fragment>
を使うとHTMLタグとして生成されずに親要素として機能してくれる。使い所と使い方
return ( <ul> <li>hoge</li> <li>hoge</li> </ul> )これは
<ul>
の1要素だから<React.Fragment>
は不要!
<ul>
のなかに<li>
がたくさんあるけどまとめなくて平気なの?と思ったが、<li>
は<ul>
の子要素だから<ul>
1つの要素として見られる。
return ( hogeリスト <ul> <li>hoge</li> <li>hoge</li> </ul> )続いてこちらは
"hogeリスト"
という文字列要素と、<ul>
要素が並列で存在しているのでエラーとなってしまう。Reactでは、そんな時に
<React.Fragment>
を使って1つの要素としてまとめることができる。2つになっちゃたのを1つのものとするためにサ○ンラップで包んであげるイメージ。
早速やってみる。return ( <React.Fragment> hogeリスト <ul> <li>hoge</li> <li>hoge</li> </ul> </React.Fragment> )これで、 という1要素に収まった。
ん? 待てよ。それなら
<div>
で囲んであげても同じように親要素としてまとめてあげることはできるぞ...と思ったが、それだとHTMLに不要な<div>
タグが生成されてしまった。
そこで<React.Fragment>
を使うことで、親要素としての役目を果たしつつ、HTMLタグは生成されなくなった。 便利!Reactのお作法を守るためだけに使うことができるから、
<React.Fragment>
を使うと良い。
- 投稿日:2019-05-21T12:31:57+09:00
フロントエンドでもtrivyを使って脆弱性対策したい!
伝えたいこと
- フロントエンドもコンテナ経由でアクセスさせよう
- コンテナならtrivyを使ってヤバそうな脆弱性を知ることができる
- trivyならCIに組み込むことも簡単
- multi-stage buildの場合は工夫が必要
- いまの状況でできる範囲の脆弱性対策からはじめよう
今回の記事でできること
CI上で、本番用のフロントエンドのコンテナをmulti-satge buildでつくり、
trivyを利用して脆弱性を検知するところまでを目指します。最後に、trivyを利用したレベル別の運用イメージも提案します。
導入
週末、trivyというコンテナ向けの脆弱性検知ツールが正式リリースされました。公開が5日目の5/21 12:00時点で 900star以上獲得しています。
trivyの詳細は原作者である @knqyf263 さんの「CIで使えるコンテナの脆弱性スキャナ」という記事を参照ください。
ただフロントエンドの方は「脆弱性はインフラ側が対応してくれるから、自分には関係ない」と思っていないでしょうか。
そのような方をメインに、今回は以下の内容を紹介します。
- コンテナを使ってフロントエンドの本番環境を構築する
- trivyを利用して、脆弱性が含まれていないかチェックする
- trivyを通じた脆弱性の運用方法を学ぶ
僕は普段はフロントエンドも担当するエンジニアで、セキュリティに対して体系だって学んだことはありません。ただ、trivyのコミッタでもあり、脆弱性の運用も少し知っているので、フロントエンドでもDevSecOpsを定着させるきっかけになるといいなと考えています。
ソースコード
今回使うコードは、すべて以下のレポジトリにあります。
https://github.com/tomoyamachi/trivy-with-react
この記事では触れませんが、開発環境でもコンテナ経由でホットリロードするような仕組みになっています。
実践 : プロジェクト作成~trivyの導入
ここからはプロジェクトの作成と、CIにtrivyを組み込むまでの手順を書きます
1. プロジェクトの作成
今回はCreate React Appを利用して、Reactのプロジェクトをまず作成します。
そして、デフォルトではパッケージ管理ツールにyarnが利用されているので、今回はプロジェクトのパッケージ管理をnpmで実行します。$ npx create-react-app trivy-with-react $ cd trivy-with-react $ rm -fr node_modules yarn.lock $ npm install2. 本番用コンテナの作成
- 本番用のコードを生成
- nginxコンテナに生成したコードを移し、nginxを起動
という手順をとります。
通常であれば1ファイル
そして通常2つのステップは、multi-stage buildを利用して、1つのDockerfileでコンテナを作成します。
Dockerfile# build environment FROM node:12.2.0-alpine as build WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json RUN npm install --silent RUN npm install react-scripts@3.0.1 -g --silent COPY . /app RUN npm run build # production environment FROM nginx:1.16.0-alpine COPY --from=build /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]1ファイルでmulti-stage buildの問題点
multi-stage buildは1つのDockerfileで最終成果物のimageだけを出力してくれるので、通常はベストプラクティスです。
その場合、以下のように、builder側のイメージがIMAGE IDでしか特定できない形で作成されているのがわかります。$ docker images REPOSITORY TAG IMAGE ID ... trivy-with-react test 96c4fdfac287 # nginx側 <none> <none> 41fd97016d25 # builder側しかし、今回、脆弱性はbuilder側のコンテナにもあることに気がついた方もいるでしょう。package-lock.json もそうですし、一般的ではないですが、Static Linkでビルドする場合も考えられます。
trivyは
REPOSITORY:TAG
の形式でコンテナイメージを指定してスキャンするので、multi-stage buildにしてしまうと、builder側の脆弱性が放置されてしまいます。そのため、builder側と、nginx側のコンテナイメージを独立させて、参照可能にしたいと思います。具体的には、Dockerfileを分けることで、それぞれのイメージにタグを付けます。
また、2回コマンド実行するのも微妙なのでmakefileを作成します。※ もしも1つのDockerfileでmulti-stage buildをし、builder側のイメージ名を指定することができるのであれば、切実に情報がほしいです!
trivy用の構成
コードを見てもらえばわかりますが、Dockerfileをbuilder用(Dockerfile.builder)と、nginx用(Dockerfile.prod)に分けて、makefileは以下のようにしています。
Dockerfile.builderFROM node:12.2.0-alpine as builder WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json RUN npm install --silent RUN npm install react-scripts@3.0.1 -g --silent COPY . /app RUN npm run buildDockerfile.prodARG tag FROM local-builder:${tag} as builder FROM nginx:1.16.0-alpine ARG tag COPY --from=builder /app/build /usr/share/nginx/html RUN rm /etc/nginx/conf.d/default.conf COPY nginx/nginx.conf /etc/nginx/conf.d EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]makefile.PHONY: build all TAG := $(shell git rev-parse HEAD) all: build build: docker build . -f Dockerfile.builder -t local-builder:$(TAG) docker build . --build-arg tag=$(TAG) -f Dockerfile.prod -t built:$(TAG)このため、以下のようにデフォルトではコミットハッシュごとにコンテナを作成し、変数TAGが指定されていればそれを利用します。
$ make build # 最新のコミットハッシュをコンテナのタグに利用 $ make build TAG=hoge # hogeをコンテナのタグに指定実際のレポジトリでは、SPA用にnginx.confを置いたりしているので、必要な方はご確認ください。
1つのDockerfileで、同様のことができる方法をご存知の方いたら教えてください...
ビルドしたコンテナのテスト
では、さっそくビルドしたコンテナにアクセスしてみましょう。
$ make build TAG=test $ docker run -p 8000:80 built:test以上を実行し、 http://localhost:8000 を開くと、画面が表示されることが確認できました。
ついでに、nginxでSPA対応できているかを確認するため、 http://localhost:8000/spa/test が開けることを確認します。
3. trivyでスキャン
では、builder側とnginx側をスキャンしましょう。
trivyはインストールしてある前提ですが、makefileに以下のコードを追加します。buildと同じようにTAGを指定することで、指定したイメージをスキャンできるようになります。
ci-scan: trivy --exit-code 1 --severity HIGH,CRITICAL --quiet --auto-refresh local-builder:$(TAG) trivy --exit-code 1 --quiet --auto-refresh built:$(TAG)
--exit-code
オプションで脆弱性を検知した場合の終了コードを指定できるので、スキャンはしたいけど、CIは止めたくない人は--exit-code 0
(デフォルトなので省略可) を利用しましょう。4. CircleCIで実行
CircleCIの場合は以下のようにします。
trivyの脆弱性データの取得が重いので、脆弱性データなどはキャッシュに保存すると2回目以降のスキャンが高速になります。version: 2 jobs: build: docker: - image: docker:18.09-git steps: - checkout - setup_remote_docker - restore_cache: key: vulnerability-db - run: name: Build image command: | apk add --update --no-cache make curl make build TAG=${CIRCLE_SHA1} - run: name: Install latest trivy command: | VERSION=$( curl --silent "https://api.github.com/repos/knqyf263/trivy/releases/latest" | \ grep '"tag_name":' | \ sed -E 's/.*"v([^"]+)".*/\1/' ) wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz mv trivy /usr/local/bin - run: name: Scan local images with trivy command: | make ci-scan TAG=${CIRCLE_SHA1} - save_cache: key: vulnerability-db paths: - $HOME/.cache/trivy workflows: version: 2 release: jobs: - build正常終了の場合、以下のようになります。
builder側とnginx側のどちらにも脆弱性がなかったということがわかりました。脆弱性があった場合CIを止めたい
脆弱性が見つかった場合に、CIを止めたい場合は
--exit-code 1
オプションを利用します。このオプションにより、脆弱性が見つかった場合、異常終了(終了コードが1)になります。
また、severity
オプションを利用することで、検知したい脆弱性の危険度を指定できます。trivy --exit-code 1 --severity HIGH,CRITICAL \ --quiet --auto-refresh \ local-builder:$(TAG)この状態で脆弱性のあるパッケージを追加して結果を見ると、CIが失敗することが確認できました!
https://circleci.com/gh/tomoyamachi/trivy-with-react/8trivyを使った運用スタイル
さて、では脆弱性が見つかった場合、どのようにすればよいでしょうか?
こうしたほうがいい、というのはありますが、それぞれの運用方針にそって決めればいいと思います。
以下に、「とりあえず導入」「ちょっと気になる」「がんばる」のコースとそれぞれによさそうな運用方針をまとめました。
とりあえず導入 ちょっと気になる がんばる 検知時のCI とめない(--exit-code 0) とめる(--exit-code 1) とめる(--exit-code 1) 対応方針 対応せずSlackなどに通知 バージョンアップして、駄目なら無視する (.trivyignore) 脆弱性を調べて、必要なら対応 検知タイミング ビルド時 ビルド時 ビルド時&定期実行 全く興味なくても、導入するだけ導入したい、という人がいてもいいと思うので、できる範囲ですすめられるといいかなと思います。
以下は、「とりあえず導入」以上の興味がある人に対しての方法論になります。
1. 脆弱性を調べ、本当にCRITICALかをチェック
まず、その脆弱性が自分のアプリケーションにとって本当に問題になるのかを理解しましょう。
NVDなどで脆弱性の内容を調べる方法もあるし、まわりの詳しい人に聞くのもありです。
自分で調べる場合、脆弱性が危険かどうかの判断は このスライドの「トリアージ」以降の章が役立つかと思います。2. 重要ではない場合は、その脆弱性を無視する
プロジェクトに
.trivyignore
を作成し、無視したい脆弱性ID(以下、VULNERABILITY ID
)を記載すると、そのIDを無視してくれます。
さきほどの例でいうと、以下のように指定すれば、それ以降同じVULNERABILITY ID
は検知されなくなります。.trivyignoreNSWG-ECO-328 CVE-2019-5428 CVE-2019-11358この対応で先程停止していたワークフローがとおるようになったのがわかります。
https://circleci.com/gh/tomoyamachi/trivy-with-react/103. 重要な場合は...
重要な場合、バージョンアップをすれば良いと思うのですが、そうではない場合、自分で修正したコードを利用する方法などもあります。
アプリケーションパッケージの場合
たとえば、npmのパッケージであれば、 この記事の方法で でforkした自分のレポジトリを入れることが可能です。
ただし、その場合、バージョン情報が以下のようになり、そのパッケージの脆弱性チェックができなくなる点に留意ください。
"jquery": { "version": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git#438b1a3e8a52d3e4efd8aba45498477038849c97", "from": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git" },OSのパッケージの場合
検出されるパッケージバージョン自体は変わらないので、patchfileを当てるコマンドを実行するのと同時に、
.trivyignore
に対象のVULNERABILITY ID
を追加してください。まとめ
今回は、以下のことを書きました。
- フロントエンドをコンテナで運用する
- trivyで脆弱性対策できる
- 脆弱性のおおまかな運用方法
なお、コンテナになっていない場合は、Vulsという素晴らしい脆弱性スキャナがあるので、こちらをご利用ください。動作中のコンテナもスキャン可能です。
すこし導入に準備が必要ですが、あなたのサーバは本当に安全ですか?今もっともイケてる脆弱性検知ツールVulsを使ってみた を参考にお試しください。謝辞
記事を書く際に、 @knqyf263 さん、 @sadayuki-matsuno さん、 @codehex さんにご意見をいただきました。ありがとうございました!!
以上です。
- 投稿日:2019-05-21T12:31:57+09:00
フロントエンドでもtrivyを使って脆弱性対策したい!
伝えたいこと
- フロントエンドもコンテナ経由でアクセスさせよう
- コンテナならtrivyを使ってヤバそうな脆弱性を知ることができる
- trivyならCIに組み込むことも簡単
- multi-stage buildの場合は工夫が必要
- いまの状況でできる範囲の脆弱性対策からはじめよう
今回の記事でできること
CI上で、本番用のフロントエンドのコンテナをmulti-satge buildでつくり、
trivyを利用して脆弱性を検知するところまでを目指します。最後に、trivyを利用したレベル別の運用イメージも提案します。
導入
週末、trivyというコンテナ向けの脆弱性検知ツールが正式リリースされました。公開が5日目の5/21 12:00時点で 900star以上獲得しています。
trivyの詳細は原作者である @knqyf263 さんの「CIで使えるコンテナの脆弱性スキャナ」という記事を参照ください。
今回は、フロントエンドをメインに、今回は以下の内容を紹介します。
- コンテナを使ってフロントエンドの本番環境を構築する
- trivyを利用して、脆弱性が含まれていないかチェックする
- trivyを通じた脆弱性の運用方法を学ぶ
僕も、普段はフロントエンドも担当するエンジニアで、セキュリティに対して体系だって学んだことはありません。ただ、trivyのコミッタでもあり、脆弱性の運用も少し知っているので、フロントエンドでもDevSecOpsを定着させるきっかけになるといいなと考えています。
ソースコード
今回使うコードは、すべて以下のレポジトリにあります。
https://github.com/tomoyamachi/trivy-with-react
この記事では触れませんが、開発環境でもコンテナ経由でホットリロードするような仕組みになっています。
実践 : プロジェクト作成~trivyの導入
ここからはプロジェクトの作成と、CIにtrivyを組み込むまでの手順を書きます
1. プロジェクトの作成
今回はCreate React Appを利用して、Reactのプロジェクトをまず作成します。
そして、デフォルトではパッケージ管理ツールにyarnが利用されているので、今回はプロジェクトのパッケージ管理をnpmで実行します。$ npx create-react-app trivy-with-react $ cd trivy-with-react $ rm -fr node_modules yarn.lock $ npm install2. 本番用コンテナの作成
- 本番用のコードを生成
- nginxコンテナに生成したコードを移し、nginxを起動
という手順をとります。
通常であれば1ファイル
そして通常2つのステップは、multi-stage buildを利用して、1つのDockerfileでコンテナを作成します。
Dockerfile# build environment FROM node:12.2.0-alpine as build WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json RUN npm install --silent RUN npm install react-scripts@3.0.1 -g --silent COPY . /app RUN npm run build # production environment FROM nginx:1.16.0-alpine COPY --from=build /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]1ファイルでmulti-stage buildの問題点
multi-stage buildは1つのDockerfileで最終成果物のimageだけを出力してくれるので、通常はベストプラクティスです。
その場合、以下のように、builder側のイメージがIMAGE IDでしか特定できない形で作成されているのがわかります。$ docker images REPOSITORY TAG IMAGE ID ... trivy-with-react test 96c4fdfac287 # nginx側 <none> <none> 41fd97016d25 # builder側しかし、今回、脆弱性はbuilder側のコンテナにもあることに気がついた方もいるでしょう。package-lock.json もそうですし、一般的ではないですが、Static Linkでビルドする場合も考えられます。
trivyは
REPOSITORY:TAG
の形式でコンテナイメージを指定してスキャンするので、multi-stage buildにしてしまうと、builder側の脆弱性が放置されてしまいます。そのため、builder側と、nginx側のコンテナイメージを独立させて、参照可能にしたいと思います。具体的には、Dockerfileを分けることで、それぞれのイメージにタグを付けます。
また、2回コマンド実行するのも微妙なのでmakefileを作成します。※ もしも1つのDockerfileでmulti-stage buildをし、builder側のイメージ名を指定することができるのであれば、切実に情報がほしいです!
trivy用の構成
コードを見てもらえばわかりますが、Dockerfileをbuilder用(Dockerfile.builder)と、nginx用(Dockerfile.prod)に分けて、makefileは以下のようにしています。
Dockerfile.builderFROM node:12.2.0-alpine as builder WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json RUN npm install --silent RUN npm install react-scripts@3.0.1 -g --silent COPY . /app RUN npm run buildDockerfile.prodARG tag FROM local-builder:${tag} as builder FROM nginx:1.16.0-alpine ARG tag COPY --from=builder /app/build /usr/share/nginx/html RUN rm /etc/nginx/conf.d/default.conf COPY nginx/nginx.conf /etc/nginx/conf.d EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]makefile.PHONY: build all TAG := $(shell git rev-parse HEAD) all: build build: docker build . -f Dockerfile.builder -t local-builder:$(TAG) docker build . --build-arg tag=$(TAG) -f Dockerfile.prod -t built:$(TAG)このため、以下のようにデフォルトではコミットハッシュごとにコンテナを作成し、変数TAGが指定されていればそれを利用します。
$ make build # 最新のコミットハッシュをコンテナのタグに利用 $ make build TAG=hoge # hogeをコンテナのタグに指定実際のレポジトリでは、SPA用にnginx.confを置いたりしているので、必要な方はご確認ください。
1つのDockerfileで、同様のことができる方法をご存知の方いたら教えてください...
ビルドしたコンテナのテスト
では、さっそくビルドしたコンテナにアクセスしてみましょう。
$ make build TAG=test $ docker run -p 8000:80 built:test以上を実行し、 http://localhost:8000 を開くと、画面が表示されることが確認できました。
ついでに、nginxでSPA対応できているかを確認するため、 http://localhost:8000/spa/test が開けることを確認します。
3. trivyでスキャン
では、builder側とnginx側をスキャンしましょう。
trivyはインストールしてある前提ですが、makefileに以下のコードを追加します。buildと同じようにTAGを指定することで、指定したイメージをスキャンできるようになります。
ci-scan: trivy --exit-code 1 --severity HIGH,CRITICAL --quiet --auto-refresh local-builder:$(TAG) trivy --exit-code 1 --quiet --auto-refresh built:$(TAG)
--exit-code
オプションで脆弱性を検知した場合の終了コードを指定できるので、スキャンはしたいけど、CIは止めたくない人は--exit-code 0
(デフォルトなので省略可) を利用しましょう。4. CircleCIで実行
CircleCIの場合は以下のようにします。
trivyの脆弱性データの取得が重いので、脆弱性データなどはキャッシュに保存すると2回目以降のスキャンが高速になります。version: 2 jobs: build: docker: - image: docker:18.09-git steps: - checkout - setup_remote_docker - restore_cache: key: vulnerability-db - run: name: Build image command: | apk add --update --no-cache make curl make build TAG=${CIRCLE_SHA1} - run: name: Install latest trivy command: | VERSION=$( curl -I https://github.com/knqyf263/trivy/releases/latest | \ grep -o '/tag/v[0-9]\+.[0-9]\+\.[0-9]\+' | \ sed -E 's:/tag/v([0-9\.]+):\1:' ) wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz mv trivy /usr/local/bin - run: name: Scan local images with trivy command: | make ci-scan TAG=${CIRCLE_SHA1} - save_cache: key: vulnerability-db paths: - $HOME/.cache/trivy workflows: version: 2 release: jobs: - build正常終了の場合、以下のようになります。
builder側とnginx側のどちらにも脆弱性がなかったということがわかりました。脆弱性があった場合CIを止めたい
脆弱性が見つかった場合に、CIを止めたい場合は
--exit-code 1
オプションを利用します。このオプションにより、脆弱性が見つかった場合、異常終了(終了コードが1)になります。
また、severity
オプションを利用することで、検知したい脆弱性の危険度を指定できます。trivy --exit-code 1 --severity HIGH,CRITICAL \ --quiet --auto-refresh \ local-builder:$(TAG)この状態で脆弱性のあるパッケージを追加して結果を見ると、CIが失敗することが確認できました!
https://circleci.com/gh/tomoyamachi/trivy-with-react/8trivyを使った運用スタイル
さて、では脆弱性が見つかった場合、どのようにすればよいでしょうか?
こうしたほうがいい、というのはありますが、それぞれの運用方針にそって決めればいいと思います。
以下に、「とりあえず導入」「ちょっと気になる」「がんばる」のコースとそれぞれによさそうな運用方針をまとめました。
とりあえず導入 ちょっと気になる がんばる 検知時のCI とめない(--exit-code 0) とめる(--exit-code 1) とめる(--exit-code 1) 対応方針 対応せずSlackなどに通知 バージョンアップして、駄目なら無視する (.trivyignore) 脆弱性を調べて、必要なら対応 検知タイミング ビルド時 ビルド時 ビルド時&定期実行 全く興味なくても、導入するだけ導入したい、という人がいてもいいと思うので、できる範囲ですすめられるといいかなと思います。
以下は、「とりあえず導入」以上の興味がある人に対しての方法論になります。
1. 脆弱性を調べ、本当にCRITICALかをチェック
まず、その脆弱性が自分のアプリケーションにとって本当に問題になるのかを理解しましょう。
NVDなどで脆弱性の内容を調べる方法もあるし、まわりの詳しい人に聞くのもありです。
自分で調べる場合、脆弱性が危険かどうかの判断は このスライドの「トリアージ」以降の章が役立つかと思います。2. 重要ではない場合は、その脆弱性を無視する
プロジェクトに
.trivyignore
を作成し、無視したい脆弱性ID(以下、VULNERABILITY ID
)を記載すると、そのIDを無視してくれます。
さきほどの例でいうと、以下のように指定すれば、それ以降同じVULNERABILITY ID
は検知されなくなります。.trivyignoreNSWG-ECO-328 CVE-2019-5428 CVE-2019-11358この対応で先程停止していたワークフローがとおるようになったのがわかります。
https://circleci.com/gh/tomoyamachi/trivy-with-react/103. 重要な場合は...
重要な場合、バージョンアップをすれば良いと思うのですが、そうではない場合、自分で修正したコードを利用する方法などもあります。
アプリケーションパッケージの場合
たとえば、npmのパッケージであれば、 この記事の方法で でforkした自分のレポジトリを入れることが可能です。
ただし、その場合、バージョン情報が以下のようになり、そのパッケージの脆弱性チェックができなくなる点に留意ください。
"jquery": { "version": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git#438b1a3e8a52d3e4efd8aba45498477038849c97", "from": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git" },OSのパッケージの場合
検出されるパッケージバージョン自体は変わらないので、patchfileを当てるコマンドを実行するのと同時に、
.trivyignore
に対象のVULNERABILITY ID
を追加してください。まとめ
今回は、以下のことを書きました。
- フロントエンドをコンテナで運用する
- trivyで脆弱性対策できる
- 脆弱性のおおまかな運用方法
ところで、 記事を書いているときにnpmを使うのであれば、
npm audit
で簡単に脆弱性を取得できることを知りました… が、環境側の脆弱性もチェックできるので、コンテナ化しているのであれば、trivyを使ってみてください。
S3などにホストしている場合は、npm audit
だけでよさそうです。なお、コンテナになっていない場合で実行環境をチェックしたい場合、Vulsという素晴らしい脆弱性スキャナがあるので、こちらをご利用ください。動作中のコンテナもスキャン可能です。
すこし導入に準備が必要ですが、あなたのサーバは本当に安全ですか?今もっともイケてる脆弱性検知ツールVulsを使ってみた を参考にお試しください。謝辞
記事を書く際に、 @knqyf263 さん、 @sadayuki-matsuno さん、 @codehex さんにご意見をいただきました。ありがとうございました!!
以上です。
- 投稿日:2019-05-21T12:31:57+09:00
フロントエンドでもTrivyを使って脆弱性対策したい!
伝えたいこと
- フロントエンドもコンテナ経由でアクセスさせよう
- コンテナならTrivyを使ってヤバそうな脆弱性を知ることができる
- TrivyならCIに組み込むことも簡単
- multi-stage buildの場合は工夫が必要
- いまの状況でできる範囲の脆弱性対策からはじめよう
今回の記事でできること
CI上で、本番用のフロントエンドのコンテナをmulti-satge buildでつくり、
Trivyを利用して脆弱性を検知するところまでを目指します。最後に、Trivyを利用したレベル別の運用イメージも提案します。
導入
週末、Trivyというコンテナ向けの脆弱性検知ツールが正式リリースされました。公開が5日目の5/21 12:00時点で 900star以上獲得しています。
Trivyの詳細は原作者である @knqyf263 さんの「CIで使えるコンテナの脆弱性スキャナ」という記事を参照ください。
今回は、フロントエンドをメインに、今回は以下の内容を紹介します。
- コンテナを使ってフロントエンドの本番環境を構築する
- Trivyを利用して、脆弱性が含まれていないかチェックする
- Trivyを通じた脆弱性の運用方法を学ぶ
僕も、普段はフロントエンドも担当するエンジニアで、セキュリティに対して体系だって学んだことはありません。ただ、Trivyのコミッタでもあり、脆弱性の運用も少し知っているので、フロントエンドでもDevSecOpsを定着させるきっかけになるといいなと考えています。
ソースコード
今回使うコードは、すべて以下のレポジトリにあります。
https://github.com/tomoyamachi/trivy-with-react
この記事では触れませんが、開発環境でもコンテナ経由でホットリロードするような仕組みになっています。
実践 : プロジェクト作成~Trivyの導入
ここからはプロジェクトの作成と、CIにTrivyを組み込むまでの手順を書きます
1. プロジェクトの作成
今回はCreate React Appを利用して、Reactのプロジェクトをまず作成します。
そして、デフォルトではパッケージ管理ツールにyarnが利用されているので、今回はプロジェクトのパッケージ管理をnpmで実行します。$ npx create-react-app trivy-with-react $ cd trivy-with-react $ rm -fr node_modules yarn.lock $ npm install2. 本番用コンテナの作成
- 本番用のコードを生成
- nginxコンテナに生成したコードを移し、nginxを起動
という手順をとります。
通常であれば1ファイル
そして通常2つのステップは、multi-stage buildを利用して、1つのDockerfileでコンテナを作成します。
Dockerfile# build environment FROM node:12.2.0-alpine as build WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json RUN npm install --silent RUN npm install react-scripts@3.0.1 -g --silent COPY . /app RUN npm run build # production environment FROM nginx:1.16.0-alpine COPY --from=build /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]1ファイルでmulti-stage buildの問題点
multi-stage buildは1つのDockerfileで最終成果物のimageだけを出力してくれるので、通常はベストプラクティスです。
その場合、以下のように、builder側のイメージがIMAGE IDでしか特定できない形で作成されているのがわかります。$ docker images REPOSITORY TAG IMAGE ID ... trivy-with-react test 96c4fdfac287 # nginx側 <none> <none> 41fd97016d25 # builder側しかし、今回、脆弱性はbuilder側のコンテナにもあることに気がついた方もいるでしょう。package-lock.json もそうですし、一般的ではないですが、Static Linkでビルドする場合も考えられます。
Trivyは
REPOSITORY:TAG
の形式でコンテナイメージを指定してスキャンするので、multi-stage buildにしてしまうと、builder側の脆弱性が放置されてしまいます。そのため、builder側と、nginx側のコンテナイメージを独立させて、参照可能にしたいと思います。具体的には、Dockerfileを分けることで、それぞれのイメージにタグを付けます。
また、2回コマンド実行するのも微妙なのでmakefileを作成します。※ もしも1つのDockerfileでmulti-stage buildをし、builder側のイメージ名を指定することができるのであれば、切実に情報がほしいです!
Trivy用の構成
コードを見てもらえばわかりますが、Dockerfileをbuilder用(Dockerfile.builder)と、nginx用(Dockerfile.prod)に分けて、makefileは以下のようにしています。
Dockerfile.builderFROM node:12.2.0-alpine as builder WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json RUN npm install --silent RUN npm install react-scripts@3.0.1 -g --silent COPY . /app RUN npm run buildDockerfile.prodARG tag FROM local-builder:${tag} as builder FROM nginx:1.16.0-alpine ARG tag COPY --from=builder /app/build /usr/share/nginx/html RUN rm /etc/nginx/conf.d/default.conf COPY nginx/nginx.conf /etc/nginx/conf.d EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]makefile.PHONY: build all TAG := $(shell git rev-parse HEAD) all: build build: docker build . -f Dockerfile.builder -t local-builder:$(TAG) docker build . --build-arg tag=$(TAG) -f Dockerfile.prod -t built:$(TAG)このため、以下のようにデフォルトではコミットハッシュごとにコンテナを作成し、変数TAGが指定されていればそれを利用します。
$ make build # 最新のコミットハッシュをコンテナのタグに利用 $ make build TAG=hoge # hogeをコンテナのタグに指定実際のレポジトリでは、SPA用にnginx.confを置いたりしているので、必要な方はご確認ください。
1つのDockerfileで、同様のことができる方法をご存知の方いたら教えてください...
ビルドしたコンテナのテスト
では、さっそくビルドしたコンテナにアクセスしてみましょう。
$ make build TAG=test $ docker run -p 8000:80 built:test以上を実行し、 http://localhost:8000 を開くと、画面が表示されることが確認できました。
ついでに、nginxでSPA対応できているかを確認するため、 http://localhost:8000/spa/test が開けることを確認します。
3. Trivyでスキャン
では、builder側とnginx側をスキャンしましょう。
Trivyはインストールしてある前提ですが、makefileに以下のコードを追加します。buildと同じようにTAGを指定することで、指定したイメージをスキャンできるようになります。
ci-scan: trivy --exit-code 1 --severity HIGH,CRITICAL --quiet --auto-refresh local-builder:$(TAG) trivy --exit-code 1 --quiet --auto-refresh built:$(TAG)
--exit-code
オプションで脆弱性を検知した場合の終了コードを指定できるので、スキャンはしたいけど、CIは止めたくない人は--exit-code 0
(デフォルトなので省略可) を利用しましょう。4. CircleCIで実行
CircleCIの場合は以下のようにします。
Trivyの脆弱性データの取得が重いので、脆弱性データなどはキャッシュに保存すると2回目以降のスキャンが高速になります。version: 2 jobs: build: docker: - image: docker:18.09-git steps: - checkout - setup_remote_docker - restore_cache: key: vulnerability-db - run: name: Build image command: | apk add --update --no-cache make curl make build TAG=${CIRCLE_SHA1} - run: name: Install latest trivy command: | VERSION=$( curl -I https://github.com/knqyf263/trivy/releases/latest | \ grep -o '/tag/v[0-9]\+.[0-9]\+\.[0-9]\+' | \ sed -E 's:/tag/v([0-9\.]+):\1:' ) wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz mv trivy /usr/local/bin - run: name: Scan local images with trivy command: | make ci-scan TAG=${CIRCLE_SHA1} - save_cache: key: vulnerability-db paths: - $HOME/.cache/trivy workflows: version: 2 release: jobs: - build正常終了の場合、以下のようになります。
builder側とnginx側のどちらにも脆弱性がなかったということがわかりました。脆弱性があった場合CIを止めたい
脆弱性が見つかった場合に、CIを止めたい場合は
--exit-code 1
オプションを利用します。このオプションにより、脆弱性が見つかった場合、異常終了(終了コードが1)になります。
また、severity
オプションを利用することで、検知したい脆弱性の危険度を指定できます。trivy --exit-code 1 --severity HIGH,CRITICAL \ --quiet --auto-refresh \ local-builder:$(TAG)この状態で脆弱性のあるパッケージを追加して結果を見ると、CIが失敗することが確認できました!
https://circleci.com/gh/tomoyamachi/trivy-with-react/8Trivyを使った運用スタイル
さて、では脆弱性が見つかった場合、どのようにすればよいでしょうか?
こうしたほうがいい、というのはありますが、それぞれの運用方針にそって決めればいいと思います。
以下に、「とりあえず導入」「ちょっと気になる」「がんばる」のコースとそれぞれによさそうな運用方針をまとめました。
とりあえず導入 ちょっと気になる がんばる 検知時のCI とめない(--exit-code 0) とめる(--exit-code 1) とめる(--exit-code 1) 対応方針 対応せずSlackなどに通知 バージョンアップして、駄目なら無視する (.trivyignore) 脆弱性を調べて、必要なら対応 検知タイミング ビルド時 ビルド時 ビルド時&定期実行 全く興味なくても、導入するだけ導入したい、という人がいてもいいと思うので、できる範囲ですすめられるといいかなと思います。
以下は、「とりあえず導入」以上の興味がある人に対しての方法論になります。
1. 脆弱性を調べ、本当にCRITICALかをチェック
まず、その脆弱性が自分のアプリケーションにとって本当に問題になるのかを理解しましょう。
NVDなどで脆弱性の内容を調べる方法もあるし、まわりの詳しい人に聞くのもありです。
自分で調べる場合、脆弱性が危険かどうかの判断は このスライドの「トリアージ」以降の章が役立つかと思います。2. 重要ではない場合は、その脆弱性を無視する
プロジェクトに
.trivyignore
を作成し、無視したい脆弱性ID(以下、VULNERABILITY ID
)を記載すると、そのIDを無視してくれます。
さきほどの例でいうと、以下のように指定すれば、それ以降同じVULNERABILITY ID
は検知されなくなります。.trivyignoreNSWG-ECO-328 CVE-2019-5428 CVE-2019-11358この対応で先程停止していたワークフローがとおるようになったのがわかります。
https://circleci.com/gh/tomoyamachi/trivy-with-react/103. 重要な場合は...
重要な場合、バージョンアップをすれば良いと思うのですが、そうではない場合、自分で修正したコードを利用する方法などもあります。
アプリケーションパッケージの場合
たとえば、npmのパッケージであれば、 この記事の方法で でforkした自分のレポジトリを入れることが可能です。
ただし、その場合、バージョン情報が以下のようになり、そのパッケージの脆弱性チェックができなくなる点に留意ください。
"jquery": { "version": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git#438b1a3e8a52d3e4efd8aba45498477038849c97", "from": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git" },OSのパッケージの場合
検出されるパッケージバージョン自体は変わらないので、patchfileを当てるコマンドを実行するのと同時に、
.trivyignore
に対象のVULNERABILITY ID
を追加してください。まとめ
今回は、以下のことを書きました。
- フロントエンドをコンテナで運用する
- Trivyで脆弱性対策できる
- 脆弱性のおおまかな運用方法
ところで、 記事を書いているときにnpmを使うのであれば、
npm audit
で簡単に脆弱性を取得できることを知りました… が、環境側の脆弱性もチェックできるので、コンテナ化しているのであれば、Trivyを使ってみてください。 Trivyはyarnも対応しているので、yarnの人にはTrivyがおすすめです。
S3などにホストしている場合は、npm audit
だけでよさそうです。なお、コンテナになっていない場合で実行環境をチェックしたい場合、Vulsという素晴らしい脆弱性スキャナがあるので、こちらをご利用ください。動作中のコンテナもスキャン可能です。
すこし導入に準備が必要ですが、あなたのサーバは本当に安全ですか?今もっともイケてる脆弱性検知ツールVulsを使ってみた を参考にお試しください。謝辞
記事を書く際に、 @knqyf263 さん、 @sadayuki-matsuno さん、 @codehex さんにご意見をいただきました。ありがとうございました!!
以上です。