Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 0 additions & 123 deletions .github/workflows/expiry.yml

This file was deleted.

50 changes: 50 additions & 0 deletions .github/workflows/pulumi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# main からしか使えない)
# 毎週月曜 → preview --refresh --expect-no-changes。Cloudflare 側を直接いじったら落ちる。
# Pulumi で表せない残り(カスタムドメイン・容量)も pulumi/report.ts で見る
# 鍵の期限も見る(pulumi/expiry.sh。30 日を切ったら Matrix の部屋 server に投稿)
#
# 書き込みの鍵は R2 の設定を書き換えられる(= バックアップのバケットを消せる)。
# 守りは 2 つ: Environment で main に限ること、doany-restic に `protect` が付いていること
Expand All @@ -17,6 +18,7 @@
# PREVIEW_CLOUDFLARE_API_TOKEN / PREVIEW_AWS_ACCESS_KEY_ID / PREVIEW_AWS_SECRET_ACCESS_KEY
# R2 の設定の読み取り + doany-pulumi の読み書き(state のロックに要る)
# PREVIEW_NB_PAT NetBird の読み取り (サービスユーザー gitops-pulumi-preview、Auditor)
# MATRIX_SERVER_WEBHOOK_URL 鍵の期限の投稿先(expiry)
# Environment `pulumi-apply` の秘密:
# CLOUDFLARE_API_TOKEN / AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
# R2 の設定の書き込み + doany-pulumi の読み書き
Expand Down Expand Up @@ -45,6 +47,11 @@ on:
schedule:
- cron: "0 4 * * 1"
workflow_dispatch:
inputs:
report:
description: 鍵の期限を、遠いものも含めて全部投稿する (expiry の動作確認用)
type: boolean
default: false

permissions:
contents: read
Expand Down Expand Up @@ -173,3 +180,46 @@ jobs:
cloud-url: ${{ secrets.PULUMI_BACKEND_URL }}
pulumi-version-file: pulumi/.pulumi-version
always-include-summary: true

# 鍵の期限(pulumi/expiry.sh)。30 日を切ったか切れていたら Matrix の部屋 server に投稿する。
# PR では投稿せずログに出すだけ。手動実行で `report` を付けると全部を投稿する。
# 鍵は自分の期限しか読めないので、preview 用と apply 用をそれぞれで見る。apply 用は Environment `pulumi-apply`
# (main だけ)にあるので PR では見ない。
expiry:
if: github.event_name != 'push'
strategy:
fail-fast: false
matrix:
key: ${{ github.event_name == 'pull_request' && fromJSON('["preview"]') || fromJSON('["preview", "apply"]') }}
runs-on: ubuntu-latest
environment: ${{ matrix.key == 'apply' && 'pulumi-apply' || '' }}
permissions:
contents: read
# Entra へ OIDC で入る (preview だけ)
id-token: write
env:
KEY: ${{ matrix.key }}
NB_PAT: ${{ matrix.key == 'apply' && secrets.NB_PAT || secrets.PREVIEW_NB_PAT }}
CF_TOKEN: ${{ matrix.key == 'apply' && secrets.CLOUDFLARE_API_TOKEN || secrets.PREVIEW_CLOUDFLARE_API_TOKEN }}
CF_ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
# hookshot の部屋 server の受け口 (https://m.doany.io/webhook/<Infisical /matrix/matrix の hook-server>)
WEBHOOK: ${{ secrets.MATRIX_SERVER_WEBHOOK_URL }}
DRY_RUN: ${{ github.event_name == 'pull_request' }}
REPORT_ALL: ${{ inputs.report == true }}
WARN_DAYS: "30"
steps:
- uses: actions/checkout@v7
- if: matrix.key == 'preview'
uses: azure/login@v3
with:
client-id: ${{ vars.PREVIEW_AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
allow-no-subscriptions: true
- run: pulumi/expiry.sh
# 確かめられなかったことも知らせる (鍵が切れて 401 になったときなど。黙って落ちると気づかない)
- if: failure() && github.event_name != 'pull_request'
env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
jq -n --arg text ":warning: 鍵の期限を確かめられなかった ($KEY 用)。鍵が切れているかもしれない: $RUN_URL" '{text: $text}' |
curl -fsS -X POST -H "Content-Type: application/json" --data-binary @- "$WEBHOOK"
2 changes: 1 addition & 1 deletion docs/cloudflare-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Pulumi で作ると値を Infisical に書く鍵が CI に要る。その鍵は
| `gitops pulumi apply` / `preview` | アカウント | pulumi/README.md「鍵と state」 | GitHub の secrets | gitops の Pulumi |

アカウントのトークンは `tools/t.ps1 cf accounts tokens list`、ユーザーのトークンは画面の **My Profile → API Tokens** で見る。
**どれも期限を付けていない。** 期限の監視(`.github/workflows/expiry.yml`)が見るのは Pulumi 用だけ。
**どれも期限を付けていない。** 期限の監視(`.github/workflows/pulumi.yml` の `expiry`、`pulumi/expiry.sh`)が見るのは Pulumi 用だけ。

## 作り直す

Expand Down
2 changes: 1 addition & 1 deletion pulumi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ state は R2 の別バケット **`doany-pulumi`**(APAC)にあり、パスフレ

| どこ | 何 |
| --- | --- |
| Actions secrets | `PULUMI_BACKEND_URL` / `PULUMI_CONFIG_PASSPHRASE` / `CLOUDFLARE_ACCOUNT_ID` / `CLOUDFLARE_NOTIFY_WEBHOOK_URL`(notifications.ts の送り先)/ `MATRIX_SERVER_WEBHOOK_URL`(expiry.yml の送り先)/ `CLOUDFLARE_ZONE_ID`(dns.ts)/ `PREVIEW_CLOUDFLARE_API_TOKEN` / `PREVIEW_AWS_ACCESS_KEY_ID` / `PREVIEW_AWS_SECRET_ACCESS_KEY` |
| Actions secrets | `PULUMI_BACKEND_URL` / `PULUMI_CONFIG_PASSPHRASE` / `CLOUDFLARE_ACCOUNT_ID` / `CLOUDFLARE_NOTIFY_WEBHOOK_URL`(notifications.ts の送り先)/ `MATRIX_SERVER_WEBHOOK_URL`(pulumi.yml の `expiry` の送り先)/ `CLOUDFLARE_ZONE_ID`(dns.ts)/ `PREVIEW_CLOUDFLARE_API_TOKEN` / `PREVIEW_AWS_ACCESS_KEY_ID` / `PREVIEW_AWS_SECRET_ACCESS_KEY` |
| Environment `pulumi-apply` | `CLOUDFLARE_API_TOKEN` / `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` |
| 手元 | パスフレーズの写し `~/.config/doany/pulumi-passphrase` |

Expand Down
68 changes: 68 additions & 0 deletions pulumi/expiry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# 鍵の期限を見て、30 日を切ったか切れていたら Matrix の部屋 server に知らせる。pulumi.yml の expiry ジョブから呼ぶ。
#
# 期限を API から読めるものだけを見る。日付を手で書くファイルは置かない (書き忘れと書き換え忘れが起きる)。
# **鍵は自分の期限だけを読む。** NetBird の Auditor は利用者の一覧は読めても他人のトークンは読めない (403) ので、
# preview 用と apply 用をそれぞれのジョブで使う (KEY=preview|apply)。
# - NetBird のサービスユーザーのトークン (README.md「NetBird」)
# - Cloudflare の API トークン (期限が付いていれば)
# - Entra のアプリ登録のクライアントシークレットと証明書 (テナントの全部。共用の Main が切れると SSO が全部止まる)。
# preview のジョブだけで見る (az login 済みであること)
# 期限の無いもの (WINGET_TOKEN など) は対象外。
#
# env: KEY NB_PAT CF_TOKEN CF_ACCOUNT WEBHOOK DRY_RUN REPORT_ALL WARN_DAYS
set -euo pipefail
now=$(date -u +%s)
items=$(mktemp) # 1 行 1 件: 残り日数<TAB>名前<TAB>期限

add() { # $1 名前 $2 期限 (ISO 8601)
local exp; exp=$(date -u -d "$2" +%s)
# 切り捨てを負の側にも効かせる (切れて 1 日未満を「あと 0 日」にしない)
local d=$(( exp - now )); d=$(( d < 0 ? -((-d + 86399) / 86400) : d / 86400 ))
printf '%s\t%s\t%s\n' "$d" "$1" "$(date -u -d "$2" +%Y-%m-%d)" >> "$items"
}

# NetBird: この鍵のサービスユーザー (gitops-pulumi-<preview|apply>) のトークン。名前で選ぶ:
# サービスユーザーは users/current が 403 で自分を引けず、apply 用 (Admin) は他人のトークンも読めて二重に数えてしまう
nb() { curl -fsS -H "Authorization: Token $NB_PAT" -H "Accept: application/json" "https://nb.doany.io/api/$1"; }
u=$(nb "users?service_user=true" | jq -r --arg n "gitops-pulumi-$KEY" '.[] | select(.name == $n) | .id')
[ -n "$u" ] || { echo "NetBird: サービスユーザー gitops-pulumi-$KEY が無い" >&2; exit 1; }
# 先に変数へ受ける (`< <(…)` だと失敗しても落ちない)
tokens=$(nb "users/$u/tokens" | jq -r '.[] | select(.expiration_date) | [.name, .expiration_date] | @tsv')
while IFS=$'\t' read -r tname exp; do
if [ -n "$tname" ]; then add "NetBird gitops-pulumi-$KEY / $tname" "$exp"; fi
done <<< "$tokens"

# Cloudflare: この鍵自身 (期限が無ければ null)。アカウントのトークンは accounts/<ID>/tokens/verify でしか確かめられない
cf() { curl -fsS -H "Authorization: Bearer $CF_TOKEN" "https://api.cloudflare.com/client/v4/$1/tokens/verify"; }
cf_exp=$( { cf user 2>/dev/null || cf "accounts/$CF_ACCOUNT"; } | jq -r '.result.expires_on // empty')
[ -n "$cf_exp" ] && add "Cloudflare ($KEY 用)" "$cf_exp"

# Entra: テナントの全アプリ登録のシークレットと証明書 (preview のジョブだけ。apply と二重に数えない)
if [ "$KEY" = preview ]; then
creds=$(az rest --method GET --url 'https://graph.microsoft.com/v1.0/applications?$select=displayName,passwordCredentials,keyCredentials&$top=999' |
jq -r '.value[] | .displayName as $app | ((.passwordCredentials // []) + (.keyCredentials // []))[] | select(.endDateTime) | [$app, (.displayName // .keyId), .endDateTime] | @tsv')
while IFS=$'\t' read -r app cname exp; do
if [ -n "$app" ]; then add "Entra $app / $cname" "$exp"; fi
done <<< "$creds"
fi

sort -n "$items" | tee /dev/stderr > "$items.sorted"
if [ "$REPORT_ALL" = true ]; then
due=$(cat "$items.sorted")
else
due=$(awk -F'\t' -v w="$WARN_DAYS" '$1 <= w' "$items.sorted")
fi
if [ -z "$due" ]; then echo "期限が近いものは無い"; exit 0; fi

lines=$(printf '%s\n' "$due" | awk -F'\t' '{ s = ($1 < 0) ? "切れている" : "あと " $1 " 日"; printf "- **%s**: %s (%s)\n", $2, $3, s }')
if [ "$REPORT_ALL" = true ]; then
text=$(printf ':key: 鍵の期限 (%s 用、全部)\n\n%s' "$KEY" "$lines")
else
text=$(printf ':key: 鍵の期限 (%s 用、%s 日以内)\n\n%s\n\n作り直して差し替える。手順は gitops の pulumi/README.md' "$KEY" "$WARN_DAYS" "$lines")
fi
if [ "$DRY_RUN" = true ]; then
echo "--- 投稿する内容 (PR なので投稿しない)"; echo "$text"; exit 0
fi
jq -n --arg text "$text" '{text: $text}' | curl -fsS -X POST -H "Content-Type: application/json" --data-binary @- "$WEBHOOK"

Loading