@@ -24,9 +24,11 @@ const (
2424)
2525
2626var (
27- debug = false
28- dialer = & websocket.Dialer {}
29- privateFieldRegex = regexp .MustCompile ("^[[:lower:]]" )
27+ debug = false
28+ dialer = & websocket.Dialer {}
29+ privateFieldRegex = regexp .MustCompile ("^[[:lower:]]" )
30+ rancherHTTPURLPattern = regexp .MustCompile (`^https?://(?:\[[0-9A-Fa-f:.%]+\]|[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?)(?::[0-9]{1,5})?(?:/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*)?(?:\?[A-Za-z0-9._~!$&'()*+,;=:@%/?-]*)?$` )
31+ rancherWebSocketURLPattern = regexp .MustCompile (`^wss?://(?:\[[0-9A-Fa-f:.%]+\]|[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?)(?::[0-9]{1,5})?(?:/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*)?(?:\?[A-Za-z0-9._~!$&'()*+,;=:@%/?-]*)?$` )
3032)
3133
3234type ClientOpts struct {
@@ -146,6 +148,65 @@ func NormalizeUrl(existingUrl string) (string, error) {
146148 return u .String (), nil
147149}
148150
151+ func rancherOrigin (raw string ) (string , string , string , error ) {
152+ u , err := url .Parse (raw )
153+ if err != nil || u .Opaque != "" || u .User != nil || u .Hostname () == "" {
154+ return "" , "" , "" , errors .New ("Rancher URL must contain a valid origin" )
155+ }
156+ scheme := strings .ToLower (u .Scheme )
157+ switch scheme {
158+ case "ws" :
159+ scheme = "http"
160+ case "wss" :
161+ scheme = "https"
162+ case "http" , "https" :
163+ default :
164+ return "" , "" , "" , errors .New ("Rancher URL has an unsupported scheme" )
165+ }
166+ port := u .Port ()
167+ if port == "" {
168+ if scheme == "https" {
169+ port = "443"
170+ } else {
171+ port = "80"
172+ }
173+ }
174+ return scheme , strings .ToLower (u .Hostname ()), port , nil
175+ }
176+
177+ func validateRancherRequestURL (baseURL , candidateURL string , websocketRequest bool ) (string , error ) {
178+ pattern := rancherHTTPURLPattern
179+ if websocketRequest {
180+ pattern = rancherWebSocketURLPattern
181+ }
182+ if ! pattern .MatchString (candidateURL ) {
183+ return "" , errors .New ("Rancher request URL has an unsupported format" )
184+ }
185+ baseScheme , baseHost , basePort , err := rancherOrigin (baseURL )
186+ if err != nil {
187+ return "" , err
188+ }
189+ targetScheme , targetHost , targetPort , err := rancherOrigin (candidateURL )
190+ if err != nil {
191+ return "" , err
192+ }
193+ if baseScheme != targetScheme || baseHost != targetHost || basePort != targetPort {
194+ return "" , errors .New ("Rancher request URL crosses the configured origin" )
195+ }
196+ return candidateURL , nil
197+ }
198+
199+ func newRancherRequest (baseURL , method , candidateURL string , body io.Reader ) (* http.Request , error ) {
200+ target , err := validateRancherRequestURL (baseURL , candidateURL , false )
201+ if err != nil {
202+ return nil , err
203+ }
204+ if ! rancherHTTPURLPattern .MatchString (target ) {
205+ return nil , errors .New ("Rancher request URL failed validation" )
206+ }
207+ return http .NewRequest (method , target , body )
208+ }
209+
149210func safeURLForLog (value string ) string {
150211 u , err := url .Parse (value )
151212 if err != nil {
@@ -182,7 +243,7 @@ func setupRancherBaseClient(rancherClient *RancherBaseClientImpl, opts *ClientOp
182243 opts .Timeout = time .Second * time .Duration (defaultTimeout ())
183244 }
184245 client := newHTTPClient (opts .Timeout )
185- req , err := http . NewRequest ( "GET" , opts .Url , nil )
246+ req , err := newRancherRequest ( opts . Url , "GET" , opts .Url , nil )
186247 if err != nil {
187248 return err
188249 }
@@ -206,7 +267,7 @@ func setupRancherBaseClient(rancherClient *RancherBaseClientImpl, opts *ClientOp
206267 }
207268
208269 if schemasUrls != opts .Url {
209- req , err = http . NewRequest ( "GET" , schemasUrls , nil )
270+ req , err = newRancherRequest ( opts . Url , "GET" , schemasUrls , nil )
210271 if err != nil {
211272 return err
212273 }
@@ -264,7 +325,7 @@ func (rancherClient *RancherBaseClientImpl) newHttpClient() *http.Client {
264325
265326func (rancherClient * RancherBaseClientImpl ) doDelete (url string ) error {
266327 client := rancherClient .newHttpClient ()
267- req , err := http . NewRequest ( "DELETE" , url , nil )
328+ req , err := newRancherRequest ( rancherClient . Opts . Url , "DELETE" , url , nil )
268329 if err != nil {
269330 return err
270331 }
@@ -297,7 +358,14 @@ func (rancherClient *RancherBaseClientImpl) Websocket(url string, headers map[st
297358 httpHeaders .Add ("Authorization" , "Basic " + base64 .StdEncoding .EncodeToString ([]byte (s )))
298359 }
299360
300- return dialer .Dial (url , http .Header (httpHeaders ))
361+ target , err := validateRancherRequestURL (rancherClient .Opts .Url , url , true )
362+ if err != nil {
363+ return nil , nil , err
364+ }
365+ if ! rancherWebSocketURLPattern .MatchString (target ) {
366+ return nil , nil , errors .New ("Rancher WebSocket URL failed validation" )
367+ }
368+ return dialer .Dial (target , http .Header (httpHeaders ))
301369}
302370
303371func (rancherClient * RancherBaseClientImpl ) doGet (url string , opts * ListOpts , respObject interface {}) error {
@@ -314,7 +382,7 @@ func (rancherClient *RancherBaseClientImpl) doGet(url string, opts *ListOpts, re
314382 }
315383
316384 client := rancherClient .newHttpClient ()
317- req , err := http . NewRequest ( "GET" , url , nil )
385+ req , err := newRancherRequest ( rancherClient . Opts . Url , "GET" , url , nil )
318386 if err != nil {
319387 return err
320388 }
@@ -398,7 +466,7 @@ func (rancherClient *RancherBaseClientImpl) doModify(method string, url string,
398466 }
399467
400468 client := rancherClient .newHttpClient ()
401- req , err := http . NewRequest ( method , url , bytes .NewBuffer (bodyContent ))
469+ req , err := newRancherRequest ( rancherClient . Opts . Url , method , url , bytes .NewBuffer (bodyContent ))
402470 if err != nil {
403471 return err
404472 }
@@ -592,7 +660,7 @@ func (rancherClient *RancherBaseClientImpl) doAction(schemaType string, action s
592660 }
593661
594662 client := rancherClient .newHttpClient ()
595- req , err := http . NewRequest ( "POST" , actionUrl , input )
663+ req , err := newRancherRequest ( rancherClient . Opts . Url , "POST" , actionUrl , input )
596664 if err != nil {
597665 return err
598666 }
0 commit comments