Skip to content

Commit

Permalink
Add support for security reports (#59)
Browse files Browse the repository at this point in the history
Sentry support security reports like CSP reports:

https://docs.sentry.io/product/security-policy-reporting/

This adds initial support for that.
  • Loading branch information
willkg committed Nov 6, 2023
1 parent f9d01ca commit 867d4b4
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 1 deletion.
35 changes: 35 additions & 0 deletions src/kent/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,17 @@ def summary(self):
if not self.payload or not isinstance(self.payload, dict):
return "no summary"

# Sentry exceptions events
exceptions = self.payload.get("exception", {}).get("values", [])
if exceptions:
first = exceptions[0]
return f"{first['type']}: {first['value']}"

# Sentry message
msg = self.payload.get("message", None)
if msg:
return msg

return "no summary"

@property
Expand Down Expand Up @@ -229,4 +235,33 @@ def store_view(project_id):

return {"success": True}

@app.route("/api/<int:project_id>/security/", methods=["POST"])
def security_view(project_id):
error_id = str(uuid.uuid4())
log_headers(dev_mode, error_id, request.headers)

body = request.data

# Decode the JSON payload
try:
json_body = json.loads(body)
except Exception:
app.logger.exception("%s: exception when JSON-decoding body.", error_id)
app.logger.error("%s: %s", error_id, body)
body = {"error": "Kent could not decode body; see logs"}
raise

ERRORS.add_error(error_id=error_id, project_id=project_id, payload=json_body)

# Log something to output
event_url = f"{request.scheme}://{request.headers['host']}/api/error/{error_id}"
app.logger.info(
"%s: security report: project id: %s, event url: %s",
error_id,
project_id,
event_url,
)

return {"success": True}

return app
38 changes: 37 additions & 1 deletion src/kent/cli_testpost.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import argparse
from urllib.parse import urlparse
import sys

try:
import requests
except ImportError:
print("You need to have requests installed.")
sys.exit(1)

try:
from sentry_sdk import init, capture_exception, capture_message
except ImportError:
Expand All @@ -18,7 +25,10 @@ def main():
"--dsn", default="http://public@localhost:5000/1", help="SENTRY_DSN to use"
)
parser.add_argument(
"kind", nargs="?", default="message", help="What kind of thing to post. ['message', 'error']",
"kind",
nargs="?",
default="message",
help="What kind of thing to post. ['message', 'error', 'security']",
)

args = parser.parse_args()
Expand All @@ -28,12 +38,38 @@ def main():
if args.kind == "message":
capture_message("test error capture")
print(f"Message posted to: {args.dsn}")

elif args.kind == "error":
try:
raise Exception
except Exception as exc:
capture_exception(exc)
print(f"Error posted to: {args.dsn}")

elif args.kind == "security":
parsed = urlparse(args.dsn)
report_uri = f"{parsed.scheme}://{parsed.username}@{parsed.netloc}/api{parsed.path}/security/"
data = [
{
"age": 0,
"body": {
"blockedURL": "https://maps.googleapis.com/maps/api/js",
"disposition": "enforce",
"documentURL": "https://test.example.com/",
"effectiveDirective": "script-src",
"originalPolicy": f"default-src 'self'; img-src 'self'; script-src 'self'; form-action 'self'; frame-ancestors 'self'; report-to csp-endpoint; report-uri {report_uri}",
"referrer": "",
"statusCode": 200,
},
"type": "csp-violation",
"url": "https://test.example.com/",
"user_agent": "Mozilla/5.0 (user agent)",
}
]
resp = requests.post(report_uri, json=data)
resp.raise_for_status()
print(f"Security report posted to: {report_uri}")

else:
print(f"{args.kind!r} is not a valid kind.")
return 1
Expand Down
12 changes: 12 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ class TestError:
},
"Exception: Intentional exception",
),
# Message payload
(
{
"message": "some message",
},
"some message",
),
],
)
def test_summary(self, payload, expected):
Expand All @@ -50,6 +57,11 @@ def test_store_view(client):
assert resp.status_code == 200


def test_security_view(client):
resp = client.post("/api/1/security/", json=[{"id": "xyz"}])
assert resp.status_code == 200


def test_api_flush_view(client):
resp = client.post("/api/flush/")
assert resp.status_code == 200
Expand Down

0 comments on commit 867d4b4

Please sign in to comment.