Menu="OtherSettings" Type="xmenu" Title="Management Access" Icon="icon-key" Tag="expeditedssl" --- function find_tasks() { global $site; $tasks = []; foreach ($site as $page) { if (empty($page['Menu'])) continue; $menu = strtok($page['Menu'], ' '); switch ($menu[0]) { case '$': $menu = get_ini_key($menu,strtok(' ')); break; case '/': $menu = get_file_key($menu,strtok(' ')); break; } while ($menu !== false) { if (substr($menu,0,5) == 'Tasks') { if (empty($page['Cond'])) $tasks[] = $page['name']; break; } $menu = strtok(' '); } } sort($tasks); return $tasks; } function acceptableCert($certFile, $hostname, $expectedURL) { if (!file_exists($certFile)) return false; $certURLs=null; // get Subject URL and all SAN URLs from cert exec("openssl x509 -noout -subject -nameopt multiline -in $certFile | sed -n 's/ *commonName *= //p' ; openssl x509 -noout -ext subjectAltName -in $certFile | grep -Eo \"DNS:[a-zA-Z 0-9.*-]*\" | sed \"s/DNS://g\"", $certURLs); foreach($certURLs as $certURL) { // adjust for wildcard certs $certURL = str_replace('*', $hostname, $certURL); // case-insensitive compare if (strcasecmp($certURL, $expectedURL) == 0) return true; } return false; } $tasks = find_tasks(); $nginx = (array)@parse_ini_file('/var/local/emhttp/nginx.ini'); $addr = _var($nginx,'NGINX_LANIP') ?: _var($nginx,'NGINX_LANIP6'); $keyfile = empty(_var($var,'regFILE')) ? false : @file_get_contents(_var($var,'regFILE')); $cert2Issuer = ''; $isWildcardCert = false; if ($keyfile !== false) $keyfile = base64_encode($keyfile); // self-signed or user-provided cert $cert1File = "/boot/config/ssl/certs/"._var($var,'NAME','tower')."_unraid_bundle.pem"; $cert1Present = file_exists("$cert1File"); $cert1SelfSigned = $cert1URLvalid = false; $cert1Issuer = ' '; if ($cert1Present) { $cert1URL = _var($var,'NAME','tower').(_var($var,'LOCAL_TLD') ? '.'._var($var,'LOCAL_TLD') : ''); // if user replaced cert without reloading nginx, the cert on the flash could be invalid $cert1URLvalid = acceptableCert($cert1File, _var($var,'NAME','tower'), $cert1URL); $cert1Subject = exec("/usr/bin/openssl x509 -in $cert1File -noout -text | sed -n 's/^.*Subject: //p'"); $cert1Issuer = exec("/usr/bin/openssl x509 -in $cert1File -noout -text | sed -n -e 's/^.*Issuer: //p'"); $cert1Expires = exec("/usr/bin/openssl x509 -in $cert1File -noout -text | sed -n -e 's/^.*Not After : //p'"); $cert1SelfSigned = ($cert1Subject == $cert1Issuer); } // myunraid.net LE cert. could potentially be user provided as well $cert2File = "/boot/config/ssl/certs/certificate_bundle.pem"; $cert2Present = file_exists("$cert2File"); if ($cert2Present) { $cert2Subject = exec("/usr/bin/openssl x509 -in $cert2File -noout -subject -nameopt multiline 2>/dev/null|sed -n 's/ *commonName *= //p'"); $cert2Issuer = exec("/usr/bin/openssl x509 -in $cert2File -noout -text | sed -n -e 's/^.*Issuer: //p'"); $cert2Expires = exec("/usr/bin/openssl x509 -in $cert2File -noout -text | sed -n -e 's/^.*Not After : //p'"); $isWildcardCert = preg_match('/.*\.myunraid\.net$/', $cert2Subject); $subject2URL = $cert2Subject; $dnsValid = false; $dnsRebindingProtection = false; if ($isWildcardCert) { exec("openssl x509 -checkend 2592000 -noout -in $cert2File 2>/dev/null", $arrout, $retval_expired); if (!$addr) { // if eth0 doesn't have an IP address, then show noip.hash.myunraid.net as a placeholder url $subject2URL = str_replace("*", 'noip', $subject2URL); $dnsValid = false; $dnsRebindingProtection = false; } elseif (strpos($addr, ":") === false) { // eth0 is IPv4 $subject2URL = str_replace("*", str_replace(".", "-", $addr), $subject2URL); $rebindtest_ip = exec("host -4 -t A rebindtest4.myunraid.net 2>/dev/null|awk '{print \$4}'"); $dnsRebindingProtection = ($rebindtest_ip != "192.168.42.42"); if (!$dnsRebindingProtection) { $cert_ip = exec("host -4 -t A $subject2URL 2>/dev/null|awk '{print \$4}'"); $dnsValid = $cert_ip==$addr; } } else { // eth0 is IPv6 $subject2URL = str_replace("*", str_replace(":", "-", $addr), $subject2URL); $rebindtest_ip = exec("host -6 -t AAAA rebindtest6.myunraid.net 2>/dev/null|awk '{print \$4}'"); // more: restore this after TTL expires // $dnsRebindingProtection = ($rebindtest_ip != "fd42::42"); $dnsRebindingProtection = ($rebindtest_ip != "fd42::42") && ($rebindtest_ip != "fd42::"); if (!$dnsRebindingProtection) { $cert_ip = exec("host -6 -t AAAA $subject2URL 2>/dev/null|awk '{print \$4}'"); $dnsValid = $cert_ip==$addr; } } } } // Tailscale LE cert $cert3File = "/boot/config/ssl/certs/ts_bundle.pem"; $cert3Present = file_exists("$cert3File"); if ($cert3Present) { $cert3Subject = exec("/usr/bin/openssl x509 -in $cert3File -noout -subject -nameopt multiline 2>/dev/null|sed -n 's/ *commonName *= //p'"); $cert3Issuer = exec("/usr/bin/openssl x509 -in $cert3File -noout -text | sed -n -e 's/^.*Issuer: //p'"); $cert3Expires = exec("/usr/bin/openssl x509 -in $cert3File -noout -text | sed -n -e 's/^.*Not After : //p'"); } // Note: this disables FQDN6 urls since they are not supported by myunraid.net DNS currently if (!empty($nginx['NGINX_LANFQDN6'])) unset($nginx['NGINX_LANFQDN6']); $http_port = _var($var,'PORT','80') != '80' ? ":{$var['PORT']}" : ''; $https_port = _var($var,'PORTSSL','443') != '443' ? ":{$var['PORTSSL']}" : ''; $http_ip_url = 'http://'._var($nginx,'NGINX_LANIP').$http_port.'/'; $https_ip_url = 'https://'._var($nginx,'NGINX_LANIP').$https_port.'/'; // bare IPv6 addresses need to be surrounded in brackets $http_ip6_url = 'http://'.'['._var($nginx,'NGINX_LANIP6').']'.$http_port.'/'; $https_ip6_url = 'https://'.'['._var($nginx,'NGINX_LANIP6').']'.$https_port.'/'; $http_mdns_url = 'http://'._var($nginx,'NGINX_LANMDNS').$http_port.'/'; $https_mdns_url = 'https://'._var($nginx,'NGINX_LANMDNS').$https_port.'/'; $https_fqdn_url = 'https://'._var($nginx,'NGINX_LANFQDN').$https_port.'/'; $https_fqdn6_url = 'https://'._var($nginx,'NGINX_LANFQDN6').$https_port.'/'; $urls = []; // push an array of five values into the $urls array: // 0 - type of url ['LAN','WAN','WG','TAILSCALE'] // 1 - the url // 3 - the url it redirects to, or null // 4 - the certificate file used, or null // 5 - self-signed certificate, or false // define LAN access urls and redirects that change based on USE_SSL setting switch(_var($var,'USE_SSL','no')) { case 'no': if (!empty($nginx['NGINX_LANIP'])) $urls[] = ['LAN', $http_ip_url, null, null, false]; if (!empty($nginx['NGINX_LANIP6'])) $urls[] = ['LAN', $http_ip6_url, null, null, false]; if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = ['LAN', $http_mdns_url, null, null, false]; break; case 'yes': if (!empty($nginx['NGINX_LANIP'])) $urls[] = ['LAN', $http_ip_url, $https_ip_url, null, false]; if (!empty($nginx['NGINX_LANIP'])) $urls[] = ['LAN', $https_ip_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned]; if (!empty($nginx['NGINX_LANIP6'])) $urls[] = ['LAN', $http_ip6_url, $https_ip6_url, null, false]; if (!empty($nginx['NGINX_LANIP6'])) $urls[] = ['LAN', $https_ip6_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned]; if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = ['LAN', $http_mdns_url, $https_mdns_url, null, false]; if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = ['LAN', $https_mdns_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned]; break; case 'auto': // aka strict if (!empty($nginx['NGINX_LANIP']) && !empty($nginx['NGINX_LANFQDN'])) $urls[] = ['LAN', $http_ip_url, $https_fqdn_url, null, false]; if (!empty($nginx['NGINX_LANIP6']) && !empty($nginx['NGINX_LANFQDN6'])) $urls[] = ['LAN', $http_ip6_url, $https_fqdn6_url, null, false]; if (!empty($nginx['NGINX_LANMDNS']) && !empty($nginx['NGINX_LANFQDN'])) $urls[] = ['LAN', $http_mdns_url, $https_fqdn_url, null, false]; break; } // define FQDN urls for each interface // when multiple FQDN urls are available for a given interface, make sure they are sorted asort($nginx); foreach ($nginx as $key => $host) { if (!$host) continue; // Only process keys that include 'FQDN' if (strpos($key, 'FQDN') === false) continue; // Extract the interface from the key, e.g., 'NGINX_LANFQDN' -> 'LAN', 'NGINX_WANFQDN' -> 'WAN', NGINX_WG0FQDN -> WG, NGINX_TAILSCALE1FQDN -> TAILSCALE // Note: this specifically excludes FQDN6 urls since they are not supported by myunraid.net DNS currently if (preg_match('/^NGINX_([A-Z]+)(\d*)FQDN$/', $key, $matches)) { $interface = $matches[1]; // Interface type (LAN, WAN, WG, TAILSCALE, etc.) // ignore the WAN interface because we don't have access to the WANPORT here if ($interface == "WAN") continue; $pem = null; if (str_ends_with($host, '.myunraid.net')) $pem = 'certificate_bundle.pem'; elseif (str_ends_with($host, '.ts.net')) $pem = 'ts_bundle.pem'; $url = 'https://'.$host.$https_port."/"; $urls[] = [$interface, $url, null, $pem, false]; } } // determine whether there are urls for a given interface function has_urls($interface) { global $urls; foreach($urls as $url) { if ($url[0] == $interface) return true; } return false; } // show all urls for a given interface function show_urls($interface) { global $urls; // 0 - type of url ['LAN','WAN','WG','TAILSCALE'] // 1 - the url // 3 - the url it redirects to, or null // 4 - the certificate file used, or null // 5 - self-signed certificate, or false $output = ""; $linestart = "