-
Couldn't load subscription status.
- Fork 2.1k
Add generic JWT detection and verification #4441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Note: the custom detector test failed in the I have seen this sporadic test failure a few times now. I'm pretty sure it's caused by iterating over a map (the order of which is unspecified in Go) in the custom detector code: |
Fixed and merged in #4446. |
|
A TODO item from the description:
I ran with this new JWT detection over 250k recent commits from GitHub. From there, about 4k distinct JWTs were found, and several dozen of them verified completely. The newly detected JWTs will be notable, but less volume than, say, |
|
@bradlarsen given clarification on the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work.
Looks good, left one minor comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a question about some verification errors this returns.
| // Attempt to verify a JWT that uses an HMAC algorithm. | ||
| // | ||
| // This implementation only attempts to verify JWTs whose issuers use the OIDC Discovery protocol to make public keys available via request. | ||
| func verifyHMAC(parsedToken *jwt.Token) (bool, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm generally opposed to functions that signify they can return things they can't actually return (an error in this case), because I think it usually adds unnecessary cognitive overhead during future maintenance. (I'm not requesting a change - I'm just flagging this because it looks like the elimination of any actually returned error might have happened in the course of writing this function rather than as your initial plan.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rosecodym thanks for pointing this out! I agree with you. And yeah, you're right, this code changed during implementation in response to comments that clarified indeterminate verification status.
In a previous commit, HMAC-signed JWTs whose other claims (timestamps, etc) checked out would be reported as indeterminate. But I later learned that that type is reserved today for essentially transient / network-related errors. So I changed the verifyHMAC function.
It's essentially dead code today, and perhaps should be ripped out entirely, to be added back in one day in the glorious future when we can more comfortably handle secrets that are probably live but not feasibly tested.
| // Attempt to verify a JWT that uses a public-key signing algorithm. | ||
| // | ||
| // This implementation only attempts to verify JWTs whose issuers use the OIDC Discovery protocol to make public keys available via request. | ||
| func verifyPublicKey(ctx context.Context, client *http.Client, tokenString string) (bool, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every single returned error here is going to cause an indeterminate result. Is that correct? The "verification error" state is designed to indicate that we couldn't verify a candidate finding because of uncontrollable external conditions, and some of these errors don't look like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rosecodym Yeah, you're right to remark on this. Thanks for the extra set of eyes.
JWTs are a tricky thing to do liveness checking on — different from just about all our other detectors. They are "stateless", and verification is done locally. (Once you get past the OIDC Discovery process at least.)
The oidcDiscoveryKeyFunc does return many error values for non-transient things, like when an issuer URL is missing or malformed. In these cases, indeed, we probably don't want to propagate the errors all the way to be indeterminate results at the finding level. I'll rework that.
I will point out for posterity that the current set of statuses (verified, unverified, and indeterminate due to external conditions) doesn't have a way of representing secrets that may be live but don't have a definitive way of doing verification.
For example: the current code will produce an indeterminate result for a JWT finding whose issuer comes from a non-routing host (localhost, internal IP, etc). These could in theory be checked for liveness, but the verification procedure would have to be performed from the appropriate location in the network. There is not a suitable status to use for this today.
Description:
This adds a generic detector and verifier for generic JWTs.
Detection is simple using regular expressions. However, this will produce many false positives. JWT verification is performed using the
github.com/golang-jwt/jwt/v5package, performing the usual checks (proper decoding, checking timestamp-related claims, checking for an allow-list of supported algorithms, etc). Only public key cryptography algorithms are supported. Additionally, OIDC Discovery is attempted against the issuer to fetch the public key for signature verification.Testing:
A few unit tests demonstrate the detector working. Further testing of verification is needed.
TODO: