Skip to content

Commit

Permalink
feat: kurtosis github login (#2113)
Browse files Browse the repository at this point in the history
## Description:
This change enables GitHub users over Docker to authorize with Kurtosis
CLI so that Kurtosis can perform git operations such as cloning packages
in private repositories. This primarily enables the use of private
GitHub locators for package runs, `import_module`, and `upload_files`.

This is accomplished via an OAuth flow where `kurtosis github login`
directs the user to authorize Kurtosis CLI to take actions on their
behalf. A token is retrieved upon success and is used by Kurtosis for
subsequent git operations. Kurtosis attempts to store the token in
secure system storage, but if not found, the token is stored in a plain
text file at `kurtosis config path`.

Github commands added:
- `kurtosis github login`
- `kurtosis github logout`
- `kurtosis github token`
- `kurtosis github status`

Flags added (these override existing GitHub login for one off
authorization use cases):
- `kurtosis engine start --github-auth-token=< token.txt`
- `kurtosis engine restart --github-auth-token=< token.txt`

## Is this change user facing?
YES

## References:
#2020
  • Loading branch information
tedim52 authored Feb 7, 2024
1 parent a75b04c commit 2f0d638
Show file tree
Hide file tree
Showing 45 changed files with 1,847 additions and 322 deletions.
8 changes: 4 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -778,9 +778,9 @@ jobs:
false
fi
- run:
name: "Verify Kurtosis cleaned up all its volumes (except for the log storage volume, so we can persist service logs)"
name: "Verify Kurtosis cleaned up all its volumes (except for the log storage and github auth storage volumes)"
command: |
if ! [ $(docker volume ls | grep -v kurtosis-logs-collector-vol | grep -v kurtosis-logs-db-vol | tail -n+2 | wc -l ) -eq 1 ]; then
if ! [ $(docker volume ls | grep -v kurtosis-logs-collector-vol | grep -v kurtosis-logs-db-vol | tail -n+2 | wc -l ) -eq 2 ]; then
docker volume ls
false
fi
Expand Down Expand Up @@ -1013,9 +1013,9 @@ jobs:
false
fi
- run:
name: "Verify Kurtosis cleaned up all its volumes (except for the log storage volume, so we can persist service logs)"
name: "Verify Kurtosis cleaned up all its volumes (except for the log storage and github auth storage volumes)"
command: |
if ! [ $(docker volume ls | grep -v kurtosis-logs-collector-vol | grep -v kurtosis-logs-db-vol | tail -n+2 | wc -l) -eq 1 ]; then
if ! [ $(docker volume ls | grep -v kurtosis-logs-collector-vol | grep -v kurtosis-logs-db-vol | tail -n+2 | wc -l) -eq 2 ]; then
docker volume ls
false
fi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func (cmd *EngineConsumingKurtosisCommand) getSetupFunc() func(context.Context)
// commands only access the Kurtosis APIs, we can remove this.
kurtosisBackend := engineManager.GetKurtosisBackend()

engineClient, closeClientFunc, err := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize)
engineClient, closeClientFunc, err := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize, defaults.DefaultGitHubAuthTokenOverride)
if err != nil {
return nil, stacktrace.Propagate(err, "An error occurred creating a new Kurtosis engine client")
}
Expand Down
5 changes: 5 additions & 0 deletions cli/cli/command_str_consts/command_str_consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ const (
PortCmdStr = "port"
PortPrintCmdStr = "print"
WebCmdStr = "web"
GitHubCmdStr = "github"
GitHubLoginCmdStr = "login"
GitHubLogoutCmdStr = "logout"
GitHubTokenCmdStr = "token"
GitHubStatusCmdStr = "status"
)

// TODO: added constant error message here, can we move to another file later.
Expand Down
2 changes: 1 addition & 1 deletion cli/cli/commands/cluster/set/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func run(ctx context.Context, flags *flags.ParsedFlags, args *args.ParsedArgs) e
// we only start in a stopped state, the idempotent visitor gets stuck with engine_manager.EngineStatus_ContainerRunningButServerNotResponding if the gateway isn't running
// TODO - fix the idempotent starter longer term
if engineStatus == engine_manager.EngineStatus_Stopped {
_, engineClientCloseFunc, err := engineManagerNewCluster.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize)
_, engineClientCloseFunc, err := engineManagerNewCluster.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize, defaults.DefaultGitHubAuthTokenOverride)
if err != nil {
return stacktrace.Propagate(err, "Engine could not be started after cluster was updated. Its status can be retrieved "+
"running 'kurtosis %s %s' and it can potentially be started running 'kurtosis %s %s'",
Expand Down
3 changes: 1 addition & 2 deletions cli/cli/commands/enclave/add/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@ func run(
if err != nil {
return stacktrace.Propagate(err, "An error occurred creating an engine manager.")
}

engineClient, closeClientFunc, err := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize)
engineClient, closeClientFunc, err := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize, defaults.DefaultGitHubAuthTokenOverride)
if err != nil {
return stacktrace.Propagate(err, "An error occurred creating a new Kurtosis engine client")
}
Expand Down
21 changes: 17 additions & 4 deletions cli/cli/commands/engine/restart/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import (
)

const (
engineVersionFlagKey = "version"
logLevelFlagKey = "log-level"
enclavePoolSizeFlagKey = "enclave-pool-size"
engineVersionFlagKey = "version"
logLevelFlagKey = "log-level"
enclavePoolSizeFlagKey = "enclave-pool-size"
githubAuthTokenOverrideFlagKey = "github-auth-token"

defaultEngineVersion = ""
restartEngineOnSameVersionIfAnyRunning = false
Expand Down Expand Up @@ -63,6 +64,13 @@ var RestartCmd = &lowlevel.LowlevelKurtosisCommand{
Type: flags.FlagType_Uint8,
Default: strconv.Itoa(int(defaults.DefaultEngineEnclavePoolSize)),
},
{
Key: githubAuthTokenOverrideFlagKey,
Usage: "The GitHub auth token that should be used to authorize git operations such as accessing packages in private repositories. Overrides existing GitHub auth config if a user is logged in.",
Shorthand: "",
Type: flags.FlagType_String,
Default: defaults.DefaultGitHubAuthTokenOverride,
},
},
PreValidationAndRunFunc: nil,
RunFunc: run,
Expand Down Expand Up @@ -93,6 +101,11 @@ func run(_ context.Context, flags *flags.ParsedFlags, _ *args.ParsedArgs) error
return stacktrace.Propagate(err, "An error occurred parsing log level string '%v'", logLevelStr)
}

githubAuthTokenOverride, err := flags.GetString(githubAuthTokenOverrideFlagKey)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting GitHub auth token override flag with key '%v'. This is a bug in Kurtosis", githubAuthTokenOverrideFlagKey)
}

engineManager, err := engine_manager.NewEngineManager(ctx)
if err != nil {
return stacktrace.Propagate(err, "An error occurred creating an engine manager.")
Expand All @@ -116,7 +129,7 @@ func run(_ context.Context, flags *flags.ParsedFlags, _ *args.ParsedArgs) error

var engineClientCloseFunc func() error
var restartEngineErr error
_, engineClientCloseFunc, restartEngineErr = engineManager.RestartEngineIdempotently(ctx, logLevel, engineVersion, restartEngineOnSameVersionIfAnyRunning, enclavePoolSize, shouldStartInDebugMode)
_, engineClientCloseFunc, restartEngineErr = engineManager.RestartEngineIdempotently(ctx, logLevel, engineVersion, restartEngineOnSameVersionIfAnyRunning, enclavePoolSize, shouldStartInDebugMode, githubAuthTokenOverride)
if restartEngineErr != nil {
return stacktrace.Propagate(restartEngineErr, "An error occurred restarting the Kurtosis engine")
}
Expand Down
26 changes: 19 additions & 7 deletions cli/cli/commands/engine/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import (
)

const (
engineVersionFlagKey = "version"
logLevelFlagKey = "log-level"
enclavePoolSizeFlagKey = "enclave-pool-size"
engineVersionFlagKey = "version"
logLevelFlagKey = "log-level"
enclavePoolSizeFlagKey = "enclave-pool-size"
githubAuthTokenOverrideFlagKey = "github-auth-token"

defaultEngineVersion = ""
kurtosisTechEngineImagePrefix = "kurtosistech/engine"
Expand Down Expand Up @@ -64,6 +65,13 @@ var StartCmd = &lowlevel.LowlevelKurtosisCommand{
Type: flags.FlagType_Uint8,
Default: strconv.Itoa(int(defaults.DefaultEngineEnclavePoolSize)),
},
{
Key: githubAuthTokenOverrideFlagKey,
Usage: "The github auth token that should be used to authorize git operations such as accessing packages in private repositories. Overrides existing github auth config if a user is logged in.",
Shorthand: "",
Type: flags.FlagType_String,
Default: defaults.DefaultGitHubAuthTokenOverride,
},
},
PreValidationAndRunFunc: nil,
RunFunc: run,
Expand Down Expand Up @@ -92,6 +100,11 @@ func run(_ context.Context, flags *flags.ParsedFlags, _ *args.ParsedArgs) error
return stacktrace.Propagate(err, "An error occurred parsing log level string '%v'", logLevelStr)
}

githubAuthTokenOverride, err := flags.GetString(githubAuthTokenOverrideFlagKey)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting GitHub auth token override flag with key '%v'. This is a bug in Kurtosis", githubAuthTokenOverrideFlagKey)
}

engineManager, err := engine_manager.NewEngineManager(ctx)
if err != nil {
return stacktrace.Propagate(err, "An error occurred creating an engine manager")
Expand All @@ -113,14 +126,13 @@ func run(_ context.Context, flags *flags.ParsedFlags, _ *args.ParsedArgs) error
if engineVersion == defaultEngineVersion && isDebugMode {
engineDebugVersion := fmt.Sprintf("%s-%s", kurtosis_version.KurtosisVersion, defaults.DefaultKurtosisContainerDebugImageNameSuffix)
logrus.Infof("Starting Kurtosis engine in debug mode from image '%v%v%v'...", kurtosisTechEngineImagePrefix, imageVersionDelimiter, engineDebugVersion)
_, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithCustomVersion(ctx, engineDebugVersion, logLevel, enclavePoolSize, true)
_, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithCustomVersion(ctx, engineDebugVersion, logLevel, enclavePoolSize, true, githubAuthTokenOverride)
} else if engineVersion == defaultEngineVersion {

logrus.Infof("Starting Kurtosis engine from image '%v%v%v'...", kurtosisTechEngineImagePrefix, imageVersionDelimiter, kurtosis_version.KurtosisVersion)
_, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, logLevel, enclavePoolSize)
_, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, logLevel, enclavePoolSize, githubAuthTokenOverride)
} else {
logrus.Infof("Starting Kurtosis engine from image '%v%v%v'...", kurtosisTechEngineImagePrefix, imageVersionDelimiter, engineVersion)
_, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithCustomVersion(ctx, engineVersion, logLevel, enclavePoolSize, defaults.DefaultEnableDebugMode)
_, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithCustomVersion(ctx, engineVersion, logLevel, enclavePoolSize, defaults.DefaultEnableDebugMode, githubAuthTokenOverride)
}
if startEngineErr != nil {
return stacktrace.Propagate(startEngineErr, "An error occurred starting the Kurtosis engine")
Expand Down
25 changes: 25 additions & 0 deletions cli/cli/commands/github/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package github

import (
"github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts"
"github.com/kurtosis-tech/kurtosis/cli/cli/commands/github/login"
"github.com/kurtosis-tech/kurtosis/cli/cli/commands/github/logout"
"github.com/kurtosis-tech/kurtosis/cli/cli/commands/github/status"
"github.com/kurtosis-tech/kurtosis/cli/cli/commands/github/token"
"github.com/spf13/cobra"
)

// GitHubCmd Suppressing exhaustruct requirement because this struct has ~40 properties
// nolint: exhaustruct
var GitHubCmd = &cobra.Command{
Use: command_str_consts.GitHubCmdStr,
Short: "Manage GitHub login",
RunE: nil,
}

func init() {
GitHubCmd.AddCommand(login.LoginCmd.MustGetCobraCommand())
GitHubCmd.AddCommand(logout.LogoutCmd.MustGetCobraCommand())
GitHubCmd.AddCommand(status.StatusCmd.MustGetCobraCommand())
GitHubCmd.AddCommand(token.TokenCmd.MustGetCobraCommand())
}
50 changes: 50 additions & 0 deletions cli/cli/commands/github/login/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package login

import (
"context"
"fmt"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts"
"github.com/kurtosis-tech/kurtosis/cli/cli/helpers/github_auth_store"
"github.com/kurtosis-tech/kurtosis/cli/cli/helpers/oauth"
"github.com/kurtosis-tech/kurtosis/cli/cli/out"
"github.com/kurtosis-tech/stacktrace"
)

var LoginCmd = &lowlevel.LowlevelKurtosisCommand{
CommandStr: command_str_consts.GitHubLoginCmdStr,
ShortDescription: "Authorizes Kurtosis CLI on behalf of a Github user.",
LongDescription: "Authorizes Kurtosis CLI to perform git operations on behalf of a GitHub user such as retrieving packages in private repositories.",
Args: nil,
Flags: nil,
PreValidationAndRunFunc: nil,
RunFunc: run,
PostValidationAndRunFunc: nil,
}

func run(_ context.Context, _ *flags.ParsedFlags, _ *args.ParsedArgs) error {
githubAuthStore, err := github_auth_store.GetGitHubAuthStore()
if err != nil {
return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth store.")
}
username, err := githubAuthStore.GetUser()
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting user to see if user already exists.")
}
if username != "" {
out.PrintOutLn(fmt.Sprintf("Logged in as GitHub user: %v", username))
return nil
}
authToken, username, err := oauth.AuthFlow()
if err != nil {
return stacktrace.Propagate(err, "An error occurred in the Github OAuth flow.")
}
err = githubAuthStore.SetUser(username, authToken)
if err != nil {
return stacktrace.Propagate(err, "An error occurred setting GitHub user: %v", username)
}
out.PrintOutLn(fmt.Sprintf("Successfully logged in GitHub user: %v", username))
return nil
}
45 changes: 45 additions & 0 deletions cli/cli/commands/github/logout/logout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package logout

import (
"context"
"fmt"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts"
"github.com/kurtosis-tech/kurtosis/cli/cli/helpers/github_auth_store"
"github.com/kurtosis-tech/kurtosis/cli/cli/out"
"github.com/kurtosis-tech/stacktrace"
)

var LogoutCmd = &lowlevel.LowlevelKurtosisCommand{
CommandStr: command_str_consts.GitHubLogoutCmdStr,
ShortDescription: "Logs out a GitHub user from Kurtosis CLI",
LongDescription: "Logs out a GitHub user from Kurtosis CLI by removing their GitHub user info and auth token from Kurtosis CLI config",
Args: nil,
Flags: nil,
PreValidationAndRunFunc: nil,
RunFunc: run,
PostValidationAndRunFunc: nil,
}

func run(_ context.Context, _ *flags.ParsedFlags, _ *args.ParsedArgs) error {
githubAuthStore, err := github_auth_store.GetGitHubAuthStore()
if err != nil {
return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth configuration.")
}
username, err := githubAuthStore.GetUser()
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting user to see if user already exists.")
}
if username == "" {
out.PrintOutLn("No GitHub user logged into Kurtosis CLI: %v")
return nil
}
err = githubAuthStore.RemoveUser()
if err != nil {
return stacktrace.Propagate(err, "An error occurred logging out GitHub user: %v", username)
}
out.PrintOutLn(fmt.Sprintf("Successfully logged GitHub user '%v' out of Kurtosis CLI", username))
return nil
}
41 changes: 41 additions & 0 deletions cli/cli/commands/github/status/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package status

import (
"context"
"fmt"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts"
"github.com/kurtosis-tech/kurtosis/cli/cli/helpers/github_auth_store"
"github.com/kurtosis-tech/kurtosis/cli/cli/out"
"github.com/kurtosis-tech/stacktrace"
)

var StatusCmd = &lowlevel.LowlevelKurtosisCommand{
CommandStr: command_str_consts.GitHubStatusCmdStr,
ShortDescription: "Displays GitHub auth info",
LongDescription: "Displays GitHub auth info by showing a logged in user's info or whether no GitHub user is logged into Kurtosis CLI",
Args: nil,
Flags: nil,
PreValidationAndRunFunc: nil,
RunFunc: run,
PostValidationAndRunFunc: nil,
}

func run(_ context.Context, _ *flags.ParsedFlags, _ *args.ParsedArgs) error {
githubAuthStore, err := github_auth_store.GetGitHubAuthStore()
if err != nil {
return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth configuration.")
}
username, err := githubAuthStore.GetUser()
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting user to see if user already exists.")
}
if username == "" {
out.PrintOutLn("No GitHub user logged into Kurtosis CLI")
return nil
}
out.PrintOutLn(fmt.Sprintf("Logged in as GitHub user: %v", username))
return nil
}
44 changes: 44 additions & 0 deletions cli/cli/commands/github/token/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package token

import (
"context"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts"
"github.com/kurtosis-tech/kurtosis/cli/cli/helpers/github_auth_store"
"github.com/kurtosis-tech/kurtosis/cli/cli/out"
"github.com/kurtosis-tech/stacktrace"
)

var TokenCmd = &lowlevel.LowlevelKurtosisCommand{
CommandStr: command_str_consts.GitHubTokenCmdStr,
ShortDescription: "Displays GitHub auth token used if a user is logged in",
LongDescription: "Displays GitHub auth token used if a user is logged in",
Args: nil,
Flags: nil,
PreValidationAndRunFunc: nil,
RunFunc: run,
PostValidationAndRunFunc: nil,
}

func run(_ context.Context, _ *flags.ParsedFlags, _ *args.ParsedArgs) error {
githubAuthStore, err := github_auth_store.GetGitHubAuthStore()
if err != nil {
return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth store.")
}
username, err := githubAuthStore.GetUser()
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting user to see if user already exists.")
}
if username == "" {
out.PrintOutLn("No GitHub user currently logged in.")
return nil
}
authToken, err := githubAuthStore.GetAuthToken()
if err != nil {
return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth token for user: %v.", username)
}
out.PrintOutLn(authToken)
return nil
}
2 changes: 1 addition & 1 deletion cli/cli/commands/kurtosis_context/set/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func SetContext(
return stacktrace.Propagate(err, "An error occurred creating an engine manager for the new context.")
}

_, engineClientCloseFunc, startEngineErr := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, logrus.InfoLevel, defaults.DefaultEngineEnclavePoolSize)
_, engineClientCloseFunc, startEngineErr := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, logrus.InfoLevel, defaults.DefaultEngineEnclavePoolSize, defaults.DefaultGitHubAuthTokenOverride)
if startEngineErr != nil {
logrus.Warnf("The context was successfully set to '%s' but Kurtosis failed to start an engine in "+
"this new context. A new engine should be started manually with '%s %s %s'. The error was:\n%v",
Expand Down
Loading

0 comments on commit 2f0d638

Please sign in to comment.