Fine grain route access policy

Hello,

I'm very new to apigee (on prem), and I'd like pointers on how to implement a security feature we used to have on our previous (homemade) gateway.

Let's say I have a rest API with URLs starting with /users/{id}/...

I'd like to restrict the access to a particular {id} depending on the credentials of the client.

Ex: client A have access to /users/123/...

client B have access to /users/456/...

client C have access to only GET requests but for any user id /users/*/...

How would I tackle that? I've looked into the RegularExpressionProtection policy but it doesn't seem like it can be linked to the credentials (mTLS so CN) nor can I retrieve the verb.

Thanks!

Solved Solved
0 5 147
1 ACCEPTED SOLUTION

Yes, you can do what you want.  The RegExp protection policy isn't the right thing. 

I think what you want to do is:

  • In the app:
    • set a custom attribute to refer to the specific valid value for the {id} path segment
  • In the proxy:
    • use ExtractVariables to extract the url path segment from the inbound request. 
    • use VerifyAPIKey or OAuthV2/VerifyAccessToken to verify the inbound credentials, and in doing so, implicitly retrieve the valid value for the {id} segment into a context variable.  After VerifyAPIKey, the variable name is verifyapikey.{policy_name}.{name_of_custom_attr}.  After VerifyAccessToken, the variable is  app.{name_of_custom_attr} 
    • Use a Condition in the flow to compare the extracted path segment to the valid path segment.  If no match, then RaiseFault with 403.  

Does this make sense? 

The ExtractVariables will be like this: 

<ExtractVariables name='EV-1'>
   <Source>request</Source>
   <VariablePrefix>extracted</VariablePrefix>
   <URIPath>
      <!-- put the extracted value into extracted.id -->
      <!-- NB: The Pattern is matched against pathsuffix (what comes AFTER the basepath) -->
      <Pattern ignoreCase='true'>/users/{id}</Pattern>
   </URIPath>
   <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</ExtractVariables>

Assuming your custom attr is named "valid-id", and you are using VerifyAccessToken, the Flow will look like this: 

<Step>
  <Name>EV-ID-from-Path</Name>
</Step>
<Step>
  <Name>OAuthV2-VerifyAccessToken</Name>
</Step>
<Step>
  <Condition>NOT(extracted.id = app.valid-id)</Condition>
  <Name>RF-Invalid-Id</Name>
</Step>

This is how you set a custom attr on an app. 

custom-attr-on-app.png

 

View solution in original post

5 REPLIES 5

Yes, you can do what you want.  The RegExp protection policy isn't the right thing. 

I think what you want to do is:

  • In the app:
    • set a custom attribute to refer to the specific valid value for the {id} path segment
  • In the proxy:
    • use ExtractVariables to extract the url path segment from the inbound request. 
    • use VerifyAPIKey or OAuthV2/VerifyAccessToken to verify the inbound credentials, and in doing so, implicitly retrieve the valid value for the {id} segment into a context variable.  After VerifyAPIKey, the variable name is verifyapikey.{policy_name}.{name_of_custom_attr}.  After VerifyAccessToken, the variable is  app.{name_of_custom_attr} 
    • Use a Condition in the flow to compare the extracted path segment to the valid path segment.  If no match, then RaiseFault with 403.  

Does this make sense? 

The ExtractVariables will be like this: 

<ExtractVariables name='EV-1'>
   <Source>request</Source>
   <VariablePrefix>extracted</VariablePrefix>
   <URIPath>
      <!-- put the extracted value into extracted.id -->
      <!-- NB: The Pattern is matched against pathsuffix (what comes AFTER the basepath) -->
      <Pattern ignoreCase='true'>/users/{id}</Pattern>
   </URIPath>
   <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</ExtractVariables>

Assuming your custom attr is named "valid-id", and you are using VerifyAccessToken, the Flow will look like this: 

<Step>
  <Name>EV-ID-from-Path</Name>
</Step>
<Step>
  <Name>OAuthV2-VerifyAccessToken</Name>
</Step>
<Step>
  <Condition>NOT(extracted.id = app.valid-id)</Condition>
  <Name>RF-Invalid-Id</Name>
</Step>

This is how you set a custom attr on an app. 

custom-attr-on-app.png

 

just re-reading what you wrote. It seems like the client credentials are neither an API key nor an access Token, which you would validate with VerifyAPIKey or VerifyAccessToken respectively.  But instead it's an x509 certificate, and you want to check the path segment against a valid segment for a particular certificate.  Is that right? 

If that's the case, then  you could use an approach that is similar to the above, but... you'd need to  find a way to map from the certificate CN to the valid path segment.  One way to do that is attach the valid path as an attribute on the cert itself.  Another way is just to have a mapping/lookup table - either managed in the Apigee KVM or externally - that maps the cert sha256 fingerprint to the valid id for that cert.  That "mapping" or "lookup" would replace the VerifyApiKey or VerifyAccessToken in my explanation above.  Everything else remains the same.

If you want to attach the valid path segment value as an attribute of the cert, you will need to parse the certificate with something like this: https://github.com/yuriylesyuk/eidas-x509-for-psd2 

Thank you for your detailed answer, this is exactly what I'm trying to achieve.

Just a precision, when you say I would replace VerifyApiKey with a lookup (cn -> allowed id), could I use a KeyValueMapOperations policy using the flow variable client.cn as a key?
It would give me this flow:
ExtractVariables(URI->id) -> KeyValueMapOperations(client.cn -> valid_id) -> Condition(id==valid_id)

Does this look correct to  you?

Yes that would work and that looks correct to me. 
you’ll want to take care regarding the CN -> ID mapping. How many CN’s are we talking about here? If there are  o(10) then having a single entry in the KVM for each CN might be reasonable. If there are o(100) or more then managing the mapping that way might be tedious. In that case you could encode the mapping as a JSON hash, and retrieve the entire thing with the kvm get, and then select the right value with a jsonpath function within AssignMessage or a JavaScript step. 

For the moment o(10) but I'll keep that in mind
Thank you so much for your support!