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