github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/provider/gce/google/config.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package google 5 6 import ( 7 "encoding/json" 8 "io" 9 "io/ioutil" 10 "net/mail" 11 12 "github.com/juju/errors" 13 ) 14 15 // The names of OS environment variables related to GCE. 16 // 17 // Note that these are not specified by Google. Instead they are 18 // defined by juju for use with the GCE provider. If Google defines 19 // equivalent environment variables they should be used instead. 20 const ( 21 OSEnvPrivateKey = "GCE_PRIVATE_KEY" 22 OSEnvClientID = "GCE_CLIENT_ID" 23 OSEnvClientEmail = "GCE_CLIENT_EMAIL" 24 OSEnvRegion = "GCE_REGION" 25 OSEnvProjectID = "GCE_PROJECT_ID" 26 OSEnvImageEndpoint = "GCE_IMAGE_URL" 27 ) 28 29 const ( 30 jsonKeyTypeServiceAccount = "service_account" 31 ) 32 33 // Credentials holds the OAuth2 credentials needed to authenticate on GCE. 34 type Credentials struct { 35 // JSONKey is the content of the JSON key file for these credentials. 36 JSONKey []byte 37 38 // ClientID is the GCE account's OAuth ID. It is part of the OAuth 39 // config used in the OAuth-wrapping network transport. 40 ClientID string 41 42 // ClientEmail is the email address associatd with the GCE account. 43 // It is used to generate a new OAuth token to use in the 44 // OAuth-wrapping network transport. 45 ClientEmail string 46 47 // PrivateKey is the private key that matches the public key 48 // associatd with the GCE account. It is used to generate a new 49 // OAuth token to use in the OAuth-wrapping network transport. 50 PrivateKey []byte 51 } 52 53 // NewCredentials returns a new Credentials based on the provided 54 // values. The keys must be recognized OS env var names for the 55 // different credential fields. 56 func NewCredentials(values map[string]string) (*Credentials, error) { 57 var creds Credentials 58 for k, v := range values { 59 switch k { 60 case OSEnvClientID: 61 creds.ClientID = v 62 case OSEnvClientEmail: 63 creds.ClientEmail = v 64 case OSEnvPrivateKey: 65 creds.PrivateKey = []byte(v) 66 default: 67 return nil, errors.NotSupportedf("key %q", k) 68 } 69 } 70 71 if err := creds.Validate(); err == nil { 72 jk, err := creds.buildJSONKey() 73 if err != nil { 74 return nil, errors.Trace(err) 75 } 76 creds.JSONKey = jk 77 } 78 79 return &creds, nil 80 } 81 82 // ParseJSONKey returns a new Credentials with values based on the 83 // provided JSON key file contents. 84 func ParseJSONKey(jsonKeyFile io.Reader) (*Credentials, error) { 85 jsonKey, err := ioutil.ReadAll(jsonKeyFile) 86 if err != nil { 87 return nil, errors.Trace(err) 88 } 89 values, err := parseJSONKey(jsonKey) 90 if err != nil { 91 return nil, errors.Trace(err) 92 } 93 delete(values, "type") 94 delete(values, "private_key_id") 95 creds, err := NewCredentials(values) 96 if err != nil { 97 return nil, errors.Trace(err) 98 } 99 creds.JSONKey = jsonKey 100 return creds, nil 101 } 102 103 // parseJSONKey extracts the auth information from the JSON file 104 // downloaded from the GCE console (under /apiui/credential). 105 func parseJSONKey(jsonKey []byte) (map[string]string, error) { 106 data := make(map[string]string) 107 if err := json.Unmarshal(jsonKey, &data); err != nil { 108 return nil, errors.Trace(err) 109 } 110 111 keyType, ok := data["type"] 112 if !ok { 113 return nil, errors.New(`missing "type"`) 114 } 115 switch keyType { 116 case jsonKeyTypeServiceAccount: 117 for k, v := range data { 118 switch k { 119 case "private_key": 120 data[OSEnvPrivateKey] = v 121 delete(data, k) 122 case "client_email": 123 data[OSEnvClientEmail] = v 124 delete(data, k) 125 case "client_id": 126 data[OSEnvClientID] = v 127 delete(data, k) 128 } 129 } 130 default: 131 return nil, errors.NotSupportedf("JSON key type %q", data["type"]) 132 } 133 return data, nil 134 } 135 136 // buildJSONKey returns the content of the JSON key file for the 137 // credential values. 138 func (gc Credentials) buildJSONKey() ([]byte, error) { 139 return json.Marshal(&map[string]string{ 140 "type": jsonKeyTypeServiceAccount, 141 "client_id": gc.ClientID, 142 "client_email": gc.ClientEmail, 143 "private_key": string(gc.PrivateKey), 144 }) 145 } 146 147 // Values returns the credentials as a simple mapping with the 148 // corresponding OS env variable names as the keys. 149 func (gc Credentials) Values() map[string]string { 150 return map[string]string{ 151 OSEnvClientID: gc.ClientID, 152 OSEnvClientEmail: gc.ClientEmail, 153 OSEnvPrivateKey: string(gc.PrivateKey), 154 } 155 } 156 157 // Validate checks the credentialss for invalid values. If the values 158 // are not valid, it returns errors.NotValid with the message set to 159 // the corresponding OS environment variable name. 160 // 161 // To be considered valid, each of the credentials must be set to some 162 // non-empty value. Furthermore, ClientEmail must be a proper email 163 // address. 164 func (gc Credentials) Validate() error { 165 if gc.ClientID == "" { 166 return NewMissingConfigValue(OSEnvClientID, "ClientID") 167 } 168 if gc.ClientEmail == "" { 169 return NewMissingConfigValue(OSEnvClientEmail, "ClientEmail") 170 } 171 if _, err := mail.ParseAddress(gc.ClientEmail); err != nil { 172 return NewInvalidConfigValue(OSEnvClientEmail, gc.ClientEmail, err) 173 } 174 if len(gc.PrivateKey) == 0 { 175 return NewMissingConfigValue(OSEnvPrivateKey, "PrivateKey") 176 } 177 return nil 178 } 179 180 // ConnectionConfig contains the config values used for a connection 181 // to the GCE API. 182 type ConnectionConfig struct { 183 // Region is the GCE region in which to operate for the connection. 184 Region string 185 186 // ProjectID is the project ID to use in all GCE API requests for 187 // the connection. 188 ProjectID string 189 } 190 191 // Validate checks the connection's fields for invalid values. 192 // If the values are not valid, it returns a config.InvalidConfigValue 193 // error with the key set to the corresponding OS environment variable 194 // name. 195 // 196 // To be considered valid, each of the connection's must be set to some 197 // non-empty value. 198 func (gc ConnectionConfig) Validate() error { 199 if gc.Region == "" { 200 return NewMissingConfigValue(OSEnvRegion, "Region") 201 } 202 if gc.ProjectID == "" { 203 return NewMissingConfigValue(OSEnvProjectID, "ProjectID") 204 } 205 return nil 206 }