test_validate_csp.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. from __future__ import absolute_import
  2. import pytest
  3. from sentry.coreapi import APIError
  4. from sentry.event_manager import EventManager
  5. def validate_and_normalize(report, client_ip='198.51.100.0',
  6. user_agent='Awesome Browser'):
  7. manager = EventManager(report, client_ip=client_ip, user_agent=user_agent)
  8. manager.process_csp_report()
  9. manager.normalize()
  10. return manager.get_data()
  11. def _build_test_report(effective_directive, violated_directive):
  12. report = {
  13. "release": "abc123",
  14. "environment": "production",
  15. "interface": 'csp',
  16. "report": {
  17. "csp-report": {
  18. "document-uri": "http://45.55.25.245:8123/csp",
  19. "referrer": "http://example.com",
  20. "violated-directive": violated_directive,
  21. "effective-directive": effective_directive,
  22. "original-policy": "default-src https://45.55.25.245:8123/; child-src https://45.55.25.245:8123/; connect-src https://45.55.25.245:8123/; font-src https://45.55.25.245:8123/; img-src https://45.55.25.245:8123/; media-src https://45.55.25.245:8123/; object-src https://45.55.25.245:8123/; script-src https://45.55.25.245:8123/; style-src https://45.55.25.245:8123/; form-action https://45.55.25.245:8123/; frame-ancestors 'none'; plugin-types 'none'; report-uri http://45.55.25.245:8123/csp-report?os=OS%20X&device=&browser_version=43.0&browser=chrome&os_version=Lion",
  23. "blocked-uri": "http://google.com",
  24. "status-code": 200,
  25. }
  26. }
  27. }
  28. if violated_directive is None:
  29. del report['report']['csp-report']['violated-directive']
  30. if effective_directive is None:
  31. del report['report']['csp-report']['effective-directive']
  32. return report
  33. @pytest.mark.parametrize(
  34. 'effective_directive,violated_directive,culprit_element', (
  35. ("img-src", "img-src https://45.55.25.245:8123/", "img-src"),
  36. ("img-src", "default-src https://45.55.25.245:8123/", "default-src"),
  37. # build a report without the effective-directive key
  38. (None, "img-src https://45.55.25.245:8123/", "img-src"),
  39. ),
  40. ids=(
  41. 'directives match',
  42. 'prefer effective-directive',
  43. 'parse effective-directive from violated-directive',
  44. )
  45. )
  46. def test_csp_validate(effective_directive, violated_directive, culprit_element):
  47. report = _build_test_report(effective_directive, violated_directive)
  48. result = validate_and_normalize(report)
  49. assert result['logger'] == 'csp'
  50. assert result['release'] == 'abc123'
  51. assert result['environment'] == 'production'
  52. assert "errors" not in result
  53. assert 'logentry' in result
  54. assert result['culprit'] == culprit_element + " 'self'"
  55. assert map(tuple, result['tags']) == [
  56. ('effective-directive', 'img-src'),
  57. ('blocked-uri', 'http://google.com'),
  58. ]
  59. assert result['user'] == {'ip_address': '198.51.100.0'}
  60. assert result['request']['url'] == 'http://45.55.25.245:8123/csp'
  61. assert dict(result['request']['headers']) == {
  62. 'User-Agent': 'Awesome Browser',
  63. 'Referer': 'http://example.com'
  64. }
  65. @pytest.mark.parametrize(
  66. 'report', (
  67. {},
  68. {"release": "abc123", "interface": 'csp', "report": {}},
  69. _build_test_report(effective_directive=None, violated_directive=None),
  70. _build_test_report(effective_directive=None, violated_directive=""),
  71. _build_test_report(effective_directive=None, violated_directive="blink-src"),
  72. ),
  73. ids=(
  74. 'empty dict',
  75. 'no csp-report',
  76. 'no violated-directive to parse (expect KeyError)',
  77. 'unsplittable violated-directive (expect IndexError)',
  78. 'invalid violated-directive (not in schema enum)',
  79. )
  80. )
  81. def test_csp_validate_failure(report):
  82. with pytest.raises(APIError):
  83. validate_and_normalize(report)
  84. def test_csp_tags_out_of_bounds():
  85. report = {
  86. "release": "abc123",
  87. "interface": 'csp',
  88. "report": {
  89. "csp-report": {
  90. "document-uri": "http://45.55.25.245:8123/csp",
  91. "referrer": "http://example.com",
  92. "violated-directive": "img-src https://45.55.25.245:8123/",
  93. "effective-directive": "img-src",
  94. "original-policy": "default-src https://45.55.25.245:8123/; child-src https://45.55.25.245:8123/; connect-src https://45.55.25.245:8123/; font-src https://45.55.25.245:8123/; img-src https://45.55.25.245:8123/; media-src https://45.55.25.245:8123/; object-src https://45.55.25.245:8123/; script-src https://45.55.25.245:8123/; style-src https://45.55.25.245:8123/; form-action https://45.55.25.245:8123/; frame-ancestors 'none'; plugin-types 'none'; report-uri http://45.55.25.245:8123/csp-report?os=OS%20X&device=&browser_version=43.0&browser=chrome&os_version=Lion",
  95. "blocked-uri": "v" * 201,
  96. "status-code": 200,
  97. }
  98. }
  99. }
  100. result = validate_and_normalize(report)
  101. assert result['tags'] == [['effective-directive', 'img-src'], None]
  102. assert len(result['errors']) == 1
  103. def test_csp_tag_value():
  104. report = {
  105. "release": "abc123",
  106. "interface": 'csp',
  107. "report": {
  108. "csp-report": {
  109. "document-uri": "http://45.55.25.245:8123/csp",
  110. "referrer": "http://example.com",
  111. "violated-directive": "img-src https://45.55.25.245:8123/",
  112. "effective-directive": "img-src",
  113. "original-policy": "default-src https://45.55.25.245:8123/; child-src https://45.55.25.245:8123/; connect-src https://45.55.25.245:8123/; font-src https://45.55.25.245:8123/; img-src https://45.55.25.245:8123/; media-src https://45.55.25.245:8123/; object-src https://45.55.25.245:8123/; script-src https://45.55.25.245:8123/; style-src https://45.55.25.245:8123/; form-action https://45.55.25.245:8123/; frame-ancestors 'none'; plugin-types 'none'; report-uri http://45.55.25.245:8123/csp-report?os=OS%20X&device=&browser_version=43.0&browser=chrome&os_version=Lion",
  114. "blocked-uri": "http://google.com",
  115. "status-code": 200,
  116. }
  117. }
  118. }
  119. result = validate_and_normalize(report)
  120. assert map(tuple, result['tags']) == [
  121. ('effective-directive', 'img-src'),
  122. ('blocked-uri', 'http://google.com'),
  123. ]
  124. assert 'errors' not in result
  125. def test_hpkp_validate_basic():
  126. report = {
  127. "release": "abc123",
  128. "interface": 'hpkp',
  129. "report": {
  130. "date-time": "2014-04-06T13:00:50Z",
  131. "hostname": "www.example.com",
  132. "port": 443,
  133. "effective-expiration-date": "2014-05-01T12:40:50Z",
  134. "include-subdomains": False,
  135. "served-certificate-chain": ["-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"],
  136. "validated-certificate-chain": ["-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"],
  137. "known-pins": ["pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\""],
  138. }
  139. }
  140. result = validate_and_normalize(report)
  141. assert result['release'] == 'abc123'
  142. assert 'errors' not in result
  143. assert 'logentry' in result
  144. assert not result.get('culprit')
  145. assert sorted(map(tuple, result['tags'])) == [
  146. ('hostname', 'www.example.com'),
  147. ('include-subdomains', 'false'),
  148. ('port', '443'),
  149. ]
  150. assert result['user'] == {'ip_address': '198.51.100.0'}
  151. expected_headers = [['User-Agent', 'Awesome Browser']]
  152. assert result['request'] == {
  153. 'url': 'www.example.com',
  154. 'headers': expected_headers
  155. }
  156. def test_hpkp_validate_failure():
  157. report = {
  158. "release": "abc123",
  159. "interface": 'hpkp',
  160. "report": {}
  161. }
  162. with pytest.raises(APIError):
  163. validate_and_normalize(report)