package grpcserver import ( "context" "log/slog" "path" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "git.wntrmute.dev/kyle/metacrypt/internal/audit" "git.wntrmute.dev/kyle/metacrypt/internal/auth" "git.wntrmute.dev/kyle/metacrypt/internal/seal" ) // sealInterceptor rejects calls with FailedPrecondition when the vault is // sealed, for the listed methods. It is intended to run as a PreInterceptor // in the mcdsl grpcserver chain, before logging and auth. func sealInterceptor(sealMgr *seal.Manager, logger *slog.Logger, methods map[string]bool) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { if !methods[info.FullMethod] { return handler(ctx, req) } if sealMgr.State() != seal.StateUnsealed { logger.Debug("grpc request rejected: vault sealed", "method", info.FullMethod) return nil, status.Error(codes.FailedPrecondition, "vault is sealed") } return handler(ctx, req) } } // auditInterceptor logs an audit event after each RPC completes. It is // intended to run as a PostInterceptor in the mcdsl grpcserver chain, // after auth so that caller info is available in the context via // auth.TokenInfoFromContext. func auditInterceptor(auditLog *audit.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { resp, err := handler(ctx, req) caller := "anonymous" var roles []string if ti := auth.TokenInfoFromContext(ctx); ti != nil { caller = ti.Username roles = ti.Roles } outcome := "success" var errMsg string if err != nil { outcome = "error" if st, ok := status.FromError(err); ok { if st.Code() == codes.PermissionDenied || st.Code() == codes.Unauthenticated { outcome = "denied" } errMsg = st.Message() } else { errMsg = err.Error() } } auditLog.Log(ctx, audit.Event{ Caller: caller, Roles: roles, Operation: path.Base(info.FullMethod), Resource: info.FullMethod, Outcome: outcome, Error: errMsg, }) return resp, err } }