{"action":"created","comment":{"url":"https://api.github.com/repos/rackerlabs/repose/pulls/comments/91373604","pull_request_review_id":11882605,"id":91373604,"diff_hunk":"@@ -1,20 +1,27 @@\n-= CORS\n-:toc:\n-:icons: font\n+= Cross-Origin Resource Sharing (CORS) Filter\n \n include::../_includes/in-progress.adoc[]\n \n The Cross-Origin Resource Sharing (CORS) filter allows Repose to manage CORS requests without the origin service needing to understand CORS.\n-For an introduction to CORS, see the <<CORS Overview>> below.\n+For an introduction to CORS, see the <<CORS Overview>> section below.\n \n-WARNING: Before enabling the CORS filter, you should be familiar with the CORS specification and the associated security implications.\n+[WARNING]\n+====\n+Before enabling the CORS filter, you should be familiar with the https://www.w3.org/TR/cors/[CORS specification] and the associated security implications.\n Enabling CORS can increase an endpoint's exposure.\n-Configuring CORS improperly could expose CSRF vulnerabilities in the origin service that were previously blocked by not supporting CORS.\n-\n-== Headers\n-\n-=== Required\n-The presence of certain headers will indicate what kind of request is being made.\n+Configuring CORS improperly could expose https://en.wikipedia.org/wiki/Cross-site_request_forgery[Cross-Site Request Forgery (CSRF)] vulnerabilities in the origin service that were previously hidden by not supporting CORS.\n+====\n+\n+== General filter information\n+* *Name:* cors\n+* *Default Configuration:* cors.cfg.xml\n+* *Released:* v7.2.0.0\n+* *Bundle:* repose-filter-bundle\n+* *Schema:* link:../schemas/cors-configuration.xsd[cors-configuration.xsd]","path":"repose-aggregator/src/docs/asciidoc/filters/cors.adoc","position":31,"original_position":31,"commit_id":"3f4176be4c23b1ee814708402dcc360c663e27b3","original_commit_id":"3f4176be4c23b1ee814708402dcc360c663e27b3","user":{"login":"dmnjohns","id":1923408,"avatar_url":"https://avatars.githubusercontent.com/u/1923408?v=3","gravatar_id":"","url":"https://api.github.com/users/dmnjohns","html_url":"https://github.com/dmnjohns","followers_url":"https://api.github.com/users/dmnjohns/followers","following_url":"https://api.github.com/users/dmnjohns/following{/other_user}","gists_url":"https://api.github.com/users/dmnjohns/gists{/gist_id}","starred_url":"https://api.github.com/users/dmnjohns/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dmnjohns/subscriptions","organizations_url":"https://api.github.com/users/dmnjohns/orgs","repos_url":"https://api.github.com/users/dmnjohns/repos","events_url":"https://api.github.com/users/dmnjohns/events{/privacy}","received_events_url":"https://api.github.com/users/dmnjohns/received_events","type":"User","site_admin":false},"body":"If we're including the schema in the general information block, I feel like we should at least put it next to the `Default Configuration` item. I'd probably put those two back-to-back at the end of this list. Perhaps even clarify that it's the configuration schema?","created_at":"2016-12-07T19:54:48Z","updated_at":"2016-12-07T19:54:48Z","html_url":"https://github.com/rackerlabs/repose/pull/1632#discussion_r91373604","pull_request_url":"https://api.github.com/repos/rackerlabs/repose/pulls/1632","_links":{"self":{"href":"https://api.github.com/repos/rackerlabs/repose/pulls/comments/91373604"},"html":{"href":"https://github.com/rackerlabs/repose/pull/1632#discussion_r91373604"},"pull_request":{"href":"https://api.github.com/repos/rackerlabs/repose/pulls/1632"}}},"pull_request":{"url":"https://api.github.com/repos/rackerlabs/repose/pulls/1632","id":96346969,"html_url":"https://github.com/rackerlabs/repose/pull/1632","diff_url":"https://github.com/rackerlabs/repose/pull/1632.diff","patch_url":"https://github.com/rackerlabs/repose/pull/1632.patch","issue_url":"https://api.github.com/repos/rackerlabs/repose/issues/1632","number":1632,"state":"open","locked":false,"title":"REP-4674 - CORS update for Chrome","user":{"login":"Mario-Lopez","id":6766560,"avatar_url":"https://avatars.githubusercontent.com/u/6766560?v=3","gravatar_id":"","url":"https://api.github.com/users/Mario-Lopez","html_url":"https://github.com/Mario-Lopez","followers_url":"https://api.github.com/users/Mario-Lopez/followers","following_url":"https://api.github.com/users/Mario-Lopez/following{/other_user}","gists_url":"https://api.github.com/users/Mario-Lopez/gists{/gist_id}","starred_url":"https://api.github.com/users/Mario-Lopez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Mario-Lopez/subscriptions","organizations_url":"https://api.github.com/users/Mario-Lopez/orgs","repos_url":"https://api.github.com/users/Mario-Lopez/repos","events_url":"https://api.github.com/users/Mario-Lopez/events{/privacy}","received_events_url":"https://api.github.com/users/Mario-Lopez/received_events","type":"User","site_admin":false},"body":"I updated the CORS Filter to handle Chrome/Safari same-origin requests correctly.\r\n\r\n**Background:**\r\nPreviously, the CORS Filter was following the CORS spec and doing server-side request rejection on top of that.  The spec does not say one way or another whether or not the server should be rejecting invalid requests, but the spec does assume you don't do rejection because it specifies that the presence of an Origin header initiates the CORS flow.  Unfortunately, the Origin header is technically permissible on all requests (both non-CORS and CORS) which Chrome and Safari do, and this becomes a problem when a same-origin request is treated like a CORS request and gets rejected.  This isn't a problem if the CORS Filter is configured to let a small number of trusted origins use the API.  It becomes a problem when the filter is configured to tightly restrict the API because several untrusted origins are being allowed to make CORS requests to the server.\r\n\r\nThere is not a lot of literature on how to get around this, but I did find a couple of recommendations to compare the Host header with the Origin header in order to determine if the request was a CORS request or a same-origin request.  This comparison is tightly constrained by the Origin spec (RFC 6454), so a simple string comparison is not sufficient.\r\n\r\nThere were also recommendations to check the X-Forwarded-Host header which, if present, would contain the original Host header value if the request had gone through a reverse-proxy server.  This header is not covered by any spec, but it seems to be treated the same as the Host header except that it could have multiple values (say in the case of going through multiple proxy servers).\r\n\r\n**Updates to the filter:**\r\nThe CORS Filter will continue to treat requests without an Origin header as non-CORS requests.  Any request with an Origin header, a preflight header, and a method of OPTIONS is immediately treated as a CORS Preflight request since no web browser should be sending a preflight header for a same-origin request.\r\n\r\nAfter checking for those two possibilities, the CORS Filter first checks for an X-Forwarded-Host header that it can successfully parse, otherwise it uses the Host header (which is already parsed and attained through the request object).  It then normalizes and converts the host and Origin to a `java.net.URI` that can then be used to compare them to each other.  If they match, then the request is considered a non-CORS request (same-origin).  If they don't match, then it'll be considered a CORS Actual request.  If the Origin header is malformed (i.e. can't be parsed as a `java.net.URI`), then the filter returns a 400.\r\n\r\nUnfortunately, I didn't find any library that did exactly what I needed for a lot of this.  `java.net.URI` parsed the strings for me, but it didn't give me the default ports for the \"http\" and \"https\" schemes nor did it handle normalizing IPv6 addresses.  Converting it to a `java.net.URL` did way too much and incorrectly considered host names with different subdomains as the same (i.e. the `java.net.URL` equality rules differed from the ones I needed in RFC 6454).  Hence the IP address and default port normalizing I'm doing in the code.\r\n\r\nAlso, my understanding is parsing a `java.net.URI` isn't cheap, so I avoid doing so if I can.  For example, I don't actually care if the Origin is malformed in a CORS Preflight request, so I don't bother validating it in that case.\r\n\r\nSince not everyone full understands CORS, I added additional logging that can help users understand why the filter might be doing what it's doing.  The DEBUG level will indicate when a request is considered a CORS request and why it was rejected.  The TRACE level will give even more information for every single request including if it was CORS or not and if it was allowed or rejected with the reason why.","created_at":"2016-12-02T20:25:39Z","updated_at":"2016-12-07T19:54:48Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4fb52205be7422792f0d43d1f275c5285e0cb2dd","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/rackerlabs/repose/pulls/1632/commits","review_comments_url":"https://api.github.com/repos/rackerlabs/repose/pulls/1632/comments","review_comment_url":"https://api.github.com/repos/rackerlabs/repose/pulls/comments{/number}","comments_url":"https://api.github.com/repos/rackerlabs/repose/issues/1632/comments","statuses_url":"https://api.github.com/repos/rackerlabs/repose/statuses/3f4176be4c23b1ee814708402dcc360c663e27b3","head":{"label":"rackerlabs:REP-4674_CORS_Chrome","ref":"REP-4674_CORS_Chrome","sha":"3f4176be4c23b1ee814708402dcc360c663e27b3","user":{"login":"rackerlabs","id":84293,"avatar_url":"https://avatars.githubusercontent.com/u/84293?v=3","gravatar_id":"","url":"https://api.github.com/users/rackerlabs","html_url":"https://github.com/rackerlabs","followers_url":"https://api.github.com/users/rackerlabs/followers","following_url":"https://api.github.com/users/rackerlabs/following{/other_user}","gists_url":"https://api.github.com/users/rackerlabs/gists{/gist_id}","starred_url":"https://api.github.com/users/rackerlabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rackerlabs/subscriptions","organizations_url":"https://api.github.com/users/rackerlabs/orgs","repos_url":"https://api.github.com/users/rackerlabs/repos","events_url":"https://api.github.com/users/rackerlabs/events{/privacy}","received_events_url":"https://api.github.com/users/rackerlabs/received_events","type":"Organization","site_admin":false},"repo":{"id":1981821,"name":"repose","full_name":"rackerlabs/repose","owner":{"login":"rackerlabs","id":84293,"avatar_url":"https://avatars.githubusercontent.com/u/84293?v=3","gravatar_id":"","url":"https://api.github.com/users/rackerlabs","html_url":"https://github.com/rackerlabs","followers_url":"https://api.github.com/users/rackerlabs/followers","following_url":"https://api.github.com/users/rackerlabs/following{/other_user}","gists_url":"https://api.github.com/users/rackerlabs/gists{/gist_id}","starred_url":"https://api.github.com/users/rackerlabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rackerlabs/subscriptions","organizations_url":"https://api.github.com/users/rackerlabs/orgs","repos_url":"https://api.github.com/users/rackerlabs/repos","events_url":"https://api.github.com/users/rackerlabs/events{/privacy}","received_events_url":"https://api.github.com/users/rackerlabs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/rackerlabs/repose","description":"The powerful, programmable, API Middleware Platform","fork":false,"url":"https://api.github.com/repos/rackerlabs/repose","forks_url":"https://api.github.com/repos/rackerlabs/repose/forks","keys_url":"https://api.github.com/repos/rackerlabs/repose/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rackerlabs/repose/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rackerlabs/repose/teams","hooks_url":"https://api.github.com/repos/rackerlabs/repose/hooks","issue_events_url":"https://api.github.com/repos/rackerlabs/repose/issues/events{/number}","events_url":"https://api.github.com/repos/rackerlabs/repose/events","assignees_url":"https://api.github.com/repos/rackerlabs/repose/assignees{/user}","branches_url":"https://api.github.com/repos/rackerlabs/repose/branches{/branch}","tags_url":"https://api.github.com/repos/rackerlabs/repose/tags","blobs_url":"https://api.github.com/repos/rackerlabs/repose/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rackerlabs/repose/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rackerlabs/repose/git/refs{/sha}","trees_url":"https://api.github.com/repos/rackerlabs/repose/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rackerlabs/repose/statuses/{sha}","languages_url":"https://api.github.com/repos/rackerlabs/repose/languages","stargazers_url":"https://api.github.com/repos/rackerlabs/repose/stargazers","contributors_url":"https://api.github.com/repos/rackerlabs/repose/contributors","subscribers_url":"https://api.github.com/repos/rackerlabs/repose/subscribers","subscription_url":"https://api.github.com/repos/rackerlabs/repose/subscription","commits_url":"https://api.github.com/repos/rackerlabs/repose/commits{/sha}","git_commits_url":"https://api.github.com/repos/rackerlabs/repose/git/commits{/sha}","comments_url":"https://api.github.com/repos/rackerlabs/repose/comments{/number}","issue_comment_url":"https://api.github.com/repos/rackerlabs/repose/issues/comments{/number}","contents_url":"https://api.github.com/repos/rackerlabs/repose/contents/{+path}","compare_url":"https://api.github.com/repos/rackerlabs/repose/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rackerlabs/repose/merges","archive_url":"https://api.github.com/repos/rackerlabs/repose/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rackerlabs/repose/downloads","issues_url":"https://api.github.com/repos/rackerlabs/repose/issues{/number}","pulls_url":"https://api.github.com/repos/rackerlabs/repose/pulls{/number}","milestones_url":"https://api.github.com/repos/rackerlabs/repose/milestones{/number}","notifications_url":"https://api.github.com/repos/rackerlabs/repose/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rackerlabs/repose/labels{/name}","releases_url":"https://api.github.com/repos/rackerlabs/repose/releases{/id}","deployments_url":"https://api.github.com/repos/rackerlabs/repose/deployments","created_at":"2011-07-01T04:39:34Z","updated_at":"2016-11-14T17:53:05Z","pushed_at":"2016-12-07T19:49:22Z","git_url":"git://github.com/rackerlabs/repose.git","ssh_url":"git@github.com:rackerlabs/repose.git","clone_url":"https://github.com/rackerlabs/repose.git","svn_url":"https://github.com/rackerlabs/repose","homepage":"http://www.openrepose.org/","size":105677,"stargazers_count":265,"watchers_count":265,"language":"Groovy","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":82,"mirror_url":null,"open_issues_count":10,"forks":82,"open_issues":10,"watchers":265,"default_branch":"master"}},"base":{"label":"rackerlabs:master","ref":"master","sha":"59055dfe5eb25fd4f627984418a2ef122838eea4","user":{"login":"rackerlabs","id":84293,"avatar_url":"https://avatars.githubusercontent.com/u/84293?v=3","gravatar_id":"","url":"https://api.github.com/users/rackerlabs","html_url":"https://github.com/rackerlabs","followers_url":"https://api.github.com/users/rackerlabs/followers","following_url":"https://api.github.com/users/rackerlabs/following{/other_user}","gists_url":"https://api.github.com/users/rackerlabs/gists{/gist_id}","starred_url":"https://api.github.com/users/rackerlabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rackerlabs/subscriptions","organizations_url":"https://api.github.com/users/rackerlabs/orgs","repos_url":"https://api.github.com/users/rackerlabs/repos","events_url":"https://api.github.com/users/rackerlabs/events{/privacy}","received_events_url":"https://api.github.com/users/rackerlabs/received_events","type":"Organization","site_admin":false},"repo":{"id":1981821,"name":"repose","full_name":"rackerlabs/repose","owner":{"login":"rackerlabs","id":84293,"avatar_url":"https://avatars.githubusercontent.com/u/84293?v=3","gravatar_id":"","url":"https://api.github.com/users/rackerlabs","html_url":"https://github.com/rackerlabs","followers_url":"https://api.github.com/users/rackerlabs/followers","following_url":"https://api.github.com/users/rackerlabs/following{/other_user}","gists_url":"https://api.github.com/users/rackerlabs/gists{/gist_id}","starred_url":"https://api.github.com/users/rackerlabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rackerlabs/subscriptions","organizations_url":"https://api.github.com/users/rackerlabs/orgs","repos_url":"https://api.github.com/users/rackerlabs/repos","events_url":"https://api.github.com/users/rackerlabs/events{/privacy}","received_events_url":"https://api.github.com/users/rackerlabs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/rackerlabs/repose","description":"The powerful, programmable, API Middleware Platform","fork":false,"url":"https://api.github.com/repos/rackerlabs/repose","forks_url":"https://api.github.com/repos/rackerlabs/repose/forks","keys_url":"https://api.github.com/repos/rackerlabs/repose/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rackerlabs/repose/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rackerlabs/repose/teams","hooks_url":"https://api.github.com/repos/rackerlabs/repose/hooks","issue_events_url":"https://api.github.com/repos/rackerlabs/repose/issues/events{/number}","events_url":"https://api.github.com/repos/rackerlabs/repose/events","assignees_url":"https://api.github.com/repos/rackerlabs/repose/assignees{/user}","branches_url":"https://api.github.com/repos/rackerlabs/repose/branches{/branch}","tags_url":"https://api.github.com/repos/rackerlabs/repose/tags","blobs_url":"https://api.github.com/repos/rackerlabs/repose/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rackerlabs/repose/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rackerlabs/repose/git/refs{/sha}","trees_url":"https://api.github.com/repos/rackerlabs/repose/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rackerlabs/repose/statuses/{sha}","languages_url":"https://api.github.com/repos/rackerlabs/repose/languages","stargazers_url":"https://api.github.com/repos/rackerlabs/repose/stargazers","contributors_url":"https://api.github.com/repos/rackerlabs/repose/contributors","subscribers_url":"https://api.github.com/repos/rackerlabs/repose/subscribers","subscription_url":"https://api.github.com/repos/rackerlabs/repose/subscription","commits_url":"https://api.github.com/repos/rackerlabs/repose/commits{/sha}","git_commits_url":"https://api.github.com/repos/rackerlabs/repose/git/commits{/sha}","comments_url":"https://api.github.com/repos/rackerlabs/repose/comments{/number}","issue_comment_url":"https://api.github.com/repos/rackerlabs/repose/issues/comments{/number}","contents_url":"https://api.github.com/repos/rackerlabs/repose/contents/{+path}","compare_url":"https://api.github.com/repos/rackerlabs/repose/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rackerlabs/repose/merges","archive_url":"https://api.github.com/repos/rackerlabs/repose/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rackerlabs/repose/downloads","issues_url":"https://api.github.com/repos/rackerlabs/repose/issues{/number}","pulls_url":"https://api.github.com/repos/rackerlabs/repose/pulls{/number}","milestones_url":"https://api.github.com/repos/rackerlabs/repose/milestones{/number}","notifications_url":"https://api.github.com/repos/rackerlabs/repose/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rackerlabs/repose/labels{/name}","releases_url":"https://api.github.com/repos/rackerlabs/repose/releases{/id}","deployments_url":"https://api.github.com/repos/rackerlabs/repose/deployments","created_at":"2011-07-01T04:39:34Z","updated_at":"2016-11-14T17:53:05Z","pushed_at":"2016-12-07T19:49:22Z","git_url":"git://github.com/rackerlabs/repose.git","ssh_url":"git@github.com:rackerlabs/repose.git","clone_url":"https://github.com/rackerlabs/repose.git","svn_url":"https://github.com/rackerlabs/repose","homepage":"http://www.openrepose.org/","size":105677,"stargazers_count":265,"watchers_count":265,"language":"Groovy","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":82,"mirror_url":null,"open_issues_count":10,"forks":82,"open_issues":10,"watchers":265,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/rackerlabs/repose/pulls/1632"},"html":{"href":"https://github.com/rackerlabs/repose/pull/1632"},"issue":{"href":"https://api.github.com/repos/rackerlabs/repose/issues/1632"},"comments":{"href":"https://api.github.com/repos/rackerlabs/repose/issues/1632/comments"},"review_comments":{"href":"https://api.github.com/repos/rackerlabs/repose/pulls/1632/comments"},"review_comment":{"href":"https://api.github.com/repos/rackerlabs/repose/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/rackerlabs/repose/pulls/1632/commits"},"statuses":{"href":"https://api.github.com/repos/rackerlabs/repose/statuses/3f4176be4c23b1ee814708402dcc360c663e27b3"}}}}
{"actor":{"display_login":"dmnjohns"}}