forked from google/go-containerregistry
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bearer.go
135 lines (116 loc) · 3.62 KB
/
bearer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright 2018 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transport
import (
"fmt"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"github.com/aaron-prindle/go-containerregistry/pkg/authn"
"github.com/aaron-prindle/go-containerregistry/pkg/name"
)
type bearerTransport struct {
// Wrapped by bearerTransport.
inner http.RoundTripper
// Basic credentials that we exchange for bearer tokens.
basic authn.Authenticator
// Holds the bearer response from the token service.
bearer *authn.Bearer
// Registry to which we send bearer tokens.
registry name.Registry
// See https://tools.ietf.org/html/rfc6750#section-3
realm string
// See https://docs.docker.com/registry/spec/auth/token/
service string
scopes []string
}
var _ http.RoundTripper = (*bearerTransport)(nil)
// RoundTrip implements http.RoundTripper
func (bt *bearerTransport) RoundTrip(in *http.Request) (*http.Response, error) {
sendRequest := func() (*http.Response, error) {
hdr, err := bt.bearer.Authorization()
if err != nil {
return nil, err
}
// http.Client handles redirects at a layer above the http.RoundTripper
// abstraction, so to avoid forwarding Authorization headers to places
// we are redirected, only set it when the authorization header matches
// the registry with which we are interacting.
// In case of redirect http.Client can use an empty Host, check URL too.
if in.Host == bt.registry.RegistryStr() || in.URL.Host == bt.registry.RegistryStr() {
in.Header.Set("Authorization", hdr)
}
in.Header.Set("User-Agent", transportName)
return bt.inner.RoundTrip(in)
}
res, err := sendRequest()
if err != nil {
return nil, err
}
// Perform a token refresh() and retry the request in case the token has expired
if res.StatusCode == http.StatusUnauthorized {
if err = bt.refresh(); err != nil {
return nil, err
}
return sendRequest()
}
return res, err
}
func (bt *bearerTransport) refresh() error {
u, err := url.Parse(bt.realm)
if err != nil {
return err
}
b := &basicTransport{
inner: bt.inner,
auth: bt.basic,
target: u.Host,
}
client := http.Client{Transport: b}
u.RawQuery = url.Values{
"scope": bt.scopes,
"service": []string{bt.service},
}.Encode()
resp, err := client.Get(u.String())
if err != nil {
return err
}
defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
// Some registries don't have "token" in the response. See #54.
type tokenResponse struct {
Token string `json:"token"`
AccessToken string `json:"access_token"`
}
var response tokenResponse
if err := json.Unmarshal(content, &response); err != nil {
return err
}
// Find a token to turn into a Bearer authenticator
var bearer authn.Bearer
if response.Token != "" {
bearer = authn.Bearer{Token: response.Token}
} else if response.AccessToken != "" {
bearer = authn.Bearer{Token: response.AccessToken}
} else {
return fmt.Errorf("no token in bearer response:\n%s", content)
}
// Replace our old bearer authenticator (if we had one) with our newly refreshed authenticator.
bt.bearer = &bearer
return nil
}