Files
teleport/lib/srv/server/ec2_watcher_test.go
T
Alex McGrath 66985553db Add agentless installer in the teleport discovery service (#19648)
* Add agentless installer

* Resolve comments

* Resolve comments

* Use GetCertAuthorities locally

* Try to get IMDS hostname

* Try get imds hostname first

This seems to be how its implemented for non-agentless nodes

* Use FIPS cipher suites

* use the openssh ca, resolve comments

* write keys to /etc/teleport/agentless by default

* Resolve comment

* lints

* test fixes
2023-01-24 14:49:06 +00:00

237 lines
5.9 KiB
Go

/*
Copyright 2022 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"context"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/lib/cloud"
"github.com/gravitational/teleport/lib/cloud/azure"
"github.com/gravitational/teleport/lib/services"
)
type mockClients struct {
cloud.Clients
ec2Client *mockEC2Client
azureClient azure.VirtualMachinesClient
}
func (c *mockClients) GetAWSEC2Client(region string) (ec2iface.EC2API, error) {
return c.ec2Client, nil
}
type mockEC2Client struct {
ec2iface.EC2API
output *ec2.DescribeInstancesOutput
}
func instanceMatches(inst *ec2.Instance, filters []*ec2.Filter) bool {
allMatched := true
for _, filter := range filters {
name := aws.StringValue(filter.Name)
val := aws.StringValue(filter.Values[0])
if name == AWSInstanceStateName && aws.StringValue(inst.State.Name) != ec2.InstanceStateNameRunning {
return false
}
for _, tag := range inst.Tags {
if aws.StringValue(tag.Key) != name[4:] {
continue
}
allMatched = allMatched && aws.StringValue(tag.Value) != val
}
}
return !allMatched
}
func (m *mockEC2Client) DescribeInstancesPagesWithContext(
ctx context.Context, input *ec2.DescribeInstancesInput,
f func(dio *ec2.DescribeInstancesOutput, b bool) bool, opts ...request.Option) error {
output := &ec2.DescribeInstancesOutput{}
for _, res := range m.output.Reservations {
var instances []*ec2.Instance
for _, inst := range res.Instances {
if instanceMatches(inst, input.Filters) {
instances = append(instances, inst)
}
}
output.Reservations = append(output.Reservations, &ec2.Reservation{
Instances: instances,
})
}
f(output, true)
return nil
}
func TestNewEC2InstanceFetcherTags(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
config ec2FetcherConfig
expectedFilters []*ec2.Filter
}{
{
name: "with glob key",
config: ec2FetcherConfig{
Labels: types.Labels{
"*": []string{},
"hello": []string{"other"},
},
},
expectedFilters: []*ec2.Filter{
{
Name: aws.String(AWSInstanceStateName),
Values: aws.StringSlice([]string{ec2.InstanceStateNameRunning}),
},
},
},
{
name: "with no glob key",
config: ec2FetcherConfig{
Labels: types.Labels{
"hello": []string{"other"},
},
},
expectedFilters: []*ec2.Filter{
{
Name: aws.String(AWSInstanceStateName),
Values: aws.StringSlice([]string{ec2.InstanceStateNameRunning}),
},
{
Name: aws.String("tag:hello"),
Values: aws.StringSlice([]string{"other"}),
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
fetcher := newEC2InstanceFetcher(tc.config)
require.Equal(t, tc.expectedFilters, fetcher.Filters)
})
}
}
func TestEC2Watcher(t *testing.T) {
t.Parallel()
clients := mockClients{
ec2Client: &mockEC2Client{},
}
matchers := []services.AWSMatcher{
{
Params: services.InstallerParams{
InstallTeleport: true,
},
Types: []string{"EC2"},
Regions: []string{"us-west-2"},
Tags: map[string]utils.Strings{"teleport": {"yes"}},
SSM: &services.AWSSSM{},
},
{
Params: services.InstallerParams{
InstallTeleport: true,
},
Types: []string{"EC2"},
Regions: []string{"us-west-2"},
Tags: map[string]utils.Strings{"env": {"dev"}},
SSM: &services.AWSSSM{},
},
}
ctx := context.Background()
present := ec2.Instance{
InstanceId: aws.String("instance-present"),
Tags: []*ec2.Tag{{
Key: aws.String("teleport"),
Value: aws.String("yes"),
}},
State: &ec2.InstanceState{
Name: aws.String(ec2.InstanceStateNameRunning),
},
}
presentOther := ec2.Instance{
InstanceId: aws.String("instance-present-2"),
Tags: []*ec2.Tag{{
Key: aws.String("env"),
Value: aws.String("dev"),
}},
State: &ec2.InstanceState{
Name: aws.String(ec2.InstanceStateNameRunning),
},
}
output := ec2.DescribeInstancesOutput{
Reservations: []*ec2.Reservation{{
Instances: []*ec2.Instance{
&present,
&presentOther,
{
InstanceId: aws.String("instance-absent"),
Tags: []*ec2.Tag{{
Key: aws.String("env"),
Value: aws.String("prod"),
}},
State: &ec2.InstanceState{
Name: aws.String(ec2.InstanceStateNameRunning),
},
},
{
InstanceId: aws.String("instance-absent-3"),
Tags: []*ec2.Tag{{
Key: aws.String("env"),
Value: aws.String("prod"),
}, {
Key: aws.String("teleport"),
Value: aws.String("yes"),
}},
State: &ec2.InstanceState{
Name: aws.String(ec2.InstanceStateNamePending),
},
},
},
}},
}
clients.ec2Client.output = &output
watcher, err := NewEC2Watcher(ctx, matchers, &clients)
require.NoError(t, err)
go watcher.Run()
result := <-watcher.InstancesC
require.Equal(t, EC2Instances{
Region: "us-west-2",
Instances: []*ec2.Instance{&present},
Parameters: map[string]string{"token": "", "scriptName": ""},
}, *result.EC2Instances)
result = <-watcher.InstancesC
require.Equal(t, EC2Instances{
Region: "us-west-2",
Instances: []*ec2.Instance{&presentOther},
Parameters: map[string]string{"token": "", "scriptName": ""},
}, *result.EC2Instances)
}