Skip to content

Commit 0a9095e

Browse files
authored
Merge pull request #145 from ruby-no-kai/csp
revive CSP headers
2 parents 76ad533 + b317cae commit 0a9095e

File tree

6 files changed

+84
-18
lines changed

6 files changed

+84
-18
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,12 @@ You need to manually invalidate the cache: https://rubykaigi.esa.io/posts/1241#%
8787
## Run locally
8888

8989
```
90-
docker build -t rko-router:latest .
91-
docker run --rm --name rko-router --publish 127.0.0.1::8080 rko-router:latest
90+
docker compose up --watch
9291
```
9392

9493
```
95-
curl -H Host:rubykaigi.org http://$(docker port rko-router 8080)/
96-
TARGET_HOST=http://$(docker port rko-router 8080) bundle exec rspec
94+
curl -H Host:rubykaigi.org http://$(docker compose port nginx 8080)/
95+
TARGET_HOST=http://$(docker compose port nginx 8080) bundle exec rspec
9796
```
9897

9998
## Test

config/github_pages.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ proxy_hide_header x-fastly-request-id;
88
proxy_hide_header x-github-request-id;
99
proxy_hide_header x-served-by;
1010

11+
proxy_hide_header strict-transport-security;
12+
1113
add_header via "1.1 rko-router.rubykaigi.org";
1214
add_header x-rk-ghp "fi=\"$upstream_http_x_fastly_request_id\",fs=\"$upstream_http_x_served_by\",ghi=\"$upstream_http_x_github_request_id\",cache=\"$upstream_http_x_cache\",cache-hits=\"$upstream_http_x_cache_hits\"";
1315

config/nginx.conf.erb

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ http {
9191

9292
rewrite_log <%= ENV.fetch("REWRITE_LOG", 'off') %>;
9393

94+
add_header_inherit merge;
95+
9496
server {
9597
listen <%= ENV.fetch("PORT") %> default_server;
9698
server_name <%= primary_host %> localhost;
@@ -210,10 +212,14 @@ http {
210212
server_name regional.rubykaigi.org;
211213

212214
set $csp_policy "";
215+
set $csp_policy_report "";
213216
if ($http_x_forwarded_proto = "https") {
214-
set $csp_policy "default-src https: 'self' 'unsafe-inline' 'unsafe-eval'; report-uri https://<%= primary_host %>/_csp";
217+
set $csp_policy "upgrade-insecure-requests; frame-ancestors 'none'; default-src https:";
218+
set $csp_policy_report "default-src https:; report-uri https://<%= primary_host %>/_csp";
215219
}
216-
add_header Content-Security-Policy-Report-Only "$csp_policy";
220+
add_header X-Content-Type-Options "nosniff";
221+
add_header Content-Security-Policy "$csp_policy";
222+
add_header Content-Security-Policy-Report-Only "$csp_policy_report";
217223

218224
location ~ ^/oedo02(.*) {
219225
return 301 https://magazine.rubyist.net/articles/0039/0039-MetPragdaveAtAsakusarb.html;
@@ -495,21 +501,28 @@ http {
495501
server_name rubykaigi.org;
496502

497503
set $csp_policy "";
504+
set $csp_policy_report "";
498505
if ($http_x_forwarded_proto = "https") {
499-
set $csp_policy "default-src https: 'self' 'unsafe-inline' 'unsafe-eval'; report-uri https://<%= primary_host %>/_csp";
506+
set $csp_policy "frame-ancestors 'none'; default-src https:";
507+
set $csp_policy_report "default-src https:; report-uri https://<%= primary_host %>/_csp";
500508
}
501-
add_header Content-Security-Policy-Report-Only "$csp_policy";
509+
add_header X-Content-Type-Options "nosniff";
510+
add_header Strict-Transport-Security "max-age=31536000";
511+
add_header Content-Security-Policy "$csp_policy";
512+
add_header Content-Security-Policy-Report-Only "$csp_policy_report";
502513

503-
location ~ ^/200[6-9] {
514+
location ~ ^/20(0[6-9]|1[0-5])(.*) {
504515
include force_https.conf;
505516
include github_pages.conf;
506517
proxy_hide_header Cache-Control;
507518
proxy_hide_header Expires;
519+
# 2015 sites and prior had mixed content issues
520+
set $csp_policy "upgrade-insecure-requests; frame-ancestors 'none'; default-src https:";
508521
add_header Cache-Control "public, max-age=604800, s-maxage=31536000";
509522
proxy_pass https://2009-2011.rubykaigi.org;
510523
}
511524

512-
location ~ ^/201[0-9](.*) {
525+
location ~ ^/201[6-9](.*) {
513526
include force_https.conf;
514527
include github_pages.conf;
515528
proxy_hide_header Cache-Control;

docker-compose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
services:
2+
nginx:
3+
build: .
4+
ports:
5+
- "127.0.0.1::8080"
6+
develop:
7+
watch:
8+
- action: rebuild
9+
path: .
10+
ignore:
11+
- tmp/

spec/regional_rubykaigi_org_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,21 @@
7272

7373
describe "/#{subdir}/" do
7474
let(:res) { http_get("https://regional.rubykaigi.org/#{subdir}/") }
75+
7576
it "returns ok" do
7677
#pending 'kanrk05.herokuapp.com is down' if path == '/kansai05/'
7778
#pending 'http://rubykaigi-hamamatsu.s3-website-ap-northeast-1.amazonaws.com/hamamatsu01/ returns C-T:application/javascript' if path == '/hamamatsu01/'
7879
#pending 'asakusa.github.io returns 301 (#110)' if path == '/oedo10/'
7980
expect(res.code).to eq("200")
8081
expect(res["content-type"]).to include("text/html")
8182
end
83+
84+
it "has minimum security headers, but no hsts" do
85+
expect(res["content-security-policy"]).to include("default-src https:")
86+
expect(res["content-security-policy"]).to include("upgrade-insecure-requests")
87+
expect(res["x-content-type-options"]).to eq("nosniff")
88+
expect(res["strict-transport-security"]).to be_nil
89+
end
8290
end
8391
end
8492
end

spec/rubykaigi_org_spec.rb

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
require_relative "./spec_helper"
22

33
describe "http://rubykaigi.org" do
4-
let(:latest_year) { "2026" }
4+
LATEST_YEAR = "2026"
5+
HOSTED_YEARS = [
6+
*(2006..2020),
7+
'2020-takeout',
8+
'2021-takeout',
9+
*(2022..LATEST_YEAR.to_i),
10+
].map(&:to_s).freeze
11+
12+
let(:latest_year) { LATEST_YEAR }
13+
14+
describe "meta: HOSTED_YEARS" do
15+
subject { HOSTED_YEARS }
16+
it { is_expected.to include(latest_year.to_s) }
17+
end
18+
19+
520

621
describe "(https) /" do
722
let(:res) { http_get("https://rubykaigi.org/") }
@@ -128,13 +143,6 @@
128143
end
129144
end
130145

131-
HOSTED_YEARS = [
132-
*(2006..2020),
133-
'2020-takeout',
134-
'2021-takeout',
135-
*(2022..2023),
136-
]
137-
138146
describe "force_https" do
139147
HOSTED_YEARS.each do |year|
140148
describe "/#{year}" do
@@ -177,6 +185,31 @@
177185
end
178186
end
179187

188+
describe "security headers" do
189+
HOSTED_YEARS.each do |year|
190+
describe "/#{year}/" do
191+
let(:res) { http_get("https://rubykaigi.org/#{year}/") }
192+
it "returns security header" do
193+
expect(res["content-security-policy-report-only"]).to include("default-src https:")
194+
expect(res["content-security-policy"]).to include("frame-ancestors 'none'")
195+
expect(res["x-content-type-options"]).to eq("nosniff")
196+
expect(res["strict-transport-security"]).to include("max-age=")
197+
end
198+
end
199+
end
200+
201+
(2006..2015).each do |year|
202+
describe "/#{year}/" do
203+
let(:res) { http_get("https://rubykaigi.org/#{year}/") }
204+
it "returns security header" do
205+
expect(res["content-security-policy"]).to include("default-src https:")
206+
expect(res["content-security-policy"]).to include("upgrade-insecure-requests")
207+
end
208+
end
209+
end
210+
end
211+
212+
180213
HOSTED_YEARS.each do |year|
181214
describe "/#{year}/" do
182215
let(:res) { http_get("https://rubykaigi.org/#{year}/") }

0 commit comments

Comments
 (0)