mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-04 09:46:40 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b01fcc70fe | ||
|
|
35fed74e49 | ||
|
|
64bae5b142 | ||
|
|
6cf1b9b010 | ||
|
|
dae169540b | ||
|
|
19f9dda490 | ||
|
|
cdf79edb00 | ||
|
|
280cbb6e22 | ||
|
|
c20babcb53 | ||
|
|
768eebe2cd | ||
|
|
44e3eedffa | ||
|
|
bb189874cb | ||
|
|
34dadd0e16 | ||
|
|
87b5cd9988 | ||
|
|
6a537a23e8 | ||
|
|
e63a6e9b77 | ||
|
|
7ce589c4f2 | ||
|
|
f36cf06e26 | ||
|
|
375211f184 | ||
|
|
81c1a1da9c | ||
|
|
52f26396ac |
@@ -1102,6 +1102,12 @@
|
|||||||
"actionGetUser": "Get User",
|
"actionGetUser": "Get User",
|
||||||
"actionGetOrgUser": "Get Organization User",
|
"actionGetOrgUser": "Get Organization User",
|
||||||
"actionListOrgDomains": "List Organization Domains",
|
"actionListOrgDomains": "List Organization Domains",
|
||||||
|
"actionGetDomain": "Get Domain",
|
||||||
|
"actionCreateOrgDomain": "Create Domain",
|
||||||
|
"actionUpdateOrgDomain": "Update Domain",
|
||||||
|
"actionDeleteOrgDomain": "Delete Domain",
|
||||||
|
"actionGetDNSRecords": "Get DNS Records",
|
||||||
|
"actionRestartOrgDomain": "Restart Domain",
|
||||||
"actionCreateSite": "Create Site",
|
"actionCreateSite": "Create Site",
|
||||||
"actionDeleteSite": "Delete Site",
|
"actionDeleteSite": "Delete Site",
|
||||||
"actionGetSite": "Get Site",
|
"actionGetSite": "Get Site",
|
||||||
|
|||||||
123
server/lib/ip.ts
123
server/lib/ip.ts
@@ -571,6 +571,129 @@ export function generateSubnetProxyTargets(
|
|||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SubnetProxyTargetV2 = {
|
||||||
|
sourcePrefixes: string[]; // must be cidrs
|
||||||
|
destPrefix: string; // must be a cidr
|
||||||
|
disableIcmp?: boolean;
|
||||||
|
rewriteTo?: string; // must be a cidr
|
||||||
|
portRange?: {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
protocol: "tcp" | "udp";
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function generateSubnetProxyTargetV2(
|
||||||
|
siteResource: SiteResource,
|
||||||
|
clients: {
|
||||||
|
clientId: number;
|
||||||
|
pubKey: string | null;
|
||||||
|
subnet: string | null;
|
||||||
|
}[]
|
||||||
|
): SubnetProxyTargetV2 | undefined {
|
||||||
|
if (clients.length === 0) {
|
||||||
|
logger.debug(
|
||||||
|
`No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let target: SubnetProxyTargetV2 | null = null;
|
||||||
|
|
||||||
|
const portRange = [
|
||||||
|
...parsePortRangeString(siteResource.tcpPortRangeString, "tcp"),
|
||||||
|
...parsePortRangeString(siteResource.udpPortRangeString, "udp")
|
||||||
|
];
|
||||||
|
const disableIcmp = siteResource.disableIcmp ?? false;
|
||||||
|
|
||||||
|
if (siteResource.mode == "host") {
|
||||||
|
let destination = siteResource.destination;
|
||||||
|
// check if this is a valid ip
|
||||||
|
const ipSchema = z.union([z.ipv4(), z.ipv6()]);
|
||||||
|
if (ipSchema.safeParse(destination).success) {
|
||||||
|
destination = `${destination}/32`;
|
||||||
|
|
||||||
|
target = {
|
||||||
|
sourcePrefixes: [],
|
||||||
|
destPrefix: destination,
|
||||||
|
portRange,
|
||||||
|
disableIcmp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (siteResource.alias && siteResource.aliasAddress) {
|
||||||
|
// also push a match for the alias address
|
||||||
|
target = {
|
||||||
|
sourcePrefixes: [],
|
||||||
|
destPrefix: `${siteResource.aliasAddress}/32`,
|
||||||
|
rewriteTo: destination,
|
||||||
|
portRange,
|
||||||
|
disableIcmp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (siteResource.mode == "cidr") {
|
||||||
|
target = {
|
||||||
|
sourcePrefixes: [],
|
||||||
|
destPrefix: siteResource.destination,
|
||||||
|
portRange,
|
||||||
|
disableIcmp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const clientSite of clients) {
|
||||||
|
if (!clientSite.subnet) {
|
||||||
|
logger.debug(
|
||||||
|
`Client ${clientSite.clientId} has no subnet, skipping for site resource ${siteResource.siteResourceId}.`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientPrefix = `${clientSite.subnet.split("/")[0]}/32`;
|
||||||
|
|
||||||
|
// add client prefix to source prefixes
|
||||||
|
target.sourcePrefixes.push(clientPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// print a nice representation of the targets
|
||||||
|
// logger.debug(
|
||||||
|
// `Generated subnet proxy targets for: ${JSON.stringify(targets, null, 2)}`
|
||||||
|
// );
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a SubnetProxyTargetV2 to an array of SubnetProxyTarget (v1)
|
||||||
|
* by expanding each source prefix into its own target entry.
|
||||||
|
* @param targetV2 - The v2 target to convert
|
||||||
|
* @returns Array of v1 SubnetProxyTarget objects
|
||||||
|
*/
|
||||||
|
export function convertSubnetProxyTargetsV2ToV1(
|
||||||
|
targetsV2: SubnetProxyTargetV2[]
|
||||||
|
): SubnetProxyTarget[] {
|
||||||
|
return targetsV2.flatMap((targetV2) =>
|
||||||
|
targetV2.sourcePrefixes.map((sourcePrefix) => ({
|
||||||
|
sourcePrefix,
|
||||||
|
destPrefix: targetV2.destPrefix,
|
||||||
|
...(targetV2.disableIcmp !== undefined && {
|
||||||
|
disableIcmp: targetV2.disableIcmp
|
||||||
|
}),
|
||||||
|
...(targetV2.rewriteTo !== undefined && {
|
||||||
|
rewriteTo: targetV2.rewriteTo
|
||||||
|
}),
|
||||||
|
...(targetV2.portRange !== undefined && {
|
||||||
|
portRange: targetV2.portRange
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Custom schema for validating port range strings
|
// Custom schema for validating port range strings
|
||||||
// Format: "80,443,8000-9000" or "*" for all ports, or empty string
|
// Format: "80,443,8000-9000" or "*" for all ports, or empty string
|
||||||
export const portRangeStringSchema = z
|
export const portRangeStringSchema = z
|
||||||
|
|||||||
@@ -302,8 +302,8 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default({
|
.default({
|
||||||
block_size: 24,
|
block_size: 24,
|
||||||
subnet_group: "100.90.128.0/24",
|
subnet_group: "100.90.128.0/20",
|
||||||
utility_subnet_group: "100.96.128.0/24"
|
utility_subnet_group: "100.96.128.0/20"
|
||||||
}),
|
}),
|
||||||
rate_limits: z
|
rate_limits: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import logger from "@server/logger";
|
|||||||
import {
|
import {
|
||||||
generateAliasConfig,
|
generateAliasConfig,
|
||||||
generateRemoteSubnets,
|
generateRemoteSubnets,
|
||||||
generateSubnetProxyTargets,
|
generateSubnetProxyTargetV2,
|
||||||
parseEndpoint,
|
parseEndpoint,
|
||||||
formatEndpoint
|
formatEndpoint
|
||||||
} from "@server/lib/ip";
|
} from "@server/lib/ip";
|
||||||
@@ -659,17 +659,14 @@ async function handleSubnetProxyTargetUpdates(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (addedClients.length > 0) {
|
if (addedClients.length > 0) {
|
||||||
const targetsToAdd = generateSubnetProxyTargets(
|
const targetToAdd = generateSubnetProxyTargetV2(
|
||||||
siteResource,
|
siteResource,
|
||||||
addedClients
|
addedClients
|
||||||
);
|
);
|
||||||
|
|
||||||
if (targetsToAdd.length > 0) {
|
if (targetToAdd) {
|
||||||
logger.info(
|
|
||||||
`Adding ${targetsToAdd.length} subnet proxy targets for siteResource ${siteResource.siteResourceId}`
|
|
||||||
);
|
|
||||||
proxyJobs.push(
|
proxyJobs.push(
|
||||||
addSubnetProxyTargets(newt.newtId, targetsToAdd)
|
addSubnetProxyTargets(newt.newtId, [targetToAdd])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,17 +692,14 @@ async function handleSubnetProxyTargetUpdates(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (removedClients.length > 0) {
|
if (removedClients.length > 0) {
|
||||||
const targetsToRemove = generateSubnetProxyTargets(
|
const targetToRemove = generateSubnetProxyTargetV2(
|
||||||
siteResource,
|
siteResource,
|
||||||
removedClients
|
removedClients
|
||||||
);
|
);
|
||||||
|
|
||||||
if (targetsToRemove.length > 0) {
|
if (targetToRemove) {
|
||||||
logger.info(
|
|
||||||
`Removing ${targetsToRemove.length} subnet proxy targets for siteResource ${siteResource.siteResourceId}`
|
|
||||||
);
|
|
||||||
proxyJobs.push(
|
proxyJobs.push(
|
||||||
removeSubnetProxyTargets(newt.newtId, targetsToRemove)
|
removeSubnetProxyTargets(newt.newtId, [targetToRemove])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1159,7 +1153,7 @@ async function handleMessagesForClientResources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const resource of resources) {
|
for (const resource of resources) {
|
||||||
const targets = generateSubnetProxyTargets(resource, [
|
const target = generateSubnetProxyTargetV2(resource, [
|
||||||
{
|
{
|
||||||
clientId: client.clientId,
|
clientId: client.clientId,
|
||||||
pubKey: client.pubKey,
|
pubKey: client.pubKey,
|
||||||
@@ -1167,8 +1161,8 @@ async function handleMessagesForClientResources(
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (targets.length > 0) {
|
if (target) {
|
||||||
proxyJobs.push(addSubnetProxyTargets(newt.newtId, targets));
|
proxyJobs.push(addSubnetProxyTargets(newt.newtId, [target]));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1230,7 +1224,7 @@ async function handleMessagesForClientResources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const resource of resources) {
|
for (const resource of resources) {
|
||||||
const targets = generateSubnetProxyTargets(resource, [
|
const target = generateSubnetProxyTargetV2(resource, [
|
||||||
{
|
{
|
||||||
clientId: client.clientId,
|
clientId: client.clientId,
|
||||||
pubKey: client.pubKey,
|
pubKey: client.pubKey,
|
||||||
@@ -1238,9 +1232,9 @@ async function handleMessagesForClientResources(
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (targets.length > 0) {
|
if (target) {
|
||||||
proxyJobs.push(
|
proxyJobs.push(
|
||||||
removeSubnetProxyTargets(newt.newtId, targets)
|
removeSubnetProxyTargets(newt.newtId, [target])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -477,7 +477,10 @@ export async function getTraefikConfig(
|
|||||||
|
|
||||||
// TODO: HOW TO HANDLE ^^^^^^ BETTER
|
// TODO: HOW TO HANDLE ^^^^^^ BETTER
|
||||||
const anySitesOnline = targets.some(
|
const anySitesOnline = targets.some(
|
||||||
(target) => target.site.online
|
(target) =>
|
||||||
|
target.site.online ||
|
||||||
|
target.site.type === "local" ||
|
||||||
|
target.site.type === "wireguard"
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -490,7 +493,7 @@ export async function getTraefikConfig(
|
|||||||
if (target.health == "unhealthy") {
|
if (target.health == "unhealthy") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any sites are online, exclude offline sites
|
// If any sites are online, exclude offline sites
|
||||||
if (anySitesOnline && !target.site.online) {
|
if (anySitesOnline && !target.site.online) {
|
||||||
return false;
|
return false;
|
||||||
@@ -605,7 +608,10 @@ export async function getTraefikConfig(
|
|||||||
servers: (() => {
|
servers: (() => {
|
||||||
// Check if any sites are online
|
// Check if any sites are online
|
||||||
const anySitesOnline = targets.some(
|
const anySitesOnline = targets.some(
|
||||||
(target) => target.site.online
|
(target) =>
|
||||||
|
target.site.online ||
|
||||||
|
target.site.type === "local" ||
|
||||||
|
target.site.type === "wireguard"
|
||||||
);
|
);
|
||||||
|
|
||||||
return targets
|
return targets
|
||||||
@@ -613,7 +619,7 @@ export async function getTraefikConfig(
|
|||||||
if (!target.enabled) {
|
if (!target.enabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any sites are online, exclude offline sites
|
// If any sites are online, exclude offline sites
|
||||||
if (anySitesOnline && !target.site.online) {
|
if (anySitesOnline && !target.site.online) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ export * from "./verifyApiKeyApiKeyAccess";
|
|||||||
export * from "./verifyApiKeyClientAccess";
|
export * from "./verifyApiKeyClientAccess";
|
||||||
export * from "./verifyApiKeySiteResourceAccess";
|
export * from "./verifyApiKeySiteResourceAccess";
|
||||||
export * from "./verifyApiKeyIdpAccess";
|
export * from "./verifyApiKeyIdpAccess";
|
||||||
|
export * from "./verifyApiKeyDomainAccess";
|
||||||
|
|||||||
90
server/middlewares/integration/verifyApiKeyDomainAccess.ts
Normal file
90
server/middlewares/integration/verifyApiKeyDomainAccess.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { db, domains, orgDomains, apiKeyOrg } from "@server/db";
|
||||||
|
import { and, eq } from "drizzle-orm";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
|
||||||
|
export async function verifyApiKeyDomainAccess(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const apiKey = req.apiKey;
|
||||||
|
const domainId =
|
||||||
|
req.params.domainId || req.body.domainId || req.query.domainId;
|
||||||
|
const orgId = req.params.orgId;
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.UNAUTHORIZED, "Key not authenticated")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!domainId) {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.BAD_REQUEST, "Invalid domain ID")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiKey.isRoot) {
|
||||||
|
// Root keys can access any domain in any org
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify domain exists and belongs to the organization
|
||||||
|
const [domain] = await db
|
||||||
|
.select()
|
||||||
|
.from(domains)
|
||||||
|
.innerJoin(orgDomains, eq(orgDomains.domainId, domains.domainId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(orgDomains.domainId, domainId),
|
||||||
|
eq(orgDomains.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!domain) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Domain with ID ${domainId} not found in organization ${orgId}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the API key has access to this organization
|
||||||
|
if (!req.apiKeyOrg) {
|
||||||
|
const apiKeyOrgRes = await db
|
||||||
|
.select()
|
||||||
|
.from(apiKeyOrg)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(apiKeyOrg.apiKeyId, apiKey.apiKeyId),
|
||||||
|
eq(apiKeyOrg.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
req.apiKeyOrg = apiKeyOrgRes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.apiKeyOrg) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.FORBIDDEN,
|
||||||
|
"Key does not have access to this organization"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
} catch (error) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Error verifying domain access"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -665,7 +665,10 @@ export async function getTraefikConfig(
|
|||||||
|
|
||||||
// TODO: HOW TO HANDLE ^^^^^^ BETTER
|
// TODO: HOW TO HANDLE ^^^^^^ BETTER
|
||||||
const anySitesOnline = targets.some(
|
const anySitesOnline = targets.some(
|
||||||
(target) => target.site.online
|
(target) =>
|
||||||
|
target.site.online ||
|
||||||
|
target.site.type === "local" ||
|
||||||
|
target.site.type === "wireguard"
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -793,7 +796,10 @@ export async function getTraefikConfig(
|
|||||||
servers: (() => {
|
servers: (() => {
|
||||||
// Check if any sites are online
|
// Check if any sites are online
|
||||||
const anySitesOnline = targets.some(
|
const anySitesOnline = targets.some(
|
||||||
(target) => target.site.online
|
(target) =>
|
||||||
|
target.site.online ||
|
||||||
|
target.site.type === "local" ||
|
||||||
|
target.site.type === "wireguard"
|
||||||
);
|
);
|
||||||
|
|
||||||
return targets
|
return targets
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import { sendToClient } from "#dynamic/routers/ws";
|
import { sendToClient } from "#dynamic/routers/ws";
|
||||||
import { db, olms, Transaction } from "@server/db";
|
import { S } from "@faker-js/faker/dist/airline-Dz1uGqgJ";
|
||||||
import { Alias, SubnetProxyTarget } from "@server/lib/ip";
|
import { db, newts, olms, Transaction } from "@server/db";
|
||||||
|
import {
|
||||||
|
Alias,
|
||||||
|
convertSubnetProxyTargetsV2ToV1,
|
||||||
|
SubnetProxyTarget,
|
||||||
|
SubnetProxyTargetV2
|
||||||
|
} from "@server/lib/ip";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
import semver from "semver";
|
||||||
|
|
||||||
const BATCH_SIZE = 50;
|
const BATCH_SIZE = 50;
|
||||||
const BATCH_DELAY_MS = 50;
|
const BATCH_DELAY_MS = 50;
|
||||||
@@ -19,57 +26,149 @@ function chunkArray<T>(array: T[], size: number): T[][] {
|
|||||||
return chunks;
|
return chunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addTargets(newtId: string, targets: SubnetProxyTarget[]) {
|
const NEWT_V2_TARGETS_VERSION = ">=1.11.0";
|
||||||
const batches = chunkArray(targets, BATCH_SIZE);
|
|
||||||
|
export async function convertTargetsIfNessicary(
|
||||||
|
newtId: string,
|
||||||
|
targets: SubnetProxyTarget[] | SubnetProxyTargetV2[]
|
||||||
|
) {
|
||||||
|
// get the newt
|
||||||
|
const [newt] = await db
|
||||||
|
.select()
|
||||||
|
.from(newts)
|
||||||
|
.where(eq(newts.newtId, newtId));
|
||||||
|
if (!newt) {
|
||||||
|
throw new Error(`No newt found for id: ${newtId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the semver
|
||||||
|
if (
|
||||||
|
newt.version &&
|
||||||
|
!semver.satisfies(newt.version, NEWT_V2_TARGETS_VERSION)
|
||||||
|
) {
|
||||||
|
logger.debug(
|
||||||
|
`addTargets Newt version ${newt.version} does not support targets v2 falling back`
|
||||||
|
);
|
||||||
|
targets = convertSubnetProxyTargetsV2ToV1(
|
||||||
|
targets as SubnetProxyTargetV2[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addTargets(
|
||||||
|
newtId: string,
|
||||||
|
targets: SubnetProxyTarget[] | SubnetProxyTargetV2[]
|
||||||
|
) {
|
||||||
|
targets = await convertTargetsIfNessicary(newtId, targets);
|
||||||
|
|
||||||
|
const batches = chunkArray<SubnetProxyTarget | SubnetProxyTargetV2>(
|
||||||
|
targets,
|
||||||
|
BATCH_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
for (let i = 0; i < batches.length; i++) {
|
for (let i = 0; i < batches.length; i++) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
await sleep(BATCH_DELAY_MS);
|
await sleep(BATCH_DELAY_MS);
|
||||||
}
|
}
|
||||||
await sendToClient(newtId, {
|
await sendToClient(
|
||||||
type: `newt/wg/targets/add`,
|
newtId,
|
||||||
data: batches[i]
|
{
|
||||||
}, { incrementConfigVersion: true });
|
type: `newt/wg/targets/add`,
|
||||||
|
data: batches[i]
|
||||||
|
},
|
||||||
|
{ incrementConfigVersion: true }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeTargets(
|
export async function removeTargets(
|
||||||
newtId: string,
|
newtId: string,
|
||||||
targets: SubnetProxyTarget[]
|
targets: SubnetProxyTarget[] | SubnetProxyTargetV2[]
|
||||||
) {
|
) {
|
||||||
const batches = chunkArray(targets, BATCH_SIZE);
|
targets = await convertTargetsIfNessicary(newtId, targets);
|
||||||
|
|
||||||
|
const batches = chunkArray<SubnetProxyTarget | SubnetProxyTargetV2>(
|
||||||
|
targets,
|
||||||
|
BATCH_SIZE
|
||||||
|
);
|
||||||
for (let i = 0; i < batches.length; i++) {
|
for (let i = 0; i < batches.length; i++) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
await sleep(BATCH_DELAY_MS);
|
await sleep(BATCH_DELAY_MS);
|
||||||
}
|
}
|
||||||
await sendToClient(newtId, {
|
await sendToClient(
|
||||||
type: `newt/wg/targets/remove`,
|
newtId,
|
||||||
data: batches[i]
|
{
|
||||||
},{ incrementConfigVersion: true });
|
type: `newt/wg/targets/remove`,
|
||||||
|
data: batches[i]
|
||||||
|
},
|
||||||
|
{ incrementConfigVersion: true }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateTargets(
|
export async function updateTargets(
|
||||||
newtId: string,
|
newtId: string,
|
||||||
targets: {
|
targets: {
|
||||||
oldTargets: SubnetProxyTarget[];
|
oldTargets: SubnetProxyTarget[] | SubnetProxyTargetV2[];
|
||||||
newTargets: SubnetProxyTarget[];
|
newTargets: SubnetProxyTarget[] | SubnetProxyTargetV2[];
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const oldBatches = chunkArray(targets.oldTargets, BATCH_SIZE);
|
// get the newt
|
||||||
const newBatches = chunkArray(targets.newTargets, BATCH_SIZE);
|
const [newt] = await db
|
||||||
|
.select()
|
||||||
|
.from(newts)
|
||||||
|
.where(eq(newts.newtId, newtId));
|
||||||
|
if (!newt) {
|
||||||
|
logger.error(`addTargetsL No newt found for id: ${newtId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the semver
|
||||||
|
if (
|
||||||
|
newt.version &&
|
||||||
|
!semver.satisfies(newt.version, NEWT_V2_TARGETS_VERSION)
|
||||||
|
) {
|
||||||
|
logger.debug(
|
||||||
|
`addTargets Newt version ${newt.version} does not support targets v2 falling back`
|
||||||
|
);
|
||||||
|
targets = {
|
||||||
|
oldTargets: convertSubnetProxyTargetsV2ToV1(
|
||||||
|
targets.oldTargets as SubnetProxyTargetV2[]
|
||||||
|
),
|
||||||
|
newTargets: convertSubnetProxyTargetsV2ToV1(
|
||||||
|
targets.newTargets as SubnetProxyTargetV2[]
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldBatches = chunkArray<SubnetProxyTarget | SubnetProxyTargetV2>(
|
||||||
|
targets.oldTargets,
|
||||||
|
BATCH_SIZE
|
||||||
|
);
|
||||||
|
const newBatches = chunkArray<SubnetProxyTarget | SubnetProxyTargetV2>(
|
||||||
|
targets.newTargets,
|
||||||
|
BATCH_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
const maxBatches = Math.max(oldBatches.length, newBatches.length);
|
const maxBatches = Math.max(oldBatches.length, newBatches.length);
|
||||||
|
|
||||||
for (let i = 0; i < maxBatches; i++) {
|
for (let i = 0; i < maxBatches; i++) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
await sleep(BATCH_DELAY_MS);
|
await sleep(BATCH_DELAY_MS);
|
||||||
}
|
}
|
||||||
await sendToClient(newtId, {
|
await sendToClient(
|
||||||
type: `newt/wg/targets/update`,
|
newtId,
|
||||||
data: {
|
{
|
||||||
oldTargets: oldBatches[i] || [],
|
type: `newt/wg/targets/update`,
|
||||||
newTargets: newBatches[i] || []
|
data: {
|
||||||
}
|
oldTargets: oldBatches[i] || [],
|
||||||
}, { incrementConfigVersion: true }).catch((error) => {
|
newTargets: newBatches[i] || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ incrementConfigVersion: true }
|
||||||
|
).catch((error) => {
|
||||||
logger.warn(`Error sending message:`, error);
|
logger.warn(`Error sending message:`, error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -94,14 +193,18 @@ export async function addPeerData(
|
|||||||
olmId = olm.olmId;
|
olmId = olm.olmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendToClient(olmId, {
|
await sendToClient(
|
||||||
type: `olm/wg/peer/data/add`,
|
olmId,
|
||||||
data: {
|
{
|
||||||
siteId: siteId,
|
type: `olm/wg/peer/data/add`,
|
||||||
remoteSubnets: remoteSubnets,
|
data: {
|
||||||
aliases: aliases
|
siteId: siteId,
|
||||||
}
|
remoteSubnets: remoteSubnets,
|
||||||
}, { incrementConfigVersion: true }).catch((error) => {
|
aliases: aliases
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ incrementConfigVersion: true }
|
||||||
|
).catch((error) => {
|
||||||
logger.warn(`Error sending message:`, error);
|
logger.warn(`Error sending message:`, error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -125,14 +228,18 @@ export async function removePeerData(
|
|||||||
olmId = olm.olmId;
|
olmId = olm.olmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendToClient(olmId, {
|
await sendToClient(
|
||||||
type: `olm/wg/peer/data/remove`,
|
olmId,
|
||||||
data: {
|
{
|
||||||
siteId: siteId,
|
type: `olm/wg/peer/data/remove`,
|
||||||
remoteSubnets: remoteSubnets,
|
data: {
|
||||||
aliases: aliases
|
siteId: siteId,
|
||||||
}
|
remoteSubnets: remoteSubnets,
|
||||||
}, { incrementConfigVersion: true }).catch((error) => {
|
aliases: aliases
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ incrementConfigVersion: true }
|
||||||
|
).catch((error) => {
|
||||||
logger.warn(`Error sending message:`, error);
|
logger.warn(`Error sending message:`, error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -166,14 +273,18 @@ export async function updatePeerData(
|
|||||||
olmId = olm.olmId;
|
olmId = olm.olmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendToClient(olmId, {
|
await sendToClient(
|
||||||
type: `olm/wg/peer/data/update`,
|
olmId,
|
||||||
data: {
|
{
|
||||||
siteId: siteId,
|
type: `olm/wg/peer/data/update`,
|
||||||
...remoteSubnets,
|
data: {
|
||||||
...aliases
|
siteId: siteId,
|
||||||
}
|
...remoteSubnets,
|
||||||
}, { incrementConfigVersion: true }).catch((error) => {
|
...aliases
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ incrementConfigVersion: true }
|
||||||
|
).catch((error) => {
|
||||||
logger.warn(`Error sending message:`, error);
|
logger.warn(`Error sending message:`, error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ import {
|
|||||||
verifyApiKeyClientAccess,
|
verifyApiKeyClientAccess,
|
||||||
verifyApiKeySiteResourceAccess,
|
verifyApiKeySiteResourceAccess,
|
||||||
verifyApiKeySetResourceClients,
|
verifyApiKeySetResourceClients,
|
||||||
verifyLimits
|
verifyLimits,
|
||||||
|
verifyApiKeyDomainAccess
|
||||||
} from "@server/middlewares";
|
} from "@server/middlewares";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
@@ -347,6 +348,56 @@ authenticated.get(
|
|||||||
domain.listDomains
|
domain.listDomains
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/domain/:domainId",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyDomainAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.getDomain),
|
||||||
|
domain.getDomain
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.put(
|
||||||
|
"/org/:orgId/domain",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.createOrgDomain),
|
||||||
|
logActionAudit(ActionsEnum.createOrgDomain),
|
||||||
|
domain.createOrgDomain
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.patch(
|
||||||
|
"/org/:orgId/domain/:domainId",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyDomainAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.updateOrgDomain),
|
||||||
|
domain.updateOrgDomain
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.delete(
|
||||||
|
"/org/:orgId/domain/:domainId",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyDomainAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.deleteOrgDomain),
|
||||||
|
logActionAudit(ActionsEnum.deleteOrgDomain),
|
||||||
|
domain.deleteAccountDomain
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/domain/:domainId/dns-records",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyDomainAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.getDNSRecords),
|
||||||
|
domain.getDNSRecords
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.post(
|
||||||
|
"/org/:orgId/domain/:domainId/restart",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyDomainAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.restartOrgDomain),
|
||||||
|
logActionAudit(ActionsEnum.restartOrgDomain),
|
||||||
|
domain.restartOrgDomain
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/invitations",
|
"/org/:orgId/invitations",
|
||||||
verifyApiKeyOrgAccess,
|
verifyApiKeyOrgAccess,
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
import { clients, clientSiteResourcesAssociationsCache, clientSitesAssociationsCache, db, ExitNode, resources, Site, siteResources, targetHealthCheck, targets } from "@server/db";
|
import {
|
||||||
|
clients,
|
||||||
|
clientSiteResourcesAssociationsCache,
|
||||||
|
clientSitesAssociationsCache,
|
||||||
|
db,
|
||||||
|
ExitNode,
|
||||||
|
resources,
|
||||||
|
Site,
|
||||||
|
siteResources,
|
||||||
|
targetHealthCheck,
|
||||||
|
targets
|
||||||
|
} from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { initPeerAddHandshake, updatePeer } from "../olm/peers";
|
import { initPeerAddHandshake, updatePeer } from "../olm/peers";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import { generateSubnetProxyTargets, SubnetProxyTarget } from "@server/lib/ip";
|
import {
|
||||||
|
generateSubnetProxyTargetV2,
|
||||||
|
SubnetProxyTargetV2
|
||||||
|
} from "@server/lib/ip";
|
||||||
|
|
||||||
export async function buildClientConfigurationForNewtClient(
|
export async function buildClientConfigurationForNewtClient(
|
||||||
site: Site,
|
site: Site,
|
||||||
@@ -126,7 +140,7 @@ export async function buildClientConfigurationForNewtClient(
|
|||||||
.from(siteResources)
|
.from(siteResources)
|
||||||
.where(eq(siteResources.siteId, siteId));
|
.where(eq(siteResources.siteId, siteId));
|
||||||
|
|
||||||
const targetsToSend: SubnetProxyTarget[] = [];
|
const targetsToSend: SubnetProxyTargetV2[] = [];
|
||||||
|
|
||||||
for (const resource of allSiteResources) {
|
for (const resource of allSiteResources) {
|
||||||
// Get clients associated with this specific resource
|
// Get clients associated with this specific resource
|
||||||
@@ -151,12 +165,14 @@ export async function buildClientConfigurationForNewtClient(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const resourceTargets = generateSubnetProxyTargets(
|
const resourceTarget = generateSubnetProxyTargetV2(
|
||||||
resource,
|
resource,
|
||||||
resourceClients
|
resourceClients
|
||||||
);
|
);
|
||||||
|
|
||||||
targetsToSend.push(...resourceTargets);
|
if (resourceTarget) {
|
||||||
|
targetsToSend.push(resourceTarget);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { db, ExitNode, exitNodes, Newt, sites } from "@server/db";
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||||
import { buildClientConfigurationForNewtClient } from "./buildConfiguration";
|
import { buildClientConfigurationForNewtClient } from "./buildConfiguration";
|
||||||
|
import { convertTargetsIfNessicary } from "../client/targets";
|
||||||
|
|
||||||
const inputSchema = z.object({
|
const inputSchema = z.object({
|
||||||
publicKey: z.string(),
|
publicKey: z.string(),
|
||||||
@@ -126,13 +127,15 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
|
|||||||
exitNode
|
exitNode
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const targetsToSend = await convertTargetsIfNessicary(newt.newtId, targets);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: {
|
message: {
|
||||||
type: "newt/wg/receive-config",
|
type: "newt/wg/receive-config",
|
||||||
data: {
|
data: {
|
||||||
ipAddress: site.address,
|
ipAddress: site.address,
|
||||||
peers,
|
peers,
|
||||||
targets
|
targets: targetsToSend
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
broadcast: false,
|
broadcast: false,
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ export async function createSite(
|
|||||||
if (type == "newt") {
|
if (type == "newt") {
|
||||||
[newSite] = await trx
|
[newSite] = await trx
|
||||||
.insert(sites)
|
.insert(sites)
|
||||||
.values({
|
.values({ // NOTE: NO SUBNET OR EXIT NODE ID PASSED IN HERE BECAUSE ITS NOW CHOSEN ON CONNECT
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
niceId,
|
niceId,
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const createSiteResourceSchema = z
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
message:
|
message:
|
||||||
"Destination must be a valid IP address or valid domain AND alias is required"
|
"Destination must be a valid IPV4 address or valid domain AND alias is required"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { updatePeerData, updateTargets } from "@server/routers/client/targets";
|
|||||||
import {
|
import {
|
||||||
generateAliasConfig,
|
generateAliasConfig,
|
||||||
generateRemoteSubnets,
|
generateRemoteSubnets,
|
||||||
generateSubnetProxyTargets,
|
generateSubnetProxyTargetV2,
|
||||||
isIpInCidr,
|
isIpInCidr,
|
||||||
portRangeStringSchema
|
portRangeStringSchema
|
||||||
} from "@server/lib/ip";
|
} from "@server/lib/ip";
|
||||||
@@ -608,18 +608,18 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
|
|
||||||
// Only update targets on newt if destination changed
|
// Only update targets on newt if destination changed
|
||||||
if (destinationChanged || portRangesChanged) {
|
if (destinationChanged || portRangesChanged) {
|
||||||
const oldTargets = generateSubnetProxyTargets(
|
const oldTarget = generateSubnetProxyTargetV2(
|
||||||
existingSiteResource,
|
existingSiteResource,
|
||||||
mergedAllClients
|
mergedAllClients
|
||||||
);
|
);
|
||||||
const newTargets = generateSubnetProxyTargets(
|
const newTarget = generateSubnetProxyTargetV2(
|
||||||
updatedSiteResource,
|
updatedSiteResource,
|
||||||
mergedAllClients
|
mergedAllClients
|
||||||
);
|
);
|
||||||
|
|
||||||
await updateTargets(newt.newtId, {
|
await updateTargets(newt.newtId, {
|
||||||
oldTargets: oldTargets,
|
oldTargets: oldTarget ? [oldTarget] : [],
|
||||||
newTargets: newTargets
|
newTargets: newTarget ? [newTarget] : []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,8 +171,7 @@ const DockerContainersTable: FC<{
|
|||||||
...Object.values(container.networks)
|
...Object.values(container.networks)
|
||||||
.map((n) => n.ipAddress)
|
.map((n) => n.ipAddress)
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
...getExposedPorts(container).map((p) => p.toString()),
|
...getExposedPorts(container).map((p) => p.toString())
|
||||||
...Object.entries(container.labels).flat()
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return searchableFields.some((field) =>
|
return searchableFields.some((field) =>
|
||||||
|
|||||||
@@ -69,15 +69,16 @@ export function LayoutMobileMenu({
|
|||||||
<SheetDescription className="sr-only">
|
<SheetDescription className="sr-only">
|
||||||
{t("navbarDescription")}
|
{t("navbarDescription")}
|
||||||
</SheetDescription>
|
</SheetDescription>
|
||||||
<div className="flex-1 overflow-y-auto relative">
|
<div className="w-full border-b border-border">
|
||||||
<div className="px-1">
|
<div className="px-1 shrink-0">
|
||||||
<OrgSelector
|
<OrgSelector
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
orgs={orgs}
|
orgs={orgs}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full border-b border-border" />
|
</div>
|
||||||
<div className="px-3 pt-3">
|
<div className="flex-1 overflow-y-auto relative">
|
||||||
|
<div className="px-3">
|
||||||
{!isAdminPage &&
|
{!isAdminPage &&
|
||||||
user.serverAdmin && (
|
user.serverAdmin && (
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ function getActionsCategories(root: boolean) {
|
|||||||
[t("actionListInvitations")]: "listInvitations",
|
[t("actionListInvitations")]: "listInvitations",
|
||||||
[t("actionRemoveUser")]: "removeUser",
|
[t("actionRemoveUser")]: "removeUser",
|
||||||
[t("actionListUsers")]: "listUsers",
|
[t("actionListUsers")]: "listUsers",
|
||||||
[t("actionListOrgDomains")]: "listOrgDomains",
|
|
||||||
[t("updateOrgUser")]: "updateOrgUser",
|
[t("updateOrgUser")]: "updateOrgUser",
|
||||||
[t("createOrgUser")]: "createOrgUser",
|
[t("createOrgUser")]: "createOrgUser",
|
||||||
[t("actionApplyBlueprint")]: "applyBlueprint",
|
[t("actionApplyBlueprint")]: "applyBlueprint",
|
||||||
@@ -39,6 +38,16 @@ function getActionsCategories(root: boolean) {
|
|||||||
[t("actionGetBlueprint")]: "getBlueprint"
|
[t("actionGetBlueprint")]: "getBlueprint"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Domain: {
|
||||||
|
[t("actionListOrgDomains")]: "listOrgDomains",
|
||||||
|
[t("actionGetDomain")]: "getDomain",
|
||||||
|
[t("actionCreateOrgDomain")]: "createOrgDomain",
|
||||||
|
[t("actionUpdateOrgDomain")]: "updateOrgDomain",
|
||||||
|
[t("actionDeleteOrgDomain")]: "deleteOrgDomain",
|
||||||
|
[t("actionGetDNSRecords")]: "getDNSRecords",
|
||||||
|
[t("actionRestartOrgDomain")]: "restartOrgDomain"
|
||||||
|
},
|
||||||
|
|
||||||
Site: {
|
Site: {
|
||||||
[t("actionCreateSite")]: "createSite",
|
[t("actionCreateSite")]: "createSite",
|
||||||
[t("actionDeleteSite")]: "deleteSite",
|
[t("actionDeleteSite")]: "deleteSite",
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export function NewtSiteInstallCommands({
|
|||||||
`helm install newt fossorial/newt \\
|
`helm install newt fossorial/newt \\
|
||||||
--create-namespace \\
|
--create-namespace \\
|
||||||
--set newtInstances[0].name="main-tunnel" \\
|
--set newtInstances[0].name="main-tunnel" \\
|
||||||
|
--set newtInstances[0].enabled=true \\
|
||||||
--set-string newtInstances[0].auth.keys.endpointKey="${endpoint}" \\
|
--set-string newtInstances[0].auth.keys.endpointKey="${endpoint}" \\
|
||||||
--set-string newtInstances[0].auth.keys.idKey="${id}" \\
|
--set-string newtInstances[0].auth.keys.idKey="${id}" \\
|
||||||
--set-string newtInstances[0].auth.keys.secretKey="${secret}"`
|
--set-string newtInstances[0].auth.keys.secretKey="${secret}"`
|
||||||
@@ -185,59 +186,72 @@ WantedBy=default.target`
|
|||||||
className="mt-4"
|
className="mt-4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
<p className="font-bold mb-3">
|
<p className="font-bold mb-3">{t("siteConfiguration")}</p>
|
||||||
{t("siteConfiguration")}
|
<div className="flex items-center space-x-2 mb-2">
|
||||||
</p>
|
<CheckboxWithLabel
|
||||||
<div className="flex items-center space-x-2 mb-2">
|
id="acceptClients"
|
||||||
<CheckboxWithLabel
|
aria-describedby="acceptClients-desc"
|
||||||
id="acceptClients"
|
checked={acceptClients}
|
||||||
aria-describedby="acceptClients-desc"
|
onCheckedChange={(checked) => {
|
||||||
checked={acceptClients}
|
const value = checked as boolean;
|
||||||
onCheckedChange={(checked) => {
|
setAcceptClients(value);
|
||||||
const value = checked as boolean;
|
}}
|
||||||
setAcceptClients(value);
|
label={t("siteAcceptClientConnections")}
|
||||||
}}
|
/>
|
||||||
label={t("siteAcceptClientConnections")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
id="acceptClients-desc"
|
|
||||||
className="text-sm text-muted-foreground"
|
|
||||||
>
|
|
||||||
{t("siteAcceptClientConnectionsDescription")}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p
|
||||||
|
id="acceptClients-desc"
|
||||||
|
className="text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
|
{t("siteAcceptClientConnectionsDescription")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
<p className="font-bold mb-3">{t("commands")}</p>
|
<p className="font-bold mb-3">{t("commands")}</p>
|
||||||
<div className="mt-2 space-y-3">
|
{platform === "kubernetes" && (
|
||||||
{commands.map((item, index) => {
|
<p className="text-sm text-muted-foreground mb-3">
|
||||||
const commandText =
|
For more and up to date Kubernetes installation
|
||||||
typeof item === "string"
|
information, see{" "}
|
||||||
? item
|
<a
|
||||||
: item.command;
|
href="https://docs.pangolin.net/manage/sites/install-kubernetes"
|
||||||
const title =
|
target="_blank"
|
||||||
typeof item === "string"
|
rel="noreferrer"
|
||||||
? undefined
|
className="underline"
|
||||||
: item.title;
|
>
|
||||||
|
docs.pangolin.net/manage/sites/install-kubernetes
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="mt-2 space-y-3">
|
||||||
|
{commands.map((item, index) => {
|
||||||
|
const commandText =
|
||||||
|
typeof item === "string" ? item : item.command;
|
||||||
|
const title =
|
||||||
|
typeof item === "string"
|
||||||
|
? undefined
|
||||||
|
: item.title;
|
||||||
|
|
||||||
return (
|
const key = `${title ?? ""}::${commandText}`;
|
||||||
<div key={index}>
|
|
||||||
{title && (
|
return (
|
||||||
<p className="text-sm font-medium mb-1.5">
|
<div key={key}>
|
||||||
{title}
|
{title && (
|
||||||
</p>
|
<p className="text-sm font-medium mb-1.5">
|
||||||
)}
|
{title}
|
||||||
<CopyTextBox
|
</p>
|
||||||
text={commandText}
|
)}
|
||||||
outline={true}
|
<CopyTextBox
|
||||||
/>
|
text={commandText}
|
||||||
</div>
|
outline={true}
|
||||||
);
|
/>
|
||||||
})}
|
</div>
|
||||||
</div>
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user