2018-09-06 21:42:41 +00:00
|
|
|
package lfshttp
|
2017-01-04 23:23:46 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"strings"
|
2017-03-24 18:37:00 +00:00
|
|
|
"time"
|
2017-01-04 23:23:46 +00:00
|
|
|
|
2021-09-01 19:41:10 +00:00
|
|
|
"github.com/git-lfs/git-lfs/v3/config"
|
|
|
|
"github.com/git-lfs/git-lfs/v3/ssh"
|
|
|
|
"github.com/git-lfs/git-lfs/v3/subprocess"
|
|
|
|
"github.com/git-lfs/git-lfs/v3/tools"
|
2017-01-04 23:23:46 +00:00
|
|
|
"github.com/rubyist/tracerx"
|
|
|
|
)
|
|
|
|
|
2017-03-23 19:48:52 +00:00
|
|
|
type SSHResolver interface {
|
2017-03-23 19:44:51 +00:00
|
|
|
Resolve(Endpoint, string) (sshAuthResponse, error)
|
|
|
|
}
|
|
|
|
|
2017-03-23 19:58:15 +00:00
|
|
|
func withSSHCache(ssh SSHResolver) SSHResolver {
|
|
|
|
return &sshCache{
|
|
|
|
endpoints: make(map[string]*sshAuthResponse),
|
|
|
|
ssh: ssh,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type sshCache struct {
|
|
|
|
endpoints map[string]*sshAuthResponse
|
|
|
|
ssh SSHResolver
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *sshCache) Resolve(e Endpoint, method string) (sshAuthResponse, error) {
|
2021-02-03 21:46:28 +00:00
|
|
|
if len(e.SSHMetadata.UserAndHost) == 0 {
|
2017-03-24 17:27:44 +00:00
|
|
|
return sshAuthResponse{}, nil
|
|
|
|
}
|
|
|
|
|
2021-02-03 21:46:28 +00:00
|
|
|
key := strings.Join([]string{e.SSHMetadata.UserAndHost, e.SSHMetadata.Port, e.SSHMetadata.Path, method}, "//")
|
2017-04-05 19:59:52 +00:00
|
|
|
if res, ok := c.endpoints[key]; ok {
|
|
|
|
if _, expired := res.IsExpiredWithin(5 * time.Second); !expired {
|
|
|
|
tracerx.Printf("ssh cache: %s git-lfs-authenticate %s %s",
|
2021-02-03 21:46:28 +00:00
|
|
|
e.SSHMetadata.UserAndHost, e.SSHMetadata.Path, endpointOperation(e, method))
|
2017-04-05 19:59:52 +00:00
|
|
|
return *res, nil
|
|
|
|
} else {
|
|
|
|
tracerx.Printf("ssh cache expired: %s git-lfs-authenticate %s %s",
|
2021-02-03 21:46:28 +00:00
|
|
|
e.SSHMetadata.UserAndHost, e.SSHMetadata.Path, endpointOperation(e, method))
|
2017-04-05 19:59:52 +00:00
|
|
|
}
|
2017-03-23 19:58:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
res, err := c.ssh.Resolve(e, method)
|
|
|
|
if err == nil {
|
|
|
|
c.endpoints[key] = &res
|
|
|
|
}
|
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
|
2017-03-23 19:44:51 +00:00
|
|
|
type sshAuthResponse struct {
|
|
|
|
Message string `json:"-"`
|
|
|
|
Href string `json:"href"`
|
|
|
|
Header map[string]string `json:"header"`
|
lfsapi/ssh.go: use zero-value sentinels
Instead of storing the ExpiresAt and ExpiresIn fields as a *time.Time,
and *int, respectively, use a time.Time and int instead.
Later, in the zero-value comparison, change from != nil to != 0 and
time.Time.IsZero(), respectively.
This has the effect of ridding the API of setting expires_in to zero,
and will fall-back to the default expiration offset, after which it will
retry, or fail the underlying request. We don't have precise
determinations on how many API requests this will affect, but it's
certainly not common usage of the specification, so the behavior change
is OK.
2018-07-02 18:21:39 +00:00
|
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
|
|
ExpiresIn int `json:"expires_in"`
|
2017-04-05 22:43:00 +00:00
|
|
|
|
|
|
|
createdAt time.Time
|
2017-04-05 19:59:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *sshAuthResponse) IsExpiredWithin(d time.Duration) (time.Time, bool) {
|
lfsapi/ssh.go: use zero-value sentinels
Instead of storing the ExpiresAt and ExpiresIn fields as a *time.Time,
and *int, respectively, use a time.Time and int instead.
Later, in the zero-value comparison, change from != nil to != 0 and
time.Time.IsZero(), respectively.
This has the effect of ridding the API of setting expires_in to zero,
and will fall-back to the default expiration offset, after which it will
retry, or fail the underlying request. We don't have precise
determinations on how many API requests this will affect, but it's
certainly not common usage of the specification, so the behavior change
is OK.
2018-07-02 18:21:39 +00:00
|
|
|
return tools.IsExpiredAtOrIn(r.createdAt, d, r.ExpiresAt,
|
|
|
|
time.Duration(r.ExpiresIn)*time.Second)
|
2017-03-23 19:44:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type sshAuthClient struct {
|
2018-02-14 22:26:47 +00:00
|
|
|
os config.Environment
|
2018-02-14 21:58:59 +00:00
|
|
|
git config.Environment
|
2017-03-23 19:44:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *sshAuthClient) Resolve(e Endpoint, method string) (sshAuthResponse, error) {
|
2017-01-04 23:23:46 +00:00
|
|
|
res := sshAuthResponse{}
|
2021-02-03 21:46:28 +00:00
|
|
|
if len(e.SSHMetadata.UserAndHost) == 0 {
|
2017-01-04 23:23:46 +00:00
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
ssh: support concurrent transfers using the pure SSH protocol
When using the pure SSH-based protocol, we can get much higher speeds by
multiplexing multiple connections on the same SSH connection. If we're
using OpenSSH, let's enable the ControlMaster option unless
lfs.ssh.automultiplex is set to false, and multiplex these shell
operations over one connection.
We prefer XDG_RUNTIME_DIR because it's guaranteed to be private and we
can share many connections over one socket, but if that's not set, let's
default to creating a new temporary directory for the socket. On
Windows, where the native SSH client doesn't support ControlMaster,
we should fall back to using multiple connections since we use
ControlMaster=auto.
Note that the option exists because users may already be using SSH
multiplexing and we would want to provide a way for them to disable
this, in addition to the case where users have an old or broken OpenSSH
which cannot support this option.
We pass the connection object into each worker and adjust our transfer
code to pass it into each function we invoke. We also make sure to
properly terminate each connection at the end by reducing our connection
count to 0, which closes the extra (i.e., all) connections.
Co-authored-by: Chris Darroch <chrisd8088@github.com>
2021-04-06 14:08:06 +00:00
|
|
|
exe, args := ssh.GetLFSExeAndArgs(c.os, c.git, &e.SSHMetadata, "git-lfs-authenticate", endpointOperation(e, method), false)
|
2020-12-21 21:08:17 +00:00
|
|
|
cmd := subprocess.ExecCommand(exe, args...)
|
2017-01-04 23:23:46 +00:00
|
|
|
|
|
|
|
// Save stdout and stderr in separate buffers
|
|
|
|
var outbuf, errbuf bytes.Buffer
|
|
|
|
cmd.Stdout = &outbuf
|
|
|
|
cmd.Stderr = &errbuf
|
|
|
|
|
2017-04-05 22:43:00 +00:00
|
|
|
now := time.Now()
|
|
|
|
|
2017-01-04 23:23:46 +00:00
|
|
|
// Execute command
|
|
|
|
err := cmd.Start()
|
|
|
|
if err == nil {
|
|
|
|
err = cmd.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Processing result
|
|
|
|
if err != nil {
|
|
|
|
res.Message = strings.TrimSpace(errbuf.String())
|
|
|
|
} else {
|
|
|
|
err = json.Unmarshal(outbuf.Bytes(), &res)
|
lfsapi/ssh.go: use zero-value sentinels
Instead of storing the ExpiresAt and ExpiresIn fields as a *time.Time,
and *int, respectively, use a time.Time and int instead.
Later, in the zero-value comparison, change from != nil to != 0 and
time.Time.IsZero(), respectively.
This has the effect of ridding the API of setting expires_in to zero,
and will fall-back to the default expiration offset, after which it will
retry, or fail the underlying request. We don't have precise
determinations on how many API requests this will affect, but it's
certainly not common usage of the specification, so the behavior change
is OK.
2018-07-02 18:21:39 +00:00
|
|
|
if res.ExpiresIn == 0 && res.ExpiresAt.IsZero() {
|
2018-02-14 21:58:59 +00:00
|
|
|
ttl := c.git.Int("lfs.defaulttokenttl", 0)
|
2018-02-15 21:25:55 +00:00
|
|
|
if ttl < 0 {
|
|
|
|
ttl = 0
|
2018-02-14 21:58:59 +00:00
|
|
|
}
|
lfsapi/ssh.go: use zero-value sentinels
Instead of storing the ExpiresAt and ExpiresIn fields as a *time.Time,
and *int, respectively, use a time.Time and int instead.
Later, in the zero-value comparison, change from != nil to != 0 and
time.Time.IsZero(), respectively.
This has the effect of ridding the API of setting expires_in to zero,
and will fall-back to the default expiration offset, after which it will
retry, or fail the underlying request. We don't have precise
determinations on how many API requests this will affect, but it's
certainly not common usage of the specification, so the behavior change
is OK.
2018-07-02 18:21:39 +00:00
|
|
|
res.ExpiresIn = ttl
|
2018-02-14 21:58:59 +00:00
|
|
|
}
|
2017-04-05 22:43:00 +00:00
|
|
|
res.createdAt = now
|
2017-01-04 23:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return res, err
|
|
|
|
}
|