github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/gce/credentials.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package gce
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"runtime"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/utils"
    15  
    16  	"github.com/juju/juju/cloud"
    17  	"github.com/juju/juju/environs"
    18  	"github.com/juju/juju/provider/gce/google"
    19  )
    20  
    21  const (
    22  	credAttrPrivateKey  = "private-key"
    23  	credAttrClientID    = "client-id"
    24  	credAttrClientEmail = "client-email"
    25  	credAttrProjectID   = "project-id"
    26  
    27  	// The contents of the file for "jsonfile" auth-type.
    28  	credAttrFile = "file"
    29  )
    30  
    31  type environProviderCredentials struct{}
    32  
    33  // CredentialSchemas is part of the environs.ProviderCredentials interface.
    34  func (environProviderCredentials) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema {
    35  	return map[cloud.AuthType]cloud.CredentialSchema{
    36  		cloud.OAuth2AuthType: {{
    37  			Name:           credAttrClientID,
    38  			CredentialAttr: cloud.CredentialAttr{Description: "client ID"},
    39  		}, {
    40  			Name:           credAttrClientEmail,
    41  			CredentialAttr: cloud.CredentialAttr{Description: "client e-mail address"},
    42  		}, {
    43  			Name: credAttrPrivateKey,
    44  			CredentialAttr: cloud.CredentialAttr{
    45  				Description: "client secret",
    46  				Hidden:      true,
    47  			},
    48  		}, {
    49  			Name:           credAttrProjectID,
    50  			CredentialAttr: cloud.CredentialAttr{Description: "project ID"},
    51  		}},
    52  		cloud.JSONFileAuthType: {{
    53  			Name: credAttrFile,
    54  			CredentialAttr: cloud.CredentialAttr{
    55  				Description: "path to the .json file containing your Google Compute Engine project credentials",
    56  				FilePath:    true,
    57  			},
    58  		}},
    59  	}
    60  }
    61  
    62  // DetectCredentials is part of the environs.ProviderCredentials interface.
    63  func (environProviderCredentials) DetectCredentials() (*cloud.CloudCredential, error) {
    64  	// Google recommends credentials in a json file:
    65  	// 1. whose path is specified by the GOOGLE_APPLICATION_CREDENTIALS environment variable.
    66  	// 2. whose location is known to the gcloud command-line tool.
    67  	//   On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
    68  	//   On other systems, $HOME/.config/gcloud/application_default_credentials.json.
    69  
    70  	validatePath := func(possibleFilePath string) string {
    71  		if possibleFilePath == "" {
    72  			return ""
    73  		}
    74  		fi, err := os.Stat(possibleFilePath)
    75  		if err != nil || fi.IsDir() {
    76  			return ""
    77  		}
    78  		return possibleFilePath
    79  	}
    80  
    81  	possibleFilePath := validatePath(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
    82  	if possibleFilePath == "" {
    83  		possibleFilePath = validatePath(wellKnownCredentialsFile())
    84  	}
    85  	if possibleFilePath == "" {
    86  		return nil, errors.NotFoundf("gce credentials")
    87  	}
    88  
    89  	authFile, err := os.Open(possibleFilePath)
    90  	if err != nil {
    91  		return nil, errors.Trace(err)
    92  	}
    93  	defer authFile.Close()
    94  
    95  	parsedCred, err := parseJSONAuthFile(authFile)
    96  	if err != nil {
    97  		return nil, errors.Annotatef(err, "invalid json credential file %s", possibleFilePath)
    98  	}
    99  
   100  	user, err := utils.LocalUsername()
   101  	if err != nil {
   102  		return nil, errors.Trace(err)
   103  	}
   104  	cred := cloud.NewCredential(cloud.JSONFileAuthType, map[string]string{
   105  		"file": possibleFilePath,
   106  	})
   107  	credName := parsedCred.Attributes()[credAttrClientEmail]
   108  	if credName == "" {
   109  		credName = parsedCred.Attributes()[credAttrClientID]
   110  	}
   111  	cred.Label = fmt.Sprintf("google credential %q", credName)
   112  	return &cloud.CloudCredential{
   113  		DefaultRegion: os.Getenv("CLOUDSDK_COMPUTE_REGION"),
   114  		AuthCredentials: map[string]cloud.Credential{
   115  			user: cred,
   116  		}}, nil
   117  }
   118  
   119  func wellKnownCredentialsFile() string {
   120  	const f = "application_default_credentials.json"
   121  	if runtime.GOOS == "windows" {
   122  		return filepath.Join(os.Getenv("APPDATA"), "gcloud", f)
   123  	}
   124  	return filepath.Join(utils.Home(), ".config", "gcloud", f)
   125  }
   126  
   127  // parseJSONAuthFile parses a file, and extracts the OAuth2 credentials within.
   128  func parseJSONAuthFile(r io.Reader) (cloud.Credential, error) {
   129  	creds, err := google.ParseJSONKey(r)
   130  	if err != nil {
   131  		return cloud.Credential{}, errors.Trace(err)
   132  	}
   133  	return cloud.NewCredential(cloud.OAuth2AuthType, map[string]string{
   134  		credAttrProjectID:   creds.ProjectID,
   135  		credAttrClientID:    creds.ClientID,
   136  		credAttrClientEmail: creds.ClientEmail,
   137  		credAttrPrivateKey:  string(creds.PrivateKey),
   138  	}), nil
   139  }
   140  
   141  // FinalizeCredential is part of the environs.ProviderCredentials interface.
   142  func (environProviderCredentials) FinalizeCredential(_ environs.FinalizeCredentialContext, args environs.FinalizeCredentialParams) (*cloud.Credential, error) {
   143  	return &args.Credential, nil
   144  }