refactor: migrate lbagent signing to predastore/auth.SignReq (#281)

agent.go signed gateway requests with aws-sdk-go v1's v4.Signer. Switch to
predastore/auth.SignReq (a thin wrapper over aws-sdk-go-v2's v4.Signer) so the
monorepo runs a single signer version and drops the last direct dependency on
aws-sdk-go v1's signer subpackage.

- Replace the *v4.Signer field with the raw access/secret keys.
- signedPost computes the hex SHA-256 payload hash and calls auth.SignReq.
- Add TestSignedPost_ProducesVerifiableSignature: the signed request parses and
  verifies server-side via auth.ParseReq/Verify with a matching body hash.
This commit is contained in:
Josh Siviour
2026-06-03 20:17:55 +10:00
committed by GitHub
parent de5ee4339a
commit b3f7251239
2 changed files with 64 additions and 10 deletions
+11 -10
View File
@@ -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)
}
+53
View File
@@ -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()