package diagnostics import ( "fmt" "regexp" "strings" kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" clientcmd "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" "k8s.io/kubernetes/pkg/util/sets" authorizationapi "github.com/openshift/origin/pkg/authorization/api" "github.com/openshift/origin/pkg/client" osclientcmd "github.com/openshift/origin/pkg/cmd/util/clientcmd" clustdiags "github.com/openshift/origin/pkg/diagnostics/cluster" agldiags "github.com/openshift/origin/pkg/diagnostics/cluster/aggregated_logging" "github.com/openshift/origin/pkg/diagnostics/types" ) var ( // availableClusterDiagnostics contains the names of cluster diagnostics that can be executed // during a single run of diagnostics. Add more diagnostics to the list as they are defined. availableClusterDiagnostics = sets.NewString( agldiags.AggregatedLoggingName, clustdiags.ClusterRegistryName, clustdiags.ClusterRouterName, clustdiags.ClusterRolesName, clustdiags.ClusterRoleBindingsName, clustdiags.MasterNodeName, clustdiags.MetricsApiProxyName, clustdiags.NodeDefinitionsName, clustdiags.ServiceExternalIPsName, ) ) // buildClusterDiagnostics builds cluster Diagnostic objects if a cluster-admin client can be extracted from the rawConfig passed in. // Returns the Diagnostics built, "ok" bool for whether to proceed or abort, and an error if any was encountered during the building of diagnostics.) { func (o DiagnosticsOptions) buildClusterDiagnostics(rawConfig *clientcmdapi.Config) ([]types.Diagnostic, bool, error) { requestedDiagnostics := availableClusterDiagnostics.Intersection(sets.NewString(o.RequestedDiagnostics...)).List() if len(requestedDiagnostics) == 0 { // no diagnostics to run here return nil, true, nil // don't waste time on discovery } var ( clusterClient *client.Client kclusterClient *kclientset.Clientset ) clusterClient, kclusterClient, found, serverUrl, err := o.findClusterClients(rawConfig) if !found { o.Logger.Notice("CED1002", "Could not configure a client with cluster-admin permissions for the current server, so cluster diagnostics will be skipped") return nil, true, err } diagnostics := []types.Diagnostic{} for _, diagnosticName := range requestedDiagnostics { var d types.Diagnostic switch diagnosticName { case agldiags.AggregatedLoggingName: d = agldiags.NewAggregatedLogging(o.MasterConfigLocation, kclusterClient, clusterClient) case clustdiags.NodeDefinitionsName: d = &clustdiags.NodeDefinitions{KubeClient: kclusterClient, OsClient: clusterClient} case clustdiags.MasterNodeName: d = &clustdiags.MasterNode{KubeClient: kclusterClient, OsClient: clusterClient, ServerUrl: serverUrl, MasterConfigFile: o.MasterConfigLocation} case clustdiags.ClusterRegistryName: d = &clustdiags.ClusterRegistry{KubeClient: kclusterClient, OsClient: clusterClient, PreventModification: o.PreventModification} case clustdiags.ClusterRouterName: d = &clustdiags.ClusterRouter{KubeClient: kclusterClient, OsClient: clusterClient} case clustdiags.ClusterRolesName: d = &clustdiags.ClusterRoles{ClusterRolesClient: clusterClient, SARClient: clusterClient} case clustdiags.ClusterRoleBindingsName: d = &clustdiags.ClusterRoleBindings{ClusterRoleBindingsClient: clusterClient, SARClient: clusterClient} case clustdiags.MetricsApiProxyName: d = &clustdiags.MetricsApiProxy{KubeClient: kclusterClient} case clustdiags.ServiceExternalIPsName: d = &clustdiags.ServiceExternalIPs{MasterConfigFile: o.MasterConfigLocation, KclusterClient: kclusterClient} default: return nil, false, fmt.Errorf("unknown diagnostic: %v", diagnosticName) } diagnostics = append(diagnostics, d) } return diagnostics, true, nil } // attempts to find which context in the config might be a cluster-admin for the server in the current context. func (o DiagnosticsOptions) findClusterClients(rawConfig *clientcmdapi.Config) (*client.Client, *kclientset.Clientset, bool, string, error) { if o.ClientClusterContext != "" { // user has specified cluster context to use if context, exists := rawConfig.Contexts[o.ClientClusterContext]; exists { configErr := fmt.Errorf("Specified '%s' as cluster-admin context, but it was not found in your client configuration.", o.ClientClusterContext) o.Logger.Error("CED1003", configErr.Error()) return nil, nil, false, "", configErr } else if os, kube, found, serverUrl, err := o.makeClusterClients(rawConfig, o.ClientClusterContext, context); found { return os, kube, true, serverUrl, err } else { return nil, nil, false, "", err } } currentContext, exists := rawConfig.Contexts[rawConfig.CurrentContext] if !exists { // config specified cluster admin context that doesn't exist; complain and quit configErr := fmt.Errorf("Current context '%s' not found in client configuration; will not attempt cluster diagnostics.", rawConfig.CurrentContext) o.Logger.Error("CED1004", configErr.Error()) return nil, nil, false, "", configErr } // check if current context is already cluster admin if os, kube, found, serverUrl, err := o.makeClusterClients(rawConfig, rawConfig.CurrentContext, currentContext); found { return os, kube, true, serverUrl, err } // otherwise, for convenience, search for a context with the same server but with the system:admin user for name, context := range rawConfig.Contexts { if context.Cluster == currentContext.Cluster && name != rawConfig.CurrentContext && strings.HasPrefix(context.AuthInfo, "system:admin/") { if os, kube, found, serverUrl, err := o.makeClusterClients(rawConfig, name, context); found { return os, kube, true, serverUrl, err } else { return nil, nil, false, "", err // don't try more than one such context, they'll probably fail the same } } } return nil, nil, false, "", nil } // makes the client from the specified context and determines whether it is a cluster-admin. func (o DiagnosticsOptions) makeClusterClients(rawConfig *clientcmdapi.Config, contextName string, context *clientcmdapi.Context) (*client.Client, *kclientset.Clientset, bool, string, error) { overrides := &clientcmd.ConfigOverrides{Context: *context} clientConfig := clientcmd.NewDefaultClientConfig(*rawConfig, overrides) serverUrl := rawConfig.Clusters[context.Cluster].Server factory := osclientcmd.NewFactory(clientConfig) o.Logger.Debug("CED1005", fmt.Sprintf("Checking if context is cluster-admin: '%s'", contextName)) if osClient, _, kubeClient, err := factory.Clients(); err != nil { o.Logger.Debug("CED1006", fmt.Sprintf("Error creating client for context '%s':\n%v", contextName, err)) return nil, nil, false, "", nil } else { subjectAccessReview := authorizationapi.SubjectAccessReview{Action: authorizationapi.Action{ // if you can do everything, you're the cluster admin. Verb: "*", Group: "*", Resource: "*", }} if resp, err := osClient.SubjectAccessReviews().Create(&subjectAccessReview); err != nil { if regexp.MustCompile(`User "[\w:]+" cannot create \w+ at the cluster scope`).MatchString(err.Error()) { o.Logger.Debug("CED1007", fmt.Sprintf("Context '%s' does not have cluster-admin access:\n%v", contextName, err)) return nil, nil, false, "", nil } else { o.Logger.Error("CED1008", fmt.Sprintf("Unknown error testing cluster-admin access for context '%s':\n%v", contextName, err)) return nil, nil, false, "", err } } else if resp.Allowed { o.Logger.Info("CED1009", fmt.Sprintf("Using context for cluster-admin access: '%s'", contextName)) return osClient, kubeClient, true, serverUrl, nil } } o.Logger.Debug("CED1010", fmt.Sprintf("Context does not have cluster-admin access: '%s'", contextName)) return nil, nil, false, "", nil }