From 023d2cde5f035660811f4c8f8dd583ff64322986 Mon Sep 17 00:00:00 2001 From: Adrian Duke <711058+adrianduke@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:13:29 +0100 Subject: [PATCH 1/7] go mod tidy --- go.mod | 9 ++++++--- go.sum | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 230b1e3..02442ab 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,14 @@ module github.com/endiangroup/cmdjail go 1.24.3 +require ( + github.com/kelseyhightower/envconfig v1.4.0 + github.com/spf13/pflag v1.0.6 + github.com/stretchr/testify v1.10.0 +) + require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/pflag v1.0.6 // indirect - github.com/stretchr/testify v1.10.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b75c295..f2ee373 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,7 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 14043bdc1348db49cd54c53accfd46aab7e74816 Mon Sep 17 00:00:00 2001 From: Adrian Duke <711058+adrianduke@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:29:38 +0100 Subject: [PATCH 2/7] add workflow v1 file for lint, test and release --- .github/workflows/ci-cd.yml | 145 ++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 .github/workflows/ci-cd.yml diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..88457e6 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,145 @@ +name: CI/CD + +on: + push: + branches: [main] + tags: + - "v*.*.*" # Trigger on version tags like v1.0.0 + pull_request: + branches: [main] + +permissions: read-all + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: "go.mod" + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.58 # Specify a linter version + args: --timeout=5m + + test: + name: Test + needs: lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for Makefile versioning + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: "go.mod" + + - name: Install ShellSpec + run: | + SHELLSPEC_VERSION=0.28.1 + curl -fsSL "https://github.com/shellspec/shellspec/releases/download/${SHELLSPEC_VERSION}/shellspec-dist.tar.gz" | tar xz + sudo cp shellspec/shellspec /usr/local/bin/ + shellspec --version + + - name: Run Unit Tests + run: make test-units + + - name: Run Feature Tests + run: make test-features + + - name: Run Fuzz Tests + run: make test-fuzz FUZZ_TIME=10s # Shorter duration for CI + + - name: Build for CI check (local OS/ARCH) + run: make bin/cmdjail + + create_release: + name: Create GitHub Release + if: startsWith(github.ref, 'refs/tags/v') + needs: test + runs-on: ubuntu-latest + permissions: + contents: write # To create releases + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref_name }} + release_name: Release ${{ github.ref_name }} + body: | + Automated release for ${{ github.ref_name }}. + See commit history for changes. + draft: false + prerelease: false + + build_and_upload_release_assets: + name: Build and Upload Release Assets + if: startsWith(github.ref, 'refs/tags/v') + needs: create_release + runs-on: ubuntu-latest + permissions: + contents: write # To upload assets to the release + strategy: + matrix: + goos: [linux, darwin] + goarch: [amd64, arm64, 386, arm] + exclude: + - goos: darmin + goarch: 386 + - goos: darmin + goarch: arm + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for Makefile versioning + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: "go.mod" + + - name: Build for ${{ matrix.goos }}/${{ matrix.goarch }} + run: make build GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + + - name: Prepare Asset Names + id: prep_asset + shell: bash + run: | + ARTIFACT_BASENAME="cmdjail-${{ matrix.goos }}-${{ matrix.goarch }}" + ASSET_FILENAME="${ARTIFACT_BASENAME}" + # Move the generic build output to the expected asset name + mv build/cmdjail-${{ matrix.goos }}-${{ matrix.goarch}} build/${ASSET_FILENAME} + echo "asset_path=build/${ASSET_FILENAME}" >> "$GITHUB_OUTPUT" + echo "asset_name=${ASSET_FILENAME}" >> "$GITHUB_OUTPUT" + + - name: Upload Release Asset for ${{ matrix.goos }}/${{ matrix.goarch }} + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ needs.create_release.outputs.upload_url }} + asset_path: ${{ steps.prep_asset.outputs.asset_path }} + asset_name: ${{ steps.prep_asset.outputs.asset_name }} + asset_content_type: application/octet-stream From d3bd30b56f308137da300c4a407734c4836edcaf Mon Sep 17 00:00:00 2001 From: Adrian Duke <711058+adrianduke@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:33:50 +0100 Subject: [PATCH 3/7] attempt to fix golang-ci-lint with upgrade to 1.64 --- .github/workflows/ci-cd.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 88457e6..0c7a2ca 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -4,7 +4,7 @@ on: push: branches: [main] tags: - - "v*.*.*" # Trigger on version tags like v1.0.0 + - "v*.*.*" pull_request: branches: [main] @@ -28,7 +28,7 @@ jobs: - name: Run golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.58 # Specify a linter version + version: v1.64 args: --timeout=5m test: @@ -39,7 +39,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - fetch-depth: 0 # Required for Makefile versioning + fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v5 @@ -71,7 +71,7 @@ jobs: needs: test runs-on: ubuntu-latest permissions: - contents: write # To create releases + contents: write outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: @@ -98,7 +98,7 @@ jobs: needs: create_release runs-on: ubuntu-latest permissions: - contents: write # To upload assets to the release + contents: write strategy: matrix: goos: [linux, darwin] @@ -112,7 +112,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - fetch-depth: 0 # Required for Makefile versioning + fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v5 From 135ff9ae1fd232def3efc70ff272852d64cacb82 Mon Sep 17 00:00:00 2001 From: Adrian Duke <711058+adrianduke@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:42:23 +0100 Subject: [PATCH 4/7] lint fixes and make lint added --- Makefile | 6 ++++++ config.go | 13 +++++++++---- config_test.go | 18 +++++++++--------- jailfile_test.go | 9 +++++---- main.go | 18 +++++++++++++++--- matchers.go | 5 ++++- print-log.go | 10 +--------- 7 files changed, 49 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index acc316a..8214e74 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ ############################################################################### GO ?= go +GOLANGCI_LINT ?= golangci-lint GOOS ?= $(shell go env GOOS) GOARCH ?= $(shell go env GOARCH) FUZZ_TIME ?= 30s @@ -53,6 +54,11 @@ build: ## Build binary for a specific platform (e.g., GOOS=linux GOARCH=arm64 ma @echo "Building for $(GOOS)/$(GOARCH)..." @GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=0 $(GO) build $(LDFLAGS) $(BUILD_VERBOSE_CONTROL) $(BUILD_CACHE_CONTROL) -o build/cmdjail-$(GOOS)-$(GOARCH) . +#-- Linting +.PHONY: lint +lint: ## Run golangci-lint + @$(GOLANGCI_LINT) run $(LINT_VERBOSE_CONTROL) ./... + #-- Testing .PHONY: test test-units test-features test: test-units test-features ## test entire application diff --git a/config.go b/config.go index e6f68b5..3b3e29c 100644 --- a/config.go +++ b/config.go @@ -135,7 +135,7 @@ The intent command can also be set directly via the CMDJAIL_CMD environment vari } } -func parseFlags(envvars envVars) []string { +func parseFlags(envvars envVars) ([]string, error) { pflag.BoolVar(&flagVersion, "version", false, flagVersionDesc) pflag.BoolVarP(&flagVerbose, "verbose", "v", envvars.Verbose, flagVerboseDesc) pflag.StringVarP(&flagLogFile, "log-file", "l", envvars.LogFile, flagLogFileDesc) @@ -147,9 +147,11 @@ func parseFlags(envvars envVars) []string { pflag.StringVarP(&flagShellCmd, "shell-cmd", "s", envvars.ShellCmd, flagShellCmdDesc) args, cmdOptions := splitAtEndOfArgs(os.Args) - pflag.CommandLine.Parse(args) + if err := pflag.CommandLine.Parse(args); err != nil { + return nil, err + } - return cmdOptions + return cmdOptions, nil } func parseEnvVars() (envVars, error) { @@ -231,7 +233,10 @@ func parseEnvAndFlags() (Config, error) { return NoConfig, err } - cmdOptions := parseFlags(envvars) + cmdOptions, err := parseFlags(envvars) + if err != nil { + return NoConfig, err + } if flagJailFile == "-" && flagCheckIntentCmds == "-" { return NoConfig, ErrJailFileAndCheckCmdsFromStdin diff --git a/config_test.go b/config_test.go index 606813f..1024dfc 100644 --- a/config_test.go +++ b/config_test.go @@ -24,14 +24,14 @@ func TestParseEnvAndFlags(t *testing.T) { t.Run("Returns config from environment variables", func(t *testing.T) { setup() // Reset flags and args cmd := "cmd -s --long-flag -a=123 -a 123" - os.Setenv(EnvPrefix+"_CMD", cmd) + assert.NoError(t, os.Setenv(EnvPrefix+"_CMD", cmd)) log := "/tmp/cmdjail.log" - os.Setenv(EnvPrefix+"_LOGFILE", log) + assert.NoError(t, os.Setenv(EnvPrefix+"_LOGFILE", log)) jailfile := "/tmp/.cmd.jail" - os.Setenv(EnvPrefix+"_JAILFILE", jailfile) - os.Setenv(EnvPrefix+"_VERBOSE", "true") + assert.NoError(t, os.Setenv(EnvPrefix+"_JAILFILE", jailfile)) + assert.NoError(t, os.Setenv(EnvPrefix+"_VERBOSE", "true")) shellCmd := "sh -c" - os.Setenv(EnvPrefix+"_SHELL_CMD", shellCmd) + assert.NoError(t, os.Setenv(EnvPrefix+"_SHELL_CMD", shellCmd)) c, err := parseEnvAndFlags() assert.NoError(t, err) @@ -46,8 +46,8 @@ func TestParseEnvAndFlags(t *testing.T) { t.Run("Returns config with command set from EnvReference", func(t *testing.T) { setup() // Reset flags and args cmd := "cmd -s --long-flag -a=123 -a 123" - os.Setenv("CMD", cmd) - os.Setenv(EnvPrefix+"_ENV_REFERENCE", "CMD") + assert.NoError(t, os.Setenv("CMD", cmd)) + assert.NoError(t, os.Setenv(EnvPrefix+"_ENV_REFERENCE", "CMD")) c, err := parseEnvAndFlags() assert.NoError(t, err) @@ -57,8 +57,8 @@ func TestParseEnvAndFlags(t *testing.T) { t.Run("Flag overrides environment variable", func(t *testing.T) { setup("--jail-file", "/flag/path/.cmd.jail", "--shell-cmd", "zsh -c") - os.Setenv(EnvPrefix+"_JAILFILE", "/env/path/.cmd.jail") - os.Setenv(EnvPrefix+"_SHELL_CMD", "bash -c") + assert.NoError(t, os.Setenv(EnvPrefix+"_JAILFILE", "/env/path/.cmd.jail")) + assert.NoError(t, os.Setenv(EnvPrefix+"_SHELL_CMD", "bash -c")) c, err := parseEnvAndFlags() assert.NoError(t, err) diff --git a/jailfile_test.go b/jailfile_test.go index 99a3ecf..8381551 100644 --- a/jailfile_test.go +++ b/jailfile_test.go @@ -201,11 +201,12 @@ func TestCmdMatcher_Matches(t *testing.T) { t.Run("Error on non-executable script", func(t *testing.T) { tmpfile, err := os.CreateTemp("", "test-script") assert.NoError(t, err) - defer os.Remove(tmpfile.Name()) + defer func() { assert.NoError(t, os.Remove(tmpfile.Name())) }() - tmpfile.WriteString("#!/bin/sh\nexit 0") - tmpfile.Close() - os.Chmod(tmpfile.Name(), 0o644) + _, err = tmpfile.WriteString("#!/bin/sh\nexit 0") + assert.NoError(t, err) + assert.NoError(t, tmpfile.Close()) + assert.NoError(t, os.Chmod(tmpfile.Name(), 0o644)) matcher := NewCmdMatcher(m, tmpfile.Name(), shellCmd) matches, err := matcher.Matches("any command") diff --git a/main.go b/main.go index 58ae71f..2998062 100644 --- a/main.go +++ b/main.go @@ -198,7 +198,11 @@ func loadCommandsForCheckMode(conf Config) (commands []string, source string, er printLogErr(os.Stderr, "reading test file %s: %v", conf.CheckIntentCmdsFile, fileErr) return nil, source, fileErr } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + printLogErr(os.Stderr, "closing test commands file %s: %v", conf.CheckIntentCmdsFile, err) + } + }() r = file } commands, err = readLines(r) @@ -276,7 +280,11 @@ func getJailFile(conf Config) JailFile { } // If the reader is a file, ensure it's closed. if closer, ok := jailFileReader.(io.Closer); ok && jailFileReader != os.Stdin { - defer closer.Close() + defer func() { + if err := closer.Close(); err != nil { + printLogErr(os.Stderr, "closing jailfile reader for %s: %v", conf.JailFile, err) + } + }() } jailFile, err := parseJailFile(conf, jailFileReader) @@ -320,7 +328,11 @@ func appendRuleToFile(filepath, intentCmd string) error { if err != nil { return err } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + printLogErr(os.Stderr, "closing record file %s: %v", filepath, err) + } + }() rule := fmt.Sprintf("+ '%s\n", intentCmd) if _, err := f.WriteString(rule); err != nil { diff --git a/matchers.go b/matchers.go index 0a42a42..877780b 100644 --- a/matchers.go +++ b/matchers.go @@ -120,7 +120,10 @@ func (c CmdMatcher) Matches(intentCmd string) (bool, error) { if err = cmd.Start(); err != nil { return false, err } - w.Write([]byte(intentCmd)) + if _, err = w.Write([]byte(intentCmd)); err != nil { + _ = w.Close() // Best effort close + return false, err + } if err = w.Close(); err != nil { return false, err } diff --git a/print-log.go b/print-log.go index a304fb4..f2f6295 100644 --- a/print-log.go +++ b/print-log.go @@ -31,7 +31,7 @@ func setLoggerToFile(path string) error { } func printMsg(printTo io.Writer, msg string, args ...any) { - fmt.Fprintf(printTo, msg+"\n", args...) + _, _ = fmt.Fprintf(printTo, msg+"\n", args...) } func logMsg(msg string, args ...any) { @@ -43,14 +43,6 @@ func printLog(printTo io.Writer, msg string, args ...any) { logMsg(msg+"\n", args...) } -func printErr(printTo io.Writer, msg string, args ...any) { - printMsg(printTo, "[error] "+msg, args...) -} - -func logErr(msg string, args ...any) { - logMsg("[error] "+msg, args...) -} - func logWarn(msg string, args ...any) { logMsg("[warn] "+msg, args...) } From 43133a18e67d0717d8251346215a0bc250c08921 Mon Sep 17 00:00:00 2001 From: Adrian Duke <711058+adrianduke@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:46:34 +0100 Subject: [PATCH 5/7] install shellspec from install.sh with version specifier --- .github/workflows/ci-cd.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 0c7a2ca..1a0e887 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -49,8 +49,7 @@ jobs: - name: Install ShellSpec run: | SHELLSPEC_VERSION=0.28.1 - curl -fsSL "https://github.com/shellspec/shellspec/releases/download/${SHELLSPEC_VERSION}/shellspec-dist.tar.gz" | tar xz - sudo cp shellspec/shellspec /usr/local/bin/ + curl -fsSL https://git.io/shellspec | sh -s ${SHELLSPEC_VERSION} --yes shellspec --version - name: Run Unit Tests From 91b434f42ae798cbb246d038231e873f8a6e93fa Mon Sep 17 00:00:00 2001 From: Adrian Duke <711058+adrianduke@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:51:05 +0100 Subject: [PATCH 6/7] fix incorrect Makefile dependency --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8214e74..28b084e 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ help: | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m %-43s\033[0m %s\n", $$1, $$2}' \ | sed -e 's/\[32m #-- /[33m/' -bin/cmdjail: bin *.go +bin/cmdjail: *.go @mkdir -p bin @$(GO) build $(LDFLAGS) $(BUILD_VERBOSE_CONTROL) $(BUILD_CACHE_CONTROL) -o bin/cmdjail . From 2b47c67022f40c33ea219b3f094eb6584553b092 Mon Sep 17 00:00:00 2001 From: Adrian Duke <711058+adrianduke@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:55:15 +0100 Subject: [PATCH 7/7] drop useless step --- .github/workflows/ci-cd.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 1a0e887..351d3b1 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -61,9 +61,6 @@ jobs: - name: Run Fuzz Tests run: make test-fuzz FUZZ_TIME=10s # Shorter duration for CI - - name: Build for CI check (local OS/ARCH) - run: make bin/cmdjail - create_release: name: Create GitHub Release if: startsWith(github.ref, 'refs/tags/v')