Skip to content

Commit 270983a

Browse files
Merge pull request #2659 from ably/WEB-3380-enforce-canonicals
[WEB-3380] Enforce canonical access to HTML content
2 parents a1ffa12 + 1624dd6 commit 270983a

File tree

5 files changed

+332
-54
lines changed

5 files changed

+332
-54
lines changed

.circleci/config.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ jobs:
5252

5353
build:
5454
environment:
55-
COMPRESS_MAX_THREADS: 8
55+
ASSET_COMPRESSION_ITERATIONS: 1
56+
COMPRESS_MAX_THREADS: 4
5657
executor:
5758
name: default
5859
resource_class: xlarge
@@ -107,6 +108,12 @@ jobs:
107108
- run:
108109
name: Verify all files are compressed
109110
command: ./bin/assert-compressed.sh
111+
- run:
112+
name: Test content request auth tokens
113+
command: |
114+
export PATH="$PWD/bin:$PWD/buildpack/build/.heroku-buildpack-nginx/ruby/bin:$PATH"
115+
mkdir -p logs/nginx
116+
./bin/assert-content-auth.sh
110117
- run:
111118
name: Test nginx configuration
112119
command: |

bin/assert-content-auth.sh

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
# set -x # uncomment to help debugging
5+
6+
#
7+
# This script verifies that the content request bearer tokens work as expected
8+
#
9+
10+
# Make sure basic auth is disabled for these tests as it will interfere with the content request auth tokens
11+
export ENABLE_BASIC_AUTH=false
12+
13+
# First we define some utility functions to help us in the test
14+
15+
wait_for_nginx_pid_file() {
16+
local operation=$1 # "start" or "stop"
17+
local count=0
18+
19+
while ( [ "$operation" = "start" ] && [ ! -f /tmp/nginx.pid ] ) || \
20+
( [ "$operation" = "stop" ] && [ -f /tmp/nginx.pid ] ); do
21+
# nginx operations are quick so we can be impatient here
22+
sleep 0.1
23+
count=$((count + 1))
24+
if [ $count -ge 100 ]; then # 0.1s * 100 = 10s
25+
echo "Error: Timeout waiting for nginx to $operation"
26+
exit 1
27+
fi
28+
done
29+
}
30+
31+
start_nginx() {
32+
# Start nginx in the background
33+
./bin/start-nginx &
34+
35+
# Wait for nginx to start successfully (pid file is created)
36+
wait_for_nginx_pid_file "start"
37+
38+
# Get the PID from the nginx.pid file
39+
NGINX_PID=$(cat /tmp/nginx.pid)
40+
41+
# Check if nginx is still running
42+
if ! kill -0 $NGINX_PID 2>/dev/null; then
43+
echo "Error: Failed to start nginx"
44+
exit 1
45+
fi
46+
}
47+
48+
stop_nginx() {
49+
# Read the PID from the file
50+
if [ -f /tmp/nginx.pid ]; then
51+
NGINX_PID=$(cat /tmp/nginx.pid)
52+
# Kill nginx if it's still running
53+
if kill -0 $NGINX_PID 2>/dev/null; then
54+
kill $NGINX_PID
55+
fi
56+
57+
# Wait for nginx to stop (pid file to be removed)
58+
wait_for_nginx_pid_file "stop"
59+
fi
60+
}
61+
62+
# Set up trap to stop nginx on script exit or failure
63+
trap stop_nginx EXIT
64+
65+
# Function to run a single test case
66+
run_test() {
67+
local path="$1"
68+
local expected_status="$2"
69+
local expected_content_type="$3"
70+
local expected_redirect_url="${4:-}"
71+
local auth_token="${5:-}"
72+
local test_name="${6:-Test case}"
73+
74+
echo "--------------------------------"
75+
echo "Running test: $test_name"
76+
echo
77+
78+
# Start nginx
79+
start_nginx
80+
81+
# Prepare curl command
82+
local curl_cmd="curl --silent --output /dev/null --write-out %{http_code}|%{content_type}|%{redirect_url}"
83+
84+
# Add auth token if provided
85+
if [ -n "$auth_token" ]; then
86+
curl_cmd="$curl_cmd --oauth2-bearer $auth_token"
87+
fi
88+
89+
# Make the request
90+
local response=$($curl_cmd "http://localhost:${PORT}${path}")
91+
local status_code=$(echo "$response" | cut -d'|' -f1)
92+
local content_type=$(echo "$response" | cut -d'|' -f2)
93+
local redirect_url=$(echo "$response" | cut -d'|' -f3)
94+
95+
# Verify status code
96+
if [ "$status_code" != "$expected_status" ]; then
97+
echo "Expected status code $expected_status, got $status_code"
98+
exit 1
99+
fi
100+
101+
# Verify content type
102+
if [[ ! $content_type =~ $expected_content_type ]]; then
103+
echo "Expected Content-Type to contain $expected_content_type, got $content_type"
104+
exit 1
105+
fi
106+
107+
# Verify redirect URL if expected
108+
if [ -n "$expected_redirect_url" ] && [[ ! $redirect_url =~ $expected_redirect_url ]]; then
109+
echo "Expected redirect URL to contain $expected_redirect_url, got $redirect_url"
110+
exit 1
111+
fi
112+
113+
echo "OK: $test_name passed"
114+
stop_nginx
115+
echo
116+
}
117+
118+
export PORT=4001
119+
120+
# Example usage of the test function:
121+
# run_test "/" "200" "text/html" "" "" "Root path without auth"
122+
# run_test "/" "404" "text/html" "" "" "Root path with auth tokens but no auth"
123+
# run_test "/" "301" "text/html" "http://www.example.com/" "" "Root path with canonical host"
124+
# run_test "/" "200" "text/html" "" "foo" "Root path with auth"
125+
126+
# 1. Verify that things work as normal when the tokens aren't set
127+
run_test "/" "200" "text/html" "" "" "Root path without auth tokens"
128+
129+
# 2. Verify that things work as expected when the tokens are set
130+
export CONTENT_REQUEST_AUTH_TOKENS=foo,bar
131+
run_test "/" "404" "text/html" "" "" "Root path with auth tokens but no auth"
132+
133+
# 3. Verify that things work as expected when the tokens are set and the canonical host is set
134+
export CONTENT_REQUEST_CANONICAL_HOST=www.example.com
135+
run_test "/" "301" "text/html" "http://www.example.com/" "" "Root path with canonical host"
136+
137+
# 4. Verify that things work as expected when the tokens are set and the canonical host is set and the request is authenticated
138+
run_test "/" "200" "text/html" "" "foo" "Root path with auth token"
139+
140+
# 5. Verify that things work as expected when the tokens are set and the canonical host is set and the request is authenticated and the request is for a .html file
141+
run_test "/index.html" "200" "text/html" "" "foo" "index.html with auth token"
142+
143+
# 6. Verify that things work as expected when the tokens are set and the canonical host is set and the request is authenticated and the request is for a .html file and the request is for a path that doesn't exist
144+
run_test "/index.html" "301" "text/html" "http://www.example.com/" "" "index.html without auth token"
145+
146+
# 7. Verify that things work as expected when the tokens are set and the canonical host is set and the request is authenticated and the request is for a .html file and the request is for a path that doesn't exist
147+
run_test "/does-not-exist.html" "404" "text/html" "" "foo" "Does not exist with auth token"
148+
149+
# 8. Verify assets requests work without auth
150+
FIRST_ASSET=$(find public -name "*.jpg" -type f | head -n1 | sed 's|^public/||')
151+
run_test "/${FIRST_ASSET}" "200" "image/jpeg" "" "" "JPEG without auth"
152+
153+
# 9. Verify JSON requests work without auth
154+
run_test "/page-data/app-data.json" "200" "application/json" "" "" "Page data without auth"
155+
156+
# Clean up environment variables
157+
unset CONTENT_REQUEST_AUTH_TOKENS
158+
unset CONTENT_REQUEST_CANONICAL_HOST

bin/start-nginx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ PORT=${PORT:-3001} \
1212
NGINX_ACCESS_LOG_PATH=${NGINX_ACCESS_LOG_PATH:-"/dev/stdout"} \
1313
NGINX_ERROR_LOG_PATH=${NGINX_ERROR_LOG_PATH:-"/dev/stderr"} \
1414
NGINX_ROOT=$(pwd)/public \
15+
NGINX_PID_FILE=${NGINX_PID_FILE:-"/tmp/nginx.pid"} \
1516
SKIP_HTTPS=${SKIP_HTTPS:-true} \
1617
ENABLE_BASIC_AUTH=${ENABLE_BASIC_AUTH:-false} \
1718
erb config/nginx.conf.erb > config/nginx.conf
1819

1920
# Cleanup when we're done
20-
trap "rm -f config/nginx.conf /tmp/nginx.socket" EXIT
21+
trap "rm -f config/nginx.conf /tmp/nginx.socket /tmp/nginx.pid" EXIT
2122

2223
# Start nginx
2324
nginx -p . -c config/nginx.conf

0 commit comments

Comments
 (0)