Skip to content

Commit 8796636

Browse files
authored
Merge pull request #21 from brokenhandsio/vapor4
Vapor 4
2 parents f265e2e + 20c0f7e commit 8796636

18 files changed

+222
-289
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
name: CI
22
on:
3-
- push
3+
push:
4+
pull_request:
45
jobs:
56
xenial:
67
container:
7-
image: vapor/swift:5.1-xenial
8+
image: vapor/swift:5.2-xenial
89
runs-on: ubuntu-latest
910
steps:
1011
- uses: actions/checkout@master
11-
- run: swift test --enable-test-discovery --enable-code-coverage
12+
- run: swift test --enable-test-discovery --enable-code-coverage --sanitize=thread
1213
bionic:
1314
container:
14-
image: vapor/swift:5.1-bionic
15+
image: vapor/swift:5.2-bionic
1516
runs-on: ubuntu-latest
1617
steps:
1718
- uses: actions/checkout@master
1819
- name: Run Bionic Tests
19-
run: swift test --enable-test-discovery --enable-code-coverage
20+
run: swift test --enable-test-discovery --enable-code-coverage --sanitize=thread
2021
- name: Setup container for codecov upload
2122
run: apt-get update && apt-get install curl
2223
- name: Process coverage file
23-
run: llvm-cov show .build/x86_64-unknown-linux/debug/VaporSecurityHeadersPackageTests.xctest -instr-profile=.build/x86_64-unknown-linux/debug/codecov/default.profdata > coverage.txt
24+
run: llvm-cov show .build/x86_64-unknown-linux-gnu/debug/VaporSecurityHeadersPackageTests.xctest -instr-profile=.build/debug/codecov/default.profdata > coverage.txt
2425
- name: Upload code coverage
2526
uses: codecov/codecov-action@v1
2627
with:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
DerivedData/
66
Package.pins
77
Package.resolved
8+
.swiftpm/

Package.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
// swift-tools-version:4.0
1+
// swift-tools-version:5.2
22

33
import PackageDescription
44

55
let package = Package(
66
name: "VaporSecurityHeaders",
7+
platforms: [
8+
.macOS(.v10_15)
9+
],
710
products: [
811
.library(name: "VaporSecurityHeaders", targets: ["VaporSecurityHeaders"]),
912
],
1013
dependencies: [
11-
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
14+
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
1215
],
1316
targets: [
14-
.target(name: "VaporSecurityHeaders", dependencies: ["Vapor"]),
17+
.target(name: "VaporSecurityHeaders", dependencies: [
18+
.product(name: "Vapor", package: "vapor")
19+
]),
1520
.testTarget(name: "VaporSecurityHeadersTests", dependencies: ["VaporSecurityHeaders"]),
1621
]
1722
)

README.md

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<br>
44
<br>
55
<a href="https://swift.org">
6-
<img src="http://img.shields.io/badge/Swift-4.1-brightgreen.svg" alt="Language">
6+
<img src="http://img.shields.io/badge/Swift-5.2-brightgreen.svg" alt="Language">
77
</a>
88
<a href="https://github.com/brokenhandsio/VaporSecurityHeaders/actions">
99
<img src="https://github.com/brokenhandsio/VaporSecurityHeaders/workflows/CI/badge.svg?branch=master" alt="Build Status">
@@ -34,16 +34,37 @@ These headers will *help* prevent cross-site scripting attacks, SSL downgrade at
3434

3535
# Usage
3636

37-
To use Vapor Security Headers, just register the middleware with your services and add it to your `MiddlewareConfig`. Vapor Security Headers makes this easy to do with a `build` function on the factory. In `configure.swift` add:
37+
## Add the package
38+
39+
Add the package as a dependency in your `Package.swift` manifest:
40+
41+
```swift
42+
dependencies: [
43+
...,
44+
.package(url: "https://github.com/brokenhandsio/VaporSecurityHeaders.git", from: "3.0.0")
45+
]
46+
```
47+
48+
Then add the dependency to your target:
49+
50+
```swift
51+
.target(name: "App",
52+
dependencies: [
53+
// ...
54+
"VaporSecurityHeaders"]),
55+
```
56+
57+
## Configuration
58+
59+
To use Vapor Security Headers, you need to add the middleware to your `Application`'s `Middlewares`. Vapor Security Headers makes this easy to do with a `build` function on the factory. **Note:** if you want security headers added to error reponses (recommended), you need to initialise the `Middlewares` from fresh and add the middleware in _after_ the `SecuriyHeaders`. In `configure.swift` add:
3860

3961
```swift
4062
let securityHeadersFactory = SecurityHeadersFactory()
41-
services.register(securityHeadersFactory.build())
4263

43-
var middlewareConfig = MiddlewareConfig()
44-
// ...
45-
middlewareConfig.use(SecurityHeaders.self)
46-
services.register(middlewareConfig)
64+
application.middleware = Middlewares()
65+
application.middleware.use(securityHeadersFactory.build())
66+
application.middleware.use(ErrorMiddleware.default(environment: application.environment))
67+
// Add other middlewares...
4768
```
4869

4970
The default factory will add default values to your site for Content-Security-Policy, X-XSS-Protection, X-Frame-Options and X-Content-Type-Options.
@@ -55,7 +76,7 @@ x-frame-options: DENY
5576
x-xss-protection: 1; mode=block
5677
```
5778

58-
***Note:*** You should ensure you set the security headers as the last middleware in your `MiddlewareConfig` (i.e., the first middleware to be applied to responses) to make sure the headers get added to all responses.
79+
***Note:*** You should ensure you set the security headers as the first middleware in your `Middlewares` (i.e., the first middleware to be applied to responses) to make sure the headers get added to all responses.
5980

6081
If you want to add your own values, it is easy to do using the factory. For instance, to add a content security policy configuration, just do:
6182

@@ -65,7 +86,7 @@ let cspValue = "default-src 'none'; script-src https://static.brokenhands.io;"
6586
let cspConfig = ContentSecurityPolicyConfiguration(value: cspValue)
6687

6788
let securityHeadersFactory = SecurityHeadersFactory().with(contentSecurityPolicy: cspConfig)
68-
services.register(securityHeadersFactory.build())
89+
application.middleware.use(securityHeadersFactory.build())
6990
```
7091

7192
```HTTP
@@ -75,15 +96,6 @@ x-frame-options: DENY
7596
x-xss-protection: 1; mode=block
7697
```
7798

78-
You will need to add it as a dependency in your `Package.swift` file:
79-
80-
```swift
81-
dependencies: [
82-
...,
83-
.package(url: "https://github.com/brokenhandsio/VaporSecurityHeaders.git", from: "2.0.0")
84-
]
85-
```
86-
8799
Each different header has its own configuration and options, details of which can be found below.
88100

89101
You can test your site by visiting the awesome [Security Headers](https://securityheaders.io) (no affiliation) website.
@@ -94,6 +106,7 @@ If you are running an API you can choose a default configuration for that by cre
94106

95107
```swift
96108
let securityHeaders = SecurityHeadersFactory.api()
109+
application.middleware.use(securityHeaders)
97110
```
98111

99112
```http
@@ -103,19 +116,11 @@ x-frame-options: DENY
103116
x-xss-protection: 1; mode=block
104117
```
105118

106-
## Manual Initialization
107-
108-
You can also build the middleware manually like so:
109-
110-
```swift
111-
let securityHeadersMiddleware = SecurityHeadersFactory().build()
112-
```
113-
114119
# Server Configuration
115120

116121
## Vapor
117122

118-
If you are running Vapor on it's own (i.e. not as a CGI application or behind and reverse proxy) then you do not need to do anything more to get it running!
123+
If you are running Vapor on it's own (i.e. not as a CGI application or behind a reverse proxy) then you do not need to do anything more to get it running!
119124

120125
## Nginx, Apache and 3rd Party Services
121126

@@ -276,7 +281,7 @@ Check out [https://report-uri.io/](https://report-uri.io/) for a free tool to se
276281

277282
### Page Specific CSP
278283

279-
Vapor Security Headers also supports setting the CSP on a route or request basis. If the middleware has been added to the `MiddlewareConfig`, you can override the CSP for a request. This allows you to have a strict default CSP, but allow content from extra sources when required, such as only allowing the Javascript for blog comments on the blog page. Create a separate `ContentSecurityPolicyConfiguration` and then add it to the request. For example, inside a route handler, you could do:
284+
Vapor Security Headers also supports setting the CSP on a route or request basis. If the middleware has been added to the `Middlewares`, you can override the CSP for a request. This allows you to have a strict default CSP, but allow content from extra sources when required, such as only allowing the Javascript for blog comments on the blog page. Create a separate `ContentSecurityPolicyConfiguration` and then add it to the request. For example, inside a route handler, you could do:
280285

281286
```swift
282287
let cspConfig = ContentSecurityPolicy()
@@ -291,14 +296,6 @@ req.contentSecurityPolicy = pageSpecificCSP
291296
content-security-policy: default-src 'none'; script-src https://comments.disqus.com
292297
```
293298

294-
You must also enable the `CSPRequestConfiguration` service for this to work. In `configure.swift` add:
295-
296-
```swift
297-
services.register { _ in
298-
return CSPRequestConfiguration()
299-
}
300-
```
301-
302299
## Content-Security-Policy-Report-Only
303300

304301
Content-Security-Policy-Report-Only works in exactly the same way as Content-Security-Policy except that any violations will not block content, but they will be reported back to you. This is extremely useful for testing a CSP before rolling it out over your site. You can run both side by side - so for example have a fairly simply policy under Content-Security-Policy but test a more restrictive policy over Content-Security-Policy-Report-Only. The great thing about this is that your users do all your testing for you!
@@ -443,7 +440,7 @@ strict-transport-security: max-age=31536000; includeSubDomains; preload
443440

444441
The Server header is usually hidden from responses in order to not give away what type of server you are running and what version you are using. This is to stop attackers from scanning your site and using known vulnerabilities against it easily. By default Vapor does not show the server header in responses for this reason.
445442

446-
However, it can be fun to add in a custom server configuration for a bit of personalization, such as your website name, or company name (look at Github's response) and the `ServerConfiguraiton` is to allow this. So, for example, if I wanted my `Server` header to be `brokenhands.io`, I would configure it like:
443+
However, it can be fun to add in a custom server configuration for a bit of personalization, such as your website name, or company name (look at Github's response) and the `ServerConfiguraiton` allows this. So, for example, if I wanted my `Server` header to be `brokenhands.io`, I would configure it like:
447444

448445
```swift
449446
let serverConfig = ServerConfiguration(value: "brokenhands.io")

Sources/VaporSecurityHeaders/Configurations/ContentSecurityPolicyConfiguration.swift

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,25 @@ public struct ContentSecurityPolicyConfiguration: SecurityHeaderConfiguration {
1414

1515
func setHeader(on response: Response, from request: Request) {
1616
if let requestCSP = request.contentSecurityPolicy {
17-
response.http.headers.replaceOrAdd(name: .contentSecurityPolicy, value: requestCSP.value)
17+
response.headers.replaceOrAdd(name: .contentSecurityPolicy, value: requestCSP.value)
1818
} else {
19-
response.http.headers.replaceOrAdd(name: .contentSecurityPolicy, value: value)
19+
response.headers.replaceOrAdd(name: .contentSecurityPolicy, value: value)
2020
}
2121
}
2222
}
2323

24-
public class CSPRequestConfiguration: Service {
25-
var configuration: ContentSecurityPolicyConfiguration?
26-
public init() {}
24+
extension ContentSecurityPolicyConfiguration: StorageKey {
25+
public typealias Value = Self
2726
}
2827

2928
extension Request {
29+
3030
public var contentSecurityPolicy: ContentSecurityPolicyConfiguration? {
3131
get {
32-
if let requestConfig = try? privateContainer.make(CSPRequestConfiguration.self) {
33-
return requestConfig.configuration
34-
} else {
35-
return nil
36-
}
32+
return self.storage[ContentSecurityPolicyConfiguration.self]
3733
}
3834
set {
39-
if let requestConfig = try? privateContainer.make(CSPRequestConfiguration.self) {
40-
requestConfig.configuration = newValue
41-
}
35+
self.storage[ContentSecurityPolicyConfiguration.self] = newValue
4236
}
4337
}
4438
}
@@ -98,81 +92,103 @@ public class ContentSecurityPolicy {
9892
return policy.joined(separator: "; ")
9993
}
10094

95+
@discardableResult
10196
public func set(value: String) -> ContentSecurityPolicy {
10297
policy.append(value)
10398
return self
10499
}
105-
100+
101+
@discardableResult
106102
public func baseUri(sources: String...) -> ContentSecurityPolicy {
107103
policy.append("base-uri \(sources.joined(separator: " "))")
108104
return self
109105
}
110106

107+
@discardableResult
111108
public func blockAllMixedContent() -> ContentSecurityPolicy {
112109
policy.append("block-all-mixed-content")
113110
return self
114111
}
112+
113+
@discardableResult
114+
public func childSrc(sources: String...) -> ContentSecurityPolicy {
115+
policy.append("child-src \(sources.joined(separator: " "))")
116+
return self
117+
}
115118

119+
@discardableResult
116120
public func connectSrc(sources: String...) -> ContentSecurityPolicy {
117121
policy.append("connect-src \(sources.joined(separator: " "))")
118122
return self
119123
}
120124

125+
@discardableResult
121126
public func defaultSrc(sources: String...) -> ContentSecurityPolicy {
122127
policy.append("default-src \(sources.joined(separator: " "))")
123128
return self
124129
}
125130

131+
@discardableResult
126132
public func fontSrc(sources: String...) -> ContentSecurityPolicy {
127133
policy.append("font-src \(sources.joined(separator: " "))")
128134
return self
129135
}
130136

137+
@discardableResult
131138
public func formAction(sources: String...) -> ContentSecurityPolicy {
132139
policy.append("form-action \(sources.joined(separator: " "))")
133140
return self
134141
}
135142

143+
@discardableResult
136144
public func frameAncestors(sources: String...) -> ContentSecurityPolicy {
137145
policy.append("frame-ancestors \(sources.joined(separator: " "))")
138146
return self
139147
}
140148

149+
@discardableResult
141150
public func frameSrc(sources: String...) -> ContentSecurityPolicy {
142151
policy.append("frame-src \(sources.joined(separator: " "))")
143152
return self
144153
}
145154

155+
@discardableResult
146156
public func imgSrc(sources: String...) -> ContentSecurityPolicy {
147157
policy.append("img-src \(sources.joined(separator: " "))")
148158
return self
149159
}
150160

161+
@discardableResult
151162
public func manifestSrc(sources: String...) -> ContentSecurityPolicy {
152163
policy.append("manifest-src \(sources.joined(separator: " "))")
153164
return self
154165
}
155166

167+
@discardableResult
156168
public func mediaSrc(sources: String...) -> ContentSecurityPolicy {
157169
policy.append("media-src \(sources.joined(separator: " "))")
158170
return self
159171
}
160172

173+
@discardableResult
161174
public func objectSrc(sources: String...) -> ContentSecurityPolicy {
162175
policy.append("object-src \(sources.joined(separator: " "))")
163176
return self
164177
}
165178

179+
@discardableResult
166180
public func pluginTypes(types: String...) -> ContentSecurityPolicy {
167181
policy.append("plugin-types \(types.joined(separator: " "))")
168182
return self
169183
}
170184

185+
@discardableResult
171186
public func requireSriFor(values: String...) -> ContentSecurityPolicy {
172187
policy.append("require-sri-for \(values.joined(separator: " "))")
173188
return self
174189
}
175190

191+
@discardableResult
176192
public func reportTo(reportToObject: CSPReportTo) -> ContentSecurityPolicy {
177193
let encoder = JSONEncoder()
178194
guard let data = try? encoder.encode(reportToObject) else { return self }
@@ -181,31 +197,37 @@ public class ContentSecurityPolicy {
181197
return self
182198
}
183199

200+
@discardableResult
184201
public func reportUri(uri: String) -> ContentSecurityPolicy {
185202
policy.append("report-uri \(uri)")
186203
return self
187204
}
188205

206+
@discardableResult
189207
public func sandbox(values: String...) -> ContentSecurityPolicy {
190208
policy.append("sandbox \(values.joined(separator: " "))")
191209
return self
192210
}
193211

212+
@discardableResult
194213
public func scriptSrc(sources: String...) -> ContentSecurityPolicy {
195214
policy.append("script-src \(sources.joined(separator: " "))")
196215
return self
197216
}
198217

218+
@discardableResult
199219
public func styleSrc(sources: String...) -> ContentSecurityPolicy {
200220
policy.append("style-src \(sources.joined(separator: " "))")
201221
return self
202222
}
203223

224+
@discardableResult
204225
public func upgradeInsecureRequests() -> ContentSecurityPolicy {
205226
policy.append("upgrade-insecure-requests")
206227
return self
207228
}
208229

230+
@discardableResult
209231
public func workerSrc(sources: String...) -> ContentSecurityPolicy {
210232
policy.append("worker-src \(sources.joined(separator: " "))")
211233
return self

0 commit comments

Comments
 (0)