diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Refresh the server configs and drop `unsafe-inline`
The inline handlers and the auto-submitting selects are gone, so
`script-src` no longer needs it, and t0004 now checks that the three
configs pin the same policy.
| -rw-r--r-- | custom/servers/apache.conf | 60 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | custom/servers/lighttpd.conf | 32 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | custom/servers/nginx.conf | 73 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rwxr-xr-x | tests/t0004-docs.sh | 18 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
4 files changed, 87 insertions, 96 deletions
diff --git a/custom/servers/apache.conf b/custom/servers/apache.conf index 3c035f7..bbd3e75 100644 --- a/custom/servers/apache.conf +++ b/custom/servers/apache.conf @@ -136,9 +136,9 @@ AddType text/plain .txt Require all granted # These assets rarely change, so let browsers cache them. Keep the - # caching scoped to this directory. cgit sends no caching headers of its - # own, so a server-wide ExpiresDefault would stamp freshness onto its - # pages, fight the no-store cgit puts on the login page, and could let + # caching scoped to this directory. Ordinary cgit pages carry no caching + # headers, so a server-wide ExpiresDefault would stamp freshness onto + # them, fight the no-store cgit puts on the login page, and could let # one visitor's page be served to another from a shared cache. <IfModule mod_expires.c> ExpiresActive On @@ -148,8 +148,7 @@ AddType text/plain .txt # The cgit binary. <Directory "/usr/lib/cgit"> - # Allow CGI execution here. ScriptAlias implies it, stating it makes the - # intent clear. + # Allow CGI execution here. Options +ExecCGI # Run cgit.cgi as a CGI even if it is ever reached through a plain Alias # rather than ScriptAlias. @@ -159,8 +158,8 @@ AddType text/plain .txt </Directory> -# This vhost only bounces plain HTTP up to HTTPS. It serves no cgit itself, -# every cgit directive lives in the HTTPS vhost below. To run without TLS for +# This vhost only bounces plain HTTP up to HTTPS. It serves no cgit itself. +# Every cgit directive lives in the HTTPS vhost below. To run without TLS for # now, convert the HTTPS vhost to port 80 and delete this whole block rather # than editing it, since deleting only the Redirect line would leave a vhost # that serves nothing. @@ -205,27 +204,25 @@ AddType text/plain .txt # Site-wide security headers are set here so they also cover the static # assets Apache serves. cgit itself sends only the headers the proxy # cannot supply. Those are Status, Content-Type, Content-Length and - # Content-Disposition on downloads, a no-store Cache-Control on - # unauthenticated responses, the auth filter's Set-Cookie, and on raw + # Content-Disposition on downloads, Location on redirects, a no-store + # Cache-Control on unauthenticated responses, the auth filter's + # Set-Cookie, and on raw # repository bytes a nosniff of its own next to the stricter policy # "default-src 'none'". Everything else, this policy included, is the # proxy's job. # # The word setifempty is load bearing on the two headers cgit can also # emit. "Header always set" replaces a same-named header even when the - # CGI sent it, which was verified against Apache 2.4.67 and would swap - # the strict policy on raw repository content for this looser site one. - # setifempty yields to whatever cgit sent and still covers every response - # without one, the HTML pages, the static assets, and with always also - # Apache's own error pages. Referrer-Policy stays a plain set because - # cgit never emits it, so there is nothing to overwrite. + # CGI sent it, which would swap the strict policy on raw repository + # content for this looser site one. setifempty yields to whatever cgit + # sent. Referrer-Policy stays a plain set because cgit never emits it. # - # script-src stays self because cgit loads only its own cgit.js, and - # style-src allows inline for the diffstat bars. form-action self covers - # the login form, the only form cgit renders. If you enable the gravatar - # or libravatar avatar filter, add its host to img-src, for example - # https://www.gravatar.com. - Header always setifempty Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'" + # form-action self covers the login form, the only form cgit renders. If + # you enable the gravatar or libravatar avatar filter, add its host to + # img-src, for example https://www.gravatar.com. A head-include or + # repo.head-content that injects a <style> block needs 'unsafe-inline' + # added to style-src. + Header always setifempty Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'" Header always setifempty X-Content-Type-Options "nosniff" Header always set Referrer-Policy "no-referrer" @@ -250,12 +247,10 @@ AddType text/plain .txt # once browsers have seen it. Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains" - # These five files are the only things served off disk. Each Alias maps - # one URL to one file. Because they come before the ScriptAlias below, a - # request for /cgit.css is answered from disk and never reaches cgit. - # cgit.css and cgit.js are the paths cgit's HTML points at by default, so - # if you relocate the assets update both these Alias targets and the css, - # js, logo and favicon settings in cgitrc to agree. + # These five files are the only things served off disk. cgit.css and + # cgit.js are the paths cgit's HTML points at by default, so if you + # relocate the assets update both these Alias targets and the css, js, + # logo and favicon settings in cgitrc to agree. Alias /cgit.css /usr/share/cgit/cgit.css Alias /cgit.js /usr/share/cgit/cgit.js Alias /cgit.png /usr/share/cgit/cgit.png @@ -265,13 +260,10 @@ AddType text/plain .txt # ScriptAlias maps a URL prefix to a path, marks it executable, and # forwards the rest of the URL as PATH_INFO. Mapping / makes cgit the # handler for every URL the static Aliases above did not already claim. - # - # The trailing slash on cgit.cgi/ is load bearing. It tells Apache that - # cgit.cgi is the program and the rest of the URL is PATH_INFO. So a - # request for /torvalds/linux/tree/kernel?h=next runs the binary with - # PATH_INFO set to /torvalds/linux/tree/kernel and QUERY_STRING set to - # h=next. cgit derives its link base from SCRIPT_NAME, which at the domain - # root is / and needs no tuning. For a sub-path install see the note below. + # The trailing slash on cgit.cgi/ is load bearing, telling Apache that + # cgit.cgi is the program and the rest of the URL is PATH_INFO. cgit + # derives its link base from SCRIPT_NAME, which at the domain root is / + # and needs no tuning. For a sub-path install see the note below. ScriptAlias / /usr/lib/cgit/cgit.cgi/ </VirtualHost> diff --git a/custom/servers/lighttpd.conf b/custom/servers/lighttpd.conf index dcfb43d..27c325d 100644 --- a/custom/servers/lighttpd.conf +++ b/custom/servers/lighttpd.conf @@ -22,10 +22,9 @@ # straight off disk and must never be routed through cgit. -# A plain assignment rather than "+=", since this config stands on its own and -# there is no distro base list to append to. mod_alias maps URL paths onto -# files, mod_setenv injects CGIT_CONFIG and the response headers, and mod_cgi -# runs cgit.cgi. Adding mod_accesslog here is what the access log below needs. +# A plain assignment rather than "+=", since this config stands on its own +# and there is no distro base list to append to. Adding mod_accesslog here +# is what the access log below needs. server.modules = ( "mod_alias", "mod_setenv", @@ -37,8 +36,8 @@ server.modules = ( server.port = 80 server.username = "http" # Debian and Ubuntu use www-data server.groupname = "http" -server.document-root = "/usr/share/cgit" # a valid docroot must exist, the - # alias rules below do the routing +server.document-root = "/usr/share/cgit" # a valid docroot must exist. The + # alias rules below do the routing. server.pid-file = "/run/lighttpd.pid" server.errorlog = "/var/log/lighttpd/error.log" accesslog.filename = "/var/log/lighttpd/access.log" @@ -76,8 +75,9 @@ $HTTP["host"] == "git.example.org" { # Site-wide security headers are set here so they also cover the static # assets lighttpd serves. cgit itself sends only the headers the server # cannot supply. Those are Status, Content-Type, Content-Length and - # Content-Disposition on downloads, a no-store Cache-Control on - # unauthenticated responses, the auth filter's Set-Cookie, and on raw + # Content-Disposition on downloads, Location on redirects, a no-store + # Cache-Control on unauthenticated responses, the auth filter's + # Set-Cookie, and on raw # repository bytes a nosniff of its own next to the stricter policy # "default-src 'none'". Everything else, this policy included, is the # server's job. @@ -89,10 +89,10 @@ $HTTP["host"] == "git.example.org" { # what cgit sent, swapping the strict policy on raw repository content # for this looser site one. Never switch add to set. # - # script-src stays self because cgit loads only its own cgit.js, and - # style-src allows inline for the diffstat bars. form-action self covers - # the login form, the only form cgit renders. If you enable the gravatar - # or libravatar avatar filter, add its host to img-src. + # form-action self covers the login form, the only form cgit renders. If + # you enable the gravatar or libravatar avatar filter, add its host to + # img-src. A head-include or repo.head-content that injects a <style> + # block needs 'unsafe-inline' added to style-src. # # Permissions-Policy refuses the browser features a git viewer never asks # for, camera and location among them, for everything served here, @@ -104,7 +104,7 @@ $HTTP["host"] == "git.example.org" { # Cross-Origin-Embedder-Policy is deliberately absent because it would # break the avatar filters mentioned above. setenv.add-response-header = ( - "Content-Security-Policy" => "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'", + "Content-Security-Policy" => "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'", "X-Content-Type-Options" => "nosniff", "Referrer-Policy" => "no-referrer", "Permissions-Policy" => "accelerometer=(), autoplay=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), usb=()", @@ -116,10 +116,8 @@ $HTTP["host"] == "git.example.org" { # it is hard to undo once browsers have seen it. #setenv.add-response-header += ( "Strict-Transport-Security" => "max-age=63072000; includeSubDomains" ) - # Register the cgit binary as a CGI program. The key cgit.cgi matches the - # binary's name and the empty value means the file is itself the program, - # with no interpreter in front of it. This is what makes lighttpd split - # the trailing path off as PATH_INFO, so never drop it. + # Register the cgit binary as a CGI program. The empty value means the + # file is itself the program rather than input to an interpreter. cgi.assign = ( "cgit.cgi" => "" ) # Routing. lighttpd's alias.url is first-match in declaration order, not diff --git a/custom/servers/nginx.conf b/custom/servers/nginx.conf index cf1e49f..3ffa2ba 100644 --- a/custom/servers/nginx.conf +++ b/custom/servers/nginx.conf @@ -16,8 +16,7 @@ # PATH_INFO and reads page options such as h= and id= from QUERY_STRING, so # nginx must pass PATH_INFO through to the binary. cgit builds its own link # base from SCRIPT_NAME. The five static assets are served straight off disk -# and must never be routed through cgit. Passing PATH_INFO through is the -# single most important part of the config below. +# and must never be routed through cgit. # # nginx cannot run CGI programs itself, so a small bridge called fcgiwrap runs # the cgit.cgi binary and speaks FastCGI to nginx. Nothing below works until @@ -48,11 +47,10 @@ events { http { - # nginx's compiled-in type table knows only text/html, and the usual - # "include mime.types" would pull in a second file. Exactly five static - # files are served off disk, so their types are declared here instead and - # this config keeps standing on its own. Everything else nginx returns - # comes from cgit, which sets its own Content-Type. + # nginx's built-in type table covers none of these five extensions, and + # the usual "include mime.types" would pull in a second file, so the five + # types are declared here. Everything else nginx returns comes from cgit, + # which sets its own Content-Type. types { text/css css; text/javascript js; @@ -80,14 +78,11 @@ http { gzip_types text/css text/javascript text/plain application/atom+xml; - # Requests carrying a Host header this config does not serve, a raw IP or - # an invented name from a scanning bot, land in these two blocks and are - # dropped without a response. cgit keys its page cache on the Host header - # so clone URLs stay honest, which means every invented hostname reaching - # it would mint a cache entry of its own and evict a real page to make - # room. Refusing strangers here keeps the cache to the names the site - # actually answers to. 444 is nginx shorthand for closing the connection - # without replying. + # Requests carrying a Host header this config does not serve are dropped + # without a response. cgit keys its page cache on the Host header, so + # every invented hostname reaching it would mint a cache entry of its + # own and evict a real page. 444 is nginx shorthand for closing the + # connection without replying. server { listen 80 default_server; listen [::]:80 default_server; @@ -158,8 +153,9 @@ http { # Site-wide security headers sit here because they must also cover the # static assets nginx serves directly. cgit itself sends only the # headers the proxy cannot supply. Those are Status, Content-Type, - # Content-Length and Content-Disposition on downloads, a no-store - # Cache-Control on unauthenticated responses, the auth filter's + # Content-Length and Content-Disposition on downloads, Location on + # redirects, a no-store Cache-Control on unauthenticated responses, + # the auth filter's # Set-Cookie, and on raw repository bytes a nosniff of its own next to # the stricter policy "default-src 'none'". Everything else, this # policy included, is the proxy's job. @@ -170,13 +166,13 @@ http { # construct that rewrites response headers here could strip the # protection cgit puts on raw repository content. # - # cgit loads only its own /cgit.js and uses inline style on the - # diffstat bars, so script-src stays self while style-src allows - # inline. form-action self covers the login form, the only form cgit + # form-action self covers the login form, the only form cgit # renders. always applies them to error responses too. If you enable # the gravatar or libravatar avatar filter, add its host to img-src, # for example https://www.gravatar.com or https://seccdn.libravatar.org. - add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'" always; + # A head-include or repo.head-content that injects a <style> block + # needs 'unsafe-inline' added to style-src. + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer" always; @@ -203,8 +199,7 @@ http { # The document root is the directory that holds the static assets. cgit # emits absolute links to /cgit.css and /cgit.png by default, so those - # files must resolve at the root of the URL space. Pointing root at the - # asset directory makes /cgit.css map to /usr/share/cgit/cgit.css. + # files must resolve at the root of the URL space. root /usr/share/cgit; # The only request body cgit ever reads is the auth-filter login form, @@ -216,15 +211,11 @@ http { error_log /var/log/nginx/cgit.error.log; # Match the assets by their exact root-level names, never by bare - # extension. cgit routes on PATH_INFO and a repository can hold files - # ending in .css or .png, so /myrepo/tree/style.css and /myrepo/plain/ - # logo.png are real cgit URLs. A broad extension match would capture - # those, look for them on disk, and return 404 before cgit could render - # them. Anchoring the regex at the start of the path matches /cgit.css - # but not /myrepo/tree/cgit.css, so it can never shadow a repository - # file. An nginx regex location is matched before the prefix location - # below, so these assets win for their exact URLs and cgit wins for the - # rest. + # extension. A repository can hold files ending in .css or .png, and + # /myrepo/tree/style.css is a real cgit URL a broad match would + # capture and 404. The regex anchored at ^/ matches /cgit.css but + # never /myrepo/tree/cgit.css, and a regex location beats the prefix + # location below, so assets win their exact URLs and cgit the rest. location ~ ^/(cgit\.css|cgit\.js|cgit\.png|favicon\.ico|robots\.txt)$ { expires 30d; access_log off; @@ -234,13 +225,10 @@ http { # Everything that is not a static asset above is a cgit URL, the repo # index, a repository, a page within a repository, a snapshot, a feed. location / { - # The CGI environment. This is normally "include fastcgi_params", - # which would be a second file, so the list is written out here. - # Of all of it cgit itself reads only CGIT_CONFIG, PATH_INFO, - # QUERY_STRING, SCRIPT_NAME, REQUEST_METHOD, CONTENT_LENGTH, - # HTTP_HOST, HTTPS, SERVER_NAME, SERVER_PORT, HTTP_COOKIE and - # HTTP_REFERER. The cookie and referer arrive on their own, since - # nginx forwards request headers as HTTP_* without being asked. + # The CGI environment, normally "include fastcgi_params", written + # out here so this config stands alone. The cookie and referer + # arrive on their own, since nginx forwards request headers as + # HTTP_* without being asked. # The program fcgiwrap runs. It must be the cgit binary itself, not # $document_root$fastcgi_script_name, which would try to run a repo @@ -291,8 +279,7 @@ http { fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; - # Hand off to the fcgiwrap socket. A TCP fcgiwrap would use for - # example 127.0.0.1:9000 here. + # Hand off to the fcgiwrap socket. fastcgi_pass unix:/run/fcgiwrap.socket; # Large outputs such as snapshot tarballs and blame on big files @@ -300,8 +287,8 @@ http { fastcgi_read_timeout 300s; fastcgi_buffering off; - # cgit sends no caching headers of its own, so browsers refetch - # dynamic pages and cgit's internal cache keeps that cheap. Do not + # Ordinary cgit pages carry no caching headers, so browsers + # refetch them and cgit's internal cache keeps that cheap. Do not # add a blanket expires or Cache-Control in this location. It # would fight the no-store cgit puts on the login page and could # let one visitor's page be served to another from a shared cache. diff --git a/tests/t0004-docs.sh b/tests/t0004-docs.sh index 292223f..85a4d8e 100755 --- a/tests/t0004-docs.sh +++ b/tests/t0004-docs.sh @@ -2,8 +2,9 @@ # Checks that the configuration documents stay consistent with each other. # The manual documents every key the reference config sets, the reference -# config sets every key the manual documents, and both settings sections of -# the manual keep their entries sorted. +# config sets every key the manual documents, both settings sections of the +# manual keep their entries sorted, and the three server configs carry one +# identical content security policy. test_description='Check the configuration documents' . ./setup.sh @@ -49,4 +50,17 @@ test_expect_success 'the manual lists its repository settings in order' ' LC_ALL=C sort -c repo-order ' +# The policy is spelled out once per server syntax, so nothing but this check +# keeps the three copies from drifting apart. +test_expect_success 'the server configs agree on one content security policy' ' + for server in apache lighttpd nginx; do + grep "Content-Security-Policy" \ + "$ROOT/custom/servers/$server.conf" | + grep "default-src" | + sed "s/.*\"\(default-src[^\"]*\)\".*/\1/" || return 1 + done >policies && + test_line_count = 3 policies && + test $(sort -u policies | wc -l) -eq 1 +' + test_done |
