Compare commits

..

4 Commits

Author SHA1 Message Date
Owen
90afe5a7ac Log errors 2026-03-11 15:42:40 -07:00
Owen
b24de85157 Handle gerbil rejecting 0
Closes #2605
2026-03-11 15:06:26 -07:00
Owen
eda43dffe1 Fix not pulling wildcard cert updates 2026-03-11 15:06:26 -07:00
Owen
82c9a1eb70 Add demo link 2026-03-11 15:06:26 -07:00
7 changed files with 109 additions and 24 deletions

View File

@@ -2343,8 +2343,8 @@
"logRetentionEndOfFollowingYear": "End of following year", "logRetentionEndOfFollowingYear": "End of following year",
"actionLogsDescription": "View a history of actions performed in this organization", "actionLogsDescription": "View a history of actions performed in this organization",
"accessLogsDescription": "View access auth requests for resources in this organization", "accessLogsDescription": "View access auth requests for resources in this organization",
"licenseRequiredToUse": "An <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> license or <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> is required to use this feature.", "licenseRequiredToUse": "An <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> license or <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> is required to use this feature. <bookADemoLink>Book a demo or POC trial</bookADemoLink>.",
"ossEnterpriseEditionRequired": "The <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is required to use this feature. This feature is also available in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.", "ossEnterpriseEditionRequired": "The <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is required to use this feature. This feature is also available in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Book a demo or POC trial</bookADemoLink>.",
"certResolver": "Certificate Resolver", "certResolver": "Certificate Resolver",
"certResolverDescription": "Select the certificate resolver to use for this resource.", "certResolverDescription": "Select the certificate resolver to use for this resource.",
"selectCertResolver": "Select Certificate Resolver", "selectCertResolver": "Select Certificate Resolver",

View File

@@ -571,7 +571,7 @@ export async function updateClientSiteDestinations(
destinations: [ destinations: [
{ {
destinationIP: site.sites.subnet.split("/")[0], destinationIP: site.sites.subnet.split("/")[0],
destinationPort: site.sites.listenPort || 0 destinationPort: site.sites.listenPort || 1 // this satisfies gerbil for now but should be reevaluated
} }
] ]
}; };
@@ -579,7 +579,7 @@ export async function updateClientSiteDestinations(
// add to the existing destinations // add to the existing destinations
destinations.destinations.push({ destinations.destinations.push({
destinationIP: site.sites.subnet.split("/")[0], destinationIP: site.sites.subnet.split("/")[0],
destinationPort: site.sites.listenPort || 0 destinationPort: site.sites.listenPort || 1 // this satisfies gerbil for now but should be reevaluated
}); });
} }

View File

@@ -218,10 +218,11 @@ export class TraefikConfigManager {
return true; return true;
} }
// Fetch if it's been more than 24 hours (for renewals)
const dayInMs = 24 * 60 * 60 * 1000; const dayInMs = 24 * 60 * 60 * 1000;
const timeSinceLastFetch = const timeSinceLastFetch =
Date.now() - this.lastCertificateFetch.getTime(); Date.now() - this.lastCertificateFetch.getTime();
// Fetch if it's been more than 24 hours (daily routine check)
if (timeSinceLastFetch > dayInMs) { if (timeSinceLastFetch > dayInMs) {
logger.info("Fetching certificates due to 24-hour renewal check"); logger.info("Fetching certificates due to 24-hour renewal check");
return true; return true;
@@ -265,7 +266,7 @@ export class TraefikConfigManager {
return true; return true;
} }
// Check if any local certificates are missing or appear to be outdated // Check if any local certificates are missing (needs immediate fetch)
for (const domain of domainsNeedingCerts) { for (const domain of domainsNeedingCerts) {
const localState = this.lastLocalCertificateState.get(domain); const localState = this.lastLocalCertificateState.get(domain);
if (!localState || !localState.exists) { if (!localState || !localState.exists) {
@@ -274,17 +275,55 @@ export class TraefikConfigManager {
); );
return true; return true;
} }
}
// Check if certificate is expiring soon (within 30 days) // For expiry checks, throttle to every 6 hours to avoid querying the
if (localState.expiresAt) { // API/DB on every monitor loop. The certificate-service renews certs
const nowInSeconds = Math.floor(Date.now() / 1000); // 45 days before expiry, so checking every 6 hours is plenty frequent
const secondsUntilExpiry = localState.expiresAt - nowInSeconds; // to pick up renewed certs promptly.
const daysUntilExpiry = secondsUntilExpiry / (60 * 60 * 24); const renewalCheckIntervalMs = 6 * 60 * 60 * 1000; // 6 hours
if (daysUntilExpiry < 30) { if (timeSinceLastFetch > renewalCheckIntervalMs) {
logger.info( // Check non-wildcard certs for expiry (within 45 days to match
`Fetching certificates due to upcoming expiry for ${domain} (${Math.round(daysUntilExpiry)} days remaining)` // the server-side renewal window in certificate-service)
); for (const domain of domainsNeedingCerts) {
return true; const localState =
this.lastLocalCertificateState.get(domain);
if (localState?.expiresAt) {
const nowInSeconds = Math.floor(Date.now() / 1000);
const secondsUntilExpiry =
localState.expiresAt - nowInSeconds;
const daysUntilExpiry =
secondsUntilExpiry / (60 * 60 * 24);
if (daysUntilExpiry < 45) {
logger.info(
`Fetching certificates due to upcoming expiry for ${domain} (${Math.round(daysUntilExpiry)} days remaining)`
);
return true;
}
}
}
// Also check wildcard certificates for expiry. These are not
// included in domainsNeedingCerts since their subdomains are
// filtered out, so we must check them separately.
for (const [certDomain, state] of this
.lastLocalCertificateState) {
if (
state.exists &&
state.wildcard &&
state.expiresAt
) {
const nowInSeconds = Math.floor(Date.now() / 1000);
const secondsUntilExpiry =
state.expiresAt - nowInSeconds;
const daysUntilExpiry =
secondsUntilExpiry / (60 * 60 * 24);
if (daysUntilExpiry < 45) {
logger.info(
`Fetching certificates due to upcoming expiry for wildcard cert ${certDomain} (${Math.round(daysUntilExpiry)} days remaining)`
);
return true;
}
} }
} }
} }
@@ -361,6 +400,32 @@ export class TraefikConfigManager {
} }
} }
// Also include wildcard cert base domains that are
// expiring or expired so they get re-fetched even though
// their subdomains were filtered out above.
for (const [certDomain, state] of this
.lastLocalCertificateState) {
if (
state.exists &&
state.wildcard &&
state.expiresAt
) {
const nowInSeconds = Math.floor(
Date.now() / 1000
);
const secondsUntilExpiry =
state.expiresAt - nowInSeconds;
const daysUntilExpiry =
secondsUntilExpiry / (60 * 60 * 24);
if (daysUntilExpiry < 45) {
domainsToFetch.add(certDomain);
logger.info(
`Including expiring wildcard cert domain ${certDomain} in fetch (${Math.round(daysUntilExpiry)} days remaining)`
);
}
}
}
if (domainsToFetch.size > 0) { if (domainsToFetch.size > 0) {
// Get valid certificates for domains not covered by wildcards // Get valid certificates for domains not covered by wildcards
validCertificates = validCertificates =

View File

@@ -125,7 +125,7 @@ export async function generateRelayMappings(exitNode: ExitNode) {
// Add site as a destination for this client // Add site as a destination for this client
const destination: PeerDestination = { const destination: PeerDestination = {
destinationIP: site.subnet.split("/")[0], destinationIP: site.subnet.split("/")[0],
destinationPort: site.listenPort destinationPort: site.listenPort || 1 // this satisfies gerbil for now but should be reevaluated
}; };
// Check if this destination is already in the array to avoid duplicates // Check if this destination is already in the array to avoid duplicates
@@ -165,7 +165,7 @@ export async function generateRelayMappings(exitNode: ExitNode) {
const destination: PeerDestination = { const destination: PeerDestination = {
destinationIP: peer.subnet.split("/")[0], destinationIP: peer.subnet.split("/")[0],
destinationPort: peer.listenPort destinationPort: peer.listenPort || 1 // this satisfies gerbil for now but should be reevaluated
}; };
// Check for duplicates // Check for duplicates

View File

@@ -112,7 +112,7 @@ export async function updateHolePunch(
destinations: destinations destinations: destinations
}); });
} catch (error) { } catch (error) {
// logger.error(error); // FIX THIS logger.error(error);
return next( return next(
createHttpError( createHttpError(
HttpCode.INTERNAL_SERVER_ERROR, HttpCode.INTERNAL_SERVER_ERROR,
@@ -262,7 +262,7 @@ export async function updateAndGenerateEndpointDestinations(
if (site.subnet && site.listenPort) { if (site.subnet && site.listenPort) {
destinations.push({ destinations.push({
destinationIP: site.subnet.split("/")[0], destinationIP: site.subnet.split("/")[0],
destinationPort: site.listenPort destinationPort: site.listenPort || 1 // this satisfies gerbil for now but should be reevaluated
}); });
} }
} }

View File

@@ -104,11 +104,11 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
const payload = { const payload = {
oldDestination: { oldDestination: {
destinationIP: existingSite.subnet?.split("/")[0], destinationIP: existingSite.subnet?.split("/")[0],
destinationPort: existingSite.listenPort destinationPort: existingSite.listenPort || 1 // this satisfies gerbil for now but should be reevaluated
}, },
newDestination: { newDestination: {
destinationIP: site.subnet?.split("/")[0], destinationIP: site.subnet?.split("/")[0],
destinationPort: site.listenPort destinationPort: site.listenPort || 1 // this satisfies gerbil for now but should be reevaluated
} }
}; };

View File

@@ -51,6 +51,7 @@ const docsLinkClassName =
const PANGOLIN_CLOUD_SIGNUP_URL = "https://app.pangolin.net/auth/signup/"; const PANGOLIN_CLOUD_SIGNUP_URL = "https://app.pangolin.net/auth/signup/";
const ENTERPRISE_DOCS_URL = const ENTERPRISE_DOCS_URL =
"https://docs.pangolin.net/self-host/enterprise-edition"; "https://docs.pangolin.net/self-host/enterprise-edition";
const BOOK_A_DEMO_URL = "https://click.fossorial.io/ep922";
function getTierLinkRenderer(billingHref: string) { function getTierLinkRenderer(billingHref: string) {
return function tierLinkRenderer(chunks: React.ReactNode) { return function tierLinkRenderer(chunks: React.ReactNode) {
@@ -78,6 +79,22 @@ function getPangolinCloudLinkRenderer() {
}; };
} }
function getBookADemoLinkRenderer() {
return function bookADemoLinkRenderer(chunks: React.ReactNode) {
return (
<Link
href={BOOK_A_DEMO_URL}
target="_blank"
rel="noopener noreferrer"
className={docsLinkClassName}
>
{chunks}
<ExternalLink className="size-3.5 shrink-0" />
</Link>
);
};
}
function getDocsLinkRenderer(href: string) { function getDocsLinkRenderer(href: string) {
return function docsLinkRenderer(chunks: React.ReactNode) { return function docsLinkRenderer(chunks: React.ReactNode) {
return ( return (
@@ -116,6 +133,7 @@ export function PaidFeaturesAlert({ tiers }: Props) {
const tierLinkRenderer = getTierLinkRenderer(billingHref); const tierLinkRenderer = getTierLinkRenderer(billingHref);
const pangolinCloudLinkRenderer = getPangolinCloudLinkRenderer(); const pangolinCloudLinkRenderer = getPangolinCloudLinkRenderer();
const enterpriseDocsLinkRenderer = getDocsLinkRenderer(ENTERPRISE_DOCS_URL); const enterpriseDocsLinkRenderer = getDocsLinkRenderer(ENTERPRISE_DOCS_URL);
const bookADemoLinkRenderer = getBookADemoLinkRenderer();
if (env.flags.disableEnterpriseFeatures) { if (env.flags.disableEnterpriseFeatures) {
return null; return null;
@@ -157,7 +175,8 @@ export function PaidFeaturesAlert({ tiers }: Props) {
{t.rich("licenseRequiredToUse", { {t.rich("licenseRequiredToUse", {
enterpriseLicenseLink: enterpriseLicenseLink:
enterpriseDocsLinkRenderer, enterpriseDocsLinkRenderer,
pangolinCloudLink: pangolinCloudLinkRenderer pangolinCloudLink: pangolinCloudLinkRenderer,
bookADemoLink: bookADemoLinkRenderer
})} })}
</span> </span>
</div> </div>
@@ -174,7 +193,8 @@ export function PaidFeaturesAlert({ tiers }: Props) {
{t.rich("ossEnterpriseEditionRequired", { {t.rich("ossEnterpriseEditionRequired", {
enterpriseEditionLink: enterpriseEditionLink:
enterpriseDocsLinkRenderer, enterpriseDocsLinkRenderer,
pangolinCloudLink: pangolinCloudLinkRenderer pangolinCloudLink: pangolinCloudLinkRenderer,
bookADemoLink: bookADemoLinkRenderer
})} })}
</span> </span>
</div> </div>