Skip to content

Commit

Permalink
cmd/sunlight: add a nice home page
Browse files Browse the repository at this point in the history
Fixes #9
  • Loading branch information
FiloSottile committed Aug 7, 2024
1 parent 00b5971 commit 96737ce
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 0 deletions.
121 changes: 121 additions & 0 deletions cmd/sunlight/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Sunlight</title>

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bitter&family=Raleway&family=Source+Code+Pro&display=swap" rel="stylesheet">

<style>
.container {
width: auto;
max-width: 700px;
padding: 0 15px;
margin: 80px auto;
}

body {
font-family: "Raleway", sans-serif;
line-height: 1.4;
}

h1, h2, h3 {
font-family: "Bitter", serif;
}

code {
font-family: "Source Code Pro", monospace;
-webkit-font-smoothing: antialiased;
}

.response {
white-space: wrap;
word-break: break-all;
}
</style>
</head>

<body>
<div class="container">
<p align="center">
<img alt="The Sunlight logo, a bench under a tree in stylized black ink, cast against a large yellow sun, with the text Sunlight underneath" width="250" height="278" src="https://sunlight.dev/images/sunlight_logo_main.png">
</p>

<p>
This is a <a href="https://sunlight.dev">Sunlight</a> Certificate Transparency log instance.

<p>
The following logs are active.

{{ range . }}

<h2>{{ .Name }}</h2>

<p>
Log ID: <code>{{ .ID }}</code><br>
NotAfter start: <strong>{{ .NotAfterStart }}</strong><br>
NotAfter limit: <strong>{{ .NotAfterLimit }}</strong><br>
Roots: <a href="{{ .HTTPPrefix }}/ct/v1/get-roots">get-roots</a><br>
Ratelimit: {{ .PoolSize }} req/s

<pre>{{ .PublicKey }}</pre>

<h3>Submit a certificate chain (PEM or JSON)</h3>

<input type="file" class="chain" data-prefix="{{ .HTTPPrefix }}">
<code><pre class="response"></pre></code>

{{ end }}

<script>
for (const fileInput of document.querySelectorAll('.chain')) {
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;

const reader = new FileReader();
reader.onload = async (e) => {
const responseDiv = event.target.nextElementSibling.querySelector('.response');
responseDiv.textContent = "...";

var contents = e.target.result;

if (!contents.startsWith('{')) {
const chain = [];
for (const line of contents.split('\n')) {
const trimmedLine = line.trim();
if (trimmedLine === '')
continue;
else if (trimmedLine === '-----BEGIN CERTIFICATE-----')
chain.push("");
else if (trimmedLine === '-----END CERTIFICATE-----')
continue;
else
chain[chain.length - 1] += trimmedLine;
}
contents = JSON.stringify({ "chain": chain });
}

const url = event.target.dataset.prefix + '/ct/v1/add-chain';
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: contents
});

const responseText = await response.text();
if (responseText === '' && !response.ok)
responseText = `HTTP ${response.status} ${response.statusText}`;
responseDiv.textContent = responseText;
};
reader.readAsText(file);
});
}
</script>
47 changes: 47 additions & 0 deletions cmd/sunlight/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
_ "embed"
"encoding/base64"
"encoding/pem"
"flag"
"io"
"log/slog"
Expand All @@ -31,6 +33,7 @@ import (
"os"
"os/signal"
"strings"
"text/template"
"time"

"filippo.io/keygen"
Expand Down Expand Up @@ -195,6 +198,26 @@ type LogConfig struct {
NotAfterLimit string
}

type homepageLog struct {
// Fields from LogConfig, we don't embed the whole struct to avoid
// accidentally exposing sensitive fields.
Name string
NotAfterStart string
NotAfterLimit string
HTTPPrefix string
PoolSize int

// ID is the base64 encoded SHA-256 of the public key.
ID string

// PublicKey is the PEM encoded SubjectPublicKeyInfo.
PublicKey string
}

//go:embed home.html
var homeHTML string
var homeTmpl = template.Must(template.New("home").Parse(homeHTML))

func main() {
fs := flag.NewFlagSet("sunlight", flag.ExitOnError)
configFlag := fs.String("c", "sunlight.yaml", "path to the config file")
Expand Down Expand Up @@ -295,6 +318,7 @@ func main() {

sequencerGroup, sequencerContext := errgroup.WithContext(ctx)

var logList []homepageLog
for _, lc := range c.Logs {
if lc.Name == "" || lc.ShortName == "" {
fatalError(logger, "missing name or short name for log")
Expand Down Expand Up @@ -401,8 +425,31 @@ func main() {

prometheus.WrapRegistererWith(prometheus.Labels{"log": lc.ShortName}, sunlightMetrics).
MustRegister(l.Metrics()...)

pkix, err := x509.MarshalPKIXPublicKey(&k.PublicKey)
if err != nil {
fatalError(logger, "failed to marshal public key for display", "err", err)
}
pemKey := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pkix})
logID := sha256.Sum256(pkix)
logList = append(logList, homepageLog{
Name: lc.Name,
ID: base64.StdEncoding.EncodeToString(logID[:]),
NotAfterStart: lc.NotAfterStart,
NotAfterLimit: lc.NotAfterLimit,
HTTPPrefix: lc.HTTPPrefix,
PoolSize: lc.PoolSize,
PublicKey: string(pemKey),
})
}

mux.HandleFunc("/{$}", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
if err := homeTmpl.Execute(w, logList); err != nil {
logger.Error("failed to execute homepage template", "err", err)
}
})

s := &http.Server{
Addr: c.Listen,
Handler: mux,
Expand Down

0 comments on commit 96737ce

Please sign in to comment.