forked from TrueCloudLab/restic
Automatically load Google auth
This change removes the hardcoded Google auth mechanism for the GCS backend, instead using Google's provided client library to discover and generate credential material. Google recommend that client libraries use their common auth mechanism in order to authorise requests against Google services. Doing so means you automatically support various types of authentication, from the standard GOOGLE_APPLICATION_CREDENTIALS environment variable to making use of Google's metadata API if running within Google Container Engine.
This commit is contained in:
parent
54c6837ec4
commit
0dfdc11ed9
5 changed files with 12 additions and 46 deletions
|
@ -440,18 +440,6 @@ func parseConfig(loc location.Location, opts options.Options) (interface{}, erro
|
||||||
cfg.ProjectID = os.Getenv("GOOGLE_PROJECT_ID")
|
cfg.ProjectID = os.Getenv("GOOGLE_PROJECT_ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.JSONKeyPath == "" {
|
|
||||||
if path := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"); path != "" {
|
|
||||||
// Check read access
|
|
||||||
if _, err := ioutil.ReadFile(path); err != nil {
|
|
||||||
return nil, errors.Fatalf("Failed to read google credential from file %v: %v", path, err)
|
|
||||||
}
|
|
||||||
cfg.JSONKeyPath = path
|
|
||||||
} else {
|
|
||||||
return nil, errors.Fatal("No credential file path is set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
|
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,13 @@ import (
|
||||||
"github.com/restic/restic/internal/options"
|
"github.com/restic/restic/internal/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config contains all configuration necessary to connect to a Google Cloud
|
// Config contains all configuration necessary to connect to a Google Cloud Storage
|
||||||
// Storage bucket.
|
// bucket. We use Google's default application credentials to acquire an access token, so
|
||||||
|
// we don't require that calling code supply any authentication material here.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ProjectID string
|
ProjectID string
|
||||||
JSONKeyPath string
|
Bucket string
|
||||||
Bucket string
|
Prefix string
|
||||||
Prefix string
|
|
||||||
|
|
||||||
Connections uint `option:"connections" help:"set a limit for the number of concurrent connections (default: 20)"`
|
Connections uint `option:"connections" help:"set a limit for the number of concurrent connections (default: 20)"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,6 @@ import (
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"golang.org/x/oauth2/google"
|
"golang.org/x/oauth2/google"
|
||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
storage "google.golang.org/api/storage/v1"
|
storage "google.golang.org/api/storage/v1"
|
||||||
|
@ -43,30 +40,12 @@ type Backend struct {
|
||||||
// Ensure that *Backend implements restic.Backend.
|
// Ensure that *Backend implements restic.Backend.
|
||||||
var _ restic.Backend = &Backend{}
|
var _ restic.Backend = &Backend{}
|
||||||
|
|
||||||
func getStorageService(jsonKeyPath string, rt http.RoundTripper) (*storage.Service, error) {
|
func getStorageService() (*storage.Service, error) {
|
||||||
|
client, err := google.DefaultClient(context.TODO(), storage.DevstorageReadWriteScope)
|
||||||
raw, err := ioutil.ReadFile(jsonKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "ReadFile")
|
|
||||||
}
|
|
||||||
|
|
||||||
conf, err := google.JWTConfigFromJSON(raw, storage.DevstorageReadWriteScope)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new HTTP client
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: rt,
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a now context with the HTTP client stored at the oauth2.HTTPClient key
|
|
||||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
|
|
||||||
|
|
||||||
// then pass this context to Client(), which returns a new HTTP client
|
|
||||||
client := conf.Client(ctx)
|
|
||||||
|
|
||||||
// that we can then pass to New()
|
|
||||||
service, err := storage.New(client)
|
service, err := storage.New(client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -80,7 +59,7 @@ const defaultListMaxItems = 1000
|
||||||
func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
|
func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||||
debug.Log("open, config %#v", cfg)
|
debug.Log("open, config %#v", cfg)
|
||||||
|
|
||||||
service, err := getStorageService(cfg.JSONKeyPath, rt)
|
service, err := getStorageService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "getStorageService")
|
return nil, errors.Wrap(err, "getStorageService")
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,6 @@ func newGSTestSuite(t testing.TB) *test.Suite {
|
||||||
|
|
||||||
cfg := gscfg.(gs.Config)
|
cfg := gscfg.(gs.Config)
|
||||||
cfg.ProjectID = os.Getenv("RESTIC_TEST_GS_PROJECT_ID")
|
cfg.ProjectID = os.Getenv("RESTIC_TEST_GS_PROJECT_ID")
|
||||||
cfg.JSONKeyPath = os.Getenv("RESTIC_TEST_GS_APPLICATION_CREDENTIALS")
|
|
||||||
cfg.Prefix = fmt.Sprintf("test-%d", time.Now().UnixNano())
|
cfg.Prefix = fmt.Sprintf("test-%d", time.Now().UnixNano())
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
},
|
},
|
||||||
|
@ -88,8 +87,8 @@ func TestBackendGS(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
vars := []string{
|
vars := []string{
|
||||||
|
"GOOGLE_APPLICATION_CREDENTIALS",
|
||||||
"RESTIC_TEST_GS_PROJECT_ID",
|
"RESTIC_TEST_GS_PROJECT_ID",
|
||||||
"RESTIC_TEST_GS_APPLICATION_CREDENTIALS",
|
|
||||||
"RESTIC_TEST_GS_REPOSITORY",
|
"RESTIC_TEST_GS_REPOSITORY",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,8 +105,8 @@ func TestBackendGS(t *testing.T) {
|
||||||
|
|
||||||
func BenchmarkBackendGS(t *testing.B) {
|
func BenchmarkBackendGS(t *testing.B) {
|
||||||
vars := []string{
|
vars := []string{
|
||||||
|
"GOOGLE_APPLICATION_CREDENTIALS",
|
||||||
"RESTIC_TEST_GS_PROJECT_ID",
|
"RESTIC_TEST_GS_PROJECT_ID",
|
||||||
"RESTIC_TEST_GS_APPLICATION_CREDENTIALS",
|
|
||||||
"RESTIC_TEST_GS_REPOSITORY",
|
"RESTIC_TEST_GS_REPOSITORY",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -183,7 +183,7 @@ func (env *TravisEnvironment) RunTests() error {
|
||||||
|
|
||||||
env.env["GOPATH"] = os.Getenv("GOPATH")
|
env.env["GOPATH"] = os.Getenv("GOPATH")
|
||||||
if env.gcsCredentialsFile != "" {
|
if env.gcsCredentialsFile != "" {
|
||||||
env.env["RESTIC_TEST_GS_APPLICATION_CREDENTIALS"] = env.gcsCredentialsFile
|
env.env["GOOGLE_APPLICATION_CREDENTIALS"] = env.gcsCredentialsFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure that the following tests cannot be silently skipped on Travis
|
// ensure that the following tests cannot be silently skipped on Travis
|
||||||
|
|
Loading…
Reference in a new issue