From b9ec0c17a97877e3d08a3ef301e06e11b98cd7d8 Mon Sep 17 00:00:00 2001 From: Richard Martikan Date: Tue, 14 Jul 2026 19:55:45 +0200 Subject: [PATCH] fix: Use SASLType connection mode to be able to pass special chars to the password also --- go.mod | 2 +- internal/broker/client.go | 20 ++++++++++++-------- internal/broker/client_test.go | 20 +++++++++++--------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 7a74e5f..ed000ab 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.25.0 require ( github.com/Azure/go-amqp v1.5.1 + github.com/docker/docker v28.5.2+incompatible github.com/google/uuid v1.6.0 github.com/spf13/cobra v1.10.2 github.com/testcontainers/testcontainers-go v0.41.0 @@ -23,7 +24,6 @@ require ( github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v28.5.2+incompatible // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.10.0 // indirect diff --git a/internal/broker/client.go b/internal/broker/client.go index 8e0cbe7..a7e1f5d 100644 --- a/internal/broker/client.go +++ b/internal/broker/client.go @@ -38,7 +38,7 @@ type Client struct { // bounded by ctx. On any failure it cleans up a half-open connection and // returns a wrapped error. The returned Client must be closed with Close. func Connect(ctx context.Context, p ConnectionProps) (*Client, error) { - conn, err := amqp.Dial(ctx, fmt.Sprintf("amqp://%s", buildConnectionURL(p)), nil) + conn, err := amqp.Dial(ctx, fmt.Sprintf("amqp://%s", p.URL), connOptions(p)) if err != nil { return nil, fmt.Errorf("dial broker: %w", err) } @@ -50,6 +50,17 @@ func Connect(ctx context.Context, p ConnectionProps) (*Client, error) { return &Client{conn: conn, sess: sess}, nil } +// connOptions builds the SASL PLAIN options for p, or nil for an anonymous +// connection. Credentials travel in the SASL handshake rather than the dial +// URL, so passwords may contain any characters (/, =, @, ...) without needing +// URL escaping. +func connOptions(p ConnectionProps) *amqp.ConnOptions { + if p.Username == "" && p.Password == "" { + return nil + } + return &amqp.ConnOptions{SASLType: amqp.SASLTypePlain(p.Username, p.Password)} +} + // Session exposes the Client's default AMQP session so callers in this package // can open their own links on it. func (c *Client) Session() *amqp.Session { return c.sess } @@ -66,10 +77,3 @@ func (c *Client) Close(ctx context.Context) error { } return nil } - -func buildConnectionURL(p ConnectionProps) string { - if p.Username != "" && p.Password != "" { - return fmt.Sprintf("%s:%s@%s", p.Username, p.Password, p.URL) - } - return p.URL -} diff --git a/internal/broker/client_test.go b/internal/broker/client_test.go index 989b735..2f1abf0 100644 --- a/internal/broker/client_test.go +++ b/internal/broker/client_test.go @@ -2,20 +2,22 @@ package broker import "testing" -func TestBuildConnectionURL(t *testing.T) { +func TestConnOptions(t *testing.T) { cases := []struct { - name string - in ConnectionProps - want string + name string + in ConnectionProps + wantSet bool }{ - {"with creds", ConnectionProps{URL: "h:61616", Username: "a", Password: "b"}, "a:b@h:61616"}, - {"no creds", ConnectionProps{URL: "h:61616"}, "h:61616"}, - {"user only", ConnectionProps{URL: "h:61616", Username: "a"}, "h:61616"}, + {"with creds", ConnectionProps{URL: "h:61616", Username: "a", Password: "b"}, true}, + {"special char password", ConnectionProps{URL: "h:61616", Username: "a", Password: "X/Y="}, true}, + {"no creds", ConnectionProps{URL: "h:61616"}, false}, + {"user only", ConnectionProps{URL: "h:61616", Username: "a"}, true}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - if got := buildConnectionURL(c.in); got != c.want { - t.Fatalf("got %q want %q", got, c.want) + opts := connOptions(c.in) + if got := opts != nil && opts.SASLType != nil; got != c.wantSet { + t.Fatalf("SASL set = %v, want %v", got, c.wantSet) } }) }