I’ve recently been working on a project with a swagger specification.
(If you wanna just get right into it, here’s the goods)
A few things lead me to try experimenting creating valid nginx server blocks from a swagger.yaml:
- “paths” is required.
- “paths” should mostly contain only request methods.
- Nginx has a way to limit requests to specific methods.
- Ansible can directly read from a yaml file for variables.
For setup, I used a swagger.yaml with paths “/pets”, and “/pets/{id}” with basePath of “/myswaggerapi.” I used this sample as my template.
This also lead me to realize that Jinja2 includes no way to really do a real regex replace, as for correctness sake, any path with {...}
should be replaced with .*
and have ~
prepended to location
in nginx, to denote regex matching.
(Note: ~*
should not be required, as your path’s should be lowercase. (Thought: maybe I should just use .lower() on the path…) )
So I created a regex_replace filter after reading this StackOverflow post.
This leads to the meat and potatoes of the path part of the template:
location {% if "{" in path %}~ {% endif %}{% if basePath is defined %}{{ basePath }}{% endif %}{{ path | regex_replace("{.*}",".*") }}
This makes location /myswaggerapi/pets {...
for the /pets path and location ~ /myswaggerapi/pets/.* {...
for the /pets/{id} path. This means all requests to swagger.tld/myswaggerapi/pets
will be forwarded to the first chunk, and any valid request containing anything will be passed to the second bit, to be validated by the actual swagger API.
(Note: This could be done by nginx, but for now it’s left up to the API)
Then, for security reasons and as it should be included in the paths, I limit requests by request type to each path using the following line:
limit_except {% for method in paths[path] %}{% if method != "parameters" %}{{ method.upper() }} {% endif %}{% endfor %}
This produces limit_except GET POST {...
for the /pets path, and limit_except GET {...
for the /pets/{id} path. Is it actually more secure? Well… maybe? It sure feels like it though!
I believe this could be used for CI using a job to Ansible to push your updated paths to an include file included in the correct server block to CI your API paths, allowing for dead simple updates to API end points without ever having to touch your nginx config (an nginx reload should still be required, but those are completely atomic).
For a working example and complete role of the above, here’s my repo.