github.com/fabianvf/ocp-release-operator-sdk@v0.0.0-20190426141702-57620ee2f090/pkg/ansible/proxy/proxy.go (about) 1 // Copyright 2018 The Operator-SDK Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package proxy 16 17 // This file contains this project's custom code, as opposed to kubectl.go 18 // which contains code retrieved from the kubernetes project. 19 20 import ( 21 "bytes" 22 "context" 23 "encoding/base64" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "io/ioutil" 28 "net/http" 29 "net/http/httputil" 30 "strings" 31 32 "github.com/operator-framework/operator-sdk/pkg/ansible/proxy/controllermap" 33 "github.com/operator-framework/operator-sdk/pkg/ansible/proxy/kubeconfig" 34 k8sRequest "github.com/operator-framework/operator-sdk/pkg/ansible/proxy/requestfactory" 35 osdkHandler "github.com/operator-framework/operator-sdk/pkg/handler" 36 "k8s.io/apimachinery/pkg/api/meta" 37 metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" 38 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 39 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 40 "k8s.io/apimachinery/pkg/runtime/schema" 41 "k8s.io/apimachinery/pkg/util/sets" 42 "k8s.io/client-go/rest" 43 "sigs.k8s.io/controller-runtime/pkg/cache" 44 "sigs.k8s.io/controller-runtime/pkg/client" 45 "sigs.k8s.io/controller-runtime/pkg/handler" 46 "sigs.k8s.io/controller-runtime/pkg/source" 47 ) 48 49 type marshaler interface { 50 MarshalJSON() ([]byte, error) 51 } 52 53 // CacheResponseHandler will handle proxied requests and check if the requested 54 // resource exists in our cache. If it does then there is no need to bombard 55 // the APIserver with our request and we should write the response from the 56 // proxy. 57 func CacheResponseHandler(h http.Handler, informerCache cache.Cache, restMapper meta.RESTMapper, watchedNamespaces map[string]interface{}, cMap *controllermap.ControllerMap, injectOwnerRef bool) http.Handler { 58 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 59 switch req.Method { 60 case http.MethodGet: 61 // GET request means we need to check the cache 62 rf := k8sRequest.RequestInfoFactory{APIPrefixes: sets.NewString("api", "apis"), GrouplessAPIPrefixes: sets.NewString("api")} 63 r, err := rf.NewRequestInfo(req) 64 if err != nil { 65 log.Error(err, "Failed to convert request") 66 break 67 } 68 69 // check if resource is present on request 70 if !r.IsResourceRequest { 71 break 72 } 73 74 // check if resource doesn't exist in watched namespaces 75 // if watchedNamespaces[""] exists then we are watching all namespaces 76 // and want to continue 77 _, allNsPresent := watchedNamespaces[metav1.NamespaceAll] 78 _, reqNsPresent := watchedNamespaces[r.Namespace] 79 if !allNsPresent && !reqNsPresent { 80 break 81 } 82 83 if strings.HasPrefix(r.Path, "/version") { 84 // Temporarily pass along to API server 85 // Ideally we cache this response as well 86 break 87 } 88 89 gvr := schema.GroupVersionResource{ 90 Group: r.APIGroup, 91 Version: r.APIVersion, 92 Resource: r.Resource, 93 } 94 if restMapper == nil { 95 restMapper = meta.NewDefaultRESTMapper([]schema.GroupVersion{schema.GroupVersion{ 96 Group: r.APIGroup, 97 Version: r.APIVersion, 98 }}) 99 } 100 101 k, err := restMapper.KindFor(gvr) 102 if err != nil { 103 // break here in case resource doesn't exist in cache 104 log.Info("Cache miss, can not find in rest mapper", "GVR", gvr) 105 break 106 } 107 108 var m marshaler 109 110 log.V(2).Info("Get resource in our cache", "r", r) 111 if r.Verb == "list" { 112 listOptions := &metav1.ListOptions{} 113 if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, listOptions); err != nil { 114 log.Error(err, "Unable to decode list options from request") 115 break 116 } 117 lo := client.InNamespace(r.Namespace) 118 if err := lo.SetLabelSelector(listOptions.LabelSelector); err != nil { 119 log.Error(err, "Unable to set label selectors for the client") 120 break 121 } 122 if listOptions.FieldSelector != "" { 123 if err := lo.SetFieldSelector(listOptions.FieldSelector); err != nil { 124 log.Error(err, "Unable to set field selectors for the client") 125 break 126 } 127 } 128 k.Kind = k.Kind + "List" 129 un := unstructured.UnstructuredList{} 130 un.SetGroupVersionKind(k) 131 err = informerCache.List(context.Background(), lo, &un) 132 if err != nil { 133 // break here in case resource doesn't exist in cache but exists on APIserver 134 // This is very unlikely but provides user with expected 404 135 log.Info(fmt.Sprintf("cache miss: %v err-%v", k, err)) 136 break 137 } 138 m = &un 139 } else { 140 un := &unstructured.Unstructured{} 141 un.SetGroupVersionKind(k) 142 obj := client.ObjectKey{Namespace: r.Namespace, Name: r.Name} 143 err = informerCache.Get(context.Background(), obj, un) 144 if err != nil { 145 // break here in case resource doesn't exist in cache but exists on APIserver 146 // This is very unlikely but provides user with expected 404 147 log.Info(fmt.Sprintf("Cache miss: %v, %v", k, obj)) 148 break 149 } 150 m = un 151 // Once we get the resource, we are going to attempt to recover the dependent watches here, 152 // This will happen in the background, and log errors. 153 if injectOwnerRef { 154 go recoverDependentWatches(req, un, cMap, restMapper) 155 } 156 } 157 158 i := bytes.Buffer{} 159 resp, err := m.MarshalJSON() 160 if err != nil { 161 // return will give a 500 162 log.Error(err, "Failed to marshal data") 163 http.Error(w, "", http.StatusInternalServerError) 164 return 165 } 166 167 // Set Content-Type header 168 w.Header().Set("Content-Type", "application/json") 169 // Set X-Cache header to signal that response is served from Cache 170 w.Header().Set("X-Cache", "HIT") 171 if err := json.Indent(&i, resp, "", " "); err != nil { 172 log.Error(err, "Failed to indent json") 173 } 174 _, err = w.Write(i.Bytes()) 175 if err != nil { 176 log.Error(err, "Failed to write response") 177 http.Error(w, "", http.StatusInternalServerError) 178 return 179 } 180 181 // Return so that request isn't passed along to APIserver 182 return 183 } 184 h.ServeHTTP(w, req) 185 }) 186 } 187 188 func recoverDependentWatches(req *http.Request, un *unstructured.Unstructured, cMap *controllermap.ControllerMap, restMapper meta.RESTMapper) { 189 ownerRef, err := getRequestOwnerRef(req) 190 if err != nil { 191 log.Error(err, "Could not get ownerRef from proxy") 192 return 193 } 194 195 for _, oRef := range un.GetOwnerReferences() { 196 if oRef.APIVersion == ownerRef.APIVersion && oRef.Kind == ownerRef.Kind { 197 err := addWatchToController(ownerRef, cMap, un, restMapper, true) 198 if err != nil { 199 log.Error(err, "Could not recover dependent resource watch", "owner", ownerRef) 200 return 201 } 202 } 203 } 204 if typeString, ok := un.GetAnnotations()[osdkHandler.TypeAnnotation]; ok { 205 ownerGV, err := schema.ParseGroupVersion(ownerRef.APIVersion) 206 if err != nil { 207 log.Error(err, "Could not get ownerRef from proxy") 208 return 209 } 210 if typeString == fmt.Sprintf("%v.%v", ownerRef.Kind, ownerGV.Group) { 211 err := addWatchToController(ownerRef, cMap, un, restMapper, false) 212 if err != nil { 213 log.Error(err, "Could not recover dependent resource watch", "owner", ownerRef) 214 return 215 } 216 } 217 } 218 } 219 220 // InjectOwnerReferenceHandler will handle proxied requests and inject the 221 // owner reference found in the authorization header. The Authorization is 222 // then deleted so that the proxy can re-set with the correct authorization. 223 func InjectOwnerReferenceHandler(h http.Handler, cMap *controllermap.ControllerMap, restMapper meta.RESTMapper, watchedNamespaces map[string]interface{}) http.Handler { 224 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 225 switch req.Method { 226 case http.MethodPost: 227 dump, _ := httputil.DumpRequest(req, false) 228 log.V(1).Info("Dumping request", "RequestDump", string(dump)) 229 rf := k8sRequest.RequestInfoFactory{APIPrefixes: sets.NewString("api", "apis"), GrouplessAPIPrefixes: sets.NewString("api")} 230 r, err := rf.NewRequestInfo(req) 231 if err != nil { 232 m := "Could not convert request" 233 log.Error(err, m) 234 http.Error(w, m, http.StatusBadRequest) 235 return 236 } 237 if r.Subresource != "" { 238 // Don't inject owner ref if we are POSTing to a subresource 239 break 240 } 241 log.Info("Injecting owner reference") 242 owner, err := getRequestOwnerRef(req) 243 if err != nil { 244 m := "Could not get owner reference" 245 log.Error(err, m) 246 http.Error(w, m, http.StatusInternalServerError) 247 return 248 } 249 250 body, err := ioutil.ReadAll(req.Body) 251 if err != nil { 252 m := "Could not read request body" 253 log.Error(err, m) 254 http.Error(w, m, http.StatusInternalServerError) 255 return 256 } 257 data := &unstructured.Unstructured{} 258 err = json.Unmarshal(body, data) 259 if err != nil { 260 m := "Could not deserialize request body" 261 log.Error(err, m) 262 http.Error(w, m, http.StatusBadRequest) 263 return 264 } 265 266 addOwnerRef, err := shouldAddOwnerRef(data, owner, restMapper) 267 if err != nil { 268 m := "Could not determine if we should add owner ref" 269 log.Error(err, m) 270 http.Error(w, m, http.StatusBadRequest) 271 return 272 } 273 if addOwnerRef { 274 data.SetOwnerReferences(append(data.GetOwnerReferences(), owner.OwnerReference)) 275 } else { 276 ownerGV, err := schema.ParseGroupVersion(owner.APIVersion) 277 if err != nil { 278 m := fmt.Sprintf("could not get broup version for: %v", owner) 279 log.Error(err, m) 280 http.Error(w, m, http.StatusBadRequest) 281 return 282 } 283 a := data.GetAnnotations() 284 if a == nil { 285 a = map[string]string{} 286 } 287 a[osdkHandler.NamespacedNameAnnotation] = strings.Join([]string{owner.Namespace, owner.Name}, "/") 288 a[osdkHandler.TypeAnnotation] = fmt.Sprintf("%v.%v", owner.Kind, ownerGV.Group) 289 290 data.SetAnnotations(a) 291 } 292 newBody, err := json.Marshal(data.Object) 293 if err != nil { 294 m := "Could not serialize body" 295 log.Error(err, m) 296 http.Error(w, m, http.StatusInternalServerError) 297 return 298 } 299 log.V(1).Info("Serialized body", "Body", string(newBody)) 300 req.Body = ioutil.NopCloser(bytes.NewBuffer(newBody)) 301 req.ContentLength = int64(len(newBody)) 302 303 // add watch for resource 304 // check if resource doesn't exist in watched namespaces 305 // if watchedNamespaces[""] exists then we are watching all namespaces 306 // and want to continue 307 // This is making sure we are not attempting to watch a resource outside of the 308 // namespaces that the cache can watch. 309 _, allNsPresent := watchedNamespaces[metav1.NamespaceAll] 310 _, reqNsPresent := watchedNamespaces[r.Namespace] 311 if allNsPresent || reqNsPresent { 312 err = addWatchToController(owner, cMap, data, restMapper, addOwnerRef) 313 if err != nil { 314 m := "could not add watch to controller" 315 log.Error(err, m) 316 http.Error(w, m, http.StatusInternalServerError) 317 return 318 } 319 } 320 } 321 h.ServeHTTP(w, req) 322 }) 323 } 324 325 func removeAuthorizationHeader(h http.Handler) http.Handler { 326 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 327 req.Header.Del("Authorization") 328 h.ServeHTTP(w, req) 329 }) 330 } 331 332 func shouldAddOwnerRef(data *unstructured.Unstructured, owner kubeconfig.NamespacedOwnerReference, restMapper meta.RESTMapper) (bool, error) { 333 dataMapping, err := restMapper.RESTMapping(data.GroupVersionKind().GroupKind(), data.GroupVersionKind().Version) 334 if err != nil { 335 m := fmt.Sprintf("Could not get rest mapping for: %v", data.GroupVersionKind()) 336 log.Error(err, m) 337 return false, err 338 339 } 340 // We need to determine whether or not the owner is a cluster-scoped 341 // resource because enqueue based on an owner reference does not work if 342 // a namespaced resource owns a cluster-scoped resource 343 ownerGV, err := schema.ParseGroupVersion(owner.APIVersion) 344 if err != nil { 345 m := fmt.Sprintf("could not get group version for: %v", owner) 346 log.Error(err, m) 347 return false, err 348 } 349 ownerMapping, err := restMapper.RESTMapping(schema.GroupKind{Kind: owner.Kind, Group: ownerGV.Group}, ownerGV.Version) 350 if err != nil { 351 m := fmt.Sprintf("could not get rest mapping for: %v", owner) 352 log.Error(err, m) 353 return false, err 354 } 355 356 dataNamespaceScoped := dataMapping.Scope.Name() != meta.RESTScopeNameRoot 357 ownerNamespaceScoped := ownerMapping.Scope.Name() != meta.RESTScopeNameRoot 358 359 if dataNamespaceScoped && ownerNamespaceScoped && data.GetNamespace() == owner.Namespace { 360 return true, nil 361 } 362 return false, nil 363 } 364 365 // RequestLogHandler - log the requests that come through the proxy. 366 func RequestLogHandler(h http.Handler) http.Handler { 367 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 368 // read body 369 body, err := ioutil.ReadAll(req.Body) 370 if err != nil { 371 log.Error(err, "Could not read request body") 372 } 373 // fix body 374 req.Body = ioutil.NopCloser(bytes.NewBuffer(body)) 375 log.Info("Request Info", "method", req.Method, "uri", req.RequestURI, "body", string(body)) 376 // Removing the authorization so that the proxy can set the correct authorization. 377 req.Header.Del("Authorization") 378 h.ServeHTTP(w, req) 379 }) 380 } 381 382 // HandlerChain will be used for users to pass defined handlers to the proxy. 383 // The hander chain will be run after InjectingOwnerReference if it is added 384 // and before the proxy handler. 385 type HandlerChain func(http.Handler) http.Handler 386 387 // Options will be used by the user to specify the desired details 388 // for the proxy. 389 type Options struct { 390 Address string 391 Port int 392 Handler HandlerChain 393 OwnerInjection bool 394 LogRequests bool 395 KubeConfig *rest.Config 396 Cache cache.Cache 397 RESTMapper meta.RESTMapper 398 ControllerMap *controllermap.ControllerMap 399 WatchedNamespaces []string 400 DisableCache bool 401 } 402 403 // Run will start a proxy server in a go routine that returns on the error 404 // channel if something is not correct on startup. Run will not return until 405 // the network socket is listening. 406 func Run(done chan error, o Options) error { 407 server, err := newServer("/", o.KubeConfig) 408 if err != nil { 409 return err 410 } 411 if o.Handler != nil { 412 server.Handler = o.Handler(server.Handler) 413 } 414 if o.ControllerMap == nil { 415 return fmt.Errorf("failed to get controller map from options") 416 } 417 if o.WatchedNamespaces == nil { 418 return fmt.Errorf("failed to get list of watched namespaces from options") 419 } 420 421 watchedNamespaceMap := make(map[string]interface{}) 422 // Convert string list to map 423 for _, ns := range o.WatchedNamespaces { 424 watchedNamespaceMap[ns] = nil 425 } 426 427 if o.Cache == nil && !o.DisableCache { 428 // Need to initialize cache since we don't have one 429 log.Info("Initializing and starting informer cache...") 430 informerCache, err := cache.New(o.KubeConfig, cache.Options{}) 431 if err != nil { 432 return err 433 } 434 stop := make(chan struct{}) 435 go func() { 436 if err := informerCache.Start(stop); err != nil { 437 log.Error(err, "Failed to start informer cache") 438 } 439 defer close(stop) 440 }() 441 log.Info("Waiting for cache to sync...") 442 synced := informerCache.WaitForCacheSync(stop) 443 if !synced { 444 return fmt.Errorf("failed to sync cache") 445 } 446 log.Info("Cache sync was successful") 447 o.Cache = informerCache 448 } 449 450 server.Handler = removeAuthorizationHeader(server.Handler) 451 452 if o.OwnerInjection { 453 server.Handler = InjectOwnerReferenceHandler(server.Handler, o.ControllerMap, o.RESTMapper, watchedNamespaceMap) 454 } else { 455 log.Info("Warning: injection of owner references and dependent watches is turned off") 456 } 457 if o.LogRequests { 458 server.Handler = RequestLogHandler(server.Handler) 459 } 460 if !o.DisableCache { 461 server.Handler = CacheResponseHandler(server.Handler, o.Cache, o.RESTMapper, watchedNamespaceMap, o.ControllerMap, o.OwnerInjection) 462 } 463 464 l, err := server.Listen(o.Address, o.Port) 465 if err != nil { 466 return err 467 } 468 go func() { 469 log.Info("Starting to serve", "Address", l.Addr().String()) 470 done <- server.ServeOnListener(l) 471 }() 472 return nil 473 } 474 475 func addWatchToController(owner kubeconfig.NamespacedOwnerReference, cMap *controllermap.ControllerMap, resource *unstructured.Unstructured, restMapper meta.RESTMapper, useOwnerRef bool) error { 476 dataMapping, err := restMapper.RESTMapping(resource.GroupVersionKind().GroupKind(), resource.GroupVersionKind().Version) 477 if err != nil { 478 m := fmt.Sprintf("Could not get rest mapping for: %v", resource.GroupVersionKind()) 479 log.Error(err, m) 480 return err 481 482 } 483 ownerGV, err := schema.ParseGroupVersion(owner.APIVersion) 484 if err != nil { 485 m := fmt.Sprintf("could not get broup version for: %v", owner) 486 log.Error(err, m) 487 return err 488 } 489 ownerMapping, err := restMapper.RESTMapping(schema.GroupKind{Kind: owner.Kind, Group: ownerGV.Group}, ownerGV.Version) 490 if err != nil { 491 m := fmt.Sprintf("could not get rest mapping for: %v", owner) 492 log.Error(err, m) 493 return err 494 } 495 496 dataNamespaceScoped := dataMapping.Scope.Name() != meta.RESTScopeNameRoot 497 contents, ok := cMap.Get(ownerMapping.GroupVersionKind) 498 if !ok { 499 return errors.New("failed to find controller in map") 500 } 501 owMap := contents.OwnerWatchMap 502 awMap := contents.AnnotationWatchMap 503 u := &unstructured.Unstructured{} 504 u.SetGroupVersionKind(ownerMapping.GroupVersionKind) 505 // Add a watch to controller 506 if contents.WatchDependentResources { 507 // Store watch in map 508 // Use EnqueueRequestForOwner unless user has configured watching cluster scoped resources and we have to 509 switch { 510 case useOwnerRef: 511 _, exists := owMap.Get(resource.GroupVersionKind()) 512 // If already watching resource no need to add a new watch 513 if exists { 514 return nil 515 } 516 517 owMap.Store(resource.GroupVersionKind()) 518 log.Info("Watching child resource", "kind", resource.GroupVersionKind(), "enqueue_kind", u.GroupVersionKind()) 519 // Store watch in map 520 err := contents.Controller.Watch(&source.Kind{Type: resource}, &handler.EnqueueRequestForOwner{OwnerType: u}) 521 if err != nil { 522 return err 523 } 524 case (!useOwnerRef && dataNamespaceScoped) || contents.WatchClusterScopedResources: 525 _, exists := awMap.Get(resource.GroupVersionKind()) 526 // If already watching resource no need to add a new watch 527 if exists { 528 return nil 529 } 530 awMap.Store(resource.GroupVersionKind()) 531 typeString := fmt.Sprintf("%v.%v", owner.Kind, ownerGV.Group) 532 log.Info("Watching child resource", "kind", resource.GroupVersionKind(), "enqueue_annotation_type", typeString) 533 err = contents.Controller.Watch(&source.Kind{Type: resource}, &osdkHandler.EnqueueRequestForAnnotation{Type: typeString}) 534 if err != nil { 535 return err 536 } 537 } 538 } 539 return nil 540 } 541 542 func getRequestOwnerRef(req *http.Request) (kubeconfig.NamespacedOwnerReference, error) { 543 owner := kubeconfig.NamespacedOwnerReference{} 544 user, _, ok := req.BasicAuth() 545 if !ok { 546 return owner, errors.New("basic auth header not found") 547 } 548 authString, err := base64.StdEncoding.DecodeString(user) 549 if err != nil { 550 m := "Could not base64 decode username" 551 log.Error(err, m) 552 return owner, err 553 } 554 // Set owner to NamespacedOwnerReference, which has metav1.OwnerReference 555 // as a subset along with the Namespace of the owner. Please see the 556 // kubeconfig.NamespacedOwnerReference type for more information. The 557 // namespace is required when creating the reconcile requests. 558 json.Unmarshal(authString, &owner) 559 if err := json.Unmarshal(authString, &owner); err != nil { 560 m := "Could not unmarshal auth string" 561 log.Error(err, m) 562 return owner, err 563 } 564 return owner, err 565 }