From 86c48d1bdbe25d9f64ac82c17039118c49079f1e Mon Sep 17 00:00:00 2001 From: wollomatic Date: Sat, 25 Jul 2026 19:07:10 +0200 Subject: [PATCH] log container name instead of container id --- internal/config/config.go | 45 +++++++++++++++++++++---- internal/config/config_test.go | 60 ++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 7 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index f929bf1..03fb6a6 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -68,6 +68,7 @@ type AllowListRegistry struct { type AllowList struct { ID string // Container ID (empty for the default allowlist) + ContainerName string // Container name (empty for the default allowlist) AllowedRequests map[string][]*regexp.Regexp // map of request methods to request path regex patterns (no requests allowed if empty) AllowedBindMounts []string // list of from portion of allowed bind mounts (all bind mounts allowed if empty) } @@ -341,7 +342,8 @@ func (cfg *Config) UpdateAllowLists() { slog.Info("Docker event stream closed") return } - slog.Debug("received Docker container event", "action", event.Action, "id", event.Actor.ID[:12]) + containerName := eventContainerName(event) + slog.Debug("received Docker container event", "action", event.Action, "container", containerName) addedIPs, removedIPs, updateErr := cfg.AllowLists.updateFromEvent(ctx, dockerClient, event) if updateErr != nil { slog.Warn("failed to update allowlists from container event", "error", updateErr) @@ -356,7 +358,7 @@ func (cfg *Config) UpdateAllowLists() { } } for _, ip := range removedIPs { - slog.Info("removed allowlist for container", "id", event.Actor.ID[:12], "ip", ip) + slog.Info("removed allowlist for container", "container", containerName, "ip", ip) } case err := <-errChan: if err != nil { @@ -427,6 +429,7 @@ func (allowLists *AllowListRegistry) initByIP(ctx context.Context, dockerClient if slices.Contains(allowLists.networks, networkID) { allowList := AllowList{ ID: cntr.ID, + ContainerName: containerName(cntr), AllowedRequests: allowedRequests, AllowedBindMounts: allowedBindMounts, } @@ -483,7 +486,7 @@ func (allowLists *AllowListRegistry) add( return nil, err } if len(containers) == 0 { - slog.Debug("container is not in a network with socket-proxy or may have stopped", "id", containerID[:12]) + slog.Debug("container is not in a network with socket-proxy or may have stopped", "id", shortContainerID(containerID)) return nil, nil } cntr := containers[0] @@ -497,6 +500,7 @@ func (allowLists *AllowListRegistry) add( if len(allowedRequests) > 0 || len(allowedBindMounts) > 0 { allowList := AllowList{ ID: cntr.ID, + ContainerName: containerName(cntr), AllowedRequests: allowedRequests, AllowedBindMounts: allowedBindMounts, } @@ -550,7 +554,7 @@ func (allowList AllowList) Print(ip string, logJSON bool) { } else { for method, regex := range allowList.AllowedRequests { slog.Info("configured request allowlist", - "id", allowList.ID[:12], + "container", allowList.ContainerName, "ip", ip, "method", method, "regex", regex, @@ -563,7 +567,7 @@ func (allowList AllowList) Print(ip string, logJSON bool) { if ip == "" { fmt.Printf("Default request allowlist:\n %-8s %s\n", "Method", "Regex") } else { - fmt.Printf("Request allowlist for %s (%s):\n %-8s %s\n", allowList.ID[:12], ip, "Method", "Regex") + fmt.Printf("Request allowlist for %s (%s):\n %-8s %s\n", allowList.ContainerName, ip, "Method", "Regex") } for method, regex := range allowList.AllowedRequests { fmt.Printf(" %-8s %s\n", method, regex) @@ -578,7 +582,7 @@ func (allowList AllowList) Print(ip string, logJSON bool) { } else { slog.Info("Docker bind mount restrictions enabled", "allowbindmountfrom", allowList.AllowedBindMounts, - "id", allowList.ID[:12], + "container", allowList.ContainerName, "ip", ip, ) } @@ -587,11 +591,38 @@ func (allowList AllowList) Print(ip string, logJSON bool) { if ip == "" { slog.Debug("no default Docker bind mount restrictions") } else { - slog.Debug("no Docker bind mount restrictions", "id", allowList.ID[:12], "ip", ip) + slog.Debug("no Docker bind mount restrictions", "container", allowList.ContainerName, "ip", ip) } } } +// containerName returns Docker's container name without its leading slash. +// It falls back to the short container ID for unusual responses without a name. +func containerName(cntr container.Summary) string { + for _, name := range cntr.Names { + if name = strings.TrimPrefix(name, "/"); name != "" { + return name + } + } + return shortContainerID(cntr.ID) +} + +// eventContainerName returns the name Docker includes with container events. +// It falls back to the short container ID when the event has no name. +func eventContainerName(event events.Message) string { + if name := event.Actor.Attributes["name"]; name != "" { + return name + } + return shortContainerID(event.Actor.ID) +} + +func shortContainerID(id string) string { + if len(id) > 12 { + return id[:12] + } + return id +} + // compile allowed requests regex pattern func compileRegexp(regex, method, configLocation string) (*regexp.Regexp, error) { r, err := regexp.Compile("^" + regex + "$") diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 116408a..9b3c8a5 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/wollomatic/socket-proxy/internal/docker/api/types/container" + "github.com/wollomatic/socket-proxy/internal/docker/api/types/events" ) func resetFlagsForTest(t *testing.T, args []string) func() { @@ -128,6 +129,65 @@ func regexMapsEqual(a, b map[string][]*regexp.Regexp) bool { return true } +func TestContainerName(t *testing.T) { + tests := []struct { + name string + cntr container.Summary + want string + }{ + { + name: "uses the first Docker container name", + cntr: container.Summary{ID: "0123456789abcdef", Names: []string{"/traefik", "/ignored"}}, + want: "traefik", + }, + { + name: "falls back to the short ID when Docker provides no name", + cntr: container.Summary{ID: "0123456789abcdef"}, + want: "0123456789ab", + }, + { + name: "does not panic for a short fallback ID", + cntr: container.Summary{ID: "short"}, + want: "short", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := containerName(tt.cntr); got != tt.want { + t.Errorf("containerName() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestEventContainerName(t *testing.T) { + tests := []struct { + name string + event events.Message + want string + }{ + { + name: "uses the container name from event attributes", + event: events.Message{Actor: events.Actor{ID: "0123456789abcdef", Attributes: map[string]string{"name": "traefik"}}}, + want: "traefik", + }, + { + name: "falls back to the short ID when the event has no name", + event: events.Message{Actor: events.Actor{ID: "0123456789abcdef"}}, + want: "0123456789ab", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := eventContainerName(tt.event); got != tt.want { + t.Errorf("eventContainerName() = %q, want %q", got, tt.want) + } + }) + } +} + func TestInitConfig_AllowMethodFlagOverridesEnv(t *testing.T) { t.Setenv("SP_ALLOW_GET", "/from-env") restore := resetFlagsForTest(t, []string{"socket-proxy", "-allowGET=/from-flag"})