mirror of
https://github.com/fosrl/pangolin.git
synced 2025-12-16 04:57:52 +00:00
Rules, client resources working
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user