From 0c6261530e994240d650cabee17f03c5aa616c8f Mon Sep 17 00:00:00 2001 From: YZ775 Date: Thu, 28 Sep 2023 00:32:47 +0000 Subject: [PATCH] enbable TLS Signed-off-by: YZ775 --- Makefile | 8 ++++++ pkg/sabakan-cryptsetup/cmd/driver.go | 12 ++++++++ pkg/sabakan-cryptsetup/cmd/root.go | 10 +++++++ pkg/sabakan/config.go | 3 ++ pkg/sabakan/main.go | 17 +++++++++++- web/machines_test.go | 2 +- web/main_test.go | 2 +- web/server.go | 41 ++++++++++++++++++++++++++-- 8 files changed, 90 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 0c874ed4..e618323e 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,8 @@ ETCD_VERSION = 3.5.7 GO_FILES=$(shell find -name '*.go' -not -name '*_test.go') BUILT_TARGET=sabakan sabactl sabakan-cryptsetup +IMAGE ?= quay.io/cybozu/sabakan +TAG ?= latest .PHONY: all all: build @@ -61,3 +63,9 @@ etcd: $(SUDO) mv /tmp/etcd/etcd /usr/local/bin/; \ rm -rf /tmp/etcd-v${ETCD_VERSION}-linux-amd64.tar.gz /tmp/etcd; \ fi + +.PHONY: docker-build +docker-build:build + cp sabactl sabakan sabakan-cryptsetup LICENSE ./docker/ + docker build -t $(IMAGE):$(TAG) ./docker + rm ./docker/sabactl ./docker/sabakan ./docker/sabakan-cryptsetup ./docker/LICENSE diff --git a/pkg/sabakan-cryptsetup/cmd/driver.go b/pkg/sabakan-cryptsetup/cmd/driver.go index 82f580b2..790a625f 100644 --- a/pkg/sabakan-cryptsetup/cmd/driver.go +++ b/pkg/sabakan-cryptsetup/cmd/driver.go @@ -3,6 +3,8 @@ package cmd import ( "context" "crypto/rand" + "crypto/tls" + "crypto/x509" "errors" "net" "net/http" @@ -35,6 +37,13 @@ type Driver struct { // It may return nil when the serial code of the machine cannot be identified, // or sabakanURL is not valid. func NewDriver(sabakanURL, cipher string, keySize int, tpmdev string, disks []Disk) (*Driver, error) { + crt, err := os.ReadFile("/etc/sabakan/neco.crt") + if err != nil { + return nil, err + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(crt) + hc := &http.Client{ Transport: &http.Transport{ DialContext: (&net.Dialer{ @@ -45,6 +54,9 @@ func NewDriver(sabakanURL, cipher string, keySize int, tpmdev string, disks []Di IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, + TLSClientConfig: &tls.Config{ + RootCAs: caCertPool, + }, }, } saba, err := sabakan.NewClient(sabakanURL, hc) diff --git a/pkg/sabakan-cryptsetup/cmd/root.go b/pkg/sabakan-cryptsetup/cmd/root.go index ca52fc50..f15e2ab2 100644 --- a/pkg/sabakan-cryptsetup/cmd/root.go +++ b/pkg/sabakan-cryptsetup/cmd/root.go @@ -3,6 +3,8 @@ package cmd import ( "errors" "fmt" + "log" + "net/url" "os" "github.com/cybozu-go/well" @@ -71,6 +73,14 @@ func init() { if sabaURL == "" { sabaURL = defaultSabakanURL } + u, err := url.Parse(sabaURL) + if err != nil { + log.Fatal(err) + } + u.Scheme = "https" + u.Host = u.Hostname() + ":10443" + sabaURL = u.String() + rootCmd.Flags().StringVar(&opts.sabakanURL, "server", sabaURL, "URL of sabakan server") rootCmd.Flags().StringVar(&opts.tpmdev, "tpmdev", defaultTPMDev, "device file path of tpm") rootCmd.Flags().StringVar(&opts.cipher, "cipher", defaultCipher, "cipher specification") diff --git a/pkg/sabakan/config.go b/pkg/sabakan/config.go index 6864fe1f..c2b02a15 100644 --- a/pkg/sabakan/config.go +++ b/pkg/sabakan/config.go @@ -4,6 +4,7 @@ import "github.com/cybozu-go/etcdutil" const ( defaultListenHTTP = "0.0.0.0:10080" + defaultListenHTTPS = "0.0.0.0:10443" defaultListenMetrics = "0.0.0.0:10081" defaultEtcdPrefix = "/sabakan/" defaultDHCPBind = "0.0.0.0:10067" @@ -18,6 +19,7 @@ var ( func newConfig() *config { return &config{ ListenHTTP: defaultListenHTTP, + ListenHTTPS: defaultListenHTTPS, ListenMetrics: defaultListenMetrics, DHCPBind: defaultDHCPBind, IPXEPath: defaultIPXEPath, @@ -29,6 +31,7 @@ func newConfig() *config { type config struct { ListenHTTP string `json:"http"` + ListenHTTPS string `json:"https"` ListenMetrics string `json:"metrics"` DHCPBind string `json:"dhcp-bind"` IPXEPath string `json:"ipxe-efi-path"` diff --git a/pkg/sabakan/main.go b/pkg/sabakan/main.go index c0ca694c..d29f4f4a 100644 --- a/pkg/sabakan/main.go +++ b/pkg/sabakan/main.go @@ -30,6 +30,7 @@ const ( var ( flagHTTP = flag.String("http", defaultListenHTTP, ":") + flagHTTPS = flag.String("https", defaultListenHTTPS, ":") flagMetrics = flag.String("metrics", defaultListenMetrics, ":") flagDHCPBind = flag.String("dhcp-bind", defaultDHCPBind, "bound ip addresses and port for dhcp server") flagIPXEPath = flag.String("ipxe-efi-path", defaultIPXEPath, "path to ipxe.efi") @@ -87,6 +88,7 @@ func subMain(ctx context.Context) error { cfg.DataDir = *flagDataDir cfg.IPXEPath = *flagIPXEPath cfg.ListenHTTP = *flagHTTP + cfg.ListenHTTPS = *flagHTTPS cfg.Playground = *flagPlayground cfg.ListenMetrics = *flagMetrics @@ -167,7 +169,7 @@ func subMain(ctx context.Context) error { return err } counter := metrics.NewCounter() - webServer := web.NewServer(model, cfg.IPXEPath, cryptsetupPath, advertiseURL, allowedIPs, cfg.Playground, counter) + webServer := web.NewServer(model, cfg.IPXEPath, cryptsetupPath, advertiseURL, allowedIPs, cfg.Playground, counter, false) s := &well.HTTPServer{ Server: &http.Server{ Addr: cfg.ListenHTTP, @@ -178,6 +180,19 @@ func subMain(ctx context.Context) error { } s.ListenAndServe() + //sabakan TLS + counter = metrics.NewCounter() + webServerHTTPS := web.NewServer(model, cfg.IPXEPath, cryptsetupPath, advertiseURL, allowedIPs, cfg.Playground, counter, true) + ss := &well.HTTPServer{ + Server: &http.Server{ + Addr: cfg.ListenHTTPS, + Handler: webServerHTTPS, + }, + ShutdownTimeout: 3 * time.Minute, + Env: env, + } + ss.ListenAndServeTLS("/etc/sabakan/sabakan-tls.crt", "/etc/sabakan/sabakan-tls.key") + // Metrics collector := metrics.NewCollector(&model) metricsHandler := metrics.GetHandler(collector) diff --git a/web/machines_test.go b/web/machines_test.go index 1c1d518f..a0ea7b74 100644 --- a/web/machines_test.go +++ b/web/machines_test.go @@ -425,7 +425,7 @@ func testMachinesDelete(t *testing.T) { func testMachinesGraphQL(t *testing.T) { m := mock.NewModel() - handler := NewServer(m, "", "", nil, nil, false, nil) + handler := NewServer(m, "", "", nil, nil, false, nil, false) m.Machine.Register(context.Background(), []*sabakan.Machine{ sabakan.NewMachine(sabakan.MachineSpec{ diff --git a/web/main_test.go b/web/main_test.go index 4ac44d1f..721f51b2 100644 --- a/web/main_test.go +++ b/web/main_test.go @@ -16,7 +16,7 @@ func newTestServer(m sabakan.Model) *Server { // https://golang.org/src/net/http/httptest/httptest.go?s=1162:1230#L31 _, ipnet, _ := net.ParseCIDR("192.0.2.1/24") u, _ := url.Parse(testMyURL) - return NewServer(m, "", "", u, []*net.IPNet{ipnet}, false, nil) + return NewServer(m, "", "", u, []*net.IPNet{ipnet}, false, nil, false) } func testWithIPAM(t *testing.T, m sabakan.Model) *sabakan.IPAMConfig { diff --git a/web/server.go b/web/server.go index 3cac5085..56adeca6 100644 --- a/web/server.go +++ b/web/server.go @@ -52,11 +52,13 @@ type Server struct { graphQL http.Handler playground http.HandlerFunc + + TLSEnabled bool } // NewServer constructs Server instance func NewServer(model sabakan.Model, ipxePath, cryptsetupPath string, - advertiseURL *url.URL, allowedIPs []*net.IPNet, enablePlayground bool, counter *metrics.APICounter) *Server { + advertiseURL *url.URL, allowedIPs []*net.IPNet, enablePlayground bool, counter *metrics.APICounter, tlsEnabled bool) *Server { graphQL := handler.NewDefaultServer(generated.NewExecutableSchema( generated.Config{ Resolvers: &graph.Resolver{Model: model}, @@ -70,6 +72,7 @@ func NewServer(model sabakan.Model, ipxePath, cryptsetupPath string, AllowedRemotes: allowedIPs, Counter: counter, graphQL: graphQL, + TLSEnabled: tlsEnabled, } if enablePlayground { @@ -81,7 +84,11 @@ func NewServer(model sabakan.Model, ipxePath, cryptsetupPath string, // ServeHTTP implements http.Handler.ServeHTTP func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { w2 := &recorderWriter{ResponseWriter: w} - s.serveHTTP(w2, r) + if s.TLSEnabled { + s.serveHTTPS(w2, r) + } else { + s.serveHTTP(w2, r) + } if s.Counter != nil { s.Counter.Inc(w2.statusCode, r.URL.Path, r.Method) } @@ -116,6 +123,18 @@ func (s Server) serveHTTP(w http.ResponseWriter, r *http.Request) { renderError(r.Context(), w, APIErrNotFound) } +func (s Server) serveHTTPS(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/api/v1/") { + s.handleAPIV1HTTPS(w, r) + return + } + if r.URL.Path == "/test" { + w.Write([]byte("response from sabakan TLS\n")) + return + } + renderError(r.Context(), w, APIErrNotFound) +} + func auditContext(r *http.Request) context.Context { ctx := r.Context() @@ -186,6 +205,24 @@ func (s Server) handleAPIV1(w http.ResponseWriter, r *http.Request) { } } +func (s Server) handleAPIV1HTTPS(w http.ResponseWriter, r *http.Request) { + p := r.URL.Path[len("/api/v1/"):] + + if !s.hasPermission(r) { + renderError(r.Context(), w, APIErrForbidden) + return + } + + r = r.WithContext(auditContext(r)) + + switch { + case strings.HasPrefix(p, "crypts/"): + s.handleCrypts(w, r) + default: + renderError(r.Context(), w, APIErrNotFound) + } +} + // hasPermission returns true if the request has a permission to the resource func (s Server) hasPermission(r *http.Request) bool { p := r.URL.Path[len("/api/v1/"):]