[FEAT] Support managing github_membership and lookups by stable user_id (#524)#3436
Conversation
|
👋 Hi! Thank you for this contribution! Just to let you know, our GitHub SDK team does a round of issue and PR reviews twice a week, every Monday and Friday! We have a process in place for prioritizing and responding to your input. Because you are a part of this community please feel free to comment, add to, or pick up any issues/PRs that are labeled with |
Security implications worth calling outBeyond fixing the state-drift annoyance from #524, this PR meaningfully reduces a real, exploitable class of attack against 1. Username-rename squatting against organizationsGitHub releases a username for reuse shortly after the original owner renames their account. Combined with a
This isn't theoretical — it's the standard GitHub-username-squat pattern applied to IaC. The org owner has no signal anything is wrong; their drift detection passes because the config still says With this PR, when the membership is keyed by This is an opt-in improvement — users who keep using 2. Admin-role continuity through renameSame mechanism, sharper edge: an org admin renaming themselves currently causes the 3. New attack surface this PR adds (honest accounting)
4. What this does NOT do
Happy to add a |
That's why I'm not using this provider for user management as of now. Mapping members by mutable usernames is just too risky for me. I hope this gets merged! |
48ac07a to
d6b22a6
Compare
The github_user data source previously required a username (login), which
GitHub allows users to change at any time. Add a new optional 'user_id'
input (mutually exclusive with 'username') that resolves the user via
GET /user/{id} so lookups stay valid across renames.
Refs integrations#524
Add an optional 'user_id' input (mutually exclusive with 'username') to the
github_membership data source. When set, the user is resolved via
GET /user/{id} and the resulting login is used to query the org
membership endpoint, which only accepts logins.
The data source now always exposes 'user_id' as a computed attribute too,
so downstream resources can refer to the stable numeric ID even when the
query used a login.
Refs integrations#524
Add an optional 'user_id' input (mutually exclusive with 'username',
both ForceNew) so org memberships can be addressed by GitHub's stable
numeric user ID. This makes the resource resilient to the user renaming
their GitHub account: Read resolves the current login via GET /user/{id}
and silently updates the 'username' attribute in state, producing no
diff.
Resource ID format changes from 'org:username' to 'org:user_id' for new
resources. Read performs a lazy migration: when an existing state has an
ID of the old shape, the username is resolved to its numeric ID and the
ID is rewritten in place. Imports support both legacy 'org:username' and
the new 'org:user_id' shape.
Includes an acceptance test (TestAccGithubMembershipRenameResilience)
that exercises the rename path end-to-end. The test requires
GH_TEST_EXTERNAL_USER_TOKEN since PATCH /user only works for the
authenticated user and skips otherwise.
Closes integrations#524
d6b22a6 to
016d723
Compare
Summary
user_id(TypeInt) togithub_membershipresource and data source, and togithub_userdata source — mutually exclusive withusernameviaExactlyOneOf.user_idare now resilient to GitHub username renames: Read resolves the live login viaGET /user/{id}and silently updates theusernameattribute in state, producing no diff.org:user_id; legacyorg:usernameIDs are migrated lazily on the next Read. Imports accept both shapes.Closes #524.
Why
The GitHub
idis globally unique and immutable;usernameis not. Today, when a user renames their account, modules usinggithub_membershipdrift and trigger recreates. Issue #524 asks for the stable ID to be a valid lookup key. This PR adds that, while keeping the existingusername-keyed workflow fully backward-compatible.Behavior
data "github_user"user_id(mutually exclusive withusername).user_idattribute always populated.Users.GetByIDwhenuser_idis set.data "github_membership"user_id(mutually exclusive withusername).user_id→ login first (GitHub's org membership endpoints only accept logins), then queries the membership.user_idattribute always populated.resource "github_membership"user_id(TypeInt, ForceNew, exactly-one-of withusername).org:user_idfor new resources.GetByID); non-digits → legacy username path, and rewrites the ID in place toorg:user_idon first refresh.usernamefrom state, which Read keeps fresh — so deletion still works correctly after a rename.Tests
Acceptance tests (require
TF_ACC=1plus the env vars inCONTRIBUTING.md):TestAccGithubUserDataSource— added subtests foruser_idhappy path + error paths (not-found, mutual exclusion).TestAccGithubMembershipDataSource— same coverage for the data source.TestAccGithubMembership— added subtest foruser_id-keyed create + mutual-exclusion error paths.TestAccGithubMembershipRenameResilience— end-to-end live-rename test. Creates a membership byuser_id, renames the external user viaPATCH /userwith their own PAT (GH_TEST_EXTERNAL_USER_TOKEN), refreshes, asserts theusernameattribute updated with no plan diff, then restores the original login in cleanup. Skips automatically whenGH_TEST_EXTERNAL_USER_TOKENis unset.Local validation:
Docs
templates/data-sources/user.md.tmpl,templates/data-sources/membership.md.tmpl,templates/resources/membership.md.tmplupdated withuser_idarguments, import notes, and rename-resilience guidance.examples/data-sources/user/example_2.tf,examples/data-sources/membership/example_2.tf,examples/resources/membership/example_2.tf.docs/*regenerated to match templates (please re-runmake generatedocson a machine withterraformon PATH to confirm bit-for-bit match — my dev machine lacked it; I synced docs to templates manually).Backward compatibility
usernamecontinue to work unchanged.org:usernameIDs migrates lazily on next Read (single extraUsers.GetByIDround-trip, one-shot).terraform import org:usernameandterraform import org:12345both supported.Notes for reviewers
data "github_user"exposes the stable ID asuser_idrather thanid(the issue's wording) because the Terraform SDKv2 reserves the top-levelidattribute as the resource ID. Usinguser_idis also more consistent with the rest of this provider (e.g.organization_role_users).SchemaVersionbump because StateUpgraders are supposed to be pure, and resolving username → user_id needs a network call. Read-side handles the migration self-healingly and incurs no upfront cost onterraform plan.Commits
[FEAT] Support looking up github_user data source by user_id[FEAT] Support looking up github_membership data source by user_id[FEAT] Support managing github_membership by stable user_id