From 4a0eb766d2ea811e3f0e648868878e329118ba7e Mon Sep 17 00:00:00 2001 From: J the Code Monkey Date: Tue, 1 Oct 2024 17:40:48 -0400 Subject: [PATCH] feat: add support for http and style output #22 --- cmd/install.go | 58 ++++++++----------- pkg/network/certbot.go | 24 ++++++-- pkg/relays/khatru29/nginx_http.go | 44 +++++++++++--- pkg/relays/khatru29/nginx_https.go | 13 +++-- pkg/relays/khatru29/success_messages.go | 4 +- pkg/relays/khatru_pyramid/nginx_http.go | 44 +++++++++++--- pkg/relays/khatru_pyramid/nginx_https.go | 13 +++-- pkg/relays/khatru_pyramid/success_messages.go | 4 +- pkg/relays/strfry/nginx_http.go | 44 +++++++++++--- pkg/relays/strfry/nginx_https.go | 13 +++-- pkg/relays/strfry/success_messages.go | 4 +- pkg/relays/wot_relay/nginx_http.go | 44 +++++++++++--- pkg/relays/wot_relay/nginx_https.go | 13 +++-- pkg/relays/wot_relay/service.go | 10 ++-- pkg/relays/wot_relay/success_messages.go | 4 +- pkg/utils/messages/utils.go | 51 ++++++++-------- pkg/utils/systemd/utils.go | 16 +++-- pkg/utils/templates/utils.go | 14 ++++- 18 files changed, 278 insertions(+), 139 deletions(-) diff --git a/cmd/install.go b/cmd/install.go index a683e96..94b06bd 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -22,10 +22,6 @@ var installCmd = &cobra.Command{ relayDomain, _ := pterm.DefaultInteractiveTextInput.Show("Relay domain name") pterm.Println() - pterm.Println(pterm.Yellow("Leave email empty if you don't want to receive notifications from Let's Encrypt about your SSL cert.")) - pterm.Println() - ssl_email, _ := pterm.DefaultInteractiveTextInput.Show("Email address") - pterm.Println() // Supported relay options options := []string{"Khatru Pyramid", "strfry", "Khatru29", "WoT Relay"} @@ -62,14 +58,12 @@ var installCmd = &cobra.Command{ khatru_pyramid.ConfigureNginxHttp(relayDomain) // Step 4: Get SSL certificates - var shouldContinue = network.GetCertificates(relayDomain, ssl_email) - if !shouldContinue { - return + var httpsEnabled = network.GetCertificates(relayDomain) + if httpsEnabled { + // Step 5: Configure Nginx for HTTPS + khatru_pyramid.ConfigureNginxHttps(relayDomain) } - // Step 5: Configure Nginx for HTTPS - khatru_pyramid.ConfigureNginxHttps(relayDomain) - // Step 6: Download and install the relay binary khatru_pyramid.InstallRelayBinary() @@ -77,7 +71,7 @@ var installCmd = &cobra.Command{ khatru_pyramid.SetupRelayService(relayDomain, pubkey) // Step 8: Show success messages - khatru_pyramid.SuccessMessages(relayDomain) + khatru_pyramid.SuccessMessages(relayDomain, httpsEnabled) } else if selectedRelayOption == "strfry" { // Step 2: Configure the firewall network.ConfigureFirewall() @@ -86,14 +80,12 @@ var installCmd = &cobra.Command{ strfry.ConfigureNginxHttp(relayDomain) // Step 4: Get SSL certificates - var shouldContinue = network.GetCertificates(relayDomain, ssl_email) - if !shouldContinue { - return + var httpsEnabled = network.GetCertificates(relayDomain) + if httpsEnabled { + // Step 5: Configure Nginx for HTTPS + strfry.ConfigureNginxHttps(relayDomain) } - // Step 5: Configure Nginx for HTTPS - strfry.ConfigureNginxHttps(relayDomain) - // Step 6: Download and install the relay binary strfry.InstallRelayBinary() @@ -101,7 +93,7 @@ var installCmd = &cobra.Command{ strfry.SetupRelayService(relayDomain) // Step 8: Show success messages - strfry.SuccessMessages(relayDomain) + strfry.SuccessMessages(relayDomain, httpsEnabled) } else if selectedRelayOption == "Khatru29" { // Step 2: Configure the firewall network.ConfigureFirewall() @@ -110,14 +102,12 @@ var installCmd = &cobra.Command{ khatru29.ConfigureNginxHttp(relayDomain) // Step 4: Get SSL certificates - var shouldContinue = network.GetCertificates(relayDomain, ssl_email) - if !shouldContinue { - return + var httpsEnabled = network.GetCertificates(relayDomain) + if httpsEnabled { + // Step 5: Configure Nginx for HTTPS + khatru29.ConfigureNginxHttps(relayDomain) } - // Step 5: Configure Nginx for HTTPS - khatru29.ConfigureNginxHttps(relayDomain) - // Step 6: Download and install the relay binary khatru29.InstallRelayBinary() @@ -125,7 +115,7 @@ var installCmd = &cobra.Command{ khatru29.SetupRelayService(relayDomain, privkey) // Step 8: Show success messages - khatru29.SuccessMessages(relayDomain) + khatru29.SuccessMessages(relayDomain, httpsEnabled) } else if selectedRelayOption == "WoT Relay" { // Step 2: Configure the firewall network.ConfigureFirewall() @@ -134,29 +124,27 @@ var installCmd = &cobra.Command{ wot_relay.ConfigureNginxHttp(relayDomain) // Step 4: Get SSL certificates - var shouldContinue = network.GetCertificates(relayDomain, ssl_email) - if !shouldContinue { - return + var httpsEnabled = network.GetCertificates(relayDomain) + if httpsEnabled { + // Step 5: Configure Nginx for HTTPS + wot_relay.ConfigureNginxHttps(relayDomain) } - // Step 5: Configure Nginx for HTTPS - wot_relay.ConfigureNginxHttps(relayDomain) - // Step 6: Download and install the relay binary wot_relay.InstallRelayBinary() // Step 7: Set up the relay service - wot_relay.SetupRelayService(relayDomain, pubkey) + wot_relay.SetupRelayService(relayDomain, pubkey, httpsEnabled) // Step 8: Show success messages - wot_relay.SuccessMessages(relayDomain) + wot_relay.SuccessMessages(relayDomain, httpsEnabled) } pterm.Println() - pterm.Println(pterm.Magenta("Join the NODE-TEC Discord to get support:")) + pterm.Println(pterm.Cyan("Join the NODE-TEC Discord to get support:")) pterm.Println(pterm.Magenta("https://discord.gg/J9gRK5pbWb")) pterm.Println() - pterm.Println(pterm.Magenta("We plan to use relay groups for support in the future...")) + pterm.Println(pterm.Cyan("We plan to use relay groups for support in the future...")) pterm.Println() pterm.Println(pterm.Magenta("You can re-run this installer with `rwz install`.")) diff --git a/pkg/network/certbot.go b/pkg/network/certbot.go index 9e2bc77..baeaf2f 100644 --- a/pkg/network/certbot.go +++ b/pkg/network/certbot.go @@ -10,15 +10,23 @@ import ( ) // Function to get SSL certificates using Certbot -func GetCertificates(domainName, email string) bool { +func GetCertificates(domainName string) bool { - options := []string{"yes", "no"} + var ThemeDefault = pterm.ThemeDefault - prompt := pterm.DefaultInteractiveContinue.WithOptions(options) + var prompt = pterm.InteractiveContinuePrinter{ + DefaultValueIndex: 0, + DefaultText: "Obtain SSL certificates?", + TextStyle: &ThemeDefault.PrimaryStyle, + Options: []string{"yes", "no"}, + OptionsStyle: &ThemeDefault.SuccessMessageStyle, + SuffixStyle: &ThemeDefault.SecondaryStyle, + Delimiter: ": ", + } pterm.Println() pterm.Println(pterm.Cyan("Do you want to obtain SSL certificates using Certbot?")) - pterm.Println(pterm.Cyan("This step requires that you already have a configured domain name.")) + pterm.Println(pterm.Cyan("If you select 'yes', then this step requires that you already have a configured domain name.")) pterm.Println(pterm.Cyan("You can always re-run this installer after you have configured your domain name.")) pterm.Println() @@ -28,6 +36,11 @@ func GetCertificates(domainName, email string) bool { return false } + pterm.Println() + pterm.Println(pterm.Yellow("Leave email empty if you don't want to receive notifications from Let's Encrypt about your SSL certificates.")) + + pterm.Println() + email, _ := pterm.DefaultInteractiveTextInput.Show("Email address") pterm.Println() spinner, _ := pterm.DefaultSpinner.Start("Checking SSL certificates...") @@ -36,7 +49,8 @@ func GetCertificates(domainName, email string) bool { // Check if certificates already exist if files.FileExists(fmt.Sprintf("%s/fullchain.pem", certificatePath)) && - files.FileExists(fmt.Sprintf("%s/privkey.pem", certificatePath)) { + files.FileExists(fmt.Sprintf("%s/privkey.pem", certificatePath)) && + files.FileExists(fmt.Sprintf("%s/chain.pem", certificatePath)) { spinner.Info("SSL certificates already exist.") return true } diff --git a/pkg/relays/khatru29/nginx_http.go b/pkg/relays/khatru29/nginx_http.go index eaeae87..f595f59 100644 --- a/pkg/relays/khatru29/nginx_http.go +++ b/pkg/relays/khatru29/nginx_http.go @@ -2,7 +2,6 @@ package khatru29 import ( "fmt" - "github.com/nodetec/rwz/pkg/utils/directories" "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/systemd" "github.com/pterm/pterm" @@ -16,8 +15,6 @@ func ConfigureNginxHttp(domainName string) { var configContent string - directories.CreateDirectory(fmt.Sprintf("/var/www/%s/.well-known/acme-challenge/", domainName), 0755) - files.RemoveFile(configFilePath) configContent = fmt.Sprintf(`map $http_upgrade $connection_upgrade { @@ -35,12 +32,10 @@ server { listen [::]:80; server_name %s; - location /.well-known/acme-challenge/ { - root /var/www/%s; - allow all; - } - location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying 404. + try_files $uri $uri/ =404; proxy_pass http://websocket_khatru29; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; @@ -48,8 +43,39 @@ server { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; } + + # Only return Nginx in server header + server_tokens off; + + #### Security Headers #### + # Test configuration: + # https://securityheaders.com/ + # https://observatory.mozilla.org/ + add_header X-Frame-Options DENY; + + # Avoid MIME type sniffing + add_header X-Content-Type-Options nosniff always; + + add_header Referrer-Policy "no-referrer" always; + + add_header X-XSS-Protection 0 always; + + add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; + + #### Content-Security-Policy (CSP) #### + add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none';" always; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name %s; + + location / { + return 301 http://%s$request_uri; + } } -`, domainName, domainName, domainName) +`, domainName, domainName, domainName, domainName) files.WriteFile(configFilePath, configContent, 0644) diff --git a/pkg/relays/khatru29/nginx_https.go b/pkg/relays/khatru29/nginx_https.go index 7f7318c..57cfe72 100644 --- a/pkg/relays/khatru29/nginx_https.go +++ b/pkg/relays/khatru29/nginx_https.go @@ -2,6 +2,7 @@ package khatru29 import ( "fmt" + "github.com/nodetec/rwz/pkg/utils/directories" "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/systemd" "github.com/pterm/pterm" @@ -17,6 +18,8 @@ func ConfigureNginxHttps(domainName string) { files.RemoveFile(configFilePath) + directories.CreateDirectory(fmt.Sprintf("/var/www/%s/.well-known/acme-challenge/", domainName), 0755) + configContent = fmt.Sprintf(`map $http_upgrade $connection_upgrade { default upgrade; '' close; @@ -45,6 +48,9 @@ server { proxy_set_header X-Forwarded-For $remote_addr; } + # Only return Nginx in server header + server_tokens off; + #### SSL Configuration #### # Test configuration: # https://www.ssllabs.com/ssltest/analyze.html @@ -54,9 +60,6 @@ server { # Verify chain of trust of OCSP response using Root CA and Intermediate certs ssl_trusted_certificate /etc/letsencrypt/live/%s/chain.pem; - # Only return Nginx in server header - server_tokens off; - # TODO # Add support to generate the file in the script #ssl_dhparam /etc/ssl/certs/dhparam.pem; @@ -87,7 +90,7 @@ server { ssl_stapling on; ssl_stapling_verify on; - # Security headers + #### Security Headers #### # Test configuration: # https://securityheaders.com/ # https://observatory.mozilla.org/ @@ -104,7 +107,7 @@ server { add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; - # Content-Security-Policy (CSP) + #### Content-Security-Policy (CSP) #### add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests;" always; } diff --git a/pkg/relays/khatru29/success_messages.go b/pkg/relays/khatru29/success_messages.go index 4b8bf1a..edc041e 100644 --- a/pkg/relays/khatru29/success_messages.go +++ b/pkg/relays/khatru29/success_messages.go @@ -4,7 +4,7 @@ import ( "github.com/nodetec/rwz/pkg/utils/messages" ) -func SuccessMessages(domain string) { +func SuccessMessages(domain string, httpsEnabled bool) { const dataDir = "/var/lib/khatru29" const envFile = "/etc/systemd/system/khatru29.env" const serviceFile = "/etc/systemd/system/khatru29.service" @@ -12,6 +12,6 @@ func SuccessMessages(domain string) { const relayName = "Khatru29" const githubLink = "https://github.com/fiatjaf/relay29/tree/master" - successMsgParams := messages.SuccessMsgParams{Domain: domain, DataDir: dataDir, EnvFile: envFile, ServiceFile: serviceFile, Service: service, RelayName: relayName, GitHubLink: githubLink} + successMsgParams := messages.SuccessMsgParams{Domain: domain, HTTPSEnabled: httpsEnabled, DataDir: dataDir, EnvFile: envFile, ServiceFile: serviceFile, Service: service, RelayName: relayName, GitHubLink: githubLink} messages.SuccessMessages(&successMsgParams) } diff --git a/pkg/relays/khatru_pyramid/nginx_http.go b/pkg/relays/khatru_pyramid/nginx_http.go index 81c6acb..2b6257d 100644 --- a/pkg/relays/khatru_pyramid/nginx_http.go +++ b/pkg/relays/khatru_pyramid/nginx_http.go @@ -2,7 +2,6 @@ package khatru_pyramid import ( "fmt" - "github.com/nodetec/rwz/pkg/utils/directories" "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/systemd" "github.com/pterm/pterm" @@ -16,8 +15,6 @@ func ConfigureNginxHttp(domainName string) { var configContent string - directories.CreateDirectory(fmt.Sprintf("/var/www/%s/.well-known/acme-challenge/", domainName), 0755) - files.RemoveFile(configFilePath) configContent = fmt.Sprintf(`map $http_upgrade $connection_upgrade { @@ -35,12 +32,10 @@ server { listen [::]:80; server_name %s; - location /.well-known/acme-challenge/ { - root /var/www/%s; - allow all; - } - location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying 404. + try_files $uri $uri/ =404; proxy_pass http://websocket_khatru_pyramid; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; @@ -48,8 +43,39 @@ server { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; } + + # Only return Nginx in server header + server_tokens off; + + #### Security Headers #### + # Test configuration: + # https://securityheaders.com/ + # https://observatory.mozilla.org/ + add_header X-Frame-Options DENY; + + # Avoid MIME type sniffing + add_header X-Content-Type-Options nosniff always; + + add_header Referrer-Policy "no-referrer" always; + + add_header X-XSS-Protection 0 always; + + add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; + + #### Content-Security-Policy (CSP) #### + add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none';" always; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name %s; + + location / { + return 301 http://%s$request_uri; + } } -`, domainName, domainName, domainName) +`, domainName, domainName, domainName, domainName) files.WriteFile(configFilePath, configContent, 0644) diff --git a/pkg/relays/khatru_pyramid/nginx_https.go b/pkg/relays/khatru_pyramid/nginx_https.go index 4432769..244568a 100644 --- a/pkg/relays/khatru_pyramid/nginx_https.go +++ b/pkg/relays/khatru_pyramid/nginx_https.go @@ -2,6 +2,7 @@ package khatru_pyramid import ( "fmt" + "github.com/nodetec/rwz/pkg/utils/directories" "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/systemd" "github.com/pterm/pterm" @@ -17,6 +18,8 @@ func ConfigureNginxHttps(domainName string) { files.RemoveFile(configFilePath) + directories.CreateDirectory(fmt.Sprintf("/var/www/%s/.well-known/acme-challenge/", domainName), 0755) + configContent = fmt.Sprintf(`map $http_upgrade $connection_upgrade { default upgrade; '' close; @@ -45,6 +48,9 @@ server { proxy_set_header X-Forwarded-For $remote_addr; } + # Only return Nginx in server header + server_tokens off; + #### SSL Configuration #### # Test configuration: # https://www.ssllabs.com/ssltest/analyze.html @@ -54,9 +60,6 @@ server { # Verify chain of trust of OCSP response using Root CA and Intermediate certs ssl_trusted_certificate /etc/letsencrypt/live/%s/chain.pem; - # Only return Nginx in server header - server_tokens off; - # TODO # Add support to generate the file in the script #ssl_dhparam /etc/ssl/certs/dhparam.pem; @@ -87,7 +90,7 @@ server { ssl_stapling on; ssl_stapling_verify on; - # Security headers + #### Security Headers #### # Test configuration: # https://securityheaders.com/ # https://observatory.mozilla.org/ @@ -104,7 +107,7 @@ server { add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; - # Content-Security-Policy (CSP) + #### Content-Security-Policy (CSP) #### add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests;" always; } diff --git a/pkg/relays/khatru_pyramid/success_messages.go b/pkg/relays/khatru_pyramid/success_messages.go index 9c42655..d85c981 100644 --- a/pkg/relays/khatru_pyramid/success_messages.go +++ b/pkg/relays/khatru_pyramid/success_messages.go @@ -4,7 +4,7 @@ import ( "github.com/nodetec/rwz/pkg/utils/messages" ) -func SuccessMessages(domain string) { +func SuccessMessages(domain string, httpsEnabled bool) { const dataDir = "/var/lib/khatru-pyramid" const envFile = "/etc/systemd/system/khatru-pyramid.env" const serviceFile = "/etc/systemd/system/khatru-pyramid.service" @@ -12,6 +12,6 @@ func SuccessMessages(domain string) { const relayName = "Khatru Pyramid" const githubLink = "https://github.com/github-tijlxyz/khatru-pyramid" - successMsgParams := messages.SuccessMsgParams{Domain: domain, DataDir: dataDir, EnvFile: envFile, ServiceFile: serviceFile, Service: service, RelayName: relayName, GitHubLink: githubLink} + successMsgParams := messages.SuccessMsgParams{Domain: domain, HTTPSEnabled: httpsEnabled, DataDir: dataDir, EnvFile: envFile, ServiceFile: serviceFile, Service: service, RelayName: relayName, GitHubLink: githubLink} messages.SuccessMessages(&successMsgParams) } diff --git a/pkg/relays/strfry/nginx_http.go b/pkg/relays/strfry/nginx_http.go index d917b98..6ffc4a2 100644 --- a/pkg/relays/strfry/nginx_http.go +++ b/pkg/relays/strfry/nginx_http.go @@ -2,7 +2,6 @@ package strfry import ( "fmt" - "github.com/nodetec/rwz/pkg/utils/directories" "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/systemd" "github.com/pterm/pterm" @@ -16,8 +15,6 @@ func ConfigureNginxHttp(domainName string) { var configContent string - directories.CreateDirectory(fmt.Sprintf("/var/www/%s/.well-known/acme-challenge/", domainName), 0755) - files.RemoveFile(configFilePath) configContent = fmt.Sprintf(`# %s @@ -26,12 +23,10 @@ server { listen [::]:80; server_name %s; - location /.well-known/acme-challenge/ { - root /var/www/%s; - allow all; - } - location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying 404. + try_files $uri $uri/ =404; proxy_pass http://127.0.0.1:7777; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; @@ -39,8 +34,39 @@ server { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } + + # Only return Nginx in server header + server_tokens off; + + #### Security Headers #### + # Test configuration: + # https://securityheaders.com/ + # https://observatory.mozilla.org/ + add_header X-Frame-Options DENY; + + # Avoid MIME type sniffing + add_header X-Content-Type-Options nosniff always; + + add_header Referrer-Policy "no-referrer" always; + + add_header X-XSS-Protection 0 always; + + add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; + + #### Content-Security-Policy (CSP) #### + add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none';" always; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name %s; + + location / { + return 301 http://%s$request_uri; + } } -`, domainName, domainName, domainName) +`, domainName, domainName, domainName, domainName) files.WriteFile(configFilePath, configContent, 0644) diff --git a/pkg/relays/strfry/nginx_https.go b/pkg/relays/strfry/nginx_https.go index 2143736..645b4e4 100644 --- a/pkg/relays/strfry/nginx_https.go +++ b/pkg/relays/strfry/nginx_https.go @@ -2,6 +2,7 @@ package strfry import ( "fmt" + "github.com/nodetec/rwz/pkg/utils/directories" "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/systemd" "github.com/pterm/pterm" @@ -17,6 +18,8 @@ func ConfigureNginxHttps(domainName string) { files.RemoveFile(configFilePath) + directories.CreateDirectory(fmt.Sprintf("/var/www/%s/.well-known/acme-challenge/", domainName), 0755) + configContent = fmt.Sprintf(`server { listen 443 ssl http2; listen [::]:443 ssl http2; @@ -36,6 +39,9 @@ func ConfigureNginxHttps(domainName string) { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } + # Only return Nginx in server header + server_tokens off; + #### SSL Configuration #### # Test configuration: # https://www.ssllabs.com/ssltest/analyze.html @@ -45,9 +51,6 @@ func ConfigureNginxHttps(domainName string) { # Verify chain of trust of OCSP response using Root CA and Intermediate certs ssl_trusted_certificate /etc/letsencrypt/live/%s/chain.pem; - # Only return Nginx in server header - server_tokens off; - # TODO # Add support to generate the file in the script #ssl_dhparam /etc/ssl/certs/dhparam.pem; @@ -78,7 +81,7 @@ func ConfigureNginxHttps(domainName string) { ssl_stapling on; ssl_stapling_verify on; - # Security headers + #### Security Headers #### # Test configuration: # https://securityheaders.com/ # https://observatory.mozilla.org/ @@ -95,7 +98,7 @@ func ConfigureNginxHttps(domainName string) { add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; - # Content-Security-Policy (CSP) + #### Content-Security-Policy (CSP) #### add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests;" always; } diff --git a/pkg/relays/strfry/success_messages.go b/pkg/relays/strfry/success_messages.go index 7ff4d0b..b23c56b 100644 --- a/pkg/relays/strfry/success_messages.go +++ b/pkg/relays/strfry/success_messages.go @@ -4,7 +4,7 @@ import ( "github.com/nodetec/rwz/pkg/utils/messages" ) -func SuccessMessages(domain string) { +func SuccessMessages(domain string, httpsEnabled bool) { const dataDir = "/var/lib/strfry" const configFile = "/etc/strfry.conf" const serviceFile = "/etc/systemd/system/strfry.service" @@ -12,6 +12,6 @@ func SuccessMessages(domain string) { const relayName = "strfry" const githubLink = "https://github.com/hoytech/strfry" - successMsgParams := messages.SuccessMsgParams{Domain: domain, DataDir: dataDir, ConfigFile: configFile, ServiceFile: serviceFile, Service: service, RelayName: relayName, GitHubLink: githubLink} + successMsgParams := messages.SuccessMsgParams{Domain: domain, HTTPSEnabled: httpsEnabled, DataDir: dataDir, ConfigFile: configFile, ServiceFile: serviceFile, Service: service, RelayName: relayName, GitHubLink: githubLink} messages.SuccessMessages(&successMsgParams) } diff --git a/pkg/relays/wot_relay/nginx_http.go b/pkg/relays/wot_relay/nginx_http.go index b8014e7..f331967 100644 --- a/pkg/relays/wot_relay/nginx_http.go +++ b/pkg/relays/wot_relay/nginx_http.go @@ -2,7 +2,6 @@ package wot_relay import ( "fmt" - "github.com/nodetec/rwz/pkg/utils/directories" "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/systemd" "github.com/pterm/pterm" @@ -16,8 +15,6 @@ func ConfigureNginxHttp(domainName string) { var configContent string - directories.CreateDirectory(fmt.Sprintf("/var/www/%s/.well-known/acme-challenge/", domainName), 0755) - files.RemoveFile(configFilePath) configContent = fmt.Sprintf(`map $http_upgrade $connection_upgrade { @@ -35,12 +32,10 @@ server { listen [::]:80; server_name %s; - location /.well-known/acme-challenge/ { - root /var/www/%s; - allow all; - } - location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying 404. + try_files $uri $uri/ =404; proxy_pass http://websocket_wot_relay; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -50,8 +45,39 @@ server { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } + + # Only return Nginx in server header + server_tokens off; + + #### Security Headers #### + # Test configuration: + # https://securityheaders.com/ + # https://observatory.mozilla.org/ + add_header X-Frame-Options DENY; + + # Avoid MIME type sniffing + add_header X-Content-Type-Options nosniff always; + + add_header Referrer-Policy "no-referrer" always; + + add_header X-XSS-Protection 0 always; + + add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; + + #### Content-Security-Policy (CSP) #### + add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none';" always; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name %s; + + location / { + return 301 http://%s$request_uri; + } } -`, domainName, domainName, domainName) +`, domainName, domainName, domainName, domainName) files.WriteFile(configFilePath, configContent, 0644) diff --git a/pkg/relays/wot_relay/nginx_https.go b/pkg/relays/wot_relay/nginx_https.go index 046d4b2..2dd7d43 100644 --- a/pkg/relays/wot_relay/nginx_https.go +++ b/pkg/relays/wot_relay/nginx_https.go @@ -2,6 +2,7 @@ package wot_relay import ( "fmt" + "github.com/nodetec/rwz/pkg/utils/directories" "github.com/nodetec/rwz/pkg/utils/files" "github.com/nodetec/rwz/pkg/utils/systemd" "github.com/pterm/pterm" @@ -17,6 +18,8 @@ func ConfigureNginxHttps(domainName string) { files.RemoveFile(configFilePath) + directories.CreateDirectory(fmt.Sprintf("/var/www/%s/.well-known/acme-challenge/", domainName), 0755) + configContent = fmt.Sprintf(`map $http_upgrade $connection_upgrade { default upgrade; '' close; @@ -47,6 +50,9 @@ server { proxy_set_header Connection $connection_upgrade; } + # Only return Nginx in server header + server_tokens off; + #### SSL Configuration #### # Test configuration: # https://www.ssllabs.com/ssltest/analyze.html @@ -56,9 +62,6 @@ server { # Verify chain of trust of OCSP response using Root CA and Intermediate certs ssl_trusted_certificate /etc/letsencrypt/live/%s/chain.pem; - # Only return Nginx in server header - server_tokens off; - # TODO # Add support to generate the file in the script #ssl_dhparam /etc/ssl/certs/dhparam.pem; @@ -89,7 +92,7 @@ server { ssl_stapling on; ssl_stapling_verify on; - # Security headers + #### Security Headers #### # Test configuration: # https://securityheaders.com/ # https://observatory.mozilla.org/ @@ -106,7 +109,7 @@ server { add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; - # Content-Security-Policy (CSP) + #### Content-Security-Policy (CSP) #### add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests;" always; } diff --git a/pkg/relays/wot_relay/service.go b/pkg/relays/wot_relay/service.go index 5b0d101..3206181 100644 --- a/pkg/relays/wot_relay/service.go +++ b/pkg/relays/wot_relay/service.go @@ -10,7 +10,7 @@ import ( ) // Function to set up the relay service -func SetupRelayService(domain, pubKey string) { +func SetupRelayService(domain, pubKey string, httpsEnabled bool) { // Template for index.html file const indexTemplate = ` @@ -19,7 +19,7 @@ func SetupRelayService(domain, pubKey string) { WoT Relay - +
@@ -43,7 +43,7 @@ func SetupRelayService(domain, pubKey string) { const envTemplate = `RELAY_NAME="WoT Relay" RELAY_DESCRIPTION="WoT Nostr Relay" RELAY_ICON="https://pfp.nostr.build/56306a93a88d4c657d8a3dfa57b55a4ed65b709eee927b5dafaab4d5330db21f.png" -RELAY_URL="wss://{{.Domain}}" +RELAY_URL="{{.WSProtocol}}://{{.Domain}}" RELAY_PUBKEY="{{.PubKey}}" RELAY_CONTACT="{{.PubKey}}" INDEX_PATH="/etc/wot-relay/templates/index.html" @@ -138,12 +138,12 @@ WantedBy=multi-user.target // Create the index.html file spinner.UpdateText("Creating index.html file...") - indexFileParams := templates.IndexFileParams{Domain: domain, PubKey: pubKey} + indexFileParams := templates.IndexFileParams{Domain: domain, HTTPSEnabled: httpsEnabled, PubKey: pubKey} templates.CreateIndexFile(indexFilePath, indexTemplate, &indexFileParams) // Create the environment file spinner.UpdateText("Creating environment file...") - envFileParams := systemd.EnvFileParams{Domain: domain, PubKey: pubKey} + envFileParams := systemd.EnvFileParams{Domain: domain, HTTPSEnabled: httpsEnabled, PubKey: pubKey} systemd.CreateEnvFile(envFilePath, envTemplate, &envFileParams) // Create the systemd service file diff --git a/pkg/relays/wot_relay/success_messages.go b/pkg/relays/wot_relay/success_messages.go index e2bde5d..e9933fc 100644 --- a/pkg/relays/wot_relay/success_messages.go +++ b/pkg/relays/wot_relay/success_messages.go @@ -4,7 +4,7 @@ import ( "github.com/nodetec/rwz/pkg/utils/messages" ) -func SuccessMessages(domain string) { +func SuccessMessages(domain string, httpsEnabled bool) { const dataDir = "/var/lib/wot-relay" const indexFile = "/etc/wot-relay/templates/index.html" const staticDir = "/etc/wot-relay/templates/static" @@ -14,6 +14,6 @@ func SuccessMessages(domain string) { const relayName = "WoT Relay" const githubLink = "https://github.com/bitvora/wot-relay" - successMsgParams := messages.SuccessMsgParams{Domain: domain, DataDir: dataDir, IndexFile: indexFile, StaticDir: staticDir, EnvFile: envFile, ServiceFile: serviceFile, Service: service, RelayName: relayName, GitHubLink: githubLink} + successMsgParams := messages.SuccessMsgParams{Domain: domain, HTTPSEnabled: httpsEnabled, DataDir: dataDir, IndexFile: indexFile, StaticDir: staticDir, EnvFile: envFile, ServiceFile: serviceFile, Service: service, RelayName: relayName, GitHubLink: githubLink} messages.SuccessMessages(&successMsgParams) } diff --git a/pkg/utils/messages/utils.go b/pkg/utils/messages/utils.go index f8b0de8..37ec3bb 100644 --- a/pkg/utils/messages/utils.go +++ b/pkg/utils/messages/utils.go @@ -6,71 +6,76 @@ import ( ) type SuccessMsgParams struct { - Domain string - DataDir string - IndexFile string - StaticDir string - ConfigFile string - EnvFile string - ServiceFile string - Service string - RelayName string - GitHubLink string + Domain string + HTTPSEnabled bool + DataDir string + IndexFile string + StaticDir string + ConfigFile string + EnvFile string + ServiceFile string + Service string + RelayName string + GitHubLink string } func SuccessMessages(successMsgParams *SuccessMsgParams) { pterm.Println() - pterm.Println(pterm.Magenta("The installation is complete.")) + pterm.Println(pterm.Green("The installation is complete.")) pterm.Println() - pterm.Println(pterm.Magenta("You can access your relay at:")) - pterm.Println(pterm.Magenta("wss://" + successMsgParams.Domain)) + pterm.Println(pterm.Cyan("You can access your relay at:")) + if successMsgParams.HTTPSEnabled { + pterm.Println(pterm.Magenta("wss://" + successMsgParams.Domain)) + } else { + pterm.Println(pterm.Magenta("ws://" + successMsgParams.Domain)) + } pterm.Println() - pterm.Println(pterm.Magenta("Your relay's data directory is located here:")) + pterm.Println(pterm.Cyan("Your relay's data directory is located here:")) pterm.Println(pterm.Magenta(successMsgParams.DataDir)) if successMsgParams.IndexFile != "" { pterm.Println() - pterm.Println(pterm.Magenta("Your relay's index.html file is located here:")) + pterm.Println(pterm.Cyan("Your relay's index.html file is located here:")) pterm.Println(pterm.Magenta(successMsgParams.IndexFile)) } if successMsgParams.StaticDir != "" { pterm.Println() - pterm.Println(pterm.Magenta("Your relay's static directory is located here:")) + pterm.Println(pterm.Cyan("Your relay's static directory is located here:")) pterm.Println(pterm.Magenta(successMsgParams.StaticDir)) } if successMsgParams.ConfigFile != "" { pterm.Println() - pterm.Println(pterm.Magenta("Your relay's config file is located here:")) + pterm.Println(pterm.Cyan("Your relay's config file is located here:")) pterm.Println(pterm.Magenta(successMsgParams.ConfigFile)) } if successMsgParams.EnvFile != "" { pterm.Println() - pterm.Println(pterm.Magenta("Your relay's environment file is located here:")) + pterm.Println(pterm.Cyan("Your relay's environment file is located here:")) pterm.Println(pterm.Magenta(successMsgParams.EnvFile)) } pterm.Println() - pterm.Println(pterm.Magenta("Your relay's service file is located here:")) + pterm.Println(pterm.Cyan("Your relay's service file is located here:")) pterm.Println(pterm.Magenta(successMsgParams.ServiceFile)) pterm.Println() - pterm.Println(pterm.Magenta("To check the status of your relay run:")) + pterm.Println(pterm.Cyan("To check the status of your relay run:")) pterm.Println(pterm.Magenta("systemctl status " + successMsgParams.Service)) pterm.Println() - pterm.Println(pterm.Magenta("To reload the relay service run:")) + pterm.Println(pterm.Cyan("To reload the relay service run:")) pterm.Println(pterm.Magenta("systemctl reload " + successMsgParams.Service)) pterm.Println() - pterm.Println(pterm.Magenta("To restart the relay service run:")) + pterm.Println(pterm.Cyan("To restart the relay service run:")) pterm.Println(pterm.Magenta("systemctl restart " + successMsgParams.Service)) pterm.Println() - pterm.Println(pterm.Magenta(fmt.Sprintf("%s GitHub", successMsgParams.RelayName))) + pterm.Println(pterm.Cyan(fmt.Sprintf("%s GitHub", successMsgParams.RelayName))) pterm.Println(pterm.Magenta(successMsgParams.GitHubLink)) } diff --git a/pkg/utils/systemd/utils.go b/pkg/utils/systemd/utils.go index ab5facc..b097fbe 100644 --- a/pkg/utils/systemd/utils.go +++ b/pkg/utils/systemd/utils.go @@ -8,9 +8,10 @@ import ( ) type EnvFileParams struct { - Domain string - PrivKey string - PubKey string + Domain string + HTTPSEnabled bool + PrivKey string + PubKey string } func CreateEnvFile(envFilePath, envTemplate string, envFileParams *EnvFileParams) { @@ -25,7 +26,14 @@ func CreateEnvFile(envFilePath, envTemplate string, envFileParams *EnvFileParams log.Fatalf("Error parsing environment template: %v", err) } - err = envTmpl.Execute(envFile, struct{ Domain, PrivKey, PubKey string }{Domain: envFileParams.Domain, PrivKey: envFileParams.PrivKey, PubKey: envFileParams.PubKey}) + var WSProtocol string + if envFileParams.HTTPSEnabled { + WSProtocol = "wss" + } else { + WSProtocol = "ws" + } + + err = envTmpl.Execute(envFile, struct{ Domain, WSProtocol, PrivKey, PubKey string }{Domain: envFileParams.Domain, WSProtocol: WSProtocol, PrivKey: envFileParams.PrivKey, PubKey: envFileParams.PubKey}) if err != nil { log.Fatalf("Error executing environment template: %v", err) } diff --git a/pkg/utils/templates/utils.go b/pkg/utils/templates/utils.go index 41f781b..e3d2831 100644 --- a/pkg/utils/templates/utils.go +++ b/pkg/utils/templates/utils.go @@ -7,8 +7,9 @@ import ( ) type IndexFileParams struct { - Domain string - PubKey string + Domain string + HTTPSEnabled bool + PubKey string } func CreateIndexFile(indexFilePath, indexTemplate string, indexFileParams *IndexFileParams) { @@ -23,7 +24,14 @@ func CreateIndexFile(indexFilePath, indexTemplate string, indexFileParams *Index log.Fatalf("Error parsing index.html template: %v", err) } - err = indexTmpl.Execute(indexFile, struct{ Domain, PubKey string }{Domain: indexFileParams.Domain, PubKey: indexFileParams.PubKey}) + var HTTPProtocol string + if indexFileParams.HTTPSEnabled { + HTTPProtocol = "https" + } else { + HTTPProtocol = "http" + } + + err = indexTmpl.Execute(indexFile, struct{ Domain, HTTPProtocol, PubKey string }{Domain: indexFileParams.Domain, HTTPProtocol: HTTPProtocol, PubKey: indexFileParams.PubKey}) if err != nil { log.Fatalf("Error executing index.html template: %v", err) }