mirror of
https://github.com/grafana/grafana.git
synced 2025-01-02 12:17:01 -06:00
GoogleCloudMonitoring: use grafana-google-sdk-go for auth (#40490)
This commit is contained in:
parent
624d7631e6
commit
97df4a57f4
1
go.mod
1
go.mod
@ -186,6 +186,7 @@ require (
|
||||
github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f // indirect
|
||||
github.com/grafana/grafana-google-sdk-go v0.0.0-20211019132340-3ff525a010d5
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -1204,6 +1204,8 @@ github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5J
|
||||
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/grafana/grafana-aws-sdk v0.7.0 h1:D+Lhxi3P/7vpyDHUK/fdX9bL2mRz8hLG04ucNf1E02o=
|
||||
github.com/grafana/grafana-aws-sdk v0.7.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
|
||||
github.com/grafana/grafana-google-sdk-go v0.0.0-20211019132340-3ff525a010d5 h1:o7w/t0nLNfkERMdj09U0h3Fl63z8ws1CxwiImeUKLIk=
|
||||
github.com/grafana/grafana-google-sdk-go v0.0.0-20211019132340-3ff525a010d5/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.114.0 h1:9I55IXw7mOT71tZ/pdqCaWGz8vxfz31CXjaDtBV9ZBo=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
|
||||
|
@ -105,8 +105,7 @@ func getTokenProvider(ctx context.Context, cfg *setting.Cfg, ds DSInfo, pluginRo
|
||||
if jwtTokenAuth == nil {
|
||||
return nil, fmt.Errorf("'jwtTokenAuth' not configured for authentication type '%s'", authType)
|
||||
}
|
||||
provider := newGceAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth)
|
||||
return provider, nil
|
||||
return newGceAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth), nil
|
||||
|
||||
case "jwt":
|
||||
if jwtTokenAuth == nil {
|
||||
|
@ -2,43 +2,31 @@ package pluginproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
googletokenprovider "github.com/grafana/grafana-google-sdk-go/pkg/tokenprovider"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
type gceAccessTokenProvider struct {
|
||||
datasourceId int64
|
||||
datasourceUpdated time.Time
|
||||
ctx context.Context
|
||||
route *plugins.AppPluginRoute
|
||||
authParams *plugins.JwtTokenAuth
|
||||
source googletokenprovider.TokenProvider
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func newGceAccessTokenProvider(ctx context.Context, ds DSInfo, pluginRoute *plugins.AppPluginRoute,
|
||||
authParams *plugins.JwtTokenAuth) *gceAccessTokenProvider {
|
||||
cfg := googletokenprovider.Config{
|
||||
RoutePath: pluginRoute.Path,
|
||||
RouteMethod: pluginRoute.Method,
|
||||
DataSourceID: ds.ID,
|
||||
DataSourceUpdated: ds.Updated,
|
||||
Scopes: authParams.Scopes,
|
||||
}
|
||||
return &gceAccessTokenProvider{
|
||||
datasourceId: ds.ID,
|
||||
datasourceUpdated: ds.Updated,
|
||||
ctx: ctx,
|
||||
route: pluginRoute,
|
||||
authParams: authParams,
|
||||
source: googletokenprovider.NewGceAccessTokenProvider(cfg),
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *gceAccessTokenProvider) GetAccessToken() (string, error) {
|
||||
tokenSrc, err := google.DefaultTokenSource(provider.ctx, provider.authParams.Scopes...)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get default token from meta data server", "error", err)
|
||||
return "", err
|
||||
} else {
|
||||
token, err := tokenSrc.Token()
|
||||
if err != nil {
|
||||
logger.Error("Failed to get default access token from meta data server", "error", err)
|
||||
return "", err
|
||||
} else {
|
||||
return token.AccessToken, nil
|
||||
}
|
||||
}
|
||||
return provider.source.GetAccessToken(provider.ctx)
|
||||
}
|
||||
|
@ -2,95 +2,46 @@ package pluginproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
googletokenprovider "github.com/grafana/grafana-google-sdk-go/pkg/tokenprovider"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
var (
|
||||
oauthJwtTokenCache = oauthJwtTokenCacheType{
|
||||
cache: map[string]*oauth2.Token{},
|
||||
}
|
||||
)
|
||||
|
||||
type oauthJwtTokenCacheType struct {
|
||||
cache map[string]*oauth2.Token
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
type jwtAccessTokenProvider struct {
|
||||
datasourceId int64
|
||||
datasourceUpdated time.Time
|
||||
ctx context.Context
|
||||
route *plugins.AppPluginRoute
|
||||
authParams *plugins.JwtTokenAuth
|
||||
source googletokenprovider.TokenProvider
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func newJwtAccessTokenProvider(ctx context.Context, ds DSInfo, pluginRoute *plugins.AppPluginRoute,
|
||||
authParams *plugins.JwtTokenAuth) *jwtAccessTokenProvider {
|
||||
jwtConf := &googletokenprovider.JwtTokenConfig{}
|
||||
if val, ok := authParams.Params["client_email"]; ok {
|
||||
jwtConf.Email = val
|
||||
}
|
||||
|
||||
if val, ok := authParams.Params["private_key"]; ok {
|
||||
jwtConf.PrivateKey = []byte(val)
|
||||
}
|
||||
|
||||
if val, ok := authParams.Params["token_uri"]; ok {
|
||||
jwtConf.URI = val
|
||||
}
|
||||
|
||||
cfg := googletokenprovider.Config{
|
||||
RoutePath: pluginRoute.Path,
|
||||
RouteMethod: pluginRoute.Method,
|
||||
DataSourceID: ds.ID,
|
||||
DataSourceUpdated: ds.Updated,
|
||||
Scopes: authParams.Scopes,
|
||||
JwtTokenConfig: jwtConf,
|
||||
}
|
||||
|
||||
return &jwtAccessTokenProvider{
|
||||
datasourceId: ds.ID,
|
||||
datasourceUpdated: ds.Updated,
|
||||
ctx: ctx,
|
||||
route: pluginRoute,
|
||||
authParams: authParams,
|
||||
source: googletokenprovider.NewJwtAccessTokenProvider(cfg),
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *jwtAccessTokenProvider) GetAccessToken() (string, error) {
|
||||
oauthJwtTokenCache.Lock()
|
||||
defer oauthJwtTokenCache.Unlock()
|
||||
if cachedToken, found := oauthJwtTokenCache.cache[provider.getAccessTokenCacheKey()]; found {
|
||||
if cachedToken.Expiry.After(timeNow().Add(time.Second * 10)) {
|
||||
logger.Debug("Using token from cache")
|
||||
return cachedToken.AccessToken, nil
|
||||
}
|
||||
}
|
||||
|
||||
conf := &jwt.Config{}
|
||||
|
||||
if val, ok := provider.authParams.Params["client_email"]; ok {
|
||||
conf.Email = val
|
||||
}
|
||||
|
||||
if val, ok := provider.authParams.Params["private_key"]; ok {
|
||||
conf.PrivateKey = []byte(val)
|
||||
}
|
||||
|
||||
if val, ok := provider.authParams.Params["token_uri"]; ok {
|
||||
conf.TokenURL = val
|
||||
}
|
||||
|
||||
conf.Scopes = provider.authParams.Scopes
|
||||
|
||||
token, err := getTokenSource(conf, provider.ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
oauthJwtTokenCache.cache[provider.getAccessTokenCacheKey()] = token
|
||||
|
||||
logger.Info("Got new access token", "ExpiresOn", token.Expiry)
|
||||
|
||||
return token.AccessToken, nil
|
||||
}
|
||||
|
||||
// getTokenSource gets a token source.
|
||||
// Stubbable by tests.
|
||||
var getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
tokenSrc := conf.TokenSource(ctx)
|
||||
token, err := tokenSrc.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (provider *jwtAccessTokenProvider) getAccessTokenCacheKey() string {
|
||||
return fmt.Sprintf("%v_%v_%v_%v", provider.datasourceId, provider.datasourceUpdated.Unix(), provider.route.Path, provider.route.Method)
|
||||
return provider.source.GetAccessToken(provider.ctx)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package pluginproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -13,105 +12,12 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
var (
|
||||
token map[string]interface{}
|
||||
)
|
||||
|
||||
func TestAccessToken_pluginWithJWTTokenAuthRoute(t *testing.T) {
|
||||
pluginRoute := &plugins.AppPluginRoute{
|
||||
Path: "pathwithjwttoken1",
|
||||
URL: "https://api.jwt.io/some/path",
|
||||
Method: "GET",
|
||||
JwtTokenAuth: &plugins.JwtTokenAuth{
|
||||
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||
Scopes: []string{
|
||||
"https://www.testapi.com/auth/monitoring.read",
|
||||
"https://www.testapi.com/auth/cloudplatformprojects.readonly",
|
||||
},
|
||||
Params: map[string]string{
|
||||
"token_uri": "{{.JsonData.tokenUri}}",
|
||||
"client_email": "{{.JsonData.clientEmail}}",
|
||||
"private_key": "{{.SecureJsonData.privateKey}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
authParams := &plugins.JwtTokenAuth{
|
||||
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||
Scopes: []string{
|
||||
"https://www.testapi.com/auth/monitoring.read",
|
||||
"https://www.testapi.com/auth/cloudplatformprojects.readonly",
|
||||
},
|
||||
Params: map[string]string{
|
||||
"token_uri": "login.url.com/token",
|
||||
"client_email": "test@test.com",
|
||||
"private_key": "testkey",
|
||||
},
|
||||
}
|
||||
|
||||
setUp := func(t *testing.T, fn func(*jwt.Config, context.Context) (*oauth2.Token, error)) {
|
||||
origFn := getTokenSource
|
||||
t.Cleanup(func() {
|
||||
getTokenSource = origFn
|
||||
})
|
||||
|
||||
getTokenSource = fn
|
||||
}
|
||||
|
||||
ds := DSInfo{ID: 1, Updated: time.Now()}
|
||||
|
||||
t.Run("should fetch token using JWT private key", func(t *testing.T) {
|
||||
setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
return &oauth2.Token{AccessToken: "abc"}, nil
|
||||
})
|
||||
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams)
|
||||
token, err := provider.GetAccessToken()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "abc", token)
|
||||
})
|
||||
|
||||
t.Run("should set JWT config values", func(t *testing.T) {
|
||||
setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
assert.Equal(t, "test@test.com", conf.Email)
|
||||
assert.Equal(t, []byte("testkey"), conf.PrivateKey)
|
||||
assert.Equal(t, 2, len(conf.Scopes))
|
||||
assert.Equal(t, "https://www.testapi.com/auth/monitoring.read", conf.Scopes[0])
|
||||
assert.Equal(t, "https://www.testapi.com/auth/cloudplatformprojects.readonly", conf.Scopes[1])
|
||||
assert.Equal(t, "login.url.com/token", conf.TokenURL)
|
||||
|
||||
return &oauth2.Token{AccessToken: "abc"}, nil
|
||||
})
|
||||
|
||||
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams)
|
||||
_, err := provider.GetAccessToken()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("should use cached token on second call", func(t *testing.T) {
|
||||
setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
return &oauth2.Token{
|
||||
AccessToken: "abc",
|
||||
Expiry: time.Now().Add(1 * time.Minute)}, nil
|
||||
})
|
||||
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams)
|
||||
token1, err := provider.GetAccessToken()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "abc", token1)
|
||||
|
||||
getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
return &oauth2.Token{AccessToken: "error: cache not used"}, nil
|
||||
}
|
||||
token2, err := provider.GetAccessToken()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "abc", token2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccessToken_pluginWithTokenAuthRoute(t *testing.T) {
|
||||
apiHandler := http.NewServeMux()
|
||||
server := httptest.NewServer(apiHandler)
|
||||
|
Loading…
Reference in New Issue
Block a user