Improve AWS OIDC Integration extensibility (#25327)

This PR is mostly tidying up and abstracting the current AWS OIDC
integration code.

This will allow for next PRs to be a little smaller.
This commit is contained in:
Marco André Dinis 2023-05-11 08:36:39 +01:00 committed by GitHub
parent ae8f2fa929
commit d84d79a027
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 41 deletions

View file

@ -19,6 +19,7 @@ package awsoidc
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/rds"
@ -26,8 +27,8 @@ import (
"github.com/gravitational/trace"
)
// RDSClientRequest contains the required fields to generate an Authenticated [rds.Client].
type RDSClientRequest struct {
// AWSClientRequest contains the required fields to set up an AWS service client.
type AWSClientRequest struct {
// Token is the token used to issue the API Call.
Token string
@ -39,7 +40,7 @@ type RDSClientRequest struct {
}
// CheckAndSetDefaults checks if the required fields are present.
func (req *RDSClientRequest) CheckAndSetDefaults() error {
func (req *AWSClientRequest) CheckAndSetDefaults() error {
if req.Token == "" {
return trace.BadParameter("token is required")
}
@ -55,8 +56,8 @@ func (req *RDSClientRequest) CheckAndSetDefaults() error {
return nil
}
// NewRDSClient creates an [rds.Client] using the provided Token, RoleARN, Region and, optionally, a custom HTTP Client.
func NewRDSClient(ctx context.Context, req RDSClientRequest) (*rds.Client, error) {
// newAWSConfig creates a new [aws.Config] using the [AWSClientRequest] fields.
func newAWSConfig(ctx context.Context, req *AWSClientRequest) (*aws.Config, error) {
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(req.Region))
if err != nil {
return nil, trace.Wrap(err)
@ -68,7 +69,17 @@ func NewRDSClient(ctx context.Context, req RDSClientRequest) (*rds.Client, error
IdentityToken(req.Token),
)
return rds.NewFromConfig(cfg), nil
return &cfg, nil
}
// newRDSClient creates an [rds.Client] using the provided Token, RoleARN and Region.
func newRDSClient(ctx context.Context, req *AWSClientRequest) (*rds.Client, error) {
cfg, err := newAWSConfig(ctx, req)
if err != nil {
return nil, trace.Wrap(err)
}
return rds.NewFromConfig(*cfg), nil
}
// IdentityToken is an implementation of [stscreds.IdentityTokenRetriever] for returning a static token.

View file

@ -92,6 +92,11 @@ type ListDatabasesClient interface {
DescribeDBClusters(ctx context.Context, params *rds.DescribeDBClustersInput, optFns ...func(*rds.Options)) (*rds.DescribeDBClustersOutput, error)
}
// NewListDatabasesClient creates a new ListDatabasesClient using a AWSClientRequest.
func NewListDatabasesClient(ctx context.Context, req *AWSClientRequest) (ListDatabasesClient, error) {
return newRDSClient(ctx, req)
}
// ListDatabases calls the following AWS API:
// https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBClusters.html
// https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBInstances.html

View file

@ -723,6 +723,7 @@ func (h *Handler) bindDefaultEndpoints() {
h.PUT("/webapi/sites/:site/integrations/:name", h.WithClusterAuth(h.integrationsUpdate))
h.DELETE("/webapi/sites/:site/integrations/:name", h.WithClusterAuth(h.integrationsDelete))
// AWS OIDC Integration Actions
h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/databases", h.WithClusterAuth(h.awsOIDCListDatabases))
// AWS OIDC Integration specific endpoints:

View file

@ -27,15 +27,46 @@ import (
"github.com/gravitational/teleport/lib/web/ui"
)
// IntegrationAWSOIDCTokenGenerator describes the required methods to generate tokens for calling AWS OIDC Integration actions.
type IntegrationAWSOIDCTokenGenerator interface {
// GenerateAWSOIDCToken generates a token to be used to execute an AWS OIDC Integration action.
GenerateAWSOIDCToken(ctx context.Context, req types.GenerateAWSOIDCTokenRequest) (string, error)
}
// awsOIDCListDatabases returns a list of databases using the ListDatabases action of the AWS OIDC Integration.
func (h *Handler) awsOIDCListDatabases(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
ctx := r.Context()
var req ui.AWSOIDCListDatabasesRequest
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
awsClientReq, err := h.awsOIDCClientRequest(r.Context(), req.Region, p, sctx, site)
if err != nil {
return nil, trace.Wrap(err)
}
listDBsClient, err := awsoidc.NewListDatabasesClient(ctx, awsClientReq)
if err != nil {
return nil, trace.Wrap(err)
}
resp, err := awsoidc.ListDatabases(ctx,
listDBsClient,
awsoidc.ListDatabasesRequest{
Region: req.Region,
NextToken: req.NextToken,
Engines: req.Engines,
RDSType: req.RDSType,
},
)
if err != nil {
return nil, trace.Wrap(err)
}
return ui.AWSOIDCListDatabasesResponse{
NextToken: resp.NextToken,
Databases: ui.MakeDatabases(resp.Databases, nil, nil),
}, nil
}
// awsOIDClientRequest receives a request to execute an action for the AWS OIDC integrations.
func (h *Handler) awsOIDCClientRequest(ctx context.Context, region string, p httprouter.Params, sctx *SessionContext, site reversetunnel.RemoteSite) (*awsoidc.AWSClientRequest, error) {
integrationName := p.ByName("name")
if integrationName == "" {
return nil, trace.BadParameter("an integration name is required")
@ -55,11 +86,6 @@ func (h *Handler) awsOIDCListDatabases(w http.ResponseWriter, r *http.Request, p
return nil, trace.BadParameter("integration subkind (%s) mismatch", integration.GetSubKind())
}
var req ui.AWSOIDCListDatabasesRequest
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
issuer, err := h.issuerFromPublicAddr()
if err != nil {
return nil, trace.Wrap(err)
@ -77,30 +103,9 @@ func (h *Handler) awsOIDCListDatabases(w http.ResponseWriter, r *http.Request, p
return nil, trace.BadParameter("missing spec fields for %q (%q) integration", integration.GetName(), integration.GetSubKind())
}
rdsClient, err := awsoidc.NewRDSClient(ctx, awsoidc.RDSClientRequest{
return &awsoidc.AWSClientRequest{
Token: token,
RoleARN: awsoidcSpec.RoleARN,
Region: req.Region,
})
if err != nil {
return nil, trace.Wrap(err)
}
resp, err := awsoidc.ListDatabases(ctx,
rdsClient,
awsoidc.ListDatabasesRequest{
Region: req.Region,
NextToken: req.NextToken,
Engines: req.Engines,
RDSType: req.RDSType,
},
)
if err != nil {
return nil, trace.Wrap(err)
}
return ui.AWSOIDCListDatabasesResponse{
NextToken: resp.NextToken,
Databases: ui.MakeDatabases(resp.Databases, nil, nil),
Region: region,
}, nil
}

View file

@ -117,7 +117,7 @@ type AWSOIDCListDatabasesRequest struct {
NextToken string `json:"nextToken"`
}
// AWSOIDCListDatabasesResponse contains a list of databases and a next token is more pages are available.
// AWSOIDCListDatabasesResponse contains a list of databases and a next token if more pages are available.
type AWSOIDCListDatabasesResponse struct {
// Databases contains the page of Databases
Databases []Database `json:"databases"`