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
style-src allowed inline styles only for the diffstat bars, which take
their width from colspan instead. t0004 now checks that the three
configs pin the same policy.
Diffstat (limited to 'custom/servers')
| -rw-r--r-- | custom/servers/apache.conf | 60 | |
| -rw-r--r-- | custom/servers/lighttpd.conf | 32 | |
| -rw-r--r-- | custom/servers/nginx.conf | 73 |
3 files changed, 71 insertions, 94 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. |
