mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-22 12:56:37 +00:00
Compare commits
4 Commits
logs-datab
...
k8s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b786497299 | ||
|
|
874794c996 | ||
|
|
5e37c4e85f | ||
|
|
4e7eac368f |
35
.github/workflows/saas.yml
vendored
35
.github/workflows/saas.yml
vendored
@@ -56,6 +56,41 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
|
- name: Download MaxMind GeoLite2 databases
|
||||||
|
env:
|
||||||
|
MAXMIND_LICENSE_KEY: ${{ secrets.MAXMIND_LICENSE_KEY }}
|
||||||
|
run: |
|
||||||
|
echo "Downloading MaxMind GeoLite2 databases..."
|
||||||
|
|
||||||
|
# Download GeoLite2-Country
|
||||||
|
curl -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz" \
|
||||||
|
-o GeoLite2-Country.tar.gz
|
||||||
|
|
||||||
|
# Download GeoLite2-ASN
|
||||||
|
curl -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz" \
|
||||||
|
-o GeoLite2-ASN.tar.gz
|
||||||
|
|
||||||
|
# Extract the .mmdb files
|
||||||
|
tar -xzf GeoLite2-Country.tar.gz --strip-components=1 --wildcards '*.mmdb'
|
||||||
|
tar -xzf GeoLite2-ASN.tar.gz --strip-components=1 --wildcards '*.mmdb'
|
||||||
|
|
||||||
|
# Verify files exist
|
||||||
|
if [ ! -f "GeoLite2-Country.mmdb" ]; then
|
||||||
|
echo "ERROR: Failed to download GeoLite2-Country.mmdb"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "GeoLite2-ASN.mmdb" ]; then
|
||||||
|
echo "ERROR: Failed to download GeoLite2-ASN.mmdb"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up tar files
|
||||||
|
rm -f GeoLite2-Country.tar.gz GeoLite2-ASN.tar.gz
|
||||||
|
|
||||||
|
echo "MaxMind databases downloaded successfully"
|
||||||
|
ls -lh GeoLite2-*.mmdb
|
||||||
|
|
||||||
- name: Monitor storage space
|
- name: Monitor storage space
|
||||||
run: |
|
run: |
|
||||||
THRESHOLD=75
|
THRESHOLD=75
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ COPY server/db/ios_models.json ./dist/ios_models.json
|
|||||||
COPY server/db/mac_models.json ./dist/mac_models.json
|
COPY server/db/mac_models.json ./dist/mac_models.json
|
||||||
COPY public ./public
|
COPY public ./public
|
||||||
|
|
||||||
|
# Copy MaxMind databases for SaaS builds
|
||||||
|
ARG BUILD=oss
|
||||||
|
RUN mkdir -p ./maxmind
|
||||||
|
|
||||||
|
# This is only for saas
|
||||||
|
COPY --from=builder-dev /app/GeoLite2-Country.mmdb ./maxmind/GeoLite2-Country.mmdb
|
||||||
|
COPY --from=builder-dev /app/GeoLite2-ASN.mmdb ./maxmind/GeoLite2-ASN.mmdb
|
||||||
|
|
||||||
# OCI Image Labels - Build Args for dynamic values
|
# OCI Image Labels - Build Args for dynamic values
|
||||||
ARG VERSION="dev"
|
ARG VERSION="dev"
|
||||||
ARG REVISION=""
|
ARG REVISION=""
|
||||||
|
|||||||
@@ -23,9 +23,14 @@ export async function verifyApiKeyRoleAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { roleIds } = req.body;
|
let allRoleIds: number[] = [];
|
||||||
const allRoleIds =
|
if (!isNaN(singleRoleId)) {
|
||||||
roleIds || (isNaN(singleRoleId) ? [] : [singleRoleId]);
|
// If roleId is provided in URL params, query params, or body (single), use it exclusively
|
||||||
|
allRoleIds = [singleRoleId];
|
||||||
|
} else if (req.body?.roleIds) {
|
||||||
|
// Only use body.roleIds if no single roleId was provided
|
||||||
|
allRoleIds = req.body.roleIds;
|
||||||
|
}
|
||||||
|
|
||||||
if (allRoleIds.length === 0) {
|
if (allRoleIds.length === 0) {
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@@ -23,8 +23,14 @@ export async function verifyRoleAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const roleIds = req.body?.roleIds;
|
let allRoleIds: number[] = [];
|
||||||
const allRoleIds = roleIds || (isNaN(singleRoleId) ? [] : [singleRoleId]);
|
if (!isNaN(singleRoleId)) {
|
||||||
|
// If roleId is provided in URL params, query params, or body (single), use it exclusively
|
||||||
|
allRoleIds = [singleRoleId];
|
||||||
|
} else if (req.body?.roleIds) {
|
||||||
|
// Only use body.roleIds if no single roleId was provided
|
||||||
|
allRoleIds = req.body.roleIds;
|
||||||
|
}
|
||||||
|
|
||||||
if (allRoleIds.length === 0) {
|
if (allRoleIds.length === 0) {
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@@ -72,15 +72,15 @@ export const privateConfigSchema = z.object({
|
|||||||
db: z.int().nonnegative().optional().default(0)
|
db: z.int().nonnegative().optional().default(0)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
.optional(),
|
||||||
|
tls: z
|
||||||
|
.object({
|
||||||
|
rejectUnauthorized: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.default(true)
|
||||||
|
})
|
||||||
.optional()
|
.optional()
|
||||||
// tls: z
|
|
||||||
// .object({
|
|
||||||
// reject_unauthorized: z
|
|
||||||
// .boolean()
|
|
||||||
// .optional()
|
|
||||||
// .default(true)
|
|
||||||
// })
|
|
||||||
// .optional()
|
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
gerbil: z
|
gerbil: z
|
||||||
|
|||||||
@@ -108,11 +108,15 @@ class RedisManager {
|
|||||||
port: redisConfig.port!,
|
port: redisConfig.port!,
|
||||||
password: redisConfig.password,
|
password: redisConfig.password,
|
||||||
db: redisConfig.db
|
db: redisConfig.db
|
||||||
// tls: {
|
|
||||||
// rejectUnauthorized:
|
|
||||||
// redisConfig.tls?.reject_unauthorized || false
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Enable TLS if configured (required for AWS ElastiCache in-transit encryption)
|
||||||
|
if (redisConfig.tls) {
|
||||||
|
opts.tls = {
|
||||||
|
rejectUnauthorized: redisConfig.tls.rejectUnauthorized ?? true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,11 +134,15 @@ class RedisManager {
|
|||||||
port: replica.port!,
|
port: replica.port!,
|
||||||
password: replica.password,
|
password: replica.password,
|
||||||
db: replica.db || redisConfig.db
|
db: replica.db || redisConfig.db
|
||||||
// tls: {
|
|
||||||
// rejectUnauthorized:
|
|
||||||
// replica.tls?.reject_unauthorized || false
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Enable TLS if configured (required for AWS ElastiCache in-transit encryption)
|
||||||
|
if (redisConfig.tls) {
|
||||||
|
opts.tls = {
|
||||||
|
rejectUnauthorized: redisConfig.tls.rejectUnauthorized ?? true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export async function signSshKey(
|
|||||||
if (!userOrg.pamUsername) {
|
if (!userOrg.pamUsername) {
|
||||||
if (req.user?.email) {
|
if (req.user?.email) {
|
||||||
// Extract username from email (first part before @)
|
// Extract username from email (first part before @)
|
||||||
usernameToUse = req.user?.email.split("@")[0];
|
usernameToUse = req.user?.email.split("@")[0].replace(/[^a-zA-Z0-9_-]/g, "");
|
||||||
if (!usernameToUse) {
|
if (!usernameToUse) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import response from "@server/lib/response";
|
|||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and, ne } from "drizzle-orm";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
|
||||||
@@ -93,7 +93,8 @@ export async function updateClient(
|
|||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(clients.niceId, niceId),
|
eq(clients.niceId, niceId),
|
||||||
eq(clients.orgId, clients.orgId)
|
eq(clients.orgId, clients.orgId),
|
||||||
|
ne(clients.clientId, clientId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Resource,
|
Resource,
|
||||||
resources
|
resources
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and, ne } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
@@ -33,7 +33,15 @@ const updateResourceParamsSchema = z.strictObject({
|
|||||||
const updateHttpResourceBodySchema = z
|
const updateHttpResourceBodySchema = z
|
||||||
.strictObject({
|
.strictObject({
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
niceId: z.string().min(1).max(255).regex(/^[a-zA-Z0-9-]+$/, "niceId can only contain letters, numbers, and dashes").optional(),
|
niceId: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(255)
|
||||||
|
.regex(
|
||||||
|
/^[a-zA-Z0-9-]+$/,
|
||||||
|
"niceId can only contain letters, numbers, and dashes"
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
subdomain: subdomainSchema.nullable().optional(),
|
subdomain: subdomainSchema.nullable().optional(),
|
||||||
ssl: z.boolean().optional(),
|
ssl: z.boolean().optional(),
|
||||||
sso: z.boolean().optional(),
|
sso: z.boolean().optional(),
|
||||||
@@ -248,14 +256,13 @@ async function updateHttpResource(
|
|||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(resources.niceId, updateData.niceId),
|
eq(resources.niceId, updateData.niceId),
|
||||||
eq(resources.orgId, resource.orgId)
|
eq(resources.orgId, resource.orgId),
|
||||||
|
ne(resources.resourceId, resource.resourceId) // exclude the current resource from the search
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
if (
|
if (existingResource) {
|
||||||
existingResource &&
|
|
||||||
existingResource.resourceId !== resource.resourceId
|
|
||||||
) {
|
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.CONFLICT,
|
HttpCode.CONFLICT,
|
||||||
@@ -343,7 +350,10 @@ async function updateHttpResource(
|
|||||||
headers = null;
|
headers = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLicensed = await isLicensedOrSubscribed(resource.orgId, tierMatrix.maintencePage);
|
const isLicensed = await isLicensedOrSubscribed(
|
||||||
|
resource.orgId,
|
||||||
|
tierMatrix.maintencePage
|
||||||
|
);
|
||||||
if (!isLicensed) {
|
if (!isLicensed) {
|
||||||
updateData.maintenanceModeEnabled = undefined;
|
updateData.maintenanceModeEnabled = undefined;
|
||||||
updateData.maintenanceModeType = undefined;
|
updateData.maintenanceModeType = undefined;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from "express";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { sites } from "@server/db";
|
import { sites } from "@server/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and, ne } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
@@ -19,8 +19,8 @@ const updateSiteBodySchema = z
|
|||||||
.strictObject({
|
.strictObject({
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
niceId: z.string().min(1).max(255).optional(),
|
niceId: z.string().min(1).max(255).optional(),
|
||||||
dockerSocketEnabled: z.boolean().optional(),
|
dockerSocketEnabled: z.boolean().optional()
|
||||||
remoteSubnets: z.string().optional()
|
// remoteSubnets: z.string().optional()
|
||||||
// subdomain: z
|
// subdomain: z
|
||||||
// .string()
|
// .string()
|
||||||
// .min(1)
|
// .min(1)
|
||||||
@@ -86,18 +86,19 @@ export async function updateSite(
|
|||||||
|
|
||||||
// if niceId is provided, check if it's already in use by another site
|
// if niceId is provided, check if it's already in use by another site
|
||||||
if (updateData.niceId) {
|
if (updateData.niceId) {
|
||||||
const existingSite = await db
|
const [existingSite] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(sites.niceId, updateData.niceId),
|
eq(sites.niceId, updateData.niceId),
|
||||||
eq(sites.orgId, sites.orgId)
|
eq(sites.orgId, sites.orgId),
|
||||||
|
ne(sites.siteId, siteId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (existingSite.length > 0 && existingSite[0].siteId !== siteId) {
|
if (existingSite) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.CONFLICT,
|
HttpCode.CONFLICT,
|
||||||
@@ -107,22 +108,22 @@ export async function updateSite(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if remoteSubnets is provided, ensure it's a valid comma-separated list of cidrs
|
// // if remoteSubnets is provided, ensure it's a valid comma-separated list of cidrs
|
||||||
if (updateData.remoteSubnets) {
|
// if (updateData.remoteSubnets) {
|
||||||
const subnets = updateData.remoteSubnets
|
// const subnets = updateData.remoteSubnets
|
||||||
.split(",")
|
// .split(",")
|
||||||
.map((s) => s.trim());
|
// .map((s) => s.trim());
|
||||||
for (const subnet of subnets) {
|
// for (const subnet of subnets) {
|
||||||
if (!isValidCIDR(subnet)) {
|
// if (!isValidCIDR(subnet)) {
|
||||||
return next(
|
// return next(
|
||||||
createHttpError(
|
// createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
// HttpCode.BAD_REQUEST,
|
||||||
`Invalid CIDR format: ${subnet}`
|
// `Invalid CIDR format: ${subnet}`
|
||||||
)
|
// )
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
const updatedSite = await db
|
const updatedSite = await db
|
||||||
.update(sites)
|
.update(sites)
|
||||||
|
|||||||
Reference in New Issue
Block a user