Skip to content

PMM-15692 Report rejected tokens as Unauthenticated. - #6087

Merged
JiriCtvrtka merged 19 commits into
mainfrom
PMM-15692-disabled-sa-unauthenticated
Oct 14, 2026
Merged

JiriCtvrtka merged 19 commits into
mainfrom
PMM-15692-disabled-sa-unauthenticated

Conversation

@JiriCtvrtka

@JiriCtvrtka JiriCtvrtka commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Ticket number: PMM-15692

Feature build: Percona-Lab/pmm-submodules#4628

Summary

When a service account or its token is disabled, pmm-admin again prints "Auth method is not service account token. Please check username and password." instead of "Please check PMM Server logs."

Grafana's /api/auth/serviceaccount route has no sign-in middleware. A token Grafana does not authenticate (wrong, expired, revoked, or from a disabled account) therefore never gets a 401: the request reaches the handler unauthenticated and gets 400 "Auth method is not service account token". Until PMM-13463 that 400 fell through to the API-key check, which answered 401. Since API keys were removed, pmm-managed has passed the 400 on as codes.Internal. Nobody saw this until #5707 made the CLIs choose their hint from the gRPC code.

getRoleForServiceToken now marks that 400 with errServiceTokenRejected, and retrieveRole maps it to codes.Unauthenticated, the same as 401 and 403. A 400 on the basic-auth or cookie path stays Internal. The HTTP status (401) and the message are unchanged; only code changes from 13 to 16. Grafana answers a token it failed to look up, such as during a DB failure, with the same 400, so that case also reads as rejected credentials. Telling the two apart needs a change in percona/grafana.

Also in this PR:

  • pmm-agent setup no longer re-registers a Node over a rejected token. The registration check from PMM-15260 Make pmm-agent Node registration idempotent #5890 relied on a rejected token carrying code 13. Setup now treats a rejection of the token the Agent runs with like a refusal: it asks again with the credentials given to setup, re-registers only if PMM Server doesn't know the Agent, and otherwise keeps the registration. A rejected password keeps the PMM-15260 Make pmm-agent Node registration idempotent #5890 behaviour, which reports a mistyped password and exits 1.
  • When the credentials given to setup find the Node, setup says that PMM Server has the Agent on that Node but rejects its token, and what --force would remove. Before, it printed a "did not confirm" warning.
  • The second lookup now uses insecure-tls from the configuration file, as the first lookup does.
  • TestServerClientConnection/Token auth - fail expects codes.Unauthenticated again; CI runs it against a live Grafana. New TestAuthServerGrafanaErrors sends requests through ServeHTTP to a fake Grafana and checks the status and body code. The agent tests model a rejected token as 401 with code 16, end to end through checkRegistrationOnServer.

How to test: register a client with PMM_AGENT_SERVER_USERNAME=service_token and a service-account token as the password. Disable the service account and wait 60 s for the auth cache to expire. pmm-admin list should then print "Auth method is not service account token. Please check username and password." With the Node still registered, run pmm-agent setup with admin credentials and without --force. It should report that PMM Server has the Agent on the Node but rejects its token, keep the registration and exit 0. After removing the Node in PMM, the same command registers it again. Re-running setup with only the token should keep the registration with a warning, not register the Node again. The e2e test PMM-T1884 (@service-account) covers the pmm-admin path.

If this PR adds, removes or alters one or more API endpoints, please review and update the relevant API documentation as well:

  • API Docs updated

No API changes

If this PR is related to other PRs, contributions, or ongoing work in this or other repositories, please reference them here:

Grafana's /api/auth/serviceaccount has no sign-in middleware, so a token
it rejects (wrong, expired, revoked, or of a disabled service account)
gets 400 "Auth method is not service account token" instead of 401.
pmm-managed mapped that 400 to codes.Internal. Since PMM-15186 the CLIs
pick their hint from the code, so pmm-admin told users to check PMM
Server logs instead of their credentials.

Before PMM-13463 that 400 fell through to the API-key check, which
answered 401; the live "Token auth - fail" test was switched to
Internal when that fallback went away. Map 400 to Unauthenticated
alongside 401 and 403, and restore the test's original expectation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 57.81%. Comparing base (31318c7) to head (9e91c9a).
⚠️ Report is 290 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main    #6087       +/-   ##
===========================================
+ Coverage   43.59%   57.81%   +14.22%     
===========================================
  Files         415      373       -42     
  Lines       43134    39988     -3146     
===========================================
+ Hits        18804    23121     +4317     
+ Misses      22454    16867     -5587     
+ Partials     1876        0     -1876     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

JiriCtvrtka and others added 4 commits October 8, 2026 09:59
pmm-managed now answers a service token Grafana rejects with
codes.Unauthenticated instead of codes.Internal. pmm-agent setup read
Unauthenticated as errCredentialsRejected and registered the Node again
without asking with the credentials given to setup. With the Node still
registered that failed on its name ("already exists", exit 1, which
stops a container running setup on start) or added a second Node under
the hostname. A brief Grafana DB failure ends in the same 400, so it
could trigger this too.

withGivenCredentials now treats a rejection like a refusal: it asks
again with the credentials given to setup and only registers when PMM
Server does not know the Agent; otherwise the registration is left
unverified and kept, as before. The agent tests modelled a refused token
as 401 with codes.Internal; they now cover 401 with
codes.Unauthenticated, end to end through checkRegistrationOnServer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiří Čtvrtka <j.ctvrtka@gmail.com>
AGENTS.md allows a comment one line, two at most; the explanation of
Grafana's 400 lives in the ticket and the first commit's message.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiří Čtvrtka <j.ctvrtka@gmail.com>
The test called retrieveRole directly, so it checked the authError but
not what a client receives. It now goes through ServeHTTP and asserts
the HTTP status and the JSON body, the "code" the CLIs pick their hint
from included.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiří Čtvrtka <j.ctvrtka@gmail.com>
@JiriCtvrtka
JiriCtvrtka marked this pull request as ready for review October 8, 2026 11:01
@JiriCtvrtka
JiriCtvrtka requested a review from a team as a code owner October 8, 2026 11:01
@JiriCtvrtka
JiriCtvrtka requested review from 4nte and ademidoff and removed request for a team October 8, 2026 11:01
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ed811376-78b9-4562-b039-1b6a58e738f6
📥 Commits

Reviewing files that changed from the base of the PR and between 881e6bf and 8b5bcf4.

📒 Files selected for processing (3)
  • managed/services/grafana/auth_server.go
  • managed/services/grafana/auth_server_test.go
  • managed/services/grafana/client.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Walkthrough

Agent setup retries a rejected service-token lookup with setup credentials when they differ from the running credentials. A successful retry returns the registered Node, and a retry that finds no Agent returns the not-found result. Grafana now identifies a rejected service token and maps that error to Unauthenticated.

Sequence Diagram(s)

sequenceDiagram
  participant AgentSetup
  participant PMM_Server
  participant RegistrationCheck
  AgentSetup->>PMM_Server: Lookup with running credentials
  PMM_Server-->>AgentSetup: Credential rejection
  AgentSetup->>PMM_Server: Retry lookup with setup credentials
  PMM_Server-->>AgentSetup: Agent found or not found
  AgentSetup->>RegistrationCheck: Return lookup outcome
Loading

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 8b5bc

This change makes Grafana's rejected service-account tokens report as unauthenticated, not internal errors. No concrete merge-blocking risk was identified. The known limitation, that a Grafana lookup failure can look like a rejected token, is documented and needs a fix in Grafana.

Pre-merge checks: Passed 4
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly identifies the main change: reporting rejected tokens as Unauthenticated. It is concise and specific.
Description check Passed The description includes the ticket number, feature build, change summary, testing details, API documentation status, and related work. It is complete and aligned with the repository template.
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ademidoff ademidoff left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reporting a disabled service-account token as Unauthenticated instead of Internal is the right goal, but the change is broader than the Grafana quirk it fixes. Together with the new branch in withGivenCredentials, it makes a few pmm-agent setup cases worse than before.

The main concerns:

  • The 400 mapping is too broad (auth_server.go:712). It catches Grafana-side lookup failures and also covers /api/user and /api/user/orgs. Moving it into getRoleForServiceToken would keep the fix and avoid most of the knock-on effects below.
  • A brief Grafana failure can now re-register the Node when setup runs with the Agent's own credentials (setup.go:98).
  • The rejected-token branch in withGivenCredentials makes some cases worse: it hides a mistyped password (setup.go:115), throws away a successful second lookup in the very case this PR targets (setup.go:117), and ignores insecure-tls from the config file (setup.go:97).

The remaining comments are about doc comments, test coverage and nits.


Generated by Claude Code

Comment thread agent/commands/setup.go Outdated
if !serverRefused(err) || sameCredentials(running, given) {
// A token of a disabled service account, or one Grafana failed to look up, is rejected too.
rejected := errors.Is(err, errCredentialsRejected)
if (!rejected && !serverRefused(err)) || sameCredentials(running, given) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When sameCredentials(running, given) is true, a rejected token still returns errCredentialsRejected unchanged, and checkRegistration maps that straight to registrationMissing. With the server change, pmm-managed now sends code 16 for every Grafana 400, and the comment above says that includes a token Grafana "failed to look up". So a brief Grafana failure now registers the Node again in this case.

Scenario: a container re-runs pmm-agent setup --config-file=... with no credential flags. Grafana's token lookup fails for a moment and answers 400.

  • Before: code 13, serverRefused, registrationUnverified, WARNING, exit 0.
  • Now: registrationMissing and register(). If Grafana is still failing, setup exits 1 and the container goes down, which is the outage registrationUnverified is there to avoid. If Grafana has recovered, RegisterNode fails with "Node ... already exists, use --force", and --force deletes the Node with all its Services. If the Node is registered under a non-hostname name and there is no --region, a second Node is created under the hostname.

Narrowing the 400 mapping on the server (see the comment on auth_server.go) would avoid most of this.


Generated by Claude Code

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.

Fixed in 7f75cd0. A rejection of the token the Agent runs with becomes errTokenRejected and is treated like a refusal, so with the Agent's own credentials it ends registrationUnverified and nothing is registered. Covered end to end by "a token alone leaves the registration unverified".

Comment thread managed/services/grafana/auth_server.go Outdated
if cErr.Code == 401 || cErr.Code == 403 {
switch cErr.Code {
// Grafana answers a token it rejects with 400 on /api/auth/serviceaccount, see PMM-15692.
case http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Every Grafana 400 during the auth check now becomes codes.Unauthenticated. That includes a valid glsa_ token whose lookup failed on Grafana's side: on any authn error the request reaches the handler unauthenticated and gets the same 400.

Scenario: Grafana's database is slow or unavailable (startup, HA failover) and authn fails while looking up a valid token. Grafana answers 400 "Auth method is not service account token", and pmm-managed answers code 16. pmm-admin and pmm-agent then print "Please check username and password" instead of "Please check PMM Server logs", and pmm-agent setup treats it as errCredentialsRejected (see the comment on setup.go:98). Before this PR it was code 13, which correctly read as a server-side failure.


Generated by Claude Code

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.

Narrowing doesn't remove this one: Grafana puts every authn error, a DB error from GetAPIKeyByHash included, into LookupTokenErr and answers 400 on /api/auth/serviceaccount itself, so pmm-managed can't tell it from a disabled token. Setup no longer acts on a token rejection (7f75cd0), so it can't re-register the Node; what remains is the CLI hint during such a failure. The real fix is in percona/grafana: CurrentServiceAccount answering 500 when the lookup error isn't an Unauthorized one. Happy to open that as a follow-up.

Comment thread managed/services/grafana/auth_server.go Outdated
Comment on lines +710 to +712
switch cErr.Code {
// Grafana answers a token it rejects with 400 on /api/auth/serviceaccount, see PMM-15692.
case http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The quirk is specific to /api/auth/serviceaccount, but the mapping is in the shared retrieveRole switch, so it also covers /api/user and /api/user/orgs. Any future 400 from the basic-auth or cookie paths (a malformed request, a Grafana validation error) would be reported as bad credentials.

Suggestion: turn the 400 into an "unauthenticated" error inside getRoleForServiceToken (client.go:550), where the quirk actually is. That fixes the disabled-token case without changing how the other endpoints are mapped.


Generated by Claude Code

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.

Done in ddd2e00: getRoleForServiceToken marks its 400 with errServiceTokenRejected, and retrieveRole maps only that, alongside 401 and 403 as before. A basic-auth 400 now stays Internal, with a test case for it.

Comment thread agent/commands/setup.go Outdated
if errors.Is(e, errAgentNotFound) {
return serverNode{}, e
}
if rejected {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When the running credentials are rejected and the credentials given to setup are rejected too, setup now ends as registrationUnverified and exits 0, so the wrong password the operator typed is no longer reported.

Scenario: the Agent config holds basic-auth credentials (for example an upgraded v2 config with admin:oldpass), and the operator runs pmm-admin config with a mistyped new password.

  • Before: errCredentialsRejected, registrationMissing, RegisterNode answers 401/16, "Invalid username or password. Please check username and password", exit 1.
  • Now: the second lookup is rejected and this branch returns an error that no longer matches the sentinel. Setup prints "Failed to check the registration ...: credentials rejected." and the WARNING, exits 0, and keeps the broken credentials. The only hint is "Settings --server-username, --server-password only take effect when the Node is registered."

Generated by Claude Code

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.

Fixed in 7f75cd0: only a rejected token is asked about again. A rejected password takes the #5890 path again, so RegisterNode reports "Please check username and password" and setup exits 1. Covered by "a rejected password is not asked about again".

Comment thread agent/commands/setup.go Outdated
}
if rejected {
// Unwrapped, so that the rejection leaves the registration unverified instead of registering again.
return node, errors.New(err.Error())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In the main case this PR targets (service account disabled, Node still present, operator passes valid admin credentials), the second lookup does find the Node. That answer is thrown away (_, e := lookup(...)), and the operator sees:

Failed to check the registration of pmm-agent X with Y: credentials rejected.
WARNING: PMM Server at Y did not confirm that pmm-agent X is registered. Nothing was verified and nothing was changed; the existing registration is kept. Re-run once PMM Server answers, or use --force to register the Node again.

PMM Server did answer and did confirm the Node. Re-running can never help, because the token is rejected for good, and the only remedy offered (--force) removes the Node with every Service on it. The Agent is reloaded with the disabled token and stays disconnected, and nothing tells the operator that the Agent's own token is the problem.

When the second lookup succeeds, it would help to report that the Node is registered but the Agent's token is rejected, and say what to do about it.


Generated by Claude Code

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.

Fixed in 6bdb479. When the credentials given to setup find the Node, that is the answer: setup prints that PMM Server has the Agent on that Node but does not accept its token, points to the Node's service account, says --force removes the Node with its Services, and the registration is confirmed. A plain refusal (401 without Unauthenticated) still keeps the first answer.

Comment thread agent/commands/clients.go

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two doc comments outside the diff are now stale:

  • serverNodeOfAgent (line 159): "The errors errAgentNotFound and errCredentialsRejected mean that the Node has to be registered again." withGivenCredentials no longer treats errCredentialsRejected as a re-register verdict.
  • serverRefused (line 222) still describes a 401 without Unauthenticated as the only grounds for asking again.

A new caller of serverNodeOfAgent that trusts the doc would re-register on errCredentialsRejected, which is what this PR guards against.


Generated by Claude Code

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.

Fixed in 8bbd7c1: serverNodeOfAgent no longer says errCredentialsRejected means re-register, and serverRefused names a rejected token as grounds for asking again too.

Comment thread agent/commands/clients.go Outdated
Comment on lines 237 to 240
// The credentials the Agent runs with are gone, so withGivenCredentials asks again with the ones given
// to setup, and registering with the same ones reports an actionable message. codes.PermissionDenied is
// deliberately not here: a service account below the admin role still holds valid credentials, it
// just cannot read the inventory, and registering the Node again over that would only add a second.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: this comment block was rewritten in this diff and is still four lines. AGENTS.md (Code Style) asks for comments of "one line, two at most".


Generated by Claude Code

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.

The lookupError comment is back to the original #5890 text in 7f75cd0, which is accurate again for a rejected password, so the block is no longer in the diff.


_, authError := s.authenticate(ctx, req, req.URL.Path, logrus.WithField("test", t.Name()))
assert.Equal(t, codes.Internal, authError.code)
assert.Equal(t, codes.Unauthenticated, authError.code)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This live-Grafana assertion for Bearer wrong was flipped from Internal to Unauthenticated without being run (the PR description says so). It passes only if Grafana actually answers 400, 401 or 403 for that token. The old codes.Internal expectation shows Grafana did not answer 401/403. If it answers 5xx or 404 for a malformed (non-glsa_) token rather than the assumed 400, TestServerClientConnection/Token auth - fail fails in CI. Worth running it against a live Grafana before merging.


Generated by Claude Code

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.

It has run: the Managed "Unit tests" job runs go test ./... without skips inside a live PMM Server, and it passed on 00a7296. Grafana answers Bearer wrong with 400, so the assertion holds.

results: []agentLookup{failed(rejected)},
calls: 1,
err: rejected,
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Branches with no tests:

  • the u == nil branch: a rejected token with no server address returns errCredentialsRejected, so registrationMissing.
  • a second lookup that fails for a reason other than credentials (timeout, PermissionDenied, TLS).

On the server side, TestAuthServerRejectedCredentials covers only the token endpoint, not the basic-auth endpoints that the 400 mapping also changes. A regression in any of these would pass the suite.


Generated by Claude Code

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.

Added in 6b3aa97: a rejected token with no server address (now errTokenRejected, so the registration is kept rather than registrationMissing), and a second lookup that hits a deadline or PermissionDenied. The basic-auth 400 is covered server side in ddd2e00.

})
}

// TestAuthServerRejectedCredentials checks the answer clients get when Grafana rejects the credentials, whose code the CLIs pick their hint from.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: the doc comment says the test covers what happens "when Grafana rejects the credentials", but the table also has a 500 "server error" case, which is not a rejection. The PR description also calls this test TestRetrieveRoleRejectedCredentials, while the code names it TestAuthServerRejectedCredentials.


Generated by Claude Code

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.

Renamed to TestAuthServerGrafanaErrors, with a doc comment that covers the whole table (bda1f50), and the PR description now uses that name.

JiriCtvrtka and others added 9 commits October 9, 2026 09:14
The 400 Grafana gives a token it did not authenticate is specific to
/api/auth/serviceaccount, which has no sign-in middleware. Mapping every
400 in retrieveRole also covered /api/user and /api/user/orgs, so a
future 400 on the basic-auth or cookie path would have read as bad
credentials. getRoleForServiceToken now marks its 400 with
errServiceTokenRejected, and retrieveRole maps only that, alongside 401
and 403 as before. The log keeps Grafana's real status.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiří Čtvrtka <j.ctvrtka@gmail.com>
The table holds a server error and a basic-auth 400 next to the
rejected credentials, so "RejectedCredentials" and its doc comment
described only part of it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiří Čtvrtka <j.ctvrtka@gmail.com>
PMM Server answers a token Grafana failed to look up exactly like one it
rejects, so a rejected token says nothing about the registration - the
rule #5890 already applies to a refused token. The previous commit
handled every errCredentialsRejected that way instead, which:

- registered the Node again when setup had only the Agent's own token,
  so a brief Grafana failure could exit 1 or add a second Node;
- hid a mistyped password: a rejected password was asked about again
  and ended unverified, where #5890 reports it and exits 1;
- relied on errors.New(err.Error()) not matching the sentinel, and
  printed only "credentials rejected".

withGivenCredentials now turns errCredentialsRejected into
errTokenRejected only when the Agent runs with a token, and treats it
like a refusal through a single predicate. A rejected password takes
the #5890 path again, so the lookupError comment is back to its
original text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiří Čtvrtka <j.ctvrtka@gmail.com>
With the service account disabled and the Node still there, the
credentials given to setup find the Node, but that answer was thrown
away: setup said "did not confirm that pmm-agent is registered", told
the operator to re-run once PMM Server answers, and offered --force,
which removes the Node with every Service on it.

For a rejected token, the Node the second lookup finds is now the
answer. Setup says that PMM Server has the Agent on that Node but does
not accept its token, points to the Node's service account, and spells
out what --force would remove; the registration is then confirmed and
kept. A refused (401 without Unauthenticated) answer keeps the first
answer as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiří Čtvrtka <j.ctvrtka@gmail.com>
The second lookup set up its transport with the --server-insecure-tls
given to setup only, ignoring insecure-tls from the configuration file
that runningServer merges in for the first lookup. With the flag left
off, it failed PMM Server's self-signed certificate instead of finding
the Node, or finding it gone, and setup ended unverified. It now uses
the merged setting, as the first lookup does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiří Čtvrtka <j.ctvrtka@gmail.com>
serverNodeOfAgent and runningServer still said that a rejected token
registers the Node again, and serverRefused named a 401 without
Unauthenticated as the only grounds for asking again. A caller trusting
them would re-register on errCredentialsRejected, which is what
withGivenCredentials now guards against for a token.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiří Čtvrtka <j.ctvrtka@gmail.com>
A rejected token with no PMM Server address to ask again, and a second
lookup that fails for something other than the credentials - a deadline
or credentials that cannot read the inventory - all have to keep the
registration rather than register the Node again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiří Čtvrtka <j.ctvrtka@gmail.com>
@JiriCtvrtka
JiriCtvrtka requested a review from ademidoff October 9, 2026 11:34

Copy link
Copy Markdown
Member

Thanks, I went through the fixes on 8b5bcf4 and they address the review. I agree with keeping Grafana's message for now, and with leaving the DB-failure case to Grafana.

One ask before merge: please open the percona/grafana follow-up you offered (CurrentServiceAccount answering 500 when the token lookup fails for a reason other than Unauthorized) and link it here, so the known limitation is tracked.

Non-blocking, take them or leave them:

  • If the token was revoked rather than its service account disabled, an operator who passes a fresh token (--server-username=service_token --server-password=...) has it discarded: setup confirms the Node, then keepRegistration writes the dead token back to the file. That comes from PMM-15260, but the new rejected-token branch now leads there. It could be a follow-up ticket.
  • The lookupError comment (clients.go:238) says that registering follows rejected credentials, which no longer holds for a token.
  • holdsToken repeats the check in agent/client/client.go:972.

Generated by Claude Code

@JiriCtvrtka
JiriCtvrtka enabled auto-merge (squash) October 14, 2026 17:26
@JiriCtvrtka
JiriCtvrtka merged commit 0d65f9c into main Oct 14, 2026
26 of 27 checks passed
@JiriCtvrtka
JiriCtvrtka deleted the PMM-15692-disabled-sa-unauthenticated branch October 14, 2026 18:45
travagliad added a commit to percona/pmm-qa that referenced this pull request Oct 15, 2026
pmm-admin now reports "check username and password" for a disabled service
account (percona/pmm#6087). pmm-managed caches a token's auth result for up to
a minute, so poll for the failure for two minutes instead of ten seconds.

Claude-Session: https://claude.ai/code/session_01Pc6CwnZoTC4TU48MW68CNb

Co-authored-by: Claude <noreply@anthropic.com>
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