Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
feat(resource-service): Compute git auth method once per API request (#…
Browse files Browse the repository at this point in the history
…8946)

feat(resource-service): Compute git auth method once per API request (#8824)

* feat(resource-service): Compute git auth method once per API request

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* pr review

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* additional tests

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>
(cherry picked from commit 2ebdc86)

Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com>
  • Loading branch information
bacherfl and odubajDT committed Oct 5, 2022
1 parent e769b7e commit 9efdf4a
Show file tree
Hide file tree
Showing 13 changed files with 360 additions and 470 deletions.
85 changes: 6 additions & 79 deletions resource-service/common/git.go
@@ -1,13 +1,10 @@
package common

import (
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
nethttp "net/http"
"net/url"
"os"
"strings"
"time"
Expand All @@ -16,14 +13,9 @@ import (
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/client"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/keptn/keptn/resource-service/common_models"
kerrors "github.com/keptn/keptn/resource-service/errors"
logger "github.com/sirupsen/logrus"
ssh2 "golang.org/x/crypto/ssh"
)

// IGit provides functions to interact with the git repository of a project
Expand Down Expand Up @@ -79,55 +71,6 @@ func getGitKeptnEmail() string {
return gitKeptnEmailDefault
}

func getAuthMethod(gitContext common_models.GitContext) (transport.AuthMethod, error) {
if gitContext.Credentials.SshAuth != nil {
publicKey, err := ssh.NewPublicKeys("git", []byte(gitContext.Credentials.SshAuth.PrivateKey), gitContext.Credentials.SshAuth.PrivateKeyPass)
if err != nil {
return nil, err
}
publicKey.HostKeyCallback = ssh2.InsecureIgnoreHostKey()
return publicKey, nil

} else if gitContext.Credentials.HttpsAuth != nil {
if gitContext.Credentials.HttpsAuth.Proxy != nil {
customClient := &nethttp.Client{
Transport: &nethttp.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: gitContext.Credentials.HttpsAuth.InsecureSkipTLS},
Proxy: nethttp.ProxyURL(&url.URL{
Scheme: gitContext.Credentials.HttpsAuth.Proxy.Scheme,
User: url.UserPassword(gitContext.Credentials.HttpsAuth.Proxy.User, gitContext.Credentials.HttpsAuth.Proxy.Password),
Host: gitContext.Credentials.HttpsAuth.Proxy.URL,
}),
},

// 15 second timeout
Timeout: 15 * time.Second,

// don't follow redirect
CheckRedirect: func(req *nethttp.Request, via []*nethttp.Request) error {
return nethttp.ErrUseLastResponse
},
}

// Istalling https protocol as a default one means that all the proxy traffic will be routed via secure connection
// To use unsecure conenction, InsecureSkipTLS parameter should be set to true and https protocol will be used without TLS verification
client.InstallProtocol("https", http.NewClient(customClient))
}

if gitContext.Credentials.User == "" {
//we try the authentication anyway since in most git servers
//any user apart from an empty string is fine when we use a token
//this auth will fail in case user is using bitbucket
gitContext.Credentials.User = "keptnuser"
}
return &http.BasicAuth{
Username: gitContext.Credentials.User,
Password: gitContext.Credentials.HttpsAuth.Token,
}, nil
}
return nil, nil
}

func (g Git) CloneRepo(gitContext common_models.GitContext) (bool, error) {
if (gitContext == common_models.GitContext{}) || (*gitContext.Credentials == common_models.GitCredentials{}) {
return false, fmt.Errorf(kerrors.ErrMsgCouldNotGitAction, "clone", "project", kerrors.ErrInvalidGitContext)
Expand All @@ -142,14 +85,10 @@ func (g Git) CloneRepo(gitContext common_models.GitContext) (bool, error) {
if err != nil {
return false, fmt.Errorf(kerrors.ErrMsgCouldNotCreatePath, projectPath, err)
}
auth, err := getAuthMethod(gitContext)
if err != nil {
return false, err
}
clone, err := g.git.PlainClone(projectPath, false,
&git.CloneOptions{
URL: gitContext.Credentials.RemoteURL,
Auth: auth,
Auth: gitContext.AuthMethod,
InsecureSkipTLS: retrieveInsecureSkipTLS(gitContext.Credentials),
},
)
Expand Down Expand Up @@ -313,13 +252,9 @@ func (g Git) Push(gitContext common_models.GitContext) error {
if err != nil {
return fmt.Errorf(kerrors.ErrMsgCouldNotGitAction, "push", gitContext.Project, err)
}
auth, err := getAuthMethod(gitContext)
if err != nil {
return err
}
err = repo.Push(&git.PushOptions{
RemoteName: "origin",
Auth: auth,
Auth: gitContext.AuthMethod,
InsecureSkipTLS: retrieveInsecureSkipTLS(gitContext.Credentials),
})
if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
Expand All @@ -342,20 +277,16 @@ func (g *Git) Pull(gitContext common_models.GitContext) error {
if err != nil {
return fmt.Errorf(kerrors.ErrMsgCouldNotGitAction, "pull", gitContext.Project, err)
}
auth, err := getAuthMethod(gitContext)
if err != nil {
return err
}
err = w.Pull(&git.PullOptions{
RemoteName: "origin",
Force: true,
ReferenceName: head.Name(),
Auth: auth,
Auth: gitContext.AuthMethod,
InsecureSkipTLS: retrieveInsecureSkipTLS(gitContext.Credentials),
})
if err != nil && errors.Is(err, plumbing.ErrReferenceNotFound) {
// reference not there yet
err = w.Pull(&git.PullOptions{RemoteName: "origin", Force: true, Auth: auth, InsecureSkipTLS: retrieveInsecureSkipTLS(gitContext.Credentials)})
err = w.Pull(&git.PullOptions{RemoteName: "origin", Force: true, Auth: gitContext.AuthMethod, InsecureSkipTLS: retrieveInsecureSkipTLS(gitContext.Credentials)})
}
if err != nil && errors.Is(err, git.ErrNonFastForwardUpdate) {
return fmt.Errorf(kerrors.ErrMsgCouldNotGitAction, "pull", gitContext.Project, err)
Expand Down Expand Up @@ -499,18 +430,14 @@ func (g *Git) checkoutBranch(gitContext common_models.GitContext, options *git.C
}

func (g *Git) fetch(gitContext common_models.GitContext, r *git.Repository) error {
auth, err := getAuthMethod(gitContext)
if err != nil {
return err
}
if err = r.Fetch(&git.FetchOptions{
if err := r.Fetch(&git.FetchOptions{
RemoteName: "origin",
RefSpecs: []config.RefSpec{"+refs/*:refs/*"},
// <src>:<dst>, + update the reference even if it isn’t a fast-forward.
//// take all branch from remote and put them in the local repo as origin branches and as branches
//RefSpecs: []config.RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/heads/*:refs/heads/*"},
Force: true,
Auth: auth,
Auth: gitContext.AuthMethod,
InsecureSkipTLS: retrieveInsecureSkipTLS(gitContext.Credentials),
}); err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
return err
Expand Down
117 changes: 0 additions & 117 deletions resource-service/common/gitsuite_test.go
Expand Up @@ -18,7 +18,6 @@ import (
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/storage/memory"
apimodels "github.com/keptn/go-utils/pkg/api/models"
Expand Down Expand Up @@ -1080,122 +1079,6 @@ func (s *BaseSuite) Test_getGitKeptnEmail(c *C) {

}

func Test_getAuthMethod(t *testing.T) {
tests := []struct {
name string
gitContext common_models.GitContext
wantErr bool
expectedOutput transport.AuthMethod
}{
{
name: "valid credentials",
gitContext: common_models.GitContext{
Credentials: &common_models.GitCredentials{
RemoteURL: "https://some.url",
HttpsAuth: &apimodels.HttpsGitAuth{
Token: "some-token",
InsecureSkipTLS: false,
},
User: "user",
},
Project: "my-proj",
},
wantErr: false,
expectedOutput: &http.BasicAuth{
Username: "user",
Password: "some-token",
},
},
{
name: "valid credentials no user",
gitContext: common_models.GitContext{
Credentials: &common_models.GitCredentials{
RemoteURL: "https://some.url",
HttpsAuth: &apimodels.HttpsGitAuth{
Token: "some-token",
InsecureSkipTLS: false,
},
User: "",
},
Project: "my-proj",
},
wantErr: false,
expectedOutput: &http.BasicAuth{
Username: "keptnuser",
Password: "some-token",
},
},
{
name: "invalid credentials",
gitContext: common_models.GitContext{
Credentials: &common_models.GitCredentials{
RemoteURL: "https://some.url",
HttpsAuth: &apimodels.HttpsGitAuth{
InsecureSkipTLS: false,
},
User: "user",
},
Project: "my-proj",
},
wantErr: false,
expectedOutput: nil,
},
{
name: "invalid ssh credentials",
gitContext: common_models.GitContext{
Credentials: &common_models.GitCredentials{
RemoteURL: "ssh://some.url",
SshAuth: &apimodels.SshGitAuth{
PrivateKey: "private-key",
PrivateKeyPass: "password",
},
User: "user",
},
Project: "my-proj",
},
wantErr: true,
expectedOutput: nil,
},
{
name: "dumb credentials",
gitContext: common_models.GitContext{
Credentials: &common_models.GitCredentials{
RemoteURL: "ssh://some.url",
HttpsAuth: &apimodels.HttpsGitAuth{
Token: "some",
InsecureSkipTLS: false,
Proxy: &apimodels.ProxyGitAuth{
URL: "",
Scheme: "",
User: "hate",
Password: "",
},
},
SshAuth: &apimodels.SshGitAuth{
PrivateKey: "",
PrivateKeyPass: "password",
},
User: "user",
},
Project: "my-proj",
},
wantErr: true,
expectedOutput: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
auth, err := getAuthMethod(tt.gitContext)
if (err != nil) != tt.wantErr {
t.Errorf("getAuthMethod() error = %v, wantErr %v", err, tt.wantErr)
}
if err != nil && auth != tt.expectedOutput {
t.Errorf("getAuthMethod() auth = %v, expectedOutput %v", err, tt.wantErr)
}
})
}
}

func (s *BaseSuite) NewGitContext() common_models.GitContext {
return common_models.GitContext{
Project: "sockshop",
Expand Down
2 changes: 2 additions & 0 deletions resource-service/common_models/git.go
Expand Up @@ -6,6 +6,7 @@ import (

apimodels "github.com/keptn/go-utils/pkg/api/models"

"github.com/go-git/go-git/v5/plumbing/transport"
kerrors "github.com/keptn/keptn/resource-service/errors"
)

Expand All @@ -15,6 +16,7 @@ type GitCredentials apimodels.GitAuthCredentials
type GitContext struct {
Project string
Credentials *GitCredentials
AuthMethod transport.AuthMethod
}

func (g GitCredentials) Validate() error {
Expand Down
1 change: 1 addition & 0 deletions resource-service/errors/errors.go
Expand Up @@ -99,6 +99,7 @@ var ErrInvalidCredentials = New("credentials need to have ssh or http auth metho
// Error messages

const ErrMsgCouldNotRetrieveCredentials = "could not read credentials for project %s: %w"
const ErrMsgCouldNotEstablishAuthMethod = "could not establish auth method for project %s: %w"
const ErrMsgInvalidRequestFormat = "Invalid request format"
const ErrMsgCouldNotSetUser = "could not set git user: %w"
const ErrMsgCouldNotCreatePath = "could not create path %s: %w"
Expand Down
60 changes: 60 additions & 0 deletions resource-service/handler/common.go
@@ -1,13 +1,24 @@
package handler

import (
"crypto/tls"
"errors"
"time"

"net/http"
"net/url"

"github.com/gin-gonic/gin"
"github.com/keptn/keptn/resource-service/common_models"
errors2 "github.com/keptn/keptn/resource-service/errors"
"github.com/keptn/keptn/resource-service/models"
logger "github.com/sirupsen/logrus"

"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/client"
nethttp "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
ssh2 "golang.org/x/crypto/ssh"
)

const pathParamProjectName = "projectName"
Expand Down Expand Up @@ -62,6 +73,55 @@ func resourceNotFound(err error) (bool, string) {
return false, ""
}

func getAuthMethod(credentials *common_models.GitCredentials) (transport.AuthMethod, error) {
if credentials.SshAuth != nil {
publicKey, err := ssh.NewPublicKeys("git", []byte(credentials.SshAuth.PrivateKey), credentials.SshAuth.PrivateKeyPass)
if err != nil {
return nil, err
}
publicKey.HostKeyCallback = ssh2.InsecureIgnoreHostKey()
return publicKey, nil

} else if credentials.HttpsAuth != nil {
if credentials.HttpsAuth.Proxy != nil {
customClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: credentials.HttpsAuth.InsecureSkipTLS},
Proxy: http.ProxyURL(&url.URL{
Scheme: credentials.HttpsAuth.Proxy.Scheme,
User: url.UserPassword(credentials.HttpsAuth.Proxy.User, credentials.HttpsAuth.Proxy.Password),
Host: credentials.HttpsAuth.Proxy.URL,
}),
},

// 15 second timeout
Timeout: 15 * time.Second,

// don't follow redirect
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}

// Istalling https protocol as a default one means that all the proxy traffic will be routed via secure connection
// To use unsecure conenction, InsecureSkipTLS parameter should be set to true and https protocol will be used without TLS verification
client.InstallProtocol("https", nethttp.NewClient(customClient))
}

if credentials.User == "" {
//we try the authentication anyway since in most git servers
//any user apart from an empty string is fine when we use a token
//this auth will fail in case user is using bitbucket
credentials.User = "keptnuser"
}
return &nethttp.BasicAuth{
Username: credentials.User,
Password: credentials.HttpsAuth.Token,
}, nil
}
return nil, nil
}

func SetFailedDependencyErrorResponse(c *gin.Context, msg string) {
c.JSON(http.StatusFailedDependency, models.Error{
Code: http.StatusFailedDependency,
Expand Down

0 comments on commit 9efdf4a

Please sign in to comment.