Skip to content

Commit

Permalink
Migrate eks discovery to aws sdk v2
Browse files Browse the repository at this point in the history
  • Loading branch information
creack committed Dec 28, 2024
1 parent 2dbdfd2 commit e270e36
Show file tree
Hide file tree
Showing 13 changed files with 373 additions and 278 deletions.
2 changes: 0 additions & 2 deletions lib/cloud/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ type AWSClients interface {
GetAWSIAMClient(ctx context.Context, region string, opts ...AWSOptionsFn) (iamiface.IAMAPI, error)
// GetAWSSTSClient returns AWS STS client for the specified region.
GetAWSSTSClient(ctx context.Context, region string, opts ...AWSOptionsFn) (stsiface.STSAPI, error)
// GetAWSEKSClient returns AWS EKS client for the specified region.
GetAWSEKSClient(ctx context.Context, region string, opts ...AWSOptionsFn) (eksiface.EKSAPI, error)
// GetAWSKMSClient returns AWS KMS client for the specified region.
GetAWSKMSClient(ctx context.Context, region string, opts ...AWSOptionsFn) (kmsiface.KMSAPI, error)
// GetAWSS3Client returns AWS S3 client.
Expand Down
6 changes: 3 additions & 3 deletions lib/integrations/awsoidc/eks_enroll_clusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,11 +716,11 @@ func installKubeAgent(ctx context.Context, cfg installKubeAgentParams) error {
vals["enterprise"] = true
}

eksTags := make(map[string]*string, len(cfg.eksCluster.Tags))
eksTags := make(map[string]string, len(cfg.eksCluster.Tags))
for k, v := range cfg.eksCluster.Tags {
eksTags[k] = aws.String(v)
eksTags[k] = v
}
eksTags[types.OriginLabel] = aws.String(types.OriginCloud)
eksTags[types.OriginLabel] = types.OriginCloud
kubeCluster, err := common.NewKubeClusterFromAWSEKS(aws.ToString(cfg.eksCluster.Name), aws.ToString(cfg.eksCluster.Arn), eksTags)
if err != nil {
return trace.Wrap(err)
Expand Down
4 changes: 2 additions & 2 deletions lib/kube/proxy/cluster_details.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ func azureRestConfigClient(cloudClients cloud.Clients) dynamicCredsClient {

// getAWSCredentials creates a dynamicKubeCreds that generates and updates the access credentials to a EKS kubernetes cluster.
func getAWSCredentials(ctx context.Context, cloudClients cloud.Clients, cfg dynamicCredsConfig) (*dynamicKubeCreds, error) {
// create a client that returns the credentials for kubeCluster
// Create a client that returns the credentials for kubeCluster.
cfg.client = getAWSClientRestConfig(cloudClients, cfg.clock, cfg.resourceMatchers)
creds, err := newDynamicKubeCreds(ctx, cfg)
return creds, trace.Wrap(err)
Expand All @@ -329,7 +329,7 @@ func getAWSResourceMatcherToCluster(kubeCluster types.KubeCluster, resourceMatch
continue
}

return &(matcher.AWS)
return &matcher.AWS
}
return nil
}
Expand Down
69 changes: 47 additions & 22 deletions lib/kube/utils/eks_token_signed.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,44 +19,69 @@
package utils

import (
"context"
"encoding/base64"
"time"

"github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/aws-sdk-go/service/sts/stsiface"
"github.com/gravitational/trace"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
"github.com/jonboulle/clockwork"

aws_sync "github.com/gravitational/teleport/lib/srv/discovery/fetchers/aws-sync"
)

type STSPresigner interface {
PresignGetCallerIdentity(ctx context.Context, params *sts.GetCallerIdentityInput, optFns ...func(*sts.PresignOptions)) (*v4.PresignedHTTPRequest, error)
}

// GenAWSEKSToken creates an AWS token to access EKS clusters.
// Logic from https://github.com/aws/aws-cli/blob/6c0d168f0b44136fc6175c57c090d4b115437ad1/awscli/customizations/eks/get_token.py#L211-L229
func GenAWSEKSToken(stsClient stsiface.STSAPI, clusterID string, clock clockwork.Clock) (string, time.Time, error) {
func GenAWSEKSToken(ctx context.Context, stsPresignClient aws_sync.STSClient, clusterID string, clock clockwork.Clock) (string, time.Time, error) {
const (
// The sts GetCallerIdentity request is valid for 15 minutes regardless of this parameters value after it has been
// signed.
requestPresignParam = 60
// The actual token expiration (presigned STS urls are valid for 15 minutes after timestamp in x-amz-date).
presignedURLExpiration = 15 * time.Minute
v1Prefix = "k8s-aws-v1."
clusterIDHeader = "x-k8s-aws-id"
)

// generate an sts:GetCallerIdentity request and add our custom cluster ID header
request, _ := stsClient.GetCallerIdentityRequest(&sts.GetCallerIdentityInput{})
request.HTTPRequest.Header.Add(clusterIDHeader, clusterID)

// Sign the request. The expires parameter (sets the x-amz-expires header) is
// currently ignored by STS, and the token expires 15 minutes after the x-amz-date
// timestamp regardless. We set it to 60 seconds for backwards compatibility (the
// parameter is a required argument to Presign(), and authenticators 0.3.0 and older are expecting a value between
// 0 and 60 on the server side).
// https://github.com/aws/aws-sdk-go/issues/2167
presignedURLString, err := request.Presign(requestPresignParam)
if err != nil {
return "", time.Time{}, trace.Wrap(err)
}
sts.New(sts.Options{}).GetCallerIdentity(nil, nil, sts.WithAPIOptions(func(s *middleware.Stack) error {
return nil
}))

presignedReq, _ := sts.NewPresignClient(nil).PresignGetCallerIdentity(ctx, &sts.GetCallerIdentityInput{}, func(po *sts.PresignOptions) {
po.ClientOptions = append(po.ClientOptions, func(o *sts.Options) {
o.APIOptions = append(o.APIOptions, func(stack *middleware.Stack) error {
return stack.Finalize.Add(middleware.FinalizeMiddlewareFunc("ClusterIDHeaderMW", func(
ctx context.Context, input middleware.FinalizeInput, next middleware.FinalizeHandler,
) (middleware.FinalizeOutput, middleware.Metadata, error) {
req, ok := input.Request.(*smithyhttp.Request)
if ok {
req.Header.Add(clusterIDHeader, clusterID)
}
return next.HandleFinalize(ctx, input)
}), middleware.After)
})
})
})

// // generate an sts:GetCallerIdentity request and add our custom cluster ID header
// request, _ := stsClient.GetCallerIdentityRequest(&sts.GetCallerIdentityInput{})
// request.HTTPRequest.Header.Add(clusterIDHeader, clusterID)

// // Sign the request. The expires parameter (sets the x-amz-expires header) is
// // currently ignored by STS, and the token expires 15 minutes after the x-amz-date
// // timestamp regardless. We set it to 60 seconds for backwards compatibility (the
// // parameter is a required argument to Presign(), and authenticators 0.3.0 and older are expecting a value between
// // 0 and 60 on the server side).
// // https://github.com/aws/aws-sdk-go/issues/2167
// presignedURLString, err := request.Presign(requestPresignParam)
// if err != nil {
// return "", time.Time{}, trace.Wrap(err)
// }

// Set token expiration to 1 minute before the presigned URL expires for some cushion
tokenExpiration := clock.Now().Add(presignedURLExpiration - 1*time.Minute)
return v1Prefix + base64.RawURLEncoding.EncodeToString([]byte(presignedURLString)), tokenExpiration, nil
return v1Prefix + base64.RawURLEncoding.EncodeToString([]byte(presignedReq.URL)), tokenExpiration, nil
}
1 change: 1 addition & 0 deletions lib/srv/discovery/access_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ func (s *Server) accessGraphFetchersFromMatchers(ctx context.Context, matchers M
aws_sync.Config{
CloudClients: s.CloudClients,
GetEC2Client: s.GetEC2Client,
GetEKSClient: s.GetEKSClient,
AssumeRole: assumeRole,
Regions: awsFetcher.Regions,
Integration: awsFetcher.Integration,
Expand Down
9 changes: 4 additions & 5 deletions lib/srv/discovery/common/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"strings"

"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go/aws"
"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/types"
Expand All @@ -40,7 +39,7 @@ func setAWSKubeName(meta types.Metadata, firstNamePart string, extraNameParts ..
}

// NewKubeClusterFromAWSEKS creates a kube_cluster resource from an EKS cluster.
func NewKubeClusterFromAWSEKS(clusterName, clusterArn string, tags map[string]*string) (types.KubeCluster, error) {
func NewKubeClusterFromAWSEKS(clusterName, clusterArn string, tags map[string]string) (types.KubeCluster, error) {
parsedARN, err := arn.Parse(clusterArn)
if err != nil {
return nil, trace.Wrap(err)
Expand All @@ -64,7 +63,7 @@ func NewKubeClusterFromAWSEKS(clusterName, clusterArn string, tags map[string]*s
}

// labelsFromAWSKubeClusterTags creates kube cluster labels.
func labelsFromAWSKubeClusterTags(tags map[string]*string, parsedARN arn.ARN) map[string]string {
func labelsFromAWSKubeClusterTags(tags map[string]string, parsedARN arn.ARN) map[string]string {
labels := awsEKSTagsToLabels(tags)
labels[types.CloudLabel] = types.CloudAWS
labels[types.DiscoveryLabelRegion] = parsedARN.Region
Expand All @@ -74,11 +73,11 @@ func labelsFromAWSKubeClusterTags(tags map[string]*string, parsedARN arn.ARN) ma
}

// awsEKSTagsToLabels converts AWS tags to a labels map.
func awsEKSTagsToLabels(tags map[string]*string) map[string]string {
func awsEKSTagsToLabels(tags map[string]string) map[string]string {
labels := make(map[string]string)
for key, val := range tags {
if types.IsValidLabelKey(key) {
labels[key] = aws.StringValue(val)
labels[key] = val
} else {
slog.DebugContext(context.Background(), "Skipping EKS tag that is not a valid label key", "tag", key)
}
Expand Down
23 changes: 20 additions & 3 deletions lib/srv/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,22 @@ import (
"time"

"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/eks"
"github.com/aws/aws-sdk-go-v2/service/ssm"
ssmtypes "github.com/aws/aws-sdk-go-v2/service/ssm/types"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/gravitational/trace"

"github.com/jonboulle/clockwork"
"google.golang.org/protobuf/types/known/timestamppb"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"

"github.com/gravitational/trace"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client/proto"
usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1"
Expand Down Expand Up @@ -117,6 +122,8 @@ type Config struct {
CloudClients cloud.Clients
// GetEC2Client gets an AWS EC2 client for the given region.
GetEC2Client server.EC2ClientGetter
// GetEKSClient gets an AWS EKS client for the given region.
GetEKSClient func(ctx context.Context, region string, opts ...awsconfig.OptionsFn) (aws_sync.EKSClient, error)
// GetSSMClient gets an AWS SSM client for the given region.
GetSSMClient func(ctx context.Context, region string, opts ...awsconfig.OptionsFn) (server.SSMClient, error)
// IntegrationOnlyCredentials discards any Matcher that don't have an Integration.
Expand Down Expand Up @@ -228,6 +235,15 @@ kubernetes matchers are present.`)
return ec2.NewFromConfig(cfg), nil
}
}
if c.GetEKSClient == nil {
c.GetEKSClient = func(ctx context.Context, region string, opts ...awsconfig.OptionsFn) (aws_sync.EKSClient, error) {
cfg, err := c.getAWSConfig(ctx, region, opts...)
if err != nil {
return nil, trace.Wrap(err)
}
return eks.NewFromConfig(cfg), nil
}
}
if c.GetSSMClient == nil {
c.GetSSMClient = func(ctx context.Context, region string, opts ...awsconfig.OptionsFn) (server.SSMClient, error) {
cfg, err := c.getAWSConfig(ctx, region, opts...)
Expand Down Expand Up @@ -441,7 +457,7 @@ func New(ctx context.Context, cfg *Config) (*Server, error) {
}
s.databaseFetchers = databaseFetchers

if err := s.initAWSWatchers(cfg.Matchers.AWS); err != nil {
if err := s.initAWSWatchers(ctx, cfg.Matchers.AWS); err != nil {
return nil, trace.Wrap(err)
}

Expand Down Expand Up @@ -507,7 +523,7 @@ func (s *Server) startDynamicMatchersWatcher(ctx context.Context) error {
}

// initAWSWatchers starts AWS resource watchers based on types provided.
func (s *Server) initAWSWatchers(matchers []types.AWSMatcher) error {
func (s *Server) initAWSWatchers(ctx context.Context, matchers []types.AWSMatcher) error {
var err error

ec2Matchers, otherMatchers := splitMatchers(matchers, func(matcherType string) bool {
Expand Down Expand Up @@ -691,6 +707,7 @@ func (s *Server) kubeFetchersFromMatchers(matchers Matchers, discoveryConfigName
awsKubeMatchers, _ := splitMatchers(matchers.AWS, func(matcherType string) bool {
return matcherType == types.AWSMatcherEKS
})

if len(awsKubeMatchers) > 0 {
eksFetchers, err := fetchers.MakeEKSFetchersFromAWSMatchers(s.Log, s.CloudClients, awsKubeMatchers, discoveryConfigName)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions lib/srv/discovery/fetchers/aws-sync/aws-sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type Config struct {
CloudClients cloud.Clients
// GetEC2Client gets an AWS EC2 client for the given region.
GetEC2Client server.EC2ClientGetter
// GetEKSClient gets an AWS EKS client for the given region.
GetEKSClient func(ctx context.Context, region string, opts ...awsconfig.OptionsFn) (EKSClient, error)
// AccountID is the AWS account ID to use when fetching resources.
AccountID string
// Regions is the list of AWS regions to fetch resources from.
Expand Down
Loading

0 comments on commit e270e36

Please sign in to comment.