Skip to content

Commit

Permalink
openshift authorization proxy: escape header key values
Browse files Browse the repository at this point in the history
The authorization.openshift.io RBAC proxy is taking values from
the Extra field of a UserInfo of a user that made a request against
this API. The fields of the map may generally contain values that
cannot appear in an HTTP header, like '/' that's commonly separating
an API annotation from the API version or resource.

Copy the k8s.io header escaping function and use it in the proxy
that's handling the OpenShift authorization API.
  • Loading branch information
stlaz committed Jun 16, 2021
1 parent ce7d8f6 commit ec6cf4e
Showing 1 changed file with 111 additions and 1 deletion.
112 changes: 111 additions & 1 deletion pkg/client/impersonatingclient/impersonate.go
@@ -1,7 +1,9 @@
package impersonatingclient

import (
"fmt"
"net/http"
"strings"

"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -50,7 +52,7 @@ func (c *impersonatingRESTClient) impersonate(req *restclient.Request) *restclie
req.SetHeader(transport.ImpersonateUserHeader, c.user.GetName())
req.SetHeader(transport.ImpersonateGroupHeader, c.user.GetGroups()...)
for k, vv := range c.user.GetExtra() {
req.SetHeader(transport.ImpersonateUserExtraHeaderPrefix+k, vv...)
req.SetHeader(transport.ImpersonateUserExtraHeaderPrefix+headerKeyEscape(k), vv...)
}
return req
}
Expand Down Expand Up @@ -86,3 +88,111 @@ func (c *impersonatingRESTClient) APIVersion() schema.GroupVersion {
func (c *impersonatingRESTClient) GetRateLimiter() flowcontrol.RateLimiter {
return c.delegate.GetRateLimiter()
}

// the below header-escaping code is copied from k8s.io/client-go roundtrippers
func legalHeaderByte(b byte) bool {
return int(b) < len(legalHeaderKeyBytes) && legalHeaderKeyBytes[b]
}

func shouldEscape(b byte) bool {
// url.PathUnescape() returns an error if any '%' is not followed by two
// hexadecimal digits, so we'll intentionally encode it.
return !legalHeaderByte(b) || b == '%'
}

func headerKeyEscape(key string) string {
buf := strings.Builder{}
for i := 0; i < len(key); i++ {
b := key[i]
if shouldEscape(b) {
// %-encode bytes that should be escaped:
// https://tools.ietf.org/html/rfc3986#section-2.1
fmt.Fprintf(&buf, "%%%02X", b)
continue
}
buf.WriteByte(b)
}
return buf.String()
}

// legalHeaderKeyBytes was copied from net/http/lex.go's isTokenTable.
// See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
var legalHeaderKeyBytes = [127]bool{
'%': true,
'!': true,
'#': true,
'$': true,
'&': true,
'\'': true,
'*': true,
'+': true,
'-': true,
'.': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'W': true,
'V': true,
'X': true,
'Y': true,
'Z': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'|': true,
'~': true,
}

0 comments on commit ec6cf4e

Please sign in to comment.