Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
0d5f90a
feat: add native credential helper support
ayush-panta Jan 3, 2026
cacfc3b
build and path fixes
ayush-panta Jan 5, 2026
0c8b274
Try better add finchhost credhhelper to PATH
ayush-panta Jan 5, 2026
14c8594
More finchhost PATH fix attempt
ayush-panta Jan 5, 2026
68d5df6
powershell paths in make
ayush-panta Jan 5, 2026
c930e16
more path fix
ayush-panta Jan 5, 2026
e9d9479
add debug log
ayush-panta Jan 5, 2026
f3e6355
simple finchhost to PATH via mounts
ayush-panta Jan 6, 2026
a3ed27c
Makefile fix for .finch on Windows
ayush-panta Jan 6, 2026
829f660
still trying to fix config.json in PS
ayush-panta Jan 6, 2026
230c406
simplify config creation ps
ayush-panta Jan 6, 2026
8486d61
unix shell bug for make
ayush-panta Jan 6, 2026
015aa25
try debug log and diff add to Path in nca
ayush-panta Jan 6, 2026
3b9688f
More debug logging
ayush-panta Jan 6, 2026
eed03f4
move to lima common provision with nca binary check
ayush-panta Jan 6, 2026
4e52a1b
remove bloated debug logs
ayush-panta Jan 6, 2026
dd52312
diagnostic isolated e2e test
ayush-panta Jan 6, 2026
a1b22bb
debug for config.json, better registry setup
ayush-panta Jan 6, 2026
de8fd92
more test debug logging
ayush-panta Jan 6, 2026
1be9efa
prompt native helper on runners for login confirmation
ayush-panta Jan 6, 2026
18d5165
more debug with inline registry create/login
ayush-panta Jan 6, 2026
1a92e6b
registry container debug
ayush-panta Jan 6, 2026
5663e2e
import and bugfix
ayush-panta Jan 6, 2026
d2c82ed
use curl to look for 401 response from registry
ayush-panta Jan 6, 2026
507409d
better debug for all finch ops
ayush-panta Jan 6, 2026
6d5774d
add logging to finchhost main and login/logout
ayush-panta Jan 6, 2026
5f5d602
direct binary test for store access in CI + socket detection debug
ayush-panta Jan 6, 2026
ca3d205
Add keychain creation for ec2 user
ayush-panta Jan 6, 2026
f1607dd
keychain and config function testing
ayush-panta Jan 7, 2026
2d8ef47
some socket test fixes; minor, may not work, not prio
ayush-panta Jan 7, 2026
dec7ae2
try socket watching, test
ayush-panta Jan 7, 2026
5b22629
pull op instead of login for socket test
ayush-panta Jan 7, 2026
cb11d48
add host env var to lima, debug windows unix socket in WSL
ayush-panta Jan 7, 2026
1a20a36
use Q for extensive socket path glob search in WSL
ayush-panta Jan 7, 2026
ef7f38e
more glob patterns and CI fallback
ayush-panta Jan 7, 2026
d143ef1
narrow down socket search
ayush-panta Jan 7, 2026
97f21f0
removing conditional windows logic
ayush-panta Jan 7, 2026
485b04a
fix build issues for bridge on windows
ayush-panta Jan 7, 2026
c5a423a
remove windows logic from e2e test
ayush-panta Jan 7, 2026
5fc0f1e
aliasing fix and rm FINCH_DIR
ayush-panta Jan 7, 2026
41f68dc
local registry working
ayush-panta Jan 8, 2026
9c4b14e
show registry blocks un-auth attempts
ayush-panta Jan 8, 2026
e2b8be1
clean testing (erroring)
ayush-panta Jan 8, 2026
d75b9f7
rework; tie socket to vm lifecycle
ayush-panta Jan 8, 2026
3bcab2b
complex socket server with debug
ayush-panta Jan 8, 2026
7a7d343
server in /pkg; issues due to process being killed
ayush-panta Jan 8, 2026
0b3c2b6
working daemon impl
ayush-panta Jan 8, 2026
4171b31
basic working with config plaintext
ayush-panta Jan 8, 2026
a53d9a1
env passed + http protocol
ayush-panta Jan 8, 2026
3b74212
e2e mac test + logging
ayush-panta Jan 8, 2026
8950d36
fixing build issues
ayush-panta Jan 8, 2026
24fad1d
keychain debug for x64
ayush-panta Jan 8, 2026
ec60a4c
minor cleanups
ayush-panta Jan 8, 2026
28c3ba4
helper to PATH for macOS 15 x64
ayush-panta Jan 8, 2026
f6bd444
sudo + error handling helper in PATH
ayush-panta Jan 8, 2026
7a724bd
add helper to PATH, removed by accident
ayush-panta Jan 8, 2026
adf87fc
remove direct keychain prompting, causing error in x64
ayush-panta Jan 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ 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
remote-all: arch-test install.finch-core-dependencies finch finch.yaml networks.yaml config.yaml $(OUTDIR)/finch-daemon/finch@.service

ifeq ($(BUILD_OS), Windows_NT)
include Makefile.windows
Expand Down Expand Up @@ -176,6 +176,31 @@ finch-native: finch-all

finch-all:
$(GO) build -ldflags $(LDFLAGS) -tags "$(GO_BUILD_TAGS)" -o $(OUTDIR)/bin/$(BINARYNAME) $(PACKAGE)/cmd/finch
"$(MAKE)" build-credential-helper
"$(MAKE)" build-credential-daemon

.PHONY: build-credential-helper
build-credential-helper:
ifeq ($(GOOS),darwin)
# Build finchhost credential helper for VM
GOOS=linux GOARCH=$(shell go env GOARCH) $(GO) build -ldflags $(LDFLAGS) -o $(OUTDIR)/bin/docker-credential-finchhost $(PACKAGE)/cmd/finchhost-credential-helper
# Copy to /tmp/lima which is mounted in macOS VM
mkdir -p /tmp/lima/finchhost
cp $(OUTDIR)/bin/docker-credential-finchhost /tmp/lima/finchhost/
chmod +x /tmp/lima/finchhost/docker-credential-finchhost
# Make osxkeychain credential helper available on host PATH
@if [ -f $(OUTDIR)/cred-helpers/docker-credential-osxkeychain ]; then \
chmod +x $(OUTDIR)/cred-helpers/docker-credential-osxkeychain; \
sudo ln -sf $(OUTDIR)/cred-helpers/docker-credential-osxkeychain /usr/local/bin/docker-credential-osxkeychain; \
fi
endif

.PHONY: build-credential-daemon
build-credential-daemon:
ifeq ($(GOOS),darwin)
# Build credential daemon for host
$(GO) build -ldflags $(LDFLAGS) -o $(OUTDIR)/bin/finch-cred-daemon $(PACKAGE)/cmd/finch-cred-daemon
endif

.PHONY: release
release: check-licenses all download-licenses
Expand Down
94 changes: 94 additions & 0 deletions cmd/finch-cred-daemon/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import (
"encoding/json"
"log"
"net"
"net/http"
"os"
"os/signal"
"syscall"

"github.com/runfinch/finch/pkg/bridge-credhelper"
)

type CredentialRequest struct {
Action string `json:"action"`
ServerURL string `json:"serverURL"`
Env map[string]string `json:"env"`
}

func main() {
if len(os.Args) < 2 {
log.Fatal("Usage: finch-cred-daemon <socket-path>")
}

socketPath := os.Args[1]

// Clean up old socket
os.Remove(socketPath)

// Create listener
listener, err := net.Listen("unix", socketPath)
if err != nil {
log.Fatalf("Failed to create socket: %v", err)
}
defer listener.Close()
defer os.Remove(socketPath)

// Set permissions
os.Chmod(socketPath, 0600)

log.Printf("Credential daemon listening on %s", socketPath)

// Handle shutdown signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

go func() {
<-sigChan
log.Println("Shutting down...")
listener.Close()
os.Exit(0)
}()

// Create HTTP server
mux := http.NewServeMux()
mux.HandleFunc("/credentials", handleCredentials)
server := &http.Server{Handler: mux}

// Serve HTTP over Unix socket
server.Serve(listener)
}

func handleCredentials(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

var req CredentialRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}

log.Printf("[DAEMON DEBUG] Received request for %s with %d env vars", req.ServerURL, len(req.Env))
for key, val := range req.Env {
truncated := val
if len(val) > 20 {
truncated = val[:20] + "..."
}
log.Printf("[DAEMON DEBUG] Env: %s=%s", key, truncated)
}

// Call credential helper with environment variables
creds, err := bridgecredhelper.CallCredentialHelperWithEnv(req.Action, req.ServerURL, "", "", req.Env)
if err != nil {
// Return empty credentials on error
creds = &bridgecredhelper.DockerCredential{ServerURL: req.ServerURL}
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(creds)
}
127 changes: 127 additions & 0 deletions cmd/finch/login_local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//go:build darwin || windows

/*
Copyright The containerd Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Copy of nerdctl login command with minor changes

package main

import (
"errors"
"io"
"strings"

"github.com/spf13/cobra"

"github.com/containerd/log"

"github.com/containerd/nerdctl/v2/pkg/api/types"
"github.com/containerd/nerdctl/v2/pkg/cmd/login"
)

func newLoginLocalCommand(_ interface{}, _ interface{}) *cobra.Command {
var cmd = &cobra.Command{
Use: "login [flags] [SERVER]",
Args: cobra.MaximumNArgs(1),
Short: "Log in to a container registry",
RunE: loginAction,
SilenceUsage: true,
SilenceErrors: true,
}
cmd.Flags().StringP("username", "u", "", "Username")
cmd.Flags().StringP("password", "p", "", "Password")
cmd.Flags().Bool("password-stdin", false, "Take the password from stdin")
return cmd
}

func loginOptions(cmd *cobra.Command) (types.LoginCommandOptions, error) {
// Simple global options without importing nerdctl/helpers -> go mod error
globalOptions := types.GlobalCommandOptions{}

username, err := cmd.Flags().GetString("username")
if err != nil {
return types.LoginCommandOptions{}, err
}
password, err := cmd.Flags().GetString("password")
if err != nil {
return types.LoginCommandOptions{}, err
}
passwordStdin, err := cmd.Flags().GetBool("password-stdin")
if err != nil {
return types.LoginCommandOptions{}, err
}

if strings.Contains(username, ":") {
return types.LoginCommandOptions{}, errors.New("username cannot contain colons")
}

if password != "" {
log.L.Warn("WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
if passwordStdin {
return types.LoginCommandOptions{}, errors.New("--password and --password-stdin are mutually exclusive")
}
}

if passwordStdin {
if username == "" {
return types.LoginCommandOptions{}, errors.New("must provide --username with --password-stdin")
}

contents, err := io.ReadAll(cmd.InOrStdin())
if err != nil {
return types.LoginCommandOptions{}, err
}

password = strings.TrimSuffix(string(contents), "\n")
password = strings.TrimSuffix(password, "\r")
}
return types.LoginCommandOptions{
GOptions: globalOptions,
Username: username,
Password: password,
}, nil
}

func loginAction(cmd *cobra.Command, args []string) error {
log.L.Error("[LOGIN DEBUG] Starting login action")
options, err := loginOptions(cmd)
if err != nil {
log.L.WithError(err).Error("[LOGIN DEBUG] Failed to parse login options")
return err
}

if len(args) == 1 {
// Normalize server address by removing default HTTPS port
serverAddr := args[0]
log.L.Errorf("[LOGIN DEBUG] Original server address: %s", serverAddr)
serverAddr = strings.TrimSuffix(serverAddr, ":443")
log.L.Errorf("[LOGIN DEBUG] Normalized server address: %s", serverAddr)
options.ServerAddress = serverAddr
}

log.L.Errorf("[LOGIN DEBUG] Login options - ServerAddress: %s, Username: %s", options.ServerAddress, options.Username)
log.L.Error("[LOGIN DEBUG] Calling nerdctl login.Login()")
err = login.Login(cmd.Context(), options, cmd.OutOrStdout())
if err != nil {
log.L.WithError(err).Error("[LOGIN DEBUG] nerdctl login.Login() failed")
} else {
log.L.Error("[LOGIN DEBUG] nerdctl login.Login() succeeded")
}
return err
}
83 changes: 83 additions & 0 deletions cmd/finch/logout_local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//go:build darwin || windows

/*
Copyright The containerd Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Copy of nerdctl logout command with minor changes

package main

import (
"github.com/spf13/cobra"

"github.com/containerd/log"

"github.com/containerd/nerdctl/v2/pkg/cmd/logout"

"github.com/runfinch/finch/pkg/flog"
)

func newLogoutLocalCommand(_ flog.Logger) *cobra.Command {
return &cobra.Command{
Use: "logout [flags] [SERVER]",
Args: cobra.MaximumNArgs(1),
Short: "Log out from a container registry",
RunE: logoutAction,
ValidArgsFunction: logoutShellComplete,
SilenceUsage: true,
SilenceErrors: true,
}
}

func logoutAction(cmd *cobra.Command, args []string) error {
log.L.Error("[LOGOUT DEBUG] Starting logout action")
logoutServer := ""
if len(args) > 0 {
logoutServer = args[0]
log.L.Errorf("[LOGOUT DEBUG] Logout server: %s", logoutServer)
} else {
log.L.Error("[LOGOUT DEBUG] No server specified, logging out from default")
}

log.L.Error("[LOGOUT DEBUG] Calling nerdctl logout.Logout()")
errGroup, err := logout.Logout(cmd.Context(), logoutServer)
if err != nil {
log.L.WithError(err).Errorf("Failed to erase credentials for: %s", logoutServer)
log.L.WithError(err).Error("[LOGOUT DEBUG] nerdctl logout.Logout() failed")
} else {
log.L.Error("[LOGOUT DEBUG] nerdctl logout.Logout() succeeded")
}
if errGroup != nil {
log.L.Error("None of the following entries could be found")
log.L.Error("[LOGOUT DEBUG] Error group found, listing entries")
for _, v := range errGroup {
log.L.Errorf("%s", v)
}
}

return err
}

func logoutShellComplete(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
candidates, err := logout.ShellCompletion()
if err != nil {
return nil, cobra.ShellCompDirectiveError
}

return candidates, cobra.ShellCompDirectiveNoFileComp
}
2 changes: 2 additions & 0 deletions cmd/finch/main_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ var newApp = func(
virtualMachineCommands(logger, fp, ncc, ecc, fs, fc, home, finchRootPath),
newSupportBundleCommand(logger, supportBundleBuilder, ncc),
newGenDocsCommand(rootCmd, logger, fs, system.NewStdLib()),
newLoginLocalCommand(nil, nil),
newLogoutLocalCommand(logger),
)

rootCmd.AddCommand(allCommands...)
Expand Down
2 changes: 0 additions & 2 deletions cmd/finch/nerdctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ var nerdctlCmds = map[string]string{
"inspect": "Return low-level information on Docker objects",
"kill": "Kill one or more running containers",
"load": "Load an image from a tar archive or STDIN",
"login": "Log in to a container registry",
"logout": "Log out from a container registry",
"logs": "Fetch the logs of a container",
"network": "Manage networks",
"pause": "Pause all processes within one or more containers",
Expand Down
Loading