-
Notifications
You must be signed in to change notification settings - Fork 44
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
first version of alb cache #176
base: main
Are you sure you want to change the base?
Conversation
THANKS for your work so far! I have a preference to do the JWKS URI thing slightly different: let’s keep the file This will entail we cannot re-use the SimpleJwksCache (I think) and we should build something else (similar but different), a cache that has an LRU strategy of size 2 for JWKS uris. For verifying, make sure we check:
|
No worries, and thank for leading this feature! Ok to keep the AlbJwksParser as is. And Ok for reimplemeting the AwsAlbJwksCache without depending of the SimpleJwksCache. I didn't really like neither the AwsAlbJwksCache "URI expand" mechanism to stick to the SimpleJwksCache implementation. If I understand well your previous comment, you would prefer having the cache looking like the example below (with a LRU strategy):
To clarify and keep writing record why I believe it's unnecessary to include the region value in the cache key, here's my reasoning:
I just wanted to confirm that we are align before starting updating the code. |
Thanks mate! Ok so before I was thinking this would be the contents of the LRU cache (sample, it may have 0, 1 or max 2 entries like here):
But you're probably right that you only need key id as cache key though. On the other hand, is there a benefit to doing that? Does that make your implementation simpler? Note that this choice should be internal to the export interface JwksCache {
getJwk(jwksUri: string, decomposedJwt: DecomposedJwt): Promise<JwkWithKid>;
getCachedJwk(jwksUri: string, decomposedJwt: DecomposedJwt): JwkWithKid;
addJwks(jwksUri: string, jwks: Jwks): void;
getJwks(jwksUri: string): Promise<Jwks>;
} So the AwsAlbJwksCache must implement all these methods. I think here, jwksUri must be the base path without kid, so one of these formats (per the docs):
But since that does not work as cache key for us in this case, we need to either add kid to the url, or just use kid as the internal cache key (as you suggest). Sorry for the rambling, hope this helps. |
Idea to make headway, let's work backwards. Let's first now only design the interface of First stab: import { AwsAlbJwtVerifier } from "aws-jwt-verify";
const verifier = AwsAlbJwtVerifier.create({
issuer: "https://cognito-idp.{region}.amazonaws.com/{userPoolId}",
clientId: "{clientId}",
loadBalancerArn: "{loadBalancerArn}",
jwksUri: "https://public-keys.auth.elb.region.amazonaws.com", // optional, can default if off of the loadBalancerArn
}); Mmm I now realize this already makes the choice to keep the interface generic over IDPs, and not take a |
Thoughts? |
Oh, I am just learning about the AWS US gov cloud, thank you for sharing this knowledge. My thoughts about the
Except the little detail ont the issuer parameter, I agree with your interface proposal. |
Finally after more thoughts, you were right, we should keep the issuer as an URI for the use case when the ALB is configured with an custom OIDC Authorization server rather than cognito. So, I 100% agree with your interface proposal |
Yeah and I dont want to have to create a Ok let's stick to that interface for now then. Maybe we should not have the How about the interface of the const cache = new AwsAlbJwksCache({
fetcher: myCustomFetcher // optional, default to the same fetcher that is used by `SimpleJwksCache`
}); I don't think we need a About the 4 methods it should expose (per the interface
|
Hi Otto, Anyway, this is not the final version of the code. If you validate the code direction, here are the missing parts:
|
Thanks, and that's great! Mmmmm I see what you mean with "complex". I think instead of a linked list to implement the LRU cache we can also use a JS Let's also add the Penaltybox now. That will also help figuring out the right cache key --> I believe it should be the JWKS uri without kid, because it must not be under attacker control and the kid is under attacker control |
…LRU cache only if fetch is OK, otherwise create penalty
Thank you Otto for the JS For the reflexion about what would be a usefull and safe key cache, here is my thought:
However, I think there is a possible mitigation. We need to remember that this is an ALB feature that we are developing. In term of architecture, it's the ALB that will send the JWT token. The backend, if the infrastructure is clean, won't be accessible from outside (security group, private subnet, ..). It means that it's impossible for an attacker to send directly any forged token to the backend (again, only if the infrastructure is clean). In conclusion, for me, the solution 1 or 2 are acceptable. And personally, the solution 1 make more sens to me. And of course, in any case, we will have to validate that the kid is at the good format (UUID) if I remeber well to avoid URI injection. |
Think you're right. It's a DOS potential either way, and saturating the memory and CPU will be a lot harder to pull off than sending in a non-existing kid every X seconds. But then this means, we don't need or want to add a penaltybox. Let's add the reasoning as a comment in the code then. |
Hi Otto, architecture-beta
group aws(cloud)[AWS]
group public[Public subnets] in aws
group private[Private subnets] in aws
service user(internet)[user]
service as(server)[Cognito OR OIDC AS] in public
service alb1(server)[ALB 1] in public
service alb2(server)[ALB 2] in public
service api(server)[Server protected with aws jwt verifier] in private
user:R --> L:alb1
user:R --> L:alb2
alb1:R --> L:api
alb2:R --> L:api
alb1:R -- L:as
api:B -- T:as
(Some link are missing like between ALB2 and the AS, or between the user and the AS but the render was messy with them) There is a specificity with this architecture, compare to the classic architecture with several AS: the data token received by the server can have 2 signers (ALB1 and ALB2) and the same issuer (Cognito or AS OIDC). Ex of token (obfuscated data token extract from a real case, with 2 ALBs authenticated with the same Cognito User Pool): {
"header": {
"typ":"JWT",
"kid":"keyId",
"alg":"ES256",
"iss":"https://cognito-idp.us-west-2.amazonaws.com/us-west-2_123456",
"client":"clientId",
"signer":"arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/AAAAAAAAAAA/AAAAAAAAAAA",
"exp": 1516239022
},
"payload": {
"sub": "1234567890",
"email": "",
"username": "1234567890",
"exp": 1516239022,
"iss": "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_123456"
}
} Data token received by the server, when the user request was sent to the ALB1 {
"header": {
"typ":"JWT",
"kid":"keyId",
"alg":"ES256",
"iss":"https://cognito-idp.us-west-2.amazonaws.com/us-west-2_123456",
"client":"clientId",
"signer":"arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/BBBBBBBBBBB/BBBBBBBBBBB",
"exp": 1516239022
},
"payload": {
"sub": "1234567890",
"email": "",
"username": "1234567890",
"exp": 1516239022,
"iss": "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_123456"
}
} Data token received by the server, when the user request was sent to the ALB2 Only the signer header parameter changed. The question is, do we want to to have a MultiProperty feature for the AlbJwtVerifier, like with other verifiers ? If yes, when multi properties are declared, each parameter correspond to an AS or ALB ? What are your thought about these 2 questions please ? Below, the hypothesis is that your answer are the same than mine The JwtVerifierbase works well when there is several issuers to manage multi user pool properties for example. If we want that each property correspond to an ALB, we need to use the field signer/loadBalancerArn to index property (instead of issuer). I can see 2 options: Option 1: develop the AlbJwtVerifier by extendening the JwtVerifierBase (like CognitoJwtVerifier)I think this solution can work, but I haven't really tested it. Pros:
Cons:
Option 2: develop the AlbJwtVerifier from scratchThe development from scratch is not too diffucult. It will be really similar to the CognitoJwtVerifier. Pros:
Cons:
Note I am currently trying to understand how |
Thanks for the work, and laying out the options! Super helpful. Option 1 would be "most consistent" and the only reason my brain was favoring option 2 (from scratch / separate) was because I thought it would be simplest. But if you can see how feasible option 1 is, maybe it's doable enough.
|
me too
me too |
Add unit tests. Rename AlbJwtVerifer by JwtDataVerifier because it verify only the data jwt token.
Draft PR
#109
Description of changes:
Proposition for an AwsAlbJwksCache implementation.
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.