mirror of
https://github.com/moby/buildkit.git
synced 2026-11-06 05:16:45 +00:00
session: use anonymous registry auth when sessions are unset
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
3 files changed
+81
-3
No files matched your search
+5
-1
@@ -7,6 +7,10 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrNoActiveSessions is returned when a session group does not contain any
|
||||
// active session IDs.
|
||||
var ErrNoActiveSessions = errors.New("no active sessions")
|
||||
|
||||
type Group interface {
|
||||
SessionIterator() Iterator
|
||||
}
|
||||
@@ -69,7 +73,7 @@ func (sm *Manager) Any(ctx context.Context, g Group, f func(context.Context, str
|
||||
if lastErr != nil {
|
||||
return lastErr
|
||||
}
|
||||
return errors.Errorf("no active sessions")
|
||||
return errors.WithStack(ErrNoActiveSessions)
|
||||
}
|
||||
|
||||
timeoutCtx, cancel := context.WithCancelCause(ctx)
|
||||
|
||||
@@ -48,6 +48,7 @@ func newAuthHandlerNS(sm *session.Manager) *authHandlerNS {
|
||||
}
|
||||
|
||||
func (a *authHandlerNS) get(ctx context.Context, host string, sm *session.Manager, g session.Group) *authFetcher {
|
||||
hasSession := false
|
||||
if g != nil {
|
||||
if iter := g.SessionIterator(); iter != nil {
|
||||
for {
|
||||
@@ -55,6 +56,7 @@ func (a *authHandlerNS) get(ctx context.Context, host string, sm *session.Manage
|
||||
if id == "" {
|
||||
break
|
||||
}
|
||||
hasSession = true
|
||||
h, ok := a.fetchers[host+"/"+id]
|
||||
if ok {
|
||||
h.lastUsed = time.Now()
|
||||
@@ -64,6 +66,14 @@ func (a *authHandlerNS) get(ctx context.Context, host string, sm *session.Manage
|
||||
}
|
||||
}
|
||||
|
||||
if !hasSession {
|
||||
h, ok := a.fetchers[host+"/"]
|
||||
if ok {
|
||||
h.lastUsed = time.Now()
|
||||
return h
|
||||
}
|
||||
}
|
||||
|
||||
// link existing fetcher
|
||||
for k, h := range a.fetchers {
|
||||
parts := strings.SplitN(k, "/", 2)
|
||||
@@ -186,12 +196,12 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
|
||||
|
||||
var username, secret string
|
||||
sessionID, pubKey, err := sessionauth.GetTokenAuthority(ctx, host, a.sm, a.session)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, session.ErrNoActiveSessions) {
|
||||
return err
|
||||
}
|
||||
if pubKey == nil {
|
||||
sessionID, username, secret, err = a.getCredentials(ctx, host)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, session.ErrNoActiveSessions) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseScopes(t *testing.T) {
|
||||
@@ -54,3 +62,59 @@ func TestParseScopes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBearerAuthFallsBackToAnonymousTokenWithoutSession(t *testing.T) {
|
||||
type tokenRequest struct {
|
||||
authorization string
|
||||
service string
|
||||
scope string
|
||||
}
|
||||
tokenRequests := make(chan tokenRequest, 1)
|
||||
tokenServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tokenRequests <- tokenRequest{
|
||||
authorization: r.Header.Get("Authorization"),
|
||||
service: r.URL.Query().Get("service"),
|
||||
scope: r.URL.Query().Get("scope"),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(map[string]any{
|
||||
"token": "anonymous-token",
|
||||
"expires_in": 60,
|
||||
}); err != nil {
|
||||
t.Errorf("failed to write token response: %v", err)
|
||||
}
|
||||
}))
|
||||
defer tokenServer.Close()
|
||||
|
||||
sm, err := session.NewManager()
|
||||
require.NoError(t, err)
|
||||
|
||||
auth := newDockerAuthorizer(tokenServer.Client(), newAuthHandlerNS(sm), sm, session.NewGroup(""))
|
||||
|
||||
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "https://registry.example/v2/library/alpine/manifests/latest", nil)
|
||||
res := &http.Response{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
Header: http.Header{},
|
||||
Request: req,
|
||||
}
|
||||
res.Header.Set("WWW-Authenticate", fmt.Sprintf(
|
||||
`Bearer realm=%q,service="registry.example",scope="repository:library/alpine:pull"`,
|
||||
tokenServer.URL+"/token",
|
||||
))
|
||||
|
||||
require.NoError(t, auth.AddResponses(t.Context(), []*http.Response{res}))
|
||||
|
||||
retryReq := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "https://registry.example/v2/library/alpine/manifests/latest", nil)
|
||||
require.NoError(t, auth.Authorize(t.Context(), retryReq))
|
||||
require.Equal(t, "Bearer anonymous-token", retryReq.Header.Get("Authorization"))
|
||||
|
||||
select {
|
||||
case req := <-tokenRequests:
|
||||
require.Empty(t, req.authorization)
|
||||
require.Equal(t, "registry.example", req.service)
|
||||
require.Equal(t, "repository:library/alpine:pull", req.scope)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("expected anonymous token request")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user