Skip to content

fix: use active gcp account #8584

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 39 additions & 20 deletions pkg/skaffold/gcp/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ limitations under the License.
package gcp

import (
"bytes"
"context"
"encoding/json"
"fmt"
"os/exec"
"sync"
"time"

"github.com/docker/cli/cli/config/configfile"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
Expand Down Expand Up @@ -59,35 +62,51 @@ func AutoConfigureGCRCredentialHelper(cf *configfile.ConfigFile) {
}
}

func activeUserCredentials(ctx context.Context) (*google.Credentials, error) {
type token struct {
AccessToken string `json:"access_token"`
TokenExpiry time.Time `json:"token_expiry"`
}

type tokenSource struct {
}

func (ts tokenSource) Token() (*oauth2.Token, error) {
// the command return a json object containing id_token, access_token, token_expiry
cmd := exec.Command("gcloud", "auth", "print-identity-token", "--format=json")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parsing credential field from the gcloud config config-helper command output should be able to achieve the same effect, but gcloud config config-helper is not an open knowledge, as may be removed as --help indicate

NOTES
    This command is an internal implementation detail and may change or
    disappear without notice.

var body bytes.Buffer
cmd.Stdout = &body
err := util.RunCmd(context.TODO(), cmd)
if err != nil {
return nil, fmt.Errorf("failed to get access token %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: might make sense to differentiate the error messages here so it is clearer which one of these 2 steps failed

}
var t token
if err := json.Unmarshal(body.Bytes(), &t); err != nil {
return nil, fmt.Errorf("failed to unmarshal gcould command result into access token %v", err)
}
return &oauth2.Token{AccessToken: t.AccessToken, Expiry: t.TokenExpiry}, nil
}

func activeUserCredentialsOnce() (*google.Credentials, error) {
credsOnce.Do(func() {
cmd := exec.Command("gcloud", "auth", "print-access-token", "--format=json")
body, err := util.RunCmdOut(ctx, cmd)
c, err := activeUserCredentials()
if err != nil {
log.Entry(context.TODO()).Infof("unable to retrieve gcloud access token: %v", err)
log.Entry(context.TODO()).Info("falling back to application default credentials")
credsErr = fmt.Errorf("retrieving gcloud access token: %w", err)
return
}
jsonCreds := make(map[string]interface{})
json.Unmarshal(body, &jsonCreds)
jsonCreds["type"] = "authorized_user"
body, _ = json.Marshal(jsonCreds)

c, err := google.CredentialsFromJSON(context.Background(), body)
if err != nil {
log.Entry(context.TODO()).Infof("unable to retrieve google creds: %v", err)
log.Entry(context.TODO()).Info("falling back to application default credentials")
return
}
_, err = c.TokenSource.Token()
if err != nil {
log.Entry(context.TODO()).Infof("unable to retrieve token: %v", err)
log.Entry(context.TODO()).Info("falling back to application default credentials")
return
}
creds = c
})

return creds, credsErr
}

func activeUserCredentials() (*google.Credentials, error) {
var ts tokenSource
t, err := ts.Token()
if err != nil {
return nil, err
}
c := &google.Credentials{TokenSource: oauth2.ReuseTokenSource(t, ts)}
return c, nil
}
45 changes: 45 additions & 0 deletions pkg/skaffold/gcp/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ limitations under the License.
package gcp

import (
"fmt"
"testing"

"github.com/docker/cli/cli/config/configfile"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/v2/testutil"
)

Expand Down Expand Up @@ -111,3 +113,46 @@ func TestAutoConfigureGCRCredentialHelper(t *testing.T) {
})
}
}

func TestActiveUserCredentials(t *testing.T) {
output := `{
"access_token": "access_token_value",
"id_token": "id_token_value",
"token_expiry": "2023-03-23T23:10:40Z"
}`

tests := []struct {
name string
mockCommand util.Command
shouldErr bool
expected string
}{
{
name: "get credential succeed",
mockCommand: testutil.CmdRunWithOutput("gcloud auth print-identity-token --format=json", output).
AndRunWithOutput("gcloud auth print-identity-token --format=json", output),
expected: "access_token_value",
},
{
name: "command error, get error",
mockCommand: testutil.CmdRunErr("gcloud auth print-identity-token --format=json", fmt.Errorf("command exited with non-zero code")),
shouldErr: true,
},
{
name: "invalid json, get error",
mockCommand: testutil.CmdRunWithOutput("gcloud auth print-identity-token --format=json", "{{{"),
shouldErr: true,
},
}
for _, test := range tests {
testutil.Run(t, test.name, func(t *testutil.T) {
t.Override(&util.DefaultExecCommand, test.mockCommand)
out, err := activeUserCredentials()
t.CheckError(test.shouldErr, err)
if err == nil {
token, _ := out.TokenSource.Token()
t.CheckDeepEqual(test.expected, token.AccessToken)
}
})
}
}
4 changes: 3 additions & 1 deletion pkg/skaffold/gcp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"google.golang.org/api/option"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/version"
)

Expand All @@ -31,7 +32,8 @@ func ClientOptions(ctx context.Context) []option.ClientOption {
option.WithUserAgent(version.UserAgent()),
}

creds, cErr := activeUserCredentials(ctx)
creds, cErr := activeUserCredentialsOnce()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: might make sense to add a debug level log entry for cErr, currently I believe it is ignored outright

log.Entry(ctx).Debug("unable to active user credentials %w", cErr)
if cErr == nil && creds != nil {
options = append(options, option.WithCredentials(creds))
}
Expand Down