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