Skip to content

[git] implement scope elevation in server #3565

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

Merged
merged 2 commits into from
Mar 27, 2021
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
4 changes: 4 additions & 0 deletions components/gitpod-cli/BUILD.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ packages:
env:
- CGO_ENABLED=0
- GOOS=linux
deps:
- components/common-go:lib
- components/supervisor-api/go:lib
- components/gitpod-protocol/go:lib
config:
packaging: app
80 changes: 64 additions & 16 deletions components/gitpod-cli/cmd/credential-helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@ package cmd

import (
"bufio"
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"google.golang.org/grpc"

"github.com/gitpod-io/gitpod/gitpod-cli/pkg/theialib"
supervisor "github.com/gitpod-io/gitpod/supervisor/api"
)

var credentialHelper = &cobra.Command{
Expand All @@ -28,6 +33,12 @@ var credentialHelper = &cobra.Command{
Hidden: true,
Run: func(cmd *cobra.Command, args []string) {
action := args[0]
log.SetOutput(io.Discard)
f, err := os.OpenFile(os.TempDir()+"/gitpod-git-credential-helper.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err == nil {
defer f.Close()
log.SetOutput(f)
}
if action != "get" {
return
}
Expand All @@ -44,39 +55,76 @@ var credentialHelper = &cobra.Command{
fmt.Printf("username=%s\npassword=%s\n", user, token)
}()

log.SetOutput(io.Discard)
f, err := os.OpenFile(os.TempDir()+"/gitpod-git-credential-helper.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err == nil {
defer f.Close()
log.SetOutput(f)
}

url, gitCommand := parsePstree()
repoURL, gitCommand := parsePstree()
host := parseHostFromStdin()
if len(host) == 0 {
log.Println("'host' is missing")
}

service, err := theialib.NewServiceFromEnv()
if isTheiaIDE() {
service, err := theialib.NewServiceFromEnv()
if err != nil {
log.WithError(err).Print("cannot connect to Theia")
return
}
if action == "get" {
resp, err := service.GetGitToken(theialib.GetGitTokenRequest{
Command: gitCommand,
Host: host,
RepoURL: repoURL,
})
if err != nil {
log.WithError(err).Print("cannot get token")
return
}
user = resp.User
token = resp.Token
}
return
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
supervisorAddr := os.Getenv("SUPERVISOR_ADDR")
if supervisorAddr == "" {
supervisorAddr = "localhost:22999"
}
supervisorConn, err := grpc.Dial(supervisorAddr, grpc.WithInsecure())
if err != nil {
log.WithError(err).Print("cannot connect to Theia")
log.WithError(err).Print("error connecting to supervisor")
return
}

resp, err := service.GetGitToken(theialib.GetGitTokenRequest{
Command: gitCommand,
Host: host,
RepoURL: url,
resp, err := supervisor.NewTokenServiceClient(supervisorConn).GetToken(ctx, &supervisor.GetTokenRequest{
Host: host,
Kind: "git",
})
if err != nil {
log.WithError(err).Print("cannot get token")
log.WithError(err).Print("error getting token from supervisior")
return
}

validator := exec.Command("/proc/self/exe", "git-token-validator",
"--user", resp.User, "--token", resp.Token, "--scopes", strings.Join(resp.Scope, ","),
"--host", host, "--repoURL", repoURL, "--gitCommand", gitCommand)
err = validator.Start()
if err != nil {
log.WithError(err).Print("error spawning validator")
return
}
err = validator.Process.Release()
if err != nil {
log.WithError(err).Print("error releasing validator")
return
}
user = resp.User
token = resp.Token
},
}

func isTheiaIDE() bool {
stat, err := os.Stat("/theia")
return !errors.Is(os.ErrNotExist, err) && stat != nil && stat.IsDir()
}

func parseHostFromStdin() string {
host := ""
scanner := bufio.NewScanner(os.Stdin)
Expand Down
121 changes: 121 additions & 0 deletions components/gitpod-cli/cmd/git-token-validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package cmd

import (
"context"
"fmt"
"os"
"strings"
"time"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"google.golang.org/grpc"

serverapi "github.com/gitpod-io/gitpod/gitpod-protocol"
supervisor "github.com/gitpod-io/gitpod/supervisor/api"
)

var gitTokenValidatorOpts struct {
User string
Token string
TokenScopes string
Host string
RepoURL string
GitCommand string
}

var gitTokenValidator = &cobra.Command{
Use: "git-token-validator",
Short: "Gitpod's Git token validator",
Long: "Tries to guess the scopes needed for a git operation and requests an appropriate token.",
Args: cobra.ExactArgs(0),
Hidden: true,
Run: func(cmd *cobra.Command, args []string) {
log.Infof("gp git-token-validator")

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
supervisorAddr := os.Getenv("SUPERVISOR_ADDR")
if supervisorAddr == "" {
supervisorAddr = "localhost:22999"
}
supervisorConn, err := grpc.Dial(supervisorAddr, grpc.WithInsecure())
if err != nil {
log.WithError(err).Fatal("error connecting to supervisor")
}
wsinfo, err := supervisor.NewInfoServiceClient(supervisorConn).WorkspaceInfo(ctx, &supervisor.WorkspaceInfoRequest{})
if err != nil {
log.WithError(err).Fatal("error getting workspace info from supervisor")
}
clientToken, err := supervisor.NewTokenServiceClient(supervisorConn).GetToken(ctx, &supervisor.GetTokenRequest{
Host: wsinfo.GitpodApi.Host,
Kind: "gitpod",
Scope: []string{
"function:guessGitTokenScopes",
},
})
if err != nil {
log.WithError(err).Fatal("error getting token from supervisor")
}
client, err := serverapi.ConnectToServer(wsinfo.GitpodApi.Endpoint, serverapi.ConnectToServerOpts{Token: clientToken.Token, Context: ctx})
if err != nil {
log.WithError(err).Fatal("error connecting to server")
}
params := &serverapi.GuessGitTokenScopesParams{
Host: gitTokenValidatorOpts.Host,
RepoURL: gitTokenValidatorOpts.RepoURL,
GitCommand: gitTokenValidatorOpts.GitCommand,
CurrentToken: &serverapi.GitToken{
Token: gitTokenValidatorOpts.Token,
Scopes: strings.Split(gitTokenValidatorOpts.TokenScopes, ","),
User: gitTokenValidatorOpts.User,
},
}
guessedTokenScopes, err := client.GuessGitTokenScopes(ctx, params)
if err != nil {
log.WithError(err).Fatal("error guessing token scopes on server")
}
if guessedTokenScopes.Message != "" {
message := fmt.Sprintf("%s Please check the permissions on the [access control page](%s/access-control).", guessedTokenScopes.Message, wsinfo.GetGitpodHost())
_, err := supervisor.NewNotificationServiceClient(supervisorConn).Notify(ctx,
&supervisor.NotifyRequest{
Level: supervisor.NotifyRequest_INFO,
Message: message,
})
log.WithError(err).Fatalf("error notifying client: '%s'", message)
}
if len(guessedTokenScopes.Scopes) > 0 {
_, err = supervisor.NewTokenServiceClient(supervisorConn).GetToken(ctx,
&supervisor.GetTokenRequest{
Host: gitTokenValidatorOpts.Host,
Scope: guessedTokenScopes.Scopes,
Description: "",
Kind: "git",
})
if err != nil {
log.WithError(err).Fatal("error getting new token from token service")
return
}
}
},
}

func init() {
rootCmd.AddCommand(gitTokenValidator)
gitTokenValidator.Flags().StringVarP(&gitTokenValidatorOpts.User, "user", "u", "", "Git user")
gitTokenValidator.Flags().StringVarP(&gitTokenValidatorOpts.Token, "token", "t", "", "The Git token to be validated")
gitTokenValidator.Flags().StringVarP(&gitTokenValidatorOpts.TokenScopes, "scopes", "s", "", "A comma spearated list of the scopes of given token")
gitTokenValidator.Flags().StringVar(&gitTokenValidatorOpts.Host, "host", "", "The Git host")
gitTokenValidator.Flags().StringVarP(&gitTokenValidatorOpts.RepoURL, "repoURL", "r", "", "The URL of the Git repository")
gitTokenValidator.Flags().StringVarP(&gitTokenValidatorOpts.GitCommand, "gitCommand", "c", "", "The Git command to be performed")
gitTokenValidator.MarkFlagRequired("user")
gitTokenValidator.MarkFlagRequired("token")
gitTokenValidator.MarkFlagRequired("scopes")
gitTokenValidator.MarkFlagRequired("host")
gitTokenValidator.MarkFlagRequired("repoURL")
gitTokenValidator.MarkFlagRequired("gitCommand")
}
63 changes: 60 additions & 3 deletions components/gitpod-cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,73 @@ go 1.16

require (
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/gitpod-io/gitpod/gitpod-protocol v0.0.0-00010101000000-000000000000
github.com/gitpod-io/gitpod/supervisor/api v0.0.0-00010101000000-000000000000
github.com/golang/mock v1.4.4
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2
github.com/gorilla/handlers v1.4.2
github.com/manifoldco/promptui v0.3.2
github.com/nicksnyder/go-i18n v1.10.1 // indirect
github.com/pkg/errors v0.8.1
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.7.0
github.com/spf13/cobra v0.0.5
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd
google.golang.org/grpc v1.36.0
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect
gopkg.in/yaml.v2 v2.2.2
gopkg.in/yaml.v2 v2.4.0
)

replace github.com/gitpod-io/gitpod/common-go => ../common-go // leeway

replace github.com/gitpod-io/gitpod/gitpod-protocol => ../gitpod-protocol/go // leeway

replace github.com/gitpod-io/gitpod/supervisor/api => ../supervisor-api/go // leeway

replace k8s.io/api => k8s.io/api v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/apimachinery => k8s.io/apimachinery v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/apiserver => k8s.io/apiserver v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/client-go => k8s.io/client-go v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/cloud-provider => k8s.io/cloud-provider v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/code-generator => k8s.io/code-generator v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/component-base => k8s.io/component-base v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/cri-api => k8s.io/cri-api v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/kube-proxy => k8s.io/kube-proxy v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/kubelet => k8s.io/kubelet v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/metrics => k8s.io/metrics v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/component-helpers => k8s.io/component-helpers v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/controller-manager => k8s.io/controller-manager v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/kubectl => k8s.io/kubectl v0.20.4 // leeway indirect from components/common-go:lib

replace k8s.io/mount-utils => k8s.io/mount-utils v0.20.4 // leeway indirect from components/common-go:lib
Loading