func TestAuthorizationRestrictedAccessForProjectAdmins(t *testing.T) { _, clusterAdminKubeConfig, err := testutil.StartTestMaster() if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } haroldClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold") if err != nil { t.Fatalf("unexpected error: %v", err) } markClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "mallet-project", "mark") if err != nil { t.Fatalf("unexpected error: %v", err) } _, err = haroldClient.DeploymentConfigs("hammer-project").List(labels.Everything(), fields.Everything()) if err != nil { t.Fatalf("unexpected error: %v", err) } _, err = markClient.DeploymentConfigs("hammer-project").List(labels.Everything(), fields.Everything()) if (err == nil) || !kapierror.IsForbidden(err) { t.Fatalf("unexpected error: %v", err) } // projects are a special case where a get of a project actually sets a namespace. Make sure that // the namespace is properly special cased and set for authorization rules _, err = haroldClient.Projects().Get("hammer-project") if err != nil { t.Fatalf("unexpected error: %v", err) } _, err = markClient.Projects().Get("hammer-project") if (err == nil) || !kapierror.IsForbidden(err) { t.Fatalf("unexpected error: %v", err) } // wait for the project authorization cache to catch the change. It is on a one second period waitForProject(t, haroldClient, "hammer-project", 1*time.Second, 10) waitForProject(t, markClient, "mallet-project", 1*time.Second, 10) }
func doServiceAccountAPIRequests(t *testing.T, c *client.Client, ns string, authenticated bool, canRead bool, canWrite bool) { testSecret := &api.Secret{ ObjectMeta: api.ObjectMeta{Name: "testSecret"}, Data: map[string][]byte{"test": []byte("data")}, } readOps := []testOperation{ func() error { _, err := c.Secrets(ns).List(labels.Everything(), fields.Everything()); return err }, func() error { _, err := c.Pods(ns).List(labels.Everything(), fields.Everything()); return err }, } writeOps := []testOperation{ func() error { _, err := c.Secrets(ns).Create(testSecret); return err }, func() error { return c.Secrets(ns).Delete(testSecret.Name) }, } for _, op := range readOps { err := op() unauthorizedError := errors.IsUnauthorized(err) forbiddenError := errors.IsForbidden(err) switch { case !authenticated && !unauthorizedError: t.Fatalf("expected unauthorized error, got %v", err) case authenticated && unauthorizedError: t.Fatalf("unexpected unauthorized error: %v", err) case authenticated && canRead && forbiddenError: t.Fatalf("unexpected forbidden error: %v", err) case authenticated && !canRead && !forbiddenError: t.Fatalf("expected forbidden error, got: %v", err) } } for _, op := range writeOps { err := op() unauthorizedError := errors.IsUnauthorized(err) forbiddenError := errors.IsForbidden(err) switch { case !authenticated && !unauthorizedError: t.Fatalf("expected unauthorized error, got %v", err) case authenticated && unauthorizedError: t.Fatalf("unexpected unauthorized error: %v", err) case authenticated && canWrite && forbiddenError: t.Fatalf("unexpected forbidden error: %v", err) case authenticated && !canWrite && !forbiddenError: t.Fatalf("expected forbidden error, got: %v", err) } } }
// Resolve searches for a template and returns a match with the object representation func (r TemplateResolver) Resolve(value string) (*ComponentMatch, error) { checked := util.NewStringSet() for _, namespace := range r.Namespaces { if checked.Has(namespace) { continue } checked.Insert(namespace) glog.V(4).Infof("checking template %s/%s", namespace, value) repo, err := r.Client.Templates(namespace).Get(value) if err != nil { if errors.IsNotFound(err) || errors.IsForbidden(err) { continue } return nil, err } return &ComponentMatch{ Value: value, Argument: fmt.Sprintf("--template=%q", value), Name: value, Description: fmt.Sprintf("Template %s in project %s", repo.Name, repo.Namespace), Score: 0, Template: repo, }, nil } return nil, ErrNoMatch{value: value} }
// Resolve will attempt to find an imagestream with a name that matches the passed in value func (r ImageStreamResolver) Resolve(value string) (*ComponentMatch, error) { ref, err := imageapi.ParseDockerImageReference(value) if err != nil || len(ref.Registry) != 0 { return nil, fmt.Errorf("image repositories must be of the form [<namespace>/]<name>[:<tag>|@<digest>]") } namespaces := r.Namespaces if len(ref.Namespace) != 0 { namespaces = []string{ref.Namespace} } searchTag := ref.Tag if len(searchTag) == 0 { searchTag = imageapi.DefaultImageTag } for _, namespace := range namespaces { glog.V(4).Infof("checking ImageStream %s/%s with ref %q", namespace, ref.Name, searchTag) repo, err := r.Client.ImageStreams(namespace).Get(ref.Name) if err != nil { if errors.IsNotFound(err) || errors.IsForbidden(err) { continue } return nil, err } ref.Namespace = namespace latest := imageapi.LatestTaggedImage(repo, searchTag) if latest == nil { // continue searching in the next namespace glog.V(2).Infof("no image recorded for %s/%s:%s", repo.Namespace, repo.Name, searchTag) continue } imageStreamImage, err := r.ImageStreamImages.ImageStreamImages(namespace).Get(ref.Name, latest.Image) if err != nil { if errors.IsNotFound(err) { // continue searching in the next namespace glog.V(2).Infof("tag %q is set, but image %q has been removed", searchTag, latest.Image) continue } return nil, err } imageData := imageStreamImage.Image ref.Registry = "" return &ComponentMatch{ Value: ref.String(), Argument: fmt.Sprintf("--image=%q", ref.String()), Name: ref.Name, Description: fmt.Sprintf("Image repository %s (tag %q) in project %s, tracks %q", repo.Name, searchTag, repo.Namespace, repo.Status.DockerImageRepository), Builder: IsBuilderImage(&imageData.DockerImageMetadata), Score: 0, ImageStream: repo, Image: &imageData.DockerImageMetadata, ImageTag: searchTag, }, nil } return nil, ErrNoMatch{value: value} }
func (d *ProjectStatusDescriber) MakeGraph(namespace string) (osgraph.Graph, util.StringSet, error) { g := osgraph.New() loaders := []GraphLoader{ &serviceLoader{namespace: namespace, lister: d.K}, &serviceAccountLoader{namespace: namespace, lister: d.K}, &secretLoader{namespace: namespace, lister: d.K}, &rcLoader{namespace: namespace, lister: d.K}, &podLoader{namespace: namespace, lister: d.K}, &bcLoader{namespace: namespace, lister: d.C}, &buildLoader{namespace: namespace, lister: d.C}, &isLoader{namespace: namespace, lister: d.C}, &dcLoader{namespace: namespace, lister: d.C}, } loadingFuncs := []func() error{} for _, loader := range loaders { loadingFuncs = append(loadingFuncs, loader.Load) } forbiddenResources := util.StringSet{} if errs := parallel.Run(loadingFuncs...); len(errs) > 0 { actualErrors := []error{} for _, err := range errs { if kapierrors.IsForbidden(err) { forbiddenErr := err.(*kapierrors.StatusError) if (forbiddenErr.Status().Details != nil) && (len(forbiddenErr.Status().Details.Kind) > 0) { forbiddenResources.Insert(forbiddenErr.Status().Details.Kind) } continue } actualErrors = append(actualErrors, err) } if len(actualErrors) > 0 { return g, forbiddenResources, utilerrors.NewAggregate(actualErrors) } } for _, loader := range loaders { loader.AddToGraph(g) } kubeedges.AddAllExposedPodTemplateSpecEdges(g) kubeedges.AddAllExposedPodEdges(g) kubeedges.AddAllManagedByRCPodEdges(g) kubeedges.AddAllRequestedServiceAccountEdges(g) kubeedges.AddAllMountableSecretEdges(g) kubeedges.AddAllMountedSecretEdges(g) buildedges.AddAllInputOutputEdges(g) buildedges.AddAllBuildEdges(g) deployedges.AddAllTriggerEdges(g) deployedges.AddAllDeploymentEdges(g) imageedges.AddAllImageStreamRefEdges(g) return g, forbiddenResources, nil }
func verifyOpenShiftUser(client *client.Client) error { if _, err := client.Users().Get("~"); err != nil { log.Errorf("Get user failed with error: %s", err) if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) { return ErrOpenShiftAccessDenied } return err } return nil }
func TestBootstrapPolicyOverwritePolicyCommand(t *testing.T) { masterConfig, clusterAdminKubeConfig, err := testutil.StartTestMaster() if err != nil { t.Fatalf("unexpected error: %v", err) } client, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Errorf("unexpected error: %v", err) } if err := client.ClusterPolicies().Delete(authorizationapi.PolicyName); err != nil { t.Errorf("unexpected error: %v", err) } // after the policy is deleted, we must wait for it to be cleared from the policy cache err = wait.Poll(10*time.Millisecond, 10*time.Second, func() (bool, error) { _, err := client.ClusterPolicies().List(labels.Everything(), fields.Everything()) if err == nil { return false, nil } if !kapierror.IsForbidden(err) { t.Errorf("unexpected error: %v", err) } return true, nil }) if err != nil { t.Errorf("timeout: %v", err) } etcdClient, err := etcd.GetAndTestEtcdClient(masterConfig.EtcdClientInfo) if err != nil { t.Errorf("unexpected error: %v", err) } etcdHelper, err := origin.NewEtcdStorage(etcdClient, masterConfig.EtcdStorageConfig.OpenShiftStorageVersion, masterConfig.EtcdStorageConfig.OpenShiftStoragePrefix) if err != nil { t.Errorf("unexpected error: %v", err) } if err := admin.OverwriteBootstrapPolicy(etcdHelper, masterConfig.PolicyConfig.BootstrapPolicyFile, admin.CreateBootstrapPolicyFileFullCommand, true, ioutil.Discard); err != nil { t.Errorf("unexpected error: %v", err) } if _, err := client.ClusterPolicies().List(labels.Everything(), fields.Everything()); err != nil { t.Errorf("unexpected error: %v", err) } }
func TestErrors(t *testing.T) { o := testclient.NewObjects(kapi.Scheme, kapi.Scheme) o.Add(&kapi.List{ Items: []runtime.Object{ &(errors.NewNotFound("DeploymentConfigList", "").(*errors.StatusError).ErrStatus), &(errors.NewForbidden("DeploymentConfigList", "", nil).(*errors.StatusError).ErrStatus), }, }) oc, _ := NewFixtureClients(o) _, err := oc.DeploymentConfigs("test").List(labels.Everything(), fields.Everything()) if !errors.IsNotFound(err) { t.Fatalf("unexpected error: %v", err) } t.Logf("error: %#v", err.(*errors.StatusError).Status()) _, err = oc.DeploymentConfigs("test").List(labels.Everything(), fields.Everything()) if !errors.IsForbidden(err) { t.Fatalf("unexpected error: %v", err) } }
func verifyPruneAccess(client *client.Client) error { sar := authorizationapi.SubjectAccessReview{ Verb: "delete", Resource: "images", } response, err := client.ClusterSubjectAccessReviews().Create(&sar) if err != nil { log.Errorf("OpenShift client error: %s", err) if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) { return ErrOpenShiftAccessDenied } return err } if !response.Allowed { log.Errorf("OpenShift access denied: %s", response.Reason) return ErrOpenShiftAccessDenied } return nil }
func verifyImageStreamAccess(namespace, imageRepo, verb string, client *client.Client) error { sar := authorizationapi.SubjectAccessReview{ Verb: verb, Resource: "imagestreams/layers", ResourceName: imageRepo, } response, err := client.SubjectAccessReviews(namespace).Create(&sar) if err != nil { log.Errorf("OpenShift client error: %s", err) if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) { return ErrOpenShiftAccessDenied } return err } if !response.Allowed { log.Errorf("OpenShift access denied: %s", response.Reason) return ErrOpenShiftAccessDenied } return nil }
func TestErrors(t *testing.T) { o := NewObjects(api.Scheme, api.Scheme) o.Add(&api.List{ Items: []runtime.Object{ // This first call to List will return this error &(errors.NewNotFound("ServiceList", "").(*errors.StatusError).ErrStatus), // The second call to List will return this error &(errors.NewForbidden("ServiceList", "", nil).(*errors.StatusError).ErrStatus), }, }) client := &Fake{ReactFn: ObjectReaction(o, latest.RESTMapper)} _, err := client.Services("test").List(labels.Everything()) if !errors.IsNotFound(err) { t.Fatalf("unexpected error: %v", err) } t.Logf("error: %#v", err.(*errors.StatusError).Status()) _, err = client.Services("test").List(labels.Everything()) if !errors.IsForbidden(err) { t.Fatalf("unexpected error: %v", err) } }
// Search searches for a template and returns matches with the object representation func (r TemplateSearcher) Search(terms ...string) (ComponentMatches, error) { matches := ComponentMatches{} for _, term := range terms { checkedNamespaces := util.NewStringSet() for _, namespace := range r.Namespaces { if checkedNamespaces.Has(namespace) { continue } checkedNamespaces.Insert(namespace) glog.V(4).Infof("checking template %s/%s", namespace, term) templates, err := r.Client.Templates(namespace).List(labels.Everything(), fields.Everything()) if err != nil { if errors.IsNotFound(err) || errors.IsForbidden(err) { continue } return nil, err } for i := range templates.Items { template := &templates.Items[i] if score, scored := templateScorer(*template, term); scored { matches = append(matches, &ComponentMatch{ Value: term, Argument: fmt.Sprintf("--template=%q", template.Name), Name: template.Name, Description: fmt.Sprintf("Template %q in project %q", template.Name, template.Namespace), Score: score, Template: template, }) } } } } return matches, nil }
// NewForbidden is a utility function to return a well-formatted admission control error response func NewForbidden(a Attributes, internalError error) error { // do not double wrap an error of same type if apierrors.IsForbidden(internalError) { return internalError } name := "Unknown" kind := a.GetKind() obj := a.GetObject() if obj != nil { objectMeta, err := api.ObjectMetaFor(obj) if err != nil { return apierrors.NewForbidden(kind, name, internalError) } // this is necessary because name object name generation has not occurred yet if len(objectMeta.Name) > 0 { name = objectMeta.Name } else if len(objectMeta.GenerateName) > 0 { name = objectMeta.GenerateName } } return apierrors.NewForbidden(kind, name, internalError) }
// Search will attempt to find imagestreams with names that matches the passed in value func (r ImageStreamSearcher) Search(terms ...string) (ComponentMatches, error) { componentMatches := ComponentMatches{} for _, term := range terms { ref, err := imageapi.ParseDockerImageReference(term) if err != nil || len(ref.Registry) != 0 { return nil, fmt.Errorf("image streams must be of the form [<namespace>/]<name>[:<tag>|@<digest>]") } namespaces := r.Namespaces if len(ref.Namespace) != 0 { namespaces = []string{ref.Namespace} } searchTag := ref.Tag if len(searchTag) == 0 { searchTag = imageapi.DefaultImageTag } for _, namespace := range namespaces { glog.V(4).Infof("checking ImageStreams %s/%s with ref %q", namespace, ref.Name, searchTag) streams, err := r.Client.ImageStreams(namespace).List(labels.Everything(), fields.Everything()) if err != nil { if errors.IsNotFound(err) || errors.IsForbidden(err) { continue } return nil, err } ref.Namespace = namespace for i := 0; i < len(streams.Items); i++ { stream := streams.Items[i] score, scored := imageStreamScorer(stream, ref.Name) if scored { imageref, _ := imageapi.ParseDockerImageReference(term) imageref.Name = stream.Name latest := imageapi.LatestTaggedImage(&stream, searchTag) if latest == nil { glog.V(2).Infof("no image recorded for %s/%s:%s", stream.Namespace, stream.Name, searchTag) componentMatches = append(componentMatches, &ComponentMatch{ Value: imageref.String(), Argument: fmt.Sprintf("--image-stream=%q", imageref.String()), Name: imageref.Name, Description: fmt.Sprintf("Image stream %s in project %s, tracks %q", stream.Name, stream.Namespace, stream.Status.DockerImageRepository), Score: 0.5 + score, ImageStream: &stream, ImageTag: searchTag, }) continue } imageStreamImage, err := r.ImageStreamImages.ImageStreamImages(namespace).Get(stream.Name, latest.Image) if err != nil { if errors.IsNotFound(err) { // continue searching glog.V(2).Infof("tag %q is set, but image %q has been removed", searchTag, latest.Image) continue } return nil, err } imageData := imageStreamImage.Image imageref.Registry = "" componentMatches = append(componentMatches, &ComponentMatch{ Value: imageref.String(), Argument: fmt.Sprintf("--image-stream=%q", imageref.String()), Name: imageref.Name, Description: fmt.Sprintf("Image stream %q (tag %q) in project %q, tracks %q", stream.Name, searchTag, stream.Namespace, stream.Status.DockerImageRepository), Builder: IsBuilderImage(&imageData.DockerImageMetadata), Score: score, ImageStream: &stream, Image: &imageData.DockerImageMetadata, ImageTag: searchTag, }) } } } } return componentMatches, nil }
func TestBuildAdmission(t *testing.T) { tests := []struct { name string kind string resource string object runtime.Object reviewResponse *authorizationapi.SubjectAccessReviewResponse expectedResource string expectAccept bool }{ { name: "allowed source build", object: testBuild(buildapi.SourceBuildStrategyType), kind: "Build", resource: buildsResource, reviewResponse: reviewResponse(true, ""), expectedResource: authorizationapi.SourceBuildResource, expectAccept: true, }, { name: "denied docker build", object: testBuild(buildapi.DockerBuildStrategyType), kind: "Build", resource: buildsResource, reviewResponse: reviewResponse(false, "cannot create build of type docker build"), expectAccept: false, expectedResource: authorizationapi.DockerBuildResource, }, { name: "allowed custom build", object: testBuild(buildapi.CustomBuildStrategyType), kind: "Build", resource: buildsResource, reviewResponse: reviewResponse(true, ""), expectedResource: authorizationapi.CustomBuildResource, expectAccept: true, }, { name: "allowed build config", object: testBuildConfig(buildapi.DockerBuildStrategyType), kind: "BuildConfig", resource: buildConfigsResource, reviewResponse: reviewResponse(true, ""), expectAccept: true, expectedResource: authorizationapi.DockerBuildResource, }, { name: "forbidden build config", object: testBuildConfig(buildapi.CustomBuildStrategyType), kind: "BuildConfig", resource: buildConfigsResource, reviewResponse: reviewResponse(false, ""), expectAccept: false, expectedResource: authorizationapi.CustomBuildResource, }, } for _, test := range tests { c := NewBuildByStrategy(fakeClient(test.expectedResource, test.reviewResponse)) attrs := admission.NewAttributesRecord(test.object, test.kind, "default", "name", test.resource, "" /*subresource*/, admission.Create, fakeUser()) err := c.Admit(attrs) if err != nil && test.expectAccept { t.Errorf("%s: unexpected error: %v", test.name, err) } if (err == nil || !apierrors.IsForbidden(err)) && !test.expectAccept { t.Errorf("%s: expecting reject error", test.name) } } }
func RunPortForward(f *cmdutil.Factory, cmd *cobra.Command, args []string, fw portForwarder) error { podName := cmdutil.GetFlagString(cmd, "pod") if len(podName) == 0 { return cmdutil.UsageError(cmd, "POD is required for exec") } if len(args) < 1 { return cmdutil.UsageError(cmd, "at least 1 PORT is required for port-forward") } namespace, _, err := f.DefaultNamespace() if err != nil { return err } client, err := f.Client() if err != nil { return err } pod, err := client.Pods(namespace).Get(podName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { glog.Fatalf("Unable to execute command because pod is not running. Current status=%v", pod.Status.Phase) } config, err := f.ClientConfig() if err != nil { return err } signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt) defer signal.Stop(signals) stopCh := make(chan struct{}, 1) go func() { <-signals close(stopCh) }() req := client.RESTClient.Post(). Resource("pods"). Namespace(namespace). Name(pod.Name). SubResource("portforward") postErr := fw.ForwardPorts(req, config, args, stopCh) // if we don't have an error, return. If we did get an error, try a GET because v3.0.0 shipped with port-forward running as a GET. if postErr == nil { return nil } // only try the get if the error is either a forbidden or method not supported, otherwise trying with a GET probably won't help if !apierrors.IsForbidden(postErr) && !apierrors.IsMethodNotSupported(postErr) { return postErr } getReq := client.RESTClient.Get(). Resource("pods"). Namespace(namespace). Name(pod.Name). SubResource("portforward") getErr := fw.ForwardPorts(getReq, config, args, stopCh) if getErr == nil { return nil } // if we got a getErr, return the postErr because it's more likely to be correct. GET is legacy return postErr }
func TestBootstrapPolicyAuthenticatedUsersAgainstOpenshiftNamespace(t *testing.T) { _, clusterAdminKubeConfig, err := testutil.StartTestMaster() if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Errorf("unexpected error: %v", err) } valerieClientConfig := *clusterAdminClientConfig valerieClientConfig.Username = "" valerieClientConfig.Password = "" valerieClientConfig.BearerToken = "" valerieClientConfig.CertFile = "" valerieClientConfig.KeyFile = "" valerieClientConfig.CertData = nil valerieClientConfig.KeyData = nil accessToken, err := tokencmd.RequestToken(&valerieClientConfig, nil, "valerie", "security!") if err != nil { t.Fatalf("unexpected error: %v", err) } valerieClientConfig.BearerToken = accessToken valerieOpenshiftClient, err := client.New(&valerieClientConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } openshiftSharedResourcesNamespace := "openshift" if _, err := valerieOpenshiftClient.Templates(openshiftSharedResourcesNamespace).List(labels.Everything(), fields.Everything()); err != nil { t.Errorf("unexpected error: %v", err) } if _, err := valerieOpenshiftClient.Templates(kapi.NamespaceDefault).List(labels.Everything(), fields.Everything()); err == nil || !kapierror.IsForbidden(err) { t.Errorf("unexpected error: %v", err) } if _, err := valerieOpenshiftClient.ImageStreams(openshiftSharedResourcesNamespace).List(labels.Everything(), fields.Everything()); err != nil { t.Errorf("unexpected error: %v", err) } if _, err := valerieOpenshiftClient.ImageStreams(kapi.NamespaceDefault).List(labels.Everything(), fields.Everything()); err == nil || !kapierror.IsForbidden(err) { t.Errorf("unexpected error: %v", err) } if _, err := valerieOpenshiftClient.ImageStreamTags(openshiftSharedResourcesNamespace).Get("name", "tag"); !kapierror.IsNotFound(err) { t.Errorf("unexpected error: %v", err) } if _, err := valerieOpenshiftClient.ImageStreamTags(kapi.NamespaceDefault).Get("name", "tag"); err == nil || !kapierror.IsForbidden(err) { t.Errorf("unexpected error: %v", err) } }
func TestDNS(t *testing.T) { masterConfig, clientFile, err := testutil.StartTestMaster() if err != nil { t.Fatalf("unexpected error: %v", err) } localIP := net.ParseIP("127.0.0.1") var masterIP net.IP // verify service DNS entry is visible stop := make(chan struct{}) util.Until(func() { m1 := &dns.Msg{ MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: false}, Question: []dns.Question{{"kubernetes.default.svc.cluster.local.", dns.TypeA, dns.ClassINET}}, } in, err := dns.Exchange(m1, masterConfig.DNSConfig.BindAddress) if err != nil { t.Logf("unexpected error: %v", err) return } if len(in.Answer) != 1 { t.Logf("unexpected answer: %#v", in) return } if a, ok := in.Answer[0].(*dns.A); ok { if a.A == nil { t.Fatalf("expected an A record with an IP: %#v", a) } masterIP = a.A } else { t.Fatalf("expected an A record: %#v", in) } t.Log(in) close(stop) }, 50*time.Millisecond, stop) client, err := testutil.GetClusterAdminKubeClient(clientFile) if err != nil { t.Fatalf("unexpected error: %v", err) } for { if _, err := client.Services(kapi.NamespaceDefault).Create(&kapi.Service{ ObjectMeta: kapi.ObjectMeta{ Name: "headless", }, Spec: kapi.ServiceSpec{ PortalIP: kapi.PortalIPNone, Ports: []kapi.ServicePort{{Port: 443}}, }, }); err != nil { if errors.IsForbidden(err) { t.Logf("forbidden, sleeping: %v", err) time.Sleep(100 * time.Millisecond) continue } t.Fatalf("unexpected error: %v", err) } if _, err := client.Endpoints(kapi.NamespaceDefault).Create(&kapi.Endpoints{ ObjectMeta: kapi.ObjectMeta{ Name: "headless", }, Subsets: []kapi.EndpointSubset{{ Addresses: []kapi.EndpointAddress{{IP: "172.0.0.1"}}, Ports: []kapi.EndpointPort{ {Port: 2345}, }, }}, }); err != nil { t.Fatalf("unexpected error: %v", err) } break } headlessIP := net.ParseIP("172.0.0.1") if _, err := client.Services(kapi.NamespaceDefault).Create(&kapi.Service{ ObjectMeta: kapi.ObjectMeta{ Name: "headless2", }, Spec: kapi.ServiceSpec{ PortalIP: kapi.PortalIPNone, Ports: []kapi.ServicePort{{Port: 443}}, }, }); err != nil { t.Fatalf("unexpected error: %v", err) } if _, err := client.Endpoints(kapi.NamespaceDefault).Create(&kapi.Endpoints{ ObjectMeta: kapi.ObjectMeta{ Name: "headless2", }, Subsets: []kapi.EndpointSubset{{ Addresses: []kapi.EndpointAddress{{IP: "172.0.0.2"}}, Ports: []kapi.EndpointPort{ {Port: 2345, Name: "other"}, {Port: 2346, Name: "http"}, }, }}, }); err != nil { t.Fatalf("unexpected error: %v", err) } headless2IP := net.ParseIP("172.0.0.2") tests := []struct { dnsQuestionName string recursionExpected bool retry bool expect []*net.IP srv []*dns.SRV }{ { // wildcard resolution of a service works dnsQuestionName: "foo.kubernetes.default.svc.cluster.local.", expect: []*net.IP{&masterIP}, }, { // resolving endpoints of a service works dnsQuestionName: "_endpoints.kubernetes.default.svc.cluster.local.", expect: []*net.IP{&localIP}, }, { // openshift override works dnsQuestionName: "openshift.default.svc.cluster.local.", expect: []*net.IP{&masterIP}, }, { // headless service dnsQuestionName: "headless.default.svc.cluster.local.", expect: []*net.IP{&headlessIP}, }, { // specific port of a headless service dnsQuestionName: "unknown-port-2345.e1.headless.default.svc.cluster.local.", expect: []*net.IP{&headlessIP}, }, { // SRV record for that service dnsQuestionName: "headless.default.svc.cluster.local.", srv: []*dns.SRV{ { Target: "unknown-port-2345.e1.headless.", Port: 2345, }, }, }, { // the SRV record resolves to the IP dnsQuestionName: "unknown-port-2345.e1.headless.default.svc.cluster.local.", expect: []*net.IP{&headlessIP}, }, { // headless 2 service dnsQuestionName: "headless2.default.svc.cluster.local.", expect: []*net.IP{&headless2IP}, }, { // SRV records for that service dnsQuestionName: "headless2.default.svc.cluster.local.", srv: []*dns.SRV{ { Target: "http.e1.headless2.", Port: 2346, }, { Target: "other.e1.headless2.", Port: 2345, }, }, }, { // the SRV record resolves to the IP dnsQuestionName: "other.e1.headless2.default.svc.cluster.local.", expect: []*net.IP{&headless2IP}, }, { dnsQuestionName: "www.google.com.", recursionExpected: false, }, } for i, tc := range tests { qType := dns.TypeA if tc.srv != nil { qType = dns.TypeSRV } m1 := &dns.Msg{ MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: false}, Question: []dns.Question{{tc.dnsQuestionName, qType, dns.ClassINET}}, } ch := make(chan struct{}) count := 0 util.Until(func() { count++ if count > 100 { t.Errorf("%d: failed after max iterations", i) close(ch) return } in, err := dns.Exchange(m1, masterConfig.DNSConfig.BindAddress) if err != nil { return } switch { case tc.srv != nil: if len(in.Answer) != len(tc.srv) { t.Logf("%d: incorrect number of answers: %#v", i, in) return } case tc.recursionExpected: if len(in.Answer) == 0 { t.Errorf("%d: expected forward resolution: %#v", i, in) } close(ch) return default: if len(in.Answer) != len(tc.expect) { t.Logf("%d: did not resolve or unexpected forward resolution: %#v", i, in) return } } for _, answer := range in.Answer { switch a := answer.(type) { case *dns.A: matches := false if a.A != nil { for _, expect := range tc.expect { if a.A.String() == expect.String() { matches = true break } } } if !matches { t.Errorf("A record does not match any expected answer: %v", a.A) } case *dns.SRV: matches := false for _, expect := range tc.srv { if expect.Port == a.Port && expect.Target == a.Target { matches = true break } } if !matches { t.Errorf("SRV record does not match any expected answer: %#v", a) } default: t.Errorf("expected an A or SRV record: %#v", in) } } t.Log(in) close(ch) }, 50*time.Millisecond, ch) } }
func TestPolicyBasedRestrictionOfBuildStrategies(t *testing.T) { const namespace = "hammer" _, clusterAdminKubeConfig, err := testutil.StartTestMaster() if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } haroldClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, namespace, "harold") if err != nil { t.Fatalf("unexpected error: %v", err) } joeClient, err := testutil.GetClientForUser(*clusterAdminClientConfig, "joe") if err != nil { t.Fatalf("unexpected error: %v", err) } addJoe := &policy.RoleModificationOptions{ RoleNamespace: "", RoleName: bootstrappolicy.EditRoleName, RoleBindingAccessor: policy.NewLocalRoleBindingAccessor(namespace, haroldClient), Users: []string{"joe"}, } if err := addJoe.AddRole(); err != nil { t.Errorf("unexpected error: %v", err) } if err := testutil.WaitForPolicyUpdate(joeClient, namespace, "create", authorizationapi.DockerBuildResource, true); err != nil { t.Error(err) } // by default admins and editors can create all type of builds _, err = createDockerBuild(t, haroldClient.Builds(namespace)) if err != nil { t.Errorf("unexpected error: %v", err) } _, err = createDockerBuild(t, joeClient.Builds(namespace)) if err != nil { t.Errorf("unexpected error: %v", err) } _, err = createSourceBuild(t, haroldClient.Builds(namespace)) if err != nil { t.Errorf("unexpected error: %v", err) } _, err = createSourceBuild(t, joeClient.Builds(namespace)) if err != nil { t.Errorf("unexpected error: %v", err) } _, err = createCustomBuild(t, haroldClient.Builds(namespace)) if err != nil { t.Errorf("unexpected error: %v", err) } _, err = createCustomBuild(t, joeClient.Builds(namespace)) if err != nil { t.Errorf("unexpected error: %v", err) } // remove resources from role so that certain build strategies are forbidden removeBuildStrategyPrivileges(t, clusterAdminClient.ClusterRoles(), bootstrappolicy.EditRoleName) if err := testutil.WaitForPolicyUpdate(joeClient, namespace, "create", authorizationapi.DockerBuildResource, false); err != nil { t.Error(err) } removeBuildStrategyPrivileges(t, clusterAdminClient.ClusterRoles(), bootstrappolicy.AdminRoleName) if err := testutil.WaitForPolicyUpdate(haroldClient, namespace, "create", authorizationapi.DockerBuildResource, false); err != nil { t.Error(err) } // make sure builds are rejected if _, err = createDockerBuild(t, haroldClient.Builds(namespace)); !kapierror.IsForbidden(err) { t.Errorf("expected forbidden, got %v", err) } if _, err = createDockerBuild(t, joeClient.Builds(namespace)); !kapierror.IsForbidden(err) { t.Errorf("expected forbidden, got %v", err) } if _, err = createSourceBuild(t, haroldClient.Builds(namespace)); !kapierror.IsForbidden(err) { t.Errorf("expected forbidden, got %v", err) } if _, err = createSourceBuild(t, joeClient.Builds(namespace)); !kapierror.IsForbidden(err) { t.Errorf("expected forbidden, got %v", err) } if _, err = createCustomBuild(t, haroldClient.Builds(namespace)); !kapierror.IsForbidden(err) { t.Errorf("expected forbidden, got %v", err) } if _, err = createCustomBuild(t, joeClient.Builds(namespace)); !kapierror.IsForbidden(err) { t.Errorf("expected forbidden, got %v", err) } }
func RunExec(f *cmdutil.Factory, cmd *cobra.Command, cmdIn io.Reader, cmdOut, cmdErr io.Writer, p *execParams, argsIn []string, re remoteExecutor) error { podName, containerName, args, err := extractPodAndContainer(cmd, argsIn, p) namespace, _, err := f.DefaultNamespace() if err != nil { return err } client, err := f.Client() if err != nil { return err } pod, err := client.Pods(namespace).Get(podName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { glog.Fatalf("Unable to execute command because pod %s is not running. Current status=%v", podName, pod.Status.Phase) } if len(containerName) == 0 { glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name) containerName = pod.Spec.Containers[0].Name } var stdin io.Reader tty := p.tty if p.stdin { stdin = cmdIn if tty { if file, ok := cmdIn.(*os.File); ok { inFd := file.Fd() if term.IsTerminal(inFd) { oldState, err := term.SetRawTerminal(inFd) if err != nil { glog.Fatal(err) } // this handles a clean exit, where the command finished defer term.RestoreTerminal(inFd, oldState) // SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens // for SIGINT and restores the terminal before exiting) // this handles SIGTERM sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) go func() { <-sigChan term.RestoreTerminal(inFd, oldState) os.Exit(0) }() } else { glog.Warning("Stdin is not a terminal") } } else { tty = false glog.Warning("Unable to use a TTY") } } } config, err := f.ClientConfig() if err != nil { return err } req := client.RESTClient.Post(). Resource("pods"). Name(pod.Name). Namespace(namespace). SubResource("exec"). Param("container", containerName) postErr := re.Execute(req, config, args, stdin, cmdOut, cmdErr, tty) // if we don't have an error, return. If we did get an error, try a GET because v3.0.0 shipped with exec running as a GET. if postErr == nil { return nil } // only try the get if the error is either a forbidden or method not supported, otherwise trying with a GET probably won't help if !apierrors.IsForbidden(postErr) && !apierrors.IsMethodNotSupported(postErr) { return postErr } getReq := client.RESTClient.Get(). Resource("pods"). Name(pod.Name). Namespace(namespace). SubResource("exec"). Param("container", containerName) getErr := re.Execute(getReq, config, args, stdin, cmdOut, cmdErr, tty) if getErr == nil { return nil } // if we got a getErr, return the postErr because it's more likely to be correct. GET is legacy return postErr }
// Run executes a validated remote execution against a pod. func (p *ExecOptions) Run() error { pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { return fmt.Errorf("pod %s is not running and cannot execute commands; current phase is %s", p.PodName, pod.Status.Phase) } containerName := p.ContainerName if len(containerName) == 0 { glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name) containerName = pod.Spec.Containers[0].Name } // TODO: refactor with terminal helpers from the edit utility once that is merged var stdin io.Reader tty := p.TTY if p.Stdin { stdin = p.In if tty { if file, ok := stdin.(*os.File); ok { inFd := file.Fd() if term.IsTerminal(inFd) { oldState, err := term.SetRawTerminal(inFd) if err != nil { glog.Fatal(err) } // this handles a clean exit, where the command finished defer term.RestoreTerminal(inFd, oldState) // SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens // for SIGINT and restores the terminal before exiting) // this handles SIGTERM sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) go func() { <-sigChan term.RestoreTerminal(inFd, oldState) os.Exit(0) }() } else { fmt.Fprintln(p.Err, "STDIN is not a terminal") } } else { tty = false fmt.Fprintln(p.Err, "Unable to use a TTY - input is not the right kind of file") } } } // TODO: consider abstracting into a client invocation or client helper req := p.Client.RESTClient.Post(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("exec"). Param("container", containerName) postErr := p.Executor.Execute(req, p.Config, p.Command, stdin, p.Out, p.Err, tty) // if we don't have an error, return. If we did get an error, try a GET because v3.0.0 shipped with exec running as a GET. if postErr == nil { return nil } // only try the get if the error is either a forbidden or method not supported, otherwise trying with a GET probably won't help if !apierrors.IsForbidden(postErr) && !apierrors.IsMethodNotSupported(postErr) { return postErr } getReq := p.Client.RESTClient.Get(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("exec"). Param("container", containerName) getErr := p.Executor.Execute(getReq, p.Config, p.Command, stdin, p.Out, p.Err, tty) if getErr == nil { return nil } // if we got a getErr, return the postErr because it's more likely to be correct. GET is legacy return postErr }
func TestServiceAccountAuthorization(t *testing.T) { saNamespace := api.NamespaceDefault saName := serviceaccountadmission.DefaultServiceAccountName saUsername := serviceaccount.MakeUsername(saNamespace, saName) // Start one OpenShift master as "cluster1" to play the external kube server cluster1MasterConfig, cluster1AdminConfigFile, err := testutil.StartTestMaster() if err != nil { t.Fatalf("unexpected error: %v", err) } cluster1AdminConfig, err := testutil.GetClusterAdminClientConfig(cluster1AdminConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } cluster1AdminKubeClient, err := testutil.GetClusterAdminKubeClient(cluster1AdminConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } cluster1AdminOSClient, err := testutil.GetClusterAdminClient(cluster1AdminConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } // Get a service account token and build a client saToken, err := waitForServiceAccountToken(cluster1AdminKubeClient, saNamespace, saName, 20, time.Second) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(saToken) == 0 { t.Fatalf("token was not created") } cluster1SAClientConfig := kclient.Config{ Host: cluster1AdminConfig.Host, Prefix: cluster1AdminConfig.Prefix, BearerToken: saToken, TLSClientConfig: kclient.TLSClientConfig{ CAFile: cluster1AdminConfig.CAFile, CAData: cluster1AdminConfig.CAData, }, } cluster1SAKubeClient, err := kclient.New(&cluster1SAClientConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Make sure the service account doesn't have access failNS := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-fail"}} if _, err := cluster1SAKubeClient.Namespaces().Create(failNS); !errors.IsForbidden(err) { t.Fatalf("expected forbidden error, got %v", err) } // Make the service account a cluster admin on cluster1 addRoleOptions := &policy.RoleModificationOptions{ RoleName: bootstrappolicy.ClusterAdminRoleName, RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(cluster1AdminOSClient), Users: []string{saUsername}, } if err := addRoleOptions.AddRole(); err != nil { t.Fatalf("could not add role to service account") } // Give the policy cache a second to catch it's breath time.Sleep(time.Second) // Make sure the service account now has access // This tests authentication using the etcd-based token getter passNS := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-pass"}} if _, err := cluster1SAKubeClient.Namespaces().Create(passNS); err != nil { t.Fatalf("unexpected error: %v", err) } // Create a kubeconfig from the serviceaccount config cluster1SAKubeConfigFile, err := ioutil.TempFile(testutil.GetBaseDir(), "cluster1-service-account.kubeconfig") if err != nil { t.Fatalf("error creating tmpfile: %v", err) } defer os.Remove(cluster1SAKubeConfigFile.Name()) if err := writeClientConfigToKubeConfig(cluster1SAClientConfig, cluster1SAKubeConfigFile.Name()); err != nil { t.Fatalf("error creating kubeconfig: %v", err) } // Set up cluster 2 to run against cluster 1 as external kubernetes cluster2MasterConfig, err := testutil.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } // Don't start kubernetes in process cluster2MasterConfig.KubernetesMasterConfig = nil // Connect to cluster1 using the service account credentials cluster2MasterConfig.MasterClients.ExternalKubernetesKubeConfig = cluster1SAKubeConfigFile.Name() // Don't start etcd cluster2MasterConfig.EtcdConfig = nil // Use the same credentials as cluster1 to connect to existing etcd cluster2MasterConfig.EtcdClientInfo = cluster1MasterConfig.EtcdClientInfo // Set a custom etcd prefix to make sure data is getting sent to cluster1 cluster2MasterConfig.EtcdStorageConfig.KubernetesStoragePrefix += "2" cluster2MasterConfig.EtcdStorageConfig.OpenShiftStoragePrefix += "2" // Don't manage any names in cluster2 cluster2MasterConfig.ServiceAccountConfig.ManagedNames = []string{} // Don't create any service account tokens in cluster2 cluster2MasterConfig.ServiceAccountConfig.PrivateKeyFile = "" // Use the same public keys to validate tokens as cluster1 cluster2MasterConfig.ServiceAccountConfig.PublicKeyFiles = cluster1MasterConfig.ServiceAccountConfig.PublicKeyFiles // Start cluster 2 (without clearing etcd) and get admin client configs and clients cluster2Options := testutil.TestOptions{DeleteAllEtcdKeys: false} cluster2AdminConfigFile, err := testutil.StartConfiguredMasterWithOptions(cluster2MasterConfig, cluster2Options) if err != nil { t.Fatalf("unexpected error: %v", err) } cluster2AdminConfig, err := testutil.GetClusterAdminClientConfig(cluster2AdminConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } cluster2AdminOSClient, err := testutil.GetClusterAdminClient(cluster2AdminConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } // Build a client to use the same service account token against cluster2 cluster2SAClientConfig := cluster1SAClientConfig cluster2SAClientConfig.Host = cluster2AdminConfig.Host cluster2SAKubeClient, err := kclient.New(&cluster2SAClientConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Make sure the service account doesn't have access // A forbidden error makes sure the token was recognized, and policy denied us // This exercises the client-based token getter // It also makes sure we don't loop back through the cluster2 kube proxy which would cause an auth loop failNS2 := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-fail2"}} if _, err := cluster2SAKubeClient.Namespaces().Create(failNS2); !errors.IsForbidden(err) { t.Fatalf("expected forbidden error, got %v", err) } // Make the service account a cluster admin on cluster2 addRoleOptions2 := &policy.RoleModificationOptions{ RoleName: bootstrappolicy.ClusterAdminRoleName, RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(cluster2AdminOSClient), Users: []string{saUsername}, } if err := addRoleOptions2.AddRole(); err != nil { t.Fatalf("could not add role to service account") } // Give the policy cache a second to catch it's breath time.Sleep(time.Second) // Make sure the service account now has access to cluster2 passNS2 := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-pass2"}} if _, err := cluster2SAKubeClient.Namespaces().Create(passNS2); err != nil { t.Fatalf("unexpected error: %v", err) } // Make sure the ns actually got created in cluster1 if _, err := cluster1SAKubeClient.Namespaces().Get(passNS2.Name); err != nil { t.Fatalf("unexpected error: %v", err) } }
func TestRequestWatch(t *testing.T) { testCases := []struct { Request *Request Err bool ErrFn func(error) bool Empty bool }{ { Request: &Request{err: errors.New("bail")}, Err: true, }, { Request: &Request{baseURL: &url.URL{}, path: "%"}, Err: true, }, { Request: &Request{ client: clientFunc(func(req *http.Request) (*http.Response, error) { return nil, errors.New("err") }), baseURL: &url.URL{}, }, Err: true, }, { Request: &Request{ codec: testapi.Codec(), client: clientFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{StatusCode: http.StatusForbidden}, nil }), baseURL: &url.URL{}, }, Err: true, ErrFn: func(err error) bool { return apierrors.IsForbidden(err) }, }, { Request: &Request{ codec: testapi.Codec(), client: clientFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{StatusCode: http.StatusUnauthorized}, nil }), baseURL: &url.URL{}, }, Err: true, ErrFn: func(err error) bool { return apierrors.IsUnauthorized(err) }, }, { Request: &Request{ codec: testapi.Codec(), client: clientFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: http.StatusUnauthorized, Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Codec(), &api.Status{ Status: api.StatusFailure, Reason: api.StatusReasonUnauthorized, })))), }, nil }), baseURL: &url.URL{}, }, Err: true, ErrFn: func(err error) bool { return apierrors.IsUnauthorized(err) }, }, { Request: &Request{ client: clientFunc(func(req *http.Request) (*http.Response, error) { return nil, io.EOF }), baseURL: &url.URL{}, }, Empty: true, }, { Request: &Request{ client: clientFunc(func(req *http.Request) (*http.Response, error) { return nil, &url.Error{Err: io.EOF} }), baseURL: &url.URL{}, }, Empty: true, }, { Request: &Request{ client: clientFunc(func(req *http.Request) (*http.Response, error) { return nil, errors.New("http: can't write HTTP request on broken connection") }), baseURL: &url.URL{}, }, Empty: true, }, { Request: &Request{ client: clientFunc(func(req *http.Request) (*http.Response, error) { return nil, errors.New("foo: connection reset by peer") }), baseURL: &url.URL{}, }, Empty: true, }, } for i, testCase := range testCases { watch, err := testCase.Request.Watch() hasErr := err != nil if hasErr != testCase.Err { t.Errorf("%d: expected %t, got %t: %v", i, testCase.Err, hasErr, err) continue } if testCase.ErrFn != nil && !testCase.ErrFn(err) { t.Errorf("%d: error not valid: %v", i, err) } if hasErr && watch != nil { t.Errorf("%d: watch should be nil when error is returned", i) continue } if testCase.Empty { _, ok := <-watch.ResultChan() if ok { t.Errorf("%d: expected the watch to be empty: %#v", i, watch) } } } }
// IsForbidden checks whether the provided error is a 'forbidden' error or not func IsForbidden(err error) bool { return kerrors.IsForbidden(err) }