From 758b44b9c035ddaf44bf219a449f633289803945 Mon Sep 17 00:00:00 2001 From: Peter Schultz Date: Tue, 21 Jul 2020 19:24:30 +0200 Subject: [PATCH] gs: support authentication with access token In the Google Cloud Storage backend, support specifying access tokens directly, as an alternative to a credentials file. This is useful when restic is used non-interactively by some other program that is already authenticated and eliminates the need to store long lived credentials. The access token is specified in the GOOGLE_ACCESS_TOKEN environment variable and takes precedence over GOOGLE_APPLICATION_CREDENTIALS. --- changelog/unreleased/pull-2849 | 7 +++++++ doc/030_preparing_a_new_repo.rst | 12 ++++++++++++ internal/backend/gs/gs.go | 20 +++++++++++++++----- internal/backend/gs/gs_test.go | 10 ++++++++-- 4 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 changelog/unreleased/pull-2849 diff --git a/changelog/unreleased/pull-2849 b/changelog/unreleased/pull-2849 new file mode 100644 index 000000000..fde8d4112 --- /dev/null +++ b/changelog/unreleased/pull-2849 @@ -0,0 +1,7 @@ +Enhancement: Authenticate to Google Cloud Storage with access token + +When using the GCS backend, it is now possible to authenticate with OAuth2 +access tokens instead of a credentials file by setting the GOOGLE_ACCESS_TOKEN +environment variable. + +https://github.com/restic/restic/pull/2849 diff --git a/doc/030_preparing_a_new_repo.rst b/doc/030_preparing_a_new_repo.rst index 780964265..60c2e24b5 100644 --- a/doc/030_preparing_a_new_repo.rst +++ b/doc/030_preparing_a_new_repo.rst @@ -458,6 +458,18 @@ which means if you're running in Google Container Engine or are otherwise located on an instance with default service accounts then these should work out of the box. +Alternatively, you can specify an existing access token directly: + +.. code-block:: console + + $ export GOOGLE_ACCESS_TOKEN=ya29.a0AfH6SMC78... + +If ``GOOGLE_ACCESS_TOKEN`` is set all other authentication mechanisms are +disabled. The access token must have at least the +``https://www.googleapis.com/auth/devstorage.read_write`` scope. Keep in mind +that access tokens are short-lived (usually one hour), so they are not suitable +if creating a backup takes longer than that, for instance. + Once authenticated, you can use the ``gs:`` backend type to create a new repository in the bucket ``foo`` at the root path: diff --git a/internal/backend/gs/gs.go b/internal/backend/gs/gs.go index feea05d07..9088f1161 100644 --- a/internal/backend/gs/gs.go +++ b/internal/backend/gs/gs.go @@ -47,15 +47,25 @@ func getStorageService(rt http.RoundTripper) (*storage.Service, error) { Transport: rt, } - // create a now context with the HTTP client stored at the oauth2.HTTPClient key + // create a new context with the HTTP client stored at the oauth2.HTTPClient key ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient) - // use this context - client, err := google.DefaultClient(ctx, storage.DevstorageReadWriteScope) - if err != nil { - return nil, err + var ts oauth2.TokenSource + if token := os.Getenv("GOOGLE_ACCESS_TOKEN"); token != "" { + ts = oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: token, + TokenType: "Bearer", + }) + } else { + var err error + ts, err = google.DefaultTokenSource(ctx, storage.DevstorageReadWriteScope) + if err != nil { + return nil, err + } } + client := oauth2.NewClient(ctx, ts) + service, err := storage.New(client) if err != nil { return nil, err diff --git a/internal/backend/gs/gs_test.go b/internal/backend/gs/gs_test.go index 27ff809ff..d7bf1422c 100644 --- a/internal/backend/gs/gs_test.go +++ b/internal/backend/gs/gs_test.go @@ -87,7 +87,6 @@ func TestBackendGS(t *testing.T) { }() vars := []string{ - "GOOGLE_APPLICATION_CREDENTIALS", "RESTIC_TEST_GS_PROJECT_ID", "RESTIC_TEST_GS_REPOSITORY", } @@ -98,6 +97,10 @@ func TestBackendGS(t *testing.T) { return } } + if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")+os.Getenv("GOOGLE_ACCESS_TOKEN") == "" { + t.Skipf("environment variable GOOGLE_APPLICATION_CREDENTIALS not set, nor GOOGLE_ACCESS_TOKEN") + return + } t.Logf("run tests") newGSTestSuite(t).RunTests(t) @@ -105,7 +108,6 @@ func TestBackendGS(t *testing.T) { func BenchmarkBackendGS(t *testing.B) { vars := []string{ - "GOOGLE_APPLICATION_CREDENTIALS", "RESTIC_TEST_GS_PROJECT_ID", "RESTIC_TEST_GS_REPOSITORY", } @@ -116,6 +118,10 @@ func BenchmarkBackendGS(t *testing.B) { return } } + if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")+os.Getenv("GOOGLE_ACCESS_TOKEN") == "" { + t.Skipf("environment variable GOOGLE_APPLICATION_CREDENTIALS not set, nor GOOGLE_ACCESS_TOKEN") + return + } t.Logf("run tests") newGSTestSuite(t).RunBenchmarks(t)