Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ Options:
#### Model Details
* [Models](#models)
* [Notes on Schemas](#notes-on-schemas)
#### Response Headers
* [CORS](#cors)
* [OWASP Secure Headers](#owasp)

### OpenAPI Mapping

Expand Down Expand Up @@ -729,6 +732,80 @@ You can automatically generate CORS response headers by setting `cors` at the fu

The generator will interpret your settings for CORS and automatically add the response headers. If for whatever reason you wish to override these, you can set them via the above `responseHeaders` setting and it'll apply your overrides.

##### OWASP

You can make use of the [OWASP Secure Headers](https://owasp.org/www-project-secure-headers/#x-permitted-cross-domain-policies) to generate response headers. These are a selection of response headers with default values that OWASP recommends returning with your response to help secure your application.

The OWASP Secure Headers Project contains a set of recommended headers to return with recommended values, when generating the documentation, the generator will attempt to get the latest version of this document and apply the latest recommendations. If you do not allow outside connections, it will default to a version of recommendations from **2023-05-26 12:22:30 UTC**.

Like CORS, if you have already set any of the OWASP Secure headers via `responseHeaders`, it will not overwrite them.

To make use of OWASP Secure Headers, you can use the following:

###### All OWASP Secure Headers

```yml
methodResponse:
- statusCode: 200
responseBody:
description: Success
responseModels:
application/json: "CreateResponse"
owasp: true
```

This will use the full set of OWASP Secure Headers and their recommended values. Some of these might not be appropriate for your application.

###### Subset of OWASP Secure Headers

```yml
methodResponse:
- statusCode: 200
responseBody:
description: Success
responseModels:
application/json: "CreateResponse"
owasp:
cacheControl: true
referrerPolicy: true
```

This will set only the `cacheControl` and `referrerPolicy` response header with the default recommendations.

The full list of OWASP Secure Headers you can set are:

* cacheControl - Cache-Control,
* clearSiteData - Clear-Site-Data,
* contentSecurityPolicy - Content-Security-Policy,
* crossOriginEmbedderPolicy - Cross-Origin-Embedder-Policy,
* crossOriginOpenerPolicy - Cross-Origin-Opener-Policy,
* crossOriginResourcePolicy - Cross-Origin-Resource-Policy,
* permissionsPolicy - Permissions-Policy,
* pragma - Pragma,
* referrerPolicy - Referrer-Policy,
* strictTransportSecurity - Strict-Transport-Security,
* xContentTypeOptions - X-Content-Type-Options,
* xFrameOptions - X-Frame-Options,
* xPermittedCrossDomainPolicies - X-Permitted-Cross-Domain-Policies

###### Subset of OWASP Secure Headers with user defined values

If you wish to override the OWASP Secure Headers, you can write your `methodResponse` like:

```yml
methodResponse:
- statusCode: 200
responseBody:
description: Success
responseModels:
application/json: "CreateResponse"
owasp:
cacheControl:
value: no-store
```

This will set the `Cache-Control` Response Header to have a value of "no-store" rather than any value the OWASP Secure Headers Project recommends.

## Example configuration

Please view the example [serverless.yml](test/serverless-tests/serverless%202/serverless.yml).
Expand Down
57 changes: 57 additions & 0 deletions json/owasp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"last_update_utc": "2023-05-26 12:22:30",
"headers": [
{
"name": "Cache-Control",
"value": "no-store, max-age=0"
},
{
"name": "Clear-Site-Data",
"value": "\"cache\",\"cookies\",\"storage\""
},
{
"name": "Content-Security-Policy",
"value": "default-src 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content"
},
{
"name": "Cross-Origin-Embedder-Policy",
"value": "require-corp"
},
{
"name": "Cross-Origin-Opener-Policy",
"value": "same-origin"
},
{
"name": "Cross-Origin-Resource-Policy",
"value": "same-origin"
},
{
"name": "Permissions-Policy",
"value": "accelerometer=(),ambient-light-sensor=(),autoplay=(),battery=(),camera=(),display-capture=(),document-domain=(),encrypted-media=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),layout-animations=(self),legacy-image-formats=(self),magnetometer=(),microphone=(),midi=(),oversized-images=(self),payment=(),picture-in-picture=(),publickey-credentials-get=(),speaker-selection=(),sync-xhr=(self),unoptimized-images=(self),unsized-media=(self),usb=(),screen-wake-lock=(),web-share=(),xr-spatial-tracking=()"
},
{
"name": "Pragma",
"value": "no-cache"
},
{
"name": "Referrer-Policy",
"value": "no-referrer"
},
{
"name": "Strict-Transport-Security",
"value": "max-age=31536000 ; includeSubDomains"
},
{
"name": "X-Content-Type-Options",
"value": "nosniff"
},
{
"name": "X-Frame-Options",
"value": "deny"
},
{
"name": "X-Permitted-Cross-Domain-Policies",
"value": "none"
}
]
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "serverless-openapi-documenter",
"version": "0.0.62",
"version": "0.0.63",
"description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
"main": "index.js",
"keywords": [
Expand Down
38 changes: 32 additions & 6 deletions src/definitionGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { v4: uuid } = require('uuid')
const validator = require('oas-validator');

const SchemaHandler = require('./schemaHandler')
const oWASP = require('./owasp')

class DefinitionGenerator {
constructor(serverless, options = {}) {
Expand Down Expand Up @@ -41,15 +42,15 @@ class DefinitionGenerator {

this.DEFAULT_CORS_HEADERS = {
'Access-Control-Allow-Origin': {
description: 'The Access-Control-Allow-Origin response header indicates whether the response can be shared with requesting code from the given origin.',
description: 'The Access-Control-Allow-Origin response header indicates whether the response can be shared with requesting code from the given [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin). - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)',
schema: {
type: 'string',
default: '*',
example: 'https://developer.mozilla.org'
}
},
'Access-Control-Allow-Credentials': {
description: `The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to the frontend JavaScript code when the request's credentials mode (Request.credentials) is include`,
description: `The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to the frontend JavaScript code when the request's credentials mode ([Request.credentials](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)) is include. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials)`,
schema: {
type: 'boolean',
default: true
Expand All @@ -68,6 +69,8 @@ class DefinitionGenerator {
async parse() {
this.createInfo()

await oWASP.getLatest()

await this.schemaHandler.addModelsToOpenAPI()
.catch(err => {
throw err
Expand Down Expand Up @@ -379,21 +382,44 @@ class DefinitionGenerator {
})
}

let owaspHeaders = {}
if (response.owasp) {
if (typeof response.owasp === 'boolean') {
owaspHeaders = await this.createResponseHeaders(oWASP.DEFAULT_OWASP_HEADERS)
.catch(err => {
throw err
})
} else {
owaspHeaders = await this.createResponseHeaders(oWASP.getHeaders(response.owasp))
.catch(err => {
throw err
})
}
}

const corsHeaders = await this.corsHeaders()
.catch(err => {
throw err;
})

if (obj.headers) {
for (const key in corsHeaders) {
const addHeaders = (headers) => {
for (const key in headers) {
if (!(key in obj.headers) && (obj.headers[key] = {})) {
obj.headers[key] = corsHeaders[key]
obj.headers[key] = headers[key]
}
}
}

if (obj.headers) {
addHeaders(corsHeaders)
addHeaders(owaspHeaders)
} else {
if (Object.keys(corsHeaders).length)
if (Object.keys(corsHeaders).length) {
obj.headers = corsHeaders
addHeaders(owaspHeaders)
} else {
obj.headers = owaspHeaders
}
}

Object.assign(responses, { [response.statusCode]: obj })
Expand Down
Loading