カンムにおけるインフラの考え方をまとめた「インフラマニフェスト」のご紹介

カンムでバンドルカードのバックエンドやインフラを担当している summerwind です。

バンドルカードではスマホ上で Visa のプリペイドカードを発行して決済に使える機能を提供しており、クレジットカード情報を扱っていることから、インフラの観点では高いセキュリティを維持することが重要になっています。バンドルカードのシステムは API や国際カードブランドと接続している決済システムなどの複数のコンポーネントで構成されていますが、システムが構築された時期によって構成や設定の方針などが異なるため、より高いセキュリティを達成するためにシステム構成の変更や整理、設定の見直しを日々進めています。

構成や設定の見直しを進めていく中で、全体的な方針や目指している姿を言語化しておいた方が周囲のエンジニアにも理解が得られやすいのではないかと感じたため、インフラに対する考え方や方針を言語化した「インフラマニフェスト」を定義することとしました。この記事では社内に公開しているマニフェストの一部を抜粋して紹介したいと思います。なお、日常的に AWSGCP を活用されているエンジニアの方には当然の内容だと思われるかもしれませんが、今回は「あえて言語化する」ことを目的としているので、その点はご了承ください。


このインフラマニフェストではカンムのインフラを「こうしていくんや!」という方針的なものを言語化しています。 これは2021年のカンムのインフラの状況に基づいて定義されており、今後の改善などにより将来的には大きく方針が変更される可能性があります。

対象

このマニフェストが対象とする「インフラ」には以下が含まれます。

ワークロードとストレージを分離する

アプリケーションやバッチなどのワークロードとそのデータを保存するストレージは明確にリソースを分離し、ワークロードとデータが同居しない構成や設計を採用します。

例えば、Redash を動かすための EC2 インスタンスがあり、そこで Redash、Redis、PostgreSQL を実行していたとします。これらは以下のようにリソースを分離できます。

  • Redash: ECS コンテナ (ワークロード)
  • Redis: ElastiCache (ストレージ)
  • PostgreSQL: RDS (ストレージ)

このように分離するのは、ワークロードは一般的に高速に動作しスケーラブルであることが求められ、また比較的短命であることに対し、ストレージは安定的に稼働し続けることが求められるなど、それぞれの要件が大きく異なるためです。複数の要件を達成しようとすると、どうしてもトレードオフが発生してしまい全ての要件を達成するのが困難になります。ワークロードとストレージを明確に分離すればそれぞれの役割における要件が達成しやすくなります。

なお、ストレージは一般的に運用負荷が高くなりがちなので、積極的にマネージドサービスを活用していきます。

ペットではなく家畜へ

ワークロードを実行するリソースは、ペットのように長時間稼働させて可愛がるのではなく、家畜のように使い捨てます。常に「私が死んでも代わりはいるもの」と言えるよう、デプロイのたびにリソースが入れ替わり続けるような状況を目指します。

これは、リソースを長時間稼働させればさせるほど、設定ドリフトが発生して設定や構成の変更がしにくくなったり、脆弱性の発見によりセキュリティが低下したりすることが背景にあります。長時間稼働により、設定ドリフトやセキュリティの低下に対応するための運用負荷も同時に高まっていきます。もしリソースを頻繁に使い捨てることが前提となっていれば、設定ドリフトは発生せず、最新のセキュリティを組み込むことも可能になり、アプリケーションやシステムの構成変更もしやすくなります。

ペットと家畜の話は以下の文書も参考してください。

メタデータで管理する

ワークロードやストレージなどの全てのリソースは名前のような一意の識別子に加え、メタデータを付与して管理します。

これは、インフラの運用ではリソースを様々な軸で集約して扱うシーンが多いためです。例えば、以下のようなシーンでは名前のような識別子だけでリソースを管理するには限界があります。集約したい軸ごとにメタデータを付与し、その値ごとに集約できると運用上の様々な課題を効率化できます。

  • サービス A とサービス B の API インスタンスのメトリクスだけを集約して見たい
  • 本番環境で稼働中の全ての RDS インスタンスのエラーログだけを集約して見たい

リソースには次のようなメタデータを必ず付与し、項目単体あるいは複数の項目を組み合わせてリソースの管理を可能にします。またこのようなメタデータを付与できるサービスやツールなどを積極的に採用することとします。

メタデータ 概要
環境 リソースがどの環境用 (本番環境、ステージング環境など) のものなのかを示す
サービス どのサービスあるいはプロダクトに属しているかを示す
役割 サービスの中での役割を示す (API、管理ツールなど)

データにより宣言的に管理する

全てのリソースの設定や状態はデータファイルに構造化データとして定義し、専用のツールを使用してその定義内容を実際のリソースと一致させることで管理します。構造化データには JSONYAML、HCL2 形式のいずれかを採用し、それらの形式でリソースを宣言的できるツールを積極的に採用します。

これまで一般的なインフラのリソースの多くは手順書などの「命令的」な定義により管理されてきましたが、これは順序に依存する仕組みであり、手順の変更などによって設定を再現できなくなったりする問題があります。古いドキュメントの手順に従って作業を実施したら環境を再現できなかったという経験がある方は多いのではないでしょうか。

この問題を解決するために、手順ではなく以下のようなデータファイルにリソースのあるべき状態を定義し、その定義に基づいてリソースの状態を一致させるツールを積極的に採用します。この例では ECS クラスタprdメタデータ付与された状態である、ということを Terraform 向けの HCL2 形式で定義しています。これらのデータファイルは Git などで管理することにより、変更のレビューやテスト、特定時点のリソースにロールバックする、といったことが可能になります。

resource "aws_ecs_cluster" "prd" {
  name = "prd"

  tags = {
    Name        = "prd"
    Service     = "A"
    Environment = "prd"
  }
}

なお、ここではクラウドサービスのリソース管理を例に挙げましたが、Linux などのインスタンスのシステム構成や、ルーター機器の設定などについても同様に可能な限り宣言的にデータファイルで管理します。

継続的にテストをする

インフラのリソースをデータとして宣言的に定義すると、次のようなテストが可能になります。これらのテストは CI サービスを利用して、バージョン管理システムに変更を追加する毎に実行し、レビュー時にはその検証結果が妥当なものであるかを評価します。

  • ツールを使用して定義の内容とリソースの実際の設定の差分を検証する
  • ポリシーツールを使用して、意図しない設定の定義が含まれていないかを検証する

これらのテストを継続的に実行することで、意図しない変更に早期に気付けたり、データファイル以外で発生した設定ドリフトを検出できるようになります。

本番環境、ステージング環境、開発環境を使い分ける

リソースは本番環境、ステージング環境、開発環境の3つの環境に用意して使い分けます。使用する環境とその用途は次の通りです。ステージング環境は本番環境と同等の環境として維持し、リリース前の最終的な動作確認や検証にのみ使用します。レビューを受けていない開発中のコードなどは開発環境に対してのみデプロイします。

環境名 用途
本番環境 利用者にサービスを提供するための環境
ステージング環境 本番環境と同じ構成でテストを実行するための環境であり、リリース用のコードのみをデプロイする
開発環境 開発中のコードの動作を検証するための環境であり、開発中のコードをデプロイする

これらの環境は全て独立したものとして管理し、各環境のリソースが他の環境のリソースに影響しないようにします。また、AWS などのクラウドプロバイダーについては、環境ごとにアカウントを分離します。

シンプルにする

リソースの構成は可能な限りシンプルにします。リソースが少なければ少ないほど運用上の負荷が減るため、少人数で開発と運用をしているカンムにおいてシンプルさを選択することはとても重要だと考えます。具体的には次のような選択をします。

  • クラウドサービスの機能を積極的に活用する
  • 自前で実装せず、オープンソースソフトウェアを活用し、その結果をコミュニティにフィードバックする
  • 最小限のリソースで構成する

いかがでしたでしょうか。社内のマニフェストにはより多くの項目について記載していますが、ここでは紹介しきれないのでまた機会を見つけてご紹介したいと思います。

カンムではセキュアでモダンなクレジットカード決済インフラを作りたいエンジニアを積極的に採用しています!

カンムを支える技術 ~機械学習編~

バックエンドエンジニアの吉田です。カンムでは機械学習を用いた機能開発を担当しています。 バンドルカードでは後払い機能であるポチっとチャージで機械学習が使われています。 去年のAdvent Calendarで石澤さんが カンムを支える技術2020 という記事を書いてくれていましたがそこではあまり触れられていなかった機械学習まわりの取り組みについて簡単にご紹介します。 バンドルカードのサービスはAWSで構築されているので基本的にはAWSに寄せつつも機械学習ではGCPも活用しマルチクラウドで運用しています。

f:id:kanmu-tech:20210610153614p:plain

Data Preparation

DWHとしてBigQueryを利用しています。BigQueryにはバンドルカードのトランザクションデータやFirebaseで取得したアプリのイベントログ、サーバのアプリケーションログ等が集約されておりデータ分析やA/Bテストの集計、障害調査等に使われています。 機械学習においては、時系列データの各種統計量やラグ特徴量の追加といった特徴量エンジニアリングや前処理にもBigQueryを使っています。SQLで処理できるものは基本的にBigQueryに任せて学習用のデータセットを作成しています。

RDSに保存されているバンドルカードのデータは日次でBigQueryに同期しています。 ここでは単純にPostgreSQLのテーブルをほぼそのままBigQueryに転送しているだけで複雑なワークフローは必要なかったことと、GUIも必要なかったのでシンプルなDigdag/Embulkを採用しECS/FargateのScheduled tasksで実行時のみコンテナを立ち上げて処理するような構成にしています。エラーのときは最低限Slackに通知しているのと容易にリトライができるように同期処理が冪等になるようにだけは気をつけています。

続いてGCPではCloud SchedulerからCloud Dataflowのジョブが起動されます。 ここではBigQueryからデータを取得し特徴量エンジニアリングや前処理を行った後、CSVファイルでCloud Storageにアップロードされます。 これも同様に日次で実行されますが、トリガーはCloud Schedulerによる定時実行なので先ほどのBigQueryの同期処理の完了時間からバッファを持たせて実行させています。本当はBigQueryへのデータ同期の正常終了に続けてジョブ実行されるようにフローを組みたいところですが同期に失敗していても大きな問題はないのとそもそもほぼ失敗することはなく実運用上困ってはいないのでよしとしています。ここで作成されたデータセットがモデルの学習で使われます。

Training & Serving

本番で稼働するモデルの再学習は新しく学習データが追加されるタイミング (ポチっとチャージでは月初) や特徴量の追加、パラメータの変更などモデルが更新されるタイミングで行われます。

モデルの学習とサービングはSageMakerを利用しています。学習/推論用のコンテナだけ用意しておけば、データセット、モデル/メトリクス等の成果物、推論用のエンドポイントを一元管理してくれるので非常に便利です。 推論コンテナはGunicorn/Flaskを使ったシンプルなREST APIとなっています。

モデルの学習からサービングまではStep Functionsによって実行されています。すべてのタスクが完了したら、学習結果のログやメトリクスから性能に問題がないことを確認します。 Lambdaから呼び出す推論エンドポイントは環境変数で管理されているので、まず検証環境のエンドポイントを書き換えてテストデータでリクエストを投げてみて想定どおりの結果が返ってくることを確認します。問題がなければ本番環境も切り替えます。モデルの更新はそこまで頻繁に行われるわけではないのでこの辺は自動化せずに慎重に人間が確認をするようにしています。

推論のリクエストパラメータが変更されるような場合にAPIのバージョニングが必要となりますがこれもLambdaで解決するようにしています。

Inference & Monitoring

バンドルカードからはリアルタイムで推論しています。アプリからのユーザの操作であったりバッチ処理であったりトリガーは様々ですが、推論及びその後処理には多少時間を要するので非同期で処理をしています。 RDSにジョブをenqueueすることでWorkerが推論処理を行い、その結果をRDSに書き込みます。

モニタリングに関してはエラーやレイテンシ等の各種メトリクスのMackerelによる監視やRDSのジョブキューの監視を行い異常があった場合はslackやPagerDutyで通知をしています。 また、推論のリクエストと結果に異常がないか、後処理の結果が想定どおりであるかRedashのダッシュボードなどを用意して確認をしています。

推論時の特徴量のデータドリフトの監視の為にSageMaker Model Monitorなども検討しましたがサービスの特性上急にinput/outputの分布が変わったりすることもないし継続的にモデルを学習させているのでそのタイミングで性能劣化や分布に変化がないか確認すれば充分なので見送っています。

Development

開発はローカルマシンで行うことが多いですが、モデルの学習などコンピューティングリソースが必要な場合はGCPインスタンスも使います。 開発環境は基本的にすべてコンテナ化してローカルでもクラウドでも同じ環境で開発ができるようにしています。 パッケージ管理はpoetryを使い、linter/formatterにpysenを使っています。 実験管理はMLflow Trackingを使っています。GitHub issue, データセット, モデル等の成果物, メトリクスを一元管理して過去の実験をすぐに再現できるようにしています。

まとめ

機械学習の立ち上げ当初はモデルや推論APIの開発, バンドルカードへのインテグレーションまですべてひとりで開発していたので、パイプラインの整備までは手が回らず手動でEmbulkを実行して、SageMaker Notebookを使ってモデルを学習させて、、みたいな最低限の状態からスタートしてそこから少しづつ自動化したり改善を行って今のような構成になりました。まだまだ改善したいところはたくさんあります。エンジニア積極採用中です。

kanmu.co.jp

Twitterのフォロワーに一様分布を依頼してみた

はじめに

こんにちは、カンムでバンドルカードの機械学習部分を担当しているfkubotaです。先日、Twitterでこんな感じのアンケートを取ってみました。

f:id:kanmu-tech:20210528204802p:plain

このアンケートの面白いところは、回答者は回答しないと現在の投票成績が見えないというところです。参加者は、「1を取る人が多いハズ、、、であれば2,3,4か?」とか裏を取ろうとします。裏の裏を取ろうとする人もいるでしょう。我ながらTwitterの投票システムを利用した面白い実験だったと思います。

結果はこんな感じ。

951票もいただきました。ありがとうございます。m(_ _)m

そんでもって

もちろん、面白い実験をして、あー面白かったで終わらせるつもりはなく、結果を考察するまでがこの実験の目的です。ちなみにみなさんは、上の結果を見て一様分布だと思いますか? 「全ての数字が同じではないから一様分布ではないよ」という意見もあれば、「まあこの程度の違いなら誤差だと思うよ」という意見もあるでしょう。このあたりどう議論すればいいでしょうか? 多少の誤差とは言いますが、どこまでが許せてどこまでが許せないのでしょうか? やっぱり定量的に賢くやりたいですよね。こういったときに使えるのがいわゆる統計学です。いろいろ手段はあると思うのですが今回は、統計的仮説検定というやつを使ってみます。統計的仮説検定がよくわからない人は、「検定 サイコロ」とか「検定 コイン」とかでググると簡単な例が出てくるので目を通すと良いかもしれません。この記事では、できるだけ検定がわからない人にもわかるようにも書きますが、事前に勉強しておくとより読みやすいと思います。 一様分布っぽさがふわふわしていて定量的にあつかえないもどかしさは、検定という手続きをつかえば定量的に扱えるという事が伝えられたら嬉しいです。

適合度検定

適合度検定(解説はこの記事とか)というのを使います。この検定はある事象がa:b:c:dで起こる時に実験値とのズレが誤差の範囲内であるかどうかを検証する時に使います。今回の場合は、アンケートの結果が一様分布になるはずだという仮説1:1:1:1に対して、実験結果は%で25:18:26:30になっており、投票数は238:181:247:285となっています。これが誤差の範囲なのかそれ以上に意味のある結果なのかを検証します。

計算するにあたりこちらのサイトを利用しました。
今回の条件数は4、観測度数はそれぞれ、238, 181, 247, 285であり、帰無仮説(一様分布である)のもとでの確率は1/4, 1/4, 1/4, 1/4となっているので、そのように入力して計算しました。また、有意水準は5%としています。 結果は以下のようになりました。

統計検定量χ2 = 23.2965
有意水準に相当するχ2の値 = 7.81743
p値 = 0.000035025

適合度検定はχ2検定で片側検定ですので、統計検定量χ^2有意水準に相当するχ^2の値 を上回っていることにより帰無仮説は棄却されます。p値についてちょっと解説すると(怖い...) 、今回の「アンケート結果は一様分布に従う」という帰無仮説が正しいとした場合、今回の結果は、0.0035%程度の確率で起こるよということを示しています。一様分布が正しい分布の場合でも今回の実験結果は起こり得る、ただし「0.0035%という低い確率」でということになります。あまりにも起こる低い確率で起こるのであればそれは仮説が間違っていると考えるのが自然だよね。であれば、5%までは許して5%以下の確率で起こる場合は、仮説が間違っていると考えようということで、有意水準5%を設定しています。

今回の結果としては、仮説が棄却されたので、検定の手続き上ではアンケート結果はどうやら一様分布ではないようです。「一様分布にして」とお願いしたので、一様分布にしようと参加者がいろいろ戦略を立てて投票しその戦略が偏った(似たような戦略を取る人が多かった?)結果なのかなと考えてみたりしました。ただこれ以上のことは今回の検証では何も言えません。今回の適合度検定で言えることは、「一様分布ではなさそう」というところまでです。「適当に投票して」というアンケートだと結果は変わったのかな?と思ったりもしました。

ちなみにどの程度であれば棄却されなかったのかと数字を少し変えて遊んでみたところ、 238, 181, 247, 285238, 230, 247, 285 にしたところ棄却されませんでした。
僕には285の数字がまだ大きく見えて一様分布には見えないなぁと思うのですが、適合度検定にはこれが一様分布に見えるようです。黙って従います。

今回の実験の背景

今回、なぜこのような実験をしようと思ったのか少し話ます。
弊社カンムはバンドルカードというサービスを開発していて、日々新しいデータが大量に入ってきます。僕自身、機械学習を担当しているということもありあらゆるデータに日々触れています。その時、もっとこうしたら良いんじゃないか?とかこれはどう説明できるだろうかなど仮説を立てて検証をしたくなることがあります。しかし僕自身この辺りの知識・業務経験に乏しくて、うまく扱えず歯痒い思いをよくしています。目の前にこれだけ質の高いデータがあるのに自分の力不足のために活用できないのはもったいないなと思い、最近はもっぱら統計的因果推論とか言われる分野を勉強しています。そして、ただ本を読むだけだと退屈だということで第一弾として簡単にできる検定をやってみたという次第です。アンケートご協力頂いた方、とても楽しかったです。ありがとうございました。

次回は、もう少し複雑な統計学の知識を使った分析をやりたいと思います。

お決まりですが

上記にもあるように、もっともっと輝けるのに力を発揮できずに眠ってしまっているデータが弊社には大量にあります。そのデータを統計学などのデータ分析の知識を生かしてさばけるデータアナリストを募集しています。僕もたくさん勉強してこのあたりも少しずつできるようになりたい。
一緒にやりませんか?

https://kanmu.co.jp/jobs/data-analyst/

僕は、2021年2月に入社してからもうすぐ4ヶ月が経ちますがとても入社してよかったなと日々思います。
なによりデータ、テクノロジーに対するリテラシーが高くデータ分析をする身としては、非常に働きやすく何より楽しいです。
社内のほとんどの人がSQL書けちゃったりします(参考記事)。 もちろんエンジニア以外の人もです。
他には、月に一度の TechDayでは、エンジニア・非エンジニアが日頃の業務の非効率をテクノロジーで解決するというイベントがあります。非エンジニア同士がGASがどうだとかSlackのbotがどうだとか議論したりするんです。僕より遥かにGASに詳しい非エンジニアがかなりいます。これは少し悔しい。

上記は一例ですが、こんな感じで企業文化がマッチすれば、楽しく働けることは少なくとも僕は保証しますので興味ある方は、データアナリストに限らず応募してください!!:)

https://kanmu.co.jp/jobs/

サイバーセキュリティ攻撃を学ぶことで防御を学ぶ

カンムでセキュリティを担当している12banです。といっても、つい最近までコーポレートエンジニアをやっていました。 一応、セキュリティはいろんな業務の中の1つとして、10年以上関わってきました。

セキュリティと一言に言っても実はものすごく幅広いです。 そんな中でPenetration Testingと呼ばれるセキュリティテストの分野があります。 これは一般的にはセキュリティ会社のペンテスターと言われる職種の人が学習する分野なのですが、 事業会社で攻撃を防御することにも役に立つのではと考え、今回チャレンジしてみました。

TryHackMeとは

今回チャレンジしてみたのはTryHackMeというサービスです。 このサイトはペンテスト(侵入攻撃)を手を動かして学べるサービスです。

全部で5つのコースがあるのですが、面白そうだなって思ったのは、 Offencive Pentesting と Cyber Defenceのラーニングパスです。

f:id:kanmu-tech:20210518190308p:plain
TryHackMeのラーニングパス例

このうち、攻撃を学びたかったため、Offencive Pentestingのパスを選んでやってみました。 下記の画像がOffcencive Pentestingの序盤です。

f:id:kanmu-tech:20210518190432p:plain
Offencive Pentesting序盤

難易度Easyの4つの部屋があり、最初のTutorial以外の3つの部屋で実際に攻撃対象となるマシンがあり、 それを攻撃して、特権(管理者権限)を取っていきます。

このうちvulnversityという部屋の攻略について記事をここに書きたいところですが、かなり内容が専門的になる、 かつ、かなり長文になるため、個人のブログに書きました。興味がある方はそちらを読んでみてください。

note.com

パズルのようで面白い

現時点で難易度Easyを4つ、難易度Mediumを1つ終わらせています。かなり面白く毎日少しずつやってます。 まだちょっとの経験しかないですが、攻撃はパターン化されているなと感じました。 言うなれば高校数学の解法パターンみたいな感じでしょうか。 最終的にはOffencive Pentesting と Cyber Defenceの2つのラーニングパスを最後までやってみようと思っています。ここまでやりきればもっと見えてくるものはありそうだと感じてます。

ちなみに今まで経験したパターンは、だいたい下記の流れです。

  • nmapを使って空きポート、使ってるサービスやアプリケーション名、バージョンを調査する
  • 脆弱性などを突いて、対象マシンに侵入する
  • 侵入後、同様に脆弱性などを突いて特権を取る

学び

今のところ、脆弱性を突いて対象マシンに侵入することが多いので、脆弱性管理の重要性を肌感として感じてます。 今までは脆弱性管理は重要と知っていても、ちょっとぐらい大丈夫という気持ちがあったような気がします。 特に従業員が使用するデバイスの方はモチベーションが上がらなかったり...

しかし、今は脆弱性管理をきっちりやりたいって気持ちでいっぱいです。脆弱性をついた攻撃は自動化されてるので、 使ってるアプリケーションとバージョンがわかってしまえば、簡単に脆弱性をついて侵入できることを実感として知ることができました。

攻撃を学ぶことで防御を学ぶことができる、というのは正しかったようです。またセキュリティ対策を会社全体で実行する時に、攻撃を知っていることで納得感のある説明ができる気がします。

事業会社のセキュリティエンジニアの良さ

このようにペンテストの経験は攻撃者の手法を知り、防御に活かすという点で、事業会社のセキュリティエンジニアにも有用だと感じました。

ちなみにこの学びは誰かから強制してやったわけではありません。業務に活かすことができるのでは?と自分で考えて行動しただけです。 事業会社のセキュリティエンジニアが扱うセキュリティ範囲は幅広いです。PCI DSSなどの第三者認証、IdPやMDMなどのエンドポイントセキュリティ、IaaSセキュリティ、プロダクトセキュリティ、リスクマネジメント、規定や監査など幅広い。 そんな中で今までの経験を活かせるところは活かし、足りないところは自分で考えて学んで活かす。そして顧客や企業、ビジネス、従業員を守ることで競合優位性を高めていけるのは事業会社のセキュリティエンジニアならではかなと思います。

最後に

セキュリティエンジニアに限らず、下記のように他にもセキュリティに関われるポジションがあるので、 まずは気軽にカジュアル面談してみませんか?

新しい環境にdotfilesをワンライナーで反映させる

はじめに

こんにちは、カンムでバンドルカードの機械学習を担当しているfkubotaです。テックブログ初参加なので今回はライトな内容で投稿させていただきます。イケイケ機械学習記事はいずれ...

この記事では、最近ちょっとだけ改善した dotfiles の管理について紹介したいと思います。
dotfiles とは、ホームディレクトリ($HOME) 下にある、.vimrc, .bashrc などの設定ファイル群を指します。
最終的には、コマンド1つでこのdotfilesが新しい環境に反映されます
魔法のコマンド:

bash -c "$(curl -fsSL https://raw.githubusercontent.com/fkubota/dotfiles/master/install.sh)"

このコマンドでdotfilesが反映されると、
これが f:id:kanmu-tech:20210520111556p:plain

こうなります f:id:kanmu-tech:20210520111616p:plain

結論だけ見たい方は、方法紹介 から見ると良いと思います。

  • こんな人が想定読者
    • dotfiles 管理しなきゃと思っているけど、何もしてません...TT
    • dotfiles をまとめたリポジトリはあるよ。でも新しい環境に反映させるのは少し面倒...。

上記に該当する人に有用な記事になるように頑張って書きます。

参考記事

基本的にはこの記事を参考にしています。
この記事の作者ほどヘビーユーザではない&もう少し気軽に使いたいのでちょっとコードを変更しています。

dotfilesを管理するモチベーション

機械学習をやっている人は、新しい環境で作業を行うという経験が多いのではないでしょうか?
会社の計算サーバーにssh接続して使うとか、GCPAWSのマシンを借りるとか。その都度、そこで自分の開発環境を構築するはずです。僕の場合であれば、vim, tmux, fish のオレオレ設定を普段使っているので新しい環境でもそれを構築しています。

僕自身、以前はめちゃくちゃしんどい管理の仕方をしていました。
歴史としてはこんな感じです。

  1. 新しい環境で作業するたびにdotfilesをコピペ
    • この管理だとどの環境が最新状態なのかわからなくなります
  2. gitで管理して、シンボリックリンクを作る
  3. シンボリックリンクを行うシェルスクリプトを作成
    • これで大分楽になった
  4. git cloneからシンボリックリンク作成までを ワンライナー
    • NOW!!

方法紹介

まずは、dotfilesリポジトリgithubで作成してください。
ちなみに僕のリポジトリこちら

今回は簡単のために、.vimrc のみを管理対象とします。
dotfiles リポジトリ内に、.vimrc を作成します。

touch dotfiles/.vimrc

次に、シンボリックリンクを行うためのシェルスクリプトをdotfilies下に作成します。
ファイル名は、install.sh としてください。
install.sh の中身は以下のようにします。

DOT_DIR={dotfilesリポジトリをクローンするディレクトリ}

# clone
git clone https://github.com/{your account name}/dotfiles.git ${DOT_DIR}

# link
ln -sf $DOT_DIR/.vimrc ~/.vimrc

上記の {dotfilesリポジトリをクローンするディレクトリ} と {your account name} をそれぞれ適切な値に設定してください。
僕の場合であれば、

DOT_DIR="$HOME/Git/dotfiles"

# clone
git clone https://github.com/fkubota/dotfiles.git ${DOT_DIR}

# link
ln -sf $DOT_DIR/.vimrc ~/.vimrc

となります。これで準備完了です。
このスクリプトを実行すれば、DOT_DIR で指定したディレクリにdotfilesリポジトリがクローンされ、.vimrcシンボリックリンクされます。lnコマンドに-fオプションが指定されているので、すでにある場合に上書きされてしまうので困る場合は外してください。

あとは、ワンライナーを叩くだけ!! です!
どこのディレクトリ下でもいいので、

bash -c "$(curl -fsSL https://raw.githubusercontent.com/{your account name}/dotfiles/main/install.sh)"

と叩いてみてください。指定した場所にリポジトリがクローンされ、シンボリックリンクが、~/.vimrc に作成されるはずです。 (デフォルトブランチ名がmainではなく、masterである場合は、mainmaster に変更してください)

ホームディレクトリ下で

ls -la

などで確認できます。僕の場合は、

.vimrc -> /home/fkubota/Git/dotfiles/vim/.vimrc

と出力されていました。

以上が簡単な紹介です。これを基本として追加したい項目を増やしていくだけです。 僕の場合は、 vim, tmux, fish のdotfilesを管理する以外にも必要なものを追加しています。
後はお好みで楽しんでください。
dotfiles、育てるとかわいくなっていきますよ。

おわりに

今回の記事で弊社のテックブログとしては、3本目。
今後も続々と楽しい記事をあげていく予定です。
カンムに少しでも興味ある方はぜひこちらから応募してください!!!

採用 | 株式会社カンム

PCI DSS のセグメンテーションテストに VPC Reachability Analyzer を使う

カンムでバンドルカードのバックエンドやインフラを担当している summerwind です。

バンドルカードはスマホ上で Visa のプリペイドカードを発行して決済に使える機能を提供しているため、クレジットカードのデータを安全に取扱うことを目的として策定された、クレジットカード業界のセキュリティ基準である PCI DSS に準拠しています。

PCI DSS にはセキュリティ基準として様々な要件が定義されており、中には次のようなものがあります。

11.3.4.1 Additional requirement for service providers only: If segmentation is used, confirm PCI DSS scope by performing penetration testing on segmentation controls at least every six months and after any changes to segmentation controls/methods.

これは、ネットワークセグメンテーションを使用してカードデータを扱う環境を分離している場合は、少なくとも6ヶ月ごとにそのセグメンテーション制御に対するテストを行ない、意図した状態になっているかをテストすることが求めています。実際には、カードデータを扱わないシステムとカードデータを扱うシステムの間の通信が必要なプロトコルやポートに制限されていることをテストします。

これまでカンムでは Nmap などのツールを利用してこのネットワークセグメンテーションのテストを行なってきましたが、以下のような課題がありました。

  • テストの実行手順が明確に定義されていない
  • Nmap などのツールの実行には時間がかかる

もうちょっと簡単に素早くテストできないかと考えて見つけたのが、2020年末にリリースされた VPC Reachability Analyzer です。これは AWS VPC 内の2つのエンドポイント間で通信の到達性を検証してくれる機能で、まさにセグメンテーションテストの要件にぴったりです。

VPC Reachability Analyzer の実行は AWS コンソールから簡単に行えます。VPC Reachability Analyzer のページにアクセスしたら、Create and analyze path ボタンを押して、Path を作成します。Path はテストしたい2つのエンドポイント間の経路を示すものになるので、送信元と送信先および通信に使用するプロトコルを指定します。

f:id:kanmu-tech:20210511131831p:plain
Create and analyze path

Path を作成すると初回の検証が自動実行され、これが完了すると、到達性について結果が表示されます。Not reachable であれば到達不可であることが確認できたこととなり、セグメンテーションが有効であることが分かります。

f:id:kanmu-tech:20210511131909p:plain
Path

後日同じ経路で改めて検証を実行したい場合は、作成した Path の画面にある Analyze path ボタンを押すだけですぐに検証ができるので便利です。PCI DSS の要件 11.3.4 では開発者以外の第三者的な立場でこのテストを実行することを求めているので、そういった観点でも VPC Reachability Analyzer を活用するのがよいのではないかと考えています。

今回は PCI DSS のセグメンテーションテストに VPC Reachability Analyzer を活用する方法を紹介しました。今後も PCI DSS の要件を達成する方法などを紹介できればと思っています。

カンムではセキュアでモダンなクレジットカード決済インフラを作りたいエンジニアを積極的に採用しています!

GoCon CTF と呼ばれた問題の紹介と解説

こんにちは、バンドルカードを作っている Kanmu という会社で CTO をしています。kneeです。

昨日行われた Go Conference 2021 Spring にて Kanmu としてスポンサーをさせていただいており、そのオフィスアワーにて「CTF なんて言葉聞いたこともない、strings コマンドなんて知らない、でも Go は触ったことあるよ」という方に向けて Go を少し絡めたゆるい CTF の rev っぽい問題を出題させていただきました。
それなりにたくさんの方に楽しんでいただけたようですので、解説と合わせて出題者の意図の紹介をします。
ちなみに自分は CTF 超絶にわかですのでその点はご容赦ください。

まだ解いてないけど、解いてみたいという方はぜひ読み進める前に解いてみてください。
所要時間は解いていた方の様子を見るに5分~3時間程度です。

問題の紹介

問題はこれです https://github.com/kanmu/gocon2021_office_hour
ヒントや解説は時間経過に応じて追加していったものですので、出題当時は以下のような感じでした。

以下のイメージの中にGoのプログラムをビルドした実行ファイルがおいてあります。 これを調べて秘密の答えを見つけてください!

docker run -it ghcr.io/kanmu/gocon2021-office-hour:latest /bin/bash

実際に中を見てみるとこんな感じ。
問題文にある実行ファイルと MEMO がおいてあるだけ。そして実行してみるといきなり答えを聞いてくる、そんな感じの問題です。

$ docker run -it ghcr.io/kanmu/gocon2021-office-hour:latest /bin/bash
root@7a909bfa6f26:/go/src/app# ls
MEMO  gocon2021_office_hour
root@7a909bfa6f26:/go/src/app# ./gocon2021_office_hour
Enter your answer 答えを入力してください: 

想定解答と出題意図

想定解答の解説をしつつ出題意図を説明していきます。

まずノーヒントなので MEMO を見ます。 ちなみにこのイメージにはデフォルトで file コマンドすらなく、この時点でなんだこれとなられた方も多かったようです。

$ cat MEMO
何回失敗しても諦めないで!
Don't give up no matter how many times you fail!

煽ってきていますね。
しかし実際に諦めないで何度も試してみるとヒントが出るようになっています。

$ ./gocon2021_office_hour
Enter your answer 答えを入力してください: a
Failed 残念
Enter your answer 答えを入力してください: b
Failed 残念
Enter your answer 答えを入力してください: c
Failed 残念
Enter your answer 答えを入力してください: d
Failed 残念
Enter your answer 答えを入力してください: e
Failed 残念
Enter your answer 答えを入力してください: f
Failed 残念

You can user one of go tool command. Let's run `go tool`.
go tool ~~~ コマンドで使えるものがあるかも…? `go tool` を実行してみてください

You can usertypo です。

gocon2021_office_hour 自体を直接調べるという選択肢を持っていない場合、できることとしてぱっと思いつくのは、実行していろいろ入力してみる、他のファイルを探しに行くことぐらいだろうと想定していました。ただ後者は脱線して時間がかかりますし、前者はただの当てずっぽうになってしまう。
そういった状況で自然に gocon2021_office_hour 自体の解析に誘導する必要があり、何回も実行するだけならどんな前提知識の方もいけるだろうということでこういう形をとりました。 ただノーヒントでわからんとなって1,2回入力して離脱したら悲しいので諦めないでという MEMO を置くことにしました。

$ go tool
addr2line
asm
buildid
cgo
compile
cover
dist
doc
fix
link
nm
objdump
pack
pprof
test2json
trace
vet

ちょっと Go 要素が出てきました。ここが一番の難所です。go tool を使えとだけヒントが出ているので、どれが使えるのか自分で調べる必要があります。ほとんどの方は普段使ったことないコマンドもたくさんあると思うので調べたり試行錯誤することになります。 この過程で普段触れることのない Go に関する情報にあたってもらえたらいいなーというような気持ちがこもっています。

ちなみに正解は go tool nm で、これはシンボルを列挙するコマンドです。が、その意味を理解しておらずとも問題はありません。
また go tool foo gocon2021_office_hour と件の実行ファイルを引数に渡して人間に可読っぽい出力がされるのはおそらく nm ぐらいかと思います。というところでなんとか答えににじり寄っていってくれたらうれしいなという感じですかね。

$ go tool nm gocon2021_office_hour
  4df6f8 r $f64.3eb0000000000000
  4df700 r $f64.3f847ae147ae147b
  ...
  54efa0 D unicode.foldLu
  54efa8 D unicode.foldM
  54efb0 D unicode.foldMn
  46dd40 T unicode.init
  46d500 T unicode/utf8.DecodeRune
  46d700 T unicode/utf8.DecodeRuneInString
  46d900 T unicode/utf8.EncodeRune
  46da80 T unicode/utf8.RuneCount
  46dbe0 T unicode/utf8.RuneCountInString
  53ff40 D unicode/utf8.acceptRanges
  5430e0 D unicode/utf8.first

とはいえその出力は2000行以上になるので、その中から必要な情報を見つける必要があります。ちなみに見つけてほしい行はこれです。

  49bc60 T type..eq.github.com/kanmu/gocon_2021_kanmu.LetsRunThisCommand_strings_gocon2021_office_hour_grep_question

ここで想定していたのは2種類の行動で、1つ目は長すぎて読めないのでとりあえず関係ありそうな単語で grep してみること。

$ go tool nm gocon2021_office_hour | grep gocon
  540560 D github.com/kanmu/gocon_2021_kanmu..inittask
  49bb60 T github.com/kanmu/gocon_2021_kanmu.New
  49bc60 T type..eq.github.com/kanmu/gocon_2021_kanmu.LetsRunThisCommand_strings_gocon2021_office_hour_grep_question

もう一つはどうすればいいかわからないけど、とりあえず実行結果を下からスクロールして眺めていったら不自然に長い行に気づく、です。出力順序は go tool nm の実装的にオプションを渡さない限りは変わらないのでスクロールしていくと割とすぐ出てくるはずです。

  466aa0 T type..eq.[6]string
  466b60 T type..eq.[9]string
  49b0e0 T type..eq.fmt.fmt
  49bc60 T type..eq.github.com/kanmu/gocon_2021_kanmu.LetsRunThisCommand_strings_gocon2021_office_hour_grep_question
  401c20 T type..eq.internal/cpu.CacheLinePad
  401c40 T type..eq.internal/cpu.option

この go tool nm を実行して、grep するなどしてこの行をみつけるところは難所だろうと思っていたので、出題してしばらく経過した後にヒントを公開しました。

続きます。 見つかった行は以下です。

  49bc60 T type..eq.github.com/kanmu/gocon_2021_kanmu.LetsRunThisCommand_strings_gocon2021_office_hour_grep_question

Let's Run This Command とあり、これが続きます strings_gocon2021_office_hour_grep_question

この _ で繋がれた文字列をみて strings コマンドを知っているかどうかで動きが変わります。知っていたらたぶんこのように分解するのは自明そうです。

strings gocon2021_office_hour | grep question

とはいえ知っていたらここにたどり着く前にゴールしてそうな気もしますし、そもそも想定解答者として strings コマンドも知らない人を置いている。かつ、そういった人にもこういうときに使う基本的なコマンドを一つでも覚えて帰ってもらいたいと思っていました。

ここで辿ってくれたらいいなーと思っていた思考の過程は以下です。 grep を知ってるのは前提で、途中で grep があるのでなにかを grep してるんじゃないかと想像し | grep question をひねり出す。つづいて strings_gocon2021_office_hour という部分の分解においては、 gocon2021_office_hour が実行ファイル名、また出題のリポジトリ名であることで一つの単語であることを想起し、余った strings という謎の存在を最後もしかしてこういうコマンドがあるのでは!?とひらめいてググってみるなどして答えにたどり着く。 という感じでした。

ここもやはり strings コマンド知らないとなかなか難しかったようですが、実際にそのような方が最終的に strings コマンドに辿り着いておられるのを拝見して、めちゃくちゃテンションがあがりました。

そしてここまで来たらもうゴールは目の前です。見つけたコマンドを実行します。

$ strings gocon2021_office_hour | grep question
P*gocon_2021_kanmu.LetsRunThisCommand_strings_gocon2021_office_hour_grep_question
  *************  question: what is the international standard of credit card messaging? The answer is ISOxxxx  ***************  000102030405060708091011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798990w
type..eq.github.com/kanmu/gocon_2021_kanmu.LetsRunThisCommand_strings_gocon2021_office_hour_grep_question
github.com/kanmu/gocon_2021_kanmu.(*LetsRunThisCommand_strings_gocon2021_office_hour_grep_question).Run
type..eq.github.com/kanmu/gocon_2021_kanmu.LetsRunThisCommand_strings_gocon2021_office_hour_grep_question

明らかに怪しい部分があるので取り出してみると、問題がかいてあります。

*************  question: what is the international standard of credit card messaging? The answer is ISOxxxx  ***************

クレジットカードのメッセージングの国際標準が答えです。意味がわからなくても問題文をそのまま検索したらヒットします。

ということで答えは ISO8583 です。

ソースコード

使った Go のソースコードが気になるところだと思うので公開します。
直前に慌てて作問していてなぜかソースコードrm するという失態をおかしたのでそのままのソースコードは残っておらず思い出しながら書きました。おおまかな作りはあってるとおもいます。 真に知りたい方はデバッガで見るなどして補完ください。

package main

import (
    "bufio"
    "fmt"
    "io/ioutil"
    "os"
  
  "github.com/kanmu/gocon_2021_kanmu"
)

func m5(text string) string {
    hasher := md5.New()
    hasher.Write([]byte(text))
    return hex.EncodeToString(hasher.Sum(nil))
}

func main() {
    x := "*************  question: what is the international standard of credit card messaging? The answer is ISOxxxx  ***************"
    fmt.Fprintf(ioutil.Discard, x)
    y := gocon_2021_kanmu.New().Run()
    fmt.Fprintf(ioutil.Discard, y)

    answer := "bc9ad0a243f1c91268437fefb3beba6b"
    i := 0
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        if m5(scanner.Text()) == answer {
            fmt.Println("Success! 正解です!すごい、おめでとうございます!")
        } else {
            fmt.Println("Failed 残念")
            if i == 5 {
                fmt.Println("You can user one of go tool command. Let's run `go tool`.")
                fmt.Println("go tool ~~~ コマンドで使えるものがあるかも…? `go tool` を実行してみてください")
            }
            i += 1
        }
    }
    if err := scanner.Err(); err != nil {
        os.Exit(1)
    }
}
package gocon_2021_kanmu

import (
    "math/rand"
    "time"
)

type LetsRunThisCommand_strings_gocon2021_office_hour_grep_question struct {
    x string
    y int
}

func (h *LetsRunThisCommand_strings_gocon2021_office_hour_grep_question) Run() string {
    return h.x
}

func New() *LetsRunThisCommand_strings_gocon2021_office_hour_grep_question {
    rand.Seed(time.Now().UnixNano())
    y := rand.Int() + 1
    return &LetsRunThisCommand_strings_gocon2021_office_hour_grep_question{
        x: "x",
        y: y,
    }
}

おわりに

Twitter での反響を見るに想定していたよりとても多くの方に楽しんでいただけたようで大変うれしかったです。
次回機会があれば strings だけでは倒せない歯ごたえがある問題も用意しようと思うので、今回興味を持ってもらえた方はぜひ CTF に入門して倒せるように鍛えてきてください!

この記事は Kanmu のテックブログの一発目なのですが、まさかの CTF の解説になってしまいました。
今後 Kanmu での普段のプロダクト開発の様子もいろいろ紹介できればと思っているのでのでぜひお楽しみに!
採用 | 株式会社カンム

GoCon の運営の皆様も参加してくださった方々も本当に楽しい会をありがとうございました!
ではまた。