diff --git a/cmd/boulder-mtca/main.go b/cmd/boulder-mtca/main.go new file mode 100644 index 00000000000..dd358d2dd73 --- /dev/null +++ b/cmd/boulder-mtca/main.go @@ -0,0 +1,81 @@ +package notmain + +import ( + "context" + "flag" + "os" + + "github.com/jmhodges/clock" + + "github.com/letsencrypt/boulder/cmd" + bgrpc "github.com/letsencrypt/boulder/grpc" + "github.com/letsencrypt/boulder/issuance" + mtca "github.com/letsencrypt/boulder/mtca" + mtcapb "github.com/letsencrypt/boulder/mtca/proto" +) + +type Config struct { + MTCA struct { + cmd.ServiceConfig + + GRPCMTCA *cmd.GRPCServerConfig + + // Issuer holds the configuration for a single MTCA instance with a single mtcaID. + // We run a separate process for each issuer. + // TODO: the issuance package parses the CA certificate as a self-signed X.509 + // certificate, but per MTC draft, a CA SHOULD be represented by an RFC 9925 + // unsigned certificate: https://www.rfc-editor.org/rfc/rfc9925.html. + Issuer issuance.IssuerConfig + } + + Syslog cmd.SyslogConfig + OpenTelemetry cmd.OpenTelemetryConfig +} + +func main() { + grpcAddr := flag.String("addr", "", "gRPC listen address override") + debugAddr := flag.String("debug-addr", "", "Debug server address override") + configFile := flag.String("config", "", "File path to the configuration file for this service") + flag.Parse() + if *configFile == "" { + flag.Usage() + os.Exit(1) + } + + var c Config + err := cmd.ReadConfigFile(*configFile, &c) + cmd.FailOnError(err, "Reading JSON config file into config structure") + + if *grpcAddr != "" { + c.MTCA.GRPCMTCA.Address = *grpcAddr + } + if *debugAddr != "" { + c.MTCA.DebugAddr = *debugAddr + } + + scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.MTCA.DebugAddr) + defer oTelShutdown(context.Background()) + cmd.LogStartup(logger) + + tlsConfig, err := c.MTCA.TLS.Load(scope) + cmd.FailOnError(err, "Loading TLS config") + + clk := clock.New() + + issuer, err := issuance.LoadIssuer(c.MTCA.Issuer, clk) + cmd.FailOnError(err, "Loading issuer") + + mtcaImpl := mtca.New(issuer) + + srv := bgrpc.NewServer(c.MTCA.GRPCMTCA, logger).Add( + &mtcapb.MTCA_ServiceDesc, mtcaImpl) + + start, err := srv.Build(tlsConfig, scope, clk) + cmd.FailOnError(err, "Unable to setup MTCA gRPC server") + + cmd.FailOnError(start(), "MTCA gRPC service failed") +} + +func init() { + cmd.RegisterCommand("boulder-mtca", main, &cmd.ConfigValidator{Config: &Config{}}) +} diff --git a/cmd/boulder/main.go b/cmd/boulder/main.go index 7d2f4ef77e9..f202fae8de5 100644 --- a/cmd/boulder/main.go +++ b/cmd/boulder/main.go @@ -7,6 +7,7 @@ import ( _ "github.com/letsencrypt/boulder/cmd/bad-key-revoker" _ "github.com/letsencrypt/boulder/cmd/boulder-ca" + _ "github.com/letsencrypt/boulder/cmd/boulder-mtca" _ "github.com/letsencrypt/boulder/cmd/boulder-observer" _ "github.com/letsencrypt/boulder/cmd/boulder-publisher" _ "github.com/letsencrypt/boulder/cmd/boulder-ra" diff --git a/cmd/boulder/main_test.go b/cmd/boulder/main_test.go index 1dbcb25b0bc..d27e56e8837 100644 --- a/cmd/boulder/main_test.go +++ b/cmd/boulder/main_test.go @@ -31,6 +31,8 @@ func TestConfigValidation(t *testing.T) { switch cmdName { case "boulder-ca": fileNames = []string{"ca.json"} + case "boulder-mtca": + fileNames = []string{"mtca.json"} case "boulder-observer": fileNames = []string{"observer.yml"} case "boulder-publisher": diff --git a/mtca/mtca.go b/mtca/mtca.go new file mode 100644 index 00000000000..f57a15578f5 --- /dev/null +++ b/mtca/mtca.go @@ -0,0 +1,26 @@ +package mtca + +import ( + "context" + "fmt" + + "github.com/letsencrypt/boulder/issuance" + mtcapb "github.com/letsencrypt/boulder/mtca/proto" +) + +var _ mtcapb.MTCAServer = &mtca{} + +func New(issuer *issuance.Issuer) *mtca { + return &mtca{ + issuer: issuer, + } +} + +type mtca struct { + mtcapb.UnimplementedMTCAServer + issuer *issuance.Issuer +} + +func (m *mtca) Issue(ctx context.Context, req *mtcapb.IssueRequest) (*mtcapb.IssueResponse, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/mtca/proto/mtca.pb.go b/mtca/proto/mtca.pb.go new file mode 100644 index 00000000000..aee4e621728 --- /dev/null +++ b/mtca/proto/mtca.pb.go @@ -0,0 +1,218 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.20.1 +// source: mtca.proto + +package proto + +import ( + proto "github.com/letsencrypt/boulder/core/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type IssueRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Next unused field number: 4 + Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + Identifiers []*proto.Identifier `protobuf:"bytes,2,rep,name=identifiers,proto3" json:"identifiers,omitempty"` + Profile string `protobuf:"bytes,3,opt,name=profile,proto3" json:"profile,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IssueRequest) Reset() { + *x = IssueRequest{} + mi := &file_mtca_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IssueRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IssueRequest) ProtoMessage() {} + +func (x *IssueRequest) ProtoReflect() protoreflect.Message { + mi := &file_mtca_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IssueRequest.ProtoReflect.Descriptor instead. +func (*IssueRequest) Descriptor() ([]byte, []int) { + return file_mtca_proto_rawDescGZIP(), []int{0} +} + +func (x *IssueRequest) GetPubkey() []byte { + if x != nil { + return x.Pubkey + } + return nil +} + +func (x *IssueRequest) GetIdentifiers() []*proto.Identifier { + if x != nil { + return x.Identifiers + } + return nil +} + +func (x *IssueRequest) GetProfile() string { + if x != nil { + return x.Profile + } + return "" +} + +type IssueResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Next unused field number: 4 + MtcLogID string `protobuf:"bytes,1,opt,name=mtcLogID,proto3" json:"mtcLogID,omitempty"` + MtcEntryIndex int64 `protobuf:"varint,2,opt,name=mtcEntryIndex,proto3" json:"mtcEntryIndex,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IssueResponse) Reset() { + *x = IssueResponse{} + mi := &file_mtca_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IssueResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IssueResponse) ProtoMessage() {} + +func (x *IssueResponse) ProtoReflect() protoreflect.Message { + mi := &file_mtca_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IssueResponse.ProtoReflect.Descriptor instead. +func (*IssueResponse) Descriptor() ([]byte, []int) { + return file_mtca_proto_rawDescGZIP(), []int{1} +} + +func (x *IssueResponse) GetMtcLogID() string { + if x != nil { + return x.MtcLogID + } + return "" +} + +func (x *IssueResponse) GetMtcEntryIndex() int64 { + if x != nil { + return x.MtcEntryIndex + } + return 0 +} + +var File_mtca_proto protoreflect.FileDescriptor + +var file_mtca_proto_rawDesc = string([]byte{ + 0x0a, 0x0a, 0x6d, 0x74, 0x63, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6d, 0x74, + 0x63, 0x61, 0x1a, 0x15, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x74, 0x0a, 0x0c, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, + 0x51, 0x0a, 0x0d, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x74, 0x63, 0x4c, 0x6f, 0x67, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x6d, 0x74, 0x63, 0x4c, 0x6f, 0x67, 0x49, 0x44, 0x12, 0x24, 0x0a, 0x0d, + 0x6d, 0x74, 0x63, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x74, 0x63, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x32, 0x3a, 0x0a, 0x04, 0x4d, 0x54, 0x43, 0x41, 0x12, 0x32, 0x0a, 0x05, 0x49, 0x73, + 0x73, 0x75, 0x65, 0x12, 0x12, 0x2e, 0x6d, 0x74, 0x63, 0x61, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6d, 0x74, 0x63, 0x61, 0x2e, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2b, + 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, + 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, + 0x2f, 0x6d, 0x74, 0x63, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +}) + +var ( + file_mtca_proto_rawDescOnce sync.Once + file_mtca_proto_rawDescData []byte +) + +func file_mtca_proto_rawDescGZIP() []byte { + file_mtca_proto_rawDescOnce.Do(func() { + file_mtca_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_mtca_proto_rawDesc), len(file_mtca_proto_rawDesc))) + }) + return file_mtca_proto_rawDescData +} + +var file_mtca_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_mtca_proto_goTypes = []any{ + (*IssueRequest)(nil), // 0: mtca.IssueRequest + (*IssueResponse)(nil), // 1: mtca.IssueResponse + (*proto.Identifier)(nil), // 2: core.Identifier +} +var file_mtca_proto_depIdxs = []int32{ + 2, // 0: mtca.IssueRequest.identifiers:type_name -> core.Identifier + 0, // 1: mtca.MTCA.Issue:input_type -> mtca.IssueRequest + 1, // 2: mtca.MTCA.Issue:output_type -> mtca.IssueResponse + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_mtca_proto_init() } +func file_mtca_proto_init() { + if File_mtca_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_mtca_proto_rawDesc), len(file_mtca_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_mtca_proto_goTypes, + DependencyIndexes: file_mtca_proto_depIdxs, + MessageInfos: file_mtca_proto_msgTypes, + }.Build() + File_mtca_proto = out.File + file_mtca_proto_goTypes = nil + file_mtca_proto_depIdxs = nil +} diff --git a/mtca/proto/mtca.proto b/mtca/proto/mtca.proto new file mode 100644 index 00000000000..523aab58801 --- /dev/null +++ b/mtca/proto/mtca.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package mtca; +option go_package = "github.com/letsencrypt/boulder/mtca/proto"; + +import "core/proto/core.proto"; + +// MTCA issues MTC certificates. +service MTCA { + // Issue requests that the CA start the process of creating a standalone certificate + // for the given request. It returns once a checkpoint has been signed that includes + // that certificate's TBSCertificateLogEntry, but does not wait for cosignatures. + rpc Issue(IssueRequest) returns (IssueResponse) {} +} + +message IssueRequest { + // Next unused field number: 4 + bytes pubkey = 1; + repeated core.Identifier identifiers = 2; + string profile = 3; +} + +message IssueResponse { + // Next unused field number: 4 + string mtcLogID = 1; + int64 mtcEntryIndex = 2; +} diff --git a/mtca/proto/mtca_grpc.pb.go b/mtca/proto/mtca_grpc.pb.go new file mode 100644 index 00000000000..dd9e6b8adaa --- /dev/null +++ b/mtca/proto/mtca_grpc.pb.go @@ -0,0 +1,131 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.20.1 +// source: mtca.proto + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + MTCA_Issue_FullMethodName = "/mtca.MTCA/Issue" +) + +// MTCAClient is the client API for MTCA service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// MTCA issues MTC certificates. +type MTCAClient interface { + // Issue requests that the CA start the process of creating a standalone certificate + // for the given request. It returns once a checkpoint has been signed that includes + // that certificate's TBSCertificateLogEntry, but does not wait for cosignatures. + Issue(ctx context.Context, in *IssueRequest, opts ...grpc.CallOption) (*IssueResponse, error) +} + +type mTCAClient struct { + cc grpc.ClientConnInterface +} + +func NewMTCAClient(cc grpc.ClientConnInterface) MTCAClient { + return &mTCAClient{cc} +} + +func (c *mTCAClient) Issue(ctx context.Context, in *IssueRequest, opts ...grpc.CallOption) (*IssueResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(IssueResponse) + err := c.cc.Invoke(ctx, MTCA_Issue_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MTCAServer is the server API for MTCA service. +// All implementations must embed UnimplementedMTCAServer +// for forward compatibility. +// +// MTCA issues MTC certificates. +type MTCAServer interface { + // Issue requests that the CA start the process of creating a standalone certificate + // for the given request. It returns once a checkpoint has been signed that includes + // that certificate's TBSCertificateLogEntry, but does not wait for cosignatures. + Issue(context.Context, *IssueRequest) (*IssueResponse, error) + mustEmbedUnimplementedMTCAServer() +} + +// UnimplementedMTCAServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedMTCAServer struct{} + +func (UnimplementedMTCAServer) Issue(context.Context, *IssueRequest) (*IssueResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Issue not implemented") +} +func (UnimplementedMTCAServer) mustEmbedUnimplementedMTCAServer() {} +func (UnimplementedMTCAServer) testEmbeddedByValue() {} + +// UnsafeMTCAServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to MTCAServer will +// result in compilation errors. +type UnsafeMTCAServer interface { + mustEmbedUnimplementedMTCAServer() +} + +func RegisterMTCAServer(s grpc.ServiceRegistrar, srv MTCAServer) { + // If the following call pancis, it indicates UnimplementedMTCAServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&MTCA_ServiceDesc, srv) +} + +func _MTCA_Issue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IssueRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MTCAServer).Issue(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MTCA_Issue_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MTCAServer).Issue(ctx, req.(*IssueRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// MTCA_ServiceDesc is the grpc.ServiceDesc for MTCA service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var MTCA_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "mtca.MTCA", + HandlerType: (*MTCAServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Issue", + Handler: _MTCA_Issue_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "mtca.proto", +} diff --git a/sa/db/01-boulder_sa_next.sql b/sa/db/01-boulder_sa_next.sql index a33ae2fed2b..30b8e291cba 100644 --- a/sa/db/01-boulder_sa_next.sql +++ b/sa/db/01-boulder_sa_next.sql @@ -250,5 +250,5 @@ ALTER TABLE `revokedCertificates` ADD KEY `serial` (`serial`); ALTER TABLE `orders` ADD COLUMN `isMTC` bool NOT NULL DEFAULT FALSE, - ADD COLUMN `mtcaID` varchar(255) DEFAULT NULL, + ADD COLUMN `mtcLogID` varchar(255) DEFAULT NULL, ADD COLUMN `mtcSerialNumber` bigint(20) unsigned DEFAULT NULL; diff --git a/test/certs/generate.sh b/test/certs/generate.sh index d7cbd42d3e3..6c576cf2269 100755 --- a/test/certs/generate.sh +++ b/test/certs/generate.sh @@ -41,7 +41,7 @@ ipki() ( # Used by Boulder gRPC services as both server and client mTLS certificates. for SERVICE in admin consul wfe bad-key-revoker \ - crl-updater crl-storer health-checker sfe email-exporter; do + crl-updater crl-storer health-checker sfe email-exporter mtca; do minica -domains "${SERVICE}.boulder" & done diff --git a/test/config-next/mtca.json b/test/config-next/mtca.json new file mode 100644 index 00000000000..679125a63e8 --- /dev/null +++ b/test/config-next/mtca.json @@ -0,0 +1,41 @@ +{ + "mtca": { + "tls": { + "caCertFile": "test/certs/ipki/minica.pem", + "certFile": "test/certs/ipki/mtca.boulder/cert.pem", + "keyFile": "test/certs/ipki/mtca.boulder/key.pem" + }, + "grpcMTCA": { + "maxConnectionAge": "30s", + "services": { + "mtca.MTCA": { + "clientNames": [ + "ra.boulder" + ] + }, + "grpc.health.v1.Health": { + "clientNames": [ + "health-checker.boulder" + ] + } + } + }, + "issuer": { + "crlShards": 10, + "issuerURL": "http://ignored.letsencrypt.org", + "crlURLBase": "http://ignored.letsencrypt.org/", + "location": { + "file": "test/certs/mtpki/mtca1.key.pem", + "certFile": "test/certs/mtpki/mtca1.cert.pem" + } + } + }, + "syslog": { + "stdoutlevel": 4, + "sysloglevel": -1 + }, + "openTelemetry": { + "endpoint": "bjaeger:4317", + "sampleratio": 1 + } +} diff --git a/test/config/mtca.json b/test/config/mtca.json new file mode 100644 index 00000000000..679125a63e8 --- /dev/null +++ b/test/config/mtca.json @@ -0,0 +1,41 @@ +{ + "mtca": { + "tls": { + "caCertFile": "test/certs/ipki/minica.pem", + "certFile": "test/certs/ipki/mtca.boulder/cert.pem", + "keyFile": "test/certs/ipki/mtca.boulder/key.pem" + }, + "grpcMTCA": { + "maxConnectionAge": "30s", + "services": { + "mtca.MTCA": { + "clientNames": [ + "ra.boulder" + ] + }, + "grpc.health.v1.Health": { + "clientNames": [ + "health-checker.boulder" + ] + } + } + }, + "issuer": { + "crlShards": 10, + "issuerURL": "http://ignored.letsencrypt.org", + "crlURLBase": "http://ignored.letsencrypt.org/", + "location": { + "file": "test/certs/mtpki/mtca1.key.pem", + "certFile": "test/certs/mtpki/mtca1.cert.pem" + } + } + }, + "syslog": { + "stdoutlevel": 4, + "sysloglevel": -1 + }, + "openTelemetry": { + "endpoint": "bjaeger:4317", + "sampleratio": 1 + } +} diff --git a/test/consul/config.hcl b/test/consul/config.hcl index bb3b2462823..35443d9c3c9 100644 --- a/test/consul/config.hcl +++ b/test/consul/config.hcl @@ -45,6 +45,14 @@ services { address = "10.77.77.77" } +services { + id = "mtca1" + name = "mtca" + address = "10.77.77.77" + port = 9396 + tags = ["tcp"] // Required for SRV RR support in gRPC DNS resolution. +} + services { id = "ca-a" name = "ca" diff --git a/test/startservers.py b/test/startservers.py index c2da443b4ee..f1de143d4ba 100644 --- a/test/startservers.py +++ b/test/startservers.py @@ -53,6 +53,10 @@ 8104, 9492, 'va.boulder', ('./bin/boulder', 'boulder-va', '--config', os.path.join(config_dir, 'va.json'), '--addr', ':9492', '--debug-addr', ':8104'), ('remoteva-a', 'remoteva-b')), + Service('boulder-mtca-1', + 8010, 9396, 'mtca.boulder', + ('./bin/boulder', 'boulder-mtca', '--config', os.path.join(config_dir, 'mtca.json'), '--addr', ':9396', '--debug-addr', ':8010'), + None), Service('boulder-ca-1', 8001, 9393, 'ca.boulder', ('./bin/boulder', 'boulder-ca', '--config', os.path.join(config_dir, 'ca.json'), '--addr', ':9393', '--debug-addr', ':8001'),