Files
metacrypt/internal/grpcserver/acme.go
Kyle Isom 8215aaccc5 Add grpcserver test coverage
- Add comprehensive test file for internal/grpcserver package
- Cover interceptors, system, engine, policy, and auth handlers
- Cover pbToRule/ruleToPB conversion helpers
- 37 tests total; CA/PKI/ACME and Login/Logout skipped (require live deps)

Co-authored-by: Junie <junie@jetbrains.com>
2026-03-15 13:07:42 -07:00

132 lines
4.2 KiB
Go

package grpcserver
import (
"context"
"encoding/json"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2"
internacme "git.wntrmute.dev/kyle/metacrypt/internal/acme"
"git.wntrmute.dev/kyle/metacrypt/internal/engine"
)
type acmeServer struct {
pb.UnimplementedACMEServiceServer
s *GRPCServer
}
func (as *acmeServer) CreateEAB(ctx context.Context, req *pb.CreateEABRequest) (*pb.CreateEABResponse, error) {
ti := tokenInfoFromContext(ctx)
h, err := as.getOrCreateHandler(req.Mount)
if err != nil {
return nil, status.Error(codes.NotFound, "mount not found")
}
cred, err := h.CreateEAB(ctx, ti.Username)
if err != nil {
as.s.logger.Error("grpc: acme create EAB", "error", err)
return nil, status.Error(codes.Internal, "failed to create EAB credentials")
}
return &pb.CreateEABResponse{Kid: cred.KID, HmacKey: cred.HMACKey}, nil
}
func (as *acmeServer) SetConfig(ctx context.Context, req *pb.SetConfigRequest) (*pb.SetConfigResponse, error) {
if req.DefaultIssuer == "" {
return nil, status.Error(codes.InvalidArgument, "default_issuer is required")
}
// Verify mount exists.
if _, err := as.getOrCreateHandler(req.Mount); err != nil {
return nil, status.Error(codes.NotFound, "mount not found")
}
cfg := &internacme.ACMEConfig{DefaultIssuer: req.DefaultIssuer}
data, _ := json.Marshal(cfg)
barrierPath := "acme/" + req.Mount + "/config.json"
if err := as.s.sealMgr.Barrier().Put(ctx, barrierPath, data); err != nil {
as.s.logger.Error("grpc: acme set config", "error", err)
return nil, status.Error(codes.Internal, "failed to save config")
}
return &pb.SetConfigResponse{}, nil
}
func (as *acmeServer) ListAccounts(ctx context.Context, req *pb.ListAccountsRequest) (*pb.ListAccountsResponse, error) {
h, err := as.getOrCreateHandler(req.Mount)
if err != nil {
return nil, status.Error(codes.NotFound, "mount not found")
}
accounts, err := h.ListAccounts(ctx)
if err != nil {
as.s.logger.Error("grpc: acme list accounts", "error", err)
return nil, status.Error(codes.Internal, "internal error")
}
pbAccounts := make([]*pb.ACMEAccount, 0, len(accounts))
for _, a := range accounts {
contacts := make([]string, len(a.Contact))
copy(contacts, a.Contact)
pbAccounts = append(pbAccounts, &pb.ACMEAccount{
Id: a.ID,
Status: a.Status,
Contact: contacts,
MciasUsername: a.MCIASUsername,
CreatedAt: timestamppb.New(a.CreatedAt),
})
}
return &pb.ListAccountsResponse{Accounts: pbAccounts}, nil
}
func (as *acmeServer) ListOrders(ctx context.Context, req *pb.ListOrdersRequest) (*pb.ListOrdersResponse, error) {
h, err := as.getOrCreateHandler(req.Mount)
if err != nil {
return nil, status.Error(codes.NotFound, "mount not found")
}
orders, err := h.ListOrders(ctx)
if err != nil {
as.s.logger.Error("grpc: acme list orders", "error", err)
return nil, status.Error(codes.Internal, "internal error")
}
pbOrders := make([]*pb.ACMEOrder, 0, len(orders))
for _, o := range orders {
identifiers := make([]string, 0, len(o.Identifiers))
for _, id := range o.Identifiers {
identifiers = append(identifiers, id.Type+":"+id.Value)
}
pbOrders = append(pbOrders, &pb.ACMEOrder{
Id: o.ID,
AccountId: o.AccountID,
Status: o.Status,
Identifiers: identifiers,
CreatedAt: timestamppb.New(o.CreatedAt),
ExpiresAt: timestamppb.New(o.ExpiresAt),
})
}
return &pb.ListOrdersResponse{Orders: pbOrders}, nil
}
func (as *acmeServer) getOrCreateHandler(mountName string) (*internacme.Handler, error) {
as.s.mu.Lock()
defer as.s.mu.Unlock()
// Verify mount is a CA engine.
mount, err := as.s.engines.GetMount(mountName)
if err != nil {
return nil, err
}
if mount.Type != engine.EngineTypeCA {
return nil, engine.ErrMountNotFound
}
// Check handler cache on GRPCServer.
if h, ok := as.s.acmeHandlers[mountName]; ok {
return h, nil
}
baseURL := as.s.cfg.Server.ExternalURL
if baseURL == "" {
baseURL = "https://" + as.s.cfg.Server.ListenAddr
}
h := internacme.NewHandler(mountName, as.s.sealMgr.Barrier(), as.s.engines, baseURL, as.s.logger)
as.s.acmeHandlers[mountName] = h
return h, nil
}