DIGGLE開発者ブログ

予実管理クラウドDIGGLEのプロダクトチームが、技術や製品開発について発信します

DIGGLEは、RubyKaigi 2024にシルバースポンサーとして協賛します!

※ 上記画像中央のロゴはさわらつきさんの著作物です
※ Rubyのロゴはまつもとゆきひろ氏の著作物です

DIGGLEのCTO水上です。

DIGGLE社は前年に引き続き、RubyKaigi 2024のシルバースポンサーとして協賛いたします。微力ながらRubyコミュニティに貢献ができればと思っております!

rubykaigi.org

DIGGLEとRuby

当社サービスは、2016年のローンチ以来、RubyとRuby on Railsをバックエンドの言語、Webフレームワークとして採用しております。私たちが事業として成長を続けられているのは、RubyとRubyコミュニティのおかげです。

特に、サービス初期の立ち上げと、頻繁かつ大胆な変更の繰り返しを乗り越えた上で、大きな負債なく継続できていることは、Rubyという言語の持つ柔軟性と設計思想、充実した周辺ライブラリがあってのことだと考えています。

DIGGLEで活躍するgemの一部

gem 'active_hash'
gem 'activerecord-import'
gem 'acts_as_list'
gem 'clockwork'
gem 'devise'
gem 'factory_bot_rails'
gem 'haml'
gem 'kaminari'
gem 'mini_racer'
gem 'mustache'
gem 'oj'
gem 'paranoia'
gem 'pundit'
gem 'rails'
gem 'ransack'
gem 'ros-apartment'
gem 'rubocop'
gem 'sidekiq'

今こそRuby / Railsを学ぶべき理由

もっとRuby界隈を盛り上げていきたいので、これを機に、特に初学者であったり、サービスを立ち上げる検討をしている方向けに、私の個人的見解でRubyやRailsを選ぶべき理由をつらつらと書いてみます。よければ選定の参考にしてください。

1. MVC Architecture

Ruby on Railsは、いわゆるMVCアーキテクチャです。

MVCは銀の弾丸ではありません。MVCを使っていると、だいたい私の体感的に、8割は非常に簡単に作れて、残り2割はレールから外れる必要があります。私たちが作るサービスの機能は、複雑度の観点でパレートの法則のような形をしているのだと考えられます。大事なことは、8割でしっかり楽して、2割でしっかり考えぬくことです。初めてサービスを作る方にとって、MVCはきっと丁度よいフレームワークです。

2. HTMLを基本として、適応的にJavaScriptを使える

Ruby on Railsは、シンプルなHTMLベースのWebサーバが基本です。

きっと多くの方が、イケてるUIを目指したいので、フロントをリッチにしたいと思うでしょう。しかし、良いフロントエンドを作るには、JavaScriptの前に、HTMLとCSSについてよく知ることが肝要です。特に、CSSはどんどん進化し多くのことはCSSでカバーできるようになっています。JavaScriptより描画パフォーマンスが良い場合がほとんどです。

Railsは、フロントエンドの節度を守るレールを用意しつつも、時々JavaScriptを使いたくなりそうな課題に対して、いくつか丁度よい道具を提供しています。最近では、それらがすべてHotwireという形で集約されました。もちろん、必要に応じて、Reactなども導入できます。

これらをRailsのレールの上で習得するメリットは、うまく手を抜く技術があがることです。スピード感のあるデリバリーが求められる現代において、頑張る必要がない所で手を抜くのは悪いことではなく、むしろビジネスとして適切な選択ができる能力として重宝されます。

3. 圧倒的なコミュニティとその「近さ」

Rubyという国産言語で、ここまで世界的に使われており、毎年カンファレンスで盛り上がり、多くの人に愛され続け、メンテナンスされている言語はRubyしかないでしょう。

RubyKaigiという一大イベントですぐに最高のハッカー達と、日本語で(あるいは英語で)会話だってできます。エンジニアとしての最高の環境がとても近くにあります。

diggle.engineer

あとがき

DIGGLEとしては、Ruby界隈に引き続き貢献していき、Rubyとともに成長していければと感じております!一企業として一つの言語に向き合いつづけることこそが、Rubyと自社の中長期的な成長に繋がると考えています。

RubyKaigi当日は弊社メンバーも参加しますので、ぜひお声がけください!

水上

herp.careers

DIGGLE React SPA開発におけるアーキテクチャの勘どころ

はじめに

初めまして。 2024年2月にDIGGLEにエンジニアとして入社したfujitaです。

私はこれまで、バックエンドの開発が中心で、フロントエンドはVanilla JS、JQueryでの簡単なDOM操作やAjax通信の実装を行った程度の経験しかありませんでした。DIGGLEでは基本的にエンジニアはバックエンド・フロントエンドの両方の開発に携わるため、フロントエンドの開発に取り組むにあたって、いくつかの困難に直面しました。具体的には、DIGGLEではReactを用いたシングルページアプリケーション(以下、SPA)として構築されており、これまで経験したことがないほどにフロントエンドのコードベースの規模が大きく複雑なため、以下のような難しさがありました。

  • 機能を追加・改修するために、どのファイルのどの部分を修正すべきかがわからない
  • コードを修正した際に、その影響範囲を把握することが難しい

しかし、フロントエンドにおけるアプリケーションアーキテクチャ(以下、アーキテクチャ)、簡単にいえば各ディレクトリ、ファイル、コンポーネントの役割・責務を理解していくにしたがって、これらの困難は徐々に解消され、開発スピードも向上してきました。

本記事では、私がReactを用いた大規模なSPA開発に初めて取り組む中で得た知見や、DIGGLEのフロントエンドアーキテクチャがどのように設計されているかについて共有したいと思います。

Reactを用いたSPAのアーキテクチャ

SPAのアーキテクチャ

基本的なSPAのアーキテクチャとしては、バックエンドでも用いられるMVCや、それが発展してできたMVVMがあり、これらは総称してMVW(Model-View-Whatever)と呼ばれます。これらはいずれもアプリケーションのデータやロジックをもつModelと、UIでありModelの視覚的表現であるViewを要素としてもちます。そして、第3の要素(C: ControllerやVM: ViewModel)によってViewとModelを適切に分離しつつ連携できるようにします。この分離によって、コードの可読性、保守性、テスト容易性などが向上します。

React

ReactはUI構築のためのJavaScriptライブラリです。大雑把にいうとReactコンポーネントはMVW のVを担当し、アーキテクチャとは直接関係しません。宣言的にコンポーネントを作成することができ、状態の変化に応じたUIの更新はReactが裏側でやってくれますが、どのようにアプリケーションのデータを取得・管理し、Reactコンポーネントへ渡すかなどは別に考えなければなりません。(公式では、Next.js や Remix などのフレームワークの使用が推奨されています)

さらに、先ほどReactはMVWのViewに相当すると述べましたが、Reactコンポーネントは自身の状態を管理・更新したり、副作用を扱ったりでき、View以上のものといえます。そのため、一般にReactではMVWとは異なるアーキテクチャが採用されます。代表的なアーキテクチャとしてFluxなどがありますが、開発チームの事情に応じて様々な選択肢があるようです。次項で、DIGGLEのアーキテクチャの一部をご紹介します。

DIGGLEのフロントエンドアーキテクチャ

DIGGLEでは、UIとロジックを分離するために Container/Presentationalパターンが、関心の異なるロジックを分離するためにHOCパターンが用いられています。

なお、状態管理については記事がありますので、ぜひご覧ください。

diggle.engineer

Container/PresentationalパターンによるUIと状態・ロジックの分離

各ページのコンポーネントを実装する際、UIに関心をもつ Presentational Componentと、状態やロジックに関心を持つContainer Componentに分割することで、UIと状態・ロジックを分離します。具体的には以下のようになります。

  1. Container Componentは状態管理APIからグローバルな状態(ログインユーザーの状態など)を取得します。
  2. Container ComponentはバックエンドAPIをコールし、リソースのデータを取得します。レスポンスからViewModelを作成し、それを状態として保持します。 ViewModelはMVVMにおけるものとは若干異なり、APIで取得したデータとそれに関連するメソッドを含んだオブジェクトです。

  3. Presentational Componentは表示に必要なデータやロジックをContainer Componentからpropsとして受け取り、UIを構築します。

以下の記事を参考にさせていただきました。

zenn.dev

コードの例を見てみましょう。以下は、レポートの作成年月やタイトルなどを表示するコードです。

ViewModel

ReportsView は、レポートに関するデータとメソッドをもちます。

ReportsView.tsx

import { Report } from 'Models';
import { Record, OrderedMap } from 'immutable';

const ReportsViewRecord = Record<{
  reports: OrderedMap<any, Report> | null;
  // その他のデータ
  ...
}>({
  reports: null,
  ...
});

export class ReportsView extends ReportsViewRecord{
  // jsonデータからViewModelを組み立てる
  load(){ ... }
      
  // その他のメソッド
  ...
}

Container Component

APIをコールしてレポートのデータを取得後、ViewModelを作成し、状態として保持します。

ReportsContainer.jsx

import { ReportsView } from 'ViewModels';
import { ReportsPresentational } from './ReportsPresentational';

const ReportsContainer = () => {
  const [model, setModel] = useState(new ReportsView());
  // その他の状態
  ...
    
  useEffect(() => {
    fetch('https://xxxx/reports')
      .then((res) => res.json())
      .then(data => setReports((model) => model.load(data));
  }, []);

  return (
    <ReportsPresentational
      model={model}
      ...
    />
  );
};

Presentational Component

レポートの作成年月、タイトルを表示します。

ReportsPresentational.jsx

export const ReportsPresentational = ({ model: { reports, ... }}) => {
  return (
    <ul>
      {reports.map(({ id, title, year, month, ... }) => (
        <li key={id}>
          <p>{year}{month}月:{title}</p>
          // その他のレポート情報を表示する
          ...
        </li>
      ))}
    </ul>
  );
};

メリット

前述したアーキテクチャを取り入れることのメリットであるコードの可読性、保守性、テスト容易性などの向上が得られます。特に主要なデータやロジックが、他のコンポーネントと分離された形でViewModelに含まれるため、ViewModelに対するテストを重点的に書くことで、効率的にテストの恩恵を受けられます。

HOCパターンによる関心の異なるデータやロジックの分離

例えば、あるコンポーネントをモーダル表示したい場合、モーダル表示に関連する状態やロジックを高階コンポーネント (HOC、Higher Order Component)に分離します。高階コンポーネントとは、あるコンポーネントを受け取って新規のコンポーネントを返すような関数です。

例を見ましょう。以下のModalizeはモーダル表示に関連するデータや状態をもつコンポーネントです。

import { Modal } from '...';

export const Modalize = (Component) => (props) => {
  const [isOpen, setIsOpen] = useState(false);
  const open = ... // モーダルを開くメソッド
  ...
  return(
    <Modal
      open={open}
      ...
    >
      <Modal.Header>
        ...
      </Modal.Header>
      <Modal.Content>
        <Component {...props} />
      </Modal.Content>
    <Modal />
  );
};

コンポーネント MyComponent をモーダルで表示したい場合は、以下のようにします。

const EnhancedMyComponent = Modalize(MyComponent);

メリット

モーダル表示に関連する状態やロジックが分離されているため、これを様々なコンポーネントをモーダル表示するために再利用することができます。また、複数の高階コンポーネントを自由に組み合わせることもできます。以下は、モーダル表示のための高階コンポーネントである Modalize と非同期処理のための高階コンポーネントである WithAsync の機能を MyComponent に組み込むコードです。

const EnhancedMyComponent = WithAsync(Modalize(MyComponent));

ただし、高階コンポーネントの数が増えるとコードが読みにくくなります。

const EnhancedMyComponent = HOCn(...(HOC2(HOC1(MyComponent)))...);

デコレータを利用すると以下のように書けます。

@HOCn
...
@HOC2
@HOC1
MyComponent

デコレータの詳細は以下をご覧ください。

github.com

www.typescriptlang.org

今後の展望

ここまで紹介したコードを分離するためのパターンは、コンポーネントを利用したものでした。そのため、不必要な再レンダリングを引き起こす可能性があったり、テストが難しかったりする場合があります。これらの問題を解決し、より簡単かつシンプルにコードの分離を行うために、今後、Hooksの活用を積極的に進めていきたいと考えています。

より根本的な目標は、ReactのHooksを活用した宣言的アプローチへの進化に追随することで、Reactの利点を最大限に引き出し、プロダクトの価値を高めることです。

We're hiring!

DIGGLE では共にプロダクトを開発してくれるエンジニアを大募集中です。

少しでも興味があれば、ぜひ下記採用サイトからエントリーください。
カジュアル面談も実施しているので、気軽なお気持ちでご応募いただければと思います!

herp.careers

YAPC::Hiroshima 2024に行ってきたよ

ご無沙汰しています。DIGGLEのhirataです。 今回初めてYAPCに行ってきたのでその時の事を書いてみようと思います。

YAPCとは「Yet Another Perl Conferenceの略で、Perlを軸としたITに関わる全ての人のためのカンファレンスです」(サイトからの引用)でPerlを軸にはしますが、Perlにとどまらず技術的な話題を取り扱うカンファレンスとのことです。

関係ないですがタイトル画像は広島のアーケード街にあったゲームセンターの入口です。手をパチパチ叩くロボットが面白かったので撮ってしまいました。

事の始まり

YAPC::Hiroshimaが開催される数ヶ月前の事。いつものようにデイリーの朝会に参加していると、「YAPCに行きたい人居ないですか?」というCTOのお言葉。 ありがたいことに希望があれば経費で行かせてくれるという事でしたが、そのときはYAPCがPerlに関連するイベントということしか知らなかったために全く関心がありませんでした。

ですが、よく話を聞くとPerlに限ったイベントではなくプログラミングやソフトウェア技術全般のイベントとのこと。後でスケジュールを見てみると面白そうな講演もいくつかあり、折角の機会なので参加させていただくことにしました。やった!会社のお金で旅行だ!

前夜祭

前日2/9の午後から新幹線で東京から広島まで移動して、どうせならということで前夜祭にも参加しました。

受付を済ませて着席し周りを見渡すと、いかにもソフトウェアエンジニアの集まりという雰囲気で、これは期待が持てる感じです。

前夜祭が始まると、まず開発者さん自身によるHonoというJavascriptのフレームワークの熱い紹介がありました。私があまり強くない分野に関連する話題が多かったため内容の理解が難しかったのですが、とにかく開発のスピード感と熱量が凄そうなフレームワークでした。

その後も登壇者の方が各々のテーマでお話いただいていました。会場ではお酒が振る舞われており、登壇者の方のお話を聞きながらお酒を飲むスタイルです。観客の皆さんはお酒が入っているため好きにガヤガヤ話していて、しかも登壇者もそれを気にしないで話し続けるというカオスな状態で盛り上がっており、前夜祭の雰囲気を十二分に味わうことができました。

本編

2/10は朝から参加です。受付を済ませた後、まだ時間があったので協賛企業のブースを廻りました。ここでDeNAさんが朝食用におにぎりを配っていて美味しそうでしたが、ホテルの朝食でたっぷり食べてしまっていたため、お腹いっぱいで食べることができませんでした。

一通りブースを回り、早めに会場に向かうと既に何やら始まっている雰囲気。うっかりオープニングの事を忘れてしまっていた…。折角だから見ておけばよかったです。それはともかく、席に座ると昨日と打って変わってサラリーマンっぽい雰囲気の人が増えてました。

午前の講演で面白いと思ったのは、「VISAカードの裏側と “手が掛かる” 決済システムの育て方」と「My Favorite Protocol: Idempotency-Key Header」の2つでした。 特にVISAカードの方は普段触れることのないカード決済の仕組みを知ることができ、とても興味深かったです。やはり昔から続くサービスはいろいろ泥臭い部分があり、歴史の積み重ねを感じました。ネットもない時代からあるのでどうしても過去の経緯を引きずらざるを得ないのだと思います。こちらはベストLTに選ばれていました。 またもう一つの「My Favorite Protocol」の方はWebでAPIを開発する上でよく遭遇する問題である、「確実に一回だけリクエストを送りたい」問題に対する解の一つだと思いました。

お昼は弁当が配られたのですが、どれも美味しそうでした。私が食べたのは穴子弁当。写真撮ったけど画質が宜しく無いですね。美味しくいただきました。

昼のおべんとう

午後の部では「理解容易性と変更容易性を支える自動テスト戦略」が面白かったです。もしかしたらテストに詳しい方だと当然の内容なのかもしれませんが、テストをネットワークアクセスやDBアクセス等の有無によってlarge, medium, smallに分けてピラミッドにするという話はなるほどと思いました。業務でテストを書く上で単体テスト、統合テスト等は書き分けて意味があるのかと感じることもありましたが、この分類であればsmallは基本的にパッと終わるテスト、largeは時間の掛かるテストになるので、普段はsmallだけテストしてぱぱっと確認し、PRを出す前にlargeも含めたテストを実施してより網羅的に確認といった事ができそうです。登壇者の方が翻訳に参加された「テスト駆動開発」という本も気になって買ってしまいました。

そして最後にあの伝説のとほほさん(敢えてひらがな)の講演がありました。登壇する前に座ってた席が実は近くだったのでびっくりしました。まさかインターネットの黎明期にPerlだったりHTMLでお世話になった伝説の人にお目にかかることができるなんて…。講演の内容もとても面白かったです。

その後は懇親会まで参加しましたが、体調があんまり良くなかったのもあり、最初に近くにいた人としか話せなかったのが心残りです。次に参加する機会があれば体調を整えて交流を楽しめるようにしたいと思いました。

懇親会場

その後

懇親会で人から聞いて知ったのですが、次の日にもいろいろ関連イベントがありました。しかし既に枠が一杯で参加できず。 なので翌日は広島城に行ったり、庭園を散策したり、広島のアーケード街を散歩したりで1日中観光してから帰りました。知らない街を散策するのはとても楽しいです。

途中で食べた備後府中焼きは広島風お好み焼きのバリエーションらしいのですがとても美味しかったです。語彙が少なくて具体的にどこがとは言えないのですが、個人的には普通のお好み焼きよりも好みの味でした。

備後府中焼き

タイミング悪く私自身の体調が良くなかったのが残念ですが、イベントは盛り上がっておりとても楽しめました。オンラインでも見ることはできますが、生の会場の雰囲気はまた違ったものがあってYAPCに参加できて良かったと思いました。

おしまい。

We're hiring!

DIGGLE では共にプロダクトを開発してくれるエンジニアを大募集中です。このように気軽に技術者の集いに参加させてもらえます。

少しでも興味があれば、ぜひ下記採用サイトからエントリーください。
カジュアル面談も実施しているので、気軽なお気持ちでご応募いただければと思います!

herp.careers

パフォーマンス分析基盤の構築。S3やAthenaを活用して柔軟性の高いデータの取り扱いを実現した話

はじめに

DIGGLEではすでにDatadogを導入してパフォーマンス指標のトレースデータ*1は取れていますが、以下の課題がありました。

  • ユースケースを分けてパフォーマンス指標を取得したい
  • DIGGLEがターゲットとしたい企業やペルソナユーザーのデータのみを取り出すのが面倒
  • 開発時点でCIが動いた時に、分析結果をプルリクのコメントに出力したい
  • Datadogに保存されているパフォーマンス指標のトレースデータには有効期限がある

上記に対応する為にまずは調査を行いました。

Datadog API Clientによるデータの取得

Datadogに保存されているデータをAPI経由で取得することによって、必要なデータが得られないかを調べました。

結果としては、RUMデータは取得できそうでしたが、APMのトレースデータは現状ではAPI提供されていませんでした。

そしてCI起点でのパフォーマンス分析を考えると、リアルタイムにパフォーマンス指標データを取れない所からDatadogベースでは厳しいです。 またDatadogを選択すると有効期限の問題や、ターゲットとしたいデータを取り出すのが面倒という問題が解消できません。

上記などの理由によりDIGGLEでは、独自でパフォーマンス指標を取得する方針としました。 DatadogはDatadogで有用なのでそちらの利用は継続し、別途パフォーマンス分析基盤を構築して、そちらに分析用のデータを保存するイメージです。

パフォーマンス分析の概要

対象データ

顧客への価値提供に影響が強い部分の、ユースケース別の実行時間などのデータになります。 特定処理の実行時間から、細かい粒度だとSQLの実行時間まで対象としています。 またDIGGLEがターゲットとする、代表的なペルソナユーザーが処理したデータになります。

分析方法

それぞれのユースケース別の実行時間を分析する形になります。 パフォーマンス改善施策などの効果を測定する為に、リリース対応前後の実行時間をクエリ抽出して効果測定します。 また、代表的なペルソナユーザーのデータを定量データとして保持して、予期せぬタイミングでパフォーマンスが悪化していないかなどの時系列分析も行います。

パフォーマンス分析基盤の構成の検討

データ保存先

トレースデータの保存先としては以下のような選択肢が考えられます。

  • DB
  • NoSQL系
  • Bigquery
  • Redshift
  • S3
  • Athena

どの選択肢も一長一短がありますが、DIGGLEではS3へログデータとして転送する方針となりました。 既存システム性能への影響が小さく、S3から先へもAthenaでデータソース化するなど色々な選択肢が取れるためです。 またS3へ保存することにより、Datadogの有効期限の問題は解消されます。

構成

S3へのデータ保存が決まりましたので、パフォーマンス分析基盤の構成はおおまかに以下のようになりました。 3と4の構成についてはデータ保存後も柔軟に変更可能です。

1のRailsから標準出力への出力先を実ファイルへ変更すると、CIでの分析も可能になります。また独自プログラムで実行するのでユースケース分けや、データのフィルタリングについてもやりやすいという利点があります。

  1. ECSのRailsから標準出力にパフォーマンス指標データをログ出力
  2. Firelensを経由してFluent bitでログをS3へ転送
  3. S3に保存されたJSONデータをAthenaでデータソース化
  4. AthenaをデータソースとしてMetabaseからクエリでデータ分析を実施

対応内容

サーバーサイド

こちらはDatadogのソースなどを参考にしながら、Rails標準のActive Support Instrumentationで取得できるパフォーマンス指標はこちらで取得し、それ以外については独自で対象のメソッドをフックして、パフォーマンス指標をリクエストなどの処理ごとに取得するように対応しました。

インフラ

ECSのRailsからは標準出力でログ出力を行い、それがFirelensからFlunet bitに渡り、Fluent bitのrewrite_tagでS3へ振り分けを行います。

Flunet bitの設定例)

jsonデータのhogeキーの値がhogehogeの場合にS3に転送される設定例になります。

# rewrite tag
[FILTER]
    Name          rewrite_tag
    Match         *-firelens-*
    Rule          $hoge hogehoge target true

# output to s3
[OUTPUT]
    Name            s3
    Match           target
    bucket          target-bucket
    region          ap-northeast-1
    use_put_object  On
    total_file_size 1M
    upload_timeout  1m
    retry_limit     False
    compression     gzip

S3に保存したJSON形式のログデータはAthenaで以下のようなクエリでテーブル化が可能です。

CREATE EXTERNAL TABLE IF NOT EXISTS hoge (
  id int,
  name string
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES (
    'null.format' = ''
)
LOCATION 's3://target-bucket/'
TBLPROPERTIES ('has_encrypted_data'='true');

注意点としてはinteger型を定義した場合は、空白データを許容してくれなくなるので、ログ出力側で0などを出力する必要があります。

最終的にDIGGLEではAthenaをデータソースにMetabaseでクエリを実行する形式にしています。

フロントエンド

フロントエンドはサーバーサイドで作った仕組みに乗っかる形で、処理単位で複数のトレースデータをサーバーサイドに送信する形を取りました。

最後に

今回はパフォーマンス分析基盤の構築について検討から実際の構築まで紹介しました。性能観点と後々の柔軟性という部分で今回はS3へデータを保存する形になりましたが、他にも様々な選択肢があると思います。

この記事が、分析基盤構築などの一助となれば幸いです。

DIGGLEのエンジニアのchikugoがお送りしました。

We're hiring!

DIGGLE では共にプロダクトを開発してくれるエンジニアを大募集中です。

少しでも興味があれば、ぜひ下記採用サイトからエントリーください。
カジュアル面談も実施しているので、気軽なお気持ちでご応募いただければと思います!

https://herp.careers/v1/diggle/_dgvcOQcFfeqherp.careers

*1:トレースデータとはアプリケーションやサービスが実行する一連の操作を表す実行時間などを含むデータです

なりすまし/迷惑メールと思われない為のDMARC導入手順

はじめに

DIGGLEエンジニアのaki0344です。
2024/02/01より、Gmailのガイドラインが変更されました。 support.google.com

Gmailに対して1日5000件以上のメールを送信しているドメインはSPF、DKIM、DMARCによる認証を設定していない場合なりすましや迷惑メールと判断されて送信先に届かない可能性があります。
今回、DIGGLEではSalesチームやプロダクトから送信したメールが迷惑メールと判定されないよう、全社のメールでDMARCに対応しました。
本記事ではその際に行った作業についてまとめてみました。
実際の手順のみ知りたい方はDMARC対応の流れからお読みください。

DMARC対応作業を始めるまで

それは一通の問い合わせから始まった

ある日、Salesチームから1通の問い合わせが届きました。

翌日、開発チームのデイリースクラムで問い合わせについて共有され、対応が必要だねという話になりました。
メールのセキュリティについて全く知識が無かった私は静観していたのですが、生憎他メンバーは直近の作業に追われており、急ぎの作業のなかった私に押し付けられましたが対応することにしました。
何のことか良くわからないけど、何を設定すれば良いかは明示されてるしそんなに大変な作業でもないでしょと思っていました。実際に作業を始めるまでは…。

そもそもDMARCって何だ?

さて、DMARCに対応するためには当然DMARCが何者なのかを知らなければいけません。
まずはDMARCで検索をかけてみたのですが、SPF/DKIMというこれまた聞いたことのない言葉が出てきました。
いきなり調べることが増えてしまった…。
様々なサイトを眺めていましたが、『構造計画研究所』さんのブログ記事がとても分かりやすかったです。

sendgrid.kke.co.jp

sendgrid.kke.co.jp

それぞれを一言で表すと以下のようになります。

  • SPF : 正規のサーバから送信されていることを証明する
  • DKIM : ドメイン管理者がメールを送信していることを証明する
  • DMARC : SPF/DKIMで認証失敗した場合にメールをどう扱うかを決める

DMARCに対応するためには、その前段階としてSPF/DKIMに対応しなければいけません。
※DMARCはSPF/DKIMいずれかのみと組み合わせての対応も可能ですが、今回DIGGLEでは全て対応することを前提に作業を進めました。

対応時の注意点

DMARCに対応するにあたり、いくつか注意点があります。

メール送信方法によって対応内容が異なる

メール送信を行うサービスを利用している場合、それぞれに対応が必要となります。
一部設定が漏れていた場合、そのサービスから送信されたメールが送付先でなりすましと判定される可能性があります。

SPF、DMARCの対応は複数サービスで同じ場所に修正を加える必要がある

詳細は後述しますが、DNSに設定するレコードはSPF、DMARCでそれぞれ1行です。
複数サービスでSPF、DMARCに対応する場合、各サービスの案内に記載されている内容をそのまま転記するとそれぞれ複数行追加してしまう可能性があります。
作業完了時、SPF、およびDMARC用のDNSのレコードはそれぞれ1行ずつと覚えてください。
なお、DKIMは各サービスごとにレコードが必要となります。

SPF/DKIM未対応の状態でDMARCに対応してはいけない

先述の通り、DMARCはSPF/DKIMの認証結果を基に次のアクションを決定する機能となります。
SPF/DKIMに未対応の状態でDMARCのみ追加した場合、送信したメールがすべてなりすましと判定される可能性があります。
DMARCは対応が必要な全てのサービスでSPF/DKIMに対応し、最後に設定を行いましょう。

定義を間違えると他のサービスにも影響を及ぼす危険性がある

複数サービスで共通する箇所に修正を入れるため、1か所失敗すると全てのサービスが正常に動作しなくなる可能性があります。
かならずクロスチェックをしましょう。

DMARC対応の流れ

ここからは実際にDMARCに対応する際に行ったことを書いていきます。

自社で保有しているドメイン一覧を特定する

複数のドメインを保有している場合、各ドメインでメールを送信している可能性があります。
今回の対応はドメイン毎に必要となるため、対応漏れが無いよう自社ドメインを全て洗い出します。

メールアドレスに使用されているドメインを特定する

メールの送信元(From)となり得る全てのドメインで対応要否の判定が必要となります。
社内の有識者に確認し、どのドメインがメール送信に使用されているかを絞り込みます。
DIGGLEでは以下の方法で絞り込みを行いました。

  • バックオフィスに確認
    各社員に割り当てられたメールアドレスを管理している部署に、他のドメインを利用しているかを確認します。
  • プロダクト管理者に確認
    プロダクトの機能でユーザーへメール送信する際に利用しているドメインを確認します。

ドメイン毎にDMARC対応が必要か判定する

Googleのガイドラインでは、1日5000件以上のメールを送信する場合にDMARCへの対応が必要となっています。
毎日大量のメールを送信しないことが明らかな場合はDMARCの対応を行わなくてもガイドラインには抵触しません。
今回のDIGGLEの対応ではドメイン毎に送信しているメールの件数について確認はしていませんが、使用用途から現在、あるいは今後1日5000件以上のメールを送信すると判断したドメインに対して対応を行うことにしました。

メール送信を行っているサービスを洗い出す

SPF/DKIMはメールサーバ毎に設定を行う必要があります。
そのため、自社内で先ほど絞り込んだドメインを送信元としてメール送信している外部サービスを確認します。
バックオフィスなど各部署の利用しているサービスを管理している部署に、自社ドメインでメール送信を行っているサービスについて問い合わせましょう。
DIGGLEでは念のためslackの全社員が登録しているチャンネルでも確認を行いました。
※文中にある各個人からのメール送信はGmailのSMTPサーバを利用しており、過去にSPF、DKIM対応済みだったため今回は対応不要としています。

各サービスでSPF、DKIMの対応方法を確認する

SPF、DKIMの対応方法はサービスによって異なるため、各サービスのマニュアルや設定画面から対応方法を特定します。
DIGGLEで利用していたサービスでは大別して4パターンあることがわかりました。

SMTPサーバを指定している

Metabaseのように自分でサーバを用意するシステムの場合、メールはSMTPサーバを指定するのが一般的です。

この場合はSMTPサーバを管理しているサービスで次に記載する内容を確認します。

SPF/DKIMを有効化するレコードをDNSに追加する

最も一般的な対応方法です。 各サービス毎に、SPF、およびDKIMを設定するための値が記載されているページを探します。 SPFは一般に公開されていることが多く、マニュアル等やFAQで記載されている個所を探します。

SPFの設定値サンプル

DKIMで設定する値はドメイン毎に異なるため、管理者画面からのみ確認が可能となっています。
ドメインや配信時のメールアドレスを設定するページで確認できることが多いです。

emaildocs.netcorecloud.com

サービス側のDNSサーバを参照するためのレコードをDNSに追加する

SendGridなどの一部サービスでは、サービスが用意しているDNSサーバがSPF/DKIMの認証を行う機能があります。

sendgrid.kke.co.jp

その場合、自社で用意しているDNSではサービス側のDNSサーバを参照するためのレコードを定義する必要があるため、その値が記載されているページを探します。
レコードの値はドメイン毎に異なるため、管理者画面からのみ確認が可能となっています。
ドメインや配信時のメールアドレスを設定するページで確認できることが多いです。

他サーバのDNSサーバ参照値サンプル

対応方法が何も書かれていない場合

DIGGLEで利用しているサービスの中には、FAQ等のドキュメントや管理者用設定画面のどこを探してもSPF、DKIM、DMARCに関する記載が一切存在しないサービスがありました。
こちらは実際にメールを配信する手順で社員へメールを送信し、ヘッダ情報でSPF、DKIM、DMARCに対応しているかを確認し、未対応の場合は問い合わせる方向で作業を進めました。
※ヘッダ情報の確認方法は後述しています。
結果的には全て対応済みだったため事なきを得ました。

DNSで各ドメインにSPF、DKIM、DMARCのレコードを追加する

各サービスで追加する内容が明確になったので、ドメイン毎にレコードを追加していきます。
ここで2点注意事項があります。
※サブドメイン毎に設定する場合はこの限りではありません。

  • SPFのレコードは1ドメインに1つ
    複数のサービスを利用している場合、『SPF/DKIMを有効化するレコードをDNSに追加する』でサービス毎のSPFを確認していると思いますが、DNSには1つのレコードにまとめて登録する必要があります。
    複数の場合はincludeをサービス数分定義し、各サービスの値を設定します。

    • 誤った設定例
      v=spf1 include:spf.corp01.value ~all
      v=spf1 include:spf.corp02.value ~all
      v=spf1 include:spf.corp03.value ~all
    • 正しい設定例
      v=spf1 include:spf.corp01.value include:spf.corp02.value include:spf.corp03.value ~all
  • DMARCのレコードは1ドメインに1つ
    DMARCはサービス数に関わらず共通の値を使用します。

SPF、DKIMのレコードを追加する

ドメインを管理しているDNSで、ドメイン毎にSPF、DKIMを有効にするためのレコードを追加します。
SPFは1つにまとめた値を、DKIMはサービス毎に指定された値の数だけレコードを追加します。
値に誤りがあると他サービスにも影響する可能性があるため、クロスチェックを行う等、確認は念入りに行ってください。
レコード追加後、各サービスからメールを送信し、ヘッダの内容からSPF、DKIMが有効になっているか確認します。
※有効になるには一定時間を要する場合があります。
Gmailの場合は『メッセージのソースを表示』で確認することが可能です。

DMARCのレコードを追加する

ドメインを管理しているDNSで、ドメイン毎にDMARCを有効にするためのレコードを追加します。
パラメータは必要に応じた内容を設定します。
www.naritai.jp

認証失敗時の動作pについてはいきなりrejectなどの厳しい制限を指定すると今までメールを受け取れていた相手に届かなくなる可能性があります。
一旦noneを設定し、レポートで状況を確認しながら段階的に制限を強めていくのがおすすめです。
以下は設定値の一例です。

v=DMARC1; p=none; rua=mailto:report-rua@my.corp.com; ruf=mailto:report-ruf@my.corp.com;

ruarufを設定しておくとメールでレポートが送られてくるため、後から到達率等の振り返りが可能となります。
レコード追加後、各サービスからメールを送信し、ヘッダの内容からDMARCが有効になっているか確認します。
※有効になるには一定時間を要する場合があります。
SPF、DKIMと同様に、Gmailの場合は『メッセージのソースを表示』で確認することが可能です。

サービス導入時の対応手順を整備する

以上の手順で現在利用しているサービスについてはSPF、DKIM、DMARCが有効になりました。
一方、今後各部署で利用するサービスを追加した場合は、今回と同様の手順でSPF、DKIMの追加が必要な可能性があります。
そのため、社内でまとめているサービス導入時の手続き等の文書にDNS管理者へ設定の依頼を行う手順を追加しておくことをお勧めします。
DIGGLEでは以下の条件を満たすサービスの場合にはDNS管理者へ通知するよう手順をまとめました。

  • 自社ドメインを利用してメールを送信する
  • 初期セットアップで送信メールサーバを指定しない

これで今後の新規サービス導入がスムーズに進むことが期待できます。

おわりに

今回はDIGGLEから送信したメールがGmailでなりすましや迷惑メールと判定されないよう、SPF、DKIM、DMARCを有効化するために行った作業をまとめてみました。
本記事では効率的で漏れのない手順をまとめていますが、実際には私の知識不足により各手順を行ったり来たりしながら無駄の多い作業となってしまいました。
幸い、DIGGLEでは最近立ち上がったSREチームにセキュリティに強い方がいたため、その方に叱られながらアドバイスをいただきながら無事作業を完了させることが出来ました。
本記事が、メールのセキュリティ強化に対応する際の一助となれば幸いです。

We're hiring!

DIGGLE では共にプロダクトを開発してくれるエンジニアを大募集中です。

少しでも興味があれば、ぜひ下記採用サイトからエントリーください。
カジュアル面談も実施しているので、気軽なお気持ちでご応募いただければと思います!

herp.careers

Rustで型付Webアプリケーション開発

この記事は Rust Advent Calendar 2023 25日目の記事です。

お久しぶりです。DIGGLEのhirataです。

今回はDIGGLEとは関係ないですが、個人的にRustの利用を促進すべくRustでのWebアプリケーションの開発経験について書きたいと思います。

所感としては一人で開発するならRustを使うべし、と思える経験でした。やはりあちこちで言われている事ではありますが、静的型とライフタイムチェックによりコンパイルを通ってしまえばほぼバグが無く動くという安心感がとても心地よいものでした。コンパイラが代わりにテストをしてくれているような心持ちです。 また、Rustで作成したサーバはとてもメモリ消費量が小さく、Railsアプリであれば下手をすると1GB程まで行ってしまいますが、数十MBで十分動くというのも個人にとってありがたいものでした。 ちなみにとても安定して動作しており、今までバグやアップデート以外の再起動はありません。

参考までに私は元組み込み系のエンジニアでして、その影響で未だにメモリを沢山使うことに罪の意識を覚えます。 そして現在の仕事のメインはRailsでメモリ潤沢な大富豪バックエンドの開発をしています。その影響で随所にRailsとの比較が出てくるかなと思います。

どうしてRustだったのか。そしてどんなWebアプリケーションか。

それまでRustでいくつか小さなプログラムを書いたことはありましたがWebアプリケーションを書くとどんな感じなのだろうかという好奇心が全てでした。

おかげで少々苦労することにはなりましたが。

プロジェクトとしては個人での副業で、開発者は一人だったのでインフラも含めて全て自由に決めることができました。運用費はできるだけ安く (できればタダ) にしたかったので、Railsだとリソース的に厳しかろうということでRustを選んだ一面もあります。 用途はとある小規模なジムで使う来客管理システムで、会員登録やその事前登録、お客様の入退場、入場料等を管理できるものになっています。 一人で開発するならSPAじゃないほうが開発は速かろうということで、マルチページでのアプケーションとしました。 使用しているクレート (Rustでは公開・共有されているライブラリの事をこう呼びます) は開発開始当初は大まかに以下の通りでした。

後は必要に応じて追加していくことになります。

まずは見切り発車での開発。

とりあえずactix-webはRustではその当時(2年ほど前)一番メジャーで高速そうなクレートだったので採用しました。確かどこかのベンチマークサイトで一番になっていたことも有ったはずです。他のも大体人気順に決めた感じでした。 あまり知識がない状態だとこんなものです。

実際に使ってみるとactix-webやteraは特に問題なく使えたのですが、dieselはなかなか難解でした。JOINの無い状態だと簡単に使えるのですがJOINした場合の型が以下のように大変なことに。。。 これはJOIN有りでSELECTした結果を引数として受け取る関数です。とても自分で書けるものではなかったので、エラーメッセージからコピペしました。

fn admittance_search_query<'a>(params: &CustomerAdmittanceSearchParams)
 -> diesel::query_builder::BoxedSelectStatement<'a, 
        (schema::customer_admittances::SqlType, schema::staffs::SqlType, schema::customers::SqlType),
        diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<schema::customer_admittances::table,
        schema::staffs::table,
        diesel::query_source::joins::Inner>,
        diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<schema::customer_admittances::columns::processed_by_id>,
        diesel::expression::nullable::Nullable<schema::staffs::columns::id>>>,
        schema::customers::table, diesel::query_source::joins::Inner>, 
        diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<schema::customer_admittances::columns::customer_id>,
        diesel::expression::nullable::Nullable<schema::customers::columns::id>>>,
        diesel::pg::Pg>

RDBを使っている以上、JOINが無いことは考えられないと思いますのでこれは避けて通れなさそうです。当時はver1.x系しか無くてそれを使っていたのですが、今はver2になっているのでもしかしたら現在は解決されているかもしれません。もしくはもっと良い書き方があるよ!という方は教えていただけると助かります。

初期段階で一番苦労したのは、Railsのように1から10までレールが引かれている訳では無いので、開発の基盤を安定させるところでした。例えば以下の事は自分で決める必要がありました。

  • モジュールをどのような構造で構成するか。MVCにするか、レイヤードアーキテクチャにするか、はたまたオリジナルにするか。そこまで複雑にならない予定だったのでRailsで慣れているMVC構成としました。
  • 構成したモジュール間の初期化処理をどのように繋げるか。簡単なようですがついついエレガントさを求めてしまい、意外に時間を取られます。そして最終的には悩んだ割にあんまりエレガントじゃないという。。。
  • エラーログはどう出力するか。どのライブラリを使うのか。エラーレベルはどう定義するのか。最終的にはenv_loggerとlogというクレートを使いました。
  • 各種設定や開発環境、本番環境の切り分けは環境変数で行うのか、設定ファイルを作るのか。コンテナで動かすつもりだったので環境変数で全て設定するようにしています。
  • JavaScript側の構成はどうするのか?JavaScriptのライブラリを使いたかったので一応webpackも入れました。

このように事ある毎に自分でレールを敷く必要があるので、このやり方でいいのかという自問との戦いでした。下手に悩むくらいなら実は酒でも飲みながら進めた方が捗ったかもしれないですね。

開発を終え保守のステージ。

無事リリースし以降数回の修正を重ねることになりますが、幾度かの修正を重ねる中、HTMLテンプレートのチェックをコンパイル時に行ってくれないteraだと、実際に動かしてみるまで些細なミスが発見できないのが苦痛になってきました。 修正後にサーバを起動しページをレンダリングしようとした段階で、結構な頻度でテンプレート内の式でエラーになってしまいます。

ということで、コンパイル時にテンプレートについても静的チェックを行ってくれるクレートを探していたところ発見しました。

github.com

これがとても良く私の用途にマッチしてくれて、おかげでテンプレート内の式の書き間違いに苦しむ事はなくなりました。一度コンパイルが通ればページレンダリングの段階でエラーになることはありません。

このようにしてHTML出力に関してはほぼ動的にエラーが発生することはなくなり、コンパイル時に気づくことができるようになりました。 ただまだHTML上でシステムにリクエストを行うような下記の部分はサーバを起動して動的にチェックする必要があります。

  • aタグやその他に書かれているシステムのURL
  • formタグの中身のinput

これらをコードで生成するライブラリを作ることができればJavaScirpt以外は全てコンパイル時にチェックされることになり動的なエラーを撲滅できそうです。 実現できれば後はJavaScriptをTypeScript化すれば全てに型が付いてコンパイラがチェックしてくれるWebアプリケーション開発環境のできあがりです。夢が膨らみます。暇が無くてできてませんが。

またコードを書いている中でどうしても、formを受け取るための構造体と、ORMで使うための構造体に差が出てきてしまうので最初は手書きでメンバをコピーするコードを書いていました。 これが苦痛でたまらない上に、ミスに気づきにくかったのですが、構造体のコピーをしてくれるようなクレートが見つからなかったため、自分でマクロを作りました。

https://crates.io/crates/clone_into_derive

これでかなりコードが簡略化できたのと、どうしてもコピーするメンバを書き忘れることがあったのですがそれが無くなって保守性があがりました。

こんなコードが有った場合、直接コピーできないのでどうしても冗長に書く必要があります。わかりやすくするために簡略化しています。

// formから変更内容を受け取る用の構造体
pub struct UpdateCustomer {
    pub second_name: String,
    pub first_name: String,
    ...
    pub blood_type: i16,
    pub memo: String,
    pub monthly_path_limit: Option<NaiveDate>,
}

// ORM用の構造体定義
pub struct Customer {
    pub id: i64,
    pub assigned_id: Option<i32>,
    pub email: String,
    pub sex: i16,
    pub birthday: NaiveDate,
    pub post_number: String,
    pub prefecture: String,
    pub municipalitie: String,
    pub address: String,
    pub tel: String,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub blood_type: i16,
    pub membered_at: Option<NaiveDateTime>,
    pub memo: String,
    pub second_name: String,
    pub first_name: String,
    pub second_name_furigana: String,
}

pub fn change_it(con: &Connection, customer_id: i64, update_customer: UpdateCustomer) {
    let customer = customer::find(con, customer_id);
    customer.second_name = update_customer.second_name.clone();
    customer.first_name = update_customer.first_name.clone();
    ...
    customer.blood_type = update_customer.blood_type;
    customer.update(con);
}

こうなります (変更部分だけ)。違いはUpdateCustomer#[derive(CloneInto)]というマクロを使用するための記述が追加されたのと、そのマクロupdate_customer_clone_into!chjange_it内でメンバのコピーに使っていることです。かなり短くなりました。

// formから変更内容を受け取る用の構造体
#[derive(CloneInto)]
pub struct UpdateCustomer {
    pub second_name: String,
    pub first_name: String,
    ...
    pub blood_type: i16,
    pub memo: String,
    pub monthly_path_limit: Option<NaiveDate>,
}

pub fn change_it(con: &Connection, customer_id: i64, update_customer: UpdateCustomer) {
    let customer = customer::find(con, customer_id);
    update_customer_clone_into!(param.customer, customer);
    customer.update(con);
}

よし!かなり使えるライブラリだ!、と思ってクレートとして公開したのですが、全然アクセスがありません。私の想定では人気のクレートになってRust界の有名人になる筈だったのですが。

是非使っていただければ。

あとがき。

という訳で、全てでは無いですがHTMLテンプレートの中までコンパイラが型チェック等をしてくれる状態になりました。 一人で開発しているとやることが多いので、動作確認の一部とはいえ肩代わりしてくれる存在が心強いです。

実装面での感想ですが、Rustでは俗に言う継承(inheritance)が無く、代わりに委譲(composition)が推奨されているのでRubyのような純粋オブジェクト指向の世界から来ると戸惑いを覚えます。 共通部分だけベースクラスとして定義して、それを継承するということは結構やってしまいがちだと思いますが、一切使えません。代わりに委譲を使いましょう。

また安心してマクロが使えるというのは特筆すべきことかなと思います。マクロとはコードを生成するコードの事で、Rustではコンパイル時にマクロからコードに展開します。 マクロはデバッグも厄介で時にバグの温床になるのですが、Rustでは生成されたコードが問題ないかどうかを型の整合性の面と変数のライフタイムからコンパイラがチェックしてくれるので、コンパイルが通ればほぼ大丈夫という安心感があります。 私の書いたマクロは単にコピー元の変数の構造体定義にあるメンバをコピー先として指定された変数の同名のメンバに代入するコードを生成するというものですが、このチェックのおかげで問題があればコンパイラが検出してくれます。なので私のクレートではコピー先の型は指定しなくても良いようにしました。 このようにマクロとコンパイラでの静的チェックはとても相性が良さそうに思います。

そしてRustはいい!コンパイルが通れば動くという安心感に加えて、クレート=ライブラリも比較的充実しており、あまり開発時に困ることもなさそうです。 私はCommon Lispも好きなのですが、欲しいライブラリが無いことがままあって悲しい思いをすることが多いです。。。

ただ、マクロが必要になることが結構ありそうなこと、ライフタイムがあること、そもそも型の概念が難しい等々で、やはりRustを扱う人には高い技術が求められそうです。 個人的にはそれに伴うリターンはあると思うので、高い技術力をもったメンバーを集めるのであれば、Rustでの開発も選択肢に入れてもいいのではないかと考えます。

以上RustでSPAでは無いアプリケーションを作ったときのお話でした。 今どきはHTMLを直接出力するWebアプリを作る機会は少ないのかもしれませんが、何かの参考にしていただければ。

We're hiring!

DIGGLE では共にプロダクトを開発してくれるエンジニアを大募集中です。

少しでも興味があれば、ぜひ下記採用サイトからエントリーください。 カジュアル面談も実施しているので、気軽なお気持ちでご応募いただければと思います!

herp.careers

OWASP Top Ten に見る Web セキュリティの20年

セキュリティ

こんにちは、DIGGLE エンジニアの庵です。決して三澤ではありません。
はじめてのアドベントカレンダー Advent Calendar 2023 の 24日目の記事です。

 

はじめに

みなさんは Web セキュリティについて、どのようなイメージを持たれていますか?
サービスダウンや情報漏洩等、利用者としても運営者としても怖いものですね。

そんな Web セキュリティについて、2003年から数年おきに提供されている OWASP Top Ten というレポートについて少し調べてみた事をお話ししたいと思います。

 

脆弱性の変遷

ある時 OWASP Top Ten を眺めていると、ふと思いました。
昔のバージョンでも名前が違うけど似たような項目があったような‥‥と。
そこで、全バージョンを表にして、変遷を見やすくしてみましょう。

OWASP Top Ten 2003 - 2021

そうですね、あまり見やすくはないですね。
レイアウトの限界という事でどうぞご理解ください。

表の見方の簡単な説明は、以下のような感じです。

  • 縦方向に上から下に1位〜10位、横方向に左から右に2003年版〜2021年版です。
  • 版をまたいで同じ分類のものについて、項目の箱を同じ色にしています。
    • 分割された項目や統合先と表記が乖離している項目は、同系統の暗めの色にしています。
  • 左下に判例があるアイコンで、それぞれの項目に補足情報を付与しています。
    • 新設 新設
    • 廃止 廃止
    • 分割 分割
    • 統合 統合
    • 長くランクインし続けている項目 (この記事の焦点) 長くランクインし続けている項目 (この記事の焦点)

OWASP Top Ten は、2003年2004年2007年2010年2013年2017年2021年と、これまでに7回提供されています。
また、Mobile 向けや API 向けの Top Ten も別途提供されています。

それでは、Web 向けの最新版である2021年度版に基づいて、各脆弱性の変遷を見ていきたいと思います。
各項目名に OWASP の解説ページへのリンクを設定しているので、詳細はそちらをご参照ください。


脆弱性カウントダウン

第10位

変遷
入選 版数 年度 順位 名称
1. #7 2021 A10 Server-Side Request Forgery (SSRF)

第7版の2021年に新たに設けられた分類として10位にランクインしました。

発生数は比較的少ないものの被害が平均よりも大きくなりがちである事から、注意喚起の為にも選出されたようです。


第9位

変遷
入選 版数 年度 順位 名称
1. #6 2017 A10 Insufficient Logging & Monitoring
2. #7 2021 A09 Security Logging and Monitoring Failures

第6版の2017年に10位でランクインし、
2021年は9位に順位を上げています。

ロギングとモニタリングはテストが難しい上に、CVE / CVSS (*1) のような脆弱性情報として現れる事が少ないですが、うまく機能しなかった場合、説明責任やフォレンジック等影響が大きいです。このような点から選出されたようです。

*1: 分かり易さの為に CVE / CVSS の管理者では無く第三者の運営するサイトにリンクしています。


第8位

変遷
入選 版数 年度 順位 名称
1. #6 2017 A08 Insecure Deserialization
2. #7 2021 A08 Software and Data Integrity Failures

第6版の2017年に8位でランクインし、
2021年も8位でフィニッシュです。

CI/CD パイプラインで整合性未検証のデータを取り込んでしまう事により、必然的に脆弱性を作り込んでしまうという影響の大きさから選出されたようです。まるでスパイに潜入されてしまうようなイメージですね。対策としては、例えば SBOM の活用を検討する等が挙げられるのではないかと思います。もちろん OWASP が挙げている防止方法もチェックしたいですね。


第7位

変遷
入選 版数 年度 順位 名称
1. #1 2003 A03 Broken Account and Session Management
2. #2 2004 A03 Broken Authentication and Session Management
3. #3 2007 A07 Broken Authentication and Session Management
4. #4 2010 A03 Broken Authentication and Session Management
5. #5 2013 A02 Broken Authentication and Session Management
6. #6 2017 A02 Broken Authentication
7. #7 2021 A07 Identification and Authentication Failures

第1版の2003年に3位でランクインし、
2007年は7位に転落、
2010年はまた3位に返り咲き、
2013年からは2位まで上昇しましたが、
2021年はまたしても7位に転落しました。

以前は認証の不備としてカテゴライズされていた項目で、不適切な証明書の問題やセッション管理の問題も内包しています。本人確認が重要である事はオフラインの社会でも同じですね。継続的に選出されている事も納得できると思います。


第6位

変遷
入選 版数 年度 順位 名称
1. #1 2003 A08 Insecure Use of Cryptography
2. #2 2004 A08 Insecure Storage
3. #3 2007 A08 Insecure Cryptographic Storage
4. #4 2010 A07 Insecure Cryptographic Storage
5. #5 2013 A09 Using Known Vulnerable Components
6. #6 2017 A09 Using Components with Known Vulnerabilities
7. #7 2021 A06 Vulnerable and Outdated Components

第1版の2003年に8位でランクインし、
2010年に7位へ上昇しましたが、
2013年2017年と9位へ転落、
2021年過去最高位の6位に上昇しました。

簡潔に言うと、ライブラリ・ミドルウェア・ OS 等のバージョンを最新に保てていない問題です。現実問題として、隅々まで漏れなくアップデート済みかというと不安が残る方もいらっしゃるのではないでしょうか。そのような点から選出されたようですね。こちらについても SBOMVuls のようなツールが有用だと思われます。AWS の ECR もイメージスキャン機能を提供しているので、ぜひ活用したいですね。


第5位

変遷
入選 版数 年度 順位 名称
1. #1 2003 A10 Web and Application Server Misconfiguration
2. #2 2004 A10 Insecure Configuration Management
3. #4 2010 A06 Security Misconfiguration
4. #5 2013 A05 Security Misconfiguration
5. #6 2017 A06 Security Misconfiguration
A04 XML External Entities (XXE)
6. #7 2021 A05 Security Misconfiguration

第1版の2003年に10位でランクインし、
2007年に一度ランク外になったにも関わらず、
2010年に再度6位にランクイン、 
そこから5位と6位を行き来しており、 
2021年は5位となっています。

統計的にアプリケーションの90%に何らかの設定ミスがあるとの事です。年々設定自体も複雑化していく事もあり、重要度が高い事から選出されたようです。


第4位

変遷
入選 版数 年度 順位 名称
1. #7 2021 A04 Insecure Design

第7版の2021年に新たに設けられた分類として4位にランクインしました。

設計やアーキテクチャの欠陥に関するリスクに焦点を当て、選出されたようです。 OWASP の概要において注目すべき CWE として、 CWE-209: エラーメッセージからの情報漏洩 や CWE-522: 適切に保護されていないクレデンシャル 等が挙げられています。他にも、ログにパスワード等が出力されている環境もまだまだあるのではないでしょうか。


第3位

変遷
入選 版数 年度 順位 名称
1. #1 2003 A04 Cross-Site Scripting (XSS) Flaws
A06 Command Injection Flaws
2. #2 2004 A04 Cross-Site Scripting (XSS) Flaws
A06 Injection Flaws
3. #3 2007 A01 Cross Site Scripting (XSS)
A02 Injection Flaws
4. #4 2010 A01 Injection
A02 Cross-Site Scripting (XSS)
5. #5 2013 A01 Injection
A03 Cross-Site Scripting (XSS)
6. #6 2017 A01 Injection
A07 Cross-Site Scripting (XSS)
7. #7 2021 A03 Injection

第1版の2003年に4位でランクインし、
2007年から2017年までの4回1位を独占し、
2021年は減速しながらも3位につけています。

統計的にアプリケーションの94%に何らかのインジェクションに関する問題があるとの事です。 SQL インジェクションや OS コマンドインジェクション等、何かしら一度は聞いた事があるかもしれません。選出は必然といったところではないかと思います。


第2位

変遷
入選 版数 年度 順位 名称
1. #3 2007 A09 Insecure Communications
2. #4 2010 A09 Insufficient Transport Layer Protection
3. #5 2013 A06 Sensitive Data Exposure
4. #6 2017 A03 Sensitive Data Exposure
5. #7 2021 A02 Cryptographic Failures

第3版の2007年に9位でランクインし、
2013年に6位へ躍進し、
2017年に3位、
2021年に2位と着実に順位を上げてきています。

機微情報の露出に直接的に関係する事や暗号化の不適切な設定や使用方法が散見される事等から選出されたようです。アリスとボブも納得ですね。


第1位

変遷
入選 版数 年度 順位 名称
1. #1 2003 A02 Broken Access Control
2. #2 2004 A02 Broken Access Control
3. #3 2007 A04 Insecure Direct Object Reference
A10 Failure to Restrict URL Access
4. #4 2010 A04 Insecure Direct Object Reference
A08 Failure to Restrict URL Access
5. #5 2013 A04 Insecure Direct Object Reference
A07 Failure to Restrict URL Access
6. #6 2017 A05 Broken Access Control
7. #7 2021 A01 Broken Access Control

第1版の2003年に2位でランクインし、
2007年に2項目に分割された後、
2017年に再び統合され、
2021年にはとうとう念願(?)の1位を獲得しました。

統計的にアプリケーションの94%に何らかのアクセス制御の不備があるとの事です。一時期大きなニュースになっていたキャッシュサーバのトラブルに伴う情報漏洩やクロスサイトリクエストフォージェリ (CSRF) 等もこちらの項目に該当します。はまちちゃん、栄光の1位選出です!

 

まとめ

全10項目の内、過半数の6項目が長くランクインし続けていて、特に4項目は第1版の2003年から毎回ランクインしています。

2003年から2021年の間に、セキュリティに対する意識や組織的な取り組みは着実に向上してきているものと思われますが、バリデーションや設定・設計の不備等からくる脆弱性は残念ながら残り続けているようですね。
IaC や CI/CD 等により自動化された取り組みを積み上げていく事が、改善の道標だと考えられますが、2021年に8位となった項目で、 CI/CD の管理上の問題により脆弱性を抱えてしまうという問題に言及されている点も注目すべきだと思われます。

また、OWASP Top Ten はあくまでも分かりやすさを重視した形でのレポートだと思いますので、これだけやっておけば完璧というものではありませんが、まずは何に注意すべきかという意味ではとてもありがたい情報ですね。

身も蓋も無い結論ではありますが、結局はいかに確実に管理し続けられるかという問題に取り組みつつ、新しい脆弱性にも対応していくしかないようですね。

We're hiring!

予実管理の DIGGLE をより良くする為、リモートで一緒に開発しませんか?

「予実管理って何?」等少しでも興味をお持ち頂いたら、ぜひカジュアル面談しましょう!

herp.careers

私たち DIGGLE についてもっと知りたいと思って下さった方は以下をご覧ください。

diggle-jp.notion.site