Skip to content

Commit

Permalink
Changed pdata context to be a map
Browse files Browse the repository at this point in the history
Signed-off-by: Juraci Paixão Kröhling <juraci@kroehling.de>
  • Loading branch information
jpkrohling committed Aug 10, 2021
1 parent a5867f0 commit 87cde06
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 37 deletions.
81 changes: 60 additions & 21 deletions config/configauth/pdatacontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,87 @@ package configauth

import (
"context"
"encoding/json"
"fmt"
"reflect"

"go.opentelemetry.io/collector/model/pdata"
)

type ctxKey struct{}

type AuthContext struct {
sub string
raw string
group []string
type AuthContext interface {
Equal(other interface{}) bool
GetAttribute(attrName string) interface{}
}

func InjectAuthContext(ctx context.Context, sub, raw string, group []string) context.Context {
return context.WithValue(ctx, ctxKey{}, &AuthContext{
sub: sub,
raw: raw,
group: group,
})
func InjectAuthContext(ctx context.Context, attrs map[interface{}]interface{}) context.Context {
ac := &authC{delegate: attrs}
return context.WithValue(ctx, ctxKey{}, ac)
}

func ExtractAuthContext(ctx context.Context) (*AuthContext, bool) {
ac, ok := ctx.Value(ctxKey{}).(*AuthContext)
func ExtractAuthContext(ctx context.Context) (AuthContext, bool) {
ac, ok := ctx.Value(ctxKey{}).(*authC)
if !ok {
return nil, false
}
return ac, true
}

func InjectPDataContext(pda pdata.PDataContext, ac *AuthContext) {
func InjectPDataContext(pda pdata.PDataContext, ac AuthContext) {
pda.Set(ctxKey{}, ac)
}

func ExtractPDataContext(pda pdata.PDataContext) *AuthContext {
return pda.Get(ctxKey{}).(*AuthContext)
func ExtractPDataContext(pda pdata.PDataContext) AuthContext {
return pda.Get(ctxKey{}).(AuthContext)
}

func (ac *AuthContext) Subject() string {
return ac.sub
type authC struct {
delegate map[interface{}]interface{}
}
func (ac *AuthContext) Raw() string {
return ac.raw

func (ac authC) Equal(other interface{}) bool {
if other == nil {
return false
}
otherAuthC, ok := other.(*authC)
if !ok {
return false
}
return reflect.DeepEqual(ac.delegate, otherAuthC.delegate)
}

func (ac authC) GetAttribute(attrName string) interface{} {
return ac.delegate[attrName]
}

func (ac authC) String() string {
return fmt.Sprintf("Auth Context: %v", ac.delegate)
}
func (ac *AuthContext) Groups() []string {
return ac.group

// MarshalJSON serializes our context into a JSON blob. Note that interface{} is converted to string or []string.
func (ac *authC) MarshalJSON() ([]byte, error) {
strmap := make(map[string]interface{})
for k, v := range ac.delegate {
strmap[fmt.Sprintf("%v", k)] = v
}
return json.Marshal(strmap)
}

// UnmarshalJSON deserializes our context from a JSON blob. Note that we cannot infer the original type before the serialization,
// so all entries are either string or []string.
func (ac *authC) UnmarshalJSON(data []byte) error {
strmap := make(map[string]interface{})
if err := json.Unmarshal(data, &strmap); err != nil {
return err
}

// converts the map[string]interface{} to map[interface{}]interface{}
delegate := make(map[interface{}]interface{})
for k, v := range strmap {
delegate[k] = v
}
ac.delegate = delegate

return nil
}
93 changes: 93 additions & 0 deletions config/configauth/pdatacontext_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 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 configauth

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

func TestMarshalJSON(t *testing.T) {
for _, tt := range []struct {
name string
ac *authC
expected string
}{
{
name: "simple",
ac: &authC{
delegate: map[interface{}]interface{}{
"subject": "username",
"tenant": "acme",
},
},
expected: `{"subject":"username","tenant":"acme"}`,
},
{
name: "groups",
ac: &authC{
delegate: map[interface{}]interface{}{
"subject": "username",
"membership": []string{"dev", "ops"},
},
},
expected: `{"membership":["dev","ops"],"subject":"username"}`,
},
} {
t.Run(tt.name, func(t *testing.T) {
json, err := tt.ac.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, tt.expected, string(json))
})
}
}

func TestUnmarshalJSON(t *testing.T) {
for _, tt := range []struct {
name string
json string
expected *authC
}{
{
name: "simple",
json: `{"subject":"username","tenant":"acme"}`,
expected: &authC{
delegate: map[interface{}]interface{}{
"subject": "username",
"tenant": "acme",
},
},
},
{
name: "groups",
json: `{"membership":["dev","ops"],"subject":"username"}`,
expected: &authC{
delegate: map[interface{}]interface{}{
"subject": "username",
"membership": []interface{}{"dev", "ops"},
},
},
},
} {
t.Run(tt.name, func(t *testing.T) {
ac := &authC{}
err := json.Unmarshal([]byte(tt.json), ac)
assert.NoError(t, err)
assert.Equal(t, tt.expected, ac)
})
}
}
15 changes: 1 addition & 14 deletions exporter/loggingexporter/logging_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/internal/otlptext"
Expand All @@ -39,19 +38,7 @@ type loggingExporter struct {
}

func (s *loggingExporter) pushTraces(_ context.Context, td pdata.Traces) error {
fields := []zap.Field{
zap.Int("#spans", td.SpanCount()),
}

ac := configauth.ExtractPDataContext(td.ResourceSpans().At(0).PDataContext())
if ac != nil {
fields = append(fields, zap.String("sub", ac.Subject()))
fields = append(fields, zap.Strings("groups", ac.Groups()))
} else {
fields = append(fields, zap.String("auth", "none"))
}

s.logger.Info("TracesExporter", fields...)
s.logger.Info("TracesExporter", zap.Int("#spans", td.SpanCount()))

if !s.debug {
return nil
Expand Down
6 changes: 5 additions & 1 deletion extension/oidcauthextension/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Authenticator - OIDC

This extension implements a `configauth.ServerAuthenticator`, to be used in receivers inside the `auth` settings. The authenticator type has to be set to `oidc`.
This extension implements a `configauth.ServerAuthenticator`, to be used in receivers inside the `auth` settings. The authenticator type has to be set to `oidc`. This authenticator places the following attributes in the authentication context:

- `raw`, string, with the raw authentication token.
- `subject`, string, with the subject extracted from the token. This is typically the value for the field under `sub` in the token, or `username_claim` when specified.
- `membership`, list of string (`[]string`), with the groups extracted from the token. This is the value for the field specified via `groups_claim`, when available in the token.

## Configuration

Expand Down
8 changes: 7 additions & 1 deletion extension/oidcauthextension/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,13 @@ func (e *oidcExtension) Authenticate(ctx context.Context, headers map[string][]s
return ctx, fmt.Errorf("failed to get groups from claims in the token: %w", err)
}

return configauth.InjectAuthContext(ctx, sub, raw, groups), nil
attrs := map[interface{}]interface{}{
"raw": raw,
"subject": sub,
"membership": groups,
}

return configauth.InjectAuthContext(ctx, attrs), nil
}

// GRPCUnaryServerInterceptor is a helper method to provide a gRPC-compatible UnaryInterceptor, typically calling the authenticator's Authenticate method.
Expand Down
7 changes: 7 additions & 0 deletions internal/otlptext/databuffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ func (b *dataBuffer) logAttr(label string, value string) {
b.logEntry(" %-15s: %s", label, value)
}

func (b *dataBuffer) logPDataContext(label string, am pdata.PDataContext) {
b.logEntry("%s:", label)
am.Range(func(_ interface{}, value interface{}) {
b.logEntry(" -> %v", value) // the context key is likely an empty struct
})
}

func (b *dataBuffer) logAttributeMap(label string, am pdata.AttributeMap) {
if am.Len() == 0 {
return
Expand Down
1 change: 1 addition & 0 deletions internal/otlptext/traces.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func (textTracesMarshaler) MarshalTraces(td pdata.Traces) ([]byte, error) {
for i := 0; i < rss.Len(); i++ {
buf.logEntry("ResourceSpans #%d", i)
rs := rss.At(i)
buf.logPDataContext("Pipeline Data Context", rs.PDataContext())
buf.logAttributeMap("Resource labels", rs.Resource().Attributes())
ilss := rs.InstrumentationLibrarySpans()
for j := 0; j < ilss.Len(); j++ {
Expand Down
6 changes: 6 additions & 0 deletions model/pdata/pdatacontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ func (pdc PDataContext) Get(key interface{}) interface{} {
}
return nil
}

func (pdc PDataContext) Range(f func(k interface{}, v interface{})) {
for _, kv := range pdc.orig.List {
f(kv.Key, kv.Value)
}
}

0 comments on commit 87cde06

Please sign in to comment.