...

Source file src/github.com/sigstore/rekor/cmd/rekor-server/e2e_test.go

Documentation: github.com/sigstore/rekor/cmd/rekor-server

     1  //
     2  // Copyright 2022 The Sigstore Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  //go:build e2e
    17  
    18  package main
    19  
    20  import (
    21  	"bufio"
    22  	"bytes"
    23  	"crypto/sha256"
    24  	"encoding/base64"
    25  	"encoding/hex"
    26  	"encoding/json"
    27  	"fmt"
    28  	"io"
    29  	"io/ioutil"
    30  	"math/rand"
    31  	"net/http"
    32  	"os"
    33  	"path/filepath"
    34  	"regexp"
    35  	"strconv"
    36  	"strings"
    37  	"testing"
    38  
    39  	"github.com/sigstore/rekor/pkg/sharding"
    40  
    41  	"github.com/sigstore/rekor/pkg/util"
    42  )
    43  
    44  func TestDuplicates(t *testing.T) {
    45  	artifactPath := filepath.Join(t.TempDir(), "artifact")
    46  	sigPath := filepath.Join(t.TempDir(), "signature.asc")
    47  
    48  	util.CreatedPGPSignedArtifact(t, artifactPath, sigPath)
    49  
    50  	// Write the public key to a file
    51  	pubPath := filepath.Join(t.TempDir(), "pubKey.asc")
    52  	if err := ioutil.WriteFile(pubPath, []byte(util.PubKey), 0644); err != nil {
    53  		t.Fatal(err)
    54  	}
    55  
    56  	// Now upload to rekor!
    57  	out := util.RunCli(t, "upload", "--artifact", artifactPath, "--signature", sigPath, "--public-key", pubPath)
    58  	util.OutputContains(t, out, "Created entry at")
    59  
    60  	// Now upload the same one again, we should get a dupe entry.
    61  	out = util.RunCli(t, "upload", "--artifact", artifactPath, "--signature", sigPath, "--public-key", pubPath)
    62  	util.OutputContains(t, out, "Entry already exists")
    63  
    64  	// Now do a new one, we should get a new entry
    65  	util.CreatedPGPSignedArtifact(t, artifactPath, sigPath)
    66  	out = util.RunCli(t, "upload", "--artifact", artifactPath, "--signature", sigPath, "--public-key", pubPath)
    67  	util.OutputContains(t, out, "Created entry at")
    68  }
    69  
    70  // Smoke test to ensure we're publishing and recording metrics when an API is
    71  // called.
    72  // TODO: use a more robust test approach here e.g. prometheus client-based?
    73  // TODO: cover all endpoints to make sure none are dropped.
    74  func TestMetricsCounts(t *testing.T) {
    75  	latencyMetric := "rekor_latency_by_api_count{method=\"GET\",path=\"/api/v1/log\"}"
    76  	qpsMetric := "rekor_qps_by_api{code=\"200\",method=\"GET\",path=\"/api/v1/log\"}"
    77  
    78  	latencyCount, err := getRekorMetricCount(latencyMetric, t)
    79  	if err != nil {
    80  		t.Fatal(err)
    81  	}
    82  
    83  	qpsCount, err := getRekorMetricCount(qpsMetric, t)
    84  	if err != nil {
    85  		t.Fatal(err)
    86  	}
    87  
    88  	resp, err := http.Get("http://localhost:3000/api/v1/log")
    89  	if err != nil {
    90  		t.Fatal(err)
    91  	}
    92  	resp.Body.Close()
    93  
    94  	latencyCount2, err := getRekorMetricCount(latencyMetric, t)
    95  	if err != nil {
    96  		t.Fatal(err)
    97  	}
    98  
    99  	qpsCount2, err := getRekorMetricCount(qpsMetric, t)
   100  	if err != nil {
   101  		t.Fatal(err)
   102  	}
   103  
   104  	if latencyCount2-latencyCount != 1 {
   105  		t.Error("rekor_latency_by_api_count did not increment")
   106  	}
   107  
   108  	if qpsCount2-qpsCount != 1 {
   109  		t.Error("rekor_qps_by_api did not increment")
   110  	}
   111  }
   112  func getRekorMetricCount(metricLine string, t *testing.T) (int, error) {
   113  	t.Helper()
   114  	re, err := regexp.Compile(fmt.Sprintf("^%s\\s*([0-9]+)$", regexp.QuoteMeta(metricLine)))
   115  	if err != nil {
   116  		return 0, err
   117  	}
   118  
   119  	resp, err := http.Get("http://localhost:2112/metrics")
   120  	if err != nil {
   121  		return 0, err
   122  	}
   123  	defer resp.Body.Close()
   124  
   125  	scanner := bufio.NewScanner(resp.Body)
   126  	for scanner.Scan() {
   127  		match := re.FindStringSubmatch(scanner.Text())
   128  		if len(match) != 2 {
   129  			continue
   130  		}
   131  
   132  		result, err := strconv.Atoi(match[1])
   133  		if err != nil {
   134  			return 0, err
   135  		}
   136  		t.Log("Matched metric line: " + scanner.Text())
   137  		return result, nil
   138  	}
   139  	return 0, nil
   140  }
   141  func TestEnvVariableValidation(t *testing.T) {
   142  	os.Setenv("REKOR_FORMAT", "bogus")
   143  	defer os.Unsetenv("REKOR_FORMAT")
   144  
   145  	util.RunCliErr(t, "loginfo")
   146  }
   147  func TestGetCLI(t *testing.T) {
   148  	// Create something and add it to the log
   149  	artifactPath := filepath.Join(t.TempDir(), "artifact")
   150  	sigPath := filepath.Join(t.TempDir(), "signature.asc")
   151  	t.Cleanup(func() {
   152  		os.Remove(artifactPath)
   153  		os.Remove(sigPath)
   154  	})
   155  	util.CreatedPGPSignedArtifact(t, artifactPath, sigPath)
   156  
   157  	// Write the public key to a file
   158  	pubPath := filepath.Join(t.TempDir(), "pubKey.asc")
   159  	if err := ioutil.WriteFile(pubPath, []byte(util.PubKey), 0644); err != nil {
   160  		t.Error(err)
   161  	}
   162  	t.Cleanup(func() {
   163  		os.Remove(pubPath)
   164  	})
   165  	out := util.RunCli(t, "upload", "--artifact", artifactPath, "--signature", sigPath, "--public-key", pubPath)
   166  	util.OutputContains(t, out, "Created entry at")
   167  
   168  	uuid, err := sharding.GetUUIDFromIDString(util.GetUUIDFromUploadOutput(t, out))
   169  	if err != nil {
   170  		t.Error(err)
   171  	}
   172  
   173  	// since we at least have 1 valid entry, check the log at index 0
   174  	util.RunCli(t, "get", "--log-index", "0")
   175  
   176  	out = util.RunCli(t, "get", "--format=json", "--uuid", uuid)
   177  
   178  	// The output here should be in JSON with this structure:
   179  	g := util.GetOut{}
   180  	if err := json.Unmarshal([]byte(out), &g); err != nil {
   181  		t.Error(err)
   182  	}
   183  
   184  	if g.IntegratedTime == 0 {
   185  		t.Errorf("Expected IntegratedTime to be set. Got %s", out)
   186  	}
   187  	// Get it with the logindex as well
   188  	util.RunCli(t, "get", "--format=json", "--log-index", strconv.Itoa(g.LogIndex))
   189  
   190  	// check index via the file and public key to ensure that the index has updated correctly
   191  	out = util.RunCli(t, "search", "--artifact", artifactPath)
   192  	util.OutputContains(t, out, uuid)
   193  
   194  	out = util.RunCli(t, "search", "--public-key", pubPath)
   195  	util.OutputContains(t, out, uuid)
   196  
   197  	artifactBytes, err := ioutil.ReadFile(artifactPath)
   198  	if err != nil {
   199  		t.Error(err)
   200  	}
   201  	sha := sha256.Sum256(artifactBytes)
   202  
   203  	out = util.RunCli(t, "search", "--sha", fmt.Sprintf("sha256:%s", hex.EncodeToString(sha[:])))
   204  	util.OutputContains(t, out, uuid)
   205  
   206  	// Exercise GET with the new EntryID (TreeID + UUID)
   207  	tid := getTreeID(t)
   208  	entryID, err := sharding.CreateEntryIDFromParts(fmt.Sprintf("%x", tid), uuid)
   209  	if err != nil {
   210  		t.Error(err)
   211  	}
   212  	out = util.RunCli(t, "get", "--format=json", "--uuid", entryID.ReturnEntryIDString())
   213  }
   214  func getTreeID(t *testing.T) int64 {
   215  	t.Helper()
   216  	out := util.RunCli(t, "loginfo")
   217  	tidStr := strings.TrimSpace(strings.Split(out, "TreeID: ")[1])
   218  	tid, err := strconv.ParseInt(tidStr, 10, 64)
   219  	if err != nil {
   220  		t.Errorf(err.Error())
   221  	}
   222  	t.Log("Tree ID:", tid)
   223  	return tid
   224  }
   225  func TestSearchNoEntriesRC1(t *testing.T) {
   226  	util.RunCliErr(t, "search", "--email", "noone@internetz.com")
   227  }
   228  func TestHostnameInSTH(t *testing.T) {
   229  	// get ID of container
   230  	rekorContainerID := strings.Trim(util.Run(t, "", "docker", "ps", "-q", "-f", "name=rekor-server"), "\n")
   231  	resp, err := http.Get(fmt.Sprintf("%s/api/v1/log", rekorServer()))
   232  	if err != nil {
   233  		t.Fatal(err)
   234  	}
   235  	defer resp.Body.Close()
   236  
   237  	body, err := ioutil.ReadAll(resp.Body)
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  
   242  	if !strings.Contains(string(body), fmt.Sprintf(" %s ", rekorContainerID)) {
   243  		t.Errorf("logInfo does not contain the hostname (%v) of the rekor-server container: %v", rekorContainerID, string(body))
   244  	}
   245  	if strings.Contains(string(body), "rekor.sigstore.dev") {
   246  		t.Errorf("logInfo contains rekor.sigstore.dev which should not be set by default")
   247  	}
   248  }
   249  func rekorServer() string {
   250  	if s := os.Getenv("REKOR_SERVER"); s != "" {
   251  		return s
   252  	}
   253  	return "http://localhost:3000"
   254  }
   255  func TestSearchSHA512(t *testing.T) {
   256  	sha512 := "c7694a1112ea1404a3c5852bdda04c2cc224b3567ef6ceb8204dbf2b382daacfc6837ee2ed9d5b82c90b880a3c7289778dbd5a8c2c08193459bcf7bd44581ed0"
   257  	var out string
   258  	out = util.RunCli(t, "upload", "--type", "intoto:0.0.2",
   259  		"--artifact", "tests/envelope.sha512",
   260  		"--pki-format", "x509",
   261  		"--public-key", "tests/test_sha512.pub")
   262  	util.OutputContains(t, out, "Created entry at")
   263  	uuid := util.GetUUIDFromTimestampOutput(t, out)
   264  	out = util.RunCli(t, "search", "--sha", fmt.Sprintf("sha512:%s", sha512))
   265  	util.OutputContains(t, out, uuid)
   266  }
   267  func TestVerifyNonExistentUUID(t *testing.T) {
   268  	// this uuid is extremely likely to not exist
   269  	out := util.RunCliErr(t, "verify", "--uuid", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
   270  	util.OutputContains(t, out, "entry in log cannot be located")
   271  
   272  	// Check response code
   273  	tid := getTreeID(t)
   274  	h := sha256.Sum256([]byte("123"))
   275  	entryID, err := sharding.CreateEntryIDFromParts(fmt.Sprintf("%x", tid),
   276  		hex.EncodeToString(h[:]))
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  	body := fmt.Sprintf("{\"entryUUIDs\":[\"%s\"]}", entryID.ReturnEntryIDString())
   281  	resp, err := http.Post(fmt.Sprintf("%s/api/v1/log/entries/retrieve", rekorServer()),
   282  		"application/json",
   283  		bytes.NewReader([]byte(body)))
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	c, _ := ioutil.ReadAll(resp.Body)
   288  	if resp.StatusCode != 200 {
   289  		t.Fatalf("expected status 200, got %d instead", resp.StatusCode)
   290  	}
   291  	if strings.TrimSpace(string(c)) != "[]" {
   292  		t.Fatalf("expected empty JSON array as response, got %s instead", string(c))
   293  	}
   294  }
   295  func TestSearchQueryLimit(t *testing.T) {
   296  	tests := []struct {
   297  		description string
   298  		limit       int
   299  		shouldErr   bool
   300  	}{
   301  		{
   302  			description: "request 6 entries",
   303  			limit:       6,
   304  		}, {
   305  			description: "request 10 entries",
   306  			limit:       10,
   307  		}, {
   308  			description: "request more than max",
   309  			limit:       12,
   310  			shouldErr:   true,
   311  		},
   312  	}
   313  
   314  	for _, test := range tests {
   315  		t.Run(test.description, func(t *testing.T) {
   316  			b := bytes.NewReader(getBody(t, test.limit))
   317  			resp, err := http.Post(fmt.Sprintf("%s/api/v1/log/entries/retrieve", rekorServer()), "application/json", b)
   318  			if err != nil {
   319  				t.Fatal(err)
   320  			}
   321  			c, _ := ioutil.ReadAll(resp.Body)
   322  			t.Log(string(c))
   323  			if resp.StatusCode != 200 && !test.shouldErr {
   324  				t.Fatalf("expected test to pass but it failed")
   325  			}
   326  			if resp.StatusCode != 422 && test.shouldErr {
   327  				t.Fatal("expected test to fail but it passed")
   328  			}
   329  			if test.shouldErr && !strings.Contains(string(c), "logIndexes in body should have at most 10 items") {
   330  				t.Fatal("expected max limit error but didn't get it")
   331  			}
   332  		})
   333  	}
   334  }
   335  func getBody(t *testing.T, limit int) []byte {
   336  	t.Helper()
   337  	s := fmt.Sprintf("{\"logIndexes\": [%d", limit)
   338  	for i := 1; i < limit; i++ {
   339  		s = fmt.Sprintf("%s, %d", s, i)
   340  	}
   341  	s += "]}"
   342  	return []byte(s)
   343  }
   344  func TestSearchQueryMalformedEntry(t *testing.T) {
   345  	wd, err := os.Getwd()
   346  	if err != nil {
   347  		t.Fatal(err)
   348  	}
   349  	b, err := ioutil.ReadFile(filepath.Join(wd, "tests/rekor.json"))
   350  	if err != nil {
   351  		t.Fatal(err)
   352  	}
   353  	body := fmt.Sprintf("{\"entries\":[\"%s\"]}", b)
   354  	resp, err := http.Post(fmt.Sprintf("%s/api/v1/log/entries/retrieve", rekorServer()),
   355  		"application/json",
   356  		bytes.NewBuffer([]byte(body)))
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  	if resp.StatusCode != 400 {
   361  		t.Fatalf("expected status 400, got %d instead", resp.StatusCode)
   362  	}
   363  }
   364  
   365  func TestHTTPMaxRequestBodySize(t *testing.T) {
   366  	// default value is 32Mb so let's try to propose something bigger
   367  	pipeR, pipeW := io.Pipe()
   368  	go func() {
   369  		_, _ = io.CopyN(base64.NewEncoder(base64.StdEncoding, pipeW), rand.New(rand.NewSource(123)), 33*1024768)
   370  		pipeW.Close()
   371  	}()
   372  	// json parsing will hit first so we need to make sure this is valid JSON
   373  	bodyReader := io.MultiReader(strings.NewReader("{ \"key\": \""), pipeR, strings.NewReader("\"}"))
   374  	resp, err := http.Post(fmt.Sprintf("%s/api/v1/log/entries/retrieve", rekorServer()),
   375  		"application/json",
   376  		bodyReader)
   377  	if err != nil {
   378  		t.Fatal(err)
   379  	}
   380  	if resp.StatusCode != http.StatusRequestEntityTooLarge {
   381  		t.Fatalf("expected status %d, got %d instead", http.StatusRequestEntityTooLarge, resp.StatusCode)
   382  	}
   383  }
   384  

View as plain text