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  }