diff --git a/Dockerfile b/Dockerfile index 33e0bbd..66eaa07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,7 @@ RUN test -e "${GOBIN}/go-away" FROM --platform=$TARGETPLATFORM ${from} COPY --from=build /go/bin/go-away /bin/go-away +COPY examples/snippets/ /snippets/ ENV TZ UTC @@ -65,7 +66,7 @@ ENV JWT_PRIVATE_KEY_SEED="${GOAWAY_JWT_PRIVATE_KEY_SEED}" ENTRYPOINT /bin/go-away --bind "${GOAWAY_BIND}" --bind-network "${GOAWAY_BIND_NETWORK}" --socket-mode "${GOAWAY_SOCKET_MODE}" \ --metrics-bind "${GOAWAY_METRICS_BIND}" --debug-bind "${GOAWAY_DEBUG_BIND}" \ --config "${GOAWAY_CONFIG}" \ - --policy "${GOAWAY_POLICY}" --policy-snippets "${GOAWAY_POLICY_SNIPPETS}" \ + --policy "${GOAWAY_POLICY}" --policy-snippets "/snippets" --policy-snippets "${GOAWAY_POLICY_SNIPPETS}" \ --client-ip-header "${GOAWAY_CLIENT_IP_HEADER}" --backend-ip-header "${GOAWAY_BACKEND_IP_HEADER}" \ --cache "${GOAWAY_CACHE}" \ --challenge-template "${GOAWAY_CHALLENGE_TEMPLATE}" --challenge-template-theme "${GOAWAY_CHALLENGE_TEMPLATE_THEME}" \ diff --git a/README.md b/README.md index c0f527e..d112d88 100644 --- a/README.md +++ b/README.md @@ -373,7 +373,7 @@ services: volumes: - "goaway_cache:/cache" - "./examples/forgejo.yml:/policy.yml:ro" - - "./examples/snippets/:/policy/snippets/:ro" + #- "./your/snippets/:/policy/snippets/:ro" environment: #GOAWAY_BIND: ":8080" # Supported tcp, unix, and proxy (for enabling PROXY module for request unwrapping) @@ -418,7 +418,9 @@ services: GOAWAY_POLICY: "/policy.yml" - GOAWAY_POLICY_SNIPPETS: "/policy/snippets" + # Include extra snippets to load from this path. + # Note that the default snippets from example/snippets/ are included by default + #GOAWAY_POLICY_SNIPPETS: "/policy/snippets" # Template, and theme for the template to pick. defaults to an anubis-like one # An file path can be specified. See embed/templates for a few examples diff --git a/cmd/go-away/main.go b/cmd/go-away/main.go index e557548..11b68be 100644 --- a/cmd/go-away/main.go +++ b/cmd/go-away/main.go @@ -73,7 +73,9 @@ func main() { cachePath := flag.String("cache", path.Join(os.TempDir(), "go_away_cache"), "path to temporary cache directory") policyFile := flag.String("policy", "", "path to policy YAML file") - policySnippets := flag.String("policy-snippets", "", "path to YAML snippets folder") + var policySnippets MultiVar + flag.Var(&policySnippets, "policy-snippets", "path to YAML snippets folder (can be specified multiple times)") + flag.StringVar(&opt.ChallengeTemplate, "challenge-template", opt.ChallengeTemplate, "name or path of the challenge template to use (anubis, forgejo)") templateTheme := flag.String("challenge-template-theme", opt.ChallengeTemplateOverrides["Theme"], "name of the challenge template theme to use (forgejo => [forgejo-auto, forgejo-dark, forgejo-light, gitea...])") @@ -232,7 +234,7 @@ func main() { return nil, fmt.Errorf("failed to read policy file: %w", err) } - p, err := policy.NewPolicy(bytes.NewReader(policyData), *policySnippets) + p, err := policy.NewPolicy(bytes.NewReader(policyData), policySnippets...) if err != nil { return nil, fmt.Errorf("failed to parse policy file: %w", err) } diff --git a/lib/policy/policy.go b/lib/policy/policy.go index f60a6eb..2bd23ce 100644 --- a/lib/policy/policy.go +++ b/lib/policy/policy.go @@ -20,66 +20,77 @@ type Policy struct { Rules []Rule `yaml:"rules"` } -func NewPolicy(r io.Reader, snippetsDirectory string) (*Policy, error) { +func NewPolicy(r io.Reader, snippetsDirectories ...string) (*Policy, error) { var p Policy p.Networks = make(map[string][]Network) p.Conditions = make(map[string][]string) p.Challenges = make(map[string]Challenge) - if snippetsDirectory == "" { + if len(snippetsDirectories) == 0 { err := yaml.NewDecoder(r).Decode(&p) if err != nil { return nil, err } } else { - err := yaml.NewDecoder(r, yaml.ReferenceDirs(snippetsDirectory)).Decode(&p) + var entries []string + for _, dir := range snippetsDirectories { + if dir == "" { + // skip nil directories + continue + } + dirFiles, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + for _, file := range dirFiles { + if file.IsDir() { + continue + } + entries = append(entries, path.Join(dir, file.Name())) + } + } + + err := yaml.NewDecoder(r, yaml.ReferenceFiles(entries...)).Decode(&p) if err != nil { return nil, err } // add specific entries from snippets - entries, err := os.ReadDir(snippetsDirectory) - if err != nil { - return nil, err - } for _, entry := range entries { var entryPolicy Policy - if !entry.IsDir() { - entryData, err := os.ReadFile(path.Join(snippetsDirectory, entry.Name())) - if err != nil { - return nil, err - } - err = yaml.NewDecoder(bytes.NewReader(entryData), yaml.ReferenceDirs(snippetsDirectory)).Decode(&entryPolicy) - if err != nil { - return nil, err - } + entryData, err := os.ReadFile(entry) + if err != nil { + return nil, err + } + err = yaml.NewDecoder(bytes.NewReader(entryData), yaml.ReferenceFiles(entries...)).Decode(&entryPolicy) + if err != nil { + return nil, err + } - // add networks / conditions / challenges definitions if they don't exist already + // add networks / conditions / challenges definitions if they don't exist already - for k, v := range entryPolicy.Networks { - // add network if policy entry does not exist - _, ok := p.Networks[k] - if !ok { - p.Networks[k] = v - } + for k, v := range entryPolicy.Networks { + // add network if policy entry does not exist + _, ok := p.Networks[k] + if !ok { + p.Networks[k] = v } + } - for k, v := range entryPolicy.Conditions { - // add condition if policy entry does not exist - _, ok := p.Conditions[k] - if !ok { - p.Conditions[k] = v - } + for k, v := range entryPolicy.Conditions { + // add condition if policy entry does not exist + _, ok := p.Conditions[k] + if !ok { + p.Conditions[k] = v } + } - for k, v := range entryPolicy.Challenges { - // add challenge if policy entry does not exist - _, ok := p.Challenges[k] - if !ok { - p.Challenges[k] = v - } + for k, v := range entryPolicy.Challenges { + // add challenge if policy entry does not exist + _, ok := p.Challenges[k] + if !ok { + p.Challenges[k] = v } - } } }