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  }