Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ boundary-run -- curl https://example.com
```bash
boundary-run --allow "domain=github.com" -- git pull
boundary-run --allow "domain=*.github.com" -- npm install # GitHub subdomains
boundary-run --allow "domain=github.com" --allow "domain=*.github.com" -- git pull # Both base domain and subdomains
boundary-run --allow "method=GET,HEAD domain=api.github.com" -- curl https://api.github.com
boundary-run --allow "method=POST domain=api.example.com path=/users,/posts" -- ./app # Multiple paths
boundary-run --allow "path=/api/v1/*,/api/v2/*" -- curl https://api.example.com/api/v1/users
Expand Down
11 changes: 8 additions & 3 deletions rulesengine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ func (re *Engine) matches(r Rule, method, url string) bool {

if r.HostPattern != nil {
// For a host pattern to match, every label has to match or be an `*`.
// Subdomains also match automatically, meaning if the pattern is "example.com"
// and the real is "api.example.com", it should match. We check this by comparing
// from the end of the actual hostname with the pattern (which is in normal order).
// Host matching is strict:
// - "github.com" matches ONLY "github.com" (exact match, no subdomains)
// - "*.github.com" matches ONLY subdomains like "api.github.com" (not the base domain)
// - To allow both, specify: "github.com, *.github.com"

labels := strings.Split(parsedUrl.Hostname(), ".")

Expand All @@ -98,6 +99,10 @@ func (re *Engine) matches(r Rule, method, url string) bool {
return false
}
}

if len(labels) > len(r.HostPattern) && r.HostPattern[0] != "*" {
return false
}
}

if r.PathPattern != nil {
Expand Down
20 changes: 19 additions & 1 deletion rulesengine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,32 @@ func TestEngineMatches(t *testing.T) {
expected: false,
},
{
name: "subdomain matches",
name: "subdomain does not match exact domain pattern",
rule: Rule{
HostPattern: []string{"example", "com"},
},
method: "GET",
url: "https://api.example.com/users",
expected: false,
},
{
name: "wildcard subdomain pattern matches subdomain",
rule: Rule{
HostPattern: []string{"*", "example", "com"},
},
method: "GET",
url: "https://api.example.com/users",
expected: true,
},
{
name: "wildcard subdomain pattern does not match base domain",
rule: Rule{
HostPattern: []string{"*", "example", "com"},
},
method: "GET",
url: "https://example.com/users",
expected: false,
},
{
name: "host pattern too long",
rule: Rule{
Expand Down
30 changes: 27 additions & 3 deletions rulesengine/parse_and_match_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,37 @@ func TestRoundTrip(t *testing.T) {
expectMatch: true,
},
{
name: "domain wildcard segment matches",
name: "domain wildcard segment matches subdomain",
rules: []string{"domain=*.github.com"},
url: "https://api.github.com/repos",
method: "GET",
expectParse: true,
expectMatch: true,
},
{
name: "domain wildcard does not match base domain",
rules: []string{"domain=*.github.com"},
url: "https://github.com/repos",
method: "GET",
expectParse: true,
expectMatch: false,
},
{
name: "exact domain does not match subdomain",
rules: []string{"domain=github.com"},
url: "https://api.github.com/repos",
method: "GET",
expectParse: true,
expectMatch: false,
},
{
name: "exact domain matches only exact domain",
rules: []string{"domain=github.com"},
url: "https://github.com/repos",
method: "GET",
expectParse: true,
expectMatch: true,
},
{
name: "domain cannot end with asterisk",
rules: []string{"domain=github.*"},
Expand Down Expand Up @@ -281,12 +305,12 @@ func TestRoundTripExtraRules(t *testing.T) {
expectMatch: false,
},
{
name: "includes all subdomains by default",
name: "exact domain does not match subdomains",
rules: []string{"domain=github.com"},
url: "https://x.users.api.github.com",
method: "GET",
expectParse: true,
expectMatch: true,
expectMatch: false,
},
{
name: "domain wildcard in the middle matches exactly one label",
Expand Down
3 changes: 2 additions & 1 deletion rulesengine/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type Rule struct {
// The labels of the host, i.e. ["google", "com"].
// - nil means all hosts allowed
// - A label of `*` acts as a wild card.
// - subdomains automatically match
// - Exact domain patterns (e.g., "github.com") match ONLY the exact domain (no subdomains)
// - Wildcard patterns starting with "*" (e.g., "*.github.com") match ONLY subdomains (not the base domain)
HostPattern []string

// The allowed http methods.
Expand Down
2 changes: 1 addition & 1 deletion rulesengine/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ func TestReadmeExamples(t *testing.T) {
}{
{"GET", "https://github.com", true},
{"POST", "https://github.com/user/repo", true},
{"GET", "https://api.github.com", true}, // subdomain match
{"GET", "https://api.github.com", false}, // subdomain does not match exact domain
{"GET", "https://example.com", false},
},
},
Expand Down
Loading