コネヒト開発者ブログ

コネヒト開発者ブログ

コネヒト株式会社は PHP Conference Japan 2024 にシルバースポンサーとして参加しました!当日の様子をご紹介します!

今年はシルバースポンサーとして協賛してブースを出展させて頂きました!本ブログではPHP Conference Japan 2024の当日の様子や感想などをご紹介していきたいと思います!

PHP Conference Japan 2024 とは

PHP Conference Japan 2024 は2024年12月22日(日)に開催された国内最大級のPHPイベントです!

phpcon.php.gr.jp

当日の様子

会場は昨年と同じく大田区産業プラザPiOで行われました。前回に続き、今回もコネヒトはスポンサーブースに出展させて頂きました。

ブースの内容はママリドリルとPHPの流行語調査アンケートです。ママリドリルやアンケートにお答え頂いた方には、ノベルティとしてママリオリジナルデザインのチロルチョコをお渡しさせて頂きました!

コネヒトのブースに来てくださった方々本当にありがとうございました!

ブースへの参加者を心待ちにしているコネヒトメンバー

PHPの流行語調査アンケート結果

今回は参加者の方々からPHPの流行語のアンケートを回答頂き、一時間ごとのアンケート結果をもとにワードクラウドを生成するという企画を実施させて頂きました!

結果はコネヒトの開発公式アカウントの方で投稿されていますので興味があればぜひご覧ください!下記は開催直後のワードクラウドです!

x.com

アンケートを見た時みなさんPHPの流行語!?というリアクションが多く、回答に悩まれていた方が多かったです!突然PHPの流行語と言われパッと思いつかない方もいらっしゃいましたが、みなさんなりのPHPの流行キーワードを考えてくださいました!

最終的には下記のような結果になりました!

最終的なアンケート結果から生成されたワードクラウド

特に目立ったキーワードはLaravel、PHP8、PHPStan、バージョンアップでした!

Laravelは文句なくフレームワークとしての人気の高さが伺えますね!PHP8はやはりPHP8.4がリリースされた影響が大きいでしょうか!新しく追加された機能であるプロパティフックもキーワードに入っているので新しいバージョンへの関心の高さは皆さんさすがですね!

またPHPStanはPHPStan2.0がリリースされたことが話題になった影響もありそうですね!その他のキーワードもPHP Conferenceで新たに得られた知見なども反映された結果になったのではないでしょうか!

改めてアンケートに回答頂いた参加者の皆様ご協力頂きありがとうございました!

感想

たくさんの参加者の皆様と交流することができ満足したカンファレンスとなりました!!コネヒトは今後もPHPコミュニティへの貢献を続けていきたい思います!

私自身こういったイベントには初参加でしたが社外の方々と交流する機会があるのはとてもいいなと思えたので今後はこういった活動に積極的に参加できたら良いなと思いました!

2024年のコネヒト開発組織を振り返る

こんにちは。CTOの永井(shnagai)です。

今年も残すところ数日。今回は、アドベントカレンダー最終日ということで、2024年のコネヒト開発組織における印象的な出来事を自分の視点で振り返っていこうと思います。

この記事は、コネヒト Advent Calendar 2024 - Adventarの25日目の記事です。

1. Aurora MySQL v3へのアップグレード (v2EOL対応)

今年対応されたチームも多いのではと思いますが、AWS Aurora MySQL 2系のEOL対応は、大きな挑戦でした。コネヒトでは昨年行ったv1からv2のアップグレード時からこのv3への備えをしていました。 v2→v3へのアップグレードはMySQL 5.7系相当からMySQL 8.0系相当へのアップグレードを意味しており、アプリケーションへの影響を最小限に抑えつつ、各チームが連携して大規模な移行をスムーズに事故なく実現することができました。 メンバーの入れ替わりもある中で、大規模メンテナンスをやりきるナレッジを開発組織にストックするという点を今回は強く意識した移行計画になっており、短期間でやりきったことで開発組織の底力が上がったと感じています。

詳しい内容を知りたい方は、アップグレードをリードしてくれた@sasashu が書いてくれた下記記事をご覧ください。

tech.connehito.com

2.データ基盤の整備とプロダクトでの活用

今年、データ基盤の整備に本格的に着手しました。これまでもプロダクトごとに散在していたデータをBigQueryに一元化することはしていたのですが、そこに手を入れもう一段進化させました。 Dataformを採用し、BigQueryのスケジュールクエリに頼っていた中間テーブル作成等のETL処理をコード化し、その流れからデータ基盤のモデリングを再設計しています。

使われない仕組みは意味がないということがツール導入時によく叫ばれますが、このデータ基盤はしっかりと使われる基盤となっています。 具体的には、プロダクトの主要指標をデイリーで通知する部分のデータソースになっていたり、新データ基盤で管理しているデータを使ったプロダクトの機能をリリースしたりとコネヒトの事業になくてはならない存在となりました。

データ専任のエンジニアがいない体制なので、優先度を調整しながらの進化になりますが、まだまだデータまわりの課題感は大きく、来年もより事業になくてはならないデータ基盤となるべく活用事例を増やしていければと思っています。

3. エンジニアが事業部へ

4月の組織変更で、エンジニアを事業部に直接配置する形に変更しました。これまでは、開発部というまとまりでエンジニアが所属しており、各チーム毎にPdMを事業とエンジニアリングの架け橋として配置するチーム構成にしていたのですが、より事業への解像度を上げ、スピード感のある開発を実現するための挑戦です。

こちらうまくいったのかは正直まだ評価をしていません。 というのも、組織に正解はないと思っているので、1年くらいのスパンで良い/悪い変化、事業側/開発側それぞれの観点でのFBを経て、より会社として勝てる形を考えて実行していければと考えています。

一つだけ、今時点で変えたことにも言及すると、コネヒトではエンジニアの目標制度として事業目標(プロダクト開発)と組織目標(技術的チャレンジや負債解消)という大きく2つの目標があるのですが、4月のタイミングで上期は組織目標をやめることにしました。 これは、選択と集中という意味が強く、新しい組織になり事業のことをよりインプットしたりコミュニケーションする時間が増えると予想し、そこに力をかけるのが今回の変更の成功につながると考えたからです。

しかし、やめたことで組織目標という枠があることが、エンジニアとしての技術研鑽やチームをまたいだ技術的な連携を生み出す良い契機になっていることが明るみになりました。EM陣で議論を重ねて最終的に、下期は復活させました。振り返ってみると、普段の開発での改善はもちろん各々の努力でできますが、チームで働くにあたり共通の目的をもって進むというのは大きなことを成すには必要だよなと改めて気付かされました。ちなみに組織目標は、Web、iOS、Android、インフラくらいのまとまりでそれぞれの領域ごとに日常的な改善が難しいくらいのレベル感の技術的なテーマを決め、半年スパンで取り組んでいます。

他にも、レポートラインに事業側/開発側どれくらいの比率で入るかも難しいポイントで、これも適宜見直しを行っています。

4. 全社向けに生成AIを中心とした推進プロジェクト[Run with Tech]

全社への生成AI浸透を図り従業員のスキルアップと新たな価値を生み出すためのプロジェクトにも力を入れ大きな躍進を見せた1年でもありました。 こちらは特設のアドベントカレンダーがありますので、よろしければご覧ください。

コネヒト生成AI Advent Calendar 2024 - Adventar

まとめ

ここには書ききれませんでしたが、他にも自治体向けの新規事業に向けた技術的な挑戦をしたチームがあったり、、ママリとしては初と言っていいくらいのUIの大きなリニューアルをしたりと大きなチャレンジができた2024年でした。

2025年は、コネヒトの開発組織として掲げているTech VisionのPhase2最後の年となります。「Beyond a Tech Company」を実現すべく、一人ひとりのエンジニアが楽しみながら仕事に向き合い、ビジネスやプロダクトがテクノロジーの力で成長しているその中心にいる開発組織になっていければと思っています。

コネヒトはPHP Conference Japan 2024に協賛します!

本日はPHP Conference Japan 2024の協賛するお知らせです!

コネヒトはPHP Conference Japan 2024に協賛いたします!

コネヒトではメインプロダクトである「ママリ」を始めとして開発のメイン言語としてPHPを活用しており、フレームワークとしてはCakePHPを採用しています。 そんなPHPを愛用しているコネヒトですがこの度PHP Conference2024のスポンサーとして協賛させていただくことになりました。

phpcon.php.gr.jp

スポンサーするにあたって、コネヒトは「人の生活になくてはならないものをつくる」というミッションを掲げているので、技術コミュニティについても同様に、サポートして一緒に盛り上げていくことができたら、と思っております。

イベント概要

  • 開催日 2024å¹´12月22日(日)
  • 場所 大田区産業プラザPiO
  • 主催 PHPカンファレンス 2024 実行委員会
  • 公式HP https://phpcon.php.gr.jp/2024/
  • タイムテーブル https://fortee.jp/phpcon-2024/timetable

ブースを出展します!

今年もコネヒトはブースを出します。

スタンプラリーも開催されますのでぜひコネヒトのブースにお立ち寄りください。 ノベルティや楽しい企画を用意してお待ちしております!

In App Purchaseの VerifyReceipt APIからApp Store Server APIに移行しました

はじめに

こんにちは! otukutun.bsky.social です。 今回はIn-App PurchaseのサーバーサイドAPIを、VerifyReceipt API から App Store Server API に移行した経験を共有できればと思います。

WWDC2023 で、VerifyReceipt APIのdeprecated化が発表され、 WWDC2024 では従来のStoreKit1がdeprecated化と宣言され、これまでのAPI群は「original API for In-App Purchase」に改名されました。まだ廃止日時などは明言されていないと思いますが、今後はApp Store Server APIやStoreKit 2に移行しなけばいけません。

弊社が提供している ママリプレミアム のサブスクリプションはiOSではIn-App Purchaseを使用し提供しています。今回はVerifyReceipt APIからApp Store Server APIに移行した際の手順や実装について解説していきます。主にPHPでの実装方法に焦点を当てています。

なお、この記事はコネヒトアドベントカレンダー 16日目の記事になります。

App Store Server APIの概要

App Store Server APIは、Appleが提供する新しい課金情報管理APIです。用途ごとに様々なAPIが提供され、購読情報だけでなく返金情報も取得できるようになりました。今回は GET /inApps/v1/subscriptions/{transactionId} を使用し、トランザクションIDをもとに最新の課金情報を取得することにしました。

PHPでの実装

JWSの取り扱い

App Store Server APIでは、レスポンスにJWS(JSON Web Signature)が返却されそれを検証することで購入情報の正当性を確認できるようになりました。AppleはPHP向けの公式ライブラリを提供しておらず、PHPでのJWT検証ライブラリとしてメジャーな firebase/php-jwt はx5c形式をサポートしていないため、PHP標準のOpenSSL 関数と組み合わせることで対応しました。

  1. firebase/php-jwt ライブラリを利用
    • JWTのデコードと検証を行います
  2. PHP標準の openssl ラッパー関数を活用
    • 証明書の有効性や署名の検証を行います

証明書の有効性の検証

Appleから返却されるJWSには、証明書情報(x5cフィールド)が含まれています。これを使用して、以下の手順で証明書の有効性を確認します。

  1. リーフ証明書、中間証明書、ルート証明書を検証
  2. 各証明書の情報(有効期限や発行者など)を検証
  3. JWSの検証

以下は具体的なコード例です。

1. リーフ証明書、中間証明書、ルート証明書を検証

<?php

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

function toPem($certificate): string {
    return join("\\n", [
        "-----BEGIN CERTIFICATE-----",
        trim(chunk_split($certificate, 64)),
        "-----END CERTIFICATE-----",
    ]);
}

// サンプルJWS(ChatGPTに生成してもらいました
$jws = 'eyJhbGciOiJFUzI1NiIsImtpZCI6IjEiLCJ0eXAiOiJKV1QiLCJ4NXQiOiJleGFtcGxleDVjIn0.eyJ0cmFuc2FjdGlvbl9pZCI6IjEwMDAwMDAxMjM0NTY3ODkiLCJvcmlnaW5hbF90cmFuc2FjdGlvbl9pZCI6IjEwMDAwMDAxMjM0NTY3ODkiLCJ3ZWJfb3JkZXJfbGluZV9pdGVtX2lkIjoiMTAwMDAwMDEyMzQ1Njc4OSIsInByb2R1Y3RfaWQiOiJjb20uZXhhbXBsZS5wcm9kdWN0Iiwic3Vic2NyaXB0aW9uX2dyb3VwX2lkZW50aWZpZXIiOiIxMjM0NTY3OCIsInB1cmNoYXNlX2RhdGUiOiIyMDI0LTExLTAxVDEyOjM0OjU2WiIsImV4cGlyZXNfZGF0ZSI6IjIwMjUtMTEtMDFUMTI6MzQ6NTZaIiwiaXNfaW5fYmlsbGluZ19yZXRyeV9wZXJpb2QiOmZhbHNlLCJlblZpcm9ubWVudCI6IlByb2R1Y3Rpb24ifQ.SIGxEcD9EXAMPLESIGNATURE';

// JWSのデコード
list($header64, $body64, $cryptob64) = explode('.', $jws);

// ヘッダー情報の取得
$headerText = JWT::urlsafeB64Decode($header64);
$header = JWT::jsonDecode($headerText);

// 証明書を取得してPEM形式に変換
$leafCertificate = toPem($header->x5c[0]);
$intermediateCertificate = toPem($header->x5c[1]);
$rootCertificate = toPem($header->x5c[2]);
// URLは適切なものを設定してください
$rootCertificateFromApple = toPem(JWT::urlsafeB64Encode(file_get_contents('APPLE_ROOT_G3_CERTIFICATE_URL')));

// リーフ証明書の検証
$result = openssl_x509_verify($leafCertificate, $intermediateCertificate);
if ($result !== 1) {
    throw new Exception('リーフ証明書の検証に失敗しました');
}

// 中間証明書の検証
$result = openssl_x509_verify($intermediateCertificate, $rootCertificate);
if ($result !== 1) {
    throw new Exception('中間証明書の検証に失敗しました');
}

// ルート証明書の検証
$result = openssl_x509_verify($rootCertificate, $rootCertificateFromApple);
if ($result !== 1) {
    throw new Exception('ルート証明書の検証に失敗しました');
}

リーフ証明書、中間証明書はJWSのheaderに付与されているのでそれらを使って検証します。PEM形式に変換し、openssl_x509_verifyを使えば検証できます。1なら有効、0なら無効なものになります。

ルート証明書の検証はルート証明書自体で行います。検証用の証明書はアップルのサイトからダウンロードできます。CER形式なので、PEM形式に変換する必要があります。

2. 各証明書の情報(有効期限や発行者など)を検証

<?php

// 各証明書情報の検証
$leafInfo = openssl_x509_parse($leafCertificate);
$intermediateInfo = openssl_x509_parse($intermediateCertificate);
$rootInfo = openssl_x509_parse($rootCertificate);

// 検証する情報は項目が多数あるのでここでは省略します

openssl_x509_parseを使って証明書の情報が取得できるので、各証明書の有効期限やOID、issuer情報などを検証すると良いと思います。有効期限はJWSに含まれているsignedDateを使って検証できます。

3. JWSの検証

<?php

$publicKey = openssl_pkey_get_public($leafCertificate);
$result = JWT::decode($jws, new Key($publicKey, $header->alg));
if (!$result) {
    throw new Exception('JWSの署名検証に失敗しました');
}

最後に、JWS自体の署名を検証します。これで検証が無事完了されれば、Transaction情報 を使って課金ステータスなどを更新すれば良いと思います。

おわりに

App Store Server APIは、従来のVerifyReceipt APIに比べてモダンな仕様であり、より強力な課金管理機能を提供されています。実装としてはライブラリが提供されていない言語では各自で対応しなければいけないことはいくつかありますが、PHPでも firebase/php-jwt とPHP標準のOpenSSL関数を使えば、対応は難しくないかなと感じました。またこの実装に加えて、OCSPやCRLなどのオンラインでの証明書期限切れの仕組みを導入することでより強固な検証プロセスを構築できると思います。

実際にご自身で実装する際にはApple提供のライブラリの実装やWWDCの動画を実際に見られることをお勧めします。ReveneuCat提供の記事はStoreKit 2について最初に理解するのにとても役立ちました。こちらが主に参考にした動画やページになります。

今回の記事が、同じ課題に直面している開発者の参考になれば幸いです!

やらないとどういった問題が起こるかという観点で SPF/DKIM/DMARC を理解する

本エントリは「コネヒト Advent Calendar 2024」14日目の記事です!

adventar.org

こんにちは。本年アドカレ2回目の登場となる @sasashuuu です。

先日 村営山中湖キャンプ場 に行ってまいりました。さすがにもう冬ということもありかなり寒さが厳しくなっておりましたが、雰囲気が良く富士山や山中湖の近さも相まって道中にも素敵な景色を楽しめる最高なキャンプ場でした...!

さて、本日はメール技術である SPF/DKIM/DMARC についての記事となります。

背景

Email sender guidelines に記載されているように Gmail のスパム規制が本格化している昨今ですね。

最近弊社内でも従業員から「xxx を使ったメールが届かなくなった」、「xxx を最後にメールが届いていない」という問い合わせが発生しておりました。

取り急ぎ現状復旧はできているものの、追われるがままの対応となっており、「そのそものメールの認証技術についてちゃんと理解できているのか?」とふと思い、このブログの執筆に至りました。

より理解を促すために、やらない場合どのような問題が発生するのかという観点で内容をまとめました。この情報が誰かのお役に立てば幸いです。

はじめに

SPF/DKIM/DMARC はメールの差出人から正規のメールサーバーを経由して受信者に届いたかを認証する技術です。これがないと不正なメールが送信者・受信者に届くリスクが高まります。それぞれの認証技術は単体で利用するのではなく組み合わせて利用することに意味があります。

各認証技術については追って説明していきます。

前提となる用語の基礎知識

SPF/DKIM/DMARC を理解するにあたり、整理しておいた方が良さそうなメール技術における用語を一部ピックアップして解説しておきます。

  • ヘッダー From
    • 差出人の情報です。メールヘッダー上で「From」と表記されています
    • メールを郵便物に模して「封筒の中の便箋に書かれている名前」と例えられることがあります
    • 容易に偽装することが可能です
  • エンベロープ From
    • こちらも差出人の情報です
    • メールを郵便物に模して「便箋が入った封筒に書かれている名前」と例えられることがあります
    • メール送信時に「エンベロープ From」という情報として送られますが、受信後は「Return-Path」という情報でメールヘッダーに追加されます(Return-Path の用途としてはバウンスメールなどの発生時の返送先として機能します)
    • ヘッダー From ほど容易には偽装できませんが、メールサーバーへログインした上でメールを送信することで偽装が可能です

SPF の認証

概要

メールが正当な送信元ドメインから送られたものであるかを検証する目的で用いられます。

なりすまし防止の目的で用いられます。

設定例

例として以下のように DNS において SPF レコードを登録します。RFC7208 に記載されていたサンプルです。

example.com.   IN TXT  "v=spf1 ip4:192.0.2.1 ip4:192.0.2.129 -all"

"v=spf1" ではバージョンを指定しています。

"ip4:x.x.x.x" では送信元として許可をするメールサーバーの ip アドレスを指定しています。

"-all" は指定されたもの以外からのメール送信を許可しないという設定です。この場合は "ip4:x.x.x.x" で指定されたものが許可対象ということになります。

このような設定によって、SPF は「エンベロープ From の情報」と「実際の送信元メールサーバーの ip アドレス」が一致しているかを検証しています。

上記は ip アドレスの指定というシンプルな例を紹介しましたが、他にも記法が色々とあるので RFC7208 を参考にしてください。

DKIM の認証

概要

公開鍵暗号方式を使った認証技術です。

メールが改ざんされていないかを検証する目的で用いられます。

設定例

例として以下のように DNS において DKIM レコードを登録します。RFC6376 を参考に組み立てたものです。

brisbane._domainkey.example.com. IN TXT (
    "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ"
    "KBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYt"
    "IxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v"
    "/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhi"
    "tdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB"
)

"brisbane" はセレクタと呼ばれる設定値で受信側のメールサーバーにて公開鍵を探すための識別となるものです。一意であれば任意の値で良いものですが、サンプルでは RFC に記載の文字列をそのまま引用しました。

"v=DKIM1" ではバージョンを指定しています。

重要となるのが "p=..." の部分です。公開鍵を Base64 でエンコードしたものです。ポイントとなるのは全文を1つの文字列として取り扱っていない点です。これは RFC1035 の仕様上、最大文字数制限(255 byte 以内)があるためです。

DMARC の認証

概要

DMARC は一言で表現しづらいですが、以下を担う認証技術です。

  • ヘッダー From を組み合わせた認証(SPF/DKIM で認証した送信元と一致しているか)
  • SPF ã‚„ DKIM の検証結果をもとに受信側でメールをどう処理するかを制御
  • SPF ã‚„ DKIM の検証結果をレポートとして通知

設定例

例として以下のように DNS において DMARC レコードを登録します。RFC7489 を参考に組み立てたものです。

_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:[email protected]"

"_dmarc" は DMARC を利用するにあたり必要となるサブドメインです。

"v=DMARC1" ではバージョンを指定しています。

"p=none" は適用ポリシーを指定しています。これが検証に失敗したメールをどうするかという制御を司る部分です。以下のような種類があります。

ポリシー 内容
none 何もしないことを意味します。DMARC の検証に失敗した旨の情報は追加されますが、通常通りメールは届きます。
quarantine 隔離を意味します。迷惑メールフォルダに振り分けられます。
reject 拒否を意味します。バウンスメールとして返送されます。

上記の例では RFC のサンプルの通り "none" を指定していますが、これだと受信側の制御としては何もしないため隔離や拒否の対応が必要な場合は "quarantine" や "reject" にする必要があります。

"rua=" は DMARC のレポートの報告するためのメールの送信先です。検証の結果がこのメールアドレス宛に届くようになります。

その他の仕様については、RFC7489 を参考にしてください。

SPF/DKIM/DMARC を適切に設定しないとどういった問題が起こるのか

SPF はなりすまし防止、DKIM は改ざん防止、そして DMARC は SPF と DKIM の検証結果をもとに検証を行う技術かつ SPF や DKIM で補えない脆弱性をカバーしているといった関係性があります。

したがって先述したように認証技術としては分かれているものの単体で利用するのではなく、組み合わせて利用することに意味があります。

ここからはさらに掘り下げて、SPF/DKIM/DMARC の3つを適切に設定をしないとどうなってしまうのかについて見ていきます。

ケース1:何も設定していない場合

言わずともがなで不正メールが送られ放題です...。

設定していないことで必ず不正メールが送られてしまうとは限りませんが、「容易に送ることができる環境を提供する」ことに繋がります。

ケース2:SPF 認証は設定済みで DMARC は未設定の場合(DKIM は不問)

まず1つめに「ヘッダー From は偽装しつつ本命のドメインの SPF の検証は pass しない状態でメールが届く」といったことが起こり得ます。

例えば SPF 設定済みの「[email protected]」を装い、SPF 未設定の「[email protected]」からメールを送信する例を考えます。

エンベロープ From は「[email protected]」とし、ヘッダー From は「[email protected]」とします。すると 「[email protected]」では SPF の検証は行われず検証結果が「spf=none」となり、結果的に不正なメールが届いてしまうことになります。

受信者側で届いたメールの検証結果のステータス(このケースでは「spf=none」)を見抜くことができれば怪しいメールだと気づけるかもしれませんが、毎回人力で目を通してチェックするのは大変です。よって DMARC による検証が必要です。

2つめに「ヘッダー From は偽装しつつ偽のドメインの SPF 認証を pass した状態でメールが届く」といったことも起こり得ます。

送信例は先ほどと同じですが、今度は「[email protected]」で SPF を設定済みにしておく場合を考えます。

すると SPF の検証結果が「spf=pass」となり、SPF 検証が pass した状態で不正なメールが届きます。

エンベロープ From は転送などの関係で必ずしもヘッダー From と一致するとは限らないため、受信者側では「正規のメールサーバーから送られてきたか」を判断するのは困難となります。よって DMARC による検証が必要です。

ケース3:DKIM は設定済みで DMARC は未設定の場合(SPF は不問)

まず1つめに「ヘッダー From は偽装しつつ偽のドメインの DKIM 検証を pass しない状態でメールが届く」といったことが起こり得ます。

詳細なフローは割愛しますが、DKIM における検証はざっくり以下のようなフローです。

  1. (送信側メールサーバー)公開鍵/秘密鍵ペアを生成
  2. (送信側メールサーバー)公開鍵は DNS に TXT レコードとして登録、秘密鍵は安全にメールサーバーで保持
  3. (送信側メールサーバー)送信時にメールの一部(ヘッダーや本文)をハッシュ化し、秘密鍵で署名を生成して DKIM-Signature ヘッダーとして追加
  4. (受信側メールサーバー)DNS から公開鍵を取得し、メールの内容から計算したハッシュ値と署名を照合して検証

この上で DKIM 設定済みの「[email protected]」を装い(ヘッダー From に「[email protected]」を指定)、DKIM 未設定の「[email protected]」からメールを送信する例を考えます。

「[email protected]」から送られたメールは DKIM の検証を行いませんので、DKIM の検証結果は「dkim=none」となった上で不正なメールが届きます。

SPF の時と同様、ヘッダー From を偽装してなりすました上で改ざんメールを送ることが可能ですし、受信者側で「DKIM の検証が pass しているか」と毎回確認するのも大変です。よって DMARC による検証が必要です。

2つ目に「ヘッダー From は偽装しつつ偽のドメインの DKIM 検証を pass した状態でメールが届く」といったことも起こり得ます。

「[email protected]」で DKIM を設定済みにしておく場合を考えます。

すると DKIM の検証結果が「dkim=pass」となり、DKIM 検証が pass した状態で不正なメールが届きます。

こちらも SPF の際と同様、受信者側では「正規のメールサーバーから送られてきたか」を判断するのは困難となります。よって DMARC による検証が必要です。

ケース4:DMARC のみ設定している場合

基本的に DMARC は SPF や DKIM の検証結果をもとに検証を行う技術ですので、意味がありません。SPF や DKIM の設定を行った上で DMARC の設定を行ってください。

参考

SPF/DKIM/DMARC に限ったことではありませんが、メールに関する技術仕様等を体系的に学ぶのには以下の書籍が参考になりました。プロトコルレベルの話からサーバー構築方法、本エントリで解説した SPF/DKIM/DMARC 等網羅的に解説されているのでおすすめです。

実務で使える メール技術の教科書 基本のしくみからプロトコル・サーバー構築・送信ドメイン認証・添付ファイル・暗号化・セキュリティ対策まで

また、その他 RFC では以下が該当するドキュメントとなりますので、詳細な技術仕様等はそちらを見てもらうのがよいかと思います。

おわりに

Gmail のスパム規制がきっかけとなりましたが、SPF、DKIM、DMARC という3つの認証技術の役割を深く理解することができました。 Saas 側の規制強化の意図やどうして認証技術が必要なのかといった部分の解像度が上がった気がします。 メールセキュリティを高めるための実践的な知識を得るとともに、今後のメール運用にも活かしていきたいと思います。

試行錯誤から見えた「個人の成長」と「目標管理」を結びつける試み


1. はじめに: 「個人の成長」と「目標管理」を結びつける試み

これはコネヒトアドベントカレンダー2024の11日目の記事です。

本記事は、キャリア形成や目標達成支援に悩むマネージャー を対象としています。チームメンバーの成長支援と組織の目標管理をどのように結びつけるかに課題を感じている方に向け、実際の運用事例として「個人のバックログ管理」を用いた取り組みを紹介します。

具体的には、以下のような課題を感じている方に役立つ内容です。

  • メンバーのキャリア志向が把握できていない
  • 成長の方向性が見えず、次に支援すべきステップが不明確
  • 日々の業務の中から成長実感を感じてもらうことが難しい
  • 組織の目標と個人のキャリア形成を結びつける運用例を知りたい

私はコネヒトで働くエンジニアリングマネージャーとして、組織の戦略と個人の成長を掛け合わせで、より大きな成果を生み出すことを重要なテーマと考えています。この問題に向き合うため、プロダクトバックログの考え方を取り入れ、目標管理と成長支援の両立を目指すアプローチを試行錯誤しています。

長期的なビジョンや中長期的なゴールを設定し、その達成を目指すバックログ管理 は、目標管理とキャリア形成を結びつけるうえで適していると判断しました。また、メンバーとキャリア形成を話す上で 個人を「プロダクト」として捉え、成長の方向性確認する ことで、遊び心を持ってキャリア志向を話せるようにしたかったことが理由の一つです。


2. 運用のプロセス: 個人の成長を支えるフレームワーク

プロダクトバックログ管理を個人の成長支援に応用するにあたり、長期・中期・短期の3つの視点 を以下の図のように整理をしました。この視点は、個人のキャリア形成と組織の目標管理を効果的に結びつける重要な要素です。

個人の成長を支えるプロダクトバックログ管理のフレームワーク

カテゴリ 定義 活用方法
プロダクトビジョン
(長期:3〜5年)
キャリアにおける長期的な方向性や理想の状態 定期的に中長期的なキャリア志向を可視化し、道筋を立てる
プロダクトゴール
(中期:6ヶ月〜1年)
ビジョンを実現するための中長期的な目標。インクリメンタルに進めるため、バックログを作る 半期の目標を立てるための判断材料にする
スプリントゴール
(短期:1ヶ月)
毎月設定する短期的な取り組みの目標 毎月のふりかえりで検査・適応を行い、業務上の成果と個人の成長を対話する

例えば、以下のようなアウトプットが作られるイメージです。*1

例
プロダクトビジョン 「技術力だけでなくリーダーシップも発揮できるエンジニアとして成長し、チームの成長を牽引できる存在になることを目指す」
プロダクトゴール 「次の1年で主要プロジェクトの技術リードを担当する2回経験し、リーダーシップを実践する」
スプリントゴール 「現状のアーキテクチャの課題をリストアップし、改善策を考える」

このフレームを元に、全体の流れとしては以下のような取り組みを進めています。

  1. コネヒト所属に依存しない、長期的なキャリア志向を考える
  2. コネヒト所属を踏まえて、マネージャーと中期的な目標を設定する
  3. 1ヶ月ごとの短期ゴールを設定し、マネージャーと検査・適応をする

2.のステップを丁寧に進めることで、会社の求める成果と個人の成長を繋げるように仕掛けています。


自己分析を行い、ビジョン/ゴールを決める

メンバーには、まずは個人のプロダクトビジョンを埋めることから始めてもらいました。 以下のような設問を用意し、自己分析を促すことで、長期的なキャリア志向を考えるきっかけを作りました。 ここではコネヒト所属、有無関係なく自分のキャリアについて考えることを強調しています。

  • 過去の振り返り
    • 過去数年間でどんな経験をして、どんな成長をしたか?逆にやり残したことは何か?
    • 自分の強み、特徴は何か?逆に苦手なことや過去のフィードバックで印象に残っていることは?
    • 人生や仕事上で大事にしていることは?
  • 長期的な目標の設定
    • バックキャスティングで考える
      1. 2~5年のうちに成し遂げたいことは何か?
      2. ワクワクすることは何か?
      3. それはなぜ?
    • フォアキャスティングで考える
      1. 過去の棚卸しを見て、2~5年のうちにどんな価値が生み出せそう?
      2. ロールモデルや憧れる人は?
      3. それはなぜ?

個人のプロダクトビジョンを設定した上で、会社の目標達成との掛け算になるプロダクトゴールを設定します。 もちろん、必ずしもプロダクトビジョンに直結する目標を置けるわけではないですが、キャリア志向を把握しておくことで、今後の機会提供の方法を考えられるようになっています。

このフレームワークで、半期ごとの成果を単なる「点」としてではなく、「中期的な視点」を踏まえて「線のコミュニケーション」が取りやすくなりました。 このプロダクトゴールを設定したうえで、1ヶ月で目指すスプリントゴールを決め、1on1や振り返りを行なっています。

3. 振り返りの仕組み: 成長に気づく対話の工夫

1on1の進め方

1on1では、スプリントゴールへの進捗度合いを、信号機に例えて確認しています。具体的には以下のテンプレートを進めています。 各色の意味合いはあえて明確に決めず、メンバーに自分で考えてもらい、なぜその色なのかを説明してもらうことで、課題の理解に努めています。

# **yyyy/MM/dd**

---

## 信号の色はいかがですか?

赤、黄色、青、もうすぐ変わりそうなど教えてください!

## 今日特にテーマはありますか?

- [ ]  仕事のパフォーマンス!
- [ ]  ワークライフバランス!
- [ ]  キャリア・転職・独立!
- [ ]  ぽじねの意見を聞きたい!
- [ ]  ゆるく話したい!
- [ ]  その他なんでも!

## 特に話したいこと

- 

## うまくいっている/いってないこと

-

信号機の色を通じてコミュニケーションをとることで、アイスブレイクも兼ねて課題をはなしてもらうきっかけになっています。


振り返りの視点

月に一度のふりかえりでは、スプリントゴールを軸に以下の視点でふりかえりをしています。

1. タスクの進め方の工夫

  • 今月、自分から進んで取り組んだ新しいタスクや役割はありましたか?
  • その業務範囲の拡大がチームやプロジェクトにどのような影響を与えましたか?

2. 仕事の認知の工夫

  • 今月直面した課題や困難は何でしたか?
  • それにどう向き合い、成長のチャンスに変える工夫をしましたか?

3. 周りのメンバーとの関係性の工夫

  • クライアントやチームメンバーとの関係をどう改善・強化しましたか?
  • 来月は誰とのコミュニケーションを強化し、どのような新しい連携を築きたいと考えていますか?

この視点で振り返ると、日々の業務が「やったことの積み重ね」ではなく、自ら工夫して得た 「成長のプロセス」 として捉えられるようになりました。 どの領域(タスク、認知、関係性)で、どのような工夫をするかを対話し、次のアクションを明確にすることができるようになっています。

4. 成果と変化: 点から線へのコミュニケーションへ

個人のプロダクトバックログ管理を取り入れたことで、仕事が断片的な「点」として扱われることが多かったのですが、今ではビジョン達成への成長のプロセスも踏まえて「線」として見えるようになりました。

これは、毎月のスプリントゴールの進捗確認に加え、成果がプロダクトゴール(半年〜1年)やプロダクトビジョン(2〜5年)にどうつながっているかを話し合うようになったことが大きな要因です。

「今取り組んでいること」が「これからの成長」にどう結びつくのかを意識する対話が増え、業務が単発のタスク処理ではなく、キャリア形成に向けた積み重ねとして見えるようになりました。

5. 今後の課題と改善点: プロダクトバックログの運用を継続するために

この取り組みを2ヶ月程継続し、以下の課題が見えてきました。

  1. 一つのことに集中するため、やったことを網羅的に振り返ることが難しい
  2. マネージャーとメンバーで閉じているため、他のメンバーに意図が伝わらない

スプリントゴールに集中しながらも、月次の振り返りではゴール以外の取り組みにも目を向ける工夫や、他のメンバーとの共有を意識した工夫について模索をしています。 特にマネージャーからも支援を受けられる状態を作れれば、より効果的に業務的な成果と成長支援を実現できると考えているため、今後の課題として取り組んでいきます。

最後までお読みいただき、ありがとうございました!

*1:この例は架空のエンジニアを元にChatGPTに生成してもらいました。

(Google Cloud 向け)Terramate と Workload Identity 連携で始める楽でセキュアな GitHub Actions の構築

本エントリは「コネヒト Advent Calendar 2024」9日目の記事です!

adventar.org

こんにちは。ついこの前引っ越しをしたのですが、自宅から富士山が眺められることに最近気づきテンションが上がっている @sasashuuu です。 本日は以前行った CI/CD 構築 についてのブログを発信します。

背景

弊社のインフラに関しては主に AWS を使用していますが、データ基盤等のシステムは Google Cloud で管理するなどマルチクラウドの構成を取っています。

AWS に関しては IaC 管理のための基盤が存在しているものの、Google Cloud には手が回っておらずほぼ管理できていない状態でした。

そこで IaC のオーケストレーションツールである Terramate や OIDC などの認証を実現するための Google Cloud の Workload Identity 連携を使用し、Google Cloud 側の IaC 管理基盤および CI/CD を GitHub Actions にてゼロベースで構築しました。

本エントリでは、特に CI/CD にフォーカスする形で内容を発信しようと思います。構築方法や使って見た所感などをつらつらとまとめる形にはなりますが、技術スタックとしてこういったツールやサービスでの構成があるという引き出しにつながる一助になれば幸いです。

Terramate とは

IaC 管理のためのオーケストレーションツールです。Terraform、OpenTofu、Terragrunt を管理対象に「スタック」と呼ばれる独自の単位(Terraform であればリソースそのものや設定を管理する *.tf ファイルや state などの組み合わせもとに構成される単位)をもとに、それらをオーケストレーションすることができます。具体のユースケースについては後述しますが、例として以下のようなことが可能です。

  • スタックに対して一括でのコマンド実行
  • ファイルの変更があったスタックのみを対象とするコマンド実行

terramate-io/terramate

Workload Identity 連携とは

従来のやり方であるサービスアカウント(サービスアカウントキー)を使った認証と異なり、ID フェデレーションを使用したセキュアな認証方法です。 この方法により、クレデンシャル情報の「管理コスト」や「流出によるセキュリティリスク」を減らすことが可能です。本エントリでは GitHub Actions から Google Cloud へアクセスする際の OIDC 認証のために使用します。

Workload Identity 連携

Terramate の導入

始めにCI/CD を組み込んだ対象リポジトリの全体像を書いておくと以下のようなイメージです。

GitHub Actions 用ワークフローファイルは .github/workflows に、config.tm.hcl を除く Terramate や Terraform 関連のファイルはプロジェクトごとのディレクトリ(例:projectA)に作成しています。

.
├── .github
│   └── workflows
│       ├── projectA-pull-request.yaml
│       ├── projectA-push-tag.yaml
│       ├── template-pull-request.yaml
│       └── template-push-tag.yaml
├── config.tm.hcl
├── projectA
│   └── terraform
│       ├── backend.tf
│       ├── iam.tf
│       ├── monitoring.tf
│       ├── provider.tf
│       ├── stack.tm.hcl
│       └── version.tf
└── projectB
...

Terramate においてポイントとなるのは、次の2ファイルです。

config.tm.hcl - Terramate で自動的に生成するための各種 tf ファイル(backend.tf、provider.tf、version.tf 等)の内容を定義している。

stack.tm.hcl - Terramate によって自動生成されるもので、スタックの管理に利用されます。基本的に手動で編集することはないファイル。

config.tm.hcl の内容

globals {
  terraform_version = "x.x.x"
  provider_version = "x.x.x"
}

generate_hcl "backend.tf" {
  content {
    terraform {
      backend "gcs" {
        bucket = "projectA-tfstate"
          prefix = "hoge/terraform/${terramate.stack.tags[0]}"
      }
    }
  }
}

generate_hcl "provider.tf" {
  content {
    provider "google" {
      project = terramate.stack.tags[0]
      }
  }
}

generate_hcl "version.tf" {
  content {
    terraform {
      required_version = global.terraform_version
        required_providers {
          google = {
            source  = "hashicorp/google"
            version = global.provider_version
          }
        }
    }
  }
}

globals は変数、generate_hcl はファイル生成のための block といった具合です。

stack.tm.hcl は Terramate によって自動生成されるもので、スタックの管理に利用されます。基本的に手動で編集することはないファイルです。

stack.tm.hcl の内容

stack {
  name        = "terraform"
  description = "terraform"
  tags        = ["projectA"]
  id          = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

tags は 後に解説しますが、terramate コマンドで特定のスタックを対象に実行する際に便利な機能です。

ここからは実際の導入方法について解説します。今回は何もない状態のリポジトリで一から作成するパターンとしています。

まずは対象のディレクトリで git init を実行します。

git init

前述した config.tm.hcl を作成します(※内容は前述した定義を参照)。

.
└── config.tm.hcl

この状態で terramate create を実行します。

terramate create projectA/terraform --tags=projectA
Created stack /projectA/terraform

以下のように stack.tm.hcl 含め関連ファイルが自動生成されます。

.
├── projectA
│   └── terraform
│       ├── backend.tf
│       ├── provider.tf
│       ├── stack.tm.hcl
│       └── version.tf
└── config.tm.hcl

それぞれ出来上がったファイルの中身は以下のような内容です。補足ですが、state ファイルの保存場所 は Cloud Strage を指定しています。terraform の実行前にはあらかじめ Cloud Storage に state ファイルを管理するためのバケットは作成しておいてください。

backend.tf

// TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT

terraform {
  backend "gcs" {
    bucket = "projectA-tfstate"
    prefix = "hoge/terraform/projectA"
  }
}

provider.tf

// TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT

provider "google" {
  project = "projectA"
}

version.tf

// TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT

terraform {
  required_version = "x.x.x"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "x.x.x"
    }
  }
}

上記で生成されたファイルは基本的には手動で編集することはなく、試しにファイル内を見てもらうとわかるように「DO NOT EDIT」の定義が存在しています。

// TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT

...

あらかじめ Terraform の lock ファイルの生成も行っておきます。stack を作成したディレクトリに作られるよう実行します。

terraform -chdir=projectA/terraform providers lock \
    -platform=linux_amd64 \
    -platform=linux_arm64 \
    -platform=darwin_amd64 \
    -platform=darwin_arm64 \
    -platform=windows_amd64

後に実行する terramate run について、git 上でコミットしていないファイルの変更があると実行できないという制約があるためこの対応を行なっています。GitHub Actions 上のホステッドランナーで実行した terraform init により lock ファイルに変更がかかり、ファイル差分が発生したまま後続する terraform plan が実行できないという事態を防ぐ目的で行います(また、幅広いプラットフォームへの対応も兼ね上記のように実行)。

ここまでで生成された各種ファイルは一度コミットしておいてください(前述したようにコミットしていないファイルがあると terramate run が実行できないためです)。

その後はスタックがある階層(ここでは projectA/terraform 配下)に ec2.tf 等などのリソース作成用の terraform 定義を記載した tf ファイルを配置していけば、terraform 側の準備は完了です。

続いて GitHub Actions 側の実装を見ていきます。

再度 GitHub Actions に関するファイルが置かれているディレクトリ構成を見ておくと以下のようになっております。

├── .github
│   └── workflows
│       ├── projectA-pull-request.yaml
│       ├── projectA-push-tag.yaml
│       ├── template-pull-request.yaml
│       └── template-push-tag.yaml

ざっくりとしたファイルの役割について触れておくと以下のようになっています。

  • template-pull-request.yaml
  • projectA-pull-request.yaml
    • 再利用可能なワークフローのテンプレート(template-pull-request.yaml)を呼び出すファイル(※今回の例ではプロジェクトごとに作成)
  • template-push-tag.yaml
    • template-pull-request.yaml 同様に再利用可能なワークフローのテンプレートファイル
    • tags のイベントで terraform apply が実行される
  • projectA-push-tag.yaml
    • 再利用可能なワークフローのテンプレート(template-push-tag.yaml)を呼び出すファイル(※今回の例ではプロジェクトごとに作成)

中身を見ていきます。まずはテンプレートとなる template-pull-request.yaml です。

on:
  workflow_call:
    inputs:
      workload_identity_provider:
        description: '使用する Workload Identity Provider'
        required: true
        type: string
      service_account:
        description: 'Workload Identity 経由で認証するサービスアカウント'
        required: true
        type: string
      terraform_version:
        description: 'Terraform のバージョン'
        required: true
        type: string
      tag_name:
        description: 'Terramate で使うタグ名(Google Cloud のプロジェクト名)'
        required: true
        type: string

permissions:
  id-token: write
  contents: read
  pull-requests: read
  checks: read

jobs:
  terraform-plan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Terramate
        uses: terramate-io/terramate-action@v2

      - name: Set up Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          # terraform の action を利用する場合は wrapper を無効化する必要があるため false に設定
          # ref. https://terramate.io/docs/cli/automation/github-actions/#:~:text=To%20install%20Terraform%20using%20the%20hashicorp/setup%2Dterraform%20GitHub%20Action%2C%20you%20must%20disable%20the%20included%20wrapper.
          terraform_wrapper: false  # Terramate 経由 で Terraform を実行するためラッパーを無効化
          terraform_version: ${{ inputs.terraform_version }}

      - name: 'Authenticate to Google Cloud'
        uses: 'google-github-actions/auth@v1'
        with:
          workload_identity_provider: ${{ inputs.workload_identity_provider }}
          service_account: ${{ inputs.service_account }}

      - name: Initialize Terraform
        run: terramate run --tags=${{ inputs.tag_name }} -- terraform init

      - name: Plan Terraform
        run: terramate run --tags=${{ inputs.tag_name }} --changed -- terraform plan

ポイントは以下です。

  • terraform_wrapper の無効化
  • google-github-actions/auth の利用
  • terramate run の実行
  • terraform_wrapper の無効化

Terramate のドキュメントにも記載がありますが、HashiCorp の Terraform Setup GitHub Action を使う場合は terraform_wrapper を無効化する必要があります。

terraform_wrapper: false  # Terramate 経由 で Terraform を実行するためラッパーを無効化

Terramate - Automating Terramate in GitHub Actions

  • google-github-actions/auth の利用

google-github-actions/auth という専用のアクションを使っています。 workload_identity_provider と service_account は inputs 経由で渡していますが、後述する Workload Identity 連携のために作成したプロバイダーとサービスアカウントを指定する必要があります。

workload_identity_provider: ${{ inputs.workload_identity_provider }}
service_account: ${{ inputs.service_account }}
  • terramate run の実行

terramate run は Terramate を利用する上でキモとなるコマンド実行です。

- name: Plan Terraform
  run: terramate run --tags=${{ inputs.tag_name }} --changed -- terraform plan

基本的には terramate run -- <実行したいコマンド> で管理している全スタックに対して、一括で<実行したいコマンド>が実行されるという仕様です。ここでは併せて --tags と --changed のオプションをつけています。--tags は実行対象となるスタックを制限するためのオプション、--changed は git をもとにファイルの変更のあったスタックのみを対象に実行してくれるオプションです。

ここで紹介しているオプションや terramate コマンドの使い方はほんの一部に過ぎず他にも便利な機能(parallel 等)があるので、詳しくは公式のドキュメントをご参照ください。

Terramate - Orchestration

そして再利用可能なワークフローの呼び出し側である projectA-pull-request.yaml は以下のような内容です。

name: Create Pull Request

on:
  pull_request:

jobs:
  terraform-plan:
    uses: ./.github/workflows/template-pull-request.yaml
    with:
      workload_identity_provider: '<後ほど作成する Workload Identity プロバイダー名>'
      service_account: '<後ほど作成するサービスアカウント名>'
      terraform_version: 'x.x.x'
      tag_name: 'projectA'

基本的にはテンプレートの呼び出しやテンプレートへの変数受け渡しを定義しているような内容です。

template-push-tag.yaml や projectA-push-tag.yaml の内容も一応記載しておきますが、基本的には同じ要領で実装しているため解説は割愛します。

template-push-tag.yaml

on:
  workflow_call:
    inputs:
      workload_identity_provider:
        description: '使用する Workload Identity Provider'
        required: true
        type: string
      service_account:
        description: 'Workload Identity 経由で認証するサービスアカウント'
        required: true
        type: string
      terraform_version:
        description: 'Terraform のバージョン'
        required: true
        type: string
      tag_name:
        description: 'Terramate で使うタグ名(Google Cloud のプロジェクト名)'
        required: true
        type: string

permissions:
  id-token: write
  contents: read
  pull-requests: read
  checks: read

jobs:
  terraform-apply:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Terramate
        uses: terramate-io/terramate-action@v2

      - name: Set up Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          # terraform の action を利用する場合は wrapper を無効化する必要があるため false に設定
          # ref. https://terramate.io/docs/cli/automation/github-actions/#:~:text=To%20install%20Terraform%20using%20the%20hashicorp/setup%2Dterraform%20GitHub%20Action%2C%20you%20must%20disable%20the%20included%20wrapper.
          terraform_wrapper: false
          terraform_version: ${{ inputs.terraform_version }}

      - name: 'Authenticate to Google Cloud'
        uses: 'google-github-actions/auth@v1'
        with:
          workload_identity_provider: ${{ inputs.workload_identity_provider }}
          service_account: ${{ inputs.service_account }}

      - name: Initialize Terraform
        run: terramate run --tags=${{ inputs.tag_name }} -- terraform init

      - name: Apply Terraform
        run: terramate run --tags=${{ inputs.tag_name }} --changed -- terraform apply -auto-approve

projectA-push-tag.yaml

name: Push Tag

on:
  push:
    tags:
      - "*"

jobs:
  terraform-apply:
    uses: ./.github/workflows/template-push-tag.yaml
    with:
      workload_identity_provider: '<後ほど作成する Workload Identity プロバイダー名>'
      service_account: '<後ほど作成するサービスアカウント名>'
      terraform_version: 'x.x.x'
      tag_name: 'projectA'

Workload Identity 連携用リソースの構築

続いて Workload Identity 連携用リソースを構築していきます。

まず Workload Identity 連携で使用するサービスアカウントを作成しておいてください。 サービスアカウントキーは使用しませんが、Workload Identity 連携を経由して Google Cloud リソースを操作するための IAM そのものは必要となります(また、1つ注意として roles/iam.workloadIdentityUser のロールを持つサービスアカウントが必要となりますで必要権限も付与しておいてください)。

Workload Identity 連携のコアとなるリソースを作成していきます。

Google Cloud Console にログインし、「IAM と管理」>「Workload Identity 連携」へ移動します。

プロバイダの追加・プールの作成をしていきます。

設定項目を見ていきます。

名前やプールについてはなんでも良いです。ここでは「github」としておきます。

プロバイダ設定は以下のように行います。

  • プロバイダの選択:OpenID Connect(OIDC)
  • プロバイダ名:任意のもの
  • 発行元 URL:https://token.actions.githubusercontent.com

次に OIDC プロバイダーから送られるトークンを Google Cloud 内で扱えるようにするためのマッピングを行います。今回は特定のリポジトリからのみのアクセスに限定するため、repository 情報が扱えるようなマッピングにします。

Google側 OIDC側(GitHub)
google.subject assertion.sub
attribute.repository assertion.repository

google.subject は必須設定という仕様のため、assertion.sub でマッピングしています。

attribute.repository は assertion.repository でマッピングし、後述する特定のリポジトリアクセスの制御に使います。

条件 CEL は マッピングした属性値を使う形で以下のようにします(OWNER や REPO の値は書き換えてください)。CEL という 言語を使用します。

assertion.repository == "<OWNER>/<REPO>"

マッピングをしているので、CEL では attribute を使用すると思いきやここでは assertion を使用することになるため注意が必要です(と言いつつ attribute でも問題ないのかは試していません)。

マッピングに関するドキュメントは以下が参考になります。

Google Cloud - Workload Identity 連携

OpenID Connect を使ったセキュリティ強化について

上記設定でプロバイダの追加とプールの作成を進めてください。

その後、プールで使用するサービスアカウントの紐付けを行います(プールの画面に戻ると「アクセスを許可」があるのでそちらから設定します)。

使用するサービスアカウントを選択し、マッピングした属性を使う形でサービスアカウントへの制御もかけます。ここでは attribute を使用しています(こちらでも OWNER や REPO の値は書き換えてください)。

ここまでの手順を終えたら、Pull Request の更新や tag の push で GitHub Actions の CI/CD が動くようになっているはずです。

感想

Terramate は簡単にコマンドの一括実行や差分検出によるフィルターなど Terraform のオーケストレーションが行えるため重宝しそうです(前述したように OpenTofu、Terragrunt なども)。変更があったファイルを対象に Terraform を実行するという制御のために独自のスクリプトなどを実装する必要もなく、オプション1つで制御できるのは大きな魅力です。導入も簡単で CI/CD もシンプルな構成になるので、とても良いなと思っています。

弊社においては正直なところ Google Cloud 向けの IaC 管理基盤は導入したばかりで、まだまだ IaC 化自体が推進できていないため Terramate の大きな恩恵はそれほど実感できていませんが、今後の IaC 化の推進でさらに役立ってくれそうな気配を感じています(まだ利用していない parallel やその他機能など)。

また、Workload Identity 連携についてもサービスアカウントキーの管理が発生しないというのはセキュリティ面で大きな魅力です。専用の action も存在するので CI/CD への組み込みも容易でした。

おわりに

今回は Terramate と Workload Identity 連携を使った GitHub Actions の CI/CD の構築方法について書きました。とても便利でセキュアですので良ければみなさんも候補の1つに入れてみてください。