Authn (jwt_auth): add tracing spans for validating newer use cases (#86812)
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@@ -123,12 +124,31 @@ func (s *Service) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id
|
||||
}
|
||||
|
||||
func (s *Service) authenticate(ctx context.Context, c authn.Client, r *authn.Request) (*authn.Identity, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authn.authenticate")
|
||||
defer span.End()
|
||||
|
||||
identity, err := c.Authenticate(ctx, r)
|
||||
if err != nil {
|
||||
span.SetStatus(codes.Error, "authenticate failed on client")
|
||||
span.RecordError(err)
|
||||
s.errorLogFunc(ctx, err)("Failed to authenticate request", "client", c.Name(), "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
span.SetAttributes(
|
||||
attribute.String("identity.ID", identity.ID.String()),
|
||||
attribute.String("identity.AuthID", identity.AuthID),
|
||||
attribute.String("identity.AuthenticatedBy", identity.AuthenticatedBy),
|
||||
)
|
||||
|
||||
if len(identity.ClientParams.FetchPermissionsParams.ActionsLookup) > 0 {
|
||||
span.SetAttributes(attribute.StringSlice("identity.ClientParams.FetchPermissionsParams.ActionsLookup", identity.ClientParams.FetchPermissionsParams.ActionsLookup))
|
||||
}
|
||||
|
||||
if len(identity.ClientParams.FetchPermissionsParams.Roles) > 0 {
|
||||
span.SetAttributes(attribute.StringSlice("identity.ClientParams.FetchPermissionsParams.Roles", identity.ClientParams.FetchPermissionsParams.Roles))
|
||||
}
|
||||
|
||||
if err := s.runPostAuthHooks(ctx, identity, r); err != nil {
|
||||
s.errorLogFunc(ctx, err)("Failed to run post auth hook", "client", c.Name(), "id", identity.ID, "error", err)
|
||||
return nil, err
|
||||
|
||||
@@ -6,10 +6,14 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
@@ -44,13 +48,54 @@ func TestService_Authenticate(t *testing.T) {
|
||||
},
|
||||
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
|
||||
},
|
||||
{
|
||||
desc: "should succeed with authentication for configured client for identity with fetch permissions params",
|
||||
clients: []authn.Client{
|
||||
&authntest.FakeClient{
|
||||
ExpectedTest: true,
|
||||
ExpectedIdentity: &authn.Identity{
|
||||
ID: authn.MustParseNamespaceID("user:2"),
|
||||
ClientParams: authn.ClientParams{
|
||||
FetchPermissionsParams: authn.FetchPermissionsParams{
|
||||
ActionsLookup: []string{
|
||||
"datasources:read",
|
||||
"datasources:query",
|
||||
},
|
||||
Roles: []string{
|
||||
"fixed:datasources:reader",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedIdentity: &authn.Identity{
|
||||
ID: authn.MustParseNamespaceID("user:2"),
|
||||
ClientParams: authn.ClientParams{
|
||||
FetchPermissionsParams: authn.FetchPermissionsParams{
|
||||
ActionsLookup: []string{
|
||||
"datasources:read",
|
||||
"datasources:query",
|
||||
},
|
||||
Roles: []string{
|
||||
"fixed:datasources:reader",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should succeed with authentication for second client when first test fail",
|
||||
clients: []authn.Client{
|
||||
&authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 1, ExpectedTest: false},
|
||||
&authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 2, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}},
|
||||
&authntest.FakeClient{
|
||||
ExpectedName: "2",
|
||||
ExpectedPriority: 2,
|
||||
ExpectedTest: true,
|
||||
ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
|
||||
},
|
||||
},
|
||||
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")},
|
||||
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
|
||||
},
|
||||
{
|
||||
desc: "should succeed with authentication for third client when error happened in first",
|
||||
@@ -90,16 +135,72 @@ func TestService_Authenticate(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
spanRecorder := tracetest.NewSpanRecorder()
|
||||
tracer := tracing.InitializeTracerForTest(tracing.WithSpanProcessor(spanRecorder))
|
||||
|
||||
svc := setupTests(t, func(svc *Service) {
|
||||
svc.tracer = tracer
|
||||
|
||||
for _, c := range tt.clients {
|
||||
svc.RegisterClient(c)
|
||||
}
|
||||
})
|
||||
|
||||
identity, err := svc.Authenticate(context.Background(), &authn.Request{})
|
||||
spans := spanRecorder.Ended()
|
||||
if len(tt.expectedErrors) == 0 {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, tt.expectedIdentity, identity)
|
||||
|
||||
matchedClients := make([]*authntest.FakeClient, 0)
|
||||
for _, client := range tt.clients {
|
||||
fakeClient, _ := client.(*authntest.FakeClient)
|
||||
if fakeClient.ExpectedTest {
|
||||
matchedClients = append(matchedClients, fakeClient)
|
||||
}
|
||||
}
|
||||
require.Len(t, spans, 1+len(matchedClients), "must have spans 1+ number of clients tried")
|
||||
|
||||
spansTested := make([]sdktrace.ReadOnlySpan, 0)
|
||||
for _, span := range spans {
|
||||
if span.Name() != "authn.Authenticate" {
|
||||
spansTested = append(spansTested, span)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Len(t, spansTested, len(matchedClients), "expected spans with name authn.authenticate to match number of clients tested")
|
||||
|
||||
// since this is a success case, at least one span should have all 3 attributes
|
||||
passedAuthnIndex := slices.IndexFunc(spansTested, func(span sdktrace.ReadOnlySpan) bool {
|
||||
return len(span.Attributes()) >= 3 // more than 3 when there are ClientParams in the identity
|
||||
})
|
||||
require.NotEqual(t, -1, passedAuthnIndex, "no spans found all 3 attributes - passed case should have authn attributes set")
|
||||
passedAuthnSpan := spansTested[passedAuthnIndex]
|
||||
for _, attr := range passedAuthnSpan.Attributes() {
|
||||
switch attr.Key {
|
||||
case "identity.ID":
|
||||
assert.Equal(t, tt.expectedIdentity.ID.String(), attr.Value.AsString())
|
||||
case "identity.AuthID":
|
||||
assert.Equal(t, tt.expectedIdentity.AuthID, attr.Value.AsString())
|
||||
case "identity.AuthenticatedBy":
|
||||
assert.Equal(t, tt.expectedIdentity.AuthenticatedBy, attr.Value.AsString())
|
||||
case "identity.ClientParams.FetchPermissionsParams.ActionsLookup":
|
||||
if len(tt.expectedIdentity.ClientParams.FetchPermissionsParams.ActionsLookup) > 0 {
|
||||
assert.Equal(t, tt.expectedIdentity.ClientParams.FetchPermissionsParams.ActionsLookup, attr.Value.AsStringSlice())
|
||||
}
|
||||
case "identity.ClientParams.FetchPermissionsParams.Roles":
|
||||
if len(tt.expectedIdentity.ClientParams.FetchPermissionsParams.Roles) > 0 {
|
||||
assert.Equal(t, tt.expectedIdentity.ClientParams.FetchPermissionsParams.Roles, attr.Value.AsStringSlice())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(matchedClients) > 1 {
|
||||
failedAuthnIndex := slices.IndexFunc(spansTested, func(span sdktrace.ReadOnlySpan) bool {
|
||||
return span.Status().Code == codes.Error
|
||||
})
|
||||
assert.NotEqual(t, -1, failedAuthnIndex, "no spans found for the error case - at least one client in multi client test must have failed")
|
||||
}
|
||||
} else {
|
||||
for _, e := range tt.expectedErrors {
|
||||
assert.ErrorIs(t, err, e)
|
||||
|
||||
Reference in New Issue
Block a user