030c7099cb
* Zanzana: Fix shadow client context * don't cancel on parent context cancel * share timeout
135 lines
4.0 KiB
Go
135 lines
4.0 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
authlib "github.com/grafana/authlib/types"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
)
|
|
|
|
var _ authlib.AccessClient = (*ShadowClient)(nil)
|
|
|
|
const zanzanaTimeout = 30 * time.Second
|
|
|
|
type ShadowClient struct {
|
|
logger log.Logger
|
|
accessClient authlib.AccessClient
|
|
zanzanaClient authlib.AccessClient
|
|
metrics *metrics
|
|
}
|
|
|
|
// WithShadowClient returns a new access client that runs zanzana checks in the background.
|
|
func WithShadowClient(accessClient authlib.AccessClient, zanzanaClient authlib.AccessClient, reg prometheus.Registerer) (authlib.AccessClient, error) {
|
|
client := &ShadowClient{
|
|
logger: log.New("zanzana-shadow-client"),
|
|
accessClient: accessClient,
|
|
zanzanaClient: zanzanaClient,
|
|
metrics: newShadowClientMetrics(reg),
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
func (c *ShadowClient) Check(ctx context.Context, id authlib.AuthInfo, req authlib.CheckRequest, folder string) (authlib.CheckResponse, error) {
|
|
acResChan := make(chan authlib.CheckResponse, 1)
|
|
acErrChan := make(chan error, 1)
|
|
|
|
go func() {
|
|
if c.zanzanaClient == nil {
|
|
return
|
|
}
|
|
|
|
zanzanaCtx := context.WithoutCancel(ctx)
|
|
zanzanaCtxTimeout, cancel := context.WithTimeout(zanzanaCtx, zanzanaTimeout)
|
|
defer cancel()
|
|
|
|
timer := prometheus.NewTimer(c.metrics.evaluationsSeconds.WithLabelValues("zanzana"))
|
|
res, err := c.zanzanaClient.Check(zanzanaCtxTimeout, id, req, folder)
|
|
if err != nil {
|
|
c.logger.Error("Failed to run zanzana check", "error", err)
|
|
}
|
|
timer.ObserveDuration()
|
|
|
|
acRes := <-acResChan
|
|
acErr := <-acErrChan
|
|
|
|
if acErr == nil {
|
|
if res.Allowed != acRes.Allowed {
|
|
c.metrics.evaluationStatusTotal.WithLabelValues("error").Inc()
|
|
c.logger.Warn("Zanzana check result does not match", "expected", acRes.Allowed, "actual", res.Allowed, "user", id.GetUID(), "request", req)
|
|
} else {
|
|
c.metrics.evaluationStatusTotal.WithLabelValues("success").Inc()
|
|
}
|
|
}
|
|
}()
|
|
|
|
timer := prometheus.NewTimer(c.metrics.evaluationsSeconds.WithLabelValues("rbac"))
|
|
res, err := c.accessClient.Check(ctx, id, req, folder)
|
|
timer.ObserveDuration()
|
|
acResChan <- res
|
|
acErrChan <- err
|
|
|
|
return res, err
|
|
}
|
|
|
|
func (c *ShadowClient) Compile(ctx context.Context, id authlib.AuthInfo, req authlib.ListRequest) (authlib.ItemChecker, authlib.Zookie, error) {
|
|
zanzanaItemCheckerChan := make(chan authlib.ItemChecker, 1)
|
|
var zanzanaItemChecker authlib.ItemChecker
|
|
var once sync.Once
|
|
|
|
go func() {
|
|
if c.zanzanaClient == nil {
|
|
zanzanaItemCheckerChan <- nil
|
|
return
|
|
}
|
|
|
|
zanzanaCtx := context.WithoutCancel(ctx)
|
|
zanzanaCtxTimeout, cancel := context.WithTimeout(zanzanaCtx, zanzanaTimeout)
|
|
defer cancel()
|
|
|
|
timer := prometheus.NewTimer(c.metrics.compileSeconds.WithLabelValues("zanzana"))
|
|
itemChecker, _, err := c.zanzanaClient.Compile(zanzanaCtxTimeout, id, req)
|
|
timer.ObserveDuration()
|
|
if err != nil {
|
|
c.logger.Warn("Failed to compile zanzana item checker", "error", err)
|
|
}
|
|
zanzanaItemCheckerChan <- itemChecker
|
|
}()
|
|
|
|
timer := prometheus.NewTimer(c.metrics.compileSeconds.WithLabelValues("rbac"))
|
|
rbacItemChecker, _, err := c.accessClient.Compile(ctx, id, req)
|
|
timer.ObserveDuration()
|
|
if err != nil {
|
|
return nil, authlib.NoopZookie{}, err
|
|
}
|
|
|
|
shadowItemChecker := func(name, folder string) bool {
|
|
rbacRes := rbacItemChecker(name, folder)
|
|
|
|
go func() {
|
|
// Wait for zanzana result to be ready and then use it to compare against RBAC
|
|
once.Do(func() {
|
|
zanzanaItemChecker = <-zanzanaItemCheckerChan
|
|
})
|
|
|
|
if zanzanaItemChecker != nil {
|
|
zanzanaRes := zanzanaItemChecker(name, folder)
|
|
if zanzanaRes != rbacRes {
|
|
c.metrics.evaluationStatusTotal.WithLabelValues("error").Inc()
|
|
c.logger.Warn("Zanzana compile result does not match", "expected", rbacRes, "actual", zanzanaRes, "name", name, "folder", folder)
|
|
} else {
|
|
c.metrics.evaluationStatusTotal.WithLabelValues("success").Inc()
|
|
}
|
|
}
|
|
}()
|
|
|
|
return rbacRes
|
|
}
|
|
|
|
return shadowItemChecker, authlib.NoopZookie{}, err
|
|
}
|