Skip to content

fix: skip duplicate key-auth credentials at reconcile time - #2900

Closed
shreemaan-abhishek wants to merge 1 commit into
apache:masterfrom
shreemaan-abhishek:fix/consumer-keyauth-dup-reconcile
Closed

shreemaan-abhishek wants to merge 1 commit into
apache:masterfrom
shreemaan-abhishek:fix/consumer-keyauth-dup-reconcile

Conversation

@shreemaan-abhishek

Copy link
Copy Markdown
Contributor

Type of change:

  • Bugfix

What this PR does / why we need it:

The Consumer webhook rejects a key-auth key that another Consumer on the same Gateway already uses, but only at admission. When a credential's secretRef points to a Secret that does not exist yet, the key is empty and the check skips it. Creating the Secret afterwards fires no Consumer webhook, and the reconciler published the key with no check, so two Consumers on one Gateway could end up sharing a key-auth key.

This PR moves the check into the reconciler, which sees every change:

  • Before Provider.Update, the reconciler drops any key-auth credential whose key an older Consumer on the same Gateway publishes. "Older" is by creation time, with namespace/name as the tie-break, so exactly one Consumer keeps the key, matching the webhook's first-come behavior. The Consumer stays published with its other credentials, and its status reports N key-auth credential(s) skipped: key already used by another Consumer.
  • Keys are looked up through new field indexes on Consumers (inline keys) and Secrets (data.key). Index values are SHA-256 hashes of the key, so plaintext keys never land in the index.
  • A credential only counts as publishing a key when the controller would load it: a cross-namespace Secret without a permitting ReferenceGrant holds no key.
  • Consumer and Secret events requeue the Consumers that share the affected key, so a skipped credential is published again once the key is free (for example, the older Consumer is deleted or changes its key).

Consumers with unique keys reconcile exactly as before.

Pre-submission checklist:

  • Did you explain what problem does this PR solve? Or what new features have been added?
  • Have you added corresponding test cases?
  • Have you modified the corresponding document?
  • Is this PR backward compatible? If it is not backward compatible, please discuss on the mailing list first

The Consumer webhook checks key-auth keys only at admission, and skips
a credential whose Secret does not exist yet. A Secret created after
its Consumer therefore published a key another Consumer on the same
Gateway already used.

The reconciler now drops a key-auth credential whose key an older
Consumer on the same Gateway publishes, and reports it on the status.
Keys are found through hashed field indexes on Consumers and Secrets,
and Consumer and Secret changes requeue Consumers sharing a key so a
skipped credential returns once the key is free.

Signed-off-by: Abhishek Choudhary <shreemaan.abhishek@gmail.com>
@shreemaan-abhishek

Copy link
Copy Markdown
Contributor Author

Paired with api7/api7-ingress-controller#491.


// GenKeyAuthKeyIndex hashes a key-auth key so the index never holds plaintext.
func GenKeyAuthKeyIndex(key string) string {
sum := sha256.Sum256([]byte(key))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive. The hash is only an in-memory lookup key for a field index. It is never stored, logged, or sent anywhere, and the plaintext key already sits in the same process cache, so there is nothing to crack offline. SHA-256 here only keeps plaintext out of the index.

@AlinsRan

AlinsRan commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Please check CI.

@AlinsRan AlinsRan left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Consumer can lose a key it is already using to another Consumer that edits its own Secret afterwards.

When two Consumers share a key, this PR lets the Consumer created earlier keep it. But "created earlier" is not "used the key first":

  1. Consumer b uses key K and is published.
  2. Consumer a, created before b, changes its own Secret's data.key to K. Secret updates go through no webhook.
  3. On the next reconcile, a is older, so a keeps K and b's credential is dropped.

b used K first but loses it, and requests with K now authenticate as a. The webhook would reject the same change if a made it inline, so this is not the webhook's first-come behavior. On master, b is not removed by the controller.

The controller does not record when a key was first used, so it cannot pick the right owner. Suggestion: when a key is shared, skip it on every Consumer that uses it and report the conflict in each Consumer's status.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants