k8s.io/apiserver@v0.31.1/pkg/authentication/request/websocket/protocol.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package websocket 18 19 import ( 20 "encoding/base64" 21 "errors" 22 "net/http" 23 "net/textproto" 24 "strings" 25 "unicode/utf8" 26 27 "k8s.io/apimachinery/pkg/util/httpstream/wsstream" 28 "k8s.io/apiserver/pkg/authentication/authenticator" 29 ) 30 31 const bearerProtocolPrefix = "base64url.bearer.authorization.k8s.io." 32 33 var protocolHeader = textproto.CanonicalMIMEHeaderKey("Sec-WebSocket-Protocol") 34 35 var errInvalidToken = errors.New("invalid bearer token") 36 37 // ProtocolAuthenticator allows a websocket connection to provide a bearer token as a subprotocol 38 // in the format "base64url.bearer.authorization.<base64url-without-padding(bearer-token)>" 39 type ProtocolAuthenticator struct { 40 // auth is the token authenticator to use to validate the token 41 auth authenticator.Token 42 } 43 44 func NewProtocolAuthenticator(auth authenticator.Token) *ProtocolAuthenticator { 45 return &ProtocolAuthenticator{auth} 46 } 47 48 func (a *ProtocolAuthenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) { 49 // Only accept websocket connections 50 if !wsstream.IsWebSocketRequest(req) { 51 return nil, false, nil 52 } 53 54 token := "" 55 sawTokenProtocol := false 56 filteredProtocols := []string{} 57 for _, protocolHeader := range req.Header[protocolHeader] { 58 for _, protocol := range strings.Split(protocolHeader, ",") { 59 protocol = strings.TrimSpace(protocol) 60 61 if !strings.HasPrefix(protocol, bearerProtocolPrefix) { 62 filteredProtocols = append(filteredProtocols, protocol) 63 continue 64 } 65 66 if sawTokenProtocol { 67 return nil, false, errors.New("multiple base64.bearer.authorization tokens specified") 68 } 69 sawTokenProtocol = true 70 71 encodedToken := strings.TrimPrefix(protocol, bearerProtocolPrefix) 72 decodedToken, err := base64.RawURLEncoding.DecodeString(encodedToken) 73 if err != nil { 74 return nil, false, errors.New("invalid base64.bearer.authorization token encoding") 75 } 76 if !utf8.Valid(decodedToken) { 77 return nil, false, errors.New("invalid base64.bearer.authorization token") 78 } 79 token = string(decodedToken) 80 } 81 } 82 83 // Must pass at least one other subprotocol so that we can remove the one containing the bearer token, 84 // and there is at least one to echo back to the client 85 if len(token) > 0 && len(filteredProtocols) == 0 { 86 return nil, false, errors.New("missing additional subprotocol") 87 } 88 89 if len(token) == 0 { 90 return nil, false, nil 91 } 92 93 resp, ok, err := a.auth.AuthenticateToken(req.Context(), token) 94 95 // on success, remove the protocol with the token 96 if ok { 97 // https://tools.ietf.org/html/rfc6455#section-11.3.4 indicates the Sec-WebSocket-Protocol header may appear multiple times 98 // in a request, and is logically the same as a single Sec-WebSocket-Protocol header field that contains all values 99 req.Header.Set(protocolHeader, strings.Join(filteredProtocols, ",")) 100 } 101 102 // If the token authenticator didn't error, provide a default error 103 if !ok && err == nil { 104 err = errInvalidToken 105 } 106 107 return resp, ok, err 108 }