Skip to content

Commit 7837ec1

Browse files
committed
feat(troubleshoot): support for distribution kind
1 parent fa6639d commit 7837ec1

File tree

11 files changed

+596
-196
lines changed

11 files changed

+596
-196
lines changed

go.mod

Lines changed: 66 additions & 56 deletions
Large diffs are not rendered by default.

go.sum

Lines changed: 133 additions & 134 deletions
Large diffs are not rendered by default.

pkg/base/rewrite.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"strings"
55

66
dockerref "github.com/containers/image/v5/docker/reference"
7-
"github.com/distribution/distribution/v3/reference"
7+
"github.com/distribution/reference"
88
"github.com/pkg/errors"
99
"github.com/replicatedhq/kots/pkg/docker/registry"
1010
dockerregistrytypes "github.com/replicatedhq/kots/pkg/docker/registry/types"

pkg/docker/registry/auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111

1212
"github.com/containers/image/v5/pkg/docker/config"
1313
"github.com/containers/image/v5/types"
14-
"github.com/distribution/distribution/v3/registry/client/auth/challenge"
1514
"github.com/pkg/errors"
15+
"github.com/replicatedhq/kots/pkg/docker/registry/challenge"
1616
"github.com/replicatedhq/kots/pkg/util"
1717
)
1818

pkg/docker/registry/challenge/addr.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// From https://github.com/distribution/distribution/tree/bc6e81e1b9a8017a66fc56a55913b92930941feb/internal/client/auth/challenge/
2+
package challenge
3+
4+
import (
5+
"net/url"
6+
"strings"
7+
)
8+
9+
// FROM: https://golang.org/src/net/http/http.go
10+
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
11+
// return true if the string includes a port.
12+
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
13+
14+
// FROM: http://golang.org/src/net/http/transport.go
15+
var portMap = map[string]string{
16+
"http": "80",
17+
"https": "443",
18+
}
19+
20+
// canonicalAddr returns url.Host but always with a ":port" suffix
21+
// FROM: http://golang.org/src/net/http/transport.go
22+
func canonicalAddr(url *url.URL) string {
23+
addr := url.Host
24+
if !hasPort(addr) {
25+
return addr + ":" + portMap[url.Scheme]
26+
}
27+
return addr
28+
}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// From https://github.com/distribution/distribution/tree/bc6e81e1b9a8017a66fc56a55913b92930941feb/internal/client/auth/challenge/
2+
package challenge
3+
4+
import (
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
"strings"
9+
"sync"
10+
)
11+
12+
// Octet types from RFC 2616.
13+
type octetType byte
14+
15+
var octetTypes [256]octetType
16+
17+
const (
18+
isToken octetType = 1 << iota
19+
isSpace
20+
)
21+
22+
func init() {
23+
// OCTET = <any 8-bit sequence of data>
24+
// CHAR = <any US-ASCII character (octets 0 - 127)>
25+
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
26+
// CR = <US-ASCII CR, carriage return (13)>
27+
// LF = <US-ASCII LF, linefeed (10)>
28+
// SP = <US-ASCII SP, space (32)>
29+
// HT = <US-ASCII HT, horizontal-tab (9)>
30+
// <"> = <US-ASCII double-quote mark (34)>
31+
// CRLF = CR LF
32+
// LWS = [CRLF] 1*( SP | HT )
33+
// TEXT = <any OCTET except CTLs, but including LWS>
34+
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
35+
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
36+
// token = 1*<any CHAR except CTLs or separators>
37+
// qdtext = <any TEXT except <">>
38+
39+
for c := 0; c < 256; c++ {
40+
var t octetType
41+
isCtl := c <= 31 || c == 127
42+
isChar := 0 <= c && c <= 127
43+
isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c))
44+
if strings.ContainsRune(" \t\r\n", rune(c)) {
45+
t |= isSpace
46+
}
47+
if isChar && !isCtl && !isSeparator {
48+
t |= isToken
49+
}
50+
octetTypes[c] = t
51+
}
52+
}
53+
54+
// Challenge carries information from a WWW-Authenticate response header.
55+
// See RFC 2617.
56+
type Challenge struct {
57+
// Scheme is the auth-scheme according to RFC 2617
58+
Scheme string
59+
60+
// Parameters are the auth-params according to RFC 2617
61+
Parameters map[string]string
62+
}
63+
64+
// Manager manages the challenges for endpoints.
65+
// The challenges are pulled out of HTTP responses. Only
66+
// responses which expect challenges should be added to
67+
// the manager, since a non-unauthorized request will be
68+
// viewed as not requiring challenges.
69+
type Manager interface {
70+
// GetChallenges returns the challenges for the given
71+
// endpoint URL.
72+
GetChallenges(endpoint url.URL) ([]Challenge, error)
73+
74+
// AddResponse adds the response to the challenge
75+
// manager. The challenges will be parsed out of
76+
// the WWW-Authenticate headers and added to the
77+
// URL which was produced the response. If the
78+
// response was authorized, any challenges for the
79+
// endpoint will be cleared.
80+
AddResponse(resp *http.Response) error
81+
}
82+
83+
// NewSimpleManager returns an instance of
84+
// Manager which only maps endpoints to challenges
85+
// based on the responses which have been added the
86+
// manager. The simple manager will make no attempt to
87+
// perform requests on the endpoints or cache the responses
88+
// to a backend.
89+
func NewSimpleManager() Manager {
90+
return &simpleManager{
91+
Challenges: make(map[string][]Challenge),
92+
}
93+
}
94+
95+
type simpleManager struct {
96+
sync.RWMutex
97+
Challenges map[string][]Challenge
98+
}
99+
100+
func normalizeURL(endpoint *url.URL) {
101+
endpoint.Host = strings.ToLower(endpoint.Host)
102+
endpoint.Host = canonicalAddr(endpoint)
103+
}
104+
105+
func (m *simpleManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
106+
normalizeURL(&endpoint)
107+
108+
m.RLock()
109+
defer m.RUnlock()
110+
challenges := m.Challenges[endpoint.String()]
111+
return challenges, nil
112+
}
113+
114+
func (m *simpleManager) AddResponse(resp *http.Response) error {
115+
challenges := ResponseChallenges(resp)
116+
if resp.Request == nil {
117+
return fmt.Errorf("missing request reference")
118+
}
119+
urlCopy := url.URL{
120+
Path: resp.Request.URL.Path,
121+
Host: resp.Request.URL.Host,
122+
Scheme: resp.Request.URL.Scheme,
123+
}
124+
normalizeURL(&urlCopy)
125+
126+
m.Lock()
127+
defer m.Unlock()
128+
m.Challenges[urlCopy.String()] = challenges
129+
return nil
130+
}
131+
132+
// ResponseChallenges returns a list of authorization challenges
133+
// for the given http Response. Challenges are only checked if
134+
// the response status code was a 401.
135+
func ResponseChallenges(resp *http.Response) []Challenge {
136+
if resp.StatusCode == http.StatusUnauthorized {
137+
// Parse the WWW-Authenticate Header and store the challenges
138+
// on this endpoint object.
139+
return parseAuthHeader(resp.Header)
140+
}
141+
142+
return nil
143+
}
144+
145+
func parseAuthHeader(header http.Header) []Challenge {
146+
challenges := []Challenge{}
147+
for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
148+
v, p := parseValueAndParams(h)
149+
if v != "" {
150+
challenges = append(challenges, Challenge{Scheme: v, Parameters: p})
151+
}
152+
}
153+
return challenges
154+
}
155+
156+
func parseValueAndParams(header string) (value string, params map[string]string) {
157+
params = make(map[string]string)
158+
value, s := expectToken(header)
159+
if value == "" {
160+
return
161+
}
162+
value = strings.ToLower(value)
163+
s = "," + skipSpace(s)
164+
for strings.HasPrefix(s, ",") {
165+
var pkey string
166+
pkey, s = expectToken(skipSpace(s[1:]))
167+
if pkey == "" {
168+
return
169+
}
170+
if !strings.HasPrefix(s, "=") {
171+
return
172+
}
173+
var pvalue string
174+
pvalue, s = expectTokenOrQuoted(s[1:])
175+
if pvalue == "" {
176+
return
177+
}
178+
pkey = strings.ToLower(pkey)
179+
params[pkey] = pvalue
180+
s = skipSpace(s)
181+
}
182+
return
183+
}
184+
185+
func skipSpace(s string) (rest string) {
186+
i := 0
187+
for ; i < len(s); i++ {
188+
if octetTypes[s[i]]&isSpace == 0 {
189+
break
190+
}
191+
}
192+
return s[i:]
193+
}
194+
195+
func expectToken(s string) (token, rest string) {
196+
i := 0
197+
for ; i < len(s); i++ {
198+
if octetTypes[s[i]]&isToken == 0 {
199+
break
200+
}
201+
}
202+
return s[:i], s[i:]
203+
}
204+
205+
func expectTokenOrQuoted(s string) (value string, rest string) {
206+
if !strings.HasPrefix(s, "\"") {
207+
return expectToken(s)
208+
}
209+
s = s[1:]
210+
for i := 0; i < len(s); i++ {
211+
switch s[i] {
212+
case '"':
213+
return s[:i], s[i+1:]
214+
case '\\':
215+
p := make([]byte, len(s)-1)
216+
j := copy(p, s[:i])
217+
escape := true
218+
for i = i + 1; i < len(s); i++ {
219+
b := s[i]
220+
switch {
221+
case escape:
222+
escape = false
223+
p[j] = b
224+
j++
225+
case b == '\\':
226+
escape = true
227+
case b == '"':
228+
return string(p[:j]), s[i+1:]
229+
default:
230+
p[j] = b
231+
j++
232+
}
233+
}
234+
return "", ""
235+
}
236+
}
237+
return "", ""
238+
}

0 commit comments

Comments
 (0)