diff --git a/docker/nginx/conf.d/client.conf b/docker/nginx/conf.d/client.conf index e77a54a8..3c20dd13 100644 --- a/docker/nginx/conf.d/client.conf +++ b/docker/nginx/conf.d/client.conf @@ -330,12 +330,13 @@ server { proxy_request_buffering off; # stream uploaded files through the proxy as it comes in proxy_set_header Expect $http_expect; + # rewrite proxy request to use correct host uri from env variable (required to return correct location header) + set_by_lua $SKYNET_SERVER_API 'return os.getenv("SKYNET_SERVER_API")'; + proxy_redirect https://siad $SKYNET_SERVER_API; + # proxy /skynet/tus requests to siad endpoint with all arguments proxy_pass http://siad; - # rewrite tus headers to use correct uri - proxy_redirect https://siad/ https://$domain.$tld/; - # extract skylink from base64 encoded upload metadata and assign to a proper header header_filter_by_lua_block { if ngx.header["Upload-Metadata"] then diff --git a/docker/nginx/conf.d/include/cors b/docker/nginx/conf.d/include/cors index 1f7e4411..5245c33f 100644 --- a/docker/nginx/conf.d/include/cors +++ b/docker/nginx/conf.d/include/cors @@ -13,4 +13,4 @@ more_set_headers 'Access-Control-Allow-Origin: $http_origin'; more_set_headers 'Access-Control-Allow-Credentials: true'; more_set_headers 'Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE'; more_set_headers 'Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,X-HTTP-Method-Override,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,location'; -more_set_headers 'Access-Control-Expose-Headers: Content-Length,Content-Range,Skynet-File-Metadata,Skynet-Skylink,Skynet-Portal-Api,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,location'; +more_set_headers 'Access-Control-Expose-Headers: Content-Length,Content-Range,Skynet-File-Metadata,Skynet-Skylink,Skynet-Portal-Api,Skynet-Server-Api,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,location'; diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf index fe14b0a4..41c15010 100644 --- a/docker/nginx/nginx.conf +++ b/docker/nginx/nginx.conf @@ -25,8 +25,10 @@ worker_processes 1; #pid logs/nginx.pid; -env SKYNET_PORTAL_API; # declare env variable to use it in config -env ACCOUNTS_ENABLED; # declare env variable to use it in config +# declare env variables to use it in config +env SKYNET_PORTAL_API; +env SKYNET_SERVER_API; +env ACCOUNTS_ENABLED; events { worker_connections 1024; @@ -64,8 +66,11 @@ http { #gzip on; - # include skynet-portal-api header on every request - header_filter_by_lua 'ngx.header["Skynet-Portal-Api"] = os.getenv("SKYNET_PORTAL_API")'; + # include skynet-portal-api and skynet-server-api header on every request + header_filter_by_lua_block { + ngx.header["Skynet-Portal-Api"] = os.getenv("SKYNET_PORTAL_API") + ngx.header["Skynet-Server-Api"] = os.getenv("SKYNET_SERVER_API") + } include /etc/nginx/conf.d/*.conf; include /etc/nginx/conf.extra.d/*.conf; diff --git a/packages/health-check/src/checks/critical.js b/packages/health-check/src/checks/critical.js index 69cfa1f9..c8abdbff 100644 --- a/packages/health-check/src/checks/critical.js +++ b/packages/health-check/src/checks/critical.js @@ -33,35 +33,60 @@ async function uploadCheck(done) { // websiteCheck checks whether the main website is working async function websiteCheck(done) { - return genericAccessCheck("website", process.env.SKYNET_PORTAL_API, done); + return done(await genericAccessCheck("website", process.env.SKYNET_PORTAL_API)); } // downloadCheck returns the result of downloading the hard coded link async function downloadCheck(done) { const url = await skynetClient.getSkylinkUrl(exampleSkylink); - return genericAccessCheck("skylink", url, done); + return done(await genericAccessCheck("skylink", url)); } // skylinkSubdomainCheck returns the result of downloading the hard coded link via subdomain async function skylinkSubdomainCheck(done) { const url = await skynetClient.getSkylinkUrl(exampleSkylink, { subdomain: true }); - return genericAccessCheck("skylink_via_subdomain", url, done); + return done(await genericAccessCheck("skylink_via_subdomain", url)); } // handshakeSubdomainCheck returns the result of downloading the skylink via handshake domain async function handshakeSubdomainCheck(done) { const url = await skynetClient.getHnsUrl("note-to-self", { subdomain: true }); - return genericAccessCheck("hns_via_subdomain", url, done); + return done(await genericAccessCheck("hns_via_subdomain", url)); } // accountWebsiteCheck returns the result of accessing account dashboard website async function accountWebsiteCheck(done) { const url = `${process.env.SKYNET_DASHBOARD_URL}/auth/login`; - return genericAccessCheck("account_website", url, done); + return done(await genericAccessCheck("account_website", url)); +} + +// directServerApiAccessCheck returns the basic server api check on direct server address +async function directServerApiAccessCheck(done) { + if (!process.env.SKYNET_SERVER_API) { + return done({ up: false, info: { message: "SKYNET_SERVER_API env variable not configured" } }); + } + + const [domainAccessCheck, directAccessCheck] = await Promise.all([ + genericAccessCheck("portal_api_access", process.env.SKYNET_PORTAL_API), + genericAccessCheck("direct_server_api_access", process.env.SKYNET_SERVER_API), + ]); + + if (domainAccessCheck.ip !== directAccessCheck.ip) { + directAccessCheck.up = false; + directAccessCheck.info = { + message: "Access ip mismatch between domain and direct access", + response: { + domain: { name: process.env.SKYNET_PORTAL_API, ip: domainAccessCheck.ip }, + domain: { name: process.env.SKYNET_SERVER_API, ip: directAccessCheck.ip }, + }, + }; + } + + return done(directAccessCheck); } // accountHealthCheck returns the result of accounts service health checks @@ -86,7 +111,7 @@ async function accountHealthCheck(done) { done({ name: "accounts", time: calculateElapsedTime(time), ...data }); } -async function genericAccessCheck(name, url, done) { +async function genericAccessCheck(name, url) { const time = process.hrtime(); const data = { up: false, url }; @@ -103,10 +128,17 @@ async function genericAccessCheck(name, url, done) { data.ip = error?.response?.ip ?? null; } - done({ name, time: calculateElapsedTime(time), ...data }); + return { name, time: calculateElapsedTime(time), ...data }; } -const checks = [uploadCheck, websiteCheck, downloadCheck, skylinkSubdomainCheck, handshakeSubdomainCheck]; +const checks = [ + uploadCheck, + websiteCheck, + downloadCheck, + skylinkSubdomainCheck, + handshakeSubdomainCheck, + directServerApiAccessCheck, +]; if (process.env.ACCOUNTS_ENABLED === "1") { checks.push(accountHealthCheck, accountWebsiteCheck); diff --git a/setup-scripts/bot_utils.py b/setup-scripts/bot_utils.py index 9e918955..841d9ccd 100644 --- a/setup-scripts/bot_utils.py +++ b/setup-scripts/bot_utils.py @@ -53,7 +53,7 @@ def setup(): bot_token = os.environ["DISCORD_BOT_TOKEN"] global portal_name - portal_name = os.getenv("PORTAL_NAME") + portal_name = os.getenv("SKYNET_SERVER_API") # Get a port or use default global port diff --git a/setup-scripts/setup-docker-services.sh b/setup-scripts/setup-docker-services.sh index c2270b3c..7ae43e85 100755 --- a/setup-scripts/setup-docker-services.sh +++ b/setup-scripts/setup-docker-services.sh @@ -22,15 +22,16 @@ docker-compose --version # sanity check # Create dummy .env file for docker-compose usage with variables # * SSL_CERTIFICATE_STRING - certificate string that will be used to generate ssl certificates, read more in docker/caddy/Caddyfile -# * SKYNET_PORTAL_API - absolute url to the portal api ie. https://example.com -# * SKYNET_DASHBOARD_URL - (optional) absolute url to the portal dashboard ie. https://account.example.com +# * SKYNET_PORTAL_API - absolute url to the portal api ie. https://siasky.net (general portal address) +# * SKYNET_SERVER_API - absolute url to the server api ie. https://eu-ger-1.siasky.net (direct server address, if this is single server portal use the same address as SKYNET_PORTAL_API) +# * SKYNET_DASHBOARD_URL - (optional) absolute url to the portal dashboard ie. https://account.siasky.net # * EMAIL_ADDRESS - this is the administrator contact email you need to supply for communication regarding SSL certification # * HSD_API_KEY - this is auto generated secure key for your handshake service integration # * CLOUDFLARE_AUTH_TOKEN - (optional) if using cloudflare as dns loadbalancer (need to change it in Caddyfile too) # * AWS_ACCESS_KEY_ID - (optional) if using route53 as a dns loadbalancer # * AWS_SECRET_ACCESS_KEY - (optional) if using route53 as a dns loadbalancer # * API_PORT - (optional) the port on which siad is listening, defaults to 9980 -# * PORTAL_NAME - the name of the portal, required by the discord bot +# * PORTAL_NAME - a string representing name of your portal e.g. `siasky.xyz` or `my skynet portal` (internal use only) # * DISCORD_BOT_TOKEN - (optional) only required if you're using the discord notifications integration # * SKYNET_DB_USER - (optional) if using `accounts` this is the MongoDB username # * SKYNET_DB_PASS - (optional) if using `accounts` this is the MongoDB password @@ -43,7 +44,7 @@ docker-compose --version # sanity check # * CR_CLUSTER_NODES - (optional) if using `accounts` the list of servers (with ports) which make up your CockroachDB cluster, e.g. `helsinki.siasky.net:26257,germany.siasky.net:26257,us-east.siasky.net:26257` if ! [ -f /home/user/skynet-webportal/.env ]; then HSD_API_KEY=$(openssl rand -base64 32) # generate safe random key for handshake - printf "SSL_CERTIFICATE_STRING=example.com, *.example.com, *.hns.example.com\nSKYNET_PORTAL_API=https://example.com\nSKYNET_DASHBOARD_URL=https://account.example.com\nEMAIL_ADDRESS=email@example.com\nSIA_WALLET_PASSWORD=\nHSD_API_KEY=${HSD_API_KEY}\nCLOUDFLARE_AUTH_TOKEN=\nAWS_ACCESS_KEY_ID=\nAWS_SECRET_ACCESS_KEY=\nPORTAL_NAME=\nDISCORD_BOT_TOKEN=\n" > /home/user/skynet-webportal/.env + printf "SSL_CERTIFICATE_STRING=siasky.net, *.siasky.net, *.hns.siasky.net\nSKYNET_PORTAL_API=https://siasky.net\nSKYNET_SERVER_API=https://eu-dc-1.siasky.net\nSKYNET_DASHBOARD_URL=https://account.example.com\nEMAIL_ADDRESS=email@example.com\nSIA_WALLET_PASSWORD=\nHSD_API_KEY=${HSD_API_KEY}\nCLOUDFLARE_AUTH_TOKEN=\nAWS_ACCESS_KEY_ID=\nAWS_SECRET_ACCESS_KEY=\nPORTAL_NAME=\nDISCORD_BOT_TOKEN=\n" > /home/user/skynet-webportal/.env fi # Start docker container with nginx and client