mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
fix: match AWS's error codes and messages for bad CIDRs, missing gateways and routes, the default group name and malformed IDs
This commit is contained in:
@@ -314,7 +314,7 @@ var (
|
||||
ErrorInvalidVpcPeeringConnectionIDNotFound = "InvalidVpcPeeringConnectionID.NotFound"
|
||||
ErrorInvalidVpcPeeringConnectionIdMalformed = "InvalidVpcPeeringConnectionId.Malformed"
|
||||
ErrorInvalidVpcPeeringConnectionStateDnsHostnamesDisabled = "InvalidVpcPeeringConnectionState.DnsHostnamesDisabled"
|
||||
ErrorInvalidVpcRange = "InvalidVpcRange"
|
||||
ErrorInvalidVpcRange = "InvalidVpc.Range"
|
||||
ErrorInvalidVpcState = "InvalidVpcState"
|
||||
ErrorInvalidVpnConnectionInvalidState = "InvalidVpnConnection.InvalidState"
|
||||
ErrorInvalidVpnConnectionInvalidType = "InvalidVpnConnection.InvalidType"
|
||||
@@ -1097,7 +1097,7 @@ var ErrorLookup = map[string]ErrorMessage{
|
||||
ErrorInvalidVpcPeeringConnectionIDNotFound: {HTTPCode: 404, Message: "The specified VPC peering connection ID does not exist."},
|
||||
ErrorInvalidVpcPeeringConnectionIdMalformed: {HTTPCode: 400, Message: "The specified VPC peering connection ID is malformed. Ensure that you provide the ID in the form pcx-xxxxxxxxxxxxxxxxx."},
|
||||
ErrorInvalidVpcPeeringConnectionStateDnsHostnamesDisabled: {HTTPCode: 400, Message: "To enable DNS hostname resolution for the VPC peering connection, DNS hostname support must be enabled for the VPCs."},
|
||||
ErrorInvalidVpcRange: {HTTPCode: 400, Message: "The specified CIDR block range is not valid. The block range must be between a /28 netmask and /16 netmask. For more information, see VPC CIDR blocks."},
|
||||
ErrorInvalidVpcRange: {HTTPCode: 400, Message: "The CIDR block is invalid."},
|
||||
ErrorInvalidVpcState: {HTTPCode: 400, Message: "The specified VPC already has a virtual private gateway attached to it."},
|
||||
ErrorInvalidVpnConnectionInvalidState: {HTTPCode: 400, Message: "The VPN connection must be in the available state to complete the request."},
|
||||
ErrorInvalidVpnConnectionInvalidType: {HTTPCode: 400, Message: "The specified VPN connection does not support static routes."},
|
||||
|
||||
@@ -2,6 +2,7 @@ package handlers_ec2_eigw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -167,6 +168,11 @@ func (s *EgressOnlyIGWServiceImpl) DescribeEgressOnlyInternetGateways(ctx contex
|
||||
slog.WarnContext(ctx, "DescribeEgressOnlyInternetGateways: invalid filter", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
for _, id := range input.EgressOnlyInternetGatewayIds {
|
||||
if id != nil && !eigwIDWellFormed(*id) {
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidEgressOnlyInternetGatewayIdMalformed, "The eigw ID %s is malformed", *id)
|
||||
}
|
||||
}
|
||||
|
||||
prefix := accountID + "."
|
||||
keys, err := s.eigwKV.Keys(ctx)
|
||||
@@ -212,6 +218,18 @@ func (s *EgressOnlyIGWServiceImpl) DescribeEgressOnlyInternetGateways(ctx contex
|
||||
}, nil
|
||||
}
|
||||
|
||||
// eigwIDWellFormed reports whether AWS accepts id as an egress-only gateway ID:
|
||||
// "eigw-" and exactly 17 hex digits of either case. A well-formed unknown ID is
|
||||
// not an error; the describe returns no gateway for it.
|
||||
func eigwIDWellFormed(id string) bool {
|
||||
suffix, ok := strings.CutPrefix(id, "eigw-")
|
||||
if !ok || len(suffix) != 17 {
|
||||
return false
|
||||
}
|
||||
_, err := hex.DecodeString("0" + suffix)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// eigwMatchesFilters checks whether an EgressOnlyIGWRecord satisfies all parsed filters.
|
||||
func eigwMatchesFilters(record *EgressOnlyIGWRecord, filters map[string][]string) bool {
|
||||
for name, values := range filters {
|
||||
|
||||
@@ -337,3 +337,24 @@ func TestCreateEgressOnlyInternetGateway_CrossAccountVPCRejected(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, out.EgressOnlyInternetGateway)
|
||||
}
|
||||
|
||||
func TestDescribeEgressOnlyInternetGateways_MalformedID(t *testing.T) {
|
||||
svc := setupTestEIGWService(t)
|
||||
describe := func(ids ...string) error {
|
||||
_, err := svc.DescribeEgressOnlyInternetGateways(context.Background(), &ec2.DescribeEgressOnlyInternetGatewaysInput{
|
||||
EgressOnlyInternetGatewayIds: aws.StringSlice(ids),
|
||||
}, testAccountID)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, id := range []string{"eigw-xyz", "eigw-12345", "eigw-0000000000000001", "eigw-000000000000000001", "foo"} {
|
||||
err := describe(id)
|
||||
code, msg, ok := awserrors.ResolveErrorDetail(err)
|
||||
require.True(t, ok, "error %v carries no AWS code", err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidEgressOnlyInternetGatewayIdMalformed, code)
|
||||
assert.Equal(t, "The eigw ID "+id+" is malformed", msg)
|
||||
}
|
||||
// Exactly 17 hex digits of either case is well formed, and unknown is not an error.
|
||||
assert.NoError(t, describe("eigw-00000000000000001"))
|
||||
assert.NoError(t, describe("eigw-0AAAAAAAAAAAAAAA1"))
|
||||
}
|
||||
|
||||
@@ -117,10 +117,11 @@ func TestEIP_AssociateByNetworkInterfaceID_NotFound(t *testing.T) {
|
||||
|
||||
_, err := svc.AssociateAddress(context.Background(), &ec2.AssociateAddressInput{
|
||||
AllocationId: aws.String(allocID),
|
||||
NetworkInterfaceId: aws.String("eni-doesnotexist"),
|
||||
NetworkInterfaceId: aws.String("eni-0000000000000dead"),
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidNetworkInterfaceIDNotFound, err.Error())
|
||||
code, _ := awserrors.ResolveErrorCode(err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidNetworkInterfaceIDNotFound, code)
|
||||
|
||||
// The allocation must survive a failed association.
|
||||
addr := describeEIP(t, svc, allocID)
|
||||
|
||||
@@ -685,6 +685,12 @@ func (s *RouteTableServiceImpl) DescribeRouteTables(ctx context.Context, input *
|
||||
}
|
||||
|
||||
// CreateRoute adds a route to a route table.
|
||||
// gatewayNotFoundError is AWS's answer to a route naming a gateway that does
|
||||
// not exist, whatever the gateway's type.
|
||||
func gatewayNotFoundError(id string) error {
|
||||
return awserrors.Errorf(awserrors.ErrorInvalidGatewayIDNotFound, "The gateway ID '%s' does not exist", id)
|
||||
}
|
||||
|
||||
func (s *RouteTableServiceImpl) CreateRoute(ctx context.Context, input *ec2.CreateRouteInput, accountID string) (*ec2.CreateRouteOutput, error) {
|
||||
if input.RouteTableId == nil || *input.RouteTableId == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
@@ -716,7 +722,7 @@ func (s *RouteTableServiceImpl) CreateRoute(ctx context.Context, input *ec2.Crea
|
||||
// Verify IGW exists and is attached to the same VPC
|
||||
igwEntry, err := s.igwKV.Get(ctx, utils.AccountKey(accountID, igwID))
|
||||
if err != nil {
|
||||
return nil, errors.New(awserrors.ErrorInvalidInternetGatewayIDNotFound)
|
||||
return nil, gatewayNotFoundError(igwID)
|
||||
}
|
||||
var igwRecord handlers_ec2_igw.IGWRecord
|
||||
if err := json.Unmarshal(igwEntry.Value(), &igwRecord); err != nil {
|
||||
@@ -868,6 +874,23 @@ func (s *RouteTableServiceImpl) ReplaceRoute(ctx context.Context, input *ec2.Rep
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// V1: only GatewayId target supported
|
||||
if input.GatewayId == nil || *input.GatewayId == "" {
|
||||
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
|
||||
}
|
||||
|
||||
igwID := *input.GatewayId
|
||||
|
||||
// AWS reports a missing gateway ahead of a missing route.
|
||||
igwEntry, err := s.igwKV.Get(ctx, utils.AccountKey(accountID, igwID))
|
||||
if err != nil {
|
||||
return nil, gatewayNotFoundError(igwID)
|
||||
}
|
||||
var igwRecord handlers_ec2_igw.IGWRecord
|
||||
if err := json.Unmarshal(igwEntry.Value(), &igwRecord); err != nil {
|
||||
return nil, errors.New(awserrors.ErrorServerInternal)
|
||||
}
|
||||
|
||||
idx := -1
|
||||
for i, r := range record.Routes {
|
||||
if r.DestinationCidrBlock == destCidr {
|
||||
@@ -876,29 +899,13 @@ func (s *RouteTableServiceImpl) ReplaceRoute(ctx context.Context, input *ec2.Rep
|
||||
}
|
||||
}
|
||||
if idx < 0 {
|
||||
return nil, errors.New(awserrors.ErrorInvalidRouteNotFound)
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidParameterValue,
|
||||
"There is no route defined for '%s' in the route table. Use CreateRoute instead.", destCidr)
|
||||
}
|
||||
|
||||
if record.Routes[idx].GatewayId == "local" {
|
||||
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
|
||||
}
|
||||
|
||||
// V1: only GatewayId target supported
|
||||
if input.GatewayId == nil || *input.GatewayId == "" {
|
||||
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
|
||||
}
|
||||
|
||||
igwID := *input.GatewayId
|
||||
|
||||
// Verify IGW exists and is attached to same VPC
|
||||
igwEntry, err := s.igwKV.Get(ctx, utils.AccountKey(accountID, igwID))
|
||||
if err != nil {
|
||||
return nil, errors.New(awserrors.ErrorInvalidInternetGatewayIDNotFound)
|
||||
}
|
||||
var igwRecord handlers_ec2_igw.IGWRecord
|
||||
if err := json.Unmarshal(igwEntry.Value(), &igwRecord); err != nil {
|
||||
return nil, errors.New(awserrors.ErrorServerInternal)
|
||||
}
|
||||
if igwRecord.VpcId != record.VpcId {
|
||||
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
|
||||
}
|
||||
|
||||
@@ -490,6 +490,49 @@ func TestReplaceRoute(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func requireAWSError(t *testing.T, err error, code, message string) {
|
||||
t.Helper()
|
||||
got, msg, ok := awserrors.ResolveErrorDetail(err)
|
||||
require.True(t, ok, "error %v carries no AWS code", err)
|
||||
assert.Equal(t, code, got)
|
||||
assert.Equal(t, message, msg)
|
||||
}
|
||||
|
||||
func TestCreateRoute_MissingGateway(t *testing.T) {
|
||||
t.Parallel()
|
||||
svc := setupTestService(t)
|
||||
rtbID := createTestRtb(t, svc)
|
||||
|
||||
_, err := svc.CreateRoute(t.Context(), &ec2.CreateRouteInput{
|
||||
RouteTableId: aws.String(rtbID),
|
||||
DestinationCidrBlock: aws.String("0.0.0.0/0"),
|
||||
GatewayId: aws.String("igw-0000000000000dead"),
|
||||
}, testAccountID)
|
||||
requireAWSError(t, err, awserrors.ErrorInvalidGatewayIDNotFound, "The gateway ID 'igw-0000000000000dead' does not exist")
|
||||
}
|
||||
|
||||
func TestReplaceRoute_MissingRouteOrGateway(t *testing.T) {
|
||||
t.Parallel()
|
||||
svc := setupTestService(t)
|
||||
rtbID := createTestRtb(t, svc)
|
||||
|
||||
_, err := svc.ReplaceRoute(t.Context(), &ec2.ReplaceRouteInput{
|
||||
RouteTableId: aws.String(rtbID),
|
||||
DestinationCidrBlock: aws.String("198.51.100.0/24"),
|
||||
GatewayId: aws.String("igw-test1"),
|
||||
}, testAccountID)
|
||||
requireAWSError(t, err, awserrors.ErrorInvalidParameterValue,
|
||||
"There is no route defined for '198.51.100.0/24' in the route table. Use CreateRoute instead.")
|
||||
|
||||
// AWS reports the missing gateway ahead of the missing route.
|
||||
_, err = svc.ReplaceRoute(t.Context(), &ec2.ReplaceRouteInput{
|
||||
RouteTableId: aws.String(rtbID),
|
||||
DestinationCidrBlock: aws.String("198.51.100.0/24"),
|
||||
GatewayId: aws.String("igw-0000000000000dead"),
|
||||
}, testAccountID)
|
||||
requireAWSError(t, err, awserrors.ErrorInvalidGatewayIDNotFound, "The gateway ID 'igw-0000000000000dead' does not exist")
|
||||
}
|
||||
|
||||
func TestAssociateRouteTable(t *testing.T) {
|
||||
t.Parallel()
|
||||
svc := setupTestService(t)
|
||||
|
||||
@@ -546,6 +546,9 @@ func (s *VPCServiceImpl) DescribeNetworkInterfaces(ctx context.Context, input *e
|
||||
eniIDs := make(map[string]bool)
|
||||
for _, id := range input.NetworkInterfaceIds {
|
||||
if id != nil {
|
||||
if err := eniIDMalformedError(*id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eniIDs[*id] = true
|
||||
}
|
||||
}
|
||||
@@ -594,9 +597,9 @@ func (s *VPCServiceImpl) DescribeNetworkInterfaces(ctx context.Context, input *e
|
||||
found[*eni.NetworkInterfaceId] = true
|
||||
}
|
||||
}
|
||||
for id := range eniIDs {
|
||||
if !found[id] {
|
||||
return nil, errors.New(awserrors.ErrorInvalidNetworkInterfaceIDNotFound)
|
||||
for _, id := range input.NetworkInterfaceIds {
|
||||
if id != nil && !found[*id] {
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidNetworkInterfaceIDNotFound, "The networkInterface ID '%s' does not exist", *id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,9 +413,30 @@ func TestDescribeNetworkInterfaces_NotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
svc := setupTestVPCService(t)
|
||||
_, err := svc.DescribeNetworkInterfaces(context.Background(), &ec2.DescribeNetworkInterfacesInput{
|
||||
NetworkInterfaceIds: []*string{aws.String("eni-nonexistent")},
|
||||
NetworkInterfaceIds: []*string{aws.String("eni-0000000000000dead")},
|
||||
}, testAccountID)
|
||||
assert.ErrorContains(t, err, "InvalidNetworkInterfaceID.NotFound")
|
||||
requireAWSError(t, err, awserrors.ErrorInvalidNetworkInterfaceIDNotFound, "The networkInterface ID 'eni-0000000000000dead' does not exist")
|
||||
}
|
||||
|
||||
func TestDescribeNetworkInterfaces_MalformedID(t *testing.T) {
|
||||
t.Parallel()
|
||||
svc := setupTestVPCService(t)
|
||||
describe := func(ids ...string) error {
|
||||
_, err := svc.DescribeNetworkInterfaces(context.Background(), &ec2.DescribeNetworkInterfacesInput{
|
||||
NetworkInterfaceIds: aws.StringSlice(ids),
|
||||
}, testAccountID)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, id := range []string{"eni-xyz", "eni-", "eni-ABC"} {
|
||||
requireAWSError(t, describe(id), awserrors.ErrorInvalidNetworkInterfaceIdMalformed, fmt.Sprintf("Invalid id: %q", id))
|
||||
}
|
||||
requireAWSError(t, describe("foo"), awserrors.ErrorInvalidNetworkInterfaceIdMalformed, `Invalid id: "foo" (expecting "eni-...")`)
|
||||
// Unlike a group ID, any length of lowercase hex is merely unknown.
|
||||
for _, id := range []string{"eni-12345", "eni-000000000000000000001"} {
|
||||
requireAWSCode(t, describe(id), awserrors.ErrorInvalidNetworkInterfaceIDNotFound)
|
||||
}
|
||||
requireAWSCode(t, describe("eni-0aaaaaaa", "eni-xyz"), awserrors.ErrorInvalidNetworkInterfaceIdMalformed)
|
||||
}
|
||||
|
||||
func TestAttachENI(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package handlers_ec2_vpc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
)
|
||||
|
||||
// isLowerHex reports whether s is non-empty and holds only lowercase hex digits.
|
||||
func isLowerHex(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, c := range s {
|
||||
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// malformedIDError is AWS's Invalid id answer for an ID that is not of the
|
||||
// form <prefix>-<suffix>, or whose suffix fails suffixOK.
|
||||
func malformedIDError(code, prefix, id string, suffixOK func(string) bool) error {
|
||||
suffix, ok := strings.CutPrefix(id, prefix+"-")
|
||||
if !ok {
|
||||
return awserrors.Errorf(code, "Invalid id: %q (expecting %q)", id, prefix+"-...")
|
||||
}
|
||||
if !suffixOK(suffix) {
|
||||
return awserrors.Errorf(code, "Invalid id: %q", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sgIDMalformedError returns AWS's error for a group ID it calls malformed, or
|
||||
// nil. AWS answers NotFound for up to 8 hex digits or exactly 17, the length
|
||||
// of every Spinifex ID; other lengths are Malformed.
|
||||
func sgIDMalformedError(id string) error {
|
||||
return malformedIDError(awserrors.ErrorInvalidGroupIdMalformed, "sg", id, func(s string) bool {
|
||||
return isLowerHex(s) && (len(s) <= 8 || len(s) == 17)
|
||||
})
|
||||
}
|
||||
|
||||
// eniIDMalformedError returns AWS's error for an interface ID it calls
|
||||
// malformed, or nil. AWS answers NotFound for lowercase hex of any length.
|
||||
func eniIDMalformedError(id string) error {
|
||||
return malformedIDError(awserrors.ErrorInvalidNetworkInterfaceIdMalformed, "eni", id, isLowerHex)
|
||||
}
|
||||
@@ -184,15 +184,14 @@ func (s *VPCServiceImpl) CreateSecurityGroup(ctx context.Context, input *ec2.Cre
|
||||
vpcId := *input.VpcId
|
||||
groupName := *input.GroupName
|
||||
|
||||
// "default" is reserved for the per-VPC default SG that CreateVpc
|
||||
// provisions internally. Matches AWS behavior.
|
||||
if groupName == defaultSecurityGroupName {
|
||||
return nil, errors.New(awserrors.ErrorInvalidGroupReserved)
|
||||
}
|
||||
|
||||
if err := s.requireVPCExists(ctx, accountID, vpcId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// "default" is reserved for the per-VPC default SG that CreateVpc
|
||||
// provisions internally. AWS checks the VPC first.
|
||||
if groupName == defaultSecurityGroupName {
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidParameterValue, "Cannot use reserved security group name: %s", groupName)
|
||||
}
|
||||
if strings.HasPrefix(groupName, "sg-") {
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidParameterValue,
|
||||
"Value (%s) for parameter GroupName is invalid. Group names may not be in the format sg-*.", groupName)
|
||||
@@ -487,6 +486,14 @@ func (s *VPCServiceImpl) DescribeSecurityGroups(ctx context.Context, input *ec2.
|
||||
slog.WarnContext(ctx, "DescribeSecurityGroups: invalid filter", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
for _, id := range input.GroupIds {
|
||||
if id == nil {
|
||||
continue
|
||||
}
|
||||
if err := sgIDMalformedError(*id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
prefix := accountID + "."
|
||||
keys, err := s.sgKV.Keys(ctx)
|
||||
@@ -533,9 +540,9 @@ func (s *VPCServiceImpl) DescribeSecurityGroups(ctx context.Context, input *ec2.
|
||||
found[*sg.GroupId] = true
|
||||
}
|
||||
}
|
||||
for id := range groupIDs {
|
||||
if !found[id] {
|
||||
return nil, errors.New(awserrors.ErrorInvalidGroupNotFound)
|
||||
for _, id := range input.GroupIds {
|
||||
if id != nil && !found[*id] {
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidGroupNotFound, "The security group '%s' does not exist", *id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,14 @@ func requireAWSCode(t *testing.T, err error, code string) {
|
||||
assert.Equal(t, code, got, "error: %v", err)
|
||||
}
|
||||
|
||||
func requireAWSError(t *testing.T, err error, code, message string) {
|
||||
t.Helper()
|
||||
got, msg, ok := awserrors.ResolveErrorDetail(err)
|
||||
require.True(t, ok, "error %v carries no AWS code", err)
|
||||
assert.Equal(t, code, got)
|
||||
assert.Equal(t, message, msg)
|
||||
}
|
||||
|
||||
func modifyRules(svc *VPCServiceImpl, sgID string, updates ...*ec2.SecurityGroupRuleUpdate) error {
|
||||
_, err := svc.ModifySecurityGroupRules(context.Background(), &ec2.ModifySecurityGroupRulesInput{
|
||||
GroupId: aws.String(sgID),
|
||||
|
||||
@@ -3,6 +3,7 @@ package handlers_ec2_vpc
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -177,10 +178,33 @@ func TestDescribeSecurityGroups_NotFound(t *testing.T) {
|
||||
svc := setupTestVPCService(t)
|
||||
|
||||
_, err := svc.DescribeSecurityGroups(context.Background(), &ec2.DescribeSecurityGroupsInput{
|
||||
GroupIds: []*string{aws.String("sg-nonexistent")},
|
||||
GroupIds: []*string{aws.String("sg-0000000000000dead")},
|
||||
}, testAccountID)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "InvalidGroup.NotFound")
|
||||
requireAWSError(t, err, awserrors.ErrorInvalidGroupNotFound, "The security group 'sg-0000000000000dead' does not exist")
|
||||
}
|
||||
|
||||
// The shapes below are the answers AWS gave for each ID; a 17-digit ID is
|
||||
// always treated as unknown here, though AWS decodes some of those as Malformed.
|
||||
func TestDescribeSecurityGroups_MalformedID(t *testing.T) {
|
||||
t.Parallel()
|
||||
svc := setupTestVPCService(t)
|
||||
describe := func(ids ...string) error {
|
||||
_, err := svc.DescribeSecurityGroups(context.Background(), &ec2.DescribeSecurityGroupsInput{
|
||||
GroupIds: aws.StringSlice(ids),
|
||||
}, testAccountID)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, id := range []string{"sg-xyz", "sg-", "sg-0AAAAAAAAAAAAAAA1", "sg-000000001", "sg-0000000000000001", "sg-000000000000000001"} {
|
||||
requireAWSError(t, describe(id), awserrors.ErrorInvalidGroupIdMalformed, fmt.Sprintf("Invalid id: %q", id))
|
||||
}
|
||||
requireAWSError(t, describe("foo"), awserrors.ErrorInvalidGroupIdMalformed, `Invalid id: "foo" (expecting "sg-...")`)
|
||||
for _, id := range []string{"sg-a", "sg-0aaaaaaa", "sg-01234567890abcdef"} {
|
||||
requireAWSCode(t, describe(id), awserrors.ErrorInvalidGroupNotFound)
|
||||
}
|
||||
|
||||
// A malformed ID wins over an unknown one, wherever it sits.
|
||||
requireAWSCode(t, describe("sg-0aaaaaaa", "sg-xyz"), awserrors.ErrorInvalidGroupIdMalformed)
|
||||
}
|
||||
|
||||
// --- AuthorizeSecurityGroupIngress ---
|
||||
@@ -1550,8 +1574,7 @@ func TestCreateSecurityGroup_RejectsReservedDefaultName(t *testing.T) {
|
||||
Description: aws.String("user-supplied"),
|
||||
VpcId: aws.String(vpcID),
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "InvalidGroup.Reserved")
|
||||
requireAWSError(t, err, awserrors.ErrorInvalidParameterValue, "Cannot use reserved security group name: default")
|
||||
}
|
||||
|
||||
func TestCreateSecurityGroup_RejectsSgPrefixedName(t *testing.T) {
|
||||
|
||||
@@ -255,22 +255,27 @@ func (s *VPCServiceImpl) nextVNI(ctx context.Context) (int64, error) {
|
||||
return allocated, nil
|
||||
}
|
||||
|
||||
// invalidCIDRBlockError is AWS's answer to a CidrBlock that does not parse.
|
||||
func invalidCIDRBlockError(cidr string) error {
|
||||
return awserrors.Errorf(awserrors.ErrorInvalidParameterValue,
|
||||
"Value (%s) for parameter cidrBlock is invalid. This is not a valid CIDR block.", cidr)
|
||||
}
|
||||
|
||||
// CreateVpc creates a new VPC.
|
||||
func (s *VPCServiceImpl) CreateVpc(ctx context.Context, input *ec2.CreateVpcInput, accountID string) (*ec2.CreateVpcOutput, error) {
|
||||
if input.CidrBlock == nil || *input.CidrBlock == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
|
||||
// Validate CIDR block
|
||||
_, ipNet, err := net.ParseCIDR(*input.CidrBlock)
|
||||
if err != nil {
|
||||
return nil, errors.New(awserrors.ErrorInvalidVpcRange)
|
||||
return nil, invalidCIDRBlockError(*input.CidrBlock)
|
||||
}
|
||||
|
||||
// AWS allows /16 to /28 for VPC CIDR blocks
|
||||
ones, _ := ipNet.Mask.Size()
|
||||
if ones < 16 || ones > 28 {
|
||||
return nil, errors.New(awserrors.ErrorInvalidVpcRange)
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidVpcRange, "The CIDR '%s' is invalid.", *input.CidrBlock)
|
||||
}
|
||||
|
||||
// Allocate VNI for overlay network
|
||||
@@ -684,6 +689,12 @@ func (s *VPCServiceImpl) CreateSubnet(ctx context.Context, input *ec2.CreateSubn
|
||||
|
||||
vpcID := *input.VpcId
|
||||
|
||||
// AWS rejects an unparseable CIDR before it looks the VPC up.
|
||||
_, subnetNet, err := net.ParseCIDR(*input.CidrBlock)
|
||||
if err != nil {
|
||||
return nil, invalidCIDRBlockError(*input.CidrBlock)
|
||||
}
|
||||
|
||||
// Verify VPC exists and belongs to this account
|
||||
vpcEntry, err := s.vpcKV.Get(ctx, utils.AccountKey(accountID, vpcID))
|
||||
if err != nil {
|
||||
@@ -694,11 +705,6 @@ func (s *VPCServiceImpl) CreateSubnet(ctx context.Context, input *ec2.CreateSubn
|
||||
if err := json.Unmarshal(vpcEntry.Value(), &vpcRecord); err != nil {
|
||||
return nil, errors.New(awserrors.ErrorServerInternal)
|
||||
}
|
||||
// Validate subnet CIDR
|
||||
_, subnetNet, err := net.ParseCIDR(*input.CidrBlock)
|
||||
if err != nil {
|
||||
return nil, errors.New(awserrors.ErrorInvalidSubnetRange)
|
||||
}
|
||||
|
||||
// AWS allows /16 to /28 for subnet CIDR blocks
|
||||
ones, _ := subnetNet.Mask.Size()
|
||||
|
||||
@@ -103,7 +103,8 @@ func TestCreateVpc_InvalidCidr(t *testing.T) {
|
||||
_, err := svc.CreateVpc(context.Background(), &ec2.CreateVpcInput{
|
||||
CidrBlock: aws.String("not-a-cidr"),
|
||||
}, testAccountID)
|
||||
assert.ErrorContains(t, err, "InvalidVpcRange")
|
||||
requireAWSError(t, err, awserrors.ErrorInvalidParameterValue,
|
||||
"Value (not-a-cidr) for parameter cidrBlock is invalid. This is not a valid CIDR block.")
|
||||
}
|
||||
|
||||
func TestCreateVpc_CidrTooLarge(t *testing.T) {
|
||||
@@ -112,7 +113,7 @@ func TestCreateVpc_CidrTooLarge(t *testing.T) {
|
||||
_, err := svc.CreateVpc(context.Background(), &ec2.CreateVpcInput{
|
||||
CidrBlock: aws.String("10.0.0.0/8"),
|
||||
}, testAccountID)
|
||||
assert.ErrorContains(t, err, "InvalidVpcRange")
|
||||
requireAWSError(t, err, "InvalidVpc.Range", "The CIDR '10.0.0.0/8' is invalid.")
|
||||
}
|
||||
|
||||
func TestCreateVpc_CidrTooSmall(t *testing.T) {
|
||||
@@ -121,7 +122,7 @@ func TestCreateVpc_CidrTooSmall(t *testing.T) {
|
||||
_, err := svc.CreateVpc(context.Background(), &ec2.CreateVpcInput{
|
||||
CidrBlock: aws.String("10.0.0.0/29"),
|
||||
}, testAccountID)
|
||||
assert.ErrorContains(t, err, "InvalidVpcRange")
|
||||
requireAWSError(t, err, "InvalidVpc.Range", "The CIDR '10.0.0.0/29' is invalid.")
|
||||
}
|
||||
|
||||
func TestCreateVpc_WithTags(t *testing.T) {
|
||||
@@ -362,7 +363,15 @@ func TestCreateSubnet_InvalidCidr(t *testing.T) {
|
||||
VpcId: aws.String(vpcID),
|
||||
CidrBlock: aws.String("not-a-cidr"),
|
||||
}, testAccountID)
|
||||
assert.ErrorContains(t, err, "InvalidSubnet.Range")
|
||||
requireAWSError(t, err, awserrors.ErrorInvalidParameterValue,
|
||||
"Value (not-a-cidr) for parameter cidrBlock is invalid. This is not a valid CIDR block.")
|
||||
|
||||
// AWS rejects the CIDR before it looks the VPC up.
|
||||
_, err = svc.CreateSubnet(context.Background(), &ec2.CreateSubnetInput{
|
||||
VpcId: aws.String("vpc-nonexistent"),
|
||||
CidrBlock: aws.String("not-a-cidr"),
|
||||
}, testAccountID)
|
||||
requireAWSCode(t, err, awserrors.ErrorInvalidParameterValue)
|
||||
}
|
||||
|
||||
func TestCreateSubnet_OutsideVpcCidr(t *testing.T) {
|
||||
|
||||
@@ -390,7 +390,7 @@ func TestReplaceRouteConvergence(t *testing.T) {
|
||||
DestinationCidrBlock: aws.String("198.51.100.0/24"),
|
||||
GatewayId: aws.String(igw2),
|
||||
})
|
||||
requireAWSErrorCode(t, err, "InvalidRoute.NotFound")
|
||||
requireAWSErrorCode(t, err, "InvalidParameterValue")
|
||||
|
||||
// Local route is immutable.
|
||||
_, err = c.ReplaceRoute(&ec2.ReplaceRouteInput{
|
||||
@@ -413,7 +413,7 @@ func TestReplaceRouteConvergence(t *testing.T) {
|
||||
DestinationCidrBlock: aws.String(dest),
|
||||
GatewayId: aws.String("igw-00000000000000000"),
|
||||
})
|
||||
requireAWSErrorCode(t, err, "InvalidInternetGatewayID.NotFound")
|
||||
requireAWSErrorCode(t, err, "InvalidGatewayID.NotFound")
|
||||
|
||||
// IGW attached to a different VPC than the route table.
|
||||
_, err = c.ReplaceRoute(&ec2.ReplaceRouteInput{
|
||||
|
||||
Reference in New Issue
Block a user