Skip to content

Commit 18db8b3

Browse files
authored
Merge pull request moby#5733 from marxarelli/feature/http-auth
http: Support authentication
2 parents 26b1f2b + ab1e99e commit 18db8b3

File tree

6 files changed

+145
-40
lines changed

6 files changed

+145
-40
lines changed

client/client_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
105105
testBuildMultiMount,
106106
testBuildHTTPSource,
107107
testBuildHTTPSourceEtagScope,
108+
testBuildHTTPSourceAuthHeaderSecret,
108109
testBuildPushAndValidate,
109110
testBuildExportWithUncompressed,
110111
testBuildExportScratch,
@@ -3009,6 +3010,47 @@ func testBuildHTTPSourceEtagScope(t *testing.T, sb integration.Sandbox) {
30093010
require.NoError(t, os.RemoveAll(filepath.Join(out2, "foo")))
30103011
}
30113012

3013+
func testBuildHTTPSourceAuthHeaderSecret(t *testing.T, sb integration.Sandbox) {
3014+
c, err := New(sb.Context(), sb.Address())
3015+
require.NoError(t, err)
3016+
defer c.Close()
3017+
3018+
modTime := time.Now().Add(-24 * time.Hour) // avoid false positive with current time
3019+
3020+
resp := httpserver.Response{
3021+
Etag: identity.NewID(),
3022+
Content: []byte("content1"),
3023+
LastModified: &modTime,
3024+
}
3025+
3026+
server := httpserver.NewTestServer(map[string]httpserver.Response{
3027+
"/foo": resp,
3028+
})
3029+
defer server.Close()
3030+
3031+
st := llb.HTTP(server.URL+"/foo", llb.AuthHeaderSecret("http-secret"))
3032+
3033+
def, err := st.Marshal(sb.Context())
3034+
require.NoError(t, err)
3035+
3036+
_, err = c.Solve(
3037+
sb.Context(),
3038+
def,
3039+
SolveOpt{
3040+
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
3041+
"http-secret": []byte("Bearer foo"),
3042+
})},
3043+
},
3044+
nil,
3045+
)
3046+
require.NoError(t, err)
3047+
3048+
allReqs := server.Stats("/foo").Requests
3049+
require.Equal(t, 1, len(allReqs))
3050+
require.Equal(t, http.MethodGet, allReqs[0].Method)
3051+
require.Equal(t, "Bearer foo", allReqs[0].Header.Get("Authorization"))
3052+
}
3053+
30123054
func testResolveAndHosts(t *testing.T, sb integration.Sandbox) {
30133055
requiresLinux(t)
30143056
c, err := New(sb.Context(), sb.Address())

client/llb/source.go

+33-12
Original file line numberDiff line numberDiff line change
@@ -360,13 +360,6 @@ func AuthTokenSecret(v string) GitOption {
360360
})
361361
}
362362

363-
func AuthHeaderSecret(v string) GitOption {
364-
return gitOptionFunc(func(gi *GitInfo) {
365-
gi.AuthHeaderSecret = v
366-
gi.addAuthCap = true
367-
})
368-
}
369-
370363
func KnownSSHHosts(key string) GitOption {
371364
key = strings.TrimSuffix(key, "\n")
372365
return gitOptionFunc(func(gi *GitInfo) {
@@ -380,6 +373,29 @@ func MountSSHSock(sshID string) GitOption {
380373
})
381374
}
382375

376+
// AuthOption can be used with either HTTP or Git sources.
377+
type AuthOption interface {
378+
GitOption
379+
HTTPOption
380+
}
381+
382+
// AuthHeaderSecret returns an AuthOption that defines the name of a
383+
// secret to use for HTTP based authentication.
384+
func AuthHeaderSecret(secretName string) AuthOption {
385+
return struct {
386+
GitOption
387+
HTTPOption
388+
}{
389+
GitOption: gitOptionFunc(func(gi *GitInfo) {
390+
gi.AuthHeaderSecret = secretName
391+
gi.addAuthCap = true
392+
}),
393+
HTTPOption: httpOptionFunc(func(hi *HTTPInfo) {
394+
hi.AuthHeaderSecret = secretName
395+
}),
396+
}
397+
}
398+
383399
// Scratch returns a state that represents an empty filesystem.
384400
func Scratch() State {
385401
return NewState(nil)
@@ -595,6 +611,10 @@ func HTTP(url string, opts ...HTTPOption) State {
595611
attrs[pb.AttrHTTPGID] = strconv.Itoa(hi.GID)
596612
addCap(&hi.Constraints, pb.CapSourceHTTPUIDGID)
597613
}
614+
if hi.AuthHeaderSecret != "" {
615+
attrs[pb.AttrHTTPAuthHeaderSecret] = hi.AuthHeaderSecret
616+
addCap(&hi.Constraints, pb.CapSourceHTTPAuth)
617+
}
598618

599619
addCap(&hi.Constraints, pb.CapSourceHTTP)
600620
source := NewSource(url, attrs, hi.Constraints)
@@ -603,11 +623,12 @@ func HTTP(url string, opts ...HTTPOption) State {
603623

604624
type HTTPInfo struct {
605625
constraintsWrapper
606-
Checksum digest.Digest
607-
Filename string
608-
Perm int
609-
UID int
610-
GID int
626+
Checksum digest.Digest
627+
Filename string
628+
Perm int
629+
UID int
630+
GID int
631+
AuthHeaderSecret string
611632
}
612633

613634
type HTTPOption interface {

solver/pb/attr.go

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const AttrHTTPFilename = "http.filename"
2020
const AttrHTTPPerm = "http.perm"
2121
const AttrHTTPUID = "http.uid"
2222
const AttrHTTPGID = "http.gid"
23+
const AttrHTTPAuthHeaderSecret = "http.authheadersecret"
2324

2425
const AttrImageResolveMode = "image.resolvemode"
2526
const AttrImageResolveModeDefault = "default"

solver/pb/caps.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ const (
3131
CapSourceGitSubdir apicaps.CapID = "source.git.subdir"
3232

3333
CapSourceHTTP apicaps.CapID = "source.http"
34+
CapSourceHTTPAuth apicaps.CapID = "source.http.auth"
3435
CapSourceHTTPChecksum apicaps.CapID = "source.http.checksum"
3536
CapSourceHTTPPerm apicaps.CapID = "source.http.perm"
36-
CapSourceHTTPUIDGID apicaps.CapID = "soruce.http.uidgid"
37+
// NOTE the historical typo
38+
CapSourceHTTPUIDGID apicaps.CapID = "soruce.http.uidgid"
3739

3840
CapSourceOCILayout apicaps.CapID = "source.ocilayout"
3941

@@ -229,6 +231,12 @@ func init() {
229231
Status: apicaps.CapStatusExperimental,
230232
})
231233

234+
Caps.Init(apicaps.Cap{
235+
ID: CapSourceHTTPAuth,
236+
Enabled: true,
237+
Status: apicaps.CapStatusExperimental,
238+
})
239+
232240
Caps.Init(apicaps.Cap{
233241
ID: CapSourceOCILayout,
234242
Enabled: true,

source/http/identifier.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ func NewHTTPIdentifier(str string, tls bool) (*HTTPIdentifier, error) {
1818
}
1919

2020
type HTTPIdentifier struct {
21-
TLS bool
22-
URL string
23-
Checksum digest.Digest
24-
Filename string
25-
Perm int
26-
UID int
27-
GID int
21+
TLS bool
22+
URL string
23+
Checksum digest.Digest
24+
Filename string
25+
Perm int
26+
UID int
27+
GID int
28+
AuthHeaderSecret string
2829
}
2930

3031
var _ source.Identifier = (*HTTPIdentifier)(nil)

source/http/source.go

+52-20
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/docker/docker/pkg/idtools"
2121
"github.com/moby/buildkit/cache"
2222
"github.com/moby/buildkit/session"
23+
"github.com/moby/buildkit/session/secrets"
2324
"github.com/moby/buildkit/snapshot"
2425
"github.com/moby/buildkit/solver"
2526
"github.com/moby/buildkit/solver/pb"
@@ -92,6 +93,8 @@ func (hs *httpSource) Identifier(scheme, ref string, attrs map[string]string, pl
9293
return nil, err
9394
}
9495
id.GID = int(i)
96+
case pb.AttrHTTPAuthHeaderSecret:
97+
id.AuthHeaderSecret = v
9598
}
9699
}
97100

@@ -127,16 +130,18 @@ func (hs *httpSourceHandler) client(g session.Group) *http.Client {
127130
// this package.
128131
func (hs *httpSourceHandler) urlHash() (digest.Digest, error) {
129132
dt, err := json.Marshal(struct {
130-
Filename []byte
131-
Perm, UID, GID int
133+
Filename []byte
134+
Perm, UID, GID int
135+
AuthHeaderSecret string `json:",omitempty"`
132136
}{
133137
Filename: bytes.Join([][]byte{
134138
[]byte(hs.src.URL),
135139
[]byte(hs.src.Filename),
136140
}, []byte{0}),
137-
Perm: hs.src.Perm,
138-
UID: hs.src.UID,
139-
GID: hs.src.GID,
141+
Perm: hs.src.Perm,
142+
UID: hs.src.UID,
143+
GID: hs.src.GID,
144+
AuthHeaderSecret: hs.src.AuthHeaderSecret,
140145
})
141146
if err != nil {
142147
return "", err
@@ -146,17 +151,19 @@ func (hs *httpSourceHandler) urlHash() (digest.Digest, error) {
146151

147152
func (hs *httpSourceHandler) formatCacheKey(filename string, dgst digest.Digest, lastModTime string) digest.Digest {
148153
dt, err := json.Marshal(struct {
149-
Filename string
150-
Perm, UID, GID int
151-
Checksum digest.Digest
152-
LastModTime string `json:",omitempty"`
154+
Filename string
155+
Perm, UID, GID int
156+
Checksum digest.Digest
157+
LastModTime string `json:",omitempty"`
158+
AuthHeaderSecret string `json:",omitempty"`
153159
}{
154-
Filename: filename,
155-
Perm: hs.src.Perm,
156-
UID: hs.src.UID,
157-
GID: hs.src.GID,
158-
Checksum: dgst,
159-
LastModTime: lastModTime,
160+
Filename: filename,
161+
Perm: hs.src.Perm,
162+
UID: hs.src.UID,
163+
GID: hs.src.GID,
164+
Checksum: dgst,
165+
LastModTime: lastModTime,
166+
AuthHeaderSecret: hs.src.AuthHeaderSecret,
160167
})
161168
if err != nil {
162169
return dgst
@@ -181,12 +188,10 @@ func (hs *httpSourceHandler) CacheKey(ctx context.Context, g session.Group, inde
181188
return "", "", nil, false, errors.Wrapf(err, "failed to search metadata for %s", uh)
182189
}
183190

184-
req, err := http.NewRequest("GET", hs.src.URL, nil)
191+
req, err := hs.newHTTPRequest(ctx, g)
185192
if err != nil {
186193
return "", "", nil, false, err
187194
}
188-
req = req.WithContext(ctx)
189-
req.Header.Add("User-Agent", version.UserAgent())
190195
m := map[string]cacheRefMetadata{}
191196

192197
// If we request a single ETag in 'If-None-Match', some servers omit the
@@ -443,11 +448,10 @@ func (hs *httpSourceHandler) Snapshot(ctx context.Context, g session.Group) (cac
443448
}
444449
}
445450

446-
req, err := http.NewRequest("GET", hs.src.URL, nil)
451+
req, err := hs.newHTTPRequest(ctx, g)
447452
if err != nil {
448453
return nil, err
449454
}
450-
req = req.WithContext(ctx)
451455

452456
client := hs.client(g)
453457

@@ -471,6 +475,34 @@ func (hs *httpSourceHandler) Snapshot(ctx context.Context, g session.Group) (cac
471475
return ref, nil
472476
}
473477

478+
func (hs *httpSourceHandler) newHTTPRequest(ctx context.Context, g session.Group) (*http.Request, error) {
479+
req, err := http.NewRequest("GET", hs.src.URL, nil)
480+
if err != nil {
481+
return nil, err
482+
}
483+
484+
req.Header.Set("User-Agent", version.UserAgent())
485+
486+
if hs.src.AuthHeaderSecret != "" {
487+
err := hs.sm.Any(ctx, g, func(ctx context.Context, _ string, caller session.Caller) error {
488+
dt, err := secrets.GetSecret(ctx, caller, hs.src.AuthHeaderSecret)
489+
if err != nil {
490+
return err
491+
}
492+
493+
req.Header.Set("Authorization", string(dt))
494+
495+
return nil
496+
})
497+
498+
if err != nil {
499+
return nil, errors.Wrapf(err, "failed to retrieve HTTP auth secret %s", hs.src.AuthHeaderSecret)
500+
}
501+
}
502+
503+
return req.WithContext(ctx), nil
504+
}
505+
474506
func getFileName(urlStr, manualFilename string, resp *http.Response) string {
475507
if manualFilename != "" {
476508
return manualFilename

0 commit comments

Comments
 (0)