Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address header and configuration vulnerabilities (closes #10) #12

Merged
merged 3 commits into from
Nov 11, 2024
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
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
omit = tests/http_obs.py
18 changes: 18 additions & 0 deletions .github/workflows/http_obs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
on:
schedule:
- cron: '20 2 * * 6'
jobs:
header-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install python packages
run: |
python3.13 -m ensurepip --upgrade
python3.13 -m pip install requests
- name: Check website headers
run: python3.13 tests/http_obs.py
3 changes: 3 additions & 0 deletions css/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {background-color:green}
h1 {color:white}
p {color:white}
9 changes: 4 additions & 5 deletions html/main.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Cache-Control" content="no-cache" />
<link rel="icon" href="favicon.svg?v=1" /><!--https://stackoverflow.com/questions/2208933/how-do-i-force-a-favicon-refresh-->
<title>24/7 Bishops</title>
<link rel="stylesheet" href="/css/main.css" />
</head>
<body style="background-color:green;">
<h1 style="color:white">24/7 Bishops</h1>
<p style="color: white">Coming Soon: a website where you can upload your chess games and collaborate on analysis with other chess players located around the world asynchronously!</p>
<body>
<h1>24/7 Bishops</h1>
<p>Coming Soon: a website where you can upload your chess games and collaborate on analysis with other chess players located around the world asynchronously!</p>
</body>
</html>
9 changes: 4 additions & 5 deletions html/maintenance.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Cache-Control" content="no-cache" />
<link rel="icon" href="data:,">
<title>24/7 Bishops</title>
<link rel="stylesheet" href="/css/main.css" />
</head>
<body style="background-color:green;">
<h1 style="color:white">24/7 Bishops is Temporarily Unavailable</h1>
<p style="color: white">Please check back soon!</p>
<body>
<h1>24/7 Bishops is Temporarily Unavailable</h1>
<p>Please check back soon!</p>
</body>
</html>
23 changes: 23 additions & 0 deletions tests/http_obs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""This tests the website's header security after it is deployed.
It is meant to be run on a schedule."""

import requests


def check_headers():
response = requests.post(
"https://observatory-api.mdn.mozilla.net/api/v2/scan?host=247bishops.com",
timeout=300,
)
response.raise_for_status()
data = response.json()
failures = data["tests_failed"]
details = data["details_url"]
if failures > 0:
raise RuntimeError(
f"Header security vulnerabilities were detected. Follow this link for details: {details}"
)


if __name__ == "__main__":
check_headers()
15 changes: 15 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
import requests

from webapp_python.main import RESPONSE_HEADERS

from .server import get_server_url


def test_main():
with get_server_url() as webapp_url:
response = requests.get(f"{webapp_url}", timeout=60)
assert response.status_code == 200
for key, val in RESPONSE_HEADERS.items():
assert response.headers[key] == val
assert "24/7 Bishops" in response.text
response = requests.get(f"{webapp_url}/favicon.svg?v=1729", timeout=60)
assert response.status_code == 200
for key, val in RESPONSE_HEADERS.items():
assert response.headers[key] == val
response = requests.get(f"{webapp_url}/css/main.css", timeout=60)
assert response.status_code == 200
for key, val in RESPONSE_HEADERS.items():
assert response.headers[key] == val
assert "background-color:green" in response.text


def test_maint():
with get_server_url(maintenance_mode=True) as webapp_url:
response = requests.get(f"{webapp_url}", timeout=60)
assert response.status_code == 503
assert "24/7 Bishops is Temporarily Unavailable" in response.text
for key, val in RESPONSE_HEADERS.items():
assert response.headers[key] == val
response = requests.post(f"{webapp_url}/random_path", timeout=60)
assert response.status_code == 503
assert "24/7 Bishops is Temporarily Unavailable" in response.text
for key, val in RESPONSE_HEADERS.items():
assert response.headers[key] == val
36 changes: 24 additions & 12 deletions webapp_python/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,41 @@

app = flask.Flask(__name__)

RESPONSE_HEADERS = {
"Content-Security-Policy": "default-src 'self'; frame-ancestors 'none'; upgrade-insecure-requests",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Strict-Transport-Security": "max-age=300; includeSubDomains",
"X-Content-Type-Options": "nosniff",
}

TOP_LEVEL_PATH = Path(__file__).parent.absolute().parent


@app.before_request
def maintenance_mode():
if environ["MAINTENANCE_MODE"] == "1":
return (
flask.send_file(
Path(__file__).parent.absolute().parent / "html" / "maintenance.html"
),
503,
)
return flask.send_file(TOP_LEVEL_PATH / "html" / "maintenance.html"), 503


@app.after_request
def apply_headers(response):
for key, val in RESPONSE_HEADERS.items():
response.headers[key] = val
return response


@app.route("/")
def main():
return flask.send_file(
Path(__file__).parent.absolute().parent / "html" / "main.html"
)
return flask.send_file(TOP_LEVEL_PATH / "html" / "main.html")


@app.route("/favicon.svg")
def favicon():
"""If this is updated, you also need to increment the version in the link tag in the html file.
See https://stackoverflow.com/questions/2208933/how-do-i-force-a-favicon-refresh"""
return flask.send_file(
Path(__file__).parent.absolute().parent / "html" / "Chess_tile_bl.svg"
)
return flask.send_file(TOP_LEVEL_PATH / "html" / "Chess_tile_bl.svg")


@app.route("/css/main.css")
def internal_css():
return flask.send_file(TOP_LEVEL_PATH / "css" / "main.css")