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 ef138e2d4..28c79217f 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 63f46e6ed..70df25cfa 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)