github.com/IBM-Cloud/bluemix-go@v0.0.0-20240423071914-9e96525baef4/api/container/containerv1/openshift.go (about)

     1  package containerv1
     2  
     3  /*******************************************************************************
     4   * IBM Confidential
     5   * OCO Source Materials
     6   * IBM Cloud Schematics
     7   * (C) Copyright IBM Corp. 2017 All Rights Reserved.
     8   * The source code for this program is not  published or otherwise divested of
     9   * its trade secrets, irrespective of what has been deposited with
    10   * the U.S. Copyright Office.
    11   ******************************************************************************/
    12  
    13  /*******************************************************************************
    14   * A file for openshift related utility functions, like getting kube
    15   * config
    16   ******************************************************************************/
    17  
    18  import (
    19  	"encoding/base64"
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"net/url"
    25  	"regexp"
    26  	"runtime/debug"
    27  	"strings"
    28  	"time"
    29  
    30  	yaml "github.com/ghodss/yaml"
    31  
    32  	bxhttp "github.com/IBM-Cloud/bluemix-go/http"
    33  	"github.com/IBM-Cloud/bluemix-go/rest"
    34  	"github.com/IBM-Cloud/bluemix-go/trace"
    35  )
    36  
    37  const (
    38  	// IAMHTTPtimeout -
    39  	IAMHTTPtimeout = 10 * time.Second
    40  )
    41  
    42  // Frame -
    43  type Frame uintptr
    44  
    45  // StackTrace -
    46  type StackTrace []Frame
    47  type stackTracer interface {
    48  	StackTrace() StackTrace
    49  }
    50  
    51  type openShiftUser struct {
    52  	Kind       string `json:"kind"`
    53  	APIVersion string `json:"apiVersion"`
    54  	Metadata   struct {
    55  		Name              string    `json:"name"`
    56  		SelfLink          string    `json:"selfLink"`
    57  		UID               string    `json:"uid"`
    58  		ResourceVersion   string    `json:"resourceVersion"`
    59  		CreationTimestamp time.Time `json:"creationTimestamp"`
    60  	} `json:"metadata"`
    61  	Identities []string `json:"identities"`
    62  	Groups     []string `json:"groups"`
    63  }
    64  
    65  type authEndpoints struct {
    66  	Issuer                string `json:"issuer"`
    67  	AuthorizationEndpoint string `json:"authorization_endpoint"`
    68  	TokenEndpoint         string `json:"token_endpoint"`
    69  	ServerURL             string `json:"server_endpoint,omitempty"`
    70  }
    71  
    72  // PanicCatch - Catch panic and give error
    73  func PanicCatch(r interface{}) error {
    74  	if r != nil {
    75  		var e error
    76  		switch x := r.(type) {
    77  		case string:
    78  			e = errors.New(x)
    79  		case error:
    80  			e = x
    81  		default:
    82  			e = errors.New("Unknown panic")
    83  		}
    84  		fmt.Printf("Panic error %v", e)
    85  		if err, ok := e.(stackTracer); ok {
    86  			fmt.Printf("Panic stack trace %v", err.StackTrace())
    87  		} else {
    88  			debug.PrintStack()
    89  		}
    90  		return e
    91  	}
    92  	return nil
    93  }
    94  
    95  // NormalizeName -
    96  func NormalizeName(name string) (string, error) {
    97  	name = strings.ToLower(name)
    98  	reg, err := regexp.Compile("[^A-Za-z0-9:]+")
    99  	if err != nil {
   100  		return "", err
   101  	}
   102  	return reg.ReplaceAllString(name, "-"), nil
   103  }
   104  
   105  // logInAndFillOCToken will update kubeConfig with an Openshift token, if one is not there
   106  func (r *clusters) FetchOCTokenForKubeConfig(kubecfg []byte, cMeta *ClusterInfo, skipSSLVerification bool) (kubecfgEdited []byte, rerr error) {
   107  	// TODO: this is not a a standard manner to login ... using propriatary OC cli reverse engineering
   108  	defer func() {
   109  		err := PanicCatch(recover())
   110  		if err != nil {
   111  			rerr = fmt.Errorf("Could not login to openshift account %s", err)
   112  		}
   113  	}()
   114  
   115  	var cfg map[string]interface{}
   116  	err := yaml.Unmarshal(kubecfg, &cfg)
   117  	if err != nil {
   118  		return kubecfg, err
   119  	}
   120  
   121  	var token string
   122  	trace.Logger.Println("Creating user passcode to login for getting oc token")
   123  	passcode, err := r.client.TokenRefresher.GetPasscode()
   124  
   125  	authEP, err := func(meta *ClusterInfo) (*authEndpoints, error) {
   126  		request := rest.GetRequest(meta.ServerURL + "/.well-known/oauth-authorization-server")
   127  		var auth authEndpoints
   128  		tempVar := r.client.ServiceName
   129  		r.client.ServiceName = ""
   130  
   131  		tempSSL := r.client.Config.SSLDisable
   132  		tempClient := r.client.Config.HTTPClient
   133  		r.client.Config.SSLDisable = skipSSLVerification
   134  		r.client.Config.HTTPClient = bxhttp.NewHTTPClient(r.client.Config)
   135  
   136  		defer func() {
   137  			r.client.ServiceName = tempVar
   138  			r.client.Config.SSLDisable = tempSSL
   139  			r.client.Config.HTTPClient = tempClient
   140  		}()
   141  		resp, err := r.client.SendRequest(request, &auth)
   142  		if err != nil {
   143  			return &auth, err
   144  		}
   145  		defer resp.Body.Close()
   146  		if resp.StatusCode > 299 {
   147  			msg, _ := ioutil.ReadAll(resp.Body)
   148  			return nil, fmt.Errorf("Bad status code [%d] returned when fetching Cluster authentication endpoints: %s", resp.StatusCode, msg)
   149  		}
   150  		auth.ServerURL = meta.ServerURL
   151  		return &auth, nil
   152  	}(cMeta)
   153  
   154  	trace.Logger.Println("Got authentication end points for getting oc token")
   155  	token, uname, err := r.openShiftAuthorizePasscode(authEP, passcode, cMeta.IsStagingSatelliteCluster())
   156  	trace.Logger.Println("Got the token and user ", uname)
   157  	clusterName, _ := NormalizeName(authEP.ServerURL[len("https://"):len(authEP.ServerURL)]) //TODO deal with http
   158  	ccontext := "default/" + clusterName + "/" + uname
   159  	uname = uname + "/" + clusterName
   160  	clusters := cfg["clusters"].([]interface{})
   161  	newCluster := map[string]interface{}{"name": clusterName, "cluster": map[string]interface{}{"server": authEP.ServerURL}}
   162  	if skipSSLVerification {
   163  		newCluster["cluster"].(map[string]interface{})["insecure-skip-tls-verify"] = true
   164  	}
   165  	clusters = append(clusters, newCluster)
   166  	cfg["clusters"] = clusters
   167  
   168  	contexts := cfg["contexts"].([]interface{})
   169  	newContext := map[string]interface{}{"name": ccontext, "context": map[string]interface{}{"cluster": clusterName, "namespace": "default", "user": uname}}
   170  	contexts = append(contexts, newContext)
   171  	cfg["contexts"] = contexts
   172  
   173  	users := cfg["users"].([]interface{})
   174  	newUser := map[string]interface{}{"name": uname, "user": map[string]interface{}{"token": token}}
   175  	users = append(users, newUser)
   176  	cfg["users"] = users
   177  
   178  	cfg["current-context"] = ccontext
   179  
   180  	bytes, err := yaml.Marshal(cfg)
   181  	if err != nil {
   182  		return kubecfg, err
   183  	}
   184  	kubecfg = bytes
   185  	return kubecfg, nil
   186  }
   187  
   188  // Never redirect. Let caller handle. This is an http.Client callback method (CheckRedirect)
   189  func neverRedirect(req *http.Request, via []*http.Request) error {
   190  	return http.ErrUseLastResponse
   191  }
   192  
   193  func (r *clusters) openShiftAuthorizePasscode(authEP *authEndpoints, passcode string, skipSSLVerification bool) (string, string, error) {
   194  	request := rest.GetRequest(authEP.AuthorizationEndpoint+"?response_type=token&client_id=openshift-challenging-client").
   195  		Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("passcode:%s", passcode))))
   196  
   197  	tempSSL := r.client.Config.SSLDisable
   198  	tempClient := r.client.Config.HTTPClient
   199  	r.client.Config.SSLDisable = skipSSLVerification
   200  	r.client.Config.HTTPClient = bxhttp.NewHTTPClient(r.client.Config)
   201  
   202  	// To never redirect for this call
   203  	tempVar := r.client.Config.HTTPClient.CheckRedirect
   204  	r.client.Config.HTTPClient.CheckRedirect = neverRedirect
   205  	defer func() {
   206  		r.client.Config.HTTPClient.CheckRedirect = tempVar
   207  		r.client.Config.SSLDisable = tempSSL
   208  		r.client.Config.HTTPClient = tempClient
   209  	}()
   210  
   211  	var respInterface interface{}
   212  	var resp *http.Response
   213  	var err error
   214  	for try := 1; try <= 3; try++ {
   215  		// bmxerror.NewRequestFailure("ServerErrorResponse", string(raw), resp.StatusCode)
   216  		resp, err = r.client.SendRequest(request, respInterface)
   217  		if err != nil {
   218  			if resp.StatusCode != 302 {
   219  				return "", "", err
   220  			}
   221  		}
   222  		defer resp.Body.Close()
   223  		if resp.StatusCode > 399 {
   224  			if try >= 3 {
   225  				msg, _ := ioutil.ReadAll(resp.Body)
   226  				return "", "", fmt.Errorf("Bad status code [%d] returned when openshift login: %s", resp.StatusCode, string(msg))
   227  			}
   228  			time.Sleep(200 * time.Millisecond)
   229  		} else {
   230  			break
   231  		}
   232  	}
   233  
   234  	loc, err := resp.Location()
   235  	if err != nil {
   236  		return "", "", err
   237  	}
   238  	val, err := url.ParseQuery(loc.Fragment)
   239  	if err != nil {
   240  		return "", "", err
   241  	}
   242  	token := val.Get("access_token")
   243  	trace.Logger.Println("Getting username after getting the token")
   244  	name, err := r.getOpenShiftUser(authEP, token)
   245  	if err != nil {
   246  		return "", "", err
   247  	}
   248  	return token, name, nil
   249  }
   250  
   251  func (r *clusters) getOpenShiftUser(authEP *authEndpoints, token string) (string, error) {
   252  	request := rest.GetRequest(authEP.ServerURL+"/apis/user.openshift.io/v1/users/~").
   253  		Set("Authorization", "Bearer "+token)
   254  
   255  	var user openShiftUser
   256  	resp, err := r.client.SendRequest(request, &user)
   257  	if err != nil {
   258  		return "", err
   259  	}
   260  	defer resp.Body.Close()
   261  	if resp.StatusCode > 299 {
   262  		msg, _ := ioutil.ReadAll(resp.Body)
   263  		return "", fmt.Errorf("Bad status code [%d] returned when fetching OpenShift user Details: %s", resp.StatusCode, string(msg))
   264  	}
   265  
   266  	return user.Metadata.Name, nil
   267  }