refactor: move AWS Query parser to ingress

This commit is contained in:
joshsiv-mulga
2026-09-29 09:37:44 +10:00
parent 7a5b90d11a
commit caaf98d9ea
15 changed files with 46 additions and 44 deletions
+3 -3
View File
@@ -13,7 +13,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
gateway_ec2 "github.com/mulgadc/spinifex/spinifex/gateway/ec2"
gateway_ec2_account "github.com/mulgadc/spinifex/spinifex/gateway/ec2/account"
@@ -76,7 +76,7 @@ func requestContext(r *http.Request) context.Context {
// ec2Action constructor so they all consume the same parse.
func parseEC2Input[In any](q map[string]string) (any, error) {
input := new(In)
if err := awsec2query.QueryParamsToStruct(q, input); err != nil {
if err := query.QueryParamsToStruct(q, input); err != nil {
return nil, err
}
return input, nil
@@ -655,7 +655,7 @@ func (gw *GatewayConfig) EC2_Request(w http.ResponseWriter, r *http.Request) err
input, err := handler.parse(queryArgs)
if err != nil {
if errors.Is(err, awsec2query.ErrSliceTooLarge) {
if errors.Is(err, query.ErrSliceTooLarge) {
return errors.New(awserrors.ErrorMalformedQueryString)
}
return err
+4 -4
View File
@@ -9,7 +9,7 @@ import (
"slices"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
)
@@ -333,7 +333,7 @@ func ResourceARNs(action, region, accountID string, input any) ([]string, error)
}
seen[resource] = struct{}{}
resources = append(resources, resource)
if len(resources) > awsec2query.MaxSliceLen {
if len(resources) > query.MaxSliceLen {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
}
@@ -360,7 +360,7 @@ func (s *resourceScope) resolve(region, accountID string, input any) ([]string,
}
return []string{anyResource}, nil
}
if len(ids) > awsec2query.MaxSliceLen {
if len(ids) > query.MaxSliceLen {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
@@ -384,7 +384,7 @@ func (s *resourceScope) resolve(region, accountID string, input any) ([]string,
// precedence. Values are deduplicated before policy evaluation.
func (s *resourceScope) identifiers(input any) []string {
for _, path := range s.paths {
values := awsec2query.StringValuesAt(input, path)
values := query.StringValuesAt(input, path)
seen := make(map[string]struct{}, len(values))
ids := make([]string, 0, len(values))
for _, value := range values {
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"testing"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -29,7 +29,7 @@ func resolve(t *testing.T, action string, q map[string]string) []string {
var input any
if prototype, ok := ec2Inputs[action]; ok {
input = reflect.New(reflect.TypeOf(prototype).Elem()).Interface()
require.NoError(t, awsec2query.QueryParamsToStruct(q, input))
require.NoError(t, query.QueryParamsToStruct(q, input))
}
got, err := ResourceARNs(action, testRegion, testAccountID, input)
require.NoError(t, err)
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"strings"
"testing"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
"github.com/stretchr/testify/assert"
@@ -224,7 +224,7 @@ func TestEC2Request_ModifySecurityGroupRulesIsGroupScoped(t *testing.T) {
func TestEC2Request_RejectsOversizedResourceList(t *testing.T) {
var body strings.Builder
body.WriteString("Action=TerminateInstances")
for i := 1; i <= awsec2query.MaxSliceLen+1; i++ {
for i := 1; i <= query.MaxSliceLen+1; i++ {
body.WriteString("&InstanceId.")
body.WriteString(strconv.Itoa(i))
body.WriteString("=i-")
+3 -3
View File
@@ -8,7 +8,7 @@ import (
"slices"
"strings"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/mulgadc/spinifex/spinifex/gateway/bodyscope"
)
@@ -143,7 +143,7 @@ func ResourceARNs(action, region, accountID string, body []byte) ([]string, erro
if _, duplicate := seen[resource]; duplicate {
continue
}
if len(resources) >= awsec2query.MaxSliceLen {
if len(resources) >= query.MaxSliceLen {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
seen[resource] = struct{}{}
@@ -172,7 +172,7 @@ func resolve(source resourceSource, action, region, accountID string, scope body
}
// Capped so a body-supplied list cannot make the gate do unbounded work
// ahead of the authorization decision.
if len(names) > awsec2query.MaxSliceLen {
if len(names) > query.MaxSliceLen {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
out := make([]string, 0, len(names))
+4 -4
View File
@@ -5,7 +5,7 @@ import (
"strconv"
"testing"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
gateway_ecrapi "github.com/mulgadc/spinifex/spinifex/gateway/ecrapi"
"github.com/stretchr/testify/assert"
@@ -113,13 +113,13 @@ func TestResourceARNs_OversizedListIsRejected(t *testing.T) {
}
_, err := gateway_ecrapi.ResourceARNs("DescribeRepositories", testRegion, testAccountID,
namesBody(awsec2query.MaxSliceLen+1))
namesBody(query.MaxSliceLen+1))
require.EqualError(t, err, awserrors.ErrorMalformedQueryString)
resources, err := gateway_ecrapi.ResourceARNs("DescribeRepositories", testRegion, testAccountID,
namesBody(awsec2query.MaxSliceLen))
namesBody(query.MaxSliceLen))
require.NoError(t, err)
assert.Len(t, resources, awsec2query.MaxSliceLen)
assert.Len(t, resources, query.MaxSliceLen)
}
// encoding/json resolves two spellings of one field in document order when the
+3 -3
View File
@@ -6,7 +6,7 @@ import (
"maps"
"slices"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/mulgadc/spinifex/spinifex/gateway/bodyscope"
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
@@ -164,7 +164,7 @@ func ResourceARNs(action, region, accountID string, body []byte) ([]string, erro
if _, duplicate := seen[resource]; duplicate {
continue
}
if len(resources) >= awsec2query.MaxSliceLen {
if len(resources) >= query.MaxSliceLen {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
seen[resource] = struct{}{}
@@ -266,7 +266,7 @@ func one(resource string) []string {
// each builds one ARN per reference, capped so a body-supplied list cannot make
// the gate do unbounded work ahead of the authorization decision.
func each(refs []string, build func(string) string) ([]string, error) {
if len(refs) > awsec2query.MaxSliceLen {
if len(refs) > query.MaxSliceLen {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
out := make([]string, 0, len(refs))
+4 -4
View File
@@ -5,7 +5,7 @@ import (
"strconv"
"testing"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
gateway_ecs "github.com/mulgadc/spinifex/spinifex/gateway/ecs"
"github.com/stretchr/testify/assert"
@@ -172,13 +172,13 @@ func TestResourceARNs_OversizedListIsRejected(t *testing.T) {
}
_, err := gateway_ecs.ResourceARNs("DescribeClusters", testRegion, testAccountID,
clustersBody(awsec2query.MaxSliceLen+1))
clustersBody(query.MaxSliceLen+1))
require.EqualError(t, err, awserrors.ErrorMalformedQueryString)
resources, err := gateway_ecs.ResourceARNs("DescribeClusters", testRegion, testAccountID,
clustersBody(awsec2query.MaxSliceLen))
clustersBody(query.MaxSliceLen))
require.NoError(t, err)
assert.Len(t, resources, awsec2query.MaxSliceLen)
assert.Len(t, resources, query.MaxSliceLen)
}
// encoding/json resolves two spellings of one field in document order when the
+3 -3
View File
@@ -7,7 +7,7 @@ import (
"net/http"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
gateway_elbv2 "github.com/mulgadc/spinifex/spinifex/gateway/elbv2"
handlers_elbv2 "github.com/mulgadc/spinifex/spinifex/handlers/elbv2"
@@ -27,7 +27,7 @@ func elbv2Handler[In any](handler func(context.Context, *In, *GatewayConfig, str
return elbv2Action{
parse: func(q map[string]string) (any, error) {
input := new(In)
if err := awsec2query.QueryParamsToStruct(q, input); err != nil {
if err := query.QueryParamsToStruct(q, input); err != nil {
return nil, err
}
return input, nil
@@ -222,7 +222,7 @@ func (gw *GatewayConfig) ELBv2_Request(w http.ResponseWriter, r *http.Request) e
input, err := handler.parse(queryArgs)
if err != nil {
if errors.Is(err, awsec2query.ErrSliceTooLarge) {
if errors.Is(err, query.ErrSliceTooLarge) {
return errors.New(awserrors.ErrorMalformedQueryString)
}
return err
+5 -5
View File
@@ -8,7 +8,7 @@ import (
"strings"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
)
@@ -147,7 +147,7 @@ func ResourceARNs(action, region, accountID string, input any) ([]string, error)
if _, duplicate := seen[resource]; duplicate {
continue
}
if len(resources) >= awsec2query.MaxSliceLen {
if len(resources) >= query.MaxSliceLen {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
seen[resource] = struct{}{}
@@ -223,13 +223,13 @@ func resolve(source resourceSource, action, region, accountID string, input any)
func suppliedARNs(region, accountID string, input any, paths ...string) ([]string, error) {
var resources []string
for _, path := range paths {
for _, value := range awsec2query.StringValuesAt(input, path) {
for _, value := range query.StringValuesAt(input, path) {
if value == "" {
continue
}
// A nested list can fan out past what any single list may hold, so the
// bound is asserted here rather than inherited from the parser.
if len(resources) >= awsec2query.MaxSliceLen {
if len(resources) >= query.MaxSliceLen {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
if err := checkARN(value, region, accountID); err != nil {
@@ -287,7 +287,7 @@ func arnError(value, why string) error {
}
func firstValue(input any, path string) string {
for _, value := range awsec2query.StringValuesAt(input, path) {
for _, value := range query.StringValuesAt(input, path) {
if value != "" {
return value
}
+3 -3
View File
@@ -7,7 +7,7 @@ import (
"net/http"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
gateway_ec2_instance "github.com/mulgadc/spinifex/spinifex/gateway/ec2/instance"
gateway_iam "github.com/mulgadc/spinifex/spinifex/gateway/iam"
@@ -26,8 +26,8 @@ func iamHandler[In any](handler func(string, *In, *GatewayConfig) (any, error))
return iamAction{
parse: func(q map[string]string) (any, error) {
input := new(In)
if err := awsec2query.QueryParamsToStruct(q, input); err != nil {
if errors.Is(err, awsec2query.ErrSliceTooLarge) {
if err := query.QueryParamsToStruct(q, input); err != nil {
if errors.Is(err, query.ErrSliceTooLarge) {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
return nil, awserrors.Errorf(awserrors.ErrorIAMInvalidInput, "The request parameters are invalid: %v", err)
+3 -3
View File
@@ -9,7 +9,7 @@ import (
"log/slog"
"sort"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/mulgadc/spinifex/spinifex/utils"
"github.com/nats-io/nats.go"
@@ -50,10 +50,10 @@ type Handler func(ctx context.Context, action string, q map[string]string, nc *n
func typedEnv[In any](handler func(context.Context, *In, *nats.Conn, Caller, Env) (any, error)) Handler {
return func(ctx context.Context, action string, q map[string]string, nc *nats.Conn, caller Caller, env Env) ([]byte, error) {
input := new(In)
if err := awsec2query.QueryParamsToStruct(q, input); err != nil {
if err := query.QueryParamsToStruct(q, input); err != nil {
// An over-long indexed list is a client-side malformation, not an
// internal failure, so it keeps its own error code.
if errors.Is(err, awsec2query.ErrSliceTooLarge) {
if errors.Is(err, query.ErrSliceTooLarge) {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
+3 -3
View File
@@ -8,7 +8,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/sts"
spxarn "github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/ingress/aws/query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
gateway_sts "github.com/mulgadc/spinifex/spinifex/gateway/sts"
handlers_sts "github.com/mulgadc/spinifex/spinifex/handlers/sts"
@@ -36,8 +36,8 @@ type STSHandler func(action string, q map[string]string, gw *GatewayConfig, c st
func stsHandler[In any](handler func(c stsCaller, input *In, gw *GatewayConfig) (any, error)) STSHandler {
return func(action string, q map[string]string, gw *GatewayConfig, c stsCaller) ([]byte, error) {
input := new(In)
if err := awsec2query.QueryParamsToStruct(q, input); err != nil {
if errors.Is(err, awsec2query.ErrSliceTooLarge) {
if err := query.QueryParamsToStruct(q, input); err != nil {
if errors.Is(err, query.ErrSliceTooLarge) {
return nil, errors.New(awserrors.ErrorMalformedQueryString)
}
return nil, errors.New(awserrors.ErrorValidationError)
@@ -1,4 +1,6 @@
package awsec2query
// Package query decodes AWS Query protocol parameters into SDK request shapes.
// It is protocol plumbing and owns no service action or resource semantics.
package query
import (
"encoding/base64"
@@ -1,4 +1,4 @@
package awsec2query
package query
import (
"fmt"