Skip to content

Commit a512148

Browse files
authored
Merge pull request #701 from fosrl/dev
1.3.2
2 parents 21f1326 + d9eccd6 commit a512148

File tree

11 files changed

+207
-67
lines changed

11 files changed

+207
-67
lines changed

install/main.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@ func main() {
6464
}
6565

6666
var config Config
67-
config.DoCrowdsecInstall = false
68-
config.Secret = generateRandomSecretKey()
69-
67+
7068
// check if there is already a config file
7169
if _, err := os.Stat("config/config.yml"); err != nil {
7270
config = collectUserInput(reader)
73-
71+
7472
loadVersions(&config)
75-
73+
config.DoCrowdsecInstall = false
74+
config.Secret = generateRandomSecretKey()
75+
7676
if err := createConfigFiles(config); err != nil {
7777
fmt.Printf("Error creating config files: %v\n", err)
7878
os.Exit(1)

server/lib/config.ts

Lines changed: 97 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ const configSchema = z.object({
2929
.optional()
3030
.pipe(z.string().url())
3131
.transform((url) => url.toLowerCase()),
32-
log_level: z.enum(["debug", "info", "warn", "error"]),
33-
save_logs: z.boolean(),
34-
log_failed_attempts: z.boolean().optional()
32+
log_level: z
33+
.enum(["debug", "info", "warn", "error"])
34+
.optional()
35+
.default("info"),
36+
save_logs: z.boolean().optional().default(false),
37+
log_failed_attempts: z.boolean().optional().default(false)
3538
}),
3639
domains: z
3740
.record(
@@ -41,8 +44,8 @@ const configSchema = z.object({
4144
.string()
4245
.nonempty("base_domain must not be empty")
4346
.transform((url) => url.toLowerCase()),
44-
cert_resolver: z.string().optional(),
45-
prefer_wildcard_cert: z.boolean().optional()
47+
cert_resolver: z.string().optional().default("letsencrypt"),
48+
prefer_wildcard_cert: z.boolean().optional().default(false)
4649
})
4750
)
4851
.refine(
@@ -62,19 +65,42 @@ const configSchema = z.object({
6265
server: z.object({
6366
integration_port: portSchema
6467
.optional()
68+
.default(3003)
6569
.transform(stoi)
6670
.pipe(portSchema.optional()),
67-
external_port: portSchema.optional().transform(stoi).pipe(portSchema),
68-
internal_port: portSchema.optional().transform(stoi).pipe(portSchema),
69-
next_port: portSchema.optional().transform(stoi).pipe(portSchema),
70-
internal_hostname: z.string().transform((url) => url.toLowerCase()),
71-
session_cookie_name: z.string(),
72-
resource_access_token_param: z.string(),
73-
resource_access_token_headers: z.object({
74-
id: z.string(),
75-
token: z.string()
76-
}),
77-
resource_session_request_param: z.string(),
71+
external_port: portSchema
72+
.optional()
73+
.default(3000)
74+
.transform(stoi)
75+
.pipe(portSchema),
76+
internal_port: portSchema
77+
.optional()
78+
.default(3001)
79+
.transform(stoi)
80+
.pipe(portSchema),
81+
next_port: portSchema
82+
.optional()
83+
.default(3002)
84+
.transform(stoi)
85+
.pipe(portSchema),
86+
internal_hostname: z
87+
.string()
88+
.optional()
89+
.default("pangolin")
90+
.transform((url) => url.toLowerCase()),
91+
session_cookie_name: z.string().optional().default("p_session_token"),
92+
resource_access_token_param: z.string().optional().default("p_token"),
93+
resource_access_token_headers: z
94+
.object({
95+
id: z.string().optional().default("P-Access-Token-Id"),
96+
token: z.string().optional().default("P-Access-Token")
97+
})
98+
.optional()
99+
.default({}),
100+
resource_session_request_param: z
101+
.string()
102+
.optional()
103+
.default("resource_session_request_param"),
78104
dashboard_session_length_hours: z
79105
.number()
80106
.positive()
@@ -102,35 +128,61 @@ const configSchema = z.object({
102128
.transform(getEnvOrYaml("SERVER_SECRET"))
103129
.pipe(z.string().min(8))
104130
}),
105-
traefik: z.object({
106-
http_entrypoint: z.string(),
107-
https_entrypoint: z.string().optional(),
108-
additional_middlewares: z.array(z.string()).optional()
109-
}),
110-
gerbil: z.object({
111-
start_port: portSchema.optional().transform(stoi).pipe(portSchema),
112-
base_endpoint: z
113-
.string()
114-
.optional()
115-
.pipe(z.string())
116-
.transform((url) => url.toLowerCase()),
117-
use_subdomain: z.boolean(),
118-
subnet_group: z.string(),
119-
block_size: z.number().positive().gt(0),
120-
site_block_size: z.number().positive().gt(0)
121-
}),
122-
rate_limits: z.object({
123-
global: z.object({
124-
window_minutes: z.number().positive().gt(0),
125-
max_requests: z.number().positive().gt(0)
126-
}),
127-
auth: z
128-
.object({
129-
window_minutes: z.number().positive().gt(0),
130-
max_requests: z.number().positive().gt(0)
131-
})
132-
.optional()
133-
}),
131+
traefik: z
132+
.object({
133+
http_entrypoint: z.string().optional().default("web"),
134+
https_entrypoint: z.string().optional().default("websecure"),
135+
additional_middlewares: z.array(z.string()).optional()
136+
})
137+
.optional()
138+
.default({}),
139+
gerbil: z
140+
.object({
141+
start_port: portSchema
142+
.optional()
143+
.default(51820)
144+
.transform(stoi)
145+
.pipe(portSchema),
146+
base_endpoint: z
147+
.string()
148+
.optional()
149+
.pipe(z.string())
150+
.transform((url) => url.toLowerCase()),
151+
use_subdomain: z.boolean().optional().default(false),
152+
subnet_group: z.string().optional().default("100.89.137.0/20"),
153+
block_size: z.number().positive().gt(0).optional().default(24),
154+
site_block_size: z.number().positive().gt(0).optional().default(30)
155+
})
156+
.optional()
157+
.default({}),
158+
rate_limits: z
159+
.object({
160+
global: z
161+
.object({
162+
window_minutes: z
163+
.number()
164+
.positive()
165+
.gt(0)
166+
.optional()
167+
.default(1),
168+
max_requests: z
169+
.number()
170+
.positive()
171+
.gt(0)
172+
.optional()
173+
.default(500)
174+
})
175+
.optional()
176+
.default({}),
177+
auth: z
178+
.object({
179+
window_minutes: z.number().positive().gt(0),
180+
max_requests: z.number().positive().gt(0)
181+
})
182+
.optional()
183+
})
184+
.optional()
185+
.default({}),
134186
email: z
135187
.object({
136188
smtp_host: z.string().optional(),

server/lib/consts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import path from "path";
22
import { fileURLToPath } from "url";
33

44
// This is a placeholder value replaced by the build process
5-
export const APP_VERSION = "1.3.0";
5+
export const APP_VERSION = "1.3.2";
66

77
export const __FILENAME = fileURLToPath(import.meta.url);
88
export const __DIRNAME = path.dirname(__FILENAME);

server/lib/validators.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ export function isValidIP(ip: string): boolean {
99
}
1010

1111
export function isValidUrlGlobPattern(pattern: string): boolean {
12+
if (pattern === "/") {
13+
return true;
14+
}
15+
1216
// Remove leading slash if present
1317
pattern = pattern.startsWith("/") ? pattern.slice(1) : pattern;
1418

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,136 @@
1-
import { isPathAllowed } from './verifySession';
21
import { assertEquals } from '@test/assert';
32

3+
function isPathAllowed(pattern: string, path: string): boolean {
4+
5+
// Normalize and split paths into segments
6+
const normalize = (p: string) => p.split("/").filter(Boolean);
7+
const patternParts = normalize(pattern);
8+
const pathParts = normalize(path);
9+
10+
11+
// Recursive function to try different wildcard matches
12+
function matchSegments(patternIndex: number, pathIndex: number): boolean {
13+
const indent = " ".repeat(pathIndex); // Indent based on recursion depth
14+
const currentPatternPart = patternParts[patternIndex];
15+
const currentPathPart = pathParts[pathIndex];
16+
17+
// If we've consumed all pattern parts, we should have consumed all path parts
18+
if (patternIndex >= patternParts.length) {
19+
const result = pathIndex >= pathParts.length;
20+
return result;
21+
}
22+
23+
// If we've consumed all path parts but still have pattern parts
24+
if (pathIndex >= pathParts.length) {
25+
// The only way this can match is if all remaining pattern parts are wildcards
26+
const remainingPattern = patternParts.slice(patternIndex);
27+
const result = remainingPattern.every((p) => p === "*");
28+
return result;
29+
}
30+
31+
// For full segment wildcards, try consuming different numbers of path segments
32+
if (currentPatternPart === "*") {
33+
34+
// Try consuming 0 segments (skip the wildcard)
35+
if (matchSegments(patternIndex + 1, pathIndex)) {
36+
return true;
37+
}
38+
39+
// Try consuming current segment and recursively try rest
40+
if (matchSegments(patternIndex, pathIndex + 1)) {
41+
return true;
42+
}
43+
44+
return false;
45+
}
46+
47+
// Check for in-segment wildcard (e.g., "prefix*" or "prefix*suffix")
48+
if (currentPatternPart.includes("*")) {
49+
// Convert the pattern segment to a regex pattern
50+
const regexPattern = currentPatternPart
51+
.replace(/\*/g, ".*") // Replace * with .* for regex wildcard
52+
.replace(/\?/g, "."); // Replace ? with . for single character wildcard if needed
53+
54+
const regex = new RegExp(`^${regexPattern}$`);
55+
56+
if (regex.test(currentPathPart)) {
57+
return matchSegments(patternIndex + 1, pathIndex + 1);
58+
}
59+
60+
return false;
61+
}
62+
63+
// For regular segments, they must match exactly
64+
if (currentPatternPart !== currentPathPart) {
65+
return false;
66+
}
67+
68+
// Move to next segments in both pattern and path
69+
return matchSegments(patternIndex + 1, pathIndex + 1);
70+
}
71+
72+
const result = matchSegments(0, 0);
73+
return result;
74+
}
75+
476
function runTests() {
577
console.log('Running path matching tests...');
6-
78+
779
// Test exact matching
880
assertEquals(isPathAllowed('foo', 'foo'), true, 'Exact match should be allowed');
981
assertEquals(isPathAllowed('foo', 'bar'), false, 'Different segments should not match');
1082
assertEquals(isPathAllowed('foo/bar', 'foo/bar'), true, 'Exact multi-segment match should be allowed');
1183
assertEquals(isPathAllowed('foo/bar', 'foo/baz'), false, 'Partial multi-segment match should not be allowed');
12-
84+
1385
// Test with leading and trailing slashes
1486
assertEquals(isPathAllowed('/foo', 'foo'), true, 'Pattern with leading slash should match');
1587
assertEquals(isPathAllowed('foo/', 'foo'), true, 'Pattern with trailing slash should match');
1688
assertEquals(isPathAllowed('/foo/', 'foo'), true, 'Pattern with both leading and trailing slashes should match');
1789
assertEquals(isPathAllowed('foo', '/foo/'), true, 'Path with leading and trailing slashes should match');
18-
90+
1991
// Test simple wildcard matching
2092
assertEquals(isPathAllowed('*', 'foo'), true, 'Single wildcard should match any single segment');
2193
assertEquals(isPathAllowed('*', 'foo/bar'), true, 'Single wildcard should match multiple segments');
2294
assertEquals(isPathAllowed('*/bar', 'foo/bar'), true, 'Wildcard prefix should match');
2395
assertEquals(isPathAllowed('foo/*', 'foo/bar'), true, 'Wildcard suffix should match');
2496
assertEquals(isPathAllowed('foo/*/baz', 'foo/bar/baz'), true, 'Wildcard in middle should match');
25-
97+
2698
// Test multiple wildcards
2799
assertEquals(isPathAllowed('*/*', 'foo/bar'), true, 'Multiple wildcards should match corresponding segments');
28100
assertEquals(isPathAllowed('*/*/*', 'foo/bar/baz'), true, 'Three wildcards should match three segments');
29101
assertEquals(isPathAllowed('foo/*/*', 'foo/bar/baz'), true, 'Specific prefix with wildcards should match');
30102
assertEquals(isPathAllowed('*/*/baz', 'foo/bar/baz'), true, 'Wildcards with specific suffix should match');
31-
103+
32104
// Test wildcard consumption behavior
33105
assertEquals(isPathAllowed('*', ''), true, 'Wildcard should optionally consume segments');
34106
assertEquals(isPathAllowed('foo/*', 'foo'), true, 'Trailing wildcard should be optional');
35107
assertEquals(isPathAllowed('*/*', 'foo'), true, 'Multiple wildcards can match fewer segments');
36108
assertEquals(isPathAllowed('*/*/*', 'foo/bar'), true, 'Extra wildcards can be skipped');
37-
109+
38110
// Test complex nested paths
39111
assertEquals(isPathAllowed('api/*/users', 'api/v1/users'), true, 'API versioning pattern should match');
40112
assertEquals(isPathAllowed('api/*/users/*', 'api/v1/users/123'), true, 'API resource pattern should match');
41113
assertEquals(isPathAllowed('api/*/users/*/profile', 'api/v1/users/123/profile'), true, 'Nested API pattern should match');
42-
114+
43115
// Test for the requested padbootstrap* pattern
44116
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap'), true, 'padbootstrap* should match padbootstrap');
45117
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrapv1'), true, 'padbootstrap* should match padbootstrapv1');
46118
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap/files'), false, 'padbootstrap* should not match padbootstrap/files');
47119
assertEquals(isPathAllowed('padbootstrap*/*', 'padbootstrap/files'), true, 'padbootstrap*/* should match padbootstrap/files');
48120
assertEquals(isPathAllowed('padbootstrap*/files', 'padbootstrapv1/files'), true, 'padbootstrap*/files should not match padbootstrapv1/files (wildcard is segment-based, not partial)');
49-
121+
50122
// Test wildcard edge cases
51123
assertEquals(isPathAllowed('*/*/*/*/*/*', 'a/b'), true, 'Many wildcards can match few segments');
52124
assertEquals(isPathAllowed('a/*/b/*/c', 'a/anything/b/something/c'), true, 'Multiple wildcards in pattern should match corresponding segments');
53-
125+
54126
// Test patterns with partial segment matches
55127
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap-123'), true, 'Wildcards in isPathAllowed should be segment-based, not character-based');
56128
assertEquals(isPathAllowed('test*', 'testuser'), true, 'Asterisk as part of segment name is treated as a literal, not a wildcard');
57129
assertEquals(isPathAllowed('my*app', 'myapp'), true, 'Asterisk in middle of segment name is treated as a literal, not a wildcard');
58130

131+
assertEquals(isPathAllowed('/', '/'), true, 'Root path should match root path');
132+
assertEquals(isPathAllowed('/', '/test'), false, 'Root path should not match non-root path');
133+
59134
console.log('All tests passed!');
60135
}
61136

@@ -64,4 +139,4 @@ try {
64139
runTests();
65140
} catch (error) {
66141
console.error('Test failed:', error);
67-
}
142+
}

server/routers/idp/validateOidcCallback.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,9 @@ export async function validateOidcCallback(
160160
);
161161

162162
const idToken = tokens.idToken();
163+
logger.debug("ID token", { idToken });
163164
const claims = arctic.decodeIdToken(idToken);
165+
logger.debug("ID token claims", { claims });
164166

165167
const userIdentifier = jmespath.search(
166168
claims,

server/routers/resource/updateResource.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,8 @@ async function updateHttpResource(
318318
domainId: updatePayload.domainId,
319319
enabled: updatePayload.enabled,
320320
stickySession: updatePayload.stickySession,
321-
tlsServerName: updatePayload.tlsServerName || null,
322-
setHostHeader: updatePayload.setHostHeader || null,
321+
tlsServerName: updatePayload.tlsServerName,
322+
setHostHeader: updatePayload.setHostHeader,
323323
fullDomain: updatePayload.fullDomain
324324
})
325325
.where(eq(resources.resourceId, resource.resourceId))

0 commit comments

Comments
 (0)