Skip to content

Commit

Permalink
monitor: make monitor testable (#1040)
Browse files Browse the repository at this point in the history
This makes the Handler and monitor funcs accept some arguments so we can
write tests for them. We write one simple golden path test to
demonstrate.

This is an outcome of trying to make the AUTOGRAPH_URL env var handling
better in #1012. That PR will be rebased on to this one.
  • Loading branch information
jmhodges authored Oct 16, 2024
1 parent 98403c2 commit 3eb0bf4
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 18 deletions.
7 changes: 4 additions & 3 deletions signer/genericrsa/rsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ const (

// Options contains options for creating and verifying PKCS15 signatures.
type Options struct {
// Hash, if not zero, overrides the hash function passed to SignPSS.
// This is the only way to specify the hash function when using the
// crypto.Signer interface.
// Hash, if not zero, overrides the hash function passed to SignPSS. This is
// the only way to specify the hash function when using the crypto.Signer
// interface. crypto.Hash is "just" a uint, so the json.Marshal/Unmarshal
// calls in this package work on it, but it's a lil sketchy.
Hash crypto.Hash
}

Expand Down
19 changes: 8 additions & 11 deletions tools/autograph-monitor/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,8 @@ type configuration struct {

const inputdata string = "AUTOGRAPH MONITORING"

var (
conf configuration
)

func main() {
conf := &configuration{}
conf.url = os.Getenv("AUTOGRAPH_URL")
if conf.url == "" {
log.Fatal("AUTOGRAPH_URL must be set to the base url of the autograph service")
Expand Down Expand Up @@ -90,9 +87,9 @@ func main() {

if os.Getenv("LAMBDA_TASK_ROOT") != "" {
// we are inside a lambda environment so run as lambda
lambda.Start(Handler)
lambda.Start(func() error { return Handler(conf, http.DefaultClient) })
} else {
err := Handler()
err := Handler(conf, http.DefaultClient)
if err != nil {
log.Fatalf("Unhandled exception from monitor: %s", err)
}
Expand Down Expand Up @@ -122,19 +119,19 @@ func LoadCertsToTruststore(pemStrings []string) (*x509.CertPool, []string, error

// Handler is a wrapper around monitor() that performs garbage collection
// before returning
func Handler() (err error) {
func Handler(conf *configuration, client *http.Client) (err error) {
defer func() {
// force gc run
// https://bugzilla.mozilla.org/show_bug.cgi?id=1621133
t1 := time.Now()
runtime.GC()
log.Println("Garbage collected in", time.Since(t1))
}()
return monitor()
return monitor(conf, client)
}

// monitor contacts the autograph service and verifies all monitoring signatures
func monitor() error {
func monitor(conf *configuration, client *http.Client) error {
log.Println("Retrieving monitoring data from", conf.url)
req, err := http.NewRequest("GET", conf.url+"__monitor__", nil)
if err != nil {
Expand All @@ -149,7 +146,7 @@ func monitor() error {
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", makeAuthHeader(req, "monitor", conf.monitoringKey))

resp, err := http.DefaultClient.Do(req)
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request to monitor endpoint failed: %w", err)
}
Expand Down Expand Up @@ -191,7 +188,7 @@ func monitor() error {
err = verifyContentSignature(x5uClient, conf.notifier, conf.rootHashes, contentSignatureIgnoredLeafCertCNs, response, []byte(inputdata))
case xpi.Type:
log.Printf("Verifying XPI signature from signer %q", response.SignerID)
err = verifyXPISignature(response.Signature)
err = verifyXPISignature(response.Signature, conf.truststore, conf.depTruststore)
case apk2.Type:
// we don't verify apk2 signatures because they can only be obtained on valid
// APK files, which is too big to fit in the monitoring logic
Expand Down
93 changes: 93 additions & 0 deletions tools/autograph-monitor/monitor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package main

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/mozilla-services/autograph/formats"
)

func TestGoldenPath(t *testing.T) {
firstRsaKey := generateRSAKey(t)
secondRsaKey := generateRSAKey(t)
mux := http.NewServeMux()
mux.HandleFunc("GET /__monitor__", func(w http.ResponseWriter, r *http.Request) {
respBytes1, err := signatureRespForGenericaRSA("first", firstRsaKey)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
respBytes2, err := signatureRespForGenericaRSA("second", secondRsaKey)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
w.Write(respBytes1)
w.Write(respBytes2)
})
server := httptest.NewTLSServer(mux)
defer server.Close()

conf := &configuration{
url: server.URL + "/",
monitoringKey: "fakenotused",
truststore: x509.NewCertPool(),
}
err := Handler(conf, server.Client())
if err != nil {
t.Errorf("handler error: %v", err)
}
}

func generateRSAKey(t *testing.T) *rsa.PrivateKey {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("failed to generate RSA key: %v", err)
}
return key
}

type rsaOption struct {
// crypto.Hash is just a uint, so the json.Marshal/Unmarshal calls work,
// but it's a lil sketchy.
crypto.Hash
}

func (r *rsaOption) HashFunc() crypto.Hash {
return r.Hash
}

func signatureRespForGenericaRSA(signerID string, key *rsa.PrivateKey) ([]byte, error) {
rsaOpt := &rsaOption{Hash: crypto.SHA256}
hash := rsaOpt.Hash.HashFunc().New()
_, err := hash.Write([]byte(inputdata))
if err != nil {
return nil, err
}

sig, err := key.Sign(rand.Reader, hash.Sum(nil), rsaOpt)
if err != nil {
return nil, err
}
marshalled, err := x509.MarshalPKIXPublicKey(key.Public())
if err != nil {
return nil, err
}
resp := &formats.SignatureResponse{
SignerID: signerID,
Type: "genericrsa",
Signature: base64.StdEncoding.EncodeToString(sig),
PublicKey: base64.StdEncoding.EncodeToString(marshalled),
Mode: "pkcs15",
SignerOpts: rsaOpt,
}
return json.Marshal(resp)
}
9 changes: 5 additions & 4 deletions tools/autograph-monitor/xpi.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package main

import (
"crypto/x509"
"log"

"github.com/mozilla-services/autograph/signer/xpi"
)

func verifyXPISignature(sig string) error {
func verifyXPISignature(sig string, truststore, depTruststore *x509.CertPool) error {
xpiSig, err := xpi.Unmarshal(sig, []byte(inputdata))
if err != nil {
log.Fatal(err)
}
err = xpiSig.VerifyWithChain(conf.truststore)
if err == nil || conf.depTruststore == nil {
err = xpiSig.VerifyWithChain(truststore)
if err == nil || depTruststore == nil {
return err
}
log.Printf("Got error %s verifying XPI signature with rel truststore trying dep truststore", err)
return xpiSig.VerifyWithChain(conf.depTruststore)
return xpiSig.VerifyWithChain(depTruststore)
}

0 comments on commit 3eb0bf4

Please sign in to comment.