Migrate gRPC server to mcdsl grpcserver package
Replace metacrypt's hand-rolled gRPC interceptor chain with the mcdsl grpcserver package, which provides TLS setup, logging, and method-map auth (public/auth-required/admin-required) out of the box. Metacrypt-specific interceptors are preserved as hooks: - sealInterceptor runs as a PreInterceptor (before logging/auth) - auditInterceptor runs as a PostInterceptor (after auth) The three legacy method maps (seal/auth/admin) are restructured into mcdsl's MethodMap (Public/AuthRequired/AdminRequired) plus a separate seal-required map for the PreInterceptor. Token context is now stored via mcdsl/auth.ContextWithTokenInfo instead of a package-local key. Bumps mcdsl from v1.0.0 to v1.0.1 (adds PreInterceptors/PostInterceptors to grpcserver.Options). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
go.mod
2
go.mod
@@ -4,7 +4,7 @@ go 1.25.7
|
||||
|
||||
require (
|
||||
git.wntrmute.dev/kyle/goutils v1.21.0
|
||||
git.wntrmute.dev/kyle/mcdsl v1.0.0
|
||||
git.wntrmute.dev/kyle/mcdsl v1.0.1
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -2,6 +2,8 @@ git.wntrmute.dev/kyle/goutils v1.21.0 h1:ZR7ovV400hsF09zc8tkdHs6vyen8TDJ7flong/d
|
||||
git.wntrmute.dev/kyle/goutils v1.21.0/go.mod h1:JQ8NL5lHSEYl719UMf20p4G1ei70RVGma0hjjNXCR2c=
|
||||
git.wntrmute.dev/kyle/mcdsl v1.0.0 h1:YB7dx4gdNYKKcVySpL6UkwHqdCJ9Nl1yS0+eHk0hNtk=
|
||||
git.wntrmute.dev/kyle/mcdsl v1.0.0/go.mod h1:wo0tGfUAxci3XnOe4/rFmR0RjUElKdYUazc+Np986sg=
|
||||
git.wntrmute.dev/kyle/mcdsl v1.0.1 h1:Dr9Ud8cjWWybulpv+KsuSKbuZmzBXPCItQztR7o2hcA=
|
||||
git.wntrmute.dev/kyle/mcdsl v1.0.1/go.mod h1:wo0tGfUAxci3XnOe4/rFmR0RjUElKdYUazc+Np986sg=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2"
|
||||
internacme "git.wntrmute.dev/kyle/metacrypt/internal/acme"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/auth"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine"
|
||||
)
|
||||
|
||||
@@ -19,7 +20,7 @@ type acmeServer struct {
|
||||
}
|
||||
|
||||
func (as *acmeServer) CreateEAB(ctx context.Context, req *pb.CreateEABRequest) (*pb.CreateEABResponse, error) {
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
ti := auth.TokenInfoFromContext(ctx)
|
||||
h, err := as.getOrCreateHandler(req.Mount)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.NotFound, "mount not found")
|
||||
|
||||
@@ -37,7 +37,7 @@ func (as *authServer) Logout(ctx context.Context, _ *pb.LogoutRequest) (*pb.Logo
|
||||
}
|
||||
|
||||
func (as *authServer) TokenInfo(ctx context.Context, _ *pb.TokenInfoRequest) (*pb.TokenInfoResponse, error) {
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
ti := auth.TokenInfoFromContext(ctx)
|
||||
if ti == nil {
|
||||
// Shouldn't happen — authInterceptor runs first — but guard anyway.
|
||||
token := extractToken(ctx)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/auth"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine/ca"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/policy"
|
||||
@@ -51,16 +52,9 @@ func (cs *caServer) caHandleRequest(ctx context.Context, mount, operation string
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func callerUsername(ctx context.Context) string {
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
if ti == nil {
|
||||
return ""
|
||||
}
|
||||
return ti.Username
|
||||
}
|
||||
|
||||
func (cs *caServer) callerInfo(ctx context.Context) *engine.CallerInfo {
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
ti := auth.TokenInfoFromContext(ctx)
|
||||
if ti == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/auth"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine"
|
||||
)
|
||||
|
||||
@@ -48,7 +49,7 @@ func (es *engineServer) Mount(ctx context.Context, req *pb.MountRequest) (*pb.Mo
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
}
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
ti := auth.TokenInfoFromContext(ctx)
|
||||
username := ""
|
||||
if ti != nil {
|
||||
username = ti.Username
|
||||
@@ -67,7 +68,7 @@ func (es *engineServer) Unmount(ctx context.Context, req *pb.UnmountRequest) (*p
|
||||
}
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
ti := auth.TokenInfoFromContext(ctx)
|
||||
username := ""
|
||||
if ti != nil {
|
||||
username = ti.Username
|
||||
|
||||
@@ -158,114 +158,6 @@ func TestSealInterceptor_SkipsUnlistedMethod(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthInterceptor_MissingToken(t *testing.T) {
|
||||
authenticator, _ := auth.NewAuthenticator(auth.Config{ServerURL: "http://localhost:0"}, slog.Default())
|
||||
methods := map[string]bool{"/test.Service/Method": true}
|
||||
interceptor := authInterceptor(authenticator, slog.Default(), methods)
|
||||
|
||||
_, err := interceptor(context.Background(), nil, methodInfo("/test.Service/Method"), okHandler)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing token")
|
||||
}
|
||||
if code := status.Code(err); code != codes.Unauthenticated {
|
||||
t.Errorf("expected Unauthenticated, got %v", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthInterceptor_SkipsUnlistedMethod(t *testing.T) {
|
||||
authenticator, _ := auth.NewAuthenticator(auth.Config{ServerURL: "http://localhost:0"}, slog.Default())
|
||||
methods := map[string]bool{"/test.Service/Other": true}
|
||||
interceptor := authInterceptor(authenticator, slog.Default(), methods)
|
||||
|
||||
resp, err := interceptor(context.Background(), nil, methodInfo("/test.Service/Method"), okHandler)
|
||||
if err != nil {
|
||||
t.Fatalf("expected pass-through, got: %v", err)
|
||||
}
|
||||
if resp != "ok" {
|
||||
t.Errorf("expected 'ok', got %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminInterceptor_NoTokenInfo(t *testing.T) {
|
||||
methods := map[string]bool{"/test.Service/Admin": true}
|
||||
interceptor := adminInterceptor(slog.Default(), methods)
|
||||
|
||||
_, err := interceptor(context.Background(), nil, methodInfo("/test.Service/Admin"), okHandler)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when no token info in context")
|
||||
}
|
||||
if code := status.Code(err); code != codes.PermissionDenied {
|
||||
t.Errorf("expected PermissionDenied, got %v", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminInterceptor_NonAdmin(t *testing.T) {
|
||||
methods := map[string]bool{"/test.Service/Admin": true}
|
||||
interceptor := adminInterceptor(slog.Default(), methods)
|
||||
|
||||
ctx := context.WithValue(context.Background(), tokenInfoKey, &auth.TokenInfo{
|
||||
Username: "user",
|
||||
IsAdmin: false,
|
||||
})
|
||||
_, err := interceptor(ctx, nil, methodInfo("/test.Service/Admin"), okHandler)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for non-admin")
|
||||
}
|
||||
if code := status.Code(err); code != codes.PermissionDenied {
|
||||
t.Errorf("expected PermissionDenied, got %v", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminInterceptor_Admin(t *testing.T) {
|
||||
methods := map[string]bool{"/test.Service/Admin": true}
|
||||
interceptor := adminInterceptor(slog.Default(), methods)
|
||||
|
||||
ctx := context.WithValue(context.Background(), tokenInfoKey, &auth.TokenInfo{
|
||||
Username: "admin",
|
||||
IsAdmin: true,
|
||||
})
|
||||
resp, err := interceptor(ctx, nil, methodInfo("/test.Service/Admin"), okHandler)
|
||||
if err != nil {
|
||||
t.Fatalf("expected success for admin, got: %v", err)
|
||||
}
|
||||
if resp != "ok" {
|
||||
t.Errorf("expected 'ok', got %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminInterceptor_SkipsUnlistedMethod(t *testing.T) {
|
||||
methods := map[string]bool{"/test.Service/Other": true}
|
||||
interceptor := adminInterceptor(slog.Default(), methods)
|
||||
|
||||
// No token info in context — but method not listed, so should pass through.
|
||||
resp, err := interceptor(context.Background(), nil, methodInfo("/test.Service/Method"), okHandler)
|
||||
if err != nil {
|
||||
t.Fatalf("expected pass-through, got: %v", err)
|
||||
}
|
||||
if resp != "ok" {
|
||||
t.Errorf("expected 'ok', got %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChainInterceptors(t *testing.T) {
|
||||
var order []int
|
||||
makeInterceptor := func(n int) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
order = append(order, n)
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
chained := chainInterceptors(makeInterceptor(1), makeInterceptor(2), makeInterceptor(3))
|
||||
_, err := chained(context.Background(), nil, methodInfo("/test/Method"), okHandler)
|
||||
if err != nil {
|
||||
t.Fatalf("chain: %v", err)
|
||||
}
|
||||
if len(order) != 3 || order[0] != 1 || order[1] != 2 || order[2] != 3 {
|
||||
t.Errorf("expected execution order [1 2 3], got %v", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractToken(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -293,6 +185,21 @@ func TestExtractToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallerUsername(t *testing.T) {
|
||||
// No token info in context.
|
||||
if got := callerUsername(context.Background()); got != "" {
|
||||
t.Errorf("expected empty, got %q", got)
|
||||
}
|
||||
|
||||
// With token info.
|
||||
ctx := auth.ContextWithTokenInfo(context.Background(), &auth.TokenInfo{
|
||||
Username: "alice",
|
||||
})
|
||||
if got := callerUsername(ctx); got != "alice" {
|
||||
t.Errorf("expected 'alice', got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- systemServer tests ----
|
||||
|
||||
func TestSystemStatus(t *testing.T) {
|
||||
@@ -668,7 +575,7 @@ func TestAuthTokenInfo_FromContext(t *testing.T) {
|
||||
|
||||
as := &authServer{s: srv}
|
||||
ti := &auth.TokenInfo{Username: "alice", Roles: []string{"user"}, IsAdmin: false}
|
||||
ctx := context.WithValue(context.Background(), tokenInfoKey, ti)
|
||||
ctx := auth.ContextWithTokenInfo(context.Background(), ti)
|
||||
|
||||
resp, err := as.TokenInfo(ctx, &pb.TokenInfoRequest{})
|
||||
if err != nil {
|
||||
@@ -718,3 +625,33 @@ func TestPbToRuleRoundtrip(t *testing.T) {
|
||||
t.Errorf("roundtrip Effect: got %q, want %q", back.Effect, original.Effect)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- method map tests ----
|
||||
|
||||
func TestMethodMapCompleteness(t *testing.T) {
|
||||
// Verify every method in sealRequired is also in one of the auth maps
|
||||
// (public, authRequired, or adminRequired).
|
||||
mm := methodMap()
|
||||
seal := sealRequiredMethods()
|
||||
|
||||
for method := range seal {
|
||||
if !mm.Public[method] && !mm.AuthRequired[method] && !mm.AdminRequired[method] {
|
||||
t.Errorf("seal-required method %s is not in any auth map (public/auth/admin)", method)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify public/auth/admin maps don't overlap.
|
||||
for method := range mm.Public {
|
||||
if mm.AuthRequired[method] {
|
||||
t.Errorf("method %s is in both Public and AuthRequired", method)
|
||||
}
|
||||
if mm.AdminRequired[method] {
|
||||
t.Errorf("method %s is in both Public and AdminRequired", method)
|
||||
}
|
||||
}
|
||||
for method := range mm.AuthRequired {
|
||||
if mm.AdminRequired[method] {
|
||||
t.Errorf("method %s is in both AuthRequired and AdminRequired", method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,9 @@ import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/audit"
|
||||
@@ -16,61 +14,9 @@ import (
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/seal"
|
||||
)
|
||||
|
||||
type contextKey int
|
||||
|
||||
const tokenInfoKey contextKey = iota
|
||||
|
||||
func tokenInfoFromContext(ctx context.Context) *auth.TokenInfo {
|
||||
v, _ := ctx.Value(tokenInfoKey).(*auth.TokenInfo)
|
||||
return v
|
||||
}
|
||||
|
||||
// authInterceptor validates the Bearer token from gRPC metadata and injects
|
||||
// *auth.TokenInfo into the context. The set of method full names that require
|
||||
// auth is passed in; all others pass through without validation.
|
||||
func authInterceptor(authenticator *auth.Authenticator, 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)
|
||||
}
|
||||
|
||||
token := extractToken(ctx)
|
||||
if token == "" {
|
||||
logger.Debug("grpc request rejected: missing token", "method", info.FullMethod)
|
||||
return nil, status.Error(codes.Unauthenticated, "missing authorization token")
|
||||
}
|
||||
|
||||
tokenInfo, err := authenticator.ValidateToken(token)
|
||||
if err != nil {
|
||||
logger.Debug("grpc request rejected: invalid token", "method", info.FullMethod, "error", err)
|
||||
return nil, status.Error(codes.Unauthenticated, "invalid token")
|
||||
}
|
||||
|
||||
logger.Debug("grpc request authenticated", "method", info.FullMethod, "username", tokenInfo.Username)
|
||||
ctx = context.WithValue(ctx, tokenInfoKey, tokenInfo)
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// adminInterceptor requires IsAdmin on the token info for the listed methods.
|
||||
// Must run after authInterceptor.
|
||||
func adminInterceptor(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)
|
||||
}
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
if ti == nil || !ti.IsAdmin {
|
||||
logger.Debug("grpc request rejected: admin required", "method", info.FullMethod)
|
||||
return nil, status.Error(codes.PermissionDenied, "admin required")
|
||||
}
|
||||
logger.Debug("grpc admin request authorized", "method", info.FullMethod, "username", ti.Username)
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// sealInterceptor rejects calls with FailedPrecondition when the vault is
|
||||
// sealed, for the listed methods.
|
||||
// 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] {
|
||||
@@ -84,30 +30,17 @@ func sealInterceptor(sealMgr *seal.Manager, logger *slog.Logger, methods map[str
|
||||
}
|
||||
}
|
||||
|
||||
// chainInterceptors reduces a slice of interceptors into a single one.
|
||||
func chainInterceptors(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
chain := handler
|
||||
for i := len(interceptors) - 1; i >= 0; i-- {
|
||||
next := chain
|
||||
ic := interceptors[i]
|
||||
chain = func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return ic(ctx, req, info, next)
|
||||
}
|
||||
}
|
||||
return chain(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// auditInterceptor logs an audit event after each RPC completes. Must run
|
||||
// after authInterceptor so that caller info is available in the context.
|
||||
// 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 := tokenInfoFromContext(ctx); ti != nil {
|
||||
if ti := auth.TokenInfoFromContext(ctx); ti != nil {
|
||||
caller = ti.Username
|
||||
roles = ti.Roles
|
||||
}
|
||||
@@ -138,19 +71,3 @@ func auditInterceptor(auditLog *audit.Logger) grpc.UnaryServerInterceptor {
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
func extractToken(ctx context.Context) string {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
vals := md.Get("authorization")
|
||||
if len(vals) == 0 {
|
||||
return ""
|
||||
}
|
||||
v := vals[0]
|
||||
if strings.HasPrefix(v, "Bearer ") {
|
||||
return strings.TrimPrefix(v, "Bearer ")
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
package grpcserver
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2"
|
||||
"git.wntrmute.dev/kyle/mcdsl/grpcserver"
|
||||
|
||||
internacme "git.wntrmute.dev/kyle/metacrypt/internal/acme"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/audit"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/auth"
|
||||
@@ -21,7 +23,7 @@ import (
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/seal"
|
||||
)
|
||||
|
||||
// GRPCServer wraps the gRPC server and all service implementations.
|
||||
// GRPCServer wraps the mcdsl gRPC server and all service implementations.
|
||||
type GRPCServer struct {
|
||||
cfg *config.Config
|
||||
sealMgr *seal.Manager
|
||||
@@ -30,7 +32,7 @@ type GRPCServer struct {
|
||||
engines *engine.Registry
|
||||
audit *audit.Logger
|
||||
logger *slog.Logger
|
||||
srv *grpc.Server
|
||||
srv *grpcserver.Server
|
||||
acmeHandlers map[string]*internacme.Handler
|
||||
mu sync.Mutex
|
||||
}
|
||||
@@ -57,61 +59,201 @@ func (s *GRPCServer) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
tlsCert, err := tls.LoadX509KeyPair(s.cfg.Server.TLSCert, s.cfg.Server.TLSKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("grpc: load TLS cert: %w", err)
|
||||
opts := &grpcserver.Options{
|
||||
PreInterceptors: []grpc.UnaryServerInterceptor{sealInterceptor(s.sealMgr, s.logger, sealRequiredMethods())},
|
||||
PostInterceptors: []grpc.UnaryServerInterceptor{auditInterceptor(s.audit)},
|
||||
}
|
||||
tlsCfg := &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
creds := credentials.NewTLS(tlsCfg)
|
||||
|
||||
interceptor := chainInterceptors(
|
||||
sealInterceptor(s.sealMgr, s.logger, sealRequiredMethods()),
|
||||
authInterceptor(s.auth, s.logger, authRequiredMethods()),
|
||||
adminInterceptor(s.logger, adminRequiredMethods()),
|
||||
auditInterceptor(s.audit),
|
||||
srv, err := grpcserver.New(
|
||||
s.cfg.Server.TLSCert,
|
||||
s.cfg.Server.TLSKey,
|
||||
s.auth,
|
||||
methodMap(),
|
||||
s.logger,
|
||||
opts,
|
||||
)
|
||||
|
||||
s.srv = grpc.NewServer(
|
||||
grpc.Creds(creds),
|
||||
grpc.UnaryInterceptor(interceptor),
|
||||
)
|
||||
|
||||
pb.RegisterSystemServiceServer(s.srv, &systemServer{s: s})
|
||||
pb.RegisterAuthServiceServer(s.srv, &authServer{s: s})
|
||||
pb.RegisterEngineServiceServer(s.srv, &engineServer{s: s})
|
||||
pb.RegisterPKIServiceServer(s.srv, &pkiServer{s: s})
|
||||
pb.RegisterCAServiceServer(s.srv, &caServer{s: s})
|
||||
pb.RegisterPolicyServiceServer(s.srv, &policyServer{s: s})
|
||||
pb.RegisterBarrierServiceServer(s.srv, &barrierServer{s: s})
|
||||
pb.RegisterUserServiceServer(s.srv, &userServer{s: s})
|
||||
pb.RegisterACMEServiceServer(s.srv, &acmeServer{s: s})
|
||||
pb.RegisterSSHCAServiceServer(s.srv, &sshcaServer{s: s})
|
||||
pb.RegisterTransitServiceServer(s.srv, &transitServer{s: s})
|
||||
|
||||
lis, err := net.Listen("tcp", s.cfg.Server.GRPCAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("grpc: listen %s: %w", s.cfg.Server.GRPCAddr, err)
|
||||
return fmt.Errorf("grpc: create server: %w", err)
|
||||
}
|
||||
s.srv = srv
|
||||
|
||||
s.logger.Info("starting gRPC server", "addr", s.cfg.Server.GRPCAddr)
|
||||
if err := s.srv.Serve(lis); err != nil {
|
||||
return fmt.Errorf("grpc: serve: %w", err)
|
||||
}
|
||||
return nil
|
||||
pb.RegisterSystemServiceServer(srv.GRPCServer, &systemServer{s: s})
|
||||
pb.RegisterAuthServiceServer(srv.GRPCServer, &authServer{s: s})
|
||||
pb.RegisterEngineServiceServer(srv.GRPCServer, &engineServer{s: s})
|
||||
pb.RegisterPKIServiceServer(srv.GRPCServer, &pkiServer{s: s})
|
||||
pb.RegisterCAServiceServer(srv.GRPCServer, &caServer{s: s})
|
||||
pb.RegisterPolicyServiceServer(srv.GRPCServer, &policyServer{s: s})
|
||||
pb.RegisterBarrierServiceServer(srv.GRPCServer, &barrierServer{s: s})
|
||||
pb.RegisterUserServiceServer(srv.GRPCServer, &userServer{s: s})
|
||||
pb.RegisterACMEServiceServer(srv.GRPCServer, &acmeServer{s: s})
|
||||
pb.RegisterSSHCAServiceServer(srv.GRPCServer, &sshcaServer{s: s})
|
||||
pb.RegisterTransitServiceServer(srv.GRPCServer, &transitServer{s: s})
|
||||
|
||||
return srv.Serve(s.cfg.Server.GRPCAddr)
|
||||
}
|
||||
|
||||
// Shutdown gracefully stops the gRPC server.
|
||||
func (s *GRPCServer) Shutdown() {
|
||||
if s.srv != nil {
|
||||
s.srv.GracefulStop()
|
||||
s.srv.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// extractToken extracts the bearer token from gRPC metadata. Used by the
|
||||
// auth service Logout handler which needs the raw token.
|
||||
func extractToken(ctx context.Context) string {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
vals := md.Get("authorization")
|
||||
if len(vals) == 0 {
|
||||
return ""
|
||||
}
|
||||
v := vals[0]
|
||||
if strings.HasPrefix(v, "Bearer ") {
|
||||
return strings.TrimPrefix(v, "Bearer ")
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// callerUsername returns the username from the token info in the context,
|
||||
// or empty string if no token info is present.
|
||||
func callerUsername(ctx context.Context) string {
|
||||
ti := auth.TokenInfoFromContext(ctx)
|
||||
if ti == nil {
|
||||
return ""
|
||||
}
|
||||
return ti.Username
|
||||
}
|
||||
|
||||
// methodMap builds the mcdsl grpcserver.MethodMap for Metacrypt.
|
||||
//
|
||||
// Public methods require no authentication. Auth-required and admin-required
|
||||
// methods are enforced by the mcdsl auth interceptor.
|
||||
func methodMap() grpcserver.MethodMap {
|
||||
return grpcserver.MethodMap{
|
||||
Public: publicMethods(),
|
||||
|
||||
AuthRequired: authRequiredMethods(),
|
||||
|
||||
AdminRequired: adminRequiredMethods(),
|
||||
}
|
||||
}
|
||||
|
||||
// publicMethods returns methods that require no authentication.
|
||||
// These include system lifecycle RPCs (status, init, unseal), the login
|
||||
// endpoint, and read-only PKI/CA/SSH CA endpoints that serve public
|
||||
// certificates and keys.
|
||||
func publicMethods() map[string]bool {
|
||||
return map[string]bool{
|
||||
// System lifecycle — always available.
|
||||
"/metacrypt.v2.SystemService/Status": true,
|
||||
"/metacrypt.v2.SystemService/Init": true,
|
||||
"/metacrypt.v2.SystemService/Unseal": true,
|
||||
// Auth — login requires no existing token.
|
||||
"/metacrypt.v2.AuthService/Login": true,
|
||||
// PKI read-only — public certificates.
|
||||
"/metacrypt.v2.PKIService/GetRootCert": true,
|
||||
"/metacrypt.v2.PKIService/GetChain": true,
|
||||
"/metacrypt.v2.PKIService/GetIssuerCert": true,
|
||||
// CA read-only — public certificates and chains.
|
||||
"/metacrypt.v2.CAService/GetRoot": true,
|
||||
"/metacrypt.v2.CAService/GetIssuer": true,
|
||||
"/metacrypt.v2.CAService/ListIssuers": true,
|
||||
"/metacrypt.v2.CAService/GetChain": true,
|
||||
// SSH CA — public key and key revocation list.
|
||||
"/metacrypt.v2.SSHCAService/GetCAPublicKey": true,
|
||||
"/metacrypt.v2.SSHCAService/GetKRL": true,
|
||||
}
|
||||
}
|
||||
|
||||
// authRequiredMethods returns methods that require a valid MCIAS token but
|
||||
// not necessarily the admin role.
|
||||
func authRequiredMethods() map[string]bool {
|
||||
return map[string]bool{
|
||||
"/metacrypt.v2.AuthService/Logout": true,
|
||||
"/metacrypt.v2.AuthService/TokenInfo": true,
|
||||
"/metacrypt.v2.EngineService/ListMounts": true,
|
||||
"/metacrypt.v2.CAService/IssueCert": true,
|
||||
"/metacrypt.v2.CAService/GetCert": true,
|
||||
"/metacrypt.v2.CAService/ListCerts": true,
|
||||
"/metacrypt.v2.CAService/RenewCert": true,
|
||||
"/metacrypt.v2.CAService/SignCSR": true,
|
||||
"/metacrypt.v2.UserService/Register": true,
|
||||
"/metacrypt.v2.UserService/GetPublicKey": true,
|
||||
"/metacrypt.v2.UserService/ListUsers": true,
|
||||
"/metacrypt.v2.UserService/Encrypt": true,
|
||||
"/metacrypt.v2.UserService/Decrypt": true,
|
||||
"/metacrypt.v2.UserService/ReEncrypt": true,
|
||||
"/metacrypt.v2.UserService/RotateKey": true,
|
||||
"/metacrypt.v2.ACMEService/CreateEAB": true,
|
||||
// SSH CA — sign and read operations.
|
||||
"/metacrypt.v2.SSHCAService/SignHost": true,
|
||||
"/metacrypt.v2.SSHCAService/SignUser": true,
|
||||
"/metacrypt.v2.SSHCAService/GetProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/ListProfiles": true,
|
||||
"/metacrypt.v2.SSHCAService/GetCert": true,
|
||||
"/metacrypt.v2.SSHCAService/ListCerts": true,
|
||||
// Transit — data-plane operations.
|
||||
"/metacrypt.v2.TransitService/GetKey": true,
|
||||
"/metacrypt.v2.TransitService/ListKeys": true,
|
||||
"/metacrypt.v2.TransitService/Encrypt": true,
|
||||
"/metacrypt.v2.TransitService/Decrypt": true,
|
||||
"/metacrypt.v2.TransitService/Rewrap": true,
|
||||
"/metacrypt.v2.TransitService/BatchEncrypt": true,
|
||||
"/metacrypt.v2.TransitService/BatchDecrypt": true,
|
||||
"/metacrypt.v2.TransitService/BatchRewrap": true,
|
||||
"/metacrypt.v2.TransitService/Sign": true,
|
||||
"/metacrypt.v2.TransitService/Verify": true,
|
||||
"/metacrypt.v2.TransitService/Hmac": true,
|
||||
"/metacrypt.v2.TransitService/GetPublicKey": true,
|
||||
}
|
||||
}
|
||||
|
||||
// adminRequiredMethods returns methods that require a valid MCIAS token
|
||||
// with the admin role.
|
||||
func adminRequiredMethods() map[string]bool {
|
||||
return map[string]bool{
|
||||
"/metacrypt.v2.SystemService/Seal": true,
|
||||
"/metacrypt.v2.EngineService/Mount": true,
|
||||
"/metacrypt.v2.EngineService/Unmount": true,
|
||||
"/metacrypt.v2.CAService/ImportRoot": true,
|
||||
"/metacrypt.v2.CAService/CreateIssuer": true,
|
||||
"/metacrypt.v2.CAService/DeleteIssuer": true,
|
||||
"/metacrypt.v2.CAService/RevokeCert": true,
|
||||
"/metacrypt.v2.CAService/DeleteCert": true,
|
||||
"/metacrypt.v2.PolicyService/CreatePolicy": true,
|
||||
"/metacrypt.v2.PolicyService/ListPolicies": true,
|
||||
"/metacrypt.v2.PolicyService/GetPolicy": true,
|
||||
"/metacrypt.v2.PolicyService/DeletePolicy": true,
|
||||
// User.
|
||||
"/metacrypt.v2.UserService/Provision": true,
|
||||
"/metacrypt.v2.UserService/DeleteUser": true,
|
||||
"/metacrypt.v2.ACMEService/SetConfig": true,
|
||||
"/metacrypt.v2.ACMEService/ListAccounts": true,
|
||||
"/metacrypt.v2.ACMEService/ListOrders": true,
|
||||
"/metacrypt.v2.BarrierService/ListKeys": true,
|
||||
"/metacrypt.v2.BarrierService/RotateMEK": true,
|
||||
"/metacrypt.v2.BarrierService/RotateKey": true,
|
||||
"/metacrypt.v2.BarrierService/Migrate": true,
|
||||
// SSH CA.
|
||||
"/metacrypt.v2.SSHCAService/CreateProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/UpdateProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/DeleteProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/RevokeCert": true,
|
||||
"/metacrypt.v2.SSHCAService/DeleteCert": true,
|
||||
// Transit.
|
||||
"/metacrypt.v2.TransitService/CreateKey": true,
|
||||
"/metacrypt.v2.TransitService/DeleteKey": true,
|
||||
"/metacrypt.v2.TransitService/RotateKey": true,
|
||||
"/metacrypt.v2.TransitService/UpdateKeyConfig": true,
|
||||
"/metacrypt.v2.TransitService/TrimKey": true,
|
||||
}
|
||||
}
|
||||
|
||||
// sealRequiredMethods returns the set of RPC full names that require the vault
|
||||
// to be unsealed.
|
||||
// to be unsealed. This is used by the sealInterceptor PreInterceptor.
|
||||
func sealRequiredMethods() map[string]bool {
|
||||
return map[string]bool{
|
||||
"/metacrypt.v2.AuthService/Login": true,
|
||||
@@ -153,11 +295,11 @@ func sealRequiredMethods() map[string]bool {
|
||||
"/metacrypt.v2.ACMEService/CreateEAB": true,
|
||||
"/metacrypt.v2.ACMEService/SetConfig": true,
|
||||
"/metacrypt.v2.ACMEService/ListAccounts": true,
|
||||
"/metacrypt.v2.ACMEService/ListOrders": true,
|
||||
"/metacrypt.v2.BarrierService/ListKeys": true,
|
||||
"/metacrypt.v2.BarrierService/RotateMEK": true,
|
||||
"/metacrypt.v2.BarrierService/RotateKey": true,
|
||||
"/metacrypt.v2.BarrierService/Migrate": true,
|
||||
"/metacrypt.v2.ACMEService/ListOrders": true,
|
||||
"/metacrypt.v2.BarrierService/ListKeys": true,
|
||||
"/metacrypt.v2.BarrierService/RotateMEK": true,
|
||||
"/metacrypt.v2.BarrierService/RotateKey": true,
|
||||
"/metacrypt.v2.BarrierService/Migrate": true,
|
||||
// SSH CA.
|
||||
"/metacrypt.v2.SSHCAService/GetCAPublicKey": true,
|
||||
"/metacrypt.v2.SSHCAService/SignHost": true,
|
||||
@@ -190,118 +332,7 @@ func sealRequiredMethods() map[string]bool {
|
||||
"/metacrypt.v2.TransitService/Verify": true,
|
||||
"/metacrypt.v2.TransitService/Hmac": true,
|
||||
"/metacrypt.v2.TransitService/GetPublicKey": true,
|
||||
}
|
||||
}
|
||||
|
||||
// authRequiredMethods returns the set of RPC full names that require a valid token.
|
||||
func authRequiredMethods() map[string]bool {
|
||||
return map[string]bool{
|
||||
"/metacrypt.v2.AuthService/Logout": true,
|
||||
"/metacrypt.v2.AuthService/TokenInfo": true,
|
||||
"/metacrypt.v2.EngineService/Mount": true,
|
||||
"/metacrypt.v2.EngineService/Unmount": true,
|
||||
"/metacrypt.v2.EngineService/ListMounts": true,
|
||||
"/metacrypt.v2.CAService/ImportRoot": true,
|
||||
"/metacrypt.v2.CAService/CreateIssuer": true,
|
||||
"/metacrypt.v2.CAService/DeleteIssuer": true,
|
||||
"/metacrypt.v2.CAService/ListIssuers": true,
|
||||
"/metacrypt.v2.CAService/IssueCert": true,
|
||||
"/metacrypt.v2.CAService/GetCert": true,
|
||||
"/metacrypt.v2.CAService/ListCerts": true,
|
||||
"/metacrypt.v2.CAService/RenewCert": true,
|
||||
"/metacrypt.v2.CAService/SignCSR": true,
|
||||
"/metacrypt.v2.CAService/RevokeCert": true,
|
||||
"/metacrypt.v2.CAService/DeleteCert": true,
|
||||
"/metacrypt.v2.PolicyService/CreatePolicy": true,
|
||||
"/metacrypt.v2.PolicyService/ListPolicies": true,
|
||||
"/metacrypt.v2.PolicyService/GetPolicy": true,
|
||||
"/metacrypt.v2.PolicyService/DeletePolicy": true,
|
||||
"/metacrypt.v2.UserService/Register": true,
|
||||
"/metacrypt.v2.UserService/Provision": true,
|
||||
"/metacrypt.v2.UserService/GetPublicKey": true,
|
||||
"/metacrypt.v2.UserService/ListUsers": true,
|
||||
"/metacrypt.v2.UserService/Encrypt": true,
|
||||
"/metacrypt.v2.UserService/Decrypt": true,
|
||||
"/metacrypt.v2.UserService/ReEncrypt": true,
|
||||
"/metacrypt.v2.UserService/RotateKey": true,
|
||||
"/metacrypt.v2.UserService/DeleteUser": true,
|
||||
"/metacrypt.v2.ACMEService/CreateEAB": true,
|
||||
"/metacrypt.v2.ACMEService/SetConfig": true,
|
||||
"/metacrypt.v2.ACMEService/ListAccounts": true,
|
||||
"/metacrypt.v2.ACMEService/ListOrders": true,
|
||||
"/metacrypt.v2.BarrierService/ListKeys": true,
|
||||
"/metacrypt.v2.BarrierService/RotateMEK": true,
|
||||
"/metacrypt.v2.BarrierService/RotateKey": true,
|
||||
"/metacrypt.v2.BarrierService/Migrate": true,
|
||||
// SSH CA.
|
||||
"/metacrypt.v2.SSHCAService/SignHost": true,
|
||||
"/metacrypt.v2.SSHCAService/SignUser": true,
|
||||
"/metacrypt.v2.SSHCAService/CreateProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/UpdateProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/GetProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/ListProfiles": true,
|
||||
"/metacrypt.v2.SSHCAService/DeleteProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/GetCert": true,
|
||||
"/metacrypt.v2.SSHCAService/ListCerts": true,
|
||||
"/metacrypt.v2.SSHCAService/RevokeCert": true,
|
||||
"/metacrypt.v2.SSHCAService/DeleteCert": true,
|
||||
// Transit.
|
||||
"/metacrypt.v2.TransitService/CreateKey": true,
|
||||
"/metacrypt.v2.TransitService/DeleteKey": true,
|
||||
"/metacrypt.v2.TransitService/GetKey": true,
|
||||
"/metacrypt.v2.TransitService/ListKeys": true,
|
||||
"/metacrypt.v2.TransitService/RotateKey": true,
|
||||
"/metacrypt.v2.TransitService/UpdateKeyConfig": true,
|
||||
"/metacrypt.v2.TransitService/TrimKey": true,
|
||||
"/metacrypt.v2.TransitService/Encrypt": true,
|
||||
"/metacrypt.v2.TransitService/Decrypt": true,
|
||||
"/metacrypt.v2.TransitService/Rewrap": true,
|
||||
"/metacrypt.v2.TransitService/BatchEncrypt": true,
|
||||
"/metacrypt.v2.TransitService/BatchDecrypt": true,
|
||||
"/metacrypt.v2.TransitService/BatchRewrap": true,
|
||||
"/metacrypt.v2.TransitService/Sign": true,
|
||||
"/metacrypt.v2.TransitService/Verify": true,
|
||||
"/metacrypt.v2.TransitService/Hmac": true,
|
||||
"/metacrypt.v2.TransitService/GetPublicKey": true,
|
||||
}
|
||||
}
|
||||
|
||||
// adminRequiredMethods returns the set of RPC full names that require admin.
|
||||
func adminRequiredMethods() map[string]bool {
|
||||
return map[string]bool{
|
||||
"/metacrypt.v2.SystemService/Seal": true,
|
||||
"/metacrypt.v2.EngineService/Mount": true,
|
||||
"/metacrypt.v2.EngineService/Unmount": true,
|
||||
"/metacrypt.v2.CAService/ImportRoot": true,
|
||||
"/metacrypt.v2.CAService/CreateIssuer": true,
|
||||
"/metacrypt.v2.CAService/DeleteIssuer": true,
|
||||
"/metacrypt.v2.CAService/RevokeCert": true,
|
||||
"/metacrypt.v2.CAService/DeleteCert": true,
|
||||
"/metacrypt.v2.PolicyService/CreatePolicy": true,
|
||||
"/metacrypt.v2.PolicyService/ListPolicies": true,
|
||||
"/metacrypt.v2.PolicyService/GetPolicy": true,
|
||||
"/metacrypt.v2.PolicyService/DeletePolicy": true,
|
||||
// User.
|
||||
"/metacrypt.v2.UserService/Provision": true,
|
||||
"/metacrypt.v2.UserService/DeleteUser": true,
|
||||
"/metacrypt.v2.ACMEService/SetConfig": true,
|
||||
"/metacrypt.v2.ACMEService/ListAccounts": true,
|
||||
"/metacrypt.v2.ACMEService/ListOrders": true,
|
||||
"/metacrypt.v2.BarrierService/ListKeys": true,
|
||||
"/metacrypt.v2.BarrierService/RotateMEK": true,
|
||||
"/metacrypt.v2.BarrierService/RotateKey": true,
|
||||
"/metacrypt.v2.BarrierService/Migrate": true,
|
||||
// SSH CA.
|
||||
"/metacrypt.v2.SSHCAService/CreateProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/UpdateProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/DeleteProfile": true,
|
||||
"/metacrypt.v2.SSHCAService/RevokeCert": true,
|
||||
"/metacrypt.v2.SSHCAService/DeleteCert": true,
|
||||
// Transit.
|
||||
"/metacrypt.v2.TransitService/CreateKey": true,
|
||||
"/metacrypt.v2.TransitService/DeleteKey": true,
|
||||
"/metacrypt.v2.TransitService/RotateKey": true,
|
||||
"/metacrypt.v2.TransitService/UpdateKeyConfig": true,
|
||||
"/metacrypt.v2.TransitService/TrimKey": true,
|
||||
// Seal itself requires unsealed state.
|
||||
"/metacrypt.v2.SystemService/Seal": true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/auth"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine/sshca"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/policy"
|
||||
@@ -50,7 +51,7 @@ func (ss *sshcaServer) sshcaHandleRequest(ctx context.Context, mount, operation
|
||||
}
|
||||
|
||||
func (ss *sshcaServer) callerInfo(ctx context.Context) *engine.CallerInfo {
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
ti := auth.TokenInfoFromContext(ctx)
|
||||
if ti == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/auth"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine/transit"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/policy"
|
||||
@@ -58,7 +59,7 @@ func (ts *transitServer) transitHandleRequest(ctx context.Context, mount, operat
|
||||
}
|
||||
|
||||
func (ts *transitServer) callerInfo(ctx context.Context) *engine.CallerInfo {
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
ti := auth.TokenInfoFromContext(ctx)
|
||||
if ti == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/auth"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine/user"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/policy"
|
||||
@@ -20,7 +21,7 @@ type userServer struct {
|
||||
}
|
||||
|
||||
func (us *userServer) callerInfo(ctx context.Context) *engine.CallerInfo {
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
ti := auth.TokenInfoFromContext(ctx)
|
||||
if ti == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user