From a7ad598fc63b69cd440ab6b397d5820e4632b04d Mon Sep 17 00:00:00 2001 From: "Edgaru089, devel" Date: Mon, 7 Apr 2025 11:42:37 +0800 Subject: [PATCH] Wildcard matched rules --- internal/perm/config.go | 28 +++++++++++ internal/perm/perm.go | 51 +++++++++++++++++-- internal/util/wildcard.go | 101 ++++++++++++++++++++++++++++++++++++++ perm.json | 10 +++- 4 files changed, 183 insertions(+), 7 deletions(-) create mode 100644 internal/util/wildcard.go diff --git a/internal/perm/config.go b/internal/perm/config.go index a3ab90b..dd8127a 100644 --- a/internal/perm/config.go +++ b/internal/perm/config.go @@ -1,6 +1,7 @@ package perm import ( + "encoding/json" "errors" "strconv" "strings" @@ -59,6 +60,27 @@ func (a Action) MarshalText() ([]byte, error) { return []byte(a.String()), nil } +// globAction is a helper to pack globs better. +type globAction struct { + Glob string + Act Action +} + +func (g *globAction) UnmarshalJSON(b []byte) error { + m := make(map[string]Action) + if err := json.Unmarshal(b, &m); err != nil { + return err + } + if len(m) != 1 { + return errors.New("Glob action object does not have & only have one key") + } + for k, v := range m { + g.Glob = k + g.Act = v + } + return nil +} + // Config is a list of address and actions, for each source address. // It can just be Marshal/Unmarshaled into/from json. type Config struct { @@ -70,6 +92,12 @@ type Config struct { // Port number is extracted by net/url.splitHostPort, copied below. // Port number is optional, but must be numeric when present. Match map[string]Action + + // Object which holds wildcard matches. + // This is searched after Match. It appends the default port if not found. + // + // Unlike Match, this is a list that is searched top-to-bottom. + MatchWildcard []globAction } // validOptionalPort reports whether port is either an empty string diff --git a/internal/perm/perm.go b/internal/perm/perm.go index dccc174..9926ade 100644 --- a/internal/perm/perm.go +++ b/internal/perm/perm.go @@ -6,11 +6,14 @@ import ( "strconv" "strings" "sync" + + "edgaru089.ink/go/regolith/internal/util" ) type int_perm struct { - match map[string]Action - def Action + match map[string]Action + match_glob []globAction + def Action } // Perm matches address:port strings to Actions @@ -77,6 +80,29 @@ func (p *Perm) Load(cs map[string]Config) { } } } + for _, glob := range c.MatchWildcard { + addr, port := splitHostPort(glob.Glob) + if port != "" { + log.Printf("loading glob target %s, action %s", glob.Glob, glob.Act) + p_int.match_glob = append( + p_int.match_glob, + globAction{ + Glob: glob.Glob, + Act: glob.Act, + }) + } else { + // TODO change this to sth faster + for _, def_port := range c.DefaultPort { + log.Printf("loading glob target %s, action %s", net.JoinHostPort(addr, strconv.Itoa(def_port)), glob.Act) + p_int.match_glob = append( + p_int.match_glob, + globAction{ + Glob: net.JoinHostPort(addr, strconv.Itoa(def_port)), + Act: glob.Act, + }) + } + } + } return } @@ -108,9 +134,18 @@ func (p *Perm) Match(src, dest string) Action { // find its source struct p_int, ok_int := p.source[src] // only check if dest is directly listed - if ok_int && p_int.match != nil { - if action, ok := p_int.match[dest]; ok { - return action + if ok_int { + // first check direct match + if p_int.match != nil { + if action, ok := p_int.match[dest]; ok { + return action + } + } + // then check glob match + for _, g := range p_int.match_glob { + if util.Match(g.Glob, dest) { + return g.Act + } } } @@ -120,6 +155,12 @@ func (p *Perm) Match(src, dest string) Action { return action } } + // then check global glob match + for _, g := range p.global.match_glob { + if util.Match(g.Glob, dest) { + return g.Act + } + } // directly listed in neither. if ok_int { diff --git a/internal/util/wildcard.go b/internal/util/wildcard.go new file mode 100644 index 0000000..88b354b --- /dev/null +++ b/internal/util/wildcard.go @@ -0,0 +1,101 @@ +package util + +// Copied from https://github.com/minio/pkg/blob/main/wildcard/match.go + +// Copyright (c) 2015-2023 MinIO, Inc. +// +// This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// MatchSimple - finds whether the text matches/satisfies the pattern string. +// supports '*' wildcard in the pattern and ? for single characters. +// Only difference to Match is that `?` at the end is optional, +// meaning `a?` pattern will match name `a`. +func MatchSimple(pattern, name string) bool { + if pattern == "" { + return name == pattern + } + if pattern == "*" { + return true + } + // Do an extended wildcard '*' and '?' match. + return deepMatchRune(name, pattern, true) +} + +// Match - finds whether the text matches/satisfies the pattern string. +// supports '*' and '?' wildcards in the pattern string. +// unlike path.Match(), considers a path as a flat name space while matching the pattern. +// The difference is illustrated in the example here https://play.golang.org/p/Ega9qgD4Qz . +func Match(pattern, name string) (matched bool) { + if pattern == "" { + return name == pattern + } + if pattern == "*" { + return true + } + // Do an extended wildcard '*' and '?' match. + return deepMatchRune(name, pattern, false) +} + +func deepMatchRune(str, pattern string, simple bool) bool { + for len(pattern) > 0 { + switch pattern[0] { + default: + if len(str) == 0 || str[0] != pattern[0] { + return false + } + case '?': + if len(str) == 0 { + return simple + } + case '*': + return len(pattern) == 1 || // Pattern ends with this star + deepMatchRune(str, pattern[1:], simple) || // Matches next part of pattern + (len(str) > 0 && deepMatchRune(str[1:], pattern, simple)) // Continue searching forward + } + str = str[1:] + pattern = pattern[1:] + } + return len(str) == 0 && len(pattern) == 0 +} + +// MatchAsPatternPrefix matches text as a prefix of the given pattern. Examples: +// +// | Pattern | Text | Match Result | +// ==================================== +// | abc* | ab | True | +// | abc* | abd | False | +// | abc*c | abcd | True | +// | ab*??d | abxxc | True | +// | ab*??d | abxc | True | +// | ab??d | abxc | True | +// | ab??d | abc | True | +// | ab??d | abcxdd | False | +// +// This function is only useful in some special situations. +func MatchAsPatternPrefix(pattern, text string) bool { + for i := 0; i < len(text) && i < len(pattern); i++ { + if pattern[i] == '*' { + return true + } + if pattern[i] == '?' { + continue + } + if pattern[i] != text[i] { + return false + } + } + return len(text) <= len(pattern) +} diff --git a/perm.json b/perm.json index 7a7b2ca..09667f0 100644 --- a/perm.json +++ b/perm.json @@ -7,7 +7,10 @@ "mirrors6.tuna.tsinghua.edu.cn": "accept", "incoming.telemetry.mozilla.org": "ignore" - } + }, + "MatchWildcard": [ + {"*.qq.com": "accept"} + ] }, "127.0.0.1": { "DefaultAction": "deny", @@ -15,7 +18,10 @@ "Match": { "pkg.go.dev": "accept", "go.dev": "accept" - } + }, + "MatchWildcard": [ + {"*.baidu.com": "accept"} + ] } }