...

Source file src/github.com/letsencrypt/boulder/ctpolicy/ctpolicy_test.go

Documentation: github.com/letsencrypt/boulder/ctpolicy

     1  package ctpolicy
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/jmhodges/clock"
    11  	"github.com/letsencrypt/boulder/core"
    12  	"github.com/letsencrypt/boulder/ctpolicy/loglist"
    13  	berrors "github.com/letsencrypt/boulder/errors"
    14  	blog "github.com/letsencrypt/boulder/log"
    15  	"github.com/letsencrypt/boulder/metrics"
    16  	pubpb "github.com/letsencrypt/boulder/publisher/proto"
    17  	"github.com/letsencrypt/boulder/test"
    18  	"github.com/prometheus/client_golang/prometheus"
    19  	"google.golang.org/grpc"
    20  )
    21  
    22  type mockPub struct{}
    23  
    24  func (mp *mockPub) SubmitToSingleCTWithResult(_ context.Context, _ *pubpb.Request, _ ...grpc.CallOption) (*pubpb.Result, error) {
    25  	return &pubpb.Result{Sct: []byte{0}}, nil
    26  }
    27  
    28  type mockFailPub struct{}
    29  
    30  func (mp *mockFailPub) SubmitToSingleCTWithResult(_ context.Context, _ *pubpb.Request, _ ...grpc.CallOption) (*pubpb.Result, error) {
    31  	return nil, errors.New("BAD")
    32  }
    33  
    34  type mockSlowPub struct{}
    35  
    36  func (mp *mockSlowPub) SubmitToSingleCTWithResult(ctx context.Context, _ *pubpb.Request, _ ...grpc.CallOption) (*pubpb.Result, error) {
    37  	<-ctx.Done()
    38  	return nil, errors.New("timed out")
    39  }
    40  
    41  func TestGetSCTs(t *testing.T) {
    42  	expired, cancel := context.WithDeadline(context.Background(), time.Now())
    43  	defer cancel()
    44  	missingSCTErr := berrors.MissingSCTs
    45  	testCases := []struct {
    46  		name       string
    47  		mock       pubpb.PublisherClient
    48  		groups     loglist.List
    49  		ctx        context.Context
    50  		result     core.SCTDERs
    51  		expectErr  string
    52  		berrorType *berrors.ErrorType
    53  	}{
    54  		{
    55  			name: "basic success case",
    56  			mock: &mockPub{},
    57  			groups: loglist.List{
    58  				"OperA": {
    59  					"LogA1": {Url: "UrlA1", Key: "KeyA1"},
    60  					"LogA2": {Url: "UrlA2", Key: "KeyA2"},
    61  				},
    62  				"OperB": {
    63  					"LogB1": {Url: "UrlB1", Key: "KeyB1"},
    64  				},
    65  				"OperC": {
    66  					"LogC1": {Url: "UrlC1", Key: "KeyC1"},
    67  				},
    68  			},
    69  			ctx:    context.Background(),
    70  			result: core.SCTDERs{[]byte{0}, []byte{0}},
    71  		},
    72  		{
    73  			name: "basic failure case",
    74  			mock: &mockFailPub{},
    75  			groups: loglist.List{
    76  				"OperA": {
    77  					"LogA1": {Url: "UrlA1", Key: "KeyA1"},
    78  					"LogA2": {Url: "UrlA2", Key: "KeyA2"},
    79  				},
    80  				"OperB": {
    81  					"LogB1": {Url: "UrlB1", Key: "KeyB1"},
    82  				},
    83  				"OperC": {
    84  					"LogC1": {Url: "UrlC1", Key: "KeyC1"},
    85  				},
    86  			},
    87  			ctx:        context.Background(),
    88  			expectErr:  "failed to get 2 SCTs, got 3 error(s)",
    89  			berrorType: &missingSCTErr,
    90  		},
    91  		{
    92  			name: "parent context timeout failure case",
    93  			mock: &mockSlowPub{},
    94  			groups: loglist.List{
    95  				"OperA": {
    96  					"LogA1": {Url: "UrlA1", Key: "KeyA1"},
    97  					"LogA2": {Url: "UrlA2", Key: "KeyA2"},
    98  				},
    99  				"OperB": {
   100  					"LogB1": {Url: "UrlB1", Key: "KeyB1"},
   101  				},
   102  				"OperC": {
   103  					"LogC1": {Url: "UrlC1", Key: "KeyC1"},
   104  				},
   105  			},
   106  			ctx:        expired,
   107  			expectErr:  "failed to get 2 SCTs before ctx finished",
   108  			berrorType: &missingSCTErr,
   109  		},
   110  	}
   111  
   112  	for _, tc := range testCases {
   113  		t.Run(tc.name, func(t *testing.T) {
   114  			ctp := New(tc.mock, tc.groups, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
   115  			ret, err := ctp.GetSCTs(tc.ctx, []byte{0}, time.Time{})
   116  			if tc.result != nil {
   117  				test.AssertDeepEquals(t, ret, tc.result)
   118  			} else if tc.expectErr != "" {
   119  				if !strings.Contains(err.Error(), tc.expectErr) {
   120  					t.Errorf("Error %q did not match expected %q", err, tc.expectErr)
   121  				}
   122  				if tc.berrorType != nil {
   123  					test.AssertErrorIs(t, err, *tc.berrorType)
   124  				}
   125  			}
   126  		})
   127  	}
   128  }
   129  
   130  type mockFailOnePub struct {
   131  	badURL string
   132  }
   133  
   134  func (mp *mockFailOnePub) SubmitToSingleCTWithResult(_ context.Context, req *pubpb.Request, _ ...grpc.CallOption) (*pubpb.Result, error) {
   135  	if req.LogURL == mp.badURL {
   136  		return nil, errors.New("BAD")
   137  	}
   138  	return &pubpb.Result{Sct: []byte{0}}, nil
   139  }
   140  
   141  func TestGetSCTsMetrics(t *testing.T) {
   142  	ctp := New(&mockFailOnePub{badURL: "UrlA1"}, loglist.List{
   143  		"OperA": {
   144  			"LogA1": {Url: "UrlA1", Key: "KeyA1"},
   145  		},
   146  		"OperB": {
   147  			"LogB1": {Url: "UrlB1", Key: "KeyB1"},
   148  		},
   149  		"OperC": {
   150  			"LogC1": {Url: "UrlC1", Key: "KeyC1"},
   151  		},
   152  	}, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
   153  	_, err := ctp.GetSCTs(context.Background(), []byte{0}, time.Time{})
   154  	test.AssertNotError(t, err, "GetSCTs failed")
   155  	test.AssertMetricWithLabelsEquals(t, ctp.winnerCounter, prometheus.Labels{"url": "UrlB1", "result": succeeded}, 1)
   156  	test.AssertMetricWithLabelsEquals(t, ctp.winnerCounter, prometheus.Labels{"url": "UrlC1", "result": succeeded}, 1)
   157  }
   158  
   159  func TestGetSCTsFailMetrics(t *testing.T) {
   160  	// Ensure the proper metrics are incremented when GetSCTs fails.
   161  	ctp := New(&mockFailOnePub{badURL: "UrlA1"}, loglist.List{
   162  		"OperA": {
   163  			"LogA1": {Url: "UrlA1", Key: "KeyA1"},
   164  		},
   165  	}, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
   166  	_, err := ctp.GetSCTs(context.Background(), []byte{0}, time.Time{})
   167  	test.AssertError(t, err, "GetSCTs should have failed")
   168  	test.AssertErrorIs(t, err, berrors.MissingSCTs)
   169  	test.AssertMetricWithLabelsEquals(t, ctp.winnerCounter, prometheus.Labels{"url": "UrlA1", "result": failed}, 1)
   170  
   171  	// Ensure the proper metrics are incremented when GetSCTs times out.
   172  	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
   173  	defer cancel()
   174  
   175  	ctp = New(&mockSlowPub{}, loglist.List{
   176  		"OperA": {
   177  			"LogA1": {Url: "UrlA1", Key: "KeyA1"},
   178  		},
   179  	}, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
   180  	_, err = ctp.GetSCTs(ctx, []byte{0}, time.Time{})
   181  	test.AssertError(t, err, "GetSCTs should have timed out")
   182  	test.AssertErrorIs(t, err, berrors.MissingSCTs)
   183  	test.AssertContains(t, err.Error(), context.DeadlineExceeded.Error())
   184  	test.AssertMetricWithLabelsEquals(t, ctp.winnerCounter, prometheus.Labels{"url": "UrlA1", "result": failed}, 1)
   185  }
   186  
   187  func TestLogListMetrics(t *testing.T) {
   188  	// Multiple operator groups with configured logs.
   189  	ctp := New(&mockPub{}, loglist.List{
   190  		"OperA": {
   191  			"LogA1": {Url: "UrlA1", Key: "KeyA1"},
   192  			"LogA2": {Url: "UrlA2", Key: "KeyA2"},
   193  		},
   194  		"OperB": {
   195  			"LogB1": {Url: "UrlB1", Key: "KeyB1"},
   196  		},
   197  		"OperC": {
   198  			"LogC1": {Url: "UrlC1", Key: "KeyC1"},
   199  		},
   200  	}, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
   201  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperA", "source": "sctLogs"}, 2)
   202  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperB", "source": "sctLogs"}, 1)
   203  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperC", "source": "sctLogs"}, 1)
   204  
   205  	// Multiple operator groups, no configured logs in one group
   206  	ctp = New(&mockPub{}, loglist.List{
   207  		"OperA": {
   208  			"LogA1": {Url: "UrlA1", Key: "KeyA1"},
   209  			"LogA2": {Url: "UrlA2", Key: "KeyA2"},
   210  		},
   211  		"OperB": {
   212  			"LogB1": {Url: "UrlB1", Key: "KeyB1"},
   213  		},
   214  		"OperC": {},
   215  	}, nil, loglist.List{
   216  		"OperA": {
   217  			"LogA1": {Url: "UrlA1", Key: "KeyA1"},
   218  		},
   219  		"OperB": {},
   220  		"OperC": {
   221  			"LogC1": {Url: "UrlC1", Key: "KeyC1"},
   222  		},
   223  	}, 0, blog.NewMock(), metrics.NoopRegisterer)
   224  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperA", "source": "sctLogs"}, 2)
   225  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperB", "source": "sctLogs"}, 1)
   226  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperC", "source": "sctLogs"}, 0)
   227  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperA", "source": "finalLogs"}, 1)
   228  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperB", "source": "finalLogs"}, 0)
   229  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperC", "source": "finalLogs"}, 1)
   230  
   231  	// Multiple operator groups with no configured logs.
   232  	ctp = New(&mockPub{}, loglist.List{
   233  		"OperA": {},
   234  		"OperB": {},
   235  	}, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
   236  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperA", "source": "sctLogs"}, 0)
   237  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperB", "source": "sctLogs"}, 0)
   238  
   239  	// Single operator group with no configured logs.
   240  	ctp = New(&mockPub{}, loglist.List{
   241  		"OperA": {},
   242  	}, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
   243  	test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperA", "source": "allLogs"}, 0)
   244  
   245  	fc := clock.NewFake()
   246  	Tomorrow := fc.Now().Add(24 * time.Hour)
   247  	NextWeek := fc.Now().Add(7 * 24 * time.Hour)
   248  
   249  	// Multiple operator groups with configured logs.
   250  	ctp = New(&mockPub{}, loglist.List{
   251  		"OperA": {
   252  			"LogA1": {Url: "UrlA1", Key: "KeyA1", Name: "LogA1", EndExclusive: Tomorrow},
   253  			"LogA2": {Url: "UrlA2", Key: "KeyA2", Name: "LogA2", EndExclusive: NextWeek},
   254  		},
   255  		"OperB": {
   256  			"LogB1": {Url: "UrlB1", Key: "KeyB1", Name: "LogB1", EndExclusive: Tomorrow},
   257  		},
   258  	}, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
   259  	test.AssertMetricWithLabelsEquals(t, ctp.shardExpiryGauge, prometheus.Labels{"operator": "OperA", "logID": "LogA1"}, 86400)
   260  	test.AssertMetricWithLabelsEquals(t, ctp.shardExpiryGauge, prometheus.Labels{"operator": "OperA", "logID": "LogA2"}, 604800)
   261  	test.AssertMetricWithLabelsEquals(t, ctp.shardExpiryGauge, prometheus.Labels{"operator": "OperB", "logID": "LogB1"}, 86400)
   262  }
   263  

View as plain text