Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
716052077b |
No files matched your search
@@ -16,6 +16,36 @@ boundary host-catalogs create plugin \
|
|||||||
-secret secret_access_key='SECRET'
|
-secret secret_access_key='SECRET'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To create a host catalog that returns addresses only from the primary network
|
||||||
|
interface:
|
||||||
|
|
||||||
|
```
|
||||||
|
boundary host-catalogs create plugin \
|
||||||
|
-scope-id p_1234567890 \
|
||||||
|
-name "Example Plugin-Based Host Catalog" \
|
||||||
|
-description "Description for plugin-based host catalog" \
|
||||||
|
-plugin-name aws \
|
||||||
|
-attr region=REGION \
|
||||||
|
-attr primary_interface_only=true \
|
||||||
|
-secret access_key_id='KEY' \
|
||||||
|
-secret secret_access_key='SECRET'
|
||||||
|
```
|
||||||
|
|
||||||
|
To create a host catalog that excludes public IPv4 addresses while keeping
|
||||||
|
private IPv4 addresses:
|
||||||
|
|
||||||
|
```
|
||||||
|
boundary host-catalogs create plugin \
|
||||||
|
-scope-id p_1234567890 \
|
||||||
|
-name "Example Plugin-Based Host Catalog" \
|
||||||
|
-description "Description for plugin-based host catalog" \
|
||||||
|
-plugin-name aws \
|
||||||
|
-attr region=REGION \
|
||||||
|
-attr exclude_public_ips=true \
|
||||||
|
-secret access_key_id='KEY' \
|
||||||
|
-secret secret_access_key='SECRET'
|
||||||
|
```
|
||||||
|
|
||||||
To create a host set, filtering the host set based on tag keys `foo` or `bar`
|
To create a host set, filtering the host set based on tag keys `foo` or `bar`
|
||||||
(either tag can be present), ensuring that any targets set to this host set only
|
(either tag can be present), ensuring that any targets set to this host set only
|
||||||
connect to external addresses in the `54.0.0.0/8` class A subnet:
|
connect to external addresses in the `54.0.0.0/8` class A subnet:
|
||||||
@@ -75,7 +105,6 @@ a configured IAM user for this provider:
|
|||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* `iam:GetUser`, `iam:CreateAccessKey`, and `iam:DeleteAccessKey`, configured to
|
* `iam:GetUser`, `iam:CreateAccessKey`, and `iam:DeleteAccessKey`, configured to
|
||||||
@@ -116,6 +145,16 @@ The following `attributes` are valid on an AWS host catalog resource:
|
|||||||
provider.
|
provider.
|
||||||
- `role_tags` (object): The key-value pair tags configured for the `AssumeRole`
|
- `role_tags` (object): The key-value pair tags configured for the `AssumeRole`
|
||||||
provider.
|
provider.
|
||||||
|
- `primary_interface_only` (bool): If `true`, only addresses from the primary
|
||||||
|
ENI are returned. When `false` or omitted, addresses from all attached ENIs
|
||||||
|
are considered.
|
||||||
|
- `exclude_private_ips` (bool): If `true`, private IPv4 addresses are omitted.
|
||||||
|
- `exclude_public_ips` (bool): If `true`, public IPv4 addresses are omitted.
|
||||||
|
|
||||||
|
If none of the exclusion flags are set, the plugin preserves the historical
|
||||||
|
behavior and returns all discovered address classes. The exclusion flags apply
|
||||||
|
to both IPv4 and IPv6 addresses based on whether the IP is classified as public
|
||||||
|
or private.
|
||||||
|
|
||||||
The following `secrets` are required on an AWS host catalog resource:
|
The following `secrets` are required on an AWS host catalog resource:
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,18 @@ type CatalogAttributes struct {
|
|||||||
|
|
||||||
// DualStack is used for configuring how the aws client will resolve requests.
|
// DualStack is used for configuring how the aws client will resolve requests.
|
||||||
DualStack bool
|
DualStack bool
|
||||||
|
|
||||||
|
// PrimaryInterfaceOnly restricts discovery to the instance's primary ENI.
|
||||||
|
PrimaryInterfaceOnly bool
|
||||||
|
|
||||||
|
// ExcludePrivateIps controls whether private IPv4 addresses are omitted.
|
||||||
|
ExcludePrivateIps bool
|
||||||
|
|
||||||
|
// ExcludePublicIps controls whether public IPv4 addresses are omitted.
|
||||||
|
ExcludePublicIps bool
|
||||||
|
|
||||||
|
// ExcludeIpv6 controls whether IPv6 addresses are omitted.
|
||||||
|
ExcludeIpv6 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
||||||
@@ -40,6 +52,36 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
|||||||
}
|
}
|
||||||
delete(unknownFields, ConstAwsDualStack)
|
delete(unknownFields, ConstAwsDualStack)
|
||||||
|
|
||||||
|
primaryInterfaceOnly, err := values.GetBoolValue(in, ConstPrimaryInterfaceOnly, false)
|
||||||
|
if err != nil {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstPrimaryInterfaceOnly)] = err.Error()
|
||||||
|
}
|
||||||
|
delete(unknownFields, ConstPrimaryInterfaceOnly)
|
||||||
|
|
||||||
|
excludePrivateIPs, err := values.GetBoolValue(in, ConstExcludePrivateIps, false)
|
||||||
|
if err != nil {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstExcludePrivateIps)] = err.Error()
|
||||||
|
}
|
||||||
|
delete(unknownFields, ConstExcludePrivateIps)
|
||||||
|
|
||||||
|
excludePublicIPs, err := values.GetBoolValue(in, ConstExcludePublicIps, false)
|
||||||
|
if err != nil {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstExcludePublicIps)] = err.Error()
|
||||||
|
}
|
||||||
|
delete(unknownFields, ConstExcludePublicIps)
|
||||||
|
|
||||||
|
excludeIpv6, err := values.GetBoolValue(in, ConstExcludeIpv6, false)
|
||||||
|
if err != nil {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstExcludeIpv6)] = err.Error()
|
||||||
|
}
|
||||||
|
delete(unknownFields, ConstExcludeIpv6)
|
||||||
|
|
||||||
|
if excludePrivateIPs && excludePublicIPs && excludeIpv6 {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstExcludePrivateIps)] = "cannot be combined with exclude_public_ips and exclude_ipv6"
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstExcludePublicIps)] = "cannot be combined with exclude_private_ips and exclude_ipv6"
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstExcludeIpv6)] = "cannot be combined with exclude_private_ips and exclude_public_ips"
|
||||||
|
}
|
||||||
|
|
||||||
for s := range unknownFields {
|
for s := range unknownFields {
|
||||||
switch s {
|
switch s {
|
||||||
// Ignore knownFields from CredentialAttributes
|
// Ignore knownFields from CredentialAttributes
|
||||||
@@ -67,6 +109,10 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
|||||||
return &CatalogAttributes{
|
return &CatalogAttributes{
|
||||||
CredentialAttributes: credAttributes,
|
CredentialAttributes: credAttributes,
|
||||||
DualStack: dualStack,
|
DualStack: dualStack,
|
||||||
|
PrimaryInterfaceOnly: primaryInterfaceOnly,
|
||||||
|
ExcludePrivateIps: excludePrivateIPs,
|
||||||
|
ExcludePublicIps: excludePublicIPs,
|
||||||
|
ExcludeIpv6: excludeIpv6,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,106 @@ func TestGetCatalogAttributes(t *testing.T) {
|
|||||||
DualStack: true,
|
DualStack: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "with primary interface only",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"primary_interface_only": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
PrimaryInterfaceOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with exclude public ip",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"exclude_public_ips": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
ExcludePublicIps: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with exclude private ip",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"exclude_private_ips": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
ExcludePrivateIps: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with mixed attributes",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"dual_stack": structpb.NewBoolValue(true),
|
||||||
|
"exclude_private_ips": structpb.NewBoolValue(true),
|
||||||
|
"exclude_ipv6": structpb.NewBoolValue(true),
|
||||||
|
"primary_interface_only": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
DualStack: true,
|
||||||
|
ExcludePrivateIps: true,
|
||||||
|
ExcludeIpv6: true,
|
||||||
|
PrimaryInterfaceOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "can exclude public and private IPs when IPv6 remains enabled",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"exclude_private_ips": structpb.NewBoolValue(true),
|
||||||
|
"exclude_public_ips": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
ExcludePrivateIps: true,
|
||||||
|
ExcludePublicIps: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cannot exclude public private and IPv6 together",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"exclude_private_ips": structpb.NewBoolValue(true),
|
||||||
|
"exclude_public_ips": structpb.NewBoolValue(true),
|
||||||
|
"exclude_ipv6": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErrContains: "attributes.exclude_ipv6: cannot be combined with exclude_private_ips and exclude_public_ips, attributes.exclude_private_ips: cannot be combined with exclude_public_ips and exclude_ipv6, attributes.exclude_public_ips: cannot be combined with exclude_private_ips and exclude_ipv6",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "default",
|
name: "default",
|
||||||
in: &structpb.Struct{
|
in: &structpb.Struct{
|
||||||
|
|||||||
@@ -13,4 +13,16 @@ const (
|
|||||||
|
|
||||||
// ConstAwsDualStack is the key for the dualstack flag in the aws s3 client.
|
// ConstAwsDualStack is the key for the dualstack flag in the aws s3 client.
|
||||||
ConstAwsDualStack = "dual_stack"
|
ConstAwsDualStack = "dual_stack"
|
||||||
|
|
||||||
|
// ConstPrimaryInterfaceOnly limits host discovery to the primary ENI.
|
||||||
|
ConstPrimaryInterfaceOnly = "primary_interface_only"
|
||||||
|
|
||||||
|
// ConstExcludePrivateIps excludes private IPv4 addresses.
|
||||||
|
ConstExcludePrivateIps = "exclude_private_ips"
|
||||||
|
|
||||||
|
// ConstExcludePublicIps excludes public IPv4 addresses associated to ENI IPs.
|
||||||
|
ConstExcludePublicIps = "exclude_public_ips"
|
||||||
|
|
||||||
|
// ConstExcludeIpv6 excludes IPv6 addresses.
|
||||||
|
ConstExcludeIpv6 = "exclude_ipv6"
|
||||||
)
|
)
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package host
|
|
||||||
|
|
||||||
// Copyright IBM Corp. 2021, 2026
|
// Copyright IBM Corp. 2021, 2026
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package host
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -550,7 +550,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
// set of hosts afterwards (possibly removing duplicates).
|
// set of hosts afterwards (possibly removing duplicates).
|
||||||
for _, reservation := range output.Reservations {
|
for _, reservation := range output.Reservations {
|
||||||
for _, instance := range reservation.Instances {
|
for _, instance := range reservation.Instances {
|
||||||
host, err := awsInstanceToHost(instance)
|
host, err := awsInstanceToHost(instance, catalogAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error processing host results for host set id %q: %s", query.Id, err)
|
return nil, errors.BadRequestStatus("error processing host results for host set id %q: %s", query.Id, err)
|
||||||
}
|
}
|
||||||
@@ -648,10 +648,13 @@ func buildDescribeInstancesInput(attrs *SetAttributes, dryRun bool) (*ec2.Descri
|
|||||||
// awsInstanceToHost processes data from an ec2.Instance and returns
|
// awsInstanceToHost processes data from an ec2.Instance and returns
|
||||||
// a ListHostsResponseHost with the ID and network addresses
|
// a ListHostsResponseHost with the ID and network addresses
|
||||||
// populated.
|
// populated.
|
||||||
func awsInstanceToHost(instance types.Instance) (*pb.ListHostsResponseHost, error) {
|
func awsInstanceToHost(instance types.Instance, catalogAttributes *CatalogAttributes) (*pb.ListHostsResponseHost, error) {
|
||||||
if aws.ToString(instance.InstanceId) == "" {
|
if aws.ToString(instance.InstanceId) == "" {
|
||||||
return nil, fmt.Errorf("response integrity error: missing instance id")
|
return nil, fmt.Errorf("response integrity error: missing instance id")
|
||||||
}
|
}
|
||||||
|
if catalogAttributes == nil {
|
||||||
|
return nil, fmt.Errorf("catalog attributes are required")
|
||||||
|
}
|
||||||
|
|
||||||
result := new(pb.ListHostsResponseHost)
|
result := new(pb.ListHostsResponseHost)
|
||||||
|
|
||||||
@@ -666,22 +669,46 @@ func awsInstanceToHost(instance types.Instance) (*pb.ListHostsResponseHost, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.IpAddresses = appendDistinct(result.IpAddresses, instance.PrivateIpAddress, instance.PublicIpAddress)
|
if !catalogAttributes.ExcludePrivateIps {
|
||||||
result.DnsNames = appendDistinct(result.DnsNames, instance.PrivateDnsName, instance.PublicDnsName)
|
result.IpAddresses = appendDistinct(result.IpAddresses, instance.PrivateIpAddress)
|
||||||
|
result.DnsNames = appendDistinct(result.DnsNames, instance.PrivateDnsName)
|
||||||
|
}
|
||||||
|
if !catalogAttributes.ExcludePublicIps {
|
||||||
|
result.IpAddresses = appendDistinct(result.IpAddresses, instance.PublicIpAddress)
|
||||||
|
result.DnsNames = appendDistinct(result.DnsNames, instance.PublicDnsName)
|
||||||
|
}
|
||||||
|
|
||||||
// Now go through all of the interfaces and log the IP address of
|
// Now go through all of the interfaces and log the IP address of
|
||||||
// every interface.
|
// every interface.
|
||||||
for _, iface := range instance.NetworkInterfaces {
|
for _, iface := range instance.NetworkInterfaces {
|
||||||
|
if catalogAttributes.PrimaryInterfaceOnly {
|
||||||
|
if iface.Attachment == nil || aws.ToInt32(iface.Attachment.DeviceIndex) != 0 {
|
||||||
|
// We only want to sync the primary interface so skip any others
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Populate default IP addresses/DNS name similar to how we do
|
// Populate default IP addresses/DNS name similar to how we do
|
||||||
// for the entire instance.
|
// for the entire instance.
|
||||||
result.IpAddresses = appendDistinct(result.IpAddresses, iface.PrivateIpAddress)
|
if !catalogAttributes.ExcludePrivateIps {
|
||||||
result.DnsNames = appendDistinct(result.DnsNames, iface.PrivateDnsName)
|
result.IpAddresses = appendDistinct(result.IpAddresses, iface.PrivateIpAddress)
|
||||||
|
result.DnsNames = appendDistinct(result.DnsNames, iface.PrivateDnsName)
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate through the private IP addresses and log the
|
// Iterate through the private IP addresses and log the
|
||||||
// information.
|
// information.
|
||||||
for _, addr := range iface.PrivateIpAddresses {
|
for _, addr := range iface.PrivateIpAddresses {
|
||||||
result.IpAddresses = appendDistinct(result.IpAddresses, addr.PrivateIpAddress)
|
if !catalogAttributes.ExcludePrivateIps {
|
||||||
result.DnsNames = appendDistinct(result.DnsNames, addr.PrivateDnsName)
|
result.IpAddresses = appendDistinct(result.IpAddresses, addr.PrivateIpAddress)
|
||||||
|
result.DnsNames = appendDistinct(result.DnsNames, addr.PrivateDnsName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if catalogAttributes.ExcludePublicIps {
|
||||||
|
// skip adding public addresses if we are excluding them
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicIP for iface is only available via an association to the private IP
|
||||||
if addr.Association != nil {
|
if addr.Association != nil {
|
||||||
result.IpAddresses = appendDistinct(result.IpAddresses, addr.Association.PublicIp)
|
result.IpAddresses = appendDistinct(result.IpAddresses, addr.Association.PublicIp)
|
||||||
result.DnsNames = appendDistinct(result.DnsNames, addr.Association.PublicDnsName)
|
result.DnsNames = appendDistinct(result.DnsNames, addr.Association.PublicDnsName)
|
||||||
@@ -689,8 +716,10 @@ func awsInstanceToHost(instance types.Instance) (*pb.ListHostsResponseHost, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the IPv6 addresses.
|
// Add the IPv6 addresses.
|
||||||
for _, addr := range iface.Ipv6Addresses {
|
if !catalogAttributes.ExcludeIpv6 {
|
||||||
result.IpAddresses = appendDistinct(result.IpAddresses, addr.Ipv6Address)
|
for _, addr := range iface.Ipv6Addresses {
|
||||||
|
result.IpAddresses = appendDistinct(result.IpAddresses, addr.Ipv6Address)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -638,7 +638,8 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
Attributes: &structpb.Struct{
|
Attributes: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
@@ -664,7 +665,8 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
Attributes: &structpb.Struct{
|
Attributes: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
@@ -694,7 +696,8 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
Attributes: &structpb.Struct{
|
Attributes: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
@@ -721,7 +724,8 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
Attributes: &structpb.Struct{
|
Attributes: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
@@ -2383,10 +2387,11 @@ func TestBuildDescribeInstancesInput(t *testing.T) {
|
|||||||
|
|
||||||
func TestAwsInstanceToHost(t *testing.T) {
|
func TestAwsInstanceToHost(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
instance types.Instance
|
instance types.Instance
|
||||||
expected *pb.ListHostsResponseHost
|
catalogAttributes *CatalogAttributes
|
||||||
expectedErr string
|
expected *pb.ListHostsResponseHost
|
||||||
|
expectedErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing instance id",
|
name: "missing instance id",
|
||||||
@@ -2591,14 +2596,45 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Ipv6Addresses: []types.InstanceIpv6Address{
|
Ipv6Addresses: []types.InstanceIpv6Address{
|
||||||
{Ipv6Address: nil}, // Just coverage for nil assertion which is skipped
|
{Ipv6Address: nil}, // Just coverage for nil assertion which is skipped
|
||||||
{Ipv6Address: aws.String("some::fake::address")},
|
{Ipv6Address: aws.String("2001:db8::1")},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &pb.ListHostsResponseHost{
|
expected: &pb.ListHostsResponseHost{
|
||||||
ExternalId: "foobar",
|
ExternalId: "foobar",
|
||||||
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "some::fake::address"},
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "2001:db8::1"},
|
||||||
|
DnsNames: []string{"test.example.internal", "test.example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good, single IP w/public addr",
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("foobar"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
PublicIpAddress: aws.String("1.1.1.1"),
|
||||||
|
PublicDnsName: aws.String("test.example.com"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.1.1.1"),
|
||||||
|
PublicDnsName: aws.String("test.example.com"),
|
||||||
|
},
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "foobar",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1"},
|
||||||
DnsNames: []string{"test.example.internal", "test.example.com"},
|
DnsNames: []string{"test.example.internal", "test.example.com"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -2676,18 +2712,663 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
DnsNames: []string{"test.example.internal", "test.example.com"},
|
DnsNames: []string{"test.example.internal", "test.example.com"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "primary_interface_only keeps all addresses on primary ENI",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
PrimaryInterfaceOnly: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("foobar"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
PublicIpAddress: aws.String("1.1.1.1"),
|
||||||
|
PublicDnsName: aws.String("test.example.com"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
Attachment: &types.InstanceNetworkInterfaceAttachment{
|
||||||
|
DeviceIndex: aws.Int32(0),
|
||||||
|
},
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
Primary: aws.Bool(true),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
},
|
||||||
|
// Secondary IPs on primary ENI (e.g. VPC CNI pod IPs).
|
||||||
|
{
|
||||||
|
Primary: aws.Bool(false),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.42"),
|
||||||
|
PrivateDnsName: aws.String("pod-a.example.internal"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Primary: aws.Bool(false),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.43"),
|
||||||
|
PrivateDnsName: aws.String("pod-b.example.internal"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Secondary ENI attached by VPC CNI for additional pod IPs.
|
||||||
|
{
|
||||||
|
Attachment: &types.InstanceNetworkInterfaceAttachment{
|
||||||
|
DeviceIndex: aws.Int32(1),
|
||||||
|
},
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.99"),
|
||||||
|
PrivateDnsName: aws.String("eni1.example.internal"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "foobar",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.42", "10.0.0.43"},
|
||||||
|
DnsNames: []string{"test.example.internal", "test.example.com", "pod-a.example.internal", "pod-b.example.internal"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "primary_interface_only keeps IPv6 on primary ENI",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
PrimaryInterfaceOnly: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("foobar"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
// Secondary ENI listed first to verify ordering does not matter.
|
||||||
|
{
|
||||||
|
Attachment: &types.InstanceNetworkInterfaceAttachment{
|
||||||
|
DeviceIndex: aws.Int32(1),
|
||||||
|
},
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.99"),
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{
|
||||||
|
{Ipv6Address: aws.String("2001:db8::2")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Attachment: &types.InstanceNetworkInterfaceAttachment{
|
||||||
|
DeviceIndex: aws.Int32(0),
|
||||||
|
},
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{
|
||||||
|
{Ipv6Address: aws.String("2001:db8::3")},
|
||||||
|
{Ipv6Address: aws.String("2001:db8::4")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "foobar",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "2001:db8::3", "2001:db8::4"},
|
||||||
|
DnsNames: []string{"test.example.internal"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "primary_interface_only without interfaces still returns top-level IPs",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
PrimaryInterfaceOnly: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("foobar"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
PublicIpAddress: aws.String("1.1.1.1"),
|
||||||
|
PublicDnsName: aws.String("test.example.com"),
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "foobar",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1"},
|
||||||
|
DnsNames: []string{"test.example.internal", "test.example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_public_ips excludes public IPv4 sources but keeps IPv6",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
ExcludePublicIps: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("i-1234"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PublicIpAddress: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::1")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "i-1234",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "2001:db8::1"},
|
||||||
|
DnsNames: []string{"host.internal"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_private_ips excludes private IPv4 sources but keeps IPv6",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
ExcludePrivateIps: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("i-1234"),
|
||||||
|
PublicIpAddress: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{Association: &types.InstanceNetworkInterfaceAssociation{PublicIp: aws.String("1.2.3.4"), PublicDnsName: aws.String("host.public")}},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::1")}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{Association: &types.InstanceNetworkInterfaceAssociation{PublicIp: aws.String("1.2.3.5"), PublicDnsName: aws.String("svc.public")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "i-1234",
|
||||||
|
IpAddresses: []string{"1.2.3.4", "2001:db8::1", "1.2.3.5"},
|
||||||
|
DnsNames: []string{"host.public", "svc.public"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_public_ips keeps IPv6 addresses",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
ExcludePublicIps: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("i-1234"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("fd00::1")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "i-1234",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "fd00::1"},
|
||||||
|
DnsNames: []string{"host.internal"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_ipv6 omits IPv6 while preserving IPv4 and DNS",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
ExcludeIpv6: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("i-1234"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PublicIpAddress: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::1")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "i-1234",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "1.2.3.4"},
|
||||||
|
DnsNames: []string{"host.internal", "host.public"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_public_ips with exclude_ipv6 keeps only private IPv4 sources",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
ExcludePublicIps: true,
|
||||||
|
ExcludeIpv6: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("i-1234"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PublicIpAddress: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.2"),
|
||||||
|
PrivateDnsName: aws.String("svc.internal"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::1")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "i-1234",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "10.0.0.2"},
|
||||||
|
DnsNames: []string{"host.internal", "svc.internal"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_private_ips with exclude_ipv6 keeps only public IPv4 sources",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
ExcludePrivateIps: true,
|
||||||
|
ExcludeIpv6: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("i-1234"),
|
||||||
|
PublicIpAddress: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.2.3.5"),
|
||||||
|
PublicDnsName: aws.String("svc.public"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("fd00::1")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "i-1234",
|
||||||
|
IpAddresses: []string{"1.2.3.4", "1.2.3.5"},
|
||||||
|
DnsNames: []string{"host.public", "svc.public"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "primary_interface_only with exclude_ipv6 drops IPv6 from primary ENI",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
PrimaryInterfaceOnly: true,
|
||||||
|
ExcludeIpv6: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("foobar"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
PublicIpAddress: aws.String("1.1.1.1"),
|
||||||
|
PublicDnsName: aws.String("test.example.com"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
Attachment: &types.InstanceNetworkInterfaceAttachment{
|
||||||
|
DeviceIndex: aws.Int32(0),
|
||||||
|
},
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.42"),
|
||||||
|
PrivateDnsName: aws.String("pod-a.example.internal"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::3")}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Attachment: &types.InstanceNetworkInterfaceAttachment{
|
||||||
|
DeviceIndex: aws.Int32(1),
|
||||||
|
},
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.99"),
|
||||||
|
PrivateDnsName: aws.String("eni1.example.internal"),
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::4")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "foobar",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.42"},
|
||||||
|
DnsNames: []string{"test.example.internal", "test.example.com", "pod-a.example.internal"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_ipv6 on IPv6-only interface leaves no addresses",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
ExcludeIpv6: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("i-1234"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{
|
||||||
|
{Ipv6Address: aws.String("2001:db8::1")},
|
||||||
|
{Ipv6Address: aws.String("fd00::1")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "i-1234",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deduplicates repeated addresses and DNS names across instance and interfaces",
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("i-1234"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PublicIpAddress: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{
|
||||||
|
{Ipv6Address: aws.String("2001:db8::1")},
|
||||||
|
{Ipv6Address: aws.String("2001:db8::1")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{
|
||||||
|
{Ipv6Address: aws.String("2001:db8::1")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "i-1234",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "1.2.3.4", "2001:db8::1"},
|
||||||
|
DnsNames: []string{"host.internal", "host.public"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "primary_interface_only skips interfaces without attachment metadata",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
PrimaryInterfaceOnly: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("i-1234"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PublicIpAddress: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.99"),
|
||||||
|
PrivateDnsName: aws.String("missing-attachment.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.99"),
|
||||||
|
PrivateDnsName: aws.String("missing-attachment.internal"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::99")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "i-1234",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "1.2.3.4"},
|
||||||
|
DnsNames: []string{"host.internal", "host.public"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_private_ips removes private DNS names even when public addresses remain",
|
||||||
|
catalogAttributes: &CatalogAttributes{
|
||||||
|
ExcludePrivateIps: true,
|
||||||
|
},
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("i-1234"),
|
||||||
|
PublicIpAddress: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("host.internal"),
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.2.3.4"),
|
||||||
|
PublicDnsName: aws.String("host.public"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.2"),
|
||||||
|
PrivateDnsName: aws.String("svc.internal"),
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.2.3.5"),
|
||||||
|
PublicDnsName: aws.String("svc.public"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "i-1234",
|
||||||
|
IpAddresses: []string{"1.2.3.4", "1.2.3.5"},
|
||||||
|
DnsNames: []string{"host.public", "svc.public"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
actual, err := awsInstanceToHost(tc.instance)
|
attrs := tc.catalogAttributes
|
||||||
|
if attrs == nil {
|
||||||
|
attrs = &CatalogAttributes{}
|
||||||
|
}
|
||||||
|
actual, err := awsInstanceToHost(tc.instance, attrs)
|
||||||
if tc.expectedErr != "" {
|
if tc.expectedErr != "" {
|
||||||
require.EqualError(err, tc.expectedErr)
|
require.EqualError(err, tc.expectedErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCatalogAttributesAddressSelection(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in *structpb.Struct
|
||||||
|
expectedErr string
|
||||||
|
expected *CatalogAttributes
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid selectors",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"primary_interface_only": structpb.NewBoolValue(true),
|
||||||
|
"exclude_public_ips": structpb.NewBoolValue(true),
|
||||||
|
"exclude_ipv6": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
PrimaryInterfaceOnly: true,
|
||||||
|
ExcludePublicIps: true,
|
||||||
|
ExcludeIpv6: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_private_ips can be combined with exclude_ipv6",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"exclude_private_ips": structpb.NewBoolValue(true),
|
||||||
|
"exclude_ipv6": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
ExcludePrivateIps: true,
|
||||||
|
ExcludeIpv6: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_public_ips can be combined with exclude_ipv6",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"exclude_public_ips": structpb.NewBoolValue(true),
|
||||||
|
"exclude_ipv6": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
ExcludePublicIps: true,
|
||||||
|
ExcludeIpv6: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_private_ips and exclude_public_ips can be combined when IPv6 remains enabled",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"exclude_private_ips": structpb.NewBoolValue(true),
|
||||||
|
"exclude_public_ips": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
ExcludePrivateIps: true,
|
||||||
|
ExcludePublicIps: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_private_ips and exclude_public_ips cannot also exclude_ipv6",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"exclude_private_ips": structpb.NewBoolValue(true),
|
||||||
|
"exclude_public_ips": structpb.NewBoolValue(true),
|
||||||
|
"exclude_ipv6": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes.exclude_ipv6: cannot be combined with exclude_private_ips and exclude_public_ips, attributes.exclude_private_ips: cannot be combined with exclude_public_ips and exclude_ipv6, attributes.exclude_public_ips: cannot be combined with exclude_private_ips and exclude_ipv6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_ipv6 alone is accepted",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"exclude_ipv6": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
ExcludeIpv6: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exclude_ipv6 requires a boolean",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"exclude_ipv6": structpb.NewStringValue("true"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes.exclude_ipv6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no selectors configured preserves historical behavior",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
actual, err := getCatalogAttributes(tc.in)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.Error(err)
|
||||||
|
require.Contains(err.Error(), tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.Equal(tc.expected, actual)
|
require.Equal(tc.expected, actual)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in new issue
Block a user