Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Google API Auth #11084

Merged
merged 15 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions plugins/common/http/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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{
Expand All @@ -56,7 +58,10 @@ func (h *HTTPClientConfig) CreateClient(ctx context.Context, log telegraf.Logger
Timeout: time.Duration(timeout),
}

client = h.OAuth2Config.CreateOauth2Client(ctx, client)
client, err = h.OAuth2Config.CreateOauth2Client(ctx, client)
if err != nil {
return nil, fmt.Errorf("failed to create OAuth2 client: %w", err)
}

if h.CookieAuthConfig.URL != "" {
if err := h.CookieAuthConfig.Start(client, log, clock.New()); err != nil {
Expand Down
25 changes: 23 additions & 2 deletions plugins/common/oauth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package oauth

import (
"context"
"fmt"
"net/http"

"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
"google.golang.org/api/idtoken"
)

type OAuth2Config struct {
Expand All @@ -14,9 +16,12 @@ type OAuth2Config struct {
ClientSecret string `toml:"client_secret"`
TokenURL string `toml:"token_url"`
Scopes []string `toml:"scopes"`
// Google API Auth
CredentialsFile string `toml:"credentials_file"`
AccessToken *oauth2.Token
}

func (o *OAuth2Config) CreateOauth2Client(ctx context.Context, client *http.Client) *http.Client {
func (o *OAuth2Config) CreateOauth2Client(ctx context.Context, client *http.Client) (*http.Client, error) {
if o.ClientID != "" && o.ClientSecret != "" && o.TokenURL != "" {
oauthConfig := clientcredentials.Config{
ClientID: o.ClientID,
Expand All @@ -28,5 +33,21 @@ func (o *OAuth2Config) CreateOauth2Client(ctx context.Context, client *http.Clie
client = oauthConfig.Client(ctx)
}

return client
return client, nil
}

func (o *OAuth2Config) GetAccessToken(ctx context.Context, audience string) error {
ts, err := idtoken.NewTokenSource(ctx, audience, idtoken.WithCredentialsFile(o.CredentialsFile))
if err != nil {
return fmt.Errorf("error creating oauth2 token source: %s", err)
}

token, err := ts.Token()
if err != nil {
return fmt.Errorf("error fetching oauth2 token: %s", err)
}

o.AccessToken = token

return nil
}
12 changes: 12 additions & 0 deletions plugins/outputs/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ format by default.
# token_url = "https://indentityprovider/oauth2/v1/token"
# scopes = ["urn:opc:idm:__myscopes__"]

## Goole API Auth
# credentials_file = "/etc/telegraf/example_secret.json"
crflanigan marked this conversation as resolved.
Show resolved Hide resolved

## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
Expand Down Expand Up @@ -104,6 +107,15 @@ format by default.
# non_retryable_statuscodes = [409, 413]
```

### Google API Auth

Configuring the `credentials_file` to the path to the json key allows Telegraf's http output plugin to communicate with
Google Cloud APIs. 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.
zachmares marked this conversation as resolved.
Show resolved Hide resolved

[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
Expand Down
12 changes: 12 additions & 0 deletions plugins/outputs/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,18 @@ func (h *HTTP) writeMetric(reqBody []byte) error {
req.SetBasicAuth(h.Username, h.Password)
}

// google api auth
if h.HTTPClientConfig.OAuth2Config.CredentialsFile != "" {
if !h.HTTPClientConfig.OAuth2Config.AccessToken.Valid() {
err := h.HTTPClientConfig.OAuth2Config.GetAccessToken(context.Background(), h.URL)
if err != nil {
return err
Hipska marked this conversation as resolved.
Show resolved Hide resolved
}
}

h.HTTPClientConfig.OAuth2Config.AccessToken.SetAuthHeader(req)
}
srebhan marked this conversation as resolved.
Show resolved Hide resolved

req.Header.Set("User-Agent", internal.ProductToken())
req.Header.Set("Content-Type", defaultContentType)
if h.ContentEncoding == "gzip" {
Expand Down
78 changes: 78 additions & 0 deletions plugins/outputs/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"

Expand Down Expand Up @@ -526,6 +527,83 @@ 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",
HTTPClientConfig: httpconfig.HTTPClientConfig{
OAuth2Config: oauth.OAuth2Config{
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()
Expand Down