Skip to content

Commit 54e69d7

Browse files
committed
Remove dependency on libgit2 credentials callback
Injects transport and auth options at the transport level directly to bypass the inbuilt credentials callback because of it's several shortcomings. Moves some of the pre-existing logic from the reconciler to the checkout implementation. Signed-off-by: Sanskar Jaiswal <[email protected]>
1 parent 55a594a commit 54e69d7

File tree

12 files changed

+674
-431
lines changed

12 files changed

+674
-431
lines changed

controllers/gitrepository_controller.go

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -458,27 +458,15 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context,
458458
repositoryURL := obj.Spec.URL
459459
// managed GIT transport only affects the libgit2 implementation
460460
if managed.Enabled() && obj.Spec.GitImplementation == sourcev1.LibGit2Implementation {
461-
// At present only HTTP connections have the ability to define remote options.
462-
// Although this can be easily extended by ensuring that the fake URL below uses the
463-
// target ssh scheme, and the libgit2/managed/ssh.go pulls that information accordingly.
464-
//
465-
// This is due to the fact the key libgit2 remote callbacks do not take place for HTTP
466-
// whilst most still work for SSH.
461+
// We set the TransportAuthID of this set of authentication options here by constructing
462+
// a unique ID that won't clash in a multi tenant environment. This unique ID is used by
463+
// libgit2 managed transports. This enables us to bypass the inbuilt credentials callback in
464+
// libgit2, which is inflexible and unstable.
467465
if strings.HasPrefix(repositoryURL, "http") {
468-
// Due to the lack of the callback feature, a fake target URL is created to allow
469-
// for the smart sub transport be able to pick the options specific for this
470-
// GitRepository object.
471-
// The URL should use unique information that do not collide in a multi tenant
472-
// deployment.
473-
repositoryURL = fmt.Sprintf("http://%s/%s/%d", obj.Name, obj.UID, obj.Generation)
474-
managed.AddTransportOptions(repositoryURL,
475-
managed.TransportOptions{
476-
TargetURL: obj.Spec.URL,
477-
CABundle: authOpts.CAFile,
478-
})
479-
480-
// We remove the options from memory, to avoid accumulating unused options over time.
481-
defer managed.RemoveTransportOptions(repositoryURL)
466+
authOpts.TransportAuthID = fmt.Sprintf("http://%s/%s/%d", obj.Name, obj.UID, obj.Generation)
467+
}
468+
if strings.HasPrefix(repositoryURL, "ssh") {
469+
authOpts.TransportAuthID = fmt.Sprintf("ssh://%s/%s/%d", obj.Name, obj.UID, obj.Generation)
482470
}
483471
}
484472

pkg/git/libgit2/checkout.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ type CheckoutBranch struct {
6969
func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (_ *git.Commit, err error) {
7070
defer recoverPanic(&err)
7171

72+
if managed.Enabled() {
73+
// We store the target url and auth options mapped to a unique ID. We overwrite the target url
74+
// with the TransportAuthID, because managed transports don't provide a way for any kind of
75+
// dependency injection. This lets us have a way of doing interop between application level code
76+
// and transport level code.
77+
// Performing all fetch operations with the TransportAuthID as the url, lets the managed
78+
// transport action use it to fetch the registered transport options which contains the
79+
// _actual_ target url and the correct credentials to use.
80+
managed.AddTransportOptions(opts.TransportAuthID, managed.TransportOptions{
81+
TargetURL: url,
82+
AuthOpts: opts,
83+
})
84+
url = opts.TransportAuthID
85+
defer managed.RemoveTransportOptions(opts.TransportAuthID)
86+
}
87+
7288
remoteCallBacks := RemoteCallbacks(ctx, opts)
7389
proxyOpts := &git2go.ProxyOptions{Type: git2go.ProxyTypeAuto}
7490

@@ -170,6 +186,15 @@ type CheckoutTag struct {
170186
func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (_ *git.Commit, err error) {
171187
defer recoverPanic(&err)
172188

189+
if managed.Enabled() {
190+
managed.AddTransportOptions(opts.TransportAuthID, managed.TransportOptions{
191+
TargetURL: url,
192+
AuthOpts: opts,
193+
})
194+
url = opts.TransportAuthID
195+
defer managed.RemoveTransportOptions(opts.TransportAuthID)
196+
}
197+
173198
remoteCallBacks := RemoteCallbacks(ctx, opts)
174199
proxyOpts := &git2go.ProxyOptions{Type: git2go.ProxyTypeAuto}
175200

@@ -249,6 +274,15 @@ type CheckoutCommit struct {
249274
func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (_ *git.Commit, err error) {
250275
defer recoverPanic(&err)
251276

277+
if managed.Enabled() {
278+
managed.AddTransportOptions(opts.TransportAuthID, managed.TransportOptions{
279+
TargetURL: url,
280+
AuthOpts: opts,
281+
})
282+
url = opts.TransportAuthID
283+
defer managed.RemoveTransportOptions(opts.TransportAuthID)
284+
}
285+
252286
repo, err := git2go.Clone(url, path, &git2go.CloneOptions{
253287
FetchOptions: git2go.FetchOptions{
254288
DownloadTags: git2go.DownloadTagsNone,
@@ -278,6 +312,15 @@ type CheckoutSemVer struct {
278312
func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (_ *git.Commit, err error) {
279313
defer recoverPanic(&err)
280314

315+
if managed.Enabled() {
316+
managed.AddTransportOptions(opts.TransportAuthID, managed.TransportOptions{
317+
TargetURL: url,
318+
AuthOpts: opts,
319+
})
320+
url = opts.TransportAuthID
321+
defer managed.RemoveTransportOptions(opts.TransportAuthID)
322+
}
323+
281324
verConstraint, err := semver.NewConstraint(c.SemVer)
282325
if err != nil {
283326
return nil, fmt.Errorf("semver parse error: %w", err)

pkg/git/libgit2/managed/http.go

Lines changed: 27 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ type httpSmartSubtransport struct {
8686
httpTransport *http.Transport
8787
}
8888

89-
func (t *httpSmartSubtransport) Action(targetUrl string, action git2go.SmartServiceAction) (git2go.SmartSubtransportStream, error) {
89+
func (t *httpSmartSubtransport) Action(transportAuthID string, action git2go.SmartServiceAction) (git2go.SmartSubtransportStream, error) {
9090
var proxyFn func(*http.Request) (*url.URL, error)
9191
proxyOpts, err := t.transport.SmartProxyOptions()
9292
if err != nil {
@@ -109,7 +109,7 @@ func (t *httpSmartSubtransport) Action(targetUrl string, action git2go.SmartServ
109109
t.httpTransport.Proxy = proxyFn
110110
t.httpTransport.DisableCompression = false
111111

112-
client, req, err := createClientRequest(targetUrl, action, t.httpTransport)
112+
client, req, err := createClientRequest(transportAuthID, action, t.httpTransport)
113113
if err != nil {
114114
return nil, err
115115
}
@@ -142,36 +142,22 @@ func (t *httpSmartSubtransport) Action(targetUrl string, action git2go.SmartServ
142142
return stream, nil
143143
}
144144

145-
func createClientRequest(targetUrl string, action git2go.SmartServiceAction, t *http.Transport) (*http.Client, *http.Request, error) {
145+
func createClientRequest(transportAuthID string, action git2go.SmartServiceAction, t *http.Transport) (*http.Client, *http.Request, error) {
146146
var req *http.Request
147147
var err error
148148

149149
if t == nil {
150150
return nil, nil, fmt.Errorf("failed to create client: transport cannot be nil")
151151
}
152152

153-
finalUrl := targetUrl
154-
opts, found := transportOptions(targetUrl)
155-
if found {
156-
if opts.TargetURL != "" {
157-
// override target URL only if options are found and a new targetURL
158-
// is provided.
159-
finalUrl = opts.TargetURL
160-
}
153+
opts, found := getTransportOptions(transportAuthID)
161154

162-
// Add any provided certificate to the http transport.
163-
if len(opts.CABundle) > 0 {
164-
cap := x509.NewCertPool()
165-
if ok := cap.AppendCertsFromPEM(opts.CABundle); !ok {
166-
return nil, nil, fmt.Errorf("failed to use certificate from PEM")
167-
}
168-
t.TLSClientConfig = &tls.Config{
169-
RootCAs: cap,
170-
}
171-
}
155+
if !found {
156+
return nil, nil, fmt.Errorf("failed to create client: could not find transport options for the object: %s", transportAuthID)
172157
}
158+
targetURL := opts.TargetURL
173159

174-
if len(finalUrl) > URLMaxLength {
160+
if len(targetURL) > URLMaxLength {
175161
return nil, nil, fmt.Errorf("URL exceeds the max length (%d)", URLMaxLength)
176162
}
177163

@@ -182,20 +168,20 @@ func createClientRequest(targetUrl string, action git2go.SmartServiceAction, t *
182168

183169
switch action {
184170
case git2go.SmartServiceActionUploadpackLs:
185-
req, err = http.NewRequest("GET", finalUrl+"/info/refs?service=git-upload-pack", nil)
171+
req, err = http.NewRequest("GET", targetURL+"/info/refs?service=git-upload-pack", nil)
186172

187173
case git2go.SmartServiceActionUploadpack:
188-
req, err = http.NewRequest("POST", finalUrl+"/git-upload-pack", nil)
174+
req, err = http.NewRequest("POST", targetURL+"/git-upload-pack", nil)
189175
if err != nil {
190176
break
191177
}
192178
req.Header.Set("Content-Type", "application/x-git-upload-pack-request")
193179

194180
case git2go.SmartServiceActionReceivepackLs:
195-
req, err = http.NewRequest("GET", finalUrl+"/info/refs?service=git-receive-pack", nil)
181+
req, err = http.NewRequest("GET", targetURL+"/info/refs?service=git-receive-pack", nil)
196182

197183
case git2go.SmartServiceActionReceivepack:
198-
req, err = http.NewRequest("POST", finalUrl+"/git-receive-pack", nil)
184+
req, err = http.NewRequest("POST", targetURL+"/git-receive-pack", nil)
199185
if err != nil {
200186
break
201187
}
@@ -209,6 +195,20 @@ func createClientRequest(targetUrl string, action git2go.SmartServiceAction, t *
209195
return nil, nil, err
210196
}
211197

198+
// Add any provided certificate to the http transport.
199+
if opts.AuthOpts != nil {
200+
req.SetBasicAuth(opts.AuthOpts.Username, opts.AuthOpts.Password)
201+
if len(opts.AuthOpts.CAFile) > 0 {
202+
certPool := x509.NewCertPool()
203+
if ok := certPool.AppendCertsFromPEM(opts.AuthOpts.CAFile); !ok {
204+
return nil, nil, fmt.Errorf("failed to use certificate from PEM")
205+
}
206+
t.TLSClientConfig = &tls.Config{
207+
RootCAs: certPool,
208+
}
209+
}
210+
}
211+
212212
req.Header.Set("User-Agent", "git/2.0 (flux-libgit2)")
213213
return client, req, nil
214214
}
@@ -239,7 +239,6 @@ type httpSmartSubtransportStream struct {
239239
recvReply sync.WaitGroup
240240
httpError error
241241
m sync.RWMutex
242-
targetURL string
243242
}
244243

245244
func newManagedHttpStream(owner *httpSmartSubtransport, req *http.Request, client *http.Client) *httpSmartSubtransportStream {
@@ -324,29 +323,8 @@ func (self *httpSmartSubtransportStream) sendRequest() error {
324323

325324
var resp *http.Response
326325
var err error
327-
var userName string
328-
var password string
329-
330-
// Obtain the credentials and use them if available.
331-
cred, err := self.owner.transport.SmartCredentials("", git2go.CredentialTypeUserpassPlaintext)
332-
if err != nil {
333-
// Passthrough error indicates that no credentials were provided.
334-
// Continue without credentials.
335-
if err.Error() != git2go.ErrorCodePassthrough.String() {
336-
return err
337-
}
338-
}
339-
340-
if cred != nil {
341-
defer cred.Free()
342-
343-
userName, password, err = cred.GetUserpassPlaintext()
344-
if err != nil {
345-
return err
346-
}
347-
}
348-
349326
var content []byte
327+
350328
for {
351329
req := &http.Request{
352330
Method: self.req.Method,
@@ -365,7 +343,6 @@ func (self *httpSmartSubtransportStream) sendRequest() error {
365343
req.ContentLength = -1
366344
}
367345

368-
req.SetBasicAuth(userName, password)
369346
traceLog.Info("[http]: new request", "method", req.Method, "URL", req.URL)
370347
resp, err = self.client.Do(req)
371348
if err != nil {

0 commit comments

Comments
 (0)