Remove caa-checker from the tree (#2351)
The VA can internally check CAA and this additional code was deemed unneeded complexity that could be hoisted outside of Boulder. Fixes #2346.
This commit is contained in:
parent
38c2ea3382
commit
e2155388a1
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/letsencrypt/boulder/bdns"
|
||||
"github.com/letsencrypt/boulder/cdr"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
caaPB "github.com/letsencrypt/boulder/cmd/caa-checker/proto"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/rpc"
|
||||
|
@ -34,8 +33,6 @@ type config struct {
|
|||
|
||||
GoogleSafeBrowsing *cmd.GoogleSafeBrowsingConfig
|
||||
|
||||
CAAService *cmd.GRPCClientConfig
|
||||
|
||||
CAADistributedResolver *cmd.CAADistributedResolverConfig
|
||||
|
||||
// The number of times to try a DNS query (that has a temporary error)
|
||||
|
@ -90,13 +87,6 @@ func main() {
|
|||
pc.TLSPort = c.VA.PortConfig.TLSPort
|
||||
}
|
||||
|
||||
var caaClient caaPB.CAACheckerClient
|
||||
if c.VA.CAAService != nil {
|
||||
conn, err := bgrpc.ClientSetup(c.VA.CAAService, scope)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create connection to service")
|
||||
caaClient = caaPB.NewCAACheckerClient(conn)
|
||||
}
|
||||
|
||||
sbc := newGoogleSafeBrowsing(c.VA.GoogleSafeBrowsing)
|
||||
|
||||
var cdrClient *cdr.CAADistributedResolver
|
||||
|
@ -138,7 +128,6 @@ func main() {
|
|||
vai := va.NewValidationAuthorityImpl(
|
||||
pc,
|
||||
sbc,
|
||||
caaClient,
|
||||
cdrClient,
|
||||
resolver,
|
||||
c.VA.UserAgent,
|
||||
|
|
|
@ -1,178 +0,0 @@
|
|||
// Code generated by protoc-gen-go.
|
||||
// source: caaChecker.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package caaChecker is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
caaChecker.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Check
|
||||
Result
|
||||
*/
|
||||
package caaChecker
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type Check struct {
|
||||
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
IssuerDomain *string `protobuf:"bytes,2,opt,name=issuerDomain" json:"issuerDomain,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Check) Reset() { *m = Check{} }
|
||||
func (m *Check) String() string { return proto.CompactTextString(m) }
|
||||
func (*Check) ProtoMessage() {}
|
||||
func (*Check) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *Check) GetName() string {
|
||||
if m != nil && m.Name != nil {
|
||||
return *m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Check) GetIssuerDomain() string {
|
||||
if m != nil && m.IssuerDomain != nil {
|
||||
return *m.IssuerDomain
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Present *bool `protobuf:"varint,1,opt,name=present" json:"present,omitempty"`
|
||||
Valid *bool `protobuf:"varint,2,opt,name=valid" json:"valid,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Result) Reset() { *m = Result{} }
|
||||
func (m *Result) String() string { return proto.CompactTextString(m) }
|
||||
func (*Result) ProtoMessage() {}
|
||||
func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *Result) GetPresent() bool {
|
||||
if m != nil && m.Present != nil {
|
||||
return *m.Present
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Result) GetValid() bool {
|
||||
if m != nil && m.Valid != nil {
|
||||
return *m.Valid
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Check)(nil), "Check")
|
||||
proto.RegisterType((*Result)(nil), "Result")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion3
|
||||
|
||||
// Client API for CAAChecker service
|
||||
|
||||
type CAACheckerClient interface {
|
||||
ValidForIssuance(ctx context.Context, in *Check, opts ...grpc.CallOption) (*Result, error)
|
||||
}
|
||||
|
||||
type cAACheckerClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewCAACheckerClient(cc *grpc.ClientConn) CAACheckerClient {
|
||||
return &cAACheckerClient{cc}
|
||||
}
|
||||
|
||||
func (c *cAACheckerClient) ValidForIssuance(ctx context.Context, in *Check, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/CAAChecker/ValidForIssuance", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for CAAChecker service
|
||||
|
||||
type CAACheckerServer interface {
|
||||
ValidForIssuance(context.Context, *Check) (*Result, error)
|
||||
}
|
||||
|
||||
func RegisterCAACheckerServer(s *grpc.Server, srv CAACheckerServer) {
|
||||
s.RegisterService(&_CAAChecker_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _CAAChecker_ValidForIssuance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Check)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CAACheckerServer).ValidForIssuance(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/CAAChecker/ValidForIssuance",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CAACheckerServer).ValidForIssuance(ctx, req.(*Check))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _CAAChecker_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "CAAChecker",
|
||||
HandlerType: (*CAACheckerServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "ValidForIssuance",
|
||||
Handler: _CAAChecker_ValidForIssuance_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: fileDescriptor0,
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("caaChecker.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 157 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x48, 0x4e, 0x4c, 0x74,
|
||||
0xce, 0x48, 0x4d, 0xce, 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0xd2, 0xe6, 0x62,
|
||||
0x05, 0x0b, 0x08, 0xf1, 0x70, 0xb1, 0xe4, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70,
|
||||
0x0a, 0x89, 0x70, 0xf1, 0x64, 0x16, 0x17, 0x97, 0xa6, 0x16, 0xb9, 0xe4, 0xe7, 0x26, 0x66, 0xe6,
|
||||
0x49, 0x30, 0x81, 0x44, 0x95, 0x34, 0xb8, 0xd8, 0x82, 0x52, 0x8b, 0x4b, 0x73, 0x4a, 0x84, 0xf8,
|
||||
0xb9, 0xd8, 0x0b, 0x8a, 0x52, 0x8b, 0x53, 0xf3, 0x4a, 0xc0, 0x1a, 0x38, 0x84, 0x78, 0xb9, 0x58,
|
||||
0xcb, 0x12, 0x73, 0x32, 0x53, 0xc0, 0x2a, 0x39, 0x8c, 0x8c, 0xb9, 0xb8, 0x9c, 0x1d, 0x1d, 0xa1,
|
||||
0x56, 0x09, 0xa9, 0x72, 0x09, 0x84, 0x81, 0x24, 0xdd, 0xf2, 0x8b, 0x3c, 0x8b, 0x8b, 0x4b, 0x13,
|
||||
0xf3, 0x92, 0x53, 0x85, 0xd8, 0xf4, 0xc0, 0xb2, 0x52, 0xec, 0x7a, 0x10, 0x23, 0x95, 0x18, 0x00,
|
||||
0x01, 0x00, 0x00, 0xff, 0xff, 0xa4, 0xed, 0x44, 0x02, 0x9e, 0x00, 0x00, 0x00,
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
syntax = "proto2";
|
||||
|
||||
service CAAChecker {
|
||||
rpc ValidForIssuance(Check) returns (Result) {}
|
||||
}
|
||||
|
||||
message Check {
|
||||
optional string name = 1;
|
||||
optional string issuerDomain = 2;
|
||||
}
|
||||
|
||||
message Result {
|
||||
optional bool present = 1;
|
||||
optional bool valid = 2;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package caaChecker
|
||||
|
||||
//go:generate protoc --go_out=plugins=grpc:. caaChecker.proto
|
|
@ -1,255 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jmhodges/clock"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/context"
|
||||
grpcCodes "google.golang.org/grpc/codes"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/letsencrypt/boulder/bdns"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
pb "github.com/letsencrypt/boulder/cmd/caa-checker/proto"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
)
|
||||
|
||||
type caaCheckerServer struct {
|
||||
resolver bdns.DNSResolver
|
||||
stats metrics.Scope
|
||||
}
|
||||
|
||||
// caaSet consists of filtered CAA records
|
||||
type caaSet struct {
|
||||
Issue []*dns.CAA
|
||||
Issuewild []*dns.CAA
|
||||
Iodef []*dns.CAA
|
||||
Unknown []*dns.CAA
|
||||
}
|
||||
|
||||
// returns true if any CAA records have unknown tag properties and are flagged critical.
|
||||
func (caaSet caaSet) criticalUnknown() bool {
|
||||
if len(caaSet.Unknown) > 0 {
|
||||
for _, caaRecord := range caaSet.Unknown {
|
||||
// The critical flag is the bit with significance 128. However, many CAA
|
||||
// record users have misinterpreted the RFC and concluded that the bit
|
||||
// with significance 1 is the critical bit. This is sufficiently
|
||||
// widespread that that bit must reasonably be considered an alias for
|
||||
// the critical bit. The remaining bits are 0/ignore as proscribed by the
|
||||
// RFC.
|
||||
if (caaRecord.Flag & (128 | 1)) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Filter CAA records by property
|
||||
func newCAASet(CAAs []*dns.CAA) *caaSet {
|
||||
var filtered caaSet
|
||||
|
||||
for _, caaRecord := range CAAs {
|
||||
switch caaRecord.Tag {
|
||||
case "issue":
|
||||
filtered.Issue = append(filtered.Issue, caaRecord)
|
||||
case "issuewild":
|
||||
filtered.Issuewild = append(filtered.Issuewild, caaRecord)
|
||||
case "iodef":
|
||||
filtered.Iodef = append(filtered.Iodef, caaRecord)
|
||||
default:
|
||||
filtered.Unknown = append(filtered.Unknown, caaRecord)
|
||||
}
|
||||
}
|
||||
|
||||
return &filtered
|
||||
}
|
||||
|
||||
func (ccs *caaCheckerServer) getCAASet(ctx context.Context, hostname string) (*caaSet, error) {
|
||||
hostname = strings.TrimRight(hostname, ".")
|
||||
labels := strings.Split(hostname, ".")
|
||||
|
||||
// See RFC 6844 "Certification Authority Processing" for pseudocode.
|
||||
// Essentially: check CAA records for the FDQN to be issued, and all
|
||||
// parent domains.
|
||||
//
|
||||
// The lookups are performed in parallel in order to avoid timing out
|
||||
// the RPC call.
|
||||
//
|
||||
// We depend on our resolver to snap CNAME and DNAME records.
|
||||
|
||||
type result struct {
|
||||
records []*dns.CAA
|
||||
err error
|
||||
}
|
||||
results := make([]result, len(labels))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < len(labels); i++ {
|
||||
// Start the concurrent DNS lookup.
|
||||
wg.Add(1)
|
||||
go func(name string, r *result) {
|
||||
r.records, r.err = ccs.resolver.LookupCAA(ctx, name)
|
||||
wg.Done()
|
||||
}(strings.Join(labels[i:], "."), &results[i])
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Return the first result
|
||||
for _, res := range results {
|
||||
if res.err != nil {
|
||||
return nil, res.err
|
||||
}
|
||||
if len(res.records) > 0 {
|
||||
return newCAASet(res.records), nil
|
||||
}
|
||||
}
|
||||
|
||||
// no CAA records found
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Given a CAA record, assume that the Value is in the issue/issuewild format,
|
||||
// that is, a domain name with zero or more additional key-value parameters.
|
||||
// Returns the domain name, which may be "" (unsatisfiable).
|
||||
func extractIssuerDomain(caa *dns.CAA) string {
|
||||
v := caa.Value
|
||||
v = strings.Trim(v, " \t") // Value can start and end with whitespace.
|
||||
idx := strings.IndexByte(v, ';')
|
||||
if idx < 0 {
|
||||
return v // no parameters; domain only
|
||||
}
|
||||
|
||||
// Currently, ignore parameters. Unfortunately, the RFC makes no statement on
|
||||
// whether any parameters are critical. Treat unknown parameters as
|
||||
// non-critical.
|
||||
return strings.Trim(v[0:idx], " \t")
|
||||
}
|
||||
|
||||
func (ccs *caaCheckerServer) checkCAA(ctx context.Context, hostname string, issuer string) (present, valid bool, err error) {
|
||||
hostname = strings.ToLower(hostname)
|
||||
caaSet, err := ccs.getCAASet(ctx, hostname)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if caaSet == nil {
|
||||
// No CAA records found, can issue
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
if caaSet.criticalUnknown() {
|
||||
// Contains unknown critical directives.
|
||||
ccs.stats.Inc("CCS.UnknownCritical", 1)
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
if len(caaSet.Unknown) > 0 {
|
||||
ccs.stats.Inc("CCS.WithUnknownNoncritical", 1)
|
||||
}
|
||||
|
||||
if len(caaSet.Issue) == 0 {
|
||||
// Although CAA records exist, none of them pertain to issuance in this case.
|
||||
// (e.g. there is only an issuewild directive, but we are checking for a
|
||||
// non-wildcard identifier, or there is only an iodef or non-critical unknown
|
||||
// directive.)
|
||||
ccs.stats.Inc("CCS.CAA.NoneRelevant", 1)
|
||||
return true, true, nil
|
||||
}
|
||||
|
||||
// There are CAA records pertaining to issuance in our case. Note that this
|
||||
// includes the case of the unsatisfiable CAA record value ";", used to
|
||||
// prevent issuance by any CA under any circumstance.
|
||||
//
|
||||
// Our CAA identity must be found in the chosen checkSet.
|
||||
for _, caa := range caaSet.Issue {
|
||||
if extractIssuerDomain(caa) == issuer {
|
||||
ccs.stats.Inc("CCS.CAA.Authorized", 1)
|
||||
return true, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// The list of authorized issuers is non-empty, but we are not in it. Fail.
|
||||
ccs.stats.Inc("CCS.CAA.Unauthorized", 1)
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
func (ccs *caaCheckerServer) ValidForIssuance(ctx context.Context, check *pb.Check) (*pb.Result, error) {
|
||||
if check.Name == nil || check.IssuerDomain == nil {
|
||||
return nil, bgrpc.CodedError(grpcCodes.InvalidArgument, "Both name and issuerDomain are required")
|
||||
}
|
||||
present, valid, err := ccs.checkCAA(ctx, *check.Name, *check.IssuerDomain)
|
||||
if err != nil {
|
||||
if err == context.DeadlineExceeded || err == context.Canceled {
|
||||
return nil, bgrpc.CodedError(bgrpc.DNSQueryTimeout, err.Error())
|
||||
}
|
||||
if dnsErr, ok := err.(*bdns.DNSError); ok {
|
||||
if dnsErr.Timeout() {
|
||||
return nil, bgrpc.CodedError(bgrpc.DNSQueryTimeout, err.Error())
|
||||
}
|
||||
return nil, bgrpc.CodedError(bgrpc.DNSError, dnsErr.Error())
|
||||
}
|
||||
return nil, bgrpc.CodedError(bgrpc.DNSError, "server failure at resolver")
|
||||
}
|
||||
return &pb.Result{Present: &present, Valid: &valid}, nil
|
||||
}
|
||||
|
||||
type config struct {
|
||||
GRPC cmd.GRPCServerConfig
|
||||
Statsd cmd.StatsdConfig
|
||||
Syslog cmd.SyslogConfig
|
||||
|
||||
DebugAddr string `yaml:"debug-addr"`
|
||||
DNSResolver string `yaml:"dns-resolver"`
|
||||
DNSNetwork string `yaml:"dns-network"`
|
||||
DNSTimeout cmd.ConfigDuration `yaml:"dns-timeout"`
|
||||
CAASERVFAILExceptions string `yaml:"caa-servfail-exceptions"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
configPath := flag.String("config", "config.yml", "Path to configuration file")
|
||||
flag.Parse()
|
||||
|
||||
configBytes, err := ioutil.ReadFile(*configPath)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Failed to read configuration file from '%s'", *configPath))
|
||||
var c config
|
||||
err = yaml.Unmarshal(configBytes, &c)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Failed to parse configuration file from '%s'", *configPath))
|
||||
|
||||
stats, logger := cmd.StatsAndLogging(c.Statsd, c.Syslog)
|
||||
scope := metrics.NewStatsdScope(stats, "CAAService")
|
||||
defer logger.AuditPanic()
|
||||
logger.Info(cmd.VersionString("CAA-Checker"))
|
||||
|
||||
caaSERVFAILExceptions, err := bdns.ReadHostList(c.CAASERVFAILExceptions)
|
||||
cmd.FailOnError(err, "Couldn't read CAASERVFAILExceptions file")
|
||||
|
||||
resolver := bdns.NewDNSResolverImpl(
|
||||
c.DNSTimeout.Duration,
|
||||
[]string{c.DNSResolver},
|
||||
caaSERVFAILExceptions,
|
||||
scope,
|
||||
clock.Default(),
|
||||
5,
|
||||
)
|
||||
|
||||
s, l, err := bgrpc.NewServer(&c.GRPC, scope)
|
||||
cmd.FailOnError(err, "Failed to setup gRPC server")
|
||||
ccs := &caaCheckerServer{resolver, scope}
|
||||
pb.RegisterCAACheckerServer(s, ccs)
|
||||
|
||||
go cmd.CatchSignals(logger, s.GracefulStop)
|
||||
go cmd.DebugServer(c.DebugAddr)
|
||||
|
||||
err = s.Serve(l)
|
||||
cmd.FailOnError(err, "gRPC service failed")
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/letsencrypt/boulder/bdns"
|
||||
pb "github.com/letsencrypt/boulder/cmd/caa-checker/proto"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
func TestChecking(t *testing.T) {
|
||||
type CAATest struct {
|
||||
Domain string
|
||||
Present bool
|
||||
Valid bool
|
||||
}
|
||||
tests := []CAATest{
|
||||
// Reserved
|
||||
{"reserved.com", true, false},
|
||||
// Critical
|
||||
{"critical.com", true, false},
|
||||
{"nx.critical.com", true, false},
|
||||
// Good (absent)
|
||||
{"absent.com", false, true},
|
||||
{"example.co.uk", false, true},
|
||||
// Good (present)
|
||||
{"present.com", true, true},
|
||||
{"present.servfail.com", true, true},
|
||||
// Good (multiple critical, one matching)
|
||||
{"multi-crit-present.com", true, true},
|
||||
// Bad (unknown critical)
|
||||
{"unknown-critical.com", true, false},
|
||||
{"unknown-critical2.com", true, false},
|
||||
// Good (unknown noncritical, no issue/issuewild records)
|
||||
{"unknown-noncritical.com", true, true},
|
||||
// Good (issue record with unknown parameters)
|
||||
{"present-with-parameter.com", true, true},
|
||||
// Bad (unsatisfiable issue record)
|
||||
{"unsatisfiable.com", true, false},
|
||||
}
|
||||
|
||||
stats := metrics.NewNoopScope()
|
||||
ccs := &caaCheckerServer{&bdns.MockDNSResolver{}, stats}
|
||||
issuerDomain := "letsencrypt.org"
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for _, caaTest := range tests {
|
||||
result, err := ccs.ValidForIssuance(ctx, &pb.Check{Name: &caaTest.Domain, IssuerDomain: &issuerDomain})
|
||||
if err != nil {
|
||||
t.Errorf("CheckCAARecords error for %s: %s", caaTest.Domain, err)
|
||||
}
|
||||
if *result.Present != caaTest.Present {
|
||||
t.Errorf("CheckCAARecords presence mismatch for %s: got %t expected %t", caaTest.Domain, *result.Present, caaTest.Present)
|
||||
}
|
||||
if *result.Valid != caaTest.Valid {
|
||||
t.Errorf("CheckCAARecords presence mismatch for %s: got %t expected %t", caaTest.Domain, *result.Valid, caaTest.Valid)
|
||||
}
|
||||
}
|
||||
|
||||
servfail := "servfail.com"
|
||||
servfailPresent := "servfail.present.com"
|
||||
result, err := ccs.ValidForIssuance(ctx, &pb.Check{Name: &servfail, IssuerDomain: &issuerDomain})
|
||||
test.AssertError(t, err, "servfail.com")
|
||||
test.Assert(t, result == nil, "result should be nil")
|
||||
test.AssertEquals(t, grpc.Code(err), bgrpc.DNSError)
|
||||
|
||||
result, err = ccs.ValidForIssuance(ctx, &pb.Check{Name: &servfailPresent, IssuerDomain: &issuerDomain})
|
||||
test.AssertError(t, err, "servfail.present.com")
|
||||
test.Assert(t, result == nil, "result should be nil")
|
||||
test.AssertEquals(t, grpc.Code(err), bgrpc.DNSError)
|
||||
|
||||
timeout := "caa-timeout.com"
|
||||
result, err = ccs.ValidForIssuance(ctx, &pb.Check{Name: &timeout, IssuerDomain: &issuerDomain})
|
||||
test.AssertError(t, err, "timeout.com")
|
||||
test.Assert(t, result == nil, "result should be nil")
|
||||
test.AssertEquals(t, grpc.Code(err), bgrpc.DNSQueryTimeout)
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
pb "github.com/letsencrypt/boulder/cmd/caa-checker/proto"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
)
|
||||
|
||||
func main() {
|
||||
addr := flag.String("addr", "boulder:9090", "CCS address")
|
||||
name := flag.String("name", "", "Name to check")
|
||||
issuer := flag.String("issuerDomain", "", "Issuer domain to check against")
|
||||
flag.Parse()
|
||||
|
||||
// Set up a connection to the server.
|
||||
conn, err := bgrpc.ClientSetup(&cmd.GRPCClientConfig{
|
||||
ServerAddresses: []string{*addr},
|
||||
ServerIssuerPath: "test/grpc-creds/minica.pem",
|
||||
ClientCertificatePath: "test/grpc-creds/boulder-client/cert.pem",
|
||||
ClientKeyPath: "test/grpc-creds/boulder-client/key.pem",
|
||||
}, metrics.NewNoopScope())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to setup client connection: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
c := pb.NewCAACheckerClient(conn)
|
||||
|
||||
r, err := c.ValidForIssuance(context.Background(), &pb.Check{Name: name, IssuerDomain: issuer})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ValidForIssuance call failed: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s valid for issuance: %t (records present: %t)\n", *name, *r.Valid, *r.Present)
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
debug-addr: 127.0.0.1:8011
|
||||
|
||||
dns-resolver: 127.0.0.1:8053
|
||||
dns-timeout: 10s
|
||||
|
||||
statsd-server: 127.0.0.1:8125
|
||||
statsd-prefix: boulder
|
||||
|
||||
grpc:
|
||||
address: boulder:9090
|
||||
server-certificate-path: test/grpc-creds/boulder-server/cert.pem
|
||||
server-key-path: test/grpc-creds/boulder-server/key.pem
|
||||
client-issuer-path: test/grpc-creds/minica.pem
|
||||
client-names:
|
||||
- boulder-client
|
|
@ -11,18 +11,6 @@
|
|||
"maxConcurrentRPCServerRequests": 16,
|
||||
"dnsTries": 3,
|
||||
"issuerDomain": "happy-hacker-ca.invalid",
|
||||
"caaService": {
|
||||
"serverAddresses": ["boulder:9090"],
|
||||
"serverIssuerPath": "test/grpc-creds/minica.pem",
|
||||
"clientCertificatePath": "test/grpc-creds/boulder-client/cert.pem",
|
||||
"clientKeyPath": "test/grpc-creds/boulder-client/key.pem"
|
||||
},
|
||||
"caaPublicResolver": {
|
||||
"timeout": "10s",
|
||||
"keepalive": "30s",
|
||||
"maxFailures": 1,
|
||||
"proxies": []
|
||||
},
|
||||
"grpc": {
|
||||
"address": "boulder:9092",
|
||||
"clientIssuerPath": "test/grpc-creds/minica.pem",
|
||||
|
|
|
@ -56,8 +56,7 @@ def start(race_detection):
|
|||
'ocsp-responder --config %s' % os.path.join(default_config_dir, "ocsp-responder.json"),
|
||||
'ct-test-srv',
|
||||
'dns-test-srv',
|
||||
'mail-test-srv --closeFirst 5',
|
||||
'caa-checker --config cmd/caa-checker/test-config.yml'
|
||||
'mail-test-srv --closeFirst 5'
|
||||
]
|
||||
if not install(race_detection):
|
||||
return False
|
||||
|
|
|
@ -35,7 +35,6 @@ func TestIsSafeDomain(t *testing.T) {
|
|||
sbc,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"user agent 1.0",
|
||||
"letsencrypt.org",
|
||||
stats,
|
||||
|
@ -86,7 +85,6 @@ func TestAllowNilInIsSafeDomain(t *testing.T) {
|
|||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"user agent 1.0",
|
||||
"letsencrypt.org",
|
||||
stats,
|
||||
|
|
38
va/va.go
38
va/va.go
|
@ -25,12 +25,9 @@ import (
|
|||
"github.com/letsencrypt/boulder/cdr"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
|
||||
caaPB "github.com/letsencrypt/boulder/cmd/caa-checker/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -56,7 +53,6 @@ type ValidationAuthorityImpl struct {
|
|||
userAgent string
|
||||
stats metrics.Scope
|
||||
clk clock.Clock
|
||||
caaClient caaPB.CAACheckerClient
|
||||
caaDR *cdr.CAADistributedResolver
|
||||
}
|
||||
|
||||
|
@ -64,7 +60,6 @@ type ValidationAuthorityImpl struct {
|
|||
func NewValidationAuthorityImpl(
|
||||
pc *cmd.PortConfig,
|
||||
sbc SafeBrowsing,
|
||||
caaClient caaPB.CAACheckerClient,
|
||||
cdrClient *cdr.CAADistributedResolver,
|
||||
resolver bdns.DNSResolver,
|
||||
userAgent string,
|
||||
|
@ -84,7 +79,6 @@ func NewValidationAuthorityImpl(
|
|||
userAgent: userAgent,
|
||||
stats: stats,
|
||||
clk: clk,
|
||||
caaClient: caaClient,
|
||||
caaDR: cdrClient,
|
||||
}
|
||||
}
|
||||
|
@ -447,12 +441,7 @@ func (va *ValidationAuthorityImpl) validateDNS01(ctx context.Context, identifier
|
|||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) checkCAA(ctx context.Context, identifier core.AcmeIdentifier) *probs.ProblemDetails {
|
||||
var prob *probs.ProblemDetails
|
||||
if va.caaClient != nil {
|
||||
prob = va.checkCAAService(ctx, identifier)
|
||||
} else {
|
||||
prob = va.checkCAAInternal(ctx, identifier)
|
||||
}
|
||||
prob := va.checkCAAInternal(ctx, identifier)
|
||||
if va.caaDR != nil && prob != nil && prob.Type == probs.ConnectionProblem {
|
||||
return va.checkGPDNS(ctx, identifier)
|
||||
}
|
||||
|
@ -476,31 +465,6 @@ func (va *ValidationAuthorityImpl) checkCAAInternal(ctx context.Context, ident c
|
|||
return nil
|
||||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) checkCAAService(ctx context.Context, ident core.AcmeIdentifier) *probs.ProblemDetails {
|
||||
r, err := va.caaClient.ValidForIssuance(ctx, &caaPB.Check{Name: &ident.Value, IssuerDomain: &va.issuerDomain})
|
||||
if err != nil {
|
||||
va.log.Warning(fmt.Sprintf("grpc: error calling ValidForIssuance: %s", err))
|
||||
return bgrpc.ErrorToProb(err)
|
||||
}
|
||||
if r.Present == nil || r.Valid == nil {
|
||||
va.log.AuditErr("gRPC: communication failure: response is missing fields")
|
||||
return &probs.ProblemDetails{
|
||||
Type: probs.ServerInternalProblem,
|
||||
Detail: "Internal communication failure",
|
||||
}
|
||||
}
|
||||
va.log.AuditInfo(fmt.Sprintf(
|
||||
"Checked CAA records for %s, [Present: %t, Valid for issuance: %t]",
|
||||
ident.Value,
|
||||
*r.Present,
|
||||
*r.Valid,
|
||||
))
|
||||
if !*r.Valid {
|
||||
return probs.ConnectionFailure(fmt.Sprintf("CAA record for %s prevents issuance", ident.Value))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) checkGPDNS(ctx context.Context, identifier core.AcmeIdentifier) *probs.ProblemDetails {
|
||||
results := va.parallelCAALookup(ctx, identifier.Value, va.caaDR.LookupCAA)
|
||||
set, err := parseResults(results)
|
||||
|
|
|
@ -850,7 +850,6 @@ func setup() (*ValidationAuthorityImpl, *mocks.Statter, *blog.Mock) {
|
|||
&cmd.PortConfig{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
&bdns.MockDNSResolver{},
|
||||
"user agent 1.0",
|
||||
"letsencrypt.org",
|
||||
|
@ -874,7 +873,6 @@ func TestCheckCAAFallback(t *testing.T) {
|
|||
va := NewValidationAuthorityImpl(
|
||||
&cmd.PortConfig{},
|
||||
nil,
|
||||
nil,
|
||||
caaDR,
|
||||
&bdns.MockDNSResolver{},
|
||||
"user agent 1.0",
|
||||
|
|
Loading…
Reference in New Issue