1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package integration
16
17 import (
18 "context"
19 "encoding/pem"
20 "flag"
21 "fmt"
22 "os"
23 "strings"
24 "testing"
25 "time"
26
27 "github.com/google/certificate-transparency-go/trillian/ctfe"
28 "github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
29 "github.com/google/trillian/crypto/keyspb"
30 "github.com/google/trillian/storage/testdb"
31 "google.golang.org/protobuf/types/known/anypb"
32 timestamp "google.golang.org/protobuf/types/known/timestamppb"
33 )
34
35 var (
36 adminServer = flag.String("admin_server", "", "Address of log admin RPC server. Required for lifecycle test.")
37 httpServers = flag.String("ct_http_servers", "localhost:8092", "Comma-separated list of (assumed interchangeable) servers, each as address:port")
38 metricsServers = flag.String("ct_metrics_servers", "localhost:8093", "Comma-separated list of (assumed interchangeable) metrics servers, each as address:port")
39 testDir = flag.String("testdata_dir", "testdata", "Name of directory with test data")
40 logConfig = flag.String("log_config", "", "File holding log config in JSON")
41 mmd = flag.Duration("mmd", 30*time.Second, "MMD for tested logs")
42 skipStats = flag.Bool("skip_stats", false, "Skip checks of expected log statistics")
43 )
44
45 func commonSetup(t *testing.T) []*configpb.LogConfig {
46 t.Helper()
47 if *logConfig == "" {
48 t.Skip("Integration test skipped as no log config provided")
49 }
50
51 cfgs, err := ctfe.LogConfigFromFile(*logConfig)
52 if err != nil {
53 t.Fatalf("Failed to read log config: %v", err)
54 }
55 return cfgs
56 }
57
58 func TestLiveCTIntegration(t *testing.T) {
59 flag.Parse()
60 cfgs := commonSetup(t)
61 for _, cfg := range cfgs {
62 cfg := cfg
63 t.Run(cfg.Prefix, func(t *testing.T) {
64 t.Parallel()
65 var stats *logStats
66 if !*skipStats {
67 stats = newLogStats(cfg.LogId)
68 }
69 if err := RunCTIntegrationForLog(cfg, *httpServers, *metricsServers, *testDir, *mmd, stats); err != nil {
70 t.Errorf("%s: failed: %v", cfg.Prefix, err)
71 }
72 })
73 }
74 }
75
76 func TestLiveLifecycleCTIntegration(t *testing.T) {
77 flag.Parse()
78 cfgs := commonSetup(t)
79 for _, cfg := range cfgs {
80 cfg := cfg
81 t.Run(cfg.Prefix, func(t *testing.T) {
82 t.Parallel()
83 var stats *logStats
84 if !*skipStats {
85 stats = newLogStats(cfg.LogId)
86 }
87 if err := RunCTLifecycleForLog(cfg, *httpServers, *metricsServers, *adminServer, *testDir, *mmd, stats); err != nil {
88 t.Errorf("%s: failed: %v", cfg.Prefix, err)
89 }
90 })
91 }
92 }
93
94 const (
95 rootsPEMFile = "../testdata/fake-ca.cert"
96 pubKeyPEMFile = "../testdata/ct-http-server.pubkey.pem"
97 privKeyPEMFile = "../testdata/ct-http-server.privkey.pem"
98 privKeyPassword = "dirk"
99 )
100
101 func TestInProcessCTIntegration(t *testing.T) {
102 testdb.SkipIfNoMySQL(t)
103
104 pubKeyDER, err := loadPublicKey(pubKeyPEMFile)
105 if err != nil {
106 t.Fatalf("Could not load public key: %v", err)
107 }
108
109 pubKey := &keyspb.PublicKey{Der: pubKeyDER}
110 privKey, err := anypb.New(&keyspb.PEMKeyFile{Path: privKeyPEMFile, Password: privKeyPassword})
111 if err != nil {
112 t.Fatalf("Could not marshal private key as protobuf Any: %v", err)
113 }
114
115 ctx := context.Background()
116 cfgs := []*configpb.LogConfig{
117 {
118 Prefix: "athos",
119 RootsPemFile: []string{rootsPEMFile},
120 PublicKey: pubKey,
121 PrivateKey: privKey,
122 },
123 {
124 Prefix: "porthos",
125 RootsPemFile: []string{rootsPEMFile},
126 PublicKey: pubKey,
127 PrivateKey: privKey,
128 },
129 {
130 Prefix: "aramis",
131 RootsPemFile: []string{rootsPEMFile},
132 PublicKey: pubKey,
133 PrivateKey: privKey,
134 },
135 }
136
137 env, err := NewCTLogEnv(ctx, cfgs, 2, "TestInProcessCTIntegration")
138 if err != nil {
139 t.Fatalf("Failed to launch test environment: %v", err)
140 }
141 defer env.Close()
142
143 mmd := 120 * time.Second
144
145
146 t.Run("container", func(t *testing.T) {
147 for _, cfg := range cfgs {
148 cfg := cfg
149 t.Run(cfg.Prefix, func(t *testing.T) {
150 t.Parallel()
151 stats := newLogStats(cfg.LogId)
152 if err := RunCTIntegrationForLog(cfg, env.CTAddr, env.CTAddr, "../testdata", mmd, stats); err != nil {
153 t.Errorf("%s: failed: %v", cfg.Prefix, err)
154 }
155 })
156 }
157 })
158 }
159
160 func loadPublicKey(path string) ([]byte, error) {
161 pemKey, err := os.ReadFile(path)
162 if err != nil {
163 return nil, err
164 }
165
166 block, _ := pem.Decode(pemKey)
167 if block == nil {
168 return nil, fmt.Errorf("could not decode PEM public key: %v", path)
169 }
170 if block.Type != "PUBLIC KEY" {
171 return nil, fmt.Errorf("got %q PEM, want \"PUBLIC KEY\": %v", block.Type, path)
172 }
173
174 return block.Bytes, nil
175 }
176
177 func TestNotAfterForLog(t *testing.T) {
178 tests := []struct {
179 desc string
180 cfg *configpb.LogConfig
181 want time.Time
182 wantErr string
183 }{
184 {
185 desc: "no-limits",
186 cfg: &configpb.LogConfig{},
187 want: time.Now().Add(24 * time.Hour),
188 },
189 {
190 desc: "malformed-start",
191 cfg: &configpb.LogConfig{
192 NotAfterStart: ×tamp.Timestamp{Seconds: 1000, Nanos: -1},
193 },
194 wantErr: "failed to parse NotAfterStart",
195 },
196 {
197 desc: "malformed-limit",
198 cfg: &configpb.LogConfig{
199 NotAfterLimit: ×tamp.Timestamp{Seconds: 1000, Nanos: -1},
200 },
201 wantErr: "failed to parse NotAfterLimit",
202 },
203 {
204 desc: "start-no-limit",
205 cfg: &configpb.LogConfig{
206 NotAfterStart: ×tamp.Timestamp{Seconds: 1230000000},
207 },
208 want: time.Date(2008, 12, 23, 2, 40, 0, 0, time.UTC).Add(24 * time.Hour),
209 },
210 {
211 desc: "limit-no-start",
212 cfg: &configpb.LogConfig{
213 NotAfterLimit: ×tamp.Timestamp{Seconds: 1230000000},
214 },
215 want: time.Date(2008, 12, 23, 2, 40, 0, 0, time.UTC).Add(-1 * time.Hour),
216 },
217 {
218 desc: "mid-range",
219 cfg: &configpb.LogConfig{
220 NotAfterStart: ×tamp.Timestamp{Seconds: 1230000000},
221 NotAfterLimit: ×tamp.Timestamp{Seconds: 1230000000 + 86400},
222 },
223 want: time.Date(2008, 12, 23, 2, 40, 0, 0, time.UTC).Add(43200 * time.Second),
224 },
225 }
226 for _, test := range tests {
227 t.Run(test.desc, func(t *testing.T) {
228 got, err := NotAfterForLog(test.cfg)
229 if err != nil {
230 if len(test.wantErr) == 0 {
231 t.Errorf("NotAfterForLog()=nil,%v, want _,nil", err)
232 } else if !strings.Contains(err.Error(), test.wantErr) {
233 t.Errorf("NotAfterForLog()=nil,%v, want _,err containing %q", err, test.wantErr)
234 }
235 return
236 }
237 if len(test.wantErr) > 0 {
238 t.Errorf("NotAfterForLog()=%v, nil, want nil,err containing %q", got, test.wantErr)
239 }
240 delta := got.Sub(test.want)
241 if delta < 0 {
242 delta = -delta
243 }
244 if delta > time.Second {
245 t.Errorf("NotAfterForLog()=%v, want %v (delta %v)", got, test.want, delta)
246 }
247 })
248
249 }
250 }
251
View as plain text