diff --git a/.github/workflows/reusable_monitoring.yml b/.github/workflows/reusable_monitoring.yml index d4dbcf3..187885f 100644 --- a/.github/workflows/reusable_monitoring.yml +++ b/.github/workflows/reusable_monitoring.yml @@ -76,7 +76,7 @@ jobs: run: cat ${{ env.LOG_FILE }} # Skip on first run continue-on-error: true - - run: go run ./cmd/verifier --file ${{ env.LOG_FILE }} --once --monitored-values "${{ inputs.identities }}" --user-agent "${{ format('{0}/{1}/{2}', needs.detect-workflow.outputs.repository, needs.detect-workflow.outputs.ref, github.run_id) }}" + - run: go run ./cmd/verifier --config-file ${{ env.REUSABLE_MONITORING_CONFIG_FILE }} --once - name: Upload checkpoint uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: diff --git a/cmd/verifier/config.go b/cmd/verifier/config.go new file mode 100644 index 0000000..1135c82 --- /dev/null +++ b/cmd/verifier/config.go @@ -0,0 +1,27 @@ +// +// Copyright 2024 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "time" +) + +type ConsistencyCheckConfiguration struct { + ServerURL string `yaml:"serverURL"` + UserAgentString string `yaml:"userAgentString"` + LogInfoFile string `yaml:"logInfoFile"` + Interval *time.Duration `yaml:"interval"` +} diff --git a/cmd/verifier/main.go b/cmd/verifier/main.go index f9aa9ff..170ee35 100644 --- a/cmd/verifier/main.go +++ b/cmd/verifier/main.go @@ -20,12 +20,14 @@ import ( "flag" "fmt" "log" + "os" "runtime" "strings" "time" "github.com/sigstore/rekor-monitor/pkg/rekor" "github.com/sigstore/rekor/pkg/client" + "gopkg.in/yaml.v2" "sigs.k8s.io/release-utils/version" ) @@ -42,14 +44,37 @@ const ( // indefinitely to perform consistency check for every time interval that was specified. func main() { // Command-line flags that are parameters to the verifier job - serverURL := flag.String("url", publicRekorServerURL, "URL to the rekor server that is to be monitored") - interval := flag.Duration("interval", 5*time.Minute, "Length of interval between each periodical consistency check") - logInfoFile := flag.String("file", logInfoFileName, "Name of the file containing initial merkle tree information") + configFilePath := flag.String("config-file", "", "Name of the file containing the consistency check workflow configuration settings") once := flag.Bool("once", false, "Perform consistency check once and exit") - userAgentString := flag.String("user-agent", "", "details to include in the user agent string") flag.Parse() - rekorClient, err := client.GetRekorClient(*serverURL, client.WithUserAgent(strings.TrimSpace(fmt.Sprintf("rekor-monitor/%s (%s; %s) %s", version.GetVersionInfo().GitVersion, runtime.GOOS, runtime.GOARCH, *userAgentString)))) + if configFilePath == nil { + log.Fatalf("empty configuration file path") + } + + readConfig, err := os.ReadFile(*configFilePath) + if err != nil { + log.Fatalf("error reading from identity monitor configuration file: %v", err) + } + + configString := string(readConfig) + var config ConsistencyCheckConfiguration + if err := yaml.Unmarshal([]byte(configString), &config); err != nil { + log.Fatalf("error parsing identities: %v", err) + } + + if config.ServerURL == "" { + config.ServerURL = publicRekorServerURL + } + if config.Interval == nil { + defaultInterval := time.Hour + config.Interval = &defaultInterval + } + if config.LogInfoFile == "" { + config.LogInfoFile = logInfoFileName + } + + rekorClient, err := client.GetRekorClient(config.ServerURL, client.WithUserAgent(strings.TrimSpace(fmt.Sprintf("rekor-monitor/%s (%s; %s) %s", version.GetVersionInfo().GitVersion, runtime.GOOS, runtime.GOARCH, config.UserAgentString)))) if err != nil { log.Fatalf("getting Rekor client: %v", err) } @@ -59,13 +84,29 @@ func main() { log.Fatal(err) } - err = rekor.VerifyConsistencyCheckInputs(interval, logInfoFile, once) + err = rekor.VerifyConsistencyCheckInputs(config.Interval, &config.LogInfoFile, once) if err != nil { log.Fatal(err) } - err = rekor.RunConsistencyCheck(*interval, rekorClient, verifier, *logInfoFile, *once) - if err != nil { - log.Fatalf("%v", err) + ticker := time.NewTicker(*config.Interval) + defer ticker.Stop() + + // Loop will: + // 1. Fetch latest checkpoint and verify + // 2. If old checkpoint is present, verify consistency proof + // 3. Write latest checkpoint to file + + // To get an immediate first tick + for ; ; <-ticker.C { + err = rekor.RunConsistencyCheck(rekorClient, verifier, config.LogInfoFile) + if err != nil { + fmt.Fprintf(os.Stderr, "error running consistency check: %v", err) + return + } + + if *once { + return + } } } diff --git a/pkg/rekor/verifier.go b/pkg/rekor/verifier.go index d657ea5..94cbc0d 100644 --- a/pkg/rekor/verifier.go +++ b/pkg/rekor/verifier.go @@ -107,53 +107,39 @@ func VerifyConsistencyCheckInputs(interval *time.Duration, logInfoFile *string, } // RunConsistencyCheck periodically verifies the root hash consistency of a Rekor log. -func RunConsistencyCheck(interval time.Duration, rekorClient *client.Rekor, verifier signature.Verifier, logInfoFile string, once bool) error { - ticker := time.NewTicker(interval) - defer ticker.Stop() - - // Loop will: - // 1. Fetch latest checkpoint and verify - // 2. If old checkpoint is present, verify consistency proof - // 3. Write latest checkpoint to file +func RunConsistencyCheck(rekorClient *client.Rekor, verifier signature.Verifier, logInfoFile string) error { + logInfo, err := GetLogInfo(context.Background(), rekorClient) + if err != nil { + return fmt.Errorf("failed to get log info: %v", err) + } + checkpoint, err := verifyLatestCheckpointSignature(logInfo, verifier) + if err != nil { + return fmt.Errorf("failed to verify signature of latest checkpoint: %v", err) + } - // To get an immediate first tick - for ; ; <-ticker.C { - logInfo, err := GetLogInfo(context.Background(), rekorClient) - if err != nil { - return fmt.Errorf("failed to get log info: %v", err) - } - checkpoint, err := verifyLatestCheckpointSignature(logInfo, verifier) + fi, err := os.Stat(logInfoFile) + // File containing previous checkpoints exists + var prevCheckpoint *util.SignedCheckpoint + if err == nil && fi.Size() != 0 { + prevCheckpoint, err = verifyCheckpointConsistency(logInfoFile, checkpoint, *logInfo.TreeID, rekorClient, verifier) if err != nil { - return fmt.Errorf("failed to verify signature of latest checkpoint: %v", err) + return fmt.Errorf("failed to verify previous checkpoint: %v", err) } - fi, err := os.Stat(logInfoFile) - // File containing previous checkpoints exists - var prevCheckpoint *util.SignedCheckpoint - if err == nil && fi.Size() != 0 { - prevCheckpoint, err = verifyCheckpointConsistency(logInfoFile, checkpoint, *logInfo.TreeID, rekorClient, verifier) - if err != nil { - return fmt.Errorf("failed to verify previous checkpoint: %v", err) - } - - } - - // Write if there was no stored checkpoint or the sizes differ - if prevCheckpoint == nil || prevCheckpoint.Size != checkpoint.Size { - if err := file.WriteCheckpoint(checkpoint, logInfoFile); err != nil { - return fmt.Errorf("failed to write checkpoint: %v", err) - } - } + } - // TODO: Switch to writing checkpoints to GitHub so that the history is preserved. Then we only need - // to persist the last checkpoint. - // Delete old checkpoints to avoid the log growing indefinitely - if err := file.DeleteOldCheckpoints(logInfoFile); err != nil { - return fmt.Errorf("failed to delete old checkpoints: %v", err) + // Write if there was no stored checkpoint or the sizes differ + if prevCheckpoint == nil || prevCheckpoint.Size != checkpoint.Size { + if err := file.WriteCheckpoint(checkpoint, logInfoFile); err != nil { + return fmt.Errorf("failed to write checkpoint: %v", err) } + } - if once { - return nil - } + // TODO: Switch to writing checkpoints to GitHub so that the history is preserved. Then we only need + // to persist the last checkpoint. + // Delete old checkpoints to avoid the log growing indefinitely + if err := file.DeleteOldCheckpoints(logInfoFile); err != nil { + return fmt.Errorf("failed to delete old checkpoints: %v", err) } + return nil }