Self-hosting documentation securely for an API platform with granular control on who can see what using filter policies

By Varun Om | October 31, 2019


Recently we've embarked on an adventure to move online documentation of an API platform for one of our clients from ReadMe to a self-hosted solution chiefly to provide granular access control capability required by the business. Self-hosting of documentation has two main elements:

  • Endpoints – This is the API reference section with OpenAPI specification for each endpoint
  • Articles – This section contains how-tos and other articles related to the system, workflows and security.

We're developing following endpoints (documentation APIs) to serve content for these two elements:

  1. GetDocumentedEndpoints – Returns a list of endpoints for which you can view the documentation.
  2. GetEndpointDocumentation – Returns the documentation (OpenAPI specification as JSON) for the given endpoint.
  3. GetArticles – Returns a list of articles you have access to.
  4. GetArticle – Returns the specific article.

Business requires a special capability to granularly control which API integrator should have access to what items in above two categories. For instance, to a prospective integrator, business desires to demonstrate only a few main of the hundreds of endpoints we have.

The one level of filter for endpoints is based on permission. I.e., you should only be able to view documentation for endpoints to which you have access. However, this is actually insufficient from the word go because we have lot of user related endpoints which we leverage on portals to serve these integrators (including above documentation APIs). Obviously, we don't want them to be using these APIs from backend systems and hence these aren't part of our integration offerings. Removing these endpoints from documentation for integrators makes perfect sense.

My answer to handle these requirements is filter policies.

Documentation Item Filter Policies

The policy definitions will help in excluding/including specific documentation items in response to calls made to documentation APIs based on the caller.
A filter policy has following properties

  • Type – Indicates on which item type the policy applies. As mentioned above we have two types of items (elements): endpoint and article.
  • NamePattern – Indicates name of the item to match. Following patterns are supported (these cannot be combined in one policy as of now):
    • “:public:” – Indicates matching all public endpoints (ones available without authentication).
    • “:role:<roleName>” – Indicates matching all endpoints permitted to a given role. The roleName should be a plain string (doesn't support any wildcards/regex pattern) and must match with an existing role within the system.
    • “<NameExpression>” – Indicates matching any endpoint name or article title that satisfies the pattern. Asterisk (*) is the only wildcard character supported and it connotes “match zero or more characters until a match for the subsequent character in the pattern is found or the end of string is reached.” It is replaced with ‘.*’ regular expression pattern internally. No regular expression support for now in nameExpressions as well. Everything other than asterisk is escaped before feeding to Regexp.
  • EntityType – Indicates the type of entity on which this policy is applicable. For instance, Insurer, Custodian, Sponsor are some of the entity types in a financial system.
  • EntityId – Indicates the specific entity on which this policy is applicable; can be null in which case it's applicable to all entity instances of specified EntityType.
  • Action – Indicates the action to be taken when a policy is found applicable for the documentation item under consideration. Possible values are Exclude (remove the item from response) and Include (Add the item to response).
  • CreatedDate – Indicates the date on which the policy was created. This also determines the order of evaluation when we have competing policies (see below).

Policy Evaluation

Instance specific policies override general policies. So, if a general policy excludes GetUser endpoint for all entities of type insurer but an instance policy includes it for a specific insurer say UHI, the documentation will be shown to UHI users for GetUser.

Policies are further ordered as per createdDate (ascending order). If we have two or more policies of the same type and namePattern, applicable to the same entityType and entityId, such policies are termed as competing policies. Ideally, we shouldn't have competing policies; in case we do, the order of evaluation will ensure the most recently created one will be able to override result of any prior competing policy.

Share on: