Skip to content

Commit 543f7e6

Browse files
refactor rules engine for key value structure with spec compliance (#61)
* add http token parsing * add parsing hosts * parse path * update names to reflect we're pattern parsing * wildcard tests * implement top level matching * don't reverse host pattern while parsing * use the logger * update proxy server tests to match new syntax * remove trailing * support for hostname * update usage in error messages * update cli examples * adding curl test to debug separate issue in ci * Revert "adding curl test to debug separate issue in ci" This reverts commit f743e6a. * update e2e test to use key/value syntax * remove superfluous interface from old impl * split engine and rules code into separate files to make easier to read through * mutate rest in parse allow rule to make pattern clearer * mutate rest throughout rules * don't allow comma separated rules * remove custom parsed types in favor of stringly typed api * adding e2e tests on allow rules progress * update parsing to allow multiple paths in rule * add multiple subdomains test * update e2e tests * whoops clean * update error message in proxy * fix url parsing when scheme missing * test: add extra tests * refactor: avoid unnecessary recursion * test: add extra tests --------- Co-authored-by: YEVHENII SHCHERBINA <[email protected]>
1 parent f21e1f4 commit 543f7e6

File tree

14 files changed

+2434
-264
lines changed

14 files changed

+2434
-264
lines changed

README.md

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ curl -fsSL https://raw.githubusercontent.com/coder/boundary/main/install.sh | ba
2525

2626
```bash
2727
# Allow only requests to github.com
28-
boundary --allow "github.com" -- curl https://github.com
28+
boundary --allow "domain=github.com" -- curl https://github.com
2929

3030
# Allow full access to GitHub issues API, but only GET/HEAD elsewhere on GitHub
3131
boundary \
32-
--allow "github.com/api/issues/*" \
33-
--allow "GET,HEAD github.com" \
32+
--allow "domain=github.com path=/api/issues/*" \
33+
--allow "method=GET,HEAD domain=github.com" \
3434
-- npm install
3535

3636
# Default deny-all: everything is blocked unless explicitly allowed
@@ -41,25 +41,30 @@ boundary -- curl https://example.com
4141

4242
### Format
4343
```text
44-
--allow "pattern" # All HTTP methods allowed
45-
--allow "METHOD[,METHOD] pattern" # Specific methods only
44+
--allow "key=value [key=value ...]"
4645
```
4746

47+
**Keys:**
48+
- `method` - HTTP method(s), comma-separated (GET, POST, etc.)
49+
- `domain` - Domain/hostname pattern
50+
- `path` - URL path pattern(s), comma-separated
51+
4852
### Examples
4953
```bash
50-
boundary --allow "github.com" -- git pull
51-
boundary --allow "*.github.com" -- npm install # GitHub subdomains
52-
boundary --allow "api.*" -- ./app # Any API domain
53-
boundary --allow "GET,HEAD api.github.com" -- curl https://api.github.com
54+
boundary --allow "domain=github.com" -- git pull
55+
boundary --allow "domain=*.github.com" -- npm install # GitHub subdomains
56+
boundary --allow "method=GET,HEAD domain=api.github.com" -- curl https://api.github.com
57+
boundary --allow "method=POST domain=api.example.com path=/users,/posts" -- ./app # Multiple paths
58+
boundary --allow "path=/api/v1/*,/api/v2/*" -- curl https://api.example.com/api/v1/users
5459
```
5560

5661
Wildcards: `*` matches any characters. All traffic is denied unless explicitly allowed.
5762

5863
## Logging
5964

6065
```bash
61-
boundary --log-level info --allow "*" -- npm install # Show all requests
62-
boundary --log-level debug --allow "github.com" -- git pull # Debug info
66+
boundary --log-level info --allow "method=*" -- npm install # Show all requests
67+
boundary --log-level debug --allow "domain=github.com" -- git pull # Debug info
6368
```
6469

6570
**Log Levels:** `error`, `warn` (default), `info`, `debug`
@@ -70,10 +75,10 @@ When you can't or don't want to run with sudo privileges, use `--unprivileged`:
7075

7176
```bash
7277
# Run without network isolation (uses HTTP_PROXY/HTTPS_PROXY environment variables)
73-
boundary --unprivileged --allow "github.com" -- npm install
78+
boundary --unprivileged --allow "domain=github.com" -- npm install
7479

7580
# Useful in containers or restricted environments
76-
boundary --unprivileged --allow "*.npmjs.org" --allow "registry.npmjs.org" -- npm install
81+
boundary --unprivileged --allow "domain=*.npmjs.org" --allow "domain=registry.npmjs.org" -- npm install
7782
```
7883

7984
**Unprivileged Mode:**

boundary.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ import (
1111
"github.com/coder/boundary/audit"
1212
"github.com/coder/boundary/jail"
1313
"github.com/coder/boundary/proxy"
14-
"github.com/coder/boundary/rules"
14+
"github.com/coder/boundary/rulesengine"
1515
)
1616

1717
type Config struct {
18-
RuleEngine rules.Evaluator
19-
Auditor audit.Auditor
20-
TLSConfig *tls.Config
21-
Logger *slog.Logger
22-
Jailer jail.Jailer
23-
ProxyPort int
18+
RuleEngine rulesengine.Engine
19+
Auditor audit.Auditor
20+
TLSConfig *tls.Config
21+
Logger *slog.Logger
22+
Jailer jail.Jailer
23+
ProxyPort int
2424
PprofEnabled bool
2525
PprofPort int
2626
}

cli/cli.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/coder/boundary"
1717
"github.com/coder/boundary/audit"
1818
"github.com/coder/boundary/jail"
19-
"github.com/coder/boundary/rules"
19+
"github.com/coder/boundary/rulesengine"
2020
"github.com/coder/boundary/tls"
2121
"github.com/coder/boundary/util"
2222
"github.com/coder/serpent"
@@ -43,10 +43,10 @@ func NewCommand() *serpent.Command {
4343
// may be called something different when used as a subcommand / there will be a leading binary (i.e. `coder boundary` vs. `boundary`).
4444
cmd.Long += `Examples:
4545
# Allow only requests to github.com
46-
boundary --allow "github.com" -- curl https://github.com
46+
boundary --allow "domain=github.com" -- curl https://github.com
4747
4848
# Monitor all requests to specific domains (allow only those)
49-
boundary --allow "github.com/api/issues/*" --allow "GET,HEAD github.com" -- npm install
49+
boundary --allow "domain=github.com path=/api/issues/*" --allow "method=GET,HEAD domain=github.com" -- npm install
5050
5151
# Block everything by default (implicit)`
5252

@@ -171,14 +171,14 @@ func Run(ctx context.Context, config Config, args []string) error {
171171
}
172172

173173
// Parse allow rules
174-
allowRules, err := rules.ParseAllowSpecs(config.AllowStrings)
174+
allowRules, err := rulesengine.ParseAllowSpecs(config.AllowStrings)
175175
if err != nil {
176176
logger.Error("Failed to parse allow rules", "error", err)
177177
return fmt.Errorf("failed to parse allow rules: %v", err)
178178
}
179179

180180
// Create rule engine
181-
ruleEngine := rules.NewRuleEngine(allowRules, logger)
181+
ruleEngine := rulesengine.NewRuleEngine(allowRules, logger)
182182

183183
// Create auditor
184184
auditor := audit.NewLogAuditor(logger)

e2e_tests/boundary_integration_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ func TestBoundaryIntegration(t *testing.T) {
7070

7171
// Start boundary process with sudo
7272
boundaryCmd := exec.CommandContext(ctx, "/tmp/boundary-test",
73-
"--allow", "dev.coder.com",
74-
"--allow", "jsonplaceholder.typicode.com",
73+
"--allow", "domain=dev.coder.com",
74+
"--allow", "domain=jsonplaceholder.typicode.com",
7575
"--log-level", "debug",
7676
"--", "/bin/bash", "-c", "/usr/bin/sleep 10 && /usr/bin/echo 'Test completed'")
7777

@@ -215,7 +215,7 @@ func TestContentLengthHeader(t *testing.T) {
215215

216216
// Start boundary process with sudo
217217
boundaryCmd := exec.CommandContext(ctx, "/tmp/boundary-test",
218-
"--allow", "example.com",
218+
"--allow", "domain=example.com",
219219
"--log-level", "debug",
220220
"--", "/bin/bash", "-c", "/usr/bin/sleep 10 && /usr/bin/echo 'Test completed'")
221221

e2e_tests/iptables_cleanup_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ func TestIPTablesCleanup(t *testing.T) {
5050

5151
// Start boundary process with sudo
5252
boundaryCmd := exec.CommandContext(ctx, "/tmp/boundary-test",
53-
"--allow", "dev.coder.com",
54-
"--allow", "jsonplaceholder.typicode.com",
53+
"--allow", "domain=dev.coder.com",
54+
"--allow", "domain=jsonplaceholder.typicode.com",
5555
"--log-level", "debug",
5656
"--", "/bin/bash", "-c", "/usr/bin/sleep 10 && /usr/bin/echo 'Test completed'")
5757

proxy/proxy.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ import (
1919
"time"
2020

2121
"github.com/coder/boundary/audit"
22-
"github.com/coder/boundary/rules"
22+
"github.com/coder/boundary/rulesengine"
2323
)
2424

2525
// Server handles HTTP and HTTPS requests with rule-based filtering
2626
type Server struct {
27-
ruleEngine rules.Evaluator
27+
ruleEngine rulesengine.Engine
2828
auditor audit.Auditor
2929
logger *slog.Logger
3030
tlsConfig *tls.Config
@@ -40,7 +40,7 @@ type Server struct {
4040
// Config holds configuration for the proxy server
4141
type Config struct {
4242
HTTPPort int
43-
RuleEngine rules.Evaluator
43+
RuleEngine rulesengine.Engine
4444
Auditor audit.Auditor
4545
Logger *slog.Logger
4646
TLSConfig *tls.Config
@@ -440,8 +440,8 @@ Request: %s %s
440440
Host: %s
441441
442442
To allow this request, restart boundary with:
443-
--allow "%s" # Allow all methods to this host
444-
--allow "%s %s" # Allow only %s requests to this host
443+
--allow "domain=%s" # Allow all methods to this host
444+
--allow "method=%s domain=%s" # Allow only %s requests to this host
445445
446446
For more help: https://github.com/coder/boundary
447447
`,

proxy/proxy_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
"github.com/stretchr/testify/require"
1818

1919
"github.com/coder/boundary/audit"
20-
"github.com/coder/boundary/rules"
20+
"github.com/coder/boundary/rulesengine"
2121
)
2222

2323
// mockAuditor is a simple mock auditor for testing
@@ -35,13 +35,13 @@ func TestProxyServerBasicHTTP(t *testing.T) {
3535
}))
3636

3737
// Create test rules (allow all for testing)
38-
testRules, err := rules.ParseAllowSpecs([]string{"*"})
38+
testRules, err := rulesengine.ParseAllowSpecs([]string{"method=*"})
3939
if err != nil {
4040
t.Fatalf("Failed to parse test rules: %v", err)
4141
}
4242

4343
// Create rule engine
44-
ruleEngine := rules.NewRuleEngine(testRules, logger)
44+
ruleEngine := rulesengine.NewRuleEngine(testRules, logger)
4545

4646
// Create mock auditor
4747
auditor := &mockAuditor{}
@@ -116,13 +116,13 @@ func TestProxyServerBasicHTTPS(t *testing.T) {
116116
}))
117117

118118
// Create test rules (allow all for testing)
119-
testRules, err := rules.ParseAllowSpecs([]string{"*"})
119+
testRules, err := rulesengine.ParseAllowSpecs([]string{"method=*"})
120120
if err != nil {
121121
t.Fatalf("Failed to parse test rules: %v", err)
122122
}
123123

124124
// Create rule engine
125-
ruleEngine := rules.NewRuleEngine(testRules, logger)
125+
ruleEngine := rulesengine.NewRuleEngine(testRules, logger)
126126

127127
// Create mock auditor
128128
auditor := &mockAuditor{}
@@ -143,7 +143,7 @@ func TestProxyServerBasicHTTPS(t *testing.T) {
143143
Gid: gid,
144144
})
145145
require.NoError(t, err)
146-
146+
147147
// Setup TLS to get cert path for jailer
148148
tlsConfig, caCertPath, configDir, err := certManager.SetupTLSAndWriteCACert()
149149
require.NoError(t, err)
@@ -212,13 +212,13 @@ func TestProxyServerCONNECT(t *testing.T) {
212212
}))
213213

214214
// Create test rules (allow all for testing)
215-
testRules, err := rules.ParseAllowSpecs([]string{"*"})
215+
testRules, err := rulesengine.ParseAllowSpecs([]string{"method=*"})
216216
if err != nil {
217217
t.Fatalf("Failed to parse test rules: %v", err)
218218
}
219219

220220
// Create rule engine
221-
ruleEngine := rules.NewRuleEngine(testRules, logger)
221+
ruleEngine := rulesengine.NewRuleEngine(testRules, logger)
222222

223223
// Create mock auditor
224224
auditor := &mockAuditor{}

0 commit comments

Comments
 (0)