From 75d3409e1e8414866c2f4accb190cf219d1c93f4 Mon Sep 17 00:00:00 2001 From: Marco Dinis Date: Tue, 11 Jun 2024 18:04:20 +0100 Subject: [PATCH] AWS OIDC: Remove App Server that uses the integration credentials (#42012) * AWS OIDC: Remove App Server that uses the integration credentials Users can enable the AWS App Access using the Integration credentials. We are also creating a way for them to disable this access. * change url placeholders --- lib/web/apiserver.go | 6 ++- lib/web/integrations.go | 2 +- lib/web/integrations_awsoidc.go | 59 +++++++++++++++++++++++++++- lib/web/integrations_awsoidc_test.go | 25 +++++++++--- 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index ef0c62902cf09..462787c2fa3f4 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -877,7 +877,7 @@ func (h *Handler) bindDefaultEndpoints() { h.POST("/webapi/sites/:site/integrations", h.WithClusterAuth(h.integrationsCreate)) h.GET("/webapi/sites/:site/integrations/:name", h.WithClusterAuth(h.integrationsGet)) h.PUT("/webapi/sites/:site/integrations/:name", h.WithClusterAuth(h.integrationsUpdate)) - h.DELETE("/webapi/sites/:site/integrations/:name", h.WithClusterAuth(h.integrationsDelete)) + h.DELETE("/webapi/sites/:site/integrations/:name_or_subkind", h.WithClusterAuth(h.integrationsDelete)) // AWS OIDC Integration Actions h.GET("/webapi/scripts/integrations/configure/awsoidc-idp.sh", h.WithLimiter(h.awsOIDCConfigureIdP)) @@ -898,6 +898,10 @@ func (h *Handler) bindDefaultEndpoints() { h.GET("/webapi/scripts/integrations/configure/access-graph-cloud-sync-iam.sh", h.WithLimiter(h.accessGraphCloudSyncOIDC)) h.GET("/webapi/scripts/integrations/configure/aws-app-access-iam.sh", h.WithLimiter(h.awsOIDCConfigureAWSAppAccessIAM)) h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/aws-app-access", h.WithClusterAuth(h.awsOIDCCreateAWSAppAccess)) + // The Integration DELETE endpoint already sets the expected named param after `/integrations/` + // It must be re-used here, otherwise the router will not start. + // See https://github.com/julienschmidt/httprouter/issues/364 + h.DELETE("/webapi/sites/:site/integrations/:name_or_subkind/aws-app-access/:name", h.WithClusterAuth(h.awsOIDCDeleteAWSAppAccess)) h.GET("/webapi/scripts/integrations/configure/ec2-ssm-iam.sh", h.WithLimiter(h.awsOIDCConfigureEC2SSMIAM)) // SAML IDP integration endpoints diff --git a/lib/web/integrations.go b/lib/web/integrations.go index d708a0606afce..5cca788e7c6f2 100644 --- a/lib/web/integrations.go +++ b/lib/web/integrations.go @@ -152,7 +152,7 @@ func (h *Handler) integrationsUpdate(w http.ResponseWriter, r *http.Request, p h // integrationsDelete removes an Integration based on its name func (h *Handler) integrationsDelete(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnelclient.RemoteSite) (interface{}, error) { - integrationName := p.ByName("name") + integrationName := p.ByName("name_or_subkind") if integrationName == "" { return nil, trace.BadParameter("an integration name is required") } diff --git a/lib/web/integrations_awsoidc.go b/lib/web/integrations_awsoidc.go index 2fea145c3eb8e..120f17a42412d 100644 --- a/lib/web/integrations_awsoidc.go +++ b/lib/web/integrations_awsoidc.go @@ -35,6 +35,7 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" + apidefaults "github.com/gravitational/teleport/api/defaults" integrationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils" @@ -925,7 +926,7 @@ func (h *Handler) awsOIDCDeployEC2ICE(w http.ResponseWriter, r *http.Request, p }, nil } -// awsOIDCDeployC2ICE creates an AppServer that uses an AWS OIDC Integration for proxying access. +// awsOIDCCreateAWSAppAccess creates an AppServer that uses an AWS OIDC Integration for proxying access. func (h *Handler) awsOIDCCreateAWSAppAccess(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnelclient.RemoteSite) (any, error) { ctx := r.Context() @@ -984,6 +985,62 @@ func (h *Handler) awsOIDCCreateAWSAppAccess(w http.ResponseWriter, r *http.Reque }), nil } +// awsOIDCDeleteAWSAppAccess deletes the AWS AppServer created that uses the AWS OIDC Integration for proxying requests. +func (h *Handler) awsOIDCDeleteAWSAppAccess(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnelclient.RemoteSite) (any, error) { + ctx := r.Context() + + subkind := p.ByName("name_or_subkind") + if subkind != types.IntegrationSubKindAWSOIDC { + return nil, trace.BadParameter("only aws oidc integrations are supported") + } + + integrationName := p.ByName("name") + if integrationName == "" { + return nil, trace.BadParameter("an integration name is required") + } + + clt, err := sctx.GetUserClient(ctx, site) + if err != nil { + return nil, trace.Wrap(err) + } + + ig, err := clt.GetIntegration(ctx, integrationName) + if err != nil { + return nil, trace.Wrap(err) + } + if ig.GetSubKind() != types.IntegrationSubKindAWSOIDC { + return nil, trace.BadParameter("only aws oidc integrations are supported") + } + + integrationAppServer, err := h.getAppServerByName(ctx, clt, integrationName) + if err != nil { + return nil, trace.Wrap(err) + } + if integrationAppServer.GetApp().GetIntegration() != integrationName { + return nil, trace.NotFound("app %s is not using integration %s", integrationAppServer.GetName(), integrationName) + } + + if err := clt.DeleteApplicationServer(ctx, apidefaults.Namespace, integrationAppServer.GetHostID(), integrationName); err != nil { + return nil, trace.Wrap(err) + } + + return nil, nil +} + +func (h *Handler) getAppServerByName(ctx context.Context, userClient authclient.ClientI, appServerName string) (types.AppServer, error) { + appServers, err := userClient.GetApplicationServers(ctx, apidefaults.Namespace) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, s := range appServers { + if s.GetName() == appServerName { + return s, nil + } + } + return nil, trace.NotFound("app %q not found", appServerName) +} + // awsOIDCConfigureIdP returns a script that configures AWS OIDC Integration // by creating an OIDC Identity Provider that trusts Teleport instance. func (h *Handler) awsOIDCConfigureIdP(w http.ResponseWriter, r *http.Request, p httprouter.Params) (any, error) { diff --git a/lib/web/integrations_awsoidc_test.go b/lib/web/integrations_awsoidc_test.go index b02c0b3fa510e..41d01db465c7c 100644 --- a/lib/web/integrations_awsoidc_test.go +++ b/lib/web/integrations_awsoidc_test.go @@ -951,14 +951,16 @@ func TestAWSOIDCSecurityGroupsRulesConverter(t *testing.T) { } } -func TestAWSOIDCAppAccessAppServerCreation(t *testing.T) { +func TestAWSOIDCAppAccessAppServerCreationDeletion(t *testing.T) { env := newWebPack(t, 1) + ctx := context.Background() roleTokenCRD, err := types.NewRole(services.RoleNameForUser("my-user"), types.RoleSpecV6{ Allow: types.RoleConditions{ + AppLabels: types.Labels{"*": []string{"*"}}, Rules: []types.Rule{ types.NewRule(types.KindIntegration, []string{types.VerbRead}), - types.NewRule(types.KindAppServer, []string{types.VerbCreate, types.VerbUpdate}), + types.NewRule(types.KindAppServer, []string{types.VerbCreate, types.VerbUpdate, types.VerbList, types.VerbDelete}), types.NewRule(types.KindUserGroup, []string{types.VerbList, types.VerbRead}), }, }, @@ -976,16 +978,22 @@ func TestAWSOIDCAppAccessAppServerCreation(t *testing.T) { }) require.NoError(t, err) - _, err = env.server.Auth().CreateIntegration(context.Background(), myIntegration) + _, err = env.server.Auth().CreateIntegration(ctx, myIntegration) require.NoError(t, err) + // Deleting the AWS App Access should return an error because it was not created yet. + deleteEndpoint := pack.clt.Endpoint("webapi", "sites", "localhost", "integrations", "aws-oidc", "aws-app-access", "my-integration") + _, err = pack.clt.Delete(ctx, deleteEndpoint) + require.Error(t, err) + require.ErrorContains(t, err, "not found") + // Create the AWS App Access for the current integration. endpoint := pack.clt.Endpoint("webapi", "sites", "localhost", "integrations", "aws-oidc", "my-integration", "aws-app-access") - _, err = pack.clt.PostJSON(context.Background(), endpoint, nil) + _, err = pack.clt.PostJSON(ctx, endpoint, nil) require.NoError(t, err) // Ensure the AppServer was correctly created. - appServers, err := env.server.Auth().GetApplicationServers(context.Background(), "default") + appServers, err := env.server.Auth().GetApplicationServers(ctx, "default") require.NoError(t, err) require.Len(t, appServers, 1) @@ -1019,4 +1027,11 @@ func TestAWSOIDCAppAccessAppServerCreation(t *testing.T) { appServers[0], cmpopts.IgnoreFields(types.Metadata{}, "Revision", "Namespace"), )) + + // After deleting the application, it should be removed from the backend. + _, err = pack.clt.Delete(ctx, deleteEndpoint) + require.NoError(t, err) + appServers, err = env.server.Auth().GetApplicationServers(ctx, "default") + require.NoError(t, err) + require.Empty(t, appServers) }