Skip to content

Commit 26ee146

Browse files
committed
feat(auth): Set BASIC auth disregarding remote preferences
1 parent d901dcd commit 26ee146

File tree

6 files changed

+175
-13
lines changed

6 files changed

+175
-13
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package store
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"strings"
7+
)
8+
9+
// Store stores BASIC authentication credentials
10+
type Store struct {
11+
logins map[string]*Login
12+
}
13+
14+
// Login stores username and password for BASIC authentication
15+
type Login struct {
16+
Username string
17+
Password string
18+
}
19+
20+
// LoadAll parses and loads a list of BASIC authentication strings
21+
func (st *Store) LoadAll(aa []string) error {
22+
logins := make(map[string]*Login, 0)
23+
24+
for _, a := range aa {
25+
registry, login, err := loadOne(strings.TrimSpace(a))
26+
27+
if err != nil {
28+
return err
29+
}
30+
31+
logins[registry] = login
32+
}
33+
34+
st.logins = logins
35+
36+
return nil
37+
}
38+
39+
// GetByHostname gets a BASIC auth login for a registry hostname passed
40+
func (st *Store) GetByHostname(registryHostname string) *Login {
41+
login, defined := st.logins[registryHostname]
42+
if !defined {
43+
return nil
44+
}
45+
46+
return login
47+
}
48+
49+
// GetByURL gets a BASIC auth login for a registry URL passed
50+
func (st *Store) GetByURL(registryURL string) *Login {
51+
u, _ := url.Parse(registryURL)
52+
53+
return st.GetByHostname(u.Host)
54+
}
55+
56+
func loadOne(a string) (string, *Login, error) {
57+
const format = "REGISTRY[:PORT] username:password"
58+
59+
var formatErr = fmt.Errorf(
60+
"invalid format for BASIC auth (should be: %s)",
61+
format,
62+
)
63+
64+
ss := strings.SplitN(a, " ", 2)
65+
if len(ss) != 2 {
66+
return "", nil, formatErr
67+
}
68+
69+
up := strings.SplitN(ss[1], ":", 2)
70+
if len(up) != 2 {
71+
return "", nil, formatErr
72+
}
73+
74+
registry := ss[0]
75+
username := up[0]
76+
password := up[1]
77+
78+
if password == "" {
79+
return "", nil, formatErr
80+
}
81+
82+
return registry, &Login{Username: username, Password: password}, nil
83+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package store
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
var examples = []string{"localhost:5000 foo:bar", "quay.io quser:qpass"}
10+
11+
func TestLoadAllValid(t *testing.T) {
12+
var store Store
13+
14+
err := store.LoadAll(examples)
15+
16+
assert.NoError(t, err)
17+
}
18+
19+
func TestLoadAllInvalid(t *testing.T) {
20+
var store Store
21+
22+
assert.Error(t, store.LoadAll([]string{""}))
23+
assert.Error(t, store.LoadAll([]string{"us.gcr.io"}))
24+
assert.Error(t, store.LoadAll([]string{"us.gcr.io forgotsomething"}))
25+
assert.Error(t, store.LoadAll([]string{"quay.io quser:"}))
26+
assert.Error(t, store.LoadAll([]string{" foo:bar"}))
27+
}
28+
29+
func TestGet(t *testing.T) {
30+
var store Store
31+
32+
store.LoadAll(examples)
33+
34+
assert.NotNil(t, store.GetByHostname("localhost:5000"))
35+
assert.NotNil(t, store.GetByHostname("quay.io"))
36+
assert.Nil(t, store.GetByHostname("eu.gcr.io"))
37+
38+
assert.NotNil(t, store.GetByURL("http://localhost:5000"))
39+
assert.NotNil(t, store.GetByURL("https://quay.io"))
40+
assert.Nil(t, store.GetByURL("https://eu.gcr.io"))
41+
}
42+
43+
func TestGetValues(t *testing.T) {
44+
var store Store
45+
46+
store.LoadAll(examples)
47+
48+
login1 := store.GetByHostname("localhost:5000")
49+
login2 := store.GetByHostname("quay.io")
50+
51+
assert.Equal(t, login1.Username, "foo")
52+
assert.Equal(t, login1.Password, "bar")
53+
assert.Equal(t, login2.Username, "quser")
54+
assert.Equal(t, login2.Password, "qpass")
55+
}

api/v1/registry/client/auth/token.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ import (
88
log "github.com/sirupsen/logrus"
99

1010
"github.com/ivanilves/lstags/api/v1/registry/client/auth/basic"
11+
basicstore "github.com/ivanilves/lstags/api/v1/registry/client/auth/basic/store"
1112
"github.com/ivanilves/lstags/api/v1/registry/client/auth/bearer"
1213
"github.com/ivanilves/lstags/api/v1/registry/client/auth/none"
1314
)
1415

16+
// BasicStore stores explicitly set BASIC authorization headers
17+
var BasicStore basicstore.Store
18+
1519
// Token is an abstraction for aggregated token-related information we get from authentication services
1620
type Token interface {
1721
Method() string
@@ -58,18 +62,30 @@ func getAuthParams(h authHeader) map[string]string {
5862
// * detects authentication type ("Bearer", "Basic" or "None")
5963
// * delegates actual authentication to the type-specific implementation
6064
func NewToken(url, username, password, scope string) (Token, error) {
61-
resp, err := http.Get(url)
62-
if err != nil {
63-
return nil, err
64-
}
65+
var method = ""
66+
var params = make(map[string]string)
6567

66-
authHeader, err := extractAuthHeader(resp.Header["Www-Authenticate"])
67-
if err != nil {
68-
return nil, err
69-
}
68+
storedBasicAuth := BasicStore.GetByURL(url)
69+
70+
if storedBasicAuth == nil {
71+
resp, err := http.Get(url)
72+
if err != nil {
73+
return nil, err
74+
}
7075

71-
method := strings.ToLower(getAuthMethod(authHeader))
72-
params := getAuthParams(authHeader)
76+
authHeader, err := extractAuthHeader(resp.Header["Www-Authenticate"])
77+
if err != nil {
78+
return nil, err
79+
}
80+
81+
method = strings.ToLower(getAuthMethod(authHeader))
82+
params = getAuthParams(authHeader)
83+
} else {
84+
method = "basic"
85+
86+
username = storedBasicAuth.Username
87+
password = storedBasicAuth.Password
88+
}
7389

7490
switch method {
7591
case "none":

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ require (
1616
github.com/sirupsen/logrus v1.4.2
1717
github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 // indirect
1818
github.com/stretchr/testify v1.4.0
19-
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc // indirect
19+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
2020
golang.org/x/net v0.0.0-20191007182048-72f939374954
2121
gopkg.in/yaml.v2 v2.2.4
2222
)

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
4444
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
4545
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
4646
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
47+
github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 h1:4bT0pPowCpQImewr+BjzfUKcuFW+KVyB8d1OF3b6oTI=
4748
github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4=
4849
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
4950
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -52,8 +53,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
5253
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
5354
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
5455
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
55-
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
56-
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
56+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
57+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
5758
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
5859
golang.org/x/net v0.0.0-20191007182048-72f939374954 h1:JGZucVF/L/TotR719NbujzadOZ2AgnYlqphQGHDCKaU=
5960
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -64,6 +65,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
6465
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
6566
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6667
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
68+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
6769
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
6870
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
6971
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=

main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
log "github.com/sirupsen/logrus"
1313

1414
v1 "github.com/ivanilves/lstags/api/v1"
15+
"github.com/ivanilves/lstags/api/v1/registry/client/auth"
1516
"github.com/ivanilves/lstags/config"
1617
)
1718

@@ -34,6 +35,7 @@ type Options struct {
3435
RetryRequests int `short:"y" long:"retry-requests" default:"2" description:"Number of retries for failed Docker registry requests" env:"RETRY_REQUESTS"`
3536
RetryDelay time.Duration `short:"D" long:"retry-delay" default:"2s" description:"Delay between retries of failed registry requests" env:"RETRY_DELAY"`
3637
InsecureRegistryEx string `short:"I" long:"insecure-registry-ex" description:"Expression to match insecure registry hostnames" env:"INSECURE_REGISTRY_EX"`
38+
BasicAuth []string `short:"B" long:"basic-auth" description:"Set per-registry BASIC auth username:password pair" env:"BASIC_AUTH"`
3739
TraceRequests bool `short:"T" long:"trace-requests" description:"Trace Docker registry HTTP requests" env:"TRACE_REQUESTS"`
3840
DoNotFail bool `short:"N" long:"do-not-fail" description:"Do not fail on non-critical errors (could be dangerous!)" env:"DO_NOT_FAIL"`
3941
DaemonMode bool `short:"d" long:"daemon-mode" description:"Run as daemon instead of just execute and exit" env:"DAEMON_MODE"`
@@ -105,6 +107,10 @@ func main() {
105107
suicide(err, true)
106108
}
107109

110+
if err := auth.BasicStore.LoadAll(o.BasicAuth); err != nil {
111+
suicide(err, true)
112+
}
113+
108114
apiConfig := v1.Config{
109115
DockerJSONConfigFile: o.DockerJSON,
110116
ConcurrentRequests: o.ConcurrentRequests,

0 commit comments

Comments
 (0)