From 98b53d84b5a17a257554201958053d3122b1dff7 Mon Sep 17 00:00:00 2001 From: Ismo Vuorinen Date: Sun, 1 Mar 2026 19:09:17 +0200 Subject: [PATCH] fix: repair Renovate config, convert Makefile to go run, update GitHub Actions (#120) * fix: repair Renovate config and convert Makefile to go run (#117) - Remove non-existent `github>renovatebot/presets:golang` preset that broke Renovate PR creation - Replace deprecated `fileMatch` with `managerFilePatterns` in customManagers - Rewrite regex to match new Makefile pattern (renovate comment above version variable assignment) - Fix `matchFileNames` glob pattern (`*.mk` -> `**/*.mk`) - Convert all tool invocations from `go install` + global binary to `go run tool@version` for reproducible builds - Convert npm global tools to `npx --yes` invocations - Remove `dev-deps` and `check-deps` targets (tools auto-download) - Add mdformat pre-commit hook with GFM support and config - Add `fmt-md` Makefile target for manual markdown formatting - Update local golangci-lint pre-commit hook to use `go run` - Apply golangci-lint v2.10.1 auto-fixes (fmt.Fprintf optimization) - Add nolint:gosec annotations for legitimate exec.Command usage - Exclude .serena/ from mdformat and megalinter - Add markdown indent_size=unset in .editorconfig for CommonMark compat * chore(deps): update GitHub Actions to latest versions - anthropics/claude-code-action: v1.0.34 -> v1.0.64 - actions/setup-go: v6.2.0 -> v6.3.0 - actions/upload-artifact: v6.0.0 -> v7.0.0 - goreleaser/goreleaser-action: v6.4.0 -> v7.0.0 - docker/login-action: v3.6.0 -> v3.7.0 - ivuorinen/actions: v2026.01.21 -> v2026.02.24 * fix: address code review feedback - Fix issue template YAML frontmatter (replace underscore separators with proper --- delimiters); exclude templates from mdformat - Replace string(rune(n)) with strconv.Itoa(n) in test files to produce deterministic numeric directory names instead of Unicode characters - Remove stale `make dev-deps` reference in README, replace with `make dev-setup` - Extract ban/unban format strings into shared.MetricsFmtBanOperations and shared.MetricsFmtUnbanOperations constants - Replace hardcoded coverage percentages in README with evergreen phrasing * fix: address round 2 code review feedback for PR #120 - Fix corrupted path traversal example in docs/security.md - Fix Renovate .mk regex to match nested paths (.*\.mk$) - Update checkmake pre-commit hook to v0.3.2 to match Makefile - Add sync.WaitGroup to unsynchronized goroutines in security tests - Fix fmt-md target to use pre-commit run mdformat - Pin markdownlint-cli2 to v0.21.0 in lint-md target - Standardize //nolint:gosec to // #nosec annotations for gosec CLI * fix(ci): install PyYAML dependency for PR lint workflow The pr-lint workflow uses ivuorinen/actions/pr-lint which internally calls validate-inputs running a Python script that imports yaml. Python was set up but PyYAML was never installed, causing ModuleNotFoundError at runtime. * fix: address round 3 code review feedback for PR #120 - Wrap Windows-style path traversal example in backtick code span so backslashes render literally in docs/security.md - Add Renovate-managed MARKDOWNLINT_CLI2_VERSION variable in Makefile to match the pattern used by all other tool versions --- .editorconfig | 3 + .github/ISSUE_TEMPLATE/bug_report.md | 7 +- .github/ISSUE_TEMPLATE/feature_request.md | 1 - .github/renovate.json | 8 +- .github/workflows/claude.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/pr-lint.yml | 9 +- .github/workflows/release.yml | 10 +- .github/workflows/stale.yml | 2 +- .github/workflows/sync-labels.yml | 2 +- .gitignore | 1 + .mdformat.toml | 2 + .mega-linter.yml | 2 + .pre-commit-config.yaml | 22 ++- CHANGELOG.md | 4 +- CLAUDE.md | 2 +- CODE_OF_CONDUCT.md | 4 +- CONTRIBUTING.md | 15 +- Makefile | 200 ++++++---------------- README.md | 55 +++--- cmd/cmd_root_test.go | 2 +- cmd/metrics_cmd.go | 32 ++-- docs/api.md | 40 ++--- docs/architecture.md | 42 +++-- docs/faq.md | 18 +- docs/linting.md | 24 +-- docs/security.md | 64 +++---- docs/testing.md | 10 +- fail2ban/fail2ban.go | 5 +- main.go | 7 +- main_performance_test.go | 3 +- main_security_test.go | 20 ++- shared/constants.go | 6 + 33 files changed, 281 insertions(+), 345 deletions(-) create mode 100644 .mdformat.toml diff --git a/.editorconfig b/.editorconfig index b7617cb..ad5ad85 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,5 +13,8 @@ indent_width = 2 [{Makefile,go.mod,go.sum}] indent_style = tab +[*.md] +indent_size = unset + [.github/renovate.json] max_line_length = off diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e53396d..8f87789 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: bug assignees: ivuorinen - --- **Describe the bug** @@ -14,9 +13,9 @@ A clear and concise description of what the bug is. Steps to reproduce the behavior: 1. Run command: `f2b [command]` -2. With arguments: `[arguments]` -3. Expected behavior: `[what should happen]` -4. Actual result: `[what actually happened]` +1. With arguments: `[arguments]` +1. Expected behavior: `[what should happen]` +1. Actual result: `[what actually happened]` **Expected behavior** A clear and concise description of what you expected to happen. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index abdc2e8..31123b0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: enhancement assignees: ivuorinen - --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/renovate.json b/.github/renovate.json index 1dd2a87..622a306 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,12 +1,12 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["github>ivuorinen/renovate-config", "github>renovatebot/presets:golang", "schedule:weekly"], + "extends": ["github>ivuorinen/renovate-config", "schedule:weekly"], "customManagers": [ { "customType": "regex", - "fileMatch": ["^Makefile$", "\\.mk$"], + "managerFilePatterns": ["^Makefile$", ".*\\.mk$"], "matchStrings": [ - "@go install (?\\S+)@(?v?\\d+\\.\\d+\\.\\d+)[\\s\\S]*?renovate:\\s*datasource=(?\\S+)\\s+depName=\\S+" + "#\\s*renovate:\\s*datasource=(?\\S+)\\s+depName=(?\\S+)\\n[A-Z_]+\\s*:?=\\s*(?v?\\d+\\.\\d+\\.\\d+\\S*)" ], "versioningTemplate": "semver" } @@ -15,7 +15,7 @@ "packageRules": [ { "matchManagers": ["custom.regex"], - "matchFileNames": ["Makefile", "*.mk"], + "matchFileNames": ["Makefile", "**/*.mk"], "groupName": "development tools", "schedule": ["before 6am on monday"] } diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 2e43ffd..811d6fe 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -53,7 +53,7 @@ jobs: - name: Run Claude Code id: claude - uses: anthropics/claude-code-action@f64219702d7454cf29fe32a74104be6ed43dc637 # v1.0.34 + uses: anthropics/claude-code-action@ba7fa4bcf054319261202aef93d71a89112a8d00 # v1.0.64 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7a6d286..5e16e82 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: node-version: 24.x - name: Set up Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 45d86c0..1d428f7 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -35,7 +35,7 @@ jobs: node-version: 24.x - name: Set up Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: go.mod cache: true @@ -46,6 +46,9 @@ jobs: with: python-version: "3.x" + - name: Install Python dependencies + run: pip install pyyaml + - name: golangci-lint uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 with: @@ -58,7 +61,7 @@ jobs: go test -race -covermode=atomic -coverprofile=coverage.out ./... - name: Upload coverage report - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: coverage-report path: coverage.out @@ -66,4 +69,4 @@ jobs: - name: Run PR Lint # Custom PR linting action that performs additional PR-specific checks # https://github.com/ivuorinen/actions - uses: ivuorinen/actions/pr-lint@f98ae7cd7d0feb1f9d6b01de0addbb11414cfc73 # v2026.01.21 + uses: ivuorinen/actions/pr-lint@8faacf8a1cae049c1471708dcb408a167e91afaf # v2026.02.24 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9ad608..b4a07f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,26 +23,26 @@ jobs: fetch-depth: 0 # Required for changelog generation - name: Set up Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: go.mod cache: true - name: Install GoReleaser - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0 with: install-only: true version: "~> v2" - name: Login to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0 with: version: "~> v2" args: release --clean @@ -52,7 +52,7 @@ jobs: # HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} - name: Upload Release Assets - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: release-artifacts path: dist/ diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index ea96501..f6eb744 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -23,4 +23,4 @@ jobs: issues: write pull-requests: write steps: - - uses: ivuorinen/actions/stale@f98ae7cd7d0feb1f9d6b01de0addbb11414cfc73 # v2026.01.21 + - uses: ivuorinen/actions/stale@8faacf8a1cae049c1471708dcb408a167e91afaf # v2026.02.24 diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index a93e84b..d9df972 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -23,4 +23,4 @@ jobs: contents: read issues: write steps: - - uses: ivuorinen/actions/sync-labels@f98ae7cd7d0feb1f9d6b01de0addbb11414cfc73 # v2026.01.21 + - uses: ivuorinen/actions/sync-labels@8faacf8a1cae049c1471708dcb408a167e91afaf # v2026.02.24 diff --git a/.gitignore b/.gitignore index d514eea..6d746d5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ dist/* !dist/.gitkeep # Anonymous test data from real fail2ban logs !fail2ban/testdata/* +/.claude/settings.local.json diff --git a/.mdformat.toml b/.mdformat.toml new file mode 100644 index 0000000..ac4ced1 --- /dev/null +++ b/.mdformat.toml @@ -0,0 +1,2 @@ +wrap = "keep" +end_of_line = "lf" diff --git a/.mega-linter.yml b/.mega-linter.yml index 481bd68..1ad1f93 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -14,6 +14,8 @@ PRINT_ALPACA: false # Print Alpaca logo in console SARIF_REPORTER: true # Generate SARIF report SHOW_SKIPPED_LINTERS: false # Show skipped linters in MegaLinter log +FILTER_REGEX_EXCLUDE: '(\.serena/)' + DISABLE_LINTERS: - REPOSITORY_DEVSKIM - GO_REVIVE # run as part of golangci-lint diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c6c69e..2279fcd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,13 +25,13 @@ repos: hooks: - id: golangci-lint name: golangci-lint - entry: golangci-lint run + entry: go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.10.1 run language: system types: [go] pass_filenames: false - repo: https://github.com/google/yamlfmt - rev: v0.20.0 + rev: v0.21.0 hooks: - id: yamlfmt @@ -48,7 +48,7 @@ repos: args: [-q, -c, .markdown-link-check.json] - repo: https://github.com/rhysd/actionlint - rev: v1.7.9 + rev: v1.7.11 hooks: - id: actionlint args: ["-shellcheck="] @@ -59,26 +59,34 @@ repos: - id: shfmt - repo: https://github.com/checkmake/checkmake - rev: 0.2.2 + rev: v0.3.2 hooks: - id: checkmake name: Makefile Linter files: ^Makefile$ - repo: https://github.com/bridgecrewio/checkov.git - rev: "3.2.495" + rev: "3.2.506" hooks: - id: checkov args: - "--quiet" - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.36.0 + rev: 0.37.0 hooks: - id: check-github-workflows args: ["--verbose"] - repo: https://github.com/editorconfig-checker/editorconfig-checker - rev: v3.6.0 + rev: v3.6.1 hooks: - id: editorconfig-checker + + - repo: https://github.com/hukkin/mdformat + rev: 1.0.0 + hooks: + - id: mdformat + additional_dependencies: + - mdformat-gfm + exclude: ^(\.serena/|\.github/ISSUE_TEMPLATE/) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68c1f5a..cc6c23e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ---- +______________________________________________________________________ ## [Unreleased] @@ -62,4 +62,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Various minor bug fixes and improved test coverage. - **Test safety**: Eliminated potential for real sudo execution during testing ---- +______________________________________________________________________ diff --git a/CLAUDE.md b/CLAUDE.md index d9fcae6..670848a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,6 +29,6 @@ Claude Code **MUST** follow ALL instructions in [AGENTS.md](AGENTS.md) when work **The f2b project is in production-ready state** with all critical infrastructure completed. ---- +______________________________________________________________________ **๐Ÿ“‹ For all development work, refer to [AGENTS.md](AGENTS.md) for complete instructions.** diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 0a8d364..7c66e8a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -121,8 +121,8 @@ version 2.0, available at Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). -[homepage]: https://www.contributor-covenant.org - For answers to common questions about this code of conduct, see the FAQ at . Translations are available at . + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70cca32..2a07ac5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Thank you for your interest in contributing to **f2b**! Your help is appreciated, whether you are fixing bugs, adding features, improving documentation, or helping others. ---- +______________________________________________________________________ ## How to Contribute @@ -48,7 +48,7 @@ go test ./... - Describe your changes, reference related issues, and explain any design decisions. - Be ready to discuss and revise your code based on feedback. ---- +______________________________________________________________________ ## Code Style @@ -61,7 +61,7 @@ go test ./... - Handle sudo privileges securely - validate before escalation, use mocks in tests. - Use argument arrays for command execution, never shell string concatenation. ---- +______________________________________________________________________ ## Security & Testing Guidelines @@ -78,7 +78,7 @@ For comprehensive security guidelines, testing patterns, and examples, see: - [docs/testing.md](docs/testing.md) - Testing strategies and mock patterns - [AGENTS.md](AGENTS.md) - AI/LLM contributor guidelines ---- +______________________________________________________________________ ## Communication @@ -86,7 +86,7 @@ For comprehensive security guidelines, testing patterns, and examples, see: - Review the [Code of Conduct](CODE_OF_CONDUCT.md). - For large or breaking changes, open an issue to discuss your approach before submitting a PR. ---- +______________________________________________________________________ ## Additional Notes @@ -95,9 +95,10 @@ For comprehensive security guidelines, testing patterns, and examples, see: - If you are an AI/LLM agent, please see [AGENTS.md](AGENTS.md) for additional guidelines. - By contributing, you agree that your contributions will be licensed under the MIT License. ---- +______________________________________________________________________ Thank you for helping make **f2b** better! -[effective_go]: https://golang.org/doc/effective_go.html [contributing](CONTRIBUTING.md) + +[effective_go]: https://golang.org/doc/effective_go.html diff --git a/Makefile b/Makefile index 8cce56a..288ff5b 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,36 @@ # f2b Makefile -.PHONY: help all build test lint fmt clean install dev-deps ci \ - check-deps test-verbose test-coverage update-deps \ - lint-go lint-md lint-yaml lint-actions lint-make \ - ci ci-coverage security dev-setup pre-commit-setup \ - release-dry-run release release-snapshot release-check _check-tag +.PHONY: help all build test lint fmt clean install +.PHONY: ci ci-coverage test-verbose test-coverage update-deps fmt-md +.PHONY: lint-go lint-md lint-yaml lint-actions lint-make +.PHONY: security dev-setup pre-commit-setup +.PHONY: release-dry-run release release-snapshot release-check _check-tag + +# Tool versions (managed by Renovate) +# renovate: datasource=go depName=github.com/goreleaser/goreleaser/v2 +GORELEASER_VERSION := v2.14.1 +# renovate: datasource=go depName=github.com/golangci/golangci-lint/v2/cmd/golangci-lint +GOLANGCI_LINT_VERSION := v2.10.1 +# renovate: datasource=go depName=github.com/google/yamlfmt/cmd/yamlfmt +YAMLFMT_VERSION := v0.21.0 +# renovate: datasource=go depName=github.com/rhysd/actionlint/cmd/actionlint +ACTIONLINT_VERSION := v1.7.11 +# renovate: datasource=go depName=golang.org/x/tools/cmd/goimports +GOIMPORTS_VERSION := v0.42.0 +# renovate: datasource=go depName=github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker +EDITORCONFIG_CHECKER_VERSION := v3.6.1 +# renovate: datasource=go depName=github.com/securego/gosec/v2/cmd/gosec +GOSEC_VERSION := v2.24.0 +# renovate: datasource=go depName=honnef.co/go/tools/cmd/staticcheck +STATICCHECK_VERSION := v0.7.0 +# renovate: datasource=go depName=github.com/mgechev/revive +REVIVE_VERSION := v1.14.0 +# renovate: datasource=go depName=github.com/checkmake/checkmake/cmd/checkmake +CHECKMAKE_VERSION := v0.3.2 +# renovate: datasource=go depName=github.com/segmentio/golines +GOLINES_VERSION := v0.13.0 +# renovate: datasource=npm depName=markdownlint-cli2 +MARKDOWNLINT_CLI2_VERSION := 0.21.0 # Default target help: ## Show this help message @@ -14,7 +40,7 @@ help: ## Show this help message @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST) all: ci ## Run all CI checks (same as ci target) - @echo "All checks completed โœ“" + @echo "All checks completed" # Build targets build: ## Build the f2b binary @@ -23,107 +49,6 @@ build: ## Build the f2b binary install: ## Install f2b globally go install github.com/ivuorinen/f2b@latest -# Development dependencies -dev-deps: ## Install development dependencies - @echo "Installing development dependencies..." - @echo "" - @echo "Installing goreleaser..." - @go install github.com/goreleaser/goreleaser/v2@v2.12.0; - # renovate: datasource=go depName=github.com/goreleaser/goreleaser/v2 - @GOLANGCI_VERSION=$$(golangci-lint version 2>/dev/null \ - | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "0.0.0"); \ - EXPECTED_VERSION="2.7.2"; \ - if [ "$$GOLANGCI_VERSION" != "$$EXPECTED_VERSION" ]; then \ - echo "Installing golangci-lint v$$EXPECTED_VERSION (current: v$$GOLANGCI_VERSION)..."; \ - go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v$$EXPECTED_VERSION; \ - fi - # renovate: datasource=go depName=github.com/golangci/golangci-lint/v2/cmd/golangci-lint - @command -v markdownlint-cli2 >/dev/null 2>&1 || { \ - echo "Installing markdownlint-cli2..."; \ - npm install -g markdownlint-cli2; \ - } - @command -v markdown-link-check >/dev/null 2>&1 || { \ - echo "Installing markdown-link-check..."; \ - npm install -g markdown-link-check; \ - } - @command -v yamlfmt >/dev/null 2>&1 || { \ - echo "Installing yamlfmt..."; \ - go install github.com/google/yamlfmt/cmd/yamlfmt@v0.17.2; \ - } - # renovate: datasource=go depName=github.com/google/yamlfmt/cmd/yamlfmt - @command -v actionlint >/dev/null 2>&1 || { \ - echo "Installing actionlint..."; \ - go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.7; \ - } - # renovate: datasource=go depName=github.com/rhysd/actionlint/cmd/actionlint - @command -v goimports >/dev/null 2>&1 || { \ - echo "Installing goimports..."; \ - go install golang.org/x/tools/cmd/goimports@v0.28.0; \ - } - # renovate: datasource=go depName=golang.org/x/tools/cmd/goimports - @command -v editorconfig-checker >/dev/null 2>&1 || { \ - echo "Installing editorconfig-checker..."; \ - go install github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.4.0; \ - } - # renovate: datasource=go depName=github.com/editorconfig-checker/editorconfig-checker/v3 - @command -v gosec >/dev/null 2>&1 || { \ - echo "Installing gosec..."; \ - go install github.com/securego/gosec/v2/cmd/gosec@v2.22.8; \ - } - # renovate: datasource=go depName=github.com/securego/gosec/v2/cmd/gosec - @command -v staticcheck >/dev/null 2>&1 || { \ - echo "Installing staticcheck..."; \ - go install honnef.co/go/tools/cmd/staticcheck@2024.1.1; \ - } - # renovate: datasource=go depName=honnef.co/go/tools/cmd/staticcheck - @command -v revive >/dev/null 2>&1 || { \ - echo "Installing revive..."; \ - go install github.com/mgechev/revive@v1.12.0; \ - } - # renovate: datasource=go depName=github.com/mgechev/revive - @command -v checkmake >/dev/null 2>&1 || { \ - echo "Installing checkmake..."; \ - go install github.com/checkmake/checkmake/cmd/checkmake@0.2.2; \ - } - # renovate: datasource=go depName=github.com/checkmake/checkmake/cmd/checkmake - @command -v golines >/dev/null 2>&1 || { \ - echo "Installing golines..."; \ - go install github.com/segmentio/golines@v0.13.0; \ - } - # renovate: datasource=go depName=github.com/segmentio/golines - -check-deps: ## Check if all development dependencies are installed - @echo "Checking development dependencies..." - @command -v go >/dev/null 2>&1 || { \ - echo "go is not installed"; exit 1; } - @command -v goreleaser >/dev/null 2>&1 || { - echo "goreleaser is not installed (run: make dev-deps)"; exit 1; } - @command -v golangci-lint >/dev/null 2>&1 || { - echo "golangci-lint is not installed (run: make dev-deps)"; exit 1; } - @command -v markdownlint-cli2 >/dev/null 2>&1 || { - echo "markdownlint-cli2 is not installed (run: make dev-deps)"; exit 1; } - @command -v markdown-link-check >/dev/null 2>&1 || { - echo "markdown-link-check is not installed (run: make dev-deps)"; exit 1; } - @command -v goimports >/dev/null 2>&1 || { - echo "goimports is not installed (run: make dev-deps)"; exit 1; } - @command -v editorconfig-checker >/dev/null 2>&1 || { - echo "editorconfig-checker is not installed (run: make dev-deps)"; exit 1; } - @command -v gosec >/dev/null 2>&1 || { - echo "gosec is not installed (run: make dev-deps)"; exit 1; } - @command -v staticcheck >/dev/null 2>&1 || { - echo "staticcheck is not installed (run: make dev-deps)"; exit 1; } - @command -v revive >/dev/null 2>&1 || { - echo "revive is not installed (run: make dev-deps)"; exit 1; } - @command -v checkmake >/dev/null 2>&1 || { - echo "checkmake is not installed (run: make dev-deps)"; exit 1; } - @command -v yamlfmt >/dev/null 2>&1 || { - echo "yamlfmt is not installed (run: make dev-deps)"; exit 1; } - @command -v actionlint >/dev/null 2>&1 || { - echo "actionlint is not installed (run: make dev-deps)"; exit 1; } - @command -v golines >/dev/null 2>&1 || { - echo "golines is not installed (run: make dev-deps)"; exit 1; } - @echo "All dependencies are installed โœ“" - # Testing targets test: ## Run all tests go test ./... @@ -134,42 +59,38 @@ test-verbose: ## Run tests with verbose output test-coverage: ## Run tests with coverage report go test -coverprofile=coverage.out ./... go tool cover -html=coverage.out -o coverage.html - @echo "Coverage report saved to coverage.html" update-deps: ## Update Go dependencies to latest patch versions - @echo "Updating Go dependencies (patch versions only)..." go get -u=patch ./... go mod tidy go mod verify - @echo "Dependencies updated โœ“" - @echo "Updated dependencies:" @go list -u -m all | grep '\[' || true # Code quality targets fmt: ## Format Go code gofmt -w . - @echo "Go code formatted โœ“" + +fmt-md: ## Format Markdown files + @pre-commit run mdformat --all-files lint: ## Run all linters using pre-commit (preferred method) - @echo "Running pre-commit linters..." @pre-commit run --all-files - @echo "All linting completed โœ“" lint-go: ## Run only Go linters go vet ./... - golangci-lint run --timeout=5m + go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) run --timeout=5m lint-md: ## Run only Markdown linter - markdownlint-cli2 *.md **/*.md + npx --yes markdownlint-cli2@$(MARKDOWNLINT_CLI2_VERSION) "*.md" "**/*.md" lint-yaml: ## Run only YAML linter - yamlfmt -lint . + go run github.com/google/yamlfmt/cmd/yamlfmt@$(YAMLFMT_VERSION) -lint . lint-actions: ## Run only GitHub Actions linter - actionlint .github/workflows/*.yml + go run github.com/rhysd/actionlint/cmd/actionlint@$(ACTIONLINT_VERSION) .github/workflows/*.yml lint-make: ## Run only Makefile linter - checkmake Makefile + go run github.com/checkmake/checkmake/cmd/checkmake@$(CHECKMAKE_VERSION) Makefile # CI targets ci: fmt lint test ## Run all CI checks (format, lint, test) @@ -178,48 +99,28 @@ ci-coverage: fmt lint test-coverage ## Run CI checks with coverage # Security targets security: ## Run security checks - gosec ./... + go run github.com/securego/gosec/v2/cmd/gosec@$(GOSEC_VERSION) ./... # Cleanup targets clean: ## Clean build artifacts - rm -f f2b - rm -f coverage.out - rm -f coverage.html + rm -f f2b coverage.out coverage.html go clean # Development targets -dev-setup: dev-deps ## Set up development environment - @echo "Setting up development environment..." - @echo "Installing pre-commit hooks..." - @command -v pre-commit >/dev/null 2>&1 || { \ - echo "Installing pre-commit..."; \ - pip install pre-commit; \ - } - @pre-commit install - @echo "Development environment setup complete โœ“" +dev-setup: pre-commit-setup ## Set up development environment pre-commit-setup: ## Install and configure pre-commit hooks - @echo "Installing pre-commit..." - @command -v pre-commit >/dev/null 2>&1 || { \ - echo "Installing pre-commit..."; \ - pip install pre-commit; \ - } + @command -v pre-commit >/dev/null 2>&1 || pip install pre-commit @pre-commit install - @echo "Pre-commit hooks installed โœ“" # Release targets release-dry-run: ## Test release process without creating artifacts - @echo "Testing release process..." @VERSION=$$(git describe --tags --exact-match 2>/dev/null || echo "v0.0.0-dev"); \ echo "Building version: $$VERSION"; \ - go build -ldflags "-X github.com/ivuorinen/f2b/cmd.version=$$VERSION" -o f2b-test . - @rm -f f2b-test - @echo "Release dry-run complete โœ“" + go build -ldflags "-X github.com/ivuorinen/f2b/cmd.version=$$VERSION" -o f2b-test . && rm -f f2b-test -release: ## Create a new release using GoReleaser - @echo "Creating release with GoReleaser..." - @$(MAKE) _check-tag - @goreleaser release --clean +release: _check-tag ## Create a new release using GoReleaser + go run github.com/goreleaser/goreleaser/v2@$(GORELEASER_VERSION) release --clean _check-tag: ## Internal: Check if a git tag exists @if [ -z "$$(git describe --exact-match 2>/dev/null)" ]; then \ @@ -228,10 +129,7 @@ _check-tag: ## Internal: Check if a git tag exists fi release-snapshot: ## Create a snapshot release (no tag required) - @echo "Creating snapshot release with GoReleaser..." - goreleaser release --snapshot --clean + go run github.com/goreleaser/goreleaser/v2@$(GORELEASER_VERSION) release --snapshot --clean release-check: ## Check if GoReleaser configuration is valid - @echo "Checking GoReleaser configuration..." - goreleaser check - @echo "GoReleaser configuration is valid โœ“" + go run github.com/goreleaser/goreleaser/v2@$(GORELEASER_VERSION) check diff --git a/README.md b/README.md index 50a72bd..dcf9245 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Built with Go, featuring automatic sudo privilege management, shell completion, [![Go Version](https://img.shields.io/badge/Go-%3E%3D1.25-blue.svg)](https://golang.org/) [![Build Status](https://img.shields.io/badge/tests-passing-brightgreen.svg)](https://github.com/ivuorinen/f2b/actions) ---- +______________________________________________________________________ ## ๐Ÿš€ Quick Start @@ -79,7 +79,7 @@ make build go build -ldflags "-X github.com/ivuorinen/f2b/cmd.version=1.2.3" -o f2b . ``` ---- +______________________________________________________________________ ## โœจ Key Features @@ -115,7 +115,7 @@ go build -ldflags "-X github.com/ivuorinen/f2b/cmd.version=1.2.3" -o f2b . - **Thread Safety**: Extensive race condition testing and protection - **Security Audit Trail**: Comprehensive logging of all privileged operations ---- +______________________________________________________________________ ## ๐Ÿ“‹ Usage Examples @@ -190,7 +190,7 @@ f2b completion fish > ~/.config/fish/completions/f2b.fish f2b completion powershell | Out-String | Invoke-Expression ``` ---- +______________________________________________________________________ ## โš™๏ธ Configuration @@ -249,7 +249,7 @@ f2b logs sshd --limit 50 --format=json f2b --log-level=debug --log-file=/tmp/f2b-debug.log ban 192.168.1.100 ``` ---- +______________________________________________________________________ ## ๐Ÿ” Security & Privileges @@ -268,7 +268,7 @@ f2b is designed with security as a fundamental principle: For detailed security practices, threat model, and contribution security guidelines, see [docs/security.md](docs/security.md). ---- +______________________________________________________________________ ## ๐Ÿ“– Complete Command Reference @@ -322,7 +322,7 @@ For convenience, most commands have short aliases: - `ban` โ†’ `banip`, `b` - `unban` โ†’ `unbanip`, `ub` ---- +______________________________________________________________________ ## ๐Ÿ—๏ธ Architecture @@ -338,8 +338,8 @@ f2b is built as an **enterprise-grade** Go application following modern architec ### ๐Ÿ“Š **Quality Metrics** -- **Test Coverage**: 76.8% (cmd/), 59.3% (fail2ban/) - Above industry standards -- **Modern Testing**: Fluent testing framework reducing code duplication by 60-70% +- **Test Coverage**: Comprehensive coverage across all packages - above industry standards +- **Modern Testing**: Fluent testing framework with significant reduction in test duplication - **Security Testing**: 13 comprehensive attack vector test cases implemented - **Performance**: Context-aware operations with configurable timeouts and resource management @@ -363,7 +363,7 @@ f2b is built as an **enterprise-grade** Go application following modern architec For detailed architecture information, implementation patterns, and extension guidelines, see [docs/architecture.md](docs/architecture.md). ---- +______________________________________________________________________ ## ๐Ÿงช Development & Testing @@ -395,8 +395,7 @@ This project uses [pre-commit](https://pre-commit.com/) for unified linting and Install the development dependencies and hooks: ```bash -make dev-deps -make pre-commit-setup +make dev-setup ``` Run all linters: @@ -428,7 +427,7 @@ f2b logs-watch all --limit 20 | while read line; do done ``` ---- +______________________________________________________________________ ## ๐Ÿš€ Releases @@ -438,19 +437,19 @@ Releases are automated using [GoReleaser](https://goreleaser.com/). To create a 1. **Tag the release:** - ```bash - git tag -a v1.2.3 -m "Release v1.2.3" - git push origin v1.2.3 - ``` +```bash +git tag -a v1.2.3 -m "Release v1.2.3" +git push origin v1.2.3 +``` 2. **GitHub Actions will automatically:** - - Build binaries for multiple platforms (Linux, macOS, Windows, BSD) - - Create a GitHub release with changelog - - Upload release artifacts - - Build and push Docker images - - Update Homebrew tap (if configured) - - Generate .deb, .rpm, and .apk packages +- Build binaries for multiple platforms (Linux, macOS, Windows, BSD) +- Create a GitHub release with changelog +- Upload release artifacts +- Build and push Docker images +- Update Homebrew tap (if configured) +- Generate .deb, .rpm, and .apk packages ### Manual Release (Development) @@ -476,7 +475,7 @@ Each release includes: - Docker images at `ghcr.io/ivuorinen/f2b` with architecture-specific tags - Linux packages (.deb, .rpm, .apk) for multiple architectures ---- +______________________________________________________________________ ## ๐Ÿค Contributing @@ -500,25 +499,25 @@ Please see: - [docs/security.md](docs/security.md) - Security practices and guidelines - [docs/testing.md](docs/testing.md) - Testing strategies and patterns ---- +______________________________________________________________________ ## ๐Ÿ“„ License [MIT License](LICENSE.md). ---- +______________________________________________________________________ ## ๐Ÿ‘จโ€๐Ÿ’ป Author **Ismo Vuorinen** ([@ivuorinen](https://github.com/ivuorinen)) ---- +______________________________________________________________________ ## ๐Ÿ†˜ Support - ๐Ÿ“ [Open an issue](https://github.com/ivuorinen/f2b/issues) - ๐Ÿ“– [Read the FAQ](docs/faq.md) ---- +______________________________________________________________________ _Built with โค๏ธ and Go. Securing systems one ban at a time._ diff --git a/cmd/cmd_root_test.go b/cmd/cmd_root_test.go index 8014da9..d9a02fa 100644 --- a/cmd/cmd_root_test.go +++ b/cmd/cmd_root_test.go @@ -513,7 +513,7 @@ func TestPersistentPreRun(t *testing.T) { t.Fatalf("failed to create temp file: %v", err) } defer func() { - if err := os.Remove(tmpFile.Name()); err != nil { + if err := os.Remove(tmpFile.Name()); err != nil { // #nosec G703 -- test file, path from CreateTemp t.Fatalf("failed to remove temp file: %v", err) } }() diff --git a/cmd/metrics_cmd.go b/cmd/metrics_cmd.go index f9e6001..c495251 100644 --- a/cmd/metrics_cmd.go +++ b/cmd/metrics_cmd.go @@ -52,46 +52,44 @@ func printMetricsPlain(output io.Writer, snapshot MetricsSnapshot) error { // System metrics sb.WriteString("System:\n") - sb.WriteString(fmt.Sprintf(" Uptime: %ds\n", snapshot.UptimeSeconds)) - sb.WriteString(fmt.Sprintf(" Max Memory: %.2f MB\n", float64(snapshot.MaxMemoryUsage)/(1024*1024))) - sb.WriteString(fmt.Sprintf(" Goroutines: %d\n\n", snapshot.GoroutineCount)) + fmt.Fprintf(&sb, " Uptime: %ds\n", snapshot.UptimeSeconds) + fmt.Fprintf(&sb, " Max Memory: %.2f MB\n", float64(snapshot.MaxMemoryUsage)/(1024*1024)) + fmt.Fprintf(&sb, " Goroutines: %d\n\n", snapshot.GoroutineCount) // Command metrics sb.WriteString("Commands:\n") - sb.WriteString(fmt.Sprintf(shared.MetricsFmtTotalExecutions, snapshot.CommandExecutions)) - sb.WriteString(fmt.Sprintf(shared.MetricsFmtTotalFailures, snapshot.CommandFailures)) + fmt.Fprintf(&sb, shared.MetricsFmtTotalExecutions, snapshot.CommandExecutions) + fmt.Fprintf(&sb, shared.MetricsFmtTotalFailures, snapshot.CommandFailures) if snapshot.CommandExecutions > 0 { avgLatency := float64(snapshot.CommandTotalDuration) / float64(snapshot.CommandExecutions) - sb.WriteString(fmt.Sprintf(shared.MetricsFmtAverageLatencyTop, avgLatency)) + fmt.Fprintf(&sb, shared.MetricsFmtAverageLatencyTop, avgLatency) } sb.WriteString("\n") // Ban/Unban metrics sb.WriteString("Ban Operations:\n") - sb.WriteString(fmt.Sprintf(" Ban Operations: %d (failures: %d)\n", snapshot.BanOperations, snapshot.BanFailures)) - sb.WriteString( - fmt.Sprintf(" Unban Operations: %d (failures: %d)\n", snapshot.UnbanOperations, snapshot.UnbanFailures), - ) + fmt.Fprintf(&sb, shared.MetricsFmtBanOperations, snapshot.BanOperations, snapshot.BanFailures) + fmt.Fprintf(&sb, shared.MetricsFmtUnbanOperations, snapshot.UnbanOperations, snapshot.UnbanFailures) sb.WriteString("\n") // Client metrics sb.WriteString("Client Operations:\n") - sb.WriteString(fmt.Sprintf(shared.MetricsFmtTotalOperations, snapshot.ClientOperations)) - sb.WriteString(fmt.Sprintf(shared.MetricsFmtTotalFailures, snapshot.ClientFailures)) + fmt.Fprintf(&sb, shared.MetricsFmtTotalOperations, snapshot.ClientOperations) + fmt.Fprintf(&sb, shared.MetricsFmtTotalFailures, snapshot.ClientFailures) if snapshot.ClientOperations > 0 { avgLatency := float64(snapshot.ClientTotalDuration) / float64(snapshot.ClientOperations) - sb.WriteString(fmt.Sprintf(shared.MetricsFmtAverageLatencyTop, avgLatency)) + fmt.Fprintf(&sb, shared.MetricsFmtAverageLatencyTop, avgLatency) } sb.WriteString("\n") // Validation metrics sb.WriteString("Validation:\n") - sb.WriteString(fmt.Sprintf(" Cache Hits: %d\n", snapshot.ValidationCacheHits)) - sb.WriteString(fmt.Sprintf(" Cache Misses: %d\n", snapshot.ValidationCacheMiss)) - sb.WriteString(fmt.Sprintf(" Failures: %d\n", snapshot.ValidationFailures)) + fmt.Fprintf(&sb, " Cache Hits: %d\n", snapshot.ValidationCacheHits) + fmt.Fprintf(&sb, " Cache Misses: %d\n", snapshot.ValidationCacheMiss) + fmt.Fprintf(&sb, " Failures: %d\n", snapshot.ValidationFailures) if total := snapshot.ValidationCacheHits + snapshot.ValidationCacheMiss; total > 0 { hitRate := float64(snapshot.ValidationCacheHits) / float64(total) * 100 - sb.WriteString(fmt.Sprintf(" Cache Hit Rate: %.2f%%\n", hitRate)) + fmt.Fprintf(&sb, " Cache Hit Rate: %.2f%%\n", hitRate) } sb.WriteString("\n") diff --git a/docs/api.md b/docs/api.md index 696fb62..dc34f73 100644 --- a/docs/api.md +++ b/docs/api.md @@ -239,14 +239,14 @@ const ( The configuration system supports the following environment variables: -| Variable | Description | Default | -| -------- | ----------- | ------- | -| `F2B_LOG_DIR` | Log directory path | `/var/log` | -| `F2B_FILTER_DIR` | Filter directory path | `/etc/fail2ban/filter.d` | -| `F2B_LOG_LEVEL` | Log level | `info` | -| `F2B_COMMAND_TIMEOUT` | Command timeout | `30s` | -| `F2B_FILE_TIMEOUT` | File operation timeout | `10s` | -| `F2B_PARALLEL_TIMEOUT` | Parallel operation timeout | `60s` | +| Variable | Description | Default | +| ---------------------- | -------------------------- | ------------------------ | +| `F2B_LOG_DIR` | Log directory path | `/var/log` | +| `F2B_FILTER_DIR` | Filter directory path | `/etc/fail2ban/filter.d` | +| `F2B_LOG_LEVEL` | Log level | `info` | +| `F2B_COMMAND_TIMEOUT` | Command timeout | `30s` | +| `F2B_FILE_TIMEOUT` | File operation timeout | `10s` | +| `F2B_PARALLEL_TIMEOUT` | Parallel operation timeout | `60s` | ### Path Security @@ -550,30 +550,30 @@ func (h *HTTPHandler) writeError(w http.ResponseWriter, code int, err error) { ### Error Handling Best Practices 1. Always use contextual errors for user-facing messages -2. Provide remediation hints where possible -3. Log errors with appropriate context -4. Use error categories for systematic handling +1. Provide remediation hints where possible +1. Log errors with appropriate context +1. Use error categories for systematic handling ### Context Usage 1. Always use context for operations that can timeout -2. Propagate context through the call chain -3. Add relevant context values for logging -4. Use context cancellation for cleanup +1. Propagate context through the call chain +1. Add relevant context values for logging +1. Use context cancellation for cleanup ### Testing 1. Use the fluent testing framework for command tests -2. Always use mock environments for integration tests -3. Test both success and failure scenarios -4. Include timeout testing for long-running operations +1. Always use mock environments for integration tests +1. Test both success and failure scenarios +1. Include timeout testing for long-running operations ### Performance 1. Use the metrics system to monitor performance -2. Implement proper caching where appropriate -3. Use object pooling for frequently allocated objects -4. Profile and optimize hot paths +1. Implement proper caching where appropriate +1. Use object pooling for frequently allocated objects +1. Profile and optimize hot paths This documentation provides a comprehensive overview of the f2b internal APIs and patterns. For specific implementation details, refer to the source code and inline documentation. diff --git a/docs/architecture.md b/docs/architecture.md index 5091887..c259673 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -35,17 +35,21 @@ validation caching, and parallel processing capabilities for enterprise-grade re ### fail2ban/ Package - **Purpose**: Core business logic and system interaction + - **Key Interfaces**: + - `Client`: Main interface for fail2ban operations with context support - `Runner`: Command execution interface - `SudoChecker`: Privilege validation interface - **Implementations**: + - `RealClient`: Production fail2ban client with timeout handling - `MockClient`: Comprehensive test double with thread-safe operations - `NoOpClient`: Safe fallback implementation - **Advanced Features**: + - Context-aware operations with timeout and cancellation support - Validation caching system with thread-safe operations - Optimized ban record parsing with object pooling @@ -105,14 +109,14 @@ validation caching, and parallel processing capabilities for enterprise-grade re ### Command Execution Flow 1. **CLI Parsing**: Cobra processes command-line arguments -2. **Context Creation**: Create context with timeout for operation -3. **Validation**: Input validation with caching and sanitization -4. **Privilege Check**: Determine if sudo is required -5. **Metrics Start**: Begin performance metrics collection -6. **Business Logic**: Execute fail2ban operations via Client interface with context -7. **Parallel Processing**: Use parallel workers for multi-jail operations -8. **Metrics End**: Record operation timing and success/failure -9. **Output**: Format and display results (plain or JSON) +1. **Context Creation**: Create context with timeout for operation +1. **Validation**: Input validation with caching and sanitization +1. **Privilege Check**: Determine if sudo is required +1. **Metrics Start**: Begin performance metrics collection +1. **Business Logic**: Execute fail2ban operations via Client interface with context +1. **Parallel Processing**: Use parallel workers for multi-jail operations +1. **Metrics End**: Record operation timing and success/failure +1. **Output**: Format and display results (plain or JSON) ### Dependency Flow @@ -170,25 +174,25 @@ fail2ban/client.go ### Adding New Commands 1. Create new file in `cmd/` package -2. Implement command using established patterns with context support -3. Use dependency injection for testability -4. Add performance metrics collection -5. Implement fluent testing framework patterns -6. Add comprehensive tests with mocks and context-aware operations +1. Implement command using established patterns with context support +1. Use dependency injection for testability +1. Add performance metrics collection +1. Implement fluent testing framework patterns +1. Add comprehensive tests with mocks and context-aware operations ### Adding New Backends 1. Implement the `Client` interface -2. Add any new required interfaces (Runner, etc.) -3. Update main.go to support new backend -4. Add configuration options +1. Add any new required interfaces (Runner, etc.) +1. Update main.go to support new backend +1. Add configuration options ### Adding New Output Formats 1. Extend output formatting helpers -2. Update command implementations -3. Add format validation -4. Test with existing commands +1. Update command implementations +1. Add format validation +1. Test with existing commands ## Testing Architecture diff --git a/docs/faq.md b/docs/faq.md index 2d85c63..16d504a 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -8,7 +8,7 @@ extensible, and user-friendly alternative to Bash scripts for interacting with Fail2Ban, with automatic sudo privilege management, shell completion, and comprehensive security features. ---- +______________________________________________________________________ ## Installation & Setup @@ -34,7 +34,7 @@ Or install globally: go install github.com/ivuorinen/f2b@latest ``` ---- +______________________________________________________________________ ## Usage @@ -171,7 +171,7 @@ f2b --command-timeout=45s ban 192.168.1.100 f2b --parallel-timeout=120s banned all ``` ---- +______________________________________________________________________ ## Troubleshooting @@ -193,9 +193,9 @@ f2b --parallel-timeout=120s banned all This means you need elevated privileges for the operation you're trying to perform: 1. **Check your privileges:** Run `f2b --log-level=debug version` to see your privilege status -2. **Add sudo:** Try `sudo f2b [command]` -3. **Join sudo group:** Ask your admin to add you to the sudo group -4. **Test sudo access:** Run `sudo -n true` to check if you can use sudo +1. **Add sudo:** Try `sudo f2b [command]` +1. **Join sudo group:** Ask your admin to add you to the sudo group +1. **Test sudo access:** Run `sudo -n true` to check if you can use sudo ### The CLI says "permission denied" or "operation not permitted" @@ -265,7 +265,7 @@ ls -la /etc/fail2ban/ sudo fail2ban-client ping ``` ---- +______________________________________________________________________ ## Development @@ -279,11 +279,11 @@ go test ./... See the `CONTRIBUTING.md` and the Contributing section in the README. ---- +______________________________________________________________________ ## Still need help? - Open an issue on GitHub: https://github.com/ivuorinen/f2b/issues - Contact the maintainer: ismo@ivuorinen.net ---- +______________________________________________________________________ diff --git a/docs/linting.md b/docs/linting.md index fdf4df9..b11d23e 100644 --- a/docs/linting.md +++ b/docs/linting.md @@ -225,11 +225,11 @@ Both workflows now use unified pre-commit: ### Before Committing 1. **Read configuration files first**: `.editorconfig`, `.golangci.yml`, - `.markdownlint.json`, `.yamlfmt.yaml`, `.pre-commit-config.yaml` -2. **Apply configuration rules** during development -3. **Run pre-commit checks**: `pre-commit run --all-files` -4. **Fix all issues** across the project -5. **Run tests**: `go test ./...` + `.markdownlint.json`, `.yamlfmt.yaml`, `.pre-commit-config.yaml` +1. **Apply configuration rules** during development +1. **Run pre-commit checks**: `pre-commit run --all-files` +1. **Fix all issues** across the project +1. **Run tests**: `go test ./...` ### Recommended IDE Setup @@ -303,19 +303,19 @@ All YAML files include schema references for better IDE support: ### Debugging Tips 1. **Run individual hooks** to isolate issues -2. **Use `--verbose` flag** with pre-commit -3. **Check configuration files** for rule customizations -4. **Verify tool versions** match CI environment +1. **Use `--verbose` flag** with pre-commit +1. **Check configuration files** for rule customizations +1. **Verify tool versions** match CI environment ## Adding New Linting Rules ### Process 1. Update configuration files (`.markdownlint.json`, `.yamlfmt.yaml`, etc.) -2. Test changes locally: `pre-commit run --all-files` -3. Update `.pre-commit-config.yaml` if adding new hooks -4. Document changes in this file -5. Consider backward compatibility +1. Test changes locally: `pre-commit run --all-files` +1. Update `.pre-commit-config.yaml` if adding new hooks +1. Document changes in this file +1. Consider backward compatibility ### Best Practices diff --git a/docs/security.md b/docs/security.md index 311afbe..a5789d0 100644 --- a/docs/security.md +++ b/docs/security.md @@ -57,13 +57,13 @@ f2b intelligently manages sudo requirements through a comprehensive privilege ch ### Privilege Escalation Process 1. **Pre-flight Check**: Determine user capabilities before command execution -2. **Context Creation**: Create context with timeout for the operation -3. **Command Classification**: Identify if the operation requires privileges -4. **Smart Escalation**: Only add sudo when necessary for specific commands -5. **Validation**: Ensure privilege escalation succeeded with timeout protection -6. **Execution**: Run command with appropriate privileges and context -7. **Timeout Handling**: Gracefully handle hanging operations with cancellation -8. **Audit**: Log privileged operations with context information +1. **Context Creation**: Create context with timeout for the operation +1. **Command Classification**: Identify if the operation requires privileges +1. **Smart Escalation**: Only add sudo when necessary for specific commands +1. **Validation**: Ensure privilege escalation succeeded with timeout protection +1. **Execution**: Run command with appropriate privileges and context +1. **Timeout Handling**: Gracefully handle hanging operations with cancellation +1. **Audit**: Log privileged operations with context information ### Error Handling @@ -358,10 +358,10 @@ func setupSecureTestEnvironment(t *testing.T) { - **Issue**: Insufficient path validation against sophisticated attacks - **Impact**: Access to files outside intended directories - **Fix**: Comprehensive path traversal protection with extensive test cases covering: - - Unicode normalization attacks (\u002e\u002e) + - Unicode normalization attacks (\\u002e\\u002e) - Mixed case traversal (/var/LOG/../../../etc/passwd) - Multiple slashes (/var/log////../../etc/passwd) - - Windows-style paths on Unix (/var/log\..\..\..\etc\passwd) + - Windows-style paths on Unix (`/var/log\..\..\..\etc\passwd`) - URL encoding variants (%2e%2e%2f) - Null byte injection attacks @@ -382,15 +382,15 @@ func setupSecureTestEnvironment(t *testing.T) { ### Defense in Depth 1. **Input Validation**: First line of defense against malicious input with caching -2. **Advanced Path Traversal Protection**: Extensive sophisticated attack vector protection -3. **Privilege Validation**: Ensure user has necessary permissions with timeout protection -4. **Context-Aware Execution**: Use argument arrays with timeout and cancellation support -5. **Safe Execution**: Never use shell strings, always use context-aware operations -6. **Error Handling**: Fail safely without information leakage, include context information -7. **Audit Logging**: Track privileged operations with contextual information -8. **Test Isolation**: Prevent test-time security compromises with comprehensive mocks -9. **Performance Security**: Validation caching prevents DoS through repeated validation -10. **Timeout Protection**: Prevent resource exhaustion through hanging operations +1. **Advanced Path Traversal Protection**: Extensive sophisticated attack vector protection +1. **Privilege Validation**: Ensure user has necessary permissions with timeout protection +1. **Context-Aware Execution**: Use argument arrays with timeout and cancellation support +1. **Safe Execution**: Never use shell strings, always use context-aware operations +1. **Error Handling**: Fail safely without information leakage, include context information +1. **Audit Logging**: Track privileged operations with contextual information +1. **Test Isolation**: Prevent test-time security compromises with comprehensive mocks +1. **Performance Security**: Validation caching prevents DoS through repeated validation +1. **Timeout Protection**: Prevent resource exhaustion through hanging operations ### Security Boundaries @@ -403,13 +403,13 @@ User Input โ†’ Context โ†’ Validation โ†’ Path Traversal โ†’ Privilege Check โ†’ **Enhanced Security Flow:** 1. **Context Creation**: Establish timeout and cancellation context -2. **Input Sanitization**: Clean and validate all user input -3. **Cache Validation**: Check validation cache for performance and DoS protection -4. **Path Traversal Protection**: Block extensive sophisticated attack vectors -5. **Privilege Verification**: Confirm user permissions with timeout protection -6. **Context-Aware Execution**: Execute with timeout and cancellation support -7. **Timeout Handling**: Gracefully handle hanging operations -8. **Comprehensive Auditing**: Log all operations with context information +1. **Input Sanitization**: Clean and validate all user input +1. **Cache Validation**: Check validation cache for performance and DoS protection +1. **Path Traversal Protection**: Block extensive sophisticated attack vectors +1. **Privilege Verification**: Confirm user permissions with timeout protection +1. **Context-Aware Execution**: Execute with timeout and cancellation support +1. **Timeout Handling**: Gracefully handle hanging operations +1. **Comprehensive Auditing**: Log all operations with context information ## Incident Response @@ -418,17 +418,17 @@ User Input โ†’ Context โ†’ Validation โ†’ Path Traversal โ†’ Privilege Check โ†’ **For security vulnerabilities:** 1. **Do not** open public GitHub issues -2. Email: `ismo@ivuorinen.net` with subject "SECURITY: f2b vulnerability" -3. Include: Description, impact assessment, reproduction steps -4. Expect: Acknowledgment within 48 hours +1. Email: `ismo@ivuorinen.net` with subject "SECURITY: f2b vulnerability" +1. Include: Description, impact assessment, reproduction steps +1. Expect: Acknowledgment within 48 hours ### Security Update Process 1. **Assessment**: Evaluate impact and affected versions -2. **Development**: Create fix with security tests -3. **Testing**: Comprehensive security testing -4. **Release**: Coordinated disclosure with security advisory -5. **Communication**: Notify users via GitHub security advisories +1. **Development**: Create fix with security tests +1. **Testing**: Comprehensive security testing +1. **Release**: Coordinated disclosure with security advisory +1. **Communication**: Notify users via GitHub security advisories ## Security Best Practices diff --git a/docs/testing.md b/docs/testing.md index 27032d6..f5310a9 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -604,11 +604,11 @@ go tool cover -func=coverage.out | grep total ### Avoid These Mistakes 1. **Real sudo execution in tests** - Always use MockSudoChecker -2. **Hardcoded file paths** - Use temporary files or mocks -3. **Network dependencies** - Mock all external calls -4. **Race conditions** - Use proper synchronization in concurrent tests -5. **Leaked goroutines** - Clean up background processes -6. **Platform dependencies** - Write portable tests +1. **Hardcoded file paths** - Use temporary files or mocks +1. **Network dependencies** - Mock all external calls +1. **Race conditions** - Use proper synchronization in concurrent tests +1. **Leaked goroutines** - Clean up background processes +1. **Platform dependencies** - Write portable tests ### Enhanced Security Testing Checklist diff --git a/fail2ban/fail2ban.go b/fail2ban/fail2ban.go index a970cac..50024e4 100644 --- a/fail2ban/fail2ban.go +++ b/fail2ban/fail2ban.go @@ -74,6 +74,7 @@ func (r *OSRunner) CombinedOutputWithContext(ctx context.Context, name string, a if err := validateCommandExecution(ctx, name, args); err != nil { return nil, err } + // #nosec G204 -- command validated by validateCommandExecution return exec.CommandContext(ctx, name, args...).CombinedOutput() } @@ -92,7 +93,7 @@ func (r *OSRunner) CombinedOutputWithSudoContext(ctx context.Context, name strin // If already root, no need for sudo if checker.IsRoot() { - return exec.CommandContext(ctx, name, args...).CombinedOutput() + return exec.CommandContext(ctx, name, args...).CombinedOutput() // #nosec G204 -- command validated above } // If command requires sudo and user has privileges, use sudo @@ -104,7 +105,7 @@ func (r *OSRunner) CombinedOutputWithSudoContext(ctx context.Context, name strin } // Otherwise run without sudo - return exec.CommandContext(ctx, name, args...).CombinedOutput() + return exec.CommandContext(ctx, name, args...).CombinedOutput() // #nosec G204 -- command validated above } // runnerManager provides thread-safe access to the global Runner. diff --git a/main.go b/main.go index 71bc0ac..7c58bc3 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,12 @@ func main() { // Check if this is a sudo privilege error if strings.Contains(err.Error(), "fail2ban operations require sudo privileges") { fmt.Fprintln(os.Stderr, "Hint: Try running with 'sudo' or ensure your user is in the sudo group") - fmt.Fprintln(os.Stderr, "Example: sudo", strings.Join(os.Args, " ")) + // #nosec G705 -- stderr hint, not user-facing HTML + fmt.Fprintln( + os.Stderr, + "Example: sudo", + strings.Join(os.Args, " "), + ) } os.Exit(1) } diff --git a/main_performance_test.go b/main_performance_test.go index 6157f53..7a1b369 100644 --- a/main_performance_test.go +++ b/main_performance_test.go @@ -4,6 +4,7 @@ import ( "context" "os" "path/filepath" + "strconv" "testing" "time" @@ -179,7 +180,7 @@ func BenchmarkGlobalStateAccess(b *testing.B) { fail2ban.GetLogDir() } else { // 50% writes - fail2ban.SetLogDir("/tmp/test-" + string(rune(b.N))) + fail2ban.SetLogDir("/tmp/test-" + strconv.Itoa(b.N)) } } }) diff --git a/main_security_test.go b/main_security_test.go index 08240c6..0e9856f 100644 --- a/main_security_test.go +++ b/main_security_test.go @@ -4,7 +4,9 @@ import ( "context" "os" "path/filepath" + "strconv" "strings" + "sync" "testing" "github.com/ivuorinen/f2b/fail2ban" @@ -378,24 +380,28 @@ func TestSecurityAudit_ConcurrentSafety(t *testing.T) { // Multiple goroutines modifying global state should not cause races // This is tested by running with -race flag in CI - for i := 0; i < 10; i++ { - go func(id int) { - fail2ban.SetLogDir("/tmp/test-" + string(rune(id))) + var wg sync.WaitGroup + for i := range 10 { + wg.Go(func() { + fail2ban.SetLogDir("/tmp/test-" + strconv.Itoa(i)) fail2ban.GetLogDir() - }(i) + }) } + wg.Wait() }) t.Run("CacheStatisticsSafety", func(_ *testing.T) { processor := fail2ban.NewOptimizedLogProcessor() // Multiple goroutines accessing cache statistics should be safe - for i := 0; i < 10; i++ { - go func() { + var wg sync.WaitGroup + for range 10 { + wg.Go(func() { processor.GetCacheStats() processor.ClearCaches() - }() + }) } + wg.Wait() }) } diff --git a/shared/constants.go b/shared/constants.go index 0ad4314..3eabefb 100644 --- a/shared/constants.go +++ b/shared/constants.go @@ -499,4 +499,10 @@ const ( // MetricsFmtAverageLatencyTop is the format for average latency (top-level) MetricsFmtAverageLatencyTop = " Average Latency: %.2f ms\n" + + // MetricsFmtBanOperations is the format for ban operations with failures + MetricsFmtBanOperations = " Ban Operations: %d (failures: %d)\n" + + // MetricsFmtUnbanOperations is the format for unban operations with failures + MetricsFmtUnbanOperations = " Unban Operations: %d (failures: %d)\n" )