Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tool/tctl/common/plugin/plugins_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type pluginInstallArgs struct {
type scimArgs struct {
cmd *kingpin.CmdClause
samlConnector string
auth string
}

type pluginDeleteArgs struct {
Expand Down
110 changes: 95 additions & 15 deletions tool/tctl/common/plugin/scim.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,35 @@ 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

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
Expand All @@ -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)
Expand All @@ -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
}

Expand All @@ -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)
}
}
Loading