Ctrl K
ComunidadGuías…

internal/rest/tls.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package rest

import (
	"context"
	"errors"
	"net/http"
	"time"

	"github.com/danielgtaylor/huma/v2"

	"github.com/sarathsp06/sparrow/internal/tlsrevocation"
	"github.com/sarathsp06/sparrow/pkg/safetransport"
)

// TLSRevocation is the server's CRL checker as the TLS endpoints see it
// (*tlsrevocation.Refresher; nil when SPARROW_TLS_REVOCATION=off).
type TLSRevocation interface {
	Mode() string
	Status() []safetransport.CRLStatus
	RequestRefresh(ctx context.Context) error
}

type crlOut struct {
	URL        string     `json:"url" doc:"CRL distribution point, from an intermediate in a receiver's chain."`
	Issuer     string     `json:"issuer" doc:"Common name of the CA that signs this CRL."`
	FetchedAt  *time.Time `json:"fetched_at,omitempty" doc:"When this instance last downloaded it successfully."`
	NextUpdate *time.Time `json:"next_update,omitempty" doc:"When the CA promises a newer CRL. Past it, the CRL is downloaded again before use."`
	Entries    int        `json:"entries" doc:"Revoked certificates it lists."`
	Error      string     `json:"error,omitempty" doc:"Why the last download or check failed. In soft mode such a CRL gives no answer and connections are allowed."`
}

type listCrlsOutput struct {
	Body struct {
		Mode string   `json:"mode" enum:"soft,hard,off" doc:"SPARROW_TLS_REVOCATION: soft allows a connection whose CRL is unavailable, hard refuses it, off skips the check."`
		CRLs []crlOut `json:"crls" doc:"CRLs this instance has cached, by URL. Each instance keeps its own cache."`
	}
}

type refreshCrlsOutput struct{}

// registerTLSRoutes registers the CRL status and refresh endpoints. rev may
// be nil (revocation off).
func registerTLSRoutes(api huma.API, rev TLSRevocation) {
	if rev == nil {
		rev = (*tlsrevocation.Refresher)(nil)
	}
	huma.Register(api, huma.Operation{
		OperationID: "listCrls",
		Method:      http.MethodGet,
		Path:        "/v1/tls/crls",
		Summary:     "List cached CRLs",
		Description: "Shows the CRLs the instance that answers has cached for checking intermediates in receivers' TLS chains, with when each was fetched and any error. " +
			"CRLs are downloaded the first time a chain needs them, refreshed in the background by the next handshake once older than SPARROW_CRL_MAX_AGE, and on demand with refreshCrls. " +
			"Each instance keeps its own cache, so behind a load balancer this shows one instance.",
		Tags: []string{"Server"},
	}, func(_ context.Context, _ *struct{}) (*listCrlsOutput, error) {
		out := &listCrlsOutput{}
		out.Body.Mode = rev.Mode()
		out.Body.CRLs = []crlOut{}
		for _, s := range rev.Status() {
			c := crlOut{URL: s.URL, Issuer: s.Issuer, Entries: s.Entries, Error: s.Error}
			if !s.FetchedAt.IsZero() {
				c.FetchedAt = &s.FetchedAt
			}
			if !s.NextUpdate.IsZero() {
				c.NextUpdate = &s.NextUpdate
			}
			out.Body.CRLs = append(out.Body.CRLs, c)
		}
		return out, nil
	})

	huma.Register(api, huma.Operation{
		OperationID:   "refreshCrls",
		Method:        http.MethodPost,
		Path:          "/v1/tls/crls:refresh",
		Summary:       "Refresh cached CRLs on every instance",
		Description:   "Asks every instance (through Postgres NOTIFY) to download its cached CRLs now, for when a CA revokes an intermediate and deliveries should stop trusting it before SPARROW_CRL_MAX_AGE passes. Returns 202 once the request is sent; check listCrls for the result. One request a minute on each server: more answer 429. 409 when SPARROW_TLS_REVOCATION=off.",
		DefaultStatus: http.StatusAccepted,
		Errors:        []int{409, 429},
		Tags:          []string{"Server"},
	}, func(ctx context.Context, _ *struct{}) (*refreshCrlsOutput, error) {
		err := rev.RequestRefresh(ctx)
		switch {
		case errors.Is(err, tlsrevocation.ErrDisabled):
			return nil, huma.Error409Conflict(err.Error())
		case errors.Is(err, tlsrevocation.ErrTooSoon):
			return nil, huma.Error429TooManyRequests(err.Error())
		case err != nil:
			return nil, mapError(ctx, err, "failed to request a CRL refresh")
		}
		return &refreshCrlsOutput{}, nil
	})
}