func doServiceAccountAPIRequests(t *testing.T, c *clientset.Clientset, 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.Core().Secrets(ns).List(api.ListOptions{}) return err }, func() error { _, err := c.Core().Pods(ns).List(api.ListOptions{}) return err }, } writeOps := []testOperation{ func() error { _, err := c.Core().Secrets(ns).Create(testSecret); return err }, func() error { return c.Core().Secrets(ns).Delete(testSecret.Name, nil) }, } 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) } } }
func verifyImageStreamAccess(ctx context.Context, namespace, imageRepo, verb string, client client.LocalSubjectAccessReviewsNamespacer) error { sar := authorizationapi.LocalSubjectAccessReview{ Action: authorizationapi.Action{ Verb: verb, Group: imageapi.GroupName, Resource: "imagestreams/layers", ResourceName: imageRepo, }, } response, err := client.LocalSubjectAccessReviews(namespace).Create(&sar) if err != nil { context.GetLogger(ctx).Errorf("OpenShift client error: %s", err) if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) { return ErrOpenShiftAccessDenied } return err } if !response.Allowed { context.GetLogger(ctx).Errorf("OpenShift access denied: %s", response.Reason) return ErrOpenShiftAccessDenied } return nil }
func validateServiceAccount(client *kclient.Client, ns string, serviceAccount string, hostNetwork bool) error { if !hostNetwork { return nil } // get cluster sccs sccList, err := client.SecurityContextConstraints().List(kapi.ListOptions{}) if err != nil { if !errors.IsUnauthorized(err) { return fmt.Errorf("could not retrieve list of security constraints to verify service account %q: %v", serviceAccount, err) } return nil } // get set of sccs applicable to the service account userInfo := serviceaccount.UserInfo(ns, serviceAccount, "") for _, scc := range sccList.Items { if admission.ConstraintAppliesTo(&scc, userInfo) { switch { case hostNetwork && scc.AllowHostNetwork: return nil } } } return fmt.Errorf("service account %q is not allowed to access the host network on nodes, needs access via a security context constraint", serviceAccount) }
func verifyImageStreamAccess(namespace, imageRepo, verb string, client *client.Client) error { sar := authorizationapi.LocalSubjectAccessReview{ Action: authorizationapi.AuthorizationAttributes{ Verb: verb, Resource: "imagestreams/layers", ResourceName: imageRepo, }, } response, err := client.LocalSubjectAccessReviews(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 (c *CacheAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) { if value, hit := c.cache.Get(token); hit { switch record := value.(type) { case *cacheRecord: if record.created.Add(c.ttl).After(c.now()) { glog.V(5).Infof("cache record found: %#v", record) return record.user, record.ok, record.err } else { glog.V(5).Infof("cache record expired: %#v", record) c.cache.Remove(token) } default: utilruntime.HandleError(fmt.Errorf("invalid cache record type: %#v", record)) } } u, ok, err := c.authenticator.AuthenticateToken(token) // Don't cache results if there was an error unrelated to authentication // TODO: figure out a better way to determine this if err == nil || kerrs.IsUnauthorized(err) { c.cache.Add(token, &cacheRecord{created: c.now(), user: u, ok: ok, err: err}) } return u, ok, err }
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 verifyOpenShiftUser(ctx context.Context, client client.UsersInterface) error { if _, err := client.Users().Get("~"); err != nil { context.GetLogger(ctx).Errorf("Get user failed with error: %s", err) if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) { return ErrOpenShiftAccessDenied } return err } return nil }
// NewCmdLogin implements the OpenShift cli login command func NewCmdLogin(fullName string, f *osclientcmd.Factory, reader io.Reader, out io.Writer) *cobra.Command { options := &LoginOptions{ Reader: reader, Out: out, } cmds := &cobra.Command{ Use: "login [URL]", Short: "Log in to a server", Long: loginLong, Example: fmt.Sprintf(loginExample, fullName), Run: func(cmd *cobra.Command, args []string) { if err := options.Complete(f, cmd, args, fullName); err != nil { kcmdutil.CheckErr(err) } if err := options.Validate(args, kcmdutil.GetFlagString(cmd, "server")); err != nil { kcmdutil.CheckErr(err) } err := RunLogin(cmd, options) if kapierrors.IsUnauthorized(err) { fmt.Fprintln(out, "Login failed (401 Unauthorized)") if err, isStatusErr := err.(*kapierrors.StatusError); isStatusErr { if details := err.Status().Details; details != nil { for _, cause := range details.Causes { fmt.Fprintln(out, cause.Message) } } } os.Exit(1) } else { kcmdutil.CheckErr(err) } }, } // Login is the only command that can negotiate a session token against the auth server using basic auth cmds.Flags().StringVarP(&options.Username, "username", "u", "", "Username, will prompt if not provided") cmds.Flags().StringVarP(&options.Password, "password", "p", "", "Password, will prompt if not provided") return cmds }
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 validateServiceAccount(client *kclient.Client, ns string, serviceAccount string) error { sccList, err := client.SecurityContextConstraints().List(kapi.ListOptions{}) if err != nil { if !errors.IsUnauthorized(err) { return fmt.Errorf("could not retrieve list of security constraints to verify service account %q: %v", serviceAccount, err) } } for _, scc := range sccList.Items { if scc.AllowPrivilegedContainer { for _, user := range scc.Users { if strings.Contains(user, serviceAccount) { return nil } } } } errMsg := "service account %q does not have sufficient privileges, grant access with oadm policy add-scc-to-user %s -z %s" return fmt.Errorf(errMsg, serviceAccount, bootstrappolicy.SecurityContextConstraintPrivileged, serviceAccount) }
func verifyPruneAccess(ctx context.Context, client *client.Client) error { sar := authorizationapi.SubjectAccessReview{ Action: authorizationapi.AuthorizationAttributes{ Verb: "delete", Resource: "images", }, } response, err := client.SubjectAccessReviews().Create(&sar) if err != nil { context.GetLogger(ctx).Errorf("OpenShift client error: %s", err) if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) { return ErrOpenShiftAccessDenied } return err } if !response.Allowed { context.GetLogger(ctx).Errorf("OpenShift access denied: %s", response.Reason) return ErrOpenShiftAccessDenied } return nil }
func validateServiceAccount(client *kclient.Client, ns string, serviceAccount string, hostNetwork, hostPorts bool) error { if !hostNetwork && !hostPorts { return nil } // get cluster sccs sccList, err := client.SecurityContextConstraints().List(kapi.ListOptions{}) if err != nil { if !errors.IsUnauthorized(err) { return fmt.Errorf("could not retrieve list of security constraints to verify service account %q: %v", serviceAccount, err) } return nil } // get set of sccs applicable to the service account userInfo := serviceaccount.UserInfo(ns, serviceAccount, "") for _, scc := range sccList.Items { if oscc.ConstraintAppliesTo(&scc, userInfo) { switch { case hostPorts && scc.AllowHostPorts: return nil case hostNetwork && scc.AllowHostNetwork: return nil } } } if hostNetwork { errMsg := "service account %q is not allowed to access the host network on nodes, grant access with oadm policy add-scc-to-user %s -z %s" return fmt.Errorf(errMsg, serviceAccount, bootstrappolicy.SecurityContextConstraintsHostNetwork, serviceAccount) } if hostPorts { errMsg := "service account %q is not allowed to access host ports on nodes, grant access with oadm policy add-scc-to-user %s -z %s" return fmt.Errorf(errMsg, serviceAccount, bootstrappolicy.SecurityContextConstraintsHostNetwork, serviceAccount) } return nil }
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{}, pathPrefix: "%"}, 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{ content: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()}, client: clientFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: http.StatusForbidden, Body: ioutil.NopCloser(bytes.NewReader([]byte{})), }, nil }), baseURL: &url.URL{}, }, Err: true, ErrFn: func(err error) bool { return apierrors.IsForbidden(err) }, }, { Request: &Request{ content: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()}, client: clientFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: http.StatusUnauthorized, Body: ioutil.NopCloser(bytes.NewReader([]byte{})), }, nil }), baseURL: &url.URL{}, }, Err: true, ErrFn: func(err error) bool { return apierrors.IsUnauthorized(err) }, }, { Request: &Request{ content: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.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.Default.Codec(), &unversioned.Status{ Status: unversioned.StatusFailure, Reason: unversioned.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 { t.Logf("testcase %v", testCase.Request) testCase.Request.backoffMgr = &NoBackoff{} 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) } } } }
func TestTransformResponse(t *testing.T) { invalid := []byte("aaaaa") uri, _ := url.Parse("http://localhost") testCases := []struct { Response *http.Response Data []byte Created bool Error bool ErrFn func(err error) bool }{ {Response: &http.Response{StatusCode: 200}, Data: []byte{}}, {Response: &http.Response{StatusCode: 201}, Data: []byte{}, Created: true}, {Response: &http.Response{StatusCode: 199}, Error: true}, {Response: &http.Response{StatusCode: 500}, Error: true}, {Response: &http.Response{StatusCode: 422}, Error: true}, {Response: &http.Response{StatusCode: 409}, Error: true}, {Response: &http.Response{StatusCode: 404}, Error: true}, {Response: &http.Response{StatusCode: 401}, Error: true}, { Response: &http.Response{ StatusCode: 401, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) }, }, { Response: &http.Response{ StatusCode: 401, Header: http.Header{"Content-Type": []string{"text/any"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Error: true, ErrFn: func(err error) bool { return strings.Contains(err.Error(), "server has asked for the client to provide") && apierrors.IsUnauthorized(err) }, }, {Response: &http.Response{StatusCode: 403}, Error: true}, {Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid}, {Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid}, } for i, test := range testCases { r := NewRequest(nil, "", uri, "", ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()}, nil, nil) if test.Response.Body == nil { test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) } result := r.transformResponse(test.Response, &http.Request{}) response, created, err := result.body, result.statusCode == http.StatusCreated, result.err hasErr := err != nil if hasErr != test.Error { t.Errorf("%d: unexpected error: %t %v", i, test.Error, err) } else if hasErr && test.Response.StatusCode > 399 { status, ok := err.(apierrors.APIStatus) if !ok { t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err) continue } if int(status.Status().Code) != test.Response.StatusCode { t.Errorf("%d: status code did not match response: %#v", i, status.Status()) } } if test.ErrFn != nil && !test.ErrFn(err) { t.Errorf("%d: error function did not match: %v", i, err) } if !(test.Data == nil && response == nil) && !api.Semantic.DeepDerivative(test.Data, response) { t.Errorf("%d: unexpected response: %#v %#v", i, test.Data, response) } if test.Created != created { t.Errorf("%d: expected created %t, got %t", i, test.Created, created) } } }
// Negotiate a bearer token with the auth server, or try to reuse one based on the // information already present. In case of any missing information, ask for user input // (usually username and password, interactive depending on the Reader). func (o *LoginOptions) gatherAuthInfo() error { directClientConfig, err := o.getClientConfig() if err != nil { return err } // make a copy and use it to avoid mutating the original t := *directClientConfig clientConfig := &t // if a token were explicitly provided, try to use it if o.tokenProvided() { clientConfig.BearerToken = o.Token if osClient, err := client.New(clientConfig); err == nil { me, err := whoAmI(osClient) if err == nil { o.Username = me.Name o.Config = clientConfig fmt.Fprintf(o.Out, "Logged into %q as %q using the token provided.\n\n", o.Config.Host, o.Username) return nil } if !kerrors.IsUnauthorized(err) { return err } fmt.Fprint(o.Out, "The token provided is invalid (probably expired).\n\n") } } // if a username was provided try to make use of it if o.usernameProvided() { // search all valid contexts with matching server stanzas to see if we have a matching user stanza kubeconfig := *o.StartingKubeConfig matchingClusters := getMatchingClusters(*clientConfig, kubeconfig) for key, context := range o.StartingKubeConfig.Contexts { if matchingClusters.Has(context.Cluster) { clientcmdConfig := kclientcmd.NewDefaultClientConfig(kubeconfig, &kclientcmd.ConfigOverrides{CurrentContext: key}) if kubeconfigClientConfig, err := clientcmdConfig.ClientConfig(); err == nil { if osClient, err := client.New(kubeconfigClientConfig); err == nil { if me, err := whoAmI(osClient); err == nil && (o.Username == me.Name) { clientConfig.BearerToken = kubeconfigClientConfig.BearerToken clientConfig.CertFile = kubeconfigClientConfig.CertFile clientConfig.CertData = kubeconfigClientConfig.CertData clientConfig.KeyFile = kubeconfigClientConfig.KeyFile clientConfig.KeyData = kubeconfigClientConfig.KeyData o.Config = clientConfig if key == o.StartingKubeConfig.CurrentContext { fmt.Fprintf(o.Out, "Logged into %q as %q using existing credentials.\n\n", o.Config.Host, o.Username) } return nil } } } } } } // if kubeconfig doesn't already have a matching user stanza... clientConfig.BearerToken = "" clientConfig.CertData = []byte{} clientConfig.KeyData = []byte{} clientConfig.CertFile = o.CertFile clientConfig.KeyFile = o.KeyFile token, err := tokencmd.RequestToken(o.Config, o.Reader, o.Username, o.Password) if err != nil { return err } clientConfig.BearerToken = token osClient, err := client.New(clientConfig) if err != nil { return err } me, err := whoAmI(osClient) if err != nil { return err } o.Username = me.Name o.Config = clientConfig fmt.Fprint(o.Out, "Login successful.\n\n") return nil }
It("should not accept cluster resources when the client has invalid authentication credentials", func() { framework.SkipUnlessFederated(f.Client) contexts := f.GetUnderlyingFederatedContexts() // `contexts` is obtained by calling // `f.GetUnderlyingFederatedContexts()`. This function in turn // checks that the contexts it returns does not include the // federation API server context. So `contexts` is guaranteed to // contain only the underlying Kubernetes cluster contexts. fcs, err := invalidAuthFederationClientSet(contexts[0].User) framework.ExpectNoError(err) nsName := f.FederationNamespace.Name svc, err := createService(fcs, nsName, FederatedServiceName) Expect(errors.IsUnauthorized(err)).To(BeTrue()) if err == nil && svc != nil { deleteServiceOrFail(fcs, nsName, svc.Name) } }) It("should not accept cluster resources when the client has no authentication credentials", func() { framework.SkipUnlessFederated(f.Client) fcs, err := invalidAuthFederationClientSet(nil) ExpectNoError(err) nsName := f.FederationNamespace.Name svc, err := createService(fcs, nsName, FederatedServiceName) Expect(errors.IsUnauthorized(err)).To(BeTrue()) if err == nil && svc != nil {
func TestTransformResponseNegotiate(t *testing.T) { invalid := []byte("aaaaa") uri, _ := url.Parse("http://localhost") testCases := []struct { Response *http.Response Data []byte Created bool Error bool ErrFn func(err error) bool ContentType string Called bool ExpectContentType string Decoder runtime.Decoder NegotiateErr error }{ { ContentType: "application/json", Response: &http.Response{ StatusCode: 401, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) }, }, { ContentType: "application/json", Response: &http.Response{ StatusCode: 401, Header: http.Header{"Content-Type": []string{"application/protobuf"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), Called: true, ExpectContentType: "application/protobuf", Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) }, }, { ContentType: "application/json", Response: &http.Response{ StatusCode: 500, Header: http.Header{"Content-Type": []string{"application/,others"}}, }, Decoder: testapi.Default.Codec(), Error: true, ErrFn: func(err error) bool { return err.Error() == "Internal error occurred: mime: expected token after slash" && err.(apierrors.APIStatus).Status().Code == 500 }, }, { // no negotiation when no content type specified Response: &http.Response{ StatusCode: 200, Header: http.Header{"Content-Type": []string{"text/any"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), }, { // no negotiation when no response content type specified ContentType: "text/any", Response: &http.Response{ StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), }, { // unrecognized content type is not handled ContentType: "application/json", Response: &http.Response{ StatusCode: 404, Header: http.Header{"Content-Type": []string{"application/unrecognized"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), NegotiateErr: fmt.Errorf("aaaa"), Called: true, ExpectContentType: "application/unrecognized", Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsNotFound(err) }, }, } for i, test := range testCases { serializers := defaultSerializers() negotiator := &renegotiator{ decoder: test.Decoder, err: test.NegotiateErr, } serializers.RenegotiatedDecoder = negotiator.invoke contentConfig := defaultContentConfig() contentConfig.ContentType = test.ContentType r := NewRequest(nil, "", uri, "", contentConfig, serializers, nil, nil) if test.Response.Body == nil { test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) } result := r.transformResponse(test.Response, &http.Request{}) _, err := result.body, result.err hasErr := err != nil if hasErr != test.Error { t.Errorf("%d: unexpected error: %t %v", i, test.Error, err) continue } else if hasErr && test.Response.StatusCode > 399 { status, ok := err.(apierrors.APIStatus) if !ok { t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err) continue } if int(status.Status().Code) != test.Response.StatusCode { t.Errorf("%d: status code did not match response: %#v", i, status.Status()) } } if test.ErrFn != nil && !test.ErrFn(err) { t.Errorf("%d: error function did not match: %v", i, err) } if negotiator.called != test.Called { t.Errorf("%d: negotiator called %t != %t", i, negotiator.called, test.Called) } if !test.Called { continue } if negotiator.contentType != test.ExpectContentType { t.Errorf("%d: unexpected content type: %s", i, negotiator.contentType) } } }
// RunVersion attempts to display client and server versions for Kubernetes and OpenShift func (o VersionOptions) RunVersion() error { fmt.Fprintf(o.Out, "%s %v\n", o.BaseName, version.Get()) fmt.Fprintf(o.Out, "kubernetes %v\n", kubeversion.Get()) if o.PrintEtcdVersion { fmt.Fprintf(o.Out, "etcd %v\n", etcdversion.Version) } if o.PrintClientFeatures { features := []string{} if tokencmd.BasicEnabled() { features = append(features, "Basic-Auth") } if tokencmd.GSSAPIEnabled() { features = append(features, "GSSAPI") features = append(features, "Kerberos") // GSSAPI or SSPI features = append(features, "SPNEGO") // GSSAPI or SSPI } fmt.Printf("features: %s\n", strings.Join(features, " ")) } // do not attempt to print server info if already running cmd as the server // or if no client config is present if o.ClientConfig == nil || o.IsServer { return nil } // max amount of time we want to wait for server to respond timeout := 10 * time.Second done := make(chan error) oVersion := "" kVersion := "" versionHost := "" // start goroutine to fetch openshift / kubernetes server version go func() { defer close(done) // confirm config exists before makig request to server var err error clientConfig, err := o.ClientConfig.ClientConfig() if err != nil { done <- err return } versionHost = clientConfig.Host oClient, kClient, err := o.Clients() if err != nil { done <- err return } ocVersionBody, err := oClient.Get().AbsPath("/version/openshift").Do().Raw() if kapierrors.IsNotFound(err) || kapierrors.IsUnauthorized(err) || kapierrors.IsForbidden(err) { return } if err != nil { done <- err return } var ocServerInfo version.Info err = json.Unmarshal(ocVersionBody, &ocServerInfo) if err != nil && len(ocVersionBody) > 0 { done <- err return } oVersion = fmt.Sprintf("%v", ocServerInfo) kubeVersionBody, err := kClient.Get().AbsPath("/version").Do().Raw() if kapierrors.IsNotFound(err) || kapierrors.IsUnauthorized(err) || kapierrors.IsForbidden(err) { return } if err != nil { done <- err return } var kubeServerInfo kubeversion.Info err = json.Unmarshal(kubeVersionBody, &kubeServerInfo) if err != nil && len(kubeVersionBody) > 0 { done <- err return } kVersion = fmt.Sprintf("%v", kubeServerInfo) }() select { case err, closed := <-done: if strings.HasSuffix(fmt.Sprintf("%v", err), "connection refused") || clientcmd.IsConfigurationMissing(err) || kclientcmd.IsConfigurationInvalid(err) { return nil } if closed && err != nil { return err } case <-time.After(timeout): return fmt.Errorf("%s", "error: server took too long to respond with version information.") } if oVersion != "" || kVersion != "" { fmt.Fprintf(o.Out, "\n%s%s\n", "Server ", versionHost) } if oVersion != "" { fmt.Fprintf(o.Out, "openshift %s\n", oVersion) } if kVersion != "" { fmt.Fprintf(o.Out, "kubernetes %s\n", kVersion) } return nil }