// NewProxyServer creates and installs a new ProxyServer. // It automatically registers the created ProxyServer to http.DefaultServeMux. // 'filter', if non-nil, protects requests to the api only. func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *client.Config) (*ProxyServer, error) { host := cfg.Host if !strings.HasSuffix(host, "/") { host = host + "/" } target, err := url.Parse(host) if err != nil { return nil, err } proxy := newProxy(target) if proxy.Transport, err = client.TransportFor(cfg); err != nil { return nil, err } proxyServer := http.Handler(proxy) if filter != nil { proxyServer = filter.HandlerFor(proxyServer) } if !strings.HasPrefix(apiProxyPrefix, "/api") { proxyServer = stripLeaveSlash(apiProxyPrefix, proxyServer) } mux := http.NewServeMux() mux.Handle(apiProxyPrefix, proxyServer) if filebase != "" { // Require user to explicitly request this behavior rather than // serving their working directory by default. mux.Handle(staticPrefix, newFileHandler(staticPrefix, filebase)) } return &ProxyServer{handler: mux}, nil }
func TestImportImageRedHatRegistry(t *testing.T) { rt, _ := kclient.TransportFor(&kclient.Config{}) importCtx := importer.NewContext(rt).WithCredentials(importer.NoCredentials) // test without the client on the context imports := &api.ImageStreamImport{ Spec: api.ImageStreamImportSpec{ Images: []api.ImageImportSpec{ {From: kapi.ObjectReference{Kind: "DockerImage", Name: "registry.access.redhat.com/rhel7"}}, }, }, } i := importer.NewImageStreamImporter(importCtx, 3, nil) if err := i.Import(gocontext.Background(), imports); err != nil { t.Fatal(err) } if imports.Status.Repository != nil { t.Errorf("unexpected repository: %#v", imports.Status.Repository) } if len(imports.Status.Images) != 1 { t.Fatalf("unexpected response: %#v", imports.Status.Images) } d := imports.Status.Images[0] if d.Image != nil || d.Status.Status != unversioned.StatusFailure || d.Status.Reason != "NotV2Registry" { t.Errorf("unexpected object: %#v", d.Status) } // test with the client on the context imports = &api.ImageStreamImport{ Spec: api.ImageStreamImportSpec{ Images: []api.ImageImportSpec{ {From: kapi.ObjectReference{Kind: "DockerImage", Name: "registry.access.redhat.com/rhel7"}}, }, }, } context := gocontext.WithValue(gocontext.Background(), importer.ContextKeyV1RegistryClient, dockerregistry.NewClient(20*time.Second, false)) importCtx = importer.NewContext(rt).WithCredentials(importer.NoCredentials) i = importer.NewImageStreamImporter(importCtx, 3, nil) if err := i.Import(context, imports); err != nil { t.Fatal(err) } if imports.Status.Repository != nil { t.Errorf("unexpected repository: %#v", imports.Status.Repository) } if len(imports.Status.Images) != 1 { t.Fatalf("unexpected response: %#v", imports.Status.Images) } d = imports.Status.Images[0] if d.Image == nil || len(d.Image.DockerImageManifest) != 0 || d.Image.DockerImageReference != "registry.access.redhat.com/rhel7:latest" || len(d.Image.DockerImageMetadata.ID) == 0 || len(d.Image.DockerImageLayers) != 0 { t.Errorf("unexpected object: %#v", d.Status) t.Logf("imports: %#v", imports.Status.Images[0].Image) } }
func init() { cache, err := newDigestToRepositoryCache(1024) if err != nil { panic(err) } cachedLayers = cache repomw.Register("openshift", repomw.InitFunc(newRepository)) secureTransport = http.DefaultTransport insecureTransport, err = kclient.TransportFor(&kclient.Config{Insecure: true}) if err != nil { panic(fmt.Sprintf("Unable to configure a default transport for importing insecure images: %v", err)) } }
// RunStartBuildWebHook tries to trigger the provided webhook. It will attempt to utilize the current client // configuration if the webhook has the same URL. func RunStartBuildWebHook(f *clientcmd.Factory, out io.Writer, webhook string, path, postReceivePath string, repo git.Repository) error { hook, err := url.Parse(webhook) if err != nil { return err } event, err := hookEventFromPostReceive(repo, path, postReceivePath) if err != nil { return err } // TODO: should be a versioned struct var data []byte if event != nil { data, err = json.Marshal(event) if err != nil { return err } } httpClient := http.DefaultClient // when using HTTPS, try to reuse the local config transport if possible to get a client cert // TODO: search all configs if hook.Scheme == "https" { config, err := f.OpenShiftClientConfig.ClientConfig() if err == nil { if url, err := client.DefaultServerURL(config.Host, "", "test", true); err == nil { if url.Host == hook.Host && url.Scheme == hook.Scheme { if rt, err := client.TransportFor(config); err == nil { httpClient = &http.Client{Transport: rt} } } } } } glog.V(4).Infof("Triggering hook %s\n%s", hook, string(data)) resp, err := httpClient.Post(hook.String(), "application/json", bytes.NewBuffer(data)) if err != nil { return err } switch { case resp.StatusCode == 301 || resp.StatusCode == 302: // TODO: follow redirect and display output case resp.StatusCode < 200 || resp.StatusCode >= 300: body, _ := ioutil.ReadAll(resp.Body) return fmt.Errorf("server rejected our request %d\nremote: %s", resp.StatusCode, string(body)) } return nil }
// NewUpgradeAwareSingleHostReverseProxy creates a new UpgradeAwareSingleHostReverseProxy. func NewUpgradeAwareSingleHostReverseProxy(clientConfig *kclient.Config, backendAddr *url.URL) (*UpgradeAwareSingleHostReverseProxy, error) { transport, err := kclient.TransportFor(clientConfig) if err != nil { return nil, err } reverseProxy := httputil.NewSingleHostReverseProxy(backendAddr) reverseProxy.FlushInterval = 200 * time.Millisecond p := &UpgradeAwareSingleHostReverseProxy{ clientConfig: clientConfig, backendAddr: backendAddr, transport: transport, reverseProxy: reverseProxy, } p.reverseProxy.Transport = p return p, nil }
// NewREST returns a REST storage implementation that handles importing images. The clientFn argument is optional // if v1 Docker Registry importing is not required func NewREST(importFn ImporterFunc, streams imagestream.Registry, internalStreams rest.CreaterUpdater, images rest.Creater, secrets client.ImageStreamSecretsNamespacer, clientFn ImporterDockerRegistryFunc) *REST { rt, err := kclient.TransportFor(&kclient.Config{}) // TODO: will be refactored upstream, or take this as input? if err != nil { panic(err) } return &REST{ importFn: importFn, streams: streams, internalStreams: internalStreams, images: images, secrets: secrets, transport: rt, clientFn: clientFn, } }
func TestImportImageDockerHub(t *testing.T) { rt, _ := kclient.TransportFor(&kclient.Config{}) importCtx := importer.NewContext(rt).WithCredentials(importer.NoCredentials) imports := &api.ImageStreamImport{ Spec: api.ImageStreamImportSpec{ Repository: &api.RepositoryImportSpec{ From: kapi.ObjectReference{Kind: "DockerImage", Name: "mongo"}, }, Images: []api.ImageImportSpec{ {From: kapi.ObjectReference{Kind: "DockerImage", Name: "redis"}}, {From: kapi.ObjectReference{Kind: "DockerImage", Name: "mysql"}}, {From: kapi.ObjectReference{Kind: "DockerImage", Name: "redis:latest"}}, {From: kapi.ObjectReference{Kind: "DockerImage", Name: "mysql/doesnotexistinanyform"}}, }, }, } i := importer.NewImageStreamImporter(importCtx, 3, nil) if err := i.Import(gocontext.Background(), imports); err != nil { t.Fatal(err) } if imports.Status.Repository.Status.Status != unversioned.StatusSuccess || len(imports.Status.Repository.Images) != 3 || len(imports.Status.Repository.AdditionalTags) < 1 { t.Errorf("unexpected repository: %#v", imports.Status.Repository) } if len(imports.Status.Images) != 4 { t.Fatalf("unexpected response: %#v", imports.Status.Images) } d := imports.Status.Images[0] if d.Image == nil || len(d.Image.DockerImageManifest) == 0 || !strings.HasPrefix(d.Image.DockerImageReference, "redis@") || len(d.Image.DockerImageMetadata.ID) == 0 || len(d.Image.DockerImageLayers) == 0 { t.Errorf("unexpected object: %#v", d.Image) } d = imports.Status.Images[1] if d.Image == nil || len(d.Image.DockerImageManifest) == 0 || !strings.HasPrefix(d.Image.DockerImageReference, "mysql@") || len(d.Image.DockerImageMetadata.ID) == 0 || len(d.Image.DockerImageLayers) == 0 { t.Errorf("unexpected object: %#v", d.Image) } d = imports.Status.Images[2] if d.Image == nil || len(d.Image.DockerImageManifest) == 0 || !strings.HasPrefix(d.Image.DockerImageReference, "redis@") || len(d.Image.DockerImageMetadata.ID) == 0 || len(d.Image.DockerImageLayers) == 0 { t.Errorf("unexpected object: %#v", d.Image) } d = imports.Status.Images[3] if d.Image != nil || d.Status.Status != unversioned.StatusFailure || d.Status.Reason != "Unauthorized" { t.Errorf("unexpected object: %#v", d) } }
// EtcdClient creates an etcd client based on the provided config. func EtcdClient(etcdClientInfo configapi.EtcdConnectionInfo) (*etcdclient.Client, error) { // etcd does a poor job of setting up the transport - use the Kube client stack transport, err := client.TransportFor(&client.Config{ TLSClientConfig: client.TLSClientConfig{ CertFile: etcdClientInfo.ClientCert.CertFile, KeyFile: etcdClientInfo.ClientCert.KeyFile, CAFile: etcdClientInfo.CA, }, WrapTransport: DefaultEtcdClientTransport, }) if err != nil { return nil, err } etcdClient := etcdclient.NewClient(etcdClientInfo.URLs) etcdClient.SetTransport(transport.(*http.Transport)) return etcdClient, nil }
func TestImportImageQuayIO(t *testing.T) { rt, _ := kclient.TransportFor(&kclient.Config{}) importCtx := importer.NewContext(rt).WithCredentials(importer.NoCredentials) imports := &api.ImageStreamImport{ Spec: api.ImageStreamImportSpec{ Images: []api.ImageImportSpec{ {From: kapi.ObjectReference{Kind: "DockerImage", Name: "quay.io/coreos/etcd"}}, }, }, } i := importer.NewImageStreamImporter(importCtx, 3, nil) if err := i.Import(gocontext.Background(), imports); err != nil { t.Fatal(err) } if imports.Status.Repository != nil { t.Errorf("unexpected repository: %#v", imports.Status.Repository) } if len(imports.Status.Images) != 1 { t.Fatalf("unexpected response: %#v", imports.Status.Images) } d := imports.Status.Images[0] if d.Status.Status != unversioned.StatusSuccess { if d.Status.Reason == "NotV2Registry" { t.Skipf("the server did not report as a v2 registry: %#v", d.Status) } t.Fatalf("unexpected error: %#v", d.Status) } if d.Image == nil || len(d.Image.DockerImageManifest) == 0 || !strings.HasPrefix(d.Image.DockerImageReference, "quay.io/coreos/etcd@") || len(d.Image.DockerImageMetadata.ID) == 0 || len(d.Image.DockerImageLayers) == 0 { t.Errorf("unexpected object: %#v", d.Image) s := spew.ConfigState{ Indent: " ", // Extra deep spew. DisableMethods: true, } t.Logf("import: %s", s.Sdump(d)) } }
// RequestToken uses the cmd arguments to locate an openshift oauth server and attempts to authenticate // it returns the access token if it gets one. An error if it does not func RequestToken(clientCfg *kclient.Config, reader io.Reader, defaultUsername string, defaultPassword string) (string, error) { challengeHandler := &BasicChallengeHandler{ Host: clientCfg.Host, Reader: reader, Username: defaultUsername, Password: defaultPassword, } rt, err := kclient.TransportFor(clientCfg) if err != nil { return "", err } // requestURL holds the current URL to make requests to. This can change if the server responds with a redirect requestURL := clientCfg.Host + "/oauth/authorize?response_type=token&client_id=openshift-challenging-client" // requestHeaders holds additional headers to add to the request. This can be changed by challengeHandlers requestHeaders := http.Header{} // requestedURLSet/requestedURLList hold the URLs we have requested, to prevent redirect loops. Gets reset when a challenge is handled. requestedURLSet := sets.NewString() requestedURLList := []string{} for { // Make the request resp, err := request(rt, requestURL, requestHeaders) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode == http.StatusUnauthorized { if resp.Header.Get("WWW-Authenticate") != "" { if !challengeHandler.CanHandle(resp.Header) { return "", apierrs.NewUnauthorized("unhandled challenge") } // Handle a challenge newRequestHeaders, shouldRetry, err := challengeHandler.HandleChallenge(resp.Header) if err != nil { return "", err } if !shouldRetry { return "", apierrs.NewUnauthorized("challenger chose not to retry the request") } // Reset request set/list. Since we're setting different headers, it is legitimate to request the same urls requestedURLSet = sets.NewString() requestedURLList = []string{} // Use the response to the challenge as the new headers requestHeaders = newRequestHeaders continue } // Unauthorized with no challenge unauthorizedError := apierrs.NewUnauthorized("") // Attempt to read body content and include as an error detail if details, err := ioutil.ReadAll(resp.Body); err == nil && len(details) > 0 { unauthorizedError.(*apierrs.StatusError).ErrStatus.Details = &api.StatusDetails{ Causes: []api.StatusCause{ {Message: string(details)}, }, } } return "", unauthorizedError } if resp.StatusCode == http.StatusFound { redirectURL := resp.Header.Get("Location") // OAuth response case (access_token or error parameter) accessToken, err := oauthAuthorizeResult(redirectURL) if err != nil { return "", err } if len(accessToken) > 0 { return accessToken, err } // Non-OAuth response, just follow the URL // add to our list of redirects requestedURLList = append(requestedURLList, redirectURL) // detect loops if !requestedURLSet.Has(redirectURL) { requestedURLSet.Insert(redirectURL) requestURL = redirectURL continue } return "", apierrs.NewInternalError(fmt.Errorf("redirect loop: %s", strings.Join(requestedURLList, " -> "))) } // Unknown response return "", apierrs.NewInternalError(fmt.Errorf("unexpected response: %d", resp.StatusCode)) } }
func (c *MasterConfig) GetRestStorage() map[string]rest.Storage { defaultRegistry := env("OPENSHIFT_DEFAULT_REGISTRY", "${DOCKER_REGISTRY_SERVICE_HOST}:${DOCKER_REGISTRY_SERVICE_PORT}") svcCache := service.NewServiceResolverCache(c.KubeClient().Services(kapi.NamespaceDefault).Get) defaultRegistryFunc, err := svcCache.Defer(defaultRegistry) if err != nil { glog.Fatalf("OPENSHIFT_DEFAULT_REGISTRY variable is invalid %q: %v", defaultRegistry, err) } kubeletClient, err := kubeletclient.NewStaticKubeletClient(c.KubeletClientConfig) if err != nil { glog.Fatalf("Unable to configure Kubelet client: %v", err) } // TODO: allow the system CAs and the local CAs to be joined together. importTransport, err := kclient.TransportFor(&kclient.Config{}) if err != nil { glog.Fatalf("Unable to configure a default transport for importing: %v", err) } insecureImportTransport, err := kclient.TransportFor(&kclient.Config{Insecure: true}) if err != nil { glog.Fatalf("Unable to configure a default transport for importing: %v", err) } buildStorage, buildDetailsStorage := buildetcd.NewREST(c.EtcdHelper) buildRegistry := buildregistry.NewRegistry(buildStorage) buildConfigStorage := buildconfigetcd.NewREST(c.EtcdHelper) buildConfigRegistry := buildconfigregistry.NewRegistry(buildConfigStorage) deployConfigStorage, deployConfigScaleStorage := deployconfigetcd.NewREST(c.EtcdHelper, c.DeploymentConfigScaleClient()) deployConfigRegistry := deployconfigregistry.NewRegistry(deployConfigStorage) routeAllocator := c.RouteAllocator() routeStorage, routeStatusStorage := routeetcd.NewREST(c.EtcdHelper, routeAllocator) hostSubnetStorage := hostsubnetetcd.NewREST(c.EtcdHelper) netNamespaceStorage := netnamespaceetcd.NewREST(c.EtcdHelper) clusterNetworkStorage := clusternetworketcd.NewREST(c.EtcdHelper) userStorage := useretcd.NewREST(c.EtcdHelper) userRegistry := userregistry.NewRegistry(userStorage) identityStorage := identityetcd.NewREST(c.EtcdHelper) identityRegistry := identityregistry.NewRegistry(identityStorage) userIdentityMappingStorage := useridentitymapping.NewREST(userRegistry, identityRegistry) policyStorage := policyetcd.NewStorage(c.EtcdHelper) policyRegistry := policyregistry.NewRegistry(policyStorage) policyBindingStorage := policybindingetcd.NewStorage(c.EtcdHelper) policyBindingRegistry := policybindingregistry.NewRegistry(policyBindingStorage) clusterPolicyStorage := clusterpolicystorage.NewStorage(c.EtcdHelper) clusterPolicyRegistry := clusterpolicyregistry.NewRegistry(clusterPolicyStorage) clusterPolicyBindingStorage := clusterpolicybindingstorage.NewStorage(c.EtcdHelper) clusterPolicyBindingRegistry := clusterpolicybindingregistry.NewRegistry(clusterPolicyBindingStorage) roleStorage := rolestorage.NewVirtualStorage(policyRegistry) roleBindingStorage := rolebindingstorage.NewVirtualStorage(policyRegistry, policyBindingRegistry, clusterPolicyRegistry, clusterPolicyBindingRegistry) clusterRoleStorage := clusterrolestorage.NewClusterRoleStorage(clusterPolicyRegistry) clusterRoleBindingStorage := clusterrolebindingstorage.NewClusterRoleBindingStorage(clusterPolicyRegistry, clusterPolicyBindingRegistry) subjectAccessReviewStorage := subjectaccessreview.NewREST(c.Authorizer) subjectAccessReviewRegistry := subjectaccessreview.NewRegistry(subjectAccessReviewStorage) localSubjectAccessReviewStorage := localsubjectaccessreview.NewREST(subjectAccessReviewRegistry) resourceAccessReviewStorage := resourceaccessreview.NewREST(c.Authorizer) resourceAccessReviewRegistry := resourceaccessreview.NewRegistry(resourceAccessReviewStorage) localResourceAccessReviewStorage := localresourceaccessreview.NewREST(resourceAccessReviewRegistry) imageStorage := imageetcd.NewREST(c.EtcdHelper) imageRegistry := image.NewRegistry(imageStorage) imageStreamSecretsStorage := imagesecret.NewREST(c.ImageStreamSecretClient()) imageStreamStorage, imageStreamStatusStorage, internalImageStreamStorage := imagestreametcd.NewREST(c.EtcdHelper, imagestream.DefaultRegistryFunc(defaultRegistryFunc), subjectAccessReviewRegistry) imageStreamRegistry := imagestream.NewRegistry(imageStreamStorage, imageStreamStatusStorage, internalImageStreamStorage) imageStreamMappingStorage := imagestreammapping.NewREST(imageRegistry, imageStreamRegistry) imageStreamTagStorage := imagestreamtag.NewREST(imageRegistry, imageStreamRegistry) imageStreamTagRegistry := imagestreamtag.NewRegistry(imageStreamTagStorage) importerFn := func(r importer.RepositoryRetriever) imageimporter.Interface { return imageimporter.NewImageStreamImporter(r, c.Options.ImagePolicyConfig.MaxImagesBulkImportedPerRepository, util.NewTokenBucketRateLimiter(2.0, 3)) } importerDockerClientFn := func() dockerregistry.Client { return dockerregistry.NewClient(20*time.Second, false) } imageStreamImportStorage := imagestreamimport.NewREST(importerFn, imageStreamRegistry, internalImageStreamStorage, imageStorage, c.ImageStreamImportSecretClient(), importTransport, insecureImportTransport, importerDockerClientFn) imageStreamImageStorage := imagestreamimage.NewREST(imageRegistry, imageStreamRegistry) imageStreamImageRegistry := imagestreamimage.NewRegistry(imageStreamImageStorage) buildGenerator := &buildgenerator.BuildGenerator{ Client: buildgenerator.Client{ GetBuildConfigFunc: buildConfigRegistry.GetBuildConfig, UpdateBuildConfigFunc: buildConfigRegistry.UpdateBuildConfig, GetBuildFunc: buildRegistry.GetBuild, CreateBuildFunc: buildRegistry.CreateBuild, GetImageStreamFunc: imageStreamRegistry.GetImageStream, GetImageStreamImageFunc: imageStreamImageRegistry.GetImageStreamImage, GetImageStreamTagFunc: imageStreamTagRegistry.GetImageStreamTag, }, ServiceAccounts: c.KubeClient(), Secrets: c.KubeClient(), } // TODO: with sharding, this needs to be changed deployConfigGenerator := &deployconfiggenerator.DeploymentConfigGenerator{ Client: deployconfiggenerator.Client{ DCFn: deployConfigRegistry.GetDeploymentConfig, ISFn: imageStreamRegistry.GetImageStream, LISFn2: imageStreamRegistry.ListImageStreams, }, } configClient, kclient := c.DeploymentConfigClients() deployRollback := &deployrollback.RollbackGenerator{} deployRollbackClient := deployrollback.Client{ DCFn: deployConfigRegistry.GetDeploymentConfig, RCFn: clientDeploymentInterface{kclient}.GetDeployment, GRFn: deployRollback.GenerateRollback, } projectStorage := projectproxy.NewREST(kclient.Namespaces(), c.ProjectAuthorizationCache) namespace, templateName, err := configapi.ParseNamespaceAndName(c.Options.ProjectConfig.ProjectRequestTemplate) if err != nil { glog.Errorf("Error parsing project request template value: %v", err) // we can continue on, the storage that gets created will be valid, it simply won't work properly. There's no reason to kill the master } projectRequestStorage := projectrequeststorage.NewREST(c.Options.ProjectConfig.ProjectRequestMessage, namespace, templateName, c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient) bcClient := c.BuildConfigWebHookClient() buildConfigWebHooks := buildconfigregistry.NewWebHookREST( buildConfigRegistry, buildclient.NewOSClientBuildConfigInstantiatorClient(bcClient), map[string]webhook.Plugin{ "generic": generic.New(), "github": github.New(), }, ) storage := map[string]rest.Storage{ "images": imageStorage, "imageStreams/secrets": imageStreamSecretsStorage, "imageStreams": imageStreamStorage, "imageStreams/status": imageStreamStatusStorage, "imageStreamImports": imageStreamImportStorage, "imageStreamImages": imageStreamImageStorage, "imageStreamMappings": imageStreamMappingStorage, "imageStreamTags": imageStreamTagStorage, "deploymentConfigs": deployConfigStorage, "deploymentConfigs/scale": deployConfigScaleStorage, "generateDeploymentConfigs": deployconfiggenerator.NewREST(deployConfigGenerator, c.EtcdHelper.Codec()), "deploymentConfigRollbacks": deployrollback.NewREST(deployRollbackClient, c.EtcdHelper.Codec()), "deploymentConfigs/log": deploylogregistry.NewREST(configClient, kclient, c.DeploymentLogClient(), kubeletClient), "processedTemplates": templateregistry.NewREST(), "templates": templateetcd.NewREST(c.EtcdHelper), "routes": routeStorage, "routes/status": routeStatusStorage, "projects": projectStorage, "projectRequests": projectRequestStorage, "hostSubnets": hostSubnetStorage, "netNamespaces": netNamespaceStorage, "clusterNetworks": clusterNetworkStorage, "users": userStorage, "groups": groupetcd.NewREST(c.EtcdHelper), "identities": identityStorage, "userIdentityMappings": userIdentityMappingStorage, "oAuthAuthorizeTokens": authorizetokenetcd.NewREST(c.EtcdHelper), "oAuthAccessTokens": accesstokenetcd.NewREST(c.EtcdHelper), "oAuthClients": clientetcd.NewREST(c.EtcdHelper), "oAuthClientAuthorizations": clientauthetcd.NewREST(c.EtcdHelper), "resourceAccessReviews": resourceAccessReviewStorage, "subjectAccessReviews": subjectAccessReviewStorage, "localSubjectAccessReviews": localSubjectAccessReviewStorage, "localResourceAccessReviews": localResourceAccessReviewStorage, "policies": policyStorage, "policyBindings": policyBindingStorage, "roles": roleStorage, "roleBindings": roleBindingStorage, "clusterPolicies": clusterPolicyStorage, "clusterPolicyBindings": clusterPolicyBindingStorage, "clusterRoleBindings": clusterRoleBindingStorage, "clusterRoles": clusterRoleStorage, } if configapi.IsBuildEnabled(&c.Options) { storage["builds"] = buildStorage storage["buildConfigs"] = buildConfigStorage storage["buildConfigs/webhooks"] = buildConfigWebHooks storage["builds/clone"] = buildclone.NewStorage(buildGenerator) storage["buildConfigs/instantiate"] = buildconfiginstantiate.NewStorage(buildGenerator) storage["buildConfigs/instantiatebinary"] = buildconfiginstantiate.NewBinaryStorage(buildGenerator, buildStorage, c.BuildLogClient(), kubeletClient) storage["builds/log"] = buildlogregistry.NewREST(buildStorage, buildStorage, c.BuildLogClient(), kubeletClient) storage["builds/details"] = buildDetailsStorage } return storage }
// TestOAuthRequestHeader checks the following scenarios: // * request containing remote user header is ignored if it doesn't have client cert auth // * request containing remote user header is honored if it has client cert auth // * unauthenticated requests are redirected to an auth proxy // * login command succeeds against a request-header identity provider via redirection to an auth proxy func TestOAuthRequestHeader(t *testing.T) { // Test data used by auth proxy users := map[string]string{ "myusername": "******", } // Write cert we're going to use to verify OAuth requestheader requests caFile, err := ioutil.TempFile("", "test.crt") if err != nil { t.Fatalf("unexpected error: %v", err) } defer os.Remove(caFile.Name()) if err := ioutil.WriteFile(caFile.Name(), rootCACert, os.FileMode(0600)); err != nil { t.Fatalf("unexpected error: %v", err) } // Get master config masterOptions, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } masterURL, _ := url.Parse(masterOptions.OAuthConfig.MasterPublicURL) // Set up an auth proxy var proxyTransport http.RoundTripper proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Decide whether to challenge username, password, hasBasicAuth := r.BasicAuth() if correctPassword, hasUser := users[username]; !hasBasicAuth || !hasUser || password != correctPassword { w.Header().Set("WWW-Authenticate", "Basic realm=Protected Area") w.WriteHeader(401) return } // Swap the scheme and host to the master, keeping path and params the same proxyURL := r.URL proxyURL.Scheme = masterURL.Scheme proxyURL.Host = masterURL.Host // Build a request, copying the original method, body, and headers, overriding the remote user headers proxyRequest, _ := http.NewRequest(r.Method, proxyURL.String(), r.Body) proxyRequest.Header = r.Header proxyRequest.Header.Set("My-Remote-User", username) proxyRequest.Header.Set("SSO-User", "") // Round trip to the back end response, err := proxyTransport.RoundTrip(r) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer response.Body.Close() // Copy response back to originator for k, v := range response.Header { w.Header()[k] = v } w.WriteHeader(response.StatusCode) if _, err := io.Copy(w, response.Body); err != nil { t.Fatalf("Unexpected error: %v", err) } })) defer proxyServer.Close() masterOptions.OAuthConfig.IdentityProviders[0] = configapi.IdentityProvider{ Name: "requestheader", UseAsChallenger: true, UseAsLogin: true, MappingMethod: "claim", Provider: &configapi.RequestHeaderIdentityProvider{ ChallengeURL: proxyServer.URL + "/oauth/authorize?${query}", LoginURL: "http://www.example.com/login?then=${url}", ClientCA: caFile.Name(), Headers: []string{"My-Remote-User", "SSO-User"}, }, } // Start server clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(masterOptions) if err != nil { t.Fatalf("unexpected error: %v", err) } clientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Use the server and CA info, but no client cert info anonConfig := kclient.Config{} anonConfig.Host = clientConfig.Host anonConfig.CAFile = clientConfig.CAFile anonConfig.CAData = clientConfig.CAData anonTransport, err := kclient.TransportFor(&anonConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Use the server and CA info, with cert info proxyConfig := anonConfig proxyConfig.CertData = clientCert proxyConfig.KeyData = clientKey proxyTransport, err = kclient.TransportFor(&proxyConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Build the authorize request, spoofing a remote user header authorizeURL := clientConfig.Host + "/oauth/authorize?client_id=openshift-challenging-client&response_type=token" req, err := http.NewRequest("GET", authorizeURL, nil) req.Header.Set("My-Remote-User", "myuser") // Make the request without cert auth resp, err := anonTransport.RoundTrip(req) if err != nil { t.Fatalf("unexpected error: %v", err) } proxyRedirect, err := resp.Location() if err != nil { t.Fatalf("expected spoofed remote user header to get 302 redirect, got error: %v", err) } if proxyRedirect.String() != proxyServer.URL+"/oauth/authorize?client_id=openshift-challenging-client&response_type=token" { t.Fatalf("expected redirect to proxy endpoint, got redirected to %v", proxyRedirect.String()) } // Request the redirected URL, which should cause the proxy to make the same request with cert auth req, err = http.NewRequest("GET", proxyRedirect.String(), nil) req.Header.Set("My-Remote-User", "myuser") req.SetBasicAuth("myusername", "mypassword") resp, err = proxyTransport.RoundTrip(req) if err != nil { t.Fatalf("unexpected error: %v", err) } tokenRedirect, err := resp.Location() if err != nil { t.Fatalf("expected 302 redirect, got error: %v", err) } if tokenRedirect.Query().Get("error") != "" { t.Fatalf("expected successful token request, got error %v", tokenRedirect.String()) } // Extract the access_token // group #0 is everything. #1 #2 #3 accessTokenRedirectRegex := regexp.MustCompile(`(^|&)access_token=([^&]+)($|&)`) accessToken := "" if matches := accessTokenRedirectRegex.FindStringSubmatch(tokenRedirect.Fragment); matches != nil { accessToken = matches[2] } if accessToken == "" { t.Fatalf("Expected access token, got %s", tokenRedirect.String()) } // Make sure we can use the token, and it represents who we expect userConfig := anonConfig userConfig.BearerToken = accessToken userClient, err := client.New(&userConfig) if err != nil { t.Fatalf("Unexpected error: %v", err) } user, err := userClient.Users().Get("~") if err != nil { t.Fatalf("Unexpected error: %v", err) } if user.Name != "myusername" { t.Fatalf("Expected myusername as the user, got %v", user) } // Get the master CA data for the login command masterCAFile := userConfig.CAFile if masterCAFile == "" { // Write master ca data tmpFile, err := ioutil.TempFile("", "ca.crt") if err != nil { t.Fatalf("unexpected error: %v", err) } defer os.Remove(tmpFile.Name()) if err := ioutil.WriteFile(tmpFile.Name(), userConfig.CAData, os.FileMode(0600)); err != nil { t.Fatalf("unexpected error: %v", err) } masterCAFile = tmpFile.Name() } // Attempt a login using a redirecting auth proxy loginOutput := &bytes.Buffer{} loginOptions := &cmd.LoginOptions{ Server: anonConfig.Host, CAFile: masterCAFile, StartingKubeConfig: &clientcmdapi.Config{}, Reader: bytes.NewBufferString("myusername\nmypassword\n"), Out: loginOutput, } if err := loginOptions.GatherInfo(); err != nil { t.Fatalf("Error trying to determine server info: %v\n%v", err, loginOutput.String()) } if loginOptions.Username != "myusername" { t.Fatalf("Unexpected user after authentication: %#v", loginOptions) } if len(loginOptions.Config.BearerToken) == 0 { t.Fatalf("Expected token after authentication: %#v", loginOptions.Config) } }
func TestRootAPIPaths(t *testing.T) { // ExceptionalExpectedCodes are codes that we expect, but are not http.StatusOK. // These codes are expected because the response from a GET on our root should // expose endpoints for discovery, but will not necessarily expose endpoints that // are supported as written - i.e. versioned endpoints or endpoints that need // context will 404 with the correct credentials and that is OK. ExceptionalExpectedCodes := map[string]int{ "/logs/": http.StatusNotFound, } masterConfig, adminConfigFile, err := testserver.StartTestMaster() if err != nil { t.Fatalf("unexpected error starting test master: %v", err) } clientConfig, err := testutil.GetClusterAdminClientConfig(adminConfigFile) if err != nil { t.Fatalf("unexpected error getting cluster admin client config: %v", err) } transport, err := kclient.TransportFor(clientConfig) if err != nil { t.Fatalf("unexpected error getting transport for client config: %v", err) } rootRequest, err := http.NewRequest("GET", masterConfig.AssetConfig.MasterPublicURL+"/", nil) rootRequest.Header.Set("Accept", "*/*") rootResponse, err := transport.RoundTrip(rootRequest) if err != nil { t.Fatalf("unexpected error issuing GET to root path: %v", err) } var broadcastRootPaths unversioned.RootPaths if err := json.NewDecoder(rootResponse.Body).Decode(&broadcastRootPaths); err != nil { t.Fatalf("unexpected error decoding root path response: %v", err) } defer rootResponse.Body.Close() // We need to make sure that any APILevels specified in the config are present in the RootPaths, and that // any not specified are not expectedOpenShiftAPILevels := sets.NewString(masterConfig.APILevels...) expectedKubeAPILevels := sets.NewString(configapi.GetEnabledAPIVersionsForGroup(*masterConfig.KubernetesMasterConfig, configapi.APIGroupKube)...) actualOpenShiftAPILevels := sets.String{} actualKubeAPILevels := sets.String{} for _, route := range broadcastRootPaths.Paths { if strings.HasPrefix(route, "/oapi/") { actualOpenShiftAPILevels.Insert(route[6:]) } if strings.HasPrefix(route, "/api/") { actualKubeAPILevels.Insert(route[5:]) } } if !expectedOpenShiftAPILevels.Equal(actualOpenShiftAPILevels) { t.Errorf("actual OpenShift API levels served don't match expected levels:\n\texpected:\n\t%s\n\tgot:\n\t%s", expectedOpenShiftAPILevels.List(), actualOpenShiftAPILevels.List()) } if !expectedKubeAPILevels.Equal(actualKubeAPILevels) { t.Errorf("actual Kube API levels served don't match expected levels:\n\texpected:\n\t%s\n\tgot:\n\t%s", expectedKubeAPILevels.List(), actualKubeAPILevels.List()) } // Send a GET to every advertised address and check that we get the correct response for _, route := range broadcastRootPaths.Paths { req, err := http.NewRequest("GET", masterConfig.AssetConfig.MasterPublicURL+route, nil) req.Header.Set("Accept", "*/*") resp, err := transport.RoundTrip(req) if err != nil { t.Errorf("unexpected error issuing GET for path %q: %v", route, err) continue } // Look up expected code if exceptional or default to 200 expectedCode, exists := ExceptionalExpectedCodes[route] if !exists { expectedCode = http.StatusOK } if resp.StatusCode != expectedCode { t.Errorf("incorrect status code for %s endpoint: expected %d, got %d", route, expectedCode, resp.StatusCode) } } }