Split out separate RPC services for issuing and for signing OCSP (#2452)

This allows finer-grained control of which components can request issuance. The OCSP Updater should not be able to request issuance.

Also, update test/grpc-creds/generate.sh to reissue the certs properly.

Resolves #2417
This commit is contained in:
Jacob Hoffman-Andrews 2017-01-05 15:08:39 -08:00 committed by Roland Bracewell Shoemaker
parent 74c5e68491
commit 9b8dacab03
11 changed files with 162 additions and 76 deletions

View File

@ -137,7 +137,6 @@ const _ = grpc.SupportPackageIsVersion3
type CertificateAuthorityClient interface {
IssueCertificate(ctx context.Context, in *IssueCertificateRequest, opts ...grpc.CallOption) (*core.Certificate, error)
GenerateOCSP(ctx context.Context, in *GenerateOCSPRequest, opts ...grpc.CallOption) (*OCSPResponse, error)
}
type certificateAuthorityClient struct {
@ -157,20 +156,10 @@ func (c *certificateAuthorityClient) IssueCertificate(ctx context.Context, in *I
return out, nil
}
func (c *certificateAuthorityClient) GenerateOCSP(ctx context.Context, in *GenerateOCSPRequest, opts ...grpc.CallOption) (*OCSPResponse, error) {
out := new(OCSPResponse)
err := grpc.Invoke(ctx, "/ca.CertificateAuthority/GenerateOCSP", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for CertificateAuthority service
type CertificateAuthorityServer interface {
IssueCertificate(context.Context, *IssueCertificateRequest) (*core.Certificate, error)
GenerateOCSP(context.Context, *GenerateOCSPRequest) (*OCSPResponse, error)
}
func RegisterCertificateAuthorityServer(s *grpc.Server, srv CertificateAuthorityServer) {
@ -195,24 +184,6 @@ func _CertificateAuthority_IssueCertificate_Handler(srv interface{}, ctx context
return interceptor(ctx, in, info, handler)
}
func _CertificateAuthority_GenerateOCSP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GenerateOCSPRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CertificateAuthorityServer).GenerateOCSP(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ca.CertificateAuthority/GenerateOCSP",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CertificateAuthorityServer).GenerateOCSP(ctx, req.(*GenerateOCSPRequest))
}
return interceptor(ctx, in, info, handler)
}
var _CertificateAuthority_serviceDesc = grpc.ServiceDesc{
ServiceName: "ca.CertificateAuthority",
HandlerType: (*CertificateAuthorityServer)(nil),
@ -221,9 +192,69 @@ var _CertificateAuthority_serviceDesc = grpc.ServiceDesc{
MethodName: "IssueCertificate",
Handler: _CertificateAuthority_IssueCertificate_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: fileDescriptor0,
}
// Client API for OCSPGenerator service
type OCSPGeneratorClient interface {
GenerateOCSP(ctx context.Context, in *GenerateOCSPRequest, opts ...grpc.CallOption) (*OCSPResponse, error)
}
type oCSPGeneratorClient struct {
cc *grpc.ClientConn
}
func NewOCSPGeneratorClient(cc *grpc.ClientConn) OCSPGeneratorClient {
return &oCSPGeneratorClient{cc}
}
func (c *oCSPGeneratorClient) GenerateOCSP(ctx context.Context, in *GenerateOCSPRequest, opts ...grpc.CallOption) (*OCSPResponse, error) {
out := new(OCSPResponse)
err := grpc.Invoke(ctx, "/ca.OCSPGenerator/GenerateOCSP", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for OCSPGenerator service
type OCSPGeneratorServer interface {
GenerateOCSP(context.Context, *GenerateOCSPRequest) (*OCSPResponse, error)
}
func RegisterOCSPGeneratorServer(s *grpc.Server, srv OCSPGeneratorServer) {
s.RegisterService(&_OCSPGenerator_serviceDesc, srv)
}
func _OCSPGenerator_GenerateOCSP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GenerateOCSPRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OCSPGeneratorServer).GenerateOCSP(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ca.OCSPGenerator/GenerateOCSP",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OCSPGeneratorServer).GenerateOCSP(ctx, req.(*GenerateOCSPRequest))
}
return interceptor(ctx, in, info, handler)
}
var _OCSPGenerator_serviceDesc = grpc.ServiceDesc{
ServiceName: "ca.OCSPGenerator",
HandlerType: (*OCSPGeneratorServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GenerateOCSP",
Handler: _CertificateAuthority_GenerateOCSP_Handler,
Handler: _OCSPGenerator_GenerateOCSP_Handler,
},
},
Streams: []grpc.StreamDesc{},
@ -233,22 +264,23 @@ var _CertificateAuthority_serviceDesc = grpc.ServiceDesc{
func init() { proto1.RegisterFile("ca/proto/ca.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 267 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x90, 0x4d, 0x4b, 0xc3, 0x40,
0x10, 0x86, 0x9b, 0xc6, 0x5a, 0x3b, 0x86, 0x9a, 0xac, 0x1f, 0x0d, 0xf1, 0x12, 0x72, 0xca, 0x29,
0x05, 0xaf, 0x82, 0x50, 0x1b, 0x91, 0x9e, 0x94, 0x7a, 0xd2, 0xdb, 0xb2, 0x8e, 0x1a, 0x84, 0x6c,
0x9d, 0x99, 0x08, 0xfe, 0x14, 0xff, 0xad, 0x64, 0xd3, 0x42, 0x10, 0xbd, 0xcd, 0xf0, 0xf2, 0x3e,
0xcc, 0x33, 0x10, 0x19, 0x3d, 0xdf, 0x90, 0x15, 0x3b, 0x37, 0xba, 0x70, 0x83, 0x1a, 0x1a, 0x9d,
0x9c, 0x1a, 0x4b, 0xb8, 0x0b, 0x2c, 0x61, 0x17, 0x65, 0x57, 0x30, 0x5b, 0x31, 0x37, 0xb8, 0x44,
0x92, 0xea, 0xa5, 0x32, 0x5a, 0x70, 0x8d, 0x1f, 0x0d, 0xb2, 0xa8, 0x43, 0xf0, 0x0d, 0x53, 0xec,
0xa5, 0x5e, 0x1e, 0xa8, 0x33, 0x98, 0x12, 0xbe, 0x56, 0x2c, 0xa4, 0xa5, 0xb2, 0xf5, 0xaa, 0x8c,
0x87, 0xa9, 0x97, 0xfb, 0xd9, 0x23, 0x1c, 0xdf, 0x62, 0x8d, 0xa4, 0x05, 0xef, 0x96, 0x0f, 0xf7,
0xbb, 0xee, 0x11, 0x8c, 0x0d, 0x92, 0x94, 0x37, 0xeb, 0x6d, 0x7f, 0x0a, 0xfb, 0x2c, 0x5a, 0x1a,
0x76, 0xbd, 0x49, 0xbb, 0x13, 0x6a, 0xb6, 0x75, 0xec, 0xa7, 0x5e, 0x3e, 0x52, 0x11, 0x4c, 0x08,
0x3f, 0xed, 0x3b, 0x3e, 0x2f, 0x24, 0xde, 0x73, 0xe8, 0x14, 0x82, 0x0e, 0xc9, 0x1b, 0x5b, 0x33,
0xaa, 0x10, 0x0e, 0x68, 0x3b, 0x77, 0xd0, 0x8b, 0x6f, 0x0f, 0x4e, 0x7a, 0x87, 0x2f, 0x1a, 0x79,
0xb3, 0x54, 0xc9, 0x97, 0x2a, 0x21, 0xfc, 0x6d, 0xa5, 0xce, 0x0b, 0xa3, 0x8b, 0x7f, 0x5c, 0x93,
0xa8, 0x70, 0x3f, 0xe9, 0x25, 0xd9, 0x40, 0x5d, 0x42, 0xd0, 0x77, 0x53, 0xb3, 0x96, 0xf0, 0x87,
0x6d, 0x12, 0xb6, 0x41, 0xff, 0xd6, 0x6c, 0x70, 0x3d, 0x7e, 0x1a, 0xb9, 0x0f, 0xff, 0x04, 0x00,
0x00, 0xff, 0xff, 0x08, 0x1f, 0xbb, 0xea, 0x90, 0x01, 0x00, 0x00,
// 276 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x90, 0xcd, 0x4b, 0xf3, 0x40,
0x10, 0xc6, 0x9b, 0xe6, 0xed, 0x5b, 0x3b, 0xc6, 0x9a, 0xac, 0x1f, 0x0d, 0xf1, 0x12, 0x72, 0xca,
0x29, 0x85, 0x5e, 0x05, 0xa1, 0x36, 0x22, 0x05, 0x41, 0xa9, 0x27, 0xc5, 0xcb, 0xb2, 0x8e, 0x1a,
0x84, 0x6c, 0x9d, 0x99, 0x08, 0xfe, 0xf7, 0x92, 0x8f, 0x42, 0x10, 0xbd, 0xcd, 0xf2, 0xf0, 0xfc,
0x76, 0x7e, 0x03, 0x81, 0xd1, 0xf3, 0x2d, 0x59, 0xb1, 0x73, 0xa3, 0xb3, 0x66, 0x50, 0x43, 0xa3,
0xa3, 0x13, 0x63, 0x09, 0x77, 0x81, 0x25, 0x6c, 0xa3, 0xe4, 0x02, 0x66, 0x6b, 0xe6, 0x0a, 0x57,
0x48, 0x52, 0xbc, 0x14, 0x46, 0x0b, 0x6e, 0xf0, 0xa3, 0x42, 0x16, 0xb5, 0x0f, 0xae, 0x61, 0x0a,
0x9d, 0xd8, 0x49, 0x3d, 0x75, 0x0a, 0x53, 0xc2, 0xd7, 0x82, 0x85, 0xb4, 0x14, 0xb6, 0x5c, 0xe7,
0xe1, 0x30, 0x76, 0x52, 0x37, 0x79, 0x80, 0xa3, 0x6b, 0x2c, 0x91, 0xb4, 0xe0, 0xed, 0xea, 0xfe,
0x6e, 0xd7, 0x3d, 0x84, 0xb1, 0x41, 0x92, 0xfc, 0x6a, 0xd3, 0xf5, 0xa7, 0xf0, 0x9f, 0x45, 0x4b,
0xc5, 0x4d, 0x6f, 0x52, 0xbf, 0x09, 0x35, 0xdb, 0x32, 0x74, 0x63, 0x27, 0x1d, 0xa9, 0x00, 0x26,
0x84, 0x9f, 0xf6, 0x1d, 0x9f, 0x97, 0x12, 0xfe, 0x6b, 0xd0, 0x31, 0x78, 0x2d, 0x92, 0xb7, 0xb6,
0x64, 0x54, 0x3e, 0xec, 0x51, 0x37, 0xb7, 0xd0, 0xc5, 0x13, 0x1c, 0xf7, 0xf6, 0x5e, 0x56, 0xf2,
0x66, 0xa9, 0x90, 0x2f, 0x95, 0x83, 0xff, 0x53, 0x4a, 0x9d, 0x65, 0x46, 0x67, 0x7f, 0xa8, 0x46,
0x41, 0xd6, 0x9c, 0xa4, 0x97, 0x24, 0x83, 0xc5, 0x0d, 0x1c, 0xd4, 0xff, 0x77, 0x7a, 0x96, 0xd4,
0x39, 0x78, 0x7d, 0x57, 0x35, 0xab, 0x91, 0xbf, 0xd8, 0x47, 0x7e, 0x1d, 0xf4, 0x77, 0x4f, 0x06,
0x97, 0xe3, 0xc7, 0x51, 0x73, 0xf1, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd4, 0x60, 0xcf, 0xa9,
0xa0, 0x01, 0x00, 0x00,
}

View File

@ -5,8 +5,16 @@ option go_package = "proto";
import "core/proto/core.proto";
// CertificateAuthority issues certificates.
service CertificateAuthority {
rpc IssueCertificate(IssueCertificateRequest) returns (core.Certificate) {}
}
// OCSPGenerator generates OCSP. We separate this out from
// CertificateAuthority so that we can restrict access to a different subset of
// hosts, so the hosts that need to request OCSP generation don't need to be
// able to request certificate issuance.
service OCSPGenerator {
rpc GenerateOCSP(GenerateOCSPRequest) returns (OCSPResponse) {}
}

View File

@ -184,9 +184,9 @@ func main() {
cmd.FailOnError(err, "Failed to create Publisher client")
}
var grpcSrv *grpc.Server
if c.CA.GRPC != nil {
s, l, err := bgrpc.NewServer(c.CA.GRPC, scope)
var caSrv *grpc.Server
if c.CA.GRPCCA != nil {
s, l, err := bgrpc.NewServer(c.CA.GRPCCA, scope)
cmd.FailOnError(err, "Unable to setup CA gRPC server")
caWrapper := bgrpc.NewCertificateAuthorityServer(cai)
caPB.RegisterCertificateAuthorityServer(s, caWrapper)
@ -194,7 +194,19 @@ func main() {
err = s.Serve(l)
cmd.FailOnError(err, "CA gRPC service failed")
}()
grpcSrv = s
caSrv = s
}
var ocspSrv *grpc.Server
if c.CA.GRPCOCSPGenerator != nil {
s, l, err := bgrpc.NewServer(c.CA.GRPCOCSPGenerator, scope)
cmd.FailOnError(err, "Unable to setup CA gRPC server")
caWrapper := bgrpc.NewCertificateAuthorityServer(cai)
caPB.RegisterOCSPGeneratorServer(s, caWrapper)
go func() {
err = s.Serve(l)
cmd.FailOnError(err, "OCSPGenerator gRPC service failed")
}()
ocspSrv = s
}
cas, err := rpc.NewAmqpRPCServer(amqpConf, c.CA.MaxConcurrentRPCServerRequests, scope, logger)
@ -202,8 +214,11 @@ func main() {
go cmd.CatchSignals(logger, func() {
cas.Stop()
if grpcSrv != nil {
grpcSrv.GracefulStop()
if caSrv != nil {
caSrv.GracefulStop()
}
if ocspSrv != nil {
ocspSrv.GracefulStop()
}
})

View File

@ -136,7 +136,10 @@ func main() {
if c.RA.CAService != nil {
conn, err := bgrpc.ClientSetup(c.RA.CAService, scope)
cmd.FailOnError(err, "Unable to create CA client")
cac = bgrpc.NewCertificateAuthorityClient(caPB.NewCertificateAuthorityClient(conn))
// Build a CA client that is only capable of issuing certificates, not
// signing OCSP. TODO(jsha): Once we've fully moved to gRPC, replace this
// with a plain caPB.NewCertificateAuthorityClient.
cac = bgrpc.NewCertificateAuthorityClient(caPB.NewCertificateAuthorityClient(conn), nil)
} else {
cac, err = rpc.NewCertificateAuthorityClient(clientName, amqpConf, scope)
cmd.FailOnError(err, "Unable to create CA client")

View File

@ -116,6 +116,9 @@ type CAConfig struct {
DBConfig
HostnamePolicyConfig
GRPCCA *GRPCServerConfig
GRPCOCSPGenerator *GRPCServerConfig
RSAProfile string
ECDSAProfile string
TestMode bool
@ -239,9 +242,9 @@ type OCSPUpdaterConfig struct {
SignFailureBackoffFactor float64
SignFailureBackoffMax ConfigDuration
Publisher *GRPCClientConfig
SAService *GRPCClientConfig
CAService *GRPCClientConfig
Publisher *GRPCClientConfig
SAService *GRPCClientConfig
OCSPGeneratorService *GRPCClientConfig
}
// GoogleSafeBrowsingConfig is the JSON config struct for the VA's use of the

View File

@ -769,10 +769,13 @@ func setupClients(c cmd.OCSPUpdaterConfig, stats metrics.Scope) (
amqpConf := c.AMQP
var cac core.CertificateAuthority
if c.CAService != nil {
conn, err := bgrpc.ClientSetup(c.CAService, stats)
if c.OCSPGeneratorService != nil {
conn, err := bgrpc.ClientSetup(c.OCSPGeneratorService, stats)
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to CA")
cac = bgrpc.NewCertificateAuthorityClient(capb.NewCertificateAuthorityClient(conn))
// Make a CA client that is only capable of signing OCSP.
// TODO(jsha): Once we've fully moved to gRPC, replace this
// with a plain caPB.NewOCSPGeneratorClient.
cac = bgrpc.NewCertificateAuthorityClient(nil, capb.NewOCSPGeneratorClient(conn))
} else {
var err error
cac, err = rpc.NewCertificateAuthorityClient(clientName, amqpConf, stats)

View File

@ -8,6 +8,7 @@ package grpc
import (
"crypto/x509"
"errors"
"time"
"golang.org/x/net/context"
@ -18,16 +19,25 @@ import (
"github.com/letsencrypt/boulder/revocation"
)
// CertificateAuthorityClientWrapper is the gRPC version of a core.CertificateAuthority client
// CertificateAuthorityClientWrapper is the gRPC version of a
// core.CertificateAuthority client. It composites a CertificateAuthorityClient
// and OCSPGeneratorClient, either of which may be nil if the calling code
// doesn't intend to use the relevant functions. Once we've fully moved to gRPC,
// calling code will do away with this wrapper and directly instantiate exactly
// the type of client it needs.
type CertificateAuthorityClientWrapper struct {
inner caPB.CertificateAuthorityClient
inner caPB.CertificateAuthorityClient
innerOCSP caPB.OCSPGeneratorClient
}
func NewCertificateAuthorityClient(inner caPB.CertificateAuthorityClient) *CertificateAuthorityClientWrapper {
return &CertificateAuthorityClientWrapper{inner}
func NewCertificateAuthorityClient(inner caPB.CertificateAuthorityClient, innerOCSP caPB.OCSPGeneratorClient) *CertificateAuthorityClientWrapper {
return &CertificateAuthorityClientWrapper{inner, innerOCSP}
}
func (cac CertificateAuthorityClientWrapper) IssueCertificate(ctx context.Context, csr x509.CertificateRequest, regID int64) (core.Certificate, error) {
if cac.inner == nil {
return core.Certificate{}, errors.New("this CA client does not support issuing certificates")
}
res, err := cac.inner.IssueCertificate(ctx, &caPB.IssueCertificateRequest{
Csr: csr.Raw,
RegistrationID: &regID,
@ -39,9 +49,12 @@ func (cac CertificateAuthorityClientWrapper) IssueCertificate(ctx context.Contex
}
func (cac CertificateAuthorityClientWrapper) GenerateOCSP(ctx context.Context, ocspReq core.OCSPSigningRequest) ([]byte, error) {
if cac.innerOCSP == nil {
return nil, errors.New("this CA client does not support generating OCSP")
}
reason := int32(ocspReq.Reason)
revokedAt := ocspReq.RevokedAt.UnixNano()
res, err := cac.inner.GenerateOCSP(ctx, &caPB.GenerateOCSPRequest{
res, err := cac.innerOCSP.GenerateOCSP(ctx, &caPB.GenerateOCSPRequest{
CertDER: ocspReq.CertDER,
Status: &ocspReq.Status,
Reason: &reason,

View File

@ -11,13 +11,21 @@
"clientKeyPath": "test/grpc-creds/ca.boulder/key.pem",
"timeout": "15s"
},
"grpc": {
"grpcCA": {
"address": ":9093",
"clientIssuerPath": "test/grpc-creds/minica.pem",
"serverCertificatePath": "test/grpc-creds/ca.boulder/cert.pem",
"serverKeyPath": "test/grpc-creds/ca.boulder/key.pem",
"clientNames": [
"ra.boulder",
"ra.boulder"
]
},
"grpcOCSPGenerator": {
"address": ":9096",
"clientIssuerPath": "test/grpc-creds/minica.pem",
"serverCertificatePath": "test/grpc-creds/ca.boulder/cert.pem",
"serverKeyPath": "test/grpc-creds/ca.boulder/key.pem",
"clientNames": [
"ocsp-updater.boulder"
]
},

View File

@ -31,8 +31,8 @@
"clientKeyPath": "test/grpc-creds/ocsp-updater.boulder/key.pem",
"timeout": "15s"
},
"caService": {
"serverAddresses": ["ca.boulder:9093"],
"ocspGeneratorService": {
"serverAddresses": ["ca.boulder:9096"],
"serverIssuerPath": "test/grpc-creds/minica.pem",
"clientCertificatePath": "test/grpc-creds/ocsp-updater.boulder/cert.pem",
"clientKeyPath": "test/grpc-creds/ocsp-updater.boulder/key.pem",

View File

@ -4,12 +4,12 @@
"submissionTimeout": "5s",
"debugAddr": ":8009",
"grpc": {
"address": "boulder:9091",
"address": ":9091",
"clientIssuerPath": "test/grpc-creds/minica.pem",
"serverCertificatePath": "test/grpc-creds/publisher.boulder/cert.pem",
"serverKeyPath": "test/grpc-creds/publisher.boulder/key.pem",
"clientNames": [
"ra.boulder",
"ca.boulder",
"ocsp-updater.boulder"
]
},

View File

@ -9,7 +9,8 @@ command -v minica >/dev/null 2>&1 || {
exit 1;
}
# Make a server certificate (a CA will be created to issue it)
minica -domains boulder-server,boulder -ip-addresses 127.0.0.1
# Make a client certificate (reuses the CA created for the server)
minica -domains boulder-client,boulder -ip-addresses 127.0.0.1
for HOSTNAME in admin-revoker.boulder ca.boulder expiration-mailer.boulder \
ocsp-updater.boulder orphan-finder.boulder publisher.boulder ra.boulder \
sa.boulder va.boulder wfe.boulder ; do
minica -domains ${HOSTNAME}
done