Authn (jwt_auth): add tracing spans for validating newer use cases (#86812)
This commit is contained in:
@@ -787,6 +787,7 @@ gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPj
|
||||
gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4=
|
||||
google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0=
|
||||
google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I=
|
||||
|
||||
@@ -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