Files
metacrypt/internal/grpcserver/acme.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

133 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/mc/metacrypt/gen/metacrypt/v2"
internacme "git.wntrmute.dev/mc/metacrypt/internal/acme"
"git.wntrmute.dev/mc/metacrypt/internal/auth"
"git.wntrmute.dev/mc/metacrypt/internal/engine"
)
type acmeServer struct {
pb.UnimplementedACMEServiceServer
s *GRPCServer
}
func (as *acmeServer) CreateEAB(ctx context.Context, req *pb.CreateEABRequest) (*pb.CreateEABResponse, error) {
ti := auth.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
}