github.com/coreos/mantle@v0.13.0/auth/azure.go (about) 1 // Copyright 2016 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package auth 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "io/ioutil" 22 "os" 23 "os/user" 24 "path/filepath" 25 26 "golang.org/x/text/encoding/unicode" 27 "golang.org/x/text/transform" 28 29 "github.com/coreos/mantle/platform" 30 ) 31 32 const ( 33 AzureAuthPath = ".azure/credentials.json" 34 AzureProfilePath = ".azure/azureProfile.json" 35 ) 36 37 // A version of the Options struct from platform/api/azure that only 38 // contains the ASM values. Otherwise there's a cyclical depdendence 39 // because platform/api/azure has to import auth to have access to 40 // the ReadAzureProfile function. 41 type Options struct { 42 *platform.Options 43 44 SubscriptionName string 45 SubscriptionID string 46 47 // Azure API endpoint. If unset, the Azure SDK default will be used. 48 ManagementURL string 49 ManagementCertificate []byte 50 51 // Azure Storage API endpoint suffix. If unset, the Azure SDK default will be used. 52 StorageEndpointSuffix string 53 } 54 55 type AzureEnvironment struct { 56 ActiveDirectoryEndpointURL string `json:"activeDirectoryEndpointUrl"` 57 ActiveDirectoryGraphAPIVersion string `json:"activeDirectoryGraphApiVersion"` 58 ActiveDirectoryGraphResourceID string `json:"activeDirectoryGraphResourceId"` 59 ActiveDirectoryResourceID string `json:"activeDirectoryResourceId"` 60 AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix string `json:"azureDataLakeAnalyticsCatalogAndJobEndpointSuffix"` 61 AzureDataLakeStoreFileSystemEndpointSuffix string `json:"azureDataLakeStoreFileSystemEndpointSuffix"` 62 GalleryEndpointURL string `json:"galleryEndpointUrl"` 63 KeyVaultDNSSuffix string `json:"keyVaultDnsSuffix"` 64 ManagementEndpointURL string `json:"managementEndpointUrl"` 65 Name string `json:"name"` 66 PortalURL string `json:"portalUrl"` 67 PublishingProfileURL string `json:"publishingProfileUrl"` 68 ResourceManagerEndpointURL string `json:"resourceManagerEndpointUrl"` 69 SqlManagementEndpointURL string `json:"sqlManagementEndpointUrl"` 70 SqlServerHostnameSuffix string `json:"sqlServerHostnameSuffix"` 71 StorageEndpointSuffix string `json:"storageEndpointSuffix"` 72 } 73 74 type AzureManagementCertificate struct { 75 Cert string `json:"cert"` 76 Key string `json:"key"` 77 } 78 79 type AzureSubscription struct { 80 EnvironmentName string `json:"environmentName"` 81 ID string `json:"id"` 82 IsDefault bool `json:"isDefault"` 83 ManagementCertificate AzureManagementCertificate `json:"managementCertificate"` 84 ManagementEndpointURL string `json:"managementEndpointUrl"` 85 Name string `json:"name"` 86 RegisteredProviders []string `json:"registeredProviders"` 87 State string `json:"state"` 88 } 89 90 // AzureProfile represents a parsed Azure Profile Configuration File. 91 type AzureProfile struct { 92 Environments []AzureEnvironment `json:"environments"` 93 Subscriptions []AzureSubscription `json:"subscriptions"` 94 } 95 96 // AsOptions converts all subscriptions into a slice of Options. 97 // If there is an environment with a name matching the subscription, that environment's storage endpoint will be copied to the options. 98 func (ap *AzureProfile) AsOptions() []Options { 99 var o []Options 100 101 for _, sub := range ap.Subscriptions { 102 newo := Options{ 103 SubscriptionName: sub.Name, 104 SubscriptionID: sub.ID, 105 ManagementURL: sub.ManagementEndpointURL, 106 ManagementCertificate: bytes.Join([][]byte{[]byte(sub.ManagementCertificate.Key), []byte(sub.ManagementCertificate.Cert)}, []byte("\n")), 107 } 108 109 // find the storage endpoint for the subscription 110 for _, e := range ap.Environments { 111 if e.Name == sub.EnvironmentName { 112 newo.StorageEndpointSuffix = e.StorageEndpointSuffix 113 break 114 } 115 } 116 117 o = append(o, newo) 118 } 119 120 return o 121 } 122 123 // SubscriptionOptions returns the name subscription in the Azure profile as a Options struct. 124 // If the subscription name is "", the first subscription is returned. 125 // If there are no subscriptions or the named subscription is not found, SubscriptionOptions returns nil. 126 func (ap *AzureProfile) SubscriptionOptions(name string) *Options { 127 opts := ap.AsOptions() 128 129 if len(opts) == 0 { 130 return nil 131 } 132 133 if name == "" { 134 return &opts[0] 135 } else { 136 for _, o := range ap.AsOptions() { 137 if o.SubscriptionName == name { 138 return &o 139 } 140 } 141 } 142 143 return nil 144 } 145 146 // ReadAzureProfile decodes an Azure Profile, as created by the Azure Cross-platform CLI. 147 // 148 // If path is empty, $HOME/.azure/azureProfile.json is read. 149 func ReadAzureProfile(path string) (*AzureProfile, error) { 150 if path == "" { 151 user, err := user.Current() 152 if err != nil { 153 return nil, err 154 } 155 156 path = filepath.Join(user.HomeDir, AzureProfilePath) 157 } 158 159 contents, err := DecodeBOMFile(path) 160 if err != nil { 161 return nil, err 162 } 163 164 var ap AzureProfile 165 if err := json.Unmarshal(contents, &ap); err != nil { 166 return nil, err 167 } 168 169 if len(ap.Subscriptions) == 0 { 170 return nil, fmt.Errorf("Azure profile %q contains no subscriptions", path) 171 } 172 173 return &ap, nil 174 } 175 176 func DecodeBOMFile(path string) ([]byte, error) { 177 f, err := os.Open(path) 178 if err != nil { 179 return nil, err 180 } 181 defer f.Close() 182 decoder := unicode.UTF8.NewDecoder() 183 reader := transform.NewReader(f, unicode.BOMOverride(decoder)) 184 return ioutil.ReadAll(reader) 185 }