{ # disable automatic SSL certificate generation auto_https off # disable admin API server # admin localhost:2019 admin off # set default SNI for old clients default_sni {$GITPOD_DOMAIN} # debug # configure plugin order # https://caddyserver.com/docs/caddyfile/directives#directive-order order gitpod.cors_origin before header order gitpod.workspace_download before redir order gitpod.headless_log_download before rewrite order gitpod.configcat before rewrite order gitpod.analytics before rewrite order gitpod.sec_websocket_key before header order gitpod.frontend_dev after forward_auth servers { protocols h1 h2 h2c metrics } } (compression) { encode zstd gzip } # configure headers to force HTTPS and enable more strict rules for the browser (security_headers) { header { # enable HSTS Strict-Transport-Security max-age=31536000 # disable clients from sniffing the media type X-Content-Type-Options nosniff # Define valid parents that may embed a page Content-Security-Policy "frame-ancestors 'self' https://*.{$GITPOD_DOMAIN} https://{$GITPOD_DOMAIN}" # keep referrer data off of HTTP connections Referrer-Policy no-referrer-when-downgrade # Enable cross-site filter (XSS) and tell browser to block detected attacks X-XSS-Protection "1; mode=block" defer # delay changes } } # workspace security headers (workspace_security_headers) { header { # Disallow sharing the same browsing context when opened in a popup Cross-Origin-Opener-Policy same-origin-allow-popups } import security_headers } (enable_log) { log { output stdout format if "status > 399" jsonselect "{severity:level} {timestamp:ts} {logName:logger} {httpRequest>requestMethod:request>method} {httpRequest>protocol:request>proto} {httpRequest>status:status} {httpRequest>responseSize:size} {httpRequest>userAgent:request>headers>User-Agent>[0]} {httpRequest>requestUrl:request>uri} {httpRequest>requestHost:request>host} {cacheStatus:resp_headers>X-Cache-Status>[0]}" { level_format "upper" time_format "rfc3339_nano" } } } (enable_log_debug) { log { output stdout format jsonselect "{severity:level} {timestamp:ts} {logName:logger} {httpRequest>requestMethod:request>method} {httpRequest>protocol:request>proto} {httpRequest>status:status} {httpRequest>responseSize:size} {httpRequest>userAgent:request>headers>User-Agent>[0]} {httpRequest>requestUrl:request>uri} {httpRequest>requestHost:request>host} {cacheStatus:resp_headers>X-Cache-Status>[0]}" { level_format "upper" time_format "rfc3339_nano" } } } (remove_server_header) { header { -server -x-powered-by } } (ssl_configuration) { tls /etc/caddy/certificates/tls.crt /etc/caddy/certificates/tls.key { protocols tls1.2 #ca_root <pem_file> } } (upstream_connection) { lb_try_duration 1s } (debug_headers) { header X-Gitpod-Region {$GITPOD_REGION}.{$GITPOD_INSTALLATION_SHORTNAME} } (workspace_transport) { transport http { tls_insecure_skip_verify keepalive 60s keepalive_idle_conns 100 } } (google_storage_headers) { header { -x-guploader-uploadid -etag -x-goog-generation -x-goog-metageneration -x-goog-hash -x-goog-stored-content-length -x-gitpod-region -x-goog-stored-content-encoding -x-goog-storage-class -x-goog-generation -x-goog-metageneration -cache-control -expires defer # delay changes } } (server_public_api) { import compression reverse_proxy server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3001 { import upstream_connection } } # Kubernetes health-check :8003 { respond /live 200 respond /ready 200 } # TODO: refactor once we can listen only in localhost :9545 { metrics /metrics { disable_openmetrics } } # Internal analytics endpoint :9546 { handle /analytics* { gitpod.analytics } } # Internal configcat endpoint :9547 { handle /configcat* { gitpod.configcat } } # public-api api.{$GITPOD_DOMAIN} { log { level DEBUG output stdout } gitpod.cors_origin { allowed_origins https://{$GITPOD_DOMAIN} } @to_server path /auth/*/callback handle @to_server { import compression reverse_proxy server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3000 { import upstream_connection } } handle /gitpod.v1.* { import server_public_api } reverse_proxy public-api-server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:9002 } # always redirect to HTTPS http:// { redir https://{host}{uri} permanent } # The services directly correlate to Public Services exposed by Dedicated # When adding new endpoints, please notify Team Engine https://services.{$GITPOD_DOMAIN} { import enable_log import remove_server_header import ssl_configuration import security_headers handle /idp/* { reverse_proxy public-api-server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:9002 } handle /apps/* { reverse_proxy server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3000 } } https://{$GITPOD_DOMAIN} { import enable_log import remove_server_header import ssl_configuration import security_headers # Make it possible to get an exposed healthcheck, for example for HTTP(s) NLBs handle /api/health { respond 200 } @proxy_server_public_api path /public-api/gitpod.experimental.v1.HelloService* handle @proxy_server_public_api { uri strip_prefix /public-api import server_public_api } @proxy_server_public_api_2 path /public-api/gitpod.v1.* handle @proxy_server_public_api_2 { uri strip_prefix /public-api import server_public_api } @proxy_public_api path /public-api* handle @proxy_public_api { uri strip_prefix /public-api reverse_proxy public-api-server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:9002 } @workspace_download path /workspace-download* handle @workspace_download { import google_storage_headers header { # The browser needs to see the correct archive content type to trigger the download. content-type "application/tar+gzip" } gitpod.workspace_download { service http://server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3000 } # redirect works here because we "navigate" to this URL, which makes the browser handle this as primary request, and not fuff around with CORS at all redir {http.gitpod.workspace_download_url} 303 } @headless_log_download path /headless-log-download* handle @headless_log_download { header { # Alltough logs are plain text "text/html" works for reliably for streaming content-type "text/html; charset=utf-8" } # Perform lookup to server and actual reverse_proxy in one go because caddy's `reverse_proxy` is not powerful enough gitpod.headless_log_download { service http://server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3000 } } @configcat path /configcat* handle @configcat { gitpod.cors_origin { any_domain true } gitpod.configcat } @analytics path /analytics* handle @analytics { gitpod.cors_origin { any_domain true } gitpod.analytics } @browser_analytics path /_analytics* handle @browser_analytics { import compression reverse_proxy server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3000 { import upstream_connection } } @backend_wss { path /api/gitpod /api/v1 } handle @backend_wss { gitpod.cors_origin { base_domain {$GITPOD_DOMAIN} } gitpod.sec_websocket_key uri strip_prefix /api reverse_proxy server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3000 { import upstream_connection } } @backend path /api/* /headless-logs/* /prebuild-logs/* handle @backend { gitpod.cors_origin { base_domain {$GITPOD_DOMAIN} } uri strip_prefix /api reverse_proxy server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3000 { import upstream_connection # required for smooth streaming of terminal logs flush_interval -1 } } @iam path /iam/* handle @iam { gitpod.cors_origin { any_domain true } uri strip_prefix /iam reverse_proxy public-api-server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:9002 { import upstream_connection } } @codesync path /code-sync* handle @codesync { gitpod.cors_origin { any_domain true } import compression reverse_proxy server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3000 { import upstream_connection flush_interval -1 } } @ide_bin { path /static/bin/* } handle @ide_bin { import compression reverse_proxy ide-proxy.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:80 { import upstream_connection } } @to_server path /auth/github/callback /auth /auth/* /apps /apps/* handle @to_server { import compression reverse_proxy server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3000 { import upstream_connection } } # Default: route to dashboard handle { # Dev URL header present? @frontend_dev { header X-Frontend-Dev-URL * } handle @frontend_dev { # ...and allowed to use it? forward_auth server.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3000 { uri /auth/frontend-dev header_up -Connection header_up -Upgrade } # Then handle it with our plugin! gitpod.frontend_dev { upstream http://dashboard.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3001 } } reverse_proxy dashboard.{$KUBE_NAMESPACE}.{$KUBE_DOMAIN}:3001 { import upstream_connection } } @legacy_urls path /github.com/* /gitlab.com/* /bitbucket.org/* handle @legacy_urls { redir https://{$GITPOD_DOMAIN}/#{uri} permanent } handle_errors { redir https://{$GITPOD_DOMAIN}/sorry/#Error%20{http.reverse_proxy.status_text} 302 } } # workspaces https://*.*.{$GITPOD_DOMAIN} { import enable_log import workspace_security_headers import remove_server_header import ssl_configuration import debug_headers # Dear future reader: If you wonder about the asterisk in the line below: So do I! # But as of now (caddy 2.6.2) it's the only way to make caddy import a file. import /etc/caddy/workspace-handler/*.{$WORKSPACE_HANDLER_FILE} } import /etc/caddy/vhosts/vhost.*