Hey all,
So I have experience writing the actual logic of microservices but I'm pretty new to the art of rigging them together and the whole API Management/Gateway/Proxy/Specification ecosystem(s) have been doing my head in for the past week or so.
I'm trying to build an API to face organizational clients. I'd like to eventually offer this in terms of an organization holding a set of individually revokable API keys of different roles, but to get something out I'm just aiming to have one API key per org.
I have a definition, in both OpenApi 2 and 3, it's a REST-y kind of api - GET /resourceAs/{uuid}/subResourceB/{uuid} and I'd like to implement it with firebase/google functions cos that's my wheelhouse, but I'm happy to try other backend platforms if they work better. The stumbling block I feel like I have hit is in configuring org-level permissions - that is I need to somehow communicate in a secure manner to the backend service that APIGee has authenticated the request as containing an API key owned by organization X (where my backend service can then handle the implication that Organization X owns the resource at /resourceAs/{uuid}/subResourceB/{uuid}).
I feel like the answer is some combination of JWT and or opaque tokens? I don't want the user's to need to have completed authentication with me other than their possession of an API key transmitted in the headers. I had a look at the Google cloud functions extension but it seems like seeing as it only trigger's HTTPS functions I can't just trust (from the backend's perspective) content added to the headers/payload sent in the Policy there (such as the custom attribute from a manually registered developer's App) - or am I wrong in thinking that?
Essentially I am looking for a way to assert some claims associated with a particular App's API key, in a secure way to my backend service. Can anyone help a hapless developer thrown into the devops arena?
Solved! Go to Solution.
There are numerous ways to communicate that information from an API Gateway like Apigee Edge into a backend system like Firebase or Google Cloud Functions. (or AppEngine, or etc)
if the inbound call is carrying an access token, then the API Proxy should call VerifyAccessToken. After successful verification, there will be context variables populated including
(see the doc for full details)
That perhaps isn't quite the information that you seek when you say "Organization X". But take heart, there is more power here.
The easiest way to attach the information about "organiztion X" to a request that the proxy sends to the backend is sometimes known as "header injection". The API Gateway can simply add a new header to the request that is sent to the backend, and this header can contain whatever you like. Do this with AssignMEssage and a Set element.
<AssignMessage name="AM-InjectHeader'> <Set> <Headers> <Header name='my-new-header'>{variable_name_from_the_token}</Header> </Headers> </Set> </AssignMessage>
If you want additional information in the header, the "text value" in the XML config is a Message Template. This means you can refer to multiple distinct variables within curly braces and the value of the header is then derived from all those referenced variables.
If the API proxy receives a request like this:
POST /v1/foo/bar Content-Type: application/json Accept: */* Authorization: Beadrer ABC12345
The backend will see something like this:
POST /v1/foo/bar Content-Type: application/json Accept: */* Authorization: Bearer ABC12345 My-New-Header: value-of-referenced-variable-here
You can also (probably should) remove the token before invoking the backend. You can o this with assignMEssage also:
<AssignMessage name="AM-RemoveAuthzHeader'> <Remove> <Headers> <Header name='authorization'/> </Headers> </Remove> </AssignMessage>
And optionally you can combine those AssignMessage policies into one:
<AssignMessage name="AM-AdjustHeaders'> <Remove> <Headers> <Header name='authorization'/> </Headers> </Remove> <Set> <Headers> <Header name='my-new-header'>{variable_name_from_the_token}</Header> </Headers> </Set> </AssignMessage>
You mentioned a JWT. The value of "my-new-header' can just as easily be a JWT, generated within the Apigee Edge proxy via the GenerateJWT policy, asserting various claims. You would do this if you wanted an extra level of security in the backend; it can verify the signature on the JWT before trusting the claims within it.
In my experience, this is more than is necessary, unless you have a somewhat complex dependency graph in your services mesh, in which service A calls B, which calls C, which calls D and E and F, and some of those call G and H and so on. And each service may wish to assert some claims. In that case a receiving service might want to understand which of the previous services has asserted claims, and you could use a signed JWT to verify the integrity and issuer of claims, at any point in the graph, in a zero-trust environment.
But often that is more than you need. Often a simple text header saying "Organization X" is suitable and satisfactory.
There are numerous ways to communicate that information from an API Gateway like Apigee Edge into a backend system like Firebase or Google Cloud Functions. (or AppEngine, or etc)
if the inbound call is carrying an access token, then the API Proxy should call VerifyAccessToken. After successful verification, there will be context variables populated including
(see the doc for full details)
That perhaps isn't quite the information that you seek when you say "Organization X". But take heart, there is more power here.
The easiest way to attach the information about "organiztion X" to a request that the proxy sends to the backend is sometimes known as "header injection". The API Gateway can simply add a new header to the request that is sent to the backend, and this header can contain whatever you like. Do this with AssignMEssage and a Set element.
<AssignMessage name="AM-InjectHeader'> <Set> <Headers> <Header name='my-new-header'>{variable_name_from_the_token}</Header> </Headers> </Set> </AssignMessage>
If you want additional information in the header, the "text value" in the XML config is a Message Template. This means you can refer to multiple distinct variables within curly braces and the value of the header is then derived from all those referenced variables.
If the API proxy receives a request like this:
POST /v1/foo/bar Content-Type: application/json Accept: */* Authorization: Beadrer ABC12345
The backend will see something like this:
POST /v1/foo/bar Content-Type: application/json Accept: */* Authorization: Bearer ABC12345 My-New-Header: value-of-referenced-variable-here
You can also (probably should) remove the token before invoking the backend. You can o this with assignMEssage also:
<AssignMessage name="AM-RemoveAuthzHeader'> <Remove> <Headers> <Header name='authorization'/> </Headers> </Remove> </AssignMessage>
And optionally you can combine those AssignMessage policies into one:
<AssignMessage name="AM-AdjustHeaders'> <Remove> <Headers> <Header name='authorization'/> </Headers> </Remove> <Set> <Headers> <Header name='my-new-header'>{variable_name_from_the_token}</Header> </Headers> </Set> </AssignMessage>
You mentioned a JWT. The value of "my-new-header' can just as easily be a JWT, generated within the Apigee Edge proxy via the GenerateJWT policy, asserting various claims. You would do this if you wanted an extra level of security in the backend; it can verify the signature on the JWT before trusting the claims within it.
In my experience, this is more than is necessary, unless you have a somewhat complex dependency graph in your services mesh, in which service A calls B, which calls C, which calls D and E and F, and some of those call G and H and so on. And each service may wish to assert some claims. In that case a receiving service might want to understand which of the previous services has asserted claims, and you could use a signed JWT to verify the integrity and issuer of claims, at any point in the graph, in a zero-trust environment.
But often that is more than you need. Often a simple text header saying "Organization X" is suitable and satisfactory.
Hey Dino,
Thanks very much, this is a lot like what my first thought had been but my worry was the ability to trust information in the headers on the backend came from a trusted source (in this case APIGee) - Would I be correct in thinking that the steps for configuring two way TLS would provide that surety? Although actually I am not sure I can have much control over the certificate GCF use for https connections. That's why I was thinking maybe I needed a JWT to be able to trust this from the backend's PoV, but was kind of hoping there was a simpler way.
Yes, correct. The 2-way TLS would assure the upstream (backend) that the request was surely coming from a peer that holds the key. If you provision the key only once - to the Apigee Edge endpoint - then TLS will insure:
It's possible that you don't have control over the Google Cloud Functions TLS config. I'm not familiar enough to comment. [ Edit - just heard back from the PM For Google Cloud Functions. He tells me there is no way to configure mutual TLS for GCF. You get 1-way TLS for free]
In that case, sure, you can bubble up to the application layer and use the same principle on a JWT. Instead of relying on the transport layer security, you would be relying on application layer security. And here again the foundaiton of trust is that the Apigee Edge endpoint is the only holder of the private key used to sign the JWT.
was kind of hoping there was a simpler way.
It's actually pretty simple to create a JWT within Apigee Edge and then inject it as a header into the request that gets sent to the backend. You'd need 2 policies.
First GenerateJWT.
<GenerateJWT name='GenerateJWT-1'> <Algorithm>PS256</Algorithm> <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables> <PrivateKey> <Value ref="private.key"/> </PrivateKey> <Subject ref="jwt_subject" /> <Issuer>urn://my-apigee-edge-proxy</Issuer> <AdditionalClaims> <Claim name='user_organization' ref='name_of_context_variable_holding_information'/> </AdditionalClaims> <ExpiresIn>1h</ExpiresIn> <OutputVariable>generated_jwt</OutputVariable> </GenerateJWT> <br>
And then the AssignMessage
<AssignMessage name='AM-1'> <Set> <Headers> <Header name='my-header'>{generated_jwt}</Header> </Headers> </Set> </AssignMessage>
If you put that in the request flow, then the request sent to the backend will have a new header, containing a JWT, signed by Apigee Edge. It would be up to the Cloud Function to verify the signature and then extract the claims and do something with that JWT.
Amazing, thanks very much for the in depth and specific help!
User | Count |
---|---|
2 | |
1 | |
1 | |
1 | |
1 |