diff --git a/spinifex/lbagent/agent.go b/spinifex/lbagent/agent.go index 4fffb401b..f2e409643 100644 --- a/spinifex/lbagent/agent.go +++ b/spinifex/lbagent/agent.go @@ -5,7 +5,9 @@ package lbagent import ( "bytes" + "crypto/sha256" "crypto/tls" + "encoding/hex" "encoding/xml" "fmt" "io" @@ -20,8 +22,7 @@ import ( "syscall" "time" - "github.com/aws/aws-sdk-go/aws/credentials" - v4 "github.com/aws/aws-sdk-go/aws/signer/v4" + "github.com/mulgadc/predastore/auth" "github.com/mulgadc/spinifex/internal/tlsconfig" ) @@ -43,8 +44,9 @@ type Agent struct { pidPath string socketPath string // HAProxy stats socket - signer *v4.Signer - client *http.Client + accessKey string + secretKey string + client *http.Client localConfigHash string stopCh chan struct{} @@ -70,9 +72,6 @@ func New(lbID, gatewayURL, accessKey, secretKey, region string) (*Agent, error) return nil, fmt.Errorf("region is required") } - creds := credentials.NewStaticCredentials(accessKey, secretKey, "") - signer := v4.NewSigner(creds) - // Use system CA trust store (CA cert injected via cloud-init ca_certs). client := &http.Client{ Timeout: 10 * time.Second, @@ -93,7 +92,8 @@ func New(lbID, gatewayURL, accessKey, secretKey, region string) (*Agent, error) configPath: DefaultConfigPath, pidPath: DefaultPIDPath, socketPath: fmt.Sprintf("/tmp/spinifex-haproxy/lb-%s.sock", lbID), - signer: signer, + accessKey: accessKey, + secretKey: secretKey, client: client, stopCh: make(chan struct{}), reloadFn: reloadHAProxy, @@ -238,8 +238,9 @@ func (a *Agent) signedPost(params url.Values) ([]byte, error) { } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - _, err = a.signer.Sign(req, bytes.NewReader([]byte(body)), "elasticloadbalancing", a.region, time.Now()) - if err != nil { + sum := sha256.Sum256([]byte(body)) + payloadHash := hex.EncodeToString(sum[:]) + if err := auth.SignReq(req, a.accessKey, a.secretKey, payloadHash, "elasticloadbalancing", a.region); err != nil { return nil, fmt.Errorf("sign request: %w", err) } diff --git a/spinifex/lbagent/agent_test.go b/spinifex/lbagent/agent_test.go index 54540087e..2129acb7c 100644 --- a/spinifex/lbagent/agent_test.go +++ b/spinifex/lbagent/agent_test.go @@ -1,14 +1,20 @@ package lbagent import ( + "crypto/sha256" + "encoding/hex" "fmt" + "io" "net/http" "net/http/httptest" + "net/url" "os" "path/filepath" "sync/atomic" "testing" "time" + + "github.com/mulgadc/predastore/auth" ) func TestNew(t *testing.T) { @@ -68,6 +74,53 @@ func TestNew_SocketPath(t *testing.T) { } } +// TestSignedPost_ProducesVerifiableSignature confirms the migrated signing path +// (predastore/auth.SignReq over aws-sdk-go-v2) produces a request the gateway's +// SigV4 verifier accepts, including a body-hash that matches X-Amz-Content-Sha256. +func TestSignedPost_ProducesVerifiableSignature(t *testing.T) { + const ( + accessKey = "AKIAIOSFODNN7EXAMPLE" + secretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + region = "us-east-1" + ) + + var parsed bool + var verifyErr error + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + sum := sha256.Sum256(body) + req, err := auth.ParseReq(r) + if err != nil { + verifyErr = err + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + parsed = true + verifyErr = req.Verify(secretKey, "elasticloadbalancing", region, + auth.WithBodyHash(hex.EncodeToString(sum[:]))) + fmt.Fprint(w, "ok") + })) + defer srv.Close() + + agent, err := New("lb-sig", srv.URL, accessKey, secretKey, region) + if err != nil { + t.Fatalf("New: %v", err) + } + + if _, err := agent.signedPost(url.Values{ + "Action": {"LBAgentHeartbeat"}, + "LoadBalancerId": {"lb-sig"}, + }); err != nil { + t.Fatalf("signedPost: %v", err) + } + if !parsed { + t.Fatal("gateway did not parse a SigV4 Authorization header") + } + if verifyErr != nil { + t.Fatalf("signed request failed server-side verification: %v", verifyErr) + } +} + // fakeGateway returns a test server that responds to LBAgentHeartbeat and GetLBConfig. func fakeGateway(t *testing.T, configHash, configText, status string) *httptest.Server { t.Helper()