
Updates the docker resolver to support authenticating with registries. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
183 lines
4.0 KiB
Go
183 lines
4.0 KiB
Go
package docker
|
|
|
|
import (
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type authenticationScheme byte
|
|
|
|
const (
|
|
basicAuth authenticationScheme = 1 << iota // Defined in RFC 7617
|
|
digestAuth // Defined in RFC 7616
|
|
bearerAuth // Defined in RFC 6750
|
|
)
|
|
|
|
// challenge carries information from a WWW-Authenticate response header.
|
|
// See RFC 2617.
|
|
type challenge struct {
|
|
// scheme is the auth-scheme according to RFC 2617
|
|
scheme authenticationScheme
|
|
|
|
// parameters are the auth-params according to RFC 2617
|
|
parameters map[string]string
|
|
}
|
|
|
|
type byScheme []challenge
|
|
|
|
func (bs byScheme) Len() int { return len(bs) }
|
|
func (bs byScheme) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] }
|
|
|
|
// Sort in priority order: token > digest > basic
|
|
func (bs byScheme) Less(i, j int) bool { return bs[i].scheme > bs[j].scheme }
|
|
|
|
// Octet types from RFC 2616.
|
|
type octetType byte
|
|
|
|
var octetTypes [256]octetType
|
|
|
|
const (
|
|
isToken octetType = 1 << iota
|
|
isSpace
|
|
)
|
|
|
|
func init() {
|
|
// OCTET = <any 8-bit sequence of data>
|
|
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
|
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
|
// CR = <US-ASCII CR, carriage return (13)>
|
|
// LF = <US-ASCII LF, linefeed (10)>
|
|
// SP = <US-ASCII SP, space (32)>
|
|
// HT = <US-ASCII HT, horizontal-tab (9)>
|
|
// <"> = <US-ASCII double-quote mark (34)>
|
|
// CRLF = CR LF
|
|
// LWS = [CRLF] 1*( SP | HT )
|
|
// TEXT = <any OCTET except CTLs, but including LWS>
|
|
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
|
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
|
// token = 1*<any CHAR except CTLs or separators>
|
|
// qdtext = <any TEXT except <">>
|
|
|
|
for c := 0; c < 256; c++ {
|
|
var t octetType
|
|
isCtl := c <= 31 || c == 127
|
|
isChar := 0 <= c && c <= 127
|
|
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
|
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
|
t |= isSpace
|
|
}
|
|
if isChar && !isCtl && !isSeparator {
|
|
t |= isToken
|
|
}
|
|
octetTypes[c] = t
|
|
}
|
|
}
|
|
|
|
func parseAuthHeader(header http.Header) []challenge {
|
|
challenges := []challenge{}
|
|
for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
|
|
v, p := parseValueAndParams(h)
|
|
var s authenticationScheme
|
|
switch v {
|
|
case "basic":
|
|
s = basicAuth
|
|
case "digest":
|
|
s = digestAuth
|
|
case "bearer":
|
|
s = bearerAuth
|
|
default:
|
|
continue
|
|
}
|
|
challenges = append(challenges, challenge{scheme: s, parameters: p})
|
|
}
|
|
sort.Stable(byScheme(challenges))
|
|
return challenges
|
|
}
|
|
|
|
func parseValueAndParams(header string) (value string, params map[string]string) {
|
|
params = make(map[string]string)
|
|
value, s := expectToken(header)
|
|
if value == "" {
|
|
return
|
|
}
|
|
value = strings.ToLower(value)
|
|
for {
|
|
var pkey string
|
|
pkey, s = expectToken(skipSpace(s))
|
|
if pkey == "" {
|
|
return
|
|
}
|
|
if !strings.HasPrefix(s, "=") {
|
|
return
|
|
}
|
|
var pvalue string
|
|
pvalue, s = expectTokenOrQuoted(s[1:])
|
|
if pvalue == "" {
|
|
return
|
|
}
|
|
pkey = strings.ToLower(pkey)
|
|
params[pkey] = pvalue
|
|
s = skipSpace(s)
|
|
if !strings.HasPrefix(s, ",") {
|
|
return
|
|
}
|
|
s = s[1:]
|
|
}
|
|
}
|
|
|
|
func skipSpace(s string) (rest string) {
|
|
i := 0
|
|
for ; i < len(s); i++ {
|
|
if octetTypes[s[i]]&isSpace == 0 {
|
|
break
|
|
}
|
|
}
|
|
return s[i:]
|
|
}
|
|
|
|
func expectToken(s string) (token, rest string) {
|
|
i := 0
|
|
for ; i < len(s); i++ {
|
|
if octetTypes[s[i]]&isToken == 0 {
|
|
break
|
|
}
|
|
}
|
|
return s[:i], s[i:]
|
|
}
|
|
|
|
func expectTokenOrQuoted(s string) (value string, rest string) {
|
|
if !strings.HasPrefix(s, "\"") {
|
|
return expectToken(s)
|
|
}
|
|
s = s[1:]
|
|
for i := 0; i < len(s); i++ {
|
|
switch s[i] {
|
|
case '"':
|
|
return s[:i], s[i+1:]
|
|
case '\\':
|
|
p := make([]byte, len(s)-1)
|
|
j := copy(p, s[:i])
|
|
escape := true
|
|
for i = i + 1; i < len(s); i++ {
|
|
b := s[i]
|
|
switch {
|
|
case escape:
|
|
escape = false
|
|
p[j] = b
|
|
j++
|
|
case b == '\\':
|
|
escape = true
|
|
case b == '"':
|
|
return string(p[:j]), s[i+1:]
|
|
default:
|
|
p[j] = b
|
|
j++
|
|
}
|
|
}
|
|
return "", ""
|
|
}
|
|
}
|
|
return "", ""
|
|
}
|