From 778168d8d70f626359d3f0e8d9d15f2cdaed40f7 Mon Sep 17 00:00:00 2001 From: STeve Huang Date: Thu, 18 Nov 2021 16:31:13 -0500 Subject: [PATCH] Add --public-addr --cert-file --key-file for teleport configure (#9049) --- lib/config/configuration_test.go | 121 ++++++++++++++++++++++++------- lib/config/fileconf.go | 33 ++++++++- tool/teleport/common/teleport.go | 17 +++++ 3 files changed, 143 insertions(+), 28 deletions(-) diff --git a/lib/config/configuration_test.go b/lib/config/configuration_test.go index 1175bfc5873d7..3c78aaed02484 100644 --- a/lib/config/configuration_test.go +++ b/lib/config/configuration_test.go @@ -107,35 +107,104 @@ func TestMain(m *testing.M) { os.Exit(res) } -func TestConfig(t *testing.T) { - t.Run("SampleConfig", func(t *testing.T) { - // generate sample config and write it into a temp file: - sfc, err := MakeSampleFileConfig(SampleFlags{ - ClusterName: "cookie.localhost", - ACMEEnabled: true, - ACMEEmail: "alice@example.com", - LicensePath: "/tmp/license.pem", - }) - require.NoError(t, err) - require.NotNil(t, sfc) - fn := filepath.Join(t.TempDir(), "default-config.yaml") - err = os.WriteFile(fn, []byte(sfc.DebugDumpToYAML()), 0660) - require.NoError(t, err) +func TestSampleConfig(t *testing.T) { + testCases := []struct { + name string + input SampleFlags + expectError bool + expectClusterName ClusterName + expectLicenseFile string + expectProxyPublicAddrs apiutils.Strings + expectProxyWebAddr string + expectProxyKeyPairs []KeyPair + }{ + { + name: "ACMEEnabled", + input: SampleFlags{ + ClusterName: "cookie.localhost", + ACMEEnabled: true, + ACMEEmail: "alice@example.com", + LicensePath: "/tmp/license.pem", + }, + expectClusterName: ClusterName("cookie.localhost"), + expectLicenseFile: "/tmp/license.pem", + expectProxyPublicAddrs: apiutils.Strings{"cookie.localhost:443"}, + expectProxyWebAddr: "0.0.0.0:443", + }, + { + name: "public addrs", + input: SampleFlags{ + PublicAddrs: []string{"tele.example.com:443"}, + }, + expectProxyPublicAddrs: apiutils.Strings{"tele.example.com:443"}, + }, + { + name: "key file missing", + input: SampleFlags{ + CertFile: "/var/lib/teleport/fullchain.pem", + }, + expectError: true, + }, + { + name: "load x509 key pair failed", + input: SampleFlags{ + KeyFile: "/var/lib/teleport/key.pem", + CertFile: "/var/lib/teleport/fullchain.pem", + }, + expectError: true, + }, + { + name: "cluster name missing", + input: SampleFlags{ + ACMEEnabled: true, + }, + expectError: true, + }, + { + name: "ACMEEnabled conflict with key file", + input: SampleFlags{ + ClusterName: "cookie.localhost", + ACMEEnabled: true, + KeyFile: "/var/lib/teleport/privkey.pem", + CertFile: "/var/lib/teleport/fullchain.pem", + }, + expectError: true, + }, + } - // make sure it could be parsed: - fc, err := ReadFromFile(fn) - require.NoError(t, err) + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + sfc, err := MakeSampleFileConfig(testCase.input) + + if testCase.expectError { + require.Error(t, err) + require.Nil(t, sfc) + return + } - // validate a couple of values: - require.Equal(t, defaults.DataDir, fc.Global.DataDir) - require.Equal(t, "INFO", fc.Logger.Severity) - require.Equal(t, fc.Auth.ClusterName, ClusterName("cookie.localhost")) - require.Equal(t, fc.Auth.LicenseFile, "/tmp/license.pem") - require.Equal(t, fc.Proxy.PublicAddr, apiutils.Strings{"cookie.localhost:443"}) - require.Equal(t, fc.Proxy.WebAddr, "0.0.0.0:443") + require.NoError(t, err) + require.NotNil(t, sfc) - require.False(t, lib.IsInsecureDevMode()) - }) + fn := filepath.Join(t.TempDir(), "default-config.yaml") + err = os.WriteFile(fn, []byte(sfc.DebugDumpToYAML()), 0660) + require.NoError(t, err) + + // make sure it could be parsed: + fc, err := ReadFromFile(fn) + require.NoError(t, err) + + // validate a couple of values: + require.Equal(t, fc.Global.DataDir, defaults.DataDir) + require.Equal(t, fc.Logger.Severity, "INFO") + require.Equal(t, testCase.expectClusterName, fc.Auth.ClusterName) + require.Equal(t, testCase.expectLicenseFile, fc.Auth.LicenseFile) + require.Equal(t, testCase.expectProxyWebAddr, fc.Proxy.WebAddr) + require.ElementsMatch(t, testCase.expectProxyPublicAddrs, fc.Proxy.PublicAddr) + require.ElementsMatch(t, testCase.expectProxyKeyPairs, fc.Proxy.KeyPairs) + + require.False(t, lib.IsInsecureDevMode()) + }) + } } // TestBooleanParsing tests that boolean options diff --git a/lib/config/fileconf.go b/lib/config/fileconf.go index 1d562544a615f..b2bbdf622413a 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -18,6 +18,7 @@ package config import ( "bytes" + "crypto/tls" "encoding/base64" "fmt" "io" @@ -137,13 +138,28 @@ type SampleFlags struct { ACMEEnabled bool // Version is the Teleport Configuration version. Version string + // PublicAddrs sets the hostport the proxy advertises for the HTTP endpoint. + PublicAddrs []string + // KeyFile is a TLS key file + KeyFile string + // CertFile is a TLS Certificate file + CertFile string } // MakeSampleFileConfig returns a sample config to start // a standalone server func MakeSampleFileConfig(flags SampleFlags) (fc *FileConfig, err error) { - if flags.ACMEEnabled && flags.ClusterName == "" { - return nil, trace.BadParameter("please provide --cluster-name when using acme, for example --cluster-name=example.com") + if (flags.KeyFile == "") != (flags.CertFile == "") { // xor + return nil, trace.BadParameter("please provide both --key-file and --cert-file") + } + + if flags.ACMEEnabled { + if flags.ClusterName == "" { + return nil, trace.BadParameter("please provide --cluster-name when using ACME, for example --cluster-name=example.com") + } + if flags.CertFile != "" { + return nil, trace.BadParameter("could not use --key-file/--cert-file when ACME is enabled") + } } conf := service.MakeDefaultConfig() @@ -196,6 +212,19 @@ func MakeSampleFileConfig(flags SampleFlags) (fc *FileConfig, err error) { p.PublicAddr = apiutils.Strings{net.JoinHostPort(flags.ClusterName, fmt.Sprintf("%d", teleport.StandardHTTPSPort))} p.WebAddr = net.JoinHostPort(defaults.BindIP, fmt.Sprintf("%d", teleport.StandardHTTPSPort)) } + if len(flags.PublicAddrs) > 0 { + p.PublicAddr = apiutils.Strings(flags.PublicAddrs) + } + if flags.KeyFile != "" && flags.CertFile != "" { + if _, err := tls.LoadX509KeyPair(flags.CertFile, flags.KeyFile); err != nil { + return nil, trace.Wrap(err, "failed to load x509 key pair from --key-file and --cert-file") + } + + p.KeyPairs = append(p.KeyPairs, KeyPair{ + PrivateKey: flags.KeyFile, + Certificate: flags.CertFile, + }) + } fc = &FileConfig{ Version: flags.Version, diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 6bde79a9e0406..813eff37839f1 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -226,6 +226,9 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con "Email to receive updates from Letsencrypt.org.").StringVar(&dumpFlags.ACMEEmail) dump.Flag("test", "Path to a configuration file to test.").ExistingFileVar(&dumpFlags.testConfigFile) dump.Flag("version", "Teleport configuration version.").Default(defaults.TeleportConfigVersionV2).StringVar(&dumpFlags.Version) + dump.Flag("public-addr", "A list of public addresses that the proxy advertises for the HTTP endpoint.").StringsVar(&dumpFlags.PublicAddrs) + dump.Flag("cert-file", "Path to a TLS certificate file for the proxy.").ExistingFileVar(&dumpFlags.CertFile) + dump.Flag("key-file", "Path to a TLS key file for the proxy.").ExistingFileVar(&dumpFlags.KeyFile) // parse CLI commands+flags: command, err := app.Parse(options.Args) @@ -366,6 +369,20 @@ func onConfigDump(flags dumpFlags) error { flags.LicensePath = filepath.Join(defaults.DataDir, "license.pem") } + if flags.KeyFile != "" && !filepath.IsAbs(flags.KeyFile) { + flags.KeyFile, err = filepath.Abs(flags.KeyFile) + if err != nil { + return trace.BadParameter("could not find absolute path for --key-file %q", flags.KeyFile) + } + } + + if flags.CertFile != "" && !filepath.IsAbs(flags.CertFile) { + flags.CertFile, err = filepath.Abs(flags.CertFile) + if err != nil { + return trace.BadParameter("could not find absolute path for --cert-file %q", flags.CertFile) + } + } + sfc, err := config.MakeSampleFileConfig(flags.SampleFlags) if err != nil { return trace.Wrap(err)