Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass configuration when enrolling a provider with a token #3388

Merged
merged 2 commits into from
May 21, 2024
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
89 changes: 84 additions & 5 deletions cmd/cli/app/provider/provider_enroll.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,25 @@ package provider

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"slices"
"time"

"github.com/pkg/browser"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"

"github.com/stacklok/minder/internal/db"
"github.com/stacklok/minder/internal/providers"
"github.com/stacklok/minder/internal/util"
"github.com/stacklok/minder/internal/util/cli"
"github.com/stacklok/minder/internal/util/rand"
minderv1 "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1"
Expand Down Expand Up @@ -67,11 +76,16 @@ func EnrollProviderCommand(ctx context.Context, cmd *cobra.Command, _ []string,
if provider == "" {
provider = viper.GetString("class")
}
providerName := viper.GetString("name")
if providerName == "" {
providerName = provider
}
project := viper.GetString("project")
token := viper.GetString("token")
owner := viper.GetString("owner")
yesFlag := viper.GetBool("yes")
skipBrowser := viper.GetBool("skip-browser")
cfgFlag := viper.GetString("config")

// No longer print usage on returned error, since we've parsed our inputs
// See https://github.com/spf13/cobra/issues/340#issuecomment-374617413
Expand All @@ -98,9 +112,20 @@ func EnrollProviderCommand(ctx context.Context, cmd *cobra.Command, _ []string,
}
}

config, err := providerConfigFromArg(cfgFlag, cmd.InOrStdin())
if err != nil {
return cli.MessageAndError("Error reading provider configuration", err)
}

// the token only applies to the old flow
if token != "" && provider == legacyGitHubProvider.ToString() {
return enrollUsingToken(ctx, cmd, oauthClient, provider, project, token, owner)
// TODO: allow for token to be passed in if the provider allows it, don't hardcode
userFlow, err := supportsToken(provider)
if err != nil {
return cli.MessageAndError("Error checking provider support", err)
}

if token != "" && userFlow {
return enrollUsingToken(ctx, cmd, oauthClient, providerClient, providerName, provider, project, token, owner, config)
}

// This will have a different timeout
Expand All @@ -113,13 +138,35 @@ func enrollUsingToken(
ctx context.Context,
cmd *cobra.Command,
client minderv1.OAuthServiceClient,
provider string,
provClient minderv1.ProvidersServiceClient,
providerName string,
providerClass string,
project string,
token string,
owner string,
providerConfig *structpb.Struct,
) error {
_, err := client.StoreProviderToken(ctx, &minderv1.StoreProviderTokenRequest{
Context: &minderv1.Context{Provider: &provider, Project: &project},
_, err := provClient.CreateProvider(ctx, &minderv1.CreateProviderRequest{
Context: &minderv1.Context{Provider: &providerName, Project: &project},
Provider: &minderv1.Provider{
Name: providerName,
Class: providerClass,
Config: providerConfig,
},
})
st, ok := status.FromError(err)
if !ok {
// We can't parse the error, so just return it
return err
}

if st.Code() != codes.AlreadyExists {
return err
}

// the provider already exists, turn this call into an update of the token
_, err = client.StoreProviderToken(ctx, &minderv1.StoreProviderTokenRequest{
Context: &minderv1.Context{Provider: &providerName, Project: &project},
AccessToken: token,
Owner: &owner,
})
Expand Down Expand Up @@ -284,13 +331,45 @@ func callBackServer(ctx context.Context, cmd *cobra.Command, project string, por
}
}

func providerConfigFromArg(configSource string, dashReader io.Reader) (*structpb.Struct, error) {
if configSource == "" {
return nil, nil
}

reader, closer, err := util.OpenFileArg(configSource, dashReader)
if err != nil {
return nil, fmt.Errorf("error opening file arg: %w", err)
}
defer closer()

var config map[string]any

// TODO: handle YAML and JSON
err = json.NewDecoder(reader).Decode(&config)
if err != nil {
return nil, fmt.Errorf("error parsing provider configuration: %w", err)
}

return structpb.NewStruct(config)
}

func supportsToken(providerClass string) (bool, error) {
providerDef, err := providers.GetProviderClassDefinition(providerClass)
if err != nil {
return false, err
}
return slices.Contains(providerDef.AuthorizationFlows, db.AuthorizationFlowUserInput), nil
}

func init() {
ProviderCmd.AddCommand(enrollCmd)
// Flags
enrollCmd.Flags().StringP("token", "t", "", "Personal Access Token (PAT) to use for enrollment (Legacy GitHub only)")
enrollCmd.Flags().StringP("owner", "o", "", "Owner to filter on for provider resources (Legacy GitHub only)")
enrollCmd.Flags().BoolP("yes", "y", false, "Bypass any yes/no prompts when enrolling a new provider")
enrollCmd.Flags().StringP("class", "c", githubAppProvider.ToString(), "Provider class, defaults to github-app")
enrollCmd.Flags().StringP("config", "f", "", "Path to the provider configuration (or - for stdin)")
enrollCmd.Flags().StringP("name", "n", "", "Name of the new provider. (Only when using a token)")

// Bind flags
if err := viper.BindPFlag("token", enrollCmd.Flags().Lookup("token")); err != nil {
Expand Down
6 changes: 4 additions & 2 deletions internal/auth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"golang.org/x/oauth2/google"

"github.com/stacklok/minder/internal/config"
"github.com/stacklok/minder/internal/db"
)

const (
Expand Down Expand Up @@ -149,8 +150,9 @@ func DeleteAccessToken(ctx context.Context, provider string, token string) error
}

// ValidateProviderToken validates the given token for the given provider
func ValidateProviderToken(_ context.Context, provider string, token string) error {
if provider == Github {
func ValidateProviderToken(_ context.Context, provider db.ProviderClass, token string) error {
// Fixme: this should really be handled by the provider. Should this be in the credentials API or the manager?
if provider == db.ProviderClassGithub {
// Create an OAuth2 token source with the PAT
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})

Expand Down
4 changes: 2 additions & 2 deletions internal/controlplane/handlers_oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,9 +465,9 @@ func (s *Server) StoreProviderToken(ctx context.Context,
}

// validate token
err = auth.ValidateProviderToken(ctx, provider.Name, in.AccessToken)
err = auth.ValidateProviderToken(ctx, provider.Class, in.AccessToken)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid token provided")
return nil, status.Errorf(codes.InvalidArgument, "invalid token provided: %v", err)
}

ftoken := &oauth2.Token{
Expand Down