From 4d04557f71fbf26b2190ecd26f65101871cbd90e Mon Sep 17 00:00:00 2001 From: Peter Sabaini Date: Fri, 12 Jul 2024 15:33:39 +0200 Subject: [PATCH] WIP: Initial RGW TLS support Signed-off-by: Peter Sabaini --- microceph/ceph/configwriter.go | 2 +- microceph/ceph/configwriter_test.go | 26 ++++++++++++++++++ microceph/ceph/rgw.go | 11 +++++--- microceph/ceph/rgw_test.go | 24 ++++++++++++++++- microceph/ceph/services_placement_rgw.go | 7 +++-- microceph/cmd/microceph/enable_rgw.go | 34 +++++++++++++++++++----- 6 files changed, 89 insertions(+), 15 deletions(-) diff --git a/microceph/ceph/configwriter.go b/microceph/ceph/configwriter.go index 2bb218d2..2f7257b3 100644 --- a/microceph/ceph/configwriter.go +++ b/microceph/ceph/configwriter.go @@ -87,7 +87,7 @@ auth allow insecure global id reclaim = false [client.radosgw.gateway] rgw init timeout = 1200 -rgw frontends = beast port={{.rgwPort}} +rgw frontends = beast port={{.rgwPort}}{{if and .sslCertificate .sslPrivateKey}} ssl_port={{.sslPort}}{{end}}{{if .sslCertificate}} ssl_certificate={{.sslCertificate}}{{end}}{{if .sslPrivateKey}} ssl_private_key={{.sslPrivateKey}}{{end}} `)), configFile: "radosgw.conf", configDir: configDir, diff --git a/microceph/ceph/configwriter_test.go b/microceph/ceph/configwriter_test.go index 0843c04d..accae91e 100644 --- a/microceph/ceph/configwriter_test.go +++ b/microceph/ceph/configwriter_test.go @@ -50,6 +50,7 @@ func (s *configWriterSuite) TestWriteRadosGWConfig() { err := config.WriteConfig( map[string]any{ "monitors": "foohost", + "rgwPort": 80, }, 0644, ) @@ -61,6 +62,31 @@ func (s *configWriterSuite) TestWriteRadosGWConfig() { data, err := os.ReadFile(config.GetPath()) assert.Equal(s.T(), nil, err) assert.Contains(s.T(), string(data), "foohost") + assert.Contains(s.T(), string(data), "rgw frontends = beast port=80\n") +} + +// Test ceph config writing +func (s *configWriterSuite) TestWriteRadosGWSSLConfig() { + config := newRadosGWConfig(s.Tmp) + err := config.WriteConfig( + map[string]any{ + "monitors": "foohost", + "rgwPort": 80, + "sslPort": 443, + "sslCertificate": "/var/snap/microceph/common/server.crt", + "sslPrivateKey": "/var/snap/microceph/common/server.key", + }, + 0644, + ) + assert.Equal(s.T(), nil, err) + // Check that the file exists + _, err = os.Stat(config.GetPath()) + assert.Equal(s.T(), nil, err) + // Check contents of the file + data, err := os.ReadFile(config.GetPath()) + assert.Equal(s.T(), nil, err) + assert.Contains(s.T(), string(data), "foohost") + assert.Contains(s.T(), string(data), "rgw frontends = beast port=80 ssl_port=443 ssl_certificate=/var/snap/microceph/common/server.crt ssl_private_key=/var/snap/microceph/common/server.key") } // Test ceph keyring writing diff --git a/microceph/ceph/rgw.go b/microceph/ceph/rgw.go index 8218edb4..5dfd5873 100644 --- a/microceph/ceph/rgw.go +++ b/microceph/ceph/rgw.go @@ -15,13 +15,16 @@ import ( ) // EnableRGW enables the RGW service on the cluster and adds initial configuration given a service port number. -func EnableRGW(s interfaces.StateInterface, port int, monitors []string) error { +func EnableRGW(s interfaces.StateInterface, port int, sslPort int, sslCertificate string, sslPrivateKey string, monitors []string) error { pathConsts := constants.GetPathConst() configs := map[string]any{ - "runDir": pathConsts.RunPath, - "monitors": strings.Join(monitors, ","), - "rgwPort": port, + "runDir": pathConsts.RunPath, + "monitors": strings.Join(monitors, ","), + "rgwPort": port, + "sslPort": sslPort, + "sslCertificate": sslCertificate, + "sslPrivateKey": sslPrivateKey, } // Create RGW configuration. diff --git a/microceph/ceph/rgw_test.go b/microceph/ceph/rgw_test.go index 90f95daf..577b6b72 100644 --- a/microceph/ceph/rgw_test.go +++ b/microceph/ceph/rgw_test.go @@ -51,6 +51,11 @@ func addStopRGWExpectations(s *rgwSuite, r *mocks.Runner) { r.On("RunCommand", tests.CmdAny("snapctl", 3)...).Return("ok", nil).Once() } +// Expect: run ceph auth +func addCreateRGWKeyringExpectations(r *mocks.Runner) { + r.On("RunCommand", tests.CmdAny("ceph", 9)...).Return("ok", nil).Once() +} + // Set up test suite func (s *rgwSuite) SetupTest() { s.BaseSuite.SetupTest() @@ -67,7 +72,7 @@ func (s *rgwSuite) TestEnableRGW() { processExec = r - err := EnableRGW(s.TestStateInterface, 80, []string{"10.1.1.1", "10.2.2.2"}) + err := EnableRGW(s.TestStateInterface, 80, 443, "", "", []string{"10.1.1.1", "10.2.2.2"}) assert.NoError(s.T(), err) @@ -77,6 +82,23 @@ func (s *rgwSuite) TestEnableRGW() { assert.Contains(s.T(), conf, "mon host = 10.1.1.1,10.2.2.2") } +// Test enabling RGW +func (s *rgwSuite) TestEnableRGWWithSSL() { + r := mocks.NewRunner(s.T()) + + addRGWEnableExpectations(r) + + processExec = r + + err := EnableRGW(s.TestStateInterface, 80, 443, "/var/snap/microceph/common/server.crt", "/var/snap/microceph/common/server.key", []string{"10.1.1.1", "10.2.2.2"}) + + assert.NoError(s.T(), err) + + // check that the radosgw.conf file contains expected values + conf := s.ReadCephConfig("radosgw.conf") + assert.Contains(s.T(), conf, "rgw frontends = beast port=80 ssl_port=443 ssl_certificate=/var/snap/microceph/common/server.crt ssl_private_key=/var/snap/microceph/common/server.key\n") +} + func (s *rgwSuite) TestDisableRGW() { r := mocks.NewRunner(s.T()) diff --git a/microceph/ceph/services_placement_rgw.go b/microceph/ceph/services_placement_rgw.go index 19e53bef..7aa4b3ff 100644 --- a/microceph/ceph/services_placement_rgw.go +++ b/microceph/ceph/services_placement_rgw.go @@ -8,7 +8,10 @@ import ( ) type RgwServicePlacement struct { - Port int + Port int + SSLPort int + SSLCertificate string + SSLPrivateKey string } func (rgw *RgwServicePlacement) PopulateParams(s interfaces.StateInterface, payload string) error { @@ -32,7 +35,7 @@ func (rgw *RgwServicePlacement) ServiceInit(s interfaces.StateInterface) error { return fmt.Errorf("failed to get config db: %w", err) } - return EnableRGW(s, rgw.Port, getMonitorAddresses(config)) + return EnableRGW(s, rgw.Port, rgw.SSLPort, rgw.SSLCertificate, rgw.SSLPrivateKey, getMonitorAddresses(config)) } func (rgw *RgwServicePlacement) PostPlacementCheck(s interfaces.StateInterface) error { diff --git a/microceph/cmd/microceph/enable_rgw.go b/microceph/cmd/microceph/enable_rgw.go index 890b2b34..820c37d9 100644 --- a/microceph/cmd/microceph/enable_rgw.go +++ b/microceph/cmd/microceph/enable_rgw.go @@ -3,6 +3,9 @@ package main import ( "context" "encoding/json" + "fmt" + "os" + "strings" "github.com/canonical/microcluster/microcluster" "github.com/spf13/cobra" @@ -13,19 +16,25 @@ import ( ) type cmdEnableRGW struct { - common *CmdControl - wait bool - flagPort int - flagTarget string + common *CmdControl + wait bool + flagPort int + flagSSLPort int + flagSSLCertificate string + flagSSLPrivateKey string + flagTarget string } func (c *cmdEnableRGW) Command() *cobra.Command { cmd := &cobra.Command{ - Use: "rgw [--port ] [--target ] [--wait ]", + Use: "rgw [--port ] [--ssl-port ] [--ssl-certificate ] [--ssl-private-key ] [--target ] [--wait ]", Short: "Enable the RGW service on the --target server (default: this server)", RunE: c.Run, } - cmd.PersistentFlags().IntVar(&c.flagPort, "port", 80, "Service port (default: 80)") + cmd.PersistentFlags().IntVar(&c.flagPort, "port", 80, "Service non-SSL port (default: 80)") + cmd.PersistentFlags().IntVar(&c.flagSSLPort, "ssl-port", 443, "Service SSL port (default: 443)") + cmd.PersistentFlags().StringVar(&c.flagSSLCertificate, "ssl-certificate", "", "Path to SSL certificate") + cmd.PersistentFlags().StringVar(&c.flagSSLPrivateKey, "ssl-private-key", "", "Path to SSL private key") cmd.PersistentFlags().StringVar(&c.flagTarget, "target", "", "Server hostname (default: this server)") cmd.Flags().BoolVar(&c.wait, "wait", true, "Wait for rgw service to be up.") return cmd @@ -43,7 +52,18 @@ func (c *cmdEnableRGW) Run(cmd *cobra.Command, args []string) error { return err } - jsp, err := json.Marshal(ceph.RgwServicePlacement{Port: c.flagPort}) + // sanity check: are ssl files in a place the microcephd can read? + if c.flagSSLCertificate != "" { + for _, sslFile := range []string{c.flagSSLCertificate, c.flagSSLPrivateKey} { + if !strings.HasPrefix(sslFile, os.Getenv("SNAP_COMMON")) && + !strings.HasPrefix(sslFile, os.Getenv("SNAP_USER_COMMON")) { + // print warning + fmt.Println("Warning: SSL files might not be readable by daemon. It's recommended to use files in $SNAP_COMMON or $SNAP_USER_COMMON.") + } + } + } + + jsp, err := json.Marshal(ceph.RgwServicePlacement{Port: c.flagPort, SSLPort: c.flagSSLPort, SSLCertificate: c.flagSSLCertificate, SSLPrivateKey: c.flagSSLPrivateKey}) if err != nil { return err }