Separate web UI into standalone metacrypt-web binary
The vault server holds in-memory unsealed state (KEK, engine keys) that is lost on restart, requiring a full unseal ceremony. Previously the web UI ran inside the vault process, so any UI change forced a restart and re-unseal. This change extracts the web UI into a separate metacrypt-web binary that communicates with the vault over an authenticated gRPC connection. The web server carries no sealed state and can be restarted freely. - gen/metacrypt/v1/: generated Go bindings from proto/metacrypt/v1/ - internal/grpcserver/: full gRPC server implementation (System, Auth, Engine, PKI, Policy, ACME services) with seal/auth/admin interceptors - internal/webserver/: web server with gRPC vault client; templates embedded via web/embed.go (no runtime web/ directory needed) - cmd/metacrypt-web/: standalone binary entry point - internal/config: added [web] section (listen_addr, vault_grpc, etc.) - internal/server/routes.go: removed all web UI routes and handlers - cmd/metacrypt/server.go: starts gRPC server alongside HTTP server - Deploy: Dockerfile builds both binaries, docker-compose adds metacrypt-web service, new metacrypt-web.service systemd unit, Makefile gains proto/metacrypt-web targets Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
56
internal/grpcserver/auth.go
Normal file
56
internal/grpcserver/auth.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package grpcserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
mcias "git.wntrmute.dev/kyle/mcias/clients/go"
|
||||
|
||||
pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v1"
|
||||
)
|
||||
|
||||
type authServer struct {
|
||||
pb.UnimplementedAuthServiceServer
|
||||
s *GRPCServer
|
||||
}
|
||||
|
||||
func (as *authServer) Login(_ context.Context, req *pb.LoginRequest) (*pb.LoginResponse, error) {
|
||||
token, expiresAt, err := as.s.auth.Login(req.Username, req.Password, req.TotpCode)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Unauthenticated, "invalid credentials")
|
||||
}
|
||||
return &pb.LoginResponse{Token: token, ExpiresAt: expiresAt}, nil
|
||||
}
|
||||
|
||||
func (as *authServer) Logout(ctx context.Context, _ *pb.LogoutRequest) (*pb.LogoutResponse, error) {
|
||||
token := extractToken(ctx)
|
||||
client, err := mcias.New(as.s.cfg.MCIAS.ServerURL, mcias.Options{
|
||||
CACertPath: as.s.cfg.MCIAS.CACert,
|
||||
Token: token,
|
||||
})
|
||||
if err == nil {
|
||||
as.s.auth.Logout(client)
|
||||
}
|
||||
return &pb.LogoutResponse{}, nil
|
||||
}
|
||||
|
||||
func (as *authServer) TokenInfo(ctx context.Context, _ *pb.TokenInfoRequest) (*pb.TokenInfoResponse, error) {
|
||||
ti := tokenInfoFromContext(ctx)
|
||||
if ti == nil {
|
||||
// Shouldn't happen — authInterceptor runs first — but guard anyway.
|
||||
token := extractToken(ctx)
|
||||
var err error
|
||||
ti, err = as.s.auth.ValidateToken(token)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Unauthenticated, "invalid token")
|
||||
}
|
||||
}
|
||||
return &pb.TokenInfoResponse{
|
||||
Username: ti.Username,
|
||||
Roles: ti.Roles,
|
||||
IsAdmin: ti.IsAdmin,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user