Files
ocsp-server/main.go
2025-01-21 08:33:49 +01:00

132 lines
4.5 KiB
Go

package main
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/prometheus/client_golang/prometheus/promhttp"
"log"
"net/http"
"ocspcrl/internal/metrics"
"os"
"os/signal"
"syscall"
"github.com/alecthomas/kingpin/v2"
cfocsp "github.com/cloudflare/cfssl/ocsp"
"ocspcrl/internal/ocsp_source"
)
func loadCrlFromFile(path string) (*x509.RevocationList, error) {
crlContent, openCrlError := os.ReadFile(path)
if openCrlError != nil {
return nil, openCrlError
}
block, rest := pem.Decode(crlContent)
if len(rest) > 0 {
return nil, fmt.Errorf("failed to decode crl")
}
crl, parseCrlError := x509.ParseRevocationList(block.Bytes)
if parseCrlError != nil {
return nil, parseCrlError
}
return crl, nil
}
type responder struct {
certificatePath string
keyPath string
}
type crlSourceFile struct {
path string
}
type configuration struct {
responder *responder
caCrtPath string
crlSourceType string
crlSourceFile *crlSourceFile
applicationListenAddress string
metricsListenAddress string
}
func main() {
config := &configuration{
responder: &responder{},
crlSourceFile: &crlSourceFile{},
}
app := kingpin.New("ocspcrl", "OCSP responder / CRL server")
app.HelpFlag.Short('h')
app.Flag("responder.certificate-path", "Path to the responder certificate").Envar("RESPONDER_CERTIFICATE_PATH").Required().ExistingFileVar(&config.responder.certificatePath)
app.Flag("responder.key-path", "Path to the responder key").Envar("RESPONDER_KEY_PATH").Required().ExistingFileVar(&config.responder.keyPath)
app.Flag("ca-crt-path", "Path to the CA certificate").Envar("CA_CRL_PATH").Required().ExistingFileVar(&config.caCrtPath)
app.Flag("crl-source-type", "Type of CRL source").Envar("CRL_SOURCE").Default("file").EnumVar(&config.crlSourceType, "file")
app.Flag("source.file.path", "Path to the CRL file").Envar("SOURCE_FILE_PATH").ExistingFileVar(&config.crlSourceFile.path)
app.Flag("web.listen-address", "Address for application endpoint").Envar("WEB_LISTEN_ADDRESS").Default(":8080").StringVar(&config.applicationListenAddress)
app.Flag("metrics.listen-address", "Address for metrics endpoint").Envar("METRICS_LISTEN_ADDRESS").Default("[::1]:8081").StringVar(&config.metricsListenAddress)
kingpin.MustParse(app.Parse(os.Args[1:]))
responderKeyPair, loadResponderKeyPairError := tls.LoadX509KeyPair(config.responder.certificatePath, config.responder.keyPath)
if loadResponderKeyPairError != nil {
log.Fatalf("failed to load responder key pair: %v", loadResponderKeyPairError)
}
caCrtContent, openCaCrtError := os.ReadFile(config.caCrtPath)
if openCaCrtError != nil {
log.Fatalf("failed to open ca certificate: %v", openCaCrtError)
}
block, rest := pem.Decode(caCrtContent)
if len(rest) > 0 {
log.Fatalln("failed to decode ca certificate")
}
caCertificate, loadCaCertificateError := x509.ParseCertificate(block.Bytes)
if loadCaCertificateError != nil {
log.Fatalf("failed to parse ca certificate: %v", loadCaCertificateError)
}
source := ocsp_source.NewCrlSource(caCertificate, responderKeyPair)
crl, loadCrlError := loadCrlFromFile(config.crlSourceFile.path)
if loadCrlError != nil {
log.Fatalf("failed to load crl: %v", loadCrlError)
}
source.UseCrl(crl)
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
applicationRouter := http.NewServeMux()
applicationRouter.Handle("/ocsp", cfocsp.NewResponder(source, nil))
applicationRouter.HandleFunc("/crl", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/pkix-crl")
w.Write(crl.Raw)
})
applicationServer := &http.Server{Addr: config.applicationListenAddress, Handler: metrics.Middleware(applicationRouter)}
metricsSever := &http.Server{Addr: config.metricsListenAddress, Handler: promhttp.Handler()}
applicationServerClosed := make(chan any)
metricsServerClosed := make(chan any)
go func() {
log.Printf("starting application server on %s", config.applicationListenAddress)
if listenError := applicationServer.ListenAndServe(); listenError != nil {
log.Printf("application error: %v", listenError)
}
close(applicationServerClosed)
}()
go func() {
log.Printf("starting metrics server on %s", config.metricsListenAddress)
if listenError := metricsSever.ListenAndServe(); listenError != nil {
log.Printf("metrics error: %v", listenError)
}
close(metricsServerClosed)
}()
<-signalChan
applicationServer.Shutdown(nil)
metricsSever.Shutdown(nil)
<-applicationServerClosed
<-metricsServerClosed
}