Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 68 additions & 3 deletions pkg/aws/services/vpclattice.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,33 +366,51 @@ func (d *defaultLattice) FindServiceNetwork(ctx context.Context, nameOrId string
nameOrId = config.DefaultServiceNetwork
}

// Step 1: Try to find in local (owned) service networks
input := &vpclattice.ListServiceNetworksInput{}
allSn, err := d.ListServiceNetworksAsList(ctx, input)
if err != nil {
return nil, err
}

snMatch, err := d.serviceNetworkMatch(allSn, nameOrId)
if err != nil {

// If found locally, return it with tags
if err == nil {
return d.buildServiceNetworkInfo(ctx, snMatch)
}

// If error is not "not found", return the error
if !errors.Is(err, ErrNotFound) {
return nil, err
}

// try to fetch tags only if SN in the same aws account with controller's config
// Step 2: Not found locally, try RAM-shared networks via VPC associations
return d.findServiceNetworkViaVPCAssociation(ctx, nameOrId)
}

// buildServiceNetworkInfo constructs ServiceNetworkInfo from a matched
// service network, attempting to fetch tags if the network is local.
func (d *defaultLattice) buildServiceNetworkInfo(ctx context.Context, snMatch *vpclattice.ServiceNetworkSummary) (*ServiceNetworkInfo, error) {
tags := Tags{}

// Try to fetch tags only if SN is in the same AWS account
isLocal, err := d.isLocalResource(aws.StringValue(snMatch.Arn))
if err != nil {
return nil, err
}

if isLocal {
tagsInput := vpclattice.ListTagsForResourceInput{ResourceArn: snMatch.Arn}
tagsOutput, err := d.ListTagsForResourceWithContext(ctx, &tagsInput)
if err != nil {
aerr, ok := err.(awserr.Error)
// In case ownAccount is not set, we cant tell if SN is foreign.
// In case ownAccount is not set, we can't tell if SN is foreign.
// In this case access denied is expected.
if !ok || aerr.Code() != vpclattice.ErrCodeAccessDeniedException {
return nil, err
}
// If access denied, proceed without tags
} else {
tags = tagsOutput.Tags
}
Expand All @@ -404,6 +422,53 @@ func (d *defaultLattice) FindServiceNetwork(ctx context.Context, nameOrId string
}, nil
}

// findServiceNetworkViaVPCAssociation attempts to find a service network
// by examining VPC associations. This is used to discover RAM-shared
// service networks that don't appear in ListServiceNetworks.
func (d *defaultLattice) findServiceNetworkViaVPCAssociation(ctx context.Context, nameOrId string) (*ServiceNetworkInfo, error) {
// List all VPC-to-Service Network associations for the controller's VPC
associations, err := d.ListServiceNetworkVpcAssociationsAsList(ctx,
&vpclattice.ListServiceNetworkVpcAssociationsInput{
VpcIdentifier: aws.String(config.VpcID),
})
if err != nil {
return nil, fmt.Errorf("failed to list VPC associations while searching for service network %s: %w", nameOrId, err)
}

// Find matching service network by name or ID
var matches []*vpclattice.ServiceNetworkVpcAssociationSummary
for _, assoc := range associations {
// Only consider active associations
if aws.StringValue(assoc.Status) != vpclattice.ServiceNetworkVpcAssociationStatusActive {
continue
}

if aws.StringValue(assoc.ServiceNetworkName) == nameOrId ||
aws.StringValue(assoc.ServiceNetworkId) == nameOrId {
matches = append(matches, assoc)
}
}

switch len(matches) {
case 0:
return nil, NewNotFoundError("Service network", nameOrId)
case 1:
assoc := matches[0]
return &ServiceNetworkInfo{
SvcNetwork: vpclattice.ServiceNetworkSummary{
Id: assoc.ServiceNetworkId,
Arn: assoc.ServiceNetworkArn,
Name: assoc.ServiceNetworkName,
},
Tags: nil, // Cannot read tags for cross-account resources
}, nil
default:
// Multiple matches - this shouldn't happen but handle defensively
return nil, fmt.Errorf("%w, multiple VPC associations found for service network %s",
ErrNameConflict, nameOrId)
}
}

// see utils.LatticeServiceName
func (d *defaultLattice) FindService(ctx context.Context, latticeServiceName string) (*vpclattice.ServiceSummary, error) {
input := vpclattice.ListServicesInput{}
Expand Down
56 changes: 48 additions & 8 deletions pkg/deploy/lattice/service_network_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/vpclattice"

pkg_aws "github.com/aws/aws-application-networking-k8s/pkg/aws"
Expand Down Expand Up @@ -52,18 +53,35 @@ func (m *defaultServiceNetworkManager) UpsertVpcAssociation(ctx context.Context,
}
if snva != nil {
// association is active
owned, err := m.cloud.TryOwn(ctx, *snva.Arn)
// Check if this is a RAM-shared network by examining the service network ARN
isLocal, err := m.isLocalServiceNetwork(sn.SvcNetwork.Arn)
if err != nil {
return "", err
}
if !owned {
return "", services.NewConflictError("snva", snName,
fmt.Sprintf("Found existing vpc association not owned by controller: %s", *snva.Arn))
}
_, err = m.updateServiceNetworkVpcAssociation(ctx, &sn.SvcNetwork, sgIds, snva.Id, additionalTags)
if err != nil {
return "", err

if isLocal {
// For local networks, check ownership as before
owned, err := m.cloud.TryOwn(ctx, *snva.Arn)
if err != nil {
return "", err
}
if !owned {
return "", services.NewConflictError("snva", snName,
fmt.Sprintf("Found existing vpc association not owned by controller: %s", *snva.Arn))
}

// Update if needed
_, err = m.updateServiceNetworkVpcAssociation(ctx, &sn.SvcNetwork, sgIds, snva.Id, additionalTags)
if err != nil {
return "", err
}
} else {
// For RAM-shared networks, we can't modify the association
// Just return the existing ARN and log
m.log.Infof(ctx, "Using existing VPC association for RAM-shared service network %s: %s",
snName, *snva.Arn)
}

return *snva.Arn, nil
} else {
tags := m.cloud.MergeTags(m.cloud.DefaultTags(), additionalTags)
Expand All @@ -87,6 +105,28 @@ func (m *defaultServiceNetworkManager) UpsertVpcAssociation(ctx context.Context,
}
}

// isLocalServiceNetwork determines if a service network belongs to the current AWS account
func (m *defaultServiceNetworkManager) isLocalServiceNetwork(arnStr *string) (bool, error) {
if arnStr == nil {
return false, fmt.Errorf("service network ARN is nil")
}

parsedArn, err := arn.Parse(*arnStr)
if err != nil {
return false, fmt.Errorf("failed to parse service network ARN %s: %w", *arnStr, err)
}

// Compare with controller's account
controllerAccount := m.cloud.Config().AccountId
if controllerAccount == "" {
// If controller account is not set, assume it's local (backward compatibility)
m.log.Debugf(context.Background(), "Controller account ID not set, assuming service network %s is local", *arnStr)
return true, nil
}

return parsedArn.AccountID == controllerAccount, nil
}

func (m *defaultServiceNetworkManager) DeleteVpcAssociation(ctx context.Context, snName string) error {
sn, err := m.cloud.Lattice().FindServiceNetwork(ctx, snName)
if err != nil {
Expand Down
Loading