Skip to content

Commit

Permalink
Add an experimental googleclientauth extension (#703)
Browse files Browse the repository at this point in the history
* add gcp client auth extension
  • Loading branch information
dashpole authored Aug 31, 2023
1 parent 1f58cfd commit e0f6140
Show file tree
Hide file tree
Showing 14 changed files with 1,016 additions and 0 deletions.
53 changes: 53 additions & 0 deletions extension/googleclientauthextension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Authenticator - Google Client Credentials

| Status | |
| ------------- |-----------|
| Stability | [alpha] |

[alpha]: https://github.com/open-telemetry/opentelemetry-collector#alpha

This extension provides Google OAuth2 Client Credentials and Metadata for gRPC and http based exporters.

The authenticator type has to be set to `googleclientauth`.

## Configuration

```yaml
extensions:
googleclientauth:

receivers:
otlp:
protocols:
grpc:

exporters:
otlp/withauth:
endpoint: 0.0.0.0:5000
ca_file: /tmp/certs/ca.pem
auth:
authenticator: googleclientauth

service:
extensions: [googleclientauth]
pipelines:
metrics:
receivers: [otlp]
processors: []
exporters: [otlp/withauth]
```
Following are the configuration fields:
- **project** - The Google Cloud Project telemetry is sent to if the gcp.project.id resource attribute is not set. If unspecified, this is determined using application default credentials.
- [**scopes**](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3) - The oauth 2.0 scopes requested by the extension.
- [**quota_project**](https://cloud.google.com/apis/docs/system-parameters) - The project for quota and billing purposes. The caller must have serviceusage.services.use permission on the project.
## Building into a Collector
This extension is not included in any collector distributions today. To build a collector with this component, add the following to your collector builder configuration:
```yaml
extensions:
- import: github.com/GoogleCloudPlatform/opentelemetry-operations-go/extension/googleclientauthextension
gomod: github.com/GoogleCloudPlatform/opentelemetry-operations-go/extension/googleclientauthextension v0.43.0
```
61 changes: 61 additions & 0 deletions extension/googleclientauthextension/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package googleclientauthextension // import "github.com/GoogleCloudPlatform/opentelemetry-operations-go/extension/googleclientauthextension"

import (
"go.opentelemetry.io/collector/component"
)

// Config stores the configuration for GCP Client Credentials.
type Config struct {
// Project is the project telemetry is sent to if the gcp.project.id
// resource attribute is not set. If unspecified, this is determined using
// application default credentials.
Project string `mapstructure:"project"`

// QuotaProject specifies a project for quota and billing purposes. The
// caller must have serviceusage.services.use permission on the project.
//
// For more information please read:
// https://cloud.google.com/apis/docs/system-parameters
QuotaProject string `mapstructure:"quota_project"`

// Scope specifies optional requested permissions.
// See https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
Scopes []string `mapstructure:"scopes,omitempty"`

// TODO: Support impersonation, similar to what exists in the googlecloud collector exporter.
}

var _ component.Config = (*Config)(nil)

// Validate checks if the extension configuration is valid.
func (cfg *Config) Validate() error {
return nil
}

// defaultScopes are the scopes required for writing logs, metrics, and traces.
var defaultScopes = []string{
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring.write",
"https://www.googleapis.com/auth/trace.append",
}

func createDefaultConfig() component.Config {
return &Config{
Scopes: defaultScopes,
}
}
57 changes: 57 additions & 0 deletions extension/googleclientauthextension/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package googleclientauthextension // import "github.com/GoogleCloudPlatform/opentelemetry-operations-go/extension/googleclientauthextension"

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap/confmaptest"

"github.com/GoogleCloudPlatform/opentelemetry-operations-go/extension/googleclientauthextension/internal/metadata"
)

func TestLoadConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()

sub, err := cm.Sub(component.NewIDWithName(metadata.Type, "").String())
require.NoError(t, err)
require.NoError(t, component.UnmarshalConfig(sub, cfg))

assert.Equal(t, cfg.(*Config), factory.CreateDefaultConfig().(*Config))

sub, err = cm.Sub(component.NewIDWithName(metadata.Type, "customname").String())
require.NoError(t, err)
require.NoError(t, component.UnmarshalConfig(sub, cfg))

assert.Equal(t,
&Config{
Project: "my-project",
Scopes: []string{"https://www.something.com/hello", "https://www.something.com/world"},
QuotaProject: "other-project",
},
cfg,
)
}

func TestValidate(t *testing.T) {
assert.NoError(t, NewFactory().CreateDefaultConfig().(*Config).Validate())
}
17 changes: 17 additions & 0 deletions extension/googleclientauthextension/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package googleclientauthextension provides gRPC and HTTP credentials and metadata
// using Application Default Credentials: https://cloud.google.com/docs/authentication#adc
package googleclientauthextension // import "github.com/GoogleCloudPlatform/opentelemetry-operations-go/extension/googleclientauthextension"
88 changes: 88 additions & 0 deletions extension/googleclientauthextension/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package googleclientauthextension // import "github.com/GoogleCloudPlatform/opentelemetry-operations-go/extension/googleclientauthextension"

import (
"context"
"encoding/json"
"errors"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/auth"
"golang.org/x/oauth2/google"
"google.golang.org/grpc/credentials/oauth"

"github.com/GoogleCloudPlatform/opentelemetry-operations-go/extension/googleclientauthextension/internal/metadata"
)

// NewFactory creates a factory for the GCP Auth extension.
func NewFactory() extension.Factory {
return extension.NewFactory(
metadata.Type,
createDefaultConfig,
createExtension,
metadata.ExtensionStability,
)
}

func createExtension(ctx context.Context, set extension.CreateSettings, cfg component.Config) (extension.Extension, error) {
config := cfg.(*Config)
creds, err := google.FindDefaultCredentials(ctx, config.Scopes...)
if err != nil {
return nil, err
}
if config.Project == "" {
config.Project = creds.ProjectID
}
if config.Project == "" {
return nil, errors.New("no project set in config or found with application default credentials")
}
if config.QuotaProject == "" {
config.QuotaProject = quotaProjectFromCreds(creds)
}

ca := clientAuthenticator{
TokenSource: oauth.TokenSource{TokenSource: creds.TokenSource},
config: config,
}

return auth.NewClient(
auth.WithClientRoundTripper(ca.roundTripper),
auth.WithClientPerRPCCredentials(ca.perRPCCredentials),
), nil
}

// clientAuthenticator supplies credentials from an oauth.TokenSource.
type clientAuthenticator struct {
oauth.TokenSource
config *Config
}

// quotaProjectFromCreds retrieves quota project from the credentials file.
// Based on how the google go client gets quota project:
// https://github.com/googleapis/google-api-go-client/blob/113082d14d54f188d1b6c34c652e416592fc51b5/internal/creds.go#L159
func quotaProjectFromCreds(creds *google.Credentials) string {
if creds == nil {
return ""
}
var v struct {
QuotaProject string `json:"quota_project_id"`
}
if err := json.Unmarshal(creds.JSON, &v); err != nil {
return ""
}
return v.QuotaProject
}
43 changes: 43 additions & 0 deletions extension/googleclientauthextension/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package googleclientauthextension // import "github.com/GoogleCloudPlatform/opentelemetry-operations-go/extension/googleclientauthextension"

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/extension"
)

func TestCreateDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
assert.NotNil(t, cfg, "failed to create default config")
assert.NoError(t, componenttest.CheckConfigStruct(cfg))
}

func TestNewFactory(t *testing.T) {
f := NewFactory()
assert.NotNil(t, f)
}

func TestCreateExtension(t *testing.T) {
t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "testdata/fake_creds.json")
ext, err := NewFactory().CreateExtension(context.Background(), extension.CreateSettings{}, createDefaultConfig())
assert.NotNil(t, ext)
assert.NoError(t, err)
}
48 changes: 48 additions & 0 deletions extension/googleclientauthextension/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module github.com/GoogleCloudPlatform/opentelemetry-operations-go/extension/googleclientauthextension

go 1.19

require (
github.com/stretchr/testify v1.8.4
go.opentelemetry.io/collector/component v0.82.0
go.opentelemetry.io/collector/confmap v0.82.0
go.opentelemetry.io/collector/extension v0.82.0
go.opentelemetry.io/collector/extension/auth v0.82.0
golang.org/x/oauth2 v0.10.0
google.golang.org/grpc v1.57.0
)

require (
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/knadh/koanf v1.5.0 // indirect
github.com/knadh/koanf/v2 v2.0.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.82.0 // indirect
go.opentelemetry.io/collector/featuregate v1.0.0-rcv0014 // indirect
go.opentelemetry.io/collector/pdata v1.0.0-rcv0014 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/otel/trace v1.16.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.25.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

retract (
v0.76.2
v0.76.1
v0.65.0
)
Loading

0 comments on commit e0f6140

Please sign in to comment.