From bcb7fa6ef9e3e15dc6006b6fbdddd8527d265309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Smoli=C5=84ski?= Date: Wed, 6 Aug 2025 12:53:05 +0200 Subject: [PATCH] SCIM: Add bearer token creds support for SCIM Plugin --- tool/tctl/common/plugin/plugins_command.go | 1 + tool/tctl/common/plugin/scim.go | 110 ++++++++++++++++++--- 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/tool/tctl/common/plugin/plugins_command.go b/tool/tctl/common/plugin/plugins_command.go index 84e06178f3b1f..a9b05f30681b0 100644 --- a/tool/tctl/common/plugin/plugins_command.go +++ b/tool/tctl/common/plugin/plugins_command.go @@ -55,6 +55,7 @@ type pluginInstallArgs struct { type scimArgs struct { cmd *kingpin.CmdClause samlConnector string + auth string } type pluginDeleteArgs struct { diff --git a/tool/tctl/common/plugin/scim.go b/tool/tctl/common/plugin/scim.go index 864135ea01b31..4db026620e0a3 100644 --- a/tool/tctl/common/plugin/scim.go +++ b/tool/tctl/common/plugin/scim.go @@ -25,12 +25,24 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/google/uuid" "github.com/gravitational/trace" + "golang.org/x/crypto/bcrypt" + "github.com/gravitational/teleport/api/client/proto" pluginspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/plugins/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/utils" ) +var ( + bearerAuthType = "bearer" + oauthAuthType = "oauth" +) + +var allAuthTypes = []string{ + bearerAuthType, + oauthAuthType, +} + func (p *PluginsCommand) initInstallSCIM(parent *kingpin.CmdClause) { p.install.scim.cmd = parent.Command("scim", "Install a Teleport SCIM plugin.") cmd := p.install.scim.cmd @@ -38,6 +50,10 @@ func (p *PluginsCommand) initInstallSCIM(parent *kingpin.CmdClause) { cmd.Flag("connector", "Name of the Teleport SAML connector to use."). Required(). StringVar(&p.install.scim.samlConnector) + + cmd.Flag("auth", "Plugin Authentication type."). + Default(oauthAuthType). + EnumVar(&p.install.scim.auth, allAuthTypes...) } // InstallSCIM implements `tctl plugins install scim`, installing a SCIM integration @@ -63,19 +79,13 @@ func (p *PluginsCommand) InstallSCIM(ctx context.Context, args installPluginArgs }, } - clientID, err := utils.CryptoRandomHex(16) - if err != nil { - return trace.Wrap(err) - } - clientSecret, err := utils.CryptoRandomHex(32) + creds, err := generateSCIMCredentials(scimArgs.auth) if err != nil { return trace.Wrap(err) } req := &pluginspb.CreatePluginRequest{ - Plugin: plugin, - StaticCredentialsList: []*types.PluginStaticCredentialsV1{ - buildOauthCreds(clientID, clientSecret), - }, + Plugin: plugin, + StaticCredentialsList: []*types.PluginStaticCredentialsV1{creds.PluginStaticCredentialsV1}, } if _, err := args.plugins.CreatePlugin(ctx, req); err != nil { return trace.Wrap(err) @@ -86,14 +96,28 @@ func (p *PluginsCommand) InstallSCIM(ctx context.Context, args installPluginArgs return trace.Wrap(err) } + fmt.Printf("\nSCIM Plugin Installed Successfully\n") + if err := printSCIMIntegrationInfo(creds, pingResp, pluginName); err != nil { + return trace.Wrap(err) + } + return nil +} + +func printSCIMIntegrationInfo(creds *credsWrapper, pingResp proto.PingResponse, pluginName string) error { scimBaseURL := fmt.Sprintf("https://%s/v1/webapi/scim/%s", pingResp.GetProxyPublicAddr(), pluginName) - scimTokenURL := fmt.Sprintf("https://%s/v1/webapi/plugin/%s/token", pingResp.GetProxyPublicAddr(), pluginName) + fmt.Println(" Base URL: ", scimBaseURL) - fmt.Printf("\nSCIM Plugin Installed Successfully\n") - fmt.Println(" Base URL: ", scimBaseURL) - fmt.Println(" OAuth Client ID: ", clientID) - fmt.Println(" OAuth Client Secret: ", clientSecret) - fmt.Println(" OAuth Token URL: ", scimTokenURL) + switch t := creds.Spec.Credentials.(type) { + case *types.PluginStaticCredentialsSpecV1_OAuthClientSecret: + scimTokenURL := fmt.Sprintf("https://%s/v1/webapi/plugin/%s/token", pingResp.GetProxyPublicAddr(), pluginName) + fmt.Println(" OAuth Token URL: ", scimTokenURL) + fmt.Println(" OAuth Client ID: ", t.OAuthClientSecret.ClientId) + fmt.Println(" OAuth Client Secret: ", t.OAuthClientSecret.ClientSecret) + case *types.PluginStaticCredentialsSpecV1_APIToken: + fmt.Println(" API Bearer Token: ", creds.rawToken) + default: + return trace.BadParameter("unsupported credentials type %T", creds.Spec.Credentials) + } return nil } @@ -112,3 +136,59 @@ func buildOauthCreds(clientID, clientSecret string) *types.PluginStaticCredentia }}, } } + +func buildBearerCreds(token string) *types.PluginStaticCredentialsV1 { + return &types.PluginStaticCredentialsV1{ + ResourceHeader: types.ResourceHeader{ + Metadata: types.Metadata{ + Name: fmt.Sprintf("%s-%s", types.PluginTypeSCIM, uuid.NewString()), + }, + }, + Spec: &types.PluginStaticCredentialsSpecV1{ + Credentials: &types.PluginStaticCredentialsSpecV1_APIToken{ + APIToken: token, + }, + }, + } +} + +type credsWrapper struct { + *types.PluginStaticCredentialsV1 + // rawToken is the raw bearer token generated for the SCIM plugin + // in case of PluginStaticCredentialsSpecV1_APIToken. + // Note that value of APIToken is bcrypt hashed, so the raw token + // is used ot printed to the user. + rawToken string +} + +func generateSCIMCredentials(authType string) (*credsWrapper, error) { + switch authType { + case oauthAuthType: + clientID, err := utils.CryptoRandomHex(16) + if err != nil { + return nil, trace.Wrap(err) + } + clientSecret, err := utils.CryptoRandomHex(32) + if err != nil { + return nil, trace.Wrap(err) + } + return &credsWrapper{ + PluginStaticCredentialsV1: buildOauthCreds(clientID, clientSecret), + }, nil + case bearerAuthType: + bearerToken, err := utils.CryptoRandomHex(32) + if err != nil { + return nil, trace.Wrap(err) + } + hashedToken, err := bcrypt.GenerateFromPassword([]byte(bearerToken), bcrypt.DefaultCost) + if err != nil { + return nil, trace.Wrap(err) + } + return &credsWrapper{ + PluginStaticCredentialsV1: buildBearerCreds(string(hashedToken)), + rawToken: bearerToken, + }, nil + default: + return nil, trace.BadParameter("unsupported auth type %q", authType) + } +}