diff --git a/lib/config/configuration.go b/lib/config/configuration.go index 24c6981994cf..3c7632bb3888 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -661,26 +661,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 da0407e85267..c4478eef8fc5 100644 --- a/lib/config/configuration_test.go +++ b/lib/config/configuration_test.go @@ -778,6 +778,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()) @@ -900,6 +903,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) @@ -912,6 +918,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 42af5d1755e5..32d19a42a780 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -803,7 +803,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 @@ -889,6 +889,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 all CA private key generation and diff --git a/lib/config/testdata_test.go b/lib/config/testdata_test.go index 1b50d6c6d3b0..86d799050a26 100644 --- a/lib/config/testdata_test.go +++ b/lib/config/testdata_test.go @@ -214,6 +214,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 = `