Files
metacrypt/internal/grpcserver/interceptors.go
Kyle Isom bbe382dc10 Migrate module path from kyle/ to mc/ org
All import paths updated to git.wntrmute.dev/mc/. Bumps mcdsl to v1.2.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:05:59 -07:00

74 lines
2.2 KiB
Go

package grpcserver
import (
"context"
"log/slog"
"path"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"git.wntrmute.dev/mc/metacrypt/internal/audit"
"git.wntrmute.dev/mc/metacrypt/internal/auth"
"git.wntrmute.dev/mc/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
}
}