feat: Google API Auth (#11084)

This commit is contained in:
crflanigan 2022-05-24 18:33:02 -05:00 committed by GitHub
parent 912e3362d2
commit 7d2016b84c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 124 additions and 2 deletions

View File

@ -2,10 +2,12 @@ package httpconfig
import (
"context"
"fmt"
"net/http"
"time"
"github.com/benbjohnson/clock"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/common/cookie"
@ -30,12 +32,12 @@ type HTTPClientConfig struct {
func (h *HTTPClientConfig) CreateClient(ctx context.Context, log telegraf.Logger) (*http.Client, error) {
tlsCfg, err := h.ClientConfig.TLSConfig()
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to set TLS config: %w", err)
}
prox, err := h.HTTPProxy.Proxy()
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to set proxy: %w", err)
}
transport := &http.Transport{

View File

@ -28,6 +28,9 @@ format by default.
# token_url = "https://indentityprovider/oauth2/v1/token"
# scopes = ["urn:opc:idm:__myscopes__"]
## Goole API Auth
# google_application_credentials = "/etc/telegraf/example_secret.json"
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
@ -104,6 +107,14 @@ format by default.
# non_retryable_statuscodes = [409, 413]
```
### Google API Auth
The `google_application_credentials` setting is used with Google Cloud APIs. It specifies the json key file. To learn about creating Google service accounts, consult Google's
[oauth2 service account documentation][create_service_account]. An example use case is a metrics proxy deployed to
Cloud Run. In this example, the service account must have the "run.routes.invoke" permission.
[create_service_account]: https://cloud.google.com/docs/authentication/production#create_service_account
### Optional Cookie Authentication Settings
The optional Cookie Authentication Settings will retrieve a cookie from the

View File

@ -19,6 +19,8 @@ import (
httpconfig "github.com/influxdata/telegraf/plugins/common/http"
"github.com/influxdata/telegraf/plugins/outputs"
"github.com/influxdata/telegraf/plugins/serializers"
"golang.org/x/oauth2"
"google.golang.org/api/idtoken"
)
const (
@ -50,6 +52,10 @@ type HTTP struct {
awsCfg *awsV2.Config
internalaws.CredentialConfig
// Google API Auth
CredentialsFile string `toml:"google_application_credentials"`
oauth2Token *oauth2.Token
}
func (h *HTTP) SetSerializer(serializer serializers.Serializer) {
@ -168,6 +174,15 @@ func (h *HTTP) writeMetric(reqBody []byte) error {
req.SetBasicAuth(h.Username, h.Password)
}
// google api auth
if h.CredentialsFile != "" {
token, err := h.getAccessToken(context.Background(), h.URL)
if err != nil {
return err
}
token.SetAuthHeader(req)
}
req.Header.Set("User-Agent", internal.ProductToken())
req.Header.Set("Content-Type", defaultContentType)
if h.ContentEncoding == "gzip" {
@ -220,3 +235,23 @@ func init() {
}
})
}
func (h *HTTP) getAccessToken(ctx context.Context, audience string) (*oauth2.Token, error) {
if h.oauth2Token.Valid() {
return h.oauth2Token, nil
}
ts, err := idtoken.NewTokenSource(ctx, audience, idtoken.WithCredentialsFile(h.CredentialsFile))
if err != nil {
return nil, fmt.Errorf("error creating oauth2 token source: %s", err)
}
token, err := ts.Token()
if err != nil {
return nil, fmt.Errorf("error fetching oauth2 token: %s", err)
}
h.oauth2Token = token
return token, nil
}

View File

@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
@ -526,6 +527,79 @@ func TestOAuthClientCredentialsGrant(t *testing.T) {
}
}
func TestOAuthAuthorizationCodeGrant(t *testing.T) {
ts := httptest.NewServer(http.NotFoundHandler())
defer ts.Close()
u, err := url.Parse(fmt.Sprintf("http://%s", ts.Listener.Addr().String()))
require.NoError(t, err)
tmpDir := t.TempDir()
tmpFile, err := os.CreateTemp(tmpDir, "test_key_file")
require.NoError(t, err)
tmpTokenURI := u.String() + "/token"
data := []byte(fmt.Sprintf("{\n \"type\": \"service_account\",\n \"project_id\": \"my-project\",\n \"private_key_id\": \"223423436436453645363456\",\n \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIICXAIBAAKBgQDX7Plvu0MJtA9TrusYtQnAogsdiYJZd9wfFIjH5FxE3SWJ4KAIE+yRWRqcqX8XnpieQLaNsfXhDPWLkWngTDydk4NO/jlAQk0e6+9+NeiZ2ViIHmtXERb9CyiiWUmo+YCd69lhzSEIMK9EPBSDHQTgQMtEfGak03G5rx3MCakE1QIDAQABAoGAOjRU4Lt3zKvO3d3u3ZAfet+zY1jn3DolCfO9EzUJcj6ymcIFIWhNgrikJcrCyZkkxrPnAbcQ8oNNxTuDcMTcKZbnyUnlQj5NtVuty5Q+zgf3/Q2pRhaE+TwrpOJ+ETtVp9R/PrPN2NC5wPo289fPNWFYkd4DPbdWZp5AJHz1XYECQQD3kKpinJxMYp9FQ1Qj1OkxGln0KPgdqRYjjW/rXI4/hUodfg+xXWHPFSGj3AgEjQIvuengbOAeH3qowF1uxVTlAkEA30hXM3EbboMCDQzNRNkkV9EiZ0MZXhj1aIGl+sQZOmOeFdcdjGkDdsA42nmaYqXCD9KAvc+S/tGJaa0Qg0VhMQJAb2+TAqh0Qn3yK39PFIH2JcAy1ZDLfq5p5L75rfwPm9AnuHbSIYhjSo+8gMG+ai3+2fTZrcfUajrJP8S3SfFRcQJBANQQPOHatxcKzlPeqMaPBXlyY553mAxK4CnVmPLGdL+EBYzwtlu5EVUj09uMSxkOHXYxk5yzHQVvtXbsrBZBOsECQBJLlkMjJmXrIIdLPmHQWL3bm9MMg1PqzupSEwz6cyrGuIIm/X91pDyxCHaKYWp38FXBkYAgohI8ow5/sgRvU5w=\\n-----END PRIVATE KEY-----\\n\",\n \"client_email\": \"test-service-account-email@example.iam.gserviceaccount.com\",\n \"client_id\": \"110300009813738675309\",\n \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n \"token_uri\": \"%s\",\n \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/test-service-account-email@example.iam.gserviceaccount.com\"\n}", tmpTokenURI))
_, err = tmpFile.Write(data)
require.NoError(t, err)
require.NoError(t, tmpFile.Close())
const token = "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg2NzUzMDliMjJiMDFiZTU2YzIxM2M5ODU0MGFiNTYzYmZmNWE1OGMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwOi8vMTI3LjAuMC4xOjU4MDI1LyIsImF6cCI6InRlc3Qtc2VydmljZS1hY2NvdW50LWVtYWlsQGV4YW1wbGUuY29tIiwiZW1haWwiOiJ0ZXN0LXNlcnZpY2UtYWNjb3VudC1lbWFpbEBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjk0NjY4NDgwMCwiaWF0Ijo5NDY2ODEyMDAsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMudGVzdC5jb20iLCJzdWIiOiIxMTAzMDAwMDk4MTM3Mzg2NzUzMDkifQ.qi2LsXP2o6nl-rbYKUlHAgTBY0QoU7Nhty5NGR4GMdc8OoGEPW-vlD0WBSaKSr11vyFcIO4ftFDWXElo9Ut-AIQPKVxinsjHIU2-LoIATgI1kyifFLyU_pBecwcI4CIXEcDK5wEkfonWFSkyDZHBeZFKbJXlQXtxj0OHvQ-DEEepXLuKY6v3s4U6GyD9_ppYUy6gzDZPYUbfPfgxCj_Jbv6qkLU0DiZ7F5-do6X6n-qkpgCRLTGHcY__rn8oe8_pSimsyJEeY49ZQ5lj4mXkVCwgL9bvL1_eW1p6sgbHaBnPKVPbM7S1_cBmzgSonm__qWyZUxfDgNdigtNsvzBQTg"
tests := []struct {
name string
plugin *HTTP
handler TestHandlerFunc
tokenHandler TestHandlerFunc
}{
{
name: "no credentials file",
plugin: &HTTP{
URL: u.String(),
},
handler: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
require.Len(t, r.Header["Authorization"], 0)
w.WriteHeader(http.StatusOK)
},
},
{
name: "success",
plugin: &HTTP{
URL: u.String() + "/write",
CredentialsFile: tmpFile.Name(),
},
tokenHandler: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
authHeader := fmt.Sprintf(`{"id_token":"%s"}`, token)
_, err = w.Write([]byte(authHeader))
require.NoError(t, err)
},
handler: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
require.Equal(t, []string{"Bearer " + token}, r.Header["Authorization"])
w.WriteHeader(http.StatusOK)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/write":
tt.handler(t, w, r)
case "/token":
tt.tokenHandler(t, w, r)
}
})
tt.plugin.SetSerializer(influx.NewSerializer())
require.NoError(t, tt.plugin.Connect())
require.NoError(t, tt.plugin.Write([]telegraf.Metric{getMetric()}))
require.NoError(t, err)
})
}
}
func TestDefaultUserAgent(t *testing.T) {
ts := httptest.NewServer(http.NotFoundHandler())
defer ts.Close()