From 94335b208325eadb05eece64790f919cbacc5645 Mon Sep 17 00:00:00 2001 From: Jakub Dyszkiewicz Date: Wed, 11 Dec 2019 12:13:15 +0100 Subject: [PATCH] feat(kuma-cp) removed dataplane token server --- app/kumactl/pkg/tokens/client_test.go | 35 +-- pkg/admin-server/server.go | 79 ++++++- pkg/admin-server/server_suite_test.go | 13 ++ pkg/admin-server/server_test.go | 146 ++++++++++++ .../testdata/authorized-client-cert.pem | 0 .../testdata/authorized-client-key.pem | 0 .../authorized-client-cert.pem | 0 .../file-that-should-be-ignored | 0 .../testdata/server-cert.pem | 0 .../testdata/server-key.pem | 0 .../testdata/unauthorized-client-cert.pem | 0 .../testdata/unauthorized-client-key.pem | 0 pkg/api-server/config_ws_test.go | 3 +- pkg/config/app/kuma-cp/kuma-cp.defaults.yaml | 2 - pkg/core/bootstrap/autoconfig.go | 2 +- .../builtin/server/dataplane_token_server.go | 215 ------------------ .../server/dataplane_token_server_test.go | 188 --------------- pkg/tokens/builtin/server/webservice.go | 19 +- pkg/tokens/builtin/server/webservice_test.go | 102 +++++++++ 19 files changed, 370 insertions(+), 434 deletions(-) create mode 100644 pkg/admin-server/server_suite_test.go create mode 100644 pkg/admin-server/server_test.go rename pkg/{tokens/builtin/server => admin-server}/testdata/authorized-client-cert.pem (100%) rename pkg/{tokens/builtin/server => admin-server}/testdata/authorized-client-key.pem (100%) rename pkg/{tokens/builtin/server => admin-server}/testdata/authorized-clients/authorized-client-cert.pem (100%) rename pkg/{tokens/builtin/server => admin-server}/testdata/authorized-clients/file-that-should-be-ignored (100%) rename pkg/{tokens/builtin/server => admin-server}/testdata/server-cert.pem (100%) rename pkg/{tokens/builtin/server => admin-server}/testdata/server-key.pem (100%) rename pkg/{tokens/builtin/server => admin-server}/testdata/unauthorized-client-cert.pem (100%) rename pkg/{tokens/builtin/server => admin-server}/testdata/unauthorized-client-key.pem (100%) delete mode 100644 pkg/tokens/builtin/server/dataplane_token_server.go delete mode 100644 pkg/tokens/builtin/server/dataplane_token_server_test.go create mode 100644 pkg/tokens/builtin/server/webservice_test.go diff --git a/app/kumactl/pkg/tokens/client_test.go b/app/kumactl/pkg/tokens/client_test.go index 4f7e88f6083c..0b1971a94472 100644 --- a/app/kumactl/pkg/tokens/client_test.go +++ b/app/kumactl/pkg/tokens/client_test.go @@ -3,8 +3,9 @@ package tokens_test import ( "fmt" "github.com/Kong/kuma/app/kumactl/pkg/tokens" + admin_server "github.com/Kong/kuma/pkg/admin-server" + admin_server_config "github.com/Kong/kuma/pkg/config/admin-server" config_kumactl "github.com/Kong/kuma/pkg/config/app/kumactl/v1alpha1" - token_server "github.com/Kong/kuma/pkg/config/token-server" "github.com/Kong/kuma/pkg/core/xds" "github.com/Kong/kuma/pkg/sds/auth" "github.com/Kong/kuma/pkg/test" @@ -45,23 +46,23 @@ var _ = Describe("Tokens Client", func() { Expect(err).ToNot(HaveOccurred()) publicPort = p - srv := server.DataplaneTokenServer{ - Config: &token_server.DataplaneTokenServerConfig{ + adminCfg := admin_server_config.AdminServerConfig{ + DataplaneTokenWs: &admin_server_config.DataplaneTokenWsConfig{ Enabled: true, - Local: &token_server.LocalDataplaneTokenServerConfig{ - Port: uint32(port), - }, - Public: &token_server.PublicDataplaneTokenServerConfig{ - Enabled: true, - Port: uint32(publicPort), - Interface: "localhost", - TlsCertFile: filepath.Join("..", "..", "..", "..", "pkg", "tokens", "builtin", "server", "testdata", "server-cert.pem"), - TlsKeyFile: filepath.Join("..", "..", "..", "..", "pkg", "tokens", "builtin", "server", "testdata", "server-key.pem"), - ClientCertsDir: filepath.Join("..", "..", "..", "..", "pkg", "tokens", "builtin", "server", "testdata", "authorized-clients"), - }, }, - Issuer: &staticTokenIssuer{}, + Local: &admin_server_config.LocalAdminServerConfig{ + Port: uint32(port), + }, + Public: &admin_server_config.PublicAdminServerConfig{ + Enabled: true, + Port: uint32(publicPort), + Interface: "localhost", + TlsCertFile: filepath.Join("..", "..", "..", "..", "pkg", "admin-server", "testdata", "server-cert.pem"), + TlsKeyFile: filepath.Join("..", "..", "..", "..", "pkg", "admin-server", "testdata", "server-key.pem"), + ClientCertsDir: filepath.Join("..", "..", "..", "..", "pkg", "admin-server", "testdata", "authorized-clients"), + }, } + srv := admin_server.NewAdminServer(adminCfg, server.NewWebservice(&staticTokenIssuer{})) ch := make(chan struct{}) errCh := make(chan error) @@ -105,8 +106,8 @@ var _ = Describe("Tokens Client", func() { return fmt.Sprintf("https://localhost:%d", publicPort) }, config: &config_kumactl.Context_DataplaneTokenApiCredentials{ - ClientCert: filepath.Join("..", "..", "..", "..", "pkg", "tokens", "builtin", "server", "testdata", "authorized-client-cert.pem"), - ClientKey: filepath.Join("..", "..", "..", "..", "pkg", "tokens", "builtin", "server", "testdata", "authorized-client-key.pem"), + ClientCert: filepath.Join("..", "..", "..", "..", "pkg", "admin-server", "testdata", "authorized-client-cert.pem"), + ClientKey: filepath.Join("..", "..", "..", "..", "pkg", "admin-server", "testdata", "authorized-client-key.pem"), }, }), ) diff --git a/pkg/admin-server/server.go b/pkg/admin-server/server.go index 7786484d5558..231a22259c8d 100644 --- a/pkg/admin-server/server.go +++ b/pkg/admin-server/server.go @@ -2,6 +2,8 @@ package admin_server import ( "context" + "crypto/tls" + "crypto/x509" "fmt" admin_server "github.com/Kong/kuma/pkg/config/admin-server" config_core "github.com/Kong/kuma/pkg/config/core" @@ -13,7 +15,10 @@ import ( "github.com/emicklei/go-restful" "github.com/pkg/errors" "go.uber.org/multierr" + "io/ioutil" "net/http" + "path/filepath" + "strings" ) var ( @@ -39,6 +44,14 @@ func NewAdminServer(cfg admin_server.AdminServerConfig, services ...*restful.Web func (a *AdminServer) Start(stop <-chan struct{}) error { httpServer, httpErrChan := a.startHttpServer() + var httpsServer *http.Server + var httpsErrChan chan error + if a.cfg.Public.Enabled { + httpsServer, httpsErrChan = a.startHttpsServer() + } else { + httpsErrChan = make(chan error) + } + select { case <-stop: log.Info("stopping") @@ -46,9 +59,16 @@ func (a *AdminServer) Start(stop <-chan struct{}) error { if err := httpServer.Shutdown(context.Background()); err != nil { multiErr = multierr.Combine(err) } + if httpsServer != nil { + if err := httpsServer.Shutdown(context.Background()); err != nil { + multiErr = multierr.Combine(err) + } + } return multiErr case err := <-httpErrChan: return err + case err := <-httpsErrChan: + return err } } @@ -75,6 +95,63 @@ func (a *AdminServer) startHttpServer() (*http.Server, chan error) { return server, errChan } +func (a *AdminServer) startHttpsServer() (*http.Server, chan error) { + errChan := make(chan error) + + tlsConfig, err := requireClientCerts(a.cfg.Public.ClientCertsDir) + if err != nil { + errChan <- err + } + + server := &http.Server{ + Addr: fmt.Sprintf("%s:%d", a.cfg.Public.Interface, a.cfg.Public.Port), + Handler: a.container, + TLSConfig: tlsConfig, + } + + go func() { + defer close(errChan) + if err := server.ListenAndServeTLS(a.cfg.Public.TlsCertFile, a.cfg.Public.TlsKeyFile); err != nil { + if err != http.ErrServerClosed { + log.Error(err, "https server terminated with an error") + errChan <- err + return + } + } + log.Info("https server terminated normally") + }() + log.Info("starting server", "interface", a.cfg.Public.Interface, "port", a.cfg.Public.Port, "tls", true) + return server, errChan +} + +func requireClientCerts(certsDir string) (*tls.Config, error) { + files, err := ioutil.ReadDir(certsDir) + if err != nil { + return nil, err + } + clientCertPool := x509.NewCertPool() + for _, file := range files { + if file.IsDir() { + continue + } + if !strings.HasSuffix(file.Name(), ".pem") { + continue + } + path := filepath.Join(certsDir, file.Name()) + caCert, err := ioutil.ReadFile(path) + if err != nil { + return nil, errors.Wrapf(err, "could not read certificate %s", path) + } + clientCertPool.AppendCertsFromPEM(caCert) + } + tlsConfig := &tls.Config{ + ClientCAs: clientCertPool, + ClientAuth: tls.RequireAndVerifyClientCert, + } + tlsConfig.BuildNameToCertificate() + return tlsConfig, nil +} + func SetupServer(rt runtime.Runtime) error { var webservices []*restful.WebService @@ -95,7 +172,7 @@ func SetupServer(rt runtime.Runtime) error { func dataplaneTokenWs(rt runtime.Runtime) (*restful.WebService, error) { if !rt.Config().AdminServer.DataplaneTokenWs.Enabled { - log.Info("Dataplane Token Webservice disabled") + log.Info("Dataplane Token Webservice is disabled. Dataplane Tokens won't be verified.") return nil, nil } diff --git a/pkg/admin-server/server_suite_test.go b/pkg/admin-server/server_suite_test.go new file mode 100644 index 000000000000..d23bd7b973ff --- /dev/null +++ b/pkg/admin-server/server_suite_test.go @@ -0,0 +1,13 @@ +package admin_server_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestAdminServer(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Admin Server Suite") +} diff --git a/pkg/admin-server/server_test.go b/pkg/admin-server/server_test.go new file mode 100644 index 000000000000..68cc1db7c3cd --- /dev/null +++ b/pkg/admin-server/server_test.go @@ -0,0 +1,146 @@ +package admin_server_test + +import ( + "fmt" + "io/ioutil" + "net/http" + "path/filepath" + + admin_server "github.com/Kong/kuma/pkg/admin-server" + admin_server_config "github.com/Kong/kuma/pkg/config/admin-server" + "github.com/Kong/kuma/pkg/test" + util_http "github.com/Kong/kuma/pkg/util/http" + "github.com/emicklei/go-restful" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Admin Server", func() { + + httpsClient := func(name string) *http.Client { + httpClient := &http.Client{} + err := util_http.ConfigureTls( + httpClient, + filepath.Join("testdata", "server-cert.pem"), + filepath.Join("testdata", fmt.Sprintf("%s-cert.pem", name)), + filepath.Join("testdata", fmt.Sprintf("%s-key.pem", name)), + ) + Expect(err).ToNot(HaveOccurred()) + return httpClient + } + + var port int + var publicPort int + + BeforeEach(func() { + // setup server + p, err := test.GetFreePort() + Expect(err).ToNot(HaveOccurred()) + port = p + p, err = test.GetFreePort() + Expect(err).ToNot(HaveOccurred()) + publicPort = p + + cfg := admin_server_config.AdminServerConfig{ + Local: &admin_server_config.LocalAdminServerConfig{ + Port: uint32(port), + }, + Public: &admin_server_config.PublicAdminServerConfig{ + Enabled: true, + Port: uint32(publicPort), + Interface: "localhost", + TlsCertFile: filepath.Join("testdata", "server-cert.pem"), + TlsKeyFile: filepath.Join("testdata", "server-key.pem"), + ClientCertsDir: filepath.Join("testdata", "authorized-clients"), + }, + } + + srv := admin_server.NewAdminServer(cfg, pingWs()) + + ch := make(chan struct{}) + go func() { + defer GinkgoRecover() + Expect(srv.Start(ch)).ToNot(HaveOccurred()) + }() + + // wait for server to start + Eventually(func() error { + req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%d/ping", port), nil) + Expect(err).ToNot(HaveOccurred()) + _, err = http.DefaultClient.Do(req) + return err + }, "5s", "100ms").ShouldNot(HaveOccurred()) + + // and should response on public port + Eventually(func() error { + req, err := http.NewRequest("GET", fmt.Sprintf("https://localhost:%d/ping", publicPort), nil) + Expect(err).ToNot(HaveOccurred()) + _, err = httpsClient("authorized-client").Do(req) + return err + }, "5s", "100ms").ShouldNot(HaveOccurred()) + }) + + It("should serve on http", func() { + // given + req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%d/ping", port), nil) + Expect(err).ToNot(HaveOccurred()) + + // when + resp, err := http.DefaultClient.Do(req) + + // then + Expect(err).ToNot(HaveOccurred()) + + // when + bytes, err := ioutil.ReadAll(resp.Body) + + // then + Expect(err).ToNot(HaveOccurred()) + Expect(string(bytes)).To(Equal("pong")) + }) + + It("should serve on https for authorized client", func() { + // given + req, err := http.NewRequest("GET", fmt.Sprintf("https://localhost:%d/ping", publicPort), nil) + Expect(err).ToNot(HaveOccurred()) + + // when + resp, err := httpsClient("authorized-client").Do(req) + + // then + Expect(err).ToNot(HaveOccurred()) + + // when + bytes, err := ioutil.ReadAll(resp.Body) + + // then + Expect(err).ToNot(HaveOccurred()) + Expect(string(bytes)).To(Equal("pong")) + }) + + It("should not let unauthorized to use an https server", func() { + // given + req, err := http.NewRequest("GET", fmt.Sprintf("https://localhost:%d/ping", publicPort), nil) + Expect(err).ToNot(HaveOccurred()) + + // when + _, err = httpsClient("unauthorized-client").Do(req) + + // then + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(HaveSuffix("tls: bad certificate")) + }) +}) + +func pingWs() *restful.WebService { + ws := new(restful.WebService). + Consumes(restful.MIME_JSON). + Produces(restful.MIME_JSON) + ws.Path("/"). + Route(ws.GET("/ping").To(func(request *restful.Request, response *restful.Response) { + _, _ = response.Write([]byte(`pong`)) + }), + ) + return ws +} diff --git a/pkg/tokens/builtin/server/testdata/authorized-client-cert.pem b/pkg/admin-server/testdata/authorized-client-cert.pem similarity index 100% rename from pkg/tokens/builtin/server/testdata/authorized-client-cert.pem rename to pkg/admin-server/testdata/authorized-client-cert.pem diff --git a/pkg/tokens/builtin/server/testdata/authorized-client-key.pem b/pkg/admin-server/testdata/authorized-client-key.pem similarity index 100% rename from pkg/tokens/builtin/server/testdata/authorized-client-key.pem rename to pkg/admin-server/testdata/authorized-client-key.pem diff --git a/pkg/tokens/builtin/server/testdata/authorized-clients/authorized-client-cert.pem b/pkg/admin-server/testdata/authorized-clients/authorized-client-cert.pem similarity index 100% rename from pkg/tokens/builtin/server/testdata/authorized-clients/authorized-client-cert.pem rename to pkg/admin-server/testdata/authorized-clients/authorized-client-cert.pem diff --git a/pkg/tokens/builtin/server/testdata/authorized-clients/file-that-should-be-ignored b/pkg/admin-server/testdata/authorized-clients/file-that-should-be-ignored similarity index 100% rename from pkg/tokens/builtin/server/testdata/authorized-clients/file-that-should-be-ignored rename to pkg/admin-server/testdata/authorized-clients/file-that-should-be-ignored diff --git a/pkg/tokens/builtin/server/testdata/server-cert.pem b/pkg/admin-server/testdata/server-cert.pem similarity index 100% rename from pkg/tokens/builtin/server/testdata/server-cert.pem rename to pkg/admin-server/testdata/server-cert.pem diff --git a/pkg/tokens/builtin/server/testdata/server-key.pem b/pkg/admin-server/testdata/server-key.pem similarity index 100% rename from pkg/tokens/builtin/server/testdata/server-key.pem rename to pkg/admin-server/testdata/server-key.pem diff --git a/pkg/tokens/builtin/server/testdata/unauthorized-client-cert.pem b/pkg/admin-server/testdata/unauthorized-client-cert.pem similarity index 100% rename from pkg/tokens/builtin/server/testdata/unauthorized-client-cert.pem rename to pkg/admin-server/testdata/unauthorized-client-cert.pem diff --git a/pkg/tokens/builtin/server/testdata/unauthorized-client-key.pem b/pkg/admin-server/testdata/unauthorized-client-key.pem similarity index 100% rename from pkg/tokens/builtin/server/testdata/unauthorized-client-key.pem rename to pkg/admin-server/testdata/unauthorized-client-key.pem diff --git a/pkg/api-server/config_ws_test.go b/pkg/api-server/config_ws_test.go index 17e37a37ea2c..e8b7c3910445 100644 --- a/pkg/api-server/config_ws_test.go +++ b/pkg/api-server/config_ws_test.go @@ -79,7 +79,6 @@ var _ = Describe("Config WS", func() { } }, "adminServer": { - "enabled": true, "local": { "port": 5679 }, @@ -91,7 +90,7 @@ var _ = Describe("Config WS", func() { "tlsCertFile": "", "tlsKeyFile": "" }, - "dataplaneTokenWs": { + "dataplaneTokenWs": { "enabled": true } }, diff --git a/pkg/config/app/kuma-cp/kuma-cp.defaults.yaml b/pkg/config/app/kuma-cp/kuma-cp.defaults.yaml index dd31b393b332..3d214bb9223c 100644 --- a/pkg/config/app/kuma-cp/kuma-cp.defaults.yaml +++ b/pkg/config/app/kuma-cp/kuma-cp.defaults.yaml @@ -79,8 +79,6 @@ dataplaneTokenServer: # Admin server configuration adminServer: - # If true then Admin Server and token verification is enabled - enabled: true # Local configuration of server that is available only on localhost local: # Port on which the server will be exposed diff --git a/pkg/core/bootstrap/autoconfig.go b/pkg/core/bootstrap/autoconfig.go index 7396ed60eef0..8a43f644b336 100644 --- a/pkg/core/bootstrap/autoconfig.go +++ b/pkg/core/bootstrap/autoconfig.go @@ -73,7 +73,7 @@ func autoconfigureSds(cfg *kuma_cp.Config) error { func autoconfigureAdminServer(cfg *kuma_cp.Config) { // use old dataplane token server config values for admin server if !reflect.DeepEqual(cfg.DataplaneTokenServer, token_server.DefaultDataplaneTokenServerConfig()) { - autoconfigureLog.Info("Deprecated config DataplaneTokenServer is used. Use AdminServer config instead.") + autoconfigureLog.Info("Deprecated DataplaneTokenServer config is used. It will be removed in the next major version of Kuma - use AdminServer config instead.") cfg.AdminServer = &admin_server.AdminServerConfig{ DataplaneTokenWs: &admin_server.DataplaneTokenWsConfig{ Enabled: cfg.DataplaneTokenServer.Enabled, diff --git a/pkg/tokens/builtin/server/dataplane_token_server.go b/pkg/tokens/builtin/server/dataplane_token_server.go deleted file mode 100644 index 57700e3d6eda..000000000000 --- a/pkg/tokens/builtin/server/dataplane_token_server.go +++ /dev/null @@ -1,215 +0,0 @@ -package server - -import ( - "context" - "crypto/tls" - "crypto/x509" - "encoding/json" - "fmt" - token_server "github.com/Kong/kuma/pkg/config/token-server" - "github.com/Kong/kuma/pkg/core" - "github.com/Kong/kuma/pkg/tokens/builtin" - "github.com/Kong/kuma/pkg/tokens/builtin/issuer" - "github.com/Kong/kuma/pkg/tokens/builtin/server/types" - "github.com/pkg/errors" - "go.uber.org/multierr" - "io/ioutil" - "net/http" - "path/filepath" - "strings" - - config_core "github.com/Kong/kuma/pkg/config/core" - core_runtime "github.com/Kong/kuma/pkg/core/runtime" -) - -var log = core.Log.WithName("dataplane-token-server") - -func SetupServer(rt core_runtime.Runtime) error { - if !rt.Config().DataplaneTokenServer.Enabled { - log.Info("server disabled") - return nil - } - switch env := rt.Config().Environment; env { - case config_core.KubernetesEnvironment: - return nil - case config_core.UniversalEnvironment: - generator, err := builtin.NewDataplaneTokenIssuer(rt) - if err != nil { - return err - } - srv := &DataplaneTokenServer{ - Config: rt.Config().DataplaneTokenServer, - Issuer: generator, - } - if err := core_runtime.Add(rt, srv); err != nil { - return err - } - default: - return errors.Errorf("unknown environment type %s", env) - } - return nil -} - -type DataplaneTokenServer struct { - Config *token_server.DataplaneTokenServerConfig - Issuer issuer.DataplaneTokenIssuer -} - -var _ core_runtime.Component = &DataplaneTokenServer{} - -func (a *DataplaneTokenServer) Start(stop <-chan struct{}) error { - httpServer, httpErrChan := a.startHttpServer() - - var httpsServer *http.Server - var httpsErrChan chan error - if a.Config.Public.Enabled { - httpsServer, httpsErrChan = a.startHttpsServer() - } else { - httpsErrChan = make(chan error) - } - - select { - case <-stop: - log.Info("stopping") - var multiErr error - if err := httpServer.Shutdown(context.Background()); err != nil { - multiErr = multierr.Combine(err) - } - if httpsServer != nil { - if err := httpsServer.Shutdown(context.Background()); err != nil { - multiErr = multierr.Combine(err) - } - } - return multiErr - case err := <-httpErrChan: - return err - case err := <-httpsErrChan: - return err - } -} - -func (a *DataplaneTokenServer) startHttpServer() (*http.Server, chan error) { - mux := http.NewServeMux() - mux.HandleFunc("/tokens", a.handleIdentityRequest) - - server := &http.Server{ - Addr: fmt.Sprintf("127.0.0.1:%d", a.Config.Local.Port), - Handler: mux, - } - - errChan := make(chan error) - - go func() { - defer close(errChan) - if err := server.ListenAndServe(); err != nil { - if err != http.ErrServerClosed { - log.Error(err, "http server terminated with an error") - errChan <- err - return - } - } - log.Info("http server terminated normally") - }() - log.Info("starting server", "port", a.Config.Local.Port) - return server, errChan -} - -func (a *DataplaneTokenServer) startHttpsServer() (*http.Server, chan error) { - mux := http.NewServeMux() - mux.HandleFunc("/tokens", a.handleIdentityRequest) - - errChan := make(chan error) - - tlsConfig, err := requireClientCerts(a.Config.Public.ClientCertsDir) - if err != nil { - errChan <- err - } - - server := &http.Server{ - Addr: fmt.Sprintf("%s:%d", a.Config.Public.Interface, a.Config.Public.Port), - Handler: mux, - TLSConfig: tlsConfig, - } - - go func() { - defer close(errChan) - if err := server.ListenAndServeTLS(a.Config.Public.TlsCertFile, a.Config.Public.TlsKeyFile); err != nil { - if err != http.ErrServerClosed { - log.Error(err, "https server terminated with an error") - errChan <- err - return - } - } - log.Info("https server terminated normally") - }() - log.Info("starting server", "interface", a.Config.Public.Interface, "port", a.Config.Public.Port, "tls", true) - return server, errChan -} - -func requireClientCerts(certsDir string) (*tls.Config, error) { - files, err := ioutil.ReadDir(certsDir) - if err != nil { - return nil, err - } - clientCertPool := x509.NewCertPool() - for _, file := range files { - if file.IsDir() { - continue - } - if !strings.HasSuffix(file.Name(), ".pem") { - continue - } - path := filepath.Join(certsDir, file.Name()) - caCert, err := ioutil.ReadFile(path) - if err != nil { - return nil, errors.Wrapf(err, "could not read certificate %s", path) - } - clientCertPool.AppendCertsFromPEM(caCert) - } - tlsConfig := &tls.Config{ - ClientCAs: clientCertPool, - ClientAuth: tls.RequireAndVerifyClientCert, - } - tlsConfig.BuildNameToCertificate() - return tlsConfig, nil -} - -func (a *DataplaneTokenServer) handleIdentityRequest(resp http.ResponseWriter, req *http.Request) { - bytes, err := ioutil.ReadAll(req.Body) - if err != nil { - log.Error(err, "Could not read a request") - resp.WriteHeader(http.StatusInternalServerError) - return - } - idReq := types.DataplaneTokenRequest{} - if err := json.Unmarshal(bytes, &idReq); err != nil { - log.Error(err, "Could not parse a request") - resp.WriteHeader(http.StatusBadRequest) - return - } - if idReq.Name == "" { - resp.WriteHeader(http.StatusBadRequest) - if _, err := resp.Write([]byte("name cannot be empty")); err != nil { - log.Error(err, "Could write a response") - } - return - } - if idReq.Mesh == "" { - resp.WriteHeader(http.StatusBadRequest) - if _, err := resp.Write([]byte("mesh cannot be empty")); err != nil { - log.Error(err, "Could write a response") - } - return - } - - token, err := a.Issuer.Generate(idReq.ToProxyId()) - if err != nil { - log.Error(err, "Could not sign a token") - resp.WriteHeader(http.StatusInternalServerError) - return - } - resp.Header().Set("content-type", "text/plain") - if _, err := resp.Write([]byte(token)); err != nil { - log.Error(err, "Could write a response") - } -} diff --git a/pkg/tokens/builtin/server/dataplane_token_server_test.go b/pkg/tokens/builtin/server/dataplane_token_server_test.go deleted file mode 100644 index b2c317e24962..000000000000 --- a/pkg/tokens/builtin/server/dataplane_token_server_test.go +++ /dev/null @@ -1,188 +0,0 @@ -package server_test - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - token_server "github.com/Kong/kuma/pkg/config/token-server" - "github.com/Kong/kuma/pkg/core/xds" - "github.com/Kong/kuma/pkg/sds/auth" - "github.com/Kong/kuma/pkg/test" - "github.com/Kong/kuma/pkg/tokens/builtin/issuer" - "github.com/Kong/kuma/pkg/tokens/builtin/server" - "github.com/Kong/kuma/pkg/tokens/builtin/server/types" - util_http "github.com/Kong/kuma/pkg/util/http" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - "io/ioutil" - "net/http" - "path/filepath" - "strings" -) - -type staticTokenIssuer struct { - resp string -} - -var _ issuer.DataplaneTokenIssuer = &staticTokenIssuer{} - -func (s *staticTokenIssuer) Generate(proxyId xds.ProxyId) (auth.Credential, error) { - return auth.Credential(s.resp), nil -} - -func (s *staticTokenIssuer) Validate(credential auth.Credential) (xds.ProxyId, error) { - return xds.ProxyId{}, errors.New("not implemented") -} - -var _ = Describe("Dataplane Token Server", func() { - - var port int - var publicPort int - const credentials = "test" - - httpsClient := func(name string) *http.Client { - httpClient := &http.Client{} - err := util_http.ConfigureTls( - httpClient, - filepath.Join("testdata", "server-cert.pem"), - filepath.Join("testdata", fmt.Sprintf("%s-cert.pem", name)), - filepath.Join("testdata", fmt.Sprintf("%s-key.pem", name)), - ) - Expect(err).ToNot(HaveOccurred()) - return httpClient - } - - BeforeEach(func() { - p, err := test.GetFreePort() - Expect(err).ToNot(HaveOccurred()) - port = p - p, err = test.GetFreePort() - Expect(err).ToNot(HaveOccurred()) - publicPort = p - srv := server.DataplaneTokenServer{ - Issuer: &staticTokenIssuer{credentials}, - Config: &token_server.DataplaneTokenServerConfig{ - Enabled: true, - Local: &token_server.LocalDataplaneTokenServerConfig{ - Port: uint32(port), - }, - Public: &token_server.PublicDataplaneTokenServerConfig{ - Enabled: true, - Port: uint32(publicPort), - Interface: "localhost", - TlsCertFile: filepath.Join("testdata", "server-cert.pem"), - TlsKeyFile: filepath.Join("testdata", "server-key.pem"), - ClientCertsDir: filepath.Join("testdata", "authorized-clients"), - }, - }, - } - - ch := make(chan struct{}) - errCh := make(chan error) - go func() { - defer GinkgoRecover() - errCh <- srv.Start(ch) - }() - - // wait for the http server to be started - Eventually(func() error { - req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/tokens", port), nil) - Expect(err).ToNot(HaveOccurred()) - _, err = http.DefaultClient.Do(req) - return err - }, "5s", "100ms").ShouldNot(HaveOccurred()) - - // wait for the https server to be started - Eventually(func() error { - req, err := http.NewRequest("POST", fmt.Sprintf("https://localhost:%d/tokens", publicPort), nil) - Expect(err).ToNot(HaveOccurred()) - _, err = httpsClient("authorized-client").Do(req) - return err - }, "5s", "100ms").ShouldNot(HaveOccurred()) - }) - - type testCase struct { - clientFn func() *http.Client - url string - } - DescribeTable("should respond with generated token", - func(given testCase) { - // given - idReq := types.DataplaneTokenRequest{ - Mesh: "defualt", - Name: "dp-1", - } - reqBytes, err := json.Marshal(idReq) - Expect(err).ToNot(HaveOccurred()) - - // when - req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%d/tokens", port), bytes.NewReader(reqBytes)) - Expect(err).ToNot(HaveOccurred()) - resp, err := http.DefaultClient.Do(req) - - // then - Expect(err).ToNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(200)) - - // when - respBody, err := ioutil.ReadAll(resp.Body) - - // then - Expect(err).ToNot(HaveOccurred()) - Expect(string(respBody)).To(Equal(credentials)) - }, - Entry("using http server", testCase{ - clientFn: func() *http.Client { - return http.DefaultClient - }, - url: fmt.Sprintf("http://localhost:%d/tokens", port), - }), - Entry("using https server and authorized client", testCase{ - clientFn: func() *http.Client { - return httpsClient("authorized-client") - }, - url: fmt.Sprintf("https://localhost:%d/tokens", publicPort), - }), - ) - - DescribeTable("should return bad request on invalid json", - func(json string) { - // when - req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%d/tokens", port), strings.NewReader(json)) - - // then - Expect(err).ToNot(HaveOccurred()) - - // when - resp, err := http.DefaultClient.Do(req) - - // then - Expect(err).ToNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(400)) - }, - Entry("json does not contain name", `{"mesh": "default"}`), - Entry("json does not contain mesh", `{"name": "default"}`), - Entry("not valid json", `not-valid-json`), - ) - - It("should not let unauthorized clients generate a token", func() { - // given - idReq := types.DataplaneTokenRequest{ - Mesh: "defualt", - Name: "dp-1", - } - reqBytes, err := json.Marshal(idReq) - Expect(err).ToNot(HaveOccurred()) - - // when - req, err := http.NewRequest("GET", fmt.Sprintf("https://localhost:%d/tokens", publicPort), bytes.NewReader(reqBytes)) - Expect(err).ToNot(HaveOccurred()) - _, err = httpsClient("unauthorized-client").Do(req) - - // then - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(HaveSuffix("tls: bad certificate")) - }) -}) diff --git a/pkg/tokens/builtin/server/webservice.go b/pkg/tokens/builtin/server/webservice.go index 2d00bcd5424c..e2ecadbca39a 100644 --- a/pkg/tokens/builtin/server/webservice.go +++ b/pkg/tokens/builtin/server/webservice.go @@ -1,6 +1,7 @@ package server import ( + "github.com/Kong/kuma/pkg/core" "github.com/Kong/kuma/pkg/core/rest/errors" "github.com/Kong/kuma/pkg/core/validators" "github.com/Kong/kuma/pkg/tokens/builtin/issuer" @@ -9,18 +10,20 @@ import ( "net/http" ) -type DataplaneTokenWebService struct { - Issuer issuer.DataplaneTokenIssuer +var log = core.Log.WithName("dataplane-token-ws") + +type dataplaneTokenWebService struct { + issuer issuer.DataplaneTokenIssuer } func NewWebservice(issuer issuer.DataplaneTokenIssuer) *restful.WebService { - ws := DataplaneTokenWebService{ - Issuer: issuer, + ws := dataplaneTokenWebService{ + issuer: issuer, } return ws.createWs() } -func (d *DataplaneTokenWebService) createWs() *restful.WebService { +func (d *dataplaneTokenWebService) createWs() *restful.WebService { ws := new(restful.WebService). Consumes(restful.MIME_JSON). Produces(restful.MIME_JSON) @@ -29,11 +32,11 @@ func (d *DataplaneTokenWebService) createWs() *restful.WebService { return ws } -func (d *DataplaneTokenWebService) handleIdentityRequest(request *restful.Request, response *restful.Response) { +func (d *dataplaneTokenWebService) handleIdentityRequest(request *restful.Request, response *restful.Response) { idReq := types.DataplaneTokenRequest{} if err := request.ReadEntity(&idReq); err != nil { log.Error(err, "Could not read a request") - response.WriteHeader(http.StatusInternalServerError) + response.WriteHeader(http.StatusBadRequest) return } verr := validators.ValidationError{} @@ -48,7 +51,7 @@ func (d *DataplaneTokenWebService) handleIdentityRequest(request *restful.Reques return } - token, err := d.Issuer.Generate(idReq.ToProxyId()) + token, err := d.issuer.Generate(idReq.ToProxyId()) if err != nil { errors.HandleError(response, err, "Could not issue a token") return diff --git a/pkg/tokens/builtin/server/webservice_test.go b/pkg/tokens/builtin/server/webservice_test.go new file mode 100644 index 000000000000..1c6887e1d431 --- /dev/null +++ b/pkg/tokens/builtin/server/webservice_test.go @@ -0,0 +1,102 @@ +package server_test + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/Kong/kuma/pkg/core/xds" + "github.com/Kong/kuma/pkg/sds/auth" + "github.com/Kong/kuma/pkg/tokens/builtin/issuer" + "github.com/Kong/kuma/pkg/tokens/builtin/server" + "github.com/Kong/kuma/pkg/tokens/builtin/server/types" + "github.com/emicklei/go-restful" + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + "github.com/pkg/errors" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" +) + +type staticTokenIssuer struct { + resp string +} + +var _ issuer.DataplaneTokenIssuer = &staticTokenIssuer{} + +func (s *staticTokenIssuer) Generate(proxyId xds.ProxyId) (auth.Credential, error) { + return auth.Credential(s.resp), nil +} + +func (s *staticTokenIssuer) Validate(credential auth.Credential) (xds.ProxyId, error) { + return xds.ProxyId{}, errors.New("not implemented") +} + +var _ = Describe("Dataplane Token Webservice", func() { + + const credentials = "test" + var url string + + BeforeEach(func() { + ws := server.NewWebservice(&staticTokenIssuer{credentials}) + + container := restful.NewContainer() + container.Add(ws) + srv := httptest.NewServer(container) + url = srv.URL + + // wait for the server + Eventually(func() error { + _, err := http.DefaultClient.Get(fmt.Sprintf("%s/tokens", srv.URL)) + return err + }).ShouldNot(HaveOccurred()) + }) + + It("should respond with generated token", func() { + // given + idReq := types.DataplaneTokenRequest{ + Mesh: "defualt", + Name: "dp-1", + } + reqBytes, err := json.Marshal(idReq) + Expect(err).ToNot(HaveOccurred()) + + // when + req, err := http.NewRequest("POST", fmt.Sprintf("%s/tokens", url), bytes.NewReader(reqBytes)) + Expect(err).ToNot(HaveOccurred()) + req.Header.Add("content-type", "application/json") + resp, err := http.DefaultClient.Do(req) + + // then + Expect(err).ToNot(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(200)) + + // when + respBody, err := ioutil.ReadAll(resp.Body) + + // then + Expect(err).ToNot(HaveOccurred()) + Expect(string(respBody)).To(Equal(credentials)) + }) + + DescribeTable("should return bad request on invalid json", + func(json string) { + // given + req, err := http.NewRequest("POST", fmt.Sprintf("%s/tokens", url), strings.NewReader(json)) + Expect(err).ToNot(HaveOccurred()) + req.Header.Add("content-type", "application/json") + + // when + resp, err := http.DefaultClient.Do(req) + + // then + Expect(err).ToNot(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(400)) + }, + Entry("json does not contain name", `{"mesh": "default"}`), + Entry("json does not contain mesh", `{"name": "default"}`), + Entry("not valid json", `not-valid-json`), + ) +})