Skip to content

Commit

Permalink
Add v1 Fulcio endpoint to prober (#1160)
Browse files Browse the repository at this point in the history
There is an SLO set up for the /api/v1/signingCert Fulcio endpoint[1],
but it is currently reporting "No SLO status data" because the prober
was never testing that endpoint. This lead to an outage that went
undetected by the monitoring system.

Cosign uses the legacy certificate request endpoint in its Fulcio
client[2][3]. This means that the v1 endpoint is likely the most used
and therefore an important health indicator. This change adds the v1
endpoint to the prober test, which should populate Prometheus with data
which should activate the SLO.

[1] https://github.com/sigstore/scaffolding/blob/8f7aa097e54eabcecbc671818f9eb5f0e723e54b/terraform/gcp/modules/monitoring/fulcio/slo.tf#L79-L83
[2] https://github.com/sigstore/cosign/blob/79db196e2d97e7dfc4d8201ef829d4ce906605a7/cmd/cosign/cli/fulcio/fulcio.go#L32
[3] https://github.com/sigstore/fulcio/blob/07b19da442b418ebcf072ac65a7abb25f0e3d5c8/pkg/api/client.go#L60

Signed-off-by: Colleen Murphy <[email protected]>
  • Loading branch information
cmurphy authored Jul 2, 2024
1 parent f775933 commit ed0c480
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 5 deletions.
7 changes: 6 additions & 1 deletion cmd/prober/prober.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,12 @@ func runProbers(ctx context.Context, freq int, runOnce bool, fulcioGrpcClient fu
cert, err := fulcioWriteEndpoint(ctx, priv)
if err != nil {
hasErr = true
Logger.Errorf("error running fulcio write prober: %v", err)
Logger.Errorf("error running fulcio v2 write prober: %v", err)
}
_, err = fulcioWriteLegacyEndpoint(ctx, priv)
if err != nil {
hasErr = true
Logger.Errorf("error running fulcio v1 write prober: %v", err)
}
if err := rekorWriteEndpoint(ctx, cert, priv); err != nil {
hasErr = true
Expand Down
114 changes: 110 additions & 4 deletions cmd/prober/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"crypto/x509"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -52,8 +53,9 @@ const (
defaultOIDCIssuer = "https://oauth2.sigstore.dev/auth"
defaultOIDCClientID = "sigstore"

fulcioEndpoint = "/api/v2/signingCert"
rekorEndpoint = "/api/v1/log/entries"
fulcioEndpoint = "/api/v2/signingCert"
fulcioLegacyEndpoint = "/api/v1/signingCert"
rekorEndpoint = "/api/v1/log/entries"
)

func setHeaders(req *retryablehttp.Request, token string) {
Expand All @@ -68,8 +70,75 @@ func setHeaders(req *retryablehttp.Request, token string) {
req.Header.Set("X-Cloud-Trace-Context", uuid.Must(uuid.NewV7()).String())
}

// fulcioWriteEndpoint tests the only write endpoint for Fulcio
// which is "/api/v2/signingCert", which requests a cert from Fulcio
// fulcioWriteLegacyEndpoint tests the /api/v1/signingCert write endpoint for Fulcio.
func fulcioWriteLegacyEndpoint(ctx context.Context, priv *ecdsa.PrivateKey) (*x509.Certificate, error) {
if !all.Enabled(ctx) {
return nil, fmt.Errorf("no auth provider for fulcio is enabled")
}
tok, err := providers.Provide(ctx, "sigstore")
if err != nil {
return nil, fmt.Errorf("getting provider: %w", err)
}
b, err := legacyCertificateRequest(ctx, tok, priv)
if err != nil {
return nil, fmt.Errorf("certificate response: %w", err)
}

// Construct the API endpoint for this handler
endpoint := fulcioLegacyEndpoint
hostPath := fulcioURL + endpoint

req, err := retryablehttp.NewRequest(http.MethodPost, hostPath, bytes.NewBuffer(b))
if err != nil {
return nil, fmt.Errorf("new request: %w", err)
}

setHeaders(req, tok)

t := time.Now()
resp, err := retryableClient.Do(req)
latency := time.Since(t).Milliseconds()
if err != nil {
Logger.Errorf("error requesting cert: %v", err)
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("invalid status code '%s' when requesting a cert from Fulcio with body '%s'", resp.Status, string(body))
}

responseBody, err := io.ReadAll(resp.Body)
if err != nil {
Logger.Errorf("error reading response from Fulcio: %v", err)
return nil, err
}
certBlock, chainPEM := pem.Decode(responseBody)
if certBlock == nil || chainPEM == nil {
Logger.Errorf("did not find expected certificates")
}
intermediateBlock, rootPEM := pem.Decode(chainPEM)
if intermediateBlock == nil || rootPEM == nil {
Logger.Errorf("did not find expected certificate chain in response from Fulcio")
}
certPEM := pem.EncodeToMemory(certBlock)
cert, err := cryptoutils.UnmarshalCertificatesFromPEM(certPEM)
if err != nil {
Logger.Errorf("error unmarshalling leaf certificate from Fulcio: %v", err)
return nil, err
}
if len(cert) != 1 {
Logger.Errorf("unexpected number of certificates after unmarshalling got %d, expected 1", len(cert))
return nil, err
}

// Export data to prometheus
exportDataToPrometheus(resp, fulcioURL, endpoint, POST, latency)
return cert[0], nil
}

// fulcioWriteEndpoint tests the /api/v2/signingCert write endpoint for Fulcio.
func fulcioWriteEndpoint(ctx context.Context, priv *ecdsa.PrivateKey) (*x509.Certificate, error) {
if !all.Enabled(ctx) {
return nil, fmt.Errorf("no auth provider for fulcio is enabled")
Expand Down Expand Up @@ -290,10 +359,43 @@ func certificateRequest(_ context.Context, idToken string, priv *ecdsa.PrivateKe
return json.Marshal(req)
}

func legacyCertificateRequest(_ context.Context, idToken string, priv *ecdsa.PrivateKey) ([]byte, error) {
pubBytesPEM, err := cryptoutils.MarshalPublicKeyToPEM(priv.Public())
if err != nil {
return nil, err
}

tok, err := oauthflow.OIDConnect(defaultOIDCIssuer, defaultOIDCClientID, "", "", &oauthflow.StaticTokenGetter{RawToken: idToken})
if err != nil {
return nil, err
}

// Sign the email address as part of the request
h := sha256.Sum256([]byte(tok.Subject))
proof, err := ecdsa.SignASN1(rand.Reader, priv, h[:])
if err != nil {
return nil, err
}

req := SigningCertificateRequestLegacy{
PublicKey: PublicKeyLegacy{
Content: pubBytesPEM,
},
SignedEmailAddress: proof,
}

return json.Marshal(req)
}

type SigningCertificateRequest struct {
PublicKeyRequest PublicKeyRequest `json:"publicKeyRequest"`
}

type SigningCertificateRequestLegacy struct {
PublicKey PublicKeyLegacy `json:"publicKey"`
SignedEmailAddress []byte `json:"signedEmailAddress"`
}

type SigningCertificateResponse struct {
CertificatesWithSct SignedCertificateEmbeddedSct `json:"signedCertificateEmbeddedSct"`
}
Expand All @@ -314,3 +416,7 @@ type PublicKeyRequest struct {
type PublicKey struct {
Content string `json:"content"`
}

type PublicKeyLegacy struct {
Content []byte `json:"content"`
}

0 comments on commit ed0c480

Please sign in to comment.