github.com/wunderio/silta-cli@v0.0.0-20240508100559-3017e4ab3a20/internal/azure/ciAuth.go (about) 1 package azure 2 3 import ( 4 b64 "encoding/base64" 5 "encoding/json" 6 "errors" 7 "io" 8 "net/http" 9 "strings" 10 ) 11 12 type Base64Kubeconfig struct { 13 Base64Kubeconfig string `json:"value"` 14 Name string `json:"name"` 15 } 16 17 type Subscription struct { 18 SubscriptionID string `json:"subscriptionId"` 19 } 20 21 // OAuth 2 token structure 22 type tokenResponse struct { 23 AccessToken string `json:"access_token"` 24 TokenType string `json:"token_type"` 25 ExpiresIn float64 `json:"expires_in"` 26 ExtExpiresIn float64 `json:"ext_expires_in"` 27 } 28 29 // Returns the first subscription ID 30 func GetDefaultSubscriptionID(token string) (subscriptionID string, err error) { 31 32 req, err := http.NewRequest(http.MethodGet, "https://management.azure.com/subscriptions?api-version=2020-01-01", nil) 33 if err != nil { 34 return "", err 35 } 36 req.Header.Set("Authorization", "Bearer "+token) 37 38 resp, err := http.DefaultClient.Do(req) 39 if err != nil { 40 return "", err 41 } 42 defer resp.Body.Close() 43 44 var subscriptions struct { 45 Value []Subscription `json:"value"` 46 } 47 if err := json.NewDecoder(resp.Body).Decode(&subscriptions); err != nil { 48 return "", err 49 } 50 51 if len(subscriptions.Value) == 0 { 52 return "", errors.New("no subscriptions found") 53 } 54 return subscriptions.Value[0].SubscriptionID, nil 55 } 56 57 // Returns access token. Failing that, returns non-nil error 58 // tenantId - Azure tenant ID 59 // clientId - Client ID. Can pass Service Principal ID 60 // clientSecret - Client secret. Cant pass Service Principal password 61 func GetAuthToken(tenantId string, clientId string, clientSecret string) (string, error) { 62 req, err := http.NewRequest(http.MethodPost, "https://login.microsoftonline.com/"+tenantId+"/oauth2/v2.0/token", nil) 63 if err != nil { 64 return "", err 65 } 66 67 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 68 69 q := req.URL.Query() 70 q.Add("grant_type", "client_credentials") 71 q.Add("client_id", clientId) 72 q.Add("client_secret", clientSecret) 73 q.Add("scope", "https://management.azure.com/.default") 74 75 req.Body = io.NopCloser(strings.NewReader(q.Encode())) 76 resp, err := http.DefaultClient.Do(req) 77 if err != nil { 78 return "", err 79 } 80 defer resp.Body.Close() 81 82 str, err := io.ReadAll(resp.Body) 83 if err != nil { 84 return "", err 85 } 86 87 var token tokenResponse 88 if err := json.Unmarshal(str, &token); err != nil { 89 return "", err 90 } 91 return token.AccessToken, nil 92 } 93 94 func GetKubeconfig(accessToken string, subscriptionId string, resourceGroupName string, clusterName string) ([]byte, error) { 95 req, err := http.NewRequest(http.MethodPost, "https://management.azure.com/subscriptions/"+subscriptionId+"/resourceGroups/"+resourceGroupName+"/providers/Microsoft.ContainerService/managedClusters/"+clusterName+"/listClusterAdminCredential?api-version=2023-02-01", nil) 96 if err != nil { 97 return nil, err 98 } 99 100 req.Header.Set("Authorization", "Bearer "+accessToken) 101 102 resp, err := http.DefaultClient.Do(req) 103 if err != nil { 104 return nil, err 105 } 106 defer resp.Body.Close() 107 108 var kubeconfigs struct { 109 Value []Base64Kubeconfig `json:"kubeconfigs"` 110 } 111 if err := json.NewDecoder(resp.Body).Decode(&kubeconfigs); err != nil { 112 return nil, err 113 } 114 115 if len(kubeconfigs.Value) == 0 { 116 return nil, errors.New("no kubeconfigs found") 117 } 118 119 kconfigByte, err := b64.StdEncoding.DecodeString(kubeconfigs.Value[0].Base64Kubeconfig) 120 if err != nil { 121 return nil, err 122 } 123 return kconfigByte, nil 124 }