github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 // ProjectID is the GCE project's ID that these credentials relate to. 43 ProjectID string 44 45 // ClientEmail is the email address associatd with the GCE account. 46 // It is used to generate a new OAuth token to use in the 47 // OAuth-wrapping network transport. 48 ClientEmail string 49 50 // PrivateKey is the private key that matches the public key 51 // associatd with the GCE account. It is used to generate a new 52 // OAuth token to use in the OAuth-wrapping network transport. 53 PrivateKey []byte 54 } 55 56 // NewCredentials returns a new Credentials based on the provided 57 // values. The keys must be recognized OS env var names for the 58 // different credential fields. 59 func NewCredentials(values map[string]string) (*Credentials, error) { 60 var creds Credentials 61 for k, v := range values { 62 switch k { 63 case OSEnvClientID: 64 creds.ClientID = v 65 case OSEnvClientEmail: 66 creds.ClientEmail = v 67 case OSEnvProjectID: 68 creds.ProjectID = v 69 case OSEnvPrivateKey: 70 creds.PrivateKey = []byte(v) 71 default: 72 return nil, errors.NotSupportedf("key %q", k) 73 } 74 } 75 76 if err := creds.Validate(); err == nil { 77 jk, err := creds.buildJSONKey() 78 if err != nil { 79 return nil, errors.Trace(err) 80 } 81 creds.JSONKey = jk 82 } 83 84 return &creds, nil 85 } 86 87 // ParseJSONKey returns a new Credentials with values based on the 88 // provided JSON key file contents. 89 func ParseJSONKey(jsonKeyFile io.Reader) (*Credentials, error) { 90 jsonKey, err := ioutil.ReadAll(jsonKeyFile) 91 if err != nil { 92 return nil, errors.Trace(err) 93 } 94 values, err := parseJSONKey(jsonKey) 95 if err != nil { 96 return nil, errors.Trace(err) 97 } 98 creds, err := NewCredentials(values) 99 if err != nil { 100 return nil, errors.Trace(err) 101 } 102 creds.JSONKey = jsonKey 103 return creds, nil 104 } 105 106 // parseJSONKey extracts the auth information from the JSON file 107 // downloaded from the GCE console (under /apiui/credential). 108 func parseJSONKey(jsonKey []byte) (map[string]string, error) { 109 in := make(map[string]string) 110 if err := json.Unmarshal(jsonKey, &in); err != nil { 111 return nil, errors.Trace(err) 112 } 113 114 keyType, ok := in["type"] 115 if !ok { 116 return nil, errors.New(`missing "type"`) 117 } 118 switch keyType { 119 case jsonKeyTypeServiceAccount: 120 out := make(map[string]string) 121 for k, v := range in { 122 switch k { 123 case "private_key": 124 out[OSEnvPrivateKey] = v 125 case "client_email": 126 out[OSEnvClientEmail] = v 127 case "client_id": 128 out[OSEnvClientID] = v 129 case "project_id": 130 out[OSEnvProjectID] = v 131 } 132 } 133 return out, nil 134 default: 135 return nil, errors.NotSupportedf("JSON key type %q", keyType) 136 } 137 } 138 139 // buildJSONKey returns the content of the JSON key file for the 140 // credential values. 141 func (gc Credentials) buildJSONKey() ([]byte, error) { 142 return json.Marshal(&map[string]string{ 143 "type": jsonKeyTypeServiceAccount, 144 "client_id": gc.ClientID, 145 "client_email": gc.ClientEmail, 146 "private_key": string(gc.PrivateKey), 147 }) 148 } 149 150 // Values returns the credentials as a simple mapping with the 151 // corresponding OS env variable names as the keys. 152 func (gc Credentials) Values() map[string]string { 153 return map[string]string{ 154 OSEnvClientID: gc.ClientID, 155 OSEnvClientEmail: gc.ClientEmail, 156 OSEnvPrivateKey: string(gc.PrivateKey), 157 OSEnvProjectID: gc.ProjectID, 158 } 159 } 160 161 // Validate checks the credentialss for invalid values. If the values 162 // are not valid, it returns errors.NotValid with the message set to 163 // the corresponding OS environment variable name. 164 // 165 // To be considered valid, each of the credentials must be set to some 166 // non-empty value. Furthermore, ClientEmail must be a proper email 167 // address. 168 func (gc Credentials) Validate() error { 169 if gc.ClientID == "" { 170 return NewMissingConfigValue(OSEnvClientID, "ClientID") 171 } 172 if gc.ClientEmail == "" { 173 return NewMissingConfigValue(OSEnvClientEmail, "ClientEmail") 174 } 175 if _, err := mail.ParseAddress(gc.ClientEmail); err != nil { 176 return NewInvalidConfigValue(OSEnvClientEmail, gc.ClientEmail, err) 177 } 178 if len(gc.PrivateKey) == 0 { 179 return NewMissingConfigValue(OSEnvPrivateKey, "PrivateKey") 180 } 181 return nil 182 } 183 184 // ConnectionConfig contains the config values used for a connection 185 // to the GCE API. 186 type ConnectionConfig struct { 187 // Region is the GCE region in which to operate for the connection. 188 Region string 189 190 // ProjectID is the project ID to use in all GCE API requests for 191 // the connection. 192 ProjectID string 193 } 194 195 // Validate checks the connection's fields for invalid values. 196 // If the values are not valid, it returns a config.InvalidConfigValue 197 // error with the key set to the corresponding OS environment variable 198 // name. 199 // 200 // To be considered valid, each of the connection's must be set to some 201 // non-empty value. 202 func (gc ConnectionConfig) Validate() error { 203 if gc.Region == "" { 204 return NewMissingConfigValue(OSEnvRegion, "Region") 205 } 206 if gc.ProjectID == "" { 207 return NewMissingConfigValue(OSEnvProjectID, "ProjectID") 208 } 209 return nil 210 }