Skip to content

Commit

Permalink
LumigoAuth v1
Browse files Browse the repository at this point in the history
Introduce an extension called `lumigoauth` that ensures that
HTTP requests processed by the associated receivers come in with
a `Authentication: LumigoToken <token>` header, and make the value
of the token available in the `auth` context for later processing.
  • Loading branch information
Michele Mancioppi committed Sep 18, 2022
1 parent e6ec2dc commit 530b7af
Show file tree
Hide file tree
Showing 16 changed files with 766 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ updates:
directory: "/extension/jaegerremotesampling"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/extension/lumigoauthextension"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/extension/oauth2clientauthextension"
schedule:
Expand Down
1 change: 1 addition & 0 deletions extension/lumigoauthextension/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
57 changes: 57 additions & 0 deletions extension/lumigoauthextension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Lumigo Authenticator

| Status | |
| ------------------------ |----------------------|
| Stability | [alpha] |
| Supported pipeline types | trace |
| Distributions | [lumigo] |

This extension implements the `configauth.ServerAuthenticator` interface to require clients to send a Lumigo token with a header with the following structure:

```
Authentication: LumigoToken <token>
```

If the `Authentication` header is not provided, ot its structure does not match the expected pattern, a `401` is returned.

The extension does not yet perform validation of the token itself.

The token is available in the `auth` context with the `auth.lumigo-token` key.
The raw value of the `Authentication` header is available as `auth.raw`.

## Configuration

The following configuration includes the setting of the token as an additional resource attribute called `lumigoToken` via the [`resourceprocessor`](../../processor/resourceprocessor/) processor:

```yaml
extensions:
lumigoauth: {}

receivers:
otlp:
protocols:
http:
auth:
authenticator: lumigoauth

processors:
resource/lumigo_token_from_auth:
attributes:
- key: lumigoToken
action: upsert
from_context: auth.lumigo-token

exporters:
otlp:

service:
extensions: [lumigoauth]
pipelines:
traces:
receivers: [otlp]
processors: [resource/lumigo_token_from_auth]
exporters: [otlp]
```
[alpha]:https://github.com/open-telemetry/opentelemetry-collector#alpha
[lumigo]:https://github.com/lumigo-io/opentelemetry-collector-contrib
27 changes: 27 additions & 0 deletions extension/lumigoauthextension/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright The OpenTelemetry Authors
//
// 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 lumigoauthextension // import "github.com/lumigo-io/opentelemetry-collector-contrib/extension/lumigoauthextension"

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

type Config struct {
config.ExtensionSettings `mapstructure:",squash"`
}

func (cfg *Config) Validate() error {
return nil
}
34 changes: 34 additions & 0 deletions extension/lumigoauthextension/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright The OpenTelemetry Authors
//
// 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 lumigoauthextension

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/service/servicetest"
)

func TestLoadConfig(t *testing.T) {
factories, err := componenttest.NopFactories()
require.NoError(t, err)

factory := NewFactory()
factories.Extensions[typeStr] = factory
_, err = servicetest.LoadConfigAndValidate(filepath.Join("testdata", "valid_config.yml"), factories)
require.NoError(t, err)
}
129 changes: 129 additions & 0 deletions extension/lumigoauthextension/extension.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright The OpenTelemetry Authors
//
// 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 lumigoauthextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/lumigoauthextension"

import (
"context"
"errors"
"strings"

"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configauth"
"go.uber.org/zap"
)

var (
errNoAuth = errors.New("the Authorization header is missing")
errInvalidSchemePrefix = errors.New("the Authorization header does not have the 'LumigoToken <token>' structure")
)

type lumigoAuth struct {
logger *zap.Logger
}

func newServerAuthExtension(cfg *Config, logger *zap.Logger) (configauth.ServerAuthenticator, error) {
la := lumigoAuth{
logger: logger,
}
return configauth.NewServerAuthenticator(
configauth.WithStart(la.serverStart),
configauth.WithAuthenticate(la.authenticate),
), nil
}

func (la *lumigoAuth) serverStart(ctx context.Context, host component.Host) error {
return nil
}

func (la *lumigoAuth) authenticate(ctx context.Context, headers map[string][]string) (context.Context, error) {
auth := la.getAuthHeader(headers)
if auth == "" {
return ctx, errNoAuth
}

authData, err := la.parseLumigoToken(auth)
if err != nil {
return ctx, err
}

cl := client.FromContext(ctx)
cl.Auth = authData
return client.NewContext(ctx, cl), nil
}

func (la *lumigoAuth) getAuthHeader(h map[string][]string) string {
const (
canonicalHeaderKey = "Authorization"
metadataKey = "authorization"
)

authHeaders, ok := h[canonicalHeaderKey]

if !ok {
authHeaders, ok = h[metadataKey]
}

if !ok {
for k, v := range h {
if strings.EqualFold(k, metadataKey) {
authHeaders = v
break
}
}
}

if len(authHeaders) == 0 {
return ""
}

return authHeaders[0]
}

func (la *lumigoAuth) parseLumigoToken(auth string) (*authData, error) {
const prefix = "LumigoToken "
if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) {
return nil, errInvalidSchemePrefix
}

token := auth[len(prefix):]

return &authData{
raw: auth,
token: token,
}, nil
}

var _ client.AuthData = (*authData)(nil)

type authData struct {
raw string
token string
}

func (a *authData) GetAttribute(name string) interface{} {
switch name {
case "lumigo-token":
return a.token
case "raw":
return a.raw
default:
return nil
}
}

func (*authData) GetAttributeNames() []string {
return []string{"raw", "lumigo-token"}
}
54 changes: 54 additions & 0 deletions extension/lumigoauthextension/extension_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright The OpenTelemetry Authors
//
// 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.

// nolint:errcheck
package lumigoauthextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension"

import (
"context"
"testing"

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

func TestLumigoAuth_NoHeader(t *testing.T) {
ext, err := newServerAuthExtension(&Config{})
require.NoError(t, err)
_, err = ext.Authenticate(context.Background(), map[string][]string{})
assert.Equal(t, errNoAuth, err)
}

func TestLumigoAuth_InvalidPrefix(t *testing.T) {
ext, err := newServerAuthExtension(&Config{})
require.NoError(t, err)
_, err = ext.Authenticate(context.Background(), map[string][]string{"authorization": {"Bearer token"}})
assert.Equal(t, errInvalidSchemePrefix, err)
}

func TestLumigoAuth_SupportedHeaders(t *testing.T) {
ext, err := newServerAuthExtension(&Config{})
require.NoError(t, err)
require.NoError(t, ext.Start(context.Background(), componenttest.NewNopHost()))

for _, k := range []string{
"Authorization",
"authorization",
"aUtHoRiZaTiOn",
} {
_, err = ext.Authenticate(context.Background(), map[string][]string{k: {"LumigoToken 1234567"}})
assert.NoError(t, err)
}
}
45 changes: 45 additions & 0 deletions extension/lumigoauthextension/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright The OpenTelemetry Authors
//
// 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 lumigoauthextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/lumigoauthextension"

import (
"context"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config"
)

const (
typeStr = "lumigoauth"
)

// NewFactory creates a factory for the static bearer token Authenticator extension.
func NewFactory() component.ExtensionFactory {
return component.NewExtensionFactory(
typeStr,
createDefaultConfig,
createExtension)
}

func createDefaultConfig() config.Extension {
return &Config{
ExtensionSettings: config.NewExtensionSettings(config.NewComponentID(typeStr)),
}
}

func createExtension(_ context.Context, set component.ExtensionCreateSettings, cfg config.Extension) (component.Extension, error) {
// check if config is a server auth(Htpasswd should be set)
return newServerAuthExtension(cfg.(*Config), set.Logger)
}
32 changes: 32 additions & 0 deletions extension/lumigoauthextension/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright The OpenTelemetry Authors
//
// 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 lumigoauthextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension"

import (
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/config/configtest"
)

func TestCreateDefaultConfig(t *testing.T) {
expected := &Config{
ExtensionSettings: config.NewExtensionSettings(config.NewComponentID(typeStr)),
}
actual := createDefaultConfig()
assert.Equal(t, expected, createDefaultConfig())
assert.NoError(t, configtest.CheckConfigStruct(actual))
}
Loading

0 comments on commit 530b7af

Please sign in to comment.