Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions cmd/skylight/skylight.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ type Config struct {
// HomeRedirect is the 302 destination of the root. Optional.
HomeRedirect string

// OperatorName is the human-readable name of the CT log operator,
// served at /logs.json per the operator-list-v1 schema. Optional.
OperatorName string

Logs []LogConfig
}

Expand Down Expand Up @@ -321,13 +325,19 @@ func main() {
handler = promhttp.InstrumentHandlerResponseSize(resSize.MustCurryWith(labels), handler,
promhttp.WithLabelFromCtx("kind", kindFromContext))

// Then, apply the rate limit handler.
handler = newRateLimitHandler(handler)
// Then, apply the rate limit handler. Keep an unrestricted handler for
// small browser-friendly endpoints like checkpoint and JSON metadata.
rateLimitedHandler := newRateLimitHandler(handler)
unlimitedHandler := handler

// Next, the request counter. It needs to go before the mux as it uses
// the context keys we set in the per-path handlers, but after the rate
// limit handler, so it will capture the 429 errors.
handler = promhttp.InstrumentHandlerCounter(reqCount.MustCurryWith(labels), handler,
unlimitedHandler = promhttp.InstrumentHandlerCounter(reqCount.MustCurryWith(labels), unlimitedHandler,
promhttp.WithLabelFromCtx("kind", kindFromContext),
promhttp.WithLabelFromCtx("reused", reused.LabelFromContext),
promhttp.WithLabelFromCtx("client", clientFromContext))
rateLimitedHandler = promhttp.InstrumentHandlerCounter(reqCount.MustCurryWith(labels), rateLimitedHandler,
promhttp.WithLabelFromCtx("kind", kindFromContext),
promhttp.WithLabelFromCtx("reused", reused.LabelFromContext),
promhttp.WithLabelFromCtx("client", clientFromContext))
Expand All @@ -351,13 +361,13 @@ func main() {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Cache-Control", "no-store")
r = r.WithContext(context.WithValue(r.Context(), kindContextKey{}, "checkpoint"))
handler.ServeHTTP(w, r)
unlimitedHandler.ServeHTTP(w, r)
})
mux.HandleFunc(patternPrefix+"/log.v3.json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
r = r.WithContext(context.WithValue(r.Context(), kindContextKey{}, "log.v3.json"))
handler.ServeHTTP(w, r)
unlimitedHandler.ServeHTTP(w, r)
})
mux.HandleFunc(patternPrefix+"/issuer/{issuer}", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
Expand All @@ -368,7 +378,7 @@ func main() {
return
}
r = r.WithContext(context.WithValue(r.Context(), kindContextKey{}, "issuer"))
handler.ServeHTTP(w, r)
rateLimitedHandler.ServeHTTP(w, r)
})
mux.HandleFunc(patternPrefix+"/tile/{tile...}", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
Expand All @@ -395,7 +405,7 @@ func main() {
default:
r = r.WithContext(context.WithValue(r.Context(), kindContextKey{}, "tile"))
}
handler.ServeHTTP(w, r)
rateLimitedHandler.ServeHTTP(w, r)
})
}

Expand All @@ -409,10 +419,16 @@ func main() {

var logs []string
for log := range roots {
logs = append(logs, log.MonitoringPrefix)
logs = append(logs, log.MonitoringPrefix+"/log.v3.json")
}
slices.Sort(logs)
json.NewEncoder(w).Encode(logs)
json.NewEncoder(w).Encode(struct {
OperatorName string `json:"operator_name,omitempty"`
Logs []string `json:"logs"`
}{
OperatorName: c.OperatorName,
Logs: logs,
})
})

if c.HomeRedirect != "" {
Expand Down
37 changes: 34 additions & 3 deletions cmd/sunlight/sunlight.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,19 +331,36 @@ type logInfo struct {
MMD int `json:"mmd"`

// Fields from the "Operator-published CT Log Metadata" proposal.
TLSOnly bool `json:"tls_only"` // always true
IntendedUse string `json:"intended_use,omitzero"` // "production" or "test"
FinalTree struct { // only included for sunsetted logs
LogSpec string `json:"log_spec"` // always "static-ct-api"
MMDSeconds int `json:"mmd_seconds"` // always 60, same as MMD
TLSOnly bool `json:"tls_only"` // always true
IntendedUse string `json:"intended_use,omitzero"` // "production" or "test"
Status string `json:"status"` // "active" or "readonly" or "inactive"
StatusTimestamp string `json:"status_timestamp"`
PlannedChanges []PlannedChange `json:"planned_changes,omitempty"`
FinalTree struct { // only included for non-active logs
RootHash []byte `json:"sha256_root_hash"`
Size int64 `json:"tree_size"`
Timestamp int64 `json:"timestamp"`
} `json:"final_tree_head,omitzero"`
SubmissionEndpoint struct {
URL string `json:"url"`
} `json:"submission_endpoint"`
MonitoringEndpoint struct {
URL string `json:"url"`
} `json:"monitoring_endpoint"`
Software struct {
Name string `json:"name"`
Version string `json:"version"`
} `json:"log_software"`
}

type PlannedChange struct {
NewStatus string `json:"new_status"`
EffectiveDate string `json:"effective_date"`
Comment string `json:"comment,omitempty"`
}

//go:embed home.html
var homeHTML string
var homeTmpl = template.Must(template.New("home").Parse(homeHTML))
Expand Down Expand Up @@ -898,10 +915,14 @@ func updateMetadata(ctx context.Context, setLogInfo func(string, logInfo), lc Lo
PublicKeyDER: pkix,
PublicKeyBase64: base64.StdEncoding.EncodeToString(pkix),
MMD: 60,
MMDSeconds: 60,
TLSOnly: true,
LogSpec: "static-ct-api",
}
log.Interval.NotAfterStart = lc.NotAfterStart
log.Interval.NotAfterLimit = lc.NotAfterLimit
log.SubmissionEndpoint.URL = log.SubmissionPrefix
log.MonitoringEndpoint.URL = log.MonitoringPrefix
switch {
case lc.Roots != "":
// No IntendedUse for custom roots.
Expand All @@ -910,10 +931,20 @@ func updateMetadata(ctx context.Context, setLogInfo func(string, logInfo), lc Lo
case lc.CCADBRoots == "testing":
log.IntendedUse = "test"
}
readOnlyAt := cc.NotAfterLimit.Add(ctlog.ReadOnlyAfter)
if e != nil {
log.Status = "readonly"
log.StatusTimestamp = readOnlyAt.Format(time.RFC3339)
log.FinalTree.RootHash = e.FinalTree.Hash[:]
log.FinalTree.Size = e.FinalTree.N
log.FinalTree.Timestamp = e.FinalTimestamp
} else {
log.Status = "active"
log.StatusTimestamp = lc.Inception + "T00:00:00Z"
log.PlannedChanges = []PlannedChange{{
NewStatus: "readonly",
EffectiveDate: readOnlyAt.Format(time.RFC3339),
}}
}
info, _ := debug.ReadBuildInfo()
log.Software.Name = info.Main.Path
Expand Down
7 changes: 5 additions & 2 deletions internal/ctlog/ctlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,14 @@ func (l *Log) SetRootsFromPEM(ctx context.Context, pemBytes []byte) error {
return nil
}

// ReadOnlyAfter is how long after the end of the shard window
// the log becomes read-only: one week.
const ReadOnlyAfter = 7 * 24 * time.Hour

// AcceptingSubmissions returns whether the log is accepting submissions. It can
// only go from true to false, when the log becomes read-only, not back.
func (l *Log) AcceptingSubmissions() bool {
// Turn read-only one week after the end of the shard window.
return time.Since(l.c.NotAfterLimit) < 7*24*time.Hour
return time.Since(l.c.NotAfterLimit) < ReadOnlyAfter
}

// Backend is an object storage. It is dedicated to a single log instance.
Expand Down
Loading