Repository navigation
151 lines (128 loc) · 6.71 KB
/
Copy pathpat-rotation-reminder.yml
File metadata and controls
151 lines (128 loc) · 6.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
name: PAT rotation reminder
on:
schedule:
# 05:00 UTC daily — after the traffic snapshot has finished.
- cron: "0 5 * * *"
workflow_dispatch: {}
permissions:
issues: write
contents: write
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Read the secret's real updated_at from the API rather than trusting a
# hand-maintained date. The ledger drifted by 16 days in Aug 2026 because
# a rotation was done but the file was never bumped, which made the
# reminder fire against a stale date and left an issue open for 10 days.
- name: Resolve last rotation date
id: resolve
env:
GH_TOKEN: ${{ secrets.TRAFFIC_PAT }}
GH_REPO: ${{ github.repository }}
run: |
set -uo pipefail
LEDGER=".github/data/pat-rotation.json"
LEDGER_DATE=$(python -c "import json;print(json.load(open('$LEDGER'))['traffic_pat_last_rotated'])")
# TRAFFIC_PAT carries Administration:Read, so it can read its own
# secret metadata. If the call fails (token expired or scope changed)
# fall back to the ledger — a stale date is better than no check.
API_DATE=$(gh api "repos/$GH_REPO/actions/secrets/TRAFFIC_PAT" --jq '.updated_at[0:10]' 2>/dev/null || true)
# Validate the shape, do not just test for non-empty. On a 401 the
# CLI writes its error body to stdout, so API_DATE ends up holding a
# multi-line JSON blob rather than a date. That is non-empty, so the
# old check took the "api" branch with garbage, and writing a
# multi-line value to GITHUB_OUTPUT then failed the whole step. The
# net effect was that this reminder died exactly when the token had
# expired, which is the one moment it needs to fire.
if ! printf '%s' "$API_DATE" | grep -Eq '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then
API_DATE=""
fi
if [ -n "$API_DATE" ]; then
SOURCE="api"
LAST_ROTATED="$API_DATE"
else
SOURCE="ledger"
LAST_ROTATED="$LEDGER_DATE"
echo "::warning::Could not read secret metadata — TRAFFIC_PAT may be expired. Falling back to the ledger date."
fi
echo "last_rotated=$LAST_ROTATED" >> "$GITHUB_OUTPUT"
echo "ledger_date=$LEDGER_DATE" >> "$GITHUB_OUTPUT"
echo "source=$SOURCE" >> "$GITHUB_OUTPUT"
echo "Last rotated: $LAST_ROTATED (source: $SOURCE)"
# Keep the human-readable ledger in sync automatically.
- name: Sync ledger if it drifted
if: steps.resolve.outputs.source == 'api' && steps.resolve.outputs.ledger_date != steps.resolve.outputs.last_rotated
env:
NEW_DATE: ${{ steps.resolve.outputs.last_rotated }}
run: |
set -euo pipefail
python - <<'PY'
import json, os
p = ".github/data/pat-rotation.json"
d = json.load(open(p))
d["traffic_pat_last_rotated"] = os.environ["NEW_DATE"]
with open(p, "w") as f:
json.dump(d, f, indent=2)
f.write("\n")
PY
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .github/data/pat-rotation.json
if ! git diff --cached --quiet; then
git commit -m "chore: sync PAT rotation ledger to ${NEW_DATE}"
git push
fi
- name: Open issue if approaching expiry
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
LAST_ROTATED: ${{ steps.resolve.outputs.last_rotated }}
run: |
set -euo pipefail
LEDGER=".github/data/pat-rotation.json"
EXPIRY_DAYS=$(python -c "import json;print(json.load(open('$LEDGER'))['traffic_pat_expiry_days'])")
WARN_DAYS=$(python -c "import json;print(json.load(open('$LEDGER'))['warn_days_before_expiry'])")
AGE=$(python -c "
from datetime import date
import os
last = date.fromisoformat(os.environ['LAST_ROTATED'])
print((date.today() - last).days)
")
REMAINING=$((EXPIRY_DAYS - AGE))
echo "TRAFFIC_PAT age ${AGE}d — ${REMAINING}d until expiry."
if [ "$AGE" -lt $((EXPIRY_DAYS - WARN_DAYS)) ]; then
echo "Still fresh. No action needed."
exit 0
fi
# Close any stale reminder that predates the current rotation —
# otherwise an old issue suppresses the new one indefinitely.
STALE=$(gh issue list --repo "$GH_REPO" --state open --label pat-rotation-due \
--json number,createdAt \
--jq ".[] | select(.createdAt[0:10] < \"$LAST_ROTATED\") | .number" || true)
for N in $STALE; do
echo "Closing stale reminder #$N (predates rotation on $LAST_ROTATED)"
gh issue close "$N" --repo "$GH_REPO" \
--comment "Superseded — TRAFFIC_PAT was rotated on ${LAST_ROTATED}."
done
EXISTING=$(gh issue list --repo "$GH_REPO" --state open --label pat-rotation-due \
--json number --jq '.[0].number // empty' || true)
if [ -n "$EXISTING" ]; then
echo "Open reminder already exists: #$EXISTING. Skipping."
exit 0
fi
BODY="TRAFFIC_PAT was last rotated on **${LAST_ROTATED}** (${AGE} days ago). It expires in **${REMAINING} day(s)**.
## Rotate it
1. Go to https://github.com/settings/tokens?type=beta and regenerate \`TRAFFIC_PAT (8-day rotating)\`. Keep all repo access plus **Administration: Read** and **Metadata: Read**. Set expiry to 8 days.
2. **Authorize SSO on the token.** Skipping this is the most common cause of a silent failure.
3. \`gh secret set TRAFFIC_PAT --repo $GH_REPO\`
4. \`gh workflow run traffic-snapshot.yml --repo $GH_REPO\` to confirm it works.
The ledger now syncs itself from the secret's \`updated_at\`, so there is no file to bump by hand.
## If this lapses
The nightly \`traffic-snapshot\` workflow fails with HTTP 403 across every microsoft/* repo. Data captured before the failure is still committed (fixed Aug 2026), but **repo traffic for the missed days is lost** — GitHub serves only a rolling 14-day window, so a gap longer than that is unrecoverable.
_Auto-opened by \`.github/workflows/pat-rotation-reminder.yml\`._"
gh issue create --repo "$GH_REPO" \
--title "🔑 Rotate TRAFFIC_PAT — ${REMAINING} day(s) until expiry" \
--label pat-rotation-due \
--body "$BODY"