From a3469ecb04cdff0569f07b78f244d323a787644f Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 20 Dec 2022 18:44:34 +0000 Subject: [PATCH] Allow `cluster_networking_config` to have `defaults` origin (#19106) (#19474) This commit ensures that `cluster_networking_config` has the `teleport.dev/origin: defaults` label when no related field is set in the configuration file. Before this commit, `cluster_networking_config` would always have the `teleport.dev/origin: config-file`, even if no related field was set. --- lib/config/configuration.go | 35 ++++++----- lib/config/configuration_test.go | 100 +++++++++++++++++++++++++++++++ lib/config/fileconf.go | 26 +++++++- lib/config/testdata_test.go | 59 ++++++++++++++++++ 4 files changed, 203 insertions(+), 17 deletions(-) diff --git a/lib/config/configuration.go b/lib/config/configuration.go index e60893e6fb61..e718855a922f 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -605,26 +605,29 @@ func applyAuthConfig(fc *FileConfig, cfg *service.Config) error { return trace.Wrap(err) } - // Set cluster networking configuration from file configuration. - cfg.Auth.NetworkingConfig, err = types.NewClusterNetworkingConfigFromConfigFile(types.ClusterNetworkingConfigSpecV2{ - ClientIdleTimeout: fc.Auth.ClientIdleTimeout, - ClientIdleTimeoutMessage: fc.Auth.ClientIdleTimeoutMessage, - WebIdleTimeout: fc.Auth.WebIdleTimeout, - KeepAliveInterval: fc.Auth.KeepAliveInterval, - KeepAliveCountMax: fc.Auth.KeepAliveCountMax, - SessionControlTimeout: fc.Auth.SessionControlTimeout, - ProxyListenerMode: fc.Auth.ProxyListenerMode, - RoutingStrategy: fc.Auth.RoutingStrategy, - TunnelStrategy: fc.Auth.TunnelStrategy, - ProxyPingInterval: fc.Auth.ProxyPingInterval, - }) - if err != nil { - return trace.Wrap(err) + // Only override networking configuration if some of its fields are + // specified in file configuration. + if fc.Auth.hasCustomNetworkingConfig() { + cfg.Auth.NetworkingConfig, err = types.NewClusterNetworkingConfigFromConfigFile(types.ClusterNetworkingConfigSpecV2{ + ClientIdleTimeout: fc.Auth.ClientIdleTimeout, + ClientIdleTimeoutMessage: fc.Auth.ClientIdleTimeoutMessage, + WebIdleTimeout: fc.Auth.WebIdleTimeout, + KeepAliveInterval: fc.Auth.KeepAliveInterval, + KeepAliveCountMax: fc.Auth.KeepAliveCountMax, + SessionControlTimeout: fc.Auth.SessionControlTimeout, + ProxyListenerMode: fc.Auth.ProxyListenerMode, + RoutingStrategy: fc.Auth.RoutingStrategy, + TunnelStrategy: fc.Auth.TunnelStrategy, + ProxyPingInterval: fc.Auth.ProxyPingInterval, + }) + if err != nil { + return trace.Wrap(err) + } } // Only override session recording configuration if either field is // specified in file configuration. - if fc.Auth.SessionRecording != "" || fc.Auth.ProxyChecksHostKeys != nil { + if fc.Auth.hasCustomSessionRecording() { cfg.Auth.SessionRecordingConfig, err = types.NewSessionRecordingConfigFromConfigFile(types.SessionRecordingConfigSpecV2{ Mode: fc.Auth.SessionRecording, ProxyChecksHostKeys: fc.Auth.ProxyChecksHostKeys, diff --git a/lib/config/configuration_test.go b/lib/config/configuration_test.go index 8a2c1090224a..324be7b7b6f9 100644 --- a/lib/config/configuration_test.go +++ b/lib/config/configuration_test.go @@ -743,6 +743,9 @@ func TestApplyConfig(t *testing.T) { require.NoError(t, err) require.Equal(t, types.AgentMesh, tunnelStrategyType) require.Equal(t, types.DefaultAgentMeshTunnelStrategy(), cfg.Auth.NetworkingConfig.GetAgentMeshTunnelStrategy()) + require.Equal(t, types.OriginConfigFile, cfg.Auth.Preference.Origin()) + require.Equal(t, types.OriginDefaults, cfg.Auth.NetworkingConfig.Origin()) + require.Equal(t, types.OriginDefaults, cfg.Auth.SessionRecordingConfig.Origin()) require.True(t, cfg.Proxy.Enabled) require.Equal(t, "tcp://webhost:3080", cfg.Proxy.WebAddr.FullAddress()) @@ -847,6 +850,9 @@ func TestApplyConfigNoneEnabled(t *testing.T) { require.False(t, cfg.Auth.Enabled) require.Empty(t, cfg.Auth.PublicAddrs) + require.Equal(t, types.OriginDefaults, cfg.Auth.Preference.Origin()) + require.Equal(t, types.OriginDefaults, cfg.Auth.NetworkingConfig.Origin()) + require.Equal(t, types.OriginDefaults, cfg.Auth.SessionRecordingConfig.Origin()) require.False(t, cfg.Proxy.Enabled) require.Empty(t, cfg.Proxy.PublicAddrs) require.False(t, cfg.SSH.Enabled) @@ -859,6 +865,100 @@ func TestApplyConfigNoneEnabled(t *testing.T) { require.Empty(t, cfg.Proxy.MySQLPublicAddrs) } +// TestApplyConfigNoneEnabled makes sure that if the auth file configuration +// does not have `cluster_auth_preference`, `cluster_networking_config` and +// `session_recording` fields, then they all have the origin label as defaults. +func TestApplyDefaultAuthResources(t *testing.T) { + conf, err := ReadConfig(bytes.NewBufferString(DefaultAuthResourcesConfigString)) + require.NoError(t, err) + + cfg := service.MakeDefaultConfig() + err = ApplyFileConfig(conf, cfg) + require.NoError(t, err) + + require.True(t, cfg.Auth.Enabled) + require.Equal(t, "example.com", cfg.Auth.ClusterName.GetClusterName()) + require.Equal(t, types.OriginDefaults, cfg.Auth.Preference.Origin()) + require.Equal(t, types.OriginDefaults, cfg.Auth.NetworkingConfig.Origin()) + require.Equal(t, types.OriginDefaults, cfg.Auth.SessionRecordingConfig.Origin()) +} + +// TestApplyCustomAuthPreference makes sure that if the auth file configuration +// has a `cluster_auth_preference` field, then it will have the origin label as +// config-file. +func TestApplyCustomAuthPreference(t *testing.T) { + conf, err := ReadConfig(bytes.NewBufferString(CustomAuthPreferenceConfigString)) + require.NoError(t, err) + + cfg := service.MakeDefaultConfig() + err = ApplyFileConfig(conf, cfg) + require.NoError(t, err) + + require.True(t, cfg.Auth.Enabled) + require.Equal(t, "example.com", cfg.Auth.ClusterName.GetClusterName()) + require.Equal(t, types.OriginConfigFile, cfg.Auth.Preference.Origin()) + require.True(t, cfg.Auth.Preference.GetDisconnectExpiredCert()) + require.Equal(t, types.OriginDefaults, cfg.Auth.NetworkingConfig.Origin()) + require.Equal(t, types.OriginDefaults, cfg.Auth.SessionRecordingConfig.Origin()) +} + +// TestApplyCustomAuthPreferenceWithMOTD makes sure that if the auth file configuration +// has only the `message_of_the_day` `cluster_auth_preference` field, then it will have +// the origin label as defaults (instead of config-file). +func TestApplyCustomAuthPreferenceWithMOTD(t *testing.T) { + conf, err := ReadConfig(bytes.NewBufferString(AuthPreferenceConfigWithMOTDString)) + require.NoError(t, err) + + cfg := service.MakeDefaultConfig() + err = ApplyFileConfig(conf, cfg) + require.NoError(t, err) + + require.True(t, cfg.Auth.Enabled) + require.Equal(t, "example.com", cfg.Auth.ClusterName.GetClusterName()) + require.Equal(t, types.OriginDefaults, cfg.Auth.Preference.Origin()) + require.Equal(t, "welcome!", cfg.Auth.Preference.GetMessageOfTheDay()) + require.Equal(t, types.OriginDefaults, cfg.Auth.NetworkingConfig.Origin()) + require.Equal(t, types.OriginDefaults, cfg.Auth.SessionRecordingConfig.Origin()) +} + +// TestApplyCustomNetworkingConfig makes sure that if the auth file configuration +// has a `cluster_networking_config` field, then it will have the origin label as +// config-file. +func TestApplyCustomNetworkingConfig(t *testing.T) { + conf, err := ReadConfig(bytes.NewBufferString(CustomNetworkingConfigString)) + require.NoError(t, err) + + cfg := service.MakeDefaultConfig() + err = ApplyFileConfig(conf, cfg) + require.NoError(t, err) + + require.True(t, cfg.Auth.Enabled) + require.Equal(t, "example.com", cfg.Auth.ClusterName.GetClusterName()) + require.Equal(t, types.OriginDefaults, cfg.Auth.Preference.Origin()) + require.Equal(t, types.OriginConfigFile, cfg.Auth.NetworkingConfig.Origin()) + require.Equal(t, 10*time.Second, cfg.Auth.NetworkingConfig.GetWebIdleTimeout()) + require.Equal(t, types.OriginDefaults, cfg.Auth.SessionRecordingConfig.Origin()) +} + +// TestApplyCustomSessionRecordingConfig makes sure that if the auth file configuration +// has a `session_recording` field, then it will have the origin label as +// config-file. +func TestApplyCustomSessionRecordingConfig(t *testing.T) { + conf, err := ReadConfig(bytes.NewBufferString(CustomSessionRecordingConfigString)) + require.NoError(t, err) + + cfg := service.MakeDefaultConfig() + err = ApplyFileConfig(conf, cfg) + require.NoError(t, err) + + require.True(t, cfg.Auth.Enabled) + require.Equal(t, cfg.Auth.ClusterName.GetClusterName(), "example.com") + require.Equal(t, types.OriginDefaults, cfg.Auth.Preference.Origin()) + require.Equal(t, types.OriginDefaults, cfg.Auth.NetworkingConfig.Origin()) + require.Equal(t, types.OriginConfigFile, cfg.Auth.SessionRecordingConfig.Origin()) + require.True(t, cfg.Auth.SessionRecordingConfig.GetProxyChecksHostKeys()) +} + // TestPostgresPublicAddr makes sure Postgres proxy public address default // port logic works correctly. func TestPostgresPublicAddr(t *testing.T) { diff --git a/lib/config/fileconf.go b/lib/config/fileconf.go index 9cf244b04841..e69c4cce87ce 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -684,7 +684,7 @@ type Auth struct { // environments where paranoid security is not needed // // Each token string has the following format: "role1,role2,..:token", - // for exmple: "auth,proxy,node:MTIzNGlvemRmOWE4MjNoaQo" + // for example: "auth,proxy,node:MTIzNGlvemRmOWE4MjNoaQo" StaticTokens StaticTokens `yaml:"tokens,omitempty"` // Authentication holds authentication configuration information like authentication @@ -770,6 +770,30 @@ type Auth struct { LoadAllCAs bool `yaml:"load_all_cas,omitempty"` } +// hasCustomNetworkingConfig returns true if any of the networking +// configuration fields have values different from an empty Auth. +func (a *Auth) hasCustomNetworkingConfig() bool { + empty := Auth{} + return a.ClientIdleTimeout != empty.ClientIdleTimeout || + a.ClientIdleTimeoutMessage != empty.ClientIdleTimeoutMessage || + a.WebIdleTimeout != empty.WebIdleTimeout || + a.KeepAliveInterval != empty.KeepAliveInterval || + a.KeepAliveCountMax != empty.KeepAliveCountMax || + a.SessionControlTimeout != empty.SessionControlTimeout || + a.ProxyListenerMode != empty.ProxyListenerMode || + a.RoutingStrategy != empty.RoutingStrategy || + a.TunnelStrategy != empty.TunnelStrategy || + a.ProxyPingInterval != empty.ProxyPingInterval +} + +// hasCustomSessionRecording returns true if any of the session recording +// configuration fields have values different from an empty Auth. +func (a *Auth) hasCustomSessionRecording() bool { + empty := Auth{} + return a.SessionRecording != empty.SessionRecording || + a.ProxyChecksHostKeys != empty.ProxyChecksHostKeys +} + // CAKeyParams configures how CA private keys will be created and stored. type CAKeyParams struct { // PKCS11 configures a PKCS#11 HSM to be used for private key generation and diff --git a/lib/config/testdata_test.go b/lib/config/testdata_test.go index ddc895c74fd1..ba74624f7a8b 100644 --- a/lib/config/testdata_test.go +++ b/lib/config/testdata_test.go @@ -205,6 +205,65 @@ app_service: enabled: no ` +// DefaultAuthResourcesConfigString is a configuration file without +// `cluster_auth_preference`, `cluster_networking_config` and `session_recording` fields. +const DefaultAuthResourcesConfigString = ` +teleport: + nodename: node.example.com + +auth_service: + enabled: yes + cluster_name: "example.com" +` + +// CustomAuthPreferenceConfigString is a configuration file with a single +// `cluster_auth_preference` field. +const CustomAuthPreferenceConfigString = ` +teleport: + nodename: node.example.com + +auth_service: + enabled: yes + cluster_name: "example.com" + disconnect_expired_cert: true +` + +// AuthPreferenceConfigWithMOTDString is a configuration file with the +// `message_of_the_day` `cluster_auth_preference` field. +const AuthPreferenceConfigWithMOTDString = ` +teleport: + nodename: node.example.com + +auth_service: + enabled: yes + cluster_name: "example.com" + message_of_the_day: "welcome!" +` + +// CustomNetworkingConfigString is a configuration file with a single +// `cluster_networking_config` field. +const CustomNetworkingConfigString = ` +teleport: + nodename: node.example.com + +auth_service: + enabled: yes + cluster_name: "example.com" + web_idle_timeout: 10s +` + +// CustomSessionRecordingConfigString is a configuration file with a single +// `session_recording` field. +const CustomSessionRecordingConfigString = ` +teleport: + nodename: node.example.com + +auth_service: + enabled: yes + cluster_name: "example.com" + proxy_checks_host_keys: true +` + // configWithFIPSKex is a configuration file with a FIPS compliant KEX // algorithm. const configWithFIPSKex = `