diff --git a/blueprint.py b/blueprint.py index b3017b5b..b4f1a99c 100644 --- a/blueprint.py +++ b/blueprint.py @@ -31,6 +31,8 @@ def convert_and_send(file_path, url, headers): # convert the parsed YAML to a JSON string json_payload = json.dumps(parsed_yaml) + print("Converted JSON payload:") + print(json_payload) # Encode the JSON string to Base64 encoded_json = base64.b64encode(json_payload.encode('utf-8')).decode('utf-8') diff --git a/blueprint.yaml b/blueprint.yaml index bb6cd531..8a0d208f 100644 --- a/blueprint.yaml +++ b/blueprint.yaml @@ -5,16 +5,19 @@ resources: full-domain: level1.test3.example.com host-header: example.com tls-server-name: example.com - auth: - pincode: 123456 - password: sadfasdfadsf - sso-enabled: true - sso-roles: - - Member - sso-users: - - owen@fossorial.io - whitelist-users: - - owen@fossorial.io + # auth: + # pincode: 123456 + # password: sadfasdfadsf + # sso-enabled: true + # sso-roles: + # - Member + # sso-users: + # - owen@fossorial.io + # whitelist-users: + # - owen@fossorial.io + headers: + - X-Example-Header: example-value + - X-Another-Header: another-value targets: - site: lively-yosemite-toad hostname: localhost @@ -24,15 +27,11 @@ resources: hostname: localhost method: http port: 8001 - - site: glossy-plains-viscacha-rat - hostname: localhost - method: http - port: 8001 resource-nice-id2: name: this is other resource protocol: tcp proxy-port: 3000 targets: - - site: glossy-plains-viscacha-rat + - site: lively-yosemite-toad hostname: localhost port: 3000 \ No newline at end of file diff --git a/server/db/pg/schema.ts b/server/db/pg/schema.ts index 1e634cc9..8c6fb4b8 100644 --- a/server/db/pg/schema.ts +++ b/server/db/pg/schema.ts @@ -96,6 +96,7 @@ export const resources = pgTable("resources", { skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, { onDelete: "cascade" }), + headers: text("headers"), // comma-separated list of headers to add to the request }); export const targets = pgTable("targets", { diff --git a/server/db/sqlite/schema.ts b/server/db/sqlite/schema.ts index 4db6fd7a..ce28e772 100644 --- a/server/db/sqlite/schema.ts +++ b/server/db/sqlite/schema.ts @@ -107,7 +107,8 @@ export const resources = sqliteTable("resources", { enableProxy: integer("enableProxy", { mode: "boolean" }).default(true), skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, { onDelete: "cascade" - }) + }), + headers: text("headers"), // comma-separated list of headers to add to the request }); export const targets = sqliteTable("targets", { diff --git a/server/lib/blueprints/resources.ts b/server/lib/blueprints/resources.ts index 8499156e..1ca928ef 100644 --- a/server/lib/blueprints/resources.ts +++ b/server/lib/blueprints/resources.ts @@ -120,6 +120,17 @@ export async function updateResources( const http = resourceData.protocol == "http"; const protocol = resourceData.protocol == "http" ? "tcp" : resourceData.protocol; + const resourceEnabled = resourceData.enabled == undefined || resourceData.enabled == null ? true : resourceData.enabled; + let headers = ""; + for (const headerObj of resourceData.headers || []) { + for (const [key, value] of Object.entries(headerObj)) { + headers += `${key}: ${value},`; + } + } + // if there are headers, remove the trailing comma + if (headers.endsWith(",")) { + headers = headers.slice(0, -1); + } if (existingResource) { let domain; @@ -152,7 +163,7 @@ export async function updateResources( fullDomain: http ? resourceData["full-domain"] : null, subdomain: domain ? domain.subdomain : null, domainId: domain ? domain.domainId : null, - enabled: resourceData.enabled ? true : false, + enabled: resourceEnabled, sso: resourceData.auth?.["sso-enabled"] || false, ssl: resourceData.ssl ? true : false, setHostHeader: resourceData["host-header"] || null, @@ -161,7 +172,8 @@ export async function updateResources( "whitelist-users" ] ? resourceData.auth["whitelist-users"].length > 0 - : false + : false, + headers: headers || null, }) .where( eq(resources.resourceId, existingResource.resourceId) @@ -379,11 +391,12 @@ export async function updateResources( fullDomain: http ? resourceData["full-domain"] : null, subdomain: domain ? domain.subdomain : null, domainId: domain ? domain.domainId : null, - enabled: resourceData.enabled ? true : false, + enabled: resourceEnabled, sso: resourceData.auth?.["sso-enabled"] || false, setHostHeader: resourceData["host-header"] || null, tlsServerName: resourceData["tls-server-name"] || null, - ssl: resourceData.ssl ? true : false + ssl: resourceData.ssl ? true : false, + headers: headers || null }) .returning(); diff --git a/server/lib/blueprints/types.ts b/server/lib/blueprints/types.ts index 62e390a8..7dd85e12 100644 --- a/server/lib/blueprints/types.ts +++ b/server/lib/blueprints/types.ts @@ -44,7 +44,8 @@ export const ResourceSchema = z targets: z.array(TargetSchema.nullable()).optional().default([]), auth: AuthSchema.optional(), "host-header": z.string().optional(), - "tls-server-name": z.string().optional() + "tls-server-name": z.string().optional(), + headers: z.array(z.record(z.string(), z.string())).optional().default([]), }) .refine( (resource) => { diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index 1a55f2bd..957dab17 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -54,7 +54,8 @@ export async function traefikConfigProvider( config.getRawConfig().traefik.site_types ); - if (traefikConfig?.http?.middlewares) { // BECAUSE SOMETIMES THE CONFIG CAN BE EMPTY IF THERE IS NOTHING + if (traefikConfig?.http?.middlewares) { + // BECAUSE SOMETIMES THE CONFIG CAN BE EMPTY IF THERE IS NOTHING traefikConfig.http.middlewares[badgerMiddlewareName] = { plugin: { [badgerMiddlewareName]: { @@ -124,6 +125,7 @@ export async function getTraefikConfig( tlsServerName: resources.tlsServerName, setHostHeader: resources.setHostHeader, enableProxy: resources.enableProxy, + headers: resources.headers, // Target fields targetId: targets.targetId, targetEnabled: targets.enabled, @@ -152,7 +154,7 @@ export async function getTraefikConfig( inArray(sites.type, siteTypes), config.getRawConfig().traefik.allow_raw_resources ? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true - : eq(resources.http, true), + : eq(resources.http, true) ) ); @@ -177,7 +179,8 @@ export async function getTraefikConfig( tlsServerName: row.tlsServerName, setHostHeader: row.setHostHeader, enableProxy: row.enableProxy, - targets: [] + targets: [], + headers: row.headers }); } @@ -296,13 +299,52 @@ export async function getTraefikConfig( const additionalMiddlewares = config.getRawConfig().traefik.additional_middlewares || []; + let routerMiddlewares = [ + badgerMiddlewareName, + ...additionalMiddlewares + ]; + + if (resource.headers && resource.headers.length > 0) { + const headersMiddlewareName = `${resource.resourceId}-headers-middleware`; + // if there are headers, parse them into an object + let headersObj: { [key: string]: string } = {}; + const headersArr = resource.headers.split(","); + for (const header of headersArr) { + const [key, value] = header + .split(":") + .map((s: string) => s.trim()); + if (key && value) { + headersObj[key] = value; + } + } + + if (resource.setHostHeader) { + headersObj["Host"] = resource.setHostHeader; + } + + // check if the object is not empty + if (Object.keys(headersObj).length > 0) { + // Add the headers middleware + if (!config_output.http.middlewares) { + config_output.http.middlewares = {}; + } + config_output.http.middlewares[headersMiddlewareName] = { + headers: { + customRequestHeaders: headersObj + } + }; + + routerMiddlewares.push(headersMiddlewareName); + } + } + config_output.http.routers![routerName] = { entryPoints: [ resource.ssl ? config.getRawConfig().traefik.https_entrypoint : config.getRawConfig().traefik.http_entrypoint ], - middlewares: [badgerMiddlewareName, ...additionalMiddlewares], + middlewares: routerMiddlewares, service: serviceName, rule: `Host(\`${fullDomain}\`)`, priority: 100, @@ -413,27 +455,6 @@ export async function getTraefikConfig( serviceName ].loadBalancer.serversTransport = transportName; } - - // Add the host header middleware - if (resource.setHostHeader) { - if (!config_output.http.middlewares) { - config_output.http.middlewares = {}; - } - config_output.http.middlewares[hostHeaderMiddlewareName] = { - headers: { - customRequestHeaders: { - Host: resource.setHostHeader - } - } - }; - if (!config_output.http.routers![routerName].middlewares) { - config_output.http.routers![routerName].middlewares = []; - } - config_output.http.routers![routerName].middlewares = [ - ...config_output.http.routers![routerName].middlewares, - hostHeaderMiddlewareName - ]; - } } else { // Non-HTTP (TCP/UDP) configuration if (!resource.enableProxy) {