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 }