<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matthew Hegarty</title>
    <description>The latest articles on DEV Community by Matthew Hegarty (@matthewhegarty).</description>
    <link>https://dev.to/matthewhegarty</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F149489%2F437b4c66-144e-433f-841e-c0ed460b80e4.jpeg</url>
      <title>DEV Community: Matthew Hegarty</title>
      <link>https://dev.to/matthewhegarty</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matthewhegarty"/>
    <language>en</language>
    <item>
      <title>Troubleshooting AWS Batch and PrivateLink</title>
      <dc:creator>Matthew Hegarty</dc:creator>
      <pubDate>Wed, 22 Apr 2020 13:47:06 +0000</pubDate>
      <link>https://dev.to/matthewhegarty/troubleshooting-aws-batch-and-privatelink-3o84</link>
      <guid>https://dev.to/matthewhegarty/troubleshooting-aws-batch-and-privatelink-3o84</guid>
      <description>&lt;p&gt;&lt;a href="https://aws.amazon.com/batch/" rel="noopener noreferrer"&gt;AWS Batch&lt;/a&gt; is a powerful platform for running batch jobs, but setting up can be difficult, especially if your environment uses private subnets and you want to make use of AWS &lt;a href="https://aws.amazon.com/privatelink/" rel="noopener noreferrer"&gt;PrivateLink&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One source of frustration is that your jobs get stuck in a 'RUNNABLE' state and don't progress.  Here are some pointers you can use for troubleshooting this issue.&lt;/p&gt;

&lt;p&gt;Read through the &lt;a href="https://docs.aws.amazon.com/batch/latest/userguide/troubleshooting.html" rel="noopener noreferrer"&gt;AWS Batch Troubleshooting guide&lt;/a&gt; and '&lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/batch-job-stuck-runnable-status/" rel="noopener noreferrer"&gt;Why is my AWS Batch job stuck in RUNNABLE status&lt;/a&gt;'.  If you carefully work through these guides then they should lead you to the source of the issue.&lt;/p&gt;

&lt;p&gt;One thing to &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/launch_container_instance.html" rel="noopener noreferrer"&gt;highlight&lt;/a&gt; is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Container instances need access to communicate with the Amazon ECS service endpoint. This can be through an interface VPC endpoint or through your container instances having public IP addresses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are using a &lt;a href="https://aws.amazon.com/privatelink/" rel="noopener noreferrer"&gt;NATGateway&lt;/a&gt;, then this shouldn't be a problem, because this is a standard solution to communicating with web resources from private subnets.&lt;/p&gt;

&lt;p&gt;However, I was configuring service access via &lt;a href="https://aws.amazon.com/privatelink/" rel="noopener noreferrer"&gt;PrivateLink&lt;/a&gt;, meaning that requests are not routed over the public internet.  Using PrivateLink brings with it additional steps for successfully running AWS Batch jobs.&lt;/p&gt;

&lt;h4&gt;
  
  
  ECS Instances not being created in the ECS cluster
&lt;/h4&gt;

&lt;p&gt;Your batch job goes to RUNNABLE, but you don't see any ECS cluster instances created (refer to '&lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/batch-job-stuck-runnable-status/" rel="noopener noreferrer"&gt;troubleshooting&lt;/a&gt;' for more on this).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Refer to &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/vpc-endpoints.html" rel="noopener noreferrer"&gt;Creating the VPC Endpoints for Amazon ECS&lt;/a&gt;.  Your containers are going to need to be able to access the following endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;com.amazonaws.&amp;lt;your region&amp;gt;.ecs-agent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazonaws.&amp;lt;your region&amp;gt;.ecs-telemetry&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazonaws.&amp;lt;your region&amp;gt;.ecs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;The Security Group associated with each endpoint need to be able to send 'all traffic' outbound requests - refer to the &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/batch-job-stuck-runnable-status/" rel="noopener noreferrer"&gt;troubleshooting guide&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;The Security Group associated with the endpoint(s) needs to be able to accept TCP requests on port 443.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Jobs run but then fail with 'CannotPullECRContainerError'
&lt;/h4&gt;

&lt;p&gt;Refer to &lt;a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/vpc-endpoints.html" rel="noopener noreferrer"&gt;this guide&lt;/a&gt; for setting up VPC Endpoints to ECR.&lt;/p&gt;

&lt;p&gt;The key points is that you will need two VPC Endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;com.amazonaws.&amp;lt;your region&amp;gt;.ecr.dkr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazonaws.&amp;lt;your region&amp;gt;.ecr.api&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I found setup of AWS Batch with VPC Endpoints was not straightforward, but hopefully some of the pointers in this guide can save you time if you are stuck.&lt;/p&gt;

&lt;h4&gt;
  
  
  Other Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/compute/using-aws-cloudformation-to-create-and-manage-aws-batch-resources/" rel="noopener noreferrer"&gt;Using AWS CloudFormation to Create and Manage AWS Batch Resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/compute/orchestrating-an-application-process-with-aws-batch-using-aws-cloudformation/" rel="noopener noreferrer"&gt;Orchestrating an application process with AWS Batch using AWS CloudFormation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/doi-t/01e5241c9595e7b8e3540f0125bd4519" rel="noopener noreferrer"&gt;Getting started with AWS Batch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>batch</category>
      <category>privatelink</category>
    </item>
    <item>
      <title>Postgresql: Better security for Django Applications</title>
      <dc:creator>Matthew Hegarty</dc:creator>
      <pubDate>Tue, 31 Mar 2020 19:54:22 +0000</pubDate>
      <link>https://dev.to/matthewhegarty/postgresql-better-security-for-django-applications-3c7m</link>
      <guid>https://dev.to/matthewhegarty/postgresql-better-security-for-django-applications-3c7m</guid>
      <description>&lt;p&gt;Whilst Django is intended to be database-agnostic, it supports many features of Postgresql which are &lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/" rel="noopener noreferrer"&gt;not shared by other databases&lt;/a&gt;, and this makes Postgres a popular choice of database for Django applications.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://security.berkeley.edu/education-awareness/best-practices-how-tos/system-application-security/database-hardening-best" rel="noopener noreferrer"&gt;core principle of web application security&lt;/a&gt; is that the application should use the 'least privilege' model, meaning that the database permissions for the application are locked-down so that the application has only the permissions it needs to do its job, and no more.&lt;/p&gt;

&lt;p&gt;For example, imagine that an attacker was able to exploit a web application bug and run their own SQL commands.  This would be bad, but the severity could be minimised if the attacker was restricted to a certain set of operations.&lt;/p&gt;

&lt;p&gt;In this blog post, I'm going to demonstrate how we can use Postgres schema and roles to ensure that Django application privileges are limited appropriately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample Application
&lt;/h3&gt;

&lt;p&gt;To demonstrate the concepts, I've created a clone of the &lt;a href="https://github.com/matthewhegarty/rest-framework-tutorial/tree/postgres-permissions" rel="noopener noreferrer"&gt;Django Rest Framework Tutorial&lt;/a&gt; which runs on Docker.&lt;/p&gt;

&lt;p&gt;Feel free to clone the repo and run with &lt;code&gt;docker-compose up&lt;/code&gt; (&lt;code&gt;Ctrl+C&lt;/code&gt; to stop).&lt;/p&gt;

&lt;p&gt;Once the app is running: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse to &lt;a href="http://localhost:8000/" rel="noopener noreferrer"&gt;http://localhost:8000/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;password123&lt;/code&gt; to login.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To start from scratch, run &lt;code&gt;docker-compose down -v&lt;/code&gt;, followed by &lt;code&gt;docker-compose up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NB&lt;/strong&gt; The sample application includes hard-coded passwords (in &lt;code&gt;docker-compose.yml&lt;/code&gt;) - this is just to make life easier for demonstration purposes, and you should never hard-code passwords in a real application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database initialisation
&lt;/h3&gt;

&lt;p&gt;When the Postgres docker container starts for the first time, it runs an &lt;a href="https://hub.docker.com/_/postgres" rel="noopener noreferrer"&gt;initialisation script&lt;/a&gt;, and this is where the database is created, and restrictions are applied.&lt;/p&gt;

&lt;p&gt;Using containers in this way makes it very easy to initialise and run an application consistently.  If you are not using Docker, then use another method of initialising the Postgres database.  For example, an external hook which runs when the db is initialised, such as an AWS Lambda function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Postgres schemas
&lt;/h3&gt;

&lt;p&gt;Postgres has the concept of &lt;a href="https://www.postgresql.org/docs/10/ddl-schemas.html" rel="noopener noreferrer"&gt;schemas&lt;/a&gt;, and this forms a useful basis for securing our application.  A schema is effectively a namespace.  If you have a table called &lt;code&gt;Customer&lt;/code&gt;, and a schema called &lt;code&gt;App&lt;/code&gt;, then the &lt;code&gt;Customer&lt;/code&gt; table can be isolated to within the &lt;code&gt;App&lt;/code&gt; schema.&lt;/p&gt;

&lt;p&gt;By explicitly defining roles which have access to a schema, we can be confident that a database user (i.e. our Django application) is accessing only those tables and sequences which they are permitted to do so.&lt;/p&gt;

&lt;p&gt;Postgres has a default schema, called &lt;code&gt;public&lt;/code&gt;, and unless otherwise specified, database users can access and create objects in this public schema.  This creates a potential security weakness which we will remove.&lt;/p&gt;

&lt;h3&gt;
  
  
  Postgres roles
&lt;/h3&gt;

&lt;p&gt;A Postgres role can be created and given explicit privileges.  A database user can then be assigned to the role.  For example, we could create a 'read-only' role and a 'read-write' role.  Permissions could be restricted according to these roles.&lt;/p&gt;

&lt;p&gt;By explicitly defining privileges, we are clear about what given roles are permitted to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  DB initialisation walkthrough
&lt;/h3&gt;

&lt;p&gt;There are some particular nuances with defining schemas and roles for use with Django.  In the rest of this post, I'll walk through the steps required to restrict user access for Django.  &lt;/p&gt;

&lt;p&gt;Specifically, our script needs to address the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a database and define schemas and roles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a 'django migrator' user which has permission to create our application objects (i.e.  those created when &lt;code&gt;manage.py migrate&lt;/code&gt; is run).  Additionally, the 'django migrator' user must be allowed to create databases, because this is necessary when &lt;a href="https://docs.djangoproject.com/en/3.0/topics/testing/overview/#the-test-database" rel="noopener noreferrer"&gt;tests are run&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a 'django application' user.  This user will have appropriate read and write privileges on our application tables.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The initialisation script
&lt;/h3&gt;

&lt;p&gt;Our script will run only when the database container is &lt;a href="https://hub.docker.com/_/postgres" rel="noopener noreferrer"&gt;started for the first time&lt;/a&gt;.  The script will connect to the Postgres db using the default user.&lt;br&gt;
The full script is visible &lt;a href="https://github.com/matthewhegarty/rest-framework-tutorial/blob/postgres-permissions/docker/db/init-user-db.sh" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note that environment variables referenced here (e.g. &lt;code&gt;$DB_NAME&lt;/code&gt;) are defined in &lt;a href="https://github.com/matthewhegarty/rest-framework-tutorial/blob/postgres-permissions/docker-compose.yml" rel="noopener noreferrer"&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create our application database
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;createdb &lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Define a &lt;a href="https://tldp.org/LDP/abs/html/here-docs.html" rel="noopener noreferrer"&gt;heredoc&lt;/a&gt; to run a script through psql.  The connection to psql is made using the default Postgres user.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;--dbname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOSQL&lt;/span&gt;&lt;span class="sh"&gt;
...
&lt;/span&gt;&lt;span class="no"&gt;EOSQL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Lockdown the 'public' schema.  This prevents creation of new objects unless given permission.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;REVOKE&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;PUBLIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Prevent &lt;em&gt;any&lt;/em&gt; connection to the new database unless explicitly given
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;REVOKE&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;DB_NAME&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;PUBLIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt; Create Migrator role.  The migrator user is allowed to create databases for the purposes of running the Django test suite.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;MIGRATOR_DB_USER&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'$MIGRATOR_DB_PASS'&lt;/span&gt; &lt;span class="k"&gt;CREATEDB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;AUTHORIZATION&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;MIGRATOR_DB_USER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt; The migrator user must have access to the 'public' schema because that is where new db tables for the test db will be created.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;MIGRATOR_DB_USER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;SEARCH_PATH&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt; Create an 'application user' role.  This user must only be allowed to query the application schema.  The &lt;a href="https://www.postgresql.org/docs/10/ddl-schemas.html#DDL-SCHEMAS-PATH" rel="noopener noreferrer"&gt;'search path'&lt;/a&gt; is defined to ensure that only our application schema is visible to the application db user.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;DB_USER&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'$DB_PASS'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;DB_USER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;SEARCH_PATH&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt; Create a role which can connect to the db and has read / write access to the application schema only.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;RW_ROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;DB_NAME&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;RW_ROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;RW_ROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;RW_ROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;SEQUENCES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;RW_ROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you examine the &lt;code&gt;GRANT&lt;/code&gt; statements above, you can see how it would be possible to restrict access further for other types of roles.  For example, we could define a 'read-only' role which allowed only &lt;code&gt;SELECT&lt;/code&gt; privileges.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Grant the defined read / write role to our db users.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;RW_ROLE&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;MIGRATOR_DB_USER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;RW_ROLE&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;DB_USER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt; Alter &lt;a href="https://www.postgresql.org/docs/10/sql-alterdefaultprivileges.html" rel="noopener noreferrer"&gt;default privileges&lt;/a&gt; for read / write role.  This is a necessary step because otherwise the role will not have access to any tables which are created later.  That will include tables created in the &lt;code&gt;manage.py migrate&lt;/code&gt; step, and that would render the application inaccessible to users!
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;MIGRATOR_DB_USER&lt;/span&gt; &lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;RW_ROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;MIGRATOR_DB_USER&lt;/span&gt; &lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;SEQUENCES&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;RW_ROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We can see the above steps in the output produced when Postgres is initialised.  Once complete, it will mean that we have two users, one of whom has restricted privileges to access database objects.&lt;/p&gt;
&lt;h3&gt;
  
  
  Application entrypoint
&lt;/h3&gt;

&lt;p&gt;If you are familiar with Docker, you will know that you can define an &lt;a href="https://docs.docker.com/engine/reference/builder/#entrypoint" rel="noopener noreferrer"&gt;entrypoint&lt;/a&gt; script which will be run when the container starts.&lt;/p&gt;

&lt;p&gt;We will use this script to run our migrations and start the server.&lt;/p&gt;
&lt;h3&gt;
  
  
  Referencing the Migrator User
&lt;/h3&gt;

&lt;p&gt;Recall that we have defined our 'migrator user' to have the appropriate permissions to create tables (and other objects).  We want to use this user when our migrations are run.  &lt;/p&gt;

&lt;p&gt;This is achieved by defining an additional &lt;code&gt;settings_migrator.py&lt;/code&gt; file, which overrides the db connection parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DATABASES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;migrator_user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  
&lt;span class="n"&gt;DATABASES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MIGRATOR_DB_PASS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We must reference this overridden settings file when we run &lt;code&gt;migrate&lt;/code&gt; (in &lt;a href="https://github.com/matthewhegarty/rest-framework-tutorial/blob/postgres-permissions/docker/entrypoint.sh" rel="noopener noreferrer"&gt;&lt;code&gt;entrypoint.sh&lt;/code&gt;&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./manage.py migrate &lt;span class="nt"&gt;--settings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tutorial.settings_migrator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will not override settings anywhere else in the entrypoint script, which means that all other &lt;code&gt;manage.py&lt;/code&gt; commands will run using our application user.&lt;/p&gt;

&lt;p&gt;Note that if you exclude the &lt;code&gt;--settings&lt;/code&gt; argument, then the migration task will fail, because it doesn't have appropriate permissions.  You can try this out by removing the argument from &lt;code&gt;entrypoint.sh&lt;/code&gt;, and running &lt;code&gt;docker-compose down -v; docker-compose up --build&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on testing
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, by default Django requires permission to create databases when running tests.  This means that tests also need to be run using the &lt;code&gt;--settings&lt;/code&gt; flag which defines the connection details for the 'migrator user'.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;This post has shown how we can restrict database access for our Django applications using Postgres features.  Two users were defined in the Postgres db.  We then used one 'migrator user' to create run our migrations.  The application user was given read / write privileges to the application schema only.&lt;/p&gt;

&lt;p&gt;When building web applications, restricting access rights at the database level is an important security consideration, therefore it is worthing spending some time understanding how Postgres configuration can help.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sellonen/django-security-tips" rel="noopener noreferrer"&gt;django security tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/database/managing-postgresql-users-and-roles/" rel="noopener noreferrer"&gt;Managing Postgres users and roles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>djangorestframework</category>
      <category>django</category>
      <category>postgres</category>
      <category>docker</category>
    </item>
    <item>
      <title>SwaggerUI inside Django Rest Framework</title>
      <dc:creator>Matthew Hegarty</dc:creator>
      <pubDate>Thu, 28 Mar 2019 11:58:06 +0000</pubDate>
      <link>https://dev.to/matthewhegarty/swaggerui-inside-django-rest-framework-1c2p</link>
      <guid>https://dev.to/matthewhegarty/swaggerui-inside-django-rest-framework-1c2p</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;An API schema provides a standard definition of the details of your API, which can be rendered in interactive web pages, or can be used to generate client code.&lt;/p&gt;

&lt;p&gt;In this post I describe how to modify a Django Rest Framework (DRF) application to serve an existing OpenAPI (aka Swagger) schema definition rendered using &lt;a href="https://swagger.io/tools/swagger-ui/" rel="noopener noreferrer"&gt;Swagger UI&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figbv76zmtpd3uyikowjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figbv76zmtpd3uyikowjd.png" alt="Swagger UI screenshot" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A sample application is available &lt;a href="https://github.com/matthewhegarty/rest-framework-tutorial/tree/swagger-ui" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not use a framework?
&lt;/h3&gt;

&lt;p&gt;DRF does support &lt;a href="https://www.django-rest-framework.org/api-guide/schemas/" rel="noopener noreferrer"&gt;generation of schemas using OpenAPI&lt;/a&gt;, however support for OpenAPI was only added in &lt;a href="https://www.django-rest-framework.org/community/3.9-announcement/#built-in-openapi-schema-support" rel="noopener noreferrer"&gt;release 3.9&lt;/a&gt;,  and it is still in its early stages.  It's worth checking DRF for ongoing OpenAPI support because this is being actively developed.&lt;/p&gt;

&lt;p&gt;If you are working with Swagger (OpenAPI v2), then you can potentially use &lt;a href="https://github.com/axnsan12/drf-yasg/" rel="noopener noreferrer"&gt;drf-yasg&lt;/a&gt; to generate and serve a schema view.  drf-yasg is a third-party plugin for DRF.  &lt;/p&gt;

&lt;p&gt;However, if you are using OpenAPI v3 (OpenAPI is the new name for Swagger), or have a pre-existing schema definition file, then using a third-party framework may be impossible or impractical, and the next best thing might be to render a static schema file, as I describe here.&lt;/p&gt;

&lt;p&gt;There are other options in DRF for rendering a schema which don't involve OpenAPI (see the &lt;a href="https://www.django-rest-framework.org/api-guide/schemas/" rel="noopener noreferrer"&gt;DRF&lt;/a&gt;) docs), although DRF is moving towards supporting OpenAPI.&lt;/p&gt;

&lt;h3&gt;
  
  
  What do we need to do?
&lt;/h3&gt;

&lt;p&gt;To serve an existing API schema file from DRF, we need to do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Define the schema definition file.&lt;/li&gt;
&lt;li&gt; Configure DRF to serve the schema file.&lt;/li&gt;
&lt;li&gt; Install Swagger UI as a static resource.&lt;/li&gt;
&lt;li&gt; Create a Django Template to serve the UI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These steps are described in detail below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install the DRF Tutorial
&lt;/h3&gt;

&lt;p&gt;I have used a fork of the &lt;a href="https://github.com/matthewhegarty/rest-framework-tutorial/tree/swagger-ui" rel="noopener noreferrer"&gt;DRF tutorial&lt;/a&gt; to demonstrate the steps required.&lt;br&gt;
You can clone the repo and run locally to see how it fits together.&lt;br&gt;
The fork includes a &lt;a href="https://github.com/matthewhegarty/rest-framework-tutorial/blob/swagger-ui/snippets/static/openapi/schema.yaml" rel="noopener noreferrer"&gt;&lt;code&gt;schema.yaml&lt;/code&gt;&lt;/a&gt; file for the tutorial project.&lt;/p&gt;

&lt;p&gt;You can install the clone as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone git@github.com:matthewhegarty/rest-framework-tutorial.git
cd rest-framework-tutorial

# Create a virtualenv to isolate our package dependencies locally
virtualenv env
source env/bin/activate  
pip install -r requirements.txt

export DJANGO_SETTINGS_MODULE=tutorial.settings
python manage.py migrate

# when prompted, create a suitable password
python manage.py createsuperuser --email user@example.com --username admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run the server with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test the API
&lt;/h3&gt;

&lt;p&gt;If the tutorial is installed correctly, then you should be able to browse &lt;a href="http://localhost:8000/" rel="noopener noreferrer"&gt;http://localhost:8000/&lt;/a&gt; and see the default API Root.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw44c7hbhrduyneeon8xp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw44c7hbhrduyneeon8xp.png" alt="DRF API root" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now login as Admin using the link at the top right hand side of the page, and log in using the credentials you created earlier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the schema
&lt;/h3&gt;

&lt;p&gt;First of all we need to serve the Schema definition.  OpenAPI definitions can be written in either yaml or json.  In this example, yaml is used.&lt;/p&gt;

&lt;p&gt;As of DRF 3.9, you can &lt;a href="https://www.django-rest-framework.org/api-guide/schemas/#generating-a-schema-with-the-generateschema-management-command" rel="noopener noreferrer"&gt;generate a schema&lt;/a&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py generateschema &amp;gt; schema.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the output of &lt;code&gt;generateschema&lt;/code&gt; is only going to be a stub of your API, and you will likely need to use this as a starting point, and add the specifics of your API.&lt;/p&gt;

&lt;p&gt;As you work on your schema, you can validate it by copying the source definition into the &lt;a href="https://editor.swagger.io/" rel="noopener noreferrer"&gt;Swagger editor tool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have already created a schema for the rest-framework-tutorial application&lt;br&gt;
&lt;a href="https://github.com/matthewhegarty/rest-framework-tutorial/blob/swagger-ui/snippets/static/openapi/schema.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Serve the schema
&lt;/h3&gt;

&lt;p&gt;Once the schema is ready, it should be checked into source control.  It can now be served as part of the application.&lt;/p&gt;

&lt;p&gt;Create a 'static' directory under the application root, in which we will put static web content to serve to clients.  For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p snippets/static/openapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Move your &lt;code&gt;schema.yaml&lt;/code&gt; into this new directory.&lt;/p&gt;

&lt;h4&gt;
  
  
  Update URLs
&lt;/h4&gt;

&lt;p&gt;Now edit the URLs file (&lt;code&gt;tutorial/urls.py&lt;/code&gt;), and add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.conf.urls.static import static
from tutorial import settings

urlpatterns = [
# Leave the existing urls defined here
] + static(settings.STATIC_URL)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is making use of Django's static file serving functionality, and you should read Django's &lt;a href="https://docs.djangoproject.com/en/dev/howto/static-files/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; on this topic.&lt;/p&gt;

&lt;p&gt;Restart the server if necessary, and now hitting the endpoint should download the schema file, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget http://localhost:8000/static/openapi/schema.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install Swagger UI
&lt;/h3&gt;

&lt;p&gt;The next step is to install the Swagger UI distribution into our static files, so that it can be served alongside the application.&lt;/p&gt;

&lt;p&gt;Clone the &lt;a href="https://github.com/swagger-api/swagger-ui" rel="noopener noreferrer"&gt;Swagger UI repo&lt;/a&gt; locally.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create static directory for Swagger UI
&lt;/h4&gt;

&lt;p&gt;Create another directory under your &lt;code&gt;static&lt;/code&gt; root to serve the SwaggerUI files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p snippets/static/openapi/swagger-dist-ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now copy the contents of SwaggerUI's &lt;a href="https://github.com/swagger-api/swagger-ui/tree/master/dist" rel="noopener noreferrer"&gt;dist&lt;/a&gt; directory into the &lt;code&gt;swagger-dist-ui&lt;/code&gt; directory you just created, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp -av ../swagger-ui/dist/* snippets/static/openapi/swagger-dist-ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a Django Template for Swagger UI
&lt;/h3&gt;

&lt;p&gt;Our final step is to configure Django to serve Swagger UI.  To do this we need to create a template which Django can serve SwaggerUI files from.&lt;/p&gt;

&lt;p&gt;Create a new directory for the template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p snippets/templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now move the SwaggerUI index.html into this directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mv snippets/static/openapi/swagger-dist-ui/index.html snippets/templates/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Define Template Directory in Config
&lt;/h4&gt;

&lt;p&gt;After the above step, check that your config references the templates directory correctly.  In &lt;code&gt;tutorial/settings.py&lt;/code&gt;, add the 'templates' directory to &lt;code&gt;DIRS&lt;/code&gt; (leave all other config as is):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# tutorial/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'],
        ...
    },
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configure Template
&lt;/h4&gt;

&lt;p&gt;Now we need to edit the &lt;code&gt;index.html&lt;/code&gt; file so that it references static resources such as Swagger UI's js and css files.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;index.html&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Add the &lt;code&gt;{% load static %}&lt;/code&gt; directive to the top of the file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Work through the file and modify all static file references to use a&lt;br&gt;
Django template directive.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, on &lt;a href="https://github.com/swagger-api/swagger-ui/blob/master/dist/index.html#L7" rel="noopener noreferrer"&gt;line 7&lt;/a&gt;, change the &lt;code&gt;href&lt;/code&gt; reference from &lt;code&gt;./swagger-ui.css&lt;/code&gt; to &lt;code&gt;{% static "openapi/swagger-dist-ui/swagger-ui.css" %}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Do this for all other file references in &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change the &lt;a href="https://github.com/swagger-api/swagger-ui/blob/master/dist/index.html#L42" rel="noopener noreferrer"&gt;url reference&lt;/a&gt; in &lt;code&gt;SwaggerUIBundle&lt;/code&gt; to reference your schema file: &lt;code&gt;url: "{% static "openapi/schema.yaml" %}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The end product should look something like &lt;a href="https://github.com/matthewhegarty/rest-framework-tutorial/blob/swagger-ui/snippets/templates/index.html" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Add a URL reference
&lt;/h4&gt;

&lt;p&gt;Last of all, we need to add a reference to the template in &lt;code&gt;urls.py&lt;/code&gt; in order to serve the &lt;code&gt;index.html&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.views.generic import TemplateView

urlpatterns = [
    url('openapi/', TemplateView.as_view(template_name="index.html")),
    url(r'^', include(router.urls))
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test the UI
&lt;/h3&gt;

&lt;p&gt;Now if we browse to &lt;a href="http://localhost:8000/openapi/" rel="noopener noreferrer"&gt;http://localhost:8000/openapi/&lt;/a&gt; we should see the Swagger UI rendered with our schema.yaml.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figbv76zmtpd3uyikowjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figbv76zmtpd3uyikowjd.png" alt="Swagger UI screenshot" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In this post I've covered rendering an existing schema file in Swagger UI and serving the UI as part of a Django Rest Framework project.&lt;/p&gt;

&lt;p&gt;This approach works well in Development, but is not suitable for Production use.  Refer to the &lt;a href="https://docs.djangoproject.com/en/2.1/howto/static-files/deployment/" rel="noopener noreferrer"&gt;Django documentation&lt;/a&gt; for further guidance on serving static files in Production.&lt;/p&gt;

&lt;p&gt;Further support for OpenAPI / Swagger is planned for Django Rest Framework, so this process might be refined in future DRF releases.  Follow the &lt;a href="https://www.django-rest-framework.org/community/release-notes/" rel="noopener noreferrer"&gt;release notes&lt;/a&gt; for updates.&lt;/p&gt;

</description>
      <category>djangorestframework</category>
      <category>openapi</category>
      <category>swagger</category>
      <category>django</category>
    </item>
  </channel>
</rss>
