Rules, client resources working

This commit is contained in:
Owen
2025-09-14 17:27:21 -07:00
parent 58c04fd196
commit eea0b86d6d
4 changed files with 188 additions and 121 deletions

View File

@@ -1,8 +1,24 @@
resources:
resource-nice-id-duce:
client-resources:
client-resource-nice-id-uno:
name: this is my resource
protocol: tcp
proxy-port: 3001
hostname: localhost
internal-port: 3000
site: lively-yosemite-toad
client-resource-nice-id-duce:
name: this is my resource
protocol: udp
proxy-port: 3000
hostname: localhost
internal-port: 3000
site: lively-yosemite-toad
proxy-resources:
resource-nice-id-uno:
name: this is my resource
protocol: http
full-domain: level1.test3.example.com
full-domain: duce.test.example.com
host-header: example.com
tls-server-name: example.com
# auth:
@@ -18,6 +34,16 @@ resources:
headers:
- X-Example-Header: example-value
- X-Another-Header: another-value
rules:
- action: allow
match: ip
value: 1.1.1.1
- action: deny
match: cidr
value: 2.2.2.2/32
- action: pass
match: path
value: /admin
targets:
- site: lively-yosemite-toad
path: /path
@@ -31,7 +57,7 @@ resources:
pathMatchType: exact
method: http
port: 8001
resource-nice-id2:
resource-nice-id-duce:
name: this is other resource
protocol: tcp
proxy-port: 3000

View File

@@ -52,10 +52,12 @@ function getContainerPort(container: Container): number | null {
}
export function processContainerLabels(containers: Container[]): {
resources: { [key: string]: ResourceConfig };
"proxy-resources": { [key: string]: ResourceConfig };
"client-resources": { [key: string]: ResourceConfig };
} {
const result: { resources: { [key: string]: ResourceConfig } } = {
resources: {}
const result = {
"proxy-resources": {} as { [key: string]: ResourceConfig },
"client-resources": {} as { [key: string]: ResourceConfig }
};
// Process each container
@@ -64,43 +66,61 @@ export function processContainerLabels(containers: Container[]): {
return;
}
const resourceLabels: DockerLabels = {};
const proxyResourceLabels: DockerLabels = {};
const clientResourceLabels: DockerLabels = {};
// Filter labels that start with "pangolin.proxy-resources."
// Filter and separate proxy-resources and client-resources labels
Object.entries(container.labels).forEach(([key, value]) => {
if (key.startsWith("pangolin.proxy-resources.") || key.startsWith("pangolin.client-resources.")) {
// remove the pangolin. prefix
const strippedKey = key.replace("pangolin.", "");
resourceLabels[strippedKey] = value;
if (key.startsWith("pangolin.proxy-resources.")) {
// remove the pangolin.proxy- prefix to get "resources.xxx"
const strippedKey = key.replace("pangolin.proxy-", "");
proxyResourceLabels[strippedKey] = value;
} else if (key.startsWith("pangolin.client-resources.")) {
// remove the pangolin.client- prefix to get "resources.xxx"
const strippedKey = key.replace("pangolin.client-", "");
clientResourceLabels[strippedKey] = value;
}
});
// Skip containers with no resource labels
if (Object.keys(resourceLabels).length === 0) {
return;
// Process proxy resources
if (Object.keys(proxyResourceLabels).length > 0) {
processResourceLabels(proxyResourceLabels, container, result["proxy-resources"]);
}
// Process client resources
if (Object.keys(clientResourceLabels).length > 0) {
processResourceLabels(clientResourceLabels, container, result["client-resources"]);
}
});
return result;
}
function processResourceLabels(
resourceLabels: DockerLabels,
container: Container,
targetResult: { [key: string]: ResourceConfig }
) {
// Parse the labels using the existing parseDockerLabels logic
const tempResult: ParsedObject = {};
Object.entries(resourceLabels).forEach(([key, value]) => {
setNestedProperty(tempResult, key, value);
});
// Merge into main result
// Merge into target result
if (tempResult.resources) {
Object.entries(tempResult.resources).forEach(
([resourceKey, resourceConfig]: [string, any]) => {
// Initialize resource if it doesn't exist
if (!result.resources[resourceKey]) {
result.resources[resourceKey] = {};
if (!targetResult[resourceKey]) {
targetResult[resourceKey] = {};
}
// Merge all properties except targets
Object.entries(resourceConfig).forEach(
([propKey, propValue]) => {
if (propKey !== "targets") {
result.resources[resourceKey][propKey] =
propValue;
targetResult[resourceKey][propKey] = propValue;
}
}
);
@@ -110,7 +130,7 @@ export function processContainerLabels(containers: Container[]): {
resourceConfig.targets &&
Array.isArray(resourceConfig.targets)
) {
const resource = result.resources[resourceKey];
const resource = targetResult[resourceKey];
if (resource) {
if (!resource.targets) {
resource.targets = [];
@@ -164,9 +184,6 @@ export function processContainerLabels(containers: Container[]): {
}
);
}
});
return result;
}
// // Test example

View File

@@ -404,21 +404,31 @@ export async function updateProxyResources(
const existingRule = existingRules[index];
if (existingRule) {
if (
existingRule.action !== rule.action ||
existingRule.match !== rule.match ||
existingRule.action !== getRuleAction(rule.action) ||
existingRule.match !== rule.match.toUpperCase() ||
existingRule.value !== rule.value
) {
validateRule(rule);
await trx
.update(resourceRules)
.set({
action: rule.action,
match: rule.match,
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value
})
.where(
eq(resourceRules.ruleId, existingRule.ruleId)
);
}
} else {
validateRule(rule);
await trx.insert(resourceRules).values({
resourceId: existingResource.resourceId,
action: rule.action.toUpperCase(),
match: rule.match.toUpperCase(),
value: rule.value,
priority: index + 1 // start priorities at 1
});
}
}
@@ -550,25 +560,11 @@ export async function updateProxyResources(
}
for (const [index, rule] of resourceData.rules?.entries() || []) {
if (rule.match === "cidr") {
if (!isValidCIDR(rule.value)) {
throw new Error(`Invalid CIDR provided: ${rule.value}`);
}
} else if (rule.match === "ip") {
if (!isValidIP(rule.value)) {
throw new Error(`Invalid IP provided: ${rule.value}`);
}
} else if (rule.match === "path") {
if (!isValidUrlGlobPattern(rule.value)) {
throw new Error(
`Invalid URL glob pattern: ${rule.value}`
);
}
}
validateRule(rule);
await trx.insert(resourceRules).values({
resourceId: newResource.resourceId,
action: rule.action,
match: rule.match,
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value,
priority: index + 1 // start priorities at 1
});
@@ -586,6 +582,34 @@ export async function updateProxyResources(
return results;
}
function getRuleAction(input: string) {
let action = "DROP";
if (input == "allow") {
action = "ACCEPT";
} else if (input == "deny") {
action = "DROP";
} else if (input == "pass") {
action = "PASS";
}
return action;
}
function validateRule(rule: any) {
if (rule.match === "cidr") {
if (!isValidCIDR(rule.value)) {
throw new Error(`Invalid CIDR provided: ${rule.value}`);
}
} else if (rule.match === "ip") {
if (!isValidIP(rule.value)) {
throw new Error(`Invalid IP provided: ${rule.value}`);
}
} else if (rule.match === "path") {
if (!isValidUrlGlobPattern(rule.value)) {
throw new Error(`Invalid URL glob pattern: ${rule.value}`);
}
}
}
async function syncRoleResources(
resourceId: number,
ssoRoles: string[],

View File

@@ -173,7 +173,7 @@ export function isTargetsOnlyResource(resource: any): boolean {
export const ClientResourceSchema = z.object({
name: z.string().min(2).max(100),
site: z.string().min(2).max(100),
site: z.string().min(2).max(100).optional(),
protocol: z.enum(["tcp", "udp"]),
"proxy-port": z.number().min(1).max(65535),
"hostname": z.string().min(1).max(255),