Skip to content

Commit

Permalink
Added socks5 proxy support for ssh based git URL, upgraded go-git to …
Browse files Browse the repository at this point in the history
…5.10.1 (argoproj#15864)

Signed-off-by: Anand Francis Joseph <[email protected]>

(cherry picked from commit 9b27aeb)
  • Loading branch information
anandf authored and oleksandr-codefresh committed Feb 7, 2024
1 parent 8e408a5 commit 9ded761
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ RUN groupadd -g $ARGOCD_USER_ID argocd && \
apt-get update && \
apt-get dist-upgrade -y && \
apt-get install -y \
git git-lfs tini gpg tzdata && \
git git-lfs tini gpg tzdata connect-proxy && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Expand Down
6 changes: 6 additions & 0 deletions cmd/argocd/commands/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
# Add a Git repository via SSH on a non-default port - need to use ssh:// style URLs here
argocd repo add ssh://[email protected]:2222/repos/repo --ssh-private-key-path ~/id_rsa
# Add a Git repository via SSH using socks5 proxy with no proxy credentials
argocd repo add ssh://[email protected]/argoproj/argocd-example-apps --ssh-private-key-path ~/id_rsa --proxy socks5://your.proxy.server.ip:1080
# Add a Git repository via SSH using socks5 proxy with proxy credentials
argocd repo add ssh://[email protected]/argoproj/argocd-example-apps --ssh-private-key-path ~/id_rsa --proxy socks5://username:[email protected]:1080
# Add a private Git repository via HTTPS using username/password and TLS client certificates:
argocd repo add https://git.example.com/repos/repo --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key
Expand Down
6 changes: 6 additions & 0 deletions docs/user-guide/commands/argocd_repo_add.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ argocd repo add REPOURL [flags]
# Add a Git repository via SSH on a non-default port - need to use ssh:// style URLs here
argocd repo add ssh://[email protected]:2222/repos/repo --ssh-private-key-path ~/id_rsa
# Add a Git repository via SSH using socks5 proxy with no proxy credentials
argocd repo add ssh://[email protected]/argoproj/argocd-example-apps --ssh-private-key-path ~/id_rsa --proxy socks5://your.proxy.server.ip:1080
# Add a Git repository via SSH using socks5 proxy with proxy credentials
argocd repo add ssh://[email protected]/argoproj/argocd-example-apps --ssh-private-key-path ~/id_rsa --proxy socks5://username:[email protected]:1080
# Add a private Git repository via HTTPS using username/password and TLS client certificates:
argocd repo add https://git.example.com/repos/repo --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ require (
github.com/evanphx/json-patch v5.6.0+incompatible
github.com/fsnotify/fsnotify v1.6.0
github.com/gfleury/go-bitbucket-v1 v0.0.0-20220301131131-8e7ed04b843e
github.com/go-git/go-git/v5 v5.11.0
github.com/go-logr/logr v1.2.4
github.com/go-git/go-git/v5 v5.10.1
github.com/go-logr/logr v1.3.0
github.com/go-openapi/loads v0.21.2
github.com/go-openapi/runtime v0.26.0
github.com/go-playground/webhooks/v6 v6.3.0
Expand Down Expand Up @@ -203,7 +203,7 @@ require (
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.3 // indirect
Expand Down Expand Up @@ -288,7 +288,7 @@ require (
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.19.0
golang.org/x/net v0.18.0
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1024,13 +1024,13 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmS
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk=
github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
Expand All @@ -1047,8 +1047,8 @@ github.com/go-logr/logr v1.0.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk=
Expand Down Expand Up @@ -2272,8 +2272,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/application/v1alpha1/repository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func (repo *Repository) GetGitCreds(store git.CredsStore) git.Creds {
return git.NewHTTPSCreds(repo.Username, repo.Password, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure(), repo.Proxy, store, repo.ForceHttpBasicAuth)
}
if repo.SSHPrivateKey != "" {
return git.NewSSHCreds(repo.SSHPrivateKey, getCAPath(repo.Repo), repo.IsInsecure(), store)
return git.NewSSHCreds(repo.SSHPrivateKey, getCAPath(repo.Repo), repo.IsInsecure(), store, repo.Proxy)
}
if repo.GithubAppPrivateKey != "" && repo.GithubAppId != 0 && repo.GithubAppInstallationId != 0 {
return git.NewGitHubAppCreds(repo.GithubAppId, repo.GithubAppInstallationId, repo.GithubAppPrivateKey, repo.GitHubAppEnterpriseBaseURL, repo.Repo, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure(), repo.Proxy, store)
Expand Down
1 change: 0 additions & 1 deletion util/git/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,6 @@ func (m *nativeGitClient) runCmdOutput(cmd *exec.Cmd, ropts runOpts) (string, er
}
}
}

cmd.Env = proxy.UpsertEnv(cmd, m.proxy)
opts := executil.ExecRunOpts{
TimeoutBehavior: argoexec.TimeoutBehavior{
Expand Down
24 changes: 22 additions & 2 deletions util/git/creds.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
"net/url"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -241,10 +242,11 @@ type SSHCreds struct {
caPath string
insecure bool
store CredsStore
proxy string
}

func NewSSHCreds(sshPrivateKey string, caPath string, insecureIgnoreHostKey bool, store CredsStore) SSHCreds {
return SSHCreds{sshPrivateKey, caPath, insecureIgnoreHostKey, store}
func NewSSHCreds(sshPrivateKey string, caPath string, insecureIgnoreHostKey bool, store CredsStore, proxy string) SSHCreds {
return SSHCreds{sshPrivateKey, caPath, insecureIgnoreHostKey, store, proxy}
}

type sshPrivateKeyFile string
Expand Down Expand Up @@ -303,7 +305,25 @@ func (c SSHCreds) Environ() (io.Closer, []string, error) {
knownHostsFile := certutil.GetSSHKnownHostsDataPath()
args = append(args, "-o", "StrictHostKeyChecking=yes", "-o", fmt.Sprintf("UserKnownHostsFile=%s", knownHostsFile))
}
// Handle SSH socks5 proxy settings
proxyEnv := []string{}
if c.proxy != "" {
parsedProxyURL, err := url.Parse(c.proxy)
if err != nil {
return nil, nil, fmt.Errorf("failed to set environment variables related to socks5 proxy, could not parse proxy URL '%s': %w", c.proxy, err)
}
args = append(args, "-o", fmt.Sprintf("ProxyCommand='connect-proxy -S %s:%s -5 %%h %%p'",
parsedProxyURL.Hostname(),
parsedProxyURL.Port()))
if parsedProxyURL.User != nil {
proxyEnv = append(proxyEnv, fmt.Sprintf("SOCKS5_USER=%s", parsedProxyURL.User.Username()))
if socks5_passwd, isPasswdSet := parsedProxyURL.User.Password(); isPasswdSet {
proxyEnv = append(proxyEnv, fmt.Sprintf("SOCKS5_PASSWD=%s", socks5_passwd))
}
}
}
env = append(env, []string{fmt.Sprintf("GIT_SSH_COMMAND=%s", strings.Join(args, " "))}...)
env = append(env, proxyEnv...)
return sshPrivateKeyFile(file.Name()), env, nil
}

Expand Down
72 changes: 71 additions & 1 deletion util/git/creds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func Test_SSHCreds_Environ(t *testing.T) {
caFile := path.Join(tempDir, "caFile")
err := os.WriteFile(caFile, []byte(""), os.FileMode(0600))
require.NoError(t, err)
creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, &NoopCredsStore{})
creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, &NoopCredsStore{}, "")
closer, env, err := creds.Environ()
require.NoError(t, err)
require.Len(t, env, 2)
Expand All @@ -232,6 +232,76 @@ func Test_SSHCreds_Environ(t *testing.T) {
}
}

func Test_SSHCreds_Environ_WithProxy(t *testing.T) {
for _, insecureIgnoreHostKey := range []bool{false, true} {
tempDir := t.TempDir()
caFile := path.Join(tempDir, "caFile")
err := os.WriteFile(caFile, []byte(""), os.FileMode(0600))
require.NoError(t, err)
creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, &NoopCredsStore{}, "socks5://127.0.0.1:1080")
closer, env, err := creds.Environ()
require.NoError(t, err)
require.Len(t, env, 2)

assert.Equal(t, fmt.Sprintf("GIT_SSL_CAINFO=%s/caFile", tempDir), env[0], "CAINFO env var must be set")

assert.True(t, strings.HasPrefix(env[1], "GIT_SSH_COMMAND="))

if insecureIgnoreHostKey {
assert.Contains(t, env[1], "-o StrictHostKeyChecking=no")
assert.Contains(t, env[1], "-o UserKnownHostsFile=/dev/null")
} else {
assert.Contains(t, env[1], "-o StrictHostKeyChecking=yes")
hostsPath := cert.GetSSHKnownHostsDataPath()
assert.Contains(t, env[1], fmt.Sprintf("-o UserKnownHostsFile=%s", hostsPath))
}
assert.Contains(t, env[1], "-o ProxyCommand='connect-proxy -S 127.0.0.1:1080 -5 %h %p'")

envRegex := regexp.MustCompile("-i ([^ ]+)")
assert.Regexp(t, envRegex, env[1])
privateKeyFile := envRegex.FindStringSubmatch(env[1])[1]
assert.FileExists(t, privateKeyFile)
io.Close(closer)
assert.NoFileExists(t, privateKeyFile)
}
}

func Test_SSHCreds_Environ_WithProxyUserNamePassword(t *testing.T) {
for _, insecureIgnoreHostKey := range []bool{false, true} {
tempDir := t.TempDir()
caFile := path.Join(tempDir, "caFile")
err := os.WriteFile(caFile, []byte(""), os.FileMode(0600))
require.NoError(t, err)
creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, &NoopCredsStore{}, "socks5://user:[email protected]:1080")
closer, env, err := creds.Environ()
require.NoError(t, err)
require.Len(t, env, 4)

assert.Equal(t, fmt.Sprintf("GIT_SSL_CAINFO=%s/caFile", tempDir), env[0], "CAINFO env var must be set")

assert.True(t, strings.HasPrefix(env[1], "GIT_SSH_COMMAND="))
assert.Equal(t, "SOCKS5_USER=user", env[2], "SOCKS5 user env var must be set")
assert.Equal(t, "SOCKS5_PASSWD=password", env[3], "SOCKS5 password env var must be set")

if insecureIgnoreHostKey {
assert.Contains(t, env[1], "-o StrictHostKeyChecking=no")
assert.Contains(t, env[1], "-o UserKnownHostsFile=/dev/null")
} else {
assert.Contains(t, env[1], "-o StrictHostKeyChecking=yes")
hostsPath := cert.GetSSHKnownHostsDataPath()
assert.Contains(t, env[1], fmt.Sprintf("-o UserKnownHostsFile=%s", hostsPath))
}
assert.Contains(t, env[1], "-o ProxyCommand='connect-proxy -S 127.0.0.1:1080 -5 %h %p'")

envRegex := regexp.MustCompile("-i ([^ ]+)")
assert.Regexp(t, envRegex, env[1])
privateKeyFile := envRegex.FindStringSubmatch(env[1])[1]
assert.FileExists(t, privateKeyFile)
io.Close(closer)
assert.NoFileExists(t, privateKeyFile)
}
}

const gcpServiceAccountKeyJSON = `{
"type": "service_account",
"project_id": "my-google-project",
Expand Down
20 changes: 20 additions & 0 deletions util/git/workaround.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package git

import (
"fmt"
neturl "net/url"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport"
Expand Down Expand Up @@ -30,6 +33,23 @@ func newClient(url string, insecure bool, creds Creds, proxy string) (transport.

if !IsHTTPSURL(url) && !IsHTTPURL(url) {
// use the default client for protocols other than HTTP/HTTPS
ep.InsecureSkipTLS = insecure
if proxy != "" {
parsedProxyURL, err := neturl.Parse(proxy)
if err != nil {
return nil, nil, fmt.Errorf("failed to create client for url '%s', error parsing proxy url '%s': %w", url, proxy, err)
}
var proxyUsername, proxyPasswd string
if parsedProxyURL.User != nil {
proxyUsername = parsedProxyURL.User.Username()
proxyPasswd, _ = parsedProxyURL.User.Password()
}
ep.Proxy = transport.ProxyOptions{
URL: fmt.Sprintf("%s://%s:%s", parsedProxyURL.Scheme, parsedProxyURL.Hostname(), parsedProxyURL.Port()),
Username: proxyUsername,
Password: proxyPasswd,
}
}
c, err := client.NewClient(ep)
if err != nil {
return nil, nil, err
Expand Down

0 comments on commit 9ded761

Please sign in to comment.