From 88eb6e85a5cf41bbf57e0346e618ba842b089554 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Sun, 14 Dec 2025 22:32:10 -0800 Subject: [PATCH 01/21] feat: native credstore implementation with docker-credential-helpers + testing Signed-off-by: ayush-panta --- .github/workflows/benchmark.yaml | 4 + .github/workflows/test-cred-security.yaml | 130 +++++ .gitignore | 1 + Makefile | 19 +- Makefile.creds | 144 +++++ cleanup-windows.ps1 | 35 ++ .../com.runfinch.cred-bridge.plist | 44 ++ .../com.runfinch.cred-bridge.plist.template | 44 ++ cmd/finch-credhelper/helper-utils.go | 126 +++++ cmd/finch-credhelper/mac-creds.go | 39 ++ cmd/finch-credhelper/windows-creds.go | 72 +++ cmd/finch/main_native_test.go | 1 + cmd/finch/nerdctl_native_test.go | 2 + cmd/finch/support_bundle.go | 14 +- cmd/finch/version_native_test.go | 2 + cmd/finch/virtual_machine_init.go | 24 + contrib/packaging/rpm/finch.spec | 35 +- deps/finch-core | 2 +- dev-prod-setup.sh | 101 ++++ dev-setup-windows-fixed.ps1 | 62 +++ dev-setup-windows.ps1 | 64 +++ dev-setup.sh | 35 ++ docs/cmd/finch_support-bundle_generate.md | 4 +- e2e/vm/support_bundle_remote_test.go | 505 ++++++++---------- finch.yaml.d/mac.yaml | 6 + finch.yaml.d/windows.yaml | 5 + fix-wsl-init.ps1 | 42 ++ go.mod | 2 +- go.sum | 4 +- patch-lima-wsl2.ps1 | 15 + pkg/command/nerdctl_native_test.go | 1 + pkg/config/config_native_test.go | 3 +- pkg/config/cred-helper-overwrite.sh | 29 + pkg/config/defaults_darwin.go | 9 +- pkg/config/defaults_windows.go | 8 + pkg/config/nerdctl_config_applier.go | 30 +- pkg/mocks/pkg_support_config.go | 14 - pkg/support/config.go | 5 +- pkg/support/config_native_linux.go | 4 - pkg/support/config_remote.go | 4 - pkg/support/support.go | 56 +- pkg/support/support_test.go | 63 +-- test-creds-windows.ps1 | 67 +++ 43 files changed, 1445 insertions(+), 431 deletions(-) create mode 100644 .github/workflows/test-cred-security.yaml create mode 100644 Makefile.creds create mode 100644 cleanup-windows.ps1 create mode 100755 cmd/finch-credhelper/com.runfinch.cred-bridge.plist create mode 100644 cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template create mode 100644 cmd/finch-credhelper/helper-utils.go create mode 100644 cmd/finch-credhelper/mac-creds.go create mode 100644 cmd/finch-credhelper/windows-creds.go create mode 100755 dev-prod-setup.sh create mode 100644 dev-setup-windows-fixed.ps1 create mode 100644 dev-setup-windows.ps1 create mode 100755 dev-setup.sh create mode 100644 fix-wsl-init.ps1 create mode 100644 patch-lima-wsl2.ps1 create mode 100644 pkg/config/cred-helper-overwrite.sh create mode 100644 test-creds-windows.ps1 diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml index 4cf291fb2..cfe0a978a 100644 --- a/.github/workflows/benchmark.yaml +++ b/.github/workflows/benchmark.yaml @@ -69,5 +69,9 @@ jobs: tool: 'go' benchmark-data-dir-path: "dev/bench/macOS/${{ env.OS_VERSION }}/${{ env.ARCH }}" output-file-path: benchmark.txt + alert-threshold: "200%" + fail-on-alert: true + comment-on-alert: true + alert-comment-cc-users: '@runfinch/maintainers' - name: Push benchmark result run: git push 'https://github.com/runfinch/finch.git' gh-pages:gh-pages diff --git a/.github/workflows/test-cred-security.yaml b/.github/workflows/test-cred-security.yaml new file mode 100644 index 000000000..ae033471a --- /dev/null +++ b/.github/workflows/test-cred-security.yaml @@ -0,0 +1,130 @@ +name: test-cred-security +on: + pull_request: + branches: + - main + schedule: + - cron: '0 8 * * *' + workflow_dispatch: + +permissions: + id-token: write + contents: read + +env: + GO_VERSION: '1.24.6' + +jobs: + macos-keychain: + runs-on: ["self-hosted", "macos", "arm64", "14", "test"] + timeout-minutes: 120 + steps: + - name: Clean macOS runner workspace + run: | + rm -rf ${{ github.workspace }}/* + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + persist-credentials: false + submodules: recursive + + - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: false + + - name: Clean up previous files + run: | + sudo rm -rf /opt/finch + sudo rm -rf ~/.finch + sudo rm -rf ./_output + if pgrep '^qemu-system'; then + sudo pkill '^qemu-system' + fi + if pgrep '^socket_vmnet'; then + sudo pkill '^socket_vmnet' + fi + + - name: Install Rosetta 2 + run: echo "A" | softwareupdate --install-rosetta || true + + - run: brew install lz4 automake autoconf libtool yq + shell: zsh {0} + + - name: Build project + run: | + export PATH="/opt/homebrew/opt/libtool/libexec/gnubin:$PATH" + make + shell: zsh {0} + + - name: Initialize Finch VM + run: | + ./_output/bin/finch vm init + shell: zsh {0} + + - name: Setup local registry with auth + run: | + # Create registry directories + mkdir -p /tmp/registry/{data,auth} + + # Create htpasswd file + docker run --rm --entrypoint htpasswd httpd:2 -Bbn testuser testpass > /tmp/registry/auth/htpasswd + + # Start local registry with auth + ./_output/bin/finch run -d \ + --name registry \ + -p 5000:5000 \ + -v /tmp/registry/data:/var/lib/registry \ + -v /tmp/registry/auth:/auth \ + -e "REGISTRY_AUTH=htpasswd" \ + -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ + -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \ + registry:2 + shell: zsh {0} + + - name: Test credential security + run: | + # Test 1: Pull public image (should work) + ./_output/bin/finch pull alpine + + # Test 2: Login to local registry + echo "testpass" | ./_output/bin/finch login localhost:5000 -u testuser --password-stdin + + # Test 3: Check credentials not stored in plaintext + if grep -r "testpass" ~/.finch/ 2>/dev/null; then + echo "ERROR: Password found in plaintext" + exit 1 + fi + + # Test 4: Push/pull from private registry + ./_output/bin/finch tag alpine localhost:5000/test-image + ./_output/bin/finch push localhost:5000/test-image + ./_output/bin/finch rmi localhost:5000/test-image + ./_output/bin/finch pull localhost:5000/test-image + + # Test 5: Logout and verify access denied + ./_output/bin/finch logout localhost:5000 + if ./_output/bin/finch push localhost:5000/test-image2 2>/dev/null; then + echo "ERROR: Push succeeded after logout" + exit 1 + fi + shell: zsh {0} + + - name: Cleanup + if: always() + run: | + ./_output/bin/finch rm -f registry || true + ./_output/bin/finch vm stop || true + rm -rf /tmp/registry || true + shell: zsh {0} + + windows-cred-manager: + runs-on: ["self-hosted", "windows", "amd64", "test"] + timeout-minutes: 120 + steps: + - name: Setup Windows credential tests + run: | + # Windows credential manager tests + echo "Windows credential tests placeholder" + diff --git a/.gitignore b/.gitignore index 709377370..1c0d1160e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ test-coverage.* *.syso msi-builder/build/ contrib/packaging/rpm/rpmbuild +*.log diff --git a/Makefile b/Makefile index 6ce060aa7..fe1342499 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,11 @@ endif FINCH_CORE_DIR := $(CURDIR)/deps/finch-core -remote-all: arch-test finch install.finch-core-dependencies finch.yaml networks.yaml config.yaml $(OUTDIR)/finch-daemon/finch@.service +ifeq ($(BUILD_OS), Windows_NT) +remote-all: arch-test finch finch-cred-bridge docker-credential-helper install.finch-core-dependencies finch.yaml networks.yaml config.yaml $(OUTDIR)/finch-daemon/finch@.service +else +remote-all: arch-test finch finch-cred-bridge docker-credential-helper install.finch-core-dependencies finch.yaml networks.yaml config.yaml $(OUTDIR)/finch-daemon/finch@.service setup-cred-bridge +endif ifeq ($(BUILD_OS), Windows_NT) include Makefile.windows @@ -92,6 +96,9 @@ else ifeq ($(BUILD_OS), Linux) all: finch endif +# Include credential helper targets +include Makefile.creds + .PHONY: install.finch-core-dependencies install.finch-core-dependencies: OUTDIR=$(OUTDIR) ARCH=$(ARCH) "$(MAKE)" -C $(FINCH_CORE_DIR) install.dependencies @@ -260,6 +267,8 @@ download-licenses: mkdir -p "$(LICENSEDIR)/github.com/lima-vm/lima" curl https://raw.githubusercontent.com/lima-vm/lima/master/LICENSE --output "$(LICENSEDIR)/github.com/lima-vm/lima/LICENSE" + mkdir -p "$(LICENSEDIR)/github.com/docker/docker-credential-helpers" + curl https://raw.githubusercontent.com/docker/docker-credential-helpers/master/LICENSE --output "$(LICENSEDIR)/github.com/docker/docker-credential-helpers/LICENSE" ### system-level dependencies - end ### @@ -406,6 +415,14 @@ mdlint: mdlint-ctr: $(BINARYNAME) run --rm -v "$(shell pwd):/repo:ro" -w /repo avtodev/markdown-lint:v1 --ignore CHANGELOG.md '**/*.md' +.PHONY: dev-clean +dev-clean: + -@rm -rf $(OUTDIR) 2>/dev/null || true + -@$(MAKE) -C $(FINCH_CORE_DIR) clean + -@rm ./*.tar.gz 2>/dev/null || true + -@rm ./*.qcow2 2>/dev/null || true + -@rm ./test-coverage.* 2>/dev/null || true + .PHONY: clean ifeq ($(GOOS),windows) clean: diff --git a/Makefile.creds b/Makefile.creds new file mode 100644 index 000000000..5b20ade57 --- /dev/null +++ b/Makefile.creds @@ -0,0 +1,144 @@ +CRED_HELPER_BASE_URL := https://github.com/docker/docker-credential-helpers/releases/download/v0.9.4 + +DARWIN_ARM64_ARTIFACT := docker-credential-osxkeychain-v0.9.4.darwin-arm64 +DARWIN_ARM64_256_DIGEST := 8db5b7cbcbe0870276e56aa416416161785e450708af64cda0f1be4c392dc2e5 + +DARWIN_AMD64_ARTIFACT := docker-credential-osxkeychain-v0.9.4.darwin-amd64 +DARWIN_AMD64_256_DIGEST := ad76d1a1e03def49edfa57fdb2874adf2c468cfa0438aae1b2589434796f7c01 + +WINDOWS_ARM64_ARTIFACT := docker-credential-wincred-v0.9.4.windows-arm64.exe +WINDOWS_ARM64_256_DIGEST := 80a6ddbbabc51a8952308acf4d03c044308357cf217300461c44df066c57fe03 + +WINDOWS_AMD64_ARTIFACT := docker-credential-wincred-v0.9.4.windows-amd64.exe +WINDOWS_AMD64_256_DIGEST := 66fdf4b50c83aeb04a9ea04af960abaf1a7b739ab263115f956b98bb0d16aa7e + +# Platform-specific credential helper configuration +ifeq ($(BUILD_OS), Darwin) +CRED_HELPER_ARTIFACT := $(if $(findstring arm64,$(ARCH)),$(DARWIN_ARM64_ARTIFACT),$(DARWIN_AMD64_ARTIFACT)) +CRED_HELPER_DIGEST := $(if $(findstring arm64,$(ARCH)),$(DARWIN_ARM64_256_DIGEST),$(DARWIN_AMD64_256_DIGEST)) +CRED_HELPER_NAME := docker-credential-osxkeychain +else ifeq ($(BUILD_OS), Windows_NT) +CRED_HELPER_ARTIFACT := $(if $(findstring arm64,$(ARCH)),$(WINDOWS_ARM64_ARTIFACT),$(WINDOWS_AMD64_ARTIFACT)) +CRED_HELPER_DIGEST := $(if $(findstring arm64,$(ARCH)),$(WINDOWS_ARM64_256_DIGEST),$(WINDOWS_AMD64_256_DIGEST)) +CRED_HELPER_NAME := docker-credential-wincred +endif + +CRED_HELPER_URL := $(CRED_HELPER_BASE_URL)/$(CRED_HELPER_ARTIFACT) +CRED_HELPER_OUTPUT := $(OUTDIR)/finch-credhelper/$(CRED_HELPER_NAME)$(if $(findstring .exe,$(CRED_HELPER_ARTIFACT)),.exe,) +CRED_HELPER_VM_PATH := ~/.finch/cred-helpers/$(CRED_HELPER_NAME) + +.PHONY: finch-cred-bridge +finch-cred-bridge: + mkdir -p $(OUTDIR)/finch-credhelper + $(GO) build -ldflags $(LDFLAGS) -tags "$(GO_BUILD_TAGS)" -o $(OUTDIR)/finch-credhelper/finch-cred-bridge $(PACKAGE)/cmd/finch-credhelper + +.PHONY: docker-credential-helper +docker-credential-helper: +ifeq ($(BUILD_OS), Linux) + @echo "No credential helper needed for Linux" +else ifeq ($(BUILD_OS), Windows_NT) + @"$(MAKE)" install-credential-helper create-dummy-helper +else + @$(MAKE) install-credential-helper create-dummy-helper +endif + +.PHONY: install-credential-helper +install-credential-helper: + mkdir -p $(dir $(CRED_HELPER_OUTPUT)) + curl -L $(CRED_HELPER_URL) -o $(CRED_HELPER_OUTPUT) + @echo "Verifying SHA256 checksum..." + @if echo "$(CRED_HELPER_DIGEST) $(CRED_HELPER_OUTPUT)" | $(if $(findstring Darwin,$(BUILD_OS)),shasum -a 256,sha256sum) -c -; then \ + echo "Checksum verification passed"; \ + else \ + echo "Checksum verification failed" && exit 1; \ + fi + chmod +x $(CRED_HELPER_OUTPUT) + +.PHONY: create-dummy-helper +create-dummy-helper: +ifeq ($(BUILD_OS), Windows_NT) + mkdir -p "$(USERPROFILE)/.finch/cred-helpers/" + cp "$(CRED_HELPER_OUTPUT)" "$(USERPROFILE)/.finch/cred-helpers/$(CRED_HELPER_NAME)" +else + mkdir -p $(dir $(CRED_HELPER_VM_PATH)) + cp $(CRED_HELPER_OUTPUT) $(CRED_HELPER_VM_PATH) +endif + +# LaunchAgent plist management (macOS only) +ifeq ($(BUILD_OS), Darwin) +PLIST_NAME := com.runfinch.cred-bridge.plist +PLIST_SOURCE := cmd/finch-credhelper/$(PLIST_NAME) +PLIST_TEMPLATE := cmd/finch-credhelper/$(PLIST_NAME).template +PLIST_DEST := $(HOME)/Library/LaunchAgents/$(PLIST_NAME) + +.PHONY: generate-plist +generate-plist: + @echo "Generating plist with current paths..." + sed -e "s|\$$HOME|$(HOME)|g" \ + -e "s|\$$USER|$(USER)|g" \ + -e "s|\$$FINCH_CRED_BRIDGE_PATH|$(CURDIR)/_output/finch-credhelper/finch-cred-bridge|g" \ + $(PLIST_TEMPLATE) > $(PLIST_SOURCE) + +.PHONY: install-plist +install-plist: generate-plist + @echo "Installing LaunchAgent plist..." + cp $(PLIST_SOURCE) $(PLIST_DEST) + launchctl load $(PLIST_DEST) + @echo "LaunchAgent loaded successfully" + +.PHONY: uninstall-plist +uninstall-plist: + @echo "Uninstalling LaunchAgent plist..." + -launchctl unload $(PLIST_DEST) 2>/dev/null || true + -rm $(PLIST_DEST) 2>/dev/null || true + @echo "LaunchAgent uninstalled" + +.PHONY: reload-plist +reload-plist: uninstall-plist install-plist + @echo "LaunchAgent reloaded" + +.PHONY: dev-install +dev-install: + @echo "Creating development symlinks to mimic /Applications/Finch installation..." + sudo mkdir -p /Applications/Finch/bin/ + sudo ln -sf $(CURDIR)/_output/bin/finch-cred-bridge /Applications/Finch/bin/finch-cred-bridge + sudo ln -sf $(CURDIR)/_output/bin/finch /Applications/Finch/bin/finch + @echo "Development installation complete - finch appears to be in /Applications/Finch" + +.PHONY: dev-uninstall +dev-uninstall: + @echo "Removing development symlinks..." + -sudo rm /Applications/Finch/bin/finch-cred-bridge 2>/dev/null || true + -sudo rm /Applications/Finch/bin/finch 2>/dev/null || true + @echo "Development installation removed" + +.PHONY: setup-cred-bridge +setup-cred-bridge: +ifeq ($(BUILD_OS), Darwin) + @echo "Setting up credential bridge..." + @if [ ! -L "/Applications/Finch/bin/finch-cred-bridge" ]; then \ + echo "Creating credential bridge symlink (requires sudo)..."; \ + sudo mkdir -p /Applications/Finch/bin/; \ + sudo ln -sf $(CURDIR)/_output/bin/finch-cred-bridge /Applications/Finch/bin/finch-cred-bridge; \ + fi + @if [ ! -L "/Applications/Finch/bin/finch" ]; then \ + echo "Creating finch binary symlink (requires sudo)..."; \ + sudo ln -sf $(CURDIR)/_output/bin/finch /Applications/Finch/bin/finch; \ + fi + @if ! launchctl list | grep -q com.runfinch.cred-bridge; then \ + echo "Installing LaunchAgent..."; \ + $(MAKE) install-plist; \ + else \ + echo "LaunchAgent already loaded"; \ + fi + @echo "Credential bridge setup complete" +else ifeq ($(BUILD_OS), Windows_NT) + @echo "Credential bridge setup for Windows - no additional setup needed" +else + @echo "Credential bridge setup not supported on this platform" +endif +else +.PHONY: install-plist uninstall-plist reload-plist +install-plist uninstall-plist reload-plist: + @echo "LaunchAgent plist management is macOS-specific" +endif \ No newline at end of file diff --git a/cleanup-windows.ps1 b/cleanup-windows.ps1 new file mode 100644 index 000000000..0d6843592 --- /dev/null +++ b/cleanup-windows.ps1 @@ -0,0 +1,35 @@ +# Clean up corrupted Finch VM state on Windows + +Write-Host "Cleaning up corrupted Finch VM state..." -ForegroundColor Yellow + +# Stop any running processes +Write-Host "Stopping Finch processes..." -ForegroundColor Green +Get-Process -Name "*finch*" -ErrorAction SilentlyContinue | Stop-Process -Force +Get-Process -Name "*lima*" -ErrorAction SilentlyContinue | Stop-Process -Force + +# Remove Lima data directories +Write-Host "Removing Lima data directories..." -ForegroundColor Green +$limaDataPaths = @( + "$env:USERPROFILE\.lima", + "$env:LOCALAPPDATA\.finch", + "C:\Users\Administrator\Documents\finch\_output\lima" +) + +foreach ($path in $limaDataPaths) { + if (Test-Path $path) { + Write-Host " Removing: $path" -ForegroundColor Cyan + Remove-Item -Path $path -Recurse -Force -ErrorAction SilentlyContinue + } +} + +# Clean WSL distributions +Write-Host "Cleaning WSL distributions..." -ForegroundColor Green +$wslDistros = wsl --list --quiet 2>$null | Where-Object { $_ -match "finch|lima" } +foreach ($distro in $wslDistros) { + if ($distro.Trim()) { + Write-Host " Unregistering WSL distro: $($distro.Trim())" -ForegroundColor Cyan + wsl --unregister $distro.Trim() 2>$null + } +} + +Write-Host "Cleanup complete! You can now run vm init." -ForegroundColor Green \ No newline at end of file diff --git a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist new file mode 100755 index 000000000..6b043456c --- /dev/null +++ b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist @@ -0,0 +1,44 @@ + + + + + + Label + com.runfinch.cred-bridge + + ProgramArguments + + /bin/sh + -c + exec /Users/ayushkp/Documents/finch-creds/finch/_output/finch-credhelper/finch-cred-bridge + + + Sockets + + Listeners + + + SockPathName + /Users/ayushkp/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock + SockPathMode + 384 + + + + + StandardOutPath + /Users/ayushkp/.finch/cred-bridge.log + StandardErrorPath + /Users/ayushkp/.finch/cred-bridge.log + + inetdCompatibility + + Wait + + + + + + + + \ No newline at end of file diff --git a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template new file mode 100644 index 000000000..1c6d19fba --- /dev/null +++ b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template @@ -0,0 +1,44 @@ + + + + + + Label + com.runfinch.cred-bridge + + ProgramArguments + + /bin/sh + -c + exec $FINCH_CRED_BRIDGE_PATH + + + Sockets + + Listeners + + + SockPathName + $HOME/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock + SockPathMode + 384 + + + + + StandardOutPath + $HOME/.finch/cred-bridge.log + StandardErrorPath + $HOME/.finch/cred-bridge.log + + inetdCompatibility + + Wait + + + + + + + + \ No newline at end of file diff --git a/cmd/finch-credhelper/helper-utils.go b/cmd/finch-credhelper/helper-utils.go new file mode 100644 index 000000000..e954c6d9a --- /dev/null +++ b/cmd/finch-credhelper/helper-utils.go @@ -0,0 +1,126 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "github.com/docker/docker-credential-helpers/credentials" +) + +const ( + maxBufferSize = 4096 +) + +func parseCredstoreRequest(request string) (command, input string, err error) { + + lines := strings.Split(request, "\n") + if len(lines) == 0 { + return "", "", fmt.Errorf("ERROR: Empty request.") + } + + command = lines[0] + if command == "list" { // too keep or not to keep? + return command, "", nil + } + if len(lines) != 2 { + return "", "", fmt.Errorf("ERROR: command %s requires input", command) + } + + return command, lines[1], nil +} + +func forwardToCredHelper(command, input string) (string, error) { + log.Printf("Forwarding command: %s, input: %s", command, input) + + credHelperPath, err := getCredentialHelperPath() + if err != nil { + return "", err + } + + cmd := exec.Command(credHelperPath, command) + cmd.Stdin = strings.NewReader(input) + cmd.Env = os.Environ() + + output, err := cmd.CombinedOutput() + response := strings.TrimSpace(string(output)) + + log.Printf("Raw output: %q", string(output)) + log.Printf("Error: %v", err) + if cmd.ProcessState != nil { + log.Printf("Exit code: %d", cmd.ProcessState.ExitCode()) + } + + if err != nil { + log.Printf("Credential helper failed: %s", response) + if command == "get" { + emptyCreds := credentials.Credentials{ServerURL: input, Username: "", Secret: ""} + credsJSON, _ := json.Marshal(emptyCreds) + log.Printf("Returning empty credentials: %s", string(credsJSON)) + return string(credsJSON), nil + } + return response, err + } + + log.Printf("Credential helper SUCCESS - response: %s", response) + return response, nil +} + +func getCredentialHelperPath() (string, error) { + var helperName string + switch runtime.GOOS { + case "darwin": + helperName = "docker-credential-osxkeychain" + case "windows": + helperName = "docker-credential-wincred.exe" + default: + return "", fmt.Errorf("unsupported platform: %s", runtime.GOOS) // ? + } + + path := filepath.Join(os.Getenv("HOME"), ".finch", "cred-helpers", helperName) + _, err := os.Stat(path) + if err != nil { + return "", fmt.Errorf("ERROR: %s not found", helperName) + } + return path, nil +} + +// Core credential processing logic - shared across platforms +func processCredentialRequest(conn interface{ Read([]byte) (int, error); Write([]byte) (int, error) }) error { + // read from buffer + buffer := make([]byte, maxBufferSize) + data, err := conn.Read(buffer) + if err != nil { + return fmt.Errorf("ERROR: read error: %w", err) + } + + // parse request + request := strings.TrimSpace(string(buffer[:data])) + command, input, err := parseCredstoreRequest(request) + if err != nil { + return fmt.Errorf("ERROR: %w", err) + } + + // forward and handle request + response, err := forwardToCredHelper(command, input) + if err != nil { + log.Printf("Credential helper error: %v", err) + } + + // handle credential not found + if strings.Contains(response, "credentials not found") { + response = "" + log.Printf("Credentials not found - returning empty response") + } else { + log.Printf("Response to VM: %q", response) + } + + // write back to connection + _, writeErr := conn.Write([]byte(response)) + return writeErr +} diff --git a/cmd/finch-credhelper/mac-creds.go b/cmd/finch-credhelper/mac-creds.go new file mode 100644 index 000000000..a6f3b095a --- /dev/null +++ b/cmd/finch-credhelper/mac-creds.go @@ -0,0 +1,39 @@ +//go:build darwin + +package main + +import ( + "fmt" + "log" + "net" + "os" + "strings" +) + +// macOS socket activation handler +func handleCredstoreRequest() error { + // connect to socket, defer close til after return + conn, err := net.FileConn(os.Stdin) + if err != nil { + return fmt.Errorf("ERROR: failed to get connection from stdin: %w", err) + } + defer conn.Close() + + // use shared credential processing logic + return processCredentialRequest(conn) +} + +func darwinKeychainHandler() { + // Test if stdin is a network connection (inetd style) + if _, err := net.FileConn(os.Stdin); err == nil { + if err := handleCredstoreRequest(); err != nil { + os.Exit(1) + } + } else { + os.Exit(0) + } +} + +func main() { + darwinKeychainHandler() +} \ No newline at end of file diff --git a/cmd/finch-credhelper/windows-creds.go b/cmd/finch-credhelper/windows-creds.go new file mode 100644 index 000000000..70a110994 --- /dev/null +++ b/cmd/finch-credhelper/windows-creds.go @@ -0,0 +1,72 @@ +//go:build windows + +package main + +import ( + "fmt" + "log" + "net" + "os" + "path/filepath" +) + +// Windows socket server (for WSL2 socket forwarding) +func startWindowsCredentialServer() error { + // Create socket path in user's Documents/finch-creds directory + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get home directory: %w", err) + } + + // Setup file logging + logPath := filepath.Join(homeDir, "Documents", "finch-creds", "finch", "_output", "finch-credhelper", "cred-bridge.log") + logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("failed to open log file: %w", err) + } + defer logFile.Close() + log.SetOutput(logFile) + + socketPath := filepath.Join(homeDir, "Documents", "finch-creds", "finch", "_output", "finch-credhelper", "native-creds.sock") + log.Printf("Starting Windows credential server on socket: %s", socketPath) + + // Remove existing socket if it exists + os.Remove(socketPath) + + // Listen on Unix socket + listener, err := net.Listen("unix", socketPath) + if err != nil { + return fmt.Errorf("failed to create socket: %w", err) + } + defer listener.Close() + + log.Printf("Windows credential server listening...") + + // Accept connections + for { + conn, err := listener.Accept() + if err != nil { + log.Printf("Failed to accept connection: %v", err) + continue + } + + // Handle each connection + go func(c net.Conn) { + defer c.Close() + if err := processCredentialRequest(c); err != nil { + log.Printf("Error processing credential request: %v", err) + } + }(conn) + } +} + +func windowsKeychainHandler() { + if err := startWindowsCredentialServer(); err != nil { + log.Printf("Windows credential server failed: %v", err) + os.Exit(1) + } +} + +func main() { + windowsKeychainHandler() +} \ No newline at end of file diff --git a/cmd/finch/main_native_test.go b/cmd/finch/main_native_test.go index 79c6743c7..93b5d2d5b 100644 --- a/cmd/finch/main_native_test.go +++ b/cmd/finch/main_native_test.go @@ -88,6 +88,7 @@ func TestXmain(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/cmd/finch/nerdctl_native_test.go b/cmd/finch/nerdctl_native_test.go index c055d3076..d4400ef1e 100644 --- a/cmd/finch/nerdctl_native_test.go +++ b/cmd/finch/nerdctl_native_test.go @@ -43,6 +43,7 @@ func TestNerdctlCommand_runAdaptor(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -159,6 +160,7 @@ func TestNerdctlCommand_run(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/cmd/finch/support_bundle.go b/cmd/finch/support_bundle.go index f77db0ef7..5d3b1c20d 100644 --- a/cmd/finch/support_bundle.go +++ b/cmd/finch/support_bundle.go @@ -33,19 +33,15 @@ func newSupportBundleGenerateCommand(logger flog.Logger, builder support.BundleB RunE: newGenerateSupportBundleAction(logger, builder, ncc).runAdapter, } - includeUsage := `additional files to include in the support bundle, specified by absolute or relative path.` + - `To include journal logs for a service, prefix the file path with "service:".` + includeUsage := "additional files to include in the support bundle, specified by absolute or relative path." if runtime.GOOS != "linux" { - includeUsage += ` To include a file from the VM, prefix the file path with "vm:"` + includeUsage += `To include a file from the VM, prefix the file path with "vm:"` } - excludeUsage := `files to exclude from the support bundle. ` + - `If you specify a base name, all files matching that base name will be excluded. ` + - `If you specify an absolute or relative path, only exact matches will be excluded.` + - `To exclude journal logs for a service, prefix the file path with "service":".` + - `To exclude all journal logs, use "service:all"` supportBundleGenerateCommand.Flags().StringArray("include", []string{}, includeUsage) - supportBundleGenerateCommand.Flags().StringArray("exclude", []string{}, excludeUsage) + supportBundleGenerateCommand.Flags().StringArray("exclude", []string{}, + //nolint:lll // usage string + "files to exclude from the support bundle. if you specify a base name, all files matching that base name will be excluded. if you specify an absolute or relative path, only exact matches will be excluded") return supportBundleGenerateCommand } diff --git a/cmd/finch/version_native_test.go b/cmd/finch/version_native_test.go index ec7b6d39d..a9fd7f996 100644 --- a/cmd/finch/version_native_test.go +++ b/cmd/finch/version_native_test.go @@ -115,6 +115,7 @@ func TestVersionAction_runAdaptor(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -294,6 +295,7 @@ func TestVersionAction_run(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/cmd/finch/virtual_machine_init.go b/cmd/finch/virtual_machine_init.go index d295180e1..b2db94963 100644 --- a/cmd/finch/virtual_machine_init.go +++ b/cmd/finch/virtual_machine_init.go @@ -7,6 +7,9 @@ package main import ( "fmt" + "os" + "os/exec" + "path/filepath" "runtime" "github.com/runfinch/finch/pkg/disk" @@ -123,6 +126,27 @@ func (iva *initVMAction) run() error { } iva.logger.Info("Finch virtual machine started successfully") + + if runtime.GOOS == "darwin" { + // Install credential helper launchd service + plistSrc := filepath.Join(os.Getenv("HOME"), "Documents/finch-creds/finch/cmd/finch/cred-helper/Info.plist") + plistDst := filepath.Join(os.Getenv("HOME"), "Library/LaunchAgents/com.runfinch.credhelper.plist") + + // Copy plist file + cpCmd := exec.Command("cp", plistSrc, plistDst) + if err := cpCmd.Run(); err != nil { + iva.logger.Warnf("Failed to copy credential helper plist: %v", err) + } else { + // Load the service + loadCmd := exec.Command("launchctl", "load", plistDst) + if err := loadCmd.Run(); err != nil { + iva.logger.Warnf("Failed to load credential helper service: %v", err) + } else { + iva.logger.Info("Credential helper service installed successfully") + } + } + } + return nil } diff --git a/contrib/packaging/rpm/finch.spec b/contrib/packaging/rpm/finch.spec index f8a289448..7c60e72ec 100644 --- a/contrib/packaging/rpm/finch.spec +++ b/contrib/packaging/rpm/finch.spec @@ -4,7 +4,40 @@ %global debug_package %{nil} %global pkg_config ../config -# finch +# default versions and commit ids +# actual values are passed as build options to rpmbuild in contrib/packaging/rpm/build.sh +%global _finch_release 1.2.3 +%global _finch_commit b84b424926d5f4e2d2abf0c51507856a73221e9d +%global _buildkit_release 0.15.1 +%global _buildkit_commit 979542e90f2cb38077c808e0867d8d2c16ed10b8 +%global _soci_release 0.7.0 +%global _soci_commit 7c6fae2c3848fe8ad161ce35d3423898cea5fde8 +%global _finch_daemon_release 0.19.1 +%global _finch_daemon_commit 7ee991cb3be01fdb0013649b9e8fc6b5e3c5a35d +%global _cosign_release 2.4.0 +%global _cosign_commit b5e7dc123a272080f4af4554054797296271e902 +%global _min_containerd_version >=1.7.24 +%global _min_nerdctl_version >=2.1.5 +%global _min_cni_plugins_version >=1.7.0 + +# build_latest takes precendence because build_local is for debugging +%if %{undefined build_latest} && %{undefined build_local} +# if neither is defined, fall back to default values +%global finch_release %{_finch_release} +%global finch_commit %{_finch_commit} +%global buildkit_release %{_buildkit_release} +%global buildkit_commit %{_buildkit_commit} +%global soci_release %{_soci_release} +%global soci_commit %{_soci_commit} +%global finch_daemon_release %{_finch_daemon_release} +%global finch_daemon_commit %{_finch_daemon_commit} +%global cosign_release %{_cosign_release} +%global cosign_commit %{_cosign_commit} +%global min_containerd_version %{_min_containerd_version} +%global min_nerdctl_version %{_min_nerdctl_version} +%global min_cni_plugins_version %{_min_cni_plugins_version} +%endif + %global finch_package github.com/runfinch/finch %global finch_src finch-%{finch_commit} %global finch_rpm_version %(r=%finch_release; echo ${r%%%%-*}) diff --git a/deps/finch-core b/deps/finch-core index a5e68a074..58bc4df92 160000 --- a/deps/finch-core +++ b/deps/finch-core @@ -1 +1 @@ -Subproject commit a5e68a07486a186d21880139a652105c77495fc3 +Subproject commit 58bc4df9208124d5c5c00759ccfb96956df74e51 diff --git a/dev-prod-setup.sh b/dev-prod-setup.sh new file mode 100755 index 000000000..b493f59fb --- /dev/null +++ b/dev-prod-setup.sh @@ -0,0 +1,101 @@ +#!/bin/bash +set -e + +echo "๐Ÿš€ Finch Development Setup Script" +echo "==================================" + +# Check if we're on macOS +if [[ "$OSTYPE" != "darwin"* ]]; then + echo "โŒ This script is for macOS only" + exit 1 +fi + +# Build what we need +echo "๐Ÿ“ฆ Building Finch and credential bridge..." +echo " Building finch binary..." +make finch 2>/dev/null || echo " โš ๏ธ Finch build had warnings (likely OK)" +echo " Building credential bridge..." +make finch-cred-bridge 2>/dev/null || echo " โš ๏ธ Credential bridge build had warnings (likely OK)" + +# Check if binaries exist +if [ ! -f "_output/bin/finch" ]; then + echo "โŒ finch binary not found. Please check build." + exit 1 +fi +if [ ! -f "_output/bin/finch-cred-bridge" ]; then + echo "โŒ finch-cred-bridge binary not found. Please check build." + exit 1 +fi +echo " โœ… Binaries built successfully" + +echo "" +echo "๐Ÿ”— Setting up development symlinks..." +echo " (This makes your dev build appear as a production installation)" + +# Create symlinks (requires sudo) +if [ ! -L "/Applications/Finch/bin/finch" ]; then + echo " Creating finch binary symlink..." + sudo mkdir -p /Applications/Finch/bin/ + sudo ln -sf "$(pwd)/_output/bin/finch" /Applications/Finch/bin/finch +else + echo " โœ… Finch binary symlink already exists" +fi + +if [ ! -L "/Applications/Finch/bin/finch-cred-bridge" ]; then + echo " Creating credential bridge symlink..." + sudo ln -sf "$(pwd)/_output/bin/finch-cred-bridge" /Applications/Finch/bin/finch-cred-bridge +else + echo " โœ… Credential bridge symlink already exists" +fi + +# Set up LaunchAgent for credential bridge +echo "" +echo "๐Ÿ”ง Setting up credential bridge LaunchAgent..." +if ! launchctl list | grep -q com.runfinch.cred-bridge; then + make install-plist + echo " โœ… LaunchAgent installed and loaded" +else + echo " โœ… LaunchAgent already loaded" +fi + +# Initialize VM if not exists +echo "" +echo "๐Ÿ–ฅ๏ธ Setting up Finch VM..." +if ! finch vm status &>/dev/null || finch vm status | grep -q "Nonexistent\|Stopped"; then + echo " Initializing VM (this may take a few minutes)..." + finch vm init + echo " Starting VM..." + finch vm start +else + echo " โœ… VM already running" +fi + +# Test credential bridge +echo "" +echo "๐Ÿงช Testing credential bridge..." +if echo -e 'list\n' | nc -U ~/.finch/creds.sock &>/dev/null; then + echo " โœ… Credential bridge is working" +else + echo " โš ๏ธ Credential bridge test failed - check logs at ~/.finch/cred-bridge.log" +fi + +echo "" +echo "โœ… Setup complete!" +echo "" +echo "๐Ÿ“ What was set up:" +echo " โ€ข Built finch and finch-cred-bridge binaries" +echo " โ€ข Created symlinks so 'finch' command uses your dev build" +echo " โ€ข Installed LaunchAgent for credential bridge" +echo " โ€ข Initialized and started Finch VM" +echo "" +echo "๐ŸŽฏ You can now use:" +echo " finch login docker.io" +echo " finch logout docker.io" +echo " finch run hello-world" +echo "" +echo "๐Ÿ” To view credential bridge logs:" +echo " tail -f ~/.finch/cred-bridge.log" +echo "" +echo "๐Ÿงน To clean up later:" +echo " make dev-uninstall" +echo " make uninstall-plist" \ No newline at end of file diff --git a/dev-setup-windows-fixed.ps1 b/dev-setup-windows-fixed.ps1 new file mode 100644 index 000000000..30fbcb884 --- /dev/null +++ b/dev-setup-windows-fixed.ps1 @@ -0,0 +1,62 @@ +# Development setup script for finch credential helper testing on Windows +# Run this in PowerShell as Administrator + +Write-Host "Checking git submodules..." -ForegroundColor Green +if (!(Test-Path "deps/finch-core/.git")) { + Write-Host " Initializing submodules..." -ForegroundColor Yellow + git submodule update --init --recursive +} else { + Write-Host " Submodules already initialized" -ForegroundColor Green +} + +Write-Host "Cleaning up previous builds..." -ForegroundColor Green +Remove-Item -Path "_output" -Recurse -Force -ErrorAction SilentlyContinue + +Write-Host "Setting Go environment..." -ForegroundColor Green +$env:GOSUMDB = "" + +Write-Host "Building finch..." -ForegroundColor Green +make clean +make + +# Check if build succeeded +if (!(Test-Path "_output/bin/finch.exe")) { + Write-Host "Build failed - finch.exe not found" -ForegroundColor Red + Write-Host "Try running: make -j1" -ForegroundColor Yellow + exit 1 +} + +Write-Host "Build successful!" -ForegroundColor Green + +# Check if credential helper was built +if (Test-Path "_output/bin/finch-credhelper.exe") { + Write-Host "Setting up credential helper binary..." -ForegroundColor Green + + # Stop any running credential helper processes + Get-Process -Name "finch-credhelper" -ErrorAction SilentlyContinue | Stop-Process -Force + + # Create credential helper directory + $credHelperDir = "$env:LOCALAPPDATA\.finch\cred-helpers" + New-Item -ItemType Directory -Path $credHelperDir -Force | Out-Null + + # Copy credential helper binary + Copy-Item -Path "_output/bin/finch-credhelper.exe" -Destination "$credHelperDir\finch-credhelper.exe" -Force + + Write-Host "Credential helper binary located at: $credHelperDir\finch-credhelper.exe" -ForegroundColor Yellow +} else { + Write-Host "Credential helper not built - skipping setup" -ForegroundColor Yellow +} + +Write-Host "Initializing VM (this may take a few minutes)..." -ForegroundColor Green +$initResult = Start-Process -FilePath "./_output/bin/finch.exe" -ArgumentList "vm", "init" -Wait -PassThru +if ($initResult.ExitCode -eq 0) { + Write-Host "VM initialized successfully!" -ForegroundColor Green +} else { + Write-Host "VM initialization failed" -ForegroundColor Red + exit 1 +} + +Write-Host "Checking VM status..." -ForegroundColor Green +Start-Process -FilePath "./_output/bin/finch.exe" -ArgumentList "vm", "status" -Wait -NoNewWindow + +Write-Host "Setup complete!" -ForegroundColor Green \ No newline at end of file diff --git a/dev-setup-windows.ps1 b/dev-setup-windows.ps1 new file mode 100644 index 000000000..6f5c95674 --- /dev/null +++ b/dev-setup-windows.ps1 @@ -0,0 +1,64 @@ +# Development setup script for finch credential helper testing on Windows +# Run this in PowerShell as Administrator + +Write-Host "๐Ÿ“ฆ Checking git submodules..." -ForegroundColor Green +if (!(Test-Path "deps/finch-core/.git")) { + Write-Host " Initializing submodules..." -ForegroundColor Yellow + git submodule update --init --recursive +} else { + Write-Host " Submodules already initialized" -ForegroundColor Green +} + +Write-Host "๐Ÿงน Cleaning up previous builds..." -ForegroundColor Green +Remove-Item -Path "_output" -Recurse -Force -ErrorAction SilentlyContinue + +Write-Host "๐Ÿ”ง Setting Go environment..." -ForegroundColor Green +$env:GOSUMDB = "" + +Write-Host "๐Ÿงฝ Skipping make clean (submodule issues)..." -ForegroundColor Yellow + +Write-Host "๐Ÿ”จ Building finch..." -ForegroundColor Green +make clean +make + +# Check if build succeeded +if (!(Test-Path "_output/bin/finch.exe")) { + Write-Host "โŒ Build failed - finch.exe not found" -ForegroundColor Red + Write-Host "Try running: make -j1" -ForegroundColor Yellow + exit 1 +} + +Write-Host "โœ… Build successful!" -ForegroundColor Green + +# Check if credential helper was built +if (Test-Path "_output/bin/finch-credhelper.exe") { + Write-Host "๐Ÿ”„ Setting up credential helper binary..." -ForegroundColor Green + + # Stop any running credential helper processes + Get-Process -Name "finch-credhelper" -ErrorAction SilentlyContinue | Stop-Process -Force + + # Create credential helper directory + $credHelperDir = "$env:LOCALAPPDATA\.finch\cred-helpers" + New-Item -ItemType Directory -Path $credHelperDir -Force | Out-Null + + # Copy credential helper binary + Copy-Item -Path "_output/bin/finch-credhelper.exe" -Destination "$credHelperDir\finch-credhelper.exe" -Force + + Write-Host "๐Ÿ“ Credential helper binary located at: $credHelperDir\finch-credhelper.exe" -ForegroundColor Yellow +} else { + Write-Host "โš ๏ธ Credential helper not built - skipping setup" -ForegroundColor Yellow +} + +Write-Host "๐Ÿ–ฅ๏ธ Initializing VM (this may take a few minutes)..." -ForegroundColor Green +$initResult = Start-Process -FilePath "./_output/bin/finch.exe" -ArgumentList "vm", "init" -Wait -PassThru +if ($LASTEXITCODE -eq 0) { + Write-Host "โœ… VM initialized successfully!" -ForegroundColor Green +} else { + Write-Host "โŒ VM initialization failed" -ForegroundColor Red + exit 1 +} + +Write-Host "๐Ÿ” Checking VM status..." -ForegroundColor Green +Start-Process -FilePath "./_output/bin/finch.exe" -ArgumentList "vm", "status" -Wait -NoNewWindow + +Write-Host "โœ… Setup complete!" -ForegroundColor Green \ No newline at end of file diff --git a/dev-setup.sh b/dev-setup.sh new file mode 100755 index 000000000..17c83a8f5 --- /dev/null +++ b/dev-setup.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Development setup script for finch credential helper testing +set -e + +echo "๐Ÿ“ฆ Initializing git submodules..." +git submodule update --init --recursive + +echo "๐Ÿงน Cleaning up previous builds..." +rm -rf _output + +echo "๐Ÿ”ง Setting Go environment..." +unset GOSUMDB + +echo "๐Ÿงฝ Running make clean..." +make clean + +echo "๐Ÿ”จ Building finch..." +make + +echo "๐Ÿงน Cleaning credential helper log..." +rm -f _output/finch-credhelper/cred-bridge.log + +echo "๐Ÿ”„ Reloading credential helper service..." +launchctl unload ~/Library/LaunchAgents/com.runfinch.cred-bridge.plist 2>/dev/null || true +make generate-plist +cp cmd/finch-credhelper/com.runfinch.cred-bridge.plist ~/Library/LaunchAgents/ +launchctl load ~/Library/LaunchAgents/com.runfinch.cred-bridge.plist + +echo "๐Ÿ–ฅ๏ธ Initializing VM..." +./_output/bin/finch vm init + +echo "โœ… Setup complete!" +echo "๐Ÿ“ Credential helper will be managed by launchd" +echo "๐Ÿ” To view logs: tail -f _output/finch-credhelper/cred-bridge.log" \ No newline at end of file diff --git a/docs/cmd/finch_support-bundle_generate.md b/docs/cmd/finch_support-bundle_generate.md index 3633b58ed..e2974168f 100644 --- a/docs/cmd/finch_support-bundle_generate.md +++ b/docs/cmd/finch_support-bundle_generate.md @@ -9,7 +9,7 @@ Generates a collection of logs and configs that can be uploaded to a Github issu ## Options ```text - --exclude stringArray files to exclude from the support bundle. If you specify a base name, all files matching that base name will be excluded. If you specify an absolute or relative path, only exact matches will be excluded. To exclude journal logs for a service, prefix the file path with "service":. To exclude all journal logs, use "service:all" + --exclude stringArray files to exclude from the support bundle. if you specify a base name, all files matching that base name will be excluded. if you specify an absolute or relative path, only exact matches will be excluded -h, --help help for generate - --include stringArray additional files to include in the support bundle, specified by absolute or relative path. To include journal logs for a service, prefix the file path with "service:". To include a file from the VM, prefix the file path with "vm:" + --include stringArray additional files to include in the support bundle, specified by absolute or relative path. to include a file from the VM, prefix the file path with "vm:" ``` diff --git a/e2e/vm/support_bundle_remote_test.go b/e2e/vm/support_bundle_remote_test.go index 8761c7d49..0f2dfdbe1 100644 --- a/e2e/vm/support_bundle_remote_test.go +++ b/e2e/vm/support_bundle_remote_test.go @@ -6,7 +6,6 @@ package vm import ( "archive/zip" "fmt" - "io" "os" "path" "path/filepath" @@ -23,29 +22,22 @@ import ( var testSupportBundle = func(o *option.Option) { ginkgo.Describe("Support bundles", func() { ginkgo.It("Should generate a support bundle", func() { - cmd := command.Run(o, "support-bundle", "generate") - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err := os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - r, err := reader.Open(path.Join(zipPrefix, "logs", "journalctl", "containerd")) - gomega.Expect(err).Should(gomega.BeNil()) - b, err := io.ReadAll(r) // just to make sure logs are populated - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(b).ShouldNot(gomega.BeEmpty()) - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) + command.Run(o, "support-bundle", "generate") + entries, err := os.ReadDir(".") + gomega.Expect(err).Should(gomega.BeNil()) + bundleExists := false + for _, dirEntry := range entries { + if strings.Contains(dirEntry.Name(), "finch-support") { + _, err := os.Stat(dirEntry.Name()) + if err == nil { + bundleExists = true + } + + err = os.Remove(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + } + } + gomega.Expect(bundleExists).Should(gomega.BeTrue()) }) ginkgo.It("Should generate a support bundle with an extra file included with --include flag by relative path", func() { includeFilename := fmt.Sprintf("tempTestfile%s", time.Now().Format("20060102150405")) @@ -58,26 +50,31 @@ var testSupportBundle = func(o *option.Option) { gomega.Expect(err).Should(gomega.BeNil()) }() - cmd := command.Run(o, "support-bundle", "generate", "--include", includeFilename) - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err = os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "misc", includeFilename)) - gomega.Expect(err).Should(gomega.BeNil()) - - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) + command.Run(o, "support-bundle", "generate", "--include", includeFilename) + entries, err := os.ReadDir(".") + gomega.Expect(err).Should(gomega.BeNil()) + bundleExists := false + for _, dirEntry := range entries { + if strings.Contains(dirEntry.Name(), "finch-support") { + _, err := os.Stat(dirEntry.Name()) + if err == nil { + bundleExists = true + } + + reader, err := zip.OpenReader(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + + zipBaseName := filepath.Base(dirEntry.Name()) + zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) + _, err = reader.Open(path.Join(zipPrefix, "misc", includeFilename)) + gomega.Expect(err).Should(gomega.BeNil()) + + gomega.Expect(reader.Close()).Should(gomega.BeNil()) + err = os.Remove(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + } + } + gomega.Expect(bundleExists).Should(gomega.BeTrue()) }) ginkgo.It("Should generate a support bundle with an extra file included with --include flag by absolute path", func() { includeFilename := fmt.Sprintf("tempTestfile%s", time.Now().Format("20060102150405")) @@ -94,94 +91,86 @@ var testSupportBundle = func(o *option.Option) { gomega.Expect(err).Should(gomega.BeNil()) includeAbsPath := filepath.Join(dir, includeFilename) - cmd := command.Run(o, "support-bundle", "generate", "--include", includeAbsPath) - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err = os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "misc", includeFilename)) - gomega.Expect(err).Should(gomega.BeNil()) - - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - }) - ginkgo.It("Should generate a support bundle with an extra journal log included with --include", func() { - includeService := "dummy" - cmd := command.Run(o, "support-bundle", "generate", "--include", fmt.Sprintf("service:%s", includeService)) - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err := os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "misc", includeService)) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) + command.Run(o, "support-bundle", "generate", "--include", includeAbsPath) + entries, err := os.ReadDir(".") + gomega.Expect(err).Should(gomega.BeNil()) + bundleExists := false + for _, dirEntry := range entries { + if strings.Contains(dirEntry.Name(), "finch-support") { + _, err := os.Stat(dirEntry.Name()) + if err == nil { + bundleExists = true + } + + reader, err := zip.OpenReader(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + + zipBaseName := filepath.Base(dirEntry.Name()) + zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) + _, err = reader.Open(path.Join(zipPrefix, "misc", includeFilename)) + gomega.Expect(err).Should(gomega.BeNil()) + + gomega.Expect(reader.Close()).Should(gomega.BeNil()) + err = os.Remove(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + } + } + gomega.Expect(bundleExists).Should(gomega.BeTrue()) }) ginkgo.It("Should generate a support bundle with no extra file included with --include flag but an invalid path", func() { fakeFileName := "test123+fakefile" - cmd := command.Run(o, "support-bundle", "generate", "--include", fakeFileName) - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err := os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "misc", fakeFileName)) - gomega.Expect(err).ShouldNot(gomega.BeNil()) - - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) + command.Run(o, "support-bundle", "generate", "--include", fakeFileName) + entries, err := os.ReadDir(".") + gomega.Expect(err).Should(gomega.BeNil()) + bundleExists := false + for _, dirEntry := range entries { + if strings.Contains(dirEntry.Name(), "finch-support") { + _, err := os.Stat(dirEntry.Name()) + if err == nil { + bundleExists = true + } + + reader, err := zip.OpenReader(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + + zipBaseName := filepath.Base(dirEntry.Name()) + zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) + _, err = reader.Open(path.Join(zipPrefix, "misc", fakeFileName)) + gomega.Expect(err).ShouldNot(gomega.BeNil()) + + gomega.Expect(reader.Close()).Should(gomega.BeNil()) + err = os.Remove(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + } + } + gomega.Expect(bundleExists).Should(gomega.BeTrue()) }) ginkgo.It("Should generate a support bundle with a default file excluded with --exclude flag by basename", func() { - cmd := command.Run(o, "support-bundle", "generate", "--exclude", "finch.yaml") - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err := os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, path.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "configs", "finch.yaml")) - gomega.Expect(err).ShouldNot(gomega.BeNil()) - - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) + command.Run(o, "support-bundle", "generate", "--exclude", "finch.yaml") + entries, err := os.ReadDir(".") + gomega.Expect(err).Should(gomega.BeNil()) + bundleExists := false + for _, dirEntry := range entries { + if strings.Contains(dirEntry.Name(), "finch-support") { + _, err := os.Stat(dirEntry.Name()) + if err == nil { + bundleExists = true + } + + reader, err := zip.OpenReader(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + + zipBaseName := filepath.Base(dirEntry.Name()) + zipPrefix := strings.TrimSuffix(zipBaseName, path.Ext(zipBaseName)) + _, err = reader.Open(path.Join(zipPrefix, "configs", "finch.yaml")) + gomega.Expect(err).ShouldNot(gomega.BeNil()) + + gomega.Expect(reader.Close()).Should(gomega.BeNil()) + err = os.Remove(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + } + } + gomega.Expect(bundleExists).Should(gomega.BeTrue()) }) ginkgo.It("Should generate a support bundle with a default file excluded with --exclude flag by absolute path", func() { includeFilename := fmt.Sprintf("tempTestfile%s", time.Now().Format("20060102150405")) @@ -196,26 +185,31 @@ var testSupportBundle = func(o *option.Option) { absPath, err := filepath.Abs(includeFilename) gomega.Expect(err).Should(gomega.BeNil()) - cmd := command.Run(o, "support-bundle", "generate", "--include", includeFilename, "--exclude", absPath) - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err = os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "misc", includeFilename)) - gomega.Expect(err).ShouldNot(gomega.BeNil()) - - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) + command.Run(o, "support-bundle", "generate", "--include", includeFilename, "--exclude", absPath) + entries, err := os.ReadDir(".") + gomega.Expect(err).Should(gomega.BeNil()) + bundleExists := false + for _, dirEntry := range entries { + if strings.Contains(dirEntry.Name(), "finch-support") { + _, err := os.Stat(dirEntry.Name()) + if err == nil { + bundleExists = true + } + + reader, err := zip.OpenReader(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + + zipBaseName := filepath.Base(dirEntry.Name()) + zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) + _, err = reader.Open(path.Join(zipPrefix, "misc", includeFilename)) + gomega.Expect(err).ShouldNot(gomega.BeNil()) + + gomega.Expect(reader.Close()).Should(gomega.BeNil()) + err = os.Remove(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + } + } + gomega.Expect(bundleExists).Should(gomega.BeTrue()) }) ginkgo.It("Should generate a support bundle with a default file excluded with --exclude flag by relative path", func() { includeFilename := fmt.Sprintf("tempTestfile%s", time.Now().Format("20060102150405")) @@ -228,94 +222,59 @@ var testSupportBundle = func(o *option.Option) { gomega.Expect(err).Should(gomega.BeNil()) }() - cmd := command.Run(o, "support-bundle", "generate", "--include", includeFilename, "--exclude", filepath.Join(".", includeFilename)) - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err = os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "misc", includeFilename)) - gomega.Expect(err).ShouldNot(gomega.BeNil()) - - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) + command.Run(o, "support-bundle", "generate", "--include", includeFilename, "--exclude", filepath.Join(".", includeFilename)) + entries, err := os.ReadDir(".") + gomega.Expect(err).Should(gomega.BeNil()) + bundleExists := false + for _, dirEntry := range entries { + if strings.Contains(dirEntry.Name(), "finch-support") { + _, err := os.Stat(dirEntry.Name()) + if err == nil { + bundleExists = true + } + + reader, err := zip.OpenReader(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + + zipBaseName := filepath.Base(dirEntry.Name()) + zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) + _, err = reader.Open(path.Join(zipPrefix, "misc", includeFilename)) + gomega.Expect(err).ShouldNot(gomega.BeNil()) + + gomega.Expect(reader.Close()).Should(gomega.BeNil()) + err = os.Remove(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + } + } + gomega.Expect(bundleExists).Should(gomega.BeTrue()) }) ginkgo.It("Should generate a support bundle with no file excluded with --exclude flag with invalid path", func() { fakeFileName := "test123+fakefile" - cmd := command.Run(o, "support-bundle", "generate", "--exclude", fakeFileName) - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err := os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "configs", "finch.yaml")) - gomega.Expect(err).Should(gomega.BeNil()) - - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - }) - ginkgo.It("Should generate a support bundle with a default journal log excluded with --exclude", func() { - excludeService := "containerd" - cmd := command.Run(o, "support-bundle", "generate", "--exclude", fmt.Sprintf("service:%s", excludeService)) - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err := os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "logs", "journalctl", excludeService)) - gomega.Expect(err).ShouldNot(gomega.BeNil()) - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - }) - ginkgo.It("Should generate a support bundle with no journal logs with --exclude service:all", func() { - cmd := command.Run(o, "support-bundle", "generate", "--exclude", "service:all") - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err := os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "logs", "journalctl")) - gomega.Expect(err).ShouldNot(gomega.BeNil()) - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) + command.Run(o, "support-bundle", "generate", "--exclude", fakeFileName) + entries, err := os.ReadDir(".") + gomega.Expect(err).Should(gomega.BeNil()) + bundleExists := false + for _, dirEntry := range entries { + if strings.Contains(dirEntry.Name(), "finch-support") { + _, err := os.Stat(dirEntry.Name()) + if err == nil { + bundleExists = true + } + + reader, err := zip.OpenReader(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + + zipBaseName := filepath.Base(dirEntry.Name()) + zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) + _, err = reader.Open(path.Join(zipPrefix, "configs", "finch.yaml")) + gomega.Expect(err).Should(gomega.BeNil()) + + gomega.Expect(reader.Close()).Should(gomega.BeNil()) + err = os.Remove(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + } + } + gomega.Expect(bundleExists).Should(gomega.BeTrue()) }) ginkgo.It("Should generate a support bundle with a file excluded when specified with both --include and --exclude", func() { includeFilename := fmt.Sprintf("tempTestfile%s", time.Now().Format("20060102150405")) @@ -328,26 +287,31 @@ var testSupportBundle = func(o *option.Option) { gomega.Expect(err).Should(gomega.BeNil()) }() - cmd := command.Run(o, "support-bundle", "generate", "--include", includeFilename, "--exclude", includeFilename) - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).ShouldNot(gomega.BeEmpty()) - - bundlePath := filepath.Join(".", zipName) - _, err = os.Stat(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - reader, err := zip.OpenReader(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) - - zipBaseName := filepath.Base(bundlePath) - zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) - _, err = reader.Open(path.Join(zipPrefix, "misc", includeFilename)) - gomega.Expect(err).ShouldNot(gomega.BeNil()) - - gomega.Expect(reader.Close()).Should(gomega.BeNil()) - err = os.Remove(bundlePath) - gomega.Expect(err).Should(gomega.BeNil()) + command.Run(o, "support-bundle", "generate", "--include", includeFilename, "--exclude", includeFilename) + entries, err := os.ReadDir(".") + gomega.Expect(err).Should(gomega.BeNil()) + bundleExists := false + for _, dirEntry := range entries { + if strings.Contains(dirEntry.Name(), "finch-support") { + _, err := os.Stat(dirEntry.Name()) + if err == nil { + bundleExists = true + } + + reader, err := zip.OpenReader(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + + zipBaseName := filepath.Base(dirEntry.Name()) + zipPrefix := strings.TrimSuffix(zipBaseName, filepath.Ext(zipBaseName)) + _, err = reader.Open(path.Join(zipPrefix, "misc", includeFilename)) + gomega.Expect(err).ShouldNot(gomega.BeNil()) + + gomega.Expect(reader.Close()).Should(gomega.BeNil()) + err = os.Remove(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + } + } + gomega.Expect(bundleExists).Should(gomega.BeTrue()) }) ginkgo.It("Should fail to generate a support bundle when the VM is nonexistent", func() { if runtime.GOOS == "linux" { @@ -359,26 +323,23 @@ var testSupportBundle = func(o *option.Option) { time.Sleep(1 * time.Second) defer command.New(o, "vm", "init").WithoutCheckingExitCode().WithTimeoutInSeconds(160).Run() - cmd := command.New(o, "support-bundle", "generate").WithoutSuccessfulExit().Run() - out := string(cmd.Wait().Err.Contents()) - zipName := getZipName(out) - gomega.Expect(zipName).Should(gomega.BeEmpty()) - }) - }) -} - -func getZipName(cmdOutput string) string { - for _, line := range strings.Split(cmdOutput, "\n") { - if strings.Contains(line, "Bundle created: finch-support") { - bundleLine := strings.Split(line, "\"") - gomega.Expect(bundleLine).To(gomega.HaveLen(5)) + command.New(o, "support-bundle", "generate").WithoutSuccessfulExit().Run() - zipName := strings.Split(bundleLine[3], ": ") - gomega.Expect(zipName).To(gomega.HaveLen(2)) - gomega.Expect(strings.HasPrefix(zipName[1], "finch-support")).To(gomega.BeTrue()) + entries, err := os.ReadDir(".") + gomega.Expect(err).Should(gomega.BeNil()) + bundleExists := false + for _, dirEntry := range entries { + if strings.Contains(dirEntry.Name(), "finch-support") { + _, err := os.Stat(dirEntry.Name()) + if err == nil { + bundleExists = true + } - return strings.TrimSpace(zipName[1]) - } - } - return "" + err = os.Remove(dirEntry.Name()) + gomega.Expect(err).Should(gomega.BeNil()) + } + } + gomega.Expect(bundleExists).Should(gomega.BeFalse()) + }) + }) } diff --git a/finch.yaml.d/mac.yaml b/finch.yaml.d/mac.yaml index 1be4e99ee..57a4236b7 100644 --- a/finch.yaml.d/mac.yaml +++ b/finch.yaml.d/mac.yaml @@ -7,6 +7,9 @@ provision: - mode: boot script: | modprobe virtiofs + - mode: boot + script: | + dnf install -y socat # port this to common.yaml after windows socket forwarding is added - mode: user script: | @@ -57,3 +60,6 @@ hostResolver: portForwards: - guestSocket: "/run/finch.sock" hostSocket: "{{.Dir}}/sock/finch.sock" +- guestSocket: "/tmp/native-creds.sock" + hostSocket: "{{.Home}}/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock" + reverse: true \ No newline at end of file diff --git a/finch.yaml.d/windows.yaml b/finch.yaml.d/windows.yaml index a8a7ddd1e..33d1b9d16 100644 --- a/finch.yaml.d/windows.yaml +++ b/finch.yaml.d/windows.yaml @@ -1,2 +1,7 @@ vmType: wsl2 mountType: wsl2 + +portForwards: +- guestSocket: "/tmp/native-creds.sock" + hostSocket: "{{.Home}}/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock" + reverse: true \ No newline at end of file diff --git a/fix-wsl-init.ps1 b/fix-wsl-init.ps1 new file mode 100644 index 000000000..6630cff66 --- /dev/null +++ b/fix-wsl-init.ps1 @@ -0,0 +1,42 @@ +# Fix WSL2 initialization issues for Finch + +Write-Host "Fixing WSL2 initialization issues..." -ForegroundColor Yellow + +# Ensure WSL is properly configured +Write-Host "Checking WSL configuration..." -ForegroundColor Green +wsl --set-default-version 2 + +# Clean up any existing lima-finch distro +Write-Host "Cleaning up existing WSL distros..." -ForegroundColor Green +$wslList = wsl --list --quiet 2>$null +foreach ($line in $wslList) { + $distro = $line.Trim() + if ($distro -and ($distro -like "*lima-finch*" -or $distro -eq "lima-finch")) { + Write-Host " Unregistering: $distro" -ForegroundColor Cyan + wsl --unregister $distro 2>$null + } +} + +# Set proper temp directory permissions +Write-Host "Setting temp directory permissions..." -ForegroundColor Green +$tempDir = $env:TEMP +if (Test-Path $tempDir) { + # Grant full control to current user + icacls $tempDir /grant "${env:USERNAME}:(OI)(CI)F" /T 2>$null +} + +# Create a custom temp directory for Lima +Write-Host "Creating Lima temp directory..." -ForegroundColor Green +$limaTemp = "C:\lima-temp" +if (!(Test-Path $limaTemp)) { + New-Item -ItemType Directory -Path $limaTemp -Force | Out-Null +} +icacls $limaTemp /grant "${env:USERNAME}:(OI)(CI)F" /T 2>$null + +# Set environment variable for Lima to use our temp directory +$env:TMPDIR = $limaTemp +$env:TMP = $limaTemp +$env:TEMP = $limaTemp + +Write-Host "WSL2 environment prepared. Try vm init again." -ForegroundColor Green +Write-Host "If it still fails, try running as Administrator." -ForegroundColor Yellow \ No newline at end of file diff --git a/go.mod b/go.mod index 753c7d6ec..842f1bc22 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/containernetworking/cni v1.3.0 // indirect - github.com/containernetworking/plugins v1.9.0 // indirect + github.com/containernetworking/plugins v1.8.0 // indirect github.com/containers/ocicrypt v1.2.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.6.0 // indirect diff --git a/go.sum b/go.sum index 9d26cec13..cc2e916b7 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,8 @@ github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++ github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo= github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4= -github.com/containernetworking/plugins v1.9.0 h1:Mg3SXBdRGkdXyFC4lcwr6u2ZB2SDeL6LC3U+QrEANuQ= -github.com/containernetworking/plugins v1.9.0/go.mod h1:JG3BxoJifxxHBhG3hFyxyhid7JgRVBu/wtooGEvWf1c= +github.com/containernetworking/plugins v1.8.0 h1:WjGbV/0UQyo8A4qBsAh6GaDAtu1hevxVxsEuqtBqUFk= +github.com/containernetworking/plugins v1.8.0/go.mod h1:JG3BxoJifxxHBhG3hFyxyhid7JgRVBu/wtooGEvWf1c= github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM= github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= diff --git a/patch-lima-wsl2.ps1 b/patch-lima-wsl2.ps1 new file mode 100644 index 000000000..c34ca4241 --- /dev/null +++ b/patch-lima-wsl2.ps1 @@ -0,0 +1,15 @@ +# Patch Lima WSL2 driver to use accessible temp location + +$limaFile = "deps/finch-core/src/lima/pkg/driver/wsl2/vm_windows.go" + +# Backup original +Copy-Item $limaFile "$limaFile.backup" + +# Replace temp file creation to use C:\ directly +$content = Get-Content $limaFile -Raw +$content = $content -replace 'os\.CreateTemp\("", "lima-wsl2-boot-\*\.sh"\)', 'os.CreateTemp("C:\\", "lima-wsl2-boot-*.sh")' + +Set-Content $limaFile $content + +Write-Host "Patched Lima to use C:\ for temp files" -ForegroundColor Green +Write-Host "Now run: make clean && make" -ForegroundColor Yellow \ No newline at end of file diff --git a/pkg/command/nerdctl_native_test.go b/pkg/command/nerdctl_native_test.go index b8a1afec4..e41a4524d 100644 --- a/pkg/command/nerdctl_native_test.go +++ b/pkg/command/nerdctl_native_test.go @@ -57,6 +57,7 @@ func TestLimaCmdCreator_Create(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/config/config_native_test.go b/pkg/config/config_native_test.go index 52ae98968..05900c7e9 100644 --- a/pkg/config/config_native_test.go +++ b/pkg/config/config_native_test.go @@ -8,11 +8,10 @@ package config import ( "testing" + "github.com/runfinch/finch/pkg/mocks" "github.com/spf13/afero" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" - - "github.com/runfinch/finch/pkg/mocks" ) func platformLoadTests(t *testing.T) []loadTestCase { diff --git a/pkg/config/cred-helper-overwrite.sh b/pkg/config/cred-helper-overwrite.sh new file mode 100644 index 000000000..988ebf0c7 --- /dev/null +++ b/pkg/config/cred-helper-overwrite.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Credential helper that forwards to host daemon via socat +LOGFILE="/tmp/cred-helper-debug.log" +echo "" >> $LOGFILE +echo "[$(date '+%%m/%%d %%l:%%M%%p')] Credential helper called with args: $@" >> $LOGFILE +echo "[$(date '+%%m/%%d %%l:%%M%%p')] Input received:" >> $LOGFILE +input=$(cat) +echo "$input" >> $LOGFILE + +# Forward to host daemon via socat +response=$(printf "%s\n%s\n" "$1" "$input" | socat - UNIX-CONNECT:/tmp/finch-creds.sock 2>>$LOGFILE) +exit_code=$? + +echo "[$(date '+%%m/%%d %%l:%%M%%p')] Response from host: $response" >> $LOGFILE + +if [ $exit_code -ne 0 ]; then + echo "[$(date '+%%m/%%d %%l:%%M%%p')] Error: socat failed with exit code $exit_code" >> $LOGFILE + echo '{"error": "credential helper connection failed"}' + exit 1 +fi + +# Handle empty response - only exit with code 1 for 'get' command when credentials not found +if [ -z "$response" ] && [ "$1" = "get" ]; then + echo "[$(date '+%%m/%%d %%l:%%M%%p')] Empty response for get - credentials not found" >> $LOGFILE + exit 1 +fi + +echo "$response" +echo "" >> $LOGFILE \ No newline at end of file diff --git a/pkg/config/defaults_darwin.go b/pkg/config/defaults_darwin.go index edb0274f3..85699a6b9 100644 --- a/pkg/config/defaults_darwin.go +++ b/pkg/config/defaults_darwin.go @@ -59,6 +59,13 @@ func cpuDefault(cfg *Finch, deps LoadSystemDeps) { } } +// Different because the other defaults use single values; this uses a slice +func credHelperDefault(cfg *Finch) { + if cfg.CredsHelpers == nil || len(cfg.CredsHelpers) == 0 { + cfg.CredsHelpers = []string{"osxkeychain"} + } +} + // applyDefaults sets default configuration options if they are not already set. func applyDefaults( cfg *Finch, @@ -75,6 +82,6 @@ func applyDefaults( } vmDefault(cfg, supportsVz) rosettaDefault(cfg) - + credHelperDefault(cfg) return cfg } diff --git a/pkg/config/defaults_windows.go b/pkg/config/defaults_windows.go index 397a987c8..ccbbd667e 100644 --- a/pkg/config/defaults_windows.go +++ b/pkg/config/defaults_windows.go @@ -18,6 +18,13 @@ func vmDefault(cfg *Finch) { } } +// Different because the other defaults use single values; this uses a slice +func credHelperDefault(cfg *Finch) { + if cfg.CredsHelpers == nil || len(cfg.CredsHelpers) == 0 { + cfg.CredsHelpers = []string{"wincred"} + } +} + // applyDefaults sets default configuration options if they are not already set. func applyDefaults( cfg *Finch, @@ -26,5 +33,6 @@ func applyDefaults( _ command.Creator, ) *Finch { vmDefault(cfg) + credHelperDefault(cfg) return cfg } diff --git a/pkg/config/nerdctl_config_applier.go b/pkg/config/nerdctl_config_applier.go index 55d7f1339..02202269c 100644 --- a/pkg/config/nerdctl_config_applier.go +++ b/pkg/config/nerdctl_config_applier.go @@ -6,6 +6,7 @@ package config import ( + _ "embed" "errors" "fmt" "path" @@ -25,6 +26,9 @@ const ( nerdctlRootfulCfgPath = "/etc/nerdctl/nerdctl.toml" ) +//go:embed cred-helper-overwrite.sh +var credHelperScript string + type nerdctlConfigApplier struct { dialer fssh.Dialer fs afero.Fs @@ -94,13 +98,26 @@ func updateEnvironment(fs afero.Fs, fc *Finch, finchDir, homeDir, limaVMHomeDir `[ -L /root/.aws ] || sudo ln -fs "$AWS_DIR" /root/.aws`, } - //nolint:gosec // G101: Potential hardcoded credentials false positive - const configureCredHelperTemplate = `([ -e "$FINCH_DIR"/cred-helpers/docker-credential-%s ] || \ - (echo "error: docker-credential-%s not found in $FINCH_DIR/cred-helpers directory.")) && \ - ([ -L /usr/local/bin/docker-credential-%s ] || sudo ln -s "$FINCH_DIR"/cred-helpers/docker-credential-%s /usr/local/bin)` - for _, credHelper := range fc.CredsHelpers { - cmdArr = append(cmdArr, fmt.Sprintf(configureCredHelperTemplate, credHelper, credHelper, credHelper, credHelper)) + cmdArr = append(cmdArr, fmt.Sprintf(`echo '{"credsStore": "%s"}' > "$FINCH_DIR"/config.json`, credHelper)) + + // Check if this is OS native credential store + if credHelper == "osxkeychain" || credHelper == "wincred" { + // Native OS credstore - create wrapper script + cmdArr = append(cmdArr, fmt.Sprintf(`[ -x /usr/local/bin/docker-credential-%s ] || ( +echo "Creating native credential wrapper for %s" && \ +sudo mkdir -p /usr/local/bin && \ +echo '#!/bin/bash' | sudo tee /usr/local/bin/docker-credential-%s > /dev/null && \ +echo 'response=$(printf "%%s\n%%s\n" "$1" "$(cat)" | socat - UNIX-CONNECT:/tmp/native-creds.sock)' | sudo tee -a /usr/local/bin/docker-credential-%s > /dev/null && \ +echo 'echo "$response"' | sudo tee -a /usr/local/bin/docker-credential-%s > /dev/null && \ +sudo chmod +x /usr/local/bin/docker-credential-%s +)`, credHelper, credHelper, credHelper, credHelper, credHelper, credHelper)) + } else { + // Mainline behavior for third-party helpers (ecr-login, etc) + cmdArr = append(cmdArr, fmt.Sprintf(`([ -e "$FINCH_DIR"/cred-helpers/docker-credential-%s ] || \ + (echo "error: docker-credential-%s not found in $FINCH_DIR/cred-helpers directory.")) && \ + ([ -L /usr/local/bin/docker-credential-%s ] || sudo ln -s "$FINCH_DIR"/cred-helpers/docker-credential-%s /usr/local/bin)`, credHelper, credHelper, credHelper, credHelper)) + } } awsDir := fmt.Sprintf("%s/.aws", homeDir) @@ -249,5 +266,6 @@ func (nca *nerdctlConfigApplier) Apply(remoteAddr string) error { if err := updateEnvironment(sftpFs, nca.fc, nca.finchDir, nca.homeDir, limaHomeDir); err != nil { return fmt.Errorf("failed to update the user's .profile file: %w", err) } + return nil } diff --git a/pkg/mocks/pkg_support_config.go b/pkg/mocks/pkg_support_config.go index 64836e4b4..84c81429b 100644 --- a/pkg/mocks/pkg_support_config.go +++ b/pkg/mocks/pkg_support_config.go @@ -56,20 +56,6 @@ func (mr *BundleConfigMockRecorder) ConfigFiles() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigFiles", reflect.TypeOf((*BundleConfig)(nil).ConfigFiles)) } -// JournalServices mocks base method. -func (m *BundleConfig) JournalServices() []string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "JournalServices") - ret0, _ := ret[0].([]string) - return ret0 -} - -// JournalServices indicates an expected call of JournalServices. -func (mr *BundleConfigMockRecorder) JournalServices() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "JournalServices", reflect.TypeOf((*BundleConfig)(nil).JournalServices)) -} - // LogFiles mocks base method. func (m *BundleConfig) LogFiles() []string { m.ctrl.T.Helper() diff --git a/pkg/support/config.go b/pkg/support/config.go index dbe5d7af1..832945b00 100644 --- a/pkg/support/config.go +++ b/pkg/support/config.go @@ -3,9 +3,7 @@ package support -import ( - fpath "github.com/runfinch/finch/pkg/path" -) +import fpath "github.com/runfinch/finch/pkg/path" type bundleConfig struct { finch fpath.Finch @@ -18,7 +16,6 @@ type bundleConfig struct { type BundleConfig interface { LogFiles() []string ConfigFiles() []string - JournalServices() []string } // NewBundleConfig creates a new bundleConfig. diff --git a/pkg/support/config_native_linux.go b/pkg/support/config_native_linux.go index 9b7ceb600..6942d6937 100644 --- a/pkg/support/config_native_linux.go +++ b/pkg/support/config_native_linux.go @@ -16,7 +16,3 @@ func (bc *bundleConfig) ConfigFiles() []string { bc.finch.ConfigFilePath(), } } - -func (bc *bundleConfig) JournalServices() []string { - return []string{"service:containerd", "service:finch", "service:buildkit", "service:soci"} -} diff --git a/pkg/support/config_remote.go b/pkg/support/config_remote.go index d592f402e..faaae43dc 100644 --- a/pkg/support/config_remote.go +++ b/pkg/support/config_remote.go @@ -29,7 +29,3 @@ func (bc *bundleConfig) ConfigFiles() []string { bc.finch.ConfigFilePath(bc.rootDir), } } - -func (bc *bundleConfig) JournalServices() []string { - return []string{"service:containerd", "service:finch", "service:buildkit", "service:soci"} -} diff --git a/pkg/support/support.go b/pkg/support/support.go index 47e32ce62..431fda377 100644 --- a/pkg/support/support.go +++ b/pkg/support/support.go @@ -14,7 +14,6 @@ import ( "path" "path/filepath" "runtime" - "slices" "strings" "time" @@ -34,10 +33,8 @@ const ( platformFileName = "platform.yaml" versionFileName = "version-output.txt" logPrefix = "logs" - journalPrefix = "logs/journalctl" configPrefix = "configs" additionalPrefix = "misc" - allServices = "service:all" ) // PlatformData defines the YAML structure for the platform data included in a support bundle. @@ -142,23 +139,6 @@ func (bb *bundleBuilder) GenerateSupportBundle(additionalFiles []string, exclude } } - if slices.Contains(excludeFiles, allServices) { - bb.logger.Info("Excluding all service logs...") - } else { - bb.logger.Debugln("Copying in journal logs...") - for _, file := range bb.config.JournalServices() { - if fileShouldBeExcluded(file, excludeFiles) { - bb.logger.Infof("Excluding %s...", file) - continue - } - bb.logger.Debugf("Copying %s...", file) - err = bb.copyFileFromVMOrLocal(writer, file, path.Join(zipPrefix, journalPrefix)) - if err != nil { - bb.logger.Warnf("Could not copy in %q. Error: %s", file, err) - } - } - } - bb.logger.Debugln("Copying in config files...") for _, file := range bb.config.ConfigFiles() { if fileShouldBeExcluded(file, excludeFiles) { @@ -198,7 +178,7 @@ type bufReader interface { } func (bb *bundleBuilder) copyFileFromVMOrLocal(writer *zip.Writer, filename, zipPath string) error { - if runtime.GOOS != "linux" && (isFileFromVM(filename) || isService(filename)) { + if runtime.GOOS != "linux" && isFileFromVM(filename) { return bb.streamFileFromVM(writer, filename, zipPath) } return bb.copyInFile(writer, filename, zipPath) @@ -239,23 +219,10 @@ func (bb *bundleBuilder) copyAndRedactFile(writer io.Writer, reader bufReader) e } func (bb *bundleBuilder) copyInFile(writer *zip.Writer, fileName string, prefix string) error { - var f io.Reader - if isService(fileName) { - service := strings.TrimPrefix(fileName, "service:") - cmd := bb.ecc.Create("journalctl", "--no-pager", "-xu", service) - out, err := cmd.Output() - if err != nil { - return err - } - f = bytes.NewReader(out) - fileName = service - } else { - var err error - // check filename validity? - f, err = bb.fs.Open(fileName) - if err != nil { - return err - } + // check filename validity? + f, err := bb.fs.Open(fileName) + if err != nil { + return err } baseName := filepath.Base(fileName) @@ -277,14 +244,7 @@ func (bb *bundleBuilder) streamFileFromVM(writer *zip.Writer, filename, prefix s errBuf := new(bytes.Buffer) _, filePathInVM, _ := strings.Cut(filename, ":") - var cmd command.Command - if isService(filename) { - cmd = bb.ncc.CreateWithoutStdio("shell", "finch", "sudo", "journalctl", "--no-pager", "-xu", filePathInVM) - // omit service prefix - filename = filePathInVM - } else { - cmd = bb.ncc.CreateWithoutStdio("shell", "finch", "sudo", "cat", filePathInVM) - } + cmd := bb.ncc.CreateWithoutStdio("shell", "finch", "sudo", "cat", filePathInVM) cmd.SetStdout(pipeWriter) cmd.SetStderr(errBuf) @@ -442,10 +402,6 @@ func isFileFromVM(filename string) bool { return strings.HasPrefix(filename, "vm:") } -func isService(filename string) bool { - return strings.HasPrefix(filename, "service:") -} - func writeVersionOutput(writer *zip.Writer, version, prefix string) error { versionFile, err := writer.Create(path.Join(prefix, versionFileName)) if err != nil { diff --git a/pkg/support/support_test.go b/pkg/support/support_test.go index 30cde950c..acb5865e4 100644 --- a/pkg/support/support_test.go +++ b/pkg/support/support_test.go @@ -5,7 +5,6 @@ package support import ( "archive/zip" - "fmt" "io" "os/user" "runtime" @@ -67,7 +66,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger *mocks.Logger, config *mocks.BundleConfig, ecc *mocks.CommandCreator, - ncc *mocks.NerdctlCmdCreator, + _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, systemDeps *mocks.SupportSystemDeps, @@ -106,8 +105,6 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger.EXPECT().Debugln("Copying in log files...") logger.EXPECT().Debugf("Copying %s...", "log1") logger.EXPECT().Debugf("Copying %s...", "log2") - checkJournalCmdOutputs(logger, config, ecc, ncc, cmd, lima, mockUser) - logger.EXPECT().Debugln("Copying in config files...") logger.EXPECT().Debugf("Copying %s...", "config1") logger.EXPECT().Debugf("Copying %s...", "config2") @@ -124,7 +121,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger *mocks.Logger, config *mocks.BundleConfig, ecc *mocks.CommandCreator, - ncc *mocks.NerdctlCmdCreator, + _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, systemDeps *mocks.SupportSystemDeps, @@ -160,8 +157,6 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger.EXPECT().Debugln("Copying in log files...") logger.EXPECT().Debugf("Copying %s...", "log1") - checkJournalCmdOutputs(logger, config, ecc, ncc, cmd, lima, mockUser) - logger.EXPECT().Debugln("Copying in config files...") logger.EXPECT().Debugf("Copying %s...", "config1") logger.EXPECT().Debugln("Copying in additional files...") @@ -178,7 +173,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger *mocks.Logger, config *mocks.BundleConfig, ecc *mocks.CommandCreator, - ncc *mocks.NerdctlCmdCreator, + _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, systemDeps *mocks.SupportSystemDeps, @@ -214,8 +209,6 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger.EXPECT().Debugln("Copying in log files...") logger.EXPECT().Infof("Excluding %s...", "log1") - checkJournalCmdOutputs(logger, config, ecc, ncc, cmd, lima, mockUser) - logger.EXPECT().Debugln("Copying in config files...") logger.EXPECT().Debugf("Copying %s...", "config1") logger.EXPECT().Debugln("Copying in additional files...") @@ -231,7 +224,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger *mocks.Logger, config *mocks.BundleConfig, ecc *mocks.CommandCreator, - ncc *mocks.NerdctlCmdCreator, + _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, systemDeps *mocks.SupportSystemDeps, @@ -267,8 +260,6 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger.EXPECT().Debugln("Copying in log files...") logger.EXPECT().Debugf("Copying %s...", "log1") - checkJournalCmdOutputs(logger, config, ecc, ncc, cmd, lima, mockUser) - logger.EXPECT().Debugln("Copying in config files...") logger.EXPECT().Infof("Excluding %s...", "config1") logger.EXPECT().Debugln("Copying in additional files...") @@ -284,7 +275,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger *mocks.Logger, config *mocks.BundleConfig, ecc *mocks.CommandCreator, - ncc *mocks.NerdctlCmdCreator, + _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, systemDeps *mocks.SupportSystemDeps, @@ -320,8 +311,6 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger.EXPECT().Debugln("Copying in log files...") logger.EXPECT().Debugf("Copying %s...", "log1") - checkJournalCmdOutputs(logger, config, ecc, ncc, cmd, lima, mockUser) - logger.EXPECT().Debugln("Copying in config files...") logger.EXPECT().Debugf("Copying %s...", "config1") logger.EXPECT().Debugln("Copying in additional files...") @@ -377,8 +366,6 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger.EXPECT().Debugln("Copying in log files...") logger.EXPECT().Debugf("Copying %s...", "log1") - checkJournalCmdOutputs(logger, config, ecc, ncc, cmd, lima, mockUser) - logger.EXPECT().Debugln("Copying in config files...") logger.EXPECT().Debugf("Copying %s...", "config1") logger.EXPECT().Debugln("Copying in additional files...") @@ -417,7 +404,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger *mocks.Logger, config *mocks.BundleConfig, ecc *mocks.CommandCreator, - ncc *mocks.NerdctlCmdCreator, + _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, systemDeps *mocks.SupportSystemDeps, @@ -453,8 +440,6 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { logger.EXPECT().Debugln("Copying in log files...") logger.EXPECT().Debugf("Copying %s...", "log1") - checkJournalCmdOutputs(logger, config, ecc, ncc, cmd, lima, mockUser) - logger.EXPECT().Debugln("Copying in config files...") logger.EXPECT().Debugf("Copying %s...", "config1") logger.EXPECT().Debugln("Copying in additional files...") @@ -740,39 +725,3 @@ func TestSupport_writeVersionOutput(t *testing.T) { }) } } - -func checkJournalCmdOutputs( - logger *mocks.Logger, - config *mocks.BundleConfig, - ecc *mocks.CommandCreator, - ncc *mocks.NerdctlCmdCreator, - cmd *mocks.Command, - lima *mocks.MockLimaWrapper, - mockUser *user.User, -) { - config.EXPECT().JournalServices().Return([]string{ - "service:containerd", - "service:finch", - "service:buildkit", - "service:soci-snapshotter", - }) - - services := []string{"containerd", "finch", "buildkit", "soci-snapshotter"} - logger.EXPECT().Debugln("Copying in journal logs...") - - for _, service := range services { - switch runtime.GOOS { - case "linux": - ecc.EXPECT().Create("journalctl", "--no-pager", "-xu", service).Return(cmd) - logger.EXPECT().Debugf("Copying %s...", fmt.Sprintf("service:%s", service)) - case "windows", "darwin": - logger.EXPECT().Debugf("Copying %s...", fmt.Sprintf("service:%s", service)) - ncc.EXPECT().CreateWithoutStdio("shell", "finch", "sudo", "journalctl", "--no-pager", "-xu", service).Return(cmd) - cmd.EXPECT().SetStdout(gomock.Any()) - cmd.EXPECT().SetStderr(gomock.Any()) - cmd.EXPECT().Start() - cmd.EXPECT().Wait() - lima.EXPECT().LimaUser(false).Return(mockUser).AnyTimes() - } - } -} diff --git a/test-creds-windows.ps1 b/test-creds-windows.ps1 new file mode 100644 index 000000000..e50415ae7 --- /dev/null +++ b/test-creds-windows.ps1 @@ -0,0 +1,67 @@ +# Windows credential helper testing script +# Run after dev-setup-windows.ps1 + +param( + [switch]$Manual = $false +) + +$credHelperPath = "$env:LOCALAPPDATA\.finch\cred-helpers\finch-credhelper.exe" + +Write-Host "๐Ÿงช Starting Windows credential helper tests..." -ForegroundColor Green + +if ($Manual) { + Write-Host "๐Ÿ”ง Manual mode: Starting credential helper process..." -ForegroundColor Yellow + + # Start credential helper in background + $credProcess = Start-Process -FilePath $credHelperPath -PassThru -WindowStyle Hidden + Write-Host " Process ID: $($credProcess.Id)" -ForegroundColor Cyan + + # Give it time to start + Start-Sleep -Seconds 2 +} + +Write-Host "๐Ÿณ Testing basic container operations..." -ForegroundColor Green + +# Test 1: Pull public image +Write-Host " 1. Pulling public image..." -ForegroundColor Cyan +& ".\_output\bin\finch.exe" pull alpine + +# Test 2: Setup local registry (if Docker is available) +Write-Host " 2. Setting up local registry..." -ForegroundColor Cyan +try { + & ".\_output\bin\finch.exe" run -d --name registry -p 5000:5000 registry:2 + Start-Sleep -Seconds 3 + + # Test 3: Tag and push to local registry + Write-Host " 3. Testing local registry push..." -ForegroundColor Cyan + & ".\_output\bin\finch.exe" tag alpine localhost:5000/test-image + & ".\_output\bin\finch.exe" push localhost:5000/test-image + + Write-Host "โœ… Registry tests passed!" -ForegroundColor Green +} catch { + Write-Host "โš ๏ธ Registry tests skipped (registry not available)" -ForegroundColor Yellow +} + +# Test 4: Check credential storage +Write-Host " 4. Checking credential storage..." -ForegroundColor Cyan +$configPath = "$env:USERPROFILE\.finch\config.json" +if (Test-Path $configPath) { + $config = Get-Content $configPath | ConvertFrom-Json + Write-Host " Config found: $configPath" -ForegroundColor Cyan + if ($config.credsStore) { + Write-Host " Credential store: $($config.credsStore)" -ForegroundColor Cyan + } +} else { + Write-Host " No config.json found" -ForegroundColor Yellow +} + +# Cleanup +Write-Host "๐Ÿงน Cleaning up..." -ForegroundColor Green +& ".\_output\bin\finch.exe" rm -f registry 2>$null + +if ($Manual -and $credProcess) { + Write-Host "๐Ÿ›‘ Stopping credential helper process..." -ForegroundColor Yellow + Stop-Process -Id $credProcess.Id -Force -ErrorAction SilentlyContinue +} + +Write-Host "โœ… Windows credential tests completed!" -ForegroundColor Green \ No newline at end of file From 0b92e143a5501d554b7285ad5ae431857d4fa797 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Sun, 14 Dec 2025 22:39:18 -0800 Subject: [PATCH 02/21] remove dev testing scripts Signed-off-by: ayush-panta --- cleanup-windows.ps1 | 35 ------------------- dev-setup-windows-fixed.ps1 | 62 ---------------------------------- dev-setup-windows.ps1 | 64 ----------------------------------- fix-wsl-init.ps1 | 42 ----------------------- patch-lima-wsl2.ps1 | 15 --------- test-creds-windows.ps1 | 67 ------------------------------------- 6 files changed, 285 deletions(-) delete mode 100644 cleanup-windows.ps1 delete mode 100644 dev-setup-windows-fixed.ps1 delete mode 100644 dev-setup-windows.ps1 delete mode 100644 fix-wsl-init.ps1 delete mode 100644 patch-lima-wsl2.ps1 delete mode 100644 test-creds-windows.ps1 diff --git a/cleanup-windows.ps1 b/cleanup-windows.ps1 deleted file mode 100644 index 0d6843592..000000000 --- a/cleanup-windows.ps1 +++ /dev/null @@ -1,35 +0,0 @@ -# Clean up corrupted Finch VM state on Windows - -Write-Host "Cleaning up corrupted Finch VM state..." -ForegroundColor Yellow - -# Stop any running processes -Write-Host "Stopping Finch processes..." -ForegroundColor Green -Get-Process -Name "*finch*" -ErrorAction SilentlyContinue | Stop-Process -Force -Get-Process -Name "*lima*" -ErrorAction SilentlyContinue | Stop-Process -Force - -# Remove Lima data directories -Write-Host "Removing Lima data directories..." -ForegroundColor Green -$limaDataPaths = @( - "$env:USERPROFILE\.lima", - "$env:LOCALAPPDATA\.finch", - "C:\Users\Administrator\Documents\finch\_output\lima" -) - -foreach ($path in $limaDataPaths) { - if (Test-Path $path) { - Write-Host " Removing: $path" -ForegroundColor Cyan - Remove-Item -Path $path -Recurse -Force -ErrorAction SilentlyContinue - } -} - -# Clean WSL distributions -Write-Host "Cleaning WSL distributions..." -ForegroundColor Green -$wslDistros = wsl --list --quiet 2>$null | Where-Object { $_ -match "finch|lima" } -foreach ($distro in $wslDistros) { - if ($distro.Trim()) { - Write-Host " Unregistering WSL distro: $($distro.Trim())" -ForegroundColor Cyan - wsl --unregister $distro.Trim() 2>$null - } -} - -Write-Host "Cleanup complete! You can now run vm init." -ForegroundColor Green \ No newline at end of file diff --git a/dev-setup-windows-fixed.ps1 b/dev-setup-windows-fixed.ps1 deleted file mode 100644 index 30fbcb884..000000000 --- a/dev-setup-windows-fixed.ps1 +++ /dev/null @@ -1,62 +0,0 @@ -# Development setup script for finch credential helper testing on Windows -# Run this in PowerShell as Administrator - -Write-Host "Checking git submodules..." -ForegroundColor Green -if (!(Test-Path "deps/finch-core/.git")) { - Write-Host " Initializing submodules..." -ForegroundColor Yellow - git submodule update --init --recursive -} else { - Write-Host " Submodules already initialized" -ForegroundColor Green -} - -Write-Host "Cleaning up previous builds..." -ForegroundColor Green -Remove-Item -Path "_output" -Recurse -Force -ErrorAction SilentlyContinue - -Write-Host "Setting Go environment..." -ForegroundColor Green -$env:GOSUMDB = "" - -Write-Host "Building finch..." -ForegroundColor Green -make clean -make - -# Check if build succeeded -if (!(Test-Path "_output/bin/finch.exe")) { - Write-Host "Build failed - finch.exe not found" -ForegroundColor Red - Write-Host "Try running: make -j1" -ForegroundColor Yellow - exit 1 -} - -Write-Host "Build successful!" -ForegroundColor Green - -# Check if credential helper was built -if (Test-Path "_output/bin/finch-credhelper.exe") { - Write-Host "Setting up credential helper binary..." -ForegroundColor Green - - # Stop any running credential helper processes - Get-Process -Name "finch-credhelper" -ErrorAction SilentlyContinue | Stop-Process -Force - - # Create credential helper directory - $credHelperDir = "$env:LOCALAPPDATA\.finch\cred-helpers" - New-Item -ItemType Directory -Path $credHelperDir -Force | Out-Null - - # Copy credential helper binary - Copy-Item -Path "_output/bin/finch-credhelper.exe" -Destination "$credHelperDir\finch-credhelper.exe" -Force - - Write-Host "Credential helper binary located at: $credHelperDir\finch-credhelper.exe" -ForegroundColor Yellow -} else { - Write-Host "Credential helper not built - skipping setup" -ForegroundColor Yellow -} - -Write-Host "Initializing VM (this may take a few minutes)..." -ForegroundColor Green -$initResult = Start-Process -FilePath "./_output/bin/finch.exe" -ArgumentList "vm", "init" -Wait -PassThru -if ($initResult.ExitCode -eq 0) { - Write-Host "VM initialized successfully!" -ForegroundColor Green -} else { - Write-Host "VM initialization failed" -ForegroundColor Red - exit 1 -} - -Write-Host "Checking VM status..." -ForegroundColor Green -Start-Process -FilePath "./_output/bin/finch.exe" -ArgumentList "vm", "status" -Wait -NoNewWindow - -Write-Host "Setup complete!" -ForegroundColor Green \ No newline at end of file diff --git a/dev-setup-windows.ps1 b/dev-setup-windows.ps1 deleted file mode 100644 index 6f5c95674..000000000 --- a/dev-setup-windows.ps1 +++ /dev/null @@ -1,64 +0,0 @@ -# Development setup script for finch credential helper testing on Windows -# Run this in PowerShell as Administrator - -Write-Host "๐Ÿ“ฆ Checking git submodules..." -ForegroundColor Green -if (!(Test-Path "deps/finch-core/.git")) { - Write-Host " Initializing submodules..." -ForegroundColor Yellow - git submodule update --init --recursive -} else { - Write-Host " Submodules already initialized" -ForegroundColor Green -} - -Write-Host "๐Ÿงน Cleaning up previous builds..." -ForegroundColor Green -Remove-Item -Path "_output" -Recurse -Force -ErrorAction SilentlyContinue - -Write-Host "๐Ÿ”ง Setting Go environment..." -ForegroundColor Green -$env:GOSUMDB = "" - -Write-Host "๐Ÿงฝ Skipping make clean (submodule issues)..." -ForegroundColor Yellow - -Write-Host "๐Ÿ”จ Building finch..." -ForegroundColor Green -make clean -make - -# Check if build succeeded -if (!(Test-Path "_output/bin/finch.exe")) { - Write-Host "โŒ Build failed - finch.exe not found" -ForegroundColor Red - Write-Host "Try running: make -j1" -ForegroundColor Yellow - exit 1 -} - -Write-Host "โœ… Build successful!" -ForegroundColor Green - -# Check if credential helper was built -if (Test-Path "_output/bin/finch-credhelper.exe") { - Write-Host "๐Ÿ”„ Setting up credential helper binary..." -ForegroundColor Green - - # Stop any running credential helper processes - Get-Process -Name "finch-credhelper" -ErrorAction SilentlyContinue | Stop-Process -Force - - # Create credential helper directory - $credHelperDir = "$env:LOCALAPPDATA\.finch\cred-helpers" - New-Item -ItemType Directory -Path $credHelperDir -Force | Out-Null - - # Copy credential helper binary - Copy-Item -Path "_output/bin/finch-credhelper.exe" -Destination "$credHelperDir\finch-credhelper.exe" -Force - - Write-Host "๐Ÿ“ Credential helper binary located at: $credHelperDir\finch-credhelper.exe" -ForegroundColor Yellow -} else { - Write-Host "โš ๏ธ Credential helper not built - skipping setup" -ForegroundColor Yellow -} - -Write-Host "๐Ÿ–ฅ๏ธ Initializing VM (this may take a few minutes)..." -ForegroundColor Green -$initResult = Start-Process -FilePath "./_output/bin/finch.exe" -ArgumentList "vm", "init" -Wait -PassThru -if ($LASTEXITCODE -eq 0) { - Write-Host "โœ… VM initialized successfully!" -ForegroundColor Green -} else { - Write-Host "โŒ VM initialization failed" -ForegroundColor Red - exit 1 -} - -Write-Host "๐Ÿ” Checking VM status..." -ForegroundColor Green -Start-Process -FilePath "./_output/bin/finch.exe" -ArgumentList "vm", "status" -Wait -NoNewWindow - -Write-Host "โœ… Setup complete!" -ForegroundColor Green \ No newline at end of file diff --git a/fix-wsl-init.ps1 b/fix-wsl-init.ps1 deleted file mode 100644 index 6630cff66..000000000 --- a/fix-wsl-init.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -# Fix WSL2 initialization issues for Finch - -Write-Host "Fixing WSL2 initialization issues..." -ForegroundColor Yellow - -# Ensure WSL is properly configured -Write-Host "Checking WSL configuration..." -ForegroundColor Green -wsl --set-default-version 2 - -# Clean up any existing lima-finch distro -Write-Host "Cleaning up existing WSL distros..." -ForegroundColor Green -$wslList = wsl --list --quiet 2>$null -foreach ($line in $wslList) { - $distro = $line.Trim() - if ($distro -and ($distro -like "*lima-finch*" -or $distro -eq "lima-finch")) { - Write-Host " Unregistering: $distro" -ForegroundColor Cyan - wsl --unregister $distro 2>$null - } -} - -# Set proper temp directory permissions -Write-Host "Setting temp directory permissions..." -ForegroundColor Green -$tempDir = $env:TEMP -if (Test-Path $tempDir) { - # Grant full control to current user - icacls $tempDir /grant "${env:USERNAME}:(OI)(CI)F" /T 2>$null -} - -# Create a custom temp directory for Lima -Write-Host "Creating Lima temp directory..." -ForegroundColor Green -$limaTemp = "C:\lima-temp" -if (!(Test-Path $limaTemp)) { - New-Item -ItemType Directory -Path $limaTemp -Force | Out-Null -} -icacls $limaTemp /grant "${env:USERNAME}:(OI)(CI)F" /T 2>$null - -# Set environment variable for Lima to use our temp directory -$env:TMPDIR = $limaTemp -$env:TMP = $limaTemp -$env:TEMP = $limaTemp - -Write-Host "WSL2 environment prepared. Try vm init again." -ForegroundColor Green -Write-Host "If it still fails, try running as Administrator." -ForegroundColor Yellow \ No newline at end of file diff --git a/patch-lima-wsl2.ps1 b/patch-lima-wsl2.ps1 deleted file mode 100644 index c34ca4241..000000000 --- a/patch-lima-wsl2.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -# Patch Lima WSL2 driver to use accessible temp location - -$limaFile = "deps/finch-core/src/lima/pkg/driver/wsl2/vm_windows.go" - -# Backup original -Copy-Item $limaFile "$limaFile.backup" - -# Replace temp file creation to use C:\ directly -$content = Get-Content $limaFile -Raw -$content = $content -replace 'os\.CreateTemp\("", "lima-wsl2-boot-\*\.sh"\)', 'os.CreateTemp("C:\\", "lima-wsl2-boot-*.sh")' - -Set-Content $limaFile $content - -Write-Host "Patched Lima to use C:\ for temp files" -ForegroundColor Green -Write-Host "Now run: make clean && make" -ForegroundColor Yellow \ No newline at end of file diff --git a/test-creds-windows.ps1 b/test-creds-windows.ps1 deleted file mode 100644 index e50415ae7..000000000 --- a/test-creds-windows.ps1 +++ /dev/null @@ -1,67 +0,0 @@ -# Windows credential helper testing script -# Run after dev-setup-windows.ps1 - -param( - [switch]$Manual = $false -) - -$credHelperPath = "$env:LOCALAPPDATA\.finch\cred-helpers\finch-credhelper.exe" - -Write-Host "๐Ÿงช Starting Windows credential helper tests..." -ForegroundColor Green - -if ($Manual) { - Write-Host "๐Ÿ”ง Manual mode: Starting credential helper process..." -ForegroundColor Yellow - - # Start credential helper in background - $credProcess = Start-Process -FilePath $credHelperPath -PassThru -WindowStyle Hidden - Write-Host " Process ID: $($credProcess.Id)" -ForegroundColor Cyan - - # Give it time to start - Start-Sleep -Seconds 2 -} - -Write-Host "๐Ÿณ Testing basic container operations..." -ForegroundColor Green - -# Test 1: Pull public image -Write-Host " 1. Pulling public image..." -ForegroundColor Cyan -& ".\_output\bin\finch.exe" pull alpine - -# Test 2: Setup local registry (if Docker is available) -Write-Host " 2. Setting up local registry..." -ForegroundColor Cyan -try { - & ".\_output\bin\finch.exe" run -d --name registry -p 5000:5000 registry:2 - Start-Sleep -Seconds 3 - - # Test 3: Tag and push to local registry - Write-Host " 3. Testing local registry push..." -ForegroundColor Cyan - & ".\_output\bin\finch.exe" tag alpine localhost:5000/test-image - & ".\_output\bin\finch.exe" push localhost:5000/test-image - - Write-Host "โœ… Registry tests passed!" -ForegroundColor Green -} catch { - Write-Host "โš ๏ธ Registry tests skipped (registry not available)" -ForegroundColor Yellow -} - -# Test 4: Check credential storage -Write-Host " 4. Checking credential storage..." -ForegroundColor Cyan -$configPath = "$env:USERPROFILE\.finch\config.json" -if (Test-Path $configPath) { - $config = Get-Content $configPath | ConvertFrom-Json - Write-Host " Config found: $configPath" -ForegroundColor Cyan - if ($config.credsStore) { - Write-Host " Credential store: $($config.credsStore)" -ForegroundColor Cyan - } -} else { - Write-Host " No config.json found" -ForegroundColor Yellow -} - -# Cleanup -Write-Host "๐Ÿงน Cleaning up..." -ForegroundColor Green -& ".\_output\bin\finch.exe" rm -f registry 2>$null - -if ($Manual -and $credProcess) { - Write-Host "๐Ÿ›‘ Stopping credential helper process..." -ForegroundColor Yellow - Stop-Process -Id $credProcess.Id -Force -ErrorAction SilentlyContinue -} - -Write-Host "โœ… Windows credential tests completed!" -ForegroundColor Green \ No newline at end of file From f5272e866c6bc4d3ef9ce7272d5597a2edca91cc Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Sun, 14 Dec 2025 22:56:49 -0800 Subject: [PATCH 03/21] basic implementation refactoring Signed-off-by: ayush-panta --- cmd/finch-credhelper/helper-utils.go | 90 ++++++++++++++-------------- cmd/finch-credhelper/mac-creds.go | 26 +++----- 2 files changed, 53 insertions(+), 63 deletions(-) diff --git a/cmd/finch-credhelper/helper-utils.go b/cmd/finch-credhelper/helper-utils.go index e954c6d9a..5c0737431 100644 --- a/cmd/finch-credhelper/helper-utils.go +++ b/cmd/finch-credhelper/helper-utils.go @@ -3,7 +3,6 @@ package main import ( "encoding/json" "fmt" - "log" "os" "os/exec" "path/filepath" @@ -14,63 +13,64 @@ import ( ) const ( - maxBufferSize = 4096 + MaxBufferSize = 4096 + CredHelpersDir = "cred-helpers" + FinchConfigDir = ".finch" ) -func parseCredstoreRequest(request string) (command, input string, err error) { +var credentialHelperNames = map[string]string{ + "darwin": "docker-credential-osxkeychain", + "windows": "docker-credential-wincred.exe", +} - lines := strings.Split(request, "\n") +// requests come in as "{command}\n{json}" +func parseCredstoreRequest(request string) (command, input string, err error) { + lines := strings.Split(strings.TrimSpace(request), "\n") if len(lines) == 0 { - return "", "", fmt.Errorf("ERROR: Empty request.") + return "", "", fmt.Errorf("empty request") } - command = lines[0] - if command == "list" { // too keep or not to keep? + command = strings.TrimSpace(lines[0]) + if command == "list" { return command, "", nil } - if len(lines) != 2 { - return "", "", fmt.Errorf("ERROR: command %s requires input", command) + if len(lines) < 2 { + return "", "", fmt.Errorf("command %s requires input", command) } - return command, lines[1], nil + return command, strings.TrimSpace(lines[1]), nil } -func forwardToCredHelper(command, input string) (string, error) { - log.Printf("Forwarding command: %s, input: %s", command, input) - +// invokes the platform-specific credential helper +func executeCredentialHelper(command, input string) (string, error) { credHelperPath, err := getCredentialHelperPath() if err != nil { return "", err } cmd := exec.Command(credHelperPath, command) - cmd.Stdin = strings.NewReader(input) + if input != "" { + cmd.Stdin = strings.NewReader(input) + } cmd.Env = os.Environ() output, err := cmd.CombinedOutput() response := strings.TrimSpace(string(output)) - log.Printf("Raw output: %q", string(output)) - log.Printf("Error: %v", err) - if cmd.ProcessState != nil { - log.Printf("Exit code: %d", cmd.ProcessState.ExitCode()) - } - if err != nil { - log.Printf("Credential helper failed: %s", response) if command == "get" { + // Return empty credentials for failed get operations emptyCreds := credentials.Credentials{ServerURL: input, Username: "", Secret: ""} credsJSON, _ := json.Marshal(emptyCreds) - log.Printf("Returning empty credentials: %s", string(credsJSON)) return string(credsJSON), nil } - return response, err + return "", fmt.Errorf("credential helper failed: %w", err) } - log.Printf("Credential helper SUCCESS - response: %s", response) return response, nil } +// get the name of the credhelper... func getCredentialHelperPath() (string, error) { var helperName string switch runtime.GOOS { @@ -79,48 +79,48 @@ func getCredentialHelperPath() (string, error) { case "windows": helperName = "docker-credential-wincred.exe" default: - return "", fmt.Errorf("unsupported platform: %s", runtime.GOOS) // ? + return "", fmt.Errorf("credential helper not supported on %s", runtime.GOOS) } - path := filepath.Join(os.Getenv("HOME"), ".finch", "cred-helpers", helperName) - _, err := os.Stat(path) + homeDir, err := os.UserHomeDir() if err != nil { - return "", fmt.Errorf("ERROR: %s not found", helperName) + return "", fmt.Errorf("failed to get home directory: %w", err) + } + + path := filepath.Join(homeDir, ".finch", "cred-helpers", helperName) + if _, err := os.Stat(path); err != nil { + return "", fmt.Errorf("credential helper %s not found at %s", helperName, path) } return path, nil } -// Core credential processing logic - shared across platforms +// processCredentialRequest handles credential requests from the VM func processCredentialRequest(conn interface{ Read([]byte) (int, error); Write([]byte) (int, error) }) error { - // read from buffer + const maxBufferSize = 4096 + buffer := make([]byte, maxBufferSize) - data, err := conn.Read(buffer) + n, err := conn.Read(buffer) if err != nil { - return fmt.Errorf("ERROR: read error: %w", err) + return fmt.Errorf("failed to read request: %w", err) } - // parse request - request := strings.TrimSpace(string(buffer[:data])) + request := strings.TrimSpace(string(buffer[:n])) command, input, err := parseCredstoreRequest(request) if err != nil { - return fmt.Errorf("ERROR: %w", err) + return fmt.Errorf("invalid request: %w", err) } - // forward and handle request - response, err := forwardToCredHelper(command, input) + response, err := executeCredentialHelper(command, input) if err != nil { - log.Printf("Credential helper error: %v", err) + // For failed operations, return empty response instead of error + response = "" } - // handle credential not found + // Handle credential not found cases if strings.Contains(response, "credentials not found") { response = "" - log.Printf("Credentials not found - returning empty response") - } else { - log.Printf("Response to VM: %q", response) } - // write back to connection - _, writeErr := conn.Write([]byte(response)) - return writeErr + _, err = conn.Write([]byte(response)) + return err } diff --git a/cmd/finch-credhelper/mac-creds.go b/cmd/finch-credhelper/mac-creds.go index a6f3b095a..97daebc4b 100644 --- a/cmd/finch-credhelper/mac-creds.go +++ b/cmd/finch-credhelper/mac-creds.go @@ -4,36 +4,26 @@ package main import ( "fmt" - "log" "net" "os" - "strings" ) -// macOS socket activation handler +// handleCredstoreRequest processes credential requests via socket activation func handleCredstoreRequest() error { - // connect to socket, defer close til after return conn, err := net.FileConn(os.Stdin) if err != nil { - return fmt.Errorf("ERROR: failed to get connection from stdin: %w", err) + return fmt.Errorf("failed to create connection from stdin: %w", err) } defer conn.Close() - // use shared credential processing logic return processCredentialRequest(conn) } -func darwinKeychainHandler() { - // Test if stdin is a network connection (inetd style) - if _, err := net.FileConn(os.Stdin); err == nil { - if err := handleCredstoreRequest(); err != nil { - os.Exit(1) - } - } else { - os.Exit(0) - } -} - func main() { - darwinKeychainHandler() + // macOS credential helper using socket activation via launchd + // launchd passes the socket connection through stdin + if err := handleCredstoreRequest(); err != nil { + fmt.Fprintf(os.Stderr, "credential helper error: %v\n", err) + os.Exit(1) + } } \ No newline at end of file From 518cd18dd170a6acb16d37c92e66882b11119642 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Sun, 14 Dec 2025 23:47:39 -0800 Subject: [PATCH 04/21] refactoring utils and mac impl. Signed-off-by: ayush-panta --- cmd/finch-credhelper/helper-utils.go | 82 ++++++++++++---------------- cmd/finch-credhelper/mac-creds.go | 2 +- 2 files changed, 36 insertions(+), 48 deletions(-) diff --git a/cmd/finch-credhelper/helper-utils.go b/cmd/finch-credhelper/helper-utils.go index 5c0737431..728daaf07 100644 --- a/cmd/finch-credhelper/helper-utils.go +++ b/cmd/finch-credhelper/helper-utils.go @@ -1,21 +1,18 @@ package main import ( - "encoding/json" "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" - - "github.com/docker/docker-credential-helpers/credentials" ) const ( - MaxBufferSize = 4096 - CredHelpersDir = "cred-helpers" - FinchConfigDir = ".finch" + maxBufferSize = 4096 + credHelpersDir = "cred-helpers" + finchConfigDir = ".finch" ) var credentialHelperNames = map[string]string{ @@ -23,7 +20,7 @@ var credentialHelperNames = map[string]string{ "windows": "docker-credential-wincred.exe", } -// requests come in as "{command}\n{json}" +// Parsing JSON requests func parseCredstoreRequest(request string) (command, input string, err error) { lines := strings.Split(strings.TrimSpace(request), "\n") if len(lines) == 0 { @@ -41,7 +38,27 @@ func parseCredstoreRequest(request string) (command, input string, err error) { return command, strings.TrimSpace(lines[1]), nil } -// invokes the platform-specific credential helper +// Determining and validating the credential helper path +func getCredentialHelperPath() (string, error) { + helperName, exists := credentialHelperNames[runtime.GOOS] + if !exists { + return "", fmt.Errorf("credential helper not supported on this platform") + } + + homeDir, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("failed to get home directory") + } + + path := filepath.Join(homeDir, finchConfigDir, credHelpersDir, helperName) + if _, err := os.Stat(path); err != nil { + return "", fmt.Errorf("credential helper not found") + } + + return path, nil +} + +// Invoking the platform-specific credential helper binary func executeCredentialHelper(command, input string) (string, error) { credHelperPath, err := getCredentialHelperPath() if err != nil { @@ -57,68 +74,39 @@ func executeCredentialHelper(command, input string) (string, error) { output, err := cmd.CombinedOutput() response := strings.TrimSpace(string(output)) + // Handling errors, with special case for "get" command requiring empty cred. JSON if err != nil { if command == "get" { - // Return empty credentials for failed get operations - emptyCreds := credentials.Credentials{ServerURL: input, Username: "", Secret: ""} - credsJSON, _ := json.Marshal(emptyCreds) - return string(credsJSON), nil + return createEmptyCredentials(input), nil } - return "", fmt.Errorf("credential helper failed: %w", err) + return "", fmt.Errorf("credential helper failed") } return response, nil } -// get the name of the credhelper... -func getCredentialHelperPath() (string, error) { - var helperName string - switch runtime.GOOS { - case "darwin": - helperName = "docker-credential-osxkeychain" - case "windows": - helperName = "docker-credential-wincred.exe" - default: - return "", fmt.Errorf("credential helper not supported on %s", runtime.GOOS) - } - - homeDir, err := os.UserHomeDir() - if err != nil { - return "", fmt.Errorf("failed to get home directory: %w", err) - } - - path := filepath.Join(homeDir, ".finch", "cred-helpers", helperName) - if _, err := os.Stat(path); err != nil { - return "", fmt.Errorf("credential helper %s not found at %s", helperName, path) - } - return path, nil +// Creates default credentials when credentials are not found +func createEmptyCredentials(serverURL string) string { + return fmt.Sprintf(`{"ServerURL":"%s","Username":"","Secret":""}`, serverURL) } -// processCredentialRequest handles credential requests from the VM +// Process inbound credential requests from Lima VM bridge func processCredentialRequest(conn interface{ Read([]byte) (int, error); Write([]byte) (int, error) }) error { - const maxBufferSize = 4096 - buffer := make([]byte, maxBufferSize) n, err := conn.Read(buffer) if err != nil { - return fmt.Errorf("failed to read request: %w", err) + return fmt.Errorf("failed to read request") } request := strings.TrimSpace(string(buffer[:n])) command, input, err := parseCredstoreRequest(request) if err != nil { - return fmt.Errorf("invalid request: %w", err) + return err } response, err := executeCredentialHelper(command, input) if err != nil { - // For failed operations, return empty response instead of error - response = "" - } - - // Handle credential not found cases - if strings.Contains(response, "credentials not found") { - response = "" + return err } _, err = conn.Write([]byte(response)) diff --git a/cmd/finch-credhelper/mac-creds.go b/cmd/finch-credhelper/mac-creds.go index 97daebc4b..98f649fe9 100644 --- a/cmd/finch-credhelper/mac-creds.go +++ b/cmd/finch-credhelper/mac-creds.go @@ -23,7 +23,7 @@ func main() { // macOS credential helper using socket activation via launchd // launchd passes the socket connection through stdin if err := handleCredstoreRequest(); err != nil { - fmt.Fprintf(os.Stderr, "credential helper error: %v\n", err) + fmt.Fprintf(os.Stderr, "credential helper failed\n") os.Exit(1) } } \ No newline at end of file From 6858059d5ca894a8b499712636108dcc589744d6 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Mon, 15 Dec 2025 00:19:35 -0800 Subject: [PATCH 05/21] Changes to install directories Signed-off-by: ayush-panta --- .../com.runfinch.cred-bridge.plist.template | 10 +----- cmd/finch-credhelper/windows-creds.go | 32 ++++--------------- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template index 1c6d19fba..e99465591 100644 --- a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template +++ b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template @@ -19,17 +19,12 @@ SockPathName - $HOME/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock + $HOME/.finch/native-creds.sock SockPathMode 384 - - StandardOutPath - $HOME/.finch/cred-bridge.log - StandardErrorPath - $HOME/.finch/cred-bridge.log inetdCompatibility @@ -37,8 +32,5 @@ - - - \ No newline at end of file diff --git a/cmd/finch-credhelper/windows-creds.go b/cmd/finch-credhelper/windows-creds.go index 70a110994..c689d18e2 100644 --- a/cmd/finch-credhelper/windows-creds.go +++ b/cmd/finch-credhelper/windows-creds.go @@ -12,25 +12,13 @@ import ( // Windows socket server (for WSL2 socket forwarding) func startWindowsCredentialServer() error { + // Create socket path in user's Documents/finch-creds directory homeDir, err := os.UserHomeDir() if err != nil { return fmt.Errorf("failed to get home directory: %w", err) } - - // Setup file logging - logPath := filepath.Join(homeDir, "Documents", "finch-creds", "finch", "_output", "finch-credhelper", "cred-bridge.log") - logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - return fmt.Errorf("failed to open log file: %w", err) - } - defer logFile.Close() - log.SetOutput(logFile) - - socketPath := filepath.Join(homeDir, "Documents", "finch-creds", "finch", "_output", "finch-credhelper", "native-creds.sock") - log.Printf("Starting Windows credential server on socket: %s", socketPath) - - // Remove existing socket if it exists + socketPath := filepath.Join(homeDir, ".finch", "native-creds.sock") os.Remove(socketPath) // Listen on Unix socket @@ -40,33 +28,27 @@ func startWindowsCredentialServer() error { } defer listener.Close() - log.Printf("Windows credential server listening...") - // Accept connections for { conn, err := listener.Accept() if err != nil { - log.Printf("Failed to accept connection: %v", err) + log.Printf("Failed to accept connection") continue } // Handle each connection go func(c net.Conn) { defer c.Close() - if err := processCredentialRequest(c); err != nil { - log.Printf("Error processing credential request: %v", err) + if err := processCredentialRequest(c); err != nil { + log.Printf("Error processing credential request") } }(conn) } } -func windowsKeychainHandler() { +func main() { if err := startWindowsCredentialServer(); err != nil { - log.Printf("Windows credential server failed: %v", err) + log.Printf("Windows credential server failed") os.Exit(1) } -} - -func main() { - windowsKeychainHandler() } \ No newline at end of file From a6a5e16e3ad8a413b970174f7450d5d68f615191 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Mon, 15 Dec 2025 00:46:12 -0800 Subject: [PATCH 06/21] Better cred makefile Signed-off-by: ayush-panta --- Makefile.creds | 129 ++++++++++++++----------------------------------- 1 file changed, 36 insertions(+), 93 deletions(-) diff --git a/Makefile.creds b/Makefile.creds index 5b20ade57..012fdf533 100644 --- a/Makefile.creds +++ b/Makefile.creds @@ -1,49 +1,42 @@ +# Credential helper configuration CRED_HELPER_BASE_URL := https://github.com/docker/docker-credential-helpers/releases/download/v0.9.4 -DARWIN_ARM64_ARTIFACT := docker-credential-osxkeychain-v0.9.4.darwin-arm64 -DARWIN_ARM64_256_DIGEST := 8db5b7cbcbe0870276e56aa416416161785e450708af64cda0f1be4c392dc2e5 - -DARWIN_AMD64_ARTIFACT := docker-credential-osxkeychain-v0.9.4.darwin-amd64 -DARWIN_AMD64_256_DIGEST := ad76d1a1e03def49edfa57fdb2874adf2c468cfa0438aae1b2589434796f7c01 - -WINDOWS_ARM64_ARTIFACT := docker-credential-wincred-v0.9.4.windows-arm64.exe -WINDOWS_ARM64_256_DIGEST := 80a6ddbbabc51a8952308acf4d03c044308357cf217300461c44df066c57fe03 - -WINDOWS_AMD64_ARTIFACT := docker-credential-wincred-v0.9.4.windows-amd64.exe -WINDOWS_AMD64_256_DIGEST := 66fdf4b50c83aeb04a9ea04af960abaf1a7b739ab263115f956b98bb0d16aa7e - -# Platform-specific credential helper configuration +# Platform-specific artifacts and checksums ifeq ($(BUILD_OS), Darwin) -CRED_HELPER_ARTIFACT := $(if $(findstring arm64,$(ARCH)),$(DARWIN_ARM64_ARTIFACT),$(DARWIN_AMD64_ARTIFACT)) -CRED_HELPER_DIGEST := $(if $(findstring arm64,$(ARCH)),$(DARWIN_ARM64_256_DIGEST),$(DARWIN_AMD64_256_DIGEST)) -CRED_HELPER_NAME := docker-credential-osxkeychain + ifeq ($(ARCH), arm64) + CRED_HELPER_ARTIFACT := docker-credential-osxkeychain-v0.9.4.darwin-arm64 + CRED_HELPER_DIGEST := 8db5b7cbcbe0870276e56aa416416161785e450708af64cda0f1be4c392dc2e5 + else + CRED_HELPER_ARTIFACT := docker-credential-osxkeychain-v0.9.4.darwin-amd64 + CRED_HELPER_DIGEST := ad76d1a1e03def49edfa57fdb2874adf2c468cfa0438aae1b2589434796f7c01 + endif + CRED_HELPER_NAME := docker-credential-osxkeychain else ifeq ($(BUILD_OS), Windows_NT) -CRED_HELPER_ARTIFACT := $(if $(findstring arm64,$(ARCH)),$(WINDOWS_ARM64_ARTIFACT),$(WINDOWS_AMD64_ARTIFACT)) -CRED_HELPER_DIGEST := $(if $(findstring arm64,$(ARCH)),$(WINDOWS_ARM64_256_DIGEST),$(WINDOWS_AMD64_256_DIGEST)) -CRED_HELPER_NAME := docker-credential-wincred + ifeq ($(ARCH), arm64) + CRED_HELPER_ARTIFACT := docker-credential-wincred-v0.9.4.windows-arm64.exe + CRED_HELPER_DIGEST := 80a6ddbbabc51a8952308acf4d03c044308357cf217300461c44df066c57fe03 + else + CRED_HELPER_ARTIFACT := docker-credential-wincred-v0.9.4.windows-amd64.exe + CRED_HELPER_DIGEST := 66fdf4b50c83aeb04a9ea04af960abaf1a7b739ab263115f956b98bb0d16aa7e + endif + CRED_HELPER_NAME := docker-credential-wincred.exe endif CRED_HELPER_URL := $(CRED_HELPER_BASE_URL)/$(CRED_HELPER_ARTIFACT) -CRED_HELPER_OUTPUT := $(OUTDIR)/finch-credhelper/$(CRED_HELPER_NAME)$(if $(findstring .exe,$(CRED_HELPER_ARTIFACT)),.exe,) -CRED_HELPER_VM_PATH := ~/.finch/cred-helpers/$(CRED_HELPER_NAME) +CRED_HELPER_OUTPUT := $(OUTDIR)/finch-credhelper/$(CRED_HELPER_NAME) +# Build finch credential bridge .PHONY: finch-cred-bridge finch-cred-bridge: mkdir -p $(OUTDIR)/finch-credhelper $(GO) build -ldflags $(LDFLAGS) -tags "$(GO_BUILD_TAGS)" -o $(OUTDIR)/finch-credhelper/finch-cred-bridge $(PACKAGE)/cmd/finch-credhelper +# Download and verify credential helper .PHONY: docker-credential-helper docker-credential-helper: ifeq ($(BUILD_OS), Linux) @echo "No credential helper needed for Linux" -else ifeq ($(BUILD_OS), Windows_NT) - @"$(MAKE)" install-credential-helper create-dummy-helper else - @$(MAKE) install-credential-helper create-dummy-helper -endif - -.PHONY: install-credential-helper -install-credential-helper: mkdir -p $(dir $(CRED_HELPER_OUTPUT)) curl -L $(CRED_HELPER_URL) -o $(CRED_HELPER_OUTPUT) @echo "Verifying SHA256 checksum..." @@ -53,92 +46,42 @@ install-credential-helper: echo "Checksum verification failed" && exit 1; \ fi chmod +x $(CRED_HELPER_OUTPUT) - -.PHONY: create-dummy-helper -create-dummy-helper: -ifeq ($(BUILD_OS), Windows_NT) - mkdir -p "$(USERPROFILE)/.finch/cred-helpers/" - cp "$(CRED_HELPER_OUTPUT)" "$(USERPROFILE)/.finch/cred-helpers/$(CRED_HELPER_NAME)" -else - mkdir -p $(dir $(CRED_HELPER_VM_PATH)) - cp $(CRED_HELPER_OUTPUT) $(CRED_HELPER_VM_PATH) endif -# LaunchAgent plist management (macOS only) +# macOS LaunchAgent management ifeq ($(BUILD_OS), Darwin) PLIST_NAME := com.runfinch.cred-bridge.plist -PLIST_SOURCE := cmd/finch-credhelper/$(PLIST_NAME) PLIST_TEMPLATE := cmd/finch-credhelper/$(PLIST_NAME).template PLIST_DEST := $(HOME)/Library/LaunchAgents/$(PLIST_NAME) -.PHONY: generate-plist -generate-plist: - @echo "Generating plist with current paths..." +.PHONY: install-launch-agent +install-launch-agent: + @echo "Installing LaunchAgent..." sed -e "s|\$$HOME|$(HOME)|g" \ - -e "s|\$$USER|$(USER)|g" \ -e "s|\$$FINCH_CRED_BRIDGE_PATH|$(CURDIR)/_output/finch-credhelper/finch-cred-bridge|g" \ - $(PLIST_TEMPLATE) > $(PLIST_SOURCE) - -.PHONY: install-plist -install-plist: generate-plist - @echo "Installing LaunchAgent plist..." - cp $(PLIST_SOURCE) $(PLIST_DEST) + $(PLIST_TEMPLATE) > $(PLIST_DEST) launchctl load $(PLIST_DEST) - @echo "LaunchAgent loaded successfully" + @echo "LaunchAgent installed and loaded" -.PHONY: uninstall-plist -uninstall-plist: - @echo "Uninstalling LaunchAgent plist..." +.PHONY: uninstall-launch-agent +uninstall-launch-agent: + @echo "Uninstalling LaunchAgent..." -launchctl unload $(PLIST_DEST) 2>/dev/null || true -rm $(PLIST_DEST) 2>/dev/null || true @echo "LaunchAgent uninstalled" -.PHONY: reload-plist -reload-plist: uninstall-plist install-plist - @echo "LaunchAgent reloaded" - -.PHONY: dev-install -dev-install: - @echo "Creating development symlinks to mimic /Applications/Finch installation..." - sudo mkdir -p /Applications/Finch/bin/ - sudo ln -sf $(CURDIR)/_output/bin/finch-cred-bridge /Applications/Finch/bin/finch-cred-bridge - sudo ln -sf $(CURDIR)/_output/bin/finch /Applications/Finch/bin/finch - @echo "Development installation complete - finch appears to be in /Applications/Finch" - -.PHONY: dev-uninstall -dev-uninstall: - @echo "Removing development symlinks..." - -sudo rm /Applications/Finch/bin/finch-cred-bridge 2>/dev/null || true - -sudo rm /Applications/Finch/bin/finch 2>/dev/null || true - @echo "Development installation removed" - .PHONY: setup-cred-bridge -setup-cred-bridge: -ifeq ($(BUILD_OS), Darwin) +setup-cred-bridge: finch-cred-bridge @echo "Setting up credential bridge..." - @if [ ! -L "/Applications/Finch/bin/finch-cred-bridge" ]; then \ - echo "Creating credential bridge symlink (requires sudo)..."; \ - sudo mkdir -p /Applications/Finch/bin/; \ - sudo ln -sf $(CURDIR)/_output/bin/finch-cred-bridge /Applications/Finch/bin/finch-cred-bridge; \ - fi - @if [ ! -L "/Applications/Finch/bin/finch" ]; then \ - echo "Creating finch binary symlink (requires sudo)..."; \ - sudo ln -sf $(CURDIR)/_output/bin/finch /Applications/Finch/bin/finch; \ - fi @if ! launchctl list | grep -q com.runfinch.cred-bridge; then \ - echo "Installing LaunchAgent..."; \ - $(MAKE) install-plist; \ + $(MAKE) install-launch-agent; \ else \ echo "LaunchAgent already loaded"; \ fi @echo "Credential bridge setup complete" -else ifeq ($(BUILD_OS), Windows_NT) - @echo "Credential bridge setup for Windows - no additional setup needed" -else - @echo "Credential bridge setup not supported on this platform" -endif + else -.PHONY: install-plist uninstall-plist reload-plist -install-plist uninstall-plist reload-plist: - @echo "LaunchAgent plist management is macOS-specific" +.PHONY: install-launch-agent uninstall-launch-agent setup-cred-bridge +install-launch-agent uninstall-launch-agent setup-cred-bridge: + @echo "LaunchAgent management is macOS-specific" endif \ No newline at end of file From 5052364ebef4723c3973a1175ab70af309c32ce0 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Mon, 15 Dec 2025 00:55:43 -0800 Subject: [PATCH 07/21] More makefile changes Signed-off-by: ayush-panta --- Makefile | 12 ++++-------- Makefile.creds | 12 +++++++++++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index fe1342499..d63a6e3ce 100644 --- a/Makefile +++ b/Makefile @@ -79,11 +79,10 @@ endif FINCH_CORE_DIR := $(CURDIR)/deps/finch-core -ifeq ($(BUILD_OS), Windows_NT) -remote-all: arch-test finch finch-cred-bridge docker-credential-helper install.finch-core-dependencies finch.yaml networks.yaml config.yaml $(OUTDIR)/finch-daemon/finch@.service -else -remote-all: arch-test finch finch-cred-bridge docker-credential-helper install.finch-core-dependencies finch.yaml networks.yaml config.yaml $(OUTDIR)/finch-daemon/finch@.service setup-cred-bridge -endif +# Include credential helper targets +include Makefile.creds + +remote-all: arch-test finch make-creds install.finch-core-dependencies finch.yaml networks.yaml config.yaml $(OUTDIR)/finch-daemon/finch@.service ifeq ($(BUILD_OS), Windows_NT) include Makefile.windows @@ -96,9 +95,6 @@ else ifeq ($(BUILD_OS), Linux) all: finch endif -# Include credential helper targets -include Makefile.creds - .PHONY: install.finch-core-dependencies install.finch-core-dependencies: OUTDIR=$(OUTDIR) ARCH=$(ARCH) "$(MAKE)" -C $(FINCH_CORE_DIR) install.dependencies diff --git a/Makefile.creds b/Makefile.creds index 012fdf533..6efe9ddc9 100644 --- a/Makefile.creds +++ b/Makefile.creds @@ -84,4 +84,14 @@ else .PHONY: install-launch-agent uninstall-launch-agent setup-cred-bridge install-launch-agent uninstall-launch-agent setup-cred-bridge: @echo "LaunchAgent management is macOS-specific" -endif \ No newline at end of file +endif + +.PHONY: make-creds +ifeq ($(BUILD_OS), Linux) +make-creds: + @echo "No credential helpers needed for Linux" +else ifeq ($(BUILD_OS), Windows_NT) +make-creds: finch-cred-bridge docker-credential-helper +else +make-creds: finch-cred-bridge docker-credential-helper setup-cred-bridge +endif From 2ba34c1029240ad0e67eba0a9b6e4c553a6d88eb Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Mon, 15 Dec 2025 01:00:22 -0800 Subject: [PATCH 08/21] remove accidental AI-made changes Signed-off-by: ayush-panta --- cmd/finch/virtual_machine_init.go | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/cmd/finch/virtual_machine_init.go b/cmd/finch/virtual_machine_init.go index b2db94963..89fdd8097 100644 --- a/cmd/finch/virtual_machine_init.go +++ b/cmd/finch/virtual_machine_init.go @@ -126,27 +126,6 @@ func (iva *initVMAction) run() error { } iva.logger.Info("Finch virtual machine started successfully") - - if runtime.GOOS == "darwin" { - // Install credential helper launchd service - plistSrc := filepath.Join(os.Getenv("HOME"), "Documents/finch-creds/finch/cmd/finch/cred-helper/Info.plist") - plistDst := filepath.Join(os.Getenv("HOME"), "Library/LaunchAgents/com.runfinch.credhelper.plist") - - // Copy plist file - cpCmd := exec.Command("cp", plistSrc, plistDst) - if err := cpCmd.Run(); err != nil { - iva.logger.Warnf("Failed to copy credential helper plist: %v", err) - } else { - // Load the service - loadCmd := exec.Command("launchctl", "load", plistDst) - if err := loadCmd.Run(); err != nil { - iva.logger.Warnf("Failed to load credential helper service: %v", err) - } else { - iva.logger.Info("Credential helper service installed successfully") - } - } - } - return nil } From e2ac84ade003a2e1394d95659aebd4ada074e974 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Mon, 15 Dec 2025 01:25:28 -0800 Subject: [PATCH 09/21] Minor fixes, mac working again Signed-off-by: ayush-panta --- cmd/finch-credhelper/com.runfinch.cred-bridge.plist | 10 +--------- .../com.runfinch.cred-bridge.plist.template | 2 +- cmd/finch/virtual_machine_init.go | 3 --- dev-setup.sh | 6 ------ 4 files changed, 2 insertions(+), 19 deletions(-) mode change 100755 => 100644 cmd/finch-credhelper/com.runfinch.cred-bridge.plist diff --git a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist old mode 100755 new mode 100644 index 6b043456c..8037fd6b2 --- a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist +++ b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist @@ -19,17 +19,12 @@ SockPathName - /Users/ayushkp/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock +- /Users/ayushkp/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock SockPathMode 384 - - StandardOutPath - /Users/ayushkp/.finch/cred-bridge.log - StandardErrorPath - /Users/ayushkp/.finch/cred-bridge.log inetdCompatibility @@ -37,8 +32,5 @@ - - - \ No newline at end of file diff --git a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template index e99465591..54ab0bf13 100644 --- a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template +++ b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template @@ -19,7 +19,7 @@ SockPathName - $HOME/.finch/native-creds.sock + $HOME/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock SockPathMode 384 diff --git a/cmd/finch/virtual_machine_init.go b/cmd/finch/virtual_machine_init.go index 89fdd8097..d295180e1 100644 --- a/cmd/finch/virtual_machine_init.go +++ b/cmd/finch/virtual_machine_init.go @@ -7,9 +7,6 @@ package main import ( "fmt" - "os" - "os/exec" - "path/filepath" "runtime" "github.com/runfinch/finch/pkg/disk" diff --git a/dev-setup.sh b/dev-setup.sh index 17c83a8f5..c241d2786 100755 --- a/dev-setup.sh +++ b/dev-setup.sh @@ -21,12 +21,6 @@ make echo "๐Ÿงน Cleaning credential helper log..." rm -f _output/finch-credhelper/cred-bridge.log -echo "๐Ÿ”„ Reloading credential helper service..." -launchctl unload ~/Library/LaunchAgents/com.runfinch.cred-bridge.plist 2>/dev/null || true -make generate-plist -cp cmd/finch-credhelper/com.runfinch.cred-bridge.plist ~/Library/LaunchAgents/ -launchctl load ~/Library/LaunchAgents/com.runfinch.cred-bridge.plist - echo "๐Ÿ–ฅ๏ธ Initializing VM..." ./_output/bin/finch vm init From 11f758208d74c1234ce3d4647e5c30015acfb540 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Mon, 15 Dec 2025 10:29:50 -0800 Subject: [PATCH 10/21] nerdctl config fixes Signed-off-by: ayush-panta --- pkg/config/cred-helper-overwrite.sh | 29 --------------------- pkg/config/defaults_darwin.go | 1 - pkg/config/nerdctl_config_applier.go | 38 +++++++++++++++------------- 3 files changed, 20 insertions(+), 48 deletions(-) delete mode 100644 pkg/config/cred-helper-overwrite.sh diff --git a/pkg/config/cred-helper-overwrite.sh b/pkg/config/cred-helper-overwrite.sh deleted file mode 100644 index 988ebf0c7..000000000 --- a/pkg/config/cred-helper-overwrite.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# Credential helper that forwards to host daemon via socat -LOGFILE="/tmp/cred-helper-debug.log" -echo "" >> $LOGFILE -echo "[$(date '+%%m/%%d %%l:%%M%%p')] Credential helper called with args: $@" >> $LOGFILE -echo "[$(date '+%%m/%%d %%l:%%M%%p')] Input received:" >> $LOGFILE -input=$(cat) -echo "$input" >> $LOGFILE - -# Forward to host daemon via socat -response=$(printf "%s\n%s\n" "$1" "$input" | socat - UNIX-CONNECT:/tmp/finch-creds.sock 2>>$LOGFILE) -exit_code=$? - -echo "[$(date '+%%m/%%d %%l:%%M%%p')] Response from host: $response" >> $LOGFILE - -if [ $exit_code -ne 0 ]; then - echo "[$(date '+%%m/%%d %%l:%%M%%p')] Error: socat failed with exit code $exit_code" >> $LOGFILE - echo '{"error": "credential helper connection failed"}' - exit 1 -fi - -# Handle empty response - only exit with code 1 for 'get' command when credentials not found -if [ -z "$response" ] && [ "$1" = "get" ]; then - echo "[$(date '+%%m/%%d %%l:%%M%%p')] Empty response for get - credentials not found" >> $LOGFILE - exit 1 -fi - -echo "$response" -echo "" >> $LOGFILE \ No newline at end of file diff --git a/pkg/config/defaults_darwin.go b/pkg/config/defaults_darwin.go index 85699a6b9..9a1ed9c17 100644 --- a/pkg/config/defaults_darwin.go +++ b/pkg/config/defaults_darwin.go @@ -59,7 +59,6 @@ func cpuDefault(cfg *Finch, deps LoadSystemDeps) { } } -// Different because the other defaults use single values; this uses a slice func credHelperDefault(cfg *Finch) { if cfg.CredsHelpers == nil || len(cfg.CredsHelpers) == 0 { cfg.CredsHelpers = []string{"osxkeychain"} diff --git a/pkg/config/nerdctl_config_applier.go b/pkg/config/nerdctl_config_applier.go index 02202269c..8f35bb3ba 100644 --- a/pkg/config/nerdctl_config_applier.go +++ b/pkg/config/nerdctl_config_applier.go @@ -6,7 +6,6 @@ package config import ( - _ "embed" "errors" "fmt" "path" @@ -26,8 +25,11 @@ const ( nerdctlRootfulCfgPath = "/etc/nerdctl/nerdctl.toml" ) -//go:embed cred-helper-overwrite.sh -var credHelperScript string +// Native credential helper wrapper script +const nativeCredHelperScript = `#!/bin/bash +input=$(cat) +printf "%s\n%s\n" "$1" "$input" | socat - UNIX-CONNECT:/tmp/native-creds.sock +` type nerdctlConfigApplier struct { dialer fssh.Dialer @@ -98,25 +100,26 @@ func updateEnvironment(fs afero.Fs, fc *Finch, finchDir, homeDir, limaVMHomeDir `[ -L /root/.aws ] || sudo ln -fs "$AWS_DIR" /root/.aws`, } + //nolint:gosec // G101: Potential hardcoded credentials false positive + const configureCredHelperTemplate = `([ -e "$FINCH_DIR"/cred-helpers/docker-credential-%s ] || \ + (echo "error: docker-credential-%s not found in $FINCH_DIR/cred-helpers directory.")) && \ + ([ -L /usr/local/bin/docker-credential-%s ] || sudo ln -s "$FINCH_DIR"/cred-helpers/docker-credential-%s /usr/local/bin)` + + //nolint:gosec // G101: Potential hardcoded credentials false positive + const nativeCredHelperTemplate = `[ -x /usr/local/bin/docker-credential-%s ] || ( + (sudo mkdir -p /usr/local/bin) && \ + (echo '%s' | sudo tee /usr/local/bin/docker-credential-%s > /dev/null) && \ + (sudo chmod +x /usr/local/bin/docker-credential-%s))` + for _, credHelper := range fc.CredsHelpers { + // Add the credhelper to config by default, removes need for user to add cmdArr = append(cmdArr, fmt.Sprintf(`echo '{"credsStore": "%s"}' > "$FINCH_DIR"/config.json`, credHelper)) - // Check if this is OS native credential store + // If using native credstore, use overwrite instead of symlink if credHelper == "osxkeychain" || credHelper == "wincred" { - // Native OS credstore - create wrapper script - cmdArr = append(cmdArr, fmt.Sprintf(`[ -x /usr/local/bin/docker-credential-%s ] || ( -echo "Creating native credential wrapper for %s" && \ -sudo mkdir -p /usr/local/bin && \ -echo '#!/bin/bash' | sudo tee /usr/local/bin/docker-credential-%s > /dev/null && \ -echo 'response=$(printf "%%s\n%%s\n" "$1" "$(cat)" | socat - UNIX-CONNECT:/tmp/native-creds.sock)' | sudo tee -a /usr/local/bin/docker-credential-%s > /dev/null && \ -echo 'echo "$response"' | sudo tee -a /usr/local/bin/docker-credential-%s > /dev/null && \ -sudo chmod +x /usr/local/bin/docker-credential-%s -)`, credHelper, credHelper, credHelper, credHelper, credHelper, credHelper)) + cmdArr = append(cmdArr, fmt.Sprintf(nativeCredHelperTemplate, credHelper, nativeCredHelperScript, credHelper, credHelper)) } else { - // Mainline behavior for third-party helpers (ecr-login, etc) - cmdArr = append(cmdArr, fmt.Sprintf(`([ -e "$FINCH_DIR"/cred-helpers/docker-credential-%s ] || \ - (echo "error: docker-credential-%s not found in $FINCH_DIR/cred-helpers directory.")) && \ - ([ -L /usr/local/bin/docker-credential-%s ] || sudo ln -s "$FINCH_DIR"/cred-helpers/docker-credential-%s /usr/local/bin)`, credHelper, credHelper, credHelper, credHelper)) + cmdArr = append(cmdArr, fmt.Sprintf(configureCredHelperTemplate, credHelper, credHelper, credHelper, credHelper)) } } @@ -266,6 +269,5 @@ func (nca *nerdctlConfigApplier) Apply(remoteAddr string) error { if err := updateEnvironment(sftpFs, nca.fc, nca.finchDir, nca.homeDir, limaHomeDir); err != nil { return fmt.Errorf("failed to update the user's .profile file: %w", err) } - return nil } From 946471f310d76555fc38bd888ffc5a34b49e91be Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Mon, 15 Dec 2025 16:08:18 -0800 Subject: [PATCH 11/21] Lots of filepath improvements + some launchd/services.msc stuff Signed-off-by: ayush-panta --- .github/workflows/test-cred-security.yaml | 101 +----------------- Makefile | 2 + Makefile.creds | 48 ++++++--- Makefile.darwin | 1 + Makefile.windows | 1 + .../com.runfinch.cred-bridge.plist | 36 ------- .../com.runfinch.cred-bridge.plist.template | 2 +- dev-setup.sh | 23 +--- finch.yaml.d/mac.yaml | 2 +- finch.yaml.d/windows.yaml | 2 +- msi-builder/install-service.bat | 22 ++++ 11 files changed, 64 insertions(+), 176 deletions(-) delete mode 100644 cmd/finch-credhelper/com.runfinch.cred-bridge.plist create mode 100644 msi-builder/install-service.bat diff --git a/.github/workflows/test-cred-security.yaml b/.github/workflows/test-cred-security.yaml index ae033471a..e3b440a66 100644 --- a/.github/workflows/test-cred-security.yaml +++ b/.github/workflows/test-cred-security.yaml @@ -14,110 +14,15 @@ permissions: env: GO_VERSION: '1.24.6' + # + jobs: macos-keychain: runs-on: ["self-hosted", "macos", "arm64", "14", "test"] timeout-minutes: 120 steps: - - name: Clean macOS runner workspace - run: | - rm -rf ${{ github.workspace }}/* - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - fetch-depth: 0 - persist-credentials: false - submodules: recursive - - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 - with: - go-version: ${{ env.GO_VERSION }} - cache: false - - - name: Clean up previous files - run: | - sudo rm -rf /opt/finch - sudo rm -rf ~/.finch - sudo rm -rf ./_output - if pgrep '^qemu-system'; then - sudo pkill '^qemu-system' - fi - if pgrep '^socket_vmnet'; then - sudo pkill '^socket_vmnet' - fi - - - name: Install Rosetta 2 - run: echo "A" | softwareupdate --install-rosetta || true - - - run: brew install lz4 automake autoconf libtool yq - shell: zsh {0} - - - name: Build project - run: | - export PATH="/opt/homebrew/opt/libtool/libexec/gnubin:$PATH" - make - shell: zsh {0} - - - name: Initialize Finch VM - run: | - ./_output/bin/finch vm init - shell: zsh {0} - - - name: Setup local registry with auth - run: | - # Create registry directories - mkdir -p /tmp/registry/{data,auth} - - # Create htpasswd file - docker run --rm --entrypoint htpasswd httpd:2 -Bbn testuser testpass > /tmp/registry/auth/htpasswd - - # Start local registry with auth - ./_output/bin/finch run -d \ - --name registry \ - -p 5000:5000 \ - -v /tmp/registry/data:/var/lib/registry \ - -v /tmp/registry/auth:/auth \ - -e "REGISTRY_AUTH=htpasswd" \ - -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ - -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \ - registry:2 - shell: zsh {0} - - - name: Test credential security - run: | - # Test 1: Pull public image (should work) - ./_output/bin/finch pull alpine - - # Test 2: Login to local registry - echo "testpass" | ./_output/bin/finch login localhost:5000 -u testuser --password-stdin - - # Test 3: Check credentials not stored in plaintext - if grep -r "testpass" ~/.finch/ 2>/dev/null; then - echo "ERROR: Password found in plaintext" - exit 1 - fi - - # Test 4: Push/pull from private registry - ./_output/bin/finch tag alpine localhost:5000/test-image - ./_output/bin/finch push localhost:5000/test-image - ./_output/bin/finch rmi localhost:5000/test-image - ./_output/bin/finch pull localhost:5000/test-image - - # Test 5: Logout and verify access denied - ./_output/bin/finch logout localhost:5000 - if ./_output/bin/finch push localhost:5000/test-image2 2>/dev/null; then - echo "ERROR: Push succeeded after logout" - exit 1 - fi - shell: zsh {0} - - - name: Cleanup - if: always() + - name: run: | - ./_output/bin/finch rm -f registry || true - ./_output/bin/finch vm stop || true - rm -rf /tmp/registry || true - shell: zsh {0} windows-cred-manager: runs-on: ["self-hosted", "windows", "amd64", "test"] diff --git a/Makefile b/Makefile index d63a6e3ce..d71cef09a 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,8 @@ MIN_MACOS_VERSION ?= 11.0 FINCH_DAEMON_LOCATION_ROOT ?= $(FINCH_OS_IMAGE_LOCATION_ROOT)/finch-daemon FINCH_DAEMON_LOCATION ?= $(FINCH_DAEMON_LOCATION_ROOT)/finch-daemon FINCH_DAEMON_CREDHELPER_LOCATION ?= $(FINCH_DAEMON_LOCATION_ROOT)/docker-credential-finch +FINCH_CREDHELPER_DIR ?= $(FINCH_OS_IMAGE_LOCATION_ROOT)/finch-credhelper +FINCH_CREDHELPER_SOCKET_LOCATION ?= $(FINCH_CREDHELPER_DIR)/native-creds.sock GOOS ?= $(shell $(GO) env GOOS) ifeq ($(GOOS),windows) diff --git a/Makefile.creds b/Makefile.creds index 6efe9ddc9..27f170874 100644 --- a/Makefile.creds +++ b/Makefile.creds @@ -23,13 +23,13 @@ else ifeq ($(BUILD_OS), Windows_NT) endif CRED_HELPER_URL := $(CRED_HELPER_BASE_URL)/$(CRED_HELPER_ARTIFACT) -CRED_HELPER_OUTPUT := $(OUTDIR)/finch-credhelper/$(CRED_HELPER_NAME) +CRED_HELPER_OUTPUT := $(HOME)/.finch/cred-helpers/$(CRED_HELPER_NAME) # Build finch credential bridge .PHONY: finch-cred-bridge finch-cred-bridge: - mkdir -p $(OUTDIR)/finch-credhelper - $(GO) build -ldflags $(LDFLAGS) -tags "$(GO_BUILD_TAGS)" -o $(OUTDIR)/finch-credhelper/finch-cred-bridge $(PACKAGE)/cmd/finch-credhelper + mkdir -p $(FINCH_CREDHELPER_DIR) + $(GO) build -ldflags $(LDFLAGS) -tags "$(GO_BUILD_TAGS)" -o $(FINCH_CREDHELPER_DIR)/finch-cred-bridge $(PACKAGE)/cmd/finch-credhelper # Download and verify credential helper .PHONY: docker-credential-helper @@ -57,10 +57,12 @@ PLIST_DEST := $(HOME)/Library/LaunchAgents/$(PLIST_NAME) .PHONY: install-launch-agent install-launch-agent: @echo "Installing LaunchAgent..." + mkdir -p $(dir $(PLIST_DEST)) sed -e "s|\$$HOME|$(HOME)|g" \ - -e "s|\$$FINCH_CRED_BRIDGE_PATH|$(CURDIR)/_output/finch-credhelper/finch-cred-bridge|g" \ + -e "s|\$$FINCH_CRED_BRIDGE_PATH|$(FINCH_CREDHELPER_DIR)/finch-cred-bridge|g" \ $(PLIST_TEMPLATE) > $(PLIST_DEST) - launchctl load $(PLIST_DEST) + -launchctl bootout gui/$$(id -u)/com.runfinch.cred-bridge 2>/dev/null || true + launchctl bootstrap gui/$$(id -u) $(PLIST_DEST) @echo "LaunchAgent installed and loaded" .PHONY: uninstall-launch-agent @@ -71,27 +73,39 @@ uninstall-launch-agent: @echo "LaunchAgent uninstalled" .PHONY: setup-cred-bridge -setup-cred-bridge: finch-cred-bridge - @echo "Setting up credential bridge..." - @if ! launchctl list | grep -q com.runfinch.cred-bridge; then \ - $(MAKE) install-launch-agent; \ - else \ - echo "LaunchAgent already loaded"; \ - fi +setup-cred-bridge: finch-cred-bridge install-launch-agent + @echo "Credential bridge setup complete" + +else ifeq ($(BUILD_OS), Windows_NT) +# Windows Service management +.PHONY: install-service +install-service: + @echo "Installing Windows Service..." + sc create "FinchCredBridge" binPath= "$(FINCH_CREDHELPER_DIR)\finch-cred-bridge.exe" start= demand + sc description "FinchCredBridge" "Finch Credential Bridge Service" + @echo "Windows Service installed" + +.PHONY: uninstall-service +uninstall-service: + @echo "Uninstalling Windows Service..." + -sc stop "FinchCredBridge" 2>nul + -sc delete "FinchCredBridge" 2>nul + @echo "Windows Service uninstalled" + +.PHONY: setup-cred-bridge +setup-cred-bridge: finch-cred-bridge install-service @echo "Credential bridge setup complete" else -.PHONY: install-launch-agent uninstall-launch-agent setup-cred-bridge -install-launch-agent uninstall-launch-agent setup-cred-bridge: - @echo "LaunchAgent management is macOS-specific" +.PHONY: install-launch-agent uninstall-launch-agent setup-cred-bridge install-service uninstall-service +install-launch-agent uninstall-launch-agent setup-cred-bridge install-service uninstall-service: + @echo "Service management is platform-specific" endif .PHONY: make-creds ifeq ($(BUILD_OS), Linux) make-creds: @echo "No credential helpers needed for Linux" -else ifeq ($(BUILD_OS), Windows_NT) -make-creds: finch-cred-bridge docker-credential-helper else make-creds: finch-cred-bridge docker-credential-helper setup-cred-bridge endif diff --git a/Makefile.darwin b/Makefile.darwin index 3f0b9b6cb..0ca4b2913 100644 --- a/Makefile.darwin +++ b/Makefile.darwin @@ -46,6 +46,7 @@ $(OS_OUTDIR)/finch.yaml: $(OS_OUTDIR) finch.yaml.d/common.yaml finch.yaml.d/mac. sed -i.bak -e "s||$(FINCH_DAEMON_LOCATION_ROOT)|g" finch.yaml.temp sed -i.bak -e "s||$(FINCH_DAEMON_LOCATION)|g" finch.yaml.temp sed -i.bak -e "s||$(FINCH_DAEMON_CREDHELPER_LOCATION)|g" finch.yaml.temp + sed -i.bak -e "s||$(FINCH_CREDHELPER_SOCKET_LOCATION)|g" finch.yaml.temp sed -i.bak -e "s||$(RUNC_OVERRIDE_AARCH64_LOCATION)|g" finch.yaml.temp sed -i.bak -e "s//$(RUNC_OVERRIDE_AARCH64_DIGEST)/g" finch.yaml.temp sed -i.bak -e "s||$(RUNC_OVERRIDE_X86_64_LOCATION)|g" finch.yaml.temp diff --git a/Makefile.windows b/Makefile.windows index 063485c8c..7ea4d2657 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -36,6 +36,7 @@ $(OS_OUTDIR)/finch.yaml: $(OS_OUTDIR) finch.yaml.d/common.yaml finch.yaml.d/wind sed -i.bak -e "s//$(RUNC_OVERRIDE_AARCH64_DIGEST)/g" finch.yaml.temp sed -i.bak -e "s||$(RUNC_OVERRIDE_X86_64_LOCATION)|g" finch.yaml.temp sed -i.bak -e "s//$(RUNC_OVERRIDE_X86_64_DIGEST)/g" finch.yaml.temp + sed -i.bak -e "s||$(FINCH_CREDHELPER_SOCKET_LOCATION)|g" finch.yaml.temp # Replacement was successful, so cleanup .bak @rm finch.yaml.temp.bak diff --git a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist deleted file mode 100644 index 8037fd6b2..000000000 --- a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - Label - com.runfinch.cred-bridge - - ProgramArguments - - /bin/sh - -c - exec /Users/ayushkp/Documents/finch-creds/finch/_output/finch-credhelper/finch-cred-bridge - - - Sockets - - Listeners - - - SockPathName -- /Users/ayushkp/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock - SockPathMode - 384 - - - - - inetdCompatibility - - Wait - - - - - \ No newline at end of file diff --git a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template index 54ab0bf13..021fa788e 100644 --- a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template +++ b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template @@ -19,7 +19,7 @@ SockPathName - $HOME/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock + $(FINCH_CREDHELPER_SOCKET_LOCATION) SockPathMode 384 diff --git a/dev-setup.sh b/dev-setup.sh index c241d2786..69e8dd939 100755 --- a/dev-setup.sh +++ b/dev-setup.sh @@ -1,29 +1,8 @@ #!/bin/bash - -# Development setup script for finch credential helper testing set -e -echo "๐Ÿ“ฆ Initializing git submodules..." git submodule update --init --recursive - -echo "๐Ÿงน Cleaning up previous builds..." -rm -rf _output - -echo "๐Ÿ”ง Setting Go environment..." unset GOSUMDB - -echo "๐Ÿงฝ Running make clean..." make clean - -echo "๐Ÿ”จ Building finch..." make - -echo "๐Ÿงน Cleaning credential helper log..." -rm -f _output/finch-credhelper/cred-bridge.log - -echo "๐Ÿ–ฅ๏ธ Initializing VM..." -./_output/bin/finch vm init - -echo "โœ… Setup complete!" -echo "๐Ÿ“ Credential helper will be managed by launchd" -echo "๐Ÿ” To view logs: tail -f _output/finch-credhelper/cred-bridge.log" \ No newline at end of file +./_output/bin/finch vm init \ No newline at end of file diff --git a/finch.yaml.d/mac.yaml b/finch.yaml.d/mac.yaml index 57a4236b7..c3a235f83 100644 --- a/finch.yaml.d/mac.yaml +++ b/finch.yaml.d/mac.yaml @@ -61,5 +61,5 @@ portForwards: - guestSocket: "/run/finch.sock" hostSocket: "{{.Dir}}/sock/finch.sock" - guestSocket: "/tmp/native-creds.sock" - hostSocket: "{{.Home}}/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock" + hostSocket: "" reverse: true \ No newline at end of file diff --git a/finch.yaml.d/windows.yaml b/finch.yaml.d/windows.yaml index 33d1b9d16..16246311f 100644 --- a/finch.yaml.d/windows.yaml +++ b/finch.yaml.d/windows.yaml @@ -3,5 +3,5 @@ mountType: wsl2 portForwards: - guestSocket: "/tmp/native-creds.sock" - hostSocket: "{{.Home}}/Documents/finch-creds/finch/_output/finch-credhelper/native-creds.sock" + hostSocket: "" reverse: true \ No newline at end of file diff --git a/msi-builder/install-service.bat b/msi-builder/install-service.bat new file mode 100644 index 000000000..186ac6551 --- /dev/null +++ b/msi-builder/install-service.bat @@ -0,0 +1,22 @@ +@echo off +REM Install Finch Credential Bridge Windows Service + +set SERVICE_NAME=FinchCredBridge +set SERVICE_DISPLAY_NAME=Finch Credential Bridge +set SERVICE_DESCRIPTION=Provides credential bridge functionality for Finch containers +set SERVICE_BINARY=%~dp0finch-credhelper\finch-cred-bridge.exe + +echo Installing %SERVICE_DISPLAY_NAME% service... + +REM Create the service +sc create "%SERVICE_NAME%" binPath= "\"%SERVICE_BINARY%\"" start= demand DisplayName= "%SERVICE_DISPLAY_NAME%" +if %ERRORLEVEL% neq 0 ( + echo Failed to create service + exit /b 1 +) + +REM Set service description +sc description "%SERVICE_NAME%" "%SERVICE_DESCRIPTION%" + +echo %SERVICE_DISPLAY_NAME% service installed successfully +echo Use services.msc to manage the service \ No newline at end of file From 325d4863a226c53703c7e5a953346dfaff78c9ef Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Mon, 15 Dec 2025 23:43:08 -0800 Subject: [PATCH 12/21] permissions changes Signed-off-by: ayush-panta --- Makefile | 2 +- Makefile.creds | 3 ++- cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template | 4 +--- pkg/config/nerdctl_config_applier.go | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index d71cef09a..7c5be1e3e 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ MIN_MACOS_VERSION ?= 11.0 FINCH_DAEMON_LOCATION_ROOT ?= $(FINCH_OS_IMAGE_LOCATION_ROOT)/finch-daemon FINCH_DAEMON_LOCATION ?= $(FINCH_DAEMON_LOCATION_ROOT)/finch-daemon FINCH_DAEMON_CREDHELPER_LOCATION ?= $(FINCH_DAEMON_LOCATION_ROOT)/docker-credential-finch -FINCH_CREDHELPER_DIR ?= $(FINCH_OS_IMAGE_LOCATION_ROOT)/finch-credhelper +FINCH_CREDHELPER_DIR ?= $(OUTDIR)/finch-credhelper FINCH_CREDHELPER_SOCKET_LOCATION ?= $(FINCH_CREDHELPER_DIR)/native-creds.sock GOOS ?= $(shell $(GO) env GOOS) diff --git a/Makefile.creds b/Makefile.creds index 27f170874..e99be1b44 100644 --- a/Makefile.creds +++ b/Makefile.creds @@ -30,6 +30,7 @@ CRED_HELPER_OUTPUT := $(HOME)/.finch/cred-helpers/$(CRED_HELPER_NAME) finch-cred-bridge: mkdir -p $(FINCH_CREDHELPER_DIR) $(GO) build -ldflags $(LDFLAGS) -tags "$(GO_BUILD_TAGS)" -o $(FINCH_CREDHELPER_DIR)/finch-cred-bridge $(PACKAGE)/cmd/finch-credhelper + chmod 700 $(FINCH_CREDHELPER_DIR)/finch-cred-bridge # Download and verify credential helper .PHONY: docker-credential-helper @@ -45,7 +46,7 @@ else else \ echo "Checksum verification failed" && exit 1; \ fi - chmod +x $(CRED_HELPER_OUTPUT) + chmod 700 $(CRED_HELPER_OUTPUT) endif # macOS LaunchAgent management diff --git a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template index 021fa788e..62a63b7a8 100644 --- a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template +++ b/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template @@ -8,9 +8,7 @@ ProgramArguments - /bin/sh - -c - exec $FINCH_CRED_BRIDGE_PATH + $(FINCH_CREDHELPER_DIR)/finch-cred-bridge Sockets diff --git a/pkg/config/nerdctl_config_applier.go b/pkg/config/nerdctl_config_applier.go index 8f35bb3ba..855efc1c8 100644 --- a/pkg/config/nerdctl_config_applier.go +++ b/pkg/config/nerdctl_config_applier.go @@ -109,7 +109,7 @@ func updateEnvironment(fs afero.Fs, fc *Finch, finchDir, homeDir, limaVMHomeDir const nativeCredHelperTemplate = `[ -x /usr/local/bin/docker-credential-%s ] || ( (sudo mkdir -p /usr/local/bin) && \ (echo '%s' | sudo tee /usr/local/bin/docker-credential-%s > /dev/null) && \ - (sudo chmod +x /usr/local/bin/docker-credential-%s))` + (sudo chmod 700 /usr/local/bin/docker-credential-%s))` for _, credHelper := range fc.CredsHelpers { // Add the credhelper to config by default, removes need for user to add From add1719d5213a316e12f70165552aabeebcb3daa Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Tue, 16 Dec 2025 11:43:44 -0800 Subject: [PATCH 13/21] npipe + relay commit as backup Signed-off-by: ayush-panta --- Makefile | 1 + Makefile.windows | 2 +- cmd/finch-credhelper/windows-creds.go | 24 ++++++++++++------------ finch.yaml.d/windows.yaml | 2 +- pkg/config/nerdctl_config_applier.go | 7 ++++++- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 7c5be1e3e..0474141df 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ FINCH_DAEMON_LOCATION ?= $(FINCH_DAEMON_LOCATION_ROOT)/finch-daemon FINCH_DAEMON_CREDHELPER_LOCATION ?= $(FINCH_DAEMON_LOCATION_ROOT)/docker-credential-finch FINCH_CREDHELPER_DIR ?= $(OUTDIR)/finch-credhelper FINCH_CREDHELPER_SOCKET_LOCATION ?= $(FINCH_CREDHELPER_DIR)/native-creds.sock +FINCH_CREDHELPER_PIPE_LOCATION ?= \\.\pipe\finch-native-creds GOOS ?= $(shell $(GO) env GOOS) ifeq ($(GOOS),windows) diff --git a/Makefile.windows b/Makefile.windows index 7ea4d2657..b4e32d67b 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -36,7 +36,7 @@ $(OS_OUTDIR)/finch.yaml: $(OS_OUTDIR) finch.yaml.d/common.yaml finch.yaml.d/wind sed -i.bak -e "s//$(RUNC_OVERRIDE_AARCH64_DIGEST)/g" finch.yaml.temp sed -i.bak -e "s||$(RUNC_OVERRIDE_X86_64_LOCATION)|g" finch.yaml.temp sed -i.bak -e "s//$(RUNC_OVERRIDE_X86_64_DIGEST)/g" finch.yaml.temp - sed -i.bak -e "s||$(FINCH_CREDHELPER_SOCKET_LOCATION)|g" finch.yaml.temp + sed -i.bak -e "s||$(FINCH_CREDHELPER_PIPE_LOCATION)|g" finch.yaml.temp # Replacement was successful, so cleanup .bak @rm finch.yaml.temp.bak diff --git a/cmd/finch-credhelper/windows-creds.go b/cmd/finch-credhelper/windows-creds.go index c689d18e2..c9233f9db 100644 --- a/cmd/finch-credhelper/windows-creds.go +++ b/cmd/finch-credhelper/windows-creds.go @@ -8,23 +8,23 @@ import ( "net" "os" "path/filepath" + + "github.com/Microsoft/go-winio" ) // Windows socket server (for WSL2 socket forwarding) func startWindowsCredentialServer() error { - // Create socket path in user's Documents/finch-creds directory - homeDir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("failed to get home directory: %w", err) + pipePath := filepath.Join(`\\.\pipe\native-creds`) + os.Remove(pipePath) + config := &winio.PipeConfig{ + SecurityDescriptor: "D:P(A;;GA;;;CO)(A;;GA;;;SY)", // owner + "root" (need to verify) + InputBufferSize: 4096, + OutputBufferSize: 4096, } - socketPath := filepath.Join(homeDir, ".finch", "native-creds.sock") - os.Remove(socketPath) - - // Listen on Unix socket - listener, err := net.Listen("unix", socketPath) + listener, err := winio.ListenPipe(pipePath, config) if err != nil { - return fmt.Errorf("failed to create socket: %w", err) + return fmt.Errorf("failed to create pipe: %w", err) } defer listener.Close() @@ -39,7 +39,7 @@ func startWindowsCredentialServer() error { // Handle each connection go func(c net.Conn) { defer c.Close() - if err := processCredentialRequest(c); err != nil { + if err := processCredentialRequest(c); err != nil { log.Printf("Error processing credential request") } }(conn) @@ -51,4 +51,4 @@ func main() { log.Printf("Windows credential server failed") os.Exit(1) } -} \ No newline at end of file +} diff --git a/finch.yaml.d/windows.yaml b/finch.yaml.d/windows.yaml index 16246311f..762cb3c03 100644 --- a/finch.yaml.d/windows.yaml +++ b/finch.yaml.d/windows.yaml @@ -3,5 +3,5 @@ mountType: wsl2 portForwards: - guestSocket: "/tmp/native-creds.sock" - hostSocket: "" + hostSocket: "" reverse: true \ No newline at end of file diff --git a/pkg/config/nerdctl_config_applier.go b/pkg/config/nerdctl_config_applier.go index 855efc1c8..97be404ee 100644 --- a/pkg/config/nerdctl_config_applier.go +++ b/pkg/config/nerdctl_config_applier.go @@ -26,11 +26,16 @@ const ( ) // Native credential helper wrapper script -const nativeCredHelperScript = `#!/bin/bash +const keychainCredHelperScript = `#!/bin/bash input=$(cat) printf "%s\n%s\n" "$1" "$input" | socat - UNIX-CONNECT:/tmp/native-creds.sock ` +const nativeCredHelperScriptWindows = `#!/bin/bash +input=$(cat) +response=$(printf "%s\n%s\n" "$1" "$input" | npiperelay.exe -ei -s //./pipe/finch-native-creds 2>/dev/null) +...` + type nerdctlConfigApplier struct { dialer fssh.Dialer fs afero.Fs From 4d9009859adce6990b5b68b8ddaba0fd126e5744 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Tue, 16 Dec 2025 14:29:32 -0800 Subject: [PATCH 14/21] revert to unix on windows due to interop Signed-off-by: ayush-panta --- Makefile | 1 - Makefile.windows | 1 - cmd/finch-credhelper/mac-creds.go | 2 +- cmd/finch-credhelper/windows-creds.go | 24 +++++++++++-------- finch.yaml.d/windows.yaml | 7 +----- pkg/config/nerdctl_config_applier.go | 34 ++++++++++++++++++++------- 6 files changed, 41 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index 0474141df..7c5be1e3e 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,6 @@ FINCH_DAEMON_LOCATION ?= $(FINCH_DAEMON_LOCATION_ROOT)/finch-daemon FINCH_DAEMON_CREDHELPER_LOCATION ?= $(FINCH_DAEMON_LOCATION_ROOT)/docker-credential-finch FINCH_CREDHELPER_DIR ?= $(OUTDIR)/finch-credhelper FINCH_CREDHELPER_SOCKET_LOCATION ?= $(FINCH_CREDHELPER_DIR)/native-creds.sock -FINCH_CREDHELPER_PIPE_LOCATION ?= \\.\pipe\finch-native-creds GOOS ?= $(shell $(GO) env GOOS) ifeq ($(GOOS),windows) diff --git a/Makefile.windows b/Makefile.windows index b4e32d67b..063485c8c 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -36,7 +36,6 @@ $(OS_OUTDIR)/finch.yaml: $(OS_OUTDIR) finch.yaml.d/common.yaml finch.yaml.d/wind sed -i.bak -e "s//$(RUNC_OVERRIDE_AARCH64_DIGEST)/g" finch.yaml.temp sed -i.bak -e "s||$(RUNC_OVERRIDE_X86_64_LOCATION)|g" finch.yaml.temp sed -i.bak -e "s//$(RUNC_OVERRIDE_X86_64_DIGEST)/g" finch.yaml.temp - sed -i.bak -e "s||$(FINCH_CREDHELPER_PIPE_LOCATION)|g" finch.yaml.temp # Replacement was successful, so cleanup .bak @rm finch.yaml.temp.bak diff --git a/cmd/finch-credhelper/mac-creds.go b/cmd/finch-credhelper/mac-creds.go index 98f649fe9..e7a7f00ed 100644 --- a/cmd/finch-credhelper/mac-creds.go +++ b/cmd/finch-credhelper/mac-creds.go @@ -26,4 +26,4 @@ func main() { fmt.Fprintf(os.Stderr, "credential helper failed\n") os.Exit(1) } -} \ No newline at end of file +} diff --git a/cmd/finch-credhelper/windows-creds.go b/cmd/finch-credhelper/windows-creds.go index c9233f9db..bc1e36c3b 100644 --- a/cmd/finch-credhelper/windows-creds.go +++ b/cmd/finch-credhelper/windows-creds.go @@ -8,24 +8,28 @@ import ( "net" "os" "path/filepath" - - "github.com/Microsoft/go-winio" ) // Windows socket server (for WSL2 socket forwarding) func startWindowsCredentialServer() error { - pipePath := filepath.Join(`\\.\pipe\native-creds`) - os.Remove(pipePath) - config := &winio.PipeConfig{ - SecurityDescriptor: "D:P(A;;GA;;;CO)(A;;GA;;;SY)", // owner + "root" (need to verify) - InputBufferSize: 4096, - OutputBufferSize: 4096, + userProfile := os.Getenv("USERPROFILE") + if userProfile == "" { + return fmt.Errorf("USERPROFILE not set") } - listener, err := winio.ListenPipe(pipePath, config) + socketPath := filepath.Join(userProfile, ".finch", "native-creds.sock") + os.Remove(socketPath) + + listener, err := net.Listen("unix", socketPath) if err != nil { - return fmt.Errorf("failed to create pipe: %w", err) + return fmt.Errorf("failed to create socket: %w", err) } + + // set socket file permissions to owner only + if err := os.Chmod(socketPath, 0600); err != nil { + return fmt.Errorf("failed to set socket permissions: %w", err) + } + defer listener.Close() // Accept connections diff --git a/finch.yaml.d/windows.yaml b/finch.yaml.d/windows.yaml index 762cb3c03..37abd8ce4 100644 --- a/finch.yaml.d/windows.yaml +++ b/finch.yaml.d/windows.yaml @@ -1,7 +1,2 @@ vmType: wsl2 -mountType: wsl2 - -portForwards: -- guestSocket: "/tmp/native-creds.sock" - hostSocket: "" - reverse: true \ No newline at end of file +mountType: wsl2 \ No newline at end of file diff --git a/pkg/config/nerdctl_config_applier.go b/pkg/config/nerdctl_config_applier.go index 97be404ee..77c072274 100644 --- a/pkg/config/nerdctl_config_applier.go +++ b/pkg/config/nerdctl_config_applier.go @@ -25,16 +25,31 @@ const ( nerdctlRootfulCfgPath = "/etc/nerdctl/nerdctl.toml" ) -// Native credential helper wrapper script -const keychainCredHelperScript = `#!/bin/bash +const osxkeychainCredHelperScript = `#!/bin/bash input=$(cat) -printf "%s\n%s\n" "$1" "$input" | socat - UNIX-CONNECT:/tmp/native-creds.sock +printf "%s\n%s\n" "$1" "$input" | socat - UNIX-CONNECT:/tmp/native-creds.sock 2>/dev/null +exit_code=$? +if [ $exit_code -ne 0 ]; then + echo '{"error": "credential helper connection failed"}' + exit 1 +fi ` -const nativeCredHelperScriptWindows = `#!/bin/bash +const wincredCredHelperScript = `#!/bin/bash input=$(cat) -response=$(printf "%s\n%s\n" "$1" "$input" | npiperelay.exe -ei -s //./pipe/finch-native-creds 2>/dev/null) -...` +SOCKET_PATH=$(wslpath "$(wslvar USERPROFILE)")/.finch/native-creds.sock +printf "%s\n%s\n" "$1" "$input" | socat - UNIX-CONNECT:"$SOCKET_PATH" 2>/dev/null +exit_code=$? +if [ $exit_code -ne 0 ]; then + echo '{"error": "credential helper connection failed"}' + exit 1 +fi +` + +var helperTemplateScripts = map[string]string{ + "osxkeychain": osxkeychainCredHelperScript, + "wincred": wincredCredHelperScript, +} type nerdctlConfigApplier struct { dialer fssh.Dialer @@ -106,7 +121,7 @@ func updateEnvironment(fs afero.Fs, fc *Finch, finchDir, homeDir, limaVMHomeDir } //nolint:gosec // G101: Potential hardcoded credentials false positive - const configureCredHelperTemplate = `([ -e "$FINCH_DIR"/cred-helpers/docker-credential-%s ] || \ + const generalCredHelperTemplate = `([ -e "$FINCH_DIR"/cred-helpers/docker-credential-%s ] || \ (echo "error: docker-credential-%s not found in $FINCH_DIR/cred-helpers directory.")) && \ ([ -L /usr/local/bin/docker-credential-%s ] || sudo ln -s "$FINCH_DIR"/cred-helpers/docker-credential-%s /usr/local/bin)` @@ -122,9 +137,10 @@ func updateEnvironment(fs afero.Fs, fc *Finch, finchDir, homeDir, limaVMHomeDir // If using native credstore, use overwrite instead of symlink if credHelper == "osxkeychain" || credHelper == "wincred" { - cmdArr = append(cmdArr, fmt.Sprintf(nativeCredHelperTemplate, credHelper, nativeCredHelperScript, credHelper, credHelper)) + helperScript := helperTemplateScripts[credHelper] + cmdArr = append(cmdArr, fmt.Sprintf(nativeCredHelperTemplate, credHelper, helperScript, credHelper, credHelper)) } else { - cmdArr = append(cmdArr, fmt.Sprintf(configureCredHelperTemplate, credHelper, credHelper, credHelper, credHelper)) + cmdArr = append(cmdArr, fmt.Sprintf(generalCredHelperTemplate, credHelper, credHelper, credHelper, credHelper)) } } From daabb6f21c2d51098ce05f1d69db551e8b754c24 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Tue, 16 Dec 2025 17:35:16 -0800 Subject: [PATCH 15/21] Add plsit handling on install for mac Signed-off-by: ayush-panta --- Makefile.creds | 12 +++++++++++- .../darwin/Resources/uninstall.sh | 18 ++++++++++++++++++ installer-builder/darwin/scripts/postinstall | 3 +++ .../com.runfinch.cred-bridge.plist.template | 0 installer-builder/tools/build-macos-pkg.sh | 7 +++++++ .../tools/native-creds-agent-start.sh | 18 ++++++++++++++++++ .../tools/native-creds-agent-stop.sh | 12 ++++++++++++ 7 files changed, 69 insertions(+), 1 deletion(-) rename {cmd/finch-credhelper => installer-builder/templates}/com.runfinch.cred-bridge.plist.template (100%) create mode 100644 installer-builder/tools/native-creds-agent-start.sh create mode 100644 installer-builder/tools/native-creds-agent-stop.sh diff --git a/Makefile.creds b/Makefile.creds index e99be1b44..c1ccd2ae2 100644 --- a/Makefile.creds +++ b/Makefile.creds @@ -52,7 +52,7 @@ endif # macOS LaunchAgent management ifeq ($(BUILD_OS), Darwin) PLIST_NAME := com.runfinch.cred-bridge.plist -PLIST_TEMPLATE := cmd/finch-credhelper/$(PLIST_NAME).template +PLIST_TEMPLATE := installer-builder/templates/$(PLIST_NAME).template PLIST_DEST := $(HOME)/Library/LaunchAgents/$(PLIST_NAME) .PHONY: install-launch-agent @@ -74,8 +74,13 @@ uninstall-launch-agent: @echo "LaunchAgent uninstalled" .PHONY: setup-cred-bridge +ifeq ($(INSTALLED), true) +setup-cred-bridge: finch-cred-bridge + @echo "Built credential bridge binary - service installation deferred to installer" +else setup-cred-bridge: finch-cred-bridge install-launch-agent @echo "Credential bridge setup complete" +endif else ifeq ($(BUILD_OS), Windows_NT) # Windows Service management @@ -94,8 +99,13 @@ uninstall-service: @echo "Windows Service uninstalled" .PHONY: setup-cred-bridge +ifeq ($(INSTALLED), true) +setup-cred-bridge: finch-cred-bridge + @echo "Built credential bridge binary - service installation deferred to installer" +else setup-cred-bridge: finch-cred-bridge install-service @echo "Credential bridge setup complete" +endif else .PHONY: install-launch-agent uninstall-launch-agent setup-cred-bridge install-service uninstall-service diff --git a/installer-builder/darwin/Resources/uninstall.sh b/installer-builder/darwin/Resources/uninstall.sh index 2b358b9e5..8f204dd34 100755 --- a/installer-builder/darwin/Resources/uninstall.sh +++ b/installer-builder/darwin/Resources/uninstall.sh @@ -37,6 +37,24 @@ else echo "[2/4] [ERROR] Could not delete application informations" >&2 fi +#remove credential bridge LaunchAgent +# handle the user who ran sudo ./uninstall.sh, or all users if run directly as root +REAL_USER="${SUDO_USER:-}" +if [ -n "$REAL_USER" ] && [ "$REAL_USER" != "root" ]; then + # Single user cleanup (most common case) + sudo -u "$REAL_USER" launchctl bootout gui/$(id -u "$REAL_USER")/com.runfinch.cred-bridge 2>/dev/null || true + rm -f "/Users/$REAL_USER/Library/LaunchAgents/com.runfinch.cred-bridge.plist" 2>/dev/null || true +else + # Cleanup for all users (fallback) + for user_home in /Users/*; do + if [ -d "$user_home/Library/LaunchAgents" ]; then + username=$(basename "$user_home") + sudo -u "$username" launchctl bootout gui/$(id -u "$username")/com.runfinch.cred-bridge 2>/dev/null || true + rm -f "$user_home/Library/LaunchAgents/com.runfinch.cred-bridge.plist" 2>/dev/null || true + fi + done +fi + #remove application source distribution [ -e "/Applications/Finch" ] && rm -rf /Applications/Finch && rm -rf /opt/finch && rm -rf /private/var/run/finch-lima && rm -rf /private/etc/sudoers.d/finch-lima if [ $? -eq 0 ] diff --git a/installer-builder/darwin/scripts/postinstall b/installer-builder/darwin/scripts/postinstall index 421bf914a..754da20e5 100755 --- a/installer-builder/darwin/scripts/postinstall +++ b/installer-builder/darwin/scripts/postinstall @@ -33,4 +33,7 @@ pkgutil --pkgs | grep '^org\.Finch\.' | grep -v '^org\.Finch\.__VERSION__' | whi sudo pkgutil --forget "$pkg" done +# Create and add native credhelper .plist to LaunchAgents +/Applications/Finch/tools/native-creds-agent-start.sh + echo "Post installation process finished." diff --git a/cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template b/installer-builder/templates/com.runfinch.cred-bridge.plist.template similarity index 100% rename from cmd/finch-credhelper/com.runfinch.cred-bridge.plist.template rename to installer-builder/templates/com.runfinch.cred-bridge.plist.template diff --git a/installer-builder/tools/build-macos-pkg.sh b/installer-builder/tools/build-macos-pkg.sh index ef267ef24..cb5391835 100755 --- a/installer-builder/tools/build-macos-pkg.sh +++ b/installer-builder/tools/build-macos-pkg.sh @@ -40,6 +40,13 @@ buildPkgInstaller() { cp ./installer-builder/darwin/Resources/uninstall.sh $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch sed -i '' -e 's/__VERSION__/'"${VERSION}"'/g' $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/uninstall.sh + #copy credential helper tools and templates, need to be accessible by postinstall and for re-init on error + mkdir -p $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/tools + cp ./installer-builder/templates/com.runfinch.cred-bridge.plist.template $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/tools/ + cp ./installer-builder/tools/native-creds-agent-start.sh $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/tools/ + cp ./installer-builder/tools/native-creds-agent-stop.sh $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/tools/ + chmod +x $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/tools/native-creds-agent-*.sh + #construct pkg directory mkdir -p $INSTALLER_FULL_PATH/package mkdir -p $INSTALLER_FULL_PATH/signed diff --git a/installer-builder/tools/native-creds-agent-start.sh b/installer-builder/tools/native-creds-agent-start.sh new file mode 100644 index 000000000..e2294bb0e --- /dev/null +++ b/installer-builder/tools/native-creds-agent-start.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +# Setting relevant paths +FINCH_INSTALL_DIR="/Applications/Finch" +TEMPLATE_PATH="$FINCH_INSTALL_DIR/tools/com.runfinch.cred-bridge.plist.template" +PLIST_PATH="$HOME/Library/LaunchAgents/com.runfinch.cred-bridge.plist" +mkdir -p "$HOME/Library/LaunchAgents" + +# Replace placeholders in template +sed -e "s|\$(FINCH_CREDHELPER_DIR)/finch-cred-bridge|$FINCH_INSTALL_DIR/finch-cred-bridge|g" \ + -e "s|\$(FINCH_CREDHELPER_SOCKET_LOCATION)|$HOME/.finch/native-creds.sock|g" \ + "$TEMPLATE_PATH" > "$PLIST_PATH" + +# Load the .plist into LaunchAgent +launchctl bootstrap gui/$(id -u) "$PLIST_PATH" + +echo "Finch credential bridge LaunchAgent installed and loaded" \ No newline at end of file diff --git a/installer-builder/tools/native-creds-agent-stop.sh b/installer-builder/tools/native-creds-agent-stop.sh new file mode 100644 index 000000000..97caf413c --- /dev/null +++ b/installer-builder/tools/native-creds-agent-stop.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +PLIST_PATH="$HOME/Library/LaunchAgents/com.runfinch.cred-bridge.plist" + +# Unload the LaunchAgent +launchctl bootout gui/$(id -u)/com.runfinch.cred-bridge 2>/dev/null || true + +# Remove the plist file +rm -f "$PLIST_PATH" + +echo "Finch credential bridge LaunchAgent uninstalled" \ No newline at end of file From 833e20dce7bc13d6f6797b7d1be0f4d583fda97f Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Tue, 16 Dec 2025 18:27:07 -0800 Subject: [PATCH 16/21] launchd and sc stuffs, apparently good to go Signed-off-by: ayush-panta --- installer-builder/darwin/scripts/postinstall | 10 ++++- installer-builder/tools/build-macos-pkg.sh | 9 ++-- .../tools/native-creds-agent-start.sh | 2 +- msi-builder/BuildFinchMSI.ps1 | 5 +++ msi-builder/FinchMSITemplate.wxs | 8 ++++ msi-builder/native-creds-service-start.ps1 | 45 +++++++++++++++++++ msi-builder/native-creds-service-stop.ps1 | 27 +++++++++++ msi-builder/postinstall.bat | 6 +++ msi-builder/uninstall.bat | 6 +++ 9 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 msi-builder/native-creds-service-start.ps1 create mode 100644 msi-builder/native-creds-service-stop.ps1 diff --git a/installer-builder/darwin/scripts/postinstall b/installer-builder/darwin/scripts/postinstall index 754da20e5..8041d8d71 100755 --- a/installer-builder/darwin/scripts/postinstall +++ b/installer-builder/darwin/scripts/postinstall @@ -33,7 +33,15 @@ pkgutil --pkgs | grep '^org\.Finch\.' | grep -v '^org\.Finch\.__VERSION__' | whi sudo pkgutil --forget "$pkg" done +# Secure credential helper directory after blanket 777 +chmod 750 /Applications/Finch/finch-credhelper +chmod 750 /Applications/Finch/finch-credhelper/finch-cred-bridge +chmod 750 /Applications/Finch/finch-credhelper/native-creds-agent-start.sh +chmod 750 /Applications/Finch/finch-credhelper/native-creds-agent-stop.sh +chmod 640 /Applications/Finch/finch-credhelper/com.runfinch.cred-bridge.plist.template +# Socket will be created by LaunchAgent with correct 600 permissions + # Create and add native credhelper .plist to LaunchAgents -/Applications/Finch/tools/native-creds-agent-start.sh +/Applications/Finch/finch-credhelper/native-creds-agent-start.sh echo "Post installation process finished." diff --git a/installer-builder/tools/build-macos-pkg.sh b/installer-builder/tools/build-macos-pkg.sh index cb5391835..46ca6fb48 100755 --- a/installer-builder/tools/build-macos-pkg.sh +++ b/installer-builder/tools/build-macos-pkg.sh @@ -41,11 +41,10 @@ buildPkgInstaller() { sed -i '' -e 's/__VERSION__/'"${VERSION}"'/g' $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/uninstall.sh #copy credential helper tools and templates, need to be accessible by postinstall and for re-init on error - mkdir -p $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/tools - cp ./installer-builder/templates/com.runfinch.cred-bridge.plist.template $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/tools/ - cp ./installer-builder/tools/native-creds-agent-start.sh $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/tools/ - cp ./installer-builder/tools/native-creds-agent-stop.sh $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/tools/ - chmod +x $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/tools/native-creds-agent-*.sh + mkdir -p $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/finch-credhelper + cp ./installer-builder/templates/com.runfinch.cred-bridge.plist.template $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/finch-credhelper/ + cp ./installer-builder/tools/native-creds-agent-start.sh $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/finch-credhelper/ + cp ./installer-builder/tools/native-creds-agent-stop.sh $INSTALLER_FULL_PATH/darwinpkg/Applications/Finch/finch-credhelper/ #construct pkg directory mkdir -p $INSTALLER_FULL_PATH/package diff --git a/installer-builder/tools/native-creds-agent-start.sh b/installer-builder/tools/native-creds-agent-start.sh index e2294bb0e..70168084a 100644 --- a/installer-builder/tools/native-creds-agent-start.sh +++ b/installer-builder/tools/native-creds-agent-start.sh @@ -3,7 +3,7 @@ set -e # Setting relevant paths FINCH_INSTALL_DIR="/Applications/Finch" -TEMPLATE_PATH="$FINCH_INSTALL_DIR/tools/com.runfinch.cred-bridge.plist.template" +TEMPLATE_PATH="$FINCH_INSTALL_DIR/finch-credhelper/com.runfinch.cred-bridge.plist.template" PLIST_PATH="$HOME/Library/LaunchAgents/com.runfinch.cred-bridge.plist" mkdir -p "$HOME/Library/LaunchAgents" diff --git a/msi-builder/BuildFinchMSI.ps1 b/msi-builder/BuildFinchMSI.ps1 index fce453af0..b708b40c1 100644 --- a/msi-builder/BuildFinchMSI.ps1 +++ b/msi-builder/BuildFinchMSI.ps1 @@ -63,6 +63,11 @@ Copy-Item -Path (Join-Path -Path $scriptDirectory -ChildPath "uninstall.bat") -D Copy-Item -Path (Join-Path -Path $scriptDirectory -ChildPath "removevm.bat") -Destination (Join-Path -Path $scriptDirectory -ChildPath "build\Finch") Copy-Item -Path (Join-Path -Path $scriptDirectory -ChildPath "finch.ico") -Destination (Join-Path -Path $scriptDirectory -ChildPath "build\Finch") Copy-Item -Path (Join-Path -Path $scriptDirectory -ChildPath "LICENSE.rtf") -Destination (Join-Path -Path $scriptDirectory -ChildPath "build\Finch") + +# Copy credential service management scripts to finch-credhelper directory +$credHelperDir = Join-Path -Path $scriptDirectory -ChildPath "build\Finch\finch-credhelper" +Copy-Item -Path (Join-Path -Path $scriptDirectory -ChildPath "native-creds-service-start.ps1") -Destination $credHelperDir +Copy-Item -Path (Join-Path -Path $scriptDirectory -ChildPath "native-creds-service-stop.ps1") -Destination $credHelperDir Write-Host "Files copied successfully." # 5. Copy WiX template and update resources path and version diff --git a/msi-builder/FinchMSITemplate.wxs b/msi-builder/FinchMSITemplate.wxs index aa1823d17..ae045dabd 100644 --- a/msi-builder/FinchMSITemplate.wxs +++ b/msi-builder/FinchMSITemplate.wxs @@ -89,6 +89,13 @@ + + + + + + + @@ -100,6 +107,7 @@ + diff --git a/msi-builder/native-creds-service-start.ps1 b/msi-builder/native-creds-service-start.ps1 new file mode 100644 index 000000000..b4539a8dd --- /dev/null +++ b/msi-builder/native-creds-service-start.ps1 @@ -0,0 +1,45 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# native-creds-service-start.ps1 +# Install and start Finch Credential Bridge Windows Service + +param( + [string]$InstallDir = $env:ProgramFiles + "\Finch" +) + +$ServiceName = "FinchCredBridge" +$ServiceDisplayName = "Finch Credential Bridge" +$ServiceDescription = "Provides credential bridge functionality for Finch containers" +$ServiceBinary = Join-Path $InstallDir "finch-credhelper\finch-cred-bridge.exe" + +Write-Host "Installing $ServiceDisplayName service..." + +# Check if service already exists +$existingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue +if ($existingService) { + Write-Host "Service already exists, stopping and removing..." + Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue + & sc.exe delete $ServiceName + Start-Sleep -Seconds 2 +} + +# Create the service to run as the current user +$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name +$result = & sc.exe create $ServiceName binPath= "`"$ServiceBinary`"" start= demand DisplayName= $ServiceDisplayName obj= $currentUser +if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to create service: $result" + exit 1 +} + +# Set service description +& sc.exe description $ServiceName $ServiceDescription + +# Secure credential helper files (Windows equivalent of chmod 700) +$credHelperDir = Join-Path $InstallDir "finch-credhelper" +icacls "$credHelperDir" /inheritance:r /grant:r "$env:USERNAME:(F)" /grant:r "Administrators:(F)" 2>$null +icacls "$credHelperDir\finch-cred-bridge.exe" /inheritance:r /grant:r "$env:USERNAME:(F)" /grant:r "Administrators:(F)" 2>$null +icacls "$credHelperDir\native-creds-service-start.ps1" /inheritance:r /grant:r "$env:USERNAME:(F)" /grant:r "Administrators:(F)" 2>$null +icacls "$credHelperDir\native-creds-service-stop.ps1" /inheritance:r /grant:r "$env:USERNAME:(F)" /grant:r "Administrators:(F)" 2>$null + +Write-Host "$ServiceDisplayName service installed successfully" \ No newline at end of file diff --git a/msi-builder/native-creds-service-stop.ps1 b/msi-builder/native-creds-service-stop.ps1 new file mode 100644 index 000000000..b77da8b92 --- /dev/null +++ b/msi-builder/native-creds-service-stop.ps1 @@ -0,0 +1,27 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# native-creds-service-stop.ps1 +# Stop and remove Finch Credential Bridge Windows Service + +$ServiceName = "FinchCredBridge" + +Write-Host "Stopping and removing $ServiceName service..." + +# Check if service exists +$existingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue +if ($existingService) { + # Stop the service + Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue + + # Remove the service + & sc.exe delete $ServiceName + + if ($LASTEXITCODE -eq 0) { + Write-Host "$ServiceName service removed successfully" + } else { + Write-Warning "Failed to remove service, but continuing..." + } +} else { + Write-Host "$ServiceName service not found, nothing to remove" +} \ No newline at end of file diff --git a/msi-builder/postinstall.bat b/msi-builder/postinstall.bat index e393bffd1..a0527bc5f 100644 --- a/msi-builder/postinstall.bat +++ b/msi-builder/postinstall.bat @@ -1,6 +1,7 @@ @echo off SET InstallDir=%~1 SET FilePath=%InstallDir%\os\finch.yaml +SET CredHelperDir=%InstallDir%\finch-credhelper if exist "%FilePath%" ( powershell -Command "$installPath = '%InstallDir%'.Replace('\', '/'); $content = Get-Content '%FilePath%' -Raw; $content = $content -replace '__INSTALLFOLDER__', $installPath; $content = $content.Replace(\"`r`n\", \"`n\"); $utf8NoBom = New-Object System.Text.UTF8Encoding $false; [System.IO.File]::WriteAllText('%FilePath%', $content, $utf8NoBom)" @@ -8,6 +9,11 @@ if exist "%FilePath%" ( icacls "%InstallDir%\lima\data" /grant Users:(OI)(CI)M +:: Install credential bridge service +if exist "%CredHelperDir%\native-creds-service-start.ps1" ( + powershell -ExecutionPolicy Bypass -File "%CredHelperDir%\native-creds-service-start.ps1" -InstallDir "%InstallDir%" +) + :: Delete files and directories if they exist if exist "%InstallDir%\lima\data\finch\" rmdir /s /q "%InstallDir%\lima\data\finch\" if exist "%InstallDir%\lima\data\_config\override.yaml" del /f /q "%InstallDir%\lima\data\_config\override.yaml" diff --git a/msi-builder/uninstall.bat b/msi-builder/uninstall.bat index 42b278102..d5920e22d 100644 --- a/msi-builder/uninstall.bat +++ b/msi-builder/uninstall.bat @@ -1,5 +1,11 @@ @echo off SET InstallDir=%~1 +SET CredHelperDir=%InstallDir%\finch-credhelper + +:: Stop and remove credential bridge service +if exist "%CredHelperDir%\native-creds-service-stop.ps1" ( + powershell -ExecutionPolicy Bypass -File "%CredHelperDir%\native-creds-service-stop.ps1" +) :: Stop and remove any running instance finch.exe vm stop -f ^ & From 9b0f8d215ee29135b69e4f6b8fef4dda2f8b91df Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Tue, 16 Dec 2025 18:29:12 -0800 Subject: [PATCH 17/21] remove legal boilerplate added by AI Signed-off-by: ayush-panta --- msi-builder/native-creds-service-start.ps1 | 6 ------ msi-builder/native-creds-service-stop.ps1 | 6 ------ 2 files changed, 12 deletions(-) diff --git a/msi-builder/native-creds-service-start.ps1 b/msi-builder/native-creds-service-start.ps1 index b4539a8dd..da8283a69 100644 --- a/msi-builder/native-creds-service-start.ps1 +++ b/msi-builder/native-creds-service-start.ps1 @@ -1,9 +1,3 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -# native-creds-service-start.ps1 -# Install and start Finch Credential Bridge Windows Service - param( [string]$InstallDir = $env:ProgramFiles + "\Finch" ) diff --git a/msi-builder/native-creds-service-stop.ps1 b/msi-builder/native-creds-service-stop.ps1 index b77da8b92..bc714c063 100644 --- a/msi-builder/native-creds-service-stop.ps1 +++ b/msi-builder/native-creds-service-stop.ps1 @@ -1,9 +1,3 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -# native-creds-service-stop.ps1 -# Stop and remove Finch Credential Bridge Windows Service - $ServiceName = "FinchCredBridge" Write-Host "Stopping and removing $ServiceName service..." From c2dd6f245a5e6c5fdd1baac72226fddb31780613 Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Tue, 16 Dec 2025 19:15:53 -0800 Subject: [PATCH 18/21] Fix mac make in dev Signed-off-by: ayush-panta --- .github/workflows/test-cred-security.yaml | 53 ++++++++++++++++++++++- Makefile.creds | 4 +- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-cred-security.yaml b/.github/workflows/test-cred-security.yaml index e3b440a66..de216a73f 100644 --- a/.github/workflows/test-cred-security.yaml +++ b/.github/workflows/test-cred-security.yaml @@ -14,7 +14,55 @@ permissions: env: GO_VERSION: '1.24.6' - # + # steps + + # basics/setup + # setup the macOS environment + # create two users, A and B (sample names) + # will run as if we are A + # setup finch as if it were an application (packaging) + # confirm only native credstore configured + # ensure that binaries/sockets were created where expected with proper permissions (host and VM) + # ensure that the launchagent is loaded + # configure a local registry with registry docker image, private credentials + # test the remove script (is launchagent gone) + # test the install script + + # testing actual functionality + # pull a large public image without registering + # test login security + # add another item (without credhelper), example private creds + # login to registry, confirm that we can enumerate and only see that + # call the binary as user B, see what happens + # essentially, make sure user B cannot do any funny business here + # make sure root also cannot do anything + # tag and push a private image + finch system clear + check the registry got it + # pull the private image back from the registry + # credential stress test (might be able to replace the prior test) + # tag a few large images, different ones + # push at the same time + # remove + # pull at the same time + # test private buildfiles + + + + + # testing if it breaks existing function + # add ecr-credhelper or something (what happens if both configured) + # or maybe, just add ecr-credhelper and nothing else + # login with dummy ECR credentials to some account + # ensure that it works? + + # cleanup + # test the uninstall script manually (is launghagent gone) + # clean up runner environment + + # QUESTIONS -> + # what else do we need to test? Build from a private dockerfile? Idk... + # are these thorough enough testing + # do we need to test existing functionality like that? + jobs: macos-keychain: @@ -24,6 +72,9 @@ jobs: - name: run: | + + + windows-cred-manager: runs-on: ["self-hosted", "windows", "amd64", "test"] timeout-minutes: 120 diff --git a/Makefile.creds b/Makefile.creds index c1ccd2ae2..83c6b265c 100644 --- a/Makefile.creds +++ b/Makefile.creds @@ -59,8 +59,8 @@ PLIST_DEST := $(HOME)/Library/LaunchAgents/$(PLIST_NAME) install-launch-agent: @echo "Installing LaunchAgent..." mkdir -p $(dir $(PLIST_DEST)) - sed -e "s|\$$HOME|$(HOME)|g" \ - -e "s|\$$FINCH_CRED_BRIDGE_PATH|$(FINCH_CREDHELPER_DIR)/finch-cred-bridge|g" \ + sed -e "s|\$$(FINCH_CREDHELPER_DIR)/finch-cred-bridge|$(FINCH_CREDHELPER_DIR)/finch-cred-bridge|g" \ + -e "s|\$$(FINCH_CREDHELPER_SOCKET_LOCATION)|$(FINCH_CREDHELPER_SOCKET_LOCATION)|g" \ $(PLIST_TEMPLATE) > $(PLIST_DEST) -launchctl bootout gui/$$(id -u)/com.runfinch.cred-bridge 2>/dev/null || true launchctl bootstrap gui/$$(id -u) $(PLIST_DEST) From 90f51b65575dbb122ce6e02cfb7f3484311a495c Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Wed, 17 Dec 2025 00:38:58 -0800 Subject: [PATCH 19/21] some workflow progress Signed-off-by: ayush-panta --- .github/workflows/test-cred-security.yaml | 198 ++++++++++++++++------ 1 file changed, 144 insertions(+), 54 deletions(-) diff --git a/.github/workflows/test-cred-security.yaml b/.github/workflows/test-cred-security.yaml index de216a73f..6abe02d93 100644 --- a/.github/workflows/test-cred-security.yaml +++ b/.github/workflows/test-cred-security.yaml @@ -6,74 +6,164 @@ on: schedule: - cron: '0 8 * * *' workflow_dispatch: + inputs: + ref_name: + description: "name of git ref for which to run security tests" + required: false + type: string permissions: + # This is required for configure-aws-credentials to request an OIDC JWT ID token to access AWS resources later on. + # More info: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings id-token: write + # This is required for actions/checkout contents: read env: GO_VERSION: '1.24.6' - # steps - - # basics/setup - # setup the macOS environment - # create two users, A and B (sample names) - # will run as if we are A - # setup finch as if it were an application (packaging) - # confirm only native credstore configured - # ensure that binaries/sockets were created where expected with proper permissions (host and VM) - # ensure that the launchagent is loaded - # configure a local registry with registry docker image, private credentials - # test the remove script (is launchagent gone) - # test the install script - - # testing actual functionality - # pull a large public image without registering - # test login security - # add another item (without credhelper), example private creds - # login to registry, confirm that we can enumerate and only see that - # call the binary as user B, see what happens - # essentially, make sure user B cannot do any funny business here - # make sure root also cannot do anything - # tag and push a private image + finch system clear + check the registry got it - # pull the private image back from the registry - # credential stress test (might be able to replace the prior test) - # tag a few large images, different ones - # push at the same time - # remove - # pull at the same time - # test private buildfiles - - - - - # testing if it breaks existing function - # add ecr-credhelper or something (what happens if both configured) - # or maybe, just add ecr-credhelper and nothing else - # login with dummy ECR credentials to some account - # ensure that it works? - - # cleanup - # test the uninstall script manually (is launghagent gone) - # clean up runner environment - - # QUESTIONS -> - # what else do we need to test? Build from a private dockerfile? Idk... - # are these thorough enough testing - # do we need to test existing functionality like that? - - jobs: - macos-keychain: - runs-on: ["self-hosted", "macos", "arm64", "14", "test"] + # taken from finch/.github/workflows/build-and-test-pkg.yaml for consistent testing + get-tag-name: + name: Get tag name + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 2 + outputs: + tag: ${{ steps.check-tag.outputs.tag }} + commit: ${{ steps.export-commit.outputs.commit }} + steps: + - name: Check tag from workflow input and github ref + id: check-tag + run: | + if [ -n "${{ inputs.ref_name }}" ]; then + tag=${{ inputs.ref_name }} + else + tag=${{ github.ref_name }} + fi + echo "using tag=${tag}" + echo "tag=$tag" >> ${GITHUB_OUTPUT} + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + ref: ${{ steps.check-tag.outputs.tag }} + fetch-depth: 0 + persist-credentials: false + submodules: true + - name: Export commit hash + id: export-commit + run: | + commit=$(git rev-parse HEAD) + echo "using commit=${commit}" + echo "commit=$commit" >> ${GITHUB_OUTPUT} + + macos-security-test: + strategy: + fail-fast: false + matrix: + arch: [arm64, amd64] + version: [14, 15] + include: + - arch: arm64 + output_arch: aarch64 + - arch: amd64 + output_arch: x86_64 + needs: get-tag-name + runs-on: ["self-hosted", "macos", "${{ matrix.arch }}", "${{ matrix.version }}", "test"] timeout-minutes: 120 steps: - - name: + - name: Clean workspace run: | + setopt NULL_GLOB && rm -rf ${{ github.workspace }}/* + shell: zsh {0} + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + ref: ${{ needs.get-tag-name.outputs.commit }} + fetch-depth: 0 + persist-credentials: false + submodules: true + + - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: false + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@00943011d9042930efac3dcd3a170e4273319bc8 # v5.1.0 + with: + role-to-assume: ${{ secrets.ROLE }} + role-session-name: security-test-session + aws-region: ${{ secrets.REGION }} + # from here, it's personal.. + + # we want to configure two users finchUser, otherUser + # finchUser is the one who will actually run things to build and install finch + + - name: Build Finch with credential helper + run: | + brew install lz4 automake autoconf libtool yq llvm + git clean -f -d + make clean + make download-licenses + make FINCH_OS_IMAGE_LOCATION_ROOT=/Applications/Finch + shell: zsh {0} + + - id: final + name: generate pkg + run: | + ./installer-builder/tools/release-installer.sh \ + ${{ inputs.output_arch }} \ + ${{ inputs.tag }} \ + ${{ secrets.INSTALLER_PRIVATE_BUCKET_NAME }} \ + ${{ secrets.EXECUTABLE_BUCKET }} \ + ${{ secrets.PKG_BUCKET }} \ + ${{ secrets.NOTARIZATION_ACCOUNT }} \ + ${{ secrets.NOTARIZATION_CREDENTIAL }} + shell: zsh {0} - + # look for all relevant filepaths and permissions + # cred-helper in /.finch + # config.json and finch.yaml in /.finch set + # stuff in /App/Finch + # look for launchagent loaded + # remove launchagent and make sure unloaded (script) + # install script and make sure its back + + + # now ready for testing + # configure a local repository using image "registry" + # doubles as a public finch image pull when not credentialed + # make finchUser, localhost as url, then finchPass + + # also use security as the user to directly inject other credentials into there + # now, login with some the said credentials to priuvate registry + + # enumerate test to make sure we cannot see, onyl see with binary (enumerate based on creds) + # then try to access as user B and ensure we cannot see, nor access via docker-credential-helper binary + # should block bc in .finch + # make sure root cant really do anything with the binary, cant see either + + # create a dockerfile + # in the dockerfile, we: + # tag our image with the local registry (some decently sized image with enough time to make a lot of requests) + # concurrently tag it as A-E (5) and then push to the registry + # finch system prune and confirm our registry empty + # check if all items are in the registry + # then pull one, confirm it works + + + # then we will do cleanup from finch/.github/workflows/test-pkg.yaml + + + + + - name: Security tests placeholder + run: | + echo "Running security tests on macOS ${{ matrix.version }} ${{ matrix.output_arch }}" + # TODO: Implement security test steps + shell: zsh {0} windows-cred-manager: runs-on: ["self-hosted", "windows", "amd64", "test"] From 70b3374fa445a09a21bdc27b6d05dde8887a4edd Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Wed, 17 Dec 2025 01:37:59 -0800 Subject: [PATCH 20/21] rough workflow file with AI Signed-off-by: ayush-panta --- .github/workflows/test-cred-security.yaml | 549 +++++++++++++++++++--- 1 file changed, 482 insertions(+), 67 deletions(-) diff --git a/.github/workflows/test-cred-security.yaml b/.github/workflows/test-cred-security.yaml index 6abe02d93..f2f0bbf45 100644 --- a/.github/workflows/test-cred-security.yaml +++ b/.github/workflows/test-cred-security.yaml @@ -3,6 +3,7 @@ on: pull_request: branches: - main + - os-native-credstore schedule: - cron: '0 8 * * *' workflow_dispatch: @@ -69,8 +70,8 @@ jobs: - arch: amd64 output_arch: x86_64 needs: get-tag-name - runs-on: ["self-hosted", "macos", "${{ matrix.arch }}", "${{ matrix.version }}", "test"] - timeout-minutes: 120 + runs-on: [self-hosted, macos, "${{ matrix.arch }}", "${{ matrix.version }}", test] + timeout-minutes: 30 steps: - name: Clean workspace run: | @@ -88,89 +89,503 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache: false - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@00943011d9042930efac3dcd3a170e4273319bc8 # v5.1.0 - with: - role-to-assume: ${{ secrets.ROLE }} - role-session-name: security-test-session - aws-region: ${{ secrets.REGION }} - # from here, it's personal.. + - name: Create test users + run: | + # Create finchUser (primary test user) + sudo dscl . -create /Users/finchUser + sudo dscl . -create /Users/finchUser UserShell /bin/zsh + sudo dscl . -create /Users/finchUser RealName "Finch User" + sudo dscl . -create /Users/finchUser UniqueID 1001 + sudo dscl . -create /Users/finchUser PrimaryGroupID 20 + sudo dscl . -create /Users/finchUser NFSHomeDirectory /Users/finchUser + sudo createhomedir -c -u finchUser + + # Create otherUser (security boundary test user) + sudo dscl . -create /Users/otherUser + sudo dscl . -create /Users/otherUser UserShell /bin/zsh + sudo dscl . -create /Users/otherUser RealName "Other User" + sudo dscl . -create /Users/otherUser UniqueID 1002 + sudo dscl . -create /Users/otherUser PrimaryGroupID 20 + sudo dscl . -create /Users/otherUser NFSHomeDirectory /Users/otherUser + sudo createhomedir -c -u otherUser + + # Give finchUser sudo access for installation + echo "finchUser ALL=(ALL) NOPASSWD: ALL" | sudo tee -a /etc/sudoers + shell: zsh {0} - # we want to configure two users finchUser, otherUser - # finchUser is the one who will actually run things to build and install finch + - name: Build Finch as finchUser + run: | + # Copy workspace to finchUser's directory + sudo cp -R ${{ github.workspace }} /Users/finchUser/finch-build + sudo chown -R finchUser:staff /Users/finchUser/finch-build + + # Setup Homebrew for finchUser and install dependencies + sudo -u finchUser /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + sudo -u finchUser zsh -c 'echo "eval \$(/opt/homebrew/bin/brew shellenv)" >> ~/.zshrc' + sudo -u finchUser zsh -c 'source ~/.zshrc && cd /Users/finchUser/finch-build && brew install lz4 automake autoconf libtool yq llvm httpd' + sudo -u finchUser zsh -c 'cd /Users/finchUser/finch-build && git clean -f -d' + sudo -u finchUser zsh -c 'cd /Users/finchUser/finch-build && make clean' + sudo -u finchUser zsh -c 'cd /Users/finchUser/finch-build && make download-licenses || true' + sudo -u finchUser zsh -c 'cd /Users/finchUser/finch-build && make FINCH_OS_IMAGE_LOCATION_ROOT=/Applications/Finch' + shell: zsh {0} - - name: Build Finch with credential helper + - name: Generate and install local PKG as finchUser run: | - brew install lz4 automake autoconf libtool yq llvm - git clean -f -d - make clean - make download-licenses - make FINCH_OS_IMAGE_LOCATION_ROOT=/Applications/Finch + # Generate local unsigned PKG + sudo -u finchUser zsh -c ' + cd /Users/finchUser/finch-build + mkdir -p ./installer-builder/output/origin + cp -RP ./_output ./installer-builder/output/origin/ + ./installer-builder/tools/build-macos-pkg.sh ${{ matrix.output_arch }} ${{ needs.get-tag-name.outputs.tag }} + ' + + # Install the generated PKG + PKG_PATH="/Users/finchUser/finch-build/installer-builder/output/installer/unsigned/package/artifact/Finch.pkg" + sudo -u finchUser installer -pkg "$PKG_PATH" -target / + + # Initialize VM immediately after installation + sudo -u finchUser finch vm init + shell: zsh {0} + + - name: Validate installation as finchUser + run: | + # Check credential helper files and permissions + sudo -u finchUser ls -lah /Users/finchUser/.finch/cred-helpers/ + sudo -u finchUser ls -lah /Users/finchUser/.finch/config.json + sudo -u finchUser ls -lah /Users/finchUser/.finch/finch.yaml + + # Check /Applications/Finch installation (system-wide, not user-specific) + sudo -u finchUser ls -lah /Applications/Finch/finch-credhelper/ + sudo -u finchUser ls -lah /Applications/Finch/bin/ + + # Check LaunchAgent plist file + sudo -u finchUser ls -lah /Users/finchUser/Library/LaunchAgents/ | grep finch || echo ".plist present" + sudo -u finchUser launchctl list | grep finch + + # Test LaunchAgent stop/start cycle + sudo -u finchUser /Applications/Finch/finch-credhelper/native-creds-agent-stop.sh + sudo -u finchUser ls -lah /Users/finchUser/Library/LaunchAgents/ | grep finch || echo ".plist removed" + sudo -u finchUser launchctl list | grep finch || echo "LaunchAgent stopped" + + sudo -u finchUser /Applications/Finch/finch-credhelper/native-creds-agent-start.sh + sudo -u finchUser ls -lah /Users/finchUser/Library/LaunchAgents/ | grep finch || echo ".plist present" + sudo -u finchUser launchctl list | grep finch + shell: zsh {0} + + - name: Set up local private registry with "registry" image + run: | + # Pull registry image (tests uncredentialed pull) + sudo -u finchUser finch pull registry:2 + sudo -u finchUser finch image ls + + # Create htpasswd file for registry authentication + sudo -u finchUser mkdir -p /Users/finchUser/registry-auth + sudo -u finchUser htpasswd -Bbn finchUser finchPass > /Users/finchUser/registry-auth/htpasswd + + # Start private registry with authentication + sudo -u finchUser finch run -d \ + --name test-registry \ + -p 5000:5000 \ + -v /Users/finchUser/registry-auth:/auth \ + -e REGISTRY_AUTH=htpasswd \ + -e REGISTRY_AUTH_HTPASSWD_REALM="Test Registry Realm" \ + -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ + registry:2 + + # Wait for registry to start + sleep 5 + + # Verify registry is running + curl -f http://localhost:5000/v2/ || echo "Registry not accessible without auth (expected)" + shell: zsh {0} + + - name: Test credential security boundaries + run: | + # Login to private registry as finchUser (stores credentials in keychain) + sudo -u finchUser finch login localhost:5000 -u finchUser -p finchPass + + # Test credential enumeration as finchUser (should work) + echo "=== Testing credential enumeration as finchUser ===" + sudo -u finchUser /Users/finchUser/.finch/cred-helpers/docker-credential-osxkeychain list || echo "List failed for finchUser" + + # Verify finchUser can access their own credentials + echo '{"ServerURL":"localhost:5000"}' | sudo -u finchUser /Users/finchUser/.finch/cred-helpers/docker-credential-osxkeychain get || echo "Get failed for finchUser" + + # Test cross-user access as otherUser (should fail) + echo "=== Testing cross-user credential access as otherUser ===" + sudo -u otherUser /Users/finchUser/.finch/cred-helpers/docker-credential-osxkeychain list 2>&1 || echo "Cross-user list blocked (expected)" + + # Test if otherUser can access finchUser's credential helper at all + echo '{"ServerURL":"localhost:5000"}' | sudo -u otherUser /Users/finchUser/.finch/cred-helpers/docker-credential-osxkeychain get 2>&1 || echo "Cross-user get blocked (expected)" + + # Test root access (should also fail - keychain is user-specific) + echo "=== Testing root credential access ===" + sudo /Users/finchUser/.finch/cred-helpers/docker-credential-osxkeychain list 2>&1 || echo "Root list blocked (expected)" + echo '{"ServerURL":"localhost:5000"}' | sudo /Users/finchUser/.finch/cred-helpers/docker-credential-osxkeychain get 2>&1 || echo "Root get blocked (expected)" + + # Test directory permissions prevent access + echo "=== Testing directory permission boundaries ===" + sudo -u otherUser ls -la /Users/finchUser/.finch/ 2>&1 || echo "Directory access blocked (expected)" + sudo -u otherUser cat /Users/finchUser/.finch/config.json 2>&1 || echo "Config access blocked (expected)" shell: zsh {0} - - id: final - name: generate pkg - run: | - ./installer-builder/tools/release-installer.sh \ - ${{ inputs.output_arch }} \ - ${{ inputs.tag }} \ - ${{ secrets.INSTALLER_PRIVATE_BUCKET_NAME }} \ - ${{ secrets.EXECUTABLE_BUCKET }} \ - ${{ secrets.PKG_BUCKET }} \ - ${{ secrets.NOTARIZATION_ACCOUNT }} \ - ${{ secrets.NOTARIZATION_CREDENTIAL }} + - name: Test concurrent operations and Dockerfile build (comprehensive) + run: | + # Pull nginx:alpine and push to private registry + sudo -u finchUser finch pull nginx:alpine + sudo -u finchUser finch tag nginx:alpine localhost:5000/nginx-base + sudo -u finchUser finch push localhost:5000/nginx-base + + # Create multiple tags for concurrent testing + for suffix in a b c d e; do + sudo -u finchUser finch tag nginx:alpine localhost:5000/nginx-test-$suffix + done + + # Concurrent push operations (tests credential helper under load) + echo "=== Starting concurrent pushes ===" + for suffix in a b c d e; do + sudo -u finchUser finch push localhost:5000/nginx-test-$suffix & + done + wait + + # Simple Dockerfile test (tests build-time credentials) + sudo -u finchUser mkdir -p /Users/finchUser/dockerfile-test + sudo -u finchUser tee /Users/finchUser/dockerfile-test/Dockerfile << 'EOF' + FROM localhost:5000/nginx-base + RUN echo "Build test successful" > /test-result + CMD ["cat", "/test-result"] + EOF + + # Build using private registry base image + sudo -u finchUser finch build -t localhost:5000/nginx-custom /Users/finchUser/dockerfile-test/ + sudo -u finchUser finch push localhost:5000/nginx-custom + + # Clean and verify + sudo -u finchUser finch system prune -af + curl -u finchUser:finchPass http://localhost:5000/v2/_catalog + + # Final verification for integrity after push and pull + sudo -u finchUser finch pull localhost:5000/nginx-custom + sudo -u finchUser finch run --rm localhost:5000/nginx-custom shell: zsh {0} - # look for all relevant filepaths and permissions - # cred-helper in /.finch - # config.json and finch.yaml in /.finch set - # stuff in /App/Finch - # look for launchagent loaded - # remove launchagent and make sure unloaded (script) - # install script and make sure its back + - name: Cleanup and uninstall + if: ${{ always() }} + run: | + # Stop registry container + sudo -u finchUser finch stop test-registry || true + sudo -u finchUser finch rm test-registry || true + + # Run uninstall script as finchUser + sudo -u finchUser /Applications/Finch/uninstall.sh || echo "Uninstall script not found or failed" + + # Verify LaunchAgent is removed + sudo -u finchUser launchctl list | grep finch || echo "LaunchAgent removed (expected)" + sudo -u finchUser ls -lah /Users/finchUser/Library/LaunchAgents/ | grep finch || echo "Plist file removed (expected)" + + # Verify /Applications/Finch is removed + ls -la /Applications/Finch/ || echo "/Applications/Finch removed (expected)" + + # Clean up test users + sudo dscl . -delete /Users/finchUser || true + sudo dscl . -delete /Users/otherUser || true + sudo rm -rf /Users/finchUser || true + sudo rm -rf /Users/otherUser || true + + # Remove from sudoers + sudo sed -i '' '/finchUser ALL=(ALL) NOPASSWD: ALL/d' /etc/sudoers || true + shell: zsh {0} + + windows-cred-manager: + needs: get-tag-name + runs-on: [self-hosted, windows, amd64, test] + timeout-minutes: 30 + steps: + - name: Clean workspace + run: | + Remove-Item -Path "${{ github.workspace }}\*" -Recurse -Force -ErrorAction SilentlyContinue + shell: powershell + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + ref: ${{ needs.get-tag-name.outputs.commit }} + fetch-depth: 0 + persist-credentials: false + submodules: true + + - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: false - # now ready for testing - # configure a local repository using image "registry" - # doubles as a public finch image pull when not credentialed - # make finchUser, localhost as url, then finchPass + - name: Create test users + run: | + # Create finchUser (primary test user) + $Password = ConvertTo-SecureString "FinchTestPass123!" -AsPlainText -Force + New-LocalUser -Name "finchUser" -Password $Password -Description "Finch Test User" -PasswordNeverExpires + Add-LocalGroupMember -Group "Users" -Member "finchUser" + Add-LocalGroupMember -Group "Administrators" -Member "finchUser" - # also use security as the user to directly inject other credentials into there - # now, login with some the said credentials to priuvate registry + # Create otherUser (security boundary test user) + $OtherPassword = ConvertTo-SecureString "OtherTestPass123!" -AsPlainText -Force + New-LocalUser -Name "otherUser" -Password $OtherPassword -Description "Other Test User" -PasswordNeverExpires + Add-LocalGroupMember -Group "Users" -Member "otherUser" + shell: powershell - # enumerate test to make sure we cannot see, onyl see with binary (enumerate based on creds) - # then try to access as user B and ensure we cannot see, nor access via docker-credential-helper binary - # should block bc in .finch - # make sure root cant really do anything with the binary, cant see either + - name: Configure git CRLF settings + run: | + git config --global core.autocrlf false + git config --global core.eol lf + shell: powershell - # create a dockerfile - # in the dockerfile, we: - # tag our image with the local registry (some decently sized image with enough time to make a lot of requests) - # concurrently tag it as A-E (5) and then push to the registry - # finch system prune and confirm our registry empty - # check if all items are in the registry - # then pull one, confirm it works + - name: Clean up previous files + run: | + Remove-Item C:\Users\finchUser\.finch -Recurse -ErrorAction Ignore + Remove-Item C:\Users\finchUser\AppData\Local\.finch -Recurse -ErrorAction Ignore + make clean + shell: powershell + - name: Build Finch as finchUser + run: | + # Build Finch using standard process + make FINCH_OS_IMAGE_LOCATION_ROOT="C:\Program Files\Finch" + shell: powershell - # then we will do cleanup from finch/.github/workflows/test-pkg.yaml + - name: Generate and install MSI as finchUser + run: | + # Generate MSI using standard build process + $version = "${{ needs.get-tag-name.outputs.tag }}".TrimStart('v') + if ($version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+$') { + $version = "0.0.1" + } + + powershell .\msi-builder\BuildFinchMSI.ps1 -Version $version + + # Install the generated MSI + $msiPath = ".\msi-builder\build\Finch-$version.msi" + Start-Process msiexec.exe -ArgumentList "/i", $msiPath, "/quiet", "/norestart" -Wait + + # Add Finch to PATH + $env:PATH += ";C:\Program Files\Finch\bin" + + # Initialize VM immediately after installation + & "C:\Program Files\Finch\bin\finch.exe" vm init + shell: powershell + - name: Validate installation as finchUser + run: | + # Check credential helper files and permissions + Get-ChildItem "C:\Users\finchUser\.finch\cred-helpers\" -Force + Get-ChildItem "C:\Users\finchUser\.finch\config.json" -Force + Get-ChildItem "C:\Users\finchUser\.finch\finch.yaml" -Force + + # Check Program Files installation + Get-ChildItem "C:\Program Files\Finch\finch-credhelper\" -Force + Get-ChildItem "C:\Program Files\Finch\bin\" -Force + + # Check Windows Service + Get-Service -Name "FinchCredentialService" -ErrorAction SilentlyContinue + + # Test service stop/start cycle + & "C:\Program Files\Finch\finch-credhelper\native-creds-service-stop.ps1" + Start-Sleep -Seconds 2 + Get-Service -Name "FinchCredentialService" -ErrorAction SilentlyContinue | Where-Object {$_.Status -eq "Stopped"} + + & "C:\Program Files\Finch\finch-credhelper\native-creds-service-start.ps1" + Start-Sleep -Seconds 2 + Get-Service -Name "FinchCredentialService" | Where-Object {$_.Status -eq "Running"} + shell: powershell - - - - name: Security tests placeholder + - name: Set up local private registry with "registry" image run: | - echo "Running security tests on macOS ${{ matrix.version }} ${{ matrix.output_arch }}" - # TODO: Implement security test steps - shell: zsh {0} + # Pull registry image (tests uncredentialed pull) + & "C:\Program Files\Finch\bin\finch.exe" pull registry:2 + & "C:\Program Files\Finch\bin\finch.exe" image ls + + # Create registry auth directory and htpasswd file + $authDir = "C:\Users\finchUser\registry-auth" + New-Item -ItemType Directory -Path $authDir -Force + + # Create htpasswd entry (using basic auth format) + $htpasswdContent = "finchUser:`$2y`$10`$abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOP" + Set-Content -Path "$authDir\htpasswd" -Value $htpasswdContent + + # Start private registry with authentication + & "C:\Program Files\Finch\bin\finch.exe" run -d ` + --name test-registry ` + -p 5000:5000 ` + -v "${authDir}:/auth" ` + -e REGISTRY_AUTH=htpasswd ` + -e "REGISTRY_AUTH_HTPASSWD_REALM=Test Registry Realm" ` + -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd ` + registry:2 + + # Wait for registry to start + Start-Sleep -Seconds 5 + + # Verify registry is running + try { + Invoke-WebRequest -Uri "http://localhost:5000/v2/" -ErrorAction Stop + } catch { + Write-Host "Registry not accessible without auth (expected)" + } + shell: powershell + + - name: Test credential security boundaries + run: | + # Login to private registry as finchUser (stores credentials in Windows Credential Manager) + & "C:\Program Files\Finch\bin\finch.exe" login localhost:5000 -u finchUser -p finchPass + + # Test credential enumeration as finchUser (should work) + Write-Host "=== Testing credential enumeration as finchUser ===" + try { + & "C:\Users\finchUser\.finch\cred-helpers\docker-credential-wincred.exe" list + } catch { + Write-Host "List failed for finchUser: $_" + } + + # Verify finchUser can access their own credentials + try { + '{"ServerURL":"localhost:5000"}' | & "C:\Users\finchUser\.finch\cred-helpers\docker-credential-wincred.exe" get + } catch { + Write-Host "Get failed for finchUser: $_" + } + + # Test cross-user access as otherUser (should fail) + Write-Host "=== Testing cross-user credential access as otherUser ===" + + # Create credential for otherUser to test isolation + $otherUserCreds = New-Object System.Management.Automation.PSCredential("otherUser", (ConvertTo-SecureString "OtherTestPass123!" -AsPlainText -Force)) + + try { + Start-Process -FilePath "C:\Users\finchUser\.finch\cred-helpers\docker-credential-wincred.exe" -ArgumentList "list" -Credential $otherUserCreds -Wait -NoNewWindow -RedirectStandardError "error.txt" + Write-Host "Cross-user list blocked (expected)" + } catch { + Write-Host "Cross-user access properly blocked: $_" + } + + # Test Administrator access (should work but access different credential store) + Write-Host "=== Testing Administrator credential access ===" + try { + & "C:\Users\finchUser\.finch\cred-helpers\docker-credential-wincred.exe" list + } catch { + Write-Host "Admin access result: $_" + } + + # Test directory permissions prevent access + Write-Host "=== Testing directory permission boundaries ===" + try { + Start-Process -FilePath "cmd.exe" -ArgumentList "/c", "dir", "C:\Users\finchUser\.finch\" -Credential $otherUserCreds -Wait -NoNewWindow -RedirectStandardError "dir_error.txt" + Write-Host "Directory access blocked (expected)" + } catch { + Write-Host "Directory access blocked: $_" + } + shell: powershell - windows-cred-manager: - runs-on: ["self-hosted", "windows", "amd64", "test"] - timeout-minutes: 120 - steps: - - name: Setup Windows credential tests + - name: Test concurrent operations and Dockerfile build (comprehensive) run: | - # Windows credential manager tests - echo "Windows credential tests placeholder" + # Pull nginx and push to private registry + & "C:\Program Files\Finch\bin\finch.exe" pull nginx:alpine + & "C:\Program Files\Finch\bin\finch.exe" tag nginx:alpine localhost:5000/nginx-base + & "C:\Program Files\Finch\bin\finch.exe" push localhost:5000/nginx-base + + # Create multiple tags for concurrent testing + $suffixes = @("a", "b", "c", "d", "e") + foreach ($suffix in $suffixes) { + & "C:\Program Files\Finch\bin\finch.exe" tag nginx:alpine "localhost:5000/nginx-test-$suffix" + } + + # Concurrent push operations (tests credential helper under load) + Write-Host "=== Starting concurrent pushes ===" + $jobs = @() + foreach ($suffix in $suffixes) { + $jobs += Start-Job -ScriptBlock { + param($suffix) + & "C:\Program Files\Finch\bin\finch.exe" push "localhost:5000/nginx-test-$suffix" + } -ArgumentList $suffix + } + $jobs | Wait-Job | Receive-Job + + # Simple Dockerfile test (tests build-time credentials) + $dockerfileDir = "C:\Users\finchUser\dockerfile-test" + New-Item -ItemType Directory -Path $dockerfileDir -Force + + # Create simple Dockerfile content + "FROM localhost:5000/nginx-base" | Out-File -FilePath "$dockerfileDir\Dockerfile" -Encoding ASCII + "RUN echo 'Build test successful' > /test-result" | Out-File -FilePath "$dockerfileDir\Dockerfile" -Append -Encoding ASCII + "CMD ['cat', '/test-result']" | Out-File -FilePath "$dockerfileDir\Dockerfile" -Append -Encoding ASCII + + # Build using private registry base image + & "C:\Program Files\Finch\bin\finch.exe" build -t localhost:5000/nginx-custom $dockerfileDir + & "C:\Program Files\Finch\bin\finch.exe" push localhost:5000/nginx-custom + + # Clean and verify + & "C:\Program Files\Finch\bin\finch.exe" system prune -af + + # Verify registry catalog + $authHeader = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("finchUser:finchPass")) + try { + Invoke-WebRequest -Uri "http://localhost:5000/v2/_catalog" -Headers @{Authorization="Basic $authHeader"} + } catch { + Write-Host "Registry catalog check failed: $_" + } + + # Final verification for integrity after push and pull + & "C:\Program Files\Finch\bin\finch.exe" pull localhost:5000/nginx-custom + & "C:\Program Files\Finch\bin\finch.exe" run --rm localhost:5000/nginx-custom + shell: powershell + + - name: Cleanup and uninstall + if: ${{ always() }} + run: | + # Stop registry container + try { + & "C:\Program Files\Finch\bin\finch.exe" stop test-registry + & "C:\Program Files\Finch\bin\finch.exe" rm test-registry + } catch { + Write-Host "Registry cleanup failed or already stopped" + } + + # Run uninstall (MSI uninstall) + $productCode = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like "*Finch*" } | Select-Object -ExpandProperty IdentifyingNumber + if ($productCode) { + msiexec /x $productCode /qn + } + + # Verify service is removed + try { + Get-Service -Name "FinchCredentialService" -ErrorAction Stop + Write-Host "Service still exists (unexpected)" + } catch { + Write-Host "Service removed (expected)" + } + + # Verify Program Files is removed + if (Test-Path "C:\Program Files\Finch") { + Write-Host "Program Files directory still exists (unexpected)" + } else { + Write-Host "Program Files directory removed (expected)" + } + shell: powershell + + - name: Remove Finch VM and Clean Up Previous Environment + if: ${{ always() }} + timeout-minutes: 5 + shell: pwsh + run: | + ./scripts/cleanup_wsl.ps1 + make clean + + # Clean up test users + try { + Remove-LocalUser -Name "finchUser" -ErrorAction SilentlyContinue + Remove-LocalUser -Name "otherUser" -ErrorAction SilentlyContinue + Remove-Item -Path "C:\Users\finchUser" -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -Path "C:\Users\otherUser" -Recurse -Force -ErrorAction SilentlyContinue + } catch { + Write-Host "User cleanup completed with warnings: $_" + } + exit 0 # Cleanup may set the exit code; just ignore From 93df066b3db04e0ed7096631e717c838fd8259ae Mon Sep 17 00:00:00 2001 From: ayush-panta Date: Wed, 17 Dec 2025 01:43:16 -0800 Subject: [PATCH 21/21] some testing stuffs Signed-off-by: ayush-panta --- Dockerfile.test-creds | 3 +++ test-creds.sh | 44 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 Dockerfile.test-creds create mode 100755 test-creds.sh diff --git a/Dockerfile.test-creds b/Dockerfile.test-creds new file mode 100644 index 000000000..99ca34bb4 --- /dev/null +++ b/Dockerfile.test-creds @@ -0,0 +1,3 @@ +FROM 299170649678.dkr.ecr.us-west-2.amazonaws.com/test:nginx +# Test dockerfile for credential bridge functionality +# This pulls from private ECR to test credential access during build \ No newline at end of file diff --git a/test-creds.sh b/test-creds.sh new file mode 100755 index 000000000..16ba08a34 --- /dev/null +++ b/test-creds.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -e + +echo "Testing credential bridge with concurrent operations..." + +# # Ensure ECR login +# export AWS_ACCOUNT_ID=299170649678 +# export AWS_REGION=us-west-2 +# aws ecr get-login-password --region $AWS_REGION | ./_output/bin/finch login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com + +# Test 1: Build with private base image (tests credential access during build) +echo "Building image with private base..." +./_output/bin/finch build -f Dockerfile.test-creds -t test-creds-image . + +# Test 2: Tag with multiple names +echo "Tagging image with multiple names..." +./_output/bin/finch tag test-creds-image $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:A +./_output/bin/finch tag test-creds-image $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:B +./_output/bin/finch tag test-creds-image $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:C +./_output/bin/finch tag test-creds-image $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:D +./_output/bin/finch tag test-creds-image $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:E + +# Test 3: Push all concurrently (stress test credential bridge) +echo "Pushing all images concurrently..." +./_output/bin/finch push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:A & +./_output/bin/finch push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:B & +./_output/bin/finch push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:C & +./_output/bin/finch push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:D & +./_output/bin/finch push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:E & + +# Wait for all pushes to complete +wait +echo "โœ… All credential operations completed successfully!" + +# Cleanup +echo "Cleaning up test images..." +./_output/bin/finch image rm test-creds-image +./_output/bin/finch image rm $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:A +./_output/bin/finch image rm $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:B +./_output/bin/finch image rm $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:C +./_output/bin/finch image rm $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:D +./_output/bin/finch image rm $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/test:E + +echo "๐ŸŽ‰ Credential bridge test completed!" \ No newline at end of file