Why can a Kerberos TGS-REQ ask for a renewable, forwardable service ticket and get one with neither flag set?
answer
- asking is not getting
- two fields, one word
- the parent ticket bounds the child
- a reduced grant is still success
- granted flags mirrored in enc-part
basics
~20 sRequesting a flag is not getting it. The client's kdc-options in KDC-REQ-BODY are a request; the TicketFlags inside EncTicketPart are the grant, bounded by realm policy and by the flags on the ticket-granting ticket presented. A reduced grant returns as success, not an error.
solid answer
~50 sTwo different fields are involved and only one of them is the client's. `kdc-options` in the `KDC-REQ-BODY` is what the client **asks** for; `TicketFlags` in the ticket's `EncTicketPart` is what the ticket-granting service **granted**. The grant is bounded by realm and principal policy and by the presented ticket-granting ticket itself — a ticket derived from one that is not `forwardable` cannot come back `forwardable`, and `renewable` is capped by the configured maximum renewable life. Some flags are not negotiable at all: `initial(9)` marks a ticket issued through the initial authentication exchange, so a ticket obtained through a TGS exchange never carries it, whatever was asked. Crucially there is **no error** for a reduced grant — the reply is a normal `TGS-REP`, and the client learns the truth only by reading the granted `flags` in its `enc-part`.
code
asn1 · 20 lines-- what the client ASKS (in the TGS-REQ)
KDC-REQ-BODY ::= SEQUENCE {
kdc-options [0] KDCOptions, -- renewable, forwardable, ...
realm [2] Realm, -- srealm of the target service
sname [3] PrincipalName OPTIONAL,
till [5] KerberosTime,
rtime [6] KerberosTime OPTIONAL,
nonce [7] UInt32,
etype [8] SEQUENCE OF Int32
}
-- what the KDC GRANTED (inside the issued ticket)
EncTicketPart ::= [APPLICATION 3] SEQUENCE {
flags [0] TicketFlags, -- the authoritative answer
key [1] EncryptionKey,
authtime [5] KerberosTime,
starttime [6] KerberosTime OPTIONAL,
endtime [7] KerberosTime,
renew-till [8] KerberosTime OPTIONAL
}go deeper
Recall that the client asks for ticket properties and the KDC decides them. The request and the issued ticket are not the same thing.
Distinguish kdc-options in the request body from TicketFlags in the issued ticket, and name what bounds the grant: the parent ticket's own flags and realm policy.
Diagnose the silent case — a job that fails hours after a request that logged success — and show the check that catches it: read the granted flags and times out of the reply's client-readable half at issue time.
Consider what realm-wide lifetime policy costs across workloads you do not own: short maxima limit exposure from a stolen ticket but push long-running work into renewal machinery that must itself be reliable.
## Two fields, two meanings, one word "Flag" is doing double duty in this exchange, and conflating the two is the defect this question hunts for. | | where it lives | who writes it | what it means | |---|---|---|---| | `kdc-options` | `KDC-REQ-BODY` of the `TGS-REQ` | the client | the request: *please* make it renewable, forwardable, postdated | | `TicketFlags` (`flags`) | `EncTicketPart`, inside the issued ticket | the ticket-granting service | the grant: what this ticket actually permits | The bit positions overlap by design — `forwardable(1)`, `proxiable(3)`, `may-postdate(5)`, `postdated(6)`, `renewable(8)` read the same in both places — which is exactly why an engineer reading a trace can believe the ask and the grant are the same field. ## What bounds the grant 1. **The presented ticket-granting ticket.** The ticket-granting service derives a ticket from the one it was handed. Capabilities the parent ticket does not carry cannot be conjured: no `forwardable` parent, no `forwardable` child. 2. **Realm and principal policy.** Maximum ticket lifetime and maximum renewable lifetime are policy values. A `till` beyond the maximum is silently clamped to the maximum, and a request to make a ticket renewable when policy forbids it simply comes back non-renewable. 3. **Protocol rules that no policy can relax.** `initial(9)` means "issued by the initial authentication exchange, not on the basis of a ticket-granting ticket". A ticket from a TGS exchange is by definition the second case, so the flag is never set. Services that insist on `initial` — the ones that want the user to have authenticated *just now* — are relying on precisely that. 4. **Inheritance of provenance.** `pre-authent(10)` records that the client was authenticated before the first ticket was issued, and it propagates from the parent ticket into tickets derived from it. ## The flags a client will meet - `forwardable(1)` and `proxiable(3)` — asked for here; what a ticket bearing them enables is a separate subject with its own mechanisms. - `renewable(8)` — the ticket may be renewed, bounded by `renew-till`. - `may-postdate(5)` and `postdated(6)` — a ticket valid from a future `starttime`. - `invalid(7)` — set on a postdated ticket, which must be validated before any service will accept it. A ticket handed out with `invalid` set is a grant that looks successful and works nowhere until a further exchange clears it. - `initial(9)`, `pre-authent(10)` — provenance, written by the KDC, never requested. ## Why this bites in production The overnight render is the classic case. An operator sets up a job that must hold a ticket for eight hours, asks for a renewable ticket, sees a `TGS-REP` come back, and treats the absence of an error as confirmation. The realm's maximum renewable life is two hours. At 02:00 the job stops with an authentication failure, and every log line at the time of the *request* says success — because it was one. The protocol has no "partially granted" response code; a reduced grant is an ordinary reply. The fix is mechanical: **read the grant, not the ask**. The granted `flags`, `starttime`, `endtime` and `renew-till` are mirrored in the reply's `enc-part`, so a client can compare what it received with what it needed at the moment it received it, rather than discovering the gap hours later. A ticket cache utility displays exactly those mirrored values. ## Where the granted answer is visible The client cannot open the ticket, so it cannot read `TicketFlags` from the place that matters. It does not need to: the reply's `enc-part` — the half sealed under the ticket-granting session key, or under the `subkey` the request's `Authenticator` proposed — mirrors the granted `flags` along with `authtime`, `starttime`, `endtime`, `renew-till`, `srealm` and `sname`. Everything a client needs in order to notice a reduced grant is therefore in its hands at the moment the reply arrives, and a ticket cache utility showing flags on a ticket it cannot decrypt is displaying those mirrored values rather than the ticket's own. The practical consequence is that there is no excuse for discovering the gap later. The check is a comparison between two values the client already holds: what it put in `kdc-options` and what came back in `flags`. ## The reading that gets candidates into trouble Saying "I requested `forwardable`, so the ticket is forwardable" is the inversion to avoid. So is the opposite overcorrection — assuming the KDC always strips what it cannot honour and therefore that nothing needs checking. Neither is a rule: the ticket-granting service honours what policy and the parent ticket allow, quietly, and the client is the only party in a position to notice the difference.
- Which ticket flag can a TGS exchange never set, and who cares?`initial(9)`. It means the ticket came from the initial authentication exchange rather than from a ticket-granting ticket, so a ticket issued by the ticket-granting service never carries it. Services that want evidence the user authenticated in person, rather than reusing a morning sign-in, check for exactly that flag.
- A ticket comes back with invalid(7) set. What does the client have?A postdated ticket that no service will accept yet. Its `starttime` is in the future, and the ticket must be validated through a further exchange with the KDC once that time arrives before it is usable. It is a grant that looks successful and works nowhere until then.
- How should a client detect that it got less than it asked for?Compare the granted `flags`, `endtime` and `renew-till` in the reply's `enc-part` against what it needed, immediately on receipt. There is no error code for a reduced grant, so the only alternative is discovering it at the moment the capability is exercised — typically hours later and somewhere else.
saying these in an interview costs you the question
- Assumes a requested flag is always present in the issued ticket
- Says the KDC returns an error when it cannot honour an option
- Thinks initial(9) can be requested on a service ticket
- Believes a ticket can be more capable than the TGT it came from
- Treats maximum ticket lifetime as a protocol constant rather than realm policy