Skip to content

Kerberos credential delegation: allowDelegation() and --allow-delegate (#141) - #182

Merged
bertysentry merged 1 commit into
mainfrom
141-kerberos-credential-delegation-allowdelegation-and-cli---allow-delegate-winrs--allowdelegate
Sep 27, 2026
Merged

bertysentry merged 1 commit into
mainfrom
141-kerberos-credential-delegation-allowdelegation-and-cli---allow-delegate-winrs--allowdelegate

Conversation

@bertysentry

@bertysentry bertysentry commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Closes #141.

What

Kerberos credential delegation, like winrs -allowdelegate: the remote command can use the caller's credentials to reach a further host (a UNC path, AD, another server) instead of failing with access denied on the second hop.

try (WinRMClient client = WinRMClient.builder("server.example.net")
        .https()
        .authentication(AuthScheme.KERBEROS)
        .allowDelegation()                       // connection-scoped, Kerberos only
        .credentials("DOMAIN\\user", password)
        .build()) {
    client.command("dir \\\\fileserver\\share").execute();
}
winrm-java -h server -u 'DOMAIN\user' -pf pw.txt --https --kerberos --allow-delegate exec dir '\\fileserver\share'
  • KerberosAuthScheme sets requestCredDeleg(true) iff delegation was requested (it used to hardcode false).
  • Fail loudly when the TGT is not forwardable. GSS silently drops the delegation request in that case; the scheme checks getCredDelegState() after the first token and fails with a message that says to set forwardable = true in [libdefaults] (or kinit -f). The CLI exits with 77.
  • Rejected without Kerberos. build() fails when Kerberos is not among the schemes (the check sits in LightWinRMService.resolveAuthScheme, where every entry point goes through, next to the Kerberos-over-HTTP rejection). The CLI gives a usage error for --allow-delegate without --kerberos. In a (KERBEROS, NTLM) fallback, a connection that falls back to NTLM is not delegated (documented).
  • One new public overload, LightWinRMService.createInstance(endpoint, timeout, ticketCache, authentications, allowDelegation, ...); the retries overload forwards to it with false. WinRMClient now calls LightWinRMService directly instead of going through WinRMExecutorFactory, a pure pass-through kept for the legacy API. This avoids adding a matching factory overload. allowDelegation sits next to authentications, the setting it qualifies, not at the end: appending a 10th parameter made the telescoping overloads share a 9-parameter prefix, which trips the CPD gate.

Also fixed: Kerberos errors lost their message

Subject.doAs wraps checked exceptions in a PrivilegedActionException that has no message of its own. So every GSS failure from initSecContext reached users as the bare text org.metricshub.winrm.exceptions.WinRMException. That includes the common Server not found in Kerberos database (connecting by IP), and it happened on main too. authenticate() now rethrows the wrapped exception. Before/after, connecting by IP:

winrm-java: org.metricshub.winrm.exceptions.WinRMException
winrm-java: No valid credentials provided (Mechanism level: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7)))

Live validation (tc-win2016, SENTRY domain, HTTPS, SENTRY\dev-admin)

Scenario Result
Kerberos, no delegation: dir \\camus...\SYSVOL Access is denied. (exit 1); klist on the host shows only the HTTP service ticket
--allow-delegate, forwardable krb5.conf: same dir lists the share (exit 0); the host holds a forwarded TGT plus the cifs/… and ldap/… tickets it obtained with it
--allow-delegate, ls \\camus...\SYSVOL (remote file API) works
PowerShell [adsisearcher] AD lookup from the host fails without delegation, works with it
--allow-delegate without forwardable = true in krb5.conf clear message, exit 77
--allow-delegate + --kerberos-kdc + a krb5.conf that only says forwardable = true works (the JDK reads the file in addition to the properties)
--allow-delegate without --kerberos usage error, exit 64
WinRMLiveTest#kerberosDelegationReachesTheSecondHop (new, gated on -Dwinrm.live.delegation.unc) passes

One finding differs from the issue text: tc-win2016 is not trusted for delegation in AD (its HTTP service ticket has no ok_as_delegate flag), and delegation still works. The JDK forwards the TGT whenever requestCredDeleg(true) is set. Windows clients such as winrs only delegate to hosts trusted for delegation. So OK-AS-DELEGATE is not a prerequisite for this client, and the docs say so, with the matching warning to only delegate to hosts you trust. Decision: this permissive behavior is intentional, so delegation works out of the box for library users. Following the Windows policy (requestDelegPolicy, delegate only to trusted hosts) would require unconstrained-delegation trust in AD, which many shops forbid.

Tests

  • KerberosAuthSchemeTest (new): the context requests delegation iff asked, and the check fails when an initialized context does not delegate. It needs no KDC and no krb5.conf: it uses a realm-qualified name, because a host-based one needs a default realm to resolve.
  • WinRMClientBuilderTest.delegationRequiresKerberos, CliArgumentsTest (parse and usage error), WinRmCliTest (help line).
  • mvn clean verify site on JDK 17 (the CI command) is green.

Docs

  • Authentication: a new Credential delegation section (prerequisites, the krb5.conf snippet, the AD finding, the fallback note, no CredSSP) and a line in the CLI part.
  • CLI manual: the options table, the Kerberos section, and an example.
  • The second hop (preparing-the-host) and the UNC note (files) now point to delegation instead of "not supported yet".

🤖 Generated with Claude Code

#141)

Let remote commands use the caller's Kerberos credentials to reach a
further host (the second hop), like winrs -allowdelegate:

- WinRMClient.Builder.allowDelegation() and the CLI's --allow-delegate
  make KerberosAuthScheme request credential delegation, so the host
  receives a forwarded TGT.
- GSS silently drops the request when the TGT is not forwardable: the
  scheme now fails with a message pointing to forwardable = true in
  krb5.conf, instead of the command failing later with access denied.
- Rejected without Kerberos, at build() and at CLI parse time.
- Kerberos errors raised inside Subject.doAs no longer lose their
  message (PrivilegedActionException has none of its own), e.g.
  "Server not found in Kerberos database".

Verified live against a domain host: the second hop works with
delegation and is denied without, even though the host is not trusted
for delegation in AD.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T11:08:13.706489Z b461f99 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@bertysentry
bertysentry merged commit 6e705a8 into main Sep 27, 2026
5 checks passed
@bertysentry
bertysentry deleted the 141-kerberos-credential-delegation-allowdelegation-and-cli---allow-delegate-winrs--allowdelegate branch September 27, 2026 21:41
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.

Kerberos credential delegation: allowDelegation() and CLI --allow-delegate (winrs -allowdelegate)

1 participant