Skip to content

Commit

Permalink
story(http): support tls and http2 (#42)
Browse files Browse the repository at this point in the history
* feat(issue-34): add tls config option

* feat(issue-34): add http2 only support

* fix(issue-34): support only allow http2 requests if config is set

* example(issue-34): add tls and http2 examples

* feat(issue-34): add validator for min http proto

* refactor(issue-34): simplify by using httpvalidator for min proto
  • Loading branch information
Zaba505 authored Dec 18, 2023
1 parent b6307a8 commit 551e4b1
Show file tree
Hide file tree
Showing 9 changed files with 407 additions and 3 deletions.
64 changes: 64 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ before:
- go mod tidy -v

builds:
- id: http2
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
goamd64:
- v3
main: example/http2/main.go
binary: http2

- id: otlp
env:
- CGO_ENABLED=0
Expand Down Expand Up @@ -58,7 +71,39 @@ builds:
main: example/simple_queue/main.go
binary: simple_queue

- id: tls_http
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
goamd64:
- v3
main: example/tls_http/main.go
binary: tls_http

dockers:
- id: http2
goos: linux
goarch: amd64
goamd64: v3
ids:
- http2
image_templates:
- "ghcr.io/z5labs/bedrock/example/http2:latest"
- "ghcr.io/z5labs/bedrock/example/http2:{{ .Tag }}"
dockerfile: example/http2/Containerfile
use: docker
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--platform=linux/amd64"

- id: otlp
goos: linux
goarch: amd64
Expand Down Expand Up @@ -134,3 +179,22 @@ dockers:
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--platform=linux/amd64"

- id: tls_http
goos: linux
goarch: amd64
goamd64: v3
ids:
- tls_http
image_templates:
- "ghcr.io/z5labs/bedrock/example/tls_http:latest"
- "ghcr.io/z5labs/bedrock/example/tls_http:{{ .Tag }}"
dockerfile: example/tls_http/Containerfile
use: docker
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--platform=linux/amd64"
9 changes: 9 additions & 0 deletions example/http2/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2023 Z5Labs and Contributors
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT

FROM scratch
EXPOSE 8080
COPY http2 /
ENTRYPOINT ["/http2"]
93 changes: 93 additions & 0 deletions example/http2/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) 2023 Z5Labs and Contributors
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

package main

import (
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"log/slog"
"math/big"
"net/http"
"os"
"time"

"github.com/z5labs/bedrock"
brhttp "github.com/z5labs/bedrock/http"
"github.com/z5labs/bedrock/pkg/otelconfig"
)

func createCert() (tls.Certificate, error) {
_, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return tls.Certificate{}, err
}
// ECDSA, ED25519 and RSA subject keys should have the DigitalSignature
// KeyUsage bits set in the x509.Certificate template
notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour)

template := x509.Certificate{
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Organization: []string{"Acme Co"},
},
NotBefore: notBefore,
NotAfter: notAfter,
SubjectKeyId: []byte{113, 117, 105, 99, 107, 115, 101, 114, 118, 101},
BasicConstraintsValid: true,
IsCA: true,
KeyUsage: x509.KeyUsageKeyEncipherment |
x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, priv.Public().(ed25519.PublicKey), priv)
if err != nil {
return tls.Certificate{}, nil
}

var cert tls.Certificate
cert.Certificate = append(cert.Certificate, derBytes)
cert.PrivateKey = priv
return cert, nil
}

func initRuntime(bc bedrock.BuildContext) (bedrock.Runtime, error) {
logHandler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{AddSource: true})

cert, err := createCert()
if err != nil {
return nil, err
}

rt := brhttp.NewRuntime(
brhttp.ListenOnPort(8080),
brhttp.LogHandler(logHandler),
brhttp.TLSConfig(&tls.Config{
Certificates: []tls.Certificate{cert},
}),
brhttp.Http2Only(),
brhttp.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "hello, world")
}),
)
return rt, nil
}

func main() {
bedrock.New(
bedrock.InitTracerProvider(func(bc bedrock.BuildContext) (otelconfig.Initializer, error) {
return otelconfig.Local(
otelconfig.ServiceName("http2"),
), nil
}),
bedrock.WithRuntimeBuilderFunc(initRuntime),
).Run()
}
9 changes: 9 additions & 0 deletions example/tls_http/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2023 Z5Labs and Contributors
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT

FROM scratch
EXPOSE 8080
COPY tls_http /
ENTRYPOINT ["/tls_http"]
92 changes: 92 additions & 0 deletions example/tls_http/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) 2023 Z5Labs and Contributors
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

package main

import (
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"log/slog"
"math/big"
"net/http"
"os"
"time"

"github.com/z5labs/bedrock"
brhttp "github.com/z5labs/bedrock/http"
"github.com/z5labs/bedrock/pkg/otelconfig"
)

func createCert() (tls.Certificate, error) {
_, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return tls.Certificate{}, err
}
// ECDSA, ED25519 and RSA subject keys should have the DigitalSignature
// KeyUsage bits set in the x509.Certificate template
notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour)

template := x509.Certificate{
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Organization: []string{"Acme Co"},
},
NotBefore: notBefore,
NotAfter: notAfter,
SubjectKeyId: []byte{113, 117, 105, 99, 107, 115, 101, 114, 118, 101},
BasicConstraintsValid: true,
IsCA: true,
KeyUsage: x509.KeyUsageKeyEncipherment |
x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, priv.Public().(ed25519.PublicKey), priv)
if err != nil {
return tls.Certificate{}, nil
}

var cert tls.Certificate
cert.Certificate = append(cert.Certificate, derBytes)
cert.PrivateKey = priv
return cert, nil
}

func initRuntime(bc bedrock.BuildContext) (bedrock.Runtime, error) {
logHandler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{AddSource: true})

cert, err := createCert()
if err != nil {
return nil, err
}

rt := brhttp.NewRuntime(
brhttp.ListenOnPort(8080),
brhttp.LogHandler(logHandler),
brhttp.TLSConfig(&tls.Config{
Certificates: []tls.Certificate{cert},
}),
brhttp.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "hello, world")
}),
)
return rt, nil
}

func main() {
bedrock.New(
bedrock.InitTracerProvider(func(bc bedrock.BuildContext) (otelconfig.Initializer, error) {
return otelconfig.Local(
otelconfig.ServiceName("tls_http"),
), nil
}),
bedrock.WithRuntimeBuilderFunc(initRuntime),
).Run()
}
40 changes: 38 additions & 2 deletions http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package http

import (
"context"
"crypto/tls"
"fmt"
"log/slog"
"net"
Expand All @@ -28,6 +29,8 @@ type runtimeOptions struct {
logHandler slog.Handler
readiness *health.Readiness
liveness *health.Liveness
tlsConfig *tls.Config
http2Only bool
}

// RuntimeOption
Expand Down Expand Up @@ -77,14 +80,30 @@ func Liveness(l *health.Liveness) RuntimeOption {
}
}

// TLSConfig
func TLSConfig(cfg *tls.Config) RuntimeOption {
return func(ro *runtimeOptions) {
ro.tlsConfig = cfg
}
}

// Http2Only
func Http2Only() RuntimeOption {
return func(ro *runtimeOptions) {
ro.http2Only = true
}
}

// Runtime
type Runtime struct {
port uint
listen func(string, string) (net.Listener, error)

log *slog.Logger

h http.Handler
tlsConfig *tls.Config
http2Only bool
h http.Handler

started *health.Started
liveness *health.Liveness
Expand All @@ -108,6 +127,8 @@ func NewRuntime(opts ...RuntimeOption) *Runtime {
port: ros.port,
listen: net.Listen,
log: slog.New(ros.logHandler),
tlsConfig: ros.tlsConfig,
http2Only: ros.http2Only,
h: ros.mux,
started: &health.Started{},
liveness: ros.liveness,
Expand Down Expand Up @@ -149,10 +170,25 @@ func (rt *Runtime) Run(ctx context.Context) error {
rt.log.Error("failed to listen for connections", slogfield.Error(err))
return err
}
if rt.tlsConfig != nil {
rt.tlsConfig.NextProtos = append([]string{"h2"}, rt.tlsConfig.NextProtos...)
if rt.http2Only {
rt.tlsConfig.NextProtos = []string{"h2"}
}
ls = tls.NewListener(ls, rt.tlsConfig)
}

handler := rt.h
if rt.http2Only {
handler = httpvalidate.Request(
rt.h,
httpvalidate.MinProto(2, 0),
)
}

s := &http.Server{
Handler: otelhttp.NewHandler(
rt.h,
handler,
"server",
otelhttp.WithMessageEvents(otelhttp.ReadEvents, otelhttp.WriteEvents),
),
Expand Down
Loading

0 comments on commit 551e4b1

Please sign in to comment.