Skip to content

Commit 9ddfd4c

Browse files
racdevAurélien GARNIER
and
Aurélien GARNIER
committed
libgit2: Configured libgit2 clone ProxyOptions
This configures ProxyOptions for all libgit2 Checkout functions when cloning and configures the options based on current environment settings using the git2go.ProxyTypeAuto option. Refs: #131 Signed-off-by: Robert Clarke <rob@robertandrewclarke.com> Co-authored-by: Aurélien GARNIER <aurelien.garnier@atos.net>
1 parent 07d1a4f commit 9ddfd4c

File tree

4 files changed

+292
-1
lines changed

4 files changed

+292
-1
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/Masterminds/semver/v3 v3.1.1
1010
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
1111
github.com/cyphar/filepath-securejoin v0.2.2
12+
github.com/elazarl/goproxy v0.0.0-20211114080932-d06c3be7c11b
1213
github.com/fluxcd/pkg/apis/meta v0.10.2
1314
github.com/fluxcd/pkg/gittestserver v0.5.0
1415
github.com/fluxcd/pkg/gitutil v0.1.0

go.sum

+5-1
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,11 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3
267267
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
268268
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
269269
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
270-
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
271270
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
271+
github.com/elazarl/goproxy v0.0.0-20211114080932-d06c3be7c11b h1:1XqENn2YoYZd6w3Awx+7oa+aR87DFIZJFLF2n1IojA0=
272+
github.com/elazarl/goproxy v0.0.0-20211114080932-d06c3be7c11b/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
273+
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
274+
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
272275
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
273276
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
274277
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
@@ -806,6 +809,7 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
806809
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
807810
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
808811
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
812+
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
809813
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
810814
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
811815
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=

pkg/git/libgit2/checkout.go

+4
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g
6464
FetchOptions: &git2go.FetchOptions{
6565
DownloadTags: git2go.DownloadTagsNone,
6666
RemoteCallbacks: RemoteCallbacks(ctx, opts),
67+
ProxyOptions: git2go.ProxyOptions{Type: git2go.ProxyTypeAuto},
6768
},
6869
CheckoutBranch: c.Branch,
6970
})
@@ -93,6 +94,7 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.
9394
FetchOptions: &git2go.FetchOptions{
9495
DownloadTags: git2go.DownloadTagsAll,
9596
RemoteCallbacks: RemoteCallbacks(ctx, opts),
97+
ProxyOptions: git2go.ProxyOptions{Type: git2go.ProxyTypeAuto},
9698
},
9799
})
98100
if err != nil {
@@ -116,6 +118,7 @@ func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *g
116118
FetchOptions: &git2go.FetchOptions{
117119
DownloadTags: git2go.DownloadTagsNone,
118120
RemoteCallbacks: RemoteCallbacks(ctx, opts),
121+
ProxyOptions: git2go.ProxyOptions{Type: git2go.ProxyTypeAuto},
119122
},
120123
})
121124
if err != nil {
@@ -147,6 +150,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *g
147150
FetchOptions: &git2go.FetchOptions{
148151
DownloadTags: git2go.DownloadTagsAll,
149152
RemoteCallbacks: RemoteCallbacks(ctx, opts),
153+
ProxyOptions: git2go.ProxyOptions{Type: git2go.ProxyTypeAuto},
150154
},
151155
})
152156
if err != nil {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
/*
2+
Copyright 2021 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package strategyproxy
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"net"
23+
"net/http"
24+
"net/url"
25+
"os"
26+
"strings"
27+
"testing"
28+
"time"
29+
30+
"github.com/elazarl/goproxy"
31+
"github.com/fluxcd/pkg/gittestserver"
32+
. "github.com/onsi/gomega"
33+
34+
"github.com/fluxcd/source-controller/pkg/git"
35+
"github.com/fluxcd/source-controller/pkg/git/gogit"
36+
"github.com/fluxcd/source-controller/pkg/git/libgit2"
37+
"github.com/fluxcd/source-controller/pkg/git/strategy"
38+
)
39+
40+
// These tests are run in a different _test.go file because go-git uses the ProxyFromEnvironment function of the net/http package
41+
// which caches the Proxy settings, hence not including other tests in the same file ensures a clean proxy setup for the tests to run.
42+
func TestCheckoutStrategyForImplementation_Proxied(t *testing.T) {
43+
proxyAddr := "localhost:9999"
44+
45+
type cleanupFunc func()
46+
47+
type testCase struct {
48+
name string
49+
gitImpl git.Implementation
50+
url string
51+
branch string
52+
setupGitProxy func(g *WithT, proxy *goproxy.ProxyHttpServer, proxyGotRequest *bool) (*git.AuthOptions, cleanupFunc)
53+
shortTimeout bool
54+
wantUsedProxy bool
55+
wantError bool
56+
}
57+
58+
// Note there is no libgit2 HTTP_PROXY test as libgit2 doesnt support proxied HTTP requests
59+
cases := []testCase{
60+
{
61+
name: "libgit2_HTTPS_PROXY",
62+
gitImpl: libgit2.Implementation,
63+
url: "https://example.com/bar/test-reponame",
64+
branch: "main",
65+
setupGitProxy: func(g *WithT, proxy *goproxy.ProxyHttpServer, proxyGotRequest *bool) (*git.AuthOptions, cleanupFunc) {
66+
// Create the git server.
67+
gitServer, err := gittestserver.NewTempGitServer()
68+
g.Expect(err).ToNot(HaveOccurred())
69+
70+
username := "test-user"
71+
password := "test-password"
72+
gitServer.Auth(username, password)
73+
gitServer.KeyDir(gitServer.Root())
74+
75+
// Start the HTTPS server.
76+
examplePublicKey, err := os.ReadFile("../testdata/certs/server.pem")
77+
g.Expect(err).ToNot(HaveOccurred())
78+
examplePrivateKey, err := os.ReadFile("../testdata/certs/server-key.pem")
79+
g.Expect(err).ToNot(HaveOccurred())
80+
exampleCA, err := os.ReadFile("../testdata/certs/ca.pem")
81+
g.Expect(err).ToNot(HaveOccurred())
82+
err = gitServer.StartHTTPS(examplePublicKey, examplePrivateKey, exampleCA, "example.com")
83+
g.Expect(err).ToNot(HaveOccurred())
84+
85+
// Initialize a git repo.
86+
repoPath := "bar/test-reponame"
87+
err = gitServer.InitRepo("../testdata/repo1", "main", repoPath)
88+
g.Expect(err).ToNot(HaveOccurred())
89+
90+
u, err := url.Parse(gitServer.HTTPAddress())
91+
g.Expect(err).ToNot(HaveOccurred())
92+
93+
//the request is being forwarded to the local test git server in this handler and that the certificate used here is valid for both example.com and localhost
94+
var proxyHandler goproxy.FuncHttpsHandler = func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
95+
// Check if the host matches with the git server address and the user-agent is the expected git client.
96+
userAgent := ctx.Req.Header.Get("User-Agent")
97+
if strings.Contains(host, "example.com") && strings.Contains(userAgent, "libgit2") {
98+
*proxyGotRequest = true
99+
return goproxy.OkConnect, u.Host
100+
}
101+
//reject if it isnt our request
102+
return goproxy.RejectConnect, host
103+
}
104+
proxy.OnRequest().HandleConnect(proxyHandler)
105+
106+
return &git.AuthOptions{
107+
Transport: git.HTTPS,
108+
Username: username,
109+
Password: password,
110+
CAFile: exampleCA,
111+
}, func() {
112+
os.RemoveAll(gitServer.Root())
113+
gitServer.StopHTTP()
114+
}
115+
},
116+
shortTimeout: false,
117+
wantUsedProxy: true,
118+
wantError: false,
119+
},
120+
{
121+
name: "gogit_HTTP_PROXY",
122+
gitImpl: gogit.Implementation,
123+
url: "http://example.com/bar/test-reponame",
124+
branch: "main",
125+
setupGitProxy: func(g *WithT, proxy *goproxy.ProxyHttpServer, proxyGotRequest *bool) (*git.AuthOptions, cleanupFunc) {
126+
// Create the git server.
127+
gitServer, err := gittestserver.NewTempGitServer()
128+
g.Expect(err).ToNot(HaveOccurred())
129+
130+
username := "test-user"
131+
password := "test-password"
132+
gitServer.Auth(username, password)
133+
gitServer.KeyDir(gitServer.Root())
134+
135+
g.Expect(gitServer.StartHTTP()).ToNot(HaveOccurred())
136+
137+
// Initialize a git repo.
138+
err = gitServer.InitRepo("../testdata/repo1", "main", "bar/test-reponame")
139+
g.Expect(err).ToNot(HaveOccurred())
140+
141+
u, err := url.Parse(gitServer.HTTPAddress())
142+
g.Expect(err).ToNot(HaveOccurred())
143+
144+
var proxyHandler goproxy.FuncReqHandler = func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
145+
userAgent := req.Header.Get("User-Agent")
146+
if strings.Contains(req.Host, "example.com") && strings.Contains(userAgent, "git") {
147+
*proxyGotRequest = true
148+
req.Host = u.Host
149+
req.URL.Host = req.Host
150+
return req, nil
151+
}
152+
//reject if it isnt our request
153+
return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, "")
154+
}
155+
proxy.OnRequest().Do(proxyHandler)
156+
157+
return &git.AuthOptions{
158+
Transport: git.HTTP,
159+
Username: username,
160+
Password: password,
161+
}, func() {
162+
os.RemoveAll(gitServer.Root())
163+
gitServer.StopHTTP()
164+
}
165+
},
166+
shortTimeout: false,
167+
wantUsedProxy: true,
168+
wantError: false,
169+
},
170+
{
171+
name: "gogit_HTTPS_PROXY",
172+
gitImpl: gogit.Implementation,
173+
url: "https://github.com/git-fixtures/basic",
174+
branch: "master",
175+
setupGitProxy: func(g *WithT, proxy *goproxy.ProxyHttpServer, proxyGotRequest *bool) (*git.AuthOptions, cleanupFunc) {
176+
var proxyHandler goproxy.FuncHttpsHandler = func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
177+
userAgent := ctx.Req.Header.Get("User-Agent")
178+
if strings.Contains(host, "github.com") && strings.Contains(userAgent, "Go-http-client") {
179+
*proxyGotRequest = true
180+
return goproxy.OkConnect, host
181+
}
182+
//reject if it isnt our request
183+
return goproxy.RejectConnect, host
184+
}
185+
proxy.OnRequest().HandleConnect(proxyHandler)
186+
187+
// go-git does not allow to use an HTTPS proxy and a custom root CA at the same time.
188+
// See https://github.com/fluxcd/source-controller/pull/524#issuecomment-1006673163.
189+
return nil, func() {}
190+
},
191+
shortTimeout: false,
192+
wantUsedProxy: true,
193+
wantError: false,
194+
},
195+
{
196+
name: "gogit_NO_PROXY",
197+
gitImpl: gogit.Implementation,
198+
url: "https://192.0.2.1/bar/test-reponame",
199+
branch: "main",
200+
setupGitProxy: func(g *WithT, proxy *goproxy.ProxyHttpServer, proxyGotRequest *bool) (*git.AuthOptions, cleanupFunc) {
201+
var proxyHandler goproxy.FuncHttpsHandler = func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
202+
// we shouldnt hit the proxy so we just want to check for any interaction, then reject
203+
*proxyGotRequest = true
204+
return goproxy.RejectConnect, host
205+
}
206+
proxy.OnRequest().HandleConnect(proxyHandler)
207+
208+
return nil, func() {}
209+
},
210+
shortTimeout: true,
211+
wantUsedProxy: false,
212+
wantError: true,
213+
},
214+
// TODO: Add a NO_PROXY test for libgit2 once the version of libgit2 used by the source controller is updated to a version that includes
215+
// the NO_PROXY functionlity
216+
}
217+
218+
testFunc := func(tt testCase) func(t *testing.T) {
219+
return func(t *testing.T) {
220+
g := NewWithT(t)
221+
222+
// Run a proxy server.
223+
proxy := goproxy.NewProxyHttpServer()
224+
proxy.Verbose = true
225+
226+
proxyGotRequest := false
227+
authOpts, cleanup := tt.setupGitProxy(g, proxy, &proxyGotRequest)
228+
defer cleanup()
229+
230+
proxyServer := http.Server{
231+
Addr: proxyAddr,
232+
Handler: proxy,
233+
}
234+
l, err := net.Listen("tcp", proxyServer.Addr)
235+
g.Expect(err).ToNot(HaveOccurred())
236+
go proxyServer.Serve(l)
237+
defer proxyServer.Close()
238+
239+
// Set the proxy env vars for both HTTP and HTTPS because go-git caches them.
240+
os.Setenv("HTTPS_PROXY", fmt.Sprintf("http://%s", proxyAddr))
241+
defer os.Unsetenv("HTTPS_PROXY")
242+
243+
os.Setenv("HTTP_PROXY", fmt.Sprintf("http://%s", proxyAddr))
244+
defer os.Unsetenv("HTTP_PROXY")
245+
246+
os.Setenv("NO_PROXY", "*.0.2.1")
247+
defer os.Unsetenv("NO_PROXY")
248+
249+
// Checkout the repo.
250+
checkoutStrategy, err := strategy.CheckoutStrategyForImplementation(context.TODO(), tt.gitImpl, git.CheckoutOptions{
251+
Branch: tt.branch,
252+
})
253+
g.Expect(err).ToNot(HaveOccurred())
254+
255+
tmpDir, err := os.MkdirTemp("", "test-checkout")
256+
g.Expect(err).ToNot(HaveOccurred())
257+
defer os.RemoveAll(tmpDir)
258+
259+
// for the NO_PROXY test we dont want to wait the 30s for it to timeout/fail, so shorten the timeout
260+
checkoutCtx := context.TODO()
261+
if tt.shortTimeout {
262+
var cancel context.CancelFunc
263+
checkoutCtx, cancel = context.WithTimeout(context.TODO(), 1*time.Second)
264+
defer cancel()
265+
}
266+
267+
_, err = checkoutStrategy.Checkout(checkoutCtx, tmpDir, tt.url, authOpts)
268+
if tt.wantError {
269+
g.Expect(err).To(HaveOccurred())
270+
} else {
271+
g.Expect(err).ToNot(HaveOccurred())
272+
}
273+
_ = proxyGotRequest
274+
g.Expect(proxyGotRequest).To(Equal(tt.wantUsedProxy))
275+
}
276+
}
277+
278+
// Run the test cases against the git implementations.
279+
for _, tt := range cases {
280+
t.Run(tt.name, testFunc(tt))
281+
}
282+
}

0 commit comments

Comments
 (0)