Skip to content

Commit 6ae19b9

Browse files
committed
explore: Use fewer cookies
Originally the site was largely stateless and I used small cookies to smuggle things around, but this means I can't really use credentials without sharing them with the client, so let's do away with that. Signed-off-by: Jon Johnson <jon.johnson@chainguard.dev>
1 parent 280e9d8 commit 6ae19b9

2 files changed

Lines changed: 54 additions & 93 deletions

File tree

internal/explore/cookies.go

Lines changed: 41 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package explore
22

33
import (
4-
"encoding/base64"
5-
"encoding/json"
6-
"log"
74
"net/http"
85
"time"
96

@@ -13,13 +10,6 @@ import (
1310
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
1411
)
1512

16-
type CookieValue struct {
17-
Reg string
18-
PingResp *transport.PingResp
19-
Repo string
20-
TokenResponse *transport.TokenResponse
21-
}
22-
2313
type RedirectCookie struct {
2414
Digest string
2515
Url string
@@ -33,102 +23,60 @@ func (h *handler) transportFromCookie(w http.ResponseWriter, r *http.Request, re
3323
scopes := []string{parsed.Scope(transport.PullScope)}
3424
reg := parsed.Registry
3525

36-
var (
37-
pr *transport.PingResp
38-
tok *transport.TokenResponse
39-
)
40-
if regCookie, err := r.Cookie("registry_token"); err == nil {
41-
b, err := base64.URLEncoding.DecodeString(regCookie.Value)
42-
if err != nil {
43-
return nil, err
44-
}
45-
var v CookieValue
46-
if err := json.Unmarshal(b, &v); err != nil {
47-
return nil, err
48-
}
49-
if v.Reg == reg.String() {
50-
pr = v.PingResp
51-
if v.Repo == repo {
52-
tok = v.TokenResponse
53-
}
54-
}
55-
}
56-
5726
t := remote.DefaultTransport
5827
t = transport.NewRetry(t)
5928
t = transport.NewUserAgent(t, h.userAgent)
6029
if r.URL.Query().Get("trace") != "" {
6130
t = transport.NewTracer(t)
6231
}
6332

64-
if pr == nil {
65-
if cpr, ok := h.pings[reg.String()]; ok {
66-
if debug {
67-
log.Printf("cached ping: %v", cpr)
68-
}
69-
pr = cpr
70-
} else {
71-
if debug {
72-
log.Printf("pinging %s", reg.String())
73-
}
74-
pr, err = transport.Ping(r.Context(), reg, t)
75-
if err != nil {
76-
return nil, err
77-
}
78-
h.pings[reg.String()] = pr
79-
}
80-
}
81-
82-
if tok == nil {
83-
if debug {
84-
log.Printf("getting token %s", reg.String())
85-
}
86-
t, tok, err = transport.NewBearer(r.Context(), pr, reg, auth, t, scopes)
33+
h.Lock()
34+
pr, ok := h.pings[reg.String()]
35+
h.Unlock()
36+
if !ok {
37+
pr, err = transport.Ping(r.Context(), reg, t)
8738
if err != nil {
8839
return nil, err
8940
}
41+
h.Lock()
42+
h.pings[reg.String()] = pr
43+
h.Unlock()
44+
}
9045

91-
// Probably no auth needed.
92-
if tok == nil {
93-
return t, nil
94-
}
46+
h.Lock()
47+
tok, ok := h.tokens[parsed.String()]
48+
h.Unlock()
49+
if ok && !tok.Expires.Before(time.Now().Add(30*time.Second)) {
50+
// If this won't expire within 30 seconds, reuse it.
51+
return transport.OldBearer(pr, tok.TokenResponse, reg, auth, t, scopes)
52+
}
9553

96-
// Clear this to make cookies smaller.
97-
tok.AccessToken = ""
54+
// We don't have a cached token or it's expired (or about to), so get a new one.
55+
rt, tr, err := transport.NewBearer(r.Context(), pr, reg, auth, t, scopes)
56+
if err != nil {
57+
return nil, err
58+
}
9859

99-
v := &CookieValue{
100-
Reg: reg.String(),
101-
PingResp: pr,
102-
Repo: repo,
103-
TokenResponse: tok,
104-
}
105-
b, err := json.Marshal(v)
106-
if err != nil {
107-
return nil, err
108-
}
109-
cv := base64.URLEncoding.EncodeToString(b)
110-
cookie := &http.Cookie{
111-
Name: "registry_token",
112-
Value: cv,
113-
Secure: true,
114-
HttpOnly: true,
115-
SameSite: http.SameSiteLaxMode,
116-
}
117-
if tok.ExpiresIn == 0 {
118-
tok.ExpiresIn = 60
119-
}
120-
exp := time.Now().Add(time.Second * time.Duration(tok.ExpiresIn))
121-
cookie.Expires = exp
122-
http.SetCookie(w, cookie)
123-
} else {
124-
if debug {
125-
log.Printf("restoring bearer %s", reg.String())
126-
}
127-
t, err = transport.OldBearer(pr, tok, reg, auth, t, scopes)
128-
if err != nil {
129-
return nil, err
130-
}
60+
// Probably no auth needed.
61+
if tr == nil {
62+
return rt, nil
63+
}
64+
65+
// Clear this to make cache smaller (sometimes this duplicates Token).
66+
tr.AccessToken = ""
67+
68+
if tr.ExpiresIn == 0 {
69+
tr.ExpiresIn = 60
13170
}
71+
exp := time.Now().Add(time.Second * time.Duration(tr.ExpiresIn))
72+
tok = token{
73+
TokenResponse: tr,
74+
Expires: exp,
75+
}
76+
77+
h.Lock()
78+
h.tokens[parsed.String()] = tok
79+
h.Unlock()
13280

133-
return t, nil
81+
return rt, nil
13482
}

internal/explore/explore.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ import (
4747
const tooBig = 1 << 22
4848
const respTooBig = 1 << 25
4949

50+
type token struct {
51+
Expires time.Time
52+
TokenResponse *transport.TokenResponse
53+
}
54+
5055
type handler struct {
5156
mux http.Handler
5257
keychain authn.Keychain
@@ -58,6 +63,12 @@ type handler struct {
5863
// reg.String() -> ping resp
5964
pings map[string]*transport.PingResp
6065

66+
// repo.String() -> token
67+
tokens map[string]token
68+
69+
// blob.Digest() -> url
70+
redirects map[string]string
71+
6172
tocCache cache
6273
indexCache cache
6374

@@ -86,6 +97,8 @@ func New(opts ...Option) http.Handler {
8697
h := handler{
8798
manifests: map[string]*remote.Descriptor{},
8899
pings: map[string]*transport.PingResp{},
100+
tokens: map[string]token{},
101+
redirects: map[string]string{},
89102
sawTags: map[string][]string{},
90103
inflight: map[string]*soci.Indexer{},
91104
tocCache: buildTocCache(),

0 commit comments

Comments
 (0)