From 72459fbea9fd124e7d716812198ebc55dcb14866 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Mon, 7 Feb 2022 15:21:09 -0800 Subject: [PATCH 01/43] Add http proxy to web clients --- api/client/webclient/webclient.go | 1 + lib/client/https_client.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/api/client/webclient/webclient.go b/api/client/webclient/webclient.go index b82e16f7ac9fe..d877609c04240 100644 --- a/api/client/webclient/webclient.go +++ b/api/client/webclient/webclient.go @@ -46,6 +46,7 @@ func newWebClient(insecure bool, pool *x509.CertPool) *http.Client { RootCAs: pool, InsecureSkipVerify: insecure, }, + Proxy: http.ProxyFromEnvironment, }, } } diff --git a/lib/client/https_client.go b/lib/client/https_client.go index 4d1c94807dcec..8a95b3cf47497 100644 --- a/lib/client/https_client.go +++ b/lib/client/https_client.go @@ -41,6 +41,7 @@ func NewInsecureWebClient() *http.Client { return &http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsConfig, + Proxy: http.ProxyFromEnvironment, }, } } @@ -54,6 +55,7 @@ func newClientWithPool(pool *x509.CertPool) *http.Client { return &http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsConfig, + Proxy: http.ProxyFromEnvironment, }, } } From 05fa8b509e151ce67fe0bd70748a23d0ad627270 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 8 Feb 2022 12:45:15 -0800 Subject: [PATCH 02/43] Add tests --- api/client/webclient/webclient_test.go | 10 ++++++ lib/client/https_client_test.go | 42 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 lib/client/https_client_test.go diff --git a/api/client/webclient/webclient_test.go b/api/client/webclient/webclient_test.go index 556d6cab96478..d3c9ba34fee70 100644 --- a/api/client/webclient/webclient_test.go +++ b/api/client/webclient/webclient_test.go @@ -22,6 +22,7 @@ import ( "net" "net/http" "net/http/httptest" + "os" "testing" "github.com/stretchr/testify/require" @@ -289,3 +290,12 @@ func TestExtract(t *testing.T) { }) } } + +func TestNewWebClientRespectHTTPProxy(t *testing.T) { + os.Setenv("HTTPS_PROXY", "localhost:9999") + defer os.Unsetenv("HTTPS_PROXY") + client := newWebClient(false, nil) + _, err := client.Get("https://example.com") + // Client should try to proxy through nonexistent server at localhost. + require.Error(t, err) +} diff --git a/lib/client/https_client_test.go b/lib/client/https_client_test.go new file mode 100644 index 0000000000000..96bf5d1036117 --- /dev/null +++ b/lib/client/https_client_test.go @@ -0,0 +1,42 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewInsecureWebClientHTTPProxy(t *testing.T) { + os.Setenv("HTTPS_PROXY", "localhost:9999") + defer os.Unsetenv("HTTPS_PROXY") + client := NewInsecureWebClient() + _, err := client.Get("https://example.com") + // Client should try to proxy through nonexistent server at localhost. + require.Error(t, err) +} + +func TestNewClientWithPoolHTTPProxy(t *testing.T) { + os.Setenv("HTTPS_PROXY", "localhost:9999") + defer os.Unsetenv("HTTPS_PROXY") + client := newClientWithPool(nil) + _, err := client.Get("https://example.com") + // Client should try to proxy through nonexistent server at localhost. + require.Error(t, err) +} From 37cd7861908e5391ca10933327ccf25a60bc71f0 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 8 Feb 2022 16:24:13 -0800 Subject: [PATCH 03/43] Reduce arg ambiguity --- api/client/webclient/webclient_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/client/webclient/webclient_test.go b/api/client/webclient/webclient_test.go index d3c9ba34fee70..5075fda39a292 100644 --- a/api/client/webclient/webclient_test.go +++ b/api/client/webclient/webclient_test.go @@ -294,7 +294,7 @@ func TestExtract(t *testing.T) { func TestNewWebClientRespectHTTPProxy(t *testing.T) { os.Setenv("HTTPS_PROXY", "localhost:9999") defer os.Unsetenv("HTTPS_PROXY") - client := newWebClient(false, nil) + client := newWebClient(false /* insecure */, nil /* pool */) _, err := client.Get("https://example.com") // Client should try to proxy through nonexistent server at localhost. require.Error(t, err) From e2df5954be28ca435fd3a5b03c7f9c5e87660db4 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 8 Feb 2022 16:41:08 -0800 Subject: [PATCH 04/43] Make error assertions more specific --- api/client/webclient/webclient_test.go | 2 ++ lib/client/https_client_test.go | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/api/client/webclient/webclient_test.go b/api/client/webclient/webclient_test.go index 5075fda39a292..ae3b78232687f 100644 --- a/api/client/webclient/webclient_test.go +++ b/api/client/webclient/webclient_test.go @@ -298,4 +298,6 @@ func TestNewWebClientRespectHTTPProxy(t *testing.T) { _, err := client.Get("https://example.com") // Client should try to proxy through nonexistent server at localhost. require.Error(t, err) + require.Contains(t, err.Error(), "proxyconnect") + require.Contains(t, err.Error(), "connection refused") } diff --git a/lib/client/https_client_test.go b/lib/client/https_client_test.go index 96bf5d1036117..e8ba11315392d 100644 --- a/lib/client/https_client_test.go +++ b/lib/client/https_client_test.go @@ -30,6 +30,8 @@ func TestNewInsecureWebClientHTTPProxy(t *testing.T) { _, err := client.Get("https://example.com") // Client should try to proxy through nonexistent server at localhost. require.Error(t, err) + require.Contains(t, err.Error(), "proxyconnect") + require.Contains(t, err.Error(), "connection refused") } func TestNewClientWithPoolHTTPProxy(t *testing.T) { @@ -39,4 +41,6 @@ func TestNewClientWithPoolHTTPProxy(t *testing.T) { _, err := client.Get("https://example.com") // Client should try to proxy through nonexistent server at localhost. require.Error(t, err) + require.Contains(t, err.Error(), "proxyconnect") + require.Contains(t, err.Error(), "connection refused") } From 8281dfc6731dddb5a2f6a0a751cc01d71d0d042b Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Wed, 9 Feb 2022 12:13:53 -0800 Subject: [PATCH 05/43] Fix linting --- api/client/webclient/webclient_test.go | 11 +++++++---- lib/client/https_client_test.go | 21 ++++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/api/client/webclient/webclient_test.go b/api/client/webclient/webclient_test.go index ae3b78232687f..f5b36f08596ea 100644 --- a/api/client/webclient/webclient_test.go +++ b/api/client/webclient/webclient_test.go @@ -22,7 +22,6 @@ import ( "net" "net/http" "net/http/httptest" - "os" "testing" "github.com/stretchr/testify/require" @@ -292,10 +291,14 @@ func TestExtract(t *testing.T) { } func TestNewWebClientRespectHTTPProxy(t *testing.T) { - os.Setenv("HTTPS_PROXY", "localhost:9999") - defer os.Unsetenv("HTTPS_PROXY") + t.Setenv("HTTPS_PROXY", "localhost:9999") client := newWebClient(false /* insecure */, nil /* pool */) - _, err := client.Get("https://example.com") + resp, err := client.Get("https://example.com") + defer func() { + if resp != nil { + resp.Body.Close() + } + }() // Client should try to proxy through nonexistent server at localhost. require.Error(t, err) require.Contains(t, err.Error(), "proxyconnect") diff --git a/lib/client/https_client_test.go b/lib/client/https_client_test.go index e8ba11315392d..446695287c9c4 100644 --- a/lib/client/https_client_test.go +++ b/lib/client/https_client_test.go @@ -17,17 +17,20 @@ limitations under the License. package client import ( - "os" "testing" "github.com/stretchr/testify/require" ) func TestNewInsecureWebClientHTTPProxy(t *testing.T) { - os.Setenv("HTTPS_PROXY", "localhost:9999") - defer os.Unsetenv("HTTPS_PROXY") + t.Setenv("HTTPS_PROXY", "localhost:9999") client := NewInsecureWebClient() - _, err := client.Get("https://example.com") + resp, err := client.Get("https://example.com") + defer func() { + if resp != nil { + resp.Body.Close() + } + }() // Client should try to proxy through nonexistent server at localhost. require.Error(t, err) require.Contains(t, err.Error(), "proxyconnect") @@ -35,10 +38,14 @@ func TestNewInsecureWebClientHTTPProxy(t *testing.T) { } func TestNewClientWithPoolHTTPProxy(t *testing.T) { - os.Setenv("HTTPS_PROXY", "localhost:9999") - defer os.Unsetenv("HTTPS_PROXY") + t.Setenv("HTTPS_PROXY", "localhost:9999") client := newClientWithPool(nil) - _, err := client.Get("https://example.com") + resp, err := client.Get("https://example.com") + defer func() { + if resp != nil { + resp.Body.Close() + } + }() // Client should try to proxy through nonexistent server at localhost. require.Error(t, err) require.Contains(t, err.Error(), "proxyconnect") From 700abc27d0e6047980e9d96e90c81a3aa88e6747 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Wed, 9 Feb 2022 12:41:49 -0800 Subject: [PATCH 06/43] Add messages to assertions --- api/client/webclient/webclient_test.go | 2 +- lib/client/https_client_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/client/webclient/webclient_test.go b/api/client/webclient/webclient_test.go index f5b36f08596ea..75fe4bcb8f584 100644 --- a/api/client/webclient/webclient_test.go +++ b/api/client/webclient/webclient_test.go @@ -300,7 +300,7 @@ func TestNewWebClientRespectHTTPProxy(t *testing.T) { } }() // Client should try to proxy through nonexistent server at localhost. - require.Error(t, err) + require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") require.Contains(t, err.Error(), "connection refused") } diff --git a/lib/client/https_client_test.go b/lib/client/https_client_test.go index 446695287c9c4..3fac6f0440d3b 100644 --- a/lib/client/https_client_test.go +++ b/lib/client/https_client_test.go @@ -32,7 +32,7 @@ func TestNewInsecureWebClientHTTPProxy(t *testing.T) { } }() // Client should try to proxy through nonexistent server at localhost. - require.Error(t, err) + require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") require.Contains(t, err.Error(), "connection refused") } @@ -47,7 +47,7 @@ func TestNewClientWithPoolHTTPProxy(t *testing.T) { } }() // Client should try to proxy through nonexistent server at localhost. - require.Error(t, err) + require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") require.Contains(t, err.Error(), "connection refused") } From d41122a6f20e90c306f6d4ce2b83d573174f3f8d Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Wed, 9 Feb 2022 13:22:01 -0800 Subject: [PATCH 07/43] Change fake proxy address in tests --- api/client/webclient/webclient_test.go | 4 ++-- lib/client/https_client_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/client/webclient/webclient_test.go b/api/client/webclient/webclient_test.go index 75fe4bcb8f584..d7148542d7988 100644 --- a/api/client/webclient/webclient_test.go +++ b/api/client/webclient/webclient_test.go @@ -291,7 +291,7 @@ func TestExtract(t *testing.T) { } func TestNewWebClientRespectHTTPProxy(t *testing.T) { - t.Setenv("HTTPS_PROXY", "localhost:9999") + t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") client := newWebClient(false /* insecure */, nil /* pool */) resp, err := client.Get("https://example.com") defer func() { @@ -302,5 +302,5 @@ func TestNewWebClientRespectHTTPProxy(t *testing.T) { // Client should try to proxy through nonexistent server at localhost. require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") - require.Contains(t, err.Error(), "connection refused") + require.Contains(t, err.Error(), "no such host") } diff --git a/lib/client/https_client_test.go b/lib/client/https_client_test.go index 3fac6f0440d3b..2bb93cb88396f 100644 --- a/lib/client/https_client_test.go +++ b/lib/client/https_client_test.go @@ -23,7 +23,7 @@ import ( ) func TestNewInsecureWebClientHTTPProxy(t *testing.T) { - t.Setenv("HTTPS_PROXY", "localhost:9999") + t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") client := NewInsecureWebClient() resp, err := client.Get("https://example.com") defer func() { @@ -34,11 +34,11 @@ func TestNewInsecureWebClientHTTPProxy(t *testing.T) { // Client should try to proxy through nonexistent server at localhost. require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") - require.Contains(t, err.Error(), "connection refused") + require.Contains(t, err.Error(), "no such host") } func TestNewClientWithPoolHTTPProxy(t *testing.T) { - t.Setenv("HTTPS_PROXY", "localhost:9999") + t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") client := newClientWithPool(nil) resp, err := client.Get("https://example.com") defer func() { @@ -49,5 +49,5 @@ func TestNewClientWithPoolHTTPProxy(t *testing.T) { // Client should try to proxy through nonexistent server at localhost. require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") - require.Contains(t, err.Error(), "connection refused") + require.Contains(t, err.Error(), "no such host") } From d225e71efce9f310eeb5962e9794682f1e20a5f7 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Thu, 10 Feb 2022 15:07:01 -0800 Subject: [PATCH 08/43] Add http proxy support for ssh --- lib/client/api.go | 40 +++++++-------------- lib/utils/proxy/proxy.go | 78 ++++++++++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/lib/client/api.go b/lib/client/api.go index dadf89ae0e3c7..6a5a6a9db97f4 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -68,6 +68,7 @@ import ( "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/agentconn" + "github.com/gravitational/teleport/lib/utils/proxy" "github.com/gravitational/trace" @@ -2198,37 +2199,20 @@ func (tc *TeleportClient) connectToProxy(ctx context.Context) (*ProxyClient, err }, nil } -func makeProxySSHClientWithTLSWrapper(tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.Client, error) { - cfg := tc.Config - clientTLSConf, err := tc.loadTLSConfig() - if err != nil { - return nil, trace.Wrap(err) - } - - clientTLSConf.NextProtos = []string{string(alpncommon.ProtocolProxySSH)} - clientTLSConf.InsecureSkipVerify = cfg.InsecureSkipVerify - - tlsConn, err := tls.Dial("tcp", cfg.WebProxyAddr, clientTLSConf) - if err != nil { - return nil, trace.Wrap(err, "failed to dial tls %v", cfg.WebProxyAddr) - } - c, chans, reqs, err := ssh.NewClientConn(tlsConn, cfg.WebProxyAddr, sshConfig) - if err != nil { - // tlsConn is closed inside ssh.NewClientConn function - return nil, trace.Wrap(err, "failed to authenticate with proxy %v", cfg.WebProxyAddr) - } - return ssh.NewClient(c, chans, reqs), nil -} - func makeProxySSHClient(tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.Client, error) { + var opts []proxy.DialerOptionFunc if tc.Config.TLSRoutingEnabled { - return makeProxySSHClientWithTLSWrapper(tc, sshConfig) - } - client, err := ssh.Dial("tcp", tc.Config.SSHProxyAddr, sshConfig) - if err != nil { - return nil, trace.Wrap(err, "failed to authenticate with proxy %v", tc.Config.SSHProxyAddr) + tlsConfig, err := tc.loadTLSConfig() + if err != nil { + return nil, trace.Wrap(err) + } + tlsConfig.NextProtos = []string{string(alpncommon.ProtocolProxySSH)} + tlsConfig.InsecureSkipVerify = tc.Config.InsecureSkipVerify + opts = append(opts, proxy.WithALPNDialer(), proxy.WithTLSConfig(tlsConfig)) } - return client, nil + dialer := proxy.DialerFromEnvironment(tc.Config.WebProxyAddr, opts...) + client, err := dialer.Dial("tcp", tc.Config.SSHProxyAddr, sshConfig) + return client, trace.Wrap(err, "failed to authenticate with proxy %v", tc.Config.SSHProxyAddr) } func (tc *TeleportClient) rootClusterName() (string, error) { diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index 9ef5d2ac57fee..6aba7c404287f 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -57,7 +57,7 @@ func dialWithDeadline(network string, addr string, config *ssh.ClientConfig) (*s // dialALPNWithDeadline allows connecting to Teleport in single-port mode. SSH protocol is wrapped into // TLS connection where TLS ALPN protocol is set to ProtocolReverseTunnel allowing ALPN Proxy to route the // incoming connection to ReverseTunnel proxy service. -func dialALPNWithDeadline(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { +func dialALPNWithDeadline(network string, addr string, config *ssh.ClientConfig, tlsConfig *tls.Config) (*ssh.Client, error) { dialer := &net.Dialer{ Timeout: config.Timeout, } @@ -65,11 +65,15 @@ func dialALPNWithDeadline(network string, addr string, config *ssh.ClientConfig) if err != nil { return nil, trace.Wrap(err) } - tlsConn, err := tls.DialWithDialer(dialer, network, addr, &tls.Config{ - NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, - InsecureSkipVerify: lib.IsInsecureDevMode(), - ServerName: address.Host(), - }) + conf := tlsConfig.Clone() + if conf == nil { + conf = &tls.Config{ + NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, + InsecureSkipVerify: lib.IsInsecureDevMode(), + } + } + conf.ServerName = address.Host() + tlsConn, err := tls.DialWithDialer(dialer, network, addr, conf) if err != nil { return nil, trace.Wrap(err) } @@ -86,13 +90,14 @@ type Dialer interface { } type directDial struct { - useTLS bool + useTLS bool + tlsConfig *tls.Config } // Dial calls ssh.Dial directly. func (d directDial) Dial(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { if d.useTLS { - client, err := dialALPNWithDeadline(network, addr, config) + client, err := dialALPNWithDeadline(network, addr, config, d.tlsConfig) if err != nil { return nil, trace.Wrap(err) } @@ -112,11 +117,15 @@ func (d directDial) DialTimeout(network, address string, timeout time.Duration) if err != nil { return nil, trace.Wrap(err) } - tlsConn, err := tls.Dial("tcp", address, &tls.Config{ - NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, - InsecureSkipVerify: lib.IsInsecureDevMode(), - ServerName: addr.Host(), - }) + conf := d.tlsConfig.Clone() + if conf == nil { + conf = &tls.Config{ + NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, + InsecureSkipVerify: lib.IsInsecureDevMode(), + } + } + conf.ServerName = addr.Host() + tlsConn, err := tls.Dial("tcp", address, conf) if err != nil { return nil, trace.Wrap(err) } @@ -132,6 +141,7 @@ func (d directDial) DialTimeout(network, address string, timeout time.Duration) type proxyDial struct { proxyHost string useTLS bool + tlsConfig *tls.Config } // DialTimeout acts like Dial but takes a timeout. @@ -152,11 +162,15 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( if err != nil { return nil, trace.Wrap(err) } - conn = tls.Client(conn, &tls.Config{ - NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, - InsecureSkipVerify: lib.IsInsecureDevMode(), - ServerName: address.Host(), - }) + conf := d.tlsConfig.Clone() + if conf == nil { + conf = &tls.Config{ + NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, + InsecureSkipVerify: lib.IsInsecureDevMode(), + } + } + conf.ServerName = address.Host() + conn = tls.Client(conn, conf) } return conn, nil } @@ -177,11 +191,15 @@ func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) ( if err != nil { return nil, trace.Wrap(err) } - pconn = tls.Client(pconn, &tls.Config{ - NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, - InsecureSkipVerify: lib.IsInsecureDevMode(), - ServerName: address.Host(), - }) + conf := d.tlsConfig.Clone() + if conf == nil { + conf = &tls.Config{ + NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, + InsecureSkipVerify: lib.IsInsecureDevMode(), + } + } + conf.ServerName = address.Host() + pconn = tls.Client(pconn, conf) } // Do the same as ssh.Dial but pass in proxy connection. @@ -196,7 +214,8 @@ func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) ( } type dialerOptions struct { - dialTLS bool + dialTLS bool + tlsConfig *tls.Config } // DialerOptionFunc allows setting options as functional arguments to DialerFromEnvironment @@ -209,6 +228,13 @@ func WithALPNDialer() DialerOptionFunc { } } +// WithTLSConfig creates a dialer that uses a specific tls config. +func WithTLSConfig(tlsConfig *tls.Config) DialerOptionFunc { + return func(options *dialerOptions) { + options.tlsConfig = tlsConfig + } +} + // DialerFromEnvironment returns a Dial function. If the https_proxy or http_proxy // environment variable are set, it returns a function that will dial through // said proxy server. If neither variable is set, it will connect to the SSH @@ -226,10 +252,10 @@ func DialerFromEnvironment(addr string, opts ...DialerOptionFunc) Dialer { // otherwise return a proxy dialer. if proxyAddr == "" { log.Debugf("No proxy set in environment, returning direct dialer.") - return directDial{useTLS: options.dialTLS} + return directDial{useTLS: options.dialTLS, tlsConfig: options.tlsConfig} } log.Debugf("Found proxy %q in environment, returning proxy dialer.", proxyAddr) - return proxyDial{proxyHost: proxyAddr, useTLS: options.dialTLS} + return proxyDial{proxyHost: proxyAddr, useTLS: options.dialTLS, tlsConfig: options.tlsConfig} } type DirectDialerOptFunc func(dial *directDial) From 810d958b90744e2101f77af63e6252f75301add0 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 15 Feb 2022 13:40:30 -0800 Subject: [PATCH 09/43] Move proxy utils to api --- {lib => api}/utils/proxy/noproxy.go | 0 api/utils/proxy/proxy.go | 144 +++++++++++++++++++++++++ {lib => api}/utils/proxy/proxy_test.go | 2 +- lib/utils/proxy/proxy.go | 125 +-------------------- 4 files changed, 149 insertions(+), 122 deletions(-) rename {lib => api}/utils/proxy/noproxy.go (100%) create mode 100644 api/utils/proxy/proxy.go rename {lib => api}/utils/proxy/proxy_test.go (98%) diff --git a/lib/utils/proxy/noproxy.go b/api/utils/proxy/noproxy.go similarity index 100% rename from lib/utils/proxy/noproxy.go rename to api/utils/proxy/noproxy.go diff --git a/api/utils/proxy/proxy.go b/api/utils/proxy/proxy.go new file mode 100644 index 0000000000000..631be761fd70a --- /dev/null +++ b/api/utils/proxy/proxy.go @@ -0,0 +1,144 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxy + +import ( + "bufio" + "context" + "net" + "net/http" + "net/url" + "os" + "strings" + + "github.com/gravitational/teleport" + "github.com/gravitational/trace" + "github.com/siddontang/go/log" +) + +func DialProxy(ctx context.Context, proxyAddr string, addr string) (net.Conn, error) { + var d net.Dialer + conn, err := d.DialContext(ctx, "tcp", proxyAddr) + if err != nil { + log.Warnf("Unable to dial to proxy: %v: %v.", proxyAddr, err) + return nil, trace.ConvertSystemError(err) + } + + connectReq := &http.Request{ + Method: http.MethodConnect, + URL: &url.URL{Opaque: addr}, + Host: addr, + Header: make(http.Header), + } + err = connectReq.Write(conn) + if err != nil { + log.Warnf("Unable to write to proxy: %v.", err) + return nil, trace.Wrap(err) + } + + // Read in the response. http.ReadResponse will read in the status line, mime + // headers, and potentially part of the response body. the body itself will + // not be read, but kept around so it can be read later. + br := bufio.NewReader(conn) + // Per the above comment, we're only using ReadResponse to check the status + // and then hand off the underlying connection to the caller. + // resp.Body.Close() would drain conn and close it, we don't need to do it + // here. Disabling bodyclose linter for this edge case. + //nolint:bodyclose + resp, err := http.ReadResponse(br, connectReq) + if err != nil { + conn.Close() + log.Warnf("Unable to read response: %v.", err) + return nil, trace.Wrap(err) + } + if resp.StatusCode != http.StatusOK { + conn.Close() + return nil, trace.BadParameter("unable to proxy connection: %v", resp.Status) + } + + // Return a bufferedConn that wraps a net.Conn and a *bufio.Reader. this + // needs to be done because http.ReadResponse will buffer part of the + // response body in the *bufio.Reader that was passed in. reads must first + // come from anything buffered, then from the underlying connection otherwise + // data will be lost. + return &bufferedConn{ + Conn: conn, + reader: br, + }, nil +} + +func GetProxyAddress(addr string) string { + envs := []string{ + teleport.HTTPSProxy, + strings.ToLower(teleport.HTTPSProxy), + teleport.HTTPProxy, + strings.ToLower(teleport.HTTPProxy), + } + + for _, v := range envs { + envAddr := os.Getenv(v) + if envAddr == "" { + continue + } + proxyAddr, err := parse(envAddr) + if err != nil { + log.Debugf("Unable to parse environment variable %q: %q.", v, envAddr) + continue + } + log.Debugf("Successfully parsed environment variable %q: %q to %q.", v, envAddr, proxyAddr) + if !useProxy(addr) { + log.Debugf("Matched NO_PROXY override for %q: %q, going to ignore proxy variable.", v, envAddr) + return "" + } + return proxyAddr + } + + log.Debugf("No valid environment variables found.") + return "" +} + +// bufferedConn is used when part of the data on a connection has already been +// read by a *bufio.Reader. Reads will first try and read from the +// *bufio.Reader and when everything has been read, reads will go to the +// underlying connection. +type bufferedConn struct { + net.Conn + reader *bufio.Reader +} + +// Read first reads from the *bufio.Reader any data that has already been +// buffered. Once all buffered data has been read, reads go to the net.Conn. +func (bc *bufferedConn) Read(b []byte) (n int, err error) { + if bc.reader.Buffered() > 0 { + return bc.reader.Read(b) + } + return bc.Conn.Read(b) +} + +// parse will extract the host:port of the proxy to dial to. If the +// value is not prefixed by "http", then it will prepend "http" and try. +func parse(addr string) (string, error) { + proxyurl, err := url.Parse(addr) + if err != nil || !strings.HasPrefix(proxyurl.Scheme, "http") { + proxyurl, err = url.Parse("http://" + addr) + if err != nil { + return "", trace.Wrap(err) + } + } + + return proxyurl.Host, nil +} diff --git a/lib/utils/proxy/proxy_test.go b/api/utils/proxy/proxy_test.go similarity index 98% rename from lib/utils/proxy/proxy_test.go rename to api/utils/proxy/proxy_test.go index a5c986acd89c5..1067c31d91ef4 100644 --- a/lib/utils/proxy/proxy_test.go +++ b/api/utils/proxy/proxy_test.go @@ -96,7 +96,7 @@ func TestGetProxyAddress(t *testing.T) { for _, env := range tt.env { t.Setenv(env.name, env.val) } - p := getProxyAddress(tt.targetAddr) + p := GetProxyAddress(tt.targetAddr) require.Equal(t, tt.proxyAddr, p) }) } diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index 6aba7c404287f..bc6f56560f5f6 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -16,19 +16,15 @@ limitations under the License. package proxy import ( - "bufio" "context" "crypto/tls" "net" - "net/http" - "net/url" - "os" - "strings" "time" "github.com/gravitational/trace" "github.com/gravitational/teleport" + apiproxy "github.com/gravitational/teleport/api/utils/proxy" "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib" alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common" @@ -153,7 +149,7 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( defer cancel() ctx = timeoutCtx } - conn, err := dialProxy(ctx, d.proxyHost, address) + conn, err := apiproxy.DialProxy(ctx, d.proxyHost, address) if err != nil { return nil, trace.Wrap(err) } @@ -179,7 +175,7 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( // SSH connection. func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { // Build a proxy connection first. - pconn, err := dialProxy(context.Background(), d.proxyHost, addr) + pconn, err := apiproxy.DialProxy(context.Background(), d.proxyHost, addr) if err != nil { return nil, trace.Wrap(err) } @@ -241,7 +237,7 @@ func WithTLSConfig(tlsConfig *tls.Config) DialerOptionFunc { // server directly. func DialerFromEnvironment(addr string, opts ...DialerOptionFunc) Dialer { // Try and get proxy addr from the environment. - proxyAddr := getProxyAddress(addr) + proxyAddr := apiproxy.GetProxyAddress(addr) var options dialerOptions for _, opt := range opts { @@ -259,116 +255,3 @@ func DialerFromEnvironment(addr string, opts ...DialerOptionFunc) Dialer { } type DirectDialerOptFunc func(dial *directDial) - -func dialProxy(ctx context.Context, proxyAddr string, addr string) (net.Conn, error) { - var d net.Dialer - conn, err := d.DialContext(ctx, "tcp", proxyAddr) - if err != nil { - log.Warnf("Unable to dial to proxy: %v: %v.", proxyAddr, err) - return nil, trace.ConvertSystemError(err) - } - - connectReq := &http.Request{ - Method: http.MethodConnect, - URL: &url.URL{Opaque: addr}, - Host: addr, - Header: make(http.Header), - } - err = connectReq.Write(conn) - if err != nil { - log.Warnf("Unable to write to proxy: %v.", err) - return nil, trace.Wrap(err) - } - - // Read in the response. http.ReadResponse will read in the status line, mime - // headers, and potentially part of the response body. the body itself will - // not be read, but kept around so it can be read later. - br := bufio.NewReader(conn) - // Per the above comment, we're only using ReadResponse to check the status - // and then hand off the underlying connection to the caller. - // resp.Body.Close() would drain conn and close it, we don't need to do it - // here. Disabling bodyclose linter for this edge case. - //nolint:bodyclose - resp, err := http.ReadResponse(br, connectReq) - if err != nil { - conn.Close() - log.Warnf("Unable to read response: %v.", err) - return nil, trace.Wrap(err) - } - if resp.StatusCode != http.StatusOK { - conn.Close() - return nil, trace.BadParameter("unable to proxy connection: %v", resp.Status) - } - - // Return a bufferedConn that wraps a net.Conn and a *bufio.Reader. this - // needs to be done because http.ReadResponse will buffer part of the - // response body in the *bufio.Reader that was passed in. reads must first - // come from anything buffered, then from the underlying connection otherwise - // data will be lost. - return &bufferedConn{ - Conn: conn, - reader: br, - }, nil -} - -func getProxyAddress(addr string) string { - envs := []string{ - teleport.HTTPSProxy, - strings.ToLower(teleport.HTTPSProxy), - teleport.HTTPProxy, - strings.ToLower(teleport.HTTPProxy), - } - - for _, v := range envs { - envAddr := os.Getenv(v) - if envAddr == "" { - continue - } - proxyAddr, err := parse(envAddr) - if err != nil { - log.Debugf("Unable to parse environment variable %q: %q.", v, envAddr) - continue - } - log.Debugf("Successfully parsed environment variable %q: %q to %q.", v, envAddr, proxyAddr) - if !useProxy(addr) { - log.Debugf("Matched NO_PROXY override for %q: %q, going to ignore proxy variable.", v, envAddr) - return "" - } - return proxyAddr - } - - log.Debugf("No valid environment variables found.") - return "" -} - -// bufferedConn is used when part of the data on a connection has already been -// read by a *bufio.Reader. Reads will first try and read from the -// *bufio.Reader and when everything has been read, reads will go to the -// underlying connection. -type bufferedConn struct { - net.Conn - reader *bufio.Reader -} - -// Read first reads from the *bufio.Reader any data that has already been -// buffered. Once all buffered data has been read, reads go to the net.Conn. -func (bc *bufferedConn) Read(b []byte) (n int, err error) { - if bc.reader.Buffered() > 0 { - return bc.reader.Read(b) - } - return bc.Conn.Read(b) -} - -// parse will extract the host:port of the proxy to dial to. If the -// value is not prefixed by "http", then it will prepend "http" and try. -func parse(addr string) (string, error) { - proxyurl, err := url.Parse(addr) - if err != nil || !strings.HasPrefix(proxyurl.Scheme, "http") { - proxyurl, err = url.Parse("http://" + addr) - if err != nil { - return "", trace.Wrap(err) - } - } - - return proxyurl.Host, nil -} From dd5e2328f80846af49c2ab4870780477cf67fc3c Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 15 Feb 2022 14:59:55 -0800 Subject: [PATCH 10/43] Add proxy-aware context dialer --- api/client/client.go | 2 +- api/client/contextdialer.go | 11 +++++++++++ api/{utils/proxy => client}/noproxy.go | 2 +- api/{utils/proxy => client}/proxy.go | 10 ++++++---- api/{utils/proxy => client}/proxy_test.go | 2 +- lib/auth/clt.go | 2 +- lib/utils/proxy/proxy.go | 8 ++++---- 7 files changed, 25 insertions(+), 12 deletions(-) rename api/{utils/proxy => client}/noproxy.go (99%) rename api/{utils/proxy => client}/proxy.go (94%) rename api/{utils/proxy => client}/proxy_test.go (99%) diff --git a/api/client/client.go b/api/client/client.go index c2ec9efe65bcc..84dc97e666815 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -283,7 +283,7 @@ type connectParams struct { // authConnect connects to the Teleport Auth Server directly. func authConnect(ctx context.Context, params connectParams) (*Client, error) { - dialer := NewDirectDialer(params.cfg.KeepAlivePeriod, params.cfg.DialTimeout) + dialer := NewDialer(params.cfg.KeepAlivePeriod, params.cfg.DialTimeout) clt := newClient(params.cfg, dialer, params.tlsConfig) if err := clt.dialGRPC(ctx, params.addr); err != nil { return nil, trace.Wrap(err, "failed to connect to addr %v as an auth server", params.addr) diff --git a/api/client/contextdialer.go b/api/client/contextdialer.go index 2c2d08e29e894..62da334808e5a 100644 --- a/api/client/contextdialer.go +++ b/api/client/contextdialer.go @@ -52,6 +52,17 @@ func NewDirectDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { } } +// NewDialer makes a new dialer that connects to an Auth server either directly or via an HTTP proxy/ +func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { + dialer := NewDirectDialer(keepAlivePeriod, dialTimeout) + return ContextDialerFunc(func(ctx context.Context, network, addr string) (net.Conn, error) { + if proxyAddr := GetProxyAddress(addr); proxyAddr != "" { + return DialProxy(ctx, proxyAddr, addr, dialer) + } + return dialer.DialContext(ctx, network, addr) + }) +} + // NewProxyDialer makes a dialer to connect to an Auth server through the SSH reverse tunnel on the proxy. // The dialer will ping the web client to discover the tunnel proxy address on each dial. func NewProxyDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Duration, discoveryAddr string, insecure bool) ContextDialer { diff --git a/api/utils/proxy/noproxy.go b/api/client/noproxy.go similarity index 99% rename from api/utils/proxy/noproxy.go rename to api/client/noproxy.go index d9dcdb5f3d8a9..159ad56b8d382 100644 --- a/api/utils/proxy/noproxy.go +++ b/api/client/noproxy.go @@ -7,7 +7,7 @@ // This is the low-level Transport implementation of RoundTripper. // The high-level interface is in client.go. -package proxy +package client import ( "os" diff --git a/api/utils/proxy/proxy.go b/api/client/proxy.go similarity index 94% rename from api/utils/proxy/proxy.go rename to api/client/proxy.go index 631be761fd70a..3fc54104ec5a6 100644 --- a/api/utils/proxy/proxy.go +++ b/api/client/proxy.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package proxy +package client import ( "bufio" @@ -30,9 +30,11 @@ import ( "github.com/siddontang/go/log" ) -func DialProxy(ctx context.Context, proxyAddr string, addr string) (net.Conn, error) { - var d net.Dialer - conn, err := d.DialContext(ctx, "tcp", proxyAddr) +func DialProxy(ctx context.Context, proxyAddr, addr string, dialer ContextDialer) (net.Conn, error) { + if dialer == nil { + dialer = &net.Dialer{} + } + conn, err := dialer.DialContext(ctx, "tcp", proxyAddr) if err != nil { log.Warnf("Unable to dial to proxy: %v: %v.", proxyAddr, err) return nil, trace.ConvertSystemError(err) diff --git a/api/utils/proxy/proxy_test.go b/api/client/proxy_test.go similarity index 99% rename from api/utils/proxy/proxy_test.go rename to api/client/proxy_test.go index 1067c31d91ef4..d1d781f137fdd 100644 --- a/api/utils/proxy/proxy_test.go +++ b/api/client/proxy_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package proxy +package client import ( "fmt" diff --git a/lib/auth/clt.go b/lib/auth/clt.go index 49a5f23b83845..e74f3654f7db5 100644 --- a/lib/auth/clt.go +++ b/lib/auth/clt.go @@ -128,7 +128,7 @@ func NewHTTPClient(cfg client.Config, tls *tls.Config, params ...roundtrip.Clien if len(cfg.Addrs) == 0 { return nil, trace.BadParameter("no addresses to dial") } - contextDialer := client.NewDirectDialer(cfg.KeepAlivePeriod, cfg.DialTimeout) + contextDialer := client.NewDialer(cfg.KeepAlivePeriod, cfg.DialTimeout) dialer = client.ContextDialerFunc(func(ctx context.Context, network, _ string) (conn net.Conn, err error) { for _, addr := range cfg.Addrs { conn, err = contextDialer.DialContext(ctx, network, addr) diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index bc6f56560f5f6..683e625144e58 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -24,7 +24,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport" - apiproxy "github.com/gravitational/teleport/api/utils/proxy" + apiclient "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib" alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common" @@ -149,7 +149,7 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( defer cancel() ctx = timeoutCtx } - conn, err := apiproxy.DialProxy(ctx, d.proxyHost, address) + conn, err := apiclient.DialProxy(ctx, d.proxyHost, address, nil) if err != nil { return nil, trace.Wrap(err) } @@ -175,7 +175,7 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( // SSH connection. func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { // Build a proxy connection first. - pconn, err := apiproxy.DialProxy(context.Background(), d.proxyHost, addr) + pconn, err := apiclient.DialProxy(context.Background(), d.proxyHost, addr, nil) if err != nil { return nil, trace.Wrap(err) } @@ -237,7 +237,7 @@ func WithTLSConfig(tlsConfig *tls.Config) DialerOptionFunc { // server directly. func DialerFromEnvironment(addr string, opts ...DialerOptionFunc) Dialer { // Try and get proxy addr from the environment. - proxyAddr := apiproxy.GetProxyAddress(addr) + proxyAddr := apiclient.GetProxyAddress(addr) var options dialerOptions for _, opt := range opts { From 7981450ce8a5e835be190a121eb06b3611e9802a Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Wed, 16 Feb 2022 15:31:26 -0800 Subject: [PATCH 11/43] Fix incorrect address in makeProxySSHClient --- api/client/contextdialer.go | 3 ++- lib/client/api.go | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/api/client/contextdialer.go b/api/client/contextdialer.go index 62da334808e5a..0797ec1a7a64e 100644 --- a/api/client/contextdialer.go +++ b/api/client/contextdialer.go @@ -52,7 +52,8 @@ func NewDirectDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { } } -// NewDialer makes a new dialer that connects to an Auth server either directly or via an HTTP proxy/ +// NewDialer makes a new dialer that connects to an Auth server either directly or via an HTTP proxy, depending +// on the environment. func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { dialer := NewDirectDialer(keepAlivePeriod, dialTimeout) return ContextDialerFunc(func(ctx context.Context, network, addr string) (net.Conn, error) { diff --git a/lib/client/api.go b/lib/client/api.go index 6a5a6a9db97f4..09e80be1ce0a4 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -2201,6 +2201,7 @@ func (tc *TeleportClient) connectToProxy(ctx context.Context) (*ProxyClient, err func makeProxySSHClient(tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.Client, error) { var opts []proxy.DialerOptionFunc + addr := tc.Config.SSHProxyAddr if tc.Config.TLSRoutingEnabled { tlsConfig, err := tc.loadTLSConfig() if err != nil { @@ -2208,10 +2209,11 @@ func makeProxySSHClient(tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.C } tlsConfig.NextProtos = []string{string(alpncommon.ProtocolProxySSH)} tlsConfig.InsecureSkipVerify = tc.Config.InsecureSkipVerify + addr = tc.Config.WebProxyAddr opts = append(opts, proxy.WithALPNDialer(), proxy.WithTLSConfig(tlsConfig)) } - dialer := proxy.DialerFromEnvironment(tc.Config.WebProxyAddr, opts...) - client, err := dialer.Dial("tcp", tc.Config.SSHProxyAddr, sshConfig) + dialer := proxy.DialerFromEnvironment(addr, opts...) + client, err := dialer.Dial("tcp", addr, sshConfig) return client, trace.Wrap(err, "failed to authenticate with proxy %v", tc.Config.SSHProxyAddr) } From ad44ccf0b5d3a095e095e5e40cdcc2866f5e69e3 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 1 Mar 2022 14:48:01 -0800 Subject: [PATCH 12/43] Add docs --- api/client/proxy.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/client/proxy.go b/api/client/proxy.go index 3fc54104ec5a6..d7ee2d9431800 100644 --- a/api/client/proxy.go +++ b/api/client/proxy.go @@ -30,6 +30,7 @@ import ( "github.com/siddontang/go/log" ) +// DialProxy creates a connection to a server via an HTTP Proxy. func DialProxy(ctx context.Context, proxyAddr, addr string, dialer ContextDialer) (net.Conn, error) { if dialer == nil { dialer = &net.Dialer{} @@ -83,6 +84,7 @@ func DialProxy(ctx context.Context, proxyAddr, addr string, dialer ContextDialer }, nil } +// GetProxyAddress gets the HTTP proxy address to use for a given address, if any. func GetProxyAddress(addr string) string { envs := []string{ teleport.HTTPSProxy, From de6c7155e6d80d9774f03b86c47e6841f9081a53 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 1 Mar 2022 15:11:03 -0800 Subject: [PATCH 13/43] Add NO_PROXY tests --- api/client/webclient/webclient_test.go | 10 ++++++++++ lib/client/https_client_test.go | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/api/client/webclient/webclient_test.go b/api/client/webclient/webclient_test.go index d7148542d7988..fd9d81fab02d8 100644 --- a/api/client/webclient/webclient_test.go +++ b/api/client/webclient/webclient_test.go @@ -304,3 +304,13 @@ func TestNewWebClientRespectHTTPProxy(t *testing.T) { require.Contains(t, err.Error(), "proxyconnect") require.Contains(t, err.Error(), "no such host") } + +func TestNewWebClientNoProxy(t *testing.T) { + t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") + t.Setenv("NO_PROXY", "example.com") + client := newWebClient(false /* insecure */, nil /* pool */) + resp, err := client.Get("https://example.com") + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) +} diff --git a/lib/client/https_client_test.go b/lib/client/https_client_test.go index 2bb93cb88396f..b0cced4188890 100644 --- a/lib/client/https_client_test.go +++ b/lib/client/https_client_test.go @@ -17,6 +17,7 @@ limitations under the License. package client import ( + "net/http" "testing" "github.com/stretchr/testify/require" @@ -37,6 +38,16 @@ func TestNewInsecureWebClientHTTPProxy(t *testing.T) { require.Contains(t, err.Error(), "no such host") } +func TestNewInsecureWebClientNoProxy(t *testing.T) { + t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") + t.Setenv("NO_PROXY", "example.com") + client := NewInsecureWebClient() + resp, err := client.Get("https://example.com") + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) +} + func TestNewClientWithPoolHTTPProxy(t *testing.T) { t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") client := newClientWithPool(nil) @@ -51,3 +62,13 @@ func TestNewClientWithPoolHTTPProxy(t *testing.T) { require.Contains(t, err.Error(), "proxyconnect") require.Contains(t, err.Error(), "no such host") } + +func TestNewClientWithPoolNoProxy(t *testing.T) { + t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") + t.Setenv("NO_PROXY", "example.com") + client := newClientWithPool(nil) + resp, err := client.Get("https://example.com") + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) +} From df6c5e2875da4913b622290bdc0b478a0872ca37 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 1 Mar 2022 16:00:36 -0800 Subject: [PATCH 14/43] Simplify bodyclose workaround --- api/client/webclient/webclient_test.go | 7 ++----- lib/client/https_client_test.go | 14 ++++---------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/api/client/webclient/webclient_test.go b/api/client/webclient/webclient_test.go index fd9d81fab02d8..963b6b5f2156c 100644 --- a/api/client/webclient/webclient_test.go +++ b/api/client/webclient/webclient_test.go @@ -293,12 +293,9 @@ func TestExtract(t *testing.T) { func TestNewWebClientRespectHTTPProxy(t *testing.T) { t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") client := newWebClient(false /* insecure */, nil /* pool */) + // resp should be nil, so there will be no body to close. + //nolint:bodyclose resp, err := client.Get("https://example.com") - defer func() { - if resp != nil { - resp.Body.Close() - } - }() // Client should try to proxy through nonexistent server at localhost. require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") diff --git a/lib/client/https_client_test.go b/lib/client/https_client_test.go index b0cced4188890..ea5fac1a89a4d 100644 --- a/lib/client/https_client_test.go +++ b/lib/client/https_client_test.go @@ -26,12 +26,9 @@ import ( func TestNewInsecureWebClientHTTPProxy(t *testing.T) { t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") client := NewInsecureWebClient() + // resp should be nil, so there will be no body to close. + //nolint:bodyclose resp, err := client.Get("https://example.com") - defer func() { - if resp != nil { - resp.Body.Close() - } - }() // Client should try to proxy through nonexistent server at localhost. require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") @@ -51,12 +48,9 @@ func TestNewInsecureWebClientNoProxy(t *testing.T) { func TestNewClientWithPoolHTTPProxy(t *testing.T) { t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") client := newClientWithPool(nil) + // resp should be nil, so there will be no body to close. + //nolint:bodyclose resp, err := client.Get("https://example.com") - defer func() { - if resp != nil { - resp.Body.Close() - } - }() // Client should try to proxy through nonexistent server at localhost. require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") From bde8fb2c7e7728c9b83a8a65432d26f23b9e1ff9 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Wed, 2 Mar 2022 16:08:50 -0800 Subject: [PATCH 15/43] Stop caching env vars --- api/client/webclient/webclient.go | 5 ++++- lib/client/https_client.go | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/api/client/webclient/webclient.go b/api/client/webclient/webclient.go index 1b684c0969218..53a496587f5b8 100644 --- a/api/client/webclient/webclient.go +++ b/api/client/webclient/webclient.go @@ -33,6 +33,7 @@ import ( "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/utils" + "golang.org/x/net/http/httpproxy" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" @@ -46,7 +47,9 @@ func newWebClient(insecure bool, pool *x509.CertPool) *http.Client { RootCAs: pool, InsecureSkipVerify: insecure, }, - Proxy: http.ProxyFromEnvironment, + Proxy: func(req *http.Request) (*url.URL, error) { + return httpproxy.FromEnvironment().ProxyFunc()(req.URL) + }, }, } } diff --git a/lib/client/https_client.go b/lib/client/https_client.go index 8a95b3cf47497..c9f01aead2cb0 100644 --- a/lib/client/https_client.go +++ b/lib/client/https_client.go @@ -27,6 +27,7 @@ import ( apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/httplib" "github.com/gravitational/teleport/lib/utils" + "golang.org/x/net/http/httpproxy" "github.com/gravitational/roundtrip" "github.com/gravitational/trace" @@ -41,7 +42,9 @@ func NewInsecureWebClient() *http.Client { return &http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsConfig, - Proxy: http.ProxyFromEnvironment, + Proxy: func(req *http.Request) (*url.URL, error) { + return httpproxy.FromEnvironment().ProxyFunc()(req.URL) + }, }, } } @@ -55,7 +58,9 @@ func newClientWithPool(pool *x509.CertPool) *http.Client { return &http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsConfig, - Proxy: http.ProxyFromEnvironment, + Proxy: func(req *http.Request) (*url.URL, error) { + return httpproxy.FromEnvironment().ProxyFunc()(req.URL) + }, }, } } From bbc3af7b1b653b22fe558e04cbf2efb59acf29a7 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Thu, 3 Mar 2022 14:05:05 -0800 Subject: [PATCH 16/43] Split makeProxySSHClient --- lib/client/api.go | 32 +++++++++++++++++++------------- lib/utils/proxy/proxy.go | 3 +-- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/lib/client/api.go b/lib/client/api.go index 285fac1476940..3ddf07a1f48cc 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -2234,21 +2234,27 @@ func (tc *TeleportClient) connectToProxy(ctx context.Context) (*ProxyClient, err } func makeProxySSHClient(tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.Client, error) { - var opts []proxy.DialerOptionFunc - addr := tc.Config.SSHProxyAddr if tc.Config.TLSRoutingEnabled { - tlsConfig, err := tc.loadTLSConfig() - if err != nil { - return nil, trace.Wrap(err) - } - tlsConfig.NextProtos = []string{string(alpncommon.ProtocolProxySSH)} - tlsConfig.InsecureSkipVerify = tc.Config.InsecureSkipVerify - addr = tc.Config.WebProxyAddr - opts = append(opts, proxy.WithALPNDialer(), proxy.WithTLSConfig(tlsConfig)) + return makeProxySSHClientWithTLSWrapper(tc, sshConfig) } - dialer := proxy.DialerFromEnvironment(addr, opts...) - client, err := dialer.Dial("tcp", addr, sshConfig) - return client, trace.Wrap(err, "failed to authenticate with proxy %v", tc.Config.SSHProxyAddr) + return makeProxySSHClientDirect(tc, sshConfig) +} + +func makeProxySSHClientDirect(tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.Client, error) { + dialer := proxy.DialerFromEnvironment(tc.Config.SSHProxyAddr) + return dialer.Dial("tcp", tc.Config.SSHProxyAddr, sshConfig) +} + +func makeProxySSHClientWithTLSWrapper(tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.Client, error) { + tlsConfig, err := tc.loadTLSConfig() + if err != nil { + return nil, trace.Wrap(err) + } + tlsConfig.NextProtos = []string{string(alpncommon.ProtocolProxySSH)} + tlsConfig.InsecureSkipVerify = tc.Config.InsecureSkipVerify + + dialer := proxy.DialerFromEnvironment(tc.Config.WebProxyAddr, proxy.WithALPNDialer(), proxy.WithTLSConfig(tlsConfig)) + return dialer.Dial("tcp", tc.Config.WebProxyAddr, sshConfig) } func (tc *TeleportClient) rootClusterName() (string, error) { diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index 1075034e85805..c9f60900c62ed 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -26,7 +26,6 @@ import ( "github.com/gravitational/teleport" apiclient "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/utils/sshutils" - "github.com/gravitational/teleport/lib" alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common" "github.com/gravitational/teleport/lib/utils" @@ -200,7 +199,7 @@ func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) ( if conf == nil { conf = &tls.Config{ NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, - InsecureSkipVerify: lib.IsInsecureDevMode(), + InsecureSkipVerify: d.insecure, } } conf.ServerName = address.Host() From 79e3f3f8b9709c89dcbe99322c12b3076d764085 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Thu, 10 Mar 2022 14:46:38 -0800 Subject: [PATCH 17/43] Remove leftover teleport imports from api --- api/client/noproxy.go | 4 ++-- api/client/proxy.go | 10 +++++----- api/client/proxy_test.go | 7 ------- api/constants/constants.go | 12 ++++++++++++ api/go.mod | 1 + api/go.sum | 2 ++ constants.go | 12 ------------ 7 files changed, 22 insertions(+), 26 deletions(-) diff --git a/api/client/noproxy.go b/api/client/noproxy.go index 159ad56b8d382..a6115d20c6a5a 100644 --- a/api/client/noproxy.go +++ b/api/client/noproxy.go @@ -13,7 +13,7 @@ import ( "os" "strings" - "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/constants" ) // useProxy reports whether requests to addr should use a proxy, @@ -24,7 +24,7 @@ func useProxy(addr string) bool { return true } var noProxy string - for _, env := range []string{teleport.NoProxy, strings.ToLower(teleport.NoProxy)} { + for _, env := range []string{constants.NoProxy, strings.ToLower(constants.NoProxy)} { noProxy = os.Getenv(env) if noProxy != "" { break diff --git a/api/client/proxy.go b/api/client/proxy.go index d7ee2d9431800..b42c45fe56cf3 100644 --- a/api/client/proxy.go +++ b/api/client/proxy.go @@ -25,7 +25,7 @@ import ( "os" "strings" - "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/constants" "github.com/gravitational/trace" "github.com/siddontang/go/log" ) @@ -87,10 +87,10 @@ func DialProxy(ctx context.Context, proxyAddr, addr string, dialer ContextDialer // GetProxyAddress gets the HTTP proxy address to use for a given address, if any. func GetProxyAddress(addr string) string { envs := []string{ - teleport.HTTPSProxy, - strings.ToLower(teleport.HTTPSProxy), - teleport.HTTPProxy, - strings.ToLower(teleport.HTTPProxy), + constants.HTTPSProxy, + strings.ToLower(constants.HTTPSProxy), + constants.HTTPProxy, + strings.ToLower(constants.HTTPProxy), } for _, v := range envs { diff --git a/api/client/proxy_test.go b/api/client/proxy_test.go index d1d781f137fdd..29cffc12e1b4f 100644 --- a/api/client/proxy_test.go +++ b/api/client/proxy_test.go @@ -18,18 +18,11 @@ package client import ( "fmt" - "os" "testing" - "github.com/gravitational/teleport/lib/utils" "github.com/stretchr/testify/require" ) -func TestMain(m *testing.M) { - utils.InitLoggerForTests() - os.Exit(m.Run()) -} - func TestGetProxyAddress(t *testing.T) { type env struct { name string diff --git a/api/constants/constants.go b/api/constants/constants.go index fbb1214b8493a..0e54e83e62b6c 100644 --- a/api/constants/constants.go +++ b/api/constants/constants.go @@ -189,3 +189,15 @@ const ( // KubeTeleportProxyALPNPrefix is a SNI Kubernetes prefix used for distinguishing the Kubernetes HTTP traffic. KubeTeleportProxyALPNPrefix = "kube-teleport-proxy-alpn." ) + +const ( + // HTTPSProxy is an environment variable pointing to a HTTPS proxy. + HTTPSProxy = "HTTPS_PROXY" + + // HTTPProxy is an environment variable pointing to a HTTP proxy. + HTTPProxy = "HTTP_PROXY" + + // NoProxy is an environment variable matching the cases + // when HTTPS_PROXY or HTTP_PROXY is ignored + NoProxy = "NO_PROXY" +) diff --git a/api/go.mod b/api/go.mod index 3a7210c771f25..a81a73769382d 100644 --- a/api/go.mod +++ b/api/go.mod @@ -8,6 +8,7 @@ require ( github.com/google/go-cmp v0.5.4 github.com/gravitational/trace v1.1.17 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8 diff --git a/api/go.sum b/api/go.sum index 72c1f18217730..69ab03da359fa 100644 --- a/api/go.sum +++ b/api/go.sum @@ -61,6 +61,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM= +github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/constants.go b/constants.go index 246cdc8809a78..13bf738f230b4 100644 --- a/constants.go +++ b/constants.go @@ -61,18 +61,6 @@ const ( HTTPNextProtoTLS = "http/1.1" ) -const ( - // HTTPSProxy is an environment variable pointing to a HTTPS proxy. - HTTPSProxy = "HTTPS_PROXY" - - // HTTPProxy is an environment variable pointing to a HTTP proxy. - HTTPProxy = "HTTP_PROXY" - - // NoProxy is an environment variable matching the cases - // when HTTPS_PROXY or HTTP_PROXY is ignored - NoProxy = "NO_PROXY" -) - const ( // TOTPValidityPeriod is the number of seconds a TOTP token is valid. TOTPValidityPeriod uint = 30 From fcf43eea14cce4e6d25b20935f09fe0dbc7b6a74 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Fri, 11 Mar 2022 14:27:52 -0800 Subject: [PATCH 18/43] Make dialALPNWithDeadline a member of directDial --- lib/utils/proxy/proxy.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index c9f60900c62ed..f128fc223ebc5 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -52,7 +52,7 @@ func dialWithDeadline(network string, addr string, config *ssh.ClientConfig) (*s // dialALPNWithDeadline allows connecting to Teleport in single-port mode. SSH protocol is wrapped into // TLS connection where TLS ALPN protocol is set to ProtocolReverseTunnel allowing ALPN Proxy to route the // incoming connection to ReverseTunnel proxy service. -func dialALPNWithDeadline(network string, addr string, config *ssh.ClientConfig, insecure bool, tlsConfig *tls.Config) (*ssh.Client, error) { +func (d directDial) dialALPNWithDeadline(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { dialer := &net.Dialer{ Timeout: config.Timeout, } @@ -60,14 +60,14 @@ func dialALPNWithDeadline(network string, addr string, config *ssh.ClientConfig, if err != nil { return nil, trace.Wrap(err) } - conf := tlsConfig.Clone() + conf := d.tlsConfig.Clone() if conf == nil { conf = &tls.Config{ NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, } } conf.ServerName = address.Host() - conf.InsecureSkipVerify = insecure + conf.InsecureSkipVerify = d.insecure tlsConn, err := tls.DialWithDialer(dialer, network, addr, conf) if err != nil { return nil, trace.Wrap(err) @@ -96,7 +96,7 @@ type directDial struct { // Dial calls ssh.Dial directly. func (d directDial) Dial(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { if d.tlsRoutingEnabled { - client, err := dialALPNWithDeadline(network, addr, config, d.insecure, d.tlsConfig) + client, err := d.dialALPNWithDeadline(network, addr, config) if err != nil { return nil, trace.Wrap(err) } From e24d3d0451be0671dd4fadfef8e47a2df87f6121 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Fri, 11 Mar 2022 14:30:59 -0800 Subject: [PATCH 19/43] Add DialProxyWithDialer for custom dialers --- api/client/contextdialer.go | 2 +- api/client/proxy.go | 10 ++++++---- lib/utils/proxy/proxy.go | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/api/client/contextdialer.go b/api/client/contextdialer.go index 0797ec1a7a64e..5c3725c5a1631 100644 --- a/api/client/contextdialer.go +++ b/api/client/contextdialer.go @@ -58,7 +58,7 @@ func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { dialer := NewDirectDialer(keepAlivePeriod, dialTimeout) return ContextDialerFunc(func(ctx context.Context, network, addr string) (net.Conn, error) { if proxyAddr := GetProxyAddress(addr); proxyAddr != "" { - return DialProxy(ctx, proxyAddr, addr, dialer) + return DialProxyWithDialer(ctx, proxyAddr, addr, dialer) } return dialer.DialContext(ctx, network, addr) }) diff --git a/api/client/proxy.go b/api/client/proxy.go index b42c45fe56cf3..42d448c8e9040 100644 --- a/api/client/proxy.go +++ b/api/client/proxy.go @@ -31,10 +31,12 @@ import ( ) // DialProxy creates a connection to a server via an HTTP Proxy. -func DialProxy(ctx context.Context, proxyAddr, addr string, dialer ContextDialer) (net.Conn, error) { - if dialer == nil { - dialer = &net.Dialer{} - } +func DialProxy(ctx context.Context, proxyAddr, addr string) (net.Conn, error) { + return DialProxyWithDialer(ctx, proxyAddr, addr, &net.Dialer{}) +} + +// DialProxyWithDialer creates a connection to a server via an HTTP Proxy using a specified dialer. +func DialProxyWithDialer(ctx context.Context, proxyAddr, addr string, dialer ContextDialer) (net.Conn, error) { conn, err := dialer.DialContext(ctx, "tcp", proxyAddr) if err != nil { log.Warnf("Unable to dial to proxy: %v: %v.", proxyAddr, err) diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index f128fc223ebc5..19fe1972a996c 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -157,7 +157,7 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( defer cancel() ctx = timeoutCtx } - conn, err := apiclient.DialProxy(ctx, d.proxyHost, address, nil) + conn, err := apiclient.DialProxy(ctx, d.proxyHost, address) if err != nil { return nil, trace.Wrap(err) } @@ -183,7 +183,7 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( // SSH connection. func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { // Build a proxy connection first. - pconn, err := apiclient.DialProxy(context.Background(), d.proxyHost, addr, nil) + pconn, err := apiclient.DialProxy(context.Background(), d.proxyHost, addr) if err != nil { return nil, trace.Wrap(err) } From 749d8896c736ae3d8d5482b4ba1c514ea35e6946 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Mon, 14 Mar 2022 10:32:35 -0700 Subject: [PATCH 20/43] Make tlsConfig mandatory --- lib/client/api.go | 4 +- lib/reversetunnel/agent.go | 6 ++- lib/reversetunnel/transport.go | 6 ++- lib/utils/proxy/proxy.go | 72 +++++++++++----------------------- 4 files changed, 34 insertions(+), 54 deletions(-) diff --git a/lib/client/api.go b/lib/client/api.go index 3ddf07a1f48cc..4c19cd4a3609e 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -2251,9 +2251,7 @@ func makeProxySSHClientWithTLSWrapper(tc *TeleportClient, sshConfig *ssh.ClientC return nil, trace.Wrap(err) } tlsConfig.NextProtos = []string{string(alpncommon.ProtocolProxySSH)} - tlsConfig.InsecureSkipVerify = tc.Config.InsecureSkipVerify - - dialer := proxy.DialerFromEnvironment(tc.Config.WebProxyAddr, proxy.WithALPNDialer(), proxy.WithTLSConfig(tlsConfig)) + dialer := proxy.DialerFromEnvironment(tc.Config.WebProxyAddr, proxy.WithALPNDialer(tlsConfig)) return dialer.Dial("tcp", tc.Config.WebProxyAddr, sshConfig) } diff --git a/lib/reversetunnel/agent.go b/lib/reversetunnel/agent.go index 22bebc91c6d54..8f040c6974b3a 100644 --- a/lib/reversetunnel/agent.go +++ b/lib/reversetunnel/agent.go @@ -22,6 +22,7 @@ package reversetunnel import ( "context" + "crypto/tls" "fmt" "sync" "time" @@ -35,6 +36,7 @@ import ( "github.com/gravitational/teleport/lib" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/reversetunnel/track" + alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/proxy" @@ -283,7 +285,9 @@ func (a *Agent) connect() (conn *ssh.Client, err error) { } if a.reverseTunnelDetails != nil && a.reverseTunnelDetails.TLSRoutingEnabled { - opts = append(opts, proxy.WithALPNDialer()) + opts = append(opts, proxy.WithALPNDialer(&tls.Config{ + NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, + })) } for _, authMethod := range a.authMethods { diff --git a/lib/reversetunnel/transport.go b/lib/reversetunnel/transport.go index 73b593690ba49..65237ca5514e4 100644 --- a/lib/reversetunnel/transport.go +++ b/lib/reversetunnel/transport.go @@ -18,6 +18,7 @@ package reversetunnel import ( "context" + "crypto/tls" "encoding/json" "fmt" "io" @@ -34,6 +35,7 @@ import ( "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/events" + alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/proxy" @@ -97,7 +99,9 @@ func (t *TunnelAuthDialer) DialContext(ctx context.Context, _, _ string) (net.Co // address thus the ping call will always fail. t.Log.Debugf("Failed to ping web proxy %q addr: %v", addr.Addr, err) } else if resp.Proxy.TLSRoutingEnabled { - opts = append(opts, proxy.WithALPNDialer()) + opts = append(opts, proxy.WithALPNDialer(&tls.Config{ + NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, + })) } dialer := proxy.DialerFromEnvironment(addr.Addr, opts...) diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index 19fe1972a996c..078d0d062e147 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -26,7 +26,6 @@ import ( "github.com/gravitational/teleport" apiclient "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/utils/sshutils" - alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common" "github.com/gravitational/teleport/lib/utils" "golang.org/x/crypto/ssh" @@ -61,11 +60,6 @@ func (d directDial) dialALPNWithDeadline(network string, addr string, config *ss return nil, trace.Wrap(err) } conf := d.tlsConfig.Clone() - if conf == nil { - conf = &tls.Config{ - NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, - } - } conf.ServerName = address.Host() conf.InsecureSkipVerify = d.insecure tlsConn, err := tls.DialWithDialer(dialer, network, addr, conf) @@ -85,17 +79,20 @@ type Dialer interface { } type directDial struct { - // tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. - tlsRoutingEnabled bool // insecure is whether to skip certificate validation. insecure bool // tlsConfig is the TLS config to use. tlsConfig *tls.Config } +// tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. +func (d directDial) tlsRoutingEnabled() bool { + return d.tlsConfig != nil +} + // Dial calls ssh.Dial directly. func (d directDial) Dial(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { - if d.tlsRoutingEnabled { + if d.tlsRoutingEnabled() { client, err := d.dialALPNWithDeadline(network, addr, config) if err != nil { return nil, trace.Wrap(err) @@ -111,17 +108,12 @@ func (d directDial) Dial(network string, addr string, config *ssh.ClientConfig) // DialTimeout acts like Dial but takes a timeout. func (d directDial) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { - if d.tlsRoutingEnabled { + if d.tlsRoutingEnabled() { addr, err := utils.ParseAddr(address) if err != nil { return nil, trace.Wrap(err) } conf := d.tlsConfig.Clone() - if conf == nil { - conf = &tls.Config{ - NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, - } - } conf.ServerName = addr.Host() conf.InsecureSkipVerify = d.insecure tlsConn, err := tls.Dial("tcp", address, conf) @@ -140,14 +132,17 @@ func (d directDial) DialTimeout(network, address string, timeout time.Duration) type proxyDial struct { // proxyHost is the HTTPS proxy address. proxyHost string - // tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. - tlsRoutingEnabled bool // insecure is whether to skip certificate validation. insecure bool // tlsConfig is the TLS config to use. tlsConfig *tls.Config } +// tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. +func (d proxyDial) tlsRoutingEnabled() bool { + return d.tlsConfig != nil +} + // DialTimeout acts like Dial but takes a timeout. func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { // Build a proxy connection first. @@ -161,17 +156,12 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( if err != nil { return nil, trace.Wrap(err) } - if d.tlsRoutingEnabled { + if d.tlsRoutingEnabled() { address, err := utils.ParseAddr(address) if err != nil { return nil, trace.Wrap(err) } conf := d.tlsConfig.Clone() - if conf == nil { - conf = &tls.Config{ - NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, - } - } conf.ServerName = address.Host() conf.InsecureSkipVerify = d.insecure conn = tls.Client(conn, conf) @@ -190,19 +180,14 @@ func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) ( if config.Timeout > 0 { pconn.SetReadDeadline(time.Now().Add(config.Timeout)) } - if d.tlsRoutingEnabled { + if d.tlsRoutingEnabled() { address, err := utils.ParseAddr(addr) if err != nil { return nil, trace.Wrap(err) } conf := d.tlsConfig.Clone() - if conf == nil { - conf = &tls.Config{ - NextProtos: []string{string(alpncommon.ProtocolReverseTunnel)}, - InsecureSkipVerify: d.insecure, - } - } conf.ServerName = address.Host() + conf.InsecureSkipVerify = d.insecure pconn = tls.Client(pconn, conf) } @@ -218,11 +203,9 @@ func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) ( } type dialerOptions struct { - // tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. - tlsRoutingEnabled bool // insecureSkipTLSVerify is whether to skip certificate validation. insecureSkipTLSVerify bool - // tlsConfig is the TLS config to use. + // tlsConfig is the TLS config to use for TLS routing. tlsConfig *tls.Config } @@ -230,9 +213,9 @@ type dialerOptions struct { type DialerOptionFunc func(options *dialerOptions) // WithALPNDialer creates a dialer that allows to Teleport running in single-port mode. -func WithALPNDialer() DialerOptionFunc { +func WithALPNDialer(tlsConfig *tls.Config) DialerOptionFunc { return func(options *dialerOptions) { - options.tlsRoutingEnabled = true + options.tlsConfig = tlsConfig } } @@ -243,13 +226,6 @@ func WithInsecureSkipTLSVerify(insecure bool) DialerOptionFunc { } } -// WithTLSConfig creates a dialer that uses a specific tls config. -func WithTLSConfig(tlsConfig *tls.Config) DialerOptionFunc { - return func(options *dialerOptions) { - options.tlsConfig = tlsConfig - } -} - // DialerFromEnvironment returns a Dial function. If the https_proxy or http_proxy // environment variable are set, it returns a function that will dial through // said proxy server. If neither variable is set, it will connect to the SSH @@ -268,17 +244,15 @@ func DialerFromEnvironment(addr string, opts ...DialerOptionFunc) Dialer { if proxyAddr == "" { log.Debugf("No proxy set in environment, returning direct dialer.") return directDial{ - tlsRoutingEnabled: options.tlsRoutingEnabled, - insecure: options.insecureSkipTLSVerify, - tlsConfig: options.tlsConfig, + insecure: options.insecureSkipTLSVerify, + tlsConfig: options.tlsConfig, } } log.Debugf("Found proxy %q in environment, returning proxy dialer.", proxyAddr) return proxyDial{ - proxyHost: proxyAddr, - tlsRoutingEnabled: options.tlsRoutingEnabled, - insecure: options.insecureSkipTLSVerify, - tlsConfig: options.tlsConfig, + proxyHost: proxyAddr, + insecure: options.insecureSkipTLSVerify, + tlsConfig: options.tlsConfig, } } From 4e2a20aa79f58de4cfce0d500d8ccd044e192be4 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Mon, 14 Mar 2022 12:02:42 -0700 Subject: [PATCH 21/43] Bring back tlsRoutingEnabled flag --- lib/utils/proxy/proxy.go | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index 078d0d062e147..12961ca9beee8 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -81,18 +81,15 @@ type Dialer interface { type directDial struct { // insecure is whether to skip certificate validation. insecure bool + // tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. + tlsRoutingEnabled bool // tlsConfig is the TLS config to use. tlsConfig *tls.Config } -// tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. -func (d directDial) tlsRoutingEnabled() bool { - return d.tlsConfig != nil -} - // Dial calls ssh.Dial directly. func (d directDial) Dial(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { - if d.tlsRoutingEnabled() { + if d.tlsRoutingEnabled { client, err := d.dialALPNWithDeadline(network, addr, config) if err != nil { return nil, trace.Wrap(err) @@ -108,7 +105,7 @@ func (d directDial) Dial(network string, addr string, config *ssh.ClientConfig) // DialTimeout acts like Dial but takes a timeout. func (d directDial) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { - if d.tlsRoutingEnabled() { + if d.tlsRoutingEnabled { addr, err := utils.ParseAddr(address) if err != nil { return nil, trace.Wrap(err) @@ -134,15 +131,12 @@ type proxyDial struct { proxyHost string // insecure is whether to skip certificate validation. insecure bool + // tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. + tlsRoutingEnabled bool // tlsConfig is the TLS config to use. tlsConfig *tls.Config } -// tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. -func (d proxyDial) tlsRoutingEnabled() bool { - return d.tlsConfig != nil -} - // DialTimeout acts like Dial but takes a timeout. func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { // Build a proxy connection first. @@ -156,7 +150,7 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( if err != nil { return nil, trace.Wrap(err) } - if d.tlsRoutingEnabled() { + if d.tlsRoutingEnabled { address, err := utils.ParseAddr(address) if err != nil { return nil, trace.Wrap(err) @@ -180,7 +174,7 @@ func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) ( if config.Timeout > 0 { pconn.SetReadDeadline(time.Now().Add(config.Timeout)) } - if d.tlsRoutingEnabled() { + if d.tlsRoutingEnabled { address, err := utils.ParseAddr(addr) if err != nil { return nil, trace.Wrap(err) @@ -205,6 +199,8 @@ func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) ( type dialerOptions struct { // insecureSkipTLSVerify is whether to skip certificate validation. insecureSkipTLSVerify bool + // tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. + tlsRoutingEnabled bool // tlsConfig is the TLS config to use for TLS routing. tlsConfig *tls.Config } @@ -215,6 +211,7 @@ type DialerOptionFunc func(options *dialerOptions) // WithALPNDialer creates a dialer that allows to Teleport running in single-port mode. func WithALPNDialer(tlsConfig *tls.Config) DialerOptionFunc { return func(options *dialerOptions) { + options.tlsRoutingEnabled = true options.tlsConfig = tlsConfig } } @@ -244,15 +241,17 @@ func DialerFromEnvironment(addr string, opts ...DialerOptionFunc) Dialer { if proxyAddr == "" { log.Debugf("No proxy set in environment, returning direct dialer.") return directDial{ - insecure: options.insecureSkipTLSVerify, - tlsConfig: options.tlsConfig, + insecure: options.insecureSkipTLSVerify, + tlsRoutingEnabled: options.tlsRoutingEnabled, + tlsConfig: options.tlsConfig, } } log.Debugf("Found proxy %q in environment, returning proxy dialer.", proxyAddr) return proxyDial{ - proxyHost: proxyAddr, - insecure: options.insecureSkipTLSVerify, - tlsConfig: options.tlsConfig, + proxyHost: proxyAddr, + insecure: options.insecureSkipTLSVerify, + tlsRoutingEnabled: options.tlsRoutingEnabled, + tlsConfig: options.tlsConfig, } } From cad2d6cd66ad692b7804c050fd3dfdd44932580f Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 15 Mar 2022 11:24:29 -0700 Subject: [PATCH 22/43] Move tls config configuration to its own function --- lib/utils/proxy/proxy.go | 50 ++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index 12961ca9beee8..90c8226cced13 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -59,9 +59,10 @@ func (d directDial) dialALPNWithDeadline(network string, addr string, config *ss if err != nil { return nil, trace.Wrap(err) } - conf := d.tlsConfig.Clone() - conf.ServerName = address.Host() - conf.InsecureSkipVerify = d.insecure + conf, err := d.getTLSConfig(address) + if err != nil { + return nil, trace.Wrap(err) + } tlsConn, err := tls.DialWithDialer(dialer, network, addr, conf) if err != nil { return nil, trace.Wrap(err) @@ -87,6 +88,17 @@ type directDial struct { tlsConfig *tls.Config } +// getTLSConfig configures the dialers TLS config for a specified address. +func (d directDial) getTLSConfig(addr *utils.NetAddr) (*tls.Config, error) { + if d.tlsConfig == nil { + return nil, trace.BadParameter("TLS config was nil") + } + tlsConfig := d.tlsConfig.Clone() + tlsConfig.ServerName = addr.Host() + tlsConfig.InsecureSkipVerify = d.insecure + return tlsConfig, nil +} + // Dial calls ssh.Dial directly. func (d directDial) Dial(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { if d.tlsRoutingEnabled { @@ -110,9 +122,10 @@ func (d directDial) DialTimeout(network, address string, timeout time.Duration) if err != nil { return nil, trace.Wrap(err) } - conf := d.tlsConfig.Clone() - conf.ServerName = addr.Host() - conf.InsecureSkipVerify = d.insecure + conf, err := d.getTLSConfig(addr) + if err != nil { + return nil, trace.Wrap(err) + } tlsConn, err := tls.Dial("tcp", address, conf) if err != nil { return nil, trace.Wrap(err) @@ -137,6 +150,17 @@ type proxyDial struct { tlsConfig *tls.Config } +// getTLSConfig configures the dialers TLS config for a specified address. +func (d proxyDial) getTLSConfig(addr *utils.NetAddr) (*tls.Config, error) { + if d.tlsConfig == nil { + return nil, trace.BadParameter("TLS config was nil") + } + tlsConfig := d.tlsConfig.Clone() + tlsConfig.ServerName = addr.Host() + tlsConfig.InsecureSkipVerify = d.insecure + return tlsConfig, nil +} + // DialTimeout acts like Dial but takes a timeout. func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { // Build a proxy connection first. @@ -155,9 +179,10 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( if err != nil { return nil, trace.Wrap(err) } - conf := d.tlsConfig.Clone() - conf.ServerName = address.Host() - conf.InsecureSkipVerify = d.insecure + conf, err := d.getTLSConfig(address) + if err != nil { + return nil, trace.Wrap(err) + } conn = tls.Client(conn, conf) } return conn, nil @@ -179,9 +204,10 @@ func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) ( if err != nil { return nil, trace.Wrap(err) } - conf := d.tlsConfig.Clone() - conf.ServerName = address.Host() - conf.InsecureSkipVerify = d.insecure + conf, err := d.getTLSConfig(address) + if err != nil { + return nil, trace.Wrap(err) + } pconn = tls.Client(pconn, conf) } From 2117a9bb2c93bb4528dde67f5c7b7c4153ea197d Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Wed, 16 Mar 2022 12:07:25 -0700 Subject: [PATCH 23/43] Update NO_PROXY matching --- api/client/noproxy.go | 74 ---------------------------------------- api/client/proxy.go | 42 ++++++++--------------- api/client/proxy_test.go | 18 ++++++++++ 3 files changed, 33 insertions(+), 101 deletions(-) delete mode 100644 api/client/noproxy.go diff --git a/api/client/noproxy.go b/api/client/noproxy.go deleted file mode 100644 index a6115d20c6a5a..0000000000000 --- a/api/client/noproxy.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// HTTP client implementation. See RFC 2616. -// -// This is the low-level Transport implementation of RoundTripper. -// The high-level interface is in client.go. - -package client - -import ( - "os" - "strings" - - "github.com/gravitational/teleport/api/constants" -) - -// useProxy reports whether requests to addr should use a proxy, -// according to the NO_PROXY or no_proxy environment variable. -// addr is always a canonicalAddr with a host and port. -func useProxy(addr string) bool { - if len(addr) == 0 { - return true - } - var noProxy string - for _, env := range []string{constants.NoProxy, strings.ToLower(constants.NoProxy)} { - noProxy = os.Getenv(env) - if noProxy != "" { - break - } - } - - if noProxy == "" { - return true - } - if noProxy == "*" { - return false - } - - addr = strings.ToLower(strings.TrimSpace(addr)) - if hasPort(addr) { - addr = addr[:strings.LastIndex(addr, ":")] - } - for _, p := range strings.Split(noProxy, ",") { - p = strings.ToLower(strings.TrimSpace(p)) - if len(p) == 0 { - continue - } - if hasPort(p) { - p = p[:strings.LastIndex(p, ":")] - } - if addr == p { - return false - } - if len(p) == 0 { - // There is no host part, likely the entry is malformed; ignore. - continue - } - if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) { - // no_proxy ".foo.com" matches "bar.foo.com" or "foo.com" - return false - } - if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' { - // no_proxy "foo.com" matches "bar.foo.com" - return false - } - } - return true -} - -// Given a string of the form "host", "host:port", or "[ipv6::address]:port", -// return true if the string includes a port. -func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } diff --git a/api/client/proxy.go b/api/client/proxy.go index 42d448c8e9040..e4af72c5946b0 100644 --- a/api/client/proxy.go +++ b/api/client/proxy.go @@ -22,12 +22,11 @@ import ( "net" "net/http" "net/url" - "os" "strings" - "github.com/gravitational/teleport/api/constants" "github.com/gravitational/trace" "github.com/siddontang/go/log" + "golang.org/x/net/http/httpproxy" ) // DialProxy creates a connection to a server via an HTTP Proxy. @@ -88,32 +87,21 @@ func DialProxyWithDialer(ctx context.Context, proxyAddr, addr string, dialer Con // GetProxyAddress gets the HTTP proxy address to use for a given address, if any. func GetProxyAddress(addr string) string { - envs := []string{ - constants.HTTPSProxy, - strings.ToLower(constants.HTTPSProxy), - constants.HTTPProxy, - strings.ToLower(constants.HTTPProxy), + addrURL, err := parse(addr) + if err != nil { + return "" } - - for _, v := range envs { - envAddr := os.Getenv(v) - if envAddr == "" { - continue - } - proxyAddr, err := parse(envAddr) - if err != nil { - log.Debugf("Unable to parse environment variable %q: %q.", v, envAddr) - continue - } - log.Debugf("Successfully parsed environment variable %q: %q to %q.", v, envAddr, proxyAddr) - if !useProxy(addr) { - log.Debugf("Matched NO_PROXY override for %q: %q, going to ignore proxy variable.", v, envAddr) - return "" + proxyFunc := httpproxy.FromEnvironment().ProxyFunc() + for _, scheme := range []string{"https", "http"} { + addrURL.Scheme = scheme + proxyURL, err := proxyFunc(addrURL) + if err == nil && proxyURL != nil { + // Trim scheme and leading slashes. + proxyURL.Scheme = "" + return proxyURL.String()[2:] } - return proxyAddr } - log.Debugf("No valid environment variables found.") return "" } @@ -137,14 +125,14 @@ func (bc *bufferedConn) Read(b []byte) (n int, err error) { // parse will extract the host:port of the proxy to dial to. If the // value is not prefixed by "http", then it will prepend "http" and try. -func parse(addr string) (string, error) { +func parse(addr string) (*url.URL, error) { proxyurl, err := url.Parse(addr) if err != nil || !strings.HasPrefix(proxyurl.Scheme, "http") { proxyurl, err = url.Parse("http://" + addr) if err != nil { - return "", trace.Wrap(err) + return nil, trace.Wrap(err) } } - return proxyurl.Host, nil + return proxyurl, nil } diff --git a/api/client/proxy_test.go b/api/client/proxy_test.go index 29cffc12e1b4f..b6127a9ec0d80 100644 --- a/api/client/proxy_test.go +++ b/api/client/proxy_test.go @@ -82,6 +82,24 @@ func TestGetProxyAddress(t *testing.T) { proxyAddr: "", targetAddr: "bla.example.com:1234", }, + { + info: "valid, no_proxy blocks matching port", + env: []env{ + {name: "https_proxy", val: "proxy:9999"}, + {name: "no_proxy", val: "example.com:1234"}, + }, + proxyAddr: "", + targetAddr: "example.com:1234", + }, + { + info: "valid, no_proxy matches host but not port", + env: []env{ + {name: "https_proxy", val: "proxy:9999"}, + {name: "no_proxy", val: "example.com:1234"}, + }, + proxyAddr: "proxy:9999", + targetAddr: "example.com:5678", + }, } for i, tt := range tests { From 5d69d456ac62ac699cdc31e7b2fc930429e6da16 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Thu, 17 Mar 2022 12:33:07 -0700 Subject: [PATCH 24/43] Change GetProxyAddress return type --- api/client/contextdialer.go | 2 +- api/client/proxy.go | 18 ++++++++---------- api/client/proxy_test.go | 6 +++++- lib/utils/proxy/proxy.go | 13 +++++++------ 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/api/client/contextdialer.go b/api/client/contextdialer.go index 5c3725c5a1631..7ec88318a72dd 100644 --- a/api/client/contextdialer.go +++ b/api/client/contextdialer.go @@ -57,7 +57,7 @@ func NewDirectDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { dialer := NewDirectDialer(keepAlivePeriod, dialTimeout) return ContextDialerFunc(func(ctx context.Context, network, addr string) (net.Conn, error) { - if proxyAddr := GetProxyAddress(addr); proxyAddr != "" { + if proxyAddr := GetProxyAddress(addr); proxyAddr != nil { return DialProxyWithDialer(ctx, proxyAddr, addr, dialer) } return dialer.DialContext(ctx, network, addr) diff --git a/api/client/proxy.go b/api/client/proxy.go index e4af72c5946b0..0444987f58476 100644 --- a/api/client/proxy.go +++ b/api/client/proxy.go @@ -30,15 +30,15 @@ import ( ) // DialProxy creates a connection to a server via an HTTP Proxy. -func DialProxy(ctx context.Context, proxyAddr, addr string) (net.Conn, error) { +func DialProxy(ctx context.Context, proxyAddr *url.URL, addr string) (net.Conn, error) { return DialProxyWithDialer(ctx, proxyAddr, addr, &net.Dialer{}) } // DialProxyWithDialer creates a connection to a server via an HTTP Proxy using a specified dialer. -func DialProxyWithDialer(ctx context.Context, proxyAddr, addr string, dialer ContextDialer) (net.Conn, error) { - conn, err := dialer.DialContext(ctx, "tcp", proxyAddr) +func DialProxyWithDialer(ctx context.Context, proxyAddr *url.URL, addr string, dialer ContextDialer) (net.Conn, error) { + conn, err := dialer.DialContext(ctx, "tcp", proxyAddr.Host) if err != nil { - log.Warnf("Unable to dial to proxy: %v: %v.", proxyAddr, err) + log.Warnf("Unable to dial to proxy: %v: %v.", proxyAddr.Host, err) return nil, trace.ConvertSystemError(err) } @@ -86,23 +86,21 @@ func DialProxyWithDialer(ctx context.Context, proxyAddr, addr string, dialer Con } // GetProxyAddress gets the HTTP proxy address to use for a given address, if any. -func GetProxyAddress(addr string) string { +func GetProxyAddress(addr string) *url.URL { addrURL, err := parse(addr) if err != nil { - return "" + return nil } proxyFunc := httpproxy.FromEnvironment().ProxyFunc() for _, scheme := range []string{"https", "http"} { addrURL.Scheme = scheme proxyURL, err := proxyFunc(addrURL) if err == nil && proxyURL != nil { - // Trim scheme and leading slashes. - proxyURL.Scheme = "" - return proxyURL.String()[2:] + return proxyURL } } - return "" + return nil } // bufferedConn is used when part of the data on a connection has already been diff --git a/api/client/proxy_test.go b/api/client/proxy_test.go index b6127a9ec0d80..9651102de1b2b 100644 --- a/api/client/proxy_test.go +++ b/api/client/proxy_test.go @@ -108,7 +108,11 @@ func TestGetProxyAddress(t *testing.T) { t.Setenv(env.name, env.val) } p := GetProxyAddress(tt.targetAddr) - require.Equal(t, tt.proxyAddr, p) + if tt.proxyAddr == "" { + require.Nil(t, p) + } else { + require.Equal(t, tt.proxyAddr, p.Host) + } }) } } diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index 90c8226cced13..ff8a49ed0eb47 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -19,6 +19,7 @@ import ( "context" "crypto/tls" "net" + "net/url" "time" "github.com/gravitational/trace" @@ -140,8 +141,8 @@ func (d directDial) DialTimeout(network, address string, timeout time.Duration) } type proxyDial struct { - // proxyHost is the HTTPS proxy address. - proxyHost string + // proxyAddr is the HTTPS proxy address. + proxyAddr *url.URL // insecure is whether to skip certificate validation. insecure bool // tlsRoutingEnabled indicates that proxy is running in TLSRouting mode. @@ -170,7 +171,7 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( defer cancel() ctx = timeoutCtx } - conn, err := apiclient.DialProxy(ctx, d.proxyHost, address) + conn, err := apiclient.DialProxy(ctx, d.proxyAddr, address) if err != nil { return nil, trace.Wrap(err) } @@ -192,7 +193,7 @@ func (d proxyDial) DialTimeout(network, address string, timeout time.Duration) ( // SSH connection. func (d proxyDial) Dial(network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { // Build a proxy connection first. - pconn, err := apiclient.DialProxy(context.Background(), d.proxyHost, addr) + pconn, err := apiclient.DialProxy(context.Background(), d.proxyAddr, addr) if err != nil { return nil, trace.Wrap(err) } @@ -264,7 +265,7 @@ func DialerFromEnvironment(addr string, opts ...DialerOptionFunc) Dialer { // If no proxy settings are in environment return regular ssh dialer, // otherwise return a proxy dialer. - if proxyAddr == "" { + if proxyAddr == nil { log.Debugf("No proxy set in environment, returning direct dialer.") return directDial{ insecure: options.insecureSkipTLSVerify, @@ -274,7 +275,7 @@ func DialerFromEnvironment(addr string, opts ...DialerOptionFunc) Dialer { } log.Debugf("Found proxy %q in environment, returning proxy dialer.", proxyAddr) return proxyDial{ - proxyHost: proxyAddr, + proxyAddr: proxyAddr, insecure: options.insecureSkipTLSVerify, tlsRoutingEnabled: options.tlsRoutingEnabled, tlsConfig: options.tlsConfig, From b3f1d1349362989879626f01fb10d5c584e3228b Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Mon, 21 Mar 2022 16:27:23 -0700 Subject: [PATCH 25/43] Add ProxyAwareRoundTripper --- api/client/contextdialer.go | 3 +- api/client/proxy.go | 34 -------------- api/client/proxy/proxy.go | 69 ++++++++++++++++++++++++++++ api/client/{ => proxy}/proxy_test.go | 25 +++++++++- api/client/webclient/webclient.go | 5 +- lib/client/https_client.go | 9 ++-- lib/utils/proxy/proxy.go | 3 +- 7 files changed, 105 insertions(+), 43 deletions(-) create mode 100644 api/client/proxy/proxy.go rename api/client/{ => proxy}/proxy_test.go (82%) diff --git a/api/client/contextdialer.go b/api/client/contextdialer.go index 7ec88318a72dd..a8396165aa173 100644 --- a/api/client/contextdialer.go +++ b/api/client/contextdialer.go @@ -22,6 +22,7 @@ import ( "net" "time" + "github.com/gravitational/teleport/api/client/proxy" "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/utils/sshutils" @@ -57,7 +58,7 @@ func NewDirectDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { dialer := NewDirectDialer(keepAlivePeriod, dialTimeout) return ContextDialerFunc(func(ctx context.Context, network, addr string) (net.Conn, error) { - if proxyAddr := GetProxyAddress(addr); proxyAddr != nil { + if proxyAddr := proxy.GetProxyAddress(addr); proxyAddr != nil { return DialProxyWithDialer(ctx, proxyAddr, addr, dialer) } return dialer.DialContext(ctx, network, addr) diff --git a/api/client/proxy.go b/api/client/proxy.go index 0444987f58476..9e933034f5986 100644 --- a/api/client/proxy.go +++ b/api/client/proxy.go @@ -22,11 +22,9 @@ import ( "net" "net/http" "net/url" - "strings" "github.com/gravitational/trace" "github.com/siddontang/go/log" - "golang.org/x/net/http/httpproxy" ) // DialProxy creates a connection to a server via an HTTP Proxy. @@ -85,24 +83,6 @@ func DialProxyWithDialer(ctx context.Context, proxyAddr *url.URL, addr string, d }, nil } -// GetProxyAddress gets the HTTP proxy address to use for a given address, if any. -func GetProxyAddress(addr string) *url.URL { - addrURL, err := parse(addr) - if err != nil { - return nil - } - proxyFunc := httpproxy.FromEnvironment().ProxyFunc() - for _, scheme := range []string{"https", "http"} { - addrURL.Scheme = scheme - proxyURL, err := proxyFunc(addrURL) - if err == nil && proxyURL != nil { - return proxyURL - } - } - - return nil -} - // bufferedConn is used when part of the data on a connection has already been // read by a *bufio.Reader. Reads will first try and read from the // *bufio.Reader and when everything has been read, reads will go to the @@ -120,17 +100,3 @@ func (bc *bufferedConn) Read(b []byte) (n int, err error) { } return bc.Conn.Read(b) } - -// parse will extract the host:port of the proxy to dial to. If the -// value is not prefixed by "http", then it will prepend "http" and try. -func parse(addr string) (*url.URL, error) { - proxyurl, err := url.Parse(addr) - if err != nil || !strings.HasPrefix(proxyurl.Scheme, "http") { - proxyurl, err = url.Parse("http://" + addr) - if err != nil { - return nil, trace.Wrap(err) - } - } - - return proxyurl, nil -} diff --git a/api/client/proxy/proxy.go b/api/client/proxy/proxy.go new file mode 100644 index 0000000000000..7477d6b897b21 --- /dev/null +++ b/api/client/proxy/proxy.go @@ -0,0 +1,69 @@ +package proxy + +import ( + "net/http" + "net/url" + "strings" + + "github.com/gravitational/trace" + "golang.org/x/net/http/httpproxy" +) + +// GetProxyAddress gets the HTTP proxy address to use for a given address, if any. +func GetProxyAddress(addr string) *url.URL { + addrURL, err := parse(addr) + if err != nil { + return nil + } + proxyFunc := httpproxy.FromEnvironment().ProxyFunc() + for _, scheme := range []string{"https", "http"} { + addrURL.Scheme = scheme + proxyURL, err := proxyFunc(addrURL) + if err == nil && proxyURL != nil { + return proxyURL + } + } + + return nil +} + +// parse will extract the host:port of the proxy to dial to. If the +// value is not prefixed by "http", then it will prepend "http" and try. +func parse(addr string) (*url.URL, error) { + proxyurl, err := url.Parse(addr) + if err != nil || !strings.HasPrefix(proxyurl.Scheme, "http") { + proxyurl, err = url.Parse("http://" + addr) + if err != nil { + return nil, trace.Wrap(err) + } + } + + return proxyurl, nil +} + +// ProxyAwareRoundTripper is a wrapper for http.Transport that can modify roundtrips as needed. +type ProxyAwareRoundTripper struct { + http.Transport +} + +func (rt *ProxyAwareRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + tlsConfig := rt.Transport.TLSClientConfig + if tlsConfig == nil { + return rt.Transport.RoundTrip(req) + } + httpProxy := httpproxy.FromEnvironment().HTTPProxy + if httpProxy == "" { + return rt.Transport.RoundTrip(req) + } + httpProxyURL, err := parse(httpProxy) + if err != nil { + return nil, trace.Wrap(err) + } + // Use plain HTTP if proxying via http://localhost in insecure mode. + if tlsConfig.InsecureSkipVerify && + httpProxyURL.Scheme == "http" && + httpProxyURL.Hostname() == "localhost" { + req.URL.Scheme = "http" + } + return rt.Transport.RoundTrip(req) +} diff --git a/api/client/proxy_test.go b/api/client/proxy/proxy_test.go similarity index 82% rename from api/client/proxy_test.go rename to api/client/proxy/proxy_test.go index 9651102de1b2b..33e2239a4fd52 100644 --- a/api/client/proxy_test.go +++ b/api/client/proxy/proxy_test.go @@ -14,13 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package client +package proxy import ( + "crypto/tls" "fmt" + "net/http" + "net/http/httptest" + "net/url" "testing" "github.com/stretchr/testify/require" + "golang.org/x/net/http/httpproxy" ) func TestGetProxyAddress(t *testing.T) { @@ -116,3 +121,21 @@ func TestGetProxyAddress(t *testing.T) { }) } } + +func TestProxyAwareRoundTripper(t *testing.T) { + t.Setenv("HTTP_PROXY", "http://localhost:8888") + rt := ProxyAwareRoundTripper{ + Transport: http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + Proxy: func(req *http.Request) (*url.URL, error) { + return httpproxy.FromEnvironment().ProxyFunc()(req.URL) + }, + }, + } + req := httptest.NewRequest(http.MethodGet, "https://localhost:9999", nil) + // Don't care about response, only if the scheme changed. + rt.RoundTrip(req) + require.Equal(t, "http", req.URL.Scheme) +} diff --git a/api/client/webclient/webclient.go b/api/client/webclient/webclient.go index 53a496587f5b8..e0f5e1b0d8f77 100644 --- a/api/client/webclient/webclient.go +++ b/api/client/webclient/webclient.go @@ -30,6 +30,7 @@ import ( "strconv" "strings" + "github.com/gravitational/teleport/api/client/proxy" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/utils" @@ -42,7 +43,7 @@ import ( // newWebClient creates a new client to the HTTPS web proxy. func newWebClient(insecure bool, pool *x509.CertPool) *http.Client { return &http.Client{ - Transport: &http.Transport{ + Transport: &proxy.ProxyAwareRoundTripper{Transport: http.Transport{ TLSClientConfig: &tls.Config{ RootCAs: pool, InsecureSkipVerify: insecure, @@ -50,7 +51,7 @@ func newWebClient(insecure bool, pool *x509.CertPool) *http.Client { Proxy: func(req *http.Request) (*url.URL, error) { return httpproxy.FromEnvironment().ProxyFunc()(req.URL) }, - }, + }}, } } diff --git a/lib/client/https_client.go b/lib/client/https_client.go index c9f01aead2cb0..a1e2bfae73032 100644 --- a/lib/client/https_client.go +++ b/lib/client/https_client.go @@ -24,6 +24,7 @@ import ( "net/url" "github.com/gravitational/teleport" + apiproxy "github.com/gravitational/teleport/api/client/proxy" apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/httplib" "github.com/gravitational/teleport/lib/utils" @@ -40,12 +41,12 @@ func NewInsecureWebClient() *http.Client { tlsConfig.InsecureSkipVerify = true return &http.Client{ - Transport: &http.Transport{ + Transport: &apiproxy.ProxyAwareRoundTripper{Transport: http.Transport{ TLSClientConfig: tlsConfig, Proxy: func(req *http.Request) (*url.URL, error) { return httpproxy.FromEnvironment().ProxyFunc()(req.URL) }, - }, + }}, } } @@ -56,12 +57,12 @@ func newClientWithPool(pool *x509.CertPool) *http.Client { tlsConfig.RootCAs = pool return &http.Client{ - Transport: &http.Transport{ + Transport: &apiproxy.ProxyAwareRoundTripper{Transport: http.Transport{ TLSClientConfig: tlsConfig, Proxy: func(req *http.Request) (*url.URL, error) { return httpproxy.FromEnvironment().ProxyFunc()(req.URL) }, - }, + }}, } } diff --git a/lib/utils/proxy/proxy.go b/lib/utils/proxy/proxy.go index ff8a49ed0eb47..d157ffe0fd931 100644 --- a/lib/utils/proxy/proxy.go +++ b/lib/utils/proxy/proxy.go @@ -26,6 +26,7 @@ import ( "github.com/gravitational/teleport" apiclient "github.com/gravitational/teleport/api/client" + apiproxy "github.com/gravitational/teleport/api/client/proxy" "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/utils" @@ -256,7 +257,7 @@ func WithInsecureSkipTLSVerify(insecure bool) DialerOptionFunc { // server directly. func DialerFromEnvironment(addr string, opts ...DialerOptionFunc) Dialer { // Try and get proxy addr from the environment. - proxyAddr := apiclient.GetProxyAddress(addr) + proxyAddr := apiproxy.GetProxyAddress(addr) var options dialerOptions for _, opt := range opts { From 2a6b7b23f9f741d97653d20112634701190d05ad Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 22 Mar 2022 10:09:12 -0700 Subject: [PATCH 26/43] Remove siddontang logger from proxy.go --- api/client/proxy.go | 2 +- api/go.mod | 1 - api/go.sum | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/api/client/proxy.go b/api/client/proxy.go index 0444987f58476..ad82e27503b35 100644 --- a/api/client/proxy.go +++ b/api/client/proxy.go @@ -25,7 +25,7 @@ import ( "strings" "github.com/gravitational/trace" - "github.com/siddontang/go/log" + log "github.com/sirupsen/logrus" "golang.org/x/net/http/httpproxy" ) diff --git a/api/go.mod b/api/go.mod index a81a73769382d..3a7210c771f25 100644 --- a/api/go.mod +++ b/api/go.mod @@ -8,7 +8,6 @@ require ( github.com/google/go-cmp v0.5.4 github.com/gravitational/trace v1.1.17 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8 diff --git a/api/go.sum b/api/go.sum index 69ab03da359fa..72c1f18217730 100644 --- a/api/go.sum +++ b/api/go.sum @@ -61,8 +61,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM= -github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From bdddc69e0c49d708e5c635271decde24dc6245b5 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Tue, 22 Mar 2022 10:09:12 -0700 Subject: [PATCH 27/43] Remove siddontang logger from proxy.go --- api/client/proxy.go | 2 +- api/go.mod | 1 - api/go.sum | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/api/client/proxy.go b/api/client/proxy.go index 9e933034f5986..e128bb1b275a0 100644 --- a/api/client/proxy.go +++ b/api/client/proxy.go @@ -24,7 +24,7 @@ import ( "net/url" "github.com/gravitational/trace" - "github.com/siddontang/go/log" + log "github.com/sirupsen/logrus" ) // DialProxy creates a connection to a server via an HTTP Proxy. diff --git a/api/go.mod b/api/go.mod index a81a73769382d..3a7210c771f25 100644 --- a/api/go.mod +++ b/api/go.mod @@ -8,7 +8,6 @@ require ( github.com/google/go-cmp v0.5.4 github.com/gravitational/trace v1.1.17 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8 diff --git a/api/go.sum b/api/go.sum index 69ab03da359fa..72c1f18217730 100644 --- a/api/go.sum +++ b/api/go.sum @@ -61,8 +61,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM= -github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From a23bf820939893108512446cb3185ffd9c246574 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Wed, 23 Mar 2022 13:48:24 -0700 Subject: [PATCH 28/43] Merge master --- .cloudbuild/ci/integration-tests.yaml | 23 +- .cloudbuild/ci/unit-tests.yaml | 24 +- .../scripts/cmd/integration-tests/args.go | 4 + .../scripts/cmd/integration-tests/main.go | 30 +- .cloudbuild/scripts/cmd/unit-tests/main.go | 32 +- .cloudbuild/scripts/go.mod | 15 +- .cloudbuild/scripts/go.sum | 27 +- .../internal/artifacts/artifacts_test.go | 3 +- .cloudbuild/scripts/internal/etcd/start.go | 199 +- .cloudbuild/scripts/internal/git/configure.go | 145 + .cloudbuild/scripts/internal/git/deploykey.go | 34 + .../scripts/internal/git/knownhosts.go | 33 + .cloudbuild/scripts/internal/git/unshallow.go | 25 + .cloudbuild/scripts/internal/github/meta.go | 58 + .cloudbuild/scripts/internal/secrets/fetch.go | 41 + .drone.yml | 46 +- .github/ISSUE_TEMPLATE/testplan.md | 20 +- .../workflows/robot/internal/bot/assign.go | 123 +- .../robot/internal/bot/assign_test.go | 146 + .github/workflows/robot/internal/bot/bot.go | 12 +- .../workflows/robot/internal/bot/bot_test.go | 19 +- .github/workflows/robot/internal/bot/check.go | 2 +- .github/workflows/robot/internal/bot/label.go | 2 +- .../robot/internal/bot/label_test.go | 4 +- .github/workflows/robot/internal/env/env.go | 24 +- .../workflows/robot/internal/env/env_test.go | 2 +- .../workflows/robot/internal/env/structs.go | 24 +- .../workflows/robot/internal/github/github.go | 130 +- .../workflows/robot/internal/review/review.go | 39 +- .../robot/internal/review/review_test.go | 304 +- .golangci.yml | 8 + Cargo.lock | 12 +- Makefile | 15 +- README.md | 2 +- api/client/client.go | 121 +- api/client/client_test.go | 37 +- api/client/contextdialer.go | 14 +- api/client/credentials.go | 4 +- api/client/credentials_test.go | 40 +- api/client/proto/authservice.pb.go | 1983 +++++++++---- api/client/proto/authservice.proto | 44 +- api/client/proto/types.go | 2 +- api/client/proxy.go | 4 +- api/client/proxy/proxy.go | 16 + api/client/webclient/webclient.go | 103 +- api/client/webclient/webclient_test.go | 32 +- api/go.mod | 2 +- api/go.sum | 24 +- api/identityfile/identityfile.go | 5 +- api/profile/profile.go | 36 +- api/types/app_test.go | 4 - api/types/appserver.go | 9 +- api/types/constants.go | 6 +- api/types/database.go | 10 - api/types/databaseserver.go | 9 +- api/types/databaseserver_test.go | 4 - api/types/desktop.go | 97 + api/types/desktop_test.go | 88 + api/types/events/events.pb.go | 1365 ++++++--- api/types/events/events.proto | 19 + api/types/events/oneof.go | 24 +- api/types/events/unknown.go | 25 + api/types/kubernetes.go | 98 + api/types/kubernetes_test.go | 84 + api/types/resource.go | 47 +- api/types/resource_test.go | 50 + api/types/role.go | 21 +- api/types/server_test.go | 4 - api/types/types.pb.go | 1501 +++++----- api/types/types.proto | 5 + assets/aws/Makefile | 2 +- assets/backport/main.go | 3 +- assets/loadtest/k8s/Makefile | 9 +- assets/loadtest/k8s/auth-etcd.yaml | 3 + assets/loadtest/k8s/auth-firestore.yaml | 3 + assets/loadtest/k8s/prometheus.yaml | 291 ++ assets/loadtest/k8s/proxy.yaml | 4 +- assets/loadtest/k8s/secrets/Makefile | 20 +- build.assets/Dockerfile | 5 + build.assets/Makefile | 10 + build.assets/build-package.sh | 6 +- .../gomod/update-api-import-path/main.go | 15 +- build.assets/install | 2 +- build.assets/tooling/cmd/check/main.go | 6 + build.assets/tooling/cmd/check/main_test.go | 9 + constants.go | 4 + docker/teleport-ent-quickstart.yml | 4 +- docker/teleport-lab.yml | 8 +- docker/teleport-quickstart.yml | 4 +- docs/config.json | 99 +- .../guides/redis/redisinsight-add-config.png | Bin 0 -> 228417 bytes .../guides/redis/redisinsight-connected.png | Bin 0 -> 181286 bytes .../guides/redis/redisinsight-startup.png | Bin 0 -> 453447 bytes .../guides/redis/redisinsight-tls-config.png | Bin 0 -> 326444 bytes .../pages/access-controls/getting-started.mdx | 2 +- .../access-controls/guides/dual-authz.mdx | 97 +- .../access-controls/guides/impersonation.mdx | 32 +- .../guides/per-session-mfa.mdx | 48 +- docs/pages/access-controls/guides/u2f.mdx | 2 +- .../pages/access-controls/guides/webauthn.mdx | 2 +- docs/pages/access-controls/reference.mdx | 20 +- docs/pages/api/getting-started.mdx | 14 +- docs/pages/application-access/controls.mdx | 2 +- .../application-access/getting-started.mdx | 83 +- .../application-access/guides/api-access.mdx | 1 + .../application-access/guides/aws-console.mdx | 38 +- .../guides/connecting-apps.mdx | 38 +- docs/pages/cloud/faq.mdx | 4 +- .../pages/database-access/getting-started.mdx | 33 +- .../guides/cockroachdb-self-hosted.mdx | 4 +- .../database-access/guides/gui-clients.mdx | 31 + .../database-access/guides/mongodb-atlas.mdx | 6 +- .../guides/mysql-self-hosted.mdx | 53 +- .../guides/postgres-redshift.mdx | 177 +- .../guides/postgres-self-hosted.mdx | 50 +- docs/pages/database-access/guides/rds.mdx | 293 +- docs/pages/database-access/guides/redis.mdx | 375 +++ docs/pages/database-access/reference/aws.mdx | 219 +- docs/pages/database-access/reference/cli.mdx | 47 + docs/pages/desktop-access/getting-started.mdx | 12 +- docs/pages/desktop-access/reference.mdx | 19 + .../desktop-access/reference/clipboard.mdx | 46 + .../desktop-access/reference/sessions.mdx | 86 + docs/pages/enterprise/hsm.mdx | 2 +- docs/pages/enterprise/sso/adfs.mdx | 83 +- docs/pages/enterprise/sso/azuread.mdx | 30 +- docs/pages/enterprise/sso/gitlab.mdx | 12 +- .../pages/enterprise/sso/google-workspace.mdx | 2 + docs/pages/enterprise/sso/oidc.mdx | 12 +- docs/pages/enterprise/sso/okta.mdx | 12 +- docs/pages/enterprise/sso/one-login.mdx | 12 +- .../workflow/ssh-approval-jira-cloud.mdx | 115 +- .../workflow/ssh-approval-jira-server.mdx | 127 +- .../workflow/ssh-approval-mattermost.mdx | 93 +- .../workflow/ssh-approval-pagerduty.mdx | 101 +- .../workflow/ssh-approval-slack.mdx | 157 +- docs/pages/faq.mdx | 2 +- docs/pages/getting-started/docker-compose.mdx | 6 +- docs/pages/getting-started/linux-server.mdx | 49 +- docs/pages/includes/acme.mdx | 29 + .../database-access/aws-bootstrap.mdx | 47 + .../database-access/database-config.yaml | 2 +- .../database-access/guides-next-steps.mdx | 1 + .../pages/includes/database-access/guides.mdx | 3 + .../database-access/start-auth-proxy.mdx | 42 +- docs/pages/includes/dns-app-access.mdx | 4 + docs/pages/includes/dns.mdx | 59 + .../enterprise/oidcauthentication.mdx | 34 + .../enterprise/samlauthentication.mdx | 33 + docs/pages/includes/helm.mdx | 2 +- .../includes/kubernetes-access/helm-k8s.mdx | 2 +- .../includes/plugins/identity-export.mdx | 29 + docs/pages/includes/plugins/rbac.mdx | 60 + docs/pages/includes/tctl.mdx | 29 +- docs/pages/index.mdx | 4 +- docs/pages/installation.mdx | 13 + docs/pages/kubernetes-access/controls.mdx | 2 +- .../getting-started/agent.mdx | 132 +- .../getting-started/cluster.mdx | 54 +- docs/pages/kubernetes-access/guides.mdx | 3 - .../kubernetes-access/guides/federation.mdx | 49 +- .../kubernetes-access/guides/migration.mdx | 392 --- .../guides/multiple-clusters.mdx | 136 +- .../guides/standalone-teleport.mdx | 114 +- .../kubernetes-access/helm/guides/aws.mdx | 36 +- .../kubernetes-access/helm/guides/custom.mdx | 20 +- .../helm/guides/digitalocean.mdx | 2 + .../kubernetes-access/helm/guides/gcp.mdx | 35 +- .../helm/guides/migration.mdx | 22 +- .../helm/includes/helm-install.mdx | 48 - .../helm/includes/helm-repo-add.mdx | 11 + .../teleport-cluster-cloud-warning.mdx | 10 + .../includes/teleport-cluster-install.mdx | 12 + .../includes/teleport-cluster-prereqs.mdx | 6 + .../kubernetes-access/helm/reference.mdx | 2575 +---------------- .../helm/reference/teleport-cluster.mdx | 1348 +++++++++ .../helm/reference/teleport-kube-agent.mdx | 1334 +++++++++ docs/pages/kubernetes-access/introduction.mdx | 9 +- docs/pages/machine-id/getting-started.mdx | 236 ++ docs/pages/machine-id/guides.mdx | 11 + docs/pages/machine-id/guides/ansible.mdx | 122 + docs/pages/machine-id/introduction.mdx | 56 + docs/pages/machine-id/reference.mdx | 8 + docs/pages/machine-id/reference/cli.mdx | 90 + .../machine-id/reference/configuration.mdx | 99 + docs/pages/server-access/getting-started.mdx | 145 +- docs/pages/server-access/guides/ansible.mdx | 126 - docs/pages/server-access/guides/vscode.mdx | 59 +- docs/pages/setup/admin/adding-nodes.mdx | 322 ++- docs/pages/setup/admin/daemon.mdx | 2 +- docs/pages/setup/admin/github-sso.mdx | 162 +- docs/pages/setup/admin/troubleshooting.mdx | 24 +- docs/pages/setup/admin/trustedclusters.mdx | 2 +- .../pages/setup/deployments/aws-terraform.mdx | 2 +- docs/pages/setup/guides/fluentd.mdx | 238 +- docs/pages/setup/guides/joining-nodes-aws.mdx | 24 +- .../pages/setup/guides/terraform-provider.mdx | 229 +- docs/pages/setup/operations/ca-rotation.mdx | 259 +- docs/pages/setup/reference/authentication.mdx | 33 +- docs/pages/setup/reference/cli.mdx | 75 +- docs/pages/setup/reference/license.mdx | 25 +- docs/pages/setup/reference/metrics.mdx | 87 +- .../setup/reference/predicate-language.mdx | 71 + docs/postrelease.md | 7 +- dronegen/mac_pkg.go | 5 +- dronegen/main.go | 5 +- e | 2 +- examples/aws/cloudformation/ent.yaml | 30 +- examples/aws/cloudformation/oss.yaml | 30 +- examples/aws/terraform/AMIS.md | 102 +- .../terraform/ha-autoscale-cluster/README.md | 2 +- .../aws/terraform/starter-cluster/README.md | 2 +- .../aws/terraform/starter-cluster/vars.tf | 2 +- examples/chart/CONTRIBUTING.md | 51 + .../teleport-auto-trustedcluster/README.md | 5 + .../.lint/{service.yml => service.yaml} | 0 .../.lint/standalone-customsize.yaml | 2 +- .../.lint/standalone-existingpvc.yaml | 2 +- .../chart/teleport-cluster/.lint/volumes.yaml | 2 +- .../teleport-cluster/.lint/webauthn.yaml | 8 + examples/chart/teleport-cluster/README.md | 4 + .../teleport-cluster/templates/config.yaml | 24 +- .../chart/teleport-cluster/tests/README.md | 18 + .../__snapshot__/certificate_test.yaml.snap | 16 + .../__snapshot__/clusterrole_test.yaml.snap | 27 + .../clusterrolebinding_test.yaml.snap | 14 + .../tests/__snapshot__/config_test.yaml.snap | 1098 +++++++ .../__snapshot__/deployment_test.yaml.snap | 1585 ++++++++++ .../tests/__snapshot__/pdb_test.yaml.snap | 14 + .../tests/__snapshot__/psp_test.yaml.snap | 34 + .../tests/__snapshot__/pvc_test.yaml.snap | 30 + .../tests/__snapshot__/service_test.yaml.snap | 143 + .../serviceaccount_test.yaml.snap | 10 + .../tests/certificate_test.yaml | 29 + .../tests/clusterrole_test.yaml | 11 + .../tests/clusterrolebinding_test.yaml | 11 + .../teleport-cluster/tests/config_test.yaml | 330 +++ .../tests/deployment_test.yaml | 533 ++++ .../teleport-cluster/tests/pdb_test.yaml | 13 + .../teleport-cluster/tests/psp_test.yaml | 21 + .../teleport-cluster/tests/pvc_test.yaml | 65 + .../teleport-cluster/tests/service_test.yaml | 66 + .../tests/serviceaccount_test.yaml | 15 + .../chart/teleport-cluster/values.schema.json | 30 + examples/chart/teleport-cluster/values.yaml | 3 + examples/chart/teleport-daemonset/README.md | 5 + .../.lint/existing-data-volume.yaml | 5 + .../teleport-kube-agent/.lint/extra-args.yaml | 5 + .../teleport-kube-agent/.lint/extra-env.yaml | 7 + .../.lint/image-pull-policy-stateful.yaml | 7 + .../.lint/image-pull-policy.yaml | 5 + .../.lint/service-account-name.yaml | 5 + .../teleport-kube-agent/.lint/volumes.yaml | 2 +- examples/chart/teleport-kube-agent/README.md | 4 + .../teleport-kube-agent/templates/config.yaml | 6 + .../templates/deployment.yaml | 27 +- .../templates/statefulset.yaml | 27 +- .../chart/teleport-kube-agent/tests/README.md | 18 + .../__snapshot__/clusterrole_test.yaml.snap | 27 + .../clusterrolebinding_test.yaml.snap | 14 + .../tests/__snapshot__/config_test.yaml.snap | 658 +++++ .../__snapshot__/deployment_test.yaml.snap | 1346 +++++++++ .../tests/__snapshot__/pdb_test.yaml.snap | 14 + .../tests/__snapshot__/psp_test.yaml.snap | 34 + .../tests/__snapshot__/secret_test.yaml.snap | 22 + .../serviceaccount_test.yaml.snap | 10 + .../__snapshot__/statefulset_test.yaml.snap | 1454 ++++++++++ .../tests/clusterrole_test.yaml | 11 + .../tests/clusterrolebinding_test.yaml | 11 + .../tests/config_test.yaml | 209 ++ .../tests/deployment_test.yaml | 349 +++ .../teleport-kube-agent/tests/pdb_test.yaml | 13 + .../teleport-kube-agent/tests/psp_test.yaml | 21 + .../tests/secret_test.yaml | 30 + .../tests/serviceaccount_test.yaml | 15 + .../tests/statefulset_test.yaml | 369 +++ .../teleport-kube-agent/values.schema.json | 10 + .../chart/teleport-kube-agent/values.yaml | 9 + examples/chart/teleport/.helmignore | 1 + examples/chart/teleport/README.md | 5 + .../plugins/teleport-jira-cloud.toml | 20 + ...ra.toml => teleport-jira-self-hosted.toml} | 0 .../plugins/teleport-mattermost-cloud.toml | 21 + ...ost.toml => teleport-mattermost-self.toml} | 0 .../plugins/teleport-pagerduty-cloud.toml | 26 + ...duty.toml => teleport-pagerduty-self.toml} | 0 .../terraform/terraform-user-role-cloud.tf | 79 + ....tf => terraform-user-role-self-hosted.tf} | 4 +- go.mod | 9 +- go.sum | 32 +- integration/app_integration_test.go | 154 +- integration/db_integration_test.go | 173 +- integration/helpers.go | 19 +- integration/hsm/hsm_test.go | 20 +- integration/integration_test.go | 130 +- integration/kube_integration_test.go | 25 +- integration/proxy_helpers_test.go | 6 +- integration/proxy_test.go | 4 +- lib/asciitable/table.go | 62 +- lib/asciitable/table_test.go | 52 + lib/auth/access.go | 2 +- lib/auth/access_test.go | 2 +- lib/auth/accountrecovery.go | 3 +- lib/auth/accountrecovery_test.go | 2 +- lib/auth/api.go | 38 +- lib/auth/apiserver.go | 16 +- lib/auth/auth.go | 205 +- lib/auth/auth_login_test.go | 392 ++- lib/auth/auth_test.go | 38 + lib/auth/auth_with_roles.go | 127 +- lib/auth/auth_with_roles_test.go | 330 ++- lib/auth/bot.go | 10 +- lib/auth/clt.go | 187 +- lib/auth/clt_test.go | 91 + lib/auth/db.go | 6 +- lib/auth/desktop.go | 2 +- lib/auth/github.go | 2 +- lib/auth/grpcserver.go | 35 +- lib/auth/grpcserver_test.go | 151 +- lib/auth/helpers.go | 8 +- lib/auth/httpfallback.go | 55 +- lib/auth/init.go | 22 +- lib/auth/init_test.go | 9 +- lib/auth/join.go | 7 +- lib/auth/join_test.go | 15 +- lib/auth/kube.go | 8 +- lib/auth/methods.go | 124 +- lib/auth/middleware.go | 21 +- lib/auth/mocku2f/mocku2f.go | 10 +- lib/auth/oidc.go | 8 +- lib/auth/password.go | 2 +- lib/auth/permissions.go | 10 +- lib/auth/permissions_test.go | 4 +- lib/auth/register.go | 37 - lib/auth/rotate.go | 15 +- lib/auth/saml.go | 8 +- lib/auth/session_access.go | 66 +- lib/auth/session_access_test.go | 44 +- lib/auth/sessions.go | 4 +- lib/auth/state_unix.go | 1 + lib/auth/tls_test.go | 130 +- lib/auth/trustedcluster.go | 53 +- lib/auth/trustedcluster_test.go | 126 + lib/auth/usertoken.go | 5 +- lib/auth/webauthn/login.go | 12 + lib/auth/webauthn/register.go | 13 + lib/auth/webauthn/register_test.go | 84 + lib/auth/webauthncli/api.go | 64 + lib/auth/webauthncli/export_fido2_test.go | 23 + lib/auth/webauthncli/fido2.go | 776 +++++ lib/auth/webauthncli/fido2_common.go | 75 + lib/auth/webauthncli/fido2_other.go | 48 + lib/auth/webauthncli/fido2_test.go | 1438 +++++++++ .../webauthncli/{login.go => u2f_login.go} | 11 +- .../{login_test.go => u2f_login_test.go} | 8 +- .../{register.go => u2f_register.go} | 11 +- ...{register_test.go => u2f_register_test.go} | 18 +- lib/backend/backend.go | 12 +- lib/backend/backend_test.go | 19 + lib/backend/dynamo/dynamodbbk.go | 16 +- lib/backend/dynamo/dynamodbbk_test.go | 7 +- lib/backend/etcdbk/etcd.go | 11 +- lib/backend/firestore/firestorebk.go | 1 - lib/backend/lite/lite.go | 164 +- lib/backend/lite/litemem_test.go | 56 - lib/backend/memory/memory.go | 1 - lib/backend/report.go | 3 - lib/backend/sanitize.go | 19 +- lib/backend/sanitize_test.go | 101 +- lib/backend/test/suite.go | 76 +- lib/backend/wrap.go | 3 - lib/benchmark/benchmark.go | 5 +- lib/bpf/bpf_test.go | 7 +- lib/cache/cache.go | 32 +- lib/cache/cache_test.go | 27 +- lib/cache/collections.go | 10 +- lib/cgroup/cgroup.go | 22 +- lib/cgroup/cgroup_test.go | 5 +- lib/client/api.go | 568 ++-- lib/client/api_login_test.go | 45 +- lib/client/api_test.go | 62 +- lib/client/client.go | 94 +- lib/client/client_test.go | 5 +- lib/client/{export_test.go => export.go} | 0 lib/client/https_client_test.go | 31 +- lib/client/identityfile/identity.go | 15 +- lib/client/identityfile/identity_test.go | 8 +- lib/client/keyagent_test.go | 3 +- lib/client/keystore.go | 45 +- lib/client/keystore_test.go | 16 +- lib/client/mfa.go | 31 +- lib/client/weblogin.go | 22 +- lib/client/weblogin_test.go | 4 +- lib/config/configuration.go | 36 +- lib/config/configuration_test.go | 12 +- lib/config/fileconf.go | 13 +- lib/datalog/access_test.go | 2 +- lib/defaults/defaults.go | 11 + lib/events/api.go | 3 + lib/events/auditlog_test.go | 9 +- lib/events/codes.go | 5 + lib/events/complete.go | 195 +- lib/events/complete_test.go | 145 + lib/events/dynamic.go | 16 +- lib/events/emitter_test.go | 3 +- lib/events/events_test.go | 3 +- lib/events/fields.go | 3 +- lib/events/filesessions/fileasync.go | 7 +- .../filesessions/fileasync_chaos_test.go | 5 +- lib/events/filesessions/fileasync_test.go | 17 +- lib/events/filesessions/filestream.go | 50 +- lib/events/filesessions/fileuploader_test.go | 5 +- lib/events/gcssessions/gcsstream.go | 7 +- lib/events/gcssessions/gcsstream_test.go | 34 + lib/events/playback.go | 3 +- lib/events/s3sessions/s3stream.go | 17 + lib/events/sessionlog.go | 7 +- lib/events/stream.go | 14 +- lib/events/test/streamsuite.go | 5 +- lib/events/test/suite.go | 8 +- lib/kube/kubeconfig/kubeconfig_test.go | 3 +- lib/kube/proxy/auth_test.go | 3 +- lib/kube/proxy/forwarder_test.go | 4 +- lib/kube/proxy/roundtrip.go | 3 +- lib/kube/proxy/sess.go | 105 +- lib/kube/utils/utils.go | 29 +- lib/kube/utils/utils_test.go | 30 + lib/multiplexer/multiplexer_test.go | 7 +- lib/restrictedsession/restricted_test.go | 3 +- lib/reversetunnel/agent.go | 6 +- lib/reversetunnel/agent_test.go | 2 +- lib/reversetunnel/api.go | 15 + lib/reversetunnel/conn.go | 10 - lib/reversetunnel/fake.go | 41 +- lib/reversetunnel/localsite.go | 64 +- lib/reversetunnel/localsite_test.go | 101 + lib/reversetunnel/remotesite.go | 6 +- lib/reversetunnel/resolver.go | 4 +- lib/reversetunnel/srv.go | 2 +- lib/reversetunnel/srv_test.go | 3 +- lib/reversetunnel/transport.go | 16 +- lib/service/connect.go | 4 +- lib/service/desktop.go | 1 + lib/service/service.go | 102 +- lib/service/service_test.go | 31 +- lib/service/state.go | 4 +- lib/services/access_request_test.go | 2 +- lib/services/database.go | 133 +- lib/services/database_test.go | 51 +- lib/services/desktop.go | 1 + lib/services/local/desktops.go | 67 + lib/services/local/desktops_test.go | 213 ++ lib/services/local/perf_test.go | 6 +- lib/services/local/presence.go | 89 +- lib/services/local/presence_test.go | 117 +- lib/services/local/session.go | 31 +- lib/services/local/trust.go | 12 +- lib/services/local/users.go | 2 + lib/services/local/users_test.go | 34 +- lib/services/matchers.go | 2 +- lib/services/matchers_test.go | 26 + lib/services/reconciler.go | 18 +- lib/services/reconciler_test.go | 8 +- lib/services/resource.go | 83 +- lib/services/role.go | 24 +- lib/services/role_test.go | 16 +- lib/services/suite/suite.go | 17 +- lib/services/trust.go | 10 +- lib/services/watcher.go | 58 +- lib/srv/alpnproxy/auth/auth_proxy.go | 23 +- lib/srv/alpnproxy/auth/auth_proxy_test.go | 76 + lib/srv/alpnproxy/common/protocols.go | 17 +- lib/srv/alpnproxy/proxy.go | 14 +- lib/srv/alpnproxy/proxy_test.go | 59 + lib/srv/app/aws/endpoints.go | 160 + lib/srv/app/aws/endpoints_test.go | 359 +++ lib/srv/app/aws/handler.go | 14 - lib/srv/app/server.go | 4 +- lib/srv/app/server_test.go | 2 +- lib/srv/app/watcher.go | 60 +- lib/srv/authhandlers.go | 3 +- lib/srv/ctx.go | 22 +- lib/srv/db/access_test.go | 83 +- lib/srv/db/audit_test.go | 8 +- lib/srv/db/ca.go | 9 +- lib/srv/db/ca_test.go | 20 +- lib/srv/db/cloud/watchers/rds.go | 25 +- lib/srv/db/cloud/watchers/redshift.go | 7 + lib/srv/db/cloud/watchers/watcher_test.go | 101 +- lib/srv/db/common/errors.go | 56 + lib/srv/db/common/test.go | 2 +- lib/srv/db/ha_test.go | 2 +- lib/srv/db/mongodb/connect.go | 23 +- lib/srv/db/mongodb/engine.go | 9 +- lib/srv/db/mongodb/test.go | 11 +- lib/srv/db/mysql/engine.go | 14 +- lib/srv/db/mysql/test.go | 25 + lib/srv/db/postgres/engine.go | 14 +- lib/srv/db/proxy_test.go | 4 +- lib/srv/db/proxyserver.go | 76 +- lib/srv/db/proxyserver_test.go | 8 +- lib/srv/db/redis/client.go | 293 ++ lib/srv/db/redis/cmds.go | 53 +- lib/srv/db/redis/engine.go | 86 +- lib/srv/db/redis/protocol/resp2.go | 54 +- lib/srv/db/redis/protocol/resp2_test.go | 106 + lib/srv/db/server.go | 4 +- lib/srv/db/watcher.go | 7 +- lib/srv/desktop/audit.go | 21 +- lib/srv/desktop/audit_test.go | 3 + lib/srv/desktop/discovery.go | 20 +- lib/srv/desktop/discovery_test.go | 10 +- lib/srv/desktop/rdp/rdpclient/client.go | 30 +- lib/srv/desktop/rdp/rdpclient/librdprs.h | 15 +- lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs | 233 +- lib/srv/desktop/rdp/rdpclient/src/lib.rs | 22 +- lib/srv/desktop/rdp/rdpclient/src/vchan.rs | 20 +- lib/srv/desktop/windows_server.go | 21 +- lib/srv/desktop/windows_server_test.go | 14 +- lib/srv/reexec.go | 19 +- lib/srv/regular/sshserver.go | 3 +- lib/srv/regular/sshserver_test.go | 9 +- lib/srv/sess.go | 202 +- lib/srv/sess_test.go | 246 ++ lib/srv/termmanager.go | 76 +- lib/srv/termmanager_test.go | 50 + lib/sshutils/scp/scp_test.go | 15 +- lib/sshutils/x11/display.go | 24 +- lib/sshutils/x11/display_test.go | 187 +- lib/tlsca/ca.go | 12 +- lib/utils/certs_test.go | 4 +- lib/utils/chconn_test.go | 4 +- lib/utils/cli.go | 74 +- lib/utils/cli_test.go | 4 +- lib/utils/conn.go | 3 +- lib/utils/environment_test.go | 3 +- lib/utils/jsontools_test.go | 5 +- lib/utils/kernel.go | 3 +- lib/utils/prompt/confirmation.go | 13 +- lib/utils/prompt/sync_reader.go | 55 + lib/utils/prompt/sync_reader_test.go | 66 + lib/utils/syslog.go | 4 +- lib/utils/timeout_test.go | 4 +- lib/utils/token.go | 4 +- lib/utils/utils.go | 7 +- lib/utils/utils_test.go | 8 +- lib/web/apiserver.go | 96 +- lib/web/apiserver_login_test.go | 139 +- lib/web/apiserver_test.go | 115 +- lib/web/app/transport.go | 13 +- lib/web/desktop/playback.go | 24 + lib/web/desktop/playback_test.go | 2 +- lib/web/gziphandler.go | 4 +- lib/web/join_tokens.go | 41 +- lib/web/join_tokens_test.go | 5 +- lib/web/mfa.go | 20 +- lib/web/resources_test.go | 4 +- lib/web/sessions.go | 11 +- lib/web/static_test.go | 11 +- lib/web/ui/perf_test.go | 8 +- lib/web/ui/usercontext.go | 3 - lib/web/ui/usercontext_test.go | 3 +- rfd/0039-sni-alpn-teleport-proxy-routing.md | 2 +- rfd/0043-kubeaccess-multiparty.md | 185 +- rfd/0046-database-access-config.md | 411 ++- rfd/0056-sql-backend.md | 137 + rfd/0060-gRPC-backend.md | 200 ++ tool/tbot/botfs/botfs.go | 156 + tool/tbot/botfs/fs_linux.go | 450 +++ tool/tbot/botfs/fs_other.go | 113 + tool/tbot/config/config.go | 101 +- tool/tbot/config/config_destination.go | 18 +- tool/tbot/config/config_test.go | 56 +- tool/tbot/config/configtemplate.go | 5 - tool/tbot/config/configtemplate_ssh.go | 121 +- tool/tbot/config/destination.go | 26 +- tool/tbot/config/destination_directory.go | 136 +- tool/tbot/config/destination_memory.go | 13 +- tool/tbot/destination/destination.go | 25 +- tool/tbot/identity/artifact.go | 144 + tool/tbot/identity/identity.go | 254 +- tool/tbot/identity/kinds.go | 86 + tool/tbot/init.go | 557 ++++ tool/tbot/init_test.go | 271 ++ tool/tbot/main.go | 534 +++- tool/tctl/common/app_command.go | 62 +- tool/tctl/common/auth_command.go | 89 +- tool/tctl/common/auth_command_test.go | 91 +- tool/tctl/common/bots_command.go | 38 +- tool/tctl/common/collection.go | 211 +- tool/tctl/common/db_command.go | 62 +- tool/tctl/common/desktop_command.go | 97 + tool/tctl/common/kube_command.go | 84 + tool/tctl/common/node_command.go | 93 +- tool/tctl/common/resource_command.go | 26 +- tool/tctl/common/resource_command_test.go | 8 +- tool/tctl/common/status_command.go | 14 +- tool/tctl/common/tctl.go | 8 + tool/tctl/common/token_command.go | 28 +- tool/tctl/common/usage.go | 10 +- tool/tctl/main.go | 3 + tool/teleport/common/teleport.go | 4 +- tool/teleport/common/teleport_test.go | 40 +- tool/tsh/app.go | 14 +- tool/tsh/aws.go | 3 +- tool/tsh/db.go | 12 +- tool/tsh/db_test.go | 3 +- tool/tsh/dbcmd.go | 2 +- tool/tsh/kube.go | 67 +- tool/tsh/mfa.go | 89 +- tool/tsh/proxy_test.go | 7 +- tool/tsh/resolve_default_addr.go | 3 +- tool/tsh/tsh.go | 173 +- tool/tsh/tsh_helper_test.go | 4 +- tool/tsh/tsh_test.go | 145 +- tool/tsh/tshconfig.go | 59 + webassets | 2 +- 617 files changed, 37048 insertions(+), 11203 deletions(-) create mode 100644 .cloudbuild/scripts/internal/git/configure.go create mode 100644 .cloudbuild/scripts/internal/git/deploykey.go create mode 100644 .cloudbuild/scripts/internal/git/knownhosts.go create mode 100644 .cloudbuild/scripts/internal/git/unshallow.go create mode 100644 .cloudbuild/scripts/internal/github/meta.go create mode 100644 .cloudbuild/scripts/internal/secrets/fetch.go create mode 100644 .github/workflows/robot/internal/bot/assign_test.go create mode 100644 api/types/desktop_test.go create mode 100644 api/types/events/unknown.go create mode 100644 api/types/kubernetes_test.go create mode 100644 assets/loadtest/k8s/prometheus.yaml create mode 100644 docs/img/database-access/guides/redis/redisinsight-add-config.png create mode 100644 docs/img/database-access/guides/redis/redisinsight-connected.png create mode 100644 docs/img/database-access/guides/redis/redisinsight-startup.png create mode 100644 docs/img/database-access/guides/redis/redisinsight-tls-config.png create mode 100644 docs/pages/database-access/guides/redis.mdx create mode 100644 docs/pages/desktop-access/reference.mdx create mode 100644 docs/pages/desktop-access/reference/clipboard.mdx create mode 100644 docs/pages/desktop-access/reference/sessions.mdx create mode 100644 docs/pages/includes/acme.mdx create mode 100644 docs/pages/includes/database-access/aws-bootstrap.mdx create mode 100644 docs/pages/includes/dns-app-access.mdx create mode 100644 docs/pages/includes/dns.mdx create mode 100644 docs/pages/includes/enterprise/oidcauthentication.mdx create mode 100644 docs/pages/includes/enterprise/samlauthentication.mdx create mode 100644 docs/pages/includes/plugins/identity-export.mdx create mode 100644 docs/pages/includes/plugins/rbac.mdx delete mode 100644 docs/pages/kubernetes-access/guides/migration.mdx delete mode 100644 docs/pages/kubernetes-access/helm/includes/helm-install.mdx create mode 100644 docs/pages/kubernetes-access/helm/includes/helm-repo-add.mdx create mode 100644 docs/pages/kubernetes-access/helm/includes/teleport-cluster-cloud-warning.mdx create mode 100644 docs/pages/kubernetes-access/helm/includes/teleport-cluster-install.mdx create mode 100644 docs/pages/kubernetes-access/helm/includes/teleport-cluster-prereqs.mdx create mode 100644 docs/pages/kubernetes-access/helm/reference/teleport-cluster.mdx create mode 100644 docs/pages/kubernetes-access/helm/reference/teleport-kube-agent.mdx create mode 100644 docs/pages/machine-id/getting-started.mdx create mode 100644 docs/pages/machine-id/guides.mdx create mode 100644 docs/pages/machine-id/guides/ansible.mdx create mode 100644 docs/pages/machine-id/introduction.mdx create mode 100644 docs/pages/machine-id/reference.mdx create mode 100644 docs/pages/machine-id/reference/cli.mdx create mode 100644 docs/pages/machine-id/reference/configuration.mdx delete mode 100644 docs/pages/server-access/guides/ansible.mdx create mode 100644 docs/pages/setup/reference/predicate-language.mdx create mode 100644 examples/chart/CONTRIBUTING.md rename examples/chart/teleport-cluster/.lint/{service.yml => service.yaml} (100%) create mode 100644 examples/chart/teleport-cluster/.lint/webauthn.yaml create mode 100644 examples/chart/teleport-cluster/tests/README.md create mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/certificate_test.yaml.snap create mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/clusterrole_test.yaml.snap create mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/clusterrolebinding_test.yaml.snap create mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/config_test.yaml.snap create mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/deployment_test.yaml.snap create mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/pdb_test.yaml.snap create mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/psp_test.yaml.snap create mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/pvc_test.yaml.snap create mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/service_test.yaml.snap create mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/serviceaccount_test.yaml.snap create mode 100644 examples/chart/teleport-cluster/tests/certificate_test.yaml create mode 100644 examples/chart/teleport-cluster/tests/clusterrole_test.yaml create mode 100644 examples/chart/teleport-cluster/tests/clusterrolebinding_test.yaml create mode 100644 examples/chart/teleport-cluster/tests/config_test.yaml create mode 100644 examples/chart/teleport-cluster/tests/deployment_test.yaml create mode 100644 examples/chart/teleport-cluster/tests/pdb_test.yaml create mode 100644 examples/chart/teleport-cluster/tests/psp_test.yaml create mode 100644 examples/chart/teleport-cluster/tests/pvc_test.yaml create mode 100644 examples/chart/teleport-cluster/tests/service_test.yaml create mode 100644 examples/chart/teleport-cluster/tests/serviceaccount_test.yaml create mode 100644 examples/chart/teleport-kube-agent/.lint/existing-data-volume.yaml create mode 100644 examples/chart/teleport-kube-agent/.lint/extra-args.yaml create mode 100644 examples/chart/teleport-kube-agent/.lint/extra-env.yaml create mode 100644 examples/chart/teleport-kube-agent/.lint/image-pull-policy-stateful.yaml create mode 100644 examples/chart/teleport-kube-agent/.lint/image-pull-policy.yaml create mode 100644 examples/chart/teleport-kube-agent/.lint/service-account-name.yaml create mode 100644 examples/chart/teleport-kube-agent/tests/README.md create mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/clusterrole_test.yaml.snap create mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/clusterrolebinding_test.yaml.snap create mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/config_test.yaml.snap create mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap create mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/pdb_test.yaml.snap create mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/psp_test.yaml.snap create mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/secret_test.yaml.snap create mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/serviceaccount_test.yaml.snap create mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap create mode 100644 examples/chart/teleport-kube-agent/tests/clusterrole_test.yaml create mode 100644 examples/chart/teleport-kube-agent/tests/clusterrolebinding_test.yaml create mode 100644 examples/chart/teleport-kube-agent/tests/config_test.yaml create mode 100644 examples/chart/teleport-kube-agent/tests/deployment_test.yaml create mode 100644 examples/chart/teleport-kube-agent/tests/pdb_test.yaml create mode 100644 examples/chart/teleport-kube-agent/tests/psp_test.yaml create mode 100644 examples/chart/teleport-kube-agent/tests/secret_test.yaml create mode 100644 examples/chart/teleport-kube-agent/tests/serviceaccount_test.yaml create mode 100644 examples/chart/teleport-kube-agent/tests/statefulset_test.yaml create mode 100644 examples/resources/plugins/teleport-jira-cloud.toml rename examples/resources/plugins/{teleport-jira.toml => teleport-jira-self-hosted.toml} (100%) create mode 100644 examples/resources/plugins/teleport-mattermost-cloud.toml rename examples/resources/plugins/{teleport-mattermost.toml => teleport-mattermost-self.toml} (100%) create mode 100644 examples/resources/plugins/teleport-pagerduty-cloud.toml rename examples/resources/plugins/{teleport-pagerduty.toml => teleport-pagerduty-self.toml} (100%) create mode 100644 examples/resources/terraform/terraform-user-role-cloud.tf rename examples/resources/terraform/{terraform-user-role.tf => terraform-user-role-self-hosted.tf} (93%) create mode 100644 lib/auth/webauthncli/api.go create mode 100644 lib/auth/webauthncli/export_fido2_test.go create mode 100644 lib/auth/webauthncli/fido2.go create mode 100644 lib/auth/webauthncli/fido2_common.go create mode 100644 lib/auth/webauthncli/fido2_other.go create mode 100644 lib/auth/webauthncli/fido2_test.go rename lib/auth/webauthncli/{login.go => u2f_login.go} (91%) rename lib/auth/webauthncli/{login_test.go => u2f_login_test.go} (98%) rename lib/auth/webauthncli/{register.go => u2f_register.go} (94%) rename lib/auth/webauthncli/{register_test.go => u2f_register_test.go} (92%) delete mode 100644 lib/backend/lite/litemem_test.go rename lib/client/{export_test.go => export.go} (100%) create mode 100644 lib/events/complete_test.go create mode 100644 lib/reversetunnel/localsite_test.go create mode 100644 lib/services/local/desktops_test.go create mode 100644 lib/srv/alpnproxy/auth/auth_proxy_test.go create mode 100644 lib/srv/app/aws/endpoints.go create mode 100644 lib/srv/app/aws/endpoints_test.go create mode 100644 lib/srv/db/redis/client.go create mode 100644 lib/srv/db/redis/protocol/resp2_test.go create mode 100644 lib/srv/termmanager_test.go create mode 100644 lib/utils/prompt/sync_reader.go create mode 100644 lib/utils/prompt/sync_reader_test.go create mode 100644 rfd/0056-sql-backend.md create mode 100644 rfd/0060-gRPC-backend.md create mode 100644 tool/tbot/botfs/botfs.go create mode 100644 tool/tbot/botfs/fs_linux.go create mode 100644 tool/tbot/botfs/fs_other.go create mode 100644 tool/tbot/identity/artifact.go create mode 100644 tool/tbot/identity/kinds.go create mode 100644 tool/tbot/init.go create mode 100644 tool/tbot/init_test.go create mode 100644 tool/tctl/common/desktop_command.go create mode 100644 tool/tctl/common/kube_command.go create mode 100644 tool/tsh/tshconfig.go diff --git a/.cloudbuild/ci/integration-tests.yaml b/.cloudbuild/ci/integration-tests.yaml index 8a64ecf50b964..61deb3012cf17 100644 --- a/.cloudbuild/ci/integration-tests.yaml +++ b/.cloudbuild/ci/integration-tests.yaml @@ -1,14 +1,14 @@ timeout: 25m options: machineType: E2_HIGHCPU_32 + + # This build needs to run in environments where the _GITHUB_DEPLOY_KEY_SRC + # substitution is defined, but also environments where it isn't. The + # ALLOW_LOOSE option disables GCBs strict checking of substitution usage, + # so that the build will still run if _GITHUB_DEPLOY_KEY_SRC is not defined. + substitution_option: ALLOW_LOOSE steps: - # GCB does a shallow checkout for a build, but if we want to check our changes - # against other branches we'll need to fetch the repo history. - - name: gcr.io/cloud-builders/git - id: fetch-history - args: ['fetch', '--unshallow'] - # Run the integration tests. Actual content of this job depends on the changes # detected in the PR - name: quay.io/gravitational/teleport-buildbox:teleport10 @@ -18,9 +18,10 @@ steps: args: - -c - | - go run ./cmd/integration-tests \ - -target $_BASE_BRANCH \ - -bucket test-logs \ - -build $BUILD_ID \ + go run ./cmd/integration-tests \ + -target "$_BASE_BRANCH" \ + -bucket test-logs \ + -build $BUILD_ID \ + -key-secret "$_GITHUB_DEPLOY_KEY_SRC" \ -a "test-logs/*.json" - timeout: 20m + timeout: 25m diff --git a/.cloudbuild/ci/unit-tests.yaml b/.cloudbuild/ci/unit-tests.yaml index c673e9485150c..ae9935c10088f 100644 --- a/.cloudbuild/ci/unit-tests.yaml +++ b/.cloudbuild/ci/unit-tests.yaml @@ -3,14 +3,13 @@ timeout: 25m options: machineType: 'E2_HIGHCPU_32' + # This build needs to run in environments where the _GITHUB_DEPLOY_KEY_SRC + # substitution is defined, but also environments where it isn't. The + # ALLOW_LOOSE option disables GCBs strict checking of substitution usage, + # so that the build will still run if _GITHUB_DEPLOY_KEY_SRC is not defined. + substitution_option: ALLOW_LOOSE + steps: - # GCB does a shallow checkout for a build, but if we want to check our changes - # against other branches we'll need to fetch the repo history. This takes less - # than 30s at the time of writing, so it is probably not worth tweaking. - - name: gcr.io/cloud-builders/git - id: fetch-history - args: ['fetch', '--unshallow'] - # Run the unit tests. Actual content of this job depends on the changes # detected in the PR - name: quay.io/gravitational/teleport-buildbox:teleport10 @@ -20,9 +19,10 @@ steps: args: - -c - | - go run ./cmd/unit-tests \ - -target $_BASE_BRANCH \ - -bucket test-logs \ - -build $BUILD_ID \ + go run ./cmd/unit-tests \ + -target $_BASE_BRANCH \ + -bucket test-logs \ + -build $BUILD_ID \ + -key-secret "$_GITHUB_DEPLOY_KEY_SRC" \ -a "test-logs/*.json" - timeout: 20m + timeout: 25m diff --git a/.cloudbuild/scripts/cmd/integration-tests/args.go b/.cloudbuild/scripts/cmd/integration-tests/args.go index aa29b10c71f91..2c98fab9c9077 100644 --- a/.cloudbuild/scripts/cmd/integration-tests/args.go +++ b/.cloudbuild/scripts/cmd/integration-tests/args.go @@ -33,6 +33,8 @@ type commandlineArgs struct { buildID string artifactSearchPatterns customflag.StringArray bucket string + githubKeySrc string + skipUnshallow bool } // validate ensures the suplied arguments are valid & internally consistent. @@ -85,6 +87,8 @@ func parseCommandLine() (*commandlineArgs, error) { flag.StringVar(&args.buildID, "build", "", "The build ID") flag.StringVar(&args.bucket, "bucket", "", "The artifact storage bucket.") flag.Var(&args.artifactSearchPatterns, "a", "Path to artifacts. May be shell-globbed, and have multiple entries.") + flag.StringVar(&args.githubKeySrc, "key-secret", "", "Location of github deploy token, as a Google Cloud Secret") + flag.BoolVar(&args.skipUnshallow, "skip-unshallow", false, "Skip unshallowing the repository.") flag.Parse() diff --git a/.cloudbuild/scripts/cmd/integration-tests/main.go b/.cloudbuild/scripts/cmd/integration-tests/main.go index f6de022e00fcc..3f64c8fee7a58 100644 --- a/.cloudbuild/scripts/cmd/integration-tests/main.go +++ b/.cloudbuild/scripts/cmd/integration-tests/main.go @@ -29,6 +29,8 @@ import ( "github.com/gravitational/teleport/.cloudbuild/scripts/internal/artifacts" "github.com/gravitational/teleport/.cloudbuild/scripts/internal/changes" "github.com/gravitational/teleport/.cloudbuild/scripts/internal/etcd" + "github.com/gravitational/teleport/.cloudbuild/scripts/internal/git" + "github.com/gravitational/teleport/.cloudbuild/scripts/internal/secrets" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" ) @@ -55,6 +57,26 @@ func innerMain() error { return trace.Wrap(err) } + // If a github deploy key location was supplied... + var deployKey []byte + if args.githubKeySrc != "" { + // fetch the deployment key from the GCB secret manager + log.Infof("Fetching deploy key from %s", args.githubKeySrc) + deployKey, err = secrets.Fetch(context.Background(), args.githubKeySrc) + if err != nil { + return trace.Wrap(err, "failed fetching deploy key") + } + } + + if !args.skipUnshallow { + unshallowCtx, unshallowCancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer unshallowCancel() + err = git.UnshallowRepository(unshallowCtx, args.workspace, deployKey) + if err != nil { + return trace.Wrap(err, "unshallow failed") + } + } + moduleCacheDir := filepath.Join(os.TempDir(), gomodcacheDir) gomodcache := fmt.Sprintf("GOMODCACHE=%s", moduleCacheDir) @@ -70,9 +92,6 @@ func innerMain() error { return nil } - cancelCtx, cancel := context.WithCancel(context.Background()) - defer cancel() - // From this point on, whatever happens we want to upload any artifacts // produced by the build defer func() { @@ -114,10 +133,13 @@ func innerMain() error { // diagnostic warnings that would pollute the build log and just confuse // people when they are trying to work out why their build failed. log.Printf("Starting etcd...") - err = etcd.Start(cancelCtx, args.workspace, nonrootUID, nonrootGID, gomodcache) + timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + etcdSvc, err := etcd.Start(timeoutCtx, args.workspace) if err != nil { return trace.Wrap(err, "failed starting etcd") } + defer etcdSvc.Stop() log.Printf("Running nonroot integration tests...") err = runNonrootIntegrationTests(args.workspace, nonrootUID, nonrootGID, gomodcache) diff --git a/.cloudbuild/scripts/cmd/unit-tests/main.go b/.cloudbuild/scripts/cmd/unit-tests/main.go index 8b374c726d772..3f8e52ef551ad 100644 --- a/.cloudbuild/scripts/cmd/unit-tests/main.go +++ b/.cloudbuild/scripts/cmd/unit-tests/main.go @@ -29,6 +29,8 @@ import ( "github.com/gravitational/teleport/.cloudbuild/scripts/internal/changes" "github.com/gravitational/teleport/.cloudbuild/scripts/internal/customflag" "github.com/gravitational/teleport/.cloudbuild/scripts/internal/etcd" + "github.com/gravitational/teleport/.cloudbuild/scripts/internal/git" + "github.com/gravitational/teleport/.cloudbuild/scripts/internal/secrets" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" ) @@ -48,6 +50,8 @@ type commandlineArgs struct { buildID string artifactSearchPatterns customflag.StringArray bucket string + githubKeySrc string + skipUnshallow bool } // NOTE: changing the interface to this build script may require follow-up @@ -61,6 +65,8 @@ func parseCommandLine() (commandlineArgs, error) { flag.StringVar(&args.buildID, "build", "", "The build ID") flag.StringVar(&args.bucket, "bucket", "", "The artifact storage bucket.") flag.Var(&args.artifactSearchPatterns, "a", "Path to artifacts. May be globbed, and have multiple entries.") + flag.StringVar(&args.githubKeySrc, "key-secret", "", "Location of github deploy token, as a Google Cloud Secret") + flag.BoolVar(&args.skipUnshallow, "skip-unshallow", false, "Skip unshallowing the repository.") flag.Parse() @@ -108,6 +114,26 @@ func run() error { return trace.Wrap(err) } + // If a github deploy key location was supplied... + var deployKey []byte + if args.githubKeySrc != "" { + // fetch the deployment key from the GCB secret manager + log.Infof("Fetching deploy key from %s", args.githubKeySrc) + deployKey, err = secrets.Fetch(context.Background(), args.githubKeySrc) + if err != nil { + return trace.Wrap(err, "failed fetching deploy key") + } + } + + if !args.skipUnshallow { + unshallowCtx, unshallowCancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer unshallowCancel() + err = git.UnshallowRepository(unshallowCtx, args.workspace, deployKey) + if err != nil { + return trace.Wrap(err, "unshallow failed") + } + } + log.Println("Analysing code changes") ch, err := changes.Analyze(args.workspace, args.targetBranch, args.commitSHA) if err != nil { @@ -121,11 +147,13 @@ func run() error { } log.Printf("Starting etcd...") - cancelCtx, cancel := context.WithCancel(context.Background()) + timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - if err := etcd.Start(cancelCtx, args.workspace, 0, 0); err != nil { + etcdSvc, err := etcd.Start(timeoutCtx, args.workspace) + if err != nil { return trace.Wrap(err, "failed starting etcd") } + defer etcdSvc.Stop() // From this point on, whatever happens we want to upload any artifacts // produced by the build diff --git a/.cloudbuild/scripts/go.mod b/.cloudbuild/scripts/go.mod index 300c5bb2c445a..49cf54361d31c 100644 --- a/.cloudbuild/scripts/go.mod +++ b/.cloudbuild/scripts/go.mod @@ -3,11 +3,15 @@ module github.com/gravitational/teleport/.cloudbuild/scripts go 1.17 require ( + cloud.google.com/go/secretmanager v1.2.0 cloud.google.com/go/storage v1.19.0 github.com/go-git/go-git/v5 v5.4.2 github.com/gravitational/trace v1.1.15 github.com/hashicorp/go-multierror v1.1.1 github.com/sirupsen/logrus v1.8.1 + github.com/stretchr/testify v1.7.0 + golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b + google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c ) require ( @@ -33,21 +37,18 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect - github.com/stretchr/objx v0.1.1 // indirect - github.com/stretchr/testify v1.7.0 // indirect + github.com/stretchr/objx v0.2.0 // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect go.opencensus.io v0.23.0 // indirect - golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/text v0.3.6 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/api v0.65.0 // indirect + google.golang.org/api v0.67.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 // indirect - google.golang.org/grpc v1.40.1 // indirect + google.golang.org/grpc v1.44.0 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect diff --git a/.cloudbuild/scripts/go.sum b/.cloudbuild/scripts/go.sum index a9f64ef4f6699..d2b25fdba3cd4 100644 --- a/.cloudbuild/scripts/go.sum +++ b/.cloudbuild/scripts/go.sum @@ -39,12 +39,15 @@ cloud.google.com/go/compute v0.1.0 h1:rSUBvAyVwNJ5uQCKNJFMwPtTvJkfN38b6Pvb9zZoqJ cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= cloud.google.com/go/iam v0.1.1 h1:4CapQyNFjiksks1/x7jsvsygFPhihslYk5GptIrlX68= cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/secretmanager v1.2.0 h1:VR6MzO4wjTj5jQKTPpsZhCF2PqqdAAZmN54BwJbQPhs= +cloud.google.com/go/secretmanager v1.2.0/go.mod h1:HNMYTaLrMrAN37vi2mM2vvFgjgaoCE1qvtccCIJwFRc= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -70,6 +73,7 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -77,7 +81,11 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -91,6 +99,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -235,9 +244,11 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -420,8 +431,9 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 h1:XDXtA5hveEEV8JB2l7nhMTp3t3cHp9ZpwcdjqyEWLlo= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -524,8 +536,9 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM= -google.golang.org/api v0.65.0 h1:MTW9c+LIBAbwoS1Gb+YV7NjFBt2f7GtAS5hIzh2NjgQ= google.golang.org/api v0.65.0/go.mod h1:ArYhxgGadlWmqO1IqVujw6Cs8IdD33bTmzKo2Sh+cbg= +google.golang.org/api v0.67.0 h1:lYaaLa+x3VVUhtosaK9xihwQ9H9KRa557REHwwZ2orM= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -597,8 +610,11 @@ google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q= google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c h1:TU4rFa5APdKTq0s6B7WTsH6Xmx0Knj86s6Biz56mErE= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -624,8 +640,9 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1 h1:pnP7OclFFFgFi4VHQDQDaoXUVauOFyktqTsqqgzFKbc= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/.cloudbuild/scripts/internal/artifacts/artifacts_test.go b/.cloudbuild/scripts/internal/artifacts/artifacts_test.go index eba6ca9b64c83..e5336608d7f11 100644 --- a/.cloudbuild/scripts/internal/artifacts/artifacts_test.go +++ b/.cloudbuild/scripts/internal/artifacts/artifacts_test.go @@ -21,7 +21,6 @@ import ( "context" "errors" "io" - "io/ioutil" "os" "path/filepath" "testing" @@ -35,7 +34,7 @@ func write(t *testing.T, data []byte, path ...string) string { t.Helper() filePath := filepath.Join(path...) require.NoError(t, os.MkdirAll(filepath.Dir(filePath), 0777)) - require.NoError(t, ioutil.WriteFile(filePath, data, 0644)) + require.NoError(t, os.WriteFile(filePath, data, 0644)) return filePath } diff --git a/.cloudbuild/scripts/internal/etcd/start.go b/.cloudbuild/scripts/internal/etcd/start.go index 932d368881c0c..fe4993228e047 100644 --- a/.cloudbuild/scripts/internal/etcd/start.go +++ b/.cloudbuild/scripts/internal/etcd/start.go @@ -17,59 +17,208 @@ limitations under the License. package etcd import ( + "bytes" "context" - "net" + "crypto/tls" + "crypto/x509" + "encoding/json" + "io" + "net/http" "os" "os/exec" - "syscall" + "path" "time" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" ) -// Start starts the etcd server using the Makefile `run-etcd` task and waits for it to start. -func Start(ctx context.Context, workspace string, uid, gid int, env ...string) error { - cmd := exec.CommandContext(ctx, "make", "run-etcd") - cmd.Dir = workspace - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr +const ( + metricsURL = "https://127.0.0.1:3379" +) + +type Instance struct { + process *exec.Cmd + dataDir string + kill context.CancelFunc + stdout bytes.Buffer + stderr bytes.Buffer +} + +func (etcd *Instance) Stop() { + log.Printf("Killing etcd process") + etcd.kill() + + log.Printf("Waiting for etcd process to exit...") + switch err := etcd.process.Wait().(type) { + case nil: + break + + case *exec.ExitError: + // we expect a return code of -1 because we've just killed the process + // above. If we get something else then etcd failed earlier for some + // reason and we should print diagnostic output + if err.ExitCode() != -1 { + log.Printf("Etcd exited with unexpected status %d", err.ExitCode()) + log.Printf("stderr:\n%s", etcd.stderr.String()) + } + + default: + log.Printf("Failed: %s", err) + } + + log.Printf("Removing data dir") + if err := os.RemoveAll(etcd.dataDir); err != nil { + log.Printf("Failed removing data dir: %s", err) + } +} + +// Start starts the etcd server using the keys extpected by the +// integration and unit tests +func Start(ctx context.Context, workspace string, env ...string) (*Instance, error) { + etcdBinary, err := exec.LookPath("etcd") + if err != nil { + return nil, trace.Wrap(err, "can't find etcd binary") + } + + dataDir, err := os.MkdirTemp(os.TempDir(), "teleport-etcd-*") + if err != nil { + return nil, trace.Wrap(err, "can't create temp dir") + } + + certsDir := path.Join(workspace, "examples", "etcd", "certs") + + etcdInstance := &Instance{} + var processCtx context.Context + processCtx, etcdInstance.kill = context.WithCancel(context.Background()) + + etcdInstance.process = exec.CommandContext(processCtx, etcdBinary, + "--name", "teleportstorage", + "--data-dir", dataDir, + "--initial-cluster-state", "new", + "--cert-file", path.Join(certsDir, "server-cert.pem"), + "--key-file", path.Join(certsDir, "server-key.pem"), + "--trusted-ca-file", path.Join(certsDir, "ca-cert.pem"), + "--advertise-client-urls=https://127.0.0.1:2379", + "--listen-client-urls=https://127.0.0.1:2379", + "--listen-metrics-urls", metricsURL, + "--client-cert-auth", + ) + etcdInstance.process.Dir = workspace + etcdInstance.process.Stdout = &etcdInstance.stdout + etcdInstance.process.Stderr = &etcdInstance.stderr if len(env) > 0 { - cmd.Env = append(os.Environ(), env...) + etcdInstance.process.Env = append(os.Environ(), env...) + } + + log.Printf("Launching etcd (%s)", etcdInstance.process.Path) + if err = etcdInstance.process.Start(); err != nil { + return nil, trace.Wrap(err, "failed starting etcd") } - // make etcd run under the supplied account - cmd.SysProcAttr = &syscall.SysProcAttr{ - Credential: &syscall.Credential{ - Uid: uint32(uid), - Gid: uint32(gid), - }, + timeoutCtx, cancelTimeout := context.WithTimeout(ctx, 5*time.Second) + defer cancelTimeout() + + if err := waitForEtcdToStart(timeoutCtx, certsDir); err != nil { + log.Printf("Etcd failed to come up. Tidying up.") + etcdInstance.Stop() + return nil, trace.Wrap(err, "failed while waiting for etcd to start") } - log.Printf("Launching etcd with %v in %q", cmd.Args, cmd.Dir) - go cmd.Run() + return etcdInstance, nil +} - log.Printf("Waiting for etcd to start...") +// waitForEtcdToStart polls an etcd server (as started with Start()) until either +// a) the etcd service reports itself as healthy, or +// b) the supplied context expires +func waitForEtcdToStart(ctx context.Context, certDir string) error { + log.Printf("Waiting for etcd to come up...") ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() - timeoutCtx, cancel := context.WithTimeout(ctx, 30*time.Second) - defer cancel() + client, err := newHTTPSTransport(certDir) + if err != nil { + return trace.Wrap(err, "failed to create https client") + } for { select { case <-ticker.C: - d := net.Dialer{Timeout: 100 * time.Millisecond} - _, err := d.Dial("tcp", "127.0.0.1:2379") - if err == nil { - log.Printf("Etcd is up") + healthy, err := pollForHealth(ctx, client, metricsURL+"/health") + if err != nil { + return trace.Wrap(err) + } + if healthy { + log.Printf("Etcd reporting healthy") return nil } - case <-timeoutCtx.Done(): + case <-ctx.Done(): return trace.Errorf("timed out waiting for etcd to start") } } } + +// newHTTPSTransport creates an HTTP client configured to use TLS with an etcd server +// as started with Start() +func newHTTPSTransport(certDir string) (*http.Client, error) { + caCertPath := path.Join(certDir, "ca-cert.pem") + caCert, err := os.ReadFile(caCertPath) + if err != nil { + return nil, trace.Wrap(err, "failed reading CA cert from %s", caCertPath) + } + + clientCert, err := tls.LoadX509KeyPair( + path.Join(certDir, "client-cert.pem"), path.Join(certDir, "client-key.pem")) + if err != nil { + return nil, trace.Wrap(err, "failed reading client cert") + } + + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.TLSClientConfig.RootCAs = x509.NewCertPool() + if !transport.TLSClientConfig.RootCAs.AppendCertsFromPEM(caCert) { + return nil, trace.Errorf("Failed adding CA cert") + } + transport.TLSClientConfig.Certificates = []tls.Certificate{clientCert} + + return &http.Client{Transport: transport}, nil +} + +// pollForHealth polls the etcd metrecs endpoint for status information, returning +// true if the service reports itself as healthy +func pollForHealth(ctx context.Context, client *http.Client, url string) (bool, error) { + type health struct { + Health string `json:"health"` + Reason string `json:"reason"` + } + + timeoutCtx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) + defer cancel() + + request, err := http.NewRequestWithContext(timeoutCtx, http.MethodGet, url, nil) + if err != nil { + return false, trace.Wrap(err, "Failed constructing poll request") + } + + // A request failure is considered "not healthy" rather than an error, as + // the etcd server may just not be up yet. + response, err := client.Do(request) + if err != nil { + return false, nil + } + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + if err != nil { + return false, trace.Wrap(err, "failed reading poll response body") + } + + status := health{} + if err = json.Unmarshal(body, &status); err != nil { + return false, trace.Wrap(err, "failed parsing poll response") + } + + return status.Health == "true", nil +} diff --git a/.cloudbuild/scripts/internal/git/configure.go b/.cloudbuild/scripts/internal/git/configure.go new file mode 100644 index 0000000000000..63d2fdb54e9b2 --- /dev/null +++ b/.cloudbuild/scripts/internal/git/configure.go @@ -0,0 +1,145 @@ +package git + +import ( + "context" + "fmt" + "os" + "os/exec" + + "github.com/gravitational/teleport/.cloudbuild/scripts/internal/github" + "github.com/gravitational/trace" + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" +) + +// Config represents a git repository that has been configured to use a +// deployment key, and acts as a handle to the resources so that we can +// clean them up when we're done. +type Config struct { + identity string + knownHosts string + repoDir string +} + +// Configure alters the configuration of the git repository in `repoDir` +// so that we can access it from build. If `deployKey` is non-nil the repo +// will be configured to use that. If no deploy key is supplied, the repository +// config is untouched. +func Configure(ctx context.Context, repoDir string, deployKey []byte) (cfg *Config, err error) { + var identity string + var hostsFile string + + // The deploy key we're using is too sensitive to just hope that every + // exit path will clean it up on failure, so let's just register a + // cleanup function now just in case. + defer func() { + if err != nil { + cleanup(identity, hostsFile) + } + }() + + // If we have been supplied with deployment key, then we need to use that + // to access the repository. This implies that need to use ssh to read from + // the remote, so we will have to deal with known_hosts, etc. + if deployKey != nil { + githubHostKeys, err := getGithubHostKeys(ctx) + if err != nil { + return nil, trace.Wrap(err, "failed configuring known hosts") + } + + // configure SSH known_hosts for github.com + hostsFile, err = configureKnownHosts("github.com", githubHostKeys) + if err != nil { + return nil, trace.Wrap(err, "failed configuring known hosts") + } + + // GCB clones via https, and our deploy key won't work with that, so + // force git to access the remote over ssh with a rewrite rule + log.Info("Adding remote url rewrite rule") + err = git(ctx, repoDir, "config", `url.git@github.com:.insteadOf`, "https://github.com/") + if err != nil { + return nil, trace.Wrap(err, "failed configuring url rewrite rule") + } + + // set up the identity for the deploy key + identity, err = writeKey(deployKey) + if err != nil { + return nil, trace.Wrap(err, "failed configuring SSH identity") + } + + // finally, force git to + // a) use our custom SSH setup when accessing the remote, and + // b) fail if the github host tries to present a host key other than + // one we got from the metadata service + log.Infof("Configuring git ssh command") + err = git(ctx, repoDir, "config", "core.sshCommand", + fmt.Sprintf("ssh -i %s -o StrictHostKeyChecking=yes -o UserKnownHostsFile=%s", identity, hostsFile)) + if err != nil { + return nil, trace.Wrap(err, "failed configuring git to use deploy key") + } + } + + return &Config{identity: identity, repoDir: repoDir, knownHosts: hostsFile}, nil +} + +// Do runs `git args...` in the configured repository +func (cfg *Config) Do(ctx context.Context, args ...string) error { + return git(ctx, cfg.repoDir, args...) +} + +// Close cleans up the repository, including deleting the deployment key (if any) +func (cfg *Config) Close() error { + cleanup(cfg.identity, cfg.knownHosts) + return nil +} + +func run(ctx context.Context, workingDir string, env []string, cmd string, args ...string) error { + p := exec.CommandContext(ctx, cmd, args...) + if len(env) != 0 { + p.Env = append(os.Environ(), env...) + } + p.Dir = workingDir + + cmdLogger := log.WithField("cmd", cmd) + p.Stdout = cmdLogger.WriterLevel(log.InfoLevel) + p.Stderr = cmdLogger.WriterLevel(log.ErrorLevel) + return p.Run() +} + +func git(ctx context.Context, repoDir string, args ...string) error { + return run(ctx, repoDir, nil, "/usr/bin/git", args...) +} + +func cleanup(deployKey, knownHosts string) { + if knownHosts != "" { + log.Infof("Removing known_hosts file %s", knownHosts) + if err := os.Remove(knownHosts); err != nil { + log.WithError(err).Error("Failed cleaning up known_hosts key") + } + } + + if deployKey != "" { + log.Infof("Removing deploy key file %s", deployKey) + if err := os.Remove(deployKey); err != nil { + log.WithError(err).Error("Failed cleaning up deploy key") + } + } +} + +// getGithubHostKeys fetches the github host keys from the github metadata +// service. The metadata is fetched over HTTPS, and so we have built-on +// protection against MitM attacks while fetching the expected host keys. +func getGithubHostKeys(ctx context.Context) ([]ssh.PublicKey, error) { + metadata, err := github.GetMetadata(ctx) + if err != nil { + return nil, trace.Wrap(err, "failed fetching github metadata") + } + + // extract the host keys + githubHostKeys, err := metadata.HostKeys() + if err != nil { + return nil, trace.Wrap(err, "failed fetching github hostKeys") + } + + return githubHostKeys, nil +} diff --git a/.cloudbuild/scripts/internal/git/deploykey.go b/.cloudbuild/scripts/internal/git/deploykey.go new file mode 100644 index 0000000000000..fc57eb2798ef7 --- /dev/null +++ b/.cloudbuild/scripts/internal/git/deploykey.go @@ -0,0 +1,34 @@ +package git + +import ( + "os" + + "github.com/gravitational/trace" + log "github.com/sirupsen/logrus" +) + +func writeKey(deployKey []byte) (string, error) { + // Note that tempfiles are automatically created with 0600, so no-one else + // should be able to read this. + keyFile, err := os.CreateTemp("", "*") + if err != nil { + return "", trace.Wrap(err, "failed creating keyfile") + } + defer keyFile.Close() + + log.Infof("Writing deploy key to %s", keyFile.Name()) + _, err = keyFile.Write(deployKey) + if err != nil { + return "", trace.Wrap(err, "failed writing deploy key") + } + + // ensure there is a trailing newline in the key, as older versions of the + // `ssh` client will barf on a key that doesn't have one, but will happily + // allow multiples + _, err = keyFile.WriteString("\n") + if err != nil { + return "", trace.Wrap(err, "failed formatting key") + } + + return keyFile.Name(), nil +} diff --git a/.cloudbuild/scripts/internal/git/knownhosts.go b/.cloudbuild/scripts/internal/git/knownhosts.go new file mode 100644 index 0000000000000..66096bb07f2d7 --- /dev/null +++ b/.cloudbuild/scripts/internal/git/knownhosts.go @@ -0,0 +1,33 @@ +package git + +import ( + "os" + + "github.com/gravitational/trace" + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/knownhosts" +) + +func configureKnownHosts(hostname string, keys []ssh.PublicKey) (string, error) { + knownHostsFile, err := os.CreateTemp("", "*") + if err != nil { + return "", trace.Wrap(err, "failed creating known hosts file") + } + defer knownHostsFile.Close() + + log.Infof("Writing known_hosts file to %s", knownHostsFile.Name()) + + addrs := []string{hostname} + for _, k := range keys { + log.Infof("processing key %s...", k.Type()) + _, err := knownHostsFile.WriteString(knownhosts.Line(addrs, k) + "\n") + if err != nil { + knownHostsFile.Close() + os.Remove(knownHostsFile.Name()) + return "", trace.Wrap(err, "failed writing known hosts") + } + } + + return knownHostsFile.Name(), nil +} diff --git a/.cloudbuild/scripts/internal/git/unshallow.go b/.cloudbuild/scripts/internal/git/unshallow.go new file mode 100644 index 0000000000000..af3387359840f --- /dev/null +++ b/.cloudbuild/scripts/internal/git/unshallow.go @@ -0,0 +1,25 @@ +package git + +import ( + "context" + + "github.com/gravitational/trace" + log "github.com/sirupsen/logrus" +) + +func UnshallowRepository(ctx context.Context, workspace string, deployKey []byte) error { + log.Info("Configuring git") + gitCfg, err := Configure(ctx, workspace, deployKey) + if err != nil { + return trace.Wrap(err, "failed configuring git") + } + defer gitCfg.Close() + + log.Info("Unshallowing repository") + err = gitCfg.Do(ctx, "fetch", "--unshallow") + if err != nil { + return trace.Wrap(err, "unshallow failed") + } + + return nil +} diff --git a/.cloudbuild/scripts/internal/github/meta.go b/.cloudbuild/scripts/internal/github/meta.go new file mode 100644 index 0000000000000..a8096a013dae2 --- /dev/null +++ b/.cloudbuild/scripts/internal/github/meta.go @@ -0,0 +1,58 @@ +package github + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/gravitational/trace" + "golang.org/x/crypto/ssh" +) + +const ( + metadataURL = "https://api.github.com/meta" +) + +// Metadata contains information about the github networking environment, +// enclosing host keys for git/ssh access +type Metadata struct { + SSHKeyFingerPrints map[string]string `json:"ssh_key_fingerprints"` + SSHKeys []string `json:"ssh_keys"` +} + +func GetMetadata(ctx context.Context) (*Metadata, error) { + request, err := http.NewRequestWithContext(ctx, http.MethodGet, metadataURL, nil) + if err != nil { + return nil, trace.Wrap(err, "failed creating metadata request") + } + + response, err := http.DefaultClient.Do(request) + if err != nil { + return nil, trace.Wrap(err, "failed issuing metadata request") + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, trace.Errorf("Metadata request failed %d", response.StatusCode) + } + + var meta Metadata + err = json.NewDecoder(response.Body).Decode(&meta) + if err != nil { + return nil, trace.Wrap(err, "failed parsing metadata") + } + + return &meta, nil +} + +func (meta *Metadata) HostKeys() ([]ssh.PublicKey, error) { + keys := make([]ssh.PublicKey, 0, len(meta.SSHKeys)) + for _, text := range meta.SSHKeys { + key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(text)) + if err != nil { + return nil, trace.Wrap(err, "failed parsing host key") + } + keys = append(keys, key) + } + return keys, nil +} diff --git a/.cloudbuild/scripts/internal/secrets/fetch.go b/.cloudbuild/scripts/internal/secrets/fetch.go new file mode 100644 index 0000000000000..38cbc1889d280 --- /dev/null +++ b/.cloudbuild/scripts/internal/secrets/fetch.go @@ -0,0 +1,41 @@ +package secrets + +import ( + "context" + + secretmanager "cloud.google.com/go/secretmanager/apiv1" + "github.com/gravitational/trace" + log "github.com/sirupsen/logrus" + secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1" +) + +// Fetch goes and grabs a single secret from the Google Cloud secret manager +func Fetch(ctx context.Context, resourceName string) ([]byte, error) { + c, err := secretmanager.NewClient(ctx) + if err != nil { + return nil, trace.Wrap(err, "failed creating SecretManager client") + } + defer c.Close() + + log.Debugf("Fetching secret %s", resourceName) + secret, err := c.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{ + Name: resourceName, + }) + + if err != nil { + return nil, trace.Wrap(err, "failed fetching secret token") + } + + return secret.Payload.Data, nil +} + +// FetchString fetches a single secret from the Google Cloud secret manager and returns +// it as a string. +func FetchString(ctx context.Context, resourceName string) (string, error) { + data, err := Fetch(ctx, resourceName) + if err != nil { + return "", trace.Wrap(err) + } + + return string(data), nil +} diff --git a/.drone.yml b/.drone.yml index 631e422378c4a..73fd7d9337bf7 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,4 +1,32 @@ --- +kind: pipeline +type: kubernetes +name: update-docs-webhook + +trigger: + event: + include: + - push + exclude: + - pull_request + branch: + include: + - master + - branch/* + repo: + include: + - gravitational/teleport + +clone: + disable: true + +steps: + - name: Trigger docs deployment + image: plugins/webhook + settings: + urls: + from_secret: DOCS_DEPLOY_HOOK +--- ################################################ # Generated using dronegen, do not edit by hand! # Use 'make dronegen' to update. @@ -787,19 +815,19 @@ steps: # increment these variables when a new major/minor version is released to bump the automatic builds # this only needs to be done on the master branch, as that's the branch that the Drone cron is configured for # build major version images which are just teleport:x - CURRENT_VERSION_ROOT: v8 - PREVIOUS_VERSION_ONE_ROOT: v7 - PREVIOUS_VERSION_TWO_ROOT: v6 + CURRENT_VERSION_ROOT: v9 + PREVIOUS_VERSION_ONE_ROOT: v8 + PREVIOUS_VERSION_TWO_ROOT: v7 commands: - apk --update --no-cache add curl go - mkdir -p /go/build && cd /go/build - # CURRENT_VERSION (8) + # CURRENT_VERSION (9) - (cd /go/build.assets/tooling && go run ./cmd/query-latest $CURRENT_VERSION_ROOT > /go/build/CURRENT_VERSION_TAG.txt) - echo "$(cat /go/build/CURRENT_VERSION_TAG.txt | cut -d. -f1 | tr -d '^v')" > /go/build/CURRENT_VERSION_TAG_GENERIC.txt - # PREVIOUS_VERSION_ONE (7) + # PREVIOUS_VERSION_ONE (8) - (cd /go/build.assets/tooling && go run ./cmd/query-latest $PREVIOUS_VERSION_ONE_ROOT > /go/build/PREVIOUS_VERSION_ONE_TAG.txt) - echo "$(cat /go/build/PREVIOUS_VERSION_ONE_TAG.txt | cut -d. -f1 | tr -d '^v')" > /go/build/PREVIOUS_VERSION_ONE_TAG_GENERIC.txt - # PREVIOUS_VERSION_TWO (6) + # PREVIOUS_VERSION_TWO (7) - (cd /go/build.assets/tooling && go run ./cmd/query-latest $PREVIOUS_VERSION_TWO_ROOT > /go/build/PREVIOUS_VERSION_TWO_TAG.txt) - echo "$(cat /go/build/PREVIOUS_VERSION_TWO_TAG.txt | cut -d. -f1 | tr -d '^v')" > /go/build/PREVIOUS_VERSION_TWO_TAG_GENERIC.txt # list versions @@ -3124,9 +3152,9 @@ steps: ARCH: amd64 BUILDBOX_PASSWORD: from_secret: BUILDBOX_PASSWORD - ENT_TARBALL_PATH: /tmp/build-darwin-amd64-pkg/go/artifacts + ENT_TARBALL_PATH: /tmp/build-darwin-amd64-pkg-tsh/go/artifacts OS: darwin - OSS_TARBALL_PATH: /tmp/build-darwin-amd64-pkg/go/artifacts + OSS_TARBALL_PATH: /tmp/build-darwin-amd64-pkg-tsh/go/artifacts WORKSPACE_DIR: /tmp/build-darwin-amd64-pkg-tsh - name: Copy Mac pkg artifacts commands: @@ -5056,6 +5084,6 @@ volumes: name: drone-s3-debrepo-pvc --- kind: signature -hmac: 91c43e2655c4225898f8ba6fc034a0b5ee3de067d2f58979a474fcba342db799 +hmac: 8d0b79ebd4a3a0432402abf3b2773f45f8d787c0114d921dcdb5c909f2c338db ... diff --git a/.github/ISSUE_TEMPLATE/testplan.md b/.github/ISSUE_TEMPLATE/testplan.md index e394322075ce1..633930f8ecc65 100644 --- a/.github/ISSUE_TEMPLATE/testplan.md +++ b/.github/ISSUE_TEMPLATE/testplan.md @@ -139,6 +139,11 @@ With every user combination, try to login and signup with invalid second factor, - [ ] ssh -L \ - [ ] ssh -L \ +- [ ] Verify proxy jump functionality + Log into leaf cluster via root, shut down the root proxy and verify proxy jump works. + - [ ] tsh ssh -J \ + - [ ] ssh -J \ + - [ ] Interact with a cluster using the Web UI - [ ] Connect to a Teleport node - [ ] Connect to a OpenSSH node @@ -276,6 +281,15 @@ tsh --proxy=proxy.example.com --user= --insecure ssh --cluster=foo.com - [ ] Test receiving a message via Teleport Slackbot - [ ] Test receiving a new Jira Ticket via Teleport Jira +### AWS Node Joining +[Docs](https://goteleport.com/docs/setup/guides/joining-nodes-aws/) +- [ ] On EC2 instance with `ec2:DescribeInstances` permissions for local account: + `TELEPORT_TEST_EC2=1 go test ./integration -run TestEC2NodeJoin` +- [ ] On EC2 instance with any attached role: + `TELEPORT_TEST_EC2=1 go test ./integration -run TestIAMNodeJoin` +- [ ] EC2 Join method in IoT mode with node and auth in different AWS accounts +- [ ] IAM Join method in IoT mode with node and auth in different AWS accounts + ## WEB UI ## Main @@ -938,6 +952,10 @@ and non interactive tsh bench loads. - [ ] Verify async recording (`mode: node` or `mode: proxy`) - [ ] Sessions show up in session recordings UI with desktop icon - [ ] Sessions can be played back, including play/pause functionality + - [ ] A session that ends with a TDP error message can be played back, ends by displaying the error message, + and the progress bar progresses to the end. + - [ ] Attempting to play back a session that doesn't exist (i.e. by entering a non-existing session id in the url) shows + a relevant error message. - [ ] RBAC for sessions: ensure users can only see their own recordings when using the RBAC rule from our [docs](../../docs/pages/access-controls/reference.mdx#rbac-for-sessions) @@ -961,4 +979,4 @@ and non interactive tsh bench loads. - [ ] Debian 9 - Verify tsh runs on: - [ ] Windows 10 - - [ ] MacOS \ No newline at end of file + - [ ] MacOS diff --git a/.github/workflows/robot/internal/bot/assign.go b/.github/workflows/robot/internal/bot/assign.go index e7743d822b569..35fe61ac3a243 100644 --- a/.github/workflows/robot/internal/bot/assign.go +++ b/.github/workflows/robot/internal/bot/assign.go @@ -19,6 +19,9 @@ package bot import ( "context" "log" + "regexp" + "strconv" + "strings" "github.com/gravitational/trace" ) @@ -50,10 +53,128 @@ func (b *Bot) Assign(ctx context.Context) error { } func (b *Bot) getReviewers(ctx context.Context) ([]string, error) { + // If a backport PR was found, assign original reviewers. Otherwise fall + // through to normal assignment logic. + if isBackport(b.c.Environment.UnsafeBase) { + reviewers, err := b.backportReviewers(ctx) + if err == nil { + return reviewers, nil + } + log.Printf("Assign: Found backport PR, but failed to find original reviewers: %v. Falling through to normal assignment logic.", err) + } + docs, code, err := b.parseChanges(ctx) if err != nil { return nil, trace.Wrap(err) } - return b.c.Review.Get(b.c.Environment.Author, docs, code), nil } + +func (b *Bot) backportReviewers(ctx context.Context) ([]string, error) { + // Search inside the PR to find a reference to the original PR. + original, err := b.findOriginal(ctx, + b.c.Environment.Organization, + b.c.Environment.Repository, + b.c.Environment.Number) + if err != nil { + return nil, trace.Wrap(err) + } + + var originalReviewers []string + + // Append list of reviewers that have yet to submit a review. + reviewers, err := b.c.GitHub.ListReviewers(ctx, + b.c.Environment.Organization, + b.c.Environment.Repository, + original) + if err != nil { + return nil, trace.Wrap(err) + } + originalReviewers = append(originalReviewers, reviewers...) + + // Append list of reviews that have submitted a review. + reviews, err := b.c.GitHub.ListReviews(ctx, + b.c.Environment.Organization, + b.c.Environment.Repository, + original) + if err != nil { + return nil, trace.Wrap(err) + } + for _, review := range reviews { + originalReviewers = append(originalReviewers, review.Author) + } + + return dedup(b.c.Environment.Author, originalReviewers), nil +} + +func (b *Bot) findOriginal(ctx context.Context, organization string, repository string, number int) (int, error) { + pull, err := b.c.GitHub.GetPullRequest(ctx, + organization, + repository, + number) + if err != nil { + return 0, trace.Wrap(err) + } + + var original string + + // Search inside the title and body for the original PR number. + title := pattern.FindStringSubmatch(pull.UnsafeTitle) + body := pattern.FindStringSubmatch(pull.UnsafeBody) + switch { + case len(title) == 0 && len(body) == 0: + return 0, trace.NotFound("no PR referenced in title or body") + case len(title) == 0 && len(body) == 2: + original = body[1] + case len(title) == 2 && len(body) == 0: + original = title[1] + case len(title) == 2 && len(body) == 2: + if title[1] != body[1] { + return 0, trace.NotFound("different PRs referenced in title and body") + } + original = title[1] + default: + return 0, trace.NotFound("failed to find reference to PR") + } + + n, err := strconv.Atoi(original) + if err != nil { + return 0, trace.Wrap(err) + } + + // Verify the number found is a Pull Request and not an Issue. + _, err = b.c.GitHub.GetPullRequest(ctx, + organization, + repository, + n) + if err != nil { + return 0, trace.NotFound("found Issue %v, not Pull Request", original) + } + + log.Printf("Assign: Found original PR #%v.", original) + return n, nil +} + +func dedup(author string, reviewers []string) []string { + m := map[string]bool{} + + for _, reviewer := range reviewers { + if reviewer == author { + continue + } + m[reviewer] = true + } + + var filtered []string + for k := range m { + filtered = append(filtered, k) + } + + return filtered +} + +func isBackport(unsafeBase string) bool { + return strings.HasPrefix(unsafeBase, "branch/v") +} + +var pattern = regexp.MustCompile(`(?:https:\/\/github\.com\/gravitational\/teleport\/pull\/|#)([0-9]+)`) diff --git a/.github/workflows/robot/internal/bot/assign_test.go b/.github/workflows/robot/internal/bot/assign_test.go new file mode 100644 index 0000000000000..b065a6023de36 --- /dev/null +++ b/.github/workflows/robot/internal/bot/assign_test.go @@ -0,0 +1,146 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bot + +import ( + "context" + "testing" + + "github.com/gravitational/teleport/.github/workflows/robot/internal/env" + "github.com/gravitational/teleport/.github/workflows/robot/internal/github" + "github.com/gravitational/teleport/.github/workflows/robot/internal/review" + + "github.com/stretchr/testify/require" +) + +// TestBackportReviewers checks if backport reviewers are correctly assigned. +func TestBackportReviewers(t *testing.T) { + r, err := review.New(&review.Config{ + CodeReviewers: map[string]review.Reviewer{}, + CodeReviewersOmit: map[string]bool{}, + DocsReviewers: map[string]review.Reviewer{}, + DocsReviewersOmit: map[string]bool{}, + Admins: []string{}, + }) + require.NoError(t, err) + + tests := []struct { + desc string + pull github.PullRequest + reviewers []string + reviews []github.Review + err bool + expected []string + }{ + { + desc: "backport-original-pr-number-approved", + pull: github.PullRequest{ + Author: "baz", + Repository: "bar", + UnsafeHead: "baz/fix", + UnsafeTitle: "Backport #0 to branch/v8", + UnsafeBody: "", + Fork: false, + }, + reviewers: []string{"3"}, + reviews: []github.Review{ + {Author: "4", State: "APPROVED"}, + }, + err: false, + expected: []string{"3", "4"}, + }, + { + desc: "backport-original-url-approved", + pull: github.PullRequest{ + Author: "baz", + Repository: "bar", + UnsafeHead: "baz/fix", + UnsafeTitle: "Fixed an issue", + UnsafeBody: "https://github.com/gravitational/teleport/pull/0", + Fork: false, + }, + reviewers: []string{"3"}, + reviews: []github.Review{ + {Author: "4", State: "APPROVED"}, + }, + err: false, + expected: []string{"3", "4"}, + }, + { + desc: "backport-multiple-reviews", + pull: github.PullRequest{ + Author: "baz", + Repository: "bar", + UnsafeHead: "baz/fix", + UnsafeTitle: "Fixed feature", + UnsafeBody: "", + Fork: false, + }, + reviewers: []string{"3"}, + reviews: []github.Review{ + {Author: "4", State: "COMMENTED"}, + {Author: "4", State: "CHANGES_REQUESTED"}, + {Author: "4", State: "APPROVED"}, + {Author: "9", State: "APPROVED"}, + }, + err: true, + expected: []string{}, + }, + { + desc: "backport-original-not-found", + pull: github.PullRequest{ + Author: "baz", + Repository: "bar", + UnsafeHead: "baz/fix", + UnsafeTitle: "Fixed feature", + UnsafeBody: "", + Fork: false, + }, + reviewers: []string{"3"}, + reviews: []github.Review{ + {Author: "4", State: "APPROVED"}, + }, + err: true, + expected: []string{}, + }, + } + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + b := &Bot{ + c: &Config{ + Environment: &env.Environment{ + Organization: "foo", + Author: "9", + Repository: "bar", + Number: 0, + UnsafeBase: "branch/v8", + UnsafeHead: "fix", + }, + Review: r, + GitHub: &fakeGithub{ + pull: test.pull, + reviewers: test.reviewers, + reviews: test.reviews, + }, + }, + } + reviewers, err := b.backportReviewers(context.Background()) + require.Equal(t, err != nil, test.err) + require.ElementsMatch(t, reviewers, test.expected) + }) + } +} diff --git a/.github/workflows/robot/internal/bot/bot.go b/.github/workflows/robot/internal/bot/bot.go index e892be1fc1ae6..5a979acdb60e7 100644 --- a/.github/workflows/robot/internal/bot/bot.go +++ b/.github/workflows/robot/internal/bot/bot.go @@ -29,16 +29,22 @@ import ( // Client implements the GitHub API. type Client interface { - // RequestReviewers is used to assign reviewers to a PR. + // RequestReviewers is used to assign reviewers to a Pull Request. RequestReviewers(ctx context.Context, organization string, repository string, number int, reviewers []string) error // ListReviews is used to list all submitted reviews for a PR. - ListReviews(ctx context.Context, organization string, repository string, number int) (map[string]*github.Review, error) + ListReviews(ctx context.Context, organization string, repository string, number int) ([]github.Review, error) + + // ListReviewers returns a list of reviewers that have yet to submit a review. + ListReviewers(ctx context.Context, organization string, repository string, number int) ([]string, error) + + // GetPullRequest returns a specific Pull Request. + GetPullRequest(ctx context.Context, organization string, repository string, number int) (github.PullRequest, error) // ListPullRequests returns a list of Pull Requests. ListPullRequests(ctx context.Context, organization string, repository string, state string) ([]github.PullRequest, error) - // ListFiles is used to list all the files within a PR. + // ListFiles is used to list all the files within a Pull Request. ListFiles(ctx context.Context, organization string, repository string, number int) ([]string, error) // AddLabels will add labels to an Issue or Pull Request. diff --git a/.github/workflows/robot/internal/bot/bot_test.go b/.github/workflows/robot/internal/bot/bot_test.go index 72ac663acf7b5..5ff4744385c9a 100644 --- a/.github/workflows/robot/internal/bot/bot_test.go +++ b/.github/workflows/robot/internal/bot/bot_test.go @@ -78,7 +78,7 @@ func TestParseChanges(t *testing.T) { Number: 0, }, GitHub: &fakeGithub{ - test.files, + files: test.files, }, }, } @@ -91,15 +91,26 @@ func TestParseChanges(t *testing.T) { } type fakeGithub struct { - files []string + files []string + pull github.PullRequest + reviewers []string + reviews []github.Review } func (f *fakeGithub) RequestReviewers(ctx context.Context, organization string, repository string, number int, reviewers []string) error { return nil } -func (f *fakeGithub) ListReviews(ctx context.Context, organization string, repository string, number int) (map[string]*github.Review, error) { - return nil, nil +func (f *fakeGithub) ListReviews(ctx context.Context, organization string, repository string, number int) ([]github.Review, error) { + return f.reviews, nil +} + +func (f *fakeGithub) ListReviewers(ctx context.Context, organization string, repository string, number int) ([]string, error) { + return f.reviewers, nil +} + +func (f *fakeGithub) GetPullRequest(ctx context.Context, organization string, repository string, number int) (github.PullRequest, error) { + return f.pull, nil } func (f *fakeGithub) ListPullRequests(ctx context.Context, organization string, repository string, state string) ([]github.PullRequest, error) { diff --git a/.github/workflows/robot/internal/bot/check.go b/.github/workflows/robot/internal/bot/check.go index d62c4d1e3a925..34fc5daf4341e 100644 --- a/.github/workflows/robot/internal/bot/check.go +++ b/.github/workflows/robot/internal/bot/check.go @@ -40,7 +40,7 @@ func (b *Bot) Check(ctx context.Context) error { err := b.dismiss(ctx, b.c.Environment.Organization, b.c.Environment.Repository, - b.c.Environment.UnsafeBranch) + b.c.Environment.UnsafeHead) if err != nil { return trace.Wrap(err) } diff --git a/.github/workflows/robot/internal/bot/label.go b/.github/workflows/robot/internal/bot/label.go index 843fe6fa37ee8..5bb5154a6b67a 100644 --- a/.github/workflows/robot/internal/bot/label.go +++ b/.github/workflows/robot/internal/bot/label.go @@ -51,7 +51,7 @@ func (b *Bot) labels(ctx context.Context) ([]string, error) { var labels []string // The branch name is unsafe, but here we are simply adding a label. - if strings.HasPrefix(b.c.Environment.UnsafeBranch, "branch/") { + if strings.HasPrefix(b.c.Environment.UnsafeHead, "branch/") { log.Println("Label: Found backport branch.") labels = append(labels, "backport") } diff --git a/.github/workflows/robot/internal/bot/label_test.go b/.github/workflows/robot/internal/bot/label_test.go index c68ff90c6d80f..3e1b1f71c343b 100644 --- a/.github/workflows/robot/internal/bot/label_test.go +++ b/.github/workflows/robot/internal/bot/label_test.go @@ -84,10 +84,10 @@ func TestLabel(t *testing.T) { Organization: "foo", Repository: "bar", Number: 0, - UnsafeBranch: test.branch, + UnsafeHead: test.branch, }, GitHub: &fakeGithub{ - test.files, + files: test.files, }, }, } diff --git a/.github/workflows/robot/internal/env/env.go b/.github/workflows/robot/internal/env/env.go index ebf989a2fffd7..73fb61b844ec3 100644 --- a/.github/workflows/robot/internal/env/env.go +++ b/.github/workflows/robot/internal/env/env.go @@ -38,13 +38,26 @@ type Environment struct { // Author is the author of the PR. Author string - // UnsafeBranch is the name of the branch the workflow is running in. + // UnsafeHead is the name of the branch the workflow is running in. // - // UnsafeBranch can be attacker controlled and should not be used in any - // security sensitive context. See the following link for more details: + // UnsafeHead can be attacker controlled and should not be used in any + // security sensitive context. For example, don't use it when crafting a URL + // to send a request to or an access decision. See the following link for + // more details: // // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections - UnsafeBranch string + UnsafeHead string + + // UnsafeBase is the name of the base branch the user is trying to merge the + // PR into. For example: "master" or "branch/v8". + // + // UnsafeBase can be attacker controlled and should not be used in any + // security sensitive context. For example, don't use it when crafting a URL + // to send a request to or an access decision. See the following link for + // more details: + // + // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections + UnsafeBase string } // New returns a new execution environment for the workflow. @@ -72,7 +85,8 @@ func New() (*Environment, error) { Repository: event.Repository.Name, Number: event.PullRequest.Number, Author: event.PullRequest.User.Login, - UnsafeBranch: event.PullRequest.UnsafeHead.UnsafeRef, + UnsafeHead: event.PullRequest.UnsafeHead.UnsafeRef, + UnsafeBase: event.PullRequest.UnsafeBase.UnsafeRef, }, nil } diff --git a/.github/workflows/robot/internal/env/env_test.go b/.github/workflows/robot/internal/env/env_test.go index 205b7a3026447..5ec2972db0120 100644 --- a/.github/workflows/robot/internal/env/env_test.go +++ b/.github/workflows/robot/internal/env/env_test.go @@ -95,7 +95,7 @@ func TestEnvironment(t *testing.T) { require.Equal(t, environment.Repository, test.repository) require.Equal(t, environment.Number, test.number) require.Equal(t, environment.Author, test.author) - require.Equal(t, environment.UnsafeBranch, test.unsafeBranch) + require.Equal(t, environment.UnsafeHead, test.unsafeBranch) } }) } diff --git a/.github/workflows/robot/internal/env/structs.go b/.github/workflows/robot/internal/env/structs.go index ac82d282c8d41..d34de2e46f27f 100644 --- a/.github/workflows/robot/internal/env/structs.go +++ b/.github/workflows/robot/internal/env/structs.go @@ -39,25 +39,39 @@ type PullRequest struct { Number int `json:"number"` // UnsafeHead can be attacker controlled and should not be used in any - // security sensitive context. See the following link for more details: + // security sensitive context. For example, don't use it when crafting a URL + // to send a request to or an access decision. See the following link for + // more details: // // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections - UnsafeHead Head `json:"head"` + UnsafeHead Branch `json:"head"` + + // UnsafeHead can be attacker controlled and should not be used in any + // security sensitive context. For example, don't use it when crafting a URL + // to send a request to or an access decision. See the following link for + // more details: + // + // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections + UnsafeBase Branch `json:"base"` } type User struct { Login string `json:"login"` } -type Head struct { +type Branch struct { // UnsafeSHA can be attacker controlled and should not be used in any - // security sensitive context. See the following link for more details: + // security sensitive context. For example, don't use it when crafting a URL + // to send a request to or an access decision. See the following link for + // more details: // // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections UnsafeSHA string `json:"sha"` // UnsafeRef can be attacker controlled and should not be used in any - // security sensitive context. See the following link for more details: + // security sensitive context. For example, don't use it when crafting a URL + // to send a request to or an access decision. See the following link for + // more details: // // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections UnsafeRef string `json:"ref"` diff --git a/.github/workflows/robot/internal/github/github.go b/.github/workflows/robot/internal/github/github.go index a52861b3e1972..e25d7dbf6ff29 100644 --- a/.github/workflows/robot/internal/github/github.go +++ b/.github/workflows/robot/internal/github/github.go @@ -22,6 +22,7 @@ import ( "net/http" "net/url" "path" + "sort" "strconv" "time" @@ -45,6 +46,7 @@ func New(ctx context.Context, token string) (*Client, error) { }, nil } +// RequestReviewers is used to assign reviewers to a Pull Requests. func (c *Client) RequestReviewers(ctx context.Context, organization string, repository string, number int, reviewers []string) error { _, _, err := c.client.PullRequests.RequestReviewers(ctx, organization, @@ -63,14 +65,15 @@ func (c *Client) RequestReviewers(ctx context.Context, organization string, repo type Review struct { // Author is the GitHub login of the user that created the PR. Author string - // State is the state of the PR, for example APPROVED or CHANGES_REQUESTED. + // State is the state of the PR, for example APPROVED, COMMENTED, + // CHANGES_REQUESTED, or DISMISSED. State string // SubmittedAt is the time the PR was created. SubmittedAt time.Time } -func (c *Client) ListReviews(ctx context.Context, organization string, repository string, number int) (map[string]*Review, error) { - reviews := map[string]*Review{} +func (c *Client) ListReviews(ctx context.Context, organization string, repository string, number int) ([]Review, error) { + var reviews []Review opt := &go_github.ListOptions{ Page: 0, @@ -87,20 +90,11 @@ func (c *Client) ListReviews(ctx context.Context, organization string, repositor } for _, r := range page { - // Always pick up the last submitted review from each reviewer. - review, ok := reviews[r.GetUser().GetLogin()] - if ok { - if r.GetSubmittedAt().After(review.SubmittedAt) { - review.State = r.GetState() - review.SubmittedAt = r.GetSubmittedAt() - } - } - - reviews[r.GetUser().GetLogin()] = &Review{ + reviews = append(reviews, Review{ Author: r.GetUser().GetLogin(), State: r.GetState(), SubmittedAt: r.GetSubmittedAt(), - } + }) } if resp.NextPage == 0 { @@ -109,6 +103,11 @@ func (c *Client) ListReviews(ctx context.Context, organization string, repositor opt.Page = resp.NextPage } + // Sort oldest review first. + sort.SliceStable(reviews, func(i, j int) bool { + return reviews[i].SubmittedAt.Before(reviews[j].SubmittedAt) + }) + return reviews, nil } @@ -118,13 +117,95 @@ type PullRequest struct { Author string // Repository is the name of the repository. Repository string - // UnsafeHead is the name of the branch this PR is created from. It is marked - // unsafe as it can be attacker controlled. + // Number is the Pull Request number. + Number int + // State is the state of the submitted review. + State string + // UnsafeHead is the name head of the branch. + // + // UnsafeHead can be attacker controlled and should not be used in any + // security sensitive context. For example, don't use it when crafting a URL + // to send a request to or an access decision. See the following link for + // more details: + // + // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections UnsafeHead string + // UnsafeTitle is the title of the Pull Request. + // + // UnsafeTitle can be attacker controlled and should not be used in any + // security sensitive context. For example, don't use it when crafting a URL + // to send a request to or an access decision. See the following link for + // more details: + // + // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections + UnsafeTitle string + // UnsafeBody is the body of the Pull Request. + // + // UnsafeBody can be attacker controlled and should not be used in any + // security sensitive context. For example, don't use it when crafting a URL + // to send a request to or an access decision. See the following link for + // more details: + // + // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections + UnsafeBody string // Fork determines if the pull request is from a fork. Fork bool } +// ListReviewers returns a list of reviewers that have yet to submit a review. +func (c *Client) ListReviewers(ctx context.Context, organization string, repository string, number int) ([]string, error) { + var reviewers []string + + opt := &go_github.ListOptions{ + Page: 0, + PerPage: perPage, + } + for { + page, resp, err := c.client.PullRequests.ListReviewers(ctx, + organization, + repository, + number, + opt) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, r := range page.Users { + reviewers = append(reviewers, r.GetLogin()) + } + + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + + return reviewers, nil +} + +// GetPullRequest returns a specific Pull Request. +func (c *Client) GetPullRequest(ctx context.Context, organization string, repository string, number int) (PullRequest, error) { + pull, _, err := c.client.PullRequests.Get(ctx, + organization, + repository, + number) + if err != nil { + return PullRequest{}, trace.Wrap(err) + } + + return PullRequest{ + Author: pull.GetUser().GetLogin(), + Repository: repository, + Number: pull.GetNumber(), + State: pull.GetState(), + UnsafeHead: pull.GetHead().GetRef(), + UnsafeTitle: pull.GetTitle(), + UnsafeBody: pull.GetBody(), + Fork: pull.GetHead().GetRepo().GetFork(), + }, nil +} + +// ListPullRequests returns a list of Pull Requests. func (c *Client) ListPullRequests(ctx context.Context, organization string, repository string, state string) ([]PullRequest, error) { var pulls []PullRequest @@ -144,12 +225,16 @@ func (c *Client) ListPullRequests(ctx context.Context, organization string, repo return nil, trace.Wrap(err) } - for _, pr := range page { + for _, pull := range page { pulls = append(pulls, PullRequest{ - Author: pr.GetUser().GetLogin(), - Repository: repository, - UnsafeHead: pr.GetHead().GetRef(), - Fork: pr.GetHead().GetRepo().GetFork(), + Author: pull.GetUser().GetLogin(), + Repository: repository, + Number: pull.GetNumber(), + State: pull.GetState(), + UnsafeHead: pull.GetHead().GetRef(), + UnsafeTitle: pull.GetTitle(), + UnsafeBody: pull.GetBody(), + Fork: pull.GetHead().GetRepo().GetFork(), }) } if resp.NextPage == 0 { @@ -161,6 +246,7 @@ func (c *Client) ListPullRequests(ctx context.Context, organization string, repo return pulls, nil } +// ListFiles is used to list all the files within a Pull Request. func (c *Client) ListFiles(ctx context.Context, organization string, repository string, number int) ([]string, error) { var files []string @@ -215,6 +301,7 @@ type Workflow struct { Path string } +// ListWorkflows lists all workflows within a repository. func (c *Client) ListWorkflows(ctx context.Context, organization string, repository string) ([]Workflow, error) { var workflows []Workflow @@ -260,6 +347,7 @@ type Run struct { CreatedAt time.Time } +// ListWorkflowRuns is used to list all workflow runs for an ID. func (c *Client) ListWorkflowRuns(ctx context.Context, organization string, repository string, branch string, workflowID int64) ([]Run, error) { var runs []Run diff --git a/.github/workflows/robot/internal/review/review.go b/.github/workflows/robot/internal/review/review.go index 2bf803c7ed531..0d1072c2c5d08 100644 --- a/.github/workflows/robot/internal/review/review.go +++ b/.github/workflows/robot/internal/review/review.go @@ -195,7 +195,7 @@ func (r *Assignments) getCodeReviewerSets(author string) ([]string, []string) { } // CheckExternal requires two admins have approved. -func (r *Assignments) CheckExternal(author string, reviews map[string]*github.Review) error { +func (r *Assignments) CheckExternal(author string, reviews []github.Review) error { log.Printf("Check: Found external author %v.", author) reviewers := r.getAdminReviewers(author) @@ -209,7 +209,7 @@ func (r *Assignments) CheckExternal(author string, reviews map[string]*github.Re // CheckInternal will verify if required reviewers have approved. Checks if // docs and if each set of code reviews have approved. Admin approvals bypass // all checks. -func (r *Assignments) CheckInternal(author string, reviews map[string]*github.Review, docs bool, code bool) error { +func (r *Assignments) CheckInternal(author string, reviews []github.Review, docs bool, code bool) error { log.Printf("Check: Found internal author %v.", author) // Skip checks if admins have approved. @@ -247,7 +247,7 @@ func (r *Assignments) CheckInternal(author string, reviews map[string]*github.Re return nil } -func (r *Assignments) checkDocsReviews(author string, reviews map[string]*github.Review) error { +func (r *Assignments) checkDocsReviews(author string, reviews []github.Review) error { reviewers := r.getDocsReviewers(author) if check(reviewers, reviews) { @@ -257,7 +257,7 @@ func (r *Assignments) checkDocsReviews(author string, reviews map[string]*github return trace.BadParameter("requires at least one approval from %v", reviewers) } -func (r *Assignments) checkCodeReviews(author string, reviews map[string]*github.Review) error { +func (r *Assignments) checkCodeReviews(author string, reviews []github.Review) error { // External code reviews should never hit this path, if they do, fail and // return an error. v, ok := r.c.CodeReviewers[author] @@ -314,23 +314,42 @@ func getReviewerSets(author string, team string, reviewers map[string]Reviewer, return setA, setB } -func check(reviewers []string, reviews map[string]*github.Review) bool { +func check(reviewers []string, reviews []github.Review) bool { return checkN(reviewers, reviews) > 0 } -func checkN(reviewers []string, reviews map[string]*github.Review) int { +func checkN(reviewers []string, reviews []github.Review) int { + r := reviewsByAuthor(reviews) + var n int for _, reviewer := range reviewers { - if review, ok := reviews[reviewer]; ok { - if review.State == approved && review.Author == reviewer { - n++ - } + if state, ok := r[reviewer]; ok && state == approved { + n++ } } return n } +func reviewsByAuthor(reviews []github.Review) map[string]string { + m := map[string]string{} + + for _, review := range reviews { + // Always pick up the last submitted review from each reviewer. + if state, ok := m[review.Author]; ok { + // If the reviewer left comments after approval, skip this review. + if review.State == commented && state == approved { + continue + } + } + m[review.Author] = review.State + } + + return m +} + const ( + // commented is a code review where the reviewer has left comments only. + commented = "COMMENTED" // approved is a code review where the reviewer has approved changes. approved = "APPROVED" // changesRequested is a code review where the reviewer has requested changes. diff --git a/.github/workflows/robot/internal/review/review_test.go b/.github/workflows/robot/internal/review/review_test.go index d8df894053445..3412e1d374f7a 100644 --- a/.github/workflows/robot/internal/review/review_test.go +++ b/.github/workflows/robot/internal/review/review_test.go @@ -37,16 +37,16 @@ func TestIsInternal(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, - "3": Reviewer{Team: "Core", Owner: false}, - "4": Reviewer{Team: "Core", Owner: false}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, + "3": {Team: "Core", Owner: false}, + "4": {Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{}, // Docs. DocsReviewers: map[string]Reviewer{ - "5": Reviewer{Team: "Core", Owner: true}, - "6": Reviewer{Team: "Core", Owner: true}, + "5": {Team: "Core", Owner: true}, + "6": {Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, // Admins. @@ -65,16 +65,16 @@ func TestIsInternal(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, - "3": Reviewer{Team: "Core", Owner: false}, - "4": Reviewer{Team: "Core", Owner: false}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, + "3": {Team: "Core", Owner: false}, + "4": {Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{}, // Docs. DocsReviewers: map[string]Reviewer{ - "5": Reviewer{Team: "Core", Owner: true}, - "6": Reviewer{Team: "Core", Owner: true}, + "5": {Team: "Core", Owner: true}, + "6": {Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, // Admins. @@ -93,16 +93,16 @@ func TestIsInternal(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, - "3": Reviewer{Team: "Core", Owner: false}, - "4": Reviewer{Team: "Core", Owner: false}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, + "3": {Team: "Core", Owner: false}, + "4": {Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{}, // Docs. DocsReviewers: map[string]Reviewer{ - "5": Reviewer{Team: "Core", Owner: true}, - "6": Reviewer{Team: "Core", Owner: true}, + "5": {Team: "Core", Owner: true}, + "6": {Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, // Admins. @@ -139,10 +139,10 @@ func TestGetCodeReviewers(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, - "3": Reviewer{Team: "Core", Owner: false}, - "4": Reviewer{Team: "Core", Owner: false}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, + "3": {Team: "Core", Owner: false}, + "4": {Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{}, // Admins. @@ -162,11 +162,11 @@ func TestGetCodeReviewers(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, - "3": Reviewer{Team: "Core", Owner: false}, - "4": Reviewer{Team: "Core", Owner: false}, - "5": Reviewer{Team: "Core", Owner: false}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, + "3": {Team: "Core", Owner: false}, + "4": {Team: "Core", Owner: false}, + "5": {Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{ "3": true, @@ -188,11 +188,11 @@ func TestGetCodeReviewers(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, - "3": Reviewer{Team: "Core", Owner: false}, - "4": Reviewer{Team: "Core", Owner: false}, - "5": Reviewer{Team: "Internal"}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, + "3": {Team: "Core", Owner: false}, + "4": {Team: "Core", Owner: false}, + "5": {Team: "Internal"}, }, CodeReviewersOmit: map[string]bool{}, // Admins. @@ -212,15 +212,15 @@ func TestGetCodeReviewers(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, - "3": Reviewer{Team: "Core", Owner: true}, - "4": Reviewer{Team: "Core", Owner: false}, - "5": Reviewer{Team: "Core", Owner: false}, - "6": Reviewer{Team: "Core", Owner: false}, - "7": Reviewer{Team: "Internal", Owner: false}, - "8": Reviewer{Team: "Cloud", Owner: false}, - "9": Reviewer{Team: "Cloud", Owner: false}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, + "3": {Team: "Core", Owner: true}, + "4": {Team: "Core", Owner: false}, + "5": {Team: "Core", Owner: false}, + "6": {Team: "Core", Owner: false}, + "7": {Team: "Internal", Owner: false}, + "8": {Team: "Cloud", Owner: false}, + "9": {Team: "Cloud", Owner: false}, }, CodeReviewersOmit: map[string]bool{ "6": true, @@ -245,13 +245,13 @@ func TestGetCodeReviewers(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, - "3": Reviewer{Team: "Core", Owner: true}, - "4": Reviewer{Team: "Core", Owner: false}, - "5": Reviewer{Team: "Core", Owner: false}, - "6": Reviewer{Team: "Core", Owner: false}, - "7": Reviewer{Team: "Internal", Owner: false}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, + "3": {Team: "Core", Owner: true}, + "4": {Team: "Core", Owner: false}, + "5": {Team: "Core", Owner: false}, + "6": {Team: "Core", Owner: false}, + "7": {Team: "Internal", Owner: false}, }, CodeReviewersOmit: map[string]bool{ "6": true, @@ -294,8 +294,8 @@ func TestGetDocsReviewers(t *testing.T) { c: &Config{ // Docs. DocsReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, // Admins. @@ -314,8 +314,8 @@ func TestGetDocsReviewers(t *testing.T) { c: &Config{ // Docs. DocsReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{ "2": true, @@ -336,8 +336,8 @@ func TestGetDocsReviewers(t *testing.T) { c: &Config{ // Docs. DocsReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, // Admins. @@ -365,12 +365,12 @@ func TestCheckExternal(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, - "3": Reviewer{Team: "Core", Owner: true}, - "4": Reviewer{Team: "Core", Owner: false}, - "5": Reviewer{Team: "Core", Owner: false}, - "6": Reviewer{Team: "Core", Owner: false}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, + "3": {Team: "Core", Owner: true}, + "4": {Team: "Core", Owner: false}, + "5": {Team: "Core", Owner: false}, + "6": {Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{}, // Default. @@ -383,72 +383,48 @@ func TestCheckExternal(t *testing.T) { tests := []struct { desc string author string - reviews map[string]*github.Review + reviews []github.Review result bool }{ { desc: "no-reviews-fail", author: "5", - reviews: map[string]*github.Review{}, + reviews: []github.Review{}, result: false, }, { desc: "two-non-admin-reviews-fail", author: "5", - reviews: map[string]*github.Review{ - "3": &github.Review{ - Author: "3", - State: approved, - }, - "4": &github.Review{ - Author: "4", - State: approved, - }, + reviews: []github.Review{ + {Author: "3", State: approved}, + {Author: "4", State: approved}, }, result: false, }, { desc: "one-admin-reviews-fail", author: "5", - reviews: map[string]*github.Review{ - "1": &github.Review{ - Author: "1", - State: approved, - }, - "4": &github.Review{ - Author: "4", - State: approved, - }, + reviews: []github.Review{ + {Author: "1", State: approved}, + {Author: "4", State: approved}, }, result: false, }, { desc: "two-admin-reviews-one-denied-success", author: "5", - reviews: map[string]*github.Review{ - "1": &github.Review{ - Author: "1", - State: changesRequested, - }, - "2": &github.Review{ - Author: "2", - State: approved, - }, + reviews: []github.Review{ + {Author: "1", State: changesRequested}, + {Author: "2", State: approved}, }, result: false, }, { desc: "two-admin-reviews-success", author: "5", - reviews: map[string]*github.Review{ - "1": &github.Review{ - Author: "1", - State: approved, - }, - "2": &github.Review{ - Author: "2", - State: approved, - }, + reviews: []github.Review{ + {Author: "1", State: approved}, + {Author: "2", State: approved}, }, result: true, }, @@ -471,21 +447,21 @@ func TestCheckInternal(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": Reviewer{Team: "Core", Owner: true}, - "2": Reviewer{Team: "Core", Owner: true}, - "3": Reviewer{Team: "Core", Owner: true}, - "9": Reviewer{Team: "Core", Owner: true}, - "4": Reviewer{Team: "Core", Owner: false}, - "5": Reviewer{Team: "Core", Owner: false}, - "6": Reviewer{Team: "Core", Owner: false}, - "8": Reviewer{Team: "Internal", Owner: false}, - "10": Reviewer{Team: "Cloud", Owner: false}, - "11": Reviewer{Team: "Cloud", Owner: false}, - "12": Reviewer{Team: "Cloud", Owner: false}, + "1": {Team: "Core", Owner: true}, + "2": {Team: "Core", Owner: true}, + "3": {Team: "Core", Owner: true}, + "9": {Team: "Core", Owner: true}, + "4": {Team: "Core", Owner: false}, + "5": {Team: "Core", Owner: false}, + "6": {Team: "Core", Owner: false}, + "8": {Team: "Internal", Owner: false}, + "10": {Team: "Cloud", Owner: false}, + "11": {Team: "Cloud", Owner: false}, + "12": {Team: "Cloud", Owner: false}, }, // Docs. DocsReviewers: map[string]Reviewer{ - "7": Reviewer{Team: "Core", Owner: true}, + "7": {Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, CodeReviewersOmit: map[string]bool{}, @@ -499,7 +475,7 @@ func TestCheckInternal(t *testing.T) { tests := []struct { desc string author string - reviews map[string]*github.Review + reviews []github.Review docs bool code bool result bool @@ -507,13 +483,13 @@ func TestCheckInternal(t *testing.T) { { desc: "no-reviews-fail", author: "4", - reviews: map[string]*github.Review{}, + reviews: []github.Review{}, result: false, }, { desc: "docs-only-no-reviews-fail", author: "4", - reviews: map[string]*github.Review{}, + reviews: []github.Review{}, docs: true, code: false, result: false, @@ -521,8 +497,8 @@ func TestCheckInternal(t *testing.T) { { desc: "docs-only-non-docs-approval-fail", author: "4", - reviews: map[string]*github.Review{ - "3": &github.Review{Author: "3", State: approved}, + reviews: []github.Review{ + {Author: "3", State: approved}, }, docs: true, code: false, @@ -531,8 +507,8 @@ func TestCheckInternal(t *testing.T) { { desc: "docs-only-docs-approval-success", author: "4", - reviews: map[string]*github.Review{ - "7": &github.Review{Author: "7", State: approved}, + reviews: []github.Review{ + {Author: "7", State: approved}, }, docs: true, code: false, @@ -541,7 +517,7 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-no-reviews-fail", author: "4", - reviews: map[string]*github.Review{}, + reviews: []github.Review{}, docs: false, code: true, result: false, @@ -549,8 +525,8 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-one-approval-fail", author: "4", - reviews: map[string]*github.Review{ - "3": &github.Review{Author: "3", State: approved}, + reviews: []github.Review{ + {Author: "3", State: approved}, }, docs: false, code: true, @@ -559,9 +535,9 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-two-approval-setb-fail", author: "4", - reviews: map[string]*github.Review{ - "5": &github.Review{Author: "5", State: approved}, - "6": &github.Review{Author: "6", State: approved}, + reviews: []github.Review{ + {Author: "5", State: approved}, + {Author: "6", State: approved}, }, docs: false, code: true, @@ -570,9 +546,9 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-one-changes-fail", author: "4", - reviews: map[string]*github.Review{ - "3": &github.Review{Author: "3", State: approved}, - "4": &github.Review{Author: "4", State: changesRequested}, + reviews: []github.Review{ + {Author: "3", State: approved}, + {Author: "4", State: changesRequested}, }, docs: false, code: true, @@ -581,9 +557,9 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-two-approvals-success", author: "6", - reviews: map[string]*github.Review{ - "3": &github.Review{Author: "3", State: approved}, - "4": &github.Review{Author: "4", State: approved}, + reviews: []github.Review{ + {Author: "3", State: approved}, + {Author: "4", State: approved}, }, docs: false, code: true, @@ -592,8 +568,8 @@ func TestCheckInternal(t *testing.T) { { desc: "docs-and-code-only-docs-approval-fail", author: "6", - reviews: map[string]*github.Review{ - "7": &github.Review{Author: "7", State: approved}, + reviews: []github.Review{ + {Author: "7", State: approved}, }, docs: true, code: true, @@ -602,9 +578,9 @@ func TestCheckInternal(t *testing.T) { { desc: "docs-and-code-only-code-approval-fail", author: "6", - reviews: map[string]*github.Review{ - "3": &github.Review{Author: "3", State: approved}, - "4": &github.Review{Author: "4", State: approved}, + reviews: []github.Review{ + {Author: "3", State: approved}, + {Author: "4", State: approved}, }, docs: true, code: true, @@ -613,10 +589,10 @@ func TestCheckInternal(t *testing.T) { { desc: "docs-and-code-docs-and-code-approval-success", author: "6", - reviews: map[string]*github.Review{ - "3": &github.Review{Author: "3", State: approved}, - "4": &github.Review{Author: "4", State: approved}, - "7": &github.Review{Author: "7", State: approved}, + reviews: []github.Review{ + {Author: "3", State: approved}, + {Author: "4", State: approved}, + {Author: "7", State: approved}, }, docs: true, code: true, @@ -625,8 +601,8 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-internal-on-approval-failure", author: "8", - reviews: map[string]*github.Review{ - "3": &github.Review{Author: "3", State: approved}, + reviews: []github.Review{ + {Author: "3", State: approved}, }, docs: false, code: true, @@ -635,9 +611,9 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-internal-code-approval-success", author: "8", - reviews: map[string]*github.Review{ - "3": &github.Review{Author: "3", State: approved}, - "4": &github.Review{Author: "4", State: approved}, + reviews: []github.Review{ + {Author: "3", State: approved}, + {Author: "4", State: approved}, }, docs: false, code: true, @@ -646,9 +622,33 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-internal-two-code-owner-approval-success", author: "4", - reviews: map[string]*github.Review{ - "3": &github.Review{Author: "3", State: approved}, - "9": &github.Review{Author: "9", State: approved}, + reviews: []github.Review{ + {Author: "3", State: approved}, + {Author: "9", State: approved}, + }, + docs: false, + code: true, + result: true, + }, + { + desc: "code-only-changes-requested-after-approval-failure", + author: "4", + reviews: []github.Review{ + {Author: "3", State: approved}, + {Author: "9", State: approved}, + {Author: "9", State: changesRequested}, + }, + docs: false, + code: true, + result: false, + }, + { + desc: "code-only-comment-after-approval-success", + author: "4", + reviews: []github.Review{ + {Author: "3", State: approved}, + {Author: "9", State: approved}, + {Author: "9", State: commented}, }, docs: false, code: true, @@ -657,9 +657,9 @@ func TestCheckInternal(t *testing.T) { { desc: "cloud-with-self-approval-failure", author: "10", - reviews: map[string]*github.Review{ - "11": &github.Review{Author: "11", State: approved}, - "12": &github.Review{Author: "12", State: approved}, + reviews: []github.Review{ + {Author: "11", State: approved}, + {Author: "12", State: approved}, }, docs: false, code: true, @@ -668,9 +668,9 @@ func TestCheckInternal(t *testing.T) { { desc: "cloud-with-core-approval-success", author: "10", - reviews: map[string]*github.Review{ - "3": &github.Review{Author: "3", State: approved}, - "9": &github.Review{Author: "9", State: approved}, + reviews: []github.Review{ + {Author: "3", State: approved}, + {Author: "9", State: approved}, }, docs: false, code: true, diff --git a/.golangci.yml b/.golangci.yml index 3d28c3459114b..89ff401c285d6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,6 +15,7 @@ linters: enable: - bodyclose - deadcode + - depguard - goimports - gosimple - govet @@ -28,6 +29,13 @@ linters: - unconvert - varcheck +linters-settings: + depguard: + list-type: denylist + include-go-root: true # check against stdlib + packages-with-error-message: + - io/ioutil: 'use "io" or "os" packages instead' + output: uniq-by-line: false diff --git a/Cargo.lock b/Cargo.lock index 0edacf469da90..e4035cdf0adc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.53" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" +checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" [[package]] name = "arrayvec" @@ -176,9 +176,9 @@ dependencies = [ [[package]] name = "crepe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a143ee68e4ec17aa70d0b95a1a5ef2c4e510b79cc3d956ea51bd2fe56d1856bd" +checksum = "6d0c81f0055a7c877a9a69ec9d667a0b14c2b38394c712f54b9a400d035f49a9" dependencies = [ "petgraph", "proc-macro-error", @@ -1176,9 +1176,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-xid" diff --git a/Makefile b/Makefile index 399933fdf75c0..f8d46f2c275ad 100644 --- a/Makefile +++ b/Makefile @@ -462,7 +462,7 @@ $(RENDER_TESTS): $(wildcard $(TOOLINGDIR)/cmd/render-tests/*.go) # Runs all Go/shell tests, called by CI/CD. # .PHONY: test -test: test-sh test-ci test-api test-go test-rust +test: test-helm test-sh test-ci test-api test-go test-rust # Runs bot Go tests. # @@ -475,6 +475,19 @@ test-bot: $(TEST_LOG_DIR): mkdir $(TEST_LOG_DIR) +# Google Cloud Build uses a weird homedir and Helm can't pick up plugins by default there, +# so override the plugin location via environment variable when running in CI. +.PHONY: test-helm +test-helm: + @if [ -d /builder/home ]; then export HELM_PLUGINS=/root/.local/share/helm/plugins; fi; \ + helm unittest examples/chart/teleport-cluster && \ + helm unittest examples/chart/teleport-kube-agent + +.PHONY: test-helm-update-snapshots +test-helm-update-snapshots: + helm unittest -u examples/chart/teleport-cluster + helm unittest -u examples/chart/teleport-kube-agent + # # Runs all Go tests except integration, called by CI/CD. # Chaos tests have high concurrency, run without race detector and have TestChaos prefix. diff --git a/README.md b/README.md index 99c7386576e91..adf743dfc2fd0 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ 1. [Installing and Running](#installing-and-running) 1. [Docker](#docker) 1. [Building Teleport](#building-teleport) -1. [Why did We Build Teleport?](#why-did-we-build-teleport) +1. [Why Did We Build Teleport?](#why-did-we-build-teleport) 1. [More Information](#more-information) 1. [Support and Contributing](#support-and-contributing) 1. [Is Teleport Secure and Production Ready?](#is-teleport-secure-and-production-ready) diff --git a/api/client/client.go b/api/client/client.go index b9fee45872518..e936e8887abc4 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -877,9 +877,14 @@ func (c *Client) UpsertKubeServiceV2(ctx context.Context, s types.Server) (*type // GetKubeServices returns the list of kubernetes services registered in the // cluster. func (c *Client) GetKubeServices(ctx context.Context) ([]types.Server, error) { - resources, err := c.GetResources(ctx, defaults.Namespace, types.KindKubeService) + resources, err := GetResourcesWithFilters(ctx, c, proto.ListResourcesRequest{ + Namespace: defaults.Namespace, + ResourceType: types.KindKubeService, + }) if err != nil { - // DELETE IN 10.0 + // Underlying ListResources for kube service was not available, use fallback. + // + // DELETE IN 11.0.0 if trace.IsNotImplemented(err) { return c.getKubeServicesFallback(ctx) } @@ -887,14 +892,9 @@ func (c *Client) GetKubeServices(ctx context.Context) ([]types.Server, error) { return nil, trace.Wrap(err) } - servers := make([]types.Server, len(resources)) - for i, resource := range resources { - srv, ok := resource.(types.Server) - if !ok { - return nil, trace.BadParameter("expected Server resource, got %T", resource) - } - - servers[i] = srv + servers, err := types.ResourcesWithLabels(resources).AsServers() + if err != nil { + return nil, trace.Wrap(err) } return servers, nil @@ -919,8 +919,14 @@ func (c *Client) getKubeServicesFallback(ctx context.Context) ([]types.Server, e // GetApplicationServers returns all registered application servers. func (c *Client) GetApplicationServers(ctx context.Context, namespace string) ([]types.AppServer, error) { - resources, err := c.GetResources(ctx, namespace, types.KindAppServer) + resources, err := GetResourcesWithFilters(ctx, c, proto.ListResourcesRequest{ + Namespace: namespace, + ResourceType: types.KindAppServer, + }) if err != nil { + // Underlying ListResources for app server was not available, use fallback. + // + // DELETE IN 11.0.0 if trace.IsNotImplemented(err) { servers, err := c.getApplicationServersFallback(ctx, namespace) if err != nil { @@ -933,14 +939,9 @@ func (c *Client) GetApplicationServers(ctx context.Context, namespace string) ([ return nil, trace.Wrap(err) } - servers := make([]types.AppServer, len(resources)) - for i, resource := range resources { - appServer, ok := resource.(types.AppServer) - if !ok { - return nil, trace.BadParameter("expected AppServer resource, got %T", resource) - } - - servers[i] = appServer + servers, err := types.ResourcesWithLabels(resources).AsAppServers() + if err != nil { + return nil, trace.Wrap(err) } // In addition, we need to fetch legacy application servers. @@ -1178,8 +1179,14 @@ func (c *Client) DeleteAllKubeServices(ctx context.Context) error { // GetDatabaseServers returns all registered database proxy servers. func (c *Client) GetDatabaseServers(ctx context.Context, namespace string) ([]types.DatabaseServer, error) { - resources, err := c.GetResources(ctx, namespace, types.KindDatabaseServer) + resources, err := GetResourcesWithFilters(ctx, c, proto.ListResourcesRequest{ + Namespace: namespace, + ResourceType: types.KindDatabaseServer, + }) if err != nil { + // Underlying ListResources for db server was not available, use fallback. + // + // DELETE IN 11.0.0 if trace.IsNotImplemented(err) { servers, err := c.getDatabaseServersFallback(ctx, namespace) if err != nil { @@ -1192,14 +1199,9 @@ func (c *Client) GetDatabaseServers(ctx context.Context, namespace string) ([]ty return nil, trace.Wrap(err) } - servers := make([]types.DatabaseServer, len(resources)) - for i, resource := range resources { - databaseServer, ok := resource.(types.DatabaseServer) - if !ok { - return nil, trace.BadParameter("expected DatabaseServer resource, got %T", resource) - } - - servers[i] = databaseServer + servers, err := types.ResourcesWithLabels(resources).AsDatabaseServers() + if err != nil { + return nil, trace.Wrap(err) } return servers, nil @@ -1624,17 +1626,46 @@ func (c *Client) GetNode(ctx context.Context, namespace, name string) (types.Ser // GetNodes returns a complete list of nodes that the user has access to in the given namespace. func (c *Client) GetNodes(ctx context.Context, namespace string) ([]types.Server, error) { - return GetNodesWithLabels(ctx, c, namespace, nil) + resources, err := GetResourcesWithFilters(ctx, c, proto.ListResourcesRequest{ + ResourceType: types.KindNode, + Namespace: namespace, + }) + if err != nil { + // Underlying ListResources for nodes was not available, use fallback. + // + // DELETE IN 11.0.0 + if trace.IsNotImplemented(err) { + servers, err := GetNodesWithLabels(ctx, c, namespace, nil) + if err != nil { + return nil, trace.Wrap(err) + } + return servers, nil + } + + return nil, trace.Wrap(err) + } + + servers, err := types.ResourcesWithLabels(resources).AsServers() + if err != nil { + return nil, trace.Wrap(err) + } + + return servers, nil } // NodeClient is an interface used by GetNodesWithLabels to abstract over implementations of // the ListNodes method. +// +// DELETE IN 11.0.0 with GetNodesWithLabels (used in both api/client/client.go and lib/auth/httpfallback.go) +// replaced by ListResourcesClient type NodeClient interface { ListNodes(ctx context.Context, req proto.ListNodesRequest) (nodes []types.Server, nextKey string, err error) } // GetNodesWithLabels is a helper for getting a list of nodes with optional label-based filtering. In addition to // iterating pages, it also correctly handles downsizing pages when LimitExceeded errors are encountered. +// +// DELETE IN 11.0.0 replaced by GetResourcesWithFilters. func GetNodesWithLabels(ctx context.Context, clt NodeClient, namespace string, labels map[string]string) ([]types.Server, error) { // Retrieve the complete list of nodes in chunks. var ( @@ -2409,6 +2440,10 @@ func (c *Client) ListResources(ctx context.Context, req proto.ListResourcesReque resources[i] = respResource.GetNode() case types.KindKubeService: resources[i] = respResource.GetKubeService() + case types.KindWindowsDesktop: + resources[i] = respResource.GetWindowsDesktop() + case types.KindKubernetesCluster: + resources[i] = respResource.GetKubeCluster() default: return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType) } @@ -2421,9 +2456,16 @@ func (c *Client) ListResources(ctx context.Context, req proto.ListResourcesReque }, nil } -// GetResources retrieves all pages from ListResourcesPage and return all -// resources. -func (c *Client) GetResources(ctx context.Context, namespace, resourceType string) ([]types.ResourceWithLabels, error) { +// ListResourcesClient is an interface used by GetResourcesWithFilters to abstract over implementations of +// the ListResources method. +type ListResourcesClient interface { + ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) +} + +// GetResourcesWithFilters is a helper for getting a list of resources with optional filtering. In addition to +// iterating pages, it also correctly handles downsizing pages when LimitExceeded errors are encountered. +func GetResourcesWithFilters(ctx context.Context, clt ListResourcesClient, req proto.ListResourcesRequest) ([]types.ResourceWithLabels, error) { + // Retrieve the complete list of resources in chunks. var ( resources []types.ResourceWithLabels startKey string @@ -2431,15 +2473,20 @@ func (c *Client) GetResources(ctx context.Context, namespace, resourceType strin ) for { - resp, err := c.ListResources(ctx, proto.ListResourcesRequest{ - Namespace: namespace, - ResourceType: resourceType, - StartKey: startKey, - Limit: chunkSize, + resp, err := clt.ListResources(ctx, proto.ListResourcesRequest{ + Namespace: req.Namespace, + ResourceType: req.ResourceType, + StartKey: startKey, + Limit: chunkSize, + Labels: req.Labels, + SearchKeywords: req.SearchKeywords, + PredicateExpression: req.PredicateExpression, }) if err != nil { if trace.IsLimitExceeded(err) { + // Cut chunkSize in half if gRPC max message size is exceeded. chunkSize = chunkSize / 2 + // This is an extremely unlikely scenario, but better to cover it anyways. if chunkSize == 0 { return nil, trace.Wrap(trail.FromGRPC(err), "resource is too large to retrieve") } diff --git a/api/client/client_test.go b/api/client/client_test.go index 5b67271ff7238..58855bd74688f 100644 --- a/api/client/client_test.go +++ b/api/client/client_test.go @@ -121,6 +121,13 @@ func (m *mockServer) ListResources(ctx context.Context, req *proto.ListResources } protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_KubeService{KubeService: srv}} + case types.KindWindowsDesktop: + desktop, ok := resource.(*types.WindowsDesktopV3) + if !ok { + return nil, trace.Errorf("windows desktop has invalid type %T", resource) + } + + protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_WindowsDesktop{WindowsDesktop: desktop}} } resp.Resources = append(resp.Resources, protoResource) @@ -226,6 +233,21 @@ func testResources(resourceType, namespace string) ([]types.ResourceWithLabels, return nil, trace.Wrap(err) } } + case types.KindWindowsDesktop: + for i := 0; i < size; i++ { + var err error + name := fmt.Sprintf("windows-desktop-%d", i) + resources[i], err = types.NewWindowsDesktopV3( + name, + map[string]string{"label": string(make([]byte, labelSize))}, + types.WindowsDesktopSpecV3{ + Addr: "_", + HostID: "_", + }) + if err != nil { + return nil, trace.Wrap(err) + } + } default: return nil, trace.Errorf("unsupported resource type %s", resourceType) @@ -479,6 +501,10 @@ func TestListResources(t *testing.T) { resourceType: types.KindKubeService, resourceStruct: &types.ServerV2{}, }, + "WindowsDesktop": { + resourceType: types.KindWindowsDesktop, + resourceStruct: &types.WindowsDesktopV3{}, + }, } // Create client @@ -558,6 +584,9 @@ func TestGetResources(t *testing.T) { "KubeService": { resourceType: types.KindKubeService, }, + "WindowsDesktop": { + resourceType: types.KindWindowsDesktop, + }, } for name, test := range testCases { @@ -565,7 +594,7 @@ func TestGetResources(t *testing.T) { expectedResources, err := testResources(test.resourceType, defaults.Namespace) require.NoError(t, err) - // listing everything at once breaks the ListResource. + // Test listing everything at once errors with limit exceeded. _, err = clt.ListResources(ctx, proto.ListResourcesRequest{ Namespace: defaults.Namespace, Limit: int32(len(expectedResources)), @@ -574,7 +603,11 @@ func TestGetResources(t *testing.T) { require.Error(t, err) require.IsType(t, &trace.LimitExceededError{}, err.(*trace.TraceErr).OrigError()) - resources, err := clt.GetResources(ctx, defaults.Namespace, test.resourceType) + // Test getting all resources by chunks to handle limit exceeded. + resources, err := GetResourcesWithFilters(ctx, clt, proto.ListResourcesRequest{ + Namespace: defaults.Namespace, + ResourceType: test.resourceType, + }) require.NoError(t, err) require.Len(t, resources, len(expectedResources)) require.Empty(t, cmp.Diff(expectedResources, resources)) diff --git a/api/client/contextdialer.go b/api/client/contextdialer.go index a8396165aa173..1710672b941d1 100644 --- a/api/client/contextdialer.go +++ b/api/client/contextdialer.go @@ -45,8 +45,8 @@ func (f ContextDialerFunc) DialContext(ctx context.Context, network, addr string return f(ctx, network, addr) } -// NewDirectDialer makes a new dialer to connect directly to an Auth server. -func NewDirectDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { +// newDirectDialer makes a new dialer to connect directly to an Auth server. +func newDirectDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { return &net.Dialer{ Timeout: dialTimeout, KeepAlive: keepAlivePeriod, @@ -56,8 +56,8 @@ func NewDirectDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { // NewDialer makes a new dialer that connects to an Auth server either directly or via an HTTP proxy, depending // on the environment. func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { - dialer := NewDirectDialer(keepAlivePeriod, dialTimeout) return ContextDialerFunc(func(ctx context.Context, network, addr string) (net.Conn, error) { + dialer := newDirectDialer(keepAlivePeriod, dialTimeout) if proxyAddr := proxy.GetProxyAddress(addr); proxyAddr != nil { return DialProxyWithDialer(ctx, proxyAddr, addr, dialer) } @@ -70,7 +70,8 @@ func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { func NewProxyDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Duration, discoveryAddr string, insecure bool) ContextDialer { dialer := newTunnelDialer(ssh, keepAlivePeriod, dialTimeout) return ContextDialerFunc(func(ctx context.Context, network, _ string) (conn net.Conn, err error) { - tunnelAddr, err := webclient.GetTunnelAddr(ctx, discoveryAddr, insecure, nil) + tunnelAddr, err := webclient.GetTunnelAddr( + &webclient.Config{Context: ctx, ProxyAddr: discoveryAddr, Insecure: insecure}) if err != nil { return nil, trace.Wrap(err) } @@ -85,7 +86,7 @@ func NewProxyDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Dura // newTunnelDialer makes a dialer to connect to an Auth server through the SSH reverse tunnel on the proxy. func newTunnelDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Duration) ContextDialer { - dialer := NewDirectDialer(keepAlivePeriod, dialTimeout) + dialer := newDirectDialer(keepAlivePeriod, dialTimeout) return ContextDialerFunc(func(ctx context.Context, network, addr string) (conn net.Conn, err error) { conn, err = dialer.DialContext(ctx, network, addr) if err != nil { @@ -104,7 +105,8 @@ func newTunnelDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Dur // through the SSH reverse tunnel on the proxy. func newTLSRoutingTunnelDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Duration, discoveryAddr string, insecure bool) ContextDialer { return ContextDialerFunc(func(ctx context.Context, network, addr string) (conn net.Conn, err error) { - tunnelAddr, err := webclient.GetTunnelAddr(ctx, discoveryAddr, insecure, nil) + tunnelAddr, err := webclient.GetTunnelAddr( + &webclient.Config{Context: ctx, ProxyAddr: discoveryAddr, Insecure: insecure}) if err != nil { return nil, trace.Wrap(err) } diff --git a/api/client/credentials.go b/api/client/credentials.go index 2b4e13312d03a..c2660c0e181c6 100644 --- a/api/client/credentials.go +++ b/api/client/credentials.go @@ -19,7 +19,7 @@ package client import ( "crypto/tls" "crypto/x509" - "io/ioutil" + "os" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/identityfile" @@ -115,7 +115,7 @@ func (c *keypairCreds) TLSConfig() (*tls.Config, error) { return nil, trace.Wrap(err) } - cas, err := ioutil.ReadFile(c.caFile) + cas, err := os.ReadFile(c.caFile) if err != nil { return nil, trace.ConvertSystemError(err) } diff --git a/api/client/credentials_test.go b/api/client/credentials_test.go index faa09c4245d6f..7985d9a4b4a5a 100644 --- a/api/client/credentials_test.go +++ b/api/client/credentials_test.go @@ -19,7 +19,6 @@ package client import ( "crypto/tls" "crypto/x509" - "io/ioutil" "os" "path/filepath" "testing" @@ -153,11 +152,11 @@ func TestLoadKeyPair(t *testing.T) { // Write key pair and CAs files from bytes. path := t.TempDir() + "username" certPath, keyPath, caPath := path+".crt", path+".key", path+".cas" - err := ioutil.WriteFile(certPath, tlsCert, 0600) + err := os.WriteFile(certPath, tlsCert, 0600) require.NoError(t, err) - err = ioutil.WriteFile(keyPath, keyPEM, 0600) + err = os.WriteFile(keyPath, keyPEM, 0600) require.NoError(t, err) - err = ioutil.WriteFile(caPath, tlsCACert, 0600) + err = os.WriteFile(caPath, tlsCACert, 0600) require.NoError(t, err) // Load key pair from disk. @@ -185,20 +184,7 @@ func TestLoadProfile(t *testing.T) { SiteName: "example.com", Username: "testUser", Dir: dir, - }, false) - testProfileContents(t, dir, profileName) - }) - - // DELETE IN 8.0.0 - t.Run("old profile", func(t *testing.T) { - t.Parallel() - dir := t.TempDir() - writeProfile(t, &profile.Profile{ - WebProxyAddr: profileName + ":3080", - SiteName: "example.com", - Username: "testUser", - Dir: dir, - }, true) + }) testProfileContents(t, dir, profileName) }) @@ -236,24 +222,18 @@ func testProfileContents(t *testing.T, dir, name string) { require.NoError(t, err) } -func writeProfile(t *testing.T, p *profile.Profile, oldSSHPath bool) { +func writeProfile(t *testing.T, p *profile.Profile) { // Save profile and keys to disk. require.NoError(t, p.SaveToDir(p.Dir, true)) require.NoError(t, os.MkdirAll(p.KeyDir(), 0700)) require.NoError(t, os.MkdirAll(p.ProxyKeyDir(), 0700)) require.NoError(t, os.MkdirAll(p.TLSClusterCASDir(), 0700)) - require.NoError(t, ioutil.WriteFile(p.UserKeyPath(), keyPEM, 0600)) - require.NoError(t, ioutil.WriteFile(p.TLSCertPath(), tlsCert, 0600)) - require.NoError(t, ioutil.WriteFile(p.TLSCAPathCluster(p.SiteName), tlsCACert, 0600)) - require.NoError(t, ioutil.WriteFile(p.KnownHostsPath(), sshCACert, 0600)) - // If oldSSHPath is specified, write the sshCert to the old ssh cert path. - // DELETE IN 8.0.0 - if oldSSHPath { - require.NoError(t, ioutil.WriteFile(p.OldSSHCertPath(), sshCert, 0600)) - return - } + require.NoError(t, os.WriteFile(p.UserKeyPath(), keyPEM, 0600)) + require.NoError(t, os.WriteFile(p.TLSCertPath(), tlsCert, 0600)) + require.NoError(t, os.WriteFile(p.TLSCAPathCluster(p.SiteName), tlsCACert, 0600)) + require.NoError(t, os.WriteFile(p.KnownHostsPath(), sshCACert, 0600)) require.NoError(t, os.MkdirAll(p.SSHDir(), 0700)) - require.NoError(t, ioutil.WriteFile(p.SSHCertPath(), sshCert, 0600)) + require.NoError(t, os.WriteFile(p.SSHCertPath(), sshCert, 0600)) } func getExpectedTLSConfig(t *testing.T) *tls.Config { diff --git a/api/client/proto/authservice.pb.go b/api/client/proto/authservice.pb.go index 5ae16cf258b81..abeac68187fcd 100644 --- a/api/client/proto/authservice.pb.go +++ b/api/client/proto/authservice.pb.go @@ -105,6 +105,39 @@ func (DeviceType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_ce8bd90b12161215, []int{1} } +type DeviceUsage int32 + +const ( + DeviceUsage_DEVICE_USAGE_UNSPECIFIED DeviceUsage = 0 + // Device intended for MFA use, but not for passwordless. + // Allows both FIDO and FIDO2 devices. + // Resident keys not required. + DeviceUsage_DEVICE_USAGE_MFA DeviceUsage = 1 + // Device intended for both MFA and passwordless. + // Requires a FIDO2 device and takes a resident key slot. + DeviceUsage_DEVICE_USAGE_PASSWORDLESS DeviceUsage = 2 +) + +var DeviceUsage_name = map[int32]string{ + 0: "DEVICE_USAGE_UNSPECIFIED", + 1: "DEVICE_USAGE_MFA", + 2: "DEVICE_USAGE_PASSWORDLESS", +} + +var DeviceUsage_value = map[string]int32{ + "DEVICE_USAGE_UNSPECIFIED": 0, + "DEVICE_USAGE_MFA": 1, + "DEVICE_USAGE_PASSWORDLESS": 2, +} + +func (x DeviceUsage) String() string { + return proto.EnumName(DeviceUsage_name, int32(x)) +} + +func (DeviceUsage) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_ce8bd90b12161215, []int{2} +} + // Order specifies any ordering of some objects as returned in regards to some aspect // of said objects which may be trivially ordered such as a timestamp. type Order int32 @@ -129,7 +162,7 @@ func (x Order) String() string { } func (Order) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{2} + return fileDescriptor_ce8bd90b12161215, []int{3} } type UserCertsRequest_CertUsage int32 @@ -5694,11 +5727,14 @@ func (*AddMFADeviceResponse) XXX_OneofWrappers() []interface{} { // AddMFADeviceRequestInit describes the new MFA device. type AddMFADeviceRequestInit struct { - DeviceName string `protobuf:"bytes,1,opt,name=DeviceName,proto3" json:"DeviceName,omitempty"` - DeviceType DeviceType `protobuf:"varint,3,opt,name=DeviceType,proto3,enum=proto.DeviceType" json:"DeviceType,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DeviceName string `protobuf:"bytes,1,opt,name=DeviceName,proto3" json:"DeviceName,omitempty"` + DeviceType DeviceType `protobuf:"varint,3,opt,name=DeviceType,proto3,enum=proto.DeviceType" json:"DeviceType,omitempty"` + // DeviceUsage is the requested usage for the device. + // Defaults to DEVICE_USAGE_MFA. + DeviceUsage DeviceUsage `protobuf:"varint,4,opt,name=DeviceUsage,proto3,enum=proto.DeviceUsage" json:"device_usage,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *AddMFADeviceRequestInit) Reset() { *m = AddMFADeviceRequestInit{} } @@ -5748,6 +5784,13 @@ func (m *AddMFADeviceRequestInit) GetDeviceType() DeviceType { return DeviceType_DEVICE_TYPE_UNSPECIFIED } +func (m *AddMFADeviceRequestInit) GetDeviceUsage() DeviceUsage { + if m != nil { + return m.DeviceUsage + } + return DeviceUsage_DEVICE_USAGE_UNSPECIFIED +} + // AddMFADeviceResponseAck is a confirmation of successful device registration. type AddMFADeviceResponseAck struct { Device *types.MFADevice `protobuf:"bytes,1,opt,name=Device,proto3" json:"Device,omitempty"` @@ -8761,16 +8804,97 @@ func (m *UserCredentials) GetPassword() []byte { return nil } +// ContextUser marks requests that rely in the currently authenticated user. +type ContextUser struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ContextUser) Reset() { *m = ContextUser{} } +func (m *ContextUser) String() string { return proto.CompactTextString(m) } +func (*ContextUser) ProtoMessage() {} +func (*ContextUser) Descriptor() ([]byte, []int) { + return fileDescriptor_ce8bd90b12161215, []int{130} +} +func (m *ContextUser) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ContextUser) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ContextUser.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ContextUser) XXX_Merge(src proto.Message) { + xxx_messageInfo_ContextUser.Merge(m, src) +} +func (m *ContextUser) XXX_Size() int { + return m.Size() +} +func (m *ContextUser) XXX_DiscardUnknown() { + xxx_messageInfo_ContextUser.DiscardUnknown(m) +} + +var xxx_messageInfo_ContextUser proto.InternalMessageInfo + +// Passwordless marks requests for passwordless challenges. +type Passwordless struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Passwordless) Reset() { *m = Passwordless{} } +func (m *Passwordless) String() string { return proto.CompactTextString(m) } +func (*Passwordless) ProtoMessage() {} +func (*Passwordless) Descriptor() ([]byte, []int) { + return fileDescriptor_ce8bd90b12161215, []int{131} +} +func (m *Passwordless) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Passwordless) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Passwordless.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Passwordless) XXX_Merge(src proto.Message) { + xxx_messageInfo_Passwordless.Merge(m, src) +} +func (m *Passwordless) XXX_Size() int { + return m.Size() +} +func (m *Passwordless) XXX_DiscardUnknown() { + xxx_messageInfo_Passwordless.DiscardUnknown(m) +} + +var xxx_messageInfo_Passwordless proto.InternalMessageInfo + // CreateAuthenticateChallengeRequest is a request for creating MFA authentication challenges for a // users mfa devices. type CreateAuthenticateChallengeRequest struct { // Request defines how the request will be verified before creating challenges. - // This field can be empty, which implies the request is to create challenges for the - // user in context (logged in user). + // An empty Request is equivalent to context_user being set. // // Types that are valid to be assigned to Request: // *CreateAuthenticateChallengeRequest_UserCredentials // *CreateAuthenticateChallengeRequest_RecoveryStartTokenID + // *CreateAuthenticateChallengeRequest_ContextUser + // *CreateAuthenticateChallengeRequest_Passwordless Request isCreateAuthenticateChallengeRequest_Request `protobuf_oneof:"Request"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -8781,7 +8905,7 @@ func (m *CreateAuthenticateChallengeRequest) Reset() { *m = CreateAuthen func (m *CreateAuthenticateChallengeRequest) String() string { return proto.CompactTextString(m) } func (*CreateAuthenticateChallengeRequest) ProtoMessage() {} func (*CreateAuthenticateChallengeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{130} + return fileDescriptor_ce8bd90b12161215, []int{132} } func (m *CreateAuthenticateChallengeRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8822,11 +8946,21 @@ type CreateAuthenticateChallengeRequest_UserCredentials struct { type CreateAuthenticateChallengeRequest_RecoveryStartTokenID struct { RecoveryStartTokenID string `protobuf:"bytes,2,opt,name=RecoveryStartTokenID,proto3,oneof" json:"recovery_start_token_id,omitempty"` } +type CreateAuthenticateChallengeRequest_ContextUser struct { + ContextUser *ContextUser `protobuf:"bytes,3,opt,name=ContextUser,proto3,oneof" json:"context_user,omitempty"` +} +type CreateAuthenticateChallengeRequest_Passwordless struct { + Passwordless *Passwordless `protobuf:"bytes,4,opt,name=Passwordless,proto3,oneof" json:"passwordless,omitempty"` +} func (*CreateAuthenticateChallengeRequest_UserCredentials) isCreateAuthenticateChallengeRequest_Request() { } func (*CreateAuthenticateChallengeRequest_RecoveryStartTokenID) isCreateAuthenticateChallengeRequest_Request() { } +func (*CreateAuthenticateChallengeRequest_ContextUser) isCreateAuthenticateChallengeRequest_Request() { +} +func (*CreateAuthenticateChallengeRequest_Passwordless) isCreateAuthenticateChallengeRequest_Request() { +} func (m *CreateAuthenticateChallengeRequest) GetRequest() isCreateAuthenticateChallengeRequest_Request { if m != nil { @@ -8849,11 +8983,27 @@ func (m *CreateAuthenticateChallengeRequest) GetRecoveryStartTokenID() string { return "" } +func (m *CreateAuthenticateChallengeRequest) GetContextUser() *ContextUser { + if x, ok := m.GetRequest().(*CreateAuthenticateChallengeRequest_ContextUser); ok { + return x.ContextUser + } + return nil +} + +func (m *CreateAuthenticateChallengeRequest) GetPasswordless() *Passwordless { + if x, ok := m.GetRequest().(*CreateAuthenticateChallengeRequest_Passwordless); ok { + return x.Passwordless + } + return nil +} + // XXX_OneofWrappers is for the internal use of the proto package. func (*CreateAuthenticateChallengeRequest) XXX_OneofWrappers() []interface{} { return []interface{}{ (*CreateAuthenticateChallengeRequest_UserCredentials)(nil), (*CreateAuthenticateChallengeRequest_RecoveryStartTokenID)(nil), + (*CreateAuthenticateChallengeRequest_ContextUser)(nil), + (*CreateAuthenticateChallengeRequest_Passwordless)(nil), } } @@ -8875,7 +9025,7 @@ func (m *CreatePrivilegeTokenRequest) Reset() { *m = CreatePrivilegeToke func (m *CreatePrivilegeTokenRequest) String() string { return proto.CompactTextString(m) } func (*CreatePrivilegeTokenRequest) ProtoMessage() {} func (*CreatePrivilegeTokenRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{131} + return fileDescriptor_ce8bd90b12161215, []int{133} } func (m *CreatePrivilegeTokenRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8918,17 +9068,20 @@ type CreateRegisterChallengeRequest struct { // All user token types are accepted except UserTokenTypeRecoveryStart. TokenID string `protobuf:"bytes,1,opt,name=TokenID,proto3" json:"token_id"` // DeviceType is the type of MFA device to make a register challenge for. - DeviceType DeviceType `protobuf:"varint,2,opt,name=DeviceType,proto3,enum=proto.DeviceType" json:"device_type"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DeviceType DeviceType `protobuf:"varint,2,opt,name=DeviceType,proto3,enum=proto.DeviceType" json:"device_type"` + // DeviceUsage is the requested usage for the device. + // Defaults to DEVICE_USAGE_MFA. + DeviceUsage DeviceUsage `protobuf:"varint,3,opt,name=DeviceUsage,proto3,enum=proto.DeviceUsage" json:"device_usage,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *CreateRegisterChallengeRequest) Reset() { *m = CreateRegisterChallengeRequest{} } func (m *CreateRegisterChallengeRequest) String() string { return proto.CompactTextString(m) } func (*CreateRegisterChallengeRequest) ProtoMessage() {} func (*CreateRegisterChallengeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{132} + return fileDescriptor_ce8bd90b12161215, []int{134} } func (m *CreateRegisterChallengeRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8971,6 +9124,13 @@ func (m *CreateRegisterChallengeRequest) GetDeviceType() DeviceType { return DeviceType_DEVICE_TYPE_UNSPECIFIED } +func (m *CreateRegisterChallengeRequest) GetDeviceUsage() DeviceUsage { + if m != nil { + return m.DeviceUsage + } + return DeviceUsage_DEVICE_USAGE_UNSPECIFIED +} + // PaginatedResource represents one of the supported resources. type PaginatedResource struct { // Resource is the resource itself. @@ -8980,6 +9140,8 @@ type PaginatedResource struct { // *PaginatedResource_AppServer // *PaginatedResource_Node // *PaginatedResource_KubeService + // *PaginatedResource_WindowsDesktop + // *PaginatedResource_KubeCluster Resource isPaginatedResource_Resource `protobuf_oneof:"resource"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -8990,7 +9152,7 @@ func (m *PaginatedResource) Reset() { *m = PaginatedResource{} } func (m *PaginatedResource) String() string { return proto.CompactTextString(m) } func (*PaginatedResource) ProtoMessage() {} func (*PaginatedResource) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{133} + return fileDescriptor_ce8bd90b12161215, []int{135} } func (m *PaginatedResource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9037,11 +9199,19 @@ type PaginatedResource_Node struct { type PaginatedResource_KubeService struct { KubeService *types.ServerV2 `protobuf:"bytes,4,opt,name=KubeService,proto3,oneof" json:"kube_service,omitempty"` } +type PaginatedResource_WindowsDesktop struct { + WindowsDesktop *types.WindowsDesktopV3 `protobuf:"bytes,5,opt,name=WindowsDesktop,proto3,oneof" json:"windows_desktop,omitempty"` +} +type PaginatedResource_KubeCluster struct { + KubeCluster *types.KubernetesClusterV3 `protobuf:"bytes,6,opt,name=KubeCluster,proto3,oneof" json:"kube_cluster,omitempty"` +} func (*PaginatedResource_DatabaseServer) isPaginatedResource_Resource() {} func (*PaginatedResource_AppServer) isPaginatedResource_Resource() {} func (*PaginatedResource_Node) isPaginatedResource_Resource() {} func (*PaginatedResource_KubeService) isPaginatedResource_Resource() {} +func (*PaginatedResource_WindowsDesktop) isPaginatedResource_Resource() {} +func (*PaginatedResource_KubeCluster) isPaginatedResource_Resource() {} func (m *PaginatedResource) GetResource() isPaginatedResource_Resource { if m != nil { @@ -9078,6 +9248,20 @@ func (m *PaginatedResource) GetKubeService() *types.ServerV2 { return nil } +func (m *PaginatedResource) GetWindowsDesktop() *types.WindowsDesktopV3 { + if x, ok := m.GetResource().(*PaginatedResource_WindowsDesktop); ok { + return x.WindowsDesktop + } + return nil +} + +func (m *PaginatedResource) GetKubeCluster() *types.KubernetesClusterV3 { + if x, ok := m.GetResource().(*PaginatedResource_KubeCluster); ok { + return x.KubeCluster + } + return nil +} + // XXX_OneofWrappers is for the internal use of the proto package. func (*PaginatedResource) XXX_OneofWrappers() []interface{} { return []interface{}{ @@ -9085,6 +9269,8 @@ func (*PaginatedResource) XXX_OneofWrappers() []interface{} { (*PaginatedResource_AppServer)(nil), (*PaginatedResource_Node)(nil), (*PaginatedResource_KubeService)(nil), + (*PaginatedResource_WindowsDesktop)(nil), + (*PaginatedResource_KubeCluster)(nil), } } @@ -9111,17 +9297,19 @@ type ListResourcesRequest struct { SortBy types.SortBy `protobuf:"bytes,8,opt,name=SortBy,proto3" json:"sort_by,omitempty"` // NeedTotalCount indicates whether or not the caller also wants the total number of resources // after filtering. - NeedTotalCount bool `protobuf:"varint,9,opt,name=NeedTotalCount,proto3" json:"need_total_count,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + NeedTotalCount bool `protobuf:"varint,9,opt,name=NeedTotalCount,proto3" json:"need_total_count,omitempty"` + // WindowsDesktopFilter specifies windows desktop specific filters. + WindowsDesktopFilter types.WindowsDesktopFilter `protobuf:"bytes,10,opt,name=WindowsDesktopFilter,proto3" json:"windows_desktop_filter,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ListResourcesRequest) Reset() { *m = ListResourcesRequest{} } func (m *ListResourcesRequest) String() string { return proto.CompactTextString(m) } func (*ListResourcesRequest) ProtoMessage() {} func (*ListResourcesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{134} + return fileDescriptor_ce8bd90b12161215, []int{136} } func (m *ListResourcesRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9213,6 +9401,13 @@ func (m *ListResourcesRequest) GetNeedTotalCount() bool { return false } +func (m *ListResourcesRequest) GetWindowsDesktopFilter() types.WindowsDesktopFilter { + if m != nil { + return m.WindowsDesktopFilter + } + return types.WindowsDesktopFilter{} +} + // ListResourceResponse response of ListResources. type ListResourcesResponse struct { // Resources is a list of resource. @@ -9232,7 +9427,7 @@ func (m *ListResourcesResponse) Reset() { *m = ListResourcesResponse{} } func (m *ListResourcesResponse) String() string { return proto.CompactTextString(m) } func (*ListResourcesResponse) ProtoMessage() {} func (*ListResourcesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{135} + return fileDescriptor_ce8bd90b12161215, []int{137} } func (m *ListResourcesResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9327,7 +9522,7 @@ func (m *CreateSessionTrackerRequest) Reset() { *m = CreateSessionTracke func (m *CreateSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*CreateSessionTrackerRequest) ProtoMessage() {} func (*CreateSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{136} + return fileDescriptor_ce8bd90b12161215, []int{138} } func (m *CreateSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9467,7 +9662,7 @@ func (m *GetSessionTrackerRequest) Reset() { *m = GetSessionTrackerReque func (m *GetSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*GetSessionTrackerRequest) ProtoMessage() {} func (*GetSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{137} + return fileDescriptor_ce8bd90b12161215, []int{139} } func (m *GetSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9516,7 +9711,7 @@ func (m *RemoveSessionTrackerRequest) Reset() { *m = RemoveSessionTracke func (m *RemoveSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*RemoveSessionTrackerRequest) ProtoMessage() {} func (*RemoveSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{138} + return fileDescriptor_ce8bd90b12161215, []int{140} } func (m *RemoveSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9564,7 +9759,7 @@ func (m *SessionTrackerUpdateState) Reset() { *m = SessionTrackerUpdateS func (m *SessionTrackerUpdateState) String() string { return proto.CompactTextString(m) } func (*SessionTrackerUpdateState) ProtoMessage() {} func (*SessionTrackerUpdateState) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{139} + return fileDescriptor_ce8bd90b12161215, []int{141} } func (m *SessionTrackerUpdateState) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9612,7 +9807,7 @@ func (m *SessionTrackerAddParticipant) Reset() { *m = SessionTrackerAddP func (m *SessionTrackerAddParticipant) String() string { return proto.CompactTextString(m) } func (*SessionTrackerAddParticipant) ProtoMessage() {} func (*SessionTrackerAddParticipant) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{140} + return fileDescriptor_ce8bd90b12161215, []int{142} } func (m *SessionTrackerAddParticipant) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9660,7 +9855,7 @@ func (m *SessionTrackerRemoveParticipant) Reset() { *m = SessionTrackerR func (m *SessionTrackerRemoveParticipant) String() string { return proto.CompactTextString(m) } func (*SessionTrackerRemoveParticipant) ProtoMessage() {} func (*SessionTrackerRemoveParticipant) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{141} + return fileDescriptor_ce8bd90b12161215, []int{143} } func (m *SessionTrackerRemoveParticipant) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9714,7 +9909,7 @@ func (m *UpdateSessionTrackerRequest) Reset() { *m = UpdateSessionTracke func (m *UpdateSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*UpdateSessionTrackerRequest) ProtoMessage() {} func (*UpdateSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{142} + return fileDescriptor_ce8bd90b12161215, []int{144} } func (m *UpdateSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9820,7 +10015,7 @@ func (m *PresenceMFAChallengeRequest) Reset() { *m = PresenceMFAChalleng func (m *PresenceMFAChallengeRequest) String() string { return proto.CompactTextString(m) } func (*PresenceMFAChallengeRequest) ProtoMessage() {} func (*PresenceMFAChallengeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{143} + return fileDescriptor_ce8bd90b12161215, []int{145} } func (m *PresenceMFAChallengeRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9871,7 +10066,7 @@ func (m *PresenceMFAChallengeSend) Reset() { *m = PresenceMFAChallengeSe func (m *PresenceMFAChallengeSend) String() string { return proto.CompactTextString(m) } func (*PresenceMFAChallengeSend) ProtoMessage() {} func (*PresenceMFAChallengeSend) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{144} + return fileDescriptor_ce8bd90b12161215, []int{146} } func (m *PresenceMFAChallengeSend) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9948,6 +10143,7 @@ func (*PresenceMFAChallengeSend) XXX_OneofWrappers() []interface{} { func init() { proto.RegisterEnum("proto.Operation", Operation_name, Operation_value) proto.RegisterEnum("proto.DeviceType", DeviceType_name, DeviceType_value) + proto.RegisterEnum("proto.DeviceUsage", DeviceUsage_name, DeviceUsage_value) proto.RegisterEnum("proto.Order", Order_name, Order_value) proto.RegisterEnum("proto.UserCertsRequest_CertUsage", UserCertsRequest_CertUsage_name, UserCertsRequest_CertUsage_value) proto.RegisterType((*Event)(nil), "proto.Event") @@ -10082,6 +10278,8 @@ func init() { proto.RegisterType((*GetAccountRecoveryTokenRequest)(nil), "proto.GetAccountRecoveryTokenRequest") proto.RegisterType((*GetAccountRecoveryCodesRequest)(nil), "proto.GetAccountRecoveryCodesRequest") proto.RegisterType((*UserCredentials)(nil), "proto.UserCredentials") + proto.RegisterType((*ContextUser)(nil), "proto.ContextUser") + proto.RegisterType((*Passwordless)(nil), "proto.Passwordless") proto.RegisterType((*CreateAuthenticateChallengeRequest)(nil), "proto.CreateAuthenticateChallengeRequest") proto.RegisterType((*CreatePrivilegeTokenRequest)(nil), "proto.CreatePrivilegeTokenRequest") proto.RegisterType((*CreateRegisterChallengeRequest)(nil), "proto.CreateRegisterChallengeRequest") @@ -10103,586 +10301,598 @@ func init() { func init() { proto.RegisterFile("authservice.proto", fileDescriptor_ce8bd90b12161215) } var fileDescriptor_ce8bd90b12161215 = []byte{ - // 9254 bytes of a gzipped FileDescriptorProto + // 9444 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x7d, 0x5b, 0x6c, 0x1b, 0xc9, - 0x96, 0x98, 0x49, 0xbd, 0xc8, 0xa3, 0x87, 0xa9, 0x92, 0x64, 0xd1, 0xb4, 0x2c, 0x7a, 0xda, 0x33, - 0xbe, 0x9e, 0xd9, 0x59, 0xdb, 0x23, 0xcd, 0xfb, 0x79, 0x49, 0x4a, 0x96, 0x64, 0xcb, 0xb2, 0xa6, - 0x29, 0xd3, 0x93, 0x7b, 0x67, 0xc3, 0x69, 0x91, 0x65, 0xa9, 0x23, 0x8a, 0xcd, 0xdb, 0xdd, 0xb4, - 0xc7, 0x08, 0x12, 0xe4, 0xb5, 0x49, 0x10, 0x24, 0xc0, 0x06, 0xc8, 0x22, 0xc9, 0x57, 0x16, 0x9b, - 0x00, 0x41, 0x12, 0xec, 0x4f, 0x10, 0xe4, 0x33, 0xbf, 0xb9, 0x09, 0x10, 0x24, 0x3f, 0x8b, 0x00, - 0xfb, 0xc1, 0xdd, 0xdc, 0x4f, 0x7d, 0x05, 0x08, 0x12, 0x20, 0xf7, 0x2b, 0xa8, 0x67, 0x57, 0x75, - 0x77, 0x91, 0x92, 0xed, 0xdc, 0xfd, 0xb1, 0xd5, 0xa7, 0xea, 0x9c, 0xaa, 0x3a, 0x75, 0xea, 0xd4, - 0xa9, 0x53, 0xa7, 0x0e, 0x61, 0xde, 0xe9, 0x87, 0xc7, 0x01, 0xf6, 0x9f, 0xbb, 0x2d, 0x7c, 0xa7, - 0xe7, 0x7b, 0xa1, 0x87, 0x26, 0xe8, 0x7f, 0xa5, 0xc5, 0x23, 0xef, 0xc8, 0xa3, 0x7f, 0xde, 0x25, - 0x7f, 0xb1, 0xc2, 0xd2, 0xb5, 0x23, 0xcf, 0x3b, 0xea, 0xe0, 0xbb, 0xf4, 0xeb, 0xb0, 0xff, 0xec, - 0x2e, 0x3e, 0xed, 0x85, 0x2f, 0x79, 0x61, 0x39, 0x5e, 0x18, 0xba, 0xa7, 0x38, 0x08, 0x9d, 0xd3, + 0x96, 0x98, 0x49, 0xbd, 0xc8, 0xa3, 0x87, 0xe9, 0x92, 0x64, 0xd1, 0xb4, 0x2c, 0x7a, 0xda, 0x33, + 0xbe, 0x9e, 0xd9, 0x59, 0xdb, 0x23, 0xcd, 0xfb, 0xb9, 0x24, 0x25, 0x4b, 0xb2, 0x65, 0x59, 0xd3, + 0x94, 0xe8, 0xc9, 0xdc, 0xd9, 0x70, 0x5a, 0x64, 0x59, 0xea, 0x88, 0x62, 0xf3, 0x76, 0x37, 0xed, + 0x31, 0x82, 0x04, 0x79, 0x6d, 0x12, 0x04, 0x08, 0xb0, 0x01, 0xb2, 0x48, 0x82, 0x7c, 0x64, 0xb1, + 0xc9, 0x4f, 0x12, 0xec, 0x4f, 0x10, 0xe4, 0x33, 0x5f, 0x01, 0x72, 0x13, 0x20, 0x48, 0x7e, 0x16, + 0x01, 0xf6, 0x83, 0x7b, 0x73, 0x3f, 0xf5, 0x1b, 0x24, 0x40, 0xee, 0x57, 0x50, 0xcf, 0xae, 0xea, + 0xee, 0x22, 0x25, 0xdb, 0xd9, 0xfc, 0xd8, 0xea, 0x53, 0x75, 0x4e, 0x55, 0x9d, 0x3a, 0x75, 0xea, + 0xd4, 0xa9, 0x53, 0x87, 0x70, 0xc5, 0xe9, 0x87, 0xc7, 0x01, 0xf6, 0x9f, 0xbb, 0x2d, 0x7c, 0xb7, + 0xe7, 0x7b, 0xa1, 0x87, 0x26, 0xe8, 0x7f, 0xa5, 0x85, 0x23, 0xef, 0xc8, 0xa3, 0x7f, 0xde, 0x23, + 0x7f, 0xb1, 0xc2, 0xd2, 0xf5, 0x23, 0xcf, 0x3b, 0xea, 0xe0, 0x7b, 0xf4, 0xeb, 0xb0, 0xff, 0xec, + 0x1e, 0x3e, 0xed, 0x85, 0x2f, 0x79, 0x61, 0x39, 0x5e, 0x18, 0xba, 0xa7, 0x38, 0x08, 0x9d, 0xd3, 0x1e, 0xaf, 0x30, 0xdd, 0xc2, 0x7e, 0x18, 0xf0, 0x8f, 0x4f, 0x8e, 0xdc, 0xf0, 0xb8, 0x7f, 0x78, - 0xa7, 0xe5, 0x9d, 0xde, 0x3d, 0xf2, 0x9d, 0xe7, 0x6e, 0xe8, 0x84, 0xae, 0xd7, 0x75, 0x3a, 0x77, - 0x43, 0xdc, 0xc1, 0x3d, 0xcf, 0x0f, 0xef, 0x3a, 0x3d, 0xf7, 0x6e, 0xf8, 0xb2, 0x87, 0x03, 0xf6, + 0xb7, 0xe5, 0x9d, 0xde, 0x3b, 0xf2, 0x9d, 0xe7, 0x6e, 0xe8, 0x84, 0xae, 0xd7, 0x75, 0x3a, 0xf7, + 0x42, 0xdc, 0xc1, 0x3d, 0xcf, 0x0f, 0xef, 0x39, 0x3d, 0xf7, 0x5e, 0xf8, 0xb2, 0x87, 0x03, 0xf6, 0x2f, 0x47, 0xac, 0x5d, 0x04, 0xf1, 0x05, 0x3e, 0x24, 0x43, 0xec, 0xca, 0x3f, 0x5e, 0x89, 0x88, 0xef, 0xf4, 0x7a, 0xd8, 0x8f, 0xfe, 0xe0, 0x44, 0xbe, 0xb9, 0x08, 0x11, 0xfc, 0x1c, 0x77, 0x43, - 0xf1, 0x1f, 0x23, 0x60, 0xfd, 0x9b, 0x45, 0x98, 0xd8, 0x24, 0x00, 0xf4, 0x29, 0x8c, 0x1f, 0xbc, - 0xec, 0xe1, 0x62, 0xe6, 0x46, 0xe6, 0xf6, 0xdc, 0x5a, 0x81, 0x95, 0xdf, 0x79, 0xdc, 0xc3, 0x3e, + 0xf1, 0x1f, 0x23, 0x60, 0xfd, 0xeb, 0x05, 0x98, 0xd8, 0x20, 0x00, 0xf4, 0x29, 0x8c, 0xef, 0xbf, + 0xec, 0xe1, 0x62, 0xe6, 0x66, 0xe6, 0xce, 0xdc, 0x6a, 0x81, 0x95, 0xdf, 0x7d, 0xd2, 0xc3, 0x3e, 0x25, 0x59, 0x45, 0x67, 0x83, 0xf2, 0x1c, 0x21, 0xf4, 0xbe, 0x77, 0xea, 0x86, 0x94, 0xeb, 0x36, - 0xc5, 0x40, 0x4f, 0x61, 0xce, 0xc6, 0x81, 0xd7, 0xf7, 0x5b, 0x78, 0x1b, 0x3b, 0x6d, 0xec, 0x17, - 0xb3, 0x37, 0x32, 0xb7, 0xa7, 0xd7, 0x96, 0xee, 0x30, 0xa6, 0xe9, 0x85, 0xd5, 0x2b, 0x67, 0x83, - 0x32, 0xf2, 0x39, 0x2c, 0x22, 0xb6, 0x7d, 0xc9, 0x8e, 0x91, 0x41, 0xdf, 0xc3, 0x6c, 0x0d, 0xfb, - 0x61, 0xa5, 0x1f, 0x1e, 0x7b, 0xbe, 0x1b, 0xbe, 0x2c, 0x8e, 0x51, 0xba, 0x57, 0x38, 0x5d, 0xad, - 0xac, 0xb1, 0x56, 0x5d, 0x39, 0x1b, 0x94, 0x8b, 0x64, 0x82, 0x9b, 0x8e, 0x80, 0x6a, 0xe4, 0x75, - 0x62, 0xe8, 0x3b, 0x98, 0xa9, 0x13, 0x76, 0xb5, 0x0e, 0xbc, 0x13, 0xdc, 0x0d, 0x8a, 0xe3, 0x5a, - 0xa7, 0xd5, 0xa2, 0xc6, 0x5a, 0xf5, 0xda, 0xd9, 0xa0, 0xbc, 0x1c, 0x50, 0x58, 0x33, 0xa4, 0x40, - 0x8d, 0xb4, 0x46, 0x09, 0xfd, 0x00, 0x73, 0xfb, 0xbe, 0xf7, 0xdc, 0x0d, 0x5c, 0xaf, 0x4b, 0x41, - 0xc5, 0x09, 0x4a, 0x7b, 0x99, 0xd3, 0xd6, 0x0b, 0x1b, 0x6b, 0xd5, 0xeb, 0x67, 0x83, 0xf2, 0xd5, - 0x9e, 0x80, 0xb2, 0x06, 0x74, 0xce, 0xe8, 0x28, 0xe8, 0x00, 0xa6, 0x6b, 0x9d, 0x7e, 0x10, 0x62, - 0x7f, 0xcf, 0x39, 0xc5, 0xc5, 0x49, 0x4a, 0x7e, 0x51, 0xf0, 0x25, 0x2a, 0x69, 0xac, 0x55, 0x4b, - 0x67, 0x83, 0xf2, 0x95, 0x16, 0x03, 0x35, 0xbb, 0xce, 0xa9, 0xce, 0x72, 0x95, 0x0c, 0xfa, 0x04, - 0xc6, 0x9f, 0x04, 0xd8, 0x2f, 0xe6, 0x28, 0xb9, 0x59, 0x4e, 0x8e, 0x80, 0x1a, 0x6b, 0x6c, 0xfe, - 0xfb, 0x01, 0xf6, 0x35, 0x7c, 0x8a, 0x40, 0x10, 0x6d, 0xaf, 0x83, 0x8b, 0x79, 0x0d, 0x91, 0x80, - 0x1a, 0x1f, 0x31, 0x44, 0xdf, 0xeb, 0xe8, 0x0d, 0x53, 0x04, 0xb4, 0x03, 0x79, 0xd2, 0x72, 0xd0, - 0x73, 0x5a, 0xb8, 0x08, 0x14, 0xbb, 0xc0, 0xb1, 0x25, 0xbc, 0xba, 0x7c, 0x36, 0x28, 0x2f, 0x74, - 0xc5, 0xa7, 0x46, 0x25, 0xc2, 0x46, 0xdf, 0xc0, 0x64, 0x1d, 0xfb, 0xcf, 0xb1, 0x5f, 0x9c, 0xa6, - 0x74, 0x2e, 0x8b, 0x89, 0xa4, 0xc0, 0xc6, 0x5a, 0x75, 0xf1, 0x6c, 0x50, 0x2e, 0x04, 0xf4, 0x4b, - 0xa3, 0xc1, 0xd1, 0x88, 0xb4, 0xd9, 0xf8, 0x39, 0xf6, 0x03, 0x7c, 0xd0, 0xef, 0x76, 0x71, 0xa7, - 0x38, 0xa3, 0x49, 0x9b, 0x56, 0x26, 0xa4, 0xcd, 0x67, 0xc0, 0x66, 0x48, 0xa1, 0xba, 0xb4, 0x69, - 0x08, 0xe8, 0x18, 0x0a, 0xec, 0xaf, 0x9a, 0xd7, 0xed, 0xe2, 0x16, 0x59, 0x52, 0xc5, 0x59, 0xda, - 0xc0, 0x55, 0xde, 0x40, 0xbc, 0xb8, 0xb1, 0x56, 0x2d, 0x9f, 0x0d, 0xca, 0xd7, 0x18, 0xed, 0x66, - 0x4b, 0x16, 0x68, 0xcd, 0x24, 0xa8, 0x92, 0x71, 0x54, 0x5a, 0x2d, 0x1c, 0x04, 0x36, 0xfe, 0x45, - 0x1f, 0x07, 0x61, 0x71, 0x4e, 0x1b, 0x87, 0x56, 0xd6, 0x58, 0x67, 0xe3, 0x70, 0x28, 0xb0, 0xe9, - 0x33, 0xa8, 0x3e, 0x0e, 0x0d, 0x01, 0xed, 0x03, 0x54, 0x7a, 0xbd, 0x3a, 0x0e, 0x88, 0x30, 0x16, - 0x2f, 0x53, 0xd2, 0x0b, 0x9c, 0xf4, 0x53, 0x7c, 0xc8, 0x0b, 0x1a, 0x6b, 0xd5, 0xab, 0x67, 0x83, - 0xf2, 0x92, 0xd3, 0xeb, 0x35, 0x03, 0x06, 0xd2, 0x88, 0x2a, 0x34, 0x18, 0xdf, 0x4f, 0xbd, 0x10, - 0x73, 0x51, 0x2c, 0x16, 0x62, 0x7c, 0x57, 0xca, 0x44, 0x7f, 0x7d, 0x0a, 0x6c, 0x72, 0xb1, 0x8e, - 0xf3, 0x5d, 0x41, 0x20, 0x6b, 0x71, 0xc3, 0x09, 0x9d, 0x43, 0x27, 0xc0, 0x5c, 0x3c, 0xe6, 0xb5, - 0xb5, 0xa8, 0x17, 0x36, 0xd6, 0xd9, 0x5a, 0x6c, 0x73, 0x68, 0x33, 0x45, 0x5e, 0x62, 0xf4, 0x08, - 0x47, 0xa2, 0x81, 0x17, 0xd1, 0x08, 0x8e, 0xbc, 0xc0, 0x87, 0xe9, 0x1c, 0x89, 0xaa, 0xa2, 0x6d, - 0xc8, 0x3d, 0xc5, 0x87, 0x4c, 0x73, 0x2c, 0x50, 0x7a, 0xf3, 0x11, 0x3d, 0xa6, 0x33, 0xd6, 0xd9, - 0xaa, 0x20, 0xd4, 0x92, 0xda, 0x42, 0x62, 0xa3, 0xdf, 0xcd, 0xc0, 0xb2, 0x58, 0xe1, 0x38, 0x7c, - 0xe1, 0xf9, 0x27, 0x6e, 0xf7, 0xa8, 0xe6, 0x75, 0x9f, 0xb9, 0x47, 0xc5, 0x45, 0x4a, 0xf9, 0x46, - 0x4c, 0x69, 0xc4, 0x6a, 0x35, 0xd6, 0xaa, 0x3f, 0x39, 0x1b, 0x94, 0x6f, 0x4a, 0x05, 0x22, 0xcb, - 0x89, 0x40, 0x3e, 0x73, 0x8f, 0xb4, 0x86, 0x4d, 0x6d, 0xa1, 0xbf, 0x9e, 0x81, 0x2b, 0x7c, 0x74, - 0x36, 0x6e, 0x79, 0x7e, 0x3b, 0xea, 0xc6, 0x12, 0xed, 0x46, 0x59, 0xae, 0xd6, 0xb4, 0x4a, 0x8d, - 0xb5, 0xea, 0xad, 0xb3, 0x41, 0xd9, 0xe2, 0x8c, 0x6b, 0xfa, 0xa2, 0x38, 0xad, 0x13, 0x86, 0x86, - 0x88, 0x24, 0x10, 0xe5, 0xbf, 0xef, 0xe3, 0x67, 0xd8, 0xc7, 0xdd, 0x16, 0x2e, 0x5e, 0xd1, 0x24, - 0x41, 0x2f, 0x14, 0x5a, 0x99, 0x6c, 0x25, 0xcd, 0x9e, 0x04, 0xeb, 0x92, 0xa0, 0xa3, 0xa0, 0x5f, - 0x00, 0xe2, 0x0c, 0xa8, 0xf4, 0xdb, 0x6e, 0xc8, 0x07, 0xb8, 0x4c, 0x5b, 0xb9, 0xa6, 0xf3, 0x59, - 0xa9, 0xd0, 0x58, 0xab, 0x5a, 0x67, 0x83, 0xf2, 0xaa, 0x60, 0xb1, 0x43, 0x8a, 0xd2, 0x06, 0x96, - 0x42, 0x9c, 0x68, 0xde, 0x5d, 0xaf, 0x75, 0x52, 0x2c, 0x6a, 0x9a, 0x97, 0x80, 0x84, 0xca, 0xee, - 0x78, 0xad, 0x13, 0x5d, 0xf3, 0x92, 0x52, 0x14, 0xc2, 0x02, 0x9f, 0x25, 0x1b, 0x07, 0xa1, 0xef, - 0x52, 0xdd, 0x11, 0x14, 0xaf, 0x52, 0x3a, 0x2b, 0x42, 0x07, 0x27, 0x6b, 0x34, 0x3e, 0x64, 0xbd, - 0xe5, 0x82, 0xd0, 0xf4, 0x95, 0x32, 0xad, 0x99, 0x34, 0xf2, 0xe8, 0xaf, 0xc0, 0xd2, 0x53, 0xb7, - 0xdb, 0xf6, 0x5e, 0x04, 0x1b, 0x38, 0x38, 0x09, 0xbd, 0x5e, 0x9d, 0x59, 0x7e, 0xc5, 0x12, 0x6d, - 0x77, 0x55, 0x88, 0x79, 0x5a, 0x9d, 0xc6, 0x7a, 0xf5, 0x9d, 0xb3, 0x41, 0xf9, 0xad, 0x17, 0xac, - 0xb0, 0xd9, 0x66, 0xa5, 0x4d, 0x6e, 0x3c, 0x6a, 0x8d, 0xa7, 0xb7, 0x42, 0x44, 0x40, 0x2f, 0x28, - 0x5e, 0xd3, 0x44, 0x40, 0x2f, 0x14, 0xca, 0x20, 0xd6, 0xa0, 0x2e, 0x02, 0x3a, 0x0a, 0xda, 0x82, - 0x9c, 0x50, 0x0f, 0xc5, 0x15, 0x6d, 0xe9, 0x0a, 0x70, 0x63, 0x9d, 0x59, 0x40, 0x42, 0xc5, 0xe8, - 0x2b, 0x57, 0xd4, 0x42, 0xbb, 0x90, 0xa7, 0x3a, 0x92, 0xaa, 0xac, 0xeb, 0x94, 0x12, 0x12, 0x82, - 0x2a, 0xe0, 0x8d, 0xf5, 0x6a, 0xf1, 0x6c, 0x50, 0x5e, 0x64, 0x5a, 0x36, 0xa1, 0xa8, 0x22, 0x02, - 0x68, 0x1d, 0xc6, 0x2a, 0xbd, 0x5e, 0x71, 0x95, 0xd2, 0x99, 0x89, 0xe8, 0x34, 0xd6, 0xab, 0xf3, - 0x67, 0x83, 0xf2, 0xac, 0xd3, 0xd3, 0x87, 0x45, 0x6a, 0x57, 0x01, 0x72, 0xc2, 0x20, 0x7b, 0x30, - 0x9e, 0x9b, 0x2a, 0xe4, 0xac, 0x6d, 0x98, 0x78, 0xea, 0x84, 0xad, 0x63, 0xf4, 0x0d, 0x4c, 0x3c, - 0x74, 0xbb, 0xed, 0xa0, 0x98, 0xb9, 0x31, 0x46, 0xf7, 0x6c, 0x66, 0x2d, 0xd2, 0x42, 0x52, 0x50, - 0x5d, 0xfe, 0xe5, 0xa0, 0x7c, 0xe9, 0x6c, 0x50, 0xbe, 0x7c, 0x42, 0xaa, 0x29, 0x26, 0x23, 0xc3, - 0xb3, 0xfe, 0x5d, 0x16, 0xf2, 0xb2, 0x36, 0x5a, 0x81, 0x71, 0xf2, 0x3f, 0xb5, 0x3d, 0xf3, 0xd5, - 0xdc, 0xd9, 0xa0, 0x3c, 0x4e, 0xf0, 0x6c, 0x0a, 0x45, 0x6b, 0x30, 0xbd, 0xeb, 0x39, 0xed, 0x3a, - 0x6e, 0xf9, 0x38, 0x0c, 0xa8, 0x71, 0x99, 0xab, 0x16, 0xce, 0x06, 0xe5, 0x99, 0x8e, 0xe7, 0xb4, - 0x9b, 0x01, 0x83, 0xdb, 0x6a, 0x25, 0x42, 0x91, 0x5a, 0x46, 0x63, 0x11, 0x45, 0x62, 0x41, 0xd8, - 0x14, 0x8a, 0x1e, 0xc0, 0xe4, 0x7d, 0xb7, 0x43, 0xf6, 0x9a, 0x71, 0xda, 0xff, 0x95, 0x78, 0xff, - 0xef, 0xb0, 0xe2, 0xcd, 0x6e, 0xe8, 0xbf, 0x64, 0x86, 0xc3, 0x33, 0x0a, 0x50, 0x06, 0xc2, 0x29, - 0xa0, 0x7b, 0x30, 0x55, 0xef, 0x1f, 0xd2, 0xee, 0x4f, 0xd0, 0xc6, 0xe8, 0xec, 0x06, 0xfd, 0xc3, - 0x26, 0x19, 0x82, 0x82, 0x20, 0xaa, 0x95, 0x3e, 0x83, 0x69, 0x85, 0x3c, 0x2a, 0xc0, 0xd8, 0x09, - 0x7e, 0xc9, 0xc6, 0x6e, 0x93, 0x3f, 0xd1, 0x22, 0x4c, 0x3c, 0x77, 0x3a, 0x7d, 0x4c, 0x87, 0x9a, - 0xb7, 0xd9, 0xc7, 0xe7, 0xd9, 0x4f, 0x33, 0xd6, 0x7f, 0x1d, 0x87, 0xc2, 0xb6, 0x17, 0x84, 0xc4, - 0x92, 0x95, 0x5b, 0xf2, 0x4d, 0x98, 0x24, 0xb0, 0x9d, 0x0d, 0xce, 0xbf, 0xe9, 0xb3, 0x41, 0x79, - 0xea, 0xd8, 0x0b, 0xc2, 0xa6, 0xdb, 0xb6, 0x79, 0x11, 0x7a, 0x17, 0x72, 0x7b, 0x5e, 0x1b, 0x53, - 0xa6, 0x50, 0xb2, 0xd5, 0xd9, 0xb3, 0x41, 0x39, 0xdf, 0xf5, 0xda, 0x98, 0x5a, 0x85, 0xb6, 0x2c, - 0x46, 0x0d, 0x6e, 0xcd, 0x31, 0xde, 0x55, 0x09, 0xef, 0x88, 0xf9, 0xf6, 0xeb, 0x41, 0xf9, 0xe3, - 0x0b, 0x1c, 0x37, 0xee, 0xd4, 0x5f, 0x06, 0x21, 0x3e, 0x25, 0x94, 0xb8, 0xb1, 0xf7, 0x14, 0x16, - 0x2b, 0xed, 0xb6, 0xcb, 0x30, 0xf6, 0x7d, 0xb7, 0xdb, 0x72, 0x7b, 0x4e, 0x27, 0xa0, 0x73, 0x90, - 0xaf, 0xde, 0x3c, 0x1b, 0x94, 0xcb, 0x8e, 0x2c, 0x6f, 0xf6, 0x64, 0x05, 0x85, 0x87, 0xa9, 0x04, - 0xd0, 0x3a, 0xe4, 0x36, 0xf6, 0xea, 0xd4, 0x14, 0x2c, 0x4e, 0x50, 0x62, 0x74, 0x73, 0x6c, 0x77, - 0x03, 0x3a, 0x34, 0x95, 0x80, 0xac, 0x88, 0x3e, 0x86, 0x99, 0xfd, 0xfe, 0x61, 0xc7, 0x6d, 0x1d, - 0xec, 0xd6, 0x1f, 0xe2, 0x97, 0xd4, 0x86, 0x9e, 0x61, 0x2a, 0xb3, 0x47, 0xe1, 0xcd, 0xb0, 0x13, - 0x34, 0x4f, 0xf0, 0x4b, 0x5b, 0xab, 0x17, 0xe1, 0xd5, 0xeb, 0xdb, 0x04, 0x6f, 0x2a, 0x81, 0x17, - 0x04, 0xc7, 0x2a, 0x1e, 0xab, 0x87, 0xee, 0x02, 0x30, 0xcb, 0xa4, 0xd2, 0x6e, 0x33, 0x13, 0x3b, - 0x5f, 0xbd, 0x7c, 0x36, 0x28, 0x4f, 0x73, 0x5b, 0xc6, 0x69, 0xb7, 0x7d, 0x5b, 0xa9, 0x82, 0x6a, - 0x90, 0xb3, 0x3d, 0xc6, 0x60, 0x6e, 0x58, 0x5f, 0x96, 0x86, 0x35, 0x03, 0xf3, 0xa3, 0x14, 0xff, - 0x52, 0x47, 0x29, 0x6a, 0xa0, 0x32, 0x4c, 0xed, 0x79, 0x35, 0xa7, 0x75, 0xcc, 0xcc, 0xeb, 0x5c, - 0x75, 0xe2, 0x6c, 0x50, 0xce, 0xfc, 0xb6, 0x2d, 0xa0, 0xd6, 0xbf, 0xc8, 0x41, 0x81, 0xd8, 0xf0, - 0x9a, 0x44, 0xbd, 0x0f, 0x79, 0xd6, 0xf7, 0x87, 0x5c, 0x30, 0x67, 0xaa, 0x73, 0x67, 0x83, 0x32, - 0xf0, 0x01, 0x92, 0xc1, 0x45, 0x15, 0xd0, 0x6d, 0xc8, 0x11, 0x0a, 0xdd, 0x48, 0xb4, 0x66, 0xce, - 0x06, 0xe5, 0x5c, 0x9f, 0xc3, 0x6c, 0x59, 0x8a, 0xea, 0x30, 0xb5, 0xf9, 0x63, 0xcf, 0xf5, 0x71, - 0xc0, 0x8f, 0x72, 0xa5, 0x3b, 0xec, 0xc4, 0x7e, 0x47, 0x9c, 0xd8, 0xef, 0x1c, 0x88, 0x13, 0x7b, - 0xf5, 0x3a, 0x57, 0x21, 0xf3, 0x98, 0xa1, 0x44, 0xe3, 0xfb, 0xbd, 0x3f, 0x2d, 0x67, 0x6c, 0x41, - 0x09, 0xbd, 0x0f, 0x93, 0xf7, 0x3d, 0xff, 0xd4, 0x09, 0xe9, 0x09, 0x2e, 0xcf, 0x97, 0x2b, 0x85, - 0x68, 0xcb, 0x95, 0x42, 0xd0, 0x7d, 0x98, 0xb3, 0xbd, 0x7e, 0x88, 0x0f, 0x3c, 0x61, 0x6e, 0xb2, - 0x55, 0xbb, 0x7a, 0x36, 0x28, 0x97, 0x7c, 0x52, 0xd2, 0x0c, 0xbd, 0xa4, 0x61, 0x69, 0xc7, 0xb0, - 0xd0, 0x26, 0xcc, 0x69, 0x86, 0x71, 0x50, 0x9c, 0xa4, 0x92, 0xc7, 0x8c, 0x06, 0xcd, 0x9c, 0x56, - 0xe5, 0x2f, 0x86, 0x84, 0xf6, 0x60, 0xfe, 0x61, 0xff, 0x10, 0xfb, 0x5d, 0x1c, 0xe2, 0x40, 0xf4, - 0x68, 0x8a, 0xf6, 0xe8, 0xc6, 0xd9, 0xa0, 0xbc, 0x72, 0x22, 0x0b, 0x53, 0xfa, 0x94, 0x44, 0x45, - 0x18, 0x2e, 0xf3, 0x8e, 0xca, 0x6d, 0x28, 0xc7, 0xcd, 0x69, 0xa6, 0xe2, 0x62, 0xa5, 0xd5, 0x9b, - 0x9c, 0xcb, 0xd7, 0xe4, 0xd8, 0x93, 0x1b, 0x93, 0x1d, 0xa7, 0x49, 0x56, 0x9c, 0xd4, 0x26, 0x79, - 0xda, 0x5b, 0x76, 0x48, 0x13, 0xda, 0x44, 0x95, 0x45, 0xa9, 0x57, 0x76, 0x61, 0xe2, 0x49, 0xe0, - 0x1c, 0x31, 0x49, 0x9c, 0x5b, 0x7b, 0x8b, 0xf7, 0x28, 0x2e, 0x7d, 0xf4, 0x5c, 0x4f, 0x2b, 0x56, - 0x17, 0xc8, 0x0e, 0xd2, 0x27, 0x7f, 0xaa, 0x3b, 0x08, 0x2d, 0x43, 0xdf, 0x02, 0xf0, 0x5e, 0x91, - 0x9d, 0x6d, 0x9a, 0xef, 0xb5, 0xda, 0x20, 0xc9, 0x26, 0xb6, 0xca, 0xc7, 0x77, 0x45, 0x8e, 0x4f, - 0xdb, 0xeb, 0x6c, 0x85, 0x08, 0xfa, 0x06, 0x66, 0xa8, 0xba, 0x12, 0x33, 0x3a, 0x43, 0x67, 0x94, - 0x1e, 0xfd, 0x89, 0x02, 0x4c, 0x9b, 0x4f, 0x0d, 0x01, 0xfd, 0x55, 0x58, 0xe2, 0xe4, 0x62, 0x66, - 0xc6, 0x2c, 0x37, 0xab, 0xb4, 0xee, 0xe9, 0x75, 0xaa, 0xef, 0xf1, 0x9e, 0x5a, 0xb2, 0xa7, 0x46, - 0xc3, 0xc3, 0x4e, 0x6f, 0xc6, 0xfa, 0x0e, 0xf2, 0x92, 0x79, 0x68, 0x0a, 0xc6, 0x2a, 0x9d, 0x4e, - 0xe1, 0x12, 0xf9, 0xa3, 0x5e, 0xdf, 0x2e, 0x64, 0xd0, 0x1c, 0x40, 0x24, 0x31, 0x85, 0x2c, 0x9a, - 0x89, 0x8c, 0x95, 0xc2, 0x18, 0xad, 0xdf, 0xeb, 0x15, 0xc6, 0x11, 0x8a, 0x5b, 0x49, 0x85, 0x09, - 0xeb, 0xbf, 0x67, 0x12, 0x82, 0x45, 0xf6, 0x65, 0x6e, 0x58, 0x51, 0x39, 0x60, 0x9b, 0x0f, 0xdd, - 0x97, 0xb9, 0x49, 0xc6, 0x36, 0x16, 0xb5, 0x12, 0xd1, 0x15, 0xfb, 0x84, 0x07, 0x2d, 0xaf, 0xa3, - 0xea, 0x8a, 0x1e, 0x87, 0xd9, 0xb2, 0x14, 0xad, 0x29, 0x5a, 0x65, 0x2c, 0xda, 0x58, 0x85, 0x56, - 0x51, 0x25, 0x4c, 0xea, 0x97, 0x35, 0xc5, 0xfa, 0x1a, 0x8f, 0x70, 0x52, 0x24, 0x5a, 0xd6, 0xb3, - 0xfa, 0x86, 0x39, 0x43, 0x5f, 0x24, 0x8c, 0x45, 0x36, 0x42, 0x2a, 0x94, 0xb1, 0xa9, 0x49, 0xd8, - 0x81, 0x65, 0x98, 0xd8, 0xf5, 0x8e, 0xdc, 0x2e, 0x1f, 0x64, 0xfe, 0x6c, 0x50, 0x9e, 0xe8, 0x10, - 0x80, 0xcd, 0xe0, 0xd6, 0xff, 0xcd, 0xa8, 0xf2, 0x2b, 0xed, 0x95, 0x4c, 0xaa, 0xbd, 0xf2, 0x3e, - 0xe4, 0xf9, 0xa1, 0x66, 0x67, 0x83, 0x53, 0xa4, 0xfa, 0x58, 0x9c, 0x87, 0xdc, 0xb6, 0x1d, 0x55, - 0x20, 0x3b, 0x0d, 0x53, 0xce, 0x74, 0xa7, 0x19, 0x8b, 0x76, 0x1a, 0xae, 0xbe, 0xd9, 0x4e, 0x13, - 0x55, 0x21, 0x13, 0xa9, 0x7a, 0x93, 0xc6, 0xa3, 0x89, 0x54, 0xfd, 0x46, 0xba, 0xaf, 0xe8, 0x73, - 0x80, 0xca, 0xd3, 0x3a, 0x91, 0xfe, 0x8a, 0xbd, 0xc7, 0x75, 0x28, 0x75, 0x35, 0x39, 0x2f, 0x82, - 0x26, 0x5d, 0x2d, 0x8e, 0xaf, 0x6e, 0x49, 0x4a, 0x6d, 0xab, 0x03, 0x73, 0x5b, 0x38, 0x24, 0xb3, - 0x26, 0x36, 0x9c, 0xe1, 0xc3, 0xff, 0x12, 0xa6, 0x9f, 0xba, 0xe1, 0xb1, 0x6e, 0x00, 0xd2, 0xc6, - 0x5e, 0xb8, 0xe1, 0xb1, 0x30, 0x00, 0x95, 0xc6, 0xd4, 0xea, 0xd6, 0x26, 0x5c, 0xe6, 0xad, 0xc9, - 0xfd, 0x6d, 0x4d, 0x27, 0x98, 0x89, 0x2c, 0x4a, 0x95, 0xa0, 0x4e, 0x06, 0xc7, 0x15, 0x3e, 0xaa, - 0x27, 0xb6, 0x00, 0x66, 0x0d, 0x9b, 0x3c, 0x2d, 0x54, 0x70, 0x62, 0x5b, 0x43, 0x7c, 0x43, 0xb0, - 0x9e, 0xc0, 0xec, 0x7e, 0xa7, 0x7f, 0xe4, 0x76, 0x89, 0x80, 0xd6, 0xf1, 0x2f, 0xd0, 0x06, 0x40, - 0x04, 0xe0, 0x2d, 0x08, 0xf7, 0x42, 0x54, 0xd0, 0x58, 0xe7, 0x53, 0x4c, 0x21, 0x54, 0x87, 0xdb, - 0x0a, 0x9e, 0xf5, 0xf7, 0xc6, 0x00, 0xf1, 0x36, 0xea, 0xa1, 0x13, 0xe2, 0x3a, 0x0e, 0xc9, 0x76, - 0x71, 0x05, 0xb2, 0xd2, 0x6c, 0x9c, 0x3c, 0x1b, 0x94, 0xb3, 0x6e, 0xdb, 0xce, 0xee, 0x6c, 0xa0, - 0x0f, 0x61, 0x82, 0x56, 0xa3, 0xbc, 0x9e, 0x93, 0xed, 0xa9, 0x14, 0x98, 0x4c, 0x07, 0xe4, 0x4f, - 0x9b, 0x55, 0x46, 0x1f, 0x41, 0x7e, 0x03, 0x77, 0xf0, 0x91, 0x13, 0x7a, 0x42, 0xee, 0x98, 0x21, - 0x26, 0x80, 0xca, 0x14, 0x45, 0x35, 0xc9, 0x06, 0x6e, 0x63, 0x27, 0xf0, 0xba, 0xea, 0x06, 0xee, - 0x53, 0x88, 0xba, 0x81, 0xb3, 0x3a, 0xe8, 0xf7, 0x33, 0x30, 0x5d, 0xe9, 0x76, 0xb9, 0x81, 0x13, - 0x70, 0xd7, 0xea, 0xd2, 0x1d, 0xe9, 0x19, 0xdf, 0x75, 0x0e, 0x71, 0xa7, 0x41, 0x4c, 0xe6, 0xa0, - 0xfa, 0x3d, 0xd1, 0xa9, 0x7f, 0x32, 0x28, 0x7f, 0xf1, 0x2a, 0xce, 0xf6, 0x3b, 0x07, 0xbe, 0xe3, - 0x86, 0x01, 0xf5, 0x63, 0x45, 0x0d, 0xaa, 0x62, 0xa6, 0xf4, 0x03, 0xbd, 0x0b, 0x13, 0x44, 0xbe, - 0x85, 0x1d, 0x40, 0x27, 0x9b, 0xac, 0x03, 0xed, 0xf0, 0x43, 0x6b, 0x58, 0x37, 0x21, 0xcf, 0x39, - 0xb9, 0xb3, 0x61, 0x9a, 0x02, 0x6b, 0x03, 0xae, 0x53, 0x2b, 0x0e, 0x13, 0xc9, 0xa5, 0xde, 0x1c, - 0x2e, 0x89, 0x91, 0xd9, 0x3f, 0x45, 0xc1, 0x12, 0x9b, 0x4e, 0x08, 0xf5, 0x06, 0xd9, 0xa2, 0xc4, - 0xaa, 0xc1, 0xca, 0x16, 0x0e, 0x6d, 0x1c, 0xe0, 0x70, 0xdf, 0x09, 0x82, 0x17, 0x9e, 0xdf, 0xa6, - 0x45, 0x17, 0x22, 0xf2, 0xb7, 0x32, 0x50, 0xae, 0xf9, 0x98, 0xcc, 0xb4, 0x91, 0xd0, 0xf0, 0x15, - 0xbc, 0xc2, 0x2f, 0x17, 0xb2, 0x51, 0x29, 0xe1, 0x35, 0xbf, 0x40, 0x78, 0x07, 0xc6, 0x0e, 0x0e, - 0x76, 0xa9, 0xc4, 0x8c, 0x51, 0xc6, 0x8d, 0x85, 0x61, 0xe7, 0xd7, 0x83, 0x72, 0x6e, 0xa3, 0xcf, - 0x2e, 0x1f, 0x6c, 0x52, 0x6e, 0x3d, 0x83, 0x25, 0x1b, 0x77, 0xf1, 0x0b, 0xe7, 0xb0, 0x83, 0x35, - 0x73, 0xb5, 0x0c, 0x13, 0xcc, 0x59, 0x96, 0x18, 0x02, 0x83, 0xeb, 0xf6, 0x6c, 0x76, 0x84, 0x3d, - 0x6b, 0xfd, 0x41, 0x06, 0x0a, 0x6c, 0xb8, 0x55, 0x2f, 0x3c, 0xdf, 0xf8, 0xf8, 0x08, 0xb2, 0xc3, - 0x47, 0x80, 0x6e, 0x45, 0xdc, 0x1e, 0x8b, 0x36, 0x3f, 0xda, 0x55, 0xa2, 0xc3, 0x45, 0x21, 0x19, - 0x10, 0x93, 0x25, 0x76, 0x34, 0xa2, 0x03, 0xa2, 0xb2, 0x24, 0x24, 0xe8, 0x8f, 0xb2, 0x30, 0xaf, - 0x74, 0x31, 0xe8, 0x79, 0xdd, 0x00, 0x93, 0x33, 0x1e, 0x11, 0x16, 0xa5, 0x9f, 0xf4, 0x8c, 0x47, - 0xb6, 0xcc, 0x66, 0x64, 0x89, 0xd3, 0x0e, 0xbf, 0x4b, 0x0e, 0x17, 0x9d, 0xc4, 0x71, 0x90, 0x2a, - 0x6e, 0x56, 0x55, 0x14, 0x9f, 0xbb, 0xd3, 0x77, 0x21, 0x47, 0xff, 0x24, 0x8c, 0x18, 0x37, 0x33, - 0x42, 0x56, 0x42, 0x2e, 0xc0, 0x03, 0xcf, 0xed, 0x3e, 0xc2, 0xe1, 0xb1, 0x27, 0x0e, 0xcf, 0x3b, - 0x44, 0x89, 0xfd, 0x25, 0xcf, 0xed, 0x36, 0x4f, 0x29, 0xf8, 0xa2, 0x87, 0xce, 0x88, 0xa0, 0xad, - 0x10, 0xb7, 0xee, 0x41, 0x81, 0xe8, 0x9b, 0xf3, 0xcf, 0xa8, 0xb5, 0x08, 0x68, 0x0b, 0x87, 0x55, - 0x4f, 0xdb, 0x38, 0xac, 0x59, 0x98, 0xde, 0x77, 0xbb, 0x47, 0xe2, 0xf3, 0xdf, 0x67, 0x61, 0x86, - 0x7d, 0xf3, 0x19, 0x88, 0xed, 0xa4, 0x99, 0xf3, 0xec, 0xa4, 0x9f, 0xc2, 0x2c, 0x77, 0xe7, 0x60, - 0x9f, 0xba, 0x90, 0xd9, 0x7c, 0xd0, 0x13, 0x25, 0xf3, 0xea, 0x34, 0x9f, 0xb3, 0x12, 0x5b, 0xaf, - 0x88, 0x76, 0x61, 0x8e, 0x01, 0xee, 0x63, 0x27, 0xec, 0x47, 0xa7, 0xaa, 0xcb, 0xdc, 0xce, 0x14, - 0x60, 0xa6, 0x8c, 0x38, 0xad, 0x67, 0x1c, 0x68, 0xc7, 0x70, 0xd1, 0x37, 0x70, 0x79, 0xdf, 0xf7, - 0x7e, 0x7c, 0xa9, 0xd8, 0x0e, 0x4c, 0x1f, 0x2f, 0x91, 0x43, 0x58, 0x8f, 0x14, 0x35, 0x55, 0x0b, - 0x22, 0x5e, 0x9b, 0xc8, 0xd4, 0x4e, 0x50, 0xf5, 0x7c, 0xb7, 0x7b, 0x44, 0x67, 0x33, 0xc7, 0x64, - 0xca, 0x0d, 0x9a, 0x87, 0x14, 0x68, 0xcb, 0x62, 0xeb, 0x0f, 0xc7, 0x20, 0x27, 0x1b, 0xbe, 0xa3, - 0x9a, 0xa5, 0x7c, 0x33, 0xa6, 0xcb, 0x33, 0x3a, 0xfc, 0xd8, 0x4a, 0x0d, 0x74, 0x95, 0x39, 0xb3, - 0x98, 0x19, 0x30, 0x45, 0x64, 0xcc, 0xe9, 0xf5, 0xa8, 0xcb, 0x8a, 0x28, 0xd3, 0x8d, 0x2a, 0xe5, - 0x42, 0x8e, 0x29, 0xd3, 0xf6, 0xa1, 0x9d, 0xdd, 0xa8, 0x92, 0xb9, 0x7e, 0xbc, 0xb3, 0x51, 0xa3, - 0x03, 0xca, 0xb1, 0xb9, 0xf6, 0xdc, 0x76, 0xcb, 0xa6, 0x50, 0x52, 0x5a, 0xaf, 0x3c, 0xda, 0xe5, - 0x9d, 0xa6, 0xa5, 0x81, 0x73, 0xda, 0xb1, 0x29, 0x94, 0xd8, 0x81, 0x6c, 0x8f, 0xae, 0x79, 0xdd, - 0xd0, 0xf7, 0x3a, 0x01, 0x75, 0x15, 0xe4, 0xb4, 0xed, 0xbc, 0xc5, 0x8b, 0xec, 0x58, 0x55, 0xf4, - 0x14, 0x96, 0x2b, 0xed, 0xe7, 0x4e, 0xb7, 0x85, 0xdb, 0xac, 0xe4, 0xa9, 0xe7, 0x9f, 0x3c, 0xeb, - 0x78, 0x2f, 0x02, 0x7a, 0xca, 0xcb, 0xf1, 0xf3, 0x22, 0xaf, 0xd2, 0xe4, 0xe4, 0x5e, 0x88, 0x4a, - 0xb6, 0x09, 0x9b, 0xa8, 0x88, 0x5a, 0xc7, 0xeb, 0xb7, 0xe9, 0xf1, 0x2e, 0xc7, 0x54, 0x44, 0x8b, - 0x00, 0x6c, 0x06, 0x27, 0x5c, 0xda, 0xae, 0x3f, 0xa2, 0xa7, 0x33, 0xce, 0xa5, 0xe3, 0xe0, 0xd4, - 0x26, 0x30, 0xf4, 0x0e, 0x4c, 0x09, 0x93, 0x96, 0x39, 0x05, 0xa8, 0xc7, 0x48, 0x98, 0xb2, 0xa2, - 0xcc, 0xfa, 0x00, 0xe6, 0xd9, 0xa2, 0x39, 0xb7, 0xa5, 0x66, 0xed, 0x03, 0xd4, 0xf1, 0xa9, 0xd3, - 0x3b, 0xf6, 0xc8, 0xc4, 0x56, 0xd5, 0x2f, 0x6e, 0xba, 0x20, 0xe9, 0xe8, 0xe7, 0x05, 0x8d, 0x75, - 0x61, 0xcb, 0x8a, 0x9a, 0xb6, 0x82, 0x65, 0xfd, 0x97, 0x2c, 0x20, 0xea, 0xf0, 0xae, 0x87, 0x3e, - 0x76, 0x4e, 0x45, 0x37, 0x3e, 0x83, 0x19, 0xa6, 0xff, 0x18, 0x98, 0x76, 0x87, 0xd8, 0x45, 0x4c, - 0xf0, 0xd5, 0xa2, 0xed, 0x4b, 0xb6, 0x56, 0x95, 0xa0, 0xda, 0x38, 0xe8, 0x9f, 0x0a, 0xd4, 0xac, - 0x86, 0xaa, 0x16, 0x11, 0x54, 0xf5, 0x1b, 0x7d, 0x03, 0x73, 0x35, 0xef, 0xb4, 0x47, 0x78, 0xc2, - 0x91, 0xc7, 0xb8, 0xf5, 0xc1, 0xdb, 0xd5, 0x0a, 0xb7, 0x2f, 0xd9, 0xb1, 0xea, 0x68, 0x0f, 0x16, - 0xee, 0x77, 0xfa, 0xc1, 0x71, 0xa5, 0xdb, 0xae, 0x75, 0xbc, 0x40, 0x50, 0x19, 0xe7, 0xce, 0x10, - 0xbe, 0x6c, 0x93, 0x35, 0xb6, 0x2f, 0xd9, 0x69, 0x88, 0xe8, 0x1d, 0x7e, 0x7b, 0xcf, 0xad, 0xa0, - 0xd9, 0x3b, 0xfc, 0x72, 0xff, 0x71, 0x17, 0x3f, 0x7e, 0xb6, 0x7d, 0xc9, 0x66, 0xa5, 0xd5, 0x3c, - 0x4c, 0x09, 0x95, 0x75, 0x17, 0xe6, 0x15, 0x76, 0x12, 0xbb, 0xad, 0x1f, 0xa0, 0x12, 0xe4, 0x9e, - 0xf4, 0x3a, 0x9e, 0xd3, 0x16, 0x66, 0x80, 0x2d, 0xbf, 0xad, 0xf7, 0x75, 0x4e, 0xa3, 0x15, 0xf5, - 0x2c, 0xc2, 0x2a, 0x47, 0x00, 0x6b, 0x5b, 0x67, 0xee, 0xf0, 0xda, 0x5a, 0xbb, 0xd9, 0x58, 0xbb, - 0x85, 0x38, 0xaf, 0xad, 0xa5, 0x54, 0xe6, 0x59, 0x0f, 0xa9, 0x89, 0x53, 0xe9, 0xf5, 0x3a, 0x6e, - 0x8b, 0xee, 0x0c, 0x4c, 0xaf, 0x49, 0xeb, 0xe0, 0xb7, 0xd4, 0x3b, 0x66, 0x65, 0x5b, 0x94, 0x37, - 0xca, 0xca, 0x2d, 0xb2, 0xf5, 0x33, 0xb8, 0x6e, 0x20, 0xc6, 0x35, 0xfc, 0x67, 0x30, 0xc5, 0x41, - 0x31, 0x81, 0x56, 0xbd, 0xf2, 0x74, 0x3d, 0x05, 0x1c, 0x53, 0xd4, 0xb7, 0xbe, 0x83, 0xd5, 0x27, - 0xbd, 0x00, 0xfb, 0x49, 0xf2, 0xa2, 0xab, 0x1f, 0xcb, 0x3b, 0xec, 0x8c, 0xd1, 0xe3, 0x0f, 0x67, - 0x83, 0xf2, 0x24, 0xa3, 0x2d, 0xae, 0xae, 0xad, 0xdf, 0xcb, 0xc0, 0x2a, 0x5b, 0xaa, 0x46, 0xd2, - 0x17, 0xe1, 0x82, 0xe2, 0x51, 0xce, 0x9a, 0x3d, 0xca, 0x43, 0x5d, 0xec, 0xd6, 0xb7, 0x60, 0xf1, - 0x1e, 0x75, 0x3a, 0x6f, 0x68, 0x6e, 0xfe, 0x46, 0x06, 0x16, 0xd9, 0xe4, 0xbc, 0x06, 0x15, 0xf4, - 0x15, 0xcc, 0xd5, 0x4f, 0xdc, 0x5e, 0xc3, 0xe9, 0xb8, 0x6d, 0xe6, 0x5c, 0x65, 0x1b, 0xc9, 0x12, - 0xdd, 0x23, 0x4f, 0xdc, 0x5e, 0xf3, 0x79, 0x54, 0x94, 0xb1, 0x63, 0x95, 0xad, 0xc7, 0xb0, 0x14, - 0xeb, 0x03, 0x17, 0x8c, 0x8f, 0xe3, 0x82, 0x91, 0x08, 0x40, 0x48, 0x97, 0x8a, 0x47, 0x70, 0x45, - 0x4a, 0x85, 0x3e, 0x65, 0xeb, 0x31, 0x69, 0x48, 0x10, 0x4c, 0x13, 0x85, 0x16, 0x5c, 0x91, 0x92, - 0xf0, 0x1a, 0x12, 0x20, 0x26, 0x37, 0x9b, 0x3a, 0xb9, 0x3b, 0x50, 0x52, 0x27, 0xf7, 0x75, 0x26, - 0xf5, 0x3f, 0x67, 0x60, 0x79, 0x0b, 0x77, 0xb1, 0xef, 0xd0, 0x2e, 0x6b, 0x67, 0x0a, 0xd5, 0xb1, - 0x9c, 0x19, 0xea, 0x58, 0x96, 0x06, 0x73, 0x36, 0xdd, 0x60, 0x26, 0xbb, 0xe1, 0x13, 0x7b, 0x87, - 0xcb, 0x2a, 0xdd, 0x0d, 0xfb, 0xbe, 0x6b, 0x13, 0x18, 0xda, 0x89, 0x9c, 0xd2, 0xe3, 0x23, 0x9d, - 0xd2, 0x0b, 0xdc, 0x49, 0x37, 0xc5, 0x9d, 0xd2, 0x9a, 0x2b, 0xda, 0xfa, 0x02, 0x8a, 0xc9, 0xb1, - 0x70, 0xf9, 0x18, 0x75, 0x48, 0xb1, 0x36, 0x22, 0xe9, 0xe6, 0xf7, 0xd7, 0xd2, 0x19, 0x1f, 0x53, - 0xa1, 0x43, 0x9c, 0x3f, 0x56, 0x3d, 0x92, 0x4f, 0x4e, 0x85, 0xb7, 0xff, 0x39, 0x91, 0x4f, 0x16, - 0xa3, 0x90, 0x31, 0xc7, 0x28, 0x70, 0x19, 0x65, 0xa8, 0x02, 0xc1, 0x7a, 0x0a, 0x57, 0x34, 0xa2, - 0x91, 0xd4, 0x7f, 0x05, 0x39, 0x01, 0x8b, 0xf9, 0x26, 0x34, 0xb2, 0x74, 0xde, 0x02, 0x81, 0x2c, - 0x51, 0xac, 0x3f, 0xcb, 0xc0, 0x32, 0xdb, 0x5d, 0x92, 0xe3, 0x3e, 0xff, 0xec, 0xff, 0x46, 0x1c, - 0x5e, 0xf7, 0x52, 0x1c, 0x5e, 0x14, 0x45, 0x75, 0x78, 0xa9, 0x6e, 0xae, 0x07, 0xe3, 0xb9, 0x6c, - 0x61, 0xcc, 0x6a, 0x40, 0x31, 0x39, 0xc2, 0x37, 0x30, 0x27, 0x5b, 0xb0, 0xac, 0x2c, 0xf4, 0xd7, - 0x96, 0x98, 0xa8, 0xc5, 0x37, 0x28, 0x31, 0x51, 0xc5, 0x37, 0x26, 0x31, 0x3b, 0xb0, 0xc0, 0x08, - 0xeb, 0xab, 0x6b, 0x4d, 0x5d, 0x5d, 0xa9, 0xf1, 0x32, 0xc9, 0x05, 0xf7, 0x88, 0x2e, 0x38, 0x51, - 0x25, 0xea, 0xe1, 0x47, 0x30, 0xc9, 0x43, 0x02, 0x59, 0xff, 0x52, 0x88, 0x51, 0xcd, 0xcb, 0xe2, - 0x00, 0x6d, 0x5e, 0xd9, 0x2a, 0xd2, 0x21, 0x93, 0x73, 0x0a, 0x77, 0x78, 0xcb, 0x53, 0xe3, 0xb7, - 0x44, 0xc5, 0xc5, 0x4a, 0x5e, 0x73, 0xd7, 0x78, 0x0c, 0x45, 0xb6, 0x6b, 0x28, 0x54, 0x5f, 0x6b, - 0xdf, 0xf8, 0x14, 0x8a, 0x4c, 0x9c, 0x52, 0x08, 0x0e, 0xdf, 0x0c, 0x56, 0x61, 0x45, 0x6e, 0x06, - 0x69, 0xa3, 0xff, 0x3b, 0x19, 0xb8, 0xba, 0x85, 0x43, 0x3d, 0x6a, 0xea, 0xcf, 0x65, 0xef, 0xfe, - 0x1e, 0x4a, 0x69, 0x1d, 0xe1, 0x53, 0xf1, 0x75, 0x7c, 0x2a, 0x8c, 0x21, 0x62, 0xe9, 0x53, 0xf2, - 0x33, 0xb8, 0xc6, 0xa6, 0x44, 0xaf, 0x2f, 0x06, 0xfa, 0x45, 0x6c, 0x56, 0x8c, 0xd4, 0xd3, 0x66, - 0xe7, 0x1f, 0x64, 0xe0, 0x1a, 0x63, 0x72, 0x3a, 0xf1, 0xdf, 0xb4, 0x75, 0xb7, 0x07, 0x65, 0x39, - 0xe7, 0x6f, 0x60, 0x62, 0xad, 0x16, 0x20, 0x41, 0xa6, 0x56, 0xb7, 0x05, 0x89, 0xab, 0x30, 0x56, - 0xab, 0xdb, 0xfc, 0x02, 0x9a, 0x6e, 0xda, 0xad, 0xc0, 0xb7, 0x09, 0x2c, 0xae, 0xc1, 0xb3, 0xe7, - 0xd0, 0xe0, 0xd6, 0xcf, 0x61, 0x41, 0x6b, 0x84, 0xcf, 0xfb, 0x0a, 0x8c, 0xd7, 0xb0, 0x1f, 0xf2, - 0x66, 0xe8, 0x48, 0x5b, 0xd8, 0x0f, 0x6d, 0x0a, 0x45, 0xb7, 0x60, 0xaa, 0x56, 0xa1, 0xde, 0x46, - 0x6a, 0x5b, 0xcc, 0x30, 0xc5, 0xd4, 0x72, 0x9a, 0x34, 0x92, 0xdc, 0x16, 0x85, 0xd6, 0x7f, 0xc8, - 0x28, 0xd4, 0x09, 0xfa, 0xe8, 0x31, 0x7c, 0x40, 0x8e, 0xc7, 0x84, 0x67, 0xca, 0x10, 0xe6, 0xc9, - 0xb6, 0xc5, 0x3d, 0x35, 0x6c, 0xe7, 0xb3, 0x95, 0x4a, 0xe7, 0xf4, 0x94, 0x8a, 0x9b, 0x39, 0x86, - 0x24, 0xbc, 0x88, 0xf2, 0x66, 0x8e, 0x93, 0x0e, 0x6c, 0xb5, 0x92, 0xf5, 0x3d, 0x2c, 0xea, 0xfd, - 0x7f, 0xa3, 0xec, 0x79, 0x9b, 0x5e, 0xf9, 0x28, 0x97, 0xa5, 0x08, 0xa9, 0x8e, 0x04, 0x2e, 0x56, - 0x9f, 0x40, 0x81, 0xd7, 0x8a, 0x96, 0xe5, 0x4d, 0x61, 0xda, 0xb1, 0x45, 0xa9, 0x07, 0x17, 0x0b, - 0x7f, 0xe8, 0x4f, 0x84, 0xab, 0x62, 0x54, 0x0b, 0xff, 0x38, 0x03, 0xc5, 0x47, 0xf7, 0x2b, 0x95, - 0x7e, 0x78, 0x8c, 0xbb, 0x21, 0x39, 0x94, 0xe0, 0xda, 0xb1, 0xd3, 0xe9, 0xe0, 0xee, 0x11, 0x46, - 0xb7, 0x61, 0xfc, 0xe0, 0xf1, 0xc1, 0x3e, 0xf7, 0x08, 0x2c, 0xf2, 0xe3, 0x38, 0x01, 0xc9, 0x3a, - 0x36, 0xad, 0x81, 0x1e, 0xc2, 0xfc, 0x53, 0x1e, 0xce, 0x2f, 0x8b, 0xb8, 0x2f, 0xe0, 0xfa, 0x1d, - 0x19, 0xe8, 0x5f, 0xf3, 0x71, 0x9b, 0xb4, 0xe2, 0x74, 0x2a, 0x01, 0x51, 0x0c, 0x64, 0x7e, 0x92, - 0x78, 0x0f, 0xc6, 0x73, 0x99, 0x42, 0xd6, 0xfa, 0xfd, 0x0c, 0x2c, 0xc7, 0x7a, 0xa6, 0x38, 0x76, - 0xd5, 0x8e, 0x2d, 0x28, 0x1d, 0x13, 0x55, 0xb6, 0x2f, 0xf1, 0x9e, 0xd5, 0x68, 0xec, 0x28, 0x6d, - 0x81, 0x77, 0xe8, 0x9d, 0xe1, 0x1d, 0x8a, 0x08, 0x48, 0x44, 0x1e, 0xf9, 0x45, 0xe1, 0xd6, 0x65, - 0x98, 0xd5, 0x38, 0x60, 0x59, 0x30, 0xa3, 0xb6, 0x4c, 0xd8, 0x5c, 0xf3, 0xda, 0x92, 0xcd, 0xe4, - 0x6f, 0xeb, 0x1f, 0x66, 0x60, 0xf1, 0xd1, 0xfd, 0x8a, 0x8d, 0x8f, 0x5c, 0xb2, 0xfc, 0x22, 0x16, - 0xaf, 0x69, 0x23, 0x59, 0xd1, 0x46, 0x12, 0xab, 0x2b, 0x87, 0xf4, 0x79, 0x62, 0x48, 0x2b, 0x69, - 0x43, 0xa2, 0x56, 0x96, 0xeb, 0x75, 0xb5, 0x91, 0x28, 0x9e, 0x8f, 0x7f, 0x92, 0x81, 0x05, 0xa5, - 0x4f, 0xb2, 0xff, 0x1f, 0x68, 0x5d, 0xba, 0x96, 0xd2, 0xa5, 0x04, 0x93, 0xab, 0x89, 0x1e, 0xbd, - 0x3d, 0xac, 0x47, 0x23, 0x79, 0xfc, 0xc7, 0x19, 0x58, 0x4a, 0xe5, 0x01, 0xba, 0x42, 0x76, 0x8d, - 0x96, 0x8f, 0x43, 0xce, 0x5e, 0xfe, 0x45, 0xe0, 0x3b, 0x41, 0xd0, 0xe7, 0x6f, 0x2d, 0xf2, 0x36, - 0xff, 0x42, 0x6f, 0xc3, 0xec, 0x3e, 0xf6, 0x5d, 0xaf, 0x5d, 0xc7, 0x2d, 0xaf, 0xdb, 0x66, 0x1e, - 0xe1, 0x59, 0x5b, 0x07, 0xa2, 0x15, 0xc8, 0x57, 0x3a, 0x47, 0x9e, 0xef, 0x86, 0xc7, 0xcc, 0xf9, - 0x94, 0xb7, 0x23, 0x00, 0xa1, 0xbd, 0xe1, 0x1e, 0xb9, 0x21, 0xbb, 0x5b, 0x9b, 0xb5, 0xf9, 0x17, - 0x2a, 0xc2, 0x54, 0xa5, 0xd5, 0xf2, 0xfa, 0xdd, 0x90, 0x7a, 0x40, 0xf3, 0xb6, 0xf8, 0x24, 0x18, - 0xdf, 0xda, 0x54, 0x08, 0x68, 0x34, 0x94, 0xcd, 0xbf, 0xac, 0xf7, 0x60, 0x31, 0x8d, 0x8f, 0xa9, - 0x22, 0xf3, 0xd7, 0xb2, 0xb0, 0x50, 0x69, 0xb7, 0x1f, 0xdd, 0xaf, 0x6c, 0x60, 0xd5, 0xf8, 0xf8, - 0x10, 0xc6, 0x77, 0xba, 0x6e, 0xc8, 0x77, 0xcd, 0x55, 0x3e, 0x3d, 0x29, 0x35, 0x49, 0x2d, 0x32, - 0x43, 0xe4, 0x7f, 0x64, 0xc3, 0xc2, 0xe6, 0x8f, 0x6e, 0x10, 0xba, 0xdd, 0x23, 0x3a, 0xe7, 0xac, - 0x61, 0x3e, 0xc7, 0x82, 0x88, 0x61, 0xb9, 0x6d, 0x5f, 0xb2, 0xd3, 0x90, 0xd1, 0x01, 0x5c, 0xd9, - 0xc3, 0x2f, 0x52, 0x44, 0x48, 0x06, 0x33, 0x49, 0xb2, 0x29, 0x92, 0x63, 0xc0, 0x55, 0x25, 0xf4, - 0x6f, 0x67, 0x69, 0x84, 0x9c, 0x32, 0x30, 0xde, 0xf2, 0x13, 0x58, 0x54, 0x3a, 0x14, 0x69, 0x9c, - 0x0c, 0x8f, 0x9d, 0x4e, 0x1d, 0x8e, 0xba, 0x90, 0x52, 0xd1, 0xd1, 0x53, 0x58, 0xd6, 0x3b, 0x15, - 0x51, 0xd6, 0x17, 0x43, 0x5a, 0x95, 0xed, 0x4b, 0xb6, 0x09, 0x1b, 0xad, 0xc1, 0x58, 0xa5, 0x75, - 0xc2, 0xd9, 0x92, 0x3e, 0x65, 0x6c, 0x64, 0x95, 0xd6, 0x09, 0x8d, 0x36, 0x6d, 0x9d, 0x68, 0xeb, - 0xa1, 0x03, 0xcb, 0x86, 0x09, 0x46, 0xab, 0x00, 0x0c, 0xa8, 0xa8, 0x76, 0x05, 0x42, 0x36, 0x55, - 0xf6, 0x45, 0xef, 0x1b, 0xc7, 0xe8, 0xf5, 0xb5, 0x08, 0x0b, 0x8a, 0x0a, 0x6c, 0xa5, 0x92, 0x55, - 0x8b, 0xb7, 0x26, 0xfb, 0x86, 0x6e, 0xc3, 0x24, 0x03, 0x72, 0x56, 0x8b, 0xc7, 0x29, 0x51, 0x65, - 0x5e, 0x6e, 0xfd, 0x41, 0x46, 0x38, 0x5e, 0x12, 0x12, 0xfc, 0x89, 0x26, 0xc1, 0x6f, 0xc9, 0xce, - 0xa4, 0x55, 0xd6, 0x84, 0xb8, 0x0a, 0xd3, 0xaf, 0x22, 0xbc, 0x2a, 0x92, 0x2a, 0x5e, 0xff, 0x3c, - 0x23, 0x8e, 0x8c, 0x49, 0x09, 0xdb, 0x84, 0x99, 0x57, 0x93, 0x2c, 0x0d, 0x0d, 0x7d, 0xc4, 0x26, - 0x3e, 0x3b, 0x7c, 0xa4, 0x43, 0xe7, 0xfe, 0x4b, 0xe1, 0x5b, 0x7a, 0x95, 0xe9, 0xb7, 0x56, 0x52, - 0xb0, 0x65, 0x73, 0x56, 0x3f, 0x51, 0x5a, 0x7f, 0xd9, 0x6d, 0x89, 0x79, 0xba, 0x15, 0xbf, 0x0b, - 0x37, 0x5e, 0x74, 0xaa, 0x7d, 0xc8, 0x46, 0xee, 0x86, 0x36, 0x8e, 0xa2, 0x9e, 0xd4, 0x4e, 0x9d, - 0x65, 0x74, 0x09, 0x7b, 0x95, 0x46, 0x6b, 0x30, 0xbb, 0x87, 0x5f, 0x24, 0xda, 0xa5, 0xd7, 0x47, - 0x5d, 0xfc, 0xa2, 0xa9, 0xb4, 0xad, 0x04, 0x1c, 0xe8, 0x38, 0xe8, 0x10, 0xe6, 0xc4, 0x92, 0x3d, - 0xaf, 0xe6, 0x62, 0x81, 0x91, 0xa4, 0x85, 0xd3, 0x67, 0x4e, 0xd3, 0xe7, 0x50, 0x35, 0xa2, 0x51, - 0xa7, 0x68, 0xed, 0x43, 0x31, 0x39, 0x56, 0x2e, 0x65, 0x1f, 0x8e, 0x5a, 0x4e, 0xec, 0xf0, 0xd3, - 0xd6, 0x97, 0xd6, 0x36, 0x3d, 0xa7, 0xcb, 0x3a, 0xf2, 0x84, 0x71, 0x2f, 0xce, 0x3a, 0x1a, 0xec, - 0x25, 0x58, 0xa7, 0x46, 0x5e, 0x47, 0xd1, 0x10, 0x4b, 0x31, 0x4a, 0xbc, 0x63, 0xef, 0xc1, 0x14, - 0x07, 0xc9, 0x88, 0xf6, 0xf8, 0x42, 0x17, 0x15, 0xac, 0x7f, 0x9a, 0x81, 0xab, 0x4f, 0x02, 0xec, - 0xd7, 0xdd, 0xee, 0x51, 0x07, 0x3f, 0x09, 0xf4, 0x58, 0x84, 0xdf, 0xd6, 0x16, 0xfb, 0xb2, 0x21, - 0xc6, 0xf1, 0xff, 0xd7, 0x12, 0xff, 0x67, 0x19, 0x28, 0xa5, 0xf5, 0xed, 0xcd, 0xae, 0xf2, 0x3b, - 0xfc, 0x48, 0xc0, 0x7a, 0x5b, 0xe4, 0xe8, 0xb2, 0x4d, 0x31, 0x58, 0x32, 0x48, 0xf2, 0xbf, 0xb6, - 0xbc, 0xff, 0x4f, 0x06, 0x16, 0x77, 0x02, 0xda, 0xfd, 0x5f, 0xf4, 0x5d, 0x1f, 0xb7, 0x05, 0xe3, - 0xee, 0xa4, 0x45, 0xc2, 0xd2, 0x79, 0xdd, 0xbe, 0x94, 0x16, 0xe9, 0xfa, 0xa1, 0x12, 0xeb, 0x97, - 0x1d, 0x16, 0xe2, 0xaa, 0x3d, 0xab, 0xb8, 0x05, 0xe3, 0x7b, 0xc4, 0xf2, 0x18, 0xe3, 0xf2, 0xc7, - 0x30, 0x08, 0x88, 0x86, 0xe5, 0x91, 0x2e, 0x93, 0x0f, 0x74, 0x3f, 0x11, 0xfc, 0x37, 0x3e, 0x3a, - 0x84, 0x33, 0xf9, 0x1e, 0xa4, 0x9a, 0x83, 0xc9, 0x03, 0xc7, 0x3f, 0xc2, 0xa1, 0xf5, 0x33, 0x28, - 0xf1, 0x3b, 0x37, 0xe6, 0xca, 0xa2, 0x37, 0x73, 0x41, 0xe4, 0x62, 0x19, 0x76, 0x4f, 0xb6, 0x0a, - 0x50, 0x0f, 0x1d, 0x3f, 0xdc, 0xe9, 0xb6, 0xf1, 0x8f, 0x74, 0xb4, 0x13, 0xb6, 0x02, 0xb1, 0x3e, - 0x82, 0xbc, 0x1c, 0x02, 0x3d, 0xf6, 0x28, 0xc6, 0x15, 0x1d, 0xce, 0xa2, 0x16, 0x8e, 0x28, 0x62, - 0x10, 0xd7, 0x61, 0x29, 0x36, 0x15, 0x5c, 0x4e, 0x4a, 0x64, 0xc2, 0x18, 0x8c, 0xdd, 0xc7, 0xdb, - 0xf2, 0xdb, 0xaa, 0xc1, 0x7c, 0x62, 0xa6, 0x11, 0xa2, 0x21, 0xa6, 0xec, 0x8c, 0x48, 0x94, 0x7a, - 0xbd, 0xbe, 0x4d, 0x60, 0x07, 0xbb, 0x75, 0x16, 0x6e, 0x43, 0x60, 0x07, 0xbb, 0xf5, 0xea, 0x24, - 0x93, 0x1c, 0xeb, 0x5f, 0x67, 0xe9, 0x49, 0x2f, 0xc1, 0x83, 0x98, 0xc7, 0x40, 0xf5, 0x5a, 0x54, - 0x21, 0x4f, 0x47, 0xbc, 0x21, 0xc2, 0xd2, 0x86, 0x3b, 0xea, 0x73, 0xbf, 0x1c, 0x94, 0x2f, 0x51, - 0xef, 0x7c, 0x84, 0x86, 0xbe, 0x86, 0xa9, 0xcd, 0x6e, 0x9b, 0x52, 0x18, 0xbb, 0x00, 0x05, 0x81, - 0x44, 0xe6, 0x81, 0x76, 0x99, 0x98, 0x0d, 0xfc, 0x58, 0x6d, 0x2b, 0x10, 0xca, 0x66, 0xf7, 0xd4, - 0x65, 0xd7, 0xb1, 0x13, 0x36, 0xfb, 0x20, 0xdc, 0xa4, 0x5d, 0x10, 0xaf, 0x0c, 0xf2, 0xb6, 0xfc, - 0x46, 0x16, 0x4c, 0x3c, 0xf6, 0xdb, 0x3c, 0xe6, 0x7b, 0x6e, 0x6d, 0x46, 0x3c, 0xbb, 0x26, 0x30, - 0x9b, 0x15, 0x59, 0xff, 0x8b, 0x5e, 0x91, 0x84, 0xa9, 0x72, 0xa3, 0x71, 0x25, 0xf3, 0xda, 0x5c, - 0xc9, 0xbe, 0x0a, 0x57, 0xe4, 0xa8, 0xc7, 0x4c, 0xa3, 0x1e, 0x37, 0x8d, 0x7a, 0xc2, 0x3c, 0xea, - 0x2d, 0x98, 0x64, 0x43, 0x45, 0x37, 0x61, 0x62, 0x27, 0xc4, 0xa7, 0x91, 0x07, 0x40, 0xbd, 0xe4, - 0xb6, 0x59, 0x19, 0x39, 0x9c, 0xec, 0x3a, 0x41, 0x28, 0x02, 0xbc, 0xf2, 0xb6, 0xf8, 0xb4, 0xfe, - 0x24, 0x03, 0x85, 0x5d, 0x37, 0x08, 0xc9, 0x42, 0x38, 0xa7, 0xac, 0xc9, 0x11, 0x65, 0x4d, 0x23, - 0x1a, 0x8b, 0x8d, 0xe8, 0x0b, 0x98, 0xa4, 0x71, 0x87, 0x01, 0x7f, 0x51, 0x74, 0x93, 0x0f, 0x29, - 0xde, 0x30, 0x8b, 0x4e, 0x0c, 0xe8, 0xcb, 0x1f, 0x9b, 0xa3, 0x94, 0x3e, 0x83, 0x69, 0x05, 0x7c, - 0xa1, 0x07, 0x41, 0xdf, 0xc1, 0xbc, 0xd2, 0x84, 0x74, 0x17, 0x8c, 0x70, 0x2a, 0x4b, 0xa7, 0x25, - 0x61, 0xdb, 0x1e, 0xfe, 0x51, 0x65, 0x1b, 0xff, 0xb4, 0x7e, 0xa0, 0x61, 0xb3, 0xbb, 0x5e, 0xeb, - 0x44, 0x71, 0xe9, 0x4d, 0x31, 0x65, 0x16, 0xf7, 0x8c, 0x93, 0x5a, 0xac, 0xc4, 0x16, 0x35, 0xd0, - 0x0d, 0x98, 0xde, 0xe9, 0xde, 0xf7, 0xfc, 0x16, 0x7e, 0xdc, 0xed, 0x30, 0xea, 0x39, 0x5b, 0x05, - 0x71, 0x6f, 0x0f, 0x6f, 0x21, 0xf2, 0xf6, 0x50, 0x40, 0xcc, 0xdb, 0xc3, 0x1e, 0x34, 0xda, 0xac, - 0x8c, 0x3b, 0x93, 0xc8, 0xdf, 0xc3, 0x5c, 0x3d, 0xd2, 0x27, 0x34, 0xaa, 0xe2, 0x21, 0x5c, 0xb5, - 0x71, 0xaf, 0xe3, 0x10, 0x5b, 0xf1, 0xd4, 0x63, 0xf5, 0xe5, 0x98, 0x6f, 0xa4, 0x44, 0x74, 0xe9, - 0x17, 0x43, 0xb2, 0xcb, 0xd9, 0x21, 0x5d, 0x3e, 0x85, 0xb7, 0xb6, 0x70, 0x98, 0xfa, 0x2a, 0x31, - 0x1a, 0xfc, 0x36, 0xe4, 0x78, 0xe4, 0xbc, 0x18, 0xff, 0xa8, 0x07, 0x91, 0xfc, 0x96, 0x84, 0xd3, - 0x91, 0x7f, 0x59, 0xdf, 0x40, 0xd9, 0xd4, 0xdc, 0xf9, 0x02, 0x79, 0x5c, 0xb8, 0x61, 0x26, 0x20, - 0xad, 0x89, 0x29, 0xde, 0xa0, 0x3c, 0x9c, 0x0f, 0xef, 0xad, 0xf4, 0x9b, 0x53, 0x7b, 0x8a, 0xff, - 0x61, 0x55, 0x45, 0xa4, 0xc0, 0x6b, 0x74, 0xb7, 0x49, 0x3d, 0xfb, 0x3a, 0x81, 0x88, 0xaf, 0x15, - 0xc8, 0x09, 0x58, 0xcc, 0xb5, 0x9f, 0x78, 0xf0, 0x49, 0x19, 0xda, 0x16, 0x04, 0x24, 0x9a, 0xf5, - 0x83, 0xf0, 0xbf, 0xeb, 0x18, 0xe7, 0x8b, 0x0e, 0x3d, 0x8f, 0xc3, 0xdd, 0xf2, 0xe0, 0xaa, 0x4e, - 0x5b, 0xf5, 0x22, 0x17, 0x14, 0x2f, 0x32, 0x73, 0x1e, 0x13, 0xb9, 0xb4, 0x77, 0x37, 0xbb, 0xed, - 0x9e, 0xe7, 0x76, 0x43, 0xbe, 0x78, 0x55, 0x10, 0x5a, 0x55, 0x7d, 0xc5, 0x33, 0xc9, 0x70, 0xda, - 0x7b, 0x50, 0x4a, 0x6b, 0x50, 0x71, 0xd1, 0x48, 0xb7, 0x2f, 0xb3, 0xe3, 0xac, 0x63, 0x58, 0xd4, - 0x52, 0x68, 0x44, 0x39, 0x01, 0xa2, 0xd4, 0x21, 0xf9, 0xea, 0x97, 0xbf, 0x1e, 0x94, 0x3f, 0xbd, - 0x48, 0xcc, 0xa6, 0xa0, 0x79, 0x20, 0x23, 0x82, 0xad, 0x65, 0x18, 0xab, 0xd9, 0xbb, 0x74, 0xd8, - 0xf6, 0xae, 0x1c, 0xb6, 0xbd, 0x6b, 0xfd, 0x3a, 0x03, 0xe5, 0xda, 0xb1, 0xd3, 0x3d, 0xa2, 0xb6, - 0x87, 0x62, 0xae, 0x2a, 0xd7, 0x9f, 0xe7, 0x3d, 0x52, 0xad, 0xc1, 0xf4, 0x1e, 0x7e, 0x21, 0xa2, - 0x99, 0x79, 0x5c, 0x30, 0xf5, 0x92, 0x93, 0xe3, 0x4e, 0x8f, 0xc3, 0x6d, 0xb5, 0x12, 0xfa, 0xcb, - 0xaf, 0xee, 0x03, 0x62, 0x0f, 0xe9, 0xa3, 0x93, 0x14, 0x2b, 0x4d, 0x3b, 0x52, 0x19, 0x9a, 0xb0, - 0xfe, 0x6d, 0x06, 0x6e, 0x98, 0x07, 0xcf, 0x27, 0x6e, 0x43, 0x4b, 0x47, 0x30, 0xe4, 0xe2, 0x96, - 0x1e, 0x59, 0x95, 0x74, 0x04, 0xf1, 0x14, 0x04, 0x36, 0x6e, 0x79, 0xcf, 0xb1, 0xff, 0x32, 0xe6, - 0x0e, 0x17, 0xe0, 0x1a, 0xd9, 0x72, 0x44, 0x32, 0x17, 0x06, 0xd2, 0x5e, 0x20, 0x72, 0x98, 0xf5, - 0x9f, 0x32, 0x70, 0x8d, 0x6e, 0x93, 0xdc, 0x59, 0x28, 0x0a, 0x2e, 0x7e, 0xcd, 0xff, 0x11, 0xcc, - 0xa8, 0x8d, 0xf3, 0x09, 0xa3, 0xef, 0x97, 0x45, 0x0f, 0x9a, 0x2d, 0xaf, 0x8d, 0x6d, 0xad, 0x1a, - 0xda, 0x81, 0x69, 0xfe, 0xad, 0xb8, 0x84, 0x96, 0x94, 0xe4, 0x26, 0x54, 0x1e, 0xd8, 0x83, 0x33, - 0x3a, 0xfb, 0x9c, 0x58, 0x93, 0x46, 0xa8, 0xab, 0xb8, 0xd6, 0xaf, 0xb2, 0xb0, 0xd2, 0xc0, 0xbe, - 0xfb, 0xec, 0xa5, 0x61, 0x30, 0x8f, 0x61, 0x51, 0x80, 0xe8, 0x98, 0x75, 0x39, 0x64, 0x2f, 0xc9, - 0x44, 0x57, 0x03, 0x52, 0xa1, 0x29, 0xc5, 0x32, 0x15, 0xf1, 0x02, 0x6f, 0x2b, 0x3f, 0x84, 0x9c, - 0x14, 0xe5, 0x31, 0xca, 0x19, 0x3a, 0x37, 0x42, 0x8c, 0xf5, 0x67, 0xe6, 0x52, 0x9e, 0xff, 0xa6, - 0xf9, 0xd6, 0x81, 0x9f, 0x78, 0x46, 0x1c, 0x46, 0x99, 0x54, 0x13, 0x89, 0x76, 0x94, 0xd2, 0x14, - 0xa9, 0xde, 0xbe, 0x64, 0x9b, 0x5a, 0xaa, 0x4e, 0x43, 0xbe, 0x42, 0xef, 0x44, 0xc8, 0x01, 0xe3, - 0x7f, 0x67, 0x61, 0x55, 0x44, 0xfe, 0x19, 0xd8, 0xfc, 0x1d, 0x2c, 0x0b, 0x50, 0xa5, 0xd7, 0xf3, - 0xbd, 0xe7, 0xb8, 0xad, 0x73, 0x9a, 0xbd, 0xe6, 0x14, 0x9c, 0x76, 0x78, 0x9d, 0x88, 0xd9, 0x26, - 0xf4, 0x37, 0xe3, 0x66, 0xf9, 0x5a, 0x57, 0x2c, 0x6c, 0x36, 0xe8, 0x7b, 0x25, 0x55, 0xb1, 0xe8, - 0x79, 0x78, 0x54, 0x25, 0xd3, 0x4e, 0xb8, 0x69, 0xc6, 0x5f, 0xd7, 0x4d, 0x43, 0x8e, 0xa6, 0x3a, - 0xcd, 0xea, 0x1c, 0xcc, 0xec, 0xe1, 0x17, 0x11, 0xdf, 0x7f, 0x37, 0x03, 0xb3, 0xda, 0xe2, 0x46, - 0xef, 0xc2, 0x04, 0xfd, 0x83, 0x6e, 0x9a, 0xfc, 0x49, 0x0b, 0x59, 0x60, 0xda, 0x93, 0x16, 0x56, - 0x75, 0x07, 0xa6, 0x58, 0x94, 0x4b, 0xfb, 0x1c, 0x67, 0x08, 0x19, 0x44, 0xd5, 0x62, 0x28, 0xec, - 0x38, 0xc1, 0xf1, 0xad, 0x87, 0xf0, 0x16, 0x0f, 0x98, 0xd1, 0x27, 0xbf, 0xa6, 0xda, 0xef, 0xe7, - 0xd4, 0xf1, 0x96, 0x03, 0xab, 0x5b, 0x38, 0xae, 0x7a, 0xb4, 0x20, 0xb3, 0x6f, 0xe0, 0xb2, 0x06, - 0x97, 0x14, 0x69, 0xd8, 0xbb, 0x94, 0x21, 0x49, 0x3a, 0x5e, 0xdb, 0xba, 0x91, 0xd6, 0x84, 0xda, - 0x59, 0x0b, 0xc3, 0x65, 0x7a, 0x52, 0x96, 0x17, 0x43, 0xc1, 0x05, 0xb4, 0xde, 0x6d, 0x65, 0x5d, - 0x33, 0x8d, 0xc7, 0x5e, 0x4c, 0x8a, 0xed, 0x49, 0x96, 0x5a, 0xff, 0x33, 0x03, 0x16, 0xe7, 0x5c, - 0x9a, 0x1b, 0x47, 0x0c, 0xf8, 0x30, 0xd1, 0x1b, 0xbe, 0x4b, 0x5c, 0x51, 0x9d, 0x55, 0x51, 0x29, - 0x13, 0x2d, 0xfa, 0x88, 0xa4, 0x15, 0x41, 0x35, 0xd1, 0x4a, 0x0c, 0xef, 0xe7, 0x06, 0x3d, 0xc8, - 0x56, 0x13, 0x4d, 0xe3, 0x61, 0xd0, 0x83, 0x1a, 0xdd, 0x54, 0x22, 0xaa, 0xbf, 0xeb, 0x1f, 0x65, - 0xe0, 0x1a, 0x1b, 0xf2, 0xbe, 0xef, 0x3e, 0x77, 0x3b, 0xf8, 0x08, 0x6b, 0x93, 0xdb, 0x4f, 0xbf, - 0x06, 0xca, 0x9c, 0x4b, 0xb3, 0xd1, 0x8c, 0x04, 0x98, 0xa3, 0x9b, 0xdc, 0x9f, 0x69, 0xf4, 0xad, - 0xbf, 0x9f, 0x81, 0x55, 0xf1, 0x60, 0x2a, 0x76, 0x37, 0x72, 0x51, 0x23, 0xa5, 0xaa, 0xdd, 0x67, - 0x64, 0x0d, 0xf7, 0x19, 0x9a, 0xff, 0x39, 0x8c, 0x5f, 0x70, 0xfc, 0x61, 0x16, 0xe6, 0xf7, 0x9d, - 0x23, 0xb7, 0x4b, 0xd6, 0x97, 0x48, 0xe9, 0x81, 0x2a, 0x89, 0xcc, 0x48, 0xc3, 0x03, 0x53, 0x52, - 0x52, 0x1f, 0xad, 0xa9, 0x49, 0x4a, 0xb2, 0xa6, 0x90, 0x65, 0x3d, 0x15, 0xc9, 0x67, 0x9a, 0x07, - 0x2e, 0x11, 0x9b, 0x44, 0x9f, 0xbd, 0x74, 0xbd, 0x76, 0x2c, 0x5b, 0x18, 0xf5, 0x62, 0x3d, 0x86, - 0x69, 0x25, 0xc0, 0x88, 0x2b, 0xc5, 0x04, 0x05, 0xaa, 0x68, 0x4f, 0xfa, 0x87, 0x38, 0x35, 0x33, - 0x8c, 0x4a, 0xa1, 0x0a, 0x90, 0x13, 0x89, 0xe8, 0xac, 0x7f, 0x39, 0x01, 0x8b, 0xe4, 0x28, 0x2d, - 0xf8, 0x13, 0x44, 0x0a, 0x62, 0x46, 0xc0, 0x14, 0x2b, 0x97, 0xef, 0xe5, 0x0c, 0xde, 0x8c, 0xe5, - 0xc5, 0xd3, 0x10, 0xd0, 0x47, 0xaa, 0xaf, 0x21, 0xab, 0xbc, 0x96, 0x4f, 0xa6, 0x34, 0x53, 0x9d, - 0x10, 0xef, 0x6a, 0x6e, 0x15, 0xa6, 0x7d, 0x3b, 0x04, 0xa0, 0x6a, 0x5f, 0xe6, 0x99, 0x58, 0x8f, - 0xfb, 0x5a, 0x58, 0x03, 0x6c, 0x65, 0x9d, 0x60, 0xcd, 0x30, 0x93, 0x2e, 0x8b, 0x27, 0xd2, 0x65, - 0x31, 0x41, 0xcf, 0x44, 0x3f, 0x51, 0x5c, 0x16, 0x71, 0x26, 0xa8, 0x6e, 0x0b, 0xf6, 0x3e, 0xb3, - 0x43, 0x01, 0xea, 0xfb, 0x4c, 0x56, 0x05, 0x1d, 0xc0, 0xc2, 0xbe, 0x8f, 0xdb, 0x74, 0x29, 0x6d, - 0xfe, 0xd8, 0xf3, 0xb9, 0x21, 0x4a, 0x1d, 0x5f, 0x2c, 0x75, 0x50, 0x4f, 0x14, 0x37, 0xb1, 0x2c, - 0x57, 0x57, 0x54, 0x0a, 0x3a, 0xda, 0x84, 0xb9, 0x3a, 0x76, 0xfc, 0xd6, 0xf1, 0x43, 0xfc, 0x92, - 0x28, 0xbb, 0xa0, 0x38, 0x15, 0xa5, 0x5b, 0x08, 0x68, 0x09, 0x19, 0x28, 0x2d, 0x52, 0x2f, 0x27, - 0x74, 0x24, 0xf4, 0x53, 0x98, 0xac, 0x7b, 0x7e, 0x58, 0x7d, 0x19, 0xcb, 0x71, 0xc7, 0x80, 0xd5, - 0xab, 0x22, 0xe5, 0x44, 0xe0, 0xf9, 0x61, 0xf3, 0x50, 0xe5, 0x1b, 0xc7, 0x43, 0xf7, 0xc9, 0xde, - 0x4c, 0xec, 0x85, 0xd0, 0xe9, 0xd4, 0xe8, 0x5d, 0x38, 0x7b, 0x61, 0xc3, 0xf7, 0x5f, 0x6a, 0x64, - 0x84, 0x4e, 0xa7, 0x49, 0x77, 0x03, 0xfd, 0x9a, 0x44, 0xc5, 0x7a, 0x1d, 0x9f, 0xcf, 0x1f, 0x67, - 0x60, 0x29, 0x36, 0x49, 0xdc, 0xf6, 0x7f, 0x0c, 0x79, 0x09, 0xe4, 0x27, 0x5d, 0xe1, 0x9d, 0x4f, - 0xac, 0x7f, 0x26, 0x22, 0x42, 0x82, 0x55, 0xa6, 0x45, 0x34, 0xd0, 0xbd, 0x98, 0x7b, 0x88, 0xd9, - 0x94, 0x5d, 0xfc, 0x63, 0x5c, 0xac, 0x44, 0x35, 0xf4, 0x19, 0x80, 0xc2, 0x1b, 0x26, 0xba, 0x34, - 0xf1, 0x59, 0x3a, 0x5b, 0x94, 0xca, 0xd6, 0x9f, 0x4e, 0x0a, 0x65, 0xce, 0x4f, 0x21, 0x07, 0xbe, - 0xd3, 0x3a, 0x89, 0x82, 0xdc, 0x3e, 0x4a, 0x46, 0x94, 0x9d, 0x67, 0x1d, 0xdd, 0xd2, 0xde, 0x9e, - 0x9a, 0xd3, 0x58, 0x46, 0xcf, 0x90, 0xc7, 0xce, 0xf1, 0x0c, 0xf9, 0x2e, 0x4c, 0xed, 0x74, 0x9f, - 0xbb, 0xc4, 0xe0, 0x61, 0xe1, 0x55, 0xd4, 0x5c, 0x70, 0x19, 0x48, 0x65, 0x0c, 0xaf, 0x85, 0x3e, - 0x83, 0x1c, 0x39, 0xe9, 0xd3, 0x1d, 0x7f, 0x22, 0x32, 0x2a, 0x43, 0xea, 0x2e, 0x6b, 0x1e, 0xf3, - 0x22, 0x75, 0xa5, 0x8a, 0xea, 0xe8, 0x63, 0x98, 0xaa, 0xb4, 0xdb, 0x64, 0x29, 0xf0, 0x65, 0x44, - 0x73, 0xe0, 0x71, 0x4c, 0x87, 0x95, 0xa8, 0x4d, 0xf2, 0xca, 0xe8, 0x4b, 0xdd, 0x77, 0x35, 0x15, - 0x3d, 0xd2, 0x4f, 0xcf, 0x07, 0xa9, 0xfb, 0xb5, 0xde, 0x15, 0x77, 0x06, 0xb9, 0x28, 0xed, 0x01, - 0x4d, 0x61, 0xa0, 0xe9, 0x1f, 0x7a, 0xe5, 0xb0, 0x03, 0xf9, 0x9d, 0xae, 0x1b, 0xba, 0xf4, 0xe1, - 0x77, 0x5e, 0xdb, 0x07, 0xf6, 0x1d, 0x3f, 0x74, 0x5b, 0x6e, 0xcf, 0xe9, 0x86, 0x6c, 0xb6, 0x5c, - 0x51, 0x51, 0x9d, 0x2d, 0x89, 0xad, 0xa6, 0x88, 0x81, 0x37, 0x96, 0x22, 0x26, 0x35, 0xcb, 0xca, - 0xf4, 0xab, 0x67, 0x59, 0x59, 0x67, 0x73, 0x49, 0x93, 0x65, 0xce, 0x44, 0x82, 0x48, 0x5d, 0x3a, - 0x7a, 0x8a, 0x4c, 0x5b, 0x56, 0x44, 0x37, 0xe8, 0x43, 0xef, 0xd9, 0x28, 0x52, 0x51, 0xbb, 0xa3, - 0xcc, 0xee, 0x6c, 0xa0, 0x26, 0xcc, 0x90, 0xda, 0xfb, 0x5e, 0xc7, 0x6d, 0xb9, 0x38, 0x28, 0xce, - 0x69, 0x3e, 0x40, 0x7d, 0x51, 0xd0, 0x4a, 0x2f, 0xeb, 0x38, 0x64, 0x3b, 0x11, 0x6d, 0xba, 0xc7, - 0x11, 0xd5, 0x9d, 0x48, 0x25, 0x68, 0xd9, 0x50, 0x8c, 0x2e, 0x12, 0x62, 0xab, 0xeb, 0xe3, 0x64, - 0xd0, 0x38, 0x4d, 0x2e, 0x16, 0x05, 0x8d, 0xab, 0x13, 0x16, 0x85, 0x8f, 0x3f, 0x81, 0x6b, 0x36, - 0x3e, 0xf5, 0x9e, 0xe3, 0x37, 0x4b, 0xf6, 0xe7, 0x70, 0x55, 0x27, 0xf8, 0xa4, 0xd7, 0xa6, 0x8f, - 0xd0, 0xd8, 0x8d, 0x45, 0x6a, 0x7a, 0x02, 0x8e, 0xc0, 0xd2, 0x13, 0xb0, 0x37, 0xaf, 0xe4, 0x4f, - 0x55, 0x5e, 0x69, 0x99, 0xe5, 0xc1, 0x8a, 0x4e, 0xbc, 0xd2, 0x6e, 0x2b, 0x82, 0x4a, 0x0c, 0x0d, - 0xe5, 0x33, 0x66, 0xd9, 0xa8, 0x12, 0x4d, 0x35, 0x5b, 0x2f, 0x02, 0xa8, 0x6b, 0x49, 0xa9, 0x67, - 0x61, 0x28, 0xc7, 0xd9, 0x43, 0x58, 0xa6, 0xb6, 0x59, 0x85, 0x59, 0xe5, 0x53, 0xda, 0xca, 0x74, - 0xa9, 0x2b, 0x2d, 0xe8, 0x0c, 0xd3, 0x51, 0xac, 0x7f, 0x35, 0x06, 0xd7, 0x38, 0x9f, 0xde, 0xe4, - 0x64, 0xa0, 0x1f, 0x60, 0x5a, 0x61, 0x3f, 0xe7, 0xc7, 0x0d, 0x71, 0xef, 0x6b, 0x9a, 0x26, 0xa6, - 0x6a, 0xfa, 0x14, 0xd0, 0x8c, 0xcd, 0x04, 0xb1, 0xc4, 0xd4, 0x19, 0xed, 0xc0, 0x9c, 0x3e, 0x07, - 0xdc, 0x3e, 0xbc, 0x99, 0xda, 0x88, 0x5e, 0x55, 0xbc, 0xa1, 0x6d, 0x37, 0x53, 0x67, 0x82, 0x26, - 0x6a, 0xd4, 0xe7, 0xf7, 0x47, 0x98, 0x4f, 0x4c, 0x00, 0x37, 0x27, 0x6f, 0xa5, 0x36, 0x98, 0xa8, - 0xcd, 0xf4, 0x86, 0x4f, 0xc1, 0xc6, 0x66, 0x93, 0x8d, 0x54, 0x73, 0x30, 0xc9, 0x86, 0x4d, 0xd6, - 0xcd, 0xbe, 0x8f, 0x03, 0xdc, 0x6d, 0x61, 0xf5, 0x72, 0xfd, 0x75, 0xd7, 0xcd, 0x7f, 0xcc, 0x40, - 0x31, 0x8d, 0x6e, 0x1d, 0x77, 0xdb, 0x68, 0x1f, 0x0a, 0xf1, 0x86, 0xb8, 0xd1, 0x6f, 0x09, 0x33, - 0xc1, 0xdc, 0xa5, 0xed, 0x4b, 0x76, 0x02, 0x9b, 0x68, 0x56, 0x05, 0x76, 0xc1, 0x28, 0x86, 0x24, - 0xaa, 0x72, 0xb6, 0x7b, 0xef, 0x3d, 0xc8, 0xcb, 0xec, 0xd3, 0x28, 0x07, 0xe3, 0x3b, 0x7b, 0x3b, - 0x07, 0x2c, 0x9b, 0xd1, 0xfe, 0x93, 0x83, 0x42, 0x06, 0x01, 0x4c, 0x6e, 0x6c, 0xee, 0x6e, 0x1e, - 0x6c, 0x16, 0xb2, 0xef, 0x35, 0xd5, 0x53, 0x12, 0xba, 0x06, 0xcb, 0x1b, 0x9b, 0x8d, 0x9d, 0xda, - 0x66, 0xf3, 0xe0, 0x2f, 0xec, 0x6f, 0x36, 0x9f, 0xec, 0xd5, 0xf7, 0x37, 0x6b, 0x3b, 0xf7, 0x77, - 0x36, 0x37, 0x0a, 0x97, 0xd0, 0x22, 0x14, 0xd4, 0xc2, 0x83, 0xc7, 0x07, 0xfb, 0x85, 0x0c, 0x2a, - 0xc2, 0xa2, 0x0a, 0x7d, 0xba, 0x59, 0xad, 0x3c, 0x39, 0xd8, 0xde, 0x2b, 0x8c, 0x59, 0xe3, 0xb9, - 0x6c, 0x21, 0xfb, 0xde, 0x2d, 0x7e, 0x63, 0x89, 0xe6, 0x00, 0x36, 0x36, 0xeb, 0xb5, 0xcd, 0xbd, - 0x8d, 0x9d, 0xbd, 0xad, 0xc2, 0x25, 0x34, 0x0b, 0xf9, 0x8a, 0xfc, 0xcc, 0xac, 0xfd, 0xd1, 0x0f, - 0x30, 0x4d, 0x46, 0x2b, 0x32, 0x4e, 0x36, 0x61, 0xf9, 0x91, 0xe3, 0x76, 0x43, 0xc7, 0xed, 0xf2, - 0x39, 0x12, 0x1c, 0x46, 0xe5, 0x21, 0x2c, 0x27, 0xb3, 0x55, 0x1a, 0x15, 0x97, 0x71, 0x3b, 0x73, - 0x2f, 0x83, 0xea, 0xb0, 0x98, 0x66, 0x33, 0x21, 0x4b, 0x7f, 0x0a, 0x9d, 0xa6, 0x0e, 0x4a, 0xcb, - 0xa9, 0x3b, 0x4b, 0xe3, 0x03, 0xf4, 0x08, 0xe6, 0x13, 0xfb, 0x84, 0xec, 0xaf, 0x69, 0x07, 0x19, - 0x46, 0xae, 0x48, 0x3d, 0x24, 0xa1, 0x1b, 0xdf, 0x25, 0x02, 0x74, 0x25, 0xb1, 0xbd, 0x6f, 0x12, - 0x91, 0x36, 0x12, 0xbb, 0x97, 0x41, 0x36, 0x2c, 0xa6, 0xed, 0x38, 0x72, 0xc8, 0x43, 0xb6, 0xa3, - 0x92, 0xa1, 0x39, 0x42, 0x33, 0x4d, 0x71, 0x4a, 0x9a, 0x43, 0xb4, 0xaa, 0x91, 0xe6, 0x97, 0xe4, - 0xcc, 0xd2, 0x6d, 0x3f, 0xc4, 0xb8, 0x57, 0xe9, 0xb8, 0xcf, 0x71, 0x80, 0x44, 0x54, 0x91, 0x04, - 0x99, 0x70, 0x6f, 0x67, 0xd0, 0x6f, 0xc1, 0x34, 0x4d, 0x47, 0xc9, 0x2f, 0xc1, 0x67, 0xd4, 0x14, - 0x95, 0x25, 0xf1, 0x45, 0x0b, 0xef, 0x65, 0xd0, 0x57, 0x30, 0xb5, 0x85, 0xe9, 0x2d, 0x30, 0x7a, - 0x2b, 0x96, 0x75, 0x7d, 0xa7, 0x2b, 0x4d, 0x61, 0xd1, 0xe1, 0xf8, 0x91, 0x19, 0xd5, 0x20, 0xc7, - 0xd1, 0x03, 0x64, 0xc5, 0xf0, 0x83, 0x14, 0x02, 0x0b, 0x31, 0x02, 0xe4, 0x34, 0x82, 0x6a, 0x90, - 0x97, 0x57, 0xd1, 0x68, 0xd9, 0x70, 0xff, 0x5d, 0x2a, 0x26, 0x0b, 0xb8, 0xd3, 0x64, 0xec, 0xef, - 0x66, 0x33, 0xe8, 0x2e, 0x00, 0x7b, 0x44, 0x43, 0xc7, 0x12, 0xef, 0x68, 0x29, 0xc1, 0x40, 0xb4, - 0x45, 0x56, 0x7e, 0x07, 0x87, 0xf8, 0xbc, 0x83, 0x37, 0xcd, 0xd6, 0x2e, 0xcc, 0xc9, 0x27, 0x2d, - 0xe7, 0xe7, 0x84, 0x89, 0xda, 0xe7, 0x64, 0x05, 0xb1, 0x97, 0xa0, 0x32, 0xf6, 0x0b, 0x99, 0xa2, - 0xc1, 0xe4, 0x74, 0xb2, 0x6a, 0x0a, 0xae, 0xcc, 0xf5, 0x29, 0x71, 0xe3, 0xd9, 0x3f, 0x63, 0xb8, - 0x18, 0x4a, 0x6a, 0xbb, 0x7a, 0x1c, 0x18, 0xba, 0xa1, 0x74, 0x20, 0x35, 0x7c, 0xad, 0xf4, 0xd6, - 0x90, 0x1a, 0x6c, 0x9e, 0xa8, 0xd6, 0x79, 0x00, 0xb3, 0x5a, 0xe4, 0x10, 0x12, 0x91, 0xc2, 0x69, - 0xa1, 0x5d, 0xa5, 0x95, 0xf4, 0x42, 0x7e, 0x68, 0xbd, 0x4f, 0x95, 0x4d, 0x2c, 0xb7, 0x56, 0x29, - 0x2d, 0x87, 0x16, 0xcb, 0x9a, 0x5a, 0x12, 0xd9, 0x16, 0x62, 0x28, 0x9b, 0xb0, 0x20, 0xfd, 0xc6, - 0x4a, 0xc2, 0x72, 0x43, 0x36, 0x2e, 0xe3, 0xcc, 0x7d, 0x03, 0x0b, 0x5c, 0x0e, 0x34, 0x32, 0x05, - 0xa9, 0x5c, 0x78, 0xe2, 0x26, 0x23, 0x81, 0x07, 0xb0, 0x54, 0x8f, 0x8d, 0x87, 0xd9, 0x38, 0x57, - 0x75, 0x12, 0x4a, 0x1e, 0x2e, 0x23, 0xad, 0x87, 0x80, 0xea, 0xfd, 0xc3, 0x53, 0x57, 0x92, 0x7b, - 0xee, 0xe2, 0x17, 0xe8, 0x7a, 0x6c, 0x48, 0x04, 0x48, 0xab, 0x51, 0xed, 0x54, 0x32, 0x8c, 0x18, - 0x1d, 0xb0, 0xa7, 0xc1, 0x2c, 0x41, 0x89, 0xd3, 0x73, 0x0e, 0xdd, 0x8e, 0x1b, 0xba, 0x98, 0x88, - 0x85, 0x8a, 0xa0, 0x16, 0x89, 0x19, 0xbc, 0x6a, 0xac, 0x81, 0xbe, 0x86, 0xd9, 0x2d, 0x1c, 0x46, - 0xa9, 0xc6, 0xd0, 0x72, 0x22, 0x39, 0x19, 0x9f, 0x37, 0x71, 0x83, 0xa8, 0xe7, 0x37, 0xdb, 0x81, - 0x02, 0x53, 0xae, 0x0a, 0x89, 0xeb, 0x09, 0x12, 0xbc, 0x8a, 0xe3, 0x3b, 0xa7, 0x81, 0x91, 0x5b, - 0x77, 0x61, 0x7c, 0xdf, 0xed, 0x1e, 0x21, 0x24, 0x1a, 0x8a, 0x52, 0xf5, 0x94, 0x16, 0x34, 0x18, - 0x17, 0xbd, 0x43, 0x28, 0xb3, 0x1c, 0x5b, 0xc9, 0xbc, 0x56, 0x22, 0x91, 0xf0, 0xdb, 0x32, 0xe0, - 0x6f, 0x48, 0x2e, 0x2e, 0xc9, 0x9f, 0x78, 0x79, 0x63, 0x1d, 0xed, 0x53, 0xae, 0x27, 0x1b, 0x40, - 0x37, 0xa3, 0xfd, 0xd4, 0x98, 0x56, 0xab, 0x84, 0xe2, 0x84, 0x1b, 0xeb, 0x48, 0xbe, 0x28, 0x4e, - 0x21, 0x7a, 0x4b, 0xdb, 0xf6, 0x2f, 0x46, 0xf7, 0x6b, 0xc8, 0xcb, 0x9c, 0x52, 0x52, 0xdf, 0xc4, - 0x13, 0x61, 0x49, 0x05, 0x9e, 0x4c, 0x3f, 0xf5, 0x25, 0x4b, 0xff, 0xa6, 0xe3, 0xc7, 0xd3, 0x2e, - 0x19, 0x27, 0xef, 0x33, 0x98, 0x56, 0x12, 0x2e, 0xc9, 0xc5, 0x92, 0x4c, 0xc2, 0x54, 0xd2, 0x7f, - 0x98, 0xe2, 0x1e, 0xd9, 0x34, 0xa6, 0x78, 0x86, 0x3f, 0xb4, 0x14, 0xa1, 0x29, 0x59, 0x6b, 0x62, - 0x28, 0xe4, 0xfc, 0x2e, 0x52, 0x02, 0xa2, 0x2b, 0x3a, 0x86, 0xb9, 0x95, 0x75, 0x00, 0x36, 0x66, - 0xda, 0x90, 0x5e, 0x6c, 0x1c, 0xd5, 0x3a, 0xd9, 0xcf, 0xda, 0x17, 0x44, 0xfa, 0x5a, 0xec, 0x69, - 0x14, 0xa9, 0xa8, 0x71, 0x52, 0x1d, 0x95, 0x09, 0x7f, 0x07, 0x0a, 0x95, 0x16, 0xd5, 0xb2, 0x32, - 0x91, 0x0e, 0x5a, 0x95, 0x2b, 0x58, 0x2f, 0x10, 0xb4, 0x96, 0xe2, 0x79, 0x79, 0x76, 0xb1, 0x43, - 0x43, 0x92, 0x96, 0xe5, 0x5e, 0x1b, 0x2b, 0x4a, 0xc7, 0x30, 0x76, 0x6a, 0x13, 0x16, 0x6b, 0x4e, - 0xb7, 0x85, 0x3b, 0xaf, 0x47, 0xe6, 0x73, 0xaa, 0x6e, 0x94, 0x24, 0x43, 0x57, 0xe2, 0xf8, 0x5c, - 0xdb, 0xcc, 0xcb, 0x83, 0x9c, 0xac, 0x5a, 0x81, 0xcb, 0x8c, 0x89, 0x11, 0x5b, 0x4c, 0xd8, 0xa6, - 0xe6, 0x3f, 0x81, 0xb9, 0x4d, 0xa2, 0x8e, 0xfb, 0x6d, 0x97, 0x45, 0xaf, 0x22, 0x3d, 0x1c, 0xd1, - 0x88, 0xb8, 0x2d, 0x12, 0xb6, 0x29, 0xd9, 0x77, 0xa4, 0x90, 0x27, 0x13, 0x1c, 0x95, 0x16, 0x05, - 0x59, 0x35, 0x51, 0x0f, 0xdd, 0x7b, 0x8f, 0x44, 0x86, 0x87, 0x58, 0x4e, 0x15, 0x55, 0xa1, 0x18, - 0x33, 0xae, 0x94, 0xde, 0x1e, 0x5e, 0x49, 0xb5, 0xc5, 0x6c, 0x58, 0x36, 0xe4, 0xab, 0x41, 0xef, - 0x48, 0xb3, 0x78, 0x58, 0x3e, 0x9b, 0x14, 0x73, 0xed, 0x3b, 0x25, 0x6b, 0x81, 0x81, 0xe6, 0xf0, - 0x44, 0x36, 0x46, 0x06, 0xcb, 0x08, 0xad, 0xd4, 0x84, 0x33, 0xe8, 0x5d, 0x9d, 0xfa, 0x90, 0xa4, - 0x34, 0xc6, 0x16, 0x1e, 0x53, 0xd1, 0x8b, 0xf2, 0x9d, 0x48, 0xa3, 0x27, 0x2d, 0x29, 0x8d, 0x34, - 0x7a, 0x52, 0xb3, 0xc5, 0x30, 0x06, 0x6f, 0xc1, 0xe5, 0x58, 0xea, 0x17, 0x74, 0x3d, 0xce, 0xd8, - 0x11, 0x0c, 0x65, 0x84, 0x1e, 0x09, 0xc1, 0x4e, 0x12, 0x4a, 0x4f, 0x06, 0x63, 0x1a, 0x23, 0x23, - 0xf7, 0x44, 0x9a, 0x40, 0x6a, 0x7a, 0x17, 0xf4, 0x56, 0x0a, 0x0b, 0xcf, 0xc7, 0x3a, 0x46, 0xb6, - 0x0e, 0x85, 0x78, 0x76, 0x14, 0xb4, 0x2a, 0xb9, 0x94, 0x9a, 0x02, 0xa6, 0x54, 0x36, 0x96, 0xf3, - 0x4d, 0xe7, 0x41, 0x34, 0x29, 0xec, 0xa6, 0x28, 0x3e, 0x29, 0x6a, 0x66, 0x8c, 0xc4, 0xa4, 0xe8, - 0x09, 0x2f, 0xb6, 0x68, 0x5c, 0xa9, 0x92, 0xe6, 0xc4, 0x78, 0x3a, 0xbd, 0x9e, 0x46, 0x27, 0xba, - 0x87, 0xa9, 0x8b, 0x04, 0x92, 0x4a, 0xbf, 0x56, 0xb5, 0x7d, 0x33, 0xd9, 0xb5, 0xb2, 0xb1, 0x5c, - 0x8e, 0xb4, 0x10, 0x4f, 0xf8, 0x21, 0x89, 0x1a, 0x32, 0x81, 0x18, 0x45, 0xf9, 0x3e, 0x2c, 0xea, - 0xb3, 0x38, 0x62, 0xbc, 0x66, 0x5b, 0x77, 0x56, 0x4b, 0xf3, 0x81, 0xc4, 0xcf, 0x9c, 0xc4, 0x32, - 0x8a, 0x24, 0xb8, 0x9f, 0x92, 0x6e, 0x84, 0x71, 0x5f, 0x49, 0x19, 0x72, 0x1e, 0xee, 0xa7, 0x65, - 0x18, 0x91, 0x8c, 0x52, 0xfa, 0x25, 0xb6, 0xbf, 0x78, 0xc1, 0x45, 0x18, 0x75, 0x9e, 0xae, 0x99, - 0xe8, 0x6c, 0x50, 0xeb, 0x46, 0xfe, 0x2e, 0xcf, 0x55, 0x8d, 0x4d, 0x9a, 0xc4, 0x97, 0xb4, 0xc1, - 0xe9, 0xc2, 0x5e, 0x83, 0x19, 0x35, 0x63, 0x89, 0xb1, 0x17, 0xd7, 0x92, 0x34, 0x02, 0xe5, 0xbc, - 0x35, 0x27, 0xb9, 0xc0, 0x7a, 0xb3, 0x12, 0x67, 0x8e, 0xd6, 0x21, 0xf3, 0x90, 0x90, 0xca, 0x9a, - 0x11, 0x5d, 0x32, 0x9b, 0x05, 0x0b, 0xcc, 0x40, 0xd2, 0x7f, 0xb6, 0xc9, 0xf0, 0xeb, 0x4f, 0x46, - 0x32, 0x4f, 0x68, 0xb4, 0xba, 0x9a, 0x7e, 0x04, 0x29, 0x52, 0x92, 0x92, 0x96, 0xa4, 0xb4, 0x6a, - 0x2a, 0x56, 0x35, 0xf4, 0xb7, 0x30, 0x9f, 0x48, 0xb3, 0x22, 0x1d, 0x61, 0xa6, 0x04, 0x2c, 0xc3, - 0xb5, 0xe0, 0x36, 0x19, 0x70, 0x0c, 0xb1, 0xb1, 0x36, 0x9a, 0x68, 0x72, 0x2f, 0xdd, 0x15, 0x01, - 0xee, 0x69, 0x9d, 0x33, 0x25, 0x73, 0x31, 0x72, 0xf0, 0x00, 0x96, 0x52, 0xd3, 0xb8, 0x48, 0xb3, - 0x62, 0x58, 0x92, 0x17, 0x23, 0xd5, 0xbf, 0x48, 0xd3, 0xa8, 0xc6, 0x52, 0x84, 0x48, 0x3f, 0x84, - 0x31, 0x2d, 0x8c, 0xf4, 0x43, 0x98, 0xf3, 0xb5, 0x30, 0x6e, 0xee, 0xc2, 0x62, 0x5a, 0xd2, 0x15, - 0xc5, 0x6f, 0x67, 0xcc, 0xc8, 0x92, 0xc2, 0x51, 0x5b, 0xac, 0x76, 0x03, 0xb5, 0x21, 0x29, 0x58, - 0x8c, 0x1c, 0xf8, 0x99, 0x48, 0xac, 0x93, 0x4c, 0x95, 0x22, 0x4f, 0x6b, 0x23, 0x72, 0xa9, 0x0c, - 0x31, 0x2a, 0x2f, 0xd7, 0xdd, 0xa3, 0xae, 0x92, 0xd5, 0x44, 0x9a, 0x94, 0xc9, 0x74, 0x2a, 0x52, - 0xb3, 0xa4, 0x25, 0x41, 0x79, 0x0c, 0x8b, 0x62, 0x8b, 0x55, 0xb3, 0x80, 0xa0, 0x04, 0x4e, 0x14, - 0x94, 0x2e, 0xb5, 0x4c, 0x6a, 0xda, 0x10, 0x76, 0x26, 0xa3, 0xbf, 0xfb, 0xa2, 0x9c, 0xc9, 0x94, - 0xf4, 0x1c, 0x25, 0x3d, 0x93, 0x07, 0xfa, 0x82, 0x9e, 0xc9, 0x58, 0xb6, 0x36, 0xb3, 0x53, 0x58, - 0xa3, 0x14, 0xe9, 0xb4, 0x75, 0xe1, 0x36, 0xa4, 0x0d, 0xea, 0x94, 0x47, 0x1f, 0xb3, 0x28, 0x92, - 0x7e, 0xcc, 0x52, 0x3b, 0x6a, 0x76, 0xce, 0xcc, 0xa8, 0x2f, 0x5d, 0x25, 0xaf, 0x52, 0x9e, 0xae, - 0x4b, 0x5e, 0xa5, 0x3d, 0x34, 0xa7, 0x56, 0xfd, 0x81, 0x30, 0xe1, 0x22, 0x7a, 0xd7, 0x87, 0xbe, - 0x14, 0x2f, 0xad, 0x0e, 0x7f, 0x5e, 0xcd, 0x6f, 0x07, 0x0a, 0xf1, 0xc7, 0xb8, 0x28, 0xed, 0x3d, - 0xbe, 0xf2, 0x22, 0x59, 0x1a, 0x22, 0xc6, 0x57, 0xbc, 0xfb, 0xc2, 0x3c, 0xd4, 0xe9, 0x1a, 0x9e, - 0x7b, 0xab, 0xa4, 0x87, 0x9b, 0x11, 0xd1, 0xbb, 0x5c, 0xd5, 0x88, 0x4b, 0xbc, 0xfb, 0x55, 0xcd, - 0x88, 0x94, 0xa7, 0xbc, 0xae, 0x08, 0x22, 0x49, 0xcf, 0xf1, 0xf2, 0xae, 0x6e, 0x66, 0x0d, 0x09, - 0x94, 0x1c, 0x79, 0xff, 0x82, 0x7e, 0x47, 0x24, 0xaf, 0x4b, 0x66, 0x40, 0x78, 0x27, 0xe6, 0x87, - 0x49, 0x8f, 0x02, 0x2c, 0x0d, 0x4b, 0xb0, 0x80, 0x1e, 0xd1, 0xf7, 0x51, 0x8f, 0x77, 0x36, 0x6a, - 0xfc, 0xd7, 0x1d, 0x3d, 0x3f, 0xe1, 0xe0, 0x56, 0x7e, 0xa1, 0x20, 0x62, 0x32, 0xab, 0xa2, 0x21, - 0x36, 0xd6, 0x51, 0x9d, 0xfa, 0x59, 0x35, 0x68, 0x8a, 0x8f, 0x3b, 0x85, 0x60, 0x29, 0x9d, 0x20, - 0x75, 0xfa, 0x6f, 0x8a, 0xdd, 0x4c, 0xef, 0xa6, 0xa1, 0x0f, 0xc3, 0xac, 0x00, 0x26, 0x36, 0xe9, - 0x64, 0x44, 0xef, 0x46, 0xc9, 0x11, 0xe3, 0x58, 0xbd, 0xf2, 0x68, 0xf7, 0x95, 0x38, 0xa6, 0x21, - 0x36, 0xd6, 0x38, 0xc7, 0x34, 0xe8, 0xc5, 0x38, 0x16, 0x23, 0xa8, 0x73, 0x4c, 0xef, 0xa6, 0xa1, - 0x0f, 0xa3, 0x39, 0x96, 0x4e, 0xe6, 0xbc, 0x1c, 0xfb, 0x96, 0xee, 0xcf, 0x5b, 0xf4, 0xb5, 0xce, - 0x85, 0x78, 0x56, 0x14, 0x16, 0xac, 0x8e, 0xda, 0x58, 0x47, 0x4f, 0x69, 0x86, 0xbe, 0x18, 0xfc, - 0x7c, 0x7c, 0x5b, 0x31, 0x11, 0xa5, 0x9c, 0xdb, 0x81, 0x25, 0xc6, 0xb9, 0x78, 0x77, 0x8d, 0x7d, - 0x31, 0x0e, 0x7b, 0x4b, 0x18, 0x3b, 0x71, 0x52, 0x17, 0xe5, 0xdf, 0x06, 0x15, 0x91, 0x03, 0x9f, - 0xd8, 0xa7, 0xed, 0xa4, 0xf1, 0xaa, 0x13, 0x11, 0x9e, 0x71, 0xbd, 0x7a, 0x63, 0x0d, 0xed, 0xd0, - 0x59, 0xd0, 0xc1, 0xc3, 0xac, 0xfb, 0x74, 0x32, 0x94, 0x49, 0xdb, 0xc2, 0x20, 0x8a, 0xf5, 0xc9, - 0xd4, 0xb6, 0xb9, 0x53, 0xf2, 0xe8, 0x73, 0xce, 0xd1, 0x99, 0x58, 0xc4, 0x36, 0x76, 0x76, 0xd2, - 0x18, 0xc5, 0x99, 0xf8, 0x2f, 0x23, 0xa3, 0x9f, 0x42, 0x5e, 0x20, 0x8f, 0x66, 0x48, 0x1c, 0x9b, - 0x32, 0xe4, 0x6b, 0x98, 0xe6, 0x0c, 0xa1, 0x3d, 0x30, 0xb5, 0x64, 0xec, 0xfe, 0x57, 0x30, 0xcd, - 0xd9, 0x30, 0x74, 0x04, 0xe6, 0x6b, 0xc5, 0xa5, 0x2d, 0x1c, 0xa6, 0xfc, 0x72, 0xe7, 0xa8, 0xc1, - 0xa4, 0xfd, 0x50, 0x28, 0x6a, 0xd0, 0x77, 0x8e, 0xa6, 0x5f, 0x59, 0x35, 0x91, 0x1c, 0xf9, 0x1b, - 0xaf, 0x84, 0x6e, 0xdd, 0x4c, 0x77, 0x24, 0xbe, 0x71, 0xf4, 0x7b, 0xb0, 0x42, 0xef, 0x20, 0x2e, - 0xda, 0x63, 0xf3, 0x21, 0xe5, 0x6a, 0x14, 0x7e, 0x10, 0xff, 0x81, 0x57, 0x13, 0xb1, 0x51, 0xbf, - 0x2d, 0x4b, 0xa8, 0xd6, 0x8d, 0x54, 0x47, 0x61, 0x0f, 0xd9, 0x8c, 0xae, 0xd1, 0xb1, 0x5f, 0xb0, - 0xb7, 0xc3, 0x35, 0x4d, 0xec, 0x17, 0x67, 0x47, 0x45, 0x4f, 0xc4, 0x7f, 0xd3, 0x96, 0x50, 0xa9, - 0x27, 0xa8, 0x98, 0x6a, 0x0f, 0xdb, 0x7c, 0xe8, 0xd0, 0xce, 0xd9, 0x1b, 0xf3, 0xe5, 0x48, 0x5e, - 0x26, 0x81, 0x40, 0x8a, 0x6d, 0xaf, 0xa5, 0x38, 0x28, 0xcd, 0xaa, 0xb1, 0x0e, 0x01, 0xaa, 0xb0, - 0x3d, 0x5e, 0x4d, 0x86, 0xa0, 0x78, 0x11, 0x53, 0xb3, 0x24, 0xc4, 0x49, 0xb0, 0xb3, 0x09, 0xfd, - 0x19, 0x5c, 0xe5, 0x6c, 0xa2, 0x3c, 0x13, 0x2f, 0xe9, 0x8f, 0xb8, 0xb9, 0x0a, 0xa3, 0x2f, 0xb9, - 0xd5, 0xfb, 0x22, 0xf5, 0xa1, 0xb8, 0x7a, 0x36, 0xd1, 0x9f, 0xb4, 0xcb, 0xb3, 0x09, 0x6d, 0x50, - 0xa7, 0x3c, 0xfa, 0x6c, 0x42, 0x91, 0xf4, 0xb3, 0x89, 0xda, 0x51, 0xf3, 0xc2, 0x43, 0xc9, 0x37, - 0xed, 0xf2, 0xdc, 0x6d, 0x7c, 0xee, 0x3e, 0xe4, 0x4a, 0x69, 0x21, 0x25, 0x7b, 0x89, 0xb4, 0xf9, - 0xcd, 0x99, 0x4d, 0x4a, 0xfa, 0xfd, 0xc8, 0xbd, 0x0c, 0xda, 0xa3, 0x79, 0x72, 0xd3, 0x7e, 0x1d, - 0xd8, 0x24, 0x3f, 0x43, 0x7f, 0x8e, 0x98, 0xd0, 0xab, 0xa7, 0xd3, 0x1b, 0x8a, 0x37, 0xe4, 0x58, - 0x77, 0x95, 0x47, 0x94, 0x5c, 0xa0, 0x8b, 0x66, 0x11, 0x9f, 0x62, 0x3e, 0x62, 0x33, 0x6a, 0x41, - 0xfd, 0x39, 0x5e, 0xba, 0x65, 0xdd, 0x81, 0x49, 0x86, 0x64, 0xdc, 0x6d, 0xb4, 0x9f, 0xf0, 0x45, - 0x1f, 0x88, 0x8b, 0x5b, 0x82, 0xa2, 0x15, 0x19, 0xfb, 0xf5, 0x01, 0xe4, 0x99, 0xdb, 0xed, 0xfc, - 0x28, 0x5f, 0x88, 0xeb, 0xdd, 0x61, 0x1d, 0x33, 0x47, 0x55, 0xcc, 0xaa, 0x0e, 0xe7, 0x8b, 0x33, - 0xf2, 0x2b, 0xea, 0xfa, 0x14, 0xae, 0x06, 0x33, 0xfe, 0x52, 0xe2, 0xe7, 0x96, 0x29, 0x4b, 0x3f, - 0xa5, 0xfe, 0x57, 0x99, 0x06, 0xc8, 0xd4, 0xfd, 0xe4, 0x8f, 0x35, 0xa3, 0x2f, 0x60, 0x8e, 0x31, - 0x57, 0x22, 0x27, 0x2b, 0x0d, 0xe1, 0xd9, 0x1c, 0x63, 0xf3, 0xab, 0x20, 0xff, 0x54, 0x38, 0x6a, - 0x47, 0x76, 0xfb, 0x3c, 0x2e, 0xda, 0xd1, 0xac, 0x33, 0x51, 0xf9, 0x1d, 0xba, 0xe9, 0xa6, 0xe7, - 0xae, 0x30, 0x12, 0xbb, 0xad, 0xb8, 0xa0, 0x87, 0x67, 0xbd, 0x38, 0xa1, 0xd1, 0x81, 0xe9, 0x3f, - 0xd8, 0x7d, 0x6b, 0x04, 0x15, 0xc1, 0x80, 0x9f, 0x8c, 0xac, 0x27, 0xfd, 0x5c, 0x3c, 0x49, 0x73, - 0x7a, 0x7b, 0x23, 0x32, 0x58, 0xa4, 0xb8, 0x0c, 0x0d, 0x89, 0x21, 0x04, 0x41, 0xfd, 0xda, 0x71, - 0xe8, 0x18, 0xcc, 0x47, 0xb4, 0x28, 0xd7, 0xf2, 0x05, 0x27, 0xc1, 0x6c, 0x46, 0xa1, 0x64, 0xba, - 0x0c, 0x79, 0x77, 0xa3, 0xc3, 0xf9, 0x85, 0xf8, 0x5b, 0x26, 0x0e, 0x07, 0xca, 0x05, 0x0e, 0x0f, - 0x45, 0x8d, 0xfd, 0x16, 0xa6, 0x29, 0xd9, 0xc6, 0x90, 0xd3, 0x19, 0x0f, 0xc6, 0x7c, 0x23, 0x84, - 0x92, 0xb3, 0x7d, 0x71, 0x42, 0xd2, 0x31, 0x1c, 0x23, 0x64, 0x0d, 0x99, 0xde, 0xd1, 0x4e, 0xaf, - 0xa2, 0x61, 0x5e, 0x2f, 0x3e, 0xa1, 0x4e, 0x14, 0xf6, 0x97, 0xcc, 0xe9, 0x21, 0xb7, 0x7d, 0x63, - 0x7e, 0x11, 0x39, 0xbb, 0x43, 0x12, 0x82, 0xd4, 0xa2, 0xdf, 0xb6, 0xd0, 0x92, 0x80, 0xd4, 0xec, - 0x5d, 0xe9, 0xae, 0x4b, 0xcb, 0x0e, 0x52, 0x02, 0x51, 0x68, 0xef, 0x92, 0xb5, 0x6e, 0x4a, 0x60, - 0x11, 0x85, 0x2e, 0x0d, 0x4f, 0xef, 0x21, 0xd7, 0xfa, 0xc8, 0x4c, 0x18, 0x7b, 0xb0, 0x98, 0x96, - 0x78, 0x42, 0x4e, 0xda, 0x90, 0xac, 0x14, 0xa9, 0xf1, 0x51, 0xfb, 0xb0, 0x94, 0x9a, 0xfc, 0x41, - 0xde, 0x90, 0x0c, 0x4b, 0x0d, 0x91, 0x4a, 0xf1, 0x3b, 0x58, 0x36, 0x64, 0x3a, 0x88, 0x1c, 0x88, - 0x43, 0x33, 0x21, 0x18, 0x05, 0xe2, 0x7b, 0x28, 0x99, 0x1f, 0xd1, 0xa3, 0xdb, 0xba, 0x13, 0xd4, - 0xfc, 0x74, 0xbd, 0x94, 0x9a, 0xf5, 0x03, 0x1d, 0xd0, 0x84, 0x64, 0x69, 0xaf, 0xea, 0x65, 0xbf, - 0x87, 0xbf, 0xba, 0x37, 0xc4, 0xb5, 0x2d, 0x1b, 0x1e, 0xd2, 0x0f, 0xa1, 0x7a, 0x8e, 0xde, 0xee, - 0x09, 0xbd, 0xa4, 0xbf, 0x11, 0x8f, 0x85, 0xc8, 0xa7, 0x3e, 0x20, 0x4f, 0xed, 0xe7, 0x03, 0x98, - 0xd5, 0x9e, 0x5f, 0x4a, 0xf1, 0x4f, 0x7b, 0x39, 0x2b, 0xbd, 0xd5, 0xa9, 0x2f, 0x36, 0xab, 0x85, - 0x5f, 0xfe, 0x8f, 0xd5, 0xcc, 0x2f, 0x7f, 0xb5, 0x9a, 0xf9, 0x6f, 0xbf, 0x5a, 0xcd, 0xfc, 0xd9, - 0xaf, 0x56, 0x33, 0x87, 0x93, 0xb4, 0xfa, 0xfa, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x8e, 0xe9, - 0x48, 0xe5, 0x19, 0x8d, 0x00, 0x00, + 0xc5, 0x40, 0x4f, 0x61, 0xce, 0xc6, 0x81, 0xd7, 0xf7, 0x5b, 0x78, 0x0b, 0x3b, 0x6d, 0xec, 0x17, + 0xb3, 0x37, 0x33, 0x77, 0xa6, 0x57, 0x17, 0xef, 0x32, 0xa6, 0xe9, 0x85, 0xd5, 0xab, 0x67, 0x83, + 0x32, 0xf2, 0x39, 0x2c, 0x22, 0xb6, 0x75, 0xc9, 0x8e, 0x91, 0x41, 0x3f, 0xc0, 0x6c, 0x0d, 0xfb, + 0x61, 0xa5, 0x1f, 0x1e, 0x7b, 0xbe, 0x1b, 0xbe, 0x2c, 0x8e, 0x51, 0xba, 0x57, 0x39, 0x5d, 0xad, + 0xac, 0xb1, 0x5a, 0x5d, 0x3e, 0x1b, 0x94, 0x8b, 0x64, 0x82, 0x9b, 0x8e, 0x80, 0x6a, 0xe4, 0x75, + 0x62, 0xe8, 0x3b, 0x98, 0xa9, 0x13, 0x76, 0xb5, 0xf6, 0xbd, 0x13, 0xdc, 0x0d, 0x8a, 0xe3, 0x5a, + 0xa7, 0xd5, 0xa2, 0xc6, 0x6a, 0xf5, 0xfa, 0xd9, 0xa0, 0xbc, 0x14, 0x50, 0x58, 0x33, 0xa4, 0x40, + 0x8d, 0xb4, 0x46, 0x09, 0xfd, 0x08, 0x73, 0x7b, 0xbe, 0xf7, 0xdc, 0x0d, 0x5c, 0xaf, 0x4b, 0x41, + 0xc5, 0x09, 0x4a, 0x7b, 0x89, 0xd3, 0xd6, 0x0b, 0x1b, 0xab, 0xd5, 0x1b, 0x67, 0x83, 0xf2, 0xb5, + 0x9e, 0x80, 0xb2, 0x06, 0x74, 0xce, 0xe8, 0x28, 0x68, 0x1f, 0xa6, 0x6b, 0x9d, 0x7e, 0x10, 0x62, + 0x7f, 0xd7, 0x39, 0xc5, 0xc5, 0x49, 0x4a, 0x7e, 0x41, 0xf0, 0x25, 0x2a, 0x69, 0xac, 0x56, 0x4b, + 0x67, 0x83, 0xf2, 0xd5, 0x16, 0x03, 0x35, 0xbb, 0xce, 0xa9, 0xce, 0x72, 0x95, 0x0c, 0xfa, 0x04, + 0xc6, 0x0f, 0x02, 0xec, 0x17, 0x73, 0x94, 0xdc, 0x2c, 0x27, 0x47, 0x40, 0x8d, 0x55, 0x36, 0xff, + 0xfd, 0x00, 0xfb, 0x1a, 0x3e, 0x45, 0x20, 0x88, 0xb6, 0xd7, 0xc1, 0xc5, 0xbc, 0x86, 0x48, 0x40, + 0x8d, 0x8f, 0x18, 0xa2, 0xef, 0x75, 0xf4, 0x86, 0x29, 0x02, 0xda, 0x86, 0x3c, 0x69, 0x39, 0xe8, + 0x39, 0x2d, 0x5c, 0x04, 0x8a, 0x5d, 0xe0, 0xd8, 0x12, 0x5e, 0x5d, 0x3a, 0x1b, 0x94, 0xe7, 0xbb, + 0xe2, 0x53, 0xa3, 0x12, 0x61, 0xa3, 0x6f, 0x60, 0xb2, 0x8e, 0xfd, 0xe7, 0xd8, 0x2f, 0x4e, 0x53, + 0x3a, 0x97, 0xc5, 0x44, 0x52, 0x60, 0x63, 0xb5, 0xba, 0x70, 0x36, 0x28, 0x17, 0x02, 0xfa, 0xa5, + 0xd1, 0xe0, 0x68, 0x44, 0xda, 0x6c, 0xfc, 0x1c, 0xfb, 0x01, 0xde, 0xef, 0x77, 0xbb, 0xb8, 0x53, + 0x9c, 0xd1, 0xa4, 0x4d, 0x2b, 0x13, 0xd2, 0xe6, 0x33, 0x60, 0x33, 0xa4, 0x50, 0x5d, 0xda, 0x34, + 0x04, 0x74, 0x0c, 0x05, 0xf6, 0x57, 0xcd, 0xeb, 0x76, 0x71, 0x8b, 0x2c, 0xa9, 0xe2, 0x2c, 0x6d, + 0xe0, 0x1a, 0x6f, 0x20, 0x5e, 0xdc, 0x58, 0xad, 0x96, 0xcf, 0x06, 0xe5, 0xeb, 0x8c, 0x76, 0xb3, + 0x25, 0x0b, 0xb4, 0x66, 0x12, 0x54, 0xc9, 0x38, 0x2a, 0xad, 0x16, 0x0e, 0x02, 0x1b, 0xff, 0xa2, + 0x8f, 0x83, 0xb0, 0x38, 0xa7, 0x8d, 0x43, 0x2b, 0x6b, 0xac, 0xb1, 0x71, 0x38, 0x14, 0xd8, 0xf4, + 0x19, 0x54, 0x1f, 0x87, 0x86, 0x80, 0xf6, 0x00, 0x2a, 0xbd, 0x5e, 0x1d, 0x07, 0x44, 0x18, 0x8b, + 0x97, 0x29, 0xe9, 0x79, 0x4e, 0xfa, 0x29, 0x3e, 0xe4, 0x05, 0x8d, 0xd5, 0xea, 0xb5, 0xb3, 0x41, + 0x79, 0xd1, 0xe9, 0xf5, 0x9a, 0x01, 0x03, 0x69, 0x44, 0x15, 0x1a, 0x8c, 0xef, 0xa7, 0x5e, 0x88, + 0xb9, 0x28, 0x16, 0x0b, 0x31, 0xbe, 0x2b, 0x65, 0xa2, 0xbf, 0x3e, 0x05, 0x36, 0xb9, 0x58, 0xc7, + 0xf9, 0xae, 0x20, 0x90, 0xb5, 0xb8, 0xee, 0x84, 0xce, 0xa1, 0x13, 0x60, 0x2e, 0x1e, 0x57, 0xb4, + 0xb5, 0xa8, 0x17, 0x36, 0xd6, 0xd8, 0x5a, 0x6c, 0x73, 0x68, 0x33, 0x45, 0x5e, 0x62, 0xf4, 0x08, + 0x47, 0xa2, 0x81, 0x17, 0xd1, 0x08, 0x8e, 0xbc, 0xc0, 0x87, 0xe9, 0x1c, 0x89, 0xaa, 0xa2, 0x2d, + 0xc8, 0x3d, 0xc5, 0x87, 0x4c, 0x73, 0xcc, 0x53, 0x7a, 0x57, 0x22, 0x7a, 0x4c, 0x67, 0xac, 0xb1, + 0x55, 0x41, 0xa8, 0x25, 0xb5, 0x85, 0xc4, 0x46, 0xbf, 0x97, 0x81, 0x25, 0xb1, 0xc2, 0x71, 0xf8, + 0xc2, 0xf3, 0x4f, 0xdc, 0xee, 0x51, 0xcd, 0xeb, 0x3e, 0x73, 0x8f, 0x8a, 0x0b, 0x94, 0xf2, 0xcd, + 0x98, 0xd2, 0x88, 0xd5, 0x6a, 0xac, 0x56, 0x7f, 0x76, 0x36, 0x28, 0xdf, 0x92, 0x0a, 0x44, 0x96, + 0x13, 0x81, 0x7c, 0xe6, 0x1e, 0x69, 0x0d, 0x9b, 0xda, 0x42, 0x7f, 0x3d, 0x03, 0x57, 0xf9, 0xe8, + 0x6c, 0xdc, 0xf2, 0xfc, 0x76, 0xd4, 0x8d, 0x45, 0xda, 0x8d, 0xb2, 0x5c, 0xad, 0x69, 0x95, 0x1a, + 0xab, 0xd5, 0xdb, 0x67, 0x83, 0xb2, 0xc5, 0x19, 0xd7, 0xf4, 0x45, 0x71, 0x5a, 0x27, 0x0c, 0x0d, + 0x11, 0x49, 0x20, 0xca, 0x7f, 0xcf, 0xc7, 0xcf, 0xb0, 0x8f, 0xbb, 0x2d, 0x5c, 0xbc, 0xaa, 0x49, + 0x82, 0x5e, 0x28, 0xb4, 0x32, 0xd9, 0x4a, 0x9a, 0x3d, 0x09, 0xd6, 0x25, 0x41, 0x47, 0x41, 0xbf, + 0x00, 0xc4, 0x19, 0x50, 0xe9, 0xb7, 0xdd, 0x90, 0x0f, 0x70, 0x89, 0xb6, 0x72, 0x5d, 0xe7, 0xb3, + 0x52, 0xa1, 0xb1, 0x5a, 0xb5, 0xce, 0x06, 0xe5, 0x15, 0xc1, 0x62, 0x87, 0x14, 0xa5, 0x0d, 0x2c, + 0x85, 0x38, 0xd1, 0xbc, 0x3b, 0x5e, 0xeb, 0xa4, 0x58, 0xd4, 0x34, 0x2f, 0x01, 0x09, 0x95, 0xdd, + 0xf1, 0x5a, 0x27, 0xba, 0xe6, 0x25, 0xa5, 0x28, 0x84, 0x79, 0x3e, 0x4b, 0x36, 0x0e, 0x42, 0xdf, + 0xa5, 0xba, 0x23, 0x28, 0x5e, 0xa3, 0x74, 0x96, 0x85, 0x0e, 0x4e, 0xd6, 0x68, 0x7c, 0xc8, 0x7a, + 0xcb, 0x05, 0xa1, 0xe9, 0x2b, 0x65, 0x5a, 0x33, 0x69, 0xe4, 0xd1, 0x5f, 0x81, 0xc5, 0xa7, 0x6e, + 0xb7, 0xed, 0xbd, 0x08, 0xd6, 0x71, 0x70, 0x12, 0x7a, 0xbd, 0x3a, 0xb3, 0xfc, 0x8a, 0x25, 0xda, + 0xee, 0x8a, 0x10, 0xf3, 0xb4, 0x3a, 0x8d, 0xb5, 0xea, 0x3b, 0x67, 0x83, 0xf2, 0x5b, 0x2f, 0x58, + 0x61, 0xb3, 0xcd, 0x4a, 0x9b, 0xdc, 0x78, 0xd4, 0x1a, 0x4f, 0x6f, 0x85, 0x88, 0x80, 0x5e, 0x50, + 0xbc, 0xae, 0x89, 0x80, 0x5e, 0x28, 0x94, 0x41, 0xac, 0x41, 0x5d, 0x04, 0x74, 0x14, 0xb4, 0x09, + 0x39, 0xa1, 0x1e, 0x8a, 0xcb, 0xda, 0xd2, 0x15, 0xe0, 0xc6, 0x1a, 0xb3, 0x80, 0x84, 0x8a, 0xd1, + 0x57, 0xae, 0xa8, 0x85, 0x76, 0x20, 0x4f, 0x75, 0x24, 0x55, 0x59, 0x37, 0x28, 0x25, 0x24, 0x04, + 0x55, 0xc0, 0x1b, 0x6b, 0xd5, 0xe2, 0xd9, 0xa0, 0xbc, 0xc0, 0xb4, 0x6c, 0x42, 0x51, 0x45, 0x04, + 0xd0, 0x1a, 0x8c, 0x55, 0x7a, 0xbd, 0xe2, 0x0a, 0xa5, 0x33, 0x13, 0xd1, 0x69, 0xac, 0x55, 0xaf, + 0x9c, 0x0d, 0xca, 0xb3, 0x4e, 0x4f, 0x1f, 0x16, 0xa9, 0x5d, 0x05, 0xc8, 0x09, 0x83, 0xec, 0xe1, + 0x78, 0x6e, 0xaa, 0x90, 0xb3, 0xb6, 0x60, 0xe2, 0xa9, 0x13, 0xb6, 0x8e, 0xd1, 0x37, 0x30, 0xf1, + 0xc8, 0xed, 0xb6, 0x83, 0x62, 0xe6, 0xe6, 0x18, 0xdd, 0xb3, 0x99, 0xb5, 0x48, 0x0b, 0x49, 0x41, + 0x75, 0xe9, 0x97, 0x83, 0xf2, 0xa5, 0xb3, 0x41, 0xf9, 0xf2, 0x09, 0xa9, 0xa6, 0x98, 0x8c, 0x0c, + 0xcf, 0xfa, 0xb7, 0x59, 0xc8, 0xcb, 0xda, 0x68, 0x19, 0xc6, 0xc9, 0xff, 0xd4, 0xf6, 0xcc, 0x57, + 0x73, 0x67, 0x83, 0xf2, 0x38, 0xc1, 0xb3, 0x29, 0x14, 0xad, 0xc2, 0xf4, 0x8e, 0xe7, 0xb4, 0xeb, + 0xb8, 0xe5, 0xe3, 0x30, 0xa0, 0xc6, 0x65, 0xae, 0x5a, 0x38, 0x1b, 0x94, 0x67, 0x3a, 0x9e, 0xd3, + 0x6e, 0x06, 0x0c, 0x6e, 0xab, 0x95, 0x08, 0x45, 0x6a, 0x19, 0x8d, 0x45, 0x14, 0x89, 0x05, 0x61, + 0x53, 0x28, 0x7a, 0x08, 0x93, 0x0f, 0xdc, 0x0e, 0xd9, 0x6b, 0xc6, 0x69, 0xff, 0x97, 0xe3, 0xfd, + 0xbf, 0xcb, 0x8a, 0x37, 0xba, 0xa1, 0xff, 0x92, 0x19, 0x0e, 0xcf, 0x28, 0x40, 0x19, 0x08, 0xa7, + 0x80, 0xee, 0xc3, 0x54, 0xbd, 0x7f, 0x48, 0xbb, 0x3f, 0x41, 0x1b, 0xa3, 0xb3, 0x1b, 0xf4, 0x0f, + 0x9b, 0x64, 0x08, 0x0a, 0x82, 0xa8, 0x56, 0xfa, 0x0c, 0xa6, 0x15, 0xf2, 0xa8, 0x00, 0x63, 0x27, + 0xf8, 0x25, 0x1b, 0xbb, 0x4d, 0xfe, 0x44, 0x0b, 0x30, 0xf1, 0xdc, 0xe9, 0xf4, 0x31, 0x1d, 0x6a, + 0xde, 0x66, 0x1f, 0x9f, 0x67, 0x3f, 0xcd, 0x58, 0xff, 0x75, 0x1c, 0x0a, 0x5b, 0x5e, 0x10, 0x12, + 0x4b, 0x56, 0x6e, 0xc9, 0xb7, 0x60, 0x92, 0xc0, 0xb6, 0xd7, 0x39, 0xff, 0xa6, 0xcf, 0x06, 0xe5, + 0xa9, 0x63, 0x2f, 0x08, 0x9b, 0x6e, 0xdb, 0xe6, 0x45, 0xe8, 0x5d, 0xc8, 0xed, 0x7a, 0x6d, 0x4c, + 0x99, 0x42, 0xc9, 0x56, 0x67, 0xcf, 0x06, 0xe5, 0x7c, 0xd7, 0x6b, 0x63, 0x6a, 0x15, 0xda, 0xb2, + 0x18, 0x35, 0xb8, 0x35, 0xc7, 0x78, 0x57, 0x25, 0xbc, 0x23, 0xe6, 0xdb, 0x6f, 0x06, 0xe5, 0x8f, + 0x2f, 0x70, 0xdc, 0xb8, 0x5b, 0x7f, 0x19, 0x84, 0xf8, 0x94, 0x50, 0xe2, 0xc6, 0xde, 0x53, 0x58, + 0xa8, 0xb4, 0xdb, 0x2e, 0xc3, 0xd8, 0xf3, 0xdd, 0x6e, 0xcb, 0xed, 0x39, 0x9d, 0x80, 0xce, 0x41, + 0xbe, 0x7a, 0xeb, 0x6c, 0x50, 0x2e, 0x3b, 0xb2, 0xbc, 0xd9, 0x93, 0x15, 0x14, 0x1e, 0xa6, 0x12, + 0x40, 0x6b, 0x90, 0x5b, 0xdf, 0xad, 0x53, 0x53, 0xb0, 0x38, 0x41, 0x89, 0xd1, 0xcd, 0xb1, 0xdd, + 0x0d, 0xe8, 0xd0, 0x54, 0x02, 0xb2, 0x22, 0xfa, 0x18, 0x66, 0xf6, 0xfa, 0x87, 0x1d, 0xb7, 0xb5, + 0xbf, 0x53, 0x7f, 0x84, 0x5f, 0x52, 0x1b, 0x7a, 0x86, 0xa9, 0xcc, 0x1e, 0x85, 0x37, 0xc3, 0x4e, + 0xd0, 0x3c, 0xc1, 0x2f, 0x6d, 0xad, 0x5e, 0x84, 0x57, 0xaf, 0x6f, 0x11, 0xbc, 0xa9, 0x04, 0x5e, + 0x10, 0x1c, 0xab, 0x78, 0xac, 0x1e, 0xba, 0x07, 0xc0, 0x2c, 0x93, 0x4a, 0xbb, 0xcd, 0x4c, 0xec, + 0x7c, 0xf5, 0xf2, 0xd9, 0xa0, 0x3c, 0xcd, 0x6d, 0x19, 0xa7, 0xdd, 0xf6, 0x6d, 0xa5, 0x0a, 0xaa, + 0x41, 0xce, 0xf6, 0x18, 0x83, 0xb9, 0x61, 0x7d, 0x59, 0x1a, 0xd6, 0x0c, 0xcc, 0x8f, 0x52, 0xfc, + 0x4b, 0x1d, 0xa5, 0xa8, 0x81, 0xca, 0x30, 0xb5, 0xeb, 0xd5, 0x9c, 0xd6, 0x31, 0x33, 0xaf, 0x73, + 0xd5, 0x89, 0xb3, 0x41, 0x39, 0xf3, 0xdb, 0xb6, 0x80, 0x5a, 0xff, 0x22, 0x07, 0x05, 0x62, 0xc3, + 0x6b, 0x12, 0xf5, 0x3e, 0xe4, 0x59, 0xdf, 0x1f, 0x71, 0xc1, 0x9c, 0xa9, 0xce, 0x9d, 0x0d, 0xca, + 0xc0, 0x07, 0x48, 0x06, 0x17, 0x55, 0x40, 0x77, 0x20, 0x47, 0x28, 0x74, 0x23, 0xd1, 0x9a, 0x39, + 0x1b, 0x94, 0x73, 0x7d, 0x0e, 0xb3, 0x65, 0x29, 0xaa, 0xc3, 0xd4, 0xc6, 0x4f, 0x3d, 0xd7, 0xc7, + 0x01, 0x3f, 0xca, 0x95, 0xee, 0xb2, 0x13, 0xfb, 0x5d, 0x71, 0x62, 0xbf, 0xbb, 0x2f, 0x4e, 0xec, + 0xd5, 0x1b, 0x5c, 0x85, 0x5c, 0xc1, 0x0c, 0x25, 0x1a, 0xdf, 0xef, 0xff, 0x59, 0x39, 0x63, 0x0b, + 0x4a, 0xe8, 0x7d, 0x98, 0x7c, 0xe0, 0xf9, 0xa7, 0x4e, 0x48, 0x4f, 0x70, 0x79, 0xbe, 0x5c, 0x29, + 0x44, 0x5b, 0xae, 0x14, 0x82, 0x1e, 0xc0, 0x9c, 0xed, 0xf5, 0x43, 0xbc, 0xef, 0x09, 0x73, 0x93, + 0xad, 0xda, 0x95, 0xb3, 0x41, 0xb9, 0xe4, 0x93, 0x92, 0x66, 0xe8, 0x25, 0x0d, 0x4b, 0x3b, 0x86, + 0x85, 0x36, 0x60, 0x4e, 0x33, 0x8c, 0x83, 0xe2, 0x24, 0x95, 0x3c, 0x66, 0x34, 0x68, 0xe6, 0xb4, + 0x2a, 0x7f, 0x31, 0x24, 0xb4, 0x0b, 0x57, 0x1e, 0xf5, 0x0f, 0xb1, 0xdf, 0xc5, 0x21, 0x0e, 0x44, + 0x8f, 0xa6, 0x68, 0x8f, 0x6e, 0x9e, 0x0d, 0xca, 0xcb, 0x27, 0xb2, 0x30, 0xa5, 0x4f, 0x49, 0x54, + 0x84, 0xe1, 0x32, 0xef, 0xa8, 0xdc, 0x86, 0x72, 0xdc, 0x9c, 0x66, 0x2a, 0x2e, 0x56, 0x5a, 0xbd, + 0xc5, 0xb9, 0x7c, 0x5d, 0x8e, 0x3d, 0xb9, 0x31, 0xd9, 0x71, 0x9a, 0x64, 0xc5, 0x49, 0x6d, 0x92, + 0xa7, 0xbd, 0x65, 0x87, 0x34, 0xa1, 0x4d, 0x54, 0x59, 0x94, 0x7a, 0x65, 0x07, 0x26, 0x0e, 0x02, + 0xe7, 0x88, 0x49, 0xe2, 0xdc, 0xea, 0x5b, 0xbc, 0x47, 0x71, 0xe9, 0xa3, 0xe7, 0x7a, 0x5a, 0xb1, + 0x3a, 0x4f, 0x76, 0x90, 0x3e, 0xf9, 0x53, 0xdd, 0x41, 0x68, 0x19, 0xfa, 0x16, 0x80, 0xf7, 0x8a, + 0xec, 0x6c, 0xd3, 0x7c, 0xaf, 0xd5, 0x06, 0x49, 0x36, 0xb1, 0x15, 0x3e, 0xbe, 0xab, 0x72, 0x7c, + 0xda, 0x5e, 0x67, 0x2b, 0x44, 0xd0, 0x37, 0x30, 0x43, 0xd5, 0x95, 0x98, 0xd1, 0x19, 0x3a, 0xa3, + 0xf4, 0xe8, 0x4f, 0x14, 0x60, 0xda, 0x7c, 0x6a, 0x08, 0xe8, 0xaf, 0xc2, 0x22, 0x27, 0x17, 0x33, + 0x33, 0x66, 0xb9, 0x59, 0xa5, 0x75, 0x4f, 0xaf, 0x53, 0x7d, 0x8f, 0xf7, 0xd4, 0x92, 0x3d, 0x35, + 0x1a, 0x1e, 0x76, 0x7a, 0x33, 0xd6, 0x77, 0x90, 0x97, 0xcc, 0x43, 0x53, 0x30, 0x56, 0xe9, 0x74, + 0x0a, 0x97, 0xc8, 0x1f, 0xf5, 0xfa, 0x56, 0x21, 0x83, 0xe6, 0x00, 0x22, 0x89, 0x29, 0x64, 0xd1, + 0x4c, 0x64, 0xac, 0x14, 0xc6, 0x68, 0xfd, 0x5e, 0xaf, 0x30, 0x8e, 0x50, 0xdc, 0x4a, 0x2a, 0x4c, + 0x58, 0xff, 0x3d, 0x93, 0x10, 0x2c, 0xb2, 0x2f, 0x73, 0xc3, 0x8a, 0xca, 0x01, 0xdb, 0x7c, 0xe8, + 0xbe, 0xcc, 0x4d, 0x32, 0xb6, 0xb1, 0xa8, 0x95, 0x88, 0xae, 0xd8, 0x23, 0x3c, 0x68, 0x79, 0x1d, + 0x55, 0x57, 0xf4, 0x38, 0xcc, 0x96, 0xa5, 0x68, 0x55, 0xd1, 0x2a, 0x63, 0xd1, 0xc6, 0x2a, 0xb4, + 0x8a, 0x2a, 0x61, 0x52, 0xbf, 0xac, 0x2a, 0xd6, 0xd7, 0x78, 0x84, 0x93, 0x22, 0xd1, 0xb2, 0x9e, + 0xd5, 0x37, 0xcc, 0x19, 0xfa, 0x22, 0x61, 0x2c, 0xb2, 0x11, 0x52, 0xa1, 0x8c, 0x4d, 0x4d, 0xc2, + 0x0e, 0x2c, 0xc3, 0xc4, 0x8e, 0x77, 0xe4, 0x76, 0xf9, 0x20, 0xf3, 0x67, 0x83, 0xf2, 0x44, 0x87, + 0x00, 0x6c, 0x06, 0xb7, 0xfe, 0x4f, 0x46, 0x95, 0x5f, 0x69, 0xaf, 0x64, 0x52, 0xed, 0x95, 0xf7, + 0x21, 0xcf, 0x0f, 0x35, 0xdb, 0xeb, 0x9c, 0x22, 0xd5, 0xc7, 0xe2, 0x3c, 0xe4, 0xb6, 0xed, 0xa8, + 0x02, 0xd9, 0x69, 0x98, 0x72, 0xa6, 0x3b, 0xcd, 0x58, 0xb4, 0xd3, 0x70, 0xf5, 0xcd, 0x76, 0x9a, + 0xa8, 0x0a, 0x99, 0x48, 0xd5, 0x9b, 0x34, 0x1e, 0x4d, 0xa4, 0xea, 0x37, 0xd2, 0x7d, 0x45, 0x9f, + 0x03, 0x54, 0x9e, 0xd6, 0x89, 0xf4, 0x57, 0xec, 0x5d, 0xae, 0x43, 0xa9, 0xab, 0xc9, 0x79, 0x11, + 0x34, 0xe9, 0x6a, 0x71, 0x7c, 0x75, 0x4b, 0x52, 0x6a, 0x5b, 0x1d, 0x98, 0xdb, 0xc4, 0x21, 0x99, + 0x35, 0xb1, 0xe1, 0x0c, 0x1f, 0xfe, 0x97, 0x30, 0xfd, 0xd4, 0x0d, 0x8f, 0x75, 0x03, 0x90, 0x36, + 0xf6, 0xc2, 0x0d, 0x8f, 0x85, 0x01, 0xa8, 0x34, 0xa6, 0x56, 0xb7, 0x36, 0xe0, 0x32, 0x6f, 0x4d, + 0xee, 0x6f, 0xab, 0x3a, 0xc1, 0x4c, 0x64, 0x51, 0xaa, 0x04, 0x75, 0x32, 0x38, 0xae, 0xf0, 0x51, + 0x3d, 0xb1, 0x05, 0x30, 0x6b, 0xd8, 0xe4, 0x69, 0xa1, 0x82, 0x13, 0xdb, 0x1a, 0xe2, 0x1b, 0x82, + 0x75, 0x00, 0xb3, 0x7b, 0x9d, 0xfe, 0x91, 0xdb, 0x25, 0x02, 0x5a, 0xc7, 0xbf, 0x40, 0xeb, 0x00, + 0x11, 0x80, 0xb7, 0x20, 0xdc, 0x0b, 0x51, 0x41, 0x63, 0x8d, 0x4f, 0x31, 0x85, 0x50, 0x1d, 0x6e, + 0x2b, 0x78, 0xd6, 0xdf, 0x1b, 0x03, 0xc4, 0xdb, 0xa8, 0x87, 0x4e, 0x88, 0xeb, 0x38, 0x24, 0xdb, + 0xc5, 0x55, 0xc8, 0x4a, 0xb3, 0x71, 0xf2, 0x6c, 0x50, 0xce, 0xba, 0x6d, 0x3b, 0xbb, 0xbd, 0x8e, + 0x3e, 0x84, 0x09, 0x5a, 0x8d, 0xf2, 0x7a, 0x4e, 0xb6, 0xa7, 0x52, 0x60, 0x32, 0x1d, 0x90, 0x3f, + 0x6d, 0x56, 0x19, 0x7d, 0x04, 0xf9, 0x75, 0xdc, 0xc1, 0x47, 0x4e, 0xe8, 0x09, 0xb9, 0x63, 0x86, + 0x98, 0x00, 0x2a, 0x53, 0x14, 0xd5, 0x24, 0x1b, 0xb8, 0x8d, 0x9d, 0xc0, 0xeb, 0xaa, 0x1b, 0xb8, + 0x4f, 0x21, 0xea, 0x06, 0xce, 0xea, 0xa0, 0x3f, 0xc8, 0xc0, 0x74, 0xa5, 0xdb, 0xe5, 0x06, 0x4e, + 0xc0, 0x5d, 0xab, 0x8b, 0x77, 0xa5, 0x67, 0x7c, 0xc7, 0x39, 0xc4, 0x9d, 0x06, 0x31, 0x99, 0x83, + 0xea, 0x0f, 0x44, 0xa7, 0xfe, 0xe9, 0xa0, 0xfc, 0xc5, 0xab, 0x38, 0xdb, 0xef, 0xee, 0xfb, 0x8e, + 0x1b, 0x06, 0xd4, 0x8f, 0x15, 0x35, 0xa8, 0x8a, 0x99, 0xd2, 0x0f, 0xf4, 0x2e, 0x4c, 0x10, 0xf9, + 0x16, 0x76, 0x00, 0x9d, 0x6c, 0xb2, 0x0e, 0xb4, 0xc3, 0x0f, 0xad, 0x61, 0xdd, 0x82, 0x3c, 0xe7, + 0xe4, 0xf6, 0xba, 0x69, 0x0a, 0xac, 0x75, 0xb8, 0x41, 0xad, 0x38, 0x4c, 0x24, 0x97, 0x7a, 0x73, + 0xb8, 0x24, 0x46, 0x66, 0xff, 0x14, 0x05, 0x4b, 0x6c, 0x3a, 0x21, 0xd4, 0x1b, 0x64, 0x8b, 0x12, + 0xab, 0x06, 0xcb, 0x9b, 0x38, 0xb4, 0x71, 0x80, 0xc3, 0x3d, 0x27, 0x08, 0x5e, 0x78, 0x7e, 0x9b, + 0x16, 0x5d, 0x88, 0xc8, 0xdf, 0xca, 0x40, 0xb9, 0xe6, 0x63, 0x32, 0xd3, 0x46, 0x42, 0xc3, 0x57, + 0xf0, 0x32, 0xbf, 0x5c, 0xc8, 0x46, 0xa5, 0x84, 0xd7, 0xfc, 0x02, 0xe1, 0x1d, 0x18, 0xdb, 0xdf, + 0xdf, 0xa1, 0x12, 0x33, 0x46, 0x19, 0x37, 0x16, 0x86, 0x9d, 0xdf, 0x0c, 0xca, 0xb9, 0xf5, 0x3e, + 0xbb, 0x7c, 0xb0, 0x49, 0xb9, 0xf5, 0x0c, 0x16, 0x6d, 0xdc, 0xc5, 0x2f, 0x9c, 0xc3, 0x0e, 0xd6, + 0xcc, 0xd5, 0x32, 0x4c, 0x30, 0x67, 0x59, 0x62, 0x08, 0x0c, 0xae, 0xdb, 0xb3, 0xd9, 0x11, 0xf6, + 0xac, 0xf5, 0x87, 0x19, 0x28, 0xb0, 0xe1, 0x56, 0xbd, 0xf0, 0x7c, 0xe3, 0xe3, 0x23, 0xc8, 0x0e, + 0x1f, 0x01, 0xba, 0x1d, 0x71, 0x7b, 0x2c, 0xda, 0xfc, 0x68, 0x57, 0x89, 0x0e, 0x17, 0x85, 0x64, + 0x40, 0x4c, 0x96, 0xd8, 0xd1, 0x88, 0x0e, 0x88, 0xca, 0x92, 0x90, 0xa0, 0x3f, 0xce, 0xc2, 0x15, + 0xa5, 0x8b, 0x41, 0xcf, 0xeb, 0x06, 0x98, 0x9c, 0xf1, 0x88, 0xb0, 0x28, 0xfd, 0xa4, 0x67, 0x3c, + 0xb2, 0x65, 0x36, 0x23, 0x4b, 0x9c, 0x76, 0xf8, 0x5d, 0x72, 0xb8, 0xe8, 0x24, 0x8e, 0x83, 0x54, + 0x71, 0xb3, 0xaa, 0xa2, 0xf8, 0xdc, 0x9d, 0xbe, 0x07, 0x39, 0xfa, 0x27, 0x61, 0xc4, 0xb8, 0x99, + 0x11, 0xb2, 0x12, 0x72, 0x01, 0x1e, 0x7a, 0x6e, 0xf7, 0x31, 0x0e, 0x8f, 0x3d, 0x71, 0x78, 0xde, + 0x26, 0x4a, 0xec, 0x2f, 0x79, 0x6e, 0xb7, 0x79, 0x4a, 0xc1, 0x17, 0x3d, 0x74, 0x46, 0x04, 0x6d, + 0x85, 0xb8, 0x75, 0x1f, 0x0a, 0x44, 0xdf, 0x9c, 0x7f, 0x46, 0xad, 0x05, 0x40, 0x9b, 0x38, 0xac, + 0x7a, 0xda, 0xc6, 0x61, 0xcd, 0xc2, 0xf4, 0x9e, 0xdb, 0x3d, 0x12, 0x9f, 0xff, 0x2e, 0x0b, 0x33, + 0xec, 0x9b, 0xcf, 0x40, 0x6c, 0x27, 0xcd, 0x9c, 0x67, 0x27, 0xfd, 0x14, 0x66, 0xb9, 0x3b, 0x07, + 0xfb, 0xd4, 0x85, 0xcc, 0xe6, 0x83, 0x9e, 0x28, 0x99, 0x57, 0xa7, 0xf9, 0x9c, 0x95, 0xd8, 0x7a, + 0x45, 0xb4, 0x03, 0x73, 0x0c, 0xf0, 0x00, 0x3b, 0x61, 0x3f, 0x3a, 0x55, 0x5d, 0xe6, 0x76, 0xa6, + 0x00, 0x33, 0x65, 0xc4, 0x69, 0x3d, 0xe3, 0x40, 0x3b, 0x86, 0x8b, 0xbe, 0x81, 0xcb, 0x7b, 0xbe, + 0xf7, 0xd3, 0x4b, 0xc5, 0x76, 0x60, 0xfa, 0x78, 0x91, 0x1c, 0xc2, 0x7a, 0xa4, 0xa8, 0xa9, 0x5a, + 0x10, 0xf1, 0xda, 0x44, 0xa6, 0xb6, 0x83, 0xaa, 0xe7, 0xbb, 0xdd, 0x23, 0x3a, 0x9b, 0x39, 0x26, + 0x53, 0x6e, 0xd0, 0x3c, 0xa4, 0x40, 0x5b, 0x16, 0x5b, 0x7f, 0x34, 0x06, 0x39, 0xd9, 0xf0, 0x5d, + 0xd5, 0x2c, 0xe5, 0x9b, 0x31, 0x5d, 0x9e, 0xd1, 0xe1, 0xc7, 0x56, 0x6a, 0xa0, 0x6b, 0xcc, 0x99, + 0xc5, 0xcc, 0x80, 0x29, 0x22, 0x63, 0x4e, 0xaf, 0x47, 0x5d, 0x56, 0x44, 0x99, 0xae, 0x57, 0x29, + 0x17, 0x72, 0x4c, 0x99, 0xb6, 0x0f, 0xed, 0xec, 0x7a, 0x95, 0xcc, 0xf5, 0x93, 0xed, 0xf5, 0x1a, + 0x1d, 0x50, 0x8e, 0xcd, 0xb5, 0xe7, 0xb6, 0x5b, 0x36, 0x85, 0x92, 0xd2, 0x7a, 0xe5, 0xf1, 0x0e, + 0xef, 0x34, 0x2d, 0x0d, 0x9c, 0xd3, 0x8e, 0x4d, 0xa1, 0xc4, 0x0e, 0x64, 0x7b, 0x74, 0xcd, 0xeb, + 0x86, 0xbe, 0xd7, 0x09, 0xa8, 0xab, 0x20, 0xa7, 0x6d, 0xe7, 0x2d, 0x5e, 0x64, 0xc7, 0xaa, 0xa2, + 0xa7, 0xb0, 0x54, 0x69, 0x3f, 0x77, 0xba, 0x2d, 0xdc, 0x66, 0x25, 0x4f, 0x3d, 0xff, 0xe4, 0x59, + 0xc7, 0x7b, 0x11, 0xd0, 0x53, 0x5e, 0x8e, 0x9f, 0x17, 0x79, 0x95, 0x26, 0x27, 0xf7, 0x42, 0x54, + 0xb2, 0x4d, 0xd8, 0x44, 0x45, 0xd4, 0x3a, 0x5e, 0xbf, 0x4d, 0x8f, 0x77, 0x39, 0xa6, 0x22, 0x5a, + 0x04, 0x60, 0x33, 0x38, 0xe1, 0xd2, 0x56, 0xfd, 0x31, 0x3d, 0x9d, 0x71, 0x2e, 0x1d, 0x07, 0xa7, + 0x36, 0x81, 0xa1, 0x77, 0x60, 0x4a, 0x98, 0xb4, 0xcc, 0x29, 0x40, 0x3d, 0x46, 0xc2, 0x94, 0x15, + 0x65, 0xd6, 0x07, 0x70, 0x85, 0x2d, 0x9a, 0x73, 0x5b, 0x6a, 0xd6, 0x1e, 0x40, 0x1d, 0x9f, 0x3a, + 0xbd, 0x63, 0x8f, 0x4c, 0x6c, 0x55, 0xfd, 0xe2, 0xa6, 0x0b, 0x92, 0x8e, 0x7e, 0x5e, 0xd0, 0x58, + 0x13, 0xb6, 0xac, 0xa8, 0x69, 0x2b, 0x58, 0xd6, 0x7f, 0xc9, 0x02, 0xa2, 0x0e, 0xef, 0x7a, 0xe8, + 0x63, 0xe7, 0x54, 0x74, 0xe3, 0x33, 0x98, 0x61, 0xfa, 0x8f, 0x81, 0x69, 0x77, 0x88, 0x5d, 0xc4, + 0x04, 0x5f, 0x2d, 0xda, 0xba, 0x64, 0x6b, 0x55, 0x09, 0xaa, 0x8d, 0x83, 0xfe, 0xa9, 0x40, 0xcd, + 0x6a, 0xa8, 0x6a, 0x11, 0x41, 0x55, 0xbf, 0xd1, 0x37, 0x30, 0x57, 0xf3, 0x4e, 0x7b, 0x84, 0x27, + 0x1c, 0x79, 0x8c, 0x5b, 0x1f, 0xbc, 0x5d, 0xad, 0x70, 0xeb, 0x92, 0x1d, 0xab, 0x8e, 0x76, 0x61, + 0xfe, 0x41, 0xa7, 0x1f, 0x1c, 0x57, 0xba, 0xed, 0x5a, 0xc7, 0x0b, 0x04, 0x95, 0x71, 0xee, 0x0c, + 0xe1, 0xcb, 0x36, 0x59, 0x63, 0xeb, 0x92, 0x9d, 0x86, 0x88, 0xde, 0xe1, 0xb7, 0xf7, 0xdc, 0x0a, + 0x9a, 0xbd, 0xcb, 0x2f, 0xf7, 0x9f, 0x74, 0xf1, 0x93, 0x67, 0x5b, 0x97, 0x6c, 0x56, 0x5a, 0xcd, + 0xc3, 0x94, 0x50, 0x59, 0xf7, 0xe0, 0x8a, 0xc2, 0x4e, 0x62, 0xb7, 0xf5, 0x03, 0x54, 0x82, 0xdc, + 0x41, 0xaf, 0xe3, 0x39, 0x6d, 0x61, 0x06, 0xd8, 0xf2, 0xdb, 0x7a, 0x5f, 0xe7, 0x34, 0x5a, 0x56, + 0xcf, 0x22, 0xac, 0x72, 0x04, 0xb0, 0xb6, 0x74, 0xe6, 0x0e, 0xaf, 0xad, 0xb5, 0x9b, 0x8d, 0xb5, + 0x5b, 0x88, 0xf3, 0xda, 0x5a, 0x4c, 0x65, 0x9e, 0xf5, 0x88, 0x9a, 0x38, 0x95, 0x5e, 0xaf, 0xe3, + 0xb6, 0xe8, 0xce, 0xc0, 0xf4, 0x9a, 0xb4, 0x0e, 0x7e, 0x4b, 0xbd, 0x63, 0x56, 0xb6, 0x45, 0x79, + 0xa3, 0xac, 0xdc, 0x22, 0x5b, 0xdf, 0xc3, 0x0d, 0x03, 0x31, 0xae, 0xe1, 0x3f, 0x83, 0x29, 0x0e, + 0x8a, 0x09, 0xb4, 0xea, 0x95, 0xa7, 0xeb, 0x29, 0xe0, 0x98, 0xa2, 0xbe, 0xf5, 0x1d, 0xac, 0x1c, + 0xf4, 0x02, 0xec, 0x27, 0xc9, 0x8b, 0xae, 0x7e, 0x2c, 0xef, 0xb0, 0x33, 0x46, 0x8f, 0x3f, 0x9c, + 0x0d, 0xca, 0x93, 0x8c, 0xb6, 0xb8, 0xba, 0xb6, 0x7e, 0x3f, 0x03, 0x2b, 0x6c, 0xa9, 0x1a, 0x49, + 0x5f, 0x84, 0x0b, 0x8a, 0x47, 0x39, 0x6b, 0xf6, 0x28, 0x0f, 0x75, 0xb1, 0x5b, 0xdf, 0x82, 0xc5, + 0x7b, 0xd4, 0xe9, 0xbc, 0xa1, 0xb9, 0xf9, 0x1b, 0x19, 0x58, 0x60, 0x93, 0xf3, 0x1a, 0x54, 0xd0, + 0x57, 0x30, 0x57, 0x3f, 0x71, 0x7b, 0x0d, 0xa7, 0xe3, 0xb6, 0x99, 0x73, 0x95, 0x6d, 0x24, 0x8b, + 0x74, 0x8f, 0x3c, 0x71, 0x7b, 0xcd, 0xe7, 0x51, 0x51, 0xc6, 0x8e, 0x55, 0xb6, 0x9e, 0xc0, 0x62, + 0xac, 0x0f, 0x5c, 0x30, 0x3e, 0x8e, 0x0b, 0x46, 0x22, 0x00, 0x21, 0x5d, 0x2a, 0x1e, 0xc3, 0x55, + 0x29, 0x15, 0xfa, 0x94, 0xad, 0xc5, 0xa4, 0x21, 0x41, 0x30, 0x4d, 0x14, 0x5a, 0x70, 0x55, 0x4a, + 0xc2, 0x6b, 0x48, 0x80, 0x98, 0xdc, 0x6c, 0xea, 0xe4, 0x6e, 0x43, 0x49, 0x9d, 0xdc, 0xd7, 0x99, + 0xd4, 0xff, 0x9c, 0x81, 0xa5, 0x4d, 0xdc, 0xc5, 0xbe, 0x43, 0xbb, 0xac, 0x9d, 0x29, 0x54, 0xc7, + 0x72, 0x66, 0xa8, 0x63, 0x59, 0x1a, 0xcc, 0xd9, 0x74, 0x83, 0x99, 0xec, 0x86, 0x07, 0xf6, 0x36, + 0x97, 0x55, 0xba, 0x1b, 0xf6, 0x7d, 0xd7, 0x26, 0x30, 0xb4, 0x1d, 0x39, 0xa5, 0xc7, 0x47, 0x3a, + 0xa5, 0xe7, 0xb9, 0x93, 0x6e, 0x8a, 0x3b, 0xa5, 0x35, 0x57, 0xb4, 0xf5, 0x05, 0x14, 0x93, 0x63, + 0xe1, 0xf2, 0x31, 0xea, 0x90, 0x62, 0xad, 0x47, 0xd2, 0xcd, 0xef, 0xaf, 0xa5, 0x33, 0x3e, 0xa6, + 0x42, 0x87, 0x38, 0x7f, 0xac, 0x7a, 0x24, 0x9f, 0x9c, 0x0a, 0x6f, 0xff, 0x73, 0x22, 0x9f, 0x2c, + 0x46, 0x21, 0x63, 0x8e, 0x51, 0xe0, 0x32, 0xca, 0x50, 0x05, 0x82, 0xf5, 0x14, 0xae, 0x6a, 0x44, + 0x23, 0xa9, 0xff, 0x0a, 0x72, 0x02, 0x16, 0xf3, 0x4d, 0x68, 0x64, 0xe9, 0xbc, 0x05, 0x02, 0x59, + 0xa2, 0x58, 0xbf, 0xca, 0xc0, 0x12, 0xdb, 0x5d, 0x92, 0xe3, 0x3e, 0xff, 0xec, 0xff, 0xb9, 0x38, + 0xbc, 0xee, 0xa7, 0x38, 0xbc, 0x28, 0x8a, 0xea, 0xf0, 0x52, 0xdd, 0x5c, 0x0f, 0xc7, 0x73, 0xd9, + 0xc2, 0x98, 0xd5, 0x80, 0x62, 0x72, 0x84, 0x6f, 0x60, 0x4e, 0x36, 0x61, 0x49, 0x59, 0xe8, 0xaf, + 0x2d, 0x31, 0x51, 0x8b, 0x6f, 0x50, 0x62, 0xa2, 0x8a, 0x6f, 0x4c, 0x62, 0xb6, 0x61, 0x9e, 0x11, + 0xd6, 0x57, 0xd7, 0xaa, 0xba, 0xba, 0x52, 0xe3, 0x65, 0x92, 0x0b, 0xee, 0x31, 0x5d, 0x70, 0xa2, + 0x4a, 0xd4, 0xc3, 0x8f, 0x60, 0x92, 0x87, 0x04, 0xb2, 0xfe, 0xa5, 0x10, 0xa3, 0x9a, 0x97, 0xc5, + 0x01, 0xda, 0xbc, 0xb2, 0x55, 0xa4, 0x43, 0x26, 0xe7, 0x14, 0xee, 0xf0, 0x96, 0xa7, 0xc6, 0x6f, + 0x89, 0x8a, 0x8b, 0x95, 0xbc, 0xe6, 0xae, 0xf1, 0x04, 0x8a, 0x6c, 0xd7, 0x50, 0xa8, 0xbe, 0xd6, + 0xbe, 0xf1, 0x29, 0x14, 0x99, 0x38, 0xa5, 0x10, 0x1c, 0xbe, 0x19, 0xac, 0xc0, 0xb2, 0xdc, 0x0c, + 0xd2, 0x46, 0xff, 0x77, 0x32, 0x70, 0x6d, 0x13, 0x87, 0x7a, 0xd4, 0xd4, 0xff, 0x97, 0xbd, 0xfb, + 0x07, 0x28, 0xa5, 0x75, 0x84, 0x4f, 0xc5, 0xd7, 0xf1, 0xa9, 0x30, 0x86, 0x88, 0xa5, 0x4f, 0xc9, + 0xf7, 0x70, 0x9d, 0x4d, 0x89, 0x5e, 0x5f, 0x0c, 0xf4, 0x8b, 0xd8, 0xac, 0x18, 0xa9, 0xa7, 0xcd, + 0xce, 0xdf, 0xcf, 0xc0, 0x75, 0xc6, 0xe4, 0x74, 0xe2, 0x7f, 0xde, 0xd6, 0xdd, 0x2e, 0x94, 0xe5, + 0x9c, 0xbf, 0x81, 0x89, 0xb5, 0x5a, 0x80, 0x04, 0x99, 0x5a, 0xdd, 0x16, 0x24, 0xae, 0xc1, 0x58, + 0xad, 0x6e, 0xf3, 0x0b, 0x68, 0xba, 0x69, 0xb7, 0x02, 0xdf, 0x26, 0xb0, 0xb8, 0x06, 0xcf, 0x9e, + 0x43, 0x83, 0x5b, 0x3f, 0x87, 0x79, 0xad, 0x11, 0x3e, 0xef, 0xcb, 0x30, 0x5e, 0xc3, 0x7e, 0xc8, + 0x9b, 0xa1, 0x23, 0x6d, 0x61, 0x3f, 0xb4, 0x29, 0x14, 0xdd, 0x86, 0xa9, 0x5a, 0x85, 0x7a, 0x1b, + 0xa9, 0x6d, 0x31, 0xc3, 0x14, 0x53, 0xcb, 0x69, 0xd2, 0x48, 0x72, 0x5b, 0x14, 0x5a, 0xff, 0x3e, + 0xa3, 0x50, 0x27, 0xe8, 0xa3, 0xc7, 0xf0, 0x01, 0x39, 0x1e, 0x13, 0x9e, 0x29, 0x43, 0xb8, 0x42, + 0xb6, 0x2d, 0xee, 0xa9, 0x61, 0x3b, 0x9f, 0xad, 0x54, 0x3a, 0xa7, 0xa7, 0x54, 0xdc, 0xcc, 0x31, + 0x24, 0xe1, 0x45, 0x94, 0x37, 0x73, 0x9c, 0x74, 0x60, 0xab, 0x95, 0xac, 0x1f, 0x60, 0x41, 0xef, + 0xff, 0x1b, 0x65, 0xcf, 0xdb, 0xf4, 0xca, 0x47, 0xb9, 0x2c, 0x45, 0x48, 0x75, 0x24, 0x70, 0xb1, + 0xfa, 0x04, 0x0a, 0xbc, 0x56, 0xb4, 0x2c, 0x6f, 0x09, 0xd3, 0x8e, 0x2d, 0x4a, 0x3d, 0xb8, 0x58, + 0xf8, 0x43, 0x7f, 0x26, 0x5c, 0x15, 0xa3, 0x5a, 0xf8, 0x47, 0x19, 0x28, 0x3e, 0x7e, 0x50, 0xa9, + 0xf4, 0xc3, 0x63, 0xdc, 0x0d, 0xc9, 0xa1, 0x04, 0xd7, 0x8e, 0x9d, 0x4e, 0x07, 0x77, 0x8f, 0x30, + 0xba, 0x03, 0xe3, 0xfb, 0x4f, 0xf6, 0xf7, 0xb8, 0x47, 0x60, 0x81, 0x1f, 0xc7, 0x09, 0x48, 0xd6, + 0xb1, 0x69, 0x0d, 0xf4, 0x08, 0xae, 0x3c, 0xe5, 0xe1, 0xfc, 0xb2, 0x88, 0xfb, 0x02, 0x6e, 0xdc, + 0x95, 0x81, 0xfe, 0x35, 0x1f, 0xb7, 0x49, 0x2b, 0x4e, 0xa7, 0x12, 0x10, 0xc5, 0x40, 0xe6, 0x27, + 0x89, 0xf7, 0x70, 0x3c, 0x97, 0x29, 0x64, 0xad, 0x3f, 0xc8, 0xc0, 0x52, 0xac, 0x67, 0x8a, 0x63, + 0x57, 0xed, 0xd8, 0xbc, 0xd2, 0x31, 0x51, 0x65, 0xeb, 0x12, 0xef, 0x59, 0x8d, 0xc6, 0x8e, 0xd2, + 0x16, 0x78, 0x87, 0xde, 0x19, 0xde, 0xa1, 0x88, 0x80, 0x44, 0xe4, 0x91, 0x5f, 0x14, 0x6e, 0x5d, + 0x86, 0x59, 0x8d, 0x03, 0x96, 0x05, 0x33, 0x6a, 0xcb, 0x84, 0xcd, 0x35, 0xaf, 0x2d, 0xd9, 0x4c, + 0xfe, 0xb6, 0xfe, 0x41, 0x06, 0x16, 0x1e, 0x3f, 0xa8, 0xd8, 0xf8, 0xc8, 0x25, 0xcb, 0x2f, 0x62, + 0xf1, 0xaa, 0x36, 0x92, 0x65, 0x6d, 0x24, 0xb1, 0xba, 0x72, 0x48, 0x9f, 0x27, 0x86, 0xb4, 0x9c, + 0x36, 0x24, 0x6a, 0x65, 0xb9, 0x5e, 0x57, 0x1b, 0x89, 0xe2, 0xf9, 0xf8, 0xc7, 0x19, 0x98, 0x57, + 0xfa, 0x24, 0xfb, 0xff, 0x81, 0xd6, 0xa5, 0xeb, 0x29, 0x5d, 0x4a, 0x30, 0xb9, 0x9a, 0xe8, 0xd1, + 0xdb, 0xc3, 0x7a, 0x34, 0x92, 0xc7, 0x7f, 0x92, 0x81, 0xc5, 0x54, 0x1e, 0xa0, 0xab, 0x64, 0xd7, + 0x68, 0xf9, 0x38, 0xe4, 0xec, 0xe5, 0x5f, 0x04, 0xbe, 0x1d, 0x04, 0x7d, 0xfe, 0xd6, 0x22, 0x6f, + 0xf3, 0x2f, 0xf4, 0x36, 0xcc, 0xee, 0x61, 0xdf, 0xf5, 0xda, 0x75, 0xdc, 0xf2, 0xba, 0x6d, 0xe6, + 0x11, 0x9e, 0xb5, 0x75, 0x20, 0x5a, 0x86, 0x7c, 0xa5, 0x73, 0xe4, 0xf9, 0x6e, 0x78, 0xcc, 0x9c, + 0x4f, 0x79, 0x3b, 0x02, 0x10, 0xda, 0xeb, 0xee, 0x91, 0x1b, 0xb2, 0xbb, 0xb5, 0x59, 0x9b, 0x7f, + 0xa1, 0x22, 0x4c, 0x55, 0x5a, 0x2d, 0xaf, 0xdf, 0x0d, 0xa9, 0x07, 0x34, 0x6f, 0x8b, 0x4f, 0x82, + 0xf1, 0xad, 0x4d, 0x85, 0x80, 0x46, 0x43, 0xd9, 0xfc, 0xcb, 0x7a, 0x0f, 0x16, 0xd2, 0xf8, 0x98, + 0x2a, 0x32, 0x7f, 0x2d, 0x0b, 0xf3, 0x95, 0x76, 0xfb, 0xf1, 0x83, 0xca, 0x3a, 0x56, 0x8d, 0x8f, + 0x0f, 0x61, 0x7c, 0xbb, 0xeb, 0x86, 0x7c, 0xd7, 0x5c, 0xe1, 0xd3, 0x93, 0x52, 0x93, 0xd4, 0x22, + 0x33, 0x44, 0xfe, 0x47, 0x36, 0xcc, 0x6f, 0xfc, 0xe4, 0x06, 0xa1, 0xdb, 0x3d, 0xa2, 0x73, 0xce, + 0x1a, 0xe6, 0x73, 0x2c, 0x88, 0x18, 0x96, 0xdb, 0xd6, 0x25, 0x3b, 0x0d, 0x19, 0xed, 0xc3, 0xd5, + 0x5d, 0xfc, 0x22, 0x45, 0x84, 0x64, 0x30, 0x93, 0x24, 0x9b, 0x22, 0x39, 0x06, 0x5c, 0x55, 0x42, + 0xff, 0x76, 0x96, 0x46, 0xc8, 0x29, 0x03, 0xe3, 0x2d, 0x1f, 0xc0, 0x82, 0xd2, 0xa1, 0x48, 0xe3, + 0x64, 0x78, 0xec, 0x74, 0xea, 0x70, 0xd4, 0x85, 0x94, 0x8a, 0x8e, 0x9e, 0xc2, 0x92, 0xde, 0xa9, + 0x88, 0xb2, 0xbe, 0x18, 0xd2, 0xaa, 0x6c, 0x5d, 0xb2, 0x4d, 0xd8, 0x68, 0x15, 0xc6, 0x2a, 0xad, + 0x13, 0xce, 0x96, 0xf4, 0x29, 0x63, 0x23, 0xab, 0xb4, 0x4e, 0x68, 0xb4, 0x69, 0xeb, 0x44, 0x5b, + 0x0f, 0xff, 0x21, 0x03, 0x4b, 0x86, 0x19, 0x46, 0x2b, 0x00, 0x0c, 0xa8, 0xe8, 0x76, 0x05, 0x42, + 0x76, 0x55, 0xf6, 0x45, 0x2f, 0x1c, 0xc7, 0xe8, 0xfd, 0xb5, 0x88, 0x0b, 0x8a, 0x0a, 0x6c, 0xa5, + 0x12, 0xda, 0x83, 0x69, 0xf6, 0xc5, 0xc2, 0x93, 0xc6, 0x29, 0x0e, 0xd2, 0x70, 0x58, 0x3c, 0x12, + 0x8d, 0x39, 0x68, 0x53, 0x40, 0x33, 0x1e, 0x96, 0xa4, 0x92, 0xe0, 0x47, 0xbf, 0x5a, 0x7c, 0x14, + 0x72, 0xd0, 0xe8, 0x0e, 0x4c, 0x32, 0x20, 0x9f, 0x43, 0xf1, 0xea, 0x25, 0xaa, 0xcc, 0xcb, 0xad, + 0x3f, 0xcc, 0x08, 0x8f, 0x4e, 0x62, 0x69, 0x7c, 0xa2, 0x2d, 0x8d, 0xb7, 0x64, 0x87, 0xd3, 0x2a, + 0x6b, 0xab, 0xa3, 0x0a, 0xd3, 0xaf, 0xb2, 0x2a, 0x54, 0x24, 0x55, 0x6e, 0xff, 0x79, 0x46, 0x9c, + 0x45, 0x93, 0xa2, 0xbb, 0x01, 0x33, 0xaf, 0x26, 0xb2, 0x1a, 0x1a, 0xfa, 0x88, 0x49, 0x54, 0x76, + 0xf8, 0x48, 0x87, 0x0a, 0xd5, 0x97, 0xc2, 0x69, 0xf5, 0x2a, 0x62, 0x65, 0x2d, 0xa7, 0x60, 0xcb, + 0xe6, 0xac, 0x7e, 0xa2, 0xb4, 0xfe, 0xb2, 0xdb, 0x12, 0xf3, 0x74, 0x3b, 0x7e, 0xc9, 0x6e, 0xbc, + 0x41, 0x55, 0xfb, 0x90, 0x8d, 0xfc, 0x18, 0x5c, 0xe4, 0xa8, 0x49, 0xab, 0x76, 0xea, 0x2c, 0xb6, + 0x4e, 0x5e, 0xa5, 0xd1, 0x1a, 0xcc, 0xee, 0xe2, 0x17, 0x89, 0x76, 0xe9, 0xbd, 0x54, 0x17, 0xbf, + 0x68, 0x2a, 0x6d, 0x2b, 0xd2, 0xae, 0xe3, 0xa0, 0x43, 0x98, 0x13, 0xba, 0xe0, 0xbc, 0x2a, 0x91, + 0x45, 0x5c, 0x92, 0x16, 0x4e, 0x9f, 0x39, 0x4d, 0x9f, 0x43, 0xd5, 0x50, 0x49, 0x9d, 0xa2, 0xb5, + 0x07, 0xc5, 0xe4, 0x58, 0xb9, 0x94, 0x7d, 0x38, 0x6a, 0x39, 0xb1, 0x53, 0x55, 0x5b, 0x5f, 0x5a, + 0x5b, 0xd4, 0x01, 0x20, 0xeb, 0xc8, 0xa3, 0xcb, 0xfd, 0x38, 0xeb, 0x68, 0x14, 0x99, 0x60, 0x9d, + 0x1a, 0xd2, 0x1d, 0x85, 0x59, 0x2c, 0xc6, 0x28, 0xf1, 0x8e, 0xbd, 0x07, 0x53, 0x1c, 0x24, 0x43, + 0xe5, 0xe3, 0x0b, 0x5d, 0x54, 0xb0, 0xfe, 0x49, 0x06, 0xae, 0x1d, 0x04, 0xd8, 0xaf, 0xbb, 0xdd, + 0xa3, 0x0e, 0x3e, 0x08, 0xf4, 0x20, 0x87, 0xdf, 0xd6, 0x16, 0xfb, 0x92, 0x21, 0x78, 0xf2, 0xff, + 0xd5, 0x12, 0xff, 0x67, 0x19, 0x28, 0xa5, 0xf5, 0xed, 0xcd, 0xae, 0xf2, 0xbb, 0xfc, 0xac, 0xc1, + 0x7a, 0x5b, 0xe4, 0xe8, 0xb2, 0x4d, 0x31, 0x58, 0x32, 0x48, 0xf2, 0xbf, 0xb6, 0xbc, 0xff, 0x77, + 0x06, 0x16, 0xb6, 0x03, 0xda, 0xfd, 0x5f, 0xf4, 0x5d, 0x1f, 0xb7, 0x05, 0xe3, 0xee, 0xa6, 0x85, + 0xd8, 0xd2, 0x79, 0xdd, 0xba, 0x94, 0x16, 0x42, 0xfb, 0xa1, 0x12, 0x44, 0x98, 0x1d, 0x16, 0x3b, + 0xab, 0xbd, 0xd7, 0xb8, 0x0d, 0xe3, 0xbb, 0xc4, 0xa4, 0x19, 0xe3, 0xf2, 0xc7, 0x30, 0x08, 0x88, + 0xc6, 0xfb, 0x91, 0x2e, 0x93, 0x0f, 0xf4, 0x20, 0x11, 0x55, 0x38, 0x3e, 0x3a, 0x36, 0x34, 0xf9, + 0xd0, 0xa4, 0x9a, 0x83, 0xc9, 0x7d, 0xc7, 0x3f, 0xc2, 0xa1, 0xf5, 0x3d, 0x94, 0xf8, 0x65, 0x1e, + 0xf3, 0x91, 0xd1, 0x2b, 0xbf, 0x20, 0xf2, 0xdd, 0x0c, 0xbb, 0x80, 0x5b, 0x01, 0xa8, 0x87, 0x8e, + 0x1f, 0x6e, 0x77, 0xdb, 0xf8, 0x27, 0x3a, 0xda, 0x09, 0x5b, 0x81, 0x58, 0x1f, 0x41, 0x5e, 0x0e, + 0x81, 0x9e, 0xa7, 0x14, 0xab, 0x8d, 0x0e, 0x67, 0x41, 0x8b, 0x73, 0x14, 0xc1, 0x8d, 0x6b, 0xb0, + 0x18, 0x9b, 0x0a, 0x2e, 0x27, 0x25, 0x32, 0x61, 0x0c, 0xc6, 0x2e, 0xfa, 0x6d, 0xf9, 0x6d, 0xd5, + 0xe0, 0x4a, 0x62, 0xa6, 0x11, 0xa2, 0xb1, 0xab, 0xec, 0xf0, 0x49, 0x94, 0x7a, 0xbd, 0xbe, 0x45, + 0x60, 0xfb, 0x3b, 0x75, 0x16, 0xc7, 0x43, 0x60, 0xfb, 0x3b, 0xf5, 0xea, 0x24, 0x93, 0x1c, 0xeb, + 0x5f, 0x65, 0xe9, 0x11, 0x32, 0xc1, 0x83, 0x98, 0x2b, 0x42, 0x75, 0x87, 0x54, 0x21, 0x4f, 0x47, + 0xbc, 0x2e, 0xe2, 0xdd, 0x86, 0xdf, 0x00, 0xe4, 0x7e, 0x39, 0x28, 0x5f, 0xa2, 0x6e, 0xff, 0x08, + 0x0d, 0x7d, 0x0d, 0x53, 0x1b, 0xdd, 0x36, 0xa5, 0x30, 0x76, 0x01, 0x0a, 0x02, 0x89, 0xcc, 0x03, + 0xed, 0x32, 0x31, 0x47, 0xf8, 0x79, 0xdd, 0x56, 0x20, 0x94, 0xcd, 0xee, 0xa9, 0xcb, 0xee, 0x79, + 0x27, 0x6c, 0xf6, 0x41, 0xb8, 0x49, 0xbb, 0x20, 0x9e, 0x2f, 0xe4, 0x6d, 0xf9, 0x8d, 0x2c, 0x98, + 0x78, 0xe2, 0xb7, 0x79, 0x30, 0xf9, 0xdc, 0xea, 0x8c, 0x78, 0xcf, 0x4d, 0x60, 0x36, 0x2b, 0xb2, + 0xfe, 0x27, 0xbd, 0x7b, 0x09, 0x53, 0xe5, 0x46, 0xe3, 0x4a, 0xe6, 0xb5, 0xb9, 0x92, 0x7d, 0x15, + 0xae, 0xc8, 0x51, 0x8f, 0x99, 0x46, 0x3d, 0x6e, 0x1a, 0xf5, 0x84, 0x79, 0xd4, 0x9b, 0x30, 0xc9, + 0x86, 0x8a, 0x6e, 0xc1, 0xc4, 0x76, 0x88, 0x4f, 0x23, 0xd7, 0x82, 0x7a, 0x7b, 0x6e, 0xb3, 0x32, + 0x72, 0xea, 0xd9, 0x71, 0x82, 0x50, 0x44, 0x8e, 0xe5, 0x6d, 0xf1, 0x69, 0xfd, 0x69, 0x06, 0x0a, + 0x3b, 0x6e, 0x10, 0x92, 0x85, 0x70, 0x4e, 0x59, 0x93, 0x23, 0xca, 0x9a, 0x46, 0x34, 0x16, 0x1b, + 0xd1, 0x17, 0x30, 0x49, 0x03, 0x1a, 0x03, 0xfe, 0x54, 0xe9, 0x16, 0x1f, 0x52, 0xbc, 0x61, 0x16, + 0xf6, 0x18, 0xd0, 0x27, 0x45, 0x36, 0x47, 0x29, 0x7d, 0x06, 0xd3, 0x0a, 0xf8, 0x42, 0x2f, 0x8d, + 0xbe, 0x83, 0x2b, 0x4a, 0x13, 0xd2, 0x0f, 0x31, 0xc2, 0x5b, 0x2d, 0xbd, 0xa1, 0x84, 0x6d, 0xbb, + 0xf8, 0x27, 0x95, 0x6d, 0xfc, 0xd3, 0xfa, 0x91, 0xc6, 0xe3, 0xee, 0x78, 0xad, 0x13, 0xc5, 0x57, + 0x38, 0xc5, 0x94, 0x59, 0xdc, 0xe5, 0x4e, 0x6a, 0xb1, 0x12, 0x5b, 0xd4, 0x40, 0x37, 0x61, 0x7a, + 0xbb, 0xfb, 0xc0, 0xf3, 0x5b, 0xf8, 0x49, 0xb7, 0xc3, 0xa8, 0xe7, 0x6c, 0x15, 0xc4, 0xdd, 0x48, + 0xbc, 0x85, 0xc8, 0x8d, 0x44, 0x01, 0x31, 0x37, 0x12, 0x7b, 0x29, 0x69, 0xb3, 0x32, 0xee, 0xa5, + 0x22, 0x7f, 0x0f, 0xf3, 0x21, 0x49, 0x67, 0xd3, 0xa8, 0x8a, 0x87, 0x70, 0xcd, 0xc6, 0xbd, 0x8e, + 0x43, 0x6c, 0xc5, 0x53, 0x8f, 0xd5, 0x97, 0x63, 0xbe, 0x99, 0x12, 0x2a, 0xa6, 0xdf, 0x38, 0xc9, + 0x2e, 0x67, 0x87, 0x74, 0xf9, 0x14, 0xde, 0xda, 0xc4, 0x61, 0xea, 0x73, 0xc7, 0x68, 0xf0, 0x5b, + 0x90, 0xe3, 0x21, 0xf9, 0x62, 0xfc, 0xa3, 0x5e, 0x5a, 0xf2, 0xeb, 0x17, 0x4e, 0x47, 0xfe, 0x65, + 0x7d, 0x03, 0x65, 0x53, 0x73, 0xe7, 0x8b, 0x10, 0x72, 0xe1, 0xa6, 0x99, 0x80, 0xb4, 0x26, 0xa6, + 0x78, 0x83, 0xf2, 0xd4, 0x3f, 0xbc, 0xb7, 0xd2, 0x21, 0x4f, 0xed, 0x29, 0xfe, 0x87, 0x55, 0x15, + 0x21, 0x08, 0xaf, 0xd1, 0xdd, 0x26, 0xbd, 0x32, 0xd0, 0x09, 0x44, 0x7c, 0xad, 0x40, 0x4e, 0xc0, + 0x62, 0x77, 0x06, 0x89, 0x97, 0xa4, 0x94, 0xa1, 0x6d, 0x41, 0x40, 0xa2, 0x59, 0x3f, 0x0a, 0xc7, + 0xbe, 0x8e, 0x71, 0xbe, 0xb0, 0xd3, 0xf3, 0x78, 0xf2, 0x2d, 0x0f, 0xae, 0xe9, 0xb4, 0x55, 0xf7, + 0x74, 0x41, 0x71, 0x4f, 0x33, 0xaf, 0x34, 0x91, 0x4b, 0x7b, 0x67, 0xa3, 0xdb, 0xee, 0x79, 0x6e, + 0x37, 0xe4, 0x8b, 0x57, 0x05, 0xa1, 0x15, 0xd5, 0x09, 0x3d, 0x93, 0x8c, 0xd3, 0xbd, 0x0f, 0xa5, + 0xb4, 0x06, 0x15, 0xdf, 0x8f, 0xf4, 0x27, 0x33, 0x3b, 0xce, 0x3a, 0x86, 0x05, 0x2d, 0x37, 0x47, + 0x94, 0x6c, 0x20, 0xca, 0x49, 0x92, 0xaf, 0x7e, 0xf9, 0x9b, 0x41, 0xf9, 0xd3, 0x8b, 0x04, 0x83, + 0x0a, 0x9a, 0xfb, 0x32, 0xd4, 0xd8, 0x5a, 0x82, 0xb1, 0x9a, 0xbd, 0x43, 0x87, 0x6d, 0xef, 0xc8, + 0x61, 0xdb, 0x3b, 0xd6, 0x6f, 0x32, 0x50, 0xae, 0x1d, 0x3b, 0xdd, 0x23, 0x6a, 0x7b, 0x28, 0xe6, + 0xaa, 0x72, 0xaf, 0x7a, 0xde, 0x23, 0xd5, 0x2a, 0x4c, 0xef, 0xe2, 0x17, 0x22, 0x4c, 0x9a, 0x07, + 0x1c, 0x53, 0xf7, 0x3b, 0x39, 0xee, 0xf4, 0x38, 0xdc, 0x56, 0x2b, 0xa1, 0xbf, 0xfc, 0xea, 0xce, + 0x25, 0xf6, 0x42, 0x3f, 0x3a, 0x49, 0xb1, 0xd2, 0xb4, 0x23, 0x95, 0xa1, 0x09, 0xeb, 0xdf, 0x64, + 0xe0, 0xa6, 0x79, 0xf0, 0x7c, 0xe2, 0xd6, 0xb5, 0x3c, 0x07, 0x43, 0x6e, 0x84, 0xe9, 0x91, 0x55, + 0xc9, 0x73, 0x10, 0xcf, 0x6d, 0x60, 0xe3, 0x96, 0xf7, 0x1c, 0xfb, 0x2f, 0x63, 0x7e, 0x76, 0x01, + 0xae, 0x91, 0x2d, 0x47, 0x64, 0x89, 0x61, 0x20, 0xed, 0x69, 0x23, 0x87, 0x59, 0xff, 0x29, 0x03, + 0xd7, 0xe9, 0x36, 0xc9, 0xbd, 0x90, 0xa2, 0xe0, 0xe2, 0xf1, 0x03, 0x1f, 0xc1, 0x8c, 0xda, 0x38, + 0x9f, 0x30, 0xfa, 0x30, 0x5a, 0xf4, 0xa0, 0xd9, 0xf2, 0xda, 0xd8, 0xd6, 0xaa, 0xa1, 0x6d, 0x98, + 0xe6, 0xdf, 0x8a, 0xab, 0x69, 0x51, 0xc9, 0x9a, 0x42, 0xe5, 0x81, 0x79, 0x8e, 0xe8, 0xec, 0x73, + 0x62, 0x4d, 0x1a, 0xfa, 0xae, 0xe2, 0x5a, 0xbf, 0xce, 0xc2, 0x72, 0x03, 0xfb, 0xee, 0xb3, 0x97, + 0x86, 0xc1, 0x3c, 0x81, 0x05, 0x01, 0xa2, 0x63, 0xd6, 0xe5, 0x90, 0x3d, 0x51, 0x13, 0x5d, 0x0d, + 0x48, 0x85, 0xa6, 0x14, 0xcb, 0x54, 0xc4, 0x0b, 0x3c, 0xda, 0xfc, 0x10, 0x72, 0x52, 0x94, 0xc7, + 0x28, 0x67, 0xe8, 0xdc, 0x08, 0x31, 0xd6, 0xdf, 0xaf, 0x4b, 0x79, 0xfe, 0x9b, 0xe6, 0xeb, 0x0c, + 0x7e, 0xe2, 0x19, 0x71, 0x18, 0x65, 0x52, 0x4d, 0x24, 0xda, 0x51, 0x4a, 0x53, 0xa4, 0x7a, 0xeb, + 0x92, 0x6d, 0x6a, 0xa9, 0x3a, 0x0d, 0xf9, 0x0a, 0xbd, 0x6c, 0x21, 0x07, 0x8c, 0xff, 0x95, 0x85, + 0x15, 0x11, 0x52, 0x68, 0x60, 0xf3, 0x77, 0xb0, 0x24, 0x40, 0x95, 0x5e, 0xcf, 0xf7, 0x9e, 0xe3, + 0xb6, 0xce, 0x69, 0xf6, 0x4c, 0x54, 0x70, 0xda, 0xe1, 0x75, 0x22, 0x66, 0x9b, 0xd0, 0xdf, 0x8c, + 0x9b, 0xe5, 0x6b, 0x5d, 0xb1, 0xb0, 0xd9, 0xa0, 0x4e, 0x49, 0x55, 0xb1, 0xe8, 0x09, 0x7e, 0x54, + 0x25, 0xd3, 0x4e, 0xb8, 0x69, 0xc6, 0x5f, 0xd7, 0x4d, 0x43, 0x8e, 0xa6, 0x3a, 0xcd, 0xea, 0x1c, + 0xcc, 0xec, 0xe2, 0x17, 0x11, 0xdf, 0x7f, 0x2f, 0x03, 0xb3, 0xda, 0xe2, 0x46, 0xef, 0xc2, 0x04, + 0xfd, 0x83, 0x6e, 0x9a, 0xfc, 0xad, 0x0c, 0x59, 0x60, 0xda, 0x5b, 0x19, 0x56, 0x75, 0x1b, 0xa6, + 0x58, 0xf8, 0x4c, 0xfb, 0x1c, 0x67, 0x08, 0x19, 0x9d, 0xd5, 0x62, 0x28, 0xec, 0x38, 0xc1, 0xf1, + 0xad, 0x47, 0xf0, 0x16, 0x8f, 0xc4, 0xd1, 0x27, 0xbf, 0xa6, 0xda, 0xef, 0xe7, 0xd4, 0xf1, 0x96, + 0x03, 0x2b, 0x9b, 0x38, 0xae, 0x7a, 0xb4, 0xe8, 0xb5, 0x6f, 0xe0, 0xb2, 0x06, 0x97, 0x14, 0x69, + 0x3c, 0xbd, 0x94, 0x21, 0x49, 0x3a, 0x5e, 0xdb, 0xba, 0x99, 0xd6, 0x84, 0xda, 0x59, 0x0b, 0xc3, + 0x65, 0x7a, 0x52, 0x96, 0x37, 0x4e, 0xc1, 0x05, 0xb4, 0xde, 0x1d, 0x65, 0x5d, 0x33, 0x8d, 0xc7, + 0x9e, 0x62, 0x8a, 0xed, 0x49, 0x96, 0x5a, 0xb3, 0x30, 0x5d, 0xf3, 0xba, 0x21, 0xfe, 0x89, 0x3e, + 0x86, 0xb0, 0xe6, 0x60, 0x46, 0x14, 0x75, 0x70, 0x10, 0x58, 0xff, 0x74, 0x0c, 0x2c, 0xce, 0xd8, + 0x34, 0x2f, 0x8f, 0xe0, 0xc7, 0x61, 0xa2, 0xb3, 0x7c, 0x13, 0xb9, 0xaa, 0xfa, 0xb2, 0xa2, 0x52, + 0x26, 0x79, 0xf4, 0xf1, 0x4a, 0x2b, 0x82, 0x6a, 0x92, 0x97, 0x18, 0xfd, 0xcf, 0x0d, 0x6a, 0x92, + 0x2d, 0x36, 0x9a, 0x3e, 0xc4, 0xa0, 0x26, 0x35, 0xba, 0xe9, 0x2a, 0xd3, 0xd6, 0xd8, 0xc0, 0xf7, + 0x65, 0x24, 0x43, 0xbf, 0x65, 0x09, 0x4f, 0xb9, 0xc5, 0x00, 0xcd, 0x44, 0xca, 0x2c, 0x95, 0x08, + 0x3a, 0xd0, 0x79, 0xc9, 0xd7, 0xa3, 0xb8, 0xe1, 0x55, 0x8b, 0x18, 0xd5, 0x9e, 0x02, 0xd1, 0x33, + 0x90, 0x69, 0x75, 0x15, 0xcf, 0xdd, 0x3f, 0xcc, 0xc0, 0x75, 0x36, 0x3b, 0x7b, 0xbe, 0xfb, 0xdc, + 0xed, 0xe0, 0x23, 0xac, 0x89, 0x69, 0x3f, 0xfd, 0xa6, 0x2c, 0x73, 0x2e, 0x1d, 0x4d, 0x93, 0x36, + 0x60, 0x8e, 0x6e, 0x72, 0xe4, 0xa6, 0xd1, 0xb7, 0x06, 0x19, 0x58, 0x11, 0x6f, 0xca, 0x62, 0xd7, + 0x47, 0x17, 0x35, 0xb7, 0xaa, 0xda, 0x8d, 0x4f, 0xd6, 0x70, 0xe3, 0xa3, 0x79, 0xd2, 0xc3, 0x11, + 0x57, 0x40, 0x63, 0xaf, 0x7d, 0x05, 0x64, 0xfd, 0x6a, 0x0c, 0xae, 0xec, 0x39, 0x47, 0x6e, 0x97, + 0xe8, 0x1e, 0x91, 0x47, 0x05, 0x55, 0x12, 0xe9, 0xa8, 0x86, 0x47, 0x03, 0xa5, 0xe4, 0x9b, 0x5a, + 0x55, 0x33, 0xc3, 0x64, 0x4d, 0x71, 0xe2, 0x7a, 0xfe, 0x97, 0xcf, 0x34, 0xef, 0x64, 0x22, 0x20, + 0x8c, 0xbe, 0x35, 0xea, 0x7a, 0xed, 0x58, 0x8a, 0x36, 0xea, 0xe1, 0x7b, 0x02, 0xd3, 0x4a, 0x54, + 0x17, 0x17, 0xd0, 0x04, 0x05, 0xca, 0x96, 0x93, 0xfe, 0x21, 0x4e, 0x4d, 0xc7, 0xa3, 0x52, 0x48, + 0x49, 0xc2, 0x33, 0xf1, 0x86, 0x93, 0xf0, 0x7c, 0xcf, 0xba, 0x2c, 0x7c, 0xbd, 0x93, 0x7c, 0xdf, + 0x60, 0xe4, 0x13, 0x0e, 0xdf, 0xc6, 0x9a, 0xd2, 0xfb, 0xb4, 0x8c, 0x62, 0x2a, 0xb1, 0x2a, 0x40, + 0x4e, 0xe4, 0x2e, 0xb4, 0xfe, 0x68, 0x12, 0x16, 0x76, 0xdc, 0x20, 0x14, 0xb3, 0x1b, 0x44, 0xaa, + 0x7f, 0x46, 0xc0, 0x94, 0xf3, 0x0b, 0xb7, 0xd2, 0x18, 0xbc, 0x19, 0x4b, 0xa5, 0xa8, 0x21, 0xa0, + 0x8f, 0x54, 0x2f, 0x52, 0x56, 0x49, 0xb0, 0x90, 0xcc, 0x82, 0xa7, 0xba, 0x97, 0xde, 0xd5, 0x1c, + 0x66, 0x6c, 0x5f, 0xed, 0x10, 0x80, 0xba, 0xaf, 0x32, 0x9f, 0xd3, 0x5a, 0xdc, 0x8b, 0xc6, 0x1a, + 0x60, 0x4a, 0xf1, 0x04, 0x6b, 0x26, 0xb7, 0x74, 0x46, 0x1d, 0x48, 0x67, 0xd4, 0x04, 0x3d, 0xed, + 0xfe, 0x4c, 0x71, 0x46, 0xc5, 0x99, 0xa0, 0x3a, 0xa4, 0xd8, 0x93, 0xde, 0x0e, 0x05, 0xa8, 0x4f, + 0x7a, 0x59, 0x15, 0xb4, 0x0f, 0xf3, 0x7b, 0x3e, 0x6e, 0x53, 0xd5, 0xb2, 0xf1, 0x53, 0xcf, 0xe7, + 0x47, 0x0c, 0xea, 0xd2, 0x64, 0xd9, 0xa6, 0x7a, 0xa2, 0xb8, 0x89, 0x65, 0xb9, 0xaa, 0x61, 0x52, + 0xd0, 0xd1, 0x06, 0xcc, 0xd5, 0xb1, 0xe3, 0xb7, 0x8e, 0x1f, 0xe1, 0x97, 0x44, 0x31, 0x06, 0xc5, + 0xa9, 0x28, 0x43, 0x47, 0x40, 0x4b, 0xc8, 0x40, 0x69, 0x91, 0x7a, 0xed, 0xa4, 0x23, 0xa1, 0xdf, + 0x81, 0xc9, 0xba, 0xe7, 0x87, 0xd5, 0x97, 0xb1, 0xb4, 0x88, 0x0c, 0x58, 0xbd, 0x26, 0xb2, 0x94, + 0x04, 0x9e, 0x1f, 0x36, 0x0f, 0x55, 0xbe, 0x71, 0x3c, 0xf4, 0x80, 0x58, 0x5d, 0xc4, 0x12, 0x0c, + 0x9d, 0x4e, 0x8d, 0x86, 0x4f, 0xb0, 0x47, 0x59, 0xdc, 0xb2, 0xa2, 0xe6, 0x63, 0xe8, 0x74, 0x9a, + 0x74, 0x9f, 0xd7, 0x2f, 0xc0, 0x54, 0x2c, 0xf4, 0x12, 0x16, 0x74, 0x41, 0xe7, 0x39, 0x8c, 0x40, + 0x4b, 0x30, 0x96, 0x56, 0xa5, 0x7a, 0x87, 0xf7, 0xf2, 0x66, 0x3c, 0x79, 0x56, 0x22, 0xad, 0x51, + 0x6a, 0x13, 0xaf, 0xe3, 0x48, 0xfc, 0x93, 0x0c, 0x2c, 0xc6, 0xe4, 0x83, 0x1f, 0x28, 0x9f, 0x40, + 0x5e, 0x02, 0xb9, 0xfb, 0xa4, 0x28, 0x37, 0xbe, 0x98, 0xe2, 0x64, 0xd2, 0x29, 0x16, 0x8f, 0x3a, + 0x5f, 0x11, 0x0d, 0x74, 0x3f, 0xe6, 0x73, 0x64, 0x07, 0x95, 0x2e, 0xd9, 0x85, 0x75, 0x89, 0x16, + 0xd5, 0xd0, 0x67, 0x00, 0xca, 0xb4, 0xb0, 0x55, 0x43, 0xd3, 0xf4, 0xa5, 0xcf, 0x88, 0x52, 0xd9, + 0xfa, 0xb3, 0x49, 0xb1, 0xaf, 0xf2, 0xa3, 0xed, 0xbe, 0xef, 0xb4, 0x4e, 0xa2, 0x90, 0xcc, 0x8f, + 0x92, 0xf1, 0x8f, 0xe7, 0x59, 0xc2, 0xb7, 0xb5, 0x97, 0xd2, 0xe6, 0xa4, 0xab, 0xd1, 0xa3, 0xf9, + 0xb1, 0x73, 0x3c, 0x9a, 0xbf, 0x07, 0x53, 0xdb, 0xdd, 0xe7, 0x2e, 0xb1, 0xa2, 0x59, 0x30, 0x20, + 0xb5, 0x41, 0x5d, 0x06, 0x52, 0x19, 0xc3, 0x6b, 0xa1, 0xcf, 0x20, 0xb7, 0xe5, 0x05, 0x21, 0x35, + 0x23, 0x27, 0xa2, 0x93, 0x4a, 0x48, 0x7d, 0xb0, 0xcd, 0x63, 0x5e, 0xa4, 0x2a, 0x09, 0x51, 0x1d, + 0x7d, 0x0c, 0x53, 0x95, 0x76, 0x9b, 0xac, 0x42, 0xbe, 0x82, 0x69, 0xc6, 0x46, 0x8e, 0xe9, 0xb0, + 0x12, 0xb5, 0x49, 0x5e, 0x19, 0x7d, 0xa9, 0x3b, 0x44, 0xa7, 0xa2, 0x94, 0x12, 0xe9, 0xd9, 0x4b, + 0x75, 0x67, 0xe9, 0xbb, 0xe2, 0x22, 0x2a, 0x17, 0x25, 0xe9, 0xa0, 0x09, 0x37, 0x34, 0xd5, 0x47, + 0xef, 0xb1, 0xb6, 0x21, 0xbf, 0xdd, 0x75, 0x43, 0x97, 0xa6, 0x29, 0xc8, 0x6b, 0x1b, 0xe8, 0x9e, + 0xe3, 0x87, 0x6e, 0xcb, 0xed, 0x39, 0xdd, 0x90, 0xcd, 0x96, 0x2b, 0x2a, 0xaa, 0xb3, 0x25, 0xb1, + 0xd5, 0x84, 0x46, 0xf0, 0xc6, 0x12, 0x1a, 0xa5, 0xe6, 0x04, 0x9a, 0x7e, 0xf5, 0x9c, 0x40, 0x6b, + 0x6c, 0x2e, 0xa9, 0xd1, 0x3a, 0x13, 0x09, 0x22, 0xf5, 0x13, 0xea, 0xd6, 0xa9, 0x2d, 0x2b, 0xa2, + 0x9b, 0x34, 0x2d, 0xc1, 0x6c, 0x14, 0x57, 0xab, 0x5d, 0x7c, 0x67, 0xb7, 0xd7, 0x51, 0x13, 0x66, + 0x48, 0xed, 0x3d, 0xaf, 0xe3, 0xb6, 0x5c, 0x1c, 0x14, 0xe7, 0x34, 0xc7, 0xb2, 0xbe, 0x28, 0x68, + 0xa5, 0x97, 0x75, 0x1c, 0xb2, 0x4d, 0x90, 0x36, 0xdd, 0xe3, 0x88, 0xea, 0x26, 0xa8, 0x12, 0xb4, + 0x6c, 0x28, 0x46, 0xb7, 0x53, 0xb1, 0xd5, 0xf5, 0x71, 0xf2, 0x89, 0x03, 0x4d, 0x85, 0x17, 0x3d, + 0x71, 0x50, 0x27, 0x2c, 0x7a, 0xec, 0x70, 0x00, 0xd7, 0x6d, 0x7c, 0xea, 0x3d, 0xc7, 0x6f, 0x96, + 0xec, 0xcf, 0xe1, 0x9a, 0x4e, 0xf0, 0xa0, 0xd7, 0xa6, 0x4f, 0x26, 0xd9, 0x35, 0x58, 0x6a, 0x32, + 0x0d, 0x8e, 0xc0, 0x92, 0x69, 0xb0, 0x17, 0xda, 0xe4, 0x4f, 0x55, 0x5e, 0x69, 0x99, 0xe5, 0xc1, + 0xb2, 0x4e, 0xbc, 0xd2, 0x6e, 0x2b, 0x82, 0x4a, 0x2c, 0x34, 0xe5, 0x33, 0x66, 0x12, 0xaa, 0x12, + 0x4d, 0x35, 0x5b, 0x2f, 0x02, 0xa8, 0x6b, 0x49, 0xa9, 0x67, 0x61, 0x28, 0xc7, 0xd9, 0x43, 0x58, + 0xa6, 0xb6, 0x59, 0x85, 0x59, 0xe5, 0x53, 0x9e, 0xb0, 0xe8, 0x52, 0x57, 0x5a, 0xd0, 0x19, 0xa6, + 0xa3, 0x58, 0xff, 0x72, 0x0c, 0xae, 0x73, 0x3e, 0xbd, 0xc9, 0xc9, 0x40, 0x3f, 0xc2, 0xb4, 0xc2, + 0x7e, 0xce, 0x8f, 0x9b, 0x22, 0x98, 0xc0, 0x34, 0x4d, 0x4c, 0xd5, 0xf4, 0x29, 0xa0, 0x19, 0x9b, + 0x09, 0x62, 0x04, 0xaa, 0x33, 0xda, 0x81, 0x39, 0x7d, 0x0e, 0xb8, 0x61, 0x7d, 0x2b, 0xb5, 0x11, + 0xbd, 0xaa, 0x78, 0xf1, 0xdd, 0x6e, 0xa6, 0xce, 0x04, 0x4d, 0x2b, 0xaa, 0xcf, 0xef, 0x4f, 0x70, + 0x25, 0x31, 0x01, 0xdc, 0x0e, 0xbf, 0x9d, 0xda, 0x60, 0xa2, 0x36, 0xd3, 0x1b, 0x3e, 0x05, 0x1b, + 0x9b, 0x4d, 0x36, 0x52, 0xcd, 0xc1, 0x24, 0x1b, 0x36, 0x59, 0x37, 0x7b, 0x3e, 0x0e, 0x70, 0xb7, + 0x85, 0xd5, 0x88, 0x8d, 0xd7, 0x5d, 0x37, 0xff, 0x31, 0x03, 0xc5, 0x34, 0xba, 0x75, 0xdc, 0x6d, + 0xa3, 0x3d, 0x28, 0xc4, 0x1b, 0xe2, 0xa7, 0x25, 0x4b, 0x98, 0x09, 0xe6, 0x2e, 0x6d, 0x5d, 0xb2, + 0x13, 0xd8, 0x44, 0xb3, 0x2a, 0xb0, 0x0b, 0x86, 0xc6, 0x24, 0x51, 0x95, 0x63, 0xf6, 0x7b, 0xef, + 0x41, 0x5e, 0xe6, 0x4a, 0x47, 0x39, 0x18, 0xdf, 0xde, 0xdd, 0xde, 0x67, 0xb9, 0xb7, 0xf6, 0x0e, + 0xf6, 0x0b, 0x19, 0x04, 0x30, 0xb9, 0xbe, 0xb1, 0xb3, 0xb1, 0xbf, 0x51, 0xc8, 0xbe, 0xd7, 0x54, + 0x0f, 0xac, 0xe8, 0x3a, 0x2c, 0xad, 0x6f, 0x34, 0xb6, 0x6b, 0x1b, 0xcd, 0xfd, 0xbf, 0xb0, 0xb7, + 0xd1, 0x3c, 0xd8, 0xad, 0xef, 0x6d, 0xd4, 0xb6, 0x1f, 0x6c, 0x6f, 0xac, 0x17, 0x2e, 0xa1, 0x05, + 0x28, 0xa8, 0x85, 0xfb, 0x4f, 0xf6, 0xf7, 0x0a, 0x19, 0x54, 0x84, 0x05, 0x15, 0xfa, 0x74, 0xa3, + 0x5a, 0x39, 0xd8, 0xdf, 0xda, 0x2d, 0x8c, 0x59, 0xe3, 0xb9, 0x6c, 0x21, 0xfb, 0xde, 0x8f, 0xda, + 0x69, 0x16, 0x2d, 0x43, 0x91, 0x57, 0x3f, 0xa8, 0x57, 0x36, 0xcd, 0x4d, 0xb0, 0xd2, 0xc7, 0x0f, + 0x2a, 0x85, 0x0c, 0xba, 0x01, 0xd7, 0x34, 0xe8, 0x5e, 0xa5, 0x5e, 0x7f, 0xfa, 0xc4, 0x5e, 0xdf, + 0xd9, 0xa8, 0xd7, 0x0b, 0xd9, 0xf7, 0x6e, 0xf3, 0x8b, 0x76, 0x34, 0x07, 0xb0, 0xbe, 0x51, 0xaf, + 0x6d, 0xec, 0xae, 0x6f, 0xef, 0x6e, 0x16, 0x2e, 0xa1, 0x59, 0xc8, 0x57, 0xe4, 0x67, 0x66, 0xf5, + 0x8f, 0x7f, 0x84, 0x69, 0xc2, 0x4f, 0x71, 0xf8, 0x6b, 0xc2, 0xd2, 0x63, 0xc7, 0xed, 0x86, 0x8e, + 0xdb, 0xe5, 0x52, 0x20, 0xe6, 0x10, 0x95, 0x87, 0x4c, 0x2a, 0x91, 0x87, 0xd2, 0xa8, 0x70, 0xa2, + 0x3b, 0x99, 0xfb, 0x19, 0x54, 0x87, 0x85, 0x34, 0xab, 0x0c, 0x59, 0x7a, 0x6a, 0x80, 0x34, 0x85, + 0x53, 0x5a, 0x4a, 0xdd, 0xbb, 0x1a, 0x1f, 0xa0, 0xc7, 0x70, 0x25, 0xb1, 0x13, 0xc9, 0xfe, 0x9a, + 0xf6, 0xa8, 0x61, 0xe4, 0x8a, 0xd4, 0xb1, 0x17, 0xba, 0xf1, 0x7d, 0x28, 0x40, 0x57, 0x13, 0x06, + 0xc4, 0x06, 0x59, 0x34, 0x46, 0x62, 0xf7, 0x33, 0xc8, 0x86, 0x85, 0xb4, 0x3d, 0x4d, 0x0e, 0x79, + 0xc8, 0x86, 0x57, 0x32, 0x34, 0x47, 0x68, 0xa6, 0xa9, 0x66, 0x49, 0x73, 0x88, 0xde, 0x36, 0xd2, + 0xfc, 0x92, 0x1c, 0xc8, 0xba, 0xed, 0x47, 0x18, 0xf7, 0x2a, 0x1d, 0xf7, 0x39, 0x0e, 0x90, 0x08, + 0x86, 0x93, 0x20, 0x13, 0xee, 0x9d, 0x0c, 0xfa, 0x2d, 0x98, 0xa6, 0xe9, 0x59, 0x79, 0xec, 0xc6, + 0x8c, 0x9a, 0xb2, 0xb5, 0x24, 0xbe, 0x68, 0xe1, 0xfd, 0x0c, 0xfa, 0x0a, 0xa6, 0x36, 0x31, 0x0d, + 0x5e, 0x40, 0x6f, 0xc5, 0x7e, 0x85, 0x60, 0xbb, 0x2b, 0x8d, 0x6d, 0xd1, 0xe1, 0xb8, 0x37, 0x03, + 0xd5, 0x20, 0xc7, 0xd1, 0x03, 0x64, 0xc5, 0xf0, 0x83, 0x14, 0x02, 0xf3, 0x31, 0x02, 0xe4, 0xbc, + 0x83, 0x6a, 0x90, 0x97, 0x11, 0x14, 0x68, 0xc9, 0x10, 0xb6, 0x51, 0x2a, 0x26, 0x0b, 0xb8, 0x87, + 0x6c, 0xec, 0xef, 0x66, 0x33, 0xe8, 0x1e, 0x00, 0x7b, 0x54, 0x46, 0xc7, 0x12, 0xef, 0x68, 0x29, + 0xc1, 0x40, 0xb4, 0x49, 0x74, 0x4b, 0x07, 0x87, 0xf8, 0xbc, 0x83, 0x37, 0xcd, 0xd6, 0x0e, 0xcc, + 0xc9, 0x27, 0x5e, 0xe7, 0xe7, 0x84, 0x89, 0xda, 0xe7, 0x64, 0x05, 0xb1, 0x97, 0xd1, 0x32, 0x64, + 0x11, 0x99, 0x82, 0x18, 0xe5, 0x74, 0xb2, 0x6a, 0x0a, 0xae, 0xcc, 0x7d, 0x2b, 0x71, 0xe3, 0xd9, + 0x70, 0x63, 0xb8, 0x18, 0x4a, 0x6a, 0xbb, 0x7a, 0xf8, 0x22, 0xba, 0xa9, 0x74, 0x20, 0x35, 0xea, + 0xb2, 0xf4, 0xd6, 0x90, 0x1a, 0x6c, 0x9e, 0xa8, 0xd6, 0x79, 0x08, 0xb3, 0x5a, 0xc0, 0x1b, 0x12, + 0x91, 0xf3, 0x69, 0x11, 0x89, 0xa5, 0xe5, 0xf4, 0x42, 0x7e, 0x2c, 0x7e, 0x40, 0x95, 0x4d, 0x2c, + 0xd7, 0x5c, 0x29, 0x2d, 0xa7, 0x1c, 0x3f, 0x9c, 0x8b, 0xec, 0x23, 0x31, 0x94, 0x0d, 0x98, 0x97, + 0xd7, 0x1d, 0x4a, 0x02, 0x7f, 0x43, 0x76, 0x3a, 0xe3, 0xcc, 0x7d, 0x03, 0xf3, 0x5c, 0x0e, 0x34, + 0x32, 0x05, 0xa9, 0x5c, 0x78, 0x22, 0x33, 0x23, 0x81, 0x87, 0xb0, 0x58, 0x8f, 0x8d, 0x87, 0x59, + 0x51, 0xd7, 0x74, 0x12, 0x4a, 0x5e, 0x3a, 0x23, 0xad, 0x47, 0x80, 0xea, 0xfd, 0xc3, 0x53, 0x57, + 0x92, 0x7b, 0xee, 0xe2, 0x17, 0xe8, 0x46, 0x6c, 0x48, 0x04, 0x48, 0xab, 0x51, 0xed, 0x54, 0x32, + 0x8c, 0x18, 0xed, 0xb3, 0xa7, 0xf2, 0x2c, 0x61, 0x8f, 0xd3, 0x73, 0x0e, 0xdd, 0x8e, 0x1b, 0xba, + 0x98, 0x88, 0x85, 0x8a, 0xa0, 0x16, 0x89, 0x19, 0xbc, 0x66, 0xac, 0x81, 0xbe, 0x86, 0xd9, 0x4d, + 0x1c, 0x46, 0xa9, 0xf7, 0xd0, 0x52, 0x22, 0x59, 0x1f, 0x9f, 0x37, 0x71, 0xf1, 0xad, 0xe7, 0xfb, + 0xdb, 0x86, 0x02, 0x53, 0xae, 0x0a, 0x89, 0x1b, 0x09, 0x12, 0xbc, 0x8a, 0xe3, 0x3b, 0xa7, 0x81, + 0x91, 0x5b, 0xf7, 0x60, 0x7c, 0xcf, 0xed, 0x1e, 0x21, 0xe1, 0xc7, 0x56, 0x52, 0x57, 0x95, 0xe6, + 0x35, 0x18, 0x17, 0xbd, 0x43, 0x28, 0xb3, 0x9c, 0x73, 0xc9, 0x3c, 0x6f, 0x22, 0xb1, 0xf6, 0xdb, + 0x32, 0x4e, 0x75, 0x48, 0x6e, 0x3a, 0xc9, 0x9f, 0x78, 0x79, 0x63, 0x0d, 0xed, 0x51, 0xae, 0x27, + 0x1b, 0x40, 0xb7, 0xa2, 0xfd, 0xd4, 0x98, 0x66, 0xae, 0x84, 0xe2, 0x84, 0x1b, 0x6b, 0x48, 0xbe, + 0xb0, 0x4f, 0x21, 0x7a, 0x5b, 0xdb, 0xf6, 0x2f, 0x46, 0xf7, 0x6b, 0xc8, 0xcb, 0x1c, 0x6b, 0x52, + 0xdf, 0xc4, 0x13, 0xc3, 0x49, 0x05, 0x9e, 0x4c, 0xc7, 0xf6, 0x25, 0x4b, 0x87, 0xa8, 0xe3, 0xc7, + 0xd3, 0x90, 0x19, 0x27, 0xef, 0x33, 0x98, 0x56, 0x12, 0x90, 0xc9, 0xc5, 0x92, 0x4c, 0x4a, 0x56, + 0xd2, 0x7f, 0xa8, 0xe5, 0x3e, 0xd9, 0x34, 0xa6, 0x78, 0xc6, 0x4b, 0xb4, 0x18, 0xa1, 0x29, 0x59, + 0x9c, 0x62, 0x28, 0x68, 0x8d, 0xee, 0x77, 0xac, 0xa1, 0xab, 0x3a, 0x86, 0xb9, 0x95, 0x35, 0x00, + 0x36, 0x66, 0xda, 0x90, 0x5e, 0x6c, 0x1c, 0xd5, 0x1a, 0xd9, 0xcf, 0xda, 0x17, 0x44, 0xfa, 0x5a, + 0xec, 0x69, 0x14, 0xa9, 0xa8, 0x71, 0x52, 0x1d, 0x95, 0x09, 0x7f, 0x1b, 0x0a, 0x95, 0x16, 0xd5, + 0xb2, 0x32, 0xb1, 0x14, 0x5a, 0x91, 0x2b, 0x58, 0x2f, 0x10, 0xb4, 0x16, 0xe3, 0x79, 0xaa, 0x76, + 0xb0, 0x43, 0x23, 0xe9, 0x96, 0xe4, 0x5e, 0x1b, 0x2b, 0x4a, 0xc7, 0x30, 0x76, 0x6a, 0x03, 0x16, + 0x6a, 0x4e, 0xb7, 0x85, 0x3b, 0xaf, 0x47, 0xe6, 0x73, 0xaa, 0x6e, 0x94, 0xa4, 0x5b, 0x57, 0xe3, + 0xf8, 0x5c, 0xdb, 0x5c, 0x91, 0x47, 0x45, 0x59, 0xb5, 0x02, 0x97, 0x19, 0x13, 0x23, 0xb6, 0x98, + 0xb0, 0x4d, 0xcd, 0x7f, 0x02, 0x73, 0x1b, 0x44, 0x1d, 0xf7, 0xdb, 0x2e, 0x0b, 0xba, 0x46, 0x7a, + 0x14, 0xad, 0x11, 0x71, 0x4b, 0x24, 0x30, 0x54, 0xb2, 0x51, 0x49, 0x21, 0x4f, 0x26, 0xfc, 0x2a, + 0x2d, 0x08, 0xb2, 0x6a, 0xe2, 0x2a, 0xba, 0xf7, 0x1e, 0x89, 0x8c, 0x27, 0xb1, 0x1c, 0x43, 0xaa, + 0x42, 0x31, 0x66, 0x20, 0x2a, 0xbd, 0x3d, 0xbc, 0x92, 0x6a, 0x8b, 0xd9, 0xb0, 0x64, 0xc8, 0xdf, + 0x84, 0xde, 0x91, 0x66, 0xf1, 0xb0, 0xfc, 0x4e, 0x29, 0xe6, 0xda, 0x77, 0x4a, 0x16, 0x0f, 0x03, + 0xcd, 0xe1, 0x89, 0x9d, 0x8c, 0x0c, 0x96, 0x81, 0x85, 0xa9, 0x09, 0x98, 0xd0, 0xbb, 0x3a, 0xf5, + 0x21, 0x49, 0x9a, 0x8c, 0x2d, 0x3c, 0xa1, 0xa2, 0x17, 0xe5, 0xff, 0x91, 0x46, 0x4f, 0x5a, 0x92, + 0x26, 0x69, 0xf4, 0xa4, 0x66, 0x4f, 0x62, 0x0c, 0xde, 0x84, 0xcb, 0xb1, 0x54, 0x48, 0xe8, 0x46, + 0x9c, 0xb1, 0x23, 0x18, 0xca, 0x08, 0x3d, 0x16, 0x82, 0x9d, 0x24, 0x94, 0x9e, 0x1c, 0xc9, 0x34, + 0x46, 0x46, 0xee, 0x40, 0x9a, 0x40, 0x6a, 0xba, 0x23, 0xf4, 0x56, 0x0a, 0x0b, 0xcf, 0xc7, 0x3a, + 0x46, 0xb6, 0x0e, 0x85, 0x78, 0xb6, 0x20, 0xb4, 0x22, 0xb9, 0x94, 0x9a, 0x12, 0xa9, 0x54, 0x36, + 0x96, 0xf3, 0x4d, 0xe7, 0x61, 0x34, 0x29, 0xec, 0x1a, 0x2c, 0x3e, 0x29, 0x6a, 0xa6, 0x98, 0xc4, + 0xa4, 0xe8, 0x09, 0x60, 0x36, 0x69, 0x38, 0xb4, 0x92, 0xf6, 0xc7, 0x78, 0x3a, 0xbd, 0x91, 0x46, + 0x27, 0xba, 0xe9, 0xa9, 0x8b, 0x84, 0xaa, 0x4a, 0xbf, 0x56, 0xb4, 0x7d, 0x33, 0xd9, 0xb5, 0xb2, + 0xb1, 0x5c, 0x8e, 0xb4, 0x10, 0x4f, 0x80, 0x23, 0x89, 0x1a, 0x32, 0xe3, 0x18, 0x45, 0xf9, 0x01, + 0x2c, 0xe8, 0xb3, 0x38, 0x62, 0xbc, 0x66, 0x5b, 0x77, 0x56, 0x4b, 0x7b, 0x83, 0xc4, 0xad, 0x5c, + 0x2c, 0xc3, 0x4e, 0x82, 0xfb, 0x29, 0xe9, 0x77, 0x18, 0xf7, 0x95, 0x14, 0x3a, 0xe7, 0xe1, 0x7e, + 0x5a, 0xc6, 0x1d, 0xc9, 0x28, 0xa5, 0x5f, 0x62, 0xfb, 0x8b, 0x17, 0x5c, 0x84, 0x51, 0xe7, 0xe9, + 0x9a, 0x89, 0xce, 0x3a, 0xb5, 0x6e, 0xe4, 0xef, 0x54, 0x5d, 0xd3, 0xd8, 0xa4, 0x49, 0x7c, 0x49, + 0x1b, 0x9c, 0x2e, 0xec, 0x35, 0x98, 0x51, 0x33, 0xf8, 0x18, 0x7b, 0x71, 0x3d, 0x49, 0x23, 0x50, + 0xce, 0x5b, 0x73, 0x92, 0x0b, 0xac, 0x37, 0xcb, 0x71, 0xe6, 0x68, 0x1d, 0x32, 0x0f, 0x09, 0xa9, + 0xac, 0x19, 0xd1, 0x25, 0xb3, 0x59, 0x30, 0xcf, 0x0c, 0x24, 0xfd, 0x67, 0xcc, 0x0c, 0xbf, 0x86, + 0x66, 0x24, 0x73, 0x40, 0x1f, 0x59, 0xa8, 0xe9, 0x78, 0x90, 0x22, 0x25, 0x29, 0x69, 0x7a, 0x4a, + 0x2b, 0xa6, 0x62, 0x55, 0x43, 0x7f, 0x0b, 0x57, 0x12, 0x69, 0x87, 0xa4, 0x23, 0xcc, 0x94, 0x90, + 0x68, 0xb8, 0x16, 0xdc, 0x22, 0x03, 0x8e, 0x21, 0x36, 0x56, 0x47, 0x13, 0x4d, 0xee, 0xa5, 0x3b, + 0xe2, 0x5d, 0x46, 0x5a, 0xe7, 0x4c, 0xc9, 0x8d, 0x8c, 0x1c, 0xdc, 0x87, 0xc5, 0xd4, 0xb4, 0x46, + 0xd2, 0xac, 0x18, 0x96, 0xf4, 0xc8, 0x48, 0xf5, 0x2f, 0xd2, 0xb4, 0xc2, 0xb1, 0x94, 0x39, 0xd2, + 0x0f, 0x61, 0x4c, 0x93, 0x24, 0xfd, 0x10, 0xe6, 0xfc, 0x45, 0x8c, 0x9b, 0x3b, 0xb0, 0x90, 0x96, + 0x84, 0x48, 0xf1, 0xdb, 0x19, 0x33, 0x14, 0xa5, 0x70, 0xd4, 0x16, 0xab, 0xdd, 0x40, 0x6d, 0x48, + 0x4a, 0x22, 0x23, 0x07, 0xbe, 0x17, 0x89, 0xa6, 0x92, 0xa9, 0x83, 0xe4, 0x69, 0x6d, 0x44, 0x6e, + 0xa1, 0x21, 0x46, 0xe5, 0xe5, 0xba, 0x7b, 0xd4, 0x55, 0xb2, 0xfc, 0x48, 0x93, 0x32, 0x99, 0x5e, + 0x48, 0x6a, 0x96, 0xb4, 0xa4, 0x40, 0x4f, 0x60, 0x41, 0x6c, 0xb1, 0x6a, 0x56, 0x1c, 0x94, 0xc0, + 0x89, 0xde, 0x52, 0x48, 0x2d, 0x93, 0x9a, 0x46, 0x87, 0x9d, 0xc9, 0xe8, 0xef, 0x20, 0x29, 0x67, + 0x32, 0x25, 0x5d, 0x4d, 0x49, 0xcf, 0x6c, 0x83, 0xbe, 0xa0, 0x67, 0x32, 0x96, 0xbd, 0xd0, 0xec, + 0x14, 0xd6, 0x28, 0x45, 0x3a, 0x6d, 0x4d, 0xb8, 0x0d, 0x69, 0x83, 0x3a, 0xe5, 0xd1, 0xc7, 0x2c, + 0x8a, 0xa4, 0x1f, 0xb3, 0xd4, 0x8e, 0x9a, 0x9d, 0x33, 0x33, 0xea, 0x03, 0x6d, 0xc9, 0xab, 0x94, + 0x4c, 0x0e, 0x92, 0x57, 0x69, 0xf9, 0x11, 0xa8, 0x55, 0xbf, 0x2f, 0x4c, 0xb8, 0x88, 0xde, 0x8d, + 0xa1, 0x09, 0x0e, 0x4a, 0x2b, 0xc3, 0xb3, 0x02, 0xf0, 0xdb, 0x81, 0x42, 0xfc, 0x0d, 0x39, 0x4a, + 0xcb, 0x4f, 0xa1, 0x3c, 0xa4, 0x97, 0x86, 0x88, 0xf1, 0xf1, 0xf9, 0x9e, 0x30, 0x0f, 0x75, 0xba, + 0x86, 0x2c, 0x05, 0x2a, 0xe9, 0xe1, 0x66, 0x44, 0xf4, 0x9c, 0x5c, 0x35, 0xe2, 0x12, 0xcf, 0xd5, + 0x55, 0x33, 0x22, 0xe5, 0x05, 0xba, 0x2b, 0xc2, 0x54, 0xd2, 0x73, 0x1e, 0xbd, 0xab, 0x9b, 0x59, + 0x43, 0x02, 0x78, 0x47, 0xde, 0xbf, 0xa0, 0xdf, 0x15, 0xc9, 0x1c, 0x93, 0x19, 0x41, 0xde, 0x89, + 0xf9, 0x61, 0xd2, 0x43, 0x3e, 0x4b, 0xc3, 0x12, 0x8e, 0xa0, 0xc7, 0xf4, 0x59, 0xdf, 0x93, 0xed, + 0xf5, 0x1a, 0xff, 0xb5, 0x53, 0xcf, 0x4f, 0x38, 0xb8, 0x95, 0x5f, 0xec, 0x88, 0x98, 0xcc, 0xaa, + 0x68, 0x88, 0x8d, 0x35, 0x54, 0xa7, 0x7e, 0x56, 0x0d, 0x9a, 0xe2, 0xe3, 0x4e, 0x21, 0x58, 0x4a, + 0x27, 0x48, 0x9d, 0xfe, 0x1b, 0x62, 0x37, 0xd3, 0xbb, 0x69, 0xe8, 0xc3, 0x30, 0x2b, 0x80, 0x89, + 0x4d, 0x3a, 0x19, 0xd1, 0xbb, 0x51, 0x72, 0xc4, 0x38, 0x56, 0xaf, 0x3c, 0xde, 0x79, 0x25, 0x8e, + 0x69, 0x88, 0x8d, 0x55, 0xce, 0x31, 0x0d, 0x7a, 0x31, 0x8e, 0xc5, 0x08, 0xea, 0x1c, 0xd3, 0xbb, + 0x69, 0xe8, 0xc3, 0x68, 0x8e, 0xa5, 0x93, 0x39, 0x2f, 0xc7, 0xbe, 0xa5, 0xfb, 0xf3, 0x26, 0x7d, + 0x64, 0x76, 0x21, 0x9e, 0x15, 0x85, 0x05, 0xab, 0xa3, 0x36, 0xd6, 0xd0, 0x53, 0x9a, 0xb1, 0x32, + 0x06, 0x3f, 0x1f, 0xdf, 0x96, 0x4d, 0x44, 0x29, 0xe7, 0xb6, 0x61, 0x91, 0x71, 0x2e, 0xde, 0x5d, + 0x63, 0x5f, 0x8c, 0xc3, 0xde, 0x14, 0xc6, 0x4e, 0x9c, 0xd4, 0x45, 0xf9, 0xb7, 0x4e, 0x45, 0x64, + 0xdf, 0x27, 0xf6, 0x69, 0x3b, 0x69, 0xbc, 0xea, 0x44, 0x84, 0x67, 0x5c, 0xaf, 0xde, 0x58, 0x45, + 0xdb, 0x74, 0x16, 0x74, 0xf0, 0x30, 0xeb, 0x3e, 0x9d, 0x0c, 0x65, 0xd2, 0x96, 0x30, 0x88, 0x62, + 0x7d, 0x32, 0xb5, 0x6d, 0xee, 0x94, 0x3c, 0xfa, 0x9c, 0x73, 0x74, 0x26, 0x16, 0xb1, 0x8d, 0x9d, + 0x9d, 0x34, 0x46, 0x71, 0x26, 0xfe, 0x4b, 0xe1, 0xe8, 0x77, 0x20, 0x2f, 0x90, 0x47, 0x33, 0x24, + 0x8e, 0x4d, 0x19, 0xf2, 0x35, 0x4c, 0x73, 0x86, 0xd0, 0x1e, 0x98, 0x5a, 0x32, 0x76, 0xff, 0x2b, + 0x98, 0xe6, 0x6c, 0x18, 0x3a, 0x02, 0xf3, 0xb5, 0xe2, 0xe2, 0x26, 0x0e, 0x53, 0x7e, 0xc9, 0x76, + 0xd4, 0x60, 0xd2, 0x7e, 0x38, 0x17, 0x35, 0xe8, 0xf3, 0x5c, 0xd3, 0xaf, 0x0e, 0x9b, 0x48, 0x8e, + 0xfc, 0xcd, 0x63, 0x42, 0xb7, 0x6e, 0xa6, 0x3b, 0x12, 0xdf, 0x38, 0xfa, 0x5d, 0x58, 0xa6, 0x77, + 0x10, 0x17, 0xed, 0xb1, 0xf9, 0x90, 0x72, 0x2d, 0x0a, 0x3f, 0x88, 0xff, 0xe0, 0xb1, 0x89, 0xd8, + 0xa8, 0xdf, 0x5a, 0x26, 0x54, 0xeb, 0x46, 0xaa, 0xa3, 0xb0, 0x87, 0x6c, 0x46, 0xd7, 0xe9, 0xd8, + 0x2f, 0xd8, 0xdb, 0xe1, 0x9a, 0x26, 0xf6, 0x0b, 0xcc, 0xa3, 0xa2, 0x27, 0xe2, 0xbf, 0xf1, 0x4c, + 0xa8, 0xd4, 0x13, 0x54, 0x4c, 0xb5, 0x87, 0x6d, 0x3e, 0x74, 0x68, 0xe7, 0xec, 0x8d, 0xf9, 0x72, + 0x24, 0x2f, 0x73, 0x97, 0x20, 0xc5, 0xb6, 0xd7, 0x32, 0x73, 0x94, 0x66, 0xd5, 0x58, 0x87, 0x00, + 0x55, 0xd8, 0x1e, 0xaf, 0xe6, 0xf0, 0x50, 0xbc, 0x88, 0xa9, 0xc9, 0x3d, 0xe2, 0x24, 0xd8, 0xd9, + 0x84, 0xfe, 0x2c, 0xb4, 0x72, 0x36, 0x51, 0xb2, 0x1b, 0x94, 0xf4, 0xdc, 0x03, 0x5c, 0x85, 0xd1, + 0x04, 0x04, 0xea, 0x7d, 0x91, 0x9a, 0xdf, 0x40, 0x3d, 0x9b, 0xe8, 0x99, 0x18, 0xe4, 0xd9, 0x84, + 0x36, 0xa8, 0x53, 0x1e, 0x7d, 0x36, 0xa1, 0x48, 0xfa, 0xd9, 0x44, 0xed, 0xa8, 0x79, 0xe1, 0xa1, + 0x64, 0x2a, 0x06, 0x79, 0xee, 0x36, 0x66, 0x69, 0x18, 0x72, 0xa5, 0x34, 0x9f, 0x92, 0x74, 0x47, + 0xda, 0xfc, 0xe6, 0x84, 0x3c, 0x25, 0xfd, 0x7e, 0xe4, 0x7e, 0x06, 0xed, 0xd2, 0xbc, 0xd1, 0x69, + 0xbf, 0x96, 0x6d, 0x92, 0x9f, 0xa1, 0x3f, 0xcf, 0x4d, 0xe8, 0xd5, 0xd3, 0xe9, 0x0d, 0xc5, 0x1b, + 0x72, 0xac, 0xbb, 0xc6, 0x23, 0x4a, 0x2e, 0xd0, 0x45, 0xb3, 0x88, 0x4f, 0x31, 0x1f, 0xb1, 0x19, + 0xb5, 0xa0, 0xfe, 0x3c, 0x35, 0xdd, 0xb2, 0xee, 0xc2, 0x24, 0x43, 0x32, 0xee, 0x36, 0xda, 0x4f, + 0x5a, 0xa3, 0x0f, 0xc4, 0xc5, 0x2d, 0x41, 0xd1, 0x8a, 0x8c, 0xfd, 0xfa, 0x00, 0xf2, 0xcc, 0xed, + 0x76, 0x7e, 0x94, 0x2f, 0xc4, 0xf5, 0xee, 0xb0, 0x8e, 0x99, 0xa3, 0x2a, 0x66, 0x55, 0x87, 0xf3, + 0xc5, 0x19, 0xf9, 0x15, 0x75, 0x7d, 0x0a, 0x57, 0x83, 0x19, 0x7f, 0x31, 0xf1, 0xf3, 0xe3, 0x94, + 0xa5, 0x9f, 0x52, 0xff, 0xab, 0xcc, 0x5e, 0x65, 0xea, 0x7e, 0xf2, 0xc7, 0xcb, 0xd1, 0x17, 0x30, + 0xc7, 0x98, 0x2b, 0x91, 0x93, 0x95, 0x86, 0xf0, 0x6c, 0x8e, 0xb1, 0xf9, 0x55, 0x90, 0x7f, 0x47, + 0x38, 0x6a, 0x47, 0x76, 0xfb, 0x3c, 0x2e, 0xda, 0xd1, 0xac, 0x33, 0x51, 0xf9, 0x5d, 0xba, 0xe9, + 0xa6, 0xa7, 0x5c, 0x31, 0x12, 0xbb, 0xa3, 0xb8, 0xa0, 0x87, 0x27, 0x6b, 0x39, 0xa1, 0xd1, 0x81, + 0xe9, 0x3f, 0x60, 0x7f, 0x7b, 0x04, 0x15, 0xc1, 0x80, 0x9f, 0x8d, 0xac, 0x27, 0xfd, 0x5c, 0x3c, + 0x69, 0x79, 0x7a, 0x7b, 0x23, 0x12, 0xaf, 0xa4, 0xb8, 0x0c, 0x0d, 0xf9, 0x4c, 0x04, 0x41, 0xfd, + 0xda, 0x71, 0xe8, 0x18, 0xcc, 0x47, 0xb4, 0x28, 0xf7, 0xf8, 0x05, 0x27, 0xc1, 0x6c, 0x46, 0xa1, + 0x64, 0x96, 0x17, 0x34, 0xec, 0x45, 0x95, 0xea, 0x90, 0x35, 0x65, 0x87, 0xd9, 0x14, 0xa1, 0xa8, + 0xb1, 0xe7, 0x89, 0xa6, 0x87, 0x8e, 0x43, 0x4e, 0x67, 0x3c, 0x18, 0xf3, 0x8d, 0x10, 0x4a, 0xce, + 0xf6, 0xc5, 0x09, 0x49, 0xc7, 0x70, 0x8c, 0x90, 0x35, 0x64, 0x7a, 0x47, 0x3b, 0xbd, 0x8a, 0x86, + 0x79, 0xbd, 0xf8, 0x84, 0x3a, 0x51, 0xd8, 0x5f, 0x32, 0x15, 0x8d, 0xdc, 0xf6, 0x8d, 0x69, 0x71, + 0xe4, 0xec, 0x0e, 0xc9, 0x63, 0x53, 0x8b, 0x7e, 0xeb, 0x45, 0xcb, 0x5d, 0x53, 0xb3, 0x77, 0xa4, + 0xbb, 0x2e, 0x2d, 0xa9, 0x4d, 0x09, 0x44, 0xa1, 0xbd, 0x43, 0xd6, 0xba, 0x29, 0xef, 0x4a, 0x14, + 0xba, 0x34, 0x3c, 0x2b, 0x8d, 0x5c, 0xeb, 0x23, 0x13, 0xb8, 0xec, 0xc2, 0x42, 0x5a, 0xbe, 0x14, + 0x39, 0x69, 0x43, 0x92, 0xa9, 0xa4, 0xc6, 0x47, 0xed, 0xc1, 0x62, 0x6a, 0xce, 0x12, 0x79, 0x43, + 0x32, 0x2c, 0xa3, 0x49, 0x2a, 0xc5, 0xef, 0x60, 0xc9, 0x90, 0xa0, 0x23, 0x72, 0x20, 0x0e, 0x4d, + 0xe0, 0x61, 0x14, 0x88, 0x1f, 0xa0, 0x64, 0xce, 0xfd, 0x80, 0xee, 0xe8, 0x4e, 0x50, 0x73, 0xc6, + 0x85, 0x52, 0x6a, 0xb2, 0x1a, 0xb4, 0x4f, 0xf3, 0xe8, 0xa5, 0x25, 0x83, 0x90, 0xfd, 0x1e, 0x9e, + 0x2c, 0xc2, 0x10, 0xd7, 0xb6, 0x64, 0xc8, 0xff, 0x30, 0x84, 0xea, 0x39, 0x7a, 0xbb, 0x2b, 0xf4, + 0x92, 0x9e, 0x10, 0x20, 0x16, 0x22, 0x9f, 0x9a, 0x2d, 0x20, 0xb5, 0x9f, 0x0f, 0x61, 0x56, 0x7b, + 0xe0, 0x29, 0xc5, 0x3f, 0xed, 0x59, 0xb0, 0xf4, 0x56, 0xa7, 0xbe, 0x09, 0xad, 0x16, 0x7e, 0xf9, + 0x3f, 0x56, 0x32, 0xbf, 0xfc, 0xf5, 0x4a, 0xe6, 0xbf, 0xfd, 0x7a, 0x25, 0xf3, 0xab, 0x5f, 0xaf, + 0x64, 0x0e, 0x27, 0x69, 0xf5, 0xb5, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x07, 0x03, 0xcc, + 0x29, 0x90, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -22123,6 +22333,11 @@ func (m *AddMFADeviceRequestInit) MarshalToSizedBuffer(dAtA []byte) (int, error) i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.DeviceUsage != 0 { + i = encodeVarintAuthservice(dAtA, i, uint64(m.DeviceUsage)) + i-- + dAtA[i] = 0x20 + } if m.DeviceType != 0 { i = encodeVarintAuthservice(dAtA, i, uint64(m.DeviceType)) i-- @@ -24443,6 +24658,60 @@ func (m *UserCredentials) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ContextUser) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ContextUser) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ContextUser) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + return len(dAtA) - i, nil +} + +func (m *Passwordless) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Passwordless) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Passwordless) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + return len(dAtA) - i, nil +} + func (m *CreateAuthenticateChallengeRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -24514,6 +24783,48 @@ func (m *CreateAuthenticateChallengeRequest_RecoveryStartTokenID) MarshalToSized dAtA[i] = 0x12 return len(dAtA) - i, nil } +func (m *CreateAuthenticateChallengeRequest_ContextUser) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CreateAuthenticateChallengeRequest_ContextUser) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.ContextUser != nil { + { + size, err := m.ContextUser.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthservice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + return len(dAtA) - i, nil +} +func (m *CreateAuthenticateChallengeRequest_Passwordless) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CreateAuthenticateChallengeRequest_Passwordless) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.Passwordless != nil { + { + size, err := m.Passwordless.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthservice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + return len(dAtA) - i, nil +} func (m *CreatePrivilegeTokenRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -24577,6 +24888,11 @@ func (m *CreateRegisterChallengeRequest) MarshalToSizedBuffer(dAtA []byte) (int, i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.DeviceUsage != 0 { + i = encodeVarintAuthservice(dAtA, i, uint64(m.DeviceUsage)) + i-- + dAtA[i] = 0x18 + } if m.DeviceType != 0 { i = encodeVarintAuthservice(dAtA, i, uint64(m.DeviceType)) i-- @@ -24712,6 +25028,48 @@ func (m *PaginatedResource_KubeService) MarshalToSizedBuffer(dAtA []byte) (int, } return len(dAtA) - i, nil } +func (m *PaginatedResource_WindowsDesktop) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PaginatedResource_WindowsDesktop) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.WindowsDesktop != nil { + { + size, err := m.WindowsDesktop.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthservice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + return len(dAtA) - i, nil +} +func (m *PaginatedResource_KubeCluster) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PaginatedResource_KubeCluster) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.KubeCluster != nil { + { + size, err := m.KubeCluster.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthservice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + return len(dAtA) - i, nil +} func (m *ListResourcesRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -24736,6 +25094,16 @@ func (m *ListResourcesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + { + size, err := m.WindowsDesktopFilter.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthservice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x52 if m.NeedTotalCount { i-- if m.NeedTotalCount { @@ -24932,12 +25300,12 @@ func (m *CreateSessionTrackerRequest) MarshalToSizedBuffer(dAtA []byte) (int, er i-- dAtA[i] = 0x5a } - n96, err96 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err96 != nil { - return 0, err96 + n101, err101 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err101 != nil { + return 0, err101 } - i -= n96 - i = encodeVarintAuthservice(dAtA, i, uint64(n96)) + i -= n101 + i = encodeVarintAuthservice(dAtA, i, uint64(n101)) i-- dAtA[i] = 0x52 if m.Initiator != nil { @@ -27615,6 +27983,9 @@ func (m *AddMFADeviceRequestInit) Size() (n int) { if m.DeviceType != 0 { n += 1 + sovAuthservice(uint64(m.DeviceType)) } + if m.DeviceUsage != 0 { + n += 1 + sovAuthservice(uint64(m.DeviceUsage)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -28719,6 +29090,30 @@ func (m *UserCredentials) Size() (n int) { return n } +func (m *ContextUser) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Passwordless) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func (m *CreateAuthenticateChallengeRequest) Size() (n int) { if m == nil { return 0 @@ -28756,6 +29151,30 @@ func (m *CreateAuthenticateChallengeRequest_RecoveryStartTokenID) Size() (n int) n += 1 + l + sovAuthservice(uint64(l)) return n } +func (m *CreateAuthenticateChallengeRequest_ContextUser) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ContextUser != nil { + l = m.ContextUser.Size() + n += 1 + l + sovAuthservice(uint64(l)) + } + return n +} +func (m *CreateAuthenticateChallengeRequest_Passwordless) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Passwordless != nil { + l = m.Passwordless.Size() + n += 1 + l + sovAuthservice(uint64(l)) + } + return n +} func (m *CreatePrivilegeTokenRequest) Size() (n int) { if m == nil { return 0 @@ -28785,6 +29204,9 @@ func (m *CreateRegisterChallengeRequest) Size() (n int) { if m.DeviceType != 0 { n += 1 + sovAuthservice(uint64(m.DeviceType)) } + if m.DeviceUsage != 0 { + n += 1 + sovAuthservice(uint64(m.DeviceUsage)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -28854,6 +29276,30 @@ func (m *PaginatedResource_KubeService) Size() (n int) { } return n } +func (m *PaginatedResource_WindowsDesktop) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.WindowsDesktop != nil { + l = m.WindowsDesktop.Size() + n += 1 + l + sovAuthservice(uint64(l)) + } + return n +} +func (m *PaginatedResource_KubeCluster) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.KubeCluster != nil { + l = m.KubeCluster.Size() + n += 1 + l + sovAuthservice(uint64(l)) + } + return n +} func (m *ListResourcesRequest) Size() (n int) { if m == nil { return 0 @@ -28898,6 +29344,8 @@ func (m *ListResourcesRequest) Size() (n int) { if m.NeedTotalCount { n += 2 } + l = m.WindowsDesktopFilter.Size() + n += 1 + l + sovAuthservice(uint64(l)) if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -39958,6 +40406,25 @@ func (m *AddMFADeviceRequestInit) Unmarshal(dAtA []byte) error { break } } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DeviceUsage", wireType) + } + m.DeviceUsage = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DeviceUsage |= DeviceUsage(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) @@ -45360,6 +45827,108 @@ func (m *UserCredentials) Unmarshal(dAtA []byte) error { } return nil } +func (m *ContextUser) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ContextUser: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ContextUser: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipAuthservice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthservice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Passwordless) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Passwordless: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Passwordless: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipAuthservice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthservice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *CreateAuthenticateChallengeRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -45456,6 +46025,76 @@ func (m *CreateAuthenticateChallengeRequest) Unmarshal(dAtA []byte) error { } m.Request = &CreateAuthenticateChallengeRequest_RecoveryStartTokenID{string(dAtA[iNdEx:postIndex])} iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContextUser", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &ContextUser{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Request = &CreateAuthenticateChallengeRequest_ContextUser{v} + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Passwordless", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &Passwordless{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Request = &CreateAuthenticateChallengeRequest_Passwordless{v} + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) @@ -45645,6 +46284,25 @@ func (m *CreateRegisterChallengeRequest) Unmarshal(dAtA []byte) error { break } } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DeviceUsage", wireType) + } + m.DeviceUsage = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DeviceUsage |= DeviceUsage(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) @@ -45836,6 +46494,76 @@ func (m *PaginatedResource) Unmarshal(dAtA []byte) error { } m.Resource = &PaginatedResource_KubeService{v} iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WindowsDesktop", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &types.WindowsDesktopV3{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Resource = &PaginatedResource_WindowsDesktop{v} + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KubeCluster", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &types.KubernetesClusterV3{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Resource = &PaginatedResource_KubeCluster{v} + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) @@ -46246,6 +46974,39 @@ func (m *ListResourcesRequest) Unmarshal(dAtA []byte) error { } } m.NeedTotalCount = bool(v != 0) + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WindowsDesktopFilter", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.WindowsDesktopFilter.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) diff --git a/api/client/proto/authservice.proto b/api/client/proto/authservice.proto index e7631dfd1aa95..1b08662e5ab2c 100644 --- a/api/client/proto/authservice.proto +++ b/api/client/proto/authservice.proto @@ -818,6 +818,19 @@ enum DeviceType { DEVICE_TYPE_WEBAUTHN = 3; } +enum DeviceUsage { + DEVICE_USAGE_UNSPECIFIED = 0; + + // Device intended for MFA use, but not for passwordless. + // Allows both FIDO and FIDO2 devices. + // Resident keys not required. + DEVICE_USAGE_MFA = 1; + + // Device intended for both MFA and passwordless. + // Requires a FIDO2 device and takes a resident key slot. + DEVICE_USAGE_PASSWORDLESS = 2; +} + // MFAAuthenticateChallenge is a challenge for all MFA devices registered for a // user. message MFAAuthenticateChallenge { @@ -928,7 +941,11 @@ message AddMFADeviceResponse { // AddMFADeviceRequestInit describes the new MFA device. message AddMFADeviceRequestInit { string DeviceName = 1; + reserved 2; // LegacyDeviceType LegacyType DeviceType DeviceType = 3; + // DeviceUsage is the requested usage for the device. + // Defaults to DEVICE_USAGE_MFA. + DeviceUsage DeviceUsage = 4 [ (gogoproto.jsontag) = "device_usage,omitempty" ]; } // AddMFADeviceResponseAck is a confirmation of successful device registration. @@ -1374,12 +1391,17 @@ message UserCredentials { bytes Password = 2 [ (gogoproto.jsontag) = "password" ]; } +// ContextUser marks requests that rely in the currently authenticated user. +message ContextUser {} + +// Passwordless marks requests for passwordless challenges. +message Passwordless {} + // CreateAuthenticateChallengeRequest is a request for creating MFA authentication challenges for a // users mfa devices. message CreateAuthenticateChallengeRequest { // Request defines how the request will be verified before creating challenges. - // This field can be empty, which implies the request is to create challenges for the - // user in context (logged in user). + // An empty Request is equivalent to context_user being set. oneof Request { // UserCredentials verifies request with username and password. Used with logins or // when the logged in user wants to change their password. @@ -1389,6 +1411,12 @@ message CreateAuthenticateChallengeRequest { // VerifyAccountRecovery (step 2 of the recovery process after RPC StartAccountRecovery). string RecoveryStartTokenID = 2 [ (gogoproto.jsontag) = "recovery_start_token_id,omitempty" ]; + // ContextUser issues a challenge for the currently-authenticated user. + // Default option if no other is provided. + ContextUser ContextUser = 3 [ (gogoproto.jsontag) = "context_user,omitempty" ]; + // Passwordless issues a passwordless challenge (authenticated user not + // required). + Passwordless Passwordless = 4 [ (gogoproto.jsontag) = "passwordless,omitempty" ]; } } @@ -1412,6 +1440,9 @@ message CreateRegisterChallengeRequest { string TokenID = 1 [ (gogoproto.jsontag) = "token_id" ]; // DeviceType is the type of MFA device to make a register challenge for. DeviceType DeviceType = 2 [ (gogoproto.jsontag) = "device_type" ]; + // DeviceUsage is the requested usage for the device. + // Defaults to DEVICE_USAGE_MFA. + DeviceUsage DeviceUsage = 3 [ (gogoproto.jsontag) = "device_usage,omitempty" ]; } // PaginatedResource represents one of the supported resources. @@ -1426,6 +1457,12 @@ message PaginatedResource { types.ServerV2 Node = 3 [ (gogoproto.jsontag) = "node,omitempty" ]; // KubeService represents a KubernetesService resource. types.ServerV2 KubeService = 4 [ (gogoproto.jsontag) = "kube_service,omitempty" ]; + // WindowsDesktop represents a WindowsDesktop resource. + types.WindowsDesktopV3 WindowsDesktop = 5 + [ (gogoproto.jsontag) = "windows_desktop,omitempty" ]; + // KubeCluster represents a KubeCluster resource. + types.KubernetesClusterV3 KubeCluster = 6 + [ (gogoproto.jsontag) = "kube_cluster,omitempty" ]; } } @@ -1454,6 +1491,9 @@ message ListResourcesRequest { // NeedTotalCount indicates whether or not the caller also wants the total number of resources // after filtering. bool NeedTotalCount = 9 [ (gogoproto.jsontag) = "need_total_count,omitempty" ]; + // WindowsDesktopFilter specifies windows desktop specific filters. + types.WindowsDesktopFilter WindowsDesktopFilter = 10 + [ (gogoproto.nullable) = false, (gogoproto.jsontag) = "windows_desktop_filter,omitempty" ]; } // ListResourceResponse response of ListResources. diff --git a/api/client/proto/types.go b/api/client/proto/types.go index ece8f561c6ba0..9e1b789474628 100644 --- a/api/client/proto/types.go +++ b/api/client/proto/types.go @@ -88,5 +88,5 @@ func (req *ListResourcesRequest) CheckAndSetDefaults() error { // RequiresFakePagination checks if we need to fallback to GetXXX calls // that retrieves entire resources upfront rather than working with subsets. func (req *ListResourcesRequest) RequiresFakePagination() bool { - return req.SortBy.Field != "" || req.NeedTotalCount + return req.SortBy.Field != "" || req.NeedTotalCount || req.ResourceType == types.KindKubernetesCluster } diff --git a/api/client/proxy.go b/api/client/proxy.go index e128bb1b275a0..7e4f80c59a09e 100644 --- a/api/client/proxy.go +++ b/api/client/proxy.go @@ -46,8 +46,8 @@ func DialProxyWithDialer(ctx context.Context, proxyAddr *url.URL, addr string, d Host: addr, Header: make(http.Header), } - err = connectReq.Write(conn) - if err != nil { + + if err := connectReq.Write(conn); err != nil { log.Warnf("Unable to write to proxy: %v.", err) return nil, trace.Wrap(err) } diff --git a/api/client/proxy/proxy.go b/api/client/proxy/proxy.go index 7477d6b897b21..c896fb7ec0711 100644 --- a/api/client/proxy/proxy.go +++ b/api/client/proxy/proxy.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package proxy import ( diff --git a/api/client/webclient/webclient.go b/api/client/webclient/webclient.go index e0f5e1b0d8f77..f65877cc161d9 100644 --- a/api/client/webclient/webclient.go +++ b/api/client/webclient/webclient.go @@ -40,19 +40,54 @@ import ( log "github.com/sirupsen/logrus" ) +// Config specifies information when building requests with the +// webclient. +type Config struct { + // Context is a context for creating webclient requests. + Context context.Context + // ProxyAddr specifies the teleport proxy address for requests. + ProxyAddr string + // Insecure turns off TLS certificate verification when enabled. + Insecure bool + // Pool defines the set of root CAs to use when verifying server + // certificates. + Pool *x509.CertPool + // ConnectorName is the name of the ODIC or SAML connector. + ConnectorName string + // ExtraHeaders is a map of extra HTTP headers to be included in + // requests. + ExtraHeaders map[string]string +} + +// CheckAndSetDefaults checks and sets defaults +func (c *Config) CheckAndSetDefaults() error { + message := "webclient config: %s" + if c.Context == nil { + return trace.BadParameter(message, "missing parameter Context") + } + if c.ProxyAddr == "" && os.Getenv(defaults.TunnelPublicAddrEnvar) == "" { + return trace.BadParameter(message, "missing parameter ProxyAddr") + } + + return nil +} + // newWebClient creates a new client to the HTTPS web proxy. -func newWebClient(insecure bool, pool *x509.CertPool) *http.Client { +func newWebClient(cfg *Config) (*http.Client, error) { + if err := cfg.CheckAndSetDefaults(); err != nil { + return nil, trace.Wrap(err) + } return &http.Client{ Transport: &proxy.ProxyAwareRoundTripper{Transport: http.Transport{ TLSClientConfig: &tls.Config{ - RootCAs: pool, - InsecureSkipVerify: insecure, + RootCAs: cfg.Pool, + InsecureSkipVerify: cfg.Insecure, }, Proxy: func(req *http.Request) (*url.URL, error) { return httpproxy.FromEnvironment().ProxyFunc()(req.URL) }, - }}, - } + }, + }}, nil } // doWithFallback attempts to execute an HTTP request using https, and then @@ -61,9 +96,13 @@ func newWebClient(insecure bool, pool *x509.CertPool) *http.Client { // * The target host must resolve to the loopback address. // If these conditions are not met, then the plain-HTTP fallback is not allowed, // and a the HTTPS failure will be considered final. -func doWithFallback(clt *http.Client, allowPlainHTTP bool, req *http.Request) (*http.Response, error) { +func doWithFallback(clt *http.Client, allowPlainHTTP bool, extraHeaders map[string]string, req *http.Request) (*http.Response, error) { // first try https and see how that goes req.URL.Scheme = "https" + for k, v := range extraHeaders { + req.Header.Add(k, v) + } + log.Debugf("Attempting %s %s%s", req.Method, req.URL.Host, req.URL.Path) resp, err := clt.Do(req) @@ -93,18 +132,21 @@ func doWithFallback(clt *http.Client, allowPlainHTTP bool, req *http.Request) (* // Find fetches discovery data by connecting to the given web proxy address. // It is designed to fetch proxy public addresses without any inefficiencies. -func Find(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (*PingResponse, error) { - clt := newWebClient(insecure, pool) +func Find(cfg *Config) (*PingResponse, error) { + clt, err := newWebClient(cfg) + if err != nil { + return nil, trace.Wrap(err) + } defer clt.CloseIdleConnections() - endpoint := fmt.Sprintf("https://%s/webapi/find", proxyAddr) + endpoint := fmt.Sprintf("https://%s/webapi/find", cfg.ProxyAddr) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + req, err := http.NewRequestWithContext(cfg.Context, http.MethodGet, endpoint, nil) if err != nil { return nil, trace.Wrap(err) } - resp, err := doWithFallback(clt, insecure, req) + resp, err := doWithFallback(clt, cfg.Insecure, cfg.ExtraHeaders, req) if err != nil { return nil, trace.Wrap(err) } @@ -123,21 +165,24 @@ func Find(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertP // errors before being asked for passwords. The second is to return the form // of authentication that the server supports. This also leads to better user // experience: users only get prompted for the type of authentication the server supports. -func Ping(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool, connectorName string) (*PingResponse, error) { - clt := newWebClient(insecure, pool) +func Ping(cfg *Config) (*PingResponse, error) { + clt, err := newWebClient(cfg) + if err != nil { + return nil, trace.Wrap(err) + } defer clt.CloseIdleConnections() - endpoint := fmt.Sprintf("https://%s/webapi/ping", proxyAddr) - if connectorName != "" { - endpoint = fmt.Sprintf("%s/%s", endpoint, connectorName) + endpoint := fmt.Sprintf("https://%s/webapi/ping", cfg.ProxyAddr) + if cfg.ConnectorName != "" { + endpoint = fmt.Sprintf("%s/%s", endpoint, cfg.ConnectorName) } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + req, err := http.NewRequestWithContext(cfg.Context, http.MethodGet, endpoint, nil) if err != nil { return nil, trace.Wrap(err) } - resp, err := doWithFallback(clt, insecure, req) + resp, err := doWithFallback(clt, cfg.Insecure, cfg.ExtraHeaders, req) if err != nil { return nil, trace.Wrap(err) } @@ -152,32 +197,38 @@ func Ping(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertP } // GetTunnelAddr returns the tunnel address either set in an environment variable or retrieved from the web proxy. -func GetTunnelAddr(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (string, error) { +func GetTunnelAddr(cfg *Config) (string, error) { + if err := cfg.CheckAndSetDefaults(); err != nil { + return "", trace.Wrap(err) + } // If TELEPORT_TUNNEL_PUBLIC_ADDR is set, nothing else has to be done, return it. if tunnelAddr := os.Getenv(defaults.TunnelPublicAddrEnvar); tunnelAddr != "" { return extractHostPort(tunnelAddr) } // Ping web proxy to retrieve tunnel proxy address. - pr, err := Find(ctx, proxyAddr, insecure, nil) + pr, err := Find(cfg) if err != nil { return "", trace.Wrap(err) } - return tunnelAddr(proxyAddr, pr.Proxy) + return tunnelAddr(cfg.ProxyAddr, pr.Proxy) } -func GetMOTD(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (*MotD, error) { - clt := newWebClient(insecure, pool) +func GetMOTD(cfg *Config) (*MotD, error) { + clt, err := newWebClient(cfg) + if err != nil { + return nil, trace.Wrap(err) + } defer clt.CloseIdleConnections() - endpoint := fmt.Sprintf("https://%s/webapi/motd", proxyAddr) + endpoint := fmt.Sprintf("https://%s/webapi/motd", cfg.ProxyAddr) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + req, err := http.NewRequestWithContext(cfg.Context, http.MethodGet, endpoint, nil) if err != nil { return nil, trace.Wrap(err) } - resp, err := clt.Do(req) + resp, err := doWithFallback(clt, cfg.Insecure, cfg.ExtraHeaders, req) if err != nil { return nil, trace.Wrap(err) } diff --git a/api/client/webclient/webclient_test.go b/api/client/webclient/webclient_test.go index 963b6b5f2156c..b908a9777a718 100644 --- a/api/client/webclient/webclient_test.go +++ b/api/client/webclient/webclient_test.go @@ -52,14 +52,15 @@ func TestPlainHttpFallback(t *testing.T) { desc: "Ping", handler: newPingHandler("/webapi/ping"), actionUnderTest: func(addr string, insecure bool) error { - _, err := Ping(context.Background(), addr, insecure, nil /*pool*/, "") + _, err := Ping( + &Config{Context: context.Background(), ProxyAddr: addr, Insecure: insecure}) return err }, }, { desc: "Find", handler: newPingHandler("/webapi/find"), actionUnderTest: func(addr string, insecure bool) error { - _, err := Find(context.Background(), addr, insecure, nil /*pool*/) + _, err := Find(&Config{Context: context.Background(), ProxyAddr: addr, Insecure: insecure}) return err }, }, @@ -104,7 +105,7 @@ func TestPlainHttpFallback(t *testing.T) { func TestGetTunnelAddr(t *testing.T) { t.Setenv(defaults.TunnelPublicAddrEnvar, "tunnel.example.com:4024") - tunnelAddr, err := GetTunnelAddr(context.Background(), "", true, nil) + tunnelAddr, err := GetTunnelAddr(&Config{Context: context.Background(), ProxyAddr: "", Insecure: false}) require.NoError(t, err) require.Equal(t, "tunnel.example.com:4024", tunnelAddr) } @@ -292,22 +293,33 @@ func TestExtract(t *testing.T) { func TestNewWebClientRespectHTTPProxy(t *testing.T) { t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") - client := newWebClient(false /* insecure */, nil /* pool */) + client, err := newWebClient(&Config{ + Context: context.Background(), + ProxyAddr: "localhost:3080", + }) + require.NoError(t, err) // resp should be nil, so there will be no body to close. //nolint:bodyclose - resp, err := client.Get("https://example.com") + resp, err := client.Get("https://fakedomain.example.com") // Client should try to proxy through nonexistent server at localhost. require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") + require.Contains(t, err.Error(), "lookup fakeproxy.example.com") require.Contains(t, err.Error(), "no such host") } func TestNewWebClientNoProxy(t *testing.T) { t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") - t.Setenv("NO_PROXY", "example.com") - client := newWebClient(false /* insecure */, nil /* pool */) - resp, err := client.Get("https://example.com") + t.Setenv("NO_PROXY", "fakedomain.example.com") + client, err := newWebClient(&Config{ + Context: context.Background(), + ProxyAddr: "localhost:3080", + }) require.NoError(t, err) - defer resp.Body.Close() - require.Equal(t, http.StatusOK, resp.StatusCode) + //nolint:bodyclose + resp, err := client.Get("https://fakedomain.example.com") + require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) + require.NotContains(t, err.Error(), "proxyconnect") + require.Contains(t, err.Error(), "lookup fakedomain.example.com") + require.Contains(t, err.Error(), "no such host") } diff --git a/api/go.mod b/api/go.mod index 3a7210c771f25..32f30b53bd167 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,7 +3,7 @@ module github.com/gravitational/teleport/api go 1.15 require ( - github.com/gogo/protobuf v1.3.1 + github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.4.3 github.com/google/go-cmp v0.5.4 github.com/gravitational/trace v1.1.17 diff --git a/api/go.sum b/api/go.sum index 72c1f18217730..e0d13feb2fd5c 100644 --- a/api/go.sum +++ b/api/go.sum @@ -21,8 +21,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -50,7 +50,7 @@ github.com/gravitational/trace v1.1.17/go.mod h1:n0ijrq6psJY0sOI/NzLp+xdd8xl79jj github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -68,8 +68,11 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8 h1:kACShD3qhmr/3rLmg1yXyt+N4HcwutKyPRB93s54TIU= golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -77,13 +80,18 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -93,11 +101,14 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -108,15 +119,20 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/api/identityfile/identityfile.go b/api/identityfile/identityfile.go index 2f45f8cc6a749..9f19ce811a91d 100644 --- a/api/identityfile/identityfile.go +++ b/api/identityfile/identityfile.go @@ -24,7 +24,6 @@ import ( "crypto/x509" "fmt" "io" - "io/ioutil" "os" "strings" @@ -102,7 +101,7 @@ func Write(idFile *IdentityFile, path string) error { if err := encodeIdentityFile(buf, idFile); err != nil { return trace.Wrap(err) } - if err := ioutil.WriteFile(path, buf.Bytes(), FilePermissions); err != nil { + if err := os.WriteFile(path, buf.Bytes(), FilePermissions); err != nil { return trace.ConvertSystemError(err) } return nil @@ -139,7 +138,7 @@ func ReadFile(path string) (*IdentityFile, error) { // separate file with -cert.pub suffix. if len(ident.Certs.SSH) == 0 { certFn := keypaths.IdentitySSHCertPath(path) - if ident.Certs.SSH, err = ioutil.ReadFile(certFn); err != nil { + if ident.Certs.SSH, err = os.ReadFile(certFn); err != nil { return nil, trace.Wrap(err, "could not find SSH cert in the identity file or %v", certFn) } } diff --git a/api/profile/profile.go b/api/profile/profile.go index ba63ce65d6517..7b9b701d63c8b 100644 --- a/api/profile/profile.go +++ b/api/profile/profile.go @@ -21,7 +21,6 @@ import ( "crypto/tls" "crypto/x509" "io/fs" - "io/ioutil" "net" "os" "os/user" @@ -118,7 +117,7 @@ func (p *Profile) TLSConfig() (*tls.Config, error) { if info.IsDir() { return nil } - cert, err := ioutil.ReadFile(path) + cert, err := os.ReadFile(path) if err != nil { return trace.ConvertSystemError(err) } @@ -139,23 +138,17 @@ func (p *Profile) TLSConfig() (*tls.Config, error) { // SSHClientConfig returns the profile's associated SSHClientConfig. func (p *Profile) SSHClientConfig() (*ssh.ClientConfig, error) { - cert, err := ioutil.ReadFile(p.SSHCertPath()) + cert, err := os.ReadFile(p.SSHCertPath()) if err != nil { - // Try reading SSHCert from old cert path, return original error otherwise - // DELETE IN 8.0.0 - var err2 error - cert, err2 = ioutil.ReadFile(p.OldSSHCertPath()) - if err2 != nil { - return nil, trace.Wrap(err) - } + return nil, trace.Wrap(err) } - key, err := ioutil.ReadFile(p.UserKeyPath()) + key, err := os.ReadFile(p.UserKeyPath()) if err != nil { return nil, trace.Wrap(err) } - caCerts, err := ioutil.ReadFile(p.KnownHostsPath()) + caCerts, err := os.ReadFile(p.KnownHostsPath()) if err != nil { return nil, trace.Wrap(err) } @@ -174,7 +167,7 @@ func SetCurrentProfileName(dir string, name string) error { } path := filepath.Join(dir, currentProfileFilename) - if err := ioutil.WriteFile(path, []byte(strings.TrimSpace(name)+"\n"), 0660); err != nil { + if err := os.WriteFile(path, []byte(strings.TrimSpace(name)+"\n"), 0660); err != nil { return trace.Wrap(err) } return nil @@ -186,7 +179,7 @@ func GetCurrentProfileName(dir string) (name string, err error) { return "", trace.BadParameter("cannot get current profile: missing dir") } - data, err := ioutil.ReadFile(filepath.Join(dir, currentProfileFilename)) + data, err := os.ReadFile(filepath.Join(dir, currentProfileFilename)) if err != nil { if os.IsNotExist(err) { return "", trace.NotFound("current-profile is not set") @@ -205,7 +198,7 @@ func ListProfileNames(dir string) ([]string, error) { if dir == "" { return nil, trace.BadParameter("cannot list profiles: missing dir") } - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) if err != nil { return nil, trace.Wrap(err) } @@ -215,7 +208,8 @@ func ListProfileNames(dir string) ([]string, error) { if file.IsDir() { continue } - if file.Mode()&os.ModeSymlink != 0 { + + if file.Type()&os.ModeSymlink != 0 { continue } if !strings.HasSuffix(file.Name(), ".yaml") { @@ -266,7 +260,7 @@ func FromDir(dir string, name string) (*Profile, error) { // profileFromFile loads the profile from a YAML file. func profileFromFile(filePath string) (*Profile, error) { - bytes, err := ioutil.ReadFile(filePath) + bytes, err := os.ReadFile(filePath) if err != nil { return nil, trace.ConvertSystemError(err) } @@ -306,7 +300,7 @@ func (p *Profile) saveToFile(filepath string) error { if err != nil { return trace.Wrap(err) } - if err = ioutil.WriteFile(filepath, bytes, 0660); err != nil { + if err = os.WriteFile(filepath, bytes, 0660); err != nil { return trace.Wrap(err) } return nil @@ -352,12 +346,6 @@ func (p *Profile) SSHCertPath() string { return keypaths.SSHCertPath(p.Dir, p.Name(), p.Username, p.SiteName) } -// OldSSHCertPath returns the old (before v6.1) path to the profile's ssh certificate. -// DELETE IN 8.0.0 -func (p *Profile) OldSSHCertPath() string { - return keypaths.OldSSHCertPath(p.Dir, p.Name(), p.Username) -} - // KnownHostsPath returns the path to the profile's ssh certificate authorities. func (p *Profile) KnownHostsPath() string { return keypaths.KnownHostsPath(p.Dir) diff --git a/api/types/app_test.go b/api/types/app_test.go index 731edb7ffa1a2..9b5b12d23c806 100644 --- a/api/types/app_test.go +++ b/api/types/app_test.go @@ -141,8 +141,6 @@ func TestAppServerSorter(t *testing.T) { for _, c := range cases { c := c t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) { - t.Parallel() - sortBy := SortBy{Field: c.fieldName, IsDesc: true} servers := AppServers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) @@ -152,8 +150,6 @@ func TestAppServerSorter(t *testing.T) { }) t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) { - t.Parallel() - sortBy := SortBy{Field: c.fieldName} servers := AppServers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) diff --git a/api/types/appserver.go b/api/types/appserver.go index 10fb2bdea62db..aa5a96b43d2d4 100644 --- a/api/types/appserver.go +++ b/api/types/appserver.go @@ -304,7 +304,14 @@ func (s AppServers) Len() int { return len(s) } // Less compares app servers by name and host ID. func (s AppServers) Less(i, j int) bool { - return s[i].GetName() < s[j].GetName() && s[i].GetHostID() < s[j].GetHostID() + switch { + case s[i].GetName() < s[j].GetName(): + return true + case s[i].GetName() > s[j].GetName(): + return false + default: + return s[i].GetHostID() < s[j].GetHostID() + } } // Swap swaps two app servers. diff --git a/api/types/constants.go b/api/types/constants.go index 54f00730c9906..52a4d64210e98 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -297,9 +297,13 @@ const ( ) const ( + // TeleportNamespace is used as the namespace prefix for any + // labels defined by teleport + TeleportNamespace = "teleport.dev" + // OriginLabel is a resource metadata label name used to identify a source // that the resource originates from. - OriginLabel = "teleport.dev/origin" + OriginLabel = TeleportNamespace + "/origin" // OriginConfigFile is an origin value indicating that the resource was // constructed as a default value. diff --git a/api/types/database.go b/api/types/database.go index 7e752b0412d40..39b94b4b96a6f 100644 --- a/api/types/database.go +++ b/api/types/database.go @@ -563,16 +563,6 @@ func DeduplicateDatabases(databases []Database) (result []Database) { // Databases is a list of database resources. type Databases []Database -// Find returns database with the specified name or nil. -func (d Databases) Find(name string) Database { - for _, database := range d { - if database.GetName() == name { - return database - } - } - return nil -} - // ToMap returns these databases as a map keyed by database name. func (d Databases) ToMap() map[string]Database { m := make(map[string]Database) diff --git a/api/types/databaseserver.go b/api/types/databaseserver.go index 4a4e6cfb0e6a4..db2637513a3cc 100644 --- a/api/types/databaseserver.go +++ b/api/types/databaseserver.go @@ -291,7 +291,14 @@ func (s DatabaseServers) Len() int { return len(s) } // Less compares database servers by name and host ID. func (s DatabaseServers) Less(i, j int) bool { - return s[i].GetName() < s[j].GetName() && s[i].GetHostID() < s[j].GetHostID() + switch { + case s[i].GetName() < s[j].GetName(): + return true + case s[i].GetName() > s[j].GetName(): + return false + default: + return s[i].GetHostID() < s[j].GetHostID() + } } // Swap swaps two database servers. diff --git a/api/types/databaseserver_test.go b/api/types/databaseserver_test.go index babd27ac9a0d7..de45c5c788df8 100644 --- a/api/types/databaseserver_test.go +++ b/api/types/databaseserver_test.go @@ -154,8 +154,6 @@ func TestDatabaseServerSorter(t *testing.T) { for _, c := range cases { c := c t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) { - t.Parallel() - sortBy := SortBy{Field: c.fieldName, IsDesc: true} servers := DatabaseServers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) @@ -165,8 +163,6 @@ func TestDatabaseServerSorter(t *testing.T) { }) t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) { - t.Parallel() - sortBy := SortBy{Field: c.fieldName} servers := DatabaseServers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) diff --git a/api/types/desktop.go b/api/types/desktop.go index 6d2199711401a..6fdda0b05fa75 100644 --- a/api/types/desktop.go +++ b/api/types/desktop.go @@ -17,6 +17,8 @@ limitations under the License. package types import ( + "sort" + "github.com/gravitational/teleport/api/utils" "github.com/gravitational/trace" ) @@ -29,6 +31,8 @@ type WindowsDesktopService interface { GetAddr() string // GetVersion returns the teleport binary version of this service. GetTeleportVersion() string + // GetHostname returns the hostname of this service + GetHostname() string } var _ WindowsDesktopService = &WindowsDesktopServiceV3{} @@ -95,6 +99,11 @@ func (s *WindowsDesktopServiceV3) GetAllLabels() map[string]string { return s.Metadata.Labels } +// GetHostname returns the windows hostname of this service. +func (s *WindowsDesktopServiceV3) GetHostname() string { + return s.Spec.Hostname +} + // MatchSearch goes through select field values and tries to // match against the list of search values. func (s *WindowsDesktopServiceV3) MatchSearch(values []string) bool { @@ -218,3 +227,91 @@ func (f *WindowsDesktopFilter) Match(req WindowsDesktop) bool { } return true } + +// WindowsDesktops represents a list of windows desktops. +type WindowsDesktops []WindowsDesktop + +// Len returns the slice length. +func (s WindowsDesktops) Len() int { return len(s) } + +// Less compares desktops by name and host ID. +func (s WindowsDesktops) Less(i, j int) bool { + switch { + case s[i].GetName() < s[j].GetName(): + return true + case s[i].GetName() > s[j].GetName(): + return false + default: + return s[i].GetHostID() < s[j].GetHostID() + } +} + +// Swap swaps two windows desktops. +func (s WindowsDesktops) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// SortByCustom custom sorts by given sort criteria. +func (s WindowsDesktops) SortByCustom(sortBy SortBy) error { + if sortBy.Field == "" { + return nil + } + + isDesc := sortBy.IsDesc + switch sortBy.Field { + case ResourceMetadataName: + sort.SliceStable(s, func(i, j int) bool { + return stringCompare(s[i].GetName(), s[j].GetName(), isDesc) + }) + case ResourceSpecAddr: + sort.SliceStable(s, func(i, j int) bool { + return stringCompare(s[i].GetAddr(), s[j].GetAddr(), isDesc) + }) + default: + return trace.NotImplemented("sorting by field %q for resource %q is not supported", sortBy.Field, KindWindowsDesktop) + } + + return nil +} + +// AsResources returns windows desktops as type resources with labels. +func (s WindowsDesktops) AsResources() []ResourceWithLabels { + resources := make([]ResourceWithLabels, 0, len(s)) + for _, server := range s { + resources = append(resources, ResourceWithLabels(server)) + } + return resources +} + +// GetFieldVals returns list of select field values. +func (s WindowsDesktops) GetFieldVals(field string) ([]string, error) { + vals := make([]string, 0, len(s)) + switch field { + case ResourceMetadataName: + for _, server := range s { + vals = append(vals, server.GetName()) + } + case ResourceSpecAddr: + for _, server := range s { + vals = append(vals, server.GetAddr()) + } + default: + return nil, trace.NotImplemented("getting field %q for resource %q is not supported", field, KindWindowsDesktop) + } + + return vals, nil +} + +// ListWindowsDesktopsResponse is a response type to ListWindowsDesktops. +type ListWindowsDesktopsResponse struct { + Desktops []WindowsDesktop + NextKey string +} + +// ListWindowsDesktopsRequest is a request type to ListWindowsDesktops. +type ListWindowsDesktopsRequest struct { + WindowsDesktopFilter + Limit int + StartKey, PredicateExpression string + Labels map[string]string + SearchKeywords []string + SortBy SortBy +} diff --git a/api/types/desktop_test.go b/api/types/desktop_test.go new file mode 100644 index 0000000000000..f17c480bd0685 --- /dev/null +++ b/api/types/desktop_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import ( + "fmt" + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" +) + +func TestWindowsDesktopsSorter(t *testing.T) { + t.Parallel() + + testValsUnordered := []string{"d", "b", "a", "c"} + + makeDesktops := func(testVals []string, testField string) []WindowsDesktop { + desktops := make([]WindowsDesktop, len(testVals)) + for i := 0; i < len(testVals); i++ { + testVal := testVals[i] + var err error + desktops[i], err = NewWindowsDesktopV3( + getTestVal(testField == ResourceMetadataName, testVal), + nil, + WindowsDesktopSpecV3{ + Addr: getTestVal(testField == ResourceSpecAddr, testVal), + }) + + require.NoError(t, err) + } + return desktops + } + + cases := []struct { + name string + fieldName string + }{ + { + name: "by name", + fieldName: ResourceMetadataName, + }, + { + name: "by addr", + fieldName: ResourceSpecAddr, + }, + } + + for _, c := range cases { + c := c + t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) { + sortBy := SortBy{Field: c.fieldName, IsDesc: true} + servers := WindowsDesktops(makeDesktops(testValsUnordered, c.fieldName)) + require.NoError(t, servers.SortByCustom(sortBy)) + targetVals, err := servers.GetFieldVals(c.fieldName) + require.NoError(t, err) + require.IsDecreasing(t, targetVals) + }) + + t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) { + sortBy := SortBy{Field: c.fieldName} + servers := WindowsDesktops(makeDesktops(testValsUnordered, c.fieldName)) + require.NoError(t, servers.SortByCustom(sortBy)) + targetVals, err := servers.GetFieldVals(c.fieldName) + require.NoError(t, err) + require.IsIncreasing(t, targetVals) + }) + } + + // Test error. + sortBy := SortBy{Field: "unsupported"} + desktops := makeDesktops(testValsUnordered, "does-not-matter") + require.True(t, trace.IsNotImplemented(WindowsDesktops(desktops).SortByCustom(sortBy))) +} diff --git a/api/types/events/events.pb.go b/api/types/events/events.pb.go index 423a29e72972f..64cd82b30c814 100644 --- a/api/types/events/events.pb.go +++ b/api/types/events/events.pb.go @@ -3673,10 +3673,12 @@ type WindowsDesktopSessionStart struct { // WindowsUser is the Windows username used to connect. WindowsUser string `protobuf:"bytes,9,opt,name=WindowsUser,proto3" json:"windows_user"` // DesktopLabels are the labels on the desktop resource. - DesktopLabels map[string]string `protobuf:"bytes,10,rep,name=DesktopLabels,proto3" json:"desktop_labels" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DesktopLabels map[string]string `protobuf:"bytes,10,rep,name=DesktopLabels,proto3" json:"desktop_labels" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // DesktopName is the name of the desktop resource. + DesktopName string `protobuf:"bytes,11,opt,name=DesktopName,proto3" json:"desktop_name"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *WindowsDesktopSessionStart) Reset() { *m = WindowsDesktopSessionStart{} } @@ -4376,6 +4378,54 @@ func (m *RenewableCertificateGenerationMismatch) XXX_DiscardUnknown() { var xxx_messageInfo_RenewableCertificateGenerationMismatch proto.InternalMessageInfo +// Unknown is a fallback event used when we don't recognize an event from the backend. +type Unknown struct { + // Metadata is a common event metadata. + Metadata `protobuf:"bytes,1,opt,name=Metadata,proto3,embedded=Metadata" json:""` + // UnknownType is the event type extracted from the unknown event. + UnknownType string `protobuf:"bytes,2,opt,name=UnknownType,proto3" json:"unknown_event"` + // UnknownCode is the event code extracted from the unknown event. + UnknownCode string `protobuf:"bytes,3,opt,name=UnknownCode,proto3" json:"unknown_code,omitempty"` + // Data is the serialized JSON data of the unknown event. + Data string `protobuf:"bytes,4,opt,name=Data,proto3" json:"data"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Unknown) Reset() { *m = Unknown{} } +func (m *Unknown) String() string { return proto.CompactTextString(m) } +func (*Unknown) ProtoMessage() {} +func (*Unknown) Descriptor() ([]byte, []int) { + return fileDescriptor_8f22242cb04491f9, []int{85} +} +func (m *Unknown) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Unknown) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Unknown.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Unknown) XXX_Merge(src proto.Message) { + xxx_messageInfo_Unknown.Merge(m, src) +} +func (m *Unknown) XXX_Size() int { + return m.Size() +} +func (m *Unknown) XXX_DiscardUnknown() { + xxx_messageInfo_Unknown.DiscardUnknown(m) +} + +var xxx_messageInfo_Unknown proto.InternalMessageInfo + // OneOf is a union of one of audit events submitted to the auth service type OneOf struct { // Event is one of the audit events @@ -4460,6 +4510,7 @@ type OneOf struct { // *OneOf_MySQLStatementFetch // *OneOf_MySQLStatementBulkExecute // *OneOf_RenewableCertificateGenerationMismatch + // *OneOf_Unknown Event isOneOf_Event `protobuf_oneof:"Event"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -4470,7 +4521,7 @@ func (m *OneOf) Reset() { *m = OneOf{} } func (m *OneOf) String() string { return proto.CompactTextString(m) } func (*OneOf) ProtoMessage() {} func (*OneOf) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{85} + return fileDescriptor_8f22242cb04491f9, []int{86} } func (m *OneOf) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4742,6 +4793,9 @@ type OneOf_MySQLStatementBulkExecute struct { type OneOf_RenewableCertificateGenerationMismatch struct { RenewableCertificateGenerationMismatch *RenewableCertificateGenerationMismatch `protobuf:"bytes,79,opt,name=RenewableCertificateGenerationMismatch,proto3,oneof" json:"RenewableCertificateGenerationMismatch,omitempty"` } +type OneOf_Unknown struct { + Unknown *Unknown `protobuf:"bytes,80,opt,name=Unknown,proto3,oneof" json:"Unknown,omitempty"` +} func (*OneOf_UserLogin) isOneOf_Event() {} func (*OneOf_UserCreate) isOneOf_Event() {} @@ -4822,6 +4876,7 @@ func (*OneOf_MySQLStatementReset) isOneOf_Event() {} func (*OneOf_MySQLStatementFetch) isOneOf_Event() {} func (*OneOf_MySQLStatementBulkExecute) isOneOf_Event() {} func (*OneOf_RenewableCertificateGenerationMismatch) isOneOf_Event() {} +func (*OneOf_Unknown) isOneOf_Event() {} func (m *OneOf) GetEvent() isOneOf_Event { if m != nil { @@ -5383,6 +5438,13 @@ func (m *OneOf) GetRenewableCertificateGenerationMismatch() *RenewableCertificat return nil } +func (m *OneOf) GetUnknown() *Unknown { + if x, ok := m.GetEvent().(*OneOf_Unknown); ok { + return x.Unknown + } + return nil +} + // XXX_OneofWrappers is for the internal use of the proto package. func (*OneOf) XXX_OneofWrappers() []interface{} { return []interface{}{ @@ -5465,6 +5527,7 @@ func (*OneOf) XXX_OneofWrappers() []interface{} { (*OneOf_MySQLStatementFetch)(nil), (*OneOf_MySQLStatementBulkExecute)(nil), (*OneOf_RenewableCertificateGenerationMismatch)(nil), + (*OneOf_Unknown)(nil), } } @@ -5485,7 +5548,7 @@ func (m *StreamStatus) Reset() { *m = StreamStatus{} } func (m *StreamStatus) String() string { return proto.CompactTextString(m) } func (*StreamStatus) ProtoMessage() {} func (*StreamStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{86} + return fileDescriptor_8f22242cb04491f9, []int{87} } func (m *StreamStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5533,7 +5596,7 @@ func (m *SessionUpload) Reset() { *m = SessionUpload{} } func (m *SessionUpload) String() string { return proto.CompactTextString(m) } func (*SessionUpload) ProtoMessage() {} func (*SessionUpload) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{87} + return fileDescriptor_8f22242cb04491f9, []int{88} } func (m *SessionUpload) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5628,7 +5691,7 @@ func (m *Identity) Reset() { *m = Identity{} } func (m *Identity) String() string { return proto.CompactTextString(m) } func (*Identity) ProtoMessage() {} func (*Identity) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{88} + return fileDescriptor_8f22242cb04491f9, []int{89} } func (m *Identity) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5678,7 +5741,7 @@ func (m *RouteToApp) Reset() { *m = RouteToApp{} } func (m *RouteToApp) String() string { return proto.CompactTextString(m) } func (*RouteToApp) ProtoMessage() {} func (*RouteToApp) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{89} + return fileDescriptor_8f22242cb04491f9, []int{90} } func (m *RouteToApp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5726,7 +5789,7 @@ func (m *RouteToDatabase) Reset() { *m = RouteToDatabase{} } func (m *RouteToDatabase) String() string { return proto.CompactTextString(m) } func (*RouteToDatabase) ProtoMessage() {} func (*RouteToDatabase) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{90} + return fileDescriptor_8f22242cb04491f9, []int{91} } func (m *RouteToDatabase) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5777,7 +5840,7 @@ func (m *MySQLStatementPrepare) Reset() { *m = MySQLStatementPrepare{} } func (m *MySQLStatementPrepare) String() string { return proto.CompactTextString(m) } func (*MySQLStatementPrepare) ProtoMessage() {} func (*MySQLStatementPrepare) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{91} + return fileDescriptor_8f22242cb04491f9, []int{92} } func (m *MySQLStatementPrepare) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5830,7 +5893,7 @@ func (m *MySQLStatementExecute) Reset() { *m = MySQLStatementExecute{} } func (m *MySQLStatementExecute) String() string { return proto.CompactTextString(m) } func (*MySQLStatementExecute) ProtoMessage() {} func (*MySQLStatementExecute) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{92} + return fileDescriptor_8f22242cb04491f9, []int{93} } func (m *MySQLStatementExecute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5885,7 +5948,7 @@ func (m *MySQLStatementSendLongData) Reset() { *m = MySQLStatementSendLo func (m *MySQLStatementSendLongData) String() string { return proto.CompactTextString(m) } func (*MySQLStatementSendLongData) ProtoMessage() {} func (*MySQLStatementSendLongData) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{93} + return fileDescriptor_8f22242cb04491f9, []int{94} } func (m *MySQLStatementSendLongData) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5936,7 +5999,7 @@ func (m *MySQLStatementClose) Reset() { *m = MySQLStatementClose{} } func (m *MySQLStatementClose) String() string { return proto.CompactTextString(m) } func (*MySQLStatementClose) ProtoMessage() {} func (*MySQLStatementClose) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{94} + return fileDescriptor_8f22242cb04491f9, []int{95} } func (m *MySQLStatementClose) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5987,7 +6050,7 @@ func (m *MySQLStatementReset) Reset() { *m = MySQLStatementReset{} } func (m *MySQLStatementReset) String() string { return proto.CompactTextString(m) } func (*MySQLStatementReset) ProtoMessage() {} func (*MySQLStatementReset) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{95} + return fileDescriptor_8f22242cb04491f9, []int{96} } func (m *MySQLStatementReset) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6040,7 +6103,7 @@ func (m *MySQLStatementFetch) Reset() { *m = MySQLStatementFetch{} } func (m *MySQLStatementFetch) String() string { return proto.CompactTextString(m) } func (*MySQLStatementFetch) ProtoMessage() {} func (*MySQLStatementFetch) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{96} + return fileDescriptor_8f22242cb04491f9, []int{97} } func (m *MySQLStatementFetch) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6093,7 +6156,7 @@ func (m *MySQLStatementBulkExecute) Reset() { *m = MySQLStatementBulkExe func (m *MySQLStatementBulkExecute) String() string { return proto.CompactTextString(m) } func (*MySQLStatementBulkExecute) ProtoMessage() {} func (*MySQLStatementBulkExecute) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{97} + return fileDescriptor_8f22242cb04491f9, []int{98} } func (m *MySQLStatementBulkExecute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6215,6 +6278,7 @@ func init() { proto.RegisterMapType((map[string]string)(nil), "events.WindowsDesktopSessionEnd.DesktopLabelsEntry") proto.RegisterType((*CertificateCreate)(nil), "events.CertificateCreate") proto.RegisterType((*RenewableCertificateGenerationMismatch)(nil), "events.RenewableCertificateGenerationMismatch") + proto.RegisterType((*Unknown)(nil), "events.Unknown") proto.RegisterType((*OneOf)(nil), "events.OneOf") proto.RegisterType((*StreamStatus)(nil), "events.StreamStatus") proto.RegisterType((*SessionUpload)(nil), "events.SessionUpload") @@ -6233,438 +6297,444 @@ func init() { func init() { proto.RegisterFile("events.proto", fileDescriptor_8f22242cb04491f9) } var fileDescriptor_8f22242cb04491f9 = []byte{ - // 6886 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5f, 0x6c, 0x24, 0xc9, - 0x59, 0xb8, 0x67, 0xc6, 0xf3, 0xc7, 0x35, 0xf6, 0xda, 0xae, 0xf5, 0xee, 0xf6, 0xed, 0xed, 0xdd, - 0xdc, 0xf5, 0x25, 0x7b, 0xbb, 0xb9, 0x3d, 0x3b, 0xbb, 0xb7, 0x77, 0x9b, 0x5c, 0xee, 0x72, 0x3b, - 0x1e, 0xdb, 0xe7, 0xc9, 0x79, 0x6d, 0x5f, 0xd9, 0x7b, 0x17, 0x29, 0xca, 0x8d, 0xda, 0xd3, 0xb5, - 0x76, 0x67, 0x67, 0xa6, 0x3b, 0xdd, 0x3d, 0xf6, 0xfa, 0x9e, 0x7e, 0x3f, 0x40, 0x10, 0x45, 0x01, - 0xa1, 0xf0, 0xc0, 0x03, 0x0f, 0xa0, 0x00, 0x12, 0x48, 0x11, 0x01, 0x84, 0x22, 0x24, 0x78, 0x81, - 0x00, 0xba, 0x08, 0x25, 0x04, 0x82, 0x40, 0xe2, 0x61, 0x02, 0x41, 0xbc, 0x0c, 0x41, 0x8a, 0x04, - 0x12, 0x81, 0x27, 0x54, 0x5f, 0x55, 0x77, 0x57, 0x75, 0xf7, 0x78, 0xd7, 0x6b, 0x1f, 0xc6, 0xbb, - 0x7e, 0xb2, 0xe7, 0xfb, 0x57, 0xdd, 0x5f, 0xfd, 0xfb, 0xea, 0xab, 0xef, 0xfb, 0x1a, 0x8d, 0xd2, - 0x6d, 0xda, 0xf1, 0xbd, 0x69, 0xc7, 0xb5, 0x7d, 0x1b, 0x17, 0xf8, 0xaf, 0xf3, 0x53, 0x9b, 0xf6, - 0xa6, 0x0d, 0xa0, 0x19, 0xf6, 0x1f, 0xc7, 0x9e, 0xaf, 0x6c, 0xda, 0xf6, 0x66, 0x8b, 0xce, 0xc0, - 0xaf, 0x8d, 0xee, 0x9d, 0x19, 0xdf, 0x6a, 0x53, 0xcf, 0x37, 0xda, 0x8e, 0x20, 0xb8, 0x10, 0x27, - 0xf0, 0x7c, 0xb7, 0xdb, 0xf4, 0x05, 0xb6, 0xb6, 0x69, 0xf9, 0x5b, 0xdd, 0x8d, 0xe9, 0xa6, 0xdd, - 0x9e, 0xd9, 0x74, 0x8d, 0x6d, 0xcb, 0x37, 0x7c, 0xcb, 0xee, 0x18, 0xad, 0x19, 0x9f, 0xb6, 0xa8, - 0x63, 0xbb, 0xfe, 0x8c, 0xe1, 0x58, 0x33, 0xfe, 0xae, 0x43, 0xbd, 0x99, 0x1d, 0xd7, 0x70, 0x1c, - 0xea, 0x46, 0xff, 0x70, 0x21, 0xfa, 0x57, 0xb3, 0xa8, 0x74, 0x8b, 0xfa, 0x86, 0x69, 0xf8, 0x06, - 0xbe, 0x80, 0xf2, 0xf5, 0x8e, 0x49, 0xef, 0x69, 0x99, 0x67, 0x32, 0x97, 0x72, 0xb3, 0x85, 0x7e, - 0xaf, 0x92, 0xa5, 0x16, 0xe1, 0x40, 0xfc, 0x14, 0x1a, 0x5e, 0xdf, 0x75, 0xa8, 0x96, 0x7d, 0x26, - 0x73, 0x69, 0x64, 0x76, 0xa4, 0xdf, 0xab, 0xe4, 0xe1, 0xf5, 0x08, 0x80, 0xf1, 0xb3, 0x28, 0x5b, - 0x9f, 0xd3, 0x72, 0x80, 0x9c, 0xec, 0xf7, 0x2a, 0x63, 0x5d, 0xcb, 0xbc, 0x62, 0xb7, 0x2d, 0x9f, - 0xb6, 0x1d, 0x7f, 0x97, 0x64, 0xeb, 0x73, 0xf8, 0x22, 0x1a, 0xae, 0xd9, 0x26, 0xd5, 0x86, 0x81, - 0x08, 0xf7, 0x7b, 0x95, 0x53, 0x4d, 0xdb, 0xa4, 0x12, 0x15, 0xe0, 0xf1, 0x4d, 0x34, 0xbc, 0x6e, - 0xb5, 0xa9, 0x96, 0x7f, 0x26, 0x73, 0xa9, 0x7c, 0xed, 0xfc, 0x34, 0x57, 0xc3, 0x74, 0xa0, 0x86, - 0xe9, 0xf5, 0x40, 0x4f, 0xb3, 0x13, 0x1f, 0xf4, 0x2a, 0x43, 0xfd, 0x5e, 0x65, 0x98, 0xa9, 0xee, - 0x17, 0x7f, 0x50, 0xc9, 0x10, 0xe0, 0xc4, 0xaf, 0xa1, 0x72, 0xad, 0xd5, 0xf5, 0x7c, 0xea, 0x2e, - 0x1b, 0x6d, 0xaa, 0x15, 0xa0, 0xc1, 0xf3, 0xfd, 0x5e, 0xe5, 0x6c, 0x93, 0x83, 0x1b, 0x1d, 0xa3, - 0x2d, 0x37, 0x2c, 0x93, 0xeb, 0x5f, 0x40, 0xe3, 0x6b, 0xd4, 0xf3, 0x2c, 0xbb, 0x13, 0xaa, 0xe6, - 0xa3, 0x68, 0x44, 0x80, 0xea, 0x73, 0xa0, 0x9e, 0x91, 0xd9, 0x62, 0xbf, 0x57, 0xc9, 0x79, 0x96, - 0x49, 0x22, 0x0c, 0xfe, 0x38, 0x2a, 0xbe, 0x6b, 0xf9, 0x5b, 0xb7, 0x16, 0xaa, 0x42, 0x4d, 0x67, - 0xfb, 0xbd, 0x0a, 0xde, 0xb1, 0xfc, 0xad, 0x46, 0xfb, 0x8e, 0x21, 0xb5, 0x17, 0x90, 0xe9, 0xbf, - 0x91, 0x45, 0xa3, 0xb7, 0x3d, 0xea, 0x86, 0x2d, 0x5d, 0x44, 0xc3, 0xec, 0xb7, 0x68, 0x04, 0x94, - 0xd4, 0xf5, 0xa8, 0x2b, 0x2b, 0x89, 0xe1, 0xf1, 0x65, 0x94, 0x5f, 0xb2, 0x37, 0xad, 0x8e, 0x68, - 0xe8, 0x74, 0xbf, 0x57, 0x19, 0x6f, 0x31, 0x80, 0x44, 0xc9, 0x29, 0xf0, 0xa7, 0xd1, 0x68, 0xbd, - 0xcd, 0x3a, 0xdd, 0xee, 0x18, 0xbe, 0xed, 0x8a, 0x4e, 0x02, 0x75, 0x58, 0x12, 0x5c, 0x62, 0x54, - 0xe8, 0xf1, 0xab, 0x08, 0x55, 0xdf, 0x5d, 0x23, 0x76, 0x8b, 0x56, 0xc9, 0xb2, 0xe8, 0x3d, 0xe0, - 0x36, 0x76, 0xbc, 0x86, 0x6b, 0xb7, 0x68, 0xc3, 0x70, 0xe5, 0x66, 0x25, 0x6a, 0x3c, 0x8f, 0x4e, - 0x55, 0x9b, 0x4d, 0xea, 0x79, 0x84, 0x7e, 0xb1, 0x4b, 0x3d, 0xdf, 0xd3, 0xf2, 0xcf, 0xe4, 0x2e, - 0x8d, 0xcc, 0x3e, 0xd5, 0xef, 0x55, 0x9e, 0x30, 0x00, 0xd3, 0x70, 0x05, 0x4a, 0x12, 0x11, 0x63, - 0xd2, 0x7f, 0x37, 0x87, 0x4e, 0xad, 0x51, 0x77, 0x5b, 0x52, 0x54, 0x95, 0xf5, 0x12, 0x83, 0xb0, - 0x3e, 0xf3, 0x1c, 0xa3, 0x49, 0x85, 0xce, 0xce, 0xf5, 0x7b, 0x95, 0xd3, 0x9d, 0x00, 0x28, 0x09, - 0x8d, 0xd3, 0xe3, 0xcb, 0xa8, 0xc4, 0x41, 0xf5, 0x39, 0xa1, 0xc6, 0xb1, 0x7e, 0xaf, 0x32, 0xe2, - 0x01, 0xac, 0x61, 0x99, 0x24, 0x44, 0xb3, 0xf7, 0xe0, 0xff, 0x2f, 0xda, 0x9e, 0xcf, 0x84, 0x0b, - 0x2d, 0xc2, 0x7b, 0x08, 0x86, 0x2d, 0x81, 0x92, 0xdf, 0x43, 0x65, 0xc2, 0x9f, 0x44, 0x88, 0x43, - 0xaa, 0xa6, 0xe9, 0x0a, 0x55, 0x3e, 0xd1, 0xef, 0x55, 0xce, 0x08, 0x11, 0x86, 0x69, 0xca, 0xfd, - 0x20, 0x11, 0xe3, 0x36, 0x1a, 0xe5, 0xbf, 0x96, 0x8c, 0x0d, 0xda, 0xe2, 0x7a, 0x2c, 0x5f, 0xbb, - 0x34, 0x2d, 0x56, 0x1c, 0x55, 0x3b, 0xd3, 0x32, 0xe9, 0x7c, 0xc7, 0x77, 0x77, 0x67, 0x2b, 0x62, - 0xae, 0x9c, 0x13, 0x4d, 0xb5, 0x00, 0x27, 0x77, 0xba, 0xcc, 0x73, 0xfe, 0x0d, 0x34, 0x99, 0x90, - 0x81, 0x27, 0x50, 0xee, 0x2e, 0xdd, 0xe5, 0x7a, 0x26, 0xec, 0x5f, 0x3c, 0x85, 0xf2, 0xdb, 0x46, - 0xab, 0x2b, 0x96, 0x05, 0xc2, 0x7f, 0xbc, 0x9a, 0xfd, 0x44, 0x46, 0xff, 0xc3, 0x0c, 0xc2, 0x35, - 0xbb, 0xd3, 0xa1, 0x4d, 0x5f, 0x9e, 0x49, 0xaf, 0xa0, 0x91, 0x25, 0xbb, 0x69, 0xb4, 0x40, 0x01, - 0xbc, 0xc3, 0xb4, 0x7e, 0xaf, 0x32, 0xc5, 0xde, 0x7c, 0xba, 0xc5, 0x30, 0xd2, 0x23, 0x45, 0xa4, - 0x4c, 0x73, 0x84, 0xb6, 0x6d, 0x9f, 0x02, 0x63, 0x36, 0xd2, 0x1c, 0x30, 0xba, 0x80, 0x92, 0x35, - 0x17, 0x11, 0xe3, 0x19, 0x54, 0x5a, 0x65, 0x6b, 0x47, 0xd3, 0x6e, 0x89, 0x5e, 0x83, 0xd9, 0x02, - 0xeb, 0x89, 0xc4, 0x12, 0x12, 0xe9, 0x3f, 0x95, 0x45, 0x4f, 0xbc, 0xd5, 0xdd, 0xa0, 0x6e, 0x87, - 0xfa, 0xd4, 0x13, 0x4b, 0x43, 0xf8, 0x06, 0xcb, 0x68, 0x32, 0x81, 0x14, 0x6f, 0xf2, 0x4c, 0xbf, - 0x57, 0xb9, 0x70, 0x37, 0x44, 0x36, 0xc4, 0x6a, 0x23, 0x35, 0x92, 0x64, 0xc5, 0x8b, 0x68, 0x3c, - 0x02, 0xb2, 0xb9, 0xed, 0x69, 0x59, 0x98, 0x23, 0x4f, 0xf7, 0x7b, 0x95, 0xf3, 0x92, 0x34, 0xb6, - 0x0e, 0xc8, 0x1d, 0x16, 0x67, 0xc3, 0x6f, 0xa1, 0x89, 0x08, 0xf4, 0xa6, 0x6b, 0x77, 0x1d, 0x4f, - 0xcb, 0x81, 0xa8, 0x4a, 0xbf, 0x57, 0x79, 0x52, 0x12, 0xb5, 0x09, 0x48, 0x49, 0x56, 0x82, 0x51, - 0xff, 0x97, 0x1c, 0x3a, 0x13, 0x01, 0x57, 0x6d, 0x33, 0x54, 0xc0, 0x8a, 0xac, 0x80, 0x55, 0xdb, - 0x84, 0x35, 0x96, 0x2b, 0xe0, 0xd9, 0x7e, 0xaf, 0xf2, 0x94, 0xd4, 0x8e, 0x63, 0x9b, 0xf1, 0xa5, - 0x36, 0xc9, 0x8b, 0xdf, 0x43, 0x67, 0x13, 0x40, 0x3e, 0xa3, 0x79, 0x3f, 0x5f, 0xec, 0xf7, 0x2a, - 0x7a, 0x8a, 0xd4, 0xf8, 0x04, 0x1f, 0x20, 0x05, 0x1b, 0xe8, 0x9c, 0xa4, 0x76, 0xbb, 0xe3, 0x1b, - 0x56, 0x47, 0x6c, 0x0d, 0x7c, 0x3c, 0x3c, 0xdf, 0xef, 0x55, 0x9e, 0x93, 0xfb, 0x2d, 0xa0, 0x89, - 0x3f, 0xfc, 0x20, 0x39, 0xd8, 0x44, 0x5a, 0x0a, 0xaa, 0xde, 0x36, 0x36, 0x83, 0xfd, 0xee, 0x52, - 0xbf, 0x57, 0xf9, 0x48, 0x6a, 0x1b, 0x16, 0xa3, 0x92, 0x1a, 0x19, 0x28, 0x09, 0x13, 0x84, 0x23, - 0xdc, 0xb2, 0x6d, 0x52, 0x78, 0x87, 0x3c, 0xc8, 0xd7, 0xfb, 0xbd, 0xca, 0xd3, 0x92, 0xfc, 0x8e, - 0x6d, 0xd2, 0xf8, 0xe3, 0xa7, 0x70, 0xeb, 0x3f, 0xc8, 0xb3, 0x85, 0x05, 0x76, 0xb0, 0x35, 0xdf, - 0x70, 0x7d, 0xfc, 0x6a, 0x64, 0x12, 0x40, 0xaf, 0x96, 0xaf, 0x4d, 0x04, 0x8b, 0x4c, 0x00, 0x9f, - 0x1d, 0x65, 0x8b, 0xc9, 0xf7, 0x7a, 0x95, 0x4c, 0xbf, 0x57, 0x19, 0x22, 0x25, 0x69, 0x76, 0xf3, - 0xdd, 0x2b, 0x0b, 0x7c, 0x53, 0x01, 0x9f, 0xbc, 0xc3, 0xc5, 0x78, 0xf9, 0x6e, 0xf6, 0x06, 0x2a, - 0x8a, 0x67, 0x80, 0x1e, 0x29, 0x5f, 0x3b, 0x17, 0xad, 0x6b, 0xca, 0x4e, 0x1c, 0xe3, 0x0e, 0xb8, - 0xf0, 0x6b, 0xa8, 0xc0, 0x97, 0x2b, 0xd0, 0x76, 0xf9, 0xda, 0xd9, 0xf4, 0x75, 0x31, 0xc6, 0x2e, - 0x78, 0xf0, 0x22, 0x42, 0xd1, 0x52, 0x15, 0xda, 0x1d, 0x42, 0x42, 0x72, 0x11, 0x8b, 0x49, 0x91, - 0x78, 0xf1, 0x2b, 0x68, 0x74, 0x9d, 0xba, 0x6d, 0xab, 0x63, 0xb4, 0xd6, 0xac, 0xf7, 0x03, 0xd3, - 0x03, 0xb6, 0x71, 0xcf, 0x7a, 0x5f, 0xee, 0x0b, 0x85, 0x0e, 0x7f, 0x3e, 0x6d, 0x51, 0x29, 0xc2, - 0x83, 0x3c, 0x1b, 0x3c, 0xc8, 0xc0, 0x25, 0x29, 0xf6, 0x3c, 0x29, 0x6b, 0xcc, 0xdb, 0x68, 0x4c, - 0x99, 0x1b, 0x5a, 0x09, 0x44, 0x3f, 0x95, 0x14, 0x2d, 0x4d, 0xf4, 0x98, 0x58, 0x55, 0x02, 0xdb, - 0x11, 0xeb, 0x1d, 0xcb, 0xb7, 0x8c, 0x56, 0xcd, 0x6e, 0xb7, 0x8d, 0x8e, 0xa9, 0x8d, 0x44, 0x3b, - 0xbb, 0xc5, 0x31, 0x8d, 0x26, 0x47, 0xc9, 0x3b, 0xa2, 0xca, 0xc4, 0xd6, 0x2c, 0xd1, 0x87, 0x84, - 0x36, 0x6d, 0xd7, 0xb4, 0x3a, 0x9b, 0x1a, 0x02, 0xa5, 0xc1, 0x9a, 0xe5, 0x71, 0x5c, 0xc3, 0x0d, - 0x90, 0xf2, 0x9a, 0x15, 0x67, 0xfc, 0xcc, 0x70, 0xa9, 0x3c, 0x31, 0x9a, 0x30, 0x1e, 0x7e, 0x3b, - 0x87, 0xca, 0x82, 0xf4, 0x33, 0xb6, 0xd5, 0x39, 0x19, 0xe0, 0x07, 0x19, 0xe0, 0xa9, 0x03, 0xb5, - 0x70, 0x58, 0x03, 0x55, 0xff, 0x4a, 0x36, 0x5c, 0x8d, 0x56, 0x5d, 0xab, 0x73, 0xb0, 0xd5, 0xe8, - 0x22, 0x42, 0xb5, 0xad, 0x6e, 0xe7, 0x2e, 0x3f, 0xd5, 0x64, 0xa3, 0x53, 0x4d, 0xd3, 0x22, 0x12, - 0x86, 0x1d, 0x6d, 0xe6, 0x98, 0x7c, 0xd6, 0x33, 0xa3, 0xb3, 0x23, 0x1f, 0x70, 0x49, 0x99, 0x17, - 0x09, 0x80, 0x71, 0x05, 0xe5, 0x67, 0x77, 0x7d, 0xea, 0x81, 0xe6, 0x73, 0xfc, 0xe8, 0xb3, 0xc1, - 0x00, 0x84, 0xc3, 0xf1, 0x75, 0x34, 0x39, 0x47, 0x5b, 0xc6, 0xee, 0x2d, 0xab, 0xd5, 0xb2, 0x3c, - 0xda, 0xb4, 0x3b, 0xa6, 0x07, 0x4a, 0x16, 0xcd, 0xb5, 0x3d, 0x92, 0x24, 0xc0, 0x3a, 0x2a, 0xac, - 0xdc, 0xb9, 0xe3, 0x51, 0x1f, 0xd4, 0x97, 0x9b, 0x45, 0xfd, 0x5e, 0xa5, 0x60, 0x03, 0x84, 0x08, - 0x8c, 0xfe, 0x8d, 0x0c, 0x9a, 0x98, 0xa3, 0xde, 0x5d, 0xdf, 0x76, 0xc2, 0x51, 0x7e, 0x20, 0x95, - 0x5c, 0x46, 0xc5, 0x5b, 0xd4, 0xf3, 0xd8, 0xb6, 0x94, 0x85, 0xb7, 0x1d, 0x17, 0x6f, 0x5b, 0x6c, - 0x73, 0x30, 0x09, 0xf0, 0xe9, 0x6f, 0x95, 0xbb, 0xcf, 0x5b, 0xe9, 0x3f, 0xce, 0xa2, 0x73, 0xe2, - 0x89, 0x6b, 0x2d, 0xcb, 0xd9, 0xb0, 0x0d, 0xd7, 0x24, 0xb4, 0x49, 0xad, 0x6d, 0x7a, 0x3c, 0x27, - 0x9e, 0x3a, 0x75, 0x86, 0x0f, 0x30, 0x75, 0xae, 0xa1, 0xb2, 0xd0, 0x0c, 0xd8, 0xb0, 0x7c, 0xdb, - 0x9e, 0xe8, 0xf7, 0x2a, 0xa3, 0x26, 0x07, 0x83, 0xf9, 0x4f, 0x64, 0x22, 0x36, 0x48, 0x96, 0x68, - 0x67, 0xd3, 0xdf, 0x82, 0x41, 0x92, 0xe7, 0x83, 0xa4, 0x05, 0x10, 0x22, 0x30, 0xfa, 0xbf, 0x65, - 0xd1, 0x54, 0x5c, 0xe5, 0x6b, 0xb4, 0x63, 0x9e, 0xe8, 0xfb, 0xc3, 0xd1, 0xf7, 0xdf, 0x64, 0xd1, - 0x58, 0xb8, 0xf5, 0x7c, 0x81, 0x36, 0x8f, 0xc6, 0x64, 0x8a, 0x36, 0x84, 0xdc, 0x81, 0x37, 0x84, - 0x83, 0x68, 0x59, 0x47, 0x05, 0x42, 0x0d, 0x4f, 0x6c, 0x2b, 0x23, 0x5c, 0x63, 0x2e, 0x40, 0x88, - 0xc0, 0xe0, 0x67, 0x51, 0xf1, 0x96, 0x71, 0xcf, 0x6a, 0x77, 0xdb, 0x62, 0xad, 0x03, 0xe7, 0x49, - 0xdb, 0xb8, 0x47, 0x02, 0xb8, 0xfe, 0xb7, 0x19, 0x76, 0xc2, 0x06, 0xa5, 0x0a, 0xe1, 0x07, 0xd2, - 0x6a, 0xa4, 0x9d, 0xec, 0x81, 0xb5, 0x93, 0x7b, 0x78, 0xed, 0xe8, 0xdf, 0x18, 0x66, 0xea, 0x61, - 0xa6, 0xdf, 0xe3, 0x3e, 0x1b, 0xa3, 0x1e, 0xc9, 0x3f, 0x44, 0x8f, 0x3c, 0x36, 0x76, 0xb5, 0xfe, - 0x9f, 0x45, 0x84, 0x84, 0xf6, 0xe7, 0x4f, 0xd6, 0xf0, 0x83, 0x8d, 0x9a, 0x39, 0x34, 0x39, 0xdf, - 0xd9, 0x32, 0x3a, 0x4d, 0x6a, 0x46, 0xa7, 0x0b, 0x36, 0x74, 0x4a, 0xdc, 0x33, 0x4b, 0x05, 0x32, - 0x3a, 0x5e, 0x90, 0x24, 0x03, 0xbe, 0x8a, 0xca, 0xf5, 0x8e, 0x4f, 0x5d, 0xa3, 0xe9, 0x5b, 0xdb, - 0x14, 0x46, 0x4f, 0x69, 0x76, 0xbc, 0xdf, 0xab, 0x94, 0xad, 0x08, 0x4c, 0x64, 0x1a, 0x7c, 0x1d, - 0x8d, 0xae, 0x1a, 0xae, 0x6f, 0x35, 0x2d, 0xc7, 0xe8, 0xf8, 0x9e, 0x56, 0x82, 0xa3, 0x11, 0xec, - 0x3d, 0x8e, 0x04, 0x27, 0x0a, 0x15, 0xfe, 0x3c, 0x1a, 0x81, 0x23, 0x38, 0x78, 0xbf, 0x47, 0xee, - 0xeb, 0xfd, 0x7e, 0x2e, 0xf2, 0xe8, 0xf1, 0x43, 0x92, 0xc7, 0x98, 0xa3, 0xa9, 0x00, 0x0e, 0xf1, - 0x48, 0x22, 0xfe, 0x2c, 0x2a, 0xce, 0x77, 0x4c, 0x10, 0x8e, 0xee, 0x2b, 0x5c, 0x17, 0xc2, 0xcf, - 0x46, 0xc2, 0x6d, 0x27, 0x26, 0x3b, 0x10, 0x97, 0x3e, 0xcb, 0xca, 0x1f, 0xde, 0x2c, 0x1b, 0xfd, - 0x10, 0x4e, 0xaf, 0x63, 0x87, 0x75, 0x7a, 0x3d, 0xf5, 0x90, 0xa7, 0x57, 0xfd, 0x7d, 0x54, 0x9e, - 0x5d, 0x5d, 0x08, 0x67, 0xef, 0x13, 0x28, 0xb7, 0x2a, 0x6e, 0x1b, 0x86, 0xf9, 0x86, 0xe9, 0x58, - 0x26, 0x61, 0x30, 0x7c, 0x19, 0x95, 0x6a, 0xe0, 0xc2, 0x13, 0x8e, 0xeb, 0x61, 0xee, 0xb8, 0x6e, - 0x02, 0x0c, 0x1c, 0xd7, 0x01, 0x1a, 0x7f, 0x14, 0x15, 0x57, 0x5d, 0x7b, 0xd3, 0x35, 0xda, 0xc2, - 0xd7, 0x55, 0x66, 0xc6, 0xbe, 0xc3, 0x41, 0x24, 0xc0, 0xe9, 0xbf, 0x94, 0x41, 0x85, 0x35, 0xdf, - 0xf0, 0xbb, 0x1e, 0xe3, 0x58, 0xeb, 0xc2, 0x09, 0x1a, 0xda, 0x2e, 0x71, 0x0e, 0x8f, 0x83, 0x48, - 0x80, 0xc3, 0x97, 0x51, 0x7e, 0xde, 0x75, 0x6d, 0x57, 0xbe, 0x80, 0xa0, 0x0c, 0x20, 0x5f, 0x40, - 0x00, 0x05, 0xbe, 0x81, 0xca, 0x7c, 0xcd, 0xe1, 0x07, 0x0f, 0xfe, 0x1c, 0x67, 0xfa, 0xbd, 0xca, - 0xa4, 0x38, 0x74, 0xc8, 0x37, 0x31, 0x12, 0xa5, 0xfe, 0xad, 0x9c, 0x64, 0x14, 0x70, 0x8d, 0x3f, - 0x86, 0x87, 0xf7, 0x97, 0x50, 0x6e, 0x76, 0x75, 0x41, 0x2c, 0x80, 0xa7, 0x03, 0x56, 0x69, 0xa8, - 0xc4, 0xf8, 0x18, 0x35, 0xbe, 0x80, 0x86, 0x57, 0xd9, 0xf0, 0x29, 0xc0, 0xf0, 0x28, 0xf5, 0x7b, - 0x95, 0x61, 0x87, 0x8d, 0x1f, 0x80, 0x02, 0xd6, 0xf0, 0xb7, 0x60, 0x2d, 0x1b, 0x11, 0x58, 0xc3, - 0xdf, 0x22, 0x00, 0x65, 0xd8, 0xaa, 0xbb, 0xb9, 0x2d, 0x56, 0x2d, 0xc0, 0x1a, 0xee, 0xe6, 0x36, - 0x01, 0x28, 0x9e, 0x41, 0x88, 0x50, 0xbf, 0xeb, 0x76, 0xe0, 0x32, 0x6f, 0x04, 0xcc, 0x64, 0x58, - 0x0d, 0x5d, 0x80, 0x36, 0x9a, 0xb6, 0x49, 0x89, 0x44, 0xa2, 0xff, 0x66, 0xe4, 0x7f, 0x99, 0xb3, - 0xbc, 0xbb, 0x27, 0x5d, 0xb8, 0x8f, 0x2e, 0x34, 0xc4, 0x49, 0x24, 0xd9, 0x49, 0x15, 0x94, 0x5f, - 0x68, 0x19, 0x9b, 0x1e, 0xf4, 0x61, 0x9e, 0x7b, 0x25, 0xee, 0x30, 0x00, 0xe1, 0xf0, 0x58, 0x3f, - 0x95, 0xee, 0xdf, 0x4f, 0xbf, 0x9c, 0x0f, 0x67, 0xdb, 0x32, 0xf5, 0x77, 0x6c, 0xf7, 0xa4, 0xab, - 0x1e, 0xb4, 0xab, 0x2e, 0xa2, 0xe2, 0x9a, 0xdb, 0x84, 0x63, 0x26, 0xef, 0xad, 0xd1, 0x7e, 0xaf, - 0x52, 0xf2, 0xdc, 0x26, 0x3f, 0x62, 0x06, 0x48, 0x46, 0x37, 0xe7, 0xf9, 0x40, 0x57, 0x8c, 0xe8, - 0x4c, 0xcf, 0x17, 0x74, 0x02, 0x29, 0xe8, 0x56, 0x6d, 0xd7, 0x17, 0x1d, 0x17, 0xd2, 0x39, 0xb6, - 0xeb, 0x93, 0x00, 0x89, 0x5f, 0x40, 0x68, 0xbd, 0xb6, 0xfa, 0x0e, 0x75, 0x41, 0x5d, 0x7c, 0x2e, - 0xc2, 0x72, 0xbd, 0xcd, 0x41, 0x44, 0x42, 0xe3, 0x75, 0x34, 0xb2, 0xe2, 0x50, 0x17, 0xc2, 0x04, - 0xc0, 0x02, 0x38, 0x75, 0xed, 0xf9, 0x98, 0x6a, 0x45, 0xbf, 0x4f, 0x8b, 0xbf, 0x21, 0x39, 0xdf, - 0x5f, 0xec, 0xe0, 0x27, 0x89, 0x04, 0xe1, 0x1b, 0xa8, 0x50, 0xe5, 0x76, 0x5e, 0x19, 0x44, 0x86, - 0x2a, 0x9b, 0x67, 0x7f, 0x38, 0x8a, 0x1f, 0x0a, 0x0d, 0xf8, 0x9f, 0x08, 0x72, 0xfd, 0x32, 0x9a, - 0x88, 0x37, 0x83, 0xcb, 0xa8, 0x58, 0x5b, 0x59, 0x5e, 0x9e, 0xaf, 0xad, 0x4f, 0x0c, 0xe1, 0x12, - 0x1a, 0x5e, 0x9b, 0x5f, 0x9e, 0x9b, 0xc8, 0xe8, 0x5f, 0x97, 0x56, 0x10, 0x36, 0xb4, 0x4e, 0x3c, - 0xb8, 0x07, 0x72, 0x8b, 0x4c, 0x80, 0xdb, 0x72, 0xdd, 0x35, 0x3a, 0x5e, 0xdb, 0xf2, 0x7d, 0x6a, - 0x8a, 0x5d, 0x02, 0xdc, 0x7a, 0xfe, 0x3d, 0x92, 0xc0, 0xe3, 0x2b, 0x68, 0x0c, 0x60, 0xc2, 0x93, - 0x67, 0xc2, 0xe8, 0x15, 0x0c, 0xee, 0x3d, 0xa2, 0x22, 0xf5, 0xbf, 0x8c, 0x9c, 0xb8, 0x4b, 0xd4, - 0x38, 0xae, 0x8e, 0xbf, 0xff, 0x23, 0xfd, 0xa5, 0xff, 0x7a, 0x0e, 0x8d, 0xb0, 0x37, 0xe2, 0xc1, - 0x1c, 0x47, 0xa1, 0xca, 0xeb, 0x81, 0x6d, 0x28, 0x34, 0x79, 0x2a, 0xd4, 0x04, 0x40, 0x13, 0x1a, - 0xe0, 0x76, 0xe4, 0x15, 0x54, 0xb8, 0x45, 0xfd, 0x2d, 0xdb, 0x14, 0x17, 0xa0, 0x53, 0xfd, 0x5e, - 0x65, 0xa2, 0x0d, 0x10, 0xc9, 0xde, 0x13, 0x34, 0xf8, 0x2e, 0xc2, 0x75, 0x93, 0x76, 0x7c, 0xcb, - 0xdf, 0xad, 0xfa, 0xbe, 0x6b, 0x6d, 0x74, 0x7d, 0xea, 0x09, 0xbd, 0x9d, 0x4b, 0x9c, 0x53, 0xd6, - 0x20, 0x12, 0x0a, 0xee, 0x3c, 0xa7, 0x8c, 0x90, 0x3c, 0x12, 0xfb, 0xdf, 0xbd, 0x4a, 0x81, 0xd3, - 0x90, 0x14, 0xb1, 0xf8, 0x6d, 0x34, 0x72, 0x6b, 0xa1, 0x3a, 0x47, 0xb7, 0xad, 0x26, 0x15, 0x97, - 0x17, 0x4f, 0x84, 0x5a, 0x0c, 0x10, 0xa1, 0x4a, 0x20, 0x3e, 0xa1, 0x7d, 0xc7, 0x68, 0x98, 0x00, - 0x97, 0xe3, 0x13, 0x42, 0x62, 0xfd, 0x47, 0x19, 0x34, 0x41, 0xa8, 0x67, 0x77, 0xdd, 0x88, 0x13, - 0x5f, 0x44, 0xc3, 0xd2, 0xe5, 0x38, 0x78, 0x2b, 0x62, 0x37, 0xb2, 0x80, 0xc7, 0x6b, 0xa8, 0x38, - 0x7f, 0xcf, 0xb1, 0x5c, 0xea, 0x89, 0xbe, 0xd9, 0xeb, 0x64, 0xf6, 0x94, 0x38, 0x99, 0x4d, 0x52, - 0xce, 0x92, 0x38, 0x94, 0x71, 0x30, 0x7e, 0x05, 0x8d, 0xdc, 0x76, 0x4c, 0xc3, 0xa7, 0xe6, 0xec, - 0xae, 0xb0, 0xb9, 0xe1, 0x4d, 0xba, 0x1c, 0xd8, 0xd8, 0xd8, 0x95, 0xdf, 0x24, 0x24, 0xc5, 0xcf, - 0xa1, 0xdc, 0xfa, 0xfa, 0x92, 0xe8, 0x34, 0x08, 0xe5, 0xf2, 0x7d, 0x39, 0x28, 0x83, 0x61, 0xf5, - 0xaf, 0x66, 0x11, 0x62, 0x63, 0xa3, 0xe6, 0x52, 0xc3, 0x3f, 0x9a, 0x09, 0x3e, 0x8b, 0x4a, 0x81, - 0xc2, 0xc5, 0xb8, 0xd4, 0x02, 0xde, 0x78, 0x47, 0xc4, 0xdb, 0x0e, 0xf0, 0xcc, 0x88, 0x22, 0x76, - 0x0b, 0xae, 0x76, 0x72, 0x41, 0x54, 0x9b, 0xcb, 0x00, 0x84, 0xc3, 0xf1, 0x0b, 0x68, 0x44, 0x4c, - 0x45, 0x3b, 0xf0, 0x20, 0xf3, 0xa3, 0x56, 0x00, 0x24, 0x11, 0x5e, 0xff, 0xb3, 0x0c, 0x57, 0xca, - 0x1c, 0x6d, 0xd1, 0xe3, 0xab, 0x14, 0xfd, 0x4b, 0x19, 0x84, 0x99, 0xb0, 0x55, 0xc3, 0xf3, 0x76, - 0x6c, 0xd7, 0xac, 0x6d, 0x19, 0x9d, 0xcd, 0x23, 0x79, 0x1d, 0xfd, 0xcb, 0x79, 0x74, 0x5a, 0xb9, - 0xcd, 0x3d, 0xe6, 0xe3, 0xed, 0xb2, 0x3a, 0xde, 0xe0, 0xd0, 0x0c, 0xe3, 0x4d, 0x3e, 0x34, 0xf3, - 0x91, 0xf7, 0x11, 0x34, 0x22, 0xde, 0xb9, 0x3e, 0x27, 0x46, 0x1e, 0x6c, 0xb7, 0x96, 0x49, 0x22, - 0x04, 0x7e, 0x11, 0x8d, 0x8a, 0x1f, 0x6c, 0xd5, 0x0d, 0xfc, 0xa2, 0x30, 0x8e, 0x3d, 0x06, 0x20, - 0x0a, 0x1a, 0xbf, 0x8c, 0x46, 0xd8, 0xe0, 0xdc, 0x84, 0x38, 0xc0, 0x62, 0x14, 0x2e, 0x67, 0x06, - 0x40, 0x79, 0x49, 0x08, 0x29, 0xd9, 0x52, 0x2e, 0x7c, 0xfc, 0xa5, 0x68, 0x29, 0xe7, 0x3e, 0x7e, - 0x79, 0x29, 0x17, 0xde, 0xfe, 0xf7, 0x50, 0xb9, 0xda, 0xe9, 0xd8, 0x3c, 0x1e, 0xd5, 0x13, 0x8e, - 0xac, 0x81, 0x6b, 0xf8, 0x73, 0x10, 0xc4, 0x15, 0xd1, 0xa7, 0x2e, 0xe2, 0xb2, 0x40, 0x7c, 0x8d, - 0x75, 0xc4, 0xb6, 0x45, 0x77, 0xa8, 0x2b, 0x42, 0x05, 0xc0, 0x99, 0xe7, 0x0a, 0x98, 0x1c, 0xd2, - 0x15, 0xd0, 0xe1, 0x59, 0x34, 0xb6, 0xea, 0xda, 0x8e, 0xed, 0x51, 0x93, 0x2b, 0xaa, 0x0c, 0x8c, - 0x17, 0xfa, 0xbd, 0x8a, 0xe6, 0x08, 0x44, 0x03, 0x34, 0x26, 0xb1, 0xab, 0x2c, 0xfa, 0x37, 0x32, - 0xb1, 0xc1, 0x78, 0x84, 0xf3, 0x5c, 0x19, 0x1d, 0xb9, 0x01, 0xa3, 0x43, 0xff, 0xd5, 0x2c, 0x2a, - 0xb3, 0x73, 0xc2, 0x82, 0xed, 0xee, 0x18, 0xee, 0xd1, 0x38, 0x4f, 0x0e, 0xed, 0x2e, 0x45, 0x32, - 0x43, 0x86, 0xf7, 0x61, 0x86, 0x5c, 0x40, 0xc3, 0xd2, 0xf5, 0x1f, 0x77, 0x66, 0xb0, 0xb3, 0x16, - 0x40, 0xf5, 0xff, 0x97, 0x45, 0xe8, 0xb3, 0x57, 0xaf, 0x3e, 0xc6, 0x0a, 0xd2, 0x7f, 0x25, 0x83, - 0xc6, 0x85, 0x77, 0x4d, 0x8a, 0x77, 0x2e, 0x06, 0x7e, 0xd1, 0x4c, 0xe4, 0x35, 0x14, 0xfe, 0x50, - 0x12, 0xe0, 0xd8, 0x4c, 0x9c, 0xbf, 0x67, 0xf9, 0xe0, 0x60, 0x90, 0x02, 0x9e, 0xa9, 0x80, 0xc9, - 0x33, 0x31, 0xa0, 0xc3, 0x2f, 0x06, 0x7e, 0xc3, 0x5c, 0xb4, 0xfc, 0x30, 0x86, 0xf9, 0x54, 0xdf, - 0xa1, 0xfe, 0xcd, 0x61, 0x34, 0x3c, 0x7f, 0x8f, 0x36, 0x8f, 0x79, 0xd7, 0x48, 0xa7, 0x91, 0xe1, - 0x03, 0x9e, 0x46, 0x1e, 0xe6, 0x22, 0xe4, 0x8d, 0xa8, 0x3f, 0x0b, 0x6a, 0xf3, 0xb1, 0x9e, 0x8f, - 0x37, 0x1f, 0xf4, 0xf4, 0xf1, 0xbb, 0x47, 0xfb, 0xf3, 0x1c, 0xca, 0xad, 0xd5, 0x56, 0x4f, 0xc6, - 0xcd, 0x91, 0x8e, 0x9b, 0xbd, 0x1d, 0xcd, 0x7a, 0xe8, 0x3b, 0x2a, 0x45, 0xb1, 0x03, 0x31, 0x37, - 0xd1, 0x57, 0xb2, 0x68, 0x64, 0xad, 0xbb, 0xe1, 0xed, 0x7a, 0x3e, 0x6d, 0x1f, 0xf3, 0xde, 0xbc, - 0x20, 0xce, 0x83, 0xc3, 0x91, 0x36, 0xd8, 0x79, 0x50, 0x9c, 0x02, 0x9f, 0x0b, 0x56, 0x46, 0xe9, - 0x9c, 0x11, 0xae, 0x8c, 0xc1, 0x7a, 0xf8, 0xfb, 0x59, 0x34, 0x51, 0x6b, 0x59, 0xb4, 0xe3, 0xcf, - 0x59, 0x5e, 0xf3, 0x10, 0x22, 0x25, 0x8e, 0x5e, 0x2b, 0x07, 0x73, 0xb4, 0x3c, 0x40, 0xfc, 0x89, - 0xfe, 0xff, 0xb3, 0xa8, 0x5c, 0xed, 0xfa, 0x5b, 0x55, 0x1f, 0x36, 0x97, 0xc7, 0x72, 0x9b, 0xff, - 0x76, 0x06, 0x8d, 0xb3, 0x07, 0x59, 0xb7, 0xef, 0xd2, 0xce, 0x21, 0x1c, 0xa3, 0xe4, 0xe3, 0x50, - 0xf6, 0x21, 0x8f, 0x43, 0x81, 0x2e, 0x73, 0xfb, 0x3c, 0x16, 0xb2, 0x83, 0x36, 0x3b, 0x25, 0x3d, - 0x22, 0xaf, 0x71, 0x08, 0xe7, 0x88, 0xa3, 0x7c, 0x8d, 0xef, 0x66, 0xd0, 0xd4, 0xba, 0xcb, 0x36, - 0x72, 0x53, 0xec, 0xe7, 0xc7, 0xbc, 0x5f, 0x92, 0x2f, 0x74, 0xcc, 0x7b, 0xe8, 0xfb, 0x19, 0xf4, - 0x84, 0xfa, 0x42, 0x8f, 0xc2, 0x2a, 0xf0, 0x57, 0x19, 0x74, 0xe6, 0x4d, 0x48, 0x82, 0x0d, 0x5d, - 0x70, 0x8f, 0xde, 0x1b, 0x1d, 0xf3, 0x91, 0xf7, 0x9d, 0x0c, 0x3a, 0xbd, 0x52, 0x9f, 0xab, 0x3d, - 0x2a, 0x3d, 0x94, 0x78, 0x9f, 0x47, 0xa0, 0x7f, 0xd6, 0xaa, 0xb7, 0x96, 0x1e, 0xa5, 0xfe, 0x51, - 0xde, 0xe7, 0x98, 0xf7, 0xcf, 0x4f, 0x17, 0x50, 0x99, 0x9d, 0x6b, 0x85, 0x4f, 0xef, 0xb1, 0xb6, - 0xf4, 0xaf, 0xa1, 0xb2, 0x50, 0x03, 0x1c, 0x29, 0xa5, 0x78, 0x7e, 0x91, 0x41, 0xde, 0x80, 0xa3, - 0xa5, 0x4c, 0xc4, 0x4e, 0x5c, 0xef, 0x50, 0x77, 0x43, 0x8e, 0xa1, 0xd9, 0xa6, 0xee, 0x06, 0x01, - 0x28, 0x5e, 0x8a, 0xee, 0xec, 0xaa, 0xab, 0x75, 0x48, 0x7c, 0x15, 0x27, 0x55, 0xc8, 0xe4, 0x75, - 0x05, 0xae, 0x61, 0x38, 0x16, 0x4f, 0x99, 0x95, 0xe3, 0xf7, 0xe2, 0x9c, 0x78, 0x19, 0x4d, 0x06, - 0xb0, 0x28, 0x83, 0xb5, 0x94, 0x22, 0x2e, 0x2d, 0x77, 0x35, 0xc9, 0x8a, 0xdf, 0x40, 0xa3, 0x01, - 0xf0, 0x2d, 0x0b, 0xf2, 0xeb, 0x98, 0xa8, 0x27, 0xfb, 0xbd, 0xca, 0xb9, 0x50, 0xd4, 0x5d, 0x4b, - 0x89, 0x4f, 0x54, 0x18, 0x64, 0x01, 0x70, 0xec, 0x44, 0x29, 0x02, 0x62, 0xf7, 0x91, 0x0a, 0x03, - 0x7e, 0x19, 0x04, 0x38, 0x76, 0xc7, 0xa3, 0xe0, 0xe3, 0x2b, 0x43, 0x80, 0x09, 0xdc, 0x09, 0xba, - 0x02, 0xce, 0xc3, 0x88, 0x14, 0x32, 0xbc, 0x82, 0x50, 0xe4, 0x8b, 0x11, 0xc1, 0x9a, 0xfb, 0xf6, - 0x12, 0x49, 0x22, 0xf4, 0xbf, 0x66, 0xe7, 0x37, 0xc7, 0x09, 0x47, 0xf2, 0x8b, 0xa8, 0x50, 0x75, - 0x9c, 0xdb, 0xa4, 0x2e, 0xbc, 0x93, 0x10, 0x4b, 0x68, 0x38, 0x4e, 0xa3, 0xeb, 0x5a, 0xf2, 0x85, - 0x04, 0x27, 0xc2, 0x35, 0x34, 0x56, 0x75, 0x9c, 0xd5, 0xee, 0x46, 0xcb, 0x6a, 0x4a, 0xe9, 0xe3, - 0xbc, 0x06, 0x81, 0xe3, 0x34, 0x1c, 0xc0, 0xc4, 0x93, 0xef, 0x55, 0x1e, 0xfc, 0x1e, 0x1a, 0xa9, - 0x3a, 0x8e, 0x48, 0xbe, 0xcf, 0x41, 0xf2, 0xbd, 0x1e, 0xbc, 0x93, 0xf4, 0x6c, 0xd3, 0x21, 0x11, - 0x4f, 0xbb, 0xbf, 0x20, 0x6e, 0x6b, 0xa7, 0x58, 0x43, 0x89, 0x9c, 0xfb, 0x48, 0x24, 0xfe, 0x38, - 0x2a, 0x56, 0x1d, 0x47, 0x72, 0x0f, 0x80, 0x2b, 0x95, 0x71, 0xc5, 0xba, 0x28, 0x20, 0x3b, 0xff, - 0x1a, 0x3a, 0xa5, 0x36, 0xb6, 0xaf, 0xfc, 0xfc, 0x9f, 0x64, 0xe0, 0x85, 0x8e, 0xf9, 0x85, 0xda, - 0x4b, 0x28, 0x57, 0x75, 0x1c, 0xb1, 0x9c, 0x9c, 0x4e, 0xe9, 0x8f, 0x78, 0xb4, 0x56, 0xd5, 0x71, - 0x82, 0x57, 0xe7, 0x57, 0xde, 0x8f, 0xd7, 0xab, 0x7f, 0x8b, 0xbf, 0xfa, 0x31, 0xbf, 0xa1, 0xfe, - 0x66, 0x0e, 0x8d, 0x57, 0x1d, 0xe7, 0x24, 0x6d, 0xfd, 0xb0, 0x62, 0xc2, 0xae, 0x22, 0x24, 0x2d, - 0x8f, 0xc5, 0x30, 0xf4, 0xa3, 0x2c, 0x2d, 0x8d, 0x5a, 0x86, 0x48, 0x44, 0xc1, 0xf0, 0x2b, 0xed, - 0x6b, 0xf8, 0xfd, 0x89, 0xd2, 0x71, 0x90, 0x82, 0x7b, 0xd2, 0x71, 0xf9, 0x03, 0x59, 0x54, 0xa7, - 0x64, 0x65, 0x8a, 0x80, 0x6f, 0x71, 0x43, 0x1f, 0xa4, 0x1f, 0x34, 0x19, 0xaa, 0x61, 0x99, 0x24, - 0x46, 0x1b, 0xf4, 0x61, 0x71, 0x5f, 0x7d, 0xf8, 0xb5, 0x2c, 0x9a, 0x8c, 0xfa, 0xf0, 0x30, 0x0c, - 0xd3, 0x19, 0x84, 0xb8, 0x93, 0x32, 0xbc, 0x48, 0x1c, 0xe3, 0x91, 0xca, 0x1e, 0x40, 0x45, 0xa4, - 0x72, 0x44, 0x12, 0xde, 0x2a, 0xe4, 0x52, 0x6f, 0x15, 0x2e, 0xa3, 0x12, 0x31, 0x76, 0xde, 0xee, - 0x52, 0x77, 0x57, 0x6c, 0xa5, 0xe0, 0x4a, 0x77, 0x8d, 0x9d, 0xc6, 0x17, 0x19, 0x90, 0x84, 0x68, - 0xac, 0x87, 0x31, 0x6a, 0x92, 0xf3, 0x98, 0xc7, 0xa8, 0x85, 0x91, 0x69, 0x42, 0x49, 0x85, 0x7d, - 0x29, 0xe9, 0xfb, 0x05, 0x34, 0x31, 0x67, 0xf8, 0xc6, 0x86, 0xe1, 0x51, 0xe9, 0x20, 0x31, 0x1e, - 0xc0, 0xd8, 0x40, 0xb0, 0xc2, 0x92, 0x45, 0x10, 0x97, 0x65, 0x6e, 0x34, 0x3c, 0x0e, 0x95, 0x6b, - 0xbc, 0xc4, 0x18, 0xf0, 0xa7, 0x22, 0xb9, 0x61, 0x51, 0x1b, 0x6e, 0xce, 0x80, 0xc6, 0xcc, 0x8d, - 0x86, 0x23, 0xc0, 0x24, 0x41, 0x88, 0xaf, 0xa0, 0x72, 0x00, 0x63, 0xc6, 0x53, 0x2e, 0x7a, 0x67, - 0x73, 0x83, 0xd9, 0x4e, 0x44, 0x46, 0xe3, 0x4f, 0xa2, 0xd1, 0xe0, 0xa7, 0x64, 0x96, 0x80, 0xad, - 0x65, 0x6e, 0x24, 0x0c, 0x47, 0x99, 0x54, 0x66, 0x85, 0xf9, 0x99, 0x57, 0x58, 0x63, 0x05, 0xad, - 0x14, 0x52, 0xfc, 0x45, 0x74, 0x2a, 0xf8, 0x2d, 0x8c, 0xad, 0x02, 0x18, 0x5b, 0x57, 0x02, 0xcd, - 0xc7, 0xd5, 0x3a, 0xad, 0x92, 0x73, 0xb3, 0xeb, 0x49, 0x61, 0x76, 0x9d, 0x36, 0x37, 0x92, 0x56, - 0x57, 0xac, 0x01, 0x5c, 0x47, 0x93, 0x01, 0xa4, 0xfa, 0xee, 0x1a, 0xa1, 0x9b, 0x6c, 0x56, 0x16, - 0x23, 0x63, 0xd9, 0xdc, 0x68, 0x40, 0xa9, 0x2b, 0x40, 0xc8, 0x36, 0x7b, 0x82, 0x0b, 0xb7, 0xd0, - 0x05, 0x05, 0x68, 0x7a, 0x5b, 0xd6, 0x1d, 0x5f, 0x58, 0xba, 0xf5, 0x39, 0x71, 0x1c, 0x80, 0x5a, - 0x30, 0xa1, 0x54, 0x4e, 0x13, 0x14, 0x0b, 0x6a, 0x28, 0x75, 0xd3, 0xf6, 0x94, 0x86, 0xd7, 0xd0, - 0x54, 0x80, 0x7f, 0xb3, 0xb6, 0xba, 0xea, 0xda, 0x5f, 0xa0, 0x4d, 0xbf, 0x3e, 0x27, 0x4e, 0x0a, - 0x90, 0x82, 0x64, 0x6e, 0x34, 0x36, 0x9b, 0x0e, 0x1b, 0x14, 0x0c, 0xa7, 0x0a, 0x4f, 0x65, 0xc6, - 0xef, 0xa0, 0x33, 0x12, 0xbc, 0xde, 0xf1, 0x7c, 0xa3, 0xd3, 0xa4, 0xf5, 0x39, 0x71, 0x7c, 0x80, - 0xa3, 0x8c, 0x90, 0x6a, 0x09, 0xa4, 0x2a, 0x36, 0x9d, 0xfd, 0x7c, 0x15, 0x9d, 0x4e, 0xe9, 0xa9, - 0x7d, 0xd9, 0xac, 0x5f, 0xc9, 0x46, 0x83, 0xe3, 0x98, 0x1b, 0xae, 0xb3, 0xa8, 0x14, 0xbc, 0x89, - 0xd8, 0x42, 0xb4, 0x41, 0x03, 0x3c, 0x2e, 0x23, 0xc0, 0x2b, 0xea, 0x38, 0xe6, 0xc6, 0xec, 0x61, - 0xa8, 0xe3, 0x83, 0x4c, 0xa4, 0x8e, 0x63, 0x6e, 0xe0, 0x7e, 0x27, 0x17, 0xcd, 0xec, 0x13, 0x2b, - 0xf7, 0xb0, 0x8c, 0xa5, 0xe8, 0xe2, 0xb4, 0xb0, 0x8f, 0x00, 0x32, 0x79, 0x68, 0x16, 0x1f, 0x72, - 0x68, 0xfe, 0x5d, 0xb2, 0x3f, 0xb9, 0x01, 0x72, 0x2c, 0xfb, 0xf3, 0x10, 0x26, 0x2b, 0xbe, 0x86, - 0xc6, 0x82, 0xff, 0xb9, 0xa5, 0x96, 0x97, 0xf2, 0xa1, 0x36, 0x84, 0xa1, 0xa6, 0x92, 0xe0, 0xcf, - 0xa1, 0x73, 0x0a, 0x60, 0xd5, 0x70, 0x8d, 0x36, 0xf5, 0xa9, 0xcb, 0x6d, 0x04, 0x51, 0x7e, 0x2e, - 0xe0, 0x6e, 0x38, 0x21, 0x5a, 0xae, 0xe0, 0x36, 0x40, 0x82, 0x34, 0x38, 0x8a, 0xfb, 0xb8, 0x55, - 0xff, 0xe7, 0x2c, 0x1a, 0x5b, 0xb5, 0x3d, 0x7f, 0xd3, 0xa5, 0xde, 0xaa, 0xe1, 0x7a, 0xf4, 0xf1, - 0xed, 0xd1, 0x4f, 0xa0, 0x31, 0x88, 0x93, 0x6d, 0xd3, 0x8e, 0x2f, 0xd5, 0xa5, 0xe3, 0x35, 0x1a, - 0x02, 0x04, 0x98, 0x8d, 0x44, 0x25, 0xc4, 0x15, 0x94, 0xe7, 0x63, 0x40, 0x8a, 0x5e, 0xe6, 0x03, - 0x80, 0xc3, 0xf5, 0xaf, 0xe5, 0xd0, 0x68, 0xa0, 0xe5, 0x59, 0xeb, 0xb8, 0x66, 0x01, 0x1f, 0xad, - 0x92, 0x67, 0x10, 0x5a, 0xb5, 0x5d, 0xdf, 0x68, 0x49, 0x25, 0x71, 0xe1, 0xc8, 0xe0, 0x00, 0x94, - 0xf3, 0x48, 0x24, 0x78, 0x1a, 0x21, 0x69, 0x82, 0x15, 0x61, 0x82, 0x9d, 0xea, 0xf7, 0x2a, 0x28, - 0x9a, 0x57, 0x44, 0xa2, 0xd0, 0xff, 0x28, 0x8b, 0xc6, 0x83, 0x4e, 0x9a, 0xbf, 0x47, 0x9b, 0x5d, - 0xff, 0x31, 0x9e, 0x0c, 0xaa, 0xb6, 0xf3, 0xf7, 0xd5, 0xb6, 0xfe, 0xef, 0xd2, 0x42, 0x52, 0x6b, - 0xd9, 0x27, 0x0b, 0xc9, 0xff, 0xc6, 0x18, 0xd7, 0x7f, 0x26, 0x87, 0xa6, 0x02, 0xad, 0x2f, 0x74, - 0x3b, 0x60, 0x26, 0xd4, 0x8c, 0x56, 0xeb, 0x71, 0xde, 0x97, 0xcb, 0x81, 0x22, 0x56, 0x44, 0xe2, - 0xc9, 0x18, 0xbf, 0x64, 0xbb, 0x23, 0xc0, 0x0d, 0xdb, 0x32, 0x89, 0x4c, 0x84, 0xdf, 0x40, 0xa3, - 0xc1, 0xcf, 0xaa, 0xbb, 0x19, 0x6c, 0xc6, 0x70, 0x74, 0x0e, 0x99, 0x0c, 0x77, 0x53, 0x29, 0x36, - 0x2c, 0x33, 0xe8, 0xff, 0x9a, 0x47, 0xe7, 0xdf, 0xb5, 0x3a, 0xa6, 0xbd, 0xe3, 0x89, 0x62, 0x5c, - 0xc7, 0xdf, 0xe8, 0x3d, 0xbc, 0x1a, 0x38, 0x91, 0x65, 0x92, 0xdf, 0x87, 0xd9, 0xfa, 0x36, 0x3a, - 0x13, 0x57, 0xa9, 0x1b, 0xe6, 0x3b, 0x8a, 0xde, 0xd9, 0xe1, 0x04, 0x8d, 0xa0, 0x1e, 0x9a, 0xf0, - 0x3f, 0x91, 0x74, 0xce, 0x78, 0x41, 0xb5, 0xe2, 0x83, 0x14, 0x54, 0xfb, 0x18, 0x2a, 0xcc, 0xd9, - 0x6d, 0xc3, 0x0a, 0x42, 0x7c, 0x61, 0x16, 0x87, 0xed, 0x02, 0x86, 0x08, 0x0a, 0x26, 0x5f, 0x34, - 0x0c, 0x5d, 0x36, 0x12, 0xc9, 0x0f, 0x18, 0xba, 0x1e, 0x75, 0x89, 0x4c, 0x84, 0x6d, 0x34, 0x26, - 0x9a, 0x13, 0xde, 0x22, 0x04, 0xde, 0xa2, 0x97, 0x03, 0x1d, 0x0d, 0x1e, 0x56, 0xd3, 0x0a, 0x1f, - 0x77, 0x1b, 0xc1, 0xd3, 0x05, 0x2f, 0xc3, 0xfd, 0x46, 0x44, 0x95, 0x7f, 0xfe, 0x26, 0xc2, 0x49, - 0xc6, 0x7d, 0x79, 0x31, 0x7e, 0x3e, 0x8b, 0x70, 0xec, 0x30, 0x30, 0xff, 0x18, 0xdb, 0x34, 0xfa, - 0xef, 0x64, 0xd0, 0x64, 0x22, 0xeb, 0x16, 0xbf, 0x84, 0x10, 0x87, 0x48, 0x19, 0xb4, 0x90, 0x2f, - 0x17, 0x65, 0xe2, 0x8a, 0xf5, 0x3c, 0x22, 0xc3, 0x33, 0xa8, 0xc4, 0x7f, 0x85, 0x15, 0xdd, 0xe3, - 0x2c, 0xdd, 0xae, 0x65, 0x92, 0x90, 0x28, 0x6a, 0x05, 0xbe, 0x6d, 0x90, 0x4b, 0x65, 0xf1, 0x77, - 0x9d, 0xb0, 0x15, 0x46, 0xa6, 0x7f, 0x2b, 0x83, 0x46, 0xc3, 0x07, 0xae, 0x9a, 0x47, 0xd5, 0x75, - 0x05, 0x91, 0xc0, 0x9c, 0xbb, 0x5f, 0x02, 0x73, 0x6c, 0x81, 0x10, 0x19, 0xcb, 0x7f, 0x91, 0x41, - 0xe3, 0x21, 0xed, 0x11, 0xfa, 0x4b, 0x0e, 0xfc, 0x22, 0xbf, 0x90, 0x41, 0xda, 0xac, 0xd5, 0x6a, - 0x59, 0x9d, 0xcd, 0x7a, 0xe7, 0x8e, 0xed, 0xb6, 0x21, 0x4f, 0xf0, 0xe8, 0x1c, 0x62, 0xfa, 0xcf, - 0x65, 0xd0, 0xa4, 0x78, 0xa0, 0x9a, 0xe1, 0x9a, 0x47, 0xe7, 0xa9, 0x8c, 0x3f, 0xc9, 0xd1, 0xf5, - 0x32, 0xc4, 0x3a, 0x2f, 0xd9, 0xcd, 0xbb, 0x8f, 0x40, 0xc8, 0x36, 0x7b, 0x8d, 0x63, 0x1e, 0x56, - 0xf6, 0xe5, 0x0c, 0x9a, 0x22, 0xb4, 0x69, 0x6f, 0x53, 0x77, 0xb7, 0x66, 0x9b, 0xf4, 0x4d, 0xda, - 0xa1, 0xee, 0x51, 0x0d, 0xd2, 0x3f, 0x86, 0xd2, 0x09, 0xd1, 0xc3, 0xdc, 0xf6, 0xa8, 0x79, 0x7c, - 0xea, 0x5c, 0xe8, 0x7f, 0x50, 0x44, 0x5a, 0xaa, 0x95, 0x71, 0x6c, 0x37, 0xf5, 0x81, 0xa6, 0xe3, - 0xf0, 0x61, 0x99, 0x8e, 0xf9, 0xfd, 0x99, 0x8e, 0x85, 0xfd, 0x9a, 0x8e, 0xc5, 0x07, 0x31, 0x1d, - 0xdb, 0x71, 0xd3, 0xb1, 0x04, 0xa6, 0xe3, 0x4b, 0x7b, 0x9a, 0x8e, 0xf3, 0x1d, 0xf3, 0xe1, 0x0c, - 0xc7, 0xe3, 0x5b, 0xdd, 0x31, 0xea, 0x3b, 0x30, 0xc5, 0xca, 0xc9, 0xbe, 0x03, 0x3b, 0x4c, 0x26, - 0xc2, 0x97, 0xd8, 0xe2, 0xd6, 0xb4, 0x5d, 0x93, 0xf2, 0x6a, 0x8d, 0x25, 0xee, 0xd9, 0x75, 0x05, - 0x8c, 0x84, 0xd8, 0x44, 0xa9, 0xcc, 0xb1, 0x07, 0x29, 0x95, 0x79, 0x08, 0x56, 0xf8, 0x77, 0x33, - 0x68, 0xb2, 0x46, 0x5d, 0xdf, 0xba, 0x63, 0x35, 0x0d, 0xff, 0x30, 0xae, 0x13, 0xab, 0x68, 0x5c, - 0x12, 0x28, 0x7d, 0x2c, 0x0b, 0x72, 0x9c, 0x9b, 0xd4, 0xf5, 0xc1, 0x94, 0x94, 0x6f, 0xf7, 0x63, - 0xf4, 0xac, 0xf9, 0xa0, 0x5c, 0x8d, 0x98, 0xbb, 0x61, 0xf3, 0x01, 0x9c, 0x2b, 0xd2, 0x12, 0xbf, - 0x48, 0x48, 0xaf, 0x7f, 0x3d, 0x83, 0x2e, 0x12, 0xda, 0xa1, 0x3b, 0xc6, 0x46, 0x8b, 0x4a, 0x82, - 0xc5, 0xda, 0xce, 0xe6, 0xbd, 0xe5, 0xb5, 0x0d, 0xbf, 0xb9, 0x75, 0xa0, 0xb7, 0x5c, 0x50, 0x3f, - 0x58, 0xb5, 0x8f, 0xd5, 0x49, 0xe1, 0xd3, 0x7f, 0xf4, 0x02, 0xca, 0xaf, 0x74, 0xe8, 0xca, 0x1d, - 0x7c, 0x55, 0xaa, 0x6f, 0x24, 0x1e, 0x67, 0x52, 0x16, 0x07, 0x88, 0xc5, 0x21, 0x22, 0x55, 0x41, - 0xba, 0x2e, 0x57, 0x9f, 0x11, 0x8f, 0x80, 0x65, 0x1e, 0x8e, 0x59, 0x1c, 0x22, 0x72, 0x95, 0x9a, - 0xeb, 0x72, 0x79, 0x16, 0xa1, 0x5f, 0x85, 0x8b, 0x63, 0x02, 0x2e, 0xb1, 0xc7, 0x2f, 0xa5, 0x55, - 0x43, 0x89, 0x1f, 0xe8, 0x93, 0x14, 0x8b, 0x43, 0x24, 0xbd, 0x8a, 0x8a, 0xf2, 0xb5, 0x15, 0x71, - 0xa4, 0x9f, 0x8a, 0xad, 0xd0, 0x80, 0x5b, 0x1c, 0x22, 0xea, 0x97, 0x59, 0x6e, 0x28, 0xdf, 0xb1, - 0x88, 0x47, 0xa4, 0x48, 0xa8, 0xc5, 0x21, 0x12, 0xfb, 0xe2, 0x85, 0xf2, 0x51, 0x05, 0x71, 0xc3, - 0x11, 0x6f, 0x14, 0x70, 0x52, 0xa3, 0xfc, 0x03, 0x0c, 0xaf, 0xc7, 0x8a, 0x9d, 0x8b, 0x88, 0xaf, - 0x33, 0x31, 0x66, 0x8e, 0x5c, 0x1c, 0x22, 0xb1, 0xd2, 0xe8, 0x97, 0x82, 0xfa, 0xd7, 0x62, 0xc9, - 0x3b, 0x25, 0xd9, 0x38, 0xd6, 0xfb, 0x4c, 0x4b, 0x41, 0x7d, 0xec, 0xeb, 0x72, 0xdd, 0x63, 0xb1, - 0x86, 0xe1, 0x58, 0x2b, 0xf3, 0x1d, 0x93, 0xf5, 0x8e, 0xb4, 0xc1, 0xde, 0x8c, 0x57, 0x08, 0x15, - 0x75, 0x67, 0xcf, 0xc6, 0x38, 0x05, 0x76, 0x71, 0x88, 0xc4, 0x2b, 0x8a, 0xde, 0x50, 0xaa, 0x53, - 0x8a, 0x70, 0xe5, 0xb8, 0x56, 0x19, 0x4a, 0xd2, 0x2a, 0xd4, 0xb1, 0xbc, 0x19, 0x2f, 0x97, 0xa8, - 0x8d, 0xa5, 0x36, 0x2d, 0xb0, 0x52, 0xd3, 0x41, 0x79, 0xc5, 0x1b, 0x4a, 0x59, 0x3b, 0xa8, 0x1c, - 0x9b, 0xd2, 0xb4, 0xe1, 0x1b, 0x72, 0xd3, 0xbc, 0x00, 0x9e, 0x52, 0x60, 0x4d, 0x1b, 0x4f, 0xed, - 0x50, 0xc0, 0x49, 0x1d, 0xca, 0x8b, 0xb1, 0xdd, 0x50, 0x6a, 0x82, 0x68, 0x13, 0x6a, 0xa3, 0x12, - 0x8a, 0x35, 0x2a, 0x57, 0x0f, 0xb9, 0x2e, 0x97, 0xca, 0xd0, 0x26, 0xd5, 0x0e, 0x8a, 0x30, 0xac, - 0x83, 0xa4, 0x92, 0x1a, 0x15, 0x48, 0xc3, 0xd7, 0x30, 0x90, 0x97, 0xc3, 0x27, 0xac, 0xad, 0x2e, - 0x0e, 0x11, 0x48, 0xd0, 0xd7, 0x79, 0x81, 0x07, 0xed, 0x34, 0x50, 0x8c, 0x86, 0xe5, 0x03, 0xef, - 0xd1, 0xe6, 0xe2, 0x10, 0xe1, 0xc5, 0x1f, 0xae, 0x4a, 0x39, 0xe0, 0xda, 0x94, 0xba, 0x44, 0x84, - 0x08, 0xb6, 0x44, 0x44, 0x99, 0xe2, 0x0b, 0xc9, 0x3c, 0x69, 0xed, 0x8c, 0x6a, 0x66, 0xc7, 0xf1, - 0x8b, 0x43, 0x24, 0x99, 0x5b, 0x7d, 0x43, 0x49, 0x1d, 0xd6, 0xce, 0xc6, 0xc2, 0xc0, 0x22, 0x14, - 0x53, 0x97, 0x9c, 0x64, 0xbc, 0x92, 0x5a, 0xba, 0x48, 0x3b, 0x07, 0x02, 0x9e, 0x0c, 0x05, 0x24, - 0x49, 0x16, 0x87, 0x48, 0x6a, 0xd1, 0xa3, 0x5a, 0x22, 0x81, 0x57, 0xd3, 0x54, 0xfb, 0x2e, 0x86, - 0x5e, 0x1c, 0x22, 0x89, 0x94, 0xdf, 0xeb, 0x72, 0xe6, 0xac, 0xf6, 0x84, 0xda, 0x89, 0x11, 0x86, - 0x75, 0xa2, 0x94, 0x61, 0x7b, 0x5d, 0x4e, 0x54, 0xd5, 0xce, 0x27, 0xb9, 0xa2, 0x95, 0x53, 0x4a, - 0x68, 0x25, 0xe9, 0x79, 0xa1, 0xda, 0x93, 0xc0, 0x7f, 0x21, 0xe0, 0x4f, 0xa3, 0x59, 0x1c, 0x22, - 0xe9, 0x39, 0xa5, 0x24, 0x3d, 0x35, 0x53, 0xbb, 0xb0, 0x97, 0xcc, 0xf0, 0xe9, 0xd2, 0xd3, 0x3a, - 0x8d, 0x3d, 0xb2, 0x23, 0xb5, 0xa7, 0xd4, 0xf4, 0x85, 0x81, 0x84, 0x8b, 0x43, 0x64, 0x8f, 0x1c, - 0xcb, 0xdb, 0x03, 0x52, 0x15, 0xb5, 0xa7, 0xd5, 0x42, 0x17, 0xa9, 0x44, 0x8b, 0x43, 0x64, 0x40, - 0xa2, 0xe3, 0xed, 0x01, 0xf9, 0x82, 0x5a, 0x65, 0x4f, 0xb1, 0xa1, 0x3e, 0x06, 0x64, 0x1b, 0xae, - 0xa4, 0x26, 0xed, 0x69, 0xcf, 0xa8, 0x43, 0x37, 0x85, 0x84, 0x0d, 0xdd, 0xb4, 0x74, 0xbf, 0x95, - 0xd4, 0xac, 0x39, 0xed, 0xd9, 0x3d, 0x04, 0x86, 0xcf, 0x98, 0x9a, 0x6f, 0xb7, 0x92, 0x9a, 0xb6, - 0xa6, 0xe9, 0xaa, 0xc0, 0x14, 0x12, 0x26, 0x30, 0x2d, 0xe1, 0x6d, 0x25, 0x35, 0x6f, 0x4c, 0x7b, - 0x6e, 0x0f, 0x81, 0xd1, 0x13, 0xa6, 0x65, 0x9c, 0xdd, 0x50, 0x12, 0xb7, 0xb4, 0x8f, 0xa8, 0xeb, - 0x86, 0x84, 0x62, 0xeb, 0x86, 0x9c, 0xe2, 0x55, 0x4b, 0xc4, 0xb6, 0x6b, 0x1f, 0x55, 0xa7, 0x79, - 0x0c, 0xcd, 0xa6, 0x79, 0x3c, 0x1a, 0xbe, 0x96, 0x88, 0xb3, 0xd6, 0x2e, 0x0e, 0x12, 0x02, 0x68, - 0x55, 0x08, 0x8f, 0xcc, 0xae, 0xa7, 0x04, 0xfa, 0x6a, 0xcf, 0xab, 0x4e, 0xba, 0x04, 0xc1, 0xe2, - 0x10, 0x49, 0x09, 0x0f, 0x26, 0xe9, 0xf1, 0x4c, 0xda, 0x25, 0x75, 0xda, 0xa6, 0xd1, 0xb0, 0x69, - 0x9b, 0x1a, 0x0b, 0xb5, 0x94, 0xe6, 0x46, 0xd7, 0x2e, 0xab, 0x86, 0x59, 0x92, 0x82, 0x19, 0x66, - 0x29, 0xee, 0x77, 0x92, 0x1e, 0xa1, 0xa3, 0x7d, 0x6c, 0xcf, 0x27, 0x04, 0x9a, 0x94, 0x27, 0xe4, - 0x01, 0x2b, 0x91, 0xed, 0x74, 0xdb, 0x69, 0xd9, 0x86, 0xa9, 0xbd, 0x90, 0x6a, 0x3b, 0x71, 0xa4, - 0x64, 0x3b, 0x71, 0x00, 0xdb, 0xe5, 0x65, 0x37, 0xb3, 0x76, 0x45, 0xdd, 0xe5, 0x65, 0x1c, 0xdb, - 0xe5, 0x15, 0x97, 0x74, 0x2d, 0xe1, 0xdc, 0xd5, 0x5e, 0x54, 0x07, 0x40, 0x0c, 0xcd, 0x06, 0x40, - 0xdc, 0x1d, 0xfc, 0xde, 0x60, 0xc7, 0xaa, 0x36, 0x0d, 0xd2, 0x9e, 0x09, 0x0b, 0x24, 0x0f, 0xa0, - 0x5b, 0x1c, 0x22, 0x83, 0x9d, 0xb3, 0xf5, 0x14, 0x3f, 0xa9, 0x36, 0xa3, 0x0e, 0xb0, 0x04, 0x01, - 0x1b, 0x60, 0x49, 0xef, 0x6a, 0x3d, 0xc5, 0xd1, 0xa9, 0x7d, 0x7c, 0xa0, 0xa8, 0xf0, 0x9d, 0x53, - 0xdc, 0xa3, 0xd7, 0x65, 0x4f, 0xa5, 0x76, 0x55, 0xdd, 0xec, 0x22, 0x0c, 0xdb, 0xec, 0x24, 0x8f, - 0xe6, 0x75, 0xd9, 0x31, 0xa8, 0x5d, 0x4b, 0x72, 0x45, 0x5b, 0xa4, 0xe4, 0x40, 0x24, 0xe9, 0x7e, - 0x38, 0xed, 0x25, 0x75, 0xd4, 0xa5, 0xd1, 0xb0, 0x51, 0x97, 0xea, 0xc3, 0x5b, 0x48, 0xba, 0xd3, - 0xb4, 0xeb, 0x71, 0x07, 0xa3, 0x8a, 0x67, 0x96, 0x4f, 0xc2, 0x05, 0x77, 0x33, 0x1e, 0x6c, 0xab, - 0xbd, 0xac, 0xda, 0xb7, 0x2a, 0x96, 0xd9, 0xb7, 0xb1, 0xe0, 0xdc, 0x9b, 0xf1, 0xf8, 0x54, 0xed, - 0x95, 0x74, 0x09, 0xe1, 0x58, 0x89, 0xc7, 0xb3, 0xde, 0x8c, 0x87, 0x74, 0x6a, 0x37, 0xd2, 0x25, - 0x84, 0xda, 0x8d, 0x87, 0x80, 0x5e, 0x95, 0xd2, 0xdc, 0xb4, 0x4f, 0xa8, 0xa6, 0x63, 0x88, 0x60, - 0xa6, 0x63, 0x94, 0x0c, 0x77, 0x55, 0x4a, 0x0f, 0xd3, 0x3e, 0x99, 0x60, 0x09, 0x1f, 0x56, 0x4a, - 0x22, 0xbb, 0x2a, 0xa5, 0x55, 0x69, 0xaf, 0x26, 0x58, 0xc2, 0xa7, 0x93, 0x92, 0xaf, 0xcc, 0xbd, - 0xee, 0xbc, 0xb5, 0x4f, 0x81, 0x0c, 0xfd, 0xfe, 0xd7, 0x98, 0x8b, 0x43, 0x64, 0xaf, 0xbb, 0xf3, - 0xf7, 0x06, 0x3b, 0x27, 0xb5, 0xd7, 0xd4, 0x29, 0x3c, 0x88, 0x8e, 0x4d, 0xe1, 0x81, 0x0e, 0xce, - 0xd7, 0x63, 0xf1, 0x6f, 0xda, 0xeb, 0xea, 0x12, 0xa7, 0x20, 0xd9, 0x12, 0x17, 0x8f, 0x96, 0x53, - 0x02, 0xbb, 0xb4, 0x4f, 0xab, 0x4b, 0x9c, 0x8c, 0x63, 0x4b, 0x9c, 0x12, 0x04, 0x56, 0x4b, 0xc4, - 0x1b, 0x69, 0x6f, 0xa8, 0x4b, 0x5c, 0x0c, 0xcd, 0x96, 0xb8, 0x78, 0x84, 0xd2, 0xeb, 0xb1, 0xb0, - 0x1b, 0xed, 0x66, 0xfa, 0xf3, 0x03, 0x52, 0x7e, 0x7e, 0x1e, 0xa4, 0x43, 0xd2, 0xe3, 0x47, 0xb4, - 0xaa, 0x3a, 0x7f, 0xd3, 0x68, 0xd8, 0xfc, 0x4d, 0x8d, 0x3d, 0x59, 0x49, 0x2d, 0x33, 0xa9, 0xcd, - 0xee, 0x71, 0x70, 0x88, 0x4c, 0x91, 0xb4, 0x02, 0x95, 0x37, 0xe3, 0x9f, 0xd6, 0xd2, 0x6a, 0x03, - 0xce, 0xc8, 0xc1, 0x31, 0x28, 0xfe, 0x29, 0xae, 0x7a, 0x8a, 0xaf, 0x4c, 0x9b, 0x53, 0x57, 0xd7, - 0x04, 0x01, 0x5b, 0x5d, 0x93, 0x1e, 0xb6, 0x85, 0xe4, 0x17, 0x0d, 0xb5, 0xf9, 0xd8, 0xcd, 0x71, - 0x0c, 0xcf, 0x56, 0xa7, 0xc4, 0x57, 0x10, 0x49, 0xfa, 0x47, 0xef, 0xb4, 0x85, 0xd8, 0x7e, 0x9d, - 0x42, 0x03, 0xfb, 0x75, 0xda, 0x07, 0xf3, 0x3e, 0x37, 0xf0, 0xdb, 0x85, 0xda, 0x9b, 0x20, 0xb6, - 0x32, 0x48, 0xac, 0x20, 0x5b, 0x1c, 0x22, 0x03, 0xbf, 0x7e, 0x78, 0x1b, 0x9d, 0xb9, 0xb5, 0xbb, - 0xf6, 0xf6, 0x52, 0x18, 0xb2, 0xb4, 0xea, 0x52, 0xc7, 0x70, 0xa9, 0xb6, 0xa8, 0xda, 0xea, 0xa9, - 0x44, 0xcc, 0x56, 0x4f, 0x45, 0x24, 0xc5, 0x06, 0x73, 0xa1, 0xbe, 0x97, 0xd8, 0x68, 0x46, 0xa4, - 0x73, 0xb3, 0xd5, 0x49, 0x45, 0x30, 0x05, 0x2d, 0xd9, 0x9d, 0x4d, 0xf0, 0x54, 0x7c, 0x46, 0x5d, - 0x9d, 0x06, 0x53, 0xb2, 0xd5, 0x69, 0x30, 0x96, 0x0d, 0x75, 0x15, 0xcb, 0xe7, 0xe0, 0x5b, 0xea, - 0x50, 0x4f, 0x21, 0x61, 0x43, 0x3d, 0x05, 0x9c, 0x14, 0x48, 0xa8, 0x47, 0x7d, 0x6d, 0x69, 0x2f, - 0x81, 0x40, 0x92, 0x14, 0x08, 0xe0, 0xa4, 0xc0, 0x05, 0xea, 0x37, 0xb7, 0xb4, 0x5b, 0x7b, 0x09, - 0x04, 0x92, 0xa4, 0x40, 0x00, 0xb3, 0xc3, 0xa6, 0x0a, 0x9e, 0xed, 0xb6, 0xee, 0x06, 0x7d, 0xb6, - 0xac, 0x1e, 0x36, 0x07, 0x12, 0xb2, 0xc3, 0xe6, 0x40, 0x24, 0xfe, 0xd2, 0x03, 0x7b, 0x82, 0xb5, - 0x15, 0x68, 0x70, 0x3a, 0xb2, 0x0b, 0x1e, 0x84, 0x6b, 0x71, 0x88, 0x3c, 0xa0, 0xfc, 0xd9, 0x22, - 0xca, 0xc3, 0x77, 0x20, 0xf4, 0x5f, 0xcb, 0xa0, 0xd1, 0x35, 0xdf, 0xa5, 0x46, 0x5b, 0xc4, 0x27, - 0x9d, 0x47, 0x25, 0x6e, 0xe6, 0x8a, 0x6f, 0x1c, 0x8d, 0x90, 0xf0, 0x37, 0xbe, 0x88, 0x4e, 0x2d, - 0x19, 0x9e, 0x0f, 0x9c, 0xd2, 0xc7, 0x5b, 0x49, 0x0c, 0x8a, 0x97, 0x38, 0x1d, 0xe7, 0x83, 0xab, - 0x8f, 0xdc, 0x7d, 0xaf, 0x3e, 0x4a, 0x1f, 0xf4, 0x2a, 0x43, 0x70, 0xc1, 0x11, 0xe3, 0xd5, 0xfb, - 0x19, 0x94, 0x30, 0xc0, 0x1f, 0xde, 0x4f, 0xbe, 0x82, 0xc6, 0x63, 0xd7, 0x6d, 0xc2, 0x4f, 0xfd, - 0x80, 0xb7, 0x71, 0x71, 0x6e, 0xfc, 0x1c, 0xca, 0xdd, 0xae, 0xcf, 0xc9, 0x75, 0xd9, 0xbb, 0x4a, - 0xda, 0x15, 0xc3, 0xe2, 0xe7, 0x43, 0x27, 0xea, 0x6d, 0xb2, 0x24, 0xae, 0xd9, 0xe0, 0xdb, 0x51, - 0x5d, 0xb7, 0x45, 0x24, 0x94, 0xfe, 0xed, 0x72, 0x74, 0xd5, 0x80, 0x2f, 0x8a, 0x9b, 0x46, 0xa9, - 0x4e, 0x7d, 0x2c, 0x47, 0x8f, 0xdf, 0x2c, 0x7e, 0x1a, 0x8d, 0xd6, 0xdb, 0x0e, 0x75, 0x3d, 0xbb, - 0x03, 0x15, 0xa4, 0xf9, 0xf5, 0xc6, 0xf9, 0x7e, 0xaf, 0x72, 0xd6, 0x92, 0xe0, 0x72, 0x9c, 0x9f, - 0x4c, 0x1f, 0x95, 0xbf, 0xce, 0xdd, 0xb7, 0xfc, 0xf5, 0x65, 0x94, 0xbf, 0xed, 0xf1, 0xaf, 0xa7, - 0x87, 0xa4, 0xdd, 0xd8, 0xb7, 0xa2, 0x38, 0x05, 0xbe, 0x82, 0x0a, 0x70, 0x2b, 0xe0, 0x69, 0x79, - 0xa0, 0x85, 0xdc, 0xd7, 0x16, 0x40, 0xe4, 0x62, 0x10, 0x9c, 0x26, 0xf5, 0x23, 0xf9, 0x85, 0x87, - 0xfc, 0x48, 0x7e, 0xda, 0xb7, 0xfb, 0x8b, 0x0f, 0xf7, 0xed, 0xfe, 0x7a, 0xf4, 0x09, 0x80, 0xd2, - 0x7d, 0xc7, 0xf0, 0x69, 0x71, 0x7d, 0x57, 0x14, 0x9f, 0x00, 0x50, 0x0b, 0xff, 0x2f, 0xa0, 0x53, - 0xc4, 0xee, 0xfa, 0x74, 0xdd, 0x0e, 0x0a, 0xb5, 0xf2, 0x48, 0x3a, 0x78, 0x26, 0x97, 0x61, 0x1a, - 0xbe, 0x1d, 0xa4, 0x1b, 0xca, 0x69, 0x91, 0x2a, 0x17, 0x5e, 0x4e, 0xab, 0xf9, 0x2a, 0x25, 0x01, - 0xca, 0x1f, 0xb3, 0x4f, 0x08, 0x4b, 0x29, 0xf2, 0xfa, 0xb3, 0x19, 0x54, 0x58, 0x77, 0x0d, 0xcb, - 0xf7, 0x84, 0x8f, 0xfe, 0xcc, 0xf4, 0x8e, 0x6b, 0x38, 0x6c, 0x7c, 0x4c, 0xc3, 0x2d, 0xde, 0x3b, - 0x46, 0xab, 0x4b, 0xbd, 0xd9, 0x77, 0xd9, 0xdb, 0xfd, 0x43, 0xaf, 0xf2, 0xa9, 0x4d, 0x70, 0x4e, - 0x4d, 0x37, 0xed, 0xf6, 0xcc, 0xa6, 0x6b, 0x6c, 0x5b, 0xbc, 0x00, 0xb8, 0xd1, 0x9a, 0xf1, 0x69, - 0x8b, 0x3a, 0xb6, 0xeb, 0xcf, 0x18, 0x8e, 0x35, 0xe3, 0xef, 0x3a, 0xd4, 0x9b, 0x09, 0x25, 0xf1, - 0x16, 0xd8, 0x10, 0xf0, 0xe1, 0x3f, 0x79, 0x08, 0x70, 0x1c, 0x5e, 0x46, 0x48, 0xbc, 0x6a, 0xd5, - 0x71, 0x84, 0xc3, 0x5f, 0xf2, 0x66, 0x06, 0x18, 0x3e, 0xb0, 0x43, 0x85, 0x19, 0x8e, 0x5c, 0xfe, - 0x45, 0x92, 0xc0, 0x46, 0xc1, 0xba, 0x78, 0xa2, 0x40, 0x4d, 0x63, 0x91, 0xc6, 0x83, 0x87, 0x4d, - 0x51, 0x52, 0x9c, 0x0d, 0x6f, 0xa0, 0x71, 0x21, 0x37, 0x8c, 0xaa, 0x3b, 0xa5, 0x2e, 0x1a, 0x31, - 0x34, 0x1f, 0xb4, 0xe1, 0x33, 0x9a, 0x02, 0x2c, 0xb7, 0x11, 0xe3, 0xc0, 0xb3, 0x51, 0xe6, 0x0d, - 0xd4, 0x9a, 0xd1, 0xc6, 0x61, 0xc4, 0x42, 0x29, 0xf4, 0x80, 0x9f, 0x97, 0xa8, 0x91, 0x8b, 0xa1, - 0x28, 0x2c, 0xb2, 0x0c, 0x3e, 0xea, 0x27, 0x52, 0x64, 0xc4, 0xc7, 0xbc, 0xca, 0x82, 0x6b, 0x68, - 0x2c, 0xf4, 0x37, 0xdc, 0x66, 0x2b, 0xdb, 0x64, 0x54, 0x95, 0x25, 0x16, 0xb0, 0x27, 0x0b, 0x51, - 0x78, 0xf0, 0x4b, 0xa8, 0xc4, 0x3d, 0xf6, 0x75, 0x7e, 0xc5, 0x10, 0x5c, 0xb6, 0x02, 0xac, 0x61, - 0xc9, 0x3d, 0x16, 0x12, 0xe2, 0xd7, 0x51, 0xb9, 0xfa, 0xee, 0x1a, 0x5b, 0x67, 0xaa, 0x64, 0xd9, - 0xd3, 0x4e, 0x47, 0xe1, 0xca, 0x90, 0x90, 0x6b, 0xb7, 0x68, 0xc3, 0x70, 0x95, 0xc5, 0x43, 0xa6, - 0xc7, 0xf3, 0x28, 0xf6, 0x85, 0x79, 0x6d, 0x2a, 0xfa, 0x76, 0xa0, 0x01, 0x98, 0x86, 0xa8, 0x48, - 0xa4, 0x64, 0x1d, 0xab, 0x4c, 0x6c, 0xd4, 0xcc, 0x59, 0x9e, 0xd1, 0x6a, 0xd9, 0x3b, 0x84, 0x5a, - 0x9e, 0xd7, 0xa5, 0x70, 0x3f, 0x51, 0xe2, 0xa3, 0xc6, 0x14, 0xa8, 0x86, 0xcb, 0x71, 0x4a, 0x4e, - 0xb8, 0xca, 0xa6, 0xff, 0x57, 0x46, 0x1e, 0xd0, 0x61, 0x95, 0xd9, 0x4c, 0x6a, 0x95, 0xd9, 0x2b, - 0x68, 0x44, 0x6c, 0x03, 0x61, 0x8c, 0x24, 0x64, 0x75, 0x04, 0x91, 0x00, 0x96, 0x49, 0x22, 0x02, - 0x88, 0xa8, 0x8f, 0x0a, 0x43, 0xe4, 0xa4, 0x88, 0xfa, 0xa8, 0x30, 0x84, 0x52, 0x16, 0xe2, 0x1a, - 0x2a, 0x8b, 0xc1, 0x2c, 0xe5, 0x8c, 0xc3, 0x6d, 0x7e, 0x90, 0xdf, 0xcc, 0x83, 0x05, 0x24, 0x22, - 0xfc, 0x2a, 0x42, 0x91, 0x7e, 0xc5, 0xa6, 0x05, 0x73, 0x4f, 0xee, 0x0e, 0x79, 0xee, 0x45, 0xd4, - 0xfa, 0xdf, 0x67, 0x12, 0x53, 0x86, 0x3d, 0x83, 0x88, 0x3b, 0x91, 0xf4, 0x00, 0xcf, 0x20, 0xa2, - 0x54, 0xc4, 0x33, 0x48, 0x44, 0xf8, 0x12, 0x2a, 0xc5, 0xf2, 0xe9, 0xe1, 0x9e, 0x3d, 0x4c, 0xa6, - 0x0f, 0xb1, 0xf8, 0x1a, 0x2a, 0xb1, 0x01, 0xcc, 0x44, 0x08, 0x85, 0x40, 0xa5, 0x9e, 0xae, 0x80, - 0xc9, 0x23, 0x2e, 0xa0, 0x63, 0x3c, 0x4a, 0x98, 0xac, 0xe0, 0x49, 0x99, 0xae, 0x51, 0x58, 0xec, - 0xef, 0x65, 0x07, 0x1c, 0x18, 0x1e, 0xdf, 0xe4, 0x84, 0x30, 0x51, 0x2c, 0x3f, 0x20, 0x51, 0xec, - 0x3f, 0xb2, 0x03, 0x4e, 0x43, 0x8f, 0x75, 0x42, 0x47, 0xa8, 0x0c, 0x35, 0xa1, 0x23, 0xca, 0xa5, - 0xb1, 0x4c, 0x22, 0x13, 0xc5, 0x52, 0xbf, 0x0a, 0xf7, 0x4d, 0xfd, 0xfa, 0xad, 0xdc, 0x5e, 0xa7, - 0xc5, 0x13, 0xdd, 0xef, 0x47, 0xf7, 0xd7, 0x50, 0x39, 0xd4, 0xac, 0x28, 0xe7, 0x32, 0x16, 0x86, - 0x43, 0x71, 0x30, 0xf0, 0x48, 0x44, 0xf8, 0x32, 0x7f, 0x56, 0xf8, 0x32, 0x76, 0x11, 0x18, 0xa0, - 0xe2, 0x09, 0x7b, 0xb6, 0x86, 0x67, 0xbd, 0x4f, 0x49, 0x88, 0xd6, 0xff, 0x34, 0x9b, 0x7a, 0xe4, - 0x3e, 0xe9, 0xa3, 0x7d, 0xf4, 0x51, 0x8a, 0x12, 0xb9, 0xb3, 0xe0, 0x44, 0x89, 0xfb, 0x50, 0xe2, - 0x8f, 0xb3, 0xa9, 0xae, 0x95, 0x13, 0x25, 0xee, 0x67, 0xb5, 0xb8, 0x82, 0x46, 0x88, 0xbd, 0xe3, - 0xd5, 0xec, 0x6e, 0xc7, 0x17, 0x6b, 0x05, 0x2c, 0xd4, 0xae, 0xbd, 0xe3, 0x35, 0x9a, 0x0c, 0x4a, - 0x22, 0x02, 0xfd, 0x27, 0xd9, 0x3d, 0x9c, 0x4f, 0x27, 0x8a, 0xff, 0x10, 0xb7, 0xc8, 0x8f, 0x3d, - 0x8f, 0xca, 0xd2, 0x07, 0x51, 0xf1, 0x28, 0x2a, 0xad, 0xcc, 0xae, 0xcd, 0x93, 0x77, 0xe6, 0xe7, - 0x26, 0x86, 0x30, 0x42, 0x85, 0xb9, 0xf9, 0xe5, 0xfa, 0xfc, 0xdc, 0x44, 0x66, 0x76, 0xe2, 0x83, - 0x7f, 0x7a, 0x7a, 0xe8, 0x83, 0x1f, 0x3e, 0x9d, 0xf9, 0xde, 0x0f, 0x9f, 0xce, 0xfc, 0xe3, 0x0f, - 0x9f, 0xce, 0x6c, 0x14, 0xc0, 0x08, 0x7d, 0xe9, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x99, - 0x5b, 0x81, 0x5d, 0x96, 0x00, 0x00, + // 6991 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5d, 0x6c, 0x24, 0xd9, + 0x55, 0xb0, 0xbb, 0xdb, 0xfd, 0xe3, 0xdb, 0xfe, 0xbd, 0xe3, 0xd9, 0xa9, 0x99, 0x9d, 0x9d, 0x9e, + 0xad, 0x49, 0x66, 0x67, 0xb2, 0xb3, 0x76, 0xc6, 0x33, 0xbb, 0x93, 0x6c, 0x76, 0xb3, 0xd3, 0x6e, + 0xdb, 0xeb, 0xce, 0x7a, 0x6c, 0xef, 0xb5, 0x67, 0x37, 0x52, 0x94, 0x6d, 0x95, 0xbb, 0xee, 0xd8, + 0x95, 0xe9, 0xae, 0xaa, 0x54, 0x55, 0xdb, 0xe3, 0x7d, 0xfa, 0x7e, 0x10, 0x44, 0x51, 0x40, 0x28, + 0x3c, 0x20, 0xc4, 0x43, 0x50, 0x00, 0x09, 0xa4, 0x88, 0x00, 0x42, 0x11, 0x12, 0xbc, 0x40, 0x00, + 0x6d, 0x84, 0x12, 0x02, 0x41, 0x41, 0xe2, 0xa1, 0x03, 0x41, 0xbc, 0xb4, 0x40, 0x8a, 0x04, 0x12, + 0x81, 0x27, 0x74, 0xcf, 0xbd, 0x55, 0x75, 0xeb, 0xa7, 0x3d, 0xe3, 0xb1, 0x17, 0xe3, 0x19, 0x3f, + 0xd9, 0x7d, 0xfe, 0x6e, 0xd5, 0xb9, 0x7f, 0xe7, 0x9e, 0x7b, 0xce, 0x29, 0x34, 0x4c, 0xb7, 0xa9, + 0xe9, 0xb9, 0x53, 0xb6, 0x63, 0x79, 0x16, 0x2e, 0xf0, 0x5f, 0xe7, 0x26, 0x37, 0xad, 0x4d, 0x0b, + 0x40, 0xd3, 0xec, 0x3f, 0x8e, 0x3d, 0x57, 0xd9, 0xb4, 0xac, 0xcd, 0x16, 0x9d, 0x86, 0x5f, 0x1b, + 0x9d, 0x7b, 0xd3, 0x9e, 0xd1, 0xa6, 0xae, 0xa7, 0xb5, 0x6d, 0x41, 0x70, 0x3e, 0x4e, 0xe0, 0x7a, + 0x4e, 0xa7, 0xe9, 0x09, 0x6c, 0x6d, 0xd3, 0xf0, 0xb6, 0x3a, 0x1b, 0x53, 0x4d, 0xab, 0x3d, 0xbd, + 0xe9, 0x68, 0xdb, 0x86, 0xa7, 0x79, 0x86, 0x65, 0x6a, 0xad, 0x69, 0x8f, 0xb6, 0xa8, 0x6d, 0x39, + 0xde, 0xb4, 0x66, 0x1b, 0xd3, 0xde, 0xae, 0x4d, 0xdd, 0xe9, 0x1d, 0x47, 0xb3, 0x6d, 0xea, 0x84, + 0xff, 0x70, 0x21, 0xea, 0x57, 0xb3, 0xa8, 0x74, 0x87, 0x7a, 0x9a, 0xae, 0x79, 0x1a, 0x3e, 0x8f, + 0xf2, 0x75, 0x53, 0xa7, 0x0f, 0x94, 0xcc, 0xc5, 0xcc, 0x95, 0xdc, 0x6c, 0xa1, 0xd7, 0xad, 0x64, + 0xa9, 0x41, 0x38, 0x10, 0x3f, 0x87, 0x06, 0xd7, 0x77, 0x6d, 0xaa, 0x64, 0x2f, 0x66, 0xae, 0x0c, + 0xcd, 0x0e, 0xf5, 0xba, 0x95, 0x3c, 0xbc, 0x1e, 0x01, 0x30, 0x7e, 0x1e, 0x65, 0xeb, 0x73, 0x4a, + 0x0e, 0x90, 0x13, 0xbd, 0x6e, 0x65, 0xa4, 0x63, 0xe8, 0xd7, 0xac, 0xb6, 0xe1, 0xd1, 0xb6, 0xed, + 0xed, 0x92, 0x6c, 0x7d, 0x0e, 0x5f, 0x46, 0x83, 0x35, 0x4b, 0xa7, 0xca, 0x20, 0x10, 0xe1, 0x5e, + 0xb7, 0x32, 0xda, 0xb4, 0x74, 0x2a, 0x51, 0x01, 0x1e, 0xdf, 0x46, 0x83, 0xeb, 0x46, 0x9b, 0x2a, + 0xf9, 0x8b, 0x99, 0x2b, 0xe5, 0x99, 0x73, 0x53, 0x5c, 0x0d, 0x53, 0xbe, 0x1a, 0xa6, 0xd6, 0x7d, + 0x3d, 0xcd, 0x8e, 0x7f, 0xd0, 0xad, 0x0c, 0xf4, 0xba, 0x95, 0x41, 0xa6, 0xba, 0x5f, 0xfc, 0x51, + 0x25, 0x43, 0x80, 0x13, 0xbf, 0x86, 0xca, 0xb5, 0x56, 0xc7, 0xf5, 0xa8, 0xb3, 0xac, 0xb5, 0xa9, + 0x52, 0x80, 0x06, 0xcf, 0xf5, 0xba, 0x95, 0x67, 0x9a, 0x1c, 0xdc, 0x30, 0xb5, 0xb6, 0xdc, 0xb0, + 0x4c, 0xae, 0x7e, 0x01, 0x8d, 0xad, 0x51, 0xd7, 0x35, 0x2c, 0x33, 0x50, 0xcd, 0x47, 0xd1, 0x90, + 0x00, 0xd5, 0xe7, 0x40, 0x3d, 0x43, 0xb3, 0xc5, 0x5e, 0xb7, 0x92, 0x73, 0x0d, 0x9d, 0x84, 0x18, + 0xfc, 0x71, 0x54, 0x7c, 0xd7, 0xf0, 0xb6, 0xee, 0x2c, 0x54, 0x85, 0x9a, 0x9e, 0xe9, 0x75, 0x2b, + 0x78, 0xc7, 0xf0, 0xb6, 0x1a, 0xed, 0x7b, 0x9a, 0xd4, 0x9e, 0x4f, 0xa6, 0xfe, 0x46, 0x16, 0x0d, + 0xdf, 0x75, 0xa9, 0x13, 0xb4, 0x74, 0x19, 0x0d, 0xb2, 0xdf, 0xa2, 0x11, 0x50, 0x52, 0xc7, 0xa5, + 0x8e, 0xac, 0x24, 0x86, 0xc7, 0x57, 0x51, 0x7e, 0xc9, 0xda, 0x34, 0x4c, 0xd1, 0xd0, 0xa9, 0x5e, + 0xb7, 0x32, 0xd6, 0x62, 0x00, 0x89, 0x92, 0x53, 0xe0, 0x4f, 0xa3, 0xe1, 0x7a, 0x9b, 0x75, 0xba, + 0x65, 0x6a, 0x9e, 0xe5, 0x88, 0x4e, 0x02, 0x75, 0x18, 0x12, 0x5c, 0x62, 0x8c, 0xd0, 0xe3, 0x57, + 0x11, 0xaa, 0xbe, 0xbb, 0x46, 0xac, 0x16, 0xad, 0x92, 0x65, 0xd1, 0x7b, 0xc0, 0xad, 0xed, 0xb8, + 0x0d, 0xc7, 0x6a, 0xd1, 0x86, 0xe6, 0xc8, 0xcd, 0x4a, 0xd4, 0x78, 0x1e, 0x8d, 0x56, 0x9b, 0x4d, + 0xea, 0xba, 0x84, 0x7e, 0xb1, 0x43, 0x5d, 0xcf, 0x55, 0xf2, 0x17, 0x73, 0x57, 0x86, 0x66, 0x9f, + 0xeb, 0x75, 0x2b, 0x67, 0x35, 0xc0, 0x34, 0x1c, 0x81, 0x92, 0x44, 0xc4, 0x98, 0xd4, 0xdf, 0xcd, + 0xa1, 0xd1, 0x35, 0xea, 0x6c, 0x4b, 0x8a, 0xaa, 0xb2, 0x5e, 0x62, 0x10, 0xd6, 0x67, 0xae, 0xad, + 0x35, 0xa9, 0xd0, 0xd9, 0x99, 0x5e, 0xb7, 0x72, 0xca, 0xf4, 0x81, 0x92, 0xd0, 0x38, 0x3d, 0xbe, + 0x8a, 0x4a, 0x1c, 0x54, 0x9f, 0x13, 0x6a, 0x1c, 0xe9, 0x75, 0x2b, 0x43, 0x2e, 0xc0, 0x1a, 0x86, + 0x4e, 0x02, 0x34, 0x7b, 0x0f, 0xfe, 0xff, 0xa2, 0xe5, 0x7a, 0x4c, 0xb8, 0xd0, 0x22, 0xbc, 0x87, + 0x60, 0xd8, 0x12, 0x28, 0xf9, 0x3d, 0xa2, 0x4c, 0xf8, 0x93, 0x08, 0x71, 0x48, 0x55, 0xd7, 0x1d, + 0xa1, 0xca, 0xb3, 0xbd, 0x6e, 0xe5, 0xb4, 0x10, 0xa1, 0xe9, 0xba, 0xdc, 0x0f, 0x12, 0x31, 0x6e, + 0xa3, 0x61, 0xfe, 0x6b, 0x49, 0xdb, 0xa0, 0x2d, 0xae, 0xc7, 0xf2, 0xcc, 0x95, 0x29, 0xb1, 0xe2, + 0x44, 0xb5, 0x33, 0x25, 0x93, 0xce, 0x9b, 0x9e, 0xb3, 0x3b, 0x5b, 0x11, 0x73, 0xe5, 0x8c, 0x68, + 0xaa, 0x05, 0x38, 0xb9, 0xd3, 0x65, 0x9e, 0x73, 0x6f, 0xa0, 0x89, 0x84, 0x0c, 0x3c, 0x8e, 0x72, + 0xf7, 0xe9, 0x2e, 0xd7, 0x33, 0x61, 0xff, 0xe2, 0x49, 0x94, 0xdf, 0xd6, 0x5a, 0x1d, 0xb1, 0x2c, + 0x10, 0xfe, 0xe3, 0xd5, 0xec, 0x27, 0x32, 0xea, 0x1f, 0x66, 0x10, 0xae, 0x59, 0xa6, 0x49, 0x9b, + 0x9e, 0x3c, 0x93, 0x5e, 0x41, 0x43, 0x4b, 0x56, 0x53, 0x6b, 0x81, 0x02, 0x78, 0x87, 0x29, 0xbd, + 0x6e, 0x65, 0x92, 0xbd, 0xf9, 0x54, 0x8b, 0x61, 0xa4, 0x47, 0x0a, 0x49, 0x99, 0xe6, 0x08, 0x6d, + 0x5b, 0x1e, 0x05, 0xc6, 0x6c, 0xa8, 0x39, 0x60, 0x74, 0x00, 0x25, 0x6b, 0x2e, 0x24, 0xc6, 0xd3, + 0xa8, 0xb4, 0xca, 0xd6, 0x8e, 0xa6, 0xd5, 0x12, 0xbd, 0x06, 0xb3, 0x05, 0xd6, 0x13, 0x89, 0x25, + 0x20, 0x52, 0xff, 0x5f, 0x16, 0x9d, 0x7d, 0xab, 0xb3, 0x41, 0x1d, 0x93, 0x7a, 0xd4, 0x15, 0x4b, + 0x43, 0xf0, 0x06, 0xcb, 0x68, 0x22, 0x81, 0x14, 0x6f, 0x72, 0xb1, 0xd7, 0xad, 0x9c, 0xbf, 0x1f, + 0x20, 0x1b, 0x62, 0xb5, 0x91, 0x1a, 0x49, 0xb2, 0xe2, 0x45, 0x34, 0x16, 0x02, 0xd9, 0xdc, 0x76, + 0x95, 0x2c, 0xcc, 0x91, 0x0b, 0xbd, 0x6e, 0xe5, 0x9c, 0x24, 0x8d, 0xad, 0x03, 0x72, 0x87, 0xc5, + 0xd9, 0xf0, 0x5b, 0x68, 0x3c, 0x04, 0xbd, 0xe9, 0x58, 0x1d, 0xdb, 0x55, 0x72, 0x20, 0xaa, 0xd2, + 0xeb, 0x56, 0x9e, 0x95, 0x44, 0x6d, 0x02, 0x52, 0x92, 0x95, 0x60, 0x54, 0xff, 0x39, 0x87, 0x4e, + 0x87, 0xc0, 0x55, 0x4b, 0x0f, 0x14, 0xb0, 0x22, 0x2b, 0x60, 0xd5, 0xd2, 0x61, 0x8d, 0xe5, 0x0a, + 0x78, 0xbe, 0xd7, 0xad, 0x3c, 0x27, 0xb5, 0x63, 0x5b, 0x7a, 0x7c, 0xa9, 0x4d, 0xf2, 0xe2, 0xf7, + 0xd0, 0x33, 0x09, 0x20, 0x9f, 0xd1, 0xbc, 0x9f, 0x2f, 0xf7, 0xba, 0x15, 0x35, 0x45, 0x6a, 0x7c, + 0x82, 0xf7, 0x91, 0x82, 0x35, 0x74, 0x46, 0x52, 0xbb, 0x65, 0x7a, 0x9a, 0x61, 0x8a, 0xad, 0x81, + 0x8f, 0x87, 0x17, 0x7a, 0xdd, 0xca, 0x25, 0xb9, 0xdf, 0x7c, 0x9a, 0xf8, 0xc3, 0xf7, 0x93, 0x83, + 0x75, 0xa4, 0xa4, 0xa0, 0xea, 0x6d, 0x6d, 0xd3, 0xdf, 0xef, 0xae, 0xf4, 0xba, 0x95, 0x8f, 0xa4, + 0xb6, 0x61, 0x30, 0x2a, 0xa9, 0x91, 0xbe, 0x92, 0x30, 0x41, 0x38, 0xc4, 0x2d, 0x5b, 0x3a, 0x85, + 0x77, 0xc8, 0x83, 0x7c, 0xb5, 0xd7, 0xad, 0x5c, 0x90, 0xe4, 0x9b, 0x96, 0x4e, 0xe3, 0x8f, 0x9f, + 0xc2, 0xad, 0xfe, 0x28, 0xcf, 0x16, 0x16, 0xd8, 0xc1, 0xd6, 0x3c, 0xcd, 0xf1, 0xf0, 0xab, 0xa1, + 0x49, 0x00, 0xbd, 0x5a, 0x9e, 0x19, 0xf7, 0x17, 0x19, 0x1f, 0x3e, 0x3b, 0xcc, 0x16, 0x93, 0xef, + 0x77, 0x2b, 0x99, 0x5e, 0xb7, 0x32, 0x40, 0x4a, 0xd2, 0xec, 0xe6, 0xbb, 0x57, 0x16, 0xf8, 0x26, + 0x7d, 0x3e, 0x79, 0x87, 0x8b, 0xf1, 0xf2, 0xdd, 0xec, 0x0d, 0x54, 0x14, 0xcf, 0x00, 0x3d, 0x52, + 0x9e, 0x39, 0x13, 0xae, 0x6b, 0x91, 0x9d, 0x38, 0xc6, 0xed, 0x73, 0xe1, 0xd7, 0x50, 0x81, 0x2f, + 0x57, 0xa0, 0xed, 0xf2, 0xcc, 0x33, 0xe9, 0xeb, 0x62, 0x8c, 0x5d, 0xf0, 0xe0, 0x45, 0x84, 0xc2, + 0xa5, 0x2a, 0xb0, 0x3b, 0x84, 0x84, 0xe4, 0x22, 0x16, 0x93, 0x22, 0xf1, 0xe2, 0x57, 0xd0, 0xf0, + 0x3a, 0x75, 0xda, 0x86, 0xa9, 0xb5, 0xd6, 0x8c, 0xf7, 0x7d, 0xd3, 0x03, 0xb6, 0x71, 0xd7, 0x78, + 0x5f, 0xee, 0x8b, 0x08, 0x1d, 0xfe, 0x7c, 0xda, 0xa2, 0x52, 0x84, 0x07, 0x79, 0xde, 0x7f, 0x90, + 0xbe, 0x4b, 0x52, 0xec, 0x79, 0x52, 0xd6, 0x98, 0xb7, 0xd1, 0x48, 0x64, 0x6e, 0x28, 0x25, 0x10, + 0xfd, 0x5c, 0x52, 0xb4, 0x34, 0xd1, 0x63, 0x62, 0xa3, 0x12, 0xd8, 0x8e, 0x58, 0x37, 0x0d, 0xcf, + 0xd0, 0x5a, 0x35, 0xab, 0xdd, 0xd6, 0x4c, 0x5d, 0x19, 0x0a, 0x77, 0x76, 0x83, 0x63, 0x1a, 0x4d, + 0x8e, 0x92, 0x77, 0xc4, 0x28, 0x13, 0x5b, 0xb3, 0x44, 0x1f, 0x12, 0xda, 0xb4, 0x1c, 0xdd, 0x30, + 0x37, 0x15, 0x04, 0x4a, 0x83, 0x35, 0xcb, 0xe5, 0xb8, 0x86, 0xe3, 0x23, 0xe5, 0x35, 0x2b, 0xce, + 0xf8, 0x99, 0xc1, 0x52, 0x79, 0x7c, 0x38, 0x61, 0x3c, 0xfc, 0x76, 0x0e, 0x95, 0x05, 0xe9, 0x67, + 0x2c, 0xc3, 0x3c, 0x19, 0xe0, 0x07, 0x19, 0xe0, 0xa9, 0x03, 0xb5, 0x70, 0x58, 0x03, 0x55, 0xfd, + 0x4a, 0x36, 0x58, 0x8d, 0x56, 0x1d, 0xc3, 0x3c, 0xd8, 0x6a, 0x74, 0x19, 0xa1, 0xda, 0x56, 0xc7, + 0xbc, 0xcf, 0x4f, 0x35, 0xd9, 0xf0, 0x54, 0xd3, 0x34, 0x88, 0x84, 0x61, 0x47, 0x9b, 0x39, 0x26, + 0x9f, 0xf5, 0xcc, 0xf0, 0xec, 0xd0, 0x07, 0x5c, 0x52, 0xe6, 0x25, 0x02, 0x60, 0x5c, 0x41, 0xf9, + 0xd9, 0x5d, 0x8f, 0xba, 0xa0, 0xf9, 0x1c, 0x3f, 0xfa, 0x6c, 0x30, 0x00, 0xe1, 0x70, 0x7c, 0x13, + 0x4d, 0xcc, 0xd1, 0x96, 0xb6, 0x7b, 0xc7, 0x68, 0xb5, 0x0c, 0x97, 0x36, 0x2d, 0x53, 0x77, 0x41, + 0xc9, 0xa2, 0xb9, 0xb6, 0x4b, 0x92, 0x04, 0x58, 0x45, 0x85, 0x95, 0x7b, 0xf7, 0x5c, 0xea, 0x81, + 0xfa, 0x72, 0xb3, 0xa8, 0xd7, 0xad, 0x14, 0x2c, 0x80, 0x10, 0x81, 0x51, 0xbf, 0x99, 0x41, 0xe3, + 0x73, 0xd4, 0xbd, 0xef, 0x59, 0x76, 0x30, 0xca, 0x0f, 0xa4, 0x92, 0xab, 0xa8, 0x78, 0x87, 0xba, + 0x2e, 0xdb, 0x96, 0xb2, 0xf0, 0xb6, 0x63, 0xe2, 0x6d, 0x8b, 0x6d, 0x0e, 0x26, 0x3e, 0x3e, 0xfd, + 0xad, 0x72, 0x0f, 0x79, 0x2b, 0xf5, 0x27, 0x59, 0x74, 0x46, 0x3c, 0x71, 0xad, 0x65, 0xd8, 0x1b, + 0x96, 0xe6, 0xe8, 0x84, 0x36, 0xa9, 0xb1, 0x4d, 0x8f, 0xe7, 0xc4, 0x8b, 0x4e, 0x9d, 0xc1, 0x03, + 0x4c, 0x9d, 0x19, 0x54, 0x16, 0x9a, 0x01, 0x1b, 0x96, 0x6f, 0xdb, 0xe3, 0xbd, 0x6e, 0x65, 0x58, + 0xe7, 0x60, 0x30, 0xff, 0x89, 0x4c, 0xc4, 0x06, 0xc9, 0x12, 0x35, 0x37, 0xbd, 0x2d, 0x18, 0x24, + 0x79, 0x3e, 0x48, 0x5a, 0x00, 0x21, 0x02, 0xa3, 0xfe, 0x6b, 0x16, 0x4d, 0xc6, 0x55, 0xbe, 0x46, + 0x4d, 0xfd, 0x44, 0xdf, 0x1f, 0x8e, 0xbe, 0xff, 0x26, 0x8b, 0x46, 0x82, 0xad, 0xe7, 0x0b, 0xb4, + 0x79, 0x34, 0x26, 0x53, 0xb8, 0x21, 0xe4, 0x0e, 0xbc, 0x21, 0x1c, 0x44, 0xcb, 0x2a, 0x2a, 0x10, + 0xaa, 0xb9, 0x62, 0x5b, 0x19, 0xe2, 0x1a, 0x73, 0x00, 0x42, 0x04, 0x06, 0x3f, 0x8f, 0x8a, 0x77, + 0xb4, 0x07, 0x46, 0xbb, 0xd3, 0x16, 0x6b, 0x1d, 0x38, 0x4f, 0xda, 0xda, 0x03, 0xe2, 0xc3, 0xd5, + 0xbf, 0xcd, 0xb0, 0x13, 0x36, 0x28, 0x55, 0x08, 0x3f, 0x90, 0x56, 0x43, 0xed, 0x64, 0x0f, 0xac, + 0x9d, 0xdc, 0xe3, 0x6b, 0x47, 0xfd, 0xe6, 0x20, 0x53, 0x0f, 0x33, 0xfd, 0x9e, 0xf6, 0xd9, 0x18, + 0xf6, 0x48, 0xfe, 0x31, 0x7a, 0xe4, 0xa9, 0xb1, 0xab, 0xd5, 0xff, 0x28, 0x22, 0x24, 0xb4, 0x3f, + 0x7f, 0xb2, 0x86, 0x1f, 0x6c, 0xd4, 0xcc, 0xa1, 0x89, 0x79, 0x73, 0x4b, 0x33, 0x9b, 0x54, 0x0f, + 0x4f, 0x17, 0x6c, 0xe8, 0x94, 0xb8, 0x67, 0x96, 0x0a, 0x64, 0x78, 0xbc, 0x20, 0x49, 0x06, 0x7c, + 0x1d, 0x95, 0xeb, 0xa6, 0x47, 0x1d, 0xad, 0xe9, 0x19, 0xdb, 0x14, 0x46, 0x4f, 0x69, 0x76, 0xac, + 0xd7, 0xad, 0x94, 0x8d, 0x10, 0x4c, 0x64, 0x1a, 0x7c, 0x13, 0x0d, 0xaf, 0x6a, 0x8e, 0x67, 0x34, + 0x0d, 0x5b, 0x33, 0x3d, 0x57, 0x29, 0xc1, 0xd1, 0x08, 0xf6, 0x1e, 0x5b, 0x82, 0x93, 0x08, 0x15, + 0xfe, 0x3c, 0x1a, 0x82, 0x23, 0x38, 0x78, 0xbf, 0x87, 0x1e, 0xea, 0xfd, 0xbe, 0x14, 0x7a, 0xf4, + 0xf8, 0x21, 0xc9, 0x65, 0xcc, 0xe1, 0x54, 0x00, 0x87, 0x78, 0x28, 0x11, 0x7f, 0x16, 0x15, 0xe7, + 0x4d, 0x1d, 0x84, 0xa3, 0x87, 0x0a, 0x57, 0x85, 0xf0, 0x67, 0x42, 0xe1, 0x96, 0x1d, 0x93, 0xed, + 0x8b, 0x4b, 0x9f, 0x65, 0xe5, 0x0f, 0x6f, 0x96, 0x0d, 0x7f, 0x08, 0xa7, 0xd7, 0x91, 0xc3, 0x3a, + 0xbd, 0x8e, 0x3e, 0xe6, 0xe9, 0x55, 0x7d, 0x1f, 0x95, 0x67, 0x57, 0x17, 0x82, 0xd9, 0x7b, 0x16, + 0xe5, 0x56, 0xc5, 0x6d, 0xc3, 0x20, 0xdf, 0x30, 0x6d, 0x43, 0x27, 0x0c, 0x86, 0xaf, 0xa2, 0x52, + 0x0d, 0x5c, 0x78, 0xc2, 0x71, 0x3d, 0xc8, 0x1d, 0xd7, 0x4d, 0x80, 0x81, 0xe3, 0xda, 0x47, 0xe3, + 0x8f, 0xa2, 0xe2, 0xaa, 0x63, 0x6d, 0x3a, 0x5a, 0x5b, 0xf8, 0xba, 0xca, 0xcc, 0xd8, 0xb7, 0x39, + 0x88, 0xf8, 0x38, 0xf5, 0x97, 0x32, 0xa8, 0xb0, 0xe6, 0x69, 0x5e, 0xc7, 0x65, 0x1c, 0x6b, 0x1d, + 0x38, 0x41, 0x43, 0xdb, 0x25, 0xce, 0xe1, 0x72, 0x10, 0xf1, 0x71, 0xf8, 0x2a, 0xca, 0xcf, 0x3b, + 0x8e, 0xe5, 0xc8, 0x17, 0x10, 0x94, 0x01, 0xe4, 0x0b, 0x08, 0xa0, 0xc0, 0xb7, 0x50, 0x99, 0xaf, + 0x39, 0xfc, 0xe0, 0xc1, 0x9f, 0xe3, 0x74, 0xaf, 0x5b, 0x99, 0x10, 0x87, 0x0e, 0xf9, 0x26, 0x46, + 0xa2, 0x54, 0xbf, 0x9d, 0x93, 0x8c, 0x02, 0xae, 0xf1, 0xa7, 0xf0, 0xf0, 0x7e, 0x03, 0xe5, 0x66, + 0x57, 0x17, 0xc4, 0x02, 0x78, 0xca, 0x67, 0x95, 0x86, 0x4a, 0x8c, 0x8f, 0x51, 0xe3, 0xf3, 0x68, + 0x70, 0x95, 0x0d, 0x9f, 0x02, 0x0c, 0x8f, 0x52, 0xaf, 0x5b, 0x19, 0xb4, 0xd9, 0xf8, 0x01, 0x28, + 0x60, 0x35, 0x6f, 0x0b, 0xd6, 0xb2, 0x21, 0x81, 0xd5, 0xbc, 0x2d, 0x02, 0x50, 0x86, 0xad, 0x3a, + 0x9b, 0xdb, 0x62, 0xd5, 0x02, 0xac, 0xe6, 0x6c, 0x6e, 0x13, 0x80, 0xe2, 0x69, 0x84, 0x08, 0xf5, + 0x3a, 0x8e, 0x09, 0x97, 0x79, 0x43, 0x60, 0x26, 0xc3, 0x6a, 0xe8, 0x00, 0xb4, 0xd1, 0xb4, 0x74, + 0x4a, 0x24, 0x12, 0xf5, 0x37, 0x43, 0xff, 0xcb, 0x9c, 0xe1, 0xde, 0x3f, 0xe9, 0xc2, 0x7d, 0x74, + 0xa1, 0x26, 0x4e, 0x22, 0xc9, 0x4e, 0xaa, 0xa0, 0xfc, 0x42, 0x4b, 0xdb, 0x74, 0xa1, 0x0f, 0xf3, + 0xdc, 0x2b, 0x71, 0x8f, 0x01, 0x08, 0x87, 0xc7, 0xfa, 0xa9, 0xf4, 0xf0, 0x7e, 0xfa, 0xe5, 0x7c, + 0x30, 0xdb, 0x96, 0xa9, 0xb7, 0x63, 0x39, 0x27, 0x5d, 0xf5, 0xa8, 0x5d, 0x75, 0x19, 0x15, 0xd7, + 0x9c, 0x26, 0x1c, 0x33, 0x79, 0x6f, 0x0d, 0xf7, 0xba, 0x95, 0x92, 0xeb, 0x34, 0xf9, 0x11, 0xd3, + 0x47, 0x32, 0xba, 0x39, 0xd7, 0x03, 0xba, 0x62, 0x48, 0xa7, 0xbb, 0x9e, 0xa0, 0x13, 0x48, 0x41, + 0xb7, 0x6a, 0x39, 0x9e, 0xe8, 0xb8, 0x80, 0xce, 0xb6, 0x1c, 0x8f, 0xf8, 0x48, 0xfc, 0x22, 0x42, + 0xeb, 0xb5, 0xd5, 0x77, 0xa8, 0x03, 0xea, 0xe2, 0x73, 0x11, 0x96, 0xeb, 0x6d, 0x0e, 0x22, 0x12, + 0x1a, 0xaf, 0xa3, 0xa1, 0x15, 0x9b, 0x3a, 0x10, 0x26, 0x00, 0x16, 0xc0, 0xe8, 0xcc, 0x0b, 0x31, + 0xd5, 0x8a, 0x7e, 0x9f, 0x12, 0x7f, 0x03, 0x72, 0xbe, 0xbf, 0x58, 0xfe, 0x4f, 0x12, 0x0a, 0xc2, + 0xb7, 0x50, 0xa1, 0xca, 0xed, 0xbc, 0x32, 0x88, 0x0c, 0x54, 0x36, 0xcf, 0xfe, 0x70, 0x14, 0x3f, + 0x14, 0x6a, 0xf0, 0x3f, 0x11, 0xe4, 0xea, 0x55, 0x34, 0x1e, 0x6f, 0x06, 0x97, 0x51, 0xb1, 0xb6, + 0xb2, 0xbc, 0x3c, 0x5f, 0x5b, 0x1f, 0x1f, 0xc0, 0x25, 0x34, 0xb8, 0x36, 0xbf, 0x3c, 0x37, 0x9e, + 0x51, 0xbf, 0x21, 0xad, 0x20, 0x6c, 0x68, 0x9d, 0x78, 0x70, 0x0f, 0xe4, 0x16, 0x19, 0x07, 0xb7, + 0xe5, 0xba, 0xa3, 0x99, 0x6e, 0xdb, 0xf0, 0x3c, 0xaa, 0x8b, 0x5d, 0x02, 0xdc, 0x7a, 0xde, 0x03, + 0x92, 0xc0, 0xe3, 0x6b, 0x68, 0x04, 0x60, 0xc2, 0x93, 0xa7, 0xc3, 0xe8, 0x15, 0x0c, 0xce, 0x03, + 0x12, 0x45, 0xaa, 0x7f, 0x19, 0x3a, 0x71, 0x97, 0xa8, 0x76, 0x5c, 0x1d, 0x7f, 0xff, 0x4b, 0xfa, + 0x4b, 0xfd, 0xf5, 0x1c, 0x1a, 0x62, 0x6f, 0xc4, 0x83, 0x39, 0x8e, 0x42, 0x95, 0x37, 0x7d, 0xdb, + 0x50, 0x68, 0x72, 0x34, 0xd0, 0x04, 0x40, 0x13, 0x1a, 0xe0, 0x76, 0xe4, 0x35, 0x54, 0xb8, 0x43, + 0xbd, 0x2d, 0x4b, 0x17, 0x17, 0xa0, 0x93, 0xbd, 0x6e, 0x65, 0xbc, 0x0d, 0x10, 0xc9, 0xde, 0x13, + 0x34, 0xf8, 0x3e, 0xc2, 0x75, 0x9d, 0x9a, 0x9e, 0xe1, 0xed, 0x56, 0x3d, 0xcf, 0x31, 0x36, 0x3a, + 0x1e, 0x75, 0x85, 0xde, 0xce, 0x24, 0xce, 0x29, 0x6b, 0x10, 0x09, 0x05, 0x77, 0x9e, 0x93, 0x5a, + 0x40, 0x1e, 0x8a, 0xfd, 0xaf, 0x6e, 0xa5, 0xc0, 0x69, 0x48, 0x8a, 0x58, 0xfc, 0x36, 0x1a, 0xba, + 0xb3, 0x50, 0x9d, 0xa3, 0xdb, 0x46, 0x93, 0x8a, 0xcb, 0x8b, 0xb3, 0x81, 0x16, 0x7d, 0x44, 0xa0, + 0x12, 0x88, 0x4f, 0x68, 0xdf, 0xd3, 0x1a, 0x3a, 0xc0, 0xe5, 0xf8, 0x84, 0x80, 0x58, 0xfd, 0x97, + 0x0c, 0x1a, 0x27, 0xd4, 0xb5, 0x3a, 0x4e, 0xc8, 0x89, 0x2f, 0xa3, 0x41, 0xe9, 0x72, 0x1c, 0xbc, + 0x15, 0xb1, 0x1b, 0x59, 0xc0, 0xe3, 0x35, 0x54, 0x9c, 0x7f, 0x60, 0x1b, 0x0e, 0x75, 0x45, 0xdf, + 0xec, 0x75, 0x32, 0x7b, 0x4e, 0x9c, 0xcc, 0x26, 0x28, 0x67, 0x49, 0x1c, 0xca, 0x38, 0x18, 0xbf, + 0x82, 0x86, 0xee, 0xda, 0xba, 0xe6, 0x51, 0x7d, 0x76, 0x57, 0xd8, 0xdc, 0xf0, 0x26, 0x1d, 0x0e, + 0x6c, 0x6c, 0xec, 0xca, 0x6f, 0x12, 0x90, 0xe2, 0x4b, 0x28, 0xb7, 0xbe, 0xbe, 0x24, 0x3a, 0x0d, + 0x42, 0xb9, 0x3c, 0x4f, 0x0e, 0xca, 0x60, 0x58, 0xf5, 0xab, 0x59, 0x84, 0xd8, 0xd8, 0xa8, 0x39, + 0x54, 0xf3, 0x8e, 0x66, 0x82, 0xcf, 0xa2, 0x92, 0xaf, 0x70, 0x31, 0x2e, 0x15, 0x9f, 0x37, 0xde, + 0x11, 0xf1, 0xb6, 0x7d, 0x3c, 0x33, 0xa2, 0x88, 0xd5, 0x82, 0xab, 0x9d, 0x9c, 0x1f, 0xd5, 0xe6, + 0x30, 0x00, 0xe1, 0x70, 0xfc, 0x22, 0x1a, 0x12, 0x53, 0xd1, 0xf2, 0x3d, 0xc8, 0xfc, 0xa8, 0xe5, + 0x03, 0x49, 0x88, 0x57, 0xff, 0x2c, 0xc3, 0x95, 0x32, 0x47, 0x5b, 0xf4, 0xf8, 0x2a, 0x45, 0xfd, + 0x52, 0x06, 0x61, 0x26, 0x6c, 0x55, 0x73, 0xdd, 0x1d, 0xcb, 0xd1, 0x6b, 0x5b, 0x9a, 0xb9, 0x79, + 0x24, 0xaf, 0xa3, 0x7e, 0x39, 0x8f, 0x4e, 0x45, 0x6e, 0x73, 0x8f, 0xf9, 0x78, 0xbb, 0x1a, 0x1d, + 0x6f, 0x70, 0x68, 0x86, 0xf1, 0x26, 0x1f, 0x9a, 0xf9, 0xc8, 0xfb, 0x08, 0x1a, 0x12, 0xef, 0x5c, + 0x9f, 0x13, 0x23, 0x0f, 0xb6, 0x5b, 0x43, 0x27, 0x21, 0x02, 0xbf, 0x84, 0x86, 0xc5, 0x0f, 0xb6, + 0xea, 0xfa, 0x7e, 0x51, 0x18, 0xc7, 0x2e, 0x03, 0x90, 0x08, 0x1a, 0xbf, 0x8c, 0x86, 0xd8, 0xe0, + 0xdc, 0x84, 0x38, 0xc0, 0x62, 0x18, 0x2e, 0xa7, 0xfb, 0x40, 0x79, 0x49, 0x08, 0x28, 0xd9, 0x52, + 0x2e, 0x7c, 0xfc, 0xa5, 0x70, 0x29, 0xe7, 0x3e, 0x7e, 0x79, 0x29, 0x17, 0xde, 0xfe, 0xf7, 0x50, + 0xb9, 0x6a, 0x9a, 0x16, 0x8f, 0x47, 0x75, 0x85, 0x23, 0xab, 0xef, 0x1a, 0x7e, 0x09, 0x82, 0xb8, + 0x42, 0xfa, 0xd4, 0x45, 0x5c, 0x16, 0x88, 0x67, 0x58, 0x47, 0x6c, 0x1b, 0x74, 0x87, 0x3a, 0x22, + 0x54, 0x00, 0x9c, 0x79, 0x8e, 0x80, 0xc9, 0x21, 0x5d, 0x3e, 0x1d, 0x9e, 0x45, 0x23, 0xab, 0x8e, + 0x65, 0x5b, 0x2e, 0xd5, 0xb9, 0xa2, 0xca, 0xc0, 0x78, 0xbe, 0xd7, 0xad, 0x28, 0xb6, 0x40, 0x34, + 0x40, 0x63, 0x12, 0x7b, 0x94, 0x45, 0xfd, 0x66, 0x26, 0x36, 0x18, 0x8f, 0x70, 0x9e, 0x47, 0x46, + 0x47, 0xae, 0xcf, 0xe8, 0x50, 0xbf, 0x96, 0x45, 0x65, 0x76, 0x4e, 0x58, 0xb0, 0x9c, 0x1d, 0xcd, + 0x39, 0x1a, 0xe7, 0xc9, 0xa1, 0xdd, 0xa5, 0x48, 0x66, 0xc8, 0xe0, 0x3e, 0xcc, 0x90, 0xf3, 0x68, + 0x50, 0xba, 0xfe, 0xe3, 0xce, 0x0c, 0x76, 0xd6, 0x02, 0xa8, 0xfa, 0x7f, 0xb2, 0x08, 0x7d, 0xf6, + 0xfa, 0xf5, 0xa7, 0x58, 0x41, 0xea, 0xaf, 0x66, 0xd0, 0x98, 0xf0, 0xae, 0x49, 0xf1, 0xce, 0x45, + 0xdf, 0x2f, 0x9a, 0x09, 0xbd, 0x86, 0xc2, 0x1f, 0x4a, 0x7c, 0x1c, 0x9b, 0x89, 0xf3, 0x0f, 0x0c, + 0x0f, 0x1c, 0x0c, 0x52, 0xc0, 0x33, 0x15, 0x30, 0x79, 0x26, 0xfa, 0x74, 0xf8, 0x25, 0xdf, 0x6f, + 0x98, 0x0b, 0x97, 0x1f, 0xc6, 0x30, 0x9f, 0xea, 0x3b, 0x54, 0xbf, 0x35, 0x88, 0x06, 0xe7, 0x1f, + 0xd0, 0xe6, 0x31, 0xef, 0x1a, 0xe9, 0x34, 0x32, 0x78, 0xc0, 0xd3, 0xc8, 0xe3, 0x5c, 0x84, 0xbc, + 0x11, 0xf6, 0x67, 0x21, 0xda, 0x7c, 0xac, 0xe7, 0xe3, 0xcd, 0xfb, 0x3d, 0x7d, 0xfc, 0xee, 0xd1, + 0xfe, 0x3c, 0x87, 0x72, 0x6b, 0xb5, 0xd5, 0x93, 0x71, 0x73, 0xa4, 0xe3, 0x66, 0x6f, 0x47, 0xb3, + 0x1a, 0xf8, 0x8e, 0x4a, 0x61, 0xec, 0x40, 0xcc, 0x4d, 0xf4, 0x95, 0x2c, 0x1a, 0x5a, 0xeb, 0x6c, + 0xb8, 0xbb, 0xae, 0x47, 0xdb, 0xc7, 0xbc, 0x37, 0xcf, 0x8b, 0xf3, 0xe0, 0x60, 0xa8, 0x0d, 0x76, + 0x1e, 0x14, 0xa7, 0xc0, 0x4b, 0xfe, 0xca, 0x28, 0x9d, 0x33, 0x82, 0x95, 0xd1, 0x5f, 0x0f, 0x7f, + 0x3f, 0x8b, 0xc6, 0x6b, 0x2d, 0x83, 0x9a, 0xde, 0x9c, 0xe1, 0x36, 0x0f, 0x21, 0x52, 0xe2, 0xe8, + 0xb5, 0x72, 0x30, 0x47, 0xcb, 0x23, 0xc4, 0x9f, 0xa8, 0xff, 0x37, 0x8b, 0xca, 0xd5, 0x8e, 0xb7, + 0x55, 0xf5, 0x60, 0x73, 0x79, 0x2a, 0xb7, 0xf9, 0xef, 0x64, 0xd0, 0x18, 0x7b, 0x90, 0x75, 0xeb, + 0x3e, 0x35, 0x0f, 0xe1, 0x18, 0x25, 0x1f, 0x87, 0xb2, 0x8f, 0x79, 0x1c, 0xf2, 0x75, 0x99, 0xdb, + 0xe7, 0xb1, 0x90, 0x1d, 0xb4, 0xd9, 0x29, 0xe9, 0x09, 0x79, 0x8d, 0x43, 0x38, 0x47, 0x1c, 0xe5, + 0x6b, 0x7c, 0x2f, 0x83, 0x26, 0xd7, 0x1d, 0xb6, 0x91, 0xeb, 0x62, 0x3f, 0x3f, 0xe6, 0xfd, 0x92, + 0x7c, 0xa1, 0x63, 0xde, 0x43, 0x3f, 0xc8, 0xa0, 0xb3, 0xd1, 0x17, 0x7a, 0x12, 0x56, 0x81, 0xbf, + 0xca, 0xa0, 0xd3, 0x6f, 0x42, 0x12, 0x6c, 0xe0, 0x82, 0x7b, 0xf2, 0xde, 0xe8, 0x98, 0x8f, 0xbc, + 0xef, 0x66, 0xd0, 0xa9, 0x95, 0xfa, 0x5c, 0xed, 0x49, 0xe9, 0xa1, 0xc4, 0xfb, 0x3c, 0x01, 0xfd, + 0xb3, 0x56, 0xbd, 0xb3, 0xf4, 0x24, 0xf5, 0x4f, 0xe4, 0x7d, 0x8e, 0x79, 0xff, 0xfc, 0xff, 0x02, + 0x2a, 0xb3, 0x73, 0xad, 0xf0, 0xe9, 0x3d, 0xd5, 0x96, 0xfe, 0x0c, 0x2a, 0x0b, 0x35, 0xc0, 0x91, + 0x52, 0x8a, 0xe7, 0x17, 0x19, 0xe4, 0x0d, 0x38, 0x5a, 0xca, 0x44, 0xec, 0xc4, 0xf5, 0x0e, 0x75, + 0x36, 0xe4, 0x18, 0x9a, 0x6d, 0xea, 0x6c, 0x10, 0x80, 0xe2, 0xa5, 0xf0, 0xce, 0xae, 0xba, 0x5a, + 0x87, 0xc4, 0x57, 0x71, 0x52, 0x85, 0x4c, 0x5e, 0x47, 0xe0, 0x1a, 0x9a, 0x6d, 0xf0, 0x94, 0x59, + 0x39, 0x7e, 0x2f, 0xce, 0x89, 0x97, 0xd1, 0x84, 0x0f, 0x0b, 0x33, 0x58, 0x4b, 0x29, 0xe2, 0xd2, + 0x72, 0x57, 0x93, 0xac, 0xf8, 0x0d, 0x34, 0xec, 0x03, 0xdf, 0x32, 0x20, 0xbf, 0x8e, 0x89, 0x7a, + 0xb6, 0xd7, 0xad, 0x9c, 0x09, 0x44, 0xdd, 0x37, 0x22, 0xf1, 0x89, 0x11, 0x06, 0x59, 0x00, 0x1c, + 0x3b, 0x51, 0x8a, 0x80, 0xd8, 0x7d, 0x64, 0x84, 0x01, 0xbf, 0x0c, 0x02, 0x6c, 0xcb, 0x74, 0x29, + 0xf8, 0xf8, 0xca, 0x10, 0x60, 0x02, 0x77, 0x82, 0x8e, 0x80, 0xf3, 0x30, 0xa2, 0x08, 0x19, 0x5e, + 0x41, 0x28, 0xf4, 0xc5, 0x88, 0x60, 0xcd, 0x7d, 0x7b, 0x89, 0x24, 0x11, 0xea, 0x5f, 0xb3, 0xf3, + 0x9b, 0x6d, 0x07, 0x23, 0xf9, 0x25, 0x54, 0xa8, 0xda, 0xf6, 0x5d, 0x52, 0x17, 0xde, 0x49, 0x88, + 0x25, 0xd4, 0x6c, 0xbb, 0xd1, 0x71, 0x0c, 0xf9, 0x42, 0x82, 0x13, 0xe1, 0x1a, 0x1a, 0xa9, 0xda, + 0xf6, 0x6a, 0x67, 0xa3, 0x65, 0x34, 0xa5, 0xf4, 0x71, 0x5e, 0x83, 0xc0, 0xb6, 0x1b, 0x36, 0x60, + 0xe2, 0xc9, 0xf7, 0x51, 0x1e, 0xfc, 0x1e, 0x1a, 0xaa, 0xda, 0xb6, 0x48, 0xbe, 0xcf, 0x41, 0xf2, + 0xbd, 0xea, 0xbf, 0x93, 0xf4, 0x6c, 0x53, 0x01, 0x11, 0x4f, 0xbb, 0x3f, 0x2f, 0x6e, 0x6b, 0x27, + 0x59, 0x43, 0x89, 0x9c, 0xfb, 0x50, 0x24, 0xfe, 0x38, 0x2a, 0x56, 0x6d, 0x5b, 0x72, 0x0f, 0x80, + 0x2b, 0x95, 0x71, 0xc5, 0xba, 0xc8, 0x27, 0x3b, 0xf7, 0x1a, 0x1a, 0x8d, 0x36, 0xb6, 0xaf, 0xfc, + 0xfc, 0x9f, 0x66, 0xe0, 0x85, 0x8e, 0xf9, 0x85, 0xda, 0x0d, 0x94, 0xab, 0xda, 0xb6, 0x58, 0x4e, + 0x4e, 0xa5, 0xf4, 0x47, 0x3c, 0x5a, 0xab, 0x6a, 0xdb, 0xfe, 0xab, 0xf3, 0x2b, 0xef, 0xa7, 0xeb, + 0xd5, 0xbf, 0xcd, 0x5f, 0xfd, 0x98, 0xdf, 0x50, 0x7f, 0x2b, 0x87, 0xc6, 0xaa, 0xb6, 0x7d, 0x92, + 0xb6, 0x7e, 0x58, 0x31, 0x61, 0xd7, 0x11, 0x92, 0x96, 0xc7, 0x62, 0x10, 0xfa, 0x51, 0x96, 0x96, + 0x46, 0x25, 0x43, 0x24, 0x22, 0x7f, 0xf8, 0x95, 0xf6, 0x35, 0xfc, 0xfe, 0x24, 0xd2, 0x71, 0x90, + 0x82, 0x7b, 0xd2, 0x71, 0xf9, 0x03, 0x59, 0x54, 0xa3, 0xb2, 0x32, 0x45, 0xc0, 0xb7, 0xb8, 0xa1, + 0xf7, 0xd3, 0x0f, 0x9a, 0x0c, 0xd5, 0x30, 0x74, 0x12, 0xa3, 0xf5, 0xfb, 0xb0, 0xb8, 0xaf, 0x3e, + 0xfc, 0x7a, 0x16, 0x4d, 0x84, 0x7d, 0x78, 0x18, 0x86, 0xe9, 0x34, 0x42, 0xdc, 0x49, 0x19, 0x5c, + 0x24, 0x8e, 0xf0, 0x48, 0x65, 0x17, 0xa0, 0x22, 0x52, 0x39, 0x24, 0x09, 0x6e, 0x15, 0x72, 0xa9, + 0xb7, 0x0a, 0x57, 0x51, 0x89, 0x68, 0x3b, 0x6f, 0x77, 0xa8, 0xb3, 0x2b, 0xb6, 0x52, 0x70, 0xa5, + 0x3b, 0xda, 0x4e, 0xe3, 0x8b, 0x0c, 0x48, 0x02, 0x34, 0x56, 0x83, 0x18, 0x35, 0xc9, 0x79, 0xcc, + 0x63, 0xd4, 0x82, 0xc8, 0x34, 0xa1, 0xa4, 0xc2, 0xbe, 0x94, 0xf4, 0x83, 0x02, 0x1a, 0x9f, 0xd3, + 0x3c, 0x6d, 0x43, 0x73, 0xa9, 0x74, 0x90, 0x18, 0xf3, 0x61, 0x6c, 0x20, 0x18, 0x41, 0xc9, 0x22, + 0x88, 0xcb, 0xd2, 0x37, 0x1a, 0x2e, 0x87, 0xca, 0x35, 0x5e, 0x62, 0x0c, 0xf8, 0x53, 0xa1, 0xdc, + 0xa0, 0xa8, 0x0d, 0x37, 0x67, 0x40, 0x63, 0xfa, 0x46, 0xc3, 0x16, 0x60, 0x92, 0x20, 0xc4, 0xd7, + 0x50, 0xd9, 0x87, 0x31, 0xe3, 0x29, 0x17, 0xbe, 0xb3, 0xbe, 0xc1, 0x6c, 0x27, 0x22, 0xa3, 0xf1, + 0x27, 0xd1, 0xb0, 0xff, 0x53, 0x32, 0x4b, 0xc0, 0xd6, 0xd2, 0x37, 0x12, 0x86, 0xa3, 0x4c, 0x2a, + 0xb3, 0xc2, 0xfc, 0xcc, 0x47, 0x58, 0x63, 0x05, 0xad, 0x22, 0xa4, 0xf8, 0x8b, 0x68, 0xd4, 0xff, + 0x2d, 0x8c, 0xad, 0x02, 0x18, 0x5b, 0xd7, 0x7c, 0xcd, 0xc7, 0xd5, 0x3a, 0x15, 0x25, 0xe7, 0x66, + 0xd7, 0xb3, 0xc2, 0xec, 0x3a, 0xa5, 0x6f, 0x24, 0xad, 0xae, 0x58, 0x03, 0xb8, 0x8e, 0x26, 0x7c, + 0x48, 0xf5, 0xdd, 0x35, 0x42, 0x37, 0xd9, 0xac, 0x2c, 0x86, 0xc6, 0xb2, 0xbe, 0xd1, 0x80, 0x52, + 0x57, 0x80, 0x90, 0x6d, 0xf6, 0x04, 0x17, 0x6e, 0xa1, 0xf3, 0x11, 0xa0, 0xee, 0x6e, 0x19, 0xf7, + 0x3c, 0x61, 0xe9, 0xd6, 0xe7, 0xc4, 0x71, 0x00, 0x6a, 0xc1, 0x04, 0x52, 0x39, 0x8d, 0x5f, 0x2c, + 0xa8, 0x11, 0xa9, 0x9b, 0xb6, 0xa7, 0x34, 0xbc, 0x86, 0x26, 0x7d, 0xfc, 0x9b, 0xb5, 0xd5, 0x55, + 0xc7, 0xfa, 0x02, 0x6d, 0x7a, 0xf5, 0x39, 0x71, 0x52, 0x80, 0x14, 0x24, 0x7d, 0xa3, 0xb1, 0xd9, + 0xb4, 0xd9, 0xa0, 0x60, 0xb8, 0xa8, 0xf0, 0x54, 0x66, 0xfc, 0x0e, 0x3a, 0x2d, 0xc1, 0xeb, 0xa6, + 0xeb, 0x69, 0x66, 0x93, 0xd6, 0xe7, 0xc4, 0xf1, 0x01, 0x8e, 0x32, 0x42, 0xaa, 0x21, 0x90, 0x51, + 0xb1, 0xe9, 0xec, 0xe7, 0xaa, 0xe8, 0x54, 0x4a, 0x4f, 0xed, 0xcb, 0x66, 0xfd, 0x4a, 0x36, 0x1c, + 0x1c, 0xc7, 0xdc, 0x70, 0x9d, 0x45, 0x25, 0xff, 0x4d, 0xc4, 0x16, 0xa2, 0xf4, 0x1b, 0xe0, 0x71, + 0x19, 0x3e, 0x3e, 0xa2, 0x8e, 0x63, 0x6e, 0xcc, 0x1e, 0x86, 0x3a, 0x3e, 0xc8, 0x84, 0xea, 0x38, + 0xe6, 0x06, 0xee, 0x77, 0x73, 0xe1, 0xcc, 0x3e, 0xb1, 0x72, 0x0f, 0xcb, 0x58, 0x0a, 0x2f, 0x4e, + 0x0b, 0xfb, 0x08, 0x20, 0x93, 0x87, 0x66, 0xf1, 0x31, 0x87, 0xe6, 0x0f, 0x93, 0xfd, 0xc9, 0x0d, + 0x90, 0x63, 0xd9, 0x9f, 0x87, 0x30, 0x59, 0xf1, 0x0c, 0x1a, 0xf1, 0xff, 0xe7, 0x96, 0x5a, 0x5e, + 0xca, 0x87, 0xda, 0x10, 0x86, 0x5a, 0x94, 0x04, 0x7f, 0x0e, 0x9d, 0x89, 0x00, 0x56, 0x35, 0x47, + 0x6b, 0x53, 0x8f, 0x3a, 0xdc, 0x46, 0x10, 0xe5, 0xe7, 0x7c, 0xee, 0x86, 0x1d, 0xa0, 0xe5, 0x0a, + 0x6e, 0x7d, 0x24, 0x48, 0x83, 0xa3, 0xb8, 0x8f, 0x5b, 0xf5, 0x7f, 0xca, 0xa2, 0x91, 0x55, 0xcb, + 0xf5, 0x36, 0x1d, 0xea, 0xae, 0x6a, 0x8e, 0x4b, 0x9f, 0xde, 0x1e, 0xfd, 0x04, 0x1a, 0x81, 0x38, + 0xd9, 0x36, 0x35, 0x3d, 0xa9, 0x2e, 0x1d, 0xaf, 0xd1, 0xe0, 0x23, 0xc0, 0x6c, 0x24, 0x51, 0x42, + 0x5c, 0x41, 0x79, 0x3e, 0x06, 0xa4, 0xe8, 0x65, 0x3e, 0x00, 0x38, 0x5c, 0xfd, 0x7a, 0x0e, 0x0d, + 0xfb, 0x5a, 0x9e, 0x35, 0x8e, 0x6b, 0x16, 0xf0, 0xd1, 0x2a, 0x79, 0x1a, 0xa1, 0x55, 0xcb, 0xf1, + 0xb4, 0x96, 0x54, 0x12, 0x17, 0x8e, 0x0c, 0x36, 0x40, 0x39, 0x8f, 0x44, 0x82, 0xa7, 0x10, 0x92, + 0x26, 0x58, 0x11, 0x26, 0xd8, 0x68, 0xaf, 0x5b, 0x41, 0xe1, 0xbc, 0x22, 0x12, 0x85, 0xfa, 0x47, + 0x59, 0x34, 0xe6, 0x77, 0xd2, 0xfc, 0x03, 0xda, 0xec, 0x78, 0x4f, 0xf1, 0x64, 0x88, 0x6a, 0x3b, + 0xff, 0x50, 0x6d, 0xab, 0xff, 0x26, 0x2d, 0x24, 0xb5, 0x96, 0x75, 0xb2, 0x90, 0xfc, 0x4f, 0x8c, + 0x71, 0xf5, 0x67, 0x72, 0x68, 0xd2, 0xd7, 0xfa, 0x42, 0xc7, 0x04, 0x33, 0xa1, 0xa6, 0xb5, 0x5a, + 0x4f, 0xf3, 0xbe, 0x5c, 0xf6, 0x15, 0xb1, 0x22, 0x12, 0x4f, 0x46, 0xf8, 0x25, 0xdb, 0x3d, 0x01, + 0x6e, 0x58, 0x86, 0x4e, 0x64, 0x22, 0xfc, 0x06, 0x1a, 0xf6, 0x7f, 0x56, 0x9d, 0x4d, 0x7f, 0x33, + 0x86, 0xa3, 0x73, 0xc0, 0xa4, 0x39, 0x9b, 0x91, 0x62, 0xc3, 0x32, 0x83, 0xfa, 0xb5, 0x02, 0x3a, + 0xf7, 0xae, 0x61, 0xea, 0xd6, 0x8e, 0x2b, 0x8a, 0x71, 0x1d, 0x7f, 0xa3, 0xf7, 0xf0, 0x6a, 0xe0, + 0x84, 0x96, 0x49, 0x7e, 0x1f, 0x66, 0xeb, 0xdb, 0xe8, 0x74, 0x5c, 0xa5, 0x4e, 0x90, 0xef, 0x28, + 0x7a, 0x67, 0x87, 0x13, 0x34, 0xfc, 0x7a, 0x68, 0xc2, 0xff, 0x44, 0xd2, 0x39, 0xe3, 0x05, 0xd5, + 0x8a, 0x8f, 0x52, 0x50, 0xed, 0x63, 0xa8, 0x30, 0x67, 0xb5, 0x35, 0xc3, 0x0f, 0xf1, 0x85, 0x59, + 0x1c, 0xb4, 0x0b, 0x18, 0x22, 0x28, 0x98, 0x7c, 0xd1, 0x30, 0x74, 0xd9, 0x50, 0x28, 0xdf, 0x67, + 0xe8, 0xb8, 0xd4, 0x21, 0x32, 0x11, 0xb6, 0xd0, 0x88, 0x68, 0x4e, 0x78, 0x8b, 0x10, 0x78, 0x8b, + 0x5e, 0xf6, 0x75, 0xd4, 0x7f, 0x58, 0x4d, 0x45, 0xf8, 0xb8, 0xdb, 0x08, 0x9e, 0xce, 0x7f, 0x19, + 0xee, 0x37, 0x22, 0x51, 0xf9, 0x92, 0x12, 0x60, 0x91, 0x29, 0x27, 0x95, 0x00, 0xab, 0x8c, 0x4c, + 0x74, 0xee, 0x36, 0xc2, 0xc9, 0xc6, 0xf6, 0xe5, 0xf9, 0xf8, 0xf9, 0x2c, 0xc2, 0xb1, 0x03, 0xc4, + 0xfc, 0x53, 0x6c, 0x07, 0xa9, 0xbf, 0x93, 0x41, 0x13, 0x89, 0x4c, 0x5d, 0x7c, 0x03, 0x21, 0x0e, + 0x91, 0xb2, 0x6e, 0x21, 0xc7, 0x2e, 0xcc, 0xde, 0x15, 0x7b, 0x40, 0x48, 0x86, 0xa7, 0x51, 0x89, + 0xff, 0x0a, 0xaa, 0xc0, 0xc7, 0x59, 0x3a, 0x1d, 0x43, 0x27, 0x01, 0x51, 0xd8, 0x0a, 0x7c, 0x0f, + 0x21, 0x97, 0xca, 0xe2, 0xed, 0xda, 0x41, 0x2b, 0x8c, 0x4c, 0xfd, 0x76, 0x06, 0x0d, 0x07, 0x0f, + 0x5c, 0xd5, 0x8f, 0xaa, 0xeb, 0x0a, 0x22, 0xe9, 0x39, 0xf7, 0xb0, 0xa4, 0xe7, 0xd8, 0xa2, 0x22, + 0xb2, 0x9c, 0xff, 0x22, 0x83, 0xc6, 0x02, 0xda, 0x23, 0xf4, 0xb1, 0x1c, 0xf8, 0x45, 0x7e, 0x21, + 0x83, 0x94, 0x59, 0xa3, 0xd5, 0x32, 0xcc, 0xcd, 0xba, 0x79, 0xcf, 0x72, 0xda, 0x90, 0x5b, 0x78, + 0x74, 0x4e, 0x34, 0xf5, 0xe7, 0x32, 0x68, 0x42, 0x3c, 0x50, 0x4d, 0x73, 0xf4, 0xa3, 0xf3, 0x6e, + 0xc6, 0x9f, 0xe4, 0xe8, 0x7a, 0x19, 0xe2, 0xa3, 0x97, 0xac, 0xe6, 0xfd, 0x27, 0x20, 0xcc, 0x9b, + 0xbd, 0xc6, 0x31, 0x0f, 0x45, 0xfb, 0x72, 0x06, 0x4d, 0x12, 0xda, 0xb4, 0xb6, 0xa9, 0xb3, 0x5b, + 0xb3, 0x74, 0xfa, 0x26, 0x35, 0xa9, 0x73, 0x54, 0x83, 0xf4, 0x8f, 0xa1, 0xdc, 0x42, 0xf8, 0x30, + 0x77, 0x5d, 0xaa, 0x1f, 0x9f, 0xda, 0x18, 0xea, 0x1f, 0x14, 0x91, 0x92, 0x6a, 0x99, 0x1c, 0xdb, + 0x4d, 0xbd, 0xaf, 0xb9, 0x39, 0x78, 0x58, 0xe6, 0x66, 0x7e, 0x7f, 0xe6, 0x66, 0x61, 0xbf, 0xe6, + 0x66, 0xf1, 0x51, 0xcc, 0xcd, 0x76, 0xdc, 0xdc, 0x2c, 0x81, 0xb9, 0x79, 0x63, 0x4f, 0x73, 0x73, + 0xde, 0xd4, 0x1f, 0xd3, 0xd8, 0x3c, 0xb6, 0x15, 0x21, 0x1f, 0xc3, 0x4a, 0xc6, 0x57, 0xd8, 0xe2, + 0xd6, 0xb4, 0x1c, 0x9d, 0xf2, 0x0a, 0x8f, 0x25, 0xee, 0x0d, 0x76, 0x04, 0x8c, 0x04, 0xd8, 0x44, + 0x79, 0xcd, 0x91, 0x47, 0x29, 0xaf, 0x79, 0x08, 0x56, 0xf8, 0xf7, 0x32, 0x68, 0xa2, 0x46, 0x1d, + 0xcf, 0xb8, 0x67, 0x34, 0x35, 0xef, 0x30, 0xae, 0x20, 0xab, 0x68, 0x4c, 0x12, 0x28, 0x7d, 0x60, + 0x0b, 0xf2, 0xa2, 0x9b, 0xd4, 0xf1, 0xc0, 0x94, 0x94, 0x23, 0x02, 0x62, 0xf4, 0xac, 0x79, 0xbf, + 0xc4, 0x8d, 0x98, 0xbb, 0x41, 0xf3, 0x3e, 0x9c, 0x2b, 0xd2, 0x10, 0xbf, 0x48, 0x40, 0xaf, 0x7e, + 0x23, 0x83, 0x2e, 0x13, 0x6a, 0xd2, 0x1d, 0x6d, 0xa3, 0x45, 0x25, 0xc1, 0x62, 0x6d, 0x67, 0xf3, + 0xde, 0x70, 0xdb, 0x9a, 0xd7, 0xdc, 0x3a, 0xd0, 0x5b, 0x2e, 0x44, 0x3f, 0x72, 0xb5, 0x8f, 0xd5, + 0x29, 0xc2, 0xa7, 0xfe, 0x30, 0x83, 0x8a, 0x77, 0xcd, 0xfb, 0xa6, 0xb5, 0x73, 0xb0, 0x42, 0x48, + 0x37, 0x50, 0x59, 0x88, 0x91, 0x34, 0xce, 0xbf, 0x5a, 0xc6, 0xc1, 0x0d, 0xfe, 0x69, 0x33, 0x99, + 0x0a, 0xbf, 0x16, 0x30, 0x41, 0x98, 0x8a, 0xf4, 0x15, 0x2d, 0x9f, 0x29, 0xf6, 0x35, 0x33, 0x99, + 0x1c, 0x9f, 0x17, 0xdf, 0x18, 0x90, 0x52, 0x3f, 0xd9, 0xa3, 0xf0, 0x4f, 0x0c, 0xa8, 0xbf, 0x72, + 0x0d, 0xe5, 0x57, 0x4c, 0xba, 0x72, 0x0f, 0x5f, 0x97, 0x8a, 0x3d, 0x89, 0xf7, 0x9a, 0x90, 0xf5, + 0x04, 0x88, 0xc5, 0x01, 0x22, 0x95, 0x84, 0xba, 0x29, 0x97, 0xe2, 0x11, 0xba, 0xc5, 0x32, 0x0f, + 0xc7, 0x2c, 0x0e, 0x10, 0xb9, 0x64, 0xcf, 0x4d, 0xb9, 0x56, 0x8d, 0x18, 0x38, 0x11, 0x2e, 0x8e, + 0xf1, 0xb9, 0x84, 0xf1, 0xb2, 0x94, 0x56, 0x1a, 0x26, 0xee, 0xdd, 0x48, 0x52, 0x2c, 0x0e, 0x90, + 0xf4, 0x92, 0x32, 0x91, 0x4f, 0xcf, 0x08, 0xff, 0xc6, 0x64, 0x6c, 0xeb, 0x01, 0xdc, 0xe2, 0x00, + 0x89, 0x7e, 0xa6, 0xe6, 0x56, 0xe4, 0xa3, 0x1e, 0xf1, 0xf0, 0x1c, 0x09, 0xb5, 0x38, 0x40, 0x62, + 0x9f, 0xff, 0x88, 0x7c, 0x61, 0x42, 0x5c, 0xf7, 0xc4, 0x1b, 0x05, 0x9c, 0xd4, 0x28, 0xff, 0x1a, + 0xc5, 0xeb, 0xb1, 0xca, 0xef, 0x22, 0xfc, 0xed, 0x74, 0x8c, 0x99, 0x23, 0x17, 0x07, 0x48, 0xac, + 0x4e, 0xfc, 0x15, 0xbf, 0x18, 0xb8, 0x58, 0xcb, 0x47, 0x25, 0xe3, 0xcd, 0x78, 0x9f, 0x69, 0xc9, + 0x2f, 0x16, 0x7e, 0x53, 0x2e, 0x02, 0x2d, 0x16, 0x67, 0x1c, 0x6b, 0x65, 0xde, 0xd4, 0x59, 0xef, + 0x48, 0x96, 0xc3, 0xed, 0x78, 0xb9, 0x54, 0x51, 0x84, 0xf7, 0x99, 0x18, 0xa7, 0xc0, 0x2e, 0x0e, + 0x90, 0x78, 0x79, 0xd5, 0x5b, 0x91, 0x52, 0x9d, 0x22, 0x76, 0x3b, 0xae, 0x55, 0x86, 0x92, 0xb4, + 0x0a, 0x45, 0x3d, 0x6f, 0xc7, 0x6b, 0x47, 0x2a, 0x23, 0xa9, 0x4d, 0x0b, 0xac, 0xd4, 0xb4, 0x5f, + 0x6b, 0xf2, 0x56, 0xa4, 0xc6, 0x1f, 0x94, 0xd1, 0x4d, 0x69, 0x5a, 0xf3, 0x34, 0xb9, 0x69, 0x5e, + 0x0d, 0x30, 0x52, 0x6d, 0x4e, 0x19, 0x4b, 0xed, 0x50, 0xc0, 0x49, 0x1d, 0xca, 0x2b, 0xd3, 0xdd, + 0x8a, 0x14, 0x48, 0x51, 0xc6, 0xa3, 0x8d, 0x4a, 0x28, 0xd6, 0xa8, 0x5c, 0x4a, 0xe5, 0xa6, 0x5c, + 0x37, 0x44, 0x99, 0x88, 0x76, 0x50, 0x88, 0x61, 0x1d, 0x24, 0xd5, 0x17, 0xa9, 0x40, 0x4d, 0x02, + 0x05, 0x03, 0x79, 0x39, 0x78, 0xc2, 0xda, 0xea, 0xe2, 0x00, 0x81, 0x6a, 0x05, 0x2a, 0xaf, 0x76, + 0xa1, 0x9c, 0x02, 0x8a, 0xe1, 0xa0, 0x96, 0xe2, 0x03, 0xda, 0x5c, 0x1c, 0x20, 0xbc, 0x12, 0xc6, + 0x75, 0x29, 0x21, 0x5e, 0x99, 0x8c, 0x2e, 0x11, 0x01, 0x82, 0x2d, 0x11, 0x61, 0xda, 0xfc, 0x42, + 0x32, 0x69, 0x5c, 0x39, 0x1d, 0x3d, 0x3f, 0xc4, 0xf1, 0x8b, 0x03, 0x24, 0x99, 0x68, 0x7e, 0x2b, + 0x92, 0x47, 0xad, 0x3c, 0x13, 0x8b, 0x89, 0x0b, 0x51, 0x4c, 0x5d, 0x72, 0xc6, 0xf5, 0x4a, 0x6a, + 0x1d, 0x27, 0xe5, 0x0c, 0x08, 0x78, 0x36, 0x10, 0x90, 0x24, 0x59, 0x1c, 0x20, 0xa9, 0x15, 0xa0, + 0x6a, 0x89, 0x6c, 0x66, 0x45, 0x89, 0x1a, 0xae, 0x31, 0xf4, 0xe2, 0x00, 0x49, 0xe4, 0x3f, 0xdf, + 0x94, 0xd3, 0x88, 0x95, 0xb3, 0xd1, 0x4e, 0x0c, 0x31, 0xac, 0x13, 0xa5, 0x74, 0xe3, 0x9b, 0x72, + 0xd6, 0xae, 0x72, 0x2e, 0xc9, 0x15, 0xae, 0x9c, 0x52, 0x76, 0x2f, 0x49, 0x4f, 0x92, 0x55, 0x9e, + 0x05, 0xfe, 0xf3, 0x3e, 0x7f, 0x1a, 0xcd, 0xe2, 0x00, 0x49, 0x4f, 0xb0, 0x25, 0xe9, 0x79, 0xaa, + 0xca, 0xf9, 0xbd, 0x64, 0x06, 0x4f, 0x97, 0x9e, 0xe3, 0xaa, 0xed, 0x91, 0x2a, 0xaa, 0x3c, 0x17, + 0xcd, 0xe5, 0xe8, 0x4b, 0xb8, 0x38, 0x40, 0xf6, 0x48, 0x38, 0xbd, 0xdb, 0x27, 0x6f, 0x53, 0xb9, + 0x10, 0xad, 0xfa, 0x91, 0x4a, 0xb4, 0x38, 0x40, 0xfa, 0x64, 0x7d, 0xde, 0xed, 0x93, 0x3c, 0xa9, + 0x54, 0xf6, 0x14, 0x1b, 0xe8, 0xa3, 0x4f, 0xea, 0xe5, 0x4a, 0x6a, 0x06, 0xa3, 0x72, 0x31, 0x3a, + 0x74, 0x53, 0x48, 0xd8, 0xd0, 0x4d, 0xcb, 0x7d, 0x5c, 0x49, 0x4d, 0x21, 0x54, 0x9e, 0xdf, 0x43, + 0x60, 0xf0, 0x8c, 0xa9, 0xc9, 0x87, 0x2b, 0xa9, 0x39, 0x7c, 0x8a, 0x1a, 0x15, 0x98, 0x42, 0xc2, + 0x04, 0xa6, 0x65, 0xff, 0xad, 0xa4, 0x26, 0xd1, 0x29, 0x97, 0xf6, 0x10, 0x18, 0x3e, 0x61, 0x5a, + 0xfa, 0xdd, 0xad, 0x48, 0x16, 0x9b, 0xf2, 0x91, 0xe8, 0xba, 0x21, 0xa1, 0xd8, 0xba, 0x21, 0xe7, + 0xbb, 0xd5, 0x12, 0x81, 0xfe, 0xca, 0x47, 0xa3, 0xd3, 0x3c, 0x86, 0x66, 0xd3, 0x3c, 0x9e, 0x1a, + 0x50, 0x4b, 0x04, 0x9d, 0x2b, 0x97, 0xfb, 0x09, 0x01, 0x74, 0x54, 0x08, 0x0f, 0x53, 0xaf, 0xa7, + 0x44, 0x3d, 0x2b, 0x2f, 0x44, 0xbd, 0x8f, 0x09, 0x82, 0xc5, 0x01, 0x92, 0x12, 0x2b, 0x4d, 0xd2, + 0x83, 0xbb, 0x94, 0x2b, 0xd1, 0x69, 0x9b, 0x46, 0xc3, 0xa6, 0x6d, 0x6a, 0x60, 0xd8, 0x52, 0xda, + 0xfd, 0x80, 0x72, 0x35, 0x6a, 0x98, 0x25, 0x29, 0x98, 0x61, 0x96, 0x72, 0xaf, 0x40, 0xd2, 0xc3, + 0x95, 0x94, 0x8f, 0xed, 0xf9, 0x84, 0x40, 0x93, 0xf2, 0x84, 0x3c, 0x7a, 0x27, 0xb4, 0x9d, 0xee, + 0xda, 0x2d, 0x4b, 0xd3, 0x95, 0x17, 0x53, 0x6d, 0x27, 0x8e, 0x94, 0x6c, 0x27, 0x0e, 0x60, 0xbb, + 0xbc, 0xec, 0x3f, 0x57, 0xae, 0x45, 0x77, 0x79, 0x19, 0xc7, 0x76, 0xf9, 0x88, 0xaf, 0xbd, 0x96, + 0xf0, 0x5a, 0x2b, 0x2f, 0x45, 0x07, 0x40, 0x0c, 0xcd, 0x06, 0x40, 0xdc, 0xcf, 0xfd, 0x5e, 0x7f, + 0x8f, 0xb1, 0x32, 0x05, 0xd2, 0x2e, 0x06, 0xd5, 0xa2, 0xfb, 0xd0, 0x2d, 0x0e, 0x90, 0xfe, 0x5e, + 0xe7, 0x7a, 0x8a, 0x03, 0x58, 0x99, 0x8e, 0x0e, 0xb0, 0x04, 0x01, 0x1b, 0x60, 0x49, 0xb7, 0x71, + 0x3d, 0xc5, 0x83, 0xab, 0x7c, 0xbc, 0xaf, 0xa8, 0xe0, 0x9d, 0x53, 0xfc, 0xbe, 0x37, 0x65, 0x17, + 0xac, 0x72, 0x3d, 0xba, 0xd9, 0x85, 0x18, 0xb6, 0xd9, 0x49, 0xae, 0xda, 0x9b, 0xb2, 0xc7, 0x53, + 0x99, 0x49, 0x72, 0x85, 0x5b, 0xa4, 0xe4, 0x19, 0x25, 0xe9, 0x0e, 0x46, 0xe5, 0x46, 0x74, 0xd4, + 0xa5, 0xd1, 0xb0, 0x51, 0x97, 0xea, 0x9c, 0x5c, 0x48, 0xfa, 0x09, 0x95, 0x9b, 0x71, 0xcf, 0x69, + 0x14, 0xcf, 0x2c, 0x9f, 0x84, 0x6f, 0xf1, 0x76, 0x3c, 0xf2, 0x58, 0x79, 0x39, 0x6a, 0xdf, 0x46, + 0xb1, 0xcc, 0xbe, 0x8d, 0x45, 0x2a, 0xdf, 0x8e, 0x07, 0xeb, 0x2a, 0xaf, 0xa4, 0x4b, 0x08, 0xc6, + 0x4a, 0x3c, 0xb8, 0xf7, 0x76, 0x3c, 0xbe, 0x55, 0xb9, 0x95, 0x2e, 0x21, 0xd0, 0x6e, 0x3c, 0x1e, + 0xf6, 0xba, 0x94, 0xf3, 0xa7, 0x7c, 0x22, 0x6a, 0x3a, 0x06, 0x08, 0x66, 0x3a, 0x86, 0x99, 0x81, + 0xd7, 0xa5, 0x5c, 0x39, 0xe5, 0x93, 0x09, 0x96, 0xe0, 0x61, 0xa5, 0x8c, 0xba, 0xeb, 0x52, 0x8e, + 0x99, 0xf2, 0x6a, 0x82, 0x25, 0x78, 0x3a, 0x29, 0x13, 0x4d, 0xdf, 0x2b, 0x00, 0x40, 0xf9, 0x14, + 0xc8, 0x50, 0x1f, 0x7e, 0xa7, 0xbb, 0x38, 0x40, 0xf6, 0x0a, 0x24, 0x78, 0xaf, 0xbf, 0xd7, 0x55, + 0x79, 0x2d, 0x3a, 0x85, 0xfb, 0xd1, 0xb1, 0x29, 0xdc, 0xd7, 0x73, 0xfb, 0x7a, 0x2c, 0x18, 0x50, + 0x79, 0x3d, 0xba, 0xc4, 0x45, 0x90, 0x6c, 0x89, 0x8b, 0x87, 0x0e, 0x46, 0xa2, 0xdc, 0x94, 0x4f, + 0x47, 0x97, 0x38, 0x19, 0xc7, 0x96, 0xb8, 0x48, 0x44, 0x5c, 0x2d, 0x11, 0x7c, 0xa5, 0xbc, 0x11, + 0x5d, 0xe2, 0x62, 0x68, 0xb6, 0xc4, 0xc5, 0xc3, 0xb5, 0x5e, 0x8f, 0xc5, 0x20, 0x29, 0xb7, 0xd3, + 0x9f, 0x1f, 0x90, 0xf2, 0xf3, 0xf3, 0x88, 0x25, 0x92, 0x1e, 0x4c, 0xa3, 0x54, 0xa3, 0xf3, 0x37, + 0x8d, 0x86, 0xcd, 0xdf, 0xd4, 0x40, 0x9c, 0x95, 0xd4, 0x9a, 0x9b, 0xca, 0xec, 0x1e, 0x07, 0x87, + 0xd0, 0x14, 0x49, 0xab, 0xd6, 0x79, 0x3b, 0xfe, 0x9d, 0x31, 0xa5, 0xd6, 0xe7, 0x8c, 0xec, 0x1f, + 0x83, 0xe2, 0xdf, 0x25, 0xab, 0xa7, 0x38, 0x01, 0x95, 0xb9, 0xe8, 0xea, 0x9a, 0x20, 0x60, 0xab, + 0x6b, 0xd2, 0x75, 0xb8, 0x90, 0xfc, 0xbc, 0xa3, 0x32, 0x1f, 0xbb, 0x12, 0x8f, 0xe1, 0xd9, 0xea, + 0x94, 0xf8, 0x24, 0x24, 0x49, 0xff, 0x02, 0xa0, 0xb2, 0x10, 0xdb, 0xaf, 0x53, 0x68, 0x60, 0xbf, + 0x4e, 0xfb, 0x7a, 0xe0, 0xe7, 0xfa, 0x7e, 0xc8, 0x51, 0x79, 0x13, 0xc4, 0x56, 0xfa, 0x89, 0x15, + 0x64, 0x8b, 0x03, 0xa4, 0xef, 0xa7, 0x20, 0xef, 0xa2, 0xd3, 0x77, 0x76, 0xd7, 0xde, 0x5e, 0x0a, + 0xe2, 0xb7, 0x56, 0x1d, 0x6a, 0x6b, 0x0e, 0x55, 0x16, 0xa3, 0xb6, 0x7a, 0x2a, 0x11, 0xb3, 0xd5, + 0x53, 0x11, 0x49, 0xb1, 0xfe, 0x5c, 0xa8, 0xef, 0x25, 0x36, 0x9c, 0x11, 0xe9, 0xdc, 0x6c, 0x75, + 0x8a, 0x22, 0x98, 0x82, 0x96, 0x2c, 0x73, 0x13, 0x3c, 0x15, 0x9f, 0x89, 0xae, 0x4e, 0xfd, 0x29, + 0xd9, 0xea, 0xd4, 0x1f, 0xcb, 0x86, 0x7a, 0x14, 0xcb, 0xe7, 0xe0, 0x5b, 0xd1, 0xa1, 0x9e, 0x42, + 0xc2, 0x86, 0x7a, 0x0a, 0x38, 0x29, 0x90, 0x50, 0x97, 0x7a, 0xca, 0xd2, 0x5e, 0x02, 0x81, 0x24, + 0x29, 0x10, 0xc0, 0x49, 0x81, 0x0b, 0xd4, 0x6b, 0x6e, 0x29, 0x77, 0xf6, 0x12, 0x08, 0x24, 0x49, + 0x81, 0x00, 0x66, 0x87, 0xcd, 0x28, 0x78, 0xb6, 0xd3, 0xba, 0xef, 0xf7, 0xd9, 0x72, 0xf4, 0xb0, + 0xd9, 0x97, 0x90, 0x1d, 0x36, 0xfb, 0x22, 0xf1, 0x97, 0x1e, 0xd9, 0xc5, 0xad, 0xac, 0x40, 0x83, + 0x53, 0xa1, 0x5d, 0xf0, 0x28, 0x5c, 0x8b, 0x03, 0xe4, 0x51, 0x5d, 0xe8, 0x2f, 0x06, 0xde, 0x6b, + 0x65, 0x15, 0x9a, 0x1a, 0x0b, 0x7c, 0x15, 0x1c, 0xbc, 0x38, 0x40, 0x7c, 0x8a, 0xd9, 0x22, 0xca, + 0xc3, 0x17, 0x34, 0xd4, 0x5f, 0xcb, 0xa0, 0xe1, 0x35, 0xcf, 0xa1, 0x5a, 0x5b, 0x44, 0x76, 0x9d, + 0x43, 0x25, 0x6e, 0x13, 0x8b, 0xaf, 0x43, 0x0d, 0x91, 0xe0, 0x37, 0xbe, 0x8c, 0x46, 0x97, 0x34, + 0xd7, 0x03, 0x4e, 0xe9, 0xb3, 0xb7, 0x24, 0x06, 0xc5, 0x4b, 0x9c, 0x8e, 0xf3, 0xc1, 0x05, 0x50, + 0xee, 0xa1, 0x17, 0x40, 0xa5, 0x0f, 0xba, 0x95, 0x01, 0xb8, 0xe6, 0x89, 0xf1, 0xaa, 0xbd, 0x0c, + 0x4a, 0x58, 0xeb, 0x8f, 0xef, 0x9d, 0x5f, 0x41, 0x63, 0xb1, 0x4b, 0x47, 0xe1, 0xd4, 0x7e, 0xc4, + 0x3b, 0xc9, 0x38, 0x37, 0xbe, 0x84, 0x72, 0x77, 0xeb, 0x73, 0x72, 0x45, 0xfb, 0x4e, 0x24, 0x61, + 0x8d, 0x61, 0xf1, 0x0b, 0x81, 0xc7, 0xf5, 0x2e, 0x59, 0x12, 0x97, 0x8d, 0xf0, 0xd5, 0xad, 0x8e, + 0xd3, 0x22, 0x12, 0x4a, 0xfd, 0x4e, 0x39, 0xbc, 0x70, 0xc1, 0x97, 0xc5, 0x7d, 0xab, 0x54, 0xe1, + 0x3f, 0x96, 0xdd, 0xc8, 0xef, 0x57, 0x3f, 0x8d, 0x86, 0xeb, 0x6d, 0x9b, 0x3a, 0xae, 0x65, 0x42, + 0xed, 0xed, 0x6c, 0x78, 0x7b, 0x60, 0x48, 0x70, 0x39, 0x42, 0x52, 0xa6, 0x0f, 0x0b, 0x87, 0xe7, + 0x1e, 0x5a, 0x38, 0xfc, 0x2a, 0xca, 0xdf, 0x75, 0xf9, 0x77, 0xe7, 0x03, 0xd2, 0x4e, 0xec, 0x2b, + 0x5b, 0x9c, 0x02, 0x5f, 0x43, 0x05, 0xb8, 0x42, 0x70, 0x95, 0x3c, 0xd0, 0x42, 0xd6, 0x70, 0x0b, + 0x20, 0x72, 0x19, 0x0d, 0x4e, 0x83, 0xdf, 0x42, 0xe3, 0x61, 0x4d, 0x0e, 0x28, 0x79, 0xe2, 0x87, + 0x7a, 0x42, 0xa6, 0xa1, 0xf4, 0xed, 0x79, 0xa8, 0x95, 0x22, 0x8b, 0x48, 0x30, 0xe2, 0x45, 0x34, + 0x16, 0xc2, 0x98, 0x8a, 0xfc, 0x10, 0xf3, 0x0b, 0xbd, 0x6e, 0xe5, 0x9c, 0x24, 0x8b, 0xa9, 0x53, + 0x16, 0x15, 0x67, 0xc3, 0xf5, 0xf0, 0xe3, 0x09, 0xa5, 0x87, 0x8e, 0xe1, 0x53, 0xe2, 0x12, 0xb3, + 0x28, 0x3e, 0x9e, 0x10, 0xfd, 0x64, 0xc2, 0x02, 0x1a, 0x25, 0x56, 0xc7, 0xa3, 0xeb, 0x96, 0x5f, + 0xe2, 0x96, 0xc7, 0x20, 0xc2, 0x33, 0x39, 0x0c, 0xd3, 0xf0, 0x2c, 0x3f, 0x51, 0x53, 0x4e, 0x28, + 0x8d, 0x72, 0xe1, 0xe5, 0xb4, 0x6a, 0xb9, 0x52, 0xfa, 0xa4, 0xf4, 0x7a, 0x49, 0x61, 0x29, 0xe5, + 0x71, 0x7f, 0x36, 0x83, 0x0a, 0xeb, 0x8e, 0x66, 0x78, 0xae, 0x70, 0xe8, 0x9f, 0x9e, 0xda, 0x71, + 0x34, 0x9b, 0x8d, 0x8f, 0x29, 0xb8, 0xcb, 0x7c, 0x47, 0x6b, 0x75, 0xa8, 0x3b, 0xfb, 0x2e, 0x7b, + 0xbb, 0xbf, 0xef, 0x56, 0x3e, 0xb5, 0x09, 0x9e, 0xac, 0xa9, 0xa6, 0xd5, 0x9e, 0xde, 0x74, 0xb4, + 0x6d, 0x83, 0x97, 0x4e, 0xd7, 0x5a, 0xd3, 0x1e, 0x6d, 0x51, 0xdb, 0x72, 0xbc, 0x69, 0xcd, 0x36, + 0xa6, 0xbd, 0x5d, 0x9b, 0xba, 0xd3, 0x81, 0x24, 0xde, 0x02, 0x1b, 0x02, 0x1e, 0xfc, 0x27, 0x0f, + 0x01, 0x8e, 0xc3, 0xcb, 0x08, 0x89, 0x57, 0xad, 0xda, 0xb6, 0xb8, 0x1d, 0x90, 0x5c, 0x9f, 0x3e, + 0x86, 0x0f, 0xec, 0x40, 0x61, 0x9a, 0x2d, 0x17, 0xce, 0x91, 0x24, 0xb0, 0x51, 0xb0, 0x2e, 0x9e, + 0xc8, 0x57, 0xd3, 0x48, 0xa8, 0x71, 0xff, 0x61, 0x53, 0x94, 0x14, 0x67, 0xc3, 0x1b, 0x68, 0x4c, + 0xc8, 0x0d, 0x62, 0x0b, 0x47, 0xa3, 0x8b, 0x46, 0x0c, 0xcd, 0x07, 0x6d, 0xf0, 0x8c, 0xba, 0x00, + 0xcb, 0x6d, 0xc4, 0x38, 0xf0, 0x6c, 0x98, 0xb3, 0x04, 0x55, 0x7a, 0x94, 0x31, 0x18, 0xb1, 0x50, + 0x44, 0xde, 0xe7, 0xe7, 0xc5, 0x7d, 0xe4, 0x32, 0x32, 0x11, 0x16, 0x59, 0x06, 0x1f, 0xf5, 0xe3, + 0x29, 0x32, 0xe2, 0x63, 0x3e, 0xca, 0x82, 0x6b, 0x68, 0x24, 0x70, 0x4e, 0xdc, 0x65, 0x2b, 0xdb, + 0x44, 0x58, 0xcf, 0x26, 0x16, 0xb6, 0x28, 0x0b, 0x89, 0xf0, 0xe0, 0x1b, 0xa8, 0xc4, 0xdd, 0xfb, + 0x75, 0x7e, 0x1f, 0xe1, 0x5f, 0x39, 0x03, 0xac, 0x61, 0xc8, 0x3d, 0x16, 0x10, 0xe2, 0xd7, 0x51, + 0xb9, 0xfa, 0xee, 0x1a, 0x5b, 0x67, 0xaa, 0x64, 0xd9, 0x55, 0x4e, 0x85, 0x81, 0xde, 0x90, 0xca, + 0x6c, 0xb5, 0x68, 0x43, 0x73, 0x22, 0x8b, 0x87, 0x4c, 0x8f, 0xe7, 0x51, 0xec, 0xdb, 0xfc, 0xca, + 0x64, 0xf8, 0xd5, 0x45, 0x0d, 0x30, 0x0d, 0x51, 0xcb, 0x29, 0x92, 0xaf, 0x1d, 0x65, 0x62, 0xa3, + 0x66, 0xce, 0x70, 0xb5, 0x56, 0xcb, 0xda, 0x21, 0xd4, 0x70, 0xdd, 0x0e, 0x85, 0xcb, 0x8c, 0x12, + 0x1f, 0x35, 0xba, 0x40, 0x35, 0x1c, 0x8e, 0x8b, 0x64, 0xd3, 0x47, 0xd9, 0xd4, 0xff, 0xcc, 0xc8, + 0x03, 0x3a, 0xa8, 0xcf, 0x9b, 0x49, 0xad, 0xcf, 0x7b, 0x0d, 0x0d, 0x89, 0x6d, 0x20, 0x88, 0x14, + 0x85, 0x7c, 0x18, 0x3f, 0x1e, 0xc2, 0xd0, 0x49, 0x48, 0x00, 0xb9, 0x08, 0x61, 0x49, 0x8d, 0x9c, + 0x94, 0x8b, 0x10, 0x96, 0xd4, 0x88, 0x14, 0xd4, 0x98, 0x41, 0x65, 0x31, 0x98, 0xa5, 0x6c, 0x7b, + 0x88, 0x69, 0xf0, 0x33, 0xc3, 0x79, 0xc8, 0x84, 0x44, 0x84, 0x5f, 0x45, 0x28, 0xd4, 0xaf, 0xd8, + 0xb4, 0x60, 0xee, 0xc9, 0xdd, 0x21, 0xcf, 0xbd, 0x90, 0x5a, 0xfd, 0xbb, 0x4c, 0x62, 0xca, 0xb0, + 0x67, 0x10, 0xd1, 0x37, 0x92, 0x1e, 0xe0, 0x19, 0x44, 0xac, 0x8e, 0x78, 0x06, 0x89, 0x08, 0x5f, + 0x41, 0xa5, 0x58, 0x25, 0x02, 0x88, 0x36, 0x08, 0xca, 0x10, 0x04, 0x58, 0x3c, 0x83, 0x4a, 0x6c, + 0x00, 0x33, 0x11, 0x42, 0x21, 0x50, 0xe3, 0xa8, 0x23, 0x60, 0xf2, 0x88, 0xf3, 0xe9, 0x18, 0x4f, + 0x24, 0x58, 0x58, 0xf0, 0xa4, 0x4c, 0xd7, 0x30, 0x38, 0xf8, 0xf7, 0xb2, 0x7d, 0x4e, 0x17, 0x4f, + 0x6f, 0x5a, 0x47, 0x90, 0x62, 0x97, 0xef, 0x93, 0x62, 0xf7, 0xef, 0xd9, 0x3e, 0x47, 0xa7, 0xa7, + 0x3a, 0x15, 0x26, 0x50, 0x46, 0x34, 0x15, 0x26, 0xcc, 0x42, 0x32, 0x74, 0x22, 0x13, 0xc5, 0x92, + 0xe6, 0x0a, 0x0f, 0x4d, 0x9a, 0xfb, 0xad, 0xdc, 0x5e, 0x47, 0xcb, 0x13, 0xdd, 0xef, 0x47, 0xf7, + 0x33, 0xa8, 0x1c, 0x68, 0x56, 0x14, 0xc2, 0x19, 0x09, 0x82, 0xc2, 0x38, 0x18, 0x78, 0x24, 0x22, + 0x7c, 0x95, 0x3f, 0x2b, 0x7c, 0x53, 0xbc, 0x08, 0x0c, 0x50, 0x2b, 0x86, 0x3d, 0x5b, 0xc3, 0x35, + 0xde, 0xa7, 0x24, 0x40, 0xab, 0x7f, 0x9a, 0x4d, 0x3d, 0x9f, 0x9f, 0xf4, 0xd1, 0x3e, 0xfa, 0x28, + 0x45, 0x89, 0xdc, 0xb3, 0x70, 0xa2, 0xc4, 0x7d, 0x28, 0xf1, 0x27, 0xd9, 0x54, 0x3f, 0xcc, 0x89, + 0x12, 0xf7, 0xb3, 0x5a, 0x5c, 0x43, 0x43, 0xc4, 0xda, 0x71, 0x6b, 0x56, 0xc7, 0xf4, 0xc4, 0x5a, + 0x01, 0x0b, 0xb5, 0x63, 0xed, 0xb8, 0x8d, 0x26, 0x83, 0x92, 0x90, 0x40, 0xfd, 0x69, 0x76, 0x0f, + 0x4f, 0xd5, 0x89, 0xe2, 0x3f, 0xc4, 0x2d, 0xf2, 0x63, 0x2f, 0xa0, 0xb2, 0xf4, 0x29, 0x59, 0x3c, + 0x8c, 0x4a, 0x2b, 0xb3, 0x6b, 0xf3, 0xe4, 0x9d, 0xf9, 0xb9, 0xf1, 0x01, 0x8c, 0x50, 0x61, 0x6e, + 0x7e, 0xb9, 0x3e, 0x3f, 0x37, 0x9e, 0x99, 0x1d, 0xff, 0xe0, 0x1f, 0x2f, 0x0c, 0x7c, 0xf0, 0xe3, + 0x0b, 0x99, 0xef, 0xff, 0xf8, 0x42, 0xe6, 0x1f, 0x7e, 0x7c, 0x21, 0xb3, 0x51, 0x00, 0x23, 0xf4, + 0xc6, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x7f, 0xeb, 0x35, 0x7a, 0x97, 0x97, 0x00, 0x00, } func (m *Metadata) Marshal() (dAtA []byte, err error) { @@ -11871,6 +11941,13 @@ func (m *WindowsDesktopSessionStart) MarshalToSizedBuffer(dAtA []byte) (int, err i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.DesktopName) > 0 { + i -= len(m.DesktopName) + copy(dAtA[i:], m.DesktopName) + i = encodeVarintEvents(dAtA, i, uint64(len(m.DesktopName))) + i-- + dAtA[i] = 0x5a + } if len(m.DesktopLabels) > 0 { for k := range m.DesktopLabels { v := m.DesktopLabels[k] @@ -12808,6 +12885,64 @@ func (m *RenewableCertificateGenerationMismatch) MarshalToSizedBuffer(dAtA []byt return len(dAtA) - i, nil } +func (m *Unknown) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Unknown) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Unknown) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x22 + } + if len(m.UnknownCode) > 0 { + i -= len(m.UnknownCode) + copy(dAtA[i:], m.UnknownCode) + i = encodeVarintEvents(dAtA, i, uint64(len(m.UnknownCode))) + i-- + dAtA[i] = 0x1a + } + if len(m.UnknownType) > 0 { + i -= len(m.UnknownType) + copy(dAtA[i:], m.UnknownType) + i = encodeVarintEvents(dAtA, i, uint64(len(m.UnknownType))) + i-- + dAtA[i] = 0x12 + } + { + size, err := m.Metadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *OneOf) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -14631,6 +14766,29 @@ func (m *OneOf_RenewableCertificateGenerationMismatch) MarshalToSizedBuffer(dAtA } return len(dAtA) - i, nil } +func (m *OneOf_Unknown) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *OneOf_Unknown) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.Unknown != nil { + { + size, err := m.Unknown.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5 + i-- + dAtA[i] = 0x82 + } + return len(dAtA) - i, nil +} func (m *StreamStatus) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -14655,12 +14813,12 @@ func (m *StreamStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n355, err355 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastUploadTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastUploadTime):]) - if err355 != nil { - return 0, err355 + n357, err357 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastUploadTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastUploadTime):]) + if err357 != nil { + return 0, err357 } - i -= n355 - i = encodeVarintEvents(dAtA, i, uint64(n355)) + i -= n357 + i = encodeVarintEvents(dAtA, i, uint64(n357)) i-- dAtA[i] = 0x1a if m.LastEventIndex != 0 { @@ -14890,12 +15048,12 @@ func (m *Identity) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x4a } - n361, err361 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err361 != nil { - return 0, err361 + n363, err363 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err363 != nil { + return 0, err363 } - i -= n361 - i = encodeVarintEvents(dAtA, i, uint64(n361)) + i -= n363 + i = encodeVarintEvents(dAtA, i, uint64(n363)) i-- dAtA[i] = 0x42 if len(m.KubernetesUsers) > 0 { @@ -17522,6 +17680,10 @@ func (m *WindowsDesktopSessionStart) Size() (n int) { n += mapEntrySize + 1 + sovEvents(uint64(mapEntrySize)) } } + l = len(m.DesktopName) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -17823,6 +17985,32 @@ func (m *RenewableCertificateGenerationMismatch) Size() (n int) { return n } +func (m *Unknown) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Metadata.Size() + n += 1 + l + sovEvents(uint64(l)) + l = len(m.UnknownType) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.UnknownCode) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func (m *OneOf) Size() (n int) { if m == nil { return 0 @@ -18786,6 +18974,18 @@ func (m *OneOf_RenewableCertificateGenerationMismatch) Size() (n int) { } return n } +func (m *OneOf_Unknown) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Unknown != nil { + l = m.Unknown.Size() + n += 2 + l + sovEvents(uint64(l)) + } + return n +} func (m *StreamStatus) Size() (n int) { if m == nil { return 0 @@ -35136,6 +35336,38 @@ func (m *WindowsDesktopSessionStart) Unmarshal(dAtA []byte) error { } m.DesktopLabels[mapkey] = mapvalue iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DesktopName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DesktopName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) @@ -37530,6 +37762,186 @@ func (m *RenewableCertificateGenerationMismatch) Unmarshal(dAtA []byte) error { } return nil } +func (m *Unknown) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Unknown: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Unknown: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnknownType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UnknownType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnknownCode", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UnknownCode = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *OneOf) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -40324,6 +40736,41 @@ func (m *OneOf) Unmarshal(dAtA []byte) error { } m.Event = &OneOf_RenewableCertificateGenerationMismatch{v} iNdEx = postIndex + case 80: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Unknown", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &Unknown{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Event = &OneOf_Unknown{v} + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/api/types/events/events.proto b/api/types/events/events.proto index 6cdcd923ddbd2..7a1e69e52b463 100644 --- a/api/types/events/events.proto +++ b/api/types/events/events.proto @@ -1573,6 +1573,8 @@ message WindowsDesktopSessionStart { string WindowsUser = 9 [ (gogoproto.jsontag) = "windows_user" ]; // DesktopLabels are the labels on the desktop resource. map DesktopLabels = 10 [ (gogoproto.jsontag) = "desktop_labels" ]; + // DesktopName is the name of the desktop resource. + string DesktopName = 11 [ (gogoproto.jsontag) = "desktop_name" ]; } // DatabaseSessionEnd is emitted when a user ends the database session. @@ -1782,6 +1784,22 @@ message RenewableCertificateGenerationMismatch { [ (gogoproto.nullable) = false, (gogoproto.embed) = true, (gogoproto.jsontag) = "" ]; } +// Unknown is a fallback event used when we don't recognize an event from the backend. +message Unknown { + // Metadata is a common event metadata. + Metadata Metadata = 1 + [ (gogoproto.nullable) = false, (gogoproto.embed) = true, (gogoproto.jsontag) = "" ]; + + // UnknownType is the event type extracted from the unknown event. + string UnknownType = 2 [ (gogoproto.jsontag) = "unknown_event" ]; + + // UnknownCode is the event code extracted from the unknown event. + string UnknownCode = 3 [ (gogoproto.jsontag) = "unknown_code,omitempty" ]; + + // Data is the serialized JSON data of the unknown event. + string Data = 4 [ (gogoproto.jsontag) = "data" ]; +} + // OneOf is a union of one of audit events submitted to the auth service message OneOf { // Event is one of the audit events @@ -1865,6 +1883,7 @@ message OneOf { events.MySQLStatementFetch MySQLStatementFetch = 77; events.MySQLStatementBulkExecute MySQLStatementBulkExecute = 78; events.RenewableCertificateGenerationMismatch RenewableCertificateGenerationMismatch = 79; + events.Unknown Unknown = 80; } } diff --git a/api/types/events/oneof.go b/api/types/events/oneof.go index 5d3476d536e9d..8181e7c98bcc0 100644 --- a/api/types/events/oneof.go +++ b/api/types/events/oneof.go @@ -17,9 +17,11 @@ limitations under the License. package events import ( + "encoding/json" "reflect" "github.com/gravitational/trace" + log "github.com/sirupsen/logrus" ) // MustToOneOf converts audit event to OneOf @@ -353,8 +355,28 @@ func ToOneOf(in AuditEvent) (*OneOf, error) { out.Event = &OneOf_RenewableCertificateGenerationMismatch{ RenewableCertificateGenerationMismatch: e, } + case *Unknown: + out.Event = &OneOf_Unknown{ + Unknown: e, + } default: - return nil, trace.BadParameter("event type %T is not supported", in) + log.Errorf("Attempted to convert dynamic event of unknown type \"%v\" into protobuf event.", in.GetType()) + unknown := &Unknown{} + unknown.Type = UnknownEvent + unknown.Code = UnknownCode + unknown.Time = in.GetTime() + unknown.ClusterName = in.GetClusterName() + unknown.UnknownType = in.GetType() + unknown.UnknownCode = in.GetCode() + data, err := json.Marshal(in) + if err != nil { + return nil, trace.Wrap(err) + } + + unknown.Data = string(data) + out.Event = &OneOf_Unknown{ + Unknown: unknown, + } } return &out, nil } diff --git a/api/types/events/unknown.go b/api/types/events/unknown.go new file mode 100644 index 0000000000000..d2f55cacd24a4 --- /dev/null +++ b/api/types/events/unknown.go @@ -0,0 +1,25 @@ +/* +Copyright 2021 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package events + +const ( + // UnknownEvent is any event received that isn't recognized as any other event type. + UnknownEvent = "unknown" + + // UnknownCode is used when an event of unknown type is encountered. + UnknownCode = "TCC00E" +) diff --git a/api/types/kubernetes.go b/api/types/kubernetes.go index 13e5c7545c946..511f527defa29 100644 --- a/api/types/kubernetes.go +++ b/api/types/kubernetes.go @@ -18,6 +18,7 @@ package types import ( "fmt" + "sort" "time" "github.com/gogo/protobuf/proto" @@ -25,6 +26,30 @@ import ( "github.com/gravitational/trace" ) +// KubeCluster represents a kubernetes cluster. +type KubeCluster interface { + // ResourceWithLabels provides common resource methods. + ResourceWithLabels + // GetNamespace returns the kube cluster namespace. + GetNamespace() string + // GetStaticLabels returns the kube cluster static labels. + GetStaticLabels() map[string]string + // SetStaticLabels sets the kube cluster static labels. + SetStaticLabels(map[string]string) + // GetDynamicLabels returns the kube cluster dynamic labels. + GetDynamicLabels() map[string]CommandLabel + // SetDynamicLabels sets the kube cluster dynamic labels. + SetDynamicLabels(map[string]CommandLabel) + // LabelsString returns all labels as a string. + LabelsString() string + // String returns string representation of the kube cluster. + String() string + // GetDescription returns the kube cluster description. + GetDescription() string + // Copy returns a copy of this kube cluster resource. + Copy() *KubernetesClusterV3 +} + // NewKubernetesClusterV3FromLegacyCluster creates a new Kubernetes cluster resource // from the legacy type. func NewKubernetesClusterV3FromLegacyCluster(namespace string, cluster *KubernetesCluster) (*KubernetesClusterV3, error) { @@ -192,3 +217,76 @@ func (k *KubernetesClusterV3) CheckAndSetDefaults() error { return nil } + +// KubeClusters represents a list of kube clusters. +type KubeClusters []KubeCluster + +// Len returns the slice length. +func (s KubeClusters) Len() int { return len(s) } + +// Less compares kube clusters by name. +func (s KubeClusters) Less(i, j int) bool { + return s[i].GetName() < s[j].GetName() +} + +// Swap swaps two kube clusters. +func (s KubeClusters) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// SortByCustom custom sorts by given sort criteria. +func (s KubeClusters) SortByCustom(sortBy SortBy) error { + if sortBy.Field == "" { + return nil + } + + isDesc := sortBy.IsDesc + switch sortBy.Field { + case ResourceMetadataName: + sort.SliceStable(s, func(i, j int) bool { + return stringCompare(s[i].GetName(), s[j].GetName(), isDesc) + }) + default: + return trace.NotImplemented("sorting by field %q for resource %q is not supported", sortBy.Field, KindKubernetesCluster) + } + + return nil +} + +// AsResources returns as type resources with labels. +func (s KubeClusters) AsResources() []ResourceWithLabels { + resources := make([]ResourceWithLabels, 0, len(s)) + for _, cluster := range s { + resources = append(resources, ResourceWithLabels(cluster)) + } + return resources +} + +// GetFieldVals returns list of select field values. +func (s KubeClusters) GetFieldVals(field string) ([]string, error) { + vals := make([]string, 0, len(s)) + switch field { + case ResourceMetadataName: + for _, server := range s { + vals = append(vals, server.GetName()) + } + default: + return nil, trace.NotImplemented("getting field %q for resource %q is not supported", field, KindKubernetesCluster) + } + + return vals, nil +} + +// DeduplicateKubeClusters deduplicates kube clusters by name. +func DeduplicateKubeClusters(kubeclusters []KubeCluster) []KubeCluster { + seen := make(map[string]struct{}) + result := make([]KubeCluster, 0, len(kubeclusters)) + + for _, cluster := range kubeclusters { + if _, ok := seen[cluster.GetName()]; ok { + continue + } + seen[cluster.GetName()] = struct{}{} + result = append(result, cluster) + } + + return result +} diff --git a/api/types/kubernetes_test.go b/api/types/kubernetes_test.go new file mode 100644 index 0000000000000..2931ebf626a0f --- /dev/null +++ b/api/types/kubernetes_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import ( + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" +) + +func TestKubeClustersSorter(t *testing.T) { + t.Parallel() + + makeClusters := func(testVals []string, testField string) []KubeCluster { + servers := make([]KubeCluster, len(testVals)) + for i := 0; i < len(testVals); i++ { + var err error + servers[i], err = NewKubernetesClusterV3FromLegacyCluster("_", &KubernetesCluster{ + Name: testVals[i], + }) + require.NoError(t, err) + } + return servers + } + + testValsUnordered := []string{"d", "b", "a", "c"} + + // Test descending. + sortBy := SortBy{Field: ResourceMetadataName, IsDesc: true} + clusters := KubeClusters(makeClusters(testValsUnordered, ResourceMetadataName)) + require.NoError(t, clusters.SortByCustom(sortBy)) + targetVals, err := clusters.GetFieldVals(ResourceMetadataName) + require.NoError(t, err) + require.IsDecreasing(t, targetVals) + + // Test ascending. + sortBy = SortBy{Field: ResourceMetadataName} + clusters = KubeClusters(makeClusters(testValsUnordered, ResourceMetadataName)) + require.NoError(t, clusters.SortByCustom(sortBy)) + targetVals, err = clusters.GetFieldVals(ResourceMetadataName) + require.NoError(t, err) + require.IsIncreasing(t, targetVals) + + // Test error. + sortBy = SortBy{Field: "unsupported"} + clusters = KubeClusters(makeClusters(testValsUnordered, ResourceMetadataName)) + require.True(t, trace.IsNotImplemented(clusters.SortByCustom(sortBy))) +} + +func TestDeduplicateKubeClusters(t *testing.T) { + t.Parallel() + + expected := []KubeCluster{ + &KubernetesClusterV3{Metadata: Metadata{Name: "a"}}, + &KubernetesClusterV3{Metadata: Metadata{Name: "b"}}, + &KubernetesClusterV3{Metadata: Metadata{Name: "c"}}, + } + + extra := []KubeCluster{ + &KubernetesClusterV3{Metadata: Metadata{Name: "a"}}, + &KubernetesClusterV3{Metadata: Metadata{Name: "a"}}, + &KubernetesClusterV3{Metadata: Metadata{Name: "b"}}, + } + + clusters := append(expected, extra...) + + result := DeduplicateKubeClusters(clusters) + require.ElementsMatch(t, result, expected) +} diff --git a/api/types/resource.go b/api/types/resource.go index ef52ae8ca0f01..65c18167cff4c 100644 --- a/api/types/resource.go +++ b/api/types/resource.go @@ -90,14 +90,21 @@ type ResourceWithLabels interface { // ResourcesWithLabels is a list of labeled resources. type ResourcesWithLabels []ResourceWithLabels -// Find returns resource with the specified name or nil. -func (r ResourcesWithLabels) Find(name string) ResourceWithLabels { - for _, resource := range r { - if resource.GetName() == name { - return resource - } +// ResourcesWithLabelsMap is like ResourcesWithLabels, but a map from resource name to its value. +type ResourcesWithLabelsMap map[string]ResourceWithLabels + +// ToMap returns these databases as a map keyed by database name. +func (r ResourcesWithLabels) ToMap() ResourcesWithLabelsMap { + rm := make(ResourcesWithLabelsMap, len(r)) + + // there may be duplicate resources in the input list. + // by iterating from end to start, the first resource of given name wins. + for i := len(r) - 1; i >= 0; i-- { + resource := r[i] + rm[resource.GetName()] = resource } - return nil + + return rm } // Len returns the slice length. @@ -148,6 +155,32 @@ func (r ResourcesWithLabels) AsDatabaseServers() ([]DatabaseServer, error) { return dbs, nil } +// AsWindowsDesktops converts each resource into type WindowsDesktop. +func (r ResourcesWithLabels) AsWindowsDesktops() ([]WindowsDesktop, error) { + desktops := make([]WindowsDesktop, 0, len(r)) + for _, resource := range r { + desktop, ok := resource.(WindowsDesktop) + if !ok { + return nil, trace.BadParameter("expected types.WindowsDesktop, got: %T", resource) + } + desktops = append(desktops, desktop) + } + return desktops, nil +} + +// AsKubeClusters converts each resource into type KubeCluster. +func (r ResourcesWithLabels) AsKubeClusters() ([]KubeCluster, error) { + clusters := make([]KubeCluster, 0, len(r)) + for _, resource := range r { + cluster, ok := resource.(KubeCluster) + if !ok { + return nil, trace.BadParameter("expected types.KubeCluster, got: %T", resource) + } + clusters = append(clusters, cluster) + } + return clusters, nil +} + // GetVersion returns resource version func (h *ResourceHeader) GetVersion() string { return h.Version diff --git a/api/types/resource_test.go b/api/types/resource_test.go index b0f9c9bb4146a..9dd7c9e88b80f 100644 --- a/api/types/resource_test.go +++ b/api/types/resource_test.go @@ -322,3 +322,53 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) { }) } } + +func TestResourcesWithLabels_ToMap(t *testing.T) { + mkServerHost := func(name string, hostname string) ResourceWithLabels { + server, err := NewServerWithLabels(name, KindNode, ServerSpecV2{ + Hostname: hostname + ".example.com", + Addr: name + ".example.com", + }, nil) + require.NoError(t, err) + + return server + } + + mkServer := func(name string) ResourceWithLabels { + return mkServerHost(name, name) + } + + tests := []struct { + name string + r ResourcesWithLabels + want ResourcesWithLabelsMap + }{ + { + name: "empty", + r: nil, + want: map[string]ResourceWithLabels{}, + }, + { + name: "simple list", + r: []ResourceWithLabels{mkServer("a"), mkServer("b"), mkServer("c")}, + want: map[string]ResourceWithLabels{ + "a": mkServer("a"), + "b": mkServer("b"), + "c": mkServer("c"), + }, + }, + { + name: "first duplicate wins", + r: []ResourceWithLabels{mkServerHost("a", "a1"), mkServerHost("a", "a2"), mkServerHost("a", "a3")}, + want: map[string]ResourceWithLabels{ + "a": mkServerHost("a", "a1"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.r.ToMap(), tt.want) + }) + } +} diff --git a/api/types/role.go b/api/types/role.go index 2f4d0ad9af991..0953b122624a7 100644 --- a/api/types/role.go +++ b/api/types/role.go @@ -159,11 +159,11 @@ type Role interface { GetSessionPolicySet() SessionTrackerPolicySet } -// NewRole constructs new standard V3 role. -// This is mostly a legacy function and will create a role with V3 RBAC semantics. +// NewRole constructs new standard V5 role. +// This creates a V5 role with V4+ RBAC semantics. func NewRole(name string, spec RoleSpecV5) (Role, error) { role := RoleV5{ - Version: V3, + Version: V5, Metadata: Metadata{ Name: name, }, @@ -175,11 +175,11 @@ func NewRole(name string, spec RoleSpecV5) (Role, error) { return &role, nil } -// NewRoleV5 constructs new standard V5 role. -// This creates a V5 role with V4+ RBAC semantics. This should be preferred over `NewRole`. -func NewRoleV5(name string, spec RoleSpecV5) (Role, error) { +// NewRoleV3 constructs new standard V3 role. +// This is mostly a legacy function and will create a role with V3 RBAC semantics. +func NewRoleV3(name string, spec RoleSpecV5) (Role, error) { role := RoleV5{ - Version: V5, + Version: V3, Metadata: Metadata{ Name: name, }, @@ -604,11 +604,8 @@ func (r *RoleV5) SetRules(rct RoleConditionType, in []Rule) { // setStaticFields sets static resource header and metadata fields. func (r *RoleV5) setStaticFields() { r.Kind = KindRole - // TODO(Joerger/nklaassen) Role should default to V4 - // but shouldn't overwrite V3. For now, this does the - // opposite due to an internal reliance on V3 defaults. - if r.Version != V4 && r.Version != V5 { - r.Version = V3 + if r.Version != V3 && r.Version != V4 { + r.Version = V5 } } diff --git a/api/types/server_test.go b/api/types/server_test.go index b5de2617ae0e4..4b145d7bb0ad2 100644 --- a/api/types/server_test.go +++ b/api/types/server_test.go @@ -76,8 +76,6 @@ func TestServerSorter(t *testing.T) { for _, c := range cases { c := c t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) { - t.Parallel() - sortBy := SortBy{Field: c.fieldName, IsDesc: true} servers := Servers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) @@ -87,8 +85,6 @@ func TestServerSorter(t *testing.T) { }) t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) { - t.Parallel() - sortBy := SortBy{Field: c.fieldName} servers := Servers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) diff --git a/api/types/types.pb.go b/api/types/types.pb.go index b8185b731b525..29ee65f6c47ad 100644 --- a/api/types/types.pb.go +++ b/api/types/types.pb.go @@ -8156,7 +8156,9 @@ type WindowsDesktopServiceSpecV3 struct { // Addr is the address that this service can be reached at. Addr string `protobuf:"bytes,1,opt,name=Addr,proto3" json:"addr"` // TeleportVersion is teleport binary version running this service. - TeleportVersion string `protobuf:"bytes,2,opt,name=TeleportVersion,proto3" json:"teleport_version"` + TeleportVersion string `protobuf:"bytes,2,opt,name=TeleportVersion,proto3" json:"teleport_version"` + // Hostname is the desktop service hostname. + Hostname string `protobuf:"bytes,3,opt,name=Hostname,proto3" json:"hostname"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -8195,8 +8197,11 @@ func (m *WindowsDesktopServiceSpecV3) XXX_DiscardUnknown() { var xxx_messageInfo_WindowsDesktopServiceSpecV3 proto.InternalMessageInfo +// WindowsDesktopFilter are filters to apply when searching for windows desktops. type WindowsDesktopFilter struct { - HostID string `protobuf:"bytes,1,opt,name=HostID,proto3" json:"host_id"` + // HostID is the ID of the host the Windows Desktop Service proxying the desktop. + HostID string `protobuf:"bytes,1,opt,name=HostID,proto3" json:"host_id"` + // Name is the name of the desktop. Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"name"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -9000,730 +9005,731 @@ func init() { func init() { proto.RegisterFile("types.proto", fileDescriptor_d938547f84707355) } var fileDescriptor_d938547f84707355 = []byte{ - // 11557 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x7d, 0x6d, 0x6c, 0x1c, 0x49, - 0x76, 0x98, 0x7a, 0x66, 0x48, 0xce, 0x3c, 0x0e, 0xc9, 0x61, 0x51, 0x1f, 0x94, 0x56, 0xbb, 0xa3, - 0xeb, 0xfd, 0x92, 0x74, 0xbb, 0xe2, 0x89, 0xba, 0x95, 0xbd, 0xb7, 0x5f, 0x37, 0x43, 0x52, 0x12, - 0x57, 0x14, 0xc9, 0xed, 0xe1, 0xc7, 0x9d, 0xef, 0xce, 0xed, 0xe6, 0x74, 0x89, 0xec, 0xe5, 0xcc, - 0xf4, 0xb8, 0xbb, 0x47, 0x12, 0xed, 0x18, 0x76, 0x10, 0x38, 0x86, 0x61, 0xf8, 0x3e, 0x8c, 0x73, - 0x6c, 0x07, 0x0e, 0xec, 0x18, 0x09, 0x12, 0x27, 0xb8, 0xfc, 0x70, 0x02, 0x24, 0x01, 0x82, 0x00, - 0x06, 0x02, 0xe3, 0x7e, 0x24, 0x88, 0xff, 0x05, 0x76, 0x02, 0x26, 0xbe, 0xcb, 0x1f, 0x13, 0x48, - 0x10, 0x20, 0xbf, 0x7c, 0x89, 0x91, 0xa0, 0x5e, 0x55, 0x75, 0x57, 0xf5, 0xf4, 0x90, 0xc3, 0x5d, - 0x2d, 0x70, 0xd2, 0x2f, 0x72, 0x5e, 0xbd, 0xf7, 0xba, 0x3e, 0x5f, 0xbd, 0x57, 0xf5, 0xde, 0x2b, - 0x18, 0x8f, 0x0e, 0xba, 0x34, 0xbc, 0xd1, 0x0d, 0xfc, 0xc8, 0x27, 0x23, 0xf8, 0xe3, 0xd2, 0xd9, - 0x5d, 0x7f, 0xd7, 0x47, 0xc8, 0x1c, 0xfb, 0x8f, 0x17, 0x5e, 0xaa, 0xee, 0xfa, 0xfe, 0x6e, 0x8b, - 0xce, 0xe1, 0xaf, 0x9d, 0xde, 0xc3, 0xb9, 0xc8, 0x6b, 0xd3, 0x30, 0x72, 0xda, 0x5d, 0x81, 0xb0, - 0xb0, 0xeb, 0x45, 0x7b, 0xbd, 0x9d, 0x1b, 0x4d, 0xbf, 0x3d, 0xb7, 0x1b, 0x38, 0x8f, 0xbc, 0xc8, - 0x89, 0x3c, 0xbf, 0xe3, 0xb4, 0xe6, 0x22, 0xda, 0xa2, 0x5d, 0x3f, 0x88, 0xe6, 0x9c, 0xae, 0x37, - 0x87, 0xdf, 0x98, 0x7b, 0x1c, 0x38, 0xdd, 0x2e, 0x0d, 0x92, 0x7f, 0x38, 0x13, 0xf3, 0xef, 0xe7, - 0xa1, 0x74, 0x9f, 0xd2, 0x6e, 0xad, 0xe5, 0x3d, 0xa2, 0xe4, 0x65, 0x28, 0xac, 0x3a, 0x6d, 0x3a, - 0x6b, 0x5c, 0x31, 0xae, 0x96, 0xea, 0x53, 0x47, 0x87, 0xd5, 0xf1, 0x90, 0x06, 0x8f, 0x68, 0x60, - 0x77, 0x9c, 0x36, 0xb5, 0xb0, 0x90, 0x7c, 0x1e, 0x4a, 0xec, 0x6f, 0xd8, 0x75, 0x9a, 0x74, 0x36, - 0x87, 0x98, 0x13, 0x47, 0x87, 0xd5, 0x52, 0x47, 0x02, 0xad, 0xa4, 0x9c, 0xbc, 0x06, 0x63, 0x2b, - 0xd4, 0x09, 0xe9, 0xf2, 0xe2, 0x6c, 0xfe, 0x8a, 0x71, 0x35, 0x5f, 0x2f, 0x1f, 0x1d, 0x56, 0x8b, - 0x2d, 0x06, 0xb2, 0x3d, 0xd7, 0x92, 0x85, 0x64, 0x19, 0xc6, 0x96, 0x9e, 0x74, 0xbd, 0x80, 0x86, - 0xb3, 0x85, 0x2b, 0xc6, 0xd5, 0xf1, 0xf9, 0x4b, 0x37, 0x78, 0xfb, 0x6f, 0xc8, 0xf6, 0xdf, 0xd8, - 0x90, 0xed, 0xaf, 0xcf, 0x7c, 0xff, 0xb0, 0x7a, 0xe6, 0xe8, 0xb0, 0x3a, 0x46, 0x39, 0xc9, 0xb7, - 0xff, 0x6b, 0xd5, 0xb0, 0x24, 0x3d, 0x79, 0x17, 0x0a, 0x1b, 0x07, 0x5d, 0x3a, 0x5b, 0xba, 0x62, - 0x5c, 0x9d, 0x9c, 0x7f, 0xe9, 0x06, 0xef, 0xf1, 0xb8, 0x91, 0xc9, 0x7f, 0x0c, 0xab, 0x5e, 0x3c, - 0x3a, 0xac, 0x16, 0x18, 0x8a, 0x85, 0x54, 0xe4, 0x4d, 0x18, 0xbd, 0xe7, 0x87, 0xd1, 0xf2, 0xe2, - 0x2c, 0x60, 0xd3, 0xce, 0x1d, 0x1d, 0x56, 0xa7, 0xf7, 0xfc, 0x30, 0xb2, 0x3d, 0xf7, 0x0d, 0xbf, - 0xed, 0x45, 0xb4, 0xdd, 0x8d, 0x0e, 0x2c, 0x81, 0x64, 0xee, 0xc0, 0x84, 0xc6, 0x8f, 0x8c, 0xc3, - 0xd8, 0xe6, 0xea, 0xfd, 0xd5, 0xb5, 0xed, 0xd5, 0xca, 0x19, 0x52, 0x84, 0xc2, 0xea, 0xda, 0xe2, - 0x52, 0xc5, 0x20, 0x63, 0x90, 0xaf, 0xad, 0xaf, 0x57, 0x72, 0xa4, 0x0c, 0xc5, 0xc5, 0xda, 0x46, - 0xad, 0x5e, 0x6b, 0x2c, 0x55, 0xf2, 0x64, 0x06, 0xa6, 0xb6, 0x97, 0x57, 0x17, 0xd7, 0xb6, 0x1b, - 0xf6, 0xe2, 0x52, 0xe3, 0xfe, 0xc6, 0xda, 0x7a, 0xa5, 0x40, 0x26, 0x01, 0xee, 0x6f, 0xd6, 0x97, - 0xac, 0xd5, 0xa5, 0x8d, 0xa5, 0x46, 0x65, 0xc4, 0xfc, 0x95, 0x3c, 0x14, 0x1f, 0xd0, 0xc8, 0x71, - 0x9d, 0xc8, 0x21, 0x97, 0xb5, 0x21, 0xc2, 0xda, 0x2b, 0x63, 0xf3, 0x72, 0xff, 0xd8, 0x8c, 0x1c, - 0x1d, 0x56, 0x8d, 0x37, 0xd5, 0x31, 0x79, 0x07, 0xc6, 0x17, 0x69, 0xd8, 0x0c, 0xbc, 0x2e, 0x9b, - 0x2f, 0x38, 0x2e, 0xa5, 0xfa, 0xc5, 0xa3, 0xc3, 0xea, 0x39, 0x37, 0x01, 0x2b, 0x6d, 0x55, 0xb1, - 0xc9, 0x32, 0x8c, 0xae, 0x38, 0x3b, 0xb4, 0x15, 0xce, 0x8e, 0x5c, 0xc9, 0x5f, 0x1d, 0x9f, 0x7f, - 0x41, 0xf4, 0xaf, 0xac, 0xe0, 0x0d, 0x5e, 0xba, 0xd4, 0x89, 0x82, 0x83, 0xfa, 0xd9, 0xa3, 0xc3, - 0x6a, 0xa5, 0x85, 0x00, 0xb5, 0xef, 0x38, 0x0a, 0x69, 0x24, 0x63, 0x3e, 0x7a, 0xe2, 0x98, 0xbf, - 0xf8, 0xfd, 0xc3, 0xaa, 0xc1, 0xc6, 0x42, 0x8c, 0x79, 0xc2, 0x4f, 0x1f, 0xfd, 0x2b, 0x90, 0x5b, - 0x5e, 0x9c, 0x1d, 0xc3, 0xb9, 0x56, 0x39, 0x3a, 0xac, 0x96, 0xb5, 0x61, 0xcb, 0x2d, 0x2f, 0x5e, - 0x7a, 0x1b, 0xc6, 0x95, 0x3a, 0x92, 0x0a, 0xe4, 0xf7, 0xe9, 0x01, 0xef, 0x4f, 0x8b, 0xfd, 0x4b, - 0xce, 0xc2, 0xc8, 0x23, 0xa7, 0xd5, 0x13, 0x1d, 0x68, 0xf1, 0x1f, 0x5f, 0xca, 0xfd, 0xa4, 0x61, - 0xfe, 0x46, 0x01, 0x8a, 0x96, 0xcf, 0xd7, 0x19, 0xb9, 0x06, 0x23, 0x8d, 0xc8, 0x89, 0xe4, 0x50, - 0xcc, 0x1c, 0x1d, 0x56, 0xa7, 0x42, 0x06, 0x50, 0xbe, 0xc7, 0x31, 0x18, 0xea, 0xfa, 0x9e, 0x13, - 0xca, 0x21, 0x41, 0xd4, 0x2e, 0x03, 0xa8, 0xa8, 0x88, 0x41, 0x5e, 0x83, 0xc2, 0x03, 0xdf, 0xa5, - 0x62, 0x54, 0xc8, 0xd1, 0x61, 0x75, 0xb2, 0xed, 0xbb, 0x2a, 0x22, 0x96, 0x93, 0x37, 0xa0, 0xb4, - 0xd0, 0x0b, 0x02, 0xda, 0x61, 0x53, 0xb5, 0x80, 0xc8, 0x93, 0x47, 0x87, 0x55, 0x68, 0x72, 0x20, - 0x5b, 0x5c, 0x09, 0x02, 0xeb, 0xea, 0x46, 0xe4, 0x04, 0x11, 0x75, 0x67, 0x47, 0x86, 0xea, 0x6a, - 0xb6, 0xbc, 0xa6, 0x43, 0x4e, 0x92, 0xee, 0x6a, 0xc1, 0x89, 0xdc, 0x83, 0xf1, 0xbb, 0x81, 0xd3, - 0xa4, 0xeb, 0x34, 0xf0, 0x7c, 0x17, 0xc7, 0x30, 0x5f, 0x7f, 0xed, 0xe8, 0xb0, 0x7a, 0x7e, 0x97, - 0x81, 0xed, 0x2e, 0xc2, 0x13, 0xea, 0x1f, 0x1d, 0x56, 0x8b, 0x8b, 0xbd, 0x00, 0x7b, 0xcf, 0x52, - 0x49, 0xc9, 0xcf, 0xb0, 0x21, 0x09, 0x23, 0xec, 0x5a, 0xea, 0xe2, 0xe8, 0x1d, 0x5f, 0x45, 0x53, - 0x54, 0xf1, 0x7c, 0xcb, 0x09, 0x23, 0x3b, 0xe0, 0x74, 0xa9, 0x7a, 0xaa, 0x2c, 0xc9, 0x1a, 0x14, - 0x1b, 0xcd, 0x3d, 0xea, 0xf6, 0x5a, 0x74, 0xb6, 0x88, 0xec, 0x2f, 0x88, 0x89, 0x2b, 0xc7, 0x53, - 0x16, 0xd7, 0x2f, 0x09, 0xde, 0x24, 0x14, 0x10, 0xa5, 0xef, 0x63, 0x26, 0x5f, 0x2a, 0xfe, 0xf6, - 0xef, 0x57, 0xcf, 0xfc, 0xd2, 0x7f, 0xb9, 0x72, 0xc6, 0xfc, 0x97, 0x39, 0xa8, 0xa4, 0x99, 0x90, - 0x87, 0x30, 0xb1, 0xd9, 0x75, 0x9d, 0x88, 0x2e, 0xb4, 0x3c, 0xda, 0x89, 0x42, 0x9c, 0x24, 0xc7, - 0xb7, 0xe9, 0x15, 0xf1, 0xdd, 0xd9, 0x1e, 0x12, 0xda, 0x4d, 0x4e, 0x99, 0x6a, 0x95, 0xce, 0x36, - 0xf9, 0x4e, 0x03, 0xe5, 0x74, 0x88, 0x33, 0xec, 0x74, 0xdf, 0xe1, 0x12, 0x7e, 0xc0, 0x77, 0x04, - 0x5b, 0x31, 0x81, 0x3a, 0xee, 0xce, 0x01, 0xce, 0xcc, 0xe1, 0x27, 0x10, 0x23, 0xc9, 0x98, 0x40, - 0x0c, 0x6c, 0xfe, 0x77, 0x03, 0x26, 0x2d, 0x1a, 0xfa, 0xbd, 0xa0, 0x49, 0xef, 0x51, 0xc7, 0xa5, - 0x01, 0x9b, 0xfe, 0xf7, 0xbd, 0x8e, 0x2b, 0xd6, 0x14, 0x4e, 0xff, 0x7d, 0xaf, 0xa3, 0x2e, 0x61, - 0x2c, 0x27, 0x5f, 0x80, 0xb1, 0x46, 0x6f, 0x07, 0x51, 0xf9, 0x9a, 0x3a, 0x8f, 0x23, 0xd6, 0xdb, - 0xb1, 0x53, 0xe8, 0x12, 0x8d, 0xcc, 0xc1, 0xd8, 0x16, 0x0d, 0xc2, 0x44, 0xe2, 0xa1, 0x64, 0x7f, - 0xc4, 0x41, 0x2a, 0x81, 0xc0, 0x22, 0x77, 0x13, 0xa9, 0x2b, 0xf6, 0xa4, 0xa9, 0x94, 0xac, 0x4b, - 0xa6, 0x4a, 0x5b, 0x40, 0xd4, 0xa9, 0x22, 0xb1, 0xcc, 0xef, 0xe4, 0xa0, 0xb2, 0xe8, 0x44, 0xce, - 0x8e, 0x13, 0x8a, 0xfe, 0xdc, 0xba, 0xc5, 0xe4, 0xb8, 0xd2, 0x50, 0x94, 0xe3, 0xac, 0xe6, 0x9f, - 0xb8, 0x79, 0xaf, 0xa6, 0x9b, 0x37, 0xce, 0x36, 0x48, 0xd1, 0xbc, 0xa4, 0x51, 0xef, 0x9d, 0xdc, - 0xa8, 0x8a, 0x68, 0x54, 0x51, 0x36, 0x2a, 0x69, 0x0a, 0x79, 0x0f, 0x0a, 0x8d, 0x2e, 0x6d, 0x0a, - 0x21, 0x22, 0x65, 0xbf, 0xde, 0x38, 0x86, 0xb0, 0x75, 0xab, 0x5e, 0x16, 0x6c, 0x0a, 0x61, 0x97, - 0x36, 0x2d, 0x24, 0x53, 0x16, 0xcd, 0x77, 0x47, 0xe1, 0x6c, 0x16, 0x19, 0x79, 0x4f, 0xdf, 0x9c, - 0x78, 0xf7, 0xbc, 0x30, 0x70, 0x73, 0x9a, 0x35, 0xf4, 0xed, 0xe9, 0x3a, 0x14, 0xd7, 0xd9, 0x84, - 0x6c, 0xfa, 0x2d, 0xd1, 0x73, 0x4c, 0x2a, 0x16, 0xbb, 0x12, 0x66, 0x58, 0x71, 0x39, 0x79, 0x01, - 0xf2, 0x9b, 0xd6, 0xb2, 0xe8, 0xae, 0xd2, 0xd1, 0x61, 0x35, 0xdf, 0x0b, 0xbc, 0x59, 0xc3, 0x62, - 0x50, 0x32, 0x07, 0xa3, 0x0b, 0xb5, 0x05, 0x1a, 0x44, 0xd8, 0x4d, 0xe5, 0xfa, 0x05, 0x36, 0x5b, - 0x9a, 0x8e, 0xdd, 0xa4, 0x41, 0xa4, 0x7d, 0x5e, 0xa0, 0x91, 0xcf, 0x43, 0xbe, 0xb6, 0xdd, 0x10, - 0x3d, 0x03, 0xa2, 0x67, 0x6a, 0xdb, 0x8d, 0xfa, 0x84, 0xe8, 0x88, 0xbc, 0xf3, 0x38, 0x64, 0xdc, - 0x6b, 0xdb, 0x0d, 0x75, 0xb4, 0x46, 0x8f, 0x19, 0xad, 0xab, 0x50, 0x64, 0x7a, 0x06, 0xdb, 0xe0, - 0x51, 0x28, 0x96, 0xb8, 0xfa, 0xb4, 0x27, 0x60, 0x56, 0x5c, 0x4a, 0x5e, 0x8e, 0xd5, 0x96, 0x62, - 0xc2, 0x4f, 0xa8, 0x2d, 0x52, 0x59, 0x21, 0x4f, 0x60, 0x62, 0xf1, 0xa0, 0xe3, 0xb4, 0xbd, 0xa6, - 0xd8, 0xc2, 0x4b, 0xb8, 0x85, 0xdf, 0x38, 0x66, 0x18, 0x6f, 0x68, 0x04, 0x7c, 0x57, 0x97, 0xc2, - 0x77, 0xd6, 0xe5, 0x65, 0x76, 0x7a, 0x87, 0x9f, 0x35, 0x2c, 0xfd, 0x43, 0x6c, 0x2d, 0x49, 0x11, - 0x89, 0x7a, 0x55, 0x32, 0xed, 0x24, 0x38, 0x59, 0x4b, 0x81, 0x80, 0xa8, 0x6b, 0x29, 0xde, 0x74, - 0xdf, 0x83, 0xfc, 0xdd, 0x85, 0xf5, 0xd9, 0x71, 0xe4, 0x41, 0x04, 0x8f, 0xbb, 0x0b, 0xeb, 0x0b, - 0x2d, 0xbf, 0xe7, 0x36, 0x3e, 0x5a, 0xa9, 0x5f, 0x10, 0x6c, 0x26, 0x76, 0x9b, 0x5d, 0xad, 0x46, - 0x8c, 0x8e, 0x2c, 0x41, 0x51, 0xb6, 0x72, 0xb6, 0x8c, 0x3c, 0xa6, 0x53, 0x8d, 0xdf, 0xba, 0xc5, - 0xd7, 0x9a, 0x2b, 0x7e, 0xab, 0xb5, 0x90, 0x38, 0x97, 0xb6, 0x81, 0xf4, 0xf7, 0x4b, 0x86, 0x26, - 0xf1, 0x79, 0x55, 0x93, 0x18, 0x9f, 0x3f, 0x27, 0xbe, 0xb5, 0xe0, 0xb7, 0xdb, 0x4e, 0xc7, 0x45, - 0xda, 0xad, 0x79, 0x55, 0xc1, 0xa8, 0xc1, 0x64, 0x52, 0x91, 0x15, 0x2f, 0x8c, 0xc8, 0x1c, 0x94, - 0x24, 0x84, 0x6d, 0x22, 0xf9, 0xcc, 0x2a, 0x5b, 0x09, 0x8e, 0xf9, 0x27, 0x39, 0x80, 0xa4, 0xe4, - 0x19, 0x95, 0x33, 0x3f, 0xa1, 0xc9, 0x99, 0x73, 0xe9, 0x09, 0x3a, 0x50, 0xc2, 0x90, 0x0f, 0x60, - 0x94, 0xa9, 0x5c, 0x3d, 0xa9, 0x52, 0x5e, 0x48, 0x93, 0x62, 0xe1, 0xd6, 0xad, 0xfa, 0xa4, 0x20, - 0x1e, 0x0d, 0x11, 0x62, 0x09, 0x32, 0x45, 0x44, 0xfd, 0xcf, 0x42, 0x32, 0x18, 0x42, 0x38, 0x5d, - 0x55, 0xa4, 0x8b, 0x91, 0xac, 0x47, 0x29, 0x5d, 0x14, 0xd9, 0x72, 0x91, 0xcb, 0x16, 0xde, 0xa9, - 0x63, 0x42, 0xb6, 0xa4, 0x25, 0x0b, 0xef, 0xc0, 0x13, 0x25, 0x4b, 0x37, 0xbd, 0x6c, 0x0b, 0x38, - 0x0d, 0xae, 0x66, 0xf6, 0x4a, 0xd6, 0x82, 0xbd, 0x72, 0xd2, 0x82, 0x4d, 0x2f, 0xd7, 0x5b, 0x83, - 0x64, 0xd9, 0x39, 0xb9, 0xba, 0x9c, 0xc7, 0x2a, 0x39, 0xca, 0xb4, 0x77, 0xf8, 0xd2, 0x1c, 0x1d, - 0xb8, 0x34, 0xcf, 0x65, 0x2e, 0x4d, 0xbe, 0x30, 0xdf, 0x81, 0x91, 0xda, 0xcf, 0xf5, 0x02, 0x2a, - 0x74, 0xbf, 0xb2, 0xfc, 0x26, 0x83, 0xc5, 0x6b, 0x7a, 0xca, 0x61, 0x3f, 0x55, 0x9d, 0x19, 0xcb, - 0xd9, 0x97, 0x37, 0x56, 0x1a, 0x42, 0xaf, 0x23, 0xa9, 0x6e, 0xd9, 0x58, 0x51, 0xaa, 0x1d, 0x69, - 0xad, 0x66, 0x54, 0x64, 0x0e, 0x72, 0xb5, 0x45, 0x34, 0x16, 0xc7, 0xe7, 0x4b, 0xf2, 0xb3, 0x8b, - 0xf5, 0xb3, 0x82, 0xa4, 0xec, 0x68, 0xf6, 0x43, 0x6d, 0xf1, 0xb3, 0x5b, 0xfc, 0x2d, 0x45, 0x4d, - 0x10, 0xd3, 0x94, 0x99, 0xa3, 0x62, 0xb2, 0x18, 0x89, 0xd2, 0xd2, 0x37, 0x59, 0xe2, 0xa9, 0x72, - 0x8d, 0x0f, 0x5c, 0xae, 0x6f, 0xe0, 0xc6, 0x95, 0x4d, 0x08, 0x87, 0xcb, 0xfc, 0x4b, 0x03, 0x71, - 0xc9, 0x1b, 0x30, 0x6a, 0xd1, 0xdd, 0x64, 0xaf, 0x45, 0x9b, 0x2d, 0x40, 0x88, 0xfa, 0x01, 0x8e, - 0x83, 0x82, 0x9c, 0xba, 0xe1, 0x9e, 0xf7, 0x30, 0x12, 0x5f, 0x89, 0x05, 0xb9, 0x00, 0x2b, 0x82, - 0x5c, 0x40, 0x34, 0x41, 0x2e, 0x60, 0x6c, 0x8a, 0x59, 0x8b, 0x0d, 0xa1, 0x4c, 0xca, 0x9a, 0x5a, - 0x8b, 0xca, 0x58, 0x05, 0xae, 0x36, 0x56, 0xd6, 0x62, 0x83, 0xdc, 0x86, 0x52, 0xad, 0xd9, 0xf4, - 0x7b, 0x8a, 0xd1, 0x33, 0x7b, 0x74, 0x58, 0x3d, 0xeb, 0x70, 0xa0, 0x6e, 0xa2, 0x27, 0xa8, 0x66, - 0x3d, 0xa9, 0x35, 0xe3, 0xb1, 0xd0, 0xea, 0x85, 0x11, 0x0d, 0x96, 0x17, 0x45, 0x93, 0x91, 0x47, - 0x93, 0x03, 0x53, 0x3c, 0x62, 0x54, 0xf3, 0x3f, 0x1b, 0x58, 0x63, 0xf2, 0x36, 0xc0, 0x72, 0x87, - 0x29, 0xb6, 0x4d, 0x1a, 0x33, 0x40, 0xe3, 0xd9, 0x13, 0x50, 0x9d, 0x83, 0x82, 0xac, 0x7f, 0x3a, - 0x37, 0xf4, 0xa7, 0xd9, 0x27, 0xa5, 0x9a, 0x2c, 0xce, 0x51, 0xc4, 0x27, 0x03, 0x01, 0x4d, 0x7d, - 0x32, 0x41, 0x26, 0xaf, 0xc1, 0xd8, 0x72, 0xed, 0x41, 0xad, 0x17, 0xed, 0x61, 0x7f, 0x15, 0xb9, - 0xc0, 0xf2, 0x9c, 0xb6, 0xed, 0xf4, 0xa2, 0x3d, 0x4b, 0x16, 0x9a, 0xbf, 0x64, 0xc0, 0xb8, 0xb2, - 0x56, 0x59, 0x55, 0xd7, 0x03, 0xff, 0x63, 0xda, 0x8c, 0xf4, 0x5e, 0xea, 0x72, 0x60, 0xaa, 0xaa, - 0x31, 0x6a, 0xaa, 0x77, 0x72, 0xa7, 0xe8, 0x1d, 0x73, 0x4e, 0x88, 0x00, 0x66, 0x03, 0x28, 0x47, - 0x1c, 0x68, 0x03, 0x30, 0x1d, 0x47, 0xb5, 0x01, 0x58, 0xb9, 0xf9, 0x87, 0x06, 0x5b, 0xba, 0x64, - 0x0e, 0xe0, 0x3e, 0x3d, 0x88, 0x9c, 0x9d, 0x3b, 0x5e, 0x4b, 0x3b, 0xba, 0xda, 0x47, 0xa8, 0xfd, - 0xd0, 0x6b, 0x51, 0x4b, 0x41, 0x21, 0xb7, 0xa0, 0x78, 0x3f, 0xd8, 0x79, 0x0b, 0xd1, 0x73, 0xb1, - 0x08, 0x9e, 0xd9, 0x0f, 0x76, 0xde, 0x42, 0x64, 0x75, 0xbe, 0x4a, 0x44, 0x62, 0xc2, 0xe8, 0xa2, - 0xdf, 0x76, 0x3c, 0xb9, 0xed, 0x01, 0xdb, 0x3b, 0x5c, 0x84, 0x58, 0xa2, 0x84, 0x09, 0xfd, 0xc6, - 0xfa, 0xaa, 0x98, 0x98, 0x28, 0xf4, 0xc3, 0x6e, 0xc7, 0x62, 0x30, 0xf3, 0x7b, 0x06, 0x8c, 0x2b, - 0x12, 0x89, 0x7c, 0x51, 0x98, 0xf9, 0x06, 0x1e, 0x52, 0x9d, 0xef, 0x97, 0x59, 0xac, 0x94, 0x6f, - 0xd7, 0xcc, 0xfc, 0x17, 0x46, 0x7f, 0x22, 0x0d, 0x72, 0xc3, 0x48, 0x83, 0xb7, 0x01, 0xb8, 0x2e, - 0x87, 0xdd, 0xa9, 0xcc, 0x1b, 0xe5, 0x50, 0x4f, 0x1d, 0x8c, 0x04, 0xd9, 0xfc, 0x9b, 0x39, 0x28, - 0x0a, 0x5b, 0x65, 0xfe, 0x19, 0xd5, 0x21, 0xde, 0xd2, 0x74, 0x88, 0x19, 0x41, 0xaa, 0x28, 0xb7, - 0xf3, 0x27, 0xd8, 0x28, 0x6f, 0x43, 0x59, 0x76, 0x01, 0xaa, 0x62, 0xd7, 0x60, 0x4c, 0x5a, 0xd9, - 0x5c, 0x11, 0x9b, 0xd2, 0x78, 0x6e, 0xcd, 0x5b, 0xb2, 0xdc, 0xfc, 0xce, 0x88, 0xa4, 0xe5, 0x5f, - 0x62, 0x5d, 0x58, 0x73, 0xdd, 0x40, 0xed, 0x42, 0xc7, 0x75, 0x03, 0x0b, 0xa1, 0x6c, 0xa0, 0xd6, - 0x7b, 0x3b, 0x2d, 0xaf, 0x89, 0x38, 0xca, 0xaa, 0xe9, 0x22, 0xd4, 0x66, 0xa8, 0xea, 0x40, 0x25, - 0xc8, 0x9a, 0x89, 0x90, 0x3f, 0xd6, 0x44, 0xf8, 0x69, 0x28, 0x2d, 0xb4, 0x5d, 0x4d, 0x85, 0x30, - 0x33, 0x3a, 0xe5, 0x46, 0x8c, 0xc4, 0x95, 0x87, 0xcb, 0xa2, 0x8f, 0xce, 0x36, 0xdb, 0x6e, 0xbf, - 0xe2, 0x90, 0xb0, 0xd4, 0x74, 0xfc, 0x91, 0x4f, 0xa3, 0xe3, 0xdf, 0x86, 0xd2, 0x66, 0x48, 0x37, - 0x7a, 0x9d, 0x0e, 0x6d, 0xa1, 0x3a, 0x51, 0xe4, 0xb2, 0xa7, 0x17, 0x52, 0x3b, 0x42, 0xa8, 0x5a, - 0x81, 0x18, 0x55, 0x9d, 0x56, 0x63, 0xc7, 0x4c, 0xab, 0x2f, 0x42, 0xa1, 0xd6, 0xed, 0x4a, 0xe3, - 0x27, 0xde, 0x24, 0xbb, 0x5d, 0xdc, 0xfa, 0x26, 0x9d, 0x6e, 0x57, 0x37, 0x65, 0x10, 0x9b, 0x50, - 0x20, 0xf7, 0x7b, 0x3b, 0x34, 0xe8, 0xd0, 0x88, 0x86, 0x42, 0x34, 0x87, 0xb3, 0x80, 0x3c, 0x66, - 0xe5, 0x19, 0x73, 0x1a, 0x01, 0x0d, 0xd7, 0x0b, 0xfb, 0xbd, 0x1d, 0x6a, 0x0b, 0x19, 0xaf, 0xf6, - 0x5d, 0x06, 0xc3, 0x4b, 0x0d, 0x98, 0xd4, 0xfb, 0xff, 0x29, 0x28, 0x16, 0x1f, 0x16, 0x8a, 0xc5, - 0x4a, 0xc9, 0xfc, 0x95, 0x1c, 0x8c, 0xd7, 0xba, 0xdd, 0x67, 0xfc, 0x04, 0xe2, 0x27, 0xb5, 0x55, - 0x7d, 0x3e, 0x19, 0xbd, 0x53, 0x1c, 0x3e, 0xfc, 0x95, 0x01, 0x53, 0x29, 0x0a, 0xb5, 0xf6, 0xc6, - 0x90, 0x16, 0x79, 0x6e, 0x48, 0x8b, 0x3c, 0x3f, 0xd8, 0x22, 0x57, 0xd7, 0x4c, 0xe1, 0xd3, 0xac, - 0x99, 0xd7, 0x21, 0x5f, 0xeb, 0x76, 0x45, 0xaf, 0x94, 0x93, 0x5e, 0xd9, 0xba, 0xc5, 0x37, 0x22, - 0xa7, 0xdb, 0xb5, 0x18, 0x86, 0xf9, 0x26, 0x94, 0x10, 0x8c, 0x12, 0xed, 0x8a, 0x58, 0x0a, 0x5c, - 0x9c, 0x69, 0x64, 0x7c, 0xda, 0x9b, 0xff, 0xc7, 0x80, 0x11, 0xfc, 0xfd, 0x8c, 0x4e, 0x97, 0x79, - 0x6d, 0xba, 0x54, 0x94, 0xe9, 0x32, 0xcc, 0x44, 0xf9, 0xa3, 0x3c, 0xf6, 0x96, 0x98, 0x22, 0xc2, - 0xa6, 0x33, 0x32, 0x6c, 0xba, 0x4f, 0x21, 0xc0, 0xf7, 0xd3, 0xd6, 0x5d, 0x1e, 0x07, 0xe3, 0xe5, - 0x74, 0x55, 0x9f, 0x8a, 0x61, 0x77, 0x0f, 0xc8, 0x72, 0x27, 0xa4, 0xcd, 0x5e, 0x40, 0x1b, 0xfb, - 0x5e, 0x77, 0x8b, 0x06, 0xde, 0xc3, 0x03, 0xa1, 0x19, 0xa2, 0x8c, 0xf5, 0x44, 0xa9, 0x1d, 0xee, - 0x7b, 0x5d, 0xfb, 0x11, 0x96, 0x5b, 0x19, 0x34, 0xe4, 0x03, 0x18, 0xb3, 0xe8, 0xe3, 0xc0, 0x8b, - 0xa8, 0xe8, 0xdb, 0xc9, 0xd8, 0x0e, 0x40, 0x28, 0xd7, 0x4d, 0x02, 0xfe, 0x43, 0x1d, 0x7f, 0x51, - 0xfe, 0xd9, 0x99, 0x51, 0xdf, 0x1d, 0xc1, 0xb5, 0x70, 0xc2, 0x4d, 0xd9, 0x31, 0x06, 0xba, 0x3e, - 0x98, 0xf9, 0xd3, 0x0c, 0xe6, 0x16, 0x94, 0x99, 0xe9, 0x96, 0xb2, 0xd4, 0x2f, 0x27, 0x63, 0x79, - 0x43, 0x2d, 0x3e, 0xee, 0x92, 0x4c, 0xe3, 0x43, 0xec, 0xf4, 0x24, 0xe1, 0x97, 0x6f, 0x2f, 0x2a, - 0x8c, 0x33, 0xa6, 0x47, 0x2c, 0x3a, 0x9a, 0xbc, 0xb3, 0x4e, 0x3d, 0x31, 0x46, 0x3f, 0xdd, 0xc4, - 0x18, 0xfb, 0x24, 0x13, 0x23, 0x7d, 0x3d, 0x59, 0x3c, 0xcd, 0xf5, 0xe4, 0xa5, 0x0f, 0x60, 0xba, - 0xaf, 0x87, 0x4f, 0x73, 0xc5, 0xf7, 0xd9, 0x4d, 0xcb, 0x5f, 0x88, 0xfb, 0x85, 0xcc, 0xa3, 0x39, - 0xea, 0x05, 0xb4, 0x19, 0xa1, 0xe8, 0x15, 0xd2, 0x32, 0x10, 0xb0, 0x94, 0xbd, 0x8c, 0x30, 0xf2, - 0x3e, 0x8c, 0xf1, 0x2b, 0x92, 0x70, 0x36, 0x87, 0x63, 0x3f, 0x21, 0xbe, 0xc8, 0xa1, 0xe2, 0x9e, - 0x9a, 0x63, 0xa8, 0xbd, 0x2a, 0x88, 0xcc, 0xbb, 0x30, 0x2a, 0xae, 0x58, 0x8e, 0x5f, 0x17, 0x55, - 0x18, 0xd9, 0x4a, 0x7a, 0x06, 0x8f, 0xc5, 0x79, 0x23, 0x2c, 0x0e, 0x37, 0x7f, 0xcd, 0x80, 0x49, - 0xbd, 0x95, 0xe4, 0x06, 0x8c, 0x8a, 0x3b, 0x40, 0x03, 0xef, 0x00, 0x59, 0x6b, 0x46, 0xf9, 0xed, - 0x9f, 0x76, 0xe7, 0x27, 0xb0, 0x98, 0xe8, 0x17, 0x1c, 0xb0, 0x2d, 0x42, 0xf4, 0x8b, 0x49, 0x6a, - 0xc9, 0x32, 0x66, 0x72, 0x59, 0x34, 0xec, 0xb5, 0x22, 0xd5, 0xe4, 0x0a, 0x10, 0x62, 0x89, 0x12, - 0xf3, 0xd0, 0x00, 0x68, 0x34, 0xee, 0xdd, 0xa7, 0x07, 0xeb, 0x8e, 0x17, 0xa0, 0xd9, 0x8a, 0xab, - 0xf1, 0xbe, 0x18, 0xad, 0xb2, 0x30, 0x5b, 0xf9, 0xca, 0xdd, 0xa7, 0x07, 0x9a, 0xd9, 0x2a, 0x51, - 0x71, 0xc9, 0x07, 0xde, 0x23, 0x27, 0xa2, 0x8c, 0x30, 0x87, 0x84, 0x7c, 0xc9, 0x73, 0x68, 0x8a, - 0x52, 0x41, 0x26, 0xdf, 0x80, 0xc9, 0xe4, 0x17, 0x3a, 0x1e, 0xe4, 0xd1, 0xa6, 0x93, 0x33, 0x42, - 0x2f, 0xac, 0xbf, 0x74, 0x74, 0x58, 0xbd, 0xa4, 0x70, 0xb5, 0x19, 0x96, 0xc2, 0x3a, 0xc5, 0xcc, - 0xfc, 0x03, 0x03, 0x60, 0x63, 0xa5, 0x21, 0x1b, 0xf8, 0x1a, 0x14, 0xe2, 0xd3, 0xa0, 0x32, 0xb7, - 0x8d, 0x53, 0xc6, 0x1f, 0x96, 0x93, 0x97, 0x21, 0x9f, 0xb4, 0x64, 0xfa, 0xe8, 0xb0, 0x3a, 0xa1, - 0xb7, 0x80, 0x95, 0x92, 0xbb, 0x30, 0x36, 0x54, 0x9d, 0x71, 0x76, 0x66, 0xd4, 0x55, 0x52, 0xe3, - 0x28, 0x7c, 0xb8, 0xbd, 0xf1, 0xfc, 0x8e, 0xc2, 0xb7, 0x72, 0x30, 0xc5, 0xfa, 0xb5, 0xd6, 0x8b, - 0xf6, 0xfc, 0xc0, 0x8b, 0x0e, 0x9e, 0x59, 0xab, 0xf8, 0x5d, 0x4d, 0x21, 0xba, 0x24, 0xc5, 0x96, - 0xda, 0xb6, 0xa1, 0x8c, 0xe3, 0xbf, 0x18, 0x83, 0x99, 0x0c, 0x2a, 0xf2, 0x86, 0xf0, 0xbe, 0x49, - 0xce, 0x8c, 0xd0, 0xbb, 0xe6, 0x47, 0x87, 0xd5, 0xb2, 0x44, 0xdf, 0x48, 0xbc, 0x6d, 0xe6, 0x61, - 0x5c, 0x98, 0x3e, 0xab, 0x89, 0x46, 0x8d, 0x6e, 0x1b, 0xf2, 0x4c, 0x0c, 0x45, 0x93, 0x8a, 0x44, - 0x6a, 0x50, 0x5e, 0xd8, 0xa3, 0xcd, 0x7d, 0xaf, 0xb3, 0x7b, 0x9f, 0x1e, 0x70, 0x7d, 0xa9, 0x5c, - 0x7f, 0x91, 0x59, 0x5a, 0x4d, 0x01, 0x67, 0x43, 0xaa, 0x1b, 0x71, 0x1a, 0x09, 0x79, 0x1f, 0xc6, - 0x1b, 0xde, 0x6e, 0x47, 0x72, 0x28, 0x20, 0x87, 0xcb, 0x47, 0x87, 0xd5, 0xf3, 0x21, 0x07, 0xf7, - 0x33, 0x50, 0x09, 0xc8, 0x35, 0x18, 0xb1, 0xfc, 0x16, 0xe5, 0xdb, 0xb0, 0xf0, 0xe7, 0x08, 0x18, - 0x40, 0x3d, 0x9b, 0x46, 0x0c, 0x72, 0x0f, 0xc6, 0xd8, 0x3f, 0x0f, 0x9c, 0xee, 0xec, 0x28, 0xca, - 0x6d, 0x12, 0x2b, 0xf8, 0x08, 0xed, 0x7a, 0x9d, 0x5d, 0x55, 0xc7, 0x6f, 0x51, 0xbb, 0xed, 0x74, - 0xb5, 0x7d, 0x91, 0x23, 0x92, 0x2d, 0x18, 0x4f, 0x04, 0x41, 0x38, 0x3b, 0xa6, 0xdd, 0x05, 0x25, - 0x25, 0xf5, 0xcf, 0x09, 0x66, 0x17, 0xa2, 0x56, 0x88, 0x73, 0xbb, 0xcb, 0xf0, 0xf5, 0xc6, 0x28, - 0x8c, 0x34, 0x1b, 0xa4, 0x38, 0xd8, 0x06, 0x31, 0x4e, 0xb4, 0x41, 0x5c, 0x00, 0xd1, 0x49, 0xb5, - 0xd6, 0xae, 0x70, 0xbf, 0xba, 0x36, 0x78, 0x82, 0xdd, 0x48, 0x90, 0x71, 0x4d, 0xf2, 0x93, 0x29, - 0xd1, 0xff, 0x4e, 0x6b, 0x57, 0x3b, 0x99, 0x8a, 0x51, 0x59, 0x37, 0x24, 0xa2, 0x46, 0x5a, 0xe0, - 0xb2, 0x1b, 0x92, 0x92, 0xa4, 0x1b, 0x3e, 0x7e, 0x1c, 0x0d, 0xea, 0x06, 0x85, 0x11, 0x59, 0x05, - 0xa8, 0x35, 0x23, 0xef, 0x11, 0xc5, 0x29, 0x31, 0xae, 0x75, 0xc4, 0x42, 0xed, 0x3e, 0x3d, 0x68, - 0xd0, 0x28, 0xf6, 0x6c, 0x38, 0xe7, 0x20, 0x6a, 0x6a, 0x9a, 0x58, 0x0a, 0x07, 0xd2, 0x85, 0x73, - 0x35, 0xd7, 0xf5, 0xb8, 0x4b, 0xde, 0x46, 0xc0, 0xe6, 0xaf, 0x8b, 0xac, 0xcb, 0xd9, 0xac, 0xaf, - 0x09, 0xd6, 0x9f, 0x73, 0x62, 0x2a, 0x3b, 0xe2, 0x64, 0xe9, 0xcf, 0x64, 0x33, 0x36, 0xd7, 0x60, - 0x52, 0xef, 0x52, 0xdd, 0x19, 0xad, 0x0c, 0x45, 0xab, 0x51, 0xb3, 0x1b, 0xf7, 0x6a, 0x37, 0x2b, - 0x06, 0xa9, 0x40, 0x59, 0xfc, 0x9a, 0xb7, 0xe7, 0xdf, 0xba, 0x5d, 0xc9, 0x69, 0x90, 0xb7, 0x6e, - 0xce, 0x57, 0xf2, 0xe6, 0x1f, 0x19, 0x50, 0x94, 0xf5, 0x23, 0xb7, 0x21, 0xdf, 0x68, 0xdc, 0x4b, - 0x5d, 0x41, 0x26, 0x5b, 0x2f, 0xdf, 0x64, 0xc2, 0x70, 0x4f, 0xdd, 0x64, 0x1a, 0x8d, 0x7b, 0x8c, - 0x6e, 0x63, 0xa5, 0x21, 0x94, 0x96, 0x8c, 0xe9, 0x3a, 0x3d, 0xe0, 0x5e, 0xe6, 0x36, 0xe4, 0x3f, - 0xdc, 0xde, 0x10, 0xd6, 0x50, 0xc6, 0xf8, 0x22, 0xdd, 0xc7, 0x8f, 0xd5, 0xad, 0x8f, 0x11, 0x98, - 0x16, 0x8c, 0x2b, 0x4b, 0x8b, 0x2b, 0x11, 0x6d, 0x3f, 0x76, 0xd3, 0x12, 0x4a, 0x04, 0x83, 0x58, - 0xa2, 0x84, 0xe9, 0x3c, 0x2b, 0x7e, 0xd3, 0x69, 0x09, 0x6d, 0x04, 0x75, 0x9e, 0x16, 0x03, 0x58, - 0x1c, 0x6e, 0xfe, 0xb1, 0x01, 0x95, 0xf5, 0xc0, 0x7f, 0xe4, 0x31, 0x09, 0xbc, 0xe1, 0xef, 0xd3, - 0xce, 0xd6, 0x4d, 0xf2, 0xa6, 0x14, 0x02, 0x5c, 0x85, 0xbb, 0xc0, 0xa8, 0x50, 0x08, 0xfc, 0xe8, - 0xb0, 0x0a, 0x8d, 0x83, 0x30, 0xa2, 0x6d, 0x56, 0x2e, 0x05, 0x81, 0xe2, 0xed, 0x96, 0x1b, 0xde, - 0x83, 0xe6, 0x04, 0x6f, 0xb7, 0x2a, 0x8c, 0x60, 0x75, 0x14, 0x27, 0x86, 0x91, 0x88, 0x01, 0x2c, - 0x0e, 0x57, 0x04, 0xf6, 0x77, 0x72, 0x7d, 0x6d, 0x98, 0x7f, 0xae, 0xbc, 0x50, 0xf4, 0xc6, 0x0d, - 0xb5, 0x89, 0x7d, 0x15, 0xce, 0xa6, 0xbb, 0x04, 0xcf, 0x45, 0x6a, 0x30, 0xa5, 0xc3, 0xe5, 0x11, - 0xc9, 0x85, 0xcc, 0x6f, 0x6d, 0xcd, 0x5b, 0x69, 0x7c, 0xf3, 0x07, 0x06, 0x94, 0xf0, 0x5f, 0xab, - 0xd7, 0xa2, 0x4c, 0xb3, 0xa9, 0x6d, 0x37, 0xc4, 0x85, 0x94, 0x7a, 0x69, 0xe4, 0x3c, 0x0e, 0x6d, - 0x71, 0x7b, 0xa5, 0xc9, 0x91, 0x18, 0x59, 0x90, 0xf2, 0xeb, 0xb7, 0x50, 0xcc, 0xd0, 0x98, 0x94, - 0xdf, 0xd3, 0x85, 0x29, 0x52, 0x81, 0xcc, 0xc6, 0x8f, 0xfd, 0xf2, 0x5b, 0xf2, 0x68, 0x18, 0xc7, - 0x0f, 0xe9, 0x7c, 0xed, 0x9a, 0x43, 0xa2, 0x91, 0x37, 0x61, 0x94, 0x7d, 0xda, 0x92, 0x97, 0x18, - 0x68, 0x55, 0x60, 0x1d, 0x03, 0xed, 0x36, 0x90, 0x23, 0x99, 0xff, 0x2a, 0x97, 0xee, 0x40, 0xa1, - 0x05, 0x9c, 0x72, 0x6d, 0xbc, 0x03, 0x23, 0xb5, 0x56, 0xcb, 0x7f, 0x2c, 0xa4, 0x84, 0x3c, 0xa6, - 0x89, 0xfb, 0x8f, 0xef, 0xb0, 0x0e, 0x43, 0xd1, 0x6e, 0x7f, 0x19, 0x80, 0x2c, 0x40, 0xa9, 0xb6, - 0xdd, 0x58, 0x5e, 0x5e, 0xdc, 0xd8, 0x58, 0x11, 0x4e, 0xc6, 0xaf, 0xca, 0xfe, 0xf1, 0x3c, 0xd7, - 0x8e, 0xa2, 0xd6, 0x00, 0x1f, 0xc4, 0x84, 0x8e, 0xbc, 0x07, 0xf0, 0xa1, 0xef, 0x75, 0x1e, 0xd0, - 0x68, 0xcf, 0x77, 0x45, 0xe3, 0x99, 0x4a, 0x31, 0xfe, 0xb1, 0xef, 0x75, 0xec, 0x36, 0x82, 0x59, - 0xdd, 0x13, 0x24, 0x4b, 0xf9, 0x9f, 0xf5, 0x74, 0xdd, 0x8f, 0x50, 0x87, 0x19, 0x49, 0x7a, 0x7a, - 0xc7, 0x8f, 0xd2, 0x77, 0x2c, 0x12, 0xcd, 0xfc, 0xf5, 0x1c, 0x4c, 0x72, 0x4b, 0x95, 0x4f, 0x98, - 0x67, 0x76, 0x31, 0xbe, 0xa3, 0x2d, 0xc6, 0x8b, 0x72, 0x63, 0x50, 0x9a, 0x36, 0xd4, 0x52, 0xdc, - 0x03, 0xd2, 0x4f, 0x43, 0x2c, 0x79, 0x9e, 0x32, 0xcc, 0x2a, 0xbc, 0x99, 0xdc, 0x1d, 0x87, 0x48, - 0x64, 0xa3, 0x28, 0x0c, 0x2d, 0x8d, 0x87, 0xf9, 0x6b, 0x39, 0x98, 0x50, 0xf4, 0xc9, 0x67, 0xb6, - 0xe3, 0xbf, 0xa4, 0x75, 0xbc, 0xbc, 0x83, 0x50, 0x5a, 0x36, 0x54, 0xbf, 0xf7, 0x60, 0xba, 0x8f, - 0x24, 0xad, 0x96, 0x1b, 0xc3, 0xa8, 0xe5, 0x6f, 0xf4, 0x5f, 0x6e, 0x73, 0x87, 0xe4, 0xf8, 0x72, - 0x5b, 0xbd, 0x4d, 0xff, 0x56, 0x0e, 0xce, 0x8a, 0x5f, 0xb5, 0x9e, 0xeb, 0x45, 0x0b, 0x7e, 0xe7, - 0xa1, 0xb7, 0xfb, 0xcc, 0x8e, 0x45, 0x4d, 0x1b, 0x8b, 0xaa, 0x3e, 0x16, 0x4a, 0x03, 0x07, 0x0f, - 0x89, 0xf9, 0x6f, 0x8a, 0x30, 0x3b, 0x88, 0x80, 0x99, 0xfd, 0x8a, 0x55, 0x85, 0x66, 0x7f, 0xca, - 0x62, 0xe5, 0xf6, 0x54, 0xe2, 0xcc, 0x91, 0x1b, 0xc2, 0x99, 0x63, 0x05, 0x2a, 0xf8, 0xa9, 0x06, - 0x0d, 0x59, 0x27, 0x84, 0x89, 0x37, 0xe4, 0x95, 0xa3, 0xc3, 0xea, 0x65, 0x87, 0x95, 0xd9, 0xa1, - 0x28, 0xb4, 0x7b, 0x81, 0xa7, 0xf0, 0xe8, 0xa3, 0x24, 0x7f, 0x60, 0xc0, 0x24, 0x02, 0x97, 0x1e, - 0xd1, 0x4e, 0x84, 0xcc, 0x0a, 0xe2, 0x92, 0x26, 0x0e, 0x3a, 0x69, 0x44, 0x81, 0xd7, 0xd9, 0xc5, - 0x83, 0xa4, 0xb0, 0xbe, 0xc3, 0x7a, 0xe1, 0xcf, 0x0f, 0xab, 0xef, 0x7e, 0x92, 0x40, 0x16, 0xc1, - 0x2a, 0x64, 0x86, 0x3c, 0xaf, 0x28, 0xc5, 0xcf, 0xa6, 0xaa, 0x99, 0xaa, 0x11, 0xf9, 0x29, 0xb8, - 0xb0, 0xd4, 0x71, 0x76, 0x5a, 0x74, 0xc1, 0xef, 0x44, 0x5e, 0xa7, 0xe7, 0xf7, 0xc2, 0xba, 0xd3, - 0xdc, 0xef, 0x75, 0x43, 0x71, 0xd8, 0x89, 0x2d, 0x6f, 0xc6, 0x85, 0xf6, 0x0e, 0x2f, 0x55, 0x58, - 0x0e, 0x62, 0x40, 0xee, 0xc1, 0x34, 0x2f, 0xaa, 0xf5, 0x22, 0xbf, 0xd1, 0x74, 0x5a, 0x5e, 0x67, - 0x17, 0xcf, 0x40, 0x8b, 0xf5, 0x4b, 0xcc, 0xb6, 0x74, 0x7a, 0x91, 0x6f, 0x87, 0x1c, 0xae, 0xf0, - 0xeb, 0x27, 0x22, 0xcb, 0x30, 0x65, 0x51, 0xc7, 0x7d, 0xe0, 0x3c, 0x59, 0x70, 0xba, 0x4e, 0xd3, - 0x8b, 0x0e, 0xd0, 0x32, 0xcb, 0xd7, 0xab, 0x47, 0x87, 0xd5, 0x17, 0x02, 0xea, 0xb8, 0x76, 0xdb, - 0x79, 0x62, 0x37, 0x45, 0xa1, 0xc2, 0x2c, 0x4d, 0x17, 0xb3, 0xf2, 0x3a, 0x31, 0xab, 0x52, 0x9a, - 0x95, 0xd7, 0x19, 0xcc, 0x2a, 0xa1, 0x93, 0xac, 0x36, 0x9c, 0x60, 0x97, 0x46, 0xfc, 0x90, 0x10, - 0xae, 0x18, 0x57, 0x0d, 0x85, 0x55, 0x84, 0x65, 0x36, 0x1e, 0x18, 0xa6, 0x59, 0x29, 0x74, 0x6c, - 0xe6, 0x6d, 0x07, 0x5e, 0x44, 0xd5, 0x16, 0x8e, 0x63, 0xb5, 0xb0, 0xff, 0xf1, 0x98, 0x74, 0x50, - 0x13, 0xfb, 0x28, 0x13, 0x6e, 0x4a, 0x23, 0xcb, 0x7d, 0xdc, 0xb2, 0x5b, 0xd9, 0x47, 0x19, 0x73, - 0x53, 0xdb, 0x39, 0x81, 0xed, 0x54, 0xb8, 0x0d, 0x68, 0x68, 0x1f, 0x25, 0x59, 0x65, 0x9d, 0x16, - 0xd1, 0x0e, 0x9b, 0xd1, 0xe2, 0x90, 0x74, 0x12, 0xab, 0xf6, 0x8a, 0xb0, 0xa9, 0x2b, 0x81, 0x2c, - 0xb6, 0x33, 0x8e, 0x4c, 0xd3, 0xc4, 0x1f, 0x16, 0x8a, 0x23, 0x95, 0x51, 0xab, 0xc2, 0xa7, 0x7c, - 0xc4, 0x26, 0x0e, 0xca, 0x62, 0xf3, 0x77, 0x72, 0x70, 0x51, 0x8a, 0x63, 0x1a, 0x3d, 0xf6, 0x83, - 0x7d, 0xaf, 0xb3, 0xfb, 0x8c, 0x4b, 0xd5, 0x3b, 0x9a, 0x54, 0x7d, 0x25, 0xb5, 0xc3, 0xa5, 0x5a, - 0x79, 0x8c, 0x68, 0xfd, 0xb3, 0x11, 0x78, 0xf1, 0x58, 0x2a, 0xf2, 0x11, 0xdb, 0x05, 0x3d, 0xda, - 0x89, 0x96, 0xdd, 0x16, 0x65, 0x66, 0x98, 0xdf, 0x8b, 0xc4, 0x61, 0xf6, 0xcb, 0x47, 0x87, 0xd5, - 0x19, 0x1e, 0x8b, 0x61, 0x7b, 0x6e, 0x8b, 0xda, 0x11, 0x2f, 0xd6, 0x86, 0xa9, 0x9f, 0x9a, 0xb1, - 0x8c, 0x23, 0xc3, 0x96, 0x3b, 0x11, 0x0d, 0x1e, 0x39, 0xdc, 0x25, 0x5d, 0xb0, 0xdc, 0xa7, 0xb4, - 0x6b, 0x3b, 0xac, 0xd4, 0xf6, 0x44, 0xb1, 0xce, 0xb2, 0x8f, 0x9a, 0xdc, 0x51, 0x58, 0x2e, 0x30, - 0xe3, 0xe0, 0x81, 0xf3, 0x44, 0x68, 0xbc, 0x78, 0xbe, 0xaa, 0xb0, 0xe4, 0xfe, 0x70, 0x6d, 0xe7, - 0x89, 0xd5, 0x4f, 0x42, 0xbe, 0x01, 0xe7, 0x84, 0xe0, 0x66, 0x42, 0x2c, 0xf0, 0x5b, 0xb2, 0xc5, - 0x05, 0xe4, 0xf5, 0xfa, 0xd1, 0x61, 0xf5, 0x82, 0x10, 0xfb, 0x76, 0x93, 0x63, 0x64, 0xb6, 0x3a, - 0x9b, 0x0b, 0xd9, 0x60, 0x1b, 0x59, 0xaa, 0x3b, 0x1e, 0xd0, 0x30, 0x74, 0x76, 0xa5, 0x76, 0xcc, - 0x6f, 0x94, 0x94, 0xce, 0xb4, 0xdb, 0xbc, 0xdc, 0x1a, 0x48, 0x49, 0xee, 0xc1, 0xe4, 0x36, 0xdd, - 0x51, 0xc7, 0x67, 0x34, 0x5e, 0xe2, 0x95, 0xc7, 0x74, 0x67, 0xf0, 0xe0, 0xa4, 0xe8, 0x88, 0x07, - 0xd3, 0xeb, 0x81, 0xff, 0xe4, 0x80, 0x99, 0x7a, 0xb4, 0x43, 0x03, 0x74, 0xc4, 0x1a, 0xc3, 0xe3, - 0xaa, 0xd9, 0x44, 0xb3, 0xd4, 0xcb, 0xeb, 0x9f, 0x3b, 0x3a, 0xac, 0xbe, 0xd8, 0x65, 0x60, 0xbb, - 0x25, 0xe0, 0x76, 0x2a, 0x30, 0xab, 0x9f, 0x2b, 0xf9, 0x19, 0x98, 0xb2, 0xfc, 0x5e, 0xe4, 0x75, - 0x76, 0x1b, 0x51, 0xe0, 0x44, 0x74, 0x97, 0x0b, 0xf2, 0xc4, 0xe3, 0x2b, 0x55, 0xca, 0x0f, 0xa6, - 0x03, 0x0e, 0xb4, 0x43, 0x01, 0xd5, 0x24, 0xa9, 0x4e, 0x60, 0xfe, 0x56, 0x0e, 0x66, 0xc5, 0x30, - 0x58, 0xb4, 0xe9, 0x07, 0xee, 0xb3, 0xbf, 0xec, 0x97, 0xb4, 0x65, 0xff, 0x72, 0xec, 0xa3, 0x94, - 0xd5, 0xc8, 0x63, 0x56, 0xfd, 0x3f, 0x33, 0xe0, 0xf2, 0x71, 0x44, 0xac, 0x77, 0x62, 0x1f, 0xbc, - 0x52, 0x9f, 0xaf, 0x5d, 0x17, 0x66, 0x70, 0x3c, 0xf1, 0xe0, 0x38, 0xbc, 0xe7, 0x87, 0x11, 0x9e, - 0xde, 0xe5, 0x34, 0x47, 0x82, 0xba, 0xef, 0xb7, 0x50, 0xce, 0xd7, 0xdf, 0x60, 0xe2, 0xfc, 0xcf, - 0x0f, 0xab, 0xc0, 0x40, 0x6b, 0x78, 0x19, 0xc9, 0xf6, 0x7c, 0x3e, 0x63, 0xf0, 0x5c, 0x3a, 0xb4, - 0xd1, 0xfb, 0x63, 0x9f, 0x1e, 0x84, 0x56, 0x16, 0x6b, 0x3c, 0xa1, 0xa9, 0xf5, 0xa2, 0xbd, 0xf5, - 0x80, 0x3e, 0xa4, 0x01, 0xed, 0x34, 0xe9, 0x73, 0x76, 0x42, 0xa3, 0x37, 0x6e, 0x28, 0xf3, 0xe4, - 0xff, 0x8d, 0xc2, 0xd9, 0x2c, 0x32, 0xd6, 0x2f, 0x8a, 0x46, 0x9c, 0x8e, 0xe2, 0xfd, 0x5b, 0x06, - 0x94, 0x1b, 0xb4, 0xe9, 0x77, 0xdc, 0x3b, 0x4e, 0x33, 0xf2, 0xa5, 0x4b, 0x86, 0xcd, 0x25, 0x1b, - 0x83, 0xdb, 0x0f, 0xb1, 0x40, 0x3b, 0x19, 0xf8, 0xf2, 0x70, 0x8a, 0x68, 0xd3, 0x47, 0xa7, 0xd5, - 0x88, 0xcd, 0xc9, 0xe4, 0x13, 0x78, 0xab, 0xa1, 0x7d, 0x94, 0xd4, 0x61, 0x62, 0xc1, 0xef, 0x74, - 0x28, 0xfb, 0xa1, 0xb8, 0x60, 0x5e, 0x3e, 0x3a, 0xac, 0xce, 0x36, 0x65, 0x41, 0xfa, 0x84, 0x40, - 0x27, 0x21, 0xb7, 0x20, 0xbf, 0x39, 0x7f, 0x47, 0x8c, 0x81, 0x74, 0x56, 0xdb, 0x9c, 0xbf, 0x83, - 0xb6, 0x2e, 0xd3, 0x1f, 0x26, 0x7a, 0xf3, 0x0f, 0xd5, 0x33, 0xd0, 0xcd, 0xf9, 0x3b, 0x64, 0x0d, - 0xa6, 0x2d, 0xfa, 0xb3, 0x3d, 0x2f, 0xa0, 0x62, 0x01, 0x3c, 0xb8, 0x53, 0xc3, 0xb1, 0x28, 0x72, - 0x39, 0x16, 0xf0, 0x42, 0xa9, 0xdb, 0xdb, 0xed, 0x87, 0x6a, 0xe4, 0x5a, 0x3f, 0x2d, 0xf9, 0x45, - 0x38, 0xb7, 0xe8, 0x85, 0xa2, 0xce, 0xfc, 0xf0, 0xd1, 0xc5, 0x7b, 0xc8, 0xd1, 0x01, 0xcb, 0xe1, - 0x27, 0x32, 0x97, 0xc3, 0xe7, 0xdc, 0x98, 0x89, 0xcd, 0x4f, 0x36, 0xdd, 0xb4, 0xef, 0x6a, 0xf6, - 0x77, 0xc8, 0xc7, 0x30, 0x89, 0xa7, 0x3d, 0x78, 0x1e, 0x8b, 0xee, 0xcc, 0x63, 0x03, 0xbe, 0xfc, - 0x85, 0xcc, 0x2f, 0x5f, 0xc2, 0xc3, 0x23, 0x1b, 0x4f, 0x75, 0xd1, 0xf5, 0x59, 0xb3, 0x11, 0x34, - 0xce, 0xe4, 0x43, 0x98, 0x12, 0x9b, 0xce, 0xda, 0xc3, 0x8d, 0x3d, 0xba, 0xe8, 0x1c, 0x08, 0x27, - 0x04, 0xd4, 0xff, 0xc4, 0x4e, 0x65, 0xfb, 0x0f, 0xed, 0x68, 0x8f, 0xda, 0xae, 0xa3, 0x89, 0xe7, - 0x14, 0x21, 0xf9, 0x79, 0x18, 0x5f, 0xf1, 0xf1, 0xe2, 0x09, 0x45, 0x4d, 0x09, 0xf9, 0x7c, 0x15, - 0x23, 0x57, 0x39, 0x38, 0xb5, 0x89, 0xfc, 0xe8, 0xb0, 0xfa, 0xce, 0x69, 0x67, 0xa1, 0xf2, 0x01, - 0x4b, 0xfd, 0x1a, 0x59, 0x80, 0xe2, 0x36, 0xdd, 0x61, 0xad, 0x4d, 0x47, 0x5d, 0x49, 0x30, 0x97, - 0x17, 0x8f, 0xc5, 0x2f, 0xf5, 0x56, 0x47, 0x62, 0x98, 0xff, 0xda, 0xc0, 0x19, 0x48, 0xae, 0xa3, - 0x23, 0x58, 0xec, 0x0d, 0x8e, 0x96, 0xa5, 0xd3, 0xed, 0xea, 0xfe, 0xdc, 0x1c, 0x85, 0x99, 0xa1, - 0x77, 0x9c, 0x26, 0x8d, 0xe4, 0x79, 0x25, 0x22, 0x3f, 0x44, 0x88, 0x6a, 0x86, 0x72, 0x1c, 0xf2, - 0x15, 0x38, 0xbb, 0x48, 0x1f, 0x79, 0x4d, 0x5a, 0x8b, 0x22, 0x1a, 0xf2, 0xd6, 0x2e, 0xd4, 0xf8, - 0xc5, 0x5e, 0xa9, 0xfe, 0xca, 0xd1, 0x61, 0xf5, 0x8a, 0x8b, 0xe5, 0xb6, 0x93, 0x20, 0xd8, 0x4d, - 0x47, 0xe5, 0x95, 0xc9, 0xc1, 0xfc, 0x5f, 0x46, 0xd2, 0x03, 0xe4, 0x75, 0x28, 0x58, 0xeb, 0x71, - 0xfd, 0xf9, 0x9d, 0x5d, 0xaa, 0xfa, 0x88, 0x40, 0xbe, 0x06, 0xe7, 0x14, 0x3e, 0x38, 0x39, 0xa8, - 0xcb, 0x2a, 0xc4, 0x1b, 0xf3, 0x2a, 0x5e, 0xd2, 0x28, 0x35, 0x71, 0x38, 0x46, 0xaa, 0x46, 0xd9, - 0x3c, 0x58, 0x63, 0x95, 0x82, 0x45, 0xda, 0xf1, 0x38, 0x6f, 0xa5, 0xb1, 0x2a, 0x6f, 0x17, 0x11, - 0xd2, 0x8d, 0xcd, 0xe2, 0xf0, 0x61, 0xa1, 0x58, 0xa8, 0x8c, 0x98, 0x7f, 0x65, 0x28, 0x29, 0x00, - 0x9e, 0xd1, 0xdd, 0xe3, 0xb6, 0xb6, 0x7b, 0x9c, 0x15, 0xa4, 0x71, 0xab, 0x58, 0x59, 0xe6, 0x8e, - 0x3f, 0x05, 0x13, 0x1a, 0x12, 0xba, 0xbc, 0x6e, 0x86, 0x34, 0xe0, 0xe7, 0x83, 0xcf, 0x97, 0xcb, - 0x6b, 0xdc, 0xae, 0xa1, 0x3c, 0x19, 0xff, 0xc2, 0x80, 0xa9, 0x14, 0x05, 0xeb, 0x0d, 0x06, 0x52, - 0x7b, 0xa3, 0x17, 0xd2, 0xc0, 0x42, 0x28, 0x77, 0x90, 0x5b, 0xd1, 0x1d, 0xe4, 0x5a, 0x16, 0x83, - 0x91, 0x2f, 0xc3, 0xc8, 0x26, 0x6a, 0xf3, 0xba, 0x8f, 0x45, 0xcc, 0x1f, 0x0b, 0xf9, 0x0a, 0xeb, - 0xb1, 0x7f, 0x55, 0x01, 0x81, 0x65, 0xa4, 0x01, 0x63, 0x0b, 0x01, 0xc5, 0x60, 0xff, 0xc2, 0xf0, - 0x97, 0x61, 0x4d, 0x4e, 0x92, 0xbe, 0x0c, 0x13, 0x9c, 0xcc, 0xdf, 0xcc, 0x01, 0x49, 0xda, 0x48, - 0x9b, 0x01, 0x8d, 0xc2, 0x67, 0x76, 0xd0, 0x3f, 0xd0, 0x06, 0xfd, 0xc5, 0xbe, 0x41, 0xe7, 0xcd, - 0x1b, 0x6a, 0xec, 0xff, 0xd8, 0x80, 0xf3, 0xd9, 0x84, 0xe4, 0x65, 0x18, 0x5d, 0xdb, 0x58, 0x97, - 0x6e, 0x3a, 0xa2, 0x29, 0x7e, 0x17, 0xb5, 0x54, 0x4b, 0x14, 0x91, 0x37, 0x61, 0xf4, 0x23, 0x6b, - 0x81, 0x6d, 0x5f, 0x4a, 0xd4, 0xc9, 0xcf, 0x06, 0x76, 0x53, 0x37, 0x7f, 0x04, 0x92, 0x3a, 0xb6, - 0xf9, 0xa7, 0x36, 0xb6, 0xdf, 0xca, 0xc1, 0x54, 0xad, 0xd9, 0xa4, 0x61, 0xc8, 0x94, 0x13, 0x1a, - 0x46, 0xcf, 0xec, 0xc0, 0x66, 0x3b, 0xe0, 0x68, 0x6d, 0x1b, 0x6a, 0x54, 0xff, 0xc4, 0x80, 0x73, - 0x92, 0xea, 0x91, 0x47, 0x1f, 0x6f, 0xec, 0x05, 0x34, 0xdc, 0xf3, 0x5b, 0xee, 0xb0, 0xf1, 0x53, - 0xb8, 0x4b, 0x7b, 0xad, 0x88, 0x06, 0xea, 0x61, 0xf1, 0x43, 0x84, 0x68, 0xbb, 0x34, 0x42, 0xc8, - 0x1c, 0x8c, 0xd5, 0xba, 0xdd, 0xc0, 0x7f, 0xc4, 0x97, 0xfd, 0x84, 0xb8, 0x1b, 0xe4, 0x20, 0xed, - 0x2e, 0x91, 0x83, 0x58, 0x35, 0x16, 0x69, 0x87, 0x7b, 0x17, 0x4f, 0xf0, 0x6a, 0xb8, 0xb4, 0xa3, - 0x6a, 0x4b, 0x58, 0x6e, 0x7e, 0xb3, 0x00, 0x65, 0xb5, 0x21, 0xc4, 0x84, 0x51, 0xee, 0x2a, 0xa2, - 0x5e, 0xd9, 0x3b, 0x08, 0xb1, 0x44, 0x49, 0xe2, 0x81, 0x93, 0x3b, 0xd1, 0x03, 0x67, 0x1b, 0x26, - 0xd6, 0x03, 0xbf, 0xeb, 0x87, 0xd4, 0xe5, 0xf9, 0x5a, 0xb8, 0xd4, 0x9a, 0x89, 0xdd, 0x52, 0x79, - 0x9f, 0xb3, 0x22, 0xae, 0x9a, 0x77, 0x05, 0xb6, 0x9d, 0xce, 0xe6, 0xa2, 0xf3, 0xe1, 0x87, 0xed, - 0x4e, 0x28, 0x5c, 0xf7, 0xe3, 0xc3, 0x76, 0x06, 0xd1, 0x0f, 0xdb, 0x19, 0x44, 0x5d, 0x16, 0x23, - 0x4f, 0x6b, 0x59, 0x90, 0xdf, 0x34, 0x60, 0xbc, 0xd6, 0xe9, 0x08, 0x0f, 0x1c, 0x19, 0xf4, 0x7c, - 0x2e, 0x39, 0x70, 0xe7, 0x2e, 0x9a, 0xfc, 0xbc, 0xfd, 0xeb, 0xe2, 0xbc, 0xfd, 0x9d, 0x4f, 0x74, - 0xde, 0xbe, 0x11, 0x38, 0x5e, 0x14, 0xe2, 0xc5, 0x6a, 0xf2, 0x41, 0xd5, 0x0d, 0x57, 0xa9, 0x07, - 0x79, 0x07, 0x2a, 0xf1, 0x7c, 0x5c, 0xee, 0xb8, 0xf4, 0x09, 0xe5, 0x0e, 0x4b, 0x13, 0x3c, 0x32, - 0x4f, 0xbb, 0x48, 0x48, 0x23, 0x9a, 0xdf, 0x32, 0xe0, 0xbc, 0x3a, 0x21, 0x1a, 0xbd, 0x9d, 0xb6, - 0x87, 0xa6, 0x08, 0xb9, 0x01, 0x25, 0x31, 0x5e, 0xb1, 0x22, 0xd7, 0x9f, 0xe4, 0x27, 0x41, 0x21, - 0x4b, 0x6c, 0x88, 0x18, 0x0f, 0x61, 0xb7, 0xcf, 0xa4, 0x96, 0x1b, 0x2b, 0xaa, 0xcf, 0x8a, 0xce, - 0xae, 0x04, 0xf8, 0x5b, 0x1f, 0x3b, 0x06, 0x31, 0xdf, 0x87, 0x69, 0xbd, 0x96, 0x0d, 0x8a, 0xe1, - 0x60, 0xb2, 0x69, 0x46, 0x76, 0xd3, 0x64, 0xb9, 0xb9, 0x0d, 0xa4, 0x8f, 0x3e, 0xc4, 0x4b, 0x23, - 0x1a, 0xc9, 0x4b, 0x4d, 0x79, 0xf4, 0xd4, 0x87, 0x18, 0xa7, 0xbb, 0x1a, 0x57, 0xbb, 0x1b, 0x49, - 0xcd, 0xbf, 0x2e, 0xc1, 0x4c, 0x86, 0xe8, 0x38, 0x61, 0x6b, 0xaf, 0xea, 0x8b, 0xa7, 0x14, 0xdf, - 0xce, 0xcb, 0x25, 0xf3, 0xbe, 0x4c, 0x6d, 0x74, 0xcc, 0x52, 0x39, 0x2e, 0xdf, 0xd1, 0x67, 0xb1, - 0xbd, 0xab, 0x0e, 0x34, 0x23, 0x4f, 0xcd, 0x81, 0xa6, 0x0e, 0x13, 0xa2, 0x55, 0x62, 0x29, 0x8f, - 0x26, 0x26, 0x7a, 0xc0, 0x0b, 0xec, 0xbe, 0x25, 0xad, 0x93, 0x70, 0x1e, 0xa1, 0xdf, 0x7a, 0x44, - 0x05, 0x8f, 0x31, 0x95, 0x07, 0x16, 0x64, 0xf2, 0x50, 0x48, 0xc8, 0x3f, 0x35, 0x80, 0x08, 0x88, - 0xba, 0x9e, 0x8b, 0xc7, 0xad, 0x67, 0xf7, 0xe9, 0xac, 0xe7, 0x17, 0x65, 0x1d, 0xb3, 0xd7, 0x75, - 0x46, 0xb5, 0xc8, 0x3f, 0x36, 0x60, 0x9a, 0x7b, 0x71, 0xa8, 0x95, 0x2d, 0x1d, 0x57, 0xd9, 0xe6, - 0xd3, 0xa9, 0xec, 0xe5, 0x10, 0x3f, 0x3b, 0xa0, 0xae, 0xfd, 0x95, 0x22, 0x3f, 0x05, 0x10, 0xaf, - 0x28, 0xe9, 0x2d, 0x78, 0x39, 0x43, 0x0a, 0xc4, 0x48, 0x49, 0xc0, 0x63, 0x14, 0xd3, 0xa9, 0xfe, - 0x35, 0x09, 0x37, 0xf2, 0x8b, 0x70, 0x96, 0xad, 0x97, 0x18, 0x22, 0x7c, 0xce, 0x66, 0xc7, 0xf1, - 0x2b, 0x5f, 0x1c, 0xbc, 0xb5, 0xdf, 0xc8, 0x22, 0xe3, 0x31, 0x1b, 0x49, 0xf8, 0x7b, 0xd4, 0x56, - 0x4d, 0xbe, 0x2c, 0x0a, 0x74, 0x2e, 0xc5, 0xda, 0x87, 0xb3, 0x65, 0xfc, 0x66, 0xa6, 0x7c, 0xbb, - 0x28, 0xd7, 0x02, 0x97, 0x6f, 0xa1, 0x1e, 0x74, 0x81, 0x20, 0xf2, 0x11, 0x90, 0x46, 0x6f, 0x77, - 0x97, 0x86, 0x11, 0x75, 0x39, 0x8c, 0x06, 0xe1, 0xec, 0x04, 0xca, 0x07, 0x3c, 0x32, 0x0a, 0x65, - 0xa9, 0x1d, 0xc8, 0x62, 0x75, 0x92, 0xf4, 0x13, 0x5f, 0xda, 0x81, 0x8b, 0x03, 0x9b, 0x99, 0x11, - 0x50, 0x31, 0xa7, 0x07, 0x54, 0x5c, 0x1c, 0x24, 0x0e, 0x43, 0x35, 0xa8, 0xe2, 0xf7, 0x8c, 0x94, - 0xfc, 0x13, 0xca, 0x0a, 0xcf, 0x02, 0x37, 0x68, 0x83, 0xc8, 0x61, 0x60, 0x3c, 0x97, 0x90, 0xb9, - 0x44, 0x49, 0x62, 0x12, 0x52, 0x95, 0xb0, 0x28, 0x2b, 0x3f, 0xa5, 0x28, 0x34, 0xff, 0x85, 0x01, - 0x84, 0xd7, 0x70, 0xc1, 0xe9, 0x3a, 0x3b, 0x5e, 0xcb, 0x8b, 0x3c, 0x1a, 0x92, 0xfb, 0x50, 0x11, - 0x2c, 0x9c, 0x9d, 0x16, 0x55, 0x7d, 0xa5, 0xc4, 0x65, 0x6a, 0x5c, 0x66, 0xa7, 0xd5, 0x9a, 0x3e, - 0xc2, 0x01, 0x83, 0x97, 0xfb, 0x14, 0x83, 0x67, 0xfe, 0xd0, 0x80, 0x8b, 0xfd, 0xd5, 0x16, 0x5f, - 0x8e, 0x3b, 0xcf, 0x38, 0xa1, 0xf3, 0xb2, 0x5a, 0x99, 0xc3, 0x63, 0xc8, 0xa7, 0xd6, 0xca, 0x7c, - 0x72, 0xaa, 0x79, 0xfa, 0x56, 0xfe, 0x6a, 0x0e, 0xca, 0xeb, 0xad, 0xde, 0xae, 0xd7, 0x59, 0x74, - 0x22, 0xe7, 0x99, 0x35, 0x29, 0xde, 0xd6, 0x4c, 0x8a, 0xd8, 0x3b, 0x2a, 0x6e, 0xd8, 0x70, 0x19, - 0xb9, 0x0c, 0x98, 0x4a, 0x48, 0xf8, 0x2a, 0xbd, 0x07, 0x05, 0xf6, 0x43, 0x68, 0x28, 0x57, 0xfa, - 0x18, 0x23, 0xd6, 0x8d, 0xf8, 0x3f, 0xa1, 0xe4, 0xeb, 0x79, 0xd0, 0x90, 0xc3, 0xa5, 0x9f, 0xe0, - 0x69, 0x8c, 0x4e, 0x9f, 0x72, 0xf1, 0x9f, 0x1b, 0x50, 0x49, 0xb7, 0x84, 0xdc, 0x87, 0x31, 0xc6, - 0xc9, 0x8b, 0x53, 0x22, 0xbd, 0x32, 0xa0, 0xcd, 0x37, 0x04, 0x1a, 0xaf, 0x1e, 0x76, 0x3e, 0xe5, - 0x10, 0x4b, 0x72, 0xb8, 0x64, 0x41, 0x59, 0xc5, 0xca, 0xa8, 0xdd, 0x1b, 0xba, 0x68, 0x3a, 0x9f, - 0xdd, 0x0f, 0x6a, 0xad, 0x7f, 0x57, 0xab, 0xb5, 0x10, 0x4a, 0xc3, 0xe6, 0xb6, 0xc3, 0xf0, 0x30, - 0x9e, 0xc1, 0x43, 0x9d, 0x67, 0x32, 0xd9, 0x87, 0x1e, 0x1e, 0xc6, 0x61, 0xcc, 0x16, 0xe1, 0xdf, - 0x13, 0xf3, 0x0c, 0x6d, 0x91, 0x2e, 0x42, 0x54, 0x7d, 0x96, 0xe3, 0x98, 0x7f, 0x2f, 0x0f, 0xe7, - 0x93, 0xea, 0xf1, 0x4c, 0x7f, 0xeb, 0x4e, 0xe0, 0xb4, 0xc3, 0x13, 0x56, 0xc0, 0xd5, 0xbe, 0xaa, - 0x61, 0xf8, 0xb3, 0xac, 0x9a, 0x52, 0x21, 0x33, 0x55, 0x21, 0x34, 0xe2, 0x78, 0x85, 0x64, 0x35, - 0xc8, 0x7d, 0xc8, 0x37, 0x68, 0x24, 0x82, 0x24, 0x5f, 0xeb, 0xeb, 0x55, 0xb5, 0x5e, 0x37, 0x1a, - 0x34, 0xe2, 0x83, 0xc8, 0xfd, 0xcc, 0xa9, 0xe6, 0xf7, 0xcd, 0xd4, 0xf1, 0x6d, 0x18, 0x5d, 0x7a, - 0xd2, 0xa5, 0xcd, 0x48, 0xc4, 0x46, 0x5e, 0x3b, 0x9e, 0x1f, 0xc7, 0x55, 0x22, 0x30, 0x29, 0x02, - 0xd4, 0xce, 0xe2, 0x28, 0x97, 0x6e, 0x43, 0x51, 0x7e, 0xfc, 0x54, 0x91, 0x84, 0x6f, 0xc3, 0xb8, - 0xf2, 0x91, 0x53, 0x4d, 0xfa, 0xbf, 0x36, 0x60, 0x94, 0x09, 0xbd, 0xad, 0xb7, 0x9e, 0x51, 0x89, - 0x74, 0x4b, 0x93, 0x48, 0xd3, 0x4a, 0xc8, 0x0b, 0xae, 0xcb, 0xb7, 0x4e, 0x90, 0x45, 0x87, 0x06, - 0x40, 0x82, 0x4c, 0xee, 0xc2, 0x18, 0xbf, 0xc8, 0x91, 0x69, 0x34, 0xd5, 0x18, 0x1a, 0x51, 0x92, - 0x68, 0x39, 0x7e, 0x37, 0xad, 0x16, 0x4a, 0x6a, 0xb2, 0x98, 0xf8, 0x19, 0xab, 0x41, 0x9b, 0x8c, - 0xcd, 0x82, 0xdf, 0xe1, 0x31, 0x15, 0xa1, 0x92, 0x6e, 0x2a, 0xdb, 0xe1, 0xb8, 0x26, 0x0e, 0x36, - 0xf2, 0xc7, 0x31, 0x39, 0x2f, 0x98, 0x64, 0x9f, 0x79, 0x7c, 0x7b, 0x9c, 0x47, 0x29, 0xc8, 0x8a, - 0xbd, 0x07, 0xe5, 0x3b, 0x7e, 0xf0, 0xd8, 0x09, 0xdc, 0xda, 0x2e, 0x15, 0x1e, 0xe2, 0x45, 0x74, - 0xf3, 0x9e, 0x78, 0xc8, 0xe1, 0xb6, 0xc3, 0x0a, 0x7e, 0x74, 0x58, 0x2d, 0xd4, 0x7d, 0xbf, 0x65, - 0x69, 0xe8, 0x64, 0x0d, 0x26, 0x1e, 0x38, 0x4f, 0xc4, 0x7d, 0xdd, 0xc6, 0xc6, 0x8a, 0xf0, 0x33, - 0xb9, 0x76, 0x74, 0x58, 0xbd, 0xd8, 0x76, 0x9e, 0xc4, 0xf7, 0x7c, 0x83, 0x5d, 0xa1, 0x75, 0x7a, - 0xe2, 0xc1, 0xe4, 0xba, 0x1f, 0x44, 0xe2, 0x23, 0x4c, 0xa7, 0xcd, 0x0f, 0xb8, 0x6e, 0x9b, 0xcb, - 0xbc, 0x6e, 0xbb, 0xc8, 0x14, 0x79, 0xfb, 0x61, 0x4c, 0xae, 0x85, 0xd6, 0x69, 0x8c, 0xc9, 0x7b, - 0x30, 0xbd, 0x40, 0x83, 0xc8, 0x7b, 0xe8, 0x35, 0x9d, 0x88, 0xde, 0xf1, 0x83, 0xb6, 0x13, 0x89, - 0x03, 0x15, 0x34, 0xa8, 0x9b, 0x94, 0x73, 0x6a, 0x3b, 0x91, 0xd5, 0x8f, 0x49, 0xbe, 0x96, 0xe5, - 0xb9, 0x33, 0x82, 0xcd, 0x7f, 0x93, 0x29, 0x05, 0x19, 0x9e, 0x3b, 0x03, 0xba, 0x20, 0xc3, 0x87, - 0x67, 0xf7, 0xb8, 0x6b, 0xcf, 0x62, 0xfd, 0xa6, 0xb8, 0x82, 0x3d, 0xf9, 0x5a, 0x33, 0x1e, 0xb7, - 0x01, 0xd7, 0x9b, 0xf3, 0x90, 0xaf, 0xaf, 0xdf, 0xc1, 0x23, 0x12, 0x71, 0xcd, 0x48, 0x3b, 0x7b, - 0x4e, 0xa7, 0x89, 0xba, 0x8c, 0xf0, 0x5d, 0x50, 0x05, 0x5e, 0x7d, 0xfd, 0x0e, 0x71, 0x60, 0x66, - 0x9d, 0x06, 0x6d, 0x2f, 0xfa, 0xca, 0xcd, 0x9b, 0xca, 0x40, 0x15, 0xb1, 0x6a, 0x73, 0xa2, 0x6a, - 0xd5, 0x2e, 0xa2, 0xd8, 0x4f, 0x6e, 0xde, 0xcc, 0x1c, 0x8e, 0xb8, 0x62, 0x59, 0xbc, 0xc8, 0x12, - 0x4c, 0x3e, 0x70, 0x9e, 0x88, 0x0b, 0xe9, 0xd8, 0xc6, 0xcb, 0xa3, 0x67, 0x3c, 0x4e, 0xac, 0x66, - 0x52, 0xa4, 0x0e, 0xb1, 0x4e, 0x44, 0xde, 0x85, 0xf1, 0x64, 0x7a, 0x85, 0x78, 0x15, 0x99, 0xe7, - 0x2e, 0x91, 0xca, 0xe4, 0xd4, 0xce, 0x92, 0x14, 0x74, 0xb2, 0x19, 0x9b, 0xe8, 0x5c, 0x21, 0x45, - 0x47, 0xc1, 0x52, 0x7d, 0x4e, 0x35, 0xd1, 0x1d, 0x2c, 0xd1, 0x9a, 0x35, 0x15, 0xab, 0xe8, 0xdc, - 0x53, 0xc6, 0xd2, 0xb9, 0x28, 0x96, 0xff, 0x7a, 0xe0, 0xb7, 0xbb, 0x11, 0x7a, 0x0c, 0xa6, 0x2c, - 0xff, 0x2e, 0x96, 0x64, 0x58, 0xfe, 0x9c, 0x24, 0xfb, 0x9e, 0x7d, 0xe2, 0x53, 0xdc, 0xb3, 0x53, - 0x28, 0xac, 0xf8, 0xcd, 0x7d, 0x74, 0x11, 0x2c, 0xd5, 0x3f, 0x62, 0xf2, 0xa3, 0xe5, 0x37, 0xf7, - 0x9f, 0xde, 0xfd, 0x30, 0xb2, 0x27, 0xab, 0xac, 0xed, 0x6c, 0x5a, 0x89, 0x4f, 0xcf, 0x4e, 0x69, - 0x37, 0x6d, 0x5a, 0x19, 0x57, 0x54, 0xf8, 0x2c, 0x94, 0x0d, 0xb1, 0x74, 0x72, 0x42, 0xa1, 0xb2, - 0x48, 0xc3, 0xfd, 0xc8, 0xef, 0x2e, 0xb4, 0xbc, 0xee, 0x8e, 0xef, 0x04, 0xee, 0x6c, 0x65, 0x80, - 0xc0, 0x78, 0x3d, 0x53, 0x60, 0x4c, 0xbb, 0x9c, 0xde, 0x6e, 0x4a, 0x06, 0x56, 0x1f, 0x4b, 0xf2, - 0x35, 0x98, 0x64, 0xab, 0x65, 0xe9, 0x49, 0x44, 0x3b, 0x7c, 0x2a, 0x4d, 0xe3, 0x56, 0x7f, 0x56, - 0x09, 0x32, 0x8c, 0x0b, 0xf9, 0x24, 0x45, 0xe9, 0x41, 0x63, 0x02, 0x75, 0x92, 0xea, 0xac, 0xcc, - 0x9f, 0x4a, 0xf5, 0x09, 0x59, 0x86, 0x31, 0x51, 0x03, 0xb1, 0xeb, 0xf4, 0xb7, 0xe5, 0xc5, 0xcc, - 0xb6, 0x8c, 0x89, 0xb6, 0x58, 0x92, 0xde, 0xfc, 0xb7, 0x06, 0x4c, 0x68, 0x9f, 0x23, 0xb7, 0x15, - 0xf7, 0x95, 0xc4, 0xed, 0x4c, 0xc3, 0xc9, 0x4c, 0x4f, 0x7f, 0x5b, 0xf8, 0x2c, 0xe5, 0x06, 0xd3, - 0x65, 0x66, 0x0e, 0x93, 0x41, 0xff, 0xf9, 0xe3, 0x83, 0xfe, 0x0b, 0x03, 0x82, 0xfe, 0xbf, 0x3d, - 0x01, 0x93, 0xfa, 0x06, 0xc7, 0x34, 0xce, 0x15, 0x7f, 0xd7, 0xeb, 0x48, 0xbb, 0x95, 0xa7, 0xb1, - 0x40, 0x88, 0x96, 0xeb, 0x1d, 0x21, 0xe4, 0x55, 0x80, 0xf8, 0x6a, 0x56, 0x9a, 0xa6, 0x22, 0x33, - 0xbd, 0x52, 0x40, 0x7e, 0x1a, 0x60, 0xd5, 0x77, 0x69, 0x9c, 0x09, 0xe5, 0x98, 0x03, 0xa5, 0xd7, - 0xc5, 0x81, 0x92, 0xc8, 0x26, 0x7f, 0x74, 0x58, 0x3d, 0xd7, 0xf1, 0x5d, 0xda, 0x9f, 0x02, 0x45, - 0xe1, 0x48, 0xbe, 0x04, 0x23, 0x56, 0xaf, 0x45, 0x65, 0x62, 0x8e, 0x71, 0x39, 0xe1, 0x7b, 0x2d, - 0x25, 0xcb, 0x64, 0xd0, 0x4b, 0xdf, 0x23, 0x30, 0x00, 0xf9, 0x00, 0xe0, 0x7e, 0x6f, 0x87, 0xde, - 0x0d, 0xfc, 0x5e, 0x57, 0x46, 0xfe, 0xa2, 0x19, 0xbb, 0x1f, 0xa7, 0x71, 0xb2, 0x77, 0xb1, 0x50, - 0xfd, 0x78, 0x42, 0x42, 0xd6, 0x60, 0x4c, 0x88, 0x0f, 0x71, 0x4e, 0xff, 0x52, 0xd6, 0x09, 0x91, - 0xa2, 0x43, 0x88, 0x4c, 0x19, 0x08, 0xd6, 0x0f, 0x6d, 0xb8, 0x19, 0xfe, 0x2e, 0x94, 0x18, 0x7b, - 0x66, 0x6a, 0x87, 0x62, 0xef, 0x40, 0xff, 0x41, 0xa5, 0x42, 0xcc, 0x2c, 0xd7, 0xf2, 0x75, 0xc5, - 0x04, 0xe4, 0x6b, 0x98, 0xdb, 0x46, 0x74, 0xf5, 0xb1, 0x07, 0x8d, 0xaf, 0xf5, 0x75, 0xf5, 0x59, - 0xa7, 0xdb, 0xcd, 0x48, 0x06, 0x16, 0xf3, 0x23, 0xbb, 0x71, 0x8c, 0x4d, 0x9c, 0x6a, 0xf8, 0x98, - 0x0f, 0x5c, 0xef, 0xfb, 0xc0, 0xac, 0x0c, 0x1b, 0xe9, 0xcf, 0x68, 0xa3, 0xf1, 0x25, 0x5d, 0xa8, - 0x24, 0x69, 0xb4, 0xc4, 0xb7, 0xe0, 0xb8, 0x6f, 0xbd, 0xd9, 0xf7, 0x2d, 0x75, 0x00, 0xfb, 0x3e, - 0xd7, 0xc7, 0x9d, 0xb8, 0x49, 0x5a, 0x58, 0xf1, 0xbd, 0xf1, 0xe3, 0xbe, 0xf7, 0x6a, 0xdf, 0xf7, - 0x66, 0xdc, 0x9d, 0xfe, 0xef, 0xa4, 0x78, 0x92, 0x77, 0x61, 0x42, 0x42, 0x70, 0x7d, 0xe0, 0x01, - 0x9f, 0xd0, 0xef, 0xdd, 0x1d, 0x74, 0x1a, 0xd3, 0xd3, 0xb9, 0xa8, 0xc8, 0x2a, 0x35, 0x9f, 0x1d, - 0x13, 0x1a, 0x75, 0x7a, 0x56, 0xe8, 0xc8, 0xe4, 0xab, 0x30, 0xbe, 0xdc, 0x66, 0x0d, 0xf1, 0x3b, - 0x4e, 0x44, 0x71, 0x33, 0x4a, 0x0e, 0x4d, 0x95, 0x12, 0x65, 0xaa, 0xf2, 0x1c, 0x8f, 0x49, 0x91, - 0xba, 0x99, 0x2b, 0x14, 0xac, 0xf3, 0xf8, 0xf1, 0x8b, 0x98, 0xc3, 0xa1, 0xd8, 0x7a, 0x5e, 0xcc, - 0x38, 0xb8, 0x54, 0xd8, 0xa3, 0x2c, 0xe7, 0xa7, 0x3a, 0xb6, 0x58, 0x10, 0x5a, 0xe7, 0xe9, 0x3c, - 0xc9, 0x7b, 0x30, 0x2e, 0x22, 0x1a, 0x6b, 0xd6, 0x6a, 0x38, 0x5b, 0xc1, 0xc6, 0x63, 0x2e, 0x36, - 0x19, 0xfc, 0x68, 0x3b, 0x41, 0xea, 0xf6, 0x2a, 0xc1, 0x27, 0x5f, 0x81, 0xb3, 0xdb, 0x5e, 0xc7, - 0xf5, 0x1f, 0x87, 0x42, 0x80, 0x0b, 0x41, 0x37, 0x9d, 0xf8, 0xe8, 0x3c, 0xe6, 0xe5, 0xb6, 0xdc, - 0xb6, 0xfa, 0x04, 0x5f, 0x26, 0x07, 0xf2, 0x0b, 0x7d, 0x9c, 0xf9, 0x0c, 0x22, 0xc7, 0xcd, 0xa0, - 0xf9, 0xbe, 0x19, 0xd4, 0xff, 0xf9, 0xf4, 0x74, 0xca, 0xfc, 0x0c, 0xf1, 0x81, 0xe8, 0x3a, 0xc7, - 0x87, 0xbe, 0xd7, 0x99, 0x9d, 0xd1, 0x1e, 0xf2, 0x88, 0x5d, 0x66, 0x11, 0x6f, 0xdd, 0x6f, 0x79, - 0xcd, 0x83, 0xba, 0x79, 0x74, 0x58, 0x7d, 0x29, 0xad, 0xcd, 0x7c, 0xec, 0x6b, 0x87, 0x0b, 0x19, - 0xac, 0xc9, 0x57, 0xa1, 0xcc, 0xfe, 0xc6, 0xaa, 0xdf, 0x59, 0xed, 0xaa, 0x4b, 0xc1, 0x14, 0xdf, - 0xc1, 0x31, 0xc2, 0x90, 0xcb, 0x0c, 0xad, 0x50, 0x63, 0x65, 0xfe, 0xd0, 0x80, 0xb3, 0x59, 0x75, - 0x3d, 0x21, 0xbf, 0x8d, 0x99, 0xba, 0xf4, 0xc6, 0x73, 0x09, 0x7e, 0xe9, 0x1d, 0x5f, 0x75, 0x57, - 0x61, 0x84, 0xd9, 0xca, 0xd2, 0x29, 0x0b, 0xb7, 0x43, 0x66, 0x4f, 0x87, 0x16, 0x87, 0x33, 0x04, - 0x74, 0xa6, 0xc7, 0xfd, 0x72, 0x84, 0x23, 0xa0, 0xc7, 0xbd, 0xc5, 0xe1, 0x0c, 0x81, 0x6d, 0xbb, - 0x72, 0x9b, 0x40, 0x04, 0xb6, 0x1b, 0x87, 0x16, 0x87, 0x93, 0xd7, 0x60, 0x6c, 0xad, 0xb3, 0x42, - 0x9d, 0x47, 0x54, 0xdc, 0x38, 0xe1, 0x39, 0x8a, 0xdf, 0xb1, 0x5b, 0x0c, 0x66, 0xc9, 0x42, 0xf3, - 0xbb, 0x06, 0x4c, 0xf7, 0x75, 0xd3, 0xc9, 0x29, 0x7c, 0x8e, 0xbf, 0xde, 0x1b, 0xa6, 0x7d, 0xbc, - 0xfa, 0x85, 0xec, 0xea, 0x9b, 0x7f, 0x99, 0x87, 0x0b, 0x03, 0x76, 0xad, 0xe4, 0x6a, 0xde, 0x38, - 0xf1, 0x6a, 0xfe, 0xeb, 0x6c, 0x97, 0x70, 0xbc, 0x76, 0xb8, 0xe1, 0x27, 0x35, 0x4e, 0x6e, 0x31, - 0xb0, 0x4c, 0xe6, 0xc8, 0x90, 0xf9, 0x1c, 0x2e, 0x36, 0x91, 0xc2, 0x8e, 0xfc, 0xbe, 0x33, 0x63, - 0x9d, 0x59, 0xdf, 0xe5, 0x78, 0xfe, 0xc7, 0xe4, 0x72, 0x5c, 0xbf, 0x92, 0x2a, 0x3c, 0xd5, 0x2b, - 0xa9, 0xec, 0x43, 0xf2, 0x91, 0x4f, 0x73, 0x15, 0xf0, 0xef, 0x53, 0xd7, 0xf1, 0x3f, 0x8e, 0x43, - 0x7d, 0x0d, 0x46, 0xb6, 0xf7, 0x68, 0x20, 0xf5, 0x5b, 0xac, 0xc8, 0x63, 0x06, 0x50, 0x2b, 0x82, - 0x18, 0xe6, 0xcf, 0x43, 0x59, 0xfd, 0x18, 0xae, 0x65, 0xf6, 0x5b, 0x2c, 0x26, 0xbe, 0x96, 0x19, - 0xc0, 0xe2, 0xf0, 0x13, 0x33, 0x62, 0x25, 0xbd, 0x90, 0x3f, 0xa9, 0x17, 0xcc, 0x7f, 0x67, 0x40, - 0x01, 0x13, 0x02, 0xbc, 0x05, 0x25, 0x79, 0x54, 0xaa, 0x06, 0xc9, 0xcf, 0xc8, 0x93, 0xd4, 0x50, - 0xf7, 0x67, 0x10, 0x40, 0xf6, 0xa9, 0x2d, 0x1a, 0xec, 0x68, 0x6e, 0x2f, 0x8f, 0x18, 0x40, 0xfd, - 0x14, 0x62, 0x9c, 0xa2, 0x4b, 0xd0, 0xb5, 0x47, 0xd8, 0xf7, 0x7c, 0xc1, 0x73, 0xd7, 0x9e, 0x3e, - 0xbb, 0x5e, 0x62, 0x99, 0xbf, 0x6d, 0xc0, 0xb9, 0x4c, 0x3d, 0x80, 0x7d, 0x95, 0x2b, 0x1c, 0xca, - 0x8c, 0x48, 0x6b, 0x1b, 0x1c, 0xe3, 0x34, 0x2e, 0x3c, 0xa7, 0x18, 0xde, 0xcf, 0x41, 0x29, 0xb6, - 0xcf, 0xc8, 0x59, 0x39, 0x74, 0x78, 0x9e, 0x26, 0x8d, 0x99, 0xbf, 0x36, 0x60, 0x94, 0x55, 0xe1, - 0x99, 0x8d, 0xae, 0xc8, 0x3e, 0x5d, 0x65, 0x4d, 0x1a, 0x2a, 0xa6, 0xe2, 0x0f, 0x46, 0x01, 0x12, - 0x64, 0xb2, 0x03, 0x93, 0x6b, 0xcb, 0x8b, 0x0b, 0xcb, 0x2e, 0xed, 0x44, 0x78, 0xcb, 0x97, 0x8a, - 0xb2, 0x67, 0x86, 0x65, 0xd0, 0x71, 0x5a, 0x02, 0xe1, 0x20, 0x59, 0x9e, 0xbe, 0xe7, 0x36, 0x6d, - 0x2f, 0xa6, 0x53, 0x15, 0x32, 0x9d, 0x23, 0xfb, 0x46, 0xa3, 0xf6, 0x60, 0x45, 0xf9, 0x46, 0x6e, - 0xc8, 0x6f, 0x84, 0x4e, 0xbb, 0x35, 0xe0, 0x1b, 0x3a, 0x47, 0xb2, 0x07, 0x95, 0xbb, 0x28, 0xbb, - 0x95, 0xaf, 0xe4, 0x8f, 0xff, 0xca, 0xcb, 0xe2, 0x2b, 0x2f, 0x70, 0xa1, 0x9f, 0xfd, 0x9d, 0x3e, - 0xae, 0xc9, 0xcc, 0x2d, 0x9c, 0x38, 0x73, 0xff, 0xb6, 0x01, 0xa3, 0x7c, 0x73, 0x88, 0xdf, 0xb2, - 0xc8, 0xdc, 0x7e, 0xb6, 0x9f, 0xce, 0xf6, 0x53, 0x89, 0xf0, 0x3f, 0xd5, 0x00, 0xe7, 0x65, 0x64, - 0x31, 0xf5, 0x30, 0x86, 0x3c, 0x42, 0x47, 0xc5, 0x94, 0x97, 0x24, 0x8e, 0x50, 0xfc, 0x4d, 0x0c, - 0x95, 0x0b, 0xc7, 0x50, 0x9f, 0xe9, 0x1b, 0xfb, 0x94, 0xcf, 0xf4, 0xad, 0x40, 0x49, 0x78, 0xf6, - 0xd4, 0x0f, 0x84, 0xf9, 0x29, 0x0f, 0x58, 0x62, 0xb8, 0x92, 0x7c, 0x9a, 0x83, 0xec, 0x1d, 0x2d, - 0x75, 0x5c, 0x8c, 0x48, 0xd6, 0xa0, 0x94, 0x84, 0x86, 0x94, 0xb4, 0x7b, 0xd0, 0x18, 0x2e, 0x5c, - 0x5f, 0x79, 0xf4, 0x61, 0x66, 0x24, 0x48, 0xc2, 0xc3, 0xfc, 0xa6, 0x01, 0x95, 0xf4, 0x7c, 0x21, - 0xef, 0xc2, 0x78, 0x1c, 0x9d, 0x13, 0xfb, 0x17, 0xe0, 0x41, 0x66, 0x12, 0xce, 0xa3, 0x79, 0x1a, - 0xa8, 0xe8, 0x64, 0x1e, 0x8a, 0x6c, 0xd9, 0x29, 0xb9, 0x83, 0x51, 0x9e, 0xf4, 0x04, 0x4c, 0xbd, - 0xd7, 0x93, 0x78, 0xca, 0xaa, 0xfd, 0x8f, 0x79, 0x18, 0x57, 0x06, 0x8b, 0x5c, 0x83, 0xe2, 0x72, - 0xb8, 0xe2, 0x37, 0xf7, 0xa9, 0x2b, 0xae, 0x0b, 0xf0, 0x15, 0x46, 0x2f, 0xb4, 0x5b, 0x08, 0xb4, - 0xe2, 0x62, 0x52, 0x87, 0x09, 0xfe, 0x9f, 0x8c, 0xc2, 0xcc, 0x25, 0x47, 0x9d, 0x1c, 0x59, 0xc6, - 0x5f, 0xaa, 0x3b, 0xac, 0x46, 0x42, 0xbe, 0x01, 0xc0, 0x01, 0x6c, 0x7c, 0x87, 0x70, 0xec, 0x95, - 0x0b, 0xf8, 0x9c, 0xf8, 0x40, 0xe4, 0xa9, 0x2d, 0xc4, 0xa9, 0xa0, 0x30, 0xc4, 0x17, 0xe0, 0xfc, - 0xe6, 0xfe, 0xf0, 0x6f, 0x40, 0x26, 0x2f, 0xc0, 0xf9, 0xcd, 0x7d, 0x3b, 0xdb, 0xcb, 0x4b, 0x65, - 0x49, 0xbe, 0x65, 0xc0, 0x25, 0x8b, 0x36, 0xfd, 0x47, 0x34, 0x38, 0xa8, 0x45, 0x88, 0xa5, 0x7e, - 0xf1, 0x64, 0x97, 0xb2, 0x5b, 0xe2, 0x8b, 0xaf, 0x07, 0x82, 0x0b, 0x86, 0xa3, 0xb4, 0xbb, 0x91, - 0x7d, 0x4c, 0x15, 0x8e, 0xf9, 0xa4, 0xf9, 0x67, 0x86, 0xb2, 0x04, 0xc8, 0x2a, 0x94, 0xe2, 0xc9, - 0x22, 0x0e, 0x1c, 0x63, 0xe5, 0x48, 0xc2, 0x2d, 0xfa, 0xb0, 0xfe, 0x82, 0x38, 0xd9, 0x9f, 0x89, - 0xa7, 0x9c, 0xb6, 0x22, 0x24, 0x90, 0x7c, 0x19, 0x0a, 0x38, 0x54, 0x27, 0x27, 0x9b, 0x92, 0x5b, - 0x4d, 0x81, 0x8d, 0x11, 0xd6, 0x1a, 0x29, 0xc9, 0x17, 0x84, 0x97, 0x47, 0x5e, 0x4b, 0xe3, 0xca, - 0x40, 0xac, 0x1e, 0xf1, 0x1e, 0x93, 0x38, 0x16, 0x2a, 0xb3, 0xf5, 0xef, 0xe4, 0xa0, 0x92, 0x5e, - 0x78, 0xe4, 0x03, 0x28, 0xaf, 0x3b, 0x61, 0xf8, 0xd8, 0x0f, 0xdc, 0x7b, 0x4e, 0xb8, 0x27, 0x52, - 0x43, 0xa2, 0xcd, 0xd7, 0x15, 0x70, 0x7b, 0xcf, 0xd1, 0x52, 0x88, 0x69, 0x04, 0x6c, 0x43, 0xde, - 0x10, 0xfe, 0xea, 0xca, 0x02, 0x8a, 0xfc, 0xa8, 0x9b, 0x4a, 0x0d, 0x29, 0xd1, 0xc8, 0x5b, 0x90, - 0xe7, 0xb1, 0x6f, 0x6a, 0x5e, 0xa1, 0x07, 0x77, 0x6a, 0x3c, 0x5c, 0x88, 0x5f, 0x26, 0xeb, 0xa7, - 0xf2, 0x0c, 0x9f, 0xac, 0x28, 0x91, 0x53, 0xa3, 0x5a, 0x7e, 0x15, 0x09, 0x8e, 0x1b, 0x77, 0x72, - 0x08, 0xd5, 0x87, 0x85, 0x62, 0xbe, 0x52, 0x10, 0xf1, 0x39, 0x7f, 0x98, 0x87, 0x52, 0xfc, 0x7d, - 0x42, 0x00, 0xf5, 0x0d, 0x71, 0x2b, 0x8c, 0xff, 0x93, 0x8b, 0x50, 0x94, 0x2a, 0x86, 0xb8, 0x19, - 0x1e, 0x0b, 0x85, 0x7a, 0x31, 0x0b, 0x52, 0x97, 0xe0, 0xea, 0x85, 0x25, 0x7f, 0x92, 0x9b, 0x10, - 0x2b, 0x0a, 0x83, 0x34, 0x8a, 0x02, 0x1b, 0x30, 0x2b, 0x46, 0x23, 0x93, 0x90, 0xf3, 0xb8, 0x2f, - 0x72, 0xc9, 0xca, 0x79, 0x2e, 0xf9, 0x00, 0x8a, 0x8e, 0xeb, 0x52, 0xd7, 0x76, 0xa2, 0x21, 0xde, - 0xe3, 0x2c, 0x32, 0x6e, 0x5c, 0xa2, 0x23, 0x55, 0x2d, 0x22, 0x35, 0x28, 0xe1, 0x73, 0x8c, 0xbd, - 0x70, 0xa8, 0x37, 0x1c, 0x13, 0x0e, 0x45, 0x46, 0xb6, 0x19, 0x52, 0x97, 0xbc, 0x0e, 0x05, 0x36, - 0x9a, 0x62, 0x3f, 0x88, 0xb3, 0xc5, 0xad, 0x6d, 0xac, 0xf3, 0x0e, 0xbb, 0x77, 0xc6, 0x42, 0x04, - 0xf2, 0x0a, 0xe4, 0x7b, 0xf3, 0x0f, 0x85, 0xa4, 0xaf, 0x24, 0x61, 0x91, 0x31, 0x1a, 0x2b, 0x26, - 0xb7, 0xa0, 0xf8, 0x58, 0x0f, 0x80, 0x3b, 0x97, 0x1a, 0xc6, 0x18, 0x3f, 0x46, 0xac, 0x17, 0x61, - 0x94, 0x87, 0x9b, 0x99, 0x2f, 0x01, 0x24, 0x9f, 0xee, 0xbf, 0xc0, 0x37, 0xbf, 0x01, 0xa5, 0xf8, - 0x93, 0xe4, 0x45, 0x80, 0x7d, 0x7a, 0x60, 0xef, 0x39, 0x1d, 0x57, 0x3c, 0x43, 0x52, 0xb6, 0x4a, - 0xfb, 0xf4, 0xe0, 0x1e, 0x02, 0xc8, 0x05, 0x18, 0xeb, 0xb2, 0x51, 0x95, 0x89, 0x4d, 0xad, 0xd1, - 0x6e, 0x6f, 0x87, 0xcd, 0xd0, 0x59, 0x18, 0xc3, 0xa3, 0x03, 0xb1, 0xd0, 0x26, 0x2c, 0xf9, 0xd3, - 0xfc, 0xbd, 0x1c, 0x86, 0xbc, 0x2b, 0xf5, 0x24, 0x2f, 0xc3, 0x44, 0x33, 0xa0, 0xb8, 0x1d, 0x39, - 0x4c, 0x2d, 0x12, 0xdf, 0x29, 0x27, 0xc0, 0x65, 0x97, 0xbc, 0x06, 0x53, 0x49, 0xa6, 0x55, 0xbb, - 0xb9, 0x23, 0xc2, 0x5f, 0xcb, 0xd6, 0x44, 0x57, 0xa6, 0x5a, 0x5d, 0xd8, 0x41, 0x1f, 0xfa, 0x8a, - 0x1a, 0x6a, 0x16, 0xc9, 0xac, 0xa9, 0x25, 0x6b, 0x4a, 0x81, 0xe3, 0xb5, 0xc3, 0x79, 0x18, 0x75, - 0x9c, 0xdd, 0x9e, 0xc7, 0xfd, 0x79, 0xcb, 0x96, 0xf8, 0x45, 0x3e, 0x0f, 0xd3, 0xa1, 0xb7, 0xdb, - 0x71, 0xa2, 0x5e, 0x20, 0x72, 0x0e, 0xd0, 0x00, 0xa7, 0xd4, 0x84, 0x55, 0x89, 0x0b, 0x16, 0x38, - 0x9c, 0xbc, 0x09, 0x44, 0xfd, 0x9e, 0xbf, 0xf3, 0x31, 0x6d, 0xf2, 0xa9, 0x56, 0xb6, 0xa6, 0x95, - 0x92, 0x35, 0x2c, 0x20, 0x9f, 0x83, 0x72, 0x40, 0x43, 0x54, 0xc9, 0xb0, 0xdb, 0x30, 0x93, 0x8a, - 0x35, 0x2e, 0x61, 0xf7, 0xe9, 0x81, 0x59, 0x87, 0xe9, 0xbe, 0xf5, 0x48, 0xde, 0xe4, 0xda, 0xbd, - 0xd8, 0x9f, 0xcb, 0xdc, 0x98, 0x61, 0x42, 0x2a, 0xf5, 0x82, 0x2f, 0x47, 0x32, 0x3b, 0x50, 0x56, - 0xe5, 0xeb, 0x09, 0x81, 0xc5, 0xe7, 0xd1, 0xb1, 0x90, 0x0b, 0x9f, 0xd1, 0xa3, 0xc3, 0x6a, 0xce, - 0x73, 0xd1, 0x9d, 0xf0, 0x2a, 0x14, 0xa5, 0x96, 0xa0, 0x3e, 0xc3, 0x21, 0x14, 0xca, 0x03, 0x2b, - 0x2e, 0x35, 0x5f, 0x87, 0x31, 0x21, 0x42, 0x8f, 0x3f, 0xc6, 0x31, 0x7f, 0x39, 0x07, 0x53, 0x16, - 0x65, 0x0b, 0x5c, 0x3c, 0x70, 0xf1, 0x9c, 0xe5, 0x9c, 0xd5, 0xda, 0x76, 0x4c, 0x1c, 0xff, 0xf7, - 0x0c, 0x98, 0xc9, 0xc0, 0xfd, 0x44, 0x49, 0xaa, 0x6e, 0x43, 0x69, 0xd1, 0x73, 0x5a, 0x35, 0xd7, - 0x8d, 0x1d, 0x24, 0x51, 0x1b, 0x74, 0xd9, 0x72, 0x72, 0x18, 0x54, 0xdd, 0x4c, 0x63, 0x54, 0x72, - 0x5d, 0x4c, 0x8a, 0x24, 0x8d, 0x9e, 0xcc, 0x6a, 0x0b, 0xbc, 0x4e, 0x49, 0x4e, 0x5b, 0x0c, 0x43, - 0xe3, 0xc0, 0xe4, 0x0e, 0xfc, 0x99, 0x1d, 0xba, 0xec, 0x30, 0xb4, 0x74, 0xf3, 0x86, 0x32, 0x3b, - 0xbf, 0x99, 0x83, 0xf3, 0xd9, 0x84, 0x9f, 0x34, 0xdf, 0x18, 0x26, 0x51, 0x50, 0x12, 0x07, 0x63, - 0xbe, 0x31, 0x9e, 0x71, 0x01, 0xf1, 0x13, 0x04, 0xf2, 0x10, 0x26, 0x56, 0x9c, 0x30, 0xba, 0x47, - 0x9d, 0x20, 0xda, 0xa1, 0x4e, 0x34, 0x84, 0x06, 0x1b, 0xbf, 0x93, 0x8b, 0x9b, 0xda, 0x9e, 0xa4, - 0x4c, 0xbf, 0x93, 0xab, 0xb1, 0x8d, 0x27, 0x4a, 0x61, 0x88, 0x89, 0xf2, 0xb3, 0x30, 0xd5, 0xa0, - 0x6d, 0xa7, 0xbb, 0xe7, 0x07, 0x54, 0x9c, 0x3c, 0xdf, 0x80, 0x89, 0x18, 0x94, 0x39, 0x5b, 0xf4, - 0x62, 0x0d, 0x5f, 0xe9, 0x88, 0x44, 0x94, 0xe8, 0xc5, 0xe6, 0xef, 0xe4, 0xe0, 0x42, 0xad, 0x29, - 0x8e, 0xe9, 0x45, 0x81, 0xbc, 0x4d, 0xfc, 0x8c, 0xbf, 0x4d, 0xe6, 0xa0, 0xf4, 0xc0, 0x79, 0x82, - 0x0f, 0xbe, 0x87, 0x22, 0x6b, 0x0d, 0x57, 0xbf, 0x9c, 0x27, 0x76, 0x7c, 0xec, 0x65, 0x25, 0x38, - 0x4f, 0xf3, 0x4d, 0x78, 0x13, 0x46, 0xef, 0xf9, 0x2d, 0x57, 0x6c, 0x4e, 0xe2, 0xd4, 0x7f, 0x0f, - 0x21, 0x96, 0x28, 0x31, 0x7f, 0x68, 0xc0, 0x64, 0x5c, 0x63, 0xac, 0xc2, 0x67, 0xde, 0x25, 0xa9, - 0xd7, 0xf1, 0x4b, 0x43, 0xbc, 0x8e, 0x3f, 0xf2, 0xe9, 0x7a, 0xc2, 0xfc, 0x27, 0x78, 0xa1, 0xa0, - 0xb6, 0x92, 0xed, 0x44, 0x4a, 0x45, 0x8c, 0x21, 0x2b, 0x92, 0x7b, 0x6a, 0x43, 0x92, 0x1f, 0x38, - 0x24, 0xbf, 0x92, 0x83, 0xf1, 0xb8, 0xb2, 0xcf, 0x59, 0xfc, 0x76, 0xdc, 0xae, 0xa1, 0xbc, 0xb3, - 0x1b, 0x8a, 0xac, 0x10, 0x4e, 0xd0, 0x5f, 0x86, 0x51, 0xb1, 0x98, 0x8c, 0xd4, 0xad, 0x5a, 0x6a, - 0x74, 0x93, 0xb7, 0x4e, 0x71, 0x40, 0x43, 0x4b, 0xd0, 0xa1, 0xfb, 0xfb, 0x36, 0xdd, 0x11, 0xf7, - 0x4b, 0xcf, 0xec, 0x1e, 0x95, 0xed, 0xfe, 0x9e, 0x34, 0x6c, 0xa8, 0xdd, 0xe9, 0x1f, 0x14, 0xa0, - 0x92, 0x26, 0x39, 0x39, 0x42, 0x7e, 0xbd, 0xb7, 0x23, 0xde, 0x29, 0xc0, 0x08, 0xf9, 0x6e, 0x6f, - 0xc7, 0x62, 0x30, 0xf2, 0x1a, 0x14, 0xd6, 0x03, 0xef, 0x11, 0xb6, 0x5a, 0x3c, 0xd3, 0xd0, 0x0d, - 0xbc, 0x47, 0xaa, 0x1f, 0x28, 0x2b, 0x47, 0x83, 0x76, 0xa5, 0xa1, 0x3c, 0x33, 0xcd, 0x0d, 0xda, - 0x56, 0x98, 0x4e, 0x8b, 0x22, 0xd1, 0xd8, 0x56, 0x59, 0xa7, 0x4e, 0x20, 0xa2, 0xb9, 0x85, 0x38, - 0xc3, 0xad, 0x72, 0x07, 0xc1, 0x3c, 0xe7, 0xa9, 0xa5, 0x22, 0x91, 0x16, 0x10, 0xe5, 0xa7, 0x5c, - 0xc0, 0x27, 0xdb, 0x78, 0xf2, 0x79, 0xa1, 0xb3, 0x2a, 0x6b, 0x5b, 0x5d, 0xcd, 0x19, 0x7c, 0x9f, - 0xe6, 0x19, 0xe1, 0x3a, 0x94, 0xf0, 0xc8, 0x0b, 0x0f, 0x32, 0x8a, 0x27, 0x32, 0x93, 0x3e, 0xb7, - 0x80, 0xb7, 0xf1, 0x76, 0x7c, 0x9c, 0x91, 0x30, 0x21, 0xef, 0xc3, 0xb8, 0xea, 0x28, 0xca, 0xdd, - 0x19, 0x2f, 0xf3, 0x08, 0xa1, 0x01, 0xe9, 0xc3, 0x54, 0x02, 0xf3, 0x0b, 0xea, 0x2c, 0x11, 0x9b, - 0xf6, 0xb1, 0xb3, 0xc4, 0xfc, 0x2d, 0x54, 0xe3, 0xdb, 0x7e, 0x44, 0x85, 0xf6, 0xf2, 0xcc, 0xca, - 0xb1, 0xe4, 0x08, 0x79, 0x44, 0xf3, 0x08, 0xd1, 0x5a, 0x77, 0x8a, 0x07, 0x96, 0xff, 0xa1, 0x01, - 0xe7, 0x32, 0x69, 0xc9, 0x0d, 0x80, 0x44, 0x47, 0x14, 0xbd, 0xc4, 0x93, 0xc9, 0xc6, 0x50, 0x4b, - 0xc1, 0x20, 0x5f, 0x4f, 0x6b, 0x77, 0x27, 0x6f, 0x4e, 0xf2, 0xc9, 0x85, 0x49, 0x5d, 0xbb, 0xcb, - 0xd0, 0xe9, 0xcc, 0xef, 0xe5, 0x61, 0xba, 0xef, 0xa9, 0xbe, 0x13, 0xee, 0xe0, 0xf7, 0x53, 0x0f, - 0x41, 0xf1, 0xeb, 0x8e, 0xeb, 0x83, 0x1e, 0x0a, 0xcc, 0x78, 0x16, 0x0a, 0x8f, 0xc5, 0x44, 0x1e, - 0xe3, 0x13, 0x5e, 0x87, 0x0a, 0xb3, 0x9f, 0x10, 0xfb, 0xfc, 0xc0, 0xaf, 0x3d, 0x85, 0xa7, 0xc4, - 0x7e, 0x8c, 0x5f, 0x5a, 0xfa, 0xad, 0x1c, 0xcc, 0xf4, 0xb5, 0xf9, 0x99, 0x5d, 0x75, 0x5f, 0xd6, - 0x76, 0xb7, 0x97, 0x06, 0x8d, 0xe9, 0x50, 0x5a, 0xc4, 0xff, 0x30, 0xe0, 0xc2, 0x00, 0x4a, 0x72, - 0x90, 0x9e, 0x44, 0x5c, 0xab, 0xb8, 0x79, 0xfc, 0x07, 0x9f, 0xca, 0x54, 0xfa, 0xcc, 0x66, 0xc2, - 0x2f, 0xe7, 0x00, 0xb6, 0xe9, 0xce, 0xb3, 0x9d, 0xfe, 0x27, 0xfb, 0x2d, 0x7c, 0xd9, 0xac, 0xa1, - 0xc6, 0x7d, 0x0d, 0x0f, 0x12, 0x87, 0xcf, 0xfd, 0x13, 0x3f, 0x2b, 0x91, 0xcb, 0x7e, 0x56, 0xc2, - 0xdc, 0x81, 0xb3, 0x77, 0x69, 0x94, 0xec, 0x84, 0xd2, 0x86, 0x3c, 0x9e, 0xed, 0x1b, 0x50, 0x12, - 0xf8, 0x7a, 0x8a, 0x70, 0xe9, 0x50, 0xe6, 0xb9, 0x56, 0x82, 0x60, 0x52, 0xb8, 0xb0, 0x48, 0x5b, - 0x34, 0xa2, 0x9f, 0xed, 0x67, 0x1a, 0x40, 0x78, 0x53, 0xf8, 0x6b, 0x03, 0x43, 0x7d, 0xe1, 0xc4, - 0xfe, 0xd9, 0x82, 0x73, 0x71, 0xdd, 0x9f, 0x26, 0xdf, 0x39, 0xa6, 0x4b, 0x88, 0x58, 0xbb, 0x84, - 0xe3, 0x31, 0x87, 0x88, 0x4f, 0xe0, 0x92, 0x24, 0xd8, 0xf6, 0xe2, 0x9b, 0x98, 0xa1, 0x68, 0xc9, - 0xbb, 0x30, 0xae, 0xd0, 0x88, 0xc0, 0x5d, 0xbc, 0xed, 0x7c, 0xec, 0x45, 0x7b, 0x76, 0xc8, 0xe1, - 0xea, 0x6d, 0xa7, 0x82, 0x6e, 0x7e, 0x0d, 0x5e, 0x88, 0xfd, 0x56, 0x32, 0x3e, 0x9d, 0x62, 0x6e, - 0x9c, 0x8e, 0xf9, 0x6a, 0xd2, 0xac, 0xe5, 0x4e, 0xec, 0x3f, 0x2e, 0x79, 0x13, 0xb5, 0x59, 0xa2, - 0x31, 0x97, 0x95, 0xb4, 0x68, 0x62, 0x2f, 0x4a, 0x00, 0xe6, 0x3b, 0x4a, 0x65, 0x33, 0x18, 0x6a, - 0xc4, 0x46, 0x9a, 0xf8, 0x97, 0x73, 0x30, 0xb5, 0xb6, 0xbc, 0xb8, 0x10, 0x1f, 0x23, 0x3f, 0x67, - 0xb9, 0x89, 0xb4, 0xb6, 0x0d, 0x96, 0x37, 0xe6, 0x26, 0xcc, 0xa4, 0xba, 0x01, 0x1f, 0x53, 0x79, - 0x9f, 0xfb, 0x97, 0xc4, 0x60, 0xb9, 0xb3, 0x9c, 0xcf, 0x62, 0xbf, 0x75, 0xcb, 0x4a, 0x61, 0x9b, - 0xdf, 0x1b, 0x4d, 0xf1, 0x15, 0x22, 0xec, 0x0d, 0x28, 0x2d, 0x87, 0x61, 0x8f, 0x06, 0x9b, 0xd6, - 0x8a, 0xaa, 0x23, 0x7a, 0x08, 0xb4, 0x7b, 0x41, 0xcb, 0x4a, 0x10, 0xc8, 0x35, 0x28, 0x8a, 0xf8, - 0x2e, 0x29, 0x13, 0xf0, 0xba, 0x3c, 0x0e, 0x0f, 0xb3, 0xe2, 0x62, 0xf2, 0x16, 0x94, 0xf9, 0xff, - 0x7c, 0xb6, 0x89, 0x0e, 0xc7, 0xb3, 0x2a, 0x81, 0xce, 0x67, 0xa7, 0xa5, 0xa1, 0x31, 0xcb, 0x4c, - 0xbe, 0xd6, 0xc8, 0x6a, 0x54, 0x48, 0x2c, 0x33, 0xf9, 0xb0, 0x23, 0xd6, 0x49, 0x45, 0x22, 0xd7, - 0x21, 0x5f, 0x5b, 0xb0, 0xd4, 0xac, 0xc8, 0x4e, 0x33, 0xe0, 0x59, 0xc5, 0xb5, 0x07, 0x91, 0x6a, - 0x0b, 0x16, 0x99, 0x87, 0x22, 0x3e, 0x78, 0xe1, 0xd2, 0x40, 0xf8, 0x8c, 0xe2, 0xac, 0xe9, 0x0a, - 0x98, 0x7a, 0xf3, 0x28, 0xf1, 0xc8, 0x1c, 0x8c, 0x2d, 0x7a, 0x61, 0xb7, 0xe5, 0x1c, 0x88, 0xa4, - 0x24, 0x78, 0x19, 0xe2, 0x72, 0x90, 0x3a, 0xcf, 0x04, 0x16, 0xb9, 0x06, 0x23, 0x8d, 0xa6, 0xdf, - 0x65, 0xd6, 0x56, 0xec, 0xda, 0x12, 0x32, 0x80, 0x96, 0xd9, 0x80, 0x01, 0x30, 0xe4, 0x98, 0x47, - 0x4e, 0x95, 0x94, 0x90, 0xe3, 0x74, 0xc4, 0x94, 0xc0, 0xe9, 0xf7, 0xff, 0x83, 0xa7, 0xe9, 0xff, - 0xb7, 0x03, 0x17, 0xee, 0xa2, 0xaa, 0xdf, 0xa0, 0x01, 0xe6, 0x81, 0xe4, 0x8f, 0xe7, 0x6c, 0x5a, - 0xcb, 0x22, 0x5a, 0xec, 0xea, 0xd1, 0x61, 0xf5, 0x15, 0x6e, 0x0d, 0xd8, 0x21, 0xc7, 0x91, 0xef, - 0xee, 0xa4, 0x5e, 0x0c, 0x18, 0xc4, 0x88, 0x7c, 0x05, 0xce, 0x66, 0x15, 0x89, 0xb8, 0x31, 0xf4, - 0x0a, 0xcf, 0xfe, 0x80, 0xea, 0x96, 0x9d, 0xc5, 0x81, 0xac, 0x40, 0x85, 0xc3, 0x6b, 0x6e, 0xdb, - 0xeb, 0x2c, 0xb5, 0x1d, 0xaf, 0x85, 0x51, 0x64, 0x22, 0x14, 0x50, 0x70, 0x75, 0x58, 0xa1, 0x4d, - 0x59, 0xa9, 0xe6, 0x9d, 0x94, 0xa2, 0x44, 0x71, 0xd4, 0xa8, 0x3d, 0x58, 0x49, 0xd6, 0xd4, 0xf3, - 0x75, 0x6f, 0xa4, 0xb5, 0xed, 0x98, 0x7b, 0xa3, 0x4d, 0x98, 0x49, 0x75, 0x83, 0x14, 0x47, 0x1a, - 0x38, 0x2d, 0x8e, 0x52, 0x34, 0x56, 0x0a, 0xdb, 0xfc, 0x4f, 0xa3, 0x29, 0xbe, 0xe2, 0xac, 0xc8, - 0x84, 0x51, 0x2e, 0x6d, 0xd4, 0xac, 0x65, 0x5c, 0x16, 0x59, 0xa2, 0x84, 0x5c, 0x84, 0x7c, 0xa3, - 0xb1, 0xa6, 0xe6, 0x54, 0x0c, 0x43, 0xdf, 0x62, 0x30, 0x36, 0x42, 0x78, 0x0c, 0xa4, 0x04, 0x68, - 0x35, 0x69, 0x10, 0x89, 0xe7, 0x3c, 0x5f, 0x4d, 0xd6, 0x71, 0x21, 0xe9, 0x6f, 0xb1, 0x8e, 0x93, - 0xd5, 0xbb, 0x00, 0xb3, 0xb5, 0x30, 0xa4, 0x41, 0xc4, 0x93, 0xb2, 0x87, 0xbd, 0x36, 0x0d, 0xc4, - 0x5c, 0x13, 0x32, 0x86, 0x3f, 0x06, 0xde, 0x0c, 0xad, 0x81, 0x88, 0xe4, 0x2a, 0x14, 0x6b, 0x3d, - 0xd7, 0xa3, 0x9d, 0xa6, 0xe6, 0x9b, 0xee, 0x08, 0x98, 0x15, 0x97, 0x92, 0x8f, 0xe0, 0x9c, 0x20, - 0x92, 0x02, 0x47, 0xf4, 0x00, 0x97, 0x35, 0xdc, 0x82, 0x15, 0x6b, 0x41, 0x8a, 0x29, 0x5b, 0x74, - 0x49, 0x36, 0x25, 0xa9, 0x41, 0x65, 0x09, 0xef, 0x49, 0xe5, 0xa3, 0xbe, 0x7e, 0x20, 0x92, 0xef, - 0xa2, 0xe4, 0xe2, 0x77, 0xa8, 0xb6, 0x1b, 0x17, 0x5a, 0x7d, 0xe8, 0xe4, 0x3e, 0xcc, 0xa4, 0x61, - 0x4c, 0x1e, 0x97, 0x92, 0x47, 0xb7, 0xfa, 0xb8, 0xa0, 0x60, 0xce, 0xa2, 0x22, 0x3b, 0x30, 0x5d, - 0x8b, 0xa2, 0xc0, 0xdb, 0xe9, 0x45, 0x34, 0x25, 0xba, 0xe4, 0x41, 0x63, 0x5c, 0x2e, 0xc5, 0xd7, - 0x0b, 0x62, 0x32, 0xce, 0x38, 0x31, 0x65, 0x2c, 0xc2, 0xac, 0x7e, 0x76, 0xc4, 0x8d, 0xdf, 0xed, - 0x13, 0x6f, 0xdb, 0x89, 0x80, 0x22, 0x79, 0xa0, 0x5b, 0x0b, 0x0f, 0xda, 0x6d, 0x1a, 0x05, 0x78, - 0x73, 0x8f, 0x6f, 0xdf, 0x99, 0xc2, 0x07, 0xe8, 0x92, 0xf2, 0x5c, 0x25, 0xbe, 0x6f, 0xa8, 0xb9, - 0x47, 0x6a, 0x3c, 0xb5, 0xed, 0xa3, 0x3c, 0xe4, 0xf6, 0xd1, 0x82, 0xe9, 0xa5, 0x4e, 0x33, 0x38, - 0xc0, 0xc8, 0x46, 0x59, 0xb9, 0x89, 0x13, 0x2a, 0x27, 0x1f, 0xb6, 0xb8, 0xec, 0xc8, 0x19, 0x96, - 0x55, 0xbd, 0x7e, 0xc6, 0xe6, 0xdf, 0x80, 0x4a, 0xba, 0x2f, 0x3f, 0xe5, 0x63, 0xc5, 0xa7, 0x71, - 0xcd, 0x66, 0x23, 0x9d, 0x6e, 0x0b, 0x99, 0xd3, 0x5e, 0xa4, 0x35, 0x92, 0xa8, 0x74, 0xe5, 0xed, - 0x58, 0xed, 0x1d, 0x5a, 0xb9, 0x8c, 0x73, 0x59, 0xcb, 0xd8, 0xfc, 0xd5, 0x1c, 0x4c, 0x73, 0x6f, - 0xd2, 0x67, 0x5f, 0x57, 0x7c, 0x5f, 0x13, 0xce, 0xf2, 0x2c, 0x30, 0xd5, 0xba, 0x63, 0xb4, 0xc5, - 0x6f, 0xc0, 0xb9, 0xbe, 0xae, 0x40, 0x01, 0xbd, 0x28, 0xfd, 0x78, 0xfb, 0x44, 0xf4, 0x6c, 0xf6, - 0x47, 0xb6, 0x6e, 0x59, 0x7d, 0x14, 0xe6, 0x3f, 0xca, 0xf5, 0xf1, 0x17, 0x7a, 0xa3, 0xaa, 0x09, - 0x1a, 0xa7, 0xd3, 0x04, 0x73, 0x9f, 0x48, 0x13, 0xcc, 0x0f, 0xa3, 0x09, 0x7e, 0x04, 0x13, 0x1b, - 0xd4, 0x61, 0x1a, 0x8d, 0x08, 0x36, 0x2b, 0x68, 0xaf, 0xc5, 0xb2, 0x32, 0x29, 0x5f, 0xe2, 0x40, - 0xd5, 0x88, 0x11, 0x30, 0xd1, 0xc2, 0xa3, 0xcf, 0x2c, 0x9d, 0x83, 0xba, 0x69, 0x8c, 0x0c, 0xde, - 0x34, 0xcc, 0x6f, 0xe6, 0x60, 0x5c, 0x61, 0x4f, 0xbe, 0x08, 0xe5, 0xb5, 0x60, 0xd7, 0xe9, 0x78, - 0x3f, 0xe7, 0x28, 0xc7, 0xaf, 0x58, 0x7d, 0x5f, 0x81, 0x5b, 0x1a, 0x16, 0xba, 0xcd, 0x50, 0xa7, - 0xad, 0x4e, 0x7c, 0x56, 0x3d, 0x0b, 0xa1, 0x4a, 0xb0, 0x70, 0x7e, 0x88, 0x60, 0x61, 0x3d, 0xd2, - 0xb6, 0x70, 0xfa, 0x48, 0x5b, 0x2d, 0x30, 0x76, 0xe4, 0x94, 0x81, 0xb1, 0xe6, 0xaf, 0xe7, 0xa0, - 0x22, 0xde, 0x55, 0x95, 0x87, 0x87, 0xcf, 0xd7, 0x3b, 0x0c, 0x7a, 0xe3, 0x8e, 0xb9, 0x1e, 0x2b, - 0xfc, 0xf6, 0xef, 0x57, 0xf1, 0x95, 0xcc, 0x74, 0x77, 0xc8, 0x57, 0x32, 0x75, 0x78, 0x3a, 0x72, - 0x20, 0x4d, 0x65, 0xa5, 0xf1, 0xcd, 0x3f, 0xcd, 0xa5, 0x79, 0x0b, 0x6d, 0xea, 0x55, 0x18, 0xe3, - 0xcf, 0x62, 0x49, 0xe7, 0x66, 0x91, 0xbb, 0x09, 0x41, 0x96, 0x2c, 0x3b, 0x4d, 0x0c, 0xc9, 0x49, - 0x4f, 0xa5, 0x92, 0xdb, 0x50, 0x46, 0x7f, 0x91, 0x9a, 0xeb, 0x06, 0x34, 0x0c, 0x85, 0xa2, 0x85, - 0x77, 0x77, 0x8f, 0xe9, 0x8e, 0xcd, 0xfd, 0x4a, 0x1c, 0xd7, 0x0d, 0x2c, 0x0d, 0x8f, 0x2c, 0xc0, - 0x59, 0xcd, 0x3d, 0x49, 0xd2, 0x8f, 0x24, 0xbb, 0x45, 0x84, 0x05, 0x9c, 0x38, 0x13, 0xf9, 0xe9, - 0x3d, 0x13, 0x6d, 0xfe, 0x6f, 0x83, 0xad, 0xb5, 0xe6, 0xfe, 0x73, 0x16, 0xdd, 0xc2, 0x9a, 0x74, - 0x8c, 0xb2, 0xff, 0x1f, 0x0c, 0xee, 0x9f, 0x2e, 0xa6, 0xcf, 0xdb, 0x30, 0xca, 0x1f, 0xe1, 0x12, - 0x9e, 0xd4, 0x2a, 0x17, 0x5e, 0x90, 0xdc, 0x4f, 0xf1, 0xa7, 0xbc, 0x2c, 0x41, 0xc0, 0x4c, 0x66, - 0xdd, 0x4d, 0x1e, 0x15, 0xcf, 0x7e, 0xff, 0x78, 0x89, 0xa5, 0xe6, 0x25, 0x1d, 0x2e, 0xdf, 0xb5, - 0x71, 0x72, 0x5e, 0x52, 0xf3, 0xff, 0xe6, 0x78, 0x7b, 0x44, 0xa5, 0x86, 0x4d, 0xb8, 0xf7, 0x1a, - 0x14, 0xf0, 0xb9, 0x57, 0x25, 0xab, 0x61, 0xea, 0xa9, 0x57, 0x2c, 0x67, 0xeb, 0x06, 0x65, 0xad, - 0x1a, 0x50, 0x85, 0xe2, 0x58, 0x5d, 0x37, 0x88, 0x81, 0xd9, 0xa4, 0x7d, 0x97, 0xaa, 0xcb, 0xa1, - 0xa3, 0x27, 0xfe, 0xc6, 0x72, 0x72, 0x5b, 0xf1, 0x6b, 0x56, 0x0f, 0x34, 0xda, 0x0f, 0x1d, 0x9b, - 0xfb, 0xd3, 0xaa, 0xd2, 0x36, 0x71, 0x81, 0x5e, 0x82, 0x49, 0x3d, 0x56, 0x59, 0x18, 0x1d, 0x18, - 0xf2, 0x9d, 0x8a, 0x73, 0x56, 0xd5, 0x5b, 0x9d, 0x88, 0xd4, 0x61, 0x42, 0x0b, 0x48, 0x55, 0x93, - 0xb0, 0xf2, 0xec, 0x30, 0x76, 0x7f, 0x26, 0x05, 0x9d, 0x44, 0x39, 0x30, 0xff, 0x02, 0x54, 0xc4, - 0xca, 0x8c, 0x63, 0xdb, 0x50, 0xb5, 0x5b, 0x5e, 0xb4, 0xd4, 0xd5, 0xd4, 0xf4, 0xdc, 0xc0, 0x42, - 0xa8, 0xf9, 0x5d, 0x03, 0x2e, 0x8a, 0xc7, 0xc5, 0x2c, 0x1a, 0x32, 0x1d, 0x12, 0x03, 0xe2, 0x70, - 0x3e, 0x7e, 0x91, 0xbc, 0x2b, 0x13, 0x4f, 0xe9, 0x02, 0x32, 0xfd, 0x8d, 0xfa, 0x84, 0x98, 0x94, - 0x23, 0x98, 0x7a, 0x4a, 0x26, 0x9c, 0x7a, 0x5b, 0x24, 0x9c, 0xca, 0x1d, 0x4f, 0x1c, 0xaf, 0x0b, - 0x97, 0x76, 0x64, 0xa2, 0xa9, 0xef, 0xe4, 0xe0, 0x5c, 0x46, 0xb5, 0xb6, 0xbe, 0xf8, 0x8c, 0x0a, - 0x87, 0xba, 0x26, 0x1c, 0x64, 0x46, 0xc2, 0x81, 0x1d, 0x9f, 0x29, 0x2b, 0x7e, 0xd7, 0x80, 0x0b, - 0xfa, 0xec, 0x11, 0xb6, 0xe8, 0xd6, 0x2d, 0xf2, 0x0e, 0x8c, 0xde, 0xa3, 0x8e, 0x4b, 0x65, 0x08, - 0x46, 0x9c, 0xdd, 0x4b, 0x9c, 0x0e, 0xf3, 0x42, 0xce, 0xf6, 0x4f, 0xf9, 0x52, 0x3e, 0x63, 0x09, - 0x12, 0xb2, 0x28, 0x2a, 0xc7, 0xaf, 0xa7, 0x4c, 0x79, 0x53, 0x93, 0xf5, 0xa9, 0x63, 0x14, 0xe3, - 0x9f, 0x87, 0x17, 0x8e, 0x21, 0x61, 0xe3, 0xc6, 0x46, 0x5e, 0x1d, 0x37, 0xdc, 0x57, 0x10, 0x4a, - 0xde, 0x87, 0xa9, 0x0d, 0x11, 0x28, 0x26, 0x47, 0x43, 0x49, 0xee, 0x2e, 0x63, 0xc8, 0x6c, 0x39, - 0x2c, 0x69, 0x64, 0xb6, 0xd7, 0xeb, 0x1f, 0x17, 0x3e, 0x0e, 0x2f, 0xc3, 0xe8, 0x3d, 0x5f, 0xc9, - 0xba, 0x8d, 0x83, 0x8b, 0xef, 0x59, 0x79, 0xae, 0x25, 0x8a, 0x62, 0x63, 0x2d, 0x97, 0x79, 0x15, - 0xf1, 0x4d, 0x03, 0x2a, 0x3a, 0xef, 0x4f, 0xdb, 0xdf, 0xef, 0x69, 0xfd, 0xfd, 0x42, 0x76, 0x7f, - 0x0f, 0xee, 0xe8, 0xbe, 0x64, 0x0c, 0x43, 0xf5, 0xb0, 0x09, 0xa3, 0x8b, 0x7e, 0xdb, 0xf1, 0x3a, - 0x6a, 0x02, 0x01, 0x17, 0x21, 0x96, 0x28, 0x51, 0x7a, 0x2b, 0x3f, 0xb0, 0xb7, 0xcc, 0x6f, 0x17, - 0xe0, 0xa2, 0x45, 0x77, 0x3d, 0xa6, 0xf5, 0x6c, 0x86, 0x5e, 0x67, 0x57, 0xbb, 0x28, 0x32, 0x53, - 0x1d, 0x2e, 0xdc, 0xe3, 0x18, 0x24, 0xee, 0xef, 0x6b, 0x50, 0x64, 0xa2, 0x57, 0xe9, 0x73, 0xb4, - 0x64, 0x30, 0x0d, 0x0e, 0x76, 0x7c, 0x5c, 0x4c, 0xae, 0x8b, 0x8d, 0x41, 0x71, 0x60, 0x66, 0x1b, - 0x43, 0xea, 0x39, 0x6e, 0xbe, 0x39, 0xc4, 0x9a, 0x52, 0x61, 0x80, 0xa6, 0xf4, 0x00, 0xce, 0xd6, - 0x5c, 0x2e, 0x74, 0x9c, 0xd6, 0x7a, 0xe0, 0x75, 0x9a, 0x5e, 0xd7, 0x69, 0x49, 0x4d, 0x9b, 0x3f, - 0x4e, 0x1e, 0x97, 0xdb, 0xdd, 0x18, 0xc1, 0xca, 0x24, 0x63, 0xcd, 0x58, 0x5c, 0x6d, 0xf0, 0x2c, - 0x27, 0xa3, 0xc8, 0x02, 0x9b, 0xe1, 0x76, 0x42, 0x9e, 0xe6, 0xc4, 0x8a, 0x8b, 0x51, 0x47, 0xc3, - 0x18, 0x86, 0x8d, 0x95, 0xc6, 0x7d, 0x11, 0x13, 0x20, 0xfd, 0xab, 0x78, 0xc8, 0x43, 0xd4, 0x0a, - 0xd1, 0x28, 0xd7, 0xf0, 0x12, 0xba, 0x46, 0xe3, 0x1e, 0xa3, 0x2b, 0xf6, 0xd1, 0x85, 0xe1, 0x9e, - 0x4a, 0xc7, 0xf1, 0x98, 0xfd, 0xcf, 0x3d, 0x54, 0x70, 0x42, 0x94, 0x12, 0x8d, 0x2e, 0x40, 0x28, - 0xd7, 0xe8, 0x14, 0x14, 0xf2, 0x2e, 0xcc, 0x2c, 0x2d, 0xcc, 0x4b, 0x67, 0xff, 0x45, 0xbf, 0xd9, - 0x6b, 0xd3, 0x4e, 0x84, 0xc1, 0x27, 0x65, 0x3e, 0x86, 0xb4, 0x39, 0xcf, 0x66, 0x41, 0x16, 0x9a, - 0x70, 0xf9, 0xe7, 0x01, 0x63, 0x0b, 0xbe, 0x4b, 0xc3, 0xad, 0x9b, 0xcf, 0x99, 0xcb, 0xbf, 0xd2, - 0x36, 0x5c, 0x6d, 0x37, 0x33, 0x57, 0xe6, 0xdf, 0x45, 0x97, 0xff, 0x3e, 0x5c, 0xf2, 0x93, 0x30, - 0x82, 0x3f, 0xc5, 0x36, 0x3a, 0x93, 0xc1, 0x36, 0xd9, 0x42, 0x9b, 0x3c, 0x61, 0x05, 0x12, 0x90, - 0xe5, 0x24, 0x23, 0xfd, 0x29, 0x1c, 0x57, 0x45, 0xd4, 0xa9, 0xfe, 0x14, 0x89, 0x0b, 0x65, 0xf5, - 0x83, 0x6c, 0x8e, 0xdc, 0x73, 0xc2, 0x3d, 0xea, 0x2e, 0xc8, 0xc7, 0x04, 0xcb, 0x7c, 0x8e, 0xec, - 0x21, 0x14, 0x9f, 0x49, 0xb1, 0x14, 0x14, 0x26, 0x1d, 0x96, 0xc3, 0xcd, 0x50, 0x54, 0x45, 0x98, - 0x36, 0x1e, 0x9a, 0xa4, 0xae, 0x25, 0x8a, 0x50, 0x5a, 0xca, 0x84, 0x8c, 0x81, 0xd3, 0xdc, 0xa7, - 0xc1, 0xd6, 0xcd, 0xcf, 0x42, 0x5a, 0xea, 0xdf, 0x38, 0x66, 0x4c, 0x7e, 0xa3, 0x18, 0xe7, 0x5b, - 0xd1, 0x90, 0x99, 0xe2, 0x97, 0x5c, 0xb7, 0x1b, 0x89, 0xe2, 0x97, 0x5c, 0xb7, 0xab, 0x8a, 0x5f, - 0x8c, 0x1a, 0xa7, 0xc3, 0xcd, 0x9d, 0x90, 0x0e, 0x77, 0x40, 0x06, 0x6e, 0xe9, 0xa9, 0xf9, 0x1c, - 0x3d, 0x46, 0xf0, 0x25, 0x28, 0xd7, 0xa2, 0xc8, 0x69, 0xee, 0x51, 0x17, 0xd3, 0x2e, 0x2b, 0xb7, - 0x7c, 0x8e, 0x80, 0xab, 0x3e, 0x60, 0x2a, 0xae, 0xf2, 0x18, 0xc9, 0xd8, 0x10, 0x8f, 0x91, 0xcc, - 0xc1, 0xd8, 0x72, 0xe7, 0x91, 0xc7, 0xfa, 0xa4, 0x98, 0x64, 0x7c, 0xf0, 0x38, 0x48, 0x7f, 0xc1, - 0x02, 0x41, 0xe4, 0x6d, 0x28, 0xb2, 0x4d, 0x07, 0x63, 0x97, 0x4b, 0x89, 0x7e, 0x2e, 0x9e, 0x41, - 0xde, 0x13, 0x45, 0xea, 0x21, 0xb2, 0x44, 0x27, 0xb7, 0x61, 0x4c, 0x9a, 0xc4, 0x90, 0xe8, 0xe4, - 0x82, 0xd2, 0xe1, 0x25, 0x5a, 0x92, 0x09, 0x61, 0x12, 0xbf, 0xab, 0x07, 0x85, 0x8c, 0x2b, 0xc1, - 0xd6, 0x4a, 0x50, 0x88, 0x16, 0x6c, 0xad, 0x84, 0x87, 0xc4, 0x16, 0x4e, 0xf9, 0x44, 0x0b, 0x67, - 0x0b, 0xca, 0xeb, 0x4e, 0x10, 0x79, 0x6c, 0x3b, 0xea, 0x44, 0x3c, 0x57, 0x56, 0x62, 0x80, 0x2b, - 0x45, 0xf5, 0x97, 0x64, 0xd0, 0x71, 0x57, 0xc1, 0xd7, 0xa3, 0x55, 0x13, 0x38, 0x59, 0xcd, 0x70, - 0x1b, 0x14, 0x99, 0x1d, 0xf1, 0x5e, 0x4f, 0x39, 0x8d, 0x12, 0x2d, 0x52, 0xcf, 0xc7, 0xfb, 0x3d, - 0x0e, 0x6f, 0xf1, 0x31, 0x40, 0x43, 0x70, 0x0a, 0xd9, 0x60, 0xca, 0x10, 0xd4, 0x2b, 0x52, 0xd6, - 0x60, 0x8c, 0x48, 0xbe, 0x0e, 0x65, 0xf6, 0x3f, 0x26, 0x0e, 0xf2, 0x28, 0xcf, 0x85, 0x95, 0xb8, - 0x91, 0xe9, 0x0b, 0x9a, 0x67, 0x17, 0x6a, 0xd0, 0x88, 0x2f, 0x60, 0x64, 0x9c, 0x3e, 0x4d, 0xd1, - 0xb8, 0x99, 0x3f, 0x34, 0xe0, 0xc2, 0x00, 0x1e, 0x43, 0x3f, 0x43, 0x34, 0x97, 0x6c, 0x48, 0x8a, - 0xc1, 0x2d, 0x36, 0x24, 0x75, 0x62, 0xc8, 0xad, 0x29, 0x3b, 0x8b, 0x55, 0xfe, 0x33, 0xcb, 0x62, - 0x65, 0x1e, 0x1a, 0x30, 0xae, 0x8c, 0xec, 0x53, 0x7c, 0x5d, 0xe0, 0x35, 0x91, 0xce, 0x31, 0x9f, - 0xe0, 0xa5, 0x1e, 0x15, 0xe6, 0xe9, 0x1b, 0xbf, 0x01, 0xb0, 0xe2, 0x84, 0x51, 0xad, 0x19, 0x79, - 0x8f, 0xe8, 0x10, 0x62, 0x2c, 0x89, 0xbe, 0x77, 0x30, 0x3b, 0x2a, 0x23, 0xeb, 0x8b, 0xbe, 0x8f, - 0x19, 0x9a, 0xab, 0x30, 0xda, 0xf0, 0x83, 0xa8, 0x7e, 0xc0, 0xf7, 0xa6, 0x45, 0x1a, 0x36, 0xd5, - 0x63, 0x37, 0x0f, 0x0d, 0xf0, 0xa6, 0x25, 0x8a, 0x98, 0x82, 0x78, 0xc7, 0xa3, 0x2d, 0x57, 0xbd, - 0x76, 0x79, 0xc8, 0x00, 0x16, 0x87, 0x5f, 0xff, 0x00, 0xa6, 0x64, 0x46, 0xb9, 0x8d, 0x95, 0x06, - 0xb6, 0x60, 0x0a, 0xc6, 0xb7, 0x96, 0xac, 0xe5, 0x3b, 0x5f, 0xb5, 0xef, 0x6c, 0xae, 0xac, 0x54, - 0xce, 0x90, 0x09, 0x28, 0x09, 0xc0, 0x42, 0xad, 0x62, 0x90, 0x32, 0x14, 0x97, 0x57, 0x1b, 0x4b, - 0x0b, 0x9b, 0xd6, 0x52, 0x25, 0x77, 0xfd, 0x55, 0x98, 0x4c, 0x2e, 0x55, 0x30, 0xf0, 0x72, 0x0c, - 0xf2, 0x56, 0x6d, 0xbb, 0x72, 0x86, 0x00, 0x8c, 0xae, 0xdf, 0x5f, 0x68, 0xdc, 0xbc, 0x59, 0x31, - 0xae, 0x7f, 0x21, 0xe3, 0x25, 0x67, 0xc6, 0xa9, 0x41, 0xbb, 0x4e, 0xe0, 0x44, 0x94, 0x7f, 0xe6, - 0x41, 0xaf, 0x15, 0x79, 0xdd, 0x16, 0x7d, 0x52, 0x31, 0xae, 0xbf, 0xdd, 0xf7, 0x20, 0x33, 0x39, - 0x07, 0xd3, 0x9b, 0xab, 0xb5, 0x07, 0xf5, 0xe5, 0xbb, 0x9b, 0x6b, 0x9b, 0x0d, 0xfb, 0x41, 0x6d, - 0x63, 0xe1, 0x5e, 0xe5, 0x0c, 0xab, 0xf0, 0x83, 0xb5, 0xc6, 0x86, 0x6d, 0x2d, 0x2d, 0x2c, 0xad, - 0x6e, 0x54, 0x8c, 0xeb, 0xbf, 0x66, 0xc0, 0xa4, 0xfe, 0xc4, 0x1d, 0xb9, 0x02, 0x97, 0x37, 0x1b, - 0x4b, 0x96, 0xbd, 0xb1, 0x76, 0x7f, 0x69, 0xd5, 0xde, 0x6c, 0xd4, 0xee, 0x2e, 0xd9, 0x9b, 0xab, - 0x8d, 0xf5, 0xa5, 0x85, 0xe5, 0x3b, 0xcb, 0x4b, 0x8b, 0x95, 0x33, 0xa4, 0x0a, 0x2f, 0x28, 0x18, - 0xd6, 0xd2, 0xc2, 0xda, 0xd6, 0x92, 0x65, 0xaf, 0xd7, 0x1a, 0x8d, 0xed, 0x35, 0x6b, 0xb1, 0x62, - 0x90, 0x4b, 0x70, 0x3e, 0x03, 0xe1, 0xc1, 0x9d, 0x5a, 0x25, 0xd7, 0x57, 0xb6, 0xba, 0xb4, 0x5d, - 0x5b, 0xb1, 0xeb, 0x6b, 0x1b, 0x95, 0xfc, 0xf5, 0x0f, 0x98, 0x16, 0x92, 0xbc, 0x41, 0x41, 0x8a, - 0x50, 0x58, 0x5d, 0x5b, 0x5d, 0xaa, 0x9c, 0x21, 0xe3, 0x30, 0xb6, 0xbe, 0xb4, 0xba, 0xb8, 0xbc, - 0x7a, 0x97, 0x77, 0x6b, 0x6d, 0x7d, 0xdd, 0x5a, 0xdb, 0x5a, 0x5a, 0xac, 0xe4, 0x58, 0xdf, 0x2d, - 0x2e, 0xad, 0xb2, 0x9a, 0xe5, 0xaf, 0x9b, 0x3c, 0xef, 0xb2, 0x96, 0x36, 0x94, 0xf5, 0xd6, 0xd2, - 0x57, 0x36, 0x96, 0x56, 0x1b, 0xcb, 0x6b, 0xab, 0x95, 0x33, 0xd7, 0x2f, 0xa7, 0x70, 0xe4, 0x48, - 0x34, 0x1a, 0xf7, 0x2a, 0x67, 0xae, 0x7f, 0x1d, 0xca, 0xea, 0x26, 0x4c, 0x2e, 0xc0, 0x8c, 0xfa, - 0x7b, 0x9d, 0x76, 0x5c, 0xaf, 0xb3, 0x5b, 0x39, 0x93, 0x2e, 0xb0, 0x7a, 0x9d, 0x0e, 0x2b, 0xc0, - 0xc6, 0xab, 0x05, 0x1b, 0x34, 0x68, 0x7b, 0x1d, 0xb6, 0xbf, 0x56, 0x72, 0xf5, 0xca, 0xf7, 0xff, - 0xe2, 0xa5, 0x33, 0xdf, 0xff, 0xc1, 0x4b, 0xc6, 0x9f, 0xfe, 0xe0, 0x25, 0xe3, 0xbf, 0xfd, 0xe0, - 0x25, 0x63, 0x67, 0x14, 0x27, 0xfa, 0xad, 0xff, 0x1f, 0x00, 0x00, 0xff, 0xff, 0x0e, 0x77, 0x83, - 0xa8, 0x44, 0xb5, 0x00, 0x00, + // 11573 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0xbd, 0x5d, 0x6c, 0x24, 0x49, + 0x72, 0x18, 0x3c, 0xd5, 0xdd, 0x24, 0xbb, 0x83, 0x4d, 0xb2, 0x99, 0x9c, 0x1f, 0xce, 0xec, 0xec, + 0xf6, 0x5c, 0xed, 0xdf, 0xcc, 0xdc, 0xee, 0xf0, 0x86, 0x73, 0x3b, 0xd2, 0xde, 0xfe, 0x5d, 0x37, + 0xc9, 0x99, 0xe1, 0x0e, 0x87, 0xe4, 0x56, 0xf3, 0xe7, 0x4e, 0x77, 0xa7, 0x52, 0xb1, 0x2b, 0x87, + 0xac, 0x65, 0x77, 0x57, 0xab, 0xaa, 0x7a, 0x66, 0x28, 0x7d, 0x82, 0xf4, 0xc1, 0x90, 0x05, 0x41, + 0xd0, 0xfd, 0x08, 0x27, 0x4b, 0x32, 0x64, 0x48, 0x16, 0x2c, 0xd8, 0xb2, 0x71, 0x7e, 0x90, 0x0d, + 0xd8, 0x06, 0x0c, 0x03, 0x02, 0x0c, 0xe1, 0x1e, 0x6c, 0x58, 0x6f, 0x86, 0x64, 0x83, 0xb6, 0xee, + 0xfc, 0x22, 0x02, 0x36, 0x0c, 0xf8, 0x49, 0x67, 0x0b, 0x36, 0x32, 0x32, 0xb3, 0x2a, 0xb3, 0xba, + 0x9a, 0x6c, 0xee, 0xce, 0x02, 0x37, 0xf3, 0x44, 0x76, 0x64, 0x44, 0x54, 0xfe, 0x46, 0x46, 0x64, + 0x46, 0x44, 0xc2, 0x78, 0x74, 0xd0, 0xa5, 0xe1, 0x8d, 0x6e, 0xe0, 0x47, 0x3e, 0x19, 0xc1, 0x1f, + 0x97, 0xce, 0xee, 0xfa, 0xbb, 0x3e, 0x42, 0xe6, 0xd8, 0x7f, 0xbc, 0xf0, 0x52, 0x75, 0xd7, 0xf7, + 0x77, 0x5b, 0x74, 0x0e, 0x7f, 0xed, 0xf4, 0x1e, 0xce, 0x45, 0x5e, 0x9b, 0x86, 0x91, 0xd3, 0xee, + 0x0a, 0x84, 0x85, 0x5d, 0x2f, 0xda, 0xeb, 0xed, 0xdc, 0x68, 0xfa, 0xed, 0xb9, 0xdd, 0xc0, 0x79, + 0xe4, 0x45, 0x4e, 0xe4, 0xf9, 0x1d, 0xa7, 0x35, 0x17, 0xd1, 0x16, 0xed, 0xfa, 0x41, 0x34, 0xe7, + 0x74, 0xbd, 0x39, 0xfc, 0xc6, 0xdc, 0xe3, 0xc0, 0xe9, 0x76, 0x69, 0x90, 0xfc, 0xc3, 0x99, 0x98, + 0x7f, 0x3f, 0x0f, 0xa5, 0xfb, 0x94, 0x76, 0x6b, 0x2d, 0xef, 0x11, 0x25, 0x2f, 0x43, 0x61, 0xd5, + 0x69, 0xd3, 0x59, 0xe3, 0x8a, 0x71, 0xb5, 0x54, 0x9f, 0x3a, 0x3a, 0xac, 0x8e, 0x87, 0x34, 0x78, + 0x44, 0x03, 0xbb, 0xe3, 0xb4, 0xa9, 0x85, 0x85, 0xe4, 0xf3, 0x50, 0x62, 0x7f, 0xc3, 0xae, 0xd3, + 0xa4, 0xb3, 0x39, 0xc4, 0x9c, 0x38, 0x3a, 0xac, 0x96, 0x3a, 0x12, 0x68, 0x25, 0xe5, 0xe4, 0x35, + 0x18, 0x5b, 0xa1, 0x4e, 0x48, 0x97, 0x17, 0x67, 0xf3, 0x57, 0x8c, 0xab, 0xf9, 0x7a, 0xf9, 0xe8, + 0xb0, 0x5a, 0x6c, 0x31, 0x90, 0xed, 0xb9, 0x96, 0x2c, 0x24, 0xcb, 0x30, 0xb6, 0xf4, 0xa4, 0xeb, + 0x05, 0x34, 0x9c, 0x2d, 0x5c, 0x31, 0xae, 0x8e, 0xcf, 0x5f, 0xba, 0xc1, 0xdb, 0x7f, 0x43, 0xb6, + 0xff, 0xc6, 0x86, 0x6c, 0x7f, 0x7d, 0xe6, 0xfb, 0x87, 0xd5, 0x33, 0x47, 0x87, 0xd5, 0x31, 0xca, + 0x49, 0xbe, 0xfd, 0x5f, 0xaa, 0x86, 0x25, 0xe9, 0xc9, 0xbb, 0x50, 0xd8, 0x38, 0xe8, 0xd2, 0xd9, + 0xd2, 0x15, 0xe3, 0xea, 0xe4, 0xfc, 0x4b, 0x37, 0x78, 0x8f, 0xc7, 0x8d, 0x4c, 0xfe, 0x63, 0x58, + 0xf5, 0xe2, 0xd1, 0x61, 0xb5, 0xc0, 0x50, 0x2c, 0xa4, 0x22, 0x6f, 0xc2, 0xe8, 0x3d, 0x3f, 0x8c, + 0x96, 0x17, 0x67, 0x01, 0x9b, 0x76, 0xee, 0xe8, 0xb0, 0x3a, 0xbd, 0xe7, 0x87, 0x91, 0xed, 0xb9, + 0x6f, 0xf8, 0x6d, 0x2f, 0xa2, 0xed, 0x6e, 0x74, 0x60, 0x09, 0x24, 0x73, 0x07, 0x26, 0x34, 0x7e, + 0x64, 0x1c, 0xc6, 0x36, 0x57, 0xef, 0xaf, 0xae, 0x6d, 0xaf, 0x56, 0xce, 0x90, 0x22, 0x14, 0x56, + 0xd7, 0x16, 0x97, 0x2a, 0x06, 0x19, 0x83, 0x7c, 0x6d, 0x7d, 0xbd, 0x92, 0x23, 0x65, 0x28, 0x2e, + 0xd6, 0x36, 0x6a, 0xf5, 0x5a, 0x63, 0xa9, 0x92, 0x27, 0x33, 0x30, 0xb5, 0xbd, 0xbc, 0xba, 0xb8, + 0xb6, 0xdd, 0xb0, 0x17, 0x97, 0x1a, 0xf7, 0x37, 0xd6, 0xd6, 0x2b, 0x05, 0x32, 0x09, 0x70, 0x7f, + 0xb3, 0xbe, 0x64, 0xad, 0x2e, 0x6d, 0x2c, 0x35, 0x2a, 0x23, 0xe6, 0xaf, 0xe4, 0xa1, 0xf8, 0x80, + 0x46, 0x8e, 0xeb, 0x44, 0x0e, 0xb9, 0xac, 0x0d, 0x11, 0xd6, 0x5e, 0x19, 0x9b, 0x97, 0xfb, 0xc7, + 0x66, 0xe4, 0xe8, 0xb0, 0x6a, 0xbc, 0xa9, 0x8e, 0xc9, 0x3b, 0x30, 0xbe, 0x48, 0xc3, 0x66, 0xe0, + 0x75, 0xd9, 0x7c, 0xc1, 0x71, 0x29, 0xd5, 0x2f, 0x1e, 0x1d, 0x56, 0xcf, 0xb9, 0x09, 0x58, 0x69, + 0xab, 0x8a, 0x4d, 0x96, 0x61, 0x74, 0xc5, 0xd9, 0xa1, 0xad, 0x70, 0x76, 0xe4, 0x4a, 0xfe, 0xea, + 0xf8, 0xfc, 0x0b, 0xa2, 0x7f, 0x65, 0x05, 0x6f, 0xf0, 0xd2, 0xa5, 0x4e, 0x14, 0x1c, 0xd4, 0xcf, + 0x1e, 0x1d, 0x56, 0x2b, 0x2d, 0x04, 0xa8, 0x7d, 0xc7, 0x51, 0x48, 0x23, 0x19, 0xf3, 0xd1, 0x13, + 0xc7, 0xfc, 0xc5, 0xef, 0x1f, 0x56, 0x0d, 0x36, 0x16, 0x62, 0xcc, 0x13, 0x7e, 0xfa, 0xe8, 0x5f, + 0x81, 0xdc, 0xf2, 0xe2, 0xec, 0x18, 0xce, 0xb5, 0xca, 0xd1, 0x61, 0xb5, 0xac, 0x0d, 0x5b, 0x6e, + 0x79, 0xf1, 0xd2, 0xdb, 0x30, 0xae, 0xd4, 0x91, 0x54, 0x20, 0xbf, 0x4f, 0x0f, 0x78, 0x7f, 0x5a, + 0xec, 0x5f, 0x72, 0x16, 0x46, 0x1e, 0x39, 0xad, 0x9e, 0xe8, 0x40, 0x8b, 0xff, 0xf8, 0x52, 0xee, + 0x27, 0x0d, 0xf3, 0x37, 0x0a, 0x50, 0xb4, 0x7c, 0xbe, 0xce, 0xc8, 0x35, 0x18, 0x69, 0x44, 0x4e, + 0x24, 0x87, 0x62, 0xe6, 0xe8, 0xb0, 0x3a, 0x15, 0x32, 0x80, 0xf2, 0x3d, 0x8e, 0xc1, 0x50, 0xd7, + 0xf7, 0x9c, 0x50, 0x0e, 0x09, 0xa2, 0x76, 0x19, 0x40, 0x45, 0x45, 0x0c, 0xf2, 0x1a, 0x14, 0x1e, + 0xf8, 0x2e, 0x15, 0xa3, 0x42, 0x8e, 0x0e, 0xab, 0x93, 0x6d, 0xdf, 0x55, 0x11, 0xb1, 0x9c, 0xbc, + 0x01, 0xa5, 0x85, 0x5e, 0x10, 0xd0, 0x0e, 0x9b, 0xaa, 0x05, 0x44, 0x9e, 0x3c, 0x3a, 0xac, 0x42, + 0x93, 0x03, 0xd9, 0xe2, 0x4a, 0x10, 0x58, 0x57, 0x37, 0x22, 0x27, 0x88, 0xa8, 0x3b, 0x3b, 0x32, + 0x54, 0x57, 0xb3, 0xe5, 0x35, 0x1d, 0x72, 0x92, 0x74, 0x57, 0x0b, 0x4e, 0xe4, 0x1e, 0x8c, 0xdf, + 0x0d, 0x9c, 0x26, 0x5d, 0xa7, 0x81, 0xe7, 0xbb, 0x38, 0x86, 0xf9, 0xfa, 0x6b, 0x47, 0x87, 0xd5, + 0xf3, 0xbb, 0x0c, 0x6c, 0x77, 0x11, 0x9e, 0x50, 0xff, 0xe8, 0xb0, 0x5a, 0x5c, 0xec, 0x05, 0xd8, + 0x7b, 0x96, 0x4a, 0x4a, 0x7e, 0x86, 0x0d, 0x49, 0x18, 0x61, 0xd7, 0x52, 0x17, 0x47, 0xef, 0xf8, + 0x2a, 0x9a, 0xa2, 0x8a, 0xe7, 0x5b, 0x4e, 0x18, 0xd9, 0x01, 0xa7, 0x4b, 0xd5, 0x53, 0x65, 0x49, + 0xd6, 0xa0, 0xd8, 0x68, 0xee, 0x51, 0xb7, 0xd7, 0xa2, 0xb3, 0x45, 0x64, 0x7f, 0x41, 0x4c, 0x5c, + 0x39, 0x9e, 0xb2, 0xb8, 0x7e, 0x49, 0xf0, 0x26, 0xa1, 0x80, 0x28, 0x7d, 0x1f, 0x33, 0xf9, 0x52, + 0xf1, 0xb7, 0x7f, 0xbf, 0x7a, 0xe6, 0x97, 0xfe, 0xf3, 0x95, 0x33, 0xe6, 0xbf, 0xc8, 0x41, 0x25, + 0xcd, 0x84, 0x3c, 0x84, 0x89, 0xcd, 0xae, 0xeb, 0x44, 0x74, 0xa1, 0xe5, 0xd1, 0x4e, 0x14, 0xe2, + 0x24, 0x39, 0xbe, 0x4d, 0xaf, 0x88, 0xef, 0xce, 0xf6, 0x90, 0xd0, 0x6e, 0x72, 0xca, 0x54, 0xab, + 0x74, 0xb6, 0xc9, 0x77, 0x1a, 0x28, 0xa7, 0x43, 0x9c, 0x61, 0xa7, 0xfb, 0x0e, 0x97, 0xf0, 0x03, + 0xbe, 0x23, 0xd8, 0x8a, 0x09, 0xd4, 0x71, 0x77, 0x0e, 0x70, 0x66, 0x0e, 0x3f, 0x81, 0x18, 0x49, + 0xc6, 0x04, 0x62, 0x60, 0xf3, 0xbf, 0x19, 0x30, 0x69, 0xd1, 0xd0, 0xef, 0x05, 0x4d, 0x7a, 0x8f, + 0x3a, 0x2e, 0x0d, 0xd8, 0xf4, 0xbf, 0xef, 0x75, 0x5c, 0xb1, 0xa6, 0x70, 0xfa, 0xef, 0x7b, 0x1d, + 0x75, 0x09, 0x63, 0x39, 0xf9, 0x02, 0x8c, 0x35, 0x7a, 0x3b, 0x88, 0xca, 0xd7, 0xd4, 0x79, 0x1c, + 0xb1, 0xde, 0x8e, 0x9d, 0x42, 0x97, 0x68, 0x64, 0x0e, 0xc6, 0xb6, 0x68, 0x10, 0x26, 0x12, 0x0f, + 0x25, 0xfb, 0x23, 0x0e, 0x52, 0x09, 0x04, 0x16, 0xb9, 0x9b, 0x48, 0x5d, 0xb1, 0x27, 0x4d, 0xa5, + 0x64, 0x5d, 0x32, 0x55, 0xda, 0x02, 0xa2, 0x4e, 0x15, 0x89, 0x65, 0x7e, 0x27, 0x07, 0x95, 0x45, + 0x27, 0x72, 0x76, 0x9c, 0x50, 0xf4, 0xe7, 0xd6, 0x2d, 0x26, 0xc7, 0x95, 0x86, 0xa2, 0x1c, 0x67, + 0x35, 0xff, 0xc4, 0xcd, 0x7b, 0x35, 0xdd, 0xbc, 0x71, 0xb6, 0x41, 0x8a, 0xe6, 0x25, 0x8d, 0x7a, + 0xef, 0xe4, 0x46, 0x55, 0x44, 0xa3, 0x8a, 0xb2, 0x51, 0x49, 0x53, 0xc8, 0x7b, 0x50, 0x68, 0x74, + 0x69, 0x53, 0x08, 0x11, 0x29, 0xfb, 0xf5, 0xc6, 0x31, 0x84, 0xad, 0x5b, 0xf5, 0xb2, 0x60, 0x53, + 0x08, 0xbb, 0xb4, 0x69, 0x21, 0x99, 0xb2, 0x68, 0xbe, 0x3b, 0x0a, 0x67, 0xb3, 0xc8, 0xc8, 0x7b, + 0xfa, 0xe6, 0xc4, 0xbb, 0xe7, 0x85, 0x81, 0x9b, 0xd3, 0xac, 0xa1, 0x6f, 0x4f, 0xd7, 0xa1, 0xb8, + 0xce, 0x26, 0x64, 0xd3, 0x6f, 0x89, 0x9e, 0x63, 0x52, 0xb1, 0xd8, 0x95, 0x30, 0xc3, 0x8a, 0xcb, + 0xc9, 0x0b, 0x90, 0xdf, 0xb4, 0x96, 0x45, 0x77, 0x95, 0x8e, 0x0e, 0xab, 0xf9, 0x5e, 0xe0, 0xcd, + 0x1a, 0x16, 0x83, 0x92, 0x39, 0x18, 0x5d, 0xa8, 0x2d, 0xd0, 0x20, 0xc2, 0x6e, 0x2a, 0xd7, 0x2f, + 0xb0, 0xd9, 0xd2, 0x74, 0xec, 0x26, 0x0d, 0x22, 0xed, 0xf3, 0x02, 0x8d, 0x7c, 0x1e, 0xf2, 0xb5, + 0xed, 0x86, 0xe8, 0x19, 0x10, 0x3d, 0x53, 0xdb, 0x6e, 0xd4, 0x27, 0x44, 0x47, 0xe4, 0x9d, 0xc7, + 0x21, 0xe3, 0x5e, 0xdb, 0x6e, 0xa8, 0xa3, 0x35, 0x7a, 0xcc, 0x68, 0x5d, 0x85, 0x22, 0xd3, 0x33, + 0xd8, 0x06, 0x8f, 0x42, 0xb1, 0xc4, 0xd5, 0xa7, 0x3d, 0x01, 0xb3, 0xe2, 0x52, 0xf2, 0x72, 0xac, + 0xb6, 0x14, 0x13, 0x7e, 0x42, 0x6d, 0x91, 0xca, 0x0a, 0x79, 0x02, 0x13, 0x8b, 0x07, 0x1d, 0xa7, + 0xed, 0x35, 0xc5, 0x16, 0x5e, 0xc2, 0x2d, 0xfc, 0xc6, 0x31, 0xc3, 0x78, 0x43, 0x23, 0xe0, 0xbb, + 0xba, 0x14, 0xbe, 0xb3, 0x2e, 0x2f, 0xb3, 0xd3, 0x3b, 0xfc, 0xac, 0x61, 0xe9, 0x1f, 0x62, 0x6b, + 0x49, 0x8a, 0x48, 0xd4, 0xab, 0x92, 0x69, 0x27, 0xc1, 0xc9, 0x5a, 0x0a, 0x04, 0x44, 0x5d, 0x4b, + 0xf1, 0xa6, 0xfb, 0x1e, 0xe4, 0xef, 0x2e, 0xac, 0xcf, 0x8e, 0x23, 0x0f, 0x22, 0x78, 0xdc, 0x5d, + 0x58, 0x5f, 0x68, 0xf9, 0x3d, 0xb7, 0xf1, 0xd1, 0x4a, 0xfd, 0x82, 0x60, 0x33, 0xb1, 0xdb, 0xec, + 0x6a, 0x35, 0x62, 0x74, 0x64, 0x09, 0x8a, 0xb2, 0x95, 0xb3, 0x65, 0xe4, 0x31, 0x9d, 0x6a, 0xfc, + 0xd6, 0x2d, 0xbe, 0xd6, 0x5c, 0xf1, 0x5b, 0xad, 0x85, 0xc4, 0xb9, 0xb4, 0x0d, 0xa4, 0xbf, 0x5f, + 0x32, 0x34, 0x89, 0xcf, 0xab, 0x9a, 0xc4, 0xf8, 0xfc, 0x39, 0xf1, 0xad, 0x05, 0xbf, 0xdd, 0x76, + 0x3a, 0x2e, 0xd2, 0x6e, 0xcd, 0xab, 0x0a, 0x46, 0x0d, 0x26, 0x93, 0x8a, 0xac, 0x78, 0x61, 0x44, + 0xe6, 0xa0, 0x24, 0x21, 0x6c, 0x13, 0xc9, 0x67, 0x56, 0xd9, 0x4a, 0x70, 0xcc, 0x3f, 0xcd, 0x01, + 0x24, 0x25, 0xcf, 0xa8, 0x9c, 0xf9, 0x09, 0x4d, 0xce, 0x9c, 0x4b, 0x4f, 0xd0, 0x81, 0x12, 0x86, + 0x7c, 0x00, 0xa3, 0x4c, 0xe5, 0xea, 0x49, 0x95, 0xf2, 0x42, 0x9a, 0x14, 0x0b, 0xb7, 0x6e, 0xd5, + 0x27, 0x05, 0xf1, 0x68, 0x88, 0x10, 0x4b, 0x90, 0x29, 0x22, 0xea, 0x7f, 0x14, 0x92, 0xc1, 0x10, + 0xc2, 0xe9, 0xaa, 0x22, 0x5d, 0x8c, 0x64, 0x3d, 0x4a, 0xe9, 0xa2, 0xc8, 0x96, 0x8b, 0x5c, 0xb6, + 0xf0, 0x4e, 0x1d, 0x13, 0xb2, 0x25, 0x2d, 0x59, 0x78, 0x07, 0x9e, 0x28, 0x59, 0xba, 0xe9, 0x65, + 0x5b, 0xc0, 0x69, 0x70, 0x35, 0xb3, 0x57, 0xb2, 0x16, 0xec, 0x95, 0x93, 0x16, 0x6c, 0x7a, 0xb9, + 0xde, 0x1a, 0x24, 0xcb, 0xce, 0xc9, 0xd5, 0xe5, 0x3c, 0x56, 0xc9, 0x51, 0xa6, 0xbd, 0xc3, 0x97, + 0xe6, 0xe8, 0xc0, 0xa5, 0x79, 0x2e, 0x73, 0x69, 0xf2, 0x85, 0xf9, 0x0e, 0x8c, 0xd4, 0x7e, 0xae, + 0x17, 0x50, 0xa1, 0xfb, 0x95, 0xe5, 0x37, 0x19, 0x2c, 0x5e, 0xd3, 0x53, 0x0e, 0xfb, 0xa9, 0xea, + 0xcc, 0x58, 0xce, 0xbe, 0xbc, 0xb1, 0xd2, 0x10, 0x7a, 0x1d, 0x49, 0x75, 0xcb, 0xc6, 0x8a, 0x52, + 0xed, 0x48, 0x6b, 0x35, 0xa3, 0x22, 0x73, 0x90, 0xab, 0x2d, 0xa2, 0xb1, 0x38, 0x3e, 0x5f, 0x92, + 0x9f, 0x5d, 0xac, 0x9f, 0x15, 0x24, 0x65, 0x47, 0xb3, 0x1f, 0x6a, 0x8b, 0x9f, 0xdd, 0xe2, 0x6f, + 0x29, 0x6a, 0x82, 0x98, 0xa6, 0xcc, 0x1c, 0x15, 0x93, 0xc5, 0x48, 0x94, 0x96, 0xbe, 0xc9, 0x12, + 0x4f, 0x95, 0x6b, 0x7c, 0xe0, 0x72, 0x7d, 0x03, 0x37, 0xae, 0x6c, 0x42, 0x38, 0x5c, 0xe6, 0x5f, + 0x19, 0x88, 0x4b, 0xde, 0x80, 0x51, 0x8b, 0xee, 0x26, 0x7b, 0x2d, 0xda, 0x6c, 0x01, 0x42, 0xd4, + 0x0f, 0x70, 0x1c, 0x14, 0xe4, 0xd4, 0x0d, 0xf7, 0xbc, 0x87, 0x91, 0xf8, 0x4a, 0x2c, 0xc8, 0x05, + 0x58, 0x11, 0xe4, 0x02, 0xa2, 0x09, 0x72, 0x01, 0x63, 0x53, 0xcc, 0x5a, 0x6c, 0x08, 0x65, 0x52, + 0xd6, 0xd4, 0x5a, 0x54, 0xc6, 0x2a, 0x70, 0xb5, 0xb1, 0xb2, 0x16, 0x1b, 0xe4, 0x36, 0x94, 0x6a, + 0xcd, 0xa6, 0xdf, 0x53, 0x8c, 0x9e, 0xd9, 0xa3, 0xc3, 0xea, 0x59, 0x87, 0x03, 0x75, 0x13, 0x3d, + 0x41, 0x35, 0xeb, 0x49, 0xad, 0x19, 0x8f, 0x85, 0x56, 0x2f, 0x8c, 0x68, 0xb0, 0xbc, 0x28, 0x9a, + 0x8c, 0x3c, 0x9a, 0x1c, 0x98, 0xe2, 0x11, 0xa3, 0x9a, 0xff, 0xc9, 0xc0, 0x1a, 0x93, 0xb7, 0x01, + 0x96, 0x3b, 0x4c, 0xb1, 0x6d, 0xd2, 0x98, 0x01, 0x1a, 0xcf, 0x9e, 0x80, 0xea, 0x1c, 0x14, 0x64, + 0xfd, 0xd3, 0xb9, 0xa1, 0x3f, 0xcd, 0x3e, 0x29, 0xd5, 0x64, 0x71, 0x8e, 0x22, 0x3e, 0x19, 0x08, + 0x68, 0xea, 0x93, 0x09, 0x32, 0x79, 0x0d, 0xc6, 0x96, 0x6b, 0x0f, 0x6a, 0xbd, 0x68, 0x0f, 0xfb, + 0xab, 0xc8, 0x05, 0x96, 0xe7, 0xb4, 0x6d, 0xa7, 0x17, 0xed, 0x59, 0xb2, 0xd0, 0xfc, 0x25, 0x03, + 0xc6, 0x95, 0xb5, 0xca, 0xaa, 0xba, 0x1e, 0xf8, 0x1f, 0xd3, 0x66, 0xa4, 0xf7, 0x52, 0x97, 0x03, + 0x53, 0x55, 0x8d, 0x51, 0x53, 0xbd, 0x93, 0x3b, 0x45, 0xef, 0x98, 0x73, 0x42, 0x04, 0x30, 0x1b, + 0x40, 0x39, 0xe2, 0x40, 0x1b, 0x80, 0xe9, 0x38, 0xaa, 0x0d, 0xc0, 0xca, 0xcd, 0x3f, 0x32, 0xd8, + 0xd2, 0x25, 0x73, 0x00, 0xf7, 0xe9, 0x41, 0xe4, 0xec, 0xdc, 0xf1, 0x5a, 0xda, 0xd1, 0xd5, 0x3e, + 0x42, 0xed, 0x87, 0x5e, 0x8b, 0x5a, 0x0a, 0x0a, 0xb9, 0x05, 0xc5, 0xfb, 0xc1, 0xce, 0x5b, 0x88, + 0x9e, 0x8b, 0x45, 0xf0, 0xcc, 0x7e, 0xb0, 0xf3, 0x16, 0x22, 0xab, 0xf3, 0x55, 0x22, 0x12, 0x13, + 0x46, 0x17, 0xfd, 0xb6, 0xe3, 0xc9, 0x6d, 0x0f, 0xd8, 0xde, 0xe1, 0x22, 0xc4, 0x12, 0x25, 0x4c, + 0xe8, 0x37, 0xd6, 0x57, 0xc5, 0xc4, 0x44, 0xa1, 0x1f, 0x76, 0x3b, 0x16, 0x83, 0x99, 0xdf, 0x33, + 0x60, 0x5c, 0x91, 0x48, 0xe4, 0x8b, 0xc2, 0xcc, 0x37, 0xf0, 0x90, 0xea, 0x7c, 0xbf, 0xcc, 0x62, + 0xa5, 0x7c, 0xbb, 0x66, 0xe6, 0xbf, 0x30, 0xfa, 0x13, 0x69, 0x90, 0x1b, 0x46, 0x1a, 0xbc, 0x0d, + 0xc0, 0x75, 0x39, 0xec, 0x4e, 0x65, 0xde, 0x28, 0x87, 0x7a, 0xea, 0x60, 0x24, 0xc8, 0xe6, 0xff, + 0x9f, 0x83, 0xa2, 0xb0, 0x55, 0xe6, 0x9f, 0x51, 0x1d, 0xe2, 0x2d, 0x4d, 0x87, 0x98, 0x11, 0xa4, + 0x8a, 0x72, 0x3b, 0x7f, 0x82, 0x8d, 0xf2, 0x36, 0x94, 0x65, 0x17, 0xa0, 0x2a, 0x76, 0x0d, 0xc6, + 0xa4, 0x95, 0xcd, 0x15, 0xb1, 0x29, 0x8d, 0xe7, 0xd6, 0xbc, 0x25, 0xcb, 0xcd, 0xef, 0x8c, 0x48, + 0x5a, 0xfe, 0x25, 0xd6, 0x85, 0x35, 0xd7, 0x0d, 0xd4, 0x2e, 0x74, 0x5c, 0x37, 0xb0, 0x10, 0xca, + 0x06, 0x6a, 0xbd, 0xb7, 0xd3, 0xf2, 0x9a, 0x88, 0xa3, 0xac, 0x9a, 0x2e, 0x42, 0x6d, 0x86, 0xaa, + 0x0e, 0x54, 0x82, 0xac, 0x99, 0x08, 0xf9, 0x63, 0x4d, 0x84, 0x9f, 0x86, 0xd2, 0x42, 0xdb, 0xd5, + 0x54, 0x08, 0x33, 0xa3, 0x53, 0x6e, 0xc4, 0x48, 0x5c, 0x79, 0xb8, 0x2c, 0xfa, 0xe8, 0x6c, 0xb3, + 0xed, 0xf6, 0x2b, 0x0e, 0x09, 0x4b, 0x4d, 0xc7, 0x1f, 0xf9, 0x34, 0x3a, 0xfe, 0x6d, 0x28, 0x6d, + 0x86, 0x74, 0xa3, 0xd7, 0xe9, 0xd0, 0x16, 0xaa, 0x13, 0x45, 0x2e, 0x7b, 0x7a, 0x21, 0xb5, 0x23, + 0x84, 0xaa, 0x15, 0x88, 0x51, 0xd5, 0x69, 0x35, 0x76, 0xcc, 0xb4, 0xfa, 0x22, 0x14, 0x6a, 0xdd, + 0xae, 0x34, 0x7e, 0xe2, 0x4d, 0xb2, 0xdb, 0xc5, 0xad, 0x6f, 0xd2, 0xe9, 0x76, 0x75, 0x53, 0x06, + 0xb1, 0x09, 0x05, 0x72, 0xbf, 0xb7, 0x43, 0x83, 0x0e, 0x8d, 0x68, 0x28, 0x44, 0x73, 0x38, 0x0b, + 0xc8, 0x63, 0x56, 0x9e, 0x31, 0xa7, 0x11, 0xd0, 0x70, 0xbd, 0xb0, 0xdf, 0xdb, 0xa1, 0xb6, 0x90, + 0xf1, 0x6a, 0xdf, 0x65, 0x30, 0xbc, 0xd4, 0x80, 0x49, 0xbd, 0xff, 0x9f, 0x82, 0x62, 0xf1, 0x61, + 0xa1, 0x58, 0xac, 0x94, 0xcc, 0x5f, 0xc9, 0xc1, 0x78, 0xad, 0xdb, 0x7d, 0xc6, 0x4f, 0x20, 0x7e, + 0x52, 0x5b, 0xd5, 0xe7, 0x93, 0xd1, 0x3b, 0xc5, 0xe1, 0xc3, 0x5f, 0x1b, 0x30, 0x95, 0xa2, 0x50, + 0x6b, 0x6f, 0x0c, 0x69, 0x91, 0xe7, 0x86, 0xb4, 0xc8, 0xf3, 0x83, 0x2d, 0x72, 0x75, 0xcd, 0x14, + 0x3e, 0xcd, 0x9a, 0x79, 0x1d, 0xf2, 0xb5, 0x6e, 0x57, 0xf4, 0x4a, 0x39, 0xe9, 0x95, 0xad, 0x5b, + 0x7c, 0x23, 0x72, 0xba, 0x5d, 0x8b, 0x61, 0x98, 0x6f, 0x42, 0x09, 0xc1, 0x28, 0xd1, 0xae, 0x88, + 0xa5, 0xc0, 0xc5, 0x99, 0x46, 0xc6, 0xa7, 0xbd, 0xf9, 0xbf, 0x0d, 0x18, 0xc1, 0xdf, 0xcf, 0xe8, + 0x74, 0x99, 0xd7, 0xa6, 0x4b, 0x45, 0x99, 0x2e, 0xc3, 0x4c, 0x94, 0x3f, 0xce, 0x63, 0x6f, 0x89, + 0x29, 0x22, 0x6c, 0x3a, 0x23, 0xc3, 0xa6, 0xfb, 0x14, 0x02, 0x7c, 0x3f, 0x6d, 0xdd, 0xe5, 0x71, + 0x30, 0x5e, 0x4e, 0x57, 0xf5, 0xa9, 0x18, 0x76, 0xf7, 0x80, 0x2c, 0x77, 0x42, 0xda, 0xec, 0x05, + 0xb4, 0xb1, 0xef, 0x75, 0xb7, 0x68, 0xe0, 0x3d, 0x3c, 0x10, 0x9a, 0x21, 0xca, 0x58, 0x4f, 0x94, + 0xda, 0xe1, 0xbe, 0xd7, 0xb5, 0x1f, 0x61, 0xb9, 0x95, 0x41, 0x43, 0x3e, 0x80, 0x31, 0x8b, 0x3e, + 0x0e, 0xbc, 0x88, 0x8a, 0xbe, 0x9d, 0x8c, 0xed, 0x00, 0x84, 0x72, 0xdd, 0x24, 0xe0, 0x3f, 0xd4, + 0xf1, 0x17, 0xe5, 0x9f, 0x9d, 0x19, 0xf5, 0xdd, 0x11, 0x5c, 0x0b, 0x27, 0xdc, 0x94, 0x1d, 0x63, + 0xa0, 0xeb, 0x83, 0x99, 0x3f, 0xcd, 0x60, 0x6e, 0x41, 0x99, 0x99, 0x6e, 0x29, 0x4b, 0xfd, 0x72, + 0x32, 0x96, 0x37, 0xd4, 0xe2, 0xe3, 0x2e, 0xc9, 0x34, 0x3e, 0xc4, 0x4e, 0x4f, 0x12, 0x7e, 0xf9, + 0xf6, 0xa2, 0xc2, 0x38, 0x63, 0x7a, 0xc4, 0xa2, 0xa3, 0xc9, 0x3b, 0xeb, 0xd4, 0x13, 0x63, 0xf4, + 0xd3, 0x4d, 0x8c, 0xb1, 0x4f, 0x32, 0x31, 0xd2, 0xd7, 0x93, 0xc5, 0xd3, 0x5c, 0x4f, 0x5e, 0xfa, + 0x00, 0xa6, 0xfb, 0x7a, 0xf8, 0x34, 0x57, 0x7c, 0x9f, 0xdd, 0xb4, 0xfc, 0x85, 0xb8, 0x5f, 0xc8, + 0x3c, 0x9a, 0xa3, 0x5e, 0x40, 0x9b, 0x11, 0x8a, 0x5e, 0x21, 0x2d, 0x03, 0x01, 0x4b, 0xd9, 0xcb, + 0x08, 0x23, 0xef, 0xc3, 0x18, 0xbf, 0x22, 0x09, 0x67, 0x73, 0x38, 0xf6, 0x13, 0xe2, 0x8b, 0x1c, + 0x2a, 0xee, 0xa9, 0x39, 0x86, 0xda, 0xab, 0x82, 0xc8, 0xbc, 0x0b, 0xa3, 0xe2, 0x8a, 0xe5, 0xf8, + 0x75, 0x51, 0x85, 0x91, 0xad, 0xa4, 0x67, 0xf0, 0x58, 0x9c, 0x37, 0xc2, 0xe2, 0x70, 0xf3, 0xd7, + 0x0c, 0x98, 0xd4, 0x5b, 0x49, 0x6e, 0xc0, 0xa8, 0xb8, 0x03, 0x34, 0xf0, 0x0e, 0x90, 0xb5, 0x66, + 0x94, 0xdf, 0xfe, 0x69, 0x77, 0x7e, 0x02, 0x8b, 0x89, 0x7e, 0xc1, 0x01, 0xdb, 0x22, 0x44, 0xbf, + 0x98, 0xa4, 0x96, 0x2c, 0x63, 0x26, 0x97, 0x45, 0xc3, 0x5e, 0x2b, 0x52, 0x4d, 0xae, 0x00, 0x21, + 0x96, 0x28, 0x31, 0x0f, 0x0d, 0x80, 0x46, 0xe3, 0xde, 0x7d, 0x7a, 0xb0, 0xee, 0x78, 0x01, 0x9a, + 0xad, 0xb8, 0x1a, 0xef, 0x8b, 0xd1, 0x2a, 0x0b, 0xb3, 0x95, 0xaf, 0xdc, 0x7d, 0x7a, 0xa0, 0x99, + 0xad, 0x12, 0x15, 0x97, 0x7c, 0xe0, 0x3d, 0x72, 0x22, 0xca, 0x08, 0x73, 0x48, 0xc8, 0x97, 0x3c, + 0x87, 0xa6, 0x28, 0x15, 0x64, 0xf2, 0x0d, 0x98, 0x4c, 0x7e, 0xa1, 0xe3, 0x41, 0x1e, 0x6d, 0x3a, + 0x39, 0x23, 0xf4, 0xc2, 0xfa, 0x4b, 0x47, 0x87, 0xd5, 0x4b, 0x0a, 0x57, 0x9b, 0x61, 0x29, 0xac, + 0x53, 0xcc, 0xcc, 0x3f, 0x30, 0x00, 0x36, 0x56, 0x1a, 0xb2, 0x81, 0xaf, 0x41, 0x21, 0x3e, 0x0d, + 0x2a, 0x73, 0xdb, 0x38, 0x65, 0xfc, 0x61, 0x39, 0x79, 0x19, 0xf2, 0x49, 0x4b, 0xa6, 0x8f, 0x0e, + 0xab, 0x13, 0x7a, 0x0b, 0x58, 0x29, 0xb9, 0x0b, 0x63, 0x43, 0xd5, 0x19, 0x67, 0x67, 0x46, 0x5d, + 0x25, 0x35, 0x8e, 0xc2, 0x87, 0xdb, 0x1b, 0xcf, 0xef, 0x28, 0x7c, 0x2b, 0x07, 0x53, 0xac, 0x5f, + 0x6b, 0xbd, 0x68, 0xcf, 0x0f, 0xbc, 0xe8, 0xe0, 0x99, 0xb5, 0x8a, 0xdf, 0xd5, 0x14, 0xa2, 0x4b, + 0x52, 0x6c, 0xa9, 0x6d, 0x1b, 0xca, 0x38, 0xfe, 0xcb, 0x31, 0x98, 0xc9, 0xa0, 0x22, 0x6f, 0x08, + 0xef, 0x9b, 0xe4, 0xcc, 0x08, 0xbd, 0x6b, 0x7e, 0x74, 0x58, 0x2d, 0x4b, 0xf4, 0x8d, 0xc4, 0xdb, + 0x66, 0x1e, 0xc6, 0x85, 0xe9, 0xb3, 0x9a, 0x68, 0xd4, 0xe8, 0xb6, 0x21, 0xcf, 0xc4, 0x50, 0x34, + 0xa9, 0x48, 0xa4, 0x06, 0xe5, 0x85, 0x3d, 0xda, 0xdc, 0xf7, 0x3a, 0xbb, 0xf7, 0xe9, 0x01, 0xd7, + 0x97, 0xca, 0xf5, 0x17, 0x99, 0xa5, 0xd5, 0x14, 0x70, 0x36, 0xa4, 0xba, 0x11, 0xa7, 0x91, 0x90, + 0xf7, 0x61, 0xbc, 0xe1, 0xed, 0x76, 0x24, 0x87, 0x02, 0x72, 0xb8, 0x7c, 0x74, 0x58, 0x3d, 0x1f, + 0x72, 0x70, 0x3f, 0x03, 0x95, 0x80, 0x5c, 0x83, 0x11, 0xcb, 0x6f, 0x51, 0xbe, 0x0d, 0x0b, 0x7f, + 0x8e, 0x80, 0x01, 0xd4, 0xb3, 0x69, 0xc4, 0x20, 0xf7, 0x60, 0x8c, 0xfd, 0xf3, 0xc0, 0xe9, 0xce, + 0x8e, 0xa2, 0xdc, 0x26, 0xb1, 0x82, 0x8f, 0xd0, 0xae, 0xd7, 0xd9, 0x55, 0x75, 0xfc, 0x16, 0xb5, + 0xdb, 0x4e, 0x57, 0xdb, 0x17, 0x39, 0x22, 0xd9, 0x82, 0xf1, 0x44, 0x10, 0x84, 0xb3, 0x63, 0xda, + 0x5d, 0x50, 0x52, 0x52, 0xff, 0x9c, 0x60, 0x76, 0x21, 0x6a, 0x85, 0x38, 0xb7, 0xbb, 0x0c, 0x5f, + 0x6f, 0x8c, 0xc2, 0x48, 0xb3, 0x41, 0x8a, 0x83, 0x6d, 0x10, 0xe3, 0x44, 0x1b, 0xc4, 0x05, 0x10, + 0x9d, 0x54, 0x6b, 0xed, 0x0a, 0xf7, 0xab, 0x6b, 0x83, 0x27, 0xd8, 0x8d, 0x04, 0x19, 0xd7, 0x24, + 0x3f, 0x99, 0x12, 0xfd, 0xef, 0xb4, 0x76, 0xb5, 0x93, 0xa9, 0x18, 0x95, 0x75, 0x43, 0x22, 0x6a, + 0xa4, 0x05, 0x2e, 0xbb, 0x21, 0x29, 0x49, 0xba, 0xe1, 0xe3, 0xc7, 0xd1, 0xa0, 0x6e, 0x50, 0x18, + 0x91, 0x55, 0x80, 0x5a, 0x33, 0xf2, 0x1e, 0x51, 0x9c, 0x12, 0xe3, 0x5a, 0x47, 0x2c, 0xd4, 0xee, + 0xd3, 0x83, 0x06, 0x8d, 0x62, 0xcf, 0x86, 0x73, 0x0e, 0xa2, 0xa6, 0xa6, 0x89, 0xa5, 0x70, 0x20, + 0x5d, 0x38, 0x57, 0x73, 0x5d, 0x8f, 0xbb, 0xe4, 0x6d, 0x04, 0x6c, 0xfe, 0xba, 0xc8, 0xba, 0x9c, + 0xcd, 0xfa, 0x9a, 0x60, 0xfd, 0x39, 0x27, 0xa6, 0xb2, 0x23, 0x4e, 0x96, 0xfe, 0x4c, 0x36, 0x63, + 0x73, 0x0d, 0x26, 0xf5, 0x2e, 0xd5, 0x9d, 0xd1, 0xca, 0x50, 0xb4, 0x1a, 0x35, 0xbb, 0x71, 0xaf, + 0x76, 0xb3, 0x62, 0x90, 0x0a, 0x94, 0xc5, 0xaf, 0x79, 0x7b, 0xfe, 0xad, 0xdb, 0x95, 0x9c, 0x06, + 0x79, 0xeb, 0xe6, 0x7c, 0x25, 0x6f, 0xfe, 0xb1, 0x01, 0x45, 0x59, 0x3f, 0x72, 0x1b, 0xf2, 0x8d, + 0xc6, 0xbd, 0xd4, 0x15, 0x64, 0xb2, 0xf5, 0xf2, 0x4d, 0x26, 0x0c, 0xf7, 0xd4, 0x4d, 0xa6, 0xd1, + 0xb8, 0xc7, 0xe8, 0x36, 0x56, 0x1a, 0x42, 0x69, 0xc9, 0x98, 0xae, 0xd3, 0x03, 0xee, 0x65, 0x6e, + 0x43, 0xfe, 0xc3, 0xed, 0x0d, 0x61, 0x0d, 0x65, 0x8c, 0x2f, 0xd2, 0x7d, 0xfc, 0x58, 0xdd, 0xfa, + 0x18, 0x81, 0x69, 0xc1, 0xb8, 0xb2, 0xb4, 0xb8, 0x12, 0xd1, 0xf6, 0x63, 0x37, 0x2d, 0xa1, 0x44, + 0x30, 0x88, 0x25, 0x4a, 0x98, 0xce, 0xb3, 0xe2, 0x37, 0x9d, 0x96, 0xd0, 0x46, 0x50, 0xe7, 0x69, + 0x31, 0x80, 0xc5, 0xe1, 0xe6, 0x9f, 0x18, 0x50, 0x59, 0x0f, 0xfc, 0x47, 0x1e, 0x93, 0xc0, 0x1b, + 0xfe, 0x3e, 0xed, 0x6c, 0xdd, 0x24, 0x6f, 0x4a, 0x21, 0xc0, 0x55, 0xb8, 0x0b, 0x8c, 0x0a, 0x85, + 0xc0, 0x8f, 0x0e, 0xab, 0xd0, 0x38, 0x08, 0x23, 0xda, 0x66, 0xe5, 0x52, 0x10, 0x28, 0xde, 0x6e, + 0xb9, 0xe1, 0x3d, 0x68, 0x4e, 0xf0, 0x76, 0xab, 0xc2, 0x08, 0x56, 0x47, 0x71, 0x62, 0x18, 0x89, + 0x18, 0xc0, 0xe2, 0x70, 0x45, 0x60, 0x7f, 0x27, 0xd7, 0xd7, 0x86, 0xf9, 0xe7, 0xca, 0x0b, 0x45, + 0x6f, 0xdc, 0x50, 0x9b, 0xd8, 0x57, 0xe1, 0x6c, 0xba, 0x4b, 0xf0, 0x5c, 0xa4, 0x06, 0x53, 0x3a, + 0x5c, 0x1e, 0x91, 0x5c, 0xc8, 0xfc, 0xd6, 0xd6, 0xbc, 0x95, 0xc6, 0x37, 0x7f, 0x60, 0x40, 0x09, + 0xff, 0xb5, 0x7a, 0x2d, 0xca, 0x34, 0x9b, 0xda, 0x76, 0x43, 0x5c, 0x48, 0xa9, 0x97, 0x46, 0xce, + 0xe3, 0xd0, 0x16, 0xb7, 0x57, 0x9a, 0x1c, 0x89, 0x91, 0x05, 0x29, 0xbf, 0x7e, 0x0b, 0xc5, 0x0c, + 0x8d, 0x49, 0xf9, 0x3d, 0x5d, 0x98, 0x22, 0x15, 0xc8, 0x6c, 0xfc, 0xd8, 0x2f, 0xbf, 0x25, 0x8f, + 0x86, 0x71, 0xfc, 0x90, 0xce, 0xd7, 0xae, 0x39, 0x24, 0x1a, 0x79, 0x13, 0x46, 0xd9, 0xa7, 0x2d, + 0x79, 0x89, 0x81, 0x56, 0x05, 0xd6, 0x31, 0xd0, 0x6e, 0x03, 0x39, 0x92, 0xf9, 0x2f, 0x73, 0xe9, + 0x0e, 0x14, 0x5a, 0xc0, 0x29, 0xd7, 0xc6, 0x3b, 0x30, 0x52, 0x6b, 0xb5, 0xfc, 0xc7, 0x42, 0x4a, + 0xc8, 0x63, 0x9a, 0xb8, 0xff, 0xf8, 0x0e, 0xeb, 0x30, 0x14, 0xed, 0xf6, 0x97, 0x01, 0xc8, 0x02, + 0x94, 0x6a, 0xdb, 0x8d, 0xe5, 0xe5, 0xc5, 0x8d, 0x8d, 0x15, 0xe1, 0x64, 0xfc, 0xaa, 0xec, 0x1f, + 0xcf, 0x73, 0xed, 0x28, 0x6a, 0x0d, 0xf0, 0x41, 0x4c, 0xe8, 0xc8, 0x7b, 0x00, 0x1f, 0xfa, 0x5e, + 0xe7, 0x01, 0x8d, 0xf6, 0x7c, 0x57, 0x34, 0x9e, 0xa9, 0x14, 0xe3, 0x1f, 0xfb, 0x5e, 0xc7, 0x6e, + 0x23, 0x98, 0xd5, 0x3d, 0x41, 0xb2, 0x94, 0xff, 0x59, 0x4f, 0xd7, 0xfd, 0x08, 0x75, 0x98, 0x91, + 0xa4, 0xa7, 0x77, 0xfc, 0x28, 0x7d, 0xc7, 0x22, 0xd1, 0xcc, 0x5f, 0xcf, 0xc1, 0x24, 0xb7, 0x54, + 0xf9, 0x84, 0x79, 0x66, 0x17, 0xe3, 0x3b, 0xda, 0x62, 0xbc, 0x28, 0x37, 0x06, 0xa5, 0x69, 0x43, + 0x2d, 0xc5, 0x3d, 0x20, 0xfd, 0x34, 0xc4, 0x92, 0xe7, 0x29, 0xc3, 0xac, 0xc2, 0x9b, 0xc9, 0xdd, + 0x71, 0x88, 0x44, 0x36, 0x8a, 0xc2, 0xd0, 0xd2, 0x78, 0x98, 0xbf, 0x96, 0x83, 0x09, 0x45, 0x9f, + 0x7c, 0x66, 0x3b, 0xfe, 0x4b, 0x5a, 0xc7, 0xcb, 0x3b, 0x08, 0xa5, 0x65, 0x43, 0xf5, 0x7b, 0x0f, + 0xa6, 0xfb, 0x48, 0xd2, 0x6a, 0xb9, 0x31, 0x8c, 0x5a, 0xfe, 0x46, 0xff, 0xe5, 0x36, 0x77, 0x48, + 0x8e, 0x2f, 0xb7, 0xd5, 0xdb, 0xf4, 0x6f, 0xe5, 0xe0, 0xac, 0xf8, 0x55, 0xeb, 0xb9, 0x5e, 0xb4, + 0xe0, 0x77, 0x1e, 0x7a, 0xbb, 0xcf, 0xec, 0x58, 0xd4, 0xb4, 0xb1, 0xa8, 0xea, 0x63, 0xa1, 0x34, + 0x70, 0xf0, 0x90, 0x98, 0xff, 0xba, 0x08, 0xb3, 0x83, 0x08, 0x98, 0xd9, 0xaf, 0x58, 0x55, 0x68, + 0xf6, 0xa7, 0x2c, 0x56, 0x6e, 0x4f, 0x25, 0xce, 0x1c, 0xb9, 0x21, 0x9c, 0x39, 0x56, 0xa0, 0x82, + 0x9f, 0x6a, 0xd0, 0x90, 0x75, 0x42, 0x98, 0x78, 0x43, 0x5e, 0x39, 0x3a, 0xac, 0x5e, 0x76, 0x58, + 0x99, 0x1d, 0x8a, 0x42, 0xbb, 0x17, 0x78, 0x0a, 0x8f, 0x3e, 0x4a, 0xf2, 0x07, 0x06, 0x4c, 0x22, + 0x70, 0xe9, 0x11, 0xed, 0x44, 0xc8, 0xac, 0x20, 0x2e, 0x69, 0xe2, 0xa0, 0x93, 0x46, 0x14, 0x78, + 0x9d, 0x5d, 0x3c, 0x48, 0x0a, 0xeb, 0x3b, 0xac, 0x17, 0xfe, 0xe2, 0xb0, 0xfa, 0xee, 0x27, 0x09, + 0x64, 0x11, 0xac, 0x42, 0x66, 0xc8, 0xf3, 0x8a, 0x52, 0xfc, 0x6c, 0xaa, 0x9a, 0xa9, 0x1a, 0x91, + 0x9f, 0x82, 0x0b, 0x4b, 0x1d, 0x67, 0xa7, 0x45, 0x17, 0xfc, 0x4e, 0xe4, 0x75, 0x7a, 0x7e, 0x2f, + 0xac, 0x3b, 0xcd, 0xfd, 0x5e, 0x37, 0x14, 0x87, 0x9d, 0xd8, 0xf2, 0x66, 0x5c, 0x68, 0xef, 0xf0, + 0x52, 0x85, 0xe5, 0x20, 0x06, 0xe4, 0x1e, 0x4c, 0xf3, 0xa2, 0x5a, 0x2f, 0xf2, 0x1b, 0x4d, 0xa7, + 0xe5, 0x75, 0x76, 0xf1, 0x0c, 0xb4, 0x58, 0xbf, 0xc4, 0x6c, 0x4b, 0xa7, 0x17, 0xf9, 0x76, 0xc8, + 0xe1, 0x0a, 0xbf, 0x7e, 0x22, 0xb2, 0x0c, 0x53, 0x16, 0x75, 0xdc, 0x07, 0xce, 0x93, 0x05, 0xa7, + 0xeb, 0x34, 0xbd, 0xe8, 0x00, 0x2d, 0xb3, 0x7c, 0xbd, 0x7a, 0x74, 0x58, 0x7d, 0x21, 0xa0, 0x8e, + 0x6b, 0xb7, 0x9d, 0x27, 0x76, 0x53, 0x14, 0x2a, 0xcc, 0xd2, 0x74, 0x31, 0x2b, 0xaf, 0x13, 0xb3, + 0x2a, 0xa5, 0x59, 0x79, 0x9d, 0xc1, 0xac, 0x12, 0x3a, 0xc9, 0x6a, 0xc3, 0x09, 0x76, 0x69, 0xc4, + 0x0f, 0x09, 0xe1, 0x8a, 0x71, 0xd5, 0x50, 0x58, 0x45, 0x58, 0x66, 0xe3, 0x81, 0x61, 0x9a, 0x95, + 0x42, 0xc7, 0x66, 0xde, 0x76, 0xe0, 0x45, 0x54, 0x6d, 0xe1, 0x38, 0x56, 0x0b, 0xfb, 0x1f, 0x8f, + 0x49, 0x07, 0x35, 0xb1, 0x8f, 0x32, 0xe1, 0xa6, 0x34, 0xb2, 0xdc, 0xc7, 0x2d, 0xbb, 0x95, 0x7d, + 0x94, 0x31, 0x37, 0xb5, 0x9d, 0x13, 0xd8, 0x4e, 0x85, 0xdb, 0x80, 0x86, 0xf6, 0x51, 0x92, 0x55, + 0xd6, 0x69, 0x11, 0xed, 0xb0, 0x19, 0x2d, 0x0e, 0x49, 0x27, 0xb1, 0x6a, 0xaf, 0x08, 0x9b, 0xba, + 0x12, 0xc8, 0x62, 0x3b, 0xe3, 0xc8, 0x34, 0x4d, 0xfc, 0x61, 0xa1, 0x38, 0x52, 0x19, 0xb5, 0x2a, + 0x7c, 0xca, 0x47, 0x6c, 0xe2, 0xa0, 0x2c, 0x36, 0x7f, 0x27, 0x07, 0x17, 0xa5, 0x38, 0xa6, 0xd1, + 0x63, 0x3f, 0xd8, 0xf7, 0x3a, 0xbb, 0xcf, 0xb8, 0x54, 0xbd, 0xa3, 0x49, 0xd5, 0x57, 0x52, 0x3b, + 0x5c, 0xaa, 0x95, 0xc7, 0x88, 0xd6, 0x3f, 0x1f, 0x81, 0x17, 0x8f, 0xa5, 0x22, 0x1f, 0xb1, 0x5d, + 0xd0, 0xa3, 0x9d, 0x68, 0xd9, 0x6d, 0x51, 0x66, 0x86, 0xf9, 0xbd, 0x48, 0x1c, 0x66, 0xbf, 0x7c, + 0x74, 0x58, 0x9d, 0xe1, 0xb1, 0x18, 0xb6, 0xe7, 0xb6, 0xa8, 0x1d, 0xf1, 0x62, 0x6d, 0x98, 0xfa, + 0xa9, 0x19, 0xcb, 0x38, 0x32, 0x6c, 0xb9, 0x13, 0xd1, 0xe0, 0x91, 0xc3, 0x5d, 0xd2, 0x05, 0xcb, + 0x7d, 0x4a, 0xbb, 0xb6, 0xc3, 0x4a, 0x6d, 0x4f, 0x14, 0xeb, 0x2c, 0xfb, 0xa8, 0xc9, 0x1d, 0x85, + 0xe5, 0x02, 0x33, 0x0e, 0x1e, 0x38, 0x4f, 0x84, 0xc6, 0x8b, 0xe7, 0xab, 0x0a, 0x4b, 0xee, 0x0f, + 0xd7, 0x76, 0x9e, 0x58, 0xfd, 0x24, 0xe4, 0x1b, 0x70, 0x4e, 0x08, 0x6e, 0x26, 0xc4, 0x02, 0xbf, + 0x25, 0x5b, 0x5c, 0x40, 0x5e, 0xaf, 0x1f, 0x1d, 0x56, 0x2f, 0x08, 0xb1, 0x6f, 0x37, 0x39, 0x46, + 0x66, 0xab, 0xb3, 0xb9, 0x90, 0x0d, 0xb6, 0x91, 0xa5, 0xba, 0xe3, 0x01, 0x0d, 0x43, 0x67, 0x57, + 0x6a, 0xc7, 0xfc, 0x46, 0x49, 0xe9, 0x4c, 0xbb, 0xcd, 0xcb, 0xad, 0x81, 0x94, 0xe4, 0x1e, 0x4c, + 0x6e, 0xd3, 0x1d, 0x75, 0x7c, 0x46, 0xe3, 0x25, 0x5e, 0x79, 0x4c, 0x77, 0x06, 0x0f, 0x4e, 0x8a, + 0x8e, 0x78, 0x30, 0xbd, 0x1e, 0xf8, 0x4f, 0x0e, 0x98, 0xa9, 0x47, 0x3b, 0x34, 0x40, 0x47, 0xac, + 0x31, 0x3c, 0xae, 0x9a, 0x4d, 0x34, 0x4b, 0xbd, 0xbc, 0xfe, 0xb9, 0xa3, 0xc3, 0xea, 0x8b, 0x5d, + 0x06, 0xb6, 0x5b, 0x02, 0x6e, 0xa7, 0x02, 0xb3, 0xfa, 0xb9, 0x92, 0x9f, 0x81, 0x29, 0xcb, 0xef, + 0x45, 0x5e, 0x67, 0xb7, 0x11, 0x05, 0x4e, 0x44, 0x77, 0xb9, 0x20, 0x4f, 0x3c, 0xbe, 0x52, 0xa5, + 0xfc, 0x60, 0x3a, 0xe0, 0x40, 0x3b, 0x14, 0x50, 0x4d, 0x92, 0xea, 0x04, 0xe6, 0x6f, 0xe5, 0x60, + 0x56, 0x0c, 0x83, 0x45, 0x9b, 0x7e, 0xe0, 0x3e, 0xfb, 0xcb, 0x7e, 0x49, 0x5b, 0xf6, 0x2f, 0xc7, + 0x3e, 0x4a, 0x59, 0x8d, 0x3c, 0x66, 0xd5, 0xff, 0x53, 0x03, 0x2e, 0x1f, 0x47, 0xc4, 0x7a, 0x27, + 0xf6, 0xc1, 0x2b, 0xf5, 0xf9, 0xda, 0x75, 0x61, 0x06, 0xc7, 0x13, 0x0f, 0x8e, 0xc3, 0x7b, 0x7e, + 0x18, 0xe1, 0xe9, 0x5d, 0x4e, 0x73, 0x24, 0xa8, 0xfb, 0x7e, 0x0b, 0xe5, 0x7c, 0xfd, 0x0d, 0x26, + 0xce, 0xff, 0xe2, 0xb0, 0x0a, 0x0c, 0xb4, 0x86, 0x97, 0x91, 0x6c, 0xcf, 0xe7, 0x33, 0x06, 0xcf, + 0xa5, 0x43, 0x1b, 0xbd, 0x3f, 0xf6, 0xe9, 0x41, 0x68, 0x65, 0xb1, 0xc6, 0x13, 0x9a, 0x5a, 0x2f, + 0xda, 0x5b, 0x0f, 0xe8, 0x43, 0x1a, 0xd0, 0x4e, 0x93, 0x3e, 0x67, 0x27, 0x34, 0x7a, 0xe3, 0x86, + 0x32, 0x4f, 0xfe, 0xef, 0x28, 0x9c, 0xcd, 0x22, 0x63, 0xfd, 0xa2, 0x68, 0xc4, 0xe9, 0x28, 0xde, + 0xbf, 0x65, 0x40, 0xb9, 0x41, 0x9b, 0x7e, 0xc7, 0xbd, 0xe3, 0x34, 0x23, 0x5f, 0xba, 0x64, 0xd8, + 0x5c, 0xb2, 0x31, 0xb8, 0xfd, 0x10, 0x0b, 0xb4, 0x93, 0x81, 0x2f, 0x0f, 0xa7, 0x88, 0x36, 0x7d, + 0x74, 0x5a, 0x8d, 0xd8, 0x9c, 0x4c, 0x3e, 0x81, 0xb7, 0x1a, 0xda, 0x47, 0x49, 0x1d, 0x26, 0x16, + 0xfc, 0x4e, 0x87, 0xb2, 0x1f, 0x8a, 0x0b, 0xe6, 0xe5, 0xa3, 0xc3, 0xea, 0x6c, 0x53, 0x16, 0xa4, + 0x4f, 0x08, 0x74, 0x12, 0x72, 0x0b, 0xf2, 0x9b, 0xf3, 0x77, 0xc4, 0x18, 0x48, 0x67, 0xb5, 0xcd, + 0xf9, 0x3b, 0x68, 0xeb, 0x32, 0xfd, 0x61, 0xa2, 0x37, 0xff, 0x50, 0x3d, 0x03, 0xdd, 0x9c, 0xbf, + 0x43, 0xd6, 0x60, 0xda, 0xa2, 0x3f, 0xdb, 0xf3, 0x02, 0x2a, 0x16, 0xc0, 0x83, 0x3b, 0x35, 0x1c, + 0x8b, 0x22, 0x97, 0x63, 0x01, 0x2f, 0x94, 0xba, 0xbd, 0xdd, 0x7e, 0xa8, 0x46, 0xae, 0xf5, 0xd3, + 0x92, 0x5f, 0x84, 0x73, 0x8b, 0x5e, 0x28, 0xea, 0xcc, 0x0f, 0x1f, 0x5d, 0xbc, 0x87, 0x1c, 0x1d, + 0xb0, 0x1c, 0x7e, 0x22, 0x73, 0x39, 0x7c, 0xce, 0x8d, 0x99, 0xd8, 0xfc, 0x64, 0xd3, 0x4d, 0xfb, + 0xae, 0x66, 0x7f, 0x87, 0x7c, 0x0c, 0x93, 0x78, 0xda, 0x83, 0xe7, 0xb1, 0xe8, 0xce, 0x3c, 0x36, + 0xe0, 0xcb, 0x5f, 0xc8, 0xfc, 0xf2, 0x25, 0x3c, 0x3c, 0xb2, 0xf1, 0x54, 0x17, 0x5d, 0x9f, 0x35, + 0x1b, 0x41, 0xe3, 0x4c, 0x3e, 0x84, 0x29, 0xb1, 0xe9, 0xac, 0x3d, 0xdc, 0xd8, 0xa3, 0x8b, 0xce, + 0x81, 0x70, 0x42, 0x40, 0xfd, 0x4f, 0xec, 0x54, 0xb6, 0xff, 0xd0, 0x8e, 0xf6, 0xa8, 0xed, 0x3a, + 0x9a, 0x78, 0x4e, 0x11, 0x92, 0x9f, 0x87, 0xf1, 0x15, 0x1f, 0x2f, 0x9e, 0x50, 0xd4, 0x94, 0x90, + 0xcf, 0x57, 0x31, 0x72, 0x95, 0x83, 0x53, 0x9b, 0xc8, 0x8f, 0x0e, 0xab, 0xef, 0x9c, 0x76, 0x16, + 0x2a, 0x1f, 0xb0, 0xd4, 0xaf, 0x91, 0x05, 0x28, 0x6e, 0xd3, 0x1d, 0xd6, 0xda, 0x74, 0xd4, 0x95, + 0x04, 0x73, 0x79, 0xf1, 0x58, 0xfc, 0x52, 0x6f, 0x75, 0x24, 0x86, 0xf9, 0xaf, 0x0c, 0x9c, 0x81, + 0xe4, 0x3a, 0x3a, 0x82, 0xc5, 0xde, 0xe0, 0x68, 0x59, 0x3a, 0xdd, 0xae, 0xee, 0xcf, 0xcd, 0x51, + 0x98, 0x19, 0x7a, 0xc7, 0x69, 0xd2, 0x48, 0x9e, 0x57, 0x22, 0xf2, 0x43, 0x84, 0xa8, 0x66, 0x28, + 0xc7, 0x21, 0x5f, 0x81, 0xb3, 0x8b, 0xf4, 0x91, 0xd7, 0xa4, 0xb5, 0x28, 0xa2, 0x21, 0x6f, 0xed, + 0x42, 0x8d, 0x5f, 0xec, 0x95, 0xea, 0xaf, 0x1c, 0x1d, 0x56, 0xaf, 0xb8, 0x58, 0x6e, 0x3b, 0x09, + 0x82, 0xdd, 0x74, 0x54, 0x5e, 0x99, 0x1c, 0xcc, 0xff, 0x69, 0x24, 0x3d, 0x40, 0x5e, 0x87, 0x82, + 0xb5, 0x1e, 0xd7, 0x9f, 0xdf, 0xd9, 0xa5, 0xaa, 0x8f, 0x08, 0xe4, 0x6b, 0x70, 0x4e, 0xe1, 0x83, + 0x93, 0x83, 0xba, 0xac, 0x42, 0xbc, 0x31, 0xaf, 0xe2, 0x25, 0x8d, 0x52, 0x13, 0x87, 0x63, 0xa4, + 0x6a, 0x94, 0xcd, 0x83, 0x35, 0x56, 0x29, 0x58, 0xa4, 0x1d, 0x8f, 0xf3, 0x56, 0x1a, 0xab, 0xf2, + 0x76, 0x11, 0x21, 0xdd, 0xd8, 0x2c, 0x0e, 0x1f, 0x16, 0x8a, 0x85, 0xca, 0x88, 0xf9, 0xd7, 0x86, + 0x92, 0x02, 0xe0, 0x19, 0xdd, 0x3d, 0x6e, 0x6b, 0xbb, 0xc7, 0x59, 0x41, 0x1a, 0xb7, 0x8a, 0x95, + 0x65, 0xee, 0xf8, 0x53, 0x30, 0xa1, 0x21, 0xa1, 0xcb, 0xeb, 0x66, 0x48, 0x03, 0x7e, 0x3e, 0xf8, + 0x7c, 0xb9, 0xbc, 0xc6, 0xed, 0x1a, 0xca, 0x93, 0xf1, 0x2f, 0x0d, 0x98, 0x4a, 0x51, 0xb0, 0xde, + 0x60, 0x20, 0xb5, 0x37, 0x7a, 0x21, 0x0d, 0x2c, 0x84, 0x72, 0x07, 0xb9, 0x15, 0xdd, 0x41, 0xae, + 0x65, 0x31, 0x18, 0xf9, 0x32, 0x8c, 0x6c, 0xa2, 0x36, 0xaf, 0xfb, 0x58, 0xc4, 0xfc, 0xb1, 0x90, + 0xaf, 0xb0, 0x1e, 0xfb, 0x57, 0x15, 0x10, 0x58, 0x46, 0x1a, 0x30, 0xb6, 0x10, 0x50, 0x0c, 0xf6, + 0x2f, 0x0c, 0x7f, 0x19, 0xd6, 0xe4, 0x24, 0xe9, 0xcb, 0x30, 0xc1, 0xc9, 0xfc, 0xcd, 0x1c, 0x90, + 0xa4, 0x8d, 0xb4, 0x19, 0xd0, 0x28, 0x7c, 0x66, 0x07, 0xfd, 0x03, 0x6d, 0xd0, 0x5f, 0xec, 0x1b, + 0x74, 0xde, 0xbc, 0xa1, 0xc6, 0xfe, 0x4f, 0x0c, 0x38, 0x9f, 0x4d, 0x48, 0x5e, 0x86, 0xd1, 0xb5, + 0x8d, 0x75, 0xe9, 0xa6, 0x23, 0x9a, 0xe2, 0x77, 0x51, 0x4b, 0xb5, 0x44, 0x11, 0x79, 0x13, 0x46, + 0x3f, 0xb2, 0x16, 0xd8, 0xf6, 0xa5, 0x44, 0x9d, 0xfc, 0x6c, 0x60, 0x37, 0x75, 0xf3, 0x47, 0x20, + 0xa9, 0x63, 0x9b, 0x7f, 0x6a, 0x63, 0xfb, 0xad, 0x1c, 0x4c, 0xd5, 0x9a, 0x4d, 0x1a, 0x86, 0x4c, + 0x39, 0xa1, 0x61, 0xf4, 0xcc, 0x0e, 0x6c, 0xb6, 0x03, 0x8e, 0xd6, 0xb6, 0xa1, 0x46, 0xf5, 0x4f, + 0x0d, 0x38, 0x27, 0xa9, 0x1e, 0x79, 0xf4, 0xf1, 0xc6, 0x5e, 0x40, 0xc3, 0x3d, 0xbf, 0xe5, 0x0e, + 0x1b, 0x3f, 0x85, 0xbb, 0xb4, 0xd7, 0x8a, 0x68, 0xa0, 0x1e, 0x16, 0x3f, 0x44, 0x88, 0xb6, 0x4b, + 0x23, 0x84, 0xcc, 0xc1, 0x58, 0xad, 0xdb, 0x0d, 0xfc, 0x47, 0x7c, 0xd9, 0x4f, 0x88, 0xbb, 0x41, + 0x0e, 0xd2, 0xee, 0x12, 0x39, 0x88, 0x55, 0x63, 0x91, 0x76, 0xb8, 0x77, 0xf1, 0x04, 0xaf, 0x86, + 0x4b, 0x3b, 0xaa, 0xb6, 0x84, 0xe5, 0xe6, 0x37, 0x0b, 0x50, 0x56, 0x1b, 0x42, 0x4c, 0x18, 0xe5, + 0xae, 0x22, 0xea, 0x95, 0xbd, 0x83, 0x10, 0x4b, 0x94, 0x24, 0x1e, 0x38, 0xb9, 0x13, 0x3d, 0x70, + 0xb6, 0x61, 0x62, 0x3d, 0xf0, 0xbb, 0x7e, 0x48, 0x5d, 0x9e, 0xaf, 0x85, 0x4b, 0xad, 0x99, 0xd8, + 0x2d, 0x95, 0xf7, 0x39, 0x2b, 0xe2, 0xaa, 0x79, 0x57, 0x60, 0xdb, 0xe9, 0x6c, 0x2e, 0x3a, 0x1f, + 0x7e, 0xd8, 0xee, 0x84, 0xc2, 0x75, 0x3f, 0x3e, 0x6c, 0x67, 0x10, 0xfd, 0xb0, 0x9d, 0x41, 0xd4, + 0x65, 0x31, 0xf2, 0xb4, 0x96, 0x05, 0xf9, 0x4d, 0x03, 0xc6, 0x6b, 0x9d, 0x8e, 0xf0, 0xc0, 0x91, + 0x41, 0xcf, 0xe7, 0x92, 0x03, 0x77, 0xee, 0xa2, 0xc9, 0xcf, 0xdb, 0xbf, 0x2e, 0xce, 0xdb, 0xdf, + 0xf9, 0x44, 0xe7, 0xed, 0x1b, 0x81, 0xe3, 0x45, 0x21, 0x5e, 0xac, 0x26, 0x1f, 0x54, 0xdd, 0x70, + 0x95, 0x7a, 0x90, 0x77, 0xa0, 0x12, 0xcf, 0xc7, 0xe5, 0x8e, 0x4b, 0x9f, 0x50, 0xee, 0xb0, 0x34, + 0xc1, 0x23, 0xf3, 0xb4, 0x8b, 0x84, 0x34, 0xa2, 0xf9, 0x2d, 0x03, 0xce, 0xab, 0x13, 0xa2, 0xd1, + 0xdb, 0x69, 0x7b, 0x68, 0x8a, 0x90, 0x1b, 0x50, 0x12, 0xe3, 0x15, 0x2b, 0x72, 0xfd, 0x49, 0x7e, + 0x12, 0x14, 0xb2, 0xc4, 0x86, 0x88, 0xf1, 0x10, 0x76, 0xfb, 0x4c, 0x6a, 0xb9, 0xb1, 0xa2, 0xfa, + 0xac, 0xe8, 0xec, 0x4a, 0x80, 0xbf, 0xf5, 0xb1, 0x63, 0x10, 0xf3, 0x7d, 0x98, 0xd6, 0x6b, 0xd9, + 0xa0, 0x18, 0x0e, 0x26, 0x9b, 0x66, 0x64, 0x37, 0x4d, 0x96, 0x9b, 0xdb, 0x40, 0xfa, 0xe8, 0x43, + 0xbc, 0x34, 0xa2, 0x91, 0xbc, 0xd4, 0x94, 0x47, 0x4f, 0x7d, 0x88, 0x71, 0xba, 0xab, 0x71, 0xb5, + 0xbb, 0x91, 0xd4, 0xfc, 0x9b, 0x12, 0xcc, 0x64, 0x88, 0x8e, 0x13, 0xb6, 0xf6, 0xaa, 0xbe, 0x78, + 0x4a, 0xf1, 0xed, 0xbc, 0x5c, 0x32, 0xef, 0xcb, 0xd4, 0x46, 0xc7, 0x2c, 0x95, 0xe3, 0xf2, 0x1d, + 0x7d, 0x16, 0xdb, 0xbb, 0xea, 0x40, 0x33, 0xf2, 0xd4, 0x1c, 0x68, 0xea, 0x30, 0x21, 0x5a, 0x25, + 0x96, 0xf2, 0x68, 0x62, 0xa2, 0x07, 0xbc, 0xc0, 0xee, 0x5b, 0xd2, 0x3a, 0x09, 0xe7, 0x11, 0xfa, + 0xad, 0x47, 0x54, 0xf0, 0x18, 0x53, 0x79, 0x60, 0x41, 0x26, 0x0f, 0x85, 0x84, 0xfc, 0x13, 0x03, + 0x88, 0x80, 0xa8, 0xeb, 0xb9, 0x78, 0xdc, 0x7a, 0x76, 0x9f, 0xce, 0x7a, 0x7e, 0x51, 0xd6, 0x31, + 0x7b, 0x5d, 0x67, 0x54, 0x8b, 0xfc, 0x23, 0x03, 0xa6, 0xb9, 0x17, 0x87, 0x5a, 0xd9, 0xd2, 0x71, + 0x95, 0x6d, 0x3e, 0x9d, 0xca, 0x5e, 0x0e, 0xf1, 0xb3, 0x03, 0xea, 0xda, 0x5f, 0x29, 0xf2, 0x53, + 0x00, 0xf1, 0x8a, 0x92, 0xde, 0x82, 0x97, 0x33, 0xa4, 0x40, 0x8c, 0x94, 0x04, 0x3c, 0x46, 0x31, + 0x9d, 0xea, 0x5f, 0x93, 0x70, 0x23, 0xbf, 0x08, 0x67, 0xd9, 0x7a, 0x89, 0x21, 0xc2, 0xe7, 0x6c, + 0x76, 0x1c, 0xbf, 0xf2, 0xc5, 0xc1, 0x5b, 0xfb, 0x8d, 0x2c, 0x32, 0x1e, 0xb3, 0x91, 0x84, 0xbf, + 0x47, 0x6d, 0xd5, 0xe4, 0xcb, 0xa2, 0x40, 0xe7, 0x52, 0xac, 0x7d, 0x38, 0x5b, 0xc6, 0x6f, 0x66, + 0xca, 0xb7, 0x8b, 0x72, 0x2d, 0x70, 0xf9, 0x16, 0xea, 0x41, 0x17, 0x08, 0x22, 0x1f, 0x01, 0x69, + 0xf4, 0x76, 0x77, 0x69, 0x18, 0x51, 0x97, 0xc3, 0x68, 0x10, 0xce, 0x4e, 0xa0, 0x7c, 0xc0, 0x23, + 0xa3, 0x50, 0x96, 0xda, 0x81, 0x2c, 0x56, 0x27, 0x49, 0x3f, 0xf1, 0xa5, 0x1d, 0xb8, 0x38, 0xb0, + 0x99, 0x19, 0x01, 0x15, 0x73, 0x7a, 0x40, 0xc5, 0xc5, 0x41, 0xe2, 0x30, 0x54, 0x83, 0x2a, 0x7e, + 0xcf, 0x48, 0xc9, 0x3f, 0xa1, 0xac, 0xf0, 0x2c, 0x70, 0x83, 0x36, 0x88, 0x1c, 0x06, 0xc6, 0x73, + 0x09, 0x99, 0x4b, 0x94, 0x24, 0x26, 0x21, 0x55, 0x09, 0x8b, 0xb2, 0xf2, 0x53, 0x8a, 0x42, 0xf3, + 0x9f, 0x1b, 0x40, 0x78, 0x0d, 0x17, 0x9c, 0xae, 0xb3, 0xe3, 0xb5, 0xbc, 0xc8, 0xa3, 0x21, 0xb9, + 0x0f, 0x15, 0xc1, 0xc2, 0xd9, 0x69, 0x51, 0xd5, 0x57, 0x4a, 0x5c, 0xa6, 0xc6, 0x65, 0x76, 0x5a, + 0xad, 0xe9, 0x23, 0x1c, 0x30, 0x78, 0xb9, 0x4f, 0x31, 0x78, 0xe6, 0x0f, 0x0d, 0xb8, 0xd8, 0x5f, + 0x6d, 0xf1, 0xe5, 0xb8, 0xf3, 0x8c, 0x13, 0x3a, 0x2f, 0xab, 0x95, 0x39, 0x3c, 0x86, 0x7c, 0x6a, + 0xad, 0xcc, 0x27, 0xa7, 0x9a, 0xa7, 0x6f, 0xe5, 0xaf, 0xe6, 0xa0, 0xbc, 0xde, 0xea, 0xed, 0x7a, + 0x9d, 0x45, 0x27, 0x72, 0x9e, 0x59, 0x93, 0xe2, 0x6d, 0xcd, 0xa4, 0x88, 0xbd, 0xa3, 0xe2, 0x86, + 0x0d, 0x97, 0x91, 0xcb, 0x80, 0xa9, 0x84, 0x84, 0xaf, 0xd2, 0x7b, 0x50, 0x60, 0x3f, 0x84, 0x86, + 0x72, 0xa5, 0x8f, 0x31, 0x62, 0xdd, 0x88, 0xff, 0x13, 0x4a, 0xbe, 0x9e, 0x07, 0x0d, 0x39, 0x5c, + 0xfa, 0x09, 0x9e, 0xc6, 0xe8, 0xf4, 0x29, 0x17, 0xff, 0x99, 0x01, 0x95, 0x74, 0x4b, 0xc8, 0x7d, + 0x18, 0x63, 0x9c, 0xbc, 0x38, 0x25, 0xd2, 0x2b, 0x03, 0xda, 0x7c, 0x43, 0xa0, 0xf1, 0xea, 0x61, + 0xe7, 0x53, 0x0e, 0xb1, 0x24, 0x87, 0x4b, 0x16, 0x94, 0x55, 0xac, 0x8c, 0xda, 0xbd, 0xa1, 0x8b, + 0xa6, 0xf3, 0xd9, 0xfd, 0xa0, 0xd6, 0xfa, 0x77, 0xb5, 0x5a, 0x0b, 0xa1, 0x34, 0x6c, 0x6e, 0x3b, + 0x0c, 0x0f, 0xe3, 0x19, 0x3c, 0xd4, 0x79, 0x26, 0x93, 0x7d, 0xe8, 0xe1, 0x61, 0x1c, 0xc6, 0x6c, + 0x11, 0xfe, 0x3d, 0x31, 0xcf, 0xd0, 0x16, 0xe9, 0x22, 0x44, 0xd5, 0x67, 0x39, 0x8e, 0xf9, 0xf7, + 0xf2, 0x70, 0x3e, 0xa9, 0x1e, 0xcf, 0xf4, 0xb7, 0xee, 0x04, 0x4e, 0x3b, 0x3c, 0x61, 0x05, 0x5c, + 0xed, 0xab, 0x1a, 0x86, 0x3f, 0xcb, 0xaa, 0x29, 0x15, 0x32, 0x53, 0x15, 0x42, 0x23, 0x8e, 0x57, + 0x48, 0x56, 0x83, 0xdc, 0x87, 0x7c, 0x83, 0x46, 0x22, 0x48, 0xf2, 0xb5, 0xbe, 0x5e, 0x55, 0xeb, + 0x75, 0xa3, 0x41, 0x23, 0x3e, 0x88, 0xdc, 0xcf, 0x9c, 0x6a, 0x7e, 0xdf, 0x4c, 0x1d, 0xdf, 0x86, + 0xd1, 0xa5, 0x27, 0x5d, 0xda, 0x8c, 0x44, 0x6c, 0xe4, 0xb5, 0xe3, 0xf9, 0x71, 0x5c, 0x25, 0x02, + 0x93, 0x22, 0x40, 0xed, 0x2c, 0x8e, 0x72, 0xe9, 0x36, 0x14, 0xe5, 0xc7, 0x4f, 0x15, 0x49, 0xf8, + 0x36, 0x8c, 0x2b, 0x1f, 0x39, 0xd5, 0xa4, 0xff, 0x1b, 0x03, 0x46, 0x99, 0xd0, 0xdb, 0x7a, 0xeb, + 0x19, 0x95, 0x48, 0xb7, 0x34, 0x89, 0x34, 0xad, 0x84, 0xbc, 0xe0, 0xba, 0x7c, 0xeb, 0x04, 0x59, + 0x74, 0x68, 0x00, 0x24, 0xc8, 0xe4, 0x2e, 0x8c, 0xf1, 0x8b, 0x1c, 0x99, 0x46, 0x53, 0x8d, 0xa1, + 0x11, 0x25, 0x89, 0x96, 0xe3, 0x77, 0xd3, 0x6a, 0xa1, 0xa4, 0x26, 0x8b, 0x89, 0x9f, 0xb1, 0x1a, + 0xb4, 0xc9, 0xd8, 0x2c, 0xf8, 0x1d, 0x1e, 0x53, 0x11, 0x2a, 0xe9, 0xa6, 0xb2, 0x1d, 0x8e, 0x6b, + 0xe2, 0x60, 0x23, 0x7f, 0x1c, 0x93, 0xf3, 0x82, 0x49, 0xf6, 0x99, 0xc7, 0xb7, 0xc7, 0x79, 0x94, + 0x82, 0xac, 0xd8, 0x7b, 0x50, 0xbe, 0xe3, 0x07, 0x8f, 0x9d, 0xc0, 0xad, 0xed, 0x52, 0xe1, 0x21, + 0x5e, 0x44, 0x37, 0xef, 0x89, 0x87, 0x1c, 0x6e, 0x3b, 0xac, 0xe0, 0x47, 0x87, 0xd5, 0x42, 0xdd, + 0xf7, 0x5b, 0x96, 0x86, 0x4e, 0xd6, 0x60, 0xe2, 0x81, 0xf3, 0x44, 0xdc, 0xd7, 0x6d, 0x6c, 0xac, + 0x08, 0x3f, 0x93, 0x6b, 0x47, 0x87, 0xd5, 0x8b, 0x6d, 0xe7, 0x49, 0x7c, 0xcf, 0x37, 0xd8, 0x15, + 0x5a, 0xa7, 0x27, 0x1e, 0x4c, 0xae, 0xfb, 0x41, 0x24, 0x3e, 0xc2, 0x74, 0xda, 0xfc, 0x80, 0xeb, + 0xb6, 0xb9, 0xcc, 0xeb, 0xb6, 0x8b, 0x4c, 0x91, 0xb7, 0x1f, 0xc6, 0xe4, 0x5a, 0x68, 0x9d, 0xc6, + 0x98, 0xbc, 0x07, 0xd3, 0x0b, 0x34, 0x88, 0xbc, 0x87, 0x5e, 0xd3, 0x89, 0xe8, 0x1d, 0x3f, 0x68, + 0x3b, 0x91, 0x38, 0x50, 0x41, 0x83, 0xba, 0x49, 0x39, 0xa7, 0xb6, 0x13, 0x59, 0xfd, 0x98, 0xe4, + 0x6b, 0x59, 0x9e, 0x3b, 0x23, 0xd8, 0xfc, 0x37, 0x99, 0x52, 0x90, 0xe1, 0xb9, 0x33, 0xa0, 0x0b, + 0x32, 0x7c, 0x78, 0x76, 0x8f, 0xbb, 0xf6, 0x2c, 0xd6, 0x6f, 0x8a, 0x2b, 0xd8, 0x93, 0xaf, 0x35, + 0xe3, 0x71, 0x1b, 0x70, 0xbd, 0x39, 0x0f, 0xf9, 0xfa, 0xfa, 0x1d, 0x3c, 0x22, 0x11, 0xd7, 0x8c, + 0xb4, 0xb3, 0xe7, 0x74, 0x9a, 0xa8, 0xcb, 0x08, 0xdf, 0x05, 0x55, 0xe0, 0xd5, 0xd7, 0xef, 0x10, + 0x07, 0x66, 0xd6, 0x69, 0xd0, 0xf6, 0xa2, 0xaf, 0xdc, 0xbc, 0xa9, 0x0c, 0x54, 0x11, 0xab, 0x36, + 0x27, 0xaa, 0x56, 0xed, 0x22, 0x8a, 0xfd, 0xe4, 0xe6, 0xcd, 0xcc, 0xe1, 0x88, 0x2b, 0x96, 0xc5, + 0x8b, 0x2c, 0xc1, 0xe4, 0x03, 0xe7, 0x89, 0xb8, 0x90, 0x8e, 0x6d, 0xbc, 0x3c, 0x7a, 0xc6, 0xe3, + 0xc4, 0x6a, 0x26, 0x45, 0xea, 0x10, 0xeb, 0x44, 0xe4, 0x5d, 0x18, 0x4f, 0xa6, 0x57, 0x88, 0x57, + 0x91, 0x79, 0xee, 0x12, 0xa9, 0x4c, 0x4e, 0xed, 0x2c, 0x49, 0x41, 0x27, 0x9b, 0xb1, 0x89, 0xce, + 0x15, 0x52, 0x74, 0x14, 0x2c, 0xd5, 0xe7, 0x54, 0x13, 0xdd, 0xc1, 0x12, 0xad, 0x59, 0x53, 0xb1, + 0x8a, 0xce, 0x3d, 0x65, 0x2c, 0x9d, 0x8b, 0x62, 0xf9, 0xaf, 0x07, 0x7e, 0xbb, 0x1b, 0xa1, 0xc7, + 0x60, 0xca, 0xf2, 0xef, 0x62, 0x49, 0x86, 0xe5, 0xcf, 0x49, 0xb2, 0xef, 0xd9, 0x27, 0x3e, 0xc5, + 0x3d, 0x3b, 0x85, 0xc2, 0x8a, 0xdf, 0xdc, 0x47, 0x17, 0xc1, 0x52, 0xfd, 0x23, 0x26, 0x3f, 0x5a, + 0x7e, 0x73, 0xff, 0xe9, 0xdd, 0x0f, 0x23, 0x7b, 0xb2, 0xca, 0xda, 0xce, 0xa6, 0x95, 0xf8, 0xf4, + 0xec, 0x94, 0x76, 0xd3, 0xa6, 0x95, 0x71, 0x45, 0x85, 0xcf, 0x42, 0xd9, 0x10, 0x4b, 0x27, 0x27, + 0x14, 0x2a, 0x8b, 0x34, 0xdc, 0x8f, 0xfc, 0xee, 0x42, 0xcb, 0xeb, 0xee, 0xf8, 0x4e, 0xe0, 0xce, + 0x56, 0x06, 0x08, 0x8c, 0xd7, 0x33, 0x05, 0xc6, 0xb4, 0xcb, 0xe9, 0xed, 0xa6, 0x64, 0x60, 0xf5, + 0xb1, 0x24, 0x5f, 0x83, 0x49, 0xb6, 0x5a, 0x96, 0x9e, 0x44, 0xb4, 0xc3, 0xa7, 0xd2, 0x34, 0x6e, + 0xf5, 0x67, 0x95, 0x20, 0xc3, 0xb8, 0x90, 0x4f, 0x52, 0x94, 0x1e, 0x34, 0x26, 0x50, 0x27, 0xa9, + 0xce, 0xca, 0xfc, 0xa9, 0x54, 0x9f, 0x90, 0x65, 0x18, 0x13, 0x35, 0x10, 0xbb, 0x4e, 0x7f, 0x5b, + 0x5e, 0xcc, 0x6c, 0xcb, 0x98, 0x68, 0x8b, 0x25, 0xe9, 0xcd, 0x7f, 0x63, 0xc0, 0x84, 0xf6, 0x39, + 0x72, 0x5b, 0x71, 0x5f, 0x49, 0xdc, 0xce, 0x34, 0x9c, 0xcc, 0xf4, 0xf4, 0xb7, 0x85, 0xcf, 0x52, + 0x6e, 0x30, 0x5d, 0x66, 0xe6, 0x30, 0x19, 0xf4, 0x9f, 0x3f, 0x3e, 0xe8, 0xbf, 0x30, 0x20, 0xe8, + 0xff, 0xdb, 0x13, 0x30, 0xa9, 0x6f, 0x70, 0x4c, 0xe3, 0x5c, 0xf1, 0x77, 0xbd, 0x8e, 0xb4, 0x5b, + 0x79, 0x1a, 0x0b, 0x84, 0x68, 0xb9, 0xde, 0x11, 0x42, 0x5e, 0x05, 0x88, 0xaf, 0x66, 0xa5, 0x69, + 0x2a, 0x32, 0xd3, 0x2b, 0x05, 0xe4, 0xa7, 0x01, 0x56, 0x7d, 0x97, 0xc6, 0x99, 0x50, 0x8e, 0x39, + 0x50, 0x7a, 0x5d, 0x1c, 0x28, 0x89, 0x6c, 0xf2, 0x47, 0x87, 0xd5, 0x73, 0x1d, 0xdf, 0xa5, 0xfd, + 0x29, 0x50, 0x14, 0x8e, 0xe4, 0x4b, 0x30, 0x62, 0xf5, 0x5a, 0x54, 0x26, 0xe6, 0x18, 0x97, 0x13, + 0xbe, 0xd7, 0x52, 0xb2, 0x4c, 0x06, 0xbd, 0xf4, 0x3d, 0x02, 0x03, 0x90, 0x0f, 0x00, 0xee, 0xf7, + 0x76, 0xe8, 0xdd, 0xc0, 0xef, 0x75, 0x65, 0xe4, 0x2f, 0x9a, 0xb1, 0xfb, 0x71, 0x1a, 0x27, 0x7b, + 0x17, 0x0b, 0xd5, 0x8f, 0x27, 0x24, 0x64, 0x0d, 0xc6, 0x84, 0xf8, 0x10, 0xe7, 0xf4, 0x2f, 0x65, + 0x9d, 0x10, 0x29, 0x3a, 0x84, 0xc8, 0x94, 0x81, 0x60, 0xfd, 0xd0, 0x86, 0x9b, 0xe1, 0xef, 0x42, + 0x89, 0xb1, 0x67, 0xa6, 0x76, 0x28, 0xf6, 0x0e, 0xf4, 0x1f, 0x54, 0x2a, 0xc4, 0xcc, 0x72, 0x2d, + 0x5f, 0x57, 0x4c, 0x40, 0xbe, 0x86, 0xb9, 0x6d, 0x44, 0x57, 0x1f, 0x7b, 0xd0, 0xf8, 0x5a, 0x5f, + 0x57, 0x9f, 0x75, 0xba, 0xdd, 0x8c, 0x64, 0x60, 0x31, 0x3f, 0xb2, 0x1b, 0xc7, 0xd8, 0xc4, 0xa9, + 0x86, 0x8f, 0xf9, 0xc0, 0xf5, 0xbe, 0x0f, 0xcc, 0xca, 0xb0, 0x91, 0xfe, 0x8c, 0x36, 0x1a, 0x5f, + 0xd2, 0x85, 0x4a, 0x92, 0x46, 0x4b, 0x7c, 0x0b, 0x8e, 0xfb, 0xd6, 0x9b, 0x7d, 0xdf, 0x52, 0x07, + 0xb0, 0xef, 0x73, 0x7d, 0xdc, 0x89, 0x9b, 0xa4, 0x85, 0x15, 0xdf, 0x1b, 0x3f, 0xee, 0x7b, 0xaf, + 0xf6, 0x7d, 0x6f, 0xc6, 0xdd, 0xe9, 0xff, 0x4e, 0x8a, 0x27, 0x79, 0x17, 0x26, 0x24, 0x04, 0xd7, + 0x07, 0x1e, 0xf0, 0x09, 0xfd, 0xde, 0xdd, 0x41, 0xa7, 0x31, 0x3d, 0x9d, 0x8b, 0x8a, 0xac, 0x52, + 0xf3, 0xd9, 0x31, 0xa1, 0x51, 0xa7, 0x67, 0x85, 0x8e, 0x4c, 0xbe, 0x0a, 0xe3, 0xcb, 0x6d, 0xd6, + 0x10, 0xbf, 0xe3, 0x44, 0x14, 0x37, 0xa3, 0xe4, 0xd0, 0x54, 0x29, 0x51, 0xa6, 0x2a, 0xcf, 0xf1, + 0x98, 0x14, 0xa9, 0x9b, 0xb9, 0x42, 0xc1, 0x3a, 0x8f, 0x1f, 0xbf, 0x88, 0x39, 0x1c, 0x8a, 0xad, + 0xe7, 0xc5, 0x8c, 0x83, 0x4b, 0x85, 0x3d, 0xca, 0x72, 0x7e, 0xaa, 0x63, 0x8b, 0x05, 0xa1, 0x75, + 0x9e, 0xce, 0x93, 0xbc, 0x07, 0xe3, 0x22, 0xa2, 0xb1, 0x66, 0xad, 0x86, 0xb3, 0x15, 0x6c, 0x3c, + 0xe6, 0x62, 0x93, 0xc1, 0x8f, 0xb6, 0x13, 0xa4, 0x6e, 0xaf, 0x12, 0x7c, 0xf2, 0x15, 0x38, 0xbb, + 0xed, 0x75, 0x5c, 0xff, 0x71, 0x28, 0x04, 0xb8, 0x10, 0x74, 0xd3, 0x89, 0x8f, 0xce, 0x63, 0x5e, + 0x6e, 0xcb, 0x6d, 0xab, 0x4f, 0xf0, 0x65, 0x72, 0x20, 0xbf, 0xd0, 0xc7, 0x99, 0xcf, 0x20, 0x72, + 0xdc, 0x0c, 0x9a, 0xef, 0x9b, 0x41, 0xfd, 0x9f, 0x4f, 0x4f, 0xa7, 0xcc, 0xcf, 0x10, 0x1f, 0x88, + 0xae, 0x73, 0x7c, 0xe8, 0x7b, 0x9d, 0xd9, 0x19, 0xed, 0x21, 0x8f, 0xd8, 0x65, 0x16, 0xf1, 0xd6, + 0xfd, 0x96, 0xd7, 0x3c, 0xa8, 0x9b, 0x47, 0x87, 0xd5, 0x97, 0xd2, 0xda, 0xcc, 0xc7, 0xbe, 0x76, + 0xb8, 0x90, 0xc1, 0x9a, 0x7c, 0x15, 0xca, 0xec, 0x6f, 0xac, 0xfa, 0x9d, 0xd5, 0xae, 0xba, 0x14, + 0x4c, 0xf1, 0x1d, 0x1c, 0x23, 0x0c, 0xb9, 0xcc, 0xd0, 0x0a, 0x35, 0x56, 0xe6, 0x0f, 0x0d, 0x38, + 0x9b, 0x55, 0xd7, 0x13, 0xf2, 0xdb, 0x98, 0xa9, 0x4b, 0x6f, 0x3c, 0x97, 0xe0, 0x97, 0xde, 0xf1, + 0x55, 0x77, 0x15, 0x46, 0x98, 0xad, 0x2c, 0x9d, 0xb2, 0x70, 0x3b, 0x64, 0xf6, 0x74, 0x68, 0x71, + 0x38, 0x43, 0x40, 0x67, 0x7a, 0xdc, 0x2f, 0x47, 0x38, 0x02, 0x7a, 0xdc, 0x5b, 0x1c, 0xce, 0x10, + 0xd8, 0xb6, 0x2b, 0xb7, 0x09, 0x44, 0x60, 0xbb, 0x71, 0x68, 0x71, 0x38, 0x79, 0x0d, 0xc6, 0xd6, + 0x3a, 0x2b, 0xd4, 0x79, 0x44, 0xc5, 0x8d, 0x13, 0x9e, 0xa3, 0xf8, 0x1d, 0xbb, 0xc5, 0x60, 0x96, + 0x2c, 0x34, 0xbf, 0x6b, 0xc0, 0x74, 0x5f, 0x37, 0x9d, 0x9c, 0xc2, 0xe7, 0xf8, 0xeb, 0xbd, 0x61, + 0xda, 0xc7, 0xab, 0x5f, 0xc8, 0xae, 0xbe, 0xf9, 0x57, 0x79, 0xb8, 0x30, 0x60, 0xd7, 0x4a, 0xae, + 0xe6, 0x8d, 0x13, 0xaf, 0xe6, 0xbf, 0xce, 0x76, 0x09, 0xc7, 0x6b, 0x87, 0x1b, 0x7e, 0x52, 0xe3, + 0xe4, 0x16, 0x03, 0xcb, 0x64, 0x8e, 0x0c, 0x99, 0xcf, 0xe1, 0x62, 0x13, 0x29, 0xec, 0xc8, 0xef, + 0x3b, 0x33, 0xd6, 0x99, 0xf5, 0x5d, 0x8e, 0xe7, 0x7f, 0x4c, 0x2e, 0xc7, 0xf5, 0x2b, 0xa9, 0xc2, + 0x53, 0xbd, 0x92, 0xca, 0x3e, 0x24, 0x1f, 0xf9, 0x34, 0x57, 0x01, 0xff, 0x2e, 0x75, 0x1d, 0xff, + 0xe3, 0x38, 0xd4, 0xd7, 0x60, 0x64, 0x7b, 0x8f, 0x06, 0x52, 0xbf, 0xc5, 0x8a, 0x3c, 0x66, 0x00, + 0xb5, 0x22, 0x88, 0x61, 0xfe, 0x3c, 0x94, 0xd5, 0x8f, 0xe1, 0x5a, 0x66, 0xbf, 0xc5, 0x62, 0xe2, + 0x6b, 0x99, 0x01, 0x2c, 0x0e, 0x3f, 0x31, 0x23, 0x56, 0xd2, 0x0b, 0xf9, 0x93, 0x7a, 0xc1, 0xfc, + 0xb7, 0x06, 0x14, 0x30, 0x21, 0xc0, 0x5b, 0x50, 0x92, 0x47, 0xa5, 0x6a, 0x90, 0xfc, 0x8c, 0x3c, + 0x49, 0x0d, 0x75, 0x7f, 0x06, 0x01, 0x64, 0x9f, 0xda, 0xa2, 0xc1, 0x8e, 0xe6, 0xf6, 0xf2, 0x88, + 0x01, 0xd4, 0x4f, 0x21, 0xc6, 0x29, 0xba, 0x04, 0x5d, 0x7b, 0x84, 0x7d, 0xcf, 0x17, 0x3c, 0x77, + 0xed, 0xe9, 0xb3, 0xeb, 0x25, 0x96, 0xf9, 0xdb, 0x06, 0x9c, 0xcb, 0xd4, 0x03, 0xd8, 0x57, 0xb9, + 0xc2, 0xa1, 0xcc, 0x88, 0xb4, 0xb6, 0xc1, 0x31, 0x4e, 0xe3, 0xc2, 0x73, 0x8a, 0xe1, 0xfd, 0x1c, + 0x94, 0x62, 0xfb, 0x8c, 0x9c, 0x95, 0x43, 0x87, 0xe7, 0x69, 0xd2, 0x98, 0xf9, 0x1b, 0x03, 0x46, + 0x59, 0x15, 0x9e, 0xd9, 0xe8, 0x8a, 0xec, 0xd3, 0x55, 0xd6, 0xa4, 0xa1, 0x62, 0x2a, 0xfe, 0x60, + 0x14, 0x20, 0x41, 0x26, 0x3b, 0x30, 0xb9, 0xb6, 0xbc, 0xb8, 0xb0, 0xec, 0xd2, 0x4e, 0x84, 0xb7, + 0x7c, 0xa9, 0x28, 0x7b, 0x66, 0x58, 0x06, 0x1d, 0xa7, 0x25, 0x10, 0x0e, 0x92, 0xe5, 0xe9, 0x7b, + 0x6e, 0xd3, 0xf6, 0x62, 0x3a, 0x55, 0x21, 0xd3, 0x39, 0xb2, 0x6f, 0x34, 0x6a, 0x0f, 0x56, 0x94, + 0x6f, 0xe4, 0x86, 0xfc, 0x46, 0xe8, 0xb4, 0x5b, 0x03, 0xbe, 0xa1, 0x73, 0x24, 0x7b, 0x50, 0xb9, + 0x8b, 0xb2, 0x5b, 0xf9, 0x4a, 0xfe, 0xf8, 0xaf, 0xbc, 0x2c, 0xbe, 0xf2, 0x02, 0x17, 0xfa, 0xd9, + 0xdf, 0xe9, 0xe3, 0x9a, 0xcc, 0xdc, 0xc2, 0x89, 0x33, 0xf7, 0x6f, 0x1b, 0x30, 0xca, 0x37, 0x87, + 0xf8, 0x2d, 0x8b, 0xcc, 0xed, 0x67, 0xfb, 0xe9, 0x6c, 0x3f, 0x95, 0x08, 0xff, 0x53, 0x0d, 0x70, + 0x5e, 0x46, 0x16, 0x53, 0x0f, 0x63, 0xc8, 0x23, 0x74, 0x54, 0x4c, 0x79, 0x49, 0xe2, 0x08, 0xc5, + 0xdf, 0xc4, 0x50, 0xb9, 0x70, 0x0c, 0xf5, 0x99, 0xbe, 0xb1, 0x4f, 0xf9, 0x4c, 0xdf, 0x0a, 0x94, + 0x84, 0x67, 0x4f, 0xfd, 0x40, 0x98, 0x9f, 0xf2, 0x80, 0x25, 0x86, 0x2b, 0xc9, 0xa7, 0x39, 0xc8, + 0xde, 0xd1, 0x52, 0xc7, 0xc5, 0x88, 0x64, 0x0d, 0x4a, 0x49, 0x68, 0x48, 0x49, 0xbb, 0x07, 0x8d, + 0xe1, 0xc2, 0xf5, 0x95, 0x47, 0x1f, 0x66, 0x46, 0x82, 0x24, 0x3c, 0xcc, 0x6f, 0x1a, 0x50, 0x49, + 0xcf, 0x17, 0xf2, 0x2e, 0x8c, 0xc7, 0xd1, 0x39, 0xb1, 0x7f, 0x01, 0x1e, 0x64, 0x26, 0xe1, 0x3c, + 0x9a, 0xa7, 0x81, 0x8a, 0x4e, 0xe6, 0xa1, 0xc8, 0x96, 0x9d, 0x92, 0x3b, 0x18, 0xe5, 0x49, 0x4f, + 0xc0, 0xd4, 0x7b, 0x3d, 0x89, 0xa7, 0xac, 0xda, 0xff, 0x90, 0x87, 0x71, 0x65, 0xb0, 0xc8, 0x35, + 0x28, 0x2e, 0x87, 0x2b, 0x7e, 0x73, 0x9f, 0xba, 0xe2, 0xba, 0x00, 0x5f, 0x61, 0xf4, 0x42, 0xbb, + 0x85, 0x40, 0x2b, 0x2e, 0x26, 0x75, 0x98, 0xe0, 0xff, 0xc9, 0x28, 0xcc, 0x5c, 0x72, 0xd4, 0xc9, + 0x91, 0x65, 0xfc, 0xa5, 0xba, 0xc3, 0x6a, 0x24, 0xe4, 0x1b, 0x00, 0x1c, 0xc0, 0xc6, 0x77, 0x08, + 0xc7, 0x5e, 0xb9, 0x80, 0xcf, 0x89, 0x0f, 0x44, 0x9e, 0xda, 0x42, 0x9c, 0x0a, 0x0a, 0x43, 0x7c, + 0x01, 0xce, 0x6f, 0xee, 0x0f, 0xff, 0x06, 0x64, 0xf2, 0x02, 0x9c, 0xdf, 0xdc, 0xb7, 0xb3, 0xbd, + 0xbc, 0x54, 0x96, 0xe4, 0x5b, 0x06, 0x5c, 0xb2, 0x68, 0xd3, 0x7f, 0x44, 0x83, 0x83, 0x5a, 0x84, + 0x58, 0xea, 0x17, 0x4f, 0x76, 0x29, 0xbb, 0x25, 0xbe, 0xf8, 0x7a, 0x20, 0xb8, 0x60, 0x38, 0x4a, + 0xbb, 0x1b, 0xd9, 0xc7, 0x54, 0xe1, 0x98, 0x4f, 0x9a, 0x7f, 0x6e, 0x28, 0x4b, 0x80, 0xac, 0x42, + 0x29, 0x9e, 0x2c, 0xe2, 0xc0, 0x31, 0x56, 0x8e, 0x24, 0xdc, 0xa2, 0x0f, 0xeb, 0x2f, 0x88, 0x93, + 0xfd, 0x99, 0x78, 0xca, 0x69, 0x2b, 0x42, 0x02, 0xc9, 0x97, 0xa1, 0x80, 0x43, 0x75, 0x72, 0xb2, + 0x29, 0xb9, 0xd5, 0x14, 0xd8, 0x18, 0x61, 0xad, 0x91, 0x92, 0x7c, 0x41, 0x78, 0x79, 0xe4, 0xb5, + 0x34, 0xae, 0x0c, 0xc4, 0xea, 0x11, 0xef, 0x31, 0x89, 0x63, 0xa1, 0x32, 0x5b, 0xff, 0x4e, 0x0e, + 0x2a, 0xe9, 0x85, 0x47, 0x3e, 0x80, 0xf2, 0xba, 0x13, 0x86, 0x8f, 0xfd, 0xc0, 0xbd, 0xe7, 0x84, + 0x7b, 0x22, 0x35, 0x24, 0xda, 0x7c, 0x5d, 0x01, 0xb7, 0xf7, 0x1c, 0x2d, 0x85, 0x98, 0x46, 0xc0, + 0x36, 0xe4, 0x0d, 0xe1, 0xaf, 0xae, 0x2c, 0xa0, 0xc8, 0x8f, 0xba, 0xa9, 0xd4, 0x90, 0x12, 0x8d, + 0xbc, 0x05, 0x79, 0x1e, 0xfb, 0xa6, 0xe6, 0x15, 0x7a, 0x70, 0xa7, 0xc6, 0xc3, 0x85, 0xf8, 0x65, + 0xb2, 0x7e, 0x2a, 0xcf, 0xf0, 0xc9, 0x8a, 0x12, 0x39, 0x35, 0xaa, 0xe5, 0x57, 0x91, 0xe0, 0xb8, + 0x71, 0x27, 0x87, 0x50, 0x7d, 0x58, 0x28, 0xe6, 0x2b, 0x05, 0x11, 0x9f, 0xf3, 0x47, 0x79, 0x28, + 0xc5, 0xdf, 0x27, 0x04, 0x50, 0xdf, 0x10, 0xb7, 0xc2, 0xf8, 0x3f, 0xb9, 0x08, 0x45, 0xa9, 0x62, + 0x88, 0x9b, 0xe1, 0xb1, 0x50, 0xa8, 0x17, 0xb3, 0x20, 0x75, 0x09, 0xae, 0x5e, 0x58, 0xf2, 0x27, + 0xb9, 0x09, 0xb1, 0xa2, 0x30, 0x48, 0xa3, 0x28, 0xb0, 0x01, 0xb3, 0x62, 0x34, 0x32, 0x09, 0x39, + 0x8f, 0xfb, 0x22, 0x97, 0xac, 0x9c, 0xe7, 0x92, 0x0f, 0xa0, 0xe8, 0xb8, 0x2e, 0x75, 0x6d, 0x27, + 0x1a, 0xe2, 0x3d, 0xce, 0x22, 0xe3, 0xc6, 0x25, 0x3a, 0x52, 0xd5, 0x22, 0x52, 0x83, 0x12, 0x3e, + 0xc7, 0xd8, 0x0b, 0x87, 0x7a, 0xc3, 0x31, 0xe1, 0x50, 0x64, 0x64, 0x9b, 0x21, 0x75, 0xc9, 0xeb, + 0x50, 0x60, 0xa3, 0x29, 0xf6, 0x83, 0x38, 0x5b, 0xdc, 0xda, 0xc6, 0x3a, 0xef, 0xb0, 0x7b, 0x67, + 0x2c, 0x44, 0x20, 0xaf, 0x40, 0xbe, 0x37, 0xff, 0x50, 0x48, 0xfa, 0x4a, 0x12, 0x16, 0x19, 0xa3, + 0xb1, 0x62, 0x72, 0x0b, 0x8a, 0x8f, 0xf5, 0x00, 0xb8, 0x73, 0xa9, 0x61, 0x8c, 0xf1, 0x63, 0xc4, + 0x7a, 0x11, 0x46, 0x79, 0xb8, 0x99, 0xf9, 0x12, 0x40, 0xf2, 0xe9, 0xfe, 0x0b, 0x7c, 0xf3, 0x1b, + 0x50, 0x8a, 0x3f, 0x49, 0x5e, 0x04, 0xd8, 0xa7, 0x07, 0xf6, 0x9e, 0xd3, 0x71, 0xc5, 0x33, 0x24, + 0x65, 0xab, 0xb4, 0x4f, 0x0f, 0xee, 0x21, 0x80, 0x5c, 0x80, 0xb1, 0x2e, 0x1b, 0x55, 0x99, 0xd8, + 0xd4, 0x1a, 0xed, 0xf6, 0x76, 0xd8, 0x0c, 0x9d, 0x85, 0x31, 0x3c, 0x3a, 0x10, 0x0b, 0x6d, 0xc2, + 0x92, 0x3f, 0xcd, 0xdf, 0xcb, 0x61, 0xc8, 0xbb, 0x52, 0x4f, 0xf2, 0x32, 0x4c, 0x34, 0x03, 0x8a, + 0xdb, 0x91, 0xc3, 0xd4, 0x22, 0xf1, 0x9d, 0x72, 0x02, 0x5c, 0x76, 0xc9, 0x6b, 0x30, 0x95, 0x64, + 0x5a, 0xb5, 0x9b, 0x3b, 0x22, 0xfc, 0xb5, 0x6c, 0x4d, 0x74, 0x65, 0xaa, 0xd5, 0x85, 0x1d, 0xf4, + 0xa1, 0xaf, 0xa8, 0xa1, 0x66, 0x91, 0xcc, 0x9a, 0x5a, 0xb2, 0xa6, 0x14, 0x38, 0x5e, 0x3b, 0x9c, + 0x87, 0x51, 0xc7, 0xd9, 0xed, 0x79, 0xdc, 0x9f, 0xb7, 0x6c, 0x89, 0x5f, 0xe4, 0xf3, 0x30, 0x1d, + 0x7a, 0xbb, 0x1d, 0x27, 0xea, 0x05, 0x22, 0xe7, 0x00, 0x0d, 0x70, 0x4a, 0x4d, 0x58, 0x95, 0xb8, + 0x60, 0x81, 0xc3, 0xc9, 0x9b, 0x40, 0xd4, 0xef, 0xf9, 0x3b, 0x1f, 0xd3, 0x26, 0x9f, 0x6a, 0x65, + 0x6b, 0x5a, 0x29, 0x59, 0xc3, 0x02, 0xf2, 0x39, 0x28, 0x07, 0x34, 0x44, 0x95, 0x0c, 0xbb, 0x0d, + 0x33, 0xa9, 0x58, 0xe3, 0x12, 0x76, 0x9f, 0x1e, 0x98, 0x75, 0x98, 0xee, 0x5b, 0x8f, 0xe4, 0x4d, + 0xae, 0xdd, 0x8b, 0xfd, 0xb9, 0xcc, 0x8d, 0x19, 0x26, 0xa4, 0x52, 0x2f, 0xf8, 0x72, 0x24, 0xb3, + 0x03, 0x65, 0x55, 0xbe, 0x9e, 0x10, 0x58, 0x7c, 0x1e, 0x1d, 0x0b, 0xb9, 0xf0, 0x19, 0x3d, 0x3a, + 0xac, 0xe6, 0x3c, 0x17, 0xdd, 0x09, 0xaf, 0x42, 0x51, 0x6a, 0x09, 0xea, 0x33, 0x1c, 0x42, 0xa1, + 0x3c, 0xb0, 0xe2, 0x52, 0xf3, 0x75, 0x18, 0x13, 0x22, 0xf4, 0xf8, 0x63, 0x1c, 0xf3, 0x97, 0x73, + 0x30, 0x65, 0x51, 0xb6, 0xc0, 0xc5, 0x03, 0x17, 0xcf, 0x59, 0xce, 0x59, 0xad, 0x6d, 0xc7, 0xc4, + 0xf1, 0x7f, 0xcf, 0x80, 0x99, 0x0c, 0xdc, 0x4f, 0x94, 0xa4, 0xea, 0x36, 0x94, 0x16, 0x3d, 0xa7, + 0x55, 0x73, 0xdd, 0xd8, 0x41, 0x12, 0xb5, 0x41, 0x97, 0x2d, 0x27, 0x87, 0x41, 0xd5, 0xcd, 0x34, + 0x46, 0x25, 0xd7, 0xc5, 0xa4, 0x48, 0xd2, 0xe8, 0xc9, 0xac, 0xb6, 0xc0, 0xeb, 0x94, 0xe4, 0xb4, + 0xc5, 0x30, 0x34, 0x0e, 0x4c, 0xee, 0xc0, 0x9f, 0xd9, 0xa1, 0xcb, 0x0e, 0x43, 0x4b, 0x37, 0x6f, + 0x28, 0xb3, 0xf3, 0x9b, 0x39, 0x38, 0x9f, 0x4d, 0xf8, 0x49, 0xf3, 0x8d, 0x61, 0x12, 0x05, 0x25, + 0x71, 0x30, 0xe6, 0x1b, 0xe3, 0x19, 0x17, 0x10, 0x3f, 0x41, 0x20, 0x0f, 0x61, 0x62, 0xc5, 0x09, + 0xa3, 0x7b, 0xd4, 0x09, 0xa2, 0x1d, 0xea, 0x44, 0x43, 0x68, 0xb0, 0xf1, 0x3b, 0xb9, 0xb8, 0xa9, + 0xed, 0x49, 0xca, 0xf4, 0x3b, 0xb9, 0x1a, 0xdb, 0x78, 0xa2, 0x14, 0x86, 0x98, 0x28, 0x3f, 0x0b, + 0x53, 0x0d, 0xda, 0x76, 0xba, 0x7b, 0x7e, 0x40, 0xc5, 0xc9, 0xf3, 0x0d, 0x98, 0x88, 0x41, 0x99, + 0xb3, 0x45, 0x2f, 0xd6, 0xf0, 0x95, 0x8e, 0x48, 0x44, 0x89, 0x5e, 0x6c, 0xfe, 0x4e, 0x0e, 0x2e, + 0xd4, 0x9a, 0xe2, 0x98, 0x5e, 0x14, 0xc8, 0xdb, 0xc4, 0xcf, 0xf8, 0xdb, 0x64, 0x0e, 0x4a, 0x0f, + 0x9c, 0x27, 0xf8, 0xe0, 0x7b, 0x28, 0xb2, 0xd6, 0x70, 0xf5, 0xcb, 0x79, 0x62, 0xc7, 0xc7, 0x5e, + 0x56, 0x82, 0xf3, 0x34, 0xdf, 0x84, 0x37, 0x61, 0xf4, 0x9e, 0xdf, 0x72, 0xc5, 0xe6, 0x24, 0x4e, + 0xfd, 0xf7, 0x10, 0x62, 0x89, 0x12, 0xf3, 0x87, 0x06, 0x4c, 0xc6, 0x35, 0xc6, 0x2a, 0x7c, 0xe6, + 0x5d, 0x92, 0x7a, 0x1d, 0xbf, 0x34, 0xc4, 0xeb, 0xf8, 0x23, 0x9f, 0xae, 0x27, 0xcc, 0x7f, 0x8c, + 0x17, 0x0a, 0x6a, 0x2b, 0xd9, 0x4e, 0xa4, 0x54, 0xc4, 0x18, 0xb2, 0x22, 0xb9, 0xa7, 0x36, 0x24, + 0xf9, 0x81, 0x43, 0xf2, 0x2b, 0x39, 0x18, 0x8f, 0x2b, 0xfb, 0x9c, 0xc5, 0x6f, 0xc7, 0xed, 0x1a, + 0xca, 0x3b, 0xbb, 0xa1, 0xc8, 0x0a, 0xe1, 0x04, 0xfd, 0x65, 0x18, 0x15, 0x8b, 0xc9, 0x48, 0xdd, + 0xaa, 0xa5, 0x46, 0x37, 0x79, 0xeb, 0x14, 0x07, 0x34, 0xb4, 0x04, 0x1d, 0xba, 0xbf, 0x6f, 0xd3, + 0x1d, 0x71, 0xbf, 0xf4, 0xcc, 0xee, 0x51, 0xd9, 0xee, 0xef, 0x49, 0xc3, 0x86, 0xda, 0x9d, 0xfe, + 0x41, 0x01, 0x2a, 0x69, 0x92, 0x93, 0x23, 0xe4, 0xd7, 0x7b, 0x3b, 0xe2, 0x9d, 0x02, 0x8c, 0x90, + 0xef, 0xf6, 0x76, 0x2c, 0x06, 0x23, 0xaf, 0x41, 0x61, 0x3d, 0xf0, 0x1e, 0x61, 0xab, 0xc5, 0x33, + 0x0d, 0xdd, 0xc0, 0x7b, 0xa4, 0xfa, 0x81, 0xb2, 0x72, 0x34, 0x68, 0x57, 0x1a, 0xca, 0x33, 0xd3, + 0xdc, 0xa0, 0x6d, 0x85, 0xe9, 0xb4, 0x28, 0x12, 0x8d, 0x6d, 0x95, 0x75, 0xea, 0x04, 0x22, 0x9a, + 0x5b, 0x88, 0x33, 0xdc, 0x2a, 0x77, 0x10, 0xcc, 0x73, 0x9e, 0x5a, 0x2a, 0x12, 0x69, 0x01, 0x51, + 0x7e, 0xca, 0x05, 0x7c, 0xb2, 0x8d, 0x27, 0x9f, 0x17, 0x3a, 0xab, 0xb2, 0xb6, 0xd5, 0xd5, 0x9c, + 0xc1, 0xf7, 0x69, 0x9e, 0x11, 0xae, 0x43, 0x09, 0x8f, 0xbc, 0xf0, 0x20, 0xa3, 0x78, 0x22, 0x33, + 0xe9, 0x73, 0x0b, 0x78, 0x1b, 0x6f, 0xc7, 0xc7, 0x19, 0x09, 0x13, 0xf2, 0x3e, 0x8c, 0xab, 0x8e, + 0xa2, 0xdc, 0x9d, 0xf1, 0x32, 0x8f, 0x10, 0x1a, 0x90, 0x3e, 0x4c, 0x25, 0x30, 0xbf, 0xa0, 0xce, + 0x12, 0xb1, 0x69, 0x1f, 0x3b, 0x4b, 0xcc, 0xdf, 0x42, 0x35, 0xbe, 0xed, 0x47, 0x54, 0x68, 0x2f, + 0xcf, 0xac, 0x1c, 0x4b, 0x8e, 0x90, 0x47, 0x34, 0x8f, 0x10, 0xad, 0x75, 0xa7, 0x78, 0x60, 0xf9, + 0x0f, 0x0d, 0x38, 0x97, 0x49, 0x4b, 0x6e, 0x00, 0x24, 0x3a, 0xa2, 0xe8, 0x25, 0x9e, 0x4c, 0x36, + 0x86, 0x5a, 0x0a, 0x06, 0xf9, 0x7a, 0x5a, 0xbb, 0x3b, 0x79, 0x73, 0x92, 0x4f, 0x2e, 0x4c, 0xea, + 0xda, 0x5d, 0x86, 0x4e, 0x67, 0x7e, 0x2f, 0x0f, 0xd3, 0x7d, 0x4f, 0xf5, 0x9d, 0x70, 0x07, 0xbf, + 0x9f, 0x7a, 0x08, 0x8a, 0x5f, 0x77, 0x5c, 0x1f, 0xf4, 0x50, 0x60, 0xc6, 0xb3, 0x50, 0x78, 0x2c, + 0x26, 0xf2, 0x18, 0x9f, 0xf0, 0x3a, 0x54, 0x98, 0xfd, 0x84, 0xd8, 0xe7, 0x07, 0x7e, 0xed, 0x29, + 0x3c, 0x25, 0xf6, 0x63, 0xfc, 0xd2, 0xd2, 0x6f, 0xe5, 0x60, 0xa6, 0xaf, 0xcd, 0xcf, 0xec, 0xaa, + 0xfb, 0xb2, 0xb6, 0xbb, 0xbd, 0x34, 0x68, 0x4c, 0x87, 0xd2, 0x22, 0xfe, 0xbb, 0x01, 0x17, 0x06, + 0x50, 0x92, 0x83, 0xf4, 0x24, 0xe2, 0x5a, 0xc5, 0xcd, 0xe3, 0x3f, 0xf8, 0x54, 0xa6, 0xd2, 0x67, + 0x36, 0x13, 0x7e, 0x39, 0x07, 0xb0, 0x4d, 0x77, 0x9e, 0xed, 0xf4, 0x3f, 0xd9, 0x6f, 0xe1, 0xcb, + 0x66, 0x0d, 0x35, 0xee, 0x6b, 0x78, 0x90, 0x38, 0x7c, 0xee, 0x9f, 0xf8, 0x59, 0x89, 0x5c, 0xf6, + 0xb3, 0x12, 0xe6, 0x0e, 0x9c, 0xbd, 0x4b, 0xa3, 0x64, 0x27, 0x94, 0x36, 0xe4, 0xf1, 0x6c, 0xdf, + 0x80, 0x92, 0xc0, 0xd7, 0x53, 0x84, 0x4b, 0x87, 0x32, 0xcf, 0xb5, 0x12, 0x04, 0x93, 0xc2, 0x85, + 0x45, 0xda, 0xa2, 0x11, 0xfd, 0x6c, 0x3f, 0xd3, 0x00, 0xc2, 0x9b, 0xc2, 0x5f, 0x1b, 0x18, 0xea, + 0x0b, 0x27, 0xf6, 0xcf, 0x16, 0x9c, 0x8b, 0xeb, 0xfe, 0x34, 0xf9, 0xce, 0x31, 0x5d, 0x42, 0xc4, + 0xda, 0x25, 0x1c, 0x8f, 0x39, 0x44, 0x7c, 0x02, 0x97, 0x24, 0xc1, 0xb6, 0x17, 0xdf, 0xc4, 0x0c, + 0x45, 0x4b, 0xde, 0x85, 0x71, 0x85, 0x46, 0x04, 0xee, 0xe2, 0x6d, 0xe7, 0x63, 0x2f, 0xda, 0xb3, + 0x43, 0x0e, 0x57, 0x6f, 0x3b, 0x15, 0x74, 0xf3, 0x6b, 0xf0, 0x42, 0xec, 0xb7, 0x92, 0xf1, 0xe9, + 0x14, 0x73, 0xe3, 0x74, 0xcc, 0x57, 0x93, 0x66, 0x2d, 0x77, 0x62, 0xff, 0x71, 0xc9, 0x9b, 0xa8, + 0xcd, 0x12, 0x8d, 0xb9, 0xac, 0xa4, 0x45, 0x13, 0x7b, 0x51, 0x02, 0x30, 0xdf, 0x51, 0x2a, 0x9b, + 0xc1, 0x50, 0x23, 0x36, 0xd2, 0xc4, 0xbf, 0x9c, 0x83, 0xa9, 0xb5, 0xe5, 0xc5, 0x85, 0xf8, 0x18, + 0xf9, 0x39, 0xcb, 0x4d, 0xa4, 0xb5, 0x6d, 0xb0, 0xbc, 0x31, 0x37, 0x61, 0x26, 0xd5, 0x0d, 0xf8, + 0x98, 0xca, 0xfb, 0xdc, 0xbf, 0x24, 0x06, 0xcb, 0x9d, 0xe5, 0x7c, 0x16, 0xfb, 0xad, 0x5b, 0x56, + 0x0a, 0xdb, 0xfc, 0xde, 0x68, 0x8a, 0xaf, 0x10, 0x61, 0x6f, 0x40, 0x69, 0x39, 0x0c, 0x7b, 0x34, + 0xd8, 0xb4, 0x56, 0x54, 0x1d, 0xd1, 0x43, 0xa0, 0xdd, 0x0b, 0x5a, 0x56, 0x82, 0x40, 0xae, 0x41, + 0x51, 0xc4, 0x77, 0x49, 0x99, 0x80, 0xd7, 0xe5, 0x71, 0x78, 0x98, 0x15, 0x17, 0x93, 0xb7, 0xa0, + 0xcc, 0xff, 0xe7, 0xb3, 0x4d, 0x74, 0x38, 0x9e, 0x55, 0x09, 0x74, 0x3e, 0x3b, 0x2d, 0x0d, 0x8d, + 0x59, 0x66, 0xf2, 0xb5, 0x46, 0x56, 0xa3, 0x42, 0x62, 0x99, 0xc9, 0x87, 0x1d, 0xb1, 0x4e, 0x2a, + 0x12, 0xb9, 0x0e, 0xf9, 0xda, 0x82, 0xa5, 0x66, 0x45, 0x76, 0x9a, 0x01, 0xcf, 0x2a, 0xae, 0x3d, + 0x88, 0x54, 0x5b, 0xb0, 0xc8, 0x3c, 0x14, 0xf1, 0xc1, 0x0b, 0x97, 0x06, 0xc2, 0x67, 0x14, 0x67, + 0x4d, 0x57, 0xc0, 0xd4, 0x9b, 0x47, 0x89, 0x47, 0xe6, 0x60, 0x6c, 0xd1, 0x0b, 0xbb, 0x2d, 0xe7, + 0x40, 0x24, 0x25, 0xc1, 0xcb, 0x10, 0x97, 0x83, 0xd4, 0x79, 0x26, 0xb0, 0xc8, 0x35, 0x18, 0x69, + 0x34, 0xfd, 0x2e, 0xb3, 0xb6, 0x62, 0xd7, 0x96, 0x90, 0x01, 0xb4, 0xcc, 0x06, 0x0c, 0x80, 0x21, + 0xc7, 0x3c, 0x72, 0xaa, 0xa4, 0x84, 0x1c, 0xa7, 0x23, 0xa6, 0x04, 0x4e, 0xbf, 0xff, 0x1f, 0x3c, + 0x4d, 0xff, 0xbf, 0x1d, 0xb8, 0x70, 0x17, 0x55, 0xfd, 0x06, 0x0d, 0x30, 0x0f, 0x24, 0x7f, 0x3c, + 0x67, 0xd3, 0x5a, 0x16, 0xd1, 0x62, 0x57, 0x8f, 0x0e, 0xab, 0xaf, 0x70, 0x6b, 0xc0, 0x0e, 0x39, + 0x8e, 0x7c, 0x77, 0x27, 0xf5, 0x62, 0xc0, 0x20, 0x46, 0xe4, 0x2b, 0x70, 0x36, 0xab, 0x48, 0xc4, + 0x8d, 0xa1, 0x57, 0x78, 0xf6, 0x07, 0x54, 0xb7, 0xec, 0x2c, 0x0e, 0x64, 0x05, 0x2a, 0x1c, 0x5e, + 0x73, 0xdb, 0x5e, 0x67, 0xa9, 0xed, 0x78, 0x2d, 0x8c, 0x22, 0x13, 0xa1, 0x80, 0x82, 0xab, 0xc3, + 0x0a, 0x6d, 0xca, 0x4a, 0x35, 0xef, 0xa4, 0x14, 0x25, 0x8a, 0xa3, 0x46, 0xed, 0xc1, 0x4a, 0xb2, + 0xa6, 0x9e, 0xaf, 0x7b, 0x23, 0xad, 0x6d, 0xc7, 0xdc, 0x1b, 0x6d, 0xc2, 0x4c, 0xaa, 0x1b, 0xa4, + 0x38, 0xd2, 0xc0, 0x69, 0x71, 0x94, 0xa2, 0xb1, 0x52, 0xd8, 0xe6, 0x7f, 0x1c, 0x4d, 0xf1, 0x15, + 0x67, 0x45, 0x26, 0x8c, 0x72, 0x69, 0xa3, 0x66, 0x2d, 0xe3, 0xb2, 0xc8, 0x12, 0x25, 0xe4, 0x22, + 0xe4, 0x1b, 0x8d, 0x35, 0x35, 0xa7, 0x62, 0x18, 0xfa, 0x16, 0x83, 0xb1, 0x11, 0xc2, 0x63, 0x20, + 0x25, 0x40, 0xab, 0x49, 0x83, 0x48, 0x3c, 0xe7, 0xf9, 0x6a, 0xb2, 0x8e, 0x0b, 0x49, 0x7f, 0x8b, + 0x75, 0x9c, 0xac, 0xde, 0x05, 0x98, 0xad, 0x85, 0x21, 0x0d, 0x22, 0x9e, 0x94, 0x3d, 0xec, 0xb5, + 0x69, 0x20, 0xe6, 0x9a, 0x90, 0x31, 0xfc, 0x31, 0xf0, 0x66, 0x68, 0x0d, 0x44, 0x24, 0x57, 0xa1, + 0x58, 0xeb, 0xb9, 0x1e, 0xed, 0x34, 0x35, 0xdf, 0x74, 0x47, 0xc0, 0xac, 0xb8, 0x94, 0x7c, 0x04, + 0xe7, 0x04, 0x91, 0x14, 0x38, 0xa2, 0x07, 0xb8, 0xac, 0xe1, 0x16, 0xac, 0x58, 0x0b, 0x52, 0x4c, + 0xd9, 0xa2, 0x4b, 0xb2, 0x29, 0x49, 0x0d, 0x2a, 0x4b, 0x78, 0x4f, 0x2a, 0x1f, 0xf5, 0xf5, 0x03, + 0x91, 0x7c, 0x17, 0x25, 0x17, 0xbf, 0x43, 0xb5, 0xdd, 0xb8, 0xd0, 0xea, 0x43, 0x27, 0xf7, 0x61, + 0x26, 0x0d, 0x63, 0xf2, 0xb8, 0x94, 0x3c, 0xba, 0xd5, 0xc7, 0x05, 0x05, 0x73, 0x16, 0x15, 0xd9, + 0x81, 0xe9, 0x5a, 0x14, 0x05, 0xde, 0x4e, 0x2f, 0xa2, 0x29, 0xd1, 0x25, 0x0f, 0x1a, 0xe3, 0x72, + 0x29, 0xbe, 0x5e, 0x10, 0x93, 0x71, 0xc6, 0x89, 0x29, 0x63, 0x11, 0x66, 0xf5, 0xb3, 0x23, 0x6e, + 0xfc, 0x6e, 0x9f, 0x78, 0xdb, 0x4e, 0x04, 0x14, 0xc9, 0x03, 0xdd, 0x5a, 0x78, 0xd0, 0x6e, 0xd3, + 0x28, 0xc0, 0x9b, 0x7b, 0x7c, 0xfb, 0xce, 0x14, 0x3e, 0x40, 0x97, 0x94, 0xe7, 0x2a, 0xf1, 0x7d, + 0x43, 0xcd, 0x3d, 0x52, 0xe3, 0xa9, 0x6d, 0x1f, 0xe5, 0x21, 0xb7, 0x8f, 0x16, 0x4c, 0x2f, 0x75, + 0x9a, 0xc1, 0x01, 0x46, 0x36, 0xca, 0xca, 0x4d, 0x9c, 0x50, 0x39, 0xf9, 0xb0, 0xc5, 0x65, 0x47, + 0xce, 0xb0, 0xac, 0xea, 0xf5, 0x33, 0x36, 0xff, 0x3f, 0xa8, 0xa4, 0xfb, 0xf2, 0x53, 0x3e, 0x56, + 0x7c, 0x1a, 0xd7, 0x6c, 0x36, 0xd2, 0xe9, 0xb6, 0x90, 0x39, 0xed, 0x45, 0x5a, 0x23, 0x89, 0x4a, + 0x57, 0xde, 0x8e, 0xd5, 0xde, 0xa1, 0x95, 0xcb, 0x38, 0x97, 0xb5, 0x8c, 0xcd, 0x5f, 0xcd, 0xc1, + 0x34, 0xf7, 0x26, 0x7d, 0xf6, 0x75, 0xc5, 0xf7, 0x35, 0xe1, 0x2c, 0xcf, 0x02, 0x53, 0xad, 0x3b, + 0x46, 0x5b, 0xfc, 0x06, 0x9c, 0xeb, 0xeb, 0x0a, 0x14, 0xd0, 0x8b, 0xd2, 0x8f, 0xb7, 0x4f, 0x44, + 0xcf, 0x66, 0x7f, 0x64, 0xeb, 0x96, 0xd5, 0x47, 0x61, 0xfe, 0xc3, 0x5c, 0x1f, 0x7f, 0xa1, 0x37, + 0xaa, 0x9a, 0xa0, 0x71, 0x3a, 0x4d, 0x30, 0xf7, 0x89, 0x34, 0xc1, 0xfc, 0x30, 0x9a, 0xe0, 0x47, + 0x30, 0xb1, 0x41, 0x1d, 0xa6, 0xd1, 0x88, 0x60, 0xb3, 0x82, 0xf6, 0x5a, 0x2c, 0x2b, 0x93, 0xf2, + 0x25, 0x0e, 0x54, 0x8d, 0x18, 0x01, 0x13, 0x2d, 0x3c, 0xfa, 0xcc, 0xd2, 0x39, 0xa8, 0x9b, 0xc6, + 0xc8, 0xe0, 0x4d, 0xc3, 0xfc, 0x66, 0x0e, 0xc6, 0x15, 0xf6, 0xe4, 0x8b, 0x50, 0x5e, 0x0b, 0x76, + 0x9d, 0x8e, 0xf7, 0x73, 0x8e, 0x72, 0xfc, 0x8a, 0xd5, 0xf7, 0x15, 0xb8, 0xa5, 0x61, 0xa1, 0xdb, + 0x0c, 0x75, 0xda, 0xea, 0xc4, 0x67, 0xd5, 0xb3, 0x10, 0xaa, 0x04, 0x0b, 0xe7, 0x87, 0x08, 0x16, + 0xd6, 0x23, 0x6d, 0x0b, 0xa7, 0x8f, 0xb4, 0xd5, 0x02, 0x63, 0x47, 0x4e, 0x19, 0x18, 0x6b, 0xfe, + 0x7a, 0x0e, 0x2a, 0xe2, 0x5d, 0x55, 0x79, 0x78, 0xf8, 0x7c, 0xbd, 0xc3, 0xa0, 0x37, 0xee, 0x98, + 0xeb, 0xb1, 0xc2, 0x6f, 0xff, 0x7e, 0x15, 0x5f, 0xc9, 0x4c, 0x77, 0x87, 0x7c, 0x25, 0x53, 0x87, + 0xa7, 0x23, 0x07, 0xd2, 0x54, 0x56, 0x1a, 0xdf, 0xfc, 0xb3, 0x5c, 0x9a, 0xb7, 0xd0, 0xa6, 0x5e, + 0x85, 0x31, 0xfe, 0x2c, 0x96, 0x74, 0x6e, 0x16, 0xb9, 0x9b, 0x10, 0x64, 0xc9, 0xb2, 0xd3, 0xc4, + 0x90, 0x9c, 0xf4, 0x54, 0x2a, 0xb9, 0x0d, 0x65, 0xf4, 0x17, 0xa9, 0xb9, 0x6e, 0x40, 0xc3, 0x50, + 0x28, 0x5a, 0x78, 0x77, 0xf7, 0x98, 0xee, 0xd8, 0xdc, 0xaf, 0xc4, 0x71, 0xdd, 0xc0, 0xd2, 0xf0, + 0xc8, 0x02, 0x9c, 0xd5, 0xdc, 0x93, 0x24, 0xfd, 0x48, 0xb2, 0x5b, 0x44, 0x58, 0xc0, 0x89, 0x33, + 0x91, 0x9f, 0xde, 0x33, 0xd1, 0xe6, 0xff, 0x32, 0xd8, 0x5a, 0x6b, 0xee, 0x3f, 0x67, 0xd1, 0x2d, + 0xac, 0x49, 0xc7, 0x28, 0xfb, 0xff, 0xde, 0xe0, 0xfe, 0xe9, 0x62, 0xfa, 0xbc, 0x0d, 0xa3, 0xfc, + 0x11, 0x2e, 0xe1, 0x49, 0xad, 0x72, 0xe1, 0x05, 0xc9, 0xfd, 0x14, 0x7f, 0xca, 0xcb, 0x12, 0x04, + 0xcc, 0x64, 0xd6, 0xdd, 0xe4, 0x51, 0xf1, 0xec, 0xf7, 0x8f, 0x97, 0x58, 0x6a, 0x5e, 0xd2, 0xe1, + 0xf2, 0x5d, 0x1b, 0x27, 0xe7, 0x25, 0x35, 0xff, 0x4f, 0x8e, 0xb7, 0x47, 0x54, 0x6a, 0xd8, 0x84, + 0x7b, 0xaf, 0x41, 0x01, 0x9f, 0x7b, 0x55, 0xb2, 0x1a, 0xa6, 0x9e, 0x7a, 0xc5, 0x72, 0xb6, 0x6e, + 0x50, 0xd6, 0xaa, 0x01, 0x55, 0x28, 0x8e, 0xd5, 0x75, 0x83, 0x18, 0x98, 0x4d, 0xda, 0x77, 0xa9, + 0xba, 0x1c, 0x3a, 0x7a, 0xe2, 0x6f, 0x2c, 0x27, 0xb7, 0x15, 0xbf, 0x66, 0xf5, 0x40, 0xa3, 0xfd, + 0xd0, 0xb1, 0xb9, 0x3f, 0xad, 0x2a, 0x6d, 0x13, 0x17, 0xe8, 0x25, 0x98, 0xd4, 0x63, 0x95, 0x85, + 0xd1, 0x81, 0x21, 0xdf, 0xa9, 0x38, 0x67, 0x55, 0xbd, 0xd5, 0x89, 0x48, 0x1d, 0x26, 0xb4, 0x80, + 0x54, 0x35, 0x09, 0x2b, 0xcf, 0x0e, 0x63, 0xf7, 0x67, 0x52, 0xd0, 0x49, 0x94, 0x03, 0xf3, 0x2f, + 0x40, 0x45, 0xac, 0xcc, 0x38, 0xb6, 0x0d, 0x55, 0xbb, 0xe5, 0x45, 0x4b, 0x5d, 0x4d, 0x4d, 0xcf, + 0x0d, 0x2c, 0x84, 0x9a, 0xdf, 0x35, 0xe0, 0xa2, 0x78, 0x5c, 0xcc, 0xa2, 0x21, 0xd3, 0x21, 0x31, + 0x20, 0x0e, 0xe7, 0xe3, 0x17, 0xc9, 0xbb, 0x32, 0xf1, 0x94, 0x2e, 0x20, 0xd3, 0xdf, 0xa8, 0x4f, + 0x88, 0x49, 0x39, 0x82, 0xa9, 0xa7, 0x64, 0xc2, 0xa9, 0xb7, 0x45, 0xc2, 0xa9, 0xdc, 0xf1, 0xc4, + 0xf1, 0xba, 0x70, 0x69, 0x47, 0x26, 0x9a, 0xfa, 0x4e, 0x0e, 0xce, 0x65, 0x54, 0x6b, 0xeb, 0x8b, + 0xcf, 0xa8, 0x70, 0xa8, 0x6b, 0xc2, 0x41, 0x66, 0x24, 0x1c, 0xd8, 0xf1, 0x99, 0xb2, 0xe2, 0x77, + 0x0d, 0xb8, 0xa0, 0xcf, 0x1e, 0x61, 0x8b, 0x6e, 0xdd, 0x22, 0xef, 0xc0, 0xe8, 0x3d, 0xea, 0xb8, + 0x54, 0x86, 0x60, 0xc4, 0xd9, 0xbd, 0xc4, 0xe9, 0x30, 0x2f, 0xe4, 0x6c, 0xff, 0x8c, 0x2f, 0xe5, + 0x33, 0x96, 0x20, 0x21, 0x8b, 0xa2, 0x72, 0xfc, 0x7a, 0xca, 0x94, 0x37, 0x35, 0x59, 0x9f, 0x3a, + 0x46, 0x31, 0xfe, 0x43, 0x03, 0x5e, 0x38, 0x86, 0x86, 0x0d, 0x1c, 0x1b, 0x7a, 0x75, 0xe0, 0x70, + 0x63, 0x41, 0x28, 0x79, 0x1f, 0xa6, 0x36, 0x44, 0xa4, 0x98, 0x1c, 0x0e, 0x25, 0xbb, 0xbb, 0x0c, + 0x22, 0xb3, 0xe5, 0xb8, 0xa4, 0x91, 0x99, 0xf5, 0x7f, 0xcf, 0x0f, 0xa3, 0x4e, 0x92, 0x2c, 0x06, + 0xad, 0xff, 0x3d, 0x01, 0xb3, 0xe2, 0x52, 0xa6, 0x16, 0xe8, 0xd5, 0x14, 0xee, 0x10, 0x2f, 0xc3, + 0x28, 0xc3, 0x89, 0xb5, 0x6b, 0x9c, 0x07, 0xf8, 0xf4, 0x95, 0xe7, 0x5a, 0xa2, 0x28, 0xb6, 0xeb, + 0x72, 0x99, 0xb7, 0x16, 0xdf, 0x34, 0xa0, 0xa2, 0xf3, 0xfe, 0xb4, 0x43, 0xf3, 0x9e, 0x36, 0x34, + 0x2f, 0x64, 0x0f, 0xcd, 0xe0, 0x31, 0xe9, 0xcb, 0xdb, 0x30, 0xd4, 0x58, 0x98, 0x30, 0xba, 0xe8, + 0xb7, 0x1d, 0xaf, 0xa3, 0xe6, 0x1a, 0x70, 0x11, 0x62, 0x89, 0x12, 0xa5, 0xb7, 0xf2, 0x03, 0x7b, + 0xcb, 0xfc, 0x76, 0x01, 0x2e, 0x5a, 0x74, 0xd7, 0x63, 0x0a, 0xd2, 0x66, 0xe8, 0x75, 0x76, 0xb5, + 0x3b, 0x25, 0x33, 0xd5, 0xe1, 0xc2, 0x93, 0x8e, 0x41, 0xe2, 0xfe, 0xbe, 0x06, 0x45, 0x26, 0xa5, + 0x95, 0x3e, 0x47, 0xa3, 0x07, 0x33, 0xe6, 0xf0, 0x71, 0x95, 0xc5, 0xe4, 0xba, 0xd8, 0x43, 0x14, + 0x5f, 0x67, 0xb6, 0x87, 0xa4, 0x5e, 0xee, 0xe6, 0xfb, 0x48, 0xac, 0x54, 0x15, 0x06, 0x28, 0x55, + 0x0f, 0xe0, 0x6c, 0xcd, 0xe5, 0xf2, 0xc9, 0x69, 0xad, 0x07, 0x5e, 0xa7, 0xe9, 0x75, 0x9d, 0x96, + 0x54, 0xca, 0xf9, 0x3b, 0xe6, 0x71, 0xb9, 0xdd, 0x8d, 0x11, 0xac, 0x4c, 0x32, 0xd6, 0x8c, 0xc5, + 0xd5, 0x06, 0x4f, 0x88, 0x32, 0x8a, 0x2c, 0xb0, 0x19, 0x6e, 0x27, 0xe4, 0x19, 0x51, 0xac, 0xb8, + 0x18, 0xd5, 0x39, 0x0c, 0x77, 0xd8, 0x58, 0x69, 0xdc, 0x17, 0xe1, 0x03, 0xd2, 0x15, 0x8b, 0x47, + 0x47, 0x44, 0xad, 0x10, 0xed, 0x77, 0x0d, 0x2f, 0xa1, 0x6b, 0x34, 0xee, 0x31, 0xba, 0x62, 0x1f, + 0x5d, 0x18, 0xee, 0xa9, 0x74, 0x1c, 0x8f, 0xcc, 0x01, 0x70, 0x67, 0x16, 0x9c, 0x10, 0xa5, 0x44, + 0xf9, 0x0b, 0x10, 0xca, 0x95, 0x3f, 0x05, 0x85, 0xbc, 0x0b, 0x33, 0x4b, 0x0b, 0xf3, 0x32, 0x2e, + 0x60, 0xd1, 0x6f, 0xf6, 0xda, 0xb4, 0x13, 0x61, 0x9c, 0x4a, 0x99, 0x8f, 0x21, 0x6d, 0xce, 0xb3, + 0x59, 0x90, 0x85, 0x26, 0xa2, 0x03, 0x78, 0x6c, 0xd9, 0x82, 0xef, 0xd2, 0x70, 0xeb, 0xe6, 0x73, + 0x16, 0x1d, 0xa0, 0xb4, 0x0d, 0x57, 0xdb, 0xcd, 0xcc, 0x95, 0xf9, 0x77, 0x31, 0x3a, 0xa0, 0x0f, + 0x97, 0xfc, 0x24, 0x8c, 0xe0, 0x4f, 0xb1, 0xe3, 0xce, 0x64, 0xb0, 0x4d, 0x76, 0xdb, 0x26, 0xcf, + 0x6d, 0x81, 0x04, 0x64, 0x39, 0x49, 0x5e, 0x7f, 0x0a, 0x1f, 0x57, 0x11, 0xa0, 0xaa, 0xbf, 0x5a, + 0xe2, 0x42, 0x59, 0xfd, 0x20, 0x9b, 0x23, 0xf7, 0x9c, 0x70, 0x8f, 0xba, 0x0b, 0xf2, 0xdd, 0xc1, + 0x32, 0x9f, 0x23, 0x7b, 0x08, 0xc5, 0x17, 0x55, 0x2c, 0x05, 0x85, 0x49, 0x87, 0xe5, 0x70, 0x33, + 0x14, 0x55, 0x11, 0x56, 0x90, 0x87, 0xd6, 0xab, 0x6b, 0x89, 0x22, 0x94, 0x96, 0x32, 0x77, 0x63, + 0xe0, 0x34, 0xf7, 0x69, 0xb0, 0x75, 0xf3, 0xb3, 0x90, 0x96, 0xfa, 0x37, 0x8e, 0x19, 0x93, 0xdf, + 0x28, 0xc6, 0xa9, 0x59, 0x34, 0x64, 0xa6, 0x23, 0x26, 0x37, 0xf3, 0x46, 0xa2, 0x23, 0x26, 0x37, + 0xf3, 0xaa, 0x8e, 0x18, 0xa3, 0xc6, 0x99, 0x73, 0x73, 0x27, 0x64, 0xce, 0x1d, 0x90, 0xac, 0x5b, + 0x3a, 0x75, 0x3e, 0x47, 0xef, 0x16, 0x7c, 0x09, 0xca, 0xb5, 0x28, 0x72, 0x9a, 0x7b, 0xd4, 0xc5, + 0x0c, 0xcd, 0xca, 0x85, 0xa0, 0x23, 0xe0, 0xaa, 0xbb, 0x98, 0x8a, 0xab, 0xbc, 0x5b, 0x32, 0x36, + 0xc4, 0xbb, 0x25, 0x73, 0x30, 0xb6, 0xdc, 0x79, 0xe4, 0xb1, 0x3e, 0x29, 0x26, 0xc9, 0x21, 0x3c, + 0x0e, 0xd2, 0x1f, 0xbb, 0x40, 0x10, 0x79, 0x5b, 0xd1, 0x20, 0x4a, 0x89, 0x2a, 0x2f, 0x5e, 0x4c, + 0x96, 0x8a, 0x84, 0x7a, 0xde, 0x2c, 0xd1, 0xc9, 0x6d, 0x18, 0x93, 0xd6, 0x33, 0x24, 0xea, 0xbb, + 0xa0, 0x74, 0x78, 0x89, 0x96, 0x8f, 0x42, 0x58, 0xcf, 0xef, 0xea, 0xf1, 0x23, 0xe3, 0x4a, 0x5c, + 0xb6, 0x12, 0x3f, 0xa2, 0xc5, 0x65, 0x2b, 0x91, 0x24, 0xb1, 0x31, 0x54, 0x3e, 0xd1, 0x18, 0xda, + 0x82, 0xf2, 0xba, 0x13, 0x44, 0x1e, 0xdb, 0x8e, 0x3a, 0x11, 0x4f, 0xab, 0x95, 0xd8, 0xea, 0x4a, + 0x51, 0xfd, 0x25, 0x19, 0x9f, 0xdc, 0x55, 0xf0, 0xf5, 0xc0, 0xd6, 0x04, 0x4e, 0x56, 0x33, 0x3c, + 0x0c, 0x45, 0x12, 0x48, 0xbc, 0x02, 0x54, 0x0e, 0xae, 0x44, 0x8b, 0xd4, 0xa3, 0xf4, 0x7e, 0xe7, + 0xc4, 0x5b, 0x7c, 0x0c, 0xd0, 0x66, 0x9c, 0x42, 0x36, 0x98, 0x5d, 0x04, 0xf5, 0x8a, 0x94, 0xe1, + 0x18, 0x23, 0x92, 0xaf, 0x43, 0x99, 0xfd, 0x8f, 0x39, 0x86, 0x3c, 0xca, 0xd3, 0x66, 0x25, 0x1e, + 0x67, 0xfa, 0x82, 0xe6, 0x89, 0x88, 0x1a, 0x34, 0xe2, 0x0b, 0x18, 0x19, 0xa7, 0x0f, 0x5e, 0x34, + 0x6e, 0xe6, 0x0f, 0x0d, 0xb8, 0x30, 0x80, 0xc7, 0xd0, 0x2f, 0x16, 0xcd, 0x25, 0x1b, 0x92, 0x62, + 0x9b, 0x8b, 0x0d, 0x49, 0x9d, 0x18, 0x72, 0x6b, 0xca, 0x4e, 0x78, 0x95, 0xff, 0xcc, 0x12, 0x5e, + 0x99, 0x87, 0x06, 0x8c, 0x2b, 0x23, 0xfb, 0x14, 0x1f, 0x22, 0x78, 0x4d, 0x64, 0x7e, 0xcc, 0x27, + 0x78, 0xa9, 0xf7, 0x87, 0x79, 0xa6, 0xc7, 0x6f, 0x00, 0xac, 0x38, 0x61, 0x54, 0x6b, 0x46, 0xde, + 0x23, 0x3a, 0x84, 0x18, 0x4b, 0x02, 0xf5, 0x1d, 0x4c, 0xa4, 0xca, 0xc8, 0xfa, 0x02, 0xf5, 0x63, + 0x86, 0xe6, 0x2a, 0x8c, 0x36, 0xfc, 0x20, 0xaa, 0x1f, 0xf0, 0xbd, 0x69, 0x91, 0x86, 0x4d, 0xf5, + 0x84, 0xce, 0x43, 0x5b, 0xbd, 0x69, 0x89, 0x22, 0xa6, 0x20, 0xde, 0xf1, 0x68, 0xcb, 0x55, 0x6f, + 0x68, 0x1e, 0x32, 0x80, 0xc5, 0xe1, 0xd7, 0x3f, 0x80, 0x29, 0x99, 0x7c, 0x6e, 0x63, 0xa5, 0x81, + 0x2d, 0x98, 0x82, 0xf1, 0xad, 0x25, 0x6b, 0xf9, 0xce, 0x57, 0xed, 0x3b, 0x9b, 0x2b, 0x2b, 0x95, + 0x33, 0x64, 0x02, 0x4a, 0x02, 0xb0, 0x50, 0xab, 0x18, 0xa4, 0x0c, 0xc5, 0xe5, 0xd5, 0xc6, 0xd2, + 0xc2, 0xa6, 0xb5, 0x54, 0xc9, 0x5d, 0x7f, 0x15, 0x26, 0x93, 0xfb, 0x17, 0x8c, 0xd1, 0x1c, 0x83, + 0xbc, 0x55, 0xdb, 0xae, 0x9c, 0x21, 0x00, 0xa3, 0xeb, 0xf7, 0x17, 0x1a, 0x37, 0x6f, 0x56, 0x8c, + 0xeb, 0x5f, 0xc8, 0x78, 0xf4, 0x99, 0x71, 0x6a, 0xd0, 0xae, 0x13, 0x38, 0x11, 0xe5, 0x9f, 0x79, + 0xd0, 0x6b, 0x45, 0x5e, 0xb7, 0x45, 0x9f, 0x54, 0x8c, 0xeb, 0x6f, 0xf7, 0xbd, 0xdd, 0x4c, 0xce, + 0xc1, 0xf4, 0xe6, 0x6a, 0xed, 0x41, 0x7d, 0xf9, 0xee, 0xe6, 0xda, 0x66, 0xc3, 0x7e, 0x50, 0xdb, + 0x58, 0xb8, 0x57, 0x39, 0xc3, 0x2a, 0xfc, 0x60, 0xad, 0xb1, 0x61, 0x5b, 0x4b, 0x0b, 0x4b, 0xab, + 0x1b, 0x15, 0xe3, 0xfa, 0xaf, 0x19, 0x30, 0xa9, 0xbf, 0x86, 0x47, 0xae, 0xc0, 0xe5, 0xcd, 0xc6, + 0x92, 0x65, 0x6f, 0xac, 0xdd, 0x5f, 0x5a, 0xb5, 0x37, 0x1b, 0xb5, 0xbb, 0x4b, 0xf6, 0xe6, 0x6a, + 0x63, 0x7d, 0x69, 0x61, 0xf9, 0xce, 0xf2, 0xd2, 0x62, 0xe5, 0x0c, 0xa9, 0xc2, 0x0b, 0x0a, 0x86, + 0xb5, 0xb4, 0xb0, 0xb6, 0xb5, 0x64, 0xd9, 0xeb, 0xb5, 0x46, 0x63, 0x7b, 0xcd, 0x5a, 0xac, 0x18, + 0xe4, 0x12, 0x9c, 0xcf, 0x40, 0x78, 0x70, 0xa7, 0x56, 0xc9, 0xf5, 0x95, 0xad, 0x2e, 0x6d, 0xd7, + 0x56, 0xec, 0xfa, 0xda, 0x46, 0x25, 0x7f, 0xfd, 0x03, 0xa6, 0x85, 0x24, 0xcf, 0x55, 0x90, 0x22, + 0x14, 0x56, 0xd7, 0x56, 0x97, 0x2a, 0x67, 0xc8, 0x38, 0x8c, 0xad, 0x2f, 0xad, 0x2e, 0x2e, 0xaf, + 0xde, 0xe5, 0xdd, 0x5a, 0x5b, 0x5f, 0xb7, 0xd6, 0xb6, 0x96, 0x16, 0x2b, 0x39, 0xd6, 0x77, 0x8b, + 0x4b, 0xab, 0xac, 0x66, 0xf9, 0xeb, 0x26, 0x4f, 0xd1, 0xac, 0x65, 0x18, 0x65, 0xbd, 0xb5, 0xf4, + 0x95, 0x8d, 0xa5, 0xd5, 0xc6, 0xf2, 0xda, 0x6a, 0xe5, 0xcc, 0xf5, 0xcb, 0x29, 0x1c, 0x39, 0x12, + 0x8d, 0xc6, 0xbd, 0xca, 0x99, 0xeb, 0x5f, 0x87, 0xb2, 0xba, 0x09, 0x93, 0x0b, 0x30, 0xa3, 0xfe, + 0x5e, 0xa7, 0x1d, 0xd7, 0xeb, 0xec, 0x56, 0xce, 0xa4, 0x0b, 0xac, 0x5e, 0xa7, 0xc3, 0x0a, 0xb0, + 0xf1, 0x6a, 0xc1, 0x06, 0x0d, 0xda, 0x5e, 0x87, 0xed, 0xaf, 0x95, 0x5c, 0xbd, 0xf2, 0xfd, 0xbf, + 0x7c, 0xe9, 0xcc, 0xf7, 0x7f, 0xf0, 0x92, 0xf1, 0x67, 0x3f, 0x78, 0xc9, 0xf8, 0xaf, 0x3f, 0x78, + 0xc9, 0xd8, 0x19, 0xc5, 0x89, 0x7e, 0xeb, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x67, 0x6d, 0x41, + 0x27, 0x6f, 0xb5, 0x00, 0x00, } func (m *KeepAlive) Marshal() (dAtA []byte, err error) { @@ -19616,6 +19622,13 @@ func (m *WindowsDesktopServiceSpecV3) MarshalToSizedBuffer(dAtA []byte) (int, er i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.Hostname) > 0 { + i -= len(m.Hostname) + copy(dAtA[i:], m.Hostname) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Hostname))) + i-- + dAtA[i] = 0x1a + } if len(m.TeleportVersion) > 0 { i -= len(m.TeleportVersion) copy(dAtA[i:], m.TeleportVersion) @@ -24830,6 +24843,10 @@ func (m *WindowsDesktopServiceSpecV3) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + l = len(m.Hostname) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -55083,6 +55100,38 @@ func (m *WindowsDesktopServiceSpecV3) Unmarshal(dAtA []byte) error { } m.TeleportVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hostname", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hostname = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/api/types/types.proto b/api/types/types.proto index 058fa01a12616..c123c47bd1585 100644 --- a/api/types/types.proto +++ b/api/types/types.proto @@ -2776,10 +2776,15 @@ message WindowsDesktopServiceSpecV3 { string Addr = 1 [ (gogoproto.jsontag) = "addr" ]; // TeleportVersion is teleport binary version running this service. string TeleportVersion = 2 [ (gogoproto.jsontag) = "teleport_version" ]; + // Hostname is the desktop service hostname. + string Hostname = 3 [ (gogoproto.jsontag) = "hostname" ]; } +// WindowsDesktopFilter are filters to apply when searching for windows desktops. message WindowsDesktopFilter { + // HostID is the ID of the host the Windows Desktop Service proxying the desktop. string HostID = 1 [ (gogoproto.jsontag) = "host_id" ]; + // Name is the name of the desktop. string Name = 2 [ (gogoproto.jsontag) = "name" ]; } diff --git a/assets/aws/Makefile b/assets/aws/Makefile index d2c2c39a42fc6..2de8319b906c5 100644 --- a/assets/aws/Makefile +++ b/assets/aws/Makefile @@ -14,7 +14,7 @@ AWS_REGION ?= us-west-2 # This must be a _released_ version of Teleport, i.e. one which has binaries # available for download on https://gravitational.com/teleport/download # Unreleased versions will fail to build. -TELEPORT_VERSION ?= 8.3.1 +TELEPORT_VERSION ?= 9.0.1 # Teleport UID is the UID of a non-privileged 'teleport' user TELEPORT_UID ?= 1007 diff --git a/assets/backport/main.go b/assets/backport/main.go index f8bd1eca3b400..804bca8d3aabc 100644 --- a/assets/backport/main.go +++ b/assets/backport/main.go @@ -20,7 +20,6 @@ import ( "context" "flag" "fmt" - "io/ioutil" "log" "os" "path/filepath" @@ -100,7 +99,7 @@ func getGithubToken() (string, error) { return "", trace.Wrap(err) } ghConfigPath := filepath.Join(dirname, githubConfigPath) - yamlFile, err := ioutil.ReadFile(ghConfigPath) + yamlFile, err := os.ReadFile(ghConfigPath) if err != nil { return "", trace.Wrap(err) } diff --git a/assets/loadtest/k8s/Makefile b/assets/loadtest/k8s/Makefile index 915636397cc06..71f785b0b3260 100644 --- a/assets/loadtest/k8s/Makefile +++ b/assets/loadtest/k8s/Makefile @@ -115,7 +115,7 @@ install-teleport: install-auth install-proxy install-node install-iot-node .PHONY: delete-teleport delete-teleport: delete-tc delete-nodes delete-proxy delete-auth -# installs grafana and influxdb +# installs grafana, influxdb, and prometheus .PHONY: install-monitor install-monitor: kubectl create configmap grafana-config -n loadtest \ @@ -128,13 +128,16 @@ install-monitor: kubectl apply -f influxdb.yaml @make expand-yaml FILENAME=grafana kubectl apply -f grafana-gen.yaml + @make expand-yaml FILENAME=prometheus + kubectl apply -f prometheus-gen.yaml -# deletes grafana and influxdb deployments, services and configmaps +# deletes grafana, influxdb, and prometheus deployments, services and configmaps .PHONY: delete-monitor delete-monitor: kubectl delete -f influxdb.yaml --ignore-not-found kubectl delete -f grafana-gen.yaml --ignore-not-found kubectl delete configmap grafana-config -n loadtest --ignore-not-found + kubectl delete -f prometheus-gen.yaml --ignore-not-found # installs an etcd cluster .PHONY: install-etcd @@ -460,4 +463,4 @@ fetch-profiles: # output file will be named the same with a -gen suffix, i.e input = test then output will be test-gen.yaml .PHONY: expand-yaml expand-yaml: - @bash -c "set -a && source ./secrets/secrets.env && set +a && envsubst < $(FILENAME).yaml > $(FILENAME)-gen.yaml" \ No newline at end of file + @bash -c "set -a && source ./secrets/secrets.env && set +a && envsubst < $(FILENAME).yaml > $(FILENAME)-gen.yaml" diff --git a/assets/loadtest/k8s/auth-etcd.yaml b/assets/loadtest/k8s/auth-etcd.yaml index 15d648055ec69..049df023b70de 100644 --- a/assets/loadtest/k8s/auth-etcd.yaml +++ b/assets/loadtest/k8s/auth-etcd.yaml @@ -14,6 +14,9 @@ spec: metadata: labels: teleport-role: auth + backend: etcd + prometheus.io/scrape: "true" + prometheus.io/port: "3434" spec: volumes: - name: config diff --git a/assets/loadtest/k8s/auth-firestore.yaml b/assets/loadtest/k8s/auth-firestore.yaml index a3acd6d26cfdb..3b5c3c3affbd5 100644 --- a/assets/loadtest/k8s/auth-firestore.yaml +++ b/assets/loadtest/k8s/auth-firestore.yaml @@ -14,6 +14,9 @@ spec: metadata: labels: teleport-role: auth + backend: firestore + prometheus.io/scrape: "true" + prometheus.io/port: "3434" spec: volumes: - name: config diff --git a/assets/loadtest/k8s/prometheus.yaml b/assets/loadtest/k8s/prometheus.yaml new file mode 100644 index 0000000000000..3649f3f7f92d0 --- /dev/null +++ b/assets/loadtest/k8s/prometheus.yaml @@ -0,0 +1,291 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: prometheus-loadtest +--- +# Source: prometheus/templates/server/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + component: "server" + app: prometheus + release: prometheus + chart: prometheus-15.4.0 + heritage: Helm + name: prometheus-server + namespace: prometheus-loadtest + annotations: + {} +--- +# Source: prometheus/templates/server/cm.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + component: "server" + app: prometheus + release: prometheus + chart: prometheus-15.4.0 + heritage: Helm + name: prometheus-server + namespace: prometheus-loadtest +data: + alerting_rules.yml: | + {} + alerts: | + {} + prometheus.yml: | + global: + evaluation_interval: 1m + scrape_interval: 1m + scrape_timeout: 10s + remote_write: + - url: ${PROM_REMOTE_URL} + basic_auth: + username: ${PROM_USER} + password: ${PROM_PASSWORD} + rule_files: + - /etc/config/recording_rules.yml + - /etc/config/alerting_rules.yml + - /etc/config/rules + - /etc/config/alerts + scrape_configs: + - job_name: kubernetes-pods + kubernetes_sd_configs: + - role: pod + relabel_configs: + - action: keep + regex: true + source_labels: + - __meta_kubernetes_pod_annotation_prometheus_io_scrape + - action: drop + regex: true + source_labels: + - __meta_kubernetes_pod_annotation_prometheus_io_scrape_slow + - action: replace + regex: (https?) + source_labels: + - __meta_kubernetes_pod_annotation_prometheus_io_scheme + target_label: __scheme__ + - action: replace + regex: (.+) + source_labels: + - __meta_kubernetes_pod_annotation_prometheus_io_path + target_label: __metrics_path__ + - action: replace + regex: ([^:]+)(?::\d+)?;(\d+) + replacement: $1:$2 + source_labels: + - __address__ + - __meta_kubernetes_pod_annotation_prometheus_io_port + target_label: __address__ + - action: labelmap + regex: __meta_kubernetes_pod_annotation_prometheus_io_param_(.+) + replacement: __param_$1 + - action: labelmap + regex: __meta_kubernetes_pod_label_(.+) + - action: replace + source_labels: + - __meta_kubernetes_namespace + target_label: namespace + - action: replace + source_labels: + - __meta_kubernetes_pod_name + target_label: pod + - action: drop + regex: Pending|Succeeded|Failed|Completed + source_labels: + - __meta_kubernetes_pod_phase + recording_rules.yml: | + {} + rules: | + {} +--- +# Source: prometheus/templates/server/clusterrole.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + component: "server" + app: prometheus + release: prometheus + chart: prometheus-15.4.0 + heritage: Helm + name: prometheus-server +rules: + - apiGroups: + - "" + resources: + - nodes + - nodes/proxy + - nodes/metrics + - services + - endpoints + - pods + - ingresses + - configmaps + verbs: + - get + - list + - watch + - apiGroups: + - "extensions" + - "networking.k8s.io" + resources: + - ingresses/status + - ingresses + verbs: + - get + - list + - watch + - nonResourceURLs: + - "/metrics" + verbs: + - get +--- +# Source: prometheus/templates/server/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + component: "server" + app: prometheus + release: prometheus + chart: prometheus-15.4.0 + heritage: Helm + name: prometheus-server +subjects: + - kind: ServiceAccount + name: prometheus-server + namespace: prometheus-loadtest +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: prometheus-server +--- +# Source: prometheus/templates/server/service.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + component: "server" + app: prometheus + release: prometheus + chart: prometheus-15.4.0 + heritage: Helm + name: prometheus-server + namespace: prometheus-loadtest +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 9090 + selector: + component: "server" + app: prometheus + release: prometheus + sessionAffinity: None + type: "ClusterIP" +--- +# Source: prometheus/templates/server/deploy.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + component: "server" + app: prometheus + release: prometheus + chart: prometheus-15.4.0 + heritage: Helm + name: prometheus-server + namespace: prometheus-loadtest +spec: + selector: + matchLabels: + component: "server" + app: prometheus + release: prometheus + replicas: 1 + template: + metadata: + labels: + component: "server" + app: prometheus + release: prometheus + chart: prometheus-15.4.0 + heritage: Helm + spec: + enableServiceLinks: true + serviceAccountName: prometheus-server + containers: + - name: prometheus-server-configmap-reload + image: "jimmidyson/configmap-reload:v0.5.0" + imagePullPolicy: "IfNotPresent" + args: + - --volume-dir=/etc/config + - --webhook-url=http://127.0.0.1:9090/-/reload + resources: + {} + volumeMounts: + - name: config-volume + mountPath: /etc/config + readOnly: true + + - name: prometheus-server + image: "quay.io/prometheus/prometheus:v2.31.1" + imagePullPolicy: "IfNotPresent" + args: + - --storage.tsdb.retention.time=15d + - --config.file=/etc/config/prometheus.yml + - --storage.tsdb.path=/data + - --web.console.libraries=/etc/prometheus/console_libraries + - --web.console.templates=/etc/prometheus/consoles + - --web.enable-lifecycle + ports: + - containerPort: 9090 + readinessProbe: + httpGet: + path: /-/ready + port: 9090 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 4 + failureThreshold: 3 + successThreshold: 1 + livenessProbe: + httpGet: + path: /-/healthy + port: 9090 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 15 + timeoutSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + resources: + {} + volumeMounts: + - name: config-volume + mountPath: /etc/config + - name: storage-volume + mountPath: /data + subPath: "" + hostNetwork: false + dnsPolicy: ClusterFirst + securityContext: + fsGroup: 65534 + runAsGroup: 65534 + runAsNonRoot: true + runAsUser: 65534 + terminationGracePeriodSeconds: 300 + volumes: + - name: config-volume + configMap: + name: prometheus-server + - name: storage-volume + emptyDir: + {} diff --git a/assets/loadtest/k8s/proxy.yaml b/assets/loadtest/k8s/proxy.yaml index 4ef2396e8cb64..4fd0dac33816e 100644 --- a/assets/loadtest/k8s/proxy.yaml +++ b/assets/loadtest/k8s/proxy.yaml @@ -14,6 +14,8 @@ spec: metadata: labels: teleport-role: proxy + prometheus.io/scrape: "true" + prometheus.io/port: "3434" spec: volumes: - name: config @@ -100,4 +102,4 @@ spec: targetPort: 3036 protocol: TCP selector: - teleport-role: proxy \ No newline at end of file + teleport-role: proxy diff --git a/assets/loadtest/k8s/secrets/Makefile b/assets/loadtest/k8s/secrets/Makefile index c0110552f208e..6151d28f801d0 100644 --- a/assets/loadtest/k8s/secrets/Makefile +++ b/assets/loadtest/k8s/secrets/Makefile @@ -23,6 +23,21 @@ env: exit 1; \ fi + @if [ -z ${PROM_REMOTE_URL} ]; then \ + echo "PROM_REMOTE_URL is not set, cannot apply cluster."; \ + exit 1; \ + fi + + @if [ -z ${PROM_USER} ]; then \ + echo "PROM_USER is not set, cannot apply cluster."; \ + exit 1; \ + fi + + @if [ -z ${PROM_PASSWORD} ]; then \ + echo "PROM_PASSWORD is not set, cannot apply cluster."; \ + exit 1; \ + fi + @echo PROXY_IP=$(shell make -C ../../network get-proxy-ip) > secrets.env @echo PROXY_HOST=${PROXY_HOST} >> secrets.env @echo GRAFANA_IP=$(shell make -C ../../network get-grafana-ip) >> secrets.env @@ -31,6 +46,9 @@ env: @echo PROXY_TOKEN=$(shell cat proxy-token) >> secrets.env @echo TC_TOKEN=$(shell cat tc-token) >> secrets.env @echo GCP_PROJECT=$(shell make -C ../../cluster get-project) >> secrets.env + @echo PROM_REMOTE_URL=${PROM_REMOTE_URL} >> secrets.env + @echo PROM_USER=${PROM_USER} >> secrets.env + @echo PROM_PASSWORD=${PROM_PASSWORD} >> secrets.env grafana-pass: openssl rand -base64 32 | tr -d '\n' > grafana-pass @@ -56,4 +74,4 @@ join-tokens: node-token proxy-token tc-token # removes everything .PHONY:clean clean: - rm -rf *-pass *-token *-auth *.env \ No newline at end of file + rm -rf *-pass *-token *-auth *.env diff --git a/build.assets/Dockerfile b/build.assets/Dockerfile index 8158548290341..171abbb1b11a9 100644 --- a/build.assets/Dockerfile +++ b/build.assets/Dockerfile @@ -114,6 +114,11 @@ RUN (curl -L https://github.com/golangci/golangci-lint/releases/download/v1.44.0 RUN (mkdir -p helm-tarball && curl -L https://get.helm.sh/helm-v3.5.2-$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C helm-tarball -xz && \ cp helm-tarball/$(go env GOOS)-$(go env GOARCH)/helm /bin/ && \ rm -r helm-tarball*) +RUN helm plugin install https://github.com/vbehar/helm3-unittest && \ + mkdir -p /home/ci/.local/share/helm && \ + cp -r /root/.local/share/helm/plugins /home/ci/.local/share/helm && \ + chown -R ci /home/ci/.local/share/helm && \ + HELM_PLUGINS=/home/ci/.local/share/helm/plugins helm plugin list # Install bats. RUN (curl -L https://github.com/bats-core/bats-core/archive/v1.2.1.tar.gz | tar -xz && \ diff --git a/build.assets/Makefile b/build.assets/Makefile index 587054299b88c..b6f1659ef0f50 100644 --- a/build.assets/Makefile +++ b/build.assets/Makefile @@ -247,6 +247,16 @@ test-sh: buildbox docker run $(DOCKERFLAGS) $(NOROOT) -t $(BUILDBOX) \ /bin/bash -c "make -C $(SRCDIR) BATSFLAGS=$(BATSFLAGS) test-sh" +.PHONY:test-helm +test-helm: buildbox + docker run $(DOCKERFLAGS) $(NOROOT) -t $(BUILDBOX) \ + /bin/bash -c "make -C $(SRCDIR) test-helm" + +.PHONY:test-helm-update-snapshots +test-helm-update-snapshots: + docker run $(DOCKERFLAGS) $(NOROOT) -t $(BUILDBOX) \ + /bin/bash -c "make -C $(SRCDIR) test-helm-update-snapshots" + .PHONY:integration integration: buildbox docker run \ diff --git a/build.assets/build-package.sh b/build.assets/build-package.sh index 17e1bfbc52eb6..22d90fd713d09 100755 --- a/build.assets/build-package.sh +++ b/build.assets/build-package.sh @@ -232,7 +232,7 @@ if [[ "${PACKAGE_TYPE}" == "pkg" ]]; then BUNDLE_ID="com.gravitational.teleport.tsh" PKG_FILENAME="tsh-${TELEPORT_VERSION}.${PACKAGE_TYPE}" else - FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport" + FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport ${TAR_PATH}/tbot" BUNDLE_ID="com.gravitational.teleport" if [[ "${TELEPORT_TYPE}" == "ent" ]]; then PKG_FILENAME="teleport-ent-${TELEPORT_VERSION}.${PACKAGE_TYPE}" @@ -241,8 +241,8 @@ if [[ "${PACKAGE_TYPE}" == "pkg" ]]; then fi fi else - FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport ${TAR_PATH}/examples/systemd/teleport.service" - LINUX_BINARY_FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport" + FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport ${TAR_PATH}/tbot ${TAR_PATH}/examples/systemd/teleport.service" + LINUX_BINARY_FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/tbot ${TAR_PATH}/teleport" LINUX_SYSTEMD_FILE_LIST="${TAR_PATH}/examples/systemd/teleport.service" EXTRA_DOCKER_OPTIONS="" RPM_SIGN_STANZA="" diff --git a/build.assets/gomod/update-api-import-path/main.go b/build.assets/gomod/update-api-import-path/main.go index f89054b2976bc..0163e9c66ceaf 100644 --- a/build.assets/gomod/update-api-import-path/main.go +++ b/build.assets/gomod/update-api-import-path/main.go @@ -52,10 +52,17 @@ func main() { buildFlags = os.Args[1:] } - // the api module import path should only be updated on releases - newVersion := semver.New(api.Version) - if newVersion.PreRelease != "" { - exitWithMessage("the current API version (%v) is not a release, continue without updating", newVersion) + newVersion, err := semver.NewVersion(api.Version) + if err != nil { + exitWithError(trace.Wrap(err, "invalid semver version"), nil) + } + + // check the version's prelease section, and exit if it is a + // non beta/alpha pre release. + switch newVersion.PreRelease.Slice()[0] { + case "", "alpha", "beta": + default: + exitWithMessage("the current API version (%v) is not a release or alpha/beta pre-release, continue without updating", newVersion) } // get the current api module import path diff --git a/build.assets/install b/build.assets/install index cc3bed422ccdb..dc6ff9d0cd39f 100755 --- a/build.assets/install +++ b/build.assets/install @@ -13,7 +13,7 @@ VARDIR=/var/lib/teleport [ ! $(id -u) != "0" ] || { echo "ERROR: You must be root"; exit 1; } cd $(dirname $0) mkdir -p $VARDIR $BINDIR -cp -f teleport tctl tsh $BINDIR/ || exit 1 +cp -f teleport tctl tsh tbot $BINDIR/ || exit 1 # # What operating system is the user running? diff --git a/build.assets/tooling/cmd/check/main.go b/build.assets/tooling/cmd/check/main.go index 16fb6dca2f6af..ac1b8fe3172b6 100644 --- a/build.assets/tooling/cmd/check/main.go +++ b/build.assets/tooling/cmd/check/main.go @@ -90,6 +90,12 @@ func checkLatest(ctx context.Context, tag string, gh github.GitHub) error { if r.GetDraft() { continue } + // Because pre-releases are not published to apt, we do not want to + // consider them when making apt publishing decisions. + // see: https://github.com/gravitational/teleport/issues/10800 + if semver.Prerelease(r.GetTagName()) != "" { + continue + } tags = append(tags, r.GetTagName()) } diff --git a/build.assets/tooling/cmd/check/main_test.go b/build.assets/tooling/cmd/check/main_test.go index 4c2ab57368c7f..3ca92aab1b9b2 100644 --- a/build.assets/tooling/cmd/check/main_test.go +++ b/build.assets/tooling/cmd/check/main_test.go @@ -105,6 +105,15 @@ func TestCheckLatest(t *testing.T) { }, wantErr: require.NoError, }, + { // see https://github.com/gravitational/teleport/issues/10800 + desc: "pass-pre-release", + tag: "v8.3.3", + releases: []string{ + "v9.0.0-beta.1", + "v8.3.2", + }, + wantErr: require.NoError, + }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { diff --git a/constants.go b/constants.go index 13bf738f230b4..1ab18552fc5ab 100644 --- a/constants.go +++ b/constants.go @@ -631,6 +631,10 @@ const ( // EnvSSHSessionInvited is an environment variable listning people invited to a session. EnvSSHSessionInvited = "TELEPORT_SESSION_JOIN_MODE" + + // EnvSSHSessionDisplayParticipantRequirements is set to true or false to indicate if participant + // requirement information should be printed. + EnvSSHSessionDisplayParticipantRequirements = "TELEPORT_SESSION_PARTICIPANT_REQUIREMENTS" ) const ( diff --git a/docker/teleport-ent-quickstart.yml b/docker/teleport-ent-quickstart.yml index b68f11aa837da..f6b25dde811d7 100644 --- a/docker/teleport-ent-quickstart.yml +++ b/docker/teleport-ent-quickstart.yml @@ -3,7 +3,7 @@ services: # The configure container starts, generates a config, writes it to # /etc/teleport/teleport.yaml and then immediately exits. configure: - image: quay.io/gravitational/teleport-ent:8 + image: quay.io/gravitational/teleport-ent:9 container_name: teleport-configure entrypoint: /bin/sh hostname: localhost @@ -14,7 +14,7 @@ services: # This container depends on the config written by the configure container above, so it # sleeps for a second on startup to allow the configure container to run first. teleport: - image: quay.io/gravitational/teleport-ent:8 + image: quay.io/gravitational/teleport-ent:9 container_name: teleport entrypoint: /bin/sh hostname: localhost diff --git a/docker/teleport-lab.yml b/docker/teleport-lab.yml index 46655bd0304ed..3298d11034d5e 100644 --- a/docker/teleport-lab.yml +++ b/docker/teleport-lab.yml @@ -3,7 +3,7 @@ services: # This container depends on the config written by the configure container above, so it # sleeps for a second on startup to allow the configure container to run first. teleport: - image: quay.io/gravitational/teleport-lab:8 + image: quay.io/gravitational/teleport-lab:9 container_name: teleport entrypoint: /bin/sh hostname: luna.teleport @@ -24,7 +24,7 @@ services: # The bootstrap container generates certificates and then immediately exits. bootstrap: - image: quay.io/gravitational/teleport-lab:8 + image: quay.io/gravitational/teleport-lab:9 container_name: teleport-bootstrap entrypoint: /bin/sh command: -c "/etc/teleport.d/scripts/generate-certs.sh" @@ -41,7 +41,7 @@ services: # openssh is a demo of openssh node # openssh: - image: quay.io/gravitational/teleport-lab:8 + image: quay.io/gravitational/teleport-lab:9 container_name: openssh hostname: mars.openssh.teleport entrypoint: /bin/sh @@ -60,7 +60,7 @@ services: # term is a container with a terminal to try things out # term: - image: quay.io/gravitational/teleport-lab:8 + image: quay.io/gravitational/teleport-lab:9 hostname: term container_name: term entrypoint: /bin/sh diff --git a/docker/teleport-quickstart.yml b/docker/teleport-quickstart.yml index a78626201ac0e..a8e1a7a7075d7 100644 --- a/docker/teleport-quickstart.yml +++ b/docker/teleport-quickstart.yml @@ -3,7 +3,7 @@ services: # The configure container starts, generates a config, writes it to # /etc/teleport/teleport.yaml and then immediately exits. configure: - image: quay.io/gravitational/teleport:8 + image: quay.io/gravitational/teleport:9 container_name: teleport-configure entrypoint: /bin/sh hostname: localhost @@ -14,7 +14,7 @@ services: # This container depends on the config written by the configure container above, so it # sleeps for a second on startup to allow the configure container to run first. teleport: - image: quay.io/gravitational/teleport:8 + image: quay.io/gravitational/teleport:9 container_name: teleport entrypoint: /bin/sh hostname: localhost diff --git a/docs/config.json b/docs/config.json index d57ae33b2a23d..3c409cf64aedd 100644 --- a/docs/config.json +++ b/docs/config.json @@ -278,10 +278,6 @@ "title": "Guides", "slug": "/server-access/guides/", "entries": [ - { - "title": "Ansible", - "slug": "/server-access/guides/ansible/" - }, { "title": "Using Teleport with PAM", "slug": "/server-access/guides/ssh-pam/" @@ -348,10 +344,6 @@ "title": "Federation", "slug": "/kubernetes-access/guides/federation/" }, - { - "title": "Migration", - "slug": "/kubernetes-access/guides/migration/" - }, { "title": "Standalone", "slug": "/kubernetes-access/guides/standalone-teleport/" @@ -386,7 +378,17 @@ }, { "title": "Helm Chart Reference", - "slug": "/kubernetes-access/helm/reference/" + "slug": "/kubernetes-access/helm/reference/", + "entries": [ + { + "title": "teleport-cluster", + "slug": "/kubernetes-access/helm/reference/teleport-cluster/" + }, + { + "title": "teleport-kube-agent", + "slug": "/kubernetes-access/helm/reference/teleport-kube-agent/" + } + ] }, { "title": "Access Controls", @@ -450,6 +452,10 @@ "title": "Self-Hosted CockroachDB", "slug": "/database-access/guides/cockroachdb-self-hosted/" }, + { + "title": "Self-Hosted Redis", + "slug": "/database-access/guides/redis/" + }, { "title": "SQL Server (Preview)", "slug": "/database-access/guides/sql-server-ad/" @@ -517,8 +523,34 @@ "slug": "/desktop-access/getting-started/" }, { - "title": "Configuration", - "slug": "/desktop-access/reference/configuration/" + "title": "Access Controls", + "slug": "/desktop-access/rbac/" + }, + { + "title": "Reference", + "slug": "/desktop-access/reference/", + "entries": [ + { + "title": "Configuration", + "slug": "/desktop-access/reference/configuration/" + }, + { + "title": "Audit Events", + "slug": "/desktop-access/reference/audit/" + }, + { + "title": "Clipboard Sharing", + "slug": "/desktop-access/reference/clipboard/" + }, + { + "title": "Session Recording", + "slug": "/desktop-access/reference/sessions/" + }, + { + "title": "CLI", + "slug": "/desktop-access/reference/cli/" + } + ] }, { "title": "Troubleshooting", @@ -526,6 +558,44 @@ } ] }, + { + "icon": "wand", + "title": "Machine ID", + "entries": [ + { + "title": "Introduction", + "slug": "/machine-id/introduction/" + }, + { + "title": "Getting Started", + "slug": "/machine-id/getting-started/" + }, + { + "title": "Reference", + "slug": "/machine-id/reference/", + "entries": [ + { + "title": "Configuration", + "slug": "/machine-id/reference/configuration/" + }, + { + "title": "CLI", + "slug": "/machine-id/reference/cli/" + } + ] + }, + { + "title": "Guides", + "slug": "/machine-id/guides/", + "entries": [ + { + "title": "Machine ID with Ansible", + "slug": "/machine-id/guides/ansible/" + } + ] + } + ] + }, { "icon": "lock", "title": "Access Controls", @@ -1022,6 +1092,11 @@ "source": "/docs/best-practices/", "destination": "/contributing/documentation/", "permanent": true + }, + { + "source": "/kubernetes-access/guides/migration/", + "destination": "/kubernetes-access/introduction/", + "permanent": true } ] -} \ No newline at end of file +} diff --git a/docs/img/database-access/guides/redis/redisinsight-add-config.png b/docs/img/database-access/guides/redis/redisinsight-add-config.png new file mode 100644 index 0000000000000000000000000000000000000000..da0298b6c6f02c69fd774aaaf84ec64cce2bcda5 GIT binary patch literal 228417 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 z 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

f6C(*V~Az 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ǯiW!!6N+b|7);_=YEM<%kY(P=|@co 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/img/database-access/guides/redis/redisinsight-connected.png b/docs/img/database-access/guides/redis/redisinsight-connected.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f54ce2f1d26a7363bc62588c299c920470e86e GIT binary patch literal 181286 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 h86R^7_Gt}^4b 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 z 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 vtCHs=4vIo$oVOk(96m4oU1QM?VcIrcC*f# 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 GRluk)C$IT0lo;TH7Wz3q>hwR|h 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 Ypl$Vw{iu6A1pGRF)b&WkVV}$Y2X#Zw6#xJL literal 0 HcmV?d00001 diff --git a/docs/img/database-access/guides/redis/redisinsight-startup.png b/docs/img/database-access/guides/redis/redisinsight-startup.png new file mode 100644 index 0000000000000000000000000000000000000000..3102842af9d8e7d9d9dfeae8f133d9914a08e8d6 GIT binary patch literal 453447 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 gi^OiZ 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

FHbMw^XYXIk0>Et)9=V2xwr!5n1!nHqAjw>f`F7 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 !bHOi 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%0A1;2O0VGz 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

n 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
    @@ -40,6 +49,20 @@ Teleport certificates. https://developers.yubico.com/WebAuthn/WebAuthn_Browser_Support/) (if using SSH from the Teleport Web UI) + +Teleport FIPS builds disable local users. To configure WebAuthn in order to use +per-session MFA with FIPS builds, provide the following in your `teleport.yaml`: + +```yaml +teleport: + auth_service: + local_auth: false + second_factor: optional + webauthn: + rp_id: teleport.example.com +``` + + ## Configuration Per-session MFA can be enforced cluster-wide or only for some specific roles. @@ -75,8 +98,8 @@ spec: ... ``` -Role-specific enforcement only applies when accessing SSH nodes or Kubernetes -clusters matching that role's `allow` section. +Role-specific enforcement only applies when accessing resources matching a +role's `allow` section. #### Roles example @@ -156,7 +179,6 @@ $ tsh ssh prod3.example.com If per-session MFA was enabled cluster-wide, Jerry would be prompted for MFA even when logging into `dev1.example.com`. - ## Database access MFA Database access supports per-connection MFA. When Jerry connects to the database @@ -195,3 +217,5 @@ Current limitations for this feature are: If you enable per-session MFA checks cluster-wide, you will not be able to use Application access. We're working on integrating per-session MFA checks for these clients. +- For Desktop Access, only WebAuthn devices are supported. Teleport does not + support U2F devices for Desktop Access MFA. diff --git a/docs/pages/access-controls/guides/u2f.mdx b/docs/pages/access-controls/guides/u2f.mdx index 4412c796fd36a..243f4a6e17602 100644 --- a/docs/pages/access-controls/guides/u2f.mdx +++ b/docs/pages/access-controls/guides/u2f.mdx @@ -128,7 +128,7 @@ $ tsh login --proxy=example.com U2F for logging into Teleport is only required for [local -users](../../setup/reference/authentication.mdx#local). SSO users should configure +users](../../setup/reference/authentication.mdx#local-no-authentication-connector). SSO users should configure multi-factor authentication in their SSO provider. diff --git a/docs/pages/access-controls/guides/webauthn.mdx b/docs/pages/access-controls/guides/webauthn.mdx index 014b15e05a362..01bf5ed02f2b0 100644 --- a/docs/pages/access-controls/guides/webauthn.mdx +++ b/docs/pages/access-controls/guides/webauthn.mdx @@ -142,7 +142,7 @@ $ tsh login --proxy=example.com WebAuthn for logging into Teleport is only required for [local users]( - ../../setup/reference/authentication.mdx#local). SSO users should configure + ../../setup/reference/authentication.mdx#local-no-authentication-connector). SSO users should configure multi-factor authentication in their SSO provider. diff --git a/docs/pages/access-controls/reference.mdx b/docs/pages/access-controls/reference.mdx index b8b00518c920a..e45a1c4b63ad7 100644 --- a/docs/pages/access-controls/reference.mdx +++ b/docs/pages/access-controls/reference.mdx @@ -243,12 +243,12 @@ that are more appropriately scoped. ### Role versions -There are currently two supported role versions: `v3` and `v5`. `v5` roles are +There are currently three supported role versions: `v3`, `v4` and `v5`. `v4` and `v5` roles are completely backwards-compatible with `v3`, the only difference lies in the default allow labels which will be applied to the role if they are not -explicitly set. +explicitly set. Additionally, `v5` is required to use [Moderated Sessions](./guides/moderated-sessions.mdx). -Label | `v3` Default | `v5` Default +Label | `v3` Default | `v4` and `v5` Default ------------------ | -------------- | --------------- `node_labels` | `[{"*": "*"}]` if the role has any logins, else `[]` | `[]` `app_labels` | `[{"*": "*"}]` | `[]` @@ -435,3 +435,17 @@ spec: Refer to the [Second Factor - U2F guide](./guides/u2f.mdx) if you have a cluster using the legacy U2F support. + +## Filter fields + +Here is an explanation of the fields used in the `where` and `filter` conditions within this guide. + +| Field | Description | +|----------------------------|-----------------------------------------------------| +| `user.roles` | The list of roles assigned to a user | +| `session.participants` | The list of participants from a session recording | +| `ssh_session.participants` | The list of participants from an SSH session | +| `user.metadata.name` | The user's name | + +Check out our [predicate language](../setup/reference/predicate-language.mdx#scoping-allowdeny-rules-in-role-resources) +guide for a more in depth explanation of the language. diff --git a/docs/pages/api/getting-started.mdx b/docs/pages/api/getting-started.mdx index 57fdeed48436b..9ba9a4d7a162a 100644 --- a/docs/pages/api/getting-started.mdx +++ b/docs/pages/api/getting-started.mdx @@ -16,7 +16,7 @@ Here are the steps we'll walkthrough: ## Prerequisites - Install [Go](https://golang.org/doc/install) (=teleport.golang=)+ and Go development environment. -- Set up Teleport through the [Getting Started Guide](../getting-started.mdx) +- Set up Teleport through one of our [getting started guides](../getting-started.mdx). You can also begin a [free trial](https://goteleport.com/signup/) of Teleport Cloud. ## Step 1/3. Create a user @@ -29,8 +29,8 @@ Here are the steps we'll walkthrough: Create a user `api-admin` with the built-in role `editor`: ```code -# Run this directly on your auth server -# Add user and login via web proxy +# Run this directly on your Auth Server unless you are using Teleport Cloud +# Add a user and login via the Proxy Service $ sudo tctl users add api-admin --roles=editor ``` @@ -56,10 +56,9 @@ $ go mod init client-demo $ go get github.com/gravitational/teleport/api/client ``` -Create a file `main.go` with the following command, modifying the `Addrs` strings as needed: +Create a file called `main.go`, modifying the `Addrs` strings as needed: -```bash -cat > main.go << 'EOF' +```go package main import ( @@ -74,6 +73,7 @@ func main() { clt, err := client.New(ctx, client.Config{ Addrs: []string{ + // Teleport Cloud customers should use .teleport.sh "tele.example.com:443", "tele.example.com:3025", "tele.example.com:3024", @@ -101,7 +101,7 @@ func main() { EOF ``` -Now you can run the program and connect the client to the Teleport Auth server to fetch the server version. +Now you can run the program and connect the client to the Teleport Auth Server to fetch the server version. ```code $ go run main.go diff --git a/docs/pages/application-access/controls.mdx b/docs/pages/application-access/controls.mdx index b94f9ab26ff2c..5691249eef7f9 100644 --- a/docs/pages/application-access/controls.mdx +++ b/docs/pages/application-access/controls.mdx @@ -79,6 +79,6 @@ allow: - [OIDC](../enterprise/sso/oidc.mdx) - [ADFS](../enterprise/sso/adfs.mdx) - [Azure AD](../enterprise/sso/azuread.mdx) - - [GSuite](../enterprise/sso/azuread.mdx) + - [Google Workspace](../enterprise/sso/google-workspace.mdx) - [Onelogin](../enterprise/sso/one-login.mdx) - [Okta](../enterprise/sso/okta.mdx) diff --git a/docs/pages/application-access/getting-started.mdx b/docs/pages/application-access/getting-started.mdx index 9b107f121a734..64f5f84ab118d 100644 --- a/docs/pages/application-access/getting-started.mdx +++ b/docs/pages/application-access/getting-started.mdx @@ -9,13 +9,24 @@ videoBanner: 5Uwhp3IQMHY Let's connect to Grafana using Teleport Application Access in three steps: - Launch Grafana in a Docker container. -- Install Teleport and configure it to proxy Grafana. +- Install the Teleport Application Service on a node and configure it to proxy Grafana. - Access Grafana through Teleport. ## Prerequisites -- We will use Docker to launch Grafana in a container. Alternatively, if you have another web application you'd like to protect with App Access, you can use that instead. -- We will assume your Teleport cluster is accessible at `teleport.example.com` and `*.teleport.example.com`. Configured DNS records are required to automatically fetch a [Let's Encrypt](https://letsencrypt.org) certificate. +- The Teleport Auth Service and Proxy Service, deployed on your own infrastructure or via Teleport Cloud. +- A Docker installation, which we will use to launch Grafana in a container. Alternatively, if you have another web application you'd like to protect with Application Access, you can use that instead. +- A host where you will run the Teleport Application Service. + + +If you have not yet deployed the Auth Service and Proxy Service, you should follow one of our [getting started guides](../getting-started.mdx). + + +We will assume your Teleport cluster is accessible at `teleport.example.com` and `*.teleport.example.com`. You can substitute the address of your Teleport Proxy Service. (For Teleport Cloud customers, this will be similar to `mytenant.teleport.sh`.) + + +(!docs/pages/includes/dns-app-access.mdx!) + ## Step 1/3. Start Grafana @@ -23,51 +34,54 @@ We've picked Grafana for this tutorial since it's very easy to run with zero configuration required. If you have another web application you'd like to expose, skip over to **Step 2**. -Grafana can be launched in a [Docker container](https://grafana.com/docs/grafana/latest/installation/docker/) -with a single command: +Grafana can be launched in a Docker container with a single command: ```code $ docker run -d -p 3000:3000 grafana/grafana ``` ## Step 2/3. Install and configure Teleport +(!docs/pages/includes/permission-warning.mdx!) -Download the latest version of Teleport for your platform from our +On your Application Service host, download the latest version of Teleport for +your platform from our [downloads page](https://goteleport.com/teleport/download). -Teleport requires a valid TLS certificate to operate and can fetch one automatically -using Let's Encrypt [ACME](https://letsencrypt.org/how-it-works/) protocol. - -We will assume that you have configured DNS records for `teleport.example.com` -and `*.teleport.example.com` to point to the Teleport node. - -(!docs/pages/includes/permission-warning.mdx!) +### Generate a token -Let's generate a Teleport config with ACME enabled: +A join token is required to authorize a Teleport Application Service agent to +join the cluster. Generate a short-lived join token and save it, for example, +in `/tmp/token` on your Teleport Application Service host: ```code -$ sudo teleport configure --cluster-name=teleport.example.com --acme --acme-email=alice@example.com -o file +$ tctl tokens add \ + --type=app \ + --app-name=grafana \ + --app-uri=http://localhost:3000 ``` - - Teleport uses [TLS-ALPN-01](https://letsencrypt.org/docs/challenge-types/#tls-alpn-01) - ACME challenge to validate certificate requests which only works on port `443`. Make sure your Teleport proxy is accessible on port `443` when using ACME for certificate management. - +### Start Teleport + +On the host where you will run the Teleport Application Service, download the latest version of Teleport for your platform from our +[downloads page](https://goteleport.com/teleport/download). Now start Teleport and point it to the application endpoint: ```code -$ sudo teleport start \ - --roles=proxy,auth,app \ - --app-name=grafana \ - --app-uri=http://localhost:3000 +$ sudo teleport app start \ + --name=grafana \ + --token=/tmp/token \ + --uri=http://localhost:3000 \ + --auth-server=https://teleport.example.com:3080 ``` -Make sure to update `--app-name` and `--app-uri` accordingly if you're using -your own web application. +Change `https://teleport.example.com:3080` to the address and port of your Teleport Proxy Server. If you are a Teleport Cloud cluster, use your tenant's subdomain, e.g., `mytenant.teleport.sh`. + +Make sure to update `--app-name` and `--app-uri` accordingly if you're using your own web application. + +The `--token` flag points to the file on the Application Service host where we stored the token that we generated earlier. + +### Create a user Next, let's create a user to access the application we've just connected. Teleport has a built-in role called `access` that allows users to access cluster resources. Create a local user assigned this role: @@ -75,19 +89,23 @@ Next, let's create a user to access the application we've just connected. Telepo $ tctl users add --roles=access alice ``` -The command will output a signup link. Use it to choose a password and set up a second factor. After that, it will take you to the Teleport web UI. +The command will output a signup link. Use it to choose a password and set up a second factor. After that, it will take you to the Teleport Web UI. ## Step 3/3. Access the application There are a couple of ways to access the proxied application. -Every application is assigned a public address which you use to navigate to +Every application is assigned a public address that you can use to navigate to the application directly. In our sample Grafana application we have provided a public address with the `--app-public-addr` flag, so go to `https://grafana.teleport.example.com` -(replace with your app public address) to access the app. If you're not logged into Teleport, +to access the app. + +Replace `grafana` with the value of the `--app-name` flag you used when starting the Teleport Application Service and `teleport.example.com` with the address of your Proxy Service. + +If you're not logged into Teleport, you will need to authenticate before the application will show. -Alternatively, log into the Teleport Web Interface at `https://teleport.example.com` (replace with your proxy public address). All available applications are displayed on the Applications tab. Click on the Grafana application tile to access it. +Alternatively, log in to the Teleport Web Interface at `https://teleport.example.com` (replace with your Proxy Service's public address). All available applications are displayed on the Applications tab. Click on the Grafana application tile to access it. ## Next steps @@ -97,3 +115,4 @@ Dive deeper into the topics relevant to your Application Access use-case: - Learn about integrating with [JWT tokens](./guides/jwt.mdx) for auth. - Learn how to use Application Access with [RESTful APIs](./guides/api-access.mdx). - See full configuration and CLI [reference](./reference.mdx). +- Read about how Let's Encrypt uses the [ACME protocol](https://letsencrypt.org/how-it-works/). \ No newline at end of file diff --git a/docs/pages/application-access/guides/api-access.mdx b/docs/pages/application-access/guides/api-access.mdx index 489030739557b..7d45468932a50 100644 --- a/docs/pages/application-access/guides/api-access.mdx +++ b/docs/pages/application-access/guides/api-access.mdx @@ -10,6 +10,7 @@ tools like [curl](https://man7.org/linux/man-pages/man1/curl.1.html) or Postman. ## Prerequisites +You will need a running Teleport cluster, either self hosted or in Teleport Cloud. We'll assume that you followed the [Getting Started](../getting-started.mdx) guide or the general [App Access Usage](./connecting-apps.mdx) guide to connect the web application providing an API to Teleport. diff --git a/docs/pages/application-access/guides/aws-console.mdx b/docs/pages/application-access/guides/aws-console.mdx index 8ffa0ee598a93..a5f81b35aad81 100644 --- a/docs/pages/application-access/guides/aws-console.mdx +++ b/docs/pages/application-access/guides/aws-console.mdx @@ -6,19 +6,21 @@ videoBanner: GVcy_rffxQw # AWS Management Console Access -Teleport can automatically sign your users into AWS management console with +Teleport can automatically sign your users into the AWS management console with appropriate IAM roles. This guide will explain how to: -- Connect your AWS account(-s) to Teleport. -- Setup example AWS IAM Read Only and Power User roles. +- Connect your AWS account(s) to Teleport. +- Set up example AWS IAM Read Only and Power User roles. - Use Teleport's role-based access control with AWS IAM roles. - View Teleport users' AWS console activity in CloudTrail. - Access the AWS Command Line Interface (CLI) through Teleport. + ## Prerequisites -- Teleport with Application Access. Follow [Getting Started](../getting-started.mdx) +- A running Teleport cluster, either self hosted or in Teleport Cloud. +- A Teleport Node with Application Access enabled. Follow the [Getting Started](../getting-started.mdx) or [Connecting Apps](./connecting-apps.mdx) guides to get it running. - IAM permissions in the AWS account you want to connect. - AWS EC2 or other instance where you can assign a IAM Security Role for the Teleport Agent. @@ -34,7 +36,7 @@ Otherwise, you will receive "400 Bad Request" errors from AWS. ## Step 1. [Optional] Configure Read Only and Power User roles -AWS provides the `ReadOnlyAccess` and `PowerUserAccess` IAM policies that can be incorporated into roles. Skip this step if you already have the roles you want to provide access to. +AWS provides the `ReadOnlyAccess` and `PowerUserAccess` IAM policies that can be incorporated into roles. **Skip this step** if you already have the roles you want to provide access to. These policies may provide too much or not enough access for your intentions. Validate these meet your expectations if you plan on using them. @@ -67,16 +69,15 @@ Enter a role name and press create role. Follow the same steps and select `PowerUserAccess` IAM Policy to create a `ExamplePowerUser` role. -## Step 2. Update IAM roles trust relationships +## Step 2. Update IAM role trust relationships -This step is only required if you are allowing access from another account. The trust relationship will already exist for the same account. +This step is only required if you are allowing access from another account. The trust relationship will already exist for the same account. -Teleport uses AWS [Federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html) -service to generate sign-in URLs for users, which relies on the `AssumeRole` API -for getting temporary security credentials. As such, you would first need to -update your IAM roles' "Trusted entities" to include AWS account ID. +Teleport uses AWS [federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html) to generate sign-in URLs for users, which relies on the `AssumeRole` API +for getting temporary security credentials. +You will need to update your IAM roles' "Trusted entities" to include your AWS account ID. Go to the [Roles](https://console.aws.amazon.com/iamv2/home#/roles) list, pick a role and create the following trust policy for it by clicking on "Edit trust @@ -99,8 +100,9 @@ relationship" button on the "Trust relationships" tab: See [How to use trust policies with IAM roles](https://aws.amazon.com/blogs/security/how-to-use-trust-policies-with-iam-roles/) for more details. After saving the trust policy, the account will show as a -trusted entity: -From the EC2 dashboard select Actions -> Security -> Modify IAM Role +trusted entity. + +From the EC2 dashboard select Actions -> Security -> Modify IAM Role. ![AWS trusted entities](../../../img/application-access/aws-trusted-entities@2x.png) Do this for each IAM role your Teleport users will need to assume. @@ -136,7 +138,7 @@ is using. The next step is to give your Teleport users permissions to assume IAM roles. -You can do this by creating a role with `aws_role_arns` field listing all IAM +You can do this by creating a role with the `aws_role_arns` field listing all IAM role ARNs this particular role permits its users to assume: ```yaml @@ -232,8 +234,8 @@ an IAM role selector: ![IAM role selector](../../../img/application-access/iam-role-selector.png) -Click on the role you want to assume and you will get redirected to AWS -management console signed in with the selected role. +Click on the role you want to assume and you will get redirected to the AWS +management console, signed in with the selected role. In the console's top-right corner you should see that you're logged in through federated login and the name of your assumed IAM role: @@ -252,7 +254,7 @@ Note that your federated login session is marked with your Teleport username. To view CloudTrail events for your federated sessions, navigate to the CloudTrail [dashboard](https://console.aws.amazon.com/cloudtrail/home) and go to "Event history". -Each Teleport federated login session uses Teleport username as the federated +Each Teleport federated login session uses a Teleport username as the federated username which you can search for to get the events history: ![CloudTrail](../../../img/application-access/cloud-trail.png) @@ -270,7 +272,7 @@ Logged into AWS app awsconsole-test. Example AWS CLI command: $ tsh aws s3 ls ``` -The `--aws-role` flag allows to specify the AWS IAM role to assume when accessing AWS API. You can either +The `--aws-role` flag allows you to specify the AWS IAM role to assume when accessing AWS API. You can either provide a role name like `--aws-role ExamplePowerUser` or a full role ARN `arn:aws:iam::1234567890:role/ExamplePowerUser` Now you can use the `tsh aws` command like the native `aws` command-line tool: diff --git a/docs/pages/application-access/guides/connecting-apps.mdx b/docs/pages/application-access/guides/connecting-apps.mdx index 4468d6544d894..d3c74ae36b496 100644 --- a/docs/pages/application-access/guides/connecting-apps.mdx +++ b/docs/pages/application-access/guides/connecting-apps.mdx @@ -54,36 +54,22 @@ applications. When setting up Teleport, the minimum requirement is a certificate for the proxy and a wildcard certificate for its sub-domain. This is where everyone will log into Teleport. + +(!docs/pages/includes/dns-app-access.mdx!) + + In our example: - `teleport.example.com` will host the Access Plane. - `*.teleport.example.com` will host all of the applications e.g. `grafana.teleport.example.com`. -Teleport can obtain a certificate automatically from Let's Encrypt using -[ACME](https://letsencrypt.org/how-it-works/) protocol. - -Enable ACME in your proxy config: - -```yaml -proxy_service: - enabled: "yes" - web_listen_addr: "0.0.0.0:443" - public_addr: "teleport.example.com:443" - acme: - enabled: "yes" - email: alice@example.com -``` - - - Teleport uses [TLS-ALPN-01](https://letsencrypt.org/docs/challenge-types/#tls-alpn-01) - ACME challenge to validate certificate requests which only works on port `443`. Make sure your Teleport proxy is accessible on port `443` when using ACME for certificate management. - - -Alternatively, if you have obtained certificate/key pairs for your domain -(e.g. using [certbot](https://certbot.eff.org/)), they can be provided directly +You can either configure Teleport to obtain a TLS certificate via Let's Encrypt or use an existing certificate and private key (e.g. using [certbot](https://certbot.eff.org/)). + + +(!docs/pages/includes/acme.mdx!) + + +If you have obtained certificate/key pairs for your domain they can be provided directly to the proxy service: ```yaml @@ -97,6 +83,8 @@ proxy_service: - key_file: "/etc/letsencrypt/live/*.teleport.example.com/privkey.pem" cert_file: "/etc/letsencrypt/live/*.teleport.example.com/fullchain.pem" ``` + + ### Create a user diff --git a/docs/pages/cloud/faq.mdx b/docs/pages/cloud/faq.mdx index 327a5199c7172..fc28f895a16cc 100644 --- a/docs/pages/cloud/faq.mdx +++ b/docs/pages/cloud/faq.mdx @@ -29,7 +29,7 @@ Currently there is no way to provide your own bucket. ## How do I add nodes to Teleport Cloud? You can connect servers, kubernetes clusters, databases and applications -using [reverse tunnels](../setup/admin/adding-nodes.mdx#adding-a-node-located-behind-nat). +using [reverse tunnels](../setup/admin/adding-nodes.mdx). There is no need to open any ports on your infrastructure for inbound traffic. @@ -56,7 +56,7 @@ $ tctl tokens add --type=node ## Are dynamic node tokens available? After [connecting](#how-can-i-access-the-tctl-admin-tool) `tctl` to Teleport Cloud, users can generate -[dynamic tokens](../setup/admin/adding-nodes.mdx#short-lived-dynamic-tokens): +[dynamic tokens](../setup/admin/adding-nodes.mdx): ```code $ tctl nodes add --ttl=5m --roles=node,proxy --token=$(uuid) diff --git a/docs/pages/database-access/getting-started.mdx b/docs/pages/database-access/getting-started.mdx index 51e4f6ff3cc42..736f67050c1a5 100644 --- a/docs/pages/database-access/getting-started.mdx +++ b/docs/pages/database-access/getting-started.mdx @@ -14,7 +14,7 @@ Here's an overview of what we will do: 2. Download and install Teleport (=teleport.version=) and connect it to the Aurora database. 3. Connect to the Aurora database via Teleport. -## Step 1/3. Setup Aurora +## Step 1/3. Set up Aurora In order to allow Teleport connections to an Aurora instance, it needs to support IAM authentication. @@ -73,33 +73,28 @@ GRANT rds_iam TO alice; For more information about connecting to the PostgreSQL instance directly, see Amazon [documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ConnectToPostgreSQLInstance.html). -## Step 2/3. Setup Teleport +## Step 2/3. Set up Teleport Teleport Database Access is available starting from `6.0.0` release. Download the appropriate version of Teleport for your platform from our [downloads page](https://goteleport.com/teleport/download). + +### Configure TLS + Teleport requires a valid TLS certificate to operate and can fetch one automatically -using Let's Encrypt [ACME](https://letsencrypt.org/how-it-works/) protocol. +using Let's Encrypt. -We will assume that you have configured DNS records for `teleport.example.com` and -`*.teleport.example.com` to point to the node where you're launching Teleport. +(!docs/pages/includes/acme.mdx!) -Let's generate a Teleport config with ACME enabled: +We will assume that you have configured a DNS record for `teleport.example.com` to point to the node where you're launching Teleport. -```code -$ teleport configure --cluster-name=teleport.example.com --acme --acme-email=alice@example.com > /tmp/teleport.yaml -``` +
    +(!docs/pages/includes/dns-app-access.mdx!) +
    - - Teleport's ACME protocol integration currently requires web proxy to run on - port 443 so open /tmp/teleport.yaml and update `proxy_service.web_listen_addr` - and `proxy_service.public_addr` to use port 443 instead of the default 3080. - +### Start Teleport Now start Teleport and point it to your Aurora database instance. Make sure to update the database endpoint and region appropriately. @@ -118,9 +113,11 @@ $ sudo teleport start --config=/tmp/teleport.yaml \ title="AWS Credentials" > The node that connects to the database should have AWS credentials configured - with the policy from [step 1](#step-13-setup-aurora). + with the policy from [step 1](#step-13-set-up-aurora).
    +### Create a user and role + Create the role that will allow a user to connect to any database using any database account: diff --git a/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx b/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx index 45bd48fa4fd67..fd06d0620a521 100644 --- a/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx +++ b/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx @@ -36,7 +36,7 @@ This guide will help you to: (!docs/pages/includes/database-access/token.mdx!) - + Start the Teleport Database Service, pointing the `--auth-server` flag to the address of your Teleport Proxy Service: ```code @@ -142,7 +142,7 @@ Log into your Teleport cluster. Your CockroachDB cluster should appear in the list of available databases: - + ```code $ tsh login --proxy=teleport.example.com --user=alice $ tsh db ls diff --git a/docs/pages/database-access/guides/gui-clients.mdx b/docs/pages/database-access/guides/gui-clients.mdx index eae7b53cd1831..9e881f6b6bb51 100644 --- a/docs/pages/database-access/guides/gui-clients.mdx +++ b/docs/pages/database-access/guides/gui-clients.mdx @@ -192,3 +192,34 @@ keep the Password field empty: ![DataGrip connection options](../../../img/database-access/guides/sqlserver/datagrip-connection@2x.png) Click OK to connect. + +## Redis Insight + + + Teleport's Redis Insight integration only supports Redis standalone instances. + + +After opening Redis Insight click `ADD REDIS DATABASE`. + +![Redis Insight Startup Screen](../../../img/database-access/guides/redis/redisinsight-startup.png) + +Log in to your Redis instance with a Redis user first by using: + +`tsh db login --db-user=alice redis-db-name`. + +Click `Add Database Manually`. Use `127.0.0.1` as the `Host` and port printed by `tsh proxy db` as described [here](#get-connection-information). + +Provide your Redis username as `Username` and password as `Password`. + +![Redis Insight Configuration](../../../img/database-access/guides/redis/redisinsight-add-config.png) + +Next, check the `Use TLS` and `Verify TLS Certificates` boxes and copy the CA certificate returned by `tsh proxy db`. +Copy the private key and certificate to corresponding fields. + +Click `Add Redis Database`. + +![Redis Insight TLS Configuration](../../../img/database-access/guides/redis/redisinsight-tls-config.png) + +Congratulations! You have just connected to your Redis instance. + +![Redis Insight Connected](../../../img/database-access/guides/redis/redisinsight-connected.png) diff --git a/docs/pages/database-access/guides/mongodb-atlas.mdx b/docs/pages/database-access/guides/mongodb-atlas.mdx index 96f900cc33837..d674516142244 100644 --- a/docs/pages/database-access/guides/mongodb-atlas.mdx +++ b/docs/pages/database-access/guides/mongodb-atlas.mdx @@ -28,7 +28,7 @@ In this guide you will: (!docs/pages/includes/database-access/token.mdx!) - + Start the Teleport Database Service, pointing the `--auth-server` flag at the address of your Teleport Proxy Service: ```code @@ -74,7 +74,7 @@ If you're starting the Database Agent with a YAML configuration instead of CLI f the following config is equivalent to the `teleport db start` command shown earlier: - + ```yaml teleport: auth_token: "/tmp/token" @@ -193,7 +193,7 @@ certificate with `CN=alice` subject. Log into your Teleport cluster and see available databases: - + ```code $ tsh login --proxy=teleport.example.com --user=alice diff --git a/docs/pages/database-access/guides/mysql-self-hosted.mdx b/docs/pages/database-access/guides/mysql-self-hosted.mdx index d4a9788beb88b..63b4eda176abe 100644 --- a/docs/pages/database-access/guides/mysql-self-hosted.mdx +++ b/docs/pages/database-access/guides/mysql-self-hosted.mdx @@ -131,48 +131,17 @@ tunnel. ### Start Database Service with Config File -Below is an example of a database service configuration file that proxies -a single self-hosted MySQL database: - -```yaml -teleport: - # The data_dir should be a different location if running on the same - # machine as Teleport auth and proxy. - data_dir: /var/lib/teleport-db - nodename: teleport-db-instance - # Teleport invitation token used to join a cluster. - # can also be passed on start using --token flag - auth_token: /tmp/token - # Proxy address to connect to. Note that it has to be the proxy address - # because database service always connects to the cluster over reverse - # tunnel. - auth_servers: - - teleport.example.com:3080 -db_service: - enabled: "yes" - # This section contains definitions of all databases proxied by this - # service, can contain multiple items. - databases: - # Name of the database proxy instance, used to reference in CLI. - - name: "example" - # Free-form description of the database proxy instance. - description: "Example MySQL" - # Database protocol. - protocol: "mysql" - # Database address, MySQL/MariaDB server endpoint in this case. - # - # Note: this URI's hostname must match the host name specified via --host - # flag to tctl auth sign command. - uri: "mysql.example.com:3306" - # Labels to assign to the database, used in RBAC. - static_labels: - env: dev -auth_service: - enabled: "no" -ssh_service: - enabled: "no" -proxy_service: - enabled: "no" +Generate a configuration file at `/etc/teleport.yaml` for the Database Service: + +```code +$ teleport db configure create \ + -o file \ + --token=/tmp/token \ + --proxy=teleport.example.com:3080 \ + --name=test \ + --protocol=mysql \ + --uri=mysql.example.com:3306 \ + --labels=env=dev ``` - - Use this policy if your Teleport database agent runs as an IAM user (for - example, uses AWS credentials file). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "redshift:DescribeClusters", - "iam:GetUserPolicy", - "iam:PutUserPolicy", - "iam:DeleteUserPolicy" - ], - "Resource": "*" - } - ] - } - ``` - - - Use this policy if your Teleport database agent runs as an IAM role (for - example, on an EC2 instance with attached IAM role). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "redshift:DescribeClusters", - "iam:GetRolePolicy", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy" - ], - "Resource": "*" - } - ] - } - ``` - - - -### Create an IAM permission boundary for Teleport -Since Teleport will be managing its own IAM policies for access to Redshift -databases, you need to create a permission boundary to limit its effective -range of permissions. - -Create another managed policy that will serve as a permission boundary on the -same [Policies](https://console.aws.amazon.com/iamv2/home#/policies) page of -the AWS Management Console. - -In addition to the set of permissions you created above, the boundary should -also include `redshift:GetClusterCredentials`, which will grant your Teleport -agent the permission to generate temporary credentials to authenticate with -Redshift databases. - -Similar to the permission polices you created above, the exact set of required -permissions for the permission boundary depends on the IAM identity your -Teleport database agent will be using (IAM user or IAM role). - - - - Use this permission boundary if your Teleport database agent runs as an IAM - user (for example, uses AWS credentials file). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "redshift:DescribeClusters", - "redshift:GetClusterCredentials", - "iam:GetUserPolicy", - "iam:PutUserPolicy", - "iam:DeleteUserPolicy" - ], - "Resource": "*" - } - ] - } - ``` - - - Use this permission boundary if your Teleport database agent runs as an IAM - role (for example, on an EC2 instance with attached IAM role). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "redshift:DescribeClusters", - "redshift:GetClusterCredentials", - "iam:GetRolePolicy", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy" - ], - "Resource": "*" - } - ] - } - ``` - - - -### Attach the policy and boundary to an IAM identity -(!docs/pages/includes/database-access/attach-iam-policies.mdx!) - - - If you prefer to self-manage IAM for your Redshift databases, see [AWS - reference](../reference/aws.mdx) for details. - - -## Step 4/5. Start the database agent -(!docs/pages/includes/database-access/token.mdx!) - -Create the database agent configuration e.g. in `/etc/teleport.yaml`: - -```yaml -teleport: - data_dir: /var/lib/teleport - auth_token: /tmp/token - auth_servers: - - teleport.example.com:443 # Teleport proxy address to connect to -auth_service: - enabled: "no" -proxy_service: - enabled: "no" -db_service: - enabled: "yes" - aws: # Matchers for registering AWS-hosted databases. - - types: ["redshift"] - regions: ["us-west-1"] # AWS regions to fetch databases from - tags: # AWS database resource tags to match - "*": "*" -``` +(!docs/pages/includes/database-access/aws-bootstrap.mdx!) -Start the database agent: +## Step 5/6. Start the database agent ```code $ teleport start --config=/etc/teleport.yaml @@ -195,7 +64,7 @@ may not propagate immediately and can take a few minutes to come into effect. for more information. -## Step 5/5. Connect +## Step 6/6. Connect Once the database agent has started and joined the cluster, log in to see the registered databases. Replace `--proxy` with the address of your Teleport Proxy Service, diff --git a/docs/pages/database-access/guides/postgres-self-hosted.mdx b/docs/pages/database-access/guides/postgres-self-hosted.mdx index 162873d4190aa..e7c2a818ae1ca 100644 --- a/docs/pages/database-access/guides/postgres-self-hosted.mdx +++ b/docs/pages/database-access/guides/postgres-self-hosted.mdx @@ -111,43 +111,17 @@ tunnel. ### Start Database service with config file -Below is an example of a database service configuration file that proxies -a single self-hosted PostgreSQL database: - -```yaml -teleport: - data_dir: /var/lib/teleport-db - nodename: test - # Proxy address to connect to. Note that it has to be the proxy address - # because database service always connects to the cluster over reverse - # tunnel. - auth_servers: - - teleport.example.com:3080 -db_service: - enabled: "yes" - # This section contains definitions of all databases proxied by this - # service, can contain multiple items. - databases: - # Name of the database proxy instance, used to reference in CLI. - - name: "example" - # Free-form description of the database proxy instance. - description: "Example PostgreSQL" - # Database protocol. - protocol: "postgres" - # Database address, PostgreSQL server endpoint in this case. - # - # Note: this URI's hostname must match the host name specified via --host - # flag to tctl auth sign command. - uri: "postgres.example.com:5432" - # Labels to assign to the database, used in RBAC. - static_labels: - env: dev -auth_service: - enabled: "no" -ssh_service: - enabled: "no" -proxy_service: - enabled: "no" +Generate a configuration file at `/etc/teleport.yaml` for the Database Service: + +```code +$ teleport db configure create \ + -o file \ + --token=/tmp/token \ + --proxy=teleport.example.com:3080 \ + --name=test \ + --protocol=postgres \ + --uri=postgres.example.com:5432 \ + --labels=env=dev ``` diff --git a/docs/pages/database-access/guides/rds.mdx b/docs/pages/database-access/guides/rds.mdx index fba667601e2d2..8b64256228a8c 100644 --- a/docs/pages/database-access/guides/rds.mdx +++ b/docs/pages/database-access/guides/rds.mdx @@ -1,6 +1,7 @@ --- -title: Database Access with AWS RDS and Aurora for PostgreSQL and MySQL -description: How to configure Teleport Database Access with AWS RDS and Aurora for PostgreSQL and MySQL. +title: Database Access with AWS RDS and Aurora +h1: Database Access with AWS RDS and Aurora for PostgreSQL, MySQL and MariaDB +description: How to configure Teleport Database Access with AWS RDS and Aurora for PostgreSQL, MySQL and MariaDB. --- This guide will help you to: @@ -9,9 +10,10 @@ This guide will help you to: - Set up Teleport to access your RDS instances and Aurora clusters. - Connect to your databases through Teleport. - - Aurora Serverless does not support IAM authentication at the time of this - writing so it can't be used with Database Access. + + The following products are not compatible with Database Access as they don't support IAM authentication: + - Aurora Serverless. + - RDS MariaDB versions lower than 10.6. ## Prerequisites @@ -20,268 +22,42 @@ This guide will help you to: - AWS account with RDS and Aurora databases and permissions to create and attach IAM policies. -## Step 1/6. Install Teleport +## Step 1/7. Install Teleport (!docs/pages/includes/database-access/start-auth-proxy.mdx!) -## Step 2/6. Create Teleport user +## Step 2/7. Create a Teleport user (!docs/pages/includes/database-access/create-user.mdx!) -## Step 3/6. Configure IAM +## Step 3/7. Create a database agent configuration -### Create IAM policy for Teleport - -Teleport needs AWS IAM permissions to be able to: - -- Discover and register RDS instances and Aurora clusters. -- Configure IAM authentication for them. - -Go to the [Policies](https://console.aws.amazon.com/iamv2/home#/policies) page -and create a managed IAM policy for the database agent. - -The exact set of required permissions depends on whether you're connecting to -RDS instances or Aurora clusters (or both), as well as the IAM identity your -Teleport database agent will be using (user or role). - - - - Use this policy if you're connecting to RDS instances and your Teleport - database agent runs as IAM user (for example, uses AWS credentials file). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBInstances", - "rds:ModifyDBInstance", - "iam:GetUserPolicy", - "iam:PutUserPolicy", - "iam:DeleteUserPolicy" - ], - "Resource": "*" - } - ] - } - ``` - - - Use this policy if you're connecting to RDS instances and your Teleport - database agent runs as IAM role (for example, on an EC2 instance with - attached IAM role). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBInstances", - "rds:ModifyDBInstance", - "iam:GetRolePolicy", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy" - ], - "Resource": "*" - } - ] - } - ``` - - - Use this policy if you're connecting to Aurora clusters and your Teleport - database agent runs as IAM user (for example, uses AWS credentials file). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBClusters", - "rds:ModifyDBCluster", - "iam:GetUserPolicy", - "iam:PutUserPolicy", - "iam:DeleteUserPolicy" - ], - "Resource": "*" - } - ] - } - ``` - - - Use this policy if you're connecting to Aurora clusters and your Teleport - database agent runs as IAM role (for example, on an EC2 instance with - attached IAM role). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBClusters", - "rds:ModifyDBCluster", - "iam:GetRolePolicy", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy" - ], - "Resource": "*" - } - ] - } - ``` - - - -### Create IAM permission boundary for Teleport - -Since Teleport will be managing its own IAM policies for access to RDS and -Aurora databases, you need to create a permission boundary to limit its -effective range of permissions. +(!docs/pages/includes/database-access/token.mdx!) -Create another managed policy that will serve as a permission boundary on the -same [Policies](https://console.aws.amazon.com/iamv2/home#/policies) page. +Create the Database Service configuration: -The boundary should have the same set of permissions as the IAM policy you -created above, plus `rds-db:connect`. +```code +$ teleport db configure create \ + -o file \ + --proxy=teleport.example.com:3080 \ + --token=/tmp/token \ + --rds-discovery=us-west-1 +``` - - - Use this permission boundary policy if you're connecting to RDS instances and - your Teleport database agent runs as IAM user (for example, uses AWS - credentials file). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBInstances", - "rds:ModifyDBInstance", - "iam:GetUserPolicy", - "iam:PutUserPolicy", - "iam:DeleteUserPolicy", - "rds-db:connect" - ], - "Resource": "*" - } - ] - } - ``` - - - Use this permission boundary policy if you're connecting to RDS instances and - your Teleport database agent runs as IAM role (for example, on an EC2 instance - with attached IAM role). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBInstances", - "rds:ModifyDBInstance", - "iam:GetRolePolicy", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy", - "rds-db:connect" - ], - "Resource": "*" - } - ] - } - ``` - - - Use this permission boundary policy if you're connecting to Aurora clusters - and your Teleport database agent runs as IAM user (for example, uses AWS - credentials file). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBClusters", - "rds:ModifyDBCluster", - "iam:GetUserPolicy", - "iam:PutUserPolicy", - "iam:DeleteUserPolicy", - "rds-db:connect" - ], - "Resource": "*" - } - ] - } - ``` - - - Use this permission boundary policy if you're connecting to Aurora clusters - and your Teleport database agent runs as IAM role (for example, on an EC2 - instance with attached IAM role). - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBClusters", - "rds:ModifyDBCluster", - "iam:GetRolePolicy", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy", - "rds-db:connect" - ], - "Resource": "*" - } - ] - } - ``` - - +The command will generate a database agent configuration with RDS/Aurora +database auto-discovery enabled on the `us-west-1` region and place it at the +`/etc/teleport.yaml` location. -### Attach the policy and boundary to an IAM identity -(!docs/pages/includes/database-access/attach-iam-policies.mdx!) +## Step 4/7. Create an IAM policy for Teleport - - If you prefer to self-manage IAM for your RDS databases, take a look at - [AWS reference](../reference/aws.mdx) for details. - +Teleport needs AWS IAM permissions to be able to: -## Step 4/6. Start the database agent +- Discover and register RDS instances and Aurora clusters. +- Configure IAM authentication for them. -(!docs/pages/includes/database-access/token.mdx!) +(!docs/pages/includes/database-access/aws-bootstrap.mdx!) -Create the database agent configuration e.g. in `/etc/teleport.yaml`: - -```yaml -teleport: - data_dir: /var/lib/teleport - auth_token: /tmp/token - auth_servers: - # Teleport proxy address to connect to. - # For Teleport Cloud users, this will resemble mytenant.teleport.sh - - teleport.example.com:3080 -auth_service: - enabled: "no" -proxy_service: - enabled: "no" -db_service: - enabled: "yes" - aws: - - types: ["rds"] - regions: ["us-west-1"] # AWS regions to fetch databases from - tags: # AWS database resource tags to match - "*": "*" -``` +## Step 5/7. Start the database agent Start the database agent: @@ -299,12 +75,11 @@ policies for the discovered databases. Keep in mind that AWS IAM changes may not propagate immediately and can take a few minutes to come into effect. - The Teleport database agent uses the default credential provider chain to find AWS - credentials. See [Specifying Credentials](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) - for more information. + The Teleport Database Service uses the default + credential provider chain to find AWS credentials. See [Specifying Credentials](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) for more information. -## Step 5/6. Create database IAM user +## Step 6/7. Create a database IAM user Database users must allow IAM authentication in order to be used with Database Access for RDS. See below how to enable it for your database engine. @@ -318,8 +93,8 @@ Access for RDS. See below how to enable it for your database engine. GRANT rds_iam TO alice; ``` - - MySQL users must have RDS authentication plugin enabled: + + MySQL and MariaDB users must have the RDS authentication plugin enabled: ```sql CREATE USER alice IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS'; @@ -337,7 +112,7 @@ Access for RDS. See below how to enable it for your database engine. See [Creating a database account using IAM authentication](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.DBAccounts.html) for more information. -## Step 6/6. Connect +## Step 7/7. Connect Once the database agent has started and joined the cluster, login to see the registered databases: diff --git a/docs/pages/database-access/guides/redis.mdx b/docs/pages/database-access/guides/redis.mdx new file mode 100644 index 0000000000000..c062781a4520e --- /dev/null +++ b/docs/pages/database-access/guides/redis.mdx @@ -0,0 +1,375 @@ +--- +title: Database Access with Redis +description: How to configure Teleport Database Access with Redis. +--- + +
    + Database access for Redis is available starting from Teleport `9.0`. +
    + +This guide will help you to: + +- Install and configure Teleport. +- Configure mutual TLS authentication between Teleport and Redis. +- Connect to Redis through Teleport. + +## Prerequisites + +- Teleport version `9.0` or newer. +- Redis (standalone or cluster) version `6.0` or newer. +- `redis-cli` installed and added to your system's `PATH` environment variable. + + + Redis `7.0` and RESP3 (REdis Serialization Protocol) are currently not supported. + + +## Step 1/5. Set up Teleport Auth and Proxy + +(!docs/pages/includes/database-access/start-auth-proxy.mdx!) + +## Step 2/5. Create a Teleport user + +(!docs/pages/includes/database-access/create-user.mdx!) + +## Step 3/5. Create Redis users + +Each Redis user must be protected by a strong password. We recommend using OpenSSL to generate passwords: + +```shell +openssl rand -hex 32 +``` + + +If you have access to Redis you can also generate a password by using the below command from the Redis console: + +``` +ACL GENPASS +``` + + +Create a `users.acl` file, which defines users for your Redis deployment, passwords required to log in as a given user, + and sets of ACL rules. Redis allows you to provide passwords in plaintext or an SHA256 hash. +We strongly recommend using an SHA256 hash instead of plaintext passwords. + +You can use the command below to generate an SHA256 hash from a password. +```shell +echo -n STRONG_GENERATED_PASSWORD | sha256sum +``` + + + + ```text + user alice on #57639ed88a85996453555f22f5aa4147b4c9614056585d931e5d976f610651e9 allcommands allkeys + user default off + ``` + + For more ACL examples refer to the [Redis documentation](https://redis.io/docs/manual/security/acl/). + + +Redis in cluster mode requires a special user created on leader nodes to enable leader-follower communication. + ```text + user alice on #57639ed88a85996453555f22f5aa4147b4c9614056585d931e5d976f610651e9 allcommands allkeys + user replica-user on #42a9798b99d4afcec9995e47a1d246b98ebc96be7a732323eee39d924006ee1d &* -@all +replconf +ping +psync + user default off + ``` + + For more ACL examples refer to the Redis documentation: [ACL](https://redis.io/docs/manual/security/acl/), [ACL rules for replicas](https://redis.io/docs/manual/security/acl/#acl-rules-for-sentinel-and-replicas). + + + + + It's very important to either disable or protect with a password the `default` user. Otherwise, everyone with access + to the database can log in as the `default` user, which by default has administrator privileges. + + +## Step 4/5. Set up mutual TLS + +(!docs/pages/includes/database-access/tctl-auth-sign.mdx!) + +Create the secrets: + + + + When connecting to standalone Redis, sign the certificate for the hostname over + which Teleport will be connecting to it. + + For example, if your Redis server is accessible at `redis.example.com`, + run: + + ```code + $ tctl auth sign --format=redis --host=redis.example.com --out=server --ttl=2190h + ``` + + (!docs/pages/includes/database-access/ttl-note.mdx!) + + The command will create three files: + - `server.cas` with Teleport's certificate authority + - `server.key` with a generated private key + - `server.crt` with a generated user certificate + +You will need these files to enable mutual TLS on your Redis server. + + + When connecting to Redis Cluster, sign certificates for each member + using their hostnames and IP addresses. + + For example, if the first member is accessible at `redis1.example.com` with IP `10.0.0.1` and + the second at `redis2.example.com` with IP `10.0.0.2`, run: + + ```code + $ tctl auth sign --format=redis --host=redis1.example.com,10.0.0.1 --out=redis1 --ttl=2190h + $ tctl auth sign --format=redis --host=redis2.example.com,10.0.0.2 --out=redis2 --ttl=2190h + ``` + + (!docs/pages/includes/database-access/ttl-note.mdx!) + + The command will create three files: + - `server.cas` with Teleport's certificate authority + - `server.key` with a generated private key + - `server.crt` with a generated user certificate + +You will need these files to enable mutual TLS on your Redis server. + + + +(!docs/pages/includes/database-access/rotation-note.mdx!) + +Use the generated secrets to enable mutual TLS in your `redis.conf` configuration +file and restart the database: + + + + ```ini + tls-port 6379 + port 0 + + aclfile /path/to/users.acl + + tls-ca-cert-file /path/to/server.cas + tls-cert-file /path/to/server.crt + tls-key-file /path/to/server.key + tls-protocols "TLSv1.2 TLSv1.3" + ``` + + + ```ini + tls-port 7001 + port 0 + + cluster-enabled yes + + tls-replication yes + tls-cluster yes + + aclfile /path/to/users.acl + + masterauth GENERATED_STRONG_PASSWORD + masteruser replica-user + + tls-cert-file /usr/local/etc/redis/certs/server.crt + tls-key-file /usr/local/etc/redis/certs/server.key + tls-ca-cert-file /usr/local/etc/redis/certs/server.cas + tls-protocols "TLSv1.2 TLSv1.3" + ``` + + + +Once mutual TLS has been enabled, you will no longer be able to connect to +the cluster without providing a valid client certificate. You can use the +`tls-auth-clients optional` setting to allow connections +from clients that do not present a certificate. + +See [TLS Support](https://redis.io/topics/encryption) +in the Redis documentation for more details. + + + When you're configuring Redis Cluster you need to create the cluster using `redis-cli`. + + Use the following command to create the cluster. Please note `redis-cli --cluster create` accepts only IP addresses. + + ```shell + export REDISCLI_AUTH=STRONG_GENERATED_PASSWORD + export CERTS_DIR=/path/to/certs/ + export IP1=10.0.0.1 # update with the real node 1 IP + export IP2=10.0.0.2 # update with the real node 2 IP + export IP3=10.0.0.3 # update with the real node 3 IP + export IP4=10.0.0.4 # update with the real node 4 IP + export IP5=10.0.0.5 # update with the real node 5 IP + export IP6=10.0.0.6 # update with the real node 6 IP + + redis-cli --user alice --cluster-replicas 1 --tls --cluster-yes \ + --cluster create ${IP1}:7001 ${IP2}:7002 ${IP3}:7003 ${IP4}:7004 ${IP5}:7005 ${IP6}:7006 \ + --cacert ${CERTS_DIR}/server.cas --key ${CERTS_DIR}/server.key --cert ${CERTS_DIR}/server.crt + ``` + + + + + + To enable Redis cluster mode in Teleport, add the `mode=cluster` parameter to the connection URI in + your Teleport Database Service config file. + + ```yaml + databases: + - name: "redis-cluster" + uri: "rediss://redis.example.com:6379?mode=cluster" + ``` + + + +## Step 5/5. Connect + +Log into your Teleport cluster and see available databases: + + + + ```code + $ tsh login --proxy=teleport.example.com --user=alice + $ tsh db ls + # Name Description Labels + # ------------- --------------- -------- + # example-redis Example Redis env=dev + ``` + + + ```code + $ tsh login --proxy=mytenant.teleport.sh --user=alice + $ tsh db ls + # Name Description Labels + # ------------- --------------- -------- + # example-redis Example Redis env=dev + ``` + + + +To connect to a particular database instance, first retrieve its certificate +using the `tsh db login` command: + +```code +$ tsh db login example-redis +``` + + + You can be logged into multiple databases simultaneously. + + +You can optionally specify the database name and the user to use by default +when connecting to the database instance: + +```code +$ tsh db login --db-user=alice example-redis +``` + + + If flag `--db-user` is not provided, Teleport logs in as the `default` user. + + +Once logged in, connect to the database: + +```code +$ tsh db connect example-redis +``` + + + The `redis-cli` command-line client should be available in the system `PATH` in order to be + able to connect. + + +Now you can log in as the previously created user using the below command: + +``` +AUTH alice STRONG_GENERATED_PASSWORD +``` + +To log out of the database and remove credentials: + +```code +# Remove credentials for a particular database instance. +$ tsh db logout example-redis +# Remove credentials for all database instances. +$ tsh db logout +``` + +### Supported Redis commands + + + + + Redis in standalone mode doesn't support the commands below. If one of the listed commands is called Teleport + returns the `ERR Teleport: not supported by Teleport` error. + + - `HELLO` + - `PUNSUBSCRIBE` + - `SSUBSCRIBE` + - `SUNSUBSCRIBE` + + + + + Redis in cluster mode doesn't support below commands. If one of the listed commands above is called Teleport + returns the `ERR Teleport: command not supported` error. + + - `ACL` + - `ASKING` + - `CLIENT` + - `CLUSTER` + - `CONFIG` + - `DEBUG` + - `EXEC` + - `HELLO` + - `INFO` + - `LATENCY` + - `MEMORY` + - `MIGRATE` + - `MODULE` + - `MONITOR` + - `MULTI` + - `PFDEBUG` + - `PFSELFTEST` + - `PSUBSCRIBE` + - `PSYNC` + - `PUNSUBSCRIBE` + - `PUNSUBSCRIBE` + - `READONLY` + - `READWRITE` + - `REPLCONF` + - `REPLICAOF` + - `ROLE` + - `SCAN` + - `SCRIPT DEBUG` + - `SCRIPT KILL` + - `SHUTDOWN` + - `SLAVEOF` + - `SLOWLOG` + - `SSUBSCRIBE` + - `SUNSUBSCRIBE` + - `SYNC` + - `TIME` + - `WAIT` + - `WATCH` + + Teleport also adds additional processing to: + +| Command | Description | +|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `DBSIZE` | Sends the query to all nodes and returns the number of keys in the whole cluster. | +| `KEYS` | Sends the query to all nodes and returns a list of all keys in the whole cluster. | +| `MGET` | Translates the commands to multiple `GET`s and sends them to multiple nodes. Result is merged in Teleport and returned back to the client. If Teleport fails to fetch at least one key an error is returned. | +| `FLUSHDB` | Sends the query to all nodes. | +| `FLUSHALL` | Works the same as `FLUSHDB`. | +| `SCRIPT EXISTS` | Sends the query to all nodes. `1` is returned only if script exists on all nodes. | +| `SCRIPT LOAD` | Sends the script to all nodes. | +| `SCRIPT FLUSH` | Sends the query to all nodes. `ASYNC` parameter is ignored. | + + + + +## Next steps + +(!docs/pages/includes/database-access/guides-next-steps.mdx!) diff --git a/docs/pages/database-access/reference/aws.mdx b/docs/pages/database-access/reference/aws.mdx index 6d862d2774300..9bf906be356eb 100644 --- a/docs/pages/database-access/reference/aws.mdx +++ b/docs/pages/database-access/reference/aws.mdx @@ -3,17 +3,224 @@ title: Database Access AWS IAM Reference description: AWS IAM policies for Teleport database access. --- -Teleport automatically discovers and configures IAM for RDS and Redshift given -proper IAM permissions as described in the [AWS RDS & Aurora -guide](../guides/rds.mdx) and the [AWS Redshift -guide](../guides/postgres-redshift.mdx). +## Auto-discovery +With the appropriate IAM permissions, Teleport automatically discovers and +configures IAM policies for Amazon RDS and Redshift. +Teleport also requires permission to update database configurations, for example, to +enable IAM authentication on RDS databases. + +You can generate and manage the permissions with the [`teleport db configure +bootstrap`](../../database-access/reference/cli.mdx#teleport-db-configure-bootstrap) +command. For example, the following command would generate and print the +IAM policies: + +```code +$ teleport db configure bootstrap --manual +``` + +Or if you prefer, you manage the IAM permissions yourself. Examples of policies +for each discovery type are shown below. + +### Aurora/RDS + + + + Use this policy if you're connecting to RDS instances and your Teleport + database agent runs as an IAM user (for example, uses an AWS credentials file). + + Replace `{account-id}` with your AWS Account ID. + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBInstances", + "rds:ModifyDBInstance" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:GetUserPolicy", + "iam:PutUserPolicy", + "iam:DeleteUserPolicy" + ], + "Resource": "arn:aws:iam::{account-id}:user/sample-user" + } + ] + } + ``` + + + Use this policy if you're connecting to RDS instances and your Teleport + database agent runs as an IAM role (for example, on an EC2 instance with + an attached IAM role). + + Replace `{account-id}` with your AWS Account ID. + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBInstances", + "rds:ModifyDBInstance" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:GetRolePolicy", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy" + ], + "Resource": "arn:aws:iam::{account-id}:role/sample-role" + } + ] + } + ``` + + + Use this policy if you're connecting to Aurora clusters and your Teleport + database agent runs as an IAM user (for example, uses an AWS credentials file). + + Replace `{account-id}` with your AWS Account ID. + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBClusters", + "rds:ModifyDBCluster" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:GetUserPolicy", + "iam:PutUserPolicy", + "iam:DeleteUserPolicy" + ], + "Resource": "arn:aws:iam::{account-id}:user/sample-user" + } + ] + } + ``` + + + Use this policy if you're connecting to Aurora clusters and your Teleport + database agent runs as an IAM role (for example, on an EC2 instance with + an attached IAM role). + + Replace `{account-id}` with your AWS Account ID. + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBClusters", + "rds:ModifyDBCluster" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:GetRolePolicy", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy" + ], + "Resource": "arn:aws:iam::{account-id}:role/sample-role" + } + ] + } + ``` + + + +### Redshift + + + + Use this permission boundary if your Teleport database agent runs as an IAM + user (for example, it uses an AWS credentials file). + + Replace `{account-id}` with your AWS Account ID. + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "redshift:DescribeClusters", + "redshift:GetClusterCredentials" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:GetUserPolicy", + "iam:PutUserPolicy", + "iam:DeleteUserPolicy" + ], + "Resource": "arn:aws:iam::{account-id}:user/sample-user" + } + ] + } + ``` + + + Use this permission boundary if your Teleport database agent runs as an IAM + role (for example, on an EC2 instance with an attached IAM role). + + Replace `{account-id}` with your AWS Account ID. + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "redshift:DescribeClusters", + "redshift:GetClusterCredentials", + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:GetRolePolicy", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy" + ], + "Resource": "arn:aws:iam::{account-id}:role/sample-role" + } + ] + } + ``` + + + +## Manual registration If you prefer to register RDS or Redshift databases manually using a [static configuration](./configuration.mdx) or [`tctl`](../guides/dynamic-registration.mdx) and manage IAM yourself, example IAM policies with the required permissions are shown below. -## RDS or Aurora policy +### RDS or Aurora policy To connect to an RDS database, the database agent's IAM identity needs to have `rds-db:connect` permissions for it: @@ -56,7 +263,7 @@ arn:aws:rds-db:::dbuser:/ See [Creating and using an IAM policy for IAM database access](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.IAMPolicy.html) for more information. -## Redshift policy +### Redshift policy Teleport uses temporary credentials generated by AWS to authenticate with Redshift databases. diff --git a/docs/pages/database-access/reference/cli.mdx b/docs/pages/database-access/reference/cli.mdx index dc77a23ce3721..d66839001b05a 100644 --- a/docs/pages/database-access/reference/cli.mdx +++ b/docs/pages/database-access/reference/cli.mdx @@ -38,6 +38,53 @@ $ teleport db start \ | `--gcp-project-id` | (Only for Cloud SQL) GCP Cloud SQL project identifier. | | `--gcp-instance-id` | (Only for Cloud SQL) GCP Cloud SQL instance identifier.| +## teleport db configure create + +Creates a sample Database Service configuration. + +```code +$ teleport db configure create --rds-discovery=us-west-1 --rds-discovery=us-west-2 +$ teleport db configure create \ + --token=/tmp/token \ + --proxy=proxy.example.com:3080 \ + --name=example \ + --protocol=postgres \ + --uri=postgres://postgres.example.com:5432 \ + --labels=env=prod +``` + +| Flag | Description | +| - | - | +| `--proxy` | Teleport Proxy Service address to connect to. Default: `0.0.0.0:3080`. | +| `--token` | Invitation token to register with the Auth Service. Default: none. | +| `--rds-discovery` | List of AWS regions the agent will discover for RDS/Aurora instances. | +| `--redshift-discovery` | List of AWS regions the agent will discover for Redshift instances. | +| `--ca-pin` | CA pin to validate the Auth Service (can be repeated for multiple pins). | +| `--name` | Name of the proxied database. | +| `--protocol` | Proxied database protocol. Supported are: [postgres mysql mongodb cockroachdb redis sqlserver]. | +| `--uri` | Address the proxied database is reachable at. | +| `--labels` | Comma-separated list of labels for the database, for example env=dev,dept=it | +| `-o/--output` | Write to stdout with `-o=stdout`, the default config file with `-o=file`, or a custom path with `-o=file:///path` | + +## teleport db configure bootstrap + +Bootstrap the necessary configuration for the database agent. It reads the provided agent configuration to determine what will be bootstrapped. + +```code +$ teleport db configure bootstrap -c /etc/teleport.yaml --attach-to-user TeleportUser +$ teleport db configure bootstrap -c /etc/teleport.yaml --attach-to-role TeleportRole +$ teleport db configure bootstrap -c /etc/teleport.yaml --manual +``` + +| Flag | Description | +| - | - | +| `-c/--config` | Path to a configuration file. Default: `/etc/teleport.yaml`. | +| `--manual` | When executed in "manual" mode, this command will print the instructions to complete the configuration instead of applying them directly. | +| `--policy-name` | Name of the Teleport Database Service policy. Default: "DatabaseAccess" | +| `--confirm` | Do not prompt the user and auto-confirm all actions. | +| `--attach-to-role` | Role name to attach the policy to. Mutually exclusive with `--attach-to-user`. If none of the attach-to flags is provided, the command will try to attach the policy to the current user/role based on the credentials. | +| `--attach-to-user` | User name to attach the policy to. Mutually exclusive with `--attach-to-role`. If none of the attach-to flags is provided, the command will try to attach the policy to the current user/role based on the credentials. | + ## tctl auth sign When invoked with a `--format=db` (or `--format=mongodb` for MongoDB) flag, diff --git a/docs/pages/desktop-access/getting-started.mdx b/docs/pages/desktop-access/getting-started.mdx index 17191b3b721c4..caa1df9757a00 100644 --- a/docs/pages/desktop-access/getting-started.mdx +++ b/docs/pages/desktop-access/getting-started.mdx @@ -156,7 +156,7 @@ certificate-based smart card authentication, and ensuring RDP is enabled. 1. Get the Teleport user CA certificate by running: -``` +```code $ tctl auth export --type=windows > user-ca.cer ``` @@ -237,16 +237,16 @@ access. 1. Publish the CA to LDAP: -``` -$ certutil –dspublish –f NTAuthCA +```powershell +certutil –dspublish –f NTAuthCA ``` 2. Force the retrieval of the CA from LDAP. While this step is not required, it speeds up the process and allows you to proceed to the next steps without waiting for the certificate to propagate. -``` -$ certutil -pulse +```powershell +certutil -pulse ``` ### Enable the Smart Card service @@ -384,7 +384,7 @@ providing a join token. First, generate a join token with the following command: -``` +```code $ tctl tokens add --type=windowsdesktop ``` diff --git a/docs/pages/desktop-access/reference.mdx b/docs/pages/desktop-access/reference.mdx new file mode 100644 index 0000000000000..156ff29aeecd6 --- /dev/null +++ b/docs/pages/desktop-access/reference.mdx @@ -0,0 +1,19 @@ +--- +title: Desktop Access Reference +layout: tocless-doc +--- + + + + Configure Teleport Desktop Access. + + + Desktop Access audit events. + + + Share your clipboard with a remote desktop. + + + Desktop session recording and playback + + \ No newline at end of file diff --git a/docs/pages/desktop-access/reference/clipboard.mdx b/docs/pages/desktop-access/reference/clipboard.mdx new file mode 100644 index 0000000000000..96f284d94d73d --- /dev/null +++ b/docs/pages/desktop-access/reference/clipboard.mdx @@ -0,0 +1,46 @@ +--- +title: Clipboard Sharing +description: Using Clipboard Sharing with Teleport Desktop Access. +--- + +Teleport Desktop Access supports copying and pasting text between your browser +and a remote Windows Desktop. + + +This feature requires a Chromium-based browser such as Google Chrome, Brave, +or Microsoft Edge. To use Desktop Access on other browsers, disable clipboard +sharing. + + +Clipboard sharing is configured via Teleport's RBAC system. It is enabled by default, +but can be disabled by setting the following role option on one or more of a user's +roles: + +```yaml +kind: role +version: v4 +metadata: + name: no_clipboard +spec: + options: + desktop_clipboard: false +``` + +If a clipboard sharing is enabled for a user, the browser will prompt for access +to the system clipboard when a desktop session is started. Grant this access to +proceed. + +{ + /* screenshot of prompt */ +} + +If you mistakenly deny clipboard permissions, you can change this setting by +clicking the lock on the left side of your address bar, toggling the clipboard +permission, and refreshing the page. + +When a desktop session is active, Teleport will indicate whether clipboard sharing +is enabled in the menu bar at the top of the screen. + +{ + /* screenshots for enabled / disabled */ +} \ No newline at end of file diff --git a/docs/pages/desktop-access/reference/sessions.mdx b/docs/pages/desktop-access/reference/sessions.mdx new file mode 100644 index 0000000000000..0afbb767fae91 --- /dev/null +++ b/docs/pages/desktop-access/reference/sessions.mdx @@ -0,0 +1,86 @@ +--- +title: Session Recording and Playback +description: Recording and Playing Back Teleport Desktop Access Sessions. +--- + +Teleport Desktop Access supports recording and playback of desktop sessions. + +## Disabling session recording + +Session recording is enabled by default, and can be disabled in one of two ways: + +### Disable session recording at the cluster level + +To disable session recording at the cluster level, edit your `teleport.yaml` +configuration file. + +```yaml +teleport: + auth_service: + session_recording: off +``` + + +Disabling session recording at the cluster level applies to all types of +recordings. For example, this configuration would prevent Teleport from +recording SSH sessions as well as desktop sessions. + + +The `session_recording` property can also be configured dynamically by using +`tctl` to edit the cluster's `session_recording_config` resource. This is the +required approach for Teleport Cloud users. + +### Disable session recording via RBAC + +Teleport's RBAC system allows more fine-grained control over which sessions are +recorded. By default, desktop session recording is enabled. To disable desktop +session recording for a particular user, add the following role option to all of +the user's roles: + +```yaml +kind: role +version: v4 +metadata: + name: no_desktop_recording +spec: + options: + record_session: + desktop: false +``` + +Because recording can be important for auditing and compliance concerns, the +presence of a single role with recording enabled will result in the session +being recorded. In other words, *all* of the roles applied to a user must +explicitly disable recording to prevent the session from being recorded. + +## Recording + +When a desktop session is active, Teleport will indicate whether the session is +being recorded in the menu bar at the top of the screen. + +{ + /* screenshots for recording, not recording */ +} + +## Playback + +Recorded sessions can be viewed in the *Session Recordings* page. Desktop +recordings show a desktop icon in the first column to distinguish them from +SSH recordings. + +{ + /* screenshot of recordings table */ +} + + +Since Teleport 8.1, sessions can be protected via RBAC. In order for a user to +see desktop sessions in this list, their roles must permit access to the +sessions resource. + + +Click the play button to open the player in a new tab. The desktop session +player supports toggling between play and pause, but does not support seeking to +a specific point in the stream, rewinding, or restarting playback when the end +of the stream is reached. To replay a session, refresh the page. + + diff --git a/docs/pages/enterprise/hsm.mdx b/docs/pages/enterprise/hsm.mdx index b1edac7522235..f018bc6f26c5f 100644 --- a/docs/pages/enterprise/hsm.mdx +++ b/docs/pages/enterprise/hsm.mdx @@ -12,7 +12,7 @@ This guide will show you how to set up the Teleport Auth Server to use a hardwar - An HSM reachable from your Teleport auth server. - The PKCS#11 module for your HSM. -
    +
    Teleport Cloud and Teleport Open Source do not currently support HSM.
    diff --git a/docs/pages/enterprise/sso/adfs.mdx b/docs/pages/enterprise/sso/adfs.mdx index 6bc68d1378f6d..0e08fd0ea8fbc 100644 --- a/docs/pages/enterprise/sso/adfs.mdx +++ b/docs/pages/enterprise/sso/adfs.mdx @@ -1,16 +1,14 @@ --- -title: SSH Authentication With ADFS How To -description: How to configure SSH access with Active Directory (ADFS) using Teleport -h1: SSH Authentication with ADFS +title: SSO with Active Directory Federation Services +description: How to configure SSH access with Active Directory Federation Services using Teleport +h1: Single Sign-On with Active Directory Federation Services --- -## Active Directory as an SSO provider for SSH authentication - This guide will cover how to configure Active Directory Federation Services -[ADFS](https://en.wikipedia.org/wiki/Active_Directory_Federation_Services) to be +([ADFS](https://en.wikipedia.org/wiki/Active_Directory_Federation_Services)) to be a single sign-on (SSO) provider to issue SSH credentials to specific groups of users. When used in combination with role -based access control (RBAC) it allows SSH administrators to define policies +based access control (RBAC), it allows SSH administrators to define policies like: - Only members of "DBA" group can SSH into machines running PostgreSQL. @@ -24,25 +22,15 @@ like: This guide requires a commercial edition of Teleport. -## Enable ADFS Authentication - -First, configure Teleport auth server to use ADFS authentication instead of the local -user database. Update `/etc/teleport.yaml` as shown below and restart the -teleport daemon. - -```yaml -auth_service: - authentication: - type: saml -``` +(!docs/pages/includes/enterprise/samlauthentication.mdx!) ## Configure ADFS You'll need to configure ADFS to export claims about a user (Claims Provider -Trust in ADFS terminology) and you'll need to configure AD FS to trust +Trust in ADFS terminology) and you'll need to configure ADFS to trust Teleport (a Relying Party Trust in ADFS terminology). -For Claims Provider Trust configuration you'll need to specify at least the +For Claims Provider Trust configuration, you'll need to specify at least the following two incoming claims: `Name ID` and `Group`. `Name ID` should be a mapping of the LDAP Attribute `E-Mail-Addresses` to `Name ID`. A group membership claim should be used to map users to roles (for example to @@ -51,29 +39,29 @@ separate normal users and admins). ![Name ID Configuration](../../../img/adfs-1.png) ![Group Configuration](../../../img/adfs-2.png) -In addition if you are using dynamic roles (see below), it may be useful to map +In addition, if you are using dynamic roles (see below), it may be useful to map the LDAP Attribute `SAM-Account-Name` to `Windows account name` and create another mapping of `E-Mail-Addresses` to `UPN`. ![WAN Configuration](../../../img/adfs-3.png) ![UPN Configuration](../../../img/adfs-4.png) -You'll also need to create a Relying Party Trust, use the below information to -help guide you through the Wizard. Note, for development purposes we recommend +You'll also need to create a Relying Party Trust. Use the below information to +help guide you through the Wizard. Note that for development purposes we recommend using `https://localhost:3080/v1/webapi/saml/acs` as the Assertion Consumer Service (ACS) URL, but for production you'll want to change this to a domain that can be accessed by other users as well. - Create a claims aware trust. - Enter data about the relying party manually. -- Set the display name to something along the lines of "Teleport". +- Set the display name to something along the lines of `Teleport`. - Skip the token encryption certificate. - Select *"Enable support for SAML 2.0 Web SSO protocol"* and set the URL to `https://localhost:3080/v1/webapi/saml/acs`. - Set the relying party trust identifier to `https://localhost:3080/v1/webapi/saml/acs` as well. - For access control policy select *"Permit everyone"*. Once the Relying Party Trust has been created, update the Claim Issuance Policy -for it. Like before make sure you send at least `Name ID` and `Group` claims to the +for it. Like before, make sure you send at least `Name ID` and `Group` claims to the relying party (Teleport). If you are using dynamic roles, it may be useful to map the LDAP Attribute `SAM-Account-Name` to *"Windows account name"* and create another mapping of `E-Mail-Addresses` to *"UPN"*. @@ -83,10 +71,10 @@ associated with it. To check this open Server Manager then *"Tools -> Active Directory Users and Computers"* and select the user and right click and open properties. Make sure the email address field is filled out. -## Create Teleport Roles +## Create Teleport roles -Lets create two Teleport roles: one for administrators and the other is for -normal users. You can create them using `tctl create {file name}` CLI command +Let's create two Teleport roles: one for administrators and the other for +normal users. You can create them using the `tctl create {file name}` CLI command or via the Web UI. ```yaml @@ -126,13 +114,18 @@ spec: This role declares: -- Devs are only allowed to login to nodes labelled with `access: relaxed` label. -- Developers can log in as `ubuntu` user -- Notice `{{external["http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]}}` login. It configures Teleport to look at - *"[http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"](http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname")* ADFS claim and use that field as an allowed login for each user. - Also note the double quotes (`"`) and square brackets (`[]`) around the claim name - these are important. -- Developers also do not have any "allow rules" i.e. they will not be able to - see/replay past sessions or re-configure the Teleport cluster. +- Devs are only allowed to log in to nodes labeled `access: relaxed`. +- Developers can log in as the `ubuntu` user. +- Developers will not be able to see or replay past sessions or + re-configure the Teleport cluster. + +The login +`{{external["http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]}}` +configures Teleport to look at the +`http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname` +ADFS claim and use that field as an allowed login for each user. Note the +double quotes (`"`) and square brackets (`[]`) around the claim name—these are +important. Next, create a SAML connector [resource](../../setup/reference/resources.mdx): @@ -149,7 +142,7 @@ name is `http://schemas.xmlsoap.org/claims/Group` with a value of *"admins"* to the *"admin"* role. Groups with the value *"users"* is being mapped to the *"users"* role. -## Export the Signing Key +## Export the signing key For the last step, you'll need to export the signing key: @@ -170,8 +163,8 @@ the same as before: $ tsh --proxy=proxy.example.com login ``` -This command will print the SSO login URL (and will try to open it -automatically in a browser). +This command will print the SSO login URL and try to open it +automatically in a browser. - Teleport only supports sending party initiated flows for SAML 2.0. This - means you can not initiate login from your identity provider, you have to - initiate login from either the Teleport Web UI or CLI. - + Teleport only supports sending party-initiated flows for SAML 2.0. This means + that you cannot initiate login from your identity provider, and must do so + from either the Teleport Web UI or CLI. + ## Troubleshooting -If you get "access denied errors" the number one place to check is the audit -log on the Teleport auth server. It is located in `/var/lib/teleport/log` by +If you get "access denied" errors, the number one place to check is the audit +log on the Teleport Auth Server. It is located in `/var/lib/teleport/log` by default and it will contain the detailed reason why a user's login was denied. Some errors (like filesystem permissions or misconfigured network) can be @@ -204,4 +197,4 @@ $ sudo journalctl -fu teleport ``` If you wish to increase the verbosity of Teleport's syslog, you can pass -`--debug` flag to `teleport start` command. +`--debug` flag to the `teleport start` command. diff --git a/docs/pages/enterprise/sso/azuread.mdx b/docs/pages/enterprise/sso/azuread.mdx index c50e0dd4f49e7..7a4a56330b187 100644 --- a/docs/pages/enterprise/sso/azuread.mdx +++ b/docs/pages/enterprise/sso/azuread.mdx @@ -28,6 +28,8 @@ Before you get started you’ll need: - To register one or more users in the directory - To create at least two security groups in Azure AD and assign one or more users to each group +(!docs/pages/includes/enterprise/samlauthentication.mdx!) + ## Configure Azure AD 1. Select **Azure AD -> Enterprise Applications** @@ -168,34 +170,6 @@ $ tctl create dev.yaml ## Testing -Update your Teleport configuration to make SAML the default authentication method. - - - -In your Teleport configuration file, include the following: - -```yaml -auth_service: - authentication: - type: saml -``` - - -Apply the following `cluster_auth_preference` resource via `tctl create`. - -```yaml - kind: cluster_auth_preference - version: v2 - metadata: - name: cluster-auth-preference - spec: - type: "saml" -``` - - - -The Web UI will now present the option to log in via Microsoft. - ![Login with Microsoft](../../../img/azuread/azure-11-loginwithmsft.png) diff --git a/docs/pages/enterprise/sso/gitlab.mdx b/docs/pages/enterprise/sso/gitlab.mdx index 9ffd4523b7463..c3b74696f4174 100644 --- a/docs/pages/enterprise/sso/gitlab.mdx +++ b/docs/pages/enterprise/sso/gitlab.mdx @@ -22,17 +22,7 @@ like: This guide requires a commercial edition of Teleport. -## Enable OIDC Authentication - -First, configure Teleport auth server to use OIDC authentication instead of the local -user database. Update `/etc/teleport.yaml` as shown below and restart the -teleport daemon. - -```yaml -auth_service: - authentication: - type: oidc -``` +(!docs/pages/includes/enterprise/oidcauthentication.mdx!) ## Configure GitLab diff --git a/docs/pages/enterprise/sso/google-workspace.mdx b/docs/pages/enterprise/sso/google-workspace.mdx index 86b977b3ff770..f2325d9569da9 100644 --- a/docs/pages/enterprise/sso/google-workspace.mdx +++ b/docs/pages/enterprise/sso/google-workspace.mdx @@ -32,6 +32,8 @@ Before you get started you’ll need: - Ability to create a Google Cloud project, which requires signing up for Google Cloud. Note that this guide will not require using any paid Google Cloud services. - Ability to set up Google Workspace groups. +(!docs/pages/includes/enterprise/oidcauthentication.mdx!) + ## Configuring Google Workspace The setup will consist of creating a new project on Google Cloud Platform, diff --git a/docs/pages/enterprise/sso/oidc.mdx b/docs/pages/enterprise/sso/oidc.mdx index ff77208d90b84..59ab7f1426193 100644 --- a/docs/pages/enterprise/sso/oidc.mdx +++ b/docs/pages/enterprise/sso/oidc.mdx @@ -20,17 +20,7 @@ administrators to define policies like: This guide requires an Enterprise edition of Teleport. -## Enable OIDC Authentication - -First, configure Teleport auth server to use OIDC authentication instead of the local -user database. Update `/etc/teleport.yaml` as show below and restart the -teleport daemon. - -```yaml -auth_service: - authentication: - type: oidc -``` +(!docs/pages/includes/enterprise/oidcauthentication.mdx!) ## Identity Providers diff --git a/docs/pages/enterprise/sso/okta.mdx b/docs/pages/enterprise/sso/okta.mdx index f5d374e9ea70a..318891f41f802 100644 --- a/docs/pages/enterprise/sso/okta.mdx +++ b/docs/pages/enterprise/sso/okta.mdx @@ -22,17 +22,7 @@ like: This guide requires a commercial edition of Teleport. -## Enable SAML Authentication - -First, configure Teleport auth server to use SAML authentication instead of the local -user database. Update `/etc/teleport.yaml` as shown below and restart the -teleport daemon. - -```yaml -auth_service: - authentication: - type: saml -``` +(!docs/pages/includes/enterprise/samlauthentication.mdx!) ## Configure Okta diff --git a/docs/pages/enterprise/sso/one-login.mdx b/docs/pages/enterprise/sso/one-login.mdx index a0222e3709c3a..37e0f863148c6 100644 --- a/docs/pages/enterprise/sso/one-login.mdx +++ b/docs/pages/enterprise/sso/one-login.mdx @@ -22,17 +22,7 @@ like: This guide requires an Enterprise edition of Teleport. -## Enable SAML Authentication - -Configure Teleport auth server to use SAML authentication instead of the local -user database. Update `/etc/teleport.yaml` as shown below and restart the -teleport daemon. - -```yaml -auth_service: - authentication: - type: saml -``` +(!docs/pages/includes/enterprise/samlauthentication.mdx!) ## Configure Application diff --git a/docs/pages/enterprise/workflow/ssh-approval-jira-cloud.mdx b/docs/pages/enterprise/workflow/ssh-approval-jira-cloud.mdx index fe5ce015141c2..fbdaa0e959ecc 100644 --- a/docs/pages/enterprise/workflow/ssh-approval-jira-cloud.mdx +++ b/docs/pages/enterprise/workflow/ssh-approval-jira-cloud.mdx @@ -4,9 +4,7 @@ description: How to configure SSH login approval using Jira and Teleport h1: SSH login approvals using Jira --- -## Teleport Jira Plugin Setup - -This guide will talk through how to setup Teleport with Jira. Teleport to Jira integration allows you to treat Teleport access and permission requests using Jira tickets. +This guide will talk through how to setup Teleport with Jira. Teleport's Jira integration allows you to treat Teleport access and permission requests using Jira tickets. ## Setup @@ -16,62 +14,20 @@ This guide assumes that you have: - Admin privileges with access to `tctl` - Jira Server or Jira Cloud installation with an owner privileges, specifically to setup webhooks, issue types, and workflows -### Create an access-plugin role and user within Teleport +Teleport Cloud requires that plugins connect through the Proxy Service (`mytenant.teleport.sh:443`). Open Source and Enterprise installations can connect to the Auth Service (`auth.example.com:3025`) directly. -First off, using an existing Teleport Cluster, we are going to create a new Teleport User and Role to access Teleport. -#### Create User and Role for access +### Create a user and role for access -Log into Teleport Authentication Server, this is where you normally run `tctl`. Create a -new user and role that only has API access to the `access_request` API. The below script -will create a yaml resource file for a new user and role. +(!docs/pages/includes/plugins/rbac.mdx!) -```bash -cat > rscs.yaml < - By default, [`tctl auth sign`](../../setup/reference/cli.mdx#tctl-auth-sign) produces certificates with a relatively short lifetime. For production deployments, the `--ttl` flag can be used to ensure a more practical certificate lifetime. `--ttl=8760h` exports a 1 year token - - -## Setting up your Jira Project +## Setting up your Jira project ### Creating the permission management project @@ -87,11 +43,11 @@ Create a new board for tasks in the permission management project. The board has 2. Approved 3. Denied -Teleport Jira Plugin will create a new issue for each new permission request in the first available column on the board. When you drag the request task to Approved column on Jira, the request will be approved. If you drag the request task to the Denied column in Jira, the request will be denied. +Teleport's Jira plugin will create a new issue for each new permission request in the first available column on the board. When you drag the request task to the Approved column in Jira, the request will be approved. If you drag the request task to the Denied column in Jira, the request will be denied. -### Setting up Request ID field on Jira +### Setting up a request ID field on Jira -Teleport Jira Plugin requires a custom issue field to be created. +The Teleport Jira plugin requires a custom issue field to be created. Go to your Jira Project settings → Issue Types → Select type `Task` → add a new Short Text field named `TeleportAccessRequestId`. @@ -103,30 +59,30 @@ If you're using Jira Cloud, navigate to [Account Settings → Security → API T For Jira Server, the URL of the API tokens page will be different depending on your installation. -### Setting up Jira Webhooks +### Setting up Jira webhooks -Go to Settings → General → System → Webhooks and create a new Webhook for Jira to tell the Teleport Plugin about updates. +Go to Settings → General → System → Webhooks and create a new webhook for Jira to tell the Teleport plugin about updates. -For the webhook URL, use the URL that you'll run the plugin on. It needs to be a publicly accessible URL that we'll set up later. Jira requires the webhook listener to run over HTTPS. +For the webhook URL, use the URL that you'll run the plugin on. It needs to be a publicly accessible URL (we will show you how to set this up later). Jira requires the webhook listener to run over HTTPS. -The Teleport Jira plugin webhook needs to be notified only about new issues being created, issues being updated, or deleted. You can leave all the other boxes empty. +The webhook needs to be notified only about new issues being created, issues being updated, or deleted. You can leave all the other boxes empty. - Jira Webhook will send updates about any issues in any projects in your Jira installation to the webhook. We suggest that you use JQL filters to limit which issues are being sent to the plugin. + Jira will send updates about any issues in any projects in your Jira installation to the webhook. We suggest that you use JQL filters to limit which issues are being sent to the plugin. - The Plugin's web server will run with TLS, but you can disable it with `--insecure-no-tls` to test things out in a dev environment. + The plugin's web server will run with TLS, but you can disable it with `--insecure-no-tls` to test things out in a dev environment. -In the webhook settings page, make sure that the webhook will only send Issue Updated updates. It's not critical if anything else gets sent, the plugin will just ignore everything else. +In the webhook settings page, make sure that the webhook will only send Issue Updated updates. It's not critical if anything else gets sent, since the plugin will just ignore everything else. ## Installing -We recommend installing the Teleport Plugins alongside the Teleport Proxy. This is an ideal +We recommend installing Teleport plugins alongside the Teleport Proxy. This is an ideal location as plugins have a low memory footprint, and will require both public internet access -and Teleport Auth access. We currently only provide linux-amd64 binaries, you can also +and Teleport Auth access. We currently only provide linux-amd64 binaries, you can also compile these plugins from [source](https://github.com/gravitational/teleport-plugins/tree/master/access/jira). ```code @@ -142,7 +98,7 @@ Run `./install` in from 'teleport-jira' or place the executable in the appropria ### Configuration file -Teleport Jira Plugin uses a config file in TOML format. Generate a boilerplate config by +The Teleport Jira plugin uses a config file in TOML format. Generate a boilerplate config by running the following command: ```code @@ -150,11 +106,20 @@ $ teleport-jira configure > teleport-jira.toml $ sudo mv teleport-jira.toml /etc ``` -By default, Jira Teleport Plugin will use a config in `/etc/teleport-jira.toml`, and you can override it with `-c config/file/path.toml` flag. +By default, the Jira Teleport plugin will use a config in `/etc/teleport-jira.toml`, and you can override it with `-c config/file/path.toml` flag. + + +```toml +(!examples/resources/plugins/teleport-jira-self-hosted.toml!) +``` + + ```toml -(!examples/resources/plugins/teleport-jira.toml!) +(!examples/resources/plugins/teleport-jira-cloud.toml!) ``` + + The `[teleport]` section describes where the teleport service running, and what keys should the plugin use to authenticate itself. Use the keys that you've generated. @@ -165,10 +130,10 @@ The `[jira]` section requires a few things: 3. Your Jira API token that you've created above. 4. A Jira Project key, available in Project settings. -`[http]` setting block describes how the Plugin's HTTP server works. The HTTP server is responsible for listening for updates from Jira, and processing updates, like when someone drags a task from Inbox to Approved column. +The `[http]` setting block describes how the plugin's HTTP server works. The HTTP server is responsible for listening for updates from Jira, and processing updates, like when someone drags a task from Inbox to Approved column. You must provide an address the server should listen on, and a certificate to use. It's possible to -setup on the same server as the Teleport Proxy, so you can use the same TLS certificate. +run the Jira plugin on the same server as the Teleport Proxy, so you can use the same TLS certificate. ## Testing @@ -186,20 +151,20 @@ Go ahead and test it: $ tsh login --request-roles=admin ``` -That should create a new permission request on Teleport (you can test if it did with `tctl request ls` ), and you should see a new task on your Jira project board. +That should create a new permission request on Teleport (you can test if it did with `tctl request ls`), and you should see a new task on your Jira project board. -### Setup with SystemD +### Set up systemd -In production, we recommend starting teleport plugin daemon via an init system like systemd . -Here's the recommended Teleport Plugin service unit file for systemd: +In production, we recommend starting the Teleport plugin daemon via an init system like systemd. +Here's the recommended Teleport plugin service unit file for systemd: ```txt (!examples/systemd/plugins/teleport-jira.service!) ``` -Save this as `teleport-jira.service`. +Save this as `teleport-jira.service`. Make sure the `teleport-jira start` command includes a `--config` flag that refers to the configuration file you created earlier. -## Audit Log +## Audit log The plugin will let anyone with access to the Jira board approve/deny requests so it's important to review Teleport's audit log. diff --git a/docs/pages/enterprise/workflow/ssh-approval-jira-server.mdx b/docs/pages/enterprise/workflow/ssh-approval-jira-server.mdx index 1357bd2971e90..5bf61b69dee15 100644 --- a/docs/pages/enterprise/workflow/ssh-approval-jira-server.mdx +++ b/docs/pages/enterprise/workflow/ssh-approval-jira-server.mdx @@ -4,9 +4,7 @@ description: How to configure SSH login approval using Jira Server and Teleport h1: SSH login approvals using Jira Server --- -## Teleport Jira Server Plugin Setup - -This guide will talk through how to setup Teleport with Jira Server. Teleport to Jira Server integration allows you to treat Teleport access and permission requests as Jira Tasks. +This guide will talk through how to set up Teleport with Jira Server. Teleport's integration with Jira Server allows you to treat Teleport access and permission requests as Jira tasks. Teleport's tsh request workflow is synchronous and needs to be approved within 1 hour of the request. @@ -32,114 +30,71 @@ This guide will talk through how to setup Teleport with Jira Server. Teleport to - A running Teleport Cluster - Admin Privileges with access and control of [`tctl`](../../setup/reference/cli.mdx#tctl) -- Jira Server installation with owner privileges, specifically to setup webhooks, issue types, and workflows. This plugin has been tested with Jira Software 8.8.0 - -### Create an access-plugin role and user within Teleport - -First off, using an existing Teleport Cluster, we are going to create a new Teleport User and Role to access Teleport. - -#### Create User and Role for access +- A Jira Server installation with owner privileges, specifically to set up webhooks, issue types, and workflows. This plugin has been tested with Jira Software 8.8.0 -Log into Teleport Authentication Server, this is where you normally run `tctl`. Create a -new user and role that only has API access to the `access_request` API. The below script -will create a yaml resource file for a new user and role. - -```bash -cat > rscs.yaml < - By default, [`tctl auth sign`](../../setup/reference/cli.mdx#tctl-auth-sign) produces certificates with a relatively short lifetime. For production deployments, the `--ttl` flag can be used to ensure a more practical certificate lifetime. `--ttl=8760h` exports a 1 year token - +We'll reference these files later when [configuring the plugins](#configuration-file). ### Setting up your Jira Server instance -#### Creating a Project +#### Creating a project -Teleport Jira Plugin relies on your Jira project having a board with at least three statuses (columns): Pending, Approved, and Denied. It's therefore the easiest scenario to create a new Jira project for Teleport to use. +The Teleport Jira plugin relies on your Jira project having a board with at least three statuses (columns): Pending, Approved, and Denied. It's therefore the easiest scenario to create a new Jira project for Teleport to use. -The specific type of project you choose when you create it doesn't matter, as long as you can setup a Kanban Board for it, but we recommend that you go with Kanban Software Development — this will reduce the amount of setup work you'll have to do and provide the board out of the box. +The specific type of project you choose when you create it doesn't matter, as long as you can setup a Kanban Board for it, but we recommend that you go with Kanban Software Development. This will reduce the amount of setup work you'll have to do and provide the board out of the box. You'll need the project key for the Teleport plugin settings later on. It's usually a 3 character code for the project. -#### Setting up Request ID field on Jira +#### Setting up a request ID field on Jira -Teleport stores the request metadata in a special Jira custom field that must be named teleportAccessRequestId. To create that field, go to Administration -> Issues -> Custom Fields -> Add Custom Field. +Teleport stores the request metadata in a special Jira custom field that must be named `teleportAccessRequestId`. To create that field, go to Administration -> Issues -> Custom Fields -> Add Custom Field. -Name the field `teleportAccessRequestId`, and choose Text Field (single line) as the field type. Assign the field to your project, or make it global. Teleport Access Request ID is an internal field and it's not supposed to be edited by users, so you can leave the Screens section blank. That means that the field won't show up in Jira UI. +Name the field `teleportAccessRequestId`, and choose Text Field (single line) as the field type. Assign the field to your project, or make it global. Teleport Access Request ID is an internal field and it's not supposed to be edited by users, so you can leave the Screens section blank. That means that the field won't show up in Jira's UI. Go to Project Settings -> Fields and make sure that the `teleportAccessRequestId` field shows up on the list of fields available in this project. #### Setting up the status board -The default Jira Software workflow has a different board setup from what Teleport needs, so we'll setup another workflow and assign that workflow to the project board. +The default Jira Software workflow has a different board setup than what Teleport needs, so we'll set up another workflow and assign that workflow to the project board. -Go to Administration -> Workflows. You can choose to add a new workflow (recommended), or edit the existing workflow, it'll be called Software Simplified Workflow for Project NAME by default. It's only used in your single project, so it's safe to edit it. +Go to Administration -> Workflows. You can choose to add a new workflow (recommended), or edit the existing workflow. It will be called Software Simplified Workflow for Project NAME by default. It's only used in your single project, so it's safe to edit it. Edit the workflow to have these three states: 1. Pending 2. Approved 3. Denied - The rules of the workflow must meet these requirements: + +The rules of the workflow must meet these requirements: - New created issues should be in Pending state. - It should be possible to move from Pending to Approved - It should be possible to move from Pending to Declined. - You can choose to make the workflow strict and restrict moving requests from Approved state to Declined state and vice versa, or leave that flexible. Teleport will only change the request status once, i.e. the first time the request is approved or denied on your Jira board. -With Workflow editor you can setup who can approve or deny the request based on their Jira user permissions. We won't cover that in this guide as it mostly relates to Jira settings. By default Teleport will allow anyone who can use the workflow to approve or deny the request. +With the Jira workflow editor, you can set up who can approve or deny an access reuqest based on their Jira user permissions. We won't cover that in this guide as it mostly relates to Jira settings. By default Teleport will allow anyone who can use the workflow to approve or deny the request. -Go to your Project Settings -> Workflows, and make sure that your workflow that you just created or edited is applied to the project you'll use for Teleport integration. +Go to your Project Settings -> Workflows, and make sure that the workflow that you just created or edited is applied to the project you'll use for Teleport integration. ### Setting up the webhook -Teleport Jira Plugin will listen for a webhook that Jira Server sends when a request is approved or denied. Go to Settings -> System -> Webhooks to setup the webhook. The webhook needs to be sent when issues are updated or deleted. +Teleport Jira Plugin will listen for a webhook that Jira Server sends when a request is approved or denied. Go to Settings -> System -> Webhooks to set up the webhook. The webhook needs to be sent when issues are updated or deleted. -#### Configuring the Teleport Jira Plugin for Jira Server ## Installing -We recommend installing the Teleport Plugins alongside the Teleport Proxy. This is an ideal +We recommend installing Teleport plugins alongside the Teleport Proxy. This is an ideal location as plugins have a low memory footprint, and will require both public internet access -and Teleport Auth access. We currently only provide linux-amd64 binaries, you can also +and Teleport Auth access. We currently only provide linux-amd64 binaries, you can also compile these plugins from [source](https://github.com/gravitational/teleport-plugins/tree/master/access/jira). ```code @@ -153,9 +108,10 @@ $ which teleport-jira # /usr/local/bin/teleport-jira ``` -Run `sudo ./install` in from 'teleport-jira' or place the executable in the appropriate `/usr/bin` or `/usr/local/bin` on the server installation. +Run `sudo ./install` from 'teleport-jira' or place the executable in the appropriate `/usr/bin` or `/usr/local/bin` on the server installation. -### Configuration file + +## Configuration file Teleport Jira Plugin uses a config file in TOML format. Generate a boilerplate config by running the following command: @@ -165,22 +121,31 @@ $ teleport-jira configure > teleport-jira.toml $ sudo mv teleport-jira.toml /etc ``` -By default, Jira Teleport Plugin will use a config in `/etc/teleport-jira.toml`, and you can override it with `-c config/file/path.toml` flag. +By default, the Jira Teleport plugin will use a config in `/etc/teleport-jira.toml`, and you can override it with `-c config/file/path.toml` flag. + + +```toml +(!examples/resources/plugins/teleport-jira-self-hosted.toml!) +``` + + ```toml -(!examples/resources/plugins/teleport-jira.toml!) +(!examples/resources/plugins/teleport-jira-cloud.toml!) ``` + + -The `[teleport]` section describes where is the teleport service running, and what keys should the plugin use to authenticate itself. Use the keys that you've generated [above in exporting your Certificate section](#export-access-plugin-certificate). +The `[teleport]` section describes where the Teleport service is running, and what keys the plugin should use to authenticate itself. Use the keys that you've generated [above](#export-access-plugin-certificate). The `[jira]` section requires a few things: -1. Your Jira Cloud or Jira Server URL. For Jira Cloud, it looks something like yourcompany.atlassian.net. +1. Your Jira Cloud or Jira Server URL. For Jira Cloud, it looks something like `yourcompany.atlassian.net`. 2. Your username on Jira, i.e. benarent **Note: Not your email address.** -3. Your Jira API token. **For Jira Server, this is a password. it's a good idea to create a separate user record with permissions limited to accessing this particular project board, and use this with the bot.** +3. Your Jira API token. **For Jira Server, this is a password. It's a good idea to create a separate user record with permissions limited to accessing this particular project board, and use this with the bot.** 4. And the Jira Project key, available in Project settings. -`[http]` setting block describes how the Plugin's HTTP server works. The HTTP server is responsible for listening for updates from Jira, and processing updates, like when someone drags a task from Inbox to Approved column. +The `[http]` setting block describes how the Plugin's HTTP server works. The HTTP server is responsible for listening for updates from Jira, and processing updates, like when someone drags a task from Inbox to Approved column. You must provide an address the server should listen on, and a certificate to use, unless you plan on running with `--insecure-no-tls`, which we don't recommend in production. @@ -201,7 +166,7 @@ $ teleport-jira start # DEBU Watcher connected access/service_job.go:62 ``` -The log output should look familiar to what Teleport service logs. You should see that it connected to Teleport, and is listening for new Teleport requests and Jira webhooks. +The log output should look familiar to what the Teleport service logs. You should see that it connected to Teleport and is listening for new Teleport requests and Jira webhooks. Go ahead and test it: @@ -211,10 +176,10 @@ $ tsh login --request-roles=admin That should create a new permission request on Teleport (you can test if it did with `tctl request ls` ), and you should see a new task on your Jira project board. -### Setup with SystemD +### Set up systemd -In production, we recommend starting teleport plugin daemon via an init system like systemd . -Here's the recommended Teleport Plugin service unit file for systemd: +In production, we recommend starting the Teleport plugin daemon via an init system like systemd. +Here's the recommended Teleport plugin service unit file for systemd: ```ini (!examples/systemd/plugins/teleport-jira.service!) @@ -222,7 +187,7 @@ Here's the recommended Teleport Plugin service unit file for systemd: Save this as `teleport-jira.service`. -## Audit Log +## Audit log The plugin will let anyone with access to the Jira board approve or deny requests, so it's important to review Teleport's audit log. diff --git a/docs/pages/enterprise/workflow/ssh-approval-mattermost.mdx b/docs/pages/enterprise/workflow/ssh-approval-mattermost.mdx index cc2ceff979154..48439b8c0f9f6 100644 --- a/docs/pages/enterprise/workflow/ssh-approval-mattermost.mdx +++ b/docs/pages/enterprise/workflow/ssh-approval-mattermost.mdx @@ -4,11 +4,9 @@ description: This guide explains how to setup a Mattermost plugin for Teleport f h1: Teleport Mattermost Plugin Setup --- -This guide will talk through how to setup Teleport with Mattermost. Teleport to -Mattermost integration allows teams to approve or deny Teleport access requests -using [Mattermost](https://mattermost.com/) an open source messaging platform. +This guide will explain how to set up Teleport with Mattermost, an open source messaging platform. Teleport's Mattermost integration allows teams to approve or deny Teleport Access Requests using Mattermost. -#### Example Mattermost Request +#### Example Mattermost request
    +
    -Verify that your Teleport client is connected: +Verify that your Teleport client is connected by running the following on your +Auth Service host: ```code $ tctl status - # Cluster tele.example.com # Version (=teleport.version=) # CA pin sha256:sha-hash-here ``` +
    +
    - -To try this flow in the cloud, login into your cluster using `tsh`, then use `tctl` remotely: +To connect to Teleport, log in to your cluster using `tsh`, then use `tctl` +remotely: ```code -$ tsh login --proxy=myinstance.teleport.sh +$ tsh login --proxy=myinstance.teleport.sh --user=email@example.com $ tctl status +# Cluster myinstance.teleport.sh +# Version (=teleport.version=) +# CA pin sha256:sha-hash-here ``` - -
    diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx index e579c391413ab..629dee1ca40ce 100644 --- a/docs/pages/index.mdx +++ b/docs/pages/index.mdx @@ -10,7 +10,7 @@ videoBanner: 0HlyGk8dihM Teleport is a Certificate Authority and an Access Plane for your infrastructure. With Teleport you can: -- Set up Single Sign-On and have one place to access your SSH servers, Kubernetes, Databases, and Web Apps. +- Set up Single Sign-On and have one place to access your SSH servers, Kubernetes, Databases, Desktops, and Web Apps. - Use your favorite programming language to define access policies to your infrastructure. - Share and record interactive sessions across all environments. @@ -61,7 +61,7 @@ Teleport is a Certificate Authority and an Access Plane for your infrastructure. Replace static keys and passwords with short-lived certificates. - Gather structured events and session recording/replay for `ssh` and `kubectl`. + Gather structured events and session recording/replay for `ssh`, `kubectl` and desktop interactions. Enforce two-factor auth with WebAuthn or TOTP. diff --git a/docs/pages/installation.mdx b/docs/pages/installation.mdx index e8af47936d980..ffb1799e12da0 100644 --- a/docs/pages/installation.mdx +++ b/docs/pages/installation.mdx @@ -6,6 +6,11 @@ h1: Installation Teleport core service [`teleport`](./setup/reference/cli.mdx#teleport) and admin tool [`tctl`](./setup/reference/cli.mdx#tctl) have been designed to run on **Linux** and **Mac** operating systems. The Teleport user client [`tsh`](./setup/reference/cli.mdx#tsh) and UI are available for **Linux, Mac**, and **Windows** operating systems. + +If you are new to Teleport, we recommend following our [getting started guides](getting-started.mdx). + + + ## Linux The following examples install the 64-bit version of Teleport binaries, but @@ -176,3 +181,11 @@ any OS supported by the [Golang toolchain](https://github.com/golang/go/wiki/Min \[2] *Teleport server does not run on Windows yet, but `tsh` (the Teleport client) supports most features on Windows 10 and later.* + +## Next steps + +Now that you know how to install Teleport, you can enable access to all of your infrastructure. Get started with: +- [Server Access](server-access/introduction.mdx) +- [Kubernetes Access](kubernetes-access/introduction.mdx) +- [Database Access](database-access/introduction.mdx) +- [Desktop Access](desktop-access/introduction.mdx) \ No newline at end of file diff --git a/docs/pages/kubernetes-access/controls.mdx b/docs/pages/kubernetes-access/controls.mdx index e54f38de256d3..25655b2f53a2a 100644 --- a/docs/pages/kubernetes-access/controls.mdx +++ b/docs/pages/kubernetes-access/controls.mdx @@ -88,7 +88,7 @@ Enterprise Edition: Identity provider admins can assign metadata to a user, such as group membership or access permissions. Administrators configure what metadata is shared with Teleport. Teleport receives user metadata keys and values as OIDC claims or SAML -attributes during [signle sign-on redirect flow](https://goteleport.com/blog/how-oidc-authentication-works/): +attributes during [single sign-on redirect flow](https://goteleport.com/blog/how-oidc-authentication-works/): ```yaml # Alice has an email alice@example.com. Email is a standard OIDC claim. diff --git a/docs/pages/kubernetes-access/getting-started/agent.mdx b/docs/pages/kubernetes-access/getting-started/agent.mdx index d517cffbc1c74..f76f99cc6b20f 100644 --- a/docs/pages/kubernetes-access/getting-started/agent.mdx +++ b/docs/pages/kubernetes-access/getting-started/agent.mdx @@ -3,35 +3,86 @@ title: Connect a Kubernetes Cluster to Teleport description: Connecting a Kubernetes cluster to Teleport --- - -You can use this guide with Teleport Open Source, Teleport Enterprise, and Teleport Cloud. - - ## Prerequisites -- Installed and running Teleport cluster, self-hosted or cloud-hosted. -- Tool `jq` to process `JSON` output. + + + +- A running Teleport cluster. For details on how to set this up, see one of our + [Getting Started](../../getting-started.mdx) guides. + +- The `jq` tool to process `JSON` output. This is available via common package managers. + +- The `tctl` admin tool version >= (=teleport.version=). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + See [Installation](../../installation.mdx) for details. + + (!docs/pages/includes/tctl.mdx!) + + + + +- A running Teleport cluster. For details on how to set this up, see one of our + [Getting Started](../../getting-started.mdx) guides. + +- The `jq` tool to process `JSON` output. This is available via common package managers. + +- The `tctl` admin tool version >= (=teleport.version=), which you can download + by visiting the + [customer portal](https://dashboard.gravitational.com/web/login). + + ```code + $ tctl version + # Teleport Enterprise v(=teleport.version=) go(=teleport.golang=) + ``` + + (!docs/pages/includes/tctl.mdx!) + + + + +- A Teleport Cloud account. If you do not have one, visit the + [sign up page](https://goteleport.com/signup/) to begin your free trial. + +- The `jq` tool to process `JSON` output. This is available via common package + managers. + +- The Enterprise version of the `tctl` admin tool. To download this, visit +the [customer portal](https://dashboard.gravitational.com/web/login). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + (!docs/pages/includes/tctl.mdx!) + + + (!docs/pages/includes/kubernetes-access/helm-k8s.mdx!) (!docs/pages/includes/tctl.mdx!) - -For self-hosted Teleport instances the `kube_listen_addr` setting in the `proxy_service` is required to enable Kubernetes Access. This is already enabled for Cloud and the Teleport `teleport-cluster` helm chart. -```yaml -proxy_service: - # ... - public_addr: proxy.example.com:3080 +## Deployment overview - kube_listen_addr: 0.0.0.0:3026 - ``` - +In this guide, we deploy the Teleport Kubernetes Service, which connects +Kubernetes cluster `cookie` to Teleport cluster `tele.example.com`: -## Deployment overview + -In this guide, we deploy a Teleport agent that connects kubernetes cluster `cookie` to -Teleport cluster `tele.example.com`: +In your Teleport Cloud account, the name of your cluster will be your tenant +domain name, e.g., `mytenant.teleport.sh`, rather than `teleport.example.com`. + +
    ![Kubernetes agent](../../../img/k8s/agent.svg) @@ -39,31 +90,58 @@ Teleport cluster `tele.example.com`: ## Step 1/2. Get a join token -Start a lightweight agent in your Kubernetes cluster `cookie` and connect it to `tele.example.com`. -We would need a join token from `tele.example.com`: +In order to start the Teleport Kubernetes Service, we will need to request a +join token from the Teleport Auth Service: ```code -# Create a join token for the cluster cookie to authenticate +# Create a join token for the Teleport Kubernetes Service to authenticate $ TOKEN=$(tctl nodes add --roles=kube --ttl=10000h --format=json | jq -r '.[0]') $ echo $TOKEN ``` ## Step 2/2. Deploy teleport-kube-agent -Switch `kubectl` to the Kubernetes cluster `cookie` and run: + + + + +Switch `kubectl` to the Kubernetes cluster `cookie` and run the following +commands, assigning `PROXY_ADDR` to the address of your Auth Service or Proxy +Service. + +```code +# Add teleport-agent chart to charts repository +$ PROXY_ADDR=tele.example.com:443 +$ helm repo add teleport https://charts.releases.teleport.dev +$ helm repo update + +# Install Kubernetes agent. It dials back to the Teleport cluster at $PROXY_ADDR +$ CLUSTER='cookie' +$ helm install teleport-agent teleport/teleport-kube-agent --set kubeClusterName=${CLUSTER?} \ + --set proxyAddr=${PROXY_ADDR?} --set authToken=${TOKEN?} --create-namespace --namespace=teleport-agent +``` + + + + +Switch `kubectl` to the Kubernetes cluster `cookie` and run the following +commands, assigning `PROXY_ADDR` to the address of your Teleport Cloud tenant. ```code # Add teleport-agent chart to charts repository +$ PROXY_ADDR=mytenant.teleport.sh $ helm repo add teleport https://charts.releases.teleport.dev $ helm repo update -# Install Kubernetes agent. It dials back to the Teleport cluster tele.example.com. +# Install Kubernetes agent. It dials back to the Teleport cluster at $PROXY_ADDR $ CLUSTER='cookie' -$ PROXY='tele.example.com:443 - replace me with your cluster' $ helm install teleport-agent teleport/teleport-kube-agent --set kubeClusterName=${CLUSTER?} \ - --set proxyAddr=${PROXY?} --set authToken=${TOKEN?} --create-namespace --namespace=teleport-agent + --set proxyAddr=${PROXY_ADDR?} --set authToken=${TOKEN?} --create-namespace --namespace=teleport-agent ``` + + + List connected clusters using `tsh kube ls` and switch between them using `tsh kube login`: @@ -78,10 +156,10 @@ $ tsh kube ls $ tsh kube login cookie # Logged into kubernetes cluster "cookie" -# kubectl command executed on `cookie` but is routed through `tele.example.com` cluster. +# kubectl command executed on `cookie` but is routed through the Teleport cluster. $ kubectl get pods ``` ## Next Steps -- Take a look at a [kube-agent helm chart reference](../helm/reference.mdx#teleport-kube-agent) for a full list of parameters. +- Take a look at a [kube-agent helm chart reference](../helm/reference/teleport-kube-agent.mdx) for a full list of parameters. diff --git a/docs/pages/kubernetes-access/getting-started/cluster.mdx b/docs/pages/kubernetes-access/getting-started/cluster.mdx index b73d91f7f236d..f75e7e9764118 100644 --- a/docs/pages/kubernetes-access/getting-started/cluster.mdx +++ b/docs/pages/kubernetes-access/getting-started/cluster.mdx @@ -66,7 +66,7 @@ Let's start with a single-pod Teleport deployment using a persistent volume as a ``` - + ```code $ CLUSTER_NAME="tele.example.com" $ EMAIL="mail@example.com" @@ -115,7 +115,7 @@ to create a public IP for Teleport. ``` - + ```code # Set kubectl context to the namespace to set some typing $ kubectl config set-context --current --namespace=teleport-cluster-ent @@ -139,45 +139,7 @@ to create a public IP for Teleport. -Set up two `A` DNS records - `tele.example.com` for UI and `*.tele.example.com` -for web apps using [application access](../../application-access/introduction.mdx). - - - - ```code - $ MYZONE="myzone" - $ MYDNS="tele.example.com" - - $ gcloud dns record-sets transaction start --zone="${MYZONE?}" - $ gcloud dns record-sets transaction add ${MYIP?} --name="${MYDNS?}" --ttl="30" --type="A" --zone="${MYZONE?}" - $ gcloud dns record-sets transaction add ${MYIP?} --name="*.${MYDNS?}" --ttl="30" --type="A" --zone="${MYZONE?}" - $ gcloud dns record-sets transaction describe --zone="${MYZONE?}" - $ gcloud dns record-sets transaction execute --zone="${MYZONE?}" - ``` - - - - ```code - # Tip for finding AWS zone id by the domain name. - $ MYZONE_DNS="example.com" - $ MYZONE=$(aws route53 list-hosted-zones-by-name --dns-name=${MYZONE_DNS?} | jq -r '.HostedZones[0].Id' | sed s_/hostedzone/__) - - $ MYDNS="tele.example.com" - - # Create a JSON file changeset for AWS. - $ jq -n --arg ip ${MYIP?} --arg dns ${MYDNS?} '{"Comment": "Create records", "Changes": [{"Action": "CREATE","ResourceRecordSet": {"Name": $dns, "Type": "A", "TTL": 300, "ResourceRecords": [{ "Value": $ip}]}},{"Action": "CREATE", "ResourceRecordSet": {"Name": ("*." + $dns), "Type": "A", "TTL": 300, "ResourceRecords": [{ "Value": $ip}]}}]}' > myrecords.json - - # Review records before applying. - $ cat myrecords.json | jq - # Apply the records and capture change id - $ CHANGEID=$(aws route53 change-resource-record-sets --hosted-zone-id ${MYZONE?} --change-batch file://myrecords.json | jq -r '.ChangeInfo.Id') - - # Verify that change has been applied - $ aws route53 get-change --id ${CHANGEID?} | jq '.ChangeInfo.Status' - # "INSYNC" - ``` - - +(!docs/pages/includes/dns.mdx!) Use the following command to confirm that Teleport is running: @@ -202,6 +164,8 @@ metadata: spec: allow: kubernetes_groups: ["system:masters"] + kubernetes_labels: + '*': '*' ``` Create the role and add a user: @@ -237,7 +201,7 @@ For other install options, check out [install guide](../../installation.mdx) ``` - + ```code $ curl -L -O https://get.gravitational.com/teleport-ent-v(=teleport.version=)-linux-amd64-bin.tar.gz $ tar -xzf teleport-ent-v(=teleport.version=)-linux-amd64-bin.tar.gz @@ -306,7 +270,7 @@ In this step, we will set up the GitHub Single Sign-On connector for the OSS ver ``` - + Follow the [SAML Okta Guide](../../enterprise/sso/okta.mdx#configure-okta) to create a SAML app. Check out [OIDC guides](../../enterprise/sso/oidc.mdx#identity-providers) for OpenID Connect apps. Save the file below as `okta.yaml` and update the `acs` field. @@ -341,7 +305,7 @@ To create a connector, we are going to run Teleport's admin tool `tctl` from the ``` - + ```code # To create an Okta connector, we are going to run Teleport's admin tool tctl from the pod. $ POD=$(kubectl get po -l app=teleport-cluster-ent -o jsonpath='{.items[0].metadata.name}') @@ -362,7 +326,7 @@ the default one in case there is a problem. ``` - + ```code $ KUBECONFIG=${HOME?}/teleport.yaml tsh login --proxy=tele.example.com --auth=okta ``` diff --git a/docs/pages/kubernetes-access/guides.mdx b/docs/pages/kubernetes-access/guides.mdx index 6c47b0cced1db..c28ec8d4ef363 100644 --- a/docs/pages/kubernetes-access/guides.mdx +++ b/docs/pages/kubernetes-access/guides.mdx @@ -11,9 +11,6 @@ layout: tocless-doc Federated Access using Teleport Trusted Clusters. - - Kubernetes Pre-5.0 Teleport Migration Guide. - Connecting Multiple Clusters with Teleport Kubernetes Access. diff --git a/docs/pages/kubernetes-access/guides/federation.mdx b/docs/pages/kubernetes-access/guides/federation.mdx index f28e5d63019c4..48b7bd010723d 100644 --- a/docs/pages/kubernetes-access/guides/federation.mdx +++ b/docs/pages/kubernetes-access/guides/federation.mdx @@ -1,17 +1,18 @@ --- -title: Teleport Kubernetes Access and Trusted Clusters +title: Federated Kubernetes access with Trusted Clusters description: Federated Access using Teleport Trusted Clusters. --- -## Federated Kubernetes access with Trusted Clusters - There are cases when you have Kubernetes clusters that have to operate independently, for example, they are part of a different organization or have intermittent connectivity. You can take advantage of [Trusted Clusters](../../setup/admin/trustedclusters.mdx) to federate trust across Kubernetes clusters. -When multiple trusted clusters are present behind a Teleport proxy, the + + + +When multiple Trusted Clusters are present behind the Teleport Proxy Service, the `kubeconfig` generated by [tsh login](../../setup/reference/cli.mdx#tsh-login) will contain the Kubernetes API endpoint determined by the `` argument to [tsh login](../../setup/reference/cli.mdx#tsh-login). @@ -19,10 +20,10 @@ login](../../setup/reference/cli.mdx#tsh-login). For example, consider the following setup: - There are three Teleport/Kubernetes clusters: `main`, `east`, and `west`. These are the names set in `cluster_name` setting in their configuration files. -- The clusters `east` and `west` are trusted clusters for `main`. +- The clusters `east` and `west` are Trusted Clusters for `main`. - Users always authenticate against `main` but use their certificates to access - SSH nodes and Kubernetes API in all three clusters. -- The DNS name of the main proxy server is `main.example.com` + SSH nodes and the Kubernetes API in all three clusters. +- The DNS name of the main Proxy Service is `main.example.com`. In this scenario, users usually log in using this command: @@ -39,3 +40,37 @@ $ tsh --proxy=main.example.com login east # User's `kubeconfig` now contains the entry for the "east" Kubernetes # endpoint, i.e. `east.main.example.com`. ``` + + + + +When multiple Trusted Clusters are present behind the Teleport Proxy Service, the +`kubeconfig` generated by [tsh login](../../setup/reference/cli.mdx#tsh-login) will contain the +Kubernetes API endpoint determined by the `` argument to [tsh +login](../../setup/reference/cli.mdx#tsh-login). + +For example, consider the following setup: + +- There are two Teleport/Kubernetes clusters, `east` and `west`. These are the names set in `cluster_name` setting in their configuration files. +- The clusters `east` and `west` are Trusted Clusters for a Teleport Cloud tenant, `mytenant.teleport.sh`. +- Users always authenticate against `mytenant.teleport.sh` but use their certificates to access + SSH nodes and the Kubernetes API in all three clusters. + +In this scenario, users usually log in using this command: + +```code +# Using login without arguments +$ tsh --proxy=mytenant.teleport.sh login + +# User's `kubeconfig` now contains one entry for the main Kubernetes +# endpoint, i.e. `mytenant.teleport.sh`. + +# Receive a certificate for "east": +$ tsh --proxy=mytenant.teleport.sh login east + +# User's `kubeconfig` now contains the entry for the "east" Kubernetes +# endpoint, i.e. `east.mytenant.teleport.sh`. +``` + + + diff --git a/docs/pages/kubernetes-access/guides/migration.mdx b/docs/pages/kubernetes-access/guides/migration.mdx deleted file mode 100644 index a0d41459e167f..0000000000000 --- a/docs/pages/kubernetes-access/guides/migration.mdx +++ /dev/null @@ -1,392 +0,0 @@ ---- -title: Kubernetes 5.0 Migration Guide -description: How to migrate pre-5.0 Teleport clusters to new configuration. -h1: Migrating to Teleport Kubernetes Access 5.0 ---- - -In release 5.0, Teleport has changed the [Kubernetes integration](../../kubernetes-access/introduction.mdx) to improve configuration and user experience. -These changes are backward compatible and there is no *required* migration. -However, to get the most out of Teleport, you should consider migrating as -described below. - -The main changes in 5.0 are: - -- New `kubernetes_service` configuration section in `teleport.yaml`, decoupled - from `proxy_service` -- Support for multiple Kubernetes clusters per Teleport cluster (replaces the - need to use [Trusted Clusters](../../setup/admin/trustedclusters.mdx) to achieve this) -- Role-Based Access Control (RBAC) support for Kubernetes clusters - -Important note: `proxy_service` with an open port for Kubernetes requests is -still required. A proxy is always the public-facing gateway into the Teleport -cluster, acting as an authentication point and a connection router. - -## Example scenarios - -We'll describe the migration for several common Teleport configurations. - -### Standalone proxy for a single cluster - -In this scenario, we're running a Teleport proxy outside of the Kubernetes -cluster and connecting to it using a `kubeconfig_file`. - -Before 5.0: - -```yaml -# teleport.yaml -proxy_service: - kubernetes: - enabled: yes - listen_addr: 0.0.0.0:3026 - kubeconfig_file: /path/to/kubeconfig -``` - -After 5.0: - -```yaml -# teleport.yaml -proxy_service: - kube_listen_addr: 0.0.0.0:3026 - -kubernetes_service: - enabled: yes - listen_addr: 0.0.0.0:3027 - kubeconfig_file: /path/to/kubeconfig -``` - -The `listen_port` for `kubernetes_service` only needs to be exposed to the -Proxies within your cluster. This port will not be directly used by the -clients. - -### Proxy in a Kubernetes pod of a single cluster - -In this scenario, we're running a Teleport proxy inside of the Kubernetes -cluster and connecting to it using the pod service account credentials. - -Before 5.0: - -```yaml -# teleport.yaml -proxy_service: - kubernetes: - enabled: yes - listen_addr: 0.0.0.0:3026 -``` - -After 5.0: - -```yaml -# teleport.yaml -proxy_service: - kube_listen_addr: 0.0.0.0:3026 - -kubernetes_service: - enabled: yes - listen_addr: 0.0.0.0:3027 -``` - -### Multiple Kubernetes clusters - -In this scenario, we're running multiple Trusted Teleport clusters, one for -each Kubernetes cluster. - -#### Recommend Option 1: Teleport Kubernetes agents in each Kubernetes cluster - -This is the recommended approach for handling multiple Kubernetes clusters. - -Instead of running multiple Teleport clusters (auth and proxy for each -Kubernetes cluster), we will connect multiple `kubernetes_service` agents to -the same root Teleport cluster. - -Each `kubernetes_service` will create its own reverse tunnel back to a Teleport -proxy to join the cluster. - -Before 5.0: - -```yaml -# teleport-root.yaml -auth_service: - enabled: yes - -proxy_service: - kubernetes: - enabled: yes - public_addr: [root.example.com:3026] - listen_addr: 0.0.0.0:3026 - ---- -# teleport-leaf-1.yaml -auth_service: - enabled: yes - -proxy_service: - kubernetes: - enabled: yes - public_addr: [leaf-1.example.com:3026] - listen_addr: 0.0.0.0:3026 - # Not specifying kubeconfig_file, we'll assume that this process runs in a - # Kubernetes pod - ---- -# teleport-leaf-2.yaml -auth_service: - enabled: yes - -proxy_service: - kubernetes: - enabled: yes - public_addr: [leaf-2.example.com:3026] - listen_addr: 0.0.0.0:3026 - # Not specifying kubeconfig_file, we'll assume that this process runs in a - # Kubernetes pod -``` - -After 5.0: - -```yaml -# teleport-root.yaml -auth_service: - enabled: yes - tokens: - # This is a static token, you can also use dynamic join tokens - - kube:secret_static_join_token - -proxy_service: - kube_public_addr: [root.example.com] - kube_listen_addr: 0.0.0.0:3026 - ---- -# teleport-kube-1.yaml -teleport: - # Note: auth_servers points at the default proxy listening port. - auth_servers: ["https://root.example.com:3080"] - auth_token: secret_static_join_token - -auth_service: - enabled: no - -proxy_service: - enabled: no - -kubernetes_service: - enabled: yes - kube_cluster_name: kube-1 - # Not specifying kubeconfig_file, we'll assume that this process runs in a - # Kubernetes pod - ---- -# teleport-kube-2.yaml -teleport: - # Note: auth_servers points at the default proxy listening port. - auth_servers: ["https://root.example.com:3080"] - auth_token: secret_static_join_token - -auth_service: - enabled: no - -proxy_service: - enabled: no - -kubernetes_service: - enabled: yes - kube_cluster_name: kube-2 - # Not specifying kubeconfig_file, we'll assume that this process runs in a - # Kubernetes pod -``` - -After all the Teleport agents are running, you can view the list of Kubernetes -clusters using `tsh kube ls` and switch between them using `tsh kube login `: - -```code -$ tsh kube ls - -# Kube Cluster Name Selected -# ----------------- -------- -# kube-1 * -# kube-2 - -$ tsh kube login kube-2 -# Logged into kubernetes cluster "kube-2" -``` - -#### Option 2: One Teleport agent connected to multiple clusters - -This approach requires you to manually craft a `kubeconfig` file that contains -the exact set of Kubernetes cluster entries you wish to connect. Updates to the -set of clusters require a restart. - -Instead of running multiple Teleport clusters (auth and proxy for each -Kubernetes cluster), we will connect multiple Kubernetes clusters to a single -`kubernetes_service` using `kubeconfig_file`. - -Before 5.0: - -```yaml -# teleport-root.yaml -auth_service: - enabled: yes - -proxy_service: - kubernetes: - enabled: yes - public_addr: [root.example.com:3026] - listen_addr: 0.0.0.0:3026 - ---- -# teleport-leaf-1.yaml -auth_service: - enabled: yes - -proxy_service: - kubernetes: - enabled: yes - public_addr: [leaf-1.example.com:3026] - listen_addr: 0.0.0.0:3026 - kubeconfig_file: kubeconfig-leaf-1.yaml - ---- -# teleport-leaf-2.yaml -auth_service: - enabled: yes - -proxy_service: - kubernetes: - enabled: yes - public_addr: [leaf-2.example.com:3026] - listen_addr: 0.0.0.0:3026 - kubeconfig_file: kubeconfig-leaf-2.yaml -``` - -After 5.0: - -```yaml -# teleport.yaml -proxy_service: - kube_public_addr: [root.example.com] - kube_listen_addr: 0.0.0.0:3026 - -kubernetes_service: - enabled: yes - listen_addr: 0.0.0.0:3027 - kubeconfig_file: kubeconfig-all.yaml - ---- -# kubeconfig-all.yaml -apiVersion: v1 -kind: Config -clusters: -- cluster: - certificate-authority-data: ... - server: https://10.0.0.1 - name: kube-1-server -- cluster: - certificate-authority-data: ... - server: https://10.0.0.2 - name: kube-2-server -contexts: -- context: - cluster: kube-1-server - user: kube-1-creds - name: kube-1 -- context: - cluster: kube-2-server - user: kube-2-creds - name: kube-2 -current-context: kube-1 -users: -- name: kube-1-creds - user: - ...cluster-specific credentials... -- name: kube-2-creds - user: - ...cluster-specific credentials... -``` - -You can view the list of Kubernetes clusters using `tsh kube ls` and switch -between them using `tsh kube login `: - -```code -$ tsh kube ls - -# Kube Cluster Name Selected -# ----------------- -------- -# kube-1 * -# kube-2 - -$ tsh kube login kube-2 -``` - -#### Option 3: Keep using Trusted Clusters - -You may keep using the Trusted Clusters as before and migrate them internally -to `kubernetes_service`, as described in the first two scenarios. - -Note that `tsh kube ls` will **not** show all Kubernetes clusters across all -Trusted Clusters. It only shows the Kubernetes clusters from the Teleport -cluster you are currently logged into. - -## RBAC - - - Kubernetes RBAC integration only works with `kubernetes_service`. Make sure you've migrated all the clusters as described above before proceeding. - - -By default, all users have access to all Kubernetes clusters (as a limited set -of Kubernetes user/group identities). In 5.0, you can restrict user access to a -subset of Kubernetes clusters using Kubernetes cluster labels and Teleport -roles. - -This RBAC integration works exactly like the [integration for SSH nodes](../../access-controls/reference.mdx). - -First, set the set of Kubernetes labels a user can access on their roles: - -```yaml -# role-admin.yaml -kind: role -version: v5 -metadata: - name: admin -spec: - allow: - logins: ['{{internal.logins}}'] - # Define the Kubernetes users and groups mapped to users with this role. - kubernetes_users: ["{{internal.kubernetes_users}}"] - kubernetes_groups: ["some-group", "{{internal.kubernetes_groups}}"] - - # List of kubernetes labels a user will be allowed to connect to: - kubernetes_labels: - # A user can only connect to clusters marked with 'test' label: - 'environment': 'test' - # The wildcard ('*') means "any cluster" - '*': '*' - # Labels can be specified as a list: - 'environment': ['test', 'staging'] - # Regular expressions are also supported, for example the equivalent - # of the list example above can be expressed as: - 'environment': '^test|staging$' - # You can also strictly restrict access using deny kubernetes_labels. - deny: {} -``` - -Next, define Kubernetes labels on the cluster side: - -```yaml -# teleport.yaml -kubernetes_service: - enabled: yes - listen_addr: 0.0.0.0:3027 - kube_cluster_name: staging - # Note: if you use kubeconfig_file, the below labels apply to all Kubernetes - # clusters in that kubeconfig. To get different sets of labels, you need to - # run separate kubernetes_service instances. - labels: - environment: staging - commands: - - name: project - command: [fetch-project-name.sh] - period: 1h -``` - -Users with a role that matches a specific Kubernetes cluster label will be -able to see and connect to that cluster. Other users will neither see nor -be able to connect to it. diff --git a/docs/pages/kubernetes-access/guides/multiple-clusters.mdx b/docs/pages/kubernetes-access/guides/multiple-clusters.mdx index 37e15817bc992..f4e71cb4aed1a 100644 --- a/docs/pages/kubernetes-access/guides/multiple-clusters.mdx +++ b/docs/pages/kubernetes-access/guides/multiple-clusters.mdx @@ -7,18 +7,84 @@ This guide will show you how to use Teleport as an access plane for multiple Kub ## Prerequisites -- [Kubernetes](https://kubernetes.io) >= v(=kubernetes.major_version=).(=kubernetes.minor_version=).0 -- [Helm](https://helm.sh) >= (=helm.version=) -- Installed and running Teleport cluster (Open Source, Enterprise, or Teleport Cloud) + + + +- A Teleport cluster running on Kubernetes, version >= + v(=kubernetes.major_version=).(=kubernetes.minor_version=).0. We will assume + that you have followed the + [Kubernetes with SSO](../getting-started/cluster.mdx) guide +- The `jq` tool to process `JSON` output. This is available via common package managers +- An additional Kubernetes cluster version >= + v(=kubernetes.major_version=).(=kubernetes.minor_version=).0 +- Helm >= (=helm.version=) (!docs/pages/includes/helm.mdx!) + + + + +- A Teleport cluster running on Kubernetes, version >= + v(=kubernetes.major_version=).(=kubernetes.minor_version=).0. We will assume + that you have followed the + [Kubernetes with SSO](../getting-started/cluster.mdx) guide +- The `jq` tool to process `JSON` output. This is available via common package managers +- An additional Kubernetes cluster version >= + v(=kubernetes.major_version=).(=kubernetes.minor_version=).0 +- Helm >= (=helm.version=) + +(!docs/pages/includes/helm.mdx!) + + + + + +- A Teleport Cloud account. If you do not have one, visit the + [sign up page](https://goteleport.com/signup/) to begin your free trial + +- The Teleport Kubernetes Service running in a Kubernetes cluster, version >= + v(=kubernetes.major_version=).(=kubernetes.minor_version=).0. We will assume + that you have already followed + [Connect a Kubernetes Cluster to Teleport](../getting-started/agent.mdx) + +- The `jq` tool to process `JSON` output. This is available via common package + managers + +- The Enterprise version of the `tctl` admin tool. To download this, visit +the [customer portal](https://dashboard.gravitational.com/web/login) + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + (!docs/pages/includes/tctl.mdx!) + +- An additional Kubernetes cluster version >= + v(=kubernetes.major_version=).(=kubernetes.minor_version=).0 + +- Helm >= (=helm.version=) + +(!docs/pages/includes/helm.mdx!) + + + + + ## Connecting clusters + + + Teleport can act as an access plane for multiple Kubernetes clusters. -We have set up the Teleport cluster `tele.example.com` in [SSO and Kubernetes](../getting-started.mdx). -Let's start a lightweight agent in another Kubernetes cluster `cookie` and connect it to `tele.example.com`. +We will assume that the domain of your Teleport cluster is `tele.example.com`. + +Let's start a lightweight agent in another Kubernetes cluster `cookie` and +connect it to `tele.example.com`. We will need a join token from `tele.example.com`: @@ -33,12 +99,11 @@ $ echo $TOKEN Switch `kubectl` to the Kubernetes cluster `cookie` and run: ```code -# Add teleport chart repository +# Add Teleport chart repository $ helm repo add teleport https://charts.releases.teleport.dev # Deploy a Kubernetes agent. It dials back to the Teleport cluster tele.example.com. $ CLUSTER='cookie' -# For Cloud users this will be similar to mytenant.teleport.sh $ PROXY='tele.example.com:443' $ helm install teleport-agent teleport/teleport-kube-agent --set kubeClusterName=${CLUSTER?} \ --set proxyAddr=${PROXY?} --set authToken=${TOKEN?} --create-namespace --namespace=teleport-agent @@ -57,8 +122,61 @@ $ tsh kube ls # kubeconfig now points to the cookie cluster $ tsh kube login cookie -# Logged into kubernetes cluster "cookie" +# Logged into Kubernetes cluster "cookie" -# kubectl command executed on `cookie` but is routed through `tele.example.com` cluster. +# kubectl command executed on `cookie` but is routed through the `tele.example.com` cluster. $ kubectl get pods ``` + + + + +Teleport can act as an access plane for multiple Kubernetes clusters. + +We will assume that the domain of your Teleport cluster is `mytenant.teleport.sh`. + +Let's start a lightweight agent in another Kubernetes cluster `cookie` and +connect it to `mytenant.teleport.sh`. + +We will need a join token from `mytenant.teleport.sh`: + +```code +# Create a join token for the cluster cookie to authenticate +$ TOKEN=$(tctl nodes add --roles=kube --ttl=10000h --format=json | jq -r '.[0]') +$ echo $TOKEN +``` + +Switch `kubectl` to the Kubernetes cluster `cookie` and run: + +```code +# Add Teleport chart repository +$ helm repo add teleport https://charts.releases.teleport.dev + +# Deploy a Kubernetes agent. It dials back to the Teleport cluster mytenant.teleport.sh. +$ CLUSTER='cookie' +$ PROXY='mytenant.teleport.sh' +$ helm install teleport-agent teleport/teleport-kube-agent --set kubeClusterName=${CLUSTER?} \ + --set proxyAddr=${PROXY?} --set authToken=${TOKEN?} --create-namespace --namespace=teleport-agent +``` + +List connected clusters using `tsh kube ls` and switch between +them using `tsh kube login`: + +```code +$ tsh kube ls + +# Kube Cluster Name Selected +# ----------------- -------- +# cookie +# mytenant.teleport.sh * + +# kubeconfig now points to the cookie cluster +$ tsh kube login cookie +# Logged into Kubernetes cluster "cookie" + +# kubectl command executed on `cookie` but is routed through the `mytenant.teleport.sh` cluster. +$ kubectl get pods +``` + + + diff --git a/docs/pages/kubernetes-access/guides/standalone-teleport.mdx b/docs/pages/kubernetes-access/guides/standalone-teleport.mdx index 0e709cb571363..ee6d462dd00e7 100644 --- a/docs/pages/kubernetes-access/guides/standalone-teleport.mdx +++ b/docs/pages/kubernetes-access/guides/standalone-teleport.mdx @@ -1,30 +1,102 @@ --- -title: Kubernetes Access from standalone Teleport +title: Kubernetes Access from a Standalone Teleport Cluster description: Connecting standalone Teleport installations to Kubernetes clusters. --- -## Standalone Teleport installation - -Teleport can connect to external Kubernetes clusters without using the Teleport Helm chart or running a pod inside of the Kubernetes cluster. To do this, Teleport needs a `kubeconfig` file to authenticate against the Kubernetes API. +Teleport can connect to external Kubernetes clusters without using the Teleport +Helm chart or running a pod inside of the Kubernetes cluster. To do this, +Teleport needs a `kubeconfig` file to authenticate against the Kubernetes API. (!docs/pages/includes/permission-warning.mdx!) -## Generating kubeconfig +## Prerequisites + + + + +- A running Teleport cluster. For details on how to set this up, see one of our + [Getting Started](../../getting-started.mdx) guides. + +- **Optional:** the `tctl` admin tool version >= (=teleport.version=). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + See [Installation](../../installation.mdx) for details. + + (!docs/pages/includes/tctl.mdx!) + + + + +- A running Teleport cluster. For details on how to set this up, see one of our + [Getting Started](../../getting-started.mdx) guides. + +- **Optional:** the `tctl` admin tool version >= (=teleport.version=), which you can download + by visiting the + [customer portal](https://dashboard.gravitational.com/web/login). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + (!docs/pages/includes/tctl.mdx!) + + + + +- A Teleport Cloud account. If you do not have one, visit the + [sign up page](https://goteleport.com/signup/) to begin your free trial. + +- A host deployed on your own infrastructure to run the Teleport Kubernetes + Service. See [Installing Teleport](../../installation.mdx) for more details. + +- **Optional:** the Enterprise version of the `tctl` admin tool. To download this, visit +the [customer portal](https://dashboard.gravitational.com/web/login). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + (!docs/pages/includes/tctl.mdx!) + + + + +## Step 1/2. Generate a kubeconfig -First, configure your local `kubectl` command to point at the Kubernetes -cluster you want to register. You can use `kubectl config get-contexts` to -verify that the correct cluster is selected, or `kubectl config use-context -${CONTEXT_NAME?}` to switch to cluster `CONTEXT_NAME`. +First, configure your local `kubectl` command to point at the Kubernetes cluster +you want to register. You can use the following command to verify that +the correct cluster is selected: + +```code +$ kubectl config get-contexts +``` + +Use this command to switch to the cluster assigned to `CONTEXT_NAME`: + +```code +$ kubectl config use-context ${CONTEXT_NAME?} +``` Next, use [get-kubeconfig.sh](https://github.com/gravitational/teleport/blob/master/examples/k8s-auth/get-kubeconfig.sh) to create a `kubeconfig` for Teleport to use. You can connect multiple Kubernetes clusters to Teleport from one `kubeconfig` if it contains multiple entries. Use [merge-kubeconfigs.sh](https://github.com/gravitational/teleport/blob/master/examples/k8s-auth/merge-kubeconfigs.sh) to combine multiple `kubeconfigs` generated by `get-kubeconfig.sh`. -## Adding kubeconfig to Teleport +## Step 2/2. Add your kubeconfig to Teleport + + + -In your Teleport Proxy or a new separate instance, add the following to -`teleport.yaml`: +In your Teleport Proxy, add the following to `teleport.yaml`, replacing +`example.com` with the domain name of your Teleport cluster: ```yaml # ... @@ -43,6 +115,24 @@ kubernetes_service: kubeconfig_file: "path/to/kubeconfig" ``` + + + +On the host you will use to run the Teleport Kubernetes Service, add the +following to `teleport.yaml`. + +```yaml +# ... +kubernetes_service: + enabled: yes + listen_addr: 0.0.0.0:3027 + # Replace this path with the actual path for your generated kubeconfig + kubeconfig_file: "path/to/kubeconfig" +``` + + + + When using `kubeconfig_file`, EKS users may need to replace illegal characters in the `context` names. Supported characters are alphanumeric characters, `.`, `_`, and `-`. EKS typically includes `:` and `@` diff --git a/docs/pages/kubernetes-access/helm/guides/aws.mdx b/docs/pages/kubernetes-access/helm/guides/aws.mdx index 1e54b39653183..83c28a54e079b 100644 --- a/docs/pages/kubernetes-access/helm/guides/aws.mdx +++ b/docs/pages/kubernetes-access/helm/guides/aws.mdx @@ -6,9 +6,21 @@ description: Install and configure an HA Teleport cluster using an AWS EKS clust In this guide, we'll go through how to set up a High Availability Teleport cluster with multiple replicas in Kubernetes using Teleport Helm charts and AWS products (DynamoDB and S3). -(!docs/pages/kubernetes-access/helm/includes/helm-install.mdx!) +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-cloud-warning.mdx!) -## Step 3. Set up AWS IAM configuration +## Prerequisites + +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-prereqs.mdx!) + +## Step 1/7. Install Helm + +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-install.mdx!) + +## Step 2/7. Add the Teleport Helm chart repository + +(!docs/pages/kubernetes-access/helm/includes/helm-repo-add.mdx!) + +## Step 3/7. Set up AWS IAM configuration For Teleport to be able to create the DynamoDB tables, indexes, and the S3 storage bucket it needs, you'll need to configure AWS IAM policies to allow access. @@ -153,7 +165,7 @@ You'll need to replace these values in the policy example below: } ``` -## Step 4. Install and configure cert-manager +## Step 4/7. Install and configure cert-manager Reference: [`cert-manager` docs](https://cert-manager.io/docs/) @@ -216,7 +228,7 @@ $ kubectl create namespace teleport $ kubectl --namespace teleport create -f aws-issuer.yaml ``` -## Step 5. Set values to configure the cluster +## Step 5/7. Set values to configure the cluster There are two different ways to configure the `teleport-cluster` Helm chart to use `aws` mode - using a `values.yaml` file, or using `--set` on the command line. @@ -300,11 +312,13 @@ $ kubectl --namespace teleport get all # replicaset.apps/teleport-5cf46ddf5f 2 2 2 4m21s ``` -## Step 6. Set up DNS +## Step 6/7. Set up DNS + +You'll need to set up a DNS `A` record for `teleport.example.com`. In our example, this record is an alias to an ELB. -You'll need to set up two DNS `A` records: `teleport.example.com` for the web UI, and `*.teleport.example.com` -for web apps using [application access](../../../application-access/introduction.mdx). In our example, both records are -aliases to an ELB. +
    +(!docs/pages/includes/dns-app-access.mdx!) +
    Here's how to do this in a hosted zone with AWS Route 53: @@ -322,7 +336,7 @@ $ MY_CLUSTER_REGION='us-west-2' $ MYZONE="$(aws route53 list-hosted-zones-by-name --dns-name="${MYZONE_DNS?}" | jq -r '.HostedZones[0].Id' | sed s_/hostedzone/__)" $ MYELB="$(kubectl --namespace "${NAMESPACE?}" get "service/${RELEASE_NAME?}" -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')" $ MYELB_NAME="${MYELB%%-*}" -$ MYELB_ZONE="$(aws elb describe-load-balancers --region "${MY_CLUSTER_REGION?}" --load-balancer-names "${MYELB_NAME?}" | jq -r '.LoadBalancerDescriptions[0].CanonicalHostedZoneNameID')" +$ MYELB_ZONE="$(aws elbv2 describe-load-balancers --region "${MY_CLUSTER_REGION?}" --names "${MYELB_NAME?}" | jq -r '.LoadBalancers[0].CanonicalHostedZoneId')" # Create a JSON file changeset for AWS. $ jq -n --arg dns "${MYDNS?}" --arg elb "${MYELB?}" --arg elbz "${MYELB_ZONE?}" \ @@ -366,7 +380,7 @@ $ aws route53 get-change --id "${CHANGEID?}" | jq '.ChangeInfo.Status' # "INSYNC" ``` -## Step 7. Create a Teleport user +## Step 7/7. Create a Teleport user Create a user to be able to log into Teleport. This needs to be done on the Teleport auth server, so we can run the command using `kubectl`: @@ -444,4 +458,4 @@ $ helm --namespace cert-manager uninstall cert-manager You can follow our [Getting Started with Teleport guide](../../../setup/guides/docker.mdx#step-34-creating-a-teleport-user) to finish setting up your Teleport cluster. -See the [high availability section of our Helm chart reference](../reference.mdx#highavailability) for more details on high availability. +See the [high availability section of our Helm chart reference](../reference/teleport-cluster.mdx#highavailability) for more details on high availability. diff --git a/docs/pages/kubernetes-access/helm/guides/custom.mdx b/docs/pages/kubernetes-access/helm/guides/custom.mdx index dc678ace25919..a5a126b170b85 100644 --- a/docs/pages/kubernetes-access/helm/guides/custom.mdx +++ b/docs/pages/kubernetes-access/helm/guides/custom.mdx @@ -9,14 +9,26 @@ config file using Teleport Helm charts. This setup can be useful when you already have an existing Teleport cluster and would like to start running it in Kubernetes, or when migrating your setup from a legacy version of the Helm charts. -(!docs/pages/kubernetes-access/helm/includes/helm-install.mdx!) +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-cloud-warning.mdx!) -## Step 3. Setting up a Teleport cluster with Helm using a custom config +## Prerequisites + +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-prereqs.mdx!) + +## Step 1/4. Install Helm + +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-install.mdx!) + +## Step 2/4. Add the Teleport Helm chart repository + +(!docs/pages/kubernetes-access/helm/includes/helm-repo-add.mdx!) + +## Step 3/4. Setting up a Teleport cluster with Helm using a custom config In `custom` mode, the `teleport-cluster` Helm chart does not create a `ConfigMap` containing a `teleport.yaml` file for you, but expects that you will provide this yourself. -For this example, we'll be using this `teleport.yaml` configuration file (with appropriately complex [static tokens](../../../setup/admin/adding-nodes.mdx#insecure-static-tokens)): +For this example, we'll be using this `teleport.yaml` configuration file with a static join token (for more information on join tokens, see [Adding Nodes to the Cluster](../../../setup/admin/adding-nodes.mdx)): ```bash $ cat << EOF > teleport.yaml @@ -130,7 +142,7 @@ $ kubectl --namespace teleport get all # replicaset.apps/teleport-5c56b4d869 1 1 1 5h8m ``` -## Step 4. Create a Teleport user (optional) +## Step 4/4. Create a Teleport user (optional) If you're not migrating an existing Teleport cluster, you'll need to create a user to be able to log into Teleport. This needs to be done on the Teleport auth server, so we can run the command using `kubectl`: diff --git a/docs/pages/kubernetes-access/helm/guides/digitalocean.mdx b/docs/pages/kubernetes-access/helm/guides/digitalocean.mdx index 1e8da7e44a7f2..0f6e816742a16 100644 --- a/docs/pages/kubernetes-access/helm/guides/digitalocean.mdx +++ b/docs/pages/kubernetes-access/helm/guides/digitalocean.mdx @@ -5,6 +5,8 @@ description: How to get started with Teleport on DigitalOcean Kubernetes This guide will show you how to get started with Teleport on DigitalOcean Kubernetes. +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-cloud-warning.mdx!) + ## Prerequisites - DigitalOcean account. - Your workstation configured with [kubectl](https://kubernetes.io/docs/tasks/tools/), [Helm](https://helm.sh/docs/intro/install/), [doctl](https://docs.digitalocean.com/reference/doctl/how-to/install/), and the Teleport [tsh](https://goteleport.com/docs/installation/) client. diff --git a/docs/pages/kubernetes-access/helm/guides/gcp.mdx b/docs/pages/kubernetes-access/helm/guides/gcp.mdx index 74b02029d3c48..73554e7f73d92 100644 --- a/docs/pages/kubernetes-access/helm/guides/gcp.mdx +++ b/docs/pages/kubernetes-access/helm/guides/gcp.mdx @@ -6,7 +6,19 @@ description: Install and configure an HA Teleport cluster using a Google Cloud G In this guide, we'll go through how to set up a High Availability Teleport cluster with multiple replicas in Kubernetes using Teleport Helm charts and Google Cloud Platform products (Firestore and Google Cloud Storage). -(!docs/pages/kubernetes-access/helm/includes/helm-install.mdx!) +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-cloud-warning.mdx!) + +## Prerequisites + +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-prereqs.mdx!) + +## Step 1/7. Install Helm + +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-install.mdx!) + +## Step 2/7. Add the Teleport Helm chart repository + +(!docs/pages/kubernetes-access/helm/includes/helm-repo-add.mdx!) -## Step 3: Google Cloud IAM configuration +## Step 3/7. Google Cloud IAM configuration For Teleport to be able to create the Firestore collections, indexes, and the Google Cloud Storage bucket it needs, you'll need to configure a Google Cloud service account with permissions to use these services. @@ -187,7 +199,7 @@ $ kubectl --namespace teleport create secret generic teleport-gcp-credentials -- The credentials file stored in any secret used must have the key name `gcp-credentials.json`. -## Step 4: Install and configure cert-manager +## Step 4/7. Install and configure cert-manager Reference the [cert-manager docs](https://cert-manager.io/docs/). @@ -255,7 +267,7 @@ After you have created the `Issuer` and updated the values, add it to your clust $ kubectl --namespace teleport create -f gcp-issuer.yaml ``` -## Step 5: Set values to configure the cluster +## Step 5/7. Set values to configure the cluster If you are installing Teleport in a brand new GCP project, make sure you have enabled the @@ -344,10 +356,13 @@ $ kubectl --namespace teleport get all # replicaset.apps/teleport-b64dd8849 2 2 2 7m16s ``` -## Step 6. Set up DNS +## Step 6/7. Set up DNS + +You'll need to set up a DNS `A` record for `teleport.example.com`. -You'll need to set up two DNS `A` records: `teleport.example.com` for the web UI, and `*.teleport.example.com` -for web apps using [application access](../../../application-access/introduction.mdx). +
    +(!docs/pages/includes/dns-app-access.mdx!) +
    Here's how to do this using Google Cloud DNS: @@ -367,7 +382,7 @@ $ gcloud dns record-sets transaction describe --zone="${MYZONE?}" $ gcloud dns record-sets transaction execute --zone="${MYZONE?}" ``` -## Step 7. Create a Teleport user +## Step 7/7. Create a Teleport user Create a user to be able to log into Teleport. This needs to be done on the Teleport auth server, so we can run the command using `kubectl`: @@ -394,7 +409,7 @@ To make changes to your Teleport cluster after deployment, you can use `helm upg Helm defaults to using the latest version of the chart available in the repo, which will also correspond to the latest version of Teleport. You can make sure that the repo is up to date by running `helm repo update`. -If you want to use a different version of Teleport, set the [`teleportVersionOverride`](../reference.mdx#teleportversionoverride) value. +If you want to use a different version of Teleport, set the [`teleportVersionOverride`](../reference/teleport-cluster.mdx#teleportversionoverride) value. Here's an example where we set the chart to use 3 replicas: @@ -448,4 +463,4 @@ $ helm --namespace cert-manager uninstall cert-manager You can follow our [Getting Started with Teleport guide](../../../setup/guides/docker.mdx#step-34-creating-a-teleport-user) to finish setting up your Teleport cluster. -See the [high availability section of our Helm chart reference](../reference.mdx#highavailability) for more details on high availability. +See the [high availability section of our Helm chart reference](../reference/teleport-cluster.mdx#highavailability) for more details on high availability. diff --git a/docs/pages/kubernetes-access/helm/guides/migration.mdx b/docs/pages/kubernetes-access/helm/guides/migration.mdx index 1810fb80562bb..f35f5cacc3270 100644 --- a/docs/pages/kubernetes-access/helm/guides/migration.mdx +++ b/docs/pages/kubernetes-access/helm/guides/migration.mdx @@ -13,9 +13,21 @@ to use the newer `teleport-cluster` Helm chart instead. consider [following a different guide](../guides.mdx) and storing your cluster's data in AWS DynamoDB or Google Cloud Firestore.
    -(!docs/pages/kubernetes-access/helm/includes/helm-install.mdx!) +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-cloud-warning.mdx!) -## Step 3. Get the Teleport configuration file from your existing cluster +## Prerequisites + +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-prereqs.mdx!) + +## Step 1/6. Install Helm + +(!docs/pages/kubernetes-access/helm/includes/teleport-cluster-install.mdx!) + +## Step 2/6. Add the Teleport Helm chart repository + +(!docs/pages/kubernetes-access/helm/includes/helm-repo-add.mdx!) + +## Step 3/6. Get the Teleport configuration file from your existing cluster If your Teleport cluster's database is currently stored in AWS DynamoDB or Google Cloud Firestore rather than @@ -81,7 +93,7 @@ $ kubectl --namespace teleport-cluster create configmap teleport --from-file=tel # configmap/teleport created ``` -## Step 4. Extracting the contents of Teleport's database +## Step 4/6. Extracting the contents of Teleport's database If you migrate your existing data, the `cluster_name` which is configured in `teleport.yaml` must stay the same. @@ -112,7 +124,7 @@ Add the backup to your new `teleport-cluster` namespace as a secret: $ kubectl --namespace teleport-cluster create secret generic bootstrap --from-file=backup.yaml ``` -## Step 5. Start the new cluster with your old config file and backup +## Step 5/6. Start the new cluster with your old config file and backup We will start the new cluster and bootstrap it using the backup of your cluster's data. Once this step is complete and the cluster is working, we'll modify the deployment to remove references to the backup data, and remove it from Kubernetes for security. @@ -185,7 +197,7 @@ $ kubectl --namespace teleport-cluster get all browser to view the page successfully. -## Step 6. Remove the bootstrap data and update the chart deployment +## Step 6/6. Remove the bootstrap data and update the chart deployment Once you've tested your new Teleport cluster and you're confident that your data has been migrated successfully, you should redeploy the chart without your backup data mounted for security. diff --git a/docs/pages/kubernetes-access/helm/includes/helm-install.mdx b/docs/pages/kubernetes-access/helm/includes/helm-install.mdx deleted file mode 100644 index 9f2afc4f8aaf4..0000000000000 --- a/docs/pages/kubernetes-access/helm/includes/helm-install.mdx +++ /dev/null @@ -1,48 +0,0 @@ -## Prerequisites - -- [Kubernetes](https://kubernetes.io) >= v(=kubernetes.major_version=).(=kubernetes.minor_version=).0 -- [Helm](https://helm.sh) >= v(=helm.version=) - -
    -This guide shows you how to install the `teleport-cluster` Helm chart, which is intended to help you get started with Teleport by deploying the Auth Service and -Proxy Service in a Kubernetes cluster so you can access that cluster via the Kubernetes Service. - -Since the Auth and Proxy Services are fully managed in Teleport Cloud, you should install our `teleport-kube-agent` chart, which is intended for deployments where the Auth Service and Proxy Service run outside your Kubernetes cluster. - -You can use the `teleport-kube-agent` chart to enable the Application Service and Database Service in addition to the Kubernetes Service. - -For more information, see our [Helm chart reference](../reference.mdx#teleport-kube-agent). -
    - -Verify that Helm and Kubernetes are installed and up to date. - -(!docs/pages/includes/permission-warning.mdx!) - -## Step 1. Install Helm - -Teleport's charts require the use of Helm version 3. You can [install Helm 3 by following these instructions](https://helm.sh/docs/intro/install/). - -Throughout this guide, we will assume that you have the `helm` and `kubectl` binaries available in your `PATH`: - -```code -$ helm version -# version.BuildInfo{Version:"v(=helm.version=)"} - -$ kubectl version -# Client Version: version.Info{Major:"(=kubernetes.major_version=)", Minor:"(=kubernetes.minor_version=)+"} -# Server Version: version.Info{Major:"(=kubernetes.major_version=)", Minor:"(=kubernetes.minor_version=)+"} -``` - -## Step 2. Add the Teleport Helm chart repository - -To allow Helm to install charts that are hosted in the Teleport Helm repository, use `helm repo add`: - -```code -$ helm repo add teleport (=teleport.helm_repo_url=) -``` - -To update the cache of charts from the remote repository, run `helm repo update`: - -```code -$ helm repo update -``` diff --git a/docs/pages/kubernetes-access/helm/includes/helm-repo-add.mdx b/docs/pages/kubernetes-access/helm/includes/helm-repo-add.mdx new file mode 100644 index 0000000000000..ecfd2bd53e642 --- /dev/null +++ b/docs/pages/kubernetes-access/helm/includes/helm-repo-add.mdx @@ -0,0 +1,11 @@ +To allow Helm to install charts that are hosted in the Teleport Helm repository, use `helm repo add`: + +```code +$ helm repo add teleport (=teleport.helm_repo_url=) +``` + +To update the cache of charts from the remote repository, run `helm repo update`: + +```code +$ helm repo update +``` diff --git a/docs/pages/kubernetes-access/helm/includes/teleport-cluster-cloud-warning.mdx b/docs/pages/kubernetes-access/helm/includes/teleport-cluster-cloud-warning.mdx new file mode 100644 index 0000000000000..552769882816d --- /dev/null +++ b/docs/pages/kubernetes-access/helm/includes/teleport-cluster-cloud-warning.mdx @@ -0,0 +1,10 @@ +
    +This guide shows you how to install the `teleport-cluster` Helm chart, which is intended to help you get started with Teleport by deploying the Auth Service and +Proxy Service in a Kubernetes cluster so you can access that cluster via the Kubernetes Service. + +Since the Auth and Proxy Services are fully managed in Teleport Cloud, you should install our `teleport-kube-agent` chart, which is intended for deployments where the Auth Service and Proxy Service run outside your Kubernetes cluster. + +You can use the `teleport-kube-agent` chart to enable the Application Service and Database Service in addition to the Kubernetes Service. + +For more information, see our [Helm chart reference](../reference/teleport-kube-agent.mdx). +
    \ No newline at end of file diff --git a/docs/pages/kubernetes-access/helm/includes/teleport-cluster-install.mdx b/docs/pages/kubernetes-access/helm/includes/teleport-cluster-install.mdx new file mode 100644 index 0000000000000..f7739d9c3408f --- /dev/null +++ b/docs/pages/kubernetes-access/helm/includes/teleport-cluster-install.mdx @@ -0,0 +1,12 @@ +Teleport's charts require the use of Helm version 3. You can [install Helm 3 by following these instructions](https://helm.sh/docs/intro/install/). + +Throughout this guide, we will assume that you have the `helm` and `kubectl` binaries available in your `PATH`: + +```code +$ helm version +# version.BuildInfo{Version:"v(=helm.version=)"} + +$ kubectl version +# Client Version: version.Info{Major:"(=kubernetes.major_version=)", Minor:"(=kubernetes.minor_version=)+"} +# Server Version: version.Info{Major:"(=kubernetes.major_version=)", Minor:"(=kubernetes.minor_version=)+"} +``` \ No newline at end of file diff --git a/docs/pages/kubernetes-access/helm/includes/teleport-cluster-prereqs.mdx b/docs/pages/kubernetes-access/helm/includes/teleport-cluster-prereqs.mdx new file mode 100644 index 0000000000000..0c49b5efbf7d0 --- /dev/null +++ b/docs/pages/kubernetes-access/helm/includes/teleport-cluster-prereqs.mdx @@ -0,0 +1,6 @@ +- [Kubernetes](https://kubernetes.io) >= v(=kubernetes.major_version=).(=kubernetes.minor_version=).0 +- [Helm](https://helm.sh) >= v(=helm.version=) + +Verify that Helm and Kubernetes are installed and up to date. + +(!docs/pages/includes/permission-warning.mdx!) \ No newline at end of file diff --git a/docs/pages/kubernetes-access/helm/reference.mdx b/docs/pages/kubernetes-access/helm/reference.mdx index bd695b85f7459..1fdeef7a6768d 100644 --- a/docs/pages/kubernetes-access/helm/reference.mdx +++ b/docs/pages/kubernetes-access/helm/reference.mdx @@ -3,2572 +3,17 @@ title: Helm chart reference description: A list of values that can be set using Teleport Helm charts and their purpose --- -The Teleport Helm repository hosts two Helm charts: + + +![Teleport ](../../../img/k8s/mini-diagrams/teleport-in-k8s-mono.svg) -- [`teleport-cluster`](#teleport-cluster) [(source on Github)](https://github.com/gravitational/teleport/tree/master/examples/chart/teleport-cluster) -- [`teleport-kube-agent`](#teleport-kube-agent) [(source on Github)](https://github.com/gravitational/teleport/tree/master/examples/chart/teleport-kube-agent) +Deploy the Teleport Auth Service and Proxy Service on Kubernetes. -(!docs/pages/includes/backup-warning.mdx!) + + +![Kubernetes agent](../../../img/k8s/mini-diagrams/k8s-to-teleport-mono.svg) -## `teleport-cluster` +Deploy the Teleport Kubernetes Service, Application Service, or Database Service on Kubernetes. -The `teleport-cluster` Helm chart is used to configure a Teleport cluster. It runs two Teleport services: - -| Teleport service | Purpose | Documentation | -| - | - | - | -| `auth_service` | Authenticates users and hosts, and issues certificates | [Auth documentation](../../architecture/authentication.mdx) -| `proxy_service`| Runs the externally-facing parts of a Teleport cluster, such as the web UI, SSH proxy and reverse tunnel service | [Proxy documentation](../../architecture/proxy.mdx) | - -The `teleport-cluster` chart can be deployed in four different modes. Get started with a guide for each mode: - -| `chartMode` | Guide | -| - | - | -| `standalone` | [Getting started with Kubernetes Access](../getting-started.mdx) | -| `aws` | [Running an HA Teleport cluster using an AWS EKS Cluster](./guides/aws.mdx) | -| `gcp` | [Running an HA Teleport cluster using a Google Cloud GKE cluster](./guides/gcp.mdx) | -| `custom` | [Running a Teleport cluster with a custom config](./guides/custom.mdx) | - -This reference details available values for the `teleport-cluster` chart. - -## `clusterName` - -| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | -| - | - | - | - | - | -| `string` | `nil` | When `chartMode` is `aws`, `gcp` or `standalone` | `auth_service.cluster_name`, `proxy_service.public_addr` | ✅ | - -`clusterName` controls the name used to refer to the Teleport cluster, along with the externally-facing public address to use to access it. - - - If using a fully qualified domain name as your `clusterName`, you will also need to configure the DNS provider for this domain to point - to the external load balancer address of your Teleport cluster. - - (!docs/pages/kubernetes-access/helm/includes/kubernetes-externaladdress.mdx!) - - You will need to manually add DNS records pointing `teleport.example.com` and `*.teleport.example.com` to either the IP or hostname - of the Kubernetes load balancer. - - If you are not using ACME certificates, you may also need to accept insecure warnings in your browser to view the page successfully. - - - -## `kubeClusterName` - -| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | -| - | - | - | - | - | -| `string` | `clusterName` value | no | `kubernetes_service.kube_cluster_name` | ✅ | - -`kubeClusterName` sets the name used for the Kubernetes cluster. This name will be shown to Teleport users connecting to the cluster. - -## `authenticationType` - -| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | -| - | - | - | - | - | -| `string` | `local` | Yes | `auth_service.authentication.type` | ❌ | - -`authenticationType` controls the authentication scheme used by Teleport. Possible values are `local` and `github` for OSS, plus `oidc`, `saml`, and `false` for Enterprise. - -## `proxyListenerMode` - -| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | -| - | - | - | - | - | -| `string` | `nil` | no | `auth_service.proxy_listener_mode` | ❌ | - -`proxyListenerMode` controls proxy TLS routing used by Teleport. Possible values are `multiplex`. - -## `enterprise` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `bool` | `false`| ✅ | - -`enterprise` controls whether to use Teleport Community Edition or Teleport Enterprise. - -Setting `enterprise` to `true` will use the Teleport Enterprise image. - -You will also need to download your Enterprise license from the Teleport dashboard and add it as a Kubernetes secret to use this: - -```code -$ kubectl --namespace teleport create secret generic license --from-file=/path/to/downloaded/license.pem -``` - - - If you installed the Teleport chart into a specific namespace, the `license` secret you create must also be added to the same namespace. - - - - The file added to the secret must be called `license.pem`. If you have renamed it, you can specify the filename to use in the secret creation command: - - ```code - $ kubectl --namespace teleport create secret generic license --from-file=license.pem=/path/to/downloaded/this-is-my-teleport-license.pem - ``` - - - - - ```yaml - enterprise: true - ``` - - - ```code - $ --set enterprise=true - ``` - - - -## `teleportVersionOverride` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `string` | `nil` | ✅ | - -Normally the version of Teleport being used will match the version of the chart being installed. If you install chart version -7.0.0, you'll be using Teleport 7.0.0. Upgrading the Helm chart will use the latest version from the repo. - -You can optionally override this to use a different published Teleport Docker image tag like `6.0.2` or `7`. - -See these links for information on Docker image versions: - -- [Community Docker image information](../../setup/guides/docker.mdx#step-14-pick-your-image) -- [Enterprise Docker image information](../../enterprise/getting-started.mdx#run-teleport-enterprise-using-docker) - - - - ```yaml - teleportVersionOverride: "7" - ``` - - - ```code - $ --set teleportVersionOverride="7" - ``` - - - -## `acme` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `bool` | `false` | ❌ | `proxy_service.acme.enabled` | - -ACME is a protocol for getting Web X.509 certificates. - -Setting acme to `true` enables the ACME protocol and will attempt to get a free TLS certificate from Let's Encrypt. -Setting acme to `false` (the default) will cause Teleport to generate and use self-signed certificates for its web UI. - - - ACME can only be used for single-pod clusters. It is not suitable for use in HA configurations. - - - - Using a self-signed TLS certificate and disabling TLS verification is OK for testing, but is not viable when running a production Teleport - cluster as it will drastically reduce security. You must configure valid TLS certificates on your Teleport cluster for production workloads. - - One option might be to use Teleport's built-in ACME support or enable [cert-manager support](#highavailabilitycertmanager). - - -## `acmeEmail` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `string` | `nil` | ❌ | `proxy_service.acme.email` | - -`acmeEmail` is the email address to provide during certificate registration (this is a Let's Encrypt requirement). - -## `acmeURI` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `string` | Let's Encrypt production server | ❌ | `proxy_service.acme.uri` | - -`acmeURI` is the ACME server to use for getting certificates. - -As an example, this can be overridden to use the [Let's Encrypt staging server](https://letsencrypt.org/docs/staging-environment/) for testing. - -You can also use any other ACME-compatible server. - - - - ```yaml - acme: true - acmeEmail: user@email.com - acmeURI: https://acme-staging-v02.api.letsencrypt.org/directory - ``` - - - ```code - $ --set acme=true \ - --set acmeEmail=user@email.com \ - --set acmeURI=https://acme-staging-v02.api.letsencrypt.org/directory - ``` - - - -## `podSecurityPolicy` - -### `podSecurityPolicy.enabled` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `bool` | `true` | ✅ | - -By default, Teleport charts also install a [`podSecurityPolicy`](https://github.com/gravitational/teleport/blob/master/examples/chart/teleport-cluster/templates/psp.yaml). - -To disable this, you can set `enabled` to `false`. - -[Kubernetes reference](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) - - - - ```yaml - podSecurityPolicy: - enabled: false - ``` - - - ```code - $ --set podSecurityPolicy.enabled=false - ``` - - - -## `labels` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `object` | `{}` | ❌ | `kubernetes_service.labels` | - -`labels` can be used to add a map of key-value pairs relating to the Teleport cluster being deployed. These labels can then be used with -Teleport's RBAC policies to define access rules for the cluster. - - - These are Teleport-specific RBAC labels, not Kubernetes labels. - - - - - ```yaml - labels: - environment: production - region: us-east - ``` - - - ```code - $ --set labels.environment=production \ - --set labels.region=us-east - ``` - - - -## `chartMode` - -| Type | Default value | -| - | - | -| `string` | `standalone` | - -`chartMode` is used to configure the chart's operation mode. You can find more information about each mode on its specific guide page: - -| `chartMode` | Guide | -| - | - | -| `standalone` | [Getting started with Kubernetes Access](../getting-started.mdx) | -| `aws` | [Running an HA Teleport cluster using an AWS EKS Cluster](./guides/aws.mdx) | -| `gcp` | [Running an HA Teleport cluster using a Google Cloud GKE cluster](./guides/gcp.mdx) | -| `custom` | [Running a Teleport cluster with a custom config](./guides/custom.mdx) | - -## `standalone` - -### `standalone.existingClaimName` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `string` | `nil` | ✅ | - -`standalone.existingClaimName` can be used to provide the name of a pre-existing `PersistentVolumeClaim` to use if desired. - -The default is left blank, which will automatically create a `PersistentVolumeClaim` to use for Teleport storage in `standalone` mode. - - - - ```yaml - standalone: - existingClaimName: my-existing-pvc-name - ``` - - - ```code - $ --set standalone.existingClaimName=my-existing-pvc-name - ``` - - - -### `standalone.volumeSize` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `string` | `10Gi` | ✅ | - -You can set `volumeSize` to request a different size of persistent volume when installing the Teleport chart in `standalone` mode. - - - `volumeSize` will be ignored if `existingClaimName` is set. - - - - - ```yaml - standalone: - volumeSize: 50Gi - ``` - - - ```code - --set standalone.volumeSize=50Gi - ``` - - - -## `aws` - -| Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | -| ❌ | See [Using DynamoDB](../../setup/reference/backends.mdx#dynamodb) and [Using Amazon S3](../../setup/reference/backends.mdx#s3) for details | - -`aws` settings are described in the AWS guide: [Running an HA Teleport cluster using an AWS EKS Cluster](./guides/aws.mdx) - -## `gcp` - -| Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | -| ❌ | See [Using Firestore](../../setup/reference/backends.mdx#dynamodb) and [Using GCS](../../setup/reference/backends.mdx#gcs) for details | - -`gcp` settings are described in the GCP guide: [Running an HA Teleport cluster using a Google Cloud GKE cluster](./guides/gcp.mdx) - -### `highAvailability` - -## `highAvailability.replicaCount` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `int` | `1` | ✅ (when using HA storage) | - -`highAvailability.replicaCount` can be used to set the number of replicas used in the deployment. - -Set to a number higher than `1` for a high availability mode where multiple Teleport pods will be deployed and connections will be load balanced between them. - - - Setting `highAvailability.replicaCount` to a value higher than `1` will disable the use of ACME certs. - - - - As a rough guide, we recommend configuring one replica per distinct availability zone where your cluster has worker nodes. - - 2 replicas/availability zones will be fine for smaller workloads. 3-5 replicas/availability zones will be more appropriate for bigger - clusters with more traffic. - - - - When using `custom` mode, you **must** use highly-available storage (e.g. etcd, DynamoDB or Firestore) for multiple replicas to be supported. - - [Information on supported Teleport storage backends](../../architecture/authentication.mdx#storage-back-ends) - - Manually configuring NFS-based storage or `ReadWriteMany` volume claims is **NOT** supported for an HA deployment and will result in errors. - - - - - ```yaml - highAvailability: - replicaCount: 3 - ``` - - - ```code - $ --set highAvailability.replicaCount=3 - ``` - - - -## `highAvailability.requireAntiAffinity` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `bool` | `false` | ✅ (when using HA storage) | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity) - -Setting `highAvailability.requireAntiAffinity` to `true` will use `requiredDuringSchedulingIgnoredDuringExecution` to require that multiple -Teleport pods must not be scheduled on the same physical host. - - - This can result in Teleport pods failing to be scheduled in very small clusters or during node downtime, so should be used with caution. - - - Setting `highAvailability.requireAntiAffinity` to `false` (the default) uses `preferredDuringSchedulingIgnoredDuringExecution` to make node - anti-affinity a soft requirement. - - - This setting only has any effect when `highAvailability.replicaCount` is greater than `1`. - - - - - ```yaml - highAvailability: - requireAntiAffinity: true - ``` - - - ```code - $ --set highAvailability.requireAntiAffinity=true - ``` - - - -## `highAvailability.podDisruptionBudget` - -### `highAvailability.podDisruptionBudget.enabled` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `bool` | `false` | ✅ (when using HA storage) | - -[Kubernetes reference](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) - -Enable a Pod Disruption Budget for the Teleport Pod to ensure HA during voluntary disruptions. - - - - ```yaml - highAvailability: - podDisruptionBudget: - enabled: true - ``` - - - ```shell - --set highAvailability.podDisruptionBudget.enabled=true - ``` - - - -### `highAvailability.podDisruptionBudget.minAvailable` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `int` | `1` | ✅ (when using HA storage) | - -[Kubernetes reference](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) - -Ensures that this number of replicas is available during voluntary disruptions, can be a number of replicas or a percentage. - - - - ```yaml - highAvailability: - podDisruptionBudget: - minAvailable: 1 - ``` - - - ```shell - --set highAvailability.podDisruptionBudget.minAvailable=1 - ``` - - - -## `highAvailability.certManager` - -See the [cert-manager](https://cert-manager.io/docs/) docs for more information. - -### `highAvailability.certManager.enabled` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `bool` | `false` | ❌ | `proxy_service.https_keypairs` (to provide your own certificates) | - -Setting `highAvailability.certManager.enabled` to `true` will use `cert-manager` to provision a TLS certificate for a Teleport -cluster deployed in HA mode. - - - You must install and configure `cert-manager` in your Kubernetes cluster yourself. - - See the [cert-manager Helm install instructions](https://cert-manager.io/docs/installation/kubernetes/#option-2-install-crds-as-part-of-the-helm-release) - and the relevant sections of the [AWS](./guides/aws.mdx#step-4-install-and-configure-cert-manager) and [GCP](./guides/gcp.mdx#step-4-install-and-configure-cert-manager) guides for more information. - - -### `highAvailability.certManager.addCommonName` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `bool` | `false` | ❌ | `proxy_service.https_keypairs` (to provide your own certificates) | - -Setting `highAvailability.certManager.addCommonName` to `true` will instruct `cert-manager` to set the commonName field in its certificate signing request to the issuing CA. - - - You must install and configure `cert-manager` in your Kubernetes cluster yourself. - - See the [cert-manager Helm install instructions](https://cert-manager.io/docs/installation/kubernetes/#option-2-install-crds-as-part-of-the-helm-release) - and the relevant sections of the [AWS](./guides/aws.mdx#step-4-install-and-configure-cert-manager) and [GCP](./guides/gcp.mdx#step-4-install-and-configure-cert-manager) guides for more information. - - - - - ```yaml - highAvailability: - certManager: - enabled: true - addCommonName: true - issuerName: letsencrypt-production - ``` - - - ```code - $ --set highAvailability.certManager.enabled=true \ - --set highAvailability.certManager.addCommonName=true \ - --set highAvailability.certManager.issuerName=letsencrypt-production - ``` - - - -### `highAvailability.certManager.issuerName` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `string` | `nil` | ❌ | None | - -Sets the name of the `cert-manager` `Issuer` or `ClusterIssuer` to use for issuing certificates. - - - You must install configure an appropriate `Issuer` supporting a DNS01 challenge yourself. - - Please see the [cert-manager DNS01 docs](https://cert-manager.io/docs/configuration/acme/dns01/#supported-dns01-providers) and the relevant sections - of the [AWS](./guides/aws.mdx#step-4-install-and-configure-cert-manager) and [GCP](./guides/gcp.mdx#step-4-install-and-configure-cert-manager) guides for more information. - - - - - ```yaml - highAvailability: - certManager: - enabled: true - issuerName: letsencrypt-production - ``` - - - ```code - $ --set highAvailability.certManager.enabled=true \ - --set highAvailability.certManager.issuerName=letsencrypt-production - ``` - - - -### `highAvailability.certManager.issuerKind` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `string` | `Issuer` | ❌ | None | - -Sets the `Kind` of `Issuer` to be used when issuing certificates with `cert-manager`. Defaults to `Issuer` to keep permissions -scoped to a single namespace. - - - - ```yaml - highAvailability: - certManager: - issuerKind: ClusterIssuer - ``` - - - ```code - --set highAvailability.certManager.issuerKind=ClusterIssuer - ``` - - - -### `highAvailability.certManager.issuerGroup` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `string` | `cert-manager.io` | ❌ | None | - -Sets the `Group` of `Issuer` to be used when issuing certificates with `cert-manager`. Defaults to `cert-manager.io` to use built-in issuers. - - - - ```yaml - highAvailability: - certManager: - issuerGroup: cert-manager.io - ``` - - - ```code - --set highAvailability.certManager.issuerGroup=cert-manager.io - ``` - - - -## `image` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `string` | `quay.io/gravitational/teleport` | ✅ | - -`image` sets the container image used for Teleport Community pods in the cluster. - -You can override this to use your own Teleport Community image rather than a Teleport-published image. - - - - ```yaml - image: my.docker.registry/teleport-community-image-name - ``` - - - ```code - --set image=my.docker.registry/teleport-community-image-name - ``` - - - -## `enterpriseImage` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `string` | `quay.io/gravitational/teleport-ent` | ✅ | - -`enterpriseImage` sets the container image used for Teleport Enterprise pods in the cluster. - -You can override this to use your own Teleport Enterprise image rather than a Teleport-published image. - - - - ```yaml - enterpriseImage: my.docker.registry/teleport-enterprise-image-name - ``` - - - ```code - --set enterpriseImage=my.docker.registry/teleport-enterprise-image - ``` - - - -## `log` - -### `log.level` - - - This field used to be called `logLevel`. For backwards compatibility this name can still be used, but we recommend changing your values file to use `log.level`. - - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `string` | `INFO` | ❌ | `teleport.log.severity` | - -`log.level` sets the log level used for the Teleport process. - -Available log levels (in order of most to least verbose) are: `DEBUG`, `INFO`, `WARNING`, `ERROR`. - -The default is `INFO`, which is recommended in production. - -`DEBUG` is useful during first-time setup or to see more detailed logs for debugging. - - - - ```yaml - log: - level: DEBUG - ``` - - - ```code - --set log.level=DEBUG - ``` - - - -### `log.output` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `string` | `stderr` | ❌ | `teleport.log.output` | - -`log.output` sets the output destination for the Teleport process. - -This can be set to any of the built-in values: `stdout`, `stderr` or `syslog` to use that destination. - -The value can also be set to a file path (such as `/var/log/teleport.log`) to write logs to a file. Bear in mind that a few service startup messages will still go to `stderr` for resilience. - - - - ```yaml - log: - output: stderr - ``` - - - ```code - --set log.output=stderr - ``` - - - -### `log.format` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `string` | `text` | ❌ | `teleport.log.format.output` | - -`log.format` sets the output type for the Teleport process. - -Possible values are `text` (default) or `json`. - - - - ```yaml - log: - format: json - ``` - - - ```code - --set log.format=json - ``` - - - -### `log.extraFields` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `list` | `["timestamp", "level", "component", "caller"]` | ❌ | `teleport.log.format.extra_fields` | - -`log.extraFields` sets the fields used in logging for the Teleport process. - -See the [Teleport config file reference](../../setup/reference/config.mdx) for more details on possible values for `extra_fields`. - - - - ```yaml - log: - extraFields: ["timestamp", "level"] - ``` - - - ```code - --set "log.extraFields[0]=timestamp" \ - --set "log.extraFields[1]=level" - ``` - - - -## `affinity` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) - -Kubernetes affinity to set for pod assignments. - - - You cannot set both `affinity` and `highAvailability.requireAntiAffinity` as they conflict with each other. Only set one or the other. - - - - - ```yaml - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: gravitational.io/dedicated - operator: In - values: - - teleport - ``` - - - ```code - $ --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key=gravitational.io/dedicated \ - --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator=In \ - --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]=teleport - ``` - - - -## `annotations.config` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `object` | `{}` | ❌ | None | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - -Kubernetes annotations which should be applied to the `ConfigMap` created by the chart. - - - These annotations will not be applied in `custom` mode, as the `ConfigMap` is not managed by the chart. - In this instance, you should apply annotations manually to your created `ConfigMap`. - - - - - ```yaml - annotations: - config: - kubernetes.io/annotation: value - ``` - - - ```code - $ --set annotations.config."kubernetes\.io\/annotation"=value - ``` - - You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend - using a `values.yaml` file instead to avoid confusion and errors. - - - - -## `annotations.deployment` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - -Kubernetes annotations which should be applied to the `Deployment` created by the chart. - - - - ```yaml - annotations: - deployment: - kubernetes.io/annotation: value - ``` - - - ```code - $ --set annotations.deployment."kubernetes\.io\/annotation"=value - ``` - - You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend - using a `values.yaml` file instead to avoid confusion and errors. - - - - -## `annotations.pod` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - -Kubernetes annotations which should be applied to each `Pod` created by the chart. - - - - ```yaml - annotations: - pod: - kubernetes.io/annotation: value - ``` - - - ```code - $ --set annotations.pod."kubernetes\.io\/annotation"=value - ``` - - You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend - using a `values.yaml` file instead to avoid confusion and errors. - - - - -## `annotations.service` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - -Kubernetes annotations which should be applied to the `Service` created by the chart. - - - - ```yaml - annotations: - service: - kubernetes.io/annotation: value - ``` - - - ```code - $ --set annotations.service."kubernetes\.io\/annotation"=value - ``` - - You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend - using a `values.yaml` file instead to avoid confusion and errors. - - - - -## `annotations.serviceAccount` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - -Kubernetes annotations which should be applied to the `serviceAccount` created by the chart. - - - - ```yaml - annotations: - serviceAccount: - kubernetes.io/annotation: value - ``` - - - ```code - $ --set annotations.serviceAccount."kubernetes\.io\/annotation"=value - ``` - - You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend - using a `values.yaml` file instead to avoid confusion and errors. - - - - -## `annotations.certSecret` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - -Kubernetes annotations which should be applied to the `secret` generated by -`cert-manager` from the `certificate` created by the chart. Only valid when -`highAvailability.certManager.enabled` is set to `true` and requires -`cert-manager` v1.5.0+. - - - - ```yaml - annotations: - certSecret: - kubernetes.io/annotation: value - ``` - - - ```code - $ --set annotations.certSecret."kubernetes\.io\/annotation"=value - ``` - - You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend - using a `values.yaml` file instead to avoid confusion and errors. - - - - -## `service.type` - -| Type | Default value | Required? | Can be used in `custom` mode? | -| - | - | - | - | -| `string` | `LoadBalancer` | Yes | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/services-networking/service/) - -Allows to specify the service type. - - - - ```yaml - service: - type: LoadBalancer - ``` - - - ```code - $ --set service.type=LoadBalancer - ``` - - - -## `service.spec.loadBalancerIP` - -| Type | Default value | Required? | Can be used in `custom` mode? | -| - | - | - | - | -| `string` | `nil` | No | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) - -Allows to specify the `loadBalancerIP`. - - - - ```yaml - service: - spec: - loadBalancerIP: 1.2.3.4 - ``` - - - ```code - $ --set service.spec.loadBalancerIP=1.2.3.4 - ``` - - - -## `extraArgs` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -A list of extra arguments to pass to the `teleport start` command when running a Teleport Pod. - - - - ```yaml - extraArgs: - - "--bootstrap=/etc/teleport-bootstrap/roles.yaml" - ``` - - - ```code - $ --set "extraArgs={--bootstrap=/etc/teleport-bootstrap/roles.yaml}" - ``` - - - -## `extraEnv` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) - -A list of extra environment variables to be set on the main Teleport container. - - - - ```yaml - extraEnv: - - name: MY_ENV - value: my-value - ``` - - - ```code - $ --set "extraEnv[0].name=MY_ENV" \ - --set "extraEnv[0].value=my-value" - ``` - - - - -## `extraVolumes` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/volumes/) - -A list of extra Kubernetes `Volumes` which should be available to any `Pod` created by the chart. These volumes -will also be available to any `initContainers` configured by the chart. - - - - ```yaml - extraVolumes: - - name: myvolume - secret: - secretName: mysecret - ``` - - - ```code - $ --set "extraVolumes[0].name=myvolume" \ - --set "extraVolumes[0].secret.secretName=mysecret" - ``` - - - -## `extraVolumeMounts` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/volumes/) - -A list of extra Kubernetes volume mounts which should be mounted into any `Pod` created by the chart. These volume -mounts will also be mounted into any `initContainers` configured by the chart. - - - - ```yaml - extraVolumeMounts: - - name: myvolume - mountPath: /path/to/mount/volume - ``` - - - ```code - $ --set "extraVolumeMounts[0].name=myvolume" \ - --set "extraVolumeMounts[0].path=/path/to/mount/volume" - ``` - - - -## `imagePullPolicy` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `string` | `IfNotPresent` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/containers/images/#updating-images) - -Allows the `imagePullPolicy` for any pods created by the chart to be overridden. - - - - ```yaml - imagePullPolicy: Always - ``` - - - ```code - $ --set imagePullPolicy=Always - ``` - - - -## `initContainers` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) - -A list of `initContainers` which will be run before the main Teleport container in any pod created by the chart. - - - - ```yaml - initContainers: - - name: teleport-init - image: alpine - args: ['echo test'] - ``` - - - ```code - $ --set "initContainers[0].name=teleport-init" \ - --set "initContainers[0].image=alpine" \ - --set "initContainers[0].args={echo test}" - ``` - - - -## `postStart` - -[Kubernetes reference](https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/) - -A `postStart` lifecycle handler to be configured on the main Teleport container. - - - - ```yaml - postStart: - command: - - echo - - foo - ``` - - - ```shell - --set "postStart.command[0]=echo" \ - --set "postStart.command[1]=foo" - ``` - - - -## `resources` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) - -Resource requests/limits which should be configured for each container inside the pod. These resource limits -will also be applied to `initContainers`. - - - - ```yaml - resources: - requests: - cpu: 1 - memory: 2Gi - ``` - - - ```code - $ --set resources.requests.cpu=1 \ - --set resources.requests.memory=2Gi - ``` - - - -## `tolerations` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) - -Kubernetes Tolerations to set for pod assignment. - - - - ```yaml - tolerations: - - key: "dedicated" - operator: "Equal" - value: "teleport" - effect: "NoSchedule" - ``` - - - ```code - $ --set tolerations[0].key=dedicated \ - --set tolerations[0].operator=Equal \ - --set tolerations[0].value=teleport \ - --set tolerations[0].effect=NoSchedule - ``` - - - -## `priorityClassName` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `string` | `""` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) - -Kubernetes PriorityClass to set for pod. - - - - ```yaml - priorityClassName: "system-cluster-critical" - ``` - - - ```code - $ --set priorityClassName=system-cluster-critical - ``` - - - -## --- - -## `teleport-kube-agent` - -The `teleport-kube-agent` Helm chart is used to configure a Teleport agent which runs in a remote Kubernetes cluster and joins back to a -Teleport cluster to provide access to services running there. - -The `teleport-kube-agent` chart can run any or all of three Teleport services: - -| Teleport service | Name for `roles` and `tctl tokens add` | Purpose | -| - | - | - | -| [`kubernetes_service`](../../kubernetes-access/guides.mdx) | `kube` | Uses Teleport to handle authentication with and proxy access to a Kubernetes cluster | -| [`application_service`](../../application-access/guides.mdx) | `app` | Uses Teleport to handle authentication with and proxy access to web-based applications | -| [`database_service`](../../database-access/guides.mdx) | `db` | Uses Teleport to handle authentication with and proxy access to databases | - -This reference details available values for the `teleport-kube-agent` chart. - -## `roles` - -This parameter is not mandatory to preserve backwards compatibility with older chart versions, but we recommend it is set. - -| Type | Default value | -| - | - | -| `string` | `kube` | - -`roles` is a comma-separated list of services which should be enabled when running the `teleport-kube-agent` chart. - -| Services | Value for `roles` | Mandatory additional settings for this role | -| - | - | - | -| Teleport Kubernetes service | `kube` | [`kubeClusterName`](#kubeclustername) | -| Teleport Application service | `app` | [`apps`](#apps) | -| Teleport Database service | `db` | [`databases`](#databases) | - - - - ```yaml - roles: kube,app,db - ``` - - - ```code - $ --set roles=kube\,app\,db - ``` - - - When specifying multiple roles using `--set` syntax, you must escape the commas using a backslash (`\`). - - This is a quirk of Helm's CLI parser. - - - - - -If you specify a role here, you may also need to specify some other settings which are detailed in this reference. - -## `authToken` - -| Type | Default value | Required? | -| - | - | - | -| `string` | `nil` | Yes | - -`authToken` provides a Teleport join token which will be used to join the Teleport agent to a Teleport cluster. - -This value **must** be provided for the chart to work. The token that you use must also be valid for every Teleport service that you -are trying to add with the `teleport-kube-agent` chart. Here are a few examples: - -| Services | Service Name | `tctl tokens add` example | `teleport.yaml` static token example | -| - | - | - | - | -| Kubernetes | `kube` | `tctl tokens add --type=kube` | `"kube:"` | -| Kubernetes, Application | `kube,app` | `tctl tokens add --type=kube,app` | `"kube,app:"` | -| Kubernetes, Application, Database | `kube,app,db` | `tctl tokens add --type=kube,app,db` | `"kube,app,db:"` | - - - When you use a token, all of the services that it provides **must** be used. - - For example, you cannot use a token of type `app,db` to only join a Teleport `app` service by itself. It must join both `app` and `db` services at once. - - Also, each static token you configure must be unique, as the token itself is used to define which services will be supported. - - You cannot reuse the same static token and specify a different set of services. - - -If you do not have the correct services (Teleport refers to these internally as `Roles`) assigned to your join token, the Teleport agent will -fail to join the Teleport cluster. - - - - ```yaml - authToken: - ``` - - - ```code - $ --set authToken= - ``` - - - -## `proxyAddr` - -| Type | Default value | Required? | -| - | - | - | -| `string` | `nil` | Yes | - -`proxyAddr` provides the public-facing Teleport proxy endpoint which should be used to join the cluster. This is the same URL that is used -to access the web UI of your Teleport cluster. It is the same as the value configured for `proxy_service.public_addr` in a traditional -Teleport cluster. The port used is usually either 3080 or 443. - -Here are a few examples: - -| Deployment method | Example `proxy_service.public_addr` | -| - | - | -| On-prem Teleport cluster | `teleport.example.com:3080` | -| Teleport Cloud cluster | `example.teleport.sh:443` | -| `teleport-cluster` Helm chart | `teleport.example.com:443` | - -## `kubeClusterName` - -| Type | Default value | Required? | -| - | - | - | -| `string` | `nil` | When `kube` chart role is used | - -`kubeClusterName` sets the name used for the Kubernetes cluster proxied by the Teleport agent. This name will be shown to Teleport users -connecting to the cluster. - - - - ```yaml - kubeClusterName: my-gke-cluster - ``` - - - ```code - $ --set kubeClusterName=my-gke-cluster - ``` - - - -## `apps` - -| Type | Default value | Required? | -| - | - | - | -| `list` | `[]` | When `app` chart role is used? | - -`apps` is a YAML list object detailing the applications that should be proxied by Teleport Application access. - -You can specify multiple apps by adding additional list elements. - - - - ```yaml - apps: - - name: grafana - uri: http://localhost:3000 - labels: - purpose: monitoring - - name: jenkins - uri: http://jenkins:8080 - labels: - purpose: ci - ``` - - - YAML is very sensitive to correct spacing. When specifying lists in a `values.yaml` file, you might like - to [use a linter](https://codebeautify.org/yaml-validator) to validate your YAML list and ensure that it is correctly formatted. - - - - ```code - $ --set "apps[0].name=grafana" \ - --set "apps[0].uri=http://localhost:3000" \ - --set "apps[0].purpose=monitoring" \ - --set "apps[1].name=grafana" \ - --set "apps[1].uri=http://jenkins:8080" \ - --set "apps[1].purpose=ci" - ``` - - - Note that when using `--set` syntax, YAML list elements must be indexed starting at `0`. - - - - - - - You can see a list of all the supported [values which can be used in a Teleport application access configuration here](../../application-access/reference.mdx#configuration). - - -## `awsDatabases` - - - This section configures database auto-discovery, which is only currently supported on AWS. You can configure databases for other platforms using the [`databases`](#databases) section below this. - - - - For AWS database auto-discovery to work, your agent pods will need to use a role which has appropriate IAM permissions as per the [database documentation](../../database-access/guides/rds.mdx#step-36-configure-iam). - - After configuring a role, you can use an `eks.amazonaws.com/role-arn` annotation with the `annotations.serviceAccount` value to associate it with the service account and grant permissions: - - ``` - annotations: - serviceAccount: - eks.amazonaws.com/role-arn: arn:aws:iam::1234567890:role/my-rds-autodiscovery-role - ``` - - -| Type | Default value | Required? | -| - | - | - | -| `list` | `[]` | No | - -`awsDatabases` is a YAML list object detailing the filters for the AWS databases that should be discovered and proxied by Teleport Database access. - -You can specify multiple database filters by adding additional list elements. - -- `types` is a list containing the types of AWS databases that should be discovered. -- `regions` is a list of AWS regions which should be scanned for databases. -- `tags` can be used to set AWS tags that must be matched for databases to be discovered. - - - - ```yaml - awsDatabases: - - types: ["rds"] - regions: ["us-east-1", "us-west-2"] - tags: - "environment": "production" - - types: ["rds"] - regions: ["us-east-1"] - tags: - "environment": "dev" - - types: ["rds"] - regions: ["eu-west-1"] - tags: - "*": "*" - ``` - - - YAML is very sensitive to correct spacing. When specifying lists in a `values.yaml` file, you might like - to [use a linter](https://codebeautify.org/yaml-validator) to validate your YAML list and ensure that it is correctly formatted. - - - - ```code - --set "awsDatabases[0].types[0]=rds" \ - --set "awsDatabases[0].regions[0]=us-east-1" \ - --set "awsDatabases[0].regions[1]=us-west-2" \ - --set "awsDatabases[0].tags[0].environment=production" \ - --set "awsDatabases[1].types[0]=rds" \ - --set "awsDatabases[1].regions[0]=us-east-1" \ - --set "awsDatabases[1].tags[0].environment=dev" \ - --set "awsDatabases[2].types[0]=rds" \ - --set "awsDatabases[2].regions[0]=eu-west-1" \ - --set "awsDatabases[2].tags[0].*=*" - ``` - - - Note that when using `--set` syntax, YAML list elements must be indexed starting at `0`. - - - - - -## `databases` - -| Type | Default value | Required? | -| - | - | - | -| `list` | `[]` | When `db` chart role is used | - -`databases` is a YAML list object detailing the databases that should be proxied by Teleport Database access. - -You can specify multiple databases by adding additional list elements. - - - - ```yaml - databases: - - name: aurora-postgres - uri: postgres-aurora-instance-1.xxx.us-east-1.rds.amazonaws.com:5432 - protocol: postgres - aws: - region: us-east-1 - static_labels: - env: staging - - name: mysql - uri: mysql-instance-1.xxx.us-east-1.rds.amazonaws.com:3306 - protocol: mysql - aws: - region: us-east-1 - static_labels: - env: staging - ``` - - - YAML is very sensitive to correct spacing. When specifying lists in a `values.yaml` file, you might like - to [use a linter](https://codebeautify.org/yaml-validator) to validate your YAML list and ensure that it is correctly formatted. - - - - ```code - $ --set "databases[0].name=aurora" \ - --set "databases[0].uri=postgres-aurora-instance-1.xxx.us-east-1.rds.amazonaws.com:5432" \ - --set "databases[0].protocol=postgres" \ - --set "databases[0].aws.region=us-east-1" \ - --set "databases[0].static_labels.env=staging" \ - --set "databases[1].name=mysql" \ - --set "databases[1].uri=mysql-instance-1.xxx.us-east-1.rds.amazonaws.com:3306" \ - --set "databases[1].protocol=mysql" \ - --set "databases[1].aws.region=us-east-1" \ - --set "databases[1].static_labels.env=staging" - ``` - - - Note that when using `--set` syntax, YAML list elements must be indexed starting at `0`. - - - - - - - You can see a list of all the supported [values which can be used in a Teleport database service configuration here](../../database-access/reference/configuration.mdx). - - -## `teleportVersionOverride` - -| Type | Default value | -| - | - | -| `string` | `nil` | - -Normally the version of Teleport being used will match the version of the chart being installed. If you install chart version -7.0.0, you'll be using Teleport 7.0.0. - -You can optionally override this to use a different published Teleport Docker image tag like `6.0.2` or `7`. - -See [this link for information on Community Docker image versions](../../setup/guides/docker.mdx#step-14-pick-your-image). - - - The `teleport-kube-agent` chart always runs using Teleport Community edition as it does not require any Enterprise features, so it does - not require a Teleport license file to be provided. - - - - - ```yaml - teleportVersionOverride: "7" - ``` - - - ```code - $ --set teleportVersionOverride="7" - ``` - - - -## `insecureSkipProxyTLSVerify` - -| Type | Default value | -| - | - | -| `bool` | `false` | - -When `insecureSkipProxyTLSVerify` is set to `true`, the agent will skip the verification of the TLS certificate presented by the Teleport -proxy server specified using [`proxyAddr`](#proxyaddr). - -This can be used for joining a Teleport agent to a Teleport cluster which does not have valid TLS certificates for testing. - - - - ```yaml - insecureSkipProxyTLSVerify: false - ``` - - - ```code - $ --set insecureSkipProxyTLSVerify=false - ``` - - - - - Using a self-signed TLS certificate and disabling TLS verification is OK for testing, but is not viable when running a production Teleport - cluster as it will drastically reduce security. You must configure valid TLS certificates on your Teleport cluster for production workloads. - - One option might be to use Teleport's built-in [ACME support](#acme) enable [cert-manager support](#highavailabilitycertmanager). - - -## `existingDataVolume` - -| Type | Default value | -| - | - | -| `string` | `""` | - -When `existingDataVolume` is set to the name of an existing volume, the `/var/lib/teleport` mount will use this volume instead of creating a new `emptyDir` volume. - - - - ```yaml - existingDataVolume: my-volume - ``` - - - ```bash - --set existingDataVolume=my-volume - ``` - - - -## `podSecurityPolicy` - -### `podSecurityPolicy.enabled` - -| Type | Default value | -| - | - | -| `bool` | `true` | - -By default, Teleport charts also install a [`podSecurityPolicy`](https://github.com/gravitational/teleport/blob/master/examples/chart/teleport-kube-agent/templates/psp.yaml). - -To disable this, you can set `enabled` to `false`. - -[Kubernetes reference](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) - - - - ```yaml - podSecurityPolicy: - enabled: false - ``` - - - ```code - $ --set podSecurityPolicy.enabled=false - ``` - - - -## `labels` - -| Type | Default value | -| - | - | -| `object` | `{}` | - -`labels` can be used to add a map of key-value pairs for the `kubernetes_service` which is deployed using the `teleport-kube-agent` chart. -These labels can then be used with Teleport's RBAC policies to define access rules for the cluster. - - - These are Teleport-specific RBAC labels, not Kubernetes labels. - - - - For historical/backwards compatibility reasons, these labels will only be applied to the Kubernetes cluster being joined via the - Teleport Kubernetes service. - - To set labels for applications, add a `labels` element to the [`apps`](#apps) section. - To set labels for databases, add a `static_labels` element to the [`databases`](#databases) section. - - For more information on how to set static/dynamic labels for Teleport services, see [labelling nodes and applications](../../setup/admin/labels.mdx). - - - - - ```yaml - labels: - environment: production - region: us-east - ``` - - - ```code - $ --set labels.environment=production \ - --set labels.region=us-east - ``` - - - -## `storage` - -### `storage.enabled` - -| Type | Default value | -| - | - | -| `bool` | `false` | - -Enables the creation of a Kubernetes persistent volume to hold Teleport agent state. - -[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) - - - - ```yaml - storage: - enabled: true - ``` - - - ```bash - --set storage.enabled=true - ``` - - - -### `storage.storageClassName` - -| Type | Default value | -| - | - | -| `string` | `nil` | - -The storage class name the persistent volume should use when creating persistent volume claims. The provided storage class -name needs to exist on the Kubernetes cluster for Teleport to use. - -[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/storage-classes/) - - - - ```yaml - storage: - storageClassName: teleport-storage-class - ``` - - - ```bash - --set storage.storageClassName=teleport-storage-class - ``` - - - -### `storage.requests` - -| Type | Default value | -| - | - | -| `string` | `128Mi` | - -The size of persistent volume to create. - - - - ```yaml - storage: - requests: 128Mi - ``` - - - ```bash - --set storage.requests=128Mi - ``` - - - -## `image` - -| Type | Default value | -| - | - | -| `string` | `quay.io/gravitational/teleport` | - -`image` sets the container image used for Teleport pods run by the `teleport-kube-agent` chart. - -You can override this to use your own Teleport image rather than a Teleport-published image. - - - - ```yaml - image: my.docker.registry/teleport-image-name - ``` - - - ```code - $ --set image=my.docker.registry/teleport-image-name - ``` - - - -## `imagePullSecrets` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod) - -A list of secrets containing authorization tokens which can be optionally used to access a private Docker registry. - - - - ```yaml - imagePullSecrets: - - name: my-docker-registry-key - ``` - - - ```shell - --set "imagePullSecrets[0].name=my-docker-registry-key" - ``` - - - -### `highAvailability` - -## `highAvailability.replicaCount` - -| Type | Default value | -| - | - | -| `int` | `1` | - -`highAvailability.replicaCount` can be used to set the number of replicas used in the deployment. - -Set to a number higher than `1` for a high availability mode where multiple Teleport agent pods will be deployed. - - - As a rough guide, we recommend configuring one replica per distinct availability zone where your cluster has worker nodes. - - 2 replicas/availability zones will be fine for smaller workloads. 3-5 replicas/availability zones will be more appropriate for bigger - clusters with more traffic. - - - - - ```yaml - highAvailability: - replicaCount: 3 - ``` - - - ```shell - --set highAvailability.replicaCount=3 - ``` - - - -## `highAvailability.requireAntiAffinity` - -| Type | Default value | -| - | - | -| `bool` | `false` | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity) - -Setting `highAvailability.requireAntiAffinity` to `true` will use `requiredDuringSchedulingIgnoredDuringExecution` to require that multiple -Teleport pods must not be scheduled on the same physical host. - - - This can result in Teleport pods failing to be scheduled in very small clusters or during node downtime, so should be used with caution. - - - Setting `highAvailability.requireAntiAffinity` to `false` (the default) uses `preferredDuringSchedulingIgnoredDuringExecution` to make node - anti-affinity a soft requirement. - - - This setting only has any effect when `highAvailability.replicaCount` is greater than `1`. - - - - - ```yaml - highAvailability: - requireAntiAffinity: true - ``` - - - ```shell - --set highAvailability.requireAntiAffinity=true - ``` - - - -## `highAvailability.podDisruptionBudget` - -### `highAvailability.podDisruptionBudget.enabled` - -| Type | Default value | -| - | - | -| `bool` | `false` | - -[Kubernetes reference](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) - -Enable a Pod Disruption Budget for the Teleport Pod to ensure HA during voluntary disruptions. - - - - ```yaml - highAvailability: - podDisruptionBudget: - enabled: true - ``` - - - ```shell - --set highAvailability.podDisruptionBudget.enabled=true - ``` - - - -### `highAvailability.podDisruptionBudget.minAvailable` - -| Type | Default value | -| - | - | -| `int` | `1` | - -[Kubernetes reference](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) - -Ensures that this number of replicas is available during voluntary disruptions, can be a number of replicas or a percentage. - - - - ```yaml - highAvailability: - podDisruptionBudget: - minAvailable: 1 - ``` - - - ```shell - --set highAvailability.podDisruptionBudget.minAvailable=1 - ``` - - - -## `clusterRoleName` - -| Type | Default value | -| - | - | -| `string` | `nil` | - -`clusterRoleName` can be optionally used to override the name of the Kubernetes `ClusterRole` used by the `teleport-kube-agent` chart's `ServiceAccount`. - - - Most users will not need to change this. - - - - - ```yaml - clusterRoleName: kubernetes-clusterrole - ``` - - - ```code - $ --set clusterRoleName=kubernetes-clusterrole - ``` - - - -## `clusterRoleBindingName` - - - Most users will not need to change this. - - -| Type | Default value | -| - | - | -| `string` | `nil` | - - -`clusterRoleBindingName` can be optionally used to override the name of the Kubernetes `ClusterRoleBinding` used by the `teleport-kube-agent` chart's `ServiceAccount`. - - - - ```yaml - clusterRoleBindingName: kubernetes-clusterrolebinding - ``` - - - ```code - $ --set clusterRoleBindingName=kubernetes-clusterrolebinding - ``` - - - -## `serviceAccountName` - - - Most users will not need to change this. - - -| Type | Default value | -| - | - | -| `string` | `nil` | - -`serviceAccountName` can be optionally used to override the name of the Kubernetes `ServiceAccount` used by the `teleport-kube-agent` chart. - - - - ```yaml - serviceAccountName: kubernetes-serviceaccount - ``` - - - ```code - $ --set serviceAccountName=kubernetes-serviceaccount - ``` - - - -## `secretName` - -| Type | Default value | -| - | - | -| `string` | `teleport-kube-agent-join-token` | - -`secretName` is the name of the Kubernetes `Secret` used to store the Teleport join token which is used by the `teleport-kube-agent` chart. - -If you set this to a blank value, the chart will not attempt to create the secret itself and will instead read the value of the -existing `teleport-kube-agent-join-token` secret. This allows you to configure this secret externally and avoid having a plaintext -join token stored in your Teleport chart values. - -To create your own join token secret, you can use a command like this: - -```code -$ kubectl --namespace teleport create secret generic teleport-kube-agent-join-token --from-literal=auth-token= -``` - - - The key used for the auth token inside the secret must be `auth-token`, as in the command above. - - - - - ```yaml - serviceAccountName: - ``` - - - ```code - $ --set serviceAccountName="" - ``` - - - -## `log` - -### `log.level` - - - This field used to be called `logLevel`. For backwards compatibility this name can still be used, but we recommend changing your values file to use `log.level`. - - -| Type | Default value | -| - | - | -| `string` | `INFO` | - -`log.level` sets the log level used for the Teleport process. - -Available log levels (in order of most to least verbose) are: `DEBUG`, `INFO`, `WARNING`, `ERROR`. - -The default is `INFO`, which is recommended in production. - -`DEBUG` is useful during first-time setup or to see more detailed logs for debugging. - - - - ```yaml - log: - level: DEBUG - ``` - - - ```code - --set log.level=DEBUG - ``` - - - -### `log.output` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `string` | `stderr` | ❌ | `teleport.log.output` | - -`log.output` sets the output destination for the Teleport process. - -This can be set to any of the built-in values: `stdout`, `stderr` or `syslog` to use that destination. - -The value can also be set to a file path (such as `/var/log/teleport.log`) to write logs to a file. Bear in mind that a few service startup messages will still go to `stderr` for resilience. - - - - ```yaml - log: - output: stderr - ``` - - - ```code - --set log.output=stderr - ``` - - - -### `log.format` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `string` | `text` | ❌ | `teleport.log.format.output` | - -`log.format` sets the output type for the Teleport process. - -Possible values are `text` (default) or `json`. - - - - ```yaml - log: - format: json - ``` - - - ```code - --set log.format=json - ``` - - - -### `log.extraFields` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `list` | `["timestamp", "level", "component", "caller"]` | ❌ | `teleport.log.format.extra_fields` | - -`log.extraFields` sets the fields used in logging for the Teleport process. - -See the [Teleport config file reference](../../setup/reference/config.mdx) for more details on possible values for `extra_fields`. - - - - ```yaml - log: - extraFields: ["timestamp", "level"] - ``` - - - ```code - --set "log.extraFields[0]=timestamp" \ - --set "log.extraFields[1]=level" - ``` - - - -## `affinity` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) - -Kubernetes affinity to set for pod assignments. - - - You cannot set both `affinity` and `highAvailability.requireAntiAffinity` as they conflict with each other. - - - - - ```yaml - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: gravitational.io/dedicated - operator: In - values: - - teleport - ``` - - - ```code - $ --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key=gravitational.io/dedicated \ - --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator=In \ - --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]=teleport - ``` - - - -## `nodeSelector` - -| Type | Default value | -| - | - | -| `object` | `{}` | - -`nodeSelector` can be used to add a map of key-value pairs to constrain the nodes the agent pods will run on. - -[Kubernetes reference](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) - - - - ```yaml - nodeSelector: - role: node - region: us-east - ``` - - - ```bash - --set nodeSelector.role=node \ - --set nodeSelector.region=us-east - ``` - - - -## `annotations.config` - -| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | -| - | - | - | - | -| `object` | `{}` | ❌ | None | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - -Kubernetes annotations which should be applied to the `ConfigMap` created by the chart. - - - These annotations will not be applied in `custom` mode, as the `ConfigMap` is not managed by the chart. - In this instance, you should apply annotations manually to your created `ConfigMap`. - - - - - ```yaml - annotations: - config: - kubernetes.io/annotation: value - ``` - - - ```code - $ --set annotations.config."kubernetes\.io\/annotation"=value - ``` - - You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend - using a `values.yaml` file instead to avoid confusion and errors. - - - - -## `annotations.deployment` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - -Kubernetes annotations which should be applied to the `Deployment` created by the chart. - - - - ```yaml - annotations: - deployment: - kubernetes.io/annotation: value - ``` - - - ```code - $ --set annotations.deployment."kubernetes\.io\/annotation"=value - ``` - - You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend - using a `values.yaml` file instead to avoid confusion and errors. - - - - -## `annotations.pod` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - -Kubernetes annotations which should be applied to each `Pod` created by the chart. - - - - ```yaml - annotations: - pod: - kubernetes.io/annotation: value - ``` - - - ```code - $ --set annotations.pod."kubernetes\.io\/annotation"=value - ``` - - You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend - using a `values.yaml` file instead to avoid confusion and errors. - - - - -## `annotations.serviceAccount` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - -Kubernetes annotations which should be applied to the `ServiceAccount` created by the chart. - - - - ```yaml - annotations: - serviceAccount: - kubernetes.io/annotation: value - ``` - - - ```code - $ --set annotations.serviceAccount."kubernetes\.io\/annotation"=value - ``` - - You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend - using a `values.yaml` file instead to avoid confusion and errors. - - - - -## `extraVolumes` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/volumes/) - -A list of extra Kubernetes `Volumes` which should be available to any `Pod` created by the chart. These volumes -will also be available to any `initContainers` configured by the chart. - - - - ```yaml - extraVolumes: - - name: myvolume - secret: - secretName: mysecret - ``` - - - ```code - $ --set "extraVolumes[0].name=myvolume" \ - --set "extraVolumes[0].secret.secretName=mysecret" - ``` - - - -## `extraVolumeMounts` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/volumes/) - -A list of extra Kubernetes volume mounts which should be mounted into any `Pod` created by the chart. These volume -mounts will also be mounted into any `initContainers` configured by the chart. - - - - ```yaml - extraVolumeMounts: - - name: myvolume - mountPath: /path/to/mount/volume - ``` - - - ```code - $ --set "extraVolumeMounts[0].name=myvolume" \ - --set "extraVolumeMounts[0].path=/path/to/mount/volume" - ``` - - - -## `imagePullPolicy` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `string` | `IfNotPresent` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/containers/images/#updating-images) - -Allows the `imagePullPolicy` for any pods created by the chart to be overridden. - - - - ```yaml - imagePullPolicy: Always - ``` - - - ```code - $ --set imagePullPolicy=Always - ``` - - - -## `initContainers` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) - -A list of `initContainers` which will be run before the main Teleport container in any pod created by the chart. - - - - ```yaml - initContainers: - - name: teleport-init - image: alpine - args: ['echo test'] - ``` - - - ```code - $ --set "initContainers[0].name=teleport-init" \ - --set "initContainers[0].image=alpine" \ - --set "initContainers[0].args={echo test}" - ``` - - - -## `resources` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `object` | `{}` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) - -Resource requests/limits which should be configured for each container inside the pod. These resource limits -will also be applied to `initContainers`. - - - - ```yaml - resources: - requests: - cpu: 1 - memory: 2Gi - ``` - - - ```code - $ --set resources.requests.cpu=1 \ - --set resources.requests.memory=2Gi - ``` - - - -## `tolerations` - -| Type | Default value | Can be used in `custom` mode? | -| - | - | - | -| `list` | `[]` | ✅ | - -[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) - -Kubernetes Tolerations to set for pod assignment. - - - - ```yaml - tolerations: - - key: "dedicated" - operator: "Equal" - value: "teleport" - effect: "NoSchedule" - ``` - - - ```code - $ --set tolerations[0].key=dedicated \ - --set tolerations[0].operator=Equal \ - --set tolerations[0].value=teleport \ - --set tolerations[0].effect=NoSchedule - ``` - - + + \ No newline at end of file diff --git a/docs/pages/kubernetes-access/helm/reference/teleport-cluster.mdx b/docs/pages/kubernetes-access/helm/reference/teleport-cluster.mdx new file mode 100644 index 0000000000000..d4205880d4cf9 --- /dev/null +++ b/docs/pages/kubernetes-access/helm/reference/teleport-cluster.mdx @@ -0,0 +1,1348 @@ +--- +title: teleport-cluster Chart Reference +description: Values that can be set using the teleport-cluster Helm chart +--- + +The `teleport-cluster` Helm chart is used to deploy the Teleport Auth Service +and Proxy Service, which is ideal for getting started with a self-hosted Teleport +cluster on Kubernetes. + + + +Teleport Cloud manages the Auth Service and Proxy Service for you. Use the +`teleport-cluster` chart if you are interested in hosting Teleport yourself, or +use the [`teleport-kube-agent`](./teleport-kube-agent.mdx) chart to deploy the +Kubernetes Service, Application Service, or Database Service. + + + +You can +[browse the source on GitHub](https://github.com/gravitational/teleport/tree/master/examples/chart/teleport-cluster). + +The `teleport-cluster` chart runs two Teleport services: + +| Teleport service | Purpose | Documentation | +| - | - | - | +| `auth_service` | Authenticates users and hosts, and issues certificates | [Auth documentation](../../../architecture/authentication.mdx) +| `proxy_service`| Runs the externally-facing parts of a Teleport cluster, such as the web UI, SSH proxy and reverse tunnel service | [Proxy documentation](../../../architecture/proxy.mdx) | + +The `teleport-cluster` chart can be deployed in four different modes. Get started with a guide for each mode: + +| `chartMode` | Guide | +| - | - | +| `standalone` | [Getting started with Kubernetes Access](../../../getting-started.mdx) | +| `aws` | [Running an HA Teleport cluster using an AWS EKS Cluster](../guides/aws.mdx) | +| `gcp` | [Running an HA Teleport cluster using a Google Cloud GKE cluster](../guides/gcp.mdx) | +| `custom` | [Running a Teleport cluster with a custom config](../guides/custom.mdx) | + +This reference details available values for the `teleport-cluster` chart. + +(!docs/pages/includes/backup-warning.mdx!) + +## `clusterName` + +| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | +| - | - | - | - | - | +| `string` | `nil` | When `chartMode` is `aws`, `gcp` or `standalone` | `auth_service.cluster_name`, `proxy_service.public_addr` | ✅ | + +`clusterName` controls the name used to refer to the Teleport cluster, along with the externally-facing public address to use to access it. + + + If using a fully qualified domain name as your `clusterName`, you will also need to configure the DNS provider for this domain to point + to the external load balancer address of your Teleport cluster. + + (!docs/pages/kubernetes-access/helm/includes/kubernetes-externaladdress.mdx!) + + You will need to manually add a DNS A record pointing `teleport.example.com` to either the IP or hostname of the Kubernetes load balancer. + +
    +(!docs/pages/includes/dns-app-access.mdx!) +
    + + If you are not using ACME certificates, you may also need to accept insecure warnings in your browser to view the page successfully. +
    + + +## `kubeClusterName` + +| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | +| - | - | - | - | - | +| `string` | `clusterName` value | no | `kubernetes_service.kube_cluster_name` | ✅ | + +`kubeClusterName` sets the name used for the Kubernetes cluster. This name will be shown to Teleport users connecting to the cluster. + +## `authenticationType` + +| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | +| - | - | - | - | - | +| `string` | `local` | Yes | `auth_service.authentication.type` | ❌ | + +`authenticationType` controls the authentication scheme used by Teleport. Possible values are `local` and `github` for OSS, plus `oidc`, `saml`, and `false` for Enterprise. + +## `authenticationSecondFactor` + +### `authenticationSecondFactor.secondFactor` + +| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | +| - | - | - | - | - | +| `string` | `otp` | Yes | `auth_service.authentication.second_factor` | ❌ | + +`authenticationSecondFactor.secondFactor` controls the second factor used for local user authentication. Possible values supported by this chart +are `off` (not recommended), `on`, `otp`, `optional` and `webauthn`. + +When set to `on`, `optional` or `webauthn`, the `authenticationSecondFactor.webauthn` section can also be used. The configured `rp_id` defaults to +the FQDN which is used to access the Teleport cluster. + +### `authenticationSecondFactor.webauthn` + +See [Second Factor - WebAuthn](../../../access-controls/guides/webauthn.mdx) for more details. + +#### `authenticationSecondFactor.webauthn.attestationAllowedCas` + +| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | +| - | - | - | - | - | +| `array` | `[]` | No | `auth_service.authentication.webauthn.attestation_allowed_cas` | ❌ | + +`authenticationSecondFactor.webauthn.attestationAllowedCas` is an optional allow list of certificate authorities (as local file paths or in-line PEM certificate string) for +[device verification](https://developers.yubico.com/WebAuthn/WebAuthn_Developer_Guide/Attestation.html). +This field allows you to restrict which device models and vendors you trust. +Devices outside of the list will be rejected during registration. +By default all devices are allowed. + +#### `authenticationSecondFactor.webauthn.attestationDeniedCas` + +| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | +| - | - | - | - | - | +| `array` | `[]` | No | `auth_service.authentication.webauthn.attestation_denied_cas` | ❌ | + +`authenticationSecondFactor.webauthn.attestationDeniedCas` is optional deny list of certificate authorities (as local file paths or in-line PEM certificate string) for +[device verification](https://developers.yubico.com/WebAuthn/WebAuthn_Developer_Guide/Attestation.html). +This field allows you to forbid specific device models and vendors, while allowing all others (provided they clear attestation_allowed_cas as well). +Devices within this list will be rejected during registration. +By default no devices are forbidden. + +## `proxyListenerMode` + +| Type | Default value | Required? | `teleport.yaml` equivalent | Can be used in `custom` mode? | +| - | - | - | - | - | +| `string` | `nil` | no | `auth_service.proxy_listener_mode` | ❌ | + +`proxyListenerMode` controls proxy TLS routing used by Teleport. Possible values are `multiplex`. + +## `enterprise` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `bool` | `false`| ✅ | + +`enterprise` controls whether to use Teleport Community Edition or Teleport Enterprise. + +Setting `enterprise` to `true` will use the Teleport Enterprise image. + +You will also need to download your Enterprise license from the Teleport dashboard and add it as a Kubernetes secret to use this: + +```code +$ kubectl --namespace teleport create secret generic license --from-file=/path/to/downloaded/license.pem +``` + + + If you installed the Teleport chart into a specific namespace, the `license` secret you create must also be added to the same namespace. + + + + The file added to the secret must be called `license.pem`. If you have renamed it, you can specify the filename to use in the secret creation command: + + ```code + $ kubectl --namespace teleport create secret generic license --from-file=license.pem=/path/to/downloaded/this-is-my-teleport-license.pem + ``` + + + + + ```yaml + enterprise: true + ``` + + + ```code + $ --set enterprise=true + ``` + + + +## `teleportVersionOverride` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `string` | `nil` | ✅ | + +Normally the version of Teleport being used will match the version of the chart being installed. If you install chart version +7.0.0, you'll be using Teleport 7.0.0. Upgrading the Helm chart will use the latest version from the repo. + +You can optionally override this to use a different published Teleport Docker image tag like `6.0.2` or `7`. + +See these links for information on Docker image versions: + +- [Community Docker image information](../../../setup/guides/docker.mdx#step-14-pick-your-image) +- [Enterprise Docker image information](../../../enterprise/getting-started.mdx#run-teleport-enterprise-using-docker) + + + + ```yaml + teleportVersionOverride: "7" + ``` + + + ```code + $ --set teleportVersionOverride="7" + ``` + + + +## `acme` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `bool` | `false` | ❌ | `proxy_service.acme.enabled` | + +ACME is a protocol for getting Web X.509 certificates. + +Setting acme to `true` enables the ACME protocol and will attempt to get a free TLS certificate from Let's Encrypt. +Setting acme to `false` (the default) will cause Teleport to generate and use self-signed certificates for its web UI. + + + ACME can only be used for single-pod clusters. It is not suitable for use in HA configurations. + + + + Using a self-signed TLS certificate and disabling TLS verification is OK for testing, but is not viable when running a production Teleport + cluster as it will drastically reduce security. You must configure valid TLS certificates on your Teleport cluster for production workloads. + + One option might be to use Teleport's built-in ACME support or enable [cert-manager support](#highavailabilitycertmanager). + + +## `acmeEmail` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `string` | `nil` | ❌ | `proxy_service.acme.email` | + +`acmeEmail` is the email address to provide during certificate registration (this is a Let's Encrypt requirement). + +## `acmeURI` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `string` | Let's Encrypt production server | ❌ | `proxy_service.acme.uri` | + +`acmeURI` is the ACME server to use for getting certificates. + +As an example, this can be overridden to use the [Let's Encrypt staging server](https://letsencrypt.org/docs/staging-environment/) for testing. + +You can also use any other ACME-compatible server. + + + + ```yaml + acme: true + acmeEmail: user@email.com + acmeURI: https://acme-staging-v02.api.letsencrypt.org/directory + ``` + + + ```code + $ --set acme=true \ + --set acmeEmail=user@email.com \ + --set acmeURI=https://acme-staging-v02.api.letsencrypt.org/directory + ``` + + + +## `podSecurityPolicy` + +### `podSecurityPolicy.enabled` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `bool` | `true` | ✅ | + +By default, Teleport charts also install a [`podSecurityPolicy`](https://github.com/gravitational/teleport/blob/master/examples/chart/teleport-cluster/templates/psp.yaml). + +To disable this, you can set `enabled` to `false`. + +[Kubernetes reference](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) + + + + ```yaml + podSecurityPolicy: + enabled: false + ``` + + + ```code + $ --set podSecurityPolicy.enabled=false + ``` + + + +## `labels` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `object` | `{}` | ❌ | `kubernetes_service.labels` | + +`labels` can be used to add a map of key-value pairs relating to the Teleport cluster being deployed. These labels can then be used with +Teleport's RBAC policies to define access rules for the cluster. + + + These are Teleport-specific RBAC labels, not Kubernetes labels. + + + + + ```yaml + labels: + environment: production + region: us-east + ``` + + + ```code + $ --set labels.environment=production \ + --set labels.region=us-east + ``` + + + +## `chartMode` + +| Type | Default value | +| - | - | +| `string` | `standalone` | + +`chartMode` is used to configure the chart's operation mode. You can find more information about each mode on its specific guide page: + +| `chartMode` | Guide | +| - | - | +| `standalone` | [Getting started with Kubernetes Access](../../../getting-started.mdx) | +| `aws` | [Running an HA Teleport cluster using an AWS EKS Cluster](../guides/aws.mdx) | +| `gcp` | [Running an HA Teleport cluster using a Google Cloud GKE cluster](../guides/gcp.mdx) | +| `custom` | [Running a Teleport cluster with a custom config](../guides/custom.mdx) | + +## `standalone` + +### `standalone.existingClaimName` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `string` | `nil` | ✅ | + +`standalone.existingClaimName` can be used to provide the name of a pre-existing `PersistentVolumeClaim` to use if desired. + +The default is left blank, which will automatically create a `PersistentVolumeClaim` to use for Teleport storage in `standalone` mode. + + + + ```yaml + standalone: + existingClaimName: my-existing-pvc-name + ``` + + + ```code + $ --set standalone.existingClaimName=my-existing-pvc-name + ``` + + + +### `standalone.volumeSize` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `string` | `10Gi` | ✅ | + +You can set `volumeSize` to request a different size of persistent volume when installing the Teleport chart in `standalone` mode. + + + `volumeSize` will be ignored if `existingClaimName` is set. + + + + + ```yaml + standalone: + volumeSize: 50Gi + ``` + + + ```code + --set standalone.volumeSize=50Gi + ``` + + + +## `aws` + +| Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | +| ❌ | See [Using DynamoDB](../../../setup/reference/backends.mdx#dynamodb) and [Using Amazon S3](../../../setup/reference/backends.mdx#s3) for details | + +`aws` settings are described in the AWS guide: [Running an HA Teleport cluster using an AWS EKS Cluster](../guides/aws.mdx) + +## `gcp` + +| Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | +| ❌ | See [Using Firestore](../../../setup/reference/backends.mdx#dynamodb) and [Using GCS](../../../setup/reference/backends.mdx#gcs) for details | + +`gcp` settings are described in the GCP guide: [Running an HA Teleport cluster using a Google Cloud GKE cluster](../guides/gcp.mdx) + +### `highAvailability` + +## `highAvailability.replicaCount` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `int` | `1` | ✅ (when using HA storage) | + +`highAvailability.replicaCount` can be used to set the number of replicas used in the deployment. + +Set to a number higher than `1` for a high availability mode where multiple Teleport pods will be deployed and connections will be load balanced between them. + + + Setting `highAvailability.replicaCount` to a value higher than `1` will disable the use of ACME certs. + + + + As a rough guide, we recommend configuring one replica per distinct availability zone where your cluster has worker nodes. + + 2 replicas/availability zones will be fine for smaller workloads. 3-5 replicas/availability zones will be more appropriate for bigger + clusters with more traffic. + + + + When using `custom` mode, you **must** use highly-available storage (e.g. etcd, DynamoDB or Firestore) for multiple replicas to be supported. + + [Information on supported Teleport storage backends](../../../architecture/authentication.mdx#storage-back-ends) + + Manually configuring NFS-based storage or `ReadWriteMany` volume claims is **NOT** supported for an HA deployment and will result in errors. + + + + + ```yaml + highAvailability: + replicaCount: 3 + ``` + + + ```code + $ --set highAvailability.replicaCount=3 + ``` + + + +## `highAvailability.requireAntiAffinity` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `bool` | `false` | ✅ (when using HA storage) | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity) + +Setting `highAvailability.requireAntiAffinity` to `true` will use `requiredDuringSchedulingIgnoredDuringExecution` to require that multiple +Teleport pods must not be scheduled on the same physical host. + + + This can result in Teleport pods failing to be scheduled in very small clusters or during node downtime, so should be used with caution. + + + Setting `highAvailability.requireAntiAffinity` to `false` (the default) uses `preferredDuringSchedulingIgnoredDuringExecution` to make node + anti-affinity a soft requirement. + + + This setting only has any effect when `highAvailability.replicaCount` is greater than `1`. + + + + + ```yaml + highAvailability: + requireAntiAffinity: true + ``` + + + ```code + $ --set highAvailability.requireAntiAffinity=true + ``` + + + +## `highAvailability.podDisruptionBudget` + +### `highAvailability.podDisruptionBudget.enabled` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `bool` | `false` | ✅ (when using HA storage) | + +[Kubernetes reference](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) + +Enable a Pod Disruption Budget for the Teleport Pod to ensure HA during voluntary disruptions. + + + + ```yaml + highAvailability: + podDisruptionBudget: + enabled: true + ``` + + + ```shell + --set highAvailability.podDisruptionBudget.enabled=true + ``` + + + +### `highAvailability.podDisruptionBudget.minAvailable` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `int` | `1` | ✅ (when using HA storage) | + +[Kubernetes reference](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) + +Ensures that this number of replicas is available during voluntary disruptions, can be a number of replicas or a percentage. + + + + ```yaml + highAvailability: + podDisruptionBudget: + minAvailable: 1 + ``` + + + ```shell + --set highAvailability.podDisruptionBudget.minAvailable=1 + ``` + + + +## `highAvailability.certManager` + +See the [cert-manager](https://cert-manager.io/docs/) docs for more information. + +### `highAvailability.certManager.enabled` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `bool` | `false` | ❌ | `proxy_service.https_keypairs` (to provide your own certificates) | + +Setting `highAvailability.certManager.enabled` to `true` will use `cert-manager` to provision a TLS certificate for a Teleport +cluster deployed in HA mode. + + + You must install and configure `cert-manager` in your Kubernetes cluster yourself. + + See the [cert-manager Helm install instructions](https://cert-manager.io/docs/installation/kubernetes/#option-2-install-crds-as-part-of-the-helm-release) + and the relevant sections of the [AWS](../guides/aws.mdx#step-47-install-and-configure-cert-manager) and [GCP](../guides/gcp.mdx#step-47-install-and-configure-cert-manager) guides for more information. + + +### `highAvailability.certManager.addCommonName` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `bool` | `false` | ❌ | `proxy_service.https_keypairs` (to provide your own certificates) | + +Setting `highAvailability.certManager.addCommonName` to `true` will instruct `cert-manager` to set the commonName field in its certificate signing request to the issuing CA. + + + You must install and configure `cert-manager` in your Kubernetes cluster yourself. + + See the [cert-manager Helm install instructions](https://cert-manager.io/docs/installation/kubernetes/#option-2-install-crds-as-part-of-the-helm-release) + and the relevant sections of the [AWS](../guides/aws.mdx#step-47-install-and-configure-cert-manager) and [GCP](../guides/gcp.mdx#step-47-install-and-configure-cert-manager) guides for more information. + + + + + ```yaml + highAvailability: + certManager: + enabled: true + addCommonName: true + issuerName: letsencrypt-production + ``` + + + ```code + $ --set highAvailability.certManager.enabled=true \ + --set highAvailability.certManager.addCommonName=true \ + --set highAvailability.certManager.issuerName=letsencrypt-production + ``` + + + +### `highAvailability.certManager.issuerName` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `string` | `nil` | ❌ | None | + +Sets the name of the `cert-manager` `Issuer` or `ClusterIssuer` to use for issuing certificates. + + + You must install configure an appropriate `Issuer` supporting a DNS01 challenge yourself. + + Please see the [cert-manager DNS01 docs](https://cert-manager.io/docs/configuration/acme/dns01/#supported-dns01-providers) and the relevant sections + of the [AWS](../guides/aws.mdx#step-47-install-and-configure-cert-manager) and [GCP](../guides/gcp.mdx#step-47-install-and-configure-cert-manager) guides for more information. + + + + + ```yaml + highAvailability: + certManager: + enabled: true + issuerName: letsencrypt-production + ``` + + + ```code + $ --set highAvailability.certManager.enabled=true \ + --set highAvailability.certManager.issuerName=letsencrypt-production + ``` + + + +### `highAvailability.certManager.issuerKind` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `string` | `Issuer` | ❌ | None | + +Sets the `Kind` of `Issuer` to be used when issuing certificates with `cert-manager`. Defaults to `Issuer` to keep permissions +scoped to a single namespace. + + + + ```yaml + highAvailability: + certManager: + issuerKind: ClusterIssuer + ``` + + + ```code + --set highAvailability.certManager.issuerKind=ClusterIssuer + ``` + + + +### `highAvailability.certManager.issuerGroup` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `string` | `cert-manager.io` | ❌ | None | + +Sets the `Group` of `Issuer` to be used when issuing certificates with `cert-manager`. Defaults to `cert-manager.io` to use built-in issuers. + + + + ```yaml + highAvailability: + certManager: + issuerGroup: cert-manager.io + ``` + + + ```code + --set highAvailability.certManager.issuerGroup=cert-manager.io + ``` + + + +## `image` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `string` | `quay.io/gravitational/teleport` | ✅ | + +`image` sets the container image used for Teleport Community pods in the cluster. + +You can override this to use your own Teleport Community image rather than a Teleport-published image. + + + + ```yaml + image: my.docker.registry/teleport-community-image-name + ``` + + + ```code + --set image=my.docker.registry/teleport-community-image-name + ``` + + + +## `enterpriseImage` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `string` | `quay.io/gravitational/teleport-ent` | ✅ | + +`enterpriseImage` sets the container image used for Teleport Enterprise pods in the cluster. + +You can override this to use your own Teleport Enterprise image rather than a Teleport-published image. + + + + ```yaml + enterpriseImage: my.docker.registry/teleport-enterprise-image-name + ``` + + + ```code + --set enterpriseImage=my.docker.registry/teleport-enterprise-image + ``` + + + +## `log` + +### `log.level` + + + This field used to be called `logLevel`. For backwards compatibility this name can still be used, but we recommend changing your values file to use `log.level`. + + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `string` | `INFO` | ❌ | `teleport.log.severity` | + +`log.level` sets the log level used for the Teleport process. + +Available log levels (in order of most to least verbose) are: `DEBUG`, `INFO`, `WARNING`, `ERROR`. + +The default is `INFO`, which is recommended in production. + +`DEBUG` is useful during first-time setup or to see more detailed logs for debugging. + + + + ```yaml + log: + level: DEBUG + ``` + + + ```code + --set log.level=DEBUG + ``` + + + +### `log.output` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `string` | `stderr` | ❌ | `teleport.log.output` | + +`log.output` sets the output destination for the Teleport process. + +This can be set to any of the built-in values: `stdout`, `stderr` or `syslog` to use that destination. + +The value can also be set to a file path (such as `/var/log/teleport.log`) to write logs to a file. Bear in mind that a few service startup messages will still go to `stderr` for resilience. + + + + ```yaml + log: + output: stderr + ``` + + + ```code + --set log.output=stderr + ``` + + + +### `log.format` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `string` | `text` | ❌ | `teleport.log.format.output` | + +`log.format` sets the output type for the Teleport process. + +Possible values are `text` (default) or `json`. + + + + ```yaml + log: + format: json + ``` + + + ```code + --set log.format=json + ``` + + + +### `log.extraFields` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `list` | `["timestamp", "level", "component", "caller"]` | ❌ | `teleport.log.format.extra_fields` | + +`log.extraFields` sets the fields used in logging for the Teleport process. + +See the [Teleport config file reference](../../../setup/reference/config.mdx) for more details on possible values for `extra_fields`. + + + + ```yaml + log: + extraFields: ["timestamp", "level"] + ``` + + + ```code + --set "log.extraFields[0]=timestamp" \ + --set "log.extraFields[1]=level" + ``` + + + +## `affinity` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + +Kubernetes affinity to set for pod assignments. + + + You cannot set both `affinity` and `highAvailability.requireAntiAffinity` as they conflict with each other. Only set one or the other. + + + + + ```yaml + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: gravitational.io/dedicated + operator: In + values: + - teleport + ``` + + + ```code + $ --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key=gravitational.io/dedicated \ + --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator=In \ + --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]=teleport + ``` + + + +## `annotations.config` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `object` | `{}` | ❌ | None | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +Kubernetes annotations which should be applied to the `ConfigMap` created by the chart. + + + These annotations will not be applied in `custom` mode, as the `ConfigMap` is not managed by the chart. + In this instance, you should apply annotations manually to your created `ConfigMap`. + + + + + ```yaml + annotations: + config: + kubernetes.io/annotation: value + ``` + + + ```code + $ --set annotations.config."kubernetes\.io\/annotation"=value + ``` + + You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend + using a `values.yaml` file instead to avoid confusion and errors. + + + + +## `annotations.deployment` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +Kubernetes annotations which should be applied to the `Deployment` created by the chart. + + + + ```yaml + annotations: + deployment: + kubernetes.io/annotation: value + ``` + + + ```code + $ --set annotations.deployment."kubernetes\.io\/annotation"=value + ``` + + You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend + using a `values.yaml` file instead to avoid confusion and errors. + + + + +## `annotations.pod` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +Kubernetes annotations which should be applied to each `Pod` created by the chart. + + + + ```yaml + annotations: + pod: + kubernetes.io/annotation: value + ``` + + + ```code + $ --set annotations.pod."kubernetes\.io\/annotation"=value + ``` + + You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend + using a `values.yaml` file instead to avoid confusion and errors. + + + + +## `annotations.service` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +Kubernetes annotations which should be applied to the `Service` created by the chart. + + + + ```yaml + annotations: + service: + kubernetes.io/annotation: value + ``` + + + ```code + $ --set annotations.service."kubernetes\.io\/annotation"=value + ``` + + You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend + using a `values.yaml` file instead to avoid confusion and errors. + + + + +## `annotations.serviceAccount` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +Kubernetes annotations which should be applied to the `serviceAccount` created by the chart. + + + + ```yaml + annotations: + serviceAccount: + kubernetes.io/annotation: value + ``` + + + ```code + $ --set annotations.serviceAccount."kubernetes\.io\/annotation"=value + ``` + + You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend + using a `values.yaml` file instead to avoid confusion and errors. + + + + +## `annotations.certSecret` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +Kubernetes annotations which should be applied to the `secret` generated by +`cert-manager` from the `certificate` created by the chart. Only valid when +`highAvailability.certManager.enabled` is set to `true` and requires +`cert-manager` v1.5.0+. + + + + ```yaml + annotations: + certSecret: + kubernetes.io/annotation: value + ``` + + + ```code + $ --set annotations.certSecret."kubernetes\.io\/annotation"=value + ``` + + You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend + using a `values.yaml` file instead to avoid confusion and errors. + + + + +## `service.type` + +| Type | Default value | Required? | Can be used in `custom` mode? | +| - | - | - | - | +| `string` | `LoadBalancer` | Yes | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/services-networking/service/) + +Allows to specify the service type. + + + + ```yaml + service: + type: LoadBalancer + ``` + + + ```code + $ --set service.type=LoadBalancer + ``` + + + +## `service.spec.loadBalancerIP` + +| Type | Default value | Required? | Can be used in `custom` mode? | +| - | - | - | - | +| `string` | `nil` | No | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) + +Allows to specify the `loadBalancerIP`. + + + + ```yaml + service: + spec: + loadBalancerIP: 1.2.3.4 + ``` + + + ```code + $ --set service.spec.loadBalancerIP=1.2.3.4 + ``` + + + +## `extraArgs` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +A list of extra arguments to pass to the `teleport start` command when running a Teleport Pod. + + + + ```yaml + extraArgs: + - "--bootstrap=/etc/teleport-bootstrap/roles.yaml" + ``` + + + ```code + $ --set "extraArgs={--bootstrap=/etc/teleport-bootstrap/roles.yaml}" + ``` + + + +## `extraEnv` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) + +A list of extra environment variables to be set on the main Teleport container. + + + + ```yaml + extraEnv: + - name: MY_ENV + value: my-value + ``` + + + ```code + $ --set "extraEnv[0].name=MY_ENV" \ + --set "extraEnv[0].value=my-value" + ``` + + + + +## `extraVolumes` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/volumes/) + +A list of extra Kubernetes `Volumes` which should be available to any `Pod` created by the chart. These volumes +will also be available to any `initContainers` configured by the chart. + + + + ```yaml + extraVolumes: + - name: myvolume + secret: + secretName: mysecret + ``` + + + ```code + $ --set "extraVolumes[0].name=myvolume" \ + --set "extraVolumes[0].secret.secretName=mysecret" + ``` + + + +## `extraVolumeMounts` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/volumes/) + +A list of extra Kubernetes volume mounts which should be mounted into any `Pod` created by the chart. These volume +mounts will also be mounted into any `initContainers` configured by the chart. + + + + ```yaml + extraVolumeMounts: + - name: myvolume + mountPath: /path/to/mount/volume + ``` + + + ```code + $ --set "extraVolumeMounts[0].name=myvolume" \ + --set "extraVolumeMounts[0].path=/path/to/mount/volume" + ``` + + + +## `imagePullPolicy` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `string` | `IfNotPresent` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/containers/images/#updating-images) + +Allows the `imagePullPolicy` for any pods created by the chart to be overridden. + + + + ```yaml + imagePullPolicy: Always + ``` + + + ```code + $ --set imagePullPolicy=Always + ``` + + + +## `initContainers` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) + +A list of `initContainers` which will be run before the main Teleport container in any pod created by the chart. + + + + ```yaml + initContainers: + - name: teleport-init + image: alpine + args: ['echo test'] + ``` + + + ```code + $ --set "initContainers[0].name=teleport-init" \ + --set "initContainers[0].image=alpine" \ + --set "initContainers[0].args={echo test}" + ``` + + + +## `postStart` + +[Kubernetes reference](https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/) + +A `postStart` lifecycle handler to be configured on the main Teleport container. + + + + ```yaml + postStart: + command: + - echo + - foo + ``` + + + ```shell + --set "postStart.command[0]=echo" \ + --set "postStart.command[1]=foo" + ``` + + + +## `resources` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) + +Resource requests/limits which should be configured for each container inside the pod. These resource limits +will also be applied to `initContainers`. + + + + ```yaml + resources: + requests: + cpu: 1 + memory: 2Gi + ``` + + + ```code + $ --set resources.requests.cpu=1 \ + --set resources.requests.memory=2Gi + ``` + + + +## `tolerations` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) + +Kubernetes Tolerations to set for pod assignment. + + + + ```yaml + tolerations: + - key: "dedicated" + operator: "Equal" + value: "teleport" + effect: "NoSchedule" + ``` + + + ```code + $ --set tolerations[0].key=dedicated \ + --set tolerations[0].operator=Equal \ + --set tolerations[0].value=teleport \ + --set tolerations[0].effect=NoSchedule + ``` + + + +## `priorityClassName` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `string` | `""` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) + +Kubernetes PriorityClass to set for pod. + + + + ```yaml + priorityClassName: "system-cluster-critical" + ``` + + + ```code + $ --set priorityClassName=system-cluster-critical + ``` + + + diff --git a/docs/pages/kubernetes-access/helm/reference/teleport-kube-agent.mdx b/docs/pages/kubernetes-access/helm/reference/teleport-kube-agent.mdx new file mode 100644 index 0000000000000..ef76f14e59562 --- /dev/null +++ b/docs/pages/kubernetes-access/helm/reference/teleport-kube-agent.mdx @@ -0,0 +1,1334 @@ +--- +title: teleport-kube-agent Chart Reference +description: Values that can be set using the teleport-cluster Helm chart +--- + +The `teleport-kube-agent` Helm chart is used to configure a Teleport agent that +runs in a remote Kubernetes cluster and joins back to a Teleport cluster to +provide access to services running there. + +You can [browse the source on GitHub](https://github.com/gravitational/teleport/tree/master/examples/chart/teleport-kube-agent). + +The `teleport-kube-agent` chart can run any or all of three Teleport services: + +| Teleport service | Name for `roles` and `tctl tokens add` | Purpose | +| - | - | - | +| [`kubernetes_service`](../../../kubernetes-access/guides.mdx) | `kube` | Uses Teleport to handle authentication with and proxy access to a Kubernetes cluster | +| [`application_service`](../../../application-access/guides.mdx) | `app` | Uses Teleport to handle authentication with and proxy access to web-based applications | +| [`database_service`](../../../database-access/guides.mdx) | `db` | Uses Teleport to handle authentication with and proxy access to databases | + +This reference details available values for the `teleport-kube-agent` chart. + +(!docs/pages/includes/backup-warning.mdx!) + +## `roles` + +This parameter is not mandatory to preserve backwards compatibility with older chart versions, but we recommend it is set. + +| Type | Default value | +| - | - | +| `string` | `kube` | + +`roles` is a comma-separated list of services which should be enabled when running the `teleport-kube-agent` chart. + +| Services | Value for `roles` | Mandatory additional settings for this role | +| - | - | - | +| Teleport Kubernetes service | `kube` | [`kubeClusterName`](#kubeclustername) | +| Teleport Application service | `app` | [`apps`](#apps) | +| Teleport Database service | `db` | [`databases`](#databases) | + + + + ```yaml + roles: kube,app,db + ``` + + + ```code + $ --set roles=kube\,app\,db + ``` + + + When specifying multiple roles using `--set` syntax, you must escape the commas using a backslash (`\`). + + This is a quirk of Helm's CLI parser. + + + + + +If you specify a role here, you may also need to specify some other settings which are detailed in this reference. + +## `authToken` + +| Type | Default value | Required? | +| - | - | - | +| `string` | `nil` | Yes | + +`authToken` provides a Teleport join token which will be used to join the Teleport agent to a Teleport cluster. + +This value **must** be provided for the chart to work. The token that you use must also be valid for every Teleport service that you +are trying to add with the `teleport-kube-agent` chart. Here are a few examples: + +| Services | Service Name | `tctl tokens add` example | `teleport.yaml` static token example | +| - | - | - | - | +| Kubernetes | `kube` | `tctl tokens add --type=kube` | `"kube:"` | +| Kubernetes, Application | `kube,app` | `tctl tokens add --type=kube,app` | `"kube,app:"` | +| Kubernetes, Application, Database | `kube,app,db` | `tctl tokens add --type=kube,app,db` | `"kube,app,db:"` | + + + When you use a token, all of the services that it provides **must** be used. + + For example, you cannot use a token of type `app,db` to only join a Teleport `app` service by itself. It must join both `app` and `db` services at once. + + Also, each static token you configure must be unique, as the token itself is used to define which services will be supported. + + You cannot reuse the same static token and specify a different set of services. + + +If you do not have the correct services (Teleport refers to these internally as `Roles`) assigned to your join token, the Teleport agent will +fail to join the Teleport cluster. + + + + ```yaml + authToken: + ``` + + + ```code + $ --set authToken= + ``` + + + +## `proxyAddr` + +| Type | Default value | Required? | +| - | - | - | +| `string` | `nil` | Yes | + +`proxyAddr` provides the public-facing Teleport proxy endpoint which should be used to join the cluster. This is the same URL that is used +to access the web UI of your Teleport cluster. It is the same as the value configured for `proxy_service.public_addr` in a traditional +Teleport cluster. The port used is usually either 3080 or 443. + +Here are a few examples: + +| Deployment method | Example `proxy_service.public_addr` | +| - | - | +| On-prem Teleport cluster | `teleport.example.com:3080` | +| Teleport Cloud cluster | `example.teleport.sh:443` | +| `teleport-cluster` Helm chart | `teleport.example.com:443` | + +## `kubeClusterName` + +| Type | Default value | Required? | +| - | - | - | +| `string` | `nil` | When `kube` chart role is used | + +`kubeClusterName` sets the name used for the Kubernetes cluster proxied by the Teleport agent. This name will be shown to Teleport users +connecting to the cluster. + + + + ```yaml + kubeClusterName: my-gke-cluster + ``` + + + ```code + $ --set kubeClusterName=my-gke-cluster + ``` + + + +## `apps` + +| Type | Default value | Required? | +| - | - | - | +| `list` | `[]` | When `app` chart role is used? | + +`apps` is a YAML list object detailing the applications that should be proxied by Teleport Application access. + +You can specify multiple apps by adding additional list elements. + + + + ```yaml + apps: + - name: grafana + uri: http://localhost:3000 + labels: + purpose: monitoring + - name: jenkins + uri: http://jenkins:8080 + labels: + purpose: ci + ``` + + + YAML is very sensitive to correct spacing. When specifying lists in a `values.yaml` file, you might like + to [use a linter](https://codebeautify.org/yaml-validator) to validate your YAML list and ensure that it is correctly formatted. + + + + ```code + $ --set "apps[0].name=grafana" \ + --set "apps[0].uri=http://localhost:3000" \ + --set "apps[0].purpose=monitoring" \ + --set "apps[1].name=grafana" \ + --set "apps[1].uri=http://jenkins:8080" \ + --set "apps[1].purpose=ci" + ``` + + + Note that when using `--set` syntax, YAML list elements must be indexed starting at `0`. + + + + + + + You can see a list of all the supported [values which can be used in a Teleport application access configuration here](../../../application-access/reference.mdx#configuration). + + +## `awsDatabases` + + + This section configures database auto-discovery, which is only currently supported on AWS. You can configure databases for other platforms using the [`databases`](#databases) section below this. + + + + For AWS database auto-discovery to work, your agent pods will need to use a role which has appropriate IAM permissions as per the [database documentation](../../../database-access/guides/rds.mdx#step-47-create-an-iam-policy-for-teleport). + + After configuring a role, you can use an `eks.amazonaws.com/role-arn` annotation with the `annotations.serviceAccount` value to associate it with the service account and grant permissions: + + ``` + annotations: + serviceAccount: + eks.amazonaws.com/role-arn: arn:aws:iam::1234567890:role/my-rds-autodiscovery-role + ``` + + +| Type | Default value | Required? | +| - | - | - | +| `list` | `[]` | No | + +`awsDatabases` is a YAML list object detailing the filters for the AWS databases that should be discovered and proxied by Teleport Database access. + +You can specify multiple database filters by adding additional list elements. + +- `types` is a list containing the types of AWS databases that should be discovered. +- `regions` is a list of AWS regions which should be scanned for databases. +- `tags` can be used to set AWS tags that must be matched for databases to be discovered. + + + + ```yaml + awsDatabases: + - types: ["rds"] + regions: ["us-east-1", "us-west-2"] + tags: + "environment": "production" + - types: ["rds"] + regions: ["us-east-1"] + tags: + "environment": "dev" + - types: ["rds"] + regions: ["eu-west-1"] + tags: + "*": "*" + ``` + + + YAML is very sensitive to correct spacing. When specifying lists in a `values.yaml` file, you might like + to [use a linter](https://codebeautify.org/yaml-validator) to validate your YAML list and ensure that it is correctly formatted. + + + + ```code + --set "awsDatabases[0].types[0]=rds" \ + --set "awsDatabases[0].regions[0]=us-east-1" \ + --set "awsDatabases[0].regions[1]=us-west-2" \ + --set "awsDatabases[0].tags[0].environment=production" \ + --set "awsDatabases[1].types[0]=rds" \ + --set "awsDatabases[1].regions[0]=us-east-1" \ + --set "awsDatabases[1].tags[0].environment=dev" \ + --set "awsDatabases[2].types[0]=rds" \ + --set "awsDatabases[2].regions[0]=eu-west-1" \ + --set "awsDatabases[2].tags[0].*=*" + ``` + + + Note that when using `--set` syntax, YAML list elements must be indexed starting at `0`. + + + + + +## `databases` + +| Type | Default value | Required? | +| - | - | - | +| `list` | `[]` | When `db` chart role is used | + +`databases` is a YAML list object detailing the databases that should be proxied by Teleport Database access. + +You can specify multiple databases by adding additional list elements. + + + + ```yaml + databases: + - name: aurora-postgres + uri: postgres-aurora-instance-1.xxx.us-east-1.rds.amazonaws.com:5432 + protocol: postgres + aws: + region: us-east-1 + static_labels: + env: staging + - name: mysql + uri: mysql-instance-1.xxx.us-east-1.rds.amazonaws.com:3306 + protocol: mysql + aws: + region: us-east-1 + static_labels: + env: staging + ``` + + + YAML is very sensitive to correct spacing. When specifying lists in a `values.yaml` file, you might like + to [use a linter](https://codebeautify.org/yaml-validator) to validate your YAML list and ensure that it is correctly formatted. + + + + ```code + $ --set "databases[0].name=aurora" \ + --set "databases[0].uri=postgres-aurora-instance-1.xxx.us-east-1.rds.amazonaws.com:5432" \ + --set "databases[0].protocol=postgres" \ + --set "databases[0].aws.region=us-east-1" \ + --set "databases[0].static_labels.env=staging" \ + --set "databases[1].name=mysql" \ + --set "databases[1].uri=mysql-instance-1.xxx.us-east-1.rds.amazonaws.com:3306" \ + --set "databases[1].protocol=mysql" \ + --set "databases[1].aws.region=us-east-1" \ + --set "databases[1].static_labels.env=staging" + ``` + + + Note that when using `--set` syntax, YAML list elements must be indexed starting at `0`. + + + + + + + You can see a list of all the supported [values which can be used in a Teleport database service configuration here](../../../database-access/reference/configuration.mdx). + + +## `teleportVersionOverride` + +| Type | Default value | +| - | - | +| `string` | `nil` | + +Normally the version of Teleport being used will match the version of the chart being installed. If you install chart version +7.0.0, you'll be using Teleport 7.0.0. + +You can optionally override this to use a different published Teleport Docker image tag like `6.0.2` or `7`. + +See [this link for information on Community Docker image versions](../../../setup/guides/docker.mdx#step-14-pick-your-image). + + + The `teleport-kube-agent` chart always runs using Teleport Community edition as it does not require any Enterprise features, so it does + not require a Teleport license file to be provided. + + + + + ```yaml + teleportVersionOverride: "7" + ``` + + + ```code + $ --set teleportVersionOverride="7" + ``` + + + +## `insecureSkipProxyTLSVerify` + +| Type | Default value | +| - | - | +| `bool` | `false` | + +When `insecureSkipProxyTLSVerify` is set to `true`, the agent will skip the verification of the TLS certificate presented by the Teleport +proxy server specified using [`proxyAddr`](#proxyaddr). + +This can be used for joining a Teleport agent to a Teleport cluster which does not have valid TLS certificates for testing. + + + + ```yaml + insecureSkipProxyTLSVerify: false + ``` + + + ```code + $ --set insecureSkipProxyTLSVerify=false + ``` + + + + + Using a self-signed TLS certificate and disabling TLS verification is OK for testing, but is not viable when running a production Teleport + cluster as it will drastically reduce security. You must configure valid TLS certificates on your Teleport cluster for production workloads. + + One option might be to use Teleport's built-in [ACME support](./teleport-cluster.mdx#acme) or enable [cert-manager support](./teleport-cluster.mdx#highavailabilitycertmanager). + + +## `existingDataVolume` + +| Type | Default value | +| - | - | +| `string` | `""` | + +When `existingDataVolume` is set to the name of an existing volume, the `/var/lib/teleport` mount will use this volume instead of creating a new `emptyDir` volume. + + + + ```yaml + existingDataVolume: my-volume + ``` + + + ```bash + --set existingDataVolume=my-volume + ``` + + + +## `podSecurityPolicy` + +### `podSecurityPolicy.enabled` + +| Type | Default value | +| - | - | +| `bool` | `true` | + +By default, Teleport charts also install a [`podSecurityPolicy`](https://github.com/gravitational/teleport/blob/master/examples/chart/teleport-kube-agent/templates/psp.yaml). + +To disable this, you can set `enabled` to `false`. + +[Kubernetes reference](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) + + + + ```yaml + podSecurityPolicy: + enabled: false + ``` + + + ```code + $ --set podSecurityPolicy.enabled=false + ``` + + + +## `labels` + +| Type | Default value | +| - | - | +| `object` | `{}` | + +`labels` can be used to add a map of key-value pairs for the `kubernetes_service` which is deployed using the `teleport-kube-agent` chart. +These labels can then be used with Teleport's RBAC policies to define access rules for the cluster. + + + These are Teleport-specific RBAC labels, not Kubernetes labels. + + + + For historical/backwards compatibility reasons, these labels will only be applied to the Kubernetes cluster being joined via the + Teleport Kubernetes service. + + To set labels for applications, add a `labels` element to the [`apps`](#apps) section. + To set labels for databases, add a `static_labels` element to the [`databases`](#databases) section. + + For more information on how to set static/dynamic labels for Teleport services, see [labelling nodes and applications](../../../setup/admin/labels.mdx). + + + + + ```yaml + labels: + environment: production + region: us-east + ``` + + + ```code + $ --set labels.environment=production \ + --set labels.region=us-east + ``` + + + +## `storage` + +### `storage.enabled` + +| Type | Default value | +| - | - | +| `bool` | `false` | + +Enables the creation of a Kubernetes persistent volume to hold Teleport agent state. + +[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) + + + + ```yaml + storage: + enabled: true + ``` + + + ```bash + --set storage.enabled=true + ``` + + + +### `storage.storageClassName` + +| Type | Default value | +| - | - | +| `string` | `nil` | + +The storage class name the persistent volume should use when creating persistent volume claims. The provided storage class +name needs to exist on the Kubernetes cluster for Teleport to use. + +[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/storage-classes/) + + + + ```yaml + storage: + storageClassName: teleport-storage-class + ``` + + + ```bash + --set storage.storageClassName=teleport-storage-class + ``` + + + +### `storage.requests` + +| Type | Default value | +| - | - | +| `string` | `128Mi` | + +The size of persistent volume to create. + + + + ```yaml + storage: + requests: 128Mi + ``` + + + ```bash + --set storage.requests=128Mi + ``` + + + +## `image` + +| Type | Default value | +| - | - | +| `string` | `quay.io/gravitational/teleport` | + +`image` sets the container image used for Teleport pods run by the `teleport-kube-agent` chart. + +You can override this to use your own Teleport image rather than a Teleport-published image. + + + + ```yaml + image: my.docker.registry/teleport-image-name + ``` + + + ```code + $ --set image=my.docker.registry/teleport-image-name + ``` + + + +## `imagePullSecrets` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod) + +A list of secrets containing authorization tokens which can be optionally used to access a private Docker registry. + + + + ```yaml + imagePullSecrets: + - name: my-docker-registry-key + ``` + + + ```shell + --set "imagePullSecrets[0].name=my-docker-registry-key" + ``` + + + +### `highAvailability` + +## `highAvailability.replicaCount` + +| Type | Default value | +| - | - | +| `int` | `1` | + +`highAvailability.replicaCount` can be used to set the number of replicas used in the deployment. + +Set to a number higher than `1` for a high availability mode where multiple Teleport agent pods will be deployed. + + + As a rough guide, we recommend configuring one replica per distinct availability zone where your cluster has worker nodes. + + 2 replicas/availability zones will be fine for smaller workloads. 3-5 replicas/availability zones will be more appropriate for bigger + clusters with more traffic. + + + + + ```yaml + highAvailability: + replicaCount: 3 + ``` + + + ```shell + --set highAvailability.replicaCount=3 + ``` + + + +## `highAvailability.requireAntiAffinity` + +| Type | Default value | +| - | - | +| `bool` | `false` | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity) + +Setting `highAvailability.requireAntiAffinity` to `true` will use `requiredDuringSchedulingIgnoredDuringExecution` to require that multiple +Teleport pods must not be scheduled on the same physical host. + + + This can result in Teleport pods failing to be scheduled in very small clusters or during node downtime, so should be used with caution. + + + Setting `highAvailability.requireAntiAffinity` to `false` (the default) uses `preferredDuringSchedulingIgnoredDuringExecution` to make node + anti-affinity a soft requirement. + + + This setting only has any effect when `highAvailability.replicaCount` is greater than `1`. + + + + + ```yaml + highAvailability: + requireAntiAffinity: true + ``` + + + ```shell + --set highAvailability.requireAntiAffinity=true + ``` + + + +## `highAvailability.podDisruptionBudget` + +### `highAvailability.podDisruptionBudget.enabled` + +| Type | Default value | +| - | - | +| `bool` | `false` | + +[Kubernetes reference](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) + +Enable a Pod Disruption Budget for the Teleport Pod to ensure HA during voluntary disruptions. + + + + ```yaml + highAvailability: + podDisruptionBudget: + enabled: true + ``` + + + ```shell + --set highAvailability.podDisruptionBudget.enabled=true + ``` + + + +### `highAvailability.podDisruptionBudget.minAvailable` + +| Type | Default value | +| - | - | +| `int` | `1` | + +[Kubernetes reference](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) + +Ensures that this number of replicas is available during voluntary disruptions, can be a number of replicas or a percentage. + + + + ```yaml + highAvailability: + podDisruptionBudget: + minAvailable: 1 + ``` + + + ```shell + --set highAvailability.podDisruptionBudget.minAvailable=1 + ``` + + + +## `clusterRoleName` + +| Type | Default value | +| - | - | +| `string` | `nil` | + +`clusterRoleName` can be optionally used to override the name of the Kubernetes `ClusterRole` used by the `teleport-kube-agent` chart's `ServiceAccount`. + + + Most users will not need to change this. + + + + + ```yaml + clusterRoleName: kubernetes-clusterrole + ``` + + + ```code + $ --set clusterRoleName=kubernetes-clusterrole + ``` + + + +## `clusterRoleBindingName` + + + Most users will not need to change this. + + +| Type | Default value | +| - | - | +| `string` | `nil` | + + +`clusterRoleBindingName` can be optionally used to override the name of the Kubernetes `ClusterRoleBinding` used by the `teleport-kube-agent` chart's `ServiceAccount`. + + + + ```yaml + clusterRoleBindingName: kubernetes-clusterrolebinding + ``` + + + ```code + $ --set clusterRoleBindingName=kubernetes-clusterrolebinding + ``` + + + +## `serviceAccountName` + + + Most users will not need to change this. + + +| Type | Default value | +| - | - | +| `string` | `nil` | + +`serviceAccountName` can be optionally used to override the name of the Kubernetes `ServiceAccount` used by the `teleport-kube-agent` chart. + + + + ```yaml + serviceAccountName: kubernetes-serviceaccount + ``` + + + ```code + $ --set serviceAccountName=kubernetes-serviceaccount + ``` + + + +## `secretName` + +| Type | Default value | +| - | - | +| `string` | `teleport-kube-agent-join-token` | + +`secretName` is the name of the Kubernetes `Secret` used to store the Teleport join token which is used by the `teleport-kube-agent` chart. + +If you set this to a blank value, the chart will not attempt to create the secret itself and will instead read the value of the +existing `teleport-kube-agent-join-token` secret. This allows you to configure this secret externally and avoid having a plaintext +join token stored in your Teleport chart values. + +To create your own join token secret, you can use a command like this: + +```code +$ kubectl --namespace teleport create secret generic teleport-kube-agent-join-token --from-literal=auth-token= +``` + + + The key used for the auth token inside the secret must be `auth-token`, as in the command above. + + + + + ```yaml + serviceAccountName: + ``` + + + ```code + $ --set serviceAccountName="" + ``` + + + +## `log` + +### `log.level` + + + This field used to be called `logLevel`. For backwards compatibility this name can still be used, but we recommend changing your values file to use `log.level`. + + +| Type | Default value | +| - | - | +| `string` | `INFO` | + +`log.level` sets the log level used for the Teleport process. + +Available log levels (in order of most to least verbose) are: `DEBUG`, `INFO`, `WARNING`, `ERROR`. + +The default is `INFO`, which is recommended in production. + +`DEBUG` is useful during first-time setup or to see more detailed logs for debugging. + + + + ```yaml + log: + level: DEBUG + ``` + + + ```code + --set log.level=DEBUG + ``` + + + +### `log.output` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `string` | `stderr` | ❌ | `teleport.log.output` | + +`log.output` sets the output destination for the Teleport process. + +This can be set to any of the built-in values: `stdout`, `stderr` or `syslog` to use that destination. + +The value can also be set to a file path (such as `/var/log/teleport.log`) to write logs to a file. Bear in mind that a few service startup messages will still go to `stderr` for resilience. + + + + ```yaml + log: + output: stderr + ``` + + + ```code + --set log.output=stderr + ``` + + + +### `log.format` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `string` | `text` | ❌ | `teleport.log.format.output` | + +`log.format` sets the output type for the Teleport process. + +Possible values are `text` (default) or `json`. + + + + ```yaml + log: + format: json + ``` + + + ```code + --set log.format=json + ``` + + + +### `log.extraFields` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `list` | `["timestamp", "level", "component", "caller"]` | ❌ | `teleport.log.format.extra_fields` | + +`log.extraFields` sets the fields used in logging for the Teleport process. + +See the [Teleport config file reference](../../../setup/reference/config.mdx) for more details on possible values for `extra_fields`. + + + + ```yaml + log: + extraFields: ["timestamp", "level"] + ``` + + + ```code + --set "log.extraFields[0]=timestamp" \ + --set "log.extraFields[1]=level" + ``` + + + +## `affinity` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + +Kubernetes affinity to set for pod assignments. + + + You cannot set both `affinity` and `highAvailability.requireAntiAffinity` as they conflict with each other. + + + + + ```yaml + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: gravitational.io/dedicated + operator: In + values: + - teleport + ``` + + + ```code + $ --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key=gravitational.io/dedicated \ + --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator=In \ + --set affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]=teleport + ``` + + + +## `nodeSelector` + +| Type | Default value | +| - | - | +| `object` | `{}` | + +`nodeSelector` can be used to add a map of key-value pairs to constrain the nodes the agent pods will run on. + +[Kubernetes reference](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) + + + + ```yaml + nodeSelector: + role: node + region: us-east + ``` + + + ```bash + --set nodeSelector.role=node \ + --set nodeSelector.region=us-east + ``` + + + +## `annotations.config` + +| Type | Default value | Can be used in `custom` mode? | `teleport.yaml` equivalent | +| - | - | - | - | +| `object` | `{}` | ❌ | None | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +Kubernetes annotations which should be applied to the `ConfigMap` created by the chart. + + + These annotations will not be applied in `custom` mode, as the `ConfigMap` is not managed by the chart. + In this instance, you should apply annotations manually to your created `ConfigMap`. + + + + + ```yaml + annotations: + config: + kubernetes.io/annotation: value + ``` + + + ```code + $ --set annotations.config."kubernetes\.io\/annotation"=value + ``` + + You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend + using a `values.yaml` file instead to avoid confusion and errors. + + + + +## `annotations.deployment` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +Kubernetes annotations which should be applied to the `Deployment` created by the chart. + + + + ```yaml + annotations: + deployment: + kubernetes.io/annotation: value + ``` + + + ```code + $ --set annotations.deployment."kubernetes\.io\/annotation"=value + ``` + + You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend + using a `values.yaml` file instead to avoid confusion and errors. + + + + +## `annotations.pod` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +Kubernetes annotations which should be applied to each `Pod` created by the chart. + + + + ```yaml + annotations: + pod: + kubernetes.io/annotation: value + ``` + + + ```code + $ --set annotations.pod."kubernetes\.io\/annotation"=value + ``` + + You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend + using a `values.yaml` file instead to avoid confusion and errors. + + + + +## `annotations.serviceAccount` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) + +Kubernetes annotations which should be applied to the `ServiceAccount` created by the chart. + + + + ```yaml + annotations: + serviceAccount: + kubernetes.io/annotation: value + ``` + + + ```code + $ --set annotations.serviceAccount."kubernetes\.io\/annotation"=value + ``` + + You must escape values entered on the command line correctly for Helm's CLI to understand them. We recommend + using a `values.yaml` file instead to avoid confusion and errors. + + + + +## `extraVolumes` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/volumes/) + +A list of extra Kubernetes `Volumes` which should be available to any `Pod` created by the chart. These volumes +will also be available to any `initContainers` configured by the chart. + + + + ```yaml + extraVolumes: + - name: myvolume + secret: + secretName: mysecret + ``` + + + ```code + $ --set "extraVolumes[0].name=myvolume" \ + --set "extraVolumes[0].secret.secretName=mysecret" + ``` + + + +## `extraArgs` + +| Type | Default value | +| - | - | +| `list` | `[]` | + +A list of extra arguments to pass to the `teleport start` command when running a Teleport Pod. + + + + ```yaml + extraArgs: + - "--debug" + ``` + + + ```code + $ --set "extraArgs={--debug}" + ``` + + + +## `extraEnv` + +| Type | Default value | +| - | - | +| `list` | `[]` | + +[Kubernetes reference](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) + +A list of extra environment variables to be set on the main Teleport container. + + + + ```yaml + extraEnv: + - name: HTTPS_PROXY + value: "http://username:password@my.proxy.host:3128" + ``` + + + ```code + $ --set "extraEnv[0].name=HTTPS_PROXY" \ + --set "extraEnv[0].value=\"http://username:password@my.proxy.host:3128\"" + ``` + + + +## `extraVolumeMounts` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/storage/volumes/) + +A list of extra Kubernetes volume mounts which should be mounted into any `Pod` created by the chart. These volume +mounts will also be mounted into any `initContainers` configured by the chart. + + + + ```yaml + extraVolumeMounts: + - name: myvolume + mountPath: /path/to/mount/volume + ``` + + + ```code + $ --set "extraVolumeMounts[0].name=myvolume" \ + --set "extraVolumeMounts[0].path=/path/to/mount/volume" + ``` + + + +## `imagePullPolicy` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `string` | `IfNotPresent` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/containers/images/#updating-images) + +Allows the `imagePullPolicy` for any pods created by the chart to be overridden. + + + + ```yaml + imagePullPolicy: Always + ``` + + + ```code + $ --set imagePullPolicy=Always + ``` + + + +## `initContainers` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) + +A list of `initContainers` which will be run before the main Teleport container in any pod created by the chart. + + + + ```yaml + initContainers: + - name: teleport-init + image: alpine + args: ['echo test'] + ``` + + + ```code + $ --set "initContainers[0].name=teleport-init" \ + --set "initContainers[0].image=alpine" \ + --set "initContainers[0].args={echo test}" + ``` + + + +## `resources` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `object` | `{}` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) + +Resource requests/limits which should be configured for each container inside the pod. These resource limits +will also be applied to `initContainers`. + + + + ```yaml + resources: + requests: + cpu: 1 + memory: 2Gi + ``` + + + ```code + $ --set resources.requests.cpu=1 \ + --set resources.requests.memory=2Gi + ``` + + + +## `tolerations` + +| Type | Default value | Can be used in `custom` mode? | +| - | - | - | +| `list` | `[]` | ✅ | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) + +Kubernetes Tolerations to set for pod assignment. + + + + ```yaml + tolerations: + - key: "dedicated" + operator: "Equal" + value: "teleport" + effect: "NoSchedule" + ``` + + + ```code + $ --set tolerations[0].key=dedicated \ + --set tolerations[0].operator=Equal \ + --set tolerations[0].value=teleport \ + --set tolerations[0].effect=NoSchedule + ``` + + diff --git a/docs/pages/kubernetes-access/introduction.mdx b/docs/pages/kubernetes-access/introduction.mdx index 792a420eccbb3..10b0b25971ce4 100644 --- a/docs/pages/kubernetes-access/introduction.mdx +++ b/docs/pages/kubernetes-access/introduction.mdx @@ -5,8 +5,8 @@ description: Teleport Kubernetes Access introduction, demo, and resources. Teleport provides secure access for Kubernetes clusters. -- Users can receive short-lived kubeconfigs (and certificates) using Single Sign-On (SSO) and switch between clusters without logging in twice. -- Admins can use *roles* to implement policies like the best practice that *developers must not access production* and enforce dual authorization using [access requests](../enterprise/workflow/index.mdx) for privileged operations. +- Users can receive short-lived kubeconfig files (and certificates) using Single Sign-On (SSO) and switch between clusters without logging in twice. +- Admins can use roles to implement policies, such as the best practice that developers must not access production. - Organizations can achieve compliance by capturing `kubectl` events and session recordings for `kubectl`. ## SSO and Audit for Kubernetes Clusters @@ -33,7 +33,7 @@ Set up SSO, capture audit events, and sessions with Teleport running in a Kubern ## Getting started -Configure Kubernetes Access in a 10 minute [Getting Started](./getting-started.mdx) guide. +Configure Kubernetes Access in a ten-minute [Getting Started](./getting-started.mdx) guide. ## Guides @@ -44,9 +44,6 @@ Configure Kubernetes Access in a 10 minute [Getting Started](./getting-started.m Federated Access using Teleport Trusted Clusters. - - Kubernetes Pre-5.0 Migration Guide. - Connecting Multiple Clusters with Teleport Kubernetes Access. diff --git a/docs/pages/machine-id/getting-started.mdx b/docs/pages/machine-id/getting-started.mdx new file mode 100644 index 0000000000000..a88bdc0fb73b2 --- /dev/null +++ b/docs/pages/machine-id/getting-started.mdx @@ -0,0 +1,236 @@ +--- +title: Machine ID Getting Started Guide +description: Getting started with Teleport Machine ID +--- + +In this getting started guide, you will use Machine ID to create a bot user for +a machine and use that identity to connect to said machine. + +Here's an overview of what you will do: + +1. Download and install Teleport (=teleport.version=) +2. Create a bot user +3. Start Machine ID +4. Use certificates issued by Machine ID to connect to a remote machine + +## Prerequisites + +Before using Machine ID, you will need an existing Teleport cluster or a +Teleport Cloud account. + +If you have not set up a Teleport cluster before, follow the +[Getting started](https://goteleport.com/docs/getting-started) guide. + +## Step 1/4. Download and install Teleport (=teleport.version=) + +In this step, you will be downloading and installing Teleport binaries onto the +machine you wish to assign an identity to. + +Each Teleport package hosted on our +downloads page ships with several useful binaries, including `teleport`, +`tctl`, `tsh`, and `tbot`: + +- `teleport` is the daemon used to initialize a Teleport cluster; this binary is not used in this guide +- `tctl` is the administrative tool you will use to create the bot user (step 1/4) +- `tsh` is the client tool you will use to log in to the Teleport Cluster (steps 2/4 and 4/4) +- `tbot` is the Machine ID tool you will use to associate a bot user with a machine (step 3/4) + +Machine ID is available starting from the Teleport `9.0.0` release. Download +the appropriate Teleport package for your platform from our +[downloads page](https://goteleport.com/teleport/download). + +## Step 2/4. Create a bot user + +Before you create a bot user, you need to determine which role(s) you want to +assign to it. You can use the `tctl` command below to examine what roles exist +on your system. + +
    +On your client machine, log in to Teleport using `tsh`, then use `tctl` to examine +what roles exist on your system. +
    +
    +Connect to the Teleport Auth Server and use `tctl` to examine what roles exist on +your system. +
    + +```code +$ tctl get roles --format=text +``` + +You will see something like the output below on a fresh install of Teleport with the +default roles—your cluster may have different roles. In this example, let's +assume you want to give the bot the `access` role to allow it to connect to +machines within your cluster. + +``` +Role Allowed to login as Node Labels Access to resources +------- --------------------------------------------- ----------- ---------------------------------------- +access {{internal.logins}} event:list,read,session:read,list +auditor no-login-6566121f-b602-47f1-a118-c9c618ee5aec session:list,read,event:list,read +editor user:list,create,read,update,delete,... +``` + +Machine ID can join with a token or the [IAM Method](https://goteleport.com/docs/setup/guides/joining-nodes-aws) on AWS. + + + + ```code + $ tctl bots add robot --roles=access + ``` + + + First, create an IAM method token that specifies the AWS account from which + the bot can join. + + ``` + kind: token + version: v2 + metadata: + # The token name is not a secret because instances must prove that they are + # running in your AWS account to use this token. + name: iam-token + # Set a long expiry time for how long you want to support IAM method for + # joining. It is safe to set this value to a very long time. + expires: "3000-01-01T00:00:00Z" + spec: + # Only allow bots to join using this token. + roles: [Bot] + + # Set the join method to be IAM. + join_method: iam + + # Define the name of the bot that will be allowed to use this token. + bot_name: robot + + allow: + # Restrict the AWS account and (optionally) ARN that can use this token. + # This information can be obtained from running the + # "aws sts get-caller-identity" command from the CLI. + - aws_account: "111111111111" + aws_arn: "arn:aws:sts::111111111111:assumed-role/teleport-bot-role/i-*" + ``` + + Next, create the bot user. + + ``` + $ tctl bots add robot --token=iam-token --roles=access + ``` + + + +## Step 3/4. Start Machine ID + +Now start Machine ID using the `tbot` binary. The `tbot start` command will +start running Machine ID in a loop, writing renewable certificates to +`/var/lib/teleport/bot` and the short-lived certificates your application will +use to `/opt/machine-id`. + +In a production environment you will want to run Machine ID in the background +using a service manager like systemd. However, in this guide you will run it in +the foreground to better understand how it works. + + + + ```code + $ tbot start \ + --data-dir=/var/lib/teleport/bot \ + --destination-dir=/opt/machine-id \ + --token=00000000000000000000000000000000 \ + --join-method=token \ + --ca-pin=sha256:1111111111111111111111111111111111111111111111111111111111111111 \ + --auth-server=auth.example.com:3025 + ``` + + + ```code + $ tbot start \ + --data-dir=/var/lib/teleport/bot \ + --destination-dir=/opt/machine-id \ + --token=iam-token \ + --join-method=iam \ + --ca-pin=sha256:1111111111111111111111111111111111111111111111111111111111111111 \ + --auth-server=auth.example.com:3025 + ``` + + + +Replace the following fields with values from your own cluster. + +
    +- `token` is the token output by the `tctl bots add` command or the name of your IAM method token +- `ca-pin` is the CA Pin for your Teleport cluster, and is output by the `tctl bots add` command +- `destination-dir` is where Machine ID writes renewable certificates, which are only used by Machine ID and should not be used by applications and tools +- `data-dir` is where Machine ID writes the short-lived certificate. This certificate should be used by applications and tools +- `auth-server` is the address of your Teleport Cloud Proxy Server, for example `example.teleport.sh:443` +
    +
    +- `token` is the token output by the `tctl bots add` command or the name of your IAM method token +- `ca-pin` is the CA Pin for your Teleport cluster, and is output by the `tctl bots add` command +- `destination-dir` is where Machine ID writes renewable certificates, which are only used by Machine ID and should not be used by applications and tools +- `data-dir` is where Machine ID writes the short-lived certificate. This certificate should be used by applications and tools +- `auth-server` is the address of your Teleport Auth Server, for example `auth.example.com:3025` +
    + +Now that Machine ID has successfully started, let's investigate the +`/opt/machine-id` directory to see what was written to disk. + +```code +$ tree /opt/machine-id +machine-id +├── key +├── key.pub +├── known_hosts +├── ssh_config +├── sshcacerts +├── sshcert +├── tlscacerts +└── tlscert + +0 directories, 8 files +``` + +This directory contains private key material in the `key.*` files, SSH +certificates in the `ssh*` files, X.509 certificates in the `tls*` files, and +OpenSSH configuration in the `ssh_config` and `known_hosts` files to make it easy +to integrate Machine ID with external applications and tools. + +## Step 4/4. Use certificates issued by Machine ID + +To use Machine ID, find a host that you want to connect to within your cluster +using `tsh ls`. You might see output like the following on your system. + +```code +$ tsh ls +Node Name Address Labels +--------- -------------- ----------------------------- +node-name 127.0.0.1:3022 arch=x86_64,group=api-servers +``` + +To use Machine ID with the OpenSSH integration, run the following command to +connect to `node-name` within cluster `example.com`. + +``` +ssh -F /opt/machine-id/ssh_config root@node-name.example.com +``` + + + If you see the below error, it means the user you are trying to log in as is + not specified under `logins` in the role you are using. + + ```code + $ ssh -F /opt/machine-id/ssh_config root@node-name.example.com + root@node-name: Permission denied (publickey). + kex_exchange_identification: Connection closed by remote host + ``` + + If you have been following along with the `access` role, do the following. + + - Export the role by running `tctl get roles/access > access.yaml` + - Edit the `logins` field in `access.yaml` + - Update the role by running `tctl create -f access.yaml` + + +Now you can replace any invocations of `ssh` with the above command to provide +your applications and tools a machine identity that can be rotated, audited, +and controlled with all the familiar Teleport access controls. diff --git a/docs/pages/machine-id/guides.mdx b/docs/pages/machine-id/guides.mdx new file mode 100644 index 0000000000000..c9d0d52ba76aa --- /dev/null +++ b/docs/pages/machine-id/guides.mdx @@ -0,0 +1,11 @@ +--- +title: Machine ID Guides +description: Teleport Machine ID guides. +layout: tocless-doc +--- + + + + How to integrate Teleport Machine ID with Ansible. + + diff --git a/docs/pages/machine-id/guides/ansible.mdx b/docs/pages/machine-id/guides/ansible.mdx new file mode 100644 index 0000000000000..b69462fd2766d --- /dev/null +++ b/docs/pages/machine-id/guides/ansible.mdx @@ -0,0 +1,122 @@ +--- +title: Using Teleport Machine ID with Ansible +description: Using Teleport Machine ID with Ansible +--- + +In this guide, you will set up an Ansible playbook to run the OpenSSH client +with a configuration file that is automatically managed by Machine ID. + +## Prerequisites + +You will need the following tools to use Teleport with Ansible. + +- [Teleport Enterprise](../../enterprise/introduction.mdx) or [Teleport Cloud](../../cloud/introduction.mdx) >= 9.0.0 +- `ssh` OpenSSH tool +- `ansible` >= (=ansible.min_version=) +- Optional tool `jq` to process `JSON` output + +In addition, if you already have not done so, follow the +[Machine ID Getting Started Guide](../getting-started.mdx) to create a bot +user and start Machine ID. + +If you followed the above guide, you are interested in the +`--destination-dir=/opt/machine-id` flag, which defines the directory where +SSH certificates and OpenSSH configuration used by Ansible will be written. + +In particular, you will be using the `/opt/machine-id/ssh_config` file in your +Ansible configuration to define how Ansible should connect to Teleport Nodes. + +## Step 1/2. Configure Ansible + +Create a folder named `ansible` where all Ansible files will be collected. + +```code +$ mkdir -p ansible +$ cd ansible +``` + +Create a file called `ansible.cfg`. We will configure Ansible +to run the OpenSSH client with the configuration file generated +by Machine ID, `/opt/machine-id/ssh_config`. + +``` +[defaults] +host_key_checking = True +inventory=./hosts +remote_tmp=/tmp + +[ssh_connection] +scp_if_ssh = True +ssh_args = -F /opt/machine-id/ssh_config +``` + +You can create an inventory file called `hosts` manually or use a script like the one +below to generate it from your environment. Note, `example.com` here is the +name of your Teleport cluster. + +```code +$ tsh ls --format=json | jq -r '.[].spec.hostname + ".example.com"' > hosts +``` + +## Step 2/2. Run a playbook + +Finally, let's create a simple Ansible playbook, `playbook.yaml`. + +The playbook below runs `hostname` on all hosts. Make sure to set the +`remote_user` parameter to a valid SSH username that works with the target host +and is allowed by Teleport RBAC. + +```yaml +- hosts: all + remote_user: ubuntu + tasks: + - name: "hostname" + command: "hostname" +``` + +From the folder `ansible`, run the Ansible playbook: + +```code +$ ansible-playbook playbook.yaml + +# PLAY [all] ***************************************************************************************************************************************** +# TASK [Gathering Facts] ***************************************************************************************************************************** +# +# ok: [terminal] +# +# TASK [hostname] ************************************************************************************************************************************ +# changed: [terminal] +# +# PLAY RECAP ***************************************************************************************************************************************** +# terminal : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 +``` + +You are all set. You have provided your machine with short-lived certificates +tied to a machine identity that can be rotated, audited, and controlled with +all the familiar Teleport access controls. + +## Troubleshooting + +In case if Ansible cannot connect, you may see error like this one: + +```txt +example.host | UNREACHABLE! => { + "changed": false, + "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname node-name: Name or service not known", + "unreachable": true +} +``` + +You can examine and tweak patterns matching the inventory hosts in `ssh_config`. + +Try the SSH connection using `ssh_config` with verbose mode to inspect the error: + +```code +$ ssh -vvv -F /opt/machine-id/ssh_config root@node-name.example.com +``` + +If `ssh` works, try running the playbook with verbose mode on: + +```code +$ ansible-playbook -vvv playbook.yaml +``` diff --git a/docs/pages/machine-id/introduction.mdx b/docs/pages/machine-id/introduction.mdx new file mode 100644 index 0000000000000..51f0b4dbf0fa2 --- /dev/null +++ b/docs/pages/machine-id/introduction.mdx @@ -0,0 +1,56 @@ +--- +title: Machine ID +description: Teleport Machine ID introduction, demo and resources. +--- + +# Machine ID + +Machine ID is a service that programmatically issues and renews short-lived +certificates to any service account (e.g., a CI/CD server) by retrieving +credentials from the Teleport Auth Service. This enables fine-grained +role-based access controls and audit. + +Some of the things you can do with Machine ID: + +- Machines can retrieve short-lived SSH certificates for CI/CD pipelines. +- Machines can retrieve short-lived X.509 certificates for use with databases or applications. +- Configure role-based access controls and locking for machines. +- Capture access events in the audit log. + +{ +/* +TODO(russjones): Once we have a demo video for Machine ID, include it here. + +## Demo + +Let's create a bot and use the machine identity to connect to a server. + + +*/ +} + +## Getting started + + + + Getting started with Teleport Machine ID + + diff --git a/docs/pages/machine-id/reference.mdx b/docs/pages/machine-id/reference.mdx new file mode 100644 index 0000000000000..e309faec20f4e --- /dev/null +++ b/docs/pages/machine-id/reference.mdx @@ -0,0 +1,8 @@ +--- +title: Machine ID Reference +description: Configuration and CLI reference for Teleport Machine ID. +--- + + +- [Configuration](./reference/configuration.mdx) +- [CLI](./reference/cli.mdx) diff --git a/docs/pages/machine-id/reference/cli.mdx b/docs/pages/machine-id/reference/cli.mdx new file mode 100644 index 0000000000000..bc5276be2a147 --- /dev/null +++ b/docs/pages/machine-id/reference/cli.mdx @@ -0,0 +1,90 @@ +--- +title: Machine ID CLI Reference +description: CLI reference for Teleport Machine ID. +--- + +## `tbot start` + +Starts the Machine ID client `tbot`, fetching and writing certificates to disk +at a set interval. + +
    +```code +$ tbot start \ + --data-dir=/var/lib/teleport/bot \ + --destination-dir=/opt/machine-id \ + --token=00000000000000000000000000000000 \ + --join-method=token \ + --ca-pin=sha256:1111111111111111111111111111111111111111111111111111111111111111 \ + --auth-server=example.teleport.sh:443 +``` +
    +
    +```code +$ tbot start \ + --data-dir=/var/lib/teleport/bot \ + --destination-dir=/opt/machine-id \ + --token=00000000000000000000000000000000 \ + --join-method=token \ + --ca-pin=sha256:1111111111111111111111111111111111111111111111111111111111111111 \ + --auth-server=auth.example.com:3025 +``` +
    + +| Flag | Description | +|----------------------|------------------------------------------------------------------------------------------------| +| `-d/--debug` | Enable verbose logging to stderr. | +| `-c/--config` | Path to a configuration file. Defaults to `/etc/tbot.yaml` if unspecified. | +| `-a/--auth-server` | Address of the Teleport Auth Server (On-Prem installs) or Teleport Cloud tenant. | +| `--token` | A bot join token, if attempting to onboard a new bot; used on first connect. | +| `--ca-pin` | CA pin to validate the Teleport Auth Server; used on first connect. | +| `--data-dir` | Directory to store internal bot data. Access to this directory should be limited. | +| `--destination-dir` | Directory to write short-lived machine certificates. | +| `--certificate-ttl` | TTL of short-lived machine certificates. | +| `--renewal-interval` | Interval at which short-lived certificates are renewed; must be less than the certificate TTL. | +| `--join-method` | Method to use to join the cluster. Can be `token` or `iam`. | +| `--one-shot` | If set, quit after the first renewal. | + +## `tbot init` + +If you want to write certificates to disk as a different user than the Machine +ID client, you can use `tbot init` to configure either file or POSIX ACLs +permissions. This allows you to lock down access to Machine ID's short-lived +certificates from other users or applications on the system. + +| Flag | Description | +|---------------------|--------------------------------------------------------------------------------------------------------------------| +| `-d/--debug` | Enable verbose logging to stderr. | +| `-c/--config` | Path to a configuration file. Defaults to `/etc/tbot.yaml` if unspecified. | +| `--destination-dir` | Directory to write short-lived machine certificates to. | +| `--owner` | Defines the Linux `user:group` owner of `--destination-dir`. Defaults to the Linux user running `tbot` if unspecified. | +| `--bot-user` | Enables POSIX ACLs and defines the Linux user that can read/write short-lived certificates to `--destination-dir`. | +| `--reader-user` | Enables POSIX ACLs and defines the Linux user that will read short-lived certificates from `--destination-dir`. | +| `--init-dir` | If using a config file and multiple destinations are configured, controls which destination dir to configure. | +| `--clean` | If set, remove unexpected files and directories from the destination. | + +### `tbot init` with file permissions + +If running `tbot` as the Linux user `root`, use the following invocation of +`tbot init` to initialize the short-lived certificate directory +`/opt/machine-id` with owner `jenkins:jenkins`. + +```code +$ tbot init \ + --destination-dir=/opt/machine-id \ + --owner=jenkins:jenkins +``` + +### `tbot init` with POSIX ACLs + +If running `tbot` as the Linux user `teleport`, use the following invocation of +`tbot init` to initialize the short-lived certificate directory +`/opt/machine-id` with owner `teleport:teleport` but allow `jenkins` to read +from `/opt/machine-id`. + +```code +$ tbot init \ + --destination-dir=/opt/machine-id \ + --bot-user=teleport \ + --reader-user=jenkins +``` diff --git a/docs/pages/machine-id/reference/configuration.mdx b/docs/pages/machine-id/reference/configuration.mdx new file mode 100644 index 0000000000000..2ed552960e6ac --- /dev/null +++ b/docs/pages/machine-id/reference/configuration.mdx @@ -0,0 +1,99 @@ +--- +title: Machine ID Configuration Reference +description: Configuration reference for Teleport Machine ID. +--- + +The following snippet shows full YAML configuration of the Machine ID client +`tbot` which by default is loaded from `/etc/tbot.yaml`. + +```yaml +# Debug enables verbose logging to stderr. +debug: true + +# Address of the Teleport Auth Server (on-prem installs) or Teleport Cloud tenant. +auth_server: "auth.example.com:3025" # or "example.teleport.sh:443" for Teleport Cloud + +# TTL of short-lived machine certificates. +certificate_ttl: "5m" + +# Interval at which short-lived certificates are renewed; must be less than +# the certificate TTL. +renewal_interval: "1m" + +# If set, quit after the first renewal. +oneshot: true + +# Onboarding values are only used on first connect. +onboarding: + # Cluster join method. Can be "token" or "iam". + join_method: "token" + + # Token used to join the cluster. (only required for join_method: token) + token: "00000000000000000000000000000000" + + # CA Path used to validate the identity of the Teleport Auth Server on first connect. + ca_path: "/path/to/ca.pem" + + # CA Pins used to validate the identity of the Teleport Auth Server on first connect. + ca_pins: + - "sha256:1111111111111111111111111111111111111111111111111111111111111111" + - "sha256:2222222222222222222222222222222222222222222222222222222222222222" + +# Storage defines where Machine ID internal data is stored. +storage: + # Directory to store internal bot data. Access to this directory should be + # limited. + directory: /var/lib/teleport/bot + + # Alternatively, internal data can be stored in memory. "directory" and + # "memory" are mutually exclusive. + memory: true + +# Destinations specifies where short-lived certificates are stored. +destinations: + # Directory specifies where short-lived certificates are stored. + - directory: /opt/machine-id + # Configure symlink attack prevention. Requires Linux 5.6+. + # Possible values: + # * try-secure (default): Attempt to securely read and write certificates + # without symlinks, but fall back (with a warning) to insecure read + # and write if the host doesn't support this. + # * secure: Attempt to securely read and write certificates, with a hard error + # if unsupported. + # * insecure: Quietly allow symlinks in paths. + symlinks: try-secure + + # Configure ACL use. Requires Linux with a file system that supports ACLs. + # Possible values: + # * try (default on Linux): Attempt to use ACLs, warn at runtime if ACLs + # are configured but invalid. + # * off (default on non-Linux): Do not attempt to use ACLs. + # * required: Always use ACLs, produce a hard error at runtime if ACLs + # are invalid. + acls: try + + # One of more roles to grant to the bot. It must have been granted (at + # least) these roles with `tctl bots add --roles=...` + # By default, all possible roles are included. + + # Subset of roles allowed during creation via `tctl bots add --roles=...`. Can + # be used to write short-lived certificates with different roles to + # different directories. + roles: [a, b, c] + + # Which types of certificates to generate. `[ssh]` is the default. + kinds: [ssh, tls] + + # A list of configuration templates to generate and write to the + # destination directory. + configs: + # ssh_client generates known_hosts and an ssh_config that can be + # included. We can ensure the correct certificate kinds are generated + # while generating the config templates. + - ssh_client + + # Alternatively, proxy port can be set on ssh_client to override the + # defaults. Useful for Telport Cloud. + - ssh_client: + proxy_port: 443 +``` diff --git a/docs/pages/server-access/getting-started.mdx b/docs/pages/server-access/getting-started.mdx index db77a4a1b656e..ab1458ae07ab8 100644 --- a/docs/pages/server-access/getting-started.mdx +++ b/docs/pages/server-access/getting-started.mdx @@ -6,16 +6,15 @@ videoBanner: EsEvO5ndNDI # Getting Started -Server Access often involves managing your resources, configuring new clusters, and issuing commands through a CLI or programmatically to an API. +Server Access involves managing your resources, configuring new clusters, and issuing commands through a CLI or programmatically to an API. This guide introduces some of these common scenarios and how to interact with Teleport to accomplish them: -1. Creating a Teleport Auth Node cluster we'll add a Teleport SSH Node to. -2. SSH into the cluster using Teleport. -3. Introspecting the cluster using Teleport features. +1. SSH into a cluster using Teleport. +2. Introspect the cluster using Teleport features. - This guide also demonstrates how to configure Teleport Nodes into the *Bastion* pattern so that [only a single Node can be accessed publicly](https://goteleport.com/blog/ssh-bastion-host/). + This guide also demonstrates how to configure Teleport Nodes using the **bastion pattern** so that only a single Node can be accessed publicly.
    +If you have not yet deployed the Teleport Auth Service and Proxy Service, learn how to do so by following one of our [getting started guides](../getting-started.mdx). + - Add an `A` record in your domain registrar entry for the `tele` subdomain and map the first IP address to the `tele` subdomain. +(!docs/pages/includes/permission-warning.mdx!) -
    - ![Subdomain and IP mapping](../../img/server-access/subdomains.png) -
    +## Step 1/4. Install Teleport - Your second instance will be a private resource. These are the ports you'll want to initially open: +1. Create a new instance of your desired Linux distribution (such as Ubuntu 20.04, CentOS 8.0-1905, or Debian 10). - | Port | Service | Description | - | - | - | - | - | 22 | Cloud Provider | Used to initially access, configure, and provision your cloud instances. We'll configure and launch our instances then demonstrate how to use the `tsh` tool and Teleport in SSH mode thereafter. | + This instance will be a private resource. Open port 22 so you can initially access, configure, and provision your instance. We'll configure and launch our instance, then demonstrate how to use the `tsh` tool and Teleport in SSH mode thereafter. -2. Install Teleport on each instance. +2. Install Teleport on your instance. (!docs/pages/includes/install-linux.mdx!) -3. Configure Teleport on the *Bastion Host*. + Next, we'll create a **join token** to add and start Teleport Server Access on the Node. - Teleport will now automatically acquire an X.509 certificate using the ACME protocol. +## Step 2/4. Add a Node to the cluster - ```code - # Configure Teleport with TLS certs - $ sudo teleport configure \ - --acme --acme-email=your_email@example.com \ - --cluster-name=tele.example.com \ - -o file - ``` +1. Create a join token to add the Node to your Teleport cluster. In run the following command, either on your Auth Service host (for self-hosted deployments) or on your local machine (for Teleport Cloud). - Run the command above on `tele.example.com`. +
    +Teleport Cloud users must download the Enterprise version of Teleport to their local machines in order to use `tctl`. To do so, visit the [Teleport Customer Portal](https://dashboard.gravitational.com/web/login). -4. Launch your *Bastion Host* by running: +Once this is done, log in to Teleport: - ```code - # Launch Teleport - $ sudo teleport start - ``` +```code +$ tsh login --proxy=myinstance.teleport.sh +``` - Next, we'll create a *join token* to add and start the second Node. - -## Step 2/4. Add a Node to the cluster - -1. Create a *join token* to add the second Node to the `tele.example.com` cluster. In a *Bastion Host* terminal run the following command: +If you have installed `tctl` as your local user, you will not need to run `tctl` commands via `sudo`. +
    ```code # Let's save the token to a file $ sudo tctl tokens add --type=node | grep -oP '(?<=token:\s).*' > token.file ``` - Each Teleport Node can be configured into SSH mode (Teleport Node) and run as an enhanced SSH server. "Node" mode specifies that the Teleport Node will act and join as an SSH server. + Each Teleport Node can be configured into SSH mode and run as an enhanced SSH server. `--type=node` specifies that the Teleport Node will act and join as an SSH server. `> token.file` indicates that you'd like to save the output to a file name `token.file`. - This helps to minimize the direct sharing of tokens even when they are *dynamically* generated. + This helps to minimize the direct sharing of tokens even when they are dynamically generated. -2. Now, in another terminal connect to the second instance. +2. Now, open a new terminal and connect to the Teleport Auth Service. - - Copy `token.file` to an appropriate, secure directory on the second instance. + - On your Node, save `token.file` to an appropriate, secure, directory you have the rights and access to read. + - Start the Node. Change `tele.example.com` to the address of your Teleport Proxy Service. For Teleport Cloud customers, use a tenant address such as `mytenant.teleport.sh`. Assign the `--token` flag to the path where you saved `token.file`. ```code # Join cluster @@ -120,10 +91,7 @@ This guide introduces some of these common scenarios and how to interact with Te --auth-server=tele.example.com:443 ``` - - Replace the `auth-server` value with the public proxy address of the machine you wish to connect to. By default, the subdomain `tele.example.com` will be available on port `443`. - - Assign `--token` to the path of the token file you created earlier. - -3. Create a user to access the `tele.example.com` Web UI through the following command: +3. Create a user to access the Web UI through the following command: ```code $ sudo tctl users add tele-admin --roles=editor,access --logins=root,ubuntu,ec2-user @@ -135,7 +103,7 @@ This guide introduces some of these common scenarios and how to interact with Te We've only given `tele-admin` the roles `editor` and `access` according to the *Principle of Least Privilege* (POLP). -4. You should now be able to view both Nodes in the Teleport Web interface after logging in as `tele-admin`: +4. You should now be able to view your Teleport Node in Teleport Web interface after logging in as `tele-admin`:
    - We previously configured our Linux instances to leave port `22` open to easily configure and install Teleport. Feel free to compare Teleport SSH to your usual `ssh` commands. + We previously configured our Linux instance to leave port `22` open to easily configure and install Teleport. Feel free to compare Teleport SSH to your usual `ssh` commands. - If you'd like to further experiment with using Teleport according to the *Bastion* pattern: + If you'd like to further experiment with using Teleport according to the bastion pattern: - - Close port `22` on your second, private, Linux instance now that your Node is configured and running. - - Optionally close port `22` on your *Bastion Host*. - - You'll be able to fully connect to both the *Bastion Host* and the private instance using `tsh ssh`. + - Close port `22` on your private Linux instance now that your Teleport Node is configured and running. + - For self-hosted deployments, optionally close port `22` on your bastion host. + - You'll be able to fully connect to the private instance and, for self-hosted deployments, the bastion host, using `tsh ssh`. -To recap, this Getting Started Guide described: +To recap, this guide described: 1. How to set up and add an SSH Node to a cluster. 2. Connect to the cluster using `tsh` to manage and introspect resources. -Feel free to shut down, clean up, and delete your resources or use them in further Getting Started exercises. +Feel free to shut down, clean up, and delete your resources, or use them in further Getting Started exercises. ## Next steps @@ -290,7 +258,8 @@ Feel free to shut down, clean up, and delete your resources or use them in furth - For a complete list of ports used by Teleport, read [The Admin Guide](../setup/reference/networking.mdx). ## Resources - +- [Setting Up an SSH Bastion Host](https://goteleport.com/blog/ssh-bastion-host/) - [Announcing Teleport SSH Server](https://goteleport.com/blog/announcing-teleport-ssh-server/) - [How to SSH properly](https://goteleport.com/blog/how-to-ssh-properly/) - Consider whether [OpenSSH or Teleport SSH](https://goteleport.com/blog/openssh-vs-teleport/) is right for you. +- [Labels](../setup/admin/labels.mdx) diff --git a/docs/pages/server-access/guides/ansible.mdx b/docs/pages/server-access/guides/ansible.mdx deleted file mode 100644 index 56fe0cc2f4033..0000000000000 --- a/docs/pages/server-access/guides/ansible.mdx +++ /dev/null @@ -1,126 +0,0 @@ ---- -title: Ansible -description: Using Teleport with Ansible ---- - -Ansible uses the OpenSSH client by default. Teleport supports SSH protocol and -works as SSH jumphost. - -In this guide we will configure OpenSSH client to work with Teleport Proxy -and run a sample ansible playbook. - -## Prerequisites - -- Installed [Teleport Enterprise](../../enterprise/introduction.mdx) or [Teleport Cloud](../../cloud/introduction.mdx) >= (=teleport.version=) -- `tsh` client tool >= (=teleport.version=) -- `ssh` openssh tool -- `ansible` >= (=ansible.min_version=) -- Optional tool `jq` to process `JSON` output. - -## Step 1/3. Login and configure SSH - -Log into Teleport with `tsh`: - -```code -$ tsh login --proxy=example.com -``` - -Generate `openssh` configuration using `tsh config` shortcut: - -```code -$ tsh config > ssh.cfg -``` - - -You can edit matching patterns used in `ssh.cfg` if something -is not working out of the box. - - -## Step 2/3. Configure Ansible - -Create a folder `ansible` where we will collect all generated files: - -```code -$ mkdir -p ansible -$ cd ansible -``` - -Create a file `ansible.cfg`: - -``` -[defaults] -host_key_checking = True -inventory=./hosts -remote_tmp=/tmp - -[ssh_connection] -scp_if_ssh = True -ssh_args = -F ./ssh.cfg -``` - -You can create an inventory file `hosts` manually or use a script below to generate it from your environment: - -```code -$ tsh ls --format=json | jq -r '.[].spec.hostname' > hosts -``` - -## Step 3/3. Run a playbook - -Finally, let's create a simple ansible playbook `playbook.yaml`. - -The playbook below runs `hostname` on all hosts. Make sure to set the `remote_user` parameter -to a valid SSH username that works with the target host and is allowed by Teleport: - -```yaml -- hosts: all - remote_user: ubuntu - tasks: - - name: "hostname" - command: "hostname" -``` - -From the folder `ansible`, run the ansible playbook: - -```code -$ ansible-playbook playbook.yaml - -# PLAY [all] ***************************************************************************************************************************************** -# TASK [Gathering Facts] ***************************************************************************************************************************** -# -# ok: [terminal] -# -# TASK [hostname] ************************************************************************************************************************************ -# changed: [terminal] -# -# PLAY RECAP ***************************************************************************************************************************************** -# terminal : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 -``` - -You are all set. You are now using short-lived SSH certificates and Teleport can now record -all ansible commands in the audit log. - -## Troubleshooting - -In case if ansible can not connect, you may see error like this one: - -```txt -example.host | UNREACHABLE! => { - "changed": false, - "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname example.host: Name or service not known", - "unreachable": true -} -``` - -You can examine and tweak patterns matching the inventory hosts in `ssh.cfg`. - -Try the SSH connection using `ssh.cfg` with verbose mode to inspect the error: - -```code -$ ssh -vvv -F ./ssh.cfg root@example.host -``` - -If `ssh` works, try running the playbook with verbose mode on: - -```code -$ ansible-playbook -vvvv playbook.yaml -``` diff --git a/docs/pages/server-access/guides/vscode.mdx b/docs/pages/server-access/guides/vscode.mdx index 8033e245efe12..609d2b17abc3b 100644 --- a/docs/pages/server-access/guides/vscode.mdx +++ b/docs/pages/server-access/guides/vscode.mdx @@ -11,6 +11,25 @@ This guide explains how to use Teleport and Visual Studio Code's remote SSH exte - [tsh client tool](https://goteleport.com/teleport/download) >= (=teleport.version=). - OpenSSH client. - Visual Studio Code with the [Remote - SSH extension](https://code.visualstudio.com/docs/remote/ssh#_system-requirements) +for the Remote - SSH extension. +- The Teleport Auth Service and Proxy Service, deployed on your own infrastructure or managed via Teleport Cloud. +- One or more Teleport Nodes with Server Access enabled. If you have not yet done this, read the [Server Access Getting Started Guide](../getting-started.mdx) to learn how. + +
    +Follow one of our [getting started](../getting-started.mdx) guides to learn how to deploy the Teleport Auth Service and Proxy Service in your environment. +
    + +
    +Sign up for a [free trial](/signup/) of Teleport Cloud to get started. +
    Linux and MacOS clients should rely on their operating system-provided OpenSSH @@ -19,9 +38,10 @@ older clients can use `ssh.exe` from either [Git for Windows][git] or Microsoft's [Win32-OpenSSH project][win32-openssh]. -## Step 1/3. First-time Setup -Configure your local SSH client to access Teleport nodes: +## Step 1/3. First-time setup + +Configure your local SSH client to access Teleport Nodes, assigning the `--proxy` flag to the address of your Teleport Proxy Service (e.g., `mytenant.teleport.sh` for Teleport Cloud users). ```code # log in to your proxy: @@ -31,6 +51,7 @@ $ tsh login --proxy proxy.foo.example.com --user alice $ tsh config --proxy proxy.foo.example.com ``` + Append the resulting configuration snippet into your SSH config file located in the path below: @@ -53,17 +74,15 @@ in the path below: -You should be able to connect to the desired node using following command: +You should be able to connect to the desired node using following command, replacing `user` with the username you would like to assume on the node. ```code -ssh user@[node name].[cluster name] +$ ssh user@[node name].[cluster name] ``` -For example, this should work: - -```code -$ ssh alice@node000.foo.example.com -``` +
    +The SSH config you generated earlier instructs your SSH client to run `tsh proxy ssh` to access a Node in your Teleport cluster. However, running an `ssh` command against the Teleport Proxy Service at `yourtenant.teleport.sh` will result in an error. +
    Teleport's certificates expire fairly quickly, after which SSH @@ -101,12 +120,12 @@ gear icon: ![VS Code sidebar with the Remote Explorer pane open and Configure highlighted](../../../img/vscode/remote-configure.png)
    -VS code will prompt you SSH config file. +VS code will prompt you for your SSH config file. -Select the one we generated during step 1 and open it in the editor. +Select the one we generated during Step 1 and open it in the editor. -Do not use VS Code's SSH config helper; if prompted for an SSH command, +Do not use VS Code's SSH config helper. If prompted for an SSH command, close the dialog and select the "Configure" icon instead. @@ -120,21 +139,20 @@ Host node000.foo.example.com When finished, save the file. If the added host doesn't automatically appear in the list, select the Refresh button. -## Step 3/3. Start a Remote Development Session +## Step 3/3. Start a Remote Development session -Start a remote development session by right -clicking on any host added above and selecting either "Connect to..." option: +Start a Remote Development session by right-clicking on any host added above and selecting either "Connect to..." option: -
    +
    ![Connecting to a Teleport host in VS Code](../../../img/vscode/connect.png)
    -On first connect, you'll be prompted to configure the remote OS; select the +On first connect, you'll be prompted to configure the remote OS. Select the proper platform and VS Code will install its server-side component. When it completes, you should be left with a working editor:
    -![VS Code connected to a Teleport node](../../../img/vscode/connected-editor.png) +![VS Code connected to a Teleport Node](../../../img/vscode/connected-editor.png)
    The status indicator in the bottom left highlights the currently connected remote host. @@ -157,8 +175,11 @@ This guide makes use of `tsh config`, added in Teleport 7.0; refer to the [Manually configure an OpenSSH client](./openssh.mdx#manual-setup) to use the VS Code's remote SSH extension with older Teleport clients. +## Further reading +- [VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) + [remote-ssh]: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh [win10]: https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse [git]: https://git-scm.com/downloads [remote-ssh-docs]: https://code.visualstudio.com/docs/remote/ssh -[win32-openssh]: https://github.com/powershell/Win32-OpenSSH +[win32-openssh]: https://github.com/powershell/Win32-OpenSSH \ No newline at end of file diff --git a/docs/pages/setup/admin/adding-nodes.mdx b/docs/pages/setup/admin/adding-nodes.mdx index c43c48ee28fa7..792bfafa44433 100644 --- a/docs/pages/setup/admin/adding-nodes.mdx +++ b/docs/pages/setup/admin/adding-nodes.mdx @@ -1,65 +1,59 @@ --- -title: Adding Nodes to the cluster -description: Adding Nodes to the cluster +title: Adding Nodes to the Cluster +description: How to add Nodes to your Teleport cluster --- -This guide explains how to add nodes to Open Source, Enterprise Teleport, -self-hosted or cloud editions. +This guide explains how to add Teleport Nodes to your cluster. ## Prerequisites -- Installed [Teleport](../../getting-started.mdx) or [Teleport Cloud](../../cloud/introduction.mdx) >= (=teleport.version=) -- [Tctl admin tool](https://goteleport.com/teleport/download) >= (=teleport.version=) + + +Install Teleport and the `tctl` admin tool version >= (=teleport.version=). -(!docs/pages/includes/tctl.mdx!) - - -For cloud, login with a teleport user with editor privileges: -```code -# tsh logs you in and receives short-lived certificates -$ tsh login --proxy=myinstance.teleport.sh --user=email@example.com -# try out the connection -$ tctl get nodes -``` - - -## Adding Nodes to the cluster +See [Installation](../../installation.mdx) for details. -Teleport only allows access to nodes that have joined the cluster. + + +Install Teleport and the `tctl` admin tool version >= (=teleport.version=). -Once a node joins, it receives its own host certificate signed by the cluster's auth server. +To download Teleport Enterprise, visit the + [customer portal](https://dashboard.gravitational.com/web/login). + + -To receive a host certificate upon joining a cluster, a new Teleport host must present an *invite token*. +Sign up for a Teleport Cloud account. If you do not have one, visit the + [sign up page](https://goteleport.com/signup/) to begin your free trial. + + -An invite token also defines which role a new host can assume within a cluster: `auth`, `proxy`, `node`, `app`, `kube`, or `db`. - -There are two ways to create invitation tokens: +(!docs/pages/includes/tctl.mdx!) -- **Short-lived Dynamic Tokens** are more secure but require more planning. -- **Static Tokens** are easy to use, but less secure. +## Adding Nodes to the cluster -### Short-lived dynamic tokens +Teleport only allows access to Nodes that have joined the cluster. -Administrators can generate tokens as they are needed. +Once a Node joins, it receives its own host certificate signed by the cluster's +Auth Service. To receive a host certificate upon joining a cluster, a new +Teleport host must present an **invite token**. -Such token can be used multiple times until its time to live (TTL) -expires. +An invite token also defines which role a new host can assume within a cluster: +`auth`, `proxy`, `node`, `app`, `kube`, or `db`. -Use the [`tctl`](../reference/cli.mdx#tctl) tool to register a new invitation token (or -it can also generate a new token for you). In the following example a new token -is created with a TTL of 5 minutes: +### Generate a token -```code -$ tctl nodes add --ttl=5m --roles=node,proxy --token=secret-value -# The invite token: secret-value -``` +Administrators can generate tokens as they are needed. A token can be used +multiple times until its time to live (TTL) expires. -If `--token` is not provided, [`tctl`](../reference/cli.mdx#tctl) will generate one: +Use the `tctl` tool to generate a new token. In the following example, a new +token is created with a TTL of five minutes: ```code # Generate a short-lived invitation token for a new node: -$ tctl nodes add --ttl=5m --roles=node,app -# The invite token: abcdefgh-do-not-use-this-token-123 +$ tctl nodes add --ttl=5m --roles=node +# The invite token: (=presets.tokens.first=) # You can also list all generated non-expired tokens: $ tctl tokens ls @@ -67,88 +61,100 @@ $ tctl tokens ls # ------------------------ ----------- --------------- # (=presets.tokens.first=) Node 25 Sep 18 00:21 UTC -# ... or revoke an invitation before it's used: +# ... or revoke an invitation token before it's used: $ tctl tokens rm (=presets.tokens.first=) ``` +If you want to provide your own token, you can do so using the `--token` flag: + +```code +$ tctl nodes add --ttl=5m --roles=node,proxy --token=secret-value +# The invite token: secret-value +``` + +
    + +Use short-lived tokens instead of long-lived static tokens. +Static tokens are easier to steal, guess, and leak. + + +Static tokens are defined ahead of time by an administrator and stored in the +auth server's config file: + +```yaml +# Config section in `/etc/teleport.yaml` file for the auth server +auth_service: + enabled: true + tokens: + # This static token allows new hosts to join the cluster as "proxy" or "node" + - "proxy,node:secret-token-value" + # A token can also be stored in a file. In this example the token for adding + # new auth servers are stored in /path/to/tokenfile + - "auth:/path/to/tokenfile" +``` +
    + ### Using Node invitation tokens -Both static and short-lived dynamic tokens are used the same way. Execute the following -command on a new node to add it to a cluster: + + +Execute one of the following commands on a new Node to add it to a cluster: ```code -# Adding a new regular SSH node to the cluster: +# Adding an SSH Node to the cluster: $ sudo teleport start --roles=node --token=(=presets.tokens.first=) --auth-server=10.0.10.5 -# Adding a new regular SSH node using Teleport Node Tunneling: +# Adding a new regular SSH Node using Teleport Node Tunneling: $ sudo teleport start --roles=node --token=(=presets.tokens.first=) --auth-server=teleport-proxy.example.com:3080 -# Adding a new proxy service on the cluster: +# Adding a new Proxy Service to the cluster: $ sudo teleport start --roles=proxy --token=(=presets.tokens.first=) --auth-server=10.0.10.5 ``` - -As new nodes come online, they start sending ping requests every few seconds to -the CA of the cluster. This allows users to explore cluster membership and size: + + +Execute the following command on a new Node to add it to a cluster. Replace +`mytenant.teleport.sh` with the domain name of your Teleport Cloud tenant. ```code -$ tctl nodes ls - -Node Name Node ID Address Labels ---------- ------- ------- ------ -turing d52527f9-b260-41d0-bb5a-e23b0cfe0f8f 10.1.0.5:3022 distro:ubuntu -dijkstra c9s93fd9-3333-91d3-9999-c9s93fd98f43 10.1.0.6:3022 distro:debian +# Adding an SSH Node to the cluster: +$ sudo teleport start --roles=node --token=(=presets.tokens.first=) --auth-server=mytenant.teleport.sh ``` -## Revoking invitations + + -As you have seen above, Teleport uses tokens to invite users to a cluster -(sign-up tokens) or to add new nodes to it (provisioning tokens). -Both types of tokens can be revoked before they can be used. To see a list of -outstanding tokens, run this command: +As new Nodes come online, they start sending ping requests every few seconds to +the Auth Service. This allows users to explore cluster membership and size: ```code -$ tctl tokens ls +$ tctl nodes ls -# Token Role Expiry Time (UTC) -# ----- ---- ----------------- -# (=presets.tokens.first=) Proxy never -# (=presets.tokens.second=) Node 17 May 16 03:51 UTC -# (=presets.tokens.third=) Signup 17 May 16 04:24 UTC +Node Name Node ID Address Labels +--------- ------- ------- ------ +turing d52527f9-b260-41d0-bb5a-e23b0cfe0f8f 10.1.0.5:3022 distro:ubuntu +dijkstra c9s93fd9-3333-91d3-9999-c9s93fd98f43 10.1.0.6:3022 distro:debian ``` -In this example, the first token has a "never" expiry date because it is a -static token configured via a config file. - -The 2nd token with the "Node" role was generated to invite a new node to this -cluster. And the 3rd token was generated to invite a new user. +{/* TODO: This lengthy Details box should be a subsection. Using the Details box +as a workaround until we have a way to control the visibility of subsections +using the scope switcher */} -The latter two tokens can be deleted (revoked) via [`tctl tokens -del`](../reference/cli.mdx#tctl-tokens-rm) command: - -```code -$ tctl tokens del (=presets.tokens.first=) -# Token (=presets.tokens.first=) has been deleted -``` - -## Adding a Node located behind NAT +
    +Teleport Node Tunneling lets you add a remote Node to an existing Teleport Cluster through a tunnel. +This can be useful for IoT applications or for managing a couple of servers in a different network. - This feature is sometimes called "Teleport IoT" or node tunneling. +We recommend setting up a [Trusted Cluster](../admin/trustedclusters.mdx) if you +have workloads split across different networks or clouds. -With the current setup, you've only been able to add nodes that have direct access to the -auth server and within the internal IP range of the cluster. We recommend -setting up a [Trusted Cluster](../admin/trustedclusters.mdx) if you have workloads split -across different networks/clouds. +To connect a Node to your cluster via Node Tunneling, use `tctl` to create a +single-use token for a Node. Instead of supplying the IP of the Auth Service for +the `--auth-server` flag, you will use the URL of the Proxy Service. -Teleport Node Tunneling lets you add a remote node to an existing Teleport Cluster through a tunnel. -This can be useful for IoT applications, or for managing a couple of servers in a different network. - -Similar to [Adding Nodes to the Cluster](#adding-nodes-to-the-cluster), use `tctl` to -create a single-use token for a node, but this time you'll replace the auth -server IP with the URL of the proxy server. In the example below, we've -replaced the auth server IP with the proxy web endpoint `teleport-proxy.example.com:3080`. +In the example below, we've replaced the auth server IP with the Proxy's web +endpoint `teleport-proxy.example.com:3080`. ```code $ tctl tokens add --type=node | grep -oP '(?<=token:\s).*' > token.file @@ -158,10 +164,22 @@ $ tctl tokens add --type=node | grep -oP '(?<=token:\s).*' > token.file $ sudo teleport start --roles=node --token=/path/to/token.file --auth-server=teleport-proxy.example.com:3080 ``` -Using the ports in the default configuration, the node needs to be able to talk to ports `3080` and `3024` on the proxy. Port `3080` is used to initially fetch the credentials (SSH and TLS certificates) and for discovery (where is the reverse tunnel running, in this case, `3024`). For those using ACME, port `443` is also required. Port `3024 `is used to establish a connection to the auth server through the proxy. +Using the ports in Teleport's default configuration, the Node needs to be able +to talk to ports `3080` and `3024` on the Proxy Service. Port `3080` is used to +initially fetch the credentials (SSH and TLS certificates) and for discovering +the reverse tunnel. Port `3024 `is used to establish a connection to the Auth +Service through the Proxy. + +For those using ACME, port `443` is also required. -To enable multiplexing so only one port is used, simply set the `tunnel_listen_addr` the same as the -`web_listen_addr` respectively within the `proxy_service`. Teleport will automatically recognize using the same port and enable multiplexing. If the log setting is set to DEBUG you will see multiplexing enabled in the server log. +To enable multiplexing so only one port is used, simply set the +`tunnel_listen_addr` to the same value as `web_listen_addr` within the +`proxy_service` section of your configuration file. Teleport will automatically +recognize that the Proxy Service is using the same port for both addresses and +enable multiplexing. + +If your log setting is set to DEBUG, you will see multiplexing enabled in the +server log. ```txt DEBU [PROC:1] Setup Proxy: Reverse tunnel proxy and web proxy listen on the same port, multiplexing is on. service/service.go:1944 @@ -171,26 +189,66 @@ DEBU [PROC:1] Setup Proxy: Reverse tunnel proxy and web proxy listen on the s type="tip" title="Load Balancers" > - The setup above also works even if the cluster uses multiple proxies behind a load balancer (LB) or a DNS entry with multiple values. This works by the node establishing a tunnel to *every* proxy. This requires that an LB - uses a round-robin or a similar balancing algorithm. Do not use sticky load balancing algorithms (a.k.a. "session affinity") with Teleport proxies. + + The setup above also works even if the cluster uses multiple Proxy Service + instances behind a load balancer (LB) or a DNS entry with multiple values. In + this case, the Node establishes a tunnel to every proxy. + + This requires that an LB + uses a round-robin or a similar balancing algorithm. Do not use sticky load balancing algorithms (a.k.a. "session affinity") with Teleport Proxy Service instances. -## Next Steps +
    -### Untrusted auth servers +## Revoking invitations -Teleport nodes use the HTTPS protocol to offer the join tokens to the auth -server running on `10.0.10.5` in the example above. In a zero-trust environment, -you must assume that an attacker can hijack the IP address of the auth server -e.g. `10.0.10.5`. +Tokens used for joining Nodes to a cluster can be revoked before they are used. +To see a list of outstanding tokens, run this command: -To prevent this from happening, you need to supply every new node with an -additional bit of information about the auth server. This technique is called -"CA Pinning". It works by asking the auth server to produce a "CA Pin". -CA Pin is a hash value of the SPKI header in a certificate. An attacker can't -easilly forge a matching private key. +```code +$ tctl tokens ls -On the auth server: +# Token Role Expiry Time (UTC) +# ----- ---- ----------------- +# (=presets.tokens.first=) Proxy never +# (=presets.tokens.second=) Node 17 May 16 03:51 UTC +# (=presets.tokens.third=) Signup 17 May 16 04:24 UTC +``` + + + +The output of `tctl tokens ls` includes tokens used for adding users alongside +tokens used for adding Nodes to your cluster. + + + +In this example, the first token has a `never` expiry date because it is a +static token configured via a config file. + +The token with the `Node` role was generated to invite a new Node to this +cluster. And the third token was generated to invite a new user to sign up. + +The latter two tokens can be deleted (revoked) via the `tctl tokens +del` command: + +```code +$ tctl tokens del (=presets.tokens.first=) +# Token (=presets.tokens.first=) has been deleted +``` + +## Untrusted Auth Servers + +Teleport Nodes use the HTTPS protocol to offer an invite token to the Auth Service. +In a zero-trust environment, you must assume that an attacker can hijack the IP +address of the Auth Service. + +To prevent this from happening, you need to supply every new Node with an +additional bit of information about the Auth Service. This technique is called +**CA pinning**. It works by asking the Auth Service to produce a CA pin, a hash +value of the SPKI header in a certificate. This way, an attacker cannot easily +forge a matching private key. + +Retrieve the CA pin of the Auth Service: ```code $ tctl status @@ -201,10 +259,9 @@ $ tctl status # CA pin sha256:7e12c17c20d9cb504bbcb3f0236be3f446861f1396dcbb44425fe28ec1c108f1 ``` -The CA pin at the bottom needs to be passed to the new nodes when they're -starting for the first time, i.e. when they join a cluster: - -Via CLI: +The CA pin at the bottom needs to be passed to new Nodes when they're +starting for the first time, i.e. when they join a cluster. Here is an +example of running `teleport start` on a Node with a CA pin: ```code $ sudo teleport start \ @@ -214,7 +271,7 @@ $ sudo teleport start \ --auth-server=10.12.0.6:3025 ``` -or via `/etc/teleport.yaml` on a node: +You can also supply a CA pin by modifying `/etc/teleport.yaml` on a Node: ```yaml teleport: @@ -226,39 +283,12 @@ teleport: - If a CA pin is not provided, the Teleport Node will join a - cluster but it will print a `WARN` message (warning) into its standard error output. - - - - The CA pin becomes invalid if a Teleport administrator + - If a CA pin is not provided, the Teleport Node will join a cluster but it will + print a `WARN` message (warning). + - The CA pin becomes invalid if a Teleport administrator performs the CA rotation by executing [`tctl auth rotate`](../reference/cli.mdx#tctl-auth-rotate) . -### Insecure: Static tokens - - -Use short-lived tokens instead of long-lived static tokens. -Static tokens are easier to steal, guess or leak. - - -Static tokens are defined ahead of time by an administrator and stored in the -auth server's config file: - -```yaml -# Config section in `/etc/teleport.yaml` file for the auth server -auth_service: - enabled: true - tokens: - # This static token allows new hosts to join the cluster as "proxy" or "node" - - "proxy,node:secret-token-value" - # A token can also be stored in a file. In this example the token for adding - # new auth servers are stored in /path/to/tokenfile - - "auth:/path/to/tokenfile" -``` diff --git a/docs/pages/setup/admin/daemon.mdx b/docs/pages/setup/admin/daemon.mdx index 718288ae09cfe..8be2c3b565482 100644 --- a/docs/pages/setup/admin/daemon.mdx +++ b/docs/pages/setup/admin/daemon.mdx @@ -53,6 +53,6 @@ administrator must: 1. Replace the Teleport binaries, usually [`teleport`](../reference/cli.mdx#teleport) and [`tctl`](../reference/cli.mdx#tctl) -2. Execute `systemctl restart teleport` +2. Execute `systemctl reload teleport` This will perform a graceful restart, i.e. the Teleport daemon will fork a new process to handle new incoming requests, leaving the old daemon process running until existing clients disconnect. diff --git a/docs/pages/setup/admin/github-sso.mdx b/docs/pages/setup/admin/github-sso.mdx index 5bc4abdc92dbb..ef0d68de25c10 100644 --- a/docs/pages/setup/admin/github-sso.mdx +++ b/docs/pages/setup/admin/github-sso.mdx @@ -1,112 +1,146 @@ --- -title: GitHub SSO +title: Set up Single Sign-On with GitHub description: Setting up Github SSO videoBanner: XjgN2WWFCX8 --- -This guide explains how to set up Github SSO with Open Source, Enterprise Teleport, -self-hosted or cloud. +This guide explains how to set up Github Single Sign On (SSO) for Teleport. ## Prerequisites -- Installed [Teleport](../../getting-started.mdx) or [Teleport Cloud](../../cloud/introduction.mdx) >= (=teleport.version=) -- [Tctl admin tool](https://goteleport.com/teleport/download) >= (=teleport.version=) + + +- Install Teleport and the `tctl` admin tool version >= (=teleport.version=). -(!docs/pages/includes/tctl.mdx!) + See [Installation](../../installation.mdx) for details. - -For cloud, login with a teleport user with editor privileges: -```code -# tsh logs you in and receives short-lived certificates -$ tsh login --proxy=myinstance.teleport.sh --user=email@example.com -# try out the connection -$ tctl get nodes -``` - +- Create and register a GitHub OAuth App. To do so, follow the instructions in + GitHub's documentation. + + [Creating an OAuth App](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app) + + Ensure that your OAuth App's "Authentication callback URL" is + `https://PROXY_ADDRESS/v1/webapi/github/`, where `PROXY_ADDRESS` is the public + address of the Teleport Proxy Service. + + +- Install Teleport and the `tctl` admin tool version >= (=teleport.version=). + + To download Teleport Enterprise, visit the + [customer portal](https://dashboard.gravitational.com/web/login). + +- Create and register a GitHub OAuth App. To do so, follow the instructions in + GitHub's documentation. + + [Creating an OAuth App](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app) + + Ensure that your OAuth App's "Authentication callback URL" is + `https://PROXY_ADDRESS/v1/webapi/github/`, where `PROXY_ADDRESS` is the public + address of the Teleport Proxy Service. + + + +- Sign up for a Teleport Cloud account. If you do not have one, visit the + [sign up page](https://goteleport.com/signup/) to begin your free trial. + +- Create and register a GitHub OAuth App. To do so, follow the instructions in GitHub's documentation. + + [Creating an OAuth App](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app) + + Ensure that your OAuth App's "Authentication callback URL" is + `https://PROXY_ADDRESS/v1/webapi/github/`, where `PROXY_ADDRESS` is the domain + name of your Teleport Cloud tenant (e.g., mytenant.teleport.sh). + + + + +(!docs/pages/includes/tctl.mdx!) -## Step 1/2. Create Github connector +## Step 1/2. Create a GitHub authentication connector -Define a Github connector: +Define a GitHub authentication connector by creating a file called `github.yaml` +with the following content: ```yaml -# Create a file called github.yaml: kind: github version: v3 metadata: - # connector name that will be used with `tsh --auth=github login` + # Connector name that will be used with `tsh --auth=github login` name: github spec: - # Client ID of Github OAuth app + # Client ID of your GitHub OAuth App client_id: - # Client secret of Github OAuth app + # Client secret of your GitHub OAuth App client_secret: - # Connector display name that will be shown on web UI login screen - display: Github + # Connector display name that will be shown on the Web UI login screen + display: GitHub # Callback URL that will be called after successful authentication redirect_url: https:///v1/webapi/github/callback # Mapping of org/team memberships onto allowed logins and roles teams_to_logins: - - organization: octocats # Github organization name - team: admins # Github team name within that organization - # maps octocats/admins to teleport role access + - organization: octocats # GitHub organization name + team: admins # GitHub team name within that organization + # Maps octocats/admins to the "access" Teleport role logins: - access ``` -To obtain a client ID and client secret, please follow Github documentation on -how to [create and register an OAuth app](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). +The values of `client_id`, `client_secret`, and `redirect_url` come from the +GitHub OAuth App you created earlier. -Be sure to set the "Authorization callback URL" to the same value as `redirect_url` in the resource spec. +Teleport will request only the `read:org` OAuth scope. Read more about OAuth scopes in GitHub's documentation: -Teleport will request only the `read:org` OAuth scope, you can read more about -[Github OAuth scopes](https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/). +[Github OAuth scopes](https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/) -Finally, create the connector using [`tctl`](../reference/cli.mdx#tctl) -[resource](../reference/resources.mdx) management command: +Finally, create the connector using `tctl`: ```code $ tctl create github.yaml ``` - When going through the Github authentication flow for the first time, + When going through the GitHub authentication flow for the first time, the application must be granted access to all organizations that are present in the "teams to logins" mapping, otherwise Teleport will not be - able to determine team memberships for these orgs. + able to determine team memberships for these organizations. ## Step 2/2. Configure authentication preference -Configure Teleport Auth Service Github for authentication: +Configure the Teleport Auth Service to enable the GitHub authentication +connector. - - - ```yaml - # Snippet from /etc/teleport.yaml - auth_service: - authentication: - type: github - ``` - - - Create a file `cap.yaml`: - ```yaml - kind: cluster_auth_preference - metadata: - name: cluster-auth-preference - spec: - type: github - webauthn: - rp_id: 'example.teleport.sh' - version: v2 - ``` +Create a file called `cap.yaml` with the following content: - Create a resource: +```yaml +kind: cluster_auth_preference +metadata: + name: cluster-auth-preference +spec: + type: github + webauthn: + rp_id: 'example.teleport.sh' +version: v2 +``` - ```code - $ tctl create -f cap.yaml - ``` - - +Create the resource: + +```code +$ tctl create -f cap.yaml +``` + +
    + +You can also edit your Teleport configuration file to include the following: + +```yaml +# Snippet from /etc/teleport.yaml +auth_service: + authentication: + type: github +``` +
    -You can now login with Teleport using `github` SSO. +You can now log in with Teleport using GitHub SSO. diff --git a/docs/pages/setup/admin/troubleshooting.mdx b/docs/pages/setup/admin/troubleshooting.mdx index a34e70d1f31e5..a25257ebdedf4 100644 --- a/docs/pages/setup/admin/troubleshooting.mdx +++ b/docs/pages/setup/admin/troubleshooting.mdx @@ -15,27 +15,9 @@ run with verbose logging enabled by passing it `-d` flag. It is not recommended to run Teleport in production with verbose logging as it generates a substantial amount of data. -Sometimes you may want to reset [`teleport`](../reference/cli.mdx#teleport) to a clean -state. This can be accomplished by erasing everything under `"data_dir"` -directory. Assuming the default location, `rm -rf /var/lib/teleport/*` will do. - -Teleport also supports HTTP endpoints for monitoring purposes. They are disabled -by default, but you can enable them: - -```code -$ sudo teleport start --diag-addr=127.0.0.1:3000 -``` - -Now you can see the monitoring information by visiting several endpoints: - -- `http://127.0.0.1:3000/metrics` is the list of internal metrics Teleport is tracking. It is compatible with [Prometheus](https://prometheus.io/) - collectors. For a full list of metrics review our [metrics reference](../reference/metrics.mdx). -- `http://127.0.0.1:3000/healthz` returns "OK" if the process is healthy or - `503` otherwise. -- `http://127.0.0.1:3000/readyz` is similar to `/healthz`, but it returns "OK" - *only after* the node successfully joined the cluster, i.e.it draws the difference between "healthy" and "ready". -- `http://127.0.0.1:3000/debug/pprof/` is Golang's standard profiler. It's only - available when `-d` flag is given in addition to `--diag-addr` +Sometimes you may want to reset [`teleport`](../reference/cli.mdx#teleport) to a +clean state. This can be accomplished by erasing everything under the `data_dir` +directory, which defaults to `/var/lib/teleport/`. ## Debug dump diff --git a/docs/pages/setup/admin/trustedclusters.mdx b/docs/pages/setup/admin/trustedclusters.mdx index 7873dbeb884ed..f8432db18ec0f 100644 --- a/docs/pages/setup/admin/trustedclusters.mdx +++ b/docs/pages/setup/admin/trustedclusters.mdx @@ -31,7 +31,7 @@ This guide's focus is on more in-depth coverage of trusted clusters features and type="tip" title="Teleport Node Tunneling" > - If you have a large number of devices on different networks, such as managed IoT devices or a couple of nodes on a different network you can utilize the [Teleport Node Tunneling](./adding-nodes.mdx#adding-a-node-located-behind-nat). + If you have a large number of devices on different networks, such as managed IoT devices or a couple of nodes on a different network you can utilize [Teleport Node Tunneling](./adding-nodes.mdx). ## Introduction diff --git a/docs/pages/setup/deployments/aws-terraform.mdx b/docs/pages/setup/deployments/aws-terraform.mdx index 655746343b030..9aed641328ee5 100644 --- a/docs/pages/setup/deployments/aws-terraform.mdx +++ b/docs/pages/setup/deployments/aws-terraform.mdx @@ -758,7 +758,7 @@ auth_servers: ### Joining nodes via Teleport IoT/node tunneling To join Teleport nodes from outside the same VPC, you will either need to investigate VPC peering/gateways (out of scope -for this document) or join your nodes using [Teleport's node tunneling](../admin/adding-nodes.mdx#adding-a-node-located-behind-nat) functionality. +for this document) or join your nodes using [Teleport's node tunneling](../admin/adding-nodes.mdx) functionality. With this method, you can join the nodes using the public facing proxy address - `teleport.example.com:443` for our example. diff --git a/docs/pages/setup/guides/fluentd.mdx b/docs/pages/setup/guides/fluentd.mdx index 28ce9f79370bf..a78eb48a2f0c9 100644 --- a/docs/pages/setup/guides/fluentd.mdx +++ b/docs/pages/setup/guides/fluentd.mdx @@ -4,37 +4,82 @@ description: Forwarding events with Fluentd and Teleport event handler videoBanner: HAqxs4rBv2c --- -This section will cover: +In this guide, we will explain how to: -- Setting up Teleport's event handler. -- Forwarding events with Fluentd for Cloud, Enterprise and Open Source editions. +- Set up Teleport's event handler. +- Forward events with Fluentd. ## Prerequisites -- Teleport v(=teleport.version=) Cloud, Open Source or Enterprise -- Teleport admin tool `tctl` configured to access the cluster. + + + +- A running Teleport cluster. For details on how to set this up, see [Getting + Started on a Linux Server](../../getting-started/linux-server.mdx). + +- The `tctl` admin tool version >= (=teleport.version=). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + See [Installation](../../installation.mdx) for details. + + (!docs/pages/includes/tctl.mdx!) + + + + +- A running Teleport cluster. For details on setting this up, see our + [Enterprise getting started guide](../../enterprise/getting-started.mdx). + +- The `tctl` admin tool version >= (=teleport.version=), which you can download + by visiting the + [customer portal](https://dashboard.gravitational.com/web/login). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + (!docs/pages/includes/tctl.mdx!) + + + + +- A Teleport Cloud account. If you do not have one, visit the + [sign up page](https://goteleport.com/signup/) to begin your free trial. + +- The Enterprise version of the `tctl` admin tool. To download this, visit +the [customer portal](https://dashboard.gravitational.com/web/login). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + (!docs/pages/includes/tctl.mdx!) + + + + - Fluentd version v(=fluentd.version=). - Docker version v(=docker.version=). -```code -$ tctl version -# Teleport v(=teleport.version=) go(teleport.golang) -# -# Test connectivity and admin permissions -$ tctl get roles -``` - -Create a folder `fluentd` to hold configuration and plugin state: +Create a folder called `fluentd` to hold configuration and plugin state: ```code $ mkdir -p event-handler $ cd event-handler ``` -## Step 1/6. Install event handler plugin +## Step 1/6. Install the event handler plugin -Teleport event handler runs alongside fluentd forwarder, receives events from Teleport's events API -and forwards them to fluentd. +The Teleport event handler runs alongside the Fluentd forwarder, receives events +from Teleport's events API, and forwards them to Fluentd. @@ -54,13 +99,30 @@ and forwards them to fluentd. ## Step 2/6. Generate configuration -Run `configure` command to generate sample configuration. Replace `teleport.example.com:443` -with the DNS name and web proxy port of Teleport's proxy: + + + +Run the `configure` command to generate a sample configuration. Replace +`teleport.example.com:443` with the DNS name and HTTPS port of Teleport's Proxy +Service: ```code $ ./teleport-event-handler configure . teleport.example.com:443 ``` + + + +Run the `configure` command to generate a sample configuration. Replace +`mytenant.teleport.sh` with the DNS name of your Teleport Cloud tenant: + +```code +$ ./teleport-event-handler configure . mytenant.teleport.sh +``` + + + + You'll see the following output: ```txt @@ -76,7 +138,7 @@ Follow-along with our getting started guide: https://goteleport.com/setup/guides/fluentd ``` -Plugin has generated several setup files: +The plugin generates several setup files: ```code $ ls -l @@ -91,15 +153,19 @@ $ ls -l # -rw------- 1 bob bob 343 Jul 1 11:14 teleport-event-handler.toml ``` -- `ca.crt` and `ca.key` is a fluentd self-signed CA certificate and a private key. -- `server.crt` and `server.key` is a fluentd server certificate and key. -- `client.crt` and `client.key` is a fluentd client certificate and key, all signed by the generated CA. -- `teleport-event-handler-role.yaml` is a Teleport's event handler client user and role. -- `fluent.conf` is a fluentd plugin configuration. +|File(s)|Purpose| +|---|---| +| `ca.crt` and `ca.key`| Self-signed CA certificate and private key for Fluentd| +| `server.crt` and `server.key`| Fluentd server certificate and key| +| `client.crt` and `client.key`| Fluentd client certificate and key, all signed by the generated CA| +| `teleport-event-handler-role.yaml`| `user` and `role` resource definitions for Teleport's event handler |client. +| `fluent.conf`| Fluentd plugin configuration| -## Step 3/6. Create user and role for reading audit events +## Step 3/6. Create a user and role for reading audit events -`configure` command generated `teleport-event-handler-role.yaml` that defines a `teleport-event-handler` role and a user with read-only access to the `event` API: +The `configure` command generates a file called +`teleport-event-handler-role.yaml` that defines a `teleport-event-handler` role +and a user with read-only access to the `event` API: ```yaml kind: user @@ -130,24 +196,16 @@ $ tctl create -f teleport-event-handler-role.yaml ## Step 4/6. Create teleport-event-handler credentials -Teleport Plugin use the fluentd role and user to read the events. We export the identity files, using tctl auth sign. - -```code -$ tctl auth sign --out identity --user teleport-event-handler -# The credentials have been written to identity -``` - -This will generate `identity` which contains TLS certificates and will be used to connect plugin to your Teleport instance. - - -Clients, for example cloud interactive users, could be missing impersonation privileges when trying to use `tctl auth sign`, -will get the following error: +### Enable impersonation of the Fluentd plugin user -```txt -ERROR: access denied: impersonation is not allowed -``` +In order for the Fluentd plugin to forward events from your Teleport cluster, it +needs a signed identity file from the cluster's certificate authority. The +Fluentd user cannot request this itself, and requires another user to +**impersonate** this account in order to request a certificate. -Create the following file with role: `teleport-event-handler-impersonator.yaml`: +Create a role that enables your user to impersonate the Fluentd user. First, +paste the following YAML document into a file called +`teleport-event-handler-impersonator.yaml`: ```yaml kind: role @@ -169,28 +227,61 @@ spec: roles: ["teleport-event-handler"] ``` +Next, create the role: + ```code $ tctl create teleport-event-handler-impersonator.yaml ``` -Assign this role to the current user by running the below command, subsituing the user as appropriate: +Assign this role to the current user by running the following command, substituting +the user as appropriate: + ```code $ tctl get users/ > out.yaml ``` -Now edit `out.yaml` with your favourite editor, adding `teleport-event-handler-impersonator` to the list of existing roles and update via `tctl` again: +Now edit `out.yaml`, adding `teleport-event-handler-impersonator` to the list of +existing roles and update via `tctl` again: + ``` $ tctl create -f out.yaml ``` -Re-login to assume the new role and try to issue certificate for the teleport-event-handle user again. - +Log in to your Teleport cluster again to assume the new role. + +### Export an identity file for the Fluentd plugin user -## Step 5/6. Start fluentd forwarder +The Fluentd Teleport plugin uses the `teleport-event-handler` role and user to +read events. We export an identity file for the user with the `tctl auth sign` +command. -The plugin will send events to the fluentd instance using keys generated on the previous step. + + +```code +$ tctl auth sign --format=tls --user=teleport-event-handler --out=auth +``` -Generated sample `fluent.conf` file sets accepts events using `TLS` and prints them: +This command should result in three PEM-encoded files: `auth.crt`, `auth.key`, +and `auth.cas` (certificate, private key, and CA certs, respectively). + + + +```code +$ tctl auth sign --user=teleport-event-handler --out=identity +``` + +The above sequence should result in one PEM-encoded file: `terraform-identity`. + + + + +## Step 5/6. Start the Fluentd forwarder + +The Fluentd plugin will send events to your Fluentd instance using keys +generated on the previous step. + +The `fluent.conf` file generated earlier configures your Fluentd instance to +accept events using TLS and print them: ``` @@ -223,7 +314,7 @@ Generated sample `fluent.conf` file sets accepts events using `TLS` and prints t ``` -To try it out, start fluentd instance: +To try out this Fluentd configuration, start your fluentd instance: ```code $ docker run -u $(id -u ${USER}):$(id -g ${USER}) -p 8888:8888 -v $(pwd):/keys -v $(pwd)/fluent.conf:/fluentd/etc/fluent.conf fluent/fluentd:edge @@ -231,8 +322,11 @@ $ docker run -u $(id -u ${USER}):$(id -g ${USER}) -p 8888:8888 -v $(pwd):/keys - ## Step 6/6. Start the event handler plugin -`configure` command generated `teleport-event-handler.toml` configuration file -for the event handler: +Earlier, we generated a file called `teleport-event-handler.toml` to configure +the Fluentd event handler. This file includes setting similar to the following: + + + ```toml storage = "./storage" @@ -251,27 +345,53 @@ addr = "example.teleport.com:443" identity = "identity" ``` -Start an export from the current moment: + + + +```toml +storage = "./storage" +timeout = "10s" +batch = 20 +namespace = "default" + +[forward.fluentd] +ca = "/home/sasha/scripts/event-handler/ca.crt" +cert = "/home/sasha/scripts/event-handler/client.crt" +key = "/home/sasha/scripts/event-handler/client.key" +url = "https://localhost:8888/test.log" + +[teleport] +addr = "example.teleport.com:443" +client_key = "auth.key" +client_crt = "auth.crt" +root_cas = "auth.cas" +``` + + + + +To start the event handler, run the following command: ```code $ ./teleport-event-handler start --config teleport-event-handler.toml ``` -This example will start export from `May 5th 2021`: + +This example will start exporting from `May 5th 2021`: ```code $ ./teleport-event-handler start --config teleport-event-handler.toml --start-time "2021-05-05T00:00:00Z" ``` -Start time can be set only once, on the first run of the tool. +The start time can be set only once, on the first run of the tool. -If you want to change the time frame later, remove plugin state dir -which you had specified in `storage-dir` argument. +If you want to change the time frame later, remove the plugin state directory +that you specified in the `storage` field of the handler's configuration file. -Once handler starts, you will see notifications about scanned and forwarded events: +Once the handler starts, you will see notifications about scanned and forwarded events: ```txt INFO[0046] Event sent id=0b5f2a3e-faa5-4d77-ab6e-362bca0994fc ts="2021-06-08 11:00:56.034 +0000 UTC" type=user.login diff --git a/docs/pages/setup/guides/joining-nodes-aws.mdx b/docs/pages/setup/guides/joining-nodes-aws.mdx index 5d1423afb3707..96c0a62af6d6d 100644 --- a/docs/pages/setup/guides/joining-nodes-aws.mdx +++ b/docs/pages/setup/guides/joining-nodes-aws.mdx @@ -1,6 +1,6 @@ --- title: Joining Nodes in AWS -description: How to join nodes and proxies running on AWS +description: How to join Nodes and Proxies running on AWS h1: Joining Nodes and Proxies in AWS --- @@ -23,7 +23,11 @@ IAM credentials with `ec2:DescribeInstances` permissions are required on your Teleport Auth server. No IAM credentials are required on the Nodes or Proxies. -The **IAM join method** is available in Teleport 8.3+ cloud or self-hosted. + +Teleport Cloud does not support the EC2 join method. + + +The **IAM join method** is available in Teleport 8.3+ Cloud or self-hosted. It is available to any Teleport Node or Proxy running anywhere with access to IAM credentials, such as an EC2 instance with an attached IAM role. No specific permissions or IAM policy is required: an IAM role with no attached @@ -102,7 +106,7 @@ for details. ## Step 2/4. Create the AWS Node Joining token Configure your Teleport auth server with a special dynamic token which will -allow nodes from your AWS account to join your Teleport cluster. +allow Nodes from your AWS account to join your Teleport cluster. @@ -132,11 +136,11 @@ spec: join_method: iam allow: - # specify the AWS account which nodes may join from + # Specify the AWS account that Nodes may join from - aws_account: "111111111111" # multiple allow rules are supported - aws_account: "222222222222" - # aws_arn is optional and allows you to restrict the IAM role of joining nodes + # aws_arn is optional and allows you to restrict the IAM role of joining Nodes - aws_account: "333333333333" aws_arn: "arn:aws:sts::111111111111:assumed-role/teleport-node-role/i-*" ``` @@ -228,7 +232,7 @@ Kubernetes, Application, or Database roles. The service should be run directly on an AWS EC2 instance and must have network access to the AWS EC2 IMDSv2 (enabled by default for most EC2 instances). -Configure your Teleport node with a custom `teleport.yaml` file. Use the +Configure your Teleport Node with a custom `teleport.yaml` file. Use the `join_params` section with `token_name` matching your token created in Step 2 and `method: ec2` as shown in the following example config: @@ -252,7 +256,7 @@ proxy_service: ## Step 4/4. Launch your Teleport Node -Start Teleport on the node and confirm that it is able to connect to and join +Start Teleport on the Node and confirm that it is able to connect to and join your cluster. You're all set! @@ -269,11 +273,11 @@ re-running `tctl create -f token.yaml`. ### Configuring the EC2 join method for Multiple AWS Accounts -This section is not necessary when using the IAM join method, multiple accounts +This section is not necessary when using the IAM join method. Multiple accounts are support by default. -In order for Teleport nodes to join from EC2 instances in AWS accounts other +In order for Teleport Nodes to join from EC2 instances in AWS accounts other than the account in which your Teleport auth server is running, Teleport must have permissions to assume an IAM role in each of those accounts and call `ec2:DescribeInstances` in the foreign account. @@ -317,7 +321,7 @@ In the AWS account where your Teleport auth server is running: 2. Attach this `teleport-AssumeRole-policy` to the IAM role your Teleport auth server has credentials for, see [Step 1.2](#step-12-attach-the-iam-policy). -When creating the AWS Node Joining token, include an allow rule for each foreign +When creating the AWS Node joining token, include an allow rule for each foreign account and specify the AWS ARN for the foreign `teleport-DescribeInstances-role`. diff --git a/docs/pages/setup/guides/terraform-provider.mdx b/docs/pages/setup/guides/terraform-provider.mdx index 33a5dd4441784..35256435a8648 100644 --- a/docs/pages/setup/guides/terraform-provider.mdx +++ b/docs/pages/setup/guides/terraform-provider.mdx @@ -4,25 +4,89 @@ description: Configuration as a code with Terraform Provider videoBanner: YgNHD4SS8dg --- -This section will cover: +This guide will explain how to: -- Setting up Teleport's terraform provider on Linux and Mac. -- Configuring users and roles using terraform provider for Cloud, Enterprise and Open Source editions. +- Set up Teleport's Terraform provider on Linux and Mac. +- Configure Teleport users and roles using the Terraform provider. ## Prerequisites + + + +- A running Teleport cluster. For details on how to set this up, see [Getting + Started on a Linux Server](../../getting-started/linux-server.mdx). + +- The `tctl` admin tool version >= (=teleport.version=). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + See [Installation](../../installation.mdx) for details. + + (!docs/pages/includes/tctl.mdx!) + - [Terraform >= (=terraform.version=)+](https://learn.hashicorp.com/tutorials/terraform/install-cli) -- Teleport (=teleport.version=) Cloud, Open Source or Enterprise -```code -$ terraform version -# Terraform v(=terraform.version=) -``` + ```code + $ terraform version + # Terraform v(=terraform.version=) + ``` + + + + +- A running Teleport cluster. For details on setting this up, see our + [Enterprise getting started guide](../../enterprise/getting-started.mdx). + +- The `tctl` admin tool version >= (=teleport.version=), which you can download + by visiting the + [customer portal](https://dashboard.gravitational.com/web/login). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + (!docs/pages/includes/tctl.mdx!) + +- [Terraform >= (=terraform.version=)+](https://learn.hashicorp.com/tutorials/terraform/install-cli) + + ```code + $ terraform version + # Terraform v(=terraform.version=) + ``` + + + + +- A Teleport Cloud account. If you do not have one, visit the + [sign up page](https://goteleport.com/signup/) to begin your free trial. + +- The Enterprise version of the `tctl` admin tool. To download this, visit +the [customer portal](https://dashboard.gravitational.com/web/login). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + (!docs/pages/includes/tctl.mdx!) + +- [Terraform >= (=terraform.version=)+](https://learn.hashicorp.com/tutorials/terraform/install-cli) + + ```code + $ terraform version + # Terraform v(=terraform.version=) + ``` + + + -```code -$ tctl version -# Teleport v(=teleport.version=) go(teleport.golang) -``` Create a folder `teleport-terraform` to hold some temporary files: @@ -31,7 +95,7 @@ $ mkdir -p teleport-terraform $ cd teleport-terraform ``` -## Step 1/4. Install terraform provider +## Step 1/4. Install the Terraform provider @@ -51,7 +115,54 @@ $ cd teleport-terraform -## Step 2/4. Create a terraform user +## Step 2/4. Create a Terraform user + +### Enable impersonation + +In order for Terraform to manage resources in your Teleport cluster, it needs a +signed identity file from the cluster's certificate authority. The Terraform user +cannot request this itself, and requires another user to **impersonate** this +account in order to request a certificate. + +Create a role that enables your user to impersonate the Terraform user. First, paste +the following YAML document into a file called `terraform-impersonator.yaml`: + +```yaml +kind: role +version: v5 +metadata: + name: terraform-impersonator +spec: + # SSH options used for user sessions + options: + # max_session_ttl defines the TTL (time to live) of SSH certificates + # issued to the users with this role. + max_session_ttl: 10h + + # The allow section declares a list of resource/verb combinations that are + # allowed for the users of this role. By default, nothing is allowed. + allow: + impersonate: + users: ['terraform'] + roles: ['terraform'] + + # The deny section uses the identical format as the 'allow' section. + # Deny rules always override allow rules. + deny: + node_labels: + '*': '*' +``` + +Next, create the role: + +```code +$ tctl create terraform-impersonator.yaml +``` + +Assign this role to the current user. Log in to your Teleport cluster to assume +the new role. + +### Create the Terraform user Put the following content into `terraform.yaml`: @@ -74,79 +185,75 @@ spec: version: v2 ``` -Run: +Create the `terraform` user and role. ```code $ tctl create terraform.yaml -$ tctl auth sign --format=file --user=terraform --out=terraform-identity --ttl=10h ``` - -Clients missing impersonation privileges when trying to use `tctl auth sign`, -will get the following error: +Next, request a signed certificate for the Terraform user: -```txt -ERROR: access denied: impersonation is not allowed + + +```code +$ tctl auth sign --format=tls --user=terraform --out=auth ``` -Create the following file with role: `terraform-impersonator.yaml`: - -```yaml -kind: role -version: v5 -metadata: - name: terraform-impersonator -spec: - # SSH options used for user sessions - options: - # max_session_ttl defines the TTL (time to live) of SSH certificates - # issued to the users with this role. - max_session_ttl: 10h - - # allow section declares a list of resource/verb combinations that are - # allowed for the users of this role. by default nothing is allowed. - allow: - impersonate: - users: ['terraform'] - roles: ['terraform'] - - # the deny section uses the identical format as the 'allow' section. - # the deny rules always override allow rules. - deny: - node_labels: - '*': '*' -``` +This command should result in three PEM-encoded files: `auth.crt`, `auth.key`, +and `auth.cas` (certificate, private key, and CA certs, respectively). + + ```code -tctl create terraform-impersonator.yaml +$ tctl auth sign --user=terraform --out=terraform-identity ``` -Assign this role to the current user. Re-login to assume the new role and try -to issue certificate for terraform user again. +The above sequence should result in one PEM-encoded file: `terraform-identity`. - + + -## Step 3/4. Create Terraform configuration +## Step 3/4. Create a Terraform configuration -Create a `main.tf` terraform file: +Paste the following into a file called `main.tf` to define an example user and +role using Terraform. + + ``` -(!examples/resources/terraform/terraform-user-role.tf!) +(!examples/resources/terraform/terraform-user-role-cloud.tf!) ``` - - -Update `teleport.example.com:443` with the address of your Teleport cluster. - + + +``` +(!examples/resources/terraform/terraform-user-role-self-hosted.tf!) +``` + + ## Step 4/4. Apply the configuration -Check the contents of `teleport-terraform` folder: +Check the contents of the `teleport-terraform` folder: + + + ```code $ ls # main.tf terraform-identity terraform-impersonator.yaml terraform.yaml ``` + + + +```code +$ ls +# main.tf auth.crt auth.key auth.cas terraform-impersonator.yaml terraform.yaml +``` + + + + Init terraform and apply the spec: ```code @@ -156,5 +263,5 @@ $ terraform apply ## Next Steps -- Find the full list of [supported terraform provider resources](../reference/terraform-provider.mdx). +- Find the full list of [supported Terraform provider resources](../reference/terraform-provider.mdx). - Read more about [impersonation](../../access-controls/guides/impersonation.mdx). diff --git a/docs/pages/setup/operations/ca-rotation.mdx b/docs/pages/setup/operations/ca-rotation.mdx index 6be371fca1769..dfeabee9a2388 100644 --- a/docs/pages/setup/operations/ca-rotation.mdx +++ b/docs/pages/setup/operations/ca-rotation.mdx @@ -5,63 +5,125 @@ description: How to rotate Teleport's certificate authority ## Prerequisites -- Installed [Teleport](../../getting-started.mdx) or [Teleport Cloud](../../cloud/introduction.mdx) >= (=teleport.version=) -- [Tctl admin tool](https://goteleport.com/teleport/download) >= (=teleport.version=) + + + +- A running Teleport cluster. For details on how to set this up, see [Getting + Started on a Linux Server](../../getting-started/linux-server.mdx). + +- The `tctl` admin tool version >= (=teleport.version=). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + See [Installation](../../installation.mdx) for details. + + + + +- A running Teleport cluster. For details on setting this up, see our + [Enterprise getting started guide](../../enterprise/getting-started.mdx). + +- The `tctl` admin tool version >= (=teleport.version=), which you can download + by visiting the + [customer portal](https://dashboard.gravitational.com/web/login). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + + + +- A Teleport Cloud account. If you do not have one, visit the + [sign up page](https://goteleport.com/signup/) to begin your free trial. + +- The Enterprise version of the `tctl` admin tool. To download this, visit +the [customer portal](https://dashboard.gravitational.com/web/login). + + ```code + $ tctl version + # Teleport v(=teleport.version=) go(=teleport.golang=) + ``` + + + (!docs/pages/includes/tctl.mdx!) - -For cloud, login with a teleport user with editor privileges: -```code -# tsh logs you in and receives short-lived certificates -$ tsh login --proxy=myinstance.teleport.sh --user=email@example.com -# try out the connection -$ tctl get nodes -``` + +## Certificate Authority rotation + +This section will show you how to implement certificate rotation in practice. + + + If you are using [CA + Pinning](../admin/adding-nodes.mdx#untrusted-auth-servers) when adding new + nodes, the CA pin will change after the rotation. Make sure you use the *new* + CA pin when adding nodes after rotation. -## Certificate Authority Rotation +### Rotation phases -Take a look at the [Certificates chapter](../../architecture/authentication.mdx#authentication-in-teleport) in the -architecture document to learn how the certificate authority rotation works. +The rotation consists of several phases: -This section will show you how to implement certificate rotation in practice. +- `standby`: All operations have completed or haven't started yet. +- `init`: All components are notified of the rotation. A new certificate + authority is issued, but not used. It is necessary for remote trusted clusters + to fetch the new certificate authority, otherwise new clients will reject it. +- `update_clients`: Internal clients certs are updated and reloaded. Servers + will use and respond with old credentials because clients have no idea about + new certificates at first. +- `update_servers`: Servers reload and start serving TLS and SSH certificates + signed by the new certificate authority, but will still accept certificates + issued by the old certificate authority. +- `rollback`: The rotation was aborted and is rolling back to the old + certificate authority. -During manual and semi-automatic certificate authority rotation, Teleport generates a new certificate -authority and issues certificates for auth servers, proxies, nodes and users. +### Rotation types -Rotation consists of several phases: +There are two kinds of certificate rotations: -- `standby` All operations have completed or haven't started yet. -- `init` - All components are notified of the rotation. A new certificate authority is issued, but not used. - It is necessary for remote trusted clusters to fetch the new certificate authority, otherwise the new clients - will reject it. -- `update_clients` - internal clients certs are updated and reloaded. - Servers will use and respond with old credentials because clients have no idea about new certificates at first. -- `update_servers` Servers will reload and would start serving -TLS and SSH certificates signed by the new certificate authority, but will still accept certificates -issued by old certificate authority. -- `rollback` rotation is rolling back to the old certificate authority. +- **Manual:** it is the cluster administrator's reponsibility to transition + between each phase of the rotation while monitoring the state of the cluster. + Manual rotations provide the greatest level of control, and are performed by + providing the desired phase using the `--phase` flag with the + `tctl auth rotate` command. +- **Semi-automatic:** Teleport automatically transitions between phases of the + rotation after some amount of time (known as a *grace period*) elapses. -Both in manual and semi-automatic rotation, cluster goes through the states above in sequence: +For both types of rotations, the cluster goes through the phases in the +following order: - `standby` -> `init` -> `update_clients` -> `update_servers` -> `standby` -Administrators can rollback all the changes before rotation is completed by entering `standby`. +Administrators can abort the rotation and revert all changes any time before +the rotation is completed by entering the `rollback` phase. -For example, if admin has detected that some nodes failed to upgrade during `update_servers`, -they can rollback to the previous certificate authority: +```sh +$ tctl auth rotate --phase=rollback --manual +``` + +For example, if an admin has detected that some nodes failed to upgrade during +`update_servers`, they can roll back to the previous certificate authority, and +the phase transitions look like this: - `update_servers` -> `rollback` -> `standby`. -Try rotation/rollback in manual mode first to understand all the edge-cases -and gotchas before going with semi-automatic version. + Try rotation/rollback in manual mode first to understand all the edge-cases + and gotchas before going with semi-automatic version. ## Manual rotation -In manual mode, we would transition between phases while monitoring the state of the cluster. +In manual mode, we manually transition between phases while monitoring the state +of the cluster. **Start the rotation** @@ -72,7 +134,7 @@ $ tctl auth rotate --phase=init --manual --type=host Updated rotation phase to "init". To check status use 'tctl status' ``` -Cluster status will reflect active rotation in progress: +Use `tctl` to confirm that there is an active rotation in progress: ```code $ tctl status @@ -96,23 +158,24 @@ $ tctl get nodes --format=json | jq '.[] | {hostname: .spec.hostname, rotation: } ``` -Host `terminal` has updated it status to phase `init`. It has downloaded a new CA public key and is ready -for state transitions. +In this example, the node named `terminal` has updated its status to phase +`init`. This means it has downloaded a new CA public key and is ready for state +transitions. - -If some nodes are offline during rotation or have failed to update the status, -you will lose connectivity after the transition `update_servers` -> `standby`. Make sure that all -nodes are up to date with the transitions. + + If some nodes are offline during rotation or have failed to update the status, + you will lose connectivity after the transition `update_servers` -> `standby`. + Make sure that all nodes are up to date with the transitions before + proceeding. **Update clients** -Execute transition `init` -> `update_clients`: +Execute the transition from `init` to `update_clients`: ```code $ tctl auth rotate --phase=update_clients --manual -# Updated rotation phase to "init". To check status use 'tctl status' +# Updated rotation phase to "update_clients". To check status use 'tctl status' $ tctl status # Cluster acme.cluster # Version (=teleport.version=) @@ -120,7 +183,8 @@ $ tctl status ``` -Clients will temporarily lose connectivity during proxy and auth servers restarts. + Clients will temporarily lose connectivity during Proxy and Auth Server + restarts. Verify that nodes have caught up and now see the current cluster state: @@ -136,11 +200,12 @@ $ tctl get nodes --format=json | jq '.[] | {hostname: .spec.hostname, rotation: **Update servers** -All nodes have caught up. Execute the transition `update_clients` -> `update_servers`: +Now that all nodes have caught up, execute the transition from `update_clients` +to `update_servers`: ```code $ tctl auth rotate --phase=update_servers --manual -# Updated rotation phase to "init". To check status use 'tctl status' +# Updated rotation phase to "update_servers". To check status use 'tctl status' $ tctl status # Cluster acme.cluster @@ -149,8 +214,9 @@ $ tctl status ``` -Usually if things go wrong, they go wrong at this transition. If you have lost connectivity to nodes, -[rollback](#rollback) to the old certificate authority. + Usually if things go wrong, they go wrong at this transition. If you have lost + connectivity to nodes, [roll back](#rollback) to the old certificate + authority. Verify that nodes have caught up: @@ -169,28 +235,23 @@ $ tctl get nodes --format=json | jq '.[] | {hostname: .spec.hostname, rotation: Before wrapping up, verify that you have not lost any nodes and can connect to them, for example: ```code -$ tsh ssh hello@terminal hostname +$ tsh ssh hello@terminal ``` -This is the last stage when you can rollback. If you have lost connectivity to nodes, -[rollback](#rollback) to the old certificate authority. + This is the last stage where you have the opportunity to roll back. If you + have lost connectivity to nodes, [roll back](#rollback) to the old certificate + authority. ```code $ tctl auth rotate --phase=standby --manual -# Updated rotation phase to "init". To check status use 'tctl status' - -$ tctl status -# Cluster acme.cluster -# Version (=teleport.version=) -# Host CA rotating servers (mode: manual, started: Sep 20 01:44:36 UTC, ending: Sep 21 07:44:36 UTC) ``` -Cluster status should indicate succesffully completed rotation. +Verify that the rotation has completed with `tctl`: ```code -tctl status +$ tctl status Cluster acme.cluster Version (=teleport.version=) Host CA rotated Sep 20 02:11:25 UTC @@ -210,31 +271,26 @@ $ tctl get nodes --format=json | jq '.[] | {hostname: .spec.hostname, rotation: } ``` - -If you are using [CA Pinning](../admin/adding-nodes.mdx#untrusted-auth-servers) when adding new nodes, the CA pin will change after the rotation. -Make sure you use the *new* CA pin when adding nodes after rotation. - - ## Semi-Automatic rotation -Semi-automatic rotation executes the same steps as the manual rotation, but with a grace period between them. -It currently does not track the states of the nodes and you can lose connectivity if things go wrong. + Semi-automatic rotation executes the same steps as the manual rotation, but + with a grace period between them. It currently does not track the states of + the nodes and you can lose connectivity if things go wrong. -You can trigger semi-automatic rotation: +You can trigger semi-automatic rotation by omitting the `--manual` and `--phase` +flags. ```code $ tctl auth rotate ``` -This will trigger a rotation process for both hosts and users with a *grace period* of 48 hours. -During the grace period, certificates issued both by old and new certificate authority work. +This will trigger a rotation process for both hosts and users with a default +grace period of 48 hours. During the grace period, certificates issued both by +old and new certificate authority work. -You can customize grace period: +You can customize grace period and CA type with additional flags: ```code # Rotate only user certificates with a grace period of 200 hours: @@ -248,33 +304,29 @@ The rotation takes time, especially for hosts, because each node in a cluster needs to be notified that a rotation is taking place and request a new certificate for itself before the grace period ends. - - Be careful when choosing a grace period when rotating host certificates. The grace period needs to be long enough for all nodes in a cluster to request a new certificate. If some nodes go offline during the - rotation and come back only after the grace period has ended, they will be - forced to leave the cluster, i.e. users will no longer be allowed to SSH - into them. - +During semi-automatic rotations, Teleport will attempt to divide the grace +period so that it spends an equal amount of time in each phase before +transitioning to the next phase. This means that using a shorter grace period +will result in faster state transitions. + + + Be careful when choosing a grace period when rotating host certificates. + + +The grace period needs to be long enough for all nodes in a cluster to request a +new certificate. If some nodes go offline during the rotation and come back only +after the grace period has ended, they will be forced to leave the cluster, i.e. +users will no longer be allowed to SSH into them. -Check the cluster status of rotation: +Check the cluster status: ```code -tctl status +$ tctl status Cluster acme.cluster Version (=teleport.version=) Host CA initialized (mode: manual, started: Sep 20 01:44:36 UTC, ending: Sep 21 07:44:36 UTC) ``` - - If you are using [CA Pinning](../admin/adding-nodes.mdx#untrusted-auth-servers) when adding new nodes, the CA pin will change after the rotation. Make sure you use the - *new* CA pin when adding nodes after rotation. - - Check the status of individual nodes: ```code @@ -287,21 +339,22 @@ $ tctl get nodes --format=json | jq '.[] | {hostname: .spec.hostname, rotation: } ``` -Host `terminal` has updated it status to phase `init`. It has downloaded a new CA public key and is ready -for state transitions. +The node named `terminal` has updated its status to phase `init`. This means it +has downloaded a new CA public key and is ready for state transitions. ## Rollback -Rollback is only possible before rotation enters `standby` state. +Rollback must be performed before the rotation enters `standby` state. -First, override the rotation to the manual rollback: +First, enter the rollback phase with a manual phase transition: ```code $ tctl auth rotate --phase=rollback --manual # Updated rotation phase to "rollback". To check status use 'tctl status' ``` -Make sure that nodes that have updated have caught up: +Make sure that any nodes which have already updated have caught up and entered +the `rollback` phase. ```code # Check rotation status of the nodes @@ -313,5 +366,11 @@ $ tctl get nodes --format=json | jq '.[] | {hostname: .spec.hostname, rotation: } ``` -If any of the nodes were lost and using the old cert authority, they should reconnect -once you switch the control plane to the old cert authority. +If connectivity to any of the nodes was lost during the rotation, this is likely +because they were still using the old cert authority. Connectivity to these +nodes should be restored when the rollback completes and the old certificate +authority is made active. + +## Further reading + +How the [Teleport Certificate Authority](../../architecture/authentication.mdx#authentication-in-teleport) works. diff --git a/docs/pages/setup/reference/authentication.mdx b/docs/pages/setup/reference/authentication.mdx index f6b2ba7e99ebd..134e980fd7e6c 100644 --- a/docs/pages/setup/reference/authentication.mdx +++ b/docs/pages/setup/reference/authentication.mdx @@ -1,22 +1,20 @@ --- title: Authentication options -description: Teleport enterprise license file configuration parameters and requirements +description: A reference for Teleport's authentication connectors --- -Teleport uses the concept of "authentication connectors" to authenticate users -when they execute [`tsh login`](./cli.mdx#tsh-login) command. There are three -types of authentication connectors: +Teleport authenticates users with an identity provider via **authentication +connectors**. There are three types of authentication connectors. -## Local +## Local (no authentication connector) Local authentication is used to authenticate against a local Teleport user -database. This database is managed by [`tctl users`](./cli.mdx#tctl-users-add) +database. This database is managed by the [`tctl users`](./cli.mdx#tctl-users-add) command. Teleport also supports second-factor authentication (2FA) for the local connector. There are several possible values (types) of 2FA: -- `otp` is the default. It implements [TOTP](https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) - standard. You can use [Google Authenticator](https://en.wikipedia.org/wiki/Google_Authenticator) - or [Authy](https://www.authy.com/) or any other TOTP client. +- `otp` is the default. It implements the [TOTP](https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) + standard. You can use [Google Authenticator](https://en.wikipedia.org/wiki/Google_Authenticator), [Authy],(https://www.authy.com/) or any other TOTP client. - `webauthn` implements the [Web Authentication standard](https://webauthn.guide) for utilizing second factor authenticators and hardware devices. You can use [YubiKeys](https://www.yubico.com/), [SoloKeys](https://solokeys.com/) or any other authenticator that @@ -63,13 +61,12 @@ You can modify these settings in the static config `teleport.yaml` or using dyna - SSO users can also register 2FA devices, but Teleport will not prompt them for 2FA during login. Login 2FA for SSO users should be handled by the SSO - provider. + SSO users can also register 2FA devices, but Teleport will not prompt them for 2FA during login. Login 2FA for SSO users should be handled by the SSO provider. ## GitHub -This connector implements Github OAuth 2.0 authentication flow. Please refer to GitHub documentation on [Creating an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) +This connector implements GitHub's OAuth 2.0 authentication flow. Please refer to GitHub's documentation on [Creating an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) to learn how to create and register an OAuth app. Here is an example of this setting in the `teleport.yaml` : @@ -84,9 +81,9 @@ See [Github OAuth 2.0](../admin/github-sso.mdx) for details on how to configure ## SAML - -You need Enterprise edition of Teleport for this option. - + +You need the Enterprise edition of Teleport for this option. + This connector type implements SAML authentication. It can be configured against any external identity manager like Okta or Auth0. This feature is only available for Teleport Enterprise. @@ -100,9 +97,9 @@ auth_service: ### OIDC - -You need Enterprise edition of Teleport for this option. - + +You need the Enterprise edition of Teleport for this option. + Teleport implements OpenID Connect (OIDC) authentication, which is similar to SAML in principle. diff --git a/docs/pages/setup/reference/cli.mdx b/docs/pages/setup/reference/cli.mdx index f1d572a5a7fc7..0339c46a3c02c 100644 --- a/docs/pages/setup/reference/cli.mdx +++ b/docs/pages/setup/reference/cli.mdx @@ -38,7 +38,7 @@ the following services. | - | - | - | - | | `-d, --debug` | none | none | enable verbose logging to stderr | | `--insecure-no-tls` | `false` | `true` or `false` | Tells proxy to not generate default self-signed TLS certificates. This is useful when running Teleport on kubernetes (behind reverse proxy) or behind things like AWS ELBs, GCP LBs or Azure Load Balancers where SSL termination is provided externally. | -| `-r, --roles` | `proxy,node,auth` | **string** comma-separated list of `proxy, node` or `auth` | start listed services/roles. These roles are explained in the [Teleport Architecture](../../architecture/overview.mdx) document. | +| `-r, --roles` | `proxy,node,auth` | **string** comma-separated list of `proxy, auth, node, db, app` or `windowsdesktop` | start listed services/roles. These roles are explained in the [Teleport Architecture](../../architecture/overview.mdx) document. | | `--pid-file` | none | **string** filepath | create a PID file at the path | | `--advertise-ip` | none | **string** IP | advertise IP to clients, often used behind NAT | | `-l, --listen-ip` | `0.0.0.0` | [**net. IP**](https://golang.org/pkg/net/#IP) | binds services to IP | @@ -311,6 +311,42 @@ $ tsh play --format=pty 1fe153d1-ce8b-4ef4-9908-6539457ba4ad.tar $ tsh play --format=json ~/play/0c0b81ed-91a9-4a2a-8d7c-7495891a6ca0.tar | jq '.event ``` +### tsh proxy app + +Starts a local TLS proxy for Application Access connections. +You can use this proxy to connect to an application repeatedly after a single login to your Teleport cluster, +which is especially useful for interacting with an application via a CLI. + +```code +$ tsh proxy app [] +``` + +#### Arguments +`` + +- `app` The name of the application to start the local proxy for. To see a list of available applications, run `tsh apps ls`. + +#### Flags + +| Name | Default Value(s) | Allowed Value(s) | Description | +| - | - | - | - | +| `--port` | none | port number | Specify the source port for the local proxy | + +#### [Global Flags](#tsh-global-flags) + +These flags are available for all commands `--login, --proxy, --user, --ttl, --identity, --cert-format, --insecure, --auth, --skip-version-check, --debug, --jumphost, --format`. +Run `tsh help ` or see the [Global Flags Section](#tsh-global-flags) + +#### Examples +```code +$ tsh proxy app + +# Proxy a connection to grafana on local port 10700 +$ tsh proxy --port 10700 app grafana & +# Proxying connections to grafana on 127.0.0.1:10700 +$ curl http://127.0.0.1:10700/api/users +``` + ### tsh scp Copies files from source to dest: @@ -902,7 +938,7 @@ $ tctl nodes add [] | Name | Default Value(s) | Allowed Value(s) | Description | | - | - | - | - | -| `--roles` | `node` | `node,auth` or `proxy` | Comma-separated list of roles for the new node to assume | +| `--roles` | `node` | `proxy, auth, node, db, app` or `windowsdesktop` | Comma-separated list of roles for the new node to assume | | `--ttl` | 30m | relative duration like 5s, 2m, or 3h | Time to live for a generated token | | `--token` | none | **string** token value | A custom token to use, auto-generated if not provided. Should match token set with `teleport start --token` | @@ -953,9 +989,9 @@ $ tctl tokens add --type=TYPE [] | Name | Default Value(s) | Allowed Value(s) | Description | | - | - | - | - | -| `--type` | none | `trusted_cluster`, `node`, `proxy` | Type of token to add | +| `--type` | none | `proxy`, `auth`, `trusted_cluster`, `node`, `db`, `kube`, `app`, `windowsdesktop` | Type of token to add | | `--value` | none | **string** token value | Value of token to add | -| `--ttl` | 1h | relative duration like 5s, 2m, or 3h, **maximum 48h** | Set expiration time for token | +| `--ttl` | 1h | relative duration like 5s, 2m, or 3h | Set expiration time for token | #### [Global Flags](#tctl-global-flags) @@ -1289,6 +1325,37 @@ Print cluster version: tctl version ``` +## Resource filtering + +Both `tsh` and `tctl` allow you to filter `nodes`, `apps`, `db`, and `kube` resources using the `--search` and `--query` flags. + +The `--search` flag performs a simple fuzzy search on resource fields. For example, `--search=mac` searches for resources containing `mac`. + +The `--query` flag allows you to perform more sophisticated searches using a [predicate language](predicate-language.mdx#resource-filtering). + +In both cases, you can further refine the results by appending a list of comma-separated labels to the command. For example: + +```bash +$ tsh ls --search=foo,bar labelKey1=labelValue1,labelKey2=labelValue2 +``` + +### Filter Examples + +```bash +# List all nodes +$ tsh ls + +# List nodes using label argument +$ tsh ls env=staging,os=mac + +# List nodes using search keywords +$ tsh ls --search=staging,mac + +# List nodes using predicate language. This query searches for nodes with labels +# with key `env` equal to `staging` and key `os` equal to `mac`. +$ tsh ls --query='labels.env == "staging" && equals(labels.os, "mac")' +``` + ## Experimental features ### tctl access ls diff --git a/docs/pages/setup/reference/license.mdx b/docs/pages/setup/reference/license.mdx index 273c4e4663fbc..6532a9f385d4c 100644 --- a/docs/pages/setup/reference/license.mdx +++ b/docs/pages/setup/reference/license.mdx @@ -3,11 +3,18 @@ title: Enterprise License File description: Teleport enterprise license file configuration parameters and requirements --- -Commercial Teleport subscriptions require a valid license. The license file can +Commercial self-hosted Teleport subscriptions require a valid license. The license file can be downloaded from the [Teleport Customer -Portal](https://dashboard.gravitational.com/web/login). +Portal](https://dashboard.gravitational.com/web/login). When downloading the file, the products you are licensed to use will be listed in the browser. -The Teleport license file contains a X.509 certificate and the corresponding private key in PEM format. Place the downloaded file on Auth servers and set the `license_file` configuration parameter of your `teleport.yaml` to point to the file location: + +Teleport Cloud manages licensing for customers, and there is no need to manage license files yourself. + + +The Teleport license file contains an X.509 certificate and the corresponding +private key in PEM format. Place the downloaded file on your Teleport Auth +Servers and set the `license_file` configuration parameter of your +`teleport.yaml` to point to the file location: ```yaml auth_service: @@ -18,9 +25,9 @@ The `license_file` path can be either absolute or relative to the configured `data_dir`. If the license file path is not set, Teleport will look for the `license.pem` file in the configured `data_dir`. - - Only Auth servers require the license. Proxies and Nodes that do not also have Auth role enabled do not need the license. - +Only Auth Servers require the license. Proxies and Nodes that do not also run the Auth Service do not need the license. + +Attempts to use unlicensed products will result in an error message and users will be unable to use those products. For example, if you attempt to use Database Access without it being included in the license, you will get a message similar to: +```code +this Teleport cluster is not licensed for database access, please contact the cluster administrator +``` diff --git a/docs/pages/setup/reference/metrics.mdx b/docs/pages/setup/reference/metrics.mdx index ae574253ff76f..69c8ff0cf30bb 100644 --- a/docs/pages/setup/reference/metrics.mdx +++ b/docs/pages/setup/reference/metrics.mdx @@ -1,30 +1,79 @@ --- -title: Teleport Metrics -description: How to set up Prometheus to monitor Teleport for SSH and Kubernetes access -h1: Metrics +title: Teleport Diagnostics +description: How to use Teleport's health, readiness, profiling, and monitoring endpoints. --- -## Teleport Prometheus endpoint - -Teleport provides HTTP endpoints for monitoring purposes. They are disabled -by default, but you can enable them using the `--diag-addr` flag to `teleport start`: +Teleport provides HTTP endpoints for monitoring purposes. They are disabled by +default, but you can enable them using the `--diag-addr` flag when running +`teleport start`: ```code $ sudo teleport start --diag-addr=127.0.0.1:3000 ``` -Now you can see the monitoring information by visiting several endpoints: - -- `http://127.0.0.1:3000/metrics` is the list of internal metrics Teleport is - tracking. It is compatible with [Prometheus](https://prometheus.io/) - collectors. -- `http://127.0.0.1:3000/healthz` returns "OK" if the process is healthy or - `503` otherwise. -- `http://127.0.0.1:3000/readyz` is similar to `/healthz`, but it returns "OK" - *only after* the node successfully joined the cluster, i.e.it draws the - difference between "healthy" and "ready". -- `http://127.0.0.1:3000/debug/pprof/` is Golang's standard profiler. It's only - available when `-d` flag is given in addition to `--diag-addr` +Now you can collect monitoring information from several endpoints. + +## `/healthz` + +The `http://127.0.0.1:3000/healthz` endpoint responds with a body of +`{"status":"ok"}` and an HTTP 200 OK status code if the process is running. + +This is a simple check, suitable for determining if the Teleport process is +still running. + +## `/readyz` + +The `http://127.0.0.1:3000/readyz` endpoint is similar to `/healthz`, but its +response includes information about the state of the process. + +The response body is a JSON object of the form: + +``` +{ "status": "a status message here"} +``` + +### `/readyz` and heartbeats + +If a Teleport component fails to execute its heartbeat procedure, it will enter +a degraded state. Teleport will begin recovering from this state when a +heartbeat completes successfully. + +The first successful heartbeat will transition Teleport into a recovering state. + +A second consecutive successful heartbeat will cause Teleport to transition to +the OK state, so long as at least 10 seconds have elapsed since the +first successful heartbeat. + +Teleport heartbeats run every 5 seconds. This means that depending on the timing +of heartbeats, it can take 10-20 seconds after connectivity is restored for +`/readyz` to start reporting healthy again. + +### Status codes + +The status code of the response can be one of: + +- HTTP 200 OK: Teleport is operating normally +- HTTP 503 Service Unavailable: Teleport has encountered a connection error and + is running in a degraded state. This happens when a Teleport heartbeat fails. +- HTTP 400 Bad Request: Teleport is either entering its initial startup phase or + has begun recovering from a degraded state. + +The same state information is also available via the `process_state` metric +under the `/metrics` endpoint. + +## `/debug/pprof` + +The `http://127.0.0.1:3000/debug/pprof/` endpoint is Go's standard pprof +profiler. This endpoint is only available if the `--debug` (or `-d`) flag is +supplied (in addition to `--diag-addr`). + +## `/metrics` + +The `http://127.0.0.1:3000/metrics` endpoint serves the internal metrics +Teleport is tracking. It is compatible with +[Prometheus](https://prometheus.io/) collectors. + +The following metrics are available: | Name | Type | Component | Description | | - | - | - | - | diff --git a/docs/pages/setup/reference/predicate-language.mdx b/docs/pages/setup/reference/predicate-language.mdx new file mode 100644 index 0000000000000..946e3e71bdd8d --- /dev/null +++ b/docs/pages/setup/reference/predicate-language.mdx @@ -0,0 +1,71 @@ +--- +title: Predicate Language +description: How to use Teleport's predicate language to define filter conditions. +--- + +Teleport's predicate language is used to define conditions for filtering in dynamic configuration resources. +It is also used as a query language to filter and search through a [list of select resources](#resource-filtering). + +The predicate language uses a slightly different syntax depending on whether it is used in: + +- [Role resources](#scoping-allowdeny-rules-in-role-resources) +- [Resource filtering](#resource-filtering) + +## Scoping allow/deny rules in role resources + +Some fields in Teleport's role resources use the predicate language to define the scope of a role's permissions: + +- [Dynamic Impersonation](../../access-controls/guides/impersonation.mdx#filter-fields) +- [RBAC for sessions](../../access-controls/reference.mdx#filter-fields) + +When used in role resources, the predicate language supports the following operators: + +| Operator | Meaning | Example | +|----------|--------------------------------------------------|----------------------------------------------------------| +| && | and (all conditions must match) | `contains(field1, field2) && equals(field2, "val")` | +| \|\| | or (any one condition should match) | `contains(field1, field2) \|\| contains(field1, "val2")` | +| ! | not (used with functions, more about this below) | `!equals(field1, field2)` | + +The language also supports the following functions: + +| Functions | Description | +|--------------------------------|---------------------------------------------------------------------------------------| +| `contains(, )` | checks if the value from `` is included in the list of strings from `` | +| `contains(, "")` | checks if `` is included in the list of strings from `` | +| `equals(, )` | checks if the value from `` is equal to the value from `` | +| `equals(, "")` | checks if `` is equal to the value from `` | + + +## Resource filtering + +Both the [`tsh`](cli.mdx#tsh) and [`tctl`](cli.mdx#tctl) CLI tools allow you to filter nodes, +applications, databases, and Kubernetes resources using the `--query` flag. The `--query` flag allows you to +perform more sophisticated searches using the predicate language. + +For common resource fields, we defined shortened field names that can easily be accessed by: + +| Short Field | Actual Field Equivalent | Example | +|----------------|-------------------------------------------------------------|---------------------------| +| `labels.` | `resource.metadata.labels` + `resource.spec.dynamic_labels` | `labels.env == "staging"` | +| `name` | `resource.metadata.name` | `name == "jenkins"` | + +The language supports the following operators: + +| Operator | Meaning | Example | +|----------|--------------------------------------|-----------------------------------------------------| +| == | equal to | `labels.env == "prod"` or `labels["env"] == "prod"` | +| != | not equal to | `labels.env != "prod"` | +| && | and (all conditions must match) | `labels.env == "prod" && labels.os == "mac"` | +| \|\| | or (any one condition should match) | `labels.env == "dev" \|\| labels.env == "qa"` | +| ! | not (used with functions) | `!equals(labels.env, "prod")` | + +The language also supports the following functions: + +| Functions (with examples) | Description | +|---------------------------------------|------------------------------------------------------------| +| `equals(labels.env, "prod")` | resources with label key `env` equal to label value `prod` | +| `exists(labels.env)` | resources with a label key `env`; label value unchecked | +| `!exists(labels.env)` | resources without a label key `env`; label value unchecked | +| `search("foo", "bar", "some phrase")` | fuzzy match against common resource fields | + +See some [examples](cli.mdx#filter-examples) of the different ways you can filter resources. diff --git a/docs/postrelease.md b/docs/postrelease.md index 3257156b7599e..8a04ea1a3a3fd 100644 --- a/docs/postrelease.md +++ b/docs/postrelease.md @@ -16,4 +16,9 @@ This checklist is to be run after cutting a release. - Example: https://github.com/gravitational/teleport/pull/4602 - [ ] Create PR to update default Teleport image referenced in docker/teleport-quickstart.yml and docker/teleport-ent-quickstart.yml - Example: https://github.com/gravitational/teleport/pull/4655 -- [ ] Create PR to update default Teleport image referenced in docker/teleport-lab.yml \ No newline at end of file +- [ ] Create PR to update default Teleport image referenced in docker/teleport-lab.yml +- [ ] Update the CI buildbox image + - [ ] Update the `BUILDBOX_VERSION` in `build.assets/Makefile` + - [ ] Run `make dronegen` and ensure _all_ buildbox references in the resulting yaml refer to the new image + - [ ] Commit and merge. Drone should build new buildbox images and push to `quay.io` + - [ ] Once the new images are confirmed in `quay.io`, update the build yaml files under `.cloudbuild` to refer to the new image diff --git a/dronegen/mac_pkg.go b/dronegen/mac_pkg.go index 4b01ff2567883..4cecacf430746 100644 --- a/dronegen/mac_pkg.go +++ b/dronegen/mac_pkg.go @@ -16,6 +16,7 @@ package main import ( "fmt" + "path/filepath" "strings" ) @@ -56,8 +57,8 @@ func darwinPkgPipeline(name, makeTarget string, pkgGlobs []string) pipeline { "APPLE_USERNAME": {fromSecret: "APPLE_USERNAME"}, "APPLE_PASSWORD": {fromSecret: "APPLE_PASSWORD"}, "BUILDBOX_PASSWORD": {fromSecret: "BUILDBOX_PASSWORD"}, - "OSS_TARBALL_PATH": {raw: "/tmp/build-darwin-amd64-pkg/go/artifacts"}, - "ENT_TARBALL_PATH": {raw: "/tmp/build-darwin-amd64-pkg/go/artifacts"}, + "OSS_TARBALL_PATH": {raw: filepath.Join(p.Workspace.Path, "go/artifacts")}, + "ENT_TARBALL_PATH": {raw: filepath.Join(p.Workspace.Path, "go/artifacts")}, "OS": {raw: b.os}, "ARCH": {raw: b.arch}, }, diff --git a/dronegen/main.go b/dronegen/main.go index 9fde790cb6088..b3bb1ccc41b64 100644 --- a/dronegen/main.go +++ b/dronegen/main.go @@ -17,7 +17,6 @@ package main import ( "bytes" "fmt" - "io/ioutil" "os" "gopkg.in/yaml.v2" @@ -55,7 +54,7 @@ func writePipelines(path string, newPipelines []pipeline) error { // TODO: When all pipelines are migrated, remove this merging logic and // write the file directly. This will be simpler and allow cleanup of // pipelines when they are removed from this generator. - existingConfig, err := ioutil.ReadFile(path) + existingConfig, err := os.ReadFile(path) if err != nil { return fmt.Errorf("failed to read existing config: %w", err) } @@ -104,7 +103,7 @@ func writePipelines(path string, newPipelines []pipeline) error { } configData := bytes.Join(pipelinesEnc, []byte("\n---\n")) - return ioutil.WriteFile(path, configData, 0664) + return os.WriteFile(path, configData, 0664) } // parsedPipeline is a single pipeline parsed from .drone.yml along with its diff --git a/e b/e index ad8fd4aa9128b..9956b32428b41 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit ad8fd4aa9128b3dd5e80e84804be9850004f58b1 +Subproject commit 9956b32428b41caa3f29c5ba8ccc0fe458c8e957 diff --git a/examples/aws/cloudformation/ent.yaml b/examples/aws/cloudformation/ent.yaml index 78cef76d49d2e..040df910cdf38 100644 --- a/examples/aws/cloudformation/ent.yaml +++ b/examples/aws/cloudformation/ent.yaml @@ -97,24 +97,24 @@ Mappings: t2.xlarge: {Arch: HVM64} AWSRegionArch2AMI: - # All AMIs from AWS - gravitational-teleport-ami-ent-8.3.1 + # All AMIs from AWS - gravitational-teleport-ami-ent-9.0.1 eu-north-1: {HVM64: ami-05ff5c0be3d4b8da4} - ap-south-1: {HVM64 : ami-0cf5de7e380590bd1} - eu-west-1: {HVM64 : ami-015d16a97a12e9f3e} - eu-west-2: {HVM64 : ami-0a1399d687e79f230} + ap-south-1: {HVM64 : ami-0cfab1c56ef76e56d} + eu-west-1: {HVM64 : ami-06d4f4a9c05b825e5} + eu-west-2: {HVM64 : ami-0dad9a7e8019828b4} eu-west-3: {HVM64: ami-087bdce4ab6a2964d} - ap-northeast-1: {HVM64 : ami-06238708fc042e0fb} - ap-northeast-2: {HVM64 : ami-0a8e2d7d4adede112} + ap-northeast-1: {HVM64 : ami-058897779d3b609da} + ap-northeast-2: {HVM64 : ami-008d7f1530778b283} ap-northeast-3: {HVM64: ami-0a36f2dfdca83ea7d} - sa-east-1: {HVM64 : ami-04a865841550cf519} - ca-central-1: {HVM64 : ami-0e964a19a5eef1633} - ap-southeast-1: {HVM64 : ami-0b8ea0f231fa1f5a1} - ap-southeast-2: {HVM64 : ami-03a3137350321d957} - eu-central-1: {HVM64 : ami-04f8ff95544fd0431} - us-east-1: {HVM64 : ami-0adc14e4ffaaad880} - us-east-2: {HVM64 : ami-0718beffe1e13c0a1} - us-west-1: {HVM64 : ami-044f8952623e0c010} - us-west-2: {HVM64 : ami-04cea237af09e9143} + sa-east-1: {HVM64 : ami-014b64ac9c28e9a8d} + ca-central-1: {HVM64 : ami-0230b681466dd1cbd} + ap-southeast-1: {HVM64 : ami-0676627a7acfa18f8} + ap-southeast-2: {HVM64 : ami-07f605ef28b14a8c5} + eu-central-1: {HVM64 : ami-0220eec85a7bd0bca} + us-east-1: {HVM64 : ami-07ffa5186f4446e88} + us-east-2: {HVM64 : ami-046f6cfab972281dd} + us-west-1: {HVM64 : ami-080f35de39e3c0a43} + us-west-2: {HVM64 : ami-0d46e313ef984eb11} Resources: # Auth server setup diff --git a/examples/aws/cloudformation/oss.yaml b/examples/aws/cloudformation/oss.yaml index e98725a5ca13a..2a8ac999d6f1a 100644 --- a/examples/aws/cloudformation/oss.yaml +++ b/examples/aws/cloudformation/oss.yaml @@ -97,24 +97,24 @@ Mappings: t2.xlarge: {Arch: HVM64} AWSRegionArch2AMI: - # All AMIs from AWS - gravitational-teleport-ami-oss-8.3.1 + # All AMIs from AWS - gravitational-teleport-ami-oss-9.0.1 eu-north-1: {HVM64: ami-0eef7480d85b07d78} - ap-south-1: {HVM64 : ami-0e71fa13d0a5e33f2} - eu-west-1: {HVM64 : ami-05a0e7906f76be419} - eu-west-2: {HVM64 : ami-06ac2f9f8b32cc9cf} + ap-south-1: {HVM64 : ami-04a5baf8089868310} + eu-west-1: {HVM64 : ami-024a59feb00e6f9d3} + eu-west-2: {HVM64 : ami-0304ace50865a0248} eu-west-3: {HVM64: ami-0211c6e2e821dd249} - ap-northeast-1: {HVM64 : ami-0a80043fecc73a945} - ap-northeast-2: {HVM64 : ami-00366bfff7fb938e3} + ap-northeast-1: {HVM64 : ami-02192f3f6b37326be} + ap-northeast-2: {HVM64 : ami-0c305fdb520cd2d03} ap-northeast-3: {HVM64: ami-02bb8618b75d025aa} - sa-east-1: {HVM64 : ami-016fc226c84d65aa0} - ca-central-1: {HVM64 : ami-03a5d00511ccae664} - ap-southeast-1: {HVM64 : ami-047dd4f4aca8ce448} - ap-southeast-2: {HVM64 : ami-0ed11afe3b8b432a5} - eu-central-1: {HVM64 : ami-0be730bb45d2696cc} - us-east-1: {HVM64 : ami-0301399a479b2fd45} - us-east-2: {HVM64 : ami-02da5e6cdbc0eeea0} - us-west-1: {HVM64 : ami-07556dfe74d1ce75b} - us-west-2: {HVM64 : ami-0b0d4a5a84433577a} + sa-east-1: {HVM64 : ami-0357206209e0e1ac8} + ca-central-1: {HVM64 : ami-02cacc908a9ecaed0} + ap-southeast-1: {HVM64 : ami-0c7d9089b8fb98d49} + ap-southeast-2: {HVM64 : ami-0735924e3c026e00e} + eu-central-1: {HVM64 : ami-0a8a74865d820373c} + us-east-1: {HVM64 : ami-050e1228432bc017e} + us-east-2: {HVM64 : ami-06ad7d932122afd7b} + us-west-1: {HVM64 : ami-07b0c77d68a2e98cf} + us-west-2: {HVM64 : ami-0c2505a13d6e118e7} Resources: # Auth server setup diff --git a/examples/aws/terraform/AMIS.md b/examples/aws/terraform/AMIS.md index 876c68e0687bc..54ef0aed0a837 100644 --- a/examples/aws/terraform/AMIS.md +++ b/examples/aws/terraform/AMIS.md @@ -6,65 +6,65 @@ is updated when new AMI versions are released. ### OSS ``` -# eu-north-1 v8.3.1 OSS: ami-05d4fa4c08dea86be -# ap-south-1 v8.3.1 OSS: ami-0e71fa13d0a5e33f2 -# eu-west-1 v8.3.1 OSS: ami-05a0e7906f76be419 -# eu-west-2 v8.3.1 OSS: ami-06ac2f9f8b32cc9cf -# eu-west-3 v8.3.1 OSS: ami-0246af46b3d1a44d8 -# ap-northeast-1 v8.3.1 OSS: ami-0a80043fecc73a945 -# ap-northeast-2 v8.3.1 OSS: ami-00366bfff7fb938e3 -# ap-northeast-3 v8.3.1 OSS: ami-098b67353e16bc627 -# sa-east-1 v8.3.1 OSS: ami-016fc226c84d65aa0 -# ca-central-1 v8.3.1 OSS: ami-03a5d00511ccae664 -# ap-southeast-1 v8.3.1 OSS: ami-047dd4f4aca8ce448 -# ap-southeast-2 v8.3.1 OSS: ami-0ed11afe3b8b432a5 -# eu-central-1 v8.3.1 OSS: ami-0be730bb45d2696cc -# us-east-1 v8.3.1 OSS: ami-0301399a479b2fd45 -# us-east-2 v8.3.1 OSS: ami-02da5e6cdbc0eeea0 -# us-west-1 v8.3.1 OSS: ami-07556dfe74d1ce75b -# us-west-2 v8.3.1 OSS: ami-0b0d4a5a84433577a +# eu-north-1 v9.0.1 OSS: ami-06c7f71980ff71b25 +# ap-south-1 v9.0.1 OSS: ami-04a5baf8089868310 +# eu-west-1 v9.0.1 OSS: ami-024a59feb00e6f9d3 +# eu-west-2 v9.0.1 OSS: ami-0304ace50865a0248 +# eu-west-3 v9.0.1 OSS: ami-076a360cd048e6a71 +# ap-northeast-1 v9.0.1 OSS: ami-02192f3f6b37326be +# ap-northeast-2 v9.0.1 OSS: ami-0c305fdb520cd2d03 +# ap-northeast-3 v9.0.1 OSS: ami-027f0e95d0c6981b0 +# sa-east-1 v9.0.1 OSS: ami-0357206209e0e1ac8 +# ca-central-1 v9.0.1 OSS: ami-02cacc908a9ecaed0 +# ap-southeast-1 v9.0.1 OSS: ami-0c7d9089b8fb98d49 +# ap-southeast-2 v9.0.1 OSS: ami-0735924e3c026e00e +# eu-central-1 v9.0.1 OSS: ami-0a8a74865d820373c +# us-east-1 v9.0.1 OSS: ami-050e1228432bc017e +# us-east-2 v9.0.1 OSS: ami-06ad7d932122afd7b +# us-west-1 v9.0.1 OSS: ami-07b0c77d68a2e98cf +# us-west-2 v9.0.1 OSS: ami-0c2505a13d6e118e7 ``` ### Enterprise ``` -# eu-north-1 v8.3.1 Enterprise: ami-0025aaaa97b15e900 -# ap-south-1 v8.3.1 Enterprise: ami-0cf5de7e380590bd1 -# eu-west-1 v8.3.1 Enterprise: ami-015d16a97a12e9f3e -# eu-west-2 v8.3.1 Enterprise: ami-0a1399d687e79f230 -# eu-west-3 v8.3.1 Enterprise: ami-03bfb7db0eb4089cf -# ap-northeast-1 v8.3.1 Enterprise: ami-06238708fc042e0fb -# ap-northeast-2 v8.3.1 Enterprise: ami-0a8e2d7d4adede112 -# ap-northeast-3 v8.3.1 Enterprise: ami-022c50efe794001ac -# sa-east-1 v8.3.1 Enterprise: ami-04a865841550cf519 -# ca-central-1 v8.3.1 Enterprise: ami-0e964a19a5eef1633 -# ap-southeast-1 v8.3.1 Enterprise: ami-0b8ea0f231fa1f5a1 -# ap-southeast-2 v8.3.1 Enterprise: ami-03a3137350321d957 -# eu-central-1 v8.3.1 Enterprise: ami-04f8ff95544fd0431 -# us-east-1 v8.3.1 Enterprise: ami-0adc14e4ffaaad880 -# us-east-2 v8.3.1 Enterprise: ami-0718beffe1e13c0a1 -# us-west-1 v8.3.1 Enterprise: ami-044f8952623e0c010 -# us-west-2 v8.3.1 Enterprise: ami-04cea237af09e9143 +# eu-north-1 v9.0.1 Enterprise: ami-0acc68f7755c2eb98 +# ap-south-1 v9.0.1 Enterprise: ami-0cfab1c56ef76e56d +# eu-west-1 v9.0.1 Enterprise: ami-06d4f4a9c05b825e5 +# eu-west-2 v9.0.1 Enterprise: ami-0dad9a7e8019828b4 +# eu-west-3 v9.0.1 Enterprise: ami-06c32606e0b2c4181 +# ap-northeast-1 v9.0.1 Enterprise: ami-058897779d3b609da +# ap-northeast-2 v9.0.1 Enterprise: ami-008d7f1530778b283 +# ap-northeast-3 v9.0.1 Enterprise: ami-08b94d30fec7bca1f +# sa-east-1 v9.0.1 Enterprise: ami-014b64ac9c28e9a8d +# ca-central-1 v9.0.1 Enterprise: ami-0230b681466dd1cbd +# ap-southeast-1 v9.0.1 Enterprise: ami-0676627a7acfa18f8 +# ap-southeast-2 v9.0.1 Enterprise: ami-07f605ef28b14a8c5 +# eu-central-1 v9.0.1 Enterprise: ami-0220eec85a7bd0bca +# us-east-1 v9.0.1 Enterprise: ami-07ffa5186f4446e88 +# us-east-2 v9.0.1 Enterprise: ami-046f6cfab972281dd +# us-west-1 v9.0.1 Enterprise: ami-080f35de39e3c0a43 +# us-west-2 v9.0.1 Enterprise: ami-0d46e313ef984eb11 ``` ### Enterprise FIPS ``` -# eu-north-1 v8.3.1 Enterprise FIPS: ami-010a0dafec42cc34c -# ap-south-1 v8.3.1 Enterprise FIPS: ami-07b3523d3ac90e210 -# eu-west-1 v8.3.1 Enterprise FIPS: ami-0c14d8258e109934f -# eu-west-2 v8.3.1 Enterprise FIPS: ami-07ffb24ab2607f0a8 -# eu-west-3 v8.3.1 Enterprise FIPS: ami-00d5e3a93c5f64518 -# ap-northeast-1 v8.3.1 Enterprise FIPS: ami-08b3f1339c51e51d7 -# ap-northeast-2 v8.3.1 Enterprise FIPS: ami-022aab582835216f0 -# ap-northeast-3 v8.3.1 Enterprise FIPS: ami-025a9b33d02b84a33 -# sa-east-1 v8.3.1 Enterprise FIPS: ami-0c273078373811da1 -# ca-central-1 v8.3.1 Enterprise FIPS: ami-0305991f60af08149 -# ap-southeast-1 v8.3.1 Enterprise FIPS: ami-013268de76ec30b64 -# ap-southeast-2 v8.3.1 Enterprise FIPS: ami-07923d4c379e64eea -# eu-central-1 v8.3.1 Enterprise FIPS: ami-0309e5091ab89a293 -# us-east-1 v8.3.1 Enterprise FIPS: ami-0a2064984c331ff46 -# us-east-2 v8.3.1 Enterprise FIPS: ami-08ac544666b38afee -# us-west-1 v8.3.1 Enterprise FIPS: ami-00839fecaafaad134 -# us-west-2 v8.3.1 Enterprise FIPS: ami-03c8665e782623436 +# eu-north-1 v9.0.1 Enterprise FIPS: ami-0ae03293e8f7c270e +# ap-south-1 v9.0.1 Enterprise FIPS: ami-0e3554edf5b4d7f43 +# eu-west-1 v9.0.1 Enterprise FIPS: ami-0a38a70f423c2af6c +# eu-west-2 v9.0.1 Enterprise FIPS: ami-0f1d2195cd6ba552a +# eu-west-3 v9.0.1 Enterprise FIPS: ami-024c90f6d18f964b8 +# ap-northeast-1 v9.0.1 Enterprise FIPS: ami-0bf0dfc5903e8d744 +# ap-northeast-2 v9.0.1 Enterprise FIPS: ami-071ab1359b4c6d0ab +# ap-northeast-3 v9.0.1 Enterprise FIPS: ami-04cfbca2410bc966e +# sa-east-1 v9.0.1 Enterprise FIPS: ami-0543fee3b7e20a557 +# ca-central-1 v9.0.1 Enterprise FIPS: ami-0b4c828432b359cb6 +# ap-southeast-1 v9.0.1 Enterprise FIPS: ami-01b4d5889fe2ab815 +# ap-southeast-2 v9.0.1 Enterprise FIPS: ami-01db9009d4853caf5 +# eu-central-1 v9.0.1 Enterprise FIPS: ami-01bd770a772f1a3ac +# us-east-1 v9.0.1 Enterprise FIPS: ami-071fe9f049c218551 +# us-east-2 v9.0.1 Enterprise FIPS: ami-0dcbda64b7ef1ded6 +# us-west-1 v9.0.1 Enterprise FIPS: ami-05ca0f53382e0b713 +# us-west-2 v9.0.1 Enterprise FIPS: ami-00defc5ca4ef52823 ``` diff --git a/examples/aws/terraform/ha-autoscale-cluster/README.md b/examples/aws/terraform/ha-autoscale-cluster/README.md index 11b3de2296baf..f4e9704cdc2c1 100644 --- a/examples/aws/terraform/ha-autoscale-cluster/README.md +++ b/examples/aws/terraform/ha-autoscale-cluster/README.md @@ -45,7 +45,7 @@ export TF_VAR_cluster_name="teleport.example.com" # OSS: aws ec2 describe-images --owners 126027368216 --filters 'Name=name,Values=gravitational-teleport-ami-oss*' # Enterprise: aws ec2 describe-images --owners 126027368216 --filters 'Name=name,Values=gravitational-teleport-ami-ent*' # FIPS 140-2 images are also available for Enterprise customers, look for '-fips' on the end of the AMI's name -export TF_VAR_ami_name="gravitational-teleport-ami-ent-8.3.1" +export TF_VAR_ami_name="gravitational-teleport-ami-ent-9.0.1" # AWS SSH key name to provision in installed instances, should be available in the region export TF_VAR_key_name="example" diff --git a/examples/aws/terraform/starter-cluster/README.md b/examples/aws/terraform/starter-cluster/README.md index 8607428e256d9..4d4fb08fcb162 100644 --- a/examples/aws/terraform/starter-cluster/README.md +++ b/examples/aws/terraform/starter-cluster/README.md @@ -86,7 +86,7 @@ TF_VAR_license_path ?="/path/to/license" # OSS: aws ec2 describe-images --owners 126027368216 --filters 'Name=name,Values=gravitational-teleport-ami-oss*' # Enterprise: aws ec2 describe-images --owners 126027368216 --filters 'Name=name,Values=gravitational-teleport-ami-ent*' # FIPS 140-2 images are also available for Enterprise customers, look for '-fips' on the end of the AMI's name -TF_VAR_ami_name ?="gravitational-teleport-ami-ent-8.3.1" +TF_VAR_ami_name ?="gravitational-teleport-ami-ent-9.0.1" # Route 53 hosted zone to use, must be a root zone registered in AWS, e.g. example.com TF_VAR_route53_zone ?="example.com" diff --git a/examples/aws/terraform/starter-cluster/vars.tf b/examples/aws/terraform/starter-cluster/vars.tf index e0f85794a0c73..318ff7f4d3c47 100644 --- a/examples/aws/terraform/starter-cluster/vars.tf +++ b/examples/aws/terraform/starter-cluster/vars.tf @@ -42,7 +42,7 @@ variable "email" { } -// SSH key name to provision instances withx +// SSH key name to provision instances with variable "key_name" { type = string } diff --git a/examples/chart/CONTRIBUTING.md b/examples/chart/CONTRIBUTING.md new file mode 100644 index 0000000000000..af2a5d9e3abda --- /dev/null +++ b/examples/chart/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing to Teleport Helm charts + +Firstly, thanks for considering a contribution to Teleport's Helm charts. + +## A couple of brief warnings + +Please note that we won't accept contributions that are particularly esoteric, difficult to use or poorly implemented. +Our goal is to: + +- keep the charts easy to use +- keep all functionality relevant to a broad audience +- always use sane defaults which are right for most deployments +- require as few values changes as possible for everyday usage + +If your functionality is only really useful to you, it's best to keep it on your own fork and deploy from there. + +Sometimes Teleport staff may take over your PR and make changes, implement it in a slightly different way, or just merge it +as part of a bigger merge of multiple PRs at once. We will make sure that you still get credit in the final commit if this +happens. + +## Guidelines + +Here is a list of things that you should do to make sure to do in order to get a smooth PR review process with minimal +changes required: + +1) Add a linter file which includes examples for any new values you add under the `.lint/` directory for the +appropriate chart. The linter will check this during CI and make sure the values are correctly formatted, along +with your chart changes. The file should contain all necessary values to deploy a reference install. + +2) Add unit tests for your functionality under the `tests/` directory for the appropriate chart, particularly if you're +adding new values. Make sure that all functionality is tested, so we can be sure that it works as intended for every use +case. A good tip is to use your newly added linter file to set values appropriate for your test. + +3) Add any new values at the correct location in the `values.schema.json` file for the appropriate chart. This +will ensure that Helm is able to validate values at install-time and can prevent users from making easy mistakes. + +4) Document any new values or changes to existing behaviour in the [chart reference](../../docs/pages/kubernetes-access/helm/reference.mdx). + +5) Run `make lint-helm test-helm` from the root of the repo before raising your PR. +You will need `yamllint`, `helm` and [helm3-unittest](https://github.com/vbehar/helm3-unittest) installed locally. + +`make -C build.assets lint-helm test-helm` will run these via Docker if you'd prefer not to install locally. + +6) If you get a snapshot error during your testing, you should verify that your changes intended to alter the output, +then run `make test-helm-update-snapshots` to update the snapshots and commit these changes along with your PR. + +Again, `make -C build.assets test-helm-update-snapshots` will run this via Docker. + +7) Document the changes you've made in the PR summary. + +Thanks! diff --git a/examples/chart/teleport-auto-trustedcluster/README.md b/examples/chart/teleport-auto-trustedcluster/README.md index 12dd081fc08dd..0ffaa7a090dd1 100644 --- a/examples/chart/teleport-auto-trustedcluster/README.md +++ b/examples/chart/teleport-auto-trustedcluster/README.md @@ -1,3 +1,8 @@ +# WARNING + +This chart is **deprecated** and no longer actively maintained or supported by Teleport. +We recommend the use of the [teleport-cluster](../teleport-cluster/README.md) chart instead. + # Teleport [Gravitational Teleport](https://github.com/gravitational/teleport) is a modern SSH/Kubernetes API proxy server for diff --git a/examples/chart/teleport-cluster/.lint/service.yml b/examples/chart/teleport-cluster/.lint/service.yaml similarity index 100% rename from examples/chart/teleport-cluster/.lint/service.yml rename to examples/chart/teleport-cluster/.lint/service.yaml diff --git a/examples/chart/teleport-cluster/.lint/standalone-customsize.yaml b/examples/chart/teleport-cluster/.lint/standalone-customsize.yaml index 345999323a716..e8acbe8cf9848 100644 --- a/examples/chart/teleport-cluster/.lint/standalone-customsize.yaml +++ b/examples/chart/teleport-cluster/.lint/standalone-customsize.yaml @@ -1,7 +1,7 @@ clusterName: test-standalone-cluster chartMode: standalone standalone: - existingClaimName: teleport-storage + volumeSize: 50Gi acme: true acmeEmail: test@email.com labels: diff --git a/examples/chart/teleport-cluster/.lint/standalone-existingpvc.yaml b/examples/chart/teleport-cluster/.lint/standalone-existingpvc.yaml index e8acbe8cf9848..345999323a716 100644 --- a/examples/chart/teleport-cluster/.lint/standalone-existingpvc.yaml +++ b/examples/chart/teleport-cluster/.lint/standalone-existingpvc.yaml @@ -1,7 +1,7 @@ clusterName: test-standalone-cluster chartMode: standalone standalone: - volumeSize: 50Gi + existingClaimName: teleport-storage acme: true acmeEmail: test@email.com labels: diff --git a/examples/chart/teleport-cluster/.lint/volumes.yaml b/examples/chart/teleport-cluster/.lint/volumes.yaml index 83bb8b8dbe95c..a1ce300828a45 100644 --- a/examples/chart/teleport-cluster/.lint/volumes.yaml +++ b/examples/chart/teleport-cluster/.lint/volumes.yaml @@ -1,7 +1,7 @@ clusterName: helm-lint extraVolumeMounts: - name: "my-mount" - path: "/path/to/mount" + mountPath: "/path/to/mount" extraVolumes: - name: "my-mount" secret: diff --git a/examples/chart/teleport-cluster/.lint/webauthn.yaml b/examples/chart/teleport-cluster/.lint/webauthn.yaml new file mode 100644 index 0000000000000..0415cc7c7f512 --- /dev/null +++ b/examples/chart/teleport-cluster/.lint/webauthn.yaml @@ -0,0 +1,8 @@ +clusterName: helm-lint +authenticationSecondFactor: + secondFactor: "on" + webauthn: + attestationAllowedCas: + - "/etc/ssl/certs/ca-certificates.crt" + attestationDeniedCas: + - "/etc/ssl/certs/ca-certificates.crt" diff --git a/examples/chart/teleport-cluster/README.md b/examples/chart/teleport-cluster/README.md index 45504877ba6e2..51ffdf73bcbab 100644 --- a/examples/chart/teleport-cluster/README.md +++ b/examples/chart/teleport-cluster/README.md @@ -47,3 +47,7 @@ secret `license` in the chart namespace. See https://goteleport.com/docs/kubernetes-access/helm/guides/ for guides on setting up HA Teleport clusters in EKS or GKE, plus a more comprehensive chart reference. + +## Contributing to the chart + +Please read [CONTRIBUTING.md](../CONTRIBUTING.md) before raising a pull request to this chart. diff --git a/examples/chart/teleport-cluster/templates/config.yaml b/examples/chart/teleport-cluster/templates/config.yaml index d86d63e4901e4..54369e4161af5 100644 --- a/examples/chart/teleport-cluster/templates/config.yaml +++ b/examples/chart/teleport-cluster/templates/config.yaml @@ -4,6 +4,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} {{- if .Values.annotations.config }} annotations: {{- toYaml .Values.annotations.config | nindent 4 }} @@ -55,12 +56,33 @@ data: {{- end }} authentication: type: {{ required "authenticationType is required in chart values" .Values.authenticationType }} + {{- if .Values.authenticationSecondFactor }} + {{- if .Values.authenticationSecondFactor.secondFactor }} + second_factor: {{ .Values.authenticationSecondFactor.secondFactor }} + {{- end }} + {{- if not (or (eq .Values.authenticationSecondFactor.secondFactor "off") (eq .Values.authenticationSecondFactor.secondFactor "otp")) }} + webauthn: + rp_id: {{ required "clusterName is required in chart values" .Values.clusterName }} + {{- if .Values.authenticationSecondFactor.webauthn }} + {{- if .Values.authenticationSecondFactor.webauthn.attestationAllowedCas }} + attestation_allowed_cas: + {{- toYaml .Values.authenticationSecondFactor.webauthn.attestationAllowedCas | nindent 12 }} + {{- end }} + {{- if .Values.authenticationSecondFactor.webauthn.attestationDeniedCas }} + attestation_denied_cas: + {{- toYaml .Values.authenticationSecondFactor.webauthn.attestationDeniedCas | nindent 12 }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} {{- if eq .Values.proxyListenerMode "multiplex" }} proxy_listener_mode: multiplex {{- end }} kubernetes_service: enabled: true - {{- if not .Values.proxyListenerMode }} + {{- if eq .Values.proxyListenerMode "multiplex" }} + listen_addr: 0.0.0.0:443 + {{- else if not .Values.proxyListenerMode }} listen_addr: 0.0.0.0:3027 {{- end }} {{- if .Values.kubeClusterName }} diff --git a/examples/chart/teleport-cluster/tests/README.md b/examples/chart/teleport-cluster/tests/README.md new file mode 100644 index 0000000000000..3cd0f8783d4e6 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/README.md @@ -0,0 +1,18 @@ +## Unit tests for Helm charts + +Helm chart unit tests run here using the [helm3-unittest](https://github.com/vbehar/helm3-unittest) Helm plugin. + +If you get a snapshot error during your testing, you should verify that your changes intended to alter the output, then run +this command from the root of your Teleport checkout to update the snapshots: + +```bash +make -C build.assets test-helm-update-snapshots +``` + +After this, re-run the tests to make sure everything is fine: + +```bash +make -C build.assets test-helm +``` + +Commit the updated snapshots along with your changes. diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/certificate_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/certificate_test.yaml.snap new file mode 100644 index 0000000000000..319cbd80eb8c0 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/__snapshot__/certificate_test.yaml.snap @@ -0,0 +1,16 @@ +should request a certificate for cluster name when cert-manager is enabled (cert-manager.yaml): + 1: | + - test-cluster + - '*.test-cluster' + 2: | + group: custom.cert-manager.io + kind: CustomClusterIssuer + name: custom +should request a certificate for cluster name when cert-manager is enabled (cert-secret.yaml): + 1: | + - test-cluster + - '*.test-cluster' + 2: | + group: cert-manager.io + kind: Issuer + name: letsencrypt diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/clusterrole_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/clusterrole_test.yaml.snap new file mode 100644 index 0000000000000..73d2b30742ace --- /dev/null +++ b/examples/chart/teleport-cluster/tests/__snapshot__/clusterrole_test.yaml.snap @@ -0,0 +1,27 @@ +creates a ClusterRole: + 1: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: RELEASE-NAME + rules: + - apiGroups: + - "" + resources: + - users + - groups + - serviceaccounts + verbs: + - impersonate + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - apiGroups: + - authorization.k8s.io + resources: + - selfsubjectaccessreviews + verbs: + - create diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/clusterrolebinding_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/clusterrolebinding_test.yaml.snap new file mode 100644 index 0000000000000..408ec5f4556e2 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/__snapshot__/clusterrolebinding_test.yaml.snap @@ -0,0 +1,14 @@ +creates a ClusterRoleBinding: + 1: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: RELEASE-NAME + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: RELEASE-NAME + subjects: + - kind: ServiceAccount + name: RELEASE-NAME + namespace: NAMESPACE diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/config_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/config_test.yaml.snap new file mode 100644 index 0000000000000..30d3cf75b5626 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/__snapshot__/config_test.yaml.snap @@ -0,0 +1,1098 @@ +matches snapshot and tests for annotations.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: helm-lint + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: helm-lint + proxy_service: + public_addr: 'helm-lint:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + annotations: + kubernetes.io/config: test-annotation + kubernetes.io/config-different: 2 + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for acme-off.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: test-cluster-name + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-cluster-name + proxy_service: + public_addr: 'test-cluster-name:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for acme-on.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: test-acme-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-acme-cluster + proxy_service: + public_addr: 'test-acme-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + acme: + enabled: true + email: test@email.com + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for acme-uri-staging.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: test-acme-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-acme-cluster + proxy_service: + public_addr: 'test-acme-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + acme: + enabled: true + email: test@email.com + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for affinity.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: firestore + project_id: gcpproj-123456 + collection_name: test-teleport-firestore-storage-collection + credentials_path: /etc/teleport-secrets/gcp-credentials.json + audit_events_uri: ['firestore://test-teleport-firestore-auditlog-collection?projectID=gcpproj-123456&credentialsPath=/etc/teleport-secrets/gcp-credentials.json'] + audit_sessions_uri: "gs://test-gcp-session-storage-bucket?projectID=gcpproj-123456&credentialsPath=/etc/teleport-secrets/gcp-credentials.json" + auth_service: + enabled: true + cluster_name: test-gcp-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-gcp-cluster + proxy_service: + public_addr: 'test-gcp-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for aws-ha-acme.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: dynamodb + region: us-west-2 + table_name: test-dynamodb-backend-table + audit_events_uri: ['dynamodb://test-dynamodb-auditlog-table'] + audit_sessions_uri: s3://test-s3-session-storage-bucket + continuous_backups: false + auth_service: + enabled: true + cluster_name: test-aws-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-aws-cluster + labels: + env: aws + proxy_service: + public_addr: 'test-aws-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + https_keypairs: + - key_file: /etc/teleport-tls/tls.key + cert_file: /etc/teleport-tls/tls.crt + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for aws-ha-antiaffinity.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: dynamodb + region: us-west-2 + table_name: test-dynamodb-backend-table + audit_events_uri: ['dynamodb://test-dynamodb-auditlog-table'] + audit_sessions_uri: s3://test-s3-session-storage-bucket + continuous_backups: false + auth_service: + enabled: true + cluster_name: test-aws-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-aws-cluster + labels: + env: aws + proxy_service: + public_addr: 'test-aws-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for aws-ha-log.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: DEBUG + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: dynamodb + region: us-west-2 + table_name: test-dynamodb-backend-table + audit_events_uri: ['dynamodb://test-dynamodb-auditlog-table', 'stdout://'] + audit_sessions_uri: s3://test-s3-session-storage-bucket + continuous_backups: false + auth_service: + enabled: true + cluster_name: test-aws-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-aws-cluster + labels: + env: aws + proxy_service: + public_addr: 'test-aws-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + https_keypairs: + - key_file: /etc/teleport-tls/tls.key + cert_file: /etc/teleport-tls/tls.crt + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for aws-ha.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: dynamodb + region: us-west-2 + table_name: test-dynamodb-backend-table + audit_events_uri: ['dynamodb://test-dynamodb-auditlog-table'] + audit_sessions_uri: s3://test-s3-session-storage-bucket + continuous_backups: false + auth_service: + enabled: true + cluster_name: test-aws-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-aws-cluster + labels: + env: aws + proxy_service: + public_addr: 'test-aws-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for aws.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: dynamodb + region: us-west-2 + table_name: test-dynamodb-backend-table + audit_events_uri: ['dynamodb://test-dynamodb-auditlog-table'] + audit_sessions_uri: s3://test-s3-session-storage-bucket + continuous_backups: false + auth_service: + enabled: true + cluster_name: test-aws-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-aws-cluster + labels: + env: aws + proxy_service: + public_addr: 'test-aws-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + acme: + enabled: true + email: test@email.com + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for gcp-ha-acme.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: firestore + project_id: gcpproj-123456 + collection_name: test-teleport-firestore-storage-collection + credentials_path: /etc/teleport-secrets/gcp-credentials.json + audit_events_uri: ['firestore://test-teleport-firestore-auditlog-collection?projectID=gcpproj-123456&credentialsPath=/etc/teleport-secrets/gcp-credentials.json'] + audit_sessions_uri: "gs://test-gcp-session-storage-bucket?projectID=gcpproj-123456&credentialsPath=/etc/teleport-secrets/gcp-credentials.json" + auth_service: + enabled: true + cluster_name: test-gcp-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-gcp-cluster + labels: + env: gcp + proxy_service: + public_addr: 'test-gcp-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + https_keypairs: + - key_file: /etc/teleport-tls/tls.key + cert_file: /etc/teleport-tls/tls.crt + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for gcp-ha-antiaffinity.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: firestore + project_id: gcpproj-123456 + collection_name: test-teleport-firestore-storage-collection + credentials_path: /etc/teleport-secrets/gcp-credentials.json + audit_events_uri: ['firestore://test-teleport-firestore-auditlog-collection?projectID=gcpproj-123456&credentialsPath=/etc/teleport-secrets/gcp-credentials.json'] + audit_sessions_uri: "gs://test-gcp-session-storage-bucket?projectID=gcpproj-123456&credentialsPath=/etc/teleport-secrets/gcp-credentials.json" + auth_service: + enabled: true + cluster_name: test-gcp-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-gcp-cluster + labels: + env: gcp + proxy_service: + public_addr: 'test-gcp-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for gcp-ha-log.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: DEBUG + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: firestore + project_id: gcpproj-123456 + collection_name: test-teleport-firestore-storage-collection + credentials_path: /etc/teleport-secrets/gcp-credentials.json + audit_events_uri: ['firestore://test-teleport-firestore-auditlog-collection?projectID=gcpproj-123456&credentialsPath=/etc/teleport-secrets/gcp-credentials.json', 'stdout://'] + audit_sessions_uri: "gs://test-gcp-session-storage-bucket?projectID=gcpproj-123456&credentialsPath=/etc/teleport-secrets/gcp-credentials.json" + auth_service: + enabled: true + cluster_name: test-gcp-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-gcp-cluster + labels: + env: gcp + proxy_service: + public_addr: 'test-gcp-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + https_keypairs: + - key_file: /etc/teleport-tls/tls.key + cert_file: /etc/teleport-tls/tls.crt + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for gcp.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: firestore + project_id: gcpproj-123456 + collection_name: test-teleport-firestore-storage-collection + credentials_path: /etc/teleport-secrets/gcp-credentials.json + audit_events_uri: ['firestore://test-teleport-firestore-auditlog-collection?projectID=gcpproj-123456&credentialsPath=/etc/teleport-secrets/gcp-credentials.json'] + audit_sessions_uri: "gs://test-gcp-session-storage-bucket?projectID=gcpproj-123456&credentialsPath=/etc/teleport-secrets/gcp-credentials.json" + auth_service: + enabled: true + cluster_name: test-gcp-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-gcp-cluster + labels: + env: gcp + proxy_service: + public_addr: 'test-gcp-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + acme: + enabled: true + email: test@email.com + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for initcontainers.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: helm-lint + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: helm-lint + proxy_service: + public_addr: 'helm-lint:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for kube-cluster-name.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: test-aws-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-kube-cluster + proxy_service: + public_addr: 'test-aws-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for log-basic.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: json + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: test-log-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-log-cluster + proxy_service: + public_addr: 'test-log-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for log-extra.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: DEBUG + output: /var/lib/teleport/test.log + format: + output: json + extra_fields: ["level","timestamp","component","caller"] + auth_service: + enabled: true + cluster_name: test-log-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-log-cluster + proxy_service: + public_addr: 'test-log-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for log-legacy.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: DEBUG + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: test-log-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-log-cluster + proxy_service: + public_addr: 'test-log-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for priority-class-name.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: helm-lint + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: helm-lint + proxy_service: + public_addr: 'helm-lint:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for proxy-listener-mode.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + version: v2 + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: test-proxy-listener-mode + authentication: + type: local + second_factor: otp + proxy_listener_mode: multiplex + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:443 + kube_cluster_name: test-proxy-listener-mode + proxy_service: + public_addr: 'test-proxy-listener-mode:443' + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for resources.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: helm-lint + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: helm-lint + proxy_service: + public_addr: 'helm-lint:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for service.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: helm-lint + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: helm-lint + proxy_service: + public_addr: 'helm-lint:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for standalone-customsize.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: test-standalone-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-standalone-cluster + labels: + env: standalone + proxy_service: + public_addr: 'test-standalone-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + acme: + enabled: true + email: test@email.com + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for standalone-existingpvc.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: test-standalone-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-standalone-cluster + labels: + env: standalone + proxy_service: + public_addr: 'test-standalone-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + acme: + enabled: true + email: test@email.com + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for tolerations.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + storage: + type: dynamodb + region: us-west-2 + table_name: test-dynamodb-backend-table + audit_events_uri: ['dynamodb://test-dynamodb-auditlog-table'] + audit_sessions_uri: s3://test-s3-session-storage-bucket + continuous_backups: false + auth_service: + enabled: true + cluster_name: test-aws-cluster + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-aws-cluster + proxy_service: + public_addr: 'test-aws-cluster:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for version-override.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: test-cluster-name + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: test-cluster-name + labels: + env: test + version: 5.2.1 + proxy_service: + public_addr: 'test-cluster-name:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for volumes.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: helm-lint + authentication: + type: local + second_factor: otp + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: helm-lint + proxy_service: + public_addr: 'helm-lint:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for webauthn.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: |- + teleport: + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + auth_service: + enabled: true + cluster_name: helm-lint + authentication: + type: local + second_factor: on + webauthn: + rp_id: helm-lint + attestation_allowed_cas: + - /etc/ssl/certs/ca-certificates.crt + attestation_denied_cas: + - /etc/ssl/certs/ca-certificates.crt + kubernetes_service: + enabled: true + listen_addr: 0.0.0.0:3027 + kube_cluster_name: helm-lint + proxy_service: + public_addr: 'helm-lint:443' + kube_listen_addr: 0.0.0.0:3026 + mysql_listen_addr: 0.0.0.0:3036 + enabled: true + ssh_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/deployment_test.yaml.snap new file mode 100644 index 0000000000000..98991aec2640e --- /dev/null +++ b/examples/chart/teleport-cluster/tests/__snapshot__/deployment_test.yaml.snap @@ -0,0 +1,1585 @@ +sets Deployment annotations when specified: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +sets Pod annotations when specified: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should add PersistentVolumeClaim as volume when in standalone mode: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should add emptyDir for data in AWS mode: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should add emptyDir for data in GCP mode: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport-secrets + name: gcp-credentials + readOnly: true + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should add emptyDir for data in custom mode: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should add insecureSkipProxyTLSVerify to args when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + - --insecure + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should add named PersistentVolumeClaim as volume when in standalone mode and standalone.existingClaimName is set: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: teleport-storage +should do enterprise things when when enterprise is set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport-ent:8.3.4 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /var/lib/license + name: license + readOnly: true + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: license + secret: + secretName: license + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should expose diag port: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should have multiple replicas when replicaCount is set: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should mount ConfigMap for config in AWS mode: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should mount ConfigMap for config in GCP mode: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport-secrets + name: gcp-credentials + readOnly: true + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should mount ConfigMap for config in custom mode: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should mount ConfigMap for config in standalone mode: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should mount GCP credentials for initContainer in GCP mode: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport-secrets + name: gcp-credentials + readOnly: true + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + initContainers: + - args: + - echo test + image: alpine + name: teleport-init + volumeMounts: + - mountPath: /etc/teleport-secrets + name: gcp-credentials + readOnly: true + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should mount GCP credentials in GCP mode: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport-secrets + name: gcp-credentials + readOnly: true + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should mount TLS certs for initContainer when cert-manager is enabled: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport-secrets + name: gcp-credentials + readOnly: true + - mountPath: /etc/teleport-tls + name: teleport-tls + readOnly: true + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + initContainers: + - args: + - echo test + image: alpine + name: teleport-init + volumeMounts: + - mountPath: /etc/teleport-secrets + name: gcp-credentials + readOnly: true + - mountPath: /etc/teleport-tls + name: teleport-tls + readOnly: true + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - name: teleport-tls + secret: + secretName: teleport-tls + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should mount TLS certs when cert-manager is enabled: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport-secrets + name: gcp-credentials + readOnly: true + - mountPath: /etc/teleport-tls + name: teleport-tls + readOnly: true + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - name: teleport-tls + secret: + secretName: teleport-tls + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should mount extraVolumes and extraVolumeMounts: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + - mountPath: /path/to/mount + name: my-mount + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME + - name: my-mount + secret: + secretName: mySecret +should not do enterprise things when when enterprise is not set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:8.3.4 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should not have more than one replica in standalone mode: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should provision initContainer correctly when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + env: + - name: SSL_CERT_FILE + value: /etc/tls/some-cert-file.pem + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 1 + memory: 2Gi + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + initContainers: + - args: + - echo test + image: alpine + name: teleport-init + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 1 + memory: 2Gi + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should set affinity when set in values: + 1: | + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: gravitational.io/dedicated + operator: In + values: + - teleport + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should set environment when extraEnv set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + env: + - name: SSL_CERT_FILE + value: /etc/tls/some-cert-file.pem + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should set imagePullPolicy when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: Always + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should set postStart command if set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + lifecycle: + postStart: + exec: + command: + - /bin/echo + - test + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should set priorityClassName when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + priorityClassName: system-cluster-critical + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should set required affinity when highAvailability.requireAntiAffinity is set: + 1: | + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data +should set resources when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 1 + memory: 2Gi + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - name: data + persistentVolumeClaim: + claimName: RELEASE-NAME +should set tolerations when set in values: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + tolerations: + - effect: NoExecute + key: dedicated + operator: Equal + value: teleport + - effect: NoSchedule + key: dedicated + operator: Equal + value: teleport + volumes: + - name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - configMap: + name: RELEASE-NAME + name: config + - emptyDir: {} + name: data diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/pdb_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/pdb_test.yaml.snap new file mode 100644 index 0000000000000..a8d3bf87b8092 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/__snapshot__/pdb_test.yaml.snap @@ -0,0 +1,14 @@ +should create a PDB when enabled in values (pdb.yaml): + 1: | + apiVersion: policy/v1beta1 + kind: PodDisruptionBudget + metadata: + labels: + app: RELEASE-NAME + name: RELEASE-NAME + namespace: NAMESPACE + spec: + minAvailable: 2 + selector: + matchLabels: + app: RELEASE-NAME diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/psp_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/psp_test.yaml.snap new file mode 100644 index 0000000000000..5536920844cf3 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/__snapshot__/psp_test.yaml.snap @@ -0,0 +1,34 @@ +creates a PodSecurityPolicy when enabled in values: + 1: | + apiVersion: policy/v1beta1 + kind: PodSecurityPolicy + metadata: + annotations: + seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default,runtime/default + seccomp.security.alpha.kubernetes.io/defaultProfileName: runtime/default + name: RELEASE-NAME + spec: + allowPrivilegeEscalation: false + fsGroup: + ranges: + - max: 65535 + min: 1 + rule: MustRunAs + hostIPC: false + hostNetwork: false + hostPID: false + privileged: false + readOnlyRootFilesystem: true + requiredDropCapabilities: + - ALL + runAsUser: + rule: MustRunAsNonRoot + seLinux: + rule: RunAsAny + supplementalGroups: + ranges: + - max: 65535 + min: 1 + rule: MustRunAs + volumes: + - '*' diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/pvc_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/pvc_test.yaml.snap new file mode 100644 index 0000000000000..8a174f4e892af --- /dev/null +++ b/examples/chart/teleport-cluster/tests/__snapshot__/pvc_test.yaml.snap @@ -0,0 +1,30 @@ +creates a PersistentVolumeClaim when chartMode=standalone: + 1: | + apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + labels: + app: RELEASE-NAME + name: RELEASE-NAME + namespace: NAMESPACE + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +creates a PersistentVolumeClaim with values from standalone-customsize.yaml: + 1: | + apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + labels: + app: RELEASE-NAME + name: RELEASE-NAME + namespace: NAMESPACE + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Gi diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/service_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/service_test.yaml.snap new file mode 100644 index 0000000000000..4386f1f3555d3 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/__snapshot__/service_test.yaml.snap @@ -0,0 +1,143 @@ +sets AWS annotations when chartMode=aws: + 1: | + apiVersion: v1 + kind: Service + metadata: + annotations: + service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp + service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true" + service.beta.kubernetes.io/aws-load-balancer-type: nlb + labels: + app: RELEASE-NAME + name: RELEASE-NAME + namespace: NAMESPACE + spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 3080 + - name: sshproxy + port: 3023 + protocol: TCP + targetPort: 3023 + - name: k8s + port: 3026 + protocol: TCP + targetPort: 3026 + - name: sshtun + port: 3024 + protocol: TCP + targetPort: 3024 + - name: mysql + port: 3036 + protocol: TCP + targetPort: 3036 + selector: + app: RELEASE-NAME + type: LoadBalancer +sets service annotations when specified: + 1: | + apiVersion: v1 + kind: Service + metadata: + annotations: + kubernetes.io/service: test-annotation + kubernetes.io/service-different: 5 + labels: + app: RELEASE-NAME + name: RELEASE-NAME + namespace: NAMESPACE + spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 3080 + - name: sshproxy + port: 3023 + protocol: TCP + targetPort: 3023 + - name: k8s + port: 3026 + protocol: TCP + targetPort: 3026 + - name: sshtun + port: 3024 + protocol: TCP + targetPort: 3024 + - name: mysql + port: 3036 + protocol: TCP + targetPort: 3036 + selector: + app: RELEASE-NAME + type: LoadBalancer +uses a ClusterIP when service.type=ClusterIP: + 1: | + apiVersion: v1 + kind: Service + metadata: + labels: + app: RELEASE-NAME + name: RELEASE-NAME + namespace: NAMESPACE + spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 3080 + - name: sshproxy + port: 3023 + protocol: TCP + targetPort: 3023 + - name: k8s + port: 3026 + protocol: TCP + targetPort: 3026 + - name: sshtun + port: 3024 + protocol: TCP + targetPort: 3024 + - name: mysql + port: 3036 + protocol: TCP + targetPort: 3036 + selector: + app: RELEASE-NAME + type: ClusterIP +uses a LoadBalancer by default: + 1: | + apiVersion: v1 + kind: Service + metadata: + labels: + app: RELEASE-NAME + name: RELEASE-NAME + namespace: NAMESPACE + spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 3080 + - name: sshproxy + port: 3023 + protocol: TCP + targetPort: 3023 + - name: k8s + port: 3026 + protocol: TCP + targetPort: 3026 + - name: sshtun + port: 3024 + protocol: TCP + targetPort: 3024 + - name: mysql + port: 3036 + protocol: TCP + targetPort: 3036 + selector: + app: RELEASE-NAME + type: LoadBalancer diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/serviceaccount_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/serviceaccount_test.yaml.snap new file mode 100644 index 0000000000000..6d6ccbeadb27e --- /dev/null +++ b/examples/chart/teleport-cluster/tests/__snapshot__/serviceaccount_test.yaml.snap @@ -0,0 +1,10 @@ +sets ServiceAccount annotations when specified: + 1: | + apiVersion: v1 + kind: ServiceAccount + metadata: + annotations: + kubernetes.io/serviceaccount: test-annotation + kubernetes.io/serviceaccount-different: 6 + name: RELEASE-NAME + namespace: NAMESPACE diff --git a/examples/chart/teleport-cluster/tests/certificate_test.yaml b/examples/chart/teleport-cluster/tests/certificate_test.yaml new file mode 100644 index 0000000000000..b2cb8cc737301 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/certificate_test.yaml @@ -0,0 +1,29 @@ +suite: Certificate +templates: + - certificate.yaml +tests: + - it: should request a certificate for cluster name when cert-manager is enabled (cert-manager.yaml) + values: + - ../.lint/cert-manager.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Certificate + - matchSnapshot: + path: spec.dnsNames + - matchSnapshot: + path: spec.issuerRef + + - it: should request a certificate for cluster name when cert-manager is enabled (cert-secret.yaml) + values: + - ../.lint/cert-secret.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Certificate + - matchSnapshot: + path: spec.dnsNames + - matchSnapshot: + path: spec.issuerRef diff --git a/examples/chart/teleport-cluster/tests/clusterrole_test.yaml b/examples/chart/teleport-cluster/tests/clusterrole_test.yaml new file mode 100644 index 0000000000000..337947d13ad2b --- /dev/null +++ b/examples/chart/teleport-cluster/tests/clusterrole_test.yaml @@ -0,0 +1,11 @@ +suite: ClusterRole +templates: + - clusterrole.yaml +tests: + - it: creates a ClusterRole + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ClusterRole + - matchSnapshot: {} diff --git a/examples/chart/teleport-cluster/tests/clusterrolebinding_test.yaml b/examples/chart/teleport-cluster/tests/clusterrolebinding_test.yaml new file mode 100644 index 0000000000000..b8f30d8cf4fc2 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/clusterrolebinding_test.yaml @@ -0,0 +1,11 @@ +suite: ClusterRoleBinding +templates: + - clusterrolebinding.yaml +tests: + - it: creates a ClusterRoleBinding + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ClusterRoleBinding + - matchSnapshot: {} diff --git a/examples/chart/teleport-cluster/tests/config_test.yaml b/examples/chart/teleport-cluster/tests/config_test.yaml new file mode 100644 index 0000000000000..1a56648cc47ea --- /dev/null +++ b/examples/chart/teleport-cluster/tests/config_test.yaml @@ -0,0 +1,330 @@ +suite: ConfigMap +templates: + - config.yaml +tests: + - it: matches snapshot for acme-off.yaml + values: + - ../.lint/acme-off.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for acme-off.yaml + values: + - ../.lint/acme-off.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for acme-on.yaml + values: + - ../.lint/acme-on.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for acme-uri-staging.yaml + values: + - ../.lint/acme-on.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for affinity.yaml + values: + - ../.lint/affinity.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot and tests for annotations.yaml + values: + - ../.lint/annotations.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - equal: + path: metadata.annotations.kubernetes\.io/config + value: test-annotation + - equal: + path: metadata.annotations.kubernetes\.io/config-different + value: 2 + - matchSnapshot: {} + + - it: matches snapshot for aws-ha-acme.yaml + values: + - ../.lint/aws-ha-acme.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for aws-ha-antiaffinity.yaml + values: + - ../.lint/aws-ha-antiaffinity.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for aws-ha-log.yaml + values: + - ../.lint/aws-ha-log.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for aws-ha.yaml + values: + - ../.lint/aws-ha.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for aws.yaml + values: + - ../.lint/aws.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: does not generate a config for cert-manager.yaml + values: + - ../.lint/cert-manager.yaml + asserts: + - hasDocuments: + count: 0 + + - it: does not generate a config for cert-secret.yaml + values: + - ../.lint/cert-secret.yaml + asserts: + - hasDocuments: + count: 0 + + - it: matches snapshot for gcp-ha-acme.yaml + values: + - ../.lint/gcp-ha-acme.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for gcp-ha-antiaffinity.yaml + values: + - ../.lint/gcp-ha-antiaffinity.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for gcp-ha-log.yaml + values: + - ../.lint/gcp-ha-log.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for gcp.yaml + values: + - ../.lint/gcp.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for initcontainers.yaml + values: + - ../.lint/initcontainers.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for kube-cluster-name.yaml + values: + - ../.lint/kube-cluster-name.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for log-basic.yaml + values: + - ../.lint/log-basic.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for log-extra.yaml + values: + - ../.lint/log-extra.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for log-legacy.yaml + values: + - ../.lint/log-legacy.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: does not generate a config for pdb.yaml + values: + - ../.lint/pdb.yaml + asserts: + - hasDocuments: + count: 0 + + - it: matches snapshot for priority-class-name.yaml + values: + - ../.lint/priority-class-name.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for proxy-listener-mode.yaml + values: + - ../.lint/proxy-listener-mode.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for resources.yaml + values: + - ../.lint/resources.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for service.yaml + values: + - ../.lint/service.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for standalone-customsize.yaml + values: + - ../.lint/standalone-customsize.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for standalone-existingpvc.yaml + values: + - ../.lint/standalone-existingpvc.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for tolerations.yaml + values: + - ../.lint/tolerations.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for version-override.yaml + values: + - ../.lint/version-override.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for volumes.yaml + values: + - ../.lint/volumes.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for webauthn.yaml + values: + - ../.lint/webauthn.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} diff --git a/examples/chart/teleport-cluster/tests/deployment_test.yaml b/examples/chart/teleport-cluster/tests/deployment_test.yaml new file mode 100644 index 0000000000000..930925c9cc5d2 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/deployment_test.yaml @@ -0,0 +1,533 @@ +suite: Deployment +templates: + - deployment.yaml + - config.yaml +tests: + - it: sets Deployment annotations when specified + values: + - ../.lint/annotations.yaml + asserts: + - equal: + path: metadata.annotations.kubernetes\.io/deployment + value: test-annotation + - equal: + path: metadata.annotations.kubernetes\.io/deployment-different + value: 3 + - matchSnapshot: + path: spec.template.spec + + - it: sets Pod annotations when specified + values: + - ../.lint/annotations.yaml + asserts: + - equal: + path: spec.template.metadata.annotations.kubernetes\.io/pod + value: test-annotation + - equal: + path: spec.template.metadata.annotations.kubernetes\.io/pod-different + value: 4 + - matchSnapshot: + path: spec.template.spec + + - it: should not have more than one replica in standalone mode + set: + chartMode: standalone + clusterName: helm-lint.example.com + asserts: + - equal: + path: spec.replicas + value: 1 + - matchSnapshot: + path: spec.template.spec + + - it: should have multiple replicas when replicaCount is set + set: + chartMode: custom + clusterName: helm-lint.example.com + highAvailability: + replicaCount: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + - matchSnapshot: + path: spec.template.spec + + - it: should set affinity when set in values + set: + chartMode: custom + clusterName: helm-lint.example.com + highAvailability: + replicaCount: 3 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: gravitational.io/dedicated + operator: In + values: + - teleport + asserts: + - isNotNull: + path: spec.template.spec.affinity + - matchSnapshot: + path: spec.template.spec + + - it: should set required affinity when highAvailability.requireAntiAffinity is set + values: + - ../.lint/aws-ha-antiaffinity.yaml + asserts: + - isNotNull: + path: spec.template.spec.affinity + - isNotNull: + path: spec.template.spec.affinity.podAntiAffinity + - isNotNull: + path: spec.template.spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution + - matchSnapshot: + path: spec.template.spec + + - it: should set tolerations when set in values + values: + - ../.lint/tolerations.yaml + asserts: + - isNotNull: + path: spec.template.spec.tolerations + - matchSnapshot: + path: spec.template.spec + + - it: should set resources when set in values + values: + - ../.lint/resources.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].resources.limits.cpu + value: 2 + - equal: + path: spec.template.spec.containers[0].resources.limits.memory + value: 4Gi + - equal: + path: spec.template.spec.containers[0].resources.requests.cpu + value: 1 + - equal: + path: spec.template.spec.containers[0].resources.requests.memory + value: 2Gi + - matchSnapshot: + path: spec.template.spec + + # we can't use the dynamic chart version or appVersion as a variable in the tests, + # so we override it manually and check that gets set instead + # this saves us having to update the test every time we cut a new release + - it: should do enterprise things when when enterprise is set in values + set: + clusterName: helm-lint.example.com + enterprise: true + teleportVersionOverride: 8.3.4 + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: quay.io/gravitational/teleport-ent:8.3.4 + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /var/lib/license + name: "license" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: license + secret: + secretName: license + - matchSnapshot: + path: spec.template.spec + + - it: should not do enterprise things when when enterprise is not set in values + set: + clusterName: helm-lint + teleportVersionOverride: 8.3.4 + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: quay.io/gravitational/teleport:8.3.4 + - notContains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /var/lib/license + name: "license" + readOnly: true + - notContains: + path: spec.template.spec.volumes + content: + name: license + secret: + secretName: license + - matchSnapshot: + path: spec.template.spec + + - it: should mount GCP credentials in GCP mode + values: + - ../.lint/gcp-ha.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /etc/teleport-secrets + name: "gcp-credentials" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: gcp-credentials + secret: + secretName: teleport-gcp-credentials + - matchSnapshot: + path: spec.template.spec + + - it: should mount GCP credentials for initContainer in GCP mode + values: + - ../.lint/gcp-ha.yaml + - ../.lint/initcontainers.yaml + asserts: + - contains: + path: spec.template.spec.initContainers[0].volumeMounts + content: + mountPath: /etc/teleport-secrets + name: "gcp-credentials" + readOnly: true + - matchSnapshot: + path: spec.template.spec + + - it: should mount TLS certs when cert-manager is enabled + values: + - ../.lint/gcp-ha-acme.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /etc/teleport-tls + name: "teleport-tls" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: teleport-tls + secret: + secretName: teleport-tls + - matchSnapshot: + path: spec.template.spec + + - it: should mount TLS certs for initContainer when cert-manager is enabled + values: + - ../.lint/gcp-ha-acme.yaml + - ../.lint/initcontainers.yaml + asserts: + - contains: + path: spec.template.spec.initContainers[0].volumeMounts + content: + mountPath: /etc/teleport-tls + name: "teleport-tls" + readOnly: true + - matchSnapshot: + path: spec.template.spec + + - it: should mount ConfigMap for config in AWS mode + values: + - ../.lint/aws-ha.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /etc/teleport + name: "config" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: config + configMap: + name: RELEASE-NAME + - matchSnapshot: + path: spec.template.spec + + - it: should mount ConfigMap for config in GCP mode + values: + - ../.lint/gcp-ha.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /etc/teleport + name: "config" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: config + configMap: + name: RELEASE-NAME + - matchSnapshot: + path: spec.template.spec + + - it: should mount ConfigMap for config in standalone mode + set: + chartMode: standalone + clusterName: helm-lint.example.com + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /etc/teleport + name: "config" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: config + configMap: + name: RELEASE-NAME + - matchSnapshot: + path: spec.template.spec + + - it: should mount ConfigMap for config in custom mode + set: + chartMode: custom + clusterName: helm-lint.example.com + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /etc/teleport + name: "config" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: config + configMap: + name: RELEASE-NAME + - matchSnapshot: + path: spec.template.spec + + - it: should mount extraVolumes and extraVolumeMounts + values: + - ../.lint/volumes.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /path/to/mount + name: my-mount + - contains: + path: spec.template.spec.volumes + content: + name: my-mount + secret: + secretName: mySecret + - matchSnapshot: + path: spec.template.spec + + - it: should mount extraVolumes and extraVolumeMounts + values: + - ../.lint/volumes.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /path/to/mount + name: my-mount + - contains: + path: spec.template.spec.volumes + content: + name: my-mount + secret: + secretName: mySecret + - matchSnapshot: + path: spec.template.spec + + - it: should mount extraVolumes and extraVolumeMounts + values: + - ../.lint/volumes.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /path/to/mount + name: my-mount + - contains: + path: spec.template.spec.volumes + content: + name: my-mount + secret: + secretName: mySecret + - matchSnapshot: + path: spec.template.spec + + - it: should set imagePullPolicy when set in values + set: + clusterName: helm-lint.example.com + imagePullPolicy: Always + asserts: + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: Always + - matchSnapshot: + path: spec.template.spec + + - it: should set environment when extraEnv set in values + set: + clusterName: helm-lint.example.com + extraEnv: + - name: SSL_CERT_FILE + value: "/etc/tls/some-cert-file.pem" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SSL_CERT_FILE + value: "/etc/tls/some-cert-file.pem" + - matchSnapshot: + path: spec.template.spec + + - it: should provision initContainer correctly when set in values + values: + - ../.lint/initcontainers.yaml + - ../.lint/resources.yaml + set: + extraEnv: + - name: SSL_CERT_FILE + value: "/etc/tls/some-cert-file.pem" + asserts: + - contains: + path: spec.template.spec.initContainers[0].args + content: "echo test" + - equal: + path: spec.template.spec.initContainers[0].name + value: "teleport-init" + - equal: + path: spec.template.spec.initContainers[0].image + value: "alpine" + - equal: + path: spec.template.spec.initContainers[0].resources.limits.cpu + value: 2 + - equal: + path: spec.template.spec.initContainers[0].resources.limits.memory + value: 4Gi + - equal: + path: spec.template.spec.initContainers[0].resources.requests.cpu + value: 1 + - equal: + path: spec.template.spec.initContainers[0].resources.requests.memory + value: 2Gi + - matchSnapshot: + path: spec.template.spec + + - it: should add insecureSkipProxyTLSVerify to args when set in values + set: + clusterName: helm-lint.example.com + insecureSkipProxyTLSVerify: true + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--insecure" + - matchSnapshot: + path: spec.template.spec + + - it: should expose diag port + set: + clusterName: helm-lint.example.com + asserts: + - contains: + path: spec.template.spec.containers[0].ports + content: + name: diag + containerPort: 3000 + protocol: TCP + - matchSnapshot: + path: spec.template.spec + + - it: should set postStart command if set in values + set: + clusterName: helm-lint.example.com + postStart: + command: ["/bin/echo", "test"] + asserts: + - equal: + path: spec.template.spec.containers[0].lifecycle.postStart.exec.command + value: ["/bin/echo", "test"] + - matchSnapshot: + path: spec.template.spec + + - it: should add PersistentVolumeClaim as volume when in standalone mode + set: + chartMode: standalone + clusterName: helm-lint.example.com + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: data + persistentVolumeClaim: + claimName: RELEASE-NAME + - matchSnapshot: + path: spec.template.spec + + - it: should add named PersistentVolumeClaim as volume when in standalone mode and standalone.existingClaimName is set + values: + - ../.lint/standalone-existingpvc.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: data + persistentVolumeClaim: + claimName: teleport-storage + - matchSnapshot: + path: spec.template.spec + + - it: should add emptyDir for data in AWS mode + values: + - ../.lint/aws-ha.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: data + emptyDir: {} + - matchSnapshot: + path: spec.template.spec + + - it: should add emptyDir for data in GCP mode + values: + - ../.lint/gcp-ha.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: data + emptyDir: {} + - matchSnapshot: + path: spec.template.spec + + - it: should add emptyDir for data in custom mode + set: + chartMode: custom + clusterName: helm-lint.example.com + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: data + emptyDir: {} + - matchSnapshot: + path: spec.template.spec + + - it: should set priorityClassName when set in values + values: + - ../.lint/priority-class-name.yaml + asserts: + - equal: + path: spec.template.spec.priorityClassName + value: system-cluster-critical + - matchSnapshot: + path: spec.template.spec diff --git a/examples/chart/teleport-cluster/tests/pdb_test.yaml b/examples/chart/teleport-cluster/tests/pdb_test.yaml new file mode 100644 index 0000000000000..b673ce8400ce3 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/pdb_test.yaml @@ -0,0 +1,13 @@ +suite: PodDisruptionBudget +templates: + - pdb.yaml +tests: + - it: should create a PDB when enabled in values (pdb.yaml) + values: + - ../.lint/pdb.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: PodDisruptionBudget + - matchSnapshot: {} diff --git a/examples/chart/teleport-cluster/tests/psp_test.yaml b/examples/chart/teleport-cluster/tests/psp_test.yaml new file mode 100644 index 0000000000000..0faad123c1dc1 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/psp_test.yaml @@ -0,0 +1,21 @@ +suite: PodSecurityPolicy +templates: + - psp.yaml +tests: + - it: creates a PodSecurityPolicy when enabled in values + set: + podSecurityPolicy: + enabled: true + asserts: + - hasDocuments: + count: 3 + - documentIndex: 0 + isKind: + of: PodSecurityPolicy + - documentIndex: 1 + isKind: + of: Role + - documentIndex: 2 + isKind: + of: RoleBinding + - matchSnapshot: {} diff --git a/examples/chart/teleport-cluster/tests/pvc_test.yaml b/examples/chart/teleport-cluster/tests/pvc_test.yaml new file mode 100644 index 0000000000000..9dbdc05448e48 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/pvc_test.yaml @@ -0,0 +1,65 @@ +suite: PersistentVolumeClaim +templates: + - pvc.yaml +tests: + - it: creates a PersistentVolumeClaim when chartMode=standalone + set: + chartMode: standalone + asserts: + - template: pvc.yaml + hasDocuments: + count: 1 + - template: pvc.yaml + isKind: + of: PersistentVolumeClaim + - matchSnapshot: {} + + - it: creates a PersistentVolumeClaim with values from standalone-customsize.yaml + values: + - ../.lint/standalone-customsize.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: PersistentVolumeClaim + - equal: + path: spec.resources.requests.storage + value: 50Gi + - matchSnapshot: {} + + - it: does not create a PersistentVolumeClaim when chartMode=standalone and existingClaimName is not blank + set: + chartMode: standalone + standalone: + existingClaimName: test-claim + asserts: + - hasDocuments: + count: 0 + + - it: does not create a PersistentVolumeClaim with values from standalone-existingpvc.yaml + values: + - ../.lint/standalone-existingpvc.yaml + asserts: + - hasDocuments: + count: 0 + + - it: does not create a PersistentVolumeClaim when chartMode=aws + set: + chartMode: aws + asserts: + - hasDocuments: + count: 0 + + - it: does not create a PersistentVolumeClaim when chartMode=gcp + set: + chartMode: gcp + asserts: + - hasDocuments: + count: 0 + + - it: does not create a PersistentVolumeClaim when chartMode=custom + set: + chartMode: custom + asserts: + - hasDocuments: + count: 0 diff --git a/examples/chart/teleport-cluster/tests/service_test.yaml b/examples/chart/teleport-cluster/tests/service_test.yaml new file mode 100644 index 0000000000000..c8cc66f050182 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/service_test.yaml @@ -0,0 +1,66 @@ +suite: Service +templates: + - service.yaml +tests: + - it: uses a LoadBalancer by default + set: + clusterName: teleport.example.com + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Service + - equal: + path: spec.type + value: LoadBalancer + - matchSnapshot: {} + + - it: uses a ClusterIP when service.type=ClusterIP + set: + clusterName: teleport.example.com + service: + type: ClusterIP + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Service + - equal: + path: spec.type + value: ClusterIP + - matchSnapshot: {} + + - it: sets AWS annotations when chartMode=aws + set: + clusterName: teleport.example.com + chartMode: aws + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Service + - equal: + path: spec.type + value: LoadBalancer + - equal: + path: metadata.annotations.service\.beta\.kubernetes\.io/aws-load-balancer-type + value: nlb + - equal: + path: metadata.annotations.service\.beta\.kubernetes\.io/aws-load-balancer-backend-protocol + value: tcp + - equal: + path: metadata.annotations.service\.beta\.kubernetes\.io/aws-load-balancer-cross-zone-load-balancing-enabled + value: "true" + - matchSnapshot: {} + + - it: sets service annotations when specified + values: + - ../.lint/annotations.yaml + asserts: + - equal: + path: metadata.annotations.kubernetes\.io/service + value: test-annotation + - equal: + path: metadata.annotations.kubernetes\.io/service-different + value: 5 + - matchSnapshot: {} diff --git a/examples/chart/teleport-cluster/tests/serviceaccount_test.yaml b/examples/chart/teleport-cluster/tests/serviceaccount_test.yaml new file mode 100644 index 0000000000000..ec5d98884a2b5 --- /dev/null +++ b/examples/chart/teleport-cluster/tests/serviceaccount_test.yaml @@ -0,0 +1,15 @@ +suite: ServiceAccount +templates: + - serviceaccount.yaml +tests: + - it: sets ServiceAccount annotations when specified + values: + - ../.lint/annotations.yaml + asserts: + - equal: + path: metadata.annotations.kubernetes\.io/serviceaccount + value: test-annotation + - equal: + path: metadata.annotations.kubernetes\.io/serviceaccount-different + value: 6 + - matchSnapshot: {} diff --git a/examples/chart/teleport-cluster/values.schema.json b/examples/chart/teleport-cluster/values.schema.json index 1aff511629094..a81b67fa3cfe4 100644 --- a/examples/chart/teleport-cluster/values.schema.json +++ b/examples/chart/teleport-cluster/values.schema.json @@ -32,6 +32,36 @@ "type": "string", "default": "local" }, + "authenticationSecondFactor": { + "$id": "#/properties/authenticationSecondFactor", + "type": "object", + "required": [], + "properties": { + "secondFactor": { + "$id": "#/properties/authenticationSecondFactor/properties/secondFactor", + "type": "string", + "enum": ["off", "on", "otp", "optional", "webauthn"], + "default": "otp" + }, + "webauthn": { + "$id": "#/properties/authenticationSecondFactor/properties/webauthn", + "type": "object", + "required": [], + "properties": { + "attestationAllowedCas": { + "$id": "#/properties/authenticationSecondFactor/properties/webauthn/properties/attestationAllowedCas", + "type": "array", + "default": [] + }, + "attestationDeniedCas": { + "$id": "#/properties/authenticationSecondFactor/properties/webauthn/properties/attestationDeniedCas", + "type": "array", + "default": [] + } + } + } + } + }, "proxyListenerMode": { "$id": "#/properties/proxyListenerMode", "type": "string", diff --git a/examples/chart/teleport-cluster/values.yaml b/examples/chart/teleport-cluster/values.yaml index 841b1cc3e0280..20d09470d065f 100644 --- a/examples/chart/teleport-cluster/values.yaml +++ b/examples/chart/teleport-cluster/values.yaml @@ -22,6 +22,9 @@ teleportVersionOverride: "" # 'false' is required for FedRAMP / FIPS; see https://gravitational.com/teleport/docs/enterprise/ssh-kubernetes-fedramp/ authenticationType: local +authenticationSecondFactor: + secondFactor: "otp" + # Teleport supports TLS routing. In this mode, all client connections are wrapped in TLS and multiplexed on one Teleport proxy port. # Default mode will not utilize TLS routing and operate in backwards-compatibility mode. # Possible values are 'multiplex' diff --git a/examples/chart/teleport-daemonset/README.md b/examples/chart/teleport-daemonset/README.md index 83d46ae567411..247176c44664a 100644 --- a/examples/chart/teleport-daemonset/README.md +++ b/examples/chart/teleport-daemonset/README.md @@ -1,3 +1,8 @@ +# WARNING + +This chart is **deprecated** and no longer actively maintained or supported by Teleport. +We recommend the use of the [teleport-cluster](../teleport-cluster/README.md) chart instead. + # Teleport [Gravitational Teleport](https://github.com/gravitational/teleport) is a modern SSH/Kubernetes API proxy server for diff --git a/examples/chart/teleport-kube-agent/.lint/existing-data-volume.yaml b/examples/chart/teleport-kube-agent/.lint/existing-data-volume.yaml new file mode 100644 index 0000000000000..511aa2f4676ea --- /dev/null +++ b/examples/chart/teleport-kube-agent/.lint/existing-data-volume.yaml @@ -0,0 +1,5 @@ +authToken: auth-token +proxyAddr: proxy.example.com:3080 +roles: kube +kubeClusterName: test-kube-cluster +existingDataVolume: teleport-kube-agent-data diff --git a/examples/chart/teleport-kube-agent/.lint/extra-args.yaml b/examples/chart/teleport-kube-agent/.lint/extra-args.yaml new file mode 100644 index 0000000000000..8353439d2484a --- /dev/null +++ b/examples/chart/teleport-kube-agent/.lint/extra-args.yaml @@ -0,0 +1,5 @@ +authToken: auth-token +proxyAddr: proxy.example.com:3080 +roles: kube +kubeClusterName: test-kube-cluster +extraArgs: ['--debug'] diff --git a/examples/chart/teleport-kube-agent/.lint/extra-env.yaml b/examples/chart/teleport-kube-agent/.lint/extra-env.yaml new file mode 100644 index 0000000000000..7f3ee92e73665 --- /dev/null +++ b/examples/chart/teleport-kube-agent/.lint/extra-env.yaml @@ -0,0 +1,7 @@ +authToken: auth-token +proxyAddr: proxy.example.com:3080 +roles: kube +kubeClusterName: test-kube-cluster +extraEnv: +- name: HTTPS_PROXY + value: "http://username:password@my.proxy.host:3128" diff --git a/examples/chart/teleport-kube-agent/.lint/image-pull-policy-stateful.yaml b/examples/chart/teleport-kube-agent/.lint/image-pull-policy-stateful.yaml new file mode 100644 index 0000000000000..83995f0c62147 --- /dev/null +++ b/examples/chart/teleport-kube-agent/.lint/image-pull-policy-stateful.yaml @@ -0,0 +1,7 @@ +authToken: auth-token +proxyAddr: proxy.example.com:3080 +kubeClusterName: test-kube-cluster-name +storage: + enabled: true + storageClassName: "aws-gp2" +imagePullPolicy: Always diff --git a/examples/chart/teleport-kube-agent/.lint/image-pull-policy.yaml b/examples/chart/teleport-kube-agent/.lint/image-pull-policy.yaml new file mode 100644 index 0000000000000..c5e389ca10a77 --- /dev/null +++ b/examples/chart/teleport-kube-agent/.lint/image-pull-policy.yaml @@ -0,0 +1,5 @@ +authToken: auth-token +proxyAddr: proxy.example.com:3080 +roles: kube +kubeClusterName: test-kube-cluster +imagePullPolicy: Always diff --git a/examples/chart/teleport-kube-agent/.lint/service-account-name.yaml b/examples/chart/teleport-kube-agent/.lint/service-account-name.yaml new file mode 100644 index 0000000000000..fbc76f97fb727 --- /dev/null +++ b/examples/chart/teleport-kube-agent/.lint/service-account-name.yaml @@ -0,0 +1,5 @@ +authToken: auth-token +proxyAddr: proxy.example.com:3080 +roles: kube +kubeClusterName: test-kube-cluster +serviceAccountName: teleport-kube-agent-sa diff --git a/examples/chart/teleport-kube-agent/.lint/volumes.yaml b/examples/chart/teleport-kube-agent/.lint/volumes.yaml index 14bdc49390f50..1f55235396f2f 100644 --- a/examples/chart/teleport-kube-agent/.lint/volumes.yaml +++ b/examples/chart/teleport-kube-agent/.lint/volumes.yaml @@ -4,7 +4,7 @@ roles: kube kubeClusterName: test-kube-cluster extraVolumeMounts: - name: "my-mount" - path: "/path/to/mount" + mountPath: "/path/to/mount" extraVolumes: - name: "my-mount" secret: diff --git a/examples/chart/teleport-kube-agent/README.md b/examples/chart/teleport-kube-agent/README.md index 74776fc848019..eb89bd447ba1b 100644 --- a/examples/chart/teleport-kube-agent/README.md +++ b/examples/chart/teleport-kube-agent/README.md @@ -194,3 +194,7 @@ If the service for a given role doesn't show up, look into the agent logs with: ```sh $ kubectl logs -n teleport deployment/teleport-kube-agent ``` + +## Contributing to the chart + +Please read [CONTRIBUTING.md](../CONTRIBUTING.md) before raising a pull request to this chart. \ No newline at end of file diff --git a/examples/chart/teleport-kube-agent/templates/config.yaml b/examples/chart/teleport-kube-agent/templates/config.yaml index c5aed3dad9ffa..bc7453847097f 100644 --- a/examples/chart/teleport-kube-agent/templates/config.yaml +++ b/examples/chart/teleport-kube-agent/templates/config.yaml @@ -1,8 +1,14 @@ {{- $logLevel := (coalesce .Values.logLevel .Values.log.level "INFO") -}} +{{- if .Values.teleportVersionOverride -}} + {{- $_ := set . "teleportVersion" .Values.teleportVersionOverride -}} +{{- else -}} + {{- $_ := set . "teleportVersion" .Chart.Version -}} +{{- end -}} apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} {{- if .Values.annotations.config }} annotations: {{- toYaml .Values.annotations.config | nindent 4 }} diff --git a/examples/chart/teleport-kube-agent/templates/deployment.yaml b/examples/chart/teleport-kube-agent/templates/deployment.yaml index 4223207eeab16..0b6dc0aab2f4c 100644 --- a/examples/chart/teleport-kube-agent/templates/deployment.yaml +++ b/examples/chart/teleport-kube-agent/templates/deployment.yaml @@ -3,16 +3,7 @@ # in the statefulset.yaml file. # {{- if not .Values.storage.enabled }} -{{- if .Values.teleportVersionOverride }} - {{- $_ := set . "teleportVersion" .Values.teleportVersionOverride }} -{{- else }} - {{- $_ := set . "teleportVersion" .Chart.Version }} -{{- end }} -{{- if .Values.replicaCount }} - {{- $_ := set . "replicaCount" .Values.replicaCount }} -{{- else }} - {{- $_ := set . "replicaCount" .Values.highAvailability.replicaCount }} -{{- end }} +{{- $replicaCount := (coalesce .Values.replicaCount .Values.highAvailability.replicaCount "1") }} apiVersion: apps/v1 kind: Deployment metadata: @@ -25,7 +16,7 @@ metadata: {{- toYaml .Values.annotations.deployment | nindent 4 }} {{- end }} spec: - replicas: {{ .replicaCount }} + replicas: {{ $replicaCount }} selector: matchLabels: app: {{ .Release.Name }} @@ -40,7 +31,7 @@ spec: labels: app: {{ .Release.Name }} spec: - {{- if or .Values.affinity (gt (int .replicaCount) 1) }} + {{- if or .Values.affinity (gt (int $replicaCount) 1) }} affinity: {{- if .Values.affinity }} {{- if .Values.highAvailability.requireAntiAffinity }} @@ -58,7 +49,7 @@ spec: values: - {{ .Release.Name }} topologyKey: "kubernetes.io/hostname" - {{- else if gt (int .replicaCount) 1 }} + {{- else if gt (int $replicaCount) 1 }} preferredDuringSchedulingIgnoredDuringExecution: - weight: 50 podAffinityTerm: @@ -114,11 +105,21 @@ spec: containers: - name: "teleport" image: "{{ if .Values.enterprise }}{{ .Values.enterpriseImage }}{{ else }}{{ .Values.image }}{{ end }}:{{ .teleportVersion }}" + {{- if .Values.imagePullPolicy }} + imagePullPolicy: {{ toYaml .Values.imagePullPolicy }} + {{- end }} + {{- if .Values.extraEnv }} + env: + {{- toYaml .Values.extraEnv | nindent 10 }} + {{- end }} args: - "--diag-addr=0.0.0.0:3000" {{- if .Values.insecureSkipProxyTLSVerify }} - "--insecure" {{- end }} + {{- if .Values.extraArgs }} + {{- toYaml .Values.extraArgs | nindent 8 }} + {{- end }} securityContext: allowPrivilegeEscalation: false capabilities: diff --git a/examples/chart/teleport-kube-agent/templates/statefulset.yaml b/examples/chart/teleport-kube-agent/templates/statefulset.yaml index 88ef070f13a37..2a34bca3b23ce 100644 --- a/examples/chart/teleport-kube-agent/templates/statefulset.yaml +++ b/examples/chart/teleport-kube-agent/templates/statefulset.yaml @@ -3,16 +3,7 @@ # in the deployment.yaml file. # {{- if .Values.storage.enabled }} -{{- if .Values.teleportVersionOverride }} - {{- $_ := set . "teleportVersion" .Values.teleportVersionOverride }} -{{- else }} - {{- $_ := set . "teleportVersion" .Chart.Version }} -{{- end }} -{{- if .Values.replicaCount }} - {{- $_ := set . "replicaCount" .Values.replicaCount }} -{{- else }} - {{- $_ := set . "replicaCount" .Values.highAvailability.replicaCount }} -{{- end }} +{{- $replicaCount := (coalesce .Values.replicaCount .Values.highAvailability.replicaCount "1") }} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -22,7 +13,7 @@ metadata: app: {{ .Release.Name }} spec: serviceName: {{ .Release.Name }} - replicas: {{ .replicaCount }} + replicas: {{ $replicaCount }} selector: matchLabels: app: {{ .Release.Name }} @@ -39,7 +30,7 @@ spec: spec: securityContext: fsGroup: 9807 - {{- if or .Values.affinity (gt (int .replicaCount) 1) }} + {{- if or .Values.affinity (gt (int $replicaCount) 1) }} affinity: {{- if .Values.affinity }} {{- if .Values.highAvailability.requireAntiAffinity }} @@ -57,7 +48,7 @@ spec: values: - {{ .Release.Name }} topologyKey: "kubernetes.io/hostname" - {{- else if gt (int .replicaCount) 1 }} + {{- else if gt (int $replicaCount) 1 }} preferredDuringSchedulingIgnoredDuringExecution: - weight: 50 podAffinityTerm: @@ -110,11 +101,21 @@ spec: containers: - name: "teleport" image: "{{ if .Values.enterprise }}{{ .Values.enterpriseImage }}{{ else }}{{ .Values.image }}{{ end }}:{{ .teleportVersion }}" + {{- if .Values.imagePullPolicy }} + imagePullPolicy: {{ toYaml .Values.imagePullPolicy }} + {{- end }} + {{- if .Values.extraEnv }} + env: + {{- toYaml .Values.extraEnv | nindent 10 }} + {{- end }} args: - "--diag-addr=0.0.0.0:3000" {{- if .Values.insecureSkipProxyTLSVerify }} - "--insecure" {{- end }} + {{- if .Values.extraArgs }} + {{- toYaml .Values.extraArgs | nindent 8 }} + {{- end }} securityContext: allowPrivilegeEscalation: false capabilities: diff --git a/examples/chart/teleport-kube-agent/tests/README.md b/examples/chart/teleport-kube-agent/tests/README.md new file mode 100644 index 0000000000000..3cd0f8783d4e6 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/README.md @@ -0,0 +1,18 @@ +## Unit tests for Helm charts + +Helm chart unit tests run here using the [helm3-unittest](https://github.com/vbehar/helm3-unittest) Helm plugin. + +If you get a snapshot error during your testing, you should verify that your changes intended to alter the output, then run +this command from the root of your Teleport checkout to update the snapshots: + +```bash +make -C build.assets test-helm-update-snapshots +``` + +After this, re-run the tests to make sure everything is fine: + +```bash +make -C build.assets test-helm +``` + +Commit the updated snapshots along with your changes. diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/clusterrole_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/clusterrole_test.yaml.snap new file mode 100644 index 0000000000000..73d2b30742ace --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/clusterrole_test.yaml.snap @@ -0,0 +1,27 @@ +creates a ClusterRole: + 1: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: RELEASE-NAME + rules: + - apiGroups: + - "" + resources: + - users + - groups + - serviceaccounts + verbs: + - impersonate + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - apiGroups: + - authorization.k8s.io + resources: + - selfsubjectaccessreviews + verbs: + - create diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/clusterrolebinding_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/clusterrolebinding_test.yaml.snap new file mode 100644 index 0000000000000..408ec5f4556e2 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/clusterrolebinding_test.yaml.snap @@ -0,0 +1,14 @@ +creates a ClusterRoleBinding: + 1: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: RELEASE-NAME + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: RELEASE-NAME + subjects: + - kind: ServiceAccount + name: RELEASE-NAME + namespace: NAMESPACE diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/config_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/config_test.yaml.snap new file mode 100644 index 0000000000000..4c73bbfb71cf9 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/config_test.yaml.snap @@ -0,0 +1,658 @@ +does not generate a config for clusterrole.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +does not generate a config for pdb.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster-name + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot and tests for annotations.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + annotations: + kubernetes.io/config: test-annotation + kubernetes.io/config-different: 2 + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for affinity.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for all-v5.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster-name + labels: + cluster: testing + + app_service: + enabled: true + apps: + - labels: + environment: test + name: grafana + uri: http://localhost:3000 + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for all-v6.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster-name + labels: + cluster: testing + + app_service: + enabled: true + apps: + - labels: + environment: test + name: grafana + uri: http://localhost:3000 + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + annotations: + kubernetes.io/config: test-annotation + kubernetes.io/config-different: 2 + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for aws-databases.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: false + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for backwards-compatibility.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster-name + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for db.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: false + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for imagepullsecrets.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for initcontainers.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for log-basic.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: json + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster-name + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for log-extra.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: DEBUG + output: /var/lib/teleport/test.log + format: + output: json + extra_fields: ["level","timestamp","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster-name + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for log-legacy.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: DEBUG + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster-name + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for node-selector.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster-name + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for pdb.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: DEBUG + output: /var/lib/teleport/test.log + format: + output: json + extra_fields: ["level","timestamp","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster-name + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for resources.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for stateful.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster-name + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for tolerations.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE +matches snapshot for volumes.yaml: + 1: | + apiVersion: v1 + data: + teleport.yaml: | + teleport: + auth_token: "/etc/teleport-secrets/auth-token" + auth_servers: ["proxy.example.com:3080"] + log: + severity: INFO + output: stderr + format: + output: text + extra_fields: ["timestamp","level","component","caller"] + + kubernetes_service: + enabled: true + kube_cluster_name: test-kube-cluster + + app_service: + enabled: false + + auth_service: + enabled: false + ssh_service: + enabled: false + proxy_service: + enabled: false + kind: ConfigMap + metadata: + name: RELEASE-NAME + namespace: NAMESPACE diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap new file mode 100644 index 0000000000000..980b420d739b2 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap @@ -0,0 +1,1346 @@ +sets Deployment annotations when specified: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +sets Pod annotations when specified: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should add emptyDir for data when existingDataVolume is not set: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should add insecureSkipProxyTLSVerify to args when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + - --insecure + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should correctly configure existingDataVolume when set: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: teleport-kube-agent-data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should expose diag port: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should have multiple replicas when replicaCount is set (using .replicaCount, deprecated): + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should have multiple replicas when replicaCount is set (using highAvailability.replicaCount): + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should have one replica when replicaCount is not set: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should mount extraVolumes and extraVolumeMounts: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + - mountPath: /path/to/mount + name: my-mount + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data + - name: my-mount + secret: + secretName: mySecret +should provision initContainer correctly when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 1 + memory: 2Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + initContainers: + - args: + - echo test + image: alpine + name: teleport-init + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 1 + memory: 2Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set SecurityContext: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set affinity when set in values: + 1: | + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: gravitational.io/dedicated + operator: In + values: + - teleport + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - teleport + topologyKey: kubernetes.io/hostname + weight: 1 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set default serviceAccountName when not set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set environment when extraEnv set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + env: + - name: HTTPS_PROXY + value: http://username:password@my.proxy.host:3128 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set image and tag correctly: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:8.3.4 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set imagePullPolicy when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: Always + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set nodeSelector if set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + nodeSelector: + gravitational.io/k8s-role: node + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set preferred affinity when more than one replica is used: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set required affinity when highAvailability.requireAntiAffinity is set: + 1: | + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set resources when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 1 + memory: 2Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set serviceAccountName when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: teleport-kube-agent-sa + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data +should set tolerations when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: data + serviceAccountName: RELEASE-NAME + tolerations: + - effect: NoExecute + key: dedicated + operator: Equal + value: teleport + - effect: NoSchedule + key: dedicated + operator: Equal + value: teleport + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - emptyDir: {} + name: data diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/pdb_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/pdb_test.yaml.snap new file mode 100644 index 0000000000000..a8d3bf87b8092 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/pdb_test.yaml.snap @@ -0,0 +1,14 @@ +should create a PDB when enabled in values (pdb.yaml): + 1: | + apiVersion: policy/v1beta1 + kind: PodDisruptionBudget + metadata: + labels: + app: RELEASE-NAME + name: RELEASE-NAME + namespace: NAMESPACE + spec: + minAvailable: 2 + selector: + matchLabels: + app: RELEASE-NAME diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/psp_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/psp_test.yaml.snap new file mode 100644 index 0000000000000..5536920844cf3 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/psp_test.yaml.snap @@ -0,0 +1,34 @@ +creates a PodSecurityPolicy when enabled in values: + 1: | + apiVersion: policy/v1beta1 + kind: PodSecurityPolicy + metadata: + annotations: + seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default,runtime/default + seccomp.security.alpha.kubernetes.io/defaultProfileName: runtime/default + name: RELEASE-NAME + spec: + allowPrivilegeEscalation: false + fsGroup: + ranges: + - max: 65535 + min: 1 + rule: MustRunAs + hostIPC: false + hostNetwork: false + hostPID: false + privileged: false + readOnlyRootFilesystem: true + requiredDropCapabilities: + - ALL + runAsUser: + rule: MustRunAsNonRoot + seLinux: + rule: RunAsAny + supplementalGroups: + ranges: + - max: 65535 + min: 1 + rule: MustRunAs + volumes: + - '*' diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/secret_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/secret_test.yaml.snap new file mode 100644 index 0000000000000..a1176d268f494 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/secret_test.yaml.snap @@ -0,0 +1,22 @@ +generates a secret when authToken is provided: + 1: | + apiVersion: v1 + kind: Secret + metadata: + name: teleport-kube-agent-join-token + namespace: NAMESPACE + stringData: + auth-token: | + sample-auth-token-dont-use-this + type: Opaque +generates a secret with a custom name when authToken and secretName are provided: + 1: | + apiVersion: v1 + kind: Secret + metadata: + name: some-other-secret-name + namespace: NAMESPACE + stringData: + auth-token: | + sample-auth-token-dont-use-this + type: Opaque diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/serviceaccount_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/serviceaccount_test.yaml.snap new file mode 100644 index 0000000000000..fa132fe5d7c65 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/serviceaccount_test.yaml.snap @@ -0,0 +1,10 @@ +sets ServiceAccount annotations when specified: + 1: | + apiVersion: v1 + kind: ServiceAccount + metadata: + annotations: + kubernetes.io/serviceaccount: test-annotation + kubernetes.io/serviceaccount-different: 5 + name: RELEASE-NAME + namespace: NAMESPACE diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap new file mode 100644 index 0000000000000..c824d810abfa9 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap @@ -0,0 +1,1454 @@ +sets Pod annotations when specified: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should add insecureSkipProxyTLSVerify to args when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + - --insecure + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should add volumeClaimTemplate for data volume when using StatefulSet: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should add volumeMount for data volume when using StatefulSet: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should expose diag port: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should have multiple replicas when replicaCount is set (using .replicaCount, deprecated): + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should have multiple replicas when replicaCount is set (using highAvailability.replicaCount): + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should have one replica when replicaCount is not set: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should mount extraVolumes and extraVolumeMounts: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + - mountPath: /path/to/mount + name: my-mount + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token + - name: my-mount + secret: + secretName: mySecret +should not add emptyDir for data when using StatefulSet: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should provision initContainer correctly when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 1 + memory: 2Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + initContainers: + - args: + - echo test + image: alpine + name: teleport-init + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 1 + memory: 2Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set SecurityContext: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set affinity when set in values: + 1: | + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: gravitational.io/dedicated + operator: In + values: + - teleport + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - teleport + topologyKey: kubernetes.io/hostname + weight: 1 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set default serviceAccountName when not set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set environment when extraEnv set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + env: + - name: HTTPS_PROXY + value: http://username:password@my.proxy.host:3128 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set image and tag correctly: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:8.3.4 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set imagePullPolicy when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: Always + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set nodeSelector if set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + nodeSelector: + gravitational.io/k8s-role: node + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set preferred affinity when more than one replica is used: + 1: | + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + weight: 50 + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set required affinity when highAvailability.requireAntiAffinity is set: + 1: | + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - RELEASE-NAME + topologyKey: kubernetes.io/hostname + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set resources when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 1 + memory: 2Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set serviceAccountName when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: teleport-kube-agent-sa + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set storage.requests when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set storage.storageClassName when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token +should set tolerations when set in values: + 1: | + containers: + - args: + - --diag-addr=0.0.0.0:3000 + image: quay.io/gravitational/teleport:10.0.0-dev + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + name: teleport + ports: + - containerPort: 3000 + name: diag + protocol: TCP + readinessProbe: + failureThreshold: 12 + httpGet: + path: /readyz + port: diag + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 9807 + volumeMounts: + - mountPath: /etc/teleport + name: config + readOnly: true + - mountPath: /etc/teleport-secrets + name: auth-token + readOnly: true + - mountPath: /var/lib/teleport + name: RELEASE-NAME-teleport-data + securityContext: + fsGroup: 9807 + serviceAccountName: RELEASE-NAME + tolerations: + - effect: NoExecute + key: dedicated + operator: Equal + value: teleport + - effect: NoSchedule + key: dedicated + operator: Equal + value: teleport + volumes: + - configMap: + name: RELEASE-NAME + name: config + - name: auth-token + secret: + secretName: teleport-kube-agent-join-token diff --git a/examples/chart/teleport-kube-agent/tests/clusterrole_test.yaml b/examples/chart/teleport-kube-agent/tests/clusterrole_test.yaml new file mode 100644 index 0000000000000..337947d13ad2b --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/clusterrole_test.yaml @@ -0,0 +1,11 @@ +suite: ClusterRole +templates: + - clusterrole.yaml +tests: + - it: creates a ClusterRole + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ClusterRole + - matchSnapshot: {} diff --git a/examples/chart/teleport-kube-agent/tests/clusterrolebinding_test.yaml b/examples/chart/teleport-kube-agent/tests/clusterrolebinding_test.yaml new file mode 100644 index 0000000000000..b8f30d8cf4fc2 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/clusterrolebinding_test.yaml @@ -0,0 +1,11 @@ +suite: ClusterRoleBinding +templates: + - clusterrolebinding.yaml +tests: + - it: creates a ClusterRoleBinding + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ClusterRoleBinding + - matchSnapshot: {} diff --git a/examples/chart/teleport-kube-agent/tests/config_test.yaml b/examples/chart/teleport-kube-agent/tests/config_test.yaml new file mode 100644 index 0000000000000..b3115d6b31352 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/config_test.yaml @@ -0,0 +1,209 @@ +suite: ConfigMap +templates: + - config.yaml +tests: + - it: matches snapshot for affinity.yaml + values: + - ../.lint/affinity.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for all-v5.yaml + values: + - ../.lint/all-v5.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for all-v6.yaml + values: + - ../.lint/all-v6.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot and tests for annotations.yaml + values: + - ../.lint/annotations.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - equal: + path: metadata.annotations.kubernetes\.io/config + value: test-annotation + - equal: + path: metadata.annotations.kubernetes\.io/config-different + value: 2 + - matchSnapshot: {} + + - it: matches snapshot for aws-databases.yaml + values: + - ../.lint/aws-databases.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for backwards-compatibility.yaml + values: + - ../.lint/backwards-compatibility.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: does not generate a config for clusterrole.yaml + values: + - ../.lint/clusterrole.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for db.yaml + values: + - ../.lint/db.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for imagepullsecrets.yaml + values: + - ../.lint/imagepullsecrets.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for initcontainers.yaml + values: + - ../.lint/initcontainers.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for log-basic.yaml + values: + - ../.lint/log-basic.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for log-extra.yaml + values: + - ../.lint/log-extra.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for log-legacy.yaml + values: + - ../.lint/log-legacy.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for node-selector.yaml + values: + - ../.lint/node-selector.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for pdb.yaml + values: + - ../.lint/log-extra.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: does not generate a config for pdb.yaml + values: + - ../.lint/pdb.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for resources.yaml + values: + - ../.lint/resources.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for stateful.yaml + values: + - ../.lint/stateful.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for tolerations.yaml + values: + - ../.lint/tolerations.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} + + - it: matches snapshot for volumes.yaml + values: + - ../.lint/volumes.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - matchSnapshot: {} diff --git a/examples/chart/teleport-kube-agent/tests/deployment_test.yaml b/examples/chart/teleport-kube-agent/tests/deployment_test.yaml new file mode 100644 index 0000000000000..0e3011e5950cb --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/deployment_test.yaml @@ -0,0 +1,349 @@ +suite: Deployment +templates: + - deployment.yaml + - config.yaml +tests: + - it: creates a Deployment + values: + - ../.lint/all-v6.yaml + asserts: + - isKind: + of: Deployment + - hasDocuments: + count: 1 + + - it: sets Deployment annotations when specified + values: + - ../.lint/annotations.yaml + asserts: + - equal: + path: metadata.annotations.kubernetes\.io/deployment + value: test-annotation + - equal: + path: metadata.annotations.kubernetes\.io/deployment-different + value: 3 + - matchSnapshot: + path: spec.template.spec + + - it: sets Pod annotations when specified + values: + - ../.lint/annotations.yaml + asserts: + - equal: + path: spec.template.metadata.annotations.kubernetes\.io/pod + value: test-annotation + - equal: + path: spec.template.metadata.annotations.kubernetes\.io/pod-different + value: 4 + - matchSnapshot: + path: spec.template.spec + + - it: should have one replica when replicaCount is not set + values: + - ../.lint/backwards-compatibility.yaml + asserts: + - equal: + path: spec.replicas + value: 1 + - matchSnapshot: + path: spec.template.spec + + - it: should have multiple replicas when replicaCount is set (using .replicaCount, deprecated) + values: + - ../.lint/backwards-compatibility.yaml + set: + replicaCount: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + - matchSnapshot: + path: spec.template.spec + + - it: should have multiple replicas when replicaCount is set (using highAvailability.replicaCount) + values: + - ../.lint/backwards-compatibility.yaml + set: + highAvailability: + replicaCount: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + - matchSnapshot: + path: spec.template.spec + + - it: should set affinity when set in values + values: + - ../.lint/affinity.yaml + asserts: + - isNotNull: + path: spec.template.spec.affinity + - matchSnapshot: + path: spec.template.spec + + - it: should set required affinity when highAvailability.requireAntiAffinity is set + values: + - ../.lint/backwards-compatibility.yaml + set: + highAvailability: + replicaCount: 2 + requireAntiAffinity: true + asserts: + - isNotNull: + path: spec.template.spec.affinity + - isNotNull: + path: spec.template.spec.affinity.podAntiAffinity + - isNotNull: + path: spec.template.spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution + - isNull: + path: spec.template.spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution + - matchSnapshot: + path: spec.template.spec + + - it: should set preferred affinity when more than one replica is used + values: + - ../.lint/backwards-compatibility.yaml + set: + highAvailability: + replicaCount: 3 + asserts: + - isNotNull: + path: spec.template.spec.affinity + - isNotNull: + path: spec.template.spec.affinity.podAntiAffinity + - isNotNull: + path: spec.template.spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution + - isNull: + path: spec.template.spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution + - matchSnapshot: + path: spec.template.spec + + - it: should set tolerations when set in values + values: + - ../.lint/tolerations.yaml + asserts: + - isNotNull: + path: spec.template.spec.tolerations + - matchSnapshot: + path: spec.template.spec + + - it: should set resources when set in values + values: + - ../.lint/resources.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].resources.limits.cpu + value: 2 + - equal: + path: spec.template.spec.containers[0].resources.limits.memory + value: 4Gi + - equal: + path: spec.template.spec.containers[0].resources.requests.cpu + value: 1 + - equal: + path: spec.template.spec.containers[0].resources.requests.memory + value: 2Gi + - matchSnapshot: + path: spec.template.spec + + - it: should set SecurityContext + values: + - ../.lint/backwards-compatibility.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation + value: false + - equal: + path: spec.template.spec.containers[0].securityContext.capabilities + value: + drop: + - all + - equal: + path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem + value: true + - equal: + path: spec.template.spec.containers[0].securityContext.runAsNonRoot + value: true + - equal: + path: spec.template.spec.containers[0].securityContext.runAsUser + value: 9807 + - matchSnapshot: + path: spec.template.spec + + - it: should set image and tag correctly + values: + - ../.lint/backwards-compatibility.yaml + set: + teleportVersionOverride: 8.3.4 + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: quay.io/gravitational/teleport:8.3.4 + - matchSnapshot: + path: spec.template.spec + + - it: should mount extraVolumes and extraVolumeMounts + values: + - ../.lint/volumes.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /path/to/mount + name: my-mount + - contains: + path: spec.template.spec.volumes + content: + name: my-mount + secret: + secretName: mySecret + - matchSnapshot: + path: spec.template.spec + + - it: should set imagePullPolicy when set in values + values: + - ../.lint/backwards-compatibility.yaml + set: + imagePullPolicy: Always + asserts: + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: Always + - matchSnapshot: + path: spec.template.spec + + - it: should set environment when extraEnv set in values + set: + proxyAddr: helm-lint.example.com + authToken: sample-auth-token-dont-use-this + kubeClusterName: helm-lint.example.com + extraEnv: + - name: HTTPS_PROXY + value: "http://username:password@my.proxy.host:3128" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: HTTPS_PROXY + value: "http://username:password@my.proxy.host:3128" + - matchSnapshot: + path: spec.template.spec + + - it: should provision initContainer correctly when set in values + values: + - ../.lint/initcontainers.yaml + asserts: + - contains: + path: spec.template.spec.initContainers[0].args + content: "echo test" + - equal: + path: spec.template.spec.initContainers[0].name + value: "teleport-init" + - equal: + path: spec.template.spec.initContainers[0].image + value: "alpine" + - equal: + path: spec.template.spec.initContainers[0].resources.limits.cpu + value: 2 + - equal: + path: spec.template.spec.initContainers[0].resources.limits.memory + value: 4Gi + - equal: + path: spec.template.spec.initContainers[0].resources.requests.cpu + value: 1 + - equal: + path: spec.template.spec.initContainers[0].resources.requests.memory + value: 2Gi + - matchSnapshot: + path: spec.template.spec + + - it: should add insecureSkipProxyTLSVerify to args when set in values + values: + - ../.lint/backwards-compatibility.yaml + set: + insecureSkipProxyTLSVerify: true + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--insecure" + - matchSnapshot: + path: spec.template.spec + + - it: should expose diag port + values: + - ../.lint/backwards-compatibility.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].ports + content: + name: diag + containerPort: 3000 + protocol: TCP + - matchSnapshot: + path: spec.template.spec + + - it: should set nodeSelector if set in values + values: + - ../.lint/node-selector.yaml + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + gravitational.io/k8s-role: node + - matchSnapshot: + path: spec.template.spec + + - it: should add emptyDir for data when existingDataVolume is not set + values: + - ../.lint/backwards-compatibility.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: data + emptyDir: {} + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /var/lib/teleport + name: data + - matchSnapshot: + path: spec.template.spec + + - it: should correctly configure existingDataVolume when set + values: + - ../.lint/existing-data-volume.yaml + asserts: + - notContains: + path: spec.template.spec.volumes + content: + name: data + emptyDir: {} + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /var/lib/teleport + name: teleport-kube-agent-data + - matchSnapshot: + path: spec.template.spec + + - it: should set serviceAccountName when set in values + values: + - ../.lint/service-account-name.yaml + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: teleport-kube-agent-sa + - matchSnapshot: + path: spec.template.spec + + - it: should set default serviceAccountName when not set in values + values: + - ../.lint/backwards-compatibility.yaml + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: RELEASE-NAME + - matchSnapshot: + path: spec.template.spec diff --git a/examples/chart/teleport-kube-agent/tests/pdb_test.yaml b/examples/chart/teleport-kube-agent/tests/pdb_test.yaml new file mode 100644 index 0000000000000..b673ce8400ce3 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/pdb_test.yaml @@ -0,0 +1,13 @@ +suite: PodDisruptionBudget +templates: + - pdb.yaml +tests: + - it: should create a PDB when enabled in values (pdb.yaml) + values: + - ../.lint/pdb.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: PodDisruptionBudget + - matchSnapshot: {} diff --git a/examples/chart/teleport-kube-agent/tests/psp_test.yaml b/examples/chart/teleport-kube-agent/tests/psp_test.yaml new file mode 100644 index 0000000000000..0faad123c1dc1 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/psp_test.yaml @@ -0,0 +1,21 @@ +suite: PodSecurityPolicy +templates: + - psp.yaml +tests: + - it: creates a PodSecurityPolicy when enabled in values + set: + podSecurityPolicy: + enabled: true + asserts: + - hasDocuments: + count: 3 + - documentIndex: 0 + isKind: + of: PodSecurityPolicy + - documentIndex: 1 + isKind: + of: Role + - documentIndex: 2 + isKind: + of: RoleBinding + - matchSnapshot: {} diff --git a/examples/chart/teleport-kube-agent/tests/secret_test.yaml b/examples/chart/teleport-kube-agent/tests/secret_test.yaml new file mode 100644 index 0000000000000..d2d7963b8e213 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/secret_test.yaml @@ -0,0 +1,30 @@ +suite: Secret +templates: + - secret.yaml +tests: + - it: generates a secret when authToken is provided + set: + authToken: sample-auth-token-dont-use-this + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Secret + - equal: + path: metadata.name + value: teleport-kube-agent-join-token + - matchSnapshot: {} + + - it: generates a secret with a custom name when authToken and secretName are provided + set: + authToken: sample-auth-token-dont-use-this + secretName: some-other-secret-name + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Secret + - equal: + path: metadata.name + value: some-other-secret-name + - matchSnapshot: {} diff --git a/examples/chart/teleport-kube-agent/tests/serviceaccount_test.yaml b/examples/chart/teleport-kube-agent/tests/serviceaccount_test.yaml new file mode 100644 index 0000000000000..fe5d1ebede622 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/serviceaccount_test.yaml @@ -0,0 +1,15 @@ +suite: ServiceAccount +templates: + - serviceaccount.yaml +tests: + - it: sets ServiceAccount annotations when specified + values: + - ../.lint/annotations.yaml + asserts: + - equal: + path: metadata.annotations.kubernetes\.io/serviceaccount + value: test-annotation + - equal: + path: metadata.annotations.kubernetes\.io/serviceaccount-different + value: 5 + - matchSnapshot: {} diff --git a/examples/chart/teleport-kube-agent/tests/statefulset_test.yaml b/examples/chart/teleport-kube-agent/tests/statefulset_test.yaml new file mode 100644 index 0000000000000..6da67d49eebb7 --- /dev/null +++ b/examples/chart/teleport-kube-agent/tests/statefulset_test.yaml @@ -0,0 +1,369 @@ +suite: StatefulSet +templates: + - statefulset.yaml + - config.yaml +tests: + - it: creates a StatefulSet + values: + - ../.lint/stateful.yaml + asserts: + - isKind: + of: StatefulSet + - hasDocuments: + count: 1 + + - it: sets Pod annotations when specified + values: + - ../.lint/annotations.yaml + - ../.lint/stateful.yaml + asserts: + - equal: + path: spec.template.metadata.annotations.kubernetes\.io/pod + value: test-annotation + - equal: + path: spec.template.metadata.annotations.kubernetes\.io/pod-different + value: 4 + - matchSnapshot: + path: spec.template.spec + + - it: should have one replica when replicaCount is not set + values: + - ../.lint/stateful.yaml + asserts: + - equal: + path: spec.replicas + value: 1 + - matchSnapshot: + path: spec.template.spec + + - it: should have multiple replicas when replicaCount is set (using .replicaCount, deprecated) + values: + - ../.lint/stateful.yaml + set: + replicaCount: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + - matchSnapshot: + path: spec.template.spec + + - it: should have multiple replicas when replicaCount is set (using highAvailability.replicaCount) + values: + - ../.lint/stateful.yaml + set: + highAvailability: + replicaCount: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + - matchSnapshot: + path: spec.template.spec + + - it: should set affinity when set in values + values: + - ../.lint/stateful.yaml + - ../.lint/affinity.yaml + asserts: + - isNotNull: + path: spec.template.spec.affinity + - matchSnapshot: + path: spec.template.spec + + - it: should set required affinity when highAvailability.requireAntiAffinity is set + values: + - ../.lint/stateful.yaml + set: + highAvailability: + replicaCount: 2 + requireAntiAffinity: true + asserts: + - isNotNull: + path: spec.template.spec.affinity + - isNotNull: + path: spec.template.spec.affinity.podAntiAffinity + - isNotNull: + path: spec.template.spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution + - isNull: + path: spec.template.spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution + - matchSnapshot: + path: spec.template.spec + + - it: should set preferred affinity when more than one replica is used + values: + - ../.lint/stateful.yaml + set: + highAvailability: + replicaCount: 3 + asserts: + - isNotNull: + path: spec.template.spec.affinity + - isNotNull: + path: spec.template.spec.affinity.podAntiAffinity + - isNotNull: + path: spec.template.spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution + - isNull: + path: spec.template.spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution + - matchSnapshot: + path: spec.template.spec + + - it: should set tolerations when set in values + values: + - ../.lint/stateful.yaml + - ../.lint/tolerations.yaml + asserts: + - isNotNull: + path: spec.template.spec.tolerations + - matchSnapshot: + path: spec.template.spec + + - it: should set resources when set in values + values: + - ../.lint/stateful.yaml + - ../.lint/resources.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].resources.limits.cpu + value: 2 + - equal: + path: spec.template.spec.containers[0].resources.limits.memory + value: 4Gi + - equal: + path: spec.template.spec.containers[0].resources.requests.cpu + value: 1 + - equal: + path: spec.template.spec.containers[0].resources.requests.memory + value: 2Gi + - matchSnapshot: + path: spec.template.spec + + - it: should set SecurityContext + values: + - ../.lint/stateful.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation + value: false + - equal: + path: spec.template.spec.containers[0].securityContext.capabilities + value: + drop: + - all + - equal: + path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem + value: true + - equal: + path: spec.template.spec.containers[0].securityContext.runAsNonRoot + value: true + - equal: + path: spec.template.spec.containers[0].securityContext.runAsUser + value: 9807 + - matchSnapshot: + path: spec.template.spec + + - it: should set image and tag correctly + values: + - ../.lint/stateful.yaml + set: + teleportVersionOverride: 8.3.4 + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: quay.io/gravitational/teleport:8.3.4 + - matchSnapshot: + path: spec.template.spec + + - it: should mount extraVolumes and extraVolumeMounts + values: + - ../.lint/stateful.yaml + - ../.lint/volumes.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /path/to/mount + name: my-mount + - contains: + path: spec.template.spec.volumes + content: + name: my-mount + secret: + secretName: mySecret + - matchSnapshot: + path: spec.template.spec + + - it: should set imagePullPolicy when set in values + values: + - ../.lint/stateful.yaml + set: + imagePullPolicy: Always + asserts: + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: Always + - matchSnapshot: + path: spec.template.spec + + - it: should set environment when extraEnv set in values + values: + - ../.lint/stateful.yaml + set: + extraEnv: + - name: HTTPS_PROXY + value: "http://username:password@my.proxy.host:3128" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: HTTPS_PROXY + value: "http://username:password@my.proxy.host:3128" + - matchSnapshot: + path: spec.template.spec + + - it: should provision initContainer correctly when set in values + values: + - ../.lint/stateful.yaml + - ../.lint/initcontainers.yaml + asserts: + - contains: + path: spec.template.spec.initContainers[0].args + content: "echo test" + - equal: + path: spec.template.spec.initContainers[0].name + value: "teleport-init" + - equal: + path: spec.template.spec.initContainers[0].image + value: "alpine" + - equal: + path: spec.template.spec.initContainers[0].resources.limits.cpu + value: 2 + - equal: + path: spec.template.spec.initContainers[0].resources.limits.memory + value: 4Gi + - equal: + path: spec.template.spec.initContainers[0].resources.requests.cpu + value: 1 + - equal: + path: spec.template.spec.initContainers[0].resources.requests.memory + value: 2Gi + - matchSnapshot: + path: spec.template.spec + + - it: should add insecureSkipProxyTLSVerify to args when set in values + values: + - ../.lint/stateful.yaml + set: + insecureSkipProxyTLSVerify: true + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--insecure" + - matchSnapshot: + path: spec.template.spec + + - it: should expose diag port + values: + - ../.lint/stateful.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].ports + content: + name: diag + containerPort: 3000 + protocol: TCP + - matchSnapshot: + path: spec.template.spec + + - it: should set nodeSelector if set in values + values: + - ../.lint/stateful.yaml + - ../.lint/node-selector.yaml + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + gravitational.io/k8s-role: node + - matchSnapshot: + path: spec.template.spec + + - it: should not add emptyDir for data when using StatefulSet + values: + - ../.lint/stateful.yaml + asserts: + - notContains: + path: spec.template.spec.volumes + content: + name: data + emptyDir: {} + - matchSnapshot: + path: spec.template.spec + + - it: should add volumeMount for data volume when using StatefulSet + values: + - ../.lint/stateful.yaml + asserts: + - notContains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: data + mountPath: RELEASE-NAME-teleport-data + - matchSnapshot: + path: spec.template.spec + + - it: should add volumeClaimTemplate for data volume when using StatefulSet + values: + - ../.lint/stateful.yaml + asserts: + - isNotNull: + path: spec.volumeClaimTemplates[0].spec + - matchSnapshot: + path: spec.template.spec + + - it: should set storage.storageClassName when set in values + values: + - ../.lint/stateful.yaml + set: + storage: + storageClassName: helm-lint-storage-class + asserts: + - equal: + path: spec.volumeClaimTemplates[0].spec.storageClassName + value: helm-lint-storage-class + - matchSnapshot: + path: spec.template.spec + + - it: should set storage.requests when set in values + values: + - ../.lint/stateful.yaml + set: + storage: + requests: 256Mi + asserts: + - equal: + path: spec.volumeClaimTemplates[0].spec.resources.requests.storage + value: 256Mi + - matchSnapshot: + path: spec.template.spec + + - it: should set serviceAccountName when set in values + values: + - ../.lint/stateful.yaml + - ../.lint/service-account-name.yaml + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: teleport-kube-agent-sa + - matchSnapshot: + path: spec.template.spec + + - it: should set default serviceAccountName when not set in values + values: + - ../.lint/stateful.yaml + - ../.lint/backwards-compatibility.yaml + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: RELEASE-NAME + - matchSnapshot: + path: spec.template.spec diff --git a/examples/chart/teleport-kube-agent/values.schema.json b/examples/chart/teleport-kube-agent/values.schema.json index 46aa080fa3e49..acda282522d5a 100644 --- a/examples/chart/teleport-kube-agent/values.schema.json +++ b/examples/chart/teleport-kube-agent/values.schema.json @@ -284,6 +284,16 @@ } } }, + "extraArgs": { + "$id": "#/properties/extraArgs", + "type": "array", + "default": [] + }, + "extraEnv": { + "$id": "#/properties/extraEnv", + "type": "array", + "default": [] + }, "extraVolumes": { "$id": "#/properties/extraVolumes", "type": "array", diff --git a/examples/chart/teleport-kube-agent/values.yaml b/examples/chart/teleport-kube-agent/values.yaml index 069258679072b..2e404f0a33ded 100644 --- a/examples/chart/teleport-kube-agent/values.yaml +++ b/examples/chart/teleport-kube-agent/values.yaml @@ -51,6 +51,8 @@ awsDatabases: [] # - name: aurora # uri: "postgres-aurora-instance-1.xxx.us-east-1.rds.amazonaws.com:5432" # protocol: "postgres" +# static_labels: +# env: "prod" databases: [] ################################################################ @@ -65,6 +67,7 @@ insecureSkipProxyTLSVerify: false # If set, will use an existing volume mounted via extraVolumes # as the Teleport data directory. +# If anything is set under the "storage" key, this will be ignored. existingDataVolume: "" # If true, create & use Pod Security Policy resources @@ -176,6 +179,12 @@ annotations: # Annotations for the ServiceAccount object serviceAccount: {} +# Extra arguments to pass to 'teleport start' for the main Teleport pod +extraArgs: [] + +# Extra environment to be configured on the Teleport pod +extraEnv: [] + # Extra volumes to mount into the Teleport pods # https://kubernetes.io/docs/concepts/storage/volumes/ extraVolumes: [] diff --git a/examples/chart/teleport/.helmignore b/examples/chart/teleport/.helmignore index 2a1a7a2420771..59ffd343fe67c 100644 --- a/examples/chart/teleport/.helmignore +++ b/examples/chart/teleport/.helmignore @@ -2,3 +2,4 @@ *.pem scripts pki +tests \ No newline at end of file diff --git a/examples/chart/teleport/README.md b/examples/chart/teleport/README.md index d5d3bad3b9eec..9c3273927bf9d 100644 --- a/examples/chart/teleport/README.md +++ b/examples/chart/teleport/README.md @@ -1,3 +1,8 @@ +# WARNING + +This chart is **deprecated** and no longer actively maintained or supported by Teleport. +We recommend the use of the [teleport-cluster](../teleport-cluster/README.md) chart instead. + # Teleport [Gravitational Teleport](https://github.com/gravitational/teleport) is a modern SSH/Kubernetes API proxy server for remotely accessing clusters of Linux containers and servers via SSH, HTTPS, or Kubernetes API. Community and Enterprise versions of Teleport are available. You can start with the Community edition with this Chart and in the future update to an Enterprise version for the same deployment. diff --git a/examples/resources/plugins/teleport-jira-cloud.toml b/examples/resources/plugins/teleport-jira-cloud.toml new file mode 100644 index 0000000000000..d8b6cde9b242c --- /dev/null +++ b/examples/resources/plugins/teleport-jira-cloud.toml @@ -0,0 +1,20 @@ +# example jira plugin configuration TOML file +[teleport] +auth_server = "myinstance.teleport.sh:443" # Teleport Cloud proxy HTTPS address +identity = "/var/lib/teleport/plugins/jira/auth.pem" # Teleport identity file location + +[jira] +url = "https://example.com/jira" # JIRA URL. For JIRA Cloud, https://[my-jira].atlassian.net +username = "bot@example.com" # JIRA username +api_token = "token" # JIRA API Basic Auth token +project = "MYPROJ" # JIRA Project key + +[http] +# listen_addr = ":8081" # Network address in format [addr]:port on which webhook server listens, e.g. 0.0.0.0:443 +# public_addr = "example.com" # URL on which webhook server is accessible externally, e.g. [https://]teleport-jira.example.com +https_key_file = "/var/lib/teleport/plugins/jira/server.key" # TLS private key +https_cert_file = "/var/lib/teleport/plugins/jira/server.crt" # TLS certificate + +[log] +output = "stderr" # Logger output. Could be "stdout", "stderr" or "/var/lib/teleport/jira.log" +severity = "INFO" # Logger severity. Could be "INFO", "ERROR", "DEBUG" or "WARN". \ No newline at end of file diff --git a/examples/resources/plugins/teleport-jira.toml b/examples/resources/plugins/teleport-jira-self-hosted.toml similarity index 100% rename from examples/resources/plugins/teleport-jira.toml rename to examples/resources/plugins/teleport-jira-self-hosted.toml diff --git a/examples/resources/plugins/teleport-mattermost-cloud.toml b/examples/resources/plugins/teleport-mattermost-cloud.toml new file mode 100644 index 0000000000000..e341be450afd5 --- /dev/null +++ b/examples/resources/plugins/teleport-mattermost-cloud.toml @@ -0,0 +1,21 @@ +# example mattermost configuration TOML file +[teleport] +auth_server = "myinstance.teleport.sh:443" # Teleport Cloud proxy HTTPS address +identity = "/var/lib/teleport/plugins/mattermost/auth.pem" # Identity file path + +[mattermost] +url = "https://mattermost.example.com" # Mattermost Server URL +team = "team-name" # Mattermsot team in which the channel resides. +channel = "channel-name" # Mattermost Channel name to post requests to +token = "api-token" # Mattermost Bot OAuth token +secret = "signing-secret-value" # Mattermost API signing Secret + +[http] +public_addr = "example.com" # URL on which callback server is accessible externally, e.g. [https://]teleport-mattermost.example.com +# listen_addr = ":8081" # Network address in format [addr]:port on which callback server listens, e.g. 0.0.0.0:443 +https_key_file = "/var/lib/teleport/plugins/mattermost/server.key" # TLS private key +https_cert_file = "/var/lib/teleport/plugins/mattermost/server.crt" # TLS certificate + +[log] +output = "stderr" # Logger output. Could be "stdout", "stderr" or "/var/lib/teleport/mattermost.log" +severity = "INFO" # Logger severity. Could be "INFO", "ERROR", "DEBUG" or "WARN". diff --git a/examples/resources/plugins/teleport-mattermost.toml b/examples/resources/plugins/teleport-mattermost-self.toml similarity index 100% rename from examples/resources/plugins/teleport-mattermost.toml rename to examples/resources/plugins/teleport-mattermost-self.toml diff --git a/examples/resources/plugins/teleport-pagerduty-cloud.toml b/examples/resources/plugins/teleport-pagerduty-cloud.toml new file mode 100644 index 0000000000000..1331624d4140a --- /dev/null +++ b/examples/resources/plugins/teleport-pagerduty-cloud.toml @@ -0,0 +1,26 @@ +# example teleport-pagerduty configuration TOML file +[teleport] +auth_server = "myinstance.teleport.sh:443" # Teleport Cloud proxy HTTPS address +identity = "/var/lib/teleport/plugins/pagerduty/auth.pem" # Identity file path + +[pagerduty] +api_key = "key" # PagerDuty API Key +user_email = "me@example.com" # PagerDuty bot user email (Could be admin email) +service_id = "PIJ90N7" # PagerDuty service id + +[http] +public_addr = "example.com" # URL on which callback server is accessible externally, e.g. [https://]teleport-pagerduty.example.com +# listen_addr = ":8081" # Network address in format [addr]:port on which callback server listens, e.g. 0.0.0.0:443 +https_key_file = "/var/lib/teleport/plugins/pagerduty/server.key" # TLS private key +https_cert_file = "/var/lib/teleport/plugins/pagerduty/server.crt" # TLS certificate + +[http.tls] +verify_client_cert = true # The preferred way to authenticate webhooks on Pagerduty. See more: https://developer.pagerduty.com/docs/webhooks/webhooks-mutual-tls + +[http.basic_auth] +user = "user" +password = "password" # If you prefer to use basic auth for Pagerduty Webhooks authentication, use this section to store user and password + +[log] +output = "stderr" # Logger output. Could be "stdout", "stderr" or "/var/lib/teleport/pagerduty.log" +severity = "INFO" # Logger severity. Could be "INFO", "ERROR", "DEBUG" or "WARN". \ No newline at end of file diff --git a/examples/resources/plugins/teleport-pagerduty.toml b/examples/resources/plugins/teleport-pagerduty-self.toml similarity index 100% rename from examples/resources/plugins/teleport-pagerduty.toml rename to examples/resources/plugins/teleport-pagerduty-self.toml diff --git a/examples/resources/terraform/terraform-user-role-cloud.tf b/examples/resources/terraform/terraform-user-role-cloud.tf new file mode 100644 index 0000000000000..cec5b0e38ce74 --- /dev/null +++ b/examples/resources/terraform/terraform-user-role-cloud.tf @@ -0,0 +1,79 @@ +terraform { + required_providers { + teleport = { + version = ">= (=teleport.version=)" + source = "gravitational.com/teleport/teleport" + } + } +} + +provider "teleport" { + # Update addr to point to your Teleport Cloud tenant URL + addr = "mytenant.teleport.sh" + identity_file_path = "terraform-identity" +} + +resource "teleport_role" "terraform-test" { + metadata { + name = "terraform-test" + description = "Terraform test role" + labels = { + example = "yes" + } + } + + spec { + options { + forward_agent = false + max_session_ttl = "30m" + port_forwarding = false + client_idle_timeout = "1h" + disconnect_expired_cert = true + permit_x11_forwarding = false + request_access = "denied" + } + + allow { + logins = ["this-user-does-not-exist"] + + rules { + resources = ["user", "role"] + verbs = ["list"] + } + + request { + roles = ["example"] + claims_to_roles { + claim = "example" + value = "example" + roles = ["example"] + } + } + + node_labels { + key = "example" + value = ["yes"] + } + } + + deny { + logins = ["anonymous"] + } + } +} + +resource "teleport_user" "terraform-test" { + metadata { + name = "terraform-test" + description = "Test terraform user" + expires = "2022-10-12T07:20:50.52Z" + + labels = { + test = "true" + } + } + + spec { + roles = ["terraform-test"] + } +} diff --git a/examples/resources/terraform/terraform-user-role.tf b/examples/resources/terraform/terraform-user-role-self-hosted.tf similarity index 93% rename from examples/resources/terraform/terraform-user-role.tf rename to examples/resources/terraform/terraform-user-role-self-hosted.tf index ac8204e31e550..893f977ca352d 100644 --- a/examples/resources/terraform/terraform-user-role.tf +++ b/examples/resources/terraform/terraform-user-role-self-hosted.tf @@ -11,7 +11,9 @@ provider "teleport" { # Update addr to point to Teleport Auth/Proxy # addr = "auth.example.com:3025" addr = "proxy.example.com:443" - identity_file_path = "terraform-identity" + cert_path = "auth.crt" + key_path = "auth.key" + root_ca_path = "auth.cas" } resource "teleport_role" "terraform-test" { diff --git a/go.mod b/go.mod index 7958409373ff5..12bffeab8bc36 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/alicebob/miniredis/v2 v2.17.0 github.com/aquasecurity/libbpfgo v0.1.0 github.com/armon/go-radix v1.0.0 - github.com/aws/aws-sdk-go v1.37.17 + github.com/aws/aws-sdk-go v1.43.15 github.com/aws/aws-sdk-go-v2 v1.9.0 github.com/aws/aws-sdk-go-v2/config v1.8.0 github.com/aws/aws-sdk-go-v2/credentials v1.4.0 @@ -62,9 +62,11 @@ require ( github.com/jcmturner/gokrb5/v8 v8.4.2 github.com/johannesboyne/gofakes3 v0.0.0-20210217223559-02ffa763be97 github.com/jonboulle/clockwork v0.2.2 + github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 github.com/json-iterator/go v1.1.12 github.com/julienschmidt/httprouter v1.3.0 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 + github.com/keys-pub/go-libfido2 v1.5.3-0.20220306005615-8ab03fb1ec27 github.com/kr/pretty v0.3.0 github.com/kr/pty v1.1.8 github.com/kylelemons/godebug v1.1.0 @@ -189,6 +191,7 @@ require ( github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d // indirect github.com/klauspost/compress v1.9.5 // indirect github.com/kr/text v0.2.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect @@ -265,6 +268,8 @@ replace ( github.com/go-redis/redis/v8 => github.com/gravitational/redis/v8 v8.11.5-0.20220211010318-7af711b76a91 github.com/gogo/protobuf => github.com/gravitational/protobuf v1.3.2-0.20201123192827-2b9fcfaffcbf github.com/gravitational/teleport/api => ./api - github.com/siddontang/go-mysql v1.1.0 => github.com/gravitational/go-mysql v1.1.1-teleport.1 + github.com/russellhaering/gosaml2 => github.com/gravitational/gosaml2 v0.0.0-20220318224559-f06932032ae2 + github.com/siddontang/go-mysql v1.1.0 => github.com/gravitational/go-mysql v1.1.1-teleport.2 github.com/sirupsen/logrus => github.com/gravitational/logrus v1.4.4-0.20210817004754-047e20245621 + github.com/vulcand/predicate => github.com/gravitational/predicate v1.2.1 ) diff --git a/go.sum b/go.sum index 8787503ddf734..73530e6c2d1d3 100644 --- a/go.sum +++ b/go.sum @@ -128,8 +128,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= -github.com/aws/aws-sdk-go v1.37.17 h1:Ga33kM38f58l7X+Z2B6JNdz9dFqxjR8AXHBbK3bXYc0= -github.com/aws/aws-sdk-go v1.37.17/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.43.15 h1:zAOUdqgNgJrkivRZi93NTjPNvuIQ5EcqNHSk0A1jrk8= +github.com/aws/aws-sdk-go v1.43.15/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v1.9.0 h1:+S+dSqQCN3MSU5vJRu1HqHrq00cJn6heIMU7X9hcsoo= github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= github.com/aws/aws-sdk-go-v2/config v1.8.0 h1:O8EMFBOl6tue5gdJJV6U3Ikyl3lqgx6WrulCYrcy2SQ= @@ -441,23 +441,25 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gravitational/configure v0.0.0-20180808141939-c3428bd84c23 h1:havbccuFO5fRj0O67oHXI7doShLig3rSIXfMrd/UnkA= github.com/gravitational/configure v0.0.0-20180808141939-c3428bd84c23/go.mod h1:XL9nebvlfNVvRzRPWdDcWootcyA0l7THiH/A+W1233g= github.com/gravitational/form v0.0.0-20151109031454-c4048f792f70 h1:To76nCJtM3DI0mdq3nGLzXqTV1wNOJByxv01+u9/BxM= github.com/gravitational/form v0.0.0-20151109031454-c4048f792f70/go.mod h1:88hFR45MpUd23d2vNWE/dYtesU50jKsbz0I9kH7UaBY= github.com/gravitational/go-mssqldb v0.11.1-0.20220202000043-bec708e9bfd0 h1:DC+S+j/tBs/0MnQCC5j7GWWbMGcR3ca5v75ksAU1LJM= github.com/gravitational/go-mssqldb v0.11.1-0.20220202000043-bec708e9bfd0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= -github.com/gravitational/go-mysql v1.1.1-teleport.1 h1:062V8u0juCyUvpYMdkYch8JDDw7wf5rdhKaIfhnojDg= -github.com/gravitational/go-mysql v1.1.1-teleport.1/go.mod h1:re0JQZ1Cy5dVlIDGq0YksfDIla/GRZlxqOoC0XPSSGE= +github.com/gravitational/go-mysql v1.1.1-teleport.2 h1:XZ36BZ7BgslA5ZCyCHjpc1wilFITThIH7cLcbLWKWzM= +github.com/gravitational/go-mysql v1.1.1-teleport.2/go.mod h1:re0JQZ1Cy5dVlIDGq0YksfDIla/GRZlxqOoC0XPSSGE= github.com/gravitational/go-oidc v0.0.5 h1:kxsCknoOZ+KqIAoYLLdHuQcvcc+SrQlnT7xxIM8oo6o= github.com/gravitational/go-oidc v0.0.5/go.mod h1:SevmOUNdOB0aD9BAIgjptZ6oHkKxMZZgA70nwPfgU/w= +github.com/gravitational/gosaml2 v0.0.0-20220318224559-f06932032ae2 h1:8z1D1fehTDV20wkiGX+JTnlevvEUeVEh4LCygvOrFBs= +github.com/gravitational/gosaml2 v0.0.0-20220318224559-f06932032ae2/go.mod h1:PiLt5KX4EMjlMIq3WLRR/xb5yqhiwtQhGr8wmU0b08M= github.com/gravitational/kingpin v2.1.11-0.20190130013101-742f2714c145+incompatible h1:CfyZl3nyo9K5lLqOmqvl9/IElY1UCnOWKZiQxJ8HKdA= github.com/gravitational/kingpin v2.1.11-0.20190130013101-742f2714c145+incompatible/go.mod h1:LWxG30M3FcrjhOn3T4zz7JmBoQJ45MWZmOXgy9Ganoc= github.com/gravitational/license v0.0.0-20210218173955-6d8fb49b117a h1:PN5vAN1ZA0zqdpM6wNdx6+bkdlQ5fImd75oaIHSbOhY= @@ -466,6 +468,8 @@ github.com/gravitational/logrus v1.4.4-0.20210817004754-047e20245621 h1:ivU//THq github.com/gravitational/logrus v1.4.4-0.20210817004754-047e20245621/go.mod h1:IIxugQsS57BiOTe+8zDv3sfnvM2BQ3smcF1xJdj3Has= github.com/gravitational/oxy v0.0.0-20211213172937-a1ba0900a4c9 h1:7NyppZS8QFt28nn2QjDI44vDTJs0kTRdUS7po/AxWOY= github.com/gravitational/oxy v0.0.0-20211213172937-a1ba0900a4c9/go.mod h1:ESOxlf8BB2yG3zJ0SfZe9U6wpYu3YF3znxIICg73FYA= +github.com/gravitational/predicate v1.2.1 h1:CXgXyZ90F2x4VHzOab1bBsBlpY2l+MGvb5RX3avrJ5U= +github.com/gravitational/predicate v1.2.1/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA= github.com/gravitational/protobuf v1.3.2-0.20201123192827-2b9fcfaffcbf h1:MQ4e8XcxvZTeuOmRl7yE519vcWc2h/lyvYzsvt41cdY= github.com/gravitational/protobuf v1.3.2-0.20201123192827-2b9fcfaffcbf/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gravitational/redis/v8 v8.11.5-0.20220211010318-7af711b76a91 h1:LWtt0fv7KDZt6ykJGBXd/04sONaHIMXFzHvmUAqhG8c= @@ -497,9 +501,9 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= @@ -578,6 +582,10 @@ github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9q github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 h1:hgVxRoDDPtQE68PT4LFvNlPz2nBKd3OMlGKIQ69OmR4= +github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531/go.mod h1:fqTUQpVYBvhCNIsMXGl2GE9q6z94DIP6NtFKXCSTVbg= +github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d h1:J8tJzRyiddAFF65YVgxli+TyWBi0f79Sld6rJP6CBcY= +github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d/go.mod h1:b+Q3v8Yrg5o15d71PSUraUzYb+jWl6wQMSBXSGS/hv0= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -596,6 +604,8 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALr github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ= +github.com/keys-pub/go-libfido2 v1.5.3-0.20220306005615-8ab03fb1ec27 h1:10nfvqVK4/KINnLT8bDICrRnfguTJ300dNGpW8D2bQo= +github.com/keys-pub/go-libfido2 v1.5.3-0.20220306005615-8ab03fb1ec27/go.mod h1:P0V19qHwJNY0htZwZDe9Ilvs/nokGhdFX7faKFyZ6+U= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= @@ -788,8 +798,6 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/russellhaering/gosaml2 v0.6.1-0.20210916051624-757d23f1bc28 h1:659ZmS9riGgajUnT9ym74yQSug2KZyvVHi3EmIqASnQ= -github.com/russellhaering/gosaml2 v0.6.1-0.20210916051624-757d23f1bc28/go.mod h1:PiLt5KX4EMjlMIq3WLRR/xb5yqhiwtQhGr8wmU0b08M= github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= @@ -861,8 +869,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/tstranex/u2f v0.0.0-20160508205855-eb799ce68da4 h1:aR+lGR8m0zBjvDlHkHOCmdsk79ipIPeiP75GqUlywKM= github.com/tstranex/u2f v0.0.0-20160508205855-eb799ce68da4/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50= -github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= @@ -956,8 +962,8 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8 h1:kACShD3qhmr/3rLmg1yXyt+N4HcwutKyPRB93s54TIU= golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= diff --git a/integration/app_integration_test.go b/integration/app_integration_test.go index 8ec91f3866f8a..b43c52ef5c396 100644 --- a/integration/app_integration_test.go +++ b/integration/app_integration_test.go @@ -635,18 +635,53 @@ func TestAppAuditEvents(t *testing.T) { func TestAppServersHA(t *testing.T) { testCases := map[string]struct { - publicAddr func(pack *pack) string - makeRequest func(pack *pack, inCookie string) (status int, err error) + packInfo func(pack *pack) (cluterName, publicAddr string, appServers []*service.TeleportProcess) + startAppServers func(pack *pack, count int) []*service.TeleportProcess + makeRequest func(pack *pack, inCookie string) (status int, err error) }{ - "HTTPApp": { - publicAddr: func(pack *pack) string { return pack.rootAppPublicAddr }, + "RootHTTPApp": { + packInfo: func(pack *pack) (string, string, []*service.TeleportProcess) { + return pack.rootAppClusterName, pack.rootAppPublicAddr, pack.rootAppServers + }, + startAppServers: func(pack *pack, count int) []*service.TeleportProcess { + return pack.startRootAppServers(t, count, []service.App{}) + }, + makeRequest: func(pack *pack, inCookie string) (int, error) { + status, _, err := pack.makeRequest(inCookie, http.MethodGet, "/") + return status, err + }, + }, + "RootWebSocketApp": { + packInfo: func(pack *pack) (string, string, []*service.TeleportProcess) { + return pack.rootAppClusterName, pack.rootWSPublicAddr, pack.rootAppServers + }, + startAppServers: func(pack *pack, count int) []*service.TeleportProcess { + return pack.startRootAppServers(t, count, []service.App{}) + }, + makeRequest: func(pack *pack, inCookie string) (int, error) { + _, err := pack.makeWebsocketRequest(inCookie, "/") + return 0, err + }, + }, + "LeafHTTPApp": { + packInfo: func(pack *pack) (string, string, []*service.TeleportProcess) { + return pack.leafAppClusterName, pack.leafAppPublicAddr, pack.leafAppServers + }, + startAppServers: func(pack *pack, count int) []*service.TeleportProcess { + return pack.startLeafAppServers(t, count, []service.App{}) + }, makeRequest: func(pack *pack, inCookie string) (int, error) { status, _, err := pack.makeRequest(inCookie, http.MethodGet, "/") return status, err }, }, - "WebSocketApp": { - publicAddr: func(pack *pack) string { return pack.rootWSPublicAddr }, + "LeafWebSocketApp": { + packInfo: func(pack *pack) (string, string, []*service.TeleportProcess) { + return pack.leafAppClusterName, pack.leafWSPublicAddr, pack.leafAppServers + }, + startAppServers: func(pack *pack, count int) []*service.TeleportProcess { + return pack.startLeafAppServers(t, count, []service.App{}) + }, makeRequest: func(pack *pack, inCookie string) (int, error) { _, err := pack.makeWebsocketRequest(inCookie, "/") return 0, err @@ -678,18 +713,19 @@ func TestAppServersHA(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { pack := setupWithOptions(t, appTestOptions{rootAppServersCount: 3}) - inCookie := pack.createAppSession(t, test.publicAddr(pack), pack.rootAppClusterName) + clusterName, publicAddr, appServers := test.packInfo(pack) + inCookie := pack.createAppSession(t, publicAddr, clusterName) status, err := test.makeRequest(pack, inCookie) responseWithoutError(t, status, err) // Stop all root app servers. - for i, appServer := range pack.rootAppServers { + for i, appServer := range appServers { appServer.Close() // issue a request right after a server is gone. status, err = test.makeRequest(pack, inCookie) - if i == len(pack.rootAppServers)-1 { + if i == len(appServers)-1 { // fails only when the last one is closed. responseWithError(t, status, err) } else { @@ -699,13 +735,13 @@ func TestAppServersHA(t *testing.T) { } } - servers := pack.startRootAppServers(t, 3, []service.App{}) + servers := test.startAppServers(pack, 3) status, err = test.makeRequest(pack, inCookie) responseWithoutError(t, status, err) // Start an additional app server and stop all current running // ones. - pack.startRootAppServers(t, 1, []service.App{}) + test.startAppServers(pack, 1) for _, appServer := range servers { appServer.Close() @@ -756,8 +792,8 @@ type pack struct { jwtAppClusterName string jwtAppURI string - leafCluster *TeleInstance - leafAppServer *service.TeleportProcess + leafCluster *TeleInstance + leafAppServers []*service.TeleportProcess leafAppName string leafAppPublicAddr string @@ -794,6 +830,7 @@ type appTestOptions struct { rootClusterPorts *InstancePorts leafClusterPorts *InstancePorts rootAppServersCount int + leafAppServersCount int rootConfig func(config *service.Config) leafConfig func(config *service.Config) @@ -1023,42 +1060,12 @@ func setupWithOptions(t *testing.T, opts appTestOptions) *pack { } p.rootAppServers = p.startRootAppServers(t, rootAppServersCount, opts.extraRootApps) - laConf := service.MakeDefaultConfig() - laConf.Console = nil - laConf.Log = log - laConf.DataDir = t.TempDir() - t.Cleanup(func() { os.RemoveAll(laConf.DataDir) }) - laConf.Token = "static-token-value" - laConf.AuthServers = []utils.NetAddr{ - { - AddrNetwork: "tcp", - Addr: net.JoinHostPort(Loopback, p.leafCluster.GetPortWeb()), - }, + // At least one leafAppServer should start during the setup + leafAppServersCount := 1 + if opts.leafAppServersCount > 0 { + leafAppServersCount = opts.leafAppServersCount } - laConf.Auth.Enabled = false - laConf.Proxy.Enabled = false - laConf.SSH.Enabled = false - laConf.Apps.Enabled = true - laConf.Apps.Apps = append([]service.App{ - { - Name: p.leafAppName, - URI: leafServer.URL, - PublicAddr: p.leafAppPublicAddr, - }, - { - Name: p.leafWSAppName, - URI: leafWSServer.URL, - PublicAddr: p.leafWSPublicAddr, - }, - { - Name: p.leafWSSAppName, - URI: leafWSSServer.URL, - PublicAddr: p.leafWSSPublicAddr, - }, - }, opts.extraLeafApps...) - p.leafAppServer, err = p.leafCluster.StartApp(laConf) - require.NoError(t, err) - t.Cleanup(func() { p.leafAppServer.Close() }) + p.leafAppServers = p.startLeafAppServers(t, leafAppServersCount, opts.extraLeafApps) // Create user for tests. p.initUser(t, opts) @@ -1247,7 +1254,7 @@ func (p *pack) ensureAuditEvent(t *testing.T, eventType string, checkEvent func( // initCertPool initializes root cluster CA pool. func (p *pack) initCertPool(t *testing.T) { authClient := p.rootCluster.GetSiteAPI(p.rootCluster.Secrets.SiteName) - ca, err := authClient.GetCertAuthority(types.CertAuthID{ + ca, err := authClient.GetCertAuthority(context.Background(), types.CertAuthID{ Type: types.HostCA, DomainName: p.rootCluster.Secrets.SiteName, }, false) @@ -1511,6 +1518,55 @@ func (p *pack) startRootAppServers(t *testing.T, count int, extraApps []service. return servers } +func (p *pack) startLeafAppServers(t *testing.T, count int, extraApps []service.App) []*service.TeleportProcess { + log := utils.NewLoggerForTests() + servers := make([]*service.TeleportProcess, count) + + for i := 0; i < count; i++ { + laConf := service.MakeDefaultConfig() + laConf.Console = nil + laConf.Log = log + laConf.DataDir = t.TempDir() + t.Cleanup(func() { os.RemoveAll(laConf.DataDir) }) + laConf.Token = "static-token-value" + laConf.AuthServers = []utils.NetAddr{ + { + AddrNetwork: "tcp", + Addr: net.JoinHostPort(Loopback, p.leafCluster.GetPortWeb()), + }, + } + laConf.Auth.Enabled = false + laConf.Proxy.Enabled = false + laConf.SSH.Enabled = false + laConf.Apps.Enabled = true + laConf.Apps.Apps = append([]service.App{ + { + Name: p.leafAppName, + URI: p.leafAppURI, + PublicAddr: p.leafAppPublicAddr, + }, + { + Name: p.leafWSAppName, + URI: p.leafWSAppURI, + PublicAddr: p.leafWSPublicAddr, + }, + { + Name: p.leafWSSAppName, + URI: p.leafWSSAppURI, + PublicAddr: p.leafWSSPublicAddr, + }, + }, extraApps...) + + appServer, err := p.leafCluster.StartApp(laConf) + require.NoError(t, err) + t.Cleanup(func() { appServer.Close() }) + + servers[i] = appServer + } + + return servers +} + var forwardedHeaderNames = []string{ teleport.AppJWTHeader, teleport.AppCFHeader, diff --git a/integration/db_integration_test.go b/integration/db_integration_test.go index 0c4b6c0133ee5..65c721c7f477e 100644 --- a/integration/db_integration_test.go +++ b/integration/db_integration_test.go @@ -31,6 +31,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/service" + "github.com/gravitational/teleport/lib/srv/db" "github.com/gravitational/teleport/lib/srv/db/common" "github.com/gravitational/teleport/lib/srv/db/mongodb" "github.com/gravitational/teleport/lib/srv/db/mysql" @@ -210,6 +211,61 @@ func TestDatabaseAccessMongoRootCluster(t *testing.T) { require.NoError(t, err) } +// TestDatabaseAccessMongoConnectionCount tests if mongo service releases +// resource after a mongo client disconnect. +func TestDatabaseAccessMongoConnectionCount(t *testing.T) { + pack := setupDatabaseTest(t) + + connectMongoClient := func(t *testing.T) (serverConnectionCount int32) { + // Connect to the database service in root cluster. + client, err := mongodb.MakeTestClient(context.Background(), common.TestClientConfig{ + AuthClient: pack.root.cluster.GetSiteAPI(pack.root.cluster.Secrets.SiteName), + AuthServer: pack.root.cluster.Process.GetAuthServer(), + Address: net.JoinHostPort(Loopback, pack.root.cluster.GetPortWeb()), + Cluster: pack.root.cluster.Secrets.SiteName, + Username: pack.root.user.GetName(), + RouteToDatabase: tlsca.RouteToDatabase{ + ServiceName: pack.root.mongoService.Name, + Protocol: pack.root.mongoService.Protocol, + Username: "admin", + }, + }) + require.NoError(t, err) + + // Execute a query. + _, err = client.Database("test").Collection("test").Find(context.Background(), bson.M{}) + require.NoError(t, err) + + // Get a server connection count before disconnect. + serverConnectionCount = pack.root.mongo.GetActiveConnectionsCount() + + // Disconnect. + err = client.Disconnect(context.Background()) + require.NoError(t, err) + + return serverConnectionCount + } + + // Get connection count while the first client is connected. + initialConnectionCount := connectMongoClient(t) + + // Check if active connections count is not growing over time when new + // clients connect to the mongo server. + clientCount := 8 + for i := 0; i < clientCount; i++ { + // Note that connection count per client fluctuates between 6 and 9. + // Use InDelta to avoid flaky test. + require.InDelta(t, initialConnectionCount, connectMongoClient(t), 3) + } + + // Wait until the server reports no more connections. This usually happens + // really quick but wait a little longer just in case. + waitUntilNoConnections := func() bool { + return 0 == pack.root.mongo.GetActiveConnectionsCount() + } + require.Eventually(t, waitUntilNoConnections, 5*time.Second, 100*time.Millisecond) +} + // TestDatabaseAccessMongoLeafCluster tests a scenario where a user connects // to a Mongo database running in a leaf cluster. func TestDatabaseAccessMongoLeafCluster(t *testing.T) { @@ -387,6 +443,121 @@ func TestDatabaseAccessPostgresSeparateListener(t *testing.T) { require.NoError(t, err) } +func init() { + // Override database agents shuffle behavior to ensure they're always + // tried in the same order during tests. Used for HA tests. + db.SetShuffleFunc(db.ShuffleSort) +} + +// TestDatabaseAccessHARootCluster verifies that proxy falls back to a healthy +// database agent when multiple agents are serving the same database and one +// of them is down in a root cluster. +func TestDatabaseAccessHARootCluster(t *testing.T) { + pack := setupDatabaseTest(t) + + // Insert a database server entry not backed by an actual running agent + // to simulate a scenario when an agent is down but the resource hasn't + // expired from the backend yet. + dbServer, err := types.NewDatabaseServerV3(types.Metadata{ + Name: pack.root.postgresService.Name, + }, types.DatabaseServerSpecV3{ + Protocol: defaults.ProtocolPostgres, + URI: pack.root.postgresAddr, + // To make sure unhealthy server is always picked in tests first, make + // sure its host ID always compares as "smaller" as the tests sort + // agents. + HostID: "0000", + Hostname: "test", + }) + require.NoError(t, err) + + _, err = pack.root.cluster.Process.GetAuthServer().UpsertDatabaseServer( + context.Background(), dbServer) + require.NoError(t, err) + + // Connect to the database service in root cluster. + client, err := postgres.MakeTestClient(context.Background(), common.TestClientConfig{ + AuthClient: pack.root.cluster.GetSiteAPI(pack.root.cluster.Secrets.SiteName), + AuthServer: pack.root.cluster.Process.GetAuthServer(), + Address: net.JoinHostPort(Loopback, pack.root.cluster.GetPortWeb()), + Cluster: pack.root.cluster.Secrets.SiteName, + Username: pack.root.user.GetName(), + RouteToDatabase: tlsca.RouteToDatabase{ + ServiceName: pack.root.postgresService.Name, + Protocol: pack.root.postgresService.Protocol, + Username: "postgres", + Database: "test", + }, + }) + require.NoError(t, err) + + // Execute a query. + result, err := client.Exec(context.Background(), "select 1").ReadAll() + require.NoError(t, err) + require.Equal(t, []*pgconn.Result{postgres.TestQueryResponse}, result) + require.Equal(t, uint32(1), pack.root.postgres.QueryCount()) + require.Equal(t, uint32(0), pack.leaf.postgres.QueryCount()) + + // Disconnect. + err = client.Close(context.Background()) + require.NoError(t, err) +} + +// TestDatabaseAccessHALeafCluster verifies that proxy falls back to a healthy +// database agent when multiple agents are serving the same database and one +// of them is down in a leaf cluster. +func TestDatabaseAccessHALeafCluster(t *testing.T) { + pack := setupDatabaseTest(t) + pack.waitForLeaf(t) + + // Insert a database server entry not backed by an actual running agent + // to simulate a scenario when an agent is down but the resource hasn't + // expired from the backend yet. + dbServer, err := types.NewDatabaseServerV3(types.Metadata{ + Name: pack.leaf.postgresService.Name, + }, types.DatabaseServerSpecV3{ + Protocol: defaults.ProtocolPostgres, + URI: pack.leaf.postgresAddr, + // To make sure unhealthy server is always picked in tests first, make + // sure its host ID always compares as "smaller" as the tests sort + // agents. + HostID: "0000", + Hostname: "test", + }) + require.NoError(t, err) + + _, err = pack.leaf.cluster.Process.GetAuthServer().UpsertDatabaseServer( + context.Background(), dbServer) + require.NoError(t, err) + + // Connect to the database service in leaf cluster via root cluster. + client, err := postgres.MakeTestClient(context.Background(), common.TestClientConfig{ + AuthClient: pack.root.cluster.GetSiteAPI(pack.root.cluster.Secrets.SiteName), + AuthServer: pack.root.cluster.Process.GetAuthServer(), + Address: net.JoinHostPort(Loopback, pack.root.cluster.GetPortWeb()), // Connecting via root cluster. + Cluster: pack.leaf.cluster.Secrets.SiteName, + Username: pack.root.user.GetName(), + RouteToDatabase: tlsca.RouteToDatabase{ + ServiceName: pack.leaf.postgresService.Name, + Protocol: pack.leaf.postgresService.Protocol, + Username: "postgres", + Database: "test", + }, + }) + require.NoError(t, err) + + // Execute a query. + result, err := client.Exec(context.Background(), "select 1").ReadAll() + require.NoError(t, err) + require.Equal(t, []*pgconn.Result{postgres.TestQueryResponse}, result) + require.Equal(t, uint32(1), pack.leaf.postgres.QueryCount()) + require.Equal(t, uint32(0), pack.root.postgres.QueryCount()) + + // Disconnect. + err = client.Close(context.Background()) + require.NoError(t, err) +} + // TestDatabaseAccessMongoSeparateListener tests mongo proxy listener running on separate port. func TestDatabaseAccessMongoSeparateListener(t *testing.T) { pack := setupDatabaseTest(t, @@ -627,7 +798,7 @@ func setupDatabaseTest(t *testing.T, options ...testOptionFunc) *databasePack { p.setupUsersAndRoles(t) // Update root's certificate authority on leaf to configure role mapping. - ca, err := p.leaf.cluster.Process.GetAuthServer().GetCertAuthority(types.CertAuthID{ + ca, err := p.leaf.cluster.Process.GetAuthServer().GetCertAuthority(context.Background(), types.CertAuthID{ Type: types.UserCA, DomainName: p.root.cluster.Secrets.SiteName, }, false) diff --git a/integration/helpers.go b/integration/helpers.go index da8f2ec80980d..e7d237cc4fd6a 100644 --- a/integration/helpers.go +++ b/integration/helpers.go @@ -23,7 +23,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net" "net/http" "os" @@ -486,7 +485,7 @@ func GenerateUserCreds(req UserCredsRequest) (*UserCreds, error) { if err != nil { return nil, trace.Wrap(err) } - ca, err := a.GetCertAuthority(types.CertAuthID{ + ca, err := a.GetCertAuthority(context.Background(), types.CertAuthID{ Type: types.HostCA, DomainName: clusterName.GetClusterName(), }, false) @@ -507,7 +506,7 @@ func GenerateUserCreds(req UserCredsRequest) (*UserCreds, error) { // GenerateConfig generates instance config func (i *TeleInstance) GenerateConfig(t *testing.T, trustedSecrets []*InstanceSecrets, tconf *service.Config) (*service.Config, error) { var err error - dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName) + dataDir, err := os.MkdirTemp("", "cluster-"+i.Secrets.SiteName) if err != nil { return nil, trace.Wrap(err) } @@ -722,7 +721,7 @@ func (i *TeleInstance) StartReverseTunnelNode(tconf *service.Config) (*service.T // startNode starts a node and connects it to the cluster. func (i *TeleInstance) startNode(tconf *service.Config, authPort string) (*service.TeleportProcess, error) { - dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName) + dataDir, err := os.MkdirTemp("", "cluster-"+i.Secrets.SiteName) if err != nil { return nil, trace.Wrap(err) } @@ -776,7 +775,7 @@ func (i *TeleInstance) startNode(tconf *service.Config, authPort string) (*servi } func (i *TeleInstance) StartApp(conf *service.Config) (*service.TeleportProcess, error) { - dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName) + dataDir, err := os.MkdirTemp("", "cluster-"+i.Secrets.SiteName) if err != nil { return nil, trace.Wrap(err) } @@ -821,7 +820,7 @@ func (i *TeleInstance) StartApp(conf *service.Config) (*service.TeleportProcess, // StartDatabase starts the database access service with the provided config. func (i *TeleInstance) StartDatabase(conf *service.Config) (*service.TeleportProcess, *auth.Client, error) { - dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName) + dataDir, err := os.MkdirTemp("", "cluster-"+i.Secrets.SiteName) if err != nil { return nil, nil, trace.Wrap(err) } @@ -886,7 +885,7 @@ func (i *TeleInstance) StartDatabase(conf *service.Config) (*service.TeleportPro // StartNodeAndProxy starts a SSH node and a Proxy Server and connects it to // the cluster. func (i *TeleInstance) StartNodeAndProxy(name string, sshPort, proxyWebPort, proxySSHPort int) error { - dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName) + dataDir, err := os.MkdirTemp("", "cluster-"+i.Secrets.SiteName) if err != nil { return trace.Wrap(err) } @@ -973,7 +972,7 @@ type ProxyConfig struct { // StartProxy starts another Proxy Server and connects it to the cluster. func (i *TeleInstance) StartProxy(cfg ProxyConfig) (reversetunnel.Server, error) { - dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName+"-"+cfg.Name) + dataDir, err := os.MkdirTemp("", "cluster-"+i.Secrets.SiteName+"-"+cfg.Name) if err != nil { return nil, trace.Wrap(err) } @@ -1192,7 +1191,7 @@ func (i *TeleInstance) NewClientWithCreds(cfg ClientConfig, creds UserCreds) (tc // NewUnauthenticatedClient returns a fully configured and pre-authenticated client // (pre-authenticated with server CAs and signed session key) func (i *TeleInstance) NewUnauthenticatedClient(cfg ClientConfig) (tc *client.TeleportClient, err error) { - keyDir, err := ioutil.TempDir(i.Config.DataDir, "tsh") + keyDir, err := os.MkdirTemp(i.Config.DataDir, "tsh") if err != nil { return nil, err } @@ -1624,7 +1623,7 @@ func externalSSHCommand(o commandOptions) (*exec.Cmd, error) { // clobber your system agent. func createAgent(me *user.User, privateKeyByte []byte, certificateBytes []byte) (*teleagent.AgentServer, string, string, error) { // create a path to the unix socket - sockDir, err := ioutil.TempDir("", "int-test") + sockDir, err := os.MkdirTemp("", "int-test") if err != nil { return nil, "", "", trace.Wrap(err) } diff --git a/integration/hsm/hsm_test.go b/integration/hsm/hsm_test.go index f4695597ab9ae..d6b79b765c273 100644 --- a/integration/hsm/hsm_test.go +++ b/integration/hsm/hsm_test.go @@ -167,7 +167,7 @@ func (t *teleportService) waitForLocalAdditionalKeys(ctx context.Context) error return trace.Wrap(ctx.Err(), "timed out waiting for %s to have local additional keys", t.name) case <-time.After(250 * time.Millisecond): } - ca, err := t.process.GetAuthServer().GetCertAuthority(hostCAID, true) + ca, err := t.process.GetAuthServer().GetCertAuthority(ctx, hostCAID, true) if err != nil { return trace.Wrap(err) } @@ -357,7 +357,7 @@ func TestHSMRotation(t *testing.T) { teleportServices = append(teleportServices, proxy) log.Debug("TestHSMRotation: sending rotation request init") - err = auth1.process.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = auth1.process.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ Type: types.HostCA, TargetPhase: types.RotationPhaseInit, Mode: types.RotationModeManual, @@ -366,7 +366,7 @@ func TestHSMRotation(t *testing.T) { require.NoError(t, teleportServices.waitForPhaseChange(ctx)) log.Debug("TestHSMRotation: sending rotation request update_clients") - err = auth1.process.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = auth1.process.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ Type: types.HostCA, TargetPhase: types.RotationPhaseUpdateClients, Mode: types.RotationModeManual, @@ -375,7 +375,7 @@ func TestHSMRotation(t *testing.T) { require.NoError(t, teleportServices.waitForRestart(ctx)) log.Debug("TestHSMRotation: sending rotation request update_servers") - err = auth1.process.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = auth1.process.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ Type: types.HostCA, TargetPhase: types.RotationPhaseUpdateServers, Mode: types.RotationModeManual, @@ -384,7 +384,7 @@ func TestHSMRotation(t *testing.T) { require.NoError(t, teleportServices.waitForRestart(ctx)) log.Debug("TestHSMRotation: sending rotation request standby") - err = auth1.process.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = auth1.process.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ Type: types.HostCA, TargetPhase: types.RotationPhaseStandby, Mode: types.RotationModeManual, @@ -542,7 +542,7 @@ func TestHSMDualAuthRotation(t *testing.T) { // do a full rotation for _, stage := range stages { log.Debugf("TestHSMDualAuthRotation: Sending rotate request %s", stage.targetPhase) - require.NoError(t, auth1.process.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + require.NoError(t, auth1.process.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ Type: types.HostCA, TargetPhase: stage.targetPhase, Mode: types.RotationModeManual, @@ -582,7 +582,7 @@ func TestHSMDualAuthRotation(t *testing.T) { // Do another full rotation from the new auth server for _, stage := range stages { log.Debugf("TestHSMDualAuthRotation: Sending rotate request %s", stage.targetPhase) - require.NoError(t, auth2.process.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + require.NoError(t, auth2.process.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ Type: types.HostCA, TargetPhase: stage.targetPhase, Mode: types.RotationModeManual, @@ -697,7 +697,7 @@ func TestHSMDualAuthRotation(t *testing.T) { } for _, stage := range stages { log.Debugf("TestHSMDualAuthRotation: Sending rotate request %s", stage.targetPhase) - require.NoError(t, auth1.process.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + require.NoError(t, auth1.process.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ Type: types.HostCA, TargetPhase: stage.targetPhase, Mode: types.RotationModeManual, @@ -862,7 +862,7 @@ func TestHSMMigrate(t *testing.T) { // do a full rotation for _, stage := range stages { log.Debugf("TestHSMMigrate: Sending rotate request %s", stage.targetPhase) - require.NoError(t, auth1.process.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + require.NoError(t, auth1.process.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ Type: types.HostCA, TargetPhase: stage.targetPhase, Mode: types.RotationModeManual, @@ -890,7 +890,7 @@ func TestHSMMigrate(t *testing.T) { // do a full rotation for _, stage := range stages { log.Debugf("TestHSMMigrate: Sending rotate request %s", stage.targetPhase) - require.NoError(t, auth1.process.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + require.NoError(t, auth1.process.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ Type: types.HostCA, TargetPhase: stage.targetPhase, Mode: types.RotationModeManual, diff --git a/integration/integration_test.go b/integration/integration_test.go index 0937fb7972d1e..b41c329b1138d 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -25,7 +25,6 @@ import ( "fmt" "io" "io/fs" - "io/ioutil" "net" "net/http" "net/http/httptest" @@ -205,6 +204,7 @@ func TestIntegrations(t *testing.T) { t.Run("TwoClustersTunnel", suite.bind(testTwoClustersTunnel)) t.Run("UUIDBasedProxy", suite.bind(testUUIDBasedProxy)) t.Run("WindowChange", suite.bind(testWindowChange)) + t.Run("SSHTracker", suite.bind(testSSHTracker)) } // testAuditOn creates a live session, records a bunch of data through it @@ -607,7 +607,7 @@ func testInteroperability(t *testing.T, suite *integrationTestSuite) { // if we are looking for the output in a file, look in the file // otherwise check stdout and stderr for the expected output if tt.outFile { - bytes, err := ioutil.ReadFile(tempfile) + bytes, err := os.ReadFile(tempfile) require.NoError(t, err) require.Contains(t, string(bytes), tt.outContains) } else { @@ -789,6 +789,38 @@ func testUUIDBasedProxy(t *testing.T, suite *integrationTestSuite) { require.NoError(t, err) } +// testSSHTracker verifies that an SSH session creates a tracker for sessions. +func testSSHTracker(t *testing.T, suite *integrationTestSuite) { + ctx := context.Background() + teleport := suite.newTeleport(t, nil, true) + defer teleport.StopAll() + + site := teleport.GetSiteAPI(Site) + require.NotNil(t, site) + + personA := NewTerminal(250) + cl, err := teleport.NewClient(ClientConfig{ + Login: suite.me.Username, + Cluster: Site, + Host: Host, + }) + require.NoError(t, err) + cl.Stdout = personA + cl.Stdin = personA + personA.Type("\aecho hi\n\r") + go cl.SSH(ctx, []string{}, false) + + condition := func() bool { + // verify that the tracker was created + trackers, err := site.GetActiveSessionTrackers(ctx) + require.NoError(t, err) + return len(trackers) == 1 + } + + // wait for the tracker to be created + require.Eventually(t, condition, time.Minute, time.Millisecond*100) +} + // testInteractive covers SSH into shell and joining the same session from another client // against a standard teleport node. func testInteractiveRegular(t *testing.T, suite *integrationTestSuite) { @@ -1179,7 +1211,7 @@ func runDisconnectTest(t *testing.T, suite *integrationTestSuite, tc disconnectT teleport := suite.newTeleportInstance() username := suite.me.Username - role, err := types.NewRole("devs", types.RoleSpecV5{ + role, err := types.NewRoleV3("devs", types.RoleSpecV5{ Options: tc.options, Allow: types.RoleConditions{ Logins: []string{username}, @@ -1510,7 +1542,7 @@ func twoClustersTunnel(t *testing.T, suite *integrationTestSuite, now time.Time, // The known_hosts file should have two certificates, the way bytes.Split // works that means the output will be 3 (2 certs + 1 empty). - buffer, err := ioutil.ReadFile(keypaths.KnownHostsPath(tc.KeysDir)) + buffer, err := os.ReadFile(keypaths.KnownHostsPath(tc.KeysDir)) require.NoError(t, err) parts := bytes.Split(buffer, []byte("\n")) require.Len(t, parts, 3) @@ -1521,7 +1553,7 @@ func twoClustersTunnel(t *testing.T, suite *integrationTestSuite, now time.Time, if info.IsDir() { return nil } - buffer, err = ioutil.ReadFile(path) + buffer, err = os.ReadFile(path) require.NoError(t, err) ok := roots.AppendCertsFromPEM(buffer) require.True(t, ok) @@ -1741,7 +1773,7 @@ func testMapRoles(t *testing.T, suite *integrationTestSuite) { // main cluster has a local user and belongs to role "main-devs" mainDevs := "main-devs" - role, err := types.NewRole(mainDevs, types.RoleSpecV5{ + role, err := types.NewRoleV3(mainDevs, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, }, @@ -1769,7 +1801,7 @@ func testMapRoles(t *testing.T, suite *integrationTestSuite) { // using trusted clusters, so remote user will be allowed to assume // role specified by mapping remote role "devs" to local role "local-devs" auxDevs := "aux-devs" - role, err = types.NewRole(auxDevs, types.RoleSpecV5{ + role, err = types.NewRoleV3(auxDevs, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, }, @@ -1905,28 +1937,28 @@ func testMapRoles(t *testing.T, suite *integrationTestSuite) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cid := types.CertAuthID{Type: types.UserCA, DomainName: tt.mainClusterName} - mainUserCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(cid, true) + mainUserCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(ctx, cid, true) tt.outChkMainUserCA(t, err) if err == nil { tt.outChkMainUserCAPrivateKey(t, mainUserCAs.GetActiveKeys().SSH[0].PrivateKey) } cid = types.CertAuthID{Type: types.HostCA, DomainName: tt.mainClusterName} - mainHostCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(cid, true) + mainHostCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(ctx, cid, true) tt.outChkMainHostCA(t, err) if err == nil { tt.outChkMainHostCAPrivateKey(t, mainHostCAs.GetActiveKeys().SSH[0].PrivateKey) } cid = types.CertAuthID{Type: types.UserCA, DomainName: tt.auxClusterName} - auxUserCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(cid, true) + auxUserCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(ctx, cid, true) tt.outChkAuxUserCA(t, err) if err == nil { tt.outChkAuxUserCAPrivateKey(t, auxUserCAs.GetActiveKeys().SSH[0].PrivateKey) } cid = types.CertAuthID{Type: types.HostCA, DomainName: tt.auxClusterName} - auxHostCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(cid, true) + auxHostCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(ctx, cid, true) tt.outChkAuxHostCA(t, err) if err == nil { tt.outChkAuxHostCAPrivateKey(t, auxHostCAs.GetActiveKeys().SSH[0].PrivateKey) @@ -2051,7 +2083,7 @@ func trustedClusters(t *testing.T, suite *integrationTestSuite, test trustedClus // main cluster has a local user and belongs to role "main-devs" and "main-admins" mainDevs := "main-devs" - devsRole, err := types.NewRole(mainDevs, types.RoleSpecV5{ + devsRole, err := types.NewRoleV3(mainDevs, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, }, @@ -2066,7 +2098,7 @@ func trustedClusters(t *testing.T, suite *integrationTestSuite, test trustedClus require.NoError(t, err) mainAdmins := "main-admins" - adminsRole, err := types.NewRole(mainAdmins, types.RoleSpecV5{ + adminsRole, err := types.NewRoleV3(mainAdmins, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{"superuser"}, }, @@ -2077,7 +2109,7 @@ func trustedClusters(t *testing.T, suite *integrationTestSuite, test trustedClus // Ops users can only access remote clusters with label 'access': 'ops' mainOps := "main-ops" - mainOpsRole, err := types.NewRole(mainOps, types.RoleSpecV5{ + mainOpsRole, err := types.NewRoleV3(mainOps, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, ClusterLabels: types.Labels{"access": []string{"ops"}}, @@ -2106,7 +2138,7 @@ func trustedClusters(t *testing.T, suite *integrationTestSuite, test trustedClus // using trusted clusters, so remote user will be allowed to assume // role specified by mapping remote role "devs" to local role "local-devs" auxDevs := "aux-devs" - auxRole, err := types.NewRole(auxDevs, types.RoleSpecV5{ + auxRole, err := types.NewRoleV3(auxDevs, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, }, @@ -2208,7 +2240,7 @@ func trustedClusters(t *testing.T, suite *integrationTestSuite, test trustedClus // ListNodes expect labels as a value of host tc.Host = "" - servers, err := tc.ListNodes(ctx) + servers, err := tc.ListNodesWithFilters(ctx) require.NoError(t, err) require.Len(t, servers, 2) tc.Host = Loopback @@ -2298,7 +2330,7 @@ func testTrustedTunnelNode(t *testing.T, suite *integrationTestSuite) { // main cluster has a local user and belongs to role "main-devs" mainDevs := "main-devs" - role, err := types.NewRole(mainDevs, types.RoleSpecV5{ + role, err := types.NewRoleV3(mainDevs, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, }, @@ -2326,7 +2358,7 @@ func testTrustedTunnelNode(t *testing.T, suite *integrationTestSuite) { // using trusted clusters, so remote user will be allowed to assume // role specified by mapping remote role "devs" to local role "local-devs" auxDevs := "aux-devs" - role, err = types.NewRole(auxDevs, types.RoleSpecV5{ + role, err = types.NewRoleV3(auxDevs, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, }, @@ -3043,7 +3075,6 @@ func waitForNodeCount(ctx context.Context, t *TeleInstance, clusterName string, return nil } return trace.BadParameter("did not find %v nodes", count) - }) if err != nil { return trace.Wrap(err) @@ -3221,7 +3252,7 @@ func testControlMaster(t *testing.T, suite *integrationTestSuite) { } for _, tt := range tests { - controlDir, err := ioutil.TempDir("", "teleport-") + controlDir, err := os.MkdirTemp("", "teleport-") require.NoError(t, err) defer os.RemoveAll(controlDir) controlPath := filepath.Join(controlDir, "control-path") @@ -3679,13 +3710,13 @@ func testRotateSuccess(t *testing.T, suite *integrationTestSuite) { t.Logf("Service started. Setting rotation state to %v", types.RotationPhaseUpdateClients) // start rotation - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseInit, Mode: types.RotationModeManual, }) require.NoError(t, err) - hostCA, err := svc.GetAuthServer().GetCertAuthority(types.CertAuthID{Type: types.HostCA, DomainName: Site}, false) + hostCA, err := svc.GetAuthServer().GetCertAuthority(ctx, types.CertAuthID{Type: types.HostCA, DomainName: Site}, false) require.NoError(t, err) t.Logf("Cert authority: %v", auth.CertAuthorityInfo(hostCA)) @@ -3694,7 +3725,7 @@ func testRotateSuccess(t *testing.T, suite *integrationTestSuite) { require.NoError(t, err) // update clients - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseUpdateClients, Mode: types.RotationModeManual, }) @@ -3721,13 +3752,13 @@ func testRotateSuccess(t *testing.T, suite *integrationTestSuite) { t.Logf("Service reloaded. Setting rotation state to %v", types.RotationPhaseUpdateServers) // move to the next phase - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseUpdateServers, Mode: types.RotationModeManual, }) require.NoError(t, err) - hostCA, err = svc.GetAuthServer().GetCertAuthority(types.CertAuthID{Type: types.HostCA, DomainName: Site}, false) + hostCA, err = svc.GetAuthServer().GetCertAuthority(ctx, types.CertAuthID{Type: types.HostCA, DomainName: Site}, false) require.NoError(t, err) t.Logf("Cert authority: %v", auth.CertAuthorityInfo(hostCA)) @@ -3750,13 +3781,13 @@ func testRotateSuccess(t *testing.T, suite *integrationTestSuite) { t.Logf("Service reloaded. Setting rotation state to %v.", types.RotationPhaseStandby) // complete rotation - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseStandby, Mode: types.RotationModeManual, }) require.NoError(t, err) - hostCA, err = svc.GetAuthServer().GetCertAuthority(types.CertAuthID{Type: types.HostCA, DomainName: Site}, false) + hostCA, err = svc.GetAuthServer().GetCertAuthority(ctx, types.CertAuthID{Type: types.HostCA, DomainName: Site}, false) require.NoError(t, err) t.Logf("Cert authority: %v", auth.CertAuthorityInfo(hostCA)) @@ -3828,7 +3859,7 @@ func testRotateRollback(t *testing.T, s *integrationTestSuite) { t.Logf("Service started. Setting rotation state to %q.", types.RotationPhaseInit) // start rotation - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseInit, Mode: types.RotationModeManual, }) @@ -3840,7 +3871,7 @@ func testRotateRollback(t *testing.T, s *integrationTestSuite) { t.Logf("Setting rotation state to %q.", types.RotationPhaseUpdateClients) // start rotation - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseUpdateClients, Mode: types.RotationModeManual, }) @@ -3866,7 +3897,7 @@ func testRotateRollback(t *testing.T, s *integrationTestSuite) { t.Logf("Service reloaded. Setting rotation state to %q.", types.RotationPhaseUpdateServers) // move to the next phase - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseUpdateServers, Mode: types.RotationModeManual, }) @@ -3879,7 +3910,7 @@ func testRotateRollback(t *testing.T, s *integrationTestSuite) { t.Logf("Service reloaded. Setting rotation state to %q.", types.RotationPhaseRollback) // complete rotation - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseRollback, Mode: types.RotationModeManual, }) @@ -3947,7 +3978,7 @@ func testRotateTrustedClusters(t *testing.T, suite *integrationTestSuite) { // main cluster has a local user and belongs to role "main-devs" mainDevs := "main-devs" - role, err := types.NewRole(mainDevs, types.RoleSpecV5{ + role, err := types.NewRoleV3(mainDevs, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{suite.me.Username}, }, @@ -3965,7 +3996,7 @@ func testRotateTrustedClusters(t *testing.T, suite *integrationTestSuite) { // using trusted clusters, so remote user will be allowed to assume // role specified by mapping remote role "devs" to local role "local-devs" auxDevs := "aux-devs" - role, err = types.NewRole(auxDevs, types.RoleSpecV5{ + role, err = types.NewRoleV3(auxDevs, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{suite.me.Username}, }, @@ -4012,7 +4043,7 @@ func testRotateTrustedClusters(t *testing.T, suite *integrationTestSuite) { t.Logf("Setting rotation state to %v", types.RotationPhaseInit) // start rotation - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseInit, Mode: types.RotationModeManual, }) @@ -4063,7 +4094,7 @@ func testRotateTrustedClusters(t *testing.T, suite *integrationTestSuite) { require.NoError(t, err) // update clients - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseUpdateClients, Mode: types.RotationModeManual, }) @@ -4083,7 +4114,7 @@ func testRotateTrustedClusters(t *testing.T, suite *integrationTestSuite) { t.Logf("Service reloaded. Setting rotation state to %v", types.RotationPhaseUpdateServers) // move to the next phase - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseUpdateServers, Mode: types.RotationModeManual, }) @@ -4110,7 +4141,7 @@ func testRotateTrustedClusters(t *testing.T, suite *integrationTestSuite) { t.Logf("Service reloaded. Setting rotation state to %v.", types.RotationPhaseStandby) // complete rotation - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseStandby, Mode: types.RotationModeManual, }) @@ -4148,6 +4179,7 @@ func testRotateTrustedClusters(t *testing.T, suite *integrationTestSuite) { // TestRotateChangeSigningAlg tests the change of CA signing algorithm on // manual rotation. func testRotateChangeSigningAlg(t *testing.T, suite *integrationTestSuite) { + ctx := context.Background() // Start with an instance using default signing alg. tconf := suite.rotationConfig(true) teleport := suite.newTeleportInstance() @@ -4196,18 +4228,18 @@ func testRotateChangeSigningAlg(t *testing.T, suite *integrationTestSuite) { } assertSigningAlg := func(svc *service.TeleportProcess, alg string) { - hostCA, err := svc.GetAuthServer().GetCertAuthority(types.CertAuthID{Type: types.HostCA, DomainName: Site}, false) + hostCA, err := svc.GetAuthServer().GetCertAuthority(ctx, types.CertAuthID{Type: types.HostCA, DomainName: Site}, false) require.NoError(t, err) require.Equal(t, alg, sshutils.GetSigningAlgName(hostCA)) - userCA, err := svc.GetAuthServer().GetCertAuthority(types.CertAuthID{Type: types.UserCA, DomainName: Site}, false) + userCA, err := svc.GetAuthServer().GetCertAuthority(ctx, types.CertAuthID{Type: types.UserCA, DomainName: Site}, false) require.NoError(t, err) require.Equal(t, alg, sshutils.GetSigningAlgName(userCA)) } rotate := func(svc *service.TeleportProcess, mode string) *service.TeleportProcess { t.Logf("Rotation phase: %q.", types.RotationPhaseInit) - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseInit, Mode: mode, }) @@ -4218,7 +4250,7 @@ func testRotateChangeSigningAlg(t *testing.T, suite *integrationTestSuite) { require.NoError(t, err) t.Logf("Rotation phase: %q.", types.RotationPhaseUpdateClients) - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseUpdateClients, Mode: mode, }) @@ -4229,7 +4261,7 @@ func testRotateChangeSigningAlg(t *testing.T, suite *integrationTestSuite) { require.NoError(t, err) t.Logf("Rotation phase: %q.", types.RotationPhaseUpdateServers) - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseUpdateServers, Mode: mode, }) @@ -4240,7 +4272,7 @@ func testRotateChangeSigningAlg(t *testing.T, suite *integrationTestSuite) { require.NoError(t, err) t.Logf("rotation phase: %q", types.RotationPhaseStandby) - err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{ + err = svc.GetAuthServer().RotateCertAuthority(ctx, auth.RotateRequest{ TargetPhase: types.RotationPhaseStandby, Mode: mode, }) @@ -4619,7 +4651,7 @@ func testList(t *testing.T, suite *integrationTestSuite) { for _, tt := range tests { t.Run(tt.inRoleName, func(t *testing.T) { // Create role with logins and labels for this test. - role, err := types.NewRole(tt.inRoleName, types.RoleSpecV5{ + role, err := types.NewRoleV3(tt.inRoleName, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{tt.inLogin}, NodeLabels: tt.inLabels, @@ -4644,7 +4676,7 @@ func testList(t *testing.T, suite *integrationTestSuite) { // Get list of nodes and check that the returned nodes match the // expected nodes. - nodes, err := userClt.ListNodes(context.Background()) + nodes, err := userClt.ListNodesWithFilters(context.Background()) require.NoError(t, err) for _, node := range nodes { ok := apiutils.SliceContainsStr(tt.outNodes, node.GetHostname()) @@ -5025,7 +5057,7 @@ func testSSHExitCode(t *testing.T, suite *integrationTestSuite) { lsPath, err := exec.LookPath("ls") require.NoError(t, err) - var tests = []struct { + tests := []struct { desc string command []string input string @@ -5352,7 +5384,7 @@ func testSessionStartContainsAccessRequest(t *testing.T, suite *integrationTestS authServer := main.Process.GetAuthServer() // Create new request role - requestedRole, err := types.NewRole(requestedRoleName, types.RoleSpecV5{ + requestedRole, err := types.NewRoleV3(requestedRoleName, types.RoleSpecV5{ Options: types.RoleOptions{}, Allow: types.RoleConditions{}, }) @@ -5362,7 +5394,7 @@ func testSessionStartContainsAccessRequest(t *testing.T, suite *integrationTestS require.NoError(t, err) // Create user role with ability to request role - userRole, err := types.NewRole(userRoleName, types.RoleSpecV5{ + userRole, err := types.NewRoleV3(userRoleName, types.RoleSpecV5{ Options: types.RoleOptions{}, Allow: types.RoleConditions{ Logins: []string{ @@ -5833,7 +5865,7 @@ func TestTraitsPropagation(t *testing.T) { }) // Update root's certificate authority on leaf to configure role mapping. - ca, err := lc.Process.GetAuthServer().GetCertAuthority(types.CertAuthID{ + ca, err := lc.Process.GetAuthServer().GetCertAuthority(context.Background(), types.CertAuthID{ Type: types.UserCA, DomainName: rc.Secrets.SiteName, }, false) diff --git a/integration/kube_integration_test.go b/integration/kube_integration_test.go index cc9292677ad42..2bcbae0519380 100644 --- a/integration/kube_integration_test.go +++ b/integration/kube_integration_test.go @@ -23,7 +23,6 @@ import ( "crypto/x509" "fmt" "io" - "io/ioutil" "net" "net/http" "net/url" @@ -182,7 +181,7 @@ func testKubeExec(t *testing.T, suite *KubeSuite) { username := suite.me.Username kubeGroups := []string{testImpersonationGroup} kubeUsers := []string{"alice@example.com"} - role, err := types.NewRole("kubemaster", types.RoleSpecV5{ + role, err := types.NewRoleV3("kubemaster", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, KubeGroups: kubeGroups, @@ -351,7 +350,7 @@ func testKubeDeny(t *testing.T, suite *KubeSuite) { username := suite.me.Username kubeGroups := []string{testImpersonationGroup} kubeUsers := []string{"alice@example.com"} - role, err := types.NewRole("kubemaster", types.RoleSpecV5{ + role, err := types.NewRoleV3("kubemaster", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, KubeGroups: kubeGroups, @@ -402,7 +401,7 @@ func testKubePortForward(t *testing.T, suite *KubeSuite) { username := suite.me.Username kubeGroups := []string{testImpersonationGroup} - role, err := types.NewRole("kubemaster", types.RoleSpecV5{ + role, err := types.NewRoleV3("kubemaster", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, KubeGroups: kubeGroups, @@ -498,7 +497,7 @@ func testKubeTrustedClustersClientCert(t *testing.T, suite *KubeSuite) { // main cluster has a role and user called main-kube username := suite.me.Username mainKubeGroups := []string{testImpersonationGroup} - mainRole, err := types.NewRole("main-kube", types.RoleSpecV5{ + mainRole, err := types.NewRoleV3("main-kube", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, KubeGroups: mainKubeGroups, @@ -533,7 +532,7 @@ func testKubeTrustedClustersClientCert(t *testing.T, suite *KubeSuite) { // using trusted clusters, so remote user will be allowed to assume // role specified by mapping remote role "aux-kube" to local role "main-kube" auxKubeGroups := []string{teleport.TraitInternalKubeGroupsVariable} - auxRole, err := types.NewRole("aux-kube", types.RoleSpecV5{ + auxRole, err := types.NewRoleV3("aux-kube", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, // Note that main cluster can pass it's kubernetes groups @@ -749,7 +748,7 @@ func testKubeTrustedClustersSNI(t *testing.T, suite *KubeSuite) { // main cluster has a role and user called main-kube username := suite.me.Username mainKubeGroups := []string{testImpersonationGroup} - mainRole, err := types.NewRole("main-kube", types.RoleSpecV5{ + mainRole, err := types.NewRoleV3("main-kube", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, KubeGroups: mainKubeGroups, @@ -788,7 +787,7 @@ func testKubeTrustedClustersSNI(t *testing.T, suite *KubeSuite) { // using trusted clusters, so remote user will be allowed to assume // role specified by mapping remote role "aux-kube" to local role "main-kube" auxKubeGroups := []string{teleport.TraitInternalKubeGroupsVariable} - auxRole, err := types.NewRole("aux-kube", types.RoleSpecV5{ + auxRole, err := types.NewRoleV3("aux-kube", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, // Note that main cluster can pass it's kubernetes groups @@ -1023,7 +1022,7 @@ func runKubeDisconnectTest(t *testing.T, suite *KubeSuite, tc disconnectTestCase username := suite.me.Username kubeGroups := []string{testImpersonationGroup} - role, err := types.NewRole("kubemaster", types.RoleSpecV5{ + role, err := types.NewRoleV3("kubemaster", types.RoleSpecV5{ Options: tc.options, Allow: types.RoleConditions{ Logins: []string{username}, @@ -1109,7 +1108,7 @@ func testKubeTransportProtocol(t *testing.T, suite *KubeSuite) { username := suite.me.Username kubeGroups := []string{testImpersonationGroup} - role, err := types.NewRole("kubemaster", types.RoleSpecV5{ + role, err := types.NewRoleV3("kubemaster", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, KubeGroups: kubeGroups, @@ -1281,7 +1280,7 @@ func kubeProxyClient(cfg kubeProxyConfig) (*kubernetes.Clientset, *rest.Config, } ttl := roles.AdjustSessionTTL(10 * time.Minute) - ca, err := authServer.GetCertAuthority(types.CertAuthID{ + ca, err := authServer.GetCertAuthority(context.Background(), types.CertAuthID{ Type: types.HostCA, DomainName: clusterName.GetClusterName(), }, true) @@ -1452,7 +1451,7 @@ func kubeExec(kubeConfig *rest.Config, args kubeExecArgs) error { // stderr channel is only set if there is no tty allocated // otherwise k8s server gets confused if !args.tty && args.stderr == nil { - args.stderr = ioutil.Discard + args.stderr = io.Discard } if args.stderr != nil && !args.tty { query.Set("stderr", "true") @@ -1523,7 +1522,7 @@ func testKubeJoin(t *testing.T, suite *KubeSuite) { participantUsername := suite.me.Username + "-participant" kubeGroups := []string{testImpersonationGroup} kubeUsers := []string{"alice@example.com"} - role, err := types.NewRole("kubemaster", types.RoleSpecV5{ + role, err := types.NewRoleV3("kubemaster", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{hostUsername}, KubeGroups: kubeGroups, diff --git a/integration/proxy_helpers_test.go b/integration/proxy_helpers_test.go index bffe58e25c180..9614826355ad8 100644 --- a/integration/proxy_helpers_test.go +++ b/integration/proxy_helpers_test.go @@ -277,7 +277,7 @@ func withLeafClusterPorts(ports *InstancePorts) proxySuiteOptionsFunc { } func newRole(t *testing.T, roleName string, username string) types.Role { - role, err := types.NewRole(roleName, types.RoleSpecV5{ + role, err := types.NewRoleV3(roleName, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{username}, }, @@ -338,7 +338,7 @@ func withStandardRoleMapping() proxySuiteOptionsFunc { rc := suite.root lc := suite.leaf role := suite.root.Secrets.Users[mustGetCurrentUser(t).Username].Roles[0] - ca, err := lc.Process.GetAuthServer().GetCertAuthority(types.CertAuthID{ + ca, err := lc.Process.GetAuthServer().GetCertAuthority(context.Background(), types.CertAuthID{ Type: types.UserCA, DomainName: rc.Secrets.SiteName, }, false) @@ -509,7 +509,7 @@ func mustCreateUserIdentityFile(t *testing.T, tc *TeleInstance, username string) key.Cert = sshCert key.TLSCert = tlsCert - hostCAs, err := tc.Process.GetAuthServer().GetCertAuthorities(types.HostCA, false) + hostCAs, err := tc.Process.GetAuthServer().GetCertAuthorities(context.Background(), types.HostCA, false) require.NoError(t, err) key.TrustedCA = auth.AuthoritiesToTrustedCerts(hostCAs) diff --git a/integration/proxy_test.go b/integration/proxy_test.go index e45126e01b684..87516fae4feea 100644 --- a/integration/proxy_test.go +++ b/integration/proxy_test.go @@ -254,7 +254,7 @@ func TestALPNSNIProxyKube(t *testing.T) { KubeUsers: []string{k8User}, }, } - kubeRole, err := types.NewRole(k8RoleName, kubeRoleSpec) + kubeRole, err := types.NewRoleV3(k8RoleName, kubeRoleSpec) require.NoError(t, err) suite := newProxySuite(t, @@ -306,7 +306,7 @@ func TestALPNSNIProxyKubeV2Leaf(t *testing.T) { KubeUsers: []string{k8User}, }, } - kubeRole, err := types.NewRole(k8RoleName, kubeRoleSpec) + kubeRole, err := types.NewRoleV3(k8RoleName, kubeRoleSpec) require.NoError(t, err) suite := newProxySuite(t, diff --git a/lib/asciitable/table.go b/lib/asciitable/table.go index c7f705f51741e..16c1d0f916975 100644 --- a/lib/asciitable/table.go +++ b/lib/asciitable/table.go @@ -21,8 +21,11 @@ package asciitable import ( "bytes" "fmt" + "os" "strings" "text/tabwriter" + + "golang.org/x/term" ) // Column represents a column in the table. @@ -50,13 +53,68 @@ func MakeHeadlessTable(columnCount int) Table { } } -// MakeTable creates a new instance of the table with given column names. -func MakeTable(headers []string) Table { +// MakeTable creates a new instance of the table with given column +// names. Optionally rows to be added to the table may be included. +func MakeTable(headers []string, rows ...[]string) Table { t := MakeHeadlessTable(len(headers)) for i := range t.columns { t.columns[i].Title = headers[i] t.columns[i].width = len(headers[i]) } + for _, row := range rows { + t.AddRow(row) + } + return t +} + +// MakeTableWithTruncatedColumn creates a table where the column +// matching truncatedColumn will be shortened to account for terminal +// width. +func MakeTableWithTruncatedColumn(columnOrder []string, rows [][]string, truncatedColumn string) Table { + width, _, err := term.GetSize(int(os.Stdin.Fd())) + if err != nil { + width = 80 + } + truncatedColMinSize := 16 + maxColWidth := (width - truncatedColMinSize) / (len(columnOrder) - 1) + t := MakeTable([]string{}) + totalLen := 0 + columns := []Column{} + + for collIndex, colName := range columnOrder { + column := Column{ + Title: colName, + MaxCellLength: len(colName), + } + if colName == truncatedColumn { // truncated column is handled separately in next loop + columns = append(columns, column) + continue + } + for _, row := range rows { + cellLen := row[collIndex] + if len(cellLen) > column.MaxCellLength { + column.MaxCellLength = len(cellLen) + } + } + if column.MaxCellLength > maxColWidth { + column.MaxCellLength = maxColWidth + totalLen += column.MaxCellLength + 4 // "..." + } else { + totalLen += column.MaxCellLength + 1 // +1 for column separator + } + columns = append(columns, column) + } + + for _, column := range columns { + if column.Title == truncatedColumn { + column.MaxCellLength = width - totalLen - len("... ") + } + t.AddColumn(column) + } + + for _, row := range rows { + t.AddRow(row) + } return t } diff --git a/lib/asciitable/table_test.go b/lib/asciitable/table_test.go index 96c7f0ddf95c4..ee482af693ab4 100644 --- a/lib/asciitable/table_test.go +++ b/lib/asciitable/table_test.go @@ -80,3 +80,55 @@ func TestTruncatedTable(t *testing.T) { require.Equal(t, truncatedTable, table.AsBuffer().String()) } + +func TestMakeTableWithTruncatedColumn(t *testing.T) { + // os.Stdin.Fd() fails during go test, so width is defaulted to 80 + columns := []string{"column1", "column2", "column3"} + rows := [][]string{{strings.Repeat("cell1", 6), strings.Repeat("cell2", 6), strings.Repeat("cell3", 6)}} + + testCases := []struct { + truncatedColumn string + expectedWidth int + expectedOutput []string + }{ + { + truncatedColumn: "column2", + expectedWidth: 80, + expectedOutput: []string{ + "column1 column2 column3 ", + "------------------------------ ----------------- ------------------------------ ", + "cell1cell1cell1cell1cell1cell1 cell2cell2cell... cell3cell3cell3cell3cell3cell3 ", + "", + }, + }, + { + truncatedColumn: "column3", + expectedWidth: 80, + expectedOutput: []string{ + "column1 column2 column3 ", + "------------------------------ ------------------------------ ----------------- ", + "cell1cell1cell1cell1cell1cell1 cell2cell2cell2cell2cell2cell2 cell3cell3cell... ", + "", + }, + }, + { + truncatedColumn: "no column match", + expectedWidth: 93, + expectedOutput: []string{ + "column1 column2 column3 ", + "------------------------------ ------------------------------ ------------------------------ ", + "cell1cell1cell1cell1cell1cell1 cell2cell2cell2cell2cell2cell2 cell3cell3cell3cell3cell3cell3 ", + "", + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.truncatedColumn, func(t *testing.T) { + table := MakeTableWithTruncatedColumn(columns, rows, testCase.truncatedColumn) + rows := strings.Split(table.AsBuffer().String(), "\n") + require.Len(t, rows, 4) + require.Len(t, rows[2], testCase.expectedWidth) + require.Equal(t, testCase.expectedOutput, rows) + }) + } +} diff --git a/lib/auth/access.go b/lib/auth/access.go index 2ec036cfe102b..d80d4df4d0cce 100644 --- a/lib/auth/access.go +++ b/lib/auth/access.go @@ -66,7 +66,7 @@ func (a *Server) DeleteRole(ctx context.Context, name string) error { } // check if it's used by some external cert authorities, e.g. // cert authorities related to external cluster - cas, err := a.Trust.GetCertAuthorities(types.UserCA, false) + cas, err := a.Trust.GetCertAuthorities(ctx, types.UserCA, false) if err != nil { return trace.Wrap(err) } diff --git a/lib/auth/access_test.go b/lib/auth/access_test.go index 071ca60201631..07312080313c7 100644 --- a/lib/auth/access_test.go +++ b/lib/auth/access_test.go @@ -35,7 +35,7 @@ func TestUpsertDeleteRoleEventsEmitted(t *testing.T) { require.NoError(t, err) // test create new role - role, err := types.NewRole("test-role", types.RoleSpecV5{ + role, err := types.NewRoleV3("test-role", types.RoleSpecV5{ Options: types.RoleOptions{}, Allow: types.RoleConditions{}, }) diff --git a/lib/auth/accountrecovery.go b/lib/auth/accountrecovery.go index 8a75c7725970d..4549177934806 100644 --- a/lib/auth/accountrecovery.go +++ b/lib/auth/accountrecovery.go @@ -260,7 +260,8 @@ func (s *Server) VerifyAccountRecovery(ctx context.Context, req *proto.VerifyAcc } if err := s.verifyAuthnWithRecoveryLock(ctx, startToken, func() error { - _, err := s.validateMFAAuthResponse(ctx, startToken.GetUser(), req.GetMFAAuthenticateResponse()) + _, _, err := s.validateMFAAuthResponse( + ctx, req.GetMFAAuthenticateResponse(), startToken.GetUser(), false /* passwordless */) return err }); err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/accountrecovery_test.go b/lib/auth/accountrecovery_test.go index 6f3cf858e537b..2801ade330e36 100644 --- a/lib/auth/accountrecovery_test.go +++ b/lib/auth/accountrecovery_test.go @@ -1291,7 +1291,7 @@ func TestGetAccountRecoveryCodes(t *testing.T) { func triggerLoginLock(t *testing.T, srv *Server, username string) { for i := 1; i <= defaults.MaxLoginAttempts; i++ { - _, err := srv.authenticateUser(context.Background(), AuthenticateUserRequest{ + _, _, err := srv.authenticateUser(context.Background(), AuthenticateUserRequest{ Username: username, OTP: &OTPCreds{}, }) diff --git a/lib/auth/api.go b/lib/auth/api.go index 21ae421ac4366..fd3a4a2041010 100644 --- a/lib/auth/api.go +++ b/lib/auth/api.go @@ -104,10 +104,10 @@ type ReadNodeAccessPoint interface { NewWatcher(ctx context.Context, watch types.Watch) (types.Watcher, error) // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) // GetClusterName gets the name of the cluster from the backend. GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) @@ -162,10 +162,10 @@ type ReadProxyAccessPoint interface { NewWatcher(ctx context.Context, watch types.Watch) (types.Watcher, error) // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) // GetClusterName gets the name of the cluster from the backend. GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) @@ -293,10 +293,10 @@ type ReadRemoteProxyAccessPoint interface { NewWatcher(ctx context.Context, watch types.Watch) (types.Watcher, error) // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) // GetClusterName gets the name of the cluster from the backend. GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) @@ -389,10 +389,10 @@ type ReadKubernetesAccessPoint interface { NewWatcher(ctx context.Context, watch types.Watch) (types.Watcher, error) // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) // GetClusterName gets the name of the cluster from the backend. GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) @@ -450,10 +450,10 @@ type ReadAppsAccessPoint interface { NewWatcher(ctx context.Context, watch types.Watch) (types.Watcher, error) // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) // GetClusterName gets the name of the cluster from the backend. GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) @@ -517,10 +517,10 @@ type ReadDatabaseAccessPoint interface { NewWatcher(ctx context.Context, watch types.Watch) (types.Watcher, error) // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) // GetClusterName gets the name of the cluster from the backend. GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) @@ -584,10 +584,10 @@ type ReadWindowsDesktopAccessPoint interface { NewWatcher(ctx context.Context, watch types.Watch) (types.Watcher, error) // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) // GetClusterName gets the name of the cluster from the backend. GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) @@ -642,10 +642,10 @@ type WindowsDesktopAccessPoint interface { // AccessCache is a subset of the interface working on the certificate authorities type AccessCache interface { // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) // GetClusterAuditConfig returns cluster audit configuration. GetClusterAuditConfig(ctx context.Context, opts ...services.MarshalOption) (types.ClusterAuditConfig, error) @@ -709,10 +709,10 @@ type Cache interface { GetAuthServers() ([]types.Server, error) // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) // GetUser returns a services.User for this cluster. GetUser(name string, withSecrets bool) (types.User, error) @@ -809,6 +809,8 @@ type Cache interface { // ListResources returns a paginated list of resources. ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) + // ListWindowsDesktops returns a paginated list of windows desktops. + ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error) } type NodeWrapper struct { diff --git a/lib/auth/apiserver.go b/lib/auth/apiserver.go index a817888340efc..d9ee0df2029ad 100644 --- a/lib/auth/apiserver.go +++ b/lib/auth/apiserver.go @@ -22,7 +22,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "net/url" "strconv" @@ -970,10 +969,7 @@ func (s *APIServer) registerUsingToken(auth ClientI, w http.ResponseWriter, r *h return nil, trace.Wrap(err) } - // Teleport 8 clients are still expecting the legacy JSON format. - // Teleport 9 clients handle both legacy and new. - // TODO(zmb3) return certs directly in Teleport 10 - return LegacyCertsFromProto(certs), nil + return certs, nil } type registerNewAuthServerReq struct { @@ -997,7 +993,7 @@ func (s *APIServer) rotateCertAuthority(auth ClientI, w http.ResponseWriter, r * if err := httplib.ReadJSON(r, &req); err != nil { return nil, trace.Wrap(err) } - if err := auth.RotateCertAuthority(req); err != nil { + if err := auth.RotateCertAuthority(r.Context(), req); err != nil { return nil, trace.Wrap(err) } return message("ok"), nil @@ -1042,7 +1038,7 @@ func (s *APIServer) rotateExternalCertAuthority(auth ClientI, w http.ResponseWri if err != nil { return nil, trace.Wrap(err) } - if err := auth.RotateExternalCertAuthority(ca); err != nil { + if err := auth.RotateExternalCertAuthority(r.Context(), ca); err != nil { return nil, trace.Wrap(err) } return message("ok"), nil @@ -1053,7 +1049,7 @@ func (s *APIServer) getCertAuthorities(auth ClientI, w http.ResponseWriter, r *h if err != nil { return nil, trace.Wrap(err) } - certs, err := auth.GetCertAuthorities(types.CertAuthType(p.ByName("type")), loadKeys) + certs, err := auth.GetCertAuthorities(r.Context(), types.CertAuthType(p.ByName("type")), loadKeys) if err != nil { return nil, trace.Wrap(err) } @@ -1077,7 +1073,7 @@ func (s *APIServer) getCertAuthority(auth ClientI, w http.ResponseWriter, r *htt Type: types.CertAuthType(p.ByName("type")), DomainName: p.ByName("domain"), } - ca, err := auth.GetCertAuthority(id, loadKeys) + ca, err := auth.GetCertAuthority(r.Context(), id, loadKeys) if err != nil { return nil, trace.Wrap(err) } @@ -1829,7 +1825,7 @@ func (s *APIServer) emitAuditEvent(auth ClientI, w http.ResponseWriter, r *http. // HTTP POST /:version/sessions/:id/slice func (s *APIServer) postSessionSlice(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) { - data, err := ioutil.ReadAll(r.Body) + data, err := io.ReadAll(r.Body) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 3709a85be4ed4..575c116029d6e 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -422,7 +422,7 @@ func (a *Server) runPeriodicOperations() { case <-a.closeCtx.Done(): return case <-ticker.Chan(): - err := a.autoRotateCertAuthorities() + err := a.autoRotateCertAuthorities(ctx) if err != nil { if trace.IsCompareFailed(err) { log.Debugf("Cert authority has been updated concurrently: %v.", err) @@ -629,7 +629,7 @@ func (a *Server) GetClusterCACert() (*LocalCAResponse, error) { return nil, trace.Wrap(err) } // Extract the TLS CA for this cluster. - hostCA, err := a.GetCache().GetCertAuthority(types.CertAuthID{ + hostCA, err := a.GetCache().GetCertAuthority(context.TODO(), types.CertAuthID{ Type: types.HostCA, DomainName: clusterName.GetClusterName(), }, false) @@ -656,7 +656,7 @@ func (a *Server) GenerateHostCert(hostPublicKey []byte, hostID, nodeName string, } // get the certificate authority that will be signing the public key of the host - ca, err := a.Trust.GetCertAuthority(types.CertAuthID{ + ca, err := a.Trust.GetCertAuthority(context.TODO(), types.CertAuthID{ Type: types.HostCA, DomainName: domainName, }, true) @@ -938,13 +938,14 @@ func (a *Server) GenerateDatabaseTestCert(req DatabaseTestCertRequest) ([]byte, // generateUserCert generates user certificates func (a *Server) generateUserCert(req certRequest) (*proto.Certs, error) { + ctx := context.TODO() err := req.check() if err != nil { return nil, trace.Wrap(err) } // Reject the cert request if there is a matching lock in force. - authPref, err := a.GetAuthPreference(context.TODO()) + authPref, err := a.GetAuthPreference(ctx) if err != nil { return nil, trace.Wrap(err) } @@ -1029,7 +1030,7 @@ func (a *Server) generateUserCert(req certRequest) (*proto.Certs, error) { } } - userCA, err := a.Trust.GetCertAuthority(types.CertAuthID{ + userCA, err := a.Trust.GetCertAuthority(ctx, types.CertAuthID{ Type: types.UserCA, DomainName: clusterName, }, true) @@ -1062,6 +1063,7 @@ func (a *Server) generateUserCert(req certRequest) (*proto.Certs, error) { DisallowReissue: req.disallowReissue, Renewable: req.renewable, Generation: req.generation, + CertificateExtensions: req.checker.CertificateExtensions(), } sshCert, err := a.Authority.GenerateUserCert(params) if err != nil { @@ -1182,7 +1184,7 @@ func (a *Server) generateUserCert(req certRequest) (*proto.Certs, error) { // also include host CA certs if requested if req.includeHostCA { - hostCA, err := a.Trust.GetCertAuthority(types.CertAuthID{ + hostCA, err := a.Trust.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName, }, false) @@ -1307,6 +1309,7 @@ func (a *Server) PreAuthenticatedSignIn(user string, identity tlsca.Identity) (t // CreateAuthenticateChallenge implements AuthService.CreateAuthenticateChallenge. func (a *Server) CreateAuthenticateChallenge(ctx context.Context, req *proto.CreateAuthenticateChallengeRequest) (*proto.MFAAuthenticateChallenge, error) { var username string + var passwordless bool switch req.GetRequest().(type) { case *proto.CreateAuthenticateChallengeRequest_UserCredentials: @@ -1331,7 +1334,10 @@ func (a *Server) CreateAuthenticateChallenge(ctx context.Context, req *proto.Cre username = token.GetUser() - default: + case *proto.CreateAuthenticateChallengeRequest_Passwordless: + passwordless = true // Allows empty username. + + default: // unset or CreateAuthenticateChallengeRequest_ContextUser. var err error username, err = GetClientUsername(ctx) if err != nil { @@ -1339,7 +1345,7 @@ func (a *Server) CreateAuthenticateChallenge(ctx context.Context, req *proto.Cre } } - challenges, err := a.mfaAuthChallenge(ctx, username) + challenges, err := a.mfaAuthChallenge(ctx, username, passwordless) if err != nil { log.Error(trace.DebugReport(err)) return nil, trace.AccessDenied("unable to create MFA challenges") @@ -1368,17 +1374,19 @@ func (a *Server) CreateRegisterChallenge(ctx context.Context, req *proto.CreateR } regChal, err := a.createRegisterChallenge(ctx, &newRegisterChallengeRequest{ - username: token.GetUser(), - token: token, - deviceType: req.GetDeviceType(), + username: token.GetUser(), + token: token, + deviceType: req.GetDeviceType(), + deviceUsage: req.GetDeviceUsage(), }) return regChal, trace.Wrap(err) } type newRegisterChallengeRequest struct { - username string - deviceType proto.DeviceType + username string + deviceType proto.DeviceType + deviceUsage proto.DeviceUsage // token is a user token resource. // It is used as following: @@ -1447,7 +1455,8 @@ func (a *Server) createRegisterChallenge(ctx context.Context, req *newRegisterCh Identity: identity, } - credentialCreation, err := webRegistration.Begin(ctx, req.username, false /* passwordless */) + passwordless := req.deviceUsage == proto.DeviceUsage_DEVICE_USAGE_PASSWORDLESS + credentialCreation, err := webRegistration.Begin(ctx, req.username, passwordless) if err != nil { return nil, trace.Wrap(err) } @@ -2053,7 +2062,7 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ if req.NoCache { client = a.Services } - ca, err := client.GetCertAuthority(types.CertAuthID{ + ca, err := client.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName.GetClusterName(), }, true) @@ -2067,7 +2076,7 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ // to the backend, which is a fine tradeoff if !req.NoCache && req.Rotation != nil && !req.Rotation.Matches(ca.GetRotation()) { log.Debugf("Client sent rotation state %v, cache state is %v, using state from the DB.", req.Rotation, ca.GetRotation()) - ca, err = a.GetCertAuthority(types.CertAuthID{ + ca, err = a.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName.GetClusterName(), }, true) @@ -2145,11 +2154,11 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ NotAfter: a.clock.Now().UTC().Add(defaults.CATTL), DNSNames: append([]string{}, req.AdditionalPrincipals...), } + // API requests need to specify a DNS name, which must be present in the certificate's DNS Names. // The target DNS is not always known in advance so we add a default one to all certificates. - if (types.SystemRoles{req.Role}).IncludeAny(types.RoleAuth, types.RoleAdmin, types.RoleProxy, types.RoleKube, types.RoleApp) { - certRequest.DNSNames = append(certRequest.DNSNames, "*."+constants.APIDomain, constants.APIDomain) - } + certRequest.DNSNames = append(certRequest.DNSNames, DefaultDNSNamesForRole(req.Role)...) + // Unlike additional principals, DNS Names is x509 specific and is limited // to services with TLS endpoints (e.g. auth, proxies, kubernetes) if (types.SystemRoles{req.Role}).IncludeAny(types.RoleAuth, types.RoleAdmin, types.RoleProxy, types.RoleKube, types.RoleWindowsDesktop) { @@ -2577,14 +2586,14 @@ func (a *Server) NewKeepAliver(ctx context.Context) (types.KeepAliver, error) { // GetCertAuthority returns certificate authority by given id. Parameter loadSigningKeys // controls if signing keys are loaded -func (a *Server) GetCertAuthority(id types.CertAuthID, loadSigningKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { - return a.GetCache().GetCertAuthority(id, loadSigningKeys, opts...) +func (a *Server) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadSigningKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { + return a.GetCache().GetCertAuthority(ctx, id, loadSigningKeys, opts...) } // GetCertAuthorities returns a list of authorities of a given type // loadSigningKeys controls whether signing keys should be loaded or not -func (a *Server) GetCertAuthorities(caType types.CertAuthType, loadSigningKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { - return a.GetCache().GetCertAuthorities(caType, loadSigningKeys, opts...) +func (a *Server) GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadSigningKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { + return a.GetCache().GetCertAuthorities(ctx, caType, loadSigningKeys, opts...) } // GenerateCertAuthorityCRL generates an empty CRL for the local CA of a given type. @@ -2594,7 +2603,7 @@ func (a *Server) GenerateCertAuthorityCRL(ctx context.Context, caType types.Cert if err != nil { return nil, trace.Wrap(err) } - ca, err := a.GetCertAuthority(types.CertAuthID{ + ca, err := a.GetCertAuthority(ctx, types.CertAuthID{ Type: caType, DomainName: clusterName.GetClusterName(), }, true) @@ -2708,9 +2717,31 @@ type ResourcePageFunc func(next []types.ResourceWithLabels) (stop bool, err erro // IterateResourcePages can be used to iterate over pages of resources. func (a *Server) IterateResourcePages(ctx context.Context, req proto.ListResourcesRequest, f ResourcePageFunc) (*types.ListResourcesResponse, error) { for { - resp, err := a.ListResources(ctx, req) - if err != nil { - return nil, trace.Wrap(err) + var resp *types.ListResourcesResponse + switch { + case req.ResourceType == types.KindWindowsDesktop: + wResp, err := a.ListWindowsDesktops(ctx, types.ListWindowsDesktopsRequest{ + WindowsDesktopFilter: req.WindowsDesktopFilter, + Limit: int(req.Limit), + StartKey: req.StartKey, + PredicateExpression: req.PredicateExpression, + Labels: req.Labels, + SearchKeywords: req.SearchKeywords, + SortBy: req.SortBy, + }) + if err != nil { + return nil, trace.Wrap(err) + } + resp = &types.ListResourcesResponse{ + Resources: types.WindowsDesktops(wResp.Desktops).AsResources(), + NextKey: wResp.NextKey, + } + default: + dResp, err := a.ListResources(ctx, req) + if err != nil { + return nil, trace.Wrap(err) + } + resp = dResp } stop, err := f(resp.Resources) @@ -3003,11 +3034,16 @@ func (a *Server) GetDatabase(ctx context.Context, name string) (types.Database, return a.GetCache().GetDatabase(ctx, name) } -// GetDatabases returns all database resources. +// ListResources returns paginated resources depending on the resource type.. func (a *Server) ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) { return a.GetCache().ListResources(ctx, req) } +// ListWindowsDesktops returns paginated windows desktops. +func (a *Server) ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error) { + return a.GetCache().ListWindowsDesktops(ctx, req) +} + // GetLock gets a lock by name from the auth server's cache. func (a *Server) GetLock(ctx context.Context, name string) (types.Lock, error) { return a.GetCache().GetLock(ctx, name) @@ -3184,7 +3220,7 @@ func (a *Server) isMFARequired(ctx context.Context, checker services.AccessCheck // mfaAuthChallenge constructs an MFAAuthenticateChallenge for all MFA devices // registered by the user. -func (a *Server) mfaAuthChallenge(ctx context.Context, user string) (*proto.MFAAuthenticateChallenge, error) { +func (a *Server) mfaAuthChallenge(ctx context.Context, user string, passwordless bool) (*proto.MFAAuthenticateChallenge, error) { // Check what kind of MFA is enabled. apref, err := a.GetAuthPreference(ctx) if err != nil { @@ -3212,16 +3248,42 @@ func (a *Server) mfaAuthChallenge(ctx context.Context, user string) (*proto.MFAA webConfig = val } - devs, err := a.Identity.GetMFADevices(ctx, user, true) + // Handle passwordless separately, it works differently from MFA. + if passwordless { + if !enableWebauthn { + return nil, trace.BadParameter("passwordless requires WebAuthn") + } + webLogin := &wanlib.PasswordlessFlow{ + Webauthn: webConfig, + Identity: a.Identity, + } + assertion, err := webLogin.Begin(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return &proto.MFAAuthenticateChallenge{ + WebauthnChallenge: wanlib.CredentialAssertionToProto(assertion), + }, nil + } + + // User required for non-passwordless. + if user == "" { + return nil, trace.BadParameter("user required") + } + + devs, err := a.Identity.GetMFADevices(ctx, user, true /* withSecrets */) if err != nil { return nil, trace.Wrap(err) } - groupedDevs := groupByDeviceType(devs, enableWebauthn) challenge := &proto.MFAAuthenticateChallenge{} + + // TOTP challenge. if enableTOTP && groupedDevs.TOTP { challenge.TOTP = &proto.TOTPChallenge{} } + + // WebAuthn challenge. if len(groupedDevs.Webauthn) > 0 { webLogin := &wanlib.LoginFlow{ U2F: u2fPref, @@ -3264,32 +3326,63 @@ func groupByDeviceType(devs []*types.MFADevice, groupWebauthn bool) devicesByTyp return res } -func (a *Server) validateMFAAuthResponse(ctx context.Context, user string, resp *proto.MFAAuthenticateResponse) (*types.MFADevice, error) { +// validateMFAAuthResponse validates an MFA or passwordless challenge. +// Returns the device used to solve the challenge (if applicable) and the +// username. +func (a *Server) validateMFAAuthResponse( + ctx context.Context, + resp *proto.MFAAuthenticateResponse, user string, passwordless bool) (*types.MFADevice, string, error) { + // Sanity check user/passwordless. + if user == "" && !passwordless { + return nil, "", trace.BadParameter("user required") + } + switch res := resp.Response.(type) { - case *proto.MFAAuthenticateResponse_TOTP: - return a.checkOTP(user, res.TOTP.Code) + // cases in order of preference case *proto.MFAAuthenticateResponse_Webauthn: + // Read necessary configurations. cap, err := a.GetAuthPreference(ctx) if err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) + } + u2f, err := cap.GetU2F() + switch { + case trace.IsNotFound(err): // OK, may happen. + case err != nil: // Unexpected. + return nil, "", trace.Wrap(err) } - u2f, _ := cap.GetU2F() webConfig, err := cap.GetWebauthn() if err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } - webLogin := &wanlib.LoginFlow{ - U2F: u2f, - Webauthn: webConfig, - Identity: a.Identity, + + assertionResp := wanlib.CredentialAssertionResponseFromProto(res.Webauthn) + var dev *types.MFADevice + if passwordless { + webLogin := &wanlib.PasswordlessFlow{ + Webauthn: webConfig, + Identity: a.Identity, + } + dev, user, err = webLogin.Finish(ctx, assertionResp) + } else { + webLogin := &wanlib.LoginFlow{ + U2F: u2f, + Webauthn: webConfig, + Identity: a.Identity, + } + dev, err = webLogin.Finish(ctx, user, wanlib.CredentialAssertionResponseFromProto(res.Webauthn)) } - dev, err := webLogin.Finish(ctx, user, wanlib.CredentialAssertionResponseFromProto(res.Webauthn)) if err != nil { - return nil, trace.AccessDenied("MFA response validation failed: %v", err) + return nil, "", trace.AccessDenied("MFA response validation failed: %v", err) } - return dev, nil + return dev, user, nil + + case *proto.MFAAuthenticateResponse_TOTP: + dev, err := a.checkOTP(user, res.TOTP.Code) + return dev, user, trace.Wrap(err) + default: - return nil, trace.BadParameter("unknown or missing MFAAuthenticateResponse type %T", resp.Response) + return nil, "", trace.BadParameter("unknown or missing MFAAuthenticateResponse type %T", resp.Response) } } @@ -3343,6 +3436,7 @@ func mergeKeySets(a, b types.CAKeySet) types.CAKeySet { // addAdditionalTrustedKeysAtomic performs an atomic CompareAndSwap to update // the given CA with newKeys added to the AdditionalTrustedKeys func (a *Server) addAddtionalTrustedKeysAtomic( + ctx context.Context, currentCA types.CertAuthority, newKeys types.CAKeySet, needsUpdate func(types.CertAuthority) bool) error { @@ -3373,7 +3467,7 @@ func (a *Server) addAddtionalTrustedKeysAtomic( } // else trace.IsCompareFailed(err) == true (CA was concurrently updated) - currentCA, err = a.Trust.GetCertAuthority(currentCA.GetID(), true) + currentCA, err = a.Trust.GetCertAuthority(ctx, currentCA.GetID(), true) if err != nil { return trace.Wrap(err) } @@ -3408,7 +3502,7 @@ func newKeySet(keyStore keystore.KeyStore, caID types.CertAuthID) (types.CAKeySe // ensureLocalAdditionalKeys adds additional trusted keys to the CA if they are not // already present. -func (a *Server) ensureLocalAdditionalKeys(ca types.CertAuthority) error { +func (a *Server) ensureLocalAdditionalKeys(ctx context.Context, ca types.CertAuthority) error { if a.keyStore.HasLocalAdditionalKeys(ca) { // nothing to do return nil @@ -3419,7 +3513,7 @@ func (a *Server) ensureLocalAdditionalKeys(ca types.CertAuthority) error { return trace.Wrap(err) } - err = a.addAddtionalTrustedKeysAtomic(ca, newKeySet, func(ca types.CertAuthority) bool { + err = a.addAddtionalTrustedKeysAtomic(ctx, ca, newKeySet, func(ca types.CertAuthority) bool { return !a.keyStore.HasLocalAdditionalKeys(ca) }) if err != nil { @@ -3457,7 +3551,7 @@ func (a *Server) createSelfSignedCA(caID types.CertAuthID) error { // deleteUnusedKeys deletes all teleport keys held in a connected HSM for this // auth server which are not currently used in any CAs. -func (a *Server) deleteUnusedKeys() error { +func (a *Server) deleteUnusedKeys(ctx context.Context) error { clusterName, err := a.GetClusterName() if err != nil { return trace.Wrap(err) @@ -3466,7 +3560,7 @@ func (a *Server) deleteUnusedKeys() error { var usedKeys [][]byte for _, caType := range []types.CertAuthType{types.HostCA, types.UserCA, types.JWTSigner} { caID := types.CertAuthID{Type: caType, DomainName: clusterName.GetClusterName()} - ca, err := a.Trust.GetCertAuthority(caID, true) + ca, err := a.Trust.GetCertAuthority(ctx, caID, true) if err != nil { return trace.Wrap(err) } @@ -3700,3 +3794,14 @@ func WithClusterCAs(tlsConfig *tls.Config, ap AccessCache, currentClusterName st return tlsCopy, nil } } + +// DefaultDNSNamesForRole returns default DNS names for the specified role. +func DefaultDNSNamesForRole(role types.SystemRole) []string { + if (types.SystemRoles{role}).IncludeAny(types.RoleAuth, types.RoleAdmin, types.RoleProxy, types.RoleKube, types.RoleApp, types.RoleDatabase, types.RoleWindowsDesktop) { + return []string{ + "*." + constants.APIDomain, + constants.APIDomain, + } + } + return nil +} diff --git a/lib/auth/auth_login_test.go b/lib/auth/auth_login_test.go index e17e1b79d8f8c..84944ed8219b3 100644 --- a/lib/auth/auth_login_test.go +++ b/lib/auth/auth_login_test.go @@ -22,10 +22,12 @@ import ( "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" - wanlib "github.com/gravitational/teleport/lib/auth/webauthn" + "github.com/gravitational/teleport/lib/auth/mocku2f" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/trace" "github.com/stretchr/testify/require" + + wanlib "github.com/gravitational/teleport/lib/auth/webauthn" ) func TestServer_CreateAuthenticateChallenge_authPreference(t *testing.T) { @@ -156,7 +158,8 @@ func TestServer_CreateAuthenticateChallenge_authPreference(t *testing.T) { UserCredentials: &proto.UserCredentials{ Username: username, Password: []byte(password), - }}, + }, + }, }) require.NoError(t, err) test.assertChallenge(challenge) @@ -164,85 +167,6 @@ func TestServer_CreateAuthenticateChallenge_authPreference(t *testing.T) { } } -// sshPubKey is a randomly-generated public key used for login tests. -// -// The corresponding private key is: -// -----BEGIN PRIVATE KEY----- -// MHcCAQEEIAKuZeB4WL4KAl5cnCrMYBy3kAX9qHt/g6OAbGGd7f3VoAoGCCqGSM49 -// AwEHoUQDQgAEa/6A3YLbc/TyJ4lED2BT8iThuw6HcrDX3dRixwkPDjWYBOP4qrJ/ -// jlGaPwXyuzeLuZgpFde7UiM1EHM2ClfGpw== -// -----END PRIVATE KEY----- -const sshPubKey = `ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGv+gN2C23P08ieJRA9gU/Ik4bsOh3Kw193UYscJDw41mATj+Kqyf45Rmj8F8rs3i7mYKRXXu1IjNRBzNgpXxqc=` - -func TestServer_AuthenticateUser_mfaDevices(t *testing.T) { - t.Parallel() - - svr := newTestTLSServer(t) - authServer := svr.Auth() - mfa := configureForMFA(t, svr) - username := mfa.User - password := mfa.Password - - tests := []struct { - name string - solveChallenge func(*proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) - }{ - {name: "OK TOTP device", solveChallenge: mfa.TOTPDev.SolveAuthn}, - {name: "OK Webauthn device", solveChallenge: mfa.WebDev.SolveAuthn}, - } - for _, test := range tests { - test := test - // makeRun is used to test both SSH and Web login by switching the - // authenticate function. - makeRun := func(authenticate func(*Server, AuthenticateUserRequest) error) func(t *testing.T) { - return func(t *testing.T) { - // 1st step: acquire challenge - challenge, err := authServer.CreateAuthenticateChallenge(context.Background(), &proto.CreateAuthenticateChallengeRequest{ - Request: &proto.CreateAuthenticateChallengeRequest_UserCredentials{UserCredentials: &proto.UserCredentials{ - Username: username, - Password: []byte(password), - }}, - }) - require.NoError(t, err) - - // Solve challenge (client-side) - resp, err := test.solveChallenge(challenge) - authReq := AuthenticateUserRequest{ - Username: username, - } - require.NoError(t, err) - - switch { - case resp.GetWebauthn() != nil: - authReq.Webauthn = wanlib.CredentialAssertionResponseFromProto(resp.GetWebauthn()) - case resp.GetTOTP() != nil: - authReq.OTP = &OTPCreds{ - Password: []byte(password), - Token: resp.GetTOTP().Code, - } - default: - t.Fatalf("Unexpected solved challenge type: %T", resp.Response) - } - - // 2nd step: finish login - either SSH or Web - require.NoError(t, authenticate(authServer, authReq)) - } - } - t.Run(test.name+"/ssh", makeRun(func(s *Server, req AuthenticateUserRequest) error { - _, err := s.AuthenticateSSHUser(AuthenticateSSHRequest{ - AuthenticateUserRequest: req, - PublicKey: []byte(sshPubKey), - TTL: 24 * time.Hour, - }) - return err - })) - t.Run(test.name+"/web", makeRun(func(s *Server, req AuthenticateUserRequest) error { - _, err := s.AuthenticateWebUser(req) - return err - })) - } -} - func TestCreateAuthenticateChallenge_WithAuth(t *testing.T) { t.Parallel() ctx := context.Background() @@ -261,7 +185,7 @@ func TestCreateAuthenticateChallenge_WithAuth(t *testing.T) { // TODO(codingllama): Use a public endpoint to verify? mfaResp, err := u.webDev.SolveAuthn(res) require.NoError(t, err) - _, err = srv.Auth().validateMFAAuthResponse(ctx, u.username, mfaResp) + _, _, err = srv.Auth().validateMFAAuthResponse(ctx, mfaResp, u.username, false /* passwordless */) require.NoError(t, err) } @@ -476,6 +400,310 @@ func TestCreateRegisterChallenge(t *testing.T) { } } +// sshPubKey is a randomly-generated public key used for login tests. +// +// The corresponding private key is: +// -----BEGIN PRIVATE KEY----- +// MHcCAQEEIAKuZeB4WL4KAl5cnCrMYBy3kAX9qHt/g6OAbGGd7f3VoAoGCCqGSM49 +// AwEHoUQDQgAEa/6A3YLbc/TyJ4lED2BT8iThuw6HcrDX3dRixwkPDjWYBOP4qrJ/ +// jlGaPwXyuzeLuZgpFde7UiM1EHM2ClfGpw== +// -----END PRIVATE KEY----- +const sshPubKey = `ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGv+gN2C23P08ieJRA9gU/Ik4bsOh3Kw193UYscJDw41mATj+Kqyf45Rmj8F8rs3i7mYKRXXu1IjNRBzNgpXxqc=` + +func TestServer_AuthenticateUser_mfaDevices(t *testing.T) { + t.Parallel() + + svr := newTestTLSServer(t) + authServer := svr.Auth() + mfa := configureForMFA(t, svr) + username := mfa.User + password := mfa.Password + + tests := []struct { + name string + solveChallenge func(*proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) + }{ + {name: "OK TOTP device", solveChallenge: mfa.TOTPDev.SolveAuthn}, + {name: "OK Webauthn device", solveChallenge: mfa.WebDev.SolveAuthn}, + } + for _, test := range tests { + test := test + // makeRun is used to test both SSH and Web login by switching the + // authenticate function. + makeRun := func(authenticate func(*Server, AuthenticateUserRequest) error) func(t *testing.T) { + return func(t *testing.T) { + // 1st step: acquire challenge + challenge, err := authServer.CreateAuthenticateChallenge(context.Background(), &proto.CreateAuthenticateChallengeRequest{ + Request: &proto.CreateAuthenticateChallengeRequest_UserCredentials{UserCredentials: &proto.UserCredentials{ + Username: username, + Password: []byte(password), + }}, + }) + require.NoError(t, err) + + // Solve challenge (client-side) + resp, err := test.solveChallenge(challenge) + authReq := AuthenticateUserRequest{ + Username: username, + } + require.NoError(t, err) + + switch { + case resp.GetWebauthn() != nil: + authReq.Webauthn = wanlib.CredentialAssertionResponseFromProto(resp.GetWebauthn()) + case resp.GetTOTP() != nil: + authReq.OTP = &OTPCreds{ + Password: []byte(password), + Token: resp.GetTOTP().Code, + } + default: + t.Fatalf("Unexpected solved challenge type: %T", resp.Response) + } + + // 2nd step: finish login - either SSH or Web + require.NoError(t, authenticate(authServer, authReq)) + } + } + t.Run(test.name+"/ssh", makeRun(func(s *Server, req AuthenticateUserRequest) error { + _, err := s.AuthenticateSSHUser(AuthenticateSSHRequest{ + AuthenticateUserRequest: req, + PublicKey: []byte(sshPubKey), + TTL: 24 * time.Hour, + }) + return err + })) + t.Run(test.name+"/web", makeRun(func(s *Server, req AuthenticateUserRequest) error { + _, err := s.AuthenticateWebUser(req) + return err + })) + } +} + +func TestServer_Authenticate_passwordless(t *testing.T) { + t.Parallel() + svr := newTestTLSServer(t) + authServer := svr.Auth() + + // Configure Auth separately, we want a passwordless-capable device + // registered too. + ctx := context.Background() + authPreference, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{ + Type: constants.Local, + SecondFactor: constants.SecondFactorOptional, + Webauthn: &types.Webauthn{ + RPID: "localhost", + }, + }) + require.NoError(t, err) + require.NoError(t, authServer.SetAuthPreference(ctx, authPreference)) + + // Create user and initial WebAuthn device (MFA). + const user = "llama" + const password = "p@ssw0rd" + _, _, err = CreateUserAndRole(authServer, user, []string{"llama", "root"}) + require.NoError(t, err) + require.NoError(t, authServer.UpsertPassword(user, []byte(password))) + userClient, err := svr.NewClient(TestUser(user)) + require.NoError(t, err) + webDev, err := RegisterTestDevice( + ctx, userClient, "web", proto.DeviceType_DEVICE_TYPE_WEBAUTHN, nil /* authenticator */) + require.NoError(t, err) + + // Acquire a privilege token so we can register a passwordless device + // synchronously. + mfaChallenge, err := userClient.CreateAuthenticateChallenge(ctx, &proto.CreateAuthenticateChallengeRequest{ + Request: &proto.CreateAuthenticateChallengeRequest_ContextUser{ + ContextUser: &proto.ContextUser{}, // already authenticated + }, + }) + require.NoError(t, err) + mfaResp, err := webDev.SolveAuthn(mfaChallenge) + require.NoError(t, err) + token, err := userClient.CreatePrivilegeToken(ctx, &proto.CreatePrivilegeTokenRequest{ + ExistingMFAResponse: mfaResp, + }) + require.NoError(t, err) + + // Register passwordless device. + registerChallenge, err := userClient.CreateRegisterChallenge(ctx, &proto.CreateRegisterChallengeRequest{ + TokenID: token.GetName(), + DeviceType: proto.DeviceType_DEVICE_TYPE_WEBAUTHN, + DeviceUsage: proto.DeviceUsage_DEVICE_USAGE_PASSWORDLESS, + }) + require.NoError(t, err, "Failed to create passwordless registration challenge") + pwdKey, err := mocku2f.Create() + require.NoError(t, err) + pwdKey.SetPasswordless() + const origin = "https://localhost" + ccr, err := pwdKey.SignCredentialCreation(origin, wanlib.CredentialCreationFromProto(registerChallenge.GetWebauthn())) + require.NoError(t, err) + _, err = userClient.AddMFADeviceSync(ctx, &proto.AddMFADeviceSyncRequest{ + TokenID: token.GetName(), + NewDeviceName: "pwdless1", + NewMFAResponse: &proto.MFARegisterResponse{ + Response: &proto.MFARegisterResponse_Webauthn{ + Webauthn: wanlib.CredentialCreationResponseToProto(ccr), + }, + }, + }) + require.NoError(t, err, "Failed to register passwordless device") + + // userWebID is what identifies the user for usernameless/passwordless. + userWebID := registerChallenge.GetWebauthn().PublicKey.User.Id + + // Use a proxy client for now on; the user's identity isn't established yet. + proxyClient, err := svr.NewClient(TestBuiltin(types.RoleProxy)) + require.NoError(t, err) + + tests := []struct { + name string + authenticate func(t *testing.T, resp *wanlib.CredentialAssertionResponse) + }{ + { + name: "ssh", + authenticate: func(t *testing.T, resp *wanlib.CredentialAssertionResponse) { + loginResp, err := proxyClient.AuthenticateSSHUser(AuthenticateSSHRequest{ + AuthenticateUserRequest: AuthenticateUserRequest{ + Webauthn: resp, + }, + PublicKey: []byte(sshPubKey), + TTL: 24 * time.Hour, + }) + require.NoError(t, err, "Failed to perform passwordless authentication") + require.NotNil(t, loginResp, "SSH response nil") + require.NotEmpty(t, loginResp.Cert, "SSH certificate empty") + require.Equal(t, user, loginResp.Username, "Unexpected username") + }, + }, + { + name: "web", + authenticate: func(t *testing.T, resp *wanlib.CredentialAssertionResponse) { + session, err := proxyClient.AuthenticateWebUser(AuthenticateUserRequest{ + Webauthn: resp, + }) + require.NoError(t, err, "Failed to perform passwordless authentication") + require.Equal(t, user, session.GetUser(), "Unexpected username") + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Fail a login attempt so have a non-empty list of attempts. + _, err := proxyClient.AuthenticateSSHUser(AuthenticateSSHRequest{ + AuthenticateUserRequest: AuthenticateUserRequest{ + Username: user, + Webauthn: &wanlib.CredentialAssertionResponse{}, // bad response + }, + PublicKey: []byte(sshPubKey), + TTL: 24 * time.Hour, + }) + require.True(t, trace.IsAccessDenied(err), "got err = %v, want AccessDenied") + attempts, err := authServer.GetUserLoginAttempts(user) + require.NoError(t, err) + require.NotEmpty(t, attempts, "Want at least one failed login attempt") + + // Create a passwordless challenge. + mfaChallenge, err := proxyClient.CreateAuthenticateChallenge(ctx, &proto.CreateAuthenticateChallengeRequest{ + Request: &proto.CreateAuthenticateChallengeRequest_Passwordless{ + Passwordless: &proto.Passwordless{}, + }, + }) + require.NoError(t, err, "Failed to create passwordless challenge") + + // Sign challenge (mocks user interaction). + assertionResp, err := pwdKey.SignAssertion(origin, wanlib.CredentialAssertionFromProto(mfaChallenge.GetWebauthnChallenge())) + require.NoError(t, err) + assertionResp.AssertionResponse.UserHandle = userWebID // identify user, a real device would set this + + // Complete login procedure (SSH or Web). + test.authenticate(t, assertionResp) + + // Verify zeroed login attempts. This is a proxy for various other user + // checks (locked, etc). + attempts, err = authServer.GetUserLoginAttempts(user) + require.NoError(t, err) + require.Empty(t, attempts, "Login attempts not reset") + }) + } +} + +func TestServer_Authenticate_nonPasswordlessRequiresUsername(t *testing.T) { + t.Parallel() + svr := newTestTLSServer(t) + + // We don't mind about the specifics of the configuration, as long as we have + // a user and TOTP/WebAuthn devices. + mfa := configureForMFA(t, svr) + username := mfa.User + password := mfa.Password + + userClient, err := svr.NewClient(TestUser(username)) + require.NoError(t, err) + proxyClient, err := svr.NewClient(TestBuiltin(types.RoleProxy)) + require.NoError(t, err) + + ctx := context.Background() + tests := []struct { + name string + dev *TestDevice + wantErr string + }{ + { + name: "OTP", + dev: mfa.TOTPDev, + wantErr: "username", // Error contains "username" + }, + { + name: "WebAuthn", + dev: mfa.WebDev, + wantErr: "invalid Webauthn response", // generic error as it _could_ be a passwordless attempt + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mfaChallenge, err := userClient.CreateAuthenticateChallenge(ctx, &proto.CreateAuthenticateChallengeRequest{ + Request: &proto.CreateAuthenticateChallengeRequest_ContextUser{ + ContextUser: &proto.ContextUser{}, + }, + }) + require.NoError(t, err) + + mfaResp, err := test.dev.SolveAuthn(mfaChallenge) + require.NoError(t, err) + + var req AuthenticateUserRequest + switch { + case mfaResp.GetWebauthn() != nil: + req.Webauthn = wanlib.CredentialAssertionResponseFromProto(mfaResp.GetWebauthn()) + case mfaResp.GetTOTP() != nil: + req.OTP = &OTPCreds{ + Password: []byte(password), + Token: mfaResp.GetTOTP().Code, + } + } + + // SSH. + _, err = proxyClient.AuthenticateSSHUser(AuthenticateSSHRequest{ + AuthenticateUserRequest: req, + PublicKey: []byte(sshPubKey), + TTL: 24 * time.Hour, + }) + require.Error(t, err, "SSH authentication expected fail (missing username)") + require.Contains(t, err.Error(), test.wantErr) + + // Web. + _, err = proxyClient.AuthenticateWebUser(req) + require.Error(t, err, "Web authentication expected fail (missing username)") + require.Contains(t, err.Error(), test.wantErr) + + // Get one right so we don't lock the user between tests. + req.Username = username + _, err = proxyClient.AuthenticateWebUser(req) + require.NoError(t, err, "Web authentication expected to succeed") + }) + } +} + type configureMFAResp struct { User, Password string TOTPDev, WebDev *TestDevice diff --git a/lib/auth/auth_test.go b/lib/auth/auth_test.go index 077e08b01eb5f..08b7d2813712f 100644 --- a/lib/auth/auth_test.go +++ b/lib/auth/auth_test.go @@ -1030,6 +1030,44 @@ func TestEmitSSOLoginFailureEvent(t *testing.T) { }) } +func TestGenerateUserCertWithCertExtension(t *testing.T) { + t.Parallel() + ctx := context.Background() + p, err := newTestPack(ctx, t.TempDir()) + require.NoError(t, err) + + user, role, err := CreateUserAndRole(p.a, "test-user", []string{}) + require.NoError(t, err) + + extension := types.CertExtension{ + Name: "abc", + Value: "cde", + Type: types.CertExtensionType_SSH, + Mode: types.CertExtensionMode_EXTENSION, + } + options := role.GetOptions() + options.CertExtensions = []*types.CertExtension{&extension} + role.SetOptions(options) + + keygen := testauthority.New() + _, pub, err := keygen.GetNewKeyPairFromPool() + require.NoError(t, err) + certReq := certRequest{ + user: user, + checker: services.NewRoleSet(role), + publicKey: pub, + } + certs, err := p.a.generateUserCert(certReq) + require.NoError(t, err) + + key, err := sshutils.ParseCertificate(certs.SSH) + require.NoError(t, err) + + val, ok := key.Extensions[extension.Name] + require.True(t, ok) + require.Equal(t, extension.Value, val) +} + func TestGenerateUserCertWithLocks(t *testing.T) { t.Parallel() ctx := context.Background() diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 2396c4c8b6003..b173dd0eebba8 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -95,23 +95,6 @@ func (a *ServerWithRoles) withOptions(opts ...actionOption) actionConfig { return cfg } -func (a *ServerWithRoles) withStaticRoles() (actionConfig, error) { - user, err := a.authServer.GetUser(a.context.User.GetName(), false) - if err != nil { - return actionConfig{}, trace.Wrap(err) - } - - checker, err := services.FetchRoles(user.GetRoles(), a.authServer, user.GetTraits()) - if err != nil { - return actionConfig{}, trace.Wrap(err) - } - - return actionConfig{context: Context{ - User: user, - Checker: checker, - }}, nil -} - func (c actionConfig) action(namespace, resource string, verbs ...string) error { if len(verbs) == 0 { return trace.BadParameter("no verbs provided for authorization check on resource %q", resource) @@ -400,28 +383,28 @@ func (a *ServerWithRoles) CreateCertAuthority(ca types.CertAuthority) error { } // RotateCertAuthority starts or restarts certificate authority rotation process. -func (a *ServerWithRoles) RotateCertAuthority(req RotateRequest) error { +func (a *ServerWithRoles) RotateCertAuthority(ctx context.Context, req RotateRequest) error { if err := req.CheckAndSetDefaults(a.authServer.clock); err != nil { return trace.Wrap(err) } if err := a.action(apidefaults.Namespace, types.KindCertAuthority, types.VerbCreate, types.VerbUpdate); err != nil { return trace.Wrap(err) } - return a.authServer.RotateCertAuthority(req) + return a.authServer.RotateCertAuthority(ctx, req) } // RotateExternalCertAuthority rotates external certificate authority, // this method is called by a remote trusted cluster and is used to update // only public keys and certificates of the certificate authority. -func (a *ServerWithRoles) RotateExternalCertAuthority(ca types.CertAuthority) error { +func (a *ServerWithRoles) RotateExternalCertAuthority(ctx context.Context, ca types.CertAuthority) error { if ca == nil { return trace.BadParameter("missing certificate authority") } - ctx := &services.Context{User: a.context.User, Resource: ca} - if err := a.actionWithContext(ctx, apidefaults.Namespace, types.KindCertAuthority, types.VerbRotate); err != nil { + sctx := &services.Context{User: a.context.User, Resource: ca} + if err := a.actionWithContext(sctx, apidefaults.Namespace, types.KindCertAuthority, types.VerbRotate); err != nil { return trace.Wrap(err) } - return a.authServer.RotateExternalCertAuthority(ca) + return a.authServer.RotateExternalCertAuthority(ctx, ca) } // UpsertCertAuthority updates existing cert authority or updates the existing one. @@ -445,7 +428,7 @@ func (a *ServerWithRoles) CompareAndSwapCertAuthority(new, existing types.CertAu return a.authServer.CompareAndSwapCertAuthority(new, existing) } -func (a *ServerWithRoles) GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { +func (a *ServerWithRoles) GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { if err := a.action(apidefaults.Namespace, types.KindCertAuthority, types.VerbList, types.VerbReadNoSecrets); err != nil { return nil, trace.Wrap(err) } @@ -454,10 +437,10 @@ func (a *ServerWithRoles) GetCertAuthorities(caType types.CertAuthType, loadKeys return nil, trace.Wrap(err) } } - return a.authServer.GetCertAuthorities(caType, loadKeys, opts...) + return a.authServer.GetCertAuthorities(ctx, caType, loadKeys, opts...) } -func (a *ServerWithRoles) GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { +func (a *ServerWithRoles) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { if err := a.action(apidefaults.Namespace, types.KindCertAuthority, types.VerbReadNoSecrets); err != nil { return nil, trace.Wrap(err) } @@ -466,7 +449,7 @@ func (a *ServerWithRoles) GetCertAuthority(id types.CertAuthID, loadKeys bool, o return nil, trace.Wrap(err) } } - return a.authServer.GetCertAuthority(id, loadKeys, opts...) + return a.authServer.GetCertAuthority(ctx, id, loadKeys, opts...) } func (a *ServerWithRoles) GetDomainName() (string, error) { @@ -914,7 +897,7 @@ func (a *ServerWithRoles) ListResources(ctx context.Context, req proto.ListResou // https://github.com/gravitational/teleport/pull/1224 actionVerbs = []string{types.VerbList} - case types.KindDatabaseServer, types.KindAppServer, types.KindKubeService: + case types.KindDatabaseServer, types.KindAppServer, types.KindKubeService, types.KindWindowsDesktop: default: return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType) @@ -991,6 +974,8 @@ func (a *ServerWithRoles) checkAccessToResource(resource types.Resource) error { default: return trace.BadParameter("could not check access to server type %q", resource.GetKind()) } + case types.WindowsDesktop: + return a.checkAccessToWindowsDesktop(r) default: return trace.BadParameter("could not check access to resource type %T", r) } @@ -1041,6 +1026,42 @@ func (a *ServerWithRoles) listResourcesWithSort(ctx context.Context, req proto.L } resources = servers.AsResources() + case types.KindKubernetesCluster: + kubeservices, err := a.GetKubeServices(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + // Extract kube clusters into its own list. + clusters := []types.KubeCluster{} + for _, svc := range kubeservices { + for _, legacyCluster := range svc.GetKubernetesClusters() { + cluster, err := types.NewKubernetesClusterV3FromLegacyCluster(svc.GetNamespace(), legacyCluster) + if err != nil { + return nil, trace.Wrap(err) + } + clusters = append(clusters, cluster) + } + } + + sortedClusters := types.KubeClusters(types.DeduplicateKubeClusters(clusters)) + if err := sortedClusters.SortByCustom(req.SortBy); err != nil { + return nil, trace.Wrap(err) + } + resources = sortedClusters.AsResources() + + case types.KindWindowsDesktop: + windowsdesktops, err := a.GetWindowsDesktops(ctx, req.GetWindowsDesktopFilter()) + if err != nil { + return nil, trace.Wrap(err) + } + + desktops := types.WindowsDesktops(windowsdesktops) + if err := desktops.SortByCustom(req.SortBy); err != nil { + return nil, trace.Wrap(err) + } + resources = desktops.AsResources() + default: return nil, trace.NotImplemented("resource type %q is not supported for listResourcesWithSort", req.ResourceType) } @@ -1054,6 +1075,11 @@ func (a *ServerWithRoles) listResourcesWithSort(ctx context.Context, req proto.L return resp, nil } +// ListWindowsDesktops not implemented: can only be called locally. +func (a *ServerWithRoles) ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error) { + return nil, trace.NotImplemented(notImplementedMessage) +} + // ListNodes returns a paginated list of nodes filtered by user access. // // DELETE IN 10.0.0 in favor of ListResources. @@ -1622,17 +1648,7 @@ func (a *ServerWithRoles) getProxyPublicAddr() string { } func (a *ServerWithRoles) DeleteAccessRequest(ctx context.Context, name string) error { - cfg, err := a.withStaticRoles() - if err != nil { - return err - } - if err := cfg.action(apidefaults.Namespace, types.KindAccessRequest, types.VerbDelete); err != nil { - if trace.IsAccessDenied(err) { - if a.withOptions(quietAction(true)).action(apidefaults.Namespace, types.KindAccessRequest, types.VerbDelete) == nil { - // the user would've had permission with the roles granted by access requests - return trace.WrapWithMessage(err, "access request deletion through elevated roles is not allowed") - } - } + if err := a.action(apidefaults.Namespace, types.KindAccessRequest, types.VerbDelete); err != nil { return trace.Wrap(err) } return a.authServer.DeleteAccessRequest(ctx, name) @@ -1722,8 +1738,8 @@ func (a *ServerWithRoles) GenerateKeyPair(pass string) ([]byte, []byte, error) { } func (a *ServerWithRoles) GenerateHostCert( - key []byte, hostID, nodeName string, principals []string, clusterName string, role types.SystemRole, ttl time.Duration) ([]byte, error) { - + key []byte, hostID, nodeName string, principals []string, clusterName string, role types.SystemRole, ttl time.Duration, +) ([]byte, error) { if err := a.action(apidefaults.Namespace, types.KindHostCert, types.VerbCreate); err != nil { return nil, trace.Wrap(err) } @@ -2645,6 +2661,21 @@ func (a *ServerWithRoles) UpsertRole(ctx context.Context, role types.Role) error } // Some options are only available with enterprise subscription + if err := checkRoleFeatureSupport(role); err != nil { + return trace.Wrap(err) + } + + // access predicate syntax is not checked as part of normal role validation in order + // to allow the available namespaces to be extended without breaking compatibility with + // older nodes/proxies (which do not need to ever evaluate said predicates). + if err := services.ValidateAccessPredicates(role); err != nil { + return trace.Wrap(err) + } + + return a.authServer.UpsertRole(ctx, role) +} + +func checkRoleFeatureSupport(role types.Role) error { features := modules.GetModules().Features() options := role.GetOptions() allowReq, allowRev := role.GetAccessRequestConditions(types.Allow), role.GetAccessReviewConditions(types.Allow) @@ -2663,16 +2694,9 @@ func (a *ServerWithRoles) UpsertRole(ctx context.Context, role types.Role) error case features.AdvancedAccessWorkflows == false && !allowRev.IsZero(): return trace.AccessDenied( "role field allow.review_requests is only available in enterprise subscriptions") + default: + return nil } - - // access predicate syntax is not checked as part of normal role validation in order - // to allow the available namespaces to be extended without breaking compatibility with - // older nodes/proxies (which do not need to ever evaluate said predicates). - if err := services.ValidateAccessPredicates(role); err != nil { - return trace.Wrap(err) - } - - return a.authServer.UpsertRole(ctx, role) } // GetRole returns role by name @@ -3471,7 +3495,7 @@ func (a *ServerWithRoles) GenerateAppToken(ctx context.Context, req types.Genera return "", trace.Wrap(err) } - session, err := a.authServer.generateAppToken(req.Username, req.Roles, req.URI, req.Expires) + session, err := a.authServer.generateAppToken(ctx, req.Username, req.Roles, req.URI, req.Expires) if err != nil { return "", trace.Wrap(err) } @@ -4163,7 +4187,6 @@ func (a *ServerWithRoles) filterWindowsDesktops(desktops []types.WindowsDesktop) func (a *ServerWithRoles) checkAccessToWindowsDesktop(w types.WindowsDesktop) error { return a.context.Checker.CheckAccess(w, // MFA is not required for operations on desktop resources - // TODO(zmb3): per-session MFA for desktops will be added after general availability services.AccessMFAParams{Verified: true}, // Note: we don't use the Windows login matcher here, as we won't know what OS user // the user is trying to log in as until they initiate the connection. diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index 2c812a80fde7c..ae6359152c9d1 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -323,7 +323,7 @@ func TestGenerateUserCertsWithRoleRequest(t *testing.T) { }) require.NoError(t, err) - dummyUserRole, err := types.NewRole("dummy-user-role", types.RoleSpecV5{}) + dummyUserRole, err := types.NewRoleV3("dummy-user-role", types.RoleSpecV5{}) require.NoError(t, err) dummyUser, err := CreateUser(srv.Auth(), "dummy-user", dummyUserRole) @@ -1893,7 +1893,7 @@ func TestKindClusterConfig(t *testing.T) { }) t.Run("with KindClusterConfig privilege", func(t *testing.T) { - role, err := types.NewRole("test-role", types.RoleSpecV5{ + role, err := types.NewRoleV3("test-role", types.RoleSpecV5{ Allow: types.RoleConditions{ Rules: []types.Rule{ types.NewRule(types.KindClusterConfig, []string{types.VerbRead}), @@ -1909,94 +1909,6 @@ func TestKindClusterConfig(t *testing.T) { }) } -func TestNoElevatedAccessRequestDeletion(t *testing.T) { - t.Parallel() - ctx := context.Background() - - srv, err := NewTestAuthServer(TestAuthServerConfig{Dir: t.TempDir()}) - require.NoError(t, err) - t.Cleanup(func() { srv.Close() }) - - deleterRole, err := types.NewRole("deleter", types.RoleSpecV5{ - Allow: types.RoleConditions{ - Rules: []types.Rule{{ - Resources: []string{"access_request"}, - Verbs: []string{"delete"}, - }}, - }, - }) - require.NoError(t, err) - deleterUser, err := CreateUser(srv.AuthServer, "deletey", deleterRole) - require.NoError(t, err) - - requesterRole, err := types.NewRole("requester", types.RoleSpecV5{ - Allow: types.RoleConditions{ - Request: &types.AccessRequestConditions{ - Roles: []string{deleterRole.GetName()}, - }, - }, - }) - require.NoError(t, err) - requesterUser, err := CreateUser(srv.AuthServer, "requesty", requesterRole) - require.NoError(t, err) - - request, err := services.NewAccessRequest(requesterUser.GetName(), deleterRole.GetName()) - require.NoError(t, err) - // the request must be for an allowed user/role combination or it will get rejected - err = srv.AuthServer.CreateAccessRequest(ctx, request) - require.NoError(t, err) - - // requesty has used some other unspecified access request to get the - // deleter role in this identity - requesterAuthContext, err := srv.Authorizer.Authorize(context.WithValue(ctx, - ContextUser, - LocalUser{ - Username: requesterUser.GetName(), - Identity: tlsca.Identity{ - Username: requesterUser.GetName(), - Groups: []string{requesterRole.GetName(), deleterRole.GetName()}, - // a tlsca.Identity must have a nonempty Traits field or the - // roles will be reloaded from the backend during Authorize - Traits: map[string][]string{"nonempty": {}}, - }, - }, - )) - require.NoError(t, err) - requesterAuth := &ServerWithRoles{ - authServer: srv.AuthServer, - sessions: srv.SessionServer, - alog: srv.AuditLog, - context: *requesterAuthContext, - } - - err = requesterAuth.DeleteAccessRequest(ctx, request.GetName()) - require.True(t, trace.IsAccessDenied(err)) - // matches the message in lib/auth/auth_with_roles.go:(*ServerWithRoles).DeleteAccessRequest() - require.Contains(t, err.Error(), "deletion through elevated roles") - - deleterAuthContext, err := srv.Authorizer.Authorize(context.WithValue(ctx, - ContextUser, - LocalUser{ - Username: deleterUser.GetName(), - Identity: tlsca.Identity{ - Username: deleterUser.GetName(), - Groups: []string{deleterRole.GetName()}, - Traits: map[string][]string{"nonempty": {}}, - }, - }, - )) - require.NoError(t, err) - deleterAuth := &ServerWithRoles{ - authServer: srv.AuthServer, - sessions: srv.SessionServer, - alog: srv.AuditLog, - context: *deleterAuthContext, - } - - err = deleterAuth.DeleteAccessRequest(ctx, request.GetName()) - require.NoError(t, err) -} - func TestGetAndList_KubeServices(t *testing.T) { t.Parallel() ctx := context.Background() @@ -2162,3 +2074,241 @@ func TestListResources_NeedTotalCountFlag(t *testing.T) { require.NotEmpty(t, resp.NextKey) require.Empty(t, resp.TotalCount) } + +func TestGetAndList_WindowsDesktops(t *testing.T) { + t.Parallel() + ctx := context.Background() + srv := newTestTLSServer(t) + + // Create test desktops. + for i := 0; i < 5; i++ { + name := uuid.New().String() + desktop, err := types.NewWindowsDesktopV3(name, map[string]string{"name": name}, + types.WindowsDesktopSpecV3{Addr: "_", HostID: "_"}) + require.NoError(t, err) + require.NoError(t, srv.Auth().UpsertWindowsDesktop(ctx, desktop)) + } + + // Test all has been upserted. + testDesktops, err := srv.Auth().GetWindowsDesktops(ctx, types.WindowsDesktopFilter{}) + require.NoError(t, err) + require.Len(t, testDesktops, 5) + + testResources := types.WindowsDesktops(testDesktops).AsResources() + + // Create user, role, and client. + username := "user" + user, role, err := CreateUserAndRole(srv.Auth(), username, nil) + require.NoError(t, err) + identity := TestUser(user.GetName()) + clt, err := srv.NewClient(identity) + require.NoError(t, err) + + // Base request. + listRequest := proto.ListResourcesRequest{ + ResourceType: types.KindWindowsDesktop, + Limit: int32(len(testDesktops) + 1), + } + + // Permit user to get the first desktop. + role.SetWindowsDesktopLabels(types.Allow, types.Labels{"name": {testDesktops[0].GetName()}}) + require.NoError(t, srv.Auth().UpsertRole(ctx, role)) + + desktops, err := clt.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{}) + require.NoError(t, err) + require.EqualValues(t, 1, len(desktops)) + require.Empty(t, cmp.Diff(testDesktops[0:1], desktops)) + + resp, err := clt.ListResources(ctx, listRequest) + require.NoError(t, err) + require.Len(t, resp.Resources, 1) + require.Empty(t, cmp.Diff(testResources[0:1], resp.Resources)) + require.Empty(t, resp.NextKey) + require.Empty(t, resp.TotalCount) + + // Permit user to get all desktops. + role.SetWindowsDesktopLabels(types.Allow, types.Labels{types.Wildcard: {types.Wildcard}}) + require.NoError(t, srv.Auth().UpsertRole(ctx, role)) + desktops, err = clt.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{}) + require.NoError(t, err) + require.EqualValues(t, len(testDesktops), len(desktops)) + require.Empty(t, cmp.Diff(testDesktops, desktops)) + + resp, err = clt.ListResources(ctx, listRequest) + require.NoError(t, err) + require.Len(t, resp.Resources, len(testResources)) + require.Empty(t, cmp.Diff(testResources, resp.Resources)) + require.Empty(t, resp.NextKey) + require.Empty(t, resp.TotalCount) + + // Test sorting is supported. + withSort := listRequest + withSort.SortBy = types.SortBy{IsDesc: true, Field: types.ResourceMetadataName} + resp, err = clt.ListResources(ctx, withSort) + require.NoError(t, err) + require.Len(t, resp.Resources, len(testResources)) + desktops, err = types.ResourcesWithLabels(resp.Resources).AsWindowsDesktops() + require.NoError(t, err) + names, err := types.WindowsDesktops(desktops).GetFieldVals(types.ResourceMetadataName) + require.NoError(t, err) + require.IsDecreasing(t, names) + + // Filter by labels. + withLabels := listRequest + withLabels.Labels = map[string]string{"name": testDesktops[0].GetName()} + resp, err = clt.ListResources(ctx, withLabels) + require.NoError(t, err) + require.Len(t, resp.Resources, 1) + require.Empty(t, cmp.Diff(testResources[0:1], resp.Resources)) + require.Empty(t, resp.NextKey) + require.Empty(t, resp.TotalCount) + + // Test search keywords match. + withSearchKeywords := listRequest + withSearchKeywords.SearchKeywords = []string{"name", testDesktops[0].GetName()} + resp, err = clt.ListResources(ctx, withSearchKeywords) + require.NoError(t, err) + require.Len(t, resp.Resources, 1) + require.Empty(t, cmp.Diff(testResources[0:1], resp.Resources)) + + // Test predicate match. + withExpression := listRequest + withExpression.PredicateExpression = fmt.Sprintf(`labels.name == "%s"`, testDesktops[0].GetName()) + resp, err = clt.ListResources(ctx, withExpression) + require.NoError(t, err) + require.Len(t, resp.Resources, 1) + require.Empty(t, cmp.Diff(testResources[0:1], resp.Resources)) + + // Deny user to get the first desktop. + role.SetWindowsDesktopLabels(types.Deny, types.Labels{"name": {testDesktops[0].GetName()}}) + require.NoError(t, srv.Auth().UpsertRole(ctx, role)) + + desktops, err = clt.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{}) + require.NoError(t, err) + require.EqualValues(t, len(testDesktops[1:]), len(desktops)) + require.Empty(t, cmp.Diff(testDesktops[1:], desktops)) + + resp, err = clt.ListResources(ctx, listRequest) + require.NoError(t, err) + require.Len(t, resp.Resources, len(testResources[1:])) + require.Empty(t, cmp.Diff(testResources[1:], resp.Resources)) + + // Deny user all desktops. + role.SetWindowsDesktopLabels(types.Deny, types.Labels{types.Wildcard: {types.Wildcard}}) + require.NoError(t, srv.Auth().UpsertRole(ctx, role)) + + desktops, err = clt.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{}) + require.NoError(t, err) + require.EqualValues(t, 0, len(desktops)) + require.Empty(t, cmp.Diff([]types.WindowsDesktop{}, desktops)) + + resp, err = clt.ListResources(ctx, listRequest) + require.NoError(t, err) + require.Len(t, resp.Resources, 0) + require.Empty(t, cmp.Diff([]types.ResourceWithLabels{}, resp.Resources)) +} + +func TestListResources_KindKubernetesCluster(t *testing.T) { + t.Parallel() + ctx := context.Background() + srv, err := NewTestAuthServer(TestAuthServerConfig{Dir: t.TempDir()}) + require.NoError(t, err) + + authContext, err := srv.Authorizer.Authorize(context.WithValue(ctx, ContextUser, TestBuiltin(types.RoleProxy).I)) + require.NoError(t, err) + + s := &ServerWithRoles{ + authServer: srv.AuthServer, + sessions: srv.SessionServer, + alog: srv.AuditLog, + context: *authContext, + } + + testNames := []string{"a", "b", "c", "d"} + + // Add some kube services. + kubeService, err := types.NewServer("bar", types.KindKubeService, types.ServerSpecV2{ + KubernetesClusters: []*types.KubernetesCluster{{Name: "d"}, {Name: "b"}, {Name: "a"}}, + }) + require.NoError(t, err) + _, err = s.UpsertKubeServiceV2(ctx, kubeService) + require.NoError(t, err) + + // Include a duplicate cluster name to test deduplicate. + kubeService, err = types.NewServer("foo", types.KindKubeService, types.ServerSpecV2{ + KubernetesClusters: []*types.KubernetesCluster{{Name: "a"}, {Name: "c"}}, + }) + require.NoError(t, err) + _, err = s.UpsertKubeServiceV2(ctx, kubeService) + require.NoError(t, err) + + // Test upsert. + kubeservices, err := s.GetKubeServices(ctx) + require.NoError(t, err) + require.Len(t, kubeservices, 2) + + t.Run("fetch all", func(t *testing.T) { + t.Parallel() + + res, err := s.ListResources(ctx, proto.ListResourcesRequest{ + ResourceType: types.KindKubernetesCluster, + Limit: 10, + }) + require.NoError(t, err) + require.Len(t, res.Resources, len(testNames)) + require.Empty(t, res.NextKey) + require.Empty(t, res.TotalCount) + + clusters, err := types.ResourcesWithLabels(res.Resources).AsKubeClusters() + require.NoError(t, err) + names, err := types.KubeClusters(clusters).GetFieldVals(types.ResourceMetadataName) + require.NoError(t, err) + require.ElementsMatch(t, names, testNames) + }) + + t.Run("start keys", func(t *testing.T) { + t.Parallel() + + // First fetch. + res, err := s.ListResources(ctx, proto.ListResourcesRequest{ + ResourceType: types.KindKubernetesCluster, + Limit: 1, + }) + require.NoError(t, err) + require.Len(t, res.Resources, 1) + require.Equal(t, kubeservices[0].GetKubernetesClusters()[1].Name, res.NextKey) + + // Second fetch. + res, err = s.ListResources(ctx, proto.ListResourcesRequest{ + ResourceType: types.KindKubernetesCluster, + Limit: 1, + StartKey: res.NextKey, + }) + require.NoError(t, err) + require.Len(t, res.Resources, 1) + require.Equal(t, kubeservices[0].GetKubernetesClusters()[2].Name, res.NextKey) + }) + + t.Run("fetch with sort and total count", func(t *testing.T) { + t.Parallel() + res, err := s.ListResources(ctx, proto.ListResourcesRequest{ + ResourceType: types.KindKubernetesCluster, + Limit: 10, + SortBy: types.SortBy{ + IsDesc: true, + Field: types.ResourceMetadataName, + }, + NeedTotalCount: true, + }) + require.NoError(t, err) + require.Empty(t, res.NextKey) + require.Len(t, res.Resources, len(testNames)) + require.Equal(t, res.TotalCount, len(testNames)) + + clusters, err := types.ResourcesWithLabels(res.Resources).AsKubeClusters() + require.NoError(t, err) + names, err := types.KubeClusters(clusters).GetFieldVals(types.ResourceMetadataName) + require.NoError(t, err) + require.IsDecreasing(t, names) + }) +} diff --git a/lib/auth/bot.go b/lib/auth/bot.go index cbc0ddc729478..3eb553e4414ff 100644 --- a/lib/auth/bot.go +++ b/lib/auth/bot.go @@ -44,7 +44,7 @@ func BotResourceName(botName string) string { // createBotRole creates a role from a bot template with the given parameters. func createBotRole(ctx context.Context, s *Server, botName string, resourceName string, roleRequests []string) (types.Role, error) { - role, err := types.NewRole(resourceName, types.RoleSpecV5{ + role, err := types.NewRoleV3(resourceName, types.RoleSpecV5{ Options: types.RoleOptions{ // TODO: inherit TTLs from cert length? MaxSessionTTL: types.Duration(12 * time.Hour), @@ -242,7 +242,7 @@ func (s *Server) getBotUsers(ctx context.Context) ([]types.User, error) { // random dynamic provision token which allows bots to join with the given // botName. Returns the token and any error. func (s *Server) checkOrCreateBotToken(ctx context.Context, req *proto.CreateBotRequest) (types.ProvisionToken, error) { - resourceName := BotResourceName(req.Name) + botName := req.Name // if the request includes a TokenID it should already exist if req.TokenID != "" { @@ -258,9 +258,9 @@ func (s *Server) checkOrCreateBotToken(ctx context.Context, req *proto.CreateBot return nil, trace.BadParameter("token %q is not valid for role %q", req.TokenID, types.RoleBot) } - if provisionToken.GetBotName() != resourceName { + if provisionToken.GetBotName() != botName { return nil, trace.BadParameter("token %q is valid for bot with name %q, not %q", - req.TokenID, provisionToken.GetBotName(), resourceName) + req.TokenID, provisionToken.GetBotName(), botName) } switch provisionToken.GetJoinMethod() { case types.JoinMethodToken, types.JoinMethodIAM: @@ -286,7 +286,7 @@ func (s *Server) checkOrCreateBotToken(ctx context.Context, req *proto.CreateBot tokenSpec := types.ProvisionTokenSpecV2{ Roles: []types.SystemRole{types.RoleBot}, JoinMethod: types.JoinMethodToken, - BotName: resourceName, + BotName: botName, } token, err := types.NewProvisionTokenFromSpec(tokenName, time.Now().Add(ttl), tokenSpec) if err != nil { diff --git a/lib/auth/clt.go b/lib/auth/clt.go index fff5a88a55d1a..68f75a5c99148 100644 --- a/lib/auth/clt.go +++ b/lib/auth/clt.go @@ -22,7 +22,7 @@ import ( "crypto/tls" "encoding/json" "fmt" - "io/ioutil" + "io" "net" "net/http" "net/url" @@ -269,28 +269,28 @@ func ClientTimeout(timeout time.Duration) roundtrip.ClientParam { } // PostJSON is a generic method that issues http POST request to the server -func (c *Client) PostJSON(endpoint string, val interface{}) (*roundtrip.Response, error) { - return httplib.ConvertResponse(c.Client.PostJSON(context.TODO(), endpoint, val)) +func (c *Client) PostJSON(ctx context.Context, endpoint string, val interface{}) (*roundtrip.Response, error) { + return httplib.ConvertResponse(c.Client.PostJSON(ctx, endpoint, val)) } // PutJSON is a generic method that issues http PUT request to the server -func (c *Client) PutJSON(endpoint string, val interface{}) (*roundtrip.Response, error) { - return httplib.ConvertResponse(c.Client.PutJSON(context.TODO(), endpoint, val)) +func (c *Client) PutJSON(ctx context.Context, endpoint string, val interface{}) (*roundtrip.Response, error) { + return httplib.ConvertResponse(c.Client.PutJSON(ctx, endpoint, val)) } // PostForm is a generic method that issues http POST request to the server -func (c *Client) PostForm(endpoint string, vals url.Values, files ...roundtrip.File) (*roundtrip.Response, error) { - return httplib.ConvertResponse(c.Client.PostForm(context.TODO(), endpoint, vals, files...)) +func (c *Client) PostForm(ctx context.Context, endpoint string, vals url.Values, files ...roundtrip.File) (*roundtrip.Response, error) { + return httplib.ConvertResponse(c.Client.PostForm(ctx, endpoint, vals, files...)) } // Get issues http GET request to the server -func (c *Client) Get(u string, params url.Values) (*roundtrip.Response, error) { - return httplib.ConvertResponse(c.Client.Get(context.TODO(), u, params)) +func (c *Client) Get(ctx context.Context, u string, params url.Values) (*roundtrip.Response, error) { + return httplib.ConvertResponse(c.Client.Get(ctx, u, params)) } // Delete issues http Delete Request to the server -func (c *Client) Delete(u string) (*roundtrip.Response, error) { - return httplib.ConvertResponse(c.Client.Delete(context.TODO(), u)) +func (c *Client) Delete(ctx context.Context, u string) (*roundtrip.Response, error) { + return httplib.ConvertResponse(c.Client.Delete(ctx, u)) } // ProcessKubeCSR processes CSR request against Kubernetes CA, returns @@ -299,7 +299,7 @@ func (c *Client) ProcessKubeCSR(req KubeCSR) (*KubeCSRResponse, error) { if err := req.CheckAndSetDefaults(); err != nil { return nil, trace.Wrap(err) } - out, err := c.PostJSON(c.Endpoint("kube", "csr"), req) + out, err := c.PostJSON(context.TODO(), c.Endpoint("kube", "csr"), req) if err != nil { return nil, trace.Wrap(err) } @@ -316,7 +316,7 @@ func (c *Client) GetSessions(namespace string) ([]session.Session, error) { if namespace == "" { return nil, trace.BadParameter(MissingNamespaceError) } - out, err := c.Get(c.Endpoint("namespaces", namespace, "sessions"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("namespaces", namespace, "sessions"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -336,7 +336,7 @@ func (c *Client) GetSession(namespace string, id session.ID) (*session.Session, if err := id.Check(); err != nil { return nil, trace.Wrap(err) } - out, err := c.Get(c.Endpoint("namespaces", namespace, "sessions", string(id)), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("namespaces", namespace, "sessions", string(id)), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -352,7 +352,7 @@ func (c *Client) DeleteSession(namespace string, id session.ID) error { if namespace == "" { return trace.BadParameter(MissingNamespaceError) } - _, err := c.Delete(c.Endpoint("namespaces", namespace, "sessions", string(id))) + _, err := c.Delete(context.TODO(), c.Endpoint("namespaces", namespace, "sessions", string(id))) return trace.Wrap(err) } @@ -361,7 +361,7 @@ func (c *Client) CreateSession(sess session.Session) error { if sess.Namespace == "" { return trace.BadParameter(MissingNamespaceError) } - _, err := c.PostJSON(c.Endpoint("namespaces", sess.Namespace, "sessions"), createSessionReq{Session: sess}) + _, err := c.PostJSON(context.TODO(), c.Endpoint("namespaces", sess.Namespace, "sessions"), createSessionReq{Session: sess}) return trace.Wrap(err) } @@ -370,13 +370,13 @@ func (c *Client) UpdateSession(req session.UpdateRequest) error { if err := req.Check(); err != nil { return trace.Wrap(err) } - _, err := c.PutJSON(c.Endpoint("namespaces", req.Namespace, "sessions", string(req.ID)), updateSessionReq{Update: req}) + _, err := c.PutJSON(context.TODO(), c.Endpoint("namespaces", req.Namespace, "sessions", string(req.ID)), updateSessionReq{Update: req}) return trace.Wrap(err) } // GetDomainName returns local auth domain of the current auth server func (c *Client) GetDomainName() (string, error) { - out, err := c.Get(c.Endpoint("domain"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("domain"), url.Values{}) if err != nil { return "", trace.Wrap(err) } @@ -390,7 +390,7 @@ func (c *Client) GetDomainName() (string, error) { // GetClusterCACert returns the PEM-encoded TLS certs for the local cluster. If // the cluster has multiple TLS certs, they will all be concatenated. func (c *Client) GetClusterCACert() (*LocalCAResponse, error) { - out, err := c.Get(c.Endpoint("cacert"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("cacert"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -416,19 +416,19 @@ func (c *Client) CreateCertAuthority(ca types.CertAuthority) error { } // RotateCertAuthority starts or restarts certificate authority rotation process. -func (c *Client) RotateCertAuthority(req RotateRequest) error { +func (c *Client) RotateCertAuthority(ctx context.Context, req RotateRequest) error { caType := "all" if req.Type != "" { caType = string(req.Type) } - _, err := c.PostJSON(c.Endpoint("authorities", caType, "rotate"), req) + _, err := c.PostJSON(ctx, c.Endpoint("authorities", caType, "rotate"), req) return trace.Wrap(err) } // RotateExternalCertAuthority rotates external certificate authority, // this method is used to update only public keys and certificates of the // the certificate authorities of trusted clusters. -func (c *Client) RotateExternalCertAuthority(ca types.CertAuthority) error { +func (c *Client) RotateExternalCertAuthority(ctx context.Context, ca types.CertAuthority) error { if err := services.ValidateCertAuthority(ca); err != nil { return trace.Wrap(err) } @@ -436,7 +436,7 @@ func (c *Client) RotateExternalCertAuthority(ca types.CertAuthority) error { if err != nil { return trace.Wrap(err) } - _, err = c.PostJSON(c.Endpoint("authorities", string(ca.GetType()), "rotate", "external"), + _, err = c.PostJSON(ctx, c.Endpoint("authorities", string(ca.GetType()), "rotate", "external"), &rotateExternalCertAuthorityRawReq{CA: data}) return trace.Wrap(err) } @@ -450,7 +450,7 @@ func (c *Client) UpsertCertAuthority(ca types.CertAuthority) error { if err != nil { return trace.Wrap(err) } - _, err = c.PostJSON(c.Endpoint("authorities", string(ca.GetType())), + _, err = c.PostJSON(context.TODO(), c.Endpoint("authorities", string(ca.GetType())), &upsertCertAuthorityRawReq{CA: data}) return trace.Wrap(err) } @@ -462,11 +462,11 @@ func (c *Client) CompareAndSwapCertAuthority(new, existing types.CertAuthority) } // GetCertAuthorities returns a list of certificate authorities -func (c *Client) GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { +func (c *Client) GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { if err := caType.Check(); err != nil { return nil, trace.Wrap(err) } - out, err := c.Get(c.Endpoint("authorities", string(caType)), url.Values{ + out, err := c.Get(ctx, c.Endpoint("authorities", string(caType)), url.Values{ "load_keys": []string{fmt.Sprintf("%t", loadKeys)}, }) if err != nil { @@ -489,11 +489,11 @@ func (c *Client) GetCertAuthorities(caType types.CertAuthType, loadKeys bool, op // GetCertAuthority returns certificate authority by given id. Parameter loadSigningKeys // controls if signing keys are loaded -func (c *Client) GetCertAuthority(id types.CertAuthID, loadSigningKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { +func (c *Client) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadSigningKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { if err := id.Check(); err != nil { return nil, trace.Wrap(err) } - out, err := c.Get(c.Endpoint("authorities", string(id.Type), id.DomainName), url.Values{ + out, err := c.Get(ctx, c.Endpoint("authorities", string(id.Type), id.DomainName), url.Values{ "load_keys": []string{fmt.Sprintf("%t", loadSigningKeys)}, }) if err != nil { @@ -507,7 +507,7 @@ func (c *Client) DeleteCertAuthority(id types.CertAuthID) error { if err := id.Check(); err != nil { return trace.Wrap(err) } - _, err := c.Delete(c.Endpoint("authorities", string(id.Type), id.DomainName)) + _, err := c.Delete(context.TODO(), c.Endpoint("authorities", string(id.Type), id.DomainName)) return trace.Wrap(err) } @@ -530,7 +530,7 @@ func (c *Client) DeactivateCertAuthority(id types.CertAuthID) error { // If token is not supplied, it will be auto generated and returned. // If TTL is not supplied, token will be valid until removed. func (c *Client) GenerateToken(ctx context.Context, req GenerateTokenRequest) (string, error) { - out, err := c.PostJSON(c.Endpoint("tokens"), req) + out, err := c.PostJSON(ctx, c.Endpoint("tokens"), req) if err != nil { return "", trace.Wrap(err) } @@ -547,7 +547,7 @@ func (c *Client) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin if err := req.CheckAndSetDefaults(); err != nil { return nil, trace.Wrap(err) } - out, err := c.PostJSON(c.Endpoint("tokens", "register"), req) + out, err := c.PostJSON(ctx, c.Endpoint("tokens", "register"), req) if err != nil { return nil, trace.Wrap(err) } @@ -557,19 +557,12 @@ func (c *Client) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.Wrap(err) } - // If we got certs, we're done, however, we may be talking to a Teleport 9 or earlier server, - // which still sends back the legacy JSON format. - if len(certs.SSH) > 0 && len(certs.TLS) > 0 { - return &certs, nil - } - - // DELETE IN 10.0.0 (zmb3) - return UnmarshalLegacyCerts(out.Bytes()) + return &certs, nil } // RegisterNewAuthServer is used to register new auth server with token func (c *Client) RegisterNewAuthServer(ctx context.Context, token string) error { - _, err := c.PostJSON(c.Endpoint("tokens", "register", "auth"), registerNewAuthServerReq{ + _, err := c.PostJSON(ctx, c.Endpoint("tokens", "register", "auth"), registerNewAuthServerReq{ Token: token, }) return trace.Wrap(err) @@ -603,7 +596,7 @@ func (c *Client) UpsertNodes(namespace string, servers []types.Server) error { Namespace: namespace, Nodes: bytes, } - _, err = c.PutJSON(c.Endpoint("namespaces", namespace, "nodes"), args) + _, err = c.PutJSON(context.TODO(), c.Endpoint("namespaces", namespace, "nodes"), args) return trace.Wrap(err) } @@ -617,7 +610,7 @@ func (c *Client) UpsertReverseTunnel(tunnel types.ReverseTunnel) error { args := &upsertReverseTunnelRawReq{ ReverseTunnel: data, } - _, err = c.PostJSON(c.Endpoint("reversetunnels"), args) + _, err = c.PostJSON(context.TODO(), c.Endpoint("reversetunnels"), args) return trace.Wrap(err) } @@ -628,7 +621,7 @@ func (c *Client) GetReverseTunnel(name string, opts ...services.MarshalOption) ( // GetReverseTunnels returns the list of created reverse tunnels func (c *Client) GetReverseTunnels(opts ...services.MarshalOption) ([]types.ReverseTunnel, error) { - out, err := c.Get(c.Endpoint("reversetunnels"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("reversetunnels"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -655,7 +648,7 @@ func (c *Client) DeleteReverseTunnel(domainName string) error { if strings.TrimSpace(domainName) == "" { return trace.BadParameter("empty domain name") } - _, err := c.Delete(c.Endpoint("reversetunnels", domainName)) + _, err := c.Delete(context.TODO(), c.Endpoint("reversetunnels", domainName)) return trace.Wrap(err) } @@ -668,7 +661,7 @@ func (c *Client) UpsertTunnelConnection(conn types.TunnelConnection) error { args := &upsertTunnelConnectionRawReq{ TunnelConnection: data, } - _, err = c.PostJSON(c.Endpoint("tunnelconnections"), args) + _, err = c.PostJSON(context.TODO(), c.Endpoint("tunnelconnections"), args) return trace.Wrap(err) } @@ -677,7 +670,7 @@ func (c *Client) GetTunnelConnections(clusterName string, opts ...services.Marsh if clusterName == "" { return nil, trace.BadParameter("missing cluster name parameter") } - out, err := c.Get(c.Endpoint("tunnelconnections", clusterName), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("tunnelconnections", clusterName), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -698,7 +691,7 @@ func (c *Client) GetTunnelConnections(clusterName string, opts ...services.Marsh // GetAllTunnelConnections returns all tunnel connections func (c *Client) GetAllTunnelConnections(opts ...services.MarshalOption) ([]types.TunnelConnection, error) { - out, err := c.Get(c.Endpoint("tunnelconnections"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("tunnelconnections"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -725,7 +718,7 @@ func (c *Client) DeleteTunnelConnection(clusterName string, connName string) err if connName == "" { return trace.BadParameter("missing parameter connection name") } - _, err := c.Delete(c.Endpoint("tunnelconnections", clusterName, connName)) + _, err := c.Delete(context.TODO(), c.Endpoint("tunnelconnections", clusterName, connName)) return trace.Wrap(err) } @@ -734,7 +727,7 @@ func (c *Client) DeleteTunnelConnections(clusterName string) error { if clusterName == "" { return trace.BadParameter("missing parameter cluster name") } - _, err := c.Delete(c.Endpoint("tunnelconnections", clusterName)) + _, err := c.Delete(context.TODO(), c.Endpoint("tunnelconnections", clusterName)) return trace.Wrap(err) } @@ -745,7 +738,7 @@ func (c *Client) DeleteAllTokens() error { // DeleteAllTunnelConnections deletes all tunnel connections func (c *Client) DeleteAllTunnelConnections() error { - _, err := c.Delete(c.Endpoint("tunnelconnections")) + _, err := c.Delete(context.TODO(), c.Endpoint("tunnelconnections")) return trace.Wrap(err) } @@ -761,7 +754,7 @@ func (c *Client) GetUserLoginAttempts(user string) ([]services.LoginAttempt, err // GetRemoteClusters returns a list of remote clusters func (c *Client) GetRemoteClusters(opts ...services.MarshalOption) ([]types.RemoteCluster, error) { - out, err := c.Get(c.Endpoint("remoteclusters"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("remoteclusters"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -785,7 +778,7 @@ func (c *Client) GetRemoteCluster(clusterName string) (types.RemoteCluster, erro if clusterName == "" { return nil, trace.BadParameter("missing cluster name") } - out, err := c.Get(c.Endpoint("remoteclusters", clusterName), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("remoteclusters", clusterName), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -797,13 +790,13 @@ func (c *Client) DeleteRemoteCluster(clusterName string) error { if clusterName == "" { return trace.BadParameter("missing parameter cluster name") } - _, err := c.Delete(c.Endpoint("remoteclusters", clusterName)) + _, err := c.Delete(context.TODO(), c.Endpoint("remoteclusters", clusterName)) return trace.Wrap(err) } // DeleteAllRemoteClusters deletes all remote clusters func (c *Client) DeleteAllRemoteClusters() error { - _, err := c.Delete(c.Endpoint("remoteclusters")) + _, err := c.Delete(context.TODO(), c.Endpoint("remoteclusters")) return trace.Wrap(err) } @@ -816,7 +809,7 @@ func (c *Client) CreateRemoteCluster(rc types.RemoteCluster) error { args := &createRemoteClusterRawReq{ RemoteCluster: data, } - _, err = c.PostJSON(c.Endpoint("remoteclusters"), args) + _, err = c.PostJSON(context.TODO(), c.Endpoint("remoteclusters"), args) return trace.Wrap(err) } @@ -830,13 +823,13 @@ func (c *Client) UpsertAuthServer(s types.Server) error { args := &upsertServerRawReq{ Server: data, } - _, err = c.PostJSON(c.Endpoint("authservers"), args) + _, err = c.PostJSON(context.TODO(), c.Endpoint("authservers"), args) return trace.Wrap(err) } // GetAuthServers returns the list of auth servers registered in the cluster. func (c *Client) GetAuthServers() ([]types.Server, error) { - out, err := c.Get(c.Endpoint("authservers"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("authservers"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -875,13 +868,13 @@ func (c *Client) UpsertProxy(s types.Server) error { args := &upsertServerRawReq{ Server: data, } - _, err = c.PostJSON(c.Endpoint("proxies"), args) + _, err = c.PostJSON(context.TODO(), c.Endpoint("proxies"), args) return trace.Wrap(err) } // GetProxies returns the list of auth servers registered in the cluster. func (c *Client) GetProxies() ([]types.Server, error) { - out, err := c.Get(c.Endpoint("proxies"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("proxies"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -902,7 +895,7 @@ func (c *Client) GetProxies() ([]types.Server, error) { // DeleteAllProxies deletes all proxies func (c *Client) DeleteAllProxies() error { - _, err := c.Delete(c.Endpoint("proxies")) + _, err := c.Delete(context.TODO(), c.Endpoint("proxies")) if err != nil { return trace.Wrap(err) } @@ -914,7 +907,7 @@ func (c *Client) DeleteProxy(name string) error { if name == "" { return trace.BadParameter("missing parameter name") } - _, err := c.Delete(c.Endpoint("proxies", name)) + _, err := c.Delete(context.TODO(), c.Endpoint("proxies", name)) if err != nil { return trace.Wrap(err) } @@ -924,6 +917,7 @@ func (c *Client) DeleteProxy(name string) error { // UpsertPassword updates web access password for the user func (c *Client) UpsertPassword(user string, password []byte) error { _, err := c.PostJSON( + context.TODO(), c.Endpoint("users", user, "web", "password"), upsertPasswordReq{ Password: string(password), @@ -941,7 +935,7 @@ func (c *Client) UpsertUser(user types.User) error { if err != nil { return trace.Wrap(err) } - _, err = c.PostJSON(c.Endpoint("users"), &upsertUserRawReq{User: data}) + _, err = c.PostJSON(context.TODO(), c.Endpoint("users"), &upsertUserRawReq{User: data}) return trace.Wrap(err) } @@ -952,13 +946,14 @@ func (c *Client) CompareAndSwapUser(ctx context.Context, new, expected types.Use // ChangePassword updates users password based on the old password. func (c *Client) ChangePassword(req services.ChangePasswordReq) error { - _, err := c.PutJSON(c.Endpoint("users", req.User, "web", "password"), req) + _, err := c.PutJSON(context.TODO(), c.Endpoint("users", req.User, "web", "password"), req) return trace.Wrap(err) } // CheckPassword checks if the suplied web access password is valid. func (c *Client) CheckPassword(user string, password []byte, otpToken string) error { _, err := c.PostJSON( + context.TODO(), c.Endpoint("users", user, "web", "password", "check"), checkPasswordReq{ Password: string(password), @@ -971,6 +966,7 @@ func (c *Client) CheckPassword(user string, password []byte, otpToken string) er // valid web session func (c *Client) ExtendWebSession(req WebSessionReq) (types.WebSession, error) { out, err := c.PostJSON( + context.TODO(), c.Endpoint("users", req.User, "web", "sessions"), req) if err != nil { return nil, trace.Wrap(err) @@ -981,6 +977,7 @@ func (c *Client) ExtendWebSession(req WebSessionReq) (types.WebSession, error) { // CreateWebSession creates a new web session for a user func (c *Client) CreateWebSession(user string) (types.WebSession, error) { out, err := c.PostJSON( + context.TODO(), c.Endpoint("users", user, "web", "sessions"), WebSessionReq{User: user}, ) @@ -994,6 +991,7 @@ func (c *Client) CreateWebSession(user string) (types.WebSession, error) { // in case if authentication is successful func (c *Client) AuthenticateWebUser(req AuthenticateUserRequest) (types.WebSession, error) { out, err := c.PostJSON( + context.TODO(), c.Endpoint("users", req.Username, "web", "authenticate"), req, ) @@ -1007,6 +1005,7 @@ func (c *Client) AuthenticateWebUser(req AuthenticateUserRequest) (types.WebSess // short lived certificates as a result func (c *Client) AuthenticateSSHUser(req AuthenticateSSHRequest) (*SSHLoginResponse, error) { out, err := c.PostJSON( + context.TODO(), c.Endpoint("users", req.Username, "ssh", "authenticate"), req, ) @@ -1024,6 +1023,7 @@ func (c *Client) AuthenticateSSHUser(req AuthenticateSSHRequest) (*SSHLoginRespo // it is valid, or error otherwise. func (c *Client) GetWebSessionInfo(ctx context.Context, user, sessionID string) (types.WebSession, error) { out, err := c.Get( + ctx, c.Endpoint("users", user, "web", "sessions", sessionID), url.Values{}) if err != nil { return nil, trace.Wrap(err) @@ -1033,7 +1033,7 @@ func (c *Client) GetWebSessionInfo(ctx context.Context, user, sessionID string) // DeleteWebSession deletes the web session specified with sid for the given user func (c *Client) DeleteWebSession(user string, sid string) error { - _, err := c.Delete(c.Endpoint("users", user, "web", "sessions", sid)) + _, err := c.Delete(context.TODO(), c.Endpoint("users", user, "web", "sessions", sid)) return trace.Wrap(err) } @@ -1041,7 +1041,7 @@ func (c *Client) DeleteWebSession(user string, sid string) error { // by password. If the pass parameter is an empty string, the key pair // is not password-protected. func (c *Client) GenerateKeyPair(pass string) ([]byte, []byte, error) { - out, err := c.PostJSON(c.Endpoint("keypair"), generateKeyPairReq{Password: pass}) + out, err := c.PostJSON(context.TODO(), c.Endpoint("keypair"), generateKeyPairReq{Password: pass}) if err != nil { return nil, nil, trace.Wrap(err) } @@ -1058,7 +1058,7 @@ func (c *Client) GenerateKeyPair(pass string) ([]byte, []byte, error) { func (c *Client) GenerateHostCert( key []byte, hostID, nodeName string, principals []string, clusterName string, role types.SystemRole, ttl time.Duration) ([]byte, error) { - out, err := c.PostJSON(c.Endpoint("ca", "host", "certs"), + out, err := c.PostJSON(context.TODO(), c.Endpoint("ca", "host", "certs"), generateHostCertReq{ Key: key, HostID: hostID, @@ -1082,7 +1082,7 @@ func (c *Client) GenerateHostCert( // CreateOIDCAuthRequest creates OIDCAuthRequest func (c *Client) CreateOIDCAuthRequest(req services.OIDCAuthRequest) (*services.OIDCAuthRequest, error) { - out, err := c.PostJSON(c.Endpoint("oidc", "requests", "create"), createOIDCAuthRequestReq{ + out, err := c.PostJSON(context.TODO(), c.Endpoint("oidc", "requests", "create"), createOIDCAuthRequestReq{ Req: req, }) if err != nil { @@ -1097,7 +1097,7 @@ func (c *Client) CreateOIDCAuthRequest(req services.OIDCAuthRequest) (*services. // ValidateOIDCAuthCallback validates OIDC auth callback returned from redirect func (c *Client) ValidateOIDCAuthCallback(q url.Values) (*OIDCAuthResponse, error) { - out, err := c.PostJSON(c.Endpoint("oidc", "requests", "validate"), validateOIDCAuthCallbackReq{ + out, err := c.PostJSON(context.TODO(), c.Endpoint("oidc", "requests", "validate"), validateOIDCAuthCallbackReq{ Query: q, }) if err != nil { @@ -1138,7 +1138,7 @@ func (c *Client) CreateSAMLConnector(ctx context.Context, connector types.SAMLCo if err != nil { return trace.Wrap(err) } - _, err = c.PostJSON(c.Endpoint("saml", "connectors"), &createSAMLConnectorRawReq{ + _, err = c.PostJSON(ctx, c.Endpoint("saml", "connectors"), &createSAMLConnectorRawReq{ Connector: data, }) if err != nil { @@ -1149,7 +1149,7 @@ func (c *Client) CreateSAMLConnector(ctx context.Context, connector types.SAMLCo // CreateSAMLAuthRequest creates SAML AuthnRequest func (c *Client) CreateSAMLAuthRequest(req services.SAMLAuthRequest) (*services.SAMLAuthRequest, error) { - out, err := c.PostJSON(c.Endpoint("saml", "requests", "create"), createSAMLAuthRequestReq{ + out, err := c.PostJSON(context.TODO(), c.Endpoint("saml", "requests", "create"), createSAMLAuthRequestReq{ Req: req, }) if err != nil { @@ -1164,7 +1164,7 @@ func (c *Client) CreateSAMLAuthRequest(req services.SAMLAuthRequest) (*services. // ValidateSAMLResponse validates response returned by SAML identity provider func (c *Client) ValidateSAMLResponse(re string) (*SAMLAuthResponse, error) { - out, err := c.PostJSON(c.Endpoint("saml", "requests", "validate"), validateSAMLResponseReq{ + out, err := c.PostJSON(context.TODO(), c.Endpoint("saml", "requests", "validate"), validateSAMLResponseReq{ Response: re, }) if err != nil { @@ -1205,7 +1205,7 @@ func (c *Client) CreateGithubConnector(connector types.GithubConnector) error { if err != nil { return trace.Wrap(err) } - _, err = c.PostJSON(c.Endpoint("github", "connectors"), &createGithubConnectorRawReq{ + _, err = c.PostJSON(context.TODO(), c.Endpoint("github", "connectors"), &createGithubConnectorRawReq{ Connector: bytes, }) if err != nil { @@ -1216,7 +1216,7 @@ func (c *Client) CreateGithubConnector(connector types.GithubConnector) error { // CreateGithubAuthRequest creates a new request for Github OAuth2 flow func (c *Client) CreateGithubAuthRequest(req services.GithubAuthRequest) (*services.GithubAuthRequest, error) { - out, err := c.PostJSON(c.Endpoint("github", "requests", "create"), + out, err := c.PostJSON(context.TODO(), c.Endpoint("github", "requests", "create"), createGithubAuthRequestReq{Req: req}) if err != nil { return nil, trace.Wrap(err) @@ -1230,7 +1230,7 @@ func (c *Client) CreateGithubAuthRequest(req services.GithubAuthRequest) (*servi // ValidateGithubAuthCallback validates Github auth callback returned from redirect func (c *Client) ValidateGithubAuthCallback(q url.Values) (*GithubAuthResponse, error) { - out, err := c.PostJSON(c.Endpoint("github", "requests", "validate"), + out, err := c.PostJSON(context.TODO(), c.Endpoint("github", "requests", "validate"), validateGithubAuthCallbackReq{Query: q}) if err != nil { return nil, trace.Wrap(err) @@ -1267,7 +1267,7 @@ func (c *Client) ValidateGithubAuthCallback(q url.Values) (*GithubAuthResponse, // EmitAuditEventLegacy sends an auditable event to the auth server (part of events.IAuditLog interface) func (c *Client) EmitAuditEventLegacy(event events.Event, fields events.EventFields) error { - _, err := c.PostJSON(c.Endpoint("events"), &auditEventReq{ + _, err := c.PostJSON(context.TODO(), c.Endpoint("events"), &auditEventReq{ Event: event, Fields: fields, // Send "type" as well for backwards compatibility. @@ -1302,7 +1302,7 @@ func (c *Client) PostSessionSlice(slice events.SessionSlice) error { // we **must** consume response by reading all of its body, otherwise the http // client will allocate a new connection for subsequent requests defer re.Body.Close() - responseBytes, _ := ioutil.ReadAll(re.Body) + responseBytes, _ := io.ReadAll(re.Body) return trace.ReadError(re.StatusCode, responseBytes) } @@ -1313,7 +1313,7 @@ func (c *Client) GetSessionChunk(namespace string, sid session.ID, offsetBytes, if namespace == "" { return nil, trace.BadParameter(MissingNamespaceError) } - response, err := c.Get(c.Endpoint("namespaces", namespace, "sessions", string(sid), "stream"), url.Values{ + response, err := c.Get(context.TODO(), c.Endpoint("namespaces", namespace, "sessions", string(sid), "stream"), url.Values{ "offset": []string{strconv.Itoa(offsetBytes)}, "bytes": []string{strconv.Itoa(maxBytes)}, }) @@ -1335,7 +1335,7 @@ func (c *Client) UploadSessionRecording(r events.SessionRecording) error { "sid": []string{string(r.SessionID)}, "namespace": []string{r.Namespace}, } - _, err := c.PostForm(c.Endpoint("namespaces", r.Namespace, "sessions", string(r.SessionID), "recording"), values, file) + _, err := c.PostForm(context.TODO(), c.Endpoint("namespaces", r.Namespace, "sessions", string(r.SessionID), "recording"), values, file) if err != nil { return trace.Wrap(err) } @@ -1361,7 +1361,7 @@ func (c *Client) GetSessionEvents(namespace string, sid session.ID, afterN int, if includePrintEvents { query.Set("print", fmt.Sprintf("%v", includePrintEvents)) } - response, err := c.Get(c.Endpoint("namespaces", namespace, "sessions", string(sid), "events"), query) + response, err := c.Get(context.TODO(), c.Endpoint("namespaces", namespace, "sessions", string(sid), "events"), query) if err != nil { return nil, trace.Wrap(err) } @@ -1401,7 +1401,7 @@ func (c *Client) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, order // GetNamespaces returns a list of namespaces func (c *Client) GetNamespaces() ([]types.Namespace, error) { - out, err := c.Get(c.Endpoint("namespaces"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("namespaces"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -1417,7 +1417,7 @@ func (c *Client) GetNamespace(name string) (*types.Namespace, error) { if name == "" { return nil, trace.BadParameter("missing namespace name") } - out, err := c.Get(c.Endpoint("namespaces", name), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("namespaces", name), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -1426,13 +1426,13 @@ func (c *Client) GetNamespace(name string) (*types.Namespace, error) { // UpsertNamespace upserts namespace func (c *Client) UpsertNamespace(ns types.Namespace) error { - _, err := c.PostJSON(c.Endpoint("namespaces"), upsertNamespaceReq{Namespace: ns}) + _, err := c.PostJSON(context.TODO(), c.Endpoint("namespaces"), upsertNamespaceReq{Namespace: ns}) return trace.Wrap(err) } // DeleteNamespace deletes namespace by name func (c *Client) DeleteNamespace(name string) error { - _, err := c.Delete(c.Endpoint("namespaces", name)) + _, err := c.Delete(context.TODO(), c.Endpoint("namespaces", name)) return trace.Wrap(err) } @@ -1443,7 +1443,7 @@ func (c *Client) CreateRole(role types.Role) error { // GetClusterName returns a cluster name func (c *Client) GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) { - out, err := c.Get(c.Endpoint("configuration", "name"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("configuration", "name"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -1464,7 +1464,7 @@ func (c *Client) SetClusterName(cn types.ClusterName) error { return trace.Wrap(err) } - _, err = c.PostJSON(c.Endpoint("configuration", "name"), &setClusterNameReq{ClusterName: data}) + _, err = c.PostJSON(context.TODO(), c.Endpoint("configuration", "name"), &setClusterNameReq{ClusterName: data}) if err != nil { return trace.Wrap(err) } @@ -1479,13 +1479,13 @@ func (c *Client) UpsertClusterName(cn types.ClusterName) error { // DeleteStaticTokens deletes static tokens func (c *Client) DeleteStaticTokens() error { - _, err := c.Delete(c.Endpoint("configuration", "static_tokens")) + _, err := c.Delete(context.TODO(), c.Endpoint("configuration", "static_tokens")) return trace.Wrap(err) } // GetStaticTokens returns a list of static register tokens func (c *Client) GetStaticTokens() (types.StaticTokens, error) { - out, err := c.Get(c.Endpoint("configuration", "static_tokens"), url.Values{}) + out, err := c.Get(context.TODO(), c.Endpoint("configuration", "static_tokens"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -1505,7 +1505,7 @@ func (c *Client) SetStaticTokens(st types.StaticTokens) error { return trace.Wrap(err) } - _, err = c.PostJSON(c.Endpoint("configuration", "static_tokens"), &setStaticTokensReq{StaticTokens: data}) + _, err = c.PostJSON(context.TODO(), c.Endpoint("configuration", "static_tokens"), &setStaticTokensReq{StaticTokens: data}) if err != nil { return trace.Wrap(err) } @@ -1548,6 +1548,11 @@ func (c *Client) DeleteAllRoles() error { return trace.NotImplemented(notImplementedMessage) } +// ListWindowsDesktops not implemented: can only be called locally. +func (c *Client) ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error) { + return nil, trace.NotImplemented(notImplementedMessage) +} + // DeleteAllUsers not implemented: can only be called locally. func (c *Client) DeleteAllUsers() error { return trace.NotImplemented(notImplementedMessage) @@ -1559,7 +1564,7 @@ func (c *Client) ValidateTrustedCluster(ctx context.Context, validateRequest *Va return nil, trace.Wrap(err) } - out, err := c.PostJSON(c.Endpoint("trustedclusters", "validate"), validateRequestRaw) + out, err := c.PostJSON(ctx, c.Endpoint("trustedclusters", "validate"), validateRequestRaw) if err != nil { return nil, trace.Wrap(err) } @@ -1945,12 +1950,12 @@ type ClientI interface { NewKeepAliver(ctx context.Context) (types.KeepAliver, error) // RotateCertAuthority starts or restarts certificate authority rotation process. - RotateCertAuthority(req RotateRequest) error + RotateCertAuthority(ctx context.Context, req RotateRequest) error // RotateExternalCertAuthority rotates external certificate authority, // this method is used to update only public keys and certificates of the // the certificate authorities of trusted clusters. - RotateExternalCertAuthority(ca types.CertAuthority) error + RotateExternalCertAuthority(ctx context.Context, ca types.CertAuthority) error // ValidateTrustedCluster validates trusted cluster token with // main cluster, in case if validation is successful, main cluster diff --git a/lib/auth/clt_test.go b/lib/auth/clt_test.go index cf85030c03e54..e9c2c17cb8960 100644 --- a/lib/auth/clt_test.go +++ b/lib/auth/clt_test.go @@ -17,16 +17,25 @@ limitations under the License. package auth import ( + "context" "crypto/tls" + "crypto/x509/pkix" + "net/http" + "net/http/httptest" "testing" "time" apiclient "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/session" + "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/trace" "github.com/stretchr/testify/require" ) func TestClient_DialTimeout(t *testing.T) { + t.Parallel() cases := []struct { desc string timeout time.Duration @@ -77,3 +86,85 @@ func TestClient_DialTimeout(t *testing.T) { }) } } + +func TestClient_RequestTimeout(t *testing.T) { + t.Parallel() + + testDone := make(chan struct{}) + sawRoot := make(chan bool, 1) + sawSlow := make(chan bool, 1) + + srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v2/authorities/host/rotate/external": + sawRoot <- true + http.Redirect(w, r, "/slow", http.StatusFound) + case "/slow": + sawSlow <- true + w.Write([]byte("Hello")) + w.(http.Flusher).Flush() + <-testDone + } + })) + t.Cleanup(func() { + close(testDone) + srv.Close() + }) + + srv.TLS = &tls.Config{InsecureSkipVerify: true} + + cfg := apiclient.Config{ + Addrs: []string{srv.Listener.Addr().String()}, + Credentials: []apiclient.Credentials{ + apiclient.LoadTLS(srv.TLS), + }, + } + clt, err := NewClient(cfg) + require.NoError(t, err) + + srv.StartTLS() + + ca := newCertAuthority(t, "test", types.HostCA) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + t.Cleanup(cancel) + + err = clt.RotateExternalCertAuthority(ctx, ca) + require.ErrorIs(t, trace.Unwrap(err), context.DeadlineExceeded) + + close(sawRoot) + require.True(t, <-sawRoot, "handler never got /v2/authorities/host/rotate/external request") + + close(sawSlow) + require.True(t, <-sawSlow, "handler never got /slow request") +} + +func newCertAuthority(t *testing.T, name string, caType types.CertAuthType) types.CertAuthority { + ta := testauthority.New() + priv, pub, err := ta.GenerateKeyPair("") + require.NoError(t, err) + + // CA for cluster1 with 1 key pair. + key, cert, err := tlsca.GenerateSelfSignedCA(pkix.Name{CommonName: name}, nil, time.Minute) + require.NoError(t, err) + + ca, err := types.NewCertAuthority(types.CertAuthoritySpecV2{ + Type: caType, + ClusterName: name, + ActiveKeys: types.CAKeySet{ + SSH: []*types.SSHKeyPair{{ + PrivateKey: priv, + PrivateKeyType: types.PrivateKeyType_RAW, + PublicKey: pub, + }}, + TLS: []*types.TLSKeyPair{{ + Cert: cert, + Key: key, + }}, + }, + Roles: nil, + SigningAlg: types.CertAuthoritySpecV2_RSA_SHA2_256, + }) + require.NoError(t, err) + return ca +} diff --git a/lib/auth/db.go b/lib/auth/db.go index d9ca5a4c35266..dbb1cd87c4512 100644 --- a/lib/auth/db.go +++ b/lib/auth/db.go @@ -43,7 +43,7 @@ func (s *Server) GenerateDatabaseCert(ctx context.Context, req *proto.DatabaseCe if err != nil { return nil, trace.Wrap(err) } - hostCA, err := s.GetCertAuthority(types.CertAuthID{ + hostCA, err := s.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName.GetClusterName(), }, true) @@ -102,7 +102,7 @@ func (s *Server) SignDatabaseCSR(ctx context.Context, req *proto.DatabaseCSRRequ return nil, trace.Wrap(err) } - hostCA, err := s.GetCertAuthority(types.CertAuthID{ + hostCA, err := s.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: req.ClusterName, }, false) @@ -144,7 +144,7 @@ func (s *Server) SignDatabaseCSR(ctx context.Context, req *proto.DatabaseCSRRequ ttl := roles.AdjustSessionTTL(apidefaults.CertDuration) // Generate the TLS certificate. - userCA, err := s.Trust.GetCertAuthority(types.CertAuthID{ + userCA, err := s.Trust.GetCertAuthority(ctx, types.CertAuthID{ Type: types.UserCA, DomainName: clusterName.GetClusterName(), }, true) diff --git a/lib/auth/desktop.go b/lib/auth/desktop.go index 394b34717ad58..1fd07b7526e7c 100644 --- a/lib/auth/desktop.go +++ b/lib/auth/desktop.go @@ -45,7 +45,7 @@ func (s *Server) GenerateWindowsDesktopCert(ctx context.Context, req *proto.Wind if err != nil { return nil, trace.Wrap(err) } - userCA, err := s.GetCertAuthority(types.CertAuthID{ + userCA, err := s.GetCertAuthority(ctx, types.CertAuthID{ Type: types.UserCA, DomainName: clusterName.GetClusterName(), }, true) diff --git a/lib/auth/github.go b/lib/auth/github.go index 1477491396a2a..2872aeee55bfb 100644 --- a/lib/auth/github.go +++ b/lib/auth/github.go @@ -293,7 +293,7 @@ func (a *Server) validateGithubAuthCallback(q url.Values) (*githubAuthResponse, re.auth.TLSCert = tlsCert // Return the host CA for this cluster only. - authority, err := a.GetCertAuthority(types.CertAuthID{ + authority, err := a.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName.GetClusterName(), }, false) diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index 4811125550916..105cd927cc5f3 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -1813,7 +1813,8 @@ func (g *GRPCServer) DeleteRole(ctx context.Context, req *proto.DeleteRoleReques func doMFAPresenceChallenge(ctx context.Context, actx *grpcContext, stream proto.AuthService_MaintainSessionPresenceServer, challengeReq *proto.PresenceMFAChallengeRequest) error { user := actx.User.GetName() - authChallenge, err := actx.authServer.mfaAuthChallenge(ctx, user) + const passwordless = false + authChallenge, err := actx.authServer.mfaAuthChallenge(ctx, user, passwordless) if err != nil { return trace.Wrap(err) } @@ -1835,7 +1836,7 @@ func doMFAPresenceChallenge(ctx context.Context, actx *grpcContext, stream proto return trace.BadParameter("expected MFAAuthenticateResponse, got %T", challengeResp) } - if _, err := actx.authServer.validateMFAAuthResponse(ctx, user, challengeResp); err != nil { + if _, _, err := actx.authServer.validateMFAAuthResponse(ctx, challengeResp, user, passwordless); err != nil { return trace.Wrap(err) } @@ -1964,7 +1965,8 @@ func addMFADeviceAuthChallenge(gctx *grpcContext, stream proto.AuthService_AddMF ctx := stream.Context() // Note: authChallenge may be empty if this user has no existing MFA devices. - authChallenge, err := auth.mfaAuthChallenge(ctx, user) + const passwordless = false + authChallenge, err := auth.mfaAuthChallenge(ctx, user, passwordless) if err != nil { return trace.Wrap(err) } @@ -1984,7 +1986,7 @@ func addMFADeviceAuthChallenge(gctx *grpcContext, stream proto.AuthService_AddMF } // Only validate if there was a challenge. if authChallenge.TOTP != nil || authChallenge.WebauthnChallenge != nil { - if _, err := auth.validateMFAAuthResponse(ctx, user, authResp); err != nil { + if _, _, err := auth.validateMFAAuthResponse(ctx, authResp, user, passwordless); err != nil { return trace.Wrap(err) } } @@ -2006,6 +2008,7 @@ func addMFADeviceRegisterChallenge(gctx *grpcContext, stream proto.AuthService_A res, err := auth.createRegisterChallenge(ctx, &newRegisterChallengeRequest{ username: user, deviceType: initReq.DeviceType, + deviceUsage: initReq.DeviceUsage, webIdentityOverride: webIdentity, }) if err != nil { @@ -2088,7 +2091,8 @@ func deleteMFADeviceAuthChallenge(gctx *grpcContext, stream proto.AuthService_De auth := gctx.authServer user := gctx.User.GetName() - authChallenge, err := auth.mfaAuthChallenge(ctx, user) + const passwordless = false + authChallenge, err := auth.mfaAuthChallenge(ctx, user, passwordless) if err != nil { return trace.Wrap(err) } @@ -2107,7 +2111,7 @@ func deleteMFADeviceAuthChallenge(gctx *grpcContext, stream proto.AuthService_De if authResp == nil { return trace.BadParameter("expected MFAAuthenticateResponse, got %T", req) } - if _, err := auth.validateMFAAuthResponse(ctx, user, authResp); err != nil { + if _, _, err := auth.validateMFAAuthResponse(ctx, authResp, user, passwordless); err != nil { return trace.Wrap(err) } return nil @@ -2251,7 +2255,8 @@ func userSingleUseCertsAuthChallenge(gctx *grpcContext, stream proto.AuthService auth := gctx.authServer user := gctx.User.GetName() - challenge, err := auth.mfaAuthChallenge(ctx, user) + const passwordless = false + challenge, err := auth.mfaAuthChallenge(ctx, user, passwordless) if err != nil { return nil, trace.Wrap(err) } @@ -2272,7 +2277,7 @@ func userSingleUseCertsAuthChallenge(gctx *grpcContext, stream proto.AuthService if authResp == nil { return nil, trace.BadParameter("expected MFAAuthenticateResponse, got %T", req.Request) } - mfaDev, err := auth.validateMFAAuthResponse(ctx, user, authResp) + mfaDev, _, err := auth.validateMFAAuthResponse(ctx, authResp, user, passwordless) if err != nil { return nil, trace.Wrap(err) } @@ -3718,6 +3723,20 @@ func (g *GRPCServer) ListResources(ctx context.Context, req *proto.ListResources } protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_KubeService{KubeService: srv}} + case types.KindWindowsDesktop: + desktop, ok := resource.(*types.WindowsDesktopV3) + if !ok { + return nil, trace.BadParameter("windows desktop has invalid type %T", resource) + } + + protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_WindowsDesktop{WindowsDesktop: desktop}} + case types.KindKubernetesCluster: + cluster, ok := resource.(*types.KubernetesClusterV3) + if !ok { + return nil, trace.BadParameter("kubernetes cluster has invalid type %T", resource) + } + + protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_KubeCluster{KubeCluster: cluster}} default: return nil, trace.NotImplemented("resource type %s doesn't support pagination", req.ResourceType) } diff --git a/lib/auth/grpcserver_test.go b/lib/auth/grpcserver_test.go index 0e82c5d768c20..efb0df91a2ca1 100644 --- a/lib/auth/grpcserver_test.go +++ b/lib/auth/grpcserver_test.go @@ -88,6 +88,8 @@ func TestMFADeviceManagement(t *testing.T) { require.NoError(t, err) webKey2.PreferRPID = true const webDev2Name = "webauthn2" + const pwdlessDevName = "pwdless" + addTests := []struct { desc string opts mfaAddTestOpts @@ -229,6 +231,41 @@ func TestMFADeviceManagement(t *testing.T) { }, }, }, + { + desc: "add passwordless device", + opts: mfaAddTestOpts{ + initReq: &proto.AddMFADeviceRequestInit{ + DeviceName: pwdlessDevName, + DeviceType: proto.DeviceType_DEVICE_TYPE_WEBAUTHN, + DeviceUsage: proto.DeviceUsage_DEVICE_USAGE_PASSWORDLESS, + }, + authHandler: devs.webAuthHandler, + checkAuthErr: require.NoError, + registerHandler: func(t *testing.T, challenge *proto.MFARegisterChallenge) *proto.MFARegisterResponse { + require.NotNil(t, challenge.GetWebauthn(), "WebAuthn challenge cannot be nil") + + key, err := mocku2f.Create() + require.NoError(t, err) + key.PreferRPID = true + key.SetPasswordless() + + ccr, err := key.SignCredentialCreation(webOrigin, wanlib.CredentialCreationFromProto(challenge.GetWebauthn())) + require.NoError(t, err) + + return &proto.MFARegisterResponse{ + Response: &proto.MFARegisterResponse_Webauthn{ + Webauthn: wanlib.CredentialCreationResponseToProto(ccr), + }, + } + }, + checkRegisterErr: require.NoError, + assertRegisteredDev: func(t *testing.T, dev *types.MFADevice) { + // Do a few simple device checks - lib/auth/webauthn goes in depth. + require.NotNil(t, dev.GetWebauthn(), "WebAuthnDevice cannot be nil") + require.True(t, true, dev.GetWebauthn().ResidentKey, "ResidentKey should be set to true") + }, + }, + }, } for _, tt := range addTests { t.Run(tt.desc, func(t *testing.T) { @@ -246,7 +283,7 @@ func TestMFADeviceManagement(t *testing.T) { deviceIDs[dev.GetName()] = dev.Id } sort.Strings(deviceNames) - require.Equal(t, deviceNames, []string{devs.TOTPName, devs.WebName, webDev2Name}) + require.Equal(t, deviceNames, []string{pwdlessDevName, devs.TOTPName, devs.WebName, webDev2Name}) // Delete several of the MFA devices. deleteTests := []struct { @@ -321,6 +358,16 @@ func TestMFADeviceManagement(t *testing.T) { checkErr: require.NoError, }, }, + { + desc: "delete pwdless device by name", + opts: mfaDeleteTestOpts{ + initReq: &proto.DeleteMFADeviceRequestInit{ + DeviceName: pwdlessDevName, + }, + authHandler: devs.webAuthHandler, + checkErr: require.NoError, + }, + }, { desc: "delete webauthn device by name", opts: mfaDeleteTestOpts{ @@ -1834,11 +1881,11 @@ func TestListResources(t *testing.T) { testCases := map[string]struct { resourceType string - createResource func(name string) error + createResource func(name string, clt *Client) error }{ "DatabaseServers": { resourceType: types.KindDatabaseServer, - createResource: func(name string) error { + createResource: func(name string, clt *Client) error { server, err := types.NewDatabaseServerV3(types.Metadata{ Name: name, }, types.DatabaseServerSpecV3{ @@ -1857,7 +1904,7 @@ func TestListResources(t *testing.T) { }, "ApplicationServers": { resourceType: types.KindAppServer, - createResource: func(name string) error { + createResource: func(name string, clt *Client) error { app, err := types.NewAppV3(types.Metadata{ Name: name, }, types.AppSpecV3{ @@ -1884,7 +1931,7 @@ func TestListResources(t *testing.T) { }, "KubeService": { resourceType: types.KindKubeService, - createResource: func(name string) error { + createResource: func(name string, clt *Client) error { server, err := types.NewServer(name, types.KindKubeService, types.ServerSpecV2{ KubernetesClusters: []*types.KubernetesCluster{ {Name: name, StaticLabels: map[string]string{"name": name}}, @@ -1899,7 +1946,7 @@ func TestListResources(t *testing.T) { }, "Node": { resourceType: types.KindNode, - createResource: func(name string) error { + createResource: func(name string, clt *Client) error { server, err := types.NewServer(name, types.KindNode, types.ServerSpecV2{}) if err != nil { return err @@ -1909,10 +1956,25 @@ func TestListResources(t *testing.T) { return err }, }, + "WindowsDesktops": { + resourceType: types.KindWindowsDesktop, + createResource: func(name string, clt *Client) error { + desktop, err := types.NewWindowsDesktopV3(name, nil, + types.WindowsDesktopSpecV3{Addr: "_", HostID: "_"}) + if err != nil { + return err + } + + return clt.UpsertWindowsDesktop(ctx, desktop) + }, + }, } for name, test := range testCases { + name := name + test := test t.Run(name, func(t *testing.T) { + t.Parallel() resp, err := clt.ListResources(ctx, proto.ListResourcesRequest{ ResourceType: test.resourceType, Namespace: apidefaults.Namespace, @@ -1923,9 +1985,9 @@ func TestListResources(t *testing.T) { require.Empty(t, resp.NextKey) // create two resources - err = test.createResource("foo") + err = test.createResource("foo", clt) require.NoError(t, err) - err = test.createResource("bar") + err = test.createResource("bar", clt) require.NoError(t, err) resp, err = clt.ListResources(ctx, proto.ListResourcesRequest{ @@ -1938,6 +2000,20 @@ func TestListResources(t *testing.T) { require.Empty(t, resp.NextKey) require.Empty(t, resp.TotalCount) + // Test types.KindKubernetesCluster + if test.resourceType == types.KindKubeService { + test.resourceType = types.KindKubernetesCluster + resp, err = clt.ListResources(ctx, proto.ListResourcesRequest{ + ResourceType: test.resourceType, + Namespace: apidefaults.Namespace, + Limit: 100, + }) + require.NoError(t, err) + require.Len(t, resp.Resources, 2) + require.Empty(t, resp.NextKey) + require.Empty(t, resp.TotalCount) + } + // Test listing with NeedTotalCount flag. if test.resourceType != types.KindKubeService { resp, err = clt.ListResources(ctx, proto.ListResourcesRequest{ @@ -1966,62 +2042,75 @@ func TestListResources(t *testing.T) { func TestCustomRateLimiting(t *testing.T) { t.Parallel() - cases := []struct { - name string - fn func(*Client) error + ctx := context.Background() + tests := []struct { + name string + burst int + fn func(*Client) error }{ { name: "RPC ChangeUserAuthentication", fn: func(clt *Client) error { - _, err := clt.ChangeUserAuthentication(context.Background(), &proto.ChangeUserAuthenticationRequest{}) + _, err := clt.ChangeUserAuthentication(ctx, &proto.ChangeUserAuthenticationRequest{}) + return err + }, + }, + { + name: "RPC CreateAuthenticateChallenge", + burst: defaults.LimiterPasswordlessBurst, + fn: func(clt *Client) error { + _, err := clt.CreateAuthenticateChallenge(ctx, &proto.CreateAuthenticateChallengeRequest{}) return err }, }, { name: "RPC GetAccountRecoveryToken", fn: func(clt *Client) error { - _, err := clt.GetAccountRecoveryToken(context.Background(), &proto.GetAccountRecoveryTokenRequest{}) + _, err := clt.GetAccountRecoveryToken(ctx, &proto.GetAccountRecoveryTokenRequest{}) return err }, }, { name: "RPC StartAccountRecovery", fn: func(clt *Client) error { - _, err := clt.StartAccountRecovery(context.Background(), &proto.StartAccountRecoveryRequest{}) + _, err := clt.StartAccountRecovery(ctx, &proto.StartAccountRecoveryRequest{}) return err }, }, { name: "RPC VerifyAccountRecovery", fn: func(clt *Client) error { - _, err := clt.VerifyAccountRecovery(context.Background(), &proto.VerifyAccountRecoveryRequest{}) + _, err := clt.VerifyAccountRecovery(ctx, &proto.VerifyAccountRecoveryRequest{}) return err }, }, } - - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { t.Parallel() - // For now since we only have one custom rate limit, - // test limit for 1 request per minute with bursts up to 10 requests. - const maxAttempts = 11 - var err error - - // Create new instance per test case, to troubleshoot - // which test case specifically failed, otherwise - // multiple cases can fail from running cases in parallel. + // Create new instance per test case, to troubleshoot which test case + // specifically failed, otherwise multiple cases can fail from running + // cases in parallel. srv := newTestTLSServer(t) clt, err := srv.NewClient(TestNop()) require.NoError(t, err) - for i := 0; i < maxAttempts; i++ { - err = c.fn(clt) - require.Error(t, err) + var attempts int + if test.burst == 0 { + attempts = 10 // Good for most tests. + } else { + attempts = test.burst } - require.True(t, trace.IsLimitExceeded(err)) + + for i := 0; i < attempts; i++ { + err = test.fn(clt) + require.False(t, trace.IsLimitExceeded(err), "got err = %v, want non-IsLimitExceeded", err) + } + + err = test.fn(clt) + require.True(t, trace.IsLimitExceeded(err), "got err = %v, want LimitExceeded", err) }) } } diff --git a/lib/auth/helpers.go b/lib/auth/helpers.go index 397aeb6d02f1a..f9007cd3f6a48 100644 --- a/lib/auth/helpers.go +++ b/lib/auth/helpers.go @@ -486,8 +486,8 @@ func (a *TestAuthServer) Clock() clockwork.Clock { } // Trust adds other server host certificate authority as trusted -func (a *TestAuthServer) Trust(remote *TestAuthServer, roleMap types.RoleMap) error { - remoteCA, err := remote.AuthServer.GetCertAuthority(types.CertAuthID{ +func (a *TestAuthServer) Trust(ctx context.Context, remote *TestAuthServer, roleMap types.RoleMap) error { + remoteCA, err := remote.AuthServer.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: remote.ClusterName, }, false) @@ -498,7 +498,7 @@ func (a *TestAuthServer) Trust(remote *TestAuthServer, roleMap types.RoleMap) er if err != nil { return trace.Wrap(err) } - remoteCA, err = remote.AuthServer.GetCertAuthority(types.CertAuthID{ + remoteCA, err = remote.AuthServer.GetCertAuthority(ctx, types.CertAuthID{ Type: types.UserCA, DomainName: remote.ClusterName, }, false) @@ -923,7 +923,7 @@ type clt interface { // CreateRole creates a role without assigning any users. Used in tests. func CreateRole(ctx context.Context, clt clt, name string, spec types.RoleSpecV5) (types.Role, error) { - role, err := types.NewRole(name, spec) + role, err := types.NewRoleV3(name, spec) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/auth/httpfallback.go b/lib/auth/httpfallback.go index 0ff46995a0575..fc0fcfd6f8b6c 100644 --- a/lib/auth/httpfallback.go +++ b/lib/auth/httpfallback.go @@ -48,7 +48,7 @@ func (c *Client) UpsertRole(ctx context.Context, role types.Role) error { if err != nil { return trace.Wrap(err) } - _, err = c.PostJSON(c.Endpoint("roles"), &upsertRoleRawReq{Role: data}) + _, err = c.PostJSON(ctx, c.Endpoint("roles"), &upsertRoleRawReq{Role: data}) return trace.Wrap(err) } @@ -65,7 +65,7 @@ func (c *Client) GetRole(ctx context.Context, name string) (types.Role, error) { if name == "" { return nil, trace.BadParameter("missing name") } - out, err := c.Get(c.Endpoint("roles", name), url.Values{}) + out, err := c.Get(ctx, c.Endpoint("roles", name), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -89,7 +89,7 @@ func (c *Client) DeleteRole(ctx context.Context, name string) error { if name == "" { return trace.BadParameter("missing name") } - _, err := c.Delete(c.Endpoint("roles", name)) + _, err := c.Delete(ctx, c.Endpoint("roles", name)) return trace.Wrap(err) } @@ -103,7 +103,7 @@ func (c *Client) GetTokens(ctx context.Context, opts ...services.MarshalOption) return resp, nil } - out, err := c.Get(c.Endpoint("tokens"), url.Values{}) + out, err := c.Get(ctx, c.Endpoint("tokens"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -124,7 +124,7 @@ func (c *Client) GetToken(ctx context.Context, token string) (types.ProvisionTok return resp, nil } - out, err := c.Get(c.Endpoint("tokens", token), url.Values{}) + out, err := c.Get(ctx, c.Endpoint("tokens", token), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -142,7 +142,7 @@ func (c *Client) DeleteToken(ctx context.Context, token string) error { return nil } - _, err := c.Delete(c.Endpoint("tokens", token)) + _, err := c.Delete(ctx, c.Endpoint("tokens", token)) return trace.Wrap(err) } @@ -160,7 +160,7 @@ func (c *Client) UpsertOIDCConnector(ctx context.Context, connector types.OIDCCo if err != nil { return trace.Wrap(err) } - _, err = c.PostJSON(c.Endpoint("oidc", "connectors"), &upsertOIDCConnectorRawReq{ + _, err = c.PostJSON(ctx, c.Endpoint("oidc", "connectors"), &upsertOIDCConnectorRawReq{ Connector: data, }) if err != nil { @@ -182,7 +182,7 @@ func (c *Client) GetOIDCConnector(ctx context.Context, id string, withSecrets bo if id == "" { return nil, trace.BadParameter("missing connector id") } - out, err := c.Get(c.Endpoint("oidc", "connectors", id), + out, err := c.Get(ctx, c.Endpoint("oidc", "connectors", id), url.Values{"with_secrets": []string{fmt.Sprintf("%t", withSecrets)}}) if err != nil { return nil, err @@ -200,7 +200,7 @@ func (c *Client) GetOIDCConnectors(ctx context.Context, withSecrets bool) ([]typ return resp, nil } - out, err := c.Get(c.Endpoint("oidc", "connectors"), + out, err := c.Get(ctx, c.Endpoint("oidc", "connectors"), url.Values{"with_secrets": []string{fmt.Sprintf("%t", withSecrets)}}) if err != nil { return nil, err @@ -233,7 +233,7 @@ func (c *Client) DeleteOIDCConnector(ctx context.Context, connectorID string) er if connectorID == "" { return trace.BadParameter("missing connector id") } - _, err := c.Delete(c.Endpoint("oidc", "connectors", connectorID)) + _, err := c.Delete(ctx, c.Endpoint("oidc", "connectors", connectorID)) return trace.Wrap(err) } @@ -251,7 +251,7 @@ func (c *Client) UpsertSAMLConnector(ctx context.Context, connector types.SAMLCo if err != nil { return trace.Wrap(err) } - _, err = c.PutJSON(c.Endpoint("saml", "connectors"), &upsertSAMLConnectorRawReq{ + _, err = c.PutJSON(ctx, c.Endpoint("saml", "connectors"), &upsertSAMLConnectorRawReq{ Connector: data, }) if err != nil { @@ -273,7 +273,7 @@ func (c *Client) GetSAMLConnector(ctx context.Context, id string, withSecrets bo if id == "" { return nil, trace.BadParameter("missing connector id") } - out, err := c.Get(c.Endpoint("saml", "connectors", id), + out, err := c.Get(ctx, c.Endpoint("saml", "connectors", id), url.Values{"with_secrets": []string{fmt.Sprintf("%t", withSecrets)}}) if err != nil { return nil, trace.Wrap(err) @@ -291,7 +291,7 @@ func (c *Client) GetSAMLConnectors(ctx context.Context, withSecrets bool) ([]typ return resp, nil } - out, err := c.Get(c.Endpoint("saml", "connectors"), + out, err := c.Get(ctx, c.Endpoint("saml", "connectors"), url.Values{"with_secrets": []string{fmt.Sprintf("%t", withSecrets)}}) if err != nil { return nil, err @@ -324,7 +324,7 @@ func (c *Client) DeleteSAMLConnector(ctx context.Context, connectorID string) er if connectorID == "" { return trace.BadParameter("missing connector id") } - _, err := c.Delete(c.Endpoint("saml", "connectors", connectorID)) + _, err := c.Delete(ctx, c.Endpoint("saml", "connectors", connectorID)) return trace.Wrap(err) } @@ -342,7 +342,7 @@ func (c *Client) UpsertGithubConnector(ctx context.Context, connector types.Gith if err != nil { return trace.Wrap(err) } - _, err = c.PutJSON(c.Endpoint("github", "connectors"), &upsertGithubConnectorRawReq{ + _, err = c.PutJSON(ctx, c.Endpoint("github", "connectors"), &upsertGithubConnectorRawReq{ Connector: bytes, }) if err != nil { @@ -361,7 +361,7 @@ func (c *Client) GetGithubConnectors(ctx context.Context, withSecrets bool) ([]t return resp, nil } - out, err := c.Get(c.Endpoint("github", "connectors"), url.Values{ + out, err := c.Get(ctx, c.Endpoint("github", "connectors"), url.Values{ "with_secrets": []string{strconv.FormatBool(withSecrets)}, }) if err != nil { @@ -392,7 +392,7 @@ func (c *Client) GetGithubConnector(ctx context.Context, id string, withSecrets return resp, nil } - out, err := c.Get(c.Endpoint("github", "connectors", id), url.Values{ + out, err := c.Get(ctx, c.Endpoint("github", "connectors", id), url.Values{ "with_secrets": []string{strconv.FormatBool(withSecrets)}, }) if err != nil { @@ -411,7 +411,7 @@ func (c *Client) DeleteGithubConnector(ctx context.Context, id string) error { return nil } - _, err := c.Delete(c.Endpoint("github", "connectors", id)) + _, err := c.Delete(ctx, c.Endpoint("github", "connectors", id)) if err != nil { return trace.Wrap(err) } @@ -427,7 +427,7 @@ func (c *Client) GetTrustedCluster(ctx context.Context, name string) (types.Trus return resp, nil } - out, err := c.Get(c.Endpoint("trustedclusters", name), url.Values{}) + out, err := c.Get(ctx, c.Endpoint("trustedclusters", name), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -449,7 +449,7 @@ func (c *Client) GetTrustedClusters(ctx context.Context) ([]types.TrustedCluster return resp, nil } - out, err := c.Get(c.Endpoint("trustedclusters"), url.Values{}) + out, err := c.Get(ctx, c.Endpoint("trustedclusters"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } @@ -484,7 +484,7 @@ func (c *Client) UpsertTrustedCluster(ctx context.Context, trustedCluster types. if err != nil { return nil, trace.Wrap(err) } - out, err := c.PostJSON(c.Endpoint("trustedclusters"), &upsertTrustedClusterReq{ + out, err := c.PostJSON(ctx, c.Endpoint("trustedclusters"), &upsertTrustedClusterReq{ TrustedCluster: trustedClusterBytes, }) if err != nil { @@ -503,7 +503,7 @@ func (c *Client) DeleteTrustedCluster(ctx context.Context, name string) error { return nil } - _, err := c.Delete(c.Endpoint("trustedclusters", name)) + _, err := c.Delete(ctx, c.Endpoint("trustedclusters", name)) return trace.Wrap(err) } @@ -517,7 +517,7 @@ func (c *Client) DeleteAllNodes(ctx context.Context, namespace string) error { return nil } - _, err := c.Delete(c.Endpoint("namespaces", namespace, "nodes")) + _, err := c.Delete(ctx, c.Endpoint("namespaces", namespace, "nodes")) if err != nil { return trace.Wrap(err) } @@ -534,7 +534,7 @@ func (c *Client) DeleteNode(ctx context.Context, namespace string, name string) return nil } - _, err := c.Delete(c.Endpoint("namespaces", namespace, "nodes", name)) + _, err := c.Delete(ctx, c.Endpoint("namespaces", namespace, "nodes", name)) if err != nil { return trace.Wrap(err) } @@ -548,6 +548,9 @@ type nodeClient interface { // GetNodesWithLabels is a helper for getting a list of nodes with optional label-based filtering. This is essentially // a wrapper around client.GetNodesWithLabels that performs fallback on NotImplemented errors. +// +// DELETE IN 11.0.0, this function is only called by lib/client/client.go (*ProxyClient).FindServersByLabels +// which is also marked for deletion (replaced by FindNodesByFilters). func GetNodesWithLabels(ctx context.Context, clt nodeClient, namespace string, labels map[string]string) ([]types.Server, error) { nodes, err := client.GetNodesWithLabels(ctx, clt, namespace, labels) if err == nil || !trace.IsNotImplemented(err) { @@ -573,6 +576,8 @@ func GetNodesWithLabels(ctx context.Context, clt nodeClient, namespace string, l } // GetNodes returns the list of servers registered in the cluster. +// +// DELETE IN 11.0.0, replaced by GetResourcesWithFilters func (c *Client) GetNodes(ctx context.Context, namespace string, opts ...services.MarshalOption) ([]types.Server, error) { if resp, err := c.APIClient.GetNodes(ctx, namespace); err != nil { if !trace.IsNotImplemented(err) { @@ -582,7 +587,7 @@ func (c *Client) GetNodes(ctx context.Context, namespace string, opts ...service return resp, nil } - out, err := c.Get(c.Endpoint("namespaces", namespace, "nodes"), url.Values{}) + out, err := c.Get(ctx, c.Endpoint("namespaces", namespace, "nodes"), url.Values{}) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/auth/init.go b/lib/auth/init.go index b831e3040985d..c99c245900d0f 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -203,7 +203,7 @@ func Init(cfg InitConfig, opts ...ServerOption) (*Server, error) { // if resources are supplied, use them to bootstrap backend state // on initial startup. if len(cfg.Resources) > 0 { - firstStart, err := isFirstStart(asrv, cfg) + firstStart, err := isFirstStart(ctx, asrv, cfg) if err != nil { return nil, trace.Wrap(err) } @@ -318,7 +318,7 @@ func Init(cfg InitConfig, opts ...ServerOption) (*Server, error) { // generate certificate authorities if they don't exist for _, caType := range []types.CertAuthType{types.HostCA, types.UserCA, types.JWTSigner} { caID := types.CertAuthID{Type: caType, DomainName: cfg.ClusterName.GetClusterName()} - ca, err := asrv.GetCertAuthority(caID, true) + ca, err := asrv.GetCertAuthority(ctx, caID, true) if err != nil { if !trace.IsNotFound(err) { return nil, trace.Wrap(err) @@ -343,11 +343,11 @@ func Init(cfg InitConfig, opts ...ServerOption) (*Server, error) { // without any active keys. These keys will not be used for // any signing operations until a CA rotation. Only the Host // CA is necessary to issue the Admin identity. - if err := asrv.ensureLocalAdditionalKeys(ca); err != nil { + if err := asrv.ensureLocalAdditionalKeys(ctx, ca); err != nil { return nil, trace.Wrap(err) } // reload updated CA for below checks - if ca, err = asrv.GetCertAuthority(caID, true); err != nil { + if ca, err = asrv.GetCertAuthority(ctx, caID, true); err != nil { return nil, trace.Wrap(err) } } @@ -367,7 +367,7 @@ func Init(cfg InitConfig, opts ...ServerOption) (*Server, error) { // Delete any unused keys from the keyStore. This is to avoid exhausting // (or wasting) HSM resources. - if err := asrv.deleteUnusedKeys(); err != nil { + if err := asrv.deleteUnusedKeys(ctx); err != nil { return nil, trace.Wrap(err) } @@ -515,9 +515,10 @@ func createPresets(asrv *Server) error { // isFirstStart returns 'true' if the auth server is starting for the 1st time // on this server. -func isFirstStart(authServer *Server, cfg InitConfig) (bool, error) { +func isFirstStart(ctx context.Context, authServer *Server, cfg InitConfig) (bool, error) { // check if the CA exists? _, err := authServer.GetCertAuthority( + ctx, types.CertAuthID{ DomainName: cfg.ClusterName.GetClusterName(), Type: types.HostCA, @@ -570,6 +571,11 @@ func checkResourceConsistency(keyStore keystore.KeyStore, clusterName string, re if r.GetName() == clusterName { return trace.BadParameter("trusted cluster has same name as local cluster (%q)", clusterName) } + case types.Role: + // Some options are only available with enterprise subscription + if err := checkRoleFeatureSupport(r); err != nil { + return trace.Wrap(err) + } default: // No validation checks for this resource type } @@ -948,7 +954,7 @@ func migrateRemoteClusters(ctx context.Context, asrv *Server) error { if err != nil { return trace.Wrap(err) } - certAuthorities, err := asrv.GetCertAuthorities(types.HostCA, false) + certAuthorities, err := asrv.GetCertAuthorities(ctx, types.HostCA, false) if err != nil { return trace.Wrap(err) } @@ -999,7 +1005,7 @@ func migrateRemoteClusters(ctx context.Context, asrv *Server) error { func migrateCertAuthorities(ctx context.Context, asrv *Server) error { var errors []error for _, caType := range []types.CertAuthType{types.HostCA, types.UserCA, types.JWTSigner} { - cas, err := asrv.GetCertAuthorities(caType, true) + cas, err := asrv.GetCertAuthorities(ctx, caType, true) if err != nil { errors = append(errors, trace.Wrap(err, "fetching %v CAs", caType)) continue diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index e26569f6764f9..08c25e725aa1c 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -446,13 +446,14 @@ func TestClusterName(t *testing.T) { } func TestCASigningAlg(t *testing.T) { + ctx := context.Background() verifyCAs := func(auth *Server, alg string) { - hostCAs, err := auth.GetCertAuthorities(types.HostCA, false) + hostCAs, err := auth.GetCertAuthorities(ctx, types.HostCA, false) require.NoError(t, err) for _, ca := range hostCAs { require.Equal(t, sshutils.GetSigningAlgName(ca), alg) } - userCAs, err := auth.GetCertAuthorities(types.UserCA, false) + userCAs, err := auth.GetCertAuthorities(ctx, types.UserCA, false) require.NoError(t, err) for _, ca := range userCAs { require.Equal(t, sshutils.GetSigningAlgName(ca), alg) @@ -635,7 +636,7 @@ func TestMigrateCertAuthorities(t *testing.T) { var caSpecs []types.CertAuthoritySpecV2 for _, typ := range []types.CertAuthType{types.HostCA, types.UserCA, types.JWTSigner} { t.Run(fmt.Sprintf("verify %v CA", typ), func(t *testing.T) { - cas, err := as.GetCertAuthorities(typ, true) + cas, err := as.GetCertAuthorities(ctx, typ, true) require.NoError(t, err) require.Len(t, cas, 1) caSpecs = append(caSpecs, cas[0].(*types.CertAuthorityV2).Spec) @@ -857,7 +858,7 @@ func TestIdentityChecker(t *testing.T) { clusterName, err := authServer.GetDomainName() require.NoError(t, err) - ca, err := authServer.GetCertAuthority(types.CertAuthID{ + ca, err := authServer.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName, }, true) diff --git a/lib/auth/join.go b/lib/auth/join.go index 6c0754034a8db..c50401c55713f 100644 --- a/lib/auth/join.go +++ b/lib/auth/join.go @@ -108,7 +108,10 @@ func (a *Server) generateCerts(ctx context.Context, provisionToken types.Provisi if req.Role == types.RoleBot { // bots use this endpoint but get a user cert // botResourceName must be set, enforced in CheckAndSetDefaults - botResourceName := provisionToken.GetBotName() + botName := provisionToken.GetBotName() + + // Append `bot-` to the bot name to derive its username. + botResourceName := BotResourceName(botName) expires := a.GetClock().Now().Add(defaults.DefaultRenewableCertTTL) joinMethod := provisionToken.GetJoinMethod() @@ -141,7 +144,7 @@ func (a *Server) generateCerts(ctx context.Context, provisionToken types.Provisi return nil, trace.BadParameter("unsupported join method %q for bot", joinMethod) } - log.Infof("Bot %q has joined the cluster.", botResourceName) + log.Infof("Bot %q has joined the cluster.", botName) return certs, nil } // generate and return host certificate and keys diff --git a/lib/auth/join_test.go b/lib/auth/join_test.go index f6c8b04f32e26..55654b9f06ce2 100644 --- a/lib/auth/join_test.go +++ b/lib/auth/join_test.go @@ -254,11 +254,11 @@ func TestAuth_RegisterUsingToken(t *testing.T) { } } -func newBotToken(t *testing.T, tokenName, user string, role types.SystemRole, expiry time.Time) types.ProvisionToken { +func newBotToken(t *testing.T, tokenName, botName string, role types.SystemRole, expiry time.Time) types.ProvisionToken { t.Helper() token, err := types.NewProvisionTokenFromSpec(tokenName, expiry, types.ProvisionTokenSpecV2{ Roles: []types.SystemRole{role}, - BotName: user, + BotName: botName, }) require.NoError(t, err, "could not create bot token") return token @@ -271,19 +271,22 @@ func TestRegister_Bot(t *testing.T) { srv := newTestTLSServer(t) + botName := "test" + botResourceName := BotResourceName(botName) + _, err := createBotRole(context.Background(), srv.Auth(), "test", "bot-test", []string{}) require.NoError(t, err) - user, err := createBotUser(context.Background(), srv.Auth(), "test", "bot-test") + _, err = createBotUser(context.Background(), srv.Auth(), botName, botResourceName) require.NoError(t, err) later := srv.Clock().Now().Add(4 * time.Hour) - goodToken := newBotToken(t, "good-token", user.GetName(), types.RoleBot, later) - expiredToken := newBotToken(t, "expired", user.GetName(), types.RoleBot, srv.Clock().Now().Add(-1*time.Hour)) + goodToken := newBotToken(t, "good-token", botName, types.RoleBot, later) + expiredToken := newBotToken(t, "expired", botName, types.RoleBot, srv.Clock().Now().Add(-1*time.Hour)) wrongKind := newBotToken(t, "wrong-kind", "", types.RoleNode, later) wrongUser := newBotToken(t, "wrong-user", "llama", types.RoleBot, later) - invalidToken := newBotToken(t, "this-token-does-not-exist", user.GetName(), types.RoleBot, later) + invalidToken := newBotToken(t, "this-token-does-not-exist", botName, types.RoleBot, later) err = srv.Auth().UpsertToken(context.Background(), goodToken) require.NoError(t, err) diff --git a/lib/auth/kube.go b/lib/auth/kube.go index 54a9a76b9264d..6ca9d970bc6b1 100644 --- a/lib/auth/kube.go +++ b/lib/auth/kube.go @@ -17,6 +17,7 @@ limitations under the License. package auth import ( + "context" "time" "github.com/gravitational/teleport" @@ -62,6 +63,7 @@ type KubeCSRResponse struct { // ProcessKubeCSR processes CSR request against Kubernetes CA, returns // signed certificate if successful. func (s *Server) ProcessKubeCSR(req KubeCSR) (*KubeCSRResponse, error) { + ctx := context.TODO() if !modules.GetModules().Features().Kubernetes { return nil, trace.AccessDenied( "this Teleport cluster is not licensed for Kubernetes, please contact the cluster administrator") @@ -79,7 +81,7 @@ func (s *Server) ProcessKubeCSR(req KubeCSR) (*KubeCSRResponse, error) { // with special provisions. log.Debugf("Generating certificate to access remote Kubernetes clusters.") - hostCA, err := s.GetCertAuthority(types.CertAuthID{ + hostCA, err := s.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: req.ClusterName, }, false) @@ -109,7 +111,7 @@ func (s *Server) ProcessKubeCSR(req KubeCSR) (*KubeCSRResponse, error) { roleNames := id.Groups // This is a remote user, map roles to local roles first. if id.TeleportCluster != clusterName.GetClusterName() { - ca, err := s.GetCertAuthority(types.CertAuthID{Type: types.UserCA, DomainName: id.TeleportCluster}, false) + ca, err := s.GetCertAuthority(ctx, types.CertAuthID{Type: types.UserCA, DomainName: id.TeleportCluster}, false) if err != nil { return nil, trace.Wrap(err) } @@ -130,7 +132,7 @@ func (s *Server) ProcessKubeCSR(req KubeCSR) (*KubeCSRResponse, error) { // Get the correct cert TTL based on roles. ttl := roles.AdjustSessionTTL(apidefaults.CertDuration) - userCA, err := s.Trust.GetCertAuthority(types.CertAuthID{ + userCA, err := s.Trust.GetCertAuthority(ctx, types.CertAuthID{ Type: types.UserCA, DomainName: clusterName.GetClusterName(), }, true) diff --git a/lib/auth/methods.go b/lib/auth/methods.go index a831d5b795284..9a2d486a99909 100644 --- a/lib/auth/methods.go +++ b/lib/auth/methods.go @@ -48,10 +48,11 @@ type AuthenticateUserRequest struct { // CheckAndSetDefaults checks and sets defaults func (a *AuthenticateUserRequest) CheckAndSetDefaults() error { - if a.Username == "" { + switch { + case a.Username == "" && a.Webauthn != nil: // OK, passwordless. + case a.Username == "": return trace.BadParameter("missing parameter 'username'") - } - if a.Pass == nil && a.Webauthn == nil && a.OTP == nil && a.Session == nil { + case a.Pass == nil && a.Webauthn == nil && a.OTP == nil && a.Session == nil: return trace.BadParameter("at least one authentication method is required") } return nil @@ -77,16 +78,28 @@ type SessionCreds struct { ID string `json:"id"` } -// AuthenticateUser authenticates user based on the request type -func (s *Server) AuthenticateUser(req AuthenticateUserRequest) error { - mfaDev, err := s.authenticateUser(context.TODO(), req) +// AuthenticateUser authenticates user based on the request type. +// Returns the username of the authenticated user. +func (s *Server) AuthenticateUser(req AuthenticateUserRequest) (string, error) { + user := req.Username + + mfaDev, actualUser, err := s.authenticateUser(context.TODO(), req) + // err is handled below. + switch { + case user != "" && actualUser != "" && user != actualUser: + log.Warnf("Authenticate user mismatch (%q vs %q). Using request user (%q)", user, actualUser, user) + case user == "" && actualUser != "": + log.Debugf("User %q authenticated via passwordless", actualUser) + user = actualUser + } + event := &apievents.UserLogin{ Metadata: apievents.Metadata{ Type: events.UserLoginEvent, Code: events.UserLocalLoginFailureCode, }, UserMetadata: apievents.UserMetadata{ - User: req.Username, + User: user, }, Method: events.LoginMethodLocal, } @@ -105,14 +118,27 @@ func (s *Server) AuthenticateUser(req AuthenticateUserRequest) error { if err := s.emitter.EmitAuditEvent(s.closeCtx, event); err != nil { log.WithError(err).Warn("Failed to emit login event.") } - return err + return user, trace.Wrap(err) } -func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserRequest) (*types.MFADevice, error) { +// authenticateWebauthnError is the generic error message returned for failed +// WebAuthn authentication attempts. +const authenticateWebauthnError = "invalid Webauthn response" + +// authenticateUser authenticates a user through various methods (password, MFA, +// passwordless) +// Returns the device used to authenticate (if applicable) and the username. +func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserRequest) (*types.MFADevice, string, error) { if err := req.CheckAndSetDefaults(); err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } user := req.Username + passwordless := user == "" + + // Only one path if passwordless, other variants shouldn't see an empty user. + if passwordless { + return s.authenticatePasswordless(ctx, req) + } // Try 2nd-factor-enabled authentication schemes first. var authenticateFn func() (*types.MFADevice, error) @@ -126,9 +152,10 @@ func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserReque Webauthn: wanlib.CredentialAssertionResponseToProto(req.Webauthn), }, } - return s.validateMFAAuthResponse(ctx, user, mfaResponse) + dev, _, err := s.validateMFAAuthResponse(ctx, mfaResponse, user, passwordless) + return dev, trace.Wrap(err) } - failMsg = "invalid Webauthn response" + failMsg = authenticateWebauthnError case req.OTP != nil: authenticateFn = func() (*types.MFADevice, error) { // OTP cannot be validated by validateMFAAuthResponse because we need to @@ -152,28 +179,28 @@ func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserReque case err != nil: log.Debugf("User %v failed to authenticate: %v.", user, err) if fieldErr := getErrorByTraceField(err); fieldErr != nil { - return nil, trace.Wrap(fieldErr) + return nil, "", trace.Wrap(fieldErr) } - return nil, trace.AccessDenied(failMsg) + return nil, "", trace.AccessDenied(failMsg) case dev == nil: log.Debugf( "MFA authentication returned nil device (Webauthn = %v, TOTP = %v): %v.", req.Webauthn != nil, req.OTP != nil, err) - return nil, trace.AccessDenied(failMsg) + return nil, "", trace.AccessDenied(failMsg) default: - return dev, nil + return dev, user, nil } } // Try password-only authentication last. if req.Pass == nil { - return nil, trace.AccessDenied("unsupported authentication method") + return nil, "", trace.AccessDenied("unsupported authentication method") } authPreference, err := s.GetAuthPreference(ctx) if err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } // When using password only make sure that auth preference does not require @@ -186,37 +213,62 @@ func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserReque // registered. devs, err := s.Identity.GetMFADevices(ctx, user, false /* withSecrets */) if err != nil && !trace.IsNotFound(err) { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } if len(devs) != 0 { log.Warningf("MFA bypass attempt by user %q, access denied.", user) - return nil, trace.AccessDenied("missing second factor authentication") + return nil, "", trace.AccessDenied("missing second factor authentication") } default: // Some form of MFA is required but none provided. Either client is // buggy (didn't send MFA response) or someone is trying to bypass // MFA. log.Warningf("MFA bypass attempt by user %q, access denied.", user) - return nil, trace.AccessDenied("missing second factor") + return nil, "", trace.AccessDenied("missing second factor") } if err = s.WithUserLock(user, func() error { return s.checkPasswordWOToken(user, req.Pass.Password) }); err != nil { if fieldErr := getErrorByTraceField(err); fieldErr != nil { - return nil, trace.Wrap(fieldErr) + return nil, "", trace.Wrap(fieldErr) } // provide obscure message on purpose, while logging the real // error server side log.Debugf("User %v failed to authenticate: %v.", user, err) - return nil, trace.AccessDenied("invalid username or password") + return nil, "", trace.AccessDenied("invalid username or password") } - return nil, nil + return nil, user, nil +} + +func (s *Server) authenticatePasswordless(ctx context.Context, req AuthenticateUserRequest) (*types.MFADevice, string, error) { + mfaResponse := &proto.MFAAuthenticateResponse{ + Response: &proto.MFAAuthenticateResponse_Webauthn{ + Webauthn: wanlib.CredentialAssertionResponseToProto(req.Webauthn), + }, + } + dev, user, err := s.validateMFAAuthResponse(ctx, mfaResponse, "", true /* passwordless */) + if err != nil { + log.Debugf("Passwordless authentication failed: %v", err) + return nil, "", trace.AccessDenied(authenticateWebauthnError) + } + + // A distinction between passwordless and "plain" MFA is that we can't + // acquire the user lock beforehand (or at all on failures!) + // We do grab it here so successful logins go through the regular process. + if err := s.WithUserLock(user, func() error { return nil }); err != nil { + log.Debugf("WithUserLock for user %q failed during passwordless authentication: %v", user, err) + return nil, user, trace.AccessDenied(authenticateWebauthnError) + } + + return dev, user, nil } // AuthenticateWebUser authenticates web user, creates and returns a web session // if authentication is successful. In case the existing session ID is used to authenticate, // returns the existing session instead of creating a new one func (s *Server) AuthenticateWebUser(req AuthenticateUserRequest) (types.WebSession, error) { + username := req.Username // Empty if passwordless. + ctx := context.TODO() authPref, err := s.GetAuthPreference(ctx) if err != nil { @@ -228,13 +280,13 @@ func (s *Server) AuthenticateWebUser(req AuthenticateUserRequest) (types.WebSess // This condition uses Session as a blanket check, because any new method added // to the local auth will be disabled by default. if !authPref.GetAllowLocalAuth() && req.Session == nil { - s.emitNoLocalAuthEvent(req.Username) + s.emitNoLocalAuthEvent(username) return nil, trace.AccessDenied(noLocalAuth) } if req.Session != nil { session, err := s.GetWebSession(context.TODO(), types.GetWebSessionRequest{ - User: req.Username, + User: username, SessionID: req.Session.ID, }) if err != nil { @@ -243,11 +295,13 @@ func (s *Server) AuthenticateWebUser(req AuthenticateUserRequest) (types.WebSess return session, nil } - if err := s.AuthenticateUser(req); err != nil { + actualUser, err := s.AuthenticateUser(req) + if err != nil { return nil, trace.Wrap(err) } + username = actualUser - user, err := s.GetUser(req.Username, false) + user, err := s.GetUser(username, false /* withSecrets */) if err != nil { return nil, trace.Wrap(err) } @@ -349,13 +403,15 @@ func AuthoritiesToTrustedCerts(authorities []types.CertAuthority) []TrustedCerts // AuthenticateSSHUser authenticates an SSH user and returns SSH and TLS // certificates for the public key in req. func (s *Server) AuthenticateSSHUser(req AuthenticateSSHRequest) (*SSHLoginResponse, error) { + username := req.Username // Empty if passwordless. + ctx := context.TODO() authPref, err := s.GetAuthPreference(ctx) if err != nil { return nil, trace.Wrap(err) } if !authPref.GetAllowLocalAuth() { - s.emitNoLocalAuthEvent(req.Username) + s.emitNoLocalAuthEvent(username) return nil, trace.AccessDenied(noLocalAuth) } @@ -364,13 +420,15 @@ func (s *Server) AuthenticateSSHUser(req AuthenticateSSHRequest) (*SSHLoginRespo return nil, trace.Wrap(err) } - if err := s.AuthenticateUser(req.AuthenticateUserRequest); err != nil { + actualUser, err := s.AuthenticateUser(req.AuthenticateUserRequest) + if err != nil { return nil, trace.Wrap(err) } + username = actualUser // It's safe to extract the roles and traits directly from services.User as // this endpoint is only used for local accounts. - user, err := s.GetUser(req.Username, false) + user, err := s.GetUser(username, false /* withSecrets */) if err != nil { return nil, trace.Wrap(err) } @@ -380,7 +438,7 @@ func (s *Server) AuthenticateSSHUser(req AuthenticateSSHRequest) (*SSHLoginRespo } // Return the host CA for this cluster only. - authority, err := s.GetCertAuthority(types.CertAuthID{ + authority, err := s.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName.GetClusterName(), }, false) @@ -406,7 +464,7 @@ func (s *Server) AuthenticateSSHUser(req AuthenticateSSHRequest) (*SSHLoginRespo } UserLoginCount.Inc() return &SSHLoginResponse{ - Username: req.Username, + Username: username, Cert: certs.SSH, TLSCert: certs.TLS, HostSigners: AuthoritiesToTrustedCerts(hostCertAuthorities), diff --git a/lib/auth/middleware.go b/lib/auth/middleware.go index 45d914dd4f17f..08752948435cc 100644 --- a/lib/auth/middleware.go +++ b/lib/auth/middleware.go @@ -30,6 +30,7 @@ import ( apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" + "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/multiplexer" "github.com/gravitational/teleport/lib/tlsca" @@ -319,6 +320,7 @@ func (a *Middleware) Wrap(h http.Handler) { func getCustomRate(endpoint string) *ratelimit.RateSet { switch endpoint { + // Account recovery RPCs. case "/proto.AuthService/ChangeUserAuthentication", "/proto.AuthService/GetAccountRecoveryToken", @@ -331,8 +333,18 @@ func getCustomRate(endpoint string) *ratelimit.RateSet { return nil } return rates + // Passwordless RPCs (potential unauthenticated challenge generation). + case "/proto.AuthService/CreateAuthenticateChallenge": + const period = defaults.LimiterPasswordlessPeriod + const average = defaults.LimiterPasswordlessAverage + const burst = defaults.LimiterPasswordlessBurst + rates := ratelimit.NewRateSet() + if err := rates.Add(period, average, burst); err != nil { + log.WithError(err).Debugf("Failed to define a custom rate for rpc method %q, using default rate", endpoint) + return nil + } + return rates } - return nil } @@ -570,14 +582,15 @@ func (a *Middleware) WrapContextWithUser(ctx context.Context, conn *tls.Conn) (c // ClientCertPool returns trusted x509 cerificate authority pool func ClientCertPool(client AccessCache, clusterName string) (*x509.CertPool, error) { + ctx := context.TODO() pool := x509.NewCertPool() var authorities []types.CertAuthority if clusterName == "" { - hostCAs, err := client.GetCertAuthorities(types.HostCA, false) + hostCAs, err := client.GetCertAuthorities(ctx, types.HostCA, false) if err != nil { return nil, trace.Wrap(err) } - userCAs, err := client.GetCertAuthorities(types.UserCA, false) + userCAs, err := client.GetCertAuthorities(ctx, types.UserCA, false) if err != nil { return nil, trace.Wrap(err) } @@ -585,12 +598,14 @@ func ClientCertPool(client AccessCache, clusterName string) (*x509.CertPool, err authorities = append(authorities, userCAs...) } else { hostCA, err := client.GetCertAuthority( + ctx, types.CertAuthID{Type: types.HostCA, DomainName: clusterName}, false) if err != nil { return nil, trace.Wrap(err) } userCA, err := client.GetCertAuthority( + ctx, types.CertAuthID{Type: types.UserCA, DomainName: clusterName}, false) if err != nil { diff --git a/lib/auth/mocku2f/mocku2f.go b/lib/auth/mocku2f/mocku2f.go index fe88acd22e224..aaa643c1d4135 100644 --- a/lib/auth/mocku2f/mocku2f.go +++ b/lib/auth/mocku2f/mocku2f.go @@ -143,6 +143,14 @@ func CreateWithKeyHandle(keyHandle []byte) (*Key, error) { }, nil } +// SetPasswordless sets common passwordless options in Key. +// Options are AllowResidentKey, IgnoreAllowedCredentials and SetUV. +func (muk *Key) SetPasswordless() { + muk.AllowResidentKey = true // Passwordless keys must be resident. + muk.IgnoreAllowedCredentials = true // Empty for passwordless challenges. + muk.SetUV = true // UV required for passwordless. +} + func (muk *Key) RegisterResponse(req *u2f.RegisterRequest) (*u2f.RegisterResponse, error) { appIDHash := sha256.Sum256([]byte(req.AppID)) @@ -201,7 +209,7 @@ func (muk *Key) signRegister(appIDHash, clientDataHash []byte) (*signRegisterRes return nil, trace.Wrap(err) } - var flags = uint8(u2fRegistrationFlags) + flags := uint8(u2fRegistrationFlags) if muk.SetUV { // Mimic WebAuthn flags if SetUV is true. flags = uint8(protocol.FlagUserPresent | protocol.FlagUserVerified | protocol.FlagAttestedCredentialData) diff --git a/lib/auth/oidc.go b/lib/auth/oidc.go index f732841395f33..fd36bba8b0d29 100644 --- a/lib/auth/oidc.go +++ b/lib/auth/oidc.go @@ -20,7 +20,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "net/url" @@ -365,7 +365,7 @@ func (a *Server) validateOIDCAuthCallback(q url.Values) (*oidcAuthResponse, erro // If the request is coming from a browser, create a web session. if req.CreateWebSession { - session, err := a.createWebSession(context.TODO(), types.NewWebSessionRequest{ + session, err := a.createWebSession(ctx, types.NewWebSessionRequest{ User: user.GetName(), Roles: user.GetRoles(), Traits: user.GetTraits(), @@ -389,7 +389,7 @@ func (a *Server) validateOIDCAuthCallback(q url.Values) (*oidcAuthResponse, erro re.auth.TLSCert = tlsCert // Return the host CA for this cluster only. - authority, err := a.GetCertAuthority(types.CertAuthID{ + authority, err := a.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName.GetClusterName(), }, false) @@ -600,7 +600,7 @@ func claimsFromUserInfo(oidcClient *oidc.Client, issuerURL string, accessToken s code == http.StatusForbidden || code == http.StatusMethodNotAllowed { return nil, trace.AccessDenied("bad status code: %v", code) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/auth/password.go b/lib/auth/password.go index a357161b48814..1023a3a17a59c 100644 --- a/lib/auth/password.go +++ b/lib/auth/password.go @@ -122,7 +122,7 @@ func (s *Server) ChangePassword(req services.ChangePasswordReq) error { Token: req.SecondFactorToken, } } - if _, err := s.authenticateUser(ctx, authReq); err != nil { + if _, _, err := s.authenticateUser(ctx, authReq); err != nil { return trace.Wrap(err) } diff --git a/lib/auth/permissions.go b/lib/auth/permissions.go index e0c886ba8d7fd..66934095315e9 100644 --- a/lib/auth/permissions.go +++ b/lib/auth/permissions.go @@ -81,10 +81,10 @@ type AuthorizerAccessPoint interface { GetUser(name string, withSecrets bool) (types.User, error) // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) // GetClusterAuditConfig returns cluster audit configuration. GetClusterAuditConfig(ctx context.Context, opts ...services.MarshalOption) (types.ClusterAuditConfig, error) @@ -174,7 +174,7 @@ func (a *authorizer) fromUser(ctx context.Context, userI interface{}) (*Context, case LocalUser: return a.authorizeLocalUser(user) case RemoteUser: - return a.authorizeRemoteUser(user) + return a.authorizeRemoteUser(ctx, user) case BuiltinRole: return a.authorizeBuiltinRole(ctx, user) case RemoteBuiltinRole: @@ -190,8 +190,8 @@ func (a *authorizer) authorizeLocalUser(u LocalUser) (*Context, error) { } // authorizeRemoteUser returns checker based on cert authority roles -func (a *authorizer) authorizeRemoteUser(u RemoteUser) (*Context, error) { - ca, err := a.accessPoint.GetCertAuthority(types.CertAuthID{ +func (a *authorizer) authorizeRemoteUser(ctx context.Context, u RemoteUser) (*Context, error) { + ca, err := a.accessPoint.GetCertAuthority(ctx, types.CertAuthID{ Type: types.UserCA, DomainName: u.ClusterName, }, false) diff --git a/lib/auth/permissions_test.go b/lib/auth/permissions_test.go index a11f9282ea075..f2dc9cc90a6ff 100644 --- a/lib/auth/permissions_test.go +++ b/lib/auth/permissions_test.go @@ -66,13 +66,13 @@ func TestAuthorizeWithLocksForLocalUser(t *testing.T) { }) require.NoError(t, err) - user, _, err := CreateUserAndRole(srv.AuthServer, "test-user", []string{}) + user, role, err := CreateUserAndRole(srv.AuthServer, "test-user", []string{}) require.NoError(t, err) localUser := LocalUser{ Username: user.GetName(), Identity: tlsca.Identity{ Username: user.GetName(), - Groups: []string{"test-role-1"}, + Groups: []string{role.GetName()}, MFAVerified: "mfa-device-id", ActiveRequests: []string{"test-request"}, }, diff --git a/lib/auth/register.go b/lib/auth/register.go index ca050b59d3ccf..d905ea00318ea 100644 --- a/lib/auth/register.go +++ b/lib/auth/register.go @@ -19,7 +19,6 @@ package auth import ( "context" "crypto/x509" - "encoding/json" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client" @@ -521,39 +520,3 @@ func ReRegister(params ReRegisterParams) (*Identity, error) { return ReadIdentityFromKeyPair(params.PrivateKey, certs) } - -// LegacyCerts is equivalent to proto.Certs, but uses -// JSON keys expected by old clients (7.x and earlier) -// DELETE in 9.0.0 (Joerger/zmb3) -type LegacyCerts struct { - SSHCert []byte `json:"cert"` - TLSCert []byte `json:"tls_cert"` - TLSCACerts [][]byte `json:"tls_ca_certs"` - SSHCACerts [][]byte `json:"ssh_ca_certs"` -} - -// LegacyCertsFromProto converts proto.Certs to LegacyCerts. -// DELETE in 10.0.0 (Joerger/zmb3) -func LegacyCertsFromProto(c *proto.Certs) *LegacyCerts { - return &LegacyCerts{ - SSHCert: c.SSH, - TLSCert: c.TLS, - SSHCACerts: c.SSHCACerts, - TLSCACerts: c.TLSCACerts, - } -} - -// UnmarshalLegacyCerts unmarshals the a legacy certs response as proto.Certs. -// DELETE in 10.0.0 (Joerger/zmb3) -func UnmarshalLegacyCerts(bytes []byte) (*proto.Certs, error) { - var lc LegacyCerts - if err := json.Unmarshal(bytes, &lc); err != nil { - return nil, trace.Wrap(err) - } - return &proto.Certs{ - SSH: lc.SSHCert, - TLS: lc.TLSCert, - TLSCACerts: lc.TLSCACerts, - SSHCACerts: lc.SSHCACerts, - }, nil -} diff --git a/lib/auth/rotate.go b/lib/auth/rotate.go index 749b64c49a4e8..b8583e41a390b 100644 --- a/lib/auth/rotate.go +++ b/lib/auth/rotate.go @@ -17,6 +17,7 @@ limitations under the License. package auth import ( + "context" "crypto/rsa" "crypto/x509/pkix" "time" @@ -201,7 +202,7 @@ type rotationReq struct { // It is possible to switch from automatic to manual by setting the phase // to the rollback phase. // -func (a *Server) RotateCertAuthority(req RotateRequest) error { +func (a *Server) RotateCertAuthority(ctx context.Context, req RotateRequest) error { if err := req.CheckAndSetDefaults(a.clock); err != nil { return trace.Wrap(err) } @@ -212,7 +213,7 @@ func (a *Server) RotateCertAuthority(req RotateRequest) error { caTypes := req.Types() for _, caType := range caTypes { - existing, err := a.Trust.GetCertAuthority(types.CertAuthID{ + existing, err := a.Trust.GetCertAuthority(ctx, types.CertAuthID{ Type: caType, DomainName: clusterName.GetClusterName(), }, true) @@ -250,7 +251,7 @@ func (a *Server) RotateCertAuthority(req RotateRequest) error { // RotateExternalCertAuthority rotates external certificate authority, // this method is called by remote trusted cluster and is used to update // only public keys and certificates of the certificate authority. -func (a *Server) RotateExternalCertAuthority(ca types.CertAuthority) error { +func (a *Server) RotateExternalCertAuthority(ctx context.Context, ca types.CertAuthority) error { if ca == nil { return trace.BadParameter("missing certificate authority") } @@ -265,7 +266,7 @@ func (a *Server) RotateExternalCertAuthority(ca types.CertAuthority) error { return trace.BadParameter("can not rotate local certificate authority") } - existing, err := a.Trust.GetCertAuthority(types.CertAuthID{ + existing, err := a.Trust.GetCertAuthority(ctx, types.CertAuthID{ Type: ca.GetType(), DomainName: ca.GetClusterName(), }, false) @@ -309,13 +310,13 @@ func (a *Server) RotateExternalCertAuthority(ca types.CertAuthority) error { // autoRotateCertAuthorities automatically rotates cert authorities, // does nothing if no rotation parameters were set up // or it is too early to rotate per schedule -func (a *Server) autoRotateCertAuthorities() error { +func (a *Server) autoRotateCertAuthorities(ctx context.Context) error { clusterName, err := a.GetClusterName() if err != nil { return trace.Wrap(err) } for _, caType := range []types.CertAuthType{types.HostCA, types.UserCA, types.JWTSigner} { - ca, err := a.Trust.GetCertAuthority(types.CertAuthID{ + ca, err := a.Trust.GetCertAuthority(ctx, types.CertAuthID{ Type: caType, DomainName: clusterName.GetClusterName(), }, true) @@ -327,7 +328,7 @@ func (a *Server) autoRotateCertAuthorities() error { } // make sure there are local AdditionalKeys during init phase of rotation if ca.GetRotation().Phase == types.RotationPhaseInit { - if err := a.ensureLocalAdditionalKeys(ca); err != nil { + if err := a.ensureLocalAdditionalKeys(ctx, ca); err != nil { return trace.Wrap(err) } } diff --git a/lib/auth/saml.go b/lib/auth/saml.go index c007476d3cc1a..8a97bee746508 100644 --- a/lib/auth/saml.go +++ b/lib/auth/saml.go @@ -22,7 +22,7 @@ import ( "context" "encoding/base64" "fmt" - "io/ioutil" + "io" "github.com/google/go-cmp/cmp" @@ -254,7 +254,7 @@ func parseSAMLInResponseTo(response string) (string, error) { err := doc.ReadFromBytes(raw) if err != nil { // Attempt to inflate the response in case it happens to be compressed (as with one case at saml.oktadev.com) - buf, err := ioutil.ReadAll(flate.NewReader(bytes.NewReader(raw))) + buf, err := io.ReadAll(flate.NewReader(bytes.NewReader(raw))) if err != nil { return "", trace.Wrap(err) } @@ -427,7 +427,7 @@ func (a *Server) validateSAMLResponse(samlResponse string) (*samlAuthResponse, e // If the request is coming from a browser, create a web session. if request.CreateWebSession { - session, err := a.createWebSession(context.TODO(), types.NewWebSessionRequest{ + session, err := a.createWebSession(ctx, types.NewWebSessionRequest{ User: user.GetName(), Roles: user.GetRoles(), Traits: user.GetTraits(), @@ -455,7 +455,7 @@ func (a *Server) validateSAMLResponse(samlResponse string) (*samlAuthResponse, e re.auth.TLSCert = tlsCert // Return the host CA for this cluster only. - authority, err := a.GetCertAuthority(types.CertAuthID{ + authority, err := a.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName.GetClusterName(), }, false) diff --git a/lib/auth/session_access.go b/lib/auth/session_access.go index c4b77b7efde5d..82df61ac6bd20 100644 --- a/lib/auth/session_access.go +++ b/lib/auth/session_access.go @@ -17,6 +17,7 @@ limitations under the License. package auth import ( + "fmt" "regexp" "strings" @@ -36,17 +37,27 @@ import ( // that is harder to debug in the case of misconfigured policies or other error and are harder to intuitively follow. // In the real world, the number of roles and session are small enough that this doesn't have a meaningful impact. type SessionAccessEvaluator struct { - kind types.SessionKind - policySets []*types.SessionTrackerPolicySet + kind types.SessionKind + policySets []*types.SessionTrackerPolicySet + isModerated bool } // NewSessionAccessEvaluator creates a new session access evaluator for a given session kind // and a set of roles attached to the host user. func NewSessionAccessEvaluator(policySets []*types.SessionTrackerPolicySet, kind types.SessionKind) SessionAccessEvaluator { - return SessionAccessEvaluator{ - kind, - policySets, + e := SessionAccessEvaluator{ + kind: kind, + policySets: policySets, } + + for _, policySet := range policySets { + if len(e.extractApplicablePolicies(policySet)) != 0 { + e.isModerated = true + break + } + } + + return e } func getAllowPolicies(participant SessionAccessContext) []*types.SessionJoinPolicy { @@ -102,8 +113,13 @@ func (ctx *SessionAccessContext) GetResource() (types.Resource, error) { return nil, trace.BadParameter("resource unsupported") } +// IsModerated returns true if the session needs moderation. +func (e *SessionAccessEvaluator) IsModerated() bool { + return e.isModerated +} + func (e *SessionAccessEvaluator) matchesPredicate(ctx *SessionAccessContext, require *types.SessionRequirePolicy, allow *types.SessionJoinPolicy) (bool, error) { - if !e.matchesKind(require.Kinds) || !e.matchesKind(allow.Kinds) { + if !e.matchesKind(allow.Kinds) { return false, nil } @@ -209,6 +225,39 @@ func (e *SessionAccessEvaluator) hasPolicies() bool { return false } +// Generate a pretty-printed string of precise requirements for session start suitable for user display. +func (e *SessionAccessEvaluator) PrettyRequirementsList() string { + s := new(strings.Builder) + s.WriteString("require all:") + + for _, policySet := range e.policySets { + policies := e.extractApplicablePolicies(policySet) + if len(policies) == 0 { + continue + } + + fmt.Fprintf(s, "\r\n one of (%v):", policySet.Name) + for _, require := range policies { + fmt.Fprintf(s, "\r\n - %vx %v with mode %v", require.Count, require.Filter, strings.Join(require.Modes, " or ")) + } + } + + return s.String() +} + +// extractApplicablePolicies extracts all policies that match the session kind. +func (e *SessionAccessEvaluator) extractApplicablePolicies(set *types.SessionTrackerPolicySet) []*types.SessionRequirePolicy { + var policies []*types.SessionRequirePolicy + + for _, require := range set.RequireSessionJoin { + if e.matchesKind(require.Kinds) { + policies = append(policies, require) + } + } + + return policies +} + // FulfilledFor checks if a given session may run with a list of participants. func (e *SessionAccessEvaluator) FulfilledFor(participants []SessionAccessContext) (bool, PolicyOptions, error) { supported, err := e.supportsSessionAccessControls() @@ -227,13 +276,14 @@ func (e *SessionAccessEvaluator) FulfilledFor(participants []SessionAccessContex // We need every policy set to match to allow the session. policySetLoop: for _, policySet := range e.policySets { - if len(policySet.RequireSessionJoin) == 0 { + policies := e.extractApplicablePolicies(policySet) + if len(policies) == 0 { continue } // Check every require policy to see if it's fulfilled. // Only one needs to be checked to pass the policyset. - for _, requirePolicy := range policySet.RequireSessionJoin { + for _, requirePolicy := range policies { // Count of how many additional participant matches we need to fulfill the policy. left := requirePolicy.Count diff --git a/lib/auth/session_access_test.go b/lib/auth/session_access_test.go index 0f2f6187d500a..50faddc0b9618 100644 --- a/lib/auth/session_access_test.go +++ b/lib/auth/session_access_test.go @@ -32,9 +32,9 @@ type startTestCase struct { } func successStartTestCase(t *testing.T) startTestCase { - hostRole, err := types.NewRoleV5("host", types.RoleSpecV5{}) + hostRole, err := types.NewRole("host", types.RoleSpecV5{}) require.NoError(t, err) - participantRole, err := types.NewRoleV5("participant", types.RoleSpecV5{}) + participantRole, err := types.NewRole("participant", types.RoleSpecV5{}) require.NoError(t, err) hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{{ @@ -72,9 +72,9 @@ func successStartTestCase(t *testing.T) startTestCase { } func failCountStartTestCase(t *testing.T) startTestCase { - hostRole, err := types.NewRoleV5("host", types.RoleSpecV5{}) + hostRole, err := types.NewRole("host", types.RoleSpecV5{}) require.NoError(t, err) - participantRole, err := types.NewRoleV5("participant", types.RoleSpecV5{}) + participantRole, err := types.NewRole("participant", types.RoleSpecV5{}) require.NoError(t, err) hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{{ @@ -110,10 +110,29 @@ func failCountStartTestCase(t *testing.T) startTestCase { } } +func succeedDiscardPolicySetStartTestCase(t *testing.T) startTestCase { + hostRole, err := types.NewRole("host", types.RoleSpecV5{}) + require.NoError(t, err) + + hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{{ + Filter: "contains(user.roles, \"host\")", + Kinds: []string{string(types.KubernetesSessionKind)}, + Count: 2, + Modes: []string{"peer"}, + }}) + + return startTestCase{ + name: "succeedDiscardPolicySet", + host: hostRole, + sessionKind: types.SSHSessionKind, + expected: true, + } +} + func failFilterStartTestCase(t *testing.T) startTestCase { - hostRole, err := types.NewRoleV5("host", types.RoleSpecV5{}) + hostRole, err := types.NewRole("host", types.RoleSpecV5{}) require.NoError(t, err) - participantRole, err := types.NewRoleV5("participant", types.RoleSpecV5{}) + participantRole, err := types.NewRole("participant", types.RoleSpecV5{}) require.NoError(t, err) hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{{ @@ -154,6 +173,7 @@ func TestSessionAccessStart(t *testing.T) { successStartTestCase(t), failCountStartTestCase(t), failFilterStartTestCase(t), + succeedDiscardPolicySetStartTestCase(t), } for _, testCase := range testCases { @@ -176,9 +196,9 @@ type joinTestCase struct { } func successJoinTestCase(t *testing.T) joinTestCase { - hostRole, err := types.NewRoleV5("host", types.RoleSpecV5{}) + hostRole, err := types.NewRole("host", types.RoleSpecV5{}) require.NoError(t, err) - participantRole, err := types.NewRoleV5("participant", types.RoleSpecV5{}) + participantRole, err := types.NewRole("participant", types.RoleSpecV5{}) require.NoError(t, err) participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{ @@ -200,9 +220,9 @@ func successJoinTestCase(t *testing.T) joinTestCase { } func failRoleJoinTestCase(t *testing.T) joinTestCase { - hostRole, err := types.NewRoleV5("host", types.RoleSpecV5{}) + hostRole, err := types.NewRole("host", types.RoleSpecV5{}) require.NoError(t, err) - participantRole, err := types.NewRoleV5("participant", types.RoleSpecV5{}) + participantRole, err := types.NewRole("participant", types.RoleSpecV5{}) require.NoError(t, err) return joinTestCase{ @@ -218,9 +238,9 @@ func failRoleJoinTestCase(t *testing.T) joinTestCase { } func failKindJoinTestCase(t *testing.T) joinTestCase { - hostRole, err := types.NewRoleV5("host", types.RoleSpecV5{}) + hostRole, err := types.NewRole("host", types.RoleSpecV5{}) require.NoError(t, err) - participantRole, err := types.NewRoleV5("participant", types.RoleSpecV5{}) + participantRole, err := types.NewRole("participant", types.RoleSpecV5{}) require.NoError(t, err) participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{ diff --git a/lib/auth/sessions.go b/lib/auth/sessions.go index ee9dbba7a3a5e..11aae3709104d 100644 --- a/lib/auth/sessions.go +++ b/lib/auth/sessions.go @@ -156,13 +156,13 @@ func WaitForAppSession(ctx context.Context, sessionID, user string, ap ReadProxy // generateAppToken generates an JWT token that will be passed along with every // application request. -func (s *Server) generateAppToken(username string, roles []string, uri string, expires time.Time) (string, error) { +func (s *Server) generateAppToken(ctx context.Context, username string, roles []string, uri string, expires time.Time) (string, error) { // Get the clusters CA. clusterName, err := s.GetDomainName() if err != nil { return "", trace.Wrap(err) } - ca, err := s.GetCertAuthority(types.CertAuthID{ + ca, err := s.GetCertAuthority(ctx, types.CertAuthID{ Type: types.JWTSigner, DomainName: clusterName, }, true) diff --git a/lib/auth/state_unix.go b/lib/auth/state_unix.go index 88672c3dd390f..0cfb1e166b8eb 100644 --- a/lib/auth/state_unix.go +++ b/lib/auth/state_unix.go @@ -36,6 +36,7 @@ func NewProcessStorage(ctx context.Context, path string) (*ProcessStorage, error litebk, err := lite.NewWithConfig(ctx, lite.Config{ Path: path, EventsOff: true, + Sync: lite.SyncFull, }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/tls_test.go b/lib/auth/tls_test.go index 5d099723b3f22..66075cdc02efc 100644 --- a/lib/auth/tls_test.go +++ b/lib/auth/tls_test.go @@ -27,7 +27,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "os" "path/filepath" "testing" @@ -116,7 +115,7 @@ func (s *TLSSuite) TestRemoteBuiltinRole(c *check.C) { fixtures.ExpectConnectionProblem(c, err) // after trust is established, things are good - err = s.server.AuthServer.Trust(remoteServer, nil) + err = s.server.AuthServer.Trust(ctx, remoteServer, nil) c.Assert(err, check.IsNil) // re initialize client with trust established. @@ -212,7 +211,7 @@ func (s *TLSSuite) TestRemoteRotation(c *check.C) { c.Assert(err, check.IsNil) // after trust is established, things are good - err = s.server.AuthServer.Trust(remoteServer, nil) + err = s.server.AuthServer.Trust(ctx, remoteServer, nil) c.Assert(err, check.IsNil) remoteProxy, err := remoteServer.NewRemoteClient( @@ -227,7 +226,7 @@ func (s *TLSSuite) TestRemoteRotation(c *check.C) { gracePeriod := time.Hour remoteServer.AuthServer.privateKey, ok = fixtures.PEMBytes["rsa"] c.Assert(ok, check.Equals, true) - err = remoteServer.AuthServer.RotateCertAuthority(RotateRequest{ + err = remoteServer.AuthServer.RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseInit, @@ -236,7 +235,7 @@ func (s *TLSSuite) TestRemoteRotation(c *check.C) { c.Assert(err, check.IsNil) // moves to update clients - err = remoteServer.AuthServer.RotateCertAuthority(RotateRequest{ + err = remoteServer.AuthServer.RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseUpdateClients, @@ -244,7 +243,7 @@ func (s *TLSSuite) TestRemoteRotation(c *check.C) { }) c.Assert(err, check.IsNil) - remoteCA, err := remoteServer.AuthServer.GetCertAuthority(types.CertAuthID{ + remoteCA, err := remoteServer.AuthServer.GetCertAuthority(ctx, types.CertAuthID{ DomainName: remoteServer.ClusterName, Type: types.HostCA, }, false) @@ -254,7 +253,7 @@ func (s *TLSSuite) TestRemoteRotation(c *check.C) { // that is not associated with the remote cluster clone := remoteCA.Clone() clone.SetName(s.server.ClusterName()) - err = remoteProxy.RotateExternalCertAuthority(clone) + err = remoteProxy.RotateExternalCertAuthority(ctx, clone) fixtures.ExpectAccessDenied(c, err) // remote proxy can't upsert the certificate authority, @@ -263,26 +262,26 @@ func (s *TLSSuite) TestRemoteRotation(c *check.C) { fixtures.ExpectAccessDenied(c, err) // remote proxy can't read local cert authority with secrets - _, err = remoteProxy.GetCertAuthority(types.CertAuthID{ + _, err = remoteProxy.GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, true) fixtures.ExpectAccessDenied(c, err) // no secrets read is allowed - _, err = remoteProxy.GetCertAuthority(types.CertAuthID{ + _, err = remoteProxy.GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, false) c.Assert(err, check.IsNil) // remote auth server will get rejected - err = remoteAuth.RotateExternalCertAuthority(remoteCA) + err = remoteAuth.RotateExternalCertAuthority(ctx, remoteCA) fixtures.ExpectAccessDenied(c, err) // remote proxy should be able to perform remote cert authority // rotation - err = remoteProxy.RotateExternalCertAuthority(remoteCA) + err = remoteProxy.RotateExternalCertAuthority(ctx, remoteCA) c.Assert(err, check.IsNil) // newRemoteProxy should be trusted by the auth server @@ -301,6 +300,7 @@ func (s *TLSSuite) TestRemoteRotation(c *check.C) { // TestLocalProxyPermissions tests new local proxy permissions // as it's now allowed to update host cert authorities of remote clusters func (s *TLSSuite) TestLocalProxyPermissions(c *check.C) { + ctx := context.Background() remoteServer, err := NewTestAuthServer(TestAuthServerConfig{ Dir: c.MkDir(), ClusterName: "remote", @@ -309,10 +309,10 @@ func (s *TLSSuite) TestLocalProxyPermissions(c *check.C) { c.Assert(err, check.IsNil) // after trust is established, things are good - err = s.server.AuthServer.Trust(remoteServer, nil) + err = s.server.AuthServer.Trust(ctx, remoteServer, nil) c.Assert(err, check.IsNil) - ca, err := s.server.Auth().GetCertAuthority(types.CertAuthID{ + ca, err := s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, false) @@ -326,7 +326,7 @@ func (s *TLSSuite) TestLocalProxyPermissions(c *check.C) { fixtures.ExpectAccessDenied(c, err) // local proxy is allowed to update host CA of remote cert authorities - remoteCA, err := s.server.Auth().GetCertAuthority(types.CertAuthID{ + remoteCA, err := s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: remoteServer.ClusterName, Type: types.HostCA, }, false) @@ -353,7 +353,7 @@ func (s *TLSSuite) TestAutoRotation(c *check.C) { s.server.Auth().privateKey, ok = fixtures.PEMBytes["rsa"] c.Assert(ok, check.Equals, true) gracePeriod := time.Hour - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, Mode: types.RotationModeAuto, @@ -362,10 +362,10 @@ func (s *TLSSuite) TestAutoRotation(c *check.C) { // advance rotation by clock s.clock.Advance(gracePeriod/3 + time.Minute) - err = s.server.Auth().autoRotateCertAuthorities() + err = s.server.Auth().autoRotateCertAuthorities(ctx) c.Assert(err, check.IsNil) - ca, err := s.server.Auth().GetCertAuthority(types.CertAuthID{ + ca, err := s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, false) @@ -382,10 +382,10 @@ func (s *TLSSuite) TestAutoRotation(c *check.C) { // advance rotation by clock s.clock.Advance((gracePeriod*2)/3 + time.Minute) - err = s.server.Auth().autoRotateCertAuthorities() + err = s.server.Auth().autoRotateCertAuthorities(ctx) c.Assert(err, check.IsNil) - ca, err = s.server.Auth().GetCertAuthority(types.CertAuthID{ + ca, err = s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, false) @@ -405,9 +405,9 @@ func (s *TLSSuite) TestAutoRotation(c *check.C) { // complete rotation - advance rotation by clock s.clock.Advance(gracePeriod/3 + time.Minute) - err = s.server.Auth().autoRotateCertAuthorities() + err = s.server.Auth().autoRotateCertAuthorities(ctx) c.Assert(err, check.IsNil) - ca, err = s.server.Auth().GetCertAuthority(types.CertAuthID{ + ca, err = s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, false) @@ -447,7 +447,7 @@ func (s *TLSSuite) TestAutoFallback(c *check.C) { s.server.Auth().privateKey, ok = fixtures.PEMBytes["rsa"] c.Assert(ok, check.Equals, true) gracePeriod := time.Hour - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, Mode: types.RotationModeAuto, @@ -456,10 +456,10 @@ func (s *TLSSuite) TestAutoFallback(c *check.C) { // advance rotation by clock s.clock.Advance(gracePeriod/3 + time.Minute) - err = s.server.Auth().autoRotateCertAuthorities() + err = s.server.Auth().autoRotateCertAuthorities(ctx) c.Assert(err, check.IsNil) - ca, err := s.server.Auth().GetCertAuthority(types.CertAuthID{ + ca, err := s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, false) @@ -468,7 +468,7 @@ func (s *TLSSuite) TestAutoFallback(c *check.C) { c.Assert(ca.GetRotation().Mode, check.Equals, types.RotationModeAuto) // rollback rotation - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseRollback, @@ -476,7 +476,7 @@ func (s *TLSSuite) TestAutoFallback(c *check.C) { }) c.Assert(err, check.IsNil) - ca, err = s.server.Auth().GetCertAuthority(types.CertAuthID{ + ca, err = s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, false) @@ -503,7 +503,7 @@ func (s *TLSSuite) TestManualRotation(c *check.C) { gracePeriod := time.Hour s.server.Auth().privateKey, ok = fixtures.PEMBytes["rsa"] c.Assert(ok, check.Equals, true) - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseUpdateServers, @@ -512,7 +512,7 @@ func (s *TLSSuite) TestManualRotation(c *check.C) { fixtures.ExpectBadParameter(c, err) // starts rotation - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseInit, @@ -525,7 +525,7 @@ func (s *TLSSuite) TestManualRotation(c *check.C) { c.Assert(err, check.IsNil) // clients reconnect - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseUpdateClients, @@ -545,7 +545,7 @@ func (s *TLSSuite) TestManualRotation(c *check.C) { c.Assert(err, check.IsNil) // can't jump to standy - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseStandby, @@ -554,7 +554,7 @@ func (s *TLSSuite) TestManualRotation(c *check.C) { fixtures.ExpectBadParameter(c, err) // advance rotation: - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseUpdateServers, @@ -571,7 +571,7 @@ func (s *TLSSuite) TestManualRotation(c *check.C) { c.Assert(err, check.IsNil) // complete rotation - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseStandby, @@ -609,7 +609,7 @@ func (s *TLSSuite) TestRollback(c *check.C) { gracePeriod := time.Hour s.server.Auth().privateKey, ok = fixtures.PEMBytes["rsa"] c.Assert(ok, check.Equals, true) - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseInit, @@ -618,7 +618,7 @@ func (s *TLSSuite) TestRollback(c *check.C) { c.Assert(err, check.IsNil) // move to update clients phase - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseUpdateClients, @@ -634,7 +634,7 @@ func (s *TLSSuite) TestRollback(c *check.C) { c.Assert(err, check.IsNil) // advance rotation: - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseUpdateServers, @@ -643,7 +643,7 @@ func (s *TLSSuite) TestRollback(c *check.C) { c.Assert(err, check.IsNil) // rollback rotation - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseRollback, @@ -657,7 +657,7 @@ func (s *TLSSuite) TestRollback(c *check.C) { c.Assert(err, check.IsNil) // can't jump to other phases - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseUpdateClients, @@ -666,7 +666,7 @@ func (s *TLSSuite) TestRollback(c *check.C) { fixtures.ExpectBadParameter(c, err) // complete rollback - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseStandby, @@ -686,6 +686,7 @@ func (s *TLSSuite) TestRollback(c *check.C) { // TestAppTokenRotation checks that JWT tokens can be rotated and tokens can or // can not be validated at the appropriate phase. func (s *TLSSuite) TestAppTokenRotation(c *check.C) { + ctx := context.Background() client, err := s.server.NewClient(TestBuiltin(types.RoleApp)) c.Assert(err, check.IsNil) @@ -701,7 +702,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { c.Assert(err, check.IsNil) // Check that the "old" CA can be used to verify tokens. - oldCA, err := s.server.Auth().GetCertAuthority(types.CertAuthID{ + oldCA, err := s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.JWTSigner, }, true) @@ -715,7 +716,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { // Start rotation and move to initial phase. A new CA will be added (for // verification), but requests will continue to be signed by the old CA. gracePeriod := time.Hour - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.JWTSigner, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseInit, @@ -724,7 +725,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { c.Assert(err, check.IsNil) // At this point in rotation, two JWT key pairs should exist. - oldCA, err = s.server.Auth().GetCertAuthority(types.CertAuthID{ + oldCA, err = s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.JWTSigner, }, true) @@ -738,7 +739,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { // Move rotation into the update client phase. In this phase, requests will // be signed by the new CA, but the old CA will be around to verify requests. - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.JWTSigner, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseUpdateClients, @@ -747,7 +748,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { c.Assert(err, check.IsNil) // New tokens should now fail to validate with the old key. - newJWT, err := client.GenerateAppToken(context.Background(), + newJWT, err := client.GenerateAppToken(ctx, types.GenerateAppTokenRequest{ Username: "foo", Roles: []string{"bar", "baz"}, @@ -757,7 +758,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { c.Assert(err, check.IsNil) // New tokens will validate with the new key. - newCA, err := s.server.Auth().GetCertAuthority(types.CertAuthID{ + newCA, err := s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.JWTSigner, }, true) @@ -772,7 +773,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { c.Assert(err, check.IsNil) // Move rotation into update servers phase. - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.JWTSigner, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseUpdateServers, @@ -781,7 +782,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { c.Assert(err, check.IsNil) // At this point only the phase on the CA should have changed. - newCA, err = s.server.Auth().GetCertAuthority(types.CertAuthID{ + newCA, err = s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.JWTSigner, }, true) @@ -796,7 +797,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { c.Assert(err, check.IsNil) // Complete rotation. The old CA will be removed. - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.JWTSigner, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseStandby, @@ -805,7 +806,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { c.Assert(err, check.IsNil) // The new CA should now only have a single key. - newCA, err = s.server.Auth().GetCertAuthority(types.CertAuthID{ + newCA, err = s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.JWTSigner, }, true) @@ -823,6 +824,7 @@ func (s *TLSSuite) TestAppTokenRotation(c *check.C) { // TestRemoteUser tests scenario when remote user connects to the local // auth server and some edge cases. func (s *TLSSuite) TestRemoteUser(c *check.C) { + ctx := context.Background() remoteServer, err := NewTestAuthServer(TestAuthServerConfig{ Dir: c.MkDir(), ClusterName: "remote", @@ -847,7 +849,7 @@ func (s *TLSSuite) TestRemoteUser(c *check.C) { // Establish trust, the request will still fail, there is // no role mapping set up - err = s.server.AuthServer.Trust(remoteServer, nil) + err = s.server.AuthServer.Trust(ctx, remoteServer, nil) c.Assert(err, check.IsNil) _, err = remoteClient.GetDomainName() fixtures.ExpectAccessDenied(c, err) @@ -856,7 +858,7 @@ func (s *TLSSuite) TestRemoteUser(c *check.C) { _, localRole, err := CreateUserAndRole(s.server.Auth(), "local-user", []string{"local-role"}) c.Assert(err, check.IsNil) - err = s.server.AuthServer.Trust(remoteServer, types.RoleMap{{Remote: remoteRole.GetName(), Local: []string{localRole.GetName()}}}) + err = s.server.AuthServer.Trust(ctx, remoteServer, types.RoleMap{{Remote: remoteRole.GetName(), Local: []string{localRole.GetName()}}}) c.Assert(err, check.IsNil) _, err = remoteClient.GetDomainName() @@ -1589,7 +1591,7 @@ func (s *TLSSuite) TestGetCertAuthority(c *check.C) { defer nodeClt.Close() // node is authorized to fetch CA without secrets - ca, err := nodeClt.GetCertAuthority(types.CertAuthID{ + ca, err := nodeClt.GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, false) @@ -1602,7 +1604,7 @@ func (s *TLSSuite) TestGetCertAuthority(c *check.C) { } // node is not authorized to fetch CA with secrets - _, err = nodeClt.GetCertAuthority(types.CertAuthID{ + _, err = nodeClt.GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, true) @@ -1626,14 +1628,14 @@ func (s *TLSSuite) TestGetCertAuthority(c *check.C) { defer userClt.Close() // user is authorized to fetch CA without secrets - _, err = userClt.GetCertAuthority(types.CertAuthID{ + _, err = userClt.GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, false) c.Assert(err, check.IsNil) // user is not authorized to fetch CA with secrets - _, err = userClt.GetCertAuthority(types.CertAuthID{ + _, err = userClt.GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, true) @@ -2045,7 +2047,7 @@ func TestGenerateCerts(t *testing.T) { t.Run("ImpersonateAllow", func(t *testing.T) { // Super impersonator impersonate anyone and login as root maxSessionTTL := 300 * time.Hour - superImpersonatorRole, err := types.NewRole("superimpersonator", types.RoleSpecV5{ + superImpersonatorRole, err := types.NewRoleV3("superimpersonator", types.RoleSpecV5{ Options: types.RoleOptions{ MaxSessionTTL: types.Duration(maxSessionTTL), }, @@ -2063,7 +2065,7 @@ func TestGenerateCerts(t *testing.T) { require.NoError(t, err) // Impersonator can generate certificates for super impersonator - role, err := types.NewRole("impersonate", types.RoleSpecV5{ + role, err := types.NewRoleV3("impersonate", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{superImpersonator.GetName()}, Impersonate: &types.ImpersonateConditions{ @@ -2308,7 +2310,7 @@ func (s *TLSSuite) TestGenerateAppToken(c *check.C) { authClient, err := s.server.NewClient(TestBuiltin(types.RoleAdmin)) c.Assert(err, check.IsNil) - ca, err := authClient.GetCertAuthority(types.CertAuthID{ + ca, err := authClient.GetCertAuthority(context.Background(), types.CertAuthID{ Type: types.JWTSigner, DomainName: s.server.ClusterName(), }, true) @@ -2864,7 +2866,7 @@ func (s *TLSSuite) TestRegisterCAPin(c *check.C) { c.Assert(err, check.NotNil) // Add another cert to the CA (dupe the current one for simplicity) - hostCA, err := s.server.AuthServer.AuthServer.GetCertAuthority(types.CertAuthID{ + hostCA, err := s.server.AuthServer.AuthServer.GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.AuthServer.ClusterName, Type: types.HostCA, }, true) @@ -2938,7 +2940,7 @@ func (s *TLSSuite) TestRegisterCAPath(c *check.C) { c.Assert(err, check.IsNil) // Extract the root CA public key and write it out to the data dir. - hostCA, err := s.server.AuthServer.AuthServer.GetCertAuthority(types.CertAuthID{ + hostCA, err := s.server.AuthServer.AuthServer.GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.AuthServer.ClusterName, Type: types.HostCA, }, false) @@ -2947,7 +2949,7 @@ func (s *TLSSuite) TestRegisterCAPath(c *check.C) { c.Assert(certs, check.HasLen, 1) certPem := certs[0] caPath := filepath.Join(s.dataDir, defaults.CACertFile) - err = ioutil.WriteFile(caPath, certPem, teleport.FileMaskOwnerOnly) + err = os.WriteFile(caPath, certPem, teleport.FileMaskOwnerOnly) c.Assert(err, check.IsNil) // Attempt to register with valid CA path, should work. @@ -3060,7 +3062,7 @@ func (s *TLSSuite) TestEventsPermissions(c *check.C) { // start rotation gracePeriod := time.Hour - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseInit, @@ -3068,7 +3070,7 @@ func (s *TLSSuite) TestEventsPermissions(c *check.C) { }) c.Assert(err, check.IsNil) - ca, err := s.server.Auth().GetCertAuthority(types.CertAuthID{ + ca, err := s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, false) @@ -3197,7 +3199,7 @@ func (s *TLSSuite) TestEventsClusterConfig(c *check.C) { // start rotation gracePeriod := time.Hour - err = s.server.Auth().RotateCertAuthority(RotateRequest{ + err = s.server.Auth().RotateCertAuthority(ctx, RotateRequest{ Type: types.HostCA, GracePeriod: &gracePeriod, TargetPhase: types.RotationPhaseInit, @@ -3205,7 +3207,7 @@ func (s *TLSSuite) TestEventsClusterConfig(c *check.C) { }) c.Assert(err, check.IsNil) - ca, err := s.server.Auth().GetCertAuthority(types.CertAuthID{ + ca, err := s.server.Auth().GetCertAuthority(ctx, types.CertAuthID{ DomainName: s.server.ClusterName(), Type: types.HostCA, }, true) diff --git a/lib/auth/trustedcluster.go b/lib/auth/trustedcluster.go index ad67360a3b321..204b0474c21c7 100644 --- a/lib/auth/trustedcluster.go +++ b/lib/auth/trustedcluster.go @@ -98,7 +98,7 @@ func (a *Server) UpsertTrustedCluster(ctx context.Context, trustedCluster types. return nil, trace.Wrap(err) } - remoteCAs, err := a.establishTrust(trustedCluster) + remoteCAs, err := a.establishTrust(ctx, trustedCluster) if err != nil { return nil, trace.Wrap(err) } @@ -122,7 +122,7 @@ func (a *Server) UpsertTrustedCluster(ctx context.Context, trustedCluster types. return nil, trace.Wrap(err) } - remoteCAs, err := a.establishTrust(trustedCluster) + remoteCAs, err := a.establishTrust(ctx, trustedCluster) if err != nil { return nil, trace.Wrap(err) } @@ -231,7 +231,7 @@ func (a *Server) DeleteTrustedCluster(ctx context.Context, name string) error { return nil } -func (a *Server) establishTrust(trustedCluster types.TrustedCluster) ([]types.CertAuthority, error) { +func (a *Server) establishTrust(ctx context.Context, trustedCluster types.TrustedCluster) ([]types.CertAuthority, error) { var localCertAuthorities []types.CertAuthority domainName, err := a.GetDomainName() @@ -240,7 +240,7 @@ func (a *Server) establishTrust(trustedCluster types.TrustedCluster) ([]types.Ce } // get a list of certificate authorities for this auth server - allLocalCAs, err := a.GetCertAuthorities(types.HostCA, false) + allLocalCAs, err := a.GetCertAuthorities(ctx, types.HostCA, false) if err != nil { return nil, trace.Wrap(err) } @@ -472,15 +472,34 @@ func (a *Server) validateTrustedCluster(ctx context.Context, validateRequest *Va return nil, trace.Wrap(err) } - // add remote cluster resource to keep track of the remote cluster - var remoteClusterName string - for _, certAuthority := range validateRequest.CAs { - // don't add a ca with the same as as local cluster name - if certAuthority.GetName() == domainName { - return nil, trace.AccessDenied("remote certificate authority has same name as cluster certificate authority: %v", domainName) - } - remoteClusterName = certAuthority.GetName() + if len(validateRequest.CAs) != 1 { + return nil, trace.AccessDenied("expected exactly one certificate authority, received %v", len(validateRequest.CAs)) + } + remoteCA := validateRequest.CAs[0] + err = remoteCA.CheckAndSetDefaults() + if err != nil { + return nil, trace.Wrap(err) + } + + if remoteCA.GetType() != types.HostCA { + return nil, trace.AccessDenied("expected host certificate authority, received CA with type %q", remoteCA.GetType()) + } + + // a host CA shouldn't have a rolemap or roles in the first place + remoteCA.SetRoleMap(nil) + remoteCA.SetRoles(nil) + + remoteClusterName := remoteCA.GetName() + if remoteClusterName == domainName { + return nil, trace.AccessDenied("remote cluster has same name as this cluster: %v", domainName) } + _, err = a.GetTrustedCluster(ctx, remoteClusterName) + if err == nil { + return nil, trace.AccessDenied("remote cluster has same name as trusted cluster: %v", remoteClusterName) + } else if !trace.IsNotFound(err) { + return nil, trace.Wrap(err) + } + remoteCluster, err := types.NewRemoteCluster(remoteClusterName) if err != nil { return nil, trace.Wrap(err) @@ -498,12 +517,9 @@ func (a *Server) validateTrustedCluster(ctx context.Context, validateRequest *Va } } - // token has been validated, upsert the given certificate authority - for _, certAuthority := range validateRequest.CAs { - err = a.UpsertCertAuthority(certAuthority) - if err != nil { - return nil, trace.Wrap(err) - } + err = a.UpsertCertAuthority(remoteCA) + if err != nil { + return nil, trace.Wrap(err) } // export local cluster certificate authority and return it to the cluster @@ -512,6 +528,7 @@ func (a *Server) validateTrustedCluster(ctx context.Context, validateRequest *Va } for _, caType := range []types.CertAuthType{types.HostCA, types.UserCA} { certAuthority, err := a.GetCertAuthority( + ctx, types.CertAuthID{Type: caType, DomainName: domainName}, false) if err != nil { diff --git a/lib/auth/trustedcluster_test.go b/lib/auth/trustedcluster_test.go index 9091451faf4e0..5eba93dbaa59f 100644 --- a/lib/auth/trustedcluster_test.go +++ b/lib/auth/trustedcluster_test.go @@ -24,6 +24,7 @@ import ( authority "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/backend/memory" "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/services/suite" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" @@ -99,6 +100,131 @@ func TestRemoteClusterStatus(t *testing.T) { require.Empty(t, cmp.Diff(rc, gotRC)) } +func TestValidateTrustedCluster(t *testing.T) { + const localClusterName = "localcluster" + const validToken = "validtoken" + ctx := context.Background() + + testAuth, err := NewTestAuthServer(TestAuthServerConfig{ + ClusterName: localClusterName, + Dir: t.TempDir(), + }) + require.NoError(t, err) + a := testAuth.AuthServer + + tks, err := types.NewStaticTokens(types.StaticTokensSpecV2{ + StaticTokens: []types.ProvisionTokenV1{{ + Roles: []types.SystemRole{types.RoleTrustedCluster}, + Token: validToken, + }}, + }) + require.NoError(t, err) + a.SetStaticTokens(tks) + + _, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{ + Token: "invalidtoken", + CAs: []types.CertAuthority{}, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid cluster token") + + _, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{ + Token: validToken, + CAs: []types.CertAuthority{}, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "expected exactly one") + + _, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{ + Token: validToken, + CAs: []types.CertAuthority{ + suite.NewTestCA(types.HostCA, "rc1"), + suite.NewTestCA(types.HostCA, "rc2"), + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "expected exactly one") + + _, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{ + Token: validToken, + CAs: []types.CertAuthority{ + suite.NewTestCA(types.UserCA, "rc3"), + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "expected host certificate authority") + + _, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{ + Token: validToken, + CAs: []types.CertAuthority{ + suite.NewTestCA(types.HostCA, localClusterName), + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "same name as this cluster") + + trustedCluster, err := types.NewTrustedCluster("trustedcluster", + types.TrustedClusterSpecV2{Roles: []string{"nonempty"}}) + require.NoError(t, err) + // use the UpsertTrustedCluster in Presence as we just want the resource in + // the backend, we don't want to actually connect + _, err = a.Presence.UpsertTrustedCluster(ctx, trustedCluster) + require.NoError(t, err) + + _, err = a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{ + Token: validToken, + CAs: []types.CertAuthority{ + suite.NewTestCA(types.HostCA, trustedCluster.GetName()), + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "same name as trusted cluster") + + leafClusterCA := types.CertAuthority(suite.NewTestCA(types.HostCA, "leafcluster")) + resp, err := a.validateTrustedCluster(ctx, &ValidateTrustedClusterRequest{ + Token: validToken, + CAs: []types.CertAuthority{leafClusterCA}, + }) + require.NoError(t, err) + + require.Len(t, resp.CAs, 2) + require.ElementsMatch(t, + []types.CertAuthType{types.HostCA, types.UserCA}, + []types.CertAuthType{resp.CAs[0].GetType(), resp.CAs[1].GetType()}, + ) + + for _, returnedCA := range resp.CAs { + localCA, err := a.GetCertAuthority(ctx, types.CertAuthID{ + Type: returnedCA.GetType(), + DomainName: localClusterName, + }, false) + require.NoError(t, err) + require.True(t, services.CertAuthoritiesEquivalent(localCA, returnedCA)) + } + + rcs, err := a.GetRemoteClusters() + require.NoError(t, err) + require.Len(t, rcs, 1) + require.Equal(t, leafClusterCA.GetName(), rcs[0].GetName()) + + hostCAs, err := a.GetCertAuthorities(ctx, types.HostCA, false) + require.NoError(t, err) + require.Len(t, hostCAs, 2) + require.ElementsMatch(t, + []string{localClusterName, leafClusterCA.GetName()}, + []string{hostCAs[0].GetName(), hostCAs[1].GetName()}, + ) + require.Empty(t, hostCAs[0].GetRoles()) + require.Empty(t, hostCAs[0].GetRoleMap()) + require.Empty(t, hostCAs[1].GetRoles()) + require.Empty(t, hostCAs[1].GetRoleMap()) + + userCAs, err := a.GetCertAuthorities(ctx, types.UserCA, false) + require.NoError(t, err) + require.Len(t, userCAs, 1) + require.Equal(t, localClusterName, userCAs[0].GetName()) +} + func newTestAuthServer(ctx context.Context, t *testing.T, name ...string) *Server { bk, err := memory.New(memory.Config{}) require.NoError(t, err) diff --git a/lib/auth/usertoken.go b/lib/auth/usertoken.go index ce47a20f0a8e6..06c4ed86441a9 100644 --- a/lib/auth/usertoken.go +++ b/lib/auth/usertoken.go @@ -499,7 +499,7 @@ func (s *Server) CreatePrivilegeToken(ctx context.Context, req *proto.CreatePriv switch { case req.GetExistingMFAResponse() == nil: // Allows users with no devices to bypass second factor re-auth. - devices, err := s.Identity.GetMFADevices(ctx, username, false) + devices, err := s.Identity.GetMFADevices(ctx, username, false /* withSecrets */) switch { case err != nil: return nil, trace.Wrap(err) @@ -511,7 +511,8 @@ func (s *Server) CreatePrivilegeToken(ctx context.Context, req *proto.CreatePriv default: if err := s.WithUserLock(username, func() error { - _, err := s.validateMFAAuthResponse(ctx, username, req.GetExistingMFAResponse()) + _, _, err := s.validateMFAAuthResponse( + ctx, req.GetExistingMFAResponse(), username, false /* passwordless */) return err }); err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/webauthn/login.go b/lib/auth/webauthn/login.go index 609b6d07d11ff..cfe0a2d038d91 100644 --- a/lib/auth/webauthn/login.go +++ b/lib/auth/webauthn/login.go @@ -21,6 +21,7 @@ import ( "context" "encoding/base64" "encoding/json" + "sort" "time" "github.com/duo-labs/webauthn/protocol" @@ -81,6 +82,17 @@ func (f *loginFlow) begin(ctx context.Context, user string, passwordless bool) ( if err != nil { return nil, trace.Wrap(err) } + + // Sort non-resident keys first, which may cause clients to favor them for + // MFA in some scenarios (eg, tsh). + sort.Slice(devices, func(i, j int) bool { + dev1, dev2 := devices[i], devices[j] + web1, web2 := dev1.GetWebauthn(), dev2.GetWebauthn() + resident1 := web1 != nil && web1.ResidentKey + resident2 := web2 != nil && web2.ResidentKey + return !resident1 && resident2 + }) + u = newWebUser(user, webID, true /* credentialIDOnly */, devices) // Let's make sure we have at least one registered credential here, since we diff --git a/lib/auth/webauthn/register.go b/lib/auth/webauthn/register.go index 6ae37d8b2fb2c..13927f9f8baf8 100644 --- a/lib/auth/webauthn/register.go +++ b/lib/auth/webauthn/register.go @@ -137,6 +137,19 @@ func (f *RegistrationFlow) Begin(ctx context.Context, user string, passwordless } var exclusions []protocol.CredentialDescriptor for _, dev := range devices { + // Skip existing U2F devices, letting users "upgrade" their registration is + // good for us. + if dev.GetU2F() != nil { + continue + } + // Skip resident/non-resident keys depending on whether it's a passwordless + // registration. + // Letting users have both allows them to "swap" between key types in the + // same device. + if webDev := dev.GetWebauthn(); webDev != nil && webDev.ResidentKey != passwordless { + continue + } + cred, ok := deviceToCredential(dev, true /* idOnly */) if !ok { continue diff --git a/lib/auth/webauthn/register_test.go b/lib/auth/webauthn/register_test.go index ee5eeaa619f64..614428f05f98b 100644 --- a/lib/auth/webauthn/register_test.go +++ b/lib/auth/webauthn/register_test.go @@ -15,8 +15,10 @@ package webauthn_test import ( + "bytes" "context" "encoding/pem" + "sort" "testing" "github.com/duo-labs/webauthn/protocol" @@ -120,6 +122,88 @@ func TestRegistrationFlow_BeginFinish(t *testing.T) { } } +func TestRegistrationFlow_Begin_excludeList(t *testing.T) { + const user = "llama" + const rpID = "localhost" + + dev1ID := []byte{1, 1, 1} // U2F + web1ID := []byte{1, 1, 2} // WebAuthn / MFA + rk1ID := []byte{1, 1, 3} // WebAuthn / passwordless + dev1 := &types.MFADevice{ + Device: &types.MFADevice_U2F{ + U2F: &types.U2FDevice{ + KeyHandle: dev1ID, + }, + }, + } + web1 := &types.MFADevice{ + Device: &types.MFADevice_Webauthn{ + Webauthn: &types.WebauthnDevice{ + CredentialId: web1ID, + }, + }, + } + rk1 := &types.MFADevice{ + Device: &types.MFADevice_Webauthn{ + Webauthn: &types.WebauthnDevice{ + CredentialId: rk1ID, + ResidentKey: true, + }, + }, + } + identity := newFakeIdentity(user, dev1, web1, rk1) + + rf := wanlib.RegistrationFlow{ + Webauthn: &types.Webauthn{ + RPID: rpID, + }, + Identity: identity, + } + + ctx := context.Background() + tests := []struct { + name string + passwordless bool + wantExcludeList [][]byte + }{ + { + name: "MFA", + wantExcludeList: [][]byte{web1ID}, // U2F and resident excluded + }, + { + name: "passwordless", + passwordless: true, + wantExcludeList: [][]byte{rk1ID}, // U2F and MFA excluded + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cc, err := rf.Begin(ctx, user, test.passwordless) + require.NoError(t, err, "Begin") + + got := cc.Response.CredentialExcludeList + sort.Slice(got, func(i, j int) bool { + return bytes.Compare(got[i].CredentialID, got[j].CredentialID) == -1 + }) + + want := make([]protocol.CredentialDescriptor, len(test.wantExcludeList)) + for i, id := range test.wantExcludeList { + want[i] = protocol.CredentialDescriptor{ + Type: protocol.PublicKeyCredentialType, + CredentialID: id, + } + } + sort.Slice(want, func(i, j int) bool { + return bytes.Compare(want[i].CredentialID, want[j].CredentialID) == -1 + }) + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("Begin() mismatch (-want +got):\n%s", diff) + } + }) + } +} + func TestRegistrationFlow_Begin_webID(t *testing.T) { const rpID = "localhost" ctx := context.Background() diff --git a/lib/auth/webauthncli/api.go b/lib/auth/webauthncli/api.go new file mode 100644 index 0000000000000..caf00749ddb9b --- /dev/null +++ b/lib/auth/webauthncli/api.go @@ -0,0 +1,64 @@ +// Copyright 2022 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webauthncli + +import ( + "context" + + "github.com/gravitational/teleport/api/client/proto" + + wanlib "github.com/gravitational/teleport/lib/auth/webauthn" + log "github.com/sirupsen/logrus" +) + +// Login performs client-side, U2F-compatible, Webauthn login. +// This method blocks until either device authentication is successful or the +// context is cancelled. Calling Login without a deadline or cancel condition +// may cause it block forever. +// The informed user is used to disambiguate credentials in case of passwordless +// logins. +// It returns an MFAAuthenticateResponse and the credential user, if a resident +// credential is used. +// The caller is expected to prompt the user for action before calling this +// method. +func Login( + ctx context.Context, + origin string, user string, assertion *wanlib.CredentialAssertion, prompt LoginPrompt, +) (*proto.MFAAuthenticateResponse, string, error) { + if IsFIDO2Available() { + log.Debug("FIDO2: Using libfido2 for assertion") + return FIDO2Login(ctx, origin, user, assertion, prompt) + } + + resp, err := U2FLogin(ctx, origin, assertion) + return resp, "" /* credentialUser */, err +} + +// Register performs client-side, U2F-compatible, Webauthn registration. +// This method blocks until either device authentication is successful or the +// context is cancelled. Calling Register without a deadline or cancel condition +// may cause it block forever. +// The caller is expected to prompt the user for action before calling this +// method. +func Register( + ctx context.Context, + origin string, cc *wanlib.CredentialCreation, prompt RegisterPrompt) (*proto.MFARegisterResponse, error) { + if IsFIDO2Available() { + log.Debug("FIDO2: Using libfido2 for credential creation") + return FIDO2Register(ctx, origin, cc, prompt) + } + + return U2FRegister(ctx, origin, cc) +} diff --git a/lib/auth/webauthncli/export_fido2_test.go b/lib/auth/webauthncli/export_fido2_test.go new file mode 100644 index 0000000000000..36b84e5ff42a3 --- /dev/null +++ b/lib/auth/webauthncli/export_fido2_test.go @@ -0,0 +1,23 @@ +//go:build libfido2 +// +build libfido2 + +// Copyright 2022 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webauthncli + +var ( + FIDODeviceLocations = &fidoDeviceLocations + FIDONewDevice = &fidoNewDevice +) diff --git a/lib/auth/webauthncli/fido2.go b/lib/auth/webauthncli/fido2.go new file mode 100644 index 0000000000000..0d6f6c43a0625 --- /dev/null +++ b/lib/auth/webauthncli/fido2.go @@ -0,0 +1,776 @@ +//go:build libfido2 +// +build libfido2 + +// Copyright 2022 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webauthncli + +import ( + "context" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "sync" + "time" + + "github.com/duo-labs/webauthn/protocol" + "github.com/duo-labs/webauthn/protocol/webauthncose" + "github.com/fxamacker/cbor/v2" + "github.com/gravitational/teleport/api/client/proto" + "github.com/gravitational/trace" + "github.com/keys-pub/go-libfido2" + + wanpb "github.com/gravitational/teleport/api/types/webauthn" + wanlib "github.com/gravitational/teleport/lib/auth/webauthn" + log "github.com/sirupsen/logrus" +) + +// FIDODevice abstracts *libfido2.Device for testing. +type FIDODevice interface { + // Info mirrors libfido2.Device.Info. + Info() (*libfido2.DeviceInfo, error) + + // Cancel mirrors libfido2.Device.Cancel. + Cancel() error + + // Credentials mirrors libfido2.Device.Credentials. + Credentials(rpID string, pin string) ([]*libfido2.Credential, error) + + // MakeCredential mirrors libfido2.Device.MakeCredential. + MakeCredential( + clientDataHash []byte, + rp libfido2.RelyingParty, + user libfido2.User, + typ libfido2.CredentialType, + pin string, + opts *libfido2.MakeCredentialOpts) (*libfido2.Attestation, error) + + // Assertion mirrors libfido2.Device.Assertion. + Assertion( + rpID string, + clientDataHash []byte, + credentialIDs [][]byte, + pin string, + opts *libfido2.AssertionOpts) (*libfido2.Assertion, error) +} + +// fidoDeviceLocations and fidoNewDevice are used to allow testing. +var fidoDeviceLocations = libfido2.DeviceLocations +var fidoNewDevice = func(path string) (FIDODevice, error) { + return libfido2.NewDevice(path) +} + +// IsFIDO2Available returns true if libfido2 is available in the current build. +func IsFIDO2Available() bool { + return true +} + +// fido2Login implements FIDO2Login. +func fido2Login( + ctx context.Context, + origin, user string, assertion *wanlib.CredentialAssertion, prompt LoginPrompt, +) (*proto.MFAAuthenticateResponse, string, error) { + switch { + case origin == "": + return nil, "", trace.BadParameter("origin required") + case assertion == nil: + return nil, "", trace.BadParameter("assertion required") + case prompt == nil: + return nil, "", trace.BadParameter("prompt required") + case len(assertion.Response.Challenge) == 0: + return nil, "", trace.BadParameter("assertion challenge required") + case assertion.Response.RelyingPartyID == "": + return nil, "", trace.BadParameter("assertion relying party ID required") + } + + allowedCreds := assertion.Response.GetAllowedCredentialIDs() + uv := assertion.Response.UserVerification == protocol.VerificationRequired + passwordless := len(allowedCreds) == 0 && uv + + // Prepare challenge data for the device. + ccdJSON, err := json.Marshal(&CollectedClientData{ + Type: string(protocol.AssertCeremony), + Challenge: base64.RawURLEncoding.EncodeToString(assertion.Response.Challenge), + Origin: origin, + }) + if err != nil { + return nil, "", trace.Wrap(err) + } + ccdHash := sha256.Sum256(ccdJSON) + + rpID := assertion.Response.RelyingPartyID + var appID string + if val, ok := assertion.Response.Extensions[wanlib.AppIDExtension]; ok { + appID = fmt.Sprint(val) + } + + // mu guards the variables below it. + var mu sync.Mutex + var assertionResp *libfido2.Assertion + var credentialID []byte + var userID []byte + var username string + var usedAppID bool + + filter := func(dev FIDODevice, info *deviceInfo) (bool, error) { + switch { + case uv && !info.uvCapable(): + log.Debugf("FIDO2: Device %v: filtered due to lack of UV", info.path) + return false, nil + case passwordless && !info.rk: + log.Debugf("FIDO2: Device %v: filtered due to lack of RK", info.path) + return false, nil + case len(allowedCreds) == 0: // Nothing else to check + return true, nil + } + + // Does the device have a suitable credential? + const pin = "" // not required to filter + if _, err := dev.Assertion(rpID, ccdHash[:], allowedCreds, pin, &libfido2.AssertionOpts{ + UP: libfido2.False, + }); err == nil { + return true, nil + } + + // Try again with the App ID, if present. + if appID == "" { + return false, nil + } + if _, err := dev.Assertion(appID, ccdHash[:], allowedCreds, pin, &libfido2.AssertionOpts{ + UP: libfido2.False, + }); err != nil { + log.Debugf("FIDO2: Device %v: filtered due to lack of allowed credential", info.path) + return false, nil + } + return true, nil + } + + deviceCallback := func(dev FIDODevice, info *deviceInfo, pin string) error { + var actualRPID string + var cID []byte + var uID []byte + var uName string + if passwordless { + cred, err := getPasswordlessCredentials(dev, pin, rpID, user) + if err != nil { + return trace.Wrap(err) + } + actualRPID = rpID + cID = cred.ID + uID = cred.User.ID + uName = cred.User.Name + } else { + // TODO(codingllama): Ideally we'd rely on fido_assert_id_ptr/_len. + var err error + actualRPID, cID, err = getMFACredentials(dev, pin, rpID, appID, allowedCreds) + if err != nil { + return trace.Wrap(err) + } + } + + if passwordless { + // Ask for another touch before the assertion, we used the first touch + // in the Credentials() call. + if err := prompt.PromptAdditionalTouch(); err != nil { + return trace.Wrap(err) + } + } + + opts := &libfido2.AssertionOpts{ + UP: libfido2.True, + } + if uv { + opts.UV = libfido2.True + } + resp, err := dev.Assertion(actualRPID, ccdHash[:], [][]byte{cID}, pin, opts) + if err != nil { + return trace.Wrap(err) + } + + // Use the first successful assertion. + // In practice it is very unlikely we'd hit this twice. + mu.Lock() + if assertionResp == nil { + assertionResp = resp + credentialID = cID + userID = uID + username = uName + usedAppID = actualRPID != rpID + } + mu.Unlock() + return nil + } + + skipAdditionalPrompt := passwordless + if err := runOnFIDO2Devices(ctx, prompt, skipAdditionalPrompt, filter, deviceCallback); err != nil { + return nil, "", trace.Wrap(err) + } + + var rawAuthData []byte + if err := cbor.Unmarshal(assertionResp.AuthDataCBOR, &rawAuthData); err != nil { + return nil, "", trace.Wrap(err) + } + + return &proto.MFAAuthenticateResponse{ + Response: &proto.MFAAuthenticateResponse_Webauthn{ + Webauthn: &wanpb.CredentialAssertionResponse{ + Type: string(protocol.PublicKeyCredentialType), + RawId: credentialID, + Response: &wanpb.AuthenticatorAssertionResponse{ + ClientDataJson: ccdJSON, + AuthenticatorData: rawAuthData, + Signature: assertionResp.Sig, + UserHandle: userID, + }, + Extensions: &wanpb.AuthenticationExtensionsClientOutputs{ + AppId: usedAppID, + }, + }, + }, + }, username, nil +} + +func getPasswordlessCredentials(dev FIDODevice, pin, rpID, user string) (*libfido2.Credential, error) { + creds, err := dev.Credentials(rpID, pin) + if err != nil { + return nil, trace.Wrap(err) + } + + switch { + case len(creds) == 0: + return nil, libfido2.ErrNoCredentials + case len(creds) == 1 && user == "": // no need to disambiguate + cred := creds[0] + log.Debugf("FIDO2: Found resident credential for user %q", cred.User.Name) + return cred, nil + case len(creds) > 1 && user == "": // can't disambiguate + return nil, trace.BadParameter("too many credentials found, explicit user required") + } + + duplicateWarning := false + var res *libfido2.Credential + for _, cred := range creds { + if cred.User.Name == user { + // Print information about matched credential, useful for debugging. + // ykman prints user IDs in hex, hence the unusual encoding choice below. + cID := base64.RawURLEncoding.EncodeToString(cred.ID) + uID := cred.User.ID + log.Debugf("FIDO2: Found resident credential for user %v, credential ID (b64) = %v, user ID (hex) = %x", user, cID, uID) + if res == nil { + res = cred + continue // Don't break, we want to warn about duplicates. + } + if !duplicateWarning { + duplicateWarning = true + log.Warnf("Found multiple credentials for %q, using first match", user) + } + } + } + if res == nil { + return nil, trace.BadParameter("no credentials for user %q", user) + } + return res, nil +} + +func getMFACredentials(dev FIDODevice, pin, rpID, appID string, allowedCreds [][]byte) (string, []byte, error) { + // The actual hash is not necessary here. + const cdh = "00000000000000000000000000000000" + + opts := &libfido2.AssertionOpts{ + UP: libfido2.False, + } + actualRPID := rpID + var cID []byte + for _, cred := range allowedCreds { + _, err := dev.Assertion(rpID, []byte(cdh), [][]byte{cred}, pin, opts) + if err == nil { + cID = cred + break + } + + // Try again with the U2F appID, if present. + if appID != "" { + _, err = dev.Assertion(appID, []byte(cdh), [][]byte{cred}, pin, opts) + if err == nil { + actualRPID = appID + cID = cred + break + } + } + } + if len(cID) == 0 { + return "", nil, libfido2.ErrNoCredentials + } + + return actualRPID, cID, nil +} + +// fido2Register implements FIDO2Register. +func fido2Register( + ctx context.Context, + origin string, cc *wanlib.CredentialCreation, prompt RegisterPrompt, +) (*proto.MFARegisterResponse, error) { + switch { + case origin == "": + return nil, trace.BadParameter("origin required") + case cc == nil: + return nil, trace.BadParameter("credential creation required") + case prompt == nil: + return nil, trace.BadParameter("prompt required") + case len(cc.Response.Challenge) == 0: + return nil, trace.BadParameter("credential creation challenge required") + case cc.Response.RelyingParty.ID == "": + return nil, trace.BadParameter("credential creation relying party ID required") + } + + rrk := cc.Response.AuthenticatorSelection.RequireResidentKey != nil && *cc.Response.AuthenticatorSelection.RequireResidentKey + if rrk { + // Be more pedantic with resident keys, some of this info gets recorded with + // the credential. + switch { + case len(cc.Response.RelyingParty.Name) == 0: + return nil, trace.BadParameter("relying party name required for resident credential") + case len(cc.Response.User.Name) == 0: + return nil, trace.BadParameter("user name required for resident credential") + case len(cc.Response.User.DisplayName) == 0: + return nil, trace.BadParameter("user display name required for resident credential") + case len(cc.Response.User.ID) == 0: + return nil, trace.BadParameter("user ID required for resident credential") + } + } + + // Can we create ES256 keys? + // TODO(codingllama): Consider supporting other algorithms and respecting + // param order in the credential. + ok := false + for _, p := range cc.Response.Parameters { + if p.Type == protocol.PublicKeyCredentialType && p.Algorithm == webauthncose.AlgES256 { + ok = true + break + } + } + if !ok { + return nil, trace.BadParameter("ES256 not allowed by credential parameters") + } + + // Prepare challenge data for the device. + ccdJSON, err := json.Marshal(&CollectedClientData{ + Type: string(protocol.CreateCeremony), + Challenge: base64.RawURLEncoding.EncodeToString(cc.Response.Challenge), + Origin: origin, + }) + if err != nil { + return nil, trace.Wrap(err) + } + ccdHash := sha256.Sum256(ccdJSON) + + rp := libfido2.RelyingParty{ + ID: cc.Response.RelyingParty.ID, + Name: cc.Response.RelyingParty.Name, + } + user := libfido2.User{ + ID: cc.Response.User.ID, + Name: cc.Response.User.Name, + DisplayName: cc.Response.User.DisplayName, + Icon: cc.Response.User.Icon, + } + plat := cc.Response.AuthenticatorSelection.AuthenticatorAttachment == protocol.Platform + uv := cc.Response.AuthenticatorSelection.UserVerification == protocol.VerificationRequired + + excludeList := make([][]byte, len(cc.Response.CredentialExcludeList)) + for i := range cc.Response.CredentialExcludeList { + excludeList[i] = cc.Response.CredentialExcludeList[i].CredentialID + } + + // mu guards attestation from goroutines. + var mu sync.Mutex + var attestation *libfido2.Attestation + + filter := func(dev FIDODevice, info *deviceInfo) (bool, error) { + switch { + case (plat && !info.plat) || (rrk && !info.rk) || (uv && !info.uvCapable()): + log.Debugf("FIDO2: Device %v: filtered due to options", info.path) + return false, nil + case len(excludeList) == 0: + return true, nil + } + + // Does the device hold an excluded credential? + const pin = "" // not required to filter + switch _, err := dev.Assertion(rp.ID, ccdHash[:], excludeList, pin, &libfido2.AssertionOpts{ + UP: libfido2.False, + }); { + case errors.Is(err, libfido2.ErrNoCredentials): + return true, nil + case err == nil: + log.Debugf("FIDO2: Device %v: filtered due to presence of excluded credential", info.path) + return false, nil + default: // unexpected error + return false, trace.Wrap(err) + } + } + + deviceCallback := func(d FIDODevice, info *deviceInfo, pin string) error { + // TODO(codingllama): We may need to setup a PIN if rrk=true. + // Do that as a response to specific MakeCredential failures. + + opts := &libfido2.MakeCredentialOpts{} + if rrk { + opts.RK = libfido2.True + } + // Only set the "uv" bit if the authenticator supports built-in + // verification. PIN-enabled devices don't claim to support "uv", but they + // are capable of UV assertions. + // See + // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#getinfo-uv. + if uv && info.uv { + opts.UV = libfido2.True + } + + resp, err := d.MakeCredential(ccdHash[:], rp, user, libfido2.ES256, pin, opts) + if err != nil { + return trace.Wrap(err) + } + + // Use the first successful attestation. + // In practice it is very unlikely we'd hit this twice. + mu.Lock() + if attestation == nil { + attestation = resp + } + mu.Unlock() + return nil + } + + const skipAdditionalPrompt = false + if err := runOnFIDO2Devices(ctx, prompt, skipAdditionalPrompt, filter, deviceCallback); err != nil { + return nil, trace.Wrap(err) + } + + var rawAuthData []byte + if err := cbor.Unmarshal(attestation.AuthData, &rawAuthData); err != nil { + return nil, trace.Wrap(err) + } + + format, attStatement, err := makeAttStatement(attestation) + if err != nil { + return nil, trace.Wrap(err) + } + attObj := &protocol.AttestationObject{ + RawAuthData: rawAuthData, + Format: format, + AttStatement: attStatement, + } + attestationCBOR, err := cbor.Marshal(attObj) + if err != nil { + return nil, trace.Wrap(err) + } + + return &proto.MFARegisterResponse{ + Response: &proto.MFARegisterResponse_Webauthn{ + Webauthn: &wanpb.CredentialCreationResponse{ + Type: string(protocol.PublicKeyCredentialType), + RawId: attestation.CredentialID, + Response: &wanpb.AuthenticatorAttestationResponse{ + ClientDataJson: ccdJSON, + AttestationObject: attestationCBOR, + }, + }, + }, + }, nil +} + +func makeAttStatement(attestation *libfido2.Attestation) (string, map[string]interface{}, error) { + const fidoU2F = "fido-u2f" + const none = "none" + const packed = "packed" + + // See https://www.w3.org/TR/webauthn-2/#sctn-defined-attestation-formats. + // The formats handled below are what we expect from the keys libfido2 + // interacts with. + format := attestation.Format + switch format { + case fidoU2F, packed: // OK, continue below + case none: + return format, nil, nil + default: + log.Debugf(`FIDO2: Unsupported attestation format %q, using "none"`, format) + return none, nil, nil + } + + sig := attestation.Sig + if len(sig) == 0 { + return "", nil, trace.BadParameter("attestation %q without signature", format) + } + cert := attestation.Cert + if len(cert) == 0 { + return "", nil, trace.BadParameter("attestation %q without certificate", format) + } + + m := map[string]interface{}{ + "sig": sig, + "x5c": []interface{}{cert}, + } + if format == packed { + m["alg"] = int64(attestation.CredentialType) + } + + return format, m, nil +} + +type deviceWithInfo struct { + FIDODevice + info *deviceInfo +} + +type deviceFilterFunc func(dev FIDODevice, info *deviceInfo) (ok bool, err error) +type deviceCallbackFunc func(dev FIDODevice, info *deviceInfo, pin string) error + +// runPrompt defines the prompt operations necessary for runOnFIDO2Devices. +type runPrompt LoginPrompt + +func runOnFIDO2Devices( + ctx context.Context, + prompt runPrompt, skipAdditionalPrompt bool, + filter deviceFilterFunc, + deviceCallback deviceCallbackFunc) error { + devices, err := findSuitableDevicesOrTimeout(ctx, filter) + if err != nil { + return trace.Wrap(err) + } + + dev, requiresPIN, err := selectDevice(ctx, devices, deviceCallback) + switch { + case err != nil: + return trace.Wrap(err) + case !requiresPIN: + return nil + } + + // Selected device requires PIN, let's use the prompt and run the callback + // again. + pin, err := prompt.PromptPIN() + if err != nil { + return trace.Wrap(err) + } + + // Ask for an additional touch after PIN. + // Works for most flows (except passwordless). + if !skipAdditionalPrompt { + if err := prompt.PromptAdditionalTouch(); err != nil { + return trace.Wrap(err) + } + } + + return trace.Wrap(deviceCallback(dev, dev.info, pin)) +} + +func findSuitableDevicesOrTimeout(ctx context.Context, filter deviceFilterFunc) ([]deviceWithInfo, error) { + ticker := time.NewTicker(FIDO2PollInterval) + defer ticker.Stop() + + knownPaths := make(map[string]struct{}) + for { + devices, err := findSuitableDevices(filter, knownPaths) + if err == nil { + return devices, nil + } + log.WithError(err).Debug("FIDO2: Selecting devices") + + select { + case <-ctx.Done(): + return nil, trace.Wrap(ctx.Err()) + case <-ticker.C: + } + } +} + +func findSuitableDevices(filter deviceFilterFunc, knownPaths map[string]struct{}) ([]deviceWithInfo, error) { + locs, err := fidoDeviceLocations() + if err != nil { + return nil, trace.Wrap(err, "device locations") + } + + var devs []deviceWithInfo + for _, loc := range locs { + path := loc.Path + if _, ok := knownPaths[path]; ok { + continue + } + knownPaths[path] = struct{}{} + + dev, err := fidoNewDevice(path) + if err != nil { + return nil, trace.Wrap(err, "device %v: open", path) + } + + var info *libfido2.DeviceInfo + const infoAttempts = 3 + for i := 0; i < infoAttempts; i++ { + info, err = dev.Info() + switch { + case errors.Is(err, libfido2.ErrTX): + // Happens occasionally, give the device a short grace period and retry. + time.Sleep(1 * time.Millisecond) + continue + case err != nil: // unexpected error + return nil, trace.Wrap(err, "device %v: info", path) + } + break // err == nil + } + if info == nil { + return nil, trace.Wrap(libfido2.ErrTX, "device %v: max info attempts reached", path) + } + log.Debugf("FIDO2: Info for device %v: %#v", path, info) + + di := makeDevInfo(path, info) + switch ok, err := filter(dev, di); { + case err != nil: + return nil, trace.Wrap(err, "device %v: filter", path) + case !ok: + continue // Skip device. + } + devs = append(devs, deviceWithInfo{FIDODevice: dev, info: di}) + } + + if len(devs) == 0 { + return nil, errors.New("no suitable devices found") + } + + return devs, nil +} + +func selectDevice(ctx context.Context, devices []deviceWithInfo, deviceCallback deviceCallbackFunc) (deviceWithInfo, bool, error) { + // We don't know the PIN in the device selection step, so we are optimistic + // about the fact that there is no PIN. + const pin = "" + + callbackWrapper := func(dev FIDODevice, info *deviceInfo, pin string) (requiresPIN bool, err error) { + // Attempt to select a device by running "deviceCallback" on it. + // For most scenarios this works, saving a touch. + if err = deviceCallback(dev, info, pin); !errors.Is(err, libfido2.ErrPinRequired) { + return + } + + // ErrPinRequired means we can't use "deviceCallback" as the selection + // mechanism. Let's run a different operation to ask for a touch. + requiresPIN = true + + // TODO(codingllama): What we really want here is fido_dev_get_touch_begin. + // Another option is to put the authenticator into U2F mode. + const rpID = "7f364cc0-958c-4177-b3ea-b2d8d7f15d4a" // arbitrary, unlikely to collide with a real RP + const cdh = "00000000000000000000000000000000" // "random", size 32 + _, err = dev.Assertion(rpID, []byte(cdh), nil /* credentials */, pin, &libfido2.AssertionOpts{ + UP: libfido2.True, + }) + if errors.Is(err, libfido2.ErrNoCredentials) { + err = nil // OK, selected successfully + } + return + } + + // No need for goroutine shenanigans with a single device. + if len(devices) == 1 { + dev := devices[0] + requiresPIN, err := callbackWrapper(dev, dev.info, pin) + return dev, requiresPIN, trace.Wrap(err) + } + + type selectResp struct { + dev deviceWithInfo + requiresPIN bool + err error + } + + respC := make(chan selectResp) + numGoroutines := len(devices) + for _, dev := range devices { + dev := dev + go func() { + requiresPIN, err := callbackWrapper(dev, dev.info, pin) + respC <- selectResp{ + dev: dev, + requiresPIN: requiresPIN, + err: err, + } + }() + } + + // Stop on timeout or first interaction, whatever comes first wins and gets + // returned. + var resp selectResp + select { + case <-ctx.Done(): + resp.err = ctx.Err() + case resp = <-respC: + numGoroutines-- + } + + // Cancel ongoing operations and wait for goroutines to complete. + for _, dev := range devices { + if dev == resp.dev { + continue + } + if err := dev.Cancel(); err != nil { + log.WithError(err).Tracef("FIDO2: Device cancel") + } + } + for i := 0; i < numGoroutines; i++ { + cancelResp := <-respC + if err := cancelResp.err; err != nil && !errors.Is(err, libfido2.ErrKeepaliveCancel) { + log.WithError(err).Debugf("FIDO2: Device errored on cancel") + } + } + + return resp.dev, resp.requiresPIN, trace.Wrap(resp.err) +} + +// deviceInfo contains an aggregate of a device's informations and capabilities. +// Various fields match options under +// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetInfo. +type deviceInfo struct { + path string + plat bool + rk bool + clientPinCapable, clientPinSet bool + uv bool +} + +// uvCapable returns true for both "uv" and pin-configured devices. +func (di *deviceInfo) uvCapable() bool { + return di.uv || di.clientPinSet +} + +func makeDevInfo(path string, info *libfido2.DeviceInfo) *deviceInfo { + di := &deviceInfo{path: path} + for _, opt := range info.Options { + // See + // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetInfo. + switch opt.Name { + case "plat": + di.plat = opt.Value == libfido2.True + case "rk": + di.rk = opt.Value == libfido2.True + case "clientPin": + di.clientPinCapable = true + di.clientPinSet = opt.Value == libfido2.True + case "uv": + di.uv = opt.Value == libfido2.True + } + } + return di +} diff --git a/lib/auth/webauthncli/fido2_common.go b/lib/auth/webauthncli/fido2_common.go new file mode 100644 index 0000000000000..9eb307c2ad1f4 --- /dev/null +++ b/lib/auth/webauthncli/fido2_common.go @@ -0,0 +1,75 @@ +// Copyright 2022 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webauthncli + +import ( + "context" + "time" + + "github.com/gravitational/teleport/api/client/proto" + + wanlib "github.com/gravitational/teleport/lib/auth/webauthn" +) + +// FIDO2PollInterval is the poll interval used to check for new FIDO2 devices. +var FIDO2PollInterval = 200 * time.Millisecond + +// LoginPrompt is the user interface for FIDO2Login. +type LoginPrompt interface { + // PromptPIN prompts the user for their PIN. + PromptPIN() (string, error) + // PromptAdditionalTouch prompts the user for an additional security key + // touch. + // Additional touches may be required after PINs and during passwordless flows. + PromptAdditionalTouch() error +} + +// FIDO2Login implements Login for CTAP1 and CTAP2 devices. +// It must be called with a context with timeout, otherwise it can run +// indefinitely. +// The informed user is used to disambiguate credentials in case of passwordless +// logins. +// It returns an MFAAuthenticateResponse and the credential user, if a resident +// credential is used. +// Most callers should call Login directly, as it is correctly guarded by +// IsFIDO2Available. +func FIDO2Login( + ctx context.Context, + origin, user string, assertion *wanlib.CredentialAssertion, prompt LoginPrompt, +) (*proto.MFAAuthenticateResponse, string, error) { + return fido2Login(ctx, origin, user, assertion, prompt) +} + +// RegisterPrompt is the user interface for FIDO2Register. +type RegisterPrompt interface { + // PromptPIN prompts the user for their PIN. + PromptPIN() (string, error) + // PromptAdditionalTouch prompts the user for an additional security key + // touch. + // Additional touches may be required after PINs. + PromptAdditionalTouch() error +} + +// FIDO2Register implements Register for CTAP1 and CTAP2 devices. +// It must be called with a context with timeout, otherwise it can run +// indefinitely. +// Most callers should call Register directly, as it is correctly guarded by +// IsFIDO2Available. +func FIDO2Register( + ctx context.Context, + origin string, cc *wanlib.CredentialCreation, prompt RegisterPrompt, +) (*proto.MFARegisterResponse, error) { + return fido2Register(ctx, origin, cc, prompt) +} diff --git a/lib/auth/webauthncli/fido2_other.go b/lib/auth/webauthncli/fido2_other.go new file mode 100644 index 0000000000000..4bb43e4a6c2c6 --- /dev/null +++ b/lib/auth/webauthncli/fido2_other.go @@ -0,0 +1,48 @@ +//go:build !libfido2 +// +build !libfido2 + +// Copyright 2022 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webauthncli + +import ( + "context" + "errors" + + "github.com/gravitational/teleport/api/client/proto" + + wanlib "github.com/gravitational/teleport/lib/auth/webauthn" +) + +var errFIDO2Unavailable = errors.New("FIDO2 unavailable in current build") + +// IsFIDO2Available returns true if libfido2 is available in the current build. +func IsFIDO2Available() bool { + return false +} + +func fido2Login( + ctx context.Context, + origin, user string, assertion *wanlib.CredentialAssertion, prompt LoginPrompt, +) (*proto.MFAAuthenticateResponse, string, error) { + return nil, "", errFIDO2Unavailable +} + +func fido2Register( + ctx context.Context, + origin string, cc *wanlib.CredentialCreation, prompt RegisterPrompt, +) (*proto.MFARegisterResponse, error) { + return nil, errFIDO2Unavailable +} diff --git a/lib/auth/webauthncli/fido2_test.go b/lib/auth/webauthncli/fido2_test.go new file mode 100644 index 0000000000000..c3c083f6f5630 --- /dev/null +++ b/lib/auth/webauthncli/fido2_test.go @@ -0,0 +1,1438 @@ +//go:build libfido2 +// +build libfido2 + +// Copyright 2022 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webauthncli_test + +import ( + "bytes" + "context" + "crypto/rand" + "errors" + "fmt" + "sync" + "testing" + "time" + + "github.com/duo-labs/webauthn/protocol" + "github.com/duo-labs/webauthn/protocol/webauthncose" + "github.com/fxamacker/cbor/v2" + "github.com/google/go-cmp/cmp" + "github.com/gravitational/teleport/lib/auth/mocku2f" + "github.com/keys-pub/go-libfido2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + wanpb "github.com/gravitational/teleport/api/types/webauthn" + wanlib "github.com/gravitational/teleport/lib/auth/webauthn" + wancli "github.com/gravitational/teleport/lib/auth/webauthncli" +) + +var makeCredentialAuthDataRaw, makeCredentialAuthDataCBOR, makeCredentialSig []byte +var assertionAuthDataRaw, assertionAuthDataCBOR, assertionSig []byte + +func init() { + // Initialize arrays with random data, but use realistic sizes. + // YMMV. + makeCredentialAuthDataRaw = make([]byte, 37) + makeCredentialSig = make([]byte, 70) + assertionAuthDataRaw = make([]byte, 37) + assertionSig = make([]byte, 70) + for _, b := range [][]byte{ + makeCredentialAuthDataRaw, + makeCredentialSig, + assertionAuthDataRaw, + assertionSig, + } { + if _, err := rand.Read(b); err != nil { + panic(err) + } + } + + // Returned authData is CBOR-encoded, so let's do that. + pairs := []*[]byte{ + &makeCredentialAuthDataRaw, &makeCredentialAuthDataCBOR, + &assertionAuthDataRaw, &assertionAuthDataCBOR, + } + for i := 0; i < len(pairs); i += 2 { + dataRaw := pairs[i] + dataCBOR := pairs[i+1] + + res, err := cbor.Marshal(*dataRaw) + if err != nil { + panic(err) + } + *dataCBOR = res + } +} + +type noopPrompt struct{} + +func (p noopPrompt) PromptPIN() (string, error) { + return "", nil +} + +func (p noopPrompt) PromptAdditionalTouch() error { + return nil +} + +func TestFIDO2Login(t *testing.T) { + resetFIDO2AfterTests(t) + wancli.FIDO2PollInterval = 1 * time.Millisecond // run fast on tests + + const rpID = "example.com" + const appID = "https://example.com" + const origin = "https://example.com" + + authOpts := []libfido2.Option{ + {Name: "rk", Value: "true"}, + {Name: "up", Value: "true"}, + {Name: "plat", Value: "false"}, + {Name: "clientPin", Value: "false"}, // supported but unset + } + pinOpts := []libfido2.Option{ + {Name: "rk", Value: "true"}, + {Name: "up", Value: "true"}, + {Name: "plat", Value: "false"}, + {Name: "clientPin", Value: "true"}, // supported and configured + } + bioOpts := []libfido2.Option{ + {Name: "rk", Value: "true"}, + {Name: "up", Value: "true"}, + {Name: "uv", Value: "true"}, + {Name: "plat", Value: "false"}, + {Name: "alwaysUv", Value: "true"}, + {Name: "bioEnroll", Value: "true"}, // supported and configured + {Name: "clientPin", Value: "true"}, // supported and configured + } + + // User IDs and names for resident credentials / passwordless. + const llamaName = "llama" + const alpacaName = "alpaca" + var llamaID = make([]byte, 16) + var alpacaID = make([]byte, 16) + for _, b := range [][]byte{llamaID, alpacaID} { + _, err := rand.Read(b) + require.NoError(t, err, "Read failed") + } + + // auth1 is a FIDO2 authenticator without a PIN configured. + auth1 := mustNewFIDO2Device("/path1", "" /* pin */, &libfido2.DeviceInfo{ + Options: authOpts, + }) + // pin1 is a FIDO2 authenticator with a PIN. + pin1 := mustNewFIDO2Device("/pin1", "supersecretpinllama", &libfido2.DeviceInfo{ + Options: pinOpts, + }) + // pin2 is a FIDO2 authenticator with a PIN. + pin2 := mustNewFIDO2Device("/pin2", "supersecretpin2", &libfido2.DeviceInfo{ + Options: pinOpts, + }) + // pin3 is a FIDO2 authenticator with a PIN and resident credentials. + pin3 := mustNewFIDO2Device("/pin3", "supersecretpin3", &libfido2.DeviceInfo{ + Options: pinOpts, + }, &libfido2.Credential{ + User: libfido2.User{ + ID: alpacaID, + Name: alpacaName, + }, + }) + // bio1 is a biometric authenticator. + bio1 := mustNewFIDO2Device("/bio1", "supersecretBIOpin", &libfido2.DeviceInfo{ + Options: bioOpts, + }) + // bio2 is a biometric authenticator with configured resident credentials. + bio2 := mustNewFIDO2Device("/bio2", "supersecretBIO2pin", &libfido2.DeviceInfo{ + Options: bioOpts, + }, &libfido2.Credential{ + User: libfido2.User{ + ID: llamaID, + Name: llamaName, + }, + }, &libfido2.Credential{ + User: libfido2.User{ + ID: alpacaID, + Name: alpacaName, + }, + }) + // legacy1 is an authenticator registered using the U2F App ID. + legacy1 := mustNewFIDO2Device("/legacy1", "" /* pin */, &libfido2.DeviceInfo{Options: authOpts}) + legacy1.wantRPID = appID + + challenge, err := protocol.CreateChallenge() + require.NoError(t, err, "CreateChallenge failed") + + baseAssertion := &wanlib.CredentialAssertion{ + Response: protocol.PublicKeyCredentialRequestOptions{ + Challenge: challenge, + RelyingPartyID: rpID, + AllowedCredentials: []protocol.CredentialDescriptor{}, + UserVerification: protocol.VerificationDiscouraged, + Extensions: map[string]interface{}{}, + }, + } + + tests := []struct { + name string + timeout time.Duration + fido2 *fakeFIDO2 + setUP func() + user string + createAssertion func() *wanlib.CredentialAssertion + prompt wancli.LoginPrompt + // assertResponse and wantErr are mutually exclusive. + assertResponse func(t *testing.T, resp *wanpb.CredentialAssertionResponse) + wantErr string + }{ + { + name: "single device", + fido2: newFakeFIDO2(auth1), + setUP: func() { + go func() { + // Simulate delayed user press. + time.Sleep(100 * time.Millisecond) + auth1.setUP() + }() + }, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = []protocol.CredentialDescriptor{ + {CredentialID: auth1.credentialID()}, + } + return &cp + }, + assertResponse: func(t *testing.T, resp *wanpb.CredentialAssertionResponse) { + assert.Equal(t, auth1.credentialID(), resp.RawId, "RawId mismatch") + }, + }, + { + name: "pin protected device", + fido2: newFakeFIDO2(pin1), + setUP: pin1.setUP, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = []protocol.CredentialDescriptor{ + {CredentialID: pin1.credentialID()}, + } + return &cp + }, + }, + { + name: "biometric device", + fido2: newFakeFIDO2(bio1), + setUP: bio1.setUP, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = []protocol.CredentialDescriptor{ + {CredentialID: bio1.credentialID()}, + } + return &cp + }, + }, + { + name: "legacy device (AppID)", + fido2: newFakeFIDO2(legacy1), + setUP: legacy1.setUP, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = []protocol.CredentialDescriptor{ + {CredentialID: legacy1.credentialID()}, + } + cp.Response.Extensions = protocol.AuthenticationExtensions{ + wanlib.AppIDExtension: appID, + } + return &cp + }, + assertResponse: func(t *testing.T, resp *wanpb.CredentialAssertionResponse) { + assert.True(t, resp.Extensions.AppId, "AppID mismatch") + }, + }, + { + name: "multiple valid devices", + fido2: newFakeFIDO2( + auth1, + pin1, + bio1, + legacy1, + ), + setUP: bio1.setUP, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = []protocol.CredentialDescriptor{ + {CredentialID: auth1.credentialID()}, + {CredentialID: pin1.credentialID()}, + {CredentialID: bio1.credentialID()}, + {CredentialID: legacy1.credentialID()}, + } + cp.Response.Extensions = protocol.AuthenticationExtensions{ + wanlib.AppIDExtension: appID, + } + return &cp + }, + assertResponse: func(t *testing.T, resp *wanpb.CredentialAssertionResponse) { + assert.Equal(t, bio1.credentialID(), resp.RawId, "RawId mismatch (want bio1)") + }, + }, + { + name: "multiple devices filtered", + fido2: newFakeFIDO2( + auth1, // allowed + pin1, // not allowed + bio1, + legacy1, // doesn't match RPID or AppID + ), + setUP: auth1.setUP, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = []protocol.CredentialDescriptor{ + {CredentialID: auth1.credentialID()}, + {CredentialID: bio1.credentialID()}, + {CredentialID: legacy1.credentialID()}, + } + cp.Response.Extensions = protocol.AuthenticationExtensions{ + wanlib.AppIDExtension: "https://badexample.com", + } + return &cp + }, + assertResponse: func(t *testing.T, resp *wanpb.CredentialAssertionResponse) { + assert.Equal(t, auth1.credentialID(), resp.RawId, "RawId mismatch (want auth1)") + }, + }, + { + name: "multiple pin devices", + fido2: newFakeFIDO2( + auth1, + pin1, pin2, + bio1, + ), + setUP: pin2.setUP, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = []protocol.CredentialDescriptor{ + {CredentialID: auth1.credentialID()}, + {CredentialID: pin1.credentialID()}, + {CredentialID: pin2.credentialID()}, + {CredentialID: bio1.credentialID()}, + } + return &cp + }, + assertResponse: func(t *testing.T, resp *wanpb.CredentialAssertionResponse) { + assert.Equal(t, pin2.credentialID(), resp.RawId, "RawId mismatch (want pin2)") + }, + }, + { + name: "NOK no devices plugged times out", + timeout: 10 * time.Millisecond, + fido2: newFakeFIDO2(), + setUP: func() {}, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = []protocol.CredentialDescriptor{ + {CredentialID: auth1.credentialID()}, + } + return &cp + }, + wantErr: context.DeadlineExceeded.Error(), + }, + { + name: "NOK no devices touched times out", + timeout: 10 * time.Millisecond, + fido2: newFakeFIDO2(auth1, pin1, bio1, legacy1), + setUP: func() {}, // no interaction + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = []protocol.CredentialDescriptor{ + {CredentialID: auth1.credentialID()}, + {CredentialID: pin1.credentialID()}, + {CredentialID: bio1.credentialID()}, + } + return &cp + }, + wantErr: context.DeadlineExceeded.Error(), + }, + { + name: "passwordless pin", + fido2: newFakeFIDO2(pin3), + setUP: pin3.setUP, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = nil + cp.Response.UserVerification = protocol.VerificationRequired + return &cp + }, + prompt: pin3, + assertResponse: func(t *testing.T, resp *wanpb.CredentialAssertionResponse) { + assert.Equal(t, pin3.credentials[0].ID, resp.RawId, "RawId mismatch (want %q resident credential)", alpacaName) + assert.Equal(t, alpacaID, resp.Response.UserHandle, "UserHandle mismatch (want %q)", alpacaName) + }, + }, + { + name: "passwordless biometric (llama)", + fido2: newFakeFIDO2(bio2), + setUP: bio2.setUP, + user: llamaName, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = nil + cp.Response.UserVerification = protocol.VerificationRequired + return &cp + }, + prompt: bio2, + assertResponse: func(t *testing.T, resp *wanpb.CredentialAssertionResponse) { + assert.Equal(t, bio2.credentials[0].ID, resp.RawId, "RawId mismatch (want %q resident credential)", llamaName) + assert.Equal(t, llamaID, resp.Response.UserHandle, "UserHandle mismatch (want %q)", llamaName) + }, + }, + { + name: "passwordless biometric (alpaca)", + fido2: newFakeFIDO2(bio2), + setUP: bio2.setUP, + user: alpacaName, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = nil + cp.Response.UserVerification = protocol.VerificationRequired + return &cp + }, + prompt: bio2, + assertResponse: func(t *testing.T, resp *wanpb.CredentialAssertionResponse) { + assert.Equal(t, bio2.credentials[1].ID, resp.RawId, "RawId mismatch (want %q resident credential)", alpacaName) + assert.Equal(t, alpacaID, resp.Response.UserHandle, "UserHandle mismatch (want %q)", alpacaName) + }, + }, + { + name: "NOK passwordless no credentials", + fido2: newFakeFIDO2(bio1), + setUP: bio1.setUP, + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = nil + cp.Response.UserVerification = protocol.VerificationRequired + return &cp + }, + prompt: bio1, + wantErr: libfido2.ErrNoCredentials.Error(), + }, + { + name: "NOK passwordless ambiguous user", + fido2: newFakeFIDO2(bio2), + setUP: bio2.setUP, + user: "", // >1 resident credential, can't pick unambiguous username. + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = nil + cp.Response.UserVerification = protocol.VerificationRequired + return &cp + }, + prompt: bio2, + wantErr: "explicit user required", + }, + { + name: "NOK passwordless unknown user", + fido2: newFakeFIDO2(bio2), + setUP: bio2.setUP, + user: "camel", // unknown + createAssertion: func() *wanlib.CredentialAssertion { + cp := *baseAssertion + cp.Response.AllowedCredentials = nil + cp.Response.UserVerification = protocol.VerificationRequired + return &cp + }, + prompt: bio2, + wantErr: "no credentials for user", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.fido2.setCallbacks() + test.setUP() + + timeout := test.timeout + if timeout == 0 { + timeout = 1 * time.Second + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + prompt := test.prompt + if prompt == nil { + prompt = noopPrompt{} + } + + mfaResp, _, err := wancli.FIDO2Login(ctx, origin, test.user, test.createAssertion(), prompt) + switch { + case test.wantErr != "" && err == nil: + t.Fatalf("FIDO2Login returned err = nil, wantErr %q", test.wantErr) + case test.wantErr != "": + require.Contains(t, err.Error(), test.wantErr, "FIDO2Login returned err = %q, wantErr %q", err, test.wantErr) + return + default: + require.NoError(t, err, "FIDO2Login failed") + require.NotNil(t, mfaResp, "mfaResp nil") + } + + // Do a few baseline checks, tests can assert further. + got := mfaResp.GetWebauthn() + require.NotNil(t, got, "assertion response nil") + require.NotNil(t, got.Response, "authenticator response nil") + assert.NotNil(t, got.Response.ClientDataJson, "ClientDataJSON nil") + want := &wanpb.CredentialAssertionResponse{ + Type: string(protocol.PublicKeyCredentialType), + RawId: got.RawId, + Response: &wanpb.AuthenticatorAssertionResponse{ + ClientDataJson: got.Response.ClientDataJson, + AuthenticatorData: assertionAuthDataRaw, + Signature: assertionSig, + UserHandle: got.Response.UserHandle, + }, + Extensions: got.Extensions, + } + if diff := cmp.Diff(want, got); diff != "" { + t.Fatalf("FIDO2Login()/CredentialAssertionResponse mismatch (-want +got):\n%v", diff) + } + + if test.assertResponse != nil { + test.assertResponse(t, got) + } + }) + } +} + +func TestFIDO2Login_errors(t *testing.T) { + resetFIDO2AfterTests(t) + + // Make sure we won't call the real libfido2. + f2 := newFakeFIDO2() + f2.setCallbacks() + + const origin = "https://example.com" + const user = "" + okAssertion := &wanlib.CredentialAssertion{ + Response: protocol.PublicKeyCredentialRequestOptions{ + Challenge: make([]byte, 32), + RelyingPartyID: "example.com", + AllowedCredentials: []protocol.CredentialDescriptor{ + {Type: protocol.PublicKeyCredentialType, CredentialID: []byte{1, 2, 3, 4, 5}}, + }, + }, + } + var prompt noopPrompt + + nilChallengeAssertion := *okAssertion + nilChallengeAssertion.Response.Challenge = nil + + emptyRPIDAssertion := *okAssertion + emptyRPIDAssertion.Response.RelyingPartyID = "" + + tests := []struct { + name string + origin string + assertion *wanlib.CredentialAssertion + prompt wancli.LoginPrompt + wantErr string + }{ + { + name: "ok - timeout", // check that good params are good + origin: origin, + assertion: okAssertion, + prompt: prompt, + wantErr: context.DeadlineExceeded.Error(), + }, + { + name: "nil origin", + assertion: okAssertion, + prompt: prompt, + wantErr: "origin", + }, + { + name: "nil assertion", + origin: origin, + prompt: prompt, + wantErr: "assertion required", + }, + { + name: "assertion without challenge", + origin: origin, + assertion: &nilChallengeAssertion, + prompt: prompt, + wantErr: "challenge", + }, + { + name: "assertion without RPID", + origin: origin, + assertion: &emptyRPIDAssertion, + prompt: prompt, + wantErr: "relying party ID", + }, + { + name: "nil prompt", + origin: origin, + assertion: okAssertion, + wantErr: "prompt", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() + + _, _, err := wancli.FIDO2Login(ctx, test.origin, user, test.assertion, test.prompt) + require.Error(t, err, "FIDO2Login returned err = nil, want %q", test.wantErr) + assert.Contains(t, err.Error(), test.wantErr, "FIDO2Login returned err = %q, want %q", err, test.wantErr) + }) + } +} + +func TestFIDO2Register(t *testing.T) { + resetFIDO2AfterTests(t) + + const rpID = "example.com" + const origin = "https://example.com" + + authOpts := []libfido2.Option{ + {Name: "rk", Value: "true"}, + {Name: "up", Value: "true"}, + {Name: "plat", Value: "false"}, + {Name: "clientPin", Value: "false"}, // supported but unset + } + pinOpts := []libfido2.Option{ + {Name: "rk", Value: "true"}, + {Name: "up", Value: "true"}, + {Name: "plat", Value: "false"}, + {Name: "clientPin", Value: "true"}, // supported and configured + } + + // auth1 is a FIDO2 authenticator without a PIN configured. + auth1 := mustNewFIDO2Device("/path1", "" /* pin */, &libfido2.DeviceInfo{ + Options: authOpts, + }) + // pin1 is a FIDO2 authenticator with a PIN. + pin1 := mustNewFIDO2Device("/pin1", "supersecretpinllama", &libfido2.DeviceInfo{ + Options: pinOpts, + }) + // pin2 is a FIDO2 authenticator with a PIN. + pin2 := mustNewFIDO2Device("/pin2", "supersecretpin2", &libfido2.DeviceInfo{ + Options: pinOpts, + }) + // bio1 is a biometric authenticator. + bio1 := mustNewFIDO2Device("/bio1", "supersecretBIOpin", &libfido2.DeviceInfo{ + Options: []libfido2.Option{ + {Name: "rk", Value: "true"}, + {Name: "up", Value: "true"}, + {Name: "uv", Value: "true"}, + {Name: "plat", Value: "false"}, + {Name: "alwaysUv", Value: "true"}, + {Name: "bioEnroll", Value: "true"}, // supported and configured + {Name: "clientPin", Value: "true"}, // supported and configured + }, + }) + // u2f1 is an authenticator that uses fido-u2f attestation. + u2f1 := mustNewFIDO2Device("/u2f1", "" /* pin */, &libfido2.DeviceInfo{Options: authOpts}) + u2f1.format = "fido-u2f" + // none1 is an authenticator that returns no attestation data. + none1 := mustNewFIDO2Device("/none1", "" /* pin */, &libfido2.DeviceInfo{Options: authOpts}) + none1.format = "none" + + challenge, err := protocol.CreateChallenge() + require.NoError(t, err, "CreateChallenge failed") + + baseCC := &wanlib.CredentialCreation{ + Response: protocol.PublicKeyCredentialCreationOptions{ + Challenge: challenge, + RelyingParty: protocol.RelyingPartyEntity{ + ID: rpID, + }, + Parameters: []protocol.CredentialParameter{ + {Type: protocol.PublicKeyCredentialType, Algorithm: webauthncose.AlgES256}, + }, + AuthenticatorSelection: protocol.AuthenticatorSelection{ + UserVerification: protocol.VerificationDiscouraged, + }, + Attestation: protocol.PreferDirectAttestation, + }, + } + pwdlessCC := *baseCC + pwdlessCC.Response.RelyingParty.Name = "Teleport" + pwdlessCC.Response.User = protocol.UserEntity{ + CredentialEntity: protocol.CredentialEntity{ + Name: "llama", + }, + DisplayName: "Llama", + ID: []byte{1, 2, 3, 4, 5}, // arbitrary + } + pwdlessRRK := true + pwdlessCC.Response.AuthenticatorSelection.RequireResidentKey = &pwdlessRRK + pwdlessCC.Response.AuthenticatorSelection.UserVerification = protocol.VerificationRequired + + tests := []struct { + name string + timeout time.Duration + fido2 *fakeFIDO2 + setUP func() + createCredential func() *wanlib.CredentialCreation + prompt wancli.RegisterPrompt + wantErr error + assertResponse func(t *testing.T, ccr *wanpb.CredentialCreationResponse, attObj *protocol.AttestationObject) + }{ + { + name: "single device, packed attestation", + fido2: newFakeFIDO2(auth1), + setUP: auth1.setUP, + createCredential: func() *wanlib.CredentialCreation { + cp := *baseCC + return &cp + }, + assertResponse: func(t *testing.T, ccr *wanpb.CredentialCreationResponse, attObj *protocol.AttestationObject) { + assert.Equal(t, auth1.credentialID(), ccr.RawId, "RawId mismatch") + + // Assert attestation algorithm and signature. + require.Equal(t, "packed", attObj.Format, "attestation format mismatch") + assert.Equal(t, int64(webauthncose.AlgES256), attObj.AttStatement["alg"], "attestation alg mismatch") + assert.Equal(t, makeCredentialSig, attObj.AttStatement["sig"], "attestation sig mismatch") + + // Assert attestation certificate. + x5cInterface := attObj.AttStatement["x5c"] + x5c, ok := x5cInterface.([]interface{}) + require.True(t, ok, "attestation x5c type mismatch (got %T)", x5cInterface) + assert.Len(t, x5c, 1, "attestation x5c length mismatch") + assert.Equal(t, auth1.cert(), x5c[0], "attestation cert mismatch") + }, + }, + { + name: "fido-u2f attestation", + fido2: newFakeFIDO2(u2f1), + setUP: u2f1.setUP, + createCredential: func() *wanlib.CredentialCreation { + cp := *baseCC + return &cp + }, + assertResponse: func(t *testing.T, ccr *wanpb.CredentialCreationResponse, attObj *protocol.AttestationObject) { + // Assert attestation signature. + require.Equal(t, "fido-u2f", attObj.Format, "attestation format mismatch") + assert.Equal(t, makeCredentialSig, attObj.AttStatement["sig"], "attestation sig mismatch") + + // Assert attestation certificate. + x5cInterface := attObj.AttStatement["x5c"] + x5c, ok := x5cInterface.([]interface{}) + require.True(t, ok, "attestation x5c type mismatch (got %T)", x5cInterface) + assert.Len(t, x5c, 1, "attestation x5c length mismatch") + assert.Equal(t, u2f1.cert(), x5c[0], "attestation cert mismatch") + }, + }, + { + name: "none attestation", + fido2: newFakeFIDO2(none1), + setUP: none1.setUP, + createCredential: func() *wanlib.CredentialCreation { + cp := *baseCC + return &cp + }, + assertResponse: func(t *testing.T, ccr *wanpb.CredentialCreationResponse, attObj *protocol.AttestationObject) { + assert.Equal(t, "none", attObj.Format, "attestation format mismatch") + }, + }, + { + name: "pin device", + fido2: newFakeFIDO2(pin1), + setUP: pin1.setUP, + createCredential: func() *wanlib.CredentialCreation { + cp := *baseCC + return &cp + }, + prompt: pin1, + }, + { + name: "multiple valid devices", + fido2: newFakeFIDO2(auth1, pin1, pin2, bio1), + setUP: bio1.setUP, + createCredential: func() *wanlib.CredentialCreation { + cp := *baseCC + return &cp + }, + assertResponse: func(t *testing.T, ccr *wanpb.CredentialCreationResponse, attObj *protocol.AttestationObject) { + assert.Equal(t, bio1.credentialID(), ccr.RawId, "RawId mismatch (want bio1)") + }, + }, + { + name: "multiple devices, uses pin", + fido2: newFakeFIDO2(auth1, pin1, pin2, bio1), + setUP: pin2.setUP, + createCredential: func() *wanlib.CredentialCreation { + cp := *baseCC + return &cp + }, + prompt: pin2, + assertResponse: func(t *testing.T, ccr *wanpb.CredentialCreationResponse, attObj *protocol.AttestationObject) { + assert.Equal(t, pin2.credentialID(), ccr.RawId, "RawId mismatch (want pin2)") + }, + }, + { + name: "excluded devices, single valid", + fido2: newFakeFIDO2(auth1, bio1), + setUP: bio1.setUP, + createCredential: func() *wanlib.CredentialCreation { + cp := *baseCC + cp.Response.CredentialExcludeList = []protocol.CredentialDescriptor{ + { + Type: protocol.PublicKeyCredentialType, + CredentialID: auth1.credentialID(), + }, + } + return &cp + }, + assertResponse: func(t *testing.T, ccr *wanpb.CredentialCreationResponse, attObj *protocol.AttestationObject) { + assert.Equal(t, bio1.credentialID(), ccr.RawId, "RawId mismatch (want bio1)") + }, + }, + { + name: "excluded devices, multiple valid", + fido2: newFakeFIDO2(auth1, pin1, pin2, bio1), + setUP: bio1.setUP, + createCredential: func() *wanlib.CredentialCreation { + cp := *baseCC + cp.Response.CredentialExcludeList = []protocol.CredentialDescriptor{ + { + Type: protocol.PublicKeyCredentialType, + CredentialID: pin1.credentialID(), + }, + { + Type: protocol.PublicKeyCredentialType, + CredentialID: pin2.credentialID(), + }, + } + return &cp + }, + assertResponse: func(t *testing.T, ccr *wanpb.CredentialCreationResponse, attObj *protocol.AttestationObject) { + assert.Equal(t, bio1.credentialID(), ccr.RawId, "RawId mismatch (want bio1)") + }, + }, + { + name: "NOK timeout without devices", + timeout: 10 * time.Millisecond, + fido2: newFakeFIDO2(), + setUP: func() {}, + createCredential: func() *wanlib.CredentialCreation { + cp := *baseCC + return &cp + }, + wantErr: context.DeadlineExceeded, + }, + { + name: "passwordless pin device", + fido2: newFakeFIDO2(pin2), + setUP: pin2.setUP, + createCredential: func() *wanlib.CredentialCreation { + cp := pwdlessCC + return &cp + }, + prompt: pin2, + assertResponse: func(t *testing.T, ccr *wanpb.CredentialCreationResponse, attObj *protocol.AttestationObject) { + require.NotEmpty(t, pin2.credentials, "no resident credentials added to pin2") + cred := pin2.credentials[len(pin2.credentials)-1] + assert.Equal(t, cred.ID, ccr.RawId, "RawId mismatch (want pin2 resident credential)") + }, + }, + { + name: "passwordless bio device", + fido2: newFakeFIDO2(bio1), + setUP: bio1.setUP, + createCredential: func() *wanlib.CredentialCreation { + cp := pwdlessCC + return &cp + }, + prompt: bio1, + assertResponse: func(t *testing.T, ccr *wanpb.CredentialCreationResponse, attObj *protocol.AttestationObject) { + require.NotEmpty(t, bio1.credentials, "no resident credentials added to bio1") + cred := bio1.credentials[len(bio1.credentials)-1] + assert.Equal(t, cred.ID, ccr.RawId, "RawId mismatch (want bio1 resident credential)") + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.fido2.setCallbacks() + test.setUP() + + timeout := test.timeout + if timeout == 0 { + timeout = 1 * time.Second + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + prompt := test.prompt + if prompt == nil { + prompt = noopPrompt{} + } + mfaResp, err := wancli.FIDO2Register(ctx, origin, test.createCredential(), prompt) + switch { + case test.wantErr != nil && err == nil: + t.Fatalf("FIDO2Register returned err = nil, wantErr %q", test.wantErr) + case test.wantErr != nil: + require.True(t, errors.Is(err, test.wantErr), "FIDO2Register returned err = %q, wantErr %q", err, test.wantErr) + return + default: + require.NoError(t, err, "FIDO2Register failed") + require.NotNil(t, mfaResp, "mfaResp nil") + } + + // Do a few baseline checks, tests can assert further. + got := mfaResp.GetWebauthn() + require.NotNil(t, got, "credential response nil") + require.NotNil(t, got.Response, "attestation response nil") + assert.NotNil(t, got.Response.ClientDataJson, "ClientDataJSON nil") + want := &wanpb.CredentialCreationResponse{ + Type: string(protocol.PublicKeyCredentialType), + RawId: got.RawId, + Response: &wanpb.AuthenticatorAttestationResponse{ + ClientDataJson: got.Response.ClientDataJson, + AttestationObject: got.Response.AttestationObject, + }, + Extensions: got.Extensions, + } + if diff := cmp.Diff(want, got); diff != "" { + t.Fatalf("FIDO2Register()/CredentialCreationResponse mismatch (-want +got):\n%v", diff) + } + + attObj := &protocol.AttestationObject{} + err = cbor.Unmarshal(got.Response.AttestationObject, attObj) + require.NoError(t, err, "Failed to unmarshal AttestationObject") + assert.Equal(t, makeCredentialAuthDataRaw, attObj.RawAuthData, "RawAuthData mismatch") + + if test.assertResponse != nil { + test.assertResponse(t, got, attObj) + } + }) + } +} + +func TestFIDO2Register_errors(t *testing.T) { + resetFIDO2AfterTests(t) + + // Make sure we won't call the real libfido2. + f2 := newFakeFIDO2() + f2.setCallbacks() + + const origin = "https://example.com" + okCC := &wanlib.CredentialCreation{ + Response: protocol.PublicKeyCredentialCreationOptions{ + Challenge: make([]byte, 32), + RelyingParty: protocol.RelyingPartyEntity{ + ID: "example.com", + }, + Parameters: []protocol.CredentialParameter{ + {Type: protocol.PublicKeyCredentialType, Algorithm: webauthncose.AlgES256}, + }, + AuthenticatorSelection: protocol.AuthenticatorSelection{ + UserVerification: protocol.VerificationDiscouraged, + }, + Attestation: protocol.PreferNoAttestation, + }, + } + + pwdlessOK := *okCC + pwdlessOK.Response.RelyingParty.Name = "Teleport" + pwdlessOK.Response.User = protocol.UserEntity{ + CredentialEntity: protocol.CredentialEntity{ + Name: "llama", + }, + DisplayName: "Llama", + ID: []byte{1, 2, 3, 4, 5}, // arbitrary + } + rrk := true + pwdlessOK.Response.AuthenticatorSelection.RequireResidentKey = &rrk + pwdlessOK.Response.AuthenticatorSelection.UserVerification = protocol.VerificationRequired + + var prompt noopPrompt + + tests := []struct { + name string + origin string + createCC func() *wanlib.CredentialCreation + prompt wancli.RegisterPrompt + wantErr string + }{ + { + name: "ok - timeout", // check that good params are good + origin: origin, + createCC: func() *wanlib.CredentialCreation { return okCC }, + prompt: prompt, + wantErr: context.DeadlineExceeded.Error(), + }, + { + name: "nil origin", + createCC: func() *wanlib.CredentialCreation { return okCC }, + prompt: prompt, + wantErr: "origin", + }, + { + name: "nil cc", + origin: origin, + createCC: func() *wanlib.CredentialCreation { return nil }, + prompt: prompt, + wantErr: "credential creation required", + }, + { + name: "cc without challenge", + origin: origin, + createCC: func() *wanlib.CredentialCreation { + cp := *okCC + cp.Response.Challenge = nil + return &cp + }, + prompt: prompt, + wantErr: "challenge", + }, + { + name: "cc without RPID", + origin: origin, + createCC: func() *wanlib.CredentialCreation { + cp := *okCC + cp.Response.RelyingParty.ID = "" + return &cp + }, + prompt: prompt, + wantErr: "relying party ID", + }, + { + name: "cc unsupported parameters", + origin: origin, + createCC: func() *wanlib.CredentialCreation { + cp := *okCC + cp.Response.Parameters = []protocol.CredentialParameter{ + {Type: protocol.PublicKeyCredentialType, Algorithm: webauthncose.AlgEdDSA}, + } + return &cp + }, + prompt: prompt, + wantErr: "ES256", + }, + { + name: "nil pinPrompt", + origin: origin, + createCC: func() *wanlib.CredentialCreation { return okCC }, + wantErr: "prompt", + }, + { + name: "rrk empty RP name", + origin: origin, + createCC: func() *wanlib.CredentialCreation { + cp := pwdlessOK + cp.Response.RelyingParty.Name = "" + return &cp + }, + prompt: prompt, + wantErr: "relying party name", + }, + { + name: "rrk empty user name", + origin: origin, + createCC: func() *wanlib.CredentialCreation { + cp := pwdlessOK + cp.Response.User.Name = "" + return &cp + }, + prompt: prompt, + wantErr: "user name", + }, + { + name: "rrk empty user display name", + origin: origin, + createCC: func() *wanlib.CredentialCreation { + cp := pwdlessOK + cp.Response.User.DisplayName = "" + return &cp + }, + prompt: prompt, + wantErr: "user display name", + }, + { + name: "rrk nil user ID", + origin: origin, + createCC: func() *wanlib.CredentialCreation { + cp := pwdlessOK + cp.Response.User.ID = nil + return &cp + }, + prompt: prompt, + wantErr: "user ID", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() + + _, err := wancli.FIDO2Register(ctx, test.origin, test.createCC(), test.prompt) + require.Error(t, err, "FIDO2Register returned err = nil, want %q", test.wantErr) + assert.Contains(t, err.Error(), test.wantErr, "FIDO2Register returned err = %q, want %q", err, test.wantErr) + }) + } +} + +func resetFIDO2AfterTests(t *testing.T) { + pollInterval := wancli.FIDO2PollInterval + devLocations := wancli.FIDODeviceLocations + newDevice := wancli.FIDONewDevice + t.Cleanup(func() { + wancli.FIDO2PollInterval = pollInterval + wancli.FIDODeviceLocations = devLocations + wancli.FIDONewDevice = newDevice + }) +} + +type fakeFIDO2 struct { + locs []*libfido2.DeviceLocation + devices map[string]*fakeFIDO2Device +} + +func newFakeFIDO2(devs ...*fakeFIDO2Device) *fakeFIDO2 { + f := &fakeFIDO2{ + devices: make(map[string]*fakeFIDO2Device), + } + for _, dev := range devs { + if _, ok := f.devices[dev.path]; ok { + panic(fmt.Sprintf("Duplicate device path registered: %q", dev.path)) + } + f.locs = append(f.locs, &libfido2.DeviceLocation{ + Path: dev.path, + }) + f.devices[dev.path] = dev + } + return f +} + +func (f *fakeFIDO2) setCallbacks() { + *wancli.FIDODeviceLocations = f.newMeteredDeviceLocations() + *wancli.FIDONewDevice = f.NewDevice +} + +func (f *fakeFIDO2) newMeteredDeviceLocations() func() ([]*libfido2.DeviceLocation, error) { + i := 0 + return func() ([]*libfido2.DeviceLocation, error) { + // Delay showing devices for a while to exercise polling. + i++ + const minLoops = 2 + if i < minLoops { + return nil, nil + } + return f.locs, nil + } +} + +func (f *fakeFIDO2) NewDevice(path string) (wancli.FIDODevice, error) { + if dev, ok := f.devices[path]; ok { + return dev, nil + } + // go-libfido2 doesn't actually error here, but we do for simplicity. + return nil, errors.New("not found") +} + +type fakeFIDO2Device struct { + path string + info *libfido2.DeviceInfo + pin string + credentials []*libfido2.Credential + + // wantRPID may be set directly to enable RPID checks on Assertion. + wantRPID string + // format may be set directly to change the attestation format. + format string + + key *mocku2f.Key + pubKey []byte + + // mu and cond guard up and cancel. + mu sync.Mutex + cond *sync.Cond + up, cancel bool +} + +func mustNewFIDO2Device(path, pin string, info *libfido2.DeviceInfo, creds ...*libfido2.Credential) *fakeFIDO2Device { + dev, err := newFIDO2Device(path, pin, info, creds...) + if err != nil { + panic(err) + } + return dev +} + +func newFIDO2Device(path, pin string, info *libfido2.DeviceInfo, creds ...*libfido2.Credential) (*fakeFIDO2Device, error) { + key, err := mocku2f.Create() + if err != nil { + return nil, err + } + + pubKeyCBOR, err := wanlib.U2FKeyToCBOR(&key.PrivateKey.PublicKey) + if err != nil { + return nil, err + } + + for _, cred := range creds { + cred.ID = make([]byte, 16) // somewhat arbitrary + if _, err := rand.Read(cred.ID); err != nil { + return nil, err + } + cred.Type = libfido2.ES256 + } + + d := &fakeFIDO2Device{ + path: path, + pin: pin, + credentials: creds, + format: "packed", + info: info, + key: key, + pubKey: pubKeyCBOR, + } + d.cond = sync.NewCond(&d.mu) + return d, nil +} + +func (f *fakeFIDO2Device) PromptPIN() (string, error) { + return f.pin, nil +} + +func (f *fakeFIDO2Device) PromptAdditionalTouch() error { + f.setUP() + return nil +} + +func (f *fakeFIDO2Device) credentialID() []byte { + return f.key.KeyHandle +} + +func (f *fakeFIDO2Device) cert() []byte { + return f.key.Cert +} + +func (f *fakeFIDO2Device) Info() (*libfido2.DeviceInfo, error) { + return f.info, nil +} + +func (f *fakeFIDO2Device) setUP() { + f.mu.Lock() + f.up = true + f.mu.Unlock() + f.cond.Broadcast() +} + +func (f *fakeFIDO2Device) Cancel() error { + f.mu.Lock() + f.cancel = true + f.mu.Unlock() + f.cond.Broadcast() + return nil +} + +func (f *fakeFIDO2Device) Credentials(rpID string, pin string) ([]*libfido2.Credential, error) { + if f.pin != "" { + if err := f.validatePIN(pin); err != nil { + return nil, err + } + } + return f.credentials, nil +} + +func (f *fakeFIDO2Device) MakeCredential( + clientDataHash []byte, + rp libfido2.RelyingParty, + user libfido2.User, + typ libfido2.CredentialType, + pin string, + opts *libfido2.MakeCredentialOpts, +) (*libfido2.Attestation, error) { + switch { + case len(clientDataHash) == 0: + return nil, errors.New("clientDataHash required") + case rp.ID == "": + return nil, errors.New("rp.ID required") + case typ != libfido2.ES256: + return nil, errors.New("bad credential type") + case opts.UV == libfido2.False: // can only be empty or true + return nil, libfido2.ErrUnsupportedOption + case opts.UV == libfido2.True && !f.hasUV(): + return nil, libfido2.ErrUnsupportedOption // PIN authenticators don't like UV + } + + // Validate PIN regardless of opts. + // This is in line with how current YubiKeys behave. + if err := f.validatePIN(pin); err != nil { + return nil, err + } + + if err := f.maybeLockUntilInteraction(true /* up */); err != nil { + return nil, err + } + + cert, sig := f.cert(), makeCredentialSig + if f.format == "none" { + // Do not return attestation data in case of "none". + // This is a hypothetical scenario, as I haven't seen device that does this. + cert, sig = nil, nil + } + + // Did we create a resident credential? Create a new ID for it and record it. + cID := f.key.KeyHandle + if opts.RK == libfido2.True { + cID = make([]byte, 16) // somewhat arbitrary + if _, err := rand.Read(cID); err != nil { + return nil, err + } + f.credentials = append(f.credentials, &libfido2.Credential{ + ID: cID, + Type: libfido2.ES256, + User: user, + }) + } + + return &libfido2.Attestation{ + ClientDataHash: clientDataHash, + AuthData: makeCredentialAuthDataCBOR, + CredentialID: cID, + CredentialType: libfido2.ES256, + PubKey: f.pubKey, + Cert: cert, + Sig: sig, + Format: f.format, + }, nil +} + +func (f *fakeFIDO2Device) Assertion( + rpID string, + clientDataHash []byte, + credentialIDs [][]byte, + pin string, + opts *libfido2.AssertionOpts, +) (*libfido2.Assertion, error) { + switch { + case rpID == "": + return nil, errors.New("rp.ID required") + case f.wantRPID != "" && f.wantRPID != rpID: + return nil, libfido2.ErrNoCredentials + case len(clientDataHash) == 0: + return nil, errors.New("clientDataHash required") + case opts.UV == libfido2.False: // can only be empty or true + return nil, libfido2.ErrUnsupportedOption + } + + // Validate PIN only if present and UP is required. + // This is in line with how current YubiKeys behave. + privilegedAccess := f.isBio() + if pin != "" && opts.UP == libfido2.True { + if err := f.validatePIN(pin); err != nil { + return nil, err + } + privilegedAccess = true + } + + // Is our credential allowed? + foundCredential := false + for _, cred := range credentialIDs { + if bytes.Equal(cred, f.key.KeyHandle) { + foundCredential = true + break + } + + // Check resident credentials if we are properly authorized. + if !privilegedAccess { + continue + } + for _, resident := range f.credentials { + if bytes.Equal(cred, resident.ID) { + foundCredential = true + break + } + } + if foundCredential { + break + } + } + explicitCreds := len(credentialIDs) > 0 + if explicitCreds && !foundCredential { + return nil, libfido2.ErrNoCredentials + } + + if err := f.maybeLockUntilInteraction(opts.UP == libfido2.True); err != nil { + return nil, err + } + + // Pick a credential for the user? + switch { + case !explicitCreds && privilegedAccess && len(f.credentials) > 0: + // OK, at this point an authenticator picks a credential for the user. + case !explicitCreds: + return nil, libfido2.ErrNoCredentials + } + + return &libfido2.Assertion{ + AuthDataCBOR: assertionAuthDataCBOR, + Sig: assertionSig, + }, nil +} + +func (f *fakeFIDO2Device) validatePIN(pin string) error { + switch { + case f.isBio() && pin == "": // OK, biometric check supersedes PIN. + case f.pin != "" && pin == "": + return libfido2.ErrPinRequired + case f.pin != "" && f.pin != pin: + return libfido2.ErrPinInvalid + } + return nil +} + +func (f *fakeFIDO2Device) hasUV() bool { + for _, opt := range f.info.Options { + if opt.Name == "uv" { + return opt.Value == libfido2.True + } + } + return false +} + +func (f *fakeFIDO2Device) isBio() bool { + for _, opt := range f.info.Options { + if opt.Name == "bioEnroll" { + return opt.Value == libfido2.True + } + } + return false +} + +func (f *fakeFIDO2Device) maybeLockUntilInteraction(up bool) error { + if !up { + return nil // without UserPresence it doesn't lock. + } + + // Lock until we get a touch or a cancel. + f.mu.Lock() + for !f.up && !f.cancel { + f.cond.Wait() + } + + // Record/reset state. + isCancel := f.cancel + f.up = false + f.cancel = false + + if isCancel { + f.mu.Unlock() + return libfido2.ErrKeepaliveCancel + } + f.mu.Unlock() + + return nil +} diff --git a/lib/auth/webauthncli/login.go b/lib/auth/webauthncli/u2f_login.go similarity index 91% rename from lib/auth/webauthncli/login.go rename to lib/auth/webauthncli/u2f_login.go index a6fd964c8c2e0..7c4e81f1da64c 100644 --- a/lib/auth/webauthncli/login.go +++ b/lib/auth/webauthncli/u2f_login.go @@ -30,13 +30,10 @@ import ( wanlib "github.com/gravitational/teleport/lib/auth/webauthn" ) -// Login performs client-side, U2F-compatible Webauthn login. -// This method blocks until either device authentication is successful or the -// context is cancelled. Calling Login without a deadline or cancel condition -// may cause it block forever. -// The caller is expected to prompt the user for action before calling this -// method. -func Login(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error) { +// U2FLogin implements Login for U2F/CTAP1 devices. +// The implementation is backed exclusively by Go code, making it useful in +// scenarios where libfido2 is unavailable. +func U2FLogin(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error) { switch { case origin == "": return nil, trace.BadParameter("origin required") diff --git a/lib/auth/webauthncli/login_test.go b/lib/auth/webauthncli/u2f_login_test.go similarity index 98% rename from lib/auth/webauthncli/login_test.go rename to lib/auth/webauthncli/u2f_login_test.go index 699a76d1104ca..83d8d8a56384c 100644 --- a/lib/auth/webauthncli/login_test.go +++ b/lib/auth/webauthncli/u2f_login_test.go @@ -146,7 +146,7 @@ func TestLogin(t *testing.T) { fakeDevs := &fakeDevices{devs: test.devs} fakeDevs.assignU2FCallbacks() - mfaResp, err := wancli.Login(ctx, origin, assertion) + mfaResp, err := wancli.U2FLogin(ctx, origin, assertion) switch hasErr := err != nil; { case hasErr != test.wantErr: t.Fatalf("Login returned err = %v, wantErr = %v", err, test.wantErr) @@ -254,7 +254,7 @@ func TestLogin_errors(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() - _, err := wancli.Login(ctx, test.origin, test.getAssertion()) + _, err := wancli.U2FLogin(ctx, test.origin, test.getAssertion()) require.True(t, trace.IsBadParameter(err)) }) } @@ -384,7 +384,9 @@ func (f *fakeDevice) Authenticate(req u2ftoken.AuthenticateRequest) (*u2ftoken.A return nil, trace.Wrap(err) } var counter uint32 - binary.Read(bytes.NewReader(rawResp[1:5]), binary.BigEndian, &counter) + if err := binary.Read(bytes.NewReader(rawResp[1:5]), binary.BigEndian, &counter); err != nil { + return nil, err + } sign := rawResp[5:] return &u2ftoken.AuthenticateResponse{ diff --git a/lib/auth/webauthncli/register.go b/lib/auth/webauthncli/u2f_register.go similarity index 94% rename from lib/auth/webauthncli/register.go rename to lib/auth/webauthncli/u2f_register.go index b06f69c4cbf08..e1863ac75f4a3 100644 --- a/lib/auth/webauthncli/register.go +++ b/lib/auth/webauthncli/u2f_register.go @@ -38,13 +38,10 @@ import ( log "github.com/sirupsen/logrus" ) -// Register performs client-side, U2F-compatible Webauthn registration. -// This method blocks until either device authentication is successful or the -// context is cancelled. Calling Register without a deadline or cancel condition -// may cause it block forever. -// The caller is expected to prompt the user for action before calling this -// method. -func Register(ctx context.Context, origin string, cc *wanlib.CredentialCreation) (*proto.MFARegisterResponse, error) { +// U2FRegister implements Register for U2F/CTAP1 devices. +// The implementation is backed exclusively by Go code, making it useful in +// scenarios where libfido2 is unavailable. +func U2FRegister(ctx context.Context, origin string, cc *wanlib.CredentialCreation) (*proto.MFARegisterResponse, error) { // Preliminary checks, more below. switch { case origin == "": diff --git a/lib/auth/webauthncli/register_test.go b/lib/auth/webauthncli/u2f_register_test.go similarity index 92% rename from lib/auth/webauthncli/register_test.go rename to lib/auth/webauthncli/u2f_register_test.go index dc01544cc3793..f45a39d956936 100644 --- a/lib/auth/webauthncli/register_test.go +++ b/lib/auth/webauthncli/u2f_register_test.go @@ -35,7 +35,6 @@ func TestRegister(t *testing.T) { const rpID = "example.com" const origin = "https://example.com" - // Prepare a few fake devices. u2fKey, err := newFakeDevice("u2fkey" /* name */, "" /* appID */) require.NoError(t, err) registeredKey, err := newFakeDevice("regkey" /* name */, rpID /* appID */) @@ -48,8 +47,17 @@ func TestRegister(t *testing.T) { RPID: rpID, }, Identity: &fakeIdentity{ - User: user, - Devices: []*types.MFADevice{registeredKey.mfaDevice}, + User: user, + Devices: []*types.MFADevice{ + // Fake a WebAuthn device record, as U2F devices are not excluded from registration. + { + Device: &types.MFADevice_Webauthn{ + Webauthn: &types.WebauthnDevice{ + CredentialId: registeredKey.key.KeyHandle, + }, + }, + }, + }, }, } @@ -93,7 +101,7 @@ func TestRegister(t *testing.T) { fakeDevs := &fakeDevices{devs: test.devs} fakeDevs.assignU2FCallbacks() - resp, err := wancli.Register(ctx, origin, cc) + resp, err := wancli.U2FRegister(ctx, origin, cc) switch hasErr := err != nil; { case hasErr != test.wantErr: t.Fatalf("Register returned err = %v, wantErr = %v", err, test.wantErr) @@ -196,7 +204,7 @@ func TestRegister_errors(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - _, err := wancli.Register(ctx, test.origin, test.makeCC()) + _, err := wancli.U2FRegister(ctx, test.origin, test.makeCC()) require.Error(t, err) require.Contains(t, err.Error(), test.wantErr) }) diff --git a/lib/backend/backend.go b/lib/backend/backend.go index 2b398ec95153e..b17e616b2d2d2 100644 --- a/lib/backend/backend.go +++ b/lib/backend/backend.go @@ -85,10 +85,6 @@ type Backend interface { // CloseWatchers closes all the watchers // without closing the backend CloseWatchers() - - // Migrate performs any data migration necessary between Teleport versions. - // Migrate must be called BEFORE using any other methods of the Backend. - Migrate(context.Context) error } // IterateRange is a helper for stepping over a range @@ -285,6 +281,8 @@ func GetPaginationKey(r types.Resource) string { return string(internalKey(resourceWithType.GetHostID(), resourceWithType.GetName())) case types.AppServer: return string(internalKey(resourceWithType.GetHostID(), resourceWithType.GetName())) + case types.WindowsDesktop: + return string(internalKey(resourceWithType.GetHostID(), resourceWithType.GetName())) default: return r.GetName() } @@ -379,9 +377,3 @@ func Key(parts ...string) []byte { func internalKey(internalPrefix string, parts ...string) []byte { return []byte(strings.Join(append([]string{internalPrefix}, parts...), string(Separator))) } - -// NoMigrations implements a nop Migrate method of Backend. -// Backend implementations should embed this when no migrations are necessary. -type NoMigrations struct{} - -func (NoMigrations) Migrate(context.Context) error { return nil } diff --git a/lib/backend/backend_test.go b/lib/backend/backend_test.go index 65101e2663710..80a5f0b7b9818 100644 --- a/lib/backend/backend_test.go +++ b/lib/backend/backend_test.go @@ -19,6 +19,8 @@ package backend import ( "testing" + + "github.com/stretchr/testify/require" ) func TestParams(t *testing.T) { @@ -36,3 +38,20 @@ func TestParams(t *testing.T) { t.Errorf("expected 'path' to be '%v', got '%v'", expectedPath, path) } } + +func TestRangeEnd(t *testing.T) { + for _, test := range []struct { + key, expected string + }{ + {"abc", "abd"}, + {"/foo/bar", "/foo/bas"}, + {"/xyz", "/xy{"}, + {"\xFF", "\x00"}, + {"\xFF\xFF\xFF", "\x00"}, + } { + t.Run(test.key, func(t *testing.T) { + end := RangeEnd([]byte(test.key)) + require.Equal(t, test.expected, string(end)) + }) + } +} diff --git a/lib/backend/dynamo/dynamodbbk.go b/lib/backend/dynamo/dynamodbbk.go index ea1dd5535b739..669ca28c251dc 100644 --- a/lib/backend/dynamo/dynamodbbk.go +++ b/lib/backend/dynamo/dynamodbbk.go @@ -122,7 +122,6 @@ func (cfg *Config) CheckAndSetDefaults() error { type Backend struct { *log.Entry Config - backend.NoMigrations svc *dynamodb.DynamoDB streams *dynamodbstreams.DynamoDBStreams clock clockwork.Clock @@ -363,6 +362,10 @@ func (b *Backend) GetRange(ctx context.Context, startKey []byte, endKey []byte, if len(endKey) == 0 { return nil, trace.BadParameter("missing parameter endKey") } + if limit <= 0 { + limit = backend.DefaultRangeLimit + } + result, err := b.getAllRecords(ctx, startKey, endKey, limit) if err != nil { return nil, trace.Wrap(err) @@ -383,6 +386,7 @@ func (b *Backend) GetRange(ctx context.Context, startKey []byte, endKey []byte, func (b *Backend) getAllRecords(ctx context.Context, startKey []byte, endKey []byte, limit int) (*getResult, error) { var result getResult + // this code is being extra careful here not to introduce endless loop // by some unfortunate series of events for i := 0; i < backend.DefaultRangeLimit/100; i++ { @@ -391,7 +395,9 @@ func (b *Backend) getAllRecords(ctx context.Context, startKey []byte, endKey []b return nil, trace.Wrap(err) } result.records = append(result.records, re.records...) - if len(result.records) >= limit || len(re.lastEvaluatedKey) == 0 { + // If the limit was exceeded or there are no more records to fetch return the current result + // otherwise updated lastEvaluatedKey and proceed with obtaining new records. + if (limit != 0 && len(result.records) >= limit) || len(re.lastEvaluatedKey) == 0 { if len(result.records) == backend.DefaultRangeLimit { b.Warnf("Range query hit backend limit. (this is a bug!) startKey=%q,limit=%d", startKey, backend.DefaultRangeLimit) } @@ -744,12 +750,12 @@ func (b *Backend) getRecords(ctx context.Context, startKey, endKey string, limit // isExpired returns 'true' if the given object (record) has a TTL and // it's due. -func (r *record) isExpired() bool { +func (r *record) isExpired(now time.Time) bool { if r.Expires == nil { return false } expiryDateUTC := time.Unix(*r.Expires, 0).UTC() - return time.Now().UTC().After(expiryDateUTC) + return now.UTC().After(expiryDateUTC) } func removeDuplicates(elements []record) []record { @@ -868,7 +874,7 @@ func (b *Backend) getKey(ctx context.Context, key []byte) (*record, error) { return nil, trace.WrapWithMessage(err, "failed to unmarshal dynamo item %q", string(key)) } // Check if key expired, if expired delete it - if r.isExpired() { + if r.isExpired(b.clock.Now()) { if err := b.deleteKey(ctx, key); err != nil { b.Warnf("Failed deleting expired key %q: %v", key, err) } diff --git a/lib/backend/dynamo/dynamodbbk_test.go b/lib/backend/dynamo/dynamodbbk_test.go index d2b336cd2441f..aba11048f0c64 100644 --- a/lib/backend/dynamo/dynamodbbk_test.go +++ b/lib/backend/dynamo/dynamodbbk_test.go @@ -22,11 +22,12 @@ import ( "testing" "time" + "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" + "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/trace" - "github.com/jonboulle/clockwork" ) func TestMain(m *testing.M) { @@ -70,7 +71,7 @@ func TestDynamoDB(t *testing.T) { if err != nil { return nil, nil, trace.Wrap(err) } - clock := clockwork.NewFakeClock() + clock := clockwork.NewFakeClockAt(time.Now()) uut.clock = clock return uut, clock, nil } diff --git a/lib/backend/etcdbk/etcd.go b/lib/backend/etcdbk/etcd.go index bec81ffe92f0e..8714618b62731 100644 --- a/lib/backend/etcdbk/etcd.go +++ b/lib/backend/etcdbk/etcd.go @@ -23,7 +23,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" - "io/ioutil" + "os" "sort" "strings" "time" @@ -132,7 +132,6 @@ var ( ) type EtcdBackend struct { - backend.NoMigrations nodes []string *log.Entry cfg *Config @@ -278,7 +277,7 @@ func (cfg *Config) Validate() error { cfg.DialTimeout = apidefaults.DefaultDialTimeout } if cfg.PasswordFile != "" { - out, err := ioutil.ReadFile(cfg.PasswordFile) + out, err := os.ReadFile(cfg.PasswordFile) if err != nil { return trace.ConvertSystemError(err) } @@ -314,11 +313,11 @@ func (b *EtcdBackend) reconnect(ctx context.Context) error { tlsConfig := utils.TLSConfig(nil) if b.cfg.TLSCertFile != "" { - clientCertPEM, err := ioutil.ReadFile(b.cfg.TLSCertFile) + clientCertPEM, err := os.ReadFile(b.cfg.TLSCertFile) if err != nil { return trace.ConvertSystemError(err) } - clientKeyPEM, err := ioutil.ReadFile(b.cfg.TLSKeyFile) + clientKeyPEM, err := os.ReadFile(b.cfg.TLSKeyFile) if err != nil { return trace.ConvertSystemError(err) } @@ -330,7 +329,7 @@ func (b *EtcdBackend) reconnect(ctx context.Context) error { } if b.cfg.TLSCAFile != "" { - caCertPEM, err := ioutil.ReadFile(b.cfg.TLSCAFile) + caCertPEM, err := os.ReadFile(b.cfg.TLSCAFile) if err != nil { return trace.ConvertSystemError(err) } diff --git a/lib/backend/firestore/firestorebk.go b/lib/backend/firestore/firestorebk.go index 17cde960a0a39..a051544f64870 100644 --- a/lib/backend/firestore/firestorebk.go +++ b/lib/backend/firestore/firestorebk.go @@ -95,7 +95,6 @@ func (cfg *backendConfig) CheckAndSetDefaults() error { type Backend struct { *log.Entry backendConfig - backend.NoMigrations // svc is the primary Firestore client svc *firestore.Client // clock is the diff --git a/lib/backend/lite/lite.go b/lib/backend/lite/lite.go index a9aada40113f4..49ac382b7730a 100644 --- a/lib/backend/lite/lite.go +++ b/lib/backend/lite/lite.go @@ -21,10 +21,11 @@ import ( "context" "database/sql" "database/sql/driver" - "fmt" + "net/url" "os" "path/filepath" "runtime/debug" + "strconv" "sync/atomic" "time" @@ -39,15 +40,36 @@ import ( ) const ( - // BackendName is the name of this backend + // BackendName is the name of this backend. BackendName = "sqlite" // AlternativeName is another name of this backend. - AlternativeName = "dir" - defaultDirMode os.FileMode = 0770 - defaultDBFile = "sqlite.db" - slowTransactionThreshold = time.Second - syncOFF = "OFF" - busyTimeout = 10000 + AlternativeName = "dir" + + // SyncOff disables file system sync after writing. + SyncOff = "OFF" + // SyncFull fsyncs the database file on disk after every write. + SyncFull = "FULL" + + // JournalMemory keeps the rollback journal in memory instead of storing it + // on disk. + JournalMemory = "MEMORY" +) + +const ( + // defaultDirMode is the mode of the newly created directories that are part + // of the Path + defaultDirMode os.FileMode = 0770 + + // defaultDBFile is the file name of the sqlite db in the directory + // specified by Path + defaultDBFile = "sqlite.db" + slowTransactionThreshold = time.Second + + // defaultSync is the default value for Sync + defaultSync = SyncOff + + // defaultBusyTimeout is the default value for BusyTimeout, in ms + defaultBusyTimeout = 10000 ) // GetName is a part of backend API and it returns SQLite backend type @@ -56,14 +78,6 @@ func GetName() string { return BackendName } -func init() { - sql.Register(BackendName, &sqlite3.SQLiteDriver{ - ConnectHook: func(conn *sqlite3.SQLiteConn) error { - return nil - }, - }) -} - // Config structure represents configuration section type Config struct { // Path is a path to the database directory @@ -77,15 +91,12 @@ type Config struct { EventsOff bool `json:"events_off,omitempty"` // Clock allows to override clock used in the backend Clock clockwork.Clock `json:"-"` - // Sync sets synchronous pragrma + // Sync sets the synchronous pragma Sync string `json:"sync,omitempty"` // BusyTimeout sets busy timeout in milliseconds BusyTimeout int `json:"busy_timeout,omitempty"` - // Memory turns memory mode of the database - Memory bool `json:"memory"` - // MemoryName sets the name of the database, - // set to "sqlite.db" by default - MemoryName string `json:"memory_name"` + // Journal sets the journal_mode pragma + Journal string `json:"journal,omitempty"` // Mirror turns on mirror mode for the backend, // which will use record IDs for Put and PutRange passed from // the resources, not generate a new one @@ -95,7 +106,7 @@ type Config struct { // CheckAndSetDefaults is a helper returns an error if the supplied configuration // is not enough to connect to sqlite func (cfg *Config) CheckAndSetDefaults() error { - if cfg.Path == "" && !cfg.Memory { + if cfg.Path == "" { return trace.BadParameter("specify directory path to the database using 'path' parameter") } if cfg.BufferSize == 0 { @@ -108,17 +119,69 @@ func (cfg *Config) CheckAndSetDefaults() error { cfg.Clock = clockwork.NewRealClock() } if cfg.Sync == "" { - cfg.Sync = syncOFF + cfg.Sync = defaultSync } if cfg.BusyTimeout == 0 { - cfg.BusyTimeout = busyTimeout - } - if cfg.MemoryName == "" { - cfg.MemoryName = defaultDBFile + cfg.BusyTimeout = defaultBusyTimeout } return nil } +// ConnectionURI returns a connection string usable with sqlite according to the +// Config. +func (cfg *Config) ConnectionURI() string { + params := url.Values{} + params.Set("_busy_timeout", strconv.Itoa(cfg.BusyTimeout)) + // The _txlock parameter is parsed by go-sqlite to determine if (all) + // transactions should be started with `BEGIN DEFERRED` (the default, same + // as `BEGIN`), `BEGIN IMMEDIATE` or `BEGIN EXCLUSIVE`. + // + // The way we use sqlite relies entirely on the busy timeout handler (also + // configured through the connection URL, with the _busy_timeout parameter) + // to address concurrency problems, and treats any SQLITE_BUSY errors as a + // fatal issue with the database; however, in scenarios with multiple + // readwriters it is possible to still get a busy error even with a generous + // busy timeout handler configured, as two transactions that both start off + // with a SELECT - thus acquiring a SHARED lock, see + // https://www.sqlite.org/lockingv3.html#transaction_control - then attempt + // to upgrade to a RESERVED lock to upsert or delete something can end up + // requiring one of the two transactions to forcibly rollback to avoid a + // deadlock, which is signaled by the sqlite engine with a SQLITE_BUSY error + // returned to one of the two. When that happens, a concurrent-aware program + // can just try the transaction again a few times - making sure to disregard + // what was read before the transaction actually committed. + // + // As we're not really interested in concurrent sqlite access (process + // storage has very little written to, sharing a sqlite database as the + // backend between two auths is not really supported, and caches shouldn't + // ever run on the same underlying sqlite backend) we instead start every + // transaction with `BEGIN IMMEDIATE`, which grabs a RESERVED lock + // immediately (waiting for the busy timeout in case some other connection + // to the database has the lock) at the beginning of the transaction, thus + // avoiding any spurious SQLITE_BUSY error that can happen halfway through a + // transaction. + // + // If we end up requiring better concurrent access to sqlite in the future + // we should consider enabling Write-Ahead Logging mode, to actually allow + // for reads to happen at the same time as writes, adding some amount of + // retries to inTransaction, and double-checking that all uses of it + // correctly handle the possibility of the transaction being restarted. + params.Set("_txlock", "immediate") + if cfg.Sync != "" { + params.Set("_sync", cfg.Sync) + } + if cfg.Journal != "" { + params.Set("_journal", cfg.Journal) + } + + u := url.URL{ + Scheme: "file", + Opaque: url.QueryEscape(filepath.Join(cfg.Path, defaultDBFile)), + RawQuery: params.Encode(), + } + return u.String() +} + // New returns a new instance of sqlite backend func New(ctx context.Context, params backend.Params) (*Backend, error) { var cfg *Config @@ -135,23 +198,18 @@ func NewWithConfig(ctx context.Context, cfg Config) (*Backend, error) { if err := cfg.CheckAndSetDefaults(); err != nil { return nil, trace.Wrap(err) } - var connectorURL string - if !cfg.Memory { - // Ensure that the path to the root directory exists. - err := os.MkdirAll(cfg.Path, defaultDirMode) - if err != nil { - return nil, trace.ConvertSystemError(err) - } - fullPath := filepath.Join(cfg.Path, defaultDBFile) - connectorURL = fmt.Sprintf("file:%v?_busy_timeout=%v&_sync=%v", fullPath, cfg.BusyTimeout, cfg.Sync) - } else { - connectorURL = fmt.Sprintf("file:%v?mode=memory", cfg.MemoryName) + connectionURI := cfg.ConnectionURI() + // Ensure that the path to the root directory exists. + err := os.MkdirAll(cfg.Path, defaultDirMode) + if err != nil { + return nil, trace.ConvertSystemError(err) } - db, err := sql.Open(BackendName, connectorURL) + db, err := sql.Open("sqlite3", cfg.ConnectionURI()) if err != nil { - return nil, trace.Wrap(err, "error opening URI: %v", connectorURL) + return nil, trace.Wrap(err, "error opening URI: %v", connectionURI) } - // serialize access to sqlite to avoid database is locked errors + // serialize access to sqlite, as we're using immediate transactions anyway, + // and in-memory go locks are faster than sqlite locks db.SetMaxOpenConns(1) buf := backend.NewCircularBuffer( backend.BufferCapacity(cfg.BufferSize), @@ -166,9 +224,9 @@ func NewWithConfig(ctx context.Context, cfg Config) (*Backend, error) { ctx: closeCtx, cancel: cancel, } - l.Debugf("Connected to: %v, poll stream period: %v", connectorURL, cfg.PollStreamPeriod) + l.Debugf("Connected to: %v, poll stream period: %v", connectionURI, cfg.PollStreamPeriod) if err := l.createSchema(); err != nil { - return nil, trace.Wrap(err, "error creating schema: %v", connectorURL) + return nil, trace.Wrap(err, "error creating schema: %v", connectionURI) } if err := l.showPragmas(); err != nil { l.Warningf("Failed to show pragma settings: %v.", err) @@ -181,7 +239,6 @@ func NewWithConfig(ctx context.Context, cfg Config) (*Backend, error) { type Backend struct { Config *log.Entry - backend.NoMigrations db *sql.DB // clock is used to generate time, // could be swapped in tests for fixed time @@ -199,17 +256,22 @@ type Backend struct { // parameters, when called, logs some key PRAGMA values func (l *Backend) showPragmas() error { return l.inTransaction(l.ctx, func(tx *sql.Tx) error { - row := tx.QueryRowContext(l.ctx, "PRAGMA synchronous;") - var syncValue string - if err := row.Scan(&syncValue); err != nil { + var journalMode string + row := tx.QueryRowContext(l.ctx, "PRAGMA journal_mode;") + if err := row.Scan(&journalMode); err != nil { + return trace.Wrap(err) + } + row = tx.QueryRowContext(l.ctx, "PRAGMA synchronous;") + var synchronous string + if err := row.Scan(&synchronous); err != nil { return trace.Wrap(err) } - var timeoutValue string + var busyTimeout string row = tx.QueryRowContext(l.ctx, "PRAGMA busy_timeout;") - if err := row.Scan(&timeoutValue); err != nil { + if err := row.Scan(&busyTimeout); err != nil { return trace.Wrap(err) } - l.Debugf("Synchronous: %v, busy timeout: %v", syncValue, timeoutValue) + l.Debugf("journal_mode=%v, synchronous=%v, busy_timeout=%v", journalMode, synchronous, busyTimeout) return nil }) } diff --git a/lib/backend/lite/litemem_test.go b/lib/backend/lite/litemem_test.go deleted file mode 100644 index 764539739e691..0000000000000 --- a/lib/backend/lite/litemem_test.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2018-2019 Gravitational, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package lite - -import ( - "context" - "testing" - "time" - - "github.com/gravitational/teleport/lib/backend" - "github.com/gravitational/teleport/lib/backend/test" - "github.com/gravitational/trace" - - "github.com/jonboulle/clockwork" -) - -func TestLiteMem(t *testing.T) { - newBackend := func(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { - clock := clockwork.NewFakeClock() - - testCfg, err := test.ApplyOptions(options) - if err != nil { - return nil, nil, err - } - - backendCfg := Config{ - Memory: true, - PollStreamPeriod: 300 * time.Millisecond, - Clock: clock, - Mirror: testCfg.MirrorMode, - } - - backend, err := NewWithConfig(context.Background(), backendCfg) - if err != nil { - return nil, nil, trace.Wrap(err) - } - - return backend, clock, nil - } - - test.RunBackendComplianceSuite(t, newBackend) -} diff --git a/lib/backend/memory/memory.go b/lib/backend/memory/memory.go index 7568c4b3d6cb8..58a384f96a498 100644 --- a/lib/backend/memory/memory.go +++ b/lib/backend/memory/memory.go @@ -116,7 +116,6 @@ type Memory struct { *sync.Mutex *log.Entry Config - backend.NoMigrations // tree is a BTree with items tree *btree.BTree // heap is a min heap with expiry records diff --git a/lib/backend/report.go b/lib/backend/report.go index b229dac8a5b07..89207f194aeef 100644 --- a/lib/backend/report.go +++ b/lib/backend/report.go @@ -253,9 +253,6 @@ func (s *Reporter) Clock() clockwork.Clock { return s.Backend.Clock() } -// Migrate runs the necessary data migrations for this backend. -func (s *Reporter) Migrate(ctx context.Context) error { return s.Backend.Migrate(ctx) } - type topRequestsCacheKey struct { component string key string diff --git a/lib/backend/sanitize.go b/lib/backend/sanitize.go index ccdc289e85ad6..f6c751035b9fc 100644 --- a/lib/backend/sanitize.go +++ b/lib/backend/sanitize.go @@ -30,16 +30,16 @@ import ( // errorMessage is the error message to return when invalid input is provided by the caller. const errorMessage = "special characters are not allowed in resource names, please use name composed only from characters, hyphens, dots, and plus signs: %q" -// whitelistPattern is the pattern of allowed characters for each key within +// allowPattern is the pattern of allowed characters for each key within // the path. -var whitelistPattern = regexp.MustCompile(`^[0-9A-Za-z@_:.\-/+]*$`) +var allowPattern = regexp.MustCompile(`^[0-9A-Za-z@_:.\-/+]*$`) -// blacklistPattern matches some unallowed combinations -var blacklistPattern = regexp.MustCompile(`//`) +// denyPattern matches some unallowed combinations +var denyPattern = regexp.MustCompile(`//`) // isKeySafe checks if the passed in key conforms to whitelist func isKeySafe(s []byte) bool { - return whitelistPattern.Match(s) && !blacklistPattern.Match(s) && utf8.Valid(s) + return allowPattern.Match(s) && !denyPattern.Match(s) && utf8.Valid(s) } // Sanitizer wraps a Backend implementation to make sure all values requested @@ -118,12 +118,12 @@ func (s *Sanitizer) Delete(ctx context.Context, key []byte) error { // DeleteRange deletes range of items func (s *Sanitizer) DeleteRange(ctx context.Context, startKey []byte, endKey []byte) error { + // we only validate the start key, since we often compute the end key + // in order to delete a bunch of related entries if !isKeySafe(startKey) { return trace.BadParameter(errorMessage, startKey) } - if !isKeySafe(endKey) { - return trace.BadParameter(errorMessage, endKey) - } + return s.backend.DeleteRange(ctx, startKey, endKey) } @@ -163,6 +163,3 @@ func (s *Sanitizer) Clock() clockwork.Clock { func (s *Sanitizer) CloseWatchers() { s.backend.CloseWatchers() } - -// Migrate runs the necessary data migrations for this backend. -func (s *Sanitizer) Migrate(ctx context.Context) error { return s.backend.Migrate(ctx) } diff --git a/lib/backend/sanitize_test.go b/lib/backend/sanitize_test.go index 42e34975c8f5f..81f4e750a55c0 100644 --- a/lib/backend/sanitize_test.go +++ b/lib/backend/sanitize_test.go @@ -16,99 +16,88 @@ package backend import ( "context" + "fmt" + "testing" "time" "github.com/jonboulle/clockwork" - "gopkg.in/check.v1" + "github.com/stretchr/testify/require" ) -type Suite struct { -} - -var _ = check.Suite(&Suite{}) - -func (s *Suite) SetUpSuite(c *check.C) { -} - -func (s *Suite) TearDownSuite(c *check.C) { -} - -func (s *Suite) TearDownTest(c *check.C) { -} - -func (s *Suite) SetUpTest(c *check.C) { -} - -func (s *Suite) TestSanitizeBucket(c *check.C) { +func TestSanitize(t *testing.T) { tests := []struct { - inKey []byte - outError bool + inKey []byte + assert require.ErrorAssertionFunc }{ { - inKey: []byte("a-b/c:d/.e_f/01"), - outError: false, + inKey: []byte("a-b/c:d/.e_f/01"), + assert: require.NoError, }, { - inKey: []byte("/namespaces//params"), - outError: true, + inKey: []byte("/namespaces//params"), + assert: require.Error, }, { - inKey: RangeEnd([]byte("a-b/c:d/.e_f/01")), - outError: false, + inKey: RangeEnd([]byte("a-b/c:d/.e_f/01")), + assert: require.NoError, }, { - inKey: RangeEnd([]byte("/")), - outError: false, + inKey: RangeEnd([]byte("/")), + assert: require.NoError, }, { - inKey: RangeEnd([]byte("Malformed \xf0\x90\x28\xbc UTF8")), - outError: true, + inKey: RangeEnd([]byte("Malformed \xf0\x90\x28\xbc UTF8")), + assert: require.Error, }, { - inKey: []byte("test+subaddr@example.com"), - outError: false, + inKey: []byte("test+subaddr@example.com"), + assert: require.NoError, + }, + { + inKey: []byte("xyz"), + assert: require.NoError, }, } - for i, tt := range tests { - comment := check.Commentf("Test %v, key: %q", i, string(tt.inKey)) - - safeBackend := NewSanitizer(&nopBackend{}) + for _, tt := range tests { + t.Run(fmt.Sprintf("%v", string(tt.inKey)), func(t *testing.T) { + ctx := context.Background() + safeBackend := NewSanitizer(&nopBackend{}) - ctx := context.TODO() - _, err := safeBackend.Get(ctx, tt.inKey) - c.Assert(err != nil, check.Equals, tt.outError, comment) + _, err := safeBackend.Get(ctx, tt.inKey) + tt.assert(t, err) - _, err = safeBackend.Create(ctx, Item{Key: tt.inKey}) - c.Assert(err != nil, check.Equals, tt.outError, comment) + _, err = safeBackend.Create(ctx, Item{Key: tt.inKey}) + tt.assert(t, err) - _, err = safeBackend.Put(ctx, Item{Key: tt.inKey}) - c.Assert(err != nil, check.Equals, tt.outError, comment) + _, err = safeBackend.Put(ctx, Item{Key: tt.inKey}) + tt.assert(t, err) - _, err = safeBackend.Update(ctx, Item{Key: tt.inKey}) - c.Assert(err != nil, check.Equals, tt.outError, comment) + _, err = safeBackend.Update(ctx, Item{Key: tt.inKey}) + tt.assert(t, err) - _, err = safeBackend.CompareAndSwap(ctx, Item{Key: tt.inKey}, Item{Key: tt.inKey}) - c.Assert(err != nil, check.Equals, tt.outError, comment) + _, err = safeBackend.CompareAndSwap(ctx, Item{Key: tt.inKey}, Item{Key: tt.inKey}) + tt.assert(t, err) - err = safeBackend.Delete(ctx, tt.inKey) - c.Assert(err != nil, check.Equals, tt.outError, comment) + err = safeBackend.Delete(ctx, tt.inKey) + tt.assert(t, err) - err = safeBackend.DeleteRange(ctx, tt.inKey, tt.inKey) - c.Assert(err != nil, check.Equals, tt.outError, comment) + err = safeBackend.DeleteRange(ctx, tt.inKey, tt.inKey) + tt.assert(t, err) + }) } } -type nopBackend struct { - NoMigrations -} +type nopBackend struct{} func (n *nopBackend) Get(_ context.Context, _ []byte) (*Item, error) { return &Item{}, nil } func (n *nopBackend) GetRange(_ context.Context, startKey []byte, endKey []byte, limit int) (*GetResult, error) { - return &GetResult{Items: []Item{Item{Key: []byte("foo"), Value: []byte("bar")}}}, nil + return &GetResult{Items: []Item{ + {Key: []byte("foo"), Value: []byte("bar")}, + }}, nil } func (n *nopBackend) Create(_ context.Context, _ Item) (*Lease, error) { diff --git a/lib/backend/test/suite.go b/lib/backend/test/suite.go index 195236eba1b6a..2ca3bc04462e6 100644 --- a/lib/backend/test/suite.go +++ b/lib/backend/test/suite.go @@ -22,6 +22,7 @@ import ( "context" "encoding/hex" "errors" + "fmt" "math/rand" "sync" "sync/atomic" @@ -140,7 +141,6 @@ func RunBackendComplianceSuite(t *testing.T, newBackend Constructor) { t.Run("Events", func(t *testing.T) { testEvents(t, newBackend) }) - t.Run("WatchersClose", func(t *testing.T) { testWatchersClose(t, newBackend) }) @@ -156,6 +156,14 @@ func RunBackendComplianceSuite(t *testing.T, newBackend Constructor) { t.Run("Mirror", func(t *testing.T) { testMirror(t, newBackend) }) + + t.Run("FetchLimit", func(t *testing.T) { + testFetchLimit(t, newBackend) + }) + + t.Run("Limit", func(t *testing.T) { + testLimit(t, newBackend) + }) } // RequireItems asserts that the supplied `actual` items collection matches @@ -572,6 +580,72 @@ func testEvents(t *testing.T, newBackend Constructor) { requireEvent(t, watcher, types.OpDelete, item.Key, 2*time.Second) } +// testFetchLimit tests fetch max items size limit. +func testFetchLimit(t *testing.T, newBackend Constructor) { + uut, _, err := newBackend() + require.NoError(t, err) + defer func() { require.NoError(t, uut.Close()) }() + + prefix := MakePrefix() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Allocate 65KB buffer. + buff := make([]byte, 1<<16) + itemsCount := 20 + // Fill the backend with events that total size is greater than 1MB (65KB * 20 > 1MB). + for i := 0; i < itemsCount; i++ { + item := &backend.Item{Key: prefix(fmt.Sprintf("/db/database%d", i)), Value: buff} + _, err = uut.Put(ctx, *item) + require.NoError(t, err) + } + + result, err := uut.GetRange(ctx, prefix("/db"), backend.RangeEnd(prefix("/db")), backend.NoLimit) + require.NoError(t, err) + require.Equal(t, itemsCount, len(result.Items)) +} + +// testLimit tests limit. +func testLimit(t *testing.T, newBackend Constructor) { + uut, clock, err := newBackend() + require.NoError(t, err) + defer func() { require.NoError(t, uut.Close()) }() + + prefix := MakePrefix() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + item := &backend.Item{ + Key: prefix("/db/database_tail_item"), + Value: []byte("data"), + Expires: clock.Now().Add(time.Minute), + } + _, err = uut.Put(ctx, *item) + require.NoError(t, err) + for i := 0; i < 10; i++ { + item := &backend.Item{ + Key: prefix(fmt.Sprintf("/db/database%d", i)), + Value: []byte("data"), + Expires: clock.Now().Add(time.Second * 10), + } + _, err = uut.Put(ctx, *item) + require.NoError(t, err) + } + clock.Advance(time.Second * 20) + + item = &backend.Item{ + Key: prefix("/db/database_head_item"), + Value: []byte("data"), + Expires: clock.Now().Add(time.Minute), + } + _, err = uut.Put(ctx, *item) + require.NoError(t, err) + + result, err := uut.GetRange(ctx, prefix("/db"), backend.RangeEnd(prefix("/db")), 2) + require.NoError(t, err) + require.Equal(t, 2, len(result.Items)) +} + // requireEvent asserts that a given event type with the given key is emitted // by a watcher within the supplied timeout, returning that event for further // inspection if successful. diff --git a/lib/backend/wrap.go b/lib/backend/wrap.go index 387c506f146f2..ec1f3dea142f8 100644 --- a/lib/backend/wrap.go +++ b/lib/backend/wrap.go @@ -136,6 +136,3 @@ func (s *Wrapper) CloseWatchers() { func (s *Wrapper) Clock() clockwork.Clock { return s.backend.Clock() } - -// Migrate runs the necessary data migrations for this backend. -func (s *Wrapper) Migrate(ctx context.Context) error { return s.backend.Migrate(ctx) } diff --git a/lib/benchmark/benchmark.go b/lib/benchmark/benchmark.go index 5b1d728b1e848..2775ddbee90f7 100644 --- a/lib/benchmark/benchmark.go +++ b/lib/benchmark/benchmark.go @@ -21,7 +21,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "os" "os/signal" "path/filepath" @@ -157,8 +156,8 @@ func ExportLatencyProfile(path string, h *hdrhistogram.Histogram, ticks int32, v // to benchmark spec. It returns benchmark result when completed. // This is a blocking function that can be cancelled via context argument. func (c *Config) Benchmark(ctx context.Context, tc *client.TeleportClient) (Result, error) { - tc.Stdout = ioutil.Discard - tc.Stderr = ioutil.Discard + tc.Stdout = io.Discard + tc.Stderr = io.Discard tc.Stdin = &bytes.Buffer{} var delay time.Duration ctx, cancel := context.WithCancel(ctx) diff --git a/lib/bpf/bpf_test.go b/lib/bpf/bpf_test.go index 5fa947e96b7f9..d276e80273094 100644 --- a/lib/bpf/bpf_test.go +++ b/lib/bpf/bpf_test.go @@ -23,7 +23,6 @@ import ( "context" _ "embed" "fmt" - "io/ioutil" "net/http" "net/http/httptest" "os" @@ -69,7 +68,7 @@ func (s *Suite) TestWatch(c *check.C) { } // Create temporary directory where cgroup2 hierarchy will be mounted. - dir, err := ioutil.TempDir("", "cgroup-test") + dir, err := os.MkdirTemp("", "cgroup-test") c.Assert(err, check.IsNil) defer os.RemoveAll(dir) @@ -171,7 +170,7 @@ func (s *Suite) TestObfuscate(c *check.C) { // has been executed. go func() { // Create temporary file. - file, err := ioutil.TempFile("", "test-script") + file, err := os.CreateTemp("", "test-script") c.Assert(err, check.IsNil) defer os.Remove(file.Name()) @@ -248,7 +247,7 @@ func (s *Suite) TestScript(c *check.C) { // has been executed. go func() { // Create temporary file. - file, err := ioutil.TempFile("", "test-script") + file, err := os.CreateTemp("", "test-script") c.Assert(err, check.IsNil) defer os.Remove(file.Name()) diff --git a/lib/cache/cache.go b/lib/cache/cache.go index 91c30e3153292..42fc0e9996872 100644 --- a/lib/cache/cache.go +++ b/lib/cache/cache.go @@ -1200,7 +1200,7 @@ var _ map[getCertAuthorityCacheKey]struct{} // compile-time hashability check // GetCertAuthority returns certificate authority by given id. Parameter loadSigningKeys // controls if signing keys are loaded -func (c *Cache) GetCertAuthority(id types.CertAuthID, loadSigningKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { +func (c *Cache) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadSigningKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { rg, err := c.read() if err != nil { return nil, trace.Wrap(err) @@ -1209,8 +1209,10 @@ func (c *Cache) GetCertAuthority(id types.CertAuthID, loadSigningKeys bool, opts if !rg.IsCacheRead() && !loadSigningKeys { ta := func(_ types.CertAuthority) {} // compile-time type assertion - ci, err := c.fnCache.Get(context.TODO(), getCertAuthorityCacheKey{id}, func() (interface{}, error) { - ca, err := rg.trust.GetCertAuthority(id, loadSigningKeys, opts...) + ci, err := c.fnCache.Get(ctx, getCertAuthorityCacheKey{id}, func() (interface{}, error) { + // use cache's close context instead of request context in order to ensure + // that we don't cache a context cancellation error. + ca, err := rg.trust.GetCertAuthority(c.ctx, id, loadSigningKeys, opts...) ta(ca) return ca, err }) @@ -1222,13 +1224,13 @@ func (c *Cache) GetCertAuthority(id types.CertAuthID, loadSigningKeys bool, opts return cachedCA.Clone(), nil } - ca, err := rg.trust.GetCertAuthority(id, loadSigningKeys, opts...) + ca, err := rg.trust.GetCertAuthority(ctx, id, loadSigningKeys, opts...) if trace.IsNotFound(err) && rg.IsCacheRead() { // release read lock early rg.Release() // fallback is sane because method is never used // in construction of derivative caches. - if ca, err := c.Config.Trust.GetCertAuthority(id, loadSigningKeys, opts...); err == nil { + if ca, err := c.Config.Trust.GetCertAuthority(ctx, id, loadSigningKeys, opts...); err == nil { return ca, nil } } @@ -1243,7 +1245,7 @@ var _ map[getCertAuthoritiesCacheKey]struct{} // compile-time hashability check // GetCertAuthorities returns a list of authorities of a given type // loadSigningKeys controls whether signing keys should be loaded or not -func (c *Cache) GetCertAuthorities(caType types.CertAuthType, loadSigningKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { +func (c *Cache) GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadSigningKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { rg, err := c.read() if err != nil { return nil, trace.Wrap(err) @@ -1251,8 +1253,10 @@ func (c *Cache) GetCertAuthorities(caType types.CertAuthType, loadSigningKeys bo defer rg.Release() if !rg.IsCacheRead() && !loadSigningKeys { ta := func(_ []types.CertAuthority) {} // compile-time type assertion - ci, err := c.fnCache.Get(context.TODO(), getCertAuthoritiesCacheKey{caType}, func() (interface{}, error) { - cas, err := rg.trust.GetCertAuthorities(caType, loadSigningKeys, opts...) + ci, err := c.fnCache.Get(ctx, getCertAuthoritiesCacheKey{caType}, func() (interface{}, error) { + // use cache's close context instead of request context in order to ensure + // that we don't cache a context cancellation error. + cas, err := rg.trust.GetCertAuthorities(c.ctx, caType, loadSigningKeys, opts...) ta(cas) return cas, trace.Wrap(err) }) @@ -1267,7 +1271,7 @@ func (c *Cache) GetCertAuthorities(caType types.CertAuthType, loadSigningKeys bo } return cas, nil } - return rg.trust.GetCertAuthorities(caType, loadSigningKeys, opts...) + return rg.trust.GetCertAuthorities(ctx, caType, loadSigningKeys, opts...) } // GetStaticTokens gets the list of static tokens used to provision nodes. @@ -1932,6 +1936,16 @@ func (c *Cache) GetWindowsDesktops(ctx context.Context, filter types.WindowsDesk return rg.windowsDesktops.GetWindowsDesktops(ctx, filter) } +// ListWindowsDesktops returns all registered Windows desktop hosts. +func (c *Cache) ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error) { + rg, err := c.read() + if err != nil { + return nil, trace.Wrap(err) + } + defer rg.Release() + return rg.windowsDesktops.ListWindowsDesktops(ctx, req) +} + // ListResources is a part of auth.Cache implementation func (c *Cache) ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) { rg, err := c.read() diff --git a/lib/cache/cache_test.go b/lib/cache/cache_test.go index 56ab363baa454..462ed97418afc 100644 --- a/lib/cache/cache_test.go +++ b/lib/cache/cache_test.go @@ -239,6 +239,7 @@ func newPack(dir string, setupConfig func(c Config) Config, opts ...packOption) func TestCA(t *testing.T) { p := newPackForAuth(t) t.Cleanup(p.Close) + ctx := context.Background() ca := suite.NewTestCA(types.UserCA, "example.com") require.NoError(t, p.trustS.UpsertCertAuthority(ca)) @@ -249,7 +250,7 @@ func TestCA(t *testing.T) { t.Fatalf("timeout waiting for event") } - out, err := p.cache.GetCertAuthority(ca.GetID(), true) + out, err := p.cache.GetCertAuthority(ctx, ca.GetID(), true) require.NoError(t, err) ca.SetResourceID(out.GetResourceID()) require.Empty(t, cmp.Diff(ca, out)) @@ -263,7 +264,7 @@ func TestCA(t *testing.T) { t.Fatalf("timeout waiting for event") } - _, err = p.cache.GetCertAuthority(ca.GetID(), false) + _, err = p.cache.GetCertAuthority(ctx, ca.GetID(), false) require.True(t, trace.IsNotFound(err)) } @@ -576,7 +577,7 @@ func TestCompletenessInit(t *testing.T) { p.backend.SetReadError(nil) - cas, err := p.cache.GetCertAuthorities(types.UserCA, false) + cas, err := p.cache.GetCertAuthorities(ctx, types.UserCA, false) // we don't actually care whether the cache ever fully constructed // the CA list. for the purposes of this test, we just care that it // doesn't return the CA list *unless* it was successfully constructed. @@ -633,7 +634,7 @@ func TestCompletenessReset(t *testing.T) { require.NoError(t, err) // verify that CAs are immediately available - cas, err := p.cache.GetCertAuthorities(types.UserCA, false) + cas, err := p.cache.GetCertAuthorities(ctx, types.UserCA, false) require.NoError(t, err) require.Len(t, cas, caCount) @@ -644,7 +645,7 @@ func TestCompletenessReset(t *testing.T) { p.backend.SetReadError(nil) // load CAs while connection is bad - cas, err := p.cache.GetCertAuthorities(types.UserCA, false) + cas, err := p.cache.GetCertAuthorities(ctx, types.UserCA, false) // we don't actually care whether the cache ever fully constructed // the CA list. for the purposes of this test, we just care that it // doesn't return the CA list *unless* it was successfully constructed. @@ -696,7 +697,7 @@ func TestTombstones(t *testing.T) { require.NoError(t, err) // verify that CAs are immediately available - cas, err := p.cache.GetCertAuthorities(types.UserCA, false) + cas, err := p.cache.GetCertAuthorities(ctx, types.UserCA, false) require.NoError(t, err) require.Len(t, cas, caCount) @@ -732,7 +733,7 @@ func TestTombstones(t *testing.T) { // verify that CAs are immediately available despite the fact // that the origin state was never available. - cas, err = p.cache.GetCertAuthorities(types.UserCA, false) + cas, err = p.cache.GetCertAuthorities(ctx, types.UserCA, false) require.NoError(t, err) require.Len(t, cas, caCount) } @@ -976,7 +977,7 @@ func initStrategy(t *testing.T) { })) require.NoError(t, err) - _, err = p.cache.GetCertAuthorities(types.UserCA, false) + _, err = p.cache.GetCertAuthorities(ctx, types.UserCA, false) require.True(t, trace.IsConnectionProblem(err)) ca := suite.NewTestCA(types.UserCA, "example.com") @@ -998,7 +999,7 @@ func initStrategy(t *testing.T) { } _ = normalizeCA - out, err := p.cache.GetCertAuthority(ca.GetID(), false) + out, err := p.cache.GetCertAuthority(ctx, ca.GetID(), false) require.NoError(t, err) require.Empty(t, cmp.Diff(normalizeCA(ca), normalizeCA(out))) @@ -1012,7 +1013,7 @@ func initStrategy(t *testing.T) { expectNextEvent(t, p.eventsC, WatcherFailed, EventProcessed, Reloading) // backend is out, but old value is available - out2, err := p.cache.GetCertAuthority(ca.GetID(), false) + out2, err := p.cache.GetCertAuthority(ctx, ca.GetID(), false) require.NoError(t, err) require.Equal(t, out.GetResourceID(), out2.GetResourceID()) require.Empty(t, cmp.Diff(normalizeCA(ca), normalizeCA(out))) @@ -1030,7 +1031,7 @@ func initStrategy(t *testing.T) { expectEvent(t, p.eventsC, WatcherStarted) // new value is available now - out, err = p.cache.GetCertAuthority(ca.GetID(), false) + out, err = p.cache.GetCertAuthority(ctx, ca.GetID(), false) require.NoError(t, err) require.Empty(t, cmp.Diff(normalizeCA(ca), normalizeCA(out))) } @@ -1070,7 +1071,7 @@ func TestRecovery(t *testing.T) { t.Fatalf("timeout waiting for event") } - out, err := p.cache.GetCertAuthority(ca2.GetID(), false) + out, err := p.cache.GetCertAuthority(context.Background(), ca2.GetID(), false) require.NoError(t, err) ca2.SetResourceID(out.GetResourceID()) types.RemoveCASecrets(ca2) @@ -1405,7 +1406,7 @@ func TestRoles(t *testing.T) { p := newPackForNode(t) t.Cleanup(p.Close) - role, err := types.NewRole("role1", types.RoleSpecV5{ + role, err := types.NewRoleV3("role1", types.RoleSpecV5{ Options: types.RoleOptions{ MaxSessionTTL: types.Duration(time.Hour), }, diff --git a/lib/cache/collections.go b/lib/cache/collections.go index 4a7837b4fc738..3b08860466bcb 100644 --- a/lib/cache/collections.go +++ b/lib/cache/collections.go @@ -813,17 +813,17 @@ func (c *certAuthority) erase(ctx context.Context) error { } func (c *certAuthority) fetch(ctx context.Context) (apply func(ctx context.Context) error, err error) { - applyHostCAs, err := c.fetchCertAuthorities(types.HostCA) + applyHostCAs, err := c.fetchCertAuthorities(ctx, types.HostCA) if err != nil { return nil, trace.Wrap(err) } - applyUserCAs, err := c.fetchCertAuthorities(types.UserCA) + applyUserCAs, err := c.fetchCertAuthorities(ctx, types.UserCA) if err != nil { return nil, trace.Wrap(err) } - applyJWTSigners, err := c.fetchCertAuthorities(types.JWTSigner) + applyJWTSigners, err := c.fetchCertAuthorities(ctx, types.JWTSigner) if err != nil { return nil, trace.Wrap(err) } @@ -839,8 +839,8 @@ func (c *certAuthority) fetch(ctx context.Context) (apply func(ctx context.Conte }, nil } -func (c *certAuthority) fetchCertAuthorities(caType types.CertAuthType) (apply func(ctx context.Context) error, err error) { - authorities, err := c.Trust.GetCertAuthorities(caType, c.watch.LoadSecrets) +func (c *certAuthority) fetchCertAuthorities(ctx context.Context, caType types.CertAuthType) (apply func(ctx context.Context) error, err error) { + authorities, err := c.Trust.GetCertAuthorities(ctx, caType, c.watch.LoadSecrets) if err != nil { // DELETE IN: 5.1 // diff --git a/lib/cgroup/cgroup.go b/lib/cgroup/cgroup.go index 1ae2a1b85ea9d..8f3a07fd5d4cd 100644 --- a/lib/cgroup/cgroup.go +++ b/lib/cgroup/cgroup.go @@ -28,9 +28,7 @@ import ( "bufio" "bytes" "encoding/binary" - "io/ioutil" "os" - "path" "path/filepath" "regexp" "strconv" @@ -83,7 +81,7 @@ func New(config *Config) (*Service, error) { s := &Service{ Config: config, - teleportRoot: path.Join(config.MountPath, teleportRoot, uuid.New().String()), + teleportRoot: filepath.Join(config.MountPath, teleportRoot, uuid.New().String()), } // Mount the cgroup2 filesystem. @@ -93,7 +91,6 @@ func New(config *Config) (*Service, error) { } log.Debugf("Teleport session hierarchy mounted at: %v.", s.teleportRoot) - return s, nil } @@ -115,7 +112,7 @@ func (s *Service) Close() error { // Create will create a cgroup for a given session. func (s *Service) Create(sessionID string) error { - err := os.Mkdir(path.Join(s.teleportRoot, sessionID), fileMode) + err := os.Mkdir(filepath.Join(s.teleportRoot, sessionID), fileMode) if err != nil { return trace.Wrap(err) } @@ -126,33 +123,32 @@ func (s *Service) Create(sessionID string) error { // moved to the root controller. func (s *Service) Remove(sessionID string) error { // Read in all PIDs for the cgroup. - pids, err := readPids(path.Join(s.teleportRoot, sessionID, cgroupProcs)) + pids, err := readPids(filepath.Join(s.teleportRoot, sessionID, cgroupProcs)) if err != nil { return trace.Wrap(err) } // Move all PIDs to the root controller. This has to be done before a cgroup // can be removed. - err = writePids(path.Join(s.MountPath, cgroupProcs), pids) + err = writePids(filepath.Join(s.MountPath, cgroupProcs), pids) if err != nil { return trace.Wrap(err) } // The rmdir syscall is used to remove a cgroup. - err = unix.Rmdir(path.Join(s.teleportRoot, sessionID)) + err = unix.Rmdir(filepath.Join(s.teleportRoot, sessionID)) if err != nil { return trace.Wrap(err) } log.Debugf("Removed cgroup for session: %v.", sessionID) - return nil } // Place place a process in the cgroup for that session. func (s *Service) Place(sessionID string, pid int) error { // Open cgroup.procs file for the cgroup. - filepath := path.Join(s.teleportRoot, sessionID, cgroupProcs) + filepath := filepath.Join(s.teleportRoot, sessionID, cgroupProcs) f, err := os.OpenFile(filepath, os.O_APPEND|os.O_WRONLY, fileMode) if err != nil { return trace.Wrap(err) @@ -213,7 +209,7 @@ func (s *Service) cleanupHierarchy() error { var sessions []string // Recursively look within the Teleport hierarchy for cgroups for session. - err := filepath.Walk(path.Join(s.teleportRoot), func(path string, info os.FileInfo, _ error) error { + err := filepath.Walk(filepath.Join(s.teleportRoot), func(path string, info os.FileInfo, _ error) error { // Only pick up cgroup.procs files. if !pattern.MatchString(path) { return nil @@ -259,7 +255,7 @@ func (s *Service) mount() error { // Check if the Teleport root cgroup exists, if it does the cgroup filesystem // is already mounted, return right away. - files, err := ioutil.ReadDir(s.MountPath) + files, err := os.ReadDir(s.MountPath) if err == nil && len(files) > 0 { // Create cgroup that will hold Teleport sessions. err = os.MkdirAll(s.teleportRoot, fileMode) @@ -335,7 +331,7 @@ type fileHandle struct { // ID returns the cgroup ID for the given session. func (s *Service) ID(sessionID string) (uint64, error) { var fh fileHandle - path := path.Join(s.teleportRoot, sessionID) + path := filepath.Join(s.teleportRoot, sessionID) // Call the "name_to_handle_at" syscall directly (unix.NameToHandleAt is a // thin wrapper around the syscall) instead of calling the glibc wrapper. diff --git a/lib/cgroup/cgroup_test.go b/lib/cgroup/cgroup_test.go index 38329f48cd7cd..5f542efb1fbf2 100644 --- a/lib/cgroup/cgroup_test.go +++ b/lib/cgroup/cgroup_test.go @@ -20,7 +20,6 @@ limitations under the License. package cgroup import ( - "io/ioutil" "os" "path" "testing" @@ -51,7 +50,7 @@ func (s *Suite) TestCreate(c *check.C) { } // Create temporary directory where cgroup2 hierarchy will be mounted. - dir, err := ioutil.TempDir("", "cgroup-test") + dir, err := os.MkdirTemp("", "cgroup-test") c.Assert(err, check.IsNil) defer os.RemoveAll(dir) @@ -103,7 +102,7 @@ func (s *Suite) TestCleanup(c *check.C) { } // Create temporary directory where cgroup2 hierarchy will be mounted. - dir, err := ioutil.TempDir("", "cgroup-test") + dir, err := os.MkdirTemp("", "cgroup-test") c.Assert(err, check.IsNil) defer os.RemoveAll(dir) diff --git a/lib/client/api.go b/lib/client/api.go index 4c19cd4a3609e..1110d1aa5dcd2 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -17,7 +17,6 @@ limitations under the License. package client import ( - "bufio" "context" "crypto/tls" "crypto/x509" @@ -26,7 +25,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net" "net/url" "os" @@ -57,6 +55,7 @@ import ( apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/api/utils/keypaths" "github.com/gravitational/teleport/lib/auth" + wanlib "github.com/gravitational/teleport/lib/auth/webauthn" "github.com/gravitational/teleport/lib/client/terminal" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/events" @@ -73,6 +72,7 @@ import ( "github.com/gravitational/trace" + "github.com/duo-labs/webauthn/protocol" "github.com/jonboulle/clockwork" "github.com/sirupsen/logrus" ) @@ -172,6 +172,12 @@ type Config struct { // Remote host to connect Host string + // SearchKeywords host to connect + SearchKeywords []string + + // PredicateExpression host to connect + PredicateExpression string + // Labels represent host Labels Labels map[string]string @@ -349,6 +355,18 @@ type Config struct { // Invited is a list of people invited to a session. Invited []string + + // DisplayParticipantRequirements is set if debug information about participants requirements + // should be printed in moderated sessions. + DisplayParticipantRequirements bool + + // ExtraProxyHeaders is a collection of http headers to be included in requests to the WebProxy. + ExtraProxyHeaders map[string]string + + // Passwordless enables passwordless authentication for TeleportClient. + // Affects the TeleportClient.Login and, indirectly, RetryWithRelogin + // functions. + Passwordless bool } // CachePolicy defines cache policy for local clients @@ -466,7 +484,6 @@ func (p *ProfileStatus) DatabaseCertPathForCluster(clusterName string, databaseN // It's kept in /keys//-app//-x509.pem func (p *ProfileStatus) AppCertPath(name string) string { return keypaths.AppCertPath(p.Dir, p.Name, p.Username, p.Cluster, name) - } // KubeConfigPath returns path to the specified kubeconfig for this profile. @@ -516,8 +533,8 @@ func (p *ProfileStatus) AppNames() (result []string) { return result } -// RetryWithRelogin is a helper error handling method, -// attempts to relogin and retry the function once +// RetryWithRelogin is a helper error handling method, attempts to relogin and +// retry the function once. func RetryWithRelogin(ctx context.Context, tc *TeleportClient, fn func() error) error { err := fn() if err == nil { @@ -1244,7 +1261,12 @@ func (tc *TeleportClient) getTargetNodes(ctx context.Context, proxy *ProxyClient retval = make([]string, 0) ) if tc.Labels != nil && len(tc.Labels) > 0 { - nodes, err = proxy.FindServersByLabels(ctx, tc.Namespace, tc.Labels) + nodes, err = proxy.FindNodesByFilters(ctx, proto.ListResourcesRequest{ + Namespace: tc.Namespace, + Labels: tc.Labels, + SearchKeywords: tc.SearchKeywords, + PredicateExpression: tc.PredicateExpression, + }) if err != nil { return nil, trace.Wrap(err) } @@ -1504,7 +1526,7 @@ func (tc *TeleportClient) Join(ctx context.Context, mode types.SessionParticipan if sessionID.Check() != nil { return trace.Errorf("Invalid session ID format: %s", string(sessionID)) } - var notFoundErrorMessage = fmt.Sprintf("session '%s' not found or it has ended", sessionID) + notFoundErrorMessage := fmt.Sprintf("session '%s' not found or it has ended", sessionID) // connect to proxy: if !tc.Config.ProxySpecified() { @@ -1667,7 +1689,7 @@ func PlayFile(ctx context.Context, tarFile io.Reader, sid string) error { var sessionEvents []events.EventFields var stream []byte protoReader := events.NewProtoReader(tarFile) - playbackDir, err := ioutil.TempDir("", "playback") + playbackDir, err := os.MkdirTemp("", "playback") if err != nil { return trace.Wrap(err) } @@ -1895,17 +1917,8 @@ func isRemoteDest(name string) bool { return strings.ContainsRune(name, ':') } -// ListNodes returns a list of nodes connected to a proxy -func (tc *TeleportClient) ListNodes(ctx context.Context) ([]types.Server, error) { - var err error - // userhost is specified? that must be labels - if tc.Host != "" { - tc.Labels, err = ParseLabelSpec(tc.Host) - if err != nil { - return nil, trace.Wrap(err) - } - } - +// ListNodesWithFilters returns a list of nodes connected to a proxy +func (tc *TeleportClient) ListNodesWithFilters(ctx context.Context) ([]types.Server, error) { // connect to the proxy and ask it to return a full list of servers proxyClient, err := tc.ConnectToProxy(ctx) if err != nil { @@ -1913,23 +1926,48 @@ func (tc *TeleportClient) ListNodes(ctx context.Context) ([]types.Server, error) } defer proxyClient.Close() - return proxyClient.FindServersByLabels(ctx, tc.Namespace, tc.Labels) + servers, err := proxyClient.FindNodesByFilters(ctx, proto.ListResourcesRequest{ + Namespace: tc.Namespace, + Labels: tc.Labels, + SearchKeywords: tc.SearchKeywords, + PredicateExpression: tc.PredicateExpression, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + return servers, nil } -// ListAppServers returns a list of application servers. -func (tc *TeleportClient) ListAppServers(ctx context.Context) ([]types.AppServer, error) { +// ListAppServersWithFilters returns a list of application servers. +func (tc *TeleportClient) ListAppServersWithFilters(ctx context.Context, customFilter *proto.ListResourcesRequest) ([]types.AppServer, error) { proxyClient, err := tc.ConnectToProxy(ctx) if err != nil { return nil, trace.Wrap(err) } defer proxyClient.Close() - return proxyClient.GetAppServers(ctx, tc.Namespace) + filter := customFilter + if customFilter == nil { + filter = &proto.ListResourcesRequest{ + Namespace: tc.Namespace, + Labels: tc.Labels, + SearchKeywords: tc.SearchKeywords, + PredicateExpression: tc.PredicateExpression, + } + } + + servers, err := proxyClient.FindAppServersByFilters(ctx, *filter) + if err != nil { + return nil, trace.Wrap(err) + } + + return servers, nil } // ListApps returns all registered applications. -func (tc *TeleportClient) ListApps(ctx context.Context) ([]types.Application, error) { - servers, err := tc.ListAppServers(ctx) +func (tc *TeleportClient) ListApps(ctx context.Context, customFilter *proto.ListResourcesRequest) ([]types.Application, error) { + servers, err := tc.ListAppServersWithFilters(ctx, customFilter) if err != nil { return nil, trace.Wrap(err) } @@ -1960,19 +1998,35 @@ func (tc *TeleportClient) DeleteAppSession(ctx context.Context, sessionID string return proxyClient.DeleteAppSession(ctx, sessionID) } -// ListDatabaseServers returns all registered database proxy servers. -func (tc *TeleportClient) ListDatabaseServers(ctx context.Context) ([]types.DatabaseServer, error) { +// ListDatabaseServersWithFilters returns all registered database proxy servers. +func (tc *TeleportClient) ListDatabaseServersWithFilters(ctx context.Context, customFilter *proto.ListResourcesRequest) ([]types.DatabaseServer, error) { proxyClient, err := tc.ConnectToProxy(ctx) if err != nil { return nil, trace.Wrap(err) } defer proxyClient.Close() - return proxyClient.GetDatabaseServers(ctx, tc.Namespace) + + filter := customFilter + if customFilter == nil { + filter = &proto.ListResourcesRequest{ + Namespace: tc.Namespace, + Labels: tc.Labels, + SearchKeywords: tc.SearchKeywords, + PredicateExpression: tc.PredicateExpression, + } + } + + servers, err := proxyClient.FindDatabaseServersByFilters(ctx, *filter) + if err != nil { + return nil, trace.Wrap(err) + } + + return servers, nil } // ListDatabases returns all registered databases. -func (tc *TeleportClient) ListDatabases(ctx context.Context) ([]types.Database, error) { - servers, err := tc.ListDatabaseServers(ctx) +func (tc *TeleportClient) ListDatabases(ctx context.Context, customFilter *proto.ListResourcesRequest) ([]types.Database, error) { + servers, err := tc.ListDatabaseServersWithFilters(ctx, customFilter) if err != nil { return nil, trace.Wrap(err) } @@ -1991,13 +2045,15 @@ func (tc *TeleportClient) ListAllNodes(ctx context.Context) ([]types.Server, err } defer proxyClient.Close() - return proxyClient.FindServersByLabels(ctx, tc.Namespace, nil) + return proxyClient.FindNodesByFilters(ctx, proto.ListResourcesRequest{ + Namespace: tc.Namespace, + }) } // runCommandOnNodes executes a given bash command on a bunch of remote nodes. func (tc *TeleportClient) runCommandOnNodes( - ctx context.Context, siteName string, nodeAddresses []string, proxyClient *ProxyClient, command []string) error { - + ctx context.Context, siteName string, nodeAddresses []string, proxyClient *ProxyClient, command []string, +) error { resultsC := make(chan error, len(nodeAddresses)) for _, address := range nodeAddresses { go func(address string) { @@ -2064,7 +2120,7 @@ func (tc *TeleportClient) runShell(ctx context.Context, nodeClient *NodeClient, env := make(map[string]string) env[teleport.EnvSSHJoinMode] = string(mode) env[teleport.EnvSSHSessionReason] = tc.Config.Reason - + env[teleport.EnvSSHSessionDisplayParticipantRequirements] = strconv.FormatBool(tc.Config.DisplayParticipantRequirements) encoded, err := json.Marshal(&tc.Config.Invited) if err != nil { return trace.Wrap(err) @@ -2380,14 +2436,8 @@ func (tc *TeleportClient) LogoutAll() error { return nil } -// Login logs the user into a Teleport cluster by talking to a Teleport proxy. -// -// The returned Key should typically be passed to ActivateKey in order to -// update local agent state. -// -func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) { - // Ping the endpoint to see if it's up and find the type of authentication - // supported. +// PingAndShowMOTD pings the Teleport Proxy and displays the Message Of The Day if it's available. +func (tc *TeleportClient) PingAndShowMOTD(ctx context.Context) (*webclient.PingResponse, error) { pr, err := tc.Ping(ctx) if err != nil { return nil, trace.Wrap(err) @@ -2399,6 +2449,22 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) { return nil, trace.Wrap(err) } } + return pr, nil +} + +// Login logs the user into a Teleport cluster by talking to a Teleport proxy. +// +// If tc.Passwordless is set, then the passwordless authentication flow is used. +// +// The returned Key should typically be passed to ActivateKey in order to +// update local agent state. +func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) { + // Ping the endpoint to see if it's up and find the type of authentication + // supported, also show the message of the day if available. + pr, err := tc.PingAndShowMOTD(ctx) + if err != nil { + return nil, trace.Wrap(err) + } // generate a new keypair. the public key will be signed via proxy if client's // password+OTP are valid @@ -2409,13 +2475,27 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) { var response *auth.SSHLoginResponse - switch pr.Auth.Type { - case constants.Local: + switch authType := pr.Auth.Type; { + case tc.Passwordless: // Takes precedence over other methods if set. + // Do a few sanity checks before obeying. + switch { + case authType != constants.Local: + return nil, trace.BadParameter("Passwordless is only available for local authentication") + case pr.Auth.Webauthn == nil: + return nil, trace.BadParameter( + "Webauthn is not configured in this cluster, please contact your administrator and ask them to follow https://goteleport.com/docs/access-controls/guides/webauthn/") + } + response, err = tc.pwdlessLogin(ctx, key.Pub) + if err != nil { + return nil, trace.Wrap(err) + } + tc.Username = response.Username + case authType == constants.Local: response, err = tc.localLogin(ctx, pr.Auth.SecondFactor, key.Pub) if err != nil { return nil, trace.Wrap(err) } - case constants.OIDC: + case authType == constants.OIDC: response, err = tc.ssoLogin(ctx, pr.Auth.OIDC.Name, key.Pub, constants.OIDC) if err != nil { return nil, trace.Wrap(err) @@ -2425,7 +2505,7 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) { if tc.localAgent != nil { tc.localAgent.username = response.Username } - case constants.SAML: + case authType == constants.SAML: response, err = tc.ssoLogin(ctx, pr.Auth.SAML.Name, key.Pub, constants.SAML) if err != nil { return nil, trace.Wrap(err) @@ -2435,7 +2515,7 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) { if tc.localAgent != nil { tc.localAgent.username = response.Username } - case constants.Github: + case authType == constants.Github: response, err = tc.ssoLogin(ctx, pr.Auth.Github.Name, key.Pub, constants.Github) if err != nil { return nil, trace.Wrap(err) @@ -2476,6 +2556,195 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) { return key, nil } +type pwdlessPrompt struct { + Out io.Writer +} + +func (l pwdlessPrompt) PromptPIN() (string, error) { + fmt.Fprintln(l.Out, "Enter your security key PIN:") + pwd, err := passwordFromConsoleFn() + if err != nil { + fmt.Fprintln(l.Out, err) + return "", trace.Wrap(err) + } + return pwd, nil +} + +func (l pwdlessPrompt) PromptAdditionalTouch() error { + fmt.Fprintln(l.Out, "Tap your security key again to complete login") + return nil +} + +func (tc *TeleportClient) pwdlessLogin(ctx context.Context, pubKey []byte) (*auth.SSHLoginResponse, error) { + webClient, webURL, err := initClient(tc.WebProxyAddr, tc.InsecureSkipVerify, loopbackPool(tc.WebProxyAddr)) + if err != nil { + return nil, trace.Wrap(err) + } + + challengeJSON, err := webClient.PostJSON( + ctx, webClient.Endpoint("webapi", "mfa", "login", "begin"), + &MFAChallengeRequest{ + Passwordless: true, + }) + if err != nil { + return nil, trace.Wrap(err) + } + challenge := &MFAAuthenticateChallenge{} + if err := json.Unmarshal(challengeJSON.Bytes(), challenge); err != nil { + return nil, trace.Wrap(err) + } + // Sanity check WebAuthn challenge. + switch { + case challenge.WebauthnChallenge == nil: + return nil, trace.BadParameter("passwordless: webauthn challenge missing") + case challenge.WebauthnChallenge.Response.UserVerification == protocol.VerificationDiscouraged: + return nil, trace.BadParameter("passwordless: user verification requirement too lax (%v)", challenge.WebauthnChallenge.Response.UserVerification) + } + + prompt := pwdlessPrompt{Out: tc.Stderr} + fmt.Fprintln(tc.Stderr, "Tap your security key") + mfaResp, _, err := prompts.Webauthn(ctx, webURL.String(), tc.Username, challenge.WebauthnChallenge, prompt) + if err != nil { + return nil, trace.Wrap(err) + } + + loginRespJSON, err := webClient.PostJSON( + ctx, webClient.Endpoint("webapi", "mfa", "login", "finish"), + &AuthenticateSSHUserRequest{ + User: "", // User carried on WebAuthn assertion. + WebauthnChallengeResponse: wanlib.CredentialAssertionResponseFromProto(mfaResp.GetWebauthn()), + PubKey: pubKey, + TTL: tc.KeyTTL, + Compatibility: tc.CertificateFormat, + RouteToCluster: tc.SiteName, + KubernetesCluster: tc.KubernetesCluster, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + loginResp := &auth.SSHLoginResponse{} + if err := json.Unmarshal(loginRespJSON.Bytes(), loginResp); err != nil { + return nil, trace.Wrap(err) + } + return loginResp, nil +} + +func (tc *TeleportClient) localLogin(ctx context.Context, secondFactor constants.SecondFactorType, pub []byte) (*auth.SSHLoginResponse, error) { + var err error + var response *auth.SSHLoginResponse + + // TODO(awly): mfa: ideally, clients should always go through mfaLocalLogin + // (with a nop MFA challenge if no 2nd factor is required). That way we can + // deprecate the direct login endpoint. + switch secondFactor { + case constants.SecondFactorOff, constants.SecondFactorOTP: + response, err = tc.directLogin(ctx, secondFactor, pub) + if err != nil { + return nil, trace.Wrap(err) + } + case constants.SecondFactorU2F, constants.SecondFactorWebauthn, constants.SecondFactorOn, constants.SecondFactorOptional: + response, err = tc.mfaLocalLogin(ctx, pub) + if err != nil { + return nil, trace.Wrap(err) + } + default: + return nil, trace.BadParameter("unsupported second factor type: %q", secondFactor) + } + + return response, nil +} + +// directLogin asks for a password + HOTP token, makes a request to CA via proxy +func (tc *TeleportClient) directLogin(ctx context.Context, secondFactorType constants.SecondFactorType, pub []byte) (*auth.SSHLoginResponse, error) { + password, err := tc.AskPassword() + if err != nil { + return nil, trace.Wrap(err) + } + + // only ask for a second factor if it's enabled + var otpToken string + if secondFactorType == constants.SecondFactorOTP { + otpToken, err = tc.AskOTP() + if err != nil { + return nil, trace.Wrap(err) + } + } + + // ask the CA (via proxy) to sign our public key: + response, err := SSHAgentLogin(ctx, SSHLoginDirect{ + SSHLogin: SSHLogin{ + ProxyAddr: tc.WebProxyAddr, + PubKey: pub, + TTL: tc.KeyTTL, + Insecure: tc.InsecureSkipVerify, + Pool: loopbackPool(tc.WebProxyAddr), + Compatibility: tc.CertificateFormat, + RouteToCluster: tc.SiteName, + KubernetesCluster: tc.KubernetesCluster, + }, + User: tc.Config.Username, + Password: password, + OTPToken: otpToken, + }) + + return response, trace.Wrap(err) +} + +// mfaLocalLogin asks for a password and performs the challenge-response authentication +func (tc *TeleportClient) mfaLocalLogin(ctx context.Context, pub []byte) (*auth.SSHLoginResponse, error) { + password, err := tc.AskPassword() + if err != nil { + return nil, trace.Wrap(err) + } + + response, err := SSHAgentMFALogin(ctx, SSHLoginMFA{ + SSHLogin: SSHLogin{ + ProxyAddr: tc.WebProxyAddr, + PubKey: pub, + TTL: tc.KeyTTL, + Insecure: tc.InsecureSkipVerify, + Pool: loopbackPool(tc.WebProxyAddr), + Compatibility: tc.CertificateFormat, + RouteToCluster: tc.SiteName, + KubernetesCluster: tc.KubernetesCluster, + }, + User: tc.Config.Username, + Password: password, + }) + + return response, trace.Wrap(err) +} + +// SSOLoginFunc is a function used in tests to mock SSO logins. +type SSOLoginFunc func(ctx context.Context, connectorID string, pub []byte, protocol string) (*auth.SSHLoginResponse, error) + +// samlLogin opens browser window and uses OIDC or SAML redirect cycle with browser +func (tc *TeleportClient) ssoLogin(ctx context.Context, connectorID string, pub []byte, protocol string) (*auth.SSHLoginResponse, error) { + if tc.MockSSOLogin != nil { + // sso login response is being mocked for testing purposes + return tc.MockSSOLogin(ctx, connectorID, pub, protocol) + } + // ask the CA (via proxy) to sign our public key: + response, err := SSHAgentSSOLogin(ctx, SSHLoginSSO{ + SSHLogin: SSHLogin{ + ProxyAddr: tc.WebProxyAddr, + PubKey: pub, + TTL: tc.KeyTTL, + Insecure: tc.InsecureSkipVerify, + Pool: loopbackPool(tc.WebProxyAddr), + Compatibility: tc.CertificateFormat, + RouteToCluster: tc.SiteName, + KubernetesCluster: tc.KubernetesCluster, + }, + ConnectorID: connectorID, + Protocol: protocol, + BindAddr: tc.BindAddr, + Browser: tc.Browser, + }) + return response, trace.Wrap(err) +} + // ActivateKey saves the target session cert into the local // keystore (and into the ssh-agent) for future use. func (tc *TeleportClient) ActivateKey(ctx context.Context, key *Key) error { @@ -2534,12 +2803,13 @@ func (tc *TeleportClient) Ping(ctx context.Context) (*webclient.PingResponse, er if tc.lastPing != nil { return tc.lastPing, nil } - pr, err := webclient.Ping( - ctx, - tc.WebProxyAddr, - tc.InsecureSkipVerify, - loopbackPool(tc.WebProxyAddr), - tc.AuthConnector) + pr, err := webclient.Ping(&webclient.Config{ + Context: ctx, + ProxyAddr: tc.WebProxyAddr, + Insecure: tc.InsecureSkipVerify, + Pool: loopbackPool(tc.WebProxyAddr), + ConnectorName: tc.AuthConnector, + ExtraHeaders: tc.ExtraProxyHeaders}) if err != nil { return nil, trace.Wrap(err) } @@ -2547,7 +2817,7 @@ func (tc *TeleportClient) Ping(ctx context.Context) (*webclient.PingResponse, er // If version checking was requested and the server advertises a minimum version. if tc.CheckVersions && pr.MinClientVersion != "" { if err := utils.CheckVersion(teleport.Version, pr.MinClientVersion); err != nil && trace.IsBadParameter(err) { - fmt.Printf(` + fmt.Fprintf(tc.Config.Stderr, ` WARNING Detected potentially incompatible client and server versions. Minimum client version supported by the server is %v but you are using %v. @@ -2571,22 +2841,25 @@ func (tc *TeleportClient) Ping(ctx context.Context) (*webclient.PingResponse, er // confirmation from the user. func (tc *TeleportClient) ShowMOTD(ctx context.Context) error { motd, err := webclient.GetMOTD( - ctx, - tc.WebProxyAddr, - tc.InsecureSkipVerify, - loopbackPool(tc.WebProxyAddr)) + &webclient.Config{ + Context: ctx, + ProxyAddr: tc.WebProxyAddr, + Insecure: tc.InsecureSkipVerify, + Pool: loopbackPool(tc.WebProxyAddr), + ExtraHeaders: tc.ExtraProxyHeaders}) + if err != nil { return trace.Wrap(err) } if motd.Text != "" { - fmt.Printf("%s\nPress [ENTER] to continue.\n", motd.Text) + fmt.Fprintf(tc.Config.Stderr, "%s\nPress [ENTER] to continue.\n", motd.Text) // We're re-using the password reader for user acknowledgment for // aesthetic purposes, because we want to hide any garbage the // use might enter at the prompt. Whatever the user enters will // be simply discarded, and the user can still CTRL+C out if they // disagree. - _, err := passwordFromConsole() + _, err := passwordFromConsoleFn() if err != nil { return trace.Wrap(err) } @@ -2615,7 +2888,7 @@ func (tc *TeleportClient) GetTrustedCA(ctx context.Context, clusterName string) } // Get the list of host certificates that this cluster knows about. - return clt.GetCertAuthorities(types.HostCA, false) + return clt.GetCertAuthorities(ctx, types.HostCA, false) } // UpdateTrustedCA connects to the Auth Server and fetches all host certificates @@ -2799,31 +3072,6 @@ func (tc *TeleportClient) applyProxySettings(proxySettings webclient.ProxySettin return nil } -func (tc *TeleportClient) localLogin(ctx context.Context, secondFactor constants.SecondFactorType, pub []byte) (*auth.SSHLoginResponse, error) { - var err error - var response *auth.SSHLoginResponse - - // TODO(awly): mfa: ideally, clients should always go through mfaLocalLogin - // (with a nop MFA challenge if no 2nd factor is required). That way we can - // deprecate the direct login endpoint. - switch secondFactor { - case constants.SecondFactorOff, constants.SecondFactorOTP: - response, err = tc.directLogin(ctx, secondFactor, pub) - if err != nil { - return nil, trace.Wrap(err) - } - case constants.SecondFactorU2F, constants.SecondFactorWebauthn, constants.SecondFactorOn, constants.SecondFactorOptional: - response, err = tc.mfaLocalLogin(ctx, pub) - if err != nil { - return nil, trace.Wrap(err) - } - default: - return nil, trace.BadParameter("unsupported second factor type: %q", secondFactor) - } - - return response, nil -} - // AddTrustedCA adds a new CA as trusted CA for this client, used in tests func (tc *TeleportClient) AddTrustedCA(ca types.CertAuthority) error { if tc.localAgent == nil { @@ -2857,96 +3105,6 @@ func (tc *TeleportClient) AddKey(key *Key) (*agent.AddedKey, error) { return tc.localAgent.AddKey(key) } -// directLogin asks for a password + HOTP token, makes a request to CA via proxy -func (tc *TeleportClient) directLogin(ctx context.Context, secondFactorType constants.SecondFactorType, pub []byte) (*auth.SSHLoginResponse, error) { - password, err := tc.AskPassword() - if err != nil { - return nil, trace.Wrap(err) - } - - // only ask for a second factor if it's enabled - var otpToken string - if secondFactorType == constants.SecondFactorOTP { - otpToken, err = tc.AskOTP() - if err != nil { - return nil, trace.Wrap(err) - } - } - - // ask the CA (via proxy) to sign our public key: - response, err := SSHAgentLogin(ctx, SSHLoginDirect{ - SSHLogin: SSHLogin{ - ProxyAddr: tc.WebProxyAddr, - PubKey: pub, - TTL: tc.KeyTTL, - Insecure: tc.InsecureSkipVerify, - Pool: loopbackPool(tc.WebProxyAddr), - Compatibility: tc.CertificateFormat, - RouteToCluster: tc.SiteName, - KubernetesCluster: tc.KubernetesCluster, - }, - User: tc.Config.Username, - Password: password, - OTPToken: otpToken, - }) - - return response, trace.Wrap(err) -} - -// SSOLoginFunc is a function used in tests to mock SSO logins. -type SSOLoginFunc func(ctx context.Context, connectorID string, pub []byte, protocol string) (*auth.SSHLoginResponse, error) - -// samlLogin opens browser window and uses OIDC or SAML redirect cycle with browser -func (tc *TeleportClient) ssoLogin(ctx context.Context, connectorID string, pub []byte, protocol string) (*auth.SSHLoginResponse, error) { - if tc.MockSSOLogin != nil { - // sso login response is being mocked for testing purposes - return tc.MockSSOLogin(ctx, connectorID, pub, protocol) - } - // ask the CA (via proxy) to sign our public key: - response, err := SSHAgentSSOLogin(ctx, SSHLoginSSO{ - SSHLogin: SSHLogin{ - ProxyAddr: tc.WebProxyAddr, - PubKey: pub, - TTL: tc.KeyTTL, - Insecure: tc.InsecureSkipVerify, - Pool: loopbackPool(tc.WebProxyAddr), - Compatibility: tc.CertificateFormat, - RouteToCluster: tc.SiteName, - KubernetesCluster: tc.KubernetesCluster, - }, - ConnectorID: connectorID, - Protocol: protocol, - BindAddr: tc.BindAddr, - Browser: tc.Browser, - }) - return response, trace.Wrap(err) -} - -// mfaLocalLogin asks for a password and performs the challenge-response authentication -func (tc *TeleportClient) mfaLocalLogin(ctx context.Context, pub []byte) (*auth.SSHLoginResponse, error) { - password, err := tc.AskPassword() - if err != nil { - return nil, trace.Wrap(err) - } - - response, err := SSHAgentMFALogin(ctx, SSHLoginMFA{ - SSHLogin: SSHLogin{ - ProxyAddr: tc.WebProxyAddr, - PubKey: pub, - TTL: tc.KeyTTL, - Insecure: tc.InsecureSkipVerify, - Pool: loopbackPool(tc.WebProxyAddr), - Compatibility: tc.CertificateFormat, - RouteToCluster: tc.SiteName, - KubernetesCluster: tc.KubernetesCluster, - }, - User: tc.Config.Username, - Password: password, - }) - - return response, trace.Wrap(err) -} - // SendEvent adds a events.EventFields to the channel. func (tc *TeleportClient) SendEvent(ctx context.Context, e events.EventFields) error { // Try and send the event to the eventsCh. If blocking, keep blocking until @@ -2976,7 +3134,7 @@ func loopbackPool(proxyAddr string) *x509.CertPool { certPool := x509.NewCertPool() certPath := filepath.Join(defaults.DataDir, defaults.SelfSignedCertPath) - pemByte, err := ioutil.ReadFile(certPath) + pemByte, err := os.ReadFile(certPath) if err != nil { log.Debugf("could not open any path in: %v", certPath) return nil @@ -3034,8 +3192,8 @@ func Username() (string, error) { // AskOTP prompts the user to enter the OTP token. func (tc *TeleportClient) AskOTP() (token string, err error) { - fmt.Printf("Enter your OTP token:\n") - token, err = lineFromConsole() + fmt.Fprintf(tc.Config.Stderr, "Enter your OTP token:\n") + token, err = passwordFromConsoleFn() if err != nil { fmt.Fprintln(tc.Stderr, err) return "", trace.Wrap(err) @@ -3045,7 +3203,7 @@ func (tc *TeleportClient) AskOTP() (token string, err error) { // AskPassword prompts the user to enter the password func (tc *TeleportClient) AskPassword() (pwd string, err error) { - fmt.Printf("Enter password for Teleport user %v:\n", tc.Config.Username) + fmt.Fprintf(tc.Config.Stderr, "Enter password for Teleport user %v:\n", tc.Config.Username) pwd, err = passwordFromConsoleFn() if err != nil { fmt.Fprintln(tc.Stderr, err) @@ -3157,19 +3315,13 @@ func passwordFromConsole() (string, error) { return string(bytes), err } -// lineFromConsole reads a line from stdin -func lineFromConsole() (string, error) { - bytes, _, err := bufio.NewReader(os.Stdin).ReadLine() - return string(bytes), err -} - // ParseLabelSpec parses a string like 'name=value,"long name"="quoted value"` into a map like // { "name" -> "value", "long name" -> "quoted value" } func ParseLabelSpec(spec string) (map[string]string, error) { tokens := []string{} - var openQuotes = false + openQuotes := false var tokenStart, assignCount int - var specLen = len(spec) + specLen := len(spec) // tokenize the label spec: for i, ch := range spec { endOfToken := false @@ -3207,6 +3359,44 @@ func ParseLabelSpec(spec string) (map[string]string, error) { return labels, nil } +// ParseSearchKeywords parses a string ie: foo,bar,"quoted value"` into a slice of +// strings: ["foo", "bar", "quoted value"]. +// Almost a replica to ParseLabelSpec, but with few modifications such as +// allowing a custom delimiter. Defaults to comma delimiter if not defined. +func ParseSearchKeywords(spec string, customDelimiter rune) []string { + delimiter := customDelimiter + if customDelimiter == 0 { + delimiter = rune(',') + } + + tokens := []string{} + openQuotes := false + var tokenStart int + specLen := len(spec) + // tokenize the label search: + for i, ch := range spec { + endOfToken := false + if i+utf8.RuneLen(ch) == specLen { + i += utf8.RuneLen(ch) + endOfToken = true + } + switch ch { + case '"': + openQuotes = !openQuotes + case delimiter: + if !openQuotes { + endOfToken = true + } + } + if endOfToken && i > tokenStart { + tokens = append(tokens, strings.TrimSpace(strings.Trim(spec[tokenStart:i], `"`))) + tokenStart = i + 1 + } + } + + return tokens +} + // Executes the given command on the client machine (localhost). If no command is given, // executes shell func runLocalCommand(command []string) error { @@ -3343,7 +3533,7 @@ func playSession(sessionEvents []events.EventFields, stream []byte) error { } } - var errorCh = make(chan error) + errorCh := make(chan error) player := newSessionPlayer(sessionEvents, stream, term) // keys: const ( diff --git a/lib/client/api_login_test.go b/lib/client/api_login_test.go index 065b274eee9bf..e9201cc5d2b76 100644 --- a/lib/client/api_login_test.go +++ b/lib/client/api_login_test.go @@ -43,10 +43,11 @@ import ( "github.com/stretchr/testify/require" wanlib "github.com/gravitational/teleport/lib/auth/webauthn" + wancli "github.com/gravitational/teleport/lib/auth/webauthncli" log "github.com/sirupsen/logrus" ) -func TestTeleportClient_Login_localMFALogin(t *testing.T) { +func TestTeleportClient_Login_local(t *testing.T) { // Silence logging during this test. lvl := log.GetLevel() t.Cleanup(func() { @@ -60,6 +61,7 @@ func TestTeleportClient_Login_localMFALogin(t *testing.T) { sa := newStandaloneTeleport(t, clock) username := sa.Username password := sa.Password + webID := sa.WebAuthnID device := sa.Device otpKey := sa.OTPKey @@ -110,6 +112,13 @@ func TestTeleportClient_Login_localMFALogin(t *testing.T) { }, }, nil } + solvePwdless := func(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error) { + resp, err := solveWebauthn(ctx, origin, assertion) + if err == nil { + resp.GetWebauthn().Response.UserHandle = webID + } + return resp, err + } ctx := context.Background() tests := []struct { @@ -117,19 +126,27 @@ func TestTeleportClient_Login_localMFALogin(t *testing.T) { secondFactor constants.SecondFactorType solveOTP func(context.Context) (string, error) solveWebauthn func(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error) + pwdless bool }{ { - name: "OK OTP device login", + name: "OTP device login", secondFactor: constants.SecondFactorOptional, solveOTP: solveOTP, solveWebauthn: promptWebauthnNoop, }, { - name: "OK Webauthn device login", + name: "WebAuthn device login", secondFactor: constants.SecondFactorOptional, solveOTP: promptOTPNoop, solveWebauthn: solveWebauthn, }, + { + name: "passwordless login", + secondFactor: constants.SecondFactorOptional, + solveOTP: promptOTPNoop, + solveWebauthn: solvePwdless, + pwdless: true, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -137,11 +154,12 @@ func TestTeleportClient_Login_localMFALogin(t *testing.T) { defer cancel() client.Prompts.Swap( - func(ctx context.Context, out io.Writer, in *prompt.ContextReader, question string) (string, error) { + func(ctx context.Context, out io.Writer, in prompt.Reader, question string) (string, error) { return test.solveOTP(ctx) }, - func(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error) { - return test.solveWebauthn(ctx, origin, assertion) + func(ctx context.Context, origin, _ string, assertion *wanlib.CredentialAssertion, _ wancli.LoginPrompt) (*proto.MFAAuthenticateResponse, string, error) { + resp, err := test.solveWebauthn(ctx, origin, assertion) + return resp, "", err }, ) @@ -155,6 +173,7 @@ func TestTeleportClient_Login_localMFALogin(t *testing.T) { tc, err := client.NewClient(cfg) require.NoError(t, err) + tc.Passwordless = test.pwdless clock.Advance(30 * time.Second) _, err = tc.Login(ctx) @@ -166,6 +185,7 @@ func TestTeleportClient_Login_localMFALogin(t *testing.T) { type standaloneBundle struct { AuthAddr, ProxyWebAddr string Username, Password string + WebAuthnID []byte Device *mocku2f.Key OTPKey string Auth, Proxy *service.TeleportProcess @@ -186,7 +206,7 @@ func newStandaloneTeleport(t *testing.T, clock clockwork.Clock) *standaloneBundl user, err := types.NewUser("llama") require.NoError(t, err) - role, err := types.NewRole(user.GetName(), types.RoleSpecV5{ + role, err := types.NewRoleV3(user.GetName(), types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{user.GetName()}, }, @@ -244,14 +264,18 @@ func newStandaloneTeleport(t *testing.T, clock clockwork.Clock) *standaloneBundl require.NoError(t, err) tokenID := token.GetName() res, err := authServer.CreateRegisterChallenge(ctx, &proto.CreateRegisterChallengeRequest{ - TokenID: tokenID, - DeviceType: proto.DeviceType_DEVICE_TYPE_WEBAUTHN, + TokenID: tokenID, + DeviceType: proto.DeviceType_DEVICE_TYPE_WEBAUTHN, + DeviceUsage: proto.DeviceUsage_DEVICE_USAGE_PASSWORDLESS, }) require.NoError(t, err) + cc := wanlib.CredentialCreationFromProto(res.GetWebauthn()) + webID := cc.Response.User.ID device, err := mocku2f.Create() require.NoError(t, err) + device.SetPasswordless() const origin = "https://localhost" - ccr, err := device.SignCredentialCreation(origin, wanlib.CredentialCreationFromProto(res.GetWebauthn())) + ccr, err := device.SignCredentialCreation(origin, cc) require.NoError(t, err) _, err = authServer.ChangeUserAuthentication(ctx, &proto.ChangeUserAuthenticationRequest{ TokenID: tokenID, @@ -296,6 +320,7 @@ func newStandaloneTeleport(t *testing.T, clock clockwork.Clock) *standaloneBundl ProxyWebAddr: proxyWebAddr.String(), Username: username, Password: password, + WebAuthnID: webID, Device: device, OTPKey: otpKey, Auth: authProcess, diff --git a/lib/client/api_test.go b/lib/client/api_test.go index dd2fde21226ba..a8e524e54ae81 100644 --- a/lib/client/api_test.go +++ b/lib/client/api_test.go @@ -34,8 +34,7 @@ func TestMain(m *testing.M) { } // register test suite -type APITestSuite struct { -} +type APITestSuite struct{} // bootstrap check func TestClientAPI(t *testing.T) { check.TestingT(t) } @@ -277,7 +276,6 @@ func (s *APITestSuite) TestPortsParsing(c *check.C) { } func (s *APITestSuite) TestDynamicPortsParsing(c *check.C) { - tests := []struct { spec []string isError bool @@ -500,3 +498,61 @@ func TestApplyProxySettings(t *testing.T) { }) } } + +func TestParseSearchKeywords(t *testing.T) { + t.Parallel() + + expected := [][]string{ + {}, + {"foo"}, + {"foo,bar", "some phrase's", "baz=qux's", "some other phrase", "another one"}, + {"服务器环境=测试,操作系统类别", "Linux", "机房=华北"}, + } + + testCases := []struct { + name string + delimiter rune + specs []string + }{ + { + name: "with comma delimiter", + delimiter: ',', + specs: []string{ + "", + "foo", + `"foo,bar","some phrase's",baz=qux's ,"some other phrase"," another one "`, + `"服务器环境=测试,操作系统类别", Linux , 机房=华北 `, + }, + }, + { + name: "with 0 value delimiter (fallback to comma)", + specs: []string{ + "", + "foo", + `"foo,bar","some phrase's",baz=qux's ,"some other phrase"," another one "`, + `"服务器环境=测试,操作系统类别", Linux , 机房=华北 `, + }, + }, + { + name: "with space delimiter", + delimiter: ' ', + specs: []string{ + "", + "foo", + `foo,bar "some phrase's" baz=qux's "some other phrase" " another one "`, + `服务器环境=测试,操作系统类别 Linux 机房=华北 `, + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + for i, spec := range tc.specs { + m := ParseSearchKeywords(spec, tc.delimiter) + require.Equal(t, expected[i], m) + } + }) + } +} diff --git a/lib/client/client.go b/lib/client/client.go index 3c0a2e59340ef..56089e0475d28 100644 --- a/lib/client/client.go +++ b/lib/client/client.go @@ -24,7 +24,6 @@ import ( "encoding/json" "errors" "io" - "io/ioutil" "net" "os" "strconv" @@ -598,6 +597,8 @@ func (proxy *ProxyClient) isAuthBoring(ctx context.Context) (bool, error) { // // A server is matched when ALL labels match. // If no labels are passed, ALL nodes are returned. +// +// DELETE IN 11.0.0 replaced by FindNodesByFilters. func (proxy *ProxyClient) FindServersByLabels(ctx context.Context, namespace string, labels map[string]string) ([]types.Server, error) { if namespace == "" { return nil, trace.BadParameter(auth.MissingNamespaceError) @@ -609,14 +610,66 @@ func (proxy *ProxyClient) FindServersByLabels(ctx context.Context, namespace str return auth.GetNodesWithLabels(ctx, site, namespace, labels) } -// GetAppServers returns a list of application servers. -func (proxy *ProxyClient) GetAppServers(ctx context.Context, namespace string) ([]types.AppServer, error) { +// FindServersByFilters returns list of the nodes which have filters matched. +func (proxy *ProxyClient) FindNodesByFilters(ctx context.Context, req proto.ListResourcesRequest) ([]types.Server, error) { + req.ResourceType = types.KindNode + + site, err := proxy.CurrentClusterAccessPoint(ctx, false) + if err != nil { + return nil, trace.Wrap(err) + } + + resources, err := client.GetResourcesWithFilters(ctx, site, req) + if err != nil { + // ListResources for nodes not availalbe, provide fallback. + // Fallback does not support search/predicate support, so if users + // provide them, it does nothing. + // + // DELETE IN 11.0.0 + if trace.IsNotImplemented(err) { + servers, err := proxy.FindServersByLabels(ctx, req.Namespace, req.Labels) + if err != nil { + return nil, trace.Wrap(err) + } + return servers, nil + } + return nil, trace.Wrap(err) + } + + servers, err := types.ResourcesWithLabels(resources).AsServers() + if err != nil { + return nil, trace.Wrap(err) + } + + return servers, nil +} + +// FindAppServersByFilters returns a list of application servers which have filters matched. +func (proxy *ProxyClient) FindAppServersByFilters(ctx context.Context, req proto.ListResourcesRequest) ([]types.AppServer, error) { + req.ResourceType = types.KindAppServer authClient, err := proxy.CurrentClusterAccessPoint(ctx, false) if err != nil { return nil, trace.Wrap(err) } - servers, err := authClient.GetApplicationServers(ctx, namespace) + resources, err := client.GetResourcesWithFilters(ctx, authClient, req) + if err != nil { + // ListResources for app servers not availalbe, provide fallback. + // Fallback does not support filters, so if users + // provide them, it does nothing. + // + // DELETE IN 11.0.0 + if trace.IsNotImplemented(err) { + servers, err := authClient.GetApplicationServers(ctx, req.Namespace) + if err != nil { + return nil, trace.Wrap(err) + } + return servers, nil + } + return nil, trace.Wrap(err) + } + + servers, err := types.ResourcesWithLabels(resources).AsAppServers() if err != nil { return nil, trace.Wrap(err) } @@ -663,13 +716,32 @@ func (proxy *ProxyClient) DeleteAppSession(ctx context.Context, sessionID string return nil } -// GetDatabaseServers returns all registered database proxy servers. -func (proxy *ProxyClient) GetDatabaseServers(ctx context.Context, namespace string) ([]types.DatabaseServer, error) { +// FindDatabaseServersByFilters returns all registered database proxy servers. +func (proxy *ProxyClient) FindDatabaseServersByFilters(ctx context.Context, req proto.ListResourcesRequest) ([]types.DatabaseServer, error) { + req.ResourceType = types.KindDatabaseServer authClient, err := proxy.CurrentClusterAccessPoint(ctx, false) if err != nil { return nil, trace.Wrap(err) } - servers, err := authClient.GetDatabaseServers(ctx, namespace) + + resources, err := client.GetResourcesWithFilters(ctx, authClient, req) + if err != nil { + // ListResources for db servers not availalbe, provide fallback. + // Fallback does not support filters, so if users + // provide them, it does nothing. + // + // DELETE IN 11.0.0 + if trace.IsNotImplemented(err) { + servers, err := authClient.GetDatabaseServers(ctx, req.Namespace) + if err != nil { + return nil, trace.Wrap(err) + } + return servers, nil + } + return nil, trace.Wrap(err) + } + + servers, err := types.ResourcesWithLabels(resources).AsDatabaseServers() if err != nil { return nil, trace.Wrap(err) } @@ -928,7 +1000,7 @@ func (proxy *ProxyClient) dialAuthServer(ctx context.Context, clusterName string if err != nil { // read the stderr output from the failed SSH session and append // it to the end of our own message: - serverErrorMsg, _ := ioutil.ReadAll(proxyErr) + serverErrorMsg, _ := io.ReadAll(proxyErr) return nil, trace.ConnectionProblem(err, "failed connecting to node %v. %s", nodeName(strings.Split(address, "@")[0]), serverErrorMsg) } @@ -1079,7 +1151,7 @@ func (proxy *ProxyClient) ConnectToNode(ctx context.Context, nodeAddress NodeAdd // read the stderr output from the failed SSH session and append // it to the end of our own message: - serverErrorMsg, _ := ioutil.ReadAll(proxyErr) + serverErrorMsg, _ := io.ReadAll(proxyErr) return nil, trace.ConnectionProblem(err, "failed connecting to node %v. %s", nodeName(nodeAddress.Addr), serverErrorMsg) } @@ -1250,8 +1322,8 @@ func (c *NodeClient) handleGlobalRequests(ctx context.Context, requestCh <-chan func newClientConn(ctx context.Context, conn net.Conn, nodeAddress string, - config *ssh.ClientConfig) (ssh.Conn, <-chan ssh.NewChannel, <-chan *ssh.Request, error) { - + config *ssh.ClientConfig, +) (ssh.Conn, <-chan ssh.NewChannel, <-chan *ssh.Request, error) { type response struct { conn ssh.Conn chanCh <-chan ssh.NewChannel diff --git a/lib/client/client_test.go b/lib/client/client_test.go index 55d25fe673980..93e9850deb44f 100644 --- a/lib/client/client_test.go +++ b/lib/client/client_test.go @@ -20,7 +20,6 @@ package client import ( "context" "io" - "io/ioutil" "net" "os" "strings" @@ -122,7 +121,7 @@ func (s *ClientTestSuite) TestProxyConnection(c *check.C) { c.Assert(err, check.IsNil) clientErrCh := make(chan error, 3) go func(con net.Conn) { - _, err := io.Copy(ioutil.Discard, con) + _, err := io.Copy(io.Discard, con) if err != nil && strings.Contains(err.Error(), "use of closed network connection") { err = nil } @@ -156,7 +155,7 @@ func (s *ClientTestSuite) TestProxyConnection(c *check.C) { localCon, err = net.Dial("tcp", localSrv.Addr().String()) c.Assert(err, check.IsNil) go func(con net.Conn) { - _, err := io.Copy(ioutil.Discard, con) + _, err := io.Copy(io.Discard, con) if err != nil && strings.Contains(err.Error(), "use of closed network connection") { err = nil } diff --git a/lib/client/export_test.go b/lib/client/export.go similarity index 100% rename from lib/client/export_test.go rename to lib/client/export.go diff --git a/lib/client/https_client_test.go b/lib/client/https_client_test.go index ea5fac1a89a4d..66adb430ef035 100644 --- a/lib/client/https_client_test.go +++ b/lib/client/https_client_test.go @@ -17,7 +17,6 @@ limitations under the License. package client import ( - "net/http" "testing" "github.com/stretchr/testify/require" @@ -28,21 +27,24 @@ func TestNewInsecureWebClientHTTPProxy(t *testing.T) { client := NewInsecureWebClient() // resp should be nil, so there will be no body to close. //nolint:bodyclose - resp, err := client.Get("https://example.com") + resp, err := client.Get("https://fakedomain.example.com") // Client should try to proxy through nonexistent server at localhost. require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") + require.Contains(t, err.Error(), "lookup fakeproxy.example.com") require.Contains(t, err.Error(), "no such host") } func TestNewInsecureWebClientNoProxy(t *testing.T) { t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") - t.Setenv("NO_PROXY", "example.com") + t.Setenv("NO_PROXY", "fakedomain.example.com") client := NewInsecureWebClient() - resp, err := client.Get("https://example.com") - require.NoError(t, err) - defer resp.Body.Close() - require.Equal(t, http.StatusOK, resp.StatusCode) + //nolint:bodyclose + resp, err := client.Get("https://fakedomain.example.com") + require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) + require.NotContains(t, err.Error(), "proxyconnect") + require.Contains(t, err.Error(), "lookup fakedomain.example.com") + require.Contains(t, err.Error(), "no such host") } func TestNewClientWithPoolHTTPProxy(t *testing.T) { @@ -50,19 +52,22 @@ func TestNewClientWithPoolHTTPProxy(t *testing.T) { client := newClientWithPool(nil) // resp should be nil, so there will be no body to close. //nolint:bodyclose - resp, err := client.Get("https://example.com") + resp, err := client.Get("https://fakedomain.example.com") // Client should try to proxy through nonexistent server at localhost. require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") + require.Contains(t, err.Error(), "lookup fakeproxy.example.com") require.Contains(t, err.Error(), "no such host") } func TestNewClientWithPoolNoProxy(t *testing.T) { t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") - t.Setenv("NO_PROXY", "example.com") + t.Setenv("NO_PROXY", "fakedomain.example.com") client := newClientWithPool(nil) - resp, err := client.Get("https://example.com") - require.NoError(t, err) - defer resp.Body.Close() - require.Equal(t, http.StatusOK, resp.StatusCode) + //nolint:bodyclose + resp, err := client.Get("https://fakedomain.example.com") + require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) + require.NotContains(t, err.Error(), "proxyconnect") + require.Contains(t, err.Error(), "lookup fakedomain.example.com") + require.Contains(t, err.Error(), "no such host") } diff --git a/lib/client/identityfile/identity.go b/lib/client/identityfile/identity.go index 854546bb4f0a6..9cdf7d0aa3bb1 100644 --- a/lib/client/identityfile/identity.go +++ b/lib/client/identityfile/identity.go @@ -20,7 +20,6 @@ package identityfile import ( "context" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -159,12 +158,12 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) { return nil, trace.Wrap(err) } - err = ioutil.WriteFile(certPath, cfg.Key.Cert, identityfile.FilePermissions) + err = os.WriteFile(certPath, cfg.Key.Cert, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } - err = ioutil.WriteFile(keyPath, cfg.Key.Priv, identityfile.FilePermissions) + err = os.WriteFile(keyPath, cfg.Key.Priv, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } @@ -186,12 +185,12 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) { return nil, trace.Wrap(err) } - err = ioutil.WriteFile(certPath, cfg.Key.TLSCert, identityfile.FilePermissions) + err = os.WriteFile(certPath, cfg.Key.TLSCert, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } - err = ioutil.WriteFile(keyPath, cfg.Key.Priv, identityfile.FilePermissions) + err = os.WriteFile(keyPath, cfg.Key.Priv, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } @@ -201,7 +200,7 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) { caCerts = append(caCerts, cert...) } } - err = ioutil.WriteFile(casPath, caCerts, identityfile.FilePermissions) + err = os.WriteFile(casPath, caCerts, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } @@ -215,7 +214,7 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) { if err := checkOverwrite(cfg.OverwriteDestination, filesWritten...); err != nil { return nil, trace.Wrap(err) } - err = ioutil.WriteFile(certPath, append(cfg.Key.TLSCert, cfg.Key.Priv...), identityfile.FilePermissions) + err = os.WriteFile(certPath, append(cfg.Key.TLSCert, cfg.Key.Priv...), identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } @@ -225,7 +224,7 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) { caCerts = append(caCerts, cert...) } } - err = ioutil.WriteFile(casPath, caCerts, identityfile.FilePermissions) + err = os.WriteFile(casPath, caCerts, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/client/identityfile/identity_test.go b/lib/client/identityfile/identity_test.go index 50a37e97561e1..42953743cc47d 100644 --- a/lib/client/identityfile/identity_test.go +++ b/lib/client/identityfile/identity_test.go @@ -18,7 +18,7 @@ import ( "bytes" "crypto/rsa" "crypto/x509/pkix" - "io/ioutil" + "os" "path/filepath" "testing" @@ -128,12 +128,12 @@ func TestWrite(t *testing.T) { require.NoError(t, err) // key is OK: - out, err := ioutil.ReadFile(cfg.OutputPath) + out, err := os.ReadFile(cfg.OutputPath) require.NoError(t, err) require.Equal(t, string(out), string(key.Priv)) // cert is OK: - out, err = ioutil.ReadFile(keypaths.IdentitySSHCertPath(cfg.OutputPath)) + out, err = os.ReadFile(keypaths.IdentitySSHCertPath(cfg.OutputPath)) require.NoError(t, err) require.Equal(t, string(out), string(key.Cert)) @@ -144,7 +144,7 @@ func TestWrite(t *testing.T) { require.NoError(t, err) // key+cert are OK: - out, err = ioutil.ReadFile(cfg.OutputPath) + out, err = os.ReadFile(cfg.OutputPath) require.NoError(t, err) wantArr := [][]byte{ diff --git a/lib/client/keyagent_test.go b/lib/client/keyagent_test.go index c2c67e97c82c9..a6df53f40ad22 100644 --- a/lib/client/keyagent_test.go +++ b/lib/client/keyagent_test.go @@ -19,7 +19,6 @@ package client import ( "bytes" "io" - "io/ioutil" "net" "os" "path/filepath" @@ -549,7 +548,7 @@ func (s *KeyAgentTestSuite) makeKey(username string, allowedLogins []string, ttl func startDebugAgent(t *testing.T) error { // Create own tmp dir instead of using t.TmpDir // because net.Listen("unix", path) has dir path length limitation - tempDir, err := ioutil.TempDir("", "teleport-test") + tempDir, err := os.MkdirTemp("", "teleport-test") require.NoError(t, err) t.Cleanup(func() { os.RemoveAll(tempDir) diff --git a/lib/client/keystore.go b/lib/client/keystore.go index fb1d59051748c..868bda99381e4 100644 --- a/lib/client/keystore.go +++ b/lib/client/keystore.go @@ -22,7 +22,6 @@ import ( "fmt" "io" osfs "io/fs" - "io/ioutil" "os" "path/filepath" "strings" @@ -50,6 +49,10 @@ const ( // keyFilePerms is the default permissions applied to key files (.cert, .key, pub) // under ~/.tsh keyFilePerms os.FileMode = 0600 + + // tshConfigFileName is the name of the directory containing the + // tsh config file. + tshConfigFileName = "config" ) // LocalKeyStore interface allows for different storage backends for tsh to @@ -180,7 +183,7 @@ func (fs *FSLocalKeyStore) writeBytes(bytes []byte, fp string) error { fs.log.Error(err) return trace.ConvertSystemError(err) } - err := ioutil.WriteFile(fp, bytes, keyFilePerms) + err := os.WriteFile(fp, bytes, keyFilePerms) if err != nil { fs.log.Error(err) } @@ -223,9 +226,27 @@ func (fs *FSLocalKeyStore) DeleteUserCerts(idx KeyIndex, opts ...CertOption) err // DeleteKeys removes all session keys. func (fs *FSLocalKeyStore) DeleteKeys() error { - if err := os.RemoveAll(fs.KeyDir); err != nil { + + files, err := os.ReadDir(fs.KeyDir) + if err != nil { return trace.ConvertSystemError(err) } + for _, file := range files { + if file.IsDir() && file.Name() == tshConfigFileName { + continue + } + if file.IsDir() { + err := os.RemoveAll(filepath.Join(fs.KeyDir, file.Name())) + if err != nil { + return trace.ConvertSystemError(err) + } + continue + } + err := os.Remove(filepath.Join(fs.KeyDir, file.Name())) + if err != nil { + return trace.ConvertSystemError(err) + } + } return nil } @@ -238,22 +259,22 @@ func (fs *FSLocalKeyStore) GetKey(idx KeyIndex, opts ...CertOption) (*Key, error } } - if _, err := ioutil.ReadDir(fs.KeyDir); err != nil && trace.IsNotFound(err) { + if _, err := os.ReadDir(fs.KeyDir); err != nil && trace.IsNotFound(err) { return nil, trace.Wrap(err, "no session keys for %+v", idx) } - priv, err := ioutil.ReadFile(fs.UserKeyPath(idx)) + priv, err := os.ReadFile(fs.UserKeyPath(idx)) if err != nil { fs.log.Error(err) return nil, trace.ConvertSystemError(err) } - pub, err := ioutil.ReadFile(fs.sshCAsPath(idx)) + pub, err := os.ReadFile(fs.sshCAsPath(idx)) if err != nil { fs.log.Error(err) return nil, trace.ConvertSystemError(err) } tlsCertFile := fs.tlsCertPath(idx) - tlsCert, err := ioutil.ReadFile(tlsCertFile) + tlsCert, err := os.ReadFile(tlsCertFile) if err != nil { fs.log.Error(err) return nil, trace.ConvertSystemError(err) @@ -309,14 +330,14 @@ func (fs *FSLocalKeyStore) updateKeyWithCerts(o CertOption, key *Key) error { if info.IsDir() { certDataMap := map[string][]byte{} - certFiles, err := ioutil.ReadDir(certPath) + certFiles, err := os.ReadDir(certPath) if err != nil { return trace.ConvertSystemError(err) } for _, certFile := range certFiles { name := keypaths.TrimCertPathSuffix(certFile.Name()) if isCert := name != certFile.Name(); isCert { - data, err := ioutil.ReadFile(filepath.Join(certPath, certFile.Name())) + data, err := os.ReadFile(filepath.Join(certPath, certFile.Name())) if err != nil { return trace.ConvertSystemError(err) } @@ -326,7 +347,7 @@ func (fs *FSLocalKeyStore) updateKeyWithCerts(o CertOption, key *Key) error { return o.updateKeyWithMap(key, certDataMap) } - certBytes, err := ioutil.ReadFile(certPath) + certBytes, err := os.ReadFile(certPath) if err != nil { return trace.ConvertSystemError(err) } @@ -616,7 +637,7 @@ func matchesWildcard(hostname, pattern string) bool { // GetKnownHostKeys returns all known public keys from `known_hosts`. func (fs *fsLocalNonSessionKeyStore) GetKnownHostKeys(hostname string) ([]ssh.PublicKey, error) { - bytes, err := ioutil.ReadFile(fs.knownHostsPath()) + bytes, err := os.ReadFile(fs.knownHostsPath()) if err != nil { if os.IsNotExist(err) { return nil, nil @@ -723,7 +744,7 @@ func (fs *fsLocalNonSessionKeyStore) GetTrustedCertsPEM(proxyHost string) ([][]b return nil } - data, err := ioutil.ReadFile(path) + data, err := os.ReadFile(path) for len(data) > 0 { if err != nil { return trace.Wrap(err) diff --git a/lib/client/keystore_test.go b/lib/client/keystore_test.go index c6e26402464b5..332641614be8d 100644 --- a/lib/client/keystore_test.go +++ b/lib/client/keystore_test.go @@ -21,7 +21,6 @@ import ( "crypto/rsa" "crypto/x509/pkix" "fmt" - "io/ioutil" "os" "path/filepath" "testing" @@ -402,6 +401,19 @@ func TestAddKey_withoutSSHCert(t *testing.T) { require.Len(t, keyCopy.DBTLSCerts, 1) } +func TestConfigDirNotDeleted(t *testing.T) { + s, cleanup := newTest(t) + t.Cleanup(cleanup) + idx := KeyIndex{"host.a", "bob", "root"} + s.store.AddKey(s.makeSignedKey(t, idx, false)) + configPath := filepath.Join(s.storeDir, "config") + require.NoError(t, os.Mkdir(configPath, 0700)) + require.NoError(t, s.store.DeleteKeys()) + require.DirExists(t, configPath) + + require.NoDirExists(t, filepath.Join(s.storeDir, "keys")) +} + type keyStoreTest struct { storeDir string store *FSLocalKeyStore @@ -493,7 +505,7 @@ func newSelfSignedCA(privateKey []byte) (*tlsca.CertAuthority, auth.TrustedCerts } func newTest(t *testing.T) (keyStoreTest, func()) { - dir, err := ioutil.TempDir("", "teleport-keystore") + dir, err := os.MkdirTemp("", "teleport-keystore") require.NoError(t, err) store, err := NewFSLocalKeyStore(dir) diff --git a/lib/client/mfa.go b/lib/client/mfa.go index 4f8e999c7965a..42d2ac5d957fe 100644 --- a/lib/client/mfa.go +++ b/lib/client/mfa.go @@ -18,6 +18,7 @@ package client import ( "context" + "errors" "fmt" "io" "os" @@ -33,8 +34,12 @@ import ( ) type ( - OTPPrompt func(ctx context.Context, out io.Writer, in *prompt.ContextReader, question string) (string, error) - WebPrompt func(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error) + OTPPrompt func(ctx context.Context, out io.Writer, in prompt.Reader, question string) (string, error) + WebPrompt func( + ctx context.Context, + origin, user string, + assertion *wanlib.CredentialAssertion, + prompt wancli.LoginPrompt) (*proto.MFAAuthenticateResponse, string, error) ) // PlatformPrompt groups functions that prompt the user for inputs. @@ -59,13 +64,29 @@ func (pp *PlatformPrompt) Swap(otp OTPPrompt, web WebPrompt) { var prompts = (&PlatformPrompt{}).Reset() +type noopPrompt struct{} + +func (p noopPrompt) PromptPIN() (string, error) { + // TODO(codingllama): Revisit? There may be authenticators out there that disagree. + // The main issue with PIN prompts in MFA is that prompts.OTP hijacks Stdin, + // so we'd have to make that into a password read and redirect it into either + // an OTP (not sensitive) or a PIN (sensitive). + return "", errors.New("PIN not supported for MFA") +} + +func (p noopPrompt) PromptAdditionalTouch() error { + return errors.New("additional touches not supported for MFA") +} + // PromptMFAChallenge prompts the user to complete MFA authentication // challenges. // // If promptDevicePrefix is set, it will be printed in prompts before "security // key" or "device". This is used to emphasize between different kinds of // devices, like registered vs new. -func PromptMFAChallenge(ctx context.Context, proxyAddr string, c *proto.MFAAuthenticateChallenge, promptDevicePrefix string, quiet bool) (*proto.MFAAuthenticateResponse, error) { +func PromptMFAChallenge( + ctx context.Context, + proxyAddr string, c *proto.MFAAuthenticateChallenge, promptDevicePrefix string, quiet bool) (*proto.MFAAuthenticateResponse, error) { // Is there a challenge present? if c.TOTP == nil && c.WebauthnChallenge == nil { return &proto.MFAAuthenticateResponse{}, nil @@ -148,7 +169,9 @@ func PromptMFAChallenge(ctx context.Context, proxyAddr string, c *proto.MFAAuthe go func() { defer wg.Done() log.Debugf("WebAuthn: prompting devices with origin %q", origin) - resp, err := prompts.Webauthn(ctx, origin, wanlib.CredentialAssertionFromProto(c.WebauthnChallenge)) + const user = "" // No ambiguity in MFA prompts. + var prompt noopPrompt // No PINs or additional touches required for MFA. + resp, _, err := prompts.Webauthn(ctx, origin, user, wanlib.CredentialAssertionFromProto(c.WebauthnChallenge), prompt) respC <- response{kind: "WEBAUTHN", resp: resp, err: err} }() } diff --git a/lib/client/weblogin.go b/lib/client/weblogin.go index fa432f142fe6a..6148704369663 100644 --- a/lib/client/weblogin.go +++ b/lib/client/weblogin.go @@ -23,6 +23,7 @@ import ( "fmt" "net" "net/url" + "os" "os/exec" "runtime" "time" @@ -86,6 +87,8 @@ type SSOLoginConsoleResponse struct { type MFAChallengeRequest struct { User string `json:"user"` Pass string `json:"pass"` + // Passwordless explicitly requests a passwordless/usernameless challenge. + Passwordless bool `json:"passwordless"` } // CreateSSHCertReq are passed by web client @@ -234,7 +237,7 @@ func initClient(proxyAddr string, insecure bool, pool *x509.CertPool) (*WebClien if insecure { // Skip https cert verification, print a warning that this is insecure. - fmt.Printf("WARNING: You are using insecure connection to SSH proxy %v\n", proxyAddr) + fmt.Fprintf(os.Stderr, "WARNING: You are using insecure connection to SSH proxy %v\n", proxyAddr) opts = append(opts, roundtrip.HTTPClient(NewInsecureWebClient())) } else if pool != nil { // use custom set of trusted CAs @@ -289,17 +292,17 @@ func SSHAgentSSOLogin(ctx context.Context, login SSHLoginSSO) (*auth.SSHLoginRes } if execCmd != nil { if err := execCmd.Start(); err != nil { - fmt.Printf("Failed to open a browser window for login: %v\n", err) + fmt.Fprintf(os.Stderr, "Failed to open a browser window for login: %v\n", err) } } // Print the URL to the screen, in case the command that launches the browser did not run. // If Browser is set to the special string teleport.BrowserNone, no browser will be opened. if login.Browser == teleport.BrowserNone { - fmt.Printf("Use the following URL to authenticate:\n %v\n", clickableURL) + fmt.Fprintf(os.Stderr, "Use the following URL to authenticate:\n %v\n", clickableURL) } else { - fmt.Printf("If browser window does not open automatically, open it by ") - fmt.Printf("clicking on the link:\n %v\n", clickableURL) + fmt.Fprintf(os.Stderr, "If browser window does not open automatically, open it by ") + fmt.Fprintf(os.Stderr, "clicking on the link:\n %v\n", clickableURL) } select { @@ -431,12 +434,5 @@ func HostCredentials(ctx context.Context, proxyAddr string, insecure bool, req t return nil, trace.Wrap(err) } - // If we got certs, we're done, however, we may be talking to a Teleport 9 or earlier server, - // which still sends back the legacy JSON format. - if len(certs.SSH) > 0 && len(certs.TLS) > 0 { - return &certs, nil - } - - // DELETE IN 10.0.0 (zmb3) - return auth.UnmarshalLegacyCerts(resp.Bytes()) + return &certs, nil } diff --git a/lib/client/weblogin_test.go b/lib/client/weblogin_test.go index ed50896d937d6..571e1014907ff 100644 --- a/lib/client/weblogin_test.go +++ b/lib/client/weblogin_test.go @@ -24,8 +24,8 @@ import ( "net/http/httptest" "testing" + "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" - "github.com/gravitational/teleport/lib/auth" "github.com/stretchr/testify/require" ) @@ -47,7 +47,7 @@ func TestPlainHttpFallback(t *testing.T) { } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(auth.LegacyCerts{}) + json.NewEncoder(w).Encode(proto.Certs{}) }, actionUnderTest: func(ctx context.Context, addr string, insecure bool) error { _, err := HostCredentials(ctx, addr, insecure, types.RegisterUsingTokenRequest{}) diff --git a/lib/config/configuration.go b/lib/config/configuration.go index 28ca24f86ce90..e1ef67529bae7 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -24,7 +24,6 @@ import ( "bufio" "crypto/x509" "io" - "io/ioutil" "net" "net/url" "os" @@ -34,6 +33,8 @@ import ( "time" "unicode" + stdlog "log" + "golang.org/x/crypto/ssh" "github.com/go-ldap/ldap/v3" @@ -289,18 +290,10 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error { } // apply logger settings - logger := utils.NewLogger() - err = applyLogConfig(fc.Logger, logger) + err = applyLogConfig(fc.Logger, cfg) if err != nil { return trace.Wrap(err) } - cfg.Log = logger - - // Apply logging configuration for the global logger instance - // DELETE this when global logger instance is no longer in use. - // - // Logging configuration has already been validated above - _ = applyLogConfig(fc.Logger, log.StandardLogger()) if fc.CachePolicy.TTL != "" { log.Warnf("cache.ttl config option is deprecated and will be ignored, caches no longer attempt to anticipate resource expiration.") @@ -427,14 +420,18 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error { return nil } -func applyLogConfig(loggerConfig Log, logger *log.Logger) error { +func applyLogConfig(loggerConfig Log, cfg *service.Config) error { + logger := log.StandardLogger() + switch loggerConfig.Output { case "": break // not set case "stderr", "error", "2": logger.SetOutput(os.Stderr) + cfg.Console = io.Discard // disable console printing case "stdout", "out", "1": logger.SetOutput(os.Stdout) + cfg.Console = io.Discard // disable console printing case teleport.Syslog: err := utils.SwitchLoggerToSyslog(logger) if err != nil { @@ -487,10 +484,13 @@ func applyLogConfig(loggerConfig Log, logger *log.Logger) error { } logger.SetFormatter(formatter) + stdlog.SetOutput(io.Discard) // disable the standard logger used by external dependencies + stdlog.SetFlags(0) default: return trace.BadParameter("unsupported log output format : %q", loggerConfig.Format.Output) } + cfg.Log = logger return nil } @@ -547,6 +547,9 @@ func applyAuthConfig(fc *FileConfig, cfg *service.Config) error { if err != nil { return trace.Wrap(err) } + } + + if fc.Auth.MessageOfTheDay != "" { cfg.Auth.Preference.SetMessageOfTheDay(fc.Auth.MessageOfTheDay) } @@ -1274,15 +1277,6 @@ func applyMetricsConfig(fc *FileConfig, cfg *service.Config) error { func applyWindowsDesktopConfig(fc *FileConfig, cfg *service.Config) error { cfg.WindowsDesktop.Enabled = true - // Support for reading an LDAP password from a file was dropped for Teleport 9. - // Check if this old option is still set and issue a clear error for one major version. - // DELETE IN 10.0 (zmb3) - if len(fc.WindowsDesktop.LDAP.PasswordFile) > 0 { - return trace.BadParameter("Support for password_file was deprecated in Teleport 9 " + - "in favor of certificate-based authentication. Remove the password_file field from " + - "teleport.yaml to fix this error.") - } - if fc.WindowsDesktop.ListenAddress != "" { listenAddr, err := utils.ParseHostPortAddr(fc.WindowsDesktop.ListenAddress, int(defaults.WindowsDesktopListenPort)) if err != nil { @@ -1749,7 +1743,7 @@ func Configure(clf *CommandLineFlags, cfg *service.Config) error { // apply --debug flag to config: if clf.Debug { - cfg.Console = ioutil.Discard + cfg.Console = io.Discard cfg.Debug = clf.Debug } diff --git a/lib/config/configuration_test.go b/lib/config/configuration_test.go index 45ac88b006ad3..71d8ed81f4c72 100644 --- a/lib/config/configuration_test.go +++ b/lib/config/configuration_test.go @@ -20,7 +20,6 @@ import ( "bytes" "encoding/base64" "fmt" - "io/ioutil" "net" "os" "path" @@ -65,7 +64,7 @@ var testConfigs testConfigFiles func writeTestConfigs() error { var err error - testConfigs.tempDir, err = ioutil.TempDir("", "teleport-config") + testConfigs.tempDir, err = os.MkdirTemp("", "teleport-config") if err != nil { return err } @@ -669,7 +668,7 @@ func TestApplyConfig(t *testing.T) { require.Equal(t, "tcp://127.0.0.1:3000", cfg.DiagnosticAddr.FullAddress()) - u2fCAFromFile, err := ioutil.ReadFile("testdata/u2f_attestation_ca.pem") + u2fCAFromFile, err := os.ReadFile("testdata/u2f_attestation_ca.pem") require.NoError(t, err) require.Empty(t, cmp.Diff(cfg.Auth.Preference, &types.AuthPreferenceV2{ Kind: types.KindClusterAuthPreference, @@ -1572,13 +1571,6 @@ func TestWindowsDesktopService(t *testing.T) { } }, }, - { - desc: "NOK - uses deprecated password_file field", - expectError: require.Error, - mutate: func(fc *FileConfig) { - fc.WindowsDesktop.LDAP.PasswordFile = "/path/to/some/file" - }, - }, } { t.Run(test.desc, func(t *testing.T) { fc := &FileConfig{} diff --git a/lib/config/fileconf.go b/lib/config/fileconf.go index 21f388a161e3d..e087351fb0c1a 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -22,7 +22,6 @@ import ( "encoding/base64" "fmt" "io" - "io/ioutil" "net" "net/url" "os" @@ -111,7 +110,7 @@ func ReadFromString(configString string) (*FileConfig, error) { // ReadConfig reads Teleport configuration from reader in YAML format func ReadConfig(reader io.Reader) (*FileConfig, error) { // read & parse YAML config: - bytes, err := ioutil.ReadAll(reader) + bytes, err := io.ReadAll(reader) if err != nil { return nil, trace.Wrap(err, "failed reading Teleport configuration") } @@ -794,7 +793,7 @@ func getAttestationPEM(certOrPath string) (string, error) { } // Try reading as a file and parsing that. - data, err := ioutil.ReadFile(certOrPath) + data, err := os.ReadFile(certOrPath) if err != nil { // Don't use trace in order to keep a clean error message. return "", fmt.Errorf("%q is not a valid x509 certificate (%v) and can't be read as a file (%v)", certOrPath, parseErr, err) @@ -1478,12 +1477,4 @@ type LDAPConfig struct { InsecureSkipVerify bool `yaml:"insecure_skip_verify"` // DEREncodedCAFile is the filepath to an optional DER encoded CA cert to be used for verification (if InsecureSkipVerify is set to false). DEREncodedCAFile string `yaml:"der_ca_file,omitempty"` - - // PasswordFile was used in Teleport 8 before we supported client certificates - // for LDAP authentication. Support for LDAP passwords was removed for Teleport 9 - // and this field remains only to issue a warning to users who are upgrading to - // Teleport 9. - // - // TODO(zmb3) DELETE IN 10.0 - PasswordFile string `yaml:"password_file"` } diff --git a/lib/datalog/access_test.go b/lib/datalog/access_test.go index 5b0a0c584ae54..12f89bee44755 100644 --- a/lib/datalog/access_test.go +++ b/lib/datalog/access_test.go @@ -93,7 +93,7 @@ func createUser(name string, roles []string, traits map[string][]string) (types. } func createRole(name string, allowLogins []string, denyLogins []string, allowLabels types.Labels, denyLabels types.Labels) (types.Role, error) { - role, err := types.NewRole(name, types.RoleSpecV5{ + role, err := types.NewRoleV3(name, types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: allowLogins, NodeLabels: allowLabels, diff --git a/lib/defaults/defaults.go b/lib/defaults/defaults.go index d65999522c772..88539fa023c4e 100644 --- a/lib/defaults/defaults.go +++ b/lib/defaults/defaults.go @@ -468,6 +468,17 @@ const ( LimiterMaxConcurrentSignatures = 10 ) +// Default rate limits for unauthenticated passwordless endpoints. +const ( + // LimiterPasswordlessPeriod is the default period for passwordless limiters. + LimiterPasswordlessPeriod = 1 * time.Minute + // LimiterPasswordlessAverage is the default average for passwordless + // limiters. + LimiterPasswordlessAverage = 10 + // LimiterPasswordlessBurst is the default burst for passwordless limiters. + LimiterPasswordlessBurst = 20 +) + const ( // HostCertCacheSize is the number of host certificates to cache at any moment. HostCertCacheSize = 4000 diff --git a/lib/events/api.go b/lib/events/api.go index e4f7b7ad503f1..8a5cf8e9f61a5 100644 --- a/lib/events/api.go +++ b/lib/events/api.go @@ -502,6 +502,9 @@ const ( // DesktopClipboardSendEvent is emitted when local clipboard data // is sent to Teleport. DesktopClipboardSendEvent = "desktop.clipboard.send" + + // UnknownEvent is any event received that isn't recognized as any other event type. + UnknownEvent = apievents.UnknownEvent ) const ( diff --git a/lib/events/auditlog_test.go b/lib/events/auditlog_test.go index c337baffefdcb..5fcd30417e377 100644 --- a/lib/events/auditlog_test.go +++ b/lib/events/auditlog_test.go @@ -20,7 +20,6 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" "testing" @@ -331,7 +330,7 @@ func (a *AuditTestSuite) TestBasicLogging(c *check.C) { c.Assert(alog.Close(), check.IsNil) // read back what's been written: - bytes, err := ioutil.ReadFile(logfile) + bytes, err := os.ReadFile(logfile) c.Assert(err, check.IsNil) c.Assert(string(bytes), check.Equals, fmt.Sprintf("{\"apples?\":\"yes\",\"event\":\"user.joined\",\"time\":\"%s\",\"uid\":\"%s\"}\n", @@ -371,7 +370,7 @@ func (a *AuditTestSuite) TestLogRotation(c *check.C) { c.Assert(dt, check.Equals, time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())) // read back what's been written: - bytes, err := ioutil.ReadFile(logfile) + bytes, err := os.ReadFile(logfile) c.Assert(err, check.IsNil) contents, err := json.Marshal(event) contents = append(contents, '\n') @@ -379,7 +378,7 @@ func (a *AuditTestSuite) TestLogRotation(c *check.C) { c.Assert(string(bytes), check.Equals, string(contents)) // read back the contents using symlink - bytes, err = ioutil.ReadFile(filepath.Join(alog.localLog.SymlinkDir, SymlinkFilename)) + bytes, err = os.ReadFile(filepath.Join(alog.localLog.SymlinkDir, SymlinkFilename)) c.Assert(err, check.IsNil) c.Assert(string(bytes), check.Equals, string(contents)) @@ -432,7 +431,7 @@ func (a *AuditTestSuite) TestLegacyHandler(c *check.C) { // Download the session in the old format ctx := context.TODO() - tarball, err := ioutil.TempFile("", "teleport-legacy") + tarball, err := os.CreateTemp("", "teleport-legacy") c.Assert(err, check.IsNil) defer os.RemoveAll(tarball.Name()) diff --git a/lib/events/codes.go b/lib/events/codes.go index 7c03d71d9a8da..9d0032d94c5b2 100644 --- a/lib/events/codes.go +++ b/lib/events/codes.go @@ -16,6 +16,8 @@ limitations under the License. package events +import apievents "github.com/gravitational/teleport/api/types/events" + // Event describes an audit log event. type Event struct { // Name is the event name. @@ -500,4 +502,7 @@ const ( // RenewableCertificateGenerationMismatchCode is the renewable cert // generation mismatch code. RenewableCertificateGenerationMismatchCode = "TCB00W" + + // UnknownCode is used when an event of unknown type is encountered. + UnknownCode = apievents.UnknownCode ) diff --git a/lib/events/complete.go b/lib/events/complete.go index 3b8a2b050f9d1..c7594c61521c3 100644 --- a/lib/events/complete.go +++ b/lib/events/complete.go @@ -18,12 +18,11 @@ package events import ( "context" + "fmt" "time" "github.com/gravitational/teleport" - apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types/events" - apievents "github.com/gravitational/teleport/api/types/events" apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/utils" @@ -137,28 +136,43 @@ func (u *UploadCompleter) CheckUploads(ctx context.Context) error { if err != nil { return trace.Wrap(err) } - if len(parts) == 0 { - continue - } - u.log.Debugf("Upload %v grace period is over. Trying to complete.", upload) + + u.log.Debugf("Upload %v grace period is over. Trying to complete.", upload.ID) if err := u.cfg.Uploader.CompleteUpload(ctx, upload, parts); err != nil { return trace.Wrap(err) } u.log.Debugf("Completed upload %v.", upload) completed++ - uploadData := u.cfg.Uploader.GetUploadMetadata(upload.SessionID) - err = u.ensureSessionEndEvent(ctx, uploadData) - if err != nil { - return trace.Wrap(err) + + if len(parts) == 0 { + continue } + + uploadData := u.cfg.Uploader.GetUploadMetadata(upload.SessionID) + + // Schedule a background operation to check for (and emit) a session end event. + // This is necessary because we'll need to download the session in order to + // enumerate its events, and the S3 API takes a little while after the upload + // is completed before version metadata becomes available. + go func() { + select { + case <-ctx.Done(): + return + case <-u.cfg.Clock.After(2 * time.Minute): + u.log.Debugf("checking for session end event for session %v", upload.SessionID) + if err := u.ensureSessionEndEvent(ctx, uploadData); err != nil { + u.log.WithError(err).Warningf("failed to ensure session end event") + } + } + }() session := &events.SessionUpload{ - Metadata: apievents.Metadata{ + Metadata: events.Metadata{ Type: SessionUploadEvent, Code: SessionUploadCode, ID: uuid.New().String(), Index: SessionUploadIndex, }, - SessionMetadata: apievents.SessionMetadata{ + SessionMetadata: events.SessionMetadata{ SessionID: string(uploadData.SessionID), }, SessionURL: uploadData.URL, @@ -181,91 +195,104 @@ func (u *UploadCompleter) Close() error { } func (u *UploadCompleter) ensureSessionEndEvent(ctx context.Context, uploadData UploadMetadata) error { - var serverID, clusterName, user, login, hostname, namespace, serverAddr string - var interactive bool + // at this point, we don't know whether we'll need to emit a session.end or a + // windows.desktop.session.end, but as soon as we see the session start we'll + // be able to start filling in the details + var sshSessionEnd events.SessionEnd + var desktopSessionEnd events.WindowsDesktopSessionEnd - // Get session events to find fields for constructed session end - sessionEvents, err := u.cfg.AuditLog.GetSessionEvents(apidefaults.Namespace, uploadData.SessionID, 0, false) - if err != nil { - return trace.Wrap(err) - } - if len(sessionEvents) == 0 { - return nil - } + first := true - // Return if session.end event already exists - for _, event := range sessionEvents { - if event.GetType() == SessionEndEvent { - return nil + // We use the streaming events API to search through the session events, because it works + // for both Desktop and SSH sessions, where as the GetSessionEvents API relies on downloading + // a copy of the session and using the SSH-specific index to iterate through events. + var lastEvent events.AuditEvent + evts, errors := u.cfg.AuditLog.StreamSessionEvents(ctx, uploadData.SessionID, 0) + +loop: + for { + select { + case evt, more := <-evts: + if !more { + break loop + } + + if first { + u.log.Infof("got first event %T", evt) + first = false + } + + lastEvent = evt + + switch e := evt.(type) { + // Return if session end event already exists + case *events.SessionEnd, *events.WindowsDesktopSessionEnd: + return nil + + case *events.WindowsDesktopSessionStart: + desktopSessionEnd.Type = WindowsDesktopSessionEndEvent + desktopSessionEnd.Code = DesktopSessionEndCode + desktopSessionEnd.ClusterName = e.ClusterName + desktopSessionEnd.StartTime = e.Time + desktopSessionEnd.Participants = append(desktopSessionEnd.Participants, e.User) + desktopSessionEnd.Recorded = true + desktopSessionEnd.UserMetadata = e.UserMetadata + desktopSessionEnd.SessionMetadata = e.SessionMetadata + desktopSessionEnd.WindowsDesktopService = e.WindowsDesktopService + desktopSessionEnd.Domain = e.Domain + desktopSessionEnd.DesktopAddr = e.DesktopAddr + desktopSessionEnd.DesktopLabels = e.DesktopLabels + desktopSessionEnd.DesktopName = fmt.Sprintf("%v (recovered)", e.DesktopName) + + case *events.SessionStart: + sshSessionEnd.Type = SessionEndEvent + sshSessionEnd.Code = SessionEndCode + sshSessionEnd.ClusterName = e.ClusterName + sshSessionEnd.StartTime = e.Time + sshSessionEnd.UserMetadata = e.UserMetadata + sshSessionEnd.SessionMetadata = e.SessionMetadata + sshSessionEnd.ServerMetadata = e.ServerMetadata + sshSessionEnd.ConnectionMetadata = e.ConnectionMetadata + sshSessionEnd.KubernetesClusterMetadata = e.KubernetesClusterMetadata + sshSessionEnd.KubernetesPodMetadata = e.KubernetesPodMetadata + sshSessionEnd.InitialCommand = e.InitialCommand + sshSessionEnd.SessionRecording = e.SessionRecording + sshSessionEnd.Interactive = e.TerminalSize != "" + sshSessionEnd.Participants = append(sshSessionEnd.Participants, e.User) + + case *events.SessionJoin: + sshSessionEnd.Participants = append(sshSessionEnd.Participants, e.User) + } + + case err := <-errors: + return trace.Wrap(err) + case <-ctx.Done(): + return ctx.Err() } } - // Session start event is the first of session events - sessionStart := sessionEvents[0] - if sessionStart.GetType() != SessionStartEvent { - return trace.BadParameter("invalid session, session start is not the first event") - } + sshSessionEnd.Participants = apiutils.Deduplicate(sshSessionEnd.Participants) + sshSessionEnd.EndTime = lastEvent.GetTime() + desktopSessionEnd.EndTime = lastEvent.GetTime() - // Set variables - serverID = sessionStart.GetString(SessionServerHostname) - clusterName = sessionStart.GetString(SessionClusterName) - hostname = sessionStart.GetString(SessionServerHostname) - namespace = sessionStart.GetString(EventNamespace) - serverAddr = sessionStart.GetString(SessionServerAddr) - user = sessionStart.GetString(EventUser) - login = sessionStart.GetString(EventLogin) - if terminalSize := sessionStart.GetString(TerminalSize); terminalSize != "" { - interactive = true + var sessionEndEvent events.AuditEvent + switch { + case sshSessionEnd.Code != "": + sessionEndEvent = &sshSessionEnd + case desktopSessionEnd.Code != "": + sessionEndEvent = &desktopSessionEnd + default: + return trace.BadParameter("invalid session, could not find session start") } - // Get last event to get session end time - lastEvent := sessionEvents[len(sessionEvents)-1] - - participants := getParticipants(sessionEvents) - - sessionEndEvent := &events.SessionEnd{ - Metadata: events.Metadata{ - Type: SessionEndEvent, - Code: SessionEndCode, - ClusterName: clusterName, - }, - ServerMetadata: events.ServerMetadata{ - ServerID: serverID, - ServerNamespace: namespace, - ServerHostname: hostname, - ServerAddr: serverAddr, - }, - SessionMetadata: events.SessionMetadata{ - SessionID: string(uploadData.SessionID), - }, - UserMetadata: events.UserMetadata{ - User: user, - Login: login, - }, - Participants: participants, - Interactive: interactive, - StartTime: sessionStart.GetTime(EventTime), - EndTime: lastEvent.GetTime(EventTime), - } + u.log.Infof("emitting %T event for completed session %v", sessionEndEvent, uploadData.SessionID) // Check and set event fields - if err = checkAndSetEventFields(sessionEndEvent, u.cfg.Clock, utils.NewRealUID(), clusterName); err != nil { + if err := checkAndSetEventFields(sessionEndEvent, u.cfg.Clock, utils.NewRealUID(), sessionEndEvent.GetClusterName()); err != nil { return trace.Wrap(err) } - if err = u.cfg.AuditLog.EmitAuditEvent(ctx, sessionEndEvent); err != nil { + if err := u.cfg.AuditLog.EmitAuditEvent(ctx, sessionEndEvent); err != nil { return trace.Wrap(err) } return nil } - -func getParticipants(sessionEvents []EventFields) []string { - var participants []string - for _, event := range sessionEvents { - if event.GetType() == SessionJoinEvent || event.GetType() == SessionStartEvent { - participant := event.GetString(EventUser) - participants = append(participants, participant) - - } - } - return apiutils.Deduplicate(participants) -} diff --git a/lib/events/complete_test.go b/lib/events/complete_test.go new file mode 100644 index 0000000000000..7e79aa2c8fce7 --- /dev/null +++ b/lib/events/complete_test.go @@ -0,0 +1,145 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package events + +import ( + "context" + "strings" + "testing" + "time" + + apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/lib/session" + "github.com/jonboulle/clockwork" + "github.com/stretchr/testify/require" +) + +// TestUploadCompleterEmitsSessionEnd verifies that the upload completer +// emits session.end or windows.desktop.session.end events for sessions +// that are completed. +func TestUploadCompleterEmitsSessionEnd(t *testing.T) { + for _, test := range []struct { + startEvent apievents.AuditEvent + endEventType string + }{ + {&apievents.SessionStart{}, SessionEndEvent}, + {&apievents.WindowsDesktopSessionStart{}, WindowsDesktopSessionEndEvent}, + } { + t.Run(test.endEventType, func(t *testing.T) { + clock := clockwork.NewFakeClock() + mu := NewMemoryUploader() + mu.Clock = clock + + log := &MockAuditLog{ + sessionEvents: []apievents.AuditEvent{test.startEvent}, + } + + uc, err := NewUploadCompleter(UploadCompleterConfig{ + Unstarted: true, + Uploader: mu, + AuditLog: log, + GracePeriod: 2 * time.Hour, + Clock: clock, + }) + require.NoError(t, err) + + upload, err := mu.CreateUpload(context.Background(), session.NewID()) + require.NoError(t, err) + + // advance the clock to force the grace period check to succeed + clock.Advance(3 * time.Hour) + + // session end events are only emitted if there's at least one + // part to be uploaded, so create that here + _, err = mu.UploadPart(context.Background(), *upload, 0, strings.NewReader("part")) + require.NoError(t, err) + + err = uc.CheckUploads(context.Background()) + require.NoError(t, err) + + // advance the clock to force the asynchronous session end event emission + clock.BlockUntil(1) + clock.Advance(3 * time.Minute) + + // expect two events - a session end and a session upload + // the session end is done asynchronously, so wait for that + require.Eventually(t, func() bool { return len(log.emitter.Events()) == 2 }, 5*time.Second, 1*time.Second, + "should have emitted 2 events, but only got %d", len(log.emitter.Events())) + + require.IsType(t, &apievents.SessionUpload{}, log.emitter.Events()[0]) + require.Equal(t, test.endEventType, log.emitter.Events()[1].GetType()) + }) + } +} + +// TestUploadCompleterCompletesEmptyUploads verifies that the upload completer +// completes uploads that have no parts. This ensures that we don't leave empty +// directories behind. +func TestUploadCompleterCompletesEmptyUploads(t *testing.T) { + clock := clockwork.NewFakeClock() + mu := NewMemoryUploader() + mu.Clock = clock + + log := &MockAuditLog{} + + uc, err := NewUploadCompleter(UploadCompleterConfig{ + Unstarted: true, + Uploader: mu, + AuditLog: log, + GracePeriod: 2 * time.Hour, + }) + require.NoError(t, err) + + upload, err := mu.CreateUpload(context.Background(), session.NewID()) + require.NoError(t, err) + clock.Advance(3 * time.Hour) + + err = uc.CheckUploads(context.Background()) + require.NoError(t, err) + + require.True(t, mu.uploads[upload.ID].completed) +} + +type MockAuditLog struct { + DiscardAuditLog + + emitter MockEmitter + sessionEvents []apievents.AuditEvent +} + +func (m *MockAuditLog) StreamSessionEvents(ctx context.Context, sid session.ID, startIndex int64) (chan apievents.AuditEvent, chan error) { + errors := make(chan error, 1) + events := make(chan apievents.AuditEvent) + + go func() { + defer close(events) + + for _, event := range m.sessionEvents { + select { + case <-ctx.Done(): + return + case events <- event: + } + } + }() + + return events, errors +} + +func (m *MockAuditLog) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error { + return m.emitter.EmitAuditEvent(ctx, event) +} diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index ae556e014005a..9f53bbcd0bffd 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -22,6 +22,7 @@ import ( apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/trace" + log "github.com/sirupsen/logrus" "encoding/json" ) @@ -213,8 +214,21 @@ func FromEventFields(fields EventFields) (apievents.AuditEvent, error) { e = &events.CertificateCreate{} case RenewableCertificateGenerationMismatchEvent: e = &events.RenewableCertificateGenerationMismatch{} + case UnknownEvent: + e = &events.Unknown{} default: - return nil, trace.BadParameter("unknown event type: %q", eventType) + log.Errorf("Attempted to convert dynamic event of unknown type \"%v\" into protobuf event.", eventType) + unknown := &events.Unknown{} + if err := utils.FastUnmarshal(data, unknown); err != nil { + return nil, trace.Wrap(err) + } + + unknown.Type = UnknownEvent + unknown.Code = UnknownCode + unknown.UnknownType = eventType + unknown.UnknownCode = fields.GetString(EventCode) + unknown.Data = string(data) + return unknown, nil } if err := utils.FastUnmarshal(data, e); err != nil { diff --git a/lib/events/emitter_test.go b/lib/events/emitter_test.go index 56e532a1319fd..621e3246ed10f 100644 --- a/lib/events/emitter_test.go +++ b/lib/events/emitter_test.go @@ -22,7 +22,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "os" "testing" "time" @@ -285,7 +284,7 @@ func TestExport(t *testing.T) { parts, err := uploader.GetParts(uploads[0].ID) require.NoError(t, err) - f, err := ioutil.TempFile("", "") + f, err := os.CreateTemp("", "") require.NoError(t, err) defer os.Remove(f.Name()) diff --git a/lib/events/events_test.go b/lib/events/events_test.go index d25ac64af2150..b464d77d8a2cf 100644 --- a/lib/events/events_test.go +++ b/lib/events/events_test.go @@ -486,7 +486,7 @@ func TestJSON(t *testing.T) { }, { name: "desktop session start", - json: `{"uid":"cd06365f-3cef-4b21-809a-4af9502c11a1","user":"foo","impersonator":"bar","login":"Administrator","success":true,"proto":"tdp","sid":"test-session","addr.local":"192.168.1.100:39887","addr.remote":"[::1]:34902","with_mfa":"mfa-device","code":"TDP00I","event":"windows.desktop.session.start","time":"2020-04-23T18:22:35.35Z","ei":4,"cluster_name":"test-cluster","windows_user":"Administrator","windows_domain":"test.example.com","desktop_addr":"[::1]:34902","windows_desktop_service":"00baaef5-ff1e-4222-85a5-c7cb0cd8e7b8","desktop_labels":{"env":"production"}}`, + json: `{"uid":"cd06365f-3cef-4b21-809a-4af9502c11a1","user":"foo","impersonator":"bar","login":"Administrator","success":true,"proto":"tdp","sid":"test-session","addr.local":"192.168.1.100:39887","addr.remote":"[::1]:34902","with_mfa":"mfa-device","code":"TDP00I","event":"windows.desktop.session.start","time":"2020-04-23T18:22:35.35Z","ei":4,"cluster_name":"test-cluster","windows_user":"Administrator","windows_domain":"test.example.com","desktop_name":"test-desktop","desktop_addr":"[::1]:34902","windows_desktop_service":"00baaef5-ff1e-4222-85a5-c7cb0cd8e7b8","desktop_labels":{"env":"production"}}`, event: apievents.WindowsDesktopSessionStart{ Metadata: apievents.Metadata{ Index: 4, @@ -514,6 +514,7 @@ func TestJSON(t *testing.T) { Success: true, }, WindowsDesktopService: "00baaef5-ff1e-4222-85a5-c7cb0cd8e7b8", + DesktopName: "test-desktop", DesktopAddr: "[::1]:34902", Domain: "test.example.com", WindowsUser: "Administrator", diff --git a/lib/events/fields.go b/lib/events/fields.go index 3952c5d6fa9c4..6d00aace3b943 100644 --- a/lib/events/fields.go +++ b/lib/events/fields.go @@ -21,7 +21,6 @@ import ( "bufio" "compress/gzip" "io" - "io/ioutil" "strings" "time" @@ -102,7 +101,7 @@ func ValidateArchive(reader io.Reader, serverID string) error { // Skip over any file in the archive that doesn't contain session events. if !strings.HasSuffix(header.Name, eventsSuffix) { - _, err = io.Copy(ioutil.Discard, tarball) + _, err = io.Copy(io.Discard, tarball) if err != nil { return trace.Wrap(err) } diff --git a/lib/events/filesessions/fileasync.go b/lib/events/filesessions/fileasync.go index db058cd1a134b..22c8ae5bf465e 100644 --- a/lib/events/filesessions/fileasync.go +++ b/lib/events/filesessions/fileasync.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "path/filepath" "time" @@ -159,7 +158,7 @@ func (u *Uploader) writeSessionError(sessionID session.ID, err error) error { return trace.BadParameter("missing session ID") } path := u.sessionErrorFilePath(sessionID) - return trace.ConvertSystemError(ioutil.WriteFile(path, []byte(err.Error()), 0600)) + return trace.ConvertSystemError(os.WriteFile(path, []byte(err.Error()), 0600)) } func (u *Uploader) checkSessionError(sessionID session.ID) (bool, error) { @@ -253,7 +252,7 @@ type ScanStats struct { // Scan scans the streaming directory and uploads recordings func (u *Uploader) Scan() (*ScanStats, error) { - files, err := ioutil.ReadDir(u.cfg.ScanDir) + files, err := os.ReadDir(u.cfg.ScanDir) if err != nil { return nil, trace.ConvertSystemError(err) } @@ -316,7 +315,7 @@ type upload struct { // readStatus reads stream status func (u *upload) readStatus() (*apievents.StreamStatus, error) { - data, err := ioutil.ReadAll(u.checkpointFile) + data, err := io.ReadAll(u.checkpointFile) if err != nil { return nil, trace.ConvertSystemError(err) } diff --git a/lib/events/filesessions/fileasync_chaos_test.go b/lib/events/filesessions/fileasync_chaos_test.go index 95d50e5068cb1..302aeae5843e2 100644 --- a/lib/events/filesessions/fileasync_chaos_test.go +++ b/lib/events/filesessions/fileasync_chaos_test.go @@ -23,7 +23,6 @@ package filesessions import ( "context" "fmt" - "io/ioutil" "os" "path/filepath" "testing" @@ -65,7 +64,7 @@ func TestChaosUpload(t *testing.T) { }) require.NoError(t, err) - scanDir, err := ioutil.TempDir("", "teleport-streams") + scanDir, err := os.MkdirTemp("", "teleport-streams") require.NoError(t, err) defer os.RemoveAll(scanDir) @@ -95,7 +94,7 @@ func TestChaosUpload(t *testing.T) { return nil, trace.ConnectionProblem(nil, "failed to resume stream") } else if resumed >= 5 && resumed < 8 { // for the next several resumes, lose checkpoint file for the stream - files, err := ioutil.ReadDir(scanDir) + files, err := os.ReadDir(scanDir) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/events/filesessions/fileasync_test.go b/lib/events/filesessions/fileasync_test.go index aec9f59121aa9..6d02ff3f82478 100644 --- a/lib/events/filesessions/fileasync_test.go +++ b/lib/events/filesessions/fileasync_test.go @@ -20,7 +20,6 @@ package filesessions import ( "bytes" "context" - "io/ioutil" "os" "path/filepath" "testing" @@ -242,7 +241,7 @@ func TestUploadResume(t *testing.T) { name: "stream created when checkpoint is lost after failure", retries: 1, onRetry: func(t *testing.T, attempt int, uploader *Uploader) { - files, err := ioutil.ReadDir(uploader.cfg.ScanDir) + files, err := os.ReadDir(uploader.cfg.ScanDir) require.Nil(t, err) checkpointsDeleted := 0 for i := range files { @@ -405,7 +404,7 @@ func TestUploadBadSession(t *testing.T) { sessionID := session.NewID() fileName := filepath.Join(p.scanDir, string(sessionID)+tarExt) - err := ioutil.WriteFile(fileName, []byte("this session is corrupted"), 0600) + err := os.WriteFile(fileName, []byte("this session is corrupted"), 0600) require.NoError(t, err) // initiate the scan by advancing clock past @@ -450,16 +449,10 @@ func (u *uploaderPack) Close(t *testing.T) { err := u.uploader.Close() require.NoError(t, err) - - if u.scanDir != "" { - err := os.RemoveAll(u.scanDir) - require.NoError(t, err) - } } func newUploaderPack(t *testing.T, wrapStreamer wrapStreamerFn) uploaderPack { - scanDir, err := ioutil.TempDir("", "teleport-streams") - require.NoError(t, err) + scanDir := t.TempDir() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) pack := uploaderPack{ @@ -518,9 +511,7 @@ func runResume(t *testing.T, testCase resumeTestCase) { test := testCase.newTest(streamer) - scanDir, err := ioutil.TempDir("", "teleport-streams") - require.Nil(t, err) - defer os.RemoveAll(scanDir) + scanDir := t.TempDir() scanPeriod := 10 * time.Second uploader, err := NewUploader(UploaderConfig{ diff --git a/lib/events/filesessions/filestream.go b/lib/events/filesessions/filestream.go index c7415c866b702..3bff4937474a8 100644 --- a/lib/events/filesessions/filestream.go +++ b/lib/events/filesessions/filestream.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "os" "path/filepath" "sort" @@ -96,9 +95,6 @@ func (h *Handler) UploadPart(ctx context.Context, upload events.StreamUpload, pa // CompleteUpload completes the upload func (h *Handler) CompleteUpload(ctx context.Context, upload events.StreamUpload, parts []events.StreamPart) error { - if len(parts) == 0 { - return trace.BadParameter("need at least one part to complete the upload") - } if err := checkUpload(upload); err != nil { return trace.Wrap(err) } @@ -127,30 +123,26 @@ func (h *Handler) CompleteUpload(ctx context.Context, upload events.StreamUpload } }() - files := make([]*os.File, 0, len(parts)) - readers := make([]io.Reader, 0, len(parts)) - - defer func() { - for i := 0; i < len(files); i++ { - if err := files[i].Close(); err != nil { - h.WithError(err).Errorf("Failed to close file %q.", files[i].Name()) - } + writePartToFile := func(path string) error { + file, err := os.Open(path) + if err != nil { + return err } - }() + defer func() { + if err := file.Close(); err != nil { + h.WithError(err).Errorf("failed to close file %q", path) + } + }() + + _, err = io.Copy(f, file) + return err + } for _, part := range parts { partPath := h.partPath(upload, part.Number) - file, err := os.Open(partPath) - if err != nil { - return trace.Wrap(err, "failed to open part file for upload") + if err := writePartToFile(partPath); err != nil { + return trace.Wrap(err) } - files = append(files, file) - readers = append(readers, file) - } - - _, err = io.Copy(f, io.MultiReader(readers...)) - if err != nil { - return trace.Wrap(err) } err = h.Config.OnBeforeComplete(ctx, upload) @@ -207,7 +199,7 @@ func (h *Handler) ListParts(ctx context.Context, upload events.StreamUpload) ([] func (h *Handler) ListUploads(ctx context.Context) ([]events.StreamUpload, error) { var uploads []events.StreamUpload - dirs, err := ioutil.ReadDir(h.uploadsPath()) + dirs, err := os.ReadDir(h.uploadsPath()) if err != nil { err = trace.ConvertSystemError(err) // The upload folder may not exist if there are no uploads yet. @@ -226,7 +218,7 @@ func (h *Handler) ListUploads(ctx context.Context) ([]events.StreamUpload, error h.WithError(err).Warningf("Skipping upload %v with bad format.", uploadID) continue } - files, err := ioutil.ReadDir(filepath.Join(h.uploadsPath(), dir.Name())) + files, err := os.ReadDir(filepath.Join(h.uploadsPath(), dir.Name())) if err != nil { err = trace.ConvertSystemError(err) if trace.IsNotFound(err) { @@ -243,10 +235,16 @@ func (h *Handler) ListUploads(ctx context.Context) ([]events.StreamUpload, error h.Warningf("Skipping upload %v, not a directory.", uploadID) continue } + + info, err := dir.Info() + if err != nil { + h.WithError(err).Warningf("Skipping upload %v: cannot read file info", uploadID) + continue + } uploads = append(uploads, events.StreamUpload{ SessionID: session.ID(filepath.Base(files[0].Name())), ID: uploadID, - Initiated: dir.ModTime(), + Initiated: info.ModTime(), }) } sort.Slice(uploads, func(i, j int) bool { diff --git a/lib/events/filesessions/fileuploader_test.go b/lib/events/filesessions/fileuploader_test.go index acb46dbf20f4e..22698e5f301bf 100644 --- a/lib/events/filesessions/fileuploader_test.go +++ b/lib/events/filesessions/fileuploader_test.go @@ -19,7 +19,6 @@ package filesessions import ( "context" - "io/ioutil" "os" "testing" @@ -40,9 +39,7 @@ func TestMain(m *testing.M) { // TestStreams tests various streaming upload scenarios func TestStreams(t *testing.T) { - dir, err := ioutil.TempDir("", "teleport-streams") - require.Nil(t, err) - defer os.RemoveAll(dir) + dir := t.TempDir() handler, err := NewHandler(Config{ Directory: dir, diff --git a/lib/events/gcssessions/gcsstream.go b/lib/events/gcssessions/gcsstream.go index 4663841b1eb9b..b1ef99f57892c 100644 --- a/lib/events/gcssessions/gcsstream.go +++ b/lib/events/gcssessions/gcsstream.go @@ -125,6 +125,11 @@ func (h *Handler) CompleteUpload(ctx context.Context, upload events.StreamUpload return convertGCSError(err) } + // If there are no parts to complete, move to cleanup + if len(parts) == 0 { + return h.cleanupUpload(ctx, upload) + } + objects := h.partsToObjects(upload, parts) for len(objects) > maxParts { h.Logger.Debugf("Got %v objects for upload %v, performing temp merge.", @@ -365,7 +370,7 @@ func uploadFromPath(path string) (*events.StreamUpload, error) { if err := sessionID.Check(); err != nil { return nil, trace.Wrap(err) } - parts := strings.Split(dir, slash) + parts := strings.Split(strings.TrimSuffix(dir, slash), slash) if len(parts) < 2 { return nil, trace.BadParameter("expected format uploads/, got %v", dir) } diff --git a/lib/events/gcssessions/gcsstream_test.go b/lib/events/gcssessions/gcsstream_test.go index cb578ae5d4b2b..7fb63ec05af59 100644 --- a/lib/events/gcssessions/gcsstream_test.go +++ b/lib/events/gcssessions/gcsstream_test.go @@ -34,6 +34,40 @@ import ( "github.com/gravitational/trace" ) +func TestUploadFromPath(t *testing.T) { + for _, test := range []struct { + path string + sessionID, uploadID string + assertErr require.ErrorAssertionFunc + }{ + { + path: "uploads/73de0358-2a40-4940-ae26-0c06877e35d9/cf9e08d5-6651-4ddd-a472-52d2286d6bb4.upload", + sessionID: "cf9e08d5-6651-4ddd-a472-52d2286d6bb4", + uploadID: "73de0358-2a40-4940-ae26-0c06877e35d9", + assertErr: require.NoError, + }, + { + path: "uploads/73de0358-2a40-4940-ae26-0c06877e35d9/cf9e08d5-6651-4ddd-a472-52d2286d6bb4.BADEXTENSION", + assertErr: require.Error, + }, + { + path: "no-dir.upload", + assertErr: require.Error, + }, + } { + t.Run(test.path, func(t *testing.T) { + upload, err := uploadFromPath(test.path) + test.assertErr(t, err) + if test.sessionID != "" { + require.Equal(t, test.sessionID, string(upload.SessionID)) + } + if test.uploadID != "" { + require.Equal(t, test.uploadID, upload.ID) + } + }) + } +} + // TestStreams tests various streaming upload scenarios func TestStreams(t *testing.T) { ctx := context.Background() diff --git a/lib/events/playback.go b/lib/events/playback.go index 82c47efda1bb7..cd39f1c1d5fd8 100644 --- a/lib/events/playback.go +++ b/lib/events/playback.go @@ -24,7 +24,6 @@ import ( "encoding/binary" "fmt" "io" - "io/ioutil" "os" "path/filepath" @@ -190,7 +189,7 @@ func (w *PlaybackWriter) SessionChunks() ([]byte, error) { return nil, trace.Wrap(err) } defer grChunk.Close() - stream, err = ioutil.ReadAll(grChunk) + stream, err = io.ReadAll(grChunk) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/events/s3sessions/s3stream.go b/lib/events/s3sessions/s3stream.go index 201723c01b529..bef290faff5b0 100644 --- a/lib/events/s3sessions/s3stream.go +++ b/lib/events/s3sessions/s3stream.go @@ -91,8 +91,25 @@ func (h *Handler) UploadPart(ctx context.Context, upload events.StreamUpload, pa return &events.StreamPart{ETag: *resp.ETag, Number: partNumber}, nil } +func (h *Handler) abortUpload(ctx context.Context, upload events.StreamUpload) error { + req := &s3.AbortMultipartUploadInput{ + Bucket: aws.String(h.Bucket), + Key: aws.String(h.path(upload.SessionID)), + UploadId: aws.String(upload.ID), + } + _, err := h.client.AbortMultipartUploadWithContext(ctx, req) + if err != nil { + return ConvertS3Error(err) + } + return nil +} + // CompleteUpload completes the upload func (h *Handler) CompleteUpload(ctx context.Context, upload events.StreamUpload, parts []events.StreamPart) error { + if len(parts) == 0 { + return h.abortUpload(ctx, upload) + } + start := time.Now() defer func() { h.Infof("UploadPart(%v) completed in %v.", upload.ID, time.Since(start)) }() diff --git a/lib/events/sessionlog.go b/lib/events/sessionlog.go index e365264f82b77..af95b69f52aa3 100644 --- a/lib/events/sessionlog.go +++ b/lib/events/sessionlog.go @@ -22,7 +22,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "os" "path/filepath" "sync" @@ -236,7 +235,7 @@ func (sl *DiskSessionLogger) finalize() error { // create a sentinel to signal completion signalFile := filepath.Join(sl.sessionDir, fmt.Sprintf("%v.completed", sl.SessionID.String())) - err := ioutil.WriteFile(signalFile, []byte("completed"), 0640) + err := os.WriteFile(signalFile, []byte("completed"), 0640) if err != nil { log.Warningf("Failed creating signal file: %v.", err) } @@ -601,7 +600,7 @@ func (f *gzipWriter) Close() error { var errors []error if f.Writer != nil { errors = append(errors, f.Writer.Close()) - f.Writer.Reset(ioutil.Discard) + f.Writer.Reset(io.Discard) writerPool.Put(f.Writer) f.Writer = nil } @@ -618,7 +617,7 @@ func (f *gzipWriter) Close() error { // internal buffers to avoid too many objects on the heap var writerPool = sync.Pool{ New: func() interface{} { - w, _ := gzip.NewWriterLevel(ioutil.Discard, gzip.BestSpeed) + w, _ := gzip.NewWriterLevel(io.Discard, gzip.BestSpeed) return w }, } diff --git a/lib/events/stream.go b/lib/events/stream.go index c00e31c1d4fa0..88957755bca63 100644 --- a/lib/events/stream.go +++ b/lib/events/stream.go @@ -22,7 +22,6 @@ import ( "encoding/binary" "errors" "io" - "io/ioutil" "sort" "sync" "time" @@ -966,7 +965,7 @@ func (r *ProtoReader) Read(ctx context.Context) (apievents.AuditEvent, error) { return nil, r.setError(trace.ConvertSystemError(err)) } r.padding = int64(binary.BigEndian.Uint64(r.sizeBytes[:Int64Size])) - gzipReader, err := newGzipReader(ioutil.NopCloser(io.LimitReader(r.reader, int64(partSize)))) + gzipReader, err := newGzipReader(io.NopCloser(io.LimitReader(r.reader, int64(partSize)))) if err != nil { return nil, r.setError(trace.Wrap(err)) } @@ -988,7 +987,7 @@ func (r *ProtoReader) Read(ctx context.Context) (apievents.AuditEvent, error) { return nil, r.setError(trace.ConvertSystemError(err)) } if r.padding != 0 { - skipped, err := io.CopyBuffer(ioutil.Discard, io.LimitReader(r.reader, r.padding), r.messageBytes[:]) + skipped, err := io.CopyBuffer(io.Discard, io.LimitReader(r.reader, r.padding), r.messageBytes[:]) if err != nil { return nil, r.setError(trace.ConvertSystemError(err)) } @@ -1074,6 +1073,8 @@ type MemoryUploader struct { uploads map[string]*MemoryUpload objects map[session.ID][]byte eventsC chan UploadEvent + + Clock clockwork.Clock } // MemoryUpload is used in tests @@ -1114,6 +1115,9 @@ func (m *MemoryUploader) CreateUpload(ctx context.Context, sessionID session.ID) ID: uuid.New().String(), SessionID: sessionID, } + if m.Clock != nil { + upload.Initiated = m.Clock.Now() + } m.uploads[upload.ID] = &MemoryUpload{ id: upload.ID, sessionID: sessionID, @@ -1158,7 +1162,7 @@ func (m *MemoryUploader) CompleteUpload(ctx context.Context, upload StreamUpload // UploadPart uploads part and returns the part func (m *MemoryUploader) UploadPart(ctx context.Context, upload StreamUpload, partNumber int64, partBody io.ReadSeeker) (*StreamPart, error) { - data, err := ioutil.ReadAll(partBody) + data, err := io.ReadAll(partBody) if err != nil { return nil, trace.Wrap(err) } @@ -1243,7 +1247,7 @@ func (m *MemoryUploader) Upload(ctx context.Context, sessionID session.ID, readC if ok { return "", trace.AlreadyExists("session %q already exists", sessionID) } - data, err := ioutil.ReadAll(readCloser) + data, err := io.ReadAll(readCloser) if err != nil { return "", trace.ConvertSystemError(err) } diff --git a/lib/events/test/streamsuite.go b/lib/events/test/streamsuite.go index e3f7810c31ff1..119d92caa53f1 100644 --- a/lib/events/test/streamsuite.go +++ b/lib/events/test/streamsuite.go @@ -16,7 +16,6 @@ package test import ( "context" - "io/ioutil" "os" "testing" "time" @@ -105,7 +104,7 @@ func StreamWithParameters(t *testing.T, handler events.MultipartHandler, params err = stream.Complete(ctx) require.Nil(t, err) - f, err := ioutil.TempFile("", string(sid)) + f, err := os.CreateTemp("", string(sid)) require.Nil(t, err) defer os.Remove(f.Name()) defer f.Close() @@ -172,7 +171,7 @@ func StreamResumeWithParameters(t *testing.T, handler events.MultipartHandler, p err = stream.Complete(ctx) require.Nil(t, err, "Complete after resume should succeed") - f, err := ioutil.TempFile("", string(sid)) + f, err := os.CreateTemp("", string(sid)) require.Nil(t, err) defer os.Remove(f.Name()) defer f.Close() diff --git a/lib/events/test/suite.go b/lib/events/test/suite.go index 5f0c95d47998a..46671c1687b34 100644 --- a/lib/events/test/suite.go +++ b/lib/events/test/suite.go @@ -21,7 +21,7 @@ import ( "bytes" "context" "encoding/json" - "io/ioutil" + "io" "os" "testing" "time" @@ -46,7 +46,7 @@ func UploadDownload(t *testing.T, handler events.MultipartHandler) { _, err := handler.Upload(context.TODO(), id, bytes.NewBuffer([]byte(val))) require.Nil(t, err) - f, err := ioutil.TempFile("", string(id)) + f, err := os.CreateTemp("", string(id)) require.Nil(t, err) defer os.Remove(f.Name()) defer f.Close() @@ -57,7 +57,7 @@ func UploadDownload(t *testing.T, handler events.MultipartHandler) { _, err = f.Seek(0, 0) require.Nil(t, err) - data, err := ioutil.ReadAll(f) + data, err := io.ReadAll(f) require.Nil(t, err) require.Equal(t, string(data), val) } @@ -66,7 +66,7 @@ func UploadDownload(t *testing.T, handler events.MultipartHandler) { func DownloadNotFound(t *testing.T, handler events.MultipartHandler) { id := session.NewID() - f, err := ioutil.TempFile("", string(id)) + f, err := os.CreateTemp("", string(id)) require.Nil(t, err) defer os.Remove(f.Name()) defer f.Close() diff --git a/lib/kube/kubeconfig/kubeconfig_test.go b/lib/kube/kubeconfig/kubeconfig_test.go index 46b3c1714e61b..571a5e7ac4b58 100644 --- a/lib/kube/kubeconfig/kubeconfig_test.go +++ b/lib/kube/kubeconfig/kubeconfig_test.go @@ -17,7 +17,6 @@ package kubeconfig import ( "crypto/x509/pkix" "fmt" - "io/ioutil" "os" "testing" "time" @@ -39,7 +38,7 @@ import ( ) func setup(t *testing.T) (string, clientcmdapi.Config) { - f, err := ioutil.TempFile("", "kubeconfig") + f, err := os.CreateTemp("", "kubeconfig") if err != nil { t.Fatalf("failed to create temp kubeconfig file: %v", err) } diff --git a/lib/kube/proxy/auth_test.go b/lib/kube/proxy/auth_test.go index df0854c33b21f..5b9b7b6a6499d 100644 --- a/lib/kube/proxy/auth_test.go +++ b/lib/kube/proxy/auth_test.go @@ -19,7 +19,6 @@ package proxy import ( "context" "errors" - "io/ioutil" "os" "testing" @@ -130,7 +129,7 @@ func TestGetKubeCreds(t *testing.T) { ctx := context.TODO() const teleClusterName = "teleport-cluster" - tmpFile, err := ioutil.TempFile("", "teleport") + tmpFile, err := os.CreateTemp("", "teleport") require.NoError(t, err) defer os.Remove(tmpFile.Name()) kubeconfigPath := tmpFile.Name() diff --git a/lib/kube/proxy/forwarder_test.go b/lib/kube/proxy/forwarder_test.go index 4483671f9d4a7..d208c3b30f096 100644 --- a/lib/kube/proxy/forwarder_test.go +++ b/lib/kube/proxy/forwarder_test.go @@ -902,7 +902,7 @@ func (ap mockAccessPoint) GetKubeServices(ctx context.Context) ([]types.Server, return ap.kubeServices, nil } -func (ap mockAccessPoint) GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { +func (ap mockAccessPoint) GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { var cas []types.CertAuthority for _, ca := range ap.cas { cas = append(cas, ca) @@ -910,7 +910,7 @@ func (ap mockAccessPoint) GetCertAuthorities(caType types.CertAuthType, loadKeys return cas, nil } -func (ap mockAccessPoint) GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { +func (ap mockAccessPoint) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { return ap.cas[id.DomainName], nil } diff --git a/lib/kube/proxy/roundtrip.go b/lib/kube/proxy/roundtrip.go index de92c54925780..491a9cccbe2f3 100644 --- a/lib/kube/proxy/roundtrip.go +++ b/lib/kube/proxy/roundtrip.go @@ -23,7 +23,6 @@ import ( "crypto/tls" "fmt" "io" - "io/ioutil" "net" "net/http" "net/url" @@ -210,7 +209,7 @@ func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (httpstream.Connec if (resp.StatusCode != http.StatusSwitchingProtocols) || !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(streamspdy.HeaderSpdy31)) { defer resp.Body.Close() responseError := "" - responseErrorBytes, err := ioutil.ReadAll(resp.Body) + responseErrorBytes, err := io.ReadAll(resp.Body) if err != nil { responseError = "unable to read error from server response" } else { diff --git a/lib/kube/proxy/sess.go b/lib/kube/proxy/sess.go index ec56a94bb3d31..e2fd43ee8f060 100644 --- a/lib/kube/proxy/sess.go +++ b/lib/kube/proxy/sess.go @@ -302,6 +302,9 @@ type session struct { // PresenceEnabled is set to true if MFA based presence is required. PresenceEnabled bool + + // Set if we should broadcast information about participant requirements to the session. + displayParticipantRequirements bool } // newSession creates a new session in pending mode. @@ -334,29 +337,39 @@ func newSession(ctx authContext, forwarder *Forwarder, req *http.Request, params } s := &session{ - ctx: ctx, - forwarder: forwarder, - req: req, - params: params, - id: id, - parties: make(map[uuid.UUID]*party), - partiesHistorical: make(map[uuid.UUID]*party), - log: log, - io: io, - state: types.SessionState_SessionStatePending, - accessEvaluator: accessEvaluator, - emitter: events.NewDiscardEmitter(), - tty: tty, - terminalSizeQueue: newMultiResizeQueue(), - started: false, - sess: sess, - closeC: make(chan struct{}), - initiator: initiator.ID, - expires: time.Now().UTC().Add(time.Hour * 24), - PresenceEnabled: ctx.Identity.GetIdentity().MFAVerified != "", - stateUpdate: sync.NewCond(&sync.Mutex{}), + ctx: ctx, + forwarder: forwarder, + req: req, + params: params, + id: id, + parties: make(map[uuid.UUID]*party), + partiesHistorical: make(map[uuid.UUID]*party), + log: log, + io: io, + state: types.SessionState_SessionStatePending, + accessEvaluator: accessEvaluator, + emitter: events.NewDiscardEmitter(), + tty: tty, + terminalSizeQueue: newMultiResizeQueue(), + started: false, + sess: sess, + closeC: make(chan struct{}), + initiator: initiator.ID, + expires: time.Now().UTC().Add(time.Hour * 24), + PresenceEnabled: ctx.Identity.GetIdentity().MFAVerified != "", + stateUpdate: sync.NewCond(&sync.Mutex{}), + displayParticipantRequirements: utils.AsBool(q.Get("displayParticipantRequirements")), } + go func() { + if _, open := <-s.io.TerminateNotifier(); open { + err := s.Close() + if err != nil { + s.log.Errorf("Failed to close session: %v.", err) + } + } + }() + err = s.trackerCreate(initiator, policySets) if err != nil { return nil, trace.Wrap(err) @@ -369,7 +382,7 @@ func newSession(ctx authContext, forwarder *Forwarder, req *http.Request, params // to fulfill the access requirements again. func (s *session) waitOnAccess() { s.io.Off() - s.io.BroadcastMessage("Session paused, Waiting for required participants...") + s.BroadcastMessage("Session paused, Waiting for required participants...") s.stateUpdate.L.Lock() defer s.stateUpdate.L.Unlock() @@ -388,7 +401,7 @@ outer: s.stateUpdate.Wait() } - s.io.BroadcastMessage("Resuming session...") + s.BroadcastMessage("Resuming session...") s.io.On() } @@ -432,10 +445,7 @@ func (s *session) launch() error { }() s.log.Debugf("Launching session: %v", s.id) - - if s.tty { - s.io.BroadcastMessage("Launching session...") - } + s.BroadcastMessage("Connecting to %v over K8S", s.podName) q := s.req.URL.Query() request := &remoteCommandRequest{ @@ -521,10 +531,7 @@ func (s *session) launch() error { case <-time.After(time.Until(s.expires)): s.mu.Lock() defer s.mu.Unlock() - - if s.tty { - s.io.BroadcastMessage("Session expired, closing...") - } + s.BroadcastMessage("Session expired, closing...") err := s.Close() if err != nil { @@ -898,10 +905,7 @@ func (s *session) join(p *party) error { } s.io.AddWriter(stringID, p.Client.stdoutStream()) - - if s.tty { - s.io.BroadcastMessage(fmt.Sprintf("User %v joined the session.", p.Ctx.User.GetName())) - } + s.BroadcastMessage("User %v joined the session.", p.Ctx.User.GetName()) if p.Mode == types.SessionModeratorMode { go func() { @@ -935,18 +939,30 @@ func (s *session) join(p *party) error { }() } else if !s.tty { return trace.AccessDenied("insufficient permissions to launch non-interactive session") - } else { - s.stateUpdate.L.Lock() - if s.state == types.SessionState_SessionStatePending { - s.io.BroadcastMessage("Waiting for required participants...") + } else if len(s.parties) == 1 { + base := "Waiting for required participants..." + + if s.displayParticipantRequirements { + s.BroadcastMessage(base+"\r\n%v", s.accessEvaluator.PrettyRequirementsList()) + } else { + s.BroadcastMessage(base) } - s.stateUpdate.L.Unlock() } } return nil } +func (s *session) BroadcastMessage(format string, args ...interface{}) { + if s.accessEvaluator.IsModerated() && s.tty { + err := s.io.BroadcastMessage(fmt.Sprintf(format, args...)) + + if err != nil { + s.log.Debugf("Failed to broadcast message: %v", err) + } + } +} + // leave removes a party from the session. func (s *session) leave(id uuid.UUID) error { s.stateUpdate.L.Lock() @@ -968,9 +984,7 @@ func (s *session) leave(id uuid.UUID) error { s.io.DeleteReader(stringID) s.io.DeleteWriter(stringID) - if s.tty { - s.io.BroadcastMessage(fmt.Sprintf("User %v left the session.", party.Ctx.User.GetName())) - } + s.BroadcastMessage("User %v left the session.", party.Ctx.User.GetName()) sessionLeaveEvent := &apievents.SessionLeave{ Metadata: apievents.Metadata{ @@ -1086,10 +1100,7 @@ func (s *session) Close() error { defer s.mu.Unlock() s.closeOnce.Do(func() { - if s.tty { - s.io.BroadcastMessage("Closing session...") - } - + s.BroadcastMessage("Closing session...") s.stateUpdate.L.Lock() defer s.stateUpdate.L.Unlock() s.state = types.SessionState_SessionStateTerminated diff --git a/lib/kube/utils/utils.go b/lib/kube/utils/utils.go index 93e926e81bdbf..f6f3928f0de12 100644 --- a/lib/kube/utils/utils.go +++ b/lib/kube/utils/utils.go @@ -21,6 +21,8 @@ import ( "encoding/hex" "sort" + "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/trace" @@ -151,11 +153,36 @@ type KubeServicesPresence interface { // KubeClusterNames returns a sorted list of unique kubernetes clusters // registered in p. +// +// DELETE IN 11.0.0, replaced by ListKubeClusterNamesWithFilters func KubeClusterNames(ctx context.Context, p KubeServicesPresence) ([]string, error) { kss, err := p.GetKubeServices(ctx) if err != nil { return nil, trace.Wrap(err) } + return extractAndSortKubeClusterNames(kss), nil +} + +// ListKubeClusterNamesWithFilters returns a sorted list of unique kubernetes clusters +// registered in p. +func ListKubeClusterNamesWithFilters(ctx context.Context, p client.ListResourcesClient, req proto.ListResourcesRequest) ([]string, error) { + req.ResourceType = types.KindKubeService + var kss []types.Server + + resources, err := client.GetResourcesWithFilters(ctx, p, req) + if err != nil { + return nil, trace.Wrap(err) + } + + kss, err = types.ResourcesWithLabels(resources).AsServers() + if err != nil { + return nil, trace.Wrap(err) + } + + return extractAndSortKubeClusterNames(kss), nil +} + +func extractAndSortKubeClusterNames(kss []types.Server) []string { kubeClusters := make(map[string]struct{}) for _, ks := range kss { for _, kc := range ks.GetKubernetesClusters() { @@ -167,7 +194,7 @@ func KubeClusterNames(ctx context.Context, p KubeServicesPresence) ([]string, er kubeClusterNames = append(kubeClusterNames, n) } sort.Strings(kubeClusterNames) - return kubeClusterNames, nil + return kubeClusterNames } // CheckOrSetKubeCluster validates kubeClusterName if it's set, or a sane diff --git a/lib/kube/utils/utils_test.go b/lib/kube/utils/utils_test.go index 5b4e387d2aa7e..dd29fdce70afb 100644 --- a/lib/kube/utils/utils_test.go +++ b/lib/kube/utils/utils_test.go @@ -120,3 +120,33 @@ func kubeService(kubeClusters ...string) types.Server { }, } } + +func TestExtractAndSortKubeClusterNames(t *testing.T) { + t.Parallel() + + server1, err := types.NewServer("foo", types.KindNode, types.ServerSpecV2{ + KubernetesClusters: []*types.KubernetesCluster{ + {Name: "watermelon"}, + }, + }) + require.NoError(t, err) + + server2, err := types.NewServer("foo", types.KindNode, types.ServerSpecV2{ + KubernetesClusters: []*types.KubernetesCluster{ + {Name: "watermelon"}, + }, + }) + require.NoError(t, err) + + server3, err := types.NewServer("bar", types.KindNode, types.ServerSpecV2{ + KubernetesClusters: []*types.KubernetesCluster{ + {Name: "banana"}, + {Name: "apple"}, + {Name: "pear"}, + }, + }) + require.NoError(t, err) + + names := extractAndSortKubeClusterNames([]types.Server{server1, server2, server3}) + require.Equal(t, []string{"apple", "banana", "pear", "watermelon"}, names) +} diff --git a/lib/multiplexer/multiplexer_test.go b/lib/multiplexer/multiplexer_test.go index 7272784d9ef0a..f87af998a11dc 100644 --- a/lib/multiplexer/multiplexer_test.go +++ b/lib/multiplexer/multiplexer_test.go @@ -26,7 +26,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net" "net/http" "net/http/httptest" @@ -122,7 +121,7 @@ func TestMux(t *testing.T) { re, err := client.Get(backend1.URL) require.Nil(t, err) defer re.Body.Close() - bytes, err := ioutil.ReadAll(re.Body) + bytes, err := io.ReadAll(re.Body) require.Nil(t, err) require.Equal(t, string(bytes), "backend 1") @@ -345,7 +344,7 @@ func TestMux(t *testing.T) { re, err := client.Get(backend1.URL) require.Nil(t, err) defer re.Body.Close() - bytes, err := ioutil.ReadAll(re.Body) + bytes, err := io.ReadAll(re.Body) require.Nil(t, err) require.Equal(t, string(bytes), "backend 1") @@ -482,7 +481,7 @@ func TestMux(t *testing.T) { re, err := client.Get(url) require.Nil(t, err) defer re.Body.Close() - bytes, err := ioutil.ReadAll(re.Body) + bytes, err := io.ReadAll(re.Body) require.Nil(t, err) require.Equal(t, string(bytes), "http backend") diff --git a/lib/restrictedsession/restricted_test.go b/lib/restrictedsession/restricted_test.go index 4c6e86291b841..4fabd1d73b7b9 100644 --- a/lib/restrictedsession/restricted_test.go +++ b/lib/restrictedsession/restricted_test.go @@ -23,7 +23,6 @@ import ( "context" "errors" "fmt" - "io/ioutil" "net" "os" "regexp" @@ -193,7 +192,7 @@ func (s *Suite) SetUpSuite(c *check.C) { } // Create temporary directory where cgroup2 hierarchy will be mounted. - s.cgroupDir, err = ioutil.TempDir("", "cgroup-test") + s.cgroupDir, err = os.MkdirTemp("", "cgroup-test") c.Assert(err, check.IsNil) // Create BPF service since we piggy-back on it diff --git a/lib/reversetunnel/agent.go b/lib/reversetunnel/agent.go index 8f040c6974b3a..a5c8b97fd1162 100644 --- a/lib/reversetunnel/agent.go +++ b/lib/reversetunnel/agent.go @@ -245,7 +245,7 @@ func (a *Agent) getPrincipalsList() []string { } func (a *Agent) getHostCheckers() ([]ssh.PublicKey, error) { - cas, err := a.AccessPoint.GetCertAuthorities(types.HostCA, false) + cas, err := a.AccessPoint.GetCertAuthorities(context.TODO(), types.HostCA, false) if err != nil { return nil, trace.Wrap(err) } @@ -264,7 +264,9 @@ func (a *Agent) getHostCheckers() ([]ssh.PublicKey, error) { // If this is Web Service port check if proxy support ALPN SNI Listener. func (a *Agent) getReverseTunnelDetails() *reverseTunnelDetails { pd := reverseTunnelDetails{TLSRoutingEnabled: false} - resp, err := webclient.Find(a.ctx, a.Addr.Addr, lib.IsInsecureDevMode(), nil) + resp, err := webclient.Find( + &webclient.Config{Context: a.ctx, ProxyAddr: a.Addr.Addr, Insecure: lib.IsInsecureDevMode()}) + if err != nil { // If TLS Routing is disabled the address is the proxy reverse tunnel // address the ping call will always fail. diff --git a/lib/reversetunnel/agent_test.go b/lib/reversetunnel/agent_test.go index 76597fbbb482e..dea1134680a57 100644 --- a/lib/reversetunnel/agent_test.go +++ b/lib/reversetunnel/agent_test.go @@ -89,7 +89,7 @@ type fakeClient struct { caKey ssh.PublicKey } -func (fc *fakeClient) GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { +func (fc *fakeClient) GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { ca, err := types.NewCertAuthority(types.CertAuthoritySpecV2{ Type: types.HostCA, ClusterName: "example.com", diff --git a/lib/reversetunnel/api.go b/lib/reversetunnel/api.go index 34fc9d572700a..952b1420ffc69 100644 --- a/lib/reversetunnel/api.go +++ b/lib/reversetunnel/api.go @@ -123,3 +123,18 @@ type Server interface { // Wait waits for server to close all outstanding operations Wait() } + +const ( + // NoApplicationTunnel is the error message returned when application + // reverse tunnel cannot be found. + // + // It usually happens when an app agent has shut down (or crashed) but + // hasn't expired from the backend yet. + NoApplicationTunnel = "could not find reverse tunnel, check that Application Service agent proxying this application is up and running" + // NoDatabaseTunnel is the error message returned when database reverse + // tunnel cannot be found. + // + // It usually happens when a database agent has shut down (or crashed) but + // hasn't expired from the backend yet. + NoDatabaseTunnel = "could not find reverse tunnel, check that Database Service agent proxying this database is up and running" +) diff --git a/lib/reversetunnel/conn.go b/lib/reversetunnel/conn.go index 63b58a148391d..557b585f2b8fa 100644 --- a/lib/reversetunnel/conn.go +++ b/lib/reversetunnel/conn.go @@ -17,7 +17,6 @@ limitations under the License. package reversetunnel import ( - "context" "fmt" "net" "sync" @@ -67,11 +66,6 @@ type remoteConn struct { // Used to make sure calling Close on the connection multiple times is safe. closed int32 - // closeContext and closeCancel are used to signal to any waiting goroutines - // that the remoteConn is now closed and to release any resources. - closeContext context.Context - closeCancel context.CancelFunc - // clock is used to control time in tests. clock clockwork.Clock @@ -115,8 +109,6 @@ func newRemoteConn(cfg *connConfig) *remoteConn { newProxiesC: make(chan []types.Server, 100), } - c.closeContext, c.closeCancel = context.WithCancel(context.Background()) - return c } @@ -125,8 +117,6 @@ func (c *remoteConn) String() string { } func (c *remoteConn) Close() error { - defer c.closeCancel() - // If the connection has already been closed, return right away. if !atomic.CompareAndSwapInt32(&c.closed, 0, 1) { return nil diff --git a/lib/reversetunnel/fake.go b/lib/reversetunnel/fake.go index ab01542329d30..c7e3a9bcca62c 100644 --- a/lib/reversetunnel/fake.go +++ b/lib/reversetunnel/fake.go @@ -18,9 +18,9 @@ package reversetunnel import ( "net" + "sync" "github.com/gravitational/teleport/lib/auth" - "github.com/gravitational/trace" ) @@ -51,12 +51,25 @@ type FakeRemoteSite struct { RemoteSite // Name is the remote site name. Name string - // ConnCh receives the connection when dialing this site. - ConnCh chan net.Conn // AccessPoint is the auth server client. AccessPoint auth.RemoteProxyAccessPoint // OfflineTunnels is a list of server IDs that will return connection error. OfflineTunnels map[string]struct{} + // connCh receives the connection when dialing this site. + connCh chan net.Conn + // closedMtx is a mutex that protects closed. + closedMtx sync.Mutex + // closed is set to true after the site is being closed. + closed bool +} + +// NewFakeRemoteSite is a FakeRemoteSite constructor. +func NewFakeRemoteSite(clusterName string, accessPoint auth.RemoteProxyAccessPoint) *FakeRemoteSite { + return &FakeRemoteSite{ + Name: clusterName, + connCh: make(chan net.Conn), + AccessPoint: accessPoint, + } } // CachingAccessPoint returns caching auth server client. @@ -69,13 +82,33 @@ func (s *FakeRemoteSite) GetName() string { return s.Name } +// ProxyConn returns proxy connection channel with incoming connections. +func (s *FakeRemoteSite) ProxyConn() <-chan net.Conn { + return s.connCh +} + // Dial returns the connection to the remote site. func (s *FakeRemoteSite) Dial(params DialParams) (net.Conn, error) { if _, ok := s.OfflineTunnels[params.ServerID]; ok { return nil, trace.ConnectionProblem(nil, "server %v tunnel is offline", params.ServerID) } + + s.closedMtx.Lock() + defer s.closedMtx.Unlock() + + if s.closed { + return nil, trace.ConnectionProblem(nil, "tunnel has been closed") + } + readerConn, writerConn := net.Pipe() - s.ConnCh <- readerConn + s.connCh <- readerConn return writerConn, nil } + +func (s *FakeRemoteSite) Close() { + s.closedMtx.Lock() + defer s.closedMtx.Unlock() + close(s.connCh) + s.closed = true +} diff --git a/lib/reversetunnel/localsite.go b/lib/reversetunnel/localsite.go index 3a33f1447dbe2..440c188f5779f 100644 --- a/lib/reversetunnel/localsite.go +++ b/lib/reversetunnel/localsite.go @@ -67,7 +67,7 @@ func newlocalSite(srv *server, domainName string, client auth.ClientI) (*localSi accessPoint: accessPoint, certificateCache: certificateCache, domainName: domainName, - remoteConns: make(map[connKey]*remoteConn), + remoteConns: make(map[connKey][]*remoteConn), clock: srv.Clock, log: log.WithFields(log.Fields{ trace.Component: teleport.ComponentReverseTunnelServer, @@ -89,8 +89,6 @@ func newlocalSite(srv *server, domainName string, client auth.ClientI) (*localSi // // it implements RemoteSite interface type localSite struct { - sync.Mutex - log log.FieldLogger domainName string srv *server @@ -104,8 +102,11 @@ type localSite struct { // certificateCache caches host certificates for the forwarding server. certificateCache *certificateCache - // remoteConns maps UUID and connection type to an remote connection. - remoteConns map[connKey]*remoteConn + // remoteConns maps UUID and connection type to remote connections, oldest to newest. + remoteConns map[connKey][]*remoteConn + + // remoteConnsMtx protects remoteConns. + remoteConnsMtx sync.Mutex // clock is used to control time in tests. clock clockwork.Clock @@ -117,8 +118,8 @@ type localSite struct { // GetTunnelsCount always the number of tunnel connections to this cluster. func (s *localSite) GetTunnelsCount() int { - s.Lock() - defer s.Unlock() + s.remoteConnsMtx.Lock() + defer s.remoteConnsMtx.Unlock() return len(s.remoteConns) } @@ -203,7 +204,7 @@ func (s *localSite) DialTCP(params DialParams) (net.Conn, error) { } s.log.Debugf("Succeeded dialing %v.", params) - return newEmitConn(s.srv.ctx, conn, s.client, s.srv.ID), nil + return conn, nil } // IsClosed always returns false because localSite is never closed. @@ -349,8 +350,8 @@ with the cluster.` } func (s *localSite) addConn(nodeID string, connType types.TunnelType, conn net.Conn, sconn ssh.Conn) (*remoteConn, error) { - s.Lock() - defer s.Unlock() + s.remoteConnsMtx.Lock() + defer s.remoteConnsMtx.Unlock() rconn := newRemoteConn(&connConfig{ conn: conn, @@ -365,7 +366,7 @@ func (s *localSite) addConn(nodeID string, connType types.TunnelType, conn net.C uuid: nodeID, connType: connType, } - s.remoteConns[key] = rconn + s.remoteConns[key] = append(s.remoteConns[key], rconn) return rconn, nil } @@ -374,10 +375,13 @@ func (s *localSite) addConn(nodeID string, connType types.TunnelType, conn net.C // list so that remote connection can notify the remote agent // about the list update func (s *localSite) fanOutProxies(proxies []types.Server) { - s.Lock() - defer s.Unlock() - for _, conn := range s.remoteConns { - conn.updateProxies(proxies) + s.remoteConnsMtx.Lock() + defer s.remoteConnsMtx.Unlock() + + for _, conns := range s.remoteConns { + for _, conn := range conns { + conn.updateProxies(proxies) + } } } @@ -446,14 +450,22 @@ func (s *localSite) handleHeartbeat(rconn *remoteConn, ch ssh.Channel, reqC <-ch } func (s *localSite) getRemoteConn(dreq *sshutils.DialReq) (*remoteConn, error) { - s.Lock() - defer s.Unlock() + s.remoteConnsMtx.Lock() + defer s.remoteConnsMtx.Unlock() // Loop over all connections and remove and invalid connections from the // connection map. - for key := range s.remoteConns { - if s.remoteConns[key].isInvalid() { + for key, conns := range s.remoteConns { + validConns := conns[:0] + for _, conn := range conns { + if !conn.isInvalid() { + validConns = append(validConns, conn) + } + } + if len(validConns) == 0 { delete(s.remoteConns, key) + } else { + s.remoteConns[key] = validConns } } @@ -461,15 +473,17 @@ func (s *localSite) getRemoteConn(dreq *sshutils.DialReq) (*remoteConn, error) { uuid: dreq.ServerID, connType: dreq.ConnType, } - rconn, ok := s.remoteConns[key] - if !ok { + if len(s.remoteConns[key]) == 0 { return nil, trace.NotFound("no %v reverse tunnel for %v found", dreq.ConnType, dreq.ServerID) } - if !rconn.isReady() { - return nil, trace.NotFound("%v is offline: no active %v tunnels found", dreq.ConnType, dreq.ServerID) - } - return rconn, nil + conns := s.remoteConns[key] + for i := len(conns) - 1; i >= 0; i-- { + if conns[i].isReady() { + return conns[i], nil + } + } + return nil, trace.NotFound("%v is offline: no active %v tunnels found", dreq.ConnType, dreq.ServerID) } func (s *localSite) chanTransportConn(rconn *remoteConn, dreq *sshutils.DialReq) (net.Conn, error) { diff --git a/lib/reversetunnel/localsite_test.go b/lib/reversetunnel/localsite_test.go new file mode 100644 index 0000000000000..34156bf046247 --- /dev/null +++ b/lib/reversetunnel/localsite_test.go @@ -0,0 +1,101 @@ +// Copyright 2022 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package reversetunnel + +import ( + "context" + "net" + "testing" + "time" + + "github.com/google/uuid" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/utils/sshutils" + "github.com/gravitational/teleport/lib/auth" + "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" +) + +func TestLocalSiteOverlap(t *testing.T) { + t.Parallel() + + // to stop (*localSite).periodicFunctions() + ctx, ctxCancel := context.WithCancel(context.Background()) + ctxCancel() + + srv := &server{ + ctx: ctx, + newAccessPoint: func(clt auth.ClientI, _ []string) (auth.RemoteProxyAccessPoint, error) { + return clt, nil + }, + } + + site, err := newlocalSite(srv, "clustername", &mockLocalSiteClient{}) + require.NoError(t, err) + + nodeID := uuid.NewString() + connType := types.NodeTunnel + dreq := &sshutils.DialReq{ + ServerID: nodeID, + ConnType: connType, + } + + conn1, err := site.addConn(nodeID, connType, mockRemoteConnConn{}, nil) + require.NoError(t, err) + + conn2, err := site.addConn(nodeID, connType, mockRemoteConnConn{}, nil) + require.NoError(t, err) + + c, err := site.getRemoteConn(dreq) + require.True(t, trace.IsNotFound(err)) + require.Nil(t, c) + + conn1.setLastHeartbeat(time.Now()) + c, err = site.getRemoteConn(dreq) + require.NoError(t, err) + require.Equal(t, conn1, c) + + conn2.setLastHeartbeat(time.Now()) + c, err = site.getRemoteConn(dreq) + require.NoError(t, err) + require.Equal(t, conn2, c) + + conn2.markInvalid(nil) + c, err = site.getRemoteConn(dreq) + require.NoError(t, err) + require.Equal(t, conn1, c) + + conn1.markInvalid(nil) + c, err = site.getRemoteConn(dreq) + require.True(t, trace.IsNotFound(err)) + require.Nil(t, c) +} + +type mockLocalSiteClient struct { + auth.Client +} + +// called by (*localSite).sshTunnelStats() as part of (*localSite).periodicFunctions() +func (mockLocalSiteClient) GetNodes(_ context.Context, _ string, _ ...services.MarshalOption) ([]types.Server, error) { + return nil, nil +} + +type mockRemoteConnConn struct { + net.Conn +} + +// called for logging by (*remoteConn).markInvalid() +func (mockRemoteConnConn) RemoteAddr() net.Addr { return nil } diff --git a/lib/reversetunnel/remotesite.go b/lib/reversetunnel/remotesite.go index 69fcb4192bde7..7fb01bea51cbf 100644 --- a/lib/reversetunnel/remotesite.go +++ b/lib/reversetunnel/remotesite.go @@ -89,7 +89,7 @@ type remoteSite struct { func (s *remoteSite) getRemoteClient() (auth.ClientI, bool, error) { // check if all cert authorities are initiated and if everything is OK - ca, err := s.srv.localAccessPoint.GetCertAuthority(types.CertAuthID{Type: types.HostCA, DomainName: s.domainName}, false) + ca, err := s.srv.localAccessPoint.GetCertAuthority(s.ctx, types.CertAuthID{Type: types.HostCA, DomainName: s.domainName}, false) if err != nil { return nil, false, trace.Wrap(err) } @@ -493,7 +493,7 @@ func (s *remoteSite) watchCertAuthorities() error { continue } - if err := s.remoteClient.RotateExternalCertAuthority(localCA); err != nil { + if err := s.remoteClient.RotateExternalCertAuthority(s.ctx, localCA); err != nil { s.WithError(err).Warn("Failed to rotate external ca") return trace.Wrap(err) } @@ -505,7 +505,7 @@ func (s *remoteSite) watchCertAuthorities() error { continue } - oldRemoteCA, err := s.localClient.GetCertAuthority(types.CertAuthID{ + oldRemoteCA, err := s.localClient.GetCertAuthority(s.ctx, types.CertAuthID{ Type: types.HostCA, DomainName: remoteCA.GetClusterName(), }, false) diff --git a/lib/reversetunnel/resolver.go b/lib/reversetunnel/resolver.go index a467f5214577b..528063a0acbc3 100644 --- a/lib/reversetunnel/resolver.go +++ b/lib/reversetunnel/resolver.go @@ -58,7 +58,9 @@ func WebClientResolver(ctx context.Context, addrs []utils.NetAddr, insecureTLS b for _, addr := range addrs { // In insecure mode, any certificate is accepted. In secure mode the hosts // CAs are used to validate the certificate on the proxy. - tunnelAddr, err := webclient.GetTunnelAddr(ctx, addr.String(), insecureTLS, nil) + tunnelAddr, err := webclient.GetTunnelAddr( + &webclient.Config{Context: ctx, ProxyAddr: addr.String(), Insecure: insecureTLS}) + if err != nil { errs = append(errs, err) continue diff --git a/lib/reversetunnel/srv.go b/lib/reversetunnel/srv.go index 67e16a01ee959..40eb296354719 100644 --- a/lib/reversetunnel/srv.go +++ b/lib/reversetunnel/srv.go @@ -724,7 +724,7 @@ func (s *server) findLocalCluster(sconn *ssh.ServerConn) (*localSite, error) { } func (s *server) getTrustedCAKeysByID(id types.CertAuthID) ([]ssh.PublicKey, error) { - ca, err := s.localAccessPoint.GetCertAuthority(id, false) + ca, err := s.localAccessPoint.GetCertAuthority(context.TODO(), id, false) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/reversetunnel/srv_test.go b/lib/reversetunnel/srv_test.go index e0747f0630dcc..792b39959735b 100644 --- a/lib/reversetunnel/srv_test.go +++ b/lib/reversetunnel/srv_test.go @@ -17,6 +17,7 @@ limitations under the License. package reversetunnel import ( + "context" "net" "testing" "time" @@ -155,6 +156,6 @@ type mockAccessPoint struct { ca types.CertAuthority } -func (ap mockAccessPoint) GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { +func (ap mockAccessPoint) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { return ap.ca, nil } diff --git a/lib/reversetunnel/transport.go b/lib/reversetunnel/transport.go index 65237ca5514e4..923a4a3cf8705 100644 --- a/lib/reversetunnel/transport.go +++ b/lib/reversetunnel/transport.go @@ -23,7 +23,6 @@ import ( "fmt" "io" "net" - "strings" "time" "golang.org/x/crypto/ssh" @@ -93,7 +92,8 @@ func (t *TunnelAuthDialer) DialContext(ctx context.Context, _, _ string) (net.Co } // Check if t.ProxyAddr is ProxyWebPort and remote Proxy supports TLS ALPNSNIListener. - resp, err := webclient.Find(ctx, addr.Addr, t.InsecureSkipTLSVerify, nil) + resp, err := webclient.Find( + &webclient.Config{Context: ctx, ProxyAddr: addr.Addr, Insecure: t.InsecureSkipTLSVerify}) if err != nil { // If TLS Routing is disabled the address is the proxy reverse tunnel // address thus the ping call will always fail. @@ -379,9 +379,13 @@ func (p *transport) getConn(servers []string, r *sshutils.DialReq) (net.Conn, bo return nil, false, trace.Wrap(err) } - // Connections to applications should never occur over a direct dial, return right away. - if r.ConnType == types.AppTunnel { - return nil, false, trace.ConnectionProblem(err, "failed to connect to application") + // Connections to applications and databases should never occur over + // a direct dial, return right away. + switch r.ConnType { + case types.AppTunnel: + return nil, false, trace.ConnectionProblem(err, NoApplicationTunnel) + case types.DatabaseTunnel: + return nil, false, trace.ConnectionProblem(err, NoDatabaseTunnel) } errTun := err @@ -441,7 +445,7 @@ func (p *transport) directDial(servers []string, serverID string) (net.Conn, err for _, addr := range servers { conn, err := net.Dial("tcp", addr) if err == nil { - return newEmitConn(p.closeContext, conn, p.emitter, strings.Split(serverID, ".")[0]), nil + return conn, nil } errors = append(errors, err) diff --git a/lib/service/connect.go b/lib/service/connect.go index 669dad410bffd..4f9a3fdad1f05 100644 --- a/lib/service/connect.go +++ b/lib/service/connect.go @@ -320,9 +320,9 @@ func (process *TeleportProcess) newWatcher(conn *Connector, watch types.Watch) ( // TLS client this method uses the local auth server. func (process *TeleportProcess) getCertAuthority(conn *Connector, id types.CertAuthID, loadPrivateKeys bool) (types.CertAuthority, error) { if conn.ClientIdentity.ID.Role == types.RoleAdmin || conn.ClientIdentity.ID.Role == types.RoleAuth { - return process.localAuth.GetCertAuthority(id, loadPrivateKeys) + return process.localAuth.GetCertAuthority(process.ExitContext(), id, loadPrivateKeys) } - return conn.Client.GetCertAuthority(id, loadPrivateKeys) + return conn.Client.GetCertAuthority(process.ExitContext(), id, loadPrivateKeys) } // reRegister receives new identity credentials for proxy, node and auth. diff --git a/lib/service/desktop.go b/lib/service/desktop.go index e281cbce997b7..8263c7d9f9fed 100644 --- a/lib/service/desktop.go +++ b/lib/service/desktop.go @@ -226,6 +226,7 @@ func (process *TeleportProcess) initWindowsDesktopServiceRegistered(log *logrus. LDAPConfig: desktop.LDAPConfig(cfg.WindowsDesktop.LDAP), DiscoveryBaseDN: cfg.WindowsDesktop.Discovery.BaseDN, DiscoveryLDAPFilters: cfg.WindowsDesktop.Discovery.Filters, + Hostname: cfg.Hostname, }) if err != nil { return trace.Wrap(err) diff --git a/lib/service/service.go b/lib/service/service.go index 0c64fb0f67ef6..4ce4847aa6b34 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -26,7 +26,6 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "net" "net/http" "net/http/httptest" @@ -1579,7 +1578,6 @@ func (process *TeleportProcess) newAccessCache(cfg accessCacheConfig) (*cache.Ca lite.Config{ Path: path, EventsOff: !cfg.events, - Memory: false, Mirror: true, PollStreamPeriod: 100 * time.Millisecond, }) @@ -2130,36 +2128,6 @@ func (process *TeleportProcess) initUploaderService(streamer events.Streamer, au } } - // DELETE IN (5.1.0) - // this uploader was superseded by filesessions.Uploader, - // see below - uploader, err := events.NewUploader(events.UploaderConfig{ - DataDir: filepath.Join(process.Config.DataDir, teleport.LogsDir), - Namespace: apidefaults.Namespace, - ServerID: teleport.ComponentUpload, - AuditLog: auditLog, - EventsC: process.Config.UploadEventsC, - }) - if err != nil { - return trace.Wrap(err) - } - process.RegisterFunc("uploader.service", func() error { - err := uploader.Serve() - if err != nil { - log.Errorf("Uploader server exited with error: %v.", err) - } - return nil - }) - - process.OnExit("uploader.shutdown", func(payload interface{}) { - log.Infof("Shutting down.") - warnOnErr(uploader.Stop(), log) - log.Infof("Exited.") - }) - - // This uploader supersedes the events.Uploader above, - // that is kept for backwards compatibility purposes for one release. - // Delete this comment once the uploader above is phased out. fileUploader, err := filesessions.NewUploader(filesessions.UploaderConfig{ ScanDir: filepath.Join(streamingDir...), Streamer: streamer, @@ -2218,7 +2186,7 @@ func (process *TeleportProcess) initMetricsService() error { pool := x509.NewCertPool() for _, caCertPath := range process.Config.Metrics.CACerts { - caCert, err := ioutil.ReadFile(caCertPath) + caCert, err := os.ReadFile(caCertPath) if err != nil { return trace.Wrap(err, "failed to read prometheus CA certificate %+v", caCertPath) } @@ -2394,6 +2362,11 @@ func (process *TeleportProcess) getAdditionalPrincipals(role types.SystemRole) ( principals = append(principals, process.Config.Hostname) } var addrs []utils.NetAddr + + // Add default DNSNames to the dnsNames list. + // For identities generated by teleport <= v6.1.6 the teleport.cluster.local DNS is not present + dnsNames = append(dnsNames, auth.DefaultDNSNamesForRole(role)...) + switch role { case types.RoleProxy: addrs = append(process.Config.Proxy.PublicAddrs, @@ -3219,7 +3192,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { var grpcServer *grpc.Server if alpnRouter != nil { - grpcServer := grpc.NewServer( + grpcServer = grpc.NewServer( grpc.ChainUnaryInterceptor( utils.ErrorConvertUnaryInterceptor, proxyLimiter.UnaryServerInterceptor(), @@ -3784,9 +3757,6 @@ func (process *TeleportProcess) initAuthStorage() (bk backend.Backend, err error if err != nil { return nil, trace.Wrap(err) } - if err := bk.Migrate(ctx); err != nil { - return nil, trace.Wrap(err) - } reporter, err := backend.NewReporter(backend.ReporterConfig{ Component: teleport.ComponentBackend, Backend: backend.NewSanitizer(bk), @@ -3828,12 +3798,18 @@ func (process *TeleportProcess) StartShutdown(ctx context.Context) context.Conte process.log.Warnf("Error waiting for all services to complete: %v", err) } process.log.Debug("All supervisor functions are completed.") - localAuth := process.getLocalAuth() - if localAuth != nil { - if err := process.localAuth.Close(); err != nil { + + if localAuth := process.getLocalAuth(); localAuth != nil { + if err := localAuth.Close(); err != nil { process.log.Warningf("Failed closing auth server: %v.", err) } } + + if process.storage != nil { + if err := process.storage.Close(); err != nil { + process.log.Warningf("Failed closing process storage: %v.", err) + } + } }() go process.printShutdownStatus(localCtx) return localCtx @@ -3855,9 +3831,9 @@ func (process *TeleportProcess) Close() error { process.Config.Keygen.Close() var errors []error - localAuth := process.getLocalAuth() - if localAuth != nil { - errors = append(errors, process.localAuth.Close()) + + if localAuth := process.getLocalAuth(); localAuth != nil { + errors = append(errors, localAuth.Close()) } if process.storage != nil { @@ -3878,7 +3854,7 @@ func validateConfig(cfg *Config) error { } if cfg.Console == nil { - cfg.Console = ioutil.Discard + cfg.Console = io.Discard } if len(cfg.AuthServers) == 0 { @@ -3932,10 +3908,10 @@ func initSelfSignedHTTPSCert(cfg *Config) (err error) { return trace.Wrap(err) } - if err := ioutil.WriteFile(keyPath, creds.PrivateKey, 0600); err != nil { + if err := os.WriteFile(keyPath, creds.PrivateKey, 0600); err != nil { return trace.Wrap(err, "error writing key PEM") } - if err := ioutil.WriteFile(certPath, creds.Cert, 0600); err != nil { + if err := os.WriteFile(certPath, creds.Cert, 0600); err != nil { return trace.Wrap(err, "error writing key PEM") } return nil @@ -4036,7 +4012,8 @@ func getPublicAddr(authClient auth.ReadAppsAccessPoint, a App) (string, error) { for { select { case <-ticker.C: - publicAddr, err := findPublicAddr(authClient, a) + publicAddr, err := app.FindPublicAddr(authClient, a.PublicAddr, a.Name) + if err == nil { return publicAddr, nil } @@ -4046,37 +4023,6 @@ func getPublicAddr(authClient auth.ReadAppsAccessPoint, a App) (string, error) { } } -// findPublicAddr tries to resolve the public address of the proxy of this cluster. -func findPublicAddr(authClient auth.ReadAppsAccessPoint, a App) (string, error) { - // If the application has a public address already set, use it. - if a.PublicAddr != "" { - return a.PublicAddr, nil - } - - // Fetch list of proxies, if first has public address set, use it. - servers, err := authClient.GetProxies() - if err != nil { - return "", trace.Wrap(err) - } - if len(servers) == 0 { - return "", trace.BadParameter("cluster has no proxied registered, at least one proxy must be registered for application access") - } - if servers[0].GetPublicAddr() != "" { - addr, err := utils.ParseAddr(servers[0].GetPublicAddr()) - if err != nil { - return "", trace.Wrap(err) - } - return fmt.Sprintf("%v.%v", a.Name, addr.Host()), nil - } - - // Fall back to cluster name. - cn, err := authClient.GetClusterName() - if err != nil { - return "", trace.Wrap(err) - } - return fmt.Sprintf("%v.%v", a.Name, cn.GetClusterName()), nil -} - // newHTTPFileSystem creates a new HTTP file system for the web handler. // It uses external configuration to make the decision func newHTTPFileSystem() (http.FileSystem, error) { diff --git a/lib/service/service_test.go b/lib/service/service_test.go index 0b7fa5fedc04e..8c233409bb238 100644 --- a/lib/service/service_test.go +++ b/lib/service/service_test.go @@ -18,7 +18,6 @@ package service import ( "context" "fmt" - "io/ioutil" "net/http" "os" "strings" @@ -88,15 +87,11 @@ func TestMonitor(t *testing.T) { cfg := MakeDefaultConfig() cfg.Clock = fakeClock var err error - cfg.DataDir, err = ioutil.TempDir("", "teleport") - require.NoError(t, err) - defer os.RemoveAll(cfg.DataDir) + cfg.DataDir = t.TempDir() cfg.DiagnosticAddr = utils.NetAddr{AddrNetwork: "tcp", Addr: "127.0.0.1:0"} cfg.AuthServers = []utils.NetAddr{{AddrNetwork: "tcp", Addr: "127.0.0.1:0"}} cfg.Auth.Enabled = true - cfg.Auth.StorageConfig.Params["path"], err = ioutil.TempDir("", "teleport") - require.NoError(t, err) - defer os.RemoveAll(cfg.DataDir) + cfg.Auth.StorageConfig.Params["path"] = t.TempDir() cfg.Auth.SSHAddr = utils.NetAddr{AddrNetwork: "tcp", Addr: "127.0.0.1:0"} cfg.Proxy.Enabled = false cfg.SSH.Enabled = false @@ -347,6 +342,8 @@ func TestGetAdditionalPrincipals(t *testing.T) { "proxy-kube-public-2", }, wantDNS: []string{ + "*.teleport.cluster.local", + "teleport.cluster.local", "*.proxy-public-1", "*.proxy-public-2", "*.proxy-kube-public-1", @@ -360,7 +357,10 @@ func TestGetAdditionalPrincipals(t *testing.T) { "auth-public-1", "auth-public-2", }, - wantDNS: []string{}, + wantDNS: []string{ + "*.teleport.cluster.local", + "teleport.cluster.local", + }, }, { role: types.RoleAdmin, @@ -369,7 +369,10 @@ func TestGetAdditionalPrincipals(t *testing.T) { "auth-public-1", "auth-public-2", }, - wantDNS: []string{}, + wantDNS: []string{ + "*.teleport.cluster.local", + "teleport.cluster.local", + }, }, { role: types.RoleNode, @@ -393,7 +396,10 @@ func TestGetAdditionalPrincipals(t *testing.T) { "kube-public-1", "kube-public-2", }, - wantDNS: []string{}, + wantDNS: []string{ + "*.teleport.cluster.local", + "teleport.cluster.local", + }, }, { role: types.RoleApp, @@ -401,7 +407,10 @@ func TestGetAdditionalPrincipals(t *testing.T) { "global-hostname", "global-uuid", }, - wantDNS: []string{}, + wantDNS: []string{ + "*.teleport.cluster.local", + "teleport.cluster.local", + }, }, { role: types.SystemRole("unknown"), diff --git a/lib/service/state.go b/lib/service/state.go index c391ae487d3b4..bc6e18685a87b 100644 --- a/lib/service/state.go +++ b/lib/service/state.go @@ -88,7 +88,7 @@ func (f *processState) update(event Event) { component, ok := event.Payload.(string) if !ok { - f.process.log.Errorf("TeleportDegradedEvent broadcasted without component name, this is a bug!") + f.process.log.Errorf("%v broadcasted without component name, this is a bug!", event.Name) return } s, ok := f.states[component] @@ -118,7 +118,7 @@ func (f *processState) update(event Event) { s.recoveryTime = f.process.Clock.Now() f.process.log.Infof("Teleport component %q is recovering from a degraded state.", component) case stateRecovering: - if f.process.Clock.Now().Sub(s.recoveryTime) > defaults.HeartbeatCheckPeriod*2 { + if f.process.Clock.Since(s.recoveryTime) > defaults.HeartbeatCheckPeriod*2 { s.state = stateOK f.process.log.Infof("Teleport component %q has recovered from a degraded state.", component) } diff --git a/lib/services/access_request_test.go b/lib/services/access_request_test.go index f1bbd5584fac0..e7a2fe16384f2 100644 --- a/lib/services/access_request_test.go +++ b/lib/services/access_request_test.go @@ -176,7 +176,7 @@ func TestReviewThresholds(t *testing.T) { roles := make(map[string]types.Role) for name, conditions := range roleDesc { - role, err := types.NewRole(name, types.RoleSpecV5{ + role, err := types.NewRoleV3(name, types.RoleSpecV5{ Allow: conditions, }) require.NoError(t, err) diff --git a/lib/services/database.go b/lib/services/database.go index eed6b7bea19a6..0d53cbe48cf8d 100644 --- a/lib/services/database.go +++ b/lib/services/database.go @@ -31,6 +31,7 @@ import ( "github.com/aws/aws-sdk-go/service/rds" "github.com/aws/aws-sdk-go/service/redshift" + "github.com/coreos/go-semver/semver" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" ) @@ -49,7 +50,7 @@ type Databases interface { DatabaseGetter // CreateDatabase creates a new database resource. CreateDatabase(context.Context, types.Database) error - // UpdateDatabse updates an existing database resource. + // UpdateDatabase updates an existing database resource. UpdateDatabase(context.Context, types.Database) error // DeleteDatabase removes the specified database resource. DeleteDatabase(ctx context.Context, name string) error @@ -290,7 +291,7 @@ func engineToProtocol(engine string) string { switch engine { case RDSEnginePostgres, RDSEngineAuroraPostgres: return defaults.ProtocolPostgres - case RDSEngineMySQL, RDSEngineAurora, RDSEngineAuroraMySQL: + case RDSEngineMySQL, RDSEngineAurora, RDSEngineAuroraMySQL, RDSEngineMariaDB: return defaults.ProtocolMySQL } return "" @@ -369,6 +370,27 @@ func rdsTagsToLabels(tags []*rds.Tag) map[string]string { return labels } +// IsRDSInstanceSupported returns true if database supports IAM authentication. +// Currently, only MariaDB is being checked. +func IsRDSInstanceSupported(instance *rds.DBInstance) bool { + // TODO(jakule): Check other engines. + if aws.StringValue(instance.Engine) != RDSEngineMariaDB { + return true + } + + // MariaDB follows semver schema: https://mariadb.org/about/ + ver, err := semver.NewVersion(aws.StringValue(instance.EngineVersion)) + if err != nil { + log.Errorf("Failed to parse RDS MariaDB version: %s", aws.StringValue(instance.EngineVersion)) + return false + } + + // Min supported MariaDB version that supports IAM is 10.6 + // https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html + minIAMSupportedVer := semver.New("10.6.0") + return !ver.LessThan(*minIAMSupportedVer) +} + // IsRDSClusterSupported checks whether the aurora cluster is supported and logs // related info if not. func IsRDSClusterSupported(cluster *rds.DBCluster) bool { @@ -390,6 +412,107 @@ func IsRDSClusterSupported(cluster *rds.DBCluster) bool { return true } +// IsRDSInstanceAvailable checks if the RDS instance is available. +func IsRDSInstanceAvailable(instance *rds.DBInstance) bool { + // For a full list of status values, see: + // https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/accessing-monitoring.html + switch aws.StringValue(instance.DBInstanceStatus) { + // Statuses marked as "Billed" in the above guide. + case "available", "backing-up", "configuring-enhanced-monitoring", + "configuring-iam-database-auth", "configuring-log-exports", + "converting-to-vpc", "incompatible-option-group", + "incompatible-parameters", "maintenance", "modifying", "moving-to-vpc", + "rebooting", "resetting-master-credentials", "renaming", "restore-error", + "storage-full", "storage-optimization", "upgrading": + return true + + // Statuses marked as "Not billed" in the above guide. + case "creating", "deleting", "failed", + "inaccessible-encryption-credentials", "incompatible-network", + "incompatible-restore": + return false + + // Statuses marked as "Billed for storage" in the above guide. + case "inaccessible-encryption-credentials-recoverable", "starting", + "stopped", "stopping": + return false + + // Statuses that have no billing information in the above guide, but + // believed to be unavailable. + case "insufficient-capacity": + return false + + default: + log.Warnf("Unknown status type: %q. Assuming RDS instance %q is available.", + aws.StringValue(instance.DBInstanceStatus), + aws.StringValue(instance.DBInstanceIdentifier), + ) + return true + } +} + +// IsRDSClusterAvailable checks if the RDS cluster is available. +func IsRDSClusterAvailable(cluster *rds.DBCluster) bool { + // For a full list of status values, see: + // https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/accessing-monitoring.html + switch aws.StringValue(cluster.Status) { + // Statuses marked as "Billed" in the above guide. + case "available", "backing-up", "backtracking", "failing-over", + "maintenance", "migrating", "modifying", "promoting", "renaming", + "resetting-master-credentials", "update-iam-db-auth", "upgrading": + return true + + // Statuses marked as "Not billed" in the above guide. + case "cloning-failed", "creating", "deleting", + "inaccessible-encryption-credentials", "migration-failed": + return false + + // Statuses marked as "Billed for storage" in the above guide. + case "starting", "stopped", "stopping": + return false + + default: + log.Warnf("Unknown status type: %q. Assuming Aurora cluster %q is available.", + aws.StringValue(cluster.Status), + aws.StringValue(cluster.DBClusterIdentifier), + ) + return true + } +} + +// IsRedshiftClusterAvailable checks if the Redshift cluster is available. +func IsRedshiftClusterAvailable(cluster *redshift.Cluster) bool { + // For a full list of status values, see: + // https://docs.aws.amazon.com/redshift/latest/mgmt/working-with-clusters.html#rs-mgmt-cluster-status + // + // Note that the Redshift guide does not specify billing information like + // the RDS and Aurora guides do. Most Redshift statuses are + // cross-referenced with similar statuses from RDS and Aurora guides to + // determine the availability. + // + // For "incompatible-xxx" statuses, the cluster is assumed to be available + // if the status is resulted by modifying the cluster, and the cluster is + // assumed to be unavailable if the cluster cannot be created or restored. + switch aws.StringValue(cluster.ClusterStatus) { + case "available", "available, prep-for-resize", "available, resize-cleanup", + "cancelling-resize", "final-snapshot", "modifying", "rebooting", + "renaming", "resizing", "rotating-keys", "storage-full", "updating-hsm", + "incompatible-parameters", "incompatible-hsm": + return true + + case "creating", "deleting", "hardware-failure", "paused", + "incompatible-network": + return false + + default: + log.Warnf("Unknown status type: %q. Assuming Redshift cluster %q is available.", + aws.StringValue(cluster.ClusterStatus), + aws.StringValue(cluster.ClusterIdentifier), + ) + return true + } +} + // auroraMySQLVersion extracts aurora mysql version from engine version func auroraMySQLVersion(cluster *rds.DBCluster) string { // version guide: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Updates.Versions.html @@ -435,6 +558,8 @@ const ( RDSEngineMySQL = "mysql" // RDSEnginePostgres is RDS engine name for Postgres instances. RDSEnginePostgres = "postgres" + // RDSEngineMariaDB is RDS engine name for MariaDB instances. + RDSEngineMariaDB = "mariadb" // RDSEngineAurora is RDS engine name for Aurora MySQL 5.6 compatible clusters. RDSEngineAurora = "aurora" // RDSEngineAuroraMySQL is RDS engine name for Aurora MySQL 5.7 compatible clusters. @@ -452,9 +577,9 @@ const ( // RDSEndpointTypeReader is the endpoint that load-balances connections across the Aurora Replicas that are // available in a RDS cluster. RDSEndpointTypeReader RDSEndpointType = "reader" - // RDSEndpointTypeCustom is the endpoint that specifieds one of the custom endpoints associated with the RDS cluster. + // RDSEndpointTypeCustom is the endpoint that specifies one of the custom endpoints associated with the RDS cluster. RDSEndpointTypeCustom RDSEndpointType = "custom" - // RDSEndpointTypeInstance is the endpoint of a RDS DB instance. + // RDSEndpointTypeInstance is the endpoint of an RDS DB instance. RDSEndpointTypeInstance RDSEndpointType = "instance" ) diff --git a/lib/services/database_test.go b/lib/services/database_test.go index c16862d506f3a..0e50fc7243930 100644 --- a/lib/services/database_test.go +++ b/lib/services/database_test.go @@ -358,8 +358,57 @@ func TestIsRDSClusterSupported(t *testing.T) { EngineVersion: aws.String(test.engineVersion), } - require.Equal(t, test.isSupported, IsRDSClusterSupported(cluster)) + got, want := IsRDSClusterSupported(cluster), test.isSupported + require.Equal(t, want, got, "IsRDSClusterSupported = %v, want = %v", got, want) + }) + } +} + +func TestIsRDSInstanceSupported(t *testing.T) { + tests := []struct { + name string + engine string + engineVersion string + isSupported bool + }{ + { + name: "non-MariaDB engine", + engine: RDSEnginePostgres, + engineVersion: "13.3", + isSupported: true, + }, + { + name: "unsupported MariaDB", + engine: RDSEngineMariaDB, + engineVersion: "10.3.28", + isSupported: false, + }, + { + name: "min supported version", + engine: RDSEngineMariaDB, + engineVersion: "10.6.2", + isSupported: true, + }, + { + name: "supported version", + engine: RDSEngineMariaDB, + engineVersion: "10.8.0", + isSupported: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cluster := &rds.DBInstance{ + DBInstanceArn: aws.String("arn:aws:rds:us-east-1:1234567890:instance:test"), + DBClusterIdentifier: aws.String(test.name), + DbiResourceId: aws.String(uuid.New().String()), + Engine: aws.String(test.engine), + EngineVersion: aws.String(test.engineVersion), + } + got, want := IsRDSInstanceSupported(cluster), test.isSupported + require.Equal(t, want, got, "IsRDSInstanceSupported = %v, want = %v", got, want) }) } } diff --git a/lib/services/desktop.go b/lib/services/desktop.go index a4cc25b01e340..5ef6089109d17 100644 --- a/lib/services/desktop.go +++ b/lib/services/desktop.go @@ -33,6 +33,7 @@ type WindowsDesktops interface { UpsertWindowsDesktop(ctx context.Context, desktop types.WindowsDesktop) error DeleteWindowsDesktop(ctx context.Context, hostID, name string) error DeleteAllWindowsDesktops(context.Context) error + ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error) } // MarshalWindowsDesktop marshals the WindowsDesktop resource to JSON. diff --git a/lib/services/local/desktops.go b/lib/services/local/desktops.go index 3e0f78f381fb5..1eae39b1cc566 100644 --- a/lib/services/local/desktops.go +++ b/lib/services/local/desktops.go @@ -163,6 +163,73 @@ func (s *WindowsDesktopService) DeleteAllWindowsDesktops(ctx context.Context) er return nil } +// ListWindowsDesktops returns all Windows desktops matching filter. +func (s *WindowsDesktopService) ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error) { + reqLimit := req.Limit + if reqLimit <= 0 { + return nil, trace.BadParameter("nonpositive parameter limit") + } + + keyPrefix := []string{windowsDesktopsPrefix} + rangeStart := backend.Key(append(keyPrefix, req.StartKey)...) + rangeEnd := backend.RangeEnd(backend.Key(keyPrefix...)) + filter := services.MatchResourceFilter{ + ResourceKind: types.KindWindowsDesktop, + Labels: req.Labels, + SearchKeywords: req.SearchKeywords, + PredicateExpression: req.PredicateExpression, + } + + // Get most limit+1 results to determine if there will be a next key. + maxLimit := reqLimit + 1 + var desktops []types.WindowsDesktop + if err := backend.IterateRange(ctx, s.Backend, rangeStart, rangeEnd, maxLimit, func(items []backend.Item) (stop bool, err error) { + for _, item := range items { + if len(desktops) == maxLimit { + break + } + + desktop, err := services.UnmarshalWindowsDesktop(item.Value, + services.WithResourceID(item.ID), services.WithExpires(item.Expires)) + if err != nil { + return false, trace.Wrap(err) + } + + if !req.WindowsDesktopFilter.Match(desktop) { + continue + } + + switch match, err := services.MatchResourceByFilters(desktop, filter); { + case err != nil: + return false, trace.Wrap(err) + case match: + desktops = append(desktops, desktop) + } + } + + return len(desktops) == maxLimit, nil + }); err != nil { + return nil, trace.Wrap(err) + } + + // If both HostID and Name are set in the filter only one desktop should be expected + if req.HostID != "" && req.Name != "" && len(desktops) == 0 { + return nil, trace.NotFound("windows desktop \"%s/%s\" doesn't exist", req.HostID, req.Name) + } + + var nextKey string + if len(desktops) > reqLimit { + nextKey = backend.GetPaginationKey(desktops[len(desktops)-1]) + // Truncate the last item that was used to determine next row existence. + desktops = desktops[:reqLimit] + } + + return &types.ListWindowsDesktopsResponse{ + Desktops: desktops, + NextKey: nextKey, + }, nil +} + const ( windowsDesktopsPrefix = "windowsDesktop" ) diff --git a/lib/services/local/desktops_test.go b/lib/services/local/desktops_test.go new file mode 100644 index 0000000000000..3500b8b53b73f --- /dev/null +++ b/lib/services/local/desktops_test.go @@ -0,0 +1,213 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package local + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/backend" + "github.com/gravitational/teleport/lib/backend/lite" + + "github.com/jonboulle/clockwork" + "github.com/stretchr/testify/require" +) + +func TestListWindowsDesktops(t *testing.T) { + t.Parallel() + ctx := context.Background() + + liteBackend, err := lite.NewWithConfig(ctx, lite.Config{ + Path: t.TempDir(), + Clock: clockwork.NewFakeClock(), + }) + require.NoError(t, err) + + service := NewWindowsDesktopService(liteBackend) + + // Initially we expect no desktops. + out, err := service.ListWindowsDesktops(ctx, types.ListWindowsDesktopsRequest{ + Limit: 10, + }) + require.NoError(t, err) + require.Empty(t, out.Desktops) + require.Empty(t, out.NextKey) + + // Upsert some windows desktops. + + // With label. + testLabel := map[string]string{"env": "test"} + d1, err := types.NewWindowsDesktopV3("apple", testLabel, types.WindowsDesktopSpecV3{Addr: "_"}) + require.NoError(t, err) + require.NoError(t, service.CreateWindowsDesktop(ctx, d1)) + + d2, err := types.NewWindowsDesktopV3("banana", testLabel, types.WindowsDesktopSpecV3{Addr: "_"}) + require.NoError(t, err) + require.NoError(t, service.CreateWindowsDesktop(ctx, d2)) + + // Without labels. + d3, err := types.NewWindowsDesktopV3("carrot", nil, types.WindowsDesktopSpecV3{Addr: "_", HostID: "test-host-id"}) + require.NoError(t, err) + require.NoError(t, service.CreateWindowsDesktop(ctx, d3)) + + // Test fetch all. + out, err = service.ListWindowsDesktops(ctx, types.ListWindowsDesktopsRequest{ + Limit: 10, + }) + require.NoError(t, err) + require.Empty(t, out.NextKey) + require.Empty(t, cmp.Diff([]types.WindowsDesktop{d1, d2, d3}, out.Desktops, + cmpopts.IgnoreFields(types.Metadata{}, "ID"), + )) + + // Test pagination. + + // First fetch. + resp, err := service.ListWindowsDesktops(ctx, types.ListWindowsDesktopsRequest{ + Limit: 1, + }) + require.NoError(t, err) + require.Len(t, resp.Desktops, 1) + require.Equal(t, out.Desktops[0], resp.Desktops[0]) + require.Equal(t, backend.GetPaginationKey(out.Desktops[1]), resp.NextKey) + + // Middle fetch. + resp, err = service.ListWindowsDesktops(ctx, types.ListWindowsDesktopsRequest{ + Limit: 1, + StartKey: resp.NextKey, + }) + require.NoError(t, err) + require.Len(t, resp.Desktops, 1) + require.Equal(t, out.Desktops[1], resp.Desktops[0]) + require.Equal(t, backend.GetPaginationKey(out.Desktops[2]), resp.NextKey) + + // Last fetch. + resp, err = service.ListWindowsDesktops(ctx, types.ListWindowsDesktopsRequest{ + Limit: 1, + StartKey: resp.NextKey, + }) + require.NoError(t, err) + require.Len(t, resp.Desktops, 1) + require.Equal(t, out.Desktops[2], resp.Desktops[0]) + require.Empty(t, resp.NextKey) +} + +func TestListWindowsDesktops_Filters(t *testing.T) { + t.Parallel() + ctx := context.Background() + + liteBackend, err := lite.NewWithConfig(ctx, lite.Config{ + Path: t.TempDir(), + Clock: clockwork.NewFakeClock(), + }) + require.NoError(t, err) + + service := NewWindowsDesktopService(liteBackend) + + // Upsert some windows desktops. + + // With label. + testLabel := map[string]string{"env": "test"} + d1, err := types.NewWindowsDesktopV3("banana", testLabel, types.WindowsDesktopSpecV3{Addr: "_", HostID: "test-host-id"}) + require.NoError(t, err) + require.NoError(t, service.CreateWindowsDesktop(ctx, d1)) + + d2, err := types.NewWindowsDesktopV3("banana", testLabel, types.WindowsDesktopSpecV3{Addr: "_"}) + require.NoError(t, err) + require.NoError(t, service.CreateWindowsDesktop(ctx, d2)) + + // Without labels. + d3, err := types.NewWindowsDesktopV3("carrot", nil, types.WindowsDesktopSpecV3{Addr: "_", HostID: "test-host-id"}) + require.NoError(t, err) + require.NoError(t, service.CreateWindowsDesktop(ctx, d3)) + + tests := []struct { + name string + filter types.ListWindowsDesktopsRequest + wantErr bool + }{ + { + name: "NOK non-matching host id and name", + filter: types.ListWindowsDesktopsRequest{ + Limit: 10, + WindowsDesktopFilter: types.WindowsDesktopFilter{ + HostID: "no-match", + Name: "no-match", + }, + }, + wantErr: true, + }, + { + name: "NOK invalid limit", + filter: types.ListWindowsDesktopsRequest{}, + wantErr: true, + }, + { + name: "matching host id", + filter: types.ListWindowsDesktopsRequest{ + Limit: 5, + WindowsDesktopFilter: types.WindowsDesktopFilter{HostID: "test-host-id"}, + }, + }, + { + name: "matching name", + filter: types.ListWindowsDesktopsRequest{ + Limit: 5, + WindowsDesktopFilter: types.WindowsDesktopFilter{Name: "banana"}, + }, + }, + { + name: "with search", + filter: types.ListWindowsDesktopsRequest{ + Limit: 5, + SearchKeywords: []string{"env", "test"}, + }, + }, + { + name: "with labels", + filter: types.ListWindowsDesktopsRequest{ + Limit: 5, + Labels: testLabel, + }, + }, + { + name: "with predicate", + filter: types.ListWindowsDesktopsRequest{ + Limit: 5, + PredicateExpression: `labels.env == "test"`, + }, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + resp, err := service.ListWindowsDesktops(ctx, tc.filter) + + if tc.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Len(t, resp.Desktops, 2) + } + }) + } +} diff --git a/lib/services/local/perf_test.go b/lib/services/local/perf_test.go index 8f790824c40de..8b05a159ae686 100644 --- a/lib/services/local/perf_test.go +++ b/lib/services/local/perf_test.go @@ -19,8 +19,6 @@ package local import ( "context" "fmt" - "io/ioutil" - "os" "testing" apidefaults "github.com/gravitational/teleport/api/defaults" @@ -70,9 +68,7 @@ func BenchmarkGetNodes(b *testing.B) { bk, err = memory.New(memory.Config{}) require.NoError(b, err) } else { - dir, err := ioutil.TempDir("", "teleport") - require.NoError(b, err) - defer os.RemoveAll(dir) + dir := b.TempDir() bk, err = lite.NewWithConfig(context.TODO(), lite.Config{ Path: dir, diff --git a/lib/services/local/presence.go b/lib/services/local/presence.go index 4e4b37cd2b8c7..b2d4f8b8f3b5d 100644 --- a/lib/services/local/presence.go +++ b/lib/services/local/presence.go @@ -798,6 +798,13 @@ func (s *PresenceService) DeleteAllRemoteClusters() error { return trace.Wrap(err) } +// this combination of backoff parameters leads to worst-case total time spent +// in backoff between 1200ms and 2400ms depending on jitter. tests are in +// place to verify that this is sufficient to resolve a 20-lease contention +// event, which is worse than should ever occur in practice. +const baseBackoff = time.Millisecond * 300 +const leaseRetryAttempts int64 = 6 + // AcquireSemaphore attempts to acquire the specified semaphore. AcquireSemaphore will automatically handle // retry on contention. If the semaphore has already reached MaxLeases, or there is too much contention, // a LimitExceeded error is returned (contention in this context means concurrent attempts to update the @@ -805,13 +812,6 @@ func (s *PresenceService) DeleteAllRemoteClusters() error { // is the only semaphore method that handles retries internally. This is because this method both blocks // user-facing operations, and contains multiple different potential contention points. func (s *PresenceService) AcquireSemaphore(ctx context.Context, req types.AcquireSemaphoreRequest) (*types.SemaphoreLease, error) { - // this combination of backoff parameters leads to worst-case total time spent - // in backoff between 1200ms and 2400ms depending on jitter. tests are in - // place to verify that this is sufficient to resolve a 20-lease contention - // event, which is worse than should ever occur in practice. - const baseBackoff = time.Millisecond * 300 - const acquireAttempts int64 = 6 - if err := req.Check(); err != nil { return nil, trace.Wrap(err) } @@ -826,7 +826,7 @@ func (s *PresenceService) AcquireSemaphore(ctx context.Context, req types.Acquir key := backend.Key(semaphoresPrefix, req.SemaphoreKind, req.SemaphoreName) Acquire: - for i := int64(0); i < acquireAttempts; i++ { + for i := int64(0); i < leaseRetryAttempts; i++ { if i > 0 { // Not our first attempt, apply backoff. If we knew that we were only in // contention with one other acquire attempt we could retry immediately @@ -997,40 +997,59 @@ func (s *PresenceService) CancelSemaphoreLease(ctx context.Context, lease types. return trace.BadParameter("the lease %v has expired at %v", lease.LeaseID, lease.Expires) } - key := backend.Key(semaphoresPrefix, lease.SemaphoreKind, lease.SemaphoreName) - item, err := s.Get(ctx, key) - if err != nil { - return trace.Wrap(err) - } + for i := int64(0); i < leaseRetryAttempts; i++ { + if i > 0 { + // Not our first attempt, apply backoff. If we knew that we were only in + // contention with one other cancel attempt we could retry immediately + // since we got here because some other attempt *succeeded*. It is safer, + // however, to assume that we are under high contention and attempt to + // spread out retries via random backoff. + select { + case <-time.After(s.jitter(baseBackoff * time.Duration(i))): + case <-ctx.Done(): + return trace.Wrap(ctx.Err()) + } + } - sem, err := services.UnmarshalSemaphore(item.Value) - if err != nil { - return trace.Wrap(err) - } + key := backend.Key(semaphoresPrefix, lease.SemaphoreKind, lease.SemaphoreName) + item, err := s.Get(ctx, key) + if err != nil { + return trace.Wrap(err) + } - if err := sem.Cancel(lease); err != nil { - return trace.Wrap(err) - } + sem, err := services.UnmarshalSemaphore(item.Value) + if err != nil { + return trace.Wrap(err) + } - newValue, err := services.MarshalSemaphore(sem) - if err != nil { - return trace.Wrap(err) - } + if err := sem.Cancel(lease); err != nil { + return trace.Wrap(err) + } - newItem := backend.Item{ - Key: key, - Value: newValue, - Expires: sem.Expiry(), - } + newValue, err := services.MarshalSemaphore(sem) + if err != nil { + return trace.Wrap(err) + } - _, err = s.CompareAndSwap(ctx, *item, newItem) - if err != nil { - if trace.IsCompareFailed(err) { - return trace.CompareFailed("semaphore %v/%v has been concurrently updated, try again", sem.GetSubKind(), sem.GetName()) + newItem := backend.Item{ + Key: key, + Value: newValue, + Expires: sem.Expiry(), + } + + _, err = s.CompareAndSwap(ctx, *item, newItem) + switch { + case err == nil: + return nil + case trace.IsCompareFailed(err): + // semaphore was concurrently updated + continue + default: + return trace.Wrap(err) } - return trace.Wrap(err) } - return nil + + return trace.LimitExceeded("too much contention on semaphore %s/%s", lease.SemaphoreKind, lease.SemaphoreName) } // GetSemaphores returns all semaphores matching the supplied filter. diff --git a/lib/services/local/presence_test.go b/lib/services/local/presence_test.go index ae9a152cea679..88c3e91592d0a 100644 --- a/lib/services/local/presence_test.go +++ b/lib/services/local/presence_test.go @@ -27,7 +27,6 @@ import ( "github.com/google/uuid" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" - "gopkg.in/check.v1" "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" @@ -40,26 +39,14 @@ import ( "github.com/gravitational/trace" ) -type PresenceSuite struct { - bk backend.Backend -} - -var _ = check.Suite(&PresenceSuite{}) - -func (s *PresenceSuite) SetUpTest(c *check.C) { - var err error - - s.bk, err = lite.New(context.TODO(), backend.Params{"path": c.MkDir()}) - c.Assert(err, check.IsNil) -} +func TestTrustedClusterCRUD(t *testing.T) { + ctx := context.Background() -func (s *PresenceSuite) TearDownTest(c *check.C) { - c.Assert(s.bk.Close(), check.IsNil) -} + bk, err := lite.New(ctx, backend.Params{"path": t.TempDir()}) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, bk.Close()) }) -func (s *PresenceSuite) TestTrustedClusterCRUD(c *check.C) { - ctx := context.Background() - presenceBackend := NewPresenceService(s.bk) + presenceBackend := NewPresenceService(bk) tc, err := types.NewTrustedCluster("foo", types.TrustedClusterSpecV2{ Enabled: true, @@ -68,7 +55,7 @@ func (s *PresenceSuite) TestTrustedClusterCRUD(c *check.C) { ProxyAddress: "quux", ReverseTunnelAddress: "quuz", }) - c.Assert(err, check.IsNil) + require.NoError(t, err) // we just insert this one for get all stc, err := types.NewTrustedCluster("bar", types.TrustedClusterSpecV2{ @@ -78,37 +65,37 @@ func (s *PresenceSuite) TestTrustedClusterCRUD(c *check.C) { ProxyAddress: "quuz", ReverseTunnelAddress: "corge", }) - c.Assert(err, check.IsNil) + require.NoError(t, err) // create trusted clusters _, err = presenceBackend.UpsertTrustedCluster(ctx, tc) - c.Assert(err, check.IsNil) + require.NoError(t, err) _, err = presenceBackend.UpsertTrustedCluster(ctx, stc) - c.Assert(err, check.IsNil) + require.NoError(t, err) // get trusted cluster make sure it's correct gotTC, err := presenceBackend.GetTrustedCluster(ctx, "foo") - c.Assert(err, check.IsNil) - c.Assert(gotTC.GetName(), check.Equals, "foo") - c.Assert(gotTC.GetEnabled(), check.Equals, true) - c.Assert(gotTC.GetRoles(), check.DeepEquals, []string{"bar", "baz"}) - c.Assert(gotTC.GetToken(), check.Equals, "qux") - c.Assert(gotTC.GetProxyAddress(), check.Equals, "quux") - c.Assert(gotTC.GetReverseTunnelAddress(), check.Equals, "quuz") + require.NoError(t, err) + require.Equal(t, "foo", gotTC.GetName()) + require.True(t, gotTC.GetEnabled()) + require.EqualValues(t, []string{"bar", "baz"}, gotTC.GetRoles()) + require.Equal(t, "qux", gotTC.GetToken()) + require.Equal(t, "quux", gotTC.GetProxyAddress()) + require.Equal(t, "quuz", gotTC.GetReverseTunnelAddress()) // get all clusters allTC, err := presenceBackend.GetTrustedClusters(ctx) - c.Assert(err, check.IsNil) - c.Assert(allTC, check.HasLen, 2) + require.NoError(t, err) + require.Len(t, allTC, 2) // delete cluster err = presenceBackend.DeleteTrustedCluster(ctx, "foo") - c.Assert(err, check.IsNil) + require.NoError(t, err) // make sure it's really gone _, err = presenceBackend.GetTrustedCluster(ctx, "foo") - c.Assert(err, check.NotNil) - c.Assert(trace.IsNotFound(err), check.Equals, true) + require.Error(t, err) + require.ErrorIs(t, err, trace.NotFound("key /trustedclusters/foo is not found")) } // TestApplicationServersCRUD verifies backend operations on app servers. @@ -1130,3 +1117,63 @@ func TestFakePaginate_TotalCount(t *testing.T) { require.Equal(t, 3, resp.TotalCount) }) } + +func TestPresenceService_CancelSemaphoreLease(t *testing.T) { + ctx := context.Background() + bk, err := lite.New(ctx, backend.Params{"path": t.TempDir()}) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, bk.Close()) }) + presence := NewPresenceService(bk) + + maxLeases := 5 + leases := make([]*types.SemaphoreLease, maxLeases) + + // Acquire max number of leases + request := types.AcquireSemaphoreRequest{ + SemaphoreKind: "test", + SemaphoreName: "test", + MaxLeases: int64(maxLeases), + Expires: time.Now().Add(time.Hour), + Holder: "test", + } + for i := range leases { + lease, err := presence.AcquireSemaphore(ctx, request) + require.NoError(t, err) + require.NotNil(t, lease) + + leases[i] = lease + } + + // Validate a semaphore exists with the correct number of leases + semaphores, err := presence.GetSemaphores(ctx, types.SemaphoreFilter{ + SemaphoreKind: "test", + SemaphoreName: "test", + }) + require.NoError(t, err) + require.Len(t, semaphores, 1) + require.Len(t, semaphores[0].LeaseRefs(), maxLeases) + + // Cancel the leases concurrently and ensure that all + // cancellations are honored + errCh := make(chan error, maxLeases) + for _, l := range leases { + l := l + go func() { + errCh <- presence.CancelSemaphoreLease(ctx, *l) + }() + } + + for i := 0; i < maxLeases; i++ { + err := <-errCh + require.NoError(t, err) + } + + // Validate the semaphore still exists but all leases were removed + semaphores, err = presence.GetSemaphores(ctx, types.SemaphoreFilter{ + SemaphoreKind: "test", + SemaphoreName: "test", + }) + require.NoError(t, err) + require.Len(t, semaphores, 1) + require.Empty(t, semaphores[0].LeaseRefs()) +} diff --git a/lib/services/local/session.go b/lib/services/local/session.go index a0e761cec64fc..ed365cb2f36a2 100644 --- a/lib/services/local/session.go +++ b/lib/services/local/session.go @@ -116,12 +116,8 @@ func (r *webSessions) Get(ctx context.Context, req types.GetWebSessionRequest) ( if err != nil && !trace.IsNotFound(err) { return nil, trace.Wrap(err) } - if session != nil { - return session, nil - } - // DELETE IN 7.x: - // Return web sessions from a legacy path under /web/users//sessions/ - return getLegacyWebSession(ctx, r.backend, req.User, req.SessionID) + + return session, trace.Wrap(err) } // List gets all regular web sessions. @@ -290,25 +286,6 @@ type webTokens struct { log logrus.FieldLogger } -// DELETE in 7.x. -// getLegacySession returns the web session for the specified user/sessionID -// under a legacy path /web/users//sessions/ -func getLegacyWebSession(ctx context.Context, backend backend.Backend, user, sessionID string) (types.WebSession, error) { - item, err := backend.Get(ctx, legacyWebSessionKey(user, sessionID)) - if err != nil { - return nil, trace.Wrap(err) - } - session, err := services.UnmarshalWebSession(item.Value) - if err != nil { - return nil, trace.Wrap(err) - } - // this is for backwards compatibility to ensure we - // always have these values - session.SetUser(user) - session.SetName(sessionID) - return session, nil -} - func webSessionKey(sessionID string) (key []byte) { return backend.Key(webPrefix, sessionsPrefix, sessionID) } @@ -316,7 +293,3 @@ func webSessionKey(sessionID string) (key []byte) { func webTokenKey(token string) (key []byte) { return backend.Key(webPrefix, tokensPrefix, token) } - -func legacyWebSessionKey(user, sessionID string) (key []byte) { - return backend.Key(webPrefix, usersPrefix, user, sessionsPrefix, sessionID) -} diff --git a/lib/services/local/trust.go b/lib/services/local/trust.go index 9d71b45e51e6f..81a4907354889 100644 --- a/lib/services/local/trust.go +++ b/lib/services/local/trust.go @@ -77,7 +77,7 @@ func (s *CA) UpsertCertAuthority(ca types.CertAuthority) error { } // try to skip writes that would have no effect - if existing, err := s.GetCertAuthority(types.CertAuthID{ + if existing, err := s.GetCertAuthority(context.TODO(), types.CertAuthID{ Type: ca.GetType(), DomainName: ca.GetClusterName(), }, true); err == nil { @@ -200,7 +200,7 @@ func (s *CA) ActivateCertAuthority(id types.CertAuthID) error { // DeactivateCertAuthority moves a CertAuthority from the normal list to // the deactivated list. func (s *CA) DeactivateCertAuthority(id types.CertAuthID) error { - certAuthority, err := s.GetCertAuthority(id, true) + certAuthority, err := s.GetCertAuthority(context.TODO(), id, true) if err != nil { if trace.IsNotFound(err) { return trace.NotFound("can not deactivate cert authority %q which does not exist", id.DomainName) @@ -234,11 +234,11 @@ func (s *CA) DeactivateCertAuthority(id types.CertAuthID) error { // GetCertAuthority returns certificate authority by given id. Parameter loadSigningKeys // controls if signing keys are loaded -func (s *CA) GetCertAuthority(id types.CertAuthID, loadSigningKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { +func (s *CA) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadSigningKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { if err := id.Check(); err != nil { return nil, trace.Wrap(err) } - item, err := s.Get(context.TODO(), backend.Key(authoritiesPrefix, string(id.Type), id.DomainName)) + item, err := s.Get(ctx, backend.Key(authoritiesPrefix, string(id.Type), id.DomainName)) if err != nil { return nil, trace.Wrap(err) } @@ -263,14 +263,14 @@ func setSigningKeys(ca types.CertAuthority, loadSigningKeys bool) { // GetCertAuthorities returns a list of authorities of a given type // loadSigningKeys controls whether signing keys should be loaded or not -func (s *CA) GetCertAuthorities(caType types.CertAuthType, loadSigningKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { +func (s *CA) GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadSigningKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) { if err := caType.Check(); err != nil { return nil, trace.Wrap(err) } // Get all items in the bucket. startKey := backend.Key(authoritiesPrefix, string(caType)) - result, err := s.GetRange(context.TODO(), startKey, backend.RangeEnd(startKey), backend.NoLimit) + result, err := s.GetRange(ctx, startKey, backend.RangeEnd(startKey), backend.NoLimit) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/services/local/users.go b/lib/services/local/users.go index 934952fecd2d1..2f64cd2c8ce70 100644 --- a/lib/services/local/users.go +++ b/lib/services/local/users.go @@ -388,6 +388,8 @@ func (s *IdentityService) DeleteUser(ctx context.Context, user string) error { if err != nil { return trace.Wrap(err) } + // each user has multiple related entries in the backend, + // so use DeleteRange to make sure we get them all startKey := backend.Key(webPrefix, usersPrefix, user) err = s.DeleteRange(ctx, startKey, backend.RangeEnd(startKey)) return trace.Wrap(err) diff --git a/lib/services/local/users_test.go b/lib/services/local/users_test.go index d0dd62336a993..424d78d8e09d9 100644 --- a/lib/services/local/users_test.go +++ b/lib/services/local/users_test.go @@ -26,6 +26,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/lite" "github.com/gravitational/teleport/lib/services/local" "github.com/gravitational/trace" @@ -60,7 +61,6 @@ func TestRecoveryCodesCRUD(t *testing.T) { } t.Run("upsert, get, delete recovery codes", func(t *testing.T) { - t.Parallel() username := "someuser" rc1, err := types.NewRecoveryCodes(mockedCodes, clock.Now(), username) @@ -100,7 +100,6 @@ func TestRecoveryCodesCRUD(t *testing.T) { }) t.Run("deleting user deletes recovery codes", func(t *testing.T) { - t.Parallel() username := "someuser2" // Create a user. @@ -124,6 +123,35 @@ func TestRecoveryCodesCRUD(t *testing.T) { _, err = identity.GetRecoveryCodes(ctx, username, true /* withSecrets */) require.True(t, trace.IsNotFound(err)) }) + + t.Run("deleting user ending with 'z'", func(t *testing.T) { + // enable the sanitizer, and use a key ending with z, + // which will produce an invalid backend key when we + // compute the end range + username := "xyz" + identity.Backend = backend.NewSanitizer(identity.Backend) + + // Create a user. + userResource := &types.UserV2{} + userResource.SetName(username) + err := identity.CreateUser(userResource) + require.NoError(t, err) + + // Test codes exist for user. + rc1, err := types.NewRecoveryCodes(mockedCodes, clock.Now(), username) + require.NoError(t, err) + err = identity.UpsertRecoveryCodes(ctx, username, rc1) + require.NoError(t, err) + codes, err := identity.GetRecoveryCodes(ctx, username, true /* withSecrets */) + require.NoError(t, err) + require.ElementsMatch(t, mockedCodes, codes.GetCodes()) + + // Test deletion of recovery code along with user. + err = identity.DeleteUser(ctx, username) + require.NoError(t, err) + _, err = identity.GetRecoveryCodes(ctx, username, true /* withSecrets */) + require.True(t, trace.IsNotFound(err)) + }) } func TestRecoveryAttemptsCRUD(t *testing.T) { @@ -138,7 +166,6 @@ func TestRecoveryAttemptsCRUD(t *testing.T) { time3 := time1.Add(4 * time.Minute) t.Run("create, get, and delete recovery attempts", func(t *testing.T) { - t.Parallel() username := "someuser" // Test creation of recovery attempt. @@ -166,7 +193,6 @@ func TestRecoveryAttemptsCRUD(t *testing.T) { }) t.Run("deleting user deletes recovery attempts", func(t *testing.T) { - t.Parallel() username := "someuser2" // Create a user, to test deletion of recovery attempts with user. diff --git a/lib/services/matchers.go b/lib/services/matchers.go index d82a4dde30e51..b734adfbfb1d8 100644 --- a/lib/services/matchers.go +++ b/lib/services/matchers.go @@ -72,7 +72,7 @@ func MatchResourceByFilters(resource types.ResourceWithLabels, filter MatchResou // We assume when filtering for services like KubeService, AppServer, and DatabaseServer // the user is wanting to filter the contained resource ie. KubeClusters, Application, and Database. switch filter.ResourceKind { - case types.KindNode: + case types.KindNode, types.KindWindowsDesktop, types.KindKubernetesCluster: specResource = resource case types.KindKubeService: diff --git a/lib/services/matchers_test.go b/lib/services/matchers_test.go index cfd225ae49b6d..1e6edf23f43d9 100644 --- a/lib/services/matchers_test.go +++ b/lib/services/matchers_test.go @@ -451,6 +451,20 @@ func TestMatchResourceByFilters(t *testing.T) { PredicateExpression: filterExpression, }, }, + { + name: "kube cluster", + resource: func() types.ResourceWithLabels { + cluster, err := types.NewKubernetesClusterV3FromLegacyCluster("_", &types.KubernetesCluster{ + Name: "foo", + }) + require.NoError(t, err) + return cluster + }, + filters: MatchResourceFilter{ + ResourceKind: types.KindKubernetesCluster, + PredicateExpression: filterExpression, + }, + }, { name: "node", resource: func() types.ResourceWithLabels { @@ -463,6 +477,18 @@ func TestMatchResourceByFilters(t *testing.T) { PredicateExpression: filterExpression, }, }, + { + name: "windows desktop", + resource: func() types.ResourceWithLabels { + desktop, err := types.NewWindowsDesktopV3("foo", nil, types.WindowsDesktopSpecV3{Addr: "_"}) + require.NoError(t, err) + return desktop + }, + filters: MatchResourceFilter{ + ResourceKind: types.KindWindowsDesktop, + PredicateExpression: filterExpression, + }, + }, } for _, tc := range testcases { diff --git a/lib/services/reconciler.go b/lib/services/reconciler.go index d99071c5bd4e0..84ea4ca40eb37 100644 --- a/lib/services/reconciler.go +++ b/lib/services/reconciler.go @@ -30,9 +30,9 @@ type ReconcilerConfig struct { // Matcher is used to match resources. Matcher Matcher // GetCurrentResources returns currently registered resources. - GetCurrentResources func() types.ResourcesWithLabels + GetCurrentResources func() types.ResourcesWithLabelsMap // GetNewResources returns resources to compare current resources against. - GetNewResources func() types.ResourcesWithLabels + GetNewResources func() types.ResourcesWithLabelsMap // OnCreate is called when a new resource is detected. OnCreate func(context.Context, types.ResourceWithLabels) error // OnUpdate is called when an existing resource is updated. @@ -83,7 +83,7 @@ func NewReconciler(cfg ReconcilerConfig) (*Reconciler, error) { }, nil } -// Reconcile reconciles currently registered resources with new resources and +// Reconciler reconciles currently registered resources with new resources and // creates/updates/deletes them appropriately. // // It's used in combination with watchers by agents (app, database, desktop) @@ -123,9 +123,9 @@ func (r *Reconciler) Reconcile(ctx context.Context) error { // processRegisteredResource checks the specified registered resource against the // new list of resources. -func (r *Reconciler) processRegisteredResource(ctx context.Context, newResources types.ResourcesWithLabels, registered types.ResourceWithLabels) error { +func (r *Reconciler) processRegisteredResource(ctx context.Context, newResources types.ResourcesWithLabelsMap, registered types.ResourceWithLabels) error { // See if this registered resource is still present among "new" resources. - if new := newResources.Find(registered.GetName()); new != nil { + if new := newResources[registered.GetName()]; new != nil { return nil } @@ -139,10 +139,10 @@ func (r *Reconciler) processRegisteredResource(ctx context.Context, newResources // processNewResource checks the provided new resource agsinst currently // registered resources. -func (r *Reconciler) processNewResource(ctx context.Context, currentResources types.ResourcesWithLabels, new types.ResourceWithLabels) error { +func (r *Reconciler) processNewResource(ctx context.Context, currentResources types.ResourcesWithLabelsMap, new types.ResourceWithLabels) error { // First see if the resource is already registered and if not, whether it // matches the selector labels and should be registered. - registered := currentResources.Find(new.GetName()) + registered := currentResources[new.GetName()] if registered == nil { if r.cfg.Matcher(new) { r.log.Infof("%v %v matches, creating.", new.GetKind(), new.GetName()) @@ -155,9 +155,9 @@ func (r *Reconciler) processNewResource(ctx context.Context, currentResources ty return nil } - // Don't overwrite resource of a different origin. + // Don't overwrite resource of a different origin (e.g., keep static resource from config and ignore dynamic resource) if registered.Origin() != new.Origin() { - r.log.Debugf("%v has different origin (%v vs %v), not updating.", new.GetName(), + r.log.Warnf("%v has different origin (%v vs %v), not updating.", new.GetName(), new.Origin(), registered.Origin()) return nil } diff --git a/lib/services/reconciler_test.go b/lib/services/reconciler_test.go index 7fe5ef4e62701..2aba193a1ba8e 100644 --- a/lib/services/reconciler_test.go +++ b/lib/services/reconciler_test.go @@ -156,11 +156,11 @@ func TestReconciler(t *testing.T) { Matcher: func(rwl types.ResourceWithLabels) bool { return MatchResourceLabels(test.selectors, rwl) }, - GetCurrentResources: func() types.ResourcesWithLabels { - return test.registeredResources + GetCurrentResources: func() types.ResourcesWithLabelsMap { + return test.registeredResources.ToMap() }, - GetNewResources: func() types.ResourcesWithLabels { - return test.newResources + GetNewResources: func() types.ResourcesWithLabelsMap { + return test.newResources.ToMap() }, OnCreate: func(ctx context.Context, r types.ResourceWithLabels) error { onCreateCalls = append(onCreateCalls, r) diff --git a/lib/services/resource.go b/lib/services/resource.go index bc7e19386cd59..04dca54f5c39f 100644 --- a/lib/services/resource.go +++ b/lib/services/resource.go @@ -412,80 +412,99 @@ func getResourceUnmarshaler(kind string) (ResourceUnmarshaler, bool) { } func init() { - RegisterResourceMarshaler(types.KindUser, func(r types.Resource, opts ...MarshalOption) ([]byte, error) { - rsc, ok := r.(types.User) + RegisterResourceMarshaler(types.KindUser, func(resource types.Resource, opts ...MarshalOption) ([]byte, error) { + user, ok := resource.(types.User) if !ok { - return nil, trace.BadParameter("expected User, got %T", r) + return nil, trace.BadParameter("expected User, got %T", resource) } - raw, err := MarshalUser(rsc, opts...) + bytes, err := MarshalUser(user, opts...) if err != nil { return nil, trace.Wrap(err) } - return raw, nil + return bytes, nil }) - RegisterResourceUnmarshaler(types.KindUser, func(b []byte, opts ...MarshalOption) (types.Resource, error) { - rsc, err := UnmarshalUser(b, opts...) + RegisterResourceUnmarshaler(types.KindUser, func(bytes []byte, opts ...MarshalOption) (types.Resource, error) { + user, err := UnmarshalUser(bytes, opts...) if err != nil { return nil, trace.Wrap(err) } - return rsc, nil + return user, nil }) - RegisterResourceMarshaler(types.KindCertAuthority, func(r types.Resource, opts ...MarshalOption) ([]byte, error) { - rsc, ok := r.(types.CertAuthority) + RegisterResourceMarshaler(types.KindCertAuthority, func(resource types.Resource, opts ...MarshalOption) ([]byte, error) { + certAuthority, ok := resource.(types.CertAuthority) if !ok { - return nil, trace.BadParameter("expected CertAuthority, got %T", r) + return nil, trace.BadParameter("expected CertAuthority, got %T", resource) } - raw, err := MarshalCertAuthority(rsc, opts...) + bytes, err := MarshalCertAuthority(certAuthority, opts...) if err != nil { return nil, trace.Wrap(err) } - return raw, nil + return bytes, nil }) - RegisterResourceUnmarshaler(types.KindCertAuthority, func(b []byte, opts ...MarshalOption) (types.Resource, error) { - rsc, err := UnmarshalCertAuthority(b, opts...) + RegisterResourceUnmarshaler(types.KindCertAuthority, func(bytes []byte, opts ...MarshalOption) (types.Resource, error) { + certAuthority, err := UnmarshalCertAuthority(bytes, opts...) if err != nil { return nil, trace.Wrap(err) } - return rsc, nil + return certAuthority, nil }) - RegisterResourceMarshaler(types.KindTrustedCluster, func(r types.Resource, opts ...MarshalOption) ([]byte, error) { - rsc, ok := r.(types.TrustedCluster) + RegisterResourceMarshaler(types.KindTrustedCluster, func(resource types.Resource, opts ...MarshalOption) ([]byte, error) { + trustedCluster, ok := resource.(types.TrustedCluster) if !ok { - return nil, trace.BadParameter("expected TrustedCluster, got %T", r) + return nil, trace.BadParameter("expected TrustedCluster, got %T", resource) } - raw, err := MarshalTrustedCluster(rsc, opts...) + bytes, err := MarshalTrustedCluster(trustedCluster, opts...) if err != nil { return nil, trace.Wrap(err) } - return raw, nil + return bytes, nil }) - RegisterResourceUnmarshaler(types.KindTrustedCluster, func(b []byte, opts ...MarshalOption) (types.Resource, error) { - rsc, err := UnmarshalTrustedCluster(b, opts...) + RegisterResourceUnmarshaler(types.KindTrustedCluster, func(bytes []byte, opts ...MarshalOption) (types.Resource, error) { + trustedCluster, err := UnmarshalTrustedCluster(bytes, opts...) if err != nil { return nil, trace.Wrap(err) } - return rsc, nil + return trustedCluster, nil }) - RegisterResourceMarshaler(types.KindGithubConnector, func(r types.Resource, opts ...MarshalOption) ([]byte, error) { - rsc, ok := r.(types.GithubConnector) + RegisterResourceMarshaler(types.KindGithubConnector, func(resource types.Resource, opts ...MarshalOption) ([]byte, error) { + githubConnector, ok := resource.(types.GithubConnector) if !ok { - return nil, trace.BadParameter("expected GithubConnector, got %T", r) + return nil, trace.BadParameter("expected GithubConnector, got %T", resource) } - raw, err := MarshalGithubConnector(rsc, opts...) + bytes, err := MarshalGithubConnector(githubConnector, opts...) if err != nil { return nil, trace.Wrap(err) } - return raw, nil + return bytes, nil }) - RegisterResourceUnmarshaler(types.KindGithubConnector, func(b []byte, opts ...MarshalOption) (types.Resource, error) { - rsc, err := UnmarshalGithubConnector(b) // XXX: Does not support marshal options. + RegisterResourceUnmarshaler(types.KindGithubConnector, func(bytes []byte, opts ...MarshalOption) (types.Resource, error) { + githubConnector, err := UnmarshalGithubConnector(bytes) // XXX: Does not support marshal options. if err != nil { return nil, trace.Wrap(err) } - return rsc, nil + return githubConnector, nil + }) + + RegisterResourceMarshaler(types.KindRole, func(resource types.Resource, opts ...MarshalOption) ([]byte, error) { + role, ok := resource.(types.Role) + if !ok { + return nil, trace.BadParameter("expected Role, got %T", resource) + } + bytes, err := MarshalRole(role, opts...) + if err != nil { + return nil, trace.Wrap(err) + } + return bytes, nil + }) + RegisterResourceUnmarshaler(types.KindRole, func(bytes []byte, opts ...MarshalOption) (types.Resource, error) { + role, err := UnmarshalRole(bytes, opts...) + if err != nil { + return nil, trace.Wrap(err) + } + return role, nil }) } diff --git a/lib/services/role.go b/lib/services/role.go index eda4222e64d2b..56ab3b285a8dd 100644 --- a/lib/services/role.go +++ b/lib/services/role.go @@ -122,7 +122,7 @@ func NewImplicitRole() types.Role { // // Used in tests only. func RoleForUser(u types.User) types.Role { - role, _ := types.NewRole(RoleNameForUser(u.GetName()), types.RoleSpecV5{ + role, _ := types.NewRoleV3(RoleNameForUser(u.GetName()), types.RoleSpecV5{ Options: types.RoleOptions{ CertificateFormat: constants.CertificateFormatStandard, MaxSessionTTL: types.NewDuration(defaults.MaxCertDuration), @@ -148,6 +148,7 @@ func RoleForUser(u types.User) types.Role { types.NewRule(types.KindApp, RW()), types.NewRule(types.KindDatabase, RW()), types.NewRule(types.KindLock, RW()), + types.NewRule(types.KindToken, RW()), }, }, }) @@ -156,7 +157,7 @@ func RoleForUser(u types.User) types.Role { // RoleForCertAuthority creates role using types.CertAuthority. func RoleForCertAuthority(ca types.CertAuthority) types.Role { - role, _ := types.NewRole(RoleNameForCertAuthority(ca.GetClusterName()), types.RoleSpecV5{ + role, _ := types.NewRoleV3(RoleNameForCertAuthority(ca.GetClusterName()), types.RoleSpecV5{ Options: types.RoleOptions{ MaxSessionTTL: types.NewDuration(defaults.MaxCertDuration), }, @@ -730,7 +731,7 @@ type AccessChecker interface { // FromSpec returns new RoleSet created from spec func FromSpec(name string, spec types.RoleSpecV5) (RoleSet, error) { - role, err := types.NewRole(name, spec) + role, err := types.NewRoleV3(name, spec) if err != nil { return nil, trace.Wrap(err) } @@ -778,9 +779,11 @@ func ExtractFromCertificate(cert *ssh.Certificate) ([]string, wrappers.Traits, e // which Teleport passes along as a *tlsca.Identity. If roles and traits do not // exist in the certificates, they are extracted from the backend. func ExtractFromIdentity(access UserGetter, identity tlsca.Identity) ([]string, wrappers.Traits, error) { - // For legacy certificates, fetch roles and traits from the services.User - // object in the backend. - if missingIdentity(identity) { + // Legacy certs are not encoded with roles or traits, + // so we fallback to the traits and roles in the backend. + // empty traits are a valid use case in standard certs, + // so we only check for whether roles are empty. + if len(identity.Groups) == 0 { u, err := access.GetUser(identity.Username, false) if err != nil { return nil, nil, trace.Wrap(err) @@ -823,15 +826,6 @@ func FetchRoles(roleNames []string, access RoleGetter, traits map[string][]strin return NewRoleSet(roles...), nil } -// missingIdentity returns true if the identity is missing or the identity -// has no roles or traits. -func missingIdentity(identity tlsca.Identity) bool { - if len(identity.Groups) == 0 || len(identity.Traits) == 0 { - return true - } - return false -} - // ExtractRolesFromCert extracts roles from certificate metadata extensions. func ExtractRolesFromCert(cert *ssh.Certificate) ([]string, error) { data, ok := cert.Extensions[teleport.CertExtensionTeleportRoles] diff --git a/lib/services/role_test.go b/lib/services/role_test.go index bf8406ca2612c..12ec744ab8ab9 100644 --- a/lib/services/role_test.go +++ b/lib/services/role_test.go @@ -1506,7 +1506,7 @@ func TestCheckRuleAccess(t *testing.T) { func TestGuessIfAccessIsPossible(t *testing.T) { // Examples from https://goteleport.com/docs/access-controls/reference/#rbac-for-sessions. - ownSessions, err := types.NewRole("own-sessions", types.RoleSpecV5{ + ownSessions, err := types.NewRoleV3("own-sessions", types.RoleSpecV5{ Allow: types.RoleConditions{ Rules: []types.Rule{ { @@ -1518,7 +1518,7 @@ func TestGuessIfAccessIsPossible(t *testing.T) { }, }) require.NoError(t, err) - ownSSHSessions, err := types.NewRole("own-ssh-sessions", types.RoleSpecV5{ + ownSSHSessions, err := types.NewRoleV3("own-ssh-sessions", types.RoleSpecV5{ Allow: types.RoleConditions{ Rules: []types.Rule{ { @@ -1540,7 +1540,7 @@ func TestGuessIfAccessIsPossible(t *testing.T) { require.NoError(t, err) // Simple, all-or-nothing roles. - readAllSessions, err := types.NewRole("all-sessions", types.RoleSpecV5{ + readAllSessions, err := types.NewRoleV3("all-sessions", types.RoleSpecV5{ Allow: types.RoleConditions{ Rules: []types.Rule{ { @@ -1551,7 +1551,7 @@ func TestGuessIfAccessIsPossible(t *testing.T) { }, }) require.NoError(t, err) - allowSSHSessions, err := types.NewRole("all-ssh-sessions", types.RoleSpecV5{ + allowSSHSessions, err := types.NewRoleV3("all-ssh-sessions", types.RoleSpecV5{ Allow: types.RoleConditions{ Rules: []types.Rule{ { @@ -1562,7 +1562,7 @@ func TestGuessIfAccessIsPossible(t *testing.T) { }, }) require.NoError(t, err) - denySSHSessions, err := types.NewRole("deny-ssh-sessions", types.RoleSpecV5{ + denySSHSessions, err := types.NewRoleV3("deny-ssh-sessions", types.RoleSpecV5{ Deny: types.RoleConditions{ Rules: []types.Rule{ { @@ -3536,7 +3536,7 @@ func TestRoleSetLockingMode(t *testing.T) { missingMode := constants.LockingMode("") newRoleWithLockingMode := func(t *testing.T, mode constants.LockingMode) types.Role { - role, err := types.NewRole(uuid.New().String(), types.RoleSpecV5{Options: types.RoleOptions{Lock: mode}}) + role, err := types.NewRoleV3(uuid.New().String(), types.RoleSpecV5{Options: types.RoleOptions{Lock: mode}}) require.NoError(t, err) return role } @@ -3575,14 +3575,14 @@ func TestExtractConditionForIdentifier(t *testing.T) { require.True(t, trace.IsAccessDenied(err)) allowWhere := func(where string) types.Role { - role, err := types.NewRole(uuid.New().String(), types.RoleSpecV5{Allow: types.RoleConditions{ + role, err := types.NewRoleV3(uuid.New().String(), types.RoleSpecV5{Allow: types.RoleConditions{ Rules: []types.Rule{{Resources: []string{types.KindSession}, Verbs: []string{types.VerbList}, Where: where}}, }}) require.NoError(t, err) return role } denyWhere := func(where string) types.Role { - role, err := types.NewRole(uuid.New().String(), types.RoleSpecV5{Deny: types.RoleConditions{ + role, err := types.NewRoleV3(uuid.New().String(), types.RoleSpecV5{Deny: types.RoleConditions{ Rules: []types.Rule{{Resources: []string{types.KindSession}, Verbs: []string{types.VerbList}, Where: where}}, }}) require.NoError(t, err) diff --git a/lib/services/suite/suite.go b/lib/services/suite/suite.go index 9d891559fb056..8d2e2452d352a 100644 --- a/lib/services/suite/suite.go +++ b/lib/services/suite/suite.go @@ -275,15 +275,16 @@ func (s *ServicesTestSuite) LoginAttempts(c *check.C) { } func (s *ServicesTestSuite) CertAuthCRUD(c *check.C) { + ctx := context.Background() ca := NewTestCA(types.UserCA, "example.com") c.Assert(s.CAS.UpsertCertAuthority(ca), check.IsNil) - out, err := s.CAS.GetCertAuthority(ca.GetID(), true) + out, err := s.CAS.GetCertAuthority(ctx, ca.GetID(), true) c.Assert(err, check.IsNil) ca.SetResourceID(out.GetResourceID()) fixtures.DeepCompare(c, out, ca) - cas, err := s.CAS.GetCertAuthorities(types.UserCA, false) + cas, err := s.CAS.GetCertAuthorities(ctx, types.UserCA, false) c.Assert(err, check.IsNil) ca2 := ca.Clone().(*types.CertAuthorityV2) ca2.Spec.ActiveKeys.SSH[0].PrivateKey = nil @@ -294,11 +295,11 @@ func (s *ServicesTestSuite) CertAuthCRUD(c *check.C) { ca2.Spec.JWTKeyPairs[0].PrivateKey = nil fixtures.DeepCompare(c, cas[0], ca2) - cas, err = s.CAS.GetCertAuthorities(types.UserCA, true) + cas, err = s.CAS.GetCertAuthorities(ctx, types.UserCA, true) c.Assert(err, check.IsNil) fixtures.DeepCompare(c, cas[0], ca) - cas, err = s.CAS.GetCertAuthorities(types.UserCA, true) + cas, err = s.CAS.GetCertAuthorities(ctx, types.UserCA, true) c.Assert(err, check.IsNil) fixtures.DeepCompare(c, cas[0], ca) @@ -322,7 +323,7 @@ func (s *ServicesTestSuite) CertAuthCRUD(c *check.C) { err = s.CAS.CompareAndSwapCertAuthority(&newCA, ca) c.Assert(err, check.IsNil) - out, err = s.CAS.GetCertAuthority(ca.GetID(), true) + out, err = s.CAS.GetCertAuthority(ctx, ca.GetID(), true) c.Assert(err, check.IsNil) newCA.SetResourceID(out.GetResourceID()) fixtures.DeepCompare(c, &newCA, out) @@ -1348,7 +1349,7 @@ func (s *ServicesTestSuite) Events(c *check.C) { ca := NewTestCA(types.UserCA, "example.com") c.Assert(s.CAS.UpsertCertAuthority(ca), check.IsNil) - out, err := s.CAS.GetCertAuthority(*ca.ID(), true) + out, err := s.CAS.GetCertAuthority(ctx, *ca.ID(), true) c.Assert(err, check.IsNil) c.Assert(s.CAS.DeleteCertAuthority(*ca.ID()), check.IsNil) @@ -1369,7 +1370,7 @@ func (s *ServicesTestSuite) Events(c *check.C) { ca := NewTestCA(types.UserCA, "example.com") c.Assert(s.CAS.UpsertCertAuthority(ca), check.IsNil) - out, err := s.CAS.GetCertAuthority(*ca.ID(), false) + out, err := s.CAS.GetCertAuthority(ctx, *ca.ID(), false) c.Assert(err, check.IsNil) c.Assert(s.CAS.DeleteCertAuthority(*ca.ID()), check.IsNil) @@ -1461,7 +1462,7 @@ func (s *ServicesTestSuite) Events(c *check.C) { Kind: types.KindRole, }, crud: func(context.Context) types.Resource { - role, err := types.NewRole("role1", types.RoleSpecV5{ + role, err := types.NewRoleV3("role1", types.RoleSpecV5{ Options: types.RoleOptions{ MaxSessionTTL: types.Duration(time.Hour), }, diff --git a/lib/services/trust.go b/lib/services/trust.go index 84d4c5d4aeb5b..996fe4595b641 100644 --- a/lib/services/trust.go +++ b/lib/services/trust.go @@ -16,15 +16,19 @@ limitations under the License. package services -import "github.com/gravitational/teleport/api/types" +import ( + "context" + + "github.com/gravitational/teleport/api/types" +) // AuthorityGetter defines interface for fetching cert authority resources. type AuthorityGetter interface { // GetCertAuthority returns cert authority by id - GetCertAuthority(id types.CertAuthID, loadKeys bool, opts ...MarshalOption) (types.CertAuthority, error) + GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...MarshalOption) (types.CertAuthority, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...MarshalOption) ([]types.CertAuthority, error) } // Trust is responsible for managing certificate authorities diff --git a/lib/services/watcher.go b/lib/services/watcher.go index c1d932323248f..16cb6dbd88801 100644 --- a/lib/services/watcher.go +++ b/lib/services/watcher.go @@ -729,7 +729,13 @@ func (p *databaseCollector) getResourcesAndUpdateCurrent(ctx context.Context) er p.lock.Lock() defer p.lock.Unlock() p.current = newCurrent - p.DatabasesC <- databases + + select { + case <-ctx.Done(): + return trace.Wrap(ctx.Err()) + case p.DatabasesC <- databases: + } + return nil } @@ -744,7 +750,10 @@ func (p *databaseCollector) processEventAndUpdateCurrent(ctx context.Context, ev switch event.Type { case types.OpDelete: delete(p.current, event.Resource.GetName()) - p.DatabasesC <- databasesToSlice(p.current) + select { + case <-ctx.Done(): + case p.DatabasesC <- databasesToSlice(p.current): + } case types.OpPut: database, ok := event.Resource.(types.Database) if !ok { @@ -752,7 +761,11 @@ func (p *databaseCollector) processEventAndUpdateCurrent(ctx context.Context, ev return } p.current[database.GetName()] = database - p.DatabasesC <- databasesToSlice(p.current) + select { + case <-ctx.Done(): + case p.DatabasesC <- databasesToSlice(p.current): + } + default: p.Log.Warnf("Unsupported event type %s.", event.Type) return @@ -845,7 +858,12 @@ func (p *appCollector) getResourcesAndUpdateCurrent(ctx context.Context) error { p.lock.Lock() defer p.lock.Unlock() p.current = newCurrent - p.AppsC <- apps + + select { + case <-ctx.Done(): + return trace.Wrap(ctx.Err()) + case p.AppsC <- apps: + } return nil } @@ -861,6 +879,12 @@ func (p *appCollector) processEventAndUpdateCurrent(ctx context.Context, event t case types.OpDelete: delete(p.current, event.Resource.GetName()) p.AppsC <- appsToSlice(p.current) + + select { + case <-ctx.Done(): + case p.AppsC <- appsToSlice(p.current): + } + case types.OpPut: app, ok := event.Resource.(types.Application) if !ok { @@ -868,7 +892,11 @@ func (p *appCollector) processEventAndUpdateCurrent(ctx context.Context, event t return } p.current[app.GetName()] = app - p.AppsC <- appsToSlice(p.current) + + select { + case <-ctx.Done(): + case p.AppsC <- appsToSlice(p.current): + } default: p.Log.Warnf("Unsupported event type %s.", event.Type) return @@ -961,7 +989,7 @@ func (c *caCollector) getResourcesAndUpdateCurrent(ctx context.Context) error { ) if c.WatchHostCA { - host, err := c.AuthorityGetter.GetCertAuthorities(types.HostCA, false) + host, err := c.AuthorityGetter.GetCertAuthorities(ctx, types.HostCA, false) if err != nil { return trace.Wrap(err) } @@ -972,7 +1000,7 @@ func (c *caCollector) getResourcesAndUpdateCurrent(ctx context.Context) error { } if c.WatchUserCA { - user, err := c.AuthorityGetter.GetCertAuthorities(types.UserCA, false) + user, err := c.AuthorityGetter.GetCertAuthorities(ctx, types.UserCA, false) if err != nil { return trace.Wrap(err) } @@ -987,7 +1015,11 @@ func (c *caCollector) getResourcesAndUpdateCurrent(ctx context.Context) error { c.user = newUser c.lock.Unlock() - c.CertAuthorityC <- casToSlice(newHost, newUser) + select { + case <-ctx.Done(): + return trace.Wrap(ctx.Err()) + case c.CertAuthorityC <- casToSlice(newHost, newUser): + } return nil } @@ -1008,7 +1040,10 @@ func (c *caCollector) processEventAndUpdateCurrent(ctx context.Context, event ty delete(c.user, event.Resource.GetName()) } - c.CertAuthorityC <- casToSlice(c.host, c.user) + select { + case <-ctx.Done(): + case c.CertAuthorityC <- casToSlice(c.host, c.user): + } case types.OpPut: ca, ok := event.Resource.(types.CertAuthority) if !ok { @@ -1023,7 +1058,10 @@ func (c *caCollector) processEventAndUpdateCurrent(ctx context.Context, event ty c.user[ca.GetName()] = ca } - c.CertAuthorityC <- casToSlice(c.host, c.user) + select { + case <-ctx.Done(): + case c.CertAuthorityC <- casToSlice(c.host, c.user): + } default: c.Log.Warnf("Unsupported event type %s.", event.Type) return diff --git a/lib/srv/alpnproxy/auth/auth_proxy.go b/lib/srv/alpnproxy/auth/auth_proxy.go index ec8740fae261e..f7b5aae4115b3 100644 --- a/lib/srv/alpnproxy/auth/auth_proxy.go +++ b/lib/srv/alpnproxy/auth/auth_proxy.go @@ -18,6 +18,7 @@ package alpnproxyauth import ( "context" + "fmt" "io" "math/rand" "net" @@ -113,13 +114,23 @@ func (s *AuthProxyDialerService) dialLocalAuthServer(ctx context.Context) (net.C if len(authServers) == 0 { return nil, trace.NotFound("empty auth servers list") } - //TODO(smallinksy) Better support for HA. Add dial retry on auth network errors. - authServerIndex := rand.Intn(len(authServers)) - conn, err := net.Dial("tcp", authServers[authServerIndex].GetAddr()) - if err != nil { - return nil, trace.Wrap(err) + var errors []error + + // iterate over the addresses in random order + for len(authServers) > 0 { + l := len(authServers) + authServerIndex := rand.Intn(l) + addr := authServers[authServerIndex].GetAddr() + var d net.Dialer + conn, err := d.DialContext(ctx, "tcp", addr) + if err == nil { + return conn, nil + } + errors = append(errors, fmt.Errorf("%s: %w", addr, err)) + authServers[authServerIndex] = authServers[l-1] + authServers = authServers[:l-1] } - return conn, nil + return nil, trace.NewAggregate(errors...) } func (s *AuthProxyDialerService) dialRemoteAuthServer(ctx context.Context, clusterName string) (net.Conn, error) { diff --git a/lib/srv/alpnproxy/auth/auth_proxy_test.go b/lib/srv/alpnproxy/auth/auth_proxy_test.go new file mode 100644 index 0000000000000..ff25f26412124 --- /dev/null +++ b/lib/srv/alpnproxy/auth/auth_proxy_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2021 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package alpnproxyauth + +import ( + "context" + "net" + "testing" + "time" + + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/services" + "github.com/stretchr/testify/require" +) + +type mockAuthGetter struct { + servers []types.Server +} + +func (m mockAuthGetter) GetClusterName(...services.MarshalOption) (types.ClusterName, error) { + return nil, nil +} + +func (m mockAuthGetter) GetAuthServers() ([]types.Server, error) { + return m.servers, nil +} + +func TestDialLocalAuthServerNoServers(t *testing.T) { + s := NewAuthProxyDialerService(nil, mockAuthGetter{servers: []types.Server{}}) + _, err := s.dialLocalAuthServer(context.Background()) + require.Error(t, err, "dialLocalAuthServer expected to fail") + require.Equal(t, "empty auth servers list", err.Error()) +} + +func TestDialLocalAuthServerNoAvailableServers(t *testing.T) { + server1, err := types.NewServer("s1", "auth", types.ServerSpecV2{Addr: "invalid:8000"}) + require.NoError(t, err) + s := NewAuthProxyDialerService(nil, mockAuthGetter{servers: []types.Server{server1}}) + _, err = s.dialLocalAuthServer(context.Background()) + require.Error(t, err, "dialLocalAuthServer expected to fail") + require.Contains(t, err.Error(), "invalid:8000:") +} + +func TestDialLocalAuthServerAvailableServers(t *testing.T) { + socket, err := net.Listen("tcp", "127.0.0.1:") + require.NoError(t, err) + defer socket.Close() + server, err := types.NewServer("s1", "auth", types.ServerSpecV2{Addr: socket.Addr().String()}) + require.NoError(t, err) + servers := []types.Server{server} + // multiple invalid servers to minimize chance that we select good one first try + for i := 0; i < 20; i++ { + server, err := types.NewServer("s1", "auth", types.ServerSpecV2{Addr: "invalid2:8000"}) + require.NoError(t, err) + servers = append(servers, server) + } + s := NewAuthProxyDialerService(nil, mockAuthGetter{servers: servers}) + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + _, err = s.dialLocalAuthServer(ctx) + require.NoError(t, err) +} diff --git a/lib/srv/alpnproxy/common/protocols.go b/lib/srv/alpnproxy/common/protocols.go index 74055f4e8c7eb..3009453a94b1c 100644 --- a/lib/srv/alpnproxy/common/protocols.go +++ b/lib/srv/alpnproxy/common/protocols.go @@ -17,9 +17,10 @@ limitations under the License. package common import ( - "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/trace" "golang.org/x/crypto/acme" + + "github.com/gravitational/teleport/lib/defaults" ) // Protocol is the TLS ALPN protocol type. @@ -105,3 +106,17 @@ func ToALPNProtocol(dbProtocol string) (Protocol, error) { return "", trace.NotImplemented("%q protocol is not supported", dbProtocol) } } + +// IsDBTLSProtocol returns if DB protocol has supported native TLS protocol. +// where connection can be TLS terminated on ALPN proxy side. +// For protocol like MySQL or Postgres where custom TLS implementation is used the incoming +// connection needs to be forwarded to proxy database service where custom TLS handler is invoked +// to terminated DB connection. +func IsDBTLSProtocol(protocol Protocol) bool { + switch protocol { + case ProtocolMongoDB, ProtocolRedisDB, ProtocolSQLServer: + return true + default: + return false + } +} diff --git a/lib/srv/alpnproxy/proxy.go b/lib/srv/alpnproxy/proxy.go index c14aa82f8ac6e..2cc4636e426d2 100644 --- a/lib/srv/alpnproxy/proxy.go +++ b/lib/srv/alpnproxy/proxy.go @@ -176,6 +176,9 @@ func (h *HandlerDecs) handle(ctx context.Context, conn net.Conn, info Connection if h.HandlerWithConnInfo != nil { return h.HandlerWithConnInfo(ctx, conn, info) } + if h.Handler == nil { + return trace.BadParameter("failed to find ALPN handler for ALPN: %v, SNI %v", info.ALPN, info.SNI) + } return h.Handler(ctx, conn) } @@ -434,15 +437,6 @@ func (p *Proxy) databaseHandlerWithTLSTermination(ctx context.Context, conn net. return trace.Wrap(p.handleDatabaseConnection(ctx, tlsConn, info)) } -func isDBTLSProtocol(protocol common.Protocol) bool { - switch protocol { - case common.ProtocolMongoDB, common.ProtocolRedisDB: - return true - default: - return false - } -} - func (p *Proxy) getHandlerDescBaseOnClientHelloMsg(clientHelloInfo *tls.ClientHelloInfo) (*HandlerDecs, error) { if shouldRouteToKubeService(clientHelloInfo.ServerName) { if p.cfg.Router.kubeHandler == nil { @@ -464,7 +458,7 @@ func (p *Proxy) getHandleDescBasedOnALPNVal(clientHelloInfo *tls.ClientHelloInfo for _, v := range clientProtocols { protocol := common.Protocol(v) - if isDBTLSProtocol(protocol) { + if common.IsDBTLSProtocol(protocol) { return &HandlerDecs{ MatchFunc: MatchByProtocol(protocol), HandlerWithConnInfo: p.databaseHandlerWithTLSTermination, diff --git a/lib/srv/alpnproxy/proxy_test.go b/lib/srv/alpnproxy/proxy_test.go index 029090c6dee16..1bf971d0fad09 100644 --- a/lib/srv/alpnproxy/proxy_test.go +++ b/lib/srv/alpnproxy/proxy_test.go @@ -178,6 +178,65 @@ func TestProxyTLSDatabaseHandler(t *testing.T) { }) } +// TestProxyRouteToDatabase tests db connection with protocol registered without any handler. +// ALPN router leverages empty handler to route the connection to DBHandler +// based on TLS RouteToDatabase identity entry. +func TestProxyRouteToDatabase(t *testing.T) { + t.Parallel() + const ( + databaseHandleResponse = "database handler response" + ) + + suite := NewSuite(t) + clientCert := mustGenCertSignedWithCA(t, suite.ca, + withIdentity(tlsca.Identity{ + Username: "test-user", + Groups: []string{"test-group"}, + RouteToDatabase: tlsca.RouteToDatabase{ + ServiceName: "mongo-test-database", + }, + }), + ) + + suite.router.AddDBTLSHandler(func(ctx context.Context, conn net.Conn) error { + defer conn.Close() + _, err := fmt.Fprint(conn, databaseHandleResponse) + require.NoError(t, err) + return nil + }) + suite.router.Add(HandlerDecs{ + MatchFunc: MatchByProtocol(common.ProtocolReverseTunnel), + }) + + suite.Start(t) + + t.Run("dial with user certs with RouteToDatabase info", func(t *testing.T) { + conn, err := tls.Dial("tcp", suite.GetServerAddress(), &tls.Config{ + NextProtos: []string{string(common.ProtocolReverseTunnel)}, + RootCAs: suite.GetCertPool(), + ServerName: "localhost", + Certificates: []tls.Certificate{ + clientCert, + }, + }) + require.NoError(t, err) + mustReadFromConnection(t, conn, databaseHandleResponse) + mustCloseConnection(t, conn) + }) + + t.Run("dial with no user certs", func(t *testing.T) { + conn, err := tls.Dial("tcp", suite.GetServerAddress(), &tls.Config{ + NextProtos: []string{string(common.ProtocolReverseTunnel)}, + RootCAs: suite.GetCertPool(), + ServerName: "localhost", + }) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, conn.Close()) + }) + }) +} + // TestLocalProxyPostgresProtocol tests Proxy Postgres connection forwarded by LocalProxy. // Client connects to LocalProxy with raw connection where downstream Proxy connection is upgraded to TLS with // ALPN value set to ProtocolPostgres. diff --git a/lib/srv/app/aws/endpoints.go b/lib/srv/app/aws/endpoints.go new file mode 100644 index 0000000000000..684a0f8a204bf --- /dev/null +++ b/lib/srv/app/aws/endpoints.go @@ -0,0 +1,160 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package aws + +import ( + "net/http" + "strings" + + "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/aws/aws-sdk-go/service/appstream" + "github.com/aws/aws-sdk-go/service/detective" + "github.com/aws/aws-sdk-go/service/ecr" + "github.com/aws/aws-sdk-go/service/ecrpublic" + "github.com/aws/aws-sdk-go/service/elasticinference" + "github.com/aws/aws-sdk-go/service/iot1clickdevicesservice" + "github.com/aws/aws-sdk-go/service/iotdataplane" + "github.com/aws/aws-sdk-go/service/iotdeviceadvisor" + "github.com/aws/aws-sdk-go/service/ioteventsdata" + "github.com/aws/aws-sdk-go/service/iotfleethub" + "github.com/aws/aws-sdk-go/service/iotjobsdataplane" + "github.com/aws/aws-sdk-go/service/iotsecuretunneling" + "github.com/aws/aws-sdk-go/service/iottwinmaker" + "github.com/aws/aws-sdk-go/service/iotwireless" + "github.com/aws/aws-sdk-go/service/lexmodelsv2" + "github.com/aws/aws-sdk-go/service/marketplacecatalog" + "github.com/aws/aws-sdk-go/service/mediatailor" + "github.com/aws/aws-sdk-go/service/memorydb" + "github.com/aws/aws-sdk-go/service/migrationhubstrategyrecommendations" + "github.com/aws/aws-sdk-go/service/mobile" + "github.com/aws/aws-sdk-go/service/pinpoint" + "github.com/aws/aws-sdk-go/service/pinpointsmsvoice" + "github.com/aws/aws-sdk-go/service/pricing" + "github.com/aws/aws-sdk-go/service/proton" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/aws/aws-sdk-go/service/ses" + "github.com/aws/aws-sdk-go/service/sso" + "github.com/aws/aws-sdk-go/service/ssooidc" + "github.com/aws/aws-sdk-go/service/timestreamquery" + + awsutils "github.com/gravitational/teleport/lib/utils/aws" + + "github.com/gravitational/trace" +) + +// resolveEndpoint extracts the aws-service on and aws-region from the request +// authorization header and resolves the aws-service and aws-region to AWS +// endpoint. +func resolveEndpoint(r *http.Request) (*endpoints.ResolvedEndpoint, error) { + awsAuthHeader, err := awsutils.ParseSigV4(r.Header.Get(awsutils.AuthorizationHeader)) + if err != nil { + return nil, trace.Wrap(err) + } + + // aws-sdk-go maintains a mapping of service endpoints which can be looked + // up by calling `endpoints.DefaultResolver().EndpointFor`. This mapping + // can be found at: + // https://github.com/aws/aws-sdk-go/blob/main/aws/endpoints/defaults.go + // + // The json equivalent can be found in botocore source code at: + // https://github.com/boto/botocore/blob/develop/botocore/data/endpoints.json + // + // The keys used for lookups are endpoints IDs, which can be different from + // the signing names. We have to translate the signing name received from + // the header back to the endpoints ID. + // + // In addition, many services are NOT found in aws-sdk-go's endpoints + // mapping. How aws-sdk-go resolves endpoints for these services is to + // allow ResolveUnknownService when creating the client sessions, which in + // turn generates the endpoint by using the endpoints ID and some default + // suffixes. We allow ResolveUnknownService here for the same purpose. + endpointsID := endpointsIDFromSigningName(awsAuthHeader.Service) + opts := func(opts *endpoints.Options) { + opts.ResolveUnknownService = true + } + + resolvedEndpoint, err := endpoints.DefaultResolver().EndpointFor(endpointsID, awsAuthHeader.Region, opts) + if err != nil { + return nil, trace.Wrap(err) + } + + // SigningName can be derived from the endpoint ID which may not be the + // correct signing name. Set it back to what is received from the header. + resolvedEndpoint.SigningName = awsAuthHeader.Service + return &resolvedEndpoint, nil +} + +// endpointsIDFromSigningName returns the endpoints ID used for endpoint +// lookups when calling endpoints.DefaultResolver().EndpointFor. +func endpointsIDFromSigningName(signingName string) string { + // Some clients may sign some services with upper case letters. We use all + // lower cases in our mapping. + signingName = strings.ToLower(signingName) + + if endpointsID, ok := signingNameToEndpointsID[signingName]; ok { + return endpointsID + } + + // If not found in the mapping, endpoints ID is expected to be the same as + // the signing name. + return signingName +} + +// signingNameToEndpointsID is a map of AWS services' signing names to their +// endpoints IDs. +// +// This mapping was created by the following process: +// 1. Compiled a mapping of all signing names to their hostnames (e.g. grep/awk +// keywords in "aws-sdk-go-v2/services/") +// 2. Created unit test "TestResolveEndpoints" to test each signing name. +// 3. Investigated the test failures, and updated this mapping to fix them. +// +// TODO Many services may sign with same names but use different hostnames. +// Will need a way to differentiate them. For now, either make the best guess +// in this mapping or use the default signing names. See signingNameToHostname +// in endpoints_test.go for conflicting services. +var signingNameToEndpointsID = map[string]string{ + "appstream": appstream.EndpointsID, + "aws-marketplace": marketplacecatalog.EndpointsID, + "awsiottwinmaker": iottwinmaker.EndpointsID, + "awsmigrationhubstrategyrecommendation": migrationhubstrategyrecommendations.EndpointsID, + "awsmobilehubservice": mobile.EndpointsID, + "awsproton20200720": proton.EndpointsID, + "awsssooidc": ssooidc.EndpointsID, + "awsssoportal": sso.EndpointsID, + "detective": detective.EndpointsID, + "ecr": ecr.EndpointsID, + "ecr-public": ecrpublic.EndpointsID, + "elastic-inference": elasticinference.EndpointsID, + "iot-jobs-data": iotjobsdataplane.EndpointsID, + "iot1click": iot1clickdevicesservice.EndpointsID, + "iotdata": iotdataplane.EndpointsID, + "iotdeviceadvisor": iotdeviceadvisor.EndpointsID, + "ioteventsdata": ioteventsdata.EndpointsID, + "iotfleethub": iotfleethub.EndpointsID, + "iotsecuredtunneling": iotsecuretunneling.EndpointsID, + "iotwireless": iotwireless.EndpointsID, + "lex": lexmodelsv2.EndpointsID, + "mediatailor": mediatailor.EndpointsID, + "memorydb": memorydb.EndpointsID, + "mobiletargeting": pinpoint.EndpointsID, + "pricing": pricing.EndpointsID, + "sagemaker": sagemaker.EndpointsID, + "ses": ses.EndpointsID, + "sms-voice": pinpointsmsvoice.EndpointsID, + "timestream": timestreamquery.EndpointsID, +} diff --git a/lib/srv/app/aws/endpoints_test.go b/lib/srv/app/aws/endpoints_test.go new file mode 100644 index 0000000000000..bc80f410144fb --- /dev/null +++ b/lib/srv/app/aws/endpoints_test.go @@ -0,0 +1,359 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package aws + +import ( + "bytes" + "net/http" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws/credentials" + v4 "github.com/aws/aws-sdk-go/aws/signer/v4" + "github.com/stretchr/testify/require" +) + +// signingNameToHostname is a map of AWS services' signing names to their +// hostnames. +var signingNameToHostname = map[string]string{ + "a4b": "a4b.us-east-1.amazonaws.com", + "access-analyzer": "access-analyzer.us-east-1.amazonaws.com", + "account": "account.us-east-1.amazonaws.com", + "acm": "acm.us-east-1.amazonaws.com", + "acm-pca": "acm-pca.us-east-1.amazonaws.com", + "airflow": "airflow.us-east-1.amazonaws.com", + "amplify": "amplify.us-east-1.amazonaws.com", + "amplifybackend": "amplifybackend.us-east-1.amazonaws.com", + "amplifyuibuilder": "amplifyuibuilder.us-east-1.amazonaws.com", + "apigateway": "apigateway.us-east-1.amazonaws.com", + "app-integrations": "app-integrations.us-east-1.amazonaws.com", + "appconfig": "appconfig.us-east-1.amazonaws.com", + "appconfigdata": "appconfigdata.us-east-1.amazonaws.com", + "appflow": "appflow.us-east-1.amazonaws.com", + "application-autoscaling": "application-autoscaling.us-east-1.amazonaws.com", + "application-cost-profiler": "application-cost-profiler.us-east-1.amazonaws.com", + "applicationinsights": "applicationinsights.us-east-1.amazonaws.com", + "appmesh": "appmesh.us-east-1.amazonaws.com", + "apprunner": "apprunner.us-east-1.amazonaws.com", + "appstream": "appstream2.us-east-1.amazonaws.com", + "appsync": "appsync.us-east-1.amazonaws.com", + "aps": "aps.us-east-1.amazonaws.com", + "athena": "athena.us-east-1.amazonaws.com", + "auditmanager": "auditmanager.us-east-1.amazonaws.com", + "autoscaling": "autoscaling.us-east-1.amazonaws.com", + "autoscaling-plans": "autoscaling-plans.us-east-1.amazonaws.com", + "aws-marketplace": "catalog.marketplace.us-east-1.amazonaws.com", + "awsiottwinmaker": "iottwinmaker.us-east-1.amazonaws.com", + "awsmigrationhubstrategyrecommendation": "migrationhub-strategy.us-east-1.amazonaws.com", + "awsmobilehubservice": "mobile.us-east-1.amazonaws.com", + "awsproton20200720": "proton.us-east-1.amazonaws.com", + "awsssooidc": "oidc.us-east-1.amazonaws.com", + "awsssoportal": "portal.sso.us-east-1.amazonaws.com", + "backup": "backup.us-east-1.amazonaws.com", + "backup-gateway": "backup-gateway.us-east-1.amazonaws.com", + "batch": "batch.us-east-1.amazonaws.com", + "braket": "braket.us-east-1.amazonaws.com", + "budgets": "budgets.amazonaws.com", + "ce": "ce.us-east-1.amazonaws.com", + "chime": "chime.us-east-1.amazonaws.com", + "cloud9": "cloud9.us-east-1.amazonaws.com", + "cloudcontrolapi": "cloudcontrolapi.us-east-1.amazonaws.com", + "clouddirectory": "clouddirectory.us-east-1.amazonaws.com", + "cloudformation": "cloudformation.us-east-1.amazonaws.com", + "cloudfront": "cloudfront.amazonaws.com", + "cloudhsm": "cloudhsm.us-east-1.amazonaws.com", + "cloudsearch": "cloudsearch.us-east-1.amazonaws.com", + "cloudtrail": "cloudtrail.us-east-1.amazonaws.com", + "codeartifact": "codeartifact.us-east-1.amazonaws.com", + "codebuild": "codebuild.us-east-1.amazonaws.com", + "codecommit": "codecommit.us-east-1.amazonaws.com", + "codedeploy": "codedeploy.us-east-1.amazonaws.com", + "codeguru-profiler": "codeguru-profiler.us-east-1.amazonaws.com", + "codeguru-reviewer": "codeguru-reviewer.us-east-1.amazonaws.com", + "codepipeline": "codepipeline.us-east-1.amazonaws.com", + "codestar": "codestar.us-east-1.amazonaws.com", + "codestar-connections": "codestar-connections.us-east-1.amazonaws.com", + "codestar-notifications": "codestar-notifications.us-east-1.amazonaws.com", + "cognito-identity": "cognito-identity.us-east-1.amazonaws.com", + "cognito-idp": "cognito-idp.us-east-1.amazonaws.com", + "cognito-sync": "cognito-sync.us-east-1.amazonaws.com", + "comprehend": "comprehend.us-east-1.amazonaws.com", + "comprehendmedical": "comprehendmedical.us-east-1.amazonaws.com", + "compute-optimizer": "compute-optimizer.us-east-1.amazonaws.com", + "config": "config.us-east-1.amazonaws.com", + "connect": "connect.us-east-1.amazonaws.com", + "cur": "cur.us-east-1.amazonaws.com", + "databrew": "databrew.us-east-1.amazonaws.com", + "dataexchange": "dataexchange.us-east-1.amazonaws.com", + "datapipeline": "datapipeline.us-east-1.amazonaws.com", + "datasync": "datasync.us-east-1.amazonaws.com", + "dax": "dax.us-east-1.amazonaws.com", + "detective": "api.detective.us-east-1.amazonaws.com", + "devicefarm": "devicefarm.us-east-1.amazonaws.com", + "devops-guru": "devops-guru.us-east-1.amazonaws.com", + "directconnect": "directconnect.us-east-1.amazonaws.com", + "discovery": "discovery.us-east-1.amazonaws.com", + "dlm": "dlm.us-east-1.amazonaws.com", + "dms": "dms.us-east-1.amazonaws.com", + "drs": "drs.us-east-1.amazonaws.com", + "ds": "ds.us-east-1.amazonaws.com", + "dynamodb": "dynamodb.us-east-1.amazonaws.com", + "ebs": "ebs.us-east-1.amazonaws.com", + "ec2": "ec2.us-east-1.amazonaws.com", + "ec2-instance-connect": "ec2-instance-connect.us-east-1.amazonaws.com", + "ecr": "api.ecr.us-east-1.amazonaws.com", + "ecr-public": "api.ecr-public.us-east-1.amazonaws.com", + "ecs": "ecs.us-east-1.amazonaws.com", + "eks": "eks.us-east-1.amazonaws.com", + "elastic-inference": "api.elastic-inference.us-east-1.amazonaws.com", + "elasticache": "elasticache.us-east-1.amazonaws.com", + "elasticbeanstalk": "elasticbeanstalk.us-east-1.amazonaws.com", + "elasticfilesystem": "elasticfilesystem.us-east-1.amazonaws.com", + "elasticloadbalancing": "elasticloadbalancing.us-east-1.amazonaws.com", + "elasticmapreduce": "elasticmapreduce.us-east-1.amazonaws.com", + "elastictranscoder": "elastictranscoder.us-east-1.amazonaws.com", + "emr-containers": "emr-containers.us-east-1.amazonaws.com", + "es": "es.us-east-1.amazonaws.com", + "events": "events.us-east-1.amazonaws.com", + "evidently": "evidently.us-east-1.amazonaws.com", + "finspace": "finspace.us-east-1.amazonaws.com", + "finspace-api": "finspace-api.us-east-1.amazonaws.com", + "firehose": "firehose.us-east-1.amazonaws.com", + "fis": "fis.us-east-1.amazonaws.com", + "fms": "fms.us-east-1.amazonaws.com", + "forecast": "forecast.us-east-1.amazonaws.com", + "frauddetector": "frauddetector.us-east-1.amazonaws.com", + "fsx": "fsx.us-east-1.amazonaws.com", + "gamelift": "gamelift.us-east-1.amazonaws.com", + "geo": "geo.us-east-1.amazonaws.com", + "glacier": "glacier.us-east-1.amazonaws.com", + "globalaccelerator": "globalaccelerator.us-east-1.amazonaws.com", + "glue": "glue.us-east-1.amazonaws.com", + "grafana": "grafana.us-east-1.amazonaws.com", + "greengrass": "greengrass.us-east-1.amazonaws.com", + "groundstation": "groundstation.us-east-1.amazonaws.com", + "guardduty": "guardduty.us-east-1.amazonaws.com", + "health": "health.us-east-1.amazonaws.com", + "healthlake": "healthlake.us-east-1.amazonaws.com", + "honeycode": "honeycode.us-east-1.amazonaws.com", + "iam": "iam.amazonaws.com", + "identitystore": "identitystore.us-east-1.amazonaws.com", + "imagebuilder": "imagebuilder.us-east-1.amazonaws.com", + "inspector": "inspector.us-east-1.amazonaws.com", + "inspector2": "inspector2.us-east-1.amazonaws.com", + "iot": "iot.us-east-1.amazonaws.com", + "iot-jobs-data": "data.jobs.iot.us-east-1.amazonaws.com", + "iot1click": "devices.iot1click.us-east-1.amazonaws.com", + "iotanalytics": "iotanalytics.us-east-1.amazonaws.com", + "iotdata": "data.iot.us-east-1.amazonaws.com", + "iotdeviceadvisor": "api.iotdeviceadvisor.us-east-1.amazonaws.com", + "iotevents": "iotevents.us-east-1.amazonaws.com", + "ioteventsdata": "data.iotevents.us-east-1.amazonaws.com", + "iotfleethub": "api.fleethub.iot.us-east-1.amazonaws.com", + "iotsecuredtunneling": "api.tunneling.iot.us-east-1.amazonaws.com", + "iotsitewise": "iotsitewise.us-east-1.amazonaws.com", + "iotthingsgraph": "iotthingsgraph.us-east-1.amazonaws.com", + "iotwireless": "api.iotwireless.us-east-1.amazonaws.com", + "ivs": "ivs.us-east-1.amazonaws.com", + "kafka": "kafka.us-east-1.amazonaws.com", + "kafkaconnect": "kafkaconnect.us-east-1.amazonaws.com", + "kendra": "kendra.us-east-1.amazonaws.com", + "kinesis": "kinesis.us-east-1.amazonaws.com", + "kinesisanalytics": "kinesisanalytics.us-east-1.amazonaws.com", + "kinesisvideo": "kinesisvideo.us-east-1.amazonaws.com", + "kms": "kms.us-east-1.amazonaws.com", + "lakeformation": "lakeformation.us-east-1.amazonaws.com", + "lambda": "lambda.us-east-1.amazonaws.com", + "lex": "models-v2-lex.us-east-1.amazonaws.com", + "license-manager": "license-manager.us-east-1.amazonaws.com", + "lightsail": "lightsail.us-east-1.amazonaws.com", + "logs": "logs.us-east-1.amazonaws.com", + "lookoutequipment": "lookoutequipment.us-east-1.amazonaws.com", + "lookoutmetrics": "lookoutmetrics.us-east-1.amazonaws.com", + "lookoutvision": "lookoutvision.us-east-1.amazonaws.com", + "machinelearning": "machinelearning.us-east-1.amazonaws.com", + "macie": "macie.us-east-1.amazonaws.com", + "macie2": "macie2.us-east-1.amazonaws.com", + "managedblockchain": "managedblockchain.us-east-1.amazonaws.com", + "marketplacecommerceanalytics": "marketplacecommerceanalytics.us-east-1.amazonaws.com", + "mediaconnect": "mediaconnect.us-east-1.amazonaws.com", + "mediaconvert": "mediaconvert.us-east-1.amazonaws.com", + "medialive": "medialive.us-east-1.amazonaws.com", + "mediapackage": "mediapackage.us-east-1.amazonaws.com", + "mediapackage-vod": "mediapackage-vod.us-east-1.amazonaws.com", + "mediastore": "mediastore.us-east-1.amazonaws.com", + "mediatailor": "api.mediatailor.us-east-1.amazonaws.com", + "memorydb": "memory-db.us-east-1.amazonaws.com", + "mgh": "mgh.us-east-1.amazonaws.com", + "mgn": "mgn.us-east-1.amazonaws.com", + "mobiletargeting": "pinpoint.us-east-1.amazonaws.com", + "monitoring": "monitoring.us-east-1.amazonaws.com", + "mq": "mq.us-east-1.amazonaws.com", + "mturk-requester": "mturk-requester.us-east-1.amazonaws.com", + "network-firewall": "network-firewall.us-east-1.amazonaws.com", + "networkmanager": "networkmanager.us-west-2.amazonaws.com", // Maps to us-west-2. + "nimble": "nimble.us-east-1.amazonaws.com", + "opsworks": "opsworks.us-east-1.amazonaws.com", + "opsworks-cm": "opsworks-cm.us-east-1.amazonaws.com", + "organizations": "organizations.us-east-1.amazonaws.com", + "outposts": "outposts.us-east-1.amazonaws.com", + "panorama": "panorama.us-east-1.amazonaws.com", + "personalize": "personalize.us-east-1.amazonaws.com", + "pi": "pi.us-east-1.amazonaws.com", + "polly": "polly.us-east-1.amazonaws.com", + "pricing": "api.pricing.us-east-1.amazonaws.com", + "profile": "profile.us-east-1.amazonaws.com", + "qldb": "qldb.us-east-1.amazonaws.com", + "quicksight": "quicksight.us-east-1.amazonaws.com", + "ram": "ram.us-east-1.amazonaws.com", + "rbin": "rbin.us-east-1.amazonaws.com", + "rds": "rds.us-east-1.amazonaws.com", + "rds-data": "rds-data.us-east-1.amazonaws.com", + "redshift": "redshift.us-east-1.amazonaws.com", + "redshift-data": "redshift-data.us-east-1.amazonaws.com", + "refactor-spaces": "refactor-spaces.us-east-1.amazonaws.com", + "rekognition": "rekognition.us-east-1.amazonaws.com", + "resiliencehub": "resiliencehub.us-east-1.amazonaws.com", + "resource-groups": "resource-groups.us-east-1.amazonaws.com", + "robomaker": "robomaker.us-east-1.amazonaws.com", + "route53": "route53.amazonaws.com", + "route53-recovery-cluster": "route53-recovery-cluster.us-east-1.amazonaws.com", + "route53-recovery-control-config": "route53-recovery-control-config.us-east-1.amazonaws.com", + "route53-recovery-readiness": "route53-recovery-readiness.us-east-1.amazonaws.com", + "route53domains": "route53domains.us-east-1.amazonaws.com", + "route53resolver": "route53resolver.us-east-1.amazonaws.com", + "rum": "rum.us-east-1.amazonaws.com", + "s3": "s3.amazonaws.com", + "s3-outposts": "s3-outposts.us-east-1.amazonaws.com", + "sagemaker": "api.sagemaker.us-east-1.amazonaws.com", + "savingsplans": "savingsplans.amazonaws.com", + "schemas": "schemas.us-east-1.amazonaws.com", + "secretsmanager": "secretsmanager.us-east-1.amazonaws.com", + "securityhub": "securityhub.us-east-1.amazonaws.com", + "serverlessrepo": "serverlessrepo.us-east-1.amazonaws.com", + "servicecatalog": "servicecatalog.us-east-1.amazonaws.com", + "servicediscovery": "servicediscovery.us-east-1.amazonaws.com", + "servicequotas": "servicequotas.us-east-1.amazonaws.com", + "ses": "email.us-east-1.amazonaws.com", + "shield": "shield.us-east-1.amazonaws.com", + "signer": "signer.us-east-1.amazonaws.com", + "sms": "sms.us-east-1.amazonaws.com", + "sms-voice": "sms-voice.pinpoint.us-east-1.amazonaws.com", + "snow-device-management": "snow-device-management.us-east-1.amazonaws.com", + "snowball": "snowball.us-east-1.amazonaws.com", + "sns": "sns.us-east-1.amazonaws.com", + "sqs": "sqs.us-east-1.amazonaws.com", + "ssm": "ssm.us-east-1.amazonaws.com", + "ssm-contacts": "ssm-contacts.us-east-1.amazonaws.com", + "ssm-incidents": "ssm-incidents.us-east-1.amazonaws.com", + "sso": "sso.us-east-1.amazonaws.com", + "states": "states.us-east-1.amazonaws.com", + "storagegateway": "storagegateway.us-east-1.amazonaws.com", + "sts": "sts.amazonaws.com", + "support": "support.us-east-1.amazonaws.com", + "swf": "swf.us-east-1.amazonaws.com", + "synthetics": "synthetics.us-east-1.amazonaws.com", + "tagging": "tagging.us-east-1.amazonaws.com", + "textract": "textract.us-east-1.amazonaws.com", + "timestream": "query.timestream.us-east-1.amazonaws.com", + "transcribe": "transcribe.us-east-1.amazonaws.com", + "transfer": "transfer.us-east-1.amazonaws.com", + "translate": "translate.us-east-1.amazonaws.com", + "voiceid": "voiceid.us-east-1.amazonaws.com", + "waf": "waf.amazonaws.com", + "waf-regional": "waf-regional.us-east-1.amazonaws.com", + "wafv2": "wafv2.us-east-1.amazonaws.com", + "wellarchitected": "wellarchitected.us-east-1.amazonaws.com", + "wisdom": "wisdom.us-east-1.amazonaws.com", + "workdocs": "workdocs.us-east-1.amazonaws.com", + "worklink": "worklink.us-east-1.amazonaws.com", + "workmail": "workmail.us-east-1.amazonaws.com", + "workmailmessageflow": "workmailmessageflow.us-east-1.amazonaws.com", + "workspaces": "workspaces.us-east-1.amazonaws.com", + "workspaces-web": "workspaces-web.us-east-1.amazonaws.com", + "xray": "xray.us-east-1.amazonaws.com", + + // TODO here is a list of hostnames sharing same signing names. They are + // currently commented out since we don't know how to handle them yet. + // "apigateway": "apigateway.us-east-1.amazonaws.com", + // "apigateway": "execute-api.us-east-1.amazonaws.com", + // "aws-marketplace": "catalog.marketplace.us-east-1.amazonaws.com", + // "aws-marketplace": "entitlement.marketplace.us-east-1.amazonaws.com", + // "aws-marketplace": "metering.marketplace.us-east-1.amazonaws.com", + // "chime": "chime.us-east-1.amazonaws.com", + // "chime": "identity-chime.us-east-1.amazonaws.com", + // "chime": "meetings-chime.us-east-1.amazonaws.com", + // "chime": "messaging-chime.us-east-1.amazonaws.com", + // "cloudhsm": "cloudhsm.us-east-1.amazonaws.com", + // "cloudhsm": "cloudhsmv2.us-east-1.amazonaws.com", + // "cloudsearch": "cloudsearch.us-east-1.amazonaws.com", + // "cloudsearch": "cloudsearchdomain.us-east-1.amazonaws.com", + // "connect": "connect.us-east-1.amazonaws.com", + // "connect": "contact-lens.us-east-1.amazonaws.com", + // "connect": "participant.connect.us-east-1.amazonaws.com", + // "dynamodb": "dynamodb.us-east-1.amazonaws.com", + // "dynamodb": "streams.dynamodb.us-east-1.amazonaws.com", + // "forecast": "forecast.us-east-1.amazonaws.com", + // "forecast": "forecastquery.us-east-1.amazonaws.com", + // "iot1click": "devices.iot1click.us-east-1.amazonaws.com", + // "iot1click": "projects.iot1click.us-east-1.amazonaws.com", + // "lex": "models-v2-lex.us-east-1.amazonaws.com", + // "lex": "models.lex.us-east-1.amazonaws.com", + // "lex": "runtime-v2-lex.us-east-1.amazonaws.com", + // "lex": "runtime.lex.us-east-1.amazonaws.com", + // "mediastore": "data.mediastore.us-east-1.amazonaws.com", + // "mediastore": "mediastore.us-east-1.amazonaws.com", + // "mgh": "mgh.us-east-1.amazonaws.com", + // "mgh": "migrationhub-config.us-east-1.amazonaws.com", + // "personalize": "personalize-events.us-east-1.amazonaws.com", + // "personalize": "personalize-runtime.us-east-1.amazonaws.com", + // "personalize": "personalize.us-east-1.amazonaws.com", + // "qldb": "session.qldb.us-east-1.amazonaws.com", + // "qldb": "qldb.us-east-1.amazonaws.com", + // "s3": "s3-control.dualstack.us-east-1.amazonaws.com", + // "s3": "s3.amazonaws.com", + // "sagemaker": "a2i-runtime.sagemaker.us-east-1.amazonaws.com", + // "sagemaker": "api.sagemaker.us-east-1.amazonaws.com", + // "sagemaker": "edge.sagemaker.us-east-1.amazonaws.com", + // "sagemaker": "featurestore-runtime.sagemaker.us-east-1.amazonaws.com", + // "sagemaker": "runtime.sagemaker.us-east-1.amazonaws.com", + // "servicecatalog": "servicecatalog-appregistry.us-east-1.amazonaws.com", + // "servicecatalog": "servicecatalog.us-east-1.amazonaws.com", + // "timestream": "ingest.timestream.us-east-1.amazonaws.com", + // "timestream": "query.timestream.us-east-1.amazonaws.com", + // "transcribe": "transcribe.us-east-1.amazonaws.com", + // "transcribe": "transcribestreaming.us-east-1.amazonaws.com", +} + +func TestResolveEndpoints(t *testing.T) { + signer := v4.NewSigner(credentials.NewStaticCredentials("fakeClientKeyID", "fakeClientSecret", "")) + region := "us-east-1" + now := time.Now() + + for signingName := range signingNameToHostname { + req, err := http.NewRequest("GET", "http://localhost", nil) + require.NoError(t, err) + + _, err = signer.Sign(req, bytes.NewReader(nil), signingName, region, now) + require.NoError(t, err) + + endpoint, err := resolveEndpoint(req) + require.NoError(t, err) + require.Equal(t, signingName, endpoint.SigningName) + require.Equal(t, "https://"+signingNameToHostname[signingName], endpoint.URL, "for signing name %q", signingName) + } +} diff --git a/lib/srv/app/aws/handler.go b/lib/srv/app/aws/handler.go index 4a5a820cb099e..96936dd2073b4 100644 --- a/lib/srv/app/aws/handler.go +++ b/lib/srv/app/aws/handler.go @@ -185,20 +185,6 @@ func (s *SigningService) formatForwardResponseError(rw http.ResponseWriter, r *h } } -// resolveEndpoint extracts the aws-service on and aws-region from the request authorization header -// and resolves the aws-service and aws-region to AWS endpoint. -func resolveEndpoint(r *http.Request) (*endpoints.ResolvedEndpoint, error) { - awsAuthHeader, err := awsutils.ParseSigV4(r.Header.Get(awsutils.AuthorizationHeader)) - if err != nil { - return nil, trace.Wrap(err) - } - resolvedEndpoint, err := endpoints.DefaultResolver().EndpointFor(awsAuthHeader.Service, awsAuthHeader.Region) - if err != nil { - return nil, trace.Wrap(err) - } - return &resolvedEndpoint, nil -} - // prepareSignedRequest creates a new HTTP request and rewrites the header from the original request and returns a new // HTTP request signed by STS AWS API. func (s *SigningService) prepareSignedRequest(r *http.Request, re *endpoints.ResolvedEndpoint, identity *tlsca.Identity) (*http.Request, error) { diff --git a/lib/srv/app/server.go b/lib/srv/app/server.go index 148a039f3c3dc..83e3a9f5884b2 100644 --- a/lib/srv/app/server.go +++ b/lib/srv/app/server.go @@ -203,10 +203,10 @@ func (m *monitoredApps) setResources(apps types.Apps) { m.resources = apps } -func (m *monitoredApps) get() types.ResourcesWithLabels { +func (m *monitoredApps) get() types.ResourcesWithLabelsMap { m.mu.Lock() defer m.mu.Unlock() - return append(m.static, m.resources...).AsResources() + return append(m.static, m.resources...).AsResources().ToMap() } // New returns a new application server. diff --git a/lib/srv/app/server_test.go b/lib/srv/app/server_test.go index 0f6bbbe67e788..aa1a6e935c0af 100644 --- a/lib/srv/app/server_test.go +++ b/lib/srv/app/server_test.go @@ -155,7 +155,7 @@ func SetUpSuiteWithConfig(t *testing.T, config suiteConfig) *Suite { err = s.tlsServer.Auth().UpsertRole(context.Background(), s.role) require.NoError(t, err) - rootCA, err := s.tlsServer.Auth().GetCertAuthority(types.CertAuthID{ + rootCA, err := s.tlsServer.Auth().GetCertAuthority(context.Background(), types.CertAuthID{ Type: types.HostCA, DomainName: "root.example.com", }, false) diff --git a/lib/srv/app/watcher.go b/lib/srv/app/watcher.go index 7f8cc11b1f81c..94552cf92be37 100644 --- a/lib/srv/app/watcher.go +++ b/lib/srv/app/watcher.go @@ -18,11 +18,13 @@ package app import ( "context" + "fmt" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/services" - + "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/trace" ) @@ -82,7 +84,11 @@ func (s *Server) startResourceWatcher(ctx context.Context) (*services.AppWatcher for { select { case apps := <-watcher.AppsC: - s.monitoredApps.setResources(apps) + appsWithAddr := make(types.Apps, 0, len(apps)) + for _, app := range apps { + appsWithAddr = append(appsWithAddr, s.guessPublicAddr(app)) + } + s.monitoredApps.setResources(appsWithAddr) select { case s.reconcileCh <- struct{}{}: case <-ctx.Done(): @@ -97,8 +103,54 @@ func (s *Server) startResourceWatcher(ctx context.Context) (*services.AppWatcher return watcher, nil } -func (s *Server) getResources() (resources types.ResourcesWithLabels) { - return s.getApps().AsResources() +// guessPublicAddr will guess PublicAddr for given application if it is missing, based on proxy information and app name. +func (s *Server) guessPublicAddr(app types.Application) types.Application { + if app.GetPublicAddr() != "" { + return app + } + appCopy := app.Copy() + pubAddr, err := FindPublicAddr(s.c.AccessPoint, app.GetPublicAddr(), app.GetName()) + if err == nil { + appCopy.Spec.PublicAddr = pubAddr + } else { + s.log.WithError(err).Errorf("Unable to find public address for app %q, leaving empty.", app.GetName()) + } + return appCopy +} + +// FindPublicAddr tries to resolve the public address of the proxy of this cluster. +func FindPublicAddr(authClient auth.ReadAppsAccessPoint, appPublicAddr string, appName string) (string, error) { + // If the application has a public address already set, use it. + if appPublicAddr != "" { + return appPublicAddr, nil + } + + // Fetch list of proxies, if first has public address set, use it. + servers, err := authClient.GetProxies() + if err != nil { + return "", trace.Wrap(err) + } + if len(servers) == 0 { + return "", trace.BadParameter("cluster has no proxy registered, at least one proxy must be registered for application access") + } + if servers[0].GetPublicAddr() != "" { + addr, err := utils.ParseAddr(servers[0].GetPublicAddr()) + if err != nil { + return "", trace.Wrap(err) + } + return fmt.Sprintf("%v.%v", appName, addr.Host()), nil + } + + // Fall back to cluster name. + cn, err := authClient.GetClusterName() + if err != nil { + return "", trace.Wrap(err) + } + return fmt.Sprintf("%v.%v", appName, cn.GetClusterName()), nil +} + +func (s *Server) getResources() (resources types.ResourcesWithLabelsMap) { + return s.getApps().AsResources().ToMap() } func (s *Server) onCreate(ctx context.Context, resource types.ResourceWithLabels) error { diff --git a/lib/srv/authhandlers.go b/lib/srv/authhandlers.go index 4664b43a2f33a..0615da51ca3f8 100644 --- a/lib/srv/authhandlers.go +++ b/lib/srv/authhandlers.go @@ -17,6 +17,7 @@ limitations under the License. package srv import ( + "context" "encoding/json" "fmt" "net" @@ -507,7 +508,7 @@ func (h *AuthHandlers) fetchRoleSet(cert *ssh.Certificate, ca types.CertAuthorit // Certificate Authority and returns it. func (h *AuthHandlers) authorityForCert(caType types.CertAuthType, key ssh.PublicKey) (types.CertAuthority, error) { // get all certificate authorities for given type - cas, err := h.c.AccessPoint.GetCertAuthorities(caType, false) + cas, err := h.c.AccessPoint.GetCertAuthorities(context.TODO(), caType, false) if err != nil { h.log.Warnf("%v", trace.DebugReport(err)) return nil, trace.Wrap(err) diff --git a/lib/srv/ctx.go b/lib/srv/ctx.go index 05157991b7809..273e9172d4c42 100644 --- a/lib/srv/ctx.go +++ b/lib/srv/ctx.go @@ -99,7 +99,7 @@ type AccessPoint interface { GetRole(ctx context.Context, name string) (types.Role, error) // GetCertAuthorities returns a list of cert authorities - GetCertAuthorities(caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) + GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool, opts ...services.MarshalOption) ([]types.CertAuthority, error) } // Server is regular or forwarding SSH server. @@ -369,6 +369,18 @@ func NewServerContext(ctx context.Context, parent *sshutils.ConnectionContext, s cancel: cancel, } + fields := log.Fields{ + "local": child.ServerConn.LocalAddr(), + "remote": child.ServerConn.RemoteAddr(), + "login": child.Identity.Login, + "teleportUser": child.Identity.TeleportUser, + "id": child.id, + } + child.Entry = log.WithFields(log.Fields{ + trace.Component: child.srv.Component(), + trace.ComponentFields: fields, + }) + authPref, err := srv.GetAccessPoint().GetAuthPreference(ctx) if err != nil { childErr := child.Close() @@ -379,13 +391,7 @@ func NewServerContext(ctx context.Context, parent *sshutils.ConnectionContext, s child.disconnectExpiredCert = identityContext.CertValidBefore } - fields := log.Fields{ - "local": child.ServerConn.LocalAddr(), - "remote": child.ServerConn.RemoteAddr(), - "login": child.Identity.Login, - "teleportUser": child.Identity.TeleportUser, - "id": child.id, - } + // Update log entry fields. if !child.disconnectExpiredCert.IsZero() { fields["cert"] = child.disconnectExpiredCert } diff --git a/lib/srv/db/access_test.go b/lib/srv/db/access_test.go index e8824cd564219..d7f0884f2ee2c 100644 --- a/lib/srv/db/access_test.go +++ b/lib/srv/db/access_test.go @@ -23,7 +23,6 @@ import ( "fmt" "net" "os" - "sort" "sync" "testing" "time" @@ -429,6 +428,31 @@ func TestAccessMySQLChangeUser(t *testing.T) { require.Error(t, err) } +func TestMySQLCloseConnection(t *testing.T) { + ctx := context.Background() + testCtx := setupTestContext(ctx, t, withSelfHostedMySQL("mysql")) + go testCtx.startHandlingConnections() + + // Create user/role with the requested permissions. + testCtx.createUserAndRole(ctx, t, "alice", "admin", []string{"alice"}, []string{types.Wildcard}) + + // Connect to the database as this user. + mysqlConn, err := testCtx.mysqlClient("alice", "mysql", "alice") + require.NoError(t, err) + + _, err = mysqlConn.Execute("select 1") + require.NoError(t, err) + + // Close connection to DB proxy + err = mysqlConn.Close() + require.NoError(t, err) + + // DB proxy should close the DB connection and send COM_QUIT message. + require.Eventually(t, func() bool { + return testCtx.mysql["mysql"].db.ConnsClosed() + }, 2*time.Second, 100*time.Millisecond) +} + // TestAccessRedisAUTHDefaultCmd checks if empty user can log in to Redis as default. func TestAccessRedisAUTHDefaultCmd(t *testing.T) { ctx := context.Background() @@ -493,6 +517,9 @@ func TestAccessMySQLServerPacket(t *testing.T) { // in a way that previously would cause our packet parsing logic to fail. _, err = mysqlConn.Execute("show tables") require.NoError(t, err) + + err = mysqlConn.Close() + require.NoError(t, err) } // TestGCPRequireSSL tests connecting to GCP Cloud SQL Postgres and MySQL @@ -771,12 +798,12 @@ func TestAccessMongoDB(t *testing.T) { testCtx.createUserAndRole(ctx, t, test.user, test.role, test.allowDbUsers, test.allowDbNames) // Try to connect to the database as this user. - client, err := testCtx.mongoClient(ctx, test.user, "mongo", test.dbUser, clientOpt.opts) - defer func() { - if client != nil { - client.Disconnect(ctx) + mongoClient, err := testCtx.mongoClient(ctx, test.user, "mongo", test.dbUser, clientOpt.opts) + t.Cleanup(func() { + if mongoClient != nil { + require.NoError(t, mongoClient.Disconnect(ctx)) } - }() + }) if test.connectErr != "" { require.Error(t, err) require.Contains(t, err.Error(), test.connectErr) @@ -785,13 +812,14 @@ func TestAccessMongoDB(t *testing.T) { require.NoError(t, err) // Execute a "find" command. Collection name doesn't matter currently. - _, err = client.Database(test.dbName).Collection("test").Find(ctx, bson.M{}) + records, err := mongoClient.Database(test.dbName).Collection("test").Find(ctx, bson.M{}) if test.queryErr != "" { require.Error(t, err) require.Contains(t, err.Error(), test.queryErr) return } require.NoError(t, err) + require.NoError(t, records.Close(ctx)) }) } } @@ -921,7 +949,7 @@ func TestCompatibilityWithOldAgents(t *testing.T) { }, }) go func() { - for conn := range testCtx.proxyConn { + for conn := range testCtx.fakeRemoteSite.ProxyConn() { go databaseServer.HandleConnection(conn) } }() @@ -1222,7 +1250,6 @@ type testContext struct { mux *multiplexer.Mux mysqlListener net.Listener webListener *multiplexer.WebListener - proxyConn chan net.Conn fakeRemoteSite *reversetunnel.FakeRemoteSite server *Server emitter *testEmitter @@ -1300,7 +1327,7 @@ func (c *testContext) startHandlingConnections() { // Start all proxy services. c.startProxy() // Start handling database client connections on the database server. - for conn := range c.proxyConn { + for conn := range c.fakeRemoteSite.ProxyConn() { go c.server.HandleConnection(conn) } } @@ -1518,6 +1545,12 @@ func (c *testContext) Close() error { return trace.NewAggregate(errors...) } +func init() { + // Override database agents shuffle behavior to ensure they're always + // tried in the same order during tests. Used for HA tests. + SetShuffleFunc(ShuffleSort) +} + func setupTestContext(ctx context.Context, t *testing.T, withDatabases ...withDatabaseOption) *testContext { testCtx := &testContext{ clusterName: "root.example.com", @@ -1538,8 +1571,12 @@ func setupTestContext(ctx context.Context, t *testing.T, withDatabases ...withDa Dir: t.TempDir(), }) require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, authServer.Close()) }) + testCtx.tlsServer, err = authServer.NewTestTLSServer() require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, testCtx.tlsServer.Close()) }) + testCtx.authServer = testCtx.tlsServer.Auth() // Create multiplexer. @@ -1557,6 +1594,7 @@ func setupTestContext(ctx context.Context, t *testing.T, withDatabases ...withDa Listener: tls.NewListener(testCtx.mux.TLS(), testCtx.makeTLSConfig(t)), }) require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, testCtx.webListener.Close()) }) // Create MySQL proxy listener. testCtx.mysqlListener, err = net.Listen("tcp", "localhost:0") @@ -1572,12 +1610,16 @@ func setupTestContext(ctx context.Context, t *testing.T, withDatabases ...withDa // Auth client for database service. testCtx.authClient, err = testCtx.tlsServer.NewClient(auth.TestServerID(types.RoleDatabase, testCtx.hostID)) require.NoError(t, err) - testCtx.hostCA, err = testCtx.authClient.GetCertAuthority(types.CertAuthID{Type: types.HostCA, DomainName: testCtx.clusterName}, false) + t.Cleanup(func() { require.NoError(t, testCtx.authClient.Close()) }) + + testCtx.hostCA, err = testCtx.authClient.GetCertAuthority(ctx, types.CertAuthID{Type: types.HostCA, DomainName: testCtx.clusterName}, false) require.NoError(t, err) // Auth client, lock watcher and authorizer for database proxy. proxyAuthClient, err := testCtx.tlsServer.NewClient(auth.TestBuiltin(types.RoleProxy)) require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, proxyAuthClient.Close()) }) + proxyLockWatcher, err := services.NewLockWatcher(ctx, services.LockWatcherConfig{ ResourceWatcherConfig: services.ResourceWatcherConfig{ Component: teleport.ComponentProxy, @@ -1601,12 +1643,10 @@ func setupTestContext(ctx context.Context, t *testing.T, withDatabases ...withDa } // Establish fake reversetunnel b/w database proxy and database service. - testCtx.proxyConn = make(chan net.Conn) - testCtx.fakeRemoteSite = &reversetunnel.FakeRemoteSite{ - Name: testCtx.clusterName, - ConnCh: testCtx.proxyConn, - AccessPoint: proxyAuthClient, - } + testCtx.fakeRemoteSite = reversetunnel.NewFakeRemoteSite(testCtx.clusterName, proxyAuthClient) + t.Cleanup(func() { + testCtx.fakeRemoteSite.Close() + }) tunnel := &reversetunnel.FakeServer{ Sites: []reversetunnel.RemoteSite{ testCtx.fakeRemoteSite, @@ -1630,11 +1670,6 @@ func setupTestContext(ctx context.Context, t *testing.T, withDatabases ...withDa Emitter: testCtx.emitter, Clock: testCtx.clock, ServerID: "proxy-server", - Shuffle: func(servers []types.DatabaseServer) []types.DatabaseServer { - // To ensure predictability in tests, sort servers instead of shuffling. - sort.Sort(types.DatabaseServers(servers)) - return servers - }, LockWatcher: proxyLockWatcher, }) require.NoError(t, err) @@ -1929,7 +1964,9 @@ func withSelfHostedMySQL(name string) withDatabaseOption { }) require.NoError(t, err) go mysqlServer.Serve() - t.Cleanup(func() { mysqlServer.Close() }) + t.Cleanup(func() { + require.NoError(t, mysqlServer.Close()) + }) database, err := types.NewDatabaseV3(types.Metadata{ Name: name, }, types.DatabaseSpecV3{ diff --git a/lib/srv/db/audit_test.go b/lib/srv/db/audit_test.go index 4b85e6058dbd7..c6d621b62b782 100644 --- a/lib/srv/db/audit_test.go +++ b/lib/srv/db/audit_test.go @@ -122,22 +122,22 @@ func TestAuditMongo(t *testing.T) { waitForEvent(t, testCtx, libevents.DatabaseSessionStartFailureCode) // Connect should trigger successful session start event. - mongo, err := testCtx.mongoClient(ctx, "alice", "mongo", "admin") + mongoClient, err := testCtx.mongoClient(ctx, "alice", "mongo", "admin") require.NoError(t, err) waitForEvent(t, testCtx, libevents.DatabaseSessionStartCode) // Find command in a database we don't have access to. - _, err = mongo.Database("notadmin").Collection("test").Find(ctx, bson.M{}) + _, err = mongoClient.Database("notadmin").Collection("test").Find(ctx, bson.M{}) require.Error(t, err) waitForEvent(t, testCtx, libevents.DatabaseSessionQueryFailedCode) // Find command should trigger the query event. - _, err = mongo.Database("admin").Collection("test").Find(ctx, bson.M{}) + _, err = mongoClient.Database("admin").Collection("test").Find(ctx, bson.M{}) require.NoError(t, err) waitForEvent(t, testCtx, libevents.DatabaseSessionQueryCode) // Closing connection should trigger session end event. - err = mongo.Disconnect(ctx) + err = mongoClient.Disconnect(ctx) require.NoError(t, err) waitForEvent(t, testCtx, libevents.DatabaseSessionEndCode) } diff --git a/lib/srv/db/ca.go b/lib/srv/db/ca.go index dbb4a05b53097..aeb3c8c01a80c 100644 --- a/lib/srv/db/ca.go +++ b/lib/srv/db/ca.go @@ -19,8 +19,9 @@ package db import ( "context" "fmt" - "io/ioutil" + "io" "net/http" + "os" "path/filepath" "github.com/gravitational/teleport" @@ -81,7 +82,7 @@ func (s *Server) getCACert(ctx context.Context, database types.Database) ([]byte // It's already downloaded. if err == nil { s.log.Debugf("Loaded CA certificate %v.", filePath) - return ioutil.ReadFile(filePath) + return os.ReadFile(filePath) } // Otherwise download it. s.log.Debugf("Downloading CA certificate for %v.", database) @@ -90,7 +91,7 @@ func (s *Server) getCACert(ctx context.Context, database types.Database) ([]byte return nil, trace.Wrap(err) } // Save to the filesystem. - err = ioutil.WriteFile(filePath, bytes, teleport.FileMaskOwnerOnly) + err = os.WriteFile(filePath, bytes, teleport.FileMaskOwnerOnly) if err != nil { return nil, trace.Wrap(err) } @@ -158,7 +159,7 @@ func (d *realDownloader) downloadFromURL(downloadURL string) ([]byte, error) { return nil, trace.BadParameter("status code %v when fetching from %q", resp.StatusCode, downloadURL) } - bytes, err := ioutil.ReadAll(resp.Body) + bytes, err := io.ReadAll(resp.Body) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/srv/db/ca_test.go b/lib/srv/db/ca_test.go index 5525f81697934..663dd5dba4e98 100644 --- a/lib/srv/db/ca_test.go +++ b/lib/srv/db/ca_test.go @@ -237,7 +237,7 @@ func setupPostgres(ctx context.Context, t *testing.T, cfg *setupTLSTestCfg) *tes }) go func() { - for conn := range testCtx.proxyConn { + for conn := range testCtx.fakeRemoteSite.ProxyConn() { go server1.HandleConnection(conn) } }() @@ -282,7 +282,7 @@ func setupMySQL(ctx context.Context, t *testing.T, cfg *setupTLSTestCfg) *testCo }) go func() { - for conn := range testCtx.proxyConn { + for conn := range testCtx.fakeRemoteSite.ProxyConn() { go server1.HandleConnection(conn) } }() @@ -307,7 +307,9 @@ func setupMongo(ctx context.Context, t *testing.T, cfg *setupTLSTestCfg) *testCo }) require.NoError(t, err) go mongoServer.Serve() - t.Cleanup(func() { mongoServer.Close() }) + t.Cleanup(func() { + require.NoError(t, mongoServer.Close()) + }) mongoDB, err := types.NewDatabaseV3(types.Metadata{ Name: "mongo", @@ -325,9 +327,12 @@ func setupMongo(ctx context.Context, t *testing.T, cfg *setupTLSTestCfg) *testCo server1 := testCtx.setupDatabaseServer(ctx, t, agentParams{ Databases: types.Databases{mongoDB}, }) + t.Cleanup(func() { + require.NoError(t, server1.Close()) + }) go func() { - for conn := range testCtx.proxyConn { + for conn := range testCtx.fakeRemoteSite.ProxyConn() { go server1.HandleConnection(conn) } }() @@ -463,11 +468,12 @@ func TestTLSConfiguration(t *testing.T) { }) mongoConn, err := testCtx.mongoClient(ctx, "bob", "mongo", "admin") - if tt.errMsg == "" { - require.NoError(t, err) - + t.Cleanup(func() { err = mongoConn.Disconnect(ctx) require.NoError(t, err) + }) + if tt.errMsg == "" { + require.NoError(t, err) } else { require.Error(t, err) // Do not verify Mongo error message. On authentication error Mongo re-tries and diff --git a/lib/srv/db/cloud/watchers/rds.go b/lib/srv/db/cloud/watchers/rds.go index 8d11c09d28334..17fa187f6787d 100644 --- a/lib/srv/db/cloud/watchers/rds.go +++ b/lib/srv/db/cloud/watchers/rds.go @@ -106,6 +106,21 @@ func (f *rdsDBInstancesFetcher) getRDSDatabases(ctx context.Context) (types.Data } databases := make(types.Databases, 0, len(instances)) for _, instance := range instances { + if !services.IsRDSInstanceSupported(instance) { + f.log.Debugf("RDS instance %q (engine mode %v, engine version %v) doesn't support IAM authentication. Skipping.", + aws.StringValue(instance.DBInstanceIdentifier), + aws.StringValue(instance.Engine), + aws.StringValue(instance.EngineVersion)) + continue + } + + if !services.IsRDSInstanceAvailable(instance) { + f.log.Debugf("The current status of RDS instance %q is %q. Skipping.", + aws.StringValue(instance.DBInstanceIdentifier), + aws.StringValue(instance.DBInstanceStatus)) + continue + } + database, err := services.NewDatabaseFromRDSInstance(instance) if err != nil { f.log.Warnf("Could not convert RDS instance %q to database resource: %v.", @@ -195,6 +210,13 @@ func (f *rdsAuroraClustersFetcher) getAuroraDatabases(ctx context.Context) (type continue } + if !services.IsRDSClusterAvailable(cluster) { + f.log.Debugf("The current status of Aurora cluster %q is %q. Skipping.", + aws.StringValue(cluster.DBClusterIdentifier), + aws.StringValue(cluster.Status)) + continue + } + // Add a database from primary endpoint database, err := services.NewDatabaseFromRDSCluster(cluster) if err != nil { @@ -261,7 +283,8 @@ func rdsFilters() []*rds.Filter { Name: aws.String("engine"), Values: aws.StringSlice([]string{ services.RDSEnginePostgres, - services.RDSEngineMySQL}), + services.RDSEngineMySQL, + services.RDSEngineMariaDB}), }} } diff --git a/lib/srv/db/cloud/watchers/redshift.go b/lib/srv/db/cloud/watchers/redshift.go index 2a8f8a98ba7db..33baed413046d 100644 --- a/lib/srv/db/cloud/watchers/redshift.go +++ b/lib/srv/db/cloud/watchers/redshift.go @@ -86,6 +86,13 @@ func (f *redshiftFetcher) Get(ctx context.Context) (types.Databases, error) { var databases types.Databases for _, cluster := range clusters { + if !services.IsRedshiftClusterAvailable(cluster) { + f.log.Debugf("The current status of Redshift cluster %q is %q. Skipping.", + aws.StringValue(cluster.ClusterIdentifier), + aws.StringValue(cluster.ClusterStatus)) + continue + } + database, err := services.NewDatabaseFromRedshiftCluster(cluster) if err != nil { f.log.Infof("Could not convert Redshift cluster %q to database resource: %v.", diff --git a/lib/srv/db/cloud/watchers/watcher_test.go b/lib/srv/db/cloud/watchers/watcher_test.go index f8ab8f50e662d..bcb3ddb8fa7e7 100644 --- a/lib/srv/db/cloud/watchers/watcher_test.go +++ b/lib/srv/db/cloud/watchers/watcher_test.go @@ -44,14 +44,20 @@ func TestWatcher(t *testing.T) { rdsInstance2, _ := makeRDSInstance(t, "instance-2", "us-east-2", map[string]string{"env": "prod"}) rdsInstance3, _ := makeRDSInstance(t, "instance-3", "us-east-1", map[string]string{"env": "dev"}) rdsInstance4, rdsDatabase4 := makeRDSInstance(t, "instance-4", "us-west-1", nil) + rdsInstanceUnavailable, _ := makeRDSInstance(t, "instance-5", "us-west-1", nil, withRDSInstanceStatus("stopped")) + rdsInstanceUnknownStatus, rdsDatabaseUnknownStatus := makeRDSInstance(t, "instance-5", "us-west-6", nil, withRDSInstanceStatus("status-does-not-exist")) - auroraCluster1, auroraDatabase1 := makeRDSCluster(t, "cluster-1", "us-east-1", services.RDSEngineModeProvisioned, map[string]string{"env": "prod"}) + auroraCluster1, auroraDatabase1 := makeRDSCluster(t, "cluster-1", "us-east-1", map[string]string{"env": "prod"}) auroraCluster2, auroraDatabases2 := makeRDSClusterWithExtraEndpoints(t, "cluster-2", "us-east-2", map[string]string{"env": "dev"}) - auroraCluster3, _ := makeRDSCluster(t, "cluster-3", "us-east-2", services.RDSEngineModeProvisioned, map[string]string{"env": "prod"}) - auroraClusterUnsupported, _ := makeRDSCluster(t, "serverless", "us-east-1", services.RDSEngineModeServerless, map[string]string{"env": "prod"}) + auroraCluster3, _ := makeRDSCluster(t, "cluster-3", "us-east-2", map[string]string{"env": "prod"}) + auroraClusterUnsupported, _ := makeRDSCluster(t, "serverless", "us-east-1", nil, withRDSClusterEngineMode("serverless")) + auroraClusterUnavailable, _ := makeRDSCluster(t, "cluster-4", "us-east-1", nil, withRDSClusterStatus("creating")) + auroraClusterUnknownStatus, auroraDatabaseUnknownStatus := makeRDSCluster(t, "cluster-5", "us-east-1", nil, withRDSClusterStatus("status-does-not-exist")) redshiftUse1Prod, redshiftDatabaseUse1Prod := makeRedshiftCluster(t, "us-east-1", "prod") redshiftUse1Dev, _ := makeRedshiftCluster(t, "us-east-1", "dev") + redshiftUse1Unavailable, _ := makeRedshiftCluster(t, "us-east-1", "qa", withRedshiftStatus("paused")) + redshiftUse1UnknownStatus, redshiftDatabaseUnknownStatus := makeRedshiftCluster(t, "us-east-1", "test", withRedshiftStatus("status-does-not-exist")) tests := []struct { name string @@ -60,7 +66,7 @@ func TestWatcher(t *testing.T) { expectedDatabases types.Databases }{ { - name: "rds labels matching", + name: "RDS labels matching", awsMatchers: []services.AWSMatcher{ { Types: []string{services.AWSMatcherRDS}, @@ -88,7 +94,7 @@ func TestWatcher(t *testing.T) { expectedDatabases: append(types.Databases{rdsDatabase1, auroraDatabase1}, auroraDatabases2...), }, { - name: "rds aurora unsupported", + name: "RDS unsupported databases are skipped", awsMatchers: []services.AWSMatcher{{ Types: []string{services.AWSMatcherRDS}, Regions: []string{"us-east-1"}, @@ -103,6 +109,21 @@ func TestWatcher(t *testing.T) { }, expectedDatabases: types.Databases{auroraDatabase1}, }, + { + name: "RDS unavailable databases are skipped", + awsMatchers: []services.AWSMatcher{{ + Types: []string{services.AWSMatcherRDS}, + Regions: []string{"us-east-1"}, + Tags: types.Labels{"*": []string{"*"}}, + }}, + clients: &common.TestCloudClients{ + RDS: &cloud.RDSMock{ + DBInstances: []*rds.DBInstance{rdsInstance1, rdsInstanceUnavailable, rdsInstanceUnknownStatus}, + DBClusters: []*rds.DBCluster{auroraCluster1, auroraClusterUnavailable, auroraClusterUnknownStatus}, + }, + }, + expectedDatabases: types.Databases{rdsDatabase1, rdsDatabaseUnknownStatus, auroraDatabase1, auroraDatabaseUnknownStatus}, + }, { name: "skip access denied errors", awsMatchers: []services.AWSMatcher{{ @@ -126,7 +147,7 @@ func TestWatcher(t *testing.T) { expectedDatabases: types.Databases{rdsDatabase4, auroraDatabase1}, }, { - name: "redshift", + name: "Redshift labels matching", awsMatchers: []services.AWSMatcher{ { Types: []string{services.AWSMatcherRedshift}, @@ -141,6 +162,22 @@ func TestWatcher(t *testing.T) { }, expectedDatabases: types.Databases{redshiftDatabaseUse1Prod}, }, + { + name: "Redshift unavailable databases are skipped", + awsMatchers: []services.AWSMatcher{ + { + Types: []string{services.AWSMatcherRedshift}, + Regions: []string{"us-east-1"}, + Tags: types.Labels{"*": []string{"*"}}, + }, + }, + clients: &common.TestCloudClients{ + Redshift: &cloud.RedshiftMock{ + Clusters: []*redshift.Cluster{redshiftUse1Prod, redshiftUse1Unavailable, redshiftUse1UnknownStatus}, + }, + }, + expectedDatabases: types.Databases{redshiftDatabaseUse1Prod, redshiftDatabaseUnknownStatus}, + }, { name: "matcher with multiple types", awsMatchers: []services.AWSMatcher{ @@ -178,43 +215,54 @@ func TestWatcher(t *testing.T) { } } -func makeRDSInstance(t *testing.T, name, region string, labels map[string]string) (*rds.DBInstance, types.Database) { +func makeRDSInstance(t *testing.T, name, region string, labels map[string]string, opts ...func(*rds.DBInstance)) (*rds.DBInstance, types.Database) { instance := &rds.DBInstance{ DBInstanceArn: aws.String(fmt.Sprintf("arn:aws:rds:%v:1234567890:db:%v", region, name)), DBInstanceIdentifier: aws.String(name), DbiResourceId: aws.String(uuid.New().String()), Engine: aws.String(services.RDSEnginePostgres), + DBInstanceStatus: aws.String("available"), Endpoint: &rds.Endpoint{ Address: aws.String("localhost"), Port: aws.Int64(5432), }, TagList: labelsToTags(labels), } + for _, opt := range opts { + opt(instance) + } + database, err := services.NewDatabaseFromRDSInstance(instance) require.NoError(t, err) return instance, database } -func makeRDSCluster(t *testing.T, name, region, engineMode string, labels map[string]string) (*rds.DBCluster, types.Database) { +func makeRDSCluster(t *testing.T, name, region string, labels map[string]string, opts ...func(*rds.DBCluster)) (*rds.DBCluster, types.Database) { cluster := &rds.DBCluster{ DBClusterArn: aws.String(fmt.Sprintf("arn:aws:rds:%v:1234567890:cluster:%v", region, name)), DBClusterIdentifier: aws.String(name), DbClusterResourceId: aws.String(uuid.New().String()), Engine: aws.String(services.RDSEngineAuroraMySQL), - EngineMode: aws.String(engineMode), + EngineMode: aws.String(services.RDSEngineModeProvisioned), + Status: aws.String("available"), Endpoint: aws.String("localhost"), Port: aws.Int64(3306), TagList: labelsToTags(labels), } + for _, opt := range opts { + opt(cluster) + } + database, err := services.NewDatabaseFromRDSCluster(cluster) require.NoError(t, err) return cluster, database } -func makeRedshiftCluster(t *testing.T, region, env string) (*redshift.Cluster, types.Database) { +func makeRedshiftCluster(t *testing.T, region, env string, opts ...func(*redshift.Cluster)) (*redshift.Cluster, types.Database) { cluster := &redshift.Cluster{ ClusterIdentifier: aws.String(env), ClusterNamespaceArn: aws.String(fmt.Sprintf("arn:aws:redshift:%s:1234567890:namespace:%s", region, env)), + ClusterStatus: aws.String("available"), Endpoint: &redshift.Endpoint{ Address: aws.String("localhost"), Port: aws.Int64(5439), @@ -224,6 +272,10 @@ func makeRedshiftCluster(t *testing.T, region, env string) (*redshift.Cluster, t Value: aws.String(env), }}, } + for _, opt := range opts { + opt(cluster) + } + database, err := services.NewDatabaseFromRedshiftCluster(cluster) require.NoError(t, err) return cluster, database @@ -236,6 +288,7 @@ func makeRDSClusterWithExtraEndpoints(t *testing.T, name, region string, labels DbClusterResourceId: aws.String(uuid.New().String()), Engine: aws.String(services.RDSEngineAuroraMySQL), EngineMode: aws.String(services.RDSEngineModeProvisioned), + Status: aws.String("available"), Endpoint: aws.String("localhost"), ReaderEndpoint: aws.String("reader.host"), Port: aws.Int64(3306), @@ -259,6 +312,34 @@ func makeRDSClusterWithExtraEndpoints(t *testing.T, name, region string, labels return cluster, append(types.Databases{primaryDatabase, readerDatabase}, customDatabases...) } +// withRDSInstanceStatus returns an option function for makeRDSInstance to overwrite status. +func withRDSInstanceStatus(status string) func(*rds.DBInstance) { + return func(instance *rds.DBInstance) { + instance.DBInstanceStatus = aws.String(status) + } +} + +// withRDSClusterEngineMode returns an option function for makeRDSCluster to overwrite engine mode. +func withRDSClusterEngineMode(mode string) func(*rds.DBCluster) { + return func(cluster *rds.DBCluster) { + cluster.EngineMode = aws.String(mode) + } +} + +// withRDSClusterStatus returns an option function for makeRDSCluster to overwrite status. +func withRDSClusterStatus(status string) func(*rds.DBCluster) { + return func(cluster *rds.DBCluster) { + cluster.Status = aws.String(status) + } +} + +// withRedshiftStatus returns an option function for makeRedshiftCluster to overwrite status. +func withRedshiftStatus(status string) func(*redshift.Cluster) { + return func(cluster *redshift.Cluster) { + cluster.ClusterStatus = aws.String(status) + } +} + func labelsToTags(labels map[string]string) (tags []*rds.Tag) { for key, val := range labels { tags = append(tags, &rds.Tag{ diff --git a/lib/srv/db/common/errors.go b/lib/srv/db/common/errors.go index 3ba4483086be4..298ce5f3187b0 100644 --- a/lib/srv/db/common/errors.go +++ b/lib/srv/db/common/errors.go @@ -18,8 +18,10 @@ package common import ( "net/http" + "strings" awslib "github.com/gravitational/teleport/lib/cloud/aws" + "github.com/gravitational/teleport/lib/defaults" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/jackc/pgconn" @@ -102,3 +104,57 @@ type causer interface { type pgError interface { Unwrap() error } + +// ConvertConnectError converts common connection errors to trace errors with +// extra information/recommendations if necessary. +func ConvertConnectError(err error, sessionCtx *Session) error { + errString := err.Error() + switch { + case strings.Contains(errString, "x509: certificate signed by unknown authority"): + return trace.AccessDenied("Database service cannot validate database's certificate: %v. Please verify if the correct CA bundle is used in the database config.", err) + + case strings.Contains(errString, "tls: unknown certificate authority"): + return trace.AccessDenied("Database cannot validate client certificate generated by database service: %v.", err) + } + + err = ConvertError(err) + + if trace.IsAccessDenied(err) && sessionCtx.Database.IsRDS() { + return createRDSAccessDeniedError(err, sessionCtx) + } + + return trace.Wrap(err) +} + +// createRDSAccessDeniedError creates an error with help message to setup IAM +// auth for RDS. +func createRDSAccessDeniedError(err error, sessionCtx *Session) error { + switch sessionCtx.Database.GetProtocol() { + case defaults.ProtocolMySQL: + return trace.AccessDenied(`Could not connect to database: + + %v + +Make sure that IAM auth is enabled for MySQL user %q and Teleport database +agent's IAM policy has "rds-connect" permissions (note that IAM changes may +take a few minutes to propagate): + +%v +`, err, sessionCtx.DatabaseUser, sessionCtx.Database.GetIAMPolicy()) + + case defaults.ProtocolPostgres: + return trace.AccessDenied(`Could not connect to database: + + %v + +Make sure that Postgres user %q has "rds_iam" role and Teleport database +agent's IAM policy has "rds-connect" permissions (note that IAM changes may +take a few minutes to propagate): + +%v +`, err, sessionCtx.DatabaseUser, sessionCtx.Database.GetIAMPolicy()) + + default: + return trace.Wrap(err) + } +} diff --git a/lib/srv/db/common/test.go b/lib/srv/db/common/test.go index 5a992e7749d3e..26568f9cb83d9 100644 --- a/lib/srv/db/common/test.go +++ b/lib/srv/db/common/test.go @@ -145,7 +145,7 @@ func MakeTestClientTLSConfig(config TestClientConfig) (*tls.Config, error) { if err != nil { return nil, trace.Wrap(err) } - ca, err := config.AuthClient.GetCertAuthority(types.CertAuthID{ + ca, err := config.AuthClient.GetCertAuthority(context.Background(), types.CertAuthID{ Type: types.HostCA, DomainName: config.Cluster, }, false) diff --git a/lib/srv/db/ha_test.go b/lib/srv/db/ha_test.go index a6ba7cdbda11a..4ee485e2b5960 100644 --- a/lib/srv/db/ha_test.go +++ b/lib/srv/db/ha_test.go @@ -76,7 +76,7 @@ func TestHA(t *testing.T) { HostID: onlineHostID, }) go func() { - for conn := range testCtx.proxyConn { + for conn := range testCtx.fakeRemoteSite.ProxyConn() { go onlineServer.HandleConnection(conn) } }() diff --git a/lib/srv/db/mongodb/connect.go b/lib/srv/db/mongodb/connect.go index 05dfc3d0609d4..b4e6f6eb8802d 100644 --- a/lib/srv/db/mongodb/connect.go +++ b/lib/srv/db/mongodb/connect.go @@ -41,10 +41,10 @@ import ( // When connecting to a replica set, returns connection to the server selected // based on the read preference connection string option. This allows users to // configure database access to always connect to a secondary for example. -func (e *Engine) connect(ctx context.Context, sessionCtx *common.Session) (driver.Connection, error) { +func (e *Engine) connect(ctx context.Context, sessionCtx *common.Session) (driver.Connection, func(), error) { options, selector, err := e.getTopologyOptions(ctx, sessionCtx) if err != nil { - return nil, trace.Wrap(err) + return nil, nil, trace.Wrap(err) } // Using driver's "topology" package allows to retain low-level control // over server connections (reading/writing wire messages) but at the @@ -52,22 +52,31 @@ func (e *Engine) connect(ctx context.Context, sessionCtx *common.Session) (drive // in a replica set. top, err := topology.New(options...) if err != nil { - return nil, trace.Wrap(err) + return nil, nil, trace.Wrap(err) } err = top.Connect() if err != nil { - return nil, trace.Wrap(err) + return nil, nil, trace.Wrap(err) } server, err := top.SelectServer(ctx, selector) if err != nil { - return nil, trace.Wrap(err) + return nil, nil, trace.Wrap(err) } e.Log.Debugf("Cluster topology: %v, selected server %v.", top, server) conn, err := server.Connection(ctx) if err != nil { - return nil, trace.Wrap(err) + return nil, nil, trace.Wrap(err) + } + + closeFn := func() { + if err := top.Disconnect(ctx); err != nil { + e.Log.WithError(err).Warn("Failed to close topology") + } + if err := conn.Close(); err != nil { + e.Log.WithError(err).Error("Failed to close server connection.") + } } - return conn, nil + return conn, closeFn, nil } // getTopologyOptions constructs topology options for connecting to a MongoDB server. diff --git a/lib/srv/db/mongodb/engine.go b/lib/srv/db/mongodb/engine.go index 5886cc5fb22c2..cfeeebde7cf59 100644 --- a/lib/srv/db/mongodb/engine.go +++ b/lib/srv/db/mongodb/engine.go @@ -80,16 +80,11 @@ func (e *Engine) HandleConnection(ctx context.Context, sessionCtx *common.Sessio return trace.Wrap(err, "error authorizing database access") } // Establish connection to the MongoDB server. - serverConn, err := e.connect(ctx, sessionCtx) + serverConn, closeFn, err := e.connect(ctx, sessionCtx) if err != nil { return trace.Wrap(err, "error connecting to the database") } - defer func() { - err := serverConn.Close() - if err != nil { - e.Log.WithError(err).Error("Failed to close server connection.") - } - }() + defer closeFn() e.Audit.OnSessionStart(e.Context, sessionCtx, nil) defer e.Audit.OnSessionEnd(e.Context, sessionCtx) // Start reading client messages and sending them to server. diff --git a/lib/srv/db/mongodb/test.go b/lib/srv/db/mongodb/test.go index 1d800d736a518..dc99e26083447 100644 --- a/lib/srv/db/mongodb/test.go +++ b/lib/srv/db/mongodb/test.go @@ -20,6 +20,7 @@ import ( "context" "crypto/tls" "net" + "sync/atomic" "time" "github.com/gravitational/teleport/lib/defaults" @@ -72,7 +73,8 @@ type TestServer struct { port string log logrus.FieldLogger - wireVersion int + wireVersion int + activeConnection int32 } // TestServerOption allows to set test server options. @@ -137,6 +139,8 @@ func (s *TestServer) Serve() error { go func() { defer s.log.Debug("Connection done.") defer conn.Close() + atomic.AddInt32(&s.activeConnection, 1) + defer atomic.AddInt32(&s.activeConnection, -1) if err := s.handleConnection(conn); err != nil { if !utils.IsOKNetworkError(err) { s.log.Errorf("Failed to handle connection: %v.", @@ -257,6 +261,11 @@ func (s *TestServer) Port() string { return s.port } +// GetActiveConnectionsCount returns the current value of activeConnection counter. +func (s *TestServer) GetActiveConnectionsCount() int32 { + return atomic.LoadInt32(&s.activeConnection) +} + // Close closes the server listener. func (s *TestServer) Close() error { return s.listener.Close() diff --git a/lib/srv/db/mysql/engine.go b/lib/srv/db/mysql/engine.go index 6b987b171b018..de84f4bbfdf85 100644 --- a/lib/srv/db/mysql/engine.go +++ b/lib/srv/db/mysql/engine.go @@ -242,19 +242,7 @@ func (e *Engine) connect(ctx context.Context, sessionCtx *common.Session) (*clie dialer, connectOpt) if err != nil { - if trace.IsAccessDenied(common.ConvertError(err)) && sessionCtx.Database.IsRDS() { - return nil, trace.AccessDenied(`Could not connect to database: - - %v - -Make sure that IAM auth is enabled for MySQL user %q and Teleport database -agent's IAM policy has "rds-connect" permissions (note that IAM changes may -take a few minutes to propagate): - -%v -`, common.ConvertError(err), sessionCtx.DatabaseUser, sessionCtx.Database.GetIAMPolicy()) - } - return nil, trace.Wrap(err) + return nil, common.ConvertConnectError(err, sessionCtx) } return conn, nil } diff --git a/lib/srv/db/mysql/test.go b/lib/srv/db/mysql/test.go index 18b4ae22db6c7..520bdc8dc31de 100644 --- a/lib/srv/db/mysql/test.go +++ b/lib/srv/db/mysql/test.go @@ -19,6 +19,7 @@ package mysql import ( "crypto/tls" "net" + "sync" "sync/atomic" "github.com/gravitational/teleport/lib/defaults" @@ -62,6 +63,11 @@ type TestServer struct { tlsConfig *tls.Config log logrus.FieldLogger handler *testHandler + + // serverConnsMtx is a mutex that guards serverConns. + serverConnsMtx sync.Mutex + // serverConns holds all connections created by the server. + serverConns []*server.Conn } // NewTestServer returns a new instance of a test MySQL server. @@ -152,6 +158,11 @@ func (s *TestServer) handleConnection(conn net.Conn) error { if err != nil { return trace.Wrap(err) } + + s.serverConnsMtx.Lock() + s.serverConns = append(s.serverConns, serverConn) + s.serverConnsMtx.Unlock() + for { if serverConn.Closed() { return nil @@ -196,6 +207,20 @@ func (s *TestServer) Close() error { return s.listener.Close() } +// ConnsClosed returns true if all connections has been correctly closed (message COM_QUIT), false otherwise. +func (s *TestServer) ConnsClosed() bool { + s.serverConnsMtx.Lock() + defer s.serverConnsMtx.Unlock() + + for _, conn := range s.serverConns { + if !conn.Closed() { + return false + } + } + + return true +} + type testHandler struct { server.EmptyHandler log logrus.FieldLogger diff --git a/lib/srv/db/postgres/engine.go b/lib/srv/db/postgres/engine.go index efd2614724d60..44d63f644ea0c 100644 --- a/lib/srv/db/postgres/engine.go +++ b/lib/srv/db/postgres/engine.go @@ -227,19 +227,7 @@ func (e *Engine) connect(ctx context.Context, sessionCtx *common.Session) (*pgpr // messages b/w server and client e.g. to get client's password. conn, err := pgconn.ConnectConfig(ctx, connectConfig) if err != nil { - if trace.IsAccessDenied(common.ConvertError(err)) && sessionCtx.Database.IsRDS() { - return nil, nil, trace.AccessDenied(`Could not connect to database: - - %v - -Make sure that Postgres user %q has "rds_iam" role and Teleport database -agent's IAM policy has "rds-connect" permissions (note that IAM changes may -take a few minutes to propagate): - -%v -`, common.ConvertError(err), sessionCtx.DatabaseUser, sessionCtx.Database.GetIAMPolicy()) - } - return nil, nil, trace.Wrap(err) + return nil, nil, common.ConvertConnectError(err, sessionCtx) } // Hijacked connection exposes some internal connection data, such as // parameters we'll need to relay back to the client (e.g. database diff --git a/lib/srv/db/proxy_test.go b/lib/srv/db/proxy_test.go index c8baf6d720c60..277fe77009850 100644 --- a/lib/srv/db/proxy_test.go +++ b/lib/srv/db/proxy_test.go @@ -197,7 +197,9 @@ func TestProxyClientDisconnectDueToLockInForce(t *testing.T) { Target: types.LockTarget{User: "alice"}, }) require.NoError(t, err) - testCtx.authServer.UpsertLock(ctx, lock) + + err = testCtx.authServer.UpsertLock(ctx, lock) + require.NoError(t, err) waitForEvent(t, testCtx, events.ClientDisconnectCode) err = mysql.Ping() diff --git a/lib/srv/db/proxyserver.go b/lib/srv/db/proxyserver.go index e4c0ae7a366e8..3c277360bff00 100644 --- a/lib/srv/db/proxyserver.go +++ b/lib/srv/db/proxyserver.go @@ -24,6 +24,9 @@ import ( "io" "math/rand" "net" + "sort" + "strings" + "sync" "time" "github.com/gravitational/teleport" @@ -85,12 +88,51 @@ type ProxyServerConfig struct { Clock clockwork.Clock // ServerID is the ID of the audit log server. ServerID string - // Shuffle allows to override shuffle logic in tests. - Shuffle func([]types.DatabaseServer) []types.DatabaseServer // LockWatcher is a lock watcher. LockWatcher *services.LockWatcher } +// ShuffleFunc defines a function that shuffles a list of database servers. +type ShuffleFunc func([]types.DatabaseServer) []types.DatabaseServer + +// ShuffleRandom is a ShuffleFunc that randomizes the order of database servers. +// Used to provide load balancing behavior when proxying to multiple agents. +func ShuffleRandom(servers []types.DatabaseServer) []types.DatabaseServer { + rand.New(rand.NewSource(time.Now().UnixNano())).Shuffle( + len(servers), func(i, j int) { + servers[i], servers[j] = servers[j], servers[i] + }) + return servers +} + +// ShuffleSort is a ShuffleFunc that sorts database servers by name and host ID. +// Used to provide predictable behavior in tests. +func ShuffleSort(servers []types.DatabaseServer) []types.DatabaseServer { + sort.Sort(types.DatabaseServers(servers)) + return servers +} + +var ( + // mu protects the shuffleFunc global access. + mu sync.RWMutex + // shuffleFunc provides shuffle behavior for multiple database agents. + shuffleFunc ShuffleFunc = ShuffleRandom +) + +// SetShuffleFunc sets the shuffle behavior when proxying to multiple agents. +func SetShuffleFunc(fn ShuffleFunc) { + mu.Lock() + defer mu.Unlock() + shuffleFunc = fn +} + +// getShuffleFunc returns the configured function used to shuffle agents. +func getShuffleFunc() ShuffleFunc { + mu.RLock() + defer mu.RUnlock() + return shuffleFunc +} + // CheckAndSetDefaults validates the config and sets default values. func (c *ProxyServerConfig) CheckAndSetDefaults() error { if c.AccessPoint == nil { @@ -114,15 +156,6 @@ func (c *ProxyServerConfig) CheckAndSetDefaults() error { if c.ServerID == "" { return trace.BadParameter("missing ServerID") } - if c.Shuffle == nil { - c.Shuffle = func(servers []types.DatabaseServer) []types.DatabaseServer { - rand.New(rand.NewSource(c.Clock.Now().UnixNano())).Shuffle( - len(servers), func(i, j int) { - servers[i], servers[j] = servers[j], servers[i] - }) - return servers - } - } if c.LockWatcher == nil { return trace.BadParameter("missing LockWatcher") } @@ -177,7 +210,7 @@ func (s *ProxyServer) ServePostgres(listener net.Listener) error { go func() { defer clientConn.Close() err := s.PostgresProxy().HandleConnection(s.closeCtx, clientConn) - if err != nil { + if err != nil && !utils.IsOKNetworkError(err) { s.log.WithError(err).Warn("Failed to handle Postgres client connection.") } }() @@ -231,7 +264,9 @@ func (s *ProxyServer) serveGenericTLS(listener net.Listener, tlsConfig *tls.Conf defer clientConn.Close() tlsConn := tls.Server(clientConn, tlsConfig) if err := tlsConn.Handshake(); err != nil { - s.log.WithError(err).Errorf("%s TLS handshake failed.", dbName) + if !utils.IsOKNetworkError(err) { + s.log.WithError(err).Errorf("%s TLS handshake failed.", dbName) + } return } err := s.handleConnection(tlsConn) @@ -351,7 +386,7 @@ func (s *ProxyServer) Connect(ctx context.Context, proxyCtx *common.ProxyContext // There may be multiple database servers proxying the same database. If // we get a connection problem error trying to dial one of them, likely // the database server is down so try the next one. - for _, server := range s.cfg.Shuffle(proxyCtx.Servers) { + for _, server := range getShuffleFunc()(proxyCtx.Servers) { s.log.Debugf("Dialing to %v.", server) tlsConfig, err := s.getConfigForServer(ctx, proxyCtx.Identity, server) if err != nil { @@ -364,9 +399,9 @@ func (s *ProxyServer) Connect(ctx context.Context, proxyCtx *common.ProxyContext ConnType: types.DatabaseTunnel, }) if err != nil { - // Connection problem indicates reverse tunnel to this server is down. - if trace.IsConnectionProblem(err) { - s.log.WithError(err).Warnf("Failed to dial %v.", server) + // If an agent is down, we'll retry on the next one (if available). + if isReverseTunnelDownError(err) { + s.log.WithError(err).Warnf("Failed to dial database %v.", server) continue } return nil, trace.Wrap(err) @@ -380,6 +415,13 @@ func (s *ProxyServer) Connect(ctx context.Context, proxyCtx *common.ProxyContext return nil, trace.BadParameter("failed to connect to any of the database servers") } +// isReverseTunnelDownError returns true if the provided error indicates that +// the reverse tunnel connection is down e.g. because the agent is down. +func isReverseTunnelDownError(err error) bool { + return trace.IsConnectionProblem(err) || + strings.Contains(err.Error(), reversetunnel.NoDatabaseTunnel) +} + // Proxy starts proxying all traffic received from database client between // this proxy and Teleport database service over reverse tunnel. // diff --git a/lib/srv/db/proxyserver_test.go b/lib/srv/db/proxyserver_test.go index 33fe5a69bce4e..df6500b49641a 100644 --- a/lib/srv/db/proxyserver_test.go +++ b/lib/srv/db/proxyserver_test.go @@ -91,10 +91,10 @@ func TestProxyConnectionLimiting(t *testing.T) { t.Run("limit can be hit", func(t *testing.T) { for i := 0; i < connLimitNumber; i++ { // Try to connect to the database. - pgConn, err := tt.connect() + dbConn, err := tt.connect() require.NoError(t, err) - connsClosers = append(connsClosers, pgConn) + connsClosers = append(connsClosers, dbConn) } // This connection should go over the limit. @@ -114,9 +114,9 @@ func TestProxyConnectionLimiting(t *testing.T) { require.NoError(t, err) // Create a new connection. We do not expect an error here as we have just closed one. - pgConn, err := tt.connect() + dbConn, err := tt.connect() require.NoError(t, err) - connsClosers = append(connsClosers, pgConn) + connsClosers = append(connsClosers, dbConn) // Here the limit should be reached again. _, err = tt.connect() diff --git a/lib/srv/db/redis/client.go b/lib/srv/db/redis/client.go new file mode 100644 index 0000000000000..de0bf1f7fdbdc --- /dev/null +++ b/lib/srv/db/redis/client.go @@ -0,0 +1,293 @@ +/* + + Copyright 2022 Gravitational, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +*/ + +package redis + +import ( + "context" + "crypto/tls" + "net" + "strings" + "sync" + + "github.com/go-redis/redis/v8" + "github.com/gravitational/teleport/lib/srv/db/redis/protocol" + "github.com/gravitational/trace" +) + +// Commands with additional processing in Teleport when using cluster mode. +const ( + dbsizeCmd = "dbsize" + keysCmd = "keys" + mgetCmd = "mget" + flushallCmd = "flushall" + flushdbCmd = "flushdb" + scriptCmd = "script" +) + +// Overridden subcommands for Redis SCRIPT command. +const ( + scriptLoadSubcmd = "load" + scriptExistsSubcmd = "exists" + scriptFLushSubcmd = "flush" +) + +// List of unsupported commands in cluster mode. +const ( + aclCmd = "acl" + askingCmd = "asking" + clientCmd = "client" + clusterCmd = "cluster" + configCmd = "config" + debugCmd = "debug" + execCmd = "exec" + infoCmd = "info" + latencyCmd = "latency" + memoryCmd = "memory" + migrateCmd = "migrate" + moduleCmd = "module" + monitorCmd = "monitor" + multiCmd = "multi" + pfdebugCmd = "pfdebug" + pfselftestCmd = "pfselftest" + psyncCmd = "psync" + readonlyCmd = "readonly" + readwriteCmd = "readwrite" + replconfCmd = "replconf" + replicaofCmd = "replicaof" + roleCmd = "role" + scanCmd = "scan" + shutdownCmd = "shutdown" + slaveofCmd = "slaveof" + slowlogCmd = "slowlog" + syncCmd = "sync" + timeCmd = "time" + waitCmd = "wait" + watchCmd = "watch" +) + +// clusterClient is a wrapper around redis.ClusterClient +type clusterClient struct { + redis.ClusterClient +} + +// newClient creates a new Redis client based on given ConnectionMode. If connection mode is not supported +// an error is returned. +func newClient(ctx context.Context, connectionOptions *ConnectionOptions, tlsConfig *tls.Config, username, password string) (redis.UniversalClient, error) { + connectionAddr := net.JoinHostPort(connectionOptions.address, connectionOptions.port) + // TODO(jakub): Use system CA bundle if connecting to AWS. + // TODO(jakub): Investigate Redis Sentinel. + switch connectionOptions.mode { + case Standalone: + return redis.NewClient(&redis.Options{ + Addr: connectionAddr, + TLSConfig: tlsConfig, + Username: username, + Password: password, + }), nil + case Cluster: + client := &clusterClient{ + ClusterClient: *redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{connectionAddr}, + TLSConfig: tlsConfig, + Username: username, + Password: password, + }), + } + // Load cluster information. + client.ReloadState(ctx) + + return client, nil + default: + // We've checked that while validating the config, but checking again can help with regression. + return nil, trace.BadParameter("incorrect connection mode %s", connectionOptions.mode) + } +} + +// Process add supports for additional cluster commands. Our Redis implementation passes most commands to +// go-redis `Process()` function which doesn't handel all Cluster commands like for ex. DBSIZE, FLUSHDB, etc. +// This function provides additional processing for those commands enabling more Redis commands in Cluster mode. +// Commands are override by a simple rule: +// * If command work only on a single slot (one shard) without any modifications and returns a CROSSSLOT error if executed on +// multiple keys it's send the Redis client as it is, and it's the client responsibility to make sure keys are in a single slot. +// * If a command returns incorrect result in the Cluster mode (for ex. DBSIZE as it would return only size of one shard not whole cluster) +// then it's handled by Teleport or blocked if reasonable processing is not possible. +// * Otherwise a commands is sent to Redis without any modifications. +func (c *clusterClient) Process(ctx context.Context, inCmd redis.Cmder) error { + cmd, ok := inCmd.(*redis.Cmd) + if !ok { + return trace.BadParameter("failed to cast Redis command type: %T", cmd) + } + + switch cmdName := strings.ToLower(cmd.Name()); cmdName { + case multiCmd, execCmd, watchCmd, scanCmd, aclCmd, askingCmd, clientCmd, clusterCmd, configCmd, debugCmd, + infoCmd, latencyCmd, memoryCmd, migrateCmd, moduleCmd, monitorCmd, pfdebugCmd, pfselftestCmd, + psyncCmd, readonlyCmd, readwriteCmd, replconfCmd, replicaofCmd, roleCmd, shutdownCmd, slaveofCmd, + slowlogCmd, syncCmd, timeCmd, waitCmd: + // block commands that return incorrect results in Cluster mode + return protocol.ErrCmdNotSupported + case dbsizeCmd: + // use go-redis dbsize implementation. It returns size of all keys in the whole cluster instead of + // just currently connected instance. + res := c.DBSize(ctx) + cmd.SetVal(res.Val()) + cmd.SetErr(res.Err()) + + return nil + case keysCmd: + var resultsKeys []string + var mtx sync.Mutex + + if err := c.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error { + scanCmd := redis.NewStringSliceCmd(ctx, cmd.Args()...) + err := client.Process(ctx, scanCmd) + if err != nil { + return trace.Wrap(err) + } + + keys, err := scanCmd.Result() + if err != nil { + return trace.Wrap(err) + } + + mtx.Lock() + resultsKeys = append(resultsKeys, keys...) + mtx.Unlock() + + return nil + }); err != nil { + return trace.Wrap(err) + } + + cmd.SetVal(resultsKeys) + + return nil + case mgetCmd: + if len(cmd.Args()) == 1 { + return trace.BadParameter("wrong number of arguments for 'mget' command") + } + + var resultsKeys []interface{} + + keys := cmd.Args()[1:] + for _, key := range keys { + k, ok := key.(string) + if !ok { + return trace.BadParameter("wrong key type, expected string, got %T", key) + } + + result := c.Get(ctx, k) + if result.Err() == redis.Nil { + resultsKeys = append(resultsKeys, redis.Nil) + continue + } + + if result.Err() != nil { + cmd.SetErr(result.Err()) + return trace.Wrap(result.Err()) + } + + resultsKeys = append(resultsKeys, result.Val()) + } + + cmd.SetVal(resultsKeys) + + return nil + case flushallCmd, flushdbCmd: + var mtx sync.Mutex + + if err := c.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error { + singleCmd := redis.NewCmd(ctx, cmd.Args()...) + err := client.Process(ctx, singleCmd) + if err != nil { + return trace.Wrap(err) + } + + mtx.Lock() + defer mtx.Unlock() + + if cmd.Err() != nil { + // If other call have already set error do not erase it. + // It should be returned to the caller. + return nil + } + + cmd.SetVal(singleCmd.Val()) + cmd.SetErr(singleCmd.Err()) + + return nil + }); err != nil { + return trace.Wrap(err) + } + + return nil + case scriptCmd: + return c.handleScriptCmd(ctx, cmd) + default: + return c.ClusterClient.Process(ctx, cmd) + } +} + +// handleScriptCmd processes Redis SCRIPT command in Cluster mode. +func (c *clusterClient) handleScriptCmd(ctx context.Context, cmd *redis.Cmd) error { + cmdArgs := cmd.Args() + + if len(cmdArgs) < 2 { + return trace.BadParameter("wrong number of arguments for 'script' command") + } + + args := make([]string, len(cmdArgs)) + + for i := range cmdArgs { + var ok bool + args[i], ok = cmdArgs[i].(string) + if !ok { + return trace.BadParameter("wrong script subcommand type, expected string, got %T", cmdArgs[i]) + } + } + + switch cmdSubName := strings.ToLower(args[1]); cmdSubName { + case scriptExistsSubcmd: + res := c.ScriptExists(ctx, args[2:]...) + + cmd.SetVal(res.Val()) + cmd.SetErr(res.Err()) + + return nil + case scriptLoadSubcmd: + res := c.ScriptLoad(ctx, args[2]) + + cmd.SetVal(res.Val()) + cmd.SetErr(res.Err()) + + return nil + + case scriptFLushSubcmd: + // TODO(jakule): ASYNC is ignored. + res := c.ScriptFlush(ctx) + + cmd.SetVal(res.Val()) + cmd.SetErr(res.Err()) + + return nil + default: + // SCRIPT KILL and SCRIPT DEBUG + return protocol.ErrCmdNotSupported + } +} diff --git a/lib/srv/db/redis/cmds.go b/lib/srv/db/redis/cmds.go index 7d33879e3cb33..cec1a3442527a 100644 --- a/lib/srv/db/redis/cmds.go +++ b/lib/srv/db/redis/cmds.go @@ -29,16 +29,19 @@ import ( "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/srv/db/common" "github.com/gravitational/teleport/lib/srv/db/common/role" + "github.com/gravitational/teleport/lib/srv/db/redis/protocol" "github.com/gravitational/trace" ) -// List of all commands that Teleport handles in a special way. +// List of commands that Teleport handles in a special way by Redis standalone and cluster. const ( - helloCmd = "hello" - authCmd = "auth" - subscribeCmd = "subscribe" - psubscribeCmd = "psubscribe" + helloCmd = "hello" + authCmd = "auth" + subscribeCmd = "subscribe" + psubscribeCmd = "psubscribe" + // TODO(jakub): go-redis doesn't expose any API for this command. Investigate alternative options. punsubscribeCmd = "punsubscribe" + // go-redis doesn't support Redis 7+ commands yet. ssubscribeCmd = "ssubscribe" sunsubscribeCmd = "sunsubscribe" ) @@ -48,29 +51,23 @@ const ( // * Redis 7.0+ commands are rejected as at the moment of writing Redis 7.0 hasn't been released and go-redis doesn't support it. // * RESP3 commands are rejected as Teleport/go-redis currently doesn't support this version of protocol. // * Subscribe related commands created a new DB connection as they change Redis request-response model to Pub/Sub. -func (e *Engine) processCmd(ctx context.Context, redisClient redis.UniversalClient, cmd *redis.Cmd) error { +func (e *Engine) processCmd(ctx context.Context, cmd *redis.Cmd) error { switch strings.ToLower(cmd.Name()) { - case helloCmd: - return trace.NotImplemented("RESP3 is not supported") + case helloCmd, punsubscribeCmd, ssubscribeCmd, sunsubscribeCmd: + return protocol.ErrCmdNotSupported case authCmd: - return e.processAuth(ctx, redisClient, cmd) + return e.processAuth(ctx, cmd) case subscribeCmd: e.Audit.OnQuery(e.Context, e.sessionCtx, common.Query{Query: cmd.String()}) - return e.subscribeCmd(ctx, redisClient.Subscribe, cmd) + return e.subscribeCmd(ctx, e.redisClient.Subscribe, cmd) case psubscribeCmd: e.Audit.OnQuery(e.Context, e.sessionCtx, common.Query{Query: cmd.String()}) - return e.subscribeCmd(ctx, redisClient.PSubscribe, cmd) - case punsubscribeCmd: - // TODO(jakub): go-redis doesn't expose any API for this command. Investigate alternative options. - return trace.NotImplemented("PUNSUBSCRIBE is not supported by Teleport") - case ssubscribeCmd, sunsubscribeCmd: - // go-redis doesn't support Redis 7+ commands yet. - return trace.NotImplemented("Redis 7.0+ is not yet supported") + return e.subscribeCmd(ctx, e.redisClient.PSubscribe, cmd) default: e.Audit.OnQuery(e.Context, e.sessionCtx, common.Query{Query: cmd.String()}) // Here the command is sent to the DB. - return redisClient.Process(ctx, cmd) + return e.redisClient.Process(ctx, cmd) } } @@ -148,7 +145,7 @@ func (e *Engine) processPubSub(ctx context.Context, pubSub *redis.PubSub) error // processAuth runs RBAC check on Redis AUTH command if command contains username. Command containing only password // is passed to Redis. Commands with incorrect number of arguments are rejected and an error is returned. -func (e *Engine) processAuth(ctx context.Context, redisClient redis.UniversalClient, cmd *redis.Cmd) error { +func (e *Engine) processAuth(ctx context.Context, cmd *redis.Cmd) error { // AUTH command may contain only password or login and password. Depends on the version we need to make sure // that the user has permission to connect as the provided db user. // ref: https://redis.io/commands/auth @@ -180,13 +177,13 @@ func (e *Engine) processAuth(ctx context.Context, redisClient redis.UniversalCli return trace.Wrap(err) } - return redisClient.Process(ctx, cmd) + return e.redisClient.Process(ctx, cmd) case 3: // Redis 6 version that contains username and password. Check the username against our RBAC before sending to Redis. // ex. AUTH bob my-secret-password dbUser, ok := cmd.Args()[1].(string) if !ok { - return trace.BadParameter("username has a wrong type, expected string") + return trace.BadParameter("username has a wrong type, expected string got %T", cmd.Args()[1]) } e.Audit.OnQuery(e.Context, e.sessionCtx, common.Query{Query: fmt.Sprintf("AUTH %s ****", dbUser)}) @@ -207,7 +204,19 @@ func (e *Engine) processAuth(ctx context.Context, redisClient redis.UniversalCli return trace.Wrap(err) } - return redisClient.Process(ctx, cmd) + password, ok := cmd.Args()[2].(string) + if !ok { + return trace.BadParameter("password has a wrong type, expected string got %T", cmd.Args()[2]) + } + + // reconnect with new username and password. go-redis manages those internally and sends AUTH commands + // at the beginning of every new connection. + e.redisClient, err = e.reconnect(dbUser, password) + if err != nil { + return trace.Wrap(err) + } + + return e.redisClient.Process(ctx, cmd) default: // Redis returns "syntax error" if AUTH has more than 2 arguments. return trace.BadParameter("syntax error") diff --git a/lib/srv/db/redis/engine.go b/lib/srv/db/redis/engine.go index 1fb4b7cb3641d..37a8fc5383d6a 100644 --- a/lib/srv/db/redis/engine.go +++ b/lib/srv/db/redis/engine.go @@ -20,7 +20,6 @@ import ( "bytes" "context" "errors" - "fmt" "net" "github.com/go-redis/redis/v8" @@ -44,6 +43,10 @@ func newEngine(ec common.EngineConfig) common.Engine { } } +// redisClientFactoryFn is a prototype that takes Redis username and password and return a new +// Redis client. +type redisClientFactoryFn func(username, password string) (redis.UniversalClient, error) + // Engine implements common.Engine. type Engine struct { // EngineConfig is the common database engine configuration. @@ -54,6 +57,10 @@ type Engine struct { clientReader *redis.Reader // sessionCtx is current session context. sessionCtx *common.Session + // newClient returns a new client connection + newClient redisClientFactoryFn + // redisClient is a current connection to Redis server. + redisClient redis.UniversalClient } // InitializeConnection initializes the database connection. @@ -139,41 +146,20 @@ func (e *Engine) HandleConnection(ctx context.Context, sessionCtx *common.Sessio return trace.Wrap(err) } - tlsConfig, err := e.Auth.GetTLSConfig(ctx, sessionCtx) + // Initialize newClient factory function with current connection state. + e.newClient, err = e.getNewClientFn(ctx, sessionCtx) if err != nil { return trace.Wrap(err) } - connectionOptions, err := ParseRedisAddress(sessionCtx.Database.GetURI()) + // Create new client without username or password. Those will be added when we receive AUTH command. + e.redisClient, err = e.newClient("", "") if err != nil { - return trace.BadParameter("Redis connection string is incorrect %q: %v", sessionCtx.Database.GetURI(), err) - } - - var ( - redisConn redis.UniversalClient - connectionAddr = fmt.Sprintf("%s:%s", connectionOptions.address, connectionOptions.port) - ) - - // TODO(jakub): Use system CA bundle if connecting to AWS. - // TODO(jakub): Investigate Redis Sentinel. - switch connectionOptions.mode { - case Standalone: - redisConn = redis.NewClient(&redis.Options{ - Addr: connectionAddr, - TLSConfig: tlsConfig, - }) - case Cluster: - redisConn = redis.NewClusterClient(&redis.ClusterOptions{ - Addrs: []string{connectionAddr}, - TLSConfig: tlsConfig, - }) - default: - // We've checked that while validating the config, but checking again can help with regression. - return trace.BadParameter("incorrect connection mode %s", connectionOptions.mode) + return trace.Wrap(err) } defer func() { - if err := redisConn.Close(); err != nil { + if err := e.redisClient.Close(); err != nil { e.Log.Errorf("Failed to close Redis connection: %v.", err) } }() @@ -181,16 +167,54 @@ func (e *Engine) HandleConnection(ctx context.Context, sessionCtx *common.Sessio e.Audit.OnSessionStart(e.Context, sessionCtx, nil) defer e.Audit.OnSessionEnd(e.Context, sessionCtx) - if err := e.process(ctx, redisConn); err != nil { + if err := e.process(ctx); err != nil { return trace.Wrap(err) } return nil } +// getNewClientFn returns a partial Redis client factory function. +func (e *Engine) getNewClientFn(ctx context.Context, sessionCtx *common.Session) (redisClientFactoryFn, error) { + tlsConfig, err := e.Auth.GetTLSConfig(ctx, sessionCtx) + if err != nil { + return nil, trace.Wrap(err) + } + + connectionOptions, err := ParseRedisAddress(sessionCtx.Database.GetURI()) + if err != nil { + return nil, trace.BadParameter("Redis connection string is incorrect %q: %v", sessionCtx.Database.GetURI(), err) + } + + return func(username, password string) (redis.UniversalClient, error) { + redisClient, err := newClient(ctx, connectionOptions, tlsConfig, username, password) + if err != nil { + return nil, trace.Wrap(err) + } + + return redisClient, nil + }, nil +} + +// reconnect closes the current Redis server connection and creates a new one pre-authenticated +// with provided username and password. +func (e *Engine) reconnect(username, password string) (redis.UniversalClient, error) { + err := e.redisClient.Close() + if err != nil { + return nil, trace.Wrap(err, "failed to close Redis connection") + } + + e.redisClient, err = e.newClient(username, password) + if err != nil { + return nil, trace.Wrap(err) + } + + return e.redisClient, nil +} + // process is the main processing function for Redis. It reads commands from connected client and passes them to // a Redis instance. This function returns when a server closes a connection or in case of connection error. -func (e *Engine) process(ctx context.Context, redisClient redis.UniversalClient) error { +func (e *Engine) process(ctx context.Context) error { for { // Read commands from connected client. cmd, err := e.readClientCmd(ctx) @@ -199,7 +223,7 @@ func (e *Engine) process(ctx context.Context, redisClient redis.UniversalClient) } // send valid commands to Redis instance/cluster. - err = e.processCmd(ctx, redisClient, cmd) + err = e.processCmd(ctx, cmd) // go-redis returns some errors as err and some as cmd.Err(). // Function below maps errors that should be returned to the // client as value or return them as err if we should terminate diff --git a/lib/srv/db/redis/protocol/resp2.go b/lib/srv/db/redis/protocol/resp2.go index 4f48bdfd6c61d..897debde67c75 100644 --- a/lib/srv/db/redis/protocol/resp2.go +++ b/lib/srv/db/redis/protocol/resp2.go @@ -20,12 +20,16 @@ package protocol import ( + "reflect" "strconv" "github.com/go-redis/redis/v8" "github.com/gravitational/trace" ) +// ErrCmdNotSupported is returned when an unsupported Redis command is sent to Teleport proxy. +var ErrCmdNotSupported = trace.NotImplemented("command not supported") + // WriteCmd writes Redis commands passed as vals to Redis wire form. // Most types are covered by go-redis implemented WriteArg() function. Types override by this function are: // * Redis errors and Go error: go-redis returns a "human-readable" string instead of RESP compatible error message @@ -95,16 +99,20 @@ func WriteCmd(wr *redis.Writer, vals interface{}) error { if err := writeUinteger(wr, val); err != nil { return trace.Wrap(err) } - case []string: - if err := writeStringSlice(wr, val); err != nil { - return trace.Wrap(err) + case interface{}: + var err error + v := reflect.ValueOf(val) + + if v.Kind() == reflect.Ptr { + v = v.Elem() } - case []interface{}: - if err := writeSlice(wr, val); err != nil { - return trace.Wrap(err) + + if v.Kind() == reflect.Slice { + err = writeSlice(wr, val) + } else { + err = wr.WriteArg(val) } - case interface{}: - err := wr.WriteArg(val) + if err != nil { return trace.Wrap(err) } @@ -132,37 +140,25 @@ func writeError(wr *redis.Writer, prefix string, val error) error { return nil } -// writeSlice converts []interface{} to Redis wire form. -func writeSlice(wr *redis.Writer, vals []interface{}) error { - if err := wr.WriteByte(redis.ArrayReply); err != nil { - return trace.Wrap(err) - } - n := len(vals) - if err := wr.WriteLen(n); err != nil { - return trace.Wrap(err) - } +// writeSlice converts a slice to Redis wire form. +func writeSlice(wr *redis.Writer, vals interface{}) error { + v := reflect.ValueOf(vals) - for _, v0 := range vals { - if err := WriteCmd(wr, v0); err != nil { - return trace.Wrap(err) - } + if v.Kind() != reflect.Slice { + return trace.BadParameter("expected slice, passed %T", vals) } - return nil -} - -// writeStringSlice converts a string slice to Redis wire form. -func writeStringSlice(wr *redis.Writer, vals []string) error { if err := wr.WriteByte(redis.ArrayReply); err != nil { return trace.Wrap(err) } - n := len(vals) + + n := v.Len() if err := wr.WriteLen(n); err != nil { return trace.Wrap(err) } - for _, v0 := range vals { - if err := WriteCmd(wr, v0); err != nil { + for i := 0; i < n; i++ { + if err := WriteCmd(wr, v.Index(i).Interface()); err != nil { return trace.Wrap(err) } } diff --git a/lib/srv/db/redis/protocol/resp2_test.go b/lib/srv/db/redis/protocol/resp2_test.go new file mode 100644 index 0000000000000..fccb0518f9f0e --- /dev/null +++ b/lib/srv/db/redis/protocol/resp2_test.go @@ -0,0 +1,106 @@ +/* + + Copyright 2022 Gravitational, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +*/ + +package protocol + +import ( + "bytes" + "errors" + "testing" + + "github.com/go-redis/redis/v8" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" +) + +func TestWriteCmd(t *testing.T) { + tests := []struct { + name string + val interface{} + expected []byte + wantErr bool + }{ + { + name: "string", + val: "test val", + expected: []byte("$8\r\ntest val\r\n"), + }, + { + name: "int", + val: 1, + expected: []byte(":1\r\n"), + }, + { + name: "int32", + val: 19, + expected: []byte(":19\r\n"), + }, + { + name: "int64", + val: 64, + expected: []byte(":64\r\n"), + }, + { + name: "float", + val: 3.14, + expected: []byte("$4\r\n3.14\r\n"), + }, + { + name: "[]string", + val: []string{"test val1", "test val 2"}, + expected: []byte("*2\r\n$9\r\ntest val1\r\n$10\r\ntest val 2\r\n"), + }, + { + name: "[]bool", + val: []bool{true, false}, + expected: []byte("*2\r\n$1\r\n1\r\n$1\r\n0\r\n"), + }, + { + name: "error", + val: errors.New("something bad"), + expected: []byte("-ERR something bad\r\n"), + }, + { + name: "Teleport error", + val: trace.Errorf("something bad"), + expected: []byte("-ERR Teleport: something bad\r\n"), + }, + { + name: "Redis nil", + val: redis.Nil, + expected: []byte("$-1\r\n"), + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + wr := redis.NewWriter(buf) + + if err := WriteCmd(wr, tt.val); (err != nil) != tt.wantErr { + t.Errorf("WriteCmd() error = %v, wantErr %v", err, tt.wantErr) + } + + require.Equal(t, tt.expected, buf.Bytes()) + }) + } +} diff --git a/lib/srv/db/server.go b/lib/srv/db/server.go index 9519aa242111d..bd35f2375547b 100644 --- a/lib/srv/db/server.go +++ b/lib/srv/db/server.go @@ -245,10 +245,10 @@ func (m *monitoredDatabases) setCloud(databases types.Databases) { m.cloud = databases } -func (m *monitoredDatabases) get() types.ResourcesWithLabels { +func (m *monitoredDatabases) get() types.ResourcesWithLabelsMap { m.mu.Lock() defer m.mu.Unlock() - return append(append(m.static, m.resources...), m.cloud...).AsResources() + return append(append(m.static, m.resources...), m.cloud...).AsResources().ToMap() } // New returns a new database server. diff --git a/lib/srv/db/watcher.go b/lib/srv/db/watcher.go index af261ac028ba5..887e4b889faeb 100644 --- a/lib/srv/db/watcher.go +++ b/lib/srv/db/watcher.go @@ -134,11 +134,8 @@ func (s *Server) startCloudWatcher(ctx context.Context) error { } // getResources returns proxied databases as resources. -func (s *Server) getResources() (resources types.ResourcesWithLabels) { - for _, database := range s.getProxiedDatabases() { - resources = append(resources, database) - } - return resources +func (s *Server) getResources() types.ResourcesWithLabelsMap { + return s.getProxiedDatabases().AsResources().ToMap() } // onCreate is called by reconciler when a new database is created. diff --git a/lib/srv/desktop/audit.go b/lib/srv/desktop/audit.go index d1710c559ecd1..1989fbf7a3053 100644 --- a/lib/srv/desktop/audit.go +++ b/lib/srv/desktop/audit.go @@ -27,7 +27,7 @@ import ( "github.com/gravitational/trace" ) -func (s *WindowsService) onSessionStart(ctx context.Context, id *tlsca.Identity, startTime time.Time, windowsUser, sessionID string, desktop types.WindowsDesktop, err error) { +func (s *WindowsService) onSessionStart(ctx context.Context, emitter events.Emitter, id *tlsca.Identity, startTime time.Time, windowsUser, sessionID string, desktop types.WindowsDesktop, err error) { userMetadata := id.GetUserMetadata() userMetadata.Login = windowsUser @@ -52,6 +52,7 @@ func (s *WindowsService) onSessionStart(ctx context.Context, id *tlsca.Identity, Success: err == nil, }, WindowsDesktopService: s.cfg.Heartbeat.HostUUID, + DesktopName: desktop.GetName(), DesktopAddr: desktop.GetAddr(), Domain: desktop.GetDomain(), WindowsUser: windowsUser, @@ -62,10 +63,10 @@ func (s *WindowsService) onSessionStart(ctx context.Context, id *tlsca.Identity, event.Error = trace.Unwrap(err).Error() event.UserMessage = err.Error() } - s.emit(ctx, event) + s.emit(ctx, emitter, event) } -func (s *WindowsService) onSessionEnd(ctx context.Context, id *tlsca.Identity, startedAt time.Time, recorded bool, windowsUser, sessionID string, desktop types.WindowsDesktop) { +func (s *WindowsService) onSessionEnd(ctx context.Context, emitter events.Emitter, id *tlsca.Identity, startedAt time.Time, recorded bool, windowsUser, sessionID string, desktop types.WindowsDesktop) { userMetadata := id.GetUserMetadata() userMetadata.Login = windowsUser @@ -93,10 +94,10 @@ func (s *WindowsService) onSessionEnd(ctx context.Context, id *tlsca.Identity, s // There can only be 1 participant, desktop sessions are not join-able. Participants: []string{userMetadata.User}, } - s.emit(ctx, event) + s.emit(ctx, emitter, event) } -func (s *WindowsService) onClipboardSend(ctx context.Context, id *tlsca.Identity, sessionID string, desktopAddr string, length int32) { +func (s *WindowsService) onClipboardSend(ctx context.Context, emitter events.Emitter, id *tlsca.Identity, sessionID string, desktopAddr string, length int32) { event := &events.DesktopClipboardSend{ Metadata: events.Metadata{ Type: libevents.DesktopClipboardSendEvent, @@ -117,10 +118,10 @@ func (s *WindowsService) onClipboardSend(ctx context.Context, id *tlsca.Identity DesktopAddr: desktopAddr, Length: length, } - s.emit(ctx, event) + s.emit(ctx, emitter, event) } -func (s *WindowsService) onClipboardReceive(ctx context.Context, id *tlsca.Identity, sessionID string, desktopAddr string, length int32) { +func (s *WindowsService) onClipboardReceive(ctx context.Context, emitter events.Emitter, id *tlsca.Identity, sessionID string, desktopAddr string, length int32) { event := &events.DesktopClipboardReceive{ Metadata: events.Metadata{ Type: libevents.DesktopClipboardReceiveEvent, @@ -141,11 +142,11 @@ func (s *WindowsService) onClipboardReceive(ctx context.Context, id *tlsca.Ident DesktopAddr: desktopAddr, Length: length, } - s.emit(ctx, event) + s.emit(ctx, emitter, event) } -func (s *WindowsService) emit(ctx context.Context, event events.AuditEvent) { - if err := s.cfg.Emitter.EmitAuditEvent(ctx, event); err != nil { +func (s *WindowsService) emit(ctx context.Context, emitter events.Emitter, event events.AuditEvent) { + if err := emitter.EmitAuditEvent(ctx, event); err != nil { s.cfg.Log.WithError(err).Errorf("Failed to emit audit event %v", event) } } diff --git a/lib/srv/desktop/audit_test.go b/lib/srv/desktop/audit_test.go index 86a46bea7aefb..e308fb11ee61a 100644 --- a/lib/srv/desktop/audit_test.go +++ b/lib/srv/desktop/audit_test.go @@ -99,6 +99,7 @@ func TestSessionStartEvent(t *testing.T) { Success: true, }, WindowsDesktopService: s.cfg.Heartbeat.HostUUID, + DesktopName: "test-desktop", DesktopAddr: desktop.GetAddr(), Domain: desktop.GetDomain(), WindowsUser: "Administrator", @@ -131,6 +132,7 @@ func TestSessionStartEvent(t *testing.T) { t.Run(test.desc, func(t *testing.T) { s.onSessionStart( context.Background(), + s.cfg.Emitter, id, s.cfg.Clock.Now().UTC().Round(time.Millisecond), "Administrator", @@ -173,6 +175,7 @@ func TestSessionEndEvent(t *testing.T) { s.onSessionEnd( context.Background(), + s.cfg.Emitter, id, startTime, true, diff --git a/lib/srv/desktop/discovery.go b/lib/srv/desktop/discovery.go index 5a3f010183fc9..d89a3135a79c1 100644 --- a/lib/srv/desktop/discovery.go +++ b/lib/srv/desktop/discovery.go @@ -69,7 +69,7 @@ func (s *WindowsService) startDesktopDiscovery() error { // pre-filtered by nature of using an LDAP search with filters. Matcher: func(r types.ResourceWithLabels) bool { return true }, - GetCurrentResources: func() types.ResourcesWithLabels { return s.lastDiscoveryResults }, + GetCurrentResources: func() types.ResourcesWithLabelsMap { return s.lastDiscoveryResults }, GetNewResources: s.getDesktopsFromLDAP, OnCreate: s.upsertDesktop, OnUpdate: s.upsertDesktop, @@ -117,7 +117,7 @@ func (s *WindowsService) ldapSearchFilter() string { } // getDesktopsFromLDAP discovers Windows hosts via LDAP -func (s *WindowsService) getDesktopsFromLDAP() types.ResourcesWithLabels { +func (s *WindowsService) getDesktopsFromLDAP() types.ResourcesWithLabelsMap { if !s.ldapReady() { s.cfg.Log.Warn("skipping desktop discovery: LDAP not yet initialized") return nil @@ -144,14 +144,14 @@ func (s *WindowsService) getDesktopsFromLDAP() types.ResourcesWithLabels { s.cfg.Log.Debugf("discovered %d Windows Desktops", len(entries)) - var result types.ResourcesWithLabels + result := make(types.ResourcesWithLabelsMap) for _, entry := range entries { desktop, err := s.ldapEntryToWindowsDesktop(s.closeCtx, entry, s.cfg.HostLabelsFn) if err != nil { s.cfg.Log.Warnf("could not create Windows Desktop from LDAP entry: %v", err) continue } - result = append(result, desktop) + result[desktop.GetName()] = desktop } // capture the result, which will be used on the next reconcile loop @@ -177,14 +177,14 @@ func (s *WindowsService) deleteDesktop(ctx context.Context, r types.ResourceWith } func applyLabelsFromLDAP(entry *ldap.Entry, labels map[string]string) { - labels["teleport.dev/dns_host_name"] = entry.GetAttributeValue(attrDNSHostName) - labels["teleport.dev/computer_name"] = entry.GetAttributeValue(attrName) - labels["teleport.dev/os"] = entry.GetAttributeValue(attrOS) - labels["teleport.dev/os_version"] = entry.GetAttributeValue(attrOSVersion) + labels[types.TeleportNamespace+"/dns_host_name"] = entry.GetAttributeValue(attrDNSHostName) + labels[types.TeleportNamespace+"/computer_name"] = entry.GetAttributeValue(attrName) + labels[types.TeleportNamespace+"/os"] = entry.GetAttributeValue(attrOS) + labels[types.TeleportNamespace+"/os_version"] = entry.GetAttributeValue(attrOSVersion) labels[types.OriginLabel] = types.OriginDynamic switch entry.GetAttributeValue(attrPrimaryGroupID) { case writableDomainControllerGroupID, readOnlyDomainControllerGroupID: - labels["teleport.dev/is_domain_controller"] = "true" + labels[types.TeleportNamespace+"/is_domain_controller"] = "true" } } @@ -193,7 +193,7 @@ func applyLabelsFromLDAP(entry *ldap.Entry, labels map[string]string) { func (s *WindowsService) ldapEntryToWindowsDesktop(ctx context.Context, entry *ldap.Entry, getHostLabels func(string) map[string]string) (types.ResourceWithLabels, error) { hostname := entry.GetAttributeValue(attrDNSHostName) labels := getHostLabels(hostname) - labels["teleport.dev/windows_domain"] = s.cfg.Domain + labels[types.TeleportNamespace+"/windows_domain"] = s.cfg.Domain applyLabelsFromLDAP(entry, labels) addrs, err := s.dnsResolver.LookupHost(ctx, hostname) diff --git a/lib/srv/desktop/discovery_test.go b/lib/srv/desktop/discovery_test.go index 5635f8fc3190e..6ad26a602a197 100644 --- a/lib/srv/desktop/discovery_test.go +++ b/lib/srv/desktop/discovery_test.go @@ -71,10 +71,10 @@ func TestAppliesLDAPLabels(t *testing.T) { applyLabelsFromLDAP(entry, l) require.Equal(t, l[types.OriginLabel], types.OriginDynamic) - require.Equal(t, l["teleport.dev/dns_host_name"], "foo.example.com") - require.Equal(t, l["teleport.dev/computer_name"], "foo") - require.Equal(t, l["teleport.dev/os"], "Windows Server") - require.Equal(t, l["teleport.dev/os_version"], "6.1") + require.Equal(t, l[types.TeleportNamespace+"/dns_host_name"], "foo.example.com") + require.Equal(t, l[types.TeleportNamespace+"/computer_name"], "foo") + require.Equal(t, l[types.TeleportNamespace+"/os"], "Windows Server") + require.Equal(t, l[types.TeleportNamespace+"/os_version"], "6.1") } func TestLabelsDomainControllers(t *testing.T) { @@ -109,7 +109,7 @@ func TestLabelsDomainControllers(t *testing.T) { l := make(map[string]string) applyLabelsFromLDAP(test.entry, l) - b, _ := strconv.ParseBool(l["teleport.dev/is_domain_controller"]) + b, _ := strconv.ParseBool(l[types.TeleportNamespace+"/is_domain_controller"]) test.assert(t, b) }) } diff --git a/lib/srv/desktop/rdp/rdpclient/client.go b/lib/srv/desktop/rdp/rdpclient/client.go index a74d6695b78b8..d191c768feba4 100644 --- a/lib/srv/desktop/rdp/rdpclient/client.go +++ b/lib/srv/desktop/rdp/rdpclient/client.go @@ -362,13 +362,17 @@ func (c *Client) start() { return } case tdp.ClipboardData: - if err := cgoError(C.update_clipboard( - c.rustClient, - (*C.uint8_t)(unsafe.Pointer(&m[0])), - C.uint32_t(len(m)), - )); err != nil { - c.cfg.Log.Warningf("Failed forwarding RDP clipboard data: %v", err) - return + if len(m) > 0 { + if err := cgoError(C.update_clipboard( + c.rustClient, + (*C.uint8_t)(unsafe.Pointer(&m[0])), + C.uint32_t(len(m)), + )); err != nil { + c.cfg.Log.Warningf("Failed forwarding RDP clipboard data: %v", err) + return + } + } else { + c.cfg.Log.Warning("Recieved an empty clipboard message") } default: c.cfg.Log.Warningf("Skipping unimplemented TDP message type %T", msg) @@ -378,17 +382,23 @@ func (c *Client) start() { } //export handle_bitmap -func handle_bitmap(handle C.uintptr_t, cb C.CGOBitmap) C.CGOError { +func handle_bitmap(handle C.uintptr_t, cb *C.CGOBitmap) C.CGOError { return cgo.Handle(handle).Value().(*Client).handleBitmap(cb) } -func (c *Client) handleBitmap(cb C.CGOBitmap) C.CGOError { +func (c *Client) handleBitmap(cb *C.CGOBitmap) C.CGOError { // Notify the input forwarding goroutine that we're ready for input. // Input can only be sent after connection was established, which we infer // from the fact that a bitmap was sent. atomic.StoreUint32(&c.readyForInput, 1) - data := C.GoBytes(unsafe.Pointer(cb.data_ptr), C.int(cb.data_len)) + // use unsafe.Slice here instead of C.GoBytes, because unsafe.Slice + // creates a Go slice backed by data managed from Rust - it does not + // copy. This way we only need one copy into img.Pix below. + ptr := unsafe.Pointer(cb.data_ptr) + uptr := (*uint8)(ptr) + data := unsafe.Slice(uptr, C.int(cb.data_len)) + // Convert BGRA to RGBA. It's likely due to Windows using uint32 values for // pixels (ARGB) and encoding them as big endian. The image.RGBA type uses // a byte slice with 4-byte segments representing pixels (RGBA). diff --git a/lib/srv/desktop/rdp/rdpclient/librdprs.h b/lib/srv/desktop/rdp/rdpclient/librdprs.h index f1a2876cdc078..e31462e98dc69 100644 --- a/lib/srv/desktop/rdp/rdpclient/librdprs.h +++ b/lib/srv/desktop/rdp/rdpclient/librdprs.h @@ -7,6 +7,19 @@ #define SPECIAL_NO_RESPONSE 4294967295 +/** + * The default maximum chunk size for virtual channel data. + * + * If an RDP server supports larger chunks, it will advertise + * the larger chunk size in the `VCChunkSize` field of the + * virtual channel capability set. + * + * See also: + * - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/6c074267-1b32-4ceb-9496-2eb941a23e6b + * - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/a8593178-80c0-4b80-876c-cb77e62cecfc + */ +#define CHANNEL_CHUNK_LEGNTH 1600 + typedef enum CGOPointerButton { PointerButtonNone, PointerButtonLeft, @@ -165,6 +178,6 @@ void free_rust_string(char *s); extern void free_go_string(char *s); -extern CGOError handle_bitmap(uintptr_t client_ref, struct CGOBitmap b); +extern CGOError handle_bitmap(uintptr_t client_ref, struct CGOBitmap *b); extern CGOError handle_remote_copy(uintptr_t client_ref, uint8_t *data, uint32_t len); diff --git a/lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs b/lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs index 48488c2d20f48..3d38a4ddeef77 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs @@ -13,24 +13,39 @@ // limitations under the License. use crate::errors::invalid_data_error; +use crate::vchan::ChannelPDUFlags; use crate::{vchan, Payload}; use bitflags::bitflags; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use num_traits::FromPrimitive; use rdp::core::{mcs, tpkt}; -use rdp::model::data::Message; use rdp::model::error::*; use rdp::try_let; use std::collections::HashMap; -use std::io::{Read, Write}; +use std::io::{Cursor, Read, Write}; pub const CHANNEL_NAME: &str = "cliprdr"; +struct PendingData { + data: Vec, + total_length: u32, + clipboard_header: Option, +} + +impl PendingData { + fn reset(&mut self, length: u32) { + self.data.clear(); + self.total_length = length; + self.clipboard_header = None; + } +} + /// Client implements a client for the clipboard virtual channel /// (CLIPRDR) extension, as defined in: /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeclip/fb9b7e0b-6db4-41c2-b83c-f889c1ee7688 pub struct Client { clipboard: HashMap>, + pending: PendingData, on_remote_copy: Box)>, } @@ -44,6 +59,11 @@ impl Client { pub fn new(on_remote_copy: Box)>) -> Self { Client { clipboard: HashMap::new(), + pending: PendingData { + data: Vec::new(), + total_length: 0, + clipboard_header: None, + }, on_remote_copy, } } @@ -54,28 +74,58 @@ impl Client { mcs: &mut mcs::Client, ) -> RdpResult<()> { let mut payload = try_let!(tpkt::Payload::Raw, payload)?; - let _pdu_header = vchan::ChannelPDUHeader::decode(&mut payload)?; - let header = ClipboardPDUHeader::decode(&mut payload)?; + let pdu_header = vchan::ChannelPDUHeader::decode(&mut payload)?; + + // TODO(zmb3): this logic is the same for all virtual channels, and should + // be moved to vchan.rs and reused for the rdpdr client as well + if pdu_header + .flags + .contains(ChannelPDUFlags::CHANNEL_FLAG_FIRST) + { + self.pending.reset(pdu_header.length); + self.pending.clipboard_header = Some(ClipboardPDUHeader::decode(&mut payload)?); + } + + payload.read_to_end(&mut self.pending.data)?; + + if pdu_header + .flags + .contains(ChannelPDUFlags::CHANNEL_FLAG_LAST) + && self.pending.clipboard_header.is_some() + { + let full_msg = self.pending.data.split_off(0); + let mut payload = Cursor::new(full_msg); + let header = self.pending.clipboard_header.take().unwrap(); + return self.handle_message(header, &mut payload, mcs); + } + + Ok(()) + } + + fn handle_message( + &mut self, + header: ClipboardPDUHeader, + payload: &mut Payload, + mcs: &mut mcs::Client, + ) -> RdpResult<()> { debug!("received {:?}", header.msg_type); - let resp = match header.msg_type { - ClipboardPDUType::CB_CLIP_CAPS => self.handle_server_caps(&mut payload)?, - ClipboardPDUType::CB_MONITOR_READY => self.handle_monitor_ready(&mut payload)?, + let responses = match header.msg_type { + ClipboardPDUType::CB_CLIP_CAPS => self.handle_server_caps(payload)?, + ClipboardPDUType::CB_MONITOR_READY => self.handle_monitor_ready(payload)?, ClipboardPDUType::CB_FORMAT_LIST => { - self.handle_format_list(&mut payload, header.data_len)? + self.handle_format_list(payload, header.data_len)? } ClipboardPDUType::CB_FORMAT_LIST_RESPONSE => { self.handle_format_list_response(header.msg_flags)? } - ClipboardPDUType::CB_FORMAT_DATA_REQUEST => { - self.handle_format_data_request(&mut payload)? - } + ClipboardPDUType::CB_FORMAT_DATA_REQUEST => self.handle_format_data_request(payload)?, ClipboardPDUType::CB_FORMAT_DATA_RESPONSE => { if header .msg_flags .contains(ClipboardHeaderFlags::CB_RESPONSE_OK) { - self.handle_format_data_response(&mut payload, header.data_len)? + self.handle_format_data_response(payload, header.data_len)? } else { warn!("RDP server failed to process format data request"); vec![] @@ -91,8 +141,8 @@ impl Client { }; let chan = &CHANNEL_NAME.to_string(); - for msg in resp { - mcs.write(chan, msg)?; + for resp in responses { + mcs.write(chan, resp)?; } Ok(()) @@ -101,7 +151,7 @@ impl Client { /// update_clipboard is invoked from Go. /// It updates the local clipboard cache and returns the encoded message /// that should be sent to the RDP server. - pub fn update_clipboard(&mut self, data: Vec) -> RdpResult> { + pub fn update_clipboard(&mut self, data: Vec) -> RdpResult>> { const CR: u8 = 13; const LF: u8 = 10; @@ -163,26 +213,23 @@ impl Client { // 1. Send our clipboard capabilities // 2. Mimic a "copy" operation by sending a format list PDU // This completes the initialization process. - - let result = vec![ - encode_message( - ClipboardPDUType::CB_CLIP_CAPS, - ClipboardCapabilitiesPDU { - general: Some(GeneralClipboardCapabilitySet { - version: CB_CAPS_VERSION_2, - flags: ClipboardGeneralCapabilityFlags::CB_USE_LONG_FORMAT_NAMES, - }), - } - .encode()?, - )?, - encode_message( - ClipboardPDUType::CB_FORMAT_LIST, - FormatListPDU:: { - format_names: vec![LongFormatName::id(0)], - } - .encode()?, - )?, - ]; + let mut result = encode_message( + ClipboardPDUType::CB_CLIP_CAPS, + ClipboardCapabilitiesPDU { + general: Some(GeneralClipboardCapabilitySet { + version: CB_CAPS_VERSION_2, + flags: ClipboardGeneralCapabilityFlags::CB_USE_LONG_FORMAT_NAMES, + }), + } + .encode()?, + )?; + result.extend(encode_message( + ClipboardPDUType::CB_FORMAT_LIST, + FormatListPDU:: { + format_names: vec![LongFormatName::id(0)], + } + .encode()?, + )?); Ok(result) } @@ -207,17 +254,14 @@ impl Client { // // see section 3.1.1.1 for details - let mut result = vec![encode_message( - ClipboardPDUType::CB_FORMAT_LIST_RESPONSE, - vec![], - )?]; + let mut result = encode_message(ClipboardPDUType::CB_FORMAT_LIST_RESPONSE, vec![])?; for name in list.format_names { match FromPrimitive::from_u32(name.format_id) { // TODO(zmb3): support CF_TEXT, CF_UNICODETEXT, ... Some(ClipboardFormat::CF_OEMTEXT) => { // request the data by imitating a paste event - result.push(encode_message( + result.extend(encode_message( ClipboardPDUType::CB_FORMAT_DATA_REQUEST, FormatDataRequestPDU::for_id(name.format_id).encode()?, )?); @@ -259,20 +303,20 @@ impl Client { } }; - Ok(vec![encode_message( + encode_message( ClipboardPDUType::CB_FORMAT_DATA_RESPONSE, FormatDataResponsePDU { data }.encode()?, - )?]) + ) } /// Receives clipboard data from the remote desktop. This is the server responding /// to our format data request. fn handle_format_data_response( - &self, + &mut self, payload: &mut Payload, length: u32, ) -> RdpResult>> { - let resp = FormatDataResponsePDU::decode(payload, length)?; + let mut resp = FormatDataResponsePDU::decode(payload, length)?; let data_len = resp.data.len(); debug!( "recieved {} bytes of copied data from Windows Desktop", @@ -281,12 +325,12 @@ impl Client { // trim the null-terminator, if it exists // (but don't worry about CRLF conversion, most non-Windows systems can handle CRLF well enough) - let mut data = resp.data; - if !data.is_empty() && data[data.len() - 1] == 0x00 { - data.truncate(data.len() - 1); + if let Some(0x00) = resp.data.last() { + resp.data.truncate(resp.data.len() - 1); } - (self.on_remote_copy)(data); + (self.on_remote_copy)(resp.data); + Ok(vec![]) } } @@ -486,7 +530,7 @@ impl FormatListPDU { fn encode(&self) -> RdpResult> { let mut w = Vec::new(); for name in &self.format_names { - w.extend_from_slice(&name.encode()?); + w.extend(name.encode()?); } Ok(w) @@ -712,8 +756,9 @@ impl FormatDataResponsePDU { } /// encode_message encodes a message by wrapping it in the appropriate -/// channel header -fn encode_message(msg_type: ClipboardPDUType, payload: Vec) -> RdpResult> { +/// channel header. If the payload exceeds the maximum size, the message +/// is split into multiple messages. +fn encode_message(msg_type: ClipboardPDUType, payload: Vec) -> RdpResult>> { let msg_flags = match msg_type { // the spec requires 0 for these messages ClipboardPDUType::CB_CLIP_CAPS => ClipboardHeaderFlags::from_bits_truncate(0), @@ -732,28 +777,51 @@ fn encode_message(msg_type: ClipboardPDUType, payload: Vec) -> RdpResult ClipboardHeaderFlags::from_bits_truncate(0), }; - let mut inner = ClipboardPDUHeader::new(msg_type, msg_flags, payload.len() as u32).encode()?; - inner.extend_from_slice(&payload); - - let mut channel_flags = vchan::ChannelPDUFlags::CHANNEL_FLAG_ONLY; - match msg_type { - ClipboardPDUType::CB_FORMAT_LIST - | ClipboardPDUType::CB_CLIP_CAPS - | ClipboardPDUType::CB_FORMAT_DATA_REQUEST - | ClipboardPDUType::CB_FORMAT_DATA_RESPONSE => { - channel_flags.set(vchan::ChannelPDUFlags::CHANNEL_FLAG_SHOW_PROTOCOL, true) + inner.extend(payload); + let total_len = inner.len() as u32; + + let mut result = Vec::new(); + let mut first = true; + while !inner.is_empty() { + let i = std::cmp::min(inner.len(), vchan::CHANNEL_CHUNK_LEGNTH); + let leftover = inner.split_off(i); + + let mut channel_flags = match msg_type { + ClipboardPDUType::CB_FORMAT_LIST + | ClipboardPDUType::CB_CLIP_CAPS + | ClipboardPDUType::CB_FORMAT_DATA_REQUEST + | ClipboardPDUType::CB_FORMAT_DATA_RESPONSE => { + vchan::ChannelPDUFlags::CHANNEL_FLAG_SHOW_PROTOCOL + } + _ => vchan::ChannelPDUFlags::from_bits_truncate(0), + }; + + if first { + channel_flags.set(vchan::ChannelPDUFlags::CHANNEL_FLAG_FIRST, true); + first = false; + } + if leftover.is_empty() { + channel_flags.set(vchan::ChannelPDUFlags::CHANNEL_FLAG_LAST, true); } - _ => {} + + // the Channel PDU Header always specifies the *total length* of the PDU, + // even if it has to be split into multpile chunks: + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/a542bf19-1c86-4c80-ab3e-61449653abf6 + let mut outer = vchan::ChannelPDUHeader::new(total_len, channel_flags).encode()?; + outer.extend(inner); + result.push(outer); + + inner = leftover; } - let mut outer = vchan::ChannelPDUHeader::new(inner.length() as u32, channel_flags).encode()?; - outer.extend_from_slice(&inner); - Ok(outer) + Ok(result) } #[cfg(test)] mod tests { + use crate::vchan::ChannelPDUFlags; + use super::*; use std::io::Cursor; use std::sync::mpsc::channel; @@ -771,7 +839,7 @@ mod tests { .unwrap(); assert_eq!( - msg, + msg[0], vec![ // virtual channel header 0x2C, 0x00, 0x00, 0x00, // length (44 bytes) @@ -800,7 +868,7 @@ mod tests { encode_message(ClipboardPDUType::CB_FORMAT_LIST, empty.encode().unwrap()).unwrap(); assert_eq!( - encoded, + encoded[0], vec![ 0x0e, 0x00, 0x00, 0x00, // message length (14 bytes) 0x13, 0x00, 0x00, 0x00, // flags (first + last + show protocol) @@ -947,6 +1015,32 @@ mod tests { assert_eq!(format_list.format_names[0].format_name, None); } + #[test] + fn encodes_large_format_data_response() { + let mut data = Vec::new(); + data.resize(vchan::CHANNEL_CHUNK_LEGNTH + 2, 0); + for (i, item) in data.iter_mut().enumerate() { + *item = (i % 256) as u8; + } + let pdu = FormatDataResponsePDU { data }; + let encoded = pdu.encode().unwrap(); + let messages = encode_message(ClipboardPDUType::CB_FORMAT_DATA_RESPONSE, encoded).unwrap(); + assert_eq!(2, messages.len()); + + let header0 = + vchan::ChannelPDUHeader::decode(&mut Cursor::new(messages[0].clone())).unwrap(); + assert_eq!( + ChannelPDUFlags::CHANNEL_FLAG_FIRST | ChannelPDUFlags::CHANNEL_FLAG_SHOW_PROTOCOL, + header0.flags + ); + let header1 = + vchan::ChannelPDUHeader::decode(&mut Cursor::new(messages[1].clone())).unwrap(); + assert_eq!( + ChannelPDUFlags::CHANNEL_FLAG_LAST | ChannelPDUFlags::CHANNEL_FLAG_SHOW_PROTOCOL, + header1.flags + ); + } + #[test] fn responds_to_format_data_request_hasdata() { // a null-terminated utf-16 string, represented as a Vec @@ -980,7 +1074,7 @@ mod tests { fn invokes_callback_with_clipboard_data() { let (send, recv) = channel(); - let c = Client::new(Box::new(move |vec| { + let mut c = Client::new(Box::new(move |vec| { send.send(vec).unwrap(); })); @@ -1003,9 +1097,10 @@ mod tests { #[test] fn update_clipboard_returns_format_list_pdu() { let mut c: Client = Default::default(); - let bytes = c + let messages = c .update_clipboard(String::from("abc").into_bytes()) .unwrap(); + let bytes = messages[0].clone(); // verify that it returns a properly encoded format list PDU let mut payload = Cursor::new(bytes); diff --git a/lib/srv/desktop/rdp/rdpclient/src/lib.rs b/lib/srv/desktop/rdp/rdpclient/src/lib.rs index 332174ee43111..ba8529174f411 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/lib.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/lib.rs @@ -404,10 +404,17 @@ pub unsafe extern "C" fn update_clipboard( match lock.cliprdr { Some(ref mut clip) => match clip.update_clipboard(data) { - Ok(message) => match lock.mcs.write(&cliprdr::CHANNEL_NAME.to_string(), message) { - Ok(()) => CGO_OK, - Err(e) => to_cgo_error(format!("failed writing cliprdr format list: {:?}", e)), - }, + Ok(messages) => { + for message in messages { + if let Err(e) = lock.mcs.write(&cliprdr::CHANNEL_NAME.to_string(), message) { + return to_cgo_error(format!( + "failed writing cliprdr format list: {:?}", + e + )); + } + } + CGO_OK + } Err(e) => to_cgo_error(format!("failed updating clipboard: {:?}", e)), }, None => CGO_OK, @@ -451,7 +458,7 @@ fn read_rdp_output_inner(client: &Client) -> Option { .unwrap() .read(|rdp_event| match rdp_event { RdpEvent::Bitmap(bitmap) => { - let cbitmap = match CGOBitmap::try_from(bitmap) { + let mut cbitmap = match CGOBitmap::try_from(bitmap) { Ok(cb) => cb, Err(e) => { error!( @@ -462,7 +469,7 @@ fn read_rdp_output_inner(client: &Client) -> Option { } }; unsafe { - err = handle_bitmap(client_ref, cbitmap) as CGOError; + err = handle_bitmap(client_ref, &mut cbitmap) as CGOError; }; } // These should never really be sent by the server to us. @@ -689,8 +696,7 @@ unsafe fn from_cgo_error(e: CGOError) -> String { // comments. extern "C" { fn free_go_string(s: *mut c_char); - fn handle_bitmap(client_ref: usize, b: CGOBitmap) -> CGOError; - + fn handle_bitmap(client_ref: usize, b: *mut CGOBitmap) -> CGOError; fn handle_remote_copy(client_ref: usize, data: *mut u8, len: u32) -> CGOError; } diff --git a/lib/srv/desktop/rdp/rdpclient/src/vchan.rs b/lib/srv/desktop/rdp/rdpclient/src/vchan.rs index 0292e036f8022..0ab8ce6ecb878 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/vchan.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/vchan.rs @@ -18,6 +18,17 @@ use bitflags::bitflags; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use rdp::model::error::*; +/// The default maximum chunk size for virtual channel data. +/// +/// If an RDP server supports larger chunks, it will advertise +/// the larger chunk size in the `VCChunkSize` field of the +/// virtual channel capability set. +/// +/// See also: +/// - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/6c074267-1b32-4ceb-9496-2eb941a23e6b +/// - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/a8593178-80c0-4b80-876c-cb77e62cecfc +pub const CHANNEL_CHUNK_LEGNTH: usize = 1600; + bitflags! { /// Channel control flags, as specified in section 2.2.6.1.1 of MS-RDPBCGR. /// @@ -43,8 +54,13 @@ bitflags! { /// It is specified in section 2.2.6.1.1 of MS-RDPBCGR. #[derive(Debug)] pub struct ChannelPDUHeader { - length: u32, - flags: ChannelPDUFlags, + /// The total length of the uncompressed PDU data, + /// excluding the length of this header. + /// Note: the data can span multiple PDUs, in which + /// case each PDU in the series contains the same + /// length field. + pub length: u32, + pub flags: ChannelPDUFlags, } impl ChannelPDUHeader { diff --git a/lib/srv/desktop/windows_server.go b/lib/srv/desktop/windows_server.go index ed33e43a9269b..ed9c513794485 100644 --- a/lib/srv/desktop/windows_server.go +++ b/lib/srv/desktop/windows_server.go @@ -113,7 +113,7 @@ type WindowsService struct { // when desktop discovery is enabled. // no synchronization is necessary because this is only read/written from // the reconciler goroutine. - lastDiscoveryResults types.ResourcesWithLabels + lastDiscoveryResults types.ResourcesWithLabelsMap // Windows hosts discovered via LDAP likely won't resolve with the // default DNS resolver, so we need a custom resolver that will @@ -163,6 +163,8 @@ type WindowsServiceConfig struct { // Windows Desktops. If multiple filters are specified, they are ANDed // together into a single search. DiscoveryLDAPFilters []string + // Hostname of the windows desktop service + Hostname string } // LDAPConfig contains parameters for connecting to an LDAP server. @@ -829,7 +831,7 @@ func (s *WindowsService) connectRDP(ctx context.Context, log logrus.FieldLogger, AllowClipboard: authCtx.Checker.DesktopClipboard(), }) if err != nil { - s.onSessionStart(ctx, &identity, sessionStartTime, windowsUser, string(sessionID), desktop, err) + s.onSessionStart(ctx, sw, &identity, sessionStartTime, windowsUser, string(sessionID), desktop, err) return trace.Wrap(err) } @@ -857,13 +859,13 @@ func (s *WindowsService) connectRDP(ctx context.Context, log logrus.FieldLogger, // consider this a connection failure and return an error // (in the happy path, rdpc remains open until Wait() completes) rdpc.Close() - s.onSessionStart(ctx, &identity, sessionStartTime, windowsUser, string(sessionID), desktop, err) + s.onSessionStart(ctx, sw, &identity, sessionStartTime, windowsUser, string(sessionID), desktop, err) return trace.Wrap(err) } - s.onSessionStart(ctx, &identity, sessionStartTime, windowsUser, string(sessionID), desktop, nil) + s.onSessionStart(ctx, sw, &identity, sessionStartTime, windowsUser, string(sessionID), desktop, nil) err = rdpc.Wait() - s.onSessionEnd(ctx, &identity, sessionStartTime, recordSession, windowsUser, string(sessionID), desktop) + s.onSessionEnd(ctx, sw, &identity, sessionStartTime, recordSession, windowsUser, string(sessionID), desktop) return trace.Wrap(err) } @@ -872,7 +874,7 @@ func (s *WindowsService) makeTDPSendHandler(ctx context.Context, emitter events. id *tlsca.Identity, sessionID, desktopAddr string) func(m tdp.Message, b []byte) { return func(m tdp.Message, b []byte) { switch b[0] { - case byte(tdp.TypePNGFrame): + case byte(tdp.TypePNGFrame), byte(tdp.TypeError): e := &events.DesktopRecording{ Metadata: events.Metadata{ Type: libevents.DesktopRecordingEvent, @@ -896,7 +898,7 @@ func (s *WindowsService) makeTDPSendHandler(ctx context.Context, emitter events. // the TDP send handler emits a clipboard receive event, because we // received clipboard data from the remote desktop and are sending // it on the TDP connection - s.onClipboardReceive(ctx, id, sessionID, desktopAddr, int32(len(clip))) + s.onClipboardReceive(ctx, emitter, id, sessionID, desktopAddr, int32(len(clip))) } } } @@ -930,7 +932,7 @@ func (s *WindowsService) makeTDPReceiveHandler(ctx context.Context, emitter even // the TDP receive handler emits a clipboard send event, because we // received clipboard data from the user (over TDP) and are sending // it to the remote desktop - s.onClipboardSend(ctx, id, sessionID, desktopAddr, int32(len(msg))) + s.onClipboardSend(ctx, emitter, id, sessionID, desktopAddr, int32(len(msg))) } } } @@ -941,6 +943,7 @@ func (s *WindowsService) getServiceHeartbeatInfo() (types.Resource, error) { types.WindowsDesktopServiceSpecV3{ Addr: s.cfg.Heartbeat.PublicAddr, TeleportVersion: teleport.Version, + Hostname: s.cfg.Hostname, }) if err != nil { return nil, trace.Wrap(err) @@ -1028,7 +1031,7 @@ func (s *WindowsService) updateCA(ctx context.Context) error { // have to do it here. // // TODO(zmb3): support multiple CA certs per cluster (such as with HSMs). - ca, err := s.cfg.AccessPoint.GetCertAuthority(types.CertAuthID{ + ca, err := s.cfg.AccessPoint.GetCertAuthority(ctx, types.CertAuthID{ Type: types.UserCA, DomainName: s.clusterName, }, false) diff --git a/lib/srv/desktop/windows_server_test.go b/lib/srv/desktop/windows_server_test.go index 0256f18441b02..4bc0f32983e34 100644 --- a/lib/srv/desktop/windows_server_test.go +++ b/lib/srv/desktop/windows_server_test.go @@ -285,13 +285,8 @@ func TestEmitsRecordingEventsOnReceive(t *testing.T) { func TestEmitsClipboardSendEvents(t *testing.T) { s, id, emitter := setup() - - // clipboard events go straight to the audit log, - // not the session recording, so they use s.cfg.Emitter - // rather than the emitter passed in here - var recordingStreamer events.Emitter /* = nil */ handler := s.makeTDPReceiveHandler(context.Background(), - recordingStreamer, func() int64 { return 0 }, + emitter, func() int64 { return 0 }, id, "session-0", "windows.example.com") fakeClipboardData := make([]byte, 1024) @@ -314,13 +309,8 @@ func TestEmitsClipboardSendEvents(t *testing.T) { func TestEmitsClipboardReceiveEvents(t *testing.T) { s, id, emitter := setup() - - // clipboard events go straight to the audit log, - // not the session recording, so they use s.cfg.Emitter - // rather than the emitter passed in here - var recordingStreamer events.Emitter /* = nil */ handler := s.makeTDPSendHandler(context.Background(), - recordingStreamer, func() int64 { return 0 }, + emitter, func() int64 { return 0 }, id, "session-0", "windows.example.com") fakeClipboardData := make([]byte, 512) diff --git a/lib/srv/reexec.go b/lib/srv/reexec.go index 1444f5295b64a..ca4018eb1d481 100644 --- a/lib/srv/reexec.go +++ b/lib/srv/reexec.go @@ -22,7 +22,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net" "os" "os/exec" @@ -202,8 +201,8 @@ func RunCommand() (errw io.Writer, code int, err error) { stderr = tty } else { stdin = os.Stdin - stdout = ioutil.Discard - stderr = ioutil.Discard + stdout = io.Discard + stderr = io.Discard } // Open the PAM context. @@ -326,7 +325,7 @@ func RunCommand() (errw io.Writer, code int, err error) { } } - return ioutil.Discard, exitCode(err), trace.Wrap(err) + return io.Discard, exitCode(err), trace.Wrap(err) } // RunForward reads in the command to run from the parent process (over a @@ -363,8 +362,8 @@ func RunForward() (errw io.Writer, code int, err error) { ServiceName: c.PAMConfig.ServiceName, Login: c.Login, Stdin: os.Stdin, - Stdout: ioutil.Discard, - Stderr: ioutil.Discard, + Stdout: io.Discard, + Stderr: io.Discard, // Set Teleport specific environment variables that PAM modules // like pam_script.so can pick up to potentially customize the // account/session. @@ -409,19 +408,19 @@ func RunForward() (errw io.Writer, code int, err error) { return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) } - return ioutil.Discard, teleport.RemoteCommandSuccess, nil + return io.Discard, teleport.RemoteCommandSuccess, nil } // runCheckHomeDir check's if the active user's $HOME dir exists. func runCheckHomeDir() (errw io.Writer, code int, err error) { home, err := os.UserHomeDir() if err != nil { - return ioutil.Discard, teleport.HomeDirNotFound, nil + return io.Discard, teleport.HomeDirNotFound, nil } if !utils.IsDir(home) { - return ioutil.Discard, teleport.HomeDirNotFound, nil + return io.Discard, teleport.HomeDirNotFound, nil } - return ioutil.Discard, teleport.RemoteCommandSuccess, nil + return io.Discard, teleport.RemoteCommandSuccess, nil } // RunAndExit will run the requested command and then exit. This wrapper diff --git a/lib/srv/regular/sshserver.go b/lib/srv/regular/sshserver.go index 8e3d0cd72981f..b68550b2215b4 100644 --- a/lib/srv/regular/sshserver.go +++ b/lib/srv/regular/sshserver.go @@ -22,7 +22,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "net" "os" "os/user" @@ -862,7 +861,7 @@ func (s *Server) serveAgent(ctx *srv.ServerContext) error { pid := os.Getpid() // build the socket path and set permissions - socketDir, err := ioutil.TempDir(os.TempDir(), "teleport-") + socketDir, err := os.MkdirTemp(os.TempDir(), "teleport-") if err != nil { return trace.Wrap(err) } diff --git a/lib/srv/regular/sshserver_test.go b/lib/srv/regular/sshserver_test.go index e37e31ecbe86e..ff1014ed80143 100644 --- a/lib/srv/regular/sshserver_test.go +++ b/lib/srv/regular/sshserver_test.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "net" "net/http" "net/http/httptest" @@ -279,7 +278,7 @@ const hostID = "00000000-0000-0000-0000-000000000000" func startReadAll(r io.Reader) <-chan []byte { ch := make(chan []byte) go func() { - data, _ := ioutil.ReadAll(r) + data, _ := io.ReadAll(r) ch <- data }() return ch @@ -449,7 +448,7 @@ func TestDirectTCPIP(t *testing.T) { defer resp.Body.Close() // Make sure the response is what was expected. - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) require.NoError(t, err) require.Equal(t, []byte("hello, world\n"), body) } @@ -620,7 +619,7 @@ func TestAgentForward(t *testing.T) { require.NoError(t, err) // create a temp file to collect the shell output into: - tmpFile, err := ioutil.TempFile(os.TempDir(), "teleport-agent-forward-test") + tmpFile, err := os.CreateTemp(os.TempDir(), "teleport-agent-forward-test") require.NoError(t, err) tmpFile.Close() defer os.Remove(tmpFile.Name()) @@ -632,7 +631,7 @@ func TestAgentForward(t *testing.T) { // wait for the output var socketPath string require.Eventually(t, func() bool { - output, err := ioutil.ReadFile(tmpFile.Name()) + output, err := os.ReadFile(tmpFile.Name()) if err == nil && len(output) != 0 { socketPath = strings.TrimSpace(string(output)) return true diff --git a/lib/srv/sess.go b/lib/srv/sess.go index dac30094a1c94..28860ac7e2f55 100644 --- a/lib/srv/sess.go +++ b/lib/srv/sess.go @@ -93,6 +93,7 @@ func NewSessionRegistry(srv Server, auth auth.ClientI) (*SessionRegistry, error) }), srv: srv, sessions: make(map[rsession.ID]*session), + auth: auth, }, nil } @@ -319,7 +320,7 @@ func (s *SessionRegistry) ForceTerminate(ctx *ServerContext) error { } sess.terminated = true - sess.io.BroadcastMessage("Forcefully terminating session...") + sess.BroadcastMessage("Forcefully terminating session...") sess.term.Kill() sess.doneCh <- struct{}{} @@ -601,6 +602,8 @@ type session struct { bpfContext *bpf.SessionContext cgroupID uint64 + + displayParticipantRequirements bool } // newSession creates a new session with a given ID within a given context. @@ -669,25 +672,35 @@ func newSession(id rsession.ID, r *SessionRegistry, ctx *ServerContext) (*sessio log: log.WithFields(log.Fields{ trace.Component: teleport.Component(teleport.ComponentSession, r.srv.Component()), }), - id: id, - registry: r, - parties: make(map[rsession.ID]*party), - participants: make(map[rsession.ID]*party), - login: ctx.Identity.Login, - closeC: make(chan bool), - lingerTTL: defaults.SessionIdlePeriod, - startTime: startTime, - serverCtx: ctx.srv.Context(), - state: types.SessionState_SessionStatePending, - access: auth.NewSessionAccessEvaluator(policySets, types.SSHSessionKind), - scx: ctx, - presenceEnabled: ctx.Identity.Certificate.Extensions[teleport.CertExtensionMFAVerified] != "", - io: NewTermManager(), - stateUpdate: sync.NewCond(&sync.Mutex{}), - doneCh: make(chan struct{}, 2), - initiator: ctx.Identity.TeleportUser, + id: id, + registry: r, + parties: make(map[rsession.ID]*party), + participants: make(map[rsession.ID]*party), + login: ctx.Identity.Login, + closeC: make(chan bool), + lingerTTL: defaults.SessionIdlePeriod, + startTime: startTime, + serverCtx: ctx.srv.Context(), + state: types.SessionState_SessionStatePending, + access: auth.NewSessionAccessEvaluator(policySets, types.SSHSessionKind), + scx: ctx, + presenceEnabled: ctx.Identity.Certificate.Extensions[teleport.CertExtensionMFAVerified] != "", + io: NewTermManager(), + stateUpdate: sync.NewCond(&sync.Mutex{}), + doneCh: make(chan struct{}, 2), + initiator: ctx.Identity.TeleportUser, + displayParticipantRequirements: utils.AsBool(ctx.env[teleport.EnvSSHSessionDisplayParticipantRequirements]), } + go func() { + if _, open := <-sess.io.TerminateNotifier(); open { + err := sess.registry.ForceTerminate(sess.scx) + if err != nil { + sess.log.Errorf("Failed to terminate session: %v.", err) + } + } + }() + err = sess.trackerCreate(ctx.Identity.TeleportUser, policySets) if err != nil { return nil, trace.Wrap(err) @@ -730,7 +743,7 @@ func (s *session) Close() error { defer s.mu.Unlock() close(s.closeC) - s.io.BroadcastMessage("Closing session...") + s.BroadcastMessage("Closing session...") s.log.Infof("Closing session %v.", s.id) if s.term != nil { s.term.Close() @@ -768,10 +781,7 @@ func (s *session) isLingering() bool { func (s *session) waitOnAccess() error { s.io.Off() - err := s.io.BroadcastMessage("Session paused, Waiting for required participants...") - if err != nil { - log.WithError(err).Errorf("Failed to broadcast message.") - } + s.BroadcastMessage("Session paused, Waiting for required participants...") s.stateUpdate.L.Lock() defer s.stateUpdate.L.Unlock() @@ -789,11 +799,21 @@ outer: s.stateUpdate.Wait() } - s.io.BroadcastMessage("Resuming session...") + s.BroadcastMessage("Resuming session...") s.io.On() return nil } +func (s *session) BroadcastMessage(format string, args ...interface{}) { + if s.access.IsModerated() && !services.IsRecordAtProxy(s.scx.SessionRecordingConfig.GetMode()) { + err := s.io.BroadcastMessage(fmt.Sprintf(format, args...)) + + if err != nil { + s.log.Debugf("Failed to broadcast message: %v", err) + } + } +} + func (s *session) launch(ctx *ServerContext) error { s.mu.Lock() defer s.mu.Unlock() @@ -801,7 +821,7 @@ func (s *session) launch(ctx *ServerContext) error { defer s.stateUpdate.L.Unlock() s.log.Debugf("Launching session %v.", s.id) - s.io.BroadcastMessage("Launching session...") + s.BroadcastMessage("Connecting to %v over SSH", ctx.srv.GetInfo().GetHostname()) s.state = types.SessionState_SessionStateRunning err := s.io.On() @@ -927,34 +947,11 @@ func (s *session) startInteractive(ch ssh.Channel, ctx *ServerContext) error { // create a new "party" (connected client) p := newParty(s, types.SessionPeerMode, ch, ctx) - // Nodes discard events in cases when proxies are already recording them. - if s.registry.srv.Component() == teleport.ComponentNode && - services.IsRecordAtProxy(ctx.SessionRecordingConfig.GetMode()) { - s.recorder = &events.DiscardStream{} - } else { - streamer, err := s.newStreamer(ctx) - if err != nil { - return trace.Wrap(err) - } - s.recorder, err = events.NewAuditWriter(events.AuditWriterConfig{ - // Audit stream is using server context, not session context, - // to make sure that session is uploaded even after it is closed - Context: ctx.srv.Context(), - Streamer: streamer, - Clock: ctx.srv.GetClock(), - SessionID: s.id, - Namespace: ctx.srv.GetNamespace(), - ServerID: ctx.srv.HostUUID(), - RecordOutput: ctx.SessionRecordingConfig.GetMode() != types.RecordOff, - Component: teleport.Component(teleport.ComponentSession, ctx.srv.Component()), - ClusterName: ctx.ClusterName, - }) - if err != nil { - return trace.Wrap(err) - } + rec, err := newRecorder(s, ctx) + if err != nil { + return trace.Wrap(err) } - - var err error + s.recorder = rec // allocate a terminal or take the one previously allocated via a // seaprate "allocate TTY" SSH request @@ -1007,10 +1004,7 @@ func (s *session) startInteractive(ch ssh.Channel, ctx *ServerContext) error { s.inWriter = inWriter s.io.AddReader("reader", inReader) s.io.AddWriter("session-recorder", utils.WriteCloserWithContext(ctx.srv.Context(), s.recorder)) - err = s.io.BroadcastMessage(fmt.Sprintf("Creating session with ID: %v...", s.id)) - if err != nil { - return trace.Wrap(err) - } + s.BroadcastMessage("Creating session with ID: %v...", s.id) if err := s.term.Run(); err != nil { ctx.Errorf("Unable to run shell command: %v.", err) @@ -1055,37 +1049,46 @@ func (s *session) startInteractive(ch ssh.Channel, ctx *ServerContext) error { return nil } -func (s *session) startExec(channel ssh.Channel, ctx *ServerContext) error { - var err error - +// newRecorder creates a new events.StreamWriter to be used as the recorder +// of the passed in session. +func newRecorder(s *session, ctx *ServerContext) (events.StreamWriter, error) { // Nodes discard events in cases when proxies are already recording them. if s.registry.srv.Component() == teleport.ComponentNode && services.IsRecordAtProxy(ctx.SessionRecordingConfig.GetMode()) { - s.recorder = &events.DiscardStream{} - } else { - streamer, err := s.newStreamer(ctx) - if err != nil { - return trace.Wrap(err) - } - rec, err := events.NewAuditWriter(events.AuditWriterConfig{ - // Audit stream is using server context, not session context, - // to make sure that session is uploaded even after it is closed - Context: ctx.srv.Context(), - Streamer: streamer, - SessionID: s.id, - Clock: ctx.srv.GetClock(), - Namespace: ctx.srv.GetNamespace(), - ServerID: ctx.srv.HostUUID(), - RecordOutput: ctx.SessionRecordingConfig.GetMode() != types.RecordOff, - Component: teleport.Component(teleport.ComponentSession, ctx.srv.Component()), - ClusterName: ctx.ClusterName, - }) - if err != nil { - return trace.Wrap(err) - } - s.recorder = rec + return &events.DiscardStream{}, nil } + streamer, err := s.newStreamer(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + rec, err := events.NewAuditWriter(events.AuditWriterConfig{ + // Audit stream is using server context, not session context, + // to make sure that session is uploaded even after it is closed + Context: ctx.srv.Context(), + Streamer: streamer, + SessionID: s.id, + Clock: ctx.srv.GetClock(), + Namespace: ctx.srv.GetNamespace(), + ServerID: ctx.srv.HostUUID(), + RecordOutput: ctx.SessionRecordingConfig.GetMode() != types.RecordOff, + Component: teleport.Component(teleport.ComponentSession, ctx.srv.Component()), + ClusterName: ctx.ClusterName, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + return rec, nil +} + +func (s *session) startExec(channel ssh.Channel, ctx *ServerContext) error { + rec, err := newRecorder(s, ctx) + if err != nil { + return trace.Wrap(err) + } + s.recorder = rec + // Emit a session.start event for the exec session. sessionStartEvent := &apievents.SessionStart{ Metadata: apievents.Metadata{ @@ -1316,7 +1319,7 @@ func (s *session) removeParty(p *party) error { } s.io.DeleteWriter(string(p.id)) - s.io.BroadcastMessage(fmt.Sprintf("User %v left the session.", p.user)) + s.BroadcastMessage("User %v left the session.", p.user) return nil } @@ -1477,6 +1480,18 @@ func (s *session) addParty(p *party, mode types.SessionParticipantMode) error { s.mu.Lock() defer s.mu.Unlock() + if len(s.parties) == 0 { + canStart, _, err := s.checkIfStart() + if err != nil { + return trace.Wrap(err) + } + + if !canStart && services.IsRecordAtProxy(p.ctx.SessionRecordingConfig.GetMode()) { + go s.Close() + return trace.AccessDenied("session requires additional moderation but is in proxy-record mode") + } + } + // Adds participant to in-memory map of party members. s.parties[p.id] = p s.participants[p.id] = p @@ -1497,7 +1512,7 @@ func (s *session) addParty(p *party, mode types.SessionParticipantMode) error { p.ctx.AddCloser(p) s.term.AddParty(1) - s.io.BroadcastMessage(fmt.Sprintf("User %v joined the session.", p.user)) + s.BroadcastMessage("User %v joined the session.", p.user) s.log.Infof("New party %v joined session: %v", p.String(), s.id) if mode == types.SessionPeerMode { @@ -1539,6 +1554,14 @@ func (s *session) addParty(p *party, mode types.SessionParticipantMode) error { s.state = types.SessionState_SessionStateRunning s.stateUpdate.Broadcast() } + } else if !s.started { + base := "Waiting for required participants..." + + if s.displayParticipantRequirements { + s.BroadcastMessage(base+"\r\n%v", s.access.PrettyRequirementsList()) + } else { + s.BroadcastMessage(base) + } } } @@ -1692,9 +1715,12 @@ func (s *session) trackerCreate(teleportUser string, policySet []*types.SessionT reason := s.scx.env[teleport.EnvSSHSessionReason] var invited []string - err := json.Unmarshal([]byte(s.scx.env[teleport.EnvSSHSessionInvited]), &invited) - if err != nil { - return trace.Wrap(err) + + if s.scx.env[teleport.EnvSSHSessionInvited] != "" { + err := json.Unmarshal([]byte(s.scx.env[teleport.EnvSSHSessionInvited]), &invited) + if err != nil { + return trace.Wrap(err) + } } req := &proto.CreateSessionTrackerRequest{ @@ -1713,7 +1739,7 @@ func (s *session) trackerCreate(teleportUser string, policySet []*types.SessionT HostPolicies: policySet, } - _, err = s.registry.auth.CreateSessionTracker(s.serverCtx, req) + _, err := s.registry.auth.CreateSessionTracker(s.serverCtx, req) return trace.Wrap(err) } diff --git a/lib/srv/sess_test.go b/lib/srv/sess_test.go index 88b433559cda8..02a98f96cec1a 100644 --- a/lib/srv/sess_test.go +++ b/lib/srv/sess_test.go @@ -17,8 +17,20 @@ limitations under the License. package srv import ( + "context" "testing" + "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/bpf" + "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/teleport/lib/pam" + restricted "github.com/gravitational/teleport/lib/restrictedsession" + "github.com/gravitational/teleport/lib/services" + rsession "github.com/gravitational/teleport/lib/session" + "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" ) @@ -63,3 +75,237 @@ func TestParseAccessRequestIDs(t *testing.T) { } } + +type mockServer struct { + events.StreamEmitter +} + +// ID is the unique ID of the server. +func (m *mockServer) ID() string { + return "test" +} + +// HostUUID is the UUID of the underlying host. For the forwarding +// server this is the proxy the forwarding server is running in. +func (m *mockServer) HostUUID() string { + return "test" +} + +// GetNamespace returns the namespace the server was created in. +func (m *mockServer) GetNamespace() string { + return "test" +} + +// AdvertiseAddr is the publicly addressable address of this server. +func (m *mockServer) AdvertiseAddr() string { + return "test" +} + +// Component is the type of server, forwarding or regular. +func (m *mockServer) Component() string { + return teleport.ComponentNode +} + +// PermitUserEnvironment returns if reading environment variables upon +// startup is allowed. +func (m *mockServer) PermitUserEnvironment() bool { + return false +} + +// GetAccessPoint returns an AccessPoint for this cluster. +func (m *mockServer) GetAccessPoint() AccessPoint { + return nil +} + +// GetSessionServer returns a session server. +func (m *mockServer) GetSessionServer() rsession.Service { + return nil +} + +// GetDataDir returns data directory of the server +func (m *mockServer) GetDataDir() string { + return "test" +} + +// GetPAM returns PAM configuration for this server. +func (m *mockServer) GetPAM() (*pam.Config, error) { + return nil, nil +} + +// GetClock returns a clock setup for the server +func (m *mockServer) GetClock() clockwork.Clock { + return clockwork.NewRealClock() +} + +// GetInfo returns a services.Server that represents this server. +func (m *mockServer) GetInfo() types.Server { + return nil +} + +// UseTunnel used to determine if this node has connected to this cluster +// using reverse tunnel. +func (m *mockServer) UseTunnel() bool { + return false +} + +// GetBPF returns the BPF service used for enhanced session recording. +func (m *mockServer) GetBPF() bpf.BPF { + return nil +} + +// GetRestrictedSessionManager returns the manager for restricting user activity +func (m *mockServer) GetRestrictedSessionManager() restricted.Manager { + return nil +} + +// Context returns server shutdown context +func (m *mockServer) Context() context.Context { + return context.Background() +} + +// GetUtmpPath returns the path of the user accounting database and log. Returns empty for system defaults. +func (m *mockServer) GetUtmpPath() (utmp, wtmp string) { + return "test", "test" +} + +// GetLockWatcher gets the server's lock watcher. +func (m *mockServer) GetLockWatcher() *services.LockWatcher { + return nil +} + +func TestSession_newRecorder(t *testing.T) { + proxyRecording, err := types.NewSessionRecordingConfigFromConfigFile(types.SessionRecordingConfigSpecV2{ + Mode: types.RecordAtProxy, + }) + require.NoError(t, err) + + proxyRecordingSync, err := types.NewSessionRecordingConfigFromConfigFile(types.SessionRecordingConfigSpecV2{ + Mode: types.RecordAtProxySync, + }) + require.NoError(t, err) + + nodeRecording, err := types.NewSessionRecordingConfigFromConfigFile(types.SessionRecordingConfigSpecV2{ + Mode: types.RecordAtNode, + }) + require.NoError(t, err) + + nodeRecordingSync, err := types.NewSessionRecordingConfigFromConfigFile(types.SessionRecordingConfigSpecV2{ + Mode: types.RecordAtNodeSync, + }) + require.NoError(t, err) + + logger := logrus.WithFields(logrus.Fields{ + trace.Component: teleport.ComponentAuth, + }) + + cases := []struct { + desc string + sess *session + sctx *ServerContext + errAssertion require.ErrorAssertionFunc + recAssertion require.ValueAssertionFunc + }{ + { + desc: "discard-stream-when-proxy-recording", + sess: &session{ + id: "test", + log: logger, + registry: &SessionRegistry{ + srv: &mockServer{}, + }, + }, + sctx: &ServerContext{ + SessionRecordingConfig: proxyRecording, + }, + errAssertion: require.NoError, + recAssertion: func(t require.TestingT, i interface{}, i2 ...interface{}) { + require.NotNil(t, i) + _, ok := i.(*events.DiscardStream) + require.True(t, ok) + }, + }, + { + desc: "discard-stream--when-proxy-sync-recording", + sess: &session{ + id: "test", + log: logger, + registry: &SessionRegistry{ + srv: &mockServer{}, + }, + }, + sctx: &ServerContext{ + SessionRecordingConfig: proxyRecordingSync, + }, + errAssertion: require.NoError, + recAssertion: func(t require.TestingT, i interface{}, i2 ...interface{}) { + require.NotNil(t, i) + _, ok := i.(*events.DiscardStream) + require.True(t, ok) + }, + }, + { + desc: "err-new-streamer-fails", + sess: &session{ + id: "test", + log: logger, + registry: &SessionRegistry{ + srv: &mockServer{}, + }, + }, + sctx: &ServerContext{ + SessionRecordingConfig: nodeRecording, + srv: &mockServer{}, + }, + errAssertion: require.Error, + recAssertion: require.Nil, + }, + { + desc: "err-new-audit-writer-fails", + sess: &session{ + id: "test", + log: logger, + registry: &SessionRegistry{ + srv: &mockServer{}, + }, + }, + sctx: &ServerContext{ + SessionRecordingConfig: nodeRecordingSync, + srv: &mockServer{}, + }, + errAssertion: require.Error, + recAssertion: require.Nil, + }, + { + desc: "audit-writer", + sess: &session{ + id: "test", + log: logger, + registry: &SessionRegistry{ + srv: &mockServer{}, + }, + }, + sctx: &ServerContext{ + ClusterName: "test", + SessionRecordingConfig: nodeRecordingSync, + srv: &mockServer{ + StreamEmitter: &events.DiscardEmitter{}, + }, + }, + errAssertion: require.NoError, + recAssertion: func(t require.TestingT, i interface{}, i2 ...interface{}) { + require.NotNil(t, i) + aw, ok := i.(*events.AuditWriter) + require.True(t, ok) + require.NoError(t, aw.Close(context.Background())) + }, + }, + } + + for _, tt := range cases { + t.Run(tt.desc, func(t *testing.T) { + rec, err := newRecorder(tt.sess, tt.sctx) + tt.errAssertion(t, err) + tt.recAssertion(t, rec) + }) + } +} diff --git a/lib/srv/termmanager.go b/lib/srv/termmanager.go index e3d8796ee65bc..b9bf958ca3c33 100644 --- a/lib/srv/termmanager.go +++ b/lib/srv/termmanager.go @@ -31,12 +31,15 @@ const maxHistory = 1000 // - history scrollback for new clients // - stream breaking type TermManager struct { + // These two fields need to be first in the struct so that they are 64-bit aligned which is a requirement + // for atomic operations on certain architectures. + countWritten uint64 + countRead uint64 + mu sync.Mutex writers map[string]io.Writer - readerState map[string]*int32 + readerState map[string]bool OnWriteError func(idString string, err error) - countWritten uint64 - countRead uint64 // buffer is used to buffer writes when turned off buffer []byte on bool @@ -46,23 +49,27 @@ type TermManager struct { incoming chan []byte // remaining is a partially read chunk of stdin data // we only support one concurrent reader so this isn't mutex protected - remaining []byte - readStateUpdate *sync.Cond - closed *int32 + remaining []byte + readStateUpdate *sync.Cond + closed bool + lastWasBroadcast bool + terminateNotifier chan struct{} } // NewTermManager creates a new TermManager. func NewTermManager() *TermManager { return &TermManager{ - writers: make(map[string]io.Writer), - readerState: make(map[string]*int32), - closed: new(int32), - readStateUpdate: sync.NewCond(&sync.Mutex{}), - incoming: make(chan []byte, 100), + writers: make(map[string]io.Writer), + readerState: make(map[string]bool), + closed: false, + readStateUpdate: sync.NewCond(&sync.Mutex{}), + incoming: make(chan []byte, 100), + terminateNotifier: make(chan struct{}), } } func (g *TermManager) writeToClients(p []byte) int { + g.lastWasBroadcast = false truncateFront := func(slice []byte, max int) []byte { if len(slice) > max { return slice[len(slice)-max:] @@ -95,6 +102,10 @@ func (g *TermManager) writeToClients(p []byte) int { return len(p) } +func (g *TermManager) TerminateNotifier() <-chan struct{} { + return g.terminateNotifier +} + func (g *TermManager) Write(p []byte) (int, error) { g.mu.Lock() defer g.mu.Unlock() @@ -163,9 +174,14 @@ func (g *TermManager) writeUnconditional(p []byte) (int, error) { // BroadcastMessage injects a message into the stream. func (g *TermManager) BroadcastMessage(message string) error { - data := []byte("\r\nTeleport > " + message + "\r\n") g.mu.Lock() defer g.mu.Unlock() + data := []byte("Teleport > " + message + "\r\n") + if g.lastWasBroadcast { + data = append([]byte("\r\n"), data...) + } else { + g.lastWasBroadcast = true + } _, err := g.writeUnconditional(data) return trace.Wrap(err) } @@ -201,8 +217,7 @@ func (g *TermManager) DeleteWriter(name string) { } func (g *TermManager) AddReader(name string, r io.Reader) { - readerState := new(int32) - g.readerState[name] = readerState + g.readerState[name] = false go func() { for { @@ -214,10 +229,28 @@ func (g *TermManager) AddReader(name string, r io.Reader) { return } + for _, b := range buf[:n] { + // This is the ASCII control code for CTRL+C. + if b == 0x03 { + g.mu.Lock() + if !g.on && !g.closed { + select { + case g.terminateNotifier <- struct{}{}: + default: + } + } + g.mu.Unlock() + break + } + } + g.incoming <- buf[:n] - if atomic.LoadInt32(g.closed) == 1 || atomic.LoadInt32(readerState) == 1 { + g.mu.Lock() + if g.closed || g.readerState[name] { + g.mu.Unlock() return } + g.mu.Unlock() } }() } @@ -225,10 +258,7 @@ func (g *TermManager) AddReader(name string, r io.Reader) { func (g *TermManager) DeleteReader(name string) { g.mu.Lock() defer g.mu.Unlock() - - if g.readerState[name] != nil { - atomic.StoreInt32(g.readerState[name], 1) - } + g.readerState[name] = true } func (g *TermManager) CountWritten() uint64 { @@ -240,7 +270,13 @@ func (g *TermManager) CountRead() uint64 { } func (g *TermManager) Close() { - atomic.StoreInt32(g.closed, 1) + g.mu.Lock() + defer g.mu.Unlock() + + if !g.closed { + g.closed = true + close(g.terminateNotifier) + } } func (g *TermManager) GetRecentHistory() []byte { diff --git a/lib/srv/termmanager_test.go b/lib/srv/termmanager_test.go new file mode 100644 index 0000000000000..feb5667bab47c --- /dev/null +++ b/lib/srv/termmanager_test.go @@ -0,0 +1,50 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package srv + +import ( + "io" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestCTRLCPassthrough(t *testing.T) { + m := NewTermManager() + m.On() + r, w := io.Pipe() + m.AddReader("foo", r) + go w.Write([]byte("\x03")) + buf := make([]byte, 1) + _, err := m.Read(buf) + require.NoError(t, err) + require.Equal(t, []byte("\x03"), buf) +} + +func TestCTRLCCapture(t *testing.T) { + m := NewTermManager() + r, w := io.Pipe() + m.AddReader("foo", r) + go w.Write([]byte("\x03")) + + select { + case <-m.TerminateNotifier(): + case <-time.After(time.Second * 10): + t.Fatal("terminateNotifier should've seen an event") + } +} diff --git a/lib/sshutils/scp/scp_test.go b/lib/sshutils/scp/scp_test.go index 01f4a0f9daf45..19579e243643b 100644 --- a/lib/sshutils/scp/scp_test.go +++ b/lib/sshutils/scp/scp_test.go @@ -19,7 +19,6 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "net/http" "net/http/httptest" "os" @@ -65,7 +64,7 @@ func TestHTTPSendFile(t *testing.T) { require.NoError(t, err) err = runSCP(cmd, "-v", "-t", outDir) require.NoError(t, err) - bytesReceived, err := ioutil.ReadFile(filepath.Join(outDir, "filename")) + bytesReceived, err := os.ReadFile(filepath.Join(outDir, "filename")) require.NoError(t, err) require.Empty(t, cmp.Diff(string(bytesReceived), string(expectedBytes))) } @@ -74,7 +73,7 @@ func TestHTTPReceiveFile(t *testing.T) { source := filepath.Join(t.TempDir(), "target") contents := []byte("hello, file contents!") - err := ioutil.WriteFile(source, contents, 0666) + err := os.WriteFile(source, contents, 0666) require.NoError(t, err) w := httptest.NewRecorder() @@ -91,7 +90,7 @@ func TestHTTPReceiveFile(t *testing.T) { err = runSCP(cmd, "-v", "-f", source) require.NoError(t, err) - data, err := ioutil.ReadAll(w.Body) + data, err := io.ReadAll(w.Body) contentLengthStr := strconv.Itoa(len(data)) require.NoError(t, err) require.Empty(t, cmp.Diff(string(data), string(contents))) @@ -428,7 +427,7 @@ func TestInvalidDir(t *testing.T) { doneC := make(chan struct{}) // Service stderr go func() { - io.Copy(ioutil.Discard, stderr) + io.Copy(io.Discard, stderr) close(doneC) }() @@ -452,7 +451,7 @@ func TestVerifyDirectoryModeFailsWithFile(t *testing.T) { // Create temporary directory with a file "target" in it. dir := t.TempDir() target := filepath.Join(dir, "target") - err := ioutil.WriteFile(target, []byte{}, 0666) + err := os.WriteFile(target, []byte{}, 0666) require.NoError(t, err) cmd, err := CreateCommand( @@ -478,7 +477,7 @@ func TestVerifyDirectoryModeIsRequiredForDirectory(t *testing.T) { // Create temporary directory with a file "target" in it. dir := t.TempDir() target := filepath.Join(dir, "target") - err := ioutil.WriteFile(target, []byte{}, 0666) + err := os.WriteFile(target, []byte{}, 0666) require.NoError(t, err) cmd, err := CreateCommand( @@ -666,7 +665,7 @@ func validateSCPContents(t *testing.T, expected *testFS, actual FileSystem) { rc, err := actual.OpenFile(path) require.NoError(t, err) defer rc.Close() - bytes, err := ioutil.ReadAll(rc) + bytes, err := io.ReadAll(rc) require.NoError(t, err) require.Empty(t, cmp.Diff(fileinfo.contents.String(), string(bytes))) } diff --git a/lib/sshutils/x11/display.go b/lib/sshutils/x11/display.go index 2fd6f66554bd2..6e0b2a152274c 100644 --- a/lib/sshutils/x11/display.go +++ b/lib/sshutils/x11/display.go @@ -118,12 +118,29 @@ func (d *Display) Listen() (XServerListener, error) { // xserverUnixSocket returns the display's associated unix socket. func (d *Display) unixSocket() (*net.UnixAddr, error) { - // For x11 unix domain sockets, the hostname must be "unix" or empty. In these cases - // we return the actual unix socket for the display "/tmp/.X11-unix/X" + // If hostname is "unix" or empty, then the actual unix socket + // for the display is "/tmp/.X11-unix/X" if d.HostName == "unix" || d.HostName == "" { sockName := filepath.Join(x11SockDir(), fmt.Sprintf("X%d", d.DisplayNumber)) return net.ResolveUnixAddr("unix", sockName) } + + // It's possible that the display is actually the full path + // to an open XServer socket, such as with xquartz on OSX: + // "/private/tmp/com.apple.com/launchd.xxx/org.xquartz.com:0" + if d.HostName[0] == '/' { + sockName := d.String() + if _, err := os.Stat(sockName); err == nil { + return net.ResolveUnixAddr("unix", sockName) + } + + // The socket might not include the screen number. + sockName = fmt.Sprintf("%s:%d", d.HostName, d.DisplayNumber) + if _, err := os.Stat(sockName); err == nil { + return net.ResolveUnixAddr("unix", sockName) + } + } + return nil, trace.BadParameter("display is not a unix socket") } @@ -131,7 +148,7 @@ func (d *Display) unixSocket() (*net.UnixAddr, error) { // e.g. "hostname:<6000+display_number>" func (d *Display) tcpSocket() (*net.TCPAddr, error) { if d.HostName == "" { - return nil, trace.BadParameter("hostname can't be empty for an XServer tcp socket") + return nil, trace.BadParameter("display is not a tcp socket, hostname can't be empty") } port := fmt.Sprint(d.DisplayNumber + x11BasePort) @@ -162,6 +179,7 @@ func GetXDisplay() (Display, error) { // display number, and screen number, or a parsing error. display must be //in one of the following formats - hostname:d[.s], unix:d[.s], :d[.s], ::d[.s]. func ParseDisplay(displayString string) (Display, error) { + if displayString == "" { return Display{}, trace.BadParameter("display cannot be an empty string") } diff --git a/lib/sshutils/x11/display_test.go b/lib/sshutils/x11/display_test.go index d28edb551d64a..0d4bdc0b1d575 100644 --- a/lib/sshutils/x11/display_test.go +++ b/lib/sshutils/x11/display_test.go @@ -22,139 +22,89 @@ import ( "github.com/stretchr/testify/require" ) -func TestParseDisplay(t *testing.T) { +func TestDisplay(t *testing.T) { t.Parallel() testCases := []struct { - desc string - displayString string - expectDisplay Display - assertErr require.ErrorAssertionFunc - validSocket string + desc string + displayString string + expectDisplay Display + expectParseError bool + expectUnixAddr string + expectTCPAddr string }{ { - desc: "unix socket", - displayString: ":10", - expectDisplay: Display{DisplayNumber: 10}, - assertErr: require.NoError, - validSocket: "unix", - }, { - desc: "unix socket", - displayString: "::10", - expectDisplay: Display{DisplayNumber: 10}, - assertErr: require.NoError, - validSocket: "unix", - }, { - desc: "unix socket", - displayString: "unix:10", - expectDisplay: Display{HostName: "unix", DisplayNumber: 10}, - assertErr: require.NoError, - validSocket: "unix", - }, { - desc: "unix socket with screen number", - displayString: "unix:10.1", - expectDisplay: Display{HostName: "unix", DisplayNumber: 10, ScreenNumber: 1}, - assertErr: require.NoError, - validSocket: "unix", + desc: "unix socket", + displayString: ":10", + expectDisplay: Display{DisplayNumber: 10}, + expectUnixAddr: filepath.Join(x11SockDir(), "X10"), + }, { + desc: "unix socket", + displayString: "::10", + expectDisplay: Display{DisplayNumber: 10}, + expectUnixAddr: filepath.Join(x11SockDir(), "X10"), + }, { + desc: "unix socket", + displayString: "unix:10", + expectDisplay: Display{HostName: "unix", DisplayNumber: 10}, + expectUnixAddr: filepath.Join(x11SockDir(), "X10"), + }, { + desc: "unix socket with screen number", + displayString: "unix:10.1", + expectDisplay: Display{HostName: "unix", DisplayNumber: 10, ScreenNumber: 1}, + expectUnixAddr: filepath.Join(x11SockDir(), "X10"), }, { desc: "localhost", displayString: "localhost:10", expectDisplay: Display{HostName: "localhost", DisplayNumber: 10}, - assertErr: require.NoError, - validSocket: "tcp", - }, { - desc: "some hostname", - displayString: "example.com:10", - expectDisplay: Display{HostName: "example.com", DisplayNumber: 10}, - assertErr: require.NoError, - validSocket: "tcp", + expectTCPAddr: "127.0.0.1:6010", }, { desc: "some ip address", displayString: "1.2.3.4:10", expectDisplay: Display{HostName: "1.2.3.4", DisplayNumber: 10}, - assertErr: require.NoError, - validSocket: "tcp", + expectTCPAddr: "1.2.3.4:6010", + }, { + desc: "invalid ip address", + displayString: "1.2.3.4.5:10", + expectDisplay: Display{HostName: "1.2.3.4.5", DisplayNumber: 10}, + }, { + desc: "empty", + displayString: "", + expectParseError: true, }, { - desc: "empty", - displayString: "", - expectDisplay: Display{}, - assertErr: require.Error, + desc: "no display number", + displayString: ":", + expectParseError: true, }, { - desc: "no display number", - displayString: ":", - expectDisplay: Display{}, - assertErr: require.Error, + desc: "negative display number", + displayString: ":-10", + expectParseError: true, }, { - desc: "negative display number", - displayString: ":-10", - expectDisplay: Display{}, - assertErr: require.Error, + desc: "negative screen number", + displayString: ":10.-1", + expectParseError: true, }, { - desc: "negative screen number", - displayString: ":10.-1", - expectDisplay: Display{}, - assertErr: require.Error, + desc: "invalid characters", + displayString: "$(exec ls)", + expectParseError: true, }, { - desc: "invalid characters", - displayString: "$(exec ls)", - expectDisplay: Display{}, - assertErr: require.Error, + desc: "invalid unix socket", + displayString: "/some/socket/without/display", + expectParseError: true, }, } for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { display, err := ParseDisplay(tc.displayString) - tc.assertErr(t, err) - require.Equal(t, tc.expectDisplay, display) - - switch tc.validSocket { - case "unix": - _, err := display.unixSocket() - require.NoError(t, err) - case "tcp": - _, err := display.tcpSocket() - require.NoError(t, err) + if tc.expectParseError == true { + require.Error(t, err) + return } - }) - } -} - -func TestDisplaySocket(t *testing.T) { - testCases := []struct { - desc string - display Display - expectUnixAddr string - expectTCPAddr string - }{ - { - desc: "unix socket no hostname", - display: Display{DisplayNumber: 10}, - expectUnixAddr: filepath.Join(os.TempDir(), ".X11-unix", "X10"), - }, { - desc: "unix socket with hostname", - display: Display{HostName: "unix", DisplayNumber: 10}, - expectUnixAddr: filepath.Join(os.TempDir(), ".X11-unix", "X10"), - }, { - desc: "localhost", - display: Display{HostName: "localhost", DisplayNumber: 10}, - expectTCPAddr: "127.0.0.1:6010", - }, { - desc: "some ip address", - display: Display{HostName: "1.2.3.4", DisplayNumber: 10}, - expectTCPAddr: "1.2.3.4:6010", - }, { - desc: "invalid ip address", - display: Display{HostName: "1.2.3.4.5", DisplayNumber: 10}, - }, { - desc: "invalid unix socket", - display: Display{HostName: filepath.Join(os.TempDir(), "socket"), DisplayNumber: 10}, - }, - } + require.NoError(t, err) + require.Equal(t, tc.expectDisplay, display) - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - unixSock, err := tc.display.unixSocket() + unixSock, err := display.unixSocket() if tc.expectUnixAddr == "" { require.Error(t, err) } else { @@ -162,7 +112,7 @@ func TestDisplaySocket(t *testing.T) { require.Equal(t, tc.expectUnixAddr, unixSock.String()) } - tcpSock, err := tc.display.tcpSocket() + tcpSock, err := display.tcpSocket() if tc.expectTCPAddr == "" { require.Error(t, err) } else { @@ -171,4 +121,25 @@ func TestDisplaySocket(t *testing.T) { } }) } + + // The unix socket full path test requires its associated + // unix addr to be discoverable in the file system + t.Run("unix socket full path", func(t *testing.T) { + tmpDir := os.TempDir() + unixSocket := filepath.Join(tmpDir, "org.xquartz.com:0") + hostName := filepath.Join(tmpDir, "org.xquartz.com") + _, err := os.Create(unixSocket) + require.NoError(t, err) + + display, err := ParseDisplay(unixSocket) + require.NoError(t, err) + require.Equal(t, Display{HostName: hostName, DisplayNumber: 0}, display) + + unixSock, err := display.unixSocket() + require.NoError(t, err) + require.Equal(t, unixSocket, unixSock.String()) + + _, err = display.tcpSocket() + require.Error(t, err) + }) } diff --git a/lib/tlsca/ca.go b/lib/tlsca/ca.go index 4d5b215f7870c..ee56515458f50 100644 --- a/lib/tlsca/ca.go +++ b/lib/tlsca/ca.go @@ -29,14 +29,15 @@ import ( "strconv" "time" + "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" + "github.com/sirupsen/logrus" + "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/types/wrappers" - - "github.com/gravitational/trace" - "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" + "github.com/gravitational/teleport/api/utils" ) var log = logrus.WithFields(logrus.Fields{ @@ -772,6 +773,9 @@ func (c *CertificateRequest) CheckAndSetDefaults() error { if c.KeyUsage == 0 { c.KeyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature } + + c.DNSNames = utils.Deduplicate(c.DNSNames) + return nil } diff --git a/lib/utils/certs_test.go b/lib/utils/certs_test.go index 4ddaa0f180651..c8dfa7d0637c7 100644 --- a/lib/utils/certs_test.go +++ b/lib/utils/certs_test.go @@ -17,7 +17,7 @@ limitations under the License. package utils import ( - "io/ioutil" + "os" "github.com/gravitational/trace" @@ -34,7 +34,7 @@ func (s *CertsSuite) TestRejectsInvalidPEMData(c *check.C) { } func (s *CertsSuite) TestRejectsSelfSignedCertificate(c *check.C) { - certificateChainBytes, err := ioutil.ReadFile("../../fixtures/certs/ca.pem") + certificateChainBytes, err := os.ReadFile("../../fixtures/certs/ca.pem") c.Assert(err, check.IsNil) certificateChain, err := ReadCertificateChain(certificateChainBytes) diff --git a/lib/utils/chconn_test.go b/lib/utils/chconn_test.go index 180fdd52299b4..6088aeee641ab 100644 --- a/lib/utils/chconn_test.go +++ b/lib/utils/chconn_test.go @@ -19,7 +19,7 @@ package utils import ( "crypto/rand" "crypto/rsa" - "io/ioutil" + "io" "net" "os" "testing" @@ -60,7 +60,7 @@ func TestChConn(t *testing.T) { go func() { // Nothing is sent on the channel so this will block until the // read is canceled by the deadline set below. - _, err := ioutil.ReadAll(chConn) + _, err := io.ReadAll(chConn) doneCh <- err }() // Set the read deadline in the past and make sure that the read diff --git a/lib/utils/cli.go b/lib/utils/cli.go index 79362ed1e62eb..1b2e685cb7ae8 100644 --- a/lib/utils/cli.go +++ b/lib/utils/cli.go @@ -22,7 +22,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" stdlog "log" "math" "os" @@ -61,7 +60,7 @@ func InitLogger(purpose LoggingPurpose, level log.Level, verbose ...bool) { log.SetFormatter(NewDefaultTextFormatter(trace.IsTerminal(os.Stderr))) log.SetOutput(os.Stderr) } else { - log.SetOutput(ioutil.Discard) + log.SetOutput(io.Discard) } case LoggingForDaemon: log.SetFormatter(NewDefaultTextFormatter(trace.IsTerminal(os.Stderr))) @@ -83,7 +82,7 @@ func InitLoggerForTests() { return } logger.SetLevel(log.WarnLevel) - logger.SetOutput(ioutil.Discard) + logger.SetOutput(io.Discard) } // NewLoggerForTests creates a new logger for test environment @@ -251,6 +250,8 @@ func formatCertError(err error) string { } const ( + // Bold is an escape code to format as bold or increased intensity + Bold = 1 // Red is an escape code for red terminal color Red = 31 // Yellow is an escape code for yellow terminal color @@ -291,7 +292,55 @@ func InitCLIParser(appName, appHelp string) (app *kingpin.Application) { app.HelpFlag.NoEnvar() // set our own help template - return app.UsageTemplate(defaultUsageTemplate) + return app.UsageTemplate(createUsageTemplate()) +} + +// createUsageTemplate creates an usage template for kingpin applications. +func createUsageTemplate(opts ...func(*usageTemplateOptions)) string { + opt := &usageTemplateOptions{ + commandPrintfWidth: defaultCommandPrintfWidth, + } + + for _, optFunc := range opts { + optFunc(opt) + } + return fmt.Sprintf(defaultUsageTemplate, opt.commandPrintfWidth) +} + +// UpdateAppUsageTemplate updates usage template for kingpin applications by +// pre-parsing the arguments then applying any changes to the usage template if +// necessary. +func UpdateAppUsageTemplate(app *kingpin.Application, args []string) { + // If ParseContext fails, kingpin will not show usage so there is no need + // to update anything here. See app.Parse for more details. + context, err := app.ParseContext(args) + if err != nil { + return + } + + app.UsageTemplate(createUsageTemplate( + withCommandPrintfWidth(app, context), + )) +} + +// withCommandPrintfWidth returns an usage template option that +// updates command printf width if longer than default. +func withCommandPrintfWidth(app *kingpin.Application, context *kingpin.ParseContext) func(*usageTemplateOptions) { + return func(opt *usageTemplateOptions) { + var commands []*kingpin.CmdModel + if context.SelectedCommand != nil { + commands = context.SelectedCommand.Model().FlattenedCommands() + } else { + commands = app.Model().FlattenedCommands() + } + + for _, command := range commands { + if !command.Hidden && len(command.FullCommand) > opt.commandPrintfWidth { + opt.commandPrintfWidth = len(command.FullCommand) + } + } + + } } // SplitIdentifiers splits list of identifiers by commas/spaces/newlines. Helpful when @@ -379,8 +428,19 @@ func needsQuoting(text string) bool { return false } -// Usage template with compactly formatted commands. -var defaultUsageTemplate = `{{define "FormatCommand"}}\ +// usageTemplateOptions defines options to format the usage template. +type usageTemplateOptions struct { + // commandPrintfWidth is the width of the command name with padding, for + // {{.FullCommand | printf "%%-%ds"}} + commandPrintfWidth int +} + +// defaultCommandPrintfWidth is the default command printf width. +const defaultCommandPrintfWidth = 12 + +// defaultUsageTemplate is a fmt format that defines the usage template with +// compactly formatted commands. Should be only used in createUsageTemplate. +const defaultUsageTemplate = `{{define "FormatCommand"}}\ {{if .FlagSummary}} {{.FlagSummary}}{{end}}\ {{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ {{end}}\ @@ -388,7 +448,7 @@ var defaultUsageTemplate = `{{define "FormatCommand"}}\ {{define "FormatCommands"}}\ {{range .FlattenedCommands}}\ {{if not .Hidden}}\ - {{.FullCommand | printf "%-12s" }}{{if .Default}} (Default){{end}} {{ .Help }} + {{.FullCommand | printf "%%-%ds"}}{{if .Default}} (Default){{end}} {{ .Help }} {{end}}\ {{end}}\ {{end}}\ diff --git a/lib/utils/cli_test.go b/lib/utils/cli_test.go index 0be2fb4c0edcc..9985aa60be042 100644 --- a/lib/utils/cli_test.go +++ b/lib/utils/cli_test.go @@ -19,7 +19,7 @@ package utils import ( "crypto/x509" "fmt" - "io/ioutil" + "io" "strings" "testing" @@ -64,7 +64,7 @@ func TestUserMessageFromError(t *testing.T) { func TestConsolefLongComponent(t *testing.T) { require.NotPanics(t, func() { component := strings.Repeat("na ", 10) + "batman!" - Consolef(ioutil.Discard, logrus.New(), component, "test message") + Consolef(io.Discard, logrus.New(), component, "test message") }) } diff --git a/lib/utils/conn.go b/lib/utils/conn.go index c43aac61d44c4..07a47b759eff7 100644 --- a/lib/utils/conn.go +++ b/lib/utils/conn.go @@ -21,7 +21,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "net" "net/http" "sync/atomic" @@ -100,7 +99,7 @@ func RoundtripWithConn(conn net.Conn) (string, error) { return "", err } defer re.Body.Close() - out, err := ioutil.ReadAll(re.Body) + out, err := io.ReadAll(re.Body) if err != nil { return "", err } diff --git a/lib/utils/environment_test.go b/lib/utils/environment_test.go index e7d7409dd3a0a..5b5e335a48dde 100644 --- a/lib/utils/environment_test.go +++ b/lib/utils/environment_test.go @@ -16,7 +16,6 @@ limitations under the License. package utils import ( - "io/ioutil" "os" "gopkg.in/check.v1" @@ -40,7 +39,7 @@ foo= `) // create a temp file with an environment in it - f, err := ioutil.TempFile("", "teleport-environment-") + f, err := os.CreateTemp("", "teleport-environment-") c.Assert(err, check.IsNil) defer os.Remove(f.Name()) _, err = f.Write(rawenv) diff --git a/lib/utils/jsontools_test.go b/lib/utils/jsontools_test.go index c4389e51f1011..8b55ffe814c31 100644 --- a/lib/utils/jsontools_test.go +++ b/lib/utils/jsontools_test.go @@ -19,6 +19,7 @@ import ( "bytes" "testing" + "github.com/gravitational/teleport/api/types" "github.com/stretchr/testify/require" ) @@ -28,8 +29,8 @@ import ( // operations that depend on the byte ordering fail (e.g. CompareAndSwap). func TestMarshalMapConsistency(t *testing.T) { value := map[string]string{ - "teleport.dev/foo": "1234", - "teleport.dev/bar": "5678", + types.TeleportNamespace + "/foo": "1234", + types.TeleportNamespace + "/bar": "5678", } compareTo, err := FastMarshal(value) diff --git a/lib/utils/kernel.go b/lib/utils/kernel.go index f687301cb752f..9f4d88103cfd1 100644 --- a/lib/utils/kernel.go +++ b/lib/utils/kernel.go @@ -20,7 +20,6 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "os" "runtime" "strings" @@ -55,7 +54,7 @@ func KernelVersion() (*semver.Version, error) { // kernelVersion reads in the kernel version from the reader and returns // a *semver.Version. func kernelVersion(reader io.Reader) (*semver.Version, error) { - buf, err := ioutil.ReadAll(reader) + buf, err := io.ReadAll(reader) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/utils/prompt/confirmation.go b/lib/utils/prompt/confirmation.go index 1710f67ccb308..82ff796a812f1 100644 --- a/lib/utils/prompt/confirmation.go +++ b/lib/utils/prompt/confirmation.go @@ -26,13 +26,20 @@ import ( "github.com/gravitational/trace" ) +// Reader is the interface for prompt readers. +type Reader interface { + // ReadContext reads from the underlying buffer, respecting context + // cancellation. + ReadContext(ctx context.Context) ([]byte, error) +} + // Confirmation prompts the user for a yes/no confirmation for question. // The prompt is written to out and the answer is read from in. // // question should be a plain sentece without "[yes/no]"-type hints at the end. // // ctx can be canceled to abort the prompt. -func Confirmation(ctx context.Context, out io.Writer, in *ContextReader, question string) (bool, error) { +func Confirmation(ctx context.Context, out io.Writer, in Reader, question string) (bool, error) { fmt.Fprintf(out, "%s [y/N]: ", question) answer, err := in.ReadContext(ctx) if err != nil { @@ -52,7 +59,7 @@ func Confirmation(ctx context.Context, out io.Writer, in *ContextReader, questio // question should be a plain sentece without the list of provided options. // // ctx can be canceled to abort the prompt. -func PickOne(ctx context.Context, out io.Writer, in *ContextReader, question string, options []string) (string, error) { +func PickOne(ctx context.Context, out io.Writer, in Reader, question string, options []string) (string, error) { fmt.Fprintf(out, "%s [%s]: ", question, strings.Join(options, ", ")) answerOrig, err := in.ReadContext(ctx) if err != nil { @@ -72,7 +79,7 @@ func PickOne(ctx context.Context, out io.Writer, in *ContextReader, question str // The prompt is written to out and the answer is read from in. // // ctx can be canceled to abort the prompt. -func Input(ctx context.Context, out io.Writer, in *ContextReader, question string) (string, error) { +func Input(ctx context.Context, out io.Writer, in Reader, question string) (string, error) { fmt.Fprintf(out, "%s: ", question) answer, err := in.ReadContext(ctx) if err != nil { diff --git a/lib/utils/prompt/sync_reader.go b/lib/utils/prompt/sync_reader.go new file mode 100644 index 0000000000000..a175ec2cc4908 --- /dev/null +++ b/lib/utils/prompt/sync_reader.go @@ -0,0 +1,55 @@ +// Copyright 2022 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prompt + +import ( + "context" + "io" + "os" + + "github.com/gravitational/trace" +) + +// StdinSync returns a synchronous reader to os.Stdin. +// It is safe for use mixed with other methods that read Stdin, albeit not +// concurrently, but doesn't respect context cancelletion. +func StdinSync() *SyncReader { + return &SyncReader{Reader: os.Stdin} +} + +// SyncReader is a synchronous version of ContextReader. +// Its main advantage is that it allows multiple sources to read from the same +// underlying reader, albeit not concurrently. +type SyncReader struct { + Reader io.Reader +} + +// ReadContext reads a chunk from the underlying reader. +// It does not respect context cancellation after the read starts. +func (c *SyncReader) ReadContext(ctx context.Context) ([]byte, error) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + buf := make([]byte, 4*1024) // 4kB, matches Linux page size. + n, err := c.Reader.Read(buf) + if err != nil { + return nil, trace.Wrap(err) + } + buf = buf[:n] + return buf, nil +} diff --git a/lib/utils/prompt/sync_reader_test.go b/lib/utils/prompt/sync_reader_test.go new file mode 100644 index 0000000000000..1038e8011141e --- /dev/null +++ b/lib/utils/prompt/sync_reader_test.go @@ -0,0 +1,66 @@ +// Copyright 2022 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prompt_test + +import ( + "bytes" + "context" + "testing" + + "github.com/gravitational/teleport/lib/utils/prompt" + "github.com/stretchr/testify/require" +) + +func TestSyncReader_ReadContext(t *testing.T) { + ctx := context.Background() + data := []byte("who let the llamas out?") + + cancelledCtx, cancel := context.WithCancel(ctx) + cancel() + + tests := []struct { + name string + data []byte + ctx context.Context + wantData []byte + wantErr error + }{ + { + name: "ok", + data: data, + ctx: ctx, + wantData: data, + }, + { + name: "ctx cancelled", + data: data, + ctx: cancelledCtx, + wantErr: cancelledCtx.Err(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + r := &prompt.SyncReader{ + Reader: bytes.NewReader(test.data), + } + got, err := r.ReadContext(test.ctx) + require.Equal(t, test.wantErr, err, "ReadContext error mismatch") + if err != nil { + return + } + require.Equal(t, test.wantData, got, "ReadContext data mismatch") + }) + } +} diff --git a/lib/utils/syslog.go b/lib/utils/syslog.go index 9037d432821ab..37630dd6b5ea5 100644 --- a/lib/utils/syslog.go +++ b/lib/utils/syslog.go @@ -20,7 +20,7 @@ limitations under the License. package utils import ( - "io/ioutil" + "io" "log/syslog" "os" @@ -46,6 +46,6 @@ func SwitchLoggerToSyslog(logger *log.Logger) error { } logger.AddHook(hook) // ... and disable stderr: - logger.SetOutput(ioutil.Discard) + logger.SetOutput(io.Discard) return nil } diff --git a/lib/utils/timeout_test.go b/lib/utils/timeout_test.go index fe113899409dd..7bd9cac3e3e40 100644 --- a/lib/utils/timeout_test.go +++ b/lib/utils/timeout_test.go @@ -20,7 +20,7 @@ package utils import ( "context" "fmt" - "io/ioutil" + "io" "net" "net/http" "net/http/httptest" @@ -95,7 +95,7 @@ func newClient(timeout time.Duration) *http.Client { // bodyText helper returns a body string from an http response func bodyText(resp *http.Response) string { - bytes, err := ioutil.ReadAll(resp.Body) + bytes, err := io.ReadAll(resp.Body) if err != nil { return "" } diff --git a/lib/utils/token.go b/lib/utils/token.go index a2b8ba14012b9..b417f9d23e52e 100644 --- a/lib/utils/token.go +++ b/lib/utils/token.go @@ -17,7 +17,7 @@ limitations under the License. package utils import ( - "io/ioutil" + "os" "strings" "github.com/gravitational/trace" @@ -31,7 +31,7 @@ func ReadToken(token string) (string, error) { return token, nil } // treat it as a file - out, err := ioutil.ReadFile(token) + out, err := os.ReadFile(token) if err != nil { return "", trace.ConvertSystemError(err) } diff --git a/lib/utils/utils.go b/lib/utils/utils.go index ee32b0bbc125c..96893ee62935e 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "net" "net/url" "os" @@ -323,7 +322,7 @@ func ReadPath(path string) ([]byte, error) { if err != nil { return nil, trace.ConvertSystemError(err) } - bytes, err := ioutil.ReadFile(abs) + bytes, err := os.ReadFile(abs) if err != nil { return nil, trace.ConvertSystemError(err) } @@ -437,7 +436,7 @@ func ReadHostUUID(dataDir string) (string, error) { // WriteHostUUID writes host UUID into a file func WriteHostUUID(dataDir string, id string) error { - err := ioutil.WriteFile(filepath.Join(dataDir, HostUUIDFile), []byte(id), os.ModeExclusive|0400) + err := os.WriteFile(filepath.Join(dataDir, HostUUIDFile), []byte(id), os.ModeExclusive|0400) if err != nil { return trace.ConvertSystemError(err) } @@ -561,7 +560,7 @@ func StoreErrorOf(f func() error, err *error) { // when limit bytes are read. func ReadAtMost(r io.Reader, limit int64) ([]byte, error) { limitedReader := &io.LimitedReader{R: r, N: limit} - data, err := ioutil.ReadAll(limitedReader) + data, err := io.ReadAll(limitedReader) if err != nil { return data, err } diff --git a/lib/utils/utils_test.go b/lib/utils/utils_test.go index a27933b315e26..fc2faf7f8c2a6 100644 --- a/lib/utils/utils_test.go +++ b/lib/utils/utils_test.go @@ -18,7 +18,7 @@ package utils import ( "bytes" - "io/ioutil" + "io" "net/url" "os" "path/filepath" @@ -104,7 +104,7 @@ func (s *UtilsSuite) TestHostUUID(c *check.C) { // newlines are getting ignored dir = c.MkDir() id := "id-with-newline\n" - err = ioutil.WriteFile(filepath.Join(dir, HostUUIDFile), []byte(id), 0666) + err = os.WriteFile(filepath.Join(dir, HostUUIDFile), []byte(id), 0666) c.Assert(err, check.IsNil) out, err := ReadHostUUID(dir) c.Assert(err, check.IsNil) @@ -502,7 +502,7 @@ func (s *UtilsSuite) TestReadToken(c *check.C) { dir := c.MkDir() tokenPath := filepath.Join(dir, "token") - err = ioutil.WriteFile(tokenPath, []byte("shmoken"), 0644) + err = os.WriteFile(tokenPath, []byte("shmoken"), 0644) c.Assert(err, check.IsNil) tok, err = ReadToken(tokenPath) @@ -542,7 +542,7 @@ func (s *UtilsSuite) TestRepeatReader(c *check.C) { }, } for _, tc := range tcs { - data, err := ioutil.ReadAll(NewRepeatReader(tc.repeat, tc.count)) + data, err := io.ReadAll(NewRepeatReader(tc.repeat, tc.count)) c.Assert(err, check.IsNil) c.Assert(string(data), check.Equals, tc.expected) } diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 7652d11ca481d..ff8cad79f1225 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -26,7 +26,6 @@ import ( "fmt" "html/template" "io" - "io/ioutil" "net" "net/http" "net/url" @@ -35,6 +34,7 @@ import ( "sync" "time" + "github.com/gravitational/oxy/ratelimit" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/client/webclient" @@ -51,6 +51,7 @@ import ( "github.com/gravitational/teleport/lib/httplib" "github.com/gravitational/teleport/lib/httplib/csrf" "github.com/gravitational/teleport/lib/jwt" + "github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/plugin" "github.com/gravitational/teleport/lib/reversetunnel" "github.com/gravitational/teleport/lib/secret" @@ -260,6 +261,23 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { } h.sshPort = sshPort + // challengeLimiter is used to limit unauthenticated challenge generation for + // passwordless. + challengeLimiter, err := limiter.NewRateLimiter(limiter.Config{ + Rates: []limiter.Rate{ + { + Period: defaults.LimiterPasswordlessPeriod, + Average: defaults.LimiterPasswordlessAverage, + Burst: defaults.LimiterPasswordlessBurst, + }, + }, + MaxConnections: defaults.LimiterMaxConnections, + MaxNumberOfUsers: defaults.LimiterMaxConcurrentUsers, + }) + if err != nil { + return nil, trace.Wrap(err) + } + // ping endpoint is used to check if the server is up. the /webapi/ping // endpoint returns the default authentication method and configuration that // the server supports. the /webapi/ping/:connector endpoint can be used to @@ -332,8 +350,13 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { h.GET("/webapi/sites/:site/nodes/:server/:login/scp", h.WithClusterAuth(h.transferFile)) h.POST("/webapi/sites/:site/nodes/:server/:login/scp", h.WithClusterAuth(h.transferFile)) + // token generation + h.POST("/webapi/token", h.WithAuth(h.createTokenHandle)) + // add Node token generation - h.POST("/webapi/nodes/token", h.WithAuth(h.createScriptJoinTokenHandle)) + // DELETE IN 11.0. Deprecated, use /webapi/token for generating tokens of any role. + h.POST("/webapi/nodes/token", h.WithAuth(h.createNodeTokenHandle)) + // join scripts h.GET("/scripts/:token/install-node.sh", httplib.MakeHandler(h.getNodeJoinScriptHandle)) h.GET("/scripts/:token/install-app.sh", httplib.MakeHandler(h.getAppJoinScriptHandle)) // web context @@ -361,7 +384,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { h.POST("/webapi/github/login/console", httplib.MakeHandler(h.githubLoginConsole)) // MFA public endpoints. - h.POST("/webapi/mfa/login/begin", httplib.MakeHandler(h.mfaLoginBegin)) + h.POST("/webapi/mfa/login/begin", h.withLimiter(challengeLimiter, h.mfaLoginBegin)) h.POST("/webapi/mfa/login/finish", httplib.MakeHandler(h.mfaLoginFinish)) h.POST("/webapi/mfa/login/finishsession", httplib.MakeHandler(h.mfaLoginFinishSession)) h.DELETE("/webapi/mfa/token/:token/devices/:devicename", httplib.MakeHandler(h.deleteMFADeviceWithTokenHandle)) @@ -419,7 +442,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { return nil, trace.Wrap(err) } defer index.Close() - indexContent, err := ioutil.ReadAll(index) + indexContent, err := io.ReadAll(index) if err != nil { return nil, trace.ConvertSystemError(err) } @@ -739,7 +762,6 @@ func defaultAuthenticationSettings(ctx context.Context, authClient auth.ClientI) func (h *Handler) ping(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { var err error - defaultSettings, err := defaultAuthenticationSettings(r.Context(), h.cfg.ProxyClient) if err != nil { return nil, trace.Wrap(err) @@ -788,12 +810,13 @@ func (h *Handler) pingWithConnector(w http.ResponseWriter, r *http.Request, p ht ServerVersion: teleport.Version, } + hasMessageOfTheDay := cap.GetMessageOfTheDay() != "" if connectorName == constants.Local { - as, err := localSettings(cap) + response.Auth, err = localSettings(cap) if err != nil { return nil, trace.Wrap(err) } - response.Auth = as + response.Auth.HasMessageOfTheDay = hasMessageOfTheDay return response, nil } @@ -801,6 +824,7 @@ func (h *Handler) pingWithConnector(w http.ResponseWriter, r *http.Request, p ht oidcConnector, err := authClient.GetOIDCConnector(r.Context(), connectorName, false) if err == nil { response.Auth = oidcSettings(oidcConnector, cap) + response.Auth.HasMessageOfTheDay = hasMessageOfTheDay return response, nil } @@ -808,6 +832,7 @@ func (h *Handler) pingWithConnector(w http.ResponseWriter, r *http.Request, p ht samlConnector, err := authClient.GetSAMLConnector(r.Context(), connectorName, false) if err == nil { response.Auth = samlSettings(samlConnector, cap) + response.Auth.HasMessageOfTheDay = hasMessageOfTheDay return response, nil } @@ -815,6 +840,7 @@ func (h *Handler) pingWithConnector(w http.ResponseWriter, r *http.Request, p ht githubConnector, err := authClient.GetGithubConnector(r.Context(), connectorName, false) if err == nil { response.Auth = githubSettings(githubConnector, cap) + response.Auth.HasMessageOfTheDay = hasMessageOfTheDay return response, nil } @@ -946,7 +972,7 @@ func (h *Handler) jwks(w http.ResponseWriter, r *http.Request, p httprouter.Para } // Fetch the JWT public keys only. - ca, err := h.cfg.ProxyClient.GetCertAuthority(types.CertAuthID{ + ca, err := h.cfg.ProxyClient.GetCertAuthority(r.Context(), types.CertAuthID{ Type: types.JWTSigner, DomainName: clusterName, }, false) @@ -1633,27 +1659,39 @@ func (h *Handler) getResetPasswordToken(ctx context.Context, tokenID string) (in } // mfaLoginBegin is the first step in the MFA authentication ceremony, which -// may be completed either via mfaLoginFinish (SSH) or mfaLoginFinishSession (Web). +// may be completed either via mfaLoginFinish (SSH) or mfaLoginFinishSession +// (Web). // // POST /webapi/mfa/login/begin // // {"user": "alex", "pass": "abc123"} +// {"passwordless": true} // // Successful response: // // {"webauthn_challenge": {...}, "totp_challenge": true} +// {"webauthn_challenge": {...}} // passwordless func (h *Handler) mfaLoginBegin(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { var req *client.MFAChallengeRequest if err := httplib.ReadJSON(r, &req); err != nil { return nil, trace.Wrap(err) } - mfaChallenge, err := h.auth.proxyClient.CreateAuthenticateChallenge(r.Context(), &proto.CreateAuthenticateChallengeRequest{ - Request: &proto.CreateAuthenticateChallengeRequest_UserCredentials{UserCredentials: &proto.UserCredentials{ - Username: req.User, - Password: []byte(req.Pass), - }}, - }) + mfaReq := &proto.CreateAuthenticateChallengeRequest{} + if req.Passwordless { + mfaReq.Request = &proto.CreateAuthenticateChallengeRequest_Passwordless{ + Passwordless: &proto.Passwordless{}, + } + } else { + mfaReq.Request = &proto.CreateAuthenticateChallengeRequest_UserCredentials{ + UserCredentials: &proto.UserCredentials{ + Username: req.User, + Password: []byte(req.Pass), + }, + } + } + + mfaChallenge, err := h.auth.proxyClient.CreateAuthenticateChallenge(r.Context(), mfaReq) if err != nil { return nil, trace.AccessDenied("bad auth credentials") } @@ -1704,10 +1742,14 @@ func (h *Handler) mfaLoginFinishSession(w http.ResponseWriter, r *http.Request, if err != nil { return nil, trace.AccessDenied("bad auth credentials") } - if err := SetSessionCookie(w, req.User, session.GetName()); err != nil { + + // Fetch user from session, user is empty for passwordless requests. + user := session.GetUser() + if err := SetSessionCookie(w, user, session.GetName()); err != nil { return nil, trace.Wrap(err) } - ctx, err := h.auth.newSessionContext(req.User, session.GetName()) + + ctx, err := h.auth.newSessionContext(user, session.GetName()) if err != nil { return nil, trace.AccessDenied("need auth") } @@ -2304,10 +2346,7 @@ func (h *Handler) hostCredentials(w http.ResponseWriter, r *http.Request, p http return nil, trace.Wrap(err) } - // Teleport 8 clients are still expecting the legacy JSON format. - // Teleport 9 clients handle both legacy and new. - // TODO(zmb3) return certs directly in Teleport 10 - return auth.LegacyCertsFromProto(certs), nil + return certs, nil } // createSSHCert is a web call that generates new SSH certificate based @@ -2499,6 +2538,21 @@ func (h *Handler) WithAuth(fn ContextHandler) httprouter.Handle { }) } +// withLimiter adds IP-based rate limiting to fn. +func (h *Handler) withLimiter(l *limiter.RateLimiter, fn httplib.HandlerFunc) httprouter.Handle { + return httplib.MakeHandler(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { + err := l.RegisterRequest(r.RemoteAddr, nil /* customRate */) + // MaxRateError doesn't play well with errors.Is, hence the cast. + if _, ok := err.(*ratelimit.MaxRateError); ok { + return nil, trace.LimitExceeded(err.Error()) + } + if err != nil { + return nil, trace.Wrap(err) + } + return fn(w, r, p) + }) +} + // AuthenticateRequest authenticates request using combination of a session cookie // and bearer token func (h *Handler) AuthenticateRequest(w http.ResponseWriter, r *http.Request, checkBearerToken bool) (*SessionContext, error) { diff --git a/lib/web/apiserver_login_test.go b/lib/web/apiserver_login_test.go index 045d252d63320..1857d66548221 100644 --- a/lib/web/apiserver_login_test.go +++ b/lib/web/apiserver_login_test.go @@ -29,8 +29,12 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/defaults" + "github.com/gravitational/trace" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" + + wanlib "github.com/gravitational/teleport/lib/auth/webauthn" ) func TestWebauthnLogin_ssh(t *testing.T) { @@ -41,7 +45,6 @@ func TestWebauthnLogin_ssh(t *testing.T) { Webauthn: &types.Webauthn{ RPID: env.server.TLS.ClusterName(), }, - // Use default Webauthn configuration. }) user := clusterMFA.User password := clusterMFA.Password @@ -97,7 +100,6 @@ func TestWebauthnLogin_web(t *testing.T) { Webauthn: &types.Webauthn{ RPID: env.server.TLS.ClusterName(), }, - // Use default Webauthn configuration. }) user := clusterMFA.User password := clusterMFA.Password @@ -136,6 +138,139 @@ func TestWebauthnLogin_web(t *testing.T) { require.NotEmpty(t, createSessionResp.SessionExpires.Unix()) } +func TestAuthenticate_passwordless(t *testing.T) { + env := newWebPack(t, 1) + clusterMFA := configureClusterForMFA(t, env, &types.AuthPreferenceSpecV2{ + Type: constants.Local, + SecondFactor: constants.SecondFactorOn, + Webauthn: &types.Webauthn{ + RPID: env.server.TLS.ClusterName(), + }, + }) + user := clusterMFA.User + device := clusterMFA.WebDev.Key + + // Fake a passwordless device. Typically this would require a separate + // registration, but because we use fake devices we can get away with it. + device.SetPasswordless() + + // Fetch the WebAuthn User Handle. In a real-world scenario the device stores + // the handle alongside the credentials during registration. + ctx := context.Background() + authServer := env.server.Auth() + wla, err := authServer.GetWebauthnLocalAuth(ctx, user) + require.NoError(t, err) + userHandle := wla.UserID + + // Prepare SSH key to be signed. + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + pub, err := ssh.NewPublicKey(&priv.PublicKey) + require.NoError(t, err) + pubBytes := ssh.MarshalAuthorizedKey(pub) + + clt, err := client.NewWebClient(env.proxies[0].webURL.String(), roundtrip.HTTPClient(client.NewInsecureWebClient())) + require.NoError(t, err) + + tests := []struct { + name string + login func(t *testing.T, assertionResp *wanlib.CredentialAssertionResponse) + }{ + { + name: "ssh", + login: func(t *testing.T, assertionResp *wanlib.CredentialAssertionResponse) { + ep := clt.Endpoint("webapi", "mfa", "login", "finish") + sshResp, err := clt.PostJSON(ctx, ep, &client.AuthenticateSSHUserRequest{ + WebauthnChallengeResponse: assertionResp, // no username + PubKey: pubBytes, + TTL: 24 * time.Hour, + }) + require.NoError(t, err, "Passwordless authentication failed") + loginResp := &auth.SSHLoginResponse{} + require.NoError(t, json.Unmarshal(sshResp.Bytes(), loginResp)) + require.Equal(t, user, loginResp.Username) + }, + }, + { + name: "web", + login: func(t *testing.T, assertionResp *wanlib.CredentialAssertionResponse) { + ep := clt.Endpoint("webapi", "mfa", "login", "finishsession") + sessionResp, err := clt.PostJSON(ctx, ep, &client.AuthenticateWebUserRequest{ + WebauthnAssertionResponse: assertionResp, // no username + }) + require.NoError(t, err, "Passwordless authentication failed") + createSessionResp := &CreateSessionResponse{} + require.NoError(t, json.Unmarshal(sessionResp.Bytes(), createSessionResp)) + require.NotEmpty(t, createSessionResp.TokenType) + require.NotEmpty(t, createSessionResp.Token) + require.NotEmpty(t, createSessionResp.TokenExpiresIn) + require.NotEmpty(t, createSessionResp.SessionExpires.Unix()) + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Request passwordless challenge. + ep := clt.Endpoint("webapi", "mfa", "login", "begin") + beginResp, err := clt.PostJSON(ctx, ep, &client.MFAChallengeRequest{ + Passwordless: true, // no username and password + }) + require.NoError(t, err, "Failed to create passwordless challenge") + mfaChallenge := &client.MFAAuthenticateChallenge{} + require.NoError(t, json.Unmarshal(beginResp.Bytes(), mfaChallenge)) + require.NotNil(t, mfaChallenge.WebauthnChallenge, "Want non-nil WebAuthn challenge") + + // Sign challenge and set user handle. + origin := "https://" + env.server.TLS.ClusterName() + assertionResp, err := device.SignAssertion(origin, mfaChallenge.WebauthnChallenge) + require.NoError(t, err) + assertionResp.AssertionResponse.UserHandle = userHandle + + // Complete passwordless login. + test.login(t, assertionResp) + }) + } +} + +func TestAuthenticate_rateLimiting(t *testing.T) { + ctx := context.Background() + + tests := []struct { + name string + burst int + fn func(clt *client.WebClient) error + }{ + { + name: "/webapi/mfa/login/begin", + burst: defaults.LimiterPasswordlessBurst, + fn: func(clt *client.WebClient) error { + ep := clt.Endpoint("webapi", "mfa", "login", "begin") + _, err := clt.PostJSON(ctx, ep, &client.MFAChallengeRequest{}) + return err + }, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + // Use a separate webPack per test, so limits won't influence one another. + env := newWebPack(t, 1) + clt, err := client.NewWebClient(env.proxies[0].webURL.String(), roundtrip.HTTPClient(client.NewInsecureWebClient())) + require.NoError(t, err) + + for i := 0; i < test.burst; i++ { + err := test.fn(clt) + require.False(t, trace.IsLimitExceeded(err), "got err = %v, want non-LimitExceeded", err) + } + + err = test.fn(clt) + require.True(t, trace.IsLimitExceeded(err), "got err = %v, want LimitExceeded", err) + }) + } +} + type configureMFAResp struct { User, Password string WebDev *auth.TestDevice diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index db8a00ee70065..7c8089b67054c 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -29,7 +29,6 @@ import ( "fmt" "image" "io" - "io/ioutil" "net" "net/http" "net/http/cookiejar" @@ -517,7 +516,7 @@ func (s *WebSuite) TestSAMLSuccess(c *C) { err = services.ValidateSAMLConnector(connector) c.Assert(err, IsNil) - role, err := types.NewRole(connector.GetAttributesToRoles()[0].Roles[0], types.RoleSpecV5{ + role, err := types.NewRoleV3(connector.GetAttributesToRoles()[0].Roles[0], types.RoleSpecV5{ Options: types.RoleOptions{ MaxSessionTTL: types.NewDuration(apidefaults.MaxCertDuration), }, @@ -559,7 +558,7 @@ func (s *WebSuite) TestSAMLSuccess(c *C) { c.Assert(u.Scheme+"://"+u.Host+u.Path, Equals, fixtures.SAMLOktaSSO) data, err := base64.StdEncoding.DecodeString(u.Query().Get("SAMLRequest")) c.Assert(err, IsNil) - buf, err := ioutil.ReadAll(flate.NewReader(bytes.NewReader(data))) + buf, err := io.ReadAll(flate.NewReader(bytes.NewReader(data))) c.Assert(err, IsNil) doc := etree.NewDocument() err = doc.ReadFromBytes(buf) @@ -1163,7 +1162,7 @@ func mustStartWindowsDesktopMock(t *testing.T, authClient *auth.Server) *windows tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert require.NoError(t, err) - ca, err := authClient.GetCertAuthority(types.CertAuthID{Type: types.UserCA, DomainName: n.GetClusterName()}, false) + ca, err := authClient.GetCertAuthority(context.Background(), types.CertAuthID{Type: types.UserCA, DomainName: n.GetClusterName()}, false) require.NoError(t, err) for _, kp := range services.GetTLSCerts(ca) { @@ -1960,6 +1959,70 @@ func (s *WebSuite) TestGetClusterDetails(c *C) { c.Assert(nodes, HasLen, cluster.NodeCount) } +func TestTokenGeneration(t *testing.T) { + tt := []struct { + name string + roles types.SystemRoles + shouldErr bool + }{ + { + name: "single node role", + roles: types.SystemRoles{types.RoleNode}, + shouldErr: false, + }, + { + name: "single app role", + roles: types.SystemRoles{types.RoleApp}, + shouldErr: false, + }, + { + name: "single db role", + roles: types.SystemRoles{types.RoleDatabase}, + shouldErr: false, + }, + { + name: "multiple roles", + roles: types.SystemRoles{types.RoleNode, types.RoleApp, types.RoleDatabase}, + shouldErr: false, + }, + { + name: "return error if no role is requested", + roles: types.SystemRoles{}, + shouldErr: true, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + env := newWebPack(t, 1) + + proxy := env.proxies[0] + pack := proxy.authPack(t, "test-user@example.com") + + endpoint := pack.clt.Endpoint("webapi", "token") + re, err := pack.clt.PostJSON(context.Background(), endpoint, createTokenRequest{ + Roles: tc.roles, + }) + + if tc.shouldErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + var responseToken nodeJoinToken + err = json.Unmarshal(re.Bytes(), &responseToken) + require.NoError(t, err) + + // generated token roles should match the requested ones + generatedToken, err := proxy.auth.Auth().GetToken(context.Background(), responseToken.ID) + require.NoError(t, err) + require.Equal(t, tc.roles, generatedToken.GetRoles()) + }) + } +} + func TestClusterDatabasesGet(t *testing.T) { env := newWebPack(t, 1) @@ -2398,37 +2461,55 @@ func TestCreateRegisterChallenge(t *testing.T) { require.NoError(t, err) tests := []struct { - name string - deviceType string + name string + req *createRegisterChallengeRequest + assertChallenge func(t *testing.T, c *client.MFARegisterChallenge) }{ { - name: "totp challenge", - deviceType: "totp", + name: "totp", + req: &createRegisterChallengeRequest{ + DeviceType: "totp", + }, }, { - name: "webauthn challenge", - deviceType: "webauthn", + name: "webauthn", + req: &createRegisterChallengeRequest{ + DeviceType: "webauthn", + }, + }, + { + name: "passwordless", + req: &createRegisterChallengeRequest{ + DeviceType: "webauthn", + DeviceUsage: "passwordless", + }, + assertChallenge: func(t *testing.T, c *client.MFARegisterChallenge) { + // rrk=true is a good proxy for passwordless. + require.NotNil(t, c.Webauthn.Response.AuthenticatorSelection.RequireResidentKey, "rrk cannot be nil") + require.True(t, *c.Webauthn.Response.AuthenticatorSelection.RequireResidentKey, "rrk cannot be false") + }, }, } - for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() endpoint := clt.Endpoint("webapi", "mfa", "token", token.GetName(), "registerchallenge") - res, err := clt.PostJSON(ctx, endpoint, &createRegisterChallengeRequest{ - DeviceType: tc.deviceType, - }) + res, err := clt.PostJSON(ctx, endpoint, tc.req) require.NoError(t, err) var chal client.MFARegisterChallenge require.NoError(t, json.Unmarshal(res.Bytes(), &chal)) - switch tc.deviceType { + switch tc.req.DeviceType { case "totp": - require.NotNil(t, chal.TOTP.QRCode) + require.NotNil(t, chal.TOTP.QRCode, "TOTP QR code cannot be nil") case "webauthn": - require.NotNil(t, chal.Webauthn) + require.NotNil(t, chal.Webauthn, "WebAuthn challenge cannot be nil") + } + + if tc.assertChallenge != nil { + tc.assertChallenge(t, &chal) } }) } diff --git a/lib/web/app/transport.go b/lib/web/app/transport.go index b8a63e00d5d72..e8781f2abc3a8 100644 --- a/lib/web/app/transport.go +++ b/lib/web/app/transport.go @@ -22,6 +22,7 @@ import ( "fmt" "net" "net/http" + "strings" "sync" "github.com/gravitational/teleport/api/constants" @@ -189,8 +190,7 @@ func (t *transport) DialContext(ctx context.Context, _, _ string) (net.Conn, err var dialErr error conn, dialErr = dialAppServer(t.c.proxyClient, t.c.identity, appServer) if dialErr != nil { - // Connection problem with the server. - if trace.IsConnectionProblem(dialErr) { + if isReverseTunnelDownError(dialErr) { t.c.log.Warnf("Failed to connect to application server %q: %v.", serverID, dialErr) t.servers.Delete(serverID) // Only goes for the next server if the error returned is a @@ -256,7 +256,7 @@ func configureTLS(c *transportConfig) (*tls.Config, error) { // Configure the pool of certificates that will be used to verify the // identity of the server. This allows the client to verify the identity of // the server it is connecting to. - ca, err := c.accessPoint.GetCertAuthority(types.CertAuthID{ + ca, err := c.accessPoint.GetCertAuthority(context.TODO(), types.CertAuthID{ Type: types.HostCA, DomainName: c.identity.RouteToApp.ClusterName, }, false) @@ -283,3 +283,10 @@ func configureTLS(c *transportConfig) (*tls.Config, error) { return tlsConfig, nil } + +// isReverseTunnelDownError returns true if the provided error indicates that +// the reverse tunnel connection is down e.g. because the agent is down. +func isReverseTunnelDownError(err error) bool { + return trace.IsConnectionProblem(err) || + strings.Contains(err.Error(), reversetunnel.NoApplicationTunnel) +} diff --git a/lib/web/desktop/playback.go b/lib/web/desktop/playback.go index 31c72c9cd8785..79f0a7c86d1d7 100644 --- a/lib/web/desktop/playback.go +++ b/lib/web/desktop/playback.go @@ -19,13 +19,16 @@ package desktop import ( "context" "errors" + "fmt" "net" + "os" "sync" "time" apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/trace" "github.com/sirupsen/logrus" "golang.org/x/net/websocket" ) @@ -203,8 +206,19 @@ func (pp *Player) streamSessionEvents(ctx context.Context, cancel context.Cancel select { case err := <-errC: + // TODO(zmb3, isaiah): send some sort of error to the browser, + // otherwise it just sits at the player UI if err != nil && !errors.Is(err, context.Canceled) { pp.log.WithError(err).Errorf("streaming session %v", pp.sID) + var errorText string + if os.IsNotExist(err) || trace.IsNotFound(err) { + errorText = "session not found" + } else { + errorText = "server error" + } + if _, err := pp.ws.Write([]byte(fmt.Sprintf(`{"message": "error", "errorText": "%v"}`, errorText))); err != nil { + pp.log.WithError(err).Error("failed to write \"error\" message over websocket") + } } return case evt := <-eventsC: @@ -225,6 +239,10 @@ func (pp *Player) streamSessionEvents(ctx context.Context, cancel context.Cancel msg, err := utils.FastMarshal(e) if err != nil { pp.log.WithError(err).Errorf("failed to marshal DesktopRecording event into JSON: %v", e) + if _, err := pp.ws.Write([]byte(`{"message":"error","errorText":"server error"}`)); err != nil { + pp.log.WithError(err).Error("failed to write \"error\" message over websocket") + } + return } if _, err := pp.ws.Write(msg); err != nil { // We expect net.ErrClosed to arise when another goroutine returns before @@ -234,6 +252,12 @@ func (pp *Player) streamSessionEvents(ctx context.Context, cancel context.Cancel } return } + case *apievents.WindowsDesktopSessionStart, *apievents.WindowsDesktopSessionEnd: + // these events are part of the stream but never needed for playback + case *apievents.DesktopClipboardReceive, *apievents.DesktopClipboardSend: + // these events are not currently needed for playback, + // but may be useful in the future + default: pp.log.Warnf("session %v contains unexpected event type %T", pp.sID, evt) } diff --git a/lib/web/desktop/playback_test.go b/lib/web/desktop/playback_test.go index bb0be1f908993..3348112580795 100644 --- a/lib/web/desktop/playback_test.go +++ b/lib/web/desktop/playback_test.go @@ -65,7 +65,7 @@ func TestStreamsDesktopEvents(t *testing.T) { b := make([]byte, 4096) n, err := ws.Read(b) require.NoError(t, err) - require.Equal(t, []byte(`{"message":"end"}`), b[:n]) + require.JSONEq(t, `{"message":"end"}`, string(b[:n])) } func newServer(t *testing.T, streamInterval time.Duration, events []apievents.AuditEvent) *httptest.Server { diff --git a/lib/web/gziphandler.go b/lib/web/gziphandler.go index 37e5a80662fd9..d7acf9b03113e 100644 --- a/lib/web/gziphandler.go +++ b/lib/web/gziphandler.go @@ -18,7 +18,7 @@ package web import ( "compress/gzip" - "io/ioutil" + "io" "net/http" "strings" "sync" @@ -30,7 +30,7 @@ import ( // internal buffers to avoid too many objects on the heap var writerPool = sync.Pool{ New: func() interface{} { - gz := gzip.NewWriter(ioutil.Discard) + gz := gzip.NewWriter(io.Discard) return gz }, } diff --git a/lib/web/join_tokens.go b/lib/web/join_tokens.go index 64ff8c9047069..328133e9fa83f 100644 --- a/lib/web/join_tokens.go +++ b/lib/web/join_tokens.go @@ -31,6 +31,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/defaults" + "github.com/gravitational/teleport/lib/httplib" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/web/scripts" @@ -56,13 +57,40 @@ type scriptSettings struct { appURI string } -func (h *Handler) createScriptJoinTokenHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) { +// createTokenRequest is the expected request body of +// the endpoint to create token +type createTokenRequest struct { + Roles types.SystemRoles `json:"roles"` +} + +func (h *Handler) createTokenHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) { + var req createTokenRequest + if err := httplib.ReadJSON(r, &req); err != nil { + log.WithError(err).Error("error reading body") + return nil, trace.Wrap(err) + } + + clt, err := ctx.GetClient() + if err != nil { + log.WithError(err).Error("error getting client") + return nil, trace.Wrap(err) + } + + return createJoinToken(r.Context(), clt, req.Roles) +} + +func (h *Handler) createNodeTokenHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) { clt, err := ctx.GetClient() if err != nil { return nil, trace.Wrap(err) } - return createScriptJoinToken(r.Context(), clt) + roles := types.SystemRoles{ + types.RoleNode, + types.RoleApp, + } + + return createJoinToken(r.Context(), clt, roles) } func (h *Handler) getNodeJoinScriptHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params) (interface{}, error) { @@ -130,13 +158,10 @@ func (h *Handler) getAppJoinScriptHandle(w http.ResponseWriter, r *http.Request, return nil, nil } -func createScriptJoinToken(ctx context.Context, m nodeAPIGetter) (*nodeJoinToken, error) { +func createJoinToken(ctx context.Context, m nodeAPIGetter, roles types.SystemRoles) (*nodeJoinToken, error) { req := auth.GenerateTokenRequest{ - Roles: types.SystemRoles{ - types.RoleNode, - types.RoleApp, - }, - TTL: defaults.NodeJoinTokenTTL, + Roles: roles, + TTL: defaults.NodeJoinTokenTTL, } token, err := m.GenerateToken(ctx, req) diff --git a/lib/web/join_tokens_test.go b/lib/web/join_tokens_test.go index 6a17db4b08a60..24ac93f0bc478 100644 --- a/lib/web/join_tokens_test.go +++ b/lib/web/join_tokens_test.go @@ -36,7 +36,10 @@ func TestCreateNodeJoinToken(t *testing.T) { return "some-token-id", nil } - token, err := createScriptJoinToken(context.Background(), m) + token, err := createJoinToken(context.Background(), m, types.SystemRoles{ + types.RoleNode, + types.RoleApp, + }) require.NoError(t, err) require.Equal(t, defaults.NodeJoinTokenTTL, token.Expiry.Sub(time.Now().UTC()).Round(time.Second)) diff --git a/lib/web/mfa.go b/lib/web/mfa.go index 70130d727ae06..8813bd5ee432d 100644 --- a/lib/web/mfa.go +++ b/lib/web/mfa.go @@ -18,6 +18,7 @@ package web import ( "net/http" + "strings" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/lib/auth/webauthn" @@ -146,6 +147,10 @@ func (h *Handler) createAuthenticateChallengeWithTokenHandle(w http.ResponseWrit type createRegisterChallengeRequest struct { // DeviceType is the type of MFA device to get a register challenge for. DeviceType string `json:"deviceType"` + // DeviceUsage is the intended usage of the device (MFA, Passwordless, etc). + // It mimics the proto.DeviceUsage enum. + // Defaults to MFA. + DeviceUsage string `json:"deviceUsage"` } // createRegisterChallengeWithTokenHandle creates and returns MFA register challenges for a new device for the specified device type. @@ -165,9 +170,20 @@ func (h *Handler) createRegisterChallengeWithTokenHandle(w http.ResponseWriter, return nil, trace.BadParameter("MFA device type %q unsupported", req.DeviceType) } + var deviceUsage proto.DeviceUsage + switch strings.ToLower(req.DeviceUsage) { + case "", "mfa": + deviceUsage = proto.DeviceUsage_DEVICE_USAGE_MFA + case "passwordless": + deviceUsage = proto.DeviceUsage_DEVICE_USAGE_PASSWORDLESS + default: + return nil, trace.BadParameter("device usage %q unsupported", req.DeviceUsage) + } + chal, err := h.cfg.ProxyClient.CreateRegisterChallenge(r.Context(), &proto.CreateRegisterChallengeRequest{ - TokenID: p.ByName("token"), - DeviceType: deviceType, + TokenID: p.ByName("token"), + DeviceType: deviceType, + DeviceUsage: deviceUsage, }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/web/resources_test.go b/lib/web/resources_test.go index 97b8b030ea142..929008e0d4387 100644 --- a/lib/web/resources_test.go +++ b/lib/web/resources_test.go @@ -122,7 +122,7 @@ spec: desktop: true version: v3 ` - role, err := types.NewRole("roleName", types.RoleSpecV5{ + role, err := types.NewRoleV3("roleName", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{"test"}, }, @@ -167,7 +167,7 @@ func TestGetRoles(t *testing.T) { m := &mockedResourceAPIGetter{} m.mockGetRoles = func(ctx context.Context) ([]types.Role, error) { - role, err := types.NewRole("test", types.RoleSpecV5{ + role, err := types.NewRoleV3("test", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{"test"}, }, diff --git a/lib/web/sessions.go b/lib/web/sessions.go index 405a4ba607ad2..947e2be6dc323 100644 --- a/lib/web/sessions.go +++ b/lib/web/sessions.go @@ -209,9 +209,10 @@ func (c *SessionContext) tryRemoteTLSClient(cluster reversetunnel.RemoteSite) (a // ClientTLSConfig returns client TLS authentication associated // with the web session context func (c *SessionContext) ClientTLSConfig(clusterName ...string) (*tls.Config, error) { + ctx := context.TODO() var certPool *x509.CertPool if len(clusterName) == 0 { - certAuthorities, err := c.parent.proxyClient.GetCertAuthorities(types.HostCA, false) + certAuthorities, err := c.parent.proxyClient.GetCertAuthorities(ctx, types.HostCA, false) if err != nil { return nil, trace.Wrap(err) } @@ -220,7 +221,7 @@ func (c *SessionContext) ClientTLSConfig(clusterName ...string) (*tls.Config, er return nil, trace.Wrap(err) } } else { - certAuthority, err := c.parent.proxyClient.GetCertAuthority(types.CertAuthID{ + certAuthority, err := c.parent.proxyClient.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName[0], }, false) @@ -305,7 +306,8 @@ func (c *SessionContext) GetAgent() (agent.Agent, *ssh.Certificate, error) { } func (c *SessionContext) getCheckers() ([]ssh.PublicKey, error) { - cas, err := c.unsafeCachedAuthClient.GetCertAuthorities(types.HostCA, false) + ctx := context.TODO() + cas, err := c.unsafeCachedAuthClient.GetCertAuthorities(ctx, types.HostCA, false) if err != nil { return nil, trace.Wrap(err) } @@ -826,7 +828,8 @@ func (s *sessionCache) newSessionContextFromSession(session types.WebSession) (* } func (s *sessionCache) tlsConfig(cert, privKey []byte) (*tls.Config, error) { - ca, err := s.proxyClient.GetCertAuthority(types.CertAuthID{ + ctx := context.TODO() + ca, err := s.proxyClient.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: s.clusterName, }, false) diff --git a/lib/web/static_test.go b/lib/web/static_test.go index 79f47fbbc7c98..6ea4d05afc996 100644 --- a/lib/web/static_test.go +++ b/lib/web/static_test.go @@ -18,7 +18,6 @@ package web import ( "io" - "io/ioutil" "os" "strings" @@ -38,7 +37,7 @@ func (s *StaticSuite) TestLocalFS(c *check.C) { f, err := fs.Open("/index.html") c.Assert(err, check.IsNil) - bytes, err := ioutil.ReadAll(f) + bytes, err := io.ReadAll(f) c.Assert(err, check.IsNil) html := string(bytes[:]) @@ -55,7 +54,7 @@ func (s *StaticSuite) TestZipFS(c *check.C) { // test simple full read: f, err := fs.Open("/index.html") c.Assert(err, check.IsNil) - bytes, err := ioutil.ReadAll(f) + bytes, err := io.ReadAll(f) c.Assert(err, check.IsNil) c.Assert(len(bytes), check.Equals, 813) c.Assert(f.Close(), check.IsNil) @@ -69,14 +68,14 @@ func (s *StaticSuite) TestZipFS(c *check.C) { c.Assert(err, check.IsNil) c.Assert(n, check.Equals, int64(10)) - bytes, err = ioutil.ReadAll(f) + bytes, err = io.ReadAll(f) c.Assert(err, check.IsNil) c.Assert(len(bytes), check.Equals, 803) n, err = f.Seek(-50, io.SeekEnd) c.Assert(err, check.IsNil) c.Assert(n, check.Equals, int64(763)) - bytes, err = ioutil.ReadAll(f) + bytes, err = io.ReadAll(f) c.Assert(err, check.IsNil) c.Assert(len(bytes), check.Equals, 50) @@ -85,7 +84,7 @@ func (s *StaticSuite) TestZipFS(c *check.C) { n, err = f.Seek(-50, io.SeekCurrent) c.Assert(err, check.IsNil) c.Assert(n, check.Equals, int64(713)) - bytes, err = ioutil.ReadAll(f) + bytes, err = io.ReadAll(f) c.Assert(err, check.IsNil) c.Assert(len(bytes), check.Equals, 100) } diff --git a/lib/web/ui/perf_test.go b/lib/web/ui/perf_test.go index eb71151b4024e..9e79dfc6233ea 100644 --- a/lib/web/ui/perf_test.go +++ b/lib/web/ui/perf_test.go @@ -19,8 +19,6 @@ package ui import ( "context" "fmt" - "io/ioutil" - "os" "testing" "time" @@ -79,12 +77,8 @@ func BenchmarkGetClusterDetails(b *testing.B) { bk, err = memory.New(memory.Config{}) require.NoError(b, err) } else { - dir, err := ioutil.TempDir("", "teleport") - require.NoError(b, err) - defer os.RemoveAll(dir) - bk, err = lite.NewWithConfig(context.TODO(), lite.Config{ - Path: dir, + Path: b.TempDir(), }) require.NoError(b, err) } diff --git a/lib/web/ui/usercontext.go b/lib/web/ui/usercontext.go index 5e7426ec8daf1..3db8a148db565 100644 --- a/lib/web/ui/usercontext.go +++ b/lib/web/ui/usercontext.go @@ -216,9 +216,6 @@ func NewUserContext(user types.User, userRoles services.RoleSet, features proto. var billingAccess access if features.Cloud { billingAccess = newAccess(userRoles, ctx, types.KindBilling) - - // disable access to preview features - desktopAccess = access{} } logins := getLogins(userRoles) diff --git a/lib/web/ui/usercontext_test.go b/lib/web/ui/usercontext_test.go index 874d5600e0293..9ccab941f65c0 100644 --- a/lib/web/ui/usercontext_test.go +++ b/lib/web/ui/usercontext_test.go @@ -157,7 +157,6 @@ func (s *UserContextSuite) TestNewUserContextCloud(c *check.C) { roleSet := []types.Role{role} allowed := access{true, true, true, true, true} - denied := access{false, false, false, false, false} userContext, err := NewUserContext(user, roleSet, proto.Features{Cloud: true}, true) c.Assert(err, check.IsNil) @@ -186,5 +185,5 @@ func (s *UserContextSuite) TestNewUserContextCloud(c *check.C) { // cloud-specific asserts c.Assert(userContext.ACL.Billing, check.DeepEquals, allowed) - c.Assert(userContext.ACL.Desktops, check.DeepEquals, denied) + c.Assert(userContext.ACL.Desktops, check.DeepEquals, allowed) } diff --git a/rfd/0039-sni-alpn-teleport-proxy-routing.md b/rfd/0039-sni-alpn-teleport-proxy-routing.md index fda09f9dc0fc4..e34ae5eaafad5 100644 --- a/rfd/0039-sni-alpn-teleport-proxy-routing.md +++ b/rfd/0039-sni-alpn-teleport-proxy-routing.md @@ -103,7 +103,7 @@ In order to expose WEB, Kubernetes proxy services in one teleport proxy port, th #### Setting SNI value for kubectl CLI: -`tsh kube login` command generates a local kubeconfig.yaml file used during accessing teleport proxy by kubectl CLI. Additional to the current configuration the tls-server-name failed will be added with appropriate SNI value: +`tsh kube login` command generates a local kubeconfig.yaml file used during accessing teleport proxy by kubectl CLI. Additional to the current configuration the `tls-server-name` field will be added with appropriate SNI value: ```yaml apiVersion: v1 diff --git a/rfd/0043-kubeaccess-multiparty.md b/rfd/0043-kubeaccess-multiparty.md index a9b1cd9140f92..77c0d487da9a1 100644 --- a/rfd/0043-kubeaccess-multiparty.md +++ b/rfd/0043-kubeaccess-multiparty.md @@ -3,65 +3,72 @@ authors: Joel Wejdenstål (jwejdenstal@goteleport.com) state: implemented --- -# RFD 43 - Shared sessions with observers for Kubernetes Access +# RFD 43 - Moderated Sessions ## What -Implement joint observer support for Kubernetes Access with support for configurable conditions similar to those of [RFD 26](https://github.com/gravitational/teleport/blob/2fd6a88800604342bfa6277060b056d8bf0cbfb2/rfd/0026-custom-approval-conditions.md). -Also support defining conditions for required observers in order to initiate and maintain a session. +Implement the ability to define RBAC policies with filters similar to those of [RFD 26](https://github.com/gravitational/teleport/blob/2fd6a88800604342bfa6277060b056d8bf0cbfb2/rfd/0026-custom-approval-conditions.md) that can be used to require others to be present in a session for it to be usable. ## Why -Heavily regulated and security critical industries require that one or more observers with a certain role -are present in Kubernetes Access sessions and viewing it live in order to guarantee that -operator does not perform any mistakes or acts of malice. +Heavily regulated and security-critical industries require that one or more participants with a certain role +are present in SSH and Kubernetes sessions and viewing it live to guarantee that +the operator does not perform any mistakes or acts of malice. -Such observers need to have the power to terminate a session immediately should anything go wrong. +Such participants need to have the power to terminate a session immediately should anything go wrong. To suit everyone this will need a more detailed configuration model based on rules -that can be used to define observers, their powers and when and in what capacity they are required. +that can be used to define observers, their powers, and when and in what capacity they are required. ## Details -### Multiparty sessions +### Terminology -SSH sessions via TSH currently have rich support for sessions with multiple users at once. +This RFD repeatedly refers to some specific nouns of which explanations can be found below: +- Participant: Any user in the session regardless of mode, including the session initiator. +- Initiator: The user that started the session. This user is in peer mode. +- Mode: One of the modes below. Any given participant must be in one mode only. +- Observer: A participant who can only view the session. +- Moderator: A participant who can view and terminate the session at any point. +- Peer: A participant with the ability to view and interact with the session. + +### Multiparty sessions for Kubernetes + +SSH sessions via TSH currently have support for sessions with multiple users at once. This concept is to be extended to Kubernetes Access which will allow us to build additional features on top. -Multiparty sessions shall be implemented by modifying the k8s request proxy forwarder in the `kubernetes_service`. This -approach was chosen as it is a hub that sessions pass through which makes it optimal for multiplexing. +Multiparty sessions shall be implemented by modifying the k8s request proxy forwarder in the `kubernetes_service`. This approach was chosen as it is a hub that sessions pass through which makes it optimal for multiplexing. An approach using multiplexing in the `proxy_service` layer was considered but was ultimately determined to be more complicated -due to the fact that proxies don't handle the final session traffic hop when using Kubernetes Access. +since proxies don't handle the final session traffic hop when using Kubernetes Access. -It will work by adding a multiplexing layer inside the forwarder that similar to the current session recording -functionality, but instead this multiplexes outputs to the session initiator and all observers -but only streams back input from the initiator. +It will be implemented by adding a multiplexing layer inside the forwarder that similar to the current session recording +functionality, but instead this multiplexes output to the session initiator and all participants +but only streams back input from participants with the `peer` mode. -#### Session observers +#### Session participants and require policies -A core feature we need to support is required observers. This will allow cluster administrators to configure +A core feature we need to support is the required participants. This will allow cluster administrators to configure policies that require certain Teleport users of a certain role to be actively monitoring the session. -This feature is useful in security critical environments where multiple people need to witness every action -in the event of severe error or malice and have the ability to halt any erroneous or malicious action. +This feature is useful in security-critical environments where multiple people need to witness every action +in the event of severe error or malice and can halt any erroneous or malicious action. #### Session states -By default, a `tsh kube exec` and `kubectl attach` request will go through as usual if no policies are defined. If a policy like the one above is defined the session will be put in a pending state +By default, a `tsh kube exec` and `tsh ssh` request will go through as usual if no policies are defined. If a policy like the one above is defined the session will be put in a pending state until the required viewers have joined. Sessions can have 3 possible states: - `PENDING`\ - When a session is in a `PENDING` state, the connection to the pod from the proxy has not yet started - and all users are shown a default message informing them that the session is pending, current participants - and who else is required for the session to start. + When a session is in a `PENDING` state, the connection to the pod from the proxy has not yet started + and all users are shown a default message informing them that the session is pending. - `RUNNING`\ A `RUNNING` session behaves like a normal multiparty session. `stdout`, `stdin` and `stdout` are mapped as usual - and the pod can be interacted with. + and the pod can be interacted with. - `TERMINATED`\ - A session becomes `TERMINATED` once the shell spawned inside the pod quits or is forcefully terminated by one of the session participants. + A session becomes `TERMINATED` once the shell spawned inside the pod quits or is forcefully terminated by one of the session participants. All sessions begin in the `PENDING` state and can change states based on the following transitions: @@ -72,13 +79,13 @@ the session transitions to a `RUNNING` state. This involves initiating the conne and setting up the shell. Finally, all clients are multiplexed onto the correct streams as described previously. -Only the session initiator is able to make input, observers are not connected to the input stream +Only the session initiator can make input, observers are not connected to the input stream and may only view stdout/stderr and terminate the session. ##### Transition 2: `RUNNING -> TERMINATED` -When the shell process created on the pod is terminated, the session transitions to a `TERMINATED` state and all clients -are disconnected as per standard `kubectl` behaviour. +When the shell process created on the pod is terminated, the session transitions to a `TERMINATED` state, and all clients +are disconnected as per standard `kubectl` behavior. ##### Transition 3: `RUNNING -> TERMINATED` @@ -96,7 +103,7 @@ Here, the connection is frozen for a configurable amount of time as a sort of gr ##### Transition 5: `PENDING -> TERMINATED` After a grace period has elapsed in a session in a session that previously was in a `RUNNING` -state, the session is automatically terminated. This can be cancelled by having the required observers +state, the session is automatically terminated. This can be canceled by having the required observers join back in which transitions the session back to `RUNNING`. ##### Transition 6: `PENDING -> TERMINATED` @@ -107,22 +114,22 @@ connection to the pod exists at this time. #### UI/UX -The initial implementation of multiparty sessions on Kubernetes access will only be supported via CLI access for implementation simplicity. +The initial implementation of multiparty sessions for SSH Kubernetes access will only be supported via CLI access for implementation simplicity. -Terminating the `kubectl` process that started the session terminates the session. Terminating an observer `tsh` process +Terminating the `tsh kube exec` or `tsh ssh` process that started the session terminates the session. Terminating a participant `tsh` process disconnects the observer from the session and applies relevant state transitions if any. -Terminating the session from a observer `tsh` instance can be done with the key combination `CTRL-T` +Terminating the session from a moderator `tsh` instance can be done with the T hotkey. ##### Session creation Session creation can happen with the existing flow using `kubectl exec` -but the wrapper command `tsh kube exec --invite=bob@example.com,eve@foo.net --reason="Need to fix this pod" -- database_pod -- /bin/bash`. This subcommand allows you to invite one or more accounts which will receive a notification saying they are invited. An arbitrary string may also be provided as a reason +but the wrapper command `tsh kube exec --invite=bob@example.com,eve@foo.net --reason="Need to fix this pod" -- database_pod -- /bin/bash`. This subcommand allows you to invite one or more accounts that will receive a notification saying they are invited. An arbitrary string may also be provided as a reason for the session invite, it could for example be used to say what the purpose of the session is. ##### Session join -`kubectl` itself has no concept of multiparty sessions. This means that we cannot easily use +Kubernetes itself has no concept of multiparty sessions. This means that we cannot easily use its built-in facilities for support session joining. To make this process easier for the user. I propose extending the current `tsh join` command @@ -132,58 +139,98 @@ to an ongoing session and displays stdout/stderr. ##### MFA tap If the standard `per_session_mfa` option is enabled for a role then MFA tap input via Yubikey or other is required for the participant to be considered active. -This requirement is on an interval of 1 minute. When there is 15 seconds left, an alert is printed to the console. +This requirement is on an interval of 1 minute. When there are 15 seconds left, an alert is printed to the console. ``` -Teleport >> Please tap your MFA key within 15 seconds. +Teleport > Please tap your MFA key within 15 seconds. ``` -If tap is made after the alert, the follwing message is shown: +If the tap is made after the alert, the following message is shown: + +``` +Teleport > MFA tap received. +``` + +#### Broadcast messages + +For TTY-enabled SSH and Kubernetes sessions that require additional participants regardless of mode as per a require policy, Teleport will now inject broadcast messages into the session to notify participants +about the state of the session. No messages will be shown in sessions that do not require additional participants. + +This is needed so that participants are aware of what is currently happening in the session +for security and usability reasons. + +Each broadcast message is prefixed with `Teleport > ` to indicate that this message +is injected by Teleport and not from the originating shell. + +There are 10 kinds of broadcast messages: +- `Creating session with uuid ...`: Sent on session creation. +- `User joined the session.`: Sent when a user joins the session. +- `User left the session.`: Sent when a user leaves the session. +- `Connecting to $HOSTNAME over $PROTOCOL`: Sent when the session is launched and thus transferred to a normal shell. +- `Session closed.`: Sent when the session is closed due to the termination of the shell. +- `Session terminated by a moderator`: Sent when moderator forcefully terminates a session. +- `Session paused, waiting for additional participants...`: Sent when a session is paused due to lack of required participants. +- `Session resumed.`: Sent when a session has the required participants and is resumed. +- `Please tap your MFA key within 15 seconds.` and `MFA tap received.` are messages shown to moderators in sessions that are MFA-presence enabled. +- `Session terminated: participant requirements not met`: Sent when a session is terminated due to lack of required participants and when the pause mode is not enabled. + +##### Verbose participant requirements + +The optional flag `--participant-req` may be passed `tsh kube exec` or `tsh ssh` when the session is started to provide a verbose output of the participant requirements. By default, we avoid this because the output is large and may be overwhelming but the option is provided for users who wish to see exactly what the requirements for starting a session are. + +In this case, the `Session paused, waiting for additional participants...` message will be replaced by the message below. ``` -Teleport >> MFA tap received. +Teleport > Session paused, waiting for additional participants: + role-1: + one-of: + - 2x `contains(user.roles, "auditor")` + - 1x `contains(user.roles, "admin")` + role-2: + one-of: + - 1x `contains(user.roles, "cs-overwatch")` ``` ##### Example -This example illustrates how a group 3 users of which Alice is the initiator and Eve and Ben are two observers -start a multiparty session. Below is a a series of events that happen that include what each user sees and what -they do. +This example illustrates how a group of 3 users of which Alice is the initiator and Eve and Ben are two observers start a multiparty session. Below is a series of events that happen that include what each user sees and actions taken. -- Alice initiates an interactive session to a pod: `tsh kube exec -st --invite=ben@foo.net,eve@foo.net,alice@foo.net database_pod /bin/bash` +- Alice initiates an interactive session to a pod: `tsh kube exec -st --invite=ben@foo.net,eve@foo.net,alice@foo.net redis-bastion /bin/bash` - Alice sees: ``` -Creating session with uuid ... -This session requires moderator. Waiting for others to join: -- role: auditor-role x2 +Teleport > Creating session with uuid ... +Teleport > User Alice joined the session. +Teleport > This session requires additional participants to start... ``` - Eve joins the session with `tsh kube join ` and sees: ``` Please tap MFA key to continue... ``` - Eve taps MFA -- Alice and Eve sees: +- Alice and Eve sees +: ``` -Creating session with uuid ... -This session requires moderator. Waiting for others to join: -- role: auditor-role x1 -Events: -- User Eve joined the session. +Teleport > Creating session with uuid ... +Teleport > User Alice joined the session. +Teleport > This session requires additional participants to start... +Teleport > User Eve joined the session. ``` - Ben joins the session with `tsh kube join ` and sees: ``` Please tap MFA key to continue... ``` - Ben taps MFA -- Alice, Eve and Ben sees +- Alice, Eve, and Ben sees ``` -Creating session with uuid ... -Session starting... -Events: -- User Eve joined the session. -- User Ben joined the session +Teleport > Creating session with uuid ... +Teleport > User Alice joined the session. +Teleport > This session requires additional participants to start... +Teleport > User Eve joined the session. +Teleport > User Ben joined the session. +Teleport > Connecting to redis-bastion over Kubernetes... +redis-bastion@localhost $ ``` -- The connection to the pod is made and each the session turns into a normal shell. +- The connection to the pod is made and each session turns into a normal shell. #### Session invites and notifications @@ -198,12 +245,12 @@ session resource which allows Teleport clients and plugins to detect notify them ##### Session resource -There currently isn't a general purpose session resource in Teleport that's suitable. +There currently isn't a general-purpose session resource in Teleport that's suitable. therefore I suggest that this shall be added. This will be initially used for tracking Kubernetes sessions but is compatible with all current and future session types. This resource is stored centrally in the backend and is used for storing and tracking metadata of active -sessions. Detailed runtime information needed to join such as the TTY size is stored in memory on the the multiplexing node. +sessions. Detailed runtime information needed to join such as the TTY size is stored in memory on the multiplexing node. This effectively replaces the resource defined [here](https://github.com/gravitational/teleport/blob/master/lib/session/session.go). @@ -302,7 +349,7 @@ enum SessionState { // to start the session. SessionStatePending = 0; - // Running variant represents a session that has had it's criteria for starting + // Running variant represents a session that has had its criteria for starting // fulfilled at least once and has transitioned to a RUNNING state. SessionStateRunning = 1; @@ -316,14 +363,14 @@ enum SessionState { Instead of having fixed fields for specifying values such as required session viewers and roles this model centers around conditional allow rules and filters. It is implemented as a bi-directional mapping between the role of the session initiator and the roles of the other session participants. -Roles can have `require_session_join` rule under `allow` containing requirements for session participants -before a session may be started with privilege access to nodes that the role provides. +Roles can have a `require_session_join` rule under `allow` containing requirements for session participants +before a session may be started with privileged access to nodes that the role provides. -Roles can also have an `join_sessions` rule under `allow` specifying which roles -and session types that that the role grants privileges to join. +Roles can also have a `join_sessions` rule under `allow` specifying which roles +and session types that the role grants privileges to join. We will only initially support the modes `moderator` for Kubernetes Access and `peer` for SSH sessions. -An `observer` mode also exists which only grants access to view but not terminate an ongoing session. +An `observer` mode also exists which only grants access to view but does not terminate an ongoing session. Imagine you have 4 roles: - `prod-access` @@ -332,7 +379,7 @@ Imagine you have 4 roles: - `maintenance-observer` And these requirements: -- `prod-access` should be able to start sessions of any type with either one `senior-dev` observeror two `dev` observers. +- `prod-access` should be able to start sessions of any type with either one `senior-dev` participant two `dev` participants. - `senior-dev` should be able to start sessions of any type without oversight. - `customer-db-maintenance` needs oversight by one `maintenance-observer` on `ssh` type sessions. @@ -410,7 +457,7 @@ To make it more workable, the language has been slimmed down significantly to ha ##### Provided variables -- `participant` +- `user` ```json { "traits": "map", diff --git a/rfd/0046-database-access-config.md b/rfd/0046-database-access-config.md index 9758f2a198121..fa83487ab3b8c 100644 --- a/rfd/0046-database-access-config.md +++ b/rfd/0046-database-access-config.md @@ -1,6 +1,6 @@ --- -authors: Roman Tkachenko (roman@goteleport.com) -state: draft +authors: Roman Tkachenko (roman@goteleport.com), Gabriel Corado (gabriel.oliveira@goteleport.com) +state: implemented --- # RFD 46 - Database access configurator @@ -26,15 +26,17 @@ this initial IAM bootstrap besides documentation guides. The same applies to self-hosted databases and other clouds. Instructions vary greatly between different database deployment models (self-hosted, cloud) and authentication types (X.509, IAM, AD) and having a tool that can guide users -through the initial setup would significanly improve the getting started +through the initial setup would significantly improve the getting started experience. ## Scope -The initial implementation will focus on bootstrapping the agent's AWS IAM -policies but we'll make the effort to design the CLI interface in a way which -would allow for extensibility to support other scenarios (self-hosted, other -clouds and auth types). +The initial implementation will focus on: + +1. Generate database agent configuration with samples; +2. Bootstrap the agent's AWS IAM policies but we'll make the effort to design + the CLI interface in a way which would allow for extensibility to support + other scenarios (self-hosted, other clouds and auth types); ## Prior art @@ -64,14 +66,284 @@ $ clusterawsadm bootstrap iam print-policy --document AWSIAMManagedPolicyControl Teleport has a `teleport configure` command that generates a sample config file. -Database agent bootstrap commands will reside under `teleport db configure` -family of subcommands. This aligns with our strategy of placing commands related -to a particular service under its own subcommand namespace. +Database agent bootstrap commands will reside under teleport db configure family +of subcommands. This aligns with our strategy of placing commands related to a +particular service under its subcommand namespace. It will provide subcommands +for generating configuration files and bootstrapping database agents’ necessary +configurations, for example, setting up IAM permissions for agents to proxy +AWS-hosted databases. + +### Create config subcommand + +Similar to `teleport configure` but focused on configurations for Database +agents. This command will generate a configuration file. It can also provide +some examples of configuration, for instance, self-hosted databases setup. + +There will be flags to enable features like Aurora/RDS auto-discovery and +dynamic registration. Each sample will show the required options with some +explanation on how to fill them and documentation links. + +It will also provide flags to configure a specific database (similar to the +flags present on the `teleport db start` command). When executed with these +flags, the samples will not be included, only the static configuration of a +single database. + +```bash +teleport db configure create [--proxy] [--token=] [--discovery-rds=] [--discovery-redshift] [--output=] [--ca-pin=] [--name=] [--protocol=] [--uri=] [--labels=] +``` + +| Flag | Description | +| ------------------------------- | ----------- | +| `--proxy` | Teleport proxy address to connect to [0.0.0.0:3080]. | +| `--token` | Invitation token to register with an auth server [none]. | +| `--discovery-rds` | List of AWS regions the agent will discover for RDS/Aurora instances. | +| `--discovery-redshift` | List of AWS regions the agent will discover for Redshift instances. | +| `--output` | Write to stdout with -o=stdout, default config file with -o=file or custom path with -o=file:///path | +| `--ca-pin` | CA pin to validate the auth server (can be repeated for multiple pins). | +| `--name` | Name of the proxied database. | +| `--protocol` | Proxied database protocol. Supported are: [postgres mysql mongodb cockroachdb]. | +| `--uri` | Address the proxied database is reachable at. | +| `--description` | Description of the proxied database. Default: "" | +| `--labels` | Comma-separated list of labels for the database, for example env=dev,dept=it | + +* When configuring a single database, `name`, `protocol` and `uri` are required; + +**Examples:** + +None of the flags are required. Running the command without flags should output +a configuration with samples. + +```bash +# will generate a configuration file for the database agent. +$ teleport db configure create + +# +# Teleport database agent configuration file. +# Configuration reference: https://goteleport.com/docs/database-access/reference/configuration/ +# +version: v2 +teleport: + nodename: localhost + data_dir: /var/lib/teleport + auth_token: /tmp/token + auth_servers: + - 127.0.0.1:3025 +db_service: + enabled: "yes" + # Matchers for database resources created with "tctl create" command. + # For more information: https://goteleport.com/docs/database-access/guides/dynamic-registration/ + resources: + - labels: + "*": "*" + # Lists statically registered databases proxied by this agent. + # databases: + # # RDS database static configuration. + # # RDS/Aurora databases Auto-discovery reference: https://goteleport.com/docs/database-access/guides/rds/ + # - name: rds + # description: AWS RDS/Aurora instance configuration example. + # # Supported protocols for RDS/Aurora: "postgres" or "mysql" + # protocol: postgres + # # Database connection endpoint. Must be reachable from Database Service. + # uri: rds-instance-1.abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432 + # # AWS specific configuration. + # aws: + # # Region the database is deployed in. + # region: us-west-1 + # # RDS/Aurora specific configuration. + # rds: + # # RDS Instance ID. Only present on RDS databases. + # instance_id: rds-instance-1 + # # Aurora database static configuration. + # # RDS/Aurora databases Auto-discovery reference: https://goteleport.com/docs/database-access/guides/rds/ + # - name: aurora + # description: AWS Aurora cluster configuration example. + # # Supported protocols for RDS/Aurora: "postgres" or "mysql" + # protocol: postgres + # # Database connection endpoint. Must be reachable from Database Service. + # uri: aurora-cluster-1.abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432 + # # AWS specific configuration. + # aws: + # # Region the database is deployed in. + # region: us-west-1 + # # RDS/Aurora specific configuration. + # rds: + # # Aurora Cluster ID. Only present on Aurora databases. + # cluster_id: aurora-cluster-1 + # # Redshift database static configuration. + # # For more information: https://goteleport.com/docs/database-access/guides/postgres-redshift/ + # - name: redshift + # description: AWS Redshift cluster configuration example. + # # Supported protocols for Redshift: "postgres". + # protocol: postgres + # # Database connection endpoint. Must be reachable from Database service. + # uri: redshift-cluster-example-1.abcdefghijklmnop.us-west-1.redshift.amazonaws.com:5439 + # # AWS specific configuration. + # aws: + # # Region the database is deployed in. + # region: us-west-1 + # # Redshift specific configuration. + # redshift: + # # Redshift Cluster ID. + # cluster_id: redshift-cluster-example-1 + # # Self-hosted static configuration. + # - name: self-hosted + # description: Self-hosted database configuration. + # # Supported protocols for self-hosted: postgres, mysql, mongodb, cockroachdb. + # protocol: postgres + # # Database connection endpoint. Must be reachable from Database service. + # uri: database.example.com:5432 +auth_service: + enabled: "no" +ssh_service: + enabled: "no" +proxy_service: + enabled: "no" +``` + +Similarly to configure command, it will also support writing the configuration +directly to a file. + +```bash +# write to the default file location. +$ teleport db configure create --output file + +Wrote config to file "/etc/teleport.yaml". Now you can start the server. Happy Teleporting! +``` + +```bash +# write to the provided file location. +$ teleport db configure create --output file:///teleport.yaml + +Wrote config to file "/teleport.yaml". Now you can start the server. Happy Teleporting! +``` + +Configure a single database. + +```bash +# generates a configuration for a Postgres database. +$ teleport db configure create \ + --token=/tmp/token \ + --auth-server=localhost:3025 \ + --name=sample-db \ + --protocol=postgres \ + --uri=postgres://localhost:5432 +``` + +### Bootstrap permissions command -The "configure" command will provide subcommands for configuring database agents -in different environments. This RFD focuses on `aws` subcommands that deal with -configuring IAM for agents that proxy AWS-hosted databases (RDS, Aurora, -Redshift). +Subcommand will bootstrap the necessary configuration for the database agent. It +reads the provided agent configuration to determine what will be bootstrapped. + +```bash +$ teleport db configure bootstrap [-c=,--config=] [--manual] [--policy-name=] [--attach-to-user=] [--attach-to-role=] [--confirm] +``` + +| Flag | Description | +| ------------------ | ----------- | +| `-c, --config` | Path to the database agent configuration file. Default: "/etc/teleport.yaml". | +| `--manual` | When executed in "manual" mode, it will print the instructions to complete the configuration instead of applying them directly. | +| `--policy-name` | Name of the Teleport Database agent policy. Default: "DatabaseAccess". | +| `--attach-to-role` | Role name to attach policy to. Mutually exclusive with --attach-to-user. If none of the attach-to flags is provided, the command will try to attach the policy to the current user/role based on the credentials.| +| `--attach-to-user` | User name to attach policy to. Mutually exclusive with --attach-to-role. If none of the attach-to flags is provided, the command will try to attach the policy to the current user/role based on the credentials. | +| `--confirm` | Do not prompt the user and auto-confirm all actions. Defaults to false. | + +**Examples:** + +```bash +# When the database configuration has RDS/Aurora or Redshift auto-discovery. +# Configure AWS provider attaching the created policies to “SampleUser” when confirmation is given. +$ teleport db configure bootstrap --attach-to-user SampleUser +Reading configuration at "/etc/teleport.yaml"... + +AWS +1. Create IAM policy "DatabaseAccess": +{ ...policy document... } + +2. Create IAM boundary policy "DatabaseAccessBoundary": +{ ...policy document... } + +3. Attach policy and boundary to "SampleUser". + +Confirm actions? [y/N] y +✅[AWS] Creating IAM Policy "DatabaseAccess"... done. +✅[AWS] Creating IAM policy boundary "DatabaseAcessBoundary"... done. +✅[AWS] Attaching IAM policy and boundary to IAM user "SampleUser"... done. +``` + +```bash +# When the database configuration has RDS/Aurora or Redshift auto-discovery. +# Configure AWS provider attaching the created policies to “SampleUser”. +$ teleport db configure bootstrap --attach-to-user SampleUser --confirm +Reading configuration at "/etc/teleport.yaml"... + +AWS +1. Create IAM policy "DatabaseAccess": +{ ...policy document... } + +2. Create IAM boundary policy "DatabaseAccessBoundary": +{ ...policy document... } + +3. Attach policy and boundary to "SampleUser". + +✅[AWS] Creating IAM Policy "DatabaseAccess"... done. +✅[AWS] Creating IAM policy boundary "DatabaseAcessBoundary"... done. +✅[AWS] Attaching IAM policy and boundary to IAM user "SampleUser"... done. +``` + +```bash +# When the database configuration has RDS/Aurora or Redshift auto-discovery. +# Show instructions on how to configure AWS. +$ teleport db configure bootstrap --manual --attach-to-user SampleUser +Reading configuration at "/etc/teleport.yaml"... +Running in "manual" mode, there will be presented the instructions to bootstrap the agent. + +AWS +1. Create IAM policy "DatabaseAccess": +{ ...policy document... } + +2. Create IAM boundary policy "DatabaseAccessBoundary": +{ ...policy document... } + +3. Attach policy and boundary to "username". +``` + +```bash +# When the database agent has a configuration that requires no bootstrapping. +$ teleport db configure bootstrap +Reading configuration at "/etc/teleport.yaml"... +Nothing to be bootstrapped. +``` + +#### Error handling: + +No external calls will be issued when running with `--mode=manual`, meaning the +command won't fail. In automatic mode, however, if the command fails, the error +will be presented. + +**Examples:** + +```bash +# Fails to attach the created policy into the user. +$ teleport db configure bootstrap --attach-to-user SampleUser +Reading configuration at "/etc/teleport.yaml"... + +AWS +1. Create IAM policy "DatabaseAccess": +{ ...policy document... } + +2. Create IAM boundary policy "DatabaseAccessBoundary": +{ ...policy document... } + +3. Attach policy and boundary to "SampleUser". + +Confirm actions? [y/N] y +✅[AWS] Creating IAM Policy "DatabaseAccess"... done. +✅[AWS] Creating IAM policy boundary "DatabaseAcessBoundary"... done. +❌[AWS] Attaching IAM policy and boundary to IAM user "SampleUser"... failed. +Failure reason: unable to find user "SampleUser". +``` + +### AWS subcommand ```bash $ teleport db configure aws ... @@ -92,7 +364,7 @@ $ teleport db configure aws create-iam $ teleport db configure aws print-iam ``` -### Automatic mode +#### Automatic mode To use automatic mode, user needs to run `teleport db configure aws` command on the machine with permissions to create and attach IAM policies. @@ -111,14 +383,14 @@ The command will: $ teleport db configure aws create-iam [--name=] [--types=] [--attach] [--role=|--user=] [--confirm] ``` -| Flag | Description | -| ------------- | ----------- | -| `--name` | Created policy name. Defaults to empty. Will be auto-generated if not provided. | -| `--types` | Permissions to include in the policy. Any of `rds`, `aurora`, `redshift`. Defaults to none. | -| `--attach` | Try to attach the policy to the IAM identity. Defaults to `true`. | -| `--role` | IAM role name to attach policy to. Mutually exclusive with `--user`. Defaults to empty. | -| `--user` | IAM user name to attach policy to. Mutually exclusive with `--role`. Defaults to empty. | -| `--confirm` | Do not prompt user and auto-confirm all actions. Defaults to `false`. | +| Flag | Description | +| ----------- | ----------- | +| `--name` | Created policy name. Defaults to empty. Will be auto-generated if not provided. | +| `--types` | Permissions to include in the policy. Any of `rds`, `aurora`, `redshift`. Defaults to none. | +| `--attach` | Try to attach the policy to the IAM identity. Defaults to `true`. | +| `--role` | IAM role name to attach policy to. Mutually exclusive with `--user`. Defaults to empty. | +| `--user` | IAM user name to attach policy to. Mutually exclusive with `--role`. Defaults to empty. | +| `--confirm` | Do not prompt user and auto-confirm all actions. Defaults to `false`. | * At least one type must be specified via `--types` flag. * If neither `--role` nor `--user` is specified, tries to attach the policy to the identity of current AWS credentials. @@ -171,7 +443,7 @@ Example output: ✅ Attaching IAM policy and boundary to DatabaseAgentRole... done ``` -#### Error handling +##### Error handling In case the automatic mode encounters a permission or any other kind of error (e.g. policy doesn't allow creating or attaching other policies), it falls back @@ -180,7 +452,7 @@ to manual mode where it prints: - IAM permissions it requires to be able to create and attach these policies. - Policy and boundary in case user wants to create them themselves. -### Manual mode +#### Manual mode In manual mode configurator only prints the policy and boundary. User is responsible for creating and attaching them to IAM identity themselves. @@ -193,13 +465,13 @@ respective policy/boundary documents, suitable for automation tools. $ teleport db configure aws print-iam [--types=] [--role=|--user=] [--policy|--boundary] ``` -| Flag | Description | -| ------------- | ----------- | -| `--types` | Permissions to include in the policy. Any of `rds`, `aurora`, `redshift`. Defaults to none. | -| `--role` | IAM role name to attach policy to. Mutually exclusive with `--user`. Defaults to empty. | -| `--user` | IAM user name to attach policy to. Mutually exclusive with `--role`. Defaults to empty. | -| `--policy` | Only print IAM policy document. Defaults to `false`. | -| `--boundary` | Only print IAM boundary policy document. Defaults to `false`. | +| Flag | Description | +| ------------ | ----------- | +| `--types` | Permissions to include in the policy. Any of `rds`, `aurora`, `redshift`. Defaults to none. | +| `--role` | IAM role name to attach policy to. Mutually exclusive with `--user`. Defaults to empty. | +| `--user` | IAM user name to attach policy to. Mutually exclusive with `--role`. Defaults to empty. | +| `--policy` | Only print IAM policy document. Defaults to `false`. | +| `--boundary` | Only print IAM boundary policy document. Defaults to `false`. | **Examples:** @@ -253,3 +525,78 @@ $ teleport db configure azure ... $ teleport db configure postgres ... $ teleport db configure mysql ... ``` + +## Security + +The bootstrap command doesn’t communicate with Teleport instances. However, it +makes calls to the AWS API. As mentioned earlier, the command won’t request the +AWS credentials, and it relies on the [default AWS credential provider +chain](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials). + +### Necessary permissions +When executed in "automatic" mode, the bootstrap command will require the +current identity to have the following permissions: + +**Policy-related:** +- `iam:GetPolicy` wildcard ("*") or the policy to be retrieved: Used to identify + if a policy already exists to update it. +- `iam.ListPolicyVersions`: wildcard ("*") or the policy to be retrieved: Used + to identify if the policy exceeds the policy versions limit; +- `iam:CreatePolicy` wildcard ("*") or policy that will be created: Used when + the policy doesn’t exist. +- `iam:DeletePolicyVersion` wildcard ("*") or policy that will be created: Used + when the policy exceeds the version’s limit. It will then delete the oldest + non-default version. +- `iam:CreatePolicyVersion` wildcard ("*") or policy that will be created: Used + when the policy already exists and will be updated; + +**Identity-related:** +- `iam:AttachUserPolicy` wildcard ("*") or provided user identity: Needed to + attach the policy to the user. Only necessary when attaching policies to a + user; +- `iam:AttachRolePolicy` wildcard ("*") or provided role identity: Needed to + attach the policy to the role. Only necessary when attaching policies to a + role; +- `iam:PutUserPermissionsBoundary` wildcard ("*") or provided user identity: + Needed to attach boundary policy to the user. Only necessary when attaching + policies to a user; +- `iam:PutRolePermissionsBoundary` wildcard ("*") or provided role identity: + Needed to attach boundary policy to the role. Only necessary when attaching + policies to a role; + +### IAM policies +The IAM policies created by the bootstrap command will follow the AWS +recommendation of [least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege), +meaning that the Teleport database access policies will be granted permissions +based on the agent configuration. For example, the policy won’t include +RDS/Aurora permissions if the agent only has Redshift databases configured. + +#### RDS databases and Aurora clusters +If the configuration has any static RDS/Aurora database or auto-discovery +enabled, the following permissions will be added: + +- `rds:DescribeDBInstances`: Is a list operation, only supporting wildcard resources; +- `rds:ModifyDBInstance`: Since the databases that will be modified are only + known during the discovery process or configured using dynamic registration, + we need wildcard access; +- `rds:DescribeDBClusters`: Is a list operation, only supporting wildcard + resources; +- `rds:ModifyDBCluster`: Since the clusters that will be modified are only known + during the discovery process or configured using dynamic registration, we + need wildcard access; + +##### Boundary +- `rds-db:connect`: The list of database/clusters the Teleport agent will + connect to is only known after the discovery; we need wildcard access; + +#### Redshift clusters +If the configuration has any static Redshift cluster or auto-discovery enabled, +the following permissions will be added: + +- `redshift:DescribeClusters`: Is a list operation, only supporting wildcard + resources. + +##### Boundary +- `redshift:GetClusterCredentials`: The list of clusters the Teleport agent will + get credentials to is only known after the discovery or configured using + dynamic registration; we need wildcard access; diff --git a/rfd/0056-sql-backend.md b/rfd/0056-sql-backend.md new file mode 100644 index 0000000000000..d4d792611703c --- /dev/null +++ b/rfd/0056-sql-backend.md @@ -0,0 +1,137 @@ +--- +authors: Jim Bishopp (jim@goteleport.com) +state: draft +--- + +# RFD 56 - SQL Backend + + +## What + +A Teleport Backend is a pluggable interface for storing core cluster state. +This RFD proposes the addition of a new backend supporting SQL database platforms. +The initial supported platforms for the SQL Backend will be PostgreSQL and +CockroachDB. Support will include self-hosted PostgreSQL/CockroachDB or +hosting the database on a cloud provider platform (AWS/GCP). + + +## Why + +Supporting a SQL Backend reduces onboarding time for Teleport customers by +allowing them to use existing infrastructure. + + +## Scope + +This RFD focuses on implementing a SQL Backend for PostgreSQL and CockroachDB +where the database is either self-hosted or cloud-hosted. The implementation's +design will be extensible to allow future work that supports other SQL databases +such as MySQL. + +Cloud-hosted configurations will support AWS RDS/Aurora/Redshift and GCP Cloud SQL. + +The implementation will support connecting to a single endpoint. Failover to +a secondary endpoint will not be supported but may be considered in a future +proposal. + + +## Authentication + +Self-hosted configurations will require mutual TLS authentication using +user-generated client certificates. The user's CA, client certificate, and +client key must be added to the Teleport configuration file. Users must ensure +the provided CA is trusted by the host where the Teleport authentication server +is running. + +AWS and GCP cloud-hosted configurations require IAM. Teleport uses the +default credential provider for both AWS and GCP to authenticate using IAM. + +Mutual TLS authentication is optionally supported for GCP Cloud SQL. +Paths to a client certificate and key must be added to the Teleport +configuration file to enable mTLS. + + +## UX + +Teleport users must first configure the instance and database where Teleport will +store its data. A new database instance and user must be created. The new user +should be granted ownership of the new database and have the ability to login. +And cloud-hosted configurations must configure and enable IAM. + +Once the database instance and user are created, Teleport users must enable the +SQL Backend by configuring the storage section in the Teleport configuration +file. Setting the storage type to either `postgres` or `cockroachdb` enables the +SQL Backend. Additional configurations may apply depending on whether the +configuration is for self-hosted or cloud-hosted environments. + +```yaml +teleport: + storage: + # Type of storage backend (postgres or cockroachdb). + type: postgres + + # Database connection address. + addr: "postgres.example.com:5432" + + # Optional database name that Teleport will use to store its data. + # Defaults to "teleport". The database must not be shared. + database: "teleport" + + # TLS validation and mutual authentication. + tls: + # Path to the CA file that Teleport will use to verify TLS connections. + ca_file: /var/lib/teleport/backend.ca.crt + + # Paths to the client certificate and key Teleport will use for mutual + # TLS authentication. Required for self-hosted configurations. Optional + # for GCP Cloud SQL. Not supported for AWS. + client_cert_file: /var/lib/teleport/backend-client.crt + client_key_file: /var/lib/teleport/backend-client.key + + # AWS specific configuration, only required for RDS/Aurora/Redshift. + aws: + # Region the database is deployed in. + region: "us-east-1" + + # Redshift specific configuration (postgres only). + redshift: + # Redshift cluster identifier. + cluster_id: "redshift-cluster-1" + + # GCP specific configuration, only required for Cloud SQL. + gcp: + # GCP project ID. + project_id: "xxx-1234" + # Cloud SQL instance ID. + instance_id: "example" +``` + +## Implementation Details + +Users can set the Teleport storage type to either `postgres` or `cockroachdb`. +The initial implementation will use the same PostgreSQL driver for both settings. + +Future iterations of Teleport will be able to easily create a unique `cockroachdb` +driver implementation if CockroachDB specific functionality is desired, such as +using hash-sharded indexes, which are not available in PostgreSQL. + +### Transaction Isolation + +One difference between CockroachDB and PostgreSQL is how they handle transaction +isolation. CockroachDB [always uses `SERIALIZABLE` isolation][1], which is the +strongest of the [four transaction isolation levels][2]. + +The `SERIALIZABLE` isolation level guarantees that even though transactions may +execute in parallel, the result is the same as if they had executed one at a time, +without any concurrency. + +The SQL Backend will enforce `SERIALIZABLE` transaction isolation for all database +platforms. The trade-off is data consistency versus slower execution and the need +to retry transactions when a conflict occurs. Teleport's Backend interface assumes +consistency. E.g. the `Create` function requires the new record does not exist, and +`CompareAndSwap` requires the previous record has a specific value. Using +`SERIALIZABLE` is the desirable isolation level for the majority of Teleport Backend +functionality. + +[1]: https://www.cockroachlabs.com/docs/stable/demo-serializable.html +[2]: https://en.wikipedia.org/wiki/Isolation_(database_systems)#Isolation_levels diff --git a/rfd/0060-gRPC-backend.md b/rfd/0060-gRPC-backend.md new file mode 100644 index 0000000000000..c5e9d7ff3443e --- /dev/null +++ b/rfd/0060-gRPC-backend.md @@ -0,0 +1,200 @@ +--- +authors: Michael McAllister (michael.mcallister@goteleport.com) +state: draft +--- + +# RFD 60 - gRPC Backend + +## What + +A new [backend](https://github.com/gravitational/teleport/tree/v8.3.1/lib/backend) that sends requests to a user defined gRPC server for persistence. Initially targeting cluster state with audit events to come later (in a subsequent RFD) + +## Why + +As Cloud-Hosted Teleport continues to grow, so does our need to have greater flexibility on not only what persistence layer we use (be it currently supported backends like DynamoDB, or otherwise) but _how_ these are implemented. + +Take for instance, the current implementation of the [DynamoDB backend](https://github.com/gravitational/teleport/tree/v8.3.1/lib/backend/dynamo) which allows in its configuration to specify the table name, but does not presently have the ability to customize the Partition key. This means that it's not possible to co-locate multiple installations of Teleport within the same DynamoDB table, and as a result are bounded by [AWS account limits](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html#limits-tables) when deploying multiple installations. + +Other backend solutions have their own challenges regarding authentication and authorization. For instance, most databases have limits on the number of users/roles that you can define which we'd need to leverage to ensure strong data isolation between each deployment. Using an authentication method that would allow one tenant to access another tenants data would be a non-option. + +Implementing a gRPC client within Teleport allows not only the Cloud team, but external developers to implement: + - A custom persistence layer that may not be supported by Teleport natively + - Custom caching logic, connection pooling, etc. + - Custom database schema, partition scheme, etc. + - Custom authentication and authorization (mTLS, LDAP, etc.) + +## Details + +### Scope + +It is proposed that the Teleport core codebase hold the protocol buffer definitions and the corresponding client. + +To keep this RFD brief the Cloud teams (aspirational) implementation of the corresponding gRPC server is out of scope of this documentation and not discussed in detail, it will also be wholly owned by the Cloud team and not apart of the core codebase. + +Additionally audit events are initially outside of scope, but will be implemented at a later date. + +### Protobuf Definition + +The proposed protobuf definition aims to mirror the interface that is defined in [backend.go](https://github.com/gravitational/teleport/blob/cf162af679f3c136b0cc5a7c5bfcd8bba14afdaa/lib/backend/backend.go#L41-L91) + +```protobuf +message Item { + // Key is a key of the key value item + bytes key = 1; + + // Value is a value of the key value item + bytes value = 2; + + // Expires is an optional record expiry time + google.protobuf.Timestamp expires = 3 [ + (gogoproto.stdtime) = true, + (gogoproto.nullable) = false + ]; + + // ID is a record ID, newer records have newer ids + sfixed64 id = 4; + + // LeaseID is a lease ID, could be set on objects with TTL + sfixed64 lease_id = 5; +} + +message Lease { + // Key is a key of the key value item + bytes key = 1; + + // ID is a record ID, newer records have newer ids + sfixed64 id = 2; +} + +message Watch { + // resume_last_id is the last item index we saw, and we want to resume from any event after that point + sfixed64 resume_last_id = 1; +} + +message CompareAndSwapRequest { + // Expected is the existing item + Item expected = 1; + + // replace_with is the new item to swap + Item replace_with = 2; +} + +message GetRequest { + // Key is a key of the key value item + bytes key = 1; +} + +message GetRangeRequest { + // start_key is the starting key to request in the range request + bytes start_key = 1; + + // end_key is the ending key to request in the range request + bytes end_key = 2; + + // limit is the maximum number of results to return + int32 limit = 3; +} + +message GetRangeResult { + repeated Item items = 1; +} + +message DeleteRequest { + // Key is a key of the key value item + bytes key = 1; +} + +message DeleteRangeRequest { + // start_key is the starting key to request in the range request + bytes start_key = 1; + + // end_key is the ending key to request in the range request + bytes end_key = 2; +} + +message KeepAliveRequest { + Lease lease = 1; + google.protobuf.Timestamp expires = 2 [ + (gogoproto.stdtime) = true, + (gogoproto.nullable) = false + ]; +} + + +message Event { + // OpType specifies operation type + enum OpType { + UNKNOWN = 0; + UNRELIABLE = 1; + INVALID = 2; + INIT = 3; + PUT = 4; + DELETE = 5; + GET = 6; + } + OpType type = 1; + Item item = 2; +} + +service BackendService { + // Create creates item if it does not exist + rpc Create(Item) returns (Lease); + + // Put upserts value into backend + rpc Put(Item) returns (Lease); + + // CompareAndSwap compares item with existing item and replaces is with replace_with item + rpc CompareAndSwap(CompareAndSwapRequest) returns (Lease); + + // Update updates value in the backend + rpc Update(Item) returns (Lease); + + // Get returns a single item or not found error + rpc Get(GetRequest) returns (Item); + + // GetRange returns query range + rpc GetRange(GetRangeRequest) returns (GetRangeResult); + + // Delete deletes item by key, returns NotFound error if item does not exist + rpc Delete(DeleteRequest) returns (google.protobuf.Empty); + + // DeleteRange deletes range of items with keys between startKey and endKey + rpc DeleteRange(DeleteRangeRequest) returns (google.protobuf.Empty); + + // KeepAlive keeps object from expiring, updates lease on the existing object, + // expires contains the new expiry to set on the lease, + // some backends may ignore expires based on the implementation + // in case if the lease managed server side + rpc KeepAlive(KeepAliveRequest) returns (google.protobuf.Empty); + + // NewWatcher returns a new event watcher + rpc NewWatcher(Watch) returns (stream Event); +} +``` + +### Configuration + +In order for the gRPC client (the Teleport side) to be able to talk to a server, at minimum the address of the server providing the gRPC service will be required, additionally the following existing configuration properties will be reused: + +- buffer size for client size buffering +- mTLS details (CA, Cert, Key) (currently used for etcd) + +Representing this in yaml the storage stanza of the configuration could potentially look like: +```yaml +storage: + type: grpc + server: endpoint.example.com:1992 + tls_ca_file: /secrets/grpc/ca.crt + tls_cert_file: /secrets/grpc/tls.crt + tls_key_file: /secrets/tls.key + audit_events_uri: ['dynamodb://example-ddb-table.events'] # Future iteration will support grpc://endpoint.example.com:1992 + audit_sessions_uri: s3://example-bucket/sessions + continuous_backups: true + auto_scaling: true + read_min_capacity: 20 + read_max_capacity: 100 + read_target_value: 50.0 + write_min_capacity: 10 + write_max_capacity: 100 + write_target_value: 70.0 +``` \ No newline at end of file diff --git a/tool/tbot/botfs/botfs.go b/tool/tbot/botfs/botfs.go new file mode 100644 index 0000000000000..ceacbb49fd071 --- /dev/null +++ b/tool/tbot/botfs/botfs.go @@ -0,0 +1,156 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package botfs + +import ( + "io/fs" + "os" + "os/user" + "runtime" + "strconv" + "syscall" + + "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/trace" + "github.com/sirupsen/logrus" +) + +var log = logrus.WithFields(logrus.Fields{ + trace.Component: teleport.ComponentTBot, +}) + +// SymlinksMode is an enum type listing various symlink behavior modes. +type SymlinksMode string + +const ( + // SymlinksInsecure does allow resolving symlink paths and does not issue + // any symlink-related warnings. + SymlinksInsecure SymlinksMode = "insecure" + + // SymlinksTrySecure attempts to write files securely and avoid symlink + // attacks, but falls back with a warning if the necessary OS / kernel + // support is missing. + SymlinksTrySecure SymlinksMode = "try-secure" + + // SymlinksSecure attempts to write files securely and fails with an error + // if the operation fails. This should be the default on systems where we + // expect it to be supported. + SymlinksSecure SymlinksMode = "secure" +) + +// ACLMode is an enum type listing various ACL behavior modes. +type ACLMode string + +const ( + // ACLOff disables ACLs + ACLOff ACLMode = "off" + + // ACLTry attempts to use ACLs but falls back to no ACLs with a warning if + // unavailable. + ACLTry ACLMode = "try" + + // ACLRequired enables ACL support and fails if ACLs are unavailable. + ACLRequired ACLMode = "required" +) + +const ( + // DefaultMode is the preferred permissions mode for bot files. + DefaultMode fs.FileMode = 0600 + + // DefaultDirMode is the preferred permissions mode for bot directories. + // Directories need the execute bit set for most operations on their + // contents to succeed. + DefaultDirMode fs.FileMode = 0700 +) + +// ACLOptions contains parameters needed to configure ACLs +type ACLOptions struct { + // BotUser is the bot user that should have write access to this entry + BotUser *user.User + + // ReaderUser is the user that should have read access to the file. This + // may be nil if the reader user is not known. + ReaderUser *user.User +} + +// openStandard attempts to open the given path for writing with O_CREATE set. +func openStandard(path string) (*os.File, error) { + file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, DefaultMode) + if err != nil { + return nil, trace.ConvertSystemError(err) + } + + return file, nil +} + +// createStandard creates an empty file or directory at the given path without +// attempting to prevent symlink attacks. +func createStandard(path string, isDir bool) error { + if isDir { + if err := os.Mkdir(path, DefaultDirMode); err != nil { + return trace.ConvertSystemError(err) + } + + return nil + } + + f, err := openStandard(path) + if err != nil { + return trace.Wrap(err) + } + + if err := f.Close(); err != nil { + log.Warnf("Failed to close file at %q: %+v", path, err) + } + + return nil +} + +// GetOwner attempts to retrieve the owner of the given file. This is not +// supported on all platforms and will return a trace.NotImplemented in that +// case. +func GetOwner(fileInfo fs.FileInfo) (*user.User, error) { + info, ok := fileInfo.Sys().(*syscall.Stat_t) + if !ok { + return nil, trace.NotImplemented("Cannot verify file ownership on this platform.") + } + + user, err := user.LookupId(strconv.Itoa(int(info.Uid))) + if err != nil { + return nil, trace.Wrap(err) + } + + return user, nil +} + +// IsOwnedBy checks that the file at the given path is owned by the given user. +// Returns a trace.NotImplemented() on unsupported platforms. +func IsOwnedBy(fileInfo fs.FileInfo, user *user.User) (bool, error) { + if runtime.GOOS == constants.WindowsOS { + // no-op on windows + return false, trace.NotImplemented("Cannot verify file ownership on this platform.") + } + + info, ok := fileInfo.Sys().(*syscall.Stat_t) + if !ok { + return false, trace.NotImplemented("Cannot verify file ownership on this platform.") + } + + // Our files are 0600, so don't bother checking gid. + return strconv.Itoa(int(info.Uid)) == user.Uid, nil +} diff --git a/tool/tbot/botfs/fs_linux.go b/tool/tbot/botfs/fs_linux.go new file mode 100644 index 0000000000000..835fc6ab0ca95 --- /dev/null +++ b/tool/tbot/botfs/fs_linux.go @@ -0,0 +1,450 @@ +//go:build linux +// +build linux + +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package botfs + +import ( + "io" + "io/fs" + "os" + "os/user" + "path/filepath" + "sync" + + "github.com/coreos/go-semver/semver" + "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/trace" + "github.com/joshlf/go-acl" + "golang.org/x/sys/unix" +) + +// Openat2MinKernel is the kernel release that adds support for the openat2() +// syscall. +const Openat2MinKernel = "5.6.0" + +// modeACLReadExecute is the permissions mode needed for read on directories. +const modeACLReadExecute fs.FileMode = 05 + +// modeACLReadWrite is the lower 3 bytes of a UNIX file mode for permission +// bits, i.e. just one r/w/x. +const modeACLReadWrite fs.FileMode = 06 + +// modeACLReadWriteExecute is the permissions mode needed for full rwx on +// directories. +const modeACLReadWriteExecute fs.FileMode = 07 + +// modeACLNone is the UNIX file mode for no permissions, used for group and +// world read/write. +const modeACLNone fs.FileMode = 0 + +// missingSyscallWarning is used to reduce log spam when a syscall is missing. +var missingSyscallWarning sync.Once + +// openSecure opens the given path for writing (with O_CREAT, mode 0600) +// with the RESOLVE_NO_SYMLINKS flag set. +func openSecure(path string) (*os.File, error) { + how := unix.OpenHow{ + // Equivalent to 0600. Unfortunately it's not worth reusing our + // default file mode constant here. + Mode: unix.O_RDONLY | unix.S_IRUSR | unix.S_IWUSR, + Flags: unix.O_RDWR | unix.O_CREAT, + Resolve: unix.RESOLVE_NO_SYMLINKS, + } + + fd, err := unix.Openat2(unix.AT_FDCWD, path, &how) + if err != nil { + // note: returning the original error here for comparison purposes + return nil, err + } + + // os.File.Close() appears to close wrapped files sanely, so rely on that + // rather than relying on callers to use unix.Close(fd) + return os.NewFile(uintptr(fd), filepath.Base(path)), nil +} + +// openSymlinks mode opens the file for read/write using the given symlink +// mode, potentially failing or logging a warning if symlinks can't be +// secured. +func openSymlinksMode(path string, symlinksMode SymlinksMode) (*os.File, error) { + var file *os.File + var err error + + switch symlinksMode { + case SymlinksSecure: + file, err = openSecure(path) + if err == unix.ENOSYS { + return nil, trace.Errorf("openSecure(%q) failed due to missing "+ + "syscall; `symlinks: insecure` may be required for this "+ + "system", path) + } else if err != nil { + return nil, trace.Wrap(err) + } + case SymlinksTrySecure: + file, err = openSecure(path) + if err == unix.ENOSYS { + log.Warnf("Failed to write to %q securely due to missing "+ + "syscall; falling back to regular file write. Set "+ + "`symlinks: insecure` on this destination to disable this "+ + "warning.", path) + file, err = openStandard(path) + if err != nil { + return nil, trace.Wrap(err) + } + } else if err != nil { + return nil, trace.Wrap(err) + } + case SymlinksInsecure: + file, err = openStandard(path) + if err != nil { + return nil, trace.Wrap(err) + } + default: + return nil, trace.BadParameter("invalid symlinks mode %q", symlinksMode) + } + + return file, nil +} + +// createStandard creates an empty file or directory at the given path while +// attempting to prevent symlink attacks. +func createSecure(path string, isDir bool) error { + if isDir { + // We can't specify RESOLVE_NO_SYMLINKS for mkdir. This isn't the end + // of the world, though: if an attacker attempts a symlink attack we'll + // just open the correct file for read/write later (and error when it + // doesn't exist). + if err := os.Mkdir(path, DefaultDirMode); err != nil { + return trace.Wrap(err) + } + + return nil + } + + f, err := openSecure(path) + if err == unix.ENOSYS { + // bubble up the original error for comparison + return err + } else if err != nil { + return trace.Wrap(err) + } + + // No writing to do, just close it. + if err := f.Close(); err != nil { + log.Warnf("Failed to close file at %q: %+v", path, err) + } + + return nil +} + +// Create attempts to create the given file or directory with the given +// symlinks mode. +func Create(path string, isDir bool, symlinksMode SymlinksMode) error { + // Implementation note: paranoid file _creation_ is only really useful for + // providing an early warning if openat2() / ACLs are unsupported on the + // host system, as it will catch compatibility issues during `tbot init`. + // Read() and Write() with Symlinks(Try)Secure are the codepaths that + // actually prevents symlink attacks. + + switch symlinksMode { + case SymlinksSecure: + if err := createSecure(path, isDir); err != nil { + if err == unix.ENOSYS { + return trace.Errorf("createSecure(%q) failed due to missing "+ + "syscall; `symlinks: insecure` may be required for this "+ + "system", path) + } + + return trace.Wrap(err) + } + case SymlinksTrySecure: + err := createSecure(path, isDir) + if err == nil { + // All good, move on. + return nil + } + + if err != unix.ENOSYS { + // Something else went wrong, fail. + return trace.Wrap(err) + } + + // It's a bit gross to stuff this sync.Once into a global, but + // hopefully that's forgivable since it just manages a log message. + missingSyscallWarning.Do(func() { + log.Warnf("Failed to create %q securely due to missing syscall; "+ + "falling back to regular file creation. Set `symlinks: "+ + "insecure` on this destination to disable this warning.", path) + }) + + return trace.Wrap(createStandard(path, isDir)) + case SymlinksInsecure: + return trace.Wrap(createStandard(path, isDir)) + default: + return trace.BadParameter("invalid symlinks mode %q", symlinksMode) + } + + return nil +} + +// Read reads the contents of the given file into memory. +func Read(path string, symlinksMode SymlinksMode) ([]byte, error) { + file, err := openSymlinksMode(path, symlinksMode) + if err != nil { + return nil, trace.Wrap(err) + } + + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return nil, trace.Wrap(err) + } + + return data, nil +} + +// Write stores the given data to the file at the given path. +func Write(path string, data []byte, symlinksMode SymlinksMode) error { + file, err := openSymlinksMode(path, symlinksMode) + if err != nil { + return trace.Wrap(err) + } + + defer file.Close() + + if _, err := file.Write(data); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// desiredPerms determines the desired bot permissions for an artifact at +// the given path. Directories require read/exec, files require read/write. +func desiredPerms(path string) (ownerMode fs.FileMode, botAndReaderMode fs.FileMode, err error) { + stat, err := os.Stat(path) + if err != nil { + return 0, 0, trace.Wrap(err) + } + + botAndReaderMode = modeACLReadWrite + ownerMode = modeACLReadWrite + if stat.IsDir() { + botAndReaderMode = modeACLReadExecute + ownerMode = modeACLReadWriteExecute + } + + return +} + +// VerifyACL verifies whether the ACL of the given file allows writes from the +// bot user. Errors may optionally be used as more informational warnings; +// ConfigureACL can be used to correct them, assuming the user has permissions. +func VerifyACL(path string, opts *ACLOptions) error { + current, err := acl.Get(path) + if err != nil { + return trace.Wrap(err) + } + + stat, err := os.Stat(path) + if err != nil { + return trace.Wrap(err) + } + + owner, err := GetOwner(stat) + if err != nil { + return trace.Wrap(err) + } + + ownerMode, botAndReaderMode, err := desiredPerms(path) + if err != nil { + return trace.Wrap(err) + } + + // Attempt to determine the max # of expected user tags. We can't know the + // reader user in all cases because we only know it during `tbot init`, so + // instead we'll try to determine the upper bound of expected entries here. + maxExpectedUserTags := 2 + if owner.Uid == opts.BotUser.Uid { + // This path is owned by the bot user, so at least one user tag will + // be missing. + maxExpectedUserTags-- + } + + // Also determine the minimum number of expected user tags. There should + // generally be at least 1 unless all users are the same. + minExpectedUserTags := 0 + if owner.Uid != opts.BotUser.Uid { + minExpectedUserTags++ + } + if opts.ReaderUser != nil && owner.Uid != opts.ReaderUser.Uid { + minExpectedUserTags++ + } + + userTagCount := 0 + errors := []error{} + + for _, entry := range current { + switch entry.Tag { + case acl.TagUserObj: + if entry.Perms != ownerMode { + errors = append(errors, trace.BadParameter("user entry has improper permissions %d", entry.Perms)) + } + case acl.TagGroupObj: + if entry.Perms != modeACLNone { + errors = append(errors, trace.BadParameter("group entry has improper permissions %d", entry.Perms)) + } + case acl.TagOther: + if entry.Perms != modeACLNone { + errors = append(errors, trace.BadParameter("other entry has improper permissions %d", entry.Perms)) + } + case acl.TagMask: + if entry.Perms != botAndReaderMode { + errors = append(errors, trace.BadParameter("mask entry has improper permissions %d", entry.Perms)) + } + case acl.TagGroup: + // Group tags currently not allowed. + errors = append(errors, trace.BadParameter("unexpected group entry found")) + case acl.TagUser: + userTagCount++ + + // It's only worth checking the qualifiers if we know all expected + // values in advance. We can't know them at bot runtime in any way + // that isn't also subject to e.g. an attacker with root / owner + // access, so we might as well not spam warnings every time we + // verify the ACLs. We'll have to rely on the tag counter instead. + if opts.BotUser != nil && + opts.ReaderUser != nil && + entry.Qualifier != opts.BotUser.Uid && + entry.Qualifier != opts.ReaderUser.Uid { + errors = append(errors, trace.BadParameter("invalid qualifier %q for user entry", entry.Qualifier)) + } + + if entry.Perms != botAndReaderMode { + errors = append(errors, trace.BadParameter("invalid permissions %q for bot user entry", entry.Perms)) + } + } + } + + if userTagCount > maxExpectedUserTags { + errors = append(errors, trace.BadParameter("too many user tags found")) + } else if userTagCount < minExpectedUserTags { + errors = append(errors, trace.BadParameter("too few user tags found")) + } + + return trace.NewAggregate(errors...) +} + +// ConfigureACL configures ACLs of the given file to allow writes from the bot +// user. +func ConfigureACL(path string, owner *user.User, opts *ACLOptions) error { + if owner.Uid == opts.BotUser.Uid && owner.Uid == opts.ReaderUser.Uid { + // We'll end up with an empty ACL. This isn't technically a problem + log.Warnf("The owner, bot, and reader all appear to be the same "+ + "user (%+v). This is an unusual configuration: consider setting "+ + "`acls: off` in the destination config to remove this warning.", + owner.Username) + } + + // We fully specify the ACL here to ensure the correct permissions are + // always set (rather than just appending an "allow" for the bot user). + // Note: These need to be sorted by tag value. + ownerMode, botAndReaderMode, err := desiredPerms(path) + if err != nil { + return trace.Wrap(err) + } + + // Note: ACL entries need to be ordered per acl_linux entryPriority + desiredACL := acl.ACL{ + { + // Note: Mask does not apply to user object. + Tag: acl.TagUserObj, + Perms: ownerMode, + }, + } + + // Only add an entry for the bot user if it isn't the owner. + if owner.Uid != opts.BotUser.Uid { + desiredACL = append(desiredACL, acl.Entry{ + // Entry to allow the bot to read/write. + Tag: acl.TagUser, + Qualifier: opts.BotUser.Uid, + Perms: botAndReaderMode, + }) + } + + // Only add entry for the reader if it isn't the owner. + if owner.Uid != opts.ReaderUser.Uid { + desiredACL = append(desiredACL, acl.Entry{ + // Entry to allow the reader user to read/write. + Tag: acl.TagUser, + Qualifier: opts.ReaderUser.Uid, + Perms: botAndReaderMode, + }) + } + + desiredACL = append(desiredACL, + acl.Entry{ + Tag: acl.TagGroupObj, + Perms: modeACLNone, + }, + acl.Entry{ + // Mask is the maximum permissions the ACL can grant. This should + // match the desired bot permissions. + Tag: acl.TagMask, + Perms: botAndReaderMode, + }, + acl.Entry{ + Tag: acl.TagOther, + Perms: modeACLNone, + }, + ) + + // Note: we currently give both the bot and reader read/write to all the + // files. This is done for simplicity and shouldn't represent a security + // risk - the bot obviously knows the contents of the destination, and + // the files being writable to the reader is (maybe arguably) not a + // security issue. + + log.Debugf("Configuring ACL on path %q: %v", path, desiredACL) + return trace.ConvertSystemError(trace.Wrap(acl.Set(path, desiredACL))) +} + +// HasACLSupport determines if this binary / system supports ACLs. +func HasACLSupport() (bool, error) { + // We just assume Linux _can_ support ACLs here, and will test for support + // at runtime. + return true, nil +} + +// HasSecureWriteSupport determines if `CreateSecure()` should be supported +// on this OS / kernel version. Note that it just checks the kernel version, +// so this should be treated as a fallible hint. +func HasSecureWriteSupport() (bool, error) { + minKernel := semver.New(Openat2MinKernel) + version, err := utils.KernelVersion() + if err != nil { + return false, trace.Wrap(err) + } + if version.LessThan(*minKernel) { + return false, nil + } + + return true, nil +} diff --git a/tool/tbot/botfs/fs_other.go b/tool/tbot/botfs/fs_other.go new file mode 100644 index 0000000000000..87b2b90e3721a --- /dev/null +++ b/tool/tbot/botfs/fs_other.go @@ -0,0 +1,113 @@ +//go:build !linux +// +build !linux + +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package botfs + +import ( + "io" + "os/user" + + "github.com/gravitational/trace" +) + +// Create attempts to create the given file or directory without +// evaluating symlinks. This is only supported on recent Linux kernel versions +// (5.6+). The resulting file permissions are unspecified; Chmod should be +// called afterward. +func Create(path string, isDir bool, symlinksMode SymlinksMode) error { + if symlinksMode == SymlinksSecure { + return trace.BadParameter("cannot write with `symlinks: secure` on unsupported platform") + } + + return trace.Wrap(createStandard(path, isDir)) +} + +// Read reads the contents of the given file into memory. +func Read(path string, symlinksMode SymlinksMode) ([]byte, error) { + switch symlinksMode { + case SymlinksSecure: + return nil, trace.BadParameter("cannot read with `symlinks: secure` on unsupported platform") + case SymlinksTrySecure: + log.Warn("Secure symlinks not supported on this platform, set `symlinks: insecure` to disable this message", path) + } + + file, err := openStandard(path) + if err != nil { + return nil, trace.Wrap(err) + } + + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return nil, trace.Wrap(err) + } + + return data, nil +} + +// Write stores the given data to the file at the given path. +func Write(path string, data []byte, symlinksMode SymlinksMode) error { + switch symlinksMode { + case SymlinksSecure: + return trace.BadParameter("cannot write with `symlinks: secure` on unsupported platform") + case SymlinksTrySecure: + log.Warn("Secure symlinks not supported on this platform, set `symlinks: insecure` to disable this message", path) + } + + file, err := openStandard(path) + if err != nil { + return trace.Wrap(err) + } + + defer file.Close() + + if _, err := file.Write(data); err != nil { + return trace.Wrap(err) + } + + return nil +} + +//nolint:staticcheck // staticcheck does not like our nop implementations. +// VerifyACL verifies whether the ACL of the given file allows writes from the +// bot user. +func VerifyACL(path string, opts *ACLOptions) error { + return trace.NotImplemented("ACLs not supported on this platform") +} + +//nolint:staticcheck // staticcheck does not like our nop implementations. +// ConfigureACL configures ACLs of the given file to allow writes from the bot +// user. +func ConfigureACL(path string, owner *user.User, opts *ACLOptions) error { + return trace.NotImplemented("ACLs not supported on this platform") +} + +// HasACLSupport determines if this binary / system supports ACLs. This +// catch-all implementation just returns false. +func HasACLSupport() (bool, error) { + return false, nil +} + +// HasSecureWriteSupport determines if `CreateSecure()` should be supported +// on this OS / kernel version. This is only supported on Linux, so this +// catch-all implementation just returns false. +func HasSecureWriteSupport() (bool, error) { + return false, nil +} diff --git a/tool/tbot/config/config.go b/tool/tbot/config/config.go index b52a5bc21a2d1..a5a1523086f3b 100644 --- a/tool/tbot/config/config.go +++ b/tool/tbot/config/config.go @@ -34,6 +34,7 @@ import ( const ( DefaultCertificateTTL = 60 * time.Minute DefaultRenewInterval = 20 * time.Minute + DefaultJoinMethod = "token" ) var log = logrus.WithFields(logrus.Fields{ @@ -60,9 +61,9 @@ type CLIConf struct { // Token is a bot join token. Token string - // RenewInterval is the interval at which certificates are renewed, as a + // RenewalInterval is the interval at which certificates are renewed, as a // time.ParseDuration() string. It must be less than the certificate TTL. - RenewInterval time.Duration + RenewalInterval time.Duration // CertificateTTL is the requested TTL of certificates. It should be some // multiple of the renewal interval to allow for failed renewals. @@ -71,6 +72,29 @@ type CLIConf struct { // JoinMethod is the method the bot should use to exchange a token for the // initial certificate JoinMethod string + + // Oneshot controls whether the bot quits after a single renewal. + Oneshot bool + + // InitDir specifies which destination to initialize if multiple are + // configured. + InitDir string + + // BotUser is a Unix username that should be given permission to write + BotUser string + + // ReaderUser is the Unix username that will be reading the files + ReaderUser string + + // Owner is the user:group that will own the destination files. Due to SSH + // restrictions on key permissions, it cannot be the same as the reader + // user. If ACL support is unused or unavailable, the reader user will own + // files directly. + Owner string + + // Clean is a flag that, if set, instructs `tbot init` to remove existing + // unexpected files. + Clean bool } // OnboardingConfig contains values only required on first connect. @@ -79,7 +103,7 @@ type OnboardingConfig struct { Token string `yaml:"token"` // CAPath is an optional path to a CA certificate. - CAPath string + CAPath string `yaml:"ca_path"` // CAPins is a list of certificate authority pins, used to validate the // connection to the Teleport auth server. @@ -93,16 +117,21 @@ type OnboardingConfig struct { // BotConfig is the bot's root config object. type BotConfig struct { Onboarding *OnboardingConfig `yaml:"onboarding,omitempty"` - Storage StorageConfig `yaml:"storage,omitempty"` + Storage *StorageConfig `yaml:"storage,omitempty"` Destinations []*DestinationConfig `yaml:"destinations,omitempty"` - Debug bool `yaml:"debug"` - AuthServer string `yaml:"auth_server"` - CertificateTTL time.Duration `yaml:"certificate_ttl"` - RenewInterval time.Duration `yaml:"renew_interval"` + Debug bool `yaml:"debug"` + AuthServer string `yaml:"auth_server"` + CertificateTTL time.Duration `yaml:"certificate_ttl"` + RenewalInterval time.Duration `yaml:"renewal_interval"` + Oneshot bool `yaml:"oneshot"` } func (conf *BotConfig) CheckAndSetDefaults() error { + if conf.Storage == nil { + conf.Storage = &StorageConfig{} + } + if err := conf.Storage.CheckAndSetDefaults(); err != nil { return trace.Wrap(err) } @@ -113,21 +142,43 @@ func (conf *BotConfig) CheckAndSetDefaults() error { } } - if conf.AuthServer == "" { - return trace.BadParameter("an auth server address must be configured") - } - if conf.CertificateTTL == 0 { conf.CertificateTTL = DefaultCertificateTTL } - if conf.RenewInterval == 0 { - conf.RenewInterval = DefaultRenewInterval + if conf.RenewalInterval == 0 { + conf.RenewalInterval = DefaultRenewInterval } return nil } +// GetDestinationByPath attempts to fetch a destination by its filesystem path. +// Only valid for filesystem destinations; returns nil if no matching +// destination exists. +func (conf *BotConfig) GetDestinationByPath(path string) (*DestinationConfig, error) { + for _, dest := range conf.Destinations { + destImpl, err := dest.GetDestination() + if err != nil { + return nil, trace.Wrap(err) + } + + destDir, ok := destImpl.(*DestinationDirectory) + if !ok { + continue + } + + // Note: this compares only paths as written in the config file. We + // might want to compare .Abs() if that proves to be confusing (though + // this may have its own problems) + if destDir.Path == path { + return dest, nil + } + } + + return nil, nil +} + // NewDefaultConfig creates a new minimal bot configuration from defaults. // CheckAndSetDefaults() will be called. func NewDefaultConfig(authServer string) (*BotConfig, error) { @@ -163,6 +214,10 @@ func FromCLIConf(cf *CLIConf) (*BotConfig, error) { config.Debug = true } + if cf.Oneshot { + config.Oneshot = true + } + if cf.AuthServer != "" { if config.AuthServer != "" { log.Warnf("CLI parameters are overriding auth server configured in %s", cf.ConfigPath) @@ -177,20 +232,22 @@ func FromCLIConf(cf *CLIConf) (*BotConfig, error) { config.CertificateTTL = cf.CertificateTTL } - if cf.RenewInterval != 0 { - if config.RenewInterval != 0 { + if cf.RenewalInterval != 0 { + if config.RenewalInterval != 0 { log.Warnf("CLI parameters are overriding renewal interval configured in %s", cf.ConfigPath) } - config.RenewInterval = cf.RenewInterval + config.RenewalInterval = cf.RenewalInterval } // DataDir overrides any previously-configured storage config if cf.DataDir != "" { - if _, err := config.Storage.GetDestination(); err != nil { - log.Warnf("CLI parameters are overriding storage location from %s", cf.ConfigPath) + if config.Storage != nil { + if _, err := config.Storage.GetDestination(); err != nil { + log.Warnf("CLI parameters are overriding storage location from %s", cf.ConfigPath) + } } - config.Storage = StorageConfig{ + config.Storage = &StorageConfig{ DestinationMixin: DestinationMixin{ Directory: &DestinationDirectory{ Path: cf.DataDir, @@ -220,9 +277,9 @@ func FromCLIConf(cf *CLIConf) (*BotConfig, error) { // (CAPath, CAPins, etc follow different codepaths so we don't want a // situation where different fields become set weirdly due to struct // merging) - if cf.Token != "" || len(cf.CAPins) > 0 || cf.JoinMethod != "" { + if cf.Token != "" || len(cf.CAPins) > 0 || cf.JoinMethod != DefaultJoinMethod { onboarding := config.Onboarding - if onboarding != nil && (onboarding.Token != "" || onboarding.CAPath != "" || len(onboarding.CAPins) > 0) || cf.JoinMethod != "" { + if onboarding != nil && (onboarding.Token != "" || onboarding.CAPath != "" || len(onboarding.CAPins) > 0) || cf.JoinMethod != DefaultJoinMethod { // To be safe, warn about possible confusion. log.Warnf("CLI parameters are overriding onboarding config from %s", cf.ConfigPath) } diff --git a/tool/tbot/config/config_destination.go b/tool/tbot/config/config_destination.go index fbe4e89fde87a..8e986a824a8a1 100644 --- a/tool/tbot/config/config_destination.go +++ b/tool/tbot/config/config_destination.go @@ -17,23 +17,17 @@ limitations under the License. package config import ( + "github.com/gravitational/teleport/tool/tbot/identity" "github.com/gravitational/trace" ) -type Kind string - -const ( - KindSSH Kind = "ssh" - KindTLS Kind = "tls" -) - // DestinationConfig configures a user certificate destination. type DestinationConfig struct { DestinationMixin `yaml:",inline"` - Roles []string `yaml:"roles,omitempty"` - Kinds []Kind `yaml:"kinds,omitempty"` - Configs []TemplateConfig `yaml:"configs,omitempty"` + Roles []string `yaml:"roles,omitempty"` + Kinds []identity.ArtifactKind `yaml:"kinds,omitempty"` + Configs []TemplateConfig `yaml:"configs,omitempty"` } // destinationDefaults applies defaults for an output sink's destination. Since @@ -52,7 +46,7 @@ func (dc *DestinationConfig) CheckAndSetDefaults() error { // time if len(dc.Kinds) == 0 && len(dc.Configs) == 0 { - dc.Kinds = []Kind{KindSSH} + dc.Kinds = []identity.ArtifactKind{identity.KindSSH} dc.Configs = []TemplateConfig{{ SSHClient: &TemplateSSHClient{}, }} @@ -68,7 +62,7 @@ func (dc *DestinationConfig) CheckAndSetDefaults() error { } // ContainsKind determines if this destination contains the given ConfigKind. -func (dc *DestinationConfig) ContainsKind(kind Kind) bool { +func (dc *DestinationConfig) ContainsKind(kind identity.ArtifactKind) bool { for _, k := range dc.Kinds { if k == kind { return true diff --git a/tool/tbot/config/config_test.go b/tool/tbot/config/config_test.go index c2147e5bba8e1..9d583a480ed1f 100644 --- a/tool/tbot/config/config_test.go +++ b/tool/tbot/config/config_test.go @@ -21,6 +21,8 @@ import ( "testing" "time" + "github.com/coreos/go-semver/semver" + "github.com/gravitational/teleport/tool/tbot/identity" "github.com/stretchr/testify/require" ) @@ -29,7 +31,7 @@ func TestConfigDefaults(t *testing.T) { require.NoError(t, err) require.Equal(t, DefaultCertificateTTL, cfg.CertificateTTL) - require.Equal(t, DefaultRenewInterval, cfg.RenewInterval) + require.Equal(t, DefaultRenewInterval, cfg.RenewalInterval) storageDest, err := cfg.Storage.GetDestination() require.NoError(t, err) @@ -73,7 +75,7 @@ func TestConfigCLIOnlySample(t *testing.T) { // A single default destination should exist require.Len(t, cfg.Destinations, 1) dest := cfg.Destinations[0] - require.ElementsMatch(t, []Kind{KindSSH}, dest.Kinds) + require.ElementsMatch(t, []identity.ArtifactKind{identity.KindSSH}, dest.Kinds) require.Len(t, dest.Configs, 1) template := dest.Configs[0] @@ -92,7 +94,7 @@ func TestConfigFile(t *testing.T) { require.NoError(t, err) require.Equal(t, "auth.example.com", cfg.AuthServer) - require.Equal(t, time.Minute*5, cfg.RenewInterval) + require.Equal(t, time.Minute*5, cfg.RenewalInterval) require.NotNil(t, cfg.Onboarding) require.Equal(t, "foo", cfg.Onboarding.Token) @@ -107,7 +109,7 @@ func TestConfigFile(t *testing.T) { require.Len(t, cfg.Destinations, 1) destination := cfg.Destinations[0] - require.ElementsMatch(t, []Kind{KindSSH, KindTLS}, destination.Kinds) + require.ElementsMatch(t, []identity.ArtifactKind{identity.KindSSH, identity.KindTLS}, destination.Kinds) require.Len(t, destination.Configs, 1) template := destination.Configs[0] @@ -124,9 +126,53 @@ func TestConfigFile(t *testing.T) { require.Equal(t, "/tmp/foo", destImplReal.Path) } +func TestParseSSHVersion(t *testing.T) { + tests := []struct { + str string + version *semver.Version + err bool + }{ + { + str: "OpenSSH_8.2p1 Ubuntu-4ubuntu0.4, OpenSSL 1.1.1f 31 Mar 2020", + version: semver.New("8.2.1"), + }, + { + str: "OpenSSH_8.8p1, OpenSSL 1.1.1m 14 Dec 2021", + version: semver.New("8.8.1"), + }, + { + str: "OpenSSH_7.5p1, OpenSSL 1.0.2s-freebsd 28 May 2019", + version: semver.New("7.5.1"), + }, + { + str: "OpenSSH_7.9p1 Raspbian-10+deb10u2, OpenSSL 1.1.1d 10 Sep 2019", + version: semver.New("7.9.1"), + }, + { + // Couldn't find a full example but in theory patch is optional: + str: "OpenSSH_8.1 foo", + version: semver.New("8.1.0"), + }, + { + str: "Teleport v8.0.0-dev.40 git:v8.0.0-dev.40-0-ge9194c256 go1.17.2", + err: true, + }, + } + + for _, test := range tests { + version, err := parseSSHVersion(test.str) + if test.err { + require.Error(t, err) + } else { + require.NoError(t, err) + require.True(t, version.Equal(*test.version), "got version = %v, want = %v", version, test.version) + } + } +} + const exampleConfigFile = ` auth_server: auth.example.com -renew_interval: 5m +renewal_interval: 5m onboarding: token: foo ca_pins: diff --git a/tool/tbot/config/configtemplate.go b/tool/tbot/config/configtemplate.go index b281a6344eaef..f54e86a6ee165 100644 --- a/tool/tbot/config/configtemplate.go +++ b/tool/tbot/config/configtemplate.go @@ -21,7 +21,6 @@ import ( "strings" "github.com/gravitational/teleport/lib/auth" - "github.com/gravitational/teleport/tool/tbot/destination" "github.com/gravitational/teleport/tool/tbot/identity" "github.com/gravitational/trace" "gopkg.in/yaml.v3" @@ -42,10 +41,6 @@ type FileDescription struct { // IsDir designates whether this describes a subdirectory inside the // destination. IsDir bool - - // ModeHint describes the intended permissions for this data, for - // Destination backends where permissions are relevant. - ModeHint destination.ModeHint } // Template defines functions for dynamically writing additional files to diff --git a/tool/tbot/config/configtemplate_ssh.go b/tool/tbot/config/configtemplate_ssh.go index f0daae845b5d3..2c8f7da6d9550 100644 --- a/tool/tbot/config/configtemplate_ssh.go +++ b/tool/tbot/config/configtemplate_ssh.go @@ -17,19 +17,22 @@ limitations under the License. package config import ( + "bytes" "context" "fmt" "os" + "os/exec" "path/filepath" + "regexp" "strconv" "strings" "text/template" + "github.com/coreos/go-semver/semver" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/teleport/tool/tbot/destination" "github.com/gravitational/teleport/tool/tbot/identity" "github.com/gravitational/trace" "golang.org/x/crypto/ssh" @@ -41,6 +44,66 @@ type TemplateSSHClient struct { ProxyPort uint16 `yaml:"proxy_port"` } +// openSSHVersionRegex is a regex used to parse OpenSSH version strings. +var openSSHVersionRegex = regexp.MustCompile(`^OpenSSH_(?P\d+)\.(?P\d+)(?:p(?P\d+))?`) + +// openSSHMinVersionForRSAWorkaround is the OpenSSH version after which the +// RSA deprecation workaround should be added to generated ssh_config. +var openSSHMinVersionForRSAWorkaround = semver.New("8.5.0") + +// parseSSHVersion attempts to parse +func parseSSHVersion(versionString string) (*semver.Version, error) { + versionTokens := strings.Split(versionString, " ") + if len(versionTokens) == 0 { + return nil, trace.BadParameter("invalid version string: %s", versionString) + } + + versionID := versionTokens[0] + matches := openSSHVersionRegex.FindStringSubmatch(versionID) + if matches == nil { + return nil, trace.BadParameter("cannot parse version string: %q", versionID) + } + + major, err := strconv.Atoi(matches[1]) + if err != nil { + return nil, trace.Wrap(err, "invalid major version number: %s", matches[1]) + } + + minor, err := strconv.Atoi(matches[2]) + if err != nil { + return nil, trace.Wrap(err, "invalid minor version number: %s", matches[2]) + } + + patch := 0 + if matches[3] != "" { + patch, err = strconv.Atoi(matches[3]) + if err != nil { + return nil, trace.Wrap(err, "invalid patch version number: %s", matches[3]) + } + } + + return &semver.Version{ + Major: int64(major), + Minor: int64(minor), + Patch: int64(patch), + }, nil +} + +// getSSHVersion attempts to query the system SSH for its current version. +func getSSHVersion() (*semver.Version, error) { + var out bytes.Buffer + + cmd := exec.Command("ssh", "-V") + cmd.Stderr = &out + + err := cmd.Run() + if err != nil { + return nil, trace.Wrap(err) + } + + return parseSSHVersion(out.String()) +} + func (c *TemplateSSHClient) CheckAndSetDefaults() error { if c.ProxyPort == 0 { c.ProxyPort = defaults.SSHProxyListenPort @@ -51,18 +114,16 @@ func (c *TemplateSSHClient) CheckAndSetDefaults() error { func (c *TemplateSSHClient) Describe() []FileDescription { return []FileDescription{ { - Name: "ssh_config", - ModeHint: destination.ModeHintSecret, + Name: "ssh_config", }, { - Name: "known_hosts", - ModeHint: destination.ModeHintSecret, + Name: "known_hosts", }, } } func (c *TemplateSSHClient) Render(ctx context.Context, authClient auth.ClientI, currentIdentity *identity.Identity, destination *DestinationConfig) error { - if !destination.ContainsKind(KindSSH) { + if !destination.ContainsKind(identity.KindSSH) { return trace.BadParameter("%s config template requires kind `ssh` to be enabled", TemplateSSHClientName) } @@ -104,7 +165,7 @@ func (c *TemplateSSHClient) Render(ctx context.Context, authClient auth.ClientI, dataDir = "" } - knownHosts, err := fetchKnownHosts(authClient, clusterName.GetClusterName(), proxyHost) + knownHosts, err := fetchKnownHosts(ctx, authClient, clusterName.GetClusterName(), proxyHost) if err != nil { return trace.Wrap(err) } @@ -114,18 +175,31 @@ func (c *TemplateSSHClient) Render(ctx context.Context, authClient auth.ClientI, return trace.Wrap(err) } + // Default to including the RSA deprecation workaround. + rsaWorkaround := true + version, err := getSSHVersion() + if err != nil { + log.WithError(err).Debugf("Could not determine SSH version, will include RSA workaround.") + } else if version.LessThan(*openSSHMinVersionForRSAWorkaround) { + log.Debugf("OpenSSH version %s does not require workaround for RSA deprecation", version) + rsaWorkaround = false + } else { + log.Debugf("OpenSSH version %s will use workaround for RSA deprecation", version) + } + var sshConfigBuilder strings.Builder identityFilePath := filepath.Join(dataDir, identity.PrivateKeyKey) certificateFilePath := filepath.Join(dataDir, identity.SSHCertKey) sshConfigPath := filepath.Join(dataDir, "ssh_config") if err := sshConfigTemplate.Execute(&sshConfigBuilder, sshConfigParameters{ - ClusterName: clusterName.GetClusterName(), - ProxyHost: proxyHost, - ProxyPort: proxyPort, - KnownHostsPath: knownHostsPath, - IdentityFilePath: identityFilePath, - CertificateFilePath: certificateFilePath, - SSHConfigPath: sshConfigPath, + ClusterName: clusterName.GetClusterName(), + ProxyHost: proxyHost, + ProxyPort: proxyPort, + KnownHostsPath: knownHostsPath, + IdentityFilePath: identityFilePath, + CertificateFilePath: certificateFilePath, + SSHConfigPath: sshConfigPath, + IncludeRSAWorkaround: rsaWorkaround, }); err != nil { return trace.Wrap(err) } @@ -145,6 +219,15 @@ type sshConfigParameters struct { ProxyHost string ProxyPort string SSHConfigPath string + + // IncludeRSAWorkaround controls whether the RSA deprecation workaround is + // included in the generated configuration. Newer versions of OpenSSH + // deprecate RSA certificates and, due to a bug in golang's ssh package, + // Teleport wrongly advertises its unaffected certificates as a + // now-deprecated certificate type. The workaround includes a config + // override to re-enable RSA certs for just Teleport hosts, however it is + // only supported on OpenSSH 8.5 and later. + IncludeRSAWorkaround bool } var sshConfigTemplate = template.Must(template.New("ssh-config").Parse(` @@ -155,19 +238,19 @@ Host *.{{ .ClusterName }} {{ .ProxyHost }} UserKnownHostsFile "{{ .KnownHostsPath }}" IdentityFile "{{ .IdentityFilePath }}" CertificateFile "{{ .CertificateFilePath }}" - HostKeyAlgorithms ssh-rsa-cert-v01@openssh.com - PubkeyAcceptedAlgorithms +ssh-rsa-cert-v01@openssh.com + HostKeyAlgorithms ssh-rsa-cert-v01@openssh.com{{- if .IncludeRSAWorkaround }} + PubkeyAcceptedAlgorithms +ssh-rsa-cert-v01@openssh.com{{- end }} # Flags for all {{ .ClusterName }} hosts except the proxy Host *.{{ .ClusterName }} !{{ .ProxyHost }} Port 3022 - ProxyCommand ssh -F {{ .SSHConfigPath }} -l %r -p {{ .ProxyPort }} {{ .ProxyHost }} -s proxy:%h:%p@{{ .ClusterName }} + ProxyCommand ssh -F {{ .SSHConfigPath }} -l %r -p {{ .ProxyPort }} {{ .ProxyHost }} -s proxy:$(echo %h | cut -d '.' -f 1):%p@{{ .ClusterName }} # End generated Teleport configuration `)) -func fetchKnownHosts(client auth.ClientI, clusterName, proxyHosts string) (string, error) { - ca, err := client.GetCertAuthority(types.CertAuthID{ +func fetchKnownHosts(ctx context.Context, client auth.ClientI, clusterName, proxyHosts string) (string, error) { + ca, err := client.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: clusterName, }, false) diff --git a/tool/tbot/config/destination.go b/tool/tbot/config/destination.go index c72a8afb7aace..6383ba82cc7a9 100644 --- a/tool/tbot/config/destination.go +++ b/tool/tbot/config/destination.go @@ -31,12 +31,12 @@ type DestinationMixin struct { type DestinationDefaults = func(*DestinationMixin) error -func (dm *DestinationMixin) CheckAndSetDefaults(applyDefaults DestinationDefaults) error { +// checkAndSetDefaultsInner performs member initialization that won't recurse +func (dm *DestinationMixin) checkAndSetDefaultsInner() (int, error) { notNilCount := 0 - if dm.Directory != nil { if err := dm.Directory.CheckAndSetDefaults(); err != nil { - return trace.Wrap(err) + return 0, trace.Wrap(err) } notNilCount++ @@ -44,17 +44,35 @@ func (dm *DestinationMixin) CheckAndSetDefaults(applyDefaults DestinationDefault if dm.Memory != nil { if err := dm.Memory.CheckAndSetDefaults(); err != nil { - return trace.Wrap(err) + return 0, trace.Wrap(err) } notNilCount++ } + return notNilCount, nil +} + +func (dm *DestinationMixin) CheckAndSetDefaults(applyDefaults DestinationDefaults) error { + notNilCount, err := dm.checkAndSetDefaultsInner() + if err != nil { + return trace.Wrap(err) + } if notNilCount == 0 { // use defaults if err := applyDefaults(dm); err != nil { return trace.Wrap(err) } + + // CheckAndSetDefaults() again + notNilCount, err := dm.checkAndSetDefaultsInner() + if err != nil { + return trace.Wrap(err) + } + + if notNilCount == 0 { + return trace.BadParameter("a destination is required") + } } else if notNilCount > 1 { return trace.BadParameter("only one destination backend may be specified at a time") } diff --git a/tool/tbot/config/destination_directory.go b/tool/tbot/config/destination_directory.go index de09b82bcc8b0..b3b39cd7da38c 100644 --- a/tool/tbot/config/destination_directory.go +++ b/tool/tbot/config/destination_directory.go @@ -19,16 +19,19 @@ package config import ( "fmt" "os" + "os/user" "path/filepath" - "github.com/gravitational/teleport/tool/tbot/destination" + "github.com/gravitational/teleport/tool/tbot/botfs" "github.com/gravitational/trace" "gopkg.in/yaml.v3" ) // DestinationDirectory is a Destination that writes to the local filesystem type DestinationDirectory struct { - Path string `yaml:"path,omitempty"` + Path string `yaml:"path,omitempty"` + Symlinks botfs.SymlinksMode `yaml:"symlinks,omitempty"` + ACLs botfs.ACLMode `yaml:"acls,omitempty"` } func (dd *DestinationDirectory) UnmarshalYAML(node *yaml.Node) error { @@ -58,23 +61,142 @@ func (dd *DestinationDirectory) CheckAndSetDefaults() error { return trace.BadParameter("destination path must not be empty") } + secureSupported, err := botfs.HasSecureWriteSupport() + if err != nil { + return trace.Wrap(err) + } + + aclsSupported, err := botfs.HasACLSupport() + if err != nil { + return trace.Wrap(err) + } + + switch dd.Symlinks { + case "": + if secureSupported { + // We expect Openat2 to be available, so try to use it by default. + dd.Symlinks = botfs.SymlinksSecure + } else { + // TrySecure will print a warning on fallback. + dd.Symlinks = botfs.SymlinksTrySecure + } + case botfs.SymlinksInsecure, botfs.SymlinksTrySecure: + // valid + case botfs.SymlinksSecure: + if !secureSupported { + return trace.BadParameter("symlink mode %q not supported on this system", dd.Symlinks) + } + default: + return trace.BadParameter("invalid symlinks mode: %q", dd.Symlinks) + } + + switch dd.ACLs { + case "": + if aclsSupported { + // Unlike openat2(), we can't ever depend on ACLs being available. + // We'll only ever try to use them, end users can opt-in to a hard + // ACL check if they wish. + dd.ACLs = botfs.ACLTry + } else { + // if aclsSupported == false here, we know it will never work, so + // don't bother trying. + dd.ACLs = botfs.ACLOff + } + case botfs.ACLOff, botfs.ACLTry: + // valid + case botfs.ACLRequired: + if !aclsSupported { + return trace.BadParameter("acls mode %q not supported on this system", dd.ACLs) + } + default: + return trace.BadParameter("invalid acls mode: %q", dd.ACLs) + } + return nil } -func (dd *DestinationDirectory) Write(name string, data []byte, modeHint destination.ModeHint) error { - // TODO: honor modeHint? - if err := os.WriteFile(filepath.Join(dd.Path, name), data, 0600); err != nil { +func (dd *DestinationDirectory) Init() error { + // Create the directory if needed. + stat, err := os.Stat(dd.Path) + if trace.IsNotFound(err) { + if err := os.MkdirAll(dd.Path, botfs.DefaultDirMode); err != nil { + return trace.Wrap(err) + } + log.Infof("Created directory %q", dd.Path) + } else if err != nil { return trace.Wrap(err) + } else if !stat.IsDir() { + return trace.BadParameter("Path %q already exists and is not a directory", dd.Path) } + return nil } +func (dd *DestinationDirectory) Verify(keys []string) error { + aclsSupported, err := botfs.HasACLSupport() + if err != nil { + return trace.Wrap(err) + } + + currentUser, err := user.Current() + if err != nil { + return trace.Wrap(err) + } + + stat, err := os.Stat(dd.Path) + if err != nil { + return trace.Wrap(err) + } + + ownedByBot, err := botfs.IsOwnedBy(stat, currentUser) + if trace.IsNotImplemented(err) { + // If file owners aren't supported, ACLs certainly aren't. Just bail. + // (Subject to change if we ever try to support Windows ACLs.) + return nil + } else if err != nil { + return trace.Wrap(err) + } + + // Make sure it's worth warning about ACLs for this destination. If ACLs + // are disabled, unsupported, or the destination is owned by the bot + // (implying the user is not trying to use ACLs), just bail. + if dd.ACLs == botfs.ACLOff || !aclsSupported || ownedByBot { + return nil + } + + errors := []error{} + for _, key := range keys { + path := filepath.Join(dd.Path, key) + + errors = append(errors, botfs.VerifyACL(path, &botfs.ACLOptions{ + BotUser: currentUser, + })) + } + + aggregate := trace.NewAggregate(errors...) + if dd.ACLs == botfs.ACLRequired { + // Hard fail if ACLs are specifically requested and there are errors. + return aggregate + } + + if aggregate != nil { + log.Warnf("Destination %q has unexpected ACLs: %v", dd.Path, aggregate) + } + + return nil +} + +func (dd *DestinationDirectory) Write(name string, data []byte) error { + return trace.Wrap(botfs.Write(filepath.Join(dd.Path, name), data, dd.Symlinks)) +} + func (dd *DestinationDirectory) Read(name string) ([]byte, error) { - b, err := os.ReadFile(filepath.Join(dd.Path, name)) + data, err := botfs.Read(filepath.Join(dd.Path, name), dd.Symlinks) if err != nil { return nil, trace.Wrap(err) } - return b, nil + + return data, nil } func (dd *DestinationDirectory) String() string { diff --git a/tool/tbot/config/destination_memory.go b/tool/tbot/config/destination_memory.go index 1cacbb61a9fe1..2778bc838a52e 100644 --- a/tool/tbot/config/destination_memory.go +++ b/tool/tbot/config/destination_memory.go @@ -17,7 +17,6 @@ limitations under the License. package config import ( - "github.com/gravitational/teleport/tool/tbot/destination" "github.com/gravitational/trace" "gopkg.in/yaml.v3" ) @@ -51,7 +50,17 @@ func (dm *DestinationMemory) CheckAndSetDefaults() error { return nil } -func (dm *DestinationMemory) Write(name string, data []byte, _ destination.ModeHint) error { +func (dm *DestinationMemory) Init() error { + // Nothing to do. + return nil +} + +func (dm *DestinationMemory) Verify(keys []string) error { + // Nothing to do. + return nil +} + +func (dm *DestinationMemory) Write(name string, data []byte) error { dm.store[name] = data return nil diff --git a/tool/tbot/destination/destination.go b/tool/tbot/destination/destination.go index 093ce60bf71b6..3ad0ca5358b3d 100644 --- a/tool/tbot/destination/destination.go +++ b/tool/tbot/destination/destination.go @@ -16,23 +16,20 @@ limitations under the License. package destination -// ModeHint is a backend-agnostic file mode hint. -type ModeHint int64 - -const ( - // ModeHintUnspecified hints that files should be created with default - // (possibly insecure) permissions. - ModeHintUnspecified ModeHint = iota - - // ModeHintSecret hints that files should be created with restricted - // permissions, appropriate for secret data. - ModeHintSecret -) - // Destination can persist renewable certificates. type Destination interface { + // Init attempts to initialize this destination for writing. Init should be + // idempotent and may write informational log messages if resources are + // created. + Init() error + + // Verify is run before renewals to check for any potential problems with + // the destination. These errors may be informational (logged warnings) or + // return an error that may potentially terminate the process. + Verify(keys []string) error + // Write stores data to the destination with the given name. - Write(name string, data []byte, modeHint ModeHint) error + Write(name string, data []byte) error // Read fetches data from the destination with a given name. Read(name string) ([]byte, error) diff --git a/tool/tbot/identity/artifact.go b/tool/tbot/identity/artifact.go new file mode 100644 index 0000000000000..840c491500a13 --- /dev/null +++ b/tool/tbot/identity/artifact.go @@ -0,0 +1,144 @@ +/* +Copyright 2021-2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package identity + +import ( + "bytes" + + "github.com/gravitational/teleport/api/client/proto" +) + +// Artifact is a component of a serialized identity. +type Artifact struct { + Key string + Kind ArtifactKind + ToBytes func(*Identity) []byte + FromBytes func(*proto.Certs, *LoadIdentityParams, []byte) +} + +// Matches returns true if this artifact's Kind matches any one of the given +// kinds or if it's kind is KindAlways +func (a *Artifact) Matches(kinds ...ArtifactKind) bool { + if a.Kind == KindAlways { + return true + } + + for _, kind := range kinds { + if a.Kind == kind { + return true + } + } + + return false +} + +var artifacts = []Artifact{ + // SSH artifacts + { + Key: SSHCertKey, + Kind: KindSSH, + ToBytes: func(i *Identity) []byte { + return i.CertBytes + }, + FromBytes: func(c *proto.Certs, p *LoadIdentityParams, b []byte) { + c.SSH = b + }, + }, + { + Key: SSHCACertsKey, + Kind: KindSSH, + ToBytes: func(i *Identity) []byte { + return bytes.Join(i.SSHCACertBytes, []byte("$")) + }, + FromBytes: func(c *proto.Certs, p *LoadIdentityParams, b []byte) { + c.SSHCACerts = bytes.Split(b, []byte("$")) + }, + }, + + // TLS artifacts + { + Key: TLSCertKey, + Kind: KindTLS, + ToBytes: func(i *Identity) []byte { + return i.TLSCertBytes + }, + FromBytes: func(c *proto.Certs, p *LoadIdentityParams, b []byte) { + c.TLS = b + }, + }, + { + Key: TLSCACertsKey, + Kind: KindTLS, + ToBytes: func(i *Identity) []byte { + return bytes.Join(i.TLSCACertsBytes, []byte("$")) + }, + FromBytes: func(c *proto.Certs, p *LoadIdentityParams, b []byte) { + c.TLSCACerts = bytes.Split(b, []byte("$")) + }, + }, + + // Common artifacts + { + Key: PrivateKeyKey, + Kind: KindAlways, + ToBytes: func(i *Identity) []byte { + return i.PrivateKeyBytes + }, + FromBytes: func(c *proto.Certs, p *LoadIdentityParams, b []byte) { + p.PrivateKeyBytes = b + }, + }, + { + Key: PublicKeyKey, + Kind: KindAlways, + ToBytes: func(i *Identity) []byte { + return i.PublicKeyBytes + }, + FromBytes: func(c *proto.Certs, p *LoadIdentityParams, b []byte) { + p.PublicKeyBytes = b + }, + }, + { + // The token hash is used to detect changes to the token and + // request a new identity when changes are detected. + Key: TokenHashKey, + Kind: KindBotInternal, + ToBytes: func(i *Identity) []byte { + return i.TokenHashBytes + }, + FromBytes: func(c *proto.Certs, p *LoadIdentityParams, b []byte) { + p.TokenHashBytes = b + }, + }, + { + // The write test is used to ensure the destination is writable before + // attempting a renewal. + Key: WriteTestKey, + Kind: KindAlways, + ToBytes: func(i *Identity) []byte { + // always empty + return []byte{} + }, + FromBytes: func(c *proto.Certs, p *LoadIdentityParams, b []byte) { + // nothing to do + }, + }, +} + +func GetArtifacts() []Artifact { + return artifacts +} diff --git a/tool/tbot/identity/identity.go b/tool/tbot/identity/identity.go index b71ad535266a8..33cd4a050d936 100644 --- a/tool/tbot/identity/identity.go +++ b/tool/tbot/identity/identity.go @@ -17,7 +17,6 @@ limitations under the License. package identity import ( - "bytes" "crypto/tls" "crypto/x509" "fmt" @@ -57,8 +56,12 @@ const ( // PublicKeyKey is the ssh public key, required for successful SSH connections. PublicKeyKey = "key.pub" - // MetadataKey is the name under which additional metadata exists in a destination. - MetadataKey = "meta" + // TokenHashKey is the key where a hash of the onboarding token will be stored. + TokenHashKey = "tokenhash" + + // WriteTestKey is the key for a file used to check that the destination is + // writable. + WriteTestKey = ".write-test" ) var log = logrus.WithFields(logrus.Fields{ @@ -69,10 +72,10 @@ var log = logrus.WithFields(logrus.Fields{ // identity. This is derived from Teleport's usual auth.Identity with small // modifications to work with user rather than host certificates. type Identity struct { - // KeyBytes is a PEM encoded private key - KeyBytes []byte - // SSHPublicKeyBytes contains bytes of the original SSH public key - SSHPublicKeyBytes []byte + // PrivateKeyBytes is a PEM encoded private key + PrivateKeyBytes []byte + // PublicKeyBytes contains bytes of the original SSH public key + PublicKeyBytes []byte // CertBytes is a PEM encoded SSH host cert CertBytes []byte // TLSCertBytes is a PEM encoded TLS x509 client certificate @@ -84,19 +87,39 @@ type Identity struct { SSHCACertBytes [][]byte // KeySigner is an SSH host certificate signer KeySigner ssh.Signer - // Cert is a parsed SSH certificate - Cert *ssh.Certificate - // XCert is X509 client certificate - XCert *x509.Certificate + // SSHCert is a parsed SSH certificate + SSHCert *ssh.Certificate + // X509Cert is an X509 client certificate + X509Cert *x509.Certificate // ClusterName is a name of host's cluster ClusterName string + // TokenHashBytes is the hash of the original join token + TokenHashBytes []byte +} + +// LoadIdentityParams contains parameters beyond proto.Certs needed to load a +// stored identity. +type LoadIdentityParams struct { + PrivateKeyBytes []byte + PublicKeyBytes []byte + TokenHashBytes []byte +} + +// Params returns the LoadIdentityParams for this Identity, which are the +// local-only parameters to be carried over to a renewed identity. +func (i *Identity) Params() *LoadIdentityParams { + return &LoadIdentityParams{ + PrivateKeyBytes: i.PrivateKeyBytes, + PublicKeyBytes: i.PublicKeyBytes, + TokenHashBytes: i.TokenHashBytes, + } } // String returns user-friendly representation of the identity. func (i *Identity) String() string { var out []string - if i.XCert != nil { - out = append(out, fmt.Sprintf("cert(%v issued by %v:%v)", i.XCert.Subject.CommonName, i.XCert.Issuer.CommonName, i.XCert.Issuer.SerialNumber)) + if i.X509Cert != nil { + out = append(out, fmt.Sprintf("cert(%v issued by %v:%v)", i.X509Cert.Subject.CommonName, i.X509Cert.Issuer.CommonName, i.X509Cert.Issuer.SerialNumber)) } for j := range i.TLSCACertsBytes { cert, err := tlsca.ParseCertificatePEM(i.TLSCACertsBytes[j]) @@ -144,7 +167,7 @@ func (i *Identity) HasTLSConfig() bool { // HasPrincipals returns whether identity has principals func (i *Identity) HasPrincipals(additionalPrincipals []string) bool { - set := utils.StringsSet(i.Cert.ValidPrincipals) + set := utils.StringsSet(i.SSHCert.ValidPrincipals) for _, principal := range additionalPrincipals { if _, ok := set[principal]; !ok { return false @@ -155,10 +178,10 @@ func (i *Identity) HasPrincipals(additionalPrincipals []string) bool { // HasDNSNames returns true if TLS certificate has required DNS names func (i *Identity) HasDNSNames(dnsNames []string) bool { - if i.XCert == nil { + if i.X509Cert == nil { return false } - set := utils.StringsSet(i.XCert.DNSNames) + set := utils.StringsSet(i.X509Cert.DNSNames) for _, dnsName := range dnsNames { if _, ok := set[dnsName]; !ok { return false @@ -174,7 +197,7 @@ func (i *Identity) TLSConfig(cipherSuites []uint16) (*tls.Config, error) { if !i.HasTLSConfig() { return nil, trace.NotFound("no TLS credentials setup for this identity") } - tlsCert, err := tls.X509KeyPair(i.TLSCertBytes, i.KeyBytes) + tlsCert, err := tls.X509KeyPair(i.TLSCertBytes, i.PrivateKeyBytes) if err != nil { return nil, trace.BadParameter("failed to parse private key: %v", err) } @@ -211,185 +234,208 @@ func (i *Identity) SSHClientConfig() (*ssh.ClientConfig, error) { if err != nil { return nil, trace.Wrap(err) } - if len(i.Cert.ValidPrincipals) < 1 { + if len(i.SSHCert.ValidPrincipals) < 1 { return nil, trace.BadParameter("user cert has no valid principals") } return &ssh.ClientConfig{ - User: i.Cert.ValidPrincipals[0], + User: i.SSHCert.ValidPrincipals[0], Auth: []ssh.AuthMethod{ssh.PublicKeys(i.KeySigner)}, HostKeyCallback: callback, Timeout: apidefaults.DefaultDialTimeout, }, nil } -// ReadIdentityFromKeyPair reads SSH and TLS identity from key pair. -func ReadIdentityFromKeyPair(privateKey []byte, publicKey []byte, certs *proto.Certs) (*Identity, error) { - identity, err := ReadSSHIdentityFromKeyPair(privateKey, publicKey, certs.SSH) - if err != nil { - return nil, trace.Wrap(err) - } +// ReadIdentityFromStore reads stored identity credentials +func ReadIdentityFromStore(params *LoadIdentityParams, certs *proto.Certs, kinds ...ArtifactKind) (*Identity, error) { + var identity Identity + if ContainsKind(KindSSH, kinds) { + if len(certs.SSH) == 0 { + return nil, trace.BadParameter("identity requires SSH certificates but they are unset") + } - if len(certs.SSHCACerts) != 0 { - identity.SSHCACertBytes = certs.SSHCACerts + err := ReadSSHIdentityFromKeyPair(&identity, params.PrivateKeyBytes, params.PrivateKeyBytes, certs.SSH) + if err != nil { + return nil, trace.Wrap(err) + } + + if len(certs.SSHCACerts) != 0 { + identity.SSHCACertBytes = certs.SSHCACerts + } } - if len(certs.TLSCACerts) != 0 { + if ContainsKind(KindTLS, kinds) { + if len(certs.TLSCACerts) == 0 || len(certs.TLS) == 0 { + return nil, trace.BadParameter("identity requires TLS certificates but they are empty") + } + // Parse the key pair to verify that identity parses properly for future use. - i, err := ReadTLSIdentityFromKeyPair(privateKey, certs.TLS, certs.TLSCACerts) - if err != nil { + if err := ReadTLSIdentityFromKeyPair(&identity, params.PrivateKeyBytes, certs.TLS, certs.TLSCACerts); err != nil { return nil, trace.Wrap(err) } - identity.XCert = i.XCert - identity.TLSCertBytes = certs.TLS - identity.TLSCACertsBytes = certs.TLSCACerts } - return identity, nil + identity.PublicKeyBytes = params.PublicKeyBytes + identity.PrivateKeyBytes = params.PrivateKeyBytes + identity.TokenHashBytes = params.TokenHashBytes + + return &identity, nil } // ReadTLSIdentityFromKeyPair reads TLS identity from key pair -func ReadTLSIdentityFromKeyPair(keyBytes, certBytes []byte, caCertsBytes [][]byte) (*Identity, error) { +func ReadTLSIdentityFromKeyPair(identity *Identity, keyBytes, certBytes []byte, caCertsBytes [][]byte) error { if len(keyBytes) == 0 { - return nil, trace.BadParameter("missing private key") + return trace.BadParameter("missing private key") } if len(certBytes) == 0 { - return nil, trace.BadParameter("missing certificate") + return trace.BadParameter("missing certificate") } cert, err := tlsca.ParseCertificatePEM(certBytes) if err != nil { - return nil, trace.Wrap(err, "failed to parse TLS certificate") + return trace.Wrap(err, "failed to parse TLS certificate") } if len(cert.Issuer.Organization) == 0 { - return nil, trace.BadParameter("missing CA organization") + return trace.BadParameter("missing CA organization") } clusterName := cert.Issuer.Organization[0] if clusterName == "" { - return nil, trace.BadParameter("misssing cluster name") - } - identity := &Identity{ - ClusterName: clusterName, - KeyBytes: keyBytes, - TLSCertBytes: certBytes, - TLSCACertsBytes: caCertsBytes, - XCert: cert, + return trace.BadParameter("misssing cluster name") } + + identity.ClusterName = clusterName + identity.PrivateKeyBytes = keyBytes + identity.TLSCertBytes = certBytes + identity.TLSCACertsBytes = caCertsBytes + identity.X509Cert = cert + // The passed in ciphersuites don't appear to matter here since the returned // *tls.Config is never actually used? _, err = identity.TLSConfig(utils.DefaultCipherSuites()) if err != nil { - return nil, trace.Wrap(err) + return trace.Wrap(err) } - return identity, nil + return nil } // ReadSSHIdentityFromKeyPair reads identity from initialized keypair -func ReadSSHIdentityFromKeyPair(keyBytes, publicKeyBytes, certBytes []byte) (*Identity, error) { +func ReadSSHIdentityFromKeyPair(identity *Identity, keyBytes, publicKeyBytes, certBytes []byte) error { if len(keyBytes) == 0 { - return nil, trace.BadParameter("PrivateKey: missing private key") + return trace.BadParameter("PrivateKey: missing private key") } if len(publicKeyBytes) == 0 { - return nil, trace.BadParameter("PublicKey: missing public key") + return trace.BadParameter("PublicKey: missing public key") } if len(certBytes) == 0 { - return nil, trace.BadParameter("Cert: missing parameter") + return trace.BadParameter("Cert: missing parameter") } cert, err := apisshutils.ParseCertificate(certBytes) if err != nil { - return nil, trace.BadParameter("failed to parse server certificate: %v", err) + return trace.BadParameter("failed to parse server certificate: %v", err) } signer, err := ssh.ParsePrivateKey(keyBytes) if err != nil { - return nil, trace.BadParameter("failed to parse private key: %v", err) + return trace.BadParameter("failed to parse private key: %v", err) } // this signer authenticates using certificate signed by the cert authority // not only by the public key certSigner, err := ssh.NewCertSigner(cert, signer) if err != nil { - return nil, trace.BadParameter("unsupported private key: %v", err) + return trace.BadParameter("unsupported private key: %v", err) } // check principals on certificate if len(cert.ValidPrincipals) < 1 { - return nil, trace.BadParameter("valid principals: at least one valid principal is required") + return trace.BadParameter("valid principals: at least one valid principal is required") } for _, validPrincipal := range cert.ValidPrincipals { if validPrincipal == "" { - return nil, trace.BadParameter("valid principal can not be empty: %q", cert.ValidPrincipals) + return trace.BadParameter("valid principal can not be empty: %q", cert.ValidPrincipals) } } clusterName := cert.Permissions.Extensions[teleport.CertExtensionTeleportRouteToCluster] if clusterName == "" { - return nil, trace.BadParameter("missing cert extension %v", utils.CertExtensionAuthority) + return trace.BadParameter("missing cert extension %v", utils.CertExtensionAuthority) } - return &Identity{ - ClusterName: clusterName, - KeyBytes: keyBytes, - SSHPublicKeyBytes: publicKeyBytes, - CertBytes: certBytes, - KeySigner: certSigner, - Cert: cert, - }, nil + identity.ClusterName = clusterName + identity.PrivateKeyBytes = keyBytes + identity.PublicKeyBytes = publicKeyBytes + identity.CertBytes = certBytes + identity.KeySigner = certSigner + identity.SSHCert = cert + + return nil +} + +// VerifyWrite attempts to write to the .write-test artifact inside the given +// destination. It should be called before attempting a renewal to help ensure +// we won't then fail to save the identity. +func VerifyWrite(dest destination.Destination) error { + return trace.Wrap(dest.Write(WriteTestKey, []byte{})) +} + +// ListKeys returns a list of artifact keys that will be written given a list +// of artifacts. +func ListKeys(kinds ...ArtifactKind) []string { + keys := []string{} + for _, artifact := range GetArtifacts() { + if !artifact.Matches(kinds...) { + continue + } + + keys = append(keys, artifact.Key) + } + + return keys } // SaveIdentity saves a bot identity to a destination. -func SaveIdentity(id *Identity, d destination.Destination) error { - for _, data := range []struct { - name string - data []byte - modeHint destination.ModeHint - }{ - {TLSCertKey, id.TLSCertBytes, destination.ModeHintSecret}, - {SSHCertKey, id.CertBytes, destination.ModeHintSecret}, - {TLSCACertsKey, bytes.Join(id.TLSCACertsBytes, []byte("$")), destination.ModeHintSecret}, - {SSHCACertsKey, bytes.Join(id.SSHCACertBytes, []byte("$")), destination.ModeHintSecret}, - {PrivateKeyKey, id.KeyBytes, destination.ModeHintSecret}, - {PublicKeyKey, id.SSHPublicKeyBytes, destination.ModeHintUnspecified}, - } { - log.Debugf("Writing %s", data.name) - if err := d.Write(data.name, data.data, data.modeHint); err != nil { - return trace.Wrap(err, "could not write to %v", data.name) +func SaveIdentity(id *Identity, d destination.Destination, kinds ...ArtifactKind) error { + for _, artifact := range GetArtifacts() { + // Only store artifacts matching one of the set kinds. + if !artifact.Matches(kinds...) { + continue + } + + data := artifact.ToBytes(id) + + log.Debugf("Writing %s", artifact.Key) + if err := d.Write(artifact.Key, data); err != nil { + return trace.WrapWithMessage(err, "could not write to %v", artifact.Key) } } + return nil } // LoadIdentity loads a bot identity from a destination. -func LoadIdentity(d destination.Destination) (*Identity, error) { - var key, sshPublicKey, tlsCA, sshCA []byte +func LoadIdentity(d destination.Destination, kinds ...ArtifactKind) (*Identity, error) { var certs proto.Certs - var err error - - for _, item := range []struct { - name string - out *[]byte - }{ - {TLSCertKey, &certs.TLS}, - {SSHCertKey, &certs.SSH}, - {TLSCACertsKey, &tlsCA}, - {SSHCACertsKey, &sshCA}, - {PrivateKeyKey, &key}, - {PublicKeyKey, &sshPublicKey}, - } { - *item.out, err = d.Read(item.name) + var params LoadIdentityParams + + for _, artifact := range GetArtifacts() { + // Only attempt to load artifacts matching one of the specified kinds + if !artifact.Matches(kinds...) { + continue + } + + data, err := d.Read(artifact.Key) if err != nil { - return nil, trace.Wrap(err, "could not read %v", item.name) + return nil, trace.WrapWithMessage(err, "could not read artifact %q from destination %s", artifact.Key, d) } - } - certs.SSHCACerts = bytes.Split(sshCA, []byte("$")) - certs.TLSCACerts = bytes.Split(tlsCA, []byte("$")) + artifact.FromBytes(&certs, ¶ms, data) + } log.Debugf("Loaded %d SSH CA certs and %d TLS CA certs", len(certs.SSHCACerts), len(certs.TLSCACerts)) - return ReadIdentityFromKeyPair(key, sshPublicKey, &certs) + return ReadIdentityFromStore(¶ms, &certs, kinds...) } diff --git a/tool/tbot/identity/kinds.go b/tool/tbot/identity/kinds.go new file mode 100644 index 0000000000000..5e98ff2da6905 --- /dev/null +++ b/tool/tbot/identity/kinds.go @@ -0,0 +1,86 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package identity + +import ( + "strings" + + "github.com/gravitational/trace" + "gopkg.in/yaml.v3" +) + +// ArtifactKind is a type of identity artifact that can be stored and loaded. +type ArtifactKind string + +const ( + // KindAlways identifies identity resources that should always be + // generated. + KindAlways ArtifactKind = "always" + + // KindSSH identifies resources that should only be generated for SSH use. + KindSSH ArtifactKind = "ssh" + + // KindTLS identifies resources that should only be stored for TLS use. + KindTLS ArtifactKind = "tls" + + // KindBotInternal identifies resources that should only be stored in the + // bot's internal data directory. + KindBotInternal ArtifactKind = "bot-internal" +) + +// allConfigKinds is a list of all ArtifactKinds allowed in config files. +var allConfigKinds = []string{string(KindSSH), string(KindTLS)} + +func (ac *ArtifactKind) UnmarshalYAML(node *yaml.Node) error { + var kind string + if err := node.Decode(&kind); err != nil { + return err + } + + // Only TLS and SSH are configurable values. + switch kind { + case string(KindTLS): + *ac = KindTLS + case string(KindSSH): + *ac = KindSSH + default: + return trace.BadParameter( + "invalid kind %q, expected one of: %s", + kind, strings.Join(allConfigKinds, ", "), + ) + } + + return nil +} + +// ContainsKind determines if a particular artifact kind is included in the +// list of kinds. +func ContainsKind(kind ArtifactKind, kinds []ArtifactKind) bool { + for _, k := range kinds { + if kind == k { + return true + } + } + + return false +} + +// BotKinds returns a list of all artifact kinds used internally by the bot. +// End-user destinations may contain a different set of artifacts. +func BotKinds() []ArtifactKind { + return []ArtifactKind{KindAlways, KindBotInternal, KindSSH, KindTLS} +} diff --git a/tool/tbot/init.go b/tool/tbot/init.go new file mode 100644 index 0000000000000..8a78090190ef8 --- /dev/null +++ b/tool/tbot/init.go @@ -0,0 +1,557 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + "os/user" + "path/filepath" + "runtime" + "strconv" + "strings" + + "github.com/google/uuid" + "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/tool/tbot/botfs" + "github.com/gravitational/teleport/tool/tbot/config" + "github.com/gravitational/teleport/tool/tbot/identity" + "github.com/gravitational/trace" +) + +// RootUID is the UID of the root user +const RootUID = "0" + +// aclTestFailedMessage is printed when an ACL test fails. +const aclTestFailedMessage = "ACLs are not usable for destination %s; " + + "Change the destination's ACL mode to `off` to silence this warning." + +// getInitArtifacts returns a map of all desired artifacts for the destination +func getInitArtifacts(destination *config.DestinationConfig) (map[string]bool, error) { + // true = directory, false = regular file + toCreate := map[string]bool{} + + // Collect all base artifacts and filter for the destination. + for _, artifact := range identity.GetArtifacts() { + if artifact.Matches(destination.Kinds...) { + toCreate[artifact.Key] = false + } + } + + // Collect all config template artifacts. + for _, templateConfig := range destination.Configs { + template, err := templateConfig.GetConfigTemplate() + if err != nil { + return nil, trace.Wrap(err) + } + + for _, file := range template.Describe() { + toCreate[file.Name] = file.IsDir + } + } + + return toCreate, nil +} + +// getExistingArtifacts fetches all entries in a destination directory +func getExistingArtifacts(dir string) (map[string]bool, error) { + existing := map[string]bool{} + + entries, err := os.ReadDir(dir) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, entry := range entries { + existing[entry.Name()] = entry.IsDir() + } + + return existing, nil +} + +// diffArtifacts computes the difference of two artifact sets +func diffArtifacts(a, b map[string]bool) map[string]bool { + diff := map[string]bool{} + + for k, v := range a { + if _, ok := b[k]; !ok { + diff[k] = v + } + } + + return diff +} + +// testACL creates a temporary file and attempts to apply an ACL to it. If the +// ACL is successfully applied, returns nil; otherwise, returns the error. +func testACL(directory string, ownerUser *user.User, opts *botfs.ACLOptions) error { + // Note: we need to create the test file in the dest dir to ensure we + // actually test the target filesystem. + id, err := uuid.NewRandom() + if err != nil { + return trace.Wrap(err) + } + + testFile := filepath.Join(directory, id.String()) + if err := botfs.Create(testFile, false, botfs.SymlinksInsecure); err != nil { + return trace.Wrap(err) + } + + defer func() { + err := os.Remove(testFile) + if err != nil { + log.Debugf("Failed to delete ACL test file %q", testFile) + } + }() + + //nolint:staticcheck // staticcheck doesn't like nop implementations in fs_other.go + if err := botfs.ConfigureACL(testFile, ownerUser, opts); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// ensurePermissionsParams sets permissions options +type ensurePermissionsParams struct { + // dirPath is the directory containing the file + dirPath string + + // symlinksMode configures symlink attack mitigation behavior + symlinksMode botfs.SymlinksMode + + // ownerUser is the user that should own the file + ownerUser *user.User + + // ownerGroup is the group that should own the file + ownerGroup *user.Group + + // aclOptions contains configuration for ACLs, if they are in use. nil if + // ACLs are disabled + aclOptions *botfs.ACLOptions + + // toCreate is a set of files that will be newly created, used to reduce + // log spam when configuring new files + toCreate map[string]bool +} + +// ensurePermissions verifies permissions on the given path and, when +// possible, attempts to fix permissions / ACLs on any misconfigured paths. +func ensurePermissions(params *ensurePermissionsParams, key string, isDir bool) error { + path := filepath.Join(params.dirPath, key) + + //nolint:staticcheck // this entirely innocuous line generates "related + // information" lints for a false positive staticcheck lint relating to + // nop function implementations in fs_other.go. + stat, err := os.Stat(path) + if err != nil { + return trace.Wrap(err) + } + + cleanPath := filepath.Clean(path) + resolved, err := filepath.EvalSymlinks(path) + if err != nil { + return trace.Wrap(err) + } + + // Precomputed flag to determine if certain log messages should be hidden. + // We expect ownership, ACLs, etc to be wrong on initial create so warnings + // are not desirable. + _, newlyCreated := params.toCreate[key] + verboseLogging := key != "" && !newlyCreated + + // This is unlikely as CreateSecure() refuses to follow symlinks, but users + // could move things around after the fact. + if cleanPath != resolved { + switch params.symlinksMode { + case botfs.SymlinksSecure: + return trace.BadParameter("Path %q contains symlinks which is not "+ + "allowed for security reasons.", path) + case botfs.SymlinksInsecure: + // do nothing + default: + log.Warnf("Path %q contains symlinks and may be subject to symlink "+ + "attacks. If this is intentional, consider setting `symlinks: "+ + "insecure` in destination config.", path) + } + } + + // A few conditions we won't try to handle... + if isDir && !stat.IsDir() { + return trace.BadParameter("File %s is expected to be a directory but is a file", path) + } else if !isDir && stat.IsDir() { + return trace.BadParameter("File %s is expected to be a file but is a directory", path) + } + + currentUser, err := user.Current() + if err != nil { + return trace.Wrap(err) + } + + // Correct ownership. + ownedByDesiredOwner, err := botfs.IsOwnedBy(stat, params.ownerUser) + if err != nil { + log.WithError(err).Debugf("Could not determine file ownership of %q", path) + + // Can't read file ownership on this platform (e.g. Windows), so always + // attempt to chown (which does work on Windows) + ownedByDesiredOwner = false + } + + if !ownedByDesiredOwner { + // If we're not running as root, this will probably fail. + if currentUser.Uid != RootUID && runtime.GOOS != constants.WindowsOS { + log.Warnf("Not running as root, ownership change is likely to fail.") + } + + uid, err := strconv.Atoi(params.ownerUser.Uid) + if err != nil { + return trace.Wrap(err) + } + + gid, err := strconv.Atoi(params.ownerGroup.Gid) + if err != nil { + return trace.Wrap(err) + } + + if verboseLogging { + log.Warnf("Ownership of %q is incorrect and will be corrected to %s", path, params.ownerUser.Username) + } + + err = os.Chown(path, uid, gid) + if err != nil { + return trace.Wrap(err) + } + } + + if params.aclOptions != nil { + // We can verify ACLs as any user with read, but can only correct ACLs + // as root or the owner. + // Note that we rely on VerifyACL to return some error if permissions + // are incorrect. + + //nolint:staticcheck // staticcheck doesn't like nop implementations in fs_other.go + err = botfs.VerifyACL(path, params.aclOptions) + //nolint:staticcheck + if err != nil && (currentUser.Uid == RootUID || currentUser.Uid == params.ownerUser.Uid) { + if verboseLogging { + log.Warnf("ACL for %q is not correct and will be corrected: %v", path, err) + } + + return trace.Wrap(botfs.ConfigureACL(path, params.ownerUser, params.aclOptions)) + } else if err != nil { + log.Errorf("ACL for %q is incorrect but `tbot init` must be run "+ + "as root or the owner (%s) to correct it: %v", + path, params.ownerUser.Username, err) + return trace.AccessDenied("Elevated permissions required") + } + + // ACL is valid. + return nil + } + + desiredMode := botfs.DefaultMode + if stat.IsDir() { + desiredMode = botfs.DefaultDirMode + } + + // Using regular permissions, so attempt to correct the file mode. + if stat.Mode().Perm() != desiredMode { + if err := os.Chmod(path, desiredMode); err != nil { + return trace.Wrap(err, "Could not fix permissions on file %q, expected %#o", path, desiredMode) + } + + log.Infof("Corrected permissions on %q from %#o to %#o", path, stat.Mode().Perm(), botfs.DefaultMode) + } + + return nil +} + +// parseOwnerString parses and looks up an user and group from a user:group +// owner string. +func parseOwnerString(owner string) (*user.User, *user.Group, error) { + ownerParts := strings.Split(owner, ":") + if len(ownerParts) != 2 { + return nil, nil, trace.BadParameter("invalid owner string: %q", owner) + } + + ownerUser, err := user.Lookup(ownerParts[0]) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + ownerGroup, err := user.LookupGroup(ownerParts[1]) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + return ownerUser, ownerGroup, nil +} + +// getOwner determines the user/group owner given a CLI parameter (--owner) +// and desired default value. An empty defaultOwner defaults to the current +// user and their primary group. +func getOwner(cliOwner, defaultOwner string) (*user.User, *user.Group, error) { + if cliOwner != "" { + // If --owner is set, always use it. + log.Debugf("Attempting to use explicitly requested owner: %s", cliOwner) + return parseOwnerString(cliOwner) + } + + if defaultOwner != "" { + log.Debugf("Attempting to use default owner: %s", defaultOwner) + // If a default owner is specified, try it instead. + return parseOwnerString(defaultOwner) + } + + log.Debugf("Will use current user as owner.") + // Otherwise, return the current user and group + currentUser, err := user.Current() + if err != nil { + return nil, nil, trace.Wrap(err) + } + + currentGroup, err := user.LookupGroupId(currentUser.Gid) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + return currentUser, currentGroup, nil +} + +// getAndTestACLOptions gets options needed to configure an ACL from CLI +// options and attempts to configure a test ACL to validate them. Ownership is +// not validated here. +func getAndTestACLOptions(cf *config.CLIConf, destDir string) (*user.User, *user.Group, *botfs.ACLOptions, error) { + if cf.BotUser == "" { + return nil, nil, nil, trace.BadParameter("--bot-user must be set") + } + + if cf.ReaderUser == "" { + return nil, nil, nil, trace.BadParameter("--reader-user must be set") + } + + botUser, err := user.Lookup(cf.BotUser) + if err != nil { + return nil, nil, nil, trace.Wrap(err) + } + + botGroup, err := user.LookupGroupId(botUser.Gid) + if err != nil { + return nil, nil, nil, trace.Wrap(err) + } + + readerUser, err := user.Lookup(cf.ReaderUser) + if err != nil { + return nil, nil, nil, trace.Wrap(err) + } + + opts := botfs.ACLOptions{ + BotUser: botUser, + ReaderUser: readerUser, + } + + // Default to letting the bot own the destination, since by this point we + // know the bot user definitely exists and is a reasonable owner choice. + defaultOwner := fmt.Sprintf("%s:%s", botUser.Username, botGroup.Name) + + ownerUser, ownerGroup, err := getOwner(cf.Owner, defaultOwner) + if err != nil { + return nil, nil, nil, trace.Wrap(err) + } + + err = testACL(destDir, ownerUser, &opts) + if err != nil && trace.IsAccessDenied(err) { + return nil, nil, nil, trace.Wrap(err, "The destination %q does not appear to be writable", destDir) + } else if err != nil { + return nil, nil, nil, trace.Wrap(err, "ACL support may need to be enabled for the filesystem.") + } + + return ownerUser, ownerGroup, &opts, nil +} + +func onInit(botConfig *config.BotConfig, cf *config.CLIConf) error { + var destination *config.DestinationConfig + var err error + + // First, resolve the correct destination. If using a config file with only + // 1 destination we can assume we want to init that one; otherwise, + // --init-dir is required. + if cf.InitDir == "" { + if len(botConfig.Destinations) == 1 { + destination = botConfig.Destinations[0] + } else { + return trace.BadParameter("A destination to initialize must be specified with --init-dir") + } + } else { + destination, err = botConfig.GetDestinationByPath(cf.InitDir) + if err != nil { + return trace.WrapWithMessage(err, "Could not find specified destination %q", cf.InitDir) + } + + if destination == nil { + // TODO: in the future if/when other backends are supported, + // destination might be nil because the user tried to enter a non + // filesystem path, so this error message could be misleading. + return trace.NotFound("Cannot initialize destination %q because "+ + "it has not been configured.", cf.InitDir) + } + } + + destImpl, err := destination.GetDestination() + if err != nil { + return trace.Wrap(err) + } + + destDir, ok := destImpl.(*config.DestinationDirectory) + if !ok { + return trace.BadParameter("`tbot init` only supports directory destinations") + } + + log.Infof("Initializing destination: %s", destImpl) + + // Create the directory if needed. We haven't checked directory ownership, + // but it will fail when the ACLs are created if anything is misconfigured. + if err := destDir.Init(); err != nil { + return trace.Wrap(err) + } + + // Next, test if we want + have ACL support, and set aclOpts if we do. + // Desired owner is derived from the ACL mode. + var aclOpts *botfs.ACLOptions + var ownerUser *user.User + var ownerGroup *user.Group + + switch destDir.ACLs { + case botfs.ACLRequired, botfs.ACLTry: + log.Debug("Testing for ACL support...") + + // Awkward control flow here, but we want these to fail together. + ownerUser, ownerGroup, aclOpts, err = getAndTestACLOptions(cf, destDir.Path) + if err != nil { + if destDir.ACLs == botfs.ACLRequired { + // ACLs were specifically requested (vs "try" mode), so fail. + return trace.Wrap(err, aclTestFailedMessage, destImpl) + } + + // Otherwise, fall back to no ACL with a warning. + log.WithError(err).Warnf(aclTestFailedMessage, destImpl) + aclOpts = nil + + // We'll also need to re-fetch the owner as the defaults are + // different in the fallback case. + ownerUser, ownerGroup, err = getOwner(cf.Owner, "") + if err != nil { + return trace.Wrap(err) + } + } else if aclOpts.ReaderUser.Uid == ownerUser.Uid { + log.Warnf("The destination owner (%s) and reader (%s) are the "+ + "same. This will break OpenSSH.", aclOpts.ReaderUser.Username, + ownerUser.Username) + } + default: + log.Info("ACLs disabled for this destination.") + ownerUser, ownerGroup, err = getOwner(cf.Owner, "") + if err != nil { + return trace.Wrap(err) + } + } + + // Next, resolve what we want and what we already have. + desired, err := getInitArtifacts(destination) + if err != nil { + return trace.Wrap(err) + } + + existing, err := getExistingArtifacts(destDir.Path) + if err != nil { + return trace.Wrap(err) + } + + toCreate := diffArtifacts(desired, existing) + toRemove := diffArtifacts(existing, desired) + + // Based on this, create any new files. + if len(toCreate) > 0 { + log.Infof("Attempting to create: %v", toCreate) + + for key, isDir := range toCreate { + path := filepath.Join(destDir.Path, key) + if err := botfs.Create(path, isDir, destDir.Symlinks); err != nil { + return trace.Wrap(err) + } + + log.Infof("Created: %s", path) + } + } else { + log.Info("Nothing to create.") + } + + // ... and warn about / remove any unneeded files. + if len(toRemove) > 0 && cf.Clean { + log.Infof("Attempting to remove: %v", toRemove) + + var errors []error + + for key := range toRemove { + path := filepath.Join(destDir.Path, key) + + if err := os.RemoveAll(path); err != nil { + errors = append(errors, err) + } else { + log.Infof("Removed: %s", path) + } + } + + if err := trace.NewAggregate(errors...); err != nil { + return trace.Wrap(err) + } + } else if len(toRemove) > 0 { + log.Warnf("Unexpected files found in destination directory, consider " + + "removing it manually or rerunning `tbot init` with the `--clean` " + + "flag.") + } else { + log.Info("Nothing to remove.") + } + + params := ensurePermissionsParams{ + dirPath: destDir.Path, + aclOptions: aclOpts, + ownerUser: ownerUser, + ownerGroup: ownerGroup, + symlinksMode: destDir.Symlinks, + toCreate: toCreate, + } + + // Check and set permissions on the directory itself. + if err := ensurePermissions(¶ms, "", true); err != nil { + return trace.Wrap(err) + } + + // Lastly, set and check permissions on all the desired files. + for key, isDir := range desired { + if err := ensurePermissions(¶ms, key, isDir); err != nil { + return trace.Wrap(err) + } + } + + log.Infof("Destination %s has been initialized. Note that these files "+ + "will be empty and invalid until the bot issues certificates.", + destImpl) + + return nil +} diff --git a/tool/tbot/init_test.go b/tool/tbot/init_test.go new file mode 100644 index 0000000000000..d154e0cb39480 --- /dev/null +++ b/tool/tbot/init_test.go @@ -0,0 +1,271 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + "os/user" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/tool/tbot/botfs" + "github.com/gravitational/teleport/tool/tbot/config" + "github.com/gravitational/teleport/tool/tbot/identity" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" +) + +// usernamesToTry contains a list of usernames we can use as ACL targets in +// testing. +var usernamesToTry = []string{"nobody", "ci", "root"} + +func contains(entries []string, entry string) bool { + for _, e := range entries { + if e == entry { + return true + } + } + + return false +} + +// filterUsers returns the input list of usernames except for those in the +// exclude list. +func filterUsers(usernames, exclude []string) []string { + ret := []string{} + + for _, username := range usernames { + if !contains(exclude, username) { + ret = append(ret, username) + } + } + + return ret +} + +// findUser attempts to find a usable user on the local system from the given +// list of usernames and returns the first match found. +func findUser(usernamesToTry, usernamesToExclude []string) (*user.User, error) { + filtered := filterUsers(usernamesToTry, usernamesToExclude) + for _, username := range filtered { + u, err := user.Lookup(username) + if err == nil { + return u, nil + } + } + + return nil, trace.NotFound("No users found matching %+v (excluding %+v)", usernamesToTry, usernamesToExclude) +} + +// getACLOptions returns sane ACLOptions for this platform. +func getACLOptions() (*botfs.ACLOptions, error) { + if runtime.GOOS != constants.LinuxOS { + return nil, trace.NotImplemented("Unsupported platform") + } + + user, err := user.Current() + if err != nil { + return nil, trace.Wrap(err) + } + + exclude := []string{user.Name} + + // Find a set of users we can test against. + readerUser, err := findUser(usernamesToTry, exclude) + if trace.IsNotFound(err) { + return nil, trace.NotFound("Not enough usable users for testing ACLs") + } else if err != nil { + return nil, trace.Wrap(err) + } + + exclude = append(exclude, readerUser.Name) + botUser, err := findUser(usernamesToTry, exclude) + if trace.IsNotFound(err) { + return nil, trace.NotFound("Not enough suitable users found for testing ACLs.") + } else if err != nil { + return nil, trace.Wrap(err) + } + + return &botfs.ACLOptions{ + ReaderUser: readerUser, + BotUser: botUser, + }, nil +} + +// testConfigFromCLI creates a BotConfig from the given CLI config. +func testConfigFromCLI(t *testing.T, cf *config.CLIConf) *config.BotConfig { + cfg, err := config.FromCLIConf(cf) + require.NoError(t, err) + + return cfg +} + +// testConfigFromString parses a YAML config file from a string. +func testConfigFromString(t *testing.T, yaml string) *config.BotConfig { + cfg, err := config.ReadConfig(strings.NewReader(yaml)) + require.NoError(t, err) + + return cfg +} + +// validateFileDestinations ensures all files in a destination exist on disk as +// expected, and returns the destination. +func validateFileDestination(t *testing.T, dest *config.DestinationConfig) *config.DestinationDirectory { + destImpl, err := dest.GetDestination() + require.NoError(t, err) + + destDir, ok := destImpl.(*config.DestinationDirectory) + require.True(t, ok) + + for _, art := range identity.GetArtifacts() { + if !art.Matches(dest.Kinds...) { + continue + } + + require.FileExists(t, filepath.Join(destDir.Path, art.Key)) + } + + return destDir +} + +// TestInit ensures defaults work regardless of host platform. With no bot user +// specified, this never tries to use ACLs. +func TestInit(t *testing.T) { + dir := t.TempDir() + cf := &config.CLIConf{ + AuthServer: "example.com", + DestinationDir: dir, + } + cfg := testConfigFromCLI(t, cf) + + // Run init. + require.NoError(t, onInit(cfg, cf)) + + // Make sure everything was created. + _ = validateFileDestination(t, cfg.Destinations[0]) +} + +// TestInitMaybeACLs tests defaults with ACLs possibly enabled, by supplying +// bot and reader users. +func TestInitMaybeACLs(t *testing.T) { + opts, err := getACLOptions() + if trace.IsNotImplemented(err) { + t.Skipf("%+v", err) + } else if trace.IsNotFound(err) { + t.Skipf("%+v", err) + } + require.NoError(t, err) + + hasACLSupport, err := botfs.HasACLSupport() + require.NoError(t, err) + + currentUser, err := user.Current() + require.NoError(t, err) + + currentGroup, err := user.LookupGroupId(currentUser.Gid) + require.NoError(t, err) + + // Determine if we expect init to use ACLs. + expectACLs := false + if hasACLSupport { + if err := testACL(t.TempDir(), currentUser, opts); err == nil { + expectACLs = true + } + } + + // Note: we'll use the current user as owner as that's the only way to + // guarantee ACL write access. + dir := t.TempDir() + cf := &config.CLIConf{ + AuthServer: "example.com", + DestinationDir: dir, + BotUser: opts.BotUser.Username, + ReaderUser: opts.ReaderUser.Username, + + // This isn't a default, but unfortunately we need to specify a + // non-nobody owner for CI purposes. + Owner: fmt.Sprintf("%s:%s", currentUser.Name, currentGroup.Name), + } + cfg := testConfigFromCLI(t, cf) + + // Run init. + require.NoError(t, onInit(cfg, cf)) + + // Make sure everything was created. + destDir := validateFileDestination(t, cfg.Destinations[0]) + + // If we expect ACLs, verify them. + if expectACLs { + require.NoError(t, destDir.Verify(identity.ListKeys(cfg.Destinations[0].Kinds...))) + } else { + t.Logf("Skipping ACL check on %q as they should not be supported.", dir) + } +} + +// testInitSymlinksTemplate is a config template with a configurable symlinks +// mode and ACLs disabled. +const testInitSymlinksTemplate = ` +auth_server: example.com +destinations: + - directory: + path: %s + acls: off + symlinks: %s +` + +// TestInitSymlink tests tbot init with a symlink in the path. +func TestInitSymlink(t *testing.T) { + secureWriteSupported, err := botfs.HasSecureWriteSupport() + require.NoError(t, err) + if !secureWriteSupported { + t.Skip("Secure write not supported on this system.") + } + + dir := t.TempDir() + + realPath := filepath.Join(dir, "data") + dataDir := filepath.Join(dir, "data-symlink") + require.NoError(t, os.Symlink(realPath, dataDir)) + + // Should fail due to symlink in path. + cfg := testConfigFromString(t, fmt.Sprintf(testInitSymlinksTemplate, dataDir, botfs.SymlinksSecure)) + require.Error(t, onInit(cfg, &config.CLIConf{})) + + // Should succeed when writing to the dir directly. + cfg = testConfigFromString(t, fmt.Sprintf(testInitSymlinksTemplate, realPath, botfs.SymlinksSecure)) + require.NoError(t, onInit(cfg, &config.CLIConf{})) + + // Make sure everything was created. + _ = validateFileDestination(t, cfg.Destinations[0]) +} + +// TestInitSymlinksInsecure should work on all platforms. +func TestInitSymlinkInsecure(t *testing.T) { + dir := t.TempDir() + + realPath := filepath.Join(dir, "data") + dataDir := filepath.Join(dir, "data-symlink") + require.NoError(t, os.Symlink(realPath, dataDir)) + + // Should succeed due to SymlinksInsecure + cfg := testConfigFromString(t, fmt.Sprintf(testInitSymlinksTemplate, dataDir, botfs.SymlinksInsecure)) + require.Error(t, onInit(cfg, &config.CLIConf{})) +} diff --git a/tool/tbot/main.go b/tool/tbot/main.go index 5c7c941c4e1c2..484a73f97caeb 100644 --- a/tool/tbot/main.go +++ b/tool/tbot/main.go @@ -17,10 +17,15 @@ limitations under the License. package main import ( + "bytes" "context" + "crypto/sha256" + "encoding/hex" "fmt" "os" + "os/signal" "strings" + "syscall" "time" "github.com/gravitational/teleport" @@ -35,6 +40,7 @@ import ( "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/tool/tbot/config" + "github.com/gravitational/teleport/tool/tbot/destination" "github.com/gravitational/teleport/tool/tbot/identity" "github.com/gravitational/trace" "github.com/kr/pretty" @@ -62,28 +68,40 @@ func Run(args []string) error { var cf config.CLIConf utils.InitLogger(utils.LoggingForDaemon, logrus.InfoLevel) - app := utils.InitCLIParser("tbot", "tbot: Teleport Credential Bot").Interspersed(false) + app := utils.InitCLIParser("tbot", "tbot: Teleport Machine ID").Interspersed(false) app.Flag("debug", "Verbose logging to stdout").Short('d').BoolVar(&cf.Debug) - app.Flag("config", "tbot.yaml path").Short('c').StringVar(&cf.ConfigPath) + app.Flag("config", "Path to a configuration file. Defaults to `/etc/tbot.yaml` if unspecified.").Short('c').StringVar(&cf.ConfigPath) + app.HelpFlag.Short('h') + + versionCmd := app.Command("version", "Print the version") startCmd := app.Command("start", "Starts the renewal bot, writing certificates to the data dir at a set interval.") - startCmd.Flag("auth-server", "Specify the Teleport auth server host").Short('a').Envar(authServerEnvVar).StringVar(&cf.AuthServer) + startCmd.Flag("auth-server", "Address of the Teleport Auth Server (On-Prem installs) or Proxy Server (Cloud installs).").Short('a').Envar(authServerEnvVar).StringVar(&cf.AuthServer) startCmd.Flag("token", "A bot join token, if attempting to onboard a new bot; used on first connect.").Envar(tokenEnvVar).StringVar(&cf.Token) - startCmd.Flag("ca-pin", "A repeatable auth server CA hash to pin; used on first connect.").StringsVar(&cf.CAPins) - startCmd.Flag("data-dir", "Directory to store internal bot data.").StringVar(&cf.DataDir) - startCmd.Flag("destination-dir", "Directory to write generated certificates").StringVar(&cf.DestinationDir) - startCmd.Flag("certificate-ttl", "TTL of generated certificates").Default("60m").DurationVar(&cf.CertificateTTL) - startCmd.Flag("renew-interval", "Interval at which certificates are renewed; must be less than the certificate TTL.").DurationVar(&cf.RenewInterval) - startCmd.Flag("join-method", "Method to use to join the cluster.").Default("token").EnumVar(&cf.JoinMethod, "token", "iam") + startCmd.Flag("ca-pin", "CA pin to validate the Teleport Auth Server; used on first connect.").StringsVar(&cf.CAPins) + startCmd.Flag("data-dir", "Directory to store internal bot data. Access to this directory should be limited.").StringVar(&cf.DataDir) + startCmd.Flag("destination-dir", "Directory to write short-lived machine certificates.").StringVar(&cf.DestinationDir) + startCmd.Flag("certificate-ttl", "TTL of short-lived machine certificates.").Default("60m").DurationVar(&cf.CertificateTTL) + startCmd.Flag("renewal-interval", "Interval at which short-lived certificates are renewed; must be less than the certificate TTL.").DurationVar(&cf.RenewalInterval) + startCmd.Flag("join-method", "Method to use to join the cluster, can be \"token\" or \"iam\".").Default(config.DefaultJoinMethod).EnumVar(&cf.JoinMethod, "token", "iam") + startCmd.Flag("oneshot", "If set, quit after the first renewal.").BoolVar(&cf.Oneshot) + + initCmd := app.Command("init", "Initialize a certificate destination directory for writes from a separate bot user.") + initCmd.Flag("destination-dir", "Directory to write short-lived machine certificates to.").StringVar(&cf.DestinationDir) + initCmd.Flag("owner", "Defines Linux \"user:group\" owner of \"--destination-dir\". Defaults to the Linux user running tbot if unspecified.").StringVar(&cf.Owner) + initCmd.Flag("bot-user", "Enables POSIX ACLs and defines Linux user that can read/write short-lived certificates to \"--destination-dir\".").StringVar(&cf.BotUser) + initCmd.Flag("reader-user", "Enables POSIX ACLs and defines Linux user that will read short-lived certificates from \"--destination-dir\".").StringVar(&cf.ReaderUser) + initCmd.Flag("init-dir", "If using a config file and multiple destinations are configured, controls which destination dir to configure.").StringVar(&cf.InitDir) + initCmd.Flag("clean", "If set, remove unexpected files and directories from the destination.").BoolVar(&cf.Clean) configCmd := app.Command("config", "Parse and dump a config file").Hidden() - initCmd := app.Command("init", "Initialize a certificate destination directory.") - - watchCmd := app.Command("watch", "Watch a destination directory for changes.") + watchCmd := app.Command("watch", "Watch a destination directory for changes.").Hidden() + utils.UpdateAppUsageTemplate(app, args) command, err := app.Parse(args) if err != nil { + app.Usage(args) return trace.Wrap(err) } @@ -98,12 +116,14 @@ func Run(args []string) error { } switch command { + case versionCmd.FullCommand(): + err = onVersion() case startCmd.FullCommand(): err = onStart(botConfig) case configCmd.FullCommand(): err = onConfig(botConfig) case initCmd.FullCommand(): - err = onInit(botConfig) + err = onInit(botConfig, &cf) case watchCmd.FullCommand(): err = onWatch(botConfig) default: @@ -114,47 +134,65 @@ func Run(args []string) error { return err } +func onVersion() error { + utils.PrintVersion() + return nil +} + func onConfig(botConfig *config.BotConfig) error { pretty.Println(botConfig) return nil } -func onInit(botConfig *config.BotConfig) error { - return trace.NotImplemented("init not yet implemented") -} - func onWatch(botConfig *config.BotConfig) error { return trace.NotImplemented("watch not yet implemented") } func onStart(botConfig *config.BotConfig) error { + if botConfig.AuthServer == "" { + return trace.BadParameter("an auth or proxy server must be set via --auth-server or configuration") + } + + // First, try to make sure all destinations are usable. + if err := checkDestinations(botConfig); err != nil { + return trace.Wrap(err) + } + // Start by loading the bot's primary destination. dest, err := botConfig.Storage.GetDestination() if err != nil { return trace.Wrap(err, "could not read bot storage destination from config") } - var authClient auth.ClientI - - // TODO: graceful shutdown via signal; see #7066 + reloadChan := make(chan struct{}) ctx, cancel := context.WithCancel(context.Background()) defer cancel() + go handleSignals(reloadChan, cancel) + + configTokenHashBytes := []byte{} + if botConfig.Onboarding != nil && botConfig.Onboarding.Token != "" { + sha := sha256.Sum256([]byte(botConfig.Onboarding.Token)) + configTokenHashBytes = []byte(hex.EncodeToString(sha[:])) + } + + var authClient auth.ClientI + // First, attempt to load an identity from storage. - ident, err := identity.LoadIdentity(dest) - if err == nil { - identStr, err := describeIdentity(ident) + ident, err := identity.LoadIdentity(dest, identity.BotKinds()...) + if err == nil && !hasTokenChanged(ident.TokenHashBytes, configTokenHashBytes) { + identStr, err := describeTLSIdentity(ident) if err != nil { return trace.Wrap(err) } log.Infof("Successfully loaded bot identity, %s", identStr) - // TODO: we should cache the token; if --token is provided but - // different, assume the user is attempting to start with a new - // identity. (May want to store a sha256 has to avoid storing the - // token directly.) + if err := checkIdentity(ident); err != nil { + return trace.Wrap(err) + } + if botConfig.Onboarding != nil { log.Warn("Note: onboarding config ignored as identity was loaded from persistent storage") } @@ -167,9 +205,19 @@ func onStart(botConfig *config.BotConfig) error { // If the identity can't be loaded, assume we're starting fresh and // need to generate our initial identity from a token + if ident != nil { + // If ident is set here, we detected a token change above. + log.Warnf("Detected a token change, will attempt to fetch a new identity.") + } + // TODO: validate that errors from LoadIdentity are sanely typed; we // actually only want to ignore NotFound errors + // Verify we can write to the destination. + if err := identity.VerifyWrite(dest); err != nil { + return trace.Wrap(err, "Could not write to destination %s, aborting.", dest) + } + // Get first identity ident, err = getIdentityFromToken(botConfig) if err != nil { @@ -187,24 +235,18 @@ func onStart(botConfig *config.BotConfig) error { return trace.Wrap(err, "unable to communicate with auth server") } - identStr, err := describeIdentity(ident) + identStr, err := describeTLSIdentity(ident) if err != nil { return trace.Wrap(err) } log.Infof("Successfully generated new bot identity, %s", identStr) log.Debugf("Storing new bot identity to %s", dest) - if err := identity.SaveIdentity(ident, dest); err != nil { + if err := identity.SaveIdentity(ident, dest, identity.BotKinds()...); err != nil { return trace.Wrap(err, "unable to save generated identity back to destination") } } - // TODO: handle cases where an identity exists on disk but we might _not_ - // want to use it: - // - identity has expired - // - user provided a new token - // - ??? - watcher, err := authClient.NewWatcher(ctx, types.Watch{ Kinds: []types.WatchKind{{ Kind: types.KindCertAuthority, @@ -218,7 +260,100 @@ func onStart(botConfig *config.BotConfig) error { defer watcher.Close() - return renewLoop(ctx, botConfig, authClient, ident) + return renewLoop(ctx, botConfig, authClient, ident, reloadChan) +} + +func hasTokenChanged(configTokenBytes, identityBytes []byte) bool { + if len(configTokenBytes) == 0 || len(identityBytes) == 0 { + return false + } + + return !bytes.Equal(identityBytes, configTokenBytes) +} + +// checkDestinations checks all destinations and tries to create any that +// don't already exist. +func checkDestinations(cfg *config.BotConfig) error { + // Note: This is vaguely problematic as we don't recommend that users + // store renewable certs under the same user as end-user certs. That said, + // - if the destination was properly created via tbot init this is a no-op + // - if users intend to follow that advice but miss a step, it should fail + // due to lack of permissions + storage, err := cfg.Storage.GetDestination() + if err != nil { + return trace.Wrap(err) + } + + // TODO: consider warning if ownership of all destintions is not expected. + + if err := storage.Init(); err != nil { + return trace.Wrap(err) + } + + for _, dest := range cfg.Destinations { + destImpl, err := dest.GetDestination() + if err != nil { + return trace.Wrap(err) + } + + if err := destImpl.Init(); err != nil { + return trace.Wrap(err) + } + } + + return nil +} + +// checkIdentity performs basic startup checks on an identity and loudly warns +// end users if it is unlikely to work. +func checkIdentity(ident *identity.Identity) error { + var validAfter time.Time + var validBefore time.Time + + if ident.X509Cert != nil { + validAfter = ident.X509Cert.NotBefore + validBefore = ident.X509Cert.NotAfter + } else if ident.SSHCert != nil { + validAfter = time.Unix(int64(ident.SSHCert.ValidAfter), 0) + validBefore = time.Unix(int64(ident.SSHCert.ValidBefore), 0) + } else { + return trace.BadParameter("identity is invalid and contains no certificates") + } + + now := time.Now().UTC() + if now.After(validBefore) { + log.Errorf( + "Identity has expired. The renewal is likely to fail. (expires: %s, current time: %s)", + validBefore.Format(time.RFC3339), + now.Format(time.RFC3339), + ) + } else if now.Before(validAfter) { + log.Warnf( + "Identity is not yet valid. Confirm that the system time is correct. (valid after: %s, current time: %s)", + validAfter.Format(time.RFC3339), + now.Format(time.RFC3339), + ) + } + + return nil +} + +// handleSignals handles incoming Unix signals. +func handleSignals(reload chan struct{}, cancel context.CancelFunc) { + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGHUP, syscall.SIGUSR1) + + for signal := range signals { + switch signal { + case syscall.SIGINT: + log.Info("Received interrupt, cancelling...") + cancel() + return + case syscall.SIGHUP, syscall.SIGUSR1: + log.Info("Received reload signal, reloading...") + reload <- struct{}{} + } + } } func watchCARotations(watcher types.Watcher) { @@ -253,7 +388,7 @@ func getIdentityFromToken(cfg *config.BotConfig) (*identity.Identity, error) { return nil, trace.WrapWithMessage(err, "unable to generate new keypairs") } - log.Info("attempting to generate new identity from token") + log.Info("Attempting to generate new identity from token") params := auth.RegisterParams{ Token: cfg.Onboarding.Token, ID: auth.IdentityID{ @@ -271,7 +406,13 @@ func getIdentityFromToken(cfg *config.BotConfig) (*identity.Identity, error) { if err != nil { return nil, trace.Wrap(err) } - ident, err := identity.ReadIdentityFromKeyPair(tlsPrivateKey, sshPublicKey, certs) + sha := sha256.Sum256([]byte(params.Token)) + tokenHash := hex.EncodeToString(sha[:]) + ident, err := identity.ReadIdentityFromStore(&identity.LoadIdentityParams{ + PrivateKeyBytes: tlsPrivateKey, + PublicKeyBytes: sshPublicKey, + TokenHashBytes: []byte(tokenHash), + }, certs, identity.BotKinds()...) return ident, trace.Wrap(err) } @@ -299,18 +440,18 @@ func renewIdentityViaAuth( // Ask the auth server to generate a new set of certs with a new // expiration date. certs, err := client.GenerateUserCerts(ctx, proto.UserCertsRequest{ - PublicKey: currentIdentity.SSHPublicKeyBytes, - Username: currentIdentity.XCert.Subject.CommonName, + PublicKey: currentIdentity.PublicKeyBytes, + Username: currentIdentity.X509Cert.Subject.CommonName, Expires: time.Now().Add(cfg.CertificateTTL), }) if err != nil { return nil, trace.Wrap(err) } - newIdentity, err := identity.ReadIdentityFromKeyPair( - currentIdentity.KeyBytes, - currentIdentity.SSHPublicKeyBytes, + newIdentity, err := identity.ReadIdentityFromStore( + currentIdentity.Params(), certs, + identity.BotKinds()..., ) if err != nil { return nil, trace.Wrap(err) @@ -331,26 +472,31 @@ func fetchDefaultRoles(ctx context.Context, roleGetter services.RoleGetter, botR return conditions.Roles, nil } -// describeIdentity writes an informational message about the given identity to +// describeTLSIdentity writes an informational message about the given identity to // the log. -func describeIdentity(ident *identity.Identity) (string, error) { - tlsIdent, err := tlsca.FromSubject(ident.XCert.Subject, ident.XCert.NotAfter) +func describeTLSIdentity(ident *identity.Identity) (string, error) { + cert := ident.X509Cert + if cert == nil { + return "", trace.BadParameter("attempted to describe TLS identity without TLS credentials") + } + + tlsIdent, err := tlsca.FromSubject(cert.Subject, cert.NotAfter) if err != nil { return "", trace.Wrap(err, "bot TLS certificate can not be parsed as an identity") } var principals []string - for _, principal := range ident.Cert.ValidPrincipals { + for _, principal := range tlsIdent.Principals { if !strings.HasPrefix(principal, constants.NoLoginPrefix) { principals = append(principals, principal) } } - duration := time.Second * time.Duration(ident.Cert.ValidBefore-ident.Cert.ValidAfter) + duration := cert.NotAfter.Sub(cert.NotBefore) return fmt.Sprintf( - "valid: after=%v, before=%v, duration=%s | properties: renewable=%v, disallow-reissue=%v, roles=%v, principals=%v, generation=%v", - time.Unix(int64(ident.Cert.ValidAfter), 0), - time.Unix(int64(ident.Cert.ValidBefore), 0), + "valid: after=%v, before=%v, duration=%s | kind=tls, renewable=%v, disallow-reissue=%v, roles=%v, principals=%v, generation=%v", + cert.NotBefore.Format(time.RFC3339), + cert.NotAfter.Format(time.RFC3339), duration, tlsIdent.Renewable, tlsIdent.DisallowReissue, @@ -360,136 +506,242 @@ func describeIdentity(ident *identity.Identity) (string, error) { ), nil } -func renewLoop(ctx context.Context, cfg *config.BotConfig, client auth.ClientI, ident *identity.Identity) error { - // TODO: failures here should probably not just end the renewal loop, there - // should be some retry / back-off logic. - - // TODO: what should this interval be? should it be user configurable? - // Also, must be < the validity period. - // TODO: validate that cert is actually renewable. +// describeSSHIdentity writes an informational message about the given SSH +// identity to the log. +func describeSSHIdentity(ident *identity.Identity) (string, error) { + cert := ident.SSHCert + if cert == nil { + return "", trace.BadParameter("attempted to describe SSH identity without SSH credentials") + } - log.Infof("Beginning renewal loop: ttl=%s interval=%s", cfg.CertificateTTL, cfg.RenewInterval) - if cfg.RenewInterval > cfg.CertificateTTL { - log.Errorf( - "Certificate TTL (%s) is shorter than the renewal interval (%s). The next renewal is likely to fail.", - cfg.CertificateTTL, - cfg.RenewInterval, - ) + renewable := false + if _, ok := cert.Extensions[teleport.CertExtensionRenewable]; ok { + renewable = true } - // Determine where the bot should write its internal data (renewable cert - // etc) - botDestination, err := cfg.Storage.GetDestination() - if err != nil { - return trace.Wrap(err) + disallowReissue := false + if _, ok := cert.Extensions[teleport.CertExtensionDisallowReissue]; ok { + disallowReissue = true } - ticker := time.NewTicker(cfg.RenewInterval) - defer ticker.Stop() - for { - log.Debug("Attempting to renew bot certificates...") - newIdentity, err := renewIdentityViaAuth(ctx, client, ident, cfg) - if err != nil { - return trace.Wrap(err) + var roles []string + if rolesStr, ok := cert.Extensions[teleport.CertExtensionTeleportRoles]; ok { + if actualRoles, err := services.UnmarshalCertRoles(rolesStr); err == nil { + roles = actualRoles } + } - identStr, err := describeIdentity(ident) - if err != nil { - return trace.Wrap(err, "Could not describe bot identity at %s", botDestination) + var principals []string + for _, principal := range cert.ValidPrincipals { + if !strings.HasPrefix(principal, constants.NoLoginPrefix) { + principals = append(principals, principal) } + } + + duration := time.Second * time.Duration(cert.ValidBefore-cert.ValidAfter) + return fmt.Sprintf( + "valid: after=%v, before=%v, duration=%s | kind=ssh, renewable=%v, disallow-reissue=%v, roles=%v, principals=%v", + time.Unix(int64(cert.ValidAfter), 0).Format(time.RFC3339), + time.Unix(int64(cert.ValidBefore), 0).Format(time.RFC3339), + duration, + renewable, + disallowReissue, + roles, + principals, + ), nil +} + +// renew performs a single renewal +func renew( + ctx context.Context, cfg *config.BotConfig, client auth.ClientI, + ident *identity.Identity, botDestination destination.Destination, +) (auth.ClientI, *identity.Identity, error) { + // Make sure we can still write to the bot's destination. + if err := identity.VerifyWrite(botDestination); err != nil { + return nil, nil, trace.Wrap(err, "Cannot write to destination %s, aborting.", botDestination) + } + + log.Debug("Attempting to renew bot certificates...") + newIdentity, err := renewIdentityViaAuth(ctx, client, ident, cfg) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + identStr, err := describeTLSIdentity(ident) + if err != nil { + return nil, nil, trace.Wrap(err, "Could not describe bot identity at %s", botDestination) + } - log.Infof("Successfully renewed bot certificates, %s", identStr) + log.Infof("Successfully renewed bot certificates, %s", identStr) - // TODO: warn if duration < certTTL? would indicate TTL > server's max renewable cert TTL - // TODO: error if duration < renewalInterval? next renewal attempt will fail + // TODO: warn if duration < certTTL? would indicate TTL > server's max renewable cert TTL + // TODO: error if duration < renewalInterval? next renewal attempt will fail - // Immediately attempt to reconnect using the new identity (still - // haven't persisted the known-good certs). - newClient, err := authenticatedUserClientFromIdentity(ctx, newIdentity, cfg.AuthServer) + // Immediately attempt to reconnect using the new identity (still + // haven't persisted the known-good certs). + newClient, err := authenticatedUserClientFromIdentity(ctx, newIdentity, cfg.AuthServer) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + // Attempt a request to make sure our client works. + // TODO: consider a retry/backoff loop. + if _, err := newClient.Ping(ctx); err != nil { + return nil, nil, trace.Wrap(err, "unable to communicate with auth server") + } + + log.Debug("Auth client now using renewed credentials.") + client = newClient + ident = newIdentity + + // Now that we're sure the new creds work, persist them. + if err := identity.SaveIdentity(newIdentity, botDestination, identity.BotKinds()...); err != nil { + return nil, nil, trace.Wrap(err) + } + + // Determine the default role list based on the bot role. The role's + // name should match the certificate's Key ID (user and role names + // should all match bot-$name) + botResourceName := ident.X509Cert.Subject.CommonName + defaultRoles, err := fetchDefaultRoles(ctx, client, botResourceName) + if err != nil { + log.WithError(err).Warnf("Unable to determine default roles, no roles will be requested if unspecified") + defaultRoles = []string{} + } + + // Next, generate impersonated certs + expires := ident.X509Cert.NotAfter + for _, dest := range cfg.Destinations { + destImpl, err := dest.GetDestination() if err != nil { - return trace.Wrap(err) + return nil, nil, trace.Wrap(err) } - // Attempt a request to make sure our client works. - // TODO: consider a retry/backoff loop. - if _, err := newClient.Ping(ctx); err != nil { - return trace.Wrap(err, "unable to communicate with auth server") + // Check the ACLs. We can't fix them, but we can warn if they're + // misconfigured. We'll need to precompute a list of keys to check. + // Note: This may only log a warning, depending on configuration. + if err := destImpl.Verify(identity.ListKeys(dest.Kinds...)); err != nil { + return nil, nil, trace.Wrap(err) } - log.Debug("Auth client now using renewed credentials.") - client = newClient - ident = newIdentity + // Ensure this destination is also writable. This is a hard fail if + // ACLs are misconfigured, regardless of configuration. + // TODO: consider not making these a hard error? e.g. write other + // destinations even if this one is broken? + if err := identity.VerifyWrite(destImpl); err != nil { + return nil, nil, trace.Wrap(err, "Could not write to destination %s, aborting.", destImpl) + } - // Now that we're sure the new creds work, persist them. - if err := identity.SaveIdentity(newIdentity, botDestination); err != nil { - return trace.Wrap(err) + var desiredRoles []string + if len(dest.Roles) > 0 { + desiredRoles = dest.Roles + } else { + log.Debugf("Destination specified no roles, defaults will be requested: %v", defaultRoles) + desiredRoles = defaultRoles } - // Determine the default role list based on the bot role. The role's - // name should match the certificate's Key ID (user and role names - // should all match bot-$name) - defaultRoles, err := fetchDefaultRoles(ctx, client, ident.Cert.KeyId) + impersonatedIdent, err := generateImpersonatedIdentity(ctx, client, ident, expires, desiredRoles, dest.Kinds) if err != nil { - log.WithError(err).Warnf("Unable to determine default roles, no roles will be requested if unspecified") - defaultRoles = []string{} + return nil, nil, trace.Wrap(err, "Failed to generate impersonated certs for %s: %+v", destImpl, err) } - // Next, generate impersonated certs - expires := time.Unix(int64(ident.Cert.ValidBefore), 0) - for _, dest := range cfg.Destinations { - destImpl, err := dest.GetDestination() + var impersonatedIdentStr string + if dest.ContainsKind(identity.KindTLS) { + impersonatedIdentStr, err = describeTLSIdentity(impersonatedIdent) if err != nil { - return trace.Wrap(err) + return nil, nil, trace.Wrap(err, "could not describe impersonated certs for destination %s", destImpl) } - - var desiredRoles []string - if len(dest.Roles) > 0 { - desiredRoles = dest.Roles - } else { - log.Debugf("Destination specified no roles, defaults will be requested: %v", defaultRoles) - desiredRoles = defaultRoles - } - - impersonatedIdent, err := generateImpersonatedIdentity(ctx, client, ident, expires, desiredRoles) + } else { + // Note: kinds must contain at least 1 of TLS or SSH + impersonatedIdentStr, err = describeSSHIdentity(impersonatedIdent) if err != nil { - return trace.Wrap(err, "Failed to generate impersonated certs for %s: %+v", destImpl, err) + return nil, nil, trace.Wrap(err, "could not describe impersonated certs for destination %s", destImpl) } + } + log.Infof("Successfully renewed impersonated certificates for %s, %s", destImpl, impersonatedIdentStr) + + if err := identity.SaveIdentity(impersonatedIdent, destImpl, dest.Kinds...); err != nil { + return nil, nil, trace.Wrap(err, "failed to save impersonated identity to destination %s", destImpl) + } - impersonatedIdentStr, err := describeIdentity(impersonatedIdent) + for _, templateConfig := range dest.Configs { + template, err := templateConfig.GetConfigTemplate() if err != nil { - return trace.Wrap(err, "could not describe impersonated certs for destination %s", destImpl) + return nil, nil, trace.Wrap(err) } - log.Infof("Successfully renewed impersonated certificates for %s, %s", destImpl, impersonatedIdentStr) - if err := identity.SaveIdentity(impersonatedIdent, destImpl); err != nil { - return trace.Wrap(err, "failed to save impersonated identity to destination %s", destImpl) + if err := template.Render(ctx, client, impersonatedIdent, dest); err != nil { + log.WithError(err).Warnf("Failed to render config template %+v", templateConfig) } + } + } - for _, templateConfig := range dest.Configs { - template, err := templateConfig.GetConfigTemplate() - if err != nil { - return trace.Wrap(err) - } + log.Infof("Persisted new certificates to disk. Next renewal in approximately %s", cfg.RenewalInterval) + return newClient, newIdentity, nil +} - if err := template.Render(ctx, client, impersonatedIdent, dest); err != nil { - log.WithError(err).Warnf("Failed to render config template %+v", templateConfig) - } - } +func renewLoop(ctx context.Context, cfg *config.BotConfig, client auth.ClientI, ident *identity.Identity, reloadChan chan struct{}) error { + // TODO: failures here should probably not just end the renewal loop, there + // should be some retry / back-off logic. + + // TODO: what should this interval be? should it be user configurable? + // Also, must be < the validity period. + // TODO: validate that cert is actually renewable. + + log.Infof("Beginning renewal loop: ttl=%s interval=%s", cfg.CertificateTTL, cfg.RenewalInterval) + if cfg.RenewalInterval > cfg.CertificateTTL { + log.Errorf( + "Certificate TTL (%s) is shorter than the renewal interval (%s). The next renewal is likely to fail.", + cfg.CertificateTTL, + cfg.RenewalInterval, + ) + } + + // Determine where the bot should write its internal data (renewable cert + // etc) + botDestination, err := cfg.Storage.GetDestination() + if err != nil { + return trace.Wrap(err) + } + + ticker := time.NewTicker(cfg.RenewalInterval) + defer ticker.Stop() + for { + newClient, newIdentity, err := renew(ctx, cfg, client, ident, botDestination) + if err != nil { + return trace.Wrap(err) } - log.Infof("Persisted new certificates to disk. Next renewal in approximately %s", cfg.RenewInterval) + if cfg.Oneshot { + log.Info("Oneshot mode enabled, exiting successfully.") + break + } + + client = newClient + ident = newIdentity select { case <-ctx.Done(): return nil case <-ticker.C: continue + case <-reloadChan: + continue } - } + + return nil } +// authenticatedUserClientFromIdentity creates a new auth client from the given +// identity. Note that depending on the connection address given, this may +// attempt to connect via the proxy and therefore requires both SSH and TLS +// credentials. func authenticatedUserClientFromIdentity(ctx context.Context, id *identity.Identity, authServer string) (auth.ClientI, error) { + if id.SSHCert == nil || id.X509Cert == nil { + return nil, trace.BadParameter("auth client requires a fully formed identity") + } + tlsConfig, err := id.TLSConfig(nil /* cipherSuites */) if err != nil { return nil, trace.Wrap(err) @@ -522,6 +774,7 @@ func generateImpersonatedIdentity( currentIdentity *identity.Identity, expires time.Time, roleRequests []string, + kinds []identity.ArtifactKind, ) (*identity.Identity, error) { // TODO: enforce expiration > renewal period (by what margin?) @@ -537,7 +790,7 @@ func generateImpersonatedIdentity( // expiration date. certs, err := client.GenerateUserCerts(ctx, proto.UserCertsRequest{ PublicKey: publicKey, - Username: currentIdentity.XCert.Subject.CommonName, + Username: currentIdentity.X509Cert.Subject.CommonName, Expires: expires, RoleRequests: roleRequests, }) @@ -566,11 +819,10 @@ func generateImpersonatedIdentity( certs.TLSCACerts = append(certs.TLSCACerts, pemBytes) } - newIdentity, err := identity.ReadIdentityFromKeyPair( - privateKey, - publicKey, - certs, - ) + newIdentity, err := identity.ReadIdentityFromStore(&identity.LoadIdentityParams{ + PrivateKeyBytes: privateKey, + PublicKeyBytes: publicKey, + }, certs, kinds...) if err != nil { return nil, trace.Wrap(err) } diff --git a/tool/tctl/common/app_command.go b/tool/tctl/common/app_command.go index 3c5554a018c86..711e18b7d01ce 100644 --- a/tool/tctl/common/app_command.go +++ b/tool/tctl/common/app_command.go @@ -25,8 +25,12 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" + libclient "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/service" ) @@ -37,6 +41,13 @@ type AppsCommand struct { // format is the output format (text, json, or yaml) format string + searchKeywords string + predicateExpr string + labels string + + // verbose sets whether full table output should be shown for labels + verbose bool + // appsList implements the "tctl apps ls" subcommand. appsList *kingpin.CmdClause } @@ -47,7 +58,11 @@ func (c *AppsCommand) Initialize(app *kingpin.Application, config *service.Confi apps := app.Command("apps", "Operate on applications registered with the cluster.") c.appsList = apps.Command("ls", "List all applications registered with the cluster.") - c.appsList.Flag("format", "Output format, 'text', 'json', or 'yaml'").Default("text").StringVar(&c.format) + c.appsList.Flag("format", "Output format, 'text', 'json', or 'yaml'").Default(teleport.Text).StringVar(&c.format) + c.appsList.Arg("labels", labelHelp).StringVar(&c.labels) + c.appsList.Flag("search", searchHelp).StringVar(&c.searchKeywords) + c.appsList.Flag("query", queryHelp).StringVar(&c.predicateExpr) + c.appsList.Flag("verbose", "Verbose table output, shows full label output").Short('v').BoolVar(&c.verbose) } // TryRun attempts to run subcommands like "apps ls". @@ -63,27 +78,52 @@ func (c *AppsCommand) TryRun(cmd string, client auth.ClientI) (match bool, err e // ListApps prints the list of applications that have recently sent heartbeats // to the cluster. -func (c *AppsCommand) ListApps(client auth.ClientI) error { - servers, err := client.GetApplicationServers(context.TODO(), apidefaults.Namespace) +func (c *AppsCommand) ListApps(clt auth.ClientI) error { + ctx := context.TODO() + + labels, err := libclient.ParseLabelSpec(c.labels) if err != nil { return trace.Wrap(err) } - coll := &appServerCollection{servers: servers} + + var servers []types.AppServer + resources, err := client.GetResourcesWithFilters(ctx, clt, proto.ListResourcesRequest{ + ResourceType: types.KindAppServer, + Labels: labels, + PredicateExpression: c.predicateExpr, + SearchKeywords: libclient.ParseSearchKeywords(c.searchKeywords, ','), + }) + switch { + // Underlying ListResources for app servers not available, use fallback. + // Using filter flags with older auth will silently do nothing. + // + // DELETE IN 11.0.0 + case trace.IsNotImplemented(err): + servers, err = clt.GetApplicationServers(ctx, apidefaults.Namespace) + if err != nil { + return trace.Wrap(err) + } + case err != nil: + return trace.Wrap(err) + default: + servers, err = types.ResourcesWithLabels(resources).AsAppServers() + if err != nil { + return trace.Wrap(err) + } + } + + coll := &appServerCollection{servers: servers, verbose: c.verbose} switch c.format { case teleport.Text: - err = coll.writeText(os.Stdout) + return trace.Wrap(coll.writeText(os.Stdout)) case teleport.JSON: - err = coll.writeJSON(os.Stdout) + return trace.Wrap(coll.writeJSON(os.Stdout)) case teleport.YAML: - err = coll.writeYAML(os.Stdout) + return trace.Wrap(coll.writeYAML(os.Stdout)) default: return trace.BadParameter("unknown format %q", c.format) } - if err != nil { - return trace.Wrap(err) - } - return nil } var appMessageTemplate = template.Must(template.New("app").Parse(`The invite token: {{.token}}. diff --git a/tool/tctl/common/auth_command.go b/tool/tctl/common/auth_command.go index 6dce83157fa70..f89acecb3155a 100644 --- a/tool/tctl/common/auth_command.go +++ b/tool/tctl/common/auth_command.go @@ -19,7 +19,6 @@ import ( "crypto/x509/pkix" "encoding/pem" "fmt" - "io/ioutil" "net" "net/url" "os" @@ -133,15 +132,16 @@ func (a *AuthCommand) Initialize(app *kingpin.Application, config *service.Confi // TryRun takes the CLI command as an argument (like "auth gen") and executes it // or returns match=false if 'cmd' does not belong to it func (a *AuthCommand) TryRun(cmd string, client auth.ClientI) (match bool, err error) { + ctx := context.Background() switch cmd { case a.authGenerate.FullCommand(): - err = a.GenerateKeys() + err = a.GenerateKeys(ctx) case a.authExport.FullCommand(): - err = a.ExportAuthorities(client) + err = a.ExportAuthorities(ctx, client) case a.authSign.FullCommand(): - err = a.GenerateAndSignKeys(client) + err = a.GenerateAndSignKeys(ctx, client) case a.authRotate.FullCommand(): - err = a.RotateCertAuthority(client) + err = a.RotateCertAuthority(ctx, client) default: return false, nil } @@ -153,7 +153,7 @@ var allowedCertificateTypes = []string{"user", "host", "tls-host", "tls-user", " // ExportAuthorities outputs the list of authorities in OpenSSH compatible formats // If --type flag is given, only prints keys for CAs of this type, otherwise // prints all keys -func (a *AuthCommand) ExportAuthorities(client auth.ClientI) error { +func (a *AuthCommand) ExportAuthorities(ctx context.Context, client auth.ClientI) error { var typesToExport []types.CertAuthType // this means to export TLS authority @@ -162,11 +162,11 @@ func (a *AuthCommand) ExportAuthorities(client auth.ClientI) error { // "tls-host" and "tls-user" were added later to allow export of the user // TLS CA. case "tls", "tls-host": - return a.exportTLSAuthority(client, types.HostCA, false) + return a.exportTLSAuthority(ctx, client, types.HostCA, false) case "tls-user": - return a.exportTLSAuthority(client, types.UserCA, false) + return a.exportTLSAuthority(ctx, client, types.UserCA, false) case "tls-user-der", "windows": - return a.exportTLSAuthority(client, types.UserCA, true) + return a.exportTLSAuthority(ctx, client, types.UserCA, true) } // if no --type flag is given, export all types @@ -188,7 +188,7 @@ func (a *AuthCommand) ExportAuthorities(client auth.ClientI) error { // trusted ones) var authorities []types.CertAuthority for _, at := range typesToExport { - cas, err := client.GetCertAuthorities(at, a.exportPrivateKeys) + cas, err := client.GetCertAuthorities(ctx, at, a.exportPrivateKeys) if err != nil { return trace.Wrap(err) } @@ -257,12 +257,13 @@ func (a *AuthCommand) ExportAuthorities(client auth.ClientI) error { return nil } -func (a *AuthCommand) exportTLSAuthority(client auth.ClientI, typ types.CertAuthType, unpackPEM bool) error { +func (a *AuthCommand) exportTLSAuthority(ctx context.Context, client auth.ClientI, typ types.CertAuthType, unpackPEM bool) error { clusterName, err := client.GetDomainName() if err != nil { return trace.Wrap(err) } certAuthority, err := client.GetCertAuthority( + ctx, types.CertAuthID{Type: typ, DomainName: clusterName}, a.exportPrivateKeys, ) @@ -295,19 +296,19 @@ func (a *AuthCommand) exportTLSAuthority(client auth.ClientI, typ types.CertAuth } // GenerateKeys generates a new keypair -func (a *AuthCommand) GenerateKeys() error { - keygen := native.New(context.TODO(), native.PrecomputeKeys(0)) +func (a *AuthCommand) GenerateKeys(ctx context.Context) error { + keygen := native.New(ctx, native.PrecomputeKeys(0)) defer keygen.Close() privBytes, pubBytes, err := keygen.GenerateKeyPair("") if err != nil { return trace.Wrap(err) } - err = ioutil.WriteFile(a.genPubPath, pubBytes, 0600) + err = os.WriteFile(a.genPubPath, pubBytes, 0600) if err != nil { return trace.Wrap(err) } - err = ioutil.WriteFile(a.genPrivPath, privBytes, 0600) + err = os.WriteFile(a.genPrivPath, privBytes, 0600) if err != nil { return trace.Wrap(err) } @@ -317,23 +318,23 @@ func (a *AuthCommand) GenerateKeys() error { } // GenerateAndSignKeys generates a new keypair and signs it for role -func (a *AuthCommand) GenerateAndSignKeys(clusterAPI auth.ClientI) error { +func (a *AuthCommand) GenerateAndSignKeys(ctx context.Context, clusterAPI auth.ClientI) error { switch a.outputFormat { case identityfile.FormatDatabase, identityfile.FormatMongo, identityfile.FormatCockroach, identityfile.FormatRedis: - return a.generateDatabaseKeys(clusterAPI) + return a.generateDatabaseKeys(ctx, clusterAPI) } switch { case a.genUser != "" && a.genHost == "": - return a.generateUserKeys(clusterAPI) + return a.generateUserKeys(ctx, clusterAPI) case a.genUser == "" && a.genHost != "": - return a.generateHostKeys(clusterAPI) + return a.generateHostKeys(ctx, clusterAPI) default: return trace.BadParameter("--user or --host must be specified") } } // RotateCertAuthority starts or restarts certificate authority rotation process -func (a *AuthCommand) RotateCertAuthority(client auth.ClientI) error { +func (a *AuthCommand) RotateCertAuthority(ctx context.Context, client auth.ClientI) error { req := auth.RotateRequest{ Type: types.CertAuthType(a.rotateType), GracePeriod: &a.rotateGracePeriod, @@ -344,7 +345,7 @@ func (a *AuthCommand) RotateCertAuthority(client auth.ClientI) error { } else { req.Mode = types.RotationModeAuto } - if err := client.RotateCertAuthority(req); err != nil { + if err := client.RotateCertAuthority(ctx, req); err != nil { return err } if a.rotateTargetPhase != "" { @@ -356,7 +357,7 @@ func (a *AuthCommand) RotateCertAuthority(client auth.ClientI) error { return nil } -func (a *AuthCommand) generateHostKeys(clusterAPI auth.ClientI) error { +func (a *AuthCommand) generateHostKeys(ctx context.Context, clusterAPI auth.ClientI) error { // only format=openssh is supported if a.outputFormat != identityfile.FormatOpenSSH { return trace.BadParameter("invalid --format flag %q, only %q is supported", a.outputFormat, identityfile.FormatOpenSSH) @@ -383,7 +384,7 @@ func (a *AuthCommand) generateHostKeys(clusterAPI auth.ClientI) error { if err != nil { return trace.Wrap(err) } - hostCAs, err := clusterAPI.GetCertAuthorities(types.HostCA, false) + hostCAs, err := clusterAPI.GetCertAuthorities(ctx, types.HostCA, false) if err != nil { return trace.Wrap(err) } @@ -410,17 +411,17 @@ func (a *AuthCommand) generateHostKeys(clusterAPI auth.ClientI) error { // generateDatabaseKeys generates a new unsigned key and signs it with Teleport // CA for database access. -func (a *AuthCommand) generateDatabaseKeys(clusterAPI auth.ClientI) error { +func (a *AuthCommand) generateDatabaseKeys(ctx context.Context, clusterAPI auth.ClientI) error { key, err := client.NewKey() if err != nil { return trace.Wrap(err) } - return a.generateDatabaseKeysForKey(clusterAPI, key) + return a.generateDatabaseKeysForKey(ctx, clusterAPI, key) } // generateDatabaseKeysForKey signs the provided unsigned key with Teleport CA // for database access. -func (a *AuthCommand) generateDatabaseKeysForKey(clusterAPI auth.ClientI, key *client.Key) error { +func (a *AuthCommand) generateDatabaseKeysForKey(ctx context.Context, clusterAPI auth.ClientI, key *client.Key) error { principals := strings.Split(a.genHost, ",") if len(principals) == 1 && principals[0] == "" { return trace.BadParameter("at least one hostname must be specified via --host flag") @@ -455,7 +456,7 @@ func (a *AuthCommand) generateDatabaseKeysForKey(clusterAPI auth.ClientI, key *c if err != nil { return trace.Wrap(err) } - resp, err := clusterAPI.GenerateDatabaseCert(context.TODO(), + resp, err := clusterAPI.GenerateDatabaseCert(ctx, &proto.DatabaseCertRequest{ CSR: csr, // Important to include SANs since CommonName has been deprecated @@ -553,13 +554,13 @@ cockroach start \ To enable mutual TLS on your Redis server, add the following to your redis.conf: tls-ca-cert-file /path/to/{{.output}}.cas -tls-cert-file /path/to/{{.output}}.crt +tls-cert-file /path/to/{{.output}}.crt tls-key-file /path/to/{{.output}}.key tls-protocols "TLSv1.2 TLSv1.3" `)) ) -func (a *AuthCommand) generateUserKeys(clusterAPI auth.ClientI) error { +func (a *AuthCommand) generateUserKeys(ctx context.Context, clusterAPI auth.ClientI) error { // Validate --proxy flag. if err := a.checkProxyAddr(clusterAPI); err != nil { return trace.Wrap(err) @@ -589,7 +590,7 @@ func (a *AuthCommand) generateUserKeys(clusterAPI auth.ClientI) error { } key.ClusterName = a.leafCluster - if err := a.checkKubeCluster(clusterAPI); err != nil { + if err := a.checkKubeCluster(ctx, clusterAPI); err != nil { return trace.Wrap(err) } @@ -597,12 +598,12 @@ func (a *AuthCommand) generateUserKeys(clusterAPI auth.ClientI) error { var certUsage proto.UserCertsRequest_CertUsage if a.appName != "" { - server, err := getApplicationServer(context.TODO(), clusterAPI, a.appName) + server, err := getApplicationServer(ctx, clusterAPI, a.appName) if err != nil { return trace.Wrap(err) } - appSession, err := clusterAPI.CreateAppSession(context.TODO(), types.CreateAppSessionRequest{ + appSession, err := clusterAPI.CreateAppSession(ctx, types.CreateAppSessionRequest{ Username: a.genUser, PublicAddr: server.GetApp().GetPublicAddr(), ClusterName: a.leafCluster, @@ -622,7 +623,7 @@ func (a *AuthCommand) generateUserKeys(clusterAPI auth.ClientI) error { reqExpiry := time.Now().UTC().Add(a.genTTL) // Request signed certs from `auth` server. - certs, err := clusterAPI.GenerateUserCerts(context.TODO(), proto.UserCertsRequest{ + certs, err := clusterAPI.GenerateUserCerts(ctx, proto.UserCertsRequest{ PublicKey: key.Pub, Username: a.genUser, Expires: reqExpiry, @@ -638,7 +639,7 @@ func (a *AuthCommand) generateUserKeys(clusterAPI auth.ClientI) error { key.Cert = certs.SSH key.TLSCert = certs.TLS - hostCAs, err := clusterAPI.GetCertAuthorities(types.HostCA, false) + hostCAs, err := clusterAPI.GetCertAuthorities(ctx, types.HostCA, false) if err != nil { return trace.Wrap(err) } @@ -699,7 +700,7 @@ func (a *AuthCommand) checkLeafCluster(clusterAPI auth.ClientI) error { } -func (a *AuthCommand) checkKubeCluster(clusterAPI auth.ClientI) error { +func (a *AuthCommand) checkKubeCluster(ctx context.Context, clusterAPI auth.ClientI) error { if a.outputFormat != identityfile.FormatKubernetes && a.kubeCluster != "" { // User set --kube-cluster-name but it's not actually used for the chosen --format. // Print a warning but continue. @@ -719,7 +720,7 @@ func (a *AuthCommand) checkKubeCluster(clusterAPI auth.ClientI) error { return nil } - a.kubeCluster, err = kubeutils.CheckOrSetKubeCluster(context.TODO(), clusterAPI, a.kubeCluster, a.leafCluster) + a.kubeCluster, err = kubeutils.CheckOrSetKubeCluster(ctx, clusterAPI, a.kubeCluster, a.leafCluster) if err != nil && !trace.IsNotFound(err) { return trace.Wrap(err) } @@ -737,7 +738,21 @@ func (a *AuthCommand) checkProxyAddr(clusterAPI auth.ClientI) error { return nil } if a.proxyAddr != "" { - return nil + // User set --proxy. Validate it and set its scheme to https in case it was omitted. + u, err := url.Parse(a.proxyAddr) + if err != nil { + return trace.WrapWithMessage(err, "specified --proxy URL is invalid") + } + switch u.Scheme { + case "": + u.Scheme = "https" + a.proxyAddr = u.String() + return nil + case "http", "https": + return nil + default: + return trace.BadParameter("expected --proxy URL with http or https scheme") + } } // User didn't specify --proxy for kubeconfig. Let's try to guess it. diff --git a/tool/tctl/common/auth_command_test.go b/tool/tctl/common/auth_command_test.go index 078160132d496..1b267066a6606 100644 --- a/tool/tctl/common/auth_command_test.go +++ b/tool/tctl/common/auth_command_test.go @@ -18,7 +18,6 @@ import ( "bytes" "context" "crypto/x509/pkix" - "io/ioutil" "os" "path/filepath" "testing" @@ -42,11 +41,7 @@ import ( func TestAuthSignKubeconfig(t *testing.T) { t.Parallel() - tmpDir, err := ioutil.TempDir("", "auth_command_test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) + tmpDir := t.TempDir() clusterName, err := services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{ ClusterName: "example.com", @@ -104,17 +99,55 @@ func TestAuthSignKubeconfig(t *testing.T) { ac AuthCommand wantAddr string wantCluster string - wantError string + assertErr require.ErrorAssertionFunc }{ { - desc: "--proxy specified", + desc: "valid --proxy URL with valid URL scheme", + ac: AuthCommand{ + output: filepath.Join(tmpDir, "kubeconfig"), + outputFormat: identityfile.FormatKubernetes, + signOverwrite: true, + proxyAddr: "https://proxy-from-flag.example.com", + }, + wantAddr: "https://proxy-from-flag.example.com", + assertErr: require.NoError, + }, + { + desc: "valid --proxy URL with invalid URL scheme", + ac: AuthCommand{ + output: filepath.Join(tmpDir, "kubeconfig"), + outputFormat: identityfile.FormatKubernetes, + signOverwrite: true, + proxyAddr: "file://proxy-from-flag.example.com", + }, + assertErr: func(t require.TestingT, err error, _ ...interface{}) { + require.Error(t, err) + require.Equal(t, err.Error(), "expected --proxy URL with http or https scheme") + }, + }, + { + desc: "valid --proxy URL without URL scheme", ac: AuthCommand{ output: filepath.Join(tmpDir, "kubeconfig"), outputFormat: identityfile.FormatKubernetes, signOverwrite: true, proxyAddr: "proxy-from-flag.example.com", }, - wantAddr: "proxy-from-flag.example.com", + wantAddr: "https://proxy-from-flag.example.com", + assertErr: require.NoError, + }, + { + desc: "invalid --proxy URL", + ac: AuthCommand{ + output: filepath.Join(tmpDir, "kubeconfig"), + outputFormat: identityfile.FormatKubernetes, + signOverwrite: true, + proxyAddr: "1https://proxy-from-flag.example.com", + }, + assertErr: func(t require.TestingT, err error, _ ...interface{}) { + require.Error(t, err) + require.Contains(t, err.Error(), "specified --proxy URL is invalid") + }, }, { desc: "k8s proxy running locally with public_addr", @@ -127,7 +160,8 @@ func TestAuthSignKubeconfig(t *testing.T) { PublicAddrs: []utils.NetAddr{{Addr: "proxy-from-config.example.com:3026"}}, }}}, }, - wantAddr: "https://proxy-from-config.example.com:3026", + wantAddr: "https://proxy-from-config.example.com:3026", + assertErr: require.NoError, }, { desc: "k8s proxy running locally without public_addr", @@ -142,7 +176,8 @@ func TestAuthSignKubeconfig(t *testing.T) { PublicAddrs: []utils.NetAddr{{Addr: "proxy-from-config.example.com:3080"}}, }}, }, - wantAddr: "https://proxy-from-config.example.com:3026", + wantAddr: "https://proxy-from-config.example.com:3026", + assertErr: require.NoError, }, { desc: "k8s proxy from cluster info", @@ -156,7 +191,8 @@ func TestAuthSignKubeconfig(t *testing.T) { }, }}, }, - wantAddr: "https://proxy-from-api.example.com:3026", + wantAddr: "https://proxy-from-api.example.com:3026", + assertErr: require.NoError, }, { desc: "--kube-cluster specified with valid cluster", @@ -172,6 +208,7 @@ func TestAuthSignKubeconfig(t *testing.T) { }}, }, wantCluster: remoteCluster.GetMetadata().Name, + assertErr: require.NoError, }, { desc: "--kube-cluster specified with invalid cluster", @@ -186,19 +223,17 @@ func TestAuthSignKubeconfig(t *testing.T) { }, }}, }, - wantError: "couldn't find leaf cluster named \"doesnotexist.example.com\"", + assertErr: func(t require.TestingT, err error, _ ...interface{}) { + require.Error(t, err) + require.Equal(t, err.Error(), "couldn't find leaf cluster named \"doesnotexist.example.com\"") + }, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { // Generate kubeconfig. - if err = tt.ac.generateUserKeys(client); err != nil && tt.wantError == "" { - t.Fatalf("generating KubeProxyConfig: %v", err) - } - - if tt.wantError != "" && (err == nil || err.Error() != tt.wantError) { - t.Errorf("got error %v, want %v", err, tt.wantError) - } + err := tt.ac.generateUserKeys(context.Background(), client) + tt.assertErr(t, err) // Validate kubeconfig contents. kc, err := kubeconfig.Load(tt.ac.output) @@ -248,7 +283,7 @@ func (c *mockClient) GenerateUserCerts(ctx context.Context, userCertsReq proto.U c.userCertsReq = &userCertsReq return c.userCerts, nil } -func (c *mockClient) GetCertAuthorities(types.CertAuthType, bool, ...services.MarshalOption) ([]types.CertAuthority, error) { +func (c *mockClient) GetCertAuthorities(context.Context, types.CertAuthType, bool, ...services.MarshalOption) ([]types.CertAuthority, error) { return c.cas, nil } func (c *mockClient) GetProxies() ([]types.Server, error) { @@ -362,7 +397,7 @@ func TestCheckKubeCluster(t *testing.T) { leafCluster: tt.leafCluster, outputFormat: tt.outputFormat, } - err := a.checkKubeCluster(client) + err := a.checkKubeCluster(context.Background(), client) tt.assertErr(t, err) require.Equal(t, tt.want, a.kubeCluster) }) @@ -499,7 +534,7 @@ func TestGenerateDatabaseKeys(t *testing.T) { genTTL: time.Hour, } - err = ac.generateDatabaseKeysForKey(authClient, key) + err = ac.generateDatabaseKeysForKey(context.Background(), authClient, key) if test.genKeyErrMsg == "" { require.NoError(t, err) } else { @@ -516,19 +551,19 @@ func TestGenerateDatabaseKeys(t *testing.T) { require.Equal(t, test.outServerNames[0], authClient.dbCertsReq.ServerName) if len(test.outKey) > 0 { - keyBytes, err := ioutil.ReadFile(filepath.Join(test.inOutDir, test.outKeyFile)) + keyBytes, err := os.ReadFile(filepath.Join(test.inOutDir, test.outKeyFile)) require.NoError(t, err) require.Equal(t, test.outKey, keyBytes, "keys match") } if len(test.outCert) > 0 { - certBytes, err := ioutil.ReadFile(filepath.Join(test.inOutDir, test.outCertFile)) + certBytes, err := os.ReadFile(filepath.Join(test.inOutDir, test.outCertFile)) require.NoError(t, err) require.Equal(t, test.outCert, certBytes, "certificates match") } if len(test.outCA) > 0 { - caBytes, err := ioutil.ReadFile(filepath.Join(test.inOutDir, test.outCAFile)) + caBytes, err := os.ReadFile(filepath.Join(test.inOutDir, test.outCAFile)) require.NoError(t, err) require.Equal(t, test.outCA, caBytes, "CA certificates match") } @@ -612,7 +647,7 @@ func TestGenerateAppCertificates(t *testing.T) { genTTL: time.Hour, appName: tc.appName, } - err = ac.generateUserKeys(authClient) + err = ac.generateUserKeys(context.Background(), authClient) tc.assertErr(t, err) if err != nil { return @@ -627,7 +662,7 @@ func TestGenerateAppCertificates(t *testing.T) { require.Equal(t, proto.UserCertsRequest_App, authClient.userCertsReq.Usage) require.Equal(t, expectedRouteToApp, authClient.userCertsReq.RouteToApp) - certBytes, err := ioutil.ReadFile(filepath.Join(tc.outDir, tc.outFileBase+".crt")) + certBytes, err := os.ReadFile(filepath.Join(tc.outDir, tc.outFileBase+".crt")) require.NoError(t, err) require.Equal(t, authClient.userCerts.TLS, certBytes, "certificates match") }) diff --git a/tool/tctl/common/bots_command.go b/tool/tctl/common/bots_command.go index 58e570cb2c882..882727f428054 100644 --- a/tool/tctl/common/bots_command.go +++ b/tool/tctl/common/bots_command.go @@ -34,6 +34,7 @@ import ( "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/service" "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/trace" ) @@ -66,6 +67,7 @@ func (c *BotsCommand) Initialize(app *kingpin.Application, config *service.Confi c.botsAdd.Flag("roles", "Roles the bot is able to assume.").Required().StringVar(&c.botRoles) c.botsAdd.Flag("ttl", "TTL for the bot join token.").DurationVar(&c.tokenTTL) c.botsAdd.Flag("token", "Name of an existing token to use.").StringVar(&c.tokenID) + c.botsAdd.Flag("format", "Output format, 'text' or 'json'").Hidden().Default(teleport.Text).EnumVar(&c.format, teleport.Text, teleport.JSON) // TODO: --ttl for setting a ttl on the join token c.botsRemove = bots.Command("rm", "Permanently remove a certificate renewal bot from the cluster.") @@ -135,10 +137,29 @@ func (c *BotsCommand) ListBots(client auth.ClientI) error { return nil } -var startMessageTemplate = template.Must(template.New("node").Parse(`The bot token: {{.token}} +// bold wraps the given text in an ANSI escape to bold it +func bold(text string) string { + return utils.Color(utils.Bold, text) +} + +var startMessageTemplate = template.Must(template.New("node").Funcs(template.FuncMap{ + "bold": bold, +}).Parse(`The bot token: {{.token}} This token will expire in {{.minutes}} minutes. -Run this on the new bot node to join the cluster: +Optionally, if running the bot under an isolated user account, first initialize +the data directory by running the following command {{ bold "as root" }}: + +> tbot init \ + --destination-dir=./tbot-user \ + --bot-user=tbot \ + --reader-user=alice + +... where "tbot" is the username of the bot's UNIX user, and "alice" is the +UNIX user that will be making use of the certificates. + +Then, run this {{ bold "as the bot user" }} to begin continuously fetching +certificates: > tbot start \ --destination-dir=./tbot-user \ @@ -149,6 +170,9 @@ Run this on the new bot node to join the cluster: Please note: + - The ./tbot-user destination directory can be changed as desired. + - /var/lib/teleport/bot must be accessible to the bot user, or --data-dir + must point to another accessible directory to store internal bot data. - This invitation token will expire in {{.minutes}} minutes - {{.auth_server}} must be reachable from the new node `)) @@ -165,6 +189,16 @@ func (c *BotsCommand) AddBot(client auth.ClientI) error { return trace.WrapWithMessage(err, "error while creating bot") } + if c.format == teleport.JSON { + out, err := json.MarshalIndent(response, "", " ") + if err != nil { + return trace.Wrap(err, "failed to marshal CreateBot response") + } + + fmt.Println(string(out)) + return nil + } + // Calculate the CA pins for this cluster. The CA pins are used by the // client to verify the identity of the Auth Server. localCAResponse, err := client.GetClusterCACert() diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index 72faa13de047d..0a5bed166ff29 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -41,7 +41,8 @@ type ResourceCollection interface { } type roleCollection struct { - roles []types.Role + roles []types.Role + verbose bool } func (r *roleCollection) resources() (res []types.Resource) { @@ -52,17 +53,26 @@ func (r *roleCollection) resources() (res []types.Resource) { } func (r *roleCollection) writeText(w io.Writer) error { - t := asciitable.MakeTable([]string{"Role", "Allowed to login as", "Node Labels", "Access to resources"}) + var rows [][]string for _, r := range r.roles { if r.GetName() == constants.DefaultImplicitRole { continue } - t.AddRow([]string{ + rows = append(rows, []string{ r.GetMetadata().Name, strings.Join(r.GetLogins(types.Allow), ","), printNodeLabels(r.GetNodeLabels(types.Allow)), printActions(r.GetRules(types.Allow))}) } + + headers := []string{"Role", "Allowed to login as", "Node Labels", "Access to resources"} + var t asciitable.Table + if r.verbose { + t = asciitable.MakeTable(headers, rows...) + } else { + t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Access to resources") + } + _, err := t.AsBuffer().WriteTo(w) return trace.Wrap(err) } @@ -116,6 +126,7 @@ func printNodeLabels(labels types.Labels) string { type serverCollection struct { servers []types.Server + verbose bool } func (s *serverCollection) resources() (r []types.Resource) { @@ -126,16 +137,38 @@ func (s *serverCollection) resources() (r []types.Resource) { } func (s *serverCollection) writeText(w io.Writer) error { - t := asciitable.MakeTable([]string{"Nodename", "UUID", "Address", "Labels"}) - for _, s := range s.servers { - t.AddRow([]string{ - s.GetHostname(), s.GetName(), s.GetAddr(), s.LabelsString(), + var rows [][]string + for _, se := range s.servers { + labels := stripInternalTeleportLabels(s.verbose, se.GetAllLabels()) + rows = append(rows, []string{ + se.GetHostname(), se.GetName(), se.GetAddr(), labels, se.GetTeleportVersion(), }) } + headers := []string{"Host", "UUID", "Public Address", "Labels", "Version"} + var t asciitable.Table + if s.verbose { + t = asciitable.MakeTable(headers, rows...) + } else { + t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Labels") + + } _, err := t.AsBuffer().WriteTo(w) return trace.Wrap(err) } +func (s *serverCollection) writeYaml(w io.Writer) error { + return utils.WriteYAML(w, s.servers) +} + +func (s *serverCollection) writeJSON(w io.Writer) error { + data, err := json.MarshalIndent(s.resources(), "", " ") + if err != nil { + return trace.Wrap(err) + } + _, err = w.Write(data) + return trace.Wrap(err) +} + type userCollection struct { users []types.User } @@ -441,6 +474,7 @@ func (c *semaphoreCollection) writeText(w io.Writer) error { type appServerCollection struct { servers []types.AppServer + verbose bool } func (a *appServerCollection) resources() (r []types.Resource) { @@ -451,13 +485,21 @@ func (a *appServerCollection) resources() (r []types.Resource) { } func (a *appServerCollection) writeText(w io.Writer) error { - t := asciitable.MakeTable([]string{"Application", "Host", "Public Address", "URI", "Labels"}) + var rows [][]string for _, server := range a.servers { app := server.GetApp() - t.AddRow([]string{ - app.GetName(), server.GetHostname(), app.GetPublicAddr(), app.GetURI(), app.LabelsString(), - }) + labels := stripInternalTeleportLabels(a.verbose, app.GetAllLabels()) + rows = append(rows, []string{ + server.GetHostname(), app.GetName(), app.GetPublicAddr(), app.GetURI(), labels, server.GetTeleportVersion()}) + } + var t asciitable.Table + headers := []string{"Host", "Name", "Public Address", "URI", "Labels", "Version"} + if a.verbose { + t = asciitable.MakeTable(headers, rows...) + } else { + t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Labels") } + _, err := t.AsBuffer().WriteTo(w) return trace.Wrap(err) } @@ -480,7 +522,8 @@ func (a *appServerCollection) writeYAML(w io.Writer) error { } type appCollection struct { - apps []types.Application + apps []types.Application + verbose bool } func (c *appCollection) resources() (r []types.Resource) { @@ -491,11 +534,18 @@ func (c *appCollection) resources() (r []types.Resource) { } func (c *appCollection) writeText(w io.Writer) error { - t := asciitable.MakeTable([]string{"Name", "Description", "URI", "Public Address", "Labels"}) + var rows [][]string for _, app := range c.apps { - t.AddRow([]string{ - app.GetName(), app.GetDescription(), app.GetURI(), app.GetPublicAddr(), app.LabelsString(), - }) + labels := stripInternalTeleportLabels(c.verbose, app.GetAllLabels()) + rows = append(rows, []string{ + app.GetName(), app.GetDescription(), app.GetURI(), app.GetPublicAddr(), labels, app.GetVersion()}) + } + headers := []string{"Name", "Description", "URI", "Public Address", "Labels", "Version"} + var t asciitable.Table + if c.verbose { + t = asciitable.MakeTable(headers, rows...) + } else { + t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Labels") } _, err := t.AsBuffer().WriteTo(w) return trace.Wrap(err) @@ -590,6 +640,7 @@ func (c *netRestrictionsCollection) writeText(w io.Writer) error { type databaseServerCollection struct { servers []types.DatabaseServer + verbose bool } func (c *databaseServerCollection) resources() (r []types.Resource) { @@ -600,17 +651,25 @@ func (c *databaseServerCollection) resources() (r []types.Resource) { } func (c *databaseServerCollection) writeText(w io.Writer) error { - t := asciitable.MakeTable([]string{"Name", "Protocol", "URI", "Labels", "Hostname", "Version"}) + var rows [][]string for _, server := range c.servers { - t.AddRow([]string{ + labels := stripInternalTeleportLabels(c.verbose, server.GetDatabase().GetAllLabels()) + rows = append(rows, []string{ + server.GetHostname(), server.GetDatabase().GetName(), server.GetDatabase().GetProtocol(), server.GetDatabase().GetURI(), - server.GetDatabase().LabelsString(), - server.GetHostname(), + labels, server.GetTeleportVersion(), }) } + headers := []string{"Host", "Name", "Protocol", "URI", "Labels", "Version"} + var t asciitable.Table + if c.verbose { + t = asciitable.MakeTable(headers, rows...) + } else { + t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Labels") + } _, err := t.AsBuffer().WriteTo(w) return trace.Wrap(err) } @@ -634,6 +693,7 @@ func (c *databaseServerCollection) writeYAML(w io.Writer) error { type databaseCollection struct { databases []types.Database + verbose bool } func (c *databaseCollection) resources() (r []types.Resource) { @@ -644,12 +704,20 @@ func (c *databaseCollection) resources() (r []types.Resource) { } func (c *databaseCollection) writeText(w io.Writer) error { - t := asciitable.MakeTable([]string{"Name", "Protocol", "URI", "Labels"}) + var rows [][]string for _, database := range c.databases { - t.AddRow([]string{ - database.GetName(), database.GetProtocol(), database.GetURI(), database.LabelsString(), + labels := stripInternalTeleportLabels(c.verbose, database.GetAllLabels()) + rows = append(rows, []string{ + database.GetName(), database.GetProtocol(), database.GetURI(), labels, }) } + headers := []string{"Name", "Protocol", "URI", "Labels"} + var t asciitable.Table + if c.verbose { + t = asciitable.MakeTable(headers, rows...) + } else { + t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Labels") + } _, err := t.AsBuffer().WriteTo(w) return trace.Wrap(err) } @@ -723,6 +791,59 @@ func (c *windowsDesktopCollection) writeText(w io.Writer) error { return trace.Wrap(err) } +type windowsDesktopAndService struct { + desktop types.WindowsDesktop + service types.WindowsDesktopService +} + +type windowsDesktopAndServiceCollection struct { + desktops []windowsDesktopAndService + verbose bool +} + +func stripInternalTeleportLabels(verbose bool, labels map[string]string) string { + if verbose { // remove teleport.dev labels unless we're in verbose mode. + return types.LabelsAsString(labels, nil) + } + for key := range labels { + if strings.HasPrefix(key, types.TeleportNamespace+"/") { + delete(labels, key) + } + } + return types.LabelsAsString(labels, nil) +} + +func (c *windowsDesktopAndServiceCollection) writeText(w io.Writer) error { + var rows [][]string + for _, d := range c.desktops { + labels := stripInternalTeleportLabels(c.verbose, d.desktop.GetAllLabels()) + rows = append(rows, []string{d.service.GetHostname(), d.desktop.GetAddr(), + d.desktop.GetDomain(), labels, d.service.GetTeleportVersion()}) + } + headers := []string{"Host", "Address", "AD Domain", "Labels", "Version"} + var t asciitable.Table + if c.verbose { + t = asciitable.MakeTable(headers, rows...) + } else { + t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Labels") + } + _, err := t.AsBuffer().WriteTo(w) + return trace.Wrap(err) +} + +func (c *windowsDesktopAndServiceCollection) writeYAML(w io.Writer) error { + return utils.WriteYAML(w, c.desktops) +} + +func (c *windowsDesktopAndServiceCollection) writeJSON(w io.Writer) error { + data, err := json.MarshalIndent(c.desktops, "", " ") + if err != nil { + return trace.Wrap(err) + } + _, err = w.Write(data) + return trace.Wrap(err) +} + type tokenCollection struct { tokens []types.ProvisionToken } @@ -743,3 +864,47 @@ func (c *tokenCollection) writeText(w io.Writer) error { } return nil } + +type kubeServerCollection struct { + servers []types.Server + verbose bool +} + +func (c *kubeServerCollection) writeText(w io.Writer) error { + var rows [][]string + for _, server := range c.servers { + kubes := server.GetKubernetesClusters() + for _, kube := range kubes { + labels := stripInternalTeleportLabels(c.verbose, + types.CombineLabels(kube.StaticLabels, kube.DynamicLabels)) + rows = append(rows, []string{ + kube.Name, + labels, + server.GetTeleportVersion(), + }) + } + } + headers := []string{"Cluster", "Labels", "Version"} + var t asciitable.Table + if c.verbose { + t = asciitable.MakeTable(headers, rows...) + } else { + t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Labels") + } + + _, err := t.AsBuffer().WriteTo(w) + return trace.Wrap(err) +} + +func (c *kubeServerCollection) writeYAML(w io.Writer) error { + return utils.WriteYAML(w, c.servers) +} + +func (c *kubeServerCollection) writeJSON(w io.Writer) error { + data, err := json.MarshalIndent(c.servers, "", " ") + if err != nil { + return trace.Wrap(err) + } + _, err = w.Write(data) + return trace.Wrap(err) +} diff --git a/tool/tctl/common/db_command.go b/tool/tctl/common/db_command.go index 50e6ff1bad122..11426be1b081d 100644 --- a/tool/tctl/common/db_command.go +++ b/tool/tctl/common/db_command.go @@ -22,8 +22,12 @@ import ( "text/template" "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" + libclient "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/service" "github.com/gravitational/kingpin" @@ -37,6 +41,13 @@ type DBCommand struct { // format is the output format (text, json or yaml). format string + searchKeywords string + predicateExpr string + labels string + + // verbose sets whether full table output should be shown for labels + verbose bool + // dbList implements the "tctl db ls" subcommand. dbList *kingpin.CmdClause } @@ -47,7 +58,11 @@ func (c *DBCommand) Initialize(app *kingpin.Application, config *service.Config) db := app.Command("db", "Operate on databases registered with the cluster.") c.dbList = db.Command("ls", "List all databases registered with the cluster.") - c.dbList.Flag("format", "Output format, 'text', 'json', or 'yaml'").Default("text").StringVar(&c.format) + c.dbList.Flag("format", "Output format, 'text', 'json', or 'yaml'").Default(teleport.Text).StringVar(&c.format) + c.dbList.Arg("labels", labelHelp).StringVar(&c.labels) + c.dbList.Flag("search", searchHelp).StringVar(&c.searchKeywords) + c.dbList.Flag("query", queryHelp).StringVar(&c.predicateExpr) + c.dbList.Flag("verbose", "Verbose table output, shows full label output").Short('v').BoolVar(&c.verbose) } // TryRun attempts to run subcommands like "db ls". @@ -63,26 +78,51 @@ func (c *DBCommand) TryRun(cmd string, client auth.ClientI) (match bool, err err // ListDatabases prints the list of database proxies that have recently sent // heartbeats to the cluster. -func (c *DBCommand) ListDatabases(client auth.ClientI) error { - servers, err := client.GetDatabaseServers(context.TODO(), apidefaults.Namespace) +func (c *DBCommand) ListDatabases(clt auth.ClientI) error { + ctx := context.TODO() + + labels, err := libclient.ParseLabelSpec(c.labels) if err != nil { return trace.Wrap(err) } - coll := &databaseServerCollection{servers: servers} + + var servers []types.DatabaseServer + resources, err := client.GetResourcesWithFilters(ctx, clt, proto.ListResourcesRequest{ + ResourceType: types.KindDatabaseServer, + Labels: labels, + PredicateExpression: c.predicateExpr, + SearchKeywords: libclient.ParseSearchKeywords(c.searchKeywords, ','), + }) + switch { + // Underlying ListResources for db servers not available, use fallback. + // Using filter flags with older auth will silently do nothing. + // + // DELETE IN 11.0.0 + case trace.IsNotImplemented(err): + servers, err = clt.GetDatabaseServers(ctx, apidefaults.Namespace) + if err != nil { + return trace.Wrap(err) + } + case err != nil: + return trace.Wrap(err) + default: + servers, err = types.ResourcesWithLabels(resources).AsDatabaseServers() + if err != nil { + return trace.Wrap(err) + } + } + + coll := &databaseServerCollection{servers: servers, verbose: c.verbose} switch c.format { case teleport.Text: - err = coll.writeText(os.Stdout) + return trace.Wrap(coll.writeText(os.Stdout)) case teleport.JSON: - err = coll.writeJSON(os.Stdout) + return trace.Wrap(coll.writeJSON(os.Stdout)) case teleport.YAML: - err = coll.writeYAML(os.Stdout) + return trace.Wrap(coll.writeYAML(os.Stdout)) default: return trace.BadParameter("unknown format %q", c.format) } - if err != nil { - return trace.Wrap(err) - } - return nil } var dbMessageTemplate = template.Must(template.New("db").Parse(`The invite token: {{.token}}. diff --git a/tool/tctl/common/desktop_command.go b/tool/tctl/common/desktop_command.go new file mode 100644 index 0000000000000..ad92a1b443d6e --- /dev/null +++ b/tool/tctl/common/desktop_command.go @@ -0,0 +1,97 @@ +/* +Copyright 2021 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "context" + "os" + + "github.com/gravitational/kingpin" + "github.com/gravitational/teleport" + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth" + "github.com/gravitational/teleport/lib/service" +) + +// DesktopCommand implements "tctl desktop" group of commands. +type DesktopCommand struct { + config *service.Config + + // format is the output format (text or yaml) + format string + + // verbose sets whether full table output should be shown for labels + verbose bool + + // desktopList implements the "tctl desktop ls" subcommand. + desktopList *kingpin.CmdClause +} + +// Initialize allows DesktopCommand to plug itself into the CLI parser +func (c *DesktopCommand) Initialize(app *kingpin.Application, config *service.Config) { + c.config = config + + desktop := app.Command("desktops", "Operate on registered desktops.") + c.desktopList = desktop.Command("ls", "List all desktops registered with the cluster.") + c.desktopList.Flag("format", "Output format, 'text', 'json' or 'yaml'").Default(teleport.Text).StringVar(&c.format) + c.desktopList.Flag("verbose", "Verbose table output, shows full label output").Short('v').BoolVar(&c.verbose) +} + +// TryRun attempts to run subcommands like "desktop ls". +func (c *DesktopCommand) TryRun(cmd string, client auth.ClientI) (match bool, err error) { + switch cmd { + case c.desktopList.FullCommand(): + err = c.ListDesktop(client) + default: + return false, nil + } + return true, trace.Wrap(err) +} + +// ListDesktop prints the list of desktops that have recently sent heartbeats +// to the cluster. +func (c *DesktopCommand) ListDesktop(client auth.ClientI) error { + desktops, err := client.GetWindowsDesktops(context.TODO(), types.WindowsDesktopFilter{}) + if err != nil { + return trace.Wrap(err) + } + coll := windowsDesktopAndServiceCollection{ + desktops: []windowsDesktopAndService{}, + verbose: c.verbose, + } + ctx := context.Background() + for _, desktop := range desktops { + ds, err := client.GetWindowsDesktopService(ctx, desktop.GetHostID()) + if err != nil { + return trace.Wrap(err) + } + coll.desktops = append(coll.desktops, + windowsDesktopAndService{desktop: desktop, service: ds}) + } + switch c.format { + case teleport.Text: + return trace.Wrap(coll.writeText(os.Stdout)) + case teleport.JSON: + return trace.Wrap(coll.writeJSON(os.Stdout)) + case teleport.YAML: + return trace.Wrap(coll.writeYAML(os.Stdout)) + default: + return trace.BadParameter("unknown format %q", c.format) + } +} diff --git a/tool/tctl/common/kube_command.go b/tool/tctl/common/kube_command.go new file mode 100644 index 0000000000000..079eab31df597 --- /dev/null +++ b/tool/tctl/common/kube_command.go @@ -0,0 +1,84 @@ +/* +Copyright 2021 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "context" + "os" + + "github.com/gravitational/kingpin" + "github.com/gravitational/trace" + + "github.com/gravitational/teleport" + "github.com/gravitational/teleport/lib/auth" + "github.com/gravitational/teleport/lib/service" +) + +// KubeCommand implements "tctl kube" group of commands. +type KubeCommand struct { + config *service.Config + + // format is the output format (text or yaml) + format string + + // verbose sets whether full table output should be shown for labels + verbose bool + + // kubeList implements the "tctl kube ls" subcommand. + kubeList *kingpin.CmdClause +} + +// Initialize allows KubeCommand to plug itself into the CLI parser +func (c *KubeCommand) Initialize(app *kingpin.Application, config *service.Config) { + c.config = config + + kube := app.Command("kube", "Operate on registered kubernetes clusters.") + c.kubeList = kube.Command("ls", "List all kubernetes clusters registered with the cluster.") + c.kubeList.Flag("format", "Output format, 'text', 'json', or 'yaml'").Default(teleport.Text).StringVar(&c.format) + c.kubeList.Flag("verbose", "Verbose table output, shows full label output").Short('v').BoolVar(&c.verbose) +} + +// TryRun attempts to run subcommands like "kube ls". +func (c *KubeCommand) TryRun(cmd string, client auth.ClientI) (match bool, err error) { + switch cmd { + case c.kubeList.FullCommand(): + err = c.ListKube(client) + default: + return false, nil + } + return true, trace.Wrap(err) +} + +// ListKube prints the list of kube clusters that have recently sent heartbeats +// to the cluster. +func (c *KubeCommand) ListKube(client auth.ClientI) error { + kubes, err := client.GetKubeServices(context.TODO()) + if err != nil { + return trace.Wrap(err) + } + coll := &kubeServerCollection{servers: kubes, verbose: c.verbose} + switch c.format { + case teleport.Text: + return trace.Wrap(coll.writeText(os.Stdout)) + case teleport.JSON: + return trace.Wrap(coll.writeJSON(os.Stdout)) + case teleport.YAML: + return trace.Wrap(coll.writeYAML(os.Stdout)) + default: + return trace.BadParameter("unknown format %q", c.format) + } +} diff --git a/tool/tctl/common/node_command.go b/tool/tctl/common/node_command.go index 1212b8ec7db24..5208268d7bca3 100644 --- a/tool/tctl/common/node_command.go +++ b/tool/tctl/common/node_command.go @@ -26,11 +26,16 @@ import ( "time" "github.com/gravitational/kingpin" + "github.com/gravitational/teleport" "github.com/gravitational/trace" + log "github.com/sirupsen/logrus" + "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" + libclient "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/service" "github.com/gravitational/teleport/lib/tlsca" @@ -52,6 +57,16 @@ type NodeCommand struct { // if not specified, is autogenerated token string + searchKeywords string + predicateExpr string + labels string + + // ls output format -- text or json + lsFormat string + + // verbose sets whether full table output should be shown for labels + verbose bool + // CLI subcommands (clauses) nodeAdd *kingpin.CmdClause nodeList *kingpin.CmdClause @@ -67,12 +82,17 @@ func (c *NodeCommand) Initialize(app *kingpin.Application, config *service.Confi c.nodeAdd.Flag("roles", "Comma-separated list of roles for the new node to assume [node]").Default("node").StringVar(&c.roles) c.nodeAdd.Flag("ttl", "Time to live for a generated token").Default(defaults.ProvisioningTokenTTL.String()).DurationVar(&c.ttl) c.nodeAdd.Flag("token", "Custom token to use, autogenerated if not provided").StringVar(&c.token) - c.nodeAdd.Flag("format", "Output format, 'text' or 'json'").Hidden().Default("text").StringVar(&c.format) + c.nodeAdd.Flag("format", "Output format, 'text' or 'json'").Hidden().Default(teleport.Text).StringVar(&c.format) c.nodeAdd.Alias(AddNodeHelp) c.nodeList = nodes.Command("ls", "List all active SSH nodes within the cluster") c.nodeList.Flag("namespace", "Namespace of the nodes").Default(apidefaults.Namespace).StringVar(&c.namespace) + c.nodeList.Flag("format", "Output format, 'text', or 'yaml'").Default(teleport.Text).StringVar(&c.lsFormat) + c.nodeList.Flag("verbose", "Verbose table output, shows full label output").Short('v').BoolVar(&c.verbose) c.nodeList.Alias(ListNodesHelp) + c.nodeList.Arg("labels", labelHelp).StringVar(&c.labels) + c.nodeList.Flag("search", searchHelp).StringVar(&c.searchKeywords) + c.nodeList.Flag("query", queryHelp).StringVar(&c.predicateExpr) } // TryRun takes the CLI command as an argument (like "nodes ls") and executes it. @@ -145,16 +165,33 @@ func (c *NodeCommand) Invite(client auth.ClientI) error { } // output format switch: - if c.format == "text" { + if c.format == teleport.Text { if roles.Include(types.RoleTrustedCluster) { fmt.Printf(trustedClusterMessage, token, int(c.ttl.Minutes())) } else { + authServer := authServers[0].GetAddr() + + pingResponse, err := client.Ping(context.TODO()) + if err != nil { + log.Debugf("unnable to ping auth client: %s.", err.Error()) + } + + if err == nil && pingResponse.GetServerFeatures().Cloud { + proxies, err := client.GetProxies() + if err != nil { + return trace.Wrap(err) + } + + if len(proxies) != 0 { + authServer = proxies[0].GetPublicAddr() + } + } return nodeMessageTemplate.Execute(os.Stdout, map[string]interface{}{ "token": token, "minutes": int(c.ttl.Minutes()), "roles": strings.ToLower(roles.String()), "ca_pins": caPins, - "auth_server": authServers[0].GetAddr(), + "auth_server": authServer, }) } } else { @@ -172,15 +209,57 @@ func (c *NodeCommand) Invite(client auth.ClientI) error { // ListActive retreives the list of nodes who recently sent heartbeats to // to a cluster and prints it to stdout -func (c *NodeCommand) ListActive(client auth.ClientI) error { +func (c *NodeCommand) ListActive(clt auth.ClientI) error { ctx := context.TODO() - nodes, err := client.GetNodes(ctx, c.namespace) + + labels, err := libclient.ParseLabelSpec(c.labels) if err != nil { return trace.Wrap(err) } - coll := &serverCollection{servers: nodes} - if err := coll.writeText(os.Stdout); err != nil { + + var nodes []types.Server + resources, err := client.GetResourcesWithFilters(ctx, clt, proto.ListResourcesRequest{ + ResourceType: types.KindNode, + Namespace: c.namespace, + Labels: labels, + PredicateExpression: c.predicateExpr, + SearchKeywords: libclient.ParseSearchKeywords(c.searchKeywords, ','), + }) + switch { + // Underlying ListResources for nodes not available, use fallback. + // Using filter flags with older auth will silently do nothing. + // + // DELETE IN 11.0.0 + case trace.IsNotImplemented(err): + nodes, err = clt.GetNodes(ctx, c.namespace) + if err != nil { + return trace.Wrap(err) + } + case err != nil: return trace.Wrap(err) + default: + nodes, err = types.ResourcesWithLabels(resources).AsServers() + if err != nil { + return trace.Wrap(err) + } + } + + coll := &serverCollection{servers: nodes, verbose: c.verbose} + switch c.lsFormat { + case teleport.Text: + if err := coll.writeText(os.Stdout); err != nil { + return trace.Wrap(err) + } + case teleport.YAML: + if err := coll.writeYaml(os.Stdout); err != nil { + return trace.Wrap(err) + } + case teleport.JSON: + if err := coll.writeJSON(os.Stdout); err != nil { + return trace.Wrap(err) + } + default: + return trace.Errorf("Invalid format %s, only text, json and yaml are supported", c.lsFormat) } return nil } diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 7ef91868417db..87a55e6af44cd 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -68,6 +68,8 @@ type ResourceCommand struct { createCmd *kingpin.CmdClause updateCmd *kingpin.CmdClause + verbose bool + CreateHandlers map[ResourceKind]ResourceCreateHandler // stdout allows to switch standard output source for resource command. Used in tests. @@ -133,6 +135,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, config *service. rc.getCmd.Flag("format", "Output format: 'yaml', 'json' or 'text'").Default(teleport.YAML).StringVar(&rc.format) rc.getCmd.Flag("namespace", "Namespace of the resources").Hidden().Default(apidefaults.Namespace).StringVar(&rc.namespace) rc.getCmd.Flag("with-secrets", "Include secrets in resources like certificate authorities or OIDC connectors").Default("false").BoolVar(&rc.withSecrets) + rc.getCmd.Flag("verbose", "Verbose table output, shows full label output").Short('v').BoolVar(&rc.verbose) rc.getCmd.Alias(getHelp) @@ -770,6 +773,21 @@ func (rc *ResourceCommand) Delete(client auth.ClientI) (err error) { return err } fmt.Printf(fmts+"\n", deleted, rc.ref.Name) + case types.KindCertAuthority: + if rc.ref.SubKind == "" || rc.ref.Name == "" { + return trace.BadParameter( + "full %s path must be specified (e.g. '%s/%s/clustername')", + types.KindCertAuthority, types.KindCertAuthority, types.HostCA, + ) + } + err := client.DeleteCertAuthority(types.CertAuthID{ + Type: types.CertAuthType(rc.ref.SubKind), + DomainName: rc.ref.Name, + }) + if err != nil { + return trace.Wrap(err) + } + fmt.Printf("%s '%s/%s' has been deleted\n", types.KindCertAuthority, rc.ref.SubKind, rc.ref.Name) default: return trace.BadParameter("deleting resources of type %q is not supported", rc.ref.Kind) } @@ -960,7 +978,7 @@ func (rc *ResourceCommand) getCollection(client auth.ClientI) (ResourceCollectio if rc.ref.SubKind == "" && rc.ref.Name == "" { var allAuthorities []types.CertAuthority for _, caType := range types.CertAuthTypes { - authorities, err := client.GetCertAuthorities(caType, rc.withSecrets) + authorities, err := client.GetCertAuthorities(ctx, caType, rc.withSecrets) if err != nil { return nil, trace.Wrap(err) } @@ -969,7 +987,7 @@ func (rc *ResourceCommand) getCollection(client auth.ClientI) (ResourceCollectio return &authorityCollection{cas: allAuthorities}, nil } id := types.CertAuthID{Type: types.CertAuthType(rc.ref.SubKind), DomainName: rc.ref.Name} - authority, err := client.GetCertAuthority(id, rc.withSecrets) + authority, err := client.GetCertAuthority(ctx, id, rc.withSecrets) if err != nil { return nil, trace.Wrap(err) } @@ -1022,13 +1040,13 @@ func (rc *ResourceCommand) getCollection(client auth.ClientI) (ResourceCollectio if err != nil { return nil, trace.Wrap(err) } - return &roleCollection{roles: roles}, nil + return &roleCollection{roles: roles, verbose: rc.verbose}, nil } role, err := client.GetRole(ctx, rc.ref.Name) if err != nil { return nil, trace.Wrap(err) } - return &roleCollection{roles: []types.Role{role}}, nil + return &roleCollection{roles: []types.Role{role}, verbose: rc.verbose}, nil case types.KindNamespace: if rc.ref.Name == "" { namespaces, err := client.GetNamespaces() diff --git a/tool/tctl/common/resource_command_test.go b/tool/tctl/common/resource_command_test.go index 952f999fc106a..d0ce85477e536 100644 --- a/tool/tctl/common/resource_command_test.go +++ b/tool/tctl/common/resource_command_test.go @@ -19,7 +19,7 @@ package common import ( "crypto/x509" "fmt" - "io/ioutil" + "os" "path/filepath" "testing" @@ -165,7 +165,7 @@ func TestDatabaseResource(t *testing.T) { // Create the databases. dbYAMLPath := filepath.Join(t.TempDir(), "db.yaml") - require.NoError(t, ioutil.WriteFile(dbYAMLPath, []byte(dbYAML), 0644)) + require.NoError(t, os.WriteFile(dbYAMLPath, []byte(dbYAML), 0644)) _, err = runResourceCommand(t, fileConfig, []string{"create", dbYAMLPath}) require.NoError(t, err) @@ -252,7 +252,7 @@ func TestAppResource(t *testing.T) { // Create the apps. appYAMLPath := filepath.Join(t.TempDir(), "app.yaml") - require.NoError(t, ioutil.WriteFile(appYAMLPath, []byte(appYAML), 0644)) + require.NoError(t, os.WriteFile(appYAMLPath, []byte(appYAML), 0644)) _, err = runResourceCommand(t, fileConfig, []string{"create", appYAMLPath}) require.NoError(t, err) @@ -315,7 +315,7 @@ func TestCreateDatabaseInInsecureMode(t *testing.T) { // Create the databases yaml file. dbYAMLPath := filepath.Join(t.TempDir(), "db.yaml") - require.NoError(t, ioutil.WriteFile(dbYAMLPath, []byte(dbYAML), 0644)) + require.NoError(t, os.WriteFile(dbYAMLPath, []byte(dbYAML), 0644)) // Reset RootCertPool and run tctl command with --insecure flag. opts := []optionsFunc{ diff --git a/tool/tctl/common/status_command.go b/tool/tctl/common/status_command.go index 261dd67f260f9..09e2c32202c8f 100644 --- a/tool/tctl/common/status_command.go +++ b/tool/tctl/common/status_command.go @@ -49,7 +49,7 @@ func (c *StatusCommand) Initialize(app *kingpin.Application, config *service.Con func (c *StatusCommand) TryRun(cmd string, client auth.ClientI) (match bool, err error) { switch cmd { case c.status.FullCommand(): - err = c.Status(client) + err = c.Status(context.Background(), client) default: return false, nil } @@ -57,29 +57,29 @@ func (c *StatusCommand) TryRun(cmd string, client auth.ClientI) (match bool, err } // Status is called to execute "status" CLI command. -func (c *StatusCommand) Status(client auth.ClientI) error { - pingRsp, err := client.Ping(context.TODO()) +func (c *StatusCommand) Status(ctx context.Context, client auth.ClientI) error { + pingRsp, err := client.Ping(ctx) if err != nil { return trace.Wrap(err) } serverVersion := pingRsp.ServerVersion clusterName := pingRsp.ClusterName - authorities := []types.CertAuthority{} + var authorities []types.CertAuthority - hostCAs, err := client.GetCertAuthorities(types.HostCA, false) + hostCAs, err := client.GetCertAuthorities(ctx, types.HostCA, false) if err != nil { return trace.Wrap(err) } authorities = append(authorities, hostCAs...) - userCAs, err := client.GetCertAuthorities(types.UserCA, false) + userCAs, err := client.GetCertAuthorities(ctx, types.UserCA, false) if err != nil { return trace.Wrap(err) } authorities = append(authorities, userCAs...) - jwtKeys, err := client.GetCertAuthorities(types.JWTSigner, false) + jwtKeys, err := client.GetCertAuthorities(ctx, types.JWTSigner, false) if err != nil { return trace.Wrap(err) } diff --git a/tool/tctl/common/tctl.go b/tool/tctl/common/tctl.go index 47dc8c5c02c7b..12010a012cd60 100644 --- a/tool/tctl/common/tctl.go +++ b/tool/tctl/common/tctl.go @@ -41,6 +41,12 @@ import ( log "github.com/sirupsen/logrus" ) +const ( + searchHelp = `List of comma separated search keywords or phrases enclosed in quotations (e.g. --search=foo,bar,"some phrase")` + queryHelp = `Query by predicate language enclosed in single quotes. Supports ==, !=, &&, and || (e.g. --query='labels.key1 == "value1" && labels.key2 != "value2"')` + labelHelp = "List of comma separated labels to filter by labels (e.g. key1=value1,key2=value2)" +) + // GlobalCLIFlags keeps the CLI flags that apply to all tctl commands type GlobalCLIFlags struct { // Debug enables verbose logging mode to the console @@ -131,8 +137,10 @@ func Run(commands []CLICommand) { app.HelpFlag.Short('h') // parse CLI commands+flags: + utils.UpdateAppUsageTemplate(app, os.Args[1:]) selectedCmd, err := app.Parse(os.Args[1:]) if err != nil { + app.Usage(os.Args[1:]) utils.FatalError(err) } diff --git a/tool/tctl/common/token_command.go b/tool/tctl/common/token_command.go index 908b998283222..9d48a924c6ba8 100644 --- a/tool/tctl/common/token_command.go +++ b/tool/tctl/common/token_command.go @@ -33,6 +33,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/service" "github.com/gravitational/teleport/lib/tlsca" + log "github.com/sirupsen/logrus" "github.com/gravitational/kingpin" "github.com/gravitational/trace" @@ -93,9 +94,10 @@ func (c *TokenCommand) Initialize(app *kingpin.Application, config *service.Conf c.tokenAdd.Flag("type", "Type of token to add").Required().StringVar(&c.tokenType) c.tokenAdd.Flag("value", "Value of token to add").StringVar(&c.value) c.tokenAdd.Flag("labels", "Set token labels, e.g. env=prod,region=us-west").StringVar(&c.labels) - c.tokenAdd.Flag("ttl", fmt.Sprintf("Set expiration time for token, default is %v hour, maximum is %v hours", - int(defaults.SignupTokenTTL/time.Hour), int(defaults.MaxSignupTokenTTL/time.Hour))). - Default(fmt.Sprintf("%v", defaults.SignupTokenTTL)).DurationVar(&c.ttl) + c.tokenAdd.Flag("ttl", fmt.Sprintf("Set expiration time for token, default is %v hour", + int(defaults.SignupTokenTTL/time.Hour))). + Default(fmt.Sprintf("%v", defaults.SignupTokenTTL)). + DurationVar(&c.ttl) c.tokenAdd.Flag("app-name", "Name of the application to add").Default("example-app").StringVar(&c.appName) c.tokenAdd.Flag("app-uri", "URI of the application to add").Default("http://localhost:8080").StringVar(&c.appURI) c.tokenAdd.Flag("db-name", "Name of the database to add").StringVar(&c.dbName) @@ -218,12 +220,30 @@ func (c *TokenCommand) Add(client auth.ClientI) error { token, int(c.ttl.Minutes())) default: + authServer := authServers[0].GetAddr() + + pingResponse, err := client.Ping(context.TODO()) + if err != nil { + log.Debugf("unnable to ping auth client: %s.", err.Error()) + } + + if err == nil && pingResponse.GetServerFeatures().Cloud { + proxies, err := client.GetProxies() + if err != nil { + return trace.Wrap(err) + } + + if len(proxies) != 0 { + authServer = proxies[0].GetPublicAddr() + } + } + return nodeMessageTemplate.Execute(os.Stdout, map[string]interface{}{ "token": token, "roles": strings.ToLower(roles.String()), "minutes": int(c.ttl.Minutes()), "ca_pins": caPins, - "auth_server": authServers[0].GetAddr(), + "auth_server": authServer, }) } diff --git a/tool/tctl/common/usage.go b/tool/tctl/common/usage.go index 3a34cbe1f49b3..e7c32c0eace77 100644 --- a/tool/tctl/common/usage.go +++ b/tool/tctl/common/usage.go @@ -17,7 +17,7 @@ limitations under the License. package common const ( - GlobalHelpString = "CLI Admin tool for the Teleport Auth service. Runs on a host where Teleport Auth is running." + GlobalHelpString = "Admin tool for the Teleport Access Plane" AddUserHelp = `Notes: @@ -37,12 +37,12 @@ Examples: ` AddNodeHelp = `Notes: - This command generates and prints an invitation token another node can use to - join the cluster. + This command generates and prints an invitation token another node can use to + join the cluster. Examples: - > tctl nodes add + > tctl nodes add Generates a token when can be used to add a regular SSH node to the cluster. The token genrated single-use token will be valid for 30 minutes. @@ -50,7 +50,7 @@ Examples: > tctl nodes add --roles=node,proxy --ttl=1h Generates a token when can be used to add an SSH node to the cluster which - will also be a proxy node. This token can be used multiple times within an + will also be a proxy node. This token can be used multiple times within an hour. ` ListNodesHelp = `Notes: diff --git a/tool/tctl/main.go b/tool/tctl/main.go index 1d3485d23aae8..ebe57353b9d94 100644 --- a/tool/tctl/main.go +++ b/tool/tctl/main.go @@ -21,6 +21,7 @@ import ( ) func main() { + // Note: these commands should be kept in sync with e/tool/tctl/main.go. commands := []common.CLICommand{ &common.UserCommand{}, &common.NodeCommand{}, @@ -32,6 +33,8 @@ func main() { &common.AccessRequestCommand{}, &common.AppsCommand{}, &common.DBCommand{}, + &common.KubeCommand{}, + &common.DesktopCommand{}, &common.AccessCommand{}, &common.LockCommand{}, &common.BotsCommand{}, diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 09aa21f954fb9..8bd6bc0a2b090 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -62,7 +62,7 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con // configure logger for a typical CLI scenario until configuration file is // parsed utils.InitLogger(utils.LoggingForDaemon, log.ErrorLevel) - app = utils.InitCLIParser("teleport", "Clustered SSH service. Learn more at https://goteleport.com/teleport") + app = utils.InitCLIParser("teleport", "Teleport Access Plane. Learn more at https://goteleport.com") // define global flags: var ( @@ -294,8 +294,10 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con dump.Flag("key-file", "Path to a TLS key file for the proxy.").ExistingFileVar(&dumpFlags.KeyFile) // parse CLI commands+flags: + utils.UpdateAppUsageTemplate(app, options.Args) command, err := app.Parse(options.Args) if err != nil { + app.Usage(options.Args) utils.FatalError(err) } diff --git a/tool/teleport/common/teleport_test.go b/tool/teleport/common/teleport_test.go index 2423248510fd7..ec99389ca9e9e 100644 --- a/tool/teleport/common/teleport_test.go +++ b/tool/teleport/common/teleport_test.go @@ -17,11 +17,11 @@ limitations under the License. package common import ( - "io/ioutil" "os" "path/filepath" "testing" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/config" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/utils" @@ -43,10 +43,26 @@ func TestTeleportMain(t *testing.T) { hostname, err := os.Hostname() require.NoError(t, err) + fixtureDir := t.TempDir() // generate the fixture config file - configFile := filepath.Join(t.TempDir(), "teleport.yaml") - err = ioutil.WriteFile(configFile, []byte(YAMLConfig), 0660) - require.NoError(t, err) + configFile := filepath.Join(fixtureDir, "teleport.yaml") + require.NoError(t, os.WriteFile(configFile, []byte(configData), 0660)) + + // generate the fixture bootstrap file + bootstrapEntries := []struct{ fileName, kind, name string }{ + {"role.yaml", types.KindRole, "role_name"}, + {"github.yaml", types.KindGithubConnector, "github"}, + {"user.yaml", types.KindRole, "user"}, + } + var bootstrapData []byte + for _, entry := range bootstrapEntries { + data, err := os.ReadFile(filepath.Join("..", "..", "..", "examples", "resources", entry.fileName)) + require.NoError(t, err) + bootstrapData = append(bootstrapData, data...) + bootstrapData = append(bootstrapData, "\n---\n"...) + } + bootstrapFile := filepath.Join(fixtureDir, "bootstrap.yaml") + require.NoError(t, os.WriteFile(bootstrapFile, bootstrapData, 0660)) // set defaults to test-mode (non-existing files&locations) defaults.ConfigFilePath = "/tmp/teleport/etc/teleport.yaml" @@ -111,6 +127,20 @@ func TestTeleportMain(t *testing.T) { require.Equal(t, "10.5.5.5", conf.AdvertiseIP) require.Equal(t, map[string]string{"a": "a1", "b": "b1"}, conf.SSH.Labels) }) + + t.Run("Bootstrap", func(t *testing.T) { + _, cmd, conf := Run(Options{ + Args: []string{"start", "--bootstrap", bootstrapFile}, + InitOnly: true, + }) + require.Equal(t, "start", cmd) + require.Equal(t, len(bootstrapEntries), len(conf.Auth.Resources)) + for i, entry := range bootstrapEntries { + require.Equal(t, entry.kind, conf.Auth.Resources[i].GetKind(), entry.fileName) + require.Equal(t, entry.name, conf.Auth.Resources[i].GetName(), entry.fileName) + require.NoError(t, conf.Auth.Resources[i].CheckAndSetDefaults(), entry.fileName) + } + }) } func TestConfigure(t *testing.T) { @@ -143,7 +173,7 @@ func TestConfigure(t *testing.T) { }) } -const YAMLConfig = ` +const configData = ` teleport: advertise_ip: 10.5.5.5 nodename: hvostongo.example.org diff --git a/tool/tsh/app.go b/tool/tsh/app.go index b602ade1dec0d..01d6dbc912803 100644 --- a/tool/tsh/app.go +++ b/tool/tsh/app.go @@ -114,11 +114,17 @@ tsh aws {{.awsCmd}} // getRegisteredApp returns the registered application with the specified name. func getRegisteredApp(cf *CLIConf, tc *client.TeleportClient) (app types.Application, err error) { + var apps []types.Application err = client.RetryWithRelogin(cf.Context, tc, func() error { - allApps, err := tc.ListApps(cf.Context) + allApps, err := tc.ListApps(cf.Context, &proto.ListResourcesRequest{ + Namespace: tc.Namespace, + PredicateExpression: fmt.Sprintf(`name == "%s"`, cf.AppName), + }) + // Kept for fallback in case older auth does not apply filters. + // DELETE IN 11.0.0 for _, a := range allApps { if a.GetName() == cf.AppName { - app = a + apps = append(apps, a) return nil } } @@ -127,10 +133,10 @@ func getRegisteredApp(cf *CLIConf, tc *client.TeleportClient) (app types.Applica if err != nil { return nil, trace.Wrap(err) } - if app == nil { + if len(apps) == 0 { return nil, trace.NotFound("app %q not found, use `tsh app ls` to see registered apps", cf.AppName) } - return app, nil + return apps[0], nil } // onAppLogout implements "tsh app logout" command. diff --git a/tool/tsh/aws.go b/tool/tsh/aws.go index ec0a791bac6f3..5ec7a0e74c739 100644 --- a/tool/tsh/aws.go +++ b/tool/tsh/aws.go @@ -23,7 +23,6 @@ import ( "crypto/x509/pkix" "fmt" "io" - "io/ioutil" "net/url" "os" "os/exec" @@ -292,7 +291,7 @@ func newTempSelfSignedLocalCert() (*tempSelfSignedLocalCert, error) { return nil, trace.Wrap(err) } - f, err := ioutil.TempFile("", "*_aws_local_proxy_cert.pem") + f, err := os.CreateTemp("", "*_aws_local_proxy_cert.pem") if err != nil { return nil, trace.Wrap(err) } diff --git a/tool/tsh/db.go b/tool/tsh/db.go index 0b8d2ee75afa8..021fdbeadd1b9 100644 --- a/tool/tsh/db.go +++ b/tool/tsh/db.go @@ -18,7 +18,6 @@ package main import ( "fmt" - "io/ioutil" "net" "os" "sort" @@ -43,7 +42,7 @@ func onListDatabases(cf *CLIConf) error { } var databases []types.Database err = client.RetryWithRelogin(cf.Context, tc, func() error { - databases, err = tc.ListDatabases(cf.Context) + databases, err = tc.ListDatabases(cf.Context, nil /* custom filter */) return trace.Wrap(err) }) if err != nil { @@ -410,7 +409,12 @@ func getDatabaseInfo(cf *CLIConf, tc *client.TeleportClient, dbName string) (*tl func getDatabase(cf *CLIConf, tc *client.TeleportClient, dbName string) (types.Database, error) { var databases []types.Database err := client.RetryWithRelogin(cf.Context, tc, func() error { - allDatabases, err := tc.ListDatabases(cf.Context) + allDatabases, err := tc.ListDatabases(cf.Context, &proto.ListResourcesRequest{ + Namespace: tc.Namespace, + PredicateExpression: fmt.Sprintf(`name == "%s"`, dbName), + }) + // Kept for fallback in case an older auth does not apply filters. + // DELETE IN 11.0.0 for _, database := range allDatabases { if database.GetName() == dbName { databases = append(databases, database) @@ -469,7 +473,7 @@ func dbInfoHasChanged(cf *CLIConf, certPath string) (bool, error) { return false, nil } - buff, err := ioutil.ReadFile(certPath) + buff, err := os.ReadFile(certPath) if err != nil { return false, trace.Wrap(err) } diff --git a/tool/tsh/db_test.go b/tool/tsh/db_test.go index 382f8e58cea8a..fe2920c590fa9 100644 --- a/tool/tsh/db_test.go +++ b/tool/tsh/db_test.go @@ -22,7 +22,6 @@ import ( "crypto/rsa" "encoding/pem" "errors" - "io/ioutil" "os" "path/filepath" "testing" @@ -232,7 +231,7 @@ func TestDBInfoHasChanged(t *testing.T) { require.NoError(t, err) certPath := filepath.Join(t.TempDir(), "mongo_db_cert.pem") - require.NoError(t, ioutil.WriteFile(certPath, certBytes, 0600)) + require.NoError(t, os.WriteFile(certPath, certBytes, 0600)) cliConf := &CLIConf{DatabaseUser: tc.databaseUserName, DatabaseName: tc.databaseName} got, err := dbInfoHasChanged(cliConf, certPath) diff --git a/tool/tsh/dbcmd.go b/tool/tsh/dbcmd.go index 36c436d01e4bb..78c7b8f92c4bc 100644 --- a/tool/tsh/dbcmd.go +++ b/tool/tsh/dbcmd.go @@ -231,7 +231,7 @@ func (c *cliCommandBuilder) getMySQLCommand() (*exec.Cmd, error) { // Check for mysql binary. Return with error as mysql and mariadb are missing. There is nothing else we can do here. if !c.isMySQLBinAvailable() { - return nil, trace.NotFound("neither \"mysql\" nor \"mariadb\" were found") + return nil, trace.NotFound("neither %q nor %q CLI clients were found, please make sure an appropriate CLI client is available in $PATH", mysqlBin, mariadbBin) } // Check which flavor is installed. Otherwise, we don't know which ssl flag to use. diff --git a/tool/tsh/kube.go b/tool/tsh/kube.go index f42c9d95cc6f1..8899f93b6de63 100644 --- a/tool/tsh/kube.go +++ b/tool/tsh/kube.go @@ -23,13 +23,14 @@ import ( "net" "net/url" "os" - + "strconv" "strings" "time" "github.com/gravitational/kingpin" "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/profile" "github.com/gravitational/teleport/api/types" @@ -292,11 +293,12 @@ type ExecOptions struct { ExecutablePodFn polymorphichelpers.AttachablePodForObjectFunc restClientGetter genericclioptions.RESTClientGetter - Pod *corev1.Pod - Executor RemoteExecutor - PodClient coreclient.PodsGetter - GetPodTimeout time.Duration - Config *restclient.Config + Pod *corev1.Pod + Executor RemoteExecutor + PodClient coreclient.PodsGetter + GetPodTimeout time.Duration + Config *restclient.Config + displayParticipantRequirements bool } // Run executes a validated remote execution against a pod. @@ -365,7 +367,8 @@ func (p *ExecOptions) Run() error { Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). - SubResource("exec") + SubResource("exec"). + Param("displayParticipantRequirements", strconv.FormatBool(p.displayParticipantRequirements)) req.VersionedParams(&corev1.PodExecOptions{ Container: containerName, Command: p.Command, @@ -383,15 +386,16 @@ func (p *ExecOptions) Run() error { type kubeExecCommand struct { *kingpin.CmdClause - target string - container string - filename string - quiet bool - stdin bool - tty bool - reason string - invited string - command []string + target string + container string + filename string + quiet bool + stdin bool + tty bool + reason string + invited string + command []string + displayParticipantRequirements bool } func newKubeExecCommand(parent *kingpin.CmdClause) *kubeExecCommand { @@ -406,6 +410,7 @@ func newKubeExecCommand(parent *kingpin.CmdClause) *kubeExecCommand { c.Flag("tty", "Stdin is a TTY").Short('t').BoolVar(&c.tty) c.Flag("reason", "The purpose of the session.").StringVar(&c.reason) c.Flag("invite", "A comma separated list of people to mark as invited for the session.").StringVar(&c.invited) + c.Flag("participant-req", "Displays a verbose list of required participants in a moderated session.").BoolVar(&c.displayParticipantRequirements) c.Arg("target", "Pod or deployment name").Required().StringVar(&c.target) c.Arg("command", "Command to execute in the container").Required().StringsVar(&c.command) return c @@ -434,6 +439,7 @@ func (c *kubeExecCommand) run(cf *CLIConf) error { p.Builder = f.NewBuilder p.restClientGetter = f p.Executor = &DefaultRemoteExecutor{} + p.displayParticipantRequirements = c.displayParticipantRequirements p.Namespace, p.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() if err != nil { return trace.Wrap(err) @@ -589,16 +595,26 @@ func (c *kubeCredentialsCommand) writeResponse(key *client.Key, kubeClusterName type kubeLSCommand struct { *kingpin.CmdClause + labels string + predicateExpr string + searchKeywords string } func newKubeLSCommand(parent *kingpin.CmdClause) *kubeLSCommand { c := &kubeLSCommand{ CmdClause: parent.Command("ls", "Get a list of kubernetes clusters"), } + c.Flag("search", searchHelp).StringVar(&c.searchKeywords) + c.Flag("query", queryHelp).StringVar(&c.predicateExpr) + c.Arg("labels", labelHelp).StringVar(&c.labels) return c } func (c *kubeLSCommand) run(cf *CLIConf) error { + cf.SearchKeywords = c.searchKeywords + cf.UserHost = c.labels + cf.PredicateExpression = c.predicateExpr + tc, err := makeClient(cf, true) if err != nil { return trace.Wrap(err) @@ -715,10 +731,27 @@ func fetchKubeClusters(ctx context.Context, tc *client.TeleportClient) (teleport } teleportCluster = cn.GetClusterName() - kubeClusters, err = kubeutils.KubeClusterNames(ctx, ac) + kubeClusters, err = kubeutils.ListKubeClusterNamesWithFilters(ctx, ac, proto.ListResourcesRequest{ + SearchKeywords: tc.SearchKeywords, + PredicateExpression: tc.PredicateExpression, + Labels: tc.Labels, + }) if err != nil { + // ListResources for kube service not availalbe, provide fallback. + // Fallback does not support filters, so if users + // provide them, it does nothing. + // + // DELETE IN 11.0.0 + if trace.IsNotImplemented(err) { + kubeClusters, err = kubeutils.KubeClusterNames(ctx, ac) + if err != nil { + return trace.Wrap(err) + } + return nil + } return trace.Wrap(err) } + return nil }) if err != nil { diff --git a/tool/tsh/mfa.go b/tool/tsh/mfa.go index 25b8e0093b50c..6e46c2f11acc1 100644 --- a/tool/tsh/mfa.go +++ b/tool/tsh/mfa.go @@ -38,6 +38,7 @@ import ( "github.com/gravitational/trace" "github.com/pquerna/otp" "github.com/pquerna/otp/totp" + "golang.org/x/term" wantypes "github.com/gravitational/teleport/api/types/webauthn" wanlib "github.com/gravitational/teleport/lib/auth/webauthn" @@ -147,6 +148,10 @@ type mfaAddCommand struct { *kingpin.CmdClause devName string devType string + // pwdless is nil if unset, true/false if explicitly set. + // If passwordless is not supported it's always set to false. + // The default behavior is the same as false. + pwdless *bool } func newMFAAddCommand(parent *kingpin.CmdClause) *mfaAddCommand { @@ -156,6 +161,22 @@ func newMFAAddCommand(parent *kingpin.CmdClause) *mfaAddCommand { c.Flag("name", "Name of the new MFA device").StringVar(&c.devName) c.Flag("type", fmt.Sprintf("Type of the new MFA device (%s)", strings.Join(defaultDeviceTypes, ", "))). StringVar(&c.devType) + + if wancli.IsFIDO2Available() { + var allowPwdless bool + c.Flag("allow-passwordless", "Allow passwordless logins"). + Action(func(_ *kingpin.ParseContext) error { + // If the callback is called it means that the flag was explicitly set, + // so we can copy its contents to the command. + c.pwdless = &allowPwdless + return nil + }). + BoolVar(&allowPwdless) + } else { + allowPwdless := false + c.pwdless = &allowPwdless + } + return c } @@ -166,6 +187,12 @@ func (c *mfaAddCommand) run(cf *CLIConf) error { } ctx := cf.Context + // IMPORTANT: mfa add may need to read secure key PINs in some scenarios, + // meaning it has to use term.ReadPassword. + // Because of that we can't use prompt.Stdin(), as it hijacks stdin, making + // password-like reads impossible. + stdin := prompt.StdinSync() + deviceTypes := defaultDeviceTypes if c.devType == "" { // If we are prompting the user for the device type, then take a glimpse at @@ -177,7 +204,7 @@ func (c *mfaAddCommand) run(cf *CLIConf) error { } deviceTypes = deviceTypesFromPreferredMFA(pingResp.Auth.PreferredLocalMFA) - c.devType, err = prompt.PickOne(ctx, os.Stdout, prompt.Stdin(), "Choose device type", deviceTypes) + c.devType, err = prompt.PickOne(ctx, os.Stdout, stdin, "Choose device type", deviceTypes) if err != nil { return trace.Wrap(err) } @@ -194,7 +221,7 @@ func (c *mfaAddCommand) run(cf *CLIConf) error { if c.devName == "" { var err error - c.devName, err = prompt.Input(ctx, os.Stdout, prompt.Stdin(), "Enter device name") + c.devName, err = prompt.Input(ctx, os.Stdout, stdin, "Enter device name") if err != nil { return trace.Wrap(err) } @@ -204,7 +231,18 @@ func (c *mfaAddCommand) run(cf *CLIConf) error { return trace.BadParameter("device name can not be empty") } - dev, err := c.addDeviceRPC(ctx, tc, c.devName, devType) + // If passwordless is supported but unset then ask the user. + if c.pwdless == nil { + answer, err := prompt.PickOne(ctx, os.Stdout, stdin, "Allow passwordless logins", []string{"YES", "NO"}) + if err != nil { + return trace.Wrap(err) + } + val := answer == "YES" + c.pwdless = &val + } + pwdless := c.pwdless != nil && *c.pwdless + + dev, err := c.addDeviceRPC(ctx, tc, c.devName, devType, pwdless, stdin) if err != nil { return trace.Wrap(err) } @@ -231,7 +269,9 @@ func deviceTypesFromPreferredMFA(preferredMFA constants.SecondFactorType) []stri } } -func (c *mfaAddCommand) addDeviceRPC(ctx context.Context, tc *client.TeleportClient, devName string, devType proto.DeviceType) (*types.MFADevice, error) { +func (c *mfaAddCommand) addDeviceRPC( + ctx context.Context, + tc *client.TeleportClient, devName string, devType proto.DeviceType, passwordless bool, r prompt.Reader) (*types.MFADevice, error) { var dev *types.MFADevice if err := client.RetryWithRelogin(ctx, tc, func() error { pc, err := tc.ConnectToProxy(ctx) @@ -252,10 +292,15 @@ func (c *mfaAddCommand) addDeviceRPC(ctx context.Context, tc *client.TeleportCli return trace.Wrap(err) } // Init. + usage := proto.DeviceUsage_DEVICE_USAGE_MFA + if passwordless { + usage = proto.DeviceUsage_DEVICE_USAGE_PASSWORDLESS + } if err := stream.Send(&proto.AddMFADeviceRequest{Request: &proto.AddMFADeviceRequest_Init{ Init: &proto.AddMFADeviceRequestInit{ - DeviceName: devName, - DeviceType: devType, + DeviceName: devName, + DeviceType: devType, + DeviceUsage: usage, }, }}); err != nil { return trace.Wrap(err) @@ -289,7 +334,7 @@ func (c *mfaAddCommand) addDeviceRPC(ctx context.Context, tc *client.TeleportCli if regChallenge == nil { return trace.BadParameter("server bug: server sent %T when client expected AddMFADeviceResponse_NewMFARegisterChallenge", resp.Response) } - regResp, err := promptRegisterChallenge(ctx, tc.Config.WebProxyAddr, regChallenge) + regResp, err := promptRegisterChallenge(ctx, tc.Config.WebProxyAddr, regChallenge, r) if err != nil { return trace.Wrap(err) } @@ -316,18 +361,20 @@ func (c *mfaAddCommand) addDeviceRPC(ctx context.Context, tc *client.TeleportCli return dev, nil } -func promptRegisterChallenge(ctx context.Context, proxyAddr string, c *proto.MFARegisterChallenge) (*proto.MFARegisterResponse, error) { +func promptRegisterChallenge(ctx context.Context, proxyAddr string, c *proto.MFARegisterChallenge, r prompt.Reader) (*proto.MFARegisterResponse, error) { switch c.Request.(type) { case *proto.MFARegisterChallenge_TOTP: - return promptTOTPRegisterChallenge(ctx, c.GetTOTP()) + return promptTOTPRegisterChallenge(ctx, c.GetTOTP(), r) case *proto.MFARegisterChallenge_Webauthn: + // WebAuthn prompt doesn't take in "r" because it reads directly from stdin + // using term.ReadPassword. return promptWebauthnRegisterChallenge(ctx, proxyAddr, c.GetWebauthn()) default: return nil, trace.BadParameter("server bug: unexpected registration challenge type: %T", c.Request) } } -func promptTOTPRegisterChallenge(ctx context.Context, c *proto.TOTPRegisterChallenge) (*proto.MFARegisterResponse, error) { +func promptTOTPRegisterChallenge(ctx context.Context, c *proto.TOTPRegisterChallenge, r prompt.Reader) (*proto.MFARegisterResponse, error) { secretBin, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(c.Secret) if err != nil { return nil, trace.BadParameter("server sent an invalid TOTP secret key %q: %v", c.Secret, err) @@ -388,7 +435,7 @@ func promptTOTPRegisterChallenge(ctx context.Context, c *proto.TOTPRegisterChall // Help the user with typos, don't submit the code until it has the right // length. for { - totpCode, err = prompt.Input(ctx, os.Stdout, prompt.Stdin(), "Once created, enter an OTP code generated by the app") + totpCode, err = prompt.Input(ctx, os.Stdout, r, "Once created, enter an OTP code generated by the app") if err != nil { return nil, trace.Wrap(err) } @@ -402,6 +449,23 @@ func promptTOTPRegisterChallenge(ctx context.Context, c *proto.TOTPRegisterChall }}, nil } +// mfaAddPrompt implements wancli.RegisterPrompt for MFA registrations. +type mfaAddPrompt struct{} + +func (m mfaAddPrompt) PromptPIN() (string, error) { + fmt.Println("Enter your *new* security key PIN") + pwd, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + return "", trace.Wrap(err) + } + return string(pwd), nil +} + +func (m mfaAddPrompt) PromptAdditionalTouch() error { + fmt.Println("Tap your *new* security key again to complete registration") + return nil +} + func promptWebauthnRegisterChallenge(ctx context.Context, proxyAddr string, cc *wantypes.CredentialCreation) (*proto.MFARegisterResponse, error) { origin := proxyAddr if !strings.HasPrefix(proxyAddr, "https://") { @@ -410,8 +474,7 @@ func promptWebauthnRegisterChallenge(ctx context.Context, proxyAddr string, cc * log.Debugf("WebAuthn: prompting MFA devices with origin %q", origin) fmt.Println("Tap your *new* security key") - - resp, err := wancli.Register(ctx, origin, wanlib.CredentialCreationFromProto(cc)) + resp, err := wancli.Register(ctx, origin, wanlib.CredentialCreationFromProto(cc), mfaAddPrompt{}) return resp, trace.Wrap(err) } diff --git a/tool/tsh/proxy_test.go b/tool/tsh/proxy_test.go index ad6a097b57460..b5d0e3c738284 100644 --- a/tool/tsh/proxy_test.go +++ b/tool/tsh/proxy_test.go @@ -19,7 +19,6 @@ package main import ( "bytes" "fmt" - "io/ioutil" "os" "os/exec" "os/user" @@ -174,7 +173,7 @@ func TestProxySSHDial(t *testing.T) { tmpHomePath := t.TempDir() connector := mockConnector(t) - sshLoginRole, err := types.NewRole("ssh-login", types.RoleSpecV5{ + sshLoginRole, err := types.NewRoleV3("ssh-login", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{"alice"}, }, @@ -320,7 +319,7 @@ func createAgent(t *testing.T) string { // Create own tmp dir instead of using t.TmpDir // because net.Listen("unix", path) has dir path length limitation and // the t.TmpDir calls creates tmp dir with test name. - sockDir, err := ioutil.TempDir("", "test") + sockDir, err := os.MkdirTemp("", "test") require.NoError(t, err) t.Cleanup(func() { os.RemoveAll(sockDir) @@ -375,7 +374,7 @@ func mustGetOpenSSHConfigFile(t *testing.T) string { tmpDir := t.TempDir() configPath := filepath.Join(tmpDir, "ssh_config") - err = ioutil.WriteFile(configPath, buff.Bytes(), 0600) + err = os.WriteFile(configPath, buff.Bytes(), 0600) require.NoError(t, err) return configPath diff --git a/tool/tsh/resolve_default_addr.go b/tool/tsh/resolve_default_addr.go index cbfe56dd40bbd..6b3f6abd2a1f4 100644 --- a/tool/tsh/resolve_default_addr.go +++ b/tool/tsh/resolve_default_addr.go @@ -21,7 +21,6 @@ import ( "crypto/tls" "fmt" "io" - "io/ioutil" "net" "net/http" "strconv" @@ -56,7 +55,7 @@ func logResponseBody(level logrus.Level, bodyStream io.Reader) { // context originally supplied to the request that initiated this // response, so no need to have an independent reading timeout // here. - body, err := ioutil.ReadAll(io.LimitReader(bodyStream, maxPingBodySize)) + body, err := io.ReadAll(io.LimitReader(bodyStream, maxPingBodySize)) if err != nil { // This is only for debugging purposes, so it's safe to just give up here. log.WithError(err).Debug("Could not read failed racer response body") diff --git a/tool/tsh/tsh.go b/tool/tsh/tsh.go index 9b6284e63e380..0e5285d080b65 100644 --- a/tool/tsh/tsh.go +++ b/tool/tsh/tsh.go @@ -26,6 +26,7 @@ import ( "os/signal" "path" "path/filepath" + "regexp" "runtime" "sort" "strings" @@ -34,16 +35,17 @@ import ( "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" - "golang.org/x/term" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/constants" apidefaults "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/api/profile" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" apisshutils "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/asciitable" "github.com/gravitational/teleport/lib/auth" + wancli "github.com/gravitational/teleport/lib/auth/webauthncli" "github.com/gravitational/teleport/lib/benchmark" "github.com/gravitational/teleport/lib/client" dbprofile "github.com/gravitational/teleport/lib/client/db" @@ -208,6 +210,12 @@ type CLIConf struct { // Format is used to change the format of output Format string + // SearchKeywords is a list of search keywords to match against resource field values. + SearchKeywords string + + // PredicateExpression defines boolean conditions that will be matched against the resource. + PredicateExpression string + // NoRemoteExec will not execute a remote command after connecting to a host, // will block instead. Useful when port forwarding. Equivalent of -N for OpenSSH. NoRemoteExec bool @@ -253,6 +261,8 @@ type CLIConf struct { // overrideStdout allows to switch standard output source for resource command. Used in tests. overrideStdout io.Writer + // overrideStderr allows to switch standard error source for resource command. Used in tests. + overrideStderr io.Writer // mockSSOLogin used in tests to override sso login handler in teleport client. mockSSOLogin client.SSOLoginFunc @@ -283,6 +293,15 @@ type CLIConf struct { // JoinMode is the participant mode someone is joining a session as. JoinMode string + + // Passwordless instructs tsh to do passwordless login. + Passwordless bool + + // displayParticipantRequirements is set if verbose participant requirement information should be printed for moderated sessions. + displayParticipantRequirements bool + + // ExtraProxyHeaders is configuration read from the .tsh/config/config.yaml file. + ExtraProxyHeaders []ExtraProxyHeaders } // Stdout returns the stdout writer. @@ -293,6 +312,14 @@ func (c *CLIConf) Stdout() io.Writer { return os.Stdout } +// Stderr returns the stderr writer. +func (c *CLIConf) Stderr() io.Writer { + if c.overrideStderr != nil { + return c.overrideStderr + } + return os.Stderr +} + func main() { cmdLineOrig := os.Args[1:] var cmdLine []string @@ -329,7 +356,9 @@ const ( clusterHelp = "Specify the Teleport cluster to connect" browserHelp = "Set to 'none' to suppress browser opening on login" - + searchHelp = `List of comma separated search keywords or phrases enclosed in quotations (e.g. --search=foo,bar,"some phrase")` + queryHelp = `Query by predicate language enclosed in single quotes. Supports ==, !=, &&, and || (e.g. --query='labels.key1 == "value1" && labels.key2 != "value2"')` + labelHelp = "List of comma separated labels to filter by labels (e.g. key1=value1,key2=value2)" // proxyDefaultResolutionTimeout is how long to wait for an unknown proxy // port to be resolved. // @@ -338,8 +367,6 @@ const ( // establishment of a TCP connection, rather than the full HTTP round- // trip that we measure against, so some tweaking may be needed. proxyDefaultResolutionTimeout = 2 * time.Second - - teleportNamespace = "teleport.dev" ) // cliOption is used in tests to inject/override configuration within Run @@ -353,12 +380,15 @@ func Run(args []string, opts ...cliOption) error { moduleCfg := modules.GetModules() // configure CLI argument parser: - app := utils.InitCLIParser("tsh", "TSH: Teleport Authentication Gateway Client").Interspersed(false) + app := utils.InitCLIParser("tsh", "Teleport Command Line Client").Interspersed(false) app.Flag("login", "Remote host login").Short('l').Envar(loginEnvVar).StringVar(&cf.NodeLogin) localUser, _ := client.Username() app.Flag("proxy", "SSH proxy address").Envar(proxyEnvVar).StringVar(&cf.Proxy) app.Flag("nocache", "do not cache cluster discovery locally").Hidden().BoolVar(&cf.NoCache) app.Flag("user", fmt.Sprintf("SSH proxy user [%s]", localUser)).Envar(userEnvVar).StringVar(&cf.Username) + if wancli.IsFIDO2Available() { + app.Flag("pwdless", "Do passwordless login").BoolVar(&cf.Passwordless) + } app.Flag("option", "").Short('o').Hidden().AllowDuplicate().PreAction(func(ctx *kingpin.ParseContext) error { return trace.BadParameter("invalid flag, perhaps you want to use this flag as tsh ssh -o?") }).String() @@ -392,7 +422,9 @@ func Run(args []string, opts ...cliOption) error { BoolVar(&cf.EnableEscapeSequences) app.Flag("bind-addr", "Override host:port used when opening a browser for cluster logins").Envar(bindAddrEnvVar).StringVar(&cf.BindAddr) app.HelpFlag.Short('h') + ver := app.Command("version", "Print the version") + // ssh ssh := app.Command("ssh", "Run shell or execute a command on a remote SSH node") ssh.Arg("[user@]host", "Remote hostname and the login to use").Required().StringVar(&cf.UserHost) @@ -410,6 +442,7 @@ func Run(args []string, opts ...cliOption) error { ssh.Flag("x11-untrusted", "Requests untrusted (secure) X11 forwarding for this session").Short('X').BoolVar(&cf.X11ForwardingUntrusted) ssh.Flag("x11-trusted", "Requests trusted (insecure) X11 forwarding for this session. This can make your local displays vulnerable to attacks, use with caution").Short('Y').BoolVar(&cf.X11ForwardingTrusted) ssh.Flag("x11-untrusted-timeout", "Sets a timeout for untrusted X11 forwarding, after which the client will reject any forwarding requests from the server").Default("10m").DurationVar((&cf.X11ForwardingTimeout)) + ssh.Flag("participant-req", "Displays a verbose list of required participants in a moderated session.").BoolVar(&cf.displayParticipantRequirements) // AWS. aws := app.Command("aws", "Access AWS API.") @@ -421,6 +454,9 @@ func Run(args []string, opts ...cliOption) error { lsApps := apps.Command("ls", "List available applications.") lsApps.Flag("verbose", "Show extra application fields.").Short('v').BoolVar(&cf.Verbose) lsApps.Flag("cluster", clusterHelp).StringVar(&cf.SiteName) + lsApps.Flag("search", searchHelp).StringVar(&cf.SearchKeywords) + lsApps.Flag("query", queryHelp).StringVar(&cf.PredicateExpression) + lsApps.Arg("labels", labelHelp).StringVar(&cf.UserHost) appLogin := apps.Command("login", "Retrieve short-lived certificate for an app.") appLogin.Arg("app", "App name to retrieve credentials for. Can be obtained from `tsh apps ls` output.").Required().StringVar(&cf.AppName) appLogin.Flag("aws-role", "(For AWS CLI access only) Amazon IAM role ARN or role name.").StringVar(&cf.AWSRole) @@ -450,6 +486,9 @@ func Run(args []string, opts ...cliOption) error { db.Flag("cluster", clusterHelp).StringVar(&cf.SiteName) dbList := db.Command("ls", "List all available databases.") dbList.Flag("verbose", "Show extra database fields.").Short('v').BoolVar(&cf.Verbose) + dbList.Flag("search", searchHelp).StringVar(&cf.SearchKeywords) + dbList.Flag("query", queryHelp).StringVar(&cf.PredicateExpression) + dbList.Arg("labels", labelHelp).StringVar(&cf.UserHost) dbLogin := db.Command("login", "Retrieve credentials for a database.") dbLogin.Arg("db", "Database to retrieve credentials for. Can be obtained from 'tsh db ls' output.").Required().StringVar(&cf.DatabaseService) dbLogin.Flag("db-user", "Optional database user to configure as default.").StringVar(&cf.DatabaseUser) @@ -494,9 +533,11 @@ func Run(args []string, opts ...cliOption) error { // ls ls := app.Command("ls", "List remote SSH nodes") ls.Flag("cluster", clusterHelp).StringVar(&cf.SiteName) - ls.Arg("labels", "List of labels to filter node list").StringVar(&cf.UserHost) ls.Flag("verbose", "One-line output (for text format), including node UUIDs").Short('v').BoolVar(&cf.Verbose) ls.Flag("format", "Format output (text, json, names)").Short('f').Default(teleport.Text).StringVar(&cf.Format) + ls.Arg("labels", labelHelp).StringVar(&cf.UserHost) + ls.Flag("search", searchHelp).StringVar(&cf.SearchKeywords) + ls.Flag("query", queryHelp).StringVar(&cf.PredicateExpression) // clusters clusters := app.Command("clusters", "List available Teleport clusters") clusters.Flag("quiet", "Quiet mode").Short('q').BoolVar(&cf.Quiet) @@ -597,8 +638,10 @@ func Run(args []string, opts ...cliOption) error { } // parse CLI commands+flags: + utils.UpdateAppUsageTemplate(app, args) command, err := app.Parse(args) if err != nil { + app.Usage(args) return trace.Wrap(err) } @@ -645,6 +688,15 @@ func Run(args []string, opts ...cliOption) error { setEnvFlags(&cf, os.Getenv) + confOptions, err := loadConfig(cf.HomePath) + if err != nil && !trace.IsNotFound(err) { + return trace.Wrap(err, "failed to load tsh config from %s", + filepath.Join(profile.FullProfilePath(cf.HomePath), tshConfigPath)) + } + if confOptions != nil { + cf.ExtraProxyHeaders = confOptions.ExtraHeaders + } + switch command { case ver.FullCommand(): utils.PrintVersion() @@ -860,23 +912,37 @@ func onLogin(cf *CLIConf) error { // in case if nothing is specified, re-fetch kube clusters and print // current status case cf.Proxy == "" && cf.SiteName == "" && cf.DesiredRoles == "" && cf.RequestID == "" && cf.IdentityFileOut == "": + _, err := tc.PingAndShowMOTD(cf.Context) + if err != nil { + return trace.Wrap(err) + } if err := updateKubeConfig(cf, tc, ""); err != nil { return trace.Wrap(err) } printProfiles(cf.Debug, profile, profiles) + return nil // in case if parameters match, re-fetch kube clusters and print // current status case host(cf.Proxy) == host(profile.ProxyURL.Host) && cf.SiteName == profile.Cluster && cf.DesiredRoles == "" && cf.RequestID == "": + _, err := tc.PingAndShowMOTD(cf.Context) + if err != nil { + return trace.Wrap(err) + } if err := updateKubeConfig(cf, tc, ""); err != nil { return trace.Wrap(err) } printProfiles(cf.Debug, profile, profiles) + return nil // proxy is unspecified or the same as the currently provided proxy, // but cluster is specified, treat this as selecting a new cluster // for the same proxy case (cf.Proxy == "" || host(cf.Proxy) == host(profile.ProxyURL.Host)) && cf.SiteName != "": + _, err := tc.PingAndShowMOTD(cf.Context) + if err != nil { + return trace.Wrap(err) + } // trigger reissue, preserving any active requests. err = tc.ReissueUserCerts(cf.Context, client.CertCacheKeep, client.ReissueParams{ AccessRequests: profile.ActiveRequests.AccessRequests, @@ -891,11 +957,16 @@ func onLogin(cf *CLIConf) error { if err := updateKubeConfig(cf, tc, ""); err != nil { return trace.Wrap(err) } + return trace.Wrap(onStatus(cf)) // proxy is unspecified or the same as the currently provided proxy, // but desired roles or request ID is specified, treat this as a // privilege escalation request for the same login session. case (cf.Proxy == "" || host(cf.Proxy) == host(profile.ProxyURL.Host)) && (cf.DesiredRoles != "" || cf.RequestID != "") && cf.IdentityFileOut == "": + _, err := tc.PingAndShowMOTD(cf.Context) + if err != nil { + return trace.Wrap(err) + } if err := executeAccessRequest(cf, tc); err != nil { return trace.Wrap(err) } @@ -1245,7 +1316,7 @@ func onListNodes(cf *CLIConf) error { // Get list of all nodes in backend and sort by "Node Name". var nodes []types.Server err = client.RetryWithRelogin(cf.Context, tc, func() error { - nodes, err = tc.ListNodes(cf.Context) + nodes, err = tc.ListNodesWithFilters(cf.Context) return err }) if err != nil { @@ -1416,7 +1487,7 @@ func printNodesAsText(nodes []types.Server, verbose bool) { rows = append(rows, []string{n.GetHostname(), getAddr(n), sortedLabels(n.GetAllLabels())}) } - t = makeTableWithTruncatedColumn([]string{"Node Name", "Address", "Labels"}, rows, "Labels") + t = asciitable.MakeTableWithTruncatedColumn([]string{"Node Name", "Address", "Labels"}, rows, "Labels") } fmt.Println(t.AsBuffer().String()) } @@ -1426,7 +1497,7 @@ func sortedLabels(labels map[string]string) string { var namespaced []string var result []string for key, val := range labels { - if strings.HasPrefix(key, teleportNamespace+"/") { + if strings.HasPrefix(key, types.TeleportNamespace+"/") { teleportNamespaced = append(teleportNamespaced, key) continue } @@ -1479,60 +1550,12 @@ func showApps(apps []types.Application, active []tlsca.RouteToApp, verbose bool) labels := sortedLabels(app.GetAllLabels()) rows = append(rows, []string{name, desc, addr, labels}) } - t := makeTableWithTruncatedColumn( + t := asciitable.MakeTableWithTruncatedColumn( []string{"Application", "Description", "Public Address", "Labels"}, rows, "Labels") fmt.Println(t.AsBuffer().String()) } } -func makeTableWithTruncatedColumn(columnOrder []string, rows [][]string, truncatedColumn string) asciitable.Table { - width, _, err := term.GetSize(int(os.Stdin.Fd())) - if err != nil { - width = 80 - } - truncatedColMinSize := 16 - maxColWidth := (width - truncatedColMinSize) / (len(columnOrder) - 1) - t := asciitable.MakeTable([]string{}) - totalLen := 0 - columns := []asciitable.Column{} - - for collIndex, colName := range columnOrder { - column := asciitable.Column{ - Title: colName, - MaxCellLength: len(colName), - } - if colName == truncatedColumn { // truncated column is handled separately in next loop - columns = append(columns, column) - continue - } - for _, row := range rows { - cellLen := row[collIndex] - if len(cellLen) > column.MaxCellLength { - column.MaxCellLength = len(cellLen) - } - } - if column.MaxCellLength > maxColWidth { - column.MaxCellLength = maxColWidth - totalLen += column.MaxCellLength + 4 // "..." - } else { - totalLen += column.MaxCellLength + 1 // +1 for column separator - } - columns = append(columns, column) - } - - for _, column := range columns { - if column.Title == truncatedColumn { - column.MaxCellLength = width - totalLen - len("... ") - } - t.AddColumn(column) - } - - for _, row := range rows { - t.AddRow(row) - } - return t -} - func showDatabases(clusterFlag string, databases []types.Database, active []tlsca.RouteToDatabase, verbose bool) { if verbose { t := asciitable.MakeTable([]string{"Name", "Description", "Protocol", "Type", "URI", "Labels", "Connect", "Expires"}) @@ -1575,7 +1598,7 @@ func showDatabases(clusterFlag string, databases []types.Database, active []tlsc connect, }) } - t := makeTableWithTruncatedColumn([]string{"Name", "Description", "Labels", "Connect"}, rows, "Labels") + t := asciitable.MakeTableWithTruncatedColumn([]string{"Name", "Description", "Labels", "Connect"}, rows, "Labels") fmt.Println(t.AsBuffer().String()) } } @@ -1739,7 +1762,8 @@ func onBenchmark(cf *CLIConf) error { fmt.Printf("\nHistogram\n\n") t := asciitable.MakeTable([]string{"Percentile", "Response Duration"}) for _, quantile := range []float64{25, 50, 75, 90, 95, 99, 100} { - t.AddRow([]string{fmt.Sprintf("%v", quantile), + t.AddRow([]string{ + fmt.Sprintf("%v", quantile), fmt.Sprintf("%v ms", result.Histogram.ValueAtQuantile(quantile)), }) } @@ -1958,12 +1982,29 @@ func makeClient(cf *CLIConf, useProfileLogin bool) (*client.TeleportClient, erro if cf.Username != "" { c.Username = cf.Username } + c.Passwordless = cf.Passwordless // if proxy is set, and proxy is not equal to profile's // loaded addresses, override the values if err := setClientWebProxyAddr(cf, c); err != nil { return nil, trace.Wrap(err) } + if c.ExtraProxyHeaders == nil { + c.ExtraProxyHeaders = map[string]string{} + } + for _, proxyHeaders := range cf.ExtraProxyHeaders { + proxyGlob := utils.GlobToRegexp(proxyHeaders.Proxy) + proxyRegexp, err := regexp.Compile(proxyGlob) + if err != nil { + return nil, trace.Wrap(err, "invalid proxy glob %q in tsh configuration file", proxyGlob) + } + if proxyRegexp.MatchString(c.WebProxyAddr) { + for k, v := range proxyHeaders.Headers { + c.ExtraProxyHeaders[k] = v + } + } + } + if len(fPorts) > 0 { c.LocalForwardPorts = fPorts } @@ -1992,6 +2033,11 @@ func makeClient(cf *CLIConf, useProfileLogin bool) (*client.TeleportClient, erro c.Labels = labels c.KeyTTL = time.Minute * time.Duration(cf.MinsToLive) c.InsecureSkipVerify = cf.InsecureSkipVerify + c.PredicateExpression = cf.PredicateExpression + + if cf.SearchKeywords != "" { + c.SearchKeywords = client.ParseSearchKeywords(cf.SearchKeywords, ',') + } // If a TTY was requested, make sure to allocate it. Note this applies to // "exec" command because a shell always has a TTY allocated. @@ -2086,8 +2132,12 @@ func makeClient(cf *CLIConf, useProfileLogin bool) (*client.TeleportClient, erro } } + tc.Config.Stderr = cf.Stderr() + tc.Config.Stdout = cf.Stdout() + tc.Config.Reason = cf.Reason tc.Config.Invited = cf.Invited + tc.Config.DisplayParticipantRequirements = cf.displayParticipantRequirements return tc, nil } @@ -2185,7 +2235,6 @@ func refuseArgs(command string, args []string) error { } else { return trace.BadParameter("unexpected argument: %s", arg) } - } return nil } @@ -2453,7 +2502,7 @@ func onApps(cf *CLIConf) error { // Get a list of all applications. var apps []types.Application err = client.RetryWithRelogin(cf.Context, tc, func() error { - apps, err = tc.ListApps(cf.Context) + apps, err = tc.ListApps(cf.Context, nil /* custom filter */) return err }) if err != nil { diff --git a/tool/tsh/tsh_helper_test.go b/tool/tsh/tsh_helper_test.go index 823f0f4f48141..faac5aae33ff0 100644 --- a/tool/tsh/tsh_helper_test.go +++ b/tool/tsh/tsh_helper_test.go @@ -84,7 +84,7 @@ func (s *suite) setupRootCluster(t *testing.T, options testSuiteOptions) { require.NoError(t, err) s.connector = mockConnector(t) - sshLoginRole, err := types.NewRole("ssh-login", types.RoleSpecV5{ + sshLoginRole, err := types.NewRoleV3("ssh-login", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{user.Username}, }, @@ -143,7 +143,7 @@ func (s *suite) setupLeafCluster(t *testing.T) { require.NoError(t, err) cfg.Proxy.DisableWebInterface = true - sshLoginRole, err := types.NewRole("ssh-login", types.RoleSpecV5{ + sshLoginRole, err := types.NewRoleV3("ssh-login", types.RoleSpecV5{ Allow: types.RoleConditions{ Logins: []string{user.Username}, }, diff --git a/tool/tsh/tsh_test.go b/tool/tsh/tsh_test.go index 375554dea5750..4ceb4adc0c994 100644 --- a/tool/tsh/tsh_test.go +++ b/tool/tsh/tsh_test.go @@ -17,9 +17,10 @@ limitations under the License. package main import ( + "bufio" + "bytes" "context" "fmt" - "io/ioutil" "net" "os" "path/filepath" @@ -153,7 +154,7 @@ func TestOIDCLogin(t *testing.T) { // set up an initial role with `request_access: always` in order to // trigger automatic post-login escalation. - populist, err := types.NewRole("populist", types.RoleSpecV5{ + populist, err := types.NewRoleV3("populist", types.RoleSpecV5{ Allow: types.RoleConditions{ Request: &types.AccessRequestConditions{ Roles: []string{"dictator"}, @@ -166,7 +167,7 @@ func TestOIDCLogin(t *testing.T) { require.NoError(t, err) // empty role which serves as our escalation target - dictator, err := types.NewRole("dictator", types.RoleSpecV5{}) + dictator, err := types.NewRoleV3("dictator", types.RoleSpecV5{}) require.NoError(t, err) alice, err := types.NewUser("alice@example.com") @@ -175,7 +176,11 @@ func TestOIDCLogin(t *testing.T) { connector := mockConnector(t) - authProcess, proxyProcess := makeTestServers(t, withBootstrap(populist, dictator, connector, alice)) + motd := "MESSAGE_OF_THE_DAY_OIDC" + authProcess, proxyProcess := makeTestServers(t, + withBootstrap(populist, dictator, connector, alice), + withMOTD(t, motd), + ) authServer := authProcess.GetAuthServer() require.NotNil(t, authServer) @@ -213,6 +218,8 @@ func TestOIDCLogin(t *testing.T) { } }() + buf := bytes.NewBuffer([]byte{}) + sc := bufio.NewScanner(buf) err = Run([]string{ "login", "--insecure", @@ -223,6 +230,7 @@ func TestOIDCLogin(t *testing.T) { }, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error { cf.mockSSOLogin = mockSSOLogin(t, authServer, alice) cf.SiteName = "localhost" + cf.overrideStderr = buf return nil })) @@ -231,11 +239,22 @@ func TestOIDCLogin(t *testing.T) { // verify that auto-request happened require.True(t, didAutoRequest.Load()) + findMOTD(t, sc, motd) // if we got this far, then tsh successfully registered name change from `alice` to // `alice@example.com`, since the correct name needed to be used for the access // request to be generated. } +func findMOTD(t *testing.T, sc *bufio.Scanner, motd string) { + t.Helper() + for sc.Scan() { + if strings.Contains(sc.Text(), motd) { + return + } + } + require.Fail(t, "Failed to find %q MOTD in the logs", motd) +} + // TestLoginIdentityOut makes sure that "tsh login --out " command // writes identity credentials to the specified path. func TestLoginIdentityOut(t *testing.T) { @@ -283,7 +302,11 @@ func TestRelogin(t *testing.T) { require.NoError(t, err) alice.SetRoles([]string{"access"}) - authProcess, proxyProcess := makeTestServers(t, withBootstrap(connector, alice)) + motd := "RELOGIN MOTD PRESENT" + authProcess, proxyProcess := makeTestServers(t, + withBootstrap(connector, alice), + withMOTD(t, motd), + ) authServer := authProcess.GetAuthServer() require.NotNil(t, authServer) @@ -291,6 +314,8 @@ func TestRelogin(t *testing.T) { proxyAddr, err := proxyProcess.ProxyWebAddr() require.NoError(t, err) + buf := bytes.NewBuffer([]byte{}) + sc := bufio.NewScanner(buf) err = Run([]string{ "login", "--insecure", @@ -299,9 +324,11 @@ func TestRelogin(t *testing.T) { "--proxy", proxyAddr.String(), }, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error { cf.mockSSOLogin = mockSSOLogin(t, authServer, alice) + cf.overrideStderr = buf return nil })) require.NoError(t, err) + findMOTD(t, sc, motd) err = Run([]string{ "login", @@ -309,10 +336,20 @@ func TestRelogin(t *testing.T) { "--debug", "--proxy", proxyAddr.String(), "localhost", - }, setHomePath(tmpHomePath)) + }, setHomePath(tmpHomePath), + cliOption(func(cf *CLIConf) error { + cf.mockSSOLogin = mockSSOLogin(t, authServer, alice) + cf.overrideStderr = buf + return nil + })) require.NoError(t, err) + findMOTD(t, sc, motd) - err = Run([]string{"logout"}, setHomePath(tmpHomePath)) + err = Run([]string{"logout"}, setHomePath(tmpHomePath), + cliOption(func(cf *CLIConf) error { + cf.overrideStderr = buf + return nil + })) require.NoError(t, err) err = Run([]string{ @@ -324,8 +361,10 @@ func TestRelogin(t *testing.T) { "localhost", }, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error { cf.mockSSOLogin = mockSSOLogin(t, authServer, alice) + cf.overrideStderr = buf return nil })) + findMOTD(t, sc, motd) require.NoError(t, err) } @@ -384,6 +423,11 @@ func TestMakeClient(t *testing.T) { conf.NodePort = 46528 conf.LocalForwardPorts = []string{"80:remote:180"} conf.DynamicForwardedPorts = []string{":8080"} + conf.ExtraProxyHeaders = []ExtraProxyHeaders{ + {Proxy: "proxy:3080", Headers: map[string]string{"A": "B"}}, + {Proxy: "*roxy:3080", Headers: map[string]string{"C": "D"}}, + {Proxy: "*hello:3080", Headers: map[string]string{"E": "F"}}, // shouldn't get included + } tc, err = makeClient(&conf, true) require.NoError(t, err) require.Equal(t, time.Minute*time.Duration(conf.MinsToLive), tc.Config.KeyTTL) @@ -403,6 +447,10 @@ func TestMakeClient(t *testing.T) { }, }, tc.Config.DynamicForwardedPorts) + require.Equal(t, + map[string]string{"A": "B", "C": "D"}, + tc.ExtraProxyHeaders) + _, proxy := makeTestServers(t) proxyWebAddr, err := proxy.ProxyWebAddr() @@ -444,7 +492,7 @@ func TestAccessRequestOnLeaf(t *testing.T) { lib.SetInsecureDevMode(isInsecure) }) - requester, err := types.NewRole("requester", types.RoleSpecV5{ + requester, err := types.NewRoleV3("requester", types.RoleSpecV5{ Allow: types.RoleConditions{ Request: &types.AccessRequestConditions{ Roles: []string{"access"}, @@ -634,7 +682,7 @@ func TestIdentityRead(t *testing.T) { require.NotNil(t, cb) // prepare the cluster CA separately - certBytes, err := ioutil.ReadFile("../../fixtures/certs/identities/ca.pem") + certBytes, err := os.ReadFile("../../fixtures/certs/identities/ca.pem") require.NoError(t, err) _, hosts, cert, _, _, err := ssh.ParseKnownHosts(certBytes) @@ -986,58 +1034,6 @@ func TestKubeConfigUpdate(t *testing.T) { } } -func TestMakeTableWithTruncatedColumn(t *testing.T) { - // os.Stdin.Fd() fails during go test, so width is defaulted to 80 - columns := []string{"column1", "column2", "column3"} - rows := [][]string{[]string{strings.Repeat("cell1", 6), strings.Repeat("cell2", 6), strings.Repeat("cell3", 6)}} - - testCases := []struct { - truncatedColumn string - expectedWidth int - expectedOutput []string - }{ - { - truncatedColumn: "column2", - expectedWidth: 80, - expectedOutput: []string{ - "column1 column2 column3 ", - "------------------------------ ----------------- ------------------------------ ", - "cell1cell1cell1cell1cell1cell1 cell2cell2cell... cell3cell3cell3cell3cell3cell3 ", - "", - }, - }, - { - truncatedColumn: "column3", - expectedWidth: 80, - expectedOutput: []string{ - "column1 column2 column3 ", - "------------------------------ ------------------------------ ----------------- ", - "cell1cell1cell1cell1cell1cell1 cell2cell2cell2cell2cell2cell2 cell3cell3cell... ", - "", - }, - }, - { - truncatedColumn: "no column match", - expectedWidth: 93, - expectedOutput: []string{ - "column1 column2 column3 ", - "------------------------------ ------------------------------ ------------------------------ ", - "cell1cell1cell1cell1cell1cell1 cell2cell2cell2cell2cell2cell2 cell3cell3cell3cell3cell3cell3 ", - "", - }, - }, - } - for _, testCase := range testCases { - t.Run(testCase.truncatedColumn, func(t *testing.T) { - table := makeTableWithTruncatedColumn(columns, rows, testCase.truncatedColumn) - rows := strings.Split(table.AsBuffer().String(), "\n") - require.Len(t, rows, 4) - require.Len(t, rows[2], testCase.expectedWidth) - require.Equal(t, testCase.expectedOutput, rows) - }) - } -} - func TestSetX11Config(t *testing.T) { t.Parallel() @@ -1238,8 +1234,8 @@ func TestSetX11Config(t *testing.T) { } type testServersOpts struct { - bootstrap []types.Resource - authConfigFunc func(cfg *service.AuthConfig) + bootstrap []types.Resource + authConfigFuncs []func(cfg *service.AuthConfig) } type testServerOptFunc func(o *testServersOpts) @@ -1252,7 +1248,11 @@ func withBootstrap(bootstrap ...types.Resource) testServerOptFunc { func withAuthConfig(fn func(cfg *service.AuthConfig)) testServerOptFunc { return func(o *testServersOpts) { - o.authConfigFunc = fn + if o.authConfigFuncs == nil { + o.authConfigFuncs = []func(cfg *service.AuthConfig){} + } + + o.authConfigFuncs = append(o.authConfigFuncs, fn) } } @@ -1267,6 +1267,17 @@ func withClusterName(t *testing.T, n string) testServerOptFunc { }) } +func withMOTD(t *testing.T, motd string) testServerOptFunc { + oldpass := client.PasswordFromConsoleFn + *client.PasswordFromConsoleFn = func() (string, error) { + return "", nil + } + t.Cleanup(func() { *client.PasswordFromConsoleFn = *oldpass }) + return withAuthConfig(func(cfg *service.AuthConfig) { + cfg.Preference.SetMessageOfTheDay(motd) + }) +} + func makeTestServers(t *testing.T, opts ...testServerOptFunc) (auth *service.TeleportProcess, proxy *service.TeleportProcess) { var options testServersOpts for _, opt := range opts { @@ -1299,8 +1310,8 @@ func makeTestServers(t *testing.T, opts ...testServerOptFunc) (auth *service.Tel cfg.Proxy.Enabled = false cfg.Log = utils.NewLoggerForTests() - if options.authConfigFunc != nil { - options.authConfigFunc(&cfg.Auth) + for _, fn := range options.authConfigFuncs { + fn(&cfg.Auth) } auth, err = service.NewTeleport(cfg) @@ -1383,7 +1394,7 @@ func mockSSOLogin(t *testing.T, authServer *auth.Server, user types.User) client require.NoError(t, err) // load CA cert - authority, err := authServer.GetCertAuthority(types.CertAuthID{ + authority, err := authServer.GetCertAuthority(ctx, types.CertAuthID{ Type: types.HostCA, DomainName: "localhost", }, false) diff --git a/tool/tsh/tshconfig.go b/tool/tsh/tshconfig.go new file mode 100644 index 0000000000000..c4a4a76c8f8b5 --- /dev/null +++ b/tool/tsh/tshconfig.go @@ -0,0 +1,59 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "os" + "path/filepath" + + "github.com/gravitational/teleport/api/profile" + "github.com/gravitational/trace" + "gopkg.in/yaml.v2" +) + +// .tsh config must go in a subdir as all .yaml files in .tsh get +// parsed automatically by the profile loader and results in yaml +// unmarshal errors. +const tshConfigPath = "config/config.yaml" + +// TshConfig represents configuration loaded from the tsh config file. +type TshConfig struct { + // ExtraHeaders are additional http headers to be included in + // webclient requests. + ExtraHeaders []ExtraProxyHeaders `yaml:"add_headers"` +} + +// ExtraProxyHeaders represents the headers to include with the +// webclient. +type ExtraProxyHeaders struct { + // Proxy is the domain of the proxy for these set of Headers, can contain globs. + Proxy string `yaml:"proxy"` + // Headers are the http header key values. + Headers map[string]string `yaml:"headers,omitempty"` +} + +func loadConfig(homePath string) (*TshConfig, error) { + confPath := filepath.Join(profile.FullProfilePath(homePath), tshConfigPath) + configFile, err := os.Open(confPath) + if err != nil { + return nil, trace.ConvertSystemError(err) + } + defer configFile.Close() + cfg := TshConfig{} + err = yaml.NewDecoder(configFile).Decode(&cfg) + return &cfg, trace.Wrap(err) +} diff --git a/webassets b/webassets index d92318d2efa30..f9ba10f271617 160000 --- a/webassets +++ b/webassets @@ -1 +1 @@ -Subproject commit d92318d2efa301f5b58ec3a242fb3b09dd580f19 +Subproject commit f9ba10f271617fabe1f247e91ea363286a84f604 From c4ab70ab96e58afe7db566f3a4e48d8e11d09a08 Mon Sep 17 00:00:00 2001 From: Andrew Burke Date: Wed, 23 Mar 2022 14:05:14 -0700 Subject: [PATCH 29/43] Revert "Merge master" This reverts commit a23bf820939893108512446cb3185ffd9c246574. --- .cloudbuild/ci/integration-tests.yaml | 23 +- .cloudbuild/ci/unit-tests.yaml | 24 +- .../scripts/cmd/integration-tests/args.go | 4 - .../scripts/cmd/integration-tests/main.go | 30 +- .cloudbuild/scripts/cmd/unit-tests/main.go | 32 +- .cloudbuild/scripts/go.mod | 15 +- .cloudbuild/scripts/go.sum | 27 +- .../internal/artifacts/artifacts_test.go | 3 +- .cloudbuild/scripts/internal/etcd/start.go | 199 +- .cloudbuild/scripts/internal/git/configure.go | 145 - .cloudbuild/scripts/internal/git/deploykey.go | 34 - .../scripts/internal/git/knownhosts.go | 33 - .cloudbuild/scripts/internal/git/unshallow.go | 25 - .cloudbuild/scripts/internal/github/meta.go | 58 - .cloudbuild/scripts/internal/secrets/fetch.go | 41 - .drone.yml | 46 +- .github/ISSUE_TEMPLATE/testplan.md | 20 +- .../workflows/robot/internal/bot/assign.go | 123 +- .../robot/internal/bot/assign_test.go | 146 - .github/workflows/robot/internal/bot/bot.go | 12 +- .../workflows/robot/internal/bot/bot_test.go | 19 +- .github/workflows/robot/internal/bot/check.go | 2 +- .github/workflows/robot/internal/bot/label.go | 2 +- .../robot/internal/bot/label_test.go | 4 +- .github/workflows/robot/internal/env/env.go | 24 +- .../workflows/robot/internal/env/env_test.go | 2 +- .../workflows/robot/internal/env/structs.go | 24 +- .../workflows/robot/internal/github/github.go | 130 +- .../workflows/robot/internal/review/review.go | 39 +- .../robot/internal/review/review_test.go | 304 +- .golangci.yml | 8 - Cargo.lock | 12 +- Makefile | 15 +- README.md | 2 +- api/client/client.go | 121 +- api/client/client_test.go | 37 +- api/client/contextdialer.go | 14 +- api/client/credentials.go | 4 +- api/client/credentials_test.go | 40 +- api/client/proto/authservice.pb.go | 1983 ++++--------- api/client/proto/authservice.proto | 44 +- api/client/proto/types.go | 2 +- api/client/proxy.go | 4 +- api/client/proxy/proxy.go | 16 - api/client/webclient/webclient.go | 103 +- api/client/webclient/webclient_test.go | 32 +- api/go.mod | 2 +- api/go.sum | 24 +- api/identityfile/identityfile.go | 5 +- api/profile/profile.go | 36 +- api/types/app_test.go | 4 + api/types/appserver.go | 9 +- api/types/constants.go | 6 +- api/types/database.go | 10 + api/types/databaseserver.go | 9 +- api/types/databaseserver_test.go | 4 + api/types/desktop.go | 97 - api/types/desktop_test.go | 88 - api/types/events/events.pb.go | 1365 +++------ api/types/events/events.proto | 19 - api/types/events/oneof.go | 24 +- api/types/events/unknown.go | 25 - api/types/kubernetes.go | 98 - api/types/kubernetes_test.go | 84 - api/types/resource.go | 47 +- api/types/resource_test.go | 50 - api/types/role.go | 21 +- api/types/server_test.go | 4 + api/types/types.pb.go | 1501 +++++----- api/types/types.proto | 5 - assets/aws/Makefile | 2 +- assets/backport/main.go | 3 +- assets/loadtest/k8s/Makefile | 9 +- assets/loadtest/k8s/auth-etcd.yaml | 3 - assets/loadtest/k8s/auth-firestore.yaml | 3 - assets/loadtest/k8s/prometheus.yaml | 291 -- assets/loadtest/k8s/proxy.yaml | 4 +- assets/loadtest/k8s/secrets/Makefile | 20 +- build.assets/Dockerfile | 5 - build.assets/Makefile | 10 - build.assets/build-package.sh | 6 +- .../gomod/update-api-import-path/main.go | 15 +- build.assets/install | 2 +- build.assets/tooling/cmd/check/main.go | 6 - build.assets/tooling/cmd/check/main_test.go | 9 - constants.go | 4 - docker/teleport-ent-quickstart.yml | 4 +- docker/teleport-lab.yml | 8 +- docker/teleport-quickstart.yml | 4 +- docs/config.json | 99 +- .../guides/redis/redisinsight-add-config.png | Bin 228417 -> 0 bytes .../guides/redis/redisinsight-connected.png | Bin 181286 -> 0 bytes .../guides/redis/redisinsight-startup.png | Bin 453447 -> 0 bytes .../guides/redis/redisinsight-tls-config.png | Bin 326444 -> 0 bytes .../pages/access-controls/getting-started.mdx | 2 +- .../access-controls/guides/dual-authz.mdx | 97 +- .../access-controls/guides/impersonation.mdx | 32 +- .../guides/per-session-mfa.mdx | 48 +- docs/pages/access-controls/guides/u2f.mdx | 2 +- .../pages/access-controls/guides/webauthn.mdx | 2 +- docs/pages/access-controls/reference.mdx | 20 +- docs/pages/api/getting-started.mdx | 14 +- docs/pages/application-access/controls.mdx | 2 +- .../application-access/getting-started.mdx | 83 +- .../application-access/guides/api-access.mdx | 1 - .../application-access/guides/aws-console.mdx | 38 +- .../guides/connecting-apps.mdx | 38 +- docs/pages/cloud/faq.mdx | 4 +- .../pages/database-access/getting-started.mdx | 33 +- .../guides/cockroachdb-self-hosted.mdx | 4 +- .../database-access/guides/gui-clients.mdx | 31 - .../database-access/guides/mongodb-atlas.mdx | 6 +- .../guides/mysql-self-hosted.mdx | 53 +- .../guides/postgres-redshift.mdx | 177 +- .../guides/postgres-self-hosted.mdx | 50 +- docs/pages/database-access/guides/rds.mdx | 293 +- docs/pages/database-access/guides/redis.mdx | 375 --- docs/pages/database-access/reference/aws.mdx | 219 +- docs/pages/database-access/reference/cli.mdx | 47 - docs/pages/desktop-access/getting-started.mdx | 12 +- docs/pages/desktop-access/reference.mdx | 19 - .../desktop-access/reference/clipboard.mdx | 46 - .../desktop-access/reference/sessions.mdx | 86 - docs/pages/enterprise/hsm.mdx | 2 +- docs/pages/enterprise/sso/adfs.mdx | 83 +- docs/pages/enterprise/sso/azuread.mdx | 30 +- docs/pages/enterprise/sso/gitlab.mdx | 12 +- .../pages/enterprise/sso/google-workspace.mdx | 2 - docs/pages/enterprise/sso/oidc.mdx | 12 +- docs/pages/enterprise/sso/okta.mdx | 12 +- docs/pages/enterprise/sso/one-login.mdx | 12 +- .../workflow/ssh-approval-jira-cloud.mdx | 115 +- .../workflow/ssh-approval-jira-server.mdx | 127 +- .../workflow/ssh-approval-mattermost.mdx | 93 +- .../workflow/ssh-approval-pagerduty.mdx | 101 +- .../workflow/ssh-approval-slack.mdx | 157 +- docs/pages/faq.mdx | 2 +- docs/pages/getting-started/docker-compose.mdx | 6 +- docs/pages/getting-started/linux-server.mdx | 49 +- docs/pages/includes/acme.mdx | 29 - .../database-access/aws-bootstrap.mdx | 47 - .../database-access/database-config.yaml | 2 +- .../database-access/guides-next-steps.mdx | 1 - .../pages/includes/database-access/guides.mdx | 3 - .../database-access/start-auth-proxy.mdx | 42 +- docs/pages/includes/dns-app-access.mdx | 4 - docs/pages/includes/dns.mdx | 59 - .../enterprise/oidcauthentication.mdx | 34 - .../enterprise/samlauthentication.mdx | 33 - docs/pages/includes/helm.mdx | 2 +- .../includes/kubernetes-access/helm-k8s.mdx | 2 +- .../includes/plugins/identity-export.mdx | 29 - docs/pages/includes/plugins/rbac.mdx | 60 - docs/pages/includes/tctl.mdx | 29 +- docs/pages/index.mdx | 4 +- docs/pages/installation.mdx | 13 - docs/pages/kubernetes-access/controls.mdx | 2 +- .../getting-started/agent.mdx | 132 +- .../getting-started/cluster.mdx | 54 +- docs/pages/kubernetes-access/guides.mdx | 3 + .../kubernetes-access/guides/federation.mdx | 49 +- .../kubernetes-access/guides/migration.mdx | 392 +++ .../guides/multiple-clusters.mdx | 136 +- .../guides/standalone-teleport.mdx | 114 +- .../kubernetes-access/helm/guides/aws.mdx | 36 +- .../kubernetes-access/helm/guides/custom.mdx | 20 +- .../helm/guides/digitalocean.mdx | 2 - .../kubernetes-access/helm/guides/gcp.mdx | 35 +- .../helm/guides/migration.mdx | 22 +- .../helm/includes/helm-install.mdx | 48 + .../helm/includes/helm-repo-add.mdx | 11 - .../teleport-cluster-cloud-warning.mdx | 10 - .../includes/teleport-cluster-install.mdx | 12 - .../includes/teleport-cluster-prereqs.mdx | 6 - .../kubernetes-access/helm/reference.mdx | 2575 ++++++++++++++++- .../helm/reference/teleport-cluster.mdx | 1348 --------- .../helm/reference/teleport-kube-agent.mdx | 1334 --------- docs/pages/kubernetes-access/introduction.mdx | 9 +- docs/pages/machine-id/getting-started.mdx | 236 -- docs/pages/machine-id/guides.mdx | 11 - docs/pages/machine-id/guides/ansible.mdx | 122 - docs/pages/machine-id/introduction.mdx | 56 - docs/pages/machine-id/reference.mdx | 8 - docs/pages/machine-id/reference/cli.mdx | 90 - .../machine-id/reference/configuration.mdx | 99 - docs/pages/server-access/getting-started.mdx | 145 +- docs/pages/server-access/guides/ansible.mdx | 126 + docs/pages/server-access/guides/vscode.mdx | 59 +- docs/pages/setup/admin/adding-nodes.mdx | 322 +-- docs/pages/setup/admin/daemon.mdx | 2 +- docs/pages/setup/admin/github-sso.mdx | 162 +- docs/pages/setup/admin/troubleshooting.mdx | 24 +- docs/pages/setup/admin/trustedclusters.mdx | 2 +- .../pages/setup/deployments/aws-terraform.mdx | 2 +- docs/pages/setup/guides/fluentd.mdx | 238 +- docs/pages/setup/guides/joining-nodes-aws.mdx | 24 +- .../pages/setup/guides/terraform-provider.mdx | 229 +- docs/pages/setup/operations/ca-rotation.mdx | 259 +- docs/pages/setup/reference/authentication.mdx | 33 +- docs/pages/setup/reference/cli.mdx | 75 +- docs/pages/setup/reference/license.mdx | 25 +- docs/pages/setup/reference/metrics.mdx | 87 +- .../setup/reference/predicate-language.mdx | 71 - docs/postrelease.md | 7 +- dronegen/mac_pkg.go | 5 +- dronegen/main.go | 5 +- e | 2 +- examples/aws/cloudformation/ent.yaml | 30 +- examples/aws/cloudformation/oss.yaml | 30 +- examples/aws/terraform/AMIS.md | 102 +- .../terraform/ha-autoscale-cluster/README.md | 2 +- .../aws/terraform/starter-cluster/README.md | 2 +- .../aws/terraform/starter-cluster/vars.tf | 2 +- examples/chart/CONTRIBUTING.md | 51 - .../teleport-auto-trustedcluster/README.md | 5 - .../.lint/{service.yaml => service.yml} | 0 .../.lint/standalone-customsize.yaml | 2 +- .../.lint/standalone-existingpvc.yaml | 2 +- .../chart/teleport-cluster/.lint/volumes.yaml | 2 +- .../teleport-cluster/.lint/webauthn.yaml | 8 - examples/chart/teleport-cluster/README.md | 4 - .../teleport-cluster/templates/config.yaml | 24 +- .../chart/teleport-cluster/tests/README.md | 18 - .../__snapshot__/certificate_test.yaml.snap | 16 - .../__snapshot__/clusterrole_test.yaml.snap | 27 - .../clusterrolebinding_test.yaml.snap | 14 - .../tests/__snapshot__/config_test.yaml.snap | 1098 ------- .../__snapshot__/deployment_test.yaml.snap | 1585 ---------- .../tests/__snapshot__/pdb_test.yaml.snap | 14 - .../tests/__snapshot__/psp_test.yaml.snap | 34 - .../tests/__snapshot__/pvc_test.yaml.snap | 30 - .../tests/__snapshot__/service_test.yaml.snap | 143 - .../serviceaccount_test.yaml.snap | 10 - .../tests/certificate_test.yaml | 29 - .../tests/clusterrole_test.yaml | 11 - .../tests/clusterrolebinding_test.yaml | 11 - .../teleport-cluster/tests/config_test.yaml | 330 --- .../tests/deployment_test.yaml | 533 ---- .../teleport-cluster/tests/pdb_test.yaml | 13 - .../teleport-cluster/tests/psp_test.yaml | 21 - .../teleport-cluster/tests/pvc_test.yaml | 65 - .../teleport-cluster/tests/service_test.yaml | 66 - .../tests/serviceaccount_test.yaml | 15 - .../chart/teleport-cluster/values.schema.json | 30 - examples/chart/teleport-cluster/values.yaml | 3 - examples/chart/teleport-daemonset/README.md | 5 - .../.lint/existing-data-volume.yaml | 5 - .../teleport-kube-agent/.lint/extra-args.yaml | 5 - .../teleport-kube-agent/.lint/extra-env.yaml | 7 - .../.lint/image-pull-policy-stateful.yaml | 7 - .../.lint/image-pull-policy.yaml | 5 - .../.lint/service-account-name.yaml | 5 - .../teleport-kube-agent/.lint/volumes.yaml | 2 +- examples/chart/teleport-kube-agent/README.md | 4 - .../teleport-kube-agent/templates/config.yaml | 6 - .../templates/deployment.yaml | 27 +- .../templates/statefulset.yaml | 27 +- .../chart/teleport-kube-agent/tests/README.md | 18 - .../__snapshot__/clusterrole_test.yaml.snap | 27 - .../clusterrolebinding_test.yaml.snap | 14 - .../tests/__snapshot__/config_test.yaml.snap | 658 ----- .../__snapshot__/deployment_test.yaml.snap | 1346 --------- .../tests/__snapshot__/pdb_test.yaml.snap | 14 - .../tests/__snapshot__/psp_test.yaml.snap | 34 - .../tests/__snapshot__/secret_test.yaml.snap | 22 - .../serviceaccount_test.yaml.snap | 10 - .../__snapshot__/statefulset_test.yaml.snap | 1454 ---------- .../tests/clusterrole_test.yaml | 11 - .../tests/clusterrolebinding_test.yaml | 11 - .../tests/config_test.yaml | 209 -- .../tests/deployment_test.yaml | 349 --- .../teleport-kube-agent/tests/pdb_test.yaml | 13 - .../teleport-kube-agent/tests/psp_test.yaml | 21 - .../tests/secret_test.yaml | 30 - .../tests/serviceaccount_test.yaml | 15 - .../tests/statefulset_test.yaml | 369 --- .../teleport-kube-agent/values.schema.json | 10 - .../chart/teleport-kube-agent/values.yaml | 9 - examples/chart/teleport/.helmignore | 1 - examples/chart/teleport/README.md | 5 - .../plugins/teleport-jira-cloud.toml | 20 - ...ra-self-hosted.toml => teleport-jira.toml} | 0 .../plugins/teleport-mattermost-cloud.toml | 21 - ...ost-self.toml => teleport-mattermost.toml} | 0 .../plugins/teleport-pagerduty-cloud.toml | 26 - ...duty-self.toml => teleport-pagerduty.toml} | 0 .../terraform/terraform-user-role-cloud.tf | 79 - ...-self-hosted.tf => terraform-user-role.tf} | 4 +- go.mod | 9 +- go.sum | 32 +- integration/app_integration_test.go | 154 +- integration/db_integration_test.go | 173 +- integration/helpers.go | 19 +- integration/hsm/hsm_test.go | 20 +- integration/integration_test.go | 130 +- integration/kube_integration_test.go | 25 +- integration/proxy_helpers_test.go | 6 +- integration/proxy_test.go | 4 +- lib/asciitable/table.go | 62 +- lib/asciitable/table_test.go | 52 - lib/auth/access.go | 2 +- lib/auth/access_test.go | 2 +- lib/auth/accountrecovery.go | 3 +- lib/auth/accountrecovery_test.go | 2 +- lib/auth/api.go | 38 +- lib/auth/apiserver.go | 16 +- lib/auth/auth.go | 205 +- lib/auth/auth_login_test.go | 392 +-- lib/auth/auth_test.go | 38 - lib/auth/auth_with_roles.go | 127 +- lib/auth/auth_with_roles_test.go | 330 +-- lib/auth/bot.go | 10 +- lib/auth/clt.go | 187 +- lib/auth/clt_test.go | 91 - lib/auth/db.go | 6 +- lib/auth/desktop.go | 2 +- lib/auth/github.go | 2 +- lib/auth/grpcserver.go | 35 +- lib/auth/grpcserver_test.go | 151 +- lib/auth/helpers.go | 8 +- lib/auth/httpfallback.go | 55 +- lib/auth/init.go | 22 +- lib/auth/init_test.go | 9 +- lib/auth/join.go | 7 +- lib/auth/join_test.go | 15 +- lib/auth/kube.go | 8 +- lib/auth/methods.go | 124 +- lib/auth/middleware.go | 21 +- lib/auth/mocku2f/mocku2f.go | 10 +- lib/auth/oidc.go | 8 +- lib/auth/password.go | 2 +- lib/auth/permissions.go | 10 +- lib/auth/permissions_test.go | 4 +- lib/auth/register.go | 37 + lib/auth/rotate.go | 15 +- lib/auth/saml.go | 8 +- lib/auth/session_access.go | 66 +- lib/auth/session_access_test.go | 44 +- lib/auth/sessions.go | 4 +- lib/auth/state_unix.go | 1 - lib/auth/tls_test.go | 130 +- lib/auth/trustedcluster.go | 53 +- lib/auth/trustedcluster_test.go | 126 - lib/auth/usertoken.go | 5 +- lib/auth/webauthn/login.go | 12 - lib/auth/webauthn/register.go | 13 - lib/auth/webauthn/register_test.go | 84 - lib/auth/webauthncli/api.go | 64 - lib/auth/webauthncli/export_fido2_test.go | 23 - lib/auth/webauthncli/fido2.go | 776 ----- lib/auth/webauthncli/fido2_common.go | 75 - lib/auth/webauthncli/fido2_other.go | 48 - lib/auth/webauthncli/fido2_test.go | 1438 --------- .../webauthncli/{u2f_login.go => login.go} | 11 +- .../{u2f_login_test.go => login_test.go} | 8 +- .../{u2f_register.go => register.go} | 11 +- ...{u2f_register_test.go => register_test.go} | 18 +- lib/backend/backend.go | 12 +- lib/backend/backend_test.go | 19 - lib/backend/dynamo/dynamodbbk.go | 16 +- lib/backend/dynamo/dynamodbbk_test.go | 7 +- lib/backend/etcdbk/etcd.go | 11 +- lib/backend/firestore/firestorebk.go | 1 + lib/backend/lite/lite.go | 164 +- lib/backend/lite/litemem_test.go | 56 + lib/backend/memory/memory.go | 1 + lib/backend/report.go | 3 + lib/backend/sanitize.go | 19 +- lib/backend/sanitize_test.go | 101 +- lib/backend/test/suite.go | 76 +- lib/backend/wrap.go | 3 + lib/benchmark/benchmark.go | 5 +- lib/bpf/bpf_test.go | 7 +- lib/cache/cache.go | 32 +- lib/cache/cache_test.go | 27 +- lib/cache/collections.go | 10 +- lib/cgroup/cgroup.go | 22 +- lib/cgroup/cgroup_test.go | 5 +- lib/client/api.go | 568 ++-- lib/client/api_login_test.go | 45 +- lib/client/api_test.go | 62 +- lib/client/client.go | 94 +- lib/client/client_test.go | 5 +- lib/client/{export.go => export_test.go} | 0 lib/client/https_client_test.go | 31 +- lib/client/identityfile/identity.go | 15 +- lib/client/identityfile/identity_test.go | 8 +- lib/client/keyagent_test.go | 3 +- lib/client/keystore.go | 45 +- lib/client/keystore_test.go | 16 +- lib/client/mfa.go | 31 +- lib/client/weblogin.go | 22 +- lib/client/weblogin_test.go | 4 +- lib/config/configuration.go | 36 +- lib/config/configuration_test.go | 12 +- lib/config/fileconf.go | 13 +- lib/datalog/access_test.go | 2 +- lib/defaults/defaults.go | 11 - lib/events/api.go | 3 - lib/events/auditlog_test.go | 9 +- lib/events/codes.go | 5 - lib/events/complete.go | 195 +- lib/events/complete_test.go | 145 - lib/events/dynamic.go | 16 +- lib/events/emitter_test.go | 3 +- lib/events/events_test.go | 3 +- lib/events/fields.go | 3 +- lib/events/filesessions/fileasync.go | 7 +- .../filesessions/fileasync_chaos_test.go | 5 +- lib/events/filesessions/fileasync_test.go | 17 +- lib/events/filesessions/filestream.go | 50 +- lib/events/filesessions/fileuploader_test.go | 5 +- lib/events/gcssessions/gcsstream.go | 7 +- lib/events/gcssessions/gcsstream_test.go | 34 - lib/events/playback.go | 3 +- lib/events/s3sessions/s3stream.go | 17 - lib/events/sessionlog.go | 7 +- lib/events/stream.go | 14 +- lib/events/test/streamsuite.go | 5 +- lib/events/test/suite.go | 8 +- lib/kube/kubeconfig/kubeconfig_test.go | 3 +- lib/kube/proxy/auth_test.go | 3 +- lib/kube/proxy/forwarder_test.go | 4 +- lib/kube/proxy/roundtrip.go | 3 +- lib/kube/proxy/sess.go | 105 +- lib/kube/utils/utils.go | 29 +- lib/kube/utils/utils_test.go | 30 - lib/multiplexer/multiplexer_test.go | 7 +- lib/restrictedsession/restricted_test.go | 3 +- lib/reversetunnel/agent.go | 6 +- lib/reversetunnel/agent_test.go | 2 +- lib/reversetunnel/api.go | 15 - lib/reversetunnel/conn.go | 10 + lib/reversetunnel/fake.go | 41 +- lib/reversetunnel/localsite.go | 64 +- lib/reversetunnel/localsite_test.go | 101 - lib/reversetunnel/remotesite.go | 6 +- lib/reversetunnel/resolver.go | 4 +- lib/reversetunnel/srv.go | 2 +- lib/reversetunnel/srv_test.go | 3 +- lib/reversetunnel/transport.go | 16 +- lib/service/connect.go | 4 +- lib/service/desktop.go | 1 - lib/service/service.go | 102 +- lib/service/service_test.go | 31 +- lib/service/state.go | 4 +- lib/services/access_request_test.go | 2 +- lib/services/database.go | 133 +- lib/services/database_test.go | 51 +- lib/services/desktop.go | 1 - lib/services/local/desktops.go | 67 - lib/services/local/desktops_test.go | 213 -- lib/services/local/perf_test.go | 6 +- lib/services/local/presence.go | 89 +- lib/services/local/presence_test.go | 117 +- lib/services/local/session.go | 31 +- lib/services/local/trust.go | 12 +- lib/services/local/users.go | 2 - lib/services/local/users_test.go | 34 +- lib/services/matchers.go | 2 +- lib/services/matchers_test.go | 26 - lib/services/reconciler.go | 18 +- lib/services/reconciler_test.go | 8 +- lib/services/resource.go | 83 +- lib/services/role.go | 24 +- lib/services/role_test.go | 16 +- lib/services/suite/suite.go | 17 +- lib/services/trust.go | 10 +- lib/services/watcher.go | 58 +- lib/srv/alpnproxy/auth/auth_proxy.go | 23 +- lib/srv/alpnproxy/auth/auth_proxy_test.go | 76 - lib/srv/alpnproxy/common/protocols.go | 17 +- lib/srv/alpnproxy/proxy.go | 14 +- lib/srv/alpnproxy/proxy_test.go | 59 - lib/srv/app/aws/endpoints.go | 160 - lib/srv/app/aws/endpoints_test.go | 359 --- lib/srv/app/aws/handler.go | 14 + lib/srv/app/server.go | 4 +- lib/srv/app/server_test.go | 2 +- lib/srv/app/watcher.go | 60 +- lib/srv/authhandlers.go | 3 +- lib/srv/ctx.go | 22 +- lib/srv/db/access_test.go | 83 +- lib/srv/db/audit_test.go | 8 +- lib/srv/db/ca.go | 9 +- lib/srv/db/ca_test.go | 20 +- lib/srv/db/cloud/watchers/rds.go | 25 +- lib/srv/db/cloud/watchers/redshift.go | 7 - lib/srv/db/cloud/watchers/watcher_test.go | 101 +- lib/srv/db/common/errors.go | 56 - lib/srv/db/common/test.go | 2 +- lib/srv/db/ha_test.go | 2 +- lib/srv/db/mongodb/connect.go | 23 +- lib/srv/db/mongodb/engine.go | 9 +- lib/srv/db/mongodb/test.go | 11 +- lib/srv/db/mysql/engine.go | 14 +- lib/srv/db/mysql/test.go | 25 - lib/srv/db/postgres/engine.go | 14 +- lib/srv/db/proxy_test.go | 4 +- lib/srv/db/proxyserver.go | 76 +- lib/srv/db/proxyserver_test.go | 8 +- lib/srv/db/redis/client.go | 293 -- lib/srv/db/redis/cmds.go | 53 +- lib/srv/db/redis/engine.go | 86 +- lib/srv/db/redis/protocol/resp2.go | 54 +- lib/srv/db/redis/protocol/resp2_test.go | 106 - lib/srv/db/server.go | 4 +- lib/srv/db/watcher.go | 7 +- lib/srv/desktop/audit.go | 21 +- lib/srv/desktop/audit_test.go | 3 - lib/srv/desktop/discovery.go | 20 +- lib/srv/desktop/discovery_test.go | 10 +- lib/srv/desktop/rdp/rdpclient/client.go | 30 +- lib/srv/desktop/rdp/rdpclient/librdprs.h | 15 +- lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs | 233 +- lib/srv/desktop/rdp/rdpclient/src/lib.rs | 22 +- lib/srv/desktop/rdp/rdpclient/src/vchan.rs | 20 +- lib/srv/desktop/windows_server.go | 21 +- lib/srv/desktop/windows_server_test.go | 14 +- lib/srv/reexec.go | 19 +- lib/srv/regular/sshserver.go | 3 +- lib/srv/regular/sshserver_test.go | 9 +- lib/srv/sess.go | 202 +- lib/srv/sess_test.go | 246 -- lib/srv/termmanager.go | 76 +- lib/srv/termmanager_test.go | 50 - lib/sshutils/scp/scp_test.go | 15 +- lib/sshutils/x11/display.go | 24 +- lib/sshutils/x11/display_test.go | 187 +- lib/tlsca/ca.go | 12 +- lib/utils/certs_test.go | 4 +- lib/utils/chconn_test.go | 4 +- lib/utils/cli.go | 74 +- lib/utils/cli_test.go | 4 +- lib/utils/conn.go | 3 +- lib/utils/environment_test.go | 3 +- lib/utils/jsontools_test.go | 5 +- lib/utils/kernel.go | 3 +- lib/utils/prompt/confirmation.go | 13 +- lib/utils/prompt/sync_reader.go | 55 - lib/utils/prompt/sync_reader_test.go | 66 - lib/utils/syslog.go | 4 +- lib/utils/timeout_test.go | 4 +- lib/utils/token.go | 4 +- lib/utils/utils.go | 7 +- lib/utils/utils_test.go | 8 +- lib/web/apiserver.go | 96 +- lib/web/apiserver_login_test.go | 139 +- lib/web/apiserver_test.go | 115 +- lib/web/app/transport.go | 13 +- lib/web/desktop/playback.go | 24 - lib/web/desktop/playback_test.go | 2 +- lib/web/gziphandler.go | 4 +- lib/web/join_tokens.go | 41 +- lib/web/join_tokens_test.go | 5 +- lib/web/mfa.go | 20 +- lib/web/resources_test.go | 4 +- lib/web/sessions.go | 11 +- lib/web/static_test.go | 11 +- lib/web/ui/perf_test.go | 8 +- lib/web/ui/usercontext.go | 3 + lib/web/ui/usercontext_test.go | 3 +- rfd/0039-sni-alpn-teleport-proxy-routing.md | 2 +- rfd/0043-kubeaccess-multiparty.md | 185 +- rfd/0046-database-access-config.md | 411 +-- rfd/0056-sql-backend.md | 137 - rfd/0060-gRPC-backend.md | 200 -- tool/tbot/botfs/botfs.go | 156 - tool/tbot/botfs/fs_linux.go | 450 --- tool/tbot/botfs/fs_other.go | 113 - tool/tbot/config/config.go | 101 +- tool/tbot/config/config_destination.go | 18 +- tool/tbot/config/config_test.go | 56 +- tool/tbot/config/configtemplate.go | 5 + tool/tbot/config/configtemplate_ssh.go | 121 +- tool/tbot/config/destination.go | 26 +- tool/tbot/config/destination_directory.go | 136 +- tool/tbot/config/destination_memory.go | 13 +- tool/tbot/destination/destination.go | 25 +- tool/tbot/identity/artifact.go | 144 - tool/tbot/identity/identity.go | 254 +- tool/tbot/identity/kinds.go | 86 - tool/tbot/init.go | 557 ---- tool/tbot/init_test.go | 271 -- tool/tbot/main.go | 534 +--- tool/tctl/common/app_command.go | 62 +- tool/tctl/common/auth_command.go | 89 +- tool/tctl/common/auth_command_test.go | 91 +- tool/tctl/common/bots_command.go | 38 +- tool/tctl/common/collection.go | 211 +- tool/tctl/common/db_command.go | 62 +- tool/tctl/common/desktop_command.go | 97 - tool/tctl/common/kube_command.go | 84 - tool/tctl/common/node_command.go | 93 +- tool/tctl/common/resource_command.go | 26 +- tool/tctl/common/resource_command_test.go | 8 +- tool/tctl/common/status_command.go | 14 +- tool/tctl/common/tctl.go | 8 - tool/tctl/common/token_command.go | 28 +- tool/tctl/common/usage.go | 10 +- tool/tctl/main.go | 3 - tool/teleport/common/teleport.go | 4 +- tool/teleport/common/teleport_test.go | 40 +- tool/tsh/app.go | 14 +- tool/tsh/aws.go | 3 +- tool/tsh/db.go | 12 +- tool/tsh/db_test.go | 3 +- tool/tsh/dbcmd.go | 2 +- tool/tsh/kube.go | 67 +- tool/tsh/mfa.go | 89 +- tool/tsh/proxy_test.go | 7 +- tool/tsh/resolve_default_addr.go | 3 +- tool/tsh/tsh.go | 173 +- tool/tsh/tsh_helper_test.go | 4 +- tool/tsh/tsh_test.go | 145 +- tool/tsh/tshconfig.go | 59 - webassets | 2 +- 617 files changed, 11203 insertions(+), 37048 deletions(-) delete mode 100644 .cloudbuild/scripts/internal/git/configure.go delete mode 100644 .cloudbuild/scripts/internal/git/deploykey.go delete mode 100644 .cloudbuild/scripts/internal/git/knownhosts.go delete mode 100644 .cloudbuild/scripts/internal/git/unshallow.go delete mode 100644 .cloudbuild/scripts/internal/github/meta.go delete mode 100644 .cloudbuild/scripts/internal/secrets/fetch.go delete mode 100644 .github/workflows/robot/internal/bot/assign_test.go delete mode 100644 api/types/desktop_test.go delete mode 100644 api/types/events/unknown.go delete mode 100644 api/types/kubernetes_test.go delete mode 100644 assets/loadtest/k8s/prometheus.yaml delete mode 100644 docs/img/database-access/guides/redis/redisinsight-add-config.png delete mode 100644 docs/img/database-access/guides/redis/redisinsight-connected.png delete mode 100644 docs/img/database-access/guides/redis/redisinsight-startup.png delete mode 100644 docs/img/database-access/guides/redis/redisinsight-tls-config.png delete mode 100644 docs/pages/database-access/guides/redis.mdx delete mode 100644 docs/pages/desktop-access/reference.mdx delete mode 100644 docs/pages/desktop-access/reference/clipboard.mdx delete mode 100644 docs/pages/desktop-access/reference/sessions.mdx delete mode 100644 docs/pages/includes/acme.mdx delete mode 100644 docs/pages/includes/database-access/aws-bootstrap.mdx delete mode 100644 docs/pages/includes/dns-app-access.mdx delete mode 100644 docs/pages/includes/dns.mdx delete mode 100644 docs/pages/includes/enterprise/oidcauthentication.mdx delete mode 100644 docs/pages/includes/enterprise/samlauthentication.mdx delete mode 100644 docs/pages/includes/plugins/identity-export.mdx delete mode 100644 docs/pages/includes/plugins/rbac.mdx create mode 100644 docs/pages/kubernetes-access/guides/migration.mdx create mode 100644 docs/pages/kubernetes-access/helm/includes/helm-install.mdx delete mode 100644 docs/pages/kubernetes-access/helm/includes/helm-repo-add.mdx delete mode 100644 docs/pages/kubernetes-access/helm/includes/teleport-cluster-cloud-warning.mdx delete mode 100644 docs/pages/kubernetes-access/helm/includes/teleport-cluster-install.mdx delete mode 100644 docs/pages/kubernetes-access/helm/includes/teleport-cluster-prereqs.mdx delete mode 100644 docs/pages/kubernetes-access/helm/reference/teleport-cluster.mdx delete mode 100644 docs/pages/kubernetes-access/helm/reference/teleport-kube-agent.mdx delete mode 100644 docs/pages/machine-id/getting-started.mdx delete mode 100644 docs/pages/machine-id/guides.mdx delete mode 100644 docs/pages/machine-id/guides/ansible.mdx delete mode 100644 docs/pages/machine-id/introduction.mdx delete mode 100644 docs/pages/machine-id/reference.mdx delete mode 100644 docs/pages/machine-id/reference/cli.mdx delete mode 100644 docs/pages/machine-id/reference/configuration.mdx create mode 100644 docs/pages/server-access/guides/ansible.mdx delete mode 100644 docs/pages/setup/reference/predicate-language.mdx delete mode 100644 examples/chart/CONTRIBUTING.md rename examples/chart/teleport-cluster/.lint/{service.yaml => service.yml} (100%) delete mode 100644 examples/chart/teleport-cluster/.lint/webauthn.yaml delete mode 100644 examples/chart/teleport-cluster/tests/README.md delete mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/certificate_test.yaml.snap delete mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/clusterrole_test.yaml.snap delete mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/clusterrolebinding_test.yaml.snap delete mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/config_test.yaml.snap delete mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/deployment_test.yaml.snap delete mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/pdb_test.yaml.snap delete mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/psp_test.yaml.snap delete mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/pvc_test.yaml.snap delete mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/service_test.yaml.snap delete mode 100644 examples/chart/teleport-cluster/tests/__snapshot__/serviceaccount_test.yaml.snap delete mode 100644 examples/chart/teleport-cluster/tests/certificate_test.yaml delete mode 100644 examples/chart/teleport-cluster/tests/clusterrole_test.yaml delete mode 100644 examples/chart/teleport-cluster/tests/clusterrolebinding_test.yaml delete mode 100644 examples/chart/teleport-cluster/tests/config_test.yaml delete mode 100644 examples/chart/teleport-cluster/tests/deployment_test.yaml delete mode 100644 examples/chart/teleport-cluster/tests/pdb_test.yaml delete mode 100644 examples/chart/teleport-cluster/tests/psp_test.yaml delete mode 100644 examples/chart/teleport-cluster/tests/pvc_test.yaml delete mode 100644 examples/chart/teleport-cluster/tests/service_test.yaml delete mode 100644 examples/chart/teleport-cluster/tests/serviceaccount_test.yaml delete mode 100644 examples/chart/teleport-kube-agent/.lint/existing-data-volume.yaml delete mode 100644 examples/chart/teleport-kube-agent/.lint/extra-args.yaml delete mode 100644 examples/chart/teleport-kube-agent/.lint/extra-env.yaml delete mode 100644 examples/chart/teleport-kube-agent/.lint/image-pull-policy-stateful.yaml delete mode 100644 examples/chart/teleport-kube-agent/.lint/image-pull-policy.yaml delete mode 100644 examples/chart/teleport-kube-agent/.lint/service-account-name.yaml delete mode 100644 examples/chart/teleport-kube-agent/tests/README.md delete mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/clusterrole_test.yaml.snap delete mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/clusterrolebinding_test.yaml.snap delete mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/config_test.yaml.snap delete mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap delete mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/pdb_test.yaml.snap delete mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/psp_test.yaml.snap delete mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/secret_test.yaml.snap delete mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/serviceaccount_test.yaml.snap delete mode 100644 examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap delete mode 100644 examples/chart/teleport-kube-agent/tests/clusterrole_test.yaml delete mode 100644 examples/chart/teleport-kube-agent/tests/clusterrolebinding_test.yaml delete mode 100644 examples/chart/teleport-kube-agent/tests/config_test.yaml delete mode 100644 examples/chart/teleport-kube-agent/tests/deployment_test.yaml delete mode 100644 examples/chart/teleport-kube-agent/tests/pdb_test.yaml delete mode 100644 examples/chart/teleport-kube-agent/tests/psp_test.yaml delete mode 100644 examples/chart/teleport-kube-agent/tests/secret_test.yaml delete mode 100644 examples/chart/teleport-kube-agent/tests/serviceaccount_test.yaml delete mode 100644 examples/chart/teleport-kube-agent/tests/statefulset_test.yaml delete mode 100644 examples/resources/plugins/teleport-jira-cloud.toml rename examples/resources/plugins/{teleport-jira-self-hosted.toml => teleport-jira.toml} (100%) delete mode 100644 examples/resources/plugins/teleport-mattermost-cloud.toml rename examples/resources/plugins/{teleport-mattermost-self.toml => teleport-mattermost.toml} (100%) delete mode 100644 examples/resources/plugins/teleport-pagerduty-cloud.toml rename examples/resources/plugins/{teleport-pagerduty-self.toml => teleport-pagerduty.toml} (100%) delete mode 100644 examples/resources/terraform/terraform-user-role-cloud.tf rename examples/resources/terraform/{terraform-user-role-self-hosted.tf => terraform-user-role.tf} (93%) delete mode 100644 lib/auth/webauthncli/api.go delete mode 100644 lib/auth/webauthncli/export_fido2_test.go delete mode 100644 lib/auth/webauthncli/fido2.go delete mode 100644 lib/auth/webauthncli/fido2_common.go delete mode 100644 lib/auth/webauthncli/fido2_other.go delete mode 100644 lib/auth/webauthncli/fido2_test.go rename lib/auth/webauthncli/{u2f_login.go => login.go} (91%) rename lib/auth/webauthncli/{u2f_login_test.go => login_test.go} (98%) rename lib/auth/webauthncli/{u2f_register.go => register.go} (94%) rename lib/auth/webauthncli/{u2f_register_test.go => register_test.go} (92%) create mode 100644 lib/backend/lite/litemem_test.go rename lib/client/{export.go => export_test.go} (100%) delete mode 100644 lib/events/complete_test.go delete mode 100644 lib/reversetunnel/localsite_test.go delete mode 100644 lib/services/local/desktops_test.go delete mode 100644 lib/srv/alpnproxy/auth/auth_proxy_test.go delete mode 100644 lib/srv/app/aws/endpoints.go delete mode 100644 lib/srv/app/aws/endpoints_test.go delete mode 100644 lib/srv/db/redis/client.go delete mode 100644 lib/srv/db/redis/protocol/resp2_test.go delete mode 100644 lib/srv/termmanager_test.go delete mode 100644 lib/utils/prompt/sync_reader.go delete mode 100644 lib/utils/prompt/sync_reader_test.go delete mode 100644 rfd/0056-sql-backend.md delete mode 100644 rfd/0060-gRPC-backend.md delete mode 100644 tool/tbot/botfs/botfs.go delete mode 100644 tool/tbot/botfs/fs_linux.go delete mode 100644 tool/tbot/botfs/fs_other.go delete mode 100644 tool/tbot/identity/artifact.go delete mode 100644 tool/tbot/identity/kinds.go delete mode 100644 tool/tbot/init.go delete mode 100644 tool/tbot/init_test.go delete mode 100644 tool/tctl/common/desktop_command.go delete mode 100644 tool/tctl/common/kube_command.go delete mode 100644 tool/tsh/tshconfig.go diff --git a/.cloudbuild/ci/integration-tests.yaml b/.cloudbuild/ci/integration-tests.yaml index 61deb3012cf17..8a64ecf50b964 100644 --- a/.cloudbuild/ci/integration-tests.yaml +++ b/.cloudbuild/ci/integration-tests.yaml @@ -1,14 +1,14 @@ timeout: 25m options: machineType: E2_HIGHCPU_32 - - # This build needs to run in environments where the _GITHUB_DEPLOY_KEY_SRC - # substitution is defined, but also environments where it isn't. The - # ALLOW_LOOSE option disables GCBs strict checking of substitution usage, - # so that the build will still run if _GITHUB_DEPLOY_KEY_SRC is not defined. - substitution_option: ALLOW_LOOSE steps: + # GCB does a shallow checkout for a build, but if we want to check our changes + # against other branches we'll need to fetch the repo history. + - name: gcr.io/cloud-builders/git + id: fetch-history + args: ['fetch', '--unshallow'] + # Run the integration tests. Actual content of this job depends on the changes # detected in the PR - name: quay.io/gravitational/teleport-buildbox:teleport10 @@ -18,10 +18,9 @@ steps: args: - -c - | - go run ./cmd/integration-tests \ - -target "$_BASE_BRANCH" \ - -bucket test-logs \ - -build $BUILD_ID \ - -key-secret "$_GITHUB_DEPLOY_KEY_SRC" \ + go run ./cmd/integration-tests \ + -target $_BASE_BRANCH \ + -bucket test-logs \ + -build $BUILD_ID \ -a "test-logs/*.json" - timeout: 25m + timeout: 20m diff --git a/.cloudbuild/ci/unit-tests.yaml b/.cloudbuild/ci/unit-tests.yaml index ae9935c10088f..c673e9485150c 100644 --- a/.cloudbuild/ci/unit-tests.yaml +++ b/.cloudbuild/ci/unit-tests.yaml @@ -3,13 +3,14 @@ timeout: 25m options: machineType: 'E2_HIGHCPU_32' - # This build needs to run in environments where the _GITHUB_DEPLOY_KEY_SRC - # substitution is defined, but also environments where it isn't. The - # ALLOW_LOOSE option disables GCBs strict checking of substitution usage, - # so that the build will still run if _GITHUB_DEPLOY_KEY_SRC is not defined. - substitution_option: ALLOW_LOOSE - steps: + # GCB does a shallow checkout for a build, but if we want to check our changes + # against other branches we'll need to fetch the repo history. This takes less + # than 30s at the time of writing, so it is probably not worth tweaking. + - name: gcr.io/cloud-builders/git + id: fetch-history + args: ['fetch', '--unshallow'] + # Run the unit tests. Actual content of this job depends on the changes # detected in the PR - name: quay.io/gravitational/teleport-buildbox:teleport10 @@ -19,10 +20,9 @@ steps: args: - -c - | - go run ./cmd/unit-tests \ - -target $_BASE_BRANCH \ - -bucket test-logs \ - -build $BUILD_ID \ - -key-secret "$_GITHUB_DEPLOY_KEY_SRC" \ + go run ./cmd/unit-tests \ + -target $_BASE_BRANCH \ + -bucket test-logs \ + -build $BUILD_ID \ -a "test-logs/*.json" - timeout: 25m + timeout: 20m diff --git a/.cloudbuild/scripts/cmd/integration-tests/args.go b/.cloudbuild/scripts/cmd/integration-tests/args.go index 2c98fab9c9077..aa29b10c71f91 100644 --- a/.cloudbuild/scripts/cmd/integration-tests/args.go +++ b/.cloudbuild/scripts/cmd/integration-tests/args.go @@ -33,8 +33,6 @@ type commandlineArgs struct { buildID string artifactSearchPatterns customflag.StringArray bucket string - githubKeySrc string - skipUnshallow bool } // validate ensures the suplied arguments are valid & internally consistent. @@ -87,8 +85,6 @@ func parseCommandLine() (*commandlineArgs, error) { flag.StringVar(&args.buildID, "build", "", "The build ID") flag.StringVar(&args.bucket, "bucket", "", "The artifact storage bucket.") flag.Var(&args.artifactSearchPatterns, "a", "Path to artifacts. May be shell-globbed, and have multiple entries.") - flag.StringVar(&args.githubKeySrc, "key-secret", "", "Location of github deploy token, as a Google Cloud Secret") - flag.BoolVar(&args.skipUnshallow, "skip-unshallow", false, "Skip unshallowing the repository.") flag.Parse() diff --git a/.cloudbuild/scripts/cmd/integration-tests/main.go b/.cloudbuild/scripts/cmd/integration-tests/main.go index 3f64c8fee7a58..f6de022e00fcc 100644 --- a/.cloudbuild/scripts/cmd/integration-tests/main.go +++ b/.cloudbuild/scripts/cmd/integration-tests/main.go @@ -29,8 +29,6 @@ import ( "github.com/gravitational/teleport/.cloudbuild/scripts/internal/artifacts" "github.com/gravitational/teleport/.cloudbuild/scripts/internal/changes" "github.com/gravitational/teleport/.cloudbuild/scripts/internal/etcd" - "github.com/gravitational/teleport/.cloudbuild/scripts/internal/git" - "github.com/gravitational/teleport/.cloudbuild/scripts/internal/secrets" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" ) @@ -57,26 +55,6 @@ func innerMain() error { return trace.Wrap(err) } - // If a github deploy key location was supplied... - var deployKey []byte - if args.githubKeySrc != "" { - // fetch the deployment key from the GCB secret manager - log.Infof("Fetching deploy key from %s", args.githubKeySrc) - deployKey, err = secrets.Fetch(context.Background(), args.githubKeySrc) - if err != nil { - return trace.Wrap(err, "failed fetching deploy key") - } - } - - if !args.skipUnshallow { - unshallowCtx, unshallowCancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer unshallowCancel() - err = git.UnshallowRepository(unshallowCtx, args.workspace, deployKey) - if err != nil { - return trace.Wrap(err, "unshallow failed") - } - } - moduleCacheDir := filepath.Join(os.TempDir(), gomodcacheDir) gomodcache := fmt.Sprintf("GOMODCACHE=%s", moduleCacheDir) @@ -92,6 +70,9 @@ func innerMain() error { return nil } + cancelCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + // From this point on, whatever happens we want to upload any artifacts // produced by the build defer func() { @@ -133,13 +114,10 @@ func innerMain() error { // diagnostic warnings that would pollute the build log and just confuse // people when they are trying to work out why their build failed. log.Printf("Starting etcd...") - timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - etcdSvc, err := etcd.Start(timeoutCtx, args.workspace) + err = etcd.Start(cancelCtx, args.workspace, nonrootUID, nonrootGID, gomodcache) if err != nil { return trace.Wrap(err, "failed starting etcd") } - defer etcdSvc.Stop() log.Printf("Running nonroot integration tests...") err = runNonrootIntegrationTests(args.workspace, nonrootUID, nonrootGID, gomodcache) diff --git a/.cloudbuild/scripts/cmd/unit-tests/main.go b/.cloudbuild/scripts/cmd/unit-tests/main.go index 3f8e52ef551ad..8b374c726d772 100644 --- a/.cloudbuild/scripts/cmd/unit-tests/main.go +++ b/.cloudbuild/scripts/cmd/unit-tests/main.go @@ -29,8 +29,6 @@ import ( "github.com/gravitational/teleport/.cloudbuild/scripts/internal/changes" "github.com/gravitational/teleport/.cloudbuild/scripts/internal/customflag" "github.com/gravitational/teleport/.cloudbuild/scripts/internal/etcd" - "github.com/gravitational/teleport/.cloudbuild/scripts/internal/git" - "github.com/gravitational/teleport/.cloudbuild/scripts/internal/secrets" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" ) @@ -50,8 +48,6 @@ type commandlineArgs struct { buildID string artifactSearchPatterns customflag.StringArray bucket string - githubKeySrc string - skipUnshallow bool } // NOTE: changing the interface to this build script may require follow-up @@ -65,8 +61,6 @@ func parseCommandLine() (commandlineArgs, error) { flag.StringVar(&args.buildID, "build", "", "The build ID") flag.StringVar(&args.bucket, "bucket", "", "The artifact storage bucket.") flag.Var(&args.artifactSearchPatterns, "a", "Path to artifacts. May be globbed, and have multiple entries.") - flag.StringVar(&args.githubKeySrc, "key-secret", "", "Location of github deploy token, as a Google Cloud Secret") - flag.BoolVar(&args.skipUnshallow, "skip-unshallow", false, "Skip unshallowing the repository.") flag.Parse() @@ -114,26 +108,6 @@ func run() error { return trace.Wrap(err) } - // If a github deploy key location was supplied... - var deployKey []byte - if args.githubKeySrc != "" { - // fetch the deployment key from the GCB secret manager - log.Infof("Fetching deploy key from %s", args.githubKeySrc) - deployKey, err = secrets.Fetch(context.Background(), args.githubKeySrc) - if err != nil { - return trace.Wrap(err, "failed fetching deploy key") - } - } - - if !args.skipUnshallow { - unshallowCtx, unshallowCancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer unshallowCancel() - err = git.UnshallowRepository(unshallowCtx, args.workspace, deployKey) - if err != nil { - return trace.Wrap(err, "unshallow failed") - } - } - log.Println("Analysing code changes") ch, err := changes.Analyze(args.workspace, args.targetBranch, args.commitSHA) if err != nil { @@ -147,13 +121,11 @@ func run() error { } log.Printf("Starting etcd...") - timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + cancelCtx, cancel := context.WithCancel(context.Background()) defer cancel() - etcdSvc, err := etcd.Start(timeoutCtx, args.workspace) - if err != nil { + if err := etcd.Start(cancelCtx, args.workspace, 0, 0); err != nil { return trace.Wrap(err, "failed starting etcd") } - defer etcdSvc.Stop() // From this point on, whatever happens we want to upload any artifacts // produced by the build diff --git a/.cloudbuild/scripts/go.mod b/.cloudbuild/scripts/go.mod index 49cf54361d31c..300c5bb2c445a 100644 --- a/.cloudbuild/scripts/go.mod +++ b/.cloudbuild/scripts/go.mod @@ -3,15 +3,11 @@ module github.com/gravitational/teleport/.cloudbuild/scripts go 1.17 require ( - cloud.google.com/go/secretmanager v1.2.0 cloud.google.com/go/storage v1.19.0 github.com/go-git/go-git/v5 v5.4.2 github.com/gravitational/trace v1.1.15 github.com/hashicorp/go-multierror v1.1.1 github.com/sirupsen/logrus v1.8.1 - github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b - google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c ) require ( @@ -37,18 +33,21 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect - github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/objx v0.1.1 // indirect + github.com/stretchr/testify v1.7.0 // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect go.opencensus.io v0.23.0 // indirect + golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/text v0.3.6 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/api v0.67.0 // indirect + google.golang.org/api v0.65.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/grpc v1.44.0 // indirect + google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 // indirect + google.golang.org/grpc v1.40.1 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect diff --git a/.cloudbuild/scripts/go.sum b/.cloudbuild/scripts/go.sum index d2b25fdba3cd4..a9f64ef4f6699 100644 --- a/.cloudbuild/scripts/go.sum +++ b/.cloudbuild/scripts/go.sum @@ -39,15 +39,12 @@ cloud.google.com/go/compute v0.1.0 h1:rSUBvAyVwNJ5uQCKNJFMwPtTvJkfN38b6Pvb9zZoqJ cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= cloud.google.com/go/iam v0.1.1 h1:4CapQyNFjiksks1/x7jsvsygFPhihslYk5GptIrlX68= cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/secretmanager v1.2.0 h1:VR6MzO4wjTj5jQKTPpsZhCF2PqqdAAZmN54BwJbQPhs= -cloud.google.com/go/secretmanager v1.2.0/go.mod h1:HNMYTaLrMrAN37vi2mM2vvFgjgaoCE1qvtccCIJwFRc= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -73,7 +70,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -81,11 +77,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -99,7 +91,6 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -244,11 +235,9 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -431,9 +420,8 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 h1:XDXtA5hveEEV8JB2l7nhMTp3t3cHp9ZpwcdjqyEWLlo= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -536,9 +524,8 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM= +google.golang.org/api v0.65.0 h1:MTW9c+LIBAbwoS1Gb+YV7NjFBt2f7GtAS5hIzh2NjgQ= google.golang.org/api v0.65.0/go.mod h1:ArYhxgGadlWmqO1IqVujw6Cs8IdD33bTmzKo2Sh+cbg= -google.golang.org/api v0.67.0 h1:lYaaLa+x3VVUhtosaK9xihwQ9H9KRa557REHwwZ2orM= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -610,11 +597,8 @@ google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q= google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c h1:TU4rFa5APdKTq0s6B7WTsH6Xmx0Knj86s6Biz56mErE= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -640,9 +624,8 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1 h1:pnP7OclFFFgFi4VHQDQDaoXUVauOFyktqTsqqgzFKbc= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/.cloudbuild/scripts/internal/artifacts/artifacts_test.go b/.cloudbuild/scripts/internal/artifacts/artifacts_test.go index e5336608d7f11..eba6ca9b64c83 100644 --- a/.cloudbuild/scripts/internal/artifacts/artifacts_test.go +++ b/.cloudbuild/scripts/internal/artifacts/artifacts_test.go @@ -21,6 +21,7 @@ import ( "context" "errors" "io" + "io/ioutil" "os" "path/filepath" "testing" @@ -34,7 +35,7 @@ func write(t *testing.T, data []byte, path ...string) string { t.Helper() filePath := filepath.Join(path...) require.NoError(t, os.MkdirAll(filepath.Dir(filePath), 0777)) - require.NoError(t, os.WriteFile(filePath, data, 0644)) + require.NoError(t, ioutil.WriteFile(filePath, data, 0644)) return filePath } diff --git a/.cloudbuild/scripts/internal/etcd/start.go b/.cloudbuild/scripts/internal/etcd/start.go index fe4993228e047..932d368881c0c 100644 --- a/.cloudbuild/scripts/internal/etcd/start.go +++ b/.cloudbuild/scripts/internal/etcd/start.go @@ -17,208 +17,59 @@ limitations under the License. package etcd import ( - "bytes" "context" - "crypto/tls" - "crypto/x509" - "encoding/json" - "io" - "net/http" + "net" "os" "os/exec" - "path" + "syscall" "time" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" ) -const ( - metricsURL = "https://127.0.0.1:3379" -) - -type Instance struct { - process *exec.Cmd - dataDir string - kill context.CancelFunc - stdout bytes.Buffer - stderr bytes.Buffer -} - -func (etcd *Instance) Stop() { - log.Printf("Killing etcd process") - etcd.kill() - - log.Printf("Waiting for etcd process to exit...") - switch err := etcd.process.Wait().(type) { - case nil: - break - - case *exec.ExitError: - // we expect a return code of -1 because we've just killed the process - // above. If we get something else then etcd failed earlier for some - // reason and we should print diagnostic output - if err.ExitCode() != -1 { - log.Printf("Etcd exited with unexpected status %d", err.ExitCode()) - log.Printf("stderr:\n%s", etcd.stderr.String()) - } - - default: - log.Printf("Failed: %s", err) - } - - log.Printf("Removing data dir") - if err := os.RemoveAll(etcd.dataDir); err != nil { - log.Printf("Failed removing data dir: %s", err) - } -} - -// Start starts the etcd server using the keys extpected by the -// integration and unit tests -func Start(ctx context.Context, workspace string, env ...string) (*Instance, error) { - etcdBinary, err := exec.LookPath("etcd") - if err != nil { - return nil, trace.Wrap(err, "can't find etcd binary") - } - - dataDir, err := os.MkdirTemp(os.TempDir(), "teleport-etcd-*") - if err != nil { - return nil, trace.Wrap(err, "can't create temp dir") - } - - certsDir := path.Join(workspace, "examples", "etcd", "certs") - - etcdInstance := &Instance{} - var processCtx context.Context - processCtx, etcdInstance.kill = context.WithCancel(context.Background()) - - etcdInstance.process = exec.CommandContext(processCtx, etcdBinary, - "--name", "teleportstorage", - "--data-dir", dataDir, - "--initial-cluster-state", "new", - "--cert-file", path.Join(certsDir, "server-cert.pem"), - "--key-file", path.Join(certsDir, "server-key.pem"), - "--trusted-ca-file", path.Join(certsDir, "ca-cert.pem"), - "--advertise-client-urls=https://127.0.0.1:2379", - "--listen-client-urls=https://127.0.0.1:2379", - "--listen-metrics-urls", metricsURL, - "--client-cert-auth", - ) - etcdInstance.process.Dir = workspace - etcdInstance.process.Stdout = &etcdInstance.stdout - etcdInstance.process.Stderr = &etcdInstance.stderr +// Start starts the etcd server using the Makefile `run-etcd` task and waits for it to start. +func Start(ctx context.Context, workspace string, uid, gid int, env ...string) error { + cmd := exec.CommandContext(ctx, "make", "run-etcd") + cmd.Dir = workspace + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr if len(env) > 0 { - etcdInstance.process.Env = append(os.Environ(), env...) - } - - log.Printf("Launching etcd (%s)", etcdInstance.process.Path) - if err = etcdInstance.process.Start(); err != nil { - return nil, trace.Wrap(err, "failed starting etcd") + cmd.Env = append(os.Environ(), env...) } - timeoutCtx, cancelTimeout := context.WithTimeout(ctx, 5*time.Second) - defer cancelTimeout() - - if err := waitForEtcdToStart(timeoutCtx, certsDir); err != nil { - log.Printf("Etcd failed to come up. Tidying up.") - etcdInstance.Stop() - return nil, trace.Wrap(err, "failed while waiting for etcd to start") + // make etcd run under the supplied account + cmd.SysProcAttr = &syscall.SysProcAttr{ + Credential: &syscall.Credential{ + Uid: uint32(uid), + Gid: uint32(gid), + }, } - return etcdInstance, nil -} + log.Printf("Launching etcd with %v in %q", cmd.Args, cmd.Dir) + go cmd.Run() -// waitForEtcdToStart polls an etcd server (as started with Start()) until either -// a) the etcd service reports itself as healthy, or -// b) the supplied context expires -func waitForEtcdToStart(ctx context.Context, certDir string) error { - log.Printf("Waiting for etcd to come up...") + log.Printf("Waiting for etcd to start...") ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() - client, err := newHTTPSTransport(certDir) - if err != nil { - return trace.Wrap(err, "failed to create https client") - } + timeoutCtx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() for { select { case <-ticker.C: - healthy, err := pollForHealth(ctx, client, metricsURL+"/health") - if err != nil { - return trace.Wrap(err) - } - if healthy { - log.Printf("Etcd reporting healthy") + d := net.Dialer{Timeout: 100 * time.Millisecond} + _, err := d.Dial("tcp", "127.0.0.1:2379") + if err == nil { + log.Printf("Etcd is up") return nil } - case <-ctx.Done(): + case <-timeoutCtx.Done(): return trace.Errorf("timed out waiting for etcd to start") } } } - -// newHTTPSTransport creates an HTTP client configured to use TLS with an etcd server -// as started with Start() -func newHTTPSTransport(certDir string) (*http.Client, error) { - caCertPath := path.Join(certDir, "ca-cert.pem") - caCert, err := os.ReadFile(caCertPath) - if err != nil { - return nil, trace.Wrap(err, "failed reading CA cert from %s", caCertPath) - } - - clientCert, err := tls.LoadX509KeyPair( - path.Join(certDir, "client-cert.pem"), path.Join(certDir, "client-key.pem")) - if err != nil { - return nil, trace.Wrap(err, "failed reading client cert") - } - - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig.RootCAs = x509.NewCertPool() - if !transport.TLSClientConfig.RootCAs.AppendCertsFromPEM(caCert) { - return nil, trace.Errorf("Failed adding CA cert") - } - transport.TLSClientConfig.Certificates = []tls.Certificate{clientCert} - - return &http.Client{Transport: transport}, nil -} - -// pollForHealth polls the etcd metrecs endpoint for status information, returning -// true if the service reports itself as healthy -func pollForHealth(ctx context.Context, client *http.Client, url string) (bool, error) { - type health struct { - Health string `json:"health"` - Reason string `json:"reason"` - } - - timeoutCtx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) - defer cancel() - - request, err := http.NewRequestWithContext(timeoutCtx, http.MethodGet, url, nil) - if err != nil { - return false, trace.Wrap(err, "Failed constructing poll request") - } - - // A request failure is considered "not healthy" rather than an error, as - // the etcd server may just not be up yet. - response, err := client.Do(request) - if err != nil { - return false, nil - } - defer response.Body.Close() - - body, err := io.ReadAll(response.Body) - if err != nil { - return false, trace.Wrap(err, "failed reading poll response body") - } - - status := health{} - if err = json.Unmarshal(body, &status); err != nil { - return false, trace.Wrap(err, "failed parsing poll response") - } - - return status.Health == "true", nil -} diff --git a/.cloudbuild/scripts/internal/git/configure.go b/.cloudbuild/scripts/internal/git/configure.go deleted file mode 100644 index 63d2fdb54e9b2..0000000000000 --- a/.cloudbuild/scripts/internal/git/configure.go +++ /dev/null @@ -1,145 +0,0 @@ -package git - -import ( - "context" - "fmt" - "os" - "os/exec" - - "github.com/gravitational/teleport/.cloudbuild/scripts/internal/github" - "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh" -) - -// Config represents a git repository that has been configured to use a -// deployment key, and acts as a handle to the resources so that we can -// clean them up when we're done. -type Config struct { - identity string - knownHosts string - repoDir string -} - -// Configure alters the configuration of the git repository in `repoDir` -// so that we can access it from build. If `deployKey` is non-nil the repo -// will be configured to use that. If no deploy key is supplied, the repository -// config is untouched. -func Configure(ctx context.Context, repoDir string, deployKey []byte) (cfg *Config, err error) { - var identity string - var hostsFile string - - // The deploy key we're using is too sensitive to just hope that every - // exit path will clean it up on failure, so let's just register a - // cleanup function now just in case. - defer func() { - if err != nil { - cleanup(identity, hostsFile) - } - }() - - // If we have been supplied with deployment key, then we need to use that - // to access the repository. This implies that need to use ssh to read from - // the remote, so we will have to deal with known_hosts, etc. - if deployKey != nil { - githubHostKeys, err := getGithubHostKeys(ctx) - if err != nil { - return nil, trace.Wrap(err, "failed configuring known hosts") - } - - // configure SSH known_hosts for github.com - hostsFile, err = configureKnownHosts("github.com", githubHostKeys) - if err != nil { - return nil, trace.Wrap(err, "failed configuring known hosts") - } - - // GCB clones via https, and our deploy key won't work with that, so - // force git to access the remote over ssh with a rewrite rule - log.Info("Adding remote url rewrite rule") - err = git(ctx, repoDir, "config", `url.git@github.com:.insteadOf`, "https://github.com/") - if err != nil { - return nil, trace.Wrap(err, "failed configuring url rewrite rule") - } - - // set up the identity for the deploy key - identity, err = writeKey(deployKey) - if err != nil { - return nil, trace.Wrap(err, "failed configuring SSH identity") - } - - // finally, force git to - // a) use our custom SSH setup when accessing the remote, and - // b) fail if the github host tries to present a host key other than - // one we got from the metadata service - log.Infof("Configuring git ssh command") - err = git(ctx, repoDir, "config", "core.sshCommand", - fmt.Sprintf("ssh -i %s -o StrictHostKeyChecking=yes -o UserKnownHostsFile=%s", identity, hostsFile)) - if err != nil { - return nil, trace.Wrap(err, "failed configuring git to use deploy key") - } - } - - return &Config{identity: identity, repoDir: repoDir, knownHosts: hostsFile}, nil -} - -// Do runs `git args...` in the configured repository -func (cfg *Config) Do(ctx context.Context, args ...string) error { - return git(ctx, cfg.repoDir, args...) -} - -// Close cleans up the repository, including deleting the deployment key (if any) -func (cfg *Config) Close() error { - cleanup(cfg.identity, cfg.knownHosts) - return nil -} - -func run(ctx context.Context, workingDir string, env []string, cmd string, args ...string) error { - p := exec.CommandContext(ctx, cmd, args...) - if len(env) != 0 { - p.Env = append(os.Environ(), env...) - } - p.Dir = workingDir - - cmdLogger := log.WithField("cmd", cmd) - p.Stdout = cmdLogger.WriterLevel(log.InfoLevel) - p.Stderr = cmdLogger.WriterLevel(log.ErrorLevel) - return p.Run() -} - -func git(ctx context.Context, repoDir string, args ...string) error { - return run(ctx, repoDir, nil, "/usr/bin/git", args...) -} - -func cleanup(deployKey, knownHosts string) { - if knownHosts != "" { - log.Infof("Removing known_hosts file %s", knownHosts) - if err := os.Remove(knownHosts); err != nil { - log.WithError(err).Error("Failed cleaning up known_hosts key") - } - } - - if deployKey != "" { - log.Infof("Removing deploy key file %s", deployKey) - if err := os.Remove(deployKey); err != nil { - log.WithError(err).Error("Failed cleaning up deploy key") - } - } -} - -// getGithubHostKeys fetches the github host keys from the github metadata -// service. The metadata is fetched over HTTPS, and so we have built-on -// protection against MitM attacks while fetching the expected host keys. -func getGithubHostKeys(ctx context.Context) ([]ssh.PublicKey, error) { - metadata, err := github.GetMetadata(ctx) - if err != nil { - return nil, trace.Wrap(err, "failed fetching github metadata") - } - - // extract the host keys - githubHostKeys, err := metadata.HostKeys() - if err != nil { - return nil, trace.Wrap(err, "failed fetching github hostKeys") - } - - return githubHostKeys, nil -} diff --git a/.cloudbuild/scripts/internal/git/deploykey.go b/.cloudbuild/scripts/internal/git/deploykey.go deleted file mode 100644 index fc57eb2798ef7..0000000000000 --- a/.cloudbuild/scripts/internal/git/deploykey.go +++ /dev/null @@ -1,34 +0,0 @@ -package git - -import ( - "os" - - "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" -) - -func writeKey(deployKey []byte) (string, error) { - // Note that tempfiles are automatically created with 0600, so no-one else - // should be able to read this. - keyFile, err := os.CreateTemp("", "*") - if err != nil { - return "", trace.Wrap(err, "failed creating keyfile") - } - defer keyFile.Close() - - log.Infof("Writing deploy key to %s", keyFile.Name()) - _, err = keyFile.Write(deployKey) - if err != nil { - return "", trace.Wrap(err, "failed writing deploy key") - } - - // ensure there is a trailing newline in the key, as older versions of the - // `ssh` client will barf on a key that doesn't have one, but will happily - // allow multiples - _, err = keyFile.WriteString("\n") - if err != nil { - return "", trace.Wrap(err, "failed formatting key") - } - - return keyFile.Name(), nil -} diff --git a/.cloudbuild/scripts/internal/git/knownhosts.go b/.cloudbuild/scripts/internal/git/knownhosts.go deleted file mode 100644 index 66096bb07f2d7..0000000000000 --- a/.cloudbuild/scripts/internal/git/knownhosts.go +++ /dev/null @@ -1,33 +0,0 @@ -package git - -import ( - "os" - - "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/knownhosts" -) - -func configureKnownHosts(hostname string, keys []ssh.PublicKey) (string, error) { - knownHostsFile, err := os.CreateTemp("", "*") - if err != nil { - return "", trace.Wrap(err, "failed creating known hosts file") - } - defer knownHostsFile.Close() - - log.Infof("Writing known_hosts file to %s", knownHostsFile.Name()) - - addrs := []string{hostname} - for _, k := range keys { - log.Infof("processing key %s...", k.Type()) - _, err := knownHostsFile.WriteString(knownhosts.Line(addrs, k) + "\n") - if err != nil { - knownHostsFile.Close() - os.Remove(knownHostsFile.Name()) - return "", trace.Wrap(err, "failed writing known hosts") - } - } - - return knownHostsFile.Name(), nil -} diff --git a/.cloudbuild/scripts/internal/git/unshallow.go b/.cloudbuild/scripts/internal/git/unshallow.go deleted file mode 100644 index af3387359840f..0000000000000 --- a/.cloudbuild/scripts/internal/git/unshallow.go +++ /dev/null @@ -1,25 +0,0 @@ -package git - -import ( - "context" - - "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" -) - -func UnshallowRepository(ctx context.Context, workspace string, deployKey []byte) error { - log.Info("Configuring git") - gitCfg, err := Configure(ctx, workspace, deployKey) - if err != nil { - return trace.Wrap(err, "failed configuring git") - } - defer gitCfg.Close() - - log.Info("Unshallowing repository") - err = gitCfg.Do(ctx, "fetch", "--unshallow") - if err != nil { - return trace.Wrap(err, "unshallow failed") - } - - return nil -} diff --git a/.cloudbuild/scripts/internal/github/meta.go b/.cloudbuild/scripts/internal/github/meta.go deleted file mode 100644 index a8096a013dae2..0000000000000 --- a/.cloudbuild/scripts/internal/github/meta.go +++ /dev/null @@ -1,58 +0,0 @@ -package github - -import ( - "context" - "encoding/json" - "net/http" - - "github.com/gravitational/trace" - "golang.org/x/crypto/ssh" -) - -const ( - metadataURL = "https://api.github.com/meta" -) - -// Metadata contains information about the github networking environment, -// enclosing host keys for git/ssh access -type Metadata struct { - SSHKeyFingerPrints map[string]string `json:"ssh_key_fingerprints"` - SSHKeys []string `json:"ssh_keys"` -} - -func GetMetadata(ctx context.Context) (*Metadata, error) { - request, err := http.NewRequestWithContext(ctx, http.MethodGet, metadataURL, nil) - if err != nil { - return nil, trace.Wrap(err, "failed creating metadata request") - } - - response, err := http.DefaultClient.Do(request) - if err != nil { - return nil, trace.Wrap(err, "failed issuing metadata request") - } - defer response.Body.Close() - - if response.StatusCode != http.StatusOK { - return nil, trace.Errorf("Metadata request failed %d", response.StatusCode) - } - - var meta Metadata - err = json.NewDecoder(response.Body).Decode(&meta) - if err != nil { - return nil, trace.Wrap(err, "failed parsing metadata") - } - - return &meta, nil -} - -func (meta *Metadata) HostKeys() ([]ssh.PublicKey, error) { - keys := make([]ssh.PublicKey, 0, len(meta.SSHKeys)) - for _, text := range meta.SSHKeys { - key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(text)) - if err != nil { - return nil, trace.Wrap(err, "failed parsing host key") - } - keys = append(keys, key) - } - return keys, nil -} diff --git a/.cloudbuild/scripts/internal/secrets/fetch.go b/.cloudbuild/scripts/internal/secrets/fetch.go deleted file mode 100644 index 38cbc1889d280..0000000000000 --- a/.cloudbuild/scripts/internal/secrets/fetch.go +++ /dev/null @@ -1,41 +0,0 @@ -package secrets - -import ( - "context" - - secretmanager "cloud.google.com/go/secretmanager/apiv1" - "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" - secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1" -) - -// Fetch goes and grabs a single secret from the Google Cloud secret manager -func Fetch(ctx context.Context, resourceName string) ([]byte, error) { - c, err := secretmanager.NewClient(ctx) - if err != nil { - return nil, trace.Wrap(err, "failed creating SecretManager client") - } - defer c.Close() - - log.Debugf("Fetching secret %s", resourceName) - secret, err := c.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{ - Name: resourceName, - }) - - if err != nil { - return nil, trace.Wrap(err, "failed fetching secret token") - } - - return secret.Payload.Data, nil -} - -// FetchString fetches a single secret from the Google Cloud secret manager and returns -// it as a string. -func FetchString(ctx context.Context, resourceName string) (string, error) { - data, err := Fetch(ctx, resourceName) - if err != nil { - return "", trace.Wrap(err) - } - - return string(data), nil -} diff --git a/.drone.yml b/.drone.yml index 73fd7d9337bf7..631e422378c4a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,32 +1,4 @@ --- -kind: pipeline -type: kubernetes -name: update-docs-webhook - -trigger: - event: - include: - - push - exclude: - - pull_request - branch: - include: - - master - - branch/* - repo: - include: - - gravitational/teleport - -clone: - disable: true - -steps: - - name: Trigger docs deployment - image: plugins/webhook - settings: - urls: - from_secret: DOCS_DEPLOY_HOOK ---- ################################################ # Generated using dronegen, do not edit by hand! # Use 'make dronegen' to update. @@ -815,19 +787,19 @@ steps: # increment these variables when a new major/minor version is released to bump the automatic builds # this only needs to be done on the master branch, as that's the branch that the Drone cron is configured for # build major version images which are just teleport:x - CURRENT_VERSION_ROOT: v9 - PREVIOUS_VERSION_ONE_ROOT: v8 - PREVIOUS_VERSION_TWO_ROOT: v7 + CURRENT_VERSION_ROOT: v8 + PREVIOUS_VERSION_ONE_ROOT: v7 + PREVIOUS_VERSION_TWO_ROOT: v6 commands: - apk --update --no-cache add curl go - mkdir -p /go/build && cd /go/build - # CURRENT_VERSION (9) + # CURRENT_VERSION (8) - (cd /go/build.assets/tooling && go run ./cmd/query-latest $CURRENT_VERSION_ROOT > /go/build/CURRENT_VERSION_TAG.txt) - echo "$(cat /go/build/CURRENT_VERSION_TAG.txt | cut -d. -f1 | tr -d '^v')" > /go/build/CURRENT_VERSION_TAG_GENERIC.txt - # PREVIOUS_VERSION_ONE (8) + # PREVIOUS_VERSION_ONE (7) - (cd /go/build.assets/tooling && go run ./cmd/query-latest $PREVIOUS_VERSION_ONE_ROOT > /go/build/PREVIOUS_VERSION_ONE_TAG.txt) - echo "$(cat /go/build/PREVIOUS_VERSION_ONE_TAG.txt | cut -d. -f1 | tr -d '^v')" > /go/build/PREVIOUS_VERSION_ONE_TAG_GENERIC.txt - # PREVIOUS_VERSION_TWO (7) + # PREVIOUS_VERSION_TWO (6) - (cd /go/build.assets/tooling && go run ./cmd/query-latest $PREVIOUS_VERSION_TWO_ROOT > /go/build/PREVIOUS_VERSION_TWO_TAG.txt) - echo "$(cat /go/build/PREVIOUS_VERSION_TWO_TAG.txt | cut -d. -f1 | tr -d '^v')" > /go/build/PREVIOUS_VERSION_TWO_TAG_GENERIC.txt # list versions @@ -3152,9 +3124,9 @@ steps: ARCH: amd64 BUILDBOX_PASSWORD: from_secret: BUILDBOX_PASSWORD - ENT_TARBALL_PATH: /tmp/build-darwin-amd64-pkg-tsh/go/artifacts + ENT_TARBALL_PATH: /tmp/build-darwin-amd64-pkg/go/artifacts OS: darwin - OSS_TARBALL_PATH: /tmp/build-darwin-amd64-pkg-tsh/go/artifacts + OSS_TARBALL_PATH: /tmp/build-darwin-amd64-pkg/go/artifacts WORKSPACE_DIR: /tmp/build-darwin-amd64-pkg-tsh - name: Copy Mac pkg artifacts commands: @@ -5084,6 +5056,6 @@ volumes: name: drone-s3-debrepo-pvc --- kind: signature -hmac: 8d0b79ebd4a3a0432402abf3b2773f45f8d787c0114d921dcdb5c909f2c338db +hmac: 91c43e2655c4225898f8ba6fc034a0b5ee3de067d2f58979a474fcba342db799 ... diff --git a/.github/ISSUE_TEMPLATE/testplan.md b/.github/ISSUE_TEMPLATE/testplan.md index 633930f8ecc65..e394322075ce1 100644 --- a/.github/ISSUE_TEMPLATE/testplan.md +++ b/.github/ISSUE_TEMPLATE/testplan.md @@ -139,11 +139,6 @@ With every user combination, try to login and signup with invalid second factor, - [ ] ssh -L \ - [ ] ssh -L \ -- [ ] Verify proxy jump functionality - Log into leaf cluster via root, shut down the root proxy and verify proxy jump works. - - [ ] tsh ssh -J \ - - [ ] ssh -J \ - - [ ] Interact with a cluster using the Web UI - [ ] Connect to a Teleport node - [ ] Connect to a OpenSSH node @@ -281,15 +276,6 @@ tsh --proxy=proxy.example.com --user= --insecure ssh --cluster=foo.com - [ ] Test receiving a message via Teleport Slackbot - [ ] Test receiving a new Jira Ticket via Teleport Jira -### AWS Node Joining -[Docs](https://goteleport.com/docs/setup/guides/joining-nodes-aws/) -- [ ] On EC2 instance with `ec2:DescribeInstances` permissions for local account: - `TELEPORT_TEST_EC2=1 go test ./integration -run TestEC2NodeJoin` -- [ ] On EC2 instance with any attached role: - `TELEPORT_TEST_EC2=1 go test ./integration -run TestIAMNodeJoin` -- [ ] EC2 Join method in IoT mode with node and auth in different AWS accounts -- [ ] IAM Join method in IoT mode with node and auth in different AWS accounts - ## WEB UI ## Main @@ -952,10 +938,6 @@ and non interactive tsh bench loads. - [ ] Verify async recording (`mode: node` or `mode: proxy`) - [ ] Sessions show up in session recordings UI with desktop icon - [ ] Sessions can be played back, including play/pause functionality - - [ ] A session that ends with a TDP error message can be played back, ends by displaying the error message, - and the progress bar progresses to the end. - - [ ] Attempting to play back a session that doesn't exist (i.e. by entering a non-existing session id in the url) shows - a relevant error message. - [ ] RBAC for sessions: ensure users can only see their own recordings when using the RBAC rule from our [docs](../../docs/pages/access-controls/reference.mdx#rbac-for-sessions) @@ -979,4 +961,4 @@ and non interactive tsh bench loads. - [ ] Debian 9 - Verify tsh runs on: - [ ] Windows 10 - - [ ] MacOS + - [ ] MacOS \ No newline at end of file diff --git a/.github/workflows/robot/internal/bot/assign.go b/.github/workflows/robot/internal/bot/assign.go index 35fe61ac3a243..e7743d822b569 100644 --- a/.github/workflows/robot/internal/bot/assign.go +++ b/.github/workflows/robot/internal/bot/assign.go @@ -19,9 +19,6 @@ package bot import ( "context" "log" - "regexp" - "strconv" - "strings" "github.com/gravitational/trace" ) @@ -53,128 +50,10 @@ func (b *Bot) Assign(ctx context.Context) error { } func (b *Bot) getReviewers(ctx context.Context) ([]string, error) { - // If a backport PR was found, assign original reviewers. Otherwise fall - // through to normal assignment logic. - if isBackport(b.c.Environment.UnsafeBase) { - reviewers, err := b.backportReviewers(ctx) - if err == nil { - return reviewers, nil - } - log.Printf("Assign: Found backport PR, but failed to find original reviewers: %v. Falling through to normal assignment logic.", err) - } - docs, code, err := b.parseChanges(ctx) if err != nil { return nil, trace.Wrap(err) } - return b.c.Review.Get(b.c.Environment.Author, docs, code), nil -} -func (b *Bot) backportReviewers(ctx context.Context) ([]string, error) { - // Search inside the PR to find a reference to the original PR. - original, err := b.findOriginal(ctx, - b.c.Environment.Organization, - b.c.Environment.Repository, - b.c.Environment.Number) - if err != nil { - return nil, trace.Wrap(err) - } - - var originalReviewers []string - - // Append list of reviewers that have yet to submit a review. - reviewers, err := b.c.GitHub.ListReviewers(ctx, - b.c.Environment.Organization, - b.c.Environment.Repository, - original) - if err != nil { - return nil, trace.Wrap(err) - } - originalReviewers = append(originalReviewers, reviewers...) - - // Append list of reviews that have submitted a review. - reviews, err := b.c.GitHub.ListReviews(ctx, - b.c.Environment.Organization, - b.c.Environment.Repository, - original) - if err != nil { - return nil, trace.Wrap(err) - } - for _, review := range reviews { - originalReviewers = append(originalReviewers, review.Author) - } - - return dedup(b.c.Environment.Author, originalReviewers), nil -} - -func (b *Bot) findOriginal(ctx context.Context, organization string, repository string, number int) (int, error) { - pull, err := b.c.GitHub.GetPullRequest(ctx, - organization, - repository, - number) - if err != nil { - return 0, trace.Wrap(err) - } - - var original string - - // Search inside the title and body for the original PR number. - title := pattern.FindStringSubmatch(pull.UnsafeTitle) - body := pattern.FindStringSubmatch(pull.UnsafeBody) - switch { - case len(title) == 0 && len(body) == 0: - return 0, trace.NotFound("no PR referenced in title or body") - case len(title) == 0 && len(body) == 2: - original = body[1] - case len(title) == 2 && len(body) == 0: - original = title[1] - case len(title) == 2 && len(body) == 2: - if title[1] != body[1] { - return 0, trace.NotFound("different PRs referenced in title and body") - } - original = title[1] - default: - return 0, trace.NotFound("failed to find reference to PR") - } - - n, err := strconv.Atoi(original) - if err != nil { - return 0, trace.Wrap(err) - } - - // Verify the number found is a Pull Request and not an Issue. - _, err = b.c.GitHub.GetPullRequest(ctx, - organization, - repository, - n) - if err != nil { - return 0, trace.NotFound("found Issue %v, not Pull Request", original) - } - - log.Printf("Assign: Found original PR #%v.", original) - return n, nil -} - -func dedup(author string, reviewers []string) []string { - m := map[string]bool{} - - for _, reviewer := range reviewers { - if reviewer == author { - continue - } - m[reviewer] = true - } - - var filtered []string - for k := range m { - filtered = append(filtered, k) - } - - return filtered -} - -func isBackport(unsafeBase string) bool { - return strings.HasPrefix(unsafeBase, "branch/v") + return b.c.Review.Get(b.c.Environment.Author, docs, code), nil } - -var pattern = regexp.MustCompile(`(?:https:\/\/github\.com\/gravitational\/teleport\/pull\/|#)([0-9]+)`) diff --git a/.github/workflows/robot/internal/bot/assign_test.go b/.github/workflows/robot/internal/bot/assign_test.go deleted file mode 100644 index b065a6023de36..0000000000000 --- a/.github/workflows/robot/internal/bot/assign_test.go +++ /dev/null @@ -1,146 +0,0 @@ -/* -Copyright 2022 Gravitational, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package bot - -import ( - "context" - "testing" - - "github.com/gravitational/teleport/.github/workflows/robot/internal/env" - "github.com/gravitational/teleport/.github/workflows/robot/internal/github" - "github.com/gravitational/teleport/.github/workflows/robot/internal/review" - - "github.com/stretchr/testify/require" -) - -// TestBackportReviewers checks if backport reviewers are correctly assigned. -func TestBackportReviewers(t *testing.T) { - r, err := review.New(&review.Config{ - CodeReviewers: map[string]review.Reviewer{}, - CodeReviewersOmit: map[string]bool{}, - DocsReviewers: map[string]review.Reviewer{}, - DocsReviewersOmit: map[string]bool{}, - Admins: []string{}, - }) - require.NoError(t, err) - - tests := []struct { - desc string - pull github.PullRequest - reviewers []string - reviews []github.Review - err bool - expected []string - }{ - { - desc: "backport-original-pr-number-approved", - pull: github.PullRequest{ - Author: "baz", - Repository: "bar", - UnsafeHead: "baz/fix", - UnsafeTitle: "Backport #0 to branch/v8", - UnsafeBody: "", - Fork: false, - }, - reviewers: []string{"3"}, - reviews: []github.Review{ - {Author: "4", State: "APPROVED"}, - }, - err: false, - expected: []string{"3", "4"}, - }, - { - desc: "backport-original-url-approved", - pull: github.PullRequest{ - Author: "baz", - Repository: "bar", - UnsafeHead: "baz/fix", - UnsafeTitle: "Fixed an issue", - UnsafeBody: "https://github.com/gravitational/teleport/pull/0", - Fork: false, - }, - reviewers: []string{"3"}, - reviews: []github.Review{ - {Author: "4", State: "APPROVED"}, - }, - err: false, - expected: []string{"3", "4"}, - }, - { - desc: "backport-multiple-reviews", - pull: github.PullRequest{ - Author: "baz", - Repository: "bar", - UnsafeHead: "baz/fix", - UnsafeTitle: "Fixed feature", - UnsafeBody: "", - Fork: false, - }, - reviewers: []string{"3"}, - reviews: []github.Review{ - {Author: "4", State: "COMMENTED"}, - {Author: "4", State: "CHANGES_REQUESTED"}, - {Author: "4", State: "APPROVED"}, - {Author: "9", State: "APPROVED"}, - }, - err: true, - expected: []string{}, - }, - { - desc: "backport-original-not-found", - pull: github.PullRequest{ - Author: "baz", - Repository: "bar", - UnsafeHead: "baz/fix", - UnsafeTitle: "Fixed feature", - UnsafeBody: "", - Fork: false, - }, - reviewers: []string{"3"}, - reviews: []github.Review{ - {Author: "4", State: "APPROVED"}, - }, - err: true, - expected: []string{}, - }, - } - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - b := &Bot{ - c: &Config{ - Environment: &env.Environment{ - Organization: "foo", - Author: "9", - Repository: "bar", - Number: 0, - UnsafeBase: "branch/v8", - UnsafeHead: "fix", - }, - Review: r, - GitHub: &fakeGithub{ - pull: test.pull, - reviewers: test.reviewers, - reviews: test.reviews, - }, - }, - } - reviewers, err := b.backportReviewers(context.Background()) - require.Equal(t, err != nil, test.err) - require.ElementsMatch(t, reviewers, test.expected) - }) - } -} diff --git a/.github/workflows/robot/internal/bot/bot.go b/.github/workflows/robot/internal/bot/bot.go index 5a979acdb60e7..e892be1fc1ae6 100644 --- a/.github/workflows/robot/internal/bot/bot.go +++ b/.github/workflows/robot/internal/bot/bot.go @@ -29,22 +29,16 @@ import ( // Client implements the GitHub API. type Client interface { - // RequestReviewers is used to assign reviewers to a Pull Request. + // RequestReviewers is used to assign reviewers to a PR. RequestReviewers(ctx context.Context, organization string, repository string, number int, reviewers []string) error // ListReviews is used to list all submitted reviews for a PR. - ListReviews(ctx context.Context, organization string, repository string, number int) ([]github.Review, error) - - // ListReviewers returns a list of reviewers that have yet to submit a review. - ListReviewers(ctx context.Context, organization string, repository string, number int) ([]string, error) - - // GetPullRequest returns a specific Pull Request. - GetPullRequest(ctx context.Context, organization string, repository string, number int) (github.PullRequest, error) + ListReviews(ctx context.Context, organization string, repository string, number int) (map[string]*github.Review, error) // ListPullRequests returns a list of Pull Requests. ListPullRequests(ctx context.Context, organization string, repository string, state string) ([]github.PullRequest, error) - // ListFiles is used to list all the files within a Pull Request. + // ListFiles is used to list all the files within a PR. ListFiles(ctx context.Context, organization string, repository string, number int) ([]string, error) // AddLabels will add labels to an Issue or Pull Request. diff --git a/.github/workflows/robot/internal/bot/bot_test.go b/.github/workflows/robot/internal/bot/bot_test.go index 5ff4744385c9a..72ac663acf7b5 100644 --- a/.github/workflows/robot/internal/bot/bot_test.go +++ b/.github/workflows/robot/internal/bot/bot_test.go @@ -78,7 +78,7 @@ func TestParseChanges(t *testing.T) { Number: 0, }, GitHub: &fakeGithub{ - files: test.files, + test.files, }, }, } @@ -91,26 +91,15 @@ func TestParseChanges(t *testing.T) { } type fakeGithub struct { - files []string - pull github.PullRequest - reviewers []string - reviews []github.Review + files []string } func (f *fakeGithub) RequestReviewers(ctx context.Context, organization string, repository string, number int, reviewers []string) error { return nil } -func (f *fakeGithub) ListReviews(ctx context.Context, organization string, repository string, number int) ([]github.Review, error) { - return f.reviews, nil -} - -func (f *fakeGithub) ListReviewers(ctx context.Context, organization string, repository string, number int) ([]string, error) { - return f.reviewers, nil -} - -func (f *fakeGithub) GetPullRequest(ctx context.Context, organization string, repository string, number int) (github.PullRequest, error) { - return f.pull, nil +func (f *fakeGithub) ListReviews(ctx context.Context, organization string, repository string, number int) (map[string]*github.Review, error) { + return nil, nil } func (f *fakeGithub) ListPullRequests(ctx context.Context, organization string, repository string, state string) ([]github.PullRequest, error) { diff --git a/.github/workflows/robot/internal/bot/check.go b/.github/workflows/robot/internal/bot/check.go index 34fc5daf4341e..d62c4d1e3a925 100644 --- a/.github/workflows/robot/internal/bot/check.go +++ b/.github/workflows/robot/internal/bot/check.go @@ -40,7 +40,7 @@ func (b *Bot) Check(ctx context.Context) error { err := b.dismiss(ctx, b.c.Environment.Organization, b.c.Environment.Repository, - b.c.Environment.UnsafeHead) + b.c.Environment.UnsafeBranch) if err != nil { return trace.Wrap(err) } diff --git a/.github/workflows/robot/internal/bot/label.go b/.github/workflows/robot/internal/bot/label.go index 5bb5154a6b67a..843fe6fa37ee8 100644 --- a/.github/workflows/robot/internal/bot/label.go +++ b/.github/workflows/robot/internal/bot/label.go @@ -51,7 +51,7 @@ func (b *Bot) labels(ctx context.Context) ([]string, error) { var labels []string // The branch name is unsafe, but here we are simply adding a label. - if strings.HasPrefix(b.c.Environment.UnsafeHead, "branch/") { + if strings.HasPrefix(b.c.Environment.UnsafeBranch, "branch/") { log.Println("Label: Found backport branch.") labels = append(labels, "backport") } diff --git a/.github/workflows/robot/internal/bot/label_test.go b/.github/workflows/robot/internal/bot/label_test.go index 3e1b1f71c343b..c68ff90c6d80f 100644 --- a/.github/workflows/robot/internal/bot/label_test.go +++ b/.github/workflows/robot/internal/bot/label_test.go @@ -84,10 +84,10 @@ func TestLabel(t *testing.T) { Organization: "foo", Repository: "bar", Number: 0, - UnsafeHead: test.branch, + UnsafeBranch: test.branch, }, GitHub: &fakeGithub{ - files: test.files, + test.files, }, }, } diff --git a/.github/workflows/robot/internal/env/env.go b/.github/workflows/robot/internal/env/env.go index 73fb61b844ec3..ebf989a2fffd7 100644 --- a/.github/workflows/robot/internal/env/env.go +++ b/.github/workflows/robot/internal/env/env.go @@ -38,26 +38,13 @@ type Environment struct { // Author is the author of the PR. Author string - // UnsafeHead is the name of the branch the workflow is running in. + // UnsafeBranch is the name of the branch the workflow is running in. // - // UnsafeHead can be attacker controlled and should not be used in any - // security sensitive context. For example, don't use it when crafting a URL - // to send a request to or an access decision. See the following link for - // more details: + // UnsafeBranch can be attacker controlled and should not be used in any + // security sensitive context. See the following link for more details: // // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections - UnsafeHead string - - // UnsafeBase is the name of the base branch the user is trying to merge the - // PR into. For example: "master" or "branch/v8". - // - // UnsafeBase can be attacker controlled and should not be used in any - // security sensitive context. For example, don't use it when crafting a URL - // to send a request to or an access decision. See the following link for - // more details: - // - // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections - UnsafeBase string + UnsafeBranch string } // New returns a new execution environment for the workflow. @@ -85,8 +72,7 @@ func New() (*Environment, error) { Repository: event.Repository.Name, Number: event.PullRequest.Number, Author: event.PullRequest.User.Login, - UnsafeHead: event.PullRequest.UnsafeHead.UnsafeRef, - UnsafeBase: event.PullRequest.UnsafeBase.UnsafeRef, + UnsafeBranch: event.PullRequest.UnsafeHead.UnsafeRef, }, nil } diff --git a/.github/workflows/robot/internal/env/env_test.go b/.github/workflows/robot/internal/env/env_test.go index 5ec2972db0120..205b7a3026447 100644 --- a/.github/workflows/robot/internal/env/env_test.go +++ b/.github/workflows/robot/internal/env/env_test.go @@ -95,7 +95,7 @@ func TestEnvironment(t *testing.T) { require.Equal(t, environment.Repository, test.repository) require.Equal(t, environment.Number, test.number) require.Equal(t, environment.Author, test.author) - require.Equal(t, environment.UnsafeHead, test.unsafeBranch) + require.Equal(t, environment.UnsafeBranch, test.unsafeBranch) } }) } diff --git a/.github/workflows/robot/internal/env/structs.go b/.github/workflows/robot/internal/env/structs.go index d34de2e46f27f..ac82d282c8d41 100644 --- a/.github/workflows/robot/internal/env/structs.go +++ b/.github/workflows/robot/internal/env/structs.go @@ -39,39 +39,25 @@ type PullRequest struct { Number int `json:"number"` // UnsafeHead can be attacker controlled and should not be used in any - // security sensitive context. For example, don't use it when crafting a URL - // to send a request to or an access decision. See the following link for - // more details: + // security sensitive context. See the following link for more details: // // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections - UnsafeHead Branch `json:"head"` - - // UnsafeHead can be attacker controlled and should not be used in any - // security sensitive context. For example, don't use it when crafting a URL - // to send a request to or an access decision. See the following link for - // more details: - // - // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections - UnsafeBase Branch `json:"base"` + UnsafeHead Head `json:"head"` } type User struct { Login string `json:"login"` } -type Branch struct { +type Head struct { // UnsafeSHA can be attacker controlled and should not be used in any - // security sensitive context. For example, don't use it when crafting a URL - // to send a request to or an access decision. See the following link for - // more details: + // security sensitive context. See the following link for more details: // // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections UnsafeSHA string `json:"sha"` // UnsafeRef can be attacker controlled and should not be used in any - // security sensitive context. For example, don't use it when crafting a URL - // to send a request to or an access decision. See the following link for - // more details: + // security sensitive context. See the following link for more details: // // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections UnsafeRef string `json:"ref"` diff --git a/.github/workflows/robot/internal/github/github.go b/.github/workflows/robot/internal/github/github.go index e25d7dbf6ff29..a52861b3e1972 100644 --- a/.github/workflows/robot/internal/github/github.go +++ b/.github/workflows/robot/internal/github/github.go @@ -22,7 +22,6 @@ import ( "net/http" "net/url" "path" - "sort" "strconv" "time" @@ -46,7 +45,6 @@ func New(ctx context.Context, token string) (*Client, error) { }, nil } -// RequestReviewers is used to assign reviewers to a Pull Requests. func (c *Client) RequestReviewers(ctx context.Context, organization string, repository string, number int, reviewers []string) error { _, _, err := c.client.PullRequests.RequestReviewers(ctx, organization, @@ -65,15 +63,14 @@ func (c *Client) RequestReviewers(ctx context.Context, organization string, repo type Review struct { // Author is the GitHub login of the user that created the PR. Author string - // State is the state of the PR, for example APPROVED, COMMENTED, - // CHANGES_REQUESTED, or DISMISSED. + // State is the state of the PR, for example APPROVED or CHANGES_REQUESTED. State string // SubmittedAt is the time the PR was created. SubmittedAt time.Time } -func (c *Client) ListReviews(ctx context.Context, organization string, repository string, number int) ([]Review, error) { - var reviews []Review +func (c *Client) ListReviews(ctx context.Context, organization string, repository string, number int) (map[string]*Review, error) { + reviews := map[string]*Review{} opt := &go_github.ListOptions{ Page: 0, @@ -90,11 +87,20 @@ func (c *Client) ListReviews(ctx context.Context, organization string, repositor } for _, r := range page { - reviews = append(reviews, Review{ + // Always pick up the last submitted review from each reviewer. + review, ok := reviews[r.GetUser().GetLogin()] + if ok { + if r.GetSubmittedAt().After(review.SubmittedAt) { + review.State = r.GetState() + review.SubmittedAt = r.GetSubmittedAt() + } + } + + reviews[r.GetUser().GetLogin()] = &Review{ Author: r.GetUser().GetLogin(), State: r.GetState(), SubmittedAt: r.GetSubmittedAt(), - }) + } } if resp.NextPage == 0 { @@ -103,11 +109,6 @@ func (c *Client) ListReviews(ctx context.Context, organization string, repositor opt.Page = resp.NextPage } - // Sort oldest review first. - sort.SliceStable(reviews, func(i, j int) bool { - return reviews[i].SubmittedAt.Before(reviews[j].SubmittedAt) - }) - return reviews, nil } @@ -117,95 +118,13 @@ type PullRequest struct { Author string // Repository is the name of the repository. Repository string - // Number is the Pull Request number. - Number int - // State is the state of the submitted review. - State string - // UnsafeHead is the name head of the branch. - // - // UnsafeHead can be attacker controlled and should not be used in any - // security sensitive context. For example, don't use it when crafting a URL - // to send a request to or an access decision. See the following link for - // more details: - // - // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections + // UnsafeHead is the name of the branch this PR is created from. It is marked + // unsafe as it can be attacker controlled. UnsafeHead string - // UnsafeTitle is the title of the Pull Request. - // - // UnsafeTitle can be attacker controlled and should not be used in any - // security sensitive context. For example, don't use it when crafting a URL - // to send a request to or an access decision. See the following link for - // more details: - // - // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections - UnsafeTitle string - // UnsafeBody is the body of the Pull Request. - // - // UnsafeBody can be attacker controlled and should not be used in any - // security sensitive context. For example, don't use it when crafting a URL - // to send a request to or an access decision. See the following link for - // more details: - // - // https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections - UnsafeBody string // Fork determines if the pull request is from a fork. Fork bool } -// ListReviewers returns a list of reviewers that have yet to submit a review. -func (c *Client) ListReviewers(ctx context.Context, organization string, repository string, number int) ([]string, error) { - var reviewers []string - - opt := &go_github.ListOptions{ - Page: 0, - PerPage: perPage, - } - for { - page, resp, err := c.client.PullRequests.ListReviewers(ctx, - organization, - repository, - number, - opt) - if err != nil { - return nil, trace.Wrap(err) - } - - for _, r := range page.Users { - reviewers = append(reviewers, r.GetLogin()) - } - - if resp.NextPage == 0 { - break - } - opt.Page = resp.NextPage - } - - return reviewers, nil -} - -// GetPullRequest returns a specific Pull Request. -func (c *Client) GetPullRequest(ctx context.Context, organization string, repository string, number int) (PullRequest, error) { - pull, _, err := c.client.PullRequests.Get(ctx, - organization, - repository, - number) - if err != nil { - return PullRequest{}, trace.Wrap(err) - } - - return PullRequest{ - Author: pull.GetUser().GetLogin(), - Repository: repository, - Number: pull.GetNumber(), - State: pull.GetState(), - UnsafeHead: pull.GetHead().GetRef(), - UnsafeTitle: pull.GetTitle(), - UnsafeBody: pull.GetBody(), - Fork: pull.GetHead().GetRepo().GetFork(), - }, nil -} - -// ListPullRequests returns a list of Pull Requests. func (c *Client) ListPullRequests(ctx context.Context, organization string, repository string, state string) ([]PullRequest, error) { var pulls []PullRequest @@ -225,16 +144,12 @@ func (c *Client) ListPullRequests(ctx context.Context, organization string, repo return nil, trace.Wrap(err) } - for _, pull := range page { + for _, pr := range page { pulls = append(pulls, PullRequest{ - Author: pull.GetUser().GetLogin(), - Repository: repository, - Number: pull.GetNumber(), - State: pull.GetState(), - UnsafeHead: pull.GetHead().GetRef(), - UnsafeTitle: pull.GetTitle(), - UnsafeBody: pull.GetBody(), - Fork: pull.GetHead().GetRepo().GetFork(), + Author: pr.GetUser().GetLogin(), + Repository: repository, + UnsafeHead: pr.GetHead().GetRef(), + Fork: pr.GetHead().GetRepo().GetFork(), }) } if resp.NextPage == 0 { @@ -246,7 +161,6 @@ func (c *Client) ListPullRequests(ctx context.Context, organization string, repo return pulls, nil } -// ListFiles is used to list all the files within a Pull Request. func (c *Client) ListFiles(ctx context.Context, organization string, repository string, number int) ([]string, error) { var files []string @@ -301,7 +215,6 @@ type Workflow struct { Path string } -// ListWorkflows lists all workflows within a repository. func (c *Client) ListWorkflows(ctx context.Context, organization string, repository string) ([]Workflow, error) { var workflows []Workflow @@ -347,7 +260,6 @@ type Run struct { CreatedAt time.Time } -// ListWorkflowRuns is used to list all workflow runs for an ID. func (c *Client) ListWorkflowRuns(ctx context.Context, organization string, repository string, branch string, workflowID int64) ([]Run, error) { var runs []Run diff --git a/.github/workflows/robot/internal/review/review.go b/.github/workflows/robot/internal/review/review.go index 0d1072c2c5d08..2bf803c7ed531 100644 --- a/.github/workflows/robot/internal/review/review.go +++ b/.github/workflows/robot/internal/review/review.go @@ -195,7 +195,7 @@ func (r *Assignments) getCodeReviewerSets(author string) ([]string, []string) { } // CheckExternal requires two admins have approved. -func (r *Assignments) CheckExternal(author string, reviews []github.Review) error { +func (r *Assignments) CheckExternal(author string, reviews map[string]*github.Review) error { log.Printf("Check: Found external author %v.", author) reviewers := r.getAdminReviewers(author) @@ -209,7 +209,7 @@ func (r *Assignments) CheckExternal(author string, reviews []github.Review) erro // CheckInternal will verify if required reviewers have approved. Checks if // docs and if each set of code reviews have approved. Admin approvals bypass // all checks. -func (r *Assignments) CheckInternal(author string, reviews []github.Review, docs bool, code bool) error { +func (r *Assignments) CheckInternal(author string, reviews map[string]*github.Review, docs bool, code bool) error { log.Printf("Check: Found internal author %v.", author) // Skip checks if admins have approved. @@ -247,7 +247,7 @@ func (r *Assignments) CheckInternal(author string, reviews []github.Review, docs return nil } -func (r *Assignments) checkDocsReviews(author string, reviews []github.Review) error { +func (r *Assignments) checkDocsReviews(author string, reviews map[string]*github.Review) error { reviewers := r.getDocsReviewers(author) if check(reviewers, reviews) { @@ -257,7 +257,7 @@ func (r *Assignments) checkDocsReviews(author string, reviews []github.Review) e return trace.BadParameter("requires at least one approval from %v", reviewers) } -func (r *Assignments) checkCodeReviews(author string, reviews []github.Review) error { +func (r *Assignments) checkCodeReviews(author string, reviews map[string]*github.Review) error { // External code reviews should never hit this path, if they do, fail and // return an error. v, ok := r.c.CodeReviewers[author] @@ -314,42 +314,23 @@ func getReviewerSets(author string, team string, reviewers map[string]Reviewer, return setA, setB } -func check(reviewers []string, reviews []github.Review) bool { +func check(reviewers []string, reviews map[string]*github.Review) bool { return checkN(reviewers, reviews) > 0 } -func checkN(reviewers []string, reviews []github.Review) int { - r := reviewsByAuthor(reviews) - +func checkN(reviewers []string, reviews map[string]*github.Review) int { var n int for _, reviewer := range reviewers { - if state, ok := r[reviewer]; ok && state == approved { - n++ - } - } - return n -} - -func reviewsByAuthor(reviews []github.Review) map[string]string { - m := map[string]string{} - - for _, review := range reviews { - // Always pick up the last submitted review from each reviewer. - if state, ok := m[review.Author]; ok { - // If the reviewer left comments after approval, skip this review. - if review.State == commented && state == approved { - continue + if review, ok := reviews[reviewer]; ok { + if review.State == approved && review.Author == reviewer { + n++ } } - m[review.Author] = review.State } - - return m + return n } const ( - // commented is a code review where the reviewer has left comments only. - commented = "COMMENTED" // approved is a code review where the reviewer has approved changes. approved = "APPROVED" // changesRequested is a code review where the reviewer has requested changes. diff --git a/.github/workflows/robot/internal/review/review_test.go b/.github/workflows/robot/internal/review/review_test.go index 3412e1d374f7a..d8df894053445 100644 --- a/.github/workflows/robot/internal/review/review_test.go +++ b/.github/workflows/robot/internal/review/review_test.go @@ -37,16 +37,16 @@ func TestIsInternal(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, - "3": {Team: "Core", Owner: false}, - "4": {Team: "Core", Owner: false}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, + "3": Reviewer{Team: "Core", Owner: false}, + "4": Reviewer{Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{}, // Docs. DocsReviewers: map[string]Reviewer{ - "5": {Team: "Core", Owner: true}, - "6": {Team: "Core", Owner: true}, + "5": Reviewer{Team: "Core", Owner: true}, + "6": Reviewer{Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, // Admins. @@ -65,16 +65,16 @@ func TestIsInternal(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, - "3": {Team: "Core", Owner: false}, - "4": {Team: "Core", Owner: false}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, + "3": Reviewer{Team: "Core", Owner: false}, + "4": Reviewer{Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{}, // Docs. DocsReviewers: map[string]Reviewer{ - "5": {Team: "Core", Owner: true}, - "6": {Team: "Core", Owner: true}, + "5": Reviewer{Team: "Core", Owner: true}, + "6": Reviewer{Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, // Admins. @@ -93,16 +93,16 @@ func TestIsInternal(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, - "3": {Team: "Core", Owner: false}, - "4": {Team: "Core", Owner: false}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, + "3": Reviewer{Team: "Core", Owner: false}, + "4": Reviewer{Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{}, // Docs. DocsReviewers: map[string]Reviewer{ - "5": {Team: "Core", Owner: true}, - "6": {Team: "Core", Owner: true}, + "5": Reviewer{Team: "Core", Owner: true}, + "6": Reviewer{Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, // Admins. @@ -139,10 +139,10 @@ func TestGetCodeReviewers(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, - "3": {Team: "Core", Owner: false}, - "4": {Team: "Core", Owner: false}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, + "3": Reviewer{Team: "Core", Owner: false}, + "4": Reviewer{Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{}, // Admins. @@ -162,11 +162,11 @@ func TestGetCodeReviewers(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, - "3": {Team: "Core", Owner: false}, - "4": {Team: "Core", Owner: false}, - "5": {Team: "Core", Owner: false}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, + "3": Reviewer{Team: "Core", Owner: false}, + "4": Reviewer{Team: "Core", Owner: false}, + "5": Reviewer{Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{ "3": true, @@ -188,11 +188,11 @@ func TestGetCodeReviewers(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, - "3": {Team: "Core", Owner: false}, - "4": {Team: "Core", Owner: false}, - "5": {Team: "Internal"}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, + "3": Reviewer{Team: "Core", Owner: false}, + "4": Reviewer{Team: "Core", Owner: false}, + "5": Reviewer{Team: "Internal"}, }, CodeReviewersOmit: map[string]bool{}, // Admins. @@ -212,15 +212,15 @@ func TestGetCodeReviewers(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, - "3": {Team: "Core", Owner: true}, - "4": {Team: "Core", Owner: false}, - "5": {Team: "Core", Owner: false}, - "6": {Team: "Core", Owner: false}, - "7": {Team: "Internal", Owner: false}, - "8": {Team: "Cloud", Owner: false}, - "9": {Team: "Cloud", Owner: false}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, + "3": Reviewer{Team: "Core", Owner: true}, + "4": Reviewer{Team: "Core", Owner: false}, + "5": Reviewer{Team: "Core", Owner: false}, + "6": Reviewer{Team: "Core", Owner: false}, + "7": Reviewer{Team: "Internal", Owner: false}, + "8": Reviewer{Team: "Cloud", Owner: false}, + "9": Reviewer{Team: "Cloud", Owner: false}, }, CodeReviewersOmit: map[string]bool{ "6": true, @@ -245,13 +245,13 @@ func TestGetCodeReviewers(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, - "3": {Team: "Core", Owner: true}, - "4": {Team: "Core", Owner: false}, - "5": {Team: "Core", Owner: false}, - "6": {Team: "Core", Owner: false}, - "7": {Team: "Internal", Owner: false}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, + "3": Reviewer{Team: "Core", Owner: true}, + "4": Reviewer{Team: "Core", Owner: false}, + "5": Reviewer{Team: "Core", Owner: false}, + "6": Reviewer{Team: "Core", Owner: false}, + "7": Reviewer{Team: "Internal", Owner: false}, }, CodeReviewersOmit: map[string]bool{ "6": true, @@ -294,8 +294,8 @@ func TestGetDocsReviewers(t *testing.T) { c: &Config{ // Docs. DocsReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, // Admins. @@ -314,8 +314,8 @@ func TestGetDocsReviewers(t *testing.T) { c: &Config{ // Docs. DocsReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{ "2": true, @@ -336,8 +336,8 @@ func TestGetDocsReviewers(t *testing.T) { c: &Config{ // Docs. DocsReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, // Admins. @@ -365,12 +365,12 @@ func TestCheckExternal(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, - "3": {Team: "Core", Owner: true}, - "4": {Team: "Core", Owner: false}, - "5": {Team: "Core", Owner: false}, - "6": {Team: "Core", Owner: false}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, + "3": Reviewer{Team: "Core", Owner: true}, + "4": Reviewer{Team: "Core", Owner: false}, + "5": Reviewer{Team: "Core", Owner: false}, + "6": Reviewer{Team: "Core", Owner: false}, }, CodeReviewersOmit: map[string]bool{}, // Default. @@ -383,48 +383,72 @@ func TestCheckExternal(t *testing.T) { tests := []struct { desc string author string - reviews []github.Review + reviews map[string]*github.Review result bool }{ { desc: "no-reviews-fail", author: "5", - reviews: []github.Review{}, + reviews: map[string]*github.Review{}, result: false, }, { desc: "two-non-admin-reviews-fail", author: "5", - reviews: []github.Review{ - {Author: "3", State: approved}, - {Author: "4", State: approved}, + reviews: map[string]*github.Review{ + "3": &github.Review{ + Author: "3", + State: approved, + }, + "4": &github.Review{ + Author: "4", + State: approved, + }, }, result: false, }, { desc: "one-admin-reviews-fail", author: "5", - reviews: []github.Review{ - {Author: "1", State: approved}, - {Author: "4", State: approved}, + reviews: map[string]*github.Review{ + "1": &github.Review{ + Author: "1", + State: approved, + }, + "4": &github.Review{ + Author: "4", + State: approved, + }, }, result: false, }, { desc: "two-admin-reviews-one-denied-success", author: "5", - reviews: []github.Review{ - {Author: "1", State: changesRequested}, - {Author: "2", State: approved}, + reviews: map[string]*github.Review{ + "1": &github.Review{ + Author: "1", + State: changesRequested, + }, + "2": &github.Review{ + Author: "2", + State: approved, + }, }, result: false, }, { desc: "two-admin-reviews-success", author: "5", - reviews: []github.Review{ - {Author: "1", State: approved}, - {Author: "2", State: approved}, + reviews: map[string]*github.Review{ + "1": &github.Review{ + Author: "1", + State: approved, + }, + "2": &github.Review{ + Author: "2", + State: approved, + }, }, result: true, }, @@ -447,21 +471,21 @@ func TestCheckInternal(t *testing.T) { c: &Config{ // Code. CodeReviewers: map[string]Reviewer{ - "1": {Team: "Core", Owner: true}, - "2": {Team: "Core", Owner: true}, - "3": {Team: "Core", Owner: true}, - "9": {Team: "Core", Owner: true}, - "4": {Team: "Core", Owner: false}, - "5": {Team: "Core", Owner: false}, - "6": {Team: "Core", Owner: false}, - "8": {Team: "Internal", Owner: false}, - "10": {Team: "Cloud", Owner: false}, - "11": {Team: "Cloud", Owner: false}, - "12": {Team: "Cloud", Owner: false}, + "1": Reviewer{Team: "Core", Owner: true}, + "2": Reviewer{Team: "Core", Owner: true}, + "3": Reviewer{Team: "Core", Owner: true}, + "9": Reviewer{Team: "Core", Owner: true}, + "4": Reviewer{Team: "Core", Owner: false}, + "5": Reviewer{Team: "Core", Owner: false}, + "6": Reviewer{Team: "Core", Owner: false}, + "8": Reviewer{Team: "Internal", Owner: false}, + "10": Reviewer{Team: "Cloud", Owner: false}, + "11": Reviewer{Team: "Cloud", Owner: false}, + "12": Reviewer{Team: "Cloud", Owner: false}, }, // Docs. DocsReviewers: map[string]Reviewer{ - "7": {Team: "Core", Owner: true}, + "7": Reviewer{Team: "Core", Owner: true}, }, DocsReviewersOmit: map[string]bool{}, CodeReviewersOmit: map[string]bool{}, @@ -475,7 +499,7 @@ func TestCheckInternal(t *testing.T) { tests := []struct { desc string author string - reviews []github.Review + reviews map[string]*github.Review docs bool code bool result bool @@ -483,13 +507,13 @@ func TestCheckInternal(t *testing.T) { { desc: "no-reviews-fail", author: "4", - reviews: []github.Review{}, + reviews: map[string]*github.Review{}, result: false, }, { desc: "docs-only-no-reviews-fail", author: "4", - reviews: []github.Review{}, + reviews: map[string]*github.Review{}, docs: true, code: false, result: false, @@ -497,8 +521,8 @@ func TestCheckInternal(t *testing.T) { { desc: "docs-only-non-docs-approval-fail", author: "4", - reviews: []github.Review{ - {Author: "3", State: approved}, + reviews: map[string]*github.Review{ + "3": &github.Review{Author: "3", State: approved}, }, docs: true, code: false, @@ -507,8 +531,8 @@ func TestCheckInternal(t *testing.T) { { desc: "docs-only-docs-approval-success", author: "4", - reviews: []github.Review{ - {Author: "7", State: approved}, + reviews: map[string]*github.Review{ + "7": &github.Review{Author: "7", State: approved}, }, docs: true, code: false, @@ -517,7 +541,7 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-no-reviews-fail", author: "4", - reviews: []github.Review{}, + reviews: map[string]*github.Review{}, docs: false, code: true, result: false, @@ -525,8 +549,8 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-one-approval-fail", author: "4", - reviews: []github.Review{ - {Author: "3", State: approved}, + reviews: map[string]*github.Review{ + "3": &github.Review{Author: "3", State: approved}, }, docs: false, code: true, @@ -535,9 +559,9 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-two-approval-setb-fail", author: "4", - reviews: []github.Review{ - {Author: "5", State: approved}, - {Author: "6", State: approved}, + reviews: map[string]*github.Review{ + "5": &github.Review{Author: "5", State: approved}, + "6": &github.Review{Author: "6", State: approved}, }, docs: false, code: true, @@ -546,9 +570,9 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-one-changes-fail", author: "4", - reviews: []github.Review{ - {Author: "3", State: approved}, - {Author: "4", State: changesRequested}, + reviews: map[string]*github.Review{ + "3": &github.Review{Author: "3", State: approved}, + "4": &github.Review{Author: "4", State: changesRequested}, }, docs: false, code: true, @@ -557,9 +581,9 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-two-approvals-success", author: "6", - reviews: []github.Review{ - {Author: "3", State: approved}, - {Author: "4", State: approved}, + reviews: map[string]*github.Review{ + "3": &github.Review{Author: "3", State: approved}, + "4": &github.Review{Author: "4", State: approved}, }, docs: false, code: true, @@ -568,8 +592,8 @@ func TestCheckInternal(t *testing.T) { { desc: "docs-and-code-only-docs-approval-fail", author: "6", - reviews: []github.Review{ - {Author: "7", State: approved}, + reviews: map[string]*github.Review{ + "7": &github.Review{Author: "7", State: approved}, }, docs: true, code: true, @@ -578,9 +602,9 @@ func TestCheckInternal(t *testing.T) { { desc: "docs-and-code-only-code-approval-fail", author: "6", - reviews: []github.Review{ - {Author: "3", State: approved}, - {Author: "4", State: approved}, + reviews: map[string]*github.Review{ + "3": &github.Review{Author: "3", State: approved}, + "4": &github.Review{Author: "4", State: approved}, }, docs: true, code: true, @@ -589,10 +613,10 @@ func TestCheckInternal(t *testing.T) { { desc: "docs-and-code-docs-and-code-approval-success", author: "6", - reviews: []github.Review{ - {Author: "3", State: approved}, - {Author: "4", State: approved}, - {Author: "7", State: approved}, + reviews: map[string]*github.Review{ + "3": &github.Review{Author: "3", State: approved}, + "4": &github.Review{Author: "4", State: approved}, + "7": &github.Review{Author: "7", State: approved}, }, docs: true, code: true, @@ -601,8 +625,8 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-internal-on-approval-failure", author: "8", - reviews: []github.Review{ - {Author: "3", State: approved}, + reviews: map[string]*github.Review{ + "3": &github.Review{Author: "3", State: approved}, }, docs: false, code: true, @@ -611,9 +635,9 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-internal-code-approval-success", author: "8", - reviews: []github.Review{ - {Author: "3", State: approved}, - {Author: "4", State: approved}, + reviews: map[string]*github.Review{ + "3": &github.Review{Author: "3", State: approved}, + "4": &github.Review{Author: "4", State: approved}, }, docs: false, code: true, @@ -622,33 +646,9 @@ func TestCheckInternal(t *testing.T) { { desc: "code-only-internal-two-code-owner-approval-success", author: "4", - reviews: []github.Review{ - {Author: "3", State: approved}, - {Author: "9", State: approved}, - }, - docs: false, - code: true, - result: true, - }, - { - desc: "code-only-changes-requested-after-approval-failure", - author: "4", - reviews: []github.Review{ - {Author: "3", State: approved}, - {Author: "9", State: approved}, - {Author: "9", State: changesRequested}, - }, - docs: false, - code: true, - result: false, - }, - { - desc: "code-only-comment-after-approval-success", - author: "4", - reviews: []github.Review{ - {Author: "3", State: approved}, - {Author: "9", State: approved}, - {Author: "9", State: commented}, + reviews: map[string]*github.Review{ + "3": &github.Review{Author: "3", State: approved}, + "9": &github.Review{Author: "9", State: approved}, }, docs: false, code: true, @@ -657,9 +657,9 @@ func TestCheckInternal(t *testing.T) { { desc: "cloud-with-self-approval-failure", author: "10", - reviews: []github.Review{ - {Author: "11", State: approved}, - {Author: "12", State: approved}, + reviews: map[string]*github.Review{ + "11": &github.Review{Author: "11", State: approved}, + "12": &github.Review{Author: "12", State: approved}, }, docs: false, code: true, @@ -668,9 +668,9 @@ func TestCheckInternal(t *testing.T) { { desc: "cloud-with-core-approval-success", author: "10", - reviews: []github.Review{ - {Author: "3", State: approved}, - {Author: "9", State: approved}, + reviews: map[string]*github.Review{ + "3": &github.Review{Author: "3", State: approved}, + "9": &github.Review{Author: "9", State: approved}, }, docs: false, code: true, diff --git a/.golangci.yml b/.golangci.yml index 89ff401c285d6..3d28c3459114b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,7 +15,6 @@ linters: enable: - bodyclose - deadcode - - depguard - goimports - gosimple - govet @@ -29,13 +28,6 @@ linters: - unconvert - varcheck -linters-settings: - depguard: - list-type: denylist - include-go-root: true # check against stdlib - packages-with-error-message: - - io/ioutil: 'use "io" or "os" packages instead' - output: uniq-by-line: false diff --git a/Cargo.lock b/Cargo.lock index e4035cdf0adc3..0edacf469da90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.55" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" [[package]] name = "arrayvec" @@ -176,9 +176,9 @@ dependencies = [ [[package]] name = "crepe" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0c81f0055a7c877a9a69ec9d667a0b14c2b38394c712f54b9a400d035f49a9" +checksum = "a143ee68e4ec17aa70d0b95a1a5ef2c4e510b79cc3d956ea51bd2fe56d1856bd" dependencies = [ "petgraph", "proc-macro-error", @@ -1176,9 +1176,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-xid" diff --git a/Makefile b/Makefile index f8d46f2c275ad..399933fdf75c0 100644 --- a/Makefile +++ b/Makefile @@ -462,7 +462,7 @@ $(RENDER_TESTS): $(wildcard $(TOOLINGDIR)/cmd/render-tests/*.go) # Runs all Go/shell tests, called by CI/CD. # .PHONY: test -test: test-helm test-sh test-ci test-api test-go test-rust +test: test-sh test-ci test-api test-go test-rust # Runs bot Go tests. # @@ -475,19 +475,6 @@ test-bot: $(TEST_LOG_DIR): mkdir $(TEST_LOG_DIR) -# Google Cloud Build uses a weird homedir and Helm can't pick up plugins by default there, -# so override the plugin location via environment variable when running in CI. -.PHONY: test-helm -test-helm: - @if [ -d /builder/home ]; then export HELM_PLUGINS=/root/.local/share/helm/plugins; fi; \ - helm unittest examples/chart/teleport-cluster && \ - helm unittest examples/chart/teleport-kube-agent - -.PHONY: test-helm-update-snapshots -test-helm-update-snapshots: - helm unittest -u examples/chart/teleport-cluster - helm unittest -u examples/chart/teleport-kube-agent - # # Runs all Go tests except integration, called by CI/CD. # Chaos tests have high concurrency, run without race detector and have TestChaos prefix. diff --git a/README.md b/README.md index adf743dfc2fd0..99c7386576e91 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ 1. [Installing and Running](#installing-and-running) 1. [Docker](#docker) 1. [Building Teleport](#building-teleport) -1. [Why Did We Build Teleport?](#why-did-we-build-teleport) +1. [Why did We Build Teleport?](#why-did-we-build-teleport) 1. [More Information](#more-information) 1. [Support and Contributing](#support-and-contributing) 1. [Is Teleport Secure and Production Ready?](#is-teleport-secure-and-production-ready) diff --git a/api/client/client.go b/api/client/client.go index e936e8887abc4..b9fee45872518 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -877,14 +877,9 @@ func (c *Client) UpsertKubeServiceV2(ctx context.Context, s types.Server) (*type // GetKubeServices returns the list of kubernetes services registered in the // cluster. func (c *Client) GetKubeServices(ctx context.Context) ([]types.Server, error) { - resources, err := GetResourcesWithFilters(ctx, c, proto.ListResourcesRequest{ - Namespace: defaults.Namespace, - ResourceType: types.KindKubeService, - }) + resources, err := c.GetResources(ctx, defaults.Namespace, types.KindKubeService) if err != nil { - // Underlying ListResources for kube service was not available, use fallback. - // - // DELETE IN 11.0.0 + // DELETE IN 10.0 if trace.IsNotImplemented(err) { return c.getKubeServicesFallback(ctx) } @@ -892,9 +887,14 @@ func (c *Client) GetKubeServices(ctx context.Context) ([]types.Server, error) { return nil, trace.Wrap(err) } - servers, err := types.ResourcesWithLabels(resources).AsServers() - if err != nil { - return nil, trace.Wrap(err) + servers := make([]types.Server, len(resources)) + for i, resource := range resources { + srv, ok := resource.(types.Server) + if !ok { + return nil, trace.BadParameter("expected Server resource, got %T", resource) + } + + servers[i] = srv } return servers, nil @@ -919,14 +919,8 @@ func (c *Client) getKubeServicesFallback(ctx context.Context) ([]types.Server, e // GetApplicationServers returns all registered application servers. func (c *Client) GetApplicationServers(ctx context.Context, namespace string) ([]types.AppServer, error) { - resources, err := GetResourcesWithFilters(ctx, c, proto.ListResourcesRequest{ - Namespace: namespace, - ResourceType: types.KindAppServer, - }) + resources, err := c.GetResources(ctx, namespace, types.KindAppServer) if err != nil { - // Underlying ListResources for app server was not available, use fallback. - // - // DELETE IN 11.0.0 if trace.IsNotImplemented(err) { servers, err := c.getApplicationServersFallback(ctx, namespace) if err != nil { @@ -939,9 +933,14 @@ func (c *Client) GetApplicationServers(ctx context.Context, namespace string) ([ return nil, trace.Wrap(err) } - servers, err := types.ResourcesWithLabels(resources).AsAppServers() - if err != nil { - return nil, trace.Wrap(err) + servers := make([]types.AppServer, len(resources)) + for i, resource := range resources { + appServer, ok := resource.(types.AppServer) + if !ok { + return nil, trace.BadParameter("expected AppServer resource, got %T", resource) + } + + servers[i] = appServer } // In addition, we need to fetch legacy application servers. @@ -1179,14 +1178,8 @@ func (c *Client) DeleteAllKubeServices(ctx context.Context) error { // GetDatabaseServers returns all registered database proxy servers. func (c *Client) GetDatabaseServers(ctx context.Context, namespace string) ([]types.DatabaseServer, error) { - resources, err := GetResourcesWithFilters(ctx, c, proto.ListResourcesRequest{ - Namespace: namespace, - ResourceType: types.KindDatabaseServer, - }) + resources, err := c.GetResources(ctx, namespace, types.KindDatabaseServer) if err != nil { - // Underlying ListResources for db server was not available, use fallback. - // - // DELETE IN 11.0.0 if trace.IsNotImplemented(err) { servers, err := c.getDatabaseServersFallback(ctx, namespace) if err != nil { @@ -1199,9 +1192,14 @@ func (c *Client) GetDatabaseServers(ctx context.Context, namespace string) ([]ty return nil, trace.Wrap(err) } - servers, err := types.ResourcesWithLabels(resources).AsDatabaseServers() - if err != nil { - return nil, trace.Wrap(err) + servers := make([]types.DatabaseServer, len(resources)) + for i, resource := range resources { + databaseServer, ok := resource.(types.DatabaseServer) + if !ok { + return nil, trace.BadParameter("expected DatabaseServer resource, got %T", resource) + } + + servers[i] = databaseServer } return servers, nil @@ -1626,46 +1624,17 @@ func (c *Client) GetNode(ctx context.Context, namespace, name string) (types.Ser // GetNodes returns a complete list of nodes that the user has access to in the given namespace. func (c *Client) GetNodes(ctx context.Context, namespace string) ([]types.Server, error) { - resources, err := GetResourcesWithFilters(ctx, c, proto.ListResourcesRequest{ - ResourceType: types.KindNode, - Namespace: namespace, - }) - if err != nil { - // Underlying ListResources for nodes was not available, use fallback. - // - // DELETE IN 11.0.0 - if trace.IsNotImplemented(err) { - servers, err := GetNodesWithLabels(ctx, c, namespace, nil) - if err != nil { - return nil, trace.Wrap(err) - } - return servers, nil - } - - return nil, trace.Wrap(err) - } - - servers, err := types.ResourcesWithLabels(resources).AsServers() - if err != nil { - return nil, trace.Wrap(err) - } - - return servers, nil + return GetNodesWithLabels(ctx, c, namespace, nil) } // NodeClient is an interface used by GetNodesWithLabels to abstract over implementations of // the ListNodes method. -// -// DELETE IN 11.0.0 with GetNodesWithLabels (used in both api/client/client.go and lib/auth/httpfallback.go) -// replaced by ListResourcesClient type NodeClient interface { ListNodes(ctx context.Context, req proto.ListNodesRequest) (nodes []types.Server, nextKey string, err error) } // GetNodesWithLabels is a helper for getting a list of nodes with optional label-based filtering. In addition to // iterating pages, it also correctly handles downsizing pages when LimitExceeded errors are encountered. -// -// DELETE IN 11.0.0 replaced by GetResourcesWithFilters. func GetNodesWithLabels(ctx context.Context, clt NodeClient, namespace string, labels map[string]string) ([]types.Server, error) { // Retrieve the complete list of nodes in chunks. var ( @@ -2440,10 +2409,6 @@ func (c *Client) ListResources(ctx context.Context, req proto.ListResourcesReque resources[i] = respResource.GetNode() case types.KindKubeService: resources[i] = respResource.GetKubeService() - case types.KindWindowsDesktop: - resources[i] = respResource.GetWindowsDesktop() - case types.KindKubernetesCluster: - resources[i] = respResource.GetKubeCluster() default: return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType) } @@ -2456,16 +2421,9 @@ func (c *Client) ListResources(ctx context.Context, req proto.ListResourcesReque }, nil } -// ListResourcesClient is an interface used by GetResourcesWithFilters to abstract over implementations of -// the ListResources method. -type ListResourcesClient interface { - ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) -} - -// GetResourcesWithFilters is a helper for getting a list of resources with optional filtering. In addition to -// iterating pages, it also correctly handles downsizing pages when LimitExceeded errors are encountered. -func GetResourcesWithFilters(ctx context.Context, clt ListResourcesClient, req proto.ListResourcesRequest) ([]types.ResourceWithLabels, error) { - // Retrieve the complete list of resources in chunks. +// GetResources retrieves all pages from ListResourcesPage and return all +// resources. +func (c *Client) GetResources(ctx context.Context, namespace, resourceType string) ([]types.ResourceWithLabels, error) { var ( resources []types.ResourceWithLabels startKey string @@ -2473,20 +2431,15 @@ func GetResourcesWithFilters(ctx context.Context, clt ListResourcesClient, req p ) for { - resp, err := clt.ListResources(ctx, proto.ListResourcesRequest{ - Namespace: req.Namespace, - ResourceType: req.ResourceType, - StartKey: startKey, - Limit: chunkSize, - Labels: req.Labels, - SearchKeywords: req.SearchKeywords, - PredicateExpression: req.PredicateExpression, + resp, err := c.ListResources(ctx, proto.ListResourcesRequest{ + Namespace: namespace, + ResourceType: resourceType, + StartKey: startKey, + Limit: chunkSize, }) if err != nil { if trace.IsLimitExceeded(err) { - // Cut chunkSize in half if gRPC max message size is exceeded. chunkSize = chunkSize / 2 - // This is an extremely unlikely scenario, but better to cover it anyways. if chunkSize == 0 { return nil, trace.Wrap(trail.FromGRPC(err), "resource is too large to retrieve") } diff --git a/api/client/client_test.go b/api/client/client_test.go index 58855bd74688f..5b67271ff7238 100644 --- a/api/client/client_test.go +++ b/api/client/client_test.go @@ -121,13 +121,6 @@ func (m *mockServer) ListResources(ctx context.Context, req *proto.ListResources } protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_KubeService{KubeService: srv}} - case types.KindWindowsDesktop: - desktop, ok := resource.(*types.WindowsDesktopV3) - if !ok { - return nil, trace.Errorf("windows desktop has invalid type %T", resource) - } - - protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_WindowsDesktop{WindowsDesktop: desktop}} } resp.Resources = append(resp.Resources, protoResource) @@ -233,21 +226,6 @@ func testResources(resourceType, namespace string) ([]types.ResourceWithLabels, return nil, trace.Wrap(err) } } - case types.KindWindowsDesktop: - for i := 0; i < size; i++ { - var err error - name := fmt.Sprintf("windows-desktop-%d", i) - resources[i], err = types.NewWindowsDesktopV3( - name, - map[string]string{"label": string(make([]byte, labelSize))}, - types.WindowsDesktopSpecV3{ - Addr: "_", - HostID: "_", - }) - if err != nil { - return nil, trace.Wrap(err) - } - } default: return nil, trace.Errorf("unsupported resource type %s", resourceType) @@ -501,10 +479,6 @@ func TestListResources(t *testing.T) { resourceType: types.KindKubeService, resourceStruct: &types.ServerV2{}, }, - "WindowsDesktop": { - resourceType: types.KindWindowsDesktop, - resourceStruct: &types.WindowsDesktopV3{}, - }, } // Create client @@ -584,9 +558,6 @@ func TestGetResources(t *testing.T) { "KubeService": { resourceType: types.KindKubeService, }, - "WindowsDesktop": { - resourceType: types.KindWindowsDesktop, - }, } for name, test := range testCases { @@ -594,7 +565,7 @@ func TestGetResources(t *testing.T) { expectedResources, err := testResources(test.resourceType, defaults.Namespace) require.NoError(t, err) - // Test listing everything at once errors with limit exceeded. + // listing everything at once breaks the ListResource. _, err = clt.ListResources(ctx, proto.ListResourcesRequest{ Namespace: defaults.Namespace, Limit: int32(len(expectedResources)), @@ -603,11 +574,7 @@ func TestGetResources(t *testing.T) { require.Error(t, err) require.IsType(t, &trace.LimitExceededError{}, err.(*trace.TraceErr).OrigError()) - // Test getting all resources by chunks to handle limit exceeded. - resources, err := GetResourcesWithFilters(ctx, clt, proto.ListResourcesRequest{ - Namespace: defaults.Namespace, - ResourceType: test.resourceType, - }) + resources, err := clt.GetResources(ctx, defaults.Namespace, test.resourceType) require.NoError(t, err) require.Len(t, resources, len(expectedResources)) require.Empty(t, cmp.Diff(expectedResources, resources)) diff --git a/api/client/contextdialer.go b/api/client/contextdialer.go index 1710672b941d1..a8396165aa173 100644 --- a/api/client/contextdialer.go +++ b/api/client/contextdialer.go @@ -45,8 +45,8 @@ func (f ContextDialerFunc) DialContext(ctx context.Context, network, addr string return f(ctx, network, addr) } -// newDirectDialer makes a new dialer to connect directly to an Auth server. -func newDirectDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { +// NewDirectDialer makes a new dialer to connect directly to an Auth server. +func NewDirectDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { return &net.Dialer{ Timeout: dialTimeout, KeepAlive: keepAlivePeriod, @@ -56,8 +56,8 @@ func newDirectDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { // NewDialer makes a new dialer that connects to an Auth server either directly or via an HTTP proxy, depending // on the environment. func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { + dialer := NewDirectDialer(keepAlivePeriod, dialTimeout) return ContextDialerFunc(func(ctx context.Context, network, addr string) (net.Conn, error) { - dialer := newDirectDialer(keepAlivePeriod, dialTimeout) if proxyAddr := proxy.GetProxyAddress(addr); proxyAddr != nil { return DialProxyWithDialer(ctx, proxyAddr, addr, dialer) } @@ -70,8 +70,7 @@ func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { func NewProxyDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Duration, discoveryAddr string, insecure bool) ContextDialer { dialer := newTunnelDialer(ssh, keepAlivePeriod, dialTimeout) return ContextDialerFunc(func(ctx context.Context, network, _ string) (conn net.Conn, err error) { - tunnelAddr, err := webclient.GetTunnelAddr( - &webclient.Config{Context: ctx, ProxyAddr: discoveryAddr, Insecure: insecure}) + tunnelAddr, err := webclient.GetTunnelAddr(ctx, discoveryAddr, insecure, nil) if err != nil { return nil, trace.Wrap(err) } @@ -86,7 +85,7 @@ func NewProxyDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Dura // newTunnelDialer makes a dialer to connect to an Auth server through the SSH reverse tunnel on the proxy. func newTunnelDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Duration) ContextDialer { - dialer := newDirectDialer(keepAlivePeriod, dialTimeout) + dialer := NewDirectDialer(keepAlivePeriod, dialTimeout) return ContextDialerFunc(func(ctx context.Context, network, addr string) (conn net.Conn, err error) { conn, err = dialer.DialContext(ctx, network, addr) if err != nil { @@ -105,8 +104,7 @@ func newTunnelDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Dur // through the SSH reverse tunnel on the proxy. func newTLSRoutingTunnelDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Duration, discoveryAddr string, insecure bool) ContextDialer { return ContextDialerFunc(func(ctx context.Context, network, addr string) (conn net.Conn, err error) { - tunnelAddr, err := webclient.GetTunnelAddr( - &webclient.Config{Context: ctx, ProxyAddr: discoveryAddr, Insecure: insecure}) + tunnelAddr, err := webclient.GetTunnelAddr(ctx, discoveryAddr, insecure, nil) if err != nil { return nil, trace.Wrap(err) } diff --git a/api/client/credentials.go b/api/client/credentials.go index c2660c0e181c6..2b4e13312d03a 100644 --- a/api/client/credentials.go +++ b/api/client/credentials.go @@ -19,7 +19,7 @@ package client import ( "crypto/tls" "crypto/x509" - "os" + "io/ioutil" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/identityfile" @@ -115,7 +115,7 @@ func (c *keypairCreds) TLSConfig() (*tls.Config, error) { return nil, trace.Wrap(err) } - cas, err := os.ReadFile(c.caFile) + cas, err := ioutil.ReadFile(c.caFile) if err != nil { return nil, trace.ConvertSystemError(err) } diff --git a/api/client/credentials_test.go b/api/client/credentials_test.go index 7985d9a4b4a5a..faa09c4245d6f 100644 --- a/api/client/credentials_test.go +++ b/api/client/credentials_test.go @@ -19,6 +19,7 @@ package client import ( "crypto/tls" "crypto/x509" + "io/ioutil" "os" "path/filepath" "testing" @@ -152,11 +153,11 @@ func TestLoadKeyPair(t *testing.T) { // Write key pair and CAs files from bytes. path := t.TempDir() + "username" certPath, keyPath, caPath := path+".crt", path+".key", path+".cas" - err := os.WriteFile(certPath, tlsCert, 0600) + err := ioutil.WriteFile(certPath, tlsCert, 0600) require.NoError(t, err) - err = os.WriteFile(keyPath, keyPEM, 0600) + err = ioutil.WriteFile(keyPath, keyPEM, 0600) require.NoError(t, err) - err = os.WriteFile(caPath, tlsCACert, 0600) + err = ioutil.WriteFile(caPath, tlsCACert, 0600) require.NoError(t, err) // Load key pair from disk. @@ -184,7 +185,20 @@ func TestLoadProfile(t *testing.T) { SiteName: "example.com", Username: "testUser", Dir: dir, - }) + }, false) + testProfileContents(t, dir, profileName) + }) + + // DELETE IN 8.0.0 + t.Run("old profile", func(t *testing.T) { + t.Parallel() + dir := t.TempDir() + writeProfile(t, &profile.Profile{ + WebProxyAddr: profileName + ":3080", + SiteName: "example.com", + Username: "testUser", + Dir: dir, + }, true) testProfileContents(t, dir, profileName) }) @@ -222,18 +236,24 @@ func testProfileContents(t *testing.T, dir, name string) { require.NoError(t, err) } -func writeProfile(t *testing.T, p *profile.Profile) { +func writeProfile(t *testing.T, p *profile.Profile, oldSSHPath bool) { // Save profile and keys to disk. require.NoError(t, p.SaveToDir(p.Dir, true)) require.NoError(t, os.MkdirAll(p.KeyDir(), 0700)) require.NoError(t, os.MkdirAll(p.ProxyKeyDir(), 0700)) require.NoError(t, os.MkdirAll(p.TLSClusterCASDir(), 0700)) - require.NoError(t, os.WriteFile(p.UserKeyPath(), keyPEM, 0600)) - require.NoError(t, os.WriteFile(p.TLSCertPath(), tlsCert, 0600)) - require.NoError(t, os.WriteFile(p.TLSCAPathCluster(p.SiteName), tlsCACert, 0600)) - require.NoError(t, os.WriteFile(p.KnownHostsPath(), sshCACert, 0600)) + require.NoError(t, ioutil.WriteFile(p.UserKeyPath(), keyPEM, 0600)) + require.NoError(t, ioutil.WriteFile(p.TLSCertPath(), tlsCert, 0600)) + require.NoError(t, ioutil.WriteFile(p.TLSCAPathCluster(p.SiteName), tlsCACert, 0600)) + require.NoError(t, ioutil.WriteFile(p.KnownHostsPath(), sshCACert, 0600)) + // If oldSSHPath is specified, write the sshCert to the old ssh cert path. + // DELETE IN 8.0.0 + if oldSSHPath { + require.NoError(t, ioutil.WriteFile(p.OldSSHCertPath(), sshCert, 0600)) + return + } require.NoError(t, os.MkdirAll(p.SSHDir(), 0700)) - require.NoError(t, os.WriteFile(p.SSHCertPath(), sshCert, 0600)) + require.NoError(t, ioutil.WriteFile(p.SSHCertPath(), sshCert, 0600)) } func getExpectedTLSConfig(t *testing.T) *tls.Config { diff --git a/api/client/proto/authservice.pb.go b/api/client/proto/authservice.pb.go index abeac68187fcd..5ae16cf258b81 100644 --- a/api/client/proto/authservice.pb.go +++ b/api/client/proto/authservice.pb.go @@ -105,39 +105,6 @@ func (DeviceType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_ce8bd90b12161215, []int{1} } -type DeviceUsage int32 - -const ( - DeviceUsage_DEVICE_USAGE_UNSPECIFIED DeviceUsage = 0 - // Device intended for MFA use, but not for passwordless. - // Allows both FIDO and FIDO2 devices. - // Resident keys not required. - DeviceUsage_DEVICE_USAGE_MFA DeviceUsage = 1 - // Device intended for both MFA and passwordless. - // Requires a FIDO2 device and takes a resident key slot. - DeviceUsage_DEVICE_USAGE_PASSWORDLESS DeviceUsage = 2 -) - -var DeviceUsage_name = map[int32]string{ - 0: "DEVICE_USAGE_UNSPECIFIED", - 1: "DEVICE_USAGE_MFA", - 2: "DEVICE_USAGE_PASSWORDLESS", -} - -var DeviceUsage_value = map[string]int32{ - "DEVICE_USAGE_UNSPECIFIED": 0, - "DEVICE_USAGE_MFA": 1, - "DEVICE_USAGE_PASSWORDLESS": 2, -} - -func (x DeviceUsage) String() string { - return proto.EnumName(DeviceUsage_name, int32(x)) -} - -func (DeviceUsage) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{2} -} - // Order specifies any ordering of some objects as returned in regards to some aspect // of said objects which may be trivially ordered such as a timestamp. type Order int32 @@ -162,7 +129,7 @@ func (x Order) String() string { } func (Order) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{3} + return fileDescriptor_ce8bd90b12161215, []int{2} } type UserCertsRequest_CertUsage int32 @@ -5727,14 +5694,11 @@ func (*AddMFADeviceResponse) XXX_OneofWrappers() []interface{} { // AddMFADeviceRequestInit describes the new MFA device. type AddMFADeviceRequestInit struct { - DeviceName string `protobuf:"bytes,1,opt,name=DeviceName,proto3" json:"DeviceName,omitempty"` - DeviceType DeviceType `protobuf:"varint,3,opt,name=DeviceType,proto3,enum=proto.DeviceType" json:"DeviceType,omitempty"` - // DeviceUsage is the requested usage for the device. - // Defaults to DEVICE_USAGE_MFA. - DeviceUsage DeviceUsage `protobuf:"varint,4,opt,name=DeviceUsage,proto3,enum=proto.DeviceUsage" json:"device_usage,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DeviceName string `protobuf:"bytes,1,opt,name=DeviceName,proto3" json:"DeviceName,omitempty"` + DeviceType DeviceType `protobuf:"varint,3,opt,name=DeviceType,proto3,enum=proto.DeviceType" json:"DeviceType,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *AddMFADeviceRequestInit) Reset() { *m = AddMFADeviceRequestInit{} } @@ -5784,13 +5748,6 @@ func (m *AddMFADeviceRequestInit) GetDeviceType() DeviceType { return DeviceType_DEVICE_TYPE_UNSPECIFIED } -func (m *AddMFADeviceRequestInit) GetDeviceUsage() DeviceUsage { - if m != nil { - return m.DeviceUsage - } - return DeviceUsage_DEVICE_USAGE_UNSPECIFIED -} - // AddMFADeviceResponseAck is a confirmation of successful device registration. type AddMFADeviceResponseAck struct { Device *types.MFADevice `protobuf:"bytes,1,opt,name=Device,proto3" json:"Device,omitempty"` @@ -8804,97 +8761,16 @@ func (m *UserCredentials) GetPassword() []byte { return nil } -// ContextUser marks requests that rely in the currently authenticated user. -type ContextUser struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *ContextUser) Reset() { *m = ContextUser{} } -func (m *ContextUser) String() string { return proto.CompactTextString(m) } -func (*ContextUser) ProtoMessage() {} -func (*ContextUser) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{130} -} -func (m *ContextUser) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ContextUser) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ContextUser.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ContextUser) XXX_Merge(src proto.Message) { - xxx_messageInfo_ContextUser.Merge(m, src) -} -func (m *ContextUser) XXX_Size() int { - return m.Size() -} -func (m *ContextUser) XXX_DiscardUnknown() { - xxx_messageInfo_ContextUser.DiscardUnknown(m) -} - -var xxx_messageInfo_ContextUser proto.InternalMessageInfo - -// Passwordless marks requests for passwordless challenges. -type Passwordless struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Passwordless) Reset() { *m = Passwordless{} } -func (m *Passwordless) String() string { return proto.CompactTextString(m) } -func (*Passwordless) ProtoMessage() {} -func (*Passwordless) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{131} -} -func (m *Passwordless) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *Passwordless) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_Passwordless.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *Passwordless) XXX_Merge(src proto.Message) { - xxx_messageInfo_Passwordless.Merge(m, src) -} -func (m *Passwordless) XXX_Size() int { - return m.Size() -} -func (m *Passwordless) XXX_DiscardUnknown() { - xxx_messageInfo_Passwordless.DiscardUnknown(m) -} - -var xxx_messageInfo_Passwordless proto.InternalMessageInfo - // CreateAuthenticateChallengeRequest is a request for creating MFA authentication challenges for a // users mfa devices. type CreateAuthenticateChallengeRequest struct { // Request defines how the request will be verified before creating challenges. - // An empty Request is equivalent to context_user being set. + // This field can be empty, which implies the request is to create challenges for the + // user in context (logged in user). // // Types that are valid to be assigned to Request: // *CreateAuthenticateChallengeRequest_UserCredentials // *CreateAuthenticateChallengeRequest_RecoveryStartTokenID - // *CreateAuthenticateChallengeRequest_ContextUser - // *CreateAuthenticateChallengeRequest_Passwordless Request isCreateAuthenticateChallengeRequest_Request `protobuf_oneof:"Request"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -8905,7 +8781,7 @@ func (m *CreateAuthenticateChallengeRequest) Reset() { *m = CreateAuthen func (m *CreateAuthenticateChallengeRequest) String() string { return proto.CompactTextString(m) } func (*CreateAuthenticateChallengeRequest) ProtoMessage() {} func (*CreateAuthenticateChallengeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{132} + return fileDescriptor_ce8bd90b12161215, []int{130} } func (m *CreateAuthenticateChallengeRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -8946,21 +8822,11 @@ type CreateAuthenticateChallengeRequest_UserCredentials struct { type CreateAuthenticateChallengeRequest_RecoveryStartTokenID struct { RecoveryStartTokenID string `protobuf:"bytes,2,opt,name=RecoveryStartTokenID,proto3,oneof" json:"recovery_start_token_id,omitempty"` } -type CreateAuthenticateChallengeRequest_ContextUser struct { - ContextUser *ContextUser `protobuf:"bytes,3,opt,name=ContextUser,proto3,oneof" json:"context_user,omitempty"` -} -type CreateAuthenticateChallengeRequest_Passwordless struct { - Passwordless *Passwordless `protobuf:"bytes,4,opt,name=Passwordless,proto3,oneof" json:"passwordless,omitempty"` -} func (*CreateAuthenticateChallengeRequest_UserCredentials) isCreateAuthenticateChallengeRequest_Request() { } func (*CreateAuthenticateChallengeRequest_RecoveryStartTokenID) isCreateAuthenticateChallengeRequest_Request() { } -func (*CreateAuthenticateChallengeRequest_ContextUser) isCreateAuthenticateChallengeRequest_Request() { -} -func (*CreateAuthenticateChallengeRequest_Passwordless) isCreateAuthenticateChallengeRequest_Request() { -} func (m *CreateAuthenticateChallengeRequest) GetRequest() isCreateAuthenticateChallengeRequest_Request { if m != nil { @@ -8983,27 +8849,11 @@ func (m *CreateAuthenticateChallengeRequest) GetRecoveryStartTokenID() string { return "" } -func (m *CreateAuthenticateChallengeRequest) GetContextUser() *ContextUser { - if x, ok := m.GetRequest().(*CreateAuthenticateChallengeRequest_ContextUser); ok { - return x.ContextUser - } - return nil -} - -func (m *CreateAuthenticateChallengeRequest) GetPasswordless() *Passwordless { - if x, ok := m.GetRequest().(*CreateAuthenticateChallengeRequest_Passwordless); ok { - return x.Passwordless - } - return nil -} - // XXX_OneofWrappers is for the internal use of the proto package. func (*CreateAuthenticateChallengeRequest) XXX_OneofWrappers() []interface{} { return []interface{}{ (*CreateAuthenticateChallengeRequest_UserCredentials)(nil), (*CreateAuthenticateChallengeRequest_RecoveryStartTokenID)(nil), - (*CreateAuthenticateChallengeRequest_ContextUser)(nil), - (*CreateAuthenticateChallengeRequest_Passwordless)(nil), } } @@ -9025,7 +8875,7 @@ func (m *CreatePrivilegeTokenRequest) Reset() { *m = CreatePrivilegeToke func (m *CreatePrivilegeTokenRequest) String() string { return proto.CompactTextString(m) } func (*CreatePrivilegeTokenRequest) ProtoMessage() {} func (*CreatePrivilegeTokenRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{133} + return fileDescriptor_ce8bd90b12161215, []int{131} } func (m *CreatePrivilegeTokenRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9068,20 +8918,17 @@ type CreateRegisterChallengeRequest struct { // All user token types are accepted except UserTokenTypeRecoveryStart. TokenID string `protobuf:"bytes,1,opt,name=TokenID,proto3" json:"token_id"` // DeviceType is the type of MFA device to make a register challenge for. - DeviceType DeviceType `protobuf:"varint,2,opt,name=DeviceType,proto3,enum=proto.DeviceType" json:"device_type"` - // DeviceUsage is the requested usage for the device. - // Defaults to DEVICE_USAGE_MFA. - DeviceUsage DeviceUsage `protobuf:"varint,3,opt,name=DeviceUsage,proto3,enum=proto.DeviceUsage" json:"device_usage,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DeviceType DeviceType `protobuf:"varint,2,opt,name=DeviceType,proto3,enum=proto.DeviceType" json:"device_type"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *CreateRegisterChallengeRequest) Reset() { *m = CreateRegisterChallengeRequest{} } func (m *CreateRegisterChallengeRequest) String() string { return proto.CompactTextString(m) } func (*CreateRegisterChallengeRequest) ProtoMessage() {} func (*CreateRegisterChallengeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{134} + return fileDescriptor_ce8bd90b12161215, []int{132} } func (m *CreateRegisterChallengeRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9124,13 +8971,6 @@ func (m *CreateRegisterChallengeRequest) GetDeviceType() DeviceType { return DeviceType_DEVICE_TYPE_UNSPECIFIED } -func (m *CreateRegisterChallengeRequest) GetDeviceUsage() DeviceUsage { - if m != nil { - return m.DeviceUsage - } - return DeviceUsage_DEVICE_USAGE_UNSPECIFIED -} - // PaginatedResource represents one of the supported resources. type PaginatedResource struct { // Resource is the resource itself. @@ -9140,8 +8980,6 @@ type PaginatedResource struct { // *PaginatedResource_AppServer // *PaginatedResource_Node // *PaginatedResource_KubeService - // *PaginatedResource_WindowsDesktop - // *PaginatedResource_KubeCluster Resource isPaginatedResource_Resource `protobuf_oneof:"resource"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -9152,7 +8990,7 @@ func (m *PaginatedResource) Reset() { *m = PaginatedResource{} } func (m *PaginatedResource) String() string { return proto.CompactTextString(m) } func (*PaginatedResource) ProtoMessage() {} func (*PaginatedResource) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{135} + return fileDescriptor_ce8bd90b12161215, []int{133} } func (m *PaginatedResource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9199,19 +9037,11 @@ type PaginatedResource_Node struct { type PaginatedResource_KubeService struct { KubeService *types.ServerV2 `protobuf:"bytes,4,opt,name=KubeService,proto3,oneof" json:"kube_service,omitempty"` } -type PaginatedResource_WindowsDesktop struct { - WindowsDesktop *types.WindowsDesktopV3 `protobuf:"bytes,5,opt,name=WindowsDesktop,proto3,oneof" json:"windows_desktop,omitempty"` -} -type PaginatedResource_KubeCluster struct { - KubeCluster *types.KubernetesClusterV3 `protobuf:"bytes,6,opt,name=KubeCluster,proto3,oneof" json:"kube_cluster,omitempty"` -} func (*PaginatedResource_DatabaseServer) isPaginatedResource_Resource() {} func (*PaginatedResource_AppServer) isPaginatedResource_Resource() {} func (*PaginatedResource_Node) isPaginatedResource_Resource() {} func (*PaginatedResource_KubeService) isPaginatedResource_Resource() {} -func (*PaginatedResource_WindowsDesktop) isPaginatedResource_Resource() {} -func (*PaginatedResource_KubeCluster) isPaginatedResource_Resource() {} func (m *PaginatedResource) GetResource() isPaginatedResource_Resource { if m != nil { @@ -9248,20 +9078,6 @@ func (m *PaginatedResource) GetKubeService() *types.ServerV2 { return nil } -func (m *PaginatedResource) GetWindowsDesktop() *types.WindowsDesktopV3 { - if x, ok := m.GetResource().(*PaginatedResource_WindowsDesktop); ok { - return x.WindowsDesktop - } - return nil -} - -func (m *PaginatedResource) GetKubeCluster() *types.KubernetesClusterV3 { - if x, ok := m.GetResource().(*PaginatedResource_KubeCluster); ok { - return x.KubeCluster - } - return nil -} - // XXX_OneofWrappers is for the internal use of the proto package. func (*PaginatedResource) XXX_OneofWrappers() []interface{} { return []interface{}{ @@ -9269,8 +9085,6 @@ func (*PaginatedResource) XXX_OneofWrappers() []interface{} { (*PaginatedResource_AppServer)(nil), (*PaginatedResource_Node)(nil), (*PaginatedResource_KubeService)(nil), - (*PaginatedResource_WindowsDesktop)(nil), - (*PaginatedResource_KubeCluster)(nil), } } @@ -9297,19 +9111,17 @@ type ListResourcesRequest struct { SortBy types.SortBy `protobuf:"bytes,8,opt,name=SortBy,proto3" json:"sort_by,omitempty"` // NeedTotalCount indicates whether or not the caller also wants the total number of resources // after filtering. - NeedTotalCount bool `protobuf:"varint,9,opt,name=NeedTotalCount,proto3" json:"need_total_count,omitempty"` - // WindowsDesktopFilter specifies windows desktop specific filters. - WindowsDesktopFilter types.WindowsDesktopFilter `protobuf:"bytes,10,opt,name=WindowsDesktopFilter,proto3" json:"windows_desktop_filter,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + NeedTotalCount bool `protobuf:"varint,9,opt,name=NeedTotalCount,proto3" json:"need_total_count,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ListResourcesRequest) Reset() { *m = ListResourcesRequest{} } func (m *ListResourcesRequest) String() string { return proto.CompactTextString(m) } func (*ListResourcesRequest) ProtoMessage() {} func (*ListResourcesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{136} + return fileDescriptor_ce8bd90b12161215, []int{134} } func (m *ListResourcesRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9401,13 +9213,6 @@ func (m *ListResourcesRequest) GetNeedTotalCount() bool { return false } -func (m *ListResourcesRequest) GetWindowsDesktopFilter() types.WindowsDesktopFilter { - if m != nil { - return m.WindowsDesktopFilter - } - return types.WindowsDesktopFilter{} -} - // ListResourceResponse response of ListResources. type ListResourcesResponse struct { // Resources is a list of resource. @@ -9427,7 +9232,7 @@ func (m *ListResourcesResponse) Reset() { *m = ListResourcesResponse{} } func (m *ListResourcesResponse) String() string { return proto.CompactTextString(m) } func (*ListResourcesResponse) ProtoMessage() {} func (*ListResourcesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{137} + return fileDescriptor_ce8bd90b12161215, []int{135} } func (m *ListResourcesResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9522,7 +9327,7 @@ func (m *CreateSessionTrackerRequest) Reset() { *m = CreateSessionTracke func (m *CreateSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*CreateSessionTrackerRequest) ProtoMessage() {} func (*CreateSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{138} + return fileDescriptor_ce8bd90b12161215, []int{136} } func (m *CreateSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9662,7 +9467,7 @@ func (m *GetSessionTrackerRequest) Reset() { *m = GetSessionTrackerReque func (m *GetSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*GetSessionTrackerRequest) ProtoMessage() {} func (*GetSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{139} + return fileDescriptor_ce8bd90b12161215, []int{137} } func (m *GetSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9711,7 +9516,7 @@ func (m *RemoveSessionTrackerRequest) Reset() { *m = RemoveSessionTracke func (m *RemoveSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*RemoveSessionTrackerRequest) ProtoMessage() {} func (*RemoveSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{140} + return fileDescriptor_ce8bd90b12161215, []int{138} } func (m *RemoveSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9759,7 +9564,7 @@ func (m *SessionTrackerUpdateState) Reset() { *m = SessionTrackerUpdateS func (m *SessionTrackerUpdateState) String() string { return proto.CompactTextString(m) } func (*SessionTrackerUpdateState) ProtoMessage() {} func (*SessionTrackerUpdateState) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{141} + return fileDescriptor_ce8bd90b12161215, []int{139} } func (m *SessionTrackerUpdateState) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9807,7 +9612,7 @@ func (m *SessionTrackerAddParticipant) Reset() { *m = SessionTrackerAddP func (m *SessionTrackerAddParticipant) String() string { return proto.CompactTextString(m) } func (*SessionTrackerAddParticipant) ProtoMessage() {} func (*SessionTrackerAddParticipant) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{142} + return fileDescriptor_ce8bd90b12161215, []int{140} } func (m *SessionTrackerAddParticipant) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9855,7 +9660,7 @@ func (m *SessionTrackerRemoveParticipant) Reset() { *m = SessionTrackerR func (m *SessionTrackerRemoveParticipant) String() string { return proto.CompactTextString(m) } func (*SessionTrackerRemoveParticipant) ProtoMessage() {} func (*SessionTrackerRemoveParticipant) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{143} + return fileDescriptor_ce8bd90b12161215, []int{141} } func (m *SessionTrackerRemoveParticipant) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -9909,7 +9714,7 @@ func (m *UpdateSessionTrackerRequest) Reset() { *m = UpdateSessionTracke func (m *UpdateSessionTrackerRequest) String() string { return proto.CompactTextString(m) } func (*UpdateSessionTrackerRequest) ProtoMessage() {} func (*UpdateSessionTrackerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{144} + return fileDescriptor_ce8bd90b12161215, []int{142} } func (m *UpdateSessionTrackerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10015,7 +9820,7 @@ func (m *PresenceMFAChallengeRequest) Reset() { *m = PresenceMFAChalleng func (m *PresenceMFAChallengeRequest) String() string { return proto.CompactTextString(m) } func (*PresenceMFAChallengeRequest) ProtoMessage() {} func (*PresenceMFAChallengeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{145} + return fileDescriptor_ce8bd90b12161215, []int{143} } func (m *PresenceMFAChallengeRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10066,7 +9871,7 @@ func (m *PresenceMFAChallengeSend) Reset() { *m = PresenceMFAChallengeSe func (m *PresenceMFAChallengeSend) String() string { return proto.CompactTextString(m) } func (*PresenceMFAChallengeSend) ProtoMessage() {} func (*PresenceMFAChallengeSend) Descriptor() ([]byte, []int) { - return fileDescriptor_ce8bd90b12161215, []int{146} + return fileDescriptor_ce8bd90b12161215, []int{144} } func (m *PresenceMFAChallengeSend) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -10143,7 +9948,6 @@ func (*PresenceMFAChallengeSend) XXX_OneofWrappers() []interface{} { func init() { proto.RegisterEnum("proto.Operation", Operation_name, Operation_value) proto.RegisterEnum("proto.DeviceType", DeviceType_name, DeviceType_value) - proto.RegisterEnum("proto.DeviceUsage", DeviceUsage_name, DeviceUsage_value) proto.RegisterEnum("proto.Order", Order_name, Order_value) proto.RegisterEnum("proto.UserCertsRequest_CertUsage", UserCertsRequest_CertUsage_name, UserCertsRequest_CertUsage_value) proto.RegisterType((*Event)(nil), "proto.Event") @@ -10278,8 +10082,6 @@ func init() { proto.RegisterType((*GetAccountRecoveryTokenRequest)(nil), "proto.GetAccountRecoveryTokenRequest") proto.RegisterType((*GetAccountRecoveryCodesRequest)(nil), "proto.GetAccountRecoveryCodesRequest") proto.RegisterType((*UserCredentials)(nil), "proto.UserCredentials") - proto.RegisterType((*ContextUser)(nil), "proto.ContextUser") - proto.RegisterType((*Passwordless)(nil), "proto.Passwordless") proto.RegisterType((*CreateAuthenticateChallengeRequest)(nil), "proto.CreateAuthenticateChallengeRequest") proto.RegisterType((*CreatePrivilegeTokenRequest)(nil), "proto.CreatePrivilegeTokenRequest") proto.RegisterType((*CreateRegisterChallengeRequest)(nil), "proto.CreateRegisterChallengeRequest") @@ -10301,598 +10103,586 @@ func init() { func init() { proto.RegisterFile("authservice.proto", fileDescriptor_ce8bd90b12161215) } var fileDescriptor_ce8bd90b12161215 = []byte{ - // 9444 bytes of a gzipped FileDescriptorProto + // 9254 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x7d, 0x5b, 0x6c, 0x1b, 0xc9, - 0x96, 0x98, 0x49, 0xbd, 0xc8, 0xa3, 0x87, 0xe9, 0x92, 0x64, 0xd1, 0xb4, 0x2c, 0x7a, 0xda, 0x33, - 0xbe, 0x9e, 0xd9, 0x59, 0xdb, 0x23, 0xcd, 0xfb, 0xb9, 0x24, 0x25, 0x4b, 0xb2, 0x65, 0x59, 0xd3, - 0x94, 0xe8, 0xc9, 0xdc, 0xd9, 0x70, 0x5a, 0x64, 0x59, 0xea, 0x88, 0x62, 0xf3, 0x76, 0x37, 0xed, - 0x31, 0x82, 0x04, 0x79, 0x6d, 0x12, 0x04, 0x08, 0xb0, 0x01, 0xb2, 0x48, 0x82, 0x7c, 0x64, 0xb1, - 0xc9, 0x4f, 0x12, 0xec, 0x4f, 0x10, 0xe4, 0x33, 0x5f, 0x01, 0x72, 0x13, 0x20, 0x48, 0x7e, 0x16, - 0x01, 0xf6, 0x83, 0x7b, 0x73, 0x3f, 0xf5, 0x1b, 0x24, 0x40, 0xee, 0x57, 0x50, 0xcf, 0xae, 0xea, - 0xee, 0x22, 0x25, 0xdb, 0xd9, 0xfc, 0xd8, 0xea, 0x53, 0x75, 0x4e, 0x55, 0x9d, 0x3a, 0x75, 0xea, - 0xd4, 0xa9, 0x53, 0x87, 0x70, 0xc5, 0xe9, 0x87, 0xc7, 0x01, 0xf6, 0x9f, 0xbb, 0x2d, 0x7c, 0xb7, - 0xe7, 0x7b, 0xa1, 0x87, 0x26, 0xe8, 0x7f, 0xa5, 0x85, 0x23, 0xef, 0xc8, 0xa3, 0x7f, 0xde, 0x23, - 0x7f, 0xb1, 0xc2, 0xd2, 0xf5, 0x23, 0xcf, 0x3b, 0xea, 0xe0, 0x7b, 0xf4, 0xeb, 0xb0, 0xff, 0xec, - 0x1e, 0x3e, 0xed, 0x85, 0x2f, 0x79, 0x61, 0x39, 0x5e, 0x18, 0xba, 0xa7, 0x38, 0x08, 0x9d, 0xd3, + 0x96, 0x98, 0x49, 0xbd, 0xc8, 0xa3, 0x87, 0xa9, 0x92, 0x64, 0xd1, 0xb4, 0x2c, 0x7a, 0xda, 0x33, + 0xbe, 0x9e, 0xd9, 0x59, 0xdb, 0x23, 0xcd, 0xfb, 0x79, 0x49, 0x4a, 0x96, 0x64, 0xcb, 0xb2, 0xa6, + 0x29, 0xd3, 0x93, 0x7b, 0x67, 0xc3, 0x69, 0x91, 0x65, 0xa9, 0x23, 0x8a, 0xcd, 0xdb, 0xdd, 0xb4, + 0xc7, 0x08, 0x12, 0xe4, 0xb5, 0x49, 0x10, 0x24, 0xc0, 0x06, 0xc8, 0x22, 0xc9, 0x57, 0x16, 0x9b, + 0x00, 0x41, 0x12, 0xec, 0x4f, 0x10, 0xe4, 0x33, 0xbf, 0xb9, 0x09, 0x10, 0x24, 0x3f, 0x8b, 0x00, + 0xfb, 0xc1, 0xdd, 0xdc, 0x4f, 0x7d, 0x05, 0x08, 0x12, 0x20, 0xf7, 0x2b, 0xa8, 0x67, 0x57, 0x75, + 0x77, 0x91, 0x92, 0xed, 0xdc, 0xfd, 0xb1, 0xd5, 0xa7, 0xea, 0x9c, 0xaa, 0x3a, 0x75, 0xea, 0xd4, + 0xa9, 0x53, 0xa7, 0x0e, 0x61, 0xde, 0xe9, 0x87, 0xc7, 0x01, 0xf6, 0x9f, 0xbb, 0x2d, 0x7c, 0xa7, + 0xe7, 0x7b, 0xa1, 0x87, 0x26, 0xe8, 0x7f, 0xa5, 0xc5, 0x23, 0xef, 0xc8, 0xa3, 0x7f, 0xde, 0x25, + 0x7f, 0xb1, 0xc2, 0xd2, 0xb5, 0x23, 0xcf, 0x3b, 0xea, 0xe0, 0xbb, 0xf4, 0xeb, 0xb0, 0xff, 0xec, + 0x2e, 0x3e, 0xed, 0x85, 0x2f, 0x79, 0x61, 0x39, 0x5e, 0x18, 0xba, 0xa7, 0x38, 0x08, 0x9d, 0xd3, 0x1e, 0xaf, 0x30, 0xdd, 0xc2, 0x7e, 0x18, 0xf0, 0x8f, 0x4f, 0x8e, 0xdc, 0xf0, 0xb8, 0x7f, 0x78, - 0xb7, 0xe5, 0x9d, 0xde, 0x3b, 0xf2, 0x9d, 0xe7, 0x6e, 0xe8, 0x84, 0xae, 0xd7, 0x75, 0x3a, 0xf7, - 0x42, 0xdc, 0xc1, 0x3d, 0xcf, 0x0f, 0xef, 0x39, 0x3d, 0xf7, 0x5e, 0xf8, 0xb2, 0x87, 0x03, 0xf6, + 0xa7, 0xe5, 0x9d, 0xde, 0x3d, 0xf2, 0x9d, 0xe7, 0x6e, 0xe8, 0x84, 0xae, 0xd7, 0x75, 0x3a, 0x77, + 0x43, 0xdc, 0xc1, 0x3d, 0xcf, 0x0f, 0xef, 0x3a, 0x3d, 0xf7, 0x6e, 0xf8, 0xb2, 0x87, 0x03, 0xf6, 0x2f, 0x47, 0xac, 0x5d, 0x04, 0xf1, 0x05, 0x3e, 0x24, 0x43, 0xec, 0xca, 0x3f, 0x5e, 0x89, 0x88, 0xef, 0xf4, 0x7a, 0xd8, 0x8f, 0xfe, 0xe0, 0x44, 0xbe, 0xb9, 0x08, 0x11, 0xfc, 0x1c, 0x77, 0x43, - 0xf1, 0x1f, 0x23, 0x60, 0xfd, 0xeb, 0x05, 0x98, 0xd8, 0x20, 0x00, 0xf4, 0x29, 0x8c, 0xef, 0xbf, - 0xec, 0xe1, 0x62, 0xe6, 0x66, 0xe6, 0xce, 0xdc, 0x6a, 0x81, 0x95, 0xdf, 0x7d, 0xd2, 0xc3, 0x3e, + 0xf1, 0x1f, 0x23, 0x60, 0xfd, 0x9b, 0x45, 0x98, 0xd8, 0x24, 0x00, 0xf4, 0x29, 0x8c, 0x1f, 0xbc, + 0xec, 0xe1, 0x62, 0xe6, 0x46, 0xe6, 0xf6, 0xdc, 0x5a, 0x81, 0x95, 0xdf, 0x79, 0xdc, 0xc3, 0x3e, 0x25, 0x59, 0x45, 0x67, 0x83, 0xf2, 0x1c, 0x21, 0xf4, 0xbe, 0x77, 0xea, 0x86, 0x94, 0xeb, 0x36, - 0xc5, 0x40, 0x4f, 0x61, 0xce, 0xc6, 0x81, 0xd7, 0xf7, 0x5b, 0x78, 0x0b, 0x3b, 0x6d, 0xec, 0x17, - 0xb3, 0x37, 0x33, 0x77, 0xa6, 0x57, 0x17, 0xef, 0x32, 0xa6, 0xe9, 0x85, 0xd5, 0xab, 0x67, 0x83, - 0x32, 0xf2, 0x39, 0x2c, 0x22, 0xb6, 0x75, 0xc9, 0x8e, 0x91, 0x41, 0x3f, 0xc0, 0x6c, 0x0d, 0xfb, - 0x61, 0xa5, 0x1f, 0x1e, 0x7b, 0xbe, 0x1b, 0xbe, 0x2c, 0x8e, 0x51, 0xba, 0x57, 0x39, 0x5d, 0xad, - 0xac, 0xb1, 0x5a, 0x5d, 0x3e, 0x1b, 0x94, 0x8b, 0x64, 0x82, 0x9b, 0x8e, 0x80, 0x6a, 0xe4, 0x75, - 0x62, 0xe8, 0x3b, 0x98, 0xa9, 0x13, 0x76, 0xb5, 0xf6, 0xbd, 0x13, 0xdc, 0x0d, 0x8a, 0xe3, 0x5a, - 0xa7, 0xd5, 0xa2, 0xc6, 0x6a, 0xf5, 0xfa, 0xd9, 0xa0, 0xbc, 0x14, 0x50, 0x58, 0x33, 0xa4, 0x40, - 0x8d, 0xb4, 0x46, 0x09, 0xfd, 0x08, 0x73, 0x7b, 0xbe, 0xf7, 0xdc, 0x0d, 0x5c, 0xaf, 0x4b, 0x41, - 0xc5, 0x09, 0x4a, 0x7b, 0x89, 0xd3, 0xd6, 0x0b, 0x1b, 0xab, 0xd5, 0x1b, 0x67, 0x83, 0xf2, 0xb5, - 0x9e, 0x80, 0xb2, 0x06, 0x74, 0xce, 0xe8, 0x28, 0x68, 0x1f, 0xa6, 0x6b, 0x9d, 0x7e, 0x10, 0x62, - 0x7f, 0xd7, 0x39, 0xc5, 0xc5, 0x49, 0x4a, 0x7e, 0x41, 0xf0, 0x25, 0x2a, 0x69, 0xac, 0x56, 0x4b, - 0x67, 0x83, 0xf2, 0xd5, 0x16, 0x03, 0x35, 0xbb, 0xce, 0xa9, 0xce, 0x72, 0x95, 0x0c, 0xfa, 0x04, - 0xc6, 0x0f, 0x02, 0xec, 0x17, 0x73, 0x94, 0xdc, 0x2c, 0x27, 0x47, 0x40, 0x8d, 0x55, 0x36, 0xff, - 0xfd, 0x00, 0xfb, 0x1a, 0x3e, 0x45, 0x20, 0x88, 0xb6, 0xd7, 0xc1, 0xc5, 0xbc, 0x86, 0x48, 0x40, - 0x8d, 0x8f, 0x18, 0xa2, 0xef, 0x75, 0xf4, 0x86, 0x29, 0x02, 0xda, 0x86, 0x3c, 0x69, 0x39, 0xe8, - 0x39, 0x2d, 0x5c, 0x04, 0x8a, 0x5d, 0xe0, 0xd8, 0x12, 0x5e, 0x5d, 0x3a, 0x1b, 0x94, 0xe7, 0xbb, - 0xe2, 0x53, 0xa3, 0x12, 0x61, 0xa3, 0x6f, 0x60, 0xb2, 0x8e, 0xfd, 0xe7, 0xd8, 0x2f, 0x4e, 0x53, - 0x3a, 0x97, 0xc5, 0x44, 0x52, 0x60, 0x63, 0xb5, 0xba, 0x70, 0x36, 0x28, 0x17, 0x02, 0xfa, 0xa5, - 0xd1, 0xe0, 0x68, 0x44, 0xda, 0x6c, 0xfc, 0x1c, 0xfb, 0x01, 0xde, 0xef, 0x77, 0xbb, 0xb8, 0x53, - 0x9c, 0xd1, 0xa4, 0x4d, 0x2b, 0x13, 0xd2, 0xe6, 0x33, 0x60, 0x33, 0xa4, 0x50, 0x5d, 0xda, 0x34, - 0x04, 0x74, 0x0c, 0x05, 0xf6, 0x57, 0xcd, 0xeb, 0x76, 0x71, 0x8b, 0x2c, 0xa9, 0xe2, 0x2c, 0x6d, - 0xe0, 0x1a, 0x6f, 0x20, 0x5e, 0xdc, 0x58, 0xad, 0x96, 0xcf, 0x06, 0xe5, 0xeb, 0x8c, 0x76, 0xb3, - 0x25, 0x0b, 0xb4, 0x66, 0x12, 0x54, 0xc9, 0x38, 0x2a, 0xad, 0x16, 0x0e, 0x02, 0x1b, 0xff, 0xa2, - 0x8f, 0x83, 0xb0, 0x38, 0xa7, 0x8d, 0x43, 0x2b, 0x6b, 0xac, 0xb1, 0x71, 0x38, 0x14, 0xd8, 0xf4, - 0x19, 0x54, 0x1f, 0x87, 0x86, 0x80, 0xf6, 0x00, 0x2a, 0xbd, 0x5e, 0x1d, 0x07, 0x44, 0x18, 0x8b, - 0x97, 0x29, 0xe9, 0x79, 0x4e, 0xfa, 0x29, 0x3e, 0xe4, 0x05, 0x8d, 0xd5, 0xea, 0xb5, 0xb3, 0x41, - 0x79, 0xd1, 0xe9, 0xf5, 0x9a, 0x01, 0x03, 0x69, 0x44, 0x15, 0x1a, 0x8c, 0xef, 0xa7, 0x5e, 0x88, - 0xb9, 0x28, 0x16, 0x0b, 0x31, 0xbe, 0x2b, 0x65, 0xa2, 0xbf, 0x3e, 0x05, 0x36, 0xb9, 0x58, 0xc7, - 0xf9, 0xae, 0x20, 0x90, 0xb5, 0xb8, 0xee, 0x84, 0xce, 0xa1, 0x13, 0x60, 0x2e, 0x1e, 0x57, 0xb4, - 0xb5, 0xa8, 0x17, 0x36, 0xd6, 0xd8, 0x5a, 0x6c, 0x73, 0x68, 0x33, 0x45, 0x5e, 0x62, 0xf4, 0x08, - 0x47, 0xa2, 0x81, 0x17, 0xd1, 0x08, 0x8e, 0xbc, 0xc0, 0x87, 0xe9, 0x1c, 0x89, 0xaa, 0xa2, 0x2d, - 0xc8, 0x3d, 0xc5, 0x87, 0x4c, 0x73, 0xcc, 0x53, 0x7a, 0x57, 0x22, 0x7a, 0x4c, 0x67, 0xac, 0xb1, - 0x55, 0x41, 0xa8, 0x25, 0xb5, 0x85, 0xc4, 0x46, 0xbf, 0x97, 0x81, 0x25, 0xb1, 0xc2, 0x71, 0xf8, - 0xc2, 0xf3, 0x4f, 0xdc, 0xee, 0x51, 0xcd, 0xeb, 0x3e, 0x73, 0x8f, 0x8a, 0x0b, 0x94, 0xf2, 0xcd, - 0x98, 0xd2, 0x88, 0xd5, 0x6a, 0xac, 0x56, 0x7f, 0x76, 0x36, 0x28, 0xdf, 0x92, 0x0a, 0x44, 0x96, - 0x13, 0x81, 0x7c, 0xe6, 0x1e, 0x69, 0x0d, 0x9b, 0xda, 0x42, 0x7f, 0x3d, 0x03, 0x57, 0xf9, 0xe8, - 0x6c, 0xdc, 0xf2, 0xfc, 0x76, 0xd4, 0x8d, 0x45, 0xda, 0x8d, 0xb2, 0x5c, 0xad, 0x69, 0x95, 0x1a, - 0xab, 0xd5, 0xdb, 0x67, 0x83, 0xb2, 0xc5, 0x19, 0xd7, 0xf4, 0x45, 0x71, 0x5a, 0x27, 0x0c, 0x0d, - 0x11, 0x49, 0x20, 0xca, 0x7f, 0xcf, 0xc7, 0xcf, 0xb0, 0x8f, 0xbb, 0x2d, 0x5c, 0xbc, 0xaa, 0x49, - 0x82, 0x5e, 0x28, 0xb4, 0x32, 0xd9, 0x4a, 0x9a, 0x3d, 0x09, 0xd6, 0x25, 0x41, 0x47, 0x41, 0xbf, - 0x00, 0xc4, 0x19, 0x50, 0xe9, 0xb7, 0xdd, 0x90, 0x0f, 0x70, 0x89, 0xb6, 0x72, 0x5d, 0xe7, 0xb3, - 0x52, 0xa1, 0xb1, 0x5a, 0xb5, 0xce, 0x06, 0xe5, 0x15, 0xc1, 0x62, 0x87, 0x14, 0xa5, 0x0d, 0x2c, - 0x85, 0x38, 0xd1, 0xbc, 0x3b, 0x5e, 0xeb, 0xa4, 0x58, 0xd4, 0x34, 0x2f, 0x01, 0x09, 0x95, 0xdd, - 0xf1, 0x5a, 0x27, 0xba, 0xe6, 0x25, 0xa5, 0x28, 0x84, 0x79, 0x3e, 0x4b, 0x36, 0x0e, 0x42, 0xdf, - 0xa5, 0xba, 0x23, 0x28, 0x5e, 0xa3, 0x74, 0x96, 0x85, 0x0e, 0x4e, 0xd6, 0x68, 0x7c, 0xc8, 0x7a, - 0xcb, 0x05, 0xa1, 0xe9, 0x2b, 0x65, 0x5a, 0x33, 0x69, 0xe4, 0xd1, 0x5f, 0x81, 0xc5, 0xa7, 0x6e, - 0xb7, 0xed, 0xbd, 0x08, 0xd6, 0x71, 0x70, 0x12, 0x7a, 0xbd, 0x3a, 0xb3, 0xfc, 0x8a, 0x25, 0xda, - 0xee, 0x8a, 0x10, 0xf3, 0xb4, 0x3a, 0x8d, 0xb5, 0xea, 0x3b, 0x67, 0x83, 0xf2, 0x5b, 0x2f, 0x58, - 0x61, 0xb3, 0xcd, 0x4a, 0x9b, 0xdc, 0x78, 0xd4, 0x1a, 0x4f, 0x6f, 0x85, 0x88, 0x80, 0x5e, 0x50, - 0xbc, 0xae, 0x89, 0x80, 0x5e, 0x28, 0x94, 0x41, 0xac, 0x41, 0x5d, 0x04, 0x74, 0x14, 0xb4, 0x09, - 0x39, 0xa1, 0x1e, 0x8a, 0xcb, 0xda, 0xd2, 0x15, 0xe0, 0xc6, 0x1a, 0xb3, 0x80, 0x84, 0x8a, 0xd1, - 0x57, 0xae, 0xa8, 0x85, 0x76, 0x20, 0x4f, 0x75, 0x24, 0x55, 0x59, 0x37, 0x28, 0x25, 0x24, 0x04, - 0x55, 0xc0, 0x1b, 0x6b, 0xd5, 0xe2, 0xd9, 0xa0, 0xbc, 0xc0, 0xb4, 0x6c, 0x42, 0x51, 0x45, 0x04, - 0xd0, 0x1a, 0x8c, 0x55, 0x7a, 0xbd, 0xe2, 0x0a, 0xa5, 0x33, 0x13, 0xd1, 0x69, 0xac, 0x55, 0xaf, - 0x9c, 0x0d, 0xca, 0xb3, 0x4e, 0x4f, 0x1f, 0x16, 0xa9, 0x5d, 0x05, 0xc8, 0x09, 0x83, 0xec, 0xe1, - 0x78, 0x6e, 0xaa, 0x90, 0xb3, 0xb6, 0x60, 0xe2, 0xa9, 0x13, 0xb6, 0x8e, 0xd1, 0x37, 0x30, 0xf1, - 0xc8, 0xed, 0xb6, 0x83, 0x62, 0xe6, 0xe6, 0x18, 0xdd, 0xb3, 0x99, 0xb5, 0x48, 0x0b, 0x49, 0x41, - 0x75, 0xe9, 0x97, 0x83, 0xf2, 0xa5, 0xb3, 0x41, 0xf9, 0xf2, 0x09, 0xa9, 0xa6, 0x98, 0x8c, 0x0c, - 0xcf, 0xfa, 0xb7, 0x59, 0xc8, 0xcb, 0xda, 0x68, 0x19, 0xc6, 0xc9, 0xff, 0xd4, 0xf6, 0xcc, 0x57, - 0x73, 0x67, 0x83, 0xf2, 0x38, 0xc1, 0xb3, 0x29, 0x14, 0xad, 0xc2, 0xf4, 0x8e, 0xe7, 0xb4, 0xeb, - 0xb8, 0xe5, 0xe3, 0x30, 0xa0, 0xc6, 0x65, 0xae, 0x5a, 0x38, 0x1b, 0x94, 0x67, 0x3a, 0x9e, 0xd3, - 0x6e, 0x06, 0x0c, 0x6e, 0xab, 0x95, 0x08, 0x45, 0x6a, 0x19, 0x8d, 0x45, 0x14, 0x89, 0x05, 0x61, - 0x53, 0x28, 0x7a, 0x08, 0x93, 0x0f, 0xdc, 0x0e, 0xd9, 0x6b, 0xc6, 0x69, 0xff, 0x97, 0xe3, 0xfd, - 0xbf, 0xcb, 0x8a, 0x37, 0xba, 0xa1, 0xff, 0x92, 0x19, 0x0e, 0xcf, 0x28, 0x40, 0x19, 0x08, 0xa7, - 0x80, 0xee, 0xc3, 0x54, 0xbd, 0x7f, 0x48, 0xbb, 0x3f, 0x41, 0x1b, 0xa3, 0xb3, 0x1b, 0xf4, 0x0f, - 0x9b, 0x64, 0x08, 0x0a, 0x82, 0xa8, 0x56, 0xfa, 0x0c, 0xa6, 0x15, 0xf2, 0xa8, 0x00, 0x63, 0x27, - 0xf8, 0x25, 0x1b, 0xbb, 0x4d, 0xfe, 0x44, 0x0b, 0x30, 0xf1, 0xdc, 0xe9, 0xf4, 0x31, 0x1d, 0x6a, - 0xde, 0x66, 0x1f, 0x9f, 0x67, 0x3f, 0xcd, 0x58, 0xff, 0x75, 0x1c, 0x0a, 0x5b, 0x5e, 0x10, 0x12, - 0x4b, 0x56, 0x6e, 0xc9, 0xb7, 0x60, 0x92, 0xc0, 0xb6, 0xd7, 0x39, 0xff, 0xa6, 0xcf, 0x06, 0xe5, - 0xa9, 0x63, 0x2f, 0x08, 0x9b, 0x6e, 0xdb, 0xe6, 0x45, 0xe8, 0x5d, 0xc8, 0xed, 0x7a, 0x6d, 0x4c, - 0x99, 0x42, 0xc9, 0x56, 0x67, 0xcf, 0x06, 0xe5, 0x7c, 0xd7, 0x6b, 0x63, 0x6a, 0x15, 0xda, 0xb2, - 0x18, 0x35, 0xb8, 0x35, 0xc7, 0x78, 0x57, 0x25, 0xbc, 0x23, 0xe6, 0xdb, 0x6f, 0x06, 0xe5, 0x8f, - 0x2f, 0x70, 0xdc, 0xb8, 0x5b, 0x7f, 0x19, 0x84, 0xf8, 0x94, 0x50, 0xe2, 0xc6, 0xde, 0x53, 0x58, - 0xa8, 0xb4, 0xdb, 0x2e, 0xc3, 0xd8, 0xf3, 0xdd, 0x6e, 0xcb, 0xed, 0x39, 0x9d, 0x80, 0xce, 0x41, - 0xbe, 0x7a, 0xeb, 0x6c, 0x50, 0x2e, 0x3b, 0xb2, 0xbc, 0xd9, 0x93, 0x15, 0x14, 0x1e, 0xa6, 0x12, - 0x40, 0x6b, 0x90, 0x5b, 0xdf, 0xad, 0x53, 0x53, 0xb0, 0x38, 0x41, 0x89, 0xd1, 0xcd, 0xb1, 0xdd, - 0x0d, 0xe8, 0xd0, 0x54, 0x02, 0xb2, 0x22, 0xfa, 0x18, 0x66, 0xf6, 0xfa, 0x87, 0x1d, 0xb7, 0xb5, - 0xbf, 0x53, 0x7f, 0x84, 0x5f, 0x52, 0x1b, 0x7a, 0x86, 0xa9, 0xcc, 0x1e, 0x85, 0x37, 0xc3, 0x4e, - 0xd0, 0x3c, 0xc1, 0x2f, 0x6d, 0xad, 0x5e, 0x84, 0x57, 0xaf, 0x6f, 0x11, 0xbc, 0xa9, 0x04, 0x5e, - 0x10, 0x1c, 0xab, 0x78, 0xac, 0x1e, 0xba, 0x07, 0xc0, 0x2c, 0x93, 0x4a, 0xbb, 0xcd, 0x4c, 0xec, - 0x7c, 0xf5, 0xf2, 0xd9, 0xa0, 0x3c, 0xcd, 0x6d, 0x19, 0xa7, 0xdd, 0xf6, 0x6d, 0xa5, 0x0a, 0xaa, - 0x41, 0xce, 0xf6, 0x18, 0x83, 0xb9, 0x61, 0x7d, 0x59, 0x1a, 0xd6, 0x0c, 0xcc, 0x8f, 0x52, 0xfc, - 0x4b, 0x1d, 0xa5, 0xa8, 0x81, 0xca, 0x30, 0xb5, 0xeb, 0xd5, 0x9c, 0xd6, 0x31, 0x33, 0xaf, 0x73, - 0xd5, 0x89, 0xb3, 0x41, 0x39, 0xf3, 0xdb, 0xb6, 0x80, 0x5a, 0xff, 0x22, 0x07, 0x05, 0x62, 0xc3, - 0x6b, 0x12, 0xf5, 0x3e, 0xe4, 0x59, 0xdf, 0x1f, 0x71, 0xc1, 0x9c, 0xa9, 0xce, 0x9d, 0x0d, 0xca, - 0xc0, 0x07, 0x48, 0x06, 0x17, 0x55, 0x40, 0x77, 0x20, 0x47, 0x28, 0x74, 0x23, 0xd1, 0x9a, 0x39, - 0x1b, 0x94, 0x73, 0x7d, 0x0e, 0xb3, 0x65, 0x29, 0xaa, 0xc3, 0xd4, 0xc6, 0x4f, 0x3d, 0xd7, 0xc7, - 0x01, 0x3f, 0xca, 0x95, 0xee, 0xb2, 0x13, 0xfb, 0x5d, 0x71, 0x62, 0xbf, 0xbb, 0x2f, 0x4e, 0xec, - 0xd5, 0x1b, 0x5c, 0x85, 0x5c, 0xc1, 0x0c, 0x25, 0x1a, 0xdf, 0xef, 0xff, 0x59, 0x39, 0x63, 0x0b, - 0x4a, 0xe8, 0x7d, 0x98, 0x7c, 0xe0, 0xf9, 0xa7, 0x4e, 0x48, 0x4f, 0x70, 0x79, 0xbe, 0x5c, 0x29, - 0x44, 0x5b, 0xae, 0x14, 0x82, 0x1e, 0xc0, 0x9c, 0xed, 0xf5, 0x43, 0xbc, 0xef, 0x09, 0x73, 0x93, - 0xad, 0xda, 0x95, 0xb3, 0x41, 0xb9, 0xe4, 0x93, 0x92, 0x66, 0xe8, 0x25, 0x0d, 0x4b, 0x3b, 0x86, - 0x85, 0x36, 0x60, 0x4e, 0x33, 0x8c, 0x83, 0xe2, 0x24, 0x95, 0x3c, 0x66, 0x34, 0x68, 0xe6, 0xb4, - 0x2a, 0x7f, 0x31, 0x24, 0xb4, 0x0b, 0x57, 0x1e, 0xf5, 0x0f, 0xb1, 0xdf, 0xc5, 0x21, 0x0e, 0x44, - 0x8f, 0xa6, 0x68, 0x8f, 0x6e, 0x9e, 0x0d, 0xca, 0xcb, 0x27, 0xb2, 0x30, 0xa5, 0x4f, 0x49, 0x54, - 0x84, 0xe1, 0x32, 0xef, 0xa8, 0xdc, 0x86, 0x72, 0xdc, 0x9c, 0x66, 0x2a, 0x2e, 0x56, 0x5a, 0xbd, - 0xc5, 0xb9, 0x7c, 0x5d, 0x8e, 0x3d, 0xb9, 0x31, 0xd9, 0x71, 0x9a, 0x64, 0xc5, 0x49, 0x6d, 0x92, - 0xa7, 0xbd, 0x65, 0x87, 0x34, 0xa1, 0x4d, 0x54, 0x59, 0x94, 0x7a, 0x65, 0x07, 0x26, 0x0e, 0x02, - 0xe7, 0x88, 0x49, 0xe2, 0xdc, 0xea, 0x5b, 0xbc, 0x47, 0x71, 0xe9, 0xa3, 0xe7, 0x7a, 0x5a, 0xb1, - 0x3a, 0x4f, 0x76, 0x90, 0x3e, 0xf9, 0x53, 0xdd, 0x41, 0x68, 0x19, 0xfa, 0x16, 0x80, 0xf7, 0x8a, - 0xec, 0x6c, 0xd3, 0x7c, 0xaf, 0xd5, 0x06, 0x49, 0x36, 0xb1, 0x15, 0x3e, 0xbe, 0xab, 0x72, 0x7c, - 0xda, 0x5e, 0x67, 0x2b, 0x44, 0xd0, 0x37, 0x30, 0x43, 0xd5, 0x95, 0x98, 0xd1, 0x19, 0x3a, 0xa3, - 0xf4, 0xe8, 0x4f, 0x14, 0x60, 0xda, 0x7c, 0x6a, 0x08, 0xe8, 0xaf, 0xc2, 0x22, 0x27, 0x17, 0x33, - 0x33, 0x66, 0xb9, 0x59, 0xa5, 0x75, 0x4f, 0xaf, 0x53, 0x7d, 0x8f, 0xf7, 0xd4, 0x92, 0x3d, 0x35, - 0x1a, 0x1e, 0x76, 0x7a, 0x33, 0xd6, 0x77, 0x90, 0x97, 0xcc, 0x43, 0x53, 0x30, 0x56, 0xe9, 0x74, - 0x0a, 0x97, 0xc8, 0x1f, 0xf5, 0xfa, 0x56, 0x21, 0x83, 0xe6, 0x00, 0x22, 0x89, 0x29, 0x64, 0xd1, - 0x4c, 0x64, 0xac, 0x14, 0xc6, 0x68, 0xfd, 0x5e, 0xaf, 0x30, 0x8e, 0x50, 0xdc, 0x4a, 0x2a, 0x4c, - 0x58, 0xff, 0x3d, 0x93, 0x10, 0x2c, 0xb2, 0x2f, 0x73, 0xc3, 0x8a, 0xca, 0x01, 0xdb, 0x7c, 0xe8, - 0xbe, 0xcc, 0x4d, 0x32, 0xb6, 0xb1, 0xa8, 0x95, 0x88, 0xae, 0xd8, 0x23, 0x3c, 0x68, 0x79, 0x1d, - 0x55, 0x57, 0xf4, 0x38, 0xcc, 0x96, 0xa5, 0x68, 0x55, 0xd1, 0x2a, 0x63, 0xd1, 0xc6, 0x2a, 0xb4, - 0x8a, 0x2a, 0x61, 0x52, 0xbf, 0xac, 0x2a, 0xd6, 0xd7, 0x78, 0x84, 0x93, 0x22, 0xd1, 0xb2, 0x9e, - 0xd5, 0x37, 0xcc, 0x19, 0xfa, 0x22, 0x61, 0x2c, 0xb2, 0x11, 0x52, 0xa1, 0x8c, 0x4d, 0x4d, 0xc2, - 0x0e, 0x2c, 0xc3, 0xc4, 0x8e, 0x77, 0xe4, 0x76, 0xf9, 0x20, 0xf3, 0x67, 0x83, 0xf2, 0x44, 0x87, - 0x00, 0x6c, 0x06, 0xb7, 0xfe, 0x4f, 0x46, 0x95, 0x5f, 0x69, 0xaf, 0x64, 0x52, 0xed, 0x95, 0xf7, - 0x21, 0xcf, 0x0f, 0x35, 0xdb, 0xeb, 0x9c, 0x22, 0xd5, 0xc7, 0xe2, 0x3c, 0xe4, 0xb6, 0xed, 0xa8, - 0x02, 0xd9, 0x69, 0x98, 0x72, 0xa6, 0x3b, 0xcd, 0x58, 0xb4, 0xd3, 0x70, 0xf5, 0xcd, 0x76, 0x9a, - 0xa8, 0x0a, 0x99, 0x48, 0xd5, 0x9b, 0x34, 0x1e, 0x4d, 0xa4, 0xea, 0x37, 0xd2, 0x7d, 0x45, 0x9f, - 0x03, 0x54, 0x9e, 0xd6, 0x89, 0xf4, 0x57, 0xec, 0x5d, 0xae, 0x43, 0xa9, 0xab, 0xc9, 0x79, 0x11, - 0x34, 0xe9, 0x6a, 0x71, 0x7c, 0x75, 0x4b, 0x52, 0x6a, 0x5b, 0x1d, 0x98, 0xdb, 0xc4, 0x21, 0x99, - 0x35, 0xb1, 0xe1, 0x0c, 0x1f, 0xfe, 0x97, 0x30, 0xfd, 0xd4, 0x0d, 0x8f, 0x75, 0x03, 0x90, 0x36, - 0xf6, 0xc2, 0x0d, 0x8f, 0x85, 0x01, 0xa8, 0x34, 0xa6, 0x56, 0xb7, 0x36, 0xe0, 0x32, 0x6f, 0x4d, - 0xee, 0x6f, 0xab, 0x3a, 0xc1, 0x4c, 0x64, 0x51, 0xaa, 0x04, 0x75, 0x32, 0x38, 0xae, 0xf0, 0x51, - 0x3d, 0xb1, 0x05, 0x30, 0x6b, 0xd8, 0xe4, 0x69, 0xa1, 0x82, 0x13, 0xdb, 0x1a, 0xe2, 0x1b, 0x82, - 0x75, 0x00, 0xb3, 0x7b, 0x9d, 0xfe, 0x91, 0xdb, 0x25, 0x02, 0x5a, 0xc7, 0xbf, 0x40, 0xeb, 0x00, - 0x11, 0x80, 0xb7, 0x20, 0xdc, 0x0b, 0x51, 0x41, 0x63, 0x8d, 0x4f, 0x31, 0x85, 0x50, 0x1d, 0x6e, - 0x2b, 0x78, 0xd6, 0xdf, 0x1b, 0x03, 0xc4, 0xdb, 0xa8, 0x87, 0x4e, 0x88, 0xeb, 0x38, 0x24, 0xdb, - 0xc5, 0x55, 0xc8, 0x4a, 0xb3, 0x71, 0xf2, 0x6c, 0x50, 0xce, 0xba, 0x6d, 0x3b, 0xbb, 0xbd, 0x8e, - 0x3e, 0x84, 0x09, 0x5a, 0x8d, 0xf2, 0x7a, 0x4e, 0xb6, 0xa7, 0x52, 0x60, 0x32, 0x1d, 0x90, 0x3f, - 0x6d, 0x56, 0x19, 0x7d, 0x04, 0xf9, 0x75, 0xdc, 0xc1, 0x47, 0x4e, 0xe8, 0x09, 0xb9, 0x63, 0x86, - 0x98, 0x00, 0x2a, 0x53, 0x14, 0xd5, 0x24, 0x1b, 0xb8, 0x8d, 0x9d, 0xc0, 0xeb, 0xaa, 0x1b, 0xb8, - 0x4f, 0x21, 0xea, 0x06, 0xce, 0xea, 0xa0, 0x3f, 0xc8, 0xc0, 0x74, 0xa5, 0xdb, 0xe5, 0x06, 0x4e, - 0xc0, 0x5d, 0xab, 0x8b, 0x77, 0xa5, 0x67, 0x7c, 0xc7, 0x39, 0xc4, 0x9d, 0x06, 0x31, 0x99, 0x83, - 0xea, 0x0f, 0x44, 0xa7, 0xfe, 0xe9, 0xa0, 0xfc, 0xc5, 0xab, 0x38, 0xdb, 0xef, 0xee, 0xfb, 0x8e, - 0x1b, 0x06, 0xd4, 0x8f, 0x15, 0x35, 0xa8, 0x8a, 0x99, 0xd2, 0x0f, 0xf4, 0x2e, 0x4c, 0x10, 0xf9, - 0x16, 0x76, 0x00, 0x9d, 0x6c, 0xb2, 0x0e, 0xb4, 0xc3, 0x0f, 0xad, 0x61, 0xdd, 0x82, 0x3c, 0xe7, - 0xe4, 0xf6, 0xba, 0x69, 0x0a, 0xac, 0x75, 0xb8, 0x41, 0xad, 0x38, 0x4c, 0x24, 0x97, 0x7a, 0x73, - 0xb8, 0x24, 0x46, 0x66, 0xff, 0x14, 0x05, 0x4b, 0x6c, 0x3a, 0x21, 0xd4, 0x1b, 0x64, 0x8b, 0x12, - 0xab, 0x06, 0xcb, 0x9b, 0x38, 0xb4, 0x71, 0x80, 0xc3, 0x3d, 0x27, 0x08, 0x5e, 0x78, 0x7e, 0x9b, - 0x16, 0x5d, 0x88, 0xc8, 0xdf, 0xca, 0x40, 0xb9, 0xe6, 0x63, 0x32, 0xd3, 0x46, 0x42, 0xc3, 0x57, - 0xf0, 0x32, 0xbf, 0x5c, 0xc8, 0x46, 0xa5, 0x84, 0xd7, 0xfc, 0x02, 0xe1, 0x1d, 0x18, 0xdb, 0xdf, - 0xdf, 0xa1, 0x12, 0x33, 0x46, 0x19, 0x37, 0x16, 0x86, 0x9d, 0xdf, 0x0c, 0xca, 0xb9, 0xf5, 0x3e, - 0xbb, 0x7c, 0xb0, 0x49, 0xb9, 0xf5, 0x0c, 0x16, 0x6d, 0xdc, 0xc5, 0x2f, 0x9c, 0xc3, 0x0e, 0xd6, - 0xcc, 0xd5, 0x32, 0x4c, 0x30, 0x67, 0x59, 0x62, 0x08, 0x0c, 0xae, 0xdb, 0xb3, 0xd9, 0x11, 0xf6, - 0xac, 0xf5, 0x87, 0x19, 0x28, 0xb0, 0xe1, 0x56, 0xbd, 0xf0, 0x7c, 0xe3, 0xe3, 0x23, 0xc8, 0x0e, - 0x1f, 0x01, 0xba, 0x1d, 0x71, 0x7b, 0x2c, 0xda, 0xfc, 0x68, 0x57, 0x89, 0x0e, 0x17, 0x85, 0x64, - 0x40, 0x4c, 0x96, 0xd8, 0xd1, 0x88, 0x0e, 0x88, 0xca, 0x92, 0x90, 0xa0, 0x3f, 0xce, 0xc2, 0x15, - 0xa5, 0x8b, 0x41, 0xcf, 0xeb, 0x06, 0x98, 0x9c, 0xf1, 0x88, 0xb0, 0x28, 0xfd, 0xa4, 0x67, 0x3c, - 0xb2, 0x65, 0x36, 0x23, 0x4b, 0x9c, 0x76, 0xf8, 0x5d, 0x72, 0xb8, 0xe8, 0x24, 0x8e, 0x83, 0x54, - 0x71, 0xb3, 0xaa, 0xa2, 0xf8, 0xdc, 0x9d, 0xbe, 0x07, 0x39, 0xfa, 0x27, 0x61, 0xc4, 0xb8, 0x99, - 0x11, 0xb2, 0x12, 0x72, 0x01, 0x1e, 0x7a, 0x6e, 0xf7, 0x31, 0x0e, 0x8f, 0x3d, 0x71, 0x78, 0xde, - 0x26, 0x4a, 0xec, 0x2f, 0x79, 0x6e, 0xb7, 0x79, 0x4a, 0xc1, 0x17, 0x3d, 0x74, 0x46, 0x04, 0x6d, - 0x85, 0xb8, 0x75, 0x1f, 0x0a, 0x44, 0xdf, 0x9c, 0x7f, 0x46, 0xad, 0x05, 0x40, 0x9b, 0x38, 0xac, - 0x7a, 0xda, 0xc6, 0x61, 0xcd, 0xc2, 0xf4, 0x9e, 0xdb, 0x3d, 0x12, 0x9f, 0xff, 0x2e, 0x0b, 0x33, - 0xec, 0x9b, 0xcf, 0x40, 0x6c, 0x27, 0xcd, 0x9c, 0x67, 0x27, 0xfd, 0x14, 0x66, 0xb9, 0x3b, 0x07, - 0xfb, 0xd4, 0x85, 0xcc, 0xe6, 0x83, 0x9e, 0x28, 0x99, 0x57, 0xa7, 0xf9, 0x9c, 0x95, 0xd8, 0x7a, - 0x45, 0xb4, 0x03, 0x73, 0x0c, 0xf0, 0x00, 0x3b, 0x61, 0x3f, 0x3a, 0x55, 0x5d, 0xe6, 0x76, 0xa6, - 0x00, 0x33, 0x65, 0xc4, 0x69, 0x3d, 0xe3, 0x40, 0x3b, 0x86, 0x8b, 0xbe, 0x81, 0xcb, 0x7b, 0xbe, - 0xf7, 0xd3, 0x4b, 0xc5, 0x76, 0x60, 0xfa, 0x78, 0x91, 0x1c, 0xc2, 0x7a, 0xa4, 0xa8, 0xa9, 0x5a, - 0x10, 0xf1, 0xda, 0x44, 0xa6, 0xb6, 0x83, 0xaa, 0xe7, 0xbb, 0xdd, 0x23, 0x3a, 0x9b, 0x39, 0x26, - 0x53, 0x6e, 0xd0, 0x3c, 0xa4, 0x40, 0x5b, 0x16, 0x5b, 0x7f, 0x34, 0x06, 0x39, 0xd9, 0xf0, 0x5d, - 0xd5, 0x2c, 0xe5, 0x9b, 0x31, 0x5d, 0x9e, 0xd1, 0xe1, 0xc7, 0x56, 0x6a, 0xa0, 0x6b, 0xcc, 0x99, - 0xc5, 0xcc, 0x80, 0x29, 0x22, 0x63, 0x4e, 0xaf, 0x47, 0x5d, 0x56, 0x44, 0x99, 0xae, 0x57, 0x29, - 0x17, 0x72, 0x4c, 0x99, 0xb6, 0x0f, 0xed, 0xec, 0x7a, 0x95, 0xcc, 0xf5, 0x93, 0xed, 0xf5, 0x1a, - 0x1d, 0x50, 0x8e, 0xcd, 0xb5, 0xe7, 0xb6, 0x5b, 0x36, 0x85, 0x92, 0xd2, 0x7a, 0xe5, 0xf1, 0x0e, - 0xef, 0x34, 0x2d, 0x0d, 0x9c, 0xd3, 0x8e, 0x4d, 0xa1, 0xc4, 0x0e, 0x64, 0x7b, 0x74, 0xcd, 0xeb, - 0x86, 0xbe, 0xd7, 0x09, 0xa8, 0xab, 0x20, 0xa7, 0x6d, 0xe7, 0x2d, 0x5e, 0x64, 0xc7, 0xaa, 0xa2, - 0xa7, 0xb0, 0x54, 0x69, 0x3f, 0x77, 0xba, 0x2d, 0xdc, 0x66, 0x25, 0x4f, 0x3d, 0xff, 0xe4, 0x59, - 0xc7, 0x7b, 0x11, 0xd0, 0x53, 0x5e, 0x8e, 0x9f, 0x17, 0x79, 0x95, 0x26, 0x27, 0xf7, 0x42, 0x54, - 0xb2, 0x4d, 0xd8, 0x44, 0x45, 0xd4, 0x3a, 0x5e, 0xbf, 0x4d, 0x8f, 0x77, 0x39, 0xa6, 0x22, 0x5a, - 0x04, 0x60, 0x33, 0x38, 0xe1, 0xd2, 0x56, 0xfd, 0x31, 0x3d, 0x9d, 0x71, 0x2e, 0x1d, 0x07, 0xa7, - 0x36, 0x81, 0xa1, 0x77, 0x60, 0x4a, 0x98, 0xb4, 0xcc, 0x29, 0x40, 0x3d, 0x46, 0xc2, 0x94, 0x15, - 0x65, 0xd6, 0x07, 0x70, 0x85, 0x2d, 0x9a, 0x73, 0x5b, 0x6a, 0xd6, 0x1e, 0x40, 0x1d, 0x9f, 0x3a, - 0xbd, 0x63, 0x8f, 0x4c, 0x6c, 0x55, 0xfd, 0xe2, 0xa6, 0x0b, 0x92, 0x8e, 0x7e, 0x5e, 0xd0, 0x58, - 0x13, 0xb6, 0xac, 0xa8, 0x69, 0x2b, 0x58, 0xd6, 0x7f, 0xc9, 0x02, 0xa2, 0x0e, 0xef, 0x7a, 0xe8, - 0x63, 0xe7, 0x54, 0x74, 0xe3, 0x33, 0x98, 0x61, 0xfa, 0x8f, 0x81, 0x69, 0x77, 0x88, 0x5d, 0xc4, - 0x04, 0x5f, 0x2d, 0xda, 0xba, 0x64, 0x6b, 0x55, 0x09, 0xaa, 0x8d, 0x83, 0xfe, 0xa9, 0x40, 0xcd, - 0x6a, 0xa8, 0x6a, 0x11, 0x41, 0x55, 0xbf, 0xd1, 0x37, 0x30, 0x57, 0xf3, 0x4e, 0x7b, 0x84, 0x27, - 0x1c, 0x79, 0x8c, 0x5b, 0x1f, 0xbc, 0x5d, 0xad, 0x70, 0xeb, 0x92, 0x1d, 0xab, 0x8e, 0x76, 0x61, - 0xfe, 0x41, 0xa7, 0x1f, 0x1c, 0x57, 0xba, 0xed, 0x5a, 0xc7, 0x0b, 0x04, 0x95, 0x71, 0xee, 0x0c, - 0xe1, 0xcb, 0x36, 0x59, 0x63, 0xeb, 0x92, 0x9d, 0x86, 0x88, 0xde, 0xe1, 0xb7, 0xf7, 0xdc, 0x0a, - 0x9a, 0xbd, 0xcb, 0x2f, 0xf7, 0x9f, 0x74, 0xf1, 0x93, 0x67, 0x5b, 0x97, 0x6c, 0x56, 0x5a, 0xcd, - 0xc3, 0x94, 0x50, 0x59, 0xf7, 0xe0, 0x8a, 0xc2, 0x4e, 0x62, 0xb7, 0xf5, 0x03, 0x54, 0x82, 0xdc, - 0x41, 0xaf, 0xe3, 0x39, 0x6d, 0x61, 0x06, 0xd8, 0xf2, 0xdb, 0x7a, 0x5f, 0xe7, 0x34, 0x5a, 0x56, - 0xcf, 0x22, 0xac, 0x72, 0x04, 0xb0, 0xb6, 0x74, 0xe6, 0x0e, 0xaf, 0xad, 0xb5, 0x9b, 0x8d, 0xb5, - 0x5b, 0x88, 0xf3, 0xda, 0x5a, 0x4c, 0x65, 0x9e, 0xf5, 0x88, 0x9a, 0x38, 0x95, 0x5e, 0xaf, 0xe3, - 0xb6, 0xe8, 0xce, 0xc0, 0xf4, 0x9a, 0xb4, 0x0e, 0x7e, 0x4b, 0xbd, 0x63, 0x56, 0xb6, 0x45, 0x79, - 0xa3, 0xac, 0xdc, 0x22, 0x5b, 0xdf, 0xc3, 0x0d, 0x03, 0x31, 0xae, 0xe1, 0x3f, 0x83, 0x29, 0x0e, - 0x8a, 0x09, 0xb4, 0xea, 0x95, 0xa7, 0xeb, 0x29, 0xe0, 0x98, 0xa2, 0xbe, 0xf5, 0x1d, 0xac, 0x1c, - 0xf4, 0x02, 0xec, 0x27, 0xc9, 0x8b, 0xae, 0x7e, 0x2c, 0xef, 0xb0, 0x33, 0x46, 0x8f, 0x3f, 0x9c, - 0x0d, 0xca, 0x93, 0x8c, 0xb6, 0xb8, 0xba, 0xb6, 0x7e, 0x3f, 0x03, 0x2b, 0x6c, 0xa9, 0x1a, 0x49, - 0x5f, 0x84, 0x0b, 0x8a, 0x47, 0x39, 0x6b, 0xf6, 0x28, 0x0f, 0x75, 0xb1, 0x5b, 0xdf, 0x82, 0xc5, - 0x7b, 0xd4, 0xe9, 0xbc, 0xa1, 0xb9, 0xf9, 0x1b, 0x19, 0x58, 0x60, 0x93, 0xf3, 0x1a, 0x54, 0xd0, - 0x57, 0x30, 0x57, 0x3f, 0x71, 0x7b, 0x0d, 0xa7, 0xe3, 0xb6, 0x99, 0x73, 0x95, 0x6d, 0x24, 0x8b, - 0x74, 0x8f, 0x3c, 0x71, 0x7b, 0xcd, 0xe7, 0x51, 0x51, 0xc6, 0x8e, 0x55, 0xb6, 0x9e, 0xc0, 0x62, - 0xac, 0x0f, 0x5c, 0x30, 0x3e, 0x8e, 0x0b, 0x46, 0x22, 0x00, 0x21, 0x5d, 0x2a, 0x1e, 0xc3, 0x55, - 0x29, 0x15, 0xfa, 0x94, 0xad, 0xc5, 0xa4, 0x21, 0x41, 0x30, 0x4d, 0x14, 0x5a, 0x70, 0x55, 0x4a, - 0xc2, 0x6b, 0x48, 0x80, 0x98, 0xdc, 0x6c, 0xea, 0xe4, 0x6e, 0x43, 0x49, 0x9d, 0xdc, 0xd7, 0x99, - 0xd4, 0xff, 0x9c, 0x81, 0xa5, 0x4d, 0xdc, 0xc5, 0xbe, 0x43, 0xbb, 0xac, 0x9d, 0x29, 0x54, 0xc7, - 0x72, 0x66, 0xa8, 0x63, 0x59, 0x1a, 0xcc, 0xd9, 0x74, 0x83, 0x99, 0xec, 0x86, 0x07, 0xf6, 0x36, - 0x97, 0x55, 0xba, 0x1b, 0xf6, 0x7d, 0xd7, 0x26, 0x30, 0xb4, 0x1d, 0x39, 0xa5, 0xc7, 0x47, 0x3a, - 0xa5, 0xe7, 0xb9, 0x93, 0x6e, 0x8a, 0x3b, 0xa5, 0x35, 0x57, 0xb4, 0xf5, 0x05, 0x14, 0x93, 0x63, - 0xe1, 0xf2, 0x31, 0xea, 0x90, 0x62, 0xad, 0x47, 0xd2, 0xcd, 0xef, 0xaf, 0xa5, 0x33, 0x3e, 0xa6, - 0x42, 0x87, 0x38, 0x7f, 0xac, 0x7a, 0x24, 0x9f, 0x9c, 0x0a, 0x6f, 0xff, 0x73, 0x22, 0x9f, 0x2c, - 0x46, 0x21, 0x63, 0x8e, 0x51, 0xe0, 0x32, 0xca, 0x50, 0x05, 0x82, 0xf5, 0x14, 0xae, 0x6a, 0x44, - 0x23, 0xa9, 0xff, 0x0a, 0x72, 0x02, 0x16, 0xf3, 0x4d, 0x68, 0x64, 0xe9, 0xbc, 0x05, 0x02, 0x59, - 0xa2, 0x58, 0xbf, 0xca, 0xc0, 0x12, 0xdb, 0x5d, 0x92, 0xe3, 0x3e, 0xff, 0xec, 0xff, 0xb9, 0x38, - 0xbc, 0xee, 0xa7, 0x38, 0xbc, 0x28, 0x8a, 0xea, 0xf0, 0x52, 0xdd, 0x5c, 0x0f, 0xc7, 0x73, 0xd9, - 0xc2, 0x98, 0xd5, 0x80, 0x62, 0x72, 0x84, 0x6f, 0x60, 0x4e, 0x36, 0x61, 0x49, 0x59, 0xe8, 0xaf, - 0x2d, 0x31, 0x51, 0x8b, 0x6f, 0x50, 0x62, 0xa2, 0x8a, 0x6f, 0x4c, 0x62, 0xb6, 0x61, 0x9e, 0x11, - 0xd6, 0x57, 0xd7, 0xaa, 0xba, 0xba, 0x52, 0xe3, 0x65, 0x92, 0x0b, 0xee, 0x31, 0x5d, 0x70, 0xa2, - 0x4a, 0xd4, 0xc3, 0x8f, 0x60, 0x92, 0x87, 0x04, 0xb2, 0xfe, 0xa5, 0x10, 0xa3, 0x9a, 0x97, 0xc5, - 0x01, 0xda, 0xbc, 0xb2, 0x55, 0xa4, 0x43, 0x26, 0xe7, 0x14, 0xee, 0xf0, 0x96, 0xa7, 0xc6, 0x6f, - 0x89, 0x8a, 0x8b, 0x95, 0xbc, 0xe6, 0xae, 0xf1, 0x04, 0x8a, 0x6c, 0xd7, 0x50, 0xa8, 0xbe, 0xd6, - 0xbe, 0xf1, 0x29, 0x14, 0x99, 0x38, 0xa5, 0x10, 0x1c, 0xbe, 0x19, 0xac, 0xc0, 0xb2, 0xdc, 0x0c, - 0xd2, 0x46, 0xff, 0x77, 0x32, 0x70, 0x6d, 0x13, 0x87, 0x7a, 0xd4, 0xd4, 0xff, 0x97, 0xbd, 0xfb, - 0x07, 0x28, 0xa5, 0x75, 0x84, 0x4f, 0xc5, 0xd7, 0xf1, 0xa9, 0x30, 0x86, 0x88, 0xa5, 0x4f, 0xc9, - 0xf7, 0x70, 0x9d, 0x4d, 0x89, 0x5e, 0x5f, 0x0c, 0xf4, 0x8b, 0xd8, 0xac, 0x18, 0xa9, 0xa7, 0xcd, - 0xce, 0xdf, 0xcf, 0xc0, 0x75, 0xc6, 0xe4, 0x74, 0xe2, 0x7f, 0xde, 0xd6, 0xdd, 0x2e, 0x94, 0xe5, - 0x9c, 0xbf, 0x81, 0x89, 0xb5, 0x5a, 0x80, 0x04, 0x99, 0x5a, 0xdd, 0x16, 0x24, 0xae, 0xc1, 0x58, - 0xad, 0x6e, 0xf3, 0x0b, 0x68, 0xba, 0x69, 0xb7, 0x02, 0xdf, 0x26, 0xb0, 0xb8, 0x06, 0xcf, 0x9e, - 0x43, 0x83, 0x5b, 0x3f, 0x87, 0x79, 0xad, 0x11, 0x3e, 0xef, 0xcb, 0x30, 0x5e, 0xc3, 0x7e, 0xc8, - 0x9b, 0xa1, 0x23, 0x6d, 0x61, 0x3f, 0xb4, 0x29, 0x14, 0xdd, 0x86, 0xa9, 0x5a, 0x85, 0x7a, 0x1b, - 0xa9, 0x6d, 0x31, 0xc3, 0x14, 0x53, 0xcb, 0x69, 0xd2, 0x48, 0x72, 0x5b, 0x14, 0x5a, 0xff, 0x3e, - 0xa3, 0x50, 0x27, 0xe8, 0xa3, 0xc7, 0xf0, 0x01, 0x39, 0x1e, 0x13, 0x9e, 0x29, 0x43, 0xb8, 0x42, - 0xb6, 0x2d, 0xee, 0xa9, 0x61, 0x3b, 0x9f, 0xad, 0x54, 0x3a, 0xa7, 0xa7, 0x54, 0xdc, 0xcc, 0x31, - 0x24, 0xe1, 0x45, 0x94, 0x37, 0x73, 0x9c, 0x74, 0x60, 0xab, 0x95, 0xac, 0x1f, 0x60, 0x41, 0xef, - 0xff, 0x1b, 0x65, 0xcf, 0xdb, 0xf4, 0xca, 0x47, 0xb9, 0x2c, 0x45, 0x48, 0x75, 0x24, 0x70, 0xb1, - 0xfa, 0x04, 0x0a, 0xbc, 0x56, 0xb4, 0x2c, 0x6f, 0x09, 0xd3, 0x8e, 0x2d, 0x4a, 0x3d, 0xb8, 0x58, - 0xf8, 0x43, 0x7f, 0x26, 0x5c, 0x15, 0xa3, 0x5a, 0xf8, 0x47, 0x19, 0x28, 0x3e, 0x7e, 0x50, 0xa9, - 0xf4, 0xc3, 0x63, 0xdc, 0x0d, 0xc9, 0xa1, 0x04, 0xd7, 0x8e, 0x9d, 0x4e, 0x07, 0x77, 0x8f, 0x30, - 0xba, 0x03, 0xe3, 0xfb, 0x4f, 0xf6, 0xf7, 0xb8, 0x47, 0x60, 0x81, 0x1f, 0xc7, 0x09, 0x48, 0xd6, - 0xb1, 0x69, 0x0d, 0xf4, 0x08, 0xae, 0x3c, 0xe5, 0xe1, 0xfc, 0xb2, 0x88, 0xfb, 0x02, 0x6e, 0xdc, - 0x95, 0x81, 0xfe, 0x35, 0x1f, 0xb7, 0x49, 0x2b, 0x4e, 0xa7, 0x12, 0x10, 0xc5, 0x40, 0xe6, 0x27, - 0x89, 0xf7, 0x70, 0x3c, 0x97, 0x29, 0x64, 0xad, 0x3f, 0xc8, 0xc0, 0x52, 0xac, 0x67, 0x8a, 0x63, - 0x57, 0xed, 0xd8, 0xbc, 0xd2, 0x31, 0x51, 0x65, 0xeb, 0x12, 0xef, 0x59, 0x8d, 0xc6, 0x8e, 0xd2, - 0x16, 0x78, 0x87, 0xde, 0x19, 0xde, 0xa1, 0x88, 0x80, 0x44, 0xe4, 0x91, 0x5f, 0x14, 0x6e, 0x5d, - 0x86, 0x59, 0x8d, 0x03, 0x96, 0x05, 0x33, 0x6a, 0xcb, 0x84, 0xcd, 0x35, 0xaf, 0x2d, 0xd9, 0x4c, - 0xfe, 0xb6, 0xfe, 0x41, 0x06, 0x16, 0x1e, 0x3f, 0xa8, 0xd8, 0xf8, 0xc8, 0x25, 0xcb, 0x2f, 0x62, - 0xf1, 0xaa, 0x36, 0x92, 0x65, 0x6d, 0x24, 0xb1, 0xba, 0x72, 0x48, 0x9f, 0x27, 0x86, 0xb4, 0x9c, - 0x36, 0x24, 0x6a, 0x65, 0xb9, 0x5e, 0x57, 0x1b, 0x89, 0xe2, 0xf9, 0xf8, 0xc7, 0x19, 0x98, 0x57, - 0xfa, 0x24, 0xfb, 0xff, 0x81, 0xd6, 0xa5, 0xeb, 0x29, 0x5d, 0x4a, 0x30, 0xb9, 0x9a, 0xe8, 0xd1, - 0xdb, 0xc3, 0x7a, 0x34, 0x92, 0xc7, 0x7f, 0x92, 0x81, 0xc5, 0x54, 0x1e, 0xa0, 0xab, 0x64, 0xd7, - 0x68, 0xf9, 0x38, 0xe4, 0xec, 0xe5, 0x5f, 0x04, 0xbe, 0x1d, 0x04, 0x7d, 0xfe, 0xd6, 0x22, 0x6f, - 0xf3, 0x2f, 0xf4, 0x36, 0xcc, 0xee, 0x61, 0xdf, 0xf5, 0xda, 0x75, 0xdc, 0xf2, 0xba, 0x6d, 0xe6, - 0x11, 0x9e, 0xb5, 0x75, 0x20, 0x5a, 0x86, 0x7c, 0xa5, 0x73, 0xe4, 0xf9, 0x6e, 0x78, 0xcc, 0x9c, - 0x4f, 0x79, 0x3b, 0x02, 0x10, 0xda, 0xeb, 0xee, 0x91, 0x1b, 0xb2, 0xbb, 0xb5, 0x59, 0x9b, 0x7f, - 0xa1, 0x22, 0x4c, 0x55, 0x5a, 0x2d, 0xaf, 0xdf, 0x0d, 0xa9, 0x07, 0x34, 0x6f, 0x8b, 0x4f, 0x82, - 0xf1, 0xad, 0x4d, 0x85, 0x80, 0x46, 0x43, 0xd9, 0xfc, 0xcb, 0x7a, 0x0f, 0x16, 0xd2, 0xf8, 0x98, - 0x2a, 0x32, 0x7f, 0x2d, 0x0b, 0xf3, 0x95, 0x76, 0xfb, 0xf1, 0x83, 0xca, 0x3a, 0x56, 0x8d, 0x8f, - 0x0f, 0x61, 0x7c, 0xbb, 0xeb, 0x86, 0x7c, 0xd7, 0x5c, 0xe1, 0xd3, 0x93, 0x52, 0x93, 0xd4, 0x22, - 0x33, 0x44, 0xfe, 0x47, 0x36, 0xcc, 0x6f, 0xfc, 0xe4, 0x06, 0xa1, 0xdb, 0x3d, 0xa2, 0x73, 0xce, - 0x1a, 0xe6, 0x73, 0x2c, 0x88, 0x18, 0x96, 0xdb, 0xd6, 0x25, 0x3b, 0x0d, 0x19, 0xed, 0xc3, 0xd5, - 0x5d, 0xfc, 0x22, 0x45, 0x84, 0x64, 0x30, 0x93, 0x24, 0x9b, 0x22, 0x39, 0x06, 0x5c, 0x55, 0x42, - 0xff, 0x76, 0x96, 0x46, 0xc8, 0x29, 0x03, 0xe3, 0x2d, 0x1f, 0xc0, 0x82, 0xd2, 0xa1, 0x48, 0xe3, - 0x64, 0x78, 0xec, 0x74, 0xea, 0x70, 0xd4, 0x85, 0x94, 0x8a, 0x8e, 0x9e, 0xc2, 0x92, 0xde, 0xa9, - 0x88, 0xb2, 0xbe, 0x18, 0xd2, 0xaa, 0x6c, 0x5d, 0xb2, 0x4d, 0xd8, 0x68, 0x15, 0xc6, 0x2a, 0xad, - 0x13, 0xce, 0x96, 0xf4, 0x29, 0x63, 0x23, 0xab, 0xb4, 0x4e, 0x68, 0xb4, 0x69, 0xeb, 0x44, 0x5b, - 0x0f, 0xff, 0x21, 0x03, 0x4b, 0x86, 0x19, 0x46, 0x2b, 0x00, 0x0c, 0xa8, 0xe8, 0x76, 0x05, 0x42, - 0x76, 0x55, 0xf6, 0x45, 0x2f, 0x1c, 0xc7, 0xe8, 0xfd, 0xb5, 0x88, 0x0b, 0x8a, 0x0a, 0x6c, 0xa5, - 0x12, 0xda, 0x83, 0x69, 0xf6, 0xc5, 0xc2, 0x93, 0xc6, 0x29, 0x0e, 0xd2, 0x70, 0x58, 0x3c, 0x12, - 0x8d, 0x39, 0x68, 0x53, 0x40, 0x33, 0x1e, 0x96, 0xa4, 0x92, 0xe0, 0x47, 0xbf, 0x5a, 0x7c, 0x14, - 0x72, 0xd0, 0xe8, 0x0e, 0x4c, 0x32, 0x20, 0x9f, 0x43, 0xf1, 0xea, 0x25, 0xaa, 0xcc, 0xcb, 0xad, - 0x3f, 0xcc, 0x08, 0x8f, 0x4e, 0x62, 0x69, 0x7c, 0xa2, 0x2d, 0x8d, 0xb7, 0x64, 0x87, 0xd3, 0x2a, - 0x6b, 0xab, 0xa3, 0x0a, 0xd3, 0xaf, 0xb2, 0x2a, 0x54, 0x24, 0x55, 0x6e, 0xff, 0x79, 0x46, 0x9c, - 0x45, 0x93, 0xa2, 0xbb, 0x01, 0x33, 0xaf, 0x26, 0xb2, 0x1a, 0x1a, 0xfa, 0x88, 0x49, 0x54, 0x76, - 0xf8, 0x48, 0x87, 0x0a, 0xd5, 0x97, 0xc2, 0x69, 0xf5, 0x2a, 0x62, 0x65, 0x2d, 0xa7, 0x60, 0xcb, - 0xe6, 0xac, 0x7e, 0xa2, 0xb4, 0xfe, 0xb2, 0xdb, 0x12, 0xf3, 0x74, 0x3b, 0x7e, 0xc9, 0x6e, 0xbc, - 0x41, 0x55, 0xfb, 0x90, 0x8d, 0xfc, 0x18, 0x5c, 0xe4, 0xa8, 0x49, 0xab, 0x76, 0xea, 0x2c, 0xb6, - 0x4e, 0x5e, 0xa5, 0xd1, 0x1a, 0xcc, 0xee, 0xe2, 0x17, 0x89, 0x76, 0xe9, 0xbd, 0x54, 0x17, 0xbf, - 0x68, 0x2a, 0x6d, 0x2b, 0xd2, 0xae, 0xe3, 0xa0, 0x43, 0x98, 0x13, 0xba, 0xe0, 0xbc, 0x2a, 0x91, - 0x45, 0x5c, 0x92, 0x16, 0x4e, 0x9f, 0x39, 0x4d, 0x9f, 0x43, 0xd5, 0x50, 0x49, 0x9d, 0xa2, 0xb5, - 0x07, 0xc5, 0xe4, 0x58, 0xb9, 0x94, 0x7d, 0x38, 0x6a, 0x39, 0xb1, 0x53, 0x55, 0x5b, 0x5f, 0x5a, - 0x5b, 0xd4, 0x01, 0x20, 0xeb, 0xc8, 0xa3, 0xcb, 0xfd, 0x38, 0xeb, 0x68, 0x14, 0x99, 0x60, 0x9d, - 0x1a, 0xd2, 0x1d, 0x85, 0x59, 0x2c, 0xc6, 0x28, 0xf1, 0x8e, 0xbd, 0x07, 0x53, 0x1c, 0x24, 0x43, - 0xe5, 0xe3, 0x0b, 0x5d, 0x54, 0xb0, 0xfe, 0x49, 0x06, 0xae, 0x1d, 0x04, 0xd8, 0xaf, 0xbb, 0xdd, - 0xa3, 0x0e, 0x3e, 0x08, 0xf4, 0x20, 0x87, 0xdf, 0xd6, 0x16, 0xfb, 0x92, 0x21, 0x78, 0xf2, 0xff, - 0xd5, 0x12, 0xff, 0x67, 0x19, 0x28, 0xa5, 0xf5, 0xed, 0xcd, 0xae, 0xf2, 0xbb, 0xfc, 0xac, 0xc1, - 0x7a, 0x5b, 0xe4, 0xe8, 0xb2, 0x4d, 0x31, 0x58, 0x32, 0x48, 0xf2, 0xbf, 0xb6, 0xbc, 0xff, 0x77, - 0x06, 0x16, 0xb6, 0x03, 0xda, 0xfd, 0x5f, 0xf4, 0x5d, 0x1f, 0xb7, 0x05, 0xe3, 0xee, 0xa6, 0x85, - 0xd8, 0xd2, 0x79, 0xdd, 0xba, 0x94, 0x16, 0x42, 0xfb, 0xa1, 0x12, 0x44, 0x98, 0x1d, 0x16, 0x3b, - 0xab, 0xbd, 0xd7, 0xb8, 0x0d, 0xe3, 0xbb, 0xc4, 0xa4, 0x19, 0xe3, 0xf2, 0xc7, 0x30, 0x08, 0x88, - 0xc6, 0xfb, 0x91, 0x2e, 0x93, 0x0f, 0xf4, 0x20, 0x11, 0x55, 0x38, 0x3e, 0x3a, 0x36, 0x34, 0xf9, - 0xd0, 0xa4, 0x9a, 0x83, 0xc9, 0x7d, 0xc7, 0x3f, 0xc2, 0xa1, 0xf5, 0x3d, 0x94, 0xf8, 0x65, 0x1e, - 0xf3, 0x91, 0xd1, 0x2b, 0xbf, 0x20, 0xf2, 0xdd, 0x0c, 0xbb, 0x80, 0x5b, 0x01, 0xa8, 0x87, 0x8e, - 0x1f, 0x6e, 0x77, 0xdb, 0xf8, 0x27, 0x3a, 0xda, 0x09, 0x5b, 0x81, 0x58, 0x1f, 0x41, 0x5e, 0x0e, - 0x81, 0x9e, 0xa7, 0x14, 0xab, 0x8d, 0x0e, 0x67, 0x41, 0x8b, 0x73, 0x14, 0xc1, 0x8d, 0x6b, 0xb0, - 0x18, 0x9b, 0x0a, 0x2e, 0x27, 0x25, 0x32, 0x61, 0x0c, 0xc6, 0x2e, 0xfa, 0x6d, 0xf9, 0x6d, 0xd5, - 0xe0, 0x4a, 0x62, 0xa6, 0x11, 0xa2, 0xb1, 0xab, 0xec, 0xf0, 0x49, 0x94, 0x7a, 0xbd, 0xbe, 0x45, - 0x60, 0xfb, 0x3b, 0x75, 0x16, 0xc7, 0x43, 0x60, 0xfb, 0x3b, 0xf5, 0xea, 0x24, 0x93, 0x1c, 0xeb, - 0x5f, 0x65, 0xe9, 0x11, 0x32, 0xc1, 0x83, 0x98, 0x2b, 0x42, 0x75, 0x87, 0x54, 0x21, 0x4f, 0x47, - 0xbc, 0x2e, 0xe2, 0xdd, 0x86, 0xdf, 0x00, 0xe4, 0x7e, 0x39, 0x28, 0x5f, 0xa2, 0x6e, 0xff, 0x08, - 0x0d, 0x7d, 0x0d, 0x53, 0x1b, 0xdd, 0x36, 0xa5, 0x30, 0x76, 0x01, 0x0a, 0x02, 0x89, 0xcc, 0x03, - 0xed, 0x32, 0x31, 0x47, 0xf8, 0x79, 0xdd, 0x56, 0x20, 0x94, 0xcd, 0xee, 0xa9, 0xcb, 0xee, 0x79, - 0x27, 0x6c, 0xf6, 0x41, 0xb8, 0x49, 0xbb, 0x20, 0x9e, 0x2f, 0xe4, 0x6d, 0xf9, 0x8d, 0x2c, 0x98, - 0x78, 0xe2, 0xb7, 0x79, 0x30, 0xf9, 0xdc, 0xea, 0x8c, 0x78, 0xcf, 0x4d, 0x60, 0x36, 0x2b, 0xb2, - 0xfe, 0x27, 0xbd, 0x7b, 0x09, 0x53, 0xe5, 0x46, 0xe3, 0x4a, 0xe6, 0xb5, 0xb9, 0x92, 0x7d, 0x15, - 0xae, 0xc8, 0x51, 0x8f, 0x99, 0x46, 0x3d, 0x6e, 0x1a, 0xf5, 0x84, 0x79, 0xd4, 0x9b, 0x30, 0xc9, - 0x86, 0x8a, 0x6e, 0xc1, 0xc4, 0x76, 0x88, 0x4f, 0x23, 0xd7, 0x82, 0x7a, 0x7b, 0x6e, 0xb3, 0x32, - 0x72, 0xea, 0xd9, 0x71, 0x82, 0x50, 0x44, 0x8e, 0xe5, 0x6d, 0xf1, 0x69, 0xfd, 0x69, 0x06, 0x0a, - 0x3b, 0x6e, 0x10, 0x92, 0x85, 0x70, 0x4e, 0x59, 0x93, 0x23, 0xca, 0x9a, 0x46, 0x34, 0x16, 0x1b, - 0xd1, 0x17, 0x30, 0x49, 0x03, 0x1a, 0x03, 0xfe, 0x54, 0xe9, 0x16, 0x1f, 0x52, 0xbc, 0x61, 0x16, - 0xf6, 0x18, 0xd0, 0x27, 0x45, 0x36, 0x47, 0x29, 0x7d, 0x06, 0xd3, 0x0a, 0xf8, 0x42, 0x2f, 0x8d, - 0xbe, 0x83, 0x2b, 0x4a, 0x13, 0xd2, 0x0f, 0x31, 0xc2, 0x5b, 0x2d, 0xbd, 0xa1, 0x84, 0x6d, 0xbb, - 0xf8, 0x27, 0x95, 0x6d, 0xfc, 0xd3, 0xfa, 0x91, 0xc6, 0xe3, 0xee, 0x78, 0xad, 0x13, 0xc5, 0x57, - 0x38, 0xc5, 0x94, 0x59, 0xdc, 0xe5, 0x4e, 0x6a, 0xb1, 0x12, 0x5b, 0xd4, 0x40, 0x37, 0x61, 0x7a, - 0xbb, 0xfb, 0xc0, 0xf3, 0x5b, 0xf8, 0x49, 0xb7, 0xc3, 0xa8, 0xe7, 0x6c, 0x15, 0xc4, 0xdd, 0x48, - 0xbc, 0x85, 0xc8, 0x8d, 0x44, 0x01, 0x31, 0x37, 0x12, 0x7b, 0x29, 0x69, 0xb3, 0x32, 0xee, 0xa5, - 0x22, 0x7f, 0x0f, 0xf3, 0x21, 0x49, 0x67, 0xd3, 0xa8, 0x8a, 0x87, 0x70, 0xcd, 0xc6, 0xbd, 0x8e, - 0x43, 0x6c, 0xc5, 0x53, 0x8f, 0xd5, 0x97, 0x63, 0xbe, 0x99, 0x12, 0x2a, 0xa6, 0xdf, 0x38, 0xc9, - 0x2e, 0x67, 0x87, 0x74, 0xf9, 0x14, 0xde, 0xda, 0xc4, 0x61, 0xea, 0x73, 0xc7, 0x68, 0xf0, 0x5b, - 0x90, 0xe3, 0x21, 0xf9, 0x62, 0xfc, 0xa3, 0x5e, 0x5a, 0xf2, 0xeb, 0x17, 0x4e, 0x47, 0xfe, 0x65, - 0x7d, 0x03, 0x65, 0x53, 0x73, 0xe7, 0x8b, 0x10, 0x72, 0xe1, 0xa6, 0x99, 0x80, 0xb4, 0x26, 0xa6, - 0x78, 0x83, 0xf2, 0xd4, 0x3f, 0xbc, 0xb7, 0xd2, 0x21, 0x4f, 0xed, 0x29, 0xfe, 0x87, 0x55, 0x15, - 0x21, 0x08, 0xaf, 0xd1, 0xdd, 0x26, 0xbd, 0x32, 0xd0, 0x09, 0x44, 0x7c, 0xad, 0x40, 0x4e, 0xc0, - 0x62, 0x77, 0x06, 0x89, 0x97, 0xa4, 0x94, 0xa1, 0x6d, 0x41, 0x40, 0xa2, 0x59, 0x3f, 0x0a, 0xc7, - 0xbe, 0x8e, 0x71, 0xbe, 0xb0, 0xd3, 0xf3, 0x78, 0xf2, 0x2d, 0x0f, 0xae, 0xe9, 0xb4, 0x55, 0xf7, - 0x74, 0x41, 0x71, 0x4f, 0x33, 0xaf, 0x34, 0x91, 0x4b, 0x7b, 0x67, 0xa3, 0xdb, 0xee, 0x79, 0x6e, - 0x37, 0xe4, 0x8b, 0x57, 0x05, 0xa1, 0x15, 0xd5, 0x09, 0x3d, 0x93, 0x8c, 0xd3, 0xbd, 0x0f, 0xa5, - 0xb4, 0x06, 0x15, 0xdf, 0x8f, 0xf4, 0x27, 0x33, 0x3b, 0xce, 0x3a, 0x86, 0x05, 0x2d, 0x37, 0x47, - 0x94, 0x6c, 0x20, 0xca, 0x49, 0x92, 0xaf, 0x7e, 0xf9, 0x9b, 0x41, 0xf9, 0xd3, 0x8b, 0x04, 0x83, - 0x0a, 0x9a, 0xfb, 0x32, 0xd4, 0xd8, 0x5a, 0x82, 0xb1, 0x9a, 0xbd, 0x43, 0x87, 0x6d, 0xef, 0xc8, - 0x61, 0xdb, 0x3b, 0xd6, 0x6f, 0x32, 0x50, 0xae, 0x1d, 0x3b, 0xdd, 0x23, 0x6a, 0x7b, 0x28, 0xe6, - 0xaa, 0x72, 0xaf, 0x7a, 0xde, 0x23, 0xd5, 0x2a, 0x4c, 0xef, 0xe2, 0x17, 0x22, 0x4c, 0x9a, 0x07, - 0x1c, 0x53, 0xf7, 0x3b, 0x39, 0xee, 0xf4, 0x38, 0xdc, 0x56, 0x2b, 0xa1, 0xbf, 0xfc, 0xea, 0xce, - 0x25, 0xf6, 0x42, 0x3f, 0x3a, 0x49, 0xb1, 0xd2, 0xb4, 0x23, 0x95, 0xa1, 0x09, 0xeb, 0xdf, 0x64, - 0xe0, 0xa6, 0x79, 0xf0, 0x7c, 0xe2, 0xd6, 0xb5, 0x3c, 0x07, 0x43, 0x6e, 0x84, 0xe9, 0x91, 0x55, - 0xc9, 0x73, 0x10, 0xcf, 0x6d, 0x60, 0xe3, 0x96, 0xf7, 0x1c, 0xfb, 0x2f, 0x63, 0x7e, 0x76, 0x01, - 0xae, 0x91, 0x2d, 0x47, 0x64, 0x89, 0x61, 0x20, 0xed, 0x69, 0x23, 0x87, 0x59, 0xff, 0x29, 0x03, - 0xd7, 0xe9, 0x36, 0xc9, 0xbd, 0x90, 0xa2, 0xe0, 0xe2, 0xf1, 0x03, 0x1f, 0xc1, 0x8c, 0xda, 0x38, - 0x9f, 0x30, 0xfa, 0x30, 0x5a, 0xf4, 0xa0, 0xd9, 0xf2, 0xda, 0xd8, 0xd6, 0xaa, 0xa1, 0x6d, 0x98, - 0xe6, 0xdf, 0x8a, 0xab, 0x69, 0x51, 0xc9, 0x9a, 0x42, 0xe5, 0x81, 0x79, 0x8e, 0xe8, 0xec, 0x73, - 0x62, 0x4d, 0x1a, 0xfa, 0xae, 0xe2, 0x5a, 0xbf, 0xce, 0xc2, 0x72, 0x03, 0xfb, 0xee, 0xb3, 0x97, - 0x86, 0xc1, 0x3c, 0x81, 0x05, 0x01, 0xa2, 0x63, 0xd6, 0xe5, 0x90, 0x3d, 0x51, 0x13, 0x5d, 0x0d, - 0x48, 0x85, 0xa6, 0x14, 0xcb, 0x54, 0xc4, 0x0b, 0x3c, 0xda, 0xfc, 0x10, 0x72, 0x52, 0x94, 0xc7, - 0x28, 0x67, 0xe8, 0xdc, 0x08, 0x31, 0xd6, 0xdf, 0xaf, 0x4b, 0x79, 0xfe, 0x9b, 0xe6, 0xeb, 0x0c, - 0x7e, 0xe2, 0x19, 0x71, 0x18, 0x65, 0x52, 0x4d, 0x24, 0xda, 0x51, 0x4a, 0x53, 0xa4, 0x7a, 0xeb, - 0x92, 0x6d, 0x6a, 0xa9, 0x3a, 0x0d, 0xf9, 0x0a, 0xbd, 0x6c, 0x21, 0x07, 0x8c, 0xff, 0x95, 0x85, - 0x15, 0x11, 0x52, 0x68, 0x60, 0xf3, 0x77, 0xb0, 0x24, 0x40, 0x95, 0x5e, 0xcf, 0xf7, 0x9e, 0xe3, - 0xb6, 0xce, 0x69, 0xf6, 0x4c, 0x54, 0x70, 0xda, 0xe1, 0x75, 0x22, 0x66, 0x9b, 0xd0, 0xdf, 0x8c, - 0x9b, 0xe5, 0x6b, 0x5d, 0xb1, 0xb0, 0xd9, 0xa0, 0x4e, 0x49, 0x55, 0xb1, 0xe8, 0x09, 0x7e, 0x54, - 0x25, 0xd3, 0x4e, 0xb8, 0x69, 0xc6, 0x5f, 0xd7, 0x4d, 0x43, 0x8e, 0xa6, 0x3a, 0xcd, 0xea, 0x1c, - 0xcc, 0xec, 0xe2, 0x17, 0x11, 0xdf, 0x7f, 0x2f, 0x03, 0xb3, 0xda, 0xe2, 0x46, 0xef, 0xc2, 0x04, - 0xfd, 0x83, 0x6e, 0x9a, 0xfc, 0xad, 0x0c, 0x59, 0x60, 0xda, 0x5b, 0x19, 0x56, 0x75, 0x1b, 0xa6, - 0x58, 0xf8, 0x4c, 0xfb, 0x1c, 0x67, 0x08, 0x19, 0x9d, 0xd5, 0x62, 0x28, 0xec, 0x38, 0xc1, 0xf1, - 0xad, 0x47, 0xf0, 0x16, 0x8f, 0xc4, 0xd1, 0x27, 0xbf, 0xa6, 0xda, 0xef, 0xe7, 0xd4, 0xf1, 0x96, - 0x03, 0x2b, 0x9b, 0x38, 0xae, 0x7a, 0xb4, 0xe8, 0xb5, 0x6f, 0xe0, 0xb2, 0x06, 0x97, 0x14, 0x69, - 0x3c, 0xbd, 0x94, 0x21, 0x49, 0x3a, 0x5e, 0xdb, 0xba, 0x99, 0xd6, 0x84, 0xda, 0x59, 0x0b, 0xc3, - 0x65, 0x7a, 0x52, 0x96, 0x37, 0x4e, 0xc1, 0x05, 0xb4, 0xde, 0x1d, 0x65, 0x5d, 0x33, 0x8d, 0xc7, - 0x9e, 0x62, 0x8a, 0xed, 0x49, 0x96, 0x5a, 0xb3, 0x30, 0x5d, 0xf3, 0xba, 0x21, 0xfe, 0x89, 0x3e, - 0x86, 0xb0, 0xe6, 0x60, 0x46, 0x14, 0x75, 0x70, 0x10, 0x58, 0xff, 0x74, 0x0c, 0x2c, 0xce, 0xd8, - 0x34, 0x2f, 0x8f, 0xe0, 0xc7, 0x61, 0xa2, 0xb3, 0x7c, 0x13, 0xb9, 0xaa, 0xfa, 0xb2, 0xa2, 0x52, - 0x26, 0x79, 0xf4, 0xf1, 0x4a, 0x2b, 0x82, 0x6a, 0x92, 0x97, 0x18, 0xfd, 0xcf, 0x0d, 0x6a, 0x92, - 0x2d, 0x36, 0x9a, 0x3e, 0xc4, 0xa0, 0x26, 0x35, 0xba, 0xe9, 0x2a, 0xd3, 0xd6, 0xd8, 0xc0, 0xf7, - 0x65, 0x24, 0x43, 0xbf, 0x65, 0x09, 0x4f, 0xb9, 0xc5, 0x00, 0xcd, 0x44, 0xca, 0x2c, 0x95, 0x08, - 0x3a, 0xd0, 0x79, 0xc9, 0xd7, 0xa3, 0xb8, 0xe1, 0x55, 0x8b, 0x18, 0xd5, 0x9e, 0x02, 0xd1, 0x33, - 0x90, 0x69, 0x75, 0x15, 0xcf, 0xdd, 0x3f, 0xcc, 0xc0, 0x75, 0x36, 0x3b, 0x7b, 0xbe, 0xfb, 0xdc, - 0xed, 0xe0, 0x23, 0xac, 0x89, 0x69, 0x3f, 0xfd, 0xa6, 0x2c, 0x73, 0x2e, 0x1d, 0x4d, 0x93, 0x36, - 0x60, 0x8e, 0x6e, 0x72, 0xe4, 0xa6, 0xd1, 0xb7, 0x06, 0x19, 0x58, 0x11, 0x6f, 0xca, 0x62, 0xd7, - 0x47, 0x17, 0x35, 0xb7, 0xaa, 0xda, 0x8d, 0x4f, 0xd6, 0x70, 0xe3, 0xa3, 0x79, 0xd2, 0xc3, 0x11, - 0x57, 0x40, 0x63, 0xaf, 0x7d, 0x05, 0x64, 0xfd, 0x6a, 0x0c, 0xae, 0xec, 0x39, 0x47, 0x6e, 0x97, - 0xe8, 0x1e, 0x91, 0x47, 0x05, 0x55, 0x12, 0xe9, 0xa8, 0x86, 0x47, 0x03, 0xa5, 0xe4, 0x9b, 0x5a, - 0x55, 0x33, 0xc3, 0x64, 0x4d, 0x71, 0xe2, 0x7a, 0xfe, 0x97, 0xcf, 0x34, 0xef, 0x64, 0x22, 0x20, - 0x8c, 0xbe, 0x35, 0xea, 0x7a, 0xed, 0x58, 0x8a, 0x36, 0xea, 0xe1, 0x7b, 0x02, 0xd3, 0x4a, 0x54, - 0x17, 0x17, 0xd0, 0x04, 0x05, 0xca, 0x96, 0x93, 0xfe, 0x21, 0x4e, 0x4d, 0xc7, 0xa3, 0x52, 0x48, - 0x49, 0xc2, 0x33, 0xf1, 0x86, 0x93, 0xf0, 0x7c, 0xcf, 0xba, 0x2c, 0x7c, 0xbd, 0x93, 0x7c, 0xdf, - 0x60, 0xe4, 0x13, 0x0e, 0xdf, 0xc6, 0x9a, 0xd2, 0xfb, 0xb4, 0x8c, 0x62, 0x2a, 0xb1, 0x2a, 0x40, - 0x4e, 0xe4, 0x2e, 0xb4, 0xfe, 0x68, 0x12, 0x16, 0x76, 0xdc, 0x20, 0x14, 0xb3, 0x1b, 0x44, 0xaa, - 0x7f, 0x46, 0xc0, 0x94, 0xf3, 0x0b, 0xb7, 0xd2, 0x18, 0xbc, 0x19, 0x4b, 0xa5, 0xa8, 0x21, 0xa0, - 0x8f, 0x54, 0x2f, 0x52, 0x56, 0x49, 0xb0, 0x90, 0xcc, 0x82, 0xa7, 0xba, 0x97, 0xde, 0xd5, 0x1c, - 0x66, 0x6c, 0x5f, 0xed, 0x10, 0x80, 0xba, 0xaf, 0x32, 0x9f, 0xd3, 0x5a, 0xdc, 0x8b, 0xc6, 0x1a, - 0x60, 0x4a, 0xf1, 0x04, 0x6b, 0x26, 0xb7, 0x74, 0x46, 0x1d, 0x48, 0x67, 0xd4, 0x04, 0x3d, 0xed, - 0xfe, 0x4c, 0x71, 0x46, 0xc5, 0x99, 0xa0, 0x3a, 0xa4, 0xd8, 0x93, 0xde, 0x0e, 0x05, 0xa8, 0x4f, - 0x7a, 0x59, 0x15, 0xb4, 0x0f, 0xf3, 0x7b, 0x3e, 0x6e, 0x53, 0xd5, 0xb2, 0xf1, 0x53, 0xcf, 0xe7, - 0x47, 0x0c, 0xea, 0xd2, 0x64, 0xd9, 0xa6, 0x7a, 0xa2, 0xb8, 0x89, 0x65, 0xb9, 0xaa, 0x61, 0x52, - 0xd0, 0xd1, 0x06, 0xcc, 0xd5, 0xb1, 0xe3, 0xb7, 0x8e, 0x1f, 0xe1, 0x97, 0x44, 0x31, 0x06, 0xc5, - 0xa9, 0x28, 0x43, 0x47, 0x40, 0x4b, 0xc8, 0x40, 0x69, 0x91, 0x7a, 0xed, 0xa4, 0x23, 0xa1, 0xdf, - 0x81, 0xc9, 0xba, 0xe7, 0x87, 0xd5, 0x97, 0xb1, 0xb4, 0x88, 0x0c, 0x58, 0xbd, 0x26, 0xb2, 0x94, - 0x04, 0x9e, 0x1f, 0x36, 0x0f, 0x55, 0xbe, 0x71, 0x3c, 0xf4, 0x80, 0x58, 0x5d, 0xc4, 0x12, 0x0c, - 0x9d, 0x4e, 0x8d, 0x86, 0x4f, 0xb0, 0x47, 0x59, 0xdc, 0xb2, 0xa2, 0xe6, 0x63, 0xe8, 0x74, 0x9a, - 0x74, 0x9f, 0xd7, 0x2f, 0xc0, 0x54, 0x2c, 0xf4, 0x12, 0x16, 0x74, 0x41, 0xe7, 0x39, 0x8c, 0x40, - 0x4b, 0x30, 0x96, 0x56, 0xa5, 0x7a, 0x87, 0xf7, 0xf2, 0x66, 0x3c, 0x79, 0x56, 0x22, 0xad, 0x51, - 0x6a, 0x13, 0xaf, 0xe3, 0x48, 0xfc, 0x93, 0x0c, 0x2c, 0xc6, 0xe4, 0x83, 0x1f, 0x28, 0x9f, 0x40, - 0x5e, 0x02, 0xb9, 0xfb, 0xa4, 0x28, 0x37, 0xbe, 0x98, 0xe2, 0x64, 0xd2, 0x29, 0x16, 0x8f, 0x3a, - 0x5f, 0x11, 0x0d, 0x74, 0x3f, 0xe6, 0x73, 0x64, 0x07, 0x95, 0x2e, 0xd9, 0x85, 0x75, 0x89, 0x16, - 0xd5, 0xd0, 0x67, 0x00, 0xca, 0xb4, 0xb0, 0x55, 0x43, 0xd3, 0xf4, 0xa5, 0xcf, 0x88, 0x52, 0xd9, - 0xfa, 0xb3, 0x49, 0xb1, 0xaf, 0xf2, 0xa3, 0xed, 0xbe, 0xef, 0xb4, 0x4e, 0xa2, 0x90, 0xcc, 0x8f, - 0x92, 0xf1, 0x8f, 0xe7, 0x59, 0xc2, 0xb7, 0xb5, 0x97, 0xd2, 0xe6, 0xa4, 0xab, 0xd1, 0xa3, 0xf9, - 0xb1, 0x73, 0x3c, 0x9a, 0xbf, 0x07, 0x53, 0xdb, 0xdd, 0xe7, 0x2e, 0xb1, 0xa2, 0x59, 0x30, 0x20, - 0xb5, 0x41, 0x5d, 0x06, 0x52, 0x19, 0xc3, 0x6b, 0xa1, 0xcf, 0x20, 0xb7, 0xe5, 0x05, 0x21, 0x35, - 0x23, 0x27, 0xa2, 0x93, 0x4a, 0x48, 0x7d, 0xb0, 0xcd, 0x63, 0x5e, 0xa4, 0x2a, 0x09, 0x51, 0x1d, - 0x7d, 0x0c, 0x53, 0x95, 0x76, 0x9b, 0xac, 0x42, 0xbe, 0x82, 0x69, 0xc6, 0x46, 0x8e, 0xe9, 0xb0, - 0x12, 0xb5, 0x49, 0x5e, 0x19, 0x7d, 0xa9, 0x3b, 0x44, 0xa7, 0xa2, 0x94, 0x12, 0xe9, 0xd9, 0x4b, - 0x75, 0x67, 0xe9, 0xbb, 0xe2, 0x22, 0x2a, 0x17, 0x25, 0xe9, 0xa0, 0x09, 0x37, 0x34, 0xd5, 0x47, - 0xef, 0xb1, 0xb6, 0x21, 0xbf, 0xdd, 0x75, 0x43, 0x97, 0xa6, 0x29, 0xc8, 0x6b, 0x1b, 0xe8, 0x9e, - 0xe3, 0x87, 0x6e, 0xcb, 0xed, 0x39, 0xdd, 0x90, 0xcd, 0x96, 0x2b, 0x2a, 0xaa, 0xb3, 0x25, 0xb1, - 0xd5, 0x84, 0x46, 0xf0, 0xc6, 0x12, 0x1a, 0xa5, 0xe6, 0x04, 0x9a, 0x7e, 0xf5, 0x9c, 0x40, 0x6b, - 0x6c, 0x2e, 0xa9, 0xd1, 0x3a, 0x13, 0x09, 0x22, 0xf5, 0x13, 0xea, 0xd6, 0xa9, 0x2d, 0x2b, 0xa2, - 0x9b, 0x34, 0x2d, 0xc1, 0x6c, 0x14, 0x57, 0xab, 0x5d, 0x7c, 0x67, 0xb7, 0xd7, 0x51, 0x13, 0x66, - 0x48, 0xed, 0x3d, 0xaf, 0xe3, 0xb6, 0x5c, 0x1c, 0x14, 0xe7, 0x34, 0xc7, 0xb2, 0xbe, 0x28, 0x68, - 0xa5, 0x97, 0x75, 0x1c, 0xb2, 0x4d, 0x90, 0x36, 0xdd, 0xe3, 0x88, 0xea, 0x26, 0xa8, 0x12, 0xb4, - 0x6c, 0x28, 0x46, 0xb7, 0x53, 0xb1, 0xd5, 0xf5, 0x71, 0xf2, 0x89, 0x03, 0x4d, 0x85, 0x17, 0x3d, - 0x71, 0x50, 0x27, 0x2c, 0x7a, 0xec, 0x70, 0x00, 0xd7, 0x6d, 0x7c, 0xea, 0x3d, 0xc7, 0x6f, 0x96, - 0xec, 0xcf, 0xe1, 0x9a, 0x4e, 0xf0, 0xa0, 0xd7, 0xa6, 0x4f, 0x26, 0xd9, 0x35, 0x58, 0x6a, 0x32, - 0x0d, 0x8e, 0xc0, 0x92, 0x69, 0xb0, 0x17, 0xda, 0xe4, 0x4f, 0x55, 0x5e, 0x69, 0x99, 0xe5, 0xc1, - 0xb2, 0x4e, 0xbc, 0xd2, 0x6e, 0x2b, 0x82, 0x4a, 0x2c, 0x34, 0xe5, 0x33, 0x66, 0x12, 0xaa, 0x12, - 0x4d, 0x35, 0x5b, 0x2f, 0x02, 0xa8, 0x6b, 0x49, 0xa9, 0x67, 0x61, 0x28, 0xc7, 0xd9, 0x43, 0x58, - 0xa6, 0xb6, 0x59, 0x85, 0x59, 0xe5, 0x53, 0x9e, 0xb0, 0xe8, 0x52, 0x57, 0x5a, 0xd0, 0x19, 0xa6, - 0xa3, 0x58, 0xff, 0x72, 0x0c, 0xae, 0x73, 0x3e, 0xbd, 0xc9, 0xc9, 0x40, 0x3f, 0xc2, 0xb4, 0xc2, - 0x7e, 0xce, 0x8f, 0x9b, 0x22, 0x98, 0xc0, 0x34, 0x4d, 0x4c, 0xd5, 0xf4, 0x29, 0xa0, 0x19, 0x9b, - 0x09, 0x62, 0x04, 0xaa, 0x33, 0xda, 0x81, 0x39, 0x7d, 0x0e, 0xb8, 0x61, 0x7d, 0x2b, 0xb5, 0x11, - 0xbd, 0xaa, 0x78, 0xf1, 0xdd, 0x6e, 0xa6, 0xce, 0x04, 0x4d, 0x2b, 0xaa, 0xcf, 0xef, 0x4f, 0x70, - 0x25, 0x31, 0x01, 0xdc, 0x0e, 0xbf, 0x9d, 0xda, 0x60, 0xa2, 0x36, 0xd3, 0x1b, 0x3e, 0x05, 0x1b, - 0x9b, 0x4d, 0x36, 0x52, 0xcd, 0xc1, 0x24, 0x1b, 0x36, 0x59, 0x37, 0x7b, 0x3e, 0x0e, 0x70, 0xb7, - 0x85, 0xd5, 0x88, 0x8d, 0xd7, 0x5d, 0x37, 0xff, 0x31, 0x03, 0xc5, 0x34, 0xba, 0x75, 0xdc, 0x6d, - 0xa3, 0x3d, 0x28, 0xc4, 0x1b, 0xe2, 0xa7, 0x25, 0x4b, 0x98, 0x09, 0xe6, 0x2e, 0x6d, 0x5d, 0xb2, - 0x13, 0xd8, 0x44, 0xb3, 0x2a, 0xb0, 0x0b, 0x86, 0xc6, 0x24, 0x51, 0x95, 0x63, 0xf6, 0x7b, 0xef, - 0x41, 0x5e, 0xe6, 0x4a, 0x47, 0x39, 0x18, 0xdf, 0xde, 0xdd, 0xde, 0x67, 0xb9, 0xb7, 0xf6, 0x0e, - 0xf6, 0x0b, 0x19, 0x04, 0x30, 0xb9, 0xbe, 0xb1, 0xb3, 0xb1, 0xbf, 0x51, 0xc8, 0xbe, 0xd7, 0x54, - 0x0f, 0xac, 0xe8, 0x3a, 0x2c, 0xad, 0x6f, 0x34, 0xb6, 0x6b, 0x1b, 0xcd, 0xfd, 0xbf, 0xb0, 0xb7, - 0xd1, 0x3c, 0xd8, 0xad, 0xef, 0x6d, 0xd4, 0xb6, 0x1f, 0x6c, 0x6f, 0xac, 0x17, 0x2e, 0xa1, 0x05, - 0x28, 0xa8, 0x85, 0xfb, 0x4f, 0xf6, 0xf7, 0x0a, 0x19, 0x54, 0x84, 0x05, 0x15, 0xfa, 0x74, 0xa3, - 0x5a, 0x39, 0xd8, 0xdf, 0xda, 0x2d, 0x8c, 0x59, 0xe3, 0xb9, 0x6c, 0x21, 0xfb, 0xde, 0x8f, 0xda, - 0x69, 0x16, 0x2d, 0x43, 0x91, 0x57, 0x3f, 0xa8, 0x57, 0x36, 0xcd, 0x4d, 0xb0, 0xd2, 0xc7, 0x0f, - 0x2a, 0x85, 0x0c, 0xba, 0x01, 0xd7, 0x34, 0xe8, 0x5e, 0xa5, 0x5e, 0x7f, 0xfa, 0xc4, 0x5e, 0xdf, - 0xd9, 0xa8, 0xd7, 0x0b, 0xd9, 0xf7, 0x6e, 0xf3, 0x8b, 0x76, 0x34, 0x07, 0xb0, 0xbe, 0x51, 0xaf, - 0x6d, 0xec, 0xae, 0x6f, 0xef, 0x6e, 0x16, 0x2e, 0xa1, 0x59, 0xc8, 0x57, 0xe4, 0x67, 0x66, 0xf5, - 0x8f, 0x7f, 0x84, 0x69, 0xc2, 0x4f, 0x71, 0xf8, 0x6b, 0xc2, 0xd2, 0x63, 0xc7, 0xed, 0x86, 0x8e, - 0xdb, 0xe5, 0x52, 0x20, 0xe6, 0x10, 0x95, 0x87, 0x4c, 0x2a, 0x91, 0x87, 0xd2, 0xa8, 0x70, 0xa2, - 0x3b, 0x99, 0xfb, 0x19, 0x54, 0x87, 0x85, 0x34, 0xab, 0x0c, 0x59, 0x7a, 0x6a, 0x80, 0x34, 0x85, - 0x53, 0x5a, 0x4a, 0xdd, 0xbb, 0x1a, 0x1f, 0xa0, 0xc7, 0x70, 0x25, 0xb1, 0x13, 0xc9, 0xfe, 0x9a, - 0xf6, 0xa8, 0x61, 0xe4, 0x8a, 0xd4, 0xb1, 0x17, 0xba, 0xf1, 0x7d, 0x28, 0x40, 0x57, 0x13, 0x06, - 0xc4, 0x06, 0x59, 0x34, 0x46, 0x62, 0xf7, 0x33, 0xc8, 0x86, 0x85, 0xb4, 0x3d, 0x4d, 0x0e, 0x79, - 0xc8, 0x86, 0x57, 0x32, 0x34, 0x47, 0x68, 0xa6, 0xa9, 0x66, 0x49, 0x73, 0x88, 0xde, 0x36, 0xd2, - 0xfc, 0x92, 0x1c, 0xc8, 0xba, 0xed, 0x47, 0x18, 0xf7, 0x2a, 0x1d, 0xf7, 0x39, 0x0e, 0x90, 0x08, - 0x86, 0x93, 0x20, 0x13, 0xee, 0x9d, 0x0c, 0xfa, 0x2d, 0x98, 0xa6, 0xe9, 0x59, 0x79, 0xec, 0xc6, - 0x8c, 0x9a, 0xb2, 0xb5, 0x24, 0xbe, 0x68, 0xe1, 0xfd, 0x0c, 0xfa, 0x0a, 0xa6, 0x36, 0x31, 0x0d, - 0x5e, 0x40, 0x6f, 0xc5, 0x7e, 0x85, 0x60, 0xbb, 0x2b, 0x8d, 0x6d, 0xd1, 0xe1, 0xb8, 0x37, 0x03, - 0xd5, 0x20, 0xc7, 0xd1, 0x03, 0x64, 0xc5, 0xf0, 0x83, 0x14, 0x02, 0xf3, 0x31, 0x02, 0xe4, 0xbc, - 0x83, 0x6a, 0x90, 0x97, 0x11, 0x14, 0x68, 0xc9, 0x10, 0xb6, 0x51, 0x2a, 0x26, 0x0b, 0xb8, 0x87, - 0x6c, 0xec, 0xef, 0x66, 0x33, 0xe8, 0x1e, 0x00, 0x7b, 0x54, 0x46, 0xc7, 0x12, 0xef, 0x68, 0x29, - 0xc1, 0x40, 0xb4, 0x49, 0x74, 0x4b, 0x07, 0x87, 0xf8, 0xbc, 0x83, 0x37, 0xcd, 0xd6, 0x0e, 0xcc, - 0xc9, 0x27, 0x5e, 0xe7, 0xe7, 0x84, 0x89, 0xda, 0xe7, 0x64, 0x05, 0xb1, 0x97, 0xd1, 0x32, 0x64, - 0x11, 0x99, 0x82, 0x18, 0xe5, 0x74, 0xb2, 0x6a, 0x0a, 0xae, 0xcc, 0x7d, 0x2b, 0x71, 0xe3, 0xd9, - 0x70, 0x63, 0xb8, 0x18, 0x4a, 0x6a, 0xbb, 0x7a, 0xf8, 0x22, 0xba, 0xa9, 0x74, 0x20, 0x35, 0xea, - 0xb2, 0xf4, 0xd6, 0x90, 0x1a, 0x6c, 0x9e, 0xa8, 0xd6, 0x79, 0x08, 0xb3, 0x5a, 0xc0, 0x1b, 0x12, - 0x91, 0xf3, 0x69, 0x11, 0x89, 0xa5, 0xe5, 0xf4, 0x42, 0x7e, 0x2c, 0x7e, 0x40, 0x95, 0x4d, 0x2c, - 0xd7, 0x5c, 0x29, 0x2d, 0xa7, 0x1c, 0x3f, 0x9c, 0x8b, 0xec, 0x23, 0x31, 0x94, 0x0d, 0x98, 0x97, - 0xd7, 0x1d, 0x4a, 0x02, 0x7f, 0x43, 0x76, 0x3a, 0xe3, 0xcc, 0x7d, 0x03, 0xf3, 0x5c, 0x0e, 0x34, - 0x32, 0x05, 0xa9, 0x5c, 0x78, 0x22, 0x33, 0x23, 0x81, 0x87, 0xb0, 0x58, 0x8f, 0x8d, 0x87, 0x59, - 0x51, 0xd7, 0x74, 0x12, 0x4a, 0x5e, 0x3a, 0x23, 0xad, 0x47, 0x80, 0xea, 0xfd, 0xc3, 0x53, 0x57, - 0x92, 0x7b, 0xee, 0xe2, 0x17, 0xe8, 0x46, 0x6c, 0x48, 0x04, 0x48, 0xab, 0x51, 0xed, 0x54, 0x32, - 0x8c, 0x18, 0xed, 0xb3, 0xa7, 0xf2, 0x2c, 0x61, 0x8f, 0xd3, 0x73, 0x0e, 0xdd, 0x8e, 0x1b, 0xba, - 0x98, 0x88, 0x85, 0x8a, 0xa0, 0x16, 0x89, 0x19, 0xbc, 0x66, 0xac, 0x81, 0xbe, 0x86, 0xd9, 0x4d, - 0x1c, 0x46, 0xa9, 0xf7, 0xd0, 0x52, 0x22, 0x59, 0x1f, 0x9f, 0x37, 0x71, 0xf1, 0xad, 0xe7, 0xfb, - 0xdb, 0x86, 0x02, 0x53, 0xae, 0x0a, 0x89, 0x1b, 0x09, 0x12, 0xbc, 0x8a, 0xe3, 0x3b, 0xa7, 0x81, - 0x91, 0x5b, 0xf7, 0x60, 0x7c, 0xcf, 0xed, 0x1e, 0x21, 0xe1, 0xc7, 0x56, 0x52, 0x57, 0x95, 0xe6, - 0x35, 0x18, 0x17, 0xbd, 0x43, 0x28, 0xb3, 0x9c, 0x73, 0xc9, 0x3c, 0x6f, 0x22, 0xb1, 0xf6, 0xdb, - 0x32, 0x4e, 0x75, 0x48, 0x6e, 0x3a, 0xc9, 0x9f, 0x78, 0x79, 0x63, 0x0d, 0xed, 0x51, 0xae, 0x27, - 0x1b, 0x40, 0xb7, 0xa2, 0xfd, 0xd4, 0x98, 0x66, 0xae, 0x84, 0xe2, 0x84, 0x1b, 0x6b, 0x48, 0xbe, - 0xb0, 0x4f, 0x21, 0x7a, 0x5b, 0xdb, 0xf6, 0x2f, 0x46, 0xf7, 0x6b, 0xc8, 0xcb, 0x1c, 0x6b, 0x52, - 0xdf, 0xc4, 0x13, 0xc3, 0x49, 0x05, 0x9e, 0x4c, 0xc7, 0xf6, 0x25, 0x4b, 0x87, 0xa8, 0xe3, 0xc7, - 0xd3, 0x90, 0x19, 0x27, 0xef, 0x33, 0x98, 0x56, 0x12, 0x90, 0xc9, 0xc5, 0x92, 0x4c, 0x4a, 0x56, - 0xd2, 0x7f, 0xa8, 0xe5, 0x3e, 0xd9, 0x34, 0xa6, 0x78, 0xc6, 0x4b, 0xb4, 0x18, 0xa1, 0x29, 0x59, - 0x9c, 0x62, 0x28, 0x68, 0x8d, 0xee, 0x77, 0xac, 0xa1, 0xab, 0x3a, 0x86, 0xb9, 0x95, 0x35, 0x00, - 0x36, 0x66, 0xda, 0x90, 0x5e, 0x6c, 0x1c, 0xd5, 0x1a, 0xd9, 0xcf, 0xda, 0x17, 0x44, 0xfa, 0x5a, - 0xec, 0x69, 0x14, 0xa9, 0xa8, 0x71, 0x52, 0x1d, 0x95, 0x09, 0x7f, 0x1b, 0x0a, 0x95, 0x16, 0xd5, - 0xb2, 0x32, 0xb1, 0x14, 0x5a, 0x91, 0x2b, 0x58, 0x2f, 0x10, 0xb4, 0x16, 0xe3, 0x79, 0xaa, 0x76, - 0xb0, 0x43, 0x23, 0xe9, 0x96, 0xe4, 0x5e, 0x1b, 0x2b, 0x4a, 0xc7, 0x30, 0x76, 0x6a, 0x03, 0x16, - 0x6a, 0x4e, 0xb7, 0x85, 0x3b, 0xaf, 0x47, 0xe6, 0x73, 0xaa, 0x6e, 0x94, 0xa4, 0x5b, 0x57, 0xe3, - 0xf8, 0x5c, 0xdb, 0x5c, 0x91, 0x47, 0x45, 0x59, 0xb5, 0x02, 0x97, 0x19, 0x13, 0x23, 0xb6, 0x98, - 0xb0, 0x4d, 0xcd, 0x7f, 0x02, 0x73, 0x1b, 0x44, 0x1d, 0xf7, 0xdb, 0x2e, 0x0b, 0xba, 0x46, 0x7a, - 0x14, 0xad, 0x11, 0x71, 0x4b, 0x24, 0x30, 0x54, 0xb2, 0x51, 0x49, 0x21, 0x4f, 0x26, 0xfc, 0x2a, - 0x2d, 0x08, 0xb2, 0x6a, 0xe2, 0x2a, 0xba, 0xf7, 0x1e, 0x89, 0x8c, 0x27, 0xb1, 0x1c, 0x43, 0xaa, - 0x42, 0x31, 0x66, 0x20, 0x2a, 0xbd, 0x3d, 0xbc, 0x92, 0x6a, 0x8b, 0xd9, 0xb0, 0x64, 0xc8, 0xdf, - 0x84, 0xde, 0x91, 0x66, 0xf1, 0xb0, 0xfc, 0x4e, 0x29, 0xe6, 0xda, 0x77, 0x4a, 0x16, 0x0f, 0x03, - 0xcd, 0xe1, 0x89, 0x9d, 0x8c, 0x0c, 0x96, 0x81, 0x85, 0xa9, 0x09, 0x98, 0xd0, 0xbb, 0x3a, 0xf5, - 0x21, 0x49, 0x9a, 0x8c, 0x2d, 0x3c, 0xa1, 0xa2, 0x17, 0xe5, 0xff, 0x91, 0x46, 0x4f, 0x5a, 0x92, - 0x26, 0x69, 0xf4, 0xa4, 0x66, 0x4f, 0x62, 0x0c, 0xde, 0x84, 0xcb, 0xb1, 0x54, 0x48, 0xe8, 0x46, - 0x9c, 0xb1, 0x23, 0x18, 0xca, 0x08, 0x3d, 0x16, 0x82, 0x9d, 0x24, 0x94, 0x9e, 0x1c, 0xc9, 0x34, - 0x46, 0x46, 0xee, 0x40, 0x9a, 0x40, 0x6a, 0xba, 0x23, 0xf4, 0x56, 0x0a, 0x0b, 0xcf, 0xc7, 0x3a, - 0x46, 0xb6, 0x0e, 0x85, 0x78, 0xb6, 0x20, 0xb4, 0x22, 0xb9, 0x94, 0x9a, 0x12, 0xa9, 0x54, 0x36, - 0x96, 0xf3, 0x4d, 0xe7, 0x61, 0x34, 0x29, 0xec, 0x1a, 0x2c, 0x3e, 0x29, 0x6a, 0xa6, 0x98, 0xc4, - 0xa4, 0xe8, 0x09, 0x60, 0x36, 0x69, 0x38, 0xb4, 0x92, 0xf6, 0xc7, 0x78, 0x3a, 0xbd, 0x91, 0x46, - 0x27, 0xba, 0xe9, 0xa9, 0x8b, 0x84, 0xaa, 0x4a, 0xbf, 0x56, 0xb4, 0x7d, 0x33, 0xd9, 0xb5, 0xb2, - 0xb1, 0x5c, 0x8e, 0xb4, 0x10, 0x4f, 0x80, 0x23, 0x89, 0x1a, 0x32, 0xe3, 0x18, 0x45, 0xf9, 0x01, - 0x2c, 0xe8, 0xb3, 0x38, 0x62, 0xbc, 0x66, 0x5b, 0x77, 0x56, 0x4b, 0x7b, 0x83, 0xc4, 0xad, 0x5c, - 0x2c, 0xc3, 0x4e, 0x82, 0xfb, 0x29, 0xe9, 0x77, 0x18, 0xf7, 0x95, 0x14, 0x3a, 0xe7, 0xe1, 0x7e, - 0x5a, 0xc6, 0x1d, 0xc9, 0x28, 0xa5, 0x5f, 0x62, 0xfb, 0x8b, 0x17, 0x5c, 0x84, 0x51, 0xe7, 0xe9, - 0x9a, 0x89, 0xce, 0x3a, 0xb5, 0x6e, 0xe4, 0xef, 0x54, 0x5d, 0xd3, 0xd8, 0xa4, 0x49, 0x7c, 0x49, - 0x1b, 0x9c, 0x2e, 0xec, 0x35, 0x98, 0x51, 0x33, 0xf8, 0x18, 0x7b, 0x71, 0x3d, 0x49, 0x23, 0x50, - 0xce, 0x5b, 0x73, 0x92, 0x0b, 0xac, 0x37, 0xcb, 0x71, 0xe6, 0x68, 0x1d, 0x32, 0x0f, 0x09, 0xa9, - 0xac, 0x19, 0xd1, 0x25, 0xb3, 0x59, 0x30, 0xcf, 0x0c, 0x24, 0xfd, 0x67, 0xcc, 0x0c, 0xbf, 0x86, - 0x66, 0x24, 0x73, 0x40, 0x1f, 0x59, 0xa8, 0xe9, 0x78, 0x90, 0x22, 0x25, 0x29, 0x69, 0x7a, 0x4a, - 0x2b, 0xa6, 0x62, 0x55, 0x43, 0x7f, 0x0b, 0x57, 0x12, 0x69, 0x87, 0xa4, 0x23, 0xcc, 0x94, 0x90, - 0x68, 0xb8, 0x16, 0xdc, 0x22, 0x03, 0x8e, 0x21, 0x36, 0x56, 0x47, 0x13, 0x4d, 0xee, 0xa5, 0x3b, - 0xe2, 0x5d, 0x46, 0x5a, 0xe7, 0x4c, 0xc9, 0x8d, 0x8c, 0x1c, 0xdc, 0x87, 0xc5, 0xd4, 0xb4, 0x46, - 0xd2, 0xac, 0x18, 0x96, 0xf4, 0xc8, 0x48, 0xf5, 0x2f, 0xd2, 0xb4, 0xc2, 0xb1, 0x94, 0x39, 0xd2, - 0x0f, 0x61, 0x4c, 0x93, 0x24, 0xfd, 0x10, 0xe6, 0xfc, 0x45, 0x8c, 0x9b, 0x3b, 0xb0, 0x90, 0x96, - 0x84, 0x48, 0xf1, 0xdb, 0x19, 0x33, 0x14, 0xa5, 0x70, 0xd4, 0x16, 0xab, 0xdd, 0x40, 0x6d, 0x48, - 0x4a, 0x22, 0x23, 0x07, 0xbe, 0x17, 0x89, 0xa6, 0x92, 0xa9, 0x83, 0xe4, 0x69, 0x6d, 0x44, 0x6e, - 0xa1, 0x21, 0x46, 0xe5, 0xe5, 0xba, 0x7b, 0xd4, 0x55, 0xb2, 0xfc, 0x48, 0x93, 0x32, 0x99, 0x5e, - 0x48, 0x6a, 0x96, 0xb4, 0xa4, 0x40, 0x4f, 0x60, 0x41, 0x6c, 0xb1, 0x6a, 0x56, 0x1c, 0x94, 0xc0, - 0x89, 0xde, 0x52, 0x48, 0x2d, 0x93, 0x9a, 0x46, 0x87, 0x9d, 0xc9, 0xe8, 0xef, 0x20, 0x29, 0x67, - 0x32, 0x25, 0x5d, 0x4d, 0x49, 0xcf, 0x6c, 0x83, 0xbe, 0xa0, 0x67, 0x32, 0x96, 0xbd, 0xd0, 0xec, - 0x14, 0xd6, 0x28, 0x45, 0x3a, 0x6d, 0x4d, 0xb8, 0x0d, 0x69, 0x83, 0x3a, 0xe5, 0xd1, 0xc7, 0x2c, - 0x8a, 0xa4, 0x1f, 0xb3, 0xd4, 0x8e, 0x9a, 0x9d, 0x33, 0x33, 0xea, 0x03, 0x6d, 0xc9, 0xab, 0x94, - 0x4c, 0x0e, 0x92, 0x57, 0x69, 0xf9, 0x11, 0xa8, 0x55, 0xbf, 0x2f, 0x4c, 0xb8, 0x88, 0xde, 0x8d, - 0xa1, 0x09, 0x0e, 0x4a, 0x2b, 0xc3, 0xb3, 0x02, 0xf0, 0xdb, 0x81, 0x42, 0xfc, 0x0d, 0x39, 0x4a, - 0xcb, 0x4f, 0xa1, 0x3c, 0xa4, 0x97, 0x86, 0x88, 0xf1, 0xf1, 0xf9, 0x9e, 0x30, 0x0f, 0x75, 0xba, - 0x86, 0x2c, 0x05, 0x2a, 0xe9, 0xe1, 0x66, 0x44, 0xf4, 0x9c, 0x5c, 0x35, 0xe2, 0x12, 0xcf, 0xd5, - 0x55, 0x33, 0x22, 0xe5, 0x05, 0xba, 0x2b, 0xc2, 0x54, 0xd2, 0x73, 0x1e, 0xbd, 0xab, 0x9b, 0x59, - 0x43, 0x02, 0x78, 0x47, 0xde, 0xbf, 0xa0, 0xdf, 0x15, 0xc9, 0x1c, 0x93, 0x19, 0x41, 0xde, 0x89, - 0xf9, 0x61, 0xd2, 0x43, 0x3e, 0x4b, 0xc3, 0x12, 0x8e, 0xa0, 0xc7, 0xf4, 0x59, 0xdf, 0x93, 0xed, - 0xf5, 0x1a, 0xff, 0xb5, 0x53, 0xcf, 0x4f, 0x38, 0xb8, 0x95, 0x5f, 0xec, 0x88, 0x98, 0xcc, 0xaa, - 0x68, 0x88, 0x8d, 0x35, 0x54, 0xa7, 0x7e, 0x56, 0x0d, 0x9a, 0xe2, 0xe3, 0x4e, 0x21, 0x58, 0x4a, - 0x27, 0x48, 0x9d, 0xfe, 0x1b, 0x62, 0x37, 0xd3, 0xbb, 0x69, 0xe8, 0xc3, 0x30, 0x2b, 0x80, 0x89, - 0x4d, 0x3a, 0x19, 0xd1, 0xbb, 0x51, 0x72, 0xc4, 0x38, 0x56, 0xaf, 0x3c, 0xde, 0x79, 0x25, 0x8e, - 0x69, 0x88, 0x8d, 0x55, 0xce, 0x31, 0x0d, 0x7a, 0x31, 0x8e, 0xc5, 0x08, 0xea, 0x1c, 0xd3, 0xbb, - 0x69, 0xe8, 0xc3, 0x68, 0x8e, 0xa5, 0x93, 0x39, 0x2f, 0xc7, 0xbe, 0xa5, 0xfb, 0xf3, 0x26, 0x7d, - 0x64, 0x76, 0x21, 0x9e, 0x15, 0x85, 0x05, 0xab, 0xa3, 0x36, 0xd6, 0xd0, 0x53, 0x9a, 0xb1, 0x32, - 0x06, 0x3f, 0x1f, 0xdf, 0x96, 0x4d, 0x44, 0x29, 0xe7, 0xb6, 0x61, 0x91, 0x71, 0x2e, 0xde, 0x5d, - 0x63, 0x5f, 0x8c, 0xc3, 0xde, 0x14, 0xc6, 0x4e, 0x9c, 0xd4, 0x45, 0xf9, 0xb7, 0x4e, 0x45, 0x64, - 0xdf, 0x27, 0xf6, 0x69, 0x3b, 0x69, 0xbc, 0xea, 0x44, 0x84, 0x67, 0x5c, 0xaf, 0xde, 0x58, 0x45, - 0xdb, 0x74, 0x16, 0x74, 0xf0, 0x30, 0xeb, 0x3e, 0x9d, 0x0c, 0x65, 0xd2, 0x96, 0x30, 0x88, 0x62, - 0x7d, 0x32, 0xb5, 0x6d, 0xee, 0x94, 0x3c, 0xfa, 0x9c, 0x73, 0x74, 0x26, 0x16, 0xb1, 0x8d, 0x9d, - 0x9d, 0x34, 0x46, 0x71, 0x26, 0xfe, 0x4b, 0xe1, 0xe8, 0x77, 0x20, 0x2f, 0x90, 0x47, 0x33, 0x24, - 0x8e, 0x4d, 0x19, 0xf2, 0x35, 0x4c, 0x73, 0x86, 0xd0, 0x1e, 0x98, 0x5a, 0x32, 0x76, 0xff, 0x2b, - 0x98, 0xe6, 0x6c, 0x18, 0x3a, 0x02, 0xf3, 0xb5, 0xe2, 0xe2, 0x26, 0x0e, 0x53, 0x7e, 0xc9, 0x76, - 0xd4, 0x60, 0xd2, 0x7e, 0x38, 0x17, 0x35, 0xe8, 0xf3, 0x5c, 0xd3, 0xaf, 0x0e, 0x9b, 0x48, 0x8e, - 0xfc, 0xcd, 0x63, 0x42, 0xb7, 0x6e, 0xa6, 0x3b, 0x12, 0xdf, 0x38, 0xfa, 0x5d, 0x58, 0xa6, 0x77, - 0x10, 0x17, 0xed, 0xb1, 0xf9, 0x90, 0x72, 0x2d, 0x0a, 0x3f, 0x88, 0xff, 0xe0, 0xb1, 0x89, 0xd8, - 0xa8, 0xdf, 0x5a, 0x26, 0x54, 0xeb, 0x46, 0xaa, 0xa3, 0xb0, 0x87, 0x6c, 0x46, 0xd7, 0xe9, 0xd8, - 0x2f, 0xd8, 0xdb, 0xe1, 0x9a, 0x26, 0xf6, 0x0b, 0xcc, 0xa3, 0xa2, 0x27, 0xe2, 0xbf, 0xf1, 0x4c, - 0xa8, 0xd4, 0x13, 0x54, 0x4c, 0xb5, 0x87, 0x6d, 0x3e, 0x74, 0x68, 0xe7, 0xec, 0x8d, 0xf9, 0x72, - 0x24, 0x2f, 0x73, 0x97, 0x20, 0xc5, 0xb6, 0xd7, 0x32, 0x73, 0x94, 0x66, 0xd5, 0x58, 0x87, 0x00, - 0x55, 0xd8, 0x1e, 0xaf, 0xe6, 0xf0, 0x50, 0xbc, 0x88, 0xa9, 0xc9, 0x3d, 0xe2, 0x24, 0xd8, 0xd9, - 0x84, 0xfe, 0x2c, 0xb4, 0x72, 0x36, 0x51, 0xb2, 0x1b, 0x94, 0xf4, 0xdc, 0x03, 0x5c, 0x85, 0xd1, - 0x04, 0x04, 0xea, 0x7d, 0x91, 0x9a, 0xdf, 0x40, 0x3d, 0x9b, 0xe8, 0x99, 0x18, 0xe4, 0xd9, 0x84, - 0x36, 0xa8, 0x53, 0x1e, 0x7d, 0x36, 0xa1, 0x48, 0xfa, 0xd9, 0x44, 0xed, 0xa8, 0x79, 0xe1, 0xa1, - 0x64, 0x2a, 0x06, 0x79, 0xee, 0x36, 0x66, 0x69, 0x18, 0x72, 0xa5, 0x34, 0x9f, 0x92, 0x74, 0x47, - 0xda, 0xfc, 0xe6, 0x84, 0x3c, 0x25, 0xfd, 0x7e, 0xe4, 0x7e, 0x06, 0xed, 0xd2, 0xbc, 0xd1, 0x69, - 0xbf, 0x96, 0x6d, 0x92, 0x9f, 0xa1, 0x3f, 0xcf, 0x4d, 0xe8, 0xd5, 0xd3, 0xe9, 0x0d, 0xc5, 0x1b, - 0x72, 0xac, 0xbb, 0xc6, 0x23, 0x4a, 0x2e, 0xd0, 0x45, 0xb3, 0x88, 0x4f, 0x31, 0x1f, 0xb1, 0x19, - 0xb5, 0xa0, 0xfe, 0x3c, 0x35, 0xdd, 0xb2, 0xee, 0xc2, 0x24, 0x43, 0x32, 0xee, 0x36, 0xda, 0x4f, - 0x5a, 0xa3, 0x0f, 0xc4, 0xc5, 0x2d, 0x41, 0xd1, 0x8a, 0x8c, 0xfd, 0xfa, 0x00, 0xf2, 0xcc, 0xed, - 0x76, 0x7e, 0x94, 0x2f, 0xc4, 0xf5, 0xee, 0xb0, 0x8e, 0x99, 0xa3, 0x2a, 0x66, 0x55, 0x87, 0xf3, - 0xc5, 0x19, 0xf9, 0x15, 0x75, 0x7d, 0x0a, 0x57, 0x83, 0x19, 0x7f, 0x31, 0xf1, 0xf3, 0xe3, 0x94, - 0xa5, 0x9f, 0x52, 0xff, 0xab, 0xcc, 0x5e, 0x65, 0xea, 0x7e, 0xf2, 0xc7, 0xcb, 0xd1, 0x17, 0x30, - 0xc7, 0x98, 0x2b, 0x91, 0x93, 0x95, 0x86, 0xf0, 0x6c, 0x8e, 0xb1, 0xf9, 0x55, 0x90, 0x7f, 0x47, - 0x38, 0x6a, 0x47, 0x76, 0xfb, 0x3c, 0x2e, 0xda, 0xd1, 0xac, 0x33, 0x51, 0xf9, 0x5d, 0xba, 0xe9, - 0xa6, 0xa7, 0x5c, 0x31, 0x12, 0xbb, 0xa3, 0xb8, 0xa0, 0x87, 0x27, 0x6b, 0x39, 0xa1, 0xd1, 0x81, - 0xe9, 0x3f, 0x60, 0x7f, 0x7b, 0x04, 0x15, 0xc1, 0x80, 0x9f, 0x8d, 0xac, 0x27, 0xfd, 0x5c, 0x3c, - 0x69, 0x79, 0x7a, 0x7b, 0x23, 0x12, 0xaf, 0xa4, 0xb8, 0x0c, 0x0d, 0xf9, 0x4c, 0x04, 0x41, 0xfd, - 0xda, 0x71, 0xe8, 0x18, 0xcc, 0x47, 0xb4, 0x28, 0xf7, 0xf8, 0x05, 0x27, 0xc1, 0x6c, 0x46, 0xa1, - 0x64, 0x96, 0x17, 0x34, 0xec, 0x45, 0x95, 0xea, 0x90, 0x35, 0x65, 0x87, 0xd9, 0x14, 0xa1, 0xa8, - 0xb1, 0xe7, 0x89, 0xa6, 0x87, 0x8e, 0x43, 0x4e, 0x67, 0x3c, 0x18, 0xf3, 0x8d, 0x10, 0x4a, 0xce, - 0xf6, 0xc5, 0x09, 0x49, 0xc7, 0x70, 0x8c, 0x90, 0x35, 0x64, 0x7a, 0x47, 0x3b, 0xbd, 0x8a, 0x86, - 0x79, 0xbd, 0xf8, 0x84, 0x3a, 0x51, 0xd8, 0x5f, 0x32, 0x15, 0x8d, 0xdc, 0xf6, 0x8d, 0x69, 0x71, - 0xe4, 0xec, 0x0e, 0xc9, 0x63, 0x53, 0x8b, 0x7e, 0xeb, 0x45, 0xcb, 0x5d, 0x53, 0xb3, 0x77, 0xa4, - 0xbb, 0x2e, 0x2d, 0xa9, 0x4d, 0x09, 0x44, 0xa1, 0xbd, 0x43, 0xd6, 0xba, 0x29, 0xef, 0x4a, 0x14, - 0xba, 0x34, 0x3c, 0x2b, 0x8d, 0x5c, 0xeb, 0x23, 0x13, 0xb8, 0xec, 0xc2, 0x42, 0x5a, 0xbe, 0x14, - 0x39, 0x69, 0x43, 0x92, 0xa9, 0xa4, 0xc6, 0x47, 0xed, 0xc1, 0x62, 0x6a, 0xce, 0x12, 0x79, 0x43, - 0x32, 0x2c, 0xa3, 0x49, 0x2a, 0xc5, 0xef, 0x60, 0xc9, 0x90, 0xa0, 0x23, 0x72, 0x20, 0x0e, 0x4d, - 0xe0, 0x61, 0x14, 0x88, 0x1f, 0xa0, 0x64, 0xce, 0xfd, 0x80, 0xee, 0xe8, 0x4e, 0x50, 0x73, 0xc6, - 0x85, 0x52, 0x6a, 0xb2, 0x1a, 0xb4, 0x4f, 0xf3, 0xe8, 0xa5, 0x25, 0x83, 0x90, 0xfd, 0x1e, 0x9e, - 0x2c, 0xc2, 0x10, 0xd7, 0xb6, 0x64, 0xc8, 0xff, 0x30, 0x84, 0xea, 0x39, 0x7a, 0xbb, 0x2b, 0xf4, - 0x92, 0x9e, 0x10, 0x20, 0x16, 0x22, 0x9f, 0x9a, 0x2d, 0x20, 0xb5, 0x9f, 0x0f, 0x61, 0x56, 0x7b, - 0xe0, 0x29, 0xc5, 0x3f, 0xed, 0x59, 0xb0, 0xf4, 0x56, 0xa7, 0xbe, 0x09, 0xad, 0x16, 0x7e, 0xf9, - 0x3f, 0x56, 0x32, 0xbf, 0xfc, 0xf5, 0x4a, 0xe6, 0xbf, 0xfd, 0x7a, 0x25, 0xf3, 0xab, 0x5f, 0xaf, - 0x64, 0x0e, 0x27, 0x69, 0xf5, 0xb5, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x07, 0x03, 0xcc, - 0x29, 0x90, 0x00, 0x00, + 0xc5, 0x40, 0x4f, 0x61, 0xce, 0xc6, 0x81, 0xd7, 0xf7, 0x5b, 0x78, 0x1b, 0x3b, 0x6d, 0xec, 0x17, + 0xb3, 0x37, 0x32, 0xb7, 0xa7, 0xd7, 0x96, 0xee, 0x30, 0xa6, 0xe9, 0x85, 0xd5, 0x2b, 0x67, 0x83, + 0x32, 0xf2, 0x39, 0x2c, 0x22, 0xb6, 0x7d, 0xc9, 0x8e, 0x91, 0x41, 0xdf, 0xc3, 0x6c, 0x0d, 0xfb, + 0x61, 0xa5, 0x1f, 0x1e, 0x7b, 0xbe, 0x1b, 0xbe, 0x2c, 0x8e, 0x51, 0xba, 0x57, 0x38, 0x5d, 0xad, + 0xac, 0xb1, 0x56, 0x5d, 0x39, 0x1b, 0x94, 0x8b, 0x64, 0x82, 0x9b, 0x8e, 0x80, 0x6a, 0xe4, 0x75, + 0x62, 0xe8, 0x3b, 0x98, 0xa9, 0x13, 0x76, 0xb5, 0x0e, 0xbc, 0x13, 0xdc, 0x0d, 0x8a, 0xe3, 0x5a, + 0xa7, 0xd5, 0xa2, 0xc6, 0x5a, 0xf5, 0xda, 0xd9, 0xa0, 0xbc, 0x1c, 0x50, 0x58, 0x33, 0xa4, 0x40, + 0x8d, 0xb4, 0x46, 0x09, 0xfd, 0x00, 0x73, 0xfb, 0xbe, 0xf7, 0xdc, 0x0d, 0x5c, 0xaf, 0x4b, 0x41, + 0xc5, 0x09, 0x4a, 0x7b, 0x99, 0xd3, 0xd6, 0x0b, 0x1b, 0x6b, 0xd5, 0xeb, 0x67, 0x83, 0xf2, 0xd5, + 0x9e, 0x80, 0xb2, 0x06, 0x74, 0xce, 0xe8, 0x28, 0xe8, 0x00, 0xa6, 0x6b, 0x9d, 0x7e, 0x10, 0x62, + 0x7f, 0xcf, 0x39, 0xc5, 0xc5, 0x49, 0x4a, 0x7e, 0x51, 0xf0, 0x25, 0x2a, 0x69, 0xac, 0x55, 0x4b, + 0x67, 0x83, 0xf2, 0x95, 0x16, 0x03, 0x35, 0xbb, 0xce, 0xa9, 0xce, 0x72, 0x95, 0x0c, 0xfa, 0x04, + 0xc6, 0x9f, 0x04, 0xd8, 0x2f, 0xe6, 0x28, 0xb9, 0x59, 0x4e, 0x8e, 0x80, 0x1a, 0x6b, 0x6c, 0xfe, + 0xfb, 0x01, 0xf6, 0x35, 0x7c, 0x8a, 0x40, 0x10, 0x6d, 0xaf, 0x83, 0x8b, 0x79, 0x0d, 0x91, 0x80, + 0x1a, 0x1f, 0x31, 0x44, 0xdf, 0xeb, 0xe8, 0x0d, 0x53, 0x04, 0xb4, 0x03, 0x79, 0xd2, 0x72, 0xd0, + 0x73, 0x5a, 0xb8, 0x08, 0x14, 0xbb, 0xc0, 0xb1, 0x25, 0xbc, 0xba, 0x7c, 0x36, 0x28, 0x2f, 0x74, + 0xc5, 0xa7, 0x46, 0x25, 0xc2, 0x46, 0xdf, 0xc0, 0x64, 0x1d, 0xfb, 0xcf, 0xb1, 0x5f, 0x9c, 0xa6, + 0x74, 0x2e, 0x8b, 0x89, 0xa4, 0xc0, 0xc6, 0x5a, 0x75, 0xf1, 0x6c, 0x50, 0x2e, 0x04, 0xf4, 0x4b, + 0xa3, 0xc1, 0xd1, 0x88, 0xb4, 0xd9, 0xf8, 0x39, 0xf6, 0x03, 0x7c, 0xd0, 0xef, 0x76, 0x71, 0xa7, + 0x38, 0xa3, 0x49, 0x9b, 0x56, 0x26, 0xa4, 0xcd, 0x67, 0xc0, 0x66, 0x48, 0xa1, 0xba, 0xb4, 0x69, + 0x08, 0xe8, 0x18, 0x0a, 0xec, 0xaf, 0x9a, 0xd7, 0xed, 0xe2, 0x16, 0x59, 0x52, 0xc5, 0x59, 0xda, + 0xc0, 0x55, 0xde, 0x40, 0xbc, 0xb8, 0xb1, 0x56, 0x2d, 0x9f, 0x0d, 0xca, 0xd7, 0x18, 0xed, 0x66, + 0x4b, 0x16, 0x68, 0xcd, 0x24, 0xa8, 0x92, 0x71, 0x54, 0x5a, 0x2d, 0x1c, 0x04, 0x36, 0xfe, 0x45, + 0x1f, 0x07, 0x61, 0x71, 0x4e, 0x1b, 0x87, 0x56, 0xd6, 0x58, 0x67, 0xe3, 0x70, 0x28, 0xb0, 0xe9, + 0x33, 0xa8, 0x3e, 0x0e, 0x0d, 0x01, 0xed, 0x03, 0x54, 0x7a, 0xbd, 0x3a, 0x0e, 0x88, 0x30, 0x16, + 0x2f, 0x53, 0xd2, 0x0b, 0x9c, 0xf4, 0x53, 0x7c, 0xc8, 0x0b, 0x1a, 0x6b, 0xd5, 0xab, 0x67, 0x83, + 0xf2, 0x92, 0xd3, 0xeb, 0x35, 0x03, 0x06, 0xd2, 0x88, 0x2a, 0x34, 0x18, 0xdf, 0x4f, 0xbd, 0x10, + 0x73, 0x51, 0x2c, 0x16, 0x62, 0x7c, 0x57, 0xca, 0x44, 0x7f, 0x7d, 0x0a, 0x6c, 0x72, 0xb1, 0x8e, + 0xf3, 0x5d, 0x41, 0x20, 0x6b, 0x71, 0xc3, 0x09, 0x9d, 0x43, 0x27, 0xc0, 0x5c, 0x3c, 0xe6, 0xb5, + 0xb5, 0xa8, 0x17, 0x36, 0xd6, 0xd9, 0x5a, 0x6c, 0x73, 0x68, 0x33, 0x45, 0x5e, 0x62, 0xf4, 0x08, + 0x47, 0xa2, 0x81, 0x17, 0xd1, 0x08, 0x8e, 0xbc, 0xc0, 0x87, 0xe9, 0x1c, 0x89, 0xaa, 0xa2, 0x6d, + 0xc8, 0x3d, 0xc5, 0x87, 0x4c, 0x73, 0x2c, 0x50, 0x7a, 0xf3, 0x11, 0x3d, 0xa6, 0x33, 0xd6, 0xd9, + 0xaa, 0x20, 0xd4, 0x92, 0xda, 0x42, 0x62, 0xa3, 0xdf, 0xcd, 0xc0, 0xb2, 0x58, 0xe1, 0x38, 0x7c, + 0xe1, 0xf9, 0x27, 0x6e, 0xf7, 0xa8, 0xe6, 0x75, 0x9f, 0xb9, 0x47, 0xc5, 0x45, 0x4a, 0xf9, 0x46, + 0x4c, 0x69, 0xc4, 0x6a, 0x35, 0xd6, 0xaa, 0x3f, 0x39, 0x1b, 0x94, 0x6f, 0x4a, 0x05, 0x22, 0xcb, + 0x89, 0x40, 0x3e, 0x73, 0x8f, 0xb4, 0x86, 0x4d, 0x6d, 0xa1, 0xbf, 0x9e, 0x81, 0x2b, 0x7c, 0x74, + 0x36, 0x6e, 0x79, 0x7e, 0x3b, 0xea, 0xc6, 0x12, 0xed, 0x46, 0x59, 0xae, 0xd6, 0xb4, 0x4a, 0x8d, + 0xb5, 0xea, 0xad, 0xb3, 0x41, 0xd9, 0xe2, 0x8c, 0x6b, 0xfa, 0xa2, 0x38, 0xad, 0x13, 0x86, 0x86, + 0x88, 0x24, 0x10, 0xe5, 0xbf, 0xef, 0xe3, 0x67, 0xd8, 0xc7, 0xdd, 0x16, 0x2e, 0x5e, 0xd1, 0x24, + 0x41, 0x2f, 0x14, 0x5a, 0x99, 0x6c, 0x25, 0xcd, 0x9e, 0x04, 0xeb, 0x92, 0xa0, 0xa3, 0xa0, 0x5f, + 0x00, 0xe2, 0x0c, 0xa8, 0xf4, 0xdb, 0x6e, 0xc8, 0x07, 0xb8, 0x4c, 0x5b, 0xb9, 0xa6, 0xf3, 0x59, + 0xa9, 0xd0, 0x58, 0xab, 0x5a, 0x67, 0x83, 0xf2, 0xaa, 0x60, 0xb1, 0x43, 0x8a, 0xd2, 0x06, 0x96, + 0x42, 0x9c, 0x68, 0xde, 0x5d, 0xaf, 0x75, 0x52, 0x2c, 0x6a, 0x9a, 0x97, 0x80, 0x84, 0xca, 0xee, + 0x78, 0xad, 0x13, 0x5d, 0xf3, 0x92, 0x52, 0x14, 0xc2, 0x02, 0x9f, 0x25, 0x1b, 0x07, 0xa1, 0xef, + 0x52, 0xdd, 0x11, 0x14, 0xaf, 0x52, 0x3a, 0x2b, 0x42, 0x07, 0x27, 0x6b, 0x34, 0x3e, 0x64, 0xbd, + 0xe5, 0x82, 0xd0, 0xf4, 0x95, 0x32, 0xad, 0x99, 0x34, 0xf2, 0xe8, 0xaf, 0xc0, 0xd2, 0x53, 0xb7, + 0xdb, 0xf6, 0x5e, 0x04, 0x1b, 0x38, 0x38, 0x09, 0xbd, 0x5e, 0x9d, 0x59, 0x7e, 0xc5, 0x12, 0x6d, + 0x77, 0x55, 0x88, 0x79, 0x5a, 0x9d, 0xc6, 0x7a, 0xf5, 0x9d, 0xb3, 0x41, 0xf9, 0xad, 0x17, 0xac, + 0xb0, 0xd9, 0x66, 0xa5, 0x4d, 0x6e, 0x3c, 0x6a, 0x8d, 0xa7, 0xb7, 0x42, 0x44, 0x40, 0x2f, 0x28, + 0x5e, 0xd3, 0x44, 0x40, 0x2f, 0x14, 0xca, 0x20, 0xd6, 0xa0, 0x2e, 0x02, 0x3a, 0x0a, 0xda, 0x82, + 0x9c, 0x50, 0x0f, 0xc5, 0x15, 0x6d, 0xe9, 0x0a, 0x70, 0x63, 0x9d, 0x59, 0x40, 0x42, 0xc5, 0xe8, + 0x2b, 0x57, 0xd4, 0x42, 0xbb, 0x90, 0xa7, 0x3a, 0x92, 0xaa, 0xac, 0xeb, 0x94, 0x12, 0x12, 0x82, + 0x2a, 0xe0, 0x8d, 0xf5, 0x6a, 0xf1, 0x6c, 0x50, 0x5e, 0x64, 0x5a, 0x36, 0xa1, 0xa8, 0x22, 0x02, + 0x68, 0x1d, 0xc6, 0x2a, 0xbd, 0x5e, 0x71, 0x95, 0xd2, 0x99, 0x89, 0xe8, 0x34, 0xd6, 0xab, 0xf3, + 0x67, 0x83, 0xf2, 0xac, 0xd3, 0xd3, 0x87, 0x45, 0x6a, 0x57, 0x01, 0x72, 0xc2, 0x20, 0x7b, 0x30, + 0x9e, 0x9b, 0x2a, 0xe4, 0xac, 0x6d, 0x98, 0x78, 0xea, 0x84, 0xad, 0x63, 0xf4, 0x0d, 0x4c, 0x3c, + 0x74, 0xbb, 0xed, 0xa0, 0x98, 0xb9, 0x31, 0x46, 0xf7, 0x6c, 0x66, 0x2d, 0xd2, 0x42, 0x52, 0x50, + 0x5d, 0xfe, 0xe5, 0xa0, 0x7c, 0xe9, 0x6c, 0x50, 0xbe, 0x7c, 0x42, 0xaa, 0x29, 0x26, 0x23, 0xc3, + 0xb3, 0xfe, 0x5d, 0x16, 0xf2, 0xb2, 0x36, 0x5a, 0x81, 0x71, 0xf2, 0x3f, 0xb5, 0x3d, 0xf3, 0xd5, + 0xdc, 0xd9, 0xa0, 0x3c, 0x4e, 0xf0, 0x6c, 0x0a, 0x45, 0x6b, 0x30, 0xbd, 0xeb, 0x39, 0xed, 0x3a, + 0x6e, 0xf9, 0x38, 0x0c, 0xa8, 0x71, 0x99, 0xab, 0x16, 0xce, 0x06, 0xe5, 0x99, 0x8e, 0xe7, 0xb4, + 0x9b, 0x01, 0x83, 0xdb, 0x6a, 0x25, 0x42, 0x91, 0x5a, 0x46, 0x63, 0x11, 0x45, 0x62, 0x41, 0xd8, + 0x14, 0x8a, 0x1e, 0xc0, 0xe4, 0x7d, 0xb7, 0x43, 0xf6, 0x9a, 0x71, 0xda, 0xff, 0x95, 0x78, 0xff, + 0xef, 0xb0, 0xe2, 0xcd, 0x6e, 0xe8, 0xbf, 0x64, 0x86, 0xc3, 0x33, 0x0a, 0x50, 0x06, 0xc2, 0x29, + 0xa0, 0x7b, 0x30, 0x55, 0xef, 0x1f, 0xd2, 0xee, 0x4f, 0xd0, 0xc6, 0xe8, 0xec, 0x06, 0xfd, 0xc3, + 0x26, 0x19, 0x82, 0x82, 0x20, 0xaa, 0x95, 0x3e, 0x83, 0x69, 0x85, 0x3c, 0x2a, 0xc0, 0xd8, 0x09, + 0x7e, 0xc9, 0xc6, 0x6e, 0x93, 0x3f, 0xd1, 0x22, 0x4c, 0x3c, 0x77, 0x3a, 0x7d, 0x4c, 0x87, 0x9a, + 0xb7, 0xd9, 0xc7, 0xe7, 0xd9, 0x4f, 0x33, 0xd6, 0x7f, 0x1d, 0x87, 0xc2, 0xb6, 0x17, 0x84, 0xc4, + 0x92, 0x95, 0x5b, 0xf2, 0x4d, 0x98, 0x24, 0xb0, 0x9d, 0x0d, 0xce, 0xbf, 0xe9, 0xb3, 0x41, 0x79, + 0xea, 0xd8, 0x0b, 0xc2, 0xa6, 0xdb, 0xb6, 0x79, 0x11, 0x7a, 0x17, 0x72, 0x7b, 0x5e, 0x1b, 0x53, + 0xa6, 0x50, 0xb2, 0xd5, 0xd9, 0xb3, 0x41, 0x39, 0xdf, 0xf5, 0xda, 0x98, 0x5a, 0x85, 0xb6, 0x2c, + 0x46, 0x0d, 0x6e, 0xcd, 0x31, 0xde, 0x55, 0x09, 0xef, 0x88, 0xf9, 0xf6, 0xeb, 0x41, 0xf9, 0xe3, + 0x0b, 0x1c, 0x37, 0xee, 0xd4, 0x5f, 0x06, 0x21, 0x3e, 0x25, 0x94, 0xb8, 0xb1, 0xf7, 0x14, 0x16, + 0x2b, 0xed, 0xb6, 0xcb, 0x30, 0xf6, 0x7d, 0xb7, 0xdb, 0x72, 0x7b, 0x4e, 0x27, 0xa0, 0x73, 0x90, + 0xaf, 0xde, 0x3c, 0x1b, 0x94, 0xcb, 0x8e, 0x2c, 0x6f, 0xf6, 0x64, 0x05, 0x85, 0x87, 0xa9, 0x04, + 0xd0, 0x3a, 0xe4, 0x36, 0xf6, 0xea, 0xd4, 0x14, 0x2c, 0x4e, 0x50, 0x62, 0x74, 0x73, 0x6c, 0x77, + 0x03, 0x3a, 0x34, 0x95, 0x80, 0xac, 0x88, 0x3e, 0x86, 0x99, 0xfd, 0xfe, 0x61, 0xc7, 0x6d, 0x1d, + 0xec, 0xd6, 0x1f, 0xe2, 0x97, 0xd4, 0x86, 0x9e, 0x61, 0x2a, 0xb3, 0x47, 0xe1, 0xcd, 0xb0, 0x13, + 0x34, 0x4f, 0xf0, 0x4b, 0x5b, 0xab, 0x17, 0xe1, 0xd5, 0xeb, 0xdb, 0x04, 0x6f, 0x2a, 0x81, 0x17, + 0x04, 0xc7, 0x2a, 0x1e, 0xab, 0x87, 0xee, 0x02, 0x30, 0xcb, 0xa4, 0xd2, 0x6e, 0x33, 0x13, 0x3b, + 0x5f, 0xbd, 0x7c, 0x36, 0x28, 0x4f, 0x73, 0x5b, 0xc6, 0x69, 0xb7, 0x7d, 0x5b, 0xa9, 0x82, 0x6a, + 0x90, 0xb3, 0x3d, 0xc6, 0x60, 0x6e, 0x58, 0x5f, 0x96, 0x86, 0x35, 0x03, 0xf3, 0xa3, 0x14, 0xff, + 0x52, 0x47, 0x29, 0x6a, 0xa0, 0x32, 0x4c, 0xed, 0x79, 0x35, 0xa7, 0x75, 0xcc, 0xcc, 0xeb, 0x5c, + 0x75, 0xe2, 0x6c, 0x50, 0xce, 0xfc, 0xb6, 0x2d, 0xa0, 0xd6, 0xbf, 0xc8, 0x41, 0x81, 0xd8, 0xf0, + 0x9a, 0x44, 0xbd, 0x0f, 0x79, 0xd6, 0xf7, 0x87, 0x5c, 0x30, 0x67, 0xaa, 0x73, 0x67, 0x83, 0x32, + 0xf0, 0x01, 0x92, 0xc1, 0x45, 0x15, 0xd0, 0x6d, 0xc8, 0x11, 0x0a, 0xdd, 0x48, 0xb4, 0x66, 0xce, + 0x06, 0xe5, 0x5c, 0x9f, 0xc3, 0x6c, 0x59, 0x8a, 0xea, 0x30, 0xb5, 0xf9, 0x63, 0xcf, 0xf5, 0x71, + 0xc0, 0x8f, 0x72, 0xa5, 0x3b, 0xec, 0xc4, 0x7e, 0x47, 0x9c, 0xd8, 0xef, 0x1c, 0x88, 0x13, 0x7b, + 0xf5, 0x3a, 0x57, 0x21, 0xf3, 0x98, 0xa1, 0x44, 0xe3, 0xfb, 0xbd, 0x3f, 0x2d, 0x67, 0x6c, 0x41, + 0x09, 0xbd, 0x0f, 0x93, 0xf7, 0x3d, 0xff, 0xd4, 0x09, 0xe9, 0x09, 0x2e, 0xcf, 0x97, 0x2b, 0x85, + 0x68, 0xcb, 0x95, 0x42, 0xd0, 0x7d, 0x98, 0xb3, 0xbd, 0x7e, 0x88, 0x0f, 0x3c, 0x61, 0x6e, 0xb2, + 0x55, 0xbb, 0x7a, 0x36, 0x28, 0x97, 0x7c, 0x52, 0xd2, 0x0c, 0xbd, 0xa4, 0x61, 0x69, 0xc7, 0xb0, + 0xd0, 0x26, 0xcc, 0x69, 0x86, 0x71, 0x50, 0x9c, 0xa4, 0x92, 0xc7, 0x8c, 0x06, 0xcd, 0x9c, 0x56, + 0xe5, 0x2f, 0x86, 0x84, 0xf6, 0x60, 0xfe, 0x61, 0xff, 0x10, 0xfb, 0x5d, 0x1c, 0xe2, 0x40, 0xf4, + 0x68, 0x8a, 0xf6, 0xe8, 0xc6, 0xd9, 0xa0, 0xbc, 0x72, 0x22, 0x0b, 0x53, 0xfa, 0x94, 0x44, 0x45, + 0x18, 0x2e, 0xf3, 0x8e, 0xca, 0x6d, 0x28, 0xc7, 0xcd, 0x69, 0xa6, 0xe2, 0x62, 0xa5, 0xd5, 0x9b, + 0x9c, 0xcb, 0xd7, 0xe4, 0xd8, 0x93, 0x1b, 0x93, 0x1d, 0xa7, 0x49, 0x56, 0x9c, 0xd4, 0x26, 0x79, + 0xda, 0x5b, 0x76, 0x48, 0x13, 0xda, 0x44, 0x95, 0x45, 0xa9, 0x57, 0x76, 0x61, 0xe2, 0x49, 0xe0, + 0x1c, 0x31, 0x49, 0x9c, 0x5b, 0x7b, 0x8b, 0xf7, 0x28, 0x2e, 0x7d, 0xf4, 0x5c, 0x4f, 0x2b, 0x56, + 0x17, 0xc8, 0x0e, 0xd2, 0x27, 0x7f, 0xaa, 0x3b, 0x08, 0x2d, 0x43, 0xdf, 0x02, 0xf0, 0x5e, 0x91, + 0x9d, 0x6d, 0x9a, 0xef, 0xb5, 0xda, 0x20, 0xc9, 0x26, 0xb6, 0xca, 0xc7, 0x77, 0x45, 0x8e, 0x4f, + 0xdb, 0xeb, 0x6c, 0x85, 0x08, 0xfa, 0x06, 0x66, 0xa8, 0xba, 0x12, 0x33, 0x3a, 0x43, 0x67, 0x94, + 0x1e, 0xfd, 0x89, 0x02, 0x4c, 0x9b, 0x4f, 0x0d, 0x01, 0xfd, 0x55, 0x58, 0xe2, 0xe4, 0x62, 0x66, + 0xc6, 0x2c, 0x37, 0xab, 0xb4, 0xee, 0xe9, 0x75, 0xaa, 0xef, 0xf1, 0x9e, 0x5a, 0xb2, 0xa7, 0x46, + 0xc3, 0xc3, 0x4e, 0x6f, 0xc6, 0xfa, 0x0e, 0xf2, 0x92, 0x79, 0x68, 0x0a, 0xc6, 0x2a, 0x9d, 0x4e, + 0xe1, 0x12, 0xf9, 0xa3, 0x5e, 0xdf, 0x2e, 0x64, 0xd0, 0x1c, 0x40, 0x24, 0x31, 0x85, 0x2c, 0x9a, + 0x89, 0x8c, 0x95, 0xc2, 0x18, 0xad, 0xdf, 0xeb, 0x15, 0xc6, 0x11, 0x8a, 0x5b, 0x49, 0x85, 0x09, + 0xeb, 0xbf, 0x67, 0x12, 0x82, 0x45, 0xf6, 0x65, 0x6e, 0x58, 0x51, 0x39, 0x60, 0x9b, 0x0f, 0xdd, + 0x97, 0xb9, 0x49, 0xc6, 0x36, 0x16, 0xb5, 0x12, 0xd1, 0x15, 0xfb, 0x84, 0x07, 0x2d, 0xaf, 0xa3, + 0xea, 0x8a, 0x1e, 0x87, 0xd9, 0xb2, 0x14, 0xad, 0x29, 0x5a, 0x65, 0x2c, 0xda, 0x58, 0x85, 0x56, + 0x51, 0x25, 0x4c, 0xea, 0x97, 0x35, 0xc5, 0xfa, 0x1a, 0x8f, 0x70, 0x52, 0x24, 0x5a, 0xd6, 0xb3, + 0xfa, 0x86, 0x39, 0x43, 0x5f, 0x24, 0x8c, 0x45, 0x36, 0x42, 0x2a, 0x94, 0xb1, 0xa9, 0x49, 0xd8, + 0x81, 0x65, 0x98, 0xd8, 0xf5, 0x8e, 0xdc, 0x2e, 0x1f, 0x64, 0xfe, 0x6c, 0x50, 0x9e, 0xe8, 0x10, + 0x80, 0xcd, 0xe0, 0xd6, 0xff, 0xcd, 0xa8, 0xf2, 0x2b, 0xed, 0x95, 0x4c, 0xaa, 0xbd, 0xf2, 0x3e, + 0xe4, 0xf9, 0xa1, 0x66, 0x67, 0x83, 0x53, 0xa4, 0xfa, 0x58, 0x9c, 0x87, 0xdc, 0xb6, 0x1d, 0x55, + 0x20, 0x3b, 0x0d, 0x53, 0xce, 0x74, 0xa7, 0x19, 0x8b, 0x76, 0x1a, 0xae, 0xbe, 0xd9, 0x4e, 0x13, + 0x55, 0x21, 0x13, 0xa9, 0x7a, 0x93, 0xc6, 0xa3, 0x89, 0x54, 0xfd, 0x46, 0xba, 0xaf, 0xe8, 0x73, + 0x80, 0xca, 0xd3, 0x3a, 0x91, 0xfe, 0x8a, 0xbd, 0xc7, 0x75, 0x28, 0x75, 0x35, 0x39, 0x2f, 0x82, + 0x26, 0x5d, 0x2d, 0x8e, 0xaf, 0x6e, 0x49, 0x4a, 0x6d, 0xab, 0x03, 0x73, 0x5b, 0x38, 0x24, 0xb3, + 0x26, 0x36, 0x9c, 0xe1, 0xc3, 0xff, 0x12, 0xa6, 0x9f, 0xba, 0xe1, 0xb1, 0x6e, 0x00, 0xd2, 0xc6, + 0x5e, 0xb8, 0xe1, 0xb1, 0x30, 0x00, 0x95, 0xc6, 0xd4, 0xea, 0xd6, 0x26, 0x5c, 0xe6, 0xad, 0xc9, + 0xfd, 0x6d, 0x4d, 0x27, 0x98, 0x89, 0x2c, 0x4a, 0x95, 0xa0, 0x4e, 0x06, 0xc7, 0x15, 0x3e, 0xaa, + 0x27, 0xb6, 0x00, 0x66, 0x0d, 0x9b, 0x3c, 0x2d, 0x54, 0x70, 0x62, 0x5b, 0x43, 0x7c, 0x43, 0xb0, + 0x9e, 0xc0, 0xec, 0x7e, 0xa7, 0x7f, 0xe4, 0x76, 0x89, 0x80, 0xd6, 0xf1, 0x2f, 0xd0, 0x06, 0x40, + 0x04, 0xe0, 0x2d, 0x08, 0xf7, 0x42, 0x54, 0xd0, 0x58, 0xe7, 0x53, 0x4c, 0x21, 0x54, 0x87, 0xdb, + 0x0a, 0x9e, 0xf5, 0xf7, 0xc6, 0x00, 0xf1, 0x36, 0xea, 0xa1, 0x13, 0xe2, 0x3a, 0x0e, 0xc9, 0x76, + 0x71, 0x05, 0xb2, 0xd2, 0x6c, 0x9c, 0x3c, 0x1b, 0x94, 0xb3, 0x6e, 0xdb, 0xce, 0xee, 0x6c, 0xa0, + 0x0f, 0x61, 0x82, 0x56, 0xa3, 0xbc, 0x9e, 0x93, 0xed, 0xa9, 0x14, 0x98, 0x4c, 0x07, 0xe4, 0x4f, + 0x9b, 0x55, 0x46, 0x1f, 0x41, 0x7e, 0x03, 0x77, 0xf0, 0x91, 0x13, 0x7a, 0x42, 0xee, 0x98, 0x21, + 0x26, 0x80, 0xca, 0x14, 0x45, 0x35, 0xc9, 0x06, 0x6e, 0x63, 0x27, 0xf0, 0xba, 0xea, 0x06, 0xee, + 0x53, 0x88, 0xba, 0x81, 0xb3, 0x3a, 0xe8, 0xf7, 0x33, 0x30, 0x5d, 0xe9, 0x76, 0xb9, 0x81, 0x13, + 0x70, 0xd7, 0xea, 0xd2, 0x1d, 0xe9, 0x19, 0xdf, 0x75, 0x0e, 0x71, 0xa7, 0x41, 0x4c, 0xe6, 0xa0, + 0xfa, 0x3d, 0xd1, 0xa9, 0x7f, 0x32, 0x28, 0x7f, 0xf1, 0x2a, 0xce, 0xf6, 0x3b, 0x07, 0xbe, 0xe3, + 0x86, 0x01, 0xf5, 0x63, 0x45, 0x0d, 0xaa, 0x62, 0xa6, 0xf4, 0x03, 0xbd, 0x0b, 0x13, 0x44, 0xbe, + 0x85, 0x1d, 0x40, 0x27, 0x9b, 0xac, 0x03, 0xed, 0xf0, 0x43, 0x6b, 0x58, 0x37, 0x21, 0xcf, 0x39, + 0xb9, 0xb3, 0x61, 0x9a, 0x02, 0x6b, 0x03, 0xae, 0x53, 0x2b, 0x0e, 0x13, 0xc9, 0xa5, 0xde, 0x1c, + 0x2e, 0x89, 0x91, 0xd9, 0x3f, 0x45, 0xc1, 0x12, 0x9b, 0x4e, 0x08, 0xf5, 0x06, 0xd9, 0xa2, 0xc4, + 0xaa, 0xc1, 0xca, 0x16, 0x0e, 0x6d, 0x1c, 0xe0, 0x70, 0xdf, 0x09, 0x82, 0x17, 0x9e, 0xdf, 0xa6, + 0x45, 0x17, 0x22, 0xf2, 0xb7, 0x32, 0x50, 0xae, 0xf9, 0x98, 0xcc, 0xb4, 0x91, 0xd0, 0xf0, 0x15, + 0xbc, 0xc2, 0x2f, 0x17, 0xb2, 0x51, 0x29, 0xe1, 0x35, 0xbf, 0x40, 0x78, 0x07, 0xc6, 0x0e, 0x0e, + 0x76, 0xa9, 0xc4, 0x8c, 0x51, 0xc6, 0x8d, 0x85, 0x61, 0xe7, 0xd7, 0x83, 0x72, 0x6e, 0xa3, 0xcf, + 0x2e, 0x1f, 0x6c, 0x52, 0x6e, 0x3d, 0x83, 0x25, 0x1b, 0x77, 0xf1, 0x0b, 0xe7, 0xb0, 0x83, 0x35, + 0x73, 0xb5, 0x0c, 0x13, 0xcc, 0x59, 0x96, 0x18, 0x02, 0x83, 0xeb, 0xf6, 0x6c, 0x76, 0x84, 0x3d, + 0x6b, 0xfd, 0x41, 0x06, 0x0a, 0x6c, 0xb8, 0x55, 0x2f, 0x3c, 0xdf, 0xf8, 0xf8, 0x08, 0xb2, 0xc3, + 0x47, 0x80, 0x6e, 0x45, 0xdc, 0x1e, 0x8b, 0x36, 0x3f, 0xda, 0x55, 0xa2, 0xc3, 0x45, 0x21, 0x19, + 0x10, 0x93, 0x25, 0x76, 0x34, 0xa2, 0x03, 0xa2, 0xb2, 0x24, 0x24, 0xe8, 0x8f, 0xb2, 0x30, 0xaf, + 0x74, 0x31, 0xe8, 0x79, 0xdd, 0x00, 0x93, 0x33, 0x1e, 0x11, 0x16, 0xa5, 0x9f, 0xf4, 0x8c, 0x47, + 0xb6, 0xcc, 0x66, 0x64, 0x89, 0xd3, 0x0e, 0xbf, 0x4b, 0x0e, 0x17, 0x9d, 0xc4, 0x71, 0x90, 0x2a, + 0x6e, 0x56, 0x55, 0x14, 0x9f, 0xbb, 0xd3, 0x77, 0x21, 0x47, 0xff, 0x24, 0x8c, 0x18, 0x37, 0x33, + 0x42, 0x56, 0x42, 0x2e, 0xc0, 0x03, 0xcf, 0xed, 0x3e, 0xc2, 0xe1, 0xb1, 0x27, 0x0e, 0xcf, 0x3b, + 0x44, 0x89, 0xfd, 0x25, 0xcf, 0xed, 0x36, 0x4f, 0x29, 0xf8, 0xa2, 0x87, 0xce, 0x88, 0xa0, 0xad, + 0x10, 0xb7, 0xee, 0x41, 0x81, 0xe8, 0x9b, 0xf3, 0xcf, 0xa8, 0xb5, 0x08, 0x68, 0x0b, 0x87, 0x55, + 0x4f, 0xdb, 0x38, 0xac, 0x59, 0x98, 0xde, 0x77, 0xbb, 0x47, 0xe2, 0xf3, 0xdf, 0x67, 0x61, 0x86, + 0x7d, 0xf3, 0x19, 0x88, 0xed, 0xa4, 0x99, 0xf3, 0xec, 0xa4, 0x9f, 0xc2, 0x2c, 0x77, 0xe7, 0x60, + 0x9f, 0xba, 0x90, 0xd9, 0x7c, 0xd0, 0x13, 0x25, 0xf3, 0xea, 0x34, 0x9f, 0xb3, 0x12, 0x5b, 0xaf, + 0x88, 0x76, 0x61, 0x8e, 0x01, 0xee, 0x63, 0x27, 0xec, 0x47, 0xa7, 0xaa, 0xcb, 0xdc, 0xce, 0x14, + 0x60, 0xa6, 0x8c, 0x38, 0xad, 0x67, 0x1c, 0x68, 0xc7, 0x70, 0xd1, 0x37, 0x70, 0x79, 0xdf, 0xf7, + 0x7e, 0x7c, 0xa9, 0xd8, 0x0e, 0x4c, 0x1f, 0x2f, 0x91, 0x43, 0x58, 0x8f, 0x14, 0x35, 0x55, 0x0b, + 0x22, 0x5e, 0x9b, 0xc8, 0xd4, 0x4e, 0x50, 0xf5, 0x7c, 0xb7, 0x7b, 0x44, 0x67, 0x33, 0xc7, 0x64, + 0xca, 0x0d, 0x9a, 0x87, 0x14, 0x68, 0xcb, 0x62, 0xeb, 0x0f, 0xc7, 0x20, 0x27, 0x1b, 0xbe, 0xa3, + 0x9a, 0xa5, 0x7c, 0x33, 0xa6, 0xcb, 0x33, 0x3a, 0xfc, 0xd8, 0x4a, 0x0d, 0x74, 0x95, 0x39, 0xb3, + 0x98, 0x19, 0x30, 0x45, 0x64, 0xcc, 0xe9, 0xf5, 0xa8, 0xcb, 0x8a, 0x28, 0xd3, 0x8d, 0x2a, 0xe5, + 0x42, 0x8e, 0x29, 0xd3, 0xf6, 0xa1, 0x9d, 0xdd, 0xa8, 0x92, 0xb9, 0x7e, 0xbc, 0xb3, 0x51, 0xa3, + 0x03, 0xca, 0xb1, 0xb9, 0xf6, 0xdc, 0x76, 0xcb, 0xa6, 0x50, 0x52, 0x5a, 0xaf, 0x3c, 0xda, 0xe5, + 0x9d, 0xa6, 0xa5, 0x81, 0x73, 0xda, 0xb1, 0x29, 0x94, 0xd8, 0x81, 0x6c, 0x8f, 0xae, 0x79, 0xdd, + 0xd0, 0xf7, 0x3a, 0x01, 0x75, 0x15, 0xe4, 0xb4, 0xed, 0xbc, 0xc5, 0x8b, 0xec, 0x58, 0x55, 0xf4, + 0x14, 0x96, 0x2b, 0xed, 0xe7, 0x4e, 0xb7, 0x85, 0xdb, 0xac, 0xe4, 0xa9, 0xe7, 0x9f, 0x3c, 0xeb, + 0x78, 0x2f, 0x02, 0x7a, 0xca, 0xcb, 0xf1, 0xf3, 0x22, 0xaf, 0xd2, 0xe4, 0xe4, 0x5e, 0x88, 0x4a, + 0xb6, 0x09, 0x9b, 0xa8, 0x88, 0x5a, 0xc7, 0xeb, 0xb7, 0xe9, 0xf1, 0x2e, 0xc7, 0x54, 0x44, 0x8b, + 0x00, 0x6c, 0x06, 0x27, 0x5c, 0xda, 0xae, 0x3f, 0xa2, 0xa7, 0x33, 0xce, 0xa5, 0xe3, 0xe0, 0xd4, + 0x26, 0x30, 0xf4, 0x0e, 0x4c, 0x09, 0x93, 0x96, 0x39, 0x05, 0xa8, 0xc7, 0x48, 0x98, 0xb2, 0xa2, + 0xcc, 0xfa, 0x00, 0xe6, 0xd9, 0xa2, 0x39, 0xb7, 0xa5, 0x66, 0xed, 0x03, 0xd4, 0xf1, 0xa9, 0xd3, + 0x3b, 0xf6, 0xc8, 0xc4, 0x56, 0xd5, 0x2f, 0x6e, 0xba, 0x20, 0xe9, 0xe8, 0xe7, 0x05, 0x8d, 0x75, + 0x61, 0xcb, 0x8a, 0x9a, 0xb6, 0x82, 0x65, 0xfd, 0x97, 0x2c, 0x20, 0xea, 0xf0, 0xae, 0x87, 0x3e, + 0x76, 0x4e, 0x45, 0x37, 0x3e, 0x83, 0x19, 0xa6, 0xff, 0x18, 0x98, 0x76, 0x87, 0xd8, 0x45, 0x4c, + 0xf0, 0xd5, 0xa2, 0xed, 0x4b, 0xb6, 0x56, 0x95, 0xa0, 0xda, 0x38, 0xe8, 0x9f, 0x0a, 0xd4, 0xac, + 0x86, 0xaa, 0x16, 0x11, 0x54, 0xf5, 0x1b, 0x7d, 0x03, 0x73, 0x35, 0xef, 0xb4, 0x47, 0x78, 0xc2, + 0x91, 0xc7, 0xb8, 0xf5, 0xc1, 0xdb, 0xd5, 0x0a, 0xb7, 0x2f, 0xd9, 0xb1, 0xea, 0x68, 0x0f, 0x16, + 0xee, 0x77, 0xfa, 0xc1, 0x71, 0xa5, 0xdb, 0xae, 0x75, 0xbc, 0x40, 0x50, 0x19, 0xe7, 0xce, 0x10, + 0xbe, 0x6c, 0x93, 0x35, 0xb6, 0x2f, 0xd9, 0x69, 0x88, 0xe8, 0x1d, 0x7e, 0x7b, 0xcf, 0xad, 0xa0, + 0xd9, 0x3b, 0xfc, 0x72, 0xff, 0x71, 0x17, 0x3f, 0x7e, 0xb6, 0x7d, 0xc9, 0x66, 0xa5, 0xd5, 0x3c, + 0x4c, 0x09, 0x95, 0x75, 0x17, 0xe6, 0x15, 0x76, 0x12, 0xbb, 0xad, 0x1f, 0xa0, 0x12, 0xe4, 0x9e, + 0xf4, 0x3a, 0x9e, 0xd3, 0x16, 0x66, 0x80, 0x2d, 0xbf, 0xad, 0xf7, 0x75, 0x4e, 0xa3, 0x15, 0xf5, + 0x2c, 0xc2, 0x2a, 0x47, 0x00, 0x6b, 0x5b, 0x67, 0xee, 0xf0, 0xda, 0x5a, 0xbb, 0xd9, 0x58, 0xbb, + 0x85, 0x38, 0xaf, 0xad, 0xa5, 0x54, 0xe6, 0x59, 0x0f, 0xa9, 0x89, 0x53, 0xe9, 0xf5, 0x3a, 0x6e, + 0x8b, 0xee, 0x0c, 0x4c, 0xaf, 0x49, 0xeb, 0xe0, 0xb7, 0xd4, 0x3b, 0x66, 0x65, 0x5b, 0x94, 0x37, + 0xca, 0xca, 0x2d, 0xb2, 0xf5, 0x33, 0xb8, 0x6e, 0x20, 0xc6, 0x35, 0xfc, 0x67, 0x30, 0xc5, 0x41, + 0x31, 0x81, 0x56, 0xbd, 0xf2, 0x74, 0x3d, 0x05, 0x1c, 0x53, 0xd4, 0xb7, 0xbe, 0x83, 0xd5, 0x27, + 0xbd, 0x00, 0xfb, 0x49, 0xf2, 0xa2, 0xab, 0x1f, 0xcb, 0x3b, 0xec, 0x8c, 0xd1, 0xe3, 0x0f, 0x67, + 0x83, 0xf2, 0x24, 0xa3, 0x2d, 0xae, 0xae, 0xad, 0xdf, 0xcb, 0xc0, 0x2a, 0x5b, 0xaa, 0x46, 0xd2, + 0x17, 0xe1, 0x82, 0xe2, 0x51, 0xce, 0x9a, 0x3d, 0xca, 0x43, 0x5d, 0xec, 0xd6, 0xb7, 0x60, 0xf1, + 0x1e, 0x75, 0x3a, 0x6f, 0x68, 0x6e, 0xfe, 0x46, 0x06, 0x16, 0xd9, 0xe4, 0xbc, 0x06, 0x15, 0xf4, + 0x15, 0xcc, 0xd5, 0x4f, 0xdc, 0x5e, 0xc3, 0xe9, 0xb8, 0x6d, 0xe6, 0x5c, 0x65, 0x1b, 0xc9, 0x12, + 0xdd, 0x23, 0x4f, 0xdc, 0x5e, 0xf3, 0x79, 0x54, 0x94, 0xb1, 0x63, 0x95, 0xad, 0xc7, 0xb0, 0x14, + 0xeb, 0x03, 0x17, 0x8c, 0x8f, 0xe3, 0x82, 0x91, 0x08, 0x40, 0x48, 0x97, 0x8a, 0x47, 0x70, 0x45, + 0x4a, 0x85, 0x3e, 0x65, 0xeb, 0x31, 0x69, 0x48, 0x10, 0x4c, 0x13, 0x85, 0x16, 0x5c, 0x91, 0x92, + 0xf0, 0x1a, 0x12, 0x20, 0x26, 0x37, 0x9b, 0x3a, 0xb9, 0x3b, 0x50, 0x52, 0x27, 0xf7, 0x75, 0x26, + 0xf5, 0x3f, 0x67, 0x60, 0x79, 0x0b, 0x77, 0xb1, 0xef, 0xd0, 0x2e, 0x6b, 0x67, 0x0a, 0xd5, 0xb1, + 0x9c, 0x19, 0xea, 0x58, 0x96, 0x06, 0x73, 0x36, 0xdd, 0x60, 0x26, 0xbb, 0xe1, 0x13, 0x7b, 0x87, + 0xcb, 0x2a, 0xdd, 0x0d, 0xfb, 0xbe, 0x6b, 0x13, 0x18, 0xda, 0x89, 0x9c, 0xd2, 0xe3, 0x23, 0x9d, + 0xd2, 0x0b, 0xdc, 0x49, 0x37, 0xc5, 0x9d, 0xd2, 0x9a, 0x2b, 0xda, 0xfa, 0x02, 0x8a, 0xc9, 0xb1, + 0x70, 0xf9, 0x18, 0x75, 0x48, 0xb1, 0x36, 0x22, 0xe9, 0xe6, 0xf7, 0xd7, 0xd2, 0x19, 0x1f, 0x53, + 0xa1, 0x43, 0x9c, 0x3f, 0x56, 0x3d, 0x92, 0x4f, 0x4e, 0x85, 0xb7, 0xff, 0x39, 0x91, 0x4f, 0x16, + 0xa3, 0x90, 0x31, 0xc7, 0x28, 0x70, 0x19, 0x65, 0xa8, 0x02, 0xc1, 0x7a, 0x0a, 0x57, 0x34, 0xa2, + 0x91, 0xd4, 0x7f, 0x05, 0x39, 0x01, 0x8b, 0xf9, 0x26, 0x34, 0xb2, 0x74, 0xde, 0x02, 0x81, 0x2c, + 0x51, 0xac, 0x3f, 0xcb, 0xc0, 0x32, 0xdb, 0x5d, 0x92, 0xe3, 0x3e, 0xff, 0xec, 0xff, 0x46, 0x1c, + 0x5e, 0xf7, 0x52, 0x1c, 0x5e, 0x14, 0x45, 0x75, 0x78, 0xa9, 0x6e, 0xae, 0x07, 0xe3, 0xb9, 0x6c, + 0x61, 0xcc, 0x6a, 0x40, 0x31, 0x39, 0xc2, 0x37, 0x30, 0x27, 0x5b, 0xb0, 0xac, 0x2c, 0xf4, 0xd7, + 0x96, 0x98, 0xa8, 0xc5, 0x37, 0x28, 0x31, 0x51, 0xc5, 0x37, 0x26, 0x31, 0x3b, 0xb0, 0xc0, 0x08, + 0xeb, 0xab, 0x6b, 0x4d, 0x5d, 0x5d, 0xa9, 0xf1, 0x32, 0xc9, 0x05, 0xf7, 0x88, 0x2e, 0x38, 0x51, + 0x25, 0xea, 0xe1, 0x47, 0x30, 0xc9, 0x43, 0x02, 0x59, 0xff, 0x52, 0x88, 0x51, 0xcd, 0xcb, 0xe2, + 0x00, 0x6d, 0x5e, 0xd9, 0x2a, 0xd2, 0x21, 0x93, 0x73, 0x0a, 0x77, 0x78, 0xcb, 0x53, 0xe3, 0xb7, + 0x44, 0xc5, 0xc5, 0x4a, 0x5e, 0x73, 0xd7, 0x78, 0x0c, 0x45, 0xb6, 0x6b, 0x28, 0x54, 0x5f, 0x6b, + 0xdf, 0xf8, 0x14, 0x8a, 0x4c, 0x9c, 0x52, 0x08, 0x0e, 0xdf, 0x0c, 0x56, 0x61, 0x45, 0x6e, 0x06, + 0x69, 0xa3, 0xff, 0x3b, 0x19, 0xb8, 0xba, 0x85, 0x43, 0x3d, 0x6a, 0xea, 0xcf, 0x65, 0xef, 0xfe, + 0x1e, 0x4a, 0x69, 0x1d, 0xe1, 0x53, 0xf1, 0x75, 0x7c, 0x2a, 0x8c, 0x21, 0x62, 0xe9, 0x53, 0xf2, + 0x33, 0xb8, 0xc6, 0xa6, 0x44, 0xaf, 0x2f, 0x06, 0xfa, 0x45, 0x6c, 0x56, 0x8c, 0xd4, 0xd3, 0x66, + 0xe7, 0x1f, 0x64, 0xe0, 0x1a, 0x63, 0x72, 0x3a, 0xf1, 0xdf, 0xb4, 0x75, 0xb7, 0x07, 0x65, 0x39, + 0xe7, 0x6f, 0x60, 0x62, 0xad, 0x16, 0x20, 0x41, 0xa6, 0x56, 0xb7, 0x05, 0x89, 0xab, 0x30, 0x56, + 0xab, 0xdb, 0xfc, 0x02, 0x9a, 0x6e, 0xda, 0xad, 0xc0, 0xb7, 0x09, 0x2c, 0xae, 0xc1, 0xb3, 0xe7, + 0xd0, 0xe0, 0xd6, 0xcf, 0x61, 0x41, 0x6b, 0x84, 0xcf, 0xfb, 0x0a, 0x8c, 0xd7, 0xb0, 0x1f, 0xf2, + 0x66, 0xe8, 0x48, 0x5b, 0xd8, 0x0f, 0x6d, 0x0a, 0x45, 0xb7, 0x60, 0xaa, 0x56, 0xa1, 0xde, 0x46, + 0x6a, 0x5b, 0xcc, 0x30, 0xc5, 0xd4, 0x72, 0x9a, 0x34, 0x92, 0xdc, 0x16, 0x85, 0xd6, 0x7f, 0xc8, + 0x28, 0xd4, 0x09, 0xfa, 0xe8, 0x31, 0x7c, 0x40, 0x8e, 0xc7, 0x84, 0x67, 0xca, 0x10, 0xe6, 0xc9, + 0xb6, 0xc5, 0x3d, 0x35, 0x6c, 0xe7, 0xb3, 0x95, 0x4a, 0xe7, 0xf4, 0x94, 0x8a, 0x9b, 0x39, 0x86, + 0x24, 0xbc, 0x88, 0xf2, 0x66, 0x8e, 0x93, 0x0e, 0x6c, 0xb5, 0x92, 0xf5, 0x3d, 0x2c, 0xea, 0xfd, + 0x7f, 0xa3, 0xec, 0x79, 0x9b, 0x5e, 0xf9, 0x28, 0x97, 0xa5, 0x08, 0xa9, 0x8e, 0x04, 0x2e, 0x56, + 0x9f, 0x40, 0x81, 0xd7, 0x8a, 0x96, 0xe5, 0x4d, 0x61, 0xda, 0xb1, 0x45, 0xa9, 0x07, 0x17, 0x0b, + 0x7f, 0xe8, 0x4f, 0x84, 0xab, 0x62, 0x54, 0x0b, 0xff, 0x38, 0x03, 0xc5, 0x47, 0xf7, 0x2b, 0x95, + 0x7e, 0x78, 0x8c, 0xbb, 0x21, 0x39, 0x94, 0xe0, 0xda, 0xb1, 0xd3, 0xe9, 0xe0, 0xee, 0x11, 0x46, + 0xb7, 0x61, 0xfc, 0xe0, 0xf1, 0xc1, 0x3e, 0xf7, 0x08, 0x2c, 0xf2, 0xe3, 0x38, 0x01, 0xc9, 0x3a, + 0x36, 0xad, 0x81, 0x1e, 0xc2, 0xfc, 0x53, 0x1e, 0xce, 0x2f, 0x8b, 0xb8, 0x2f, 0xe0, 0xfa, 0x1d, + 0x19, 0xe8, 0x5f, 0xf3, 0x71, 0x9b, 0xb4, 0xe2, 0x74, 0x2a, 0x01, 0x51, 0x0c, 0x64, 0x7e, 0x92, + 0x78, 0x0f, 0xc6, 0x73, 0x99, 0x42, 0xd6, 0xfa, 0xfd, 0x0c, 0x2c, 0xc7, 0x7a, 0xa6, 0x38, 0x76, + 0xd5, 0x8e, 0x2d, 0x28, 0x1d, 0x13, 0x55, 0xb6, 0x2f, 0xf1, 0x9e, 0xd5, 0x68, 0xec, 0x28, 0x6d, + 0x81, 0x77, 0xe8, 0x9d, 0xe1, 0x1d, 0x8a, 0x08, 0x48, 0x44, 0x1e, 0xf9, 0x45, 0xe1, 0xd6, 0x65, + 0x98, 0xd5, 0x38, 0x60, 0x59, 0x30, 0xa3, 0xb6, 0x4c, 0xd8, 0x5c, 0xf3, 0xda, 0x92, 0xcd, 0xe4, + 0x6f, 0xeb, 0x1f, 0x66, 0x60, 0xf1, 0xd1, 0xfd, 0x8a, 0x8d, 0x8f, 0x5c, 0xb2, 0xfc, 0x22, 0x16, + 0xaf, 0x69, 0x23, 0x59, 0xd1, 0x46, 0x12, 0xab, 0x2b, 0x87, 0xf4, 0x79, 0x62, 0x48, 0x2b, 0x69, + 0x43, 0xa2, 0x56, 0x96, 0xeb, 0x75, 0xb5, 0x91, 0x28, 0x9e, 0x8f, 0x7f, 0x92, 0x81, 0x05, 0xa5, + 0x4f, 0xb2, 0xff, 0x1f, 0x68, 0x5d, 0xba, 0x96, 0xd2, 0xa5, 0x04, 0x93, 0xab, 0x89, 0x1e, 0xbd, + 0x3d, 0xac, 0x47, 0x23, 0x79, 0xfc, 0xc7, 0x19, 0x58, 0x4a, 0xe5, 0x01, 0xba, 0x42, 0x76, 0x8d, + 0x96, 0x8f, 0x43, 0xce, 0x5e, 0xfe, 0x45, 0xe0, 0x3b, 0x41, 0xd0, 0xe7, 0x6f, 0x2d, 0xf2, 0x36, + 0xff, 0x42, 0x6f, 0xc3, 0xec, 0x3e, 0xf6, 0x5d, 0xaf, 0x5d, 0xc7, 0x2d, 0xaf, 0xdb, 0x66, 0x1e, + 0xe1, 0x59, 0x5b, 0x07, 0xa2, 0x15, 0xc8, 0x57, 0x3a, 0x47, 0x9e, 0xef, 0x86, 0xc7, 0xcc, 0xf9, + 0x94, 0xb7, 0x23, 0x00, 0xa1, 0xbd, 0xe1, 0x1e, 0xb9, 0x21, 0xbb, 0x5b, 0x9b, 0xb5, 0xf9, 0x17, + 0x2a, 0xc2, 0x54, 0xa5, 0xd5, 0xf2, 0xfa, 0xdd, 0x90, 0x7a, 0x40, 0xf3, 0xb6, 0xf8, 0x24, 0x18, + 0xdf, 0xda, 0x54, 0x08, 0x68, 0x34, 0x94, 0xcd, 0xbf, 0xac, 0xf7, 0x60, 0x31, 0x8d, 0x8f, 0xa9, + 0x22, 0xf3, 0xd7, 0xb2, 0xb0, 0x50, 0x69, 0xb7, 0x1f, 0xdd, 0xaf, 0x6c, 0x60, 0xd5, 0xf8, 0xf8, + 0x10, 0xc6, 0x77, 0xba, 0x6e, 0xc8, 0x77, 0xcd, 0x55, 0x3e, 0x3d, 0x29, 0x35, 0x49, 0x2d, 0x32, + 0x43, 0xe4, 0x7f, 0x64, 0xc3, 0xc2, 0xe6, 0x8f, 0x6e, 0x10, 0xba, 0xdd, 0x23, 0x3a, 0xe7, 0xac, + 0x61, 0x3e, 0xc7, 0x82, 0x88, 0x61, 0xb9, 0x6d, 0x5f, 0xb2, 0xd3, 0x90, 0xd1, 0x01, 0x5c, 0xd9, + 0xc3, 0x2f, 0x52, 0x44, 0x48, 0x06, 0x33, 0x49, 0xb2, 0x29, 0x92, 0x63, 0xc0, 0x55, 0x25, 0xf4, + 0x6f, 0x67, 0x69, 0x84, 0x9c, 0x32, 0x30, 0xde, 0xf2, 0x13, 0x58, 0x54, 0x3a, 0x14, 0x69, 0x9c, + 0x0c, 0x8f, 0x9d, 0x4e, 0x1d, 0x8e, 0xba, 0x90, 0x52, 0xd1, 0xd1, 0x53, 0x58, 0xd6, 0x3b, 0x15, + 0x51, 0xd6, 0x17, 0x43, 0x5a, 0x95, 0xed, 0x4b, 0xb6, 0x09, 0x1b, 0xad, 0xc1, 0x58, 0xa5, 0x75, + 0xc2, 0xd9, 0x92, 0x3e, 0x65, 0x6c, 0x64, 0x95, 0xd6, 0x09, 0x8d, 0x36, 0x6d, 0x9d, 0x68, 0xeb, + 0xa1, 0x03, 0xcb, 0x86, 0x09, 0x46, 0xab, 0x00, 0x0c, 0xa8, 0xa8, 0x76, 0x05, 0x42, 0x36, 0x55, + 0xf6, 0x45, 0xef, 0x1b, 0xc7, 0xe8, 0xf5, 0xb5, 0x08, 0x0b, 0x8a, 0x0a, 0x6c, 0xa5, 0x92, 0x55, + 0x8b, 0xb7, 0x26, 0xfb, 0x86, 0x6e, 0xc3, 0x24, 0x03, 0x72, 0x56, 0x8b, 0xc7, 0x29, 0x51, 0x65, + 0x5e, 0x6e, 0xfd, 0x41, 0x46, 0x38, 0x5e, 0x12, 0x12, 0xfc, 0x89, 0x26, 0xc1, 0x6f, 0xc9, 0xce, + 0xa4, 0x55, 0xd6, 0x84, 0xb8, 0x0a, 0xd3, 0xaf, 0x22, 0xbc, 0x2a, 0x92, 0x2a, 0x5e, 0xff, 0x3c, + 0x23, 0x8e, 0x8c, 0x49, 0x09, 0xdb, 0x84, 0x99, 0x57, 0x93, 0x2c, 0x0d, 0x0d, 0x7d, 0xc4, 0x26, + 0x3e, 0x3b, 0x7c, 0xa4, 0x43, 0xe7, 0xfe, 0x4b, 0xe1, 0x5b, 0x7a, 0x95, 0xe9, 0xb7, 0x56, 0x52, + 0xb0, 0x65, 0x73, 0x56, 0x3f, 0x51, 0x5a, 0x7f, 0xd9, 0x6d, 0x89, 0x79, 0xba, 0x15, 0xbf, 0x0b, + 0x37, 0x5e, 0x74, 0xaa, 0x7d, 0xc8, 0x46, 0xee, 0x86, 0x36, 0x8e, 0xa2, 0x9e, 0xd4, 0x4e, 0x9d, + 0x65, 0x74, 0x09, 0x7b, 0x95, 0x46, 0x6b, 0x30, 0xbb, 0x87, 0x5f, 0x24, 0xda, 0xa5, 0xd7, 0x47, + 0x5d, 0xfc, 0xa2, 0xa9, 0xb4, 0xad, 0x04, 0x1c, 0xe8, 0x38, 0xe8, 0x10, 0xe6, 0xc4, 0x92, 0x3d, + 0xaf, 0xe6, 0x62, 0x81, 0x91, 0xa4, 0x85, 0xd3, 0x67, 0x4e, 0xd3, 0xe7, 0x50, 0x35, 0xa2, 0x51, + 0xa7, 0x68, 0xed, 0x43, 0x31, 0x39, 0x56, 0x2e, 0x65, 0x1f, 0x8e, 0x5a, 0x4e, 0xec, 0xf0, 0xd3, + 0xd6, 0x97, 0xd6, 0x36, 0x3d, 0xa7, 0xcb, 0x3a, 0xf2, 0x84, 0x71, 0x2f, 0xce, 0x3a, 0x1a, 0xec, + 0x25, 0x58, 0xa7, 0x46, 0x5e, 0x47, 0xd1, 0x10, 0x4b, 0x31, 0x4a, 0xbc, 0x63, 0xef, 0xc1, 0x14, + 0x07, 0xc9, 0x88, 0xf6, 0xf8, 0x42, 0x17, 0x15, 0xac, 0x7f, 0x9a, 0x81, 0xab, 0x4f, 0x02, 0xec, + 0xd7, 0xdd, 0xee, 0x51, 0x07, 0x3f, 0x09, 0xf4, 0x58, 0x84, 0xdf, 0xd6, 0x16, 0xfb, 0xb2, 0x21, + 0xc6, 0xf1, 0xff, 0xd7, 0x12, 0xff, 0x67, 0x19, 0x28, 0xa5, 0xf5, 0xed, 0xcd, 0xae, 0xf2, 0x3b, + 0xfc, 0x48, 0xc0, 0x7a, 0x5b, 0xe4, 0xe8, 0xb2, 0x4d, 0x31, 0x58, 0x32, 0x48, 0xf2, 0xbf, 0xb6, + 0xbc, 0xff, 0x4f, 0x06, 0x16, 0x77, 0x02, 0xda, 0xfd, 0x5f, 0xf4, 0x5d, 0x1f, 0xb7, 0x05, 0xe3, + 0xee, 0xa4, 0x45, 0xc2, 0xd2, 0x79, 0xdd, 0xbe, 0x94, 0x16, 0xe9, 0xfa, 0xa1, 0x12, 0xeb, 0x97, + 0x1d, 0x16, 0xe2, 0xaa, 0x3d, 0xab, 0xb8, 0x05, 0xe3, 0x7b, 0xc4, 0xf2, 0x18, 0xe3, 0xf2, 0xc7, + 0x30, 0x08, 0x88, 0x86, 0xe5, 0x91, 0x2e, 0x93, 0x0f, 0x74, 0x3f, 0x11, 0xfc, 0x37, 0x3e, 0x3a, + 0x84, 0x33, 0xf9, 0x1e, 0xa4, 0x9a, 0x83, 0xc9, 0x03, 0xc7, 0x3f, 0xc2, 0xa1, 0xf5, 0x33, 0x28, + 0xf1, 0x3b, 0x37, 0xe6, 0xca, 0xa2, 0x37, 0x73, 0x41, 0xe4, 0x62, 0x19, 0x76, 0x4f, 0xb6, 0x0a, + 0x50, 0x0f, 0x1d, 0x3f, 0xdc, 0xe9, 0xb6, 0xf1, 0x8f, 0x74, 0xb4, 0x13, 0xb6, 0x02, 0xb1, 0x3e, + 0x82, 0xbc, 0x1c, 0x02, 0x3d, 0xf6, 0x28, 0xc6, 0x15, 0x1d, 0xce, 0xa2, 0x16, 0x8e, 0x28, 0x62, + 0x10, 0xd7, 0x61, 0x29, 0x36, 0x15, 0x5c, 0x4e, 0x4a, 0x64, 0xc2, 0x18, 0x8c, 0xdd, 0xc7, 0xdb, + 0xf2, 0xdb, 0xaa, 0xc1, 0x7c, 0x62, 0xa6, 0x11, 0xa2, 0x21, 0xa6, 0xec, 0x8c, 0x48, 0x94, 0x7a, + 0xbd, 0xbe, 0x4d, 0x60, 0x07, 0xbb, 0x75, 0x16, 0x6e, 0x43, 0x60, 0x07, 0xbb, 0xf5, 0xea, 0x24, + 0x93, 0x1c, 0xeb, 0x5f, 0x67, 0xe9, 0x49, 0x2f, 0xc1, 0x83, 0x98, 0xc7, 0x40, 0xf5, 0x5a, 0x54, + 0x21, 0x4f, 0x47, 0xbc, 0x21, 0xc2, 0xd2, 0x86, 0x3b, 0xea, 0x73, 0xbf, 0x1c, 0x94, 0x2f, 0x51, + 0xef, 0x7c, 0x84, 0x86, 0xbe, 0x86, 0xa9, 0xcd, 0x6e, 0x9b, 0x52, 0x18, 0xbb, 0x00, 0x05, 0x81, + 0x44, 0xe6, 0x81, 0x76, 0x99, 0x98, 0x0d, 0xfc, 0x58, 0x6d, 0x2b, 0x10, 0xca, 0x66, 0xf7, 0xd4, + 0x65, 0xd7, 0xb1, 0x13, 0x36, 0xfb, 0x20, 0xdc, 0xa4, 0x5d, 0x10, 0xaf, 0x0c, 0xf2, 0xb6, 0xfc, + 0x46, 0x16, 0x4c, 0x3c, 0xf6, 0xdb, 0x3c, 0xe6, 0x7b, 0x6e, 0x6d, 0x46, 0x3c, 0xbb, 0x26, 0x30, + 0x9b, 0x15, 0x59, 0xff, 0x8b, 0x5e, 0x91, 0x84, 0xa9, 0x72, 0xa3, 0x71, 0x25, 0xf3, 0xda, 0x5c, + 0xc9, 0xbe, 0x0a, 0x57, 0xe4, 0xa8, 0xc7, 0x4c, 0xa3, 0x1e, 0x37, 0x8d, 0x7a, 0xc2, 0x3c, 0xea, + 0x2d, 0x98, 0x64, 0x43, 0x45, 0x37, 0x61, 0x62, 0x27, 0xc4, 0xa7, 0x91, 0x07, 0x40, 0xbd, 0xe4, + 0xb6, 0x59, 0x19, 0x39, 0x9c, 0xec, 0x3a, 0x41, 0x28, 0x02, 0xbc, 0xf2, 0xb6, 0xf8, 0xb4, 0xfe, + 0x24, 0x03, 0x85, 0x5d, 0x37, 0x08, 0xc9, 0x42, 0x38, 0xa7, 0xac, 0xc9, 0x11, 0x65, 0x4d, 0x23, + 0x1a, 0x8b, 0x8d, 0xe8, 0x0b, 0x98, 0xa4, 0x71, 0x87, 0x01, 0x7f, 0x51, 0x74, 0x93, 0x0f, 0x29, + 0xde, 0x30, 0x8b, 0x4e, 0x0c, 0xe8, 0xcb, 0x1f, 0x9b, 0xa3, 0x94, 0x3e, 0x83, 0x69, 0x05, 0x7c, + 0xa1, 0x07, 0x41, 0xdf, 0xc1, 0xbc, 0xd2, 0x84, 0x74, 0x17, 0x8c, 0x70, 0x2a, 0x4b, 0xa7, 0x25, + 0x61, 0xdb, 0x1e, 0xfe, 0x51, 0x65, 0x1b, 0xff, 0xb4, 0x7e, 0xa0, 0x61, 0xb3, 0xbb, 0x5e, 0xeb, + 0x44, 0x71, 0xe9, 0x4d, 0x31, 0x65, 0x16, 0xf7, 0x8c, 0x93, 0x5a, 0xac, 0xc4, 0x16, 0x35, 0xd0, + 0x0d, 0x98, 0xde, 0xe9, 0xde, 0xf7, 0xfc, 0x16, 0x7e, 0xdc, 0xed, 0x30, 0xea, 0x39, 0x5b, 0x05, + 0x71, 0x6f, 0x0f, 0x6f, 0x21, 0xf2, 0xf6, 0x50, 0x40, 0xcc, 0xdb, 0xc3, 0x1e, 0x34, 0xda, 0xac, + 0x8c, 0x3b, 0x93, 0xc8, 0xdf, 0xc3, 0x5c, 0x3d, 0xd2, 0x27, 0x34, 0xaa, 0xe2, 0x21, 0x5c, 0xb5, + 0x71, 0xaf, 0xe3, 0x10, 0x5b, 0xf1, 0xd4, 0x63, 0xf5, 0xe5, 0x98, 0x6f, 0xa4, 0x44, 0x74, 0xe9, + 0x17, 0x43, 0xb2, 0xcb, 0xd9, 0x21, 0x5d, 0x3e, 0x85, 0xb7, 0xb6, 0x70, 0x98, 0xfa, 0x2a, 0x31, + 0x1a, 0xfc, 0x36, 0xe4, 0x78, 0xe4, 0xbc, 0x18, 0xff, 0xa8, 0x07, 0x91, 0xfc, 0x96, 0x84, 0xd3, + 0x91, 0x7f, 0x59, 0xdf, 0x40, 0xd9, 0xd4, 0xdc, 0xf9, 0x02, 0x79, 0x5c, 0xb8, 0x61, 0x26, 0x20, + 0xad, 0x89, 0x29, 0xde, 0xa0, 0x3c, 0x9c, 0x0f, 0xef, 0xad, 0xf4, 0x9b, 0x53, 0x7b, 0x8a, 0xff, + 0x61, 0x55, 0x45, 0xa4, 0xc0, 0x6b, 0x74, 0xb7, 0x49, 0x3d, 0xfb, 0x3a, 0x81, 0x88, 0xaf, 0x15, + 0xc8, 0x09, 0x58, 0xcc, 0xb5, 0x9f, 0x78, 0xf0, 0x49, 0x19, 0xda, 0x16, 0x04, 0x24, 0x9a, 0xf5, + 0x83, 0xf0, 0xbf, 0xeb, 0x18, 0xe7, 0x8b, 0x0e, 0x3d, 0x8f, 0xc3, 0xdd, 0xf2, 0xe0, 0xaa, 0x4e, + 0x5b, 0xf5, 0x22, 0x17, 0x14, 0x2f, 0x32, 0x73, 0x1e, 0x13, 0xb9, 0xb4, 0x77, 0x37, 0xbb, 0xed, + 0x9e, 0xe7, 0x76, 0x43, 0xbe, 0x78, 0x55, 0x10, 0x5a, 0x55, 0x7d, 0xc5, 0x33, 0xc9, 0x70, 0xda, + 0x7b, 0x50, 0x4a, 0x6b, 0x50, 0x71, 0xd1, 0x48, 0xb7, 0x2f, 0xb3, 0xe3, 0xac, 0x63, 0x58, 0xd4, + 0x52, 0x68, 0x44, 0x39, 0x01, 0xa2, 0xd4, 0x21, 0xf9, 0xea, 0x97, 0xbf, 0x1e, 0x94, 0x3f, 0xbd, + 0x48, 0xcc, 0xa6, 0xa0, 0x79, 0x20, 0x23, 0x82, 0xad, 0x65, 0x18, 0xab, 0xd9, 0xbb, 0x74, 0xd8, + 0xf6, 0xae, 0x1c, 0xb6, 0xbd, 0x6b, 0xfd, 0x3a, 0x03, 0xe5, 0xda, 0xb1, 0xd3, 0x3d, 0xa2, 0xb6, + 0x87, 0x62, 0xae, 0x2a, 0xd7, 0x9f, 0xe7, 0x3d, 0x52, 0xad, 0xc1, 0xf4, 0x1e, 0x7e, 0x21, 0xa2, + 0x99, 0x79, 0x5c, 0x30, 0xf5, 0x92, 0x93, 0xe3, 0x4e, 0x8f, 0xc3, 0x6d, 0xb5, 0x12, 0xfa, 0xcb, + 0xaf, 0xee, 0x03, 0x62, 0x0f, 0xe9, 0xa3, 0x93, 0x14, 0x2b, 0x4d, 0x3b, 0x52, 0x19, 0x9a, 0xb0, + 0xfe, 0x6d, 0x06, 0x6e, 0x98, 0x07, 0xcf, 0x27, 0x6e, 0x43, 0x4b, 0x47, 0x30, 0xe4, 0xe2, 0x96, + 0x1e, 0x59, 0x95, 0x74, 0x04, 0xf1, 0x14, 0x04, 0x36, 0x6e, 0x79, 0xcf, 0xb1, 0xff, 0x32, 0xe6, + 0x0e, 0x17, 0xe0, 0x1a, 0xd9, 0x72, 0x44, 0x32, 0x17, 0x06, 0xd2, 0x5e, 0x20, 0x72, 0x98, 0xf5, + 0x9f, 0x32, 0x70, 0x8d, 0x6e, 0x93, 0xdc, 0x59, 0x28, 0x0a, 0x2e, 0x7e, 0xcd, 0xff, 0x11, 0xcc, + 0xa8, 0x8d, 0xf3, 0x09, 0xa3, 0xef, 0x97, 0x45, 0x0f, 0x9a, 0x2d, 0xaf, 0x8d, 0x6d, 0xad, 0x1a, + 0xda, 0x81, 0x69, 0xfe, 0xad, 0xb8, 0x84, 0x96, 0x94, 0xe4, 0x26, 0x54, 0x1e, 0xd8, 0x83, 0x33, + 0x3a, 0xfb, 0x9c, 0x58, 0x93, 0x46, 0xa8, 0xab, 0xb8, 0xd6, 0xaf, 0xb2, 0xb0, 0xd2, 0xc0, 0xbe, + 0xfb, 0xec, 0xa5, 0x61, 0x30, 0x8f, 0x61, 0x51, 0x80, 0xe8, 0x98, 0x75, 0x39, 0x64, 0x2f, 0xc9, + 0x44, 0x57, 0x03, 0x52, 0xa1, 0x29, 0xc5, 0x32, 0x15, 0xf1, 0x02, 0x6f, 0x2b, 0x3f, 0x84, 0x9c, + 0x14, 0xe5, 0x31, 0xca, 0x19, 0x3a, 0x37, 0x42, 0x8c, 0xf5, 0x67, 0xe6, 0x52, 0x9e, 0xff, 0xa6, + 0xf9, 0xd6, 0x81, 0x9f, 0x78, 0x46, 0x1c, 0x46, 0x99, 0x54, 0x13, 0x89, 0x76, 0x94, 0xd2, 0x14, + 0xa9, 0xde, 0xbe, 0x64, 0x9b, 0x5a, 0xaa, 0x4e, 0x43, 0xbe, 0x42, 0xef, 0x44, 0xc8, 0x01, 0xe3, + 0x7f, 0x67, 0x61, 0x55, 0x44, 0xfe, 0x19, 0xd8, 0xfc, 0x1d, 0x2c, 0x0b, 0x50, 0xa5, 0xd7, 0xf3, + 0xbd, 0xe7, 0xb8, 0xad, 0x73, 0x9a, 0xbd, 0xe6, 0x14, 0x9c, 0x76, 0x78, 0x9d, 0x88, 0xd9, 0x26, + 0xf4, 0x37, 0xe3, 0x66, 0xf9, 0x5a, 0x57, 0x2c, 0x6c, 0x36, 0xe8, 0x7b, 0x25, 0x55, 0xb1, 0xe8, + 0x79, 0x78, 0x54, 0x25, 0xd3, 0x4e, 0xb8, 0x69, 0xc6, 0x5f, 0xd7, 0x4d, 0x43, 0x8e, 0xa6, 0x3a, + 0xcd, 0xea, 0x1c, 0xcc, 0xec, 0xe1, 0x17, 0x11, 0xdf, 0x7f, 0x37, 0x03, 0xb3, 0xda, 0xe2, 0x46, + 0xef, 0xc2, 0x04, 0xfd, 0x83, 0x6e, 0x9a, 0xfc, 0x49, 0x0b, 0x59, 0x60, 0xda, 0x93, 0x16, 0x56, + 0x75, 0x07, 0xa6, 0x58, 0x94, 0x4b, 0xfb, 0x1c, 0x67, 0x08, 0x19, 0x44, 0xd5, 0x62, 0x28, 0xec, + 0x38, 0xc1, 0xf1, 0xad, 0x87, 0xf0, 0x16, 0x0f, 0x98, 0xd1, 0x27, 0xbf, 0xa6, 0xda, 0xef, 0xe7, + 0xd4, 0xf1, 0x96, 0x03, 0xab, 0x5b, 0x38, 0xae, 0x7a, 0xb4, 0x20, 0xb3, 0x6f, 0xe0, 0xb2, 0x06, + 0x97, 0x14, 0x69, 0xd8, 0xbb, 0x94, 0x21, 0x49, 0x3a, 0x5e, 0xdb, 0xba, 0x91, 0xd6, 0x84, 0xda, + 0x59, 0x0b, 0xc3, 0x65, 0x7a, 0x52, 0x96, 0x17, 0x43, 0xc1, 0x05, 0xb4, 0xde, 0x6d, 0x65, 0x5d, + 0x33, 0x8d, 0xc7, 0x5e, 0x4c, 0x8a, 0xed, 0x49, 0x96, 0x5a, 0xff, 0x33, 0x03, 0x16, 0xe7, 0x5c, + 0x9a, 0x1b, 0x47, 0x0c, 0xf8, 0x30, 0xd1, 0x1b, 0xbe, 0x4b, 0x5c, 0x51, 0x9d, 0x55, 0x51, 0x29, + 0x13, 0x2d, 0xfa, 0x88, 0xa4, 0x15, 0x41, 0x35, 0xd1, 0x4a, 0x0c, 0xef, 0xe7, 0x06, 0x3d, 0xc8, + 0x56, 0x13, 0x4d, 0xe3, 0x61, 0xd0, 0x83, 0x1a, 0xdd, 0x54, 0x22, 0xaa, 0xbf, 0xeb, 0x1f, 0x65, + 0xe0, 0x1a, 0x1b, 0xf2, 0xbe, 0xef, 0x3e, 0x77, 0x3b, 0xf8, 0x08, 0x6b, 0x93, 0xdb, 0x4f, 0xbf, + 0x06, 0xca, 0x9c, 0x4b, 0xb3, 0xd1, 0x8c, 0x04, 0x98, 0xa3, 0x9b, 0xdc, 0x9f, 0x69, 0xf4, 0xad, + 0xbf, 0x9f, 0x81, 0x55, 0xf1, 0x60, 0x2a, 0x76, 0x37, 0x72, 0x51, 0x23, 0xa5, 0xaa, 0xdd, 0x67, + 0x64, 0x0d, 0xf7, 0x19, 0x9a, 0xff, 0x39, 0x8c, 0x5f, 0x70, 0xfc, 0x61, 0x16, 0xe6, 0xf7, 0x9d, + 0x23, 0xb7, 0x4b, 0xd6, 0x97, 0x48, 0xe9, 0x81, 0x2a, 0x89, 0xcc, 0x48, 0xc3, 0x03, 0x53, 0x52, + 0x52, 0x1f, 0xad, 0xa9, 0x49, 0x4a, 0xb2, 0xa6, 0x90, 0x65, 0x3d, 0x15, 0xc9, 0x67, 0x9a, 0x07, + 0x2e, 0x11, 0x9b, 0x44, 0x9f, 0xbd, 0x74, 0xbd, 0x76, 0x2c, 0x5b, 0x18, 0xf5, 0x62, 0x3d, 0x86, + 0x69, 0x25, 0xc0, 0x88, 0x2b, 0xc5, 0x04, 0x05, 0xaa, 0x68, 0x4f, 0xfa, 0x87, 0x38, 0x35, 0x33, + 0x8c, 0x4a, 0xa1, 0x0a, 0x90, 0x13, 0x89, 0xe8, 0xac, 0x7f, 0x39, 0x01, 0x8b, 0xe4, 0x28, 0x2d, + 0xf8, 0x13, 0x44, 0x0a, 0x62, 0x46, 0xc0, 0x14, 0x2b, 0x97, 0xef, 0xe5, 0x0c, 0xde, 0x8c, 0xe5, + 0xc5, 0xd3, 0x10, 0xd0, 0x47, 0xaa, 0xaf, 0x21, 0xab, 0xbc, 0x96, 0x4f, 0xa6, 0x34, 0x53, 0x9d, + 0x10, 0xef, 0x6a, 0x6e, 0x15, 0xa6, 0x7d, 0x3b, 0x04, 0xa0, 0x6a, 0x5f, 0xe6, 0x99, 0x58, 0x8f, + 0xfb, 0x5a, 0x58, 0x03, 0x6c, 0x65, 0x9d, 0x60, 0xcd, 0x30, 0x93, 0x2e, 0x8b, 0x27, 0xd2, 0x65, + 0x31, 0x41, 0xcf, 0x44, 0x3f, 0x51, 0x5c, 0x16, 0x71, 0x26, 0xa8, 0x6e, 0x0b, 0xf6, 0x3e, 0xb3, + 0x43, 0x01, 0xea, 0xfb, 0x4c, 0x56, 0x05, 0x1d, 0xc0, 0xc2, 0xbe, 0x8f, 0xdb, 0x74, 0x29, 0x6d, + 0xfe, 0xd8, 0xf3, 0xb9, 0x21, 0x4a, 0x1d, 0x5f, 0x2c, 0x75, 0x50, 0x4f, 0x14, 0x37, 0xb1, 0x2c, + 0x57, 0x57, 0x54, 0x0a, 0x3a, 0xda, 0x84, 0xb9, 0x3a, 0x76, 0xfc, 0xd6, 0xf1, 0x43, 0xfc, 0x92, + 0x28, 0xbb, 0xa0, 0x38, 0x15, 0xa5, 0x5b, 0x08, 0x68, 0x09, 0x19, 0x28, 0x2d, 0x52, 0x2f, 0x27, + 0x74, 0x24, 0xf4, 0x53, 0x98, 0xac, 0x7b, 0x7e, 0x58, 0x7d, 0x19, 0xcb, 0x71, 0xc7, 0x80, 0xd5, + 0xab, 0x22, 0xe5, 0x44, 0xe0, 0xf9, 0x61, 0xf3, 0x50, 0xe5, 0x1b, 0xc7, 0x43, 0xf7, 0xc9, 0xde, + 0x4c, 0xec, 0x85, 0xd0, 0xe9, 0xd4, 0xe8, 0x5d, 0x38, 0x7b, 0x61, 0xc3, 0xf7, 0x5f, 0x6a, 0x64, + 0x84, 0x4e, 0xa7, 0x49, 0x77, 0x03, 0xfd, 0x9a, 0x44, 0xc5, 0x7a, 0x1d, 0x9f, 0xcf, 0x1f, 0x67, + 0x60, 0x29, 0x36, 0x49, 0xdc, 0xf6, 0x7f, 0x0c, 0x79, 0x09, 0xe4, 0x27, 0x5d, 0xe1, 0x9d, 0x4f, + 0xac, 0x7f, 0x26, 0x22, 0x42, 0x82, 0x55, 0xa6, 0x45, 0x34, 0xd0, 0xbd, 0x98, 0x7b, 0x88, 0xd9, + 0x94, 0x5d, 0xfc, 0x63, 0x5c, 0xac, 0x44, 0x35, 0xf4, 0x19, 0x80, 0xc2, 0x1b, 0x26, 0xba, 0x34, + 0xf1, 0x59, 0x3a, 0x5b, 0x94, 0xca, 0xd6, 0x9f, 0x4e, 0x0a, 0x65, 0xce, 0x4f, 0x21, 0x07, 0xbe, + 0xd3, 0x3a, 0x89, 0x82, 0xdc, 0x3e, 0x4a, 0x46, 0x94, 0x9d, 0x67, 0x1d, 0xdd, 0xd2, 0xde, 0x9e, + 0x9a, 0xd3, 0x58, 0x46, 0xcf, 0x90, 0xc7, 0xce, 0xf1, 0x0c, 0xf9, 0x2e, 0x4c, 0xed, 0x74, 0x9f, + 0xbb, 0xc4, 0xe0, 0x61, 0xe1, 0x55, 0xd4, 0x5c, 0x70, 0x19, 0x48, 0x65, 0x0c, 0xaf, 0x85, 0x3e, + 0x83, 0x1c, 0x39, 0xe9, 0xd3, 0x1d, 0x7f, 0x22, 0x32, 0x2a, 0x43, 0xea, 0x2e, 0x6b, 0x1e, 0xf3, + 0x22, 0x75, 0xa5, 0x8a, 0xea, 0xe8, 0x63, 0x98, 0xaa, 0xb4, 0xdb, 0x64, 0x29, 0xf0, 0x65, 0x44, + 0x73, 0xe0, 0x71, 0x4c, 0x87, 0x95, 0xa8, 0x4d, 0xf2, 0xca, 0xe8, 0x4b, 0xdd, 0x77, 0x35, 0x15, + 0x3d, 0xd2, 0x4f, 0xcf, 0x07, 0xa9, 0xfb, 0xb5, 0xde, 0x15, 0x77, 0x06, 0xb9, 0x28, 0xed, 0x01, + 0x4d, 0x61, 0xa0, 0xe9, 0x1f, 0x7a, 0xe5, 0xb0, 0x03, 0xf9, 0x9d, 0xae, 0x1b, 0xba, 0xf4, 0xe1, + 0x77, 0x5e, 0xdb, 0x07, 0xf6, 0x1d, 0x3f, 0x74, 0x5b, 0x6e, 0xcf, 0xe9, 0x86, 0x6c, 0xb6, 0x5c, + 0x51, 0x51, 0x9d, 0x2d, 0x89, 0xad, 0xa6, 0x88, 0x81, 0x37, 0x96, 0x22, 0x26, 0x35, 0xcb, 0xca, + 0xf4, 0xab, 0x67, 0x59, 0x59, 0x67, 0x73, 0x49, 0x93, 0x65, 0xce, 0x44, 0x82, 0x48, 0x5d, 0x3a, + 0x7a, 0x8a, 0x4c, 0x5b, 0x56, 0x44, 0x37, 0xe8, 0x43, 0xef, 0xd9, 0x28, 0x52, 0x51, 0xbb, 0xa3, + 0xcc, 0xee, 0x6c, 0xa0, 0x26, 0xcc, 0x90, 0xda, 0xfb, 0x5e, 0xc7, 0x6d, 0xb9, 0x38, 0x28, 0xce, + 0x69, 0x3e, 0x40, 0x7d, 0x51, 0xd0, 0x4a, 0x2f, 0xeb, 0x38, 0x64, 0x3b, 0x11, 0x6d, 0xba, 0xc7, + 0x11, 0xd5, 0x9d, 0x48, 0x25, 0x68, 0xd9, 0x50, 0x8c, 0x2e, 0x12, 0x62, 0xab, 0xeb, 0xe3, 0x64, + 0xd0, 0x38, 0x4d, 0x2e, 0x16, 0x05, 0x8d, 0xab, 0x13, 0x16, 0x85, 0x8f, 0x3f, 0x81, 0x6b, 0x36, + 0x3e, 0xf5, 0x9e, 0xe3, 0x37, 0x4b, 0xf6, 0xe7, 0x70, 0x55, 0x27, 0xf8, 0xa4, 0xd7, 0xa6, 0x8f, + 0xd0, 0xd8, 0x8d, 0x45, 0x6a, 0x7a, 0x02, 0x8e, 0xc0, 0xd2, 0x13, 0xb0, 0x37, 0xaf, 0xe4, 0x4f, + 0x55, 0x5e, 0x69, 0x99, 0xe5, 0xc1, 0x8a, 0x4e, 0xbc, 0xd2, 0x6e, 0x2b, 0x82, 0x4a, 0x0c, 0x0d, + 0xe5, 0x33, 0x66, 0xd9, 0xa8, 0x12, 0x4d, 0x35, 0x5b, 0x2f, 0x02, 0xa8, 0x6b, 0x49, 0xa9, 0x67, + 0x61, 0x28, 0xc7, 0xd9, 0x43, 0x58, 0xa6, 0xb6, 0x59, 0x85, 0x59, 0xe5, 0x53, 0xda, 0xca, 0x74, + 0xa9, 0x2b, 0x2d, 0xe8, 0x0c, 0xd3, 0x51, 0xac, 0x7f, 0x35, 0x06, 0xd7, 0x38, 0x9f, 0xde, 0xe4, + 0x64, 0xa0, 0x1f, 0x60, 0x5a, 0x61, 0x3f, 0xe7, 0xc7, 0x0d, 0x71, 0xef, 0x6b, 0x9a, 0x26, 0xa6, + 0x6a, 0xfa, 0x14, 0xd0, 0x8c, 0xcd, 0x04, 0xb1, 0xc4, 0xd4, 0x19, 0xed, 0xc0, 0x9c, 0x3e, 0x07, + 0xdc, 0x3e, 0xbc, 0x99, 0xda, 0x88, 0x5e, 0x55, 0xbc, 0xa1, 0x6d, 0x37, 0x53, 0x67, 0x82, 0x26, + 0x6a, 0xd4, 0xe7, 0xf7, 0x47, 0x98, 0x4f, 0x4c, 0x00, 0x37, 0x27, 0x6f, 0xa5, 0x36, 0x98, 0xa8, + 0xcd, 0xf4, 0x86, 0x4f, 0xc1, 0xc6, 0x66, 0x93, 0x8d, 0x54, 0x73, 0x30, 0xc9, 0x86, 0x4d, 0xd6, + 0xcd, 0xbe, 0x8f, 0x03, 0xdc, 0x6d, 0x61, 0xf5, 0x72, 0xfd, 0x75, 0xd7, 0xcd, 0x7f, 0xcc, 0x40, + 0x31, 0x8d, 0x6e, 0x1d, 0x77, 0xdb, 0x68, 0x1f, 0x0a, 0xf1, 0x86, 0xb8, 0xd1, 0x6f, 0x09, 0x33, + 0xc1, 0xdc, 0xa5, 0xed, 0x4b, 0x76, 0x02, 0x9b, 0x68, 0x56, 0x05, 0x76, 0xc1, 0x28, 0x86, 0x24, + 0xaa, 0x72, 0xb6, 0x7b, 0xef, 0x3d, 0xc8, 0xcb, 0xec, 0xd3, 0x28, 0x07, 0xe3, 0x3b, 0x7b, 0x3b, + 0x07, 0x2c, 0x9b, 0xd1, 0xfe, 0x93, 0x83, 0x42, 0x06, 0x01, 0x4c, 0x6e, 0x6c, 0xee, 0x6e, 0x1e, + 0x6c, 0x16, 0xb2, 0xef, 0x35, 0xd5, 0x53, 0x12, 0xba, 0x06, 0xcb, 0x1b, 0x9b, 0x8d, 0x9d, 0xda, + 0x66, 0xf3, 0xe0, 0x2f, 0xec, 0x6f, 0x36, 0x9f, 0xec, 0xd5, 0xf7, 0x37, 0x6b, 0x3b, 0xf7, 0x77, + 0x36, 0x37, 0x0a, 0x97, 0xd0, 0x22, 0x14, 0xd4, 0xc2, 0x83, 0xc7, 0x07, 0xfb, 0x85, 0x0c, 0x2a, + 0xc2, 0xa2, 0x0a, 0x7d, 0xba, 0x59, 0xad, 0x3c, 0x39, 0xd8, 0xde, 0x2b, 0x8c, 0x59, 0xe3, 0xb9, + 0x6c, 0x21, 0xfb, 0xde, 0x2d, 0x7e, 0x63, 0x89, 0xe6, 0x00, 0x36, 0x36, 0xeb, 0xb5, 0xcd, 0xbd, + 0x8d, 0x9d, 0xbd, 0xad, 0xc2, 0x25, 0x34, 0x0b, 0xf9, 0x8a, 0xfc, 0xcc, 0xac, 0xfd, 0xd1, 0x0f, + 0x30, 0x4d, 0x46, 0x2b, 0x32, 0x4e, 0x36, 0x61, 0xf9, 0x91, 0xe3, 0x76, 0x43, 0xc7, 0xed, 0xf2, + 0x39, 0x12, 0x1c, 0x46, 0xe5, 0x21, 0x2c, 0x27, 0xb3, 0x55, 0x1a, 0x15, 0x97, 0x71, 0x3b, 0x73, + 0x2f, 0x83, 0xea, 0xb0, 0x98, 0x66, 0x33, 0x21, 0x4b, 0x7f, 0x0a, 0x9d, 0xa6, 0x0e, 0x4a, 0xcb, + 0xa9, 0x3b, 0x4b, 0xe3, 0x03, 0xf4, 0x08, 0xe6, 0x13, 0xfb, 0x84, 0xec, 0xaf, 0x69, 0x07, 0x19, + 0x46, 0xae, 0x48, 0x3d, 0x24, 0xa1, 0x1b, 0xdf, 0x25, 0x02, 0x74, 0x25, 0xb1, 0xbd, 0x6f, 0x12, + 0x91, 0x36, 0x12, 0xbb, 0x97, 0x41, 0x36, 0x2c, 0xa6, 0xed, 0x38, 0x72, 0xc8, 0x43, 0xb6, 0xa3, + 0x92, 0xa1, 0x39, 0x42, 0x33, 0x4d, 0x71, 0x4a, 0x9a, 0x43, 0xb4, 0xaa, 0x91, 0xe6, 0x97, 0xe4, + 0xcc, 0xd2, 0x6d, 0x3f, 0xc4, 0xb8, 0x57, 0xe9, 0xb8, 0xcf, 0x71, 0x80, 0x44, 0x54, 0x91, 0x04, + 0x99, 0x70, 0x6f, 0x67, 0xd0, 0x6f, 0xc1, 0x34, 0x4d, 0x47, 0xc9, 0x2f, 0xc1, 0x67, 0xd4, 0x14, + 0x95, 0x25, 0xf1, 0x45, 0x0b, 0xef, 0x65, 0xd0, 0x57, 0x30, 0xb5, 0x85, 0xe9, 0x2d, 0x30, 0x7a, + 0x2b, 0x96, 0x75, 0x7d, 0xa7, 0x2b, 0x4d, 0x61, 0xd1, 0xe1, 0xf8, 0x91, 0x19, 0xd5, 0x20, 0xc7, + 0xd1, 0x03, 0x64, 0xc5, 0xf0, 0x83, 0x14, 0x02, 0x0b, 0x31, 0x02, 0xe4, 0x34, 0x82, 0x6a, 0x90, + 0x97, 0x57, 0xd1, 0x68, 0xd9, 0x70, 0xff, 0x5d, 0x2a, 0x26, 0x0b, 0xb8, 0xd3, 0x64, 0xec, 0xef, + 0x66, 0x33, 0xe8, 0x2e, 0x00, 0x7b, 0x44, 0x43, 0xc7, 0x12, 0xef, 0x68, 0x29, 0xc1, 0x40, 0xb4, + 0x45, 0x56, 0x7e, 0x07, 0x87, 0xf8, 0xbc, 0x83, 0x37, 0xcd, 0xd6, 0x2e, 0xcc, 0xc9, 0x27, 0x2d, + 0xe7, 0xe7, 0x84, 0x89, 0xda, 0xe7, 0x64, 0x05, 0xb1, 0x97, 0xa0, 0x32, 0xf6, 0x0b, 0x99, 0xa2, + 0xc1, 0xe4, 0x74, 0xb2, 0x6a, 0x0a, 0xae, 0xcc, 0xf5, 0x29, 0x71, 0xe3, 0xd9, 0x3f, 0x63, 0xb8, + 0x18, 0x4a, 0x6a, 0xbb, 0x7a, 0x1c, 0x18, 0xba, 0xa1, 0x74, 0x20, 0x35, 0x7c, 0xad, 0xf4, 0xd6, + 0x90, 0x1a, 0x6c, 0x9e, 0xa8, 0xd6, 0x79, 0x00, 0xb3, 0x5a, 0xe4, 0x10, 0x12, 0x91, 0xc2, 0x69, + 0xa1, 0x5d, 0xa5, 0x95, 0xf4, 0x42, 0x7e, 0x68, 0xbd, 0x4f, 0x95, 0x4d, 0x2c, 0xb7, 0x56, 0x29, + 0x2d, 0x87, 0x16, 0xcb, 0x9a, 0x5a, 0x12, 0xd9, 0x16, 0x62, 0x28, 0x9b, 0xb0, 0x20, 0xfd, 0xc6, + 0x4a, 0xc2, 0x72, 0x43, 0x36, 0x2e, 0xe3, 0xcc, 0x7d, 0x03, 0x0b, 0x5c, 0x0e, 0x34, 0x32, 0x05, + 0xa9, 0x5c, 0x78, 0xe2, 0x26, 0x23, 0x81, 0x07, 0xb0, 0x54, 0x8f, 0x8d, 0x87, 0xd9, 0x38, 0x57, + 0x75, 0x12, 0x4a, 0x1e, 0x2e, 0x23, 0xad, 0x87, 0x80, 0xea, 0xfd, 0xc3, 0x53, 0x57, 0x92, 0x7b, + 0xee, 0xe2, 0x17, 0xe8, 0x7a, 0x6c, 0x48, 0x04, 0x48, 0xab, 0x51, 0xed, 0x54, 0x32, 0x8c, 0x18, + 0x1d, 0xb0, 0xa7, 0xc1, 0x2c, 0x41, 0x89, 0xd3, 0x73, 0x0e, 0xdd, 0x8e, 0x1b, 0xba, 0x98, 0x88, + 0x85, 0x8a, 0xa0, 0x16, 0x89, 0x19, 0xbc, 0x6a, 0xac, 0x81, 0xbe, 0x86, 0xd9, 0x2d, 0x1c, 0x46, + 0xa9, 0xc6, 0xd0, 0x72, 0x22, 0x39, 0x19, 0x9f, 0x37, 0x71, 0x83, 0xa8, 0xe7, 0x37, 0xdb, 0x81, + 0x02, 0x53, 0xae, 0x0a, 0x89, 0xeb, 0x09, 0x12, 0xbc, 0x8a, 0xe3, 0x3b, 0xa7, 0x81, 0x91, 0x5b, + 0x77, 0x61, 0x7c, 0xdf, 0xed, 0x1e, 0x21, 0x24, 0x1a, 0x8a, 0x52, 0xf5, 0x94, 0x16, 0x34, 0x18, + 0x17, 0xbd, 0x43, 0x28, 0xb3, 0x1c, 0x5b, 0xc9, 0xbc, 0x56, 0x22, 0x91, 0xf0, 0xdb, 0x32, 0xe0, + 0x6f, 0x48, 0x2e, 0x2e, 0xc9, 0x9f, 0x78, 0x79, 0x63, 0x1d, 0xed, 0x53, 0xae, 0x27, 0x1b, 0x40, + 0x37, 0xa3, 0xfd, 0xd4, 0x98, 0x56, 0xab, 0x84, 0xe2, 0x84, 0x1b, 0xeb, 0x48, 0xbe, 0x28, 0x4e, + 0x21, 0x7a, 0x4b, 0xdb, 0xf6, 0x2f, 0x46, 0xf7, 0x6b, 0xc8, 0xcb, 0x9c, 0x52, 0x52, 0xdf, 0xc4, + 0x13, 0x61, 0x49, 0x05, 0x9e, 0x4c, 0x3f, 0xf5, 0x25, 0x4b, 0xff, 0xa6, 0xe3, 0xc7, 0xd3, 0x2e, + 0x19, 0x27, 0xef, 0x33, 0x98, 0x56, 0x12, 0x2e, 0xc9, 0xc5, 0x92, 0x4c, 0xc2, 0x54, 0xd2, 0x7f, + 0x98, 0xe2, 0x1e, 0xd9, 0x34, 0xa6, 0x78, 0x86, 0x3f, 0xb4, 0x14, 0xa1, 0x29, 0x59, 0x6b, 0x62, + 0x28, 0xe4, 0xfc, 0x2e, 0x52, 0x02, 0xa2, 0x2b, 0x3a, 0x86, 0xb9, 0x95, 0x75, 0x00, 0x36, 0x66, + 0xda, 0x90, 0x5e, 0x6c, 0x1c, 0xd5, 0x3a, 0xd9, 0xcf, 0xda, 0x17, 0x44, 0xfa, 0x5a, 0xec, 0x69, + 0x14, 0xa9, 0xa8, 0x71, 0x52, 0x1d, 0x95, 0x09, 0x7f, 0x07, 0x0a, 0x95, 0x16, 0xd5, 0xb2, 0x32, + 0x91, 0x0e, 0x5a, 0x95, 0x2b, 0x58, 0x2f, 0x10, 0xb4, 0x96, 0xe2, 0x79, 0x79, 0x76, 0xb1, 0x43, + 0x43, 0x92, 0x96, 0xe5, 0x5e, 0x1b, 0x2b, 0x4a, 0xc7, 0x30, 0x76, 0x6a, 0x13, 0x16, 0x6b, 0x4e, + 0xb7, 0x85, 0x3b, 0xaf, 0x47, 0xe6, 0x73, 0xaa, 0x6e, 0x94, 0x24, 0x43, 0x57, 0xe2, 0xf8, 0x5c, + 0xdb, 0xcc, 0xcb, 0x83, 0x9c, 0xac, 0x5a, 0x81, 0xcb, 0x8c, 0x89, 0x11, 0x5b, 0x4c, 0xd8, 0xa6, + 0xe6, 0x3f, 0x81, 0xb9, 0x4d, 0xa2, 0x8e, 0xfb, 0x6d, 0x97, 0x45, 0xaf, 0x22, 0x3d, 0x1c, 0xd1, + 0x88, 0xb8, 0x2d, 0x12, 0xb6, 0x29, 0xd9, 0x77, 0xa4, 0x90, 0x27, 0x13, 0x1c, 0x95, 0x16, 0x05, + 0x59, 0x35, 0x51, 0x0f, 0xdd, 0x7b, 0x8f, 0x44, 0x86, 0x87, 0x58, 0x4e, 0x15, 0x55, 0xa1, 0x18, + 0x33, 0xae, 0x94, 0xde, 0x1e, 0x5e, 0x49, 0xb5, 0xc5, 0x6c, 0x58, 0x36, 0xe4, 0xab, 0x41, 0xef, + 0x48, 0xb3, 0x78, 0x58, 0x3e, 0x9b, 0x14, 0x73, 0xed, 0x3b, 0x25, 0x6b, 0x81, 0x81, 0xe6, 0xf0, + 0x44, 0x36, 0x46, 0x06, 0xcb, 0x08, 0xad, 0xd4, 0x84, 0x33, 0xe8, 0x5d, 0x9d, 0xfa, 0x90, 0xa4, + 0x34, 0xc6, 0x16, 0x1e, 0x53, 0xd1, 0x8b, 0xf2, 0x9d, 0x48, 0xa3, 0x27, 0x2d, 0x29, 0x8d, 0x34, + 0x7a, 0x52, 0xb3, 0xc5, 0x30, 0x06, 0x6f, 0xc1, 0xe5, 0x58, 0xea, 0x17, 0x74, 0x3d, 0xce, 0xd8, + 0x11, 0x0c, 0x65, 0x84, 0x1e, 0x09, 0xc1, 0x4e, 0x12, 0x4a, 0x4f, 0x06, 0x63, 0x1a, 0x23, 0x23, + 0xf7, 0x44, 0x9a, 0x40, 0x6a, 0x7a, 0x17, 0xf4, 0x56, 0x0a, 0x0b, 0xcf, 0xc7, 0x3a, 0x46, 0xb6, + 0x0e, 0x85, 0x78, 0x76, 0x14, 0xb4, 0x2a, 0xb9, 0x94, 0x9a, 0x02, 0xa6, 0x54, 0x36, 0x96, 0xf3, + 0x4d, 0xe7, 0x41, 0x34, 0x29, 0xec, 0xa6, 0x28, 0x3e, 0x29, 0x6a, 0x66, 0x8c, 0xc4, 0xa4, 0xe8, + 0x09, 0x2f, 0xb6, 0x68, 0x5c, 0xa9, 0x92, 0xe6, 0xc4, 0x78, 0x3a, 0xbd, 0x9e, 0x46, 0x27, 0xba, + 0x87, 0xa9, 0x8b, 0x04, 0x92, 0x4a, 0xbf, 0x56, 0xb5, 0x7d, 0x33, 0xd9, 0xb5, 0xb2, 0xb1, 0x5c, + 0x8e, 0xb4, 0x10, 0x4f, 0xf8, 0x21, 0x89, 0x1a, 0x32, 0x81, 0x18, 0x45, 0xf9, 0x3e, 0x2c, 0xea, + 0xb3, 0x38, 0x62, 0xbc, 0x66, 0x5b, 0x77, 0x56, 0x4b, 0xf3, 0x81, 0xc4, 0xcf, 0x9c, 0xc4, 0x32, + 0x8a, 0x24, 0xb8, 0x9f, 0x92, 0x6e, 0x84, 0x71, 0x5f, 0x49, 0x19, 0x72, 0x1e, 0xee, 0xa7, 0x65, + 0x18, 0x91, 0x8c, 0x52, 0xfa, 0x25, 0xb6, 0xbf, 0x78, 0xc1, 0x45, 0x18, 0x75, 0x9e, 0xae, 0x99, + 0xe8, 0x6c, 0x50, 0xeb, 0x46, 0xfe, 0x2e, 0xcf, 0x55, 0x8d, 0x4d, 0x9a, 0xc4, 0x97, 0xb4, 0xc1, + 0xe9, 0xc2, 0x5e, 0x83, 0x19, 0x35, 0x63, 0x89, 0xb1, 0x17, 0xd7, 0x92, 0x34, 0x02, 0xe5, 0xbc, + 0x35, 0x27, 0xb9, 0xc0, 0x7a, 0xb3, 0x12, 0x67, 0x8e, 0xd6, 0x21, 0xf3, 0x90, 0x90, 0xca, 0x9a, + 0x11, 0x5d, 0x32, 0x9b, 0x05, 0x0b, 0xcc, 0x40, 0xd2, 0x7f, 0xb6, 0xc9, 0xf0, 0xeb, 0x4f, 0x46, + 0x32, 0x4f, 0x68, 0xb4, 0xba, 0x9a, 0x7e, 0x04, 0x29, 0x52, 0x92, 0x92, 0x96, 0xa4, 0xb4, 0x6a, + 0x2a, 0x56, 0x35, 0xf4, 0xb7, 0x30, 0x9f, 0x48, 0xb3, 0x22, 0x1d, 0x61, 0xa6, 0x04, 0x2c, 0xc3, + 0xb5, 0xe0, 0x36, 0x19, 0x70, 0x0c, 0xb1, 0xb1, 0x36, 0x9a, 0x68, 0x72, 0x2f, 0xdd, 0x15, 0x01, + 0xee, 0x69, 0x9d, 0x33, 0x25, 0x73, 0x31, 0x72, 0xf0, 0x00, 0x96, 0x52, 0xd3, 0xb8, 0x48, 0xb3, + 0x62, 0x58, 0x92, 0x17, 0x23, 0xd5, 0xbf, 0x48, 0xd3, 0xa8, 0xc6, 0x52, 0x84, 0x48, 0x3f, 0x84, + 0x31, 0x2d, 0x8c, 0xf4, 0x43, 0x98, 0xf3, 0xb5, 0x30, 0x6e, 0xee, 0xc2, 0x62, 0x5a, 0xd2, 0x15, + 0xc5, 0x6f, 0x67, 0xcc, 0xc8, 0x92, 0xc2, 0x51, 0x5b, 0xac, 0x76, 0x03, 0xb5, 0x21, 0x29, 0x58, + 0x8c, 0x1c, 0xf8, 0x99, 0x48, 0xac, 0x93, 0x4c, 0x95, 0x22, 0x4f, 0x6b, 0x23, 0x72, 0xa9, 0x0c, + 0x31, 0x2a, 0x2f, 0xd7, 0xdd, 0xa3, 0xae, 0x92, 0xd5, 0x44, 0x9a, 0x94, 0xc9, 0x74, 0x2a, 0x52, + 0xb3, 0xa4, 0x25, 0x41, 0x79, 0x0c, 0x8b, 0x62, 0x8b, 0x55, 0xb3, 0x80, 0xa0, 0x04, 0x4e, 0x14, + 0x94, 0x2e, 0xb5, 0x4c, 0x6a, 0xda, 0x10, 0x76, 0x26, 0xa3, 0xbf, 0xfb, 0xa2, 0x9c, 0xc9, 0x94, + 0xf4, 0x1c, 0x25, 0x3d, 0x93, 0x07, 0xfa, 0x82, 0x9e, 0xc9, 0x58, 0xb6, 0x36, 0xb3, 0x53, 0x58, + 0xa3, 0x14, 0xe9, 0xb4, 0x75, 0xe1, 0x36, 0xa4, 0x0d, 0xea, 0x94, 0x47, 0x1f, 0xb3, 0x28, 0x92, + 0x7e, 0xcc, 0x52, 0x3b, 0x6a, 0x76, 0xce, 0xcc, 0xa8, 0x2f, 0x5d, 0x25, 0xaf, 0x52, 0x9e, 0xae, + 0x4b, 0x5e, 0xa5, 0x3d, 0x34, 0xa7, 0x56, 0xfd, 0x81, 0x30, 0xe1, 0x22, 0x7a, 0xd7, 0x87, 0xbe, + 0x14, 0x2f, 0xad, 0x0e, 0x7f, 0x5e, 0xcd, 0x6f, 0x07, 0x0a, 0xf1, 0xc7, 0xb8, 0x28, 0xed, 0x3d, + 0xbe, 0xf2, 0x22, 0x59, 0x1a, 0x22, 0xc6, 0x57, 0xbc, 0xfb, 0xc2, 0x3c, 0xd4, 0xe9, 0x1a, 0x9e, + 0x7b, 0xab, 0xa4, 0x87, 0x9b, 0x11, 0xd1, 0xbb, 0x5c, 0xd5, 0x88, 0x4b, 0xbc, 0xfb, 0x55, 0xcd, + 0x88, 0x94, 0xa7, 0xbc, 0xae, 0x08, 0x22, 0x49, 0xcf, 0xf1, 0xf2, 0xae, 0x6e, 0x66, 0x0d, 0x09, + 0x94, 0x1c, 0x79, 0xff, 0x82, 0x7e, 0x47, 0x24, 0xaf, 0x4b, 0x66, 0x40, 0x78, 0x27, 0xe6, 0x87, + 0x49, 0x8f, 0x02, 0x2c, 0x0d, 0x4b, 0xb0, 0x80, 0x1e, 0xd1, 0xf7, 0x51, 0x8f, 0x77, 0x36, 0x6a, + 0xfc, 0xd7, 0x1d, 0x3d, 0x3f, 0xe1, 0xe0, 0x56, 0x7e, 0xa1, 0x20, 0x62, 0x32, 0xab, 0xa2, 0x21, + 0x36, 0xd6, 0x51, 0x9d, 0xfa, 0x59, 0x35, 0x68, 0x8a, 0x8f, 0x3b, 0x85, 0x60, 0x29, 0x9d, 0x20, + 0x75, 0xfa, 0x6f, 0x8a, 0xdd, 0x4c, 0xef, 0xa6, 0xa1, 0x0f, 0xc3, 0xac, 0x00, 0x26, 0x36, 0xe9, + 0x64, 0x44, 0xef, 0x46, 0xc9, 0x11, 0xe3, 0x58, 0xbd, 0xf2, 0x68, 0xf7, 0x95, 0x38, 0xa6, 0x21, + 0x36, 0xd6, 0x38, 0xc7, 0x34, 0xe8, 0xc5, 0x38, 0x16, 0x23, 0xa8, 0x73, 0x4c, 0xef, 0xa6, 0xa1, + 0x0f, 0xa3, 0x39, 0x96, 0x4e, 0xe6, 0xbc, 0x1c, 0xfb, 0x96, 0xee, 0xcf, 0x5b, 0xf4, 0xb5, 0xce, + 0x85, 0x78, 0x56, 0x14, 0x16, 0xac, 0x8e, 0xda, 0x58, 0x47, 0x4f, 0x69, 0x86, 0xbe, 0x18, 0xfc, + 0x7c, 0x7c, 0x5b, 0x31, 0x11, 0xa5, 0x9c, 0xdb, 0x81, 0x25, 0xc6, 0xb9, 0x78, 0x77, 0x8d, 0x7d, + 0x31, 0x0e, 0x7b, 0x4b, 0x18, 0x3b, 0x71, 0x52, 0x17, 0xe5, 0xdf, 0x06, 0x15, 0x91, 0x03, 0x9f, + 0xd8, 0xa7, 0xed, 0xa4, 0xf1, 0xaa, 0x13, 0x11, 0x9e, 0x71, 0xbd, 0x7a, 0x63, 0x0d, 0xed, 0xd0, + 0x59, 0xd0, 0xc1, 0xc3, 0xac, 0xfb, 0x74, 0x32, 0x94, 0x49, 0xdb, 0xc2, 0x20, 0x8a, 0xf5, 0xc9, + 0xd4, 0xb6, 0xb9, 0x53, 0xf2, 0xe8, 0x73, 0xce, 0xd1, 0x99, 0x58, 0xc4, 0x36, 0x76, 0x76, 0xd2, + 0x18, 0xc5, 0x99, 0xf8, 0x2f, 0x23, 0xa3, 0x9f, 0x42, 0x5e, 0x20, 0x8f, 0x66, 0x48, 0x1c, 0x9b, + 0x32, 0xe4, 0x6b, 0x98, 0xe6, 0x0c, 0xa1, 0x3d, 0x30, 0xb5, 0x64, 0xec, 0xfe, 0x57, 0x30, 0xcd, + 0xd9, 0x30, 0x74, 0x04, 0xe6, 0x6b, 0xc5, 0xa5, 0x2d, 0x1c, 0xa6, 0xfc, 0x72, 0xe7, 0xa8, 0xc1, + 0xa4, 0xfd, 0x50, 0x28, 0x6a, 0xd0, 0x77, 0x8e, 0xa6, 0x5f, 0x59, 0x35, 0x91, 0x1c, 0xf9, 0x1b, + 0xaf, 0x84, 0x6e, 0xdd, 0x4c, 0x77, 0x24, 0xbe, 0x71, 0xf4, 0x7b, 0xb0, 0x42, 0xef, 0x20, 0x2e, + 0xda, 0x63, 0xf3, 0x21, 0xe5, 0x6a, 0x14, 0x7e, 0x10, 0xff, 0x81, 0x57, 0x13, 0xb1, 0x51, 0xbf, + 0x2d, 0x4b, 0xa8, 0xd6, 0x8d, 0x54, 0x47, 0x61, 0x0f, 0xd9, 0x8c, 0xae, 0xd1, 0xb1, 0x5f, 0xb0, + 0xb7, 0xc3, 0x35, 0x4d, 0xec, 0x17, 0x67, 0x47, 0x45, 0x4f, 0xc4, 0x7f, 0xd3, 0x96, 0x50, 0xa9, + 0x27, 0xa8, 0x98, 0x6a, 0x0f, 0xdb, 0x7c, 0xe8, 0xd0, 0xce, 0xd9, 0x1b, 0xf3, 0xe5, 0x48, 0x5e, + 0x26, 0x81, 0x40, 0x8a, 0x6d, 0xaf, 0xa5, 0x38, 0x28, 0xcd, 0xaa, 0xb1, 0x0e, 0x01, 0xaa, 0xb0, + 0x3d, 0x5e, 0x4d, 0x86, 0xa0, 0x78, 0x11, 0x53, 0xb3, 0x24, 0xc4, 0x49, 0xb0, 0xb3, 0x09, 0xfd, + 0x19, 0x5c, 0xe5, 0x6c, 0xa2, 0x3c, 0x13, 0x2f, 0xe9, 0x8f, 0xb8, 0xb9, 0x0a, 0xa3, 0x2f, 0xb9, + 0xd5, 0xfb, 0x22, 0xf5, 0xa1, 0xb8, 0x7a, 0x36, 0xd1, 0x9f, 0xb4, 0xcb, 0xb3, 0x09, 0x6d, 0x50, + 0xa7, 0x3c, 0xfa, 0x6c, 0x42, 0x91, 0xf4, 0xb3, 0x89, 0xda, 0x51, 0xf3, 0xc2, 0x43, 0xc9, 0x37, + 0xed, 0xf2, 0xdc, 0x6d, 0x7c, 0xee, 0x3e, 0xe4, 0x4a, 0x69, 0x21, 0x25, 0x7b, 0x89, 0xb4, 0xf9, + 0xcd, 0x99, 0x4d, 0x4a, 0xfa, 0xfd, 0xc8, 0xbd, 0x0c, 0xda, 0xa3, 0x79, 0x72, 0xd3, 0x7e, 0x1d, + 0xd8, 0x24, 0x3f, 0x43, 0x7f, 0x8e, 0x98, 0xd0, 0xab, 0xa7, 0xd3, 0x1b, 0x8a, 0x37, 0xe4, 0x58, + 0x77, 0x95, 0x47, 0x94, 0x5c, 0xa0, 0x8b, 0x66, 0x11, 0x9f, 0x62, 0x3e, 0x62, 0x33, 0x6a, 0x41, + 0xfd, 0x39, 0x5e, 0xba, 0x65, 0xdd, 0x81, 0x49, 0x86, 0x64, 0xdc, 0x6d, 0xb4, 0x9f, 0xf0, 0x45, + 0x1f, 0x88, 0x8b, 0x5b, 0x82, 0xa2, 0x15, 0x19, 0xfb, 0xf5, 0x01, 0xe4, 0x99, 0xdb, 0xed, 0xfc, + 0x28, 0x5f, 0x88, 0xeb, 0xdd, 0x61, 0x1d, 0x33, 0x47, 0x55, 0xcc, 0xaa, 0x0e, 0xe7, 0x8b, 0x33, + 0xf2, 0x2b, 0xea, 0xfa, 0x14, 0xae, 0x06, 0x33, 0xfe, 0x52, 0xe2, 0xe7, 0x96, 0x29, 0x4b, 0x3f, + 0xa5, 0xfe, 0x57, 0x99, 0x06, 0xc8, 0xd4, 0xfd, 0xe4, 0x8f, 0x35, 0xa3, 0x2f, 0x60, 0x8e, 0x31, + 0x57, 0x22, 0x27, 0x2b, 0x0d, 0xe1, 0xd9, 0x1c, 0x63, 0xf3, 0xab, 0x20, 0xff, 0x54, 0x38, 0x6a, + 0x47, 0x76, 0xfb, 0x3c, 0x2e, 0xda, 0xd1, 0xac, 0x33, 0x51, 0xf9, 0x1d, 0xba, 0xe9, 0xa6, 0xe7, + 0xae, 0x30, 0x12, 0xbb, 0xad, 0xb8, 0xa0, 0x87, 0x67, 0xbd, 0x38, 0xa1, 0xd1, 0x81, 0xe9, 0x3f, + 0xd8, 0x7d, 0x6b, 0x04, 0x15, 0xc1, 0x80, 0x9f, 0x8c, 0xac, 0x27, 0xfd, 0x5c, 0x3c, 0x49, 0x73, + 0x7a, 0x7b, 0x23, 0x32, 0x58, 0xa4, 0xb8, 0x0c, 0x0d, 0x89, 0x21, 0x04, 0x41, 0xfd, 0xda, 0x71, + 0xe8, 0x18, 0xcc, 0x47, 0xb4, 0x28, 0xd7, 0xf2, 0x05, 0x27, 0xc1, 0x6c, 0x46, 0xa1, 0x64, 0xba, + 0x0c, 0x79, 0x77, 0xa3, 0xc3, 0xf9, 0x85, 0xf8, 0x5b, 0x26, 0x0e, 0x07, 0xca, 0x05, 0x0e, 0x0f, + 0x45, 0x8d, 0xfd, 0x16, 0xa6, 0x29, 0xd9, 0xc6, 0x90, 0xd3, 0x19, 0x0f, 0xc6, 0x7c, 0x23, 0x84, + 0x92, 0xb3, 0x7d, 0x71, 0x42, 0xd2, 0x31, 0x1c, 0x23, 0x64, 0x0d, 0x99, 0xde, 0xd1, 0x4e, 0xaf, + 0xa2, 0x61, 0x5e, 0x2f, 0x3e, 0xa1, 0x4e, 0x14, 0xf6, 0x97, 0xcc, 0xe9, 0x21, 0xb7, 0x7d, 0x63, + 0x7e, 0x11, 0x39, 0xbb, 0x43, 0x12, 0x82, 0xd4, 0xa2, 0xdf, 0xb6, 0xd0, 0x92, 0x80, 0xd4, 0xec, + 0x5d, 0xe9, 0xae, 0x4b, 0xcb, 0x0e, 0x52, 0x02, 0x51, 0x68, 0xef, 0x92, 0xb5, 0x6e, 0x4a, 0x60, + 0x11, 0x85, 0x2e, 0x0d, 0x4f, 0xef, 0x21, 0xd7, 0xfa, 0xc8, 0x4c, 0x18, 0x7b, 0xb0, 0x98, 0x96, + 0x78, 0x42, 0x4e, 0xda, 0x90, 0xac, 0x14, 0xa9, 0xf1, 0x51, 0xfb, 0xb0, 0x94, 0x9a, 0xfc, 0x41, + 0xde, 0x90, 0x0c, 0x4b, 0x0d, 0x91, 0x4a, 0xf1, 0x3b, 0x58, 0x36, 0x64, 0x3a, 0x88, 0x1c, 0x88, + 0x43, 0x33, 0x21, 0x18, 0x05, 0xe2, 0x7b, 0x28, 0x99, 0x1f, 0xd1, 0xa3, 0xdb, 0xba, 0x13, 0xd4, + 0xfc, 0x74, 0xbd, 0x94, 0x9a, 0xf5, 0x03, 0x1d, 0xd0, 0x84, 0x64, 0x69, 0xaf, 0xea, 0x65, 0xbf, + 0x87, 0xbf, 0xba, 0x37, 0xc4, 0xb5, 0x2d, 0x1b, 0x1e, 0xd2, 0x0f, 0xa1, 0x7a, 0x8e, 0xde, 0xee, + 0x09, 0xbd, 0xa4, 0xbf, 0x11, 0x8f, 0x85, 0xc8, 0xa7, 0x3e, 0x20, 0x4f, 0xed, 0xe7, 0x03, 0x98, + 0xd5, 0x9e, 0x5f, 0x4a, 0xf1, 0x4f, 0x7b, 0x39, 0x2b, 0xbd, 0xd5, 0xa9, 0x2f, 0x36, 0xab, 0x85, + 0x5f, 0xfe, 0x8f, 0xd5, 0xcc, 0x2f, 0x7f, 0xb5, 0x9a, 0xf9, 0x6f, 0xbf, 0x5a, 0xcd, 0xfc, 0xd9, + 0xaf, 0x56, 0x33, 0x87, 0x93, 0xb4, 0xfa, 0xfa, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x8e, 0xe9, + 0x48, 0xe5, 0x19, 0x8d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -22333,11 +22123,6 @@ func (m *AddMFADeviceRequestInit) MarshalToSizedBuffer(dAtA []byte) (int, error) i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - if m.DeviceUsage != 0 { - i = encodeVarintAuthservice(dAtA, i, uint64(m.DeviceUsage)) - i-- - dAtA[i] = 0x20 - } if m.DeviceType != 0 { i = encodeVarintAuthservice(dAtA, i, uint64(m.DeviceType)) i-- @@ -24658,60 +24443,6 @@ func (m *UserCredentials) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *ContextUser) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ContextUser) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ContextUser) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.XXX_unrecognized != nil { - i -= len(m.XXX_unrecognized) - copy(dAtA[i:], m.XXX_unrecognized) - } - return len(dAtA) - i, nil -} - -func (m *Passwordless) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *Passwordless) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *Passwordless) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.XXX_unrecognized != nil { - i -= len(m.XXX_unrecognized) - copy(dAtA[i:], m.XXX_unrecognized) - } - return len(dAtA) - i, nil -} - func (m *CreateAuthenticateChallengeRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -24783,48 +24514,6 @@ func (m *CreateAuthenticateChallengeRequest_RecoveryStartTokenID) MarshalToSized dAtA[i] = 0x12 return len(dAtA) - i, nil } -func (m *CreateAuthenticateChallengeRequest_ContextUser) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *CreateAuthenticateChallengeRequest_ContextUser) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - if m.ContextUser != nil { - { - size, err := m.ContextUser.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintAuthservice(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - return len(dAtA) - i, nil -} -func (m *CreateAuthenticateChallengeRequest_Passwordless) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *CreateAuthenticateChallengeRequest_Passwordless) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - if m.Passwordless != nil { - { - size, err := m.Passwordless.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintAuthservice(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - return len(dAtA) - i, nil -} func (m *CreatePrivilegeTokenRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -24888,11 +24577,6 @@ func (m *CreateRegisterChallengeRequest) MarshalToSizedBuffer(dAtA []byte) (int, i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - if m.DeviceUsage != 0 { - i = encodeVarintAuthservice(dAtA, i, uint64(m.DeviceUsage)) - i-- - dAtA[i] = 0x18 - } if m.DeviceType != 0 { i = encodeVarintAuthservice(dAtA, i, uint64(m.DeviceType)) i-- @@ -25028,48 +24712,6 @@ func (m *PaginatedResource_KubeService) MarshalToSizedBuffer(dAtA []byte) (int, } return len(dAtA) - i, nil } -func (m *PaginatedResource_WindowsDesktop) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PaginatedResource_WindowsDesktop) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - if m.WindowsDesktop != nil { - { - size, err := m.WindowsDesktop.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintAuthservice(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - return len(dAtA) - i, nil -} -func (m *PaginatedResource_KubeCluster) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PaginatedResource_KubeCluster) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - if m.KubeCluster != nil { - { - size, err := m.KubeCluster.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintAuthservice(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x32 - } - return len(dAtA) - i, nil -} func (m *ListResourcesRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -25094,16 +24736,6 @@ func (m *ListResourcesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - { - size, err := m.WindowsDesktopFilter.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintAuthservice(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x52 if m.NeedTotalCount { i-- if m.NeedTotalCount { @@ -25300,12 +24932,12 @@ func (m *CreateSessionTrackerRequest) MarshalToSizedBuffer(dAtA []byte) (int, er i-- dAtA[i] = 0x5a } - n101, err101 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err101 != nil { - return 0, err101 + n96, err96 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err96 != nil { + return 0, err96 } - i -= n101 - i = encodeVarintAuthservice(dAtA, i, uint64(n101)) + i -= n96 + i = encodeVarintAuthservice(dAtA, i, uint64(n96)) i-- dAtA[i] = 0x52 if m.Initiator != nil { @@ -27983,9 +27615,6 @@ func (m *AddMFADeviceRequestInit) Size() (n int) { if m.DeviceType != 0 { n += 1 + sovAuthservice(uint64(m.DeviceType)) } - if m.DeviceUsage != 0 { - n += 1 + sovAuthservice(uint64(m.DeviceUsage)) - } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -29090,30 +28719,6 @@ func (m *UserCredentials) Size() (n int) { return n } -func (m *ContextUser) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.XXX_unrecognized != nil { - n += len(m.XXX_unrecognized) - } - return n -} - -func (m *Passwordless) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.XXX_unrecognized != nil { - n += len(m.XXX_unrecognized) - } - return n -} - func (m *CreateAuthenticateChallengeRequest) Size() (n int) { if m == nil { return 0 @@ -29151,30 +28756,6 @@ func (m *CreateAuthenticateChallengeRequest_RecoveryStartTokenID) Size() (n int) n += 1 + l + sovAuthservice(uint64(l)) return n } -func (m *CreateAuthenticateChallengeRequest_ContextUser) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.ContextUser != nil { - l = m.ContextUser.Size() - n += 1 + l + sovAuthservice(uint64(l)) - } - return n -} -func (m *CreateAuthenticateChallengeRequest_Passwordless) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Passwordless != nil { - l = m.Passwordless.Size() - n += 1 + l + sovAuthservice(uint64(l)) - } - return n -} func (m *CreatePrivilegeTokenRequest) Size() (n int) { if m == nil { return 0 @@ -29204,9 +28785,6 @@ func (m *CreateRegisterChallengeRequest) Size() (n int) { if m.DeviceType != 0 { n += 1 + sovAuthservice(uint64(m.DeviceType)) } - if m.DeviceUsage != 0 { - n += 1 + sovAuthservice(uint64(m.DeviceUsage)) - } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -29276,30 +28854,6 @@ func (m *PaginatedResource_KubeService) Size() (n int) { } return n } -func (m *PaginatedResource_WindowsDesktop) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.WindowsDesktop != nil { - l = m.WindowsDesktop.Size() - n += 1 + l + sovAuthservice(uint64(l)) - } - return n -} -func (m *PaginatedResource_KubeCluster) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.KubeCluster != nil { - l = m.KubeCluster.Size() - n += 1 + l + sovAuthservice(uint64(l)) - } - return n -} func (m *ListResourcesRequest) Size() (n int) { if m == nil { return 0 @@ -29344,8 +28898,6 @@ func (m *ListResourcesRequest) Size() (n int) { if m.NeedTotalCount { n += 2 } - l = m.WindowsDesktopFilter.Size() - n += 1 + l + sovAuthservice(uint64(l)) if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -40406,25 +39958,6 @@ func (m *AddMFADeviceRequestInit) Unmarshal(dAtA []byte) error { break } } - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DeviceUsage", wireType) - } - m.DeviceUsage = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthservice - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.DeviceUsage |= DeviceUsage(b&0x7F) << shift - if b < 0x80 { - break - } - } default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) @@ -45827,108 +45360,6 @@ func (m *UserCredentials) Unmarshal(dAtA []byte) error { } return nil } -func (m *ContextUser) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthservice - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ContextUser: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ContextUser: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipAuthservice(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthAuthservice - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *Passwordless) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthservice - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Passwordless: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Passwordless: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipAuthservice(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthAuthservice - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *CreateAuthenticateChallengeRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -46025,76 +45456,6 @@ func (m *CreateAuthenticateChallengeRequest) Unmarshal(dAtA []byte) error { } m.Request = &CreateAuthenticateChallengeRequest_RecoveryStartTokenID{string(dAtA[iNdEx:postIndex])} iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ContextUser", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthservice - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthAuthservice - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthAuthservice - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - v := &ContextUser{} - if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - m.Request = &CreateAuthenticateChallengeRequest_ContextUser{v} - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Passwordless", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthservice - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthAuthservice - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthAuthservice - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - v := &Passwordless{} - if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - m.Request = &CreateAuthenticateChallengeRequest_Passwordless{v} - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) @@ -46284,25 +45645,6 @@ func (m *CreateRegisterChallengeRequest) Unmarshal(dAtA []byte) error { break } } - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DeviceUsage", wireType) - } - m.DeviceUsage = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthservice - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.DeviceUsage |= DeviceUsage(b&0x7F) << shift - if b < 0x80 { - break - } - } default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) @@ -46494,76 +45836,6 @@ func (m *PaginatedResource) Unmarshal(dAtA []byte) error { } m.Resource = &PaginatedResource_KubeService{v} iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field WindowsDesktop", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthservice - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthAuthservice - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthAuthservice - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - v := &types.WindowsDesktopV3{} - if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - m.Resource = &PaginatedResource_WindowsDesktop{v} - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field KubeCluster", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthservice - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthAuthservice - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthAuthservice - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - v := &types.KubernetesClusterV3{} - if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - m.Resource = &PaginatedResource_KubeCluster{v} - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) @@ -46974,39 +46246,6 @@ func (m *ListResourcesRequest) Unmarshal(dAtA []byte) error { } } m.NeedTotalCount = bool(v != 0) - case 10: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field WindowsDesktopFilter", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthservice - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthAuthservice - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthAuthservice - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.WindowsDesktopFilter.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) diff --git a/api/client/proto/authservice.proto b/api/client/proto/authservice.proto index 1b08662e5ab2c..e7631dfd1aa95 100644 --- a/api/client/proto/authservice.proto +++ b/api/client/proto/authservice.proto @@ -818,19 +818,6 @@ enum DeviceType { DEVICE_TYPE_WEBAUTHN = 3; } -enum DeviceUsage { - DEVICE_USAGE_UNSPECIFIED = 0; - - // Device intended for MFA use, but not for passwordless. - // Allows both FIDO and FIDO2 devices. - // Resident keys not required. - DEVICE_USAGE_MFA = 1; - - // Device intended for both MFA and passwordless. - // Requires a FIDO2 device and takes a resident key slot. - DEVICE_USAGE_PASSWORDLESS = 2; -} - // MFAAuthenticateChallenge is a challenge for all MFA devices registered for a // user. message MFAAuthenticateChallenge { @@ -941,11 +928,7 @@ message AddMFADeviceResponse { // AddMFADeviceRequestInit describes the new MFA device. message AddMFADeviceRequestInit { string DeviceName = 1; - reserved 2; // LegacyDeviceType LegacyType DeviceType DeviceType = 3; - // DeviceUsage is the requested usage for the device. - // Defaults to DEVICE_USAGE_MFA. - DeviceUsage DeviceUsage = 4 [ (gogoproto.jsontag) = "device_usage,omitempty" ]; } // AddMFADeviceResponseAck is a confirmation of successful device registration. @@ -1391,17 +1374,12 @@ message UserCredentials { bytes Password = 2 [ (gogoproto.jsontag) = "password" ]; } -// ContextUser marks requests that rely in the currently authenticated user. -message ContextUser {} - -// Passwordless marks requests for passwordless challenges. -message Passwordless {} - // CreateAuthenticateChallengeRequest is a request for creating MFA authentication challenges for a // users mfa devices. message CreateAuthenticateChallengeRequest { // Request defines how the request will be verified before creating challenges. - // An empty Request is equivalent to context_user being set. + // This field can be empty, which implies the request is to create challenges for the + // user in context (logged in user). oneof Request { // UserCredentials verifies request with username and password. Used with logins or // when the logged in user wants to change their password. @@ -1411,12 +1389,6 @@ message CreateAuthenticateChallengeRequest { // VerifyAccountRecovery (step 2 of the recovery process after RPC StartAccountRecovery). string RecoveryStartTokenID = 2 [ (gogoproto.jsontag) = "recovery_start_token_id,omitempty" ]; - // ContextUser issues a challenge for the currently-authenticated user. - // Default option if no other is provided. - ContextUser ContextUser = 3 [ (gogoproto.jsontag) = "context_user,omitempty" ]; - // Passwordless issues a passwordless challenge (authenticated user not - // required). - Passwordless Passwordless = 4 [ (gogoproto.jsontag) = "passwordless,omitempty" ]; } } @@ -1440,9 +1412,6 @@ message CreateRegisterChallengeRequest { string TokenID = 1 [ (gogoproto.jsontag) = "token_id" ]; // DeviceType is the type of MFA device to make a register challenge for. DeviceType DeviceType = 2 [ (gogoproto.jsontag) = "device_type" ]; - // DeviceUsage is the requested usage for the device. - // Defaults to DEVICE_USAGE_MFA. - DeviceUsage DeviceUsage = 3 [ (gogoproto.jsontag) = "device_usage,omitempty" ]; } // PaginatedResource represents one of the supported resources. @@ -1457,12 +1426,6 @@ message PaginatedResource { types.ServerV2 Node = 3 [ (gogoproto.jsontag) = "node,omitempty" ]; // KubeService represents a KubernetesService resource. types.ServerV2 KubeService = 4 [ (gogoproto.jsontag) = "kube_service,omitempty" ]; - // WindowsDesktop represents a WindowsDesktop resource. - types.WindowsDesktopV3 WindowsDesktop = 5 - [ (gogoproto.jsontag) = "windows_desktop,omitempty" ]; - // KubeCluster represents a KubeCluster resource. - types.KubernetesClusterV3 KubeCluster = 6 - [ (gogoproto.jsontag) = "kube_cluster,omitempty" ]; } } @@ -1491,9 +1454,6 @@ message ListResourcesRequest { // NeedTotalCount indicates whether or not the caller also wants the total number of resources // after filtering. bool NeedTotalCount = 9 [ (gogoproto.jsontag) = "need_total_count,omitempty" ]; - // WindowsDesktopFilter specifies windows desktop specific filters. - types.WindowsDesktopFilter WindowsDesktopFilter = 10 - [ (gogoproto.nullable) = false, (gogoproto.jsontag) = "windows_desktop_filter,omitempty" ]; } // ListResourceResponse response of ListResources. diff --git a/api/client/proto/types.go b/api/client/proto/types.go index 9e1b789474628..ece8f561c6ba0 100644 --- a/api/client/proto/types.go +++ b/api/client/proto/types.go @@ -88,5 +88,5 @@ func (req *ListResourcesRequest) CheckAndSetDefaults() error { // RequiresFakePagination checks if we need to fallback to GetXXX calls // that retrieves entire resources upfront rather than working with subsets. func (req *ListResourcesRequest) RequiresFakePagination() bool { - return req.SortBy.Field != "" || req.NeedTotalCount || req.ResourceType == types.KindKubernetesCluster + return req.SortBy.Field != "" || req.NeedTotalCount } diff --git a/api/client/proxy.go b/api/client/proxy.go index 7e4f80c59a09e..e128bb1b275a0 100644 --- a/api/client/proxy.go +++ b/api/client/proxy.go @@ -46,8 +46,8 @@ func DialProxyWithDialer(ctx context.Context, proxyAddr *url.URL, addr string, d Host: addr, Header: make(http.Header), } - - if err := connectReq.Write(conn); err != nil { + err = connectReq.Write(conn) + if err != nil { log.Warnf("Unable to write to proxy: %v.", err) return nil, trace.Wrap(err) } diff --git a/api/client/proxy/proxy.go b/api/client/proxy/proxy.go index c896fb7ec0711..7477d6b897b21 100644 --- a/api/client/proxy/proxy.go +++ b/api/client/proxy/proxy.go @@ -1,19 +1,3 @@ -/* -Copyright 2022 Gravitational, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - package proxy import ( diff --git a/api/client/webclient/webclient.go b/api/client/webclient/webclient.go index f65877cc161d9..e0f5e1b0d8f77 100644 --- a/api/client/webclient/webclient.go +++ b/api/client/webclient/webclient.go @@ -40,54 +40,19 @@ import ( log "github.com/sirupsen/logrus" ) -// Config specifies information when building requests with the -// webclient. -type Config struct { - // Context is a context for creating webclient requests. - Context context.Context - // ProxyAddr specifies the teleport proxy address for requests. - ProxyAddr string - // Insecure turns off TLS certificate verification when enabled. - Insecure bool - // Pool defines the set of root CAs to use when verifying server - // certificates. - Pool *x509.CertPool - // ConnectorName is the name of the ODIC or SAML connector. - ConnectorName string - // ExtraHeaders is a map of extra HTTP headers to be included in - // requests. - ExtraHeaders map[string]string -} - -// CheckAndSetDefaults checks and sets defaults -func (c *Config) CheckAndSetDefaults() error { - message := "webclient config: %s" - if c.Context == nil { - return trace.BadParameter(message, "missing parameter Context") - } - if c.ProxyAddr == "" && os.Getenv(defaults.TunnelPublicAddrEnvar) == "" { - return trace.BadParameter(message, "missing parameter ProxyAddr") - } - - return nil -} - // newWebClient creates a new client to the HTTPS web proxy. -func newWebClient(cfg *Config) (*http.Client, error) { - if err := cfg.CheckAndSetDefaults(); err != nil { - return nil, trace.Wrap(err) - } +func newWebClient(insecure bool, pool *x509.CertPool) *http.Client { return &http.Client{ Transport: &proxy.ProxyAwareRoundTripper{Transport: http.Transport{ TLSClientConfig: &tls.Config{ - RootCAs: cfg.Pool, - InsecureSkipVerify: cfg.Insecure, + RootCAs: pool, + InsecureSkipVerify: insecure, }, Proxy: func(req *http.Request) (*url.URL, error) { return httpproxy.FromEnvironment().ProxyFunc()(req.URL) }, - }, - }}, nil + }}, + } } // doWithFallback attempts to execute an HTTP request using https, and then @@ -96,13 +61,9 @@ func newWebClient(cfg *Config) (*http.Client, error) { // * The target host must resolve to the loopback address. // If these conditions are not met, then the plain-HTTP fallback is not allowed, // and a the HTTPS failure will be considered final. -func doWithFallback(clt *http.Client, allowPlainHTTP bool, extraHeaders map[string]string, req *http.Request) (*http.Response, error) { +func doWithFallback(clt *http.Client, allowPlainHTTP bool, req *http.Request) (*http.Response, error) { // first try https and see how that goes req.URL.Scheme = "https" - for k, v := range extraHeaders { - req.Header.Add(k, v) - } - log.Debugf("Attempting %s %s%s", req.Method, req.URL.Host, req.URL.Path) resp, err := clt.Do(req) @@ -132,21 +93,18 @@ func doWithFallback(clt *http.Client, allowPlainHTTP bool, extraHeaders map[stri // Find fetches discovery data by connecting to the given web proxy address. // It is designed to fetch proxy public addresses without any inefficiencies. -func Find(cfg *Config) (*PingResponse, error) { - clt, err := newWebClient(cfg) - if err != nil { - return nil, trace.Wrap(err) - } +func Find(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (*PingResponse, error) { + clt := newWebClient(insecure, pool) defer clt.CloseIdleConnections() - endpoint := fmt.Sprintf("https://%s/webapi/find", cfg.ProxyAddr) + endpoint := fmt.Sprintf("https://%s/webapi/find", proxyAddr) - req, err := http.NewRequestWithContext(cfg.Context, http.MethodGet, endpoint, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, trace.Wrap(err) } - resp, err := doWithFallback(clt, cfg.Insecure, cfg.ExtraHeaders, req) + resp, err := doWithFallback(clt, insecure, req) if err != nil { return nil, trace.Wrap(err) } @@ -165,24 +123,21 @@ func Find(cfg *Config) (*PingResponse, error) { // errors before being asked for passwords. The second is to return the form // of authentication that the server supports. This also leads to better user // experience: users only get prompted for the type of authentication the server supports. -func Ping(cfg *Config) (*PingResponse, error) { - clt, err := newWebClient(cfg) - if err != nil { - return nil, trace.Wrap(err) - } +func Ping(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool, connectorName string) (*PingResponse, error) { + clt := newWebClient(insecure, pool) defer clt.CloseIdleConnections() - endpoint := fmt.Sprintf("https://%s/webapi/ping", cfg.ProxyAddr) - if cfg.ConnectorName != "" { - endpoint = fmt.Sprintf("%s/%s", endpoint, cfg.ConnectorName) + endpoint := fmt.Sprintf("https://%s/webapi/ping", proxyAddr) + if connectorName != "" { + endpoint = fmt.Sprintf("%s/%s", endpoint, connectorName) } - req, err := http.NewRequestWithContext(cfg.Context, http.MethodGet, endpoint, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, trace.Wrap(err) } - resp, err := doWithFallback(clt, cfg.Insecure, cfg.ExtraHeaders, req) + resp, err := doWithFallback(clt, insecure, req) if err != nil { return nil, trace.Wrap(err) } @@ -197,38 +152,32 @@ func Ping(cfg *Config) (*PingResponse, error) { } // GetTunnelAddr returns the tunnel address either set in an environment variable or retrieved from the web proxy. -func GetTunnelAddr(cfg *Config) (string, error) { - if err := cfg.CheckAndSetDefaults(); err != nil { - return "", trace.Wrap(err) - } +func GetTunnelAddr(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (string, error) { // If TELEPORT_TUNNEL_PUBLIC_ADDR is set, nothing else has to be done, return it. if tunnelAddr := os.Getenv(defaults.TunnelPublicAddrEnvar); tunnelAddr != "" { return extractHostPort(tunnelAddr) } // Ping web proxy to retrieve tunnel proxy address. - pr, err := Find(cfg) + pr, err := Find(ctx, proxyAddr, insecure, nil) if err != nil { return "", trace.Wrap(err) } - return tunnelAddr(cfg.ProxyAddr, pr.Proxy) + return tunnelAddr(proxyAddr, pr.Proxy) } -func GetMOTD(cfg *Config) (*MotD, error) { - clt, err := newWebClient(cfg) - if err != nil { - return nil, trace.Wrap(err) - } +func GetMOTD(ctx context.Context, proxyAddr string, insecure bool, pool *x509.CertPool) (*MotD, error) { + clt := newWebClient(insecure, pool) defer clt.CloseIdleConnections() - endpoint := fmt.Sprintf("https://%s/webapi/motd", cfg.ProxyAddr) + endpoint := fmt.Sprintf("https://%s/webapi/motd", proxyAddr) - req, err := http.NewRequestWithContext(cfg.Context, http.MethodGet, endpoint, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, trace.Wrap(err) } - resp, err := doWithFallback(clt, cfg.Insecure, cfg.ExtraHeaders, req) + resp, err := clt.Do(req) if err != nil { return nil, trace.Wrap(err) } diff --git a/api/client/webclient/webclient_test.go b/api/client/webclient/webclient_test.go index b908a9777a718..963b6b5f2156c 100644 --- a/api/client/webclient/webclient_test.go +++ b/api/client/webclient/webclient_test.go @@ -52,15 +52,14 @@ func TestPlainHttpFallback(t *testing.T) { desc: "Ping", handler: newPingHandler("/webapi/ping"), actionUnderTest: func(addr string, insecure bool) error { - _, err := Ping( - &Config{Context: context.Background(), ProxyAddr: addr, Insecure: insecure}) + _, err := Ping(context.Background(), addr, insecure, nil /*pool*/, "") return err }, }, { desc: "Find", handler: newPingHandler("/webapi/find"), actionUnderTest: func(addr string, insecure bool) error { - _, err := Find(&Config{Context: context.Background(), ProxyAddr: addr, Insecure: insecure}) + _, err := Find(context.Background(), addr, insecure, nil /*pool*/) return err }, }, @@ -105,7 +104,7 @@ func TestPlainHttpFallback(t *testing.T) { func TestGetTunnelAddr(t *testing.T) { t.Setenv(defaults.TunnelPublicAddrEnvar, "tunnel.example.com:4024") - tunnelAddr, err := GetTunnelAddr(&Config{Context: context.Background(), ProxyAddr: "", Insecure: false}) + tunnelAddr, err := GetTunnelAddr(context.Background(), "", true, nil) require.NoError(t, err) require.Equal(t, "tunnel.example.com:4024", tunnelAddr) } @@ -293,33 +292,22 @@ func TestExtract(t *testing.T) { func TestNewWebClientRespectHTTPProxy(t *testing.T) { t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") - client, err := newWebClient(&Config{ - Context: context.Background(), - ProxyAddr: "localhost:3080", - }) - require.NoError(t, err) + client := newWebClient(false /* insecure */, nil /* pool */) // resp should be nil, so there will be no body to close. //nolint:bodyclose - resp, err := client.Get("https://fakedomain.example.com") + resp, err := client.Get("https://example.com") // Client should try to proxy through nonexistent server at localhost. require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) require.Contains(t, err.Error(), "proxyconnect") - require.Contains(t, err.Error(), "lookup fakeproxy.example.com") require.Contains(t, err.Error(), "no such host") } func TestNewWebClientNoProxy(t *testing.T) { t.Setenv("HTTPS_PROXY", "fakeproxy.example.com:9999") - t.Setenv("NO_PROXY", "fakedomain.example.com") - client, err := newWebClient(&Config{ - Context: context.Background(), - ProxyAddr: "localhost:3080", - }) + t.Setenv("NO_PROXY", "example.com") + client := newWebClient(false /* insecure */, nil /* pool */) + resp, err := client.Get("https://example.com") require.NoError(t, err) - //nolint:bodyclose - resp, err := client.Get("https://fakedomain.example.com") - require.Error(t, err, "GET unexpectedly succeeded: %+v", resp) - require.NotContains(t, err.Error(), "proxyconnect") - require.Contains(t, err.Error(), "lookup fakedomain.example.com") - require.Contains(t, err.Error(), "no such host") + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) } diff --git a/api/go.mod b/api/go.mod index 32f30b53bd167..3a7210c771f25 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,7 +3,7 @@ module github.com/gravitational/teleport/api go 1.15 require ( - github.com/gogo/protobuf v1.3.2 + github.com/gogo/protobuf v1.3.1 github.com/golang/protobuf v1.4.3 github.com/google/go-cmp v0.5.4 github.com/gravitational/trace v1.1.17 diff --git a/api/go.sum b/api/go.sum index e0d13feb2fd5c..72c1f18217730 100644 --- a/api/go.sum +++ b/api/go.sum @@ -21,8 +21,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -50,7 +50,7 @@ github.com/gravitational/trace v1.1.17/go.mod h1:n0ijrq6psJY0sOI/NzLp+xdd8xl79jj github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -68,11 +68,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8 h1:kACShD3qhmr/3rLmg1yXyt+N4HcwutKyPRB93s54TIU= golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -80,18 +77,13 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -101,14 +93,11 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -119,20 +108,15 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/api/identityfile/identityfile.go b/api/identityfile/identityfile.go index 9f19ce811a91d..2f45f8cc6a749 100644 --- a/api/identityfile/identityfile.go +++ b/api/identityfile/identityfile.go @@ -24,6 +24,7 @@ import ( "crypto/x509" "fmt" "io" + "io/ioutil" "os" "strings" @@ -101,7 +102,7 @@ func Write(idFile *IdentityFile, path string) error { if err := encodeIdentityFile(buf, idFile); err != nil { return trace.Wrap(err) } - if err := os.WriteFile(path, buf.Bytes(), FilePermissions); err != nil { + if err := ioutil.WriteFile(path, buf.Bytes(), FilePermissions); err != nil { return trace.ConvertSystemError(err) } return nil @@ -138,7 +139,7 @@ func ReadFile(path string) (*IdentityFile, error) { // separate file with -cert.pub suffix. if len(ident.Certs.SSH) == 0 { certFn := keypaths.IdentitySSHCertPath(path) - if ident.Certs.SSH, err = os.ReadFile(certFn); err != nil { + if ident.Certs.SSH, err = ioutil.ReadFile(certFn); err != nil { return nil, trace.Wrap(err, "could not find SSH cert in the identity file or %v", certFn) } } diff --git a/api/profile/profile.go b/api/profile/profile.go index 7b9b701d63c8b..ba63ce65d6517 100644 --- a/api/profile/profile.go +++ b/api/profile/profile.go @@ -21,6 +21,7 @@ import ( "crypto/tls" "crypto/x509" "io/fs" + "io/ioutil" "net" "os" "os/user" @@ -117,7 +118,7 @@ func (p *Profile) TLSConfig() (*tls.Config, error) { if info.IsDir() { return nil } - cert, err := os.ReadFile(path) + cert, err := ioutil.ReadFile(path) if err != nil { return trace.ConvertSystemError(err) } @@ -138,17 +139,23 @@ func (p *Profile) TLSConfig() (*tls.Config, error) { // SSHClientConfig returns the profile's associated SSHClientConfig. func (p *Profile) SSHClientConfig() (*ssh.ClientConfig, error) { - cert, err := os.ReadFile(p.SSHCertPath()) + cert, err := ioutil.ReadFile(p.SSHCertPath()) if err != nil { - return nil, trace.Wrap(err) + // Try reading SSHCert from old cert path, return original error otherwise + // DELETE IN 8.0.0 + var err2 error + cert, err2 = ioutil.ReadFile(p.OldSSHCertPath()) + if err2 != nil { + return nil, trace.Wrap(err) + } } - key, err := os.ReadFile(p.UserKeyPath()) + key, err := ioutil.ReadFile(p.UserKeyPath()) if err != nil { return nil, trace.Wrap(err) } - caCerts, err := os.ReadFile(p.KnownHostsPath()) + caCerts, err := ioutil.ReadFile(p.KnownHostsPath()) if err != nil { return nil, trace.Wrap(err) } @@ -167,7 +174,7 @@ func SetCurrentProfileName(dir string, name string) error { } path := filepath.Join(dir, currentProfileFilename) - if err := os.WriteFile(path, []byte(strings.TrimSpace(name)+"\n"), 0660); err != nil { + if err := ioutil.WriteFile(path, []byte(strings.TrimSpace(name)+"\n"), 0660); err != nil { return trace.Wrap(err) } return nil @@ -179,7 +186,7 @@ func GetCurrentProfileName(dir string) (name string, err error) { return "", trace.BadParameter("cannot get current profile: missing dir") } - data, err := os.ReadFile(filepath.Join(dir, currentProfileFilename)) + data, err := ioutil.ReadFile(filepath.Join(dir, currentProfileFilename)) if err != nil { if os.IsNotExist(err) { return "", trace.NotFound("current-profile is not set") @@ -198,7 +205,7 @@ func ListProfileNames(dir string) ([]string, error) { if dir == "" { return nil, trace.BadParameter("cannot list profiles: missing dir") } - files, err := os.ReadDir(dir) + files, err := ioutil.ReadDir(dir) if err != nil { return nil, trace.Wrap(err) } @@ -208,8 +215,7 @@ func ListProfileNames(dir string) ([]string, error) { if file.IsDir() { continue } - - if file.Type()&os.ModeSymlink != 0 { + if file.Mode()&os.ModeSymlink != 0 { continue } if !strings.HasSuffix(file.Name(), ".yaml") { @@ -260,7 +266,7 @@ func FromDir(dir string, name string) (*Profile, error) { // profileFromFile loads the profile from a YAML file. func profileFromFile(filePath string) (*Profile, error) { - bytes, err := os.ReadFile(filePath) + bytes, err := ioutil.ReadFile(filePath) if err != nil { return nil, trace.ConvertSystemError(err) } @@ -300,7 +306,7 @@ func (p *Profile) saveToFile(filepath string) error { if err != nil { return trace.Wrap(err) } - if err = os.WriteFile(filepath, bytes, 0660); err != nil { + if err = ioutil.WriteFile(filepath, bytes, 0660); err != nil { return trace.Wrap(err) } return nil @@ -346,6 +352,12 @@ func (p *Profile) SSHCertPath() string { return keypaths.SSHCertPath(p.Dir, p.Name(), p.Username, p.SiteName) } +// OldSSHCertPath returns the old (before v6.1) path to the profile's ssh certificate. +// DELETE IN 8.0.0 +func (p *Profile) OldSSHCertPath() string { + return keypaths.OldSSHCertPath(p.Dir, p.Name(), p.Username) +} + // KnownHostsPath returns the path to the profile's ssh certificate authorities. func (p *Profile) KnownHostsPath() string { return keypaths.KnownHostsPath(p.Dir) diff --git a/api/types/app_test.go b/api/types/app_test.go index 9b5b12d23c806..731edb7ffa1a2 100644 --- a/api/types/app_test.go +++ b/api/types/app_test.go @@ -141,6 +141,8 @@ func TestAppServerSorter(t *testing.T) { for _, c := range cases { c := c t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) { + t.Parallel() + sortBy := SortBy{Field: c.fieldName, IsDesc: true} servers := AppServers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) @@ -150,6 +152,8 @@ func TestAppServerSorter(t *testing.T) { }) t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) { + t.Parallel() + sortBy := SortBy{Field: c.fieldName} servers := AppServers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) diff --git a/api/types/appserver.go b/api/types/appserver.go index aa5a96b43d2d4..10fb2bdea62db 100644 --- a/api/types/appserver.go +++ b/api/types/appserver.go @@ -304,14 +304,7 @@ func (s AppServers) Len() int { return len(s) } // Less compares app servers by name and host ID. func (s AppServers) Less(i, j int) bool { - switch { - case s[i].GetName() < s[j].GetName(): - return true - case s[i].GetName() > s[j].GetName(): - return false - default: - return s[i].GetHostID() < s[j].GetHostID() - } + return s[i].GetName() < s[j].GetName() && s[i].GetHostID() < s[j].GetHostID() } // Swap swaps two app servers. diff --git a/api/types/constants.go b/api/types/constants.go index 52a4d64210e98..54f00730c9906 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -297,13 +297,9 @@ const ( ) const ( - // TeleportNamespace is used as the namespace prefix for any - // labels defined by teleport - TeleportNamespace = "teleport.dev" - // OriginLabel is a resource metadata label name used to identify a source // that the resource originates from. - OriginLabel = TeleportNamespace + "/origin" + OriginLabel = "teleport.dev/origin" // OriginConfigFile is an origin value indicating that the resource was // constructed as a default value. diff --git a/api/types/database.go b/api/types/database.go index 39b94b4b96a6f..7e752b0412d40 100644 --- a/api/types/database.go +++ b/api/types/database.go @@ -563,6 +563,16 @@ func DeduplicateDatabases(databases []Database) (result []Database) { // Databases is a list of database resources. type Databases []Database +// Find returns database with the specified name or nil. +func (d Databases) Find(name string) Database { + for _, database := range d { + if database.GetName() == name { + return database + } + } + return nil +} + // ToMap returns these databases as a map keyed by database name. func (d Databases) ToMap() map[string]Database { m := make(map[string]Database) diff --git a/api/types/databaseserver.go b/api/types/databaseserver.go index db2637513a3cc..4a4e6cfb0e6a4 100644 --- a/api/types/databaseserver.go +++ b/api/types/databaseserver.go @@ -291,14 +291,7 @@ func (s DatabaseServers) Len() int { return len(s) } // Less compares database servers by name and host ID. func (s DatabaseServers) Less(i, j int) bool { - switch { - case s[i].GetName() < s[j].GetName(): - return true - case s[i].GetName() > s[j].GetName(): - return false - default: - return s[i].GetHostID() < s[j].GetHostID() - } + return s[i].GetName() < s[j].GetName() && s[i].GetHostID() < s[j].GetHostID() } // Swap swaps two database servers. diff --git a/api/types/databaseserver_test.go b/api/types/databaseserver_test.go index de45c5c788df8..babd27ac9a0d7 100644 --- a/api/types/databaseserver_test.go +++ b/api/types/databaseserver_test.go @@ -154,6 +154,8 @@ func TestDatabaseServerSorter(t *testing.T) { for _, c := range cases { c := c t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) { + t.Parallel() + sortBy := SortBy{Field: c.fieldName, IsDesc: true} servers := DatabaseServers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) @@ -163,6 +165,8 @@ func TestDatabaseServerSorter(t *testing.T) { }) t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) { + t.Parallel() + sortBy := SortBy{Field: c.fieldName} servers := DatabaseServers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) diff --git a/api/types/desktop.go b/api/types/desktop.go index 6fdda0b05fa75..6d2199711401a 100644 --- a/api/types/desktop.go +++ b/api/types/desktop.go @@ -17,8 +17,6 @@ limitations under the License. package types import ( - "sort" - "github.com/gravitational/teleport/api/utils" "github.com/gravitational/trace" ) @@ -31,8 +29,6 @@ type WindowsDesktopService interface { GetAddr() string // GetVersion returns the teleport binary version of this service. GetTeleportVersion() string - // GetHostname returns the hostname of this service - GetHostname() string } var _ WindowsDesktopService = &WindowsDesktopServiceV3{} @@ -99,11 +95,6 @@ func (s *WindowsDesktopServiceV3) GetAllLabels() map[string]string { return s.Metadata.Labels } -// GetHostname returns the windows hostname of this service. -func (s *WindowsDesktopServiceV3) GetHostname() string { - return s.Spec.Hostname -} - // MatchSearch goes through select field values and tries to // match against the list of search values. func (s *WindowsDesktopServiceV3) MatchSearch(values []string) bool { @@ -227,91 +218,3 @@ func (f *WindowsDesktopFilter) Match(req WindowsDesktop) bool { } return true } - -// WindowsDesktops represents a list of windows desktops. -type WindowsDesktops []WindowsDesktop - -// Len returns the slice length. -func (s WindowsDesktops) Len() int { return len(s) } - -// Less compares desktops by name and host ID. -func (s WindowsDesktops) Less(i, j int) bool { - switch { - case s[i].GetName() < s[j].GetName(): - return true - case s[i].GetName() > s[j].GetName(): - return false - default: - return s[i].GetHostID() < s[j].GetHostID() - } -} - -// Swap swaps two windows desktops. -func (s WindowsDesktops) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// SortByCustom custom sorts by given sort criteria. -func (s WindowsDesktops) SortByCustom(sortBy SortBy) error { - if sortBy.Field == "" { - return nil - } - - isDesc := sortBy.IsDesc - switch sortBy.Field { - case ResourceMetadataName: - sort.SliceStable(s, func(i, j int) bool { - return stringCompare(s[i].GetName(), s[j].GetName(), isDesc) - }) - case ResourceSpecAddr: - sort.SliceStable(s, func(i, j int) bool { - return stringCompare(s[i].GetAddr(), s[j].GetAddr(), isDesc) - }) - default: - return trace.NotImplemented("sorting by field %q for resource %q is not supported", sortBy.Field, KindWindowsDesktop) - } - - return nil -} - -// AsResources returns windows desktops as type resources with labels. -func (s WindowsDesktops) AsResources() []ResourceWithLabels { - resources := make([]ResourceWithLabels, 0, len(s)) - for _, server := range s { - resources = append(resources, ResourceWithLabels(server)) - } - return resources -} - -// GetFieldVals returns list of select field values. -func (s WindowsDesktops) GetFieldVals(field string) ([]string, error) { - vals := make([]string, 0, len(s)) - switch field { - case ResourceMetadataName: - for _, server := range s { - vals = append(vals, server.GetName()) - } - case ResourceSpecAddr: - for _, server := range s { - vals = append(vals, server.GetAddr()) - } - default: - return nil, trace.NotImplemented("getting field %q for resource %q is not supported", field, KindWindowsDesktop) - } - - return vals, nil -} - -// ListWindowsDesktopsResponse is a response type to ListWindowsDesktops. -type ListWindowsDesktopsResponse struct { - Desktops []WindowsDesktop - NextKey string -} - -// ListWindowsDesktopsRequest is a request type to ListWindowsDesktops. -type ListWindowsDesktopsRequest struct { - WindowsDesktopFilter - Limit int - StartKey, PredicateExpression string - Labels map[string]string - SearchKeywords []string - SortBy SortBy -} diff --git a/api/types/desktop_test.go b/api/types/desktop_test.go deleted file mode 100644 index f17c480bd0685..0000000000000 --- a/api/types/desktop_test.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -Copyright 2022 Gravitational, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package types - -import ( - "fmt" - "testing" - - "github.com/gravitational/trace" - "github.com/stretchr/testify/require" -) - -func TestWindowsDesktopsSorter(t *testing.T) { - t.Parallel() - - testValsUnordered := []string{"d", "b", "a", "c"} - - makeDesktops := func(testVals []string, testField string) []WindowsDesktop { - desktops := make([]WindowsDesktop, len(testVals)) - for i := 0; i < len(testVals); i++ { - testVal := testVals[i] - var err error - desktops[i], err = NewWindowsDesktopV3( - getTestVal(testField == ResourceMetadataName, testVal), - nil, - WindowsDesktopSpecV3{ - Addr: getTestVal(testField == ResourceSpecAddr, testVal), - }) - - require.NoError(t, err) - } - return desktops - } - - cases := []struct { - name string - fieldName string - }{ - { - name: "by name", - fieldName: ResourceMetadataName, - }, - { - name: "by addr", - fieldName: ResourceSpecAddr, - }, - } - - for _, c := range cases { - c := c - t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) { - sortBy := SortBy{Field: c.fieldName, IsDesc: true} - servers := WindowsDesktops(makeDesktops(testValsUnordered, c.fieldName)) - require.NoError(t, servers.SortByCustom(sortBy)) - targetVals, err := servers.GetFieldVals(c.fieldName) - require.NoError(t, err) - require.IsDecreasing(t, targetVals) - }) - - t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) { - sortBy := SortBy{Field: c.fieldName} - servers := WindowsDesktops(makeDesktops(testValsUnordered, c.fieldName)) - require.NoError(t, servers.SortByCustom(sortBy)) - targetVals, err := servers.GetFieldVals(c.fieldName) - require.NoError(t, err) - require.IsIncreasing(t, targetVals) - }) - } - - // Test error. - sortBy := SortBy{Field: "unsupported"} - desktops := makeDesktops(testValsUnordered, "does-not-matter") - require.True(t, trace.IsNotImplemented(WindowsDesktops(desktops).SortByCustom(sortBy))) -} diff --git a/api/types/events/events.pb.go b/api/types/events/events.pb.go index 64cd82b30c814..423a29e72972f 100644 --- a/api/types/events/events.pb.go +++ b/api/types/events/events.pb.go @@ -3673,12 +3673,10 @@ type WindowsDesktopSessionStart struct { // WindowsUser is the Windows username used to connect. WindowsUser string `protobuf:"bytes,9,opt,name=WindowsUser,proto3" json:"windows_user"` // DesktopLabels are the labels on the desktop resource. - DesktopLabels map[string]string `protobuf:"bytes,10,rep,name=DesktopLabels,proto3" json:"desktop_labels" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - // DesktopName is the name of the desktop resource. - DesktopName string `protobuf:"bytes,11,opt,name=DesktopName,proto3" json:"desktop_name"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DesktopLabels map[string]string `protobuf:"bytes,10,rep,name=DesktopLabels,proto3" json:"desktop_labels" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *WindowsDesktopSessionStart) Reset() { *m = WindowsDesktopSessionStart{} } @@ -4378,54 +4376,6 @@ func (m *RenewableCertificateGenerationMismatch) XXX_DiscardUnknown() { var xxx_messageInfo_RenewableCertificateGenerationMismatch proto.InternalMessageInfo -// Unknown is a fallback event used when we don't recognize an event from the backend. -type Unknown struct { - // Metadata is a common event metadata. - Metadata `protobuf:"bytes,1,opt,name=Metadata,proto3,embedded=Metadata" json:""` - // UnknownType is the event type extracted from the unknown event. - UnknownType string `protobuf:"bytes,2,opt,name=UnknownType,proto3" json:"unknown_event"` - // UnknownCode is the event code extracted from the unknown event. - UnknownCode string `protobuf:"bytes,3,opt,name=UnknownCode,proto3" json:"unknown_code,omitempty"` - // Data is the serialized JSON data of the unknown event. - Data string `protobuf:"bytes,4,opt,name=Data,proto3" json:"data"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Unknown) Reset() { *m = Unknown{} } -func (m *Unknown) String() string { return proto.CompactTextString(m) } -func (*Unknown) ProtoMessage() {} -func (*Unknown) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{85} -} -func (m *Unknown) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *Unknown) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_Unknown.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *Unknown) XXX_Merge(src proto.Message) { - xxx_messageInfo_Unknown.Merge(m, src) -} -func (m *Unknown) XXX_Size() int { - return m.Size() -} -func (m *Unknown) XXX_DiscardUnknown() { - xxx_messageInfo_Unknown.DiscardUnknown(m) -} - -var xxx_messageInfo_Unknown proto.InternalMessageInfo - // OneOf is a union of one of audit events submitted to the auth service type OneOf struct { // Event is one of the audit events @@ -4510,7 +4460,6 @@ type OneOf struct { // *OneOf_MySQLStatementFetch // *OneOf_MySQLStatementBulkExecute // *OneOf_RenewableCertificateGenerationMismatch - // *OneOf_Unknown Event isOneOf_Event `protobuf_oneof:"Event"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -4521,7 +4470,7 @@ func (m *OneOf) Reset() { *m = OneOf{} } func (m *OneOf) String() string { return proto.CompactTextString(m) } func (*OneOf) ProtoMessage() {} func (*OneOf) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{86} + return fileDescriptor_8f22242cb04491f9, []int{85} } func (m *OneOf) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4793,9 +4742,6 @@ type OneOf_MySQLStatementBulkExecute struct { type OneOf_RenewableCertificateGenerationMismatch struct { RenewableCertificateGenerationMismatch *RenewableCertificateGenerationMismatch `protobuf:"bytes,79,opt,name=RenewableCertificateGenerationMismatch,proto3,oneof" json:"RenewableCertificateGenerationMismatch,omitempty"` } -type OneOf_Unknown struct { - Unknown *Unknown `protobuf:"bytes,80,opt,name=Unknown,proto3,oneof" json:"Unknown,omitempty"` -} func (*OneOf_UserLogin) isOneOf_Event() {} func (*OneOf_UserCreate) isOneOf_Event() {} @@ -4876,7 +4822,6 @@ func (*OneOf_MySQLStatementReset) isOneOf_Event() {} func (*OneOf_MySQLStatementFetch) isOneOf_Event() {} func (*OneOf_MySQLStatementBulkExecute) isOneOf_Event() {} func (*OneOf_RenewableCertificateGenerationMismatch) isOneOf_Event() {} -func (*OneOf_Unknown) isOneOf_Event() {} func (m *OneOf) GetEvent() isOneOf_Event { if m != nil { @@ -5438,13 +5383,6 @@ func (m *OneOf) GetRenewableCertificateGenerationMismatch() *RenewableCertificat return nil } -func (m *OneOf) GetUnknown() *Unknown { - if x, ok := m.GetEvent().(*OneOf_Unknown); ok { - return x.Unknown - } - return nil -} - // XXX_OneofWrappers is for the internal use of the proto package. func (*OneOf) XXX_OneofWrappers() []interface{} { return []interface{}{ @@ -5527,7 +5465,6 @@ func (*OneOf) XXX_OneofWrappers() []interface{} { (*OneOf_MySQLStatementFetch)(nil), (*OneOf_MySQLStatementBulkExecute)(nil), (*OneOf_RenewableCertificateGenerationMismatch)(nil), - (*OneOf_Unknown)(nil), } } @@ -5548,7 +5485,7 @@ func (m *StreamStatus) Reset() { *m = StreamStatus{} } func (m *StreamStatus) String() string { return proto.CompactTextString(m) } func (*StreamStatus) ProtoMessage() {} func (*StreamStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{87} + return fileDescriptor_8f22242cb04491f9, []int{86} } func (m *StreamStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5596,7 +5533,7 @@ func (m *SessionUpload) Reset() { *m = SessionUpload{} } func (m *SessionUpload) String() string { return proto.CompactTextString(m) } func (*SessionUpload) ProtoMessage() {} func (*SessionUpload) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{88} + return fileDescriptor_8f22242cb04491f9, []int{87} } func (m *SessionUpload) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5691,7 +5628,7 @@ func (m *Identity) Reset() { *m = Identity{} } func (m *Identity) String() string { return proto.CompactTextString(m) } func (*Identity) ProtoMessage() {} func (*Identity) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{89} + return fileDescriptor_8f22242cb04491f9, []int{88} } func (m *Identity) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5741,7 +5678,7 @@ func (m *RouteToApp) Reset() { *m = RouteToApp{} } func (m *RouteToApp) String() string { return proto.CompactTextString(m) } func (*RouteToApp) ProtoMessage() {} func (*RouteToApp) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{90} + return fileDescriptor_8f22242cb04491f9, []int{89} } func (m *RouteToApp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5789,7 +5726,7 @@ func (m *RouteToDatabase) Reset() { *m = RouteToDatabase{} } func (m *RouteToDatabase) String() string { return proto.CompactTextString(m) } func (*RouteToDatabase) ProtoMessage() {} func (*RouteToDatabase) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{91} + return fileDescriptor_8f22242cb04491f9, []int{90} } func (m *RouteToDatabase) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5840,7 +5777,7 @@ func (m *MySQLStatementPrepare) Reset() { *m = MySQLStatementPrepare{} } func (m *MySQLStatementPrepare) String() string { return proto.CompactTextString(m) } func (*MySQLStatementPrepare) ProtoMessage() {} func (*MySQLStatementPrepare) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{92} + return fileDescriptor_8f22242cb04491f9, []int{91} } func (m *MySQLStatementPrepare) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5893,7 +5830,7 @@ func (m *MySQLStatementExecute) Reset() { *m = MySQLStatementExecute{} } func (m *MySQLStatementExecute) String() string { return proto.CompactTextString(m) } func (*MySQLStatementExecute) ProtoMessage() {} func (*MySQLStatementExecute) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{93} + return fileDescriptor_8f22242cb04491f9, []int{92} } func (m *MySQLStatementExecute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5948,7 +5885,7 @@ func (m *MySQLStatementSendLongData) Reset() { *m = MySQLStatementSendLo func (m *MySQLStatementSendLongData) String() string { return proto.CompactTextString(m) } func (*MySQLStatementSendLongData) ProtoMessage() {} func (*MySQLStatementSendLongData) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{94} + return fileDescriptor_8f22242cb04491f9, []int{93} } func (m *MySQLStatementSendLongData) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5999,7 +5936,7 @@ func (m *MySQLStatementClose) Reset() { *m = MySQLStatementClose{} } func (m *MySQLStatementClose) String() string { return proto.CompactTextString(m) } func (*MySQLStatementClose) ProtoMessage() {} func (*MySQLStatementClose) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{95} + return fileDescriptor_8f22242cb04491f9, []int{94} } func (m *MySQLStatementClose) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6050,7 +5987,7 @@ func (m *MySQLStatementReset) Reset() { *m = MySQLStatementReset{} } func (m *MySQLStatementReset) String() string { return proto.CompactTextString(m) } func (*MySQLStatementReset) ProtoMessage() {} func (*MySQLStatementReset) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{96} + return fileDescriptor_8f22242cb04491f9, []int{95} } func (m *MySQLStatementReset) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6103,7 +6040,7 @@ func (m *MySQLStatementFetch) Reset() { *m = MySQLStatementFetch{} } func (m *MySQLStatementFetch) String() string { return proto.CompactTextString(m) } func (*MySQLStatementFetch) ProtoMessage() {} func (*MySQLStatementFetch) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{97} + return fileDescriptor_8f22242cb04491f9, []int{96} } func (m *MySQLStatementFetch) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6156,7 +6093,7 @@ func (m *MySQLStatementBulkExecute) Reset() { *m = MySQLStatementBulkExe func (m *MySQLStatementBulkExecute) String() string { return proto.CompactTextString(m) } func (*MySQLStatementBulkExecute) ProtoMessage() {} func (*MySQLStatementBulkExecute) Descriptor() ([]byte, []int) { - return fileDescriptor_8f22242cb04491f9, []int{98} + return fileDescriptor_8f22242cb04491f9, []int{97} } func (m *MySQLStatementBulkExecute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6278,7 +6215,6 @@ func init() { proto.RegisterMapType((map[string]string)(nil), "events.WindowsDesktopSessionEnd.DesktopLabelsEntry") proto.RegisterType((*CertificateCreate)(nil), "events.CertificateCreate") proto.RegisterType((*RenewableCertificateGenerationMismatch)(nil), "events.RenewableCertificateGenerationMismatch") - proto.RegisterType((*Unknown)(nil), "events.Unknown") proto.RegisterType((*OneOf)(nil), "events.OneOf") proto.RegisterType((*StreamStatus)(nil), "events.StreamStatus") proto.RegisterType((*SessionUpload)(nil), "events.SessionUpload") @@ -6297,444 +6233,438 @@ func init() { func init() { proto.RegisterFile("events.proto", fileDescriptor_8f22242cb04491f9) } var fileDescriptor_8f22242cb04491f9 = []byte{ - // 6991 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5d, 0x6c, 0x24, 0xd9, - 0x55, 0xb0, 0xbb, 0xdb, 0xfd, 0xe3, 0xdb, 0xfe, 0xbd, 0xe3, 0xd9, 0xa9, 0x99, 0x9d, 0x9d, 0x9e, - 0xad, 0x49, 0x66, 0x67, 0xb2, 0xb3, 0x76, 0xc6, 0x33, 0xbb, 0x93, 0x6c, 0x76, 0xb3, 0xd3, 0x6e, - 0xdb, 0xeb, 0xce, 0x7a, 0x6c, 0xef, 0xb5, 0x67, 0x37, 0x52, 0x94, 0x6d, 0x95, 0xbb, 0xee, 0xd8, - 0x95, 0xe9, 0xae, 0xaa, 0x54, 0x55, 0xdb, 0xe3, 0x7d, 0xfa, 0x7e, 0x10, 0x44, 0x51, 0x40, 0x28, - 0x3c, 0x20, 0xc4, 0x43, 0x50, 0x00, 0x09, 0xa4, 0x88, 0x00, 0x42, 0x11, 0x12, 0xbc, 0x40, 0x00, - 0x6d, 0x84, 0x12, 0x02, 0x41, 0x41, 0xe2, 0xa1, 0x03, 0x41, 0xbc, 0xb4, 0x40, 0x8a, 0x04, 0x12, - 0x81, 0x27, 0x74, 0xcf, 0xbd, 0x55, 0x75, 0xeb, 0xa7, 0x3d, 0xe3, 0xb1, 0x17, 0xe3, 0x19, 0x3f, - 0xd9, 0x7d, 0xfe, 0x6e, 0xd5, 0xb9, 0x7f, 0xe7, 0x9e, 0x7b, 0xce, 0x29, 0x34, 0x4c, 0xb7, 0xa9, - 0xe9, 0xb9, 0x53, 0xb6, 0x63, 0x79, 0x16, 0x2e, 0xf0, 0x5f, 0xe7, 0x26, 0x37, 0xad, 0x4d, 0x0b, - 0x40, 0xd3, 0xec, 0x3f, 0x8e, 0x3d, 0x57, 0xd9, 0xb4, 0xac, 0xcd, 0x16, 0x9d, 0x86, 0x5f, 0x1b, - 0x9d, 0x7b, 0xd3, 0x9e, 0xd1, 0xa6, 0xae, 0xa7, 0xb5, 0x6d, 0x41, 0x70, 0x3e, 0x4e, 0xe0, 0x7a, - 0x4e, 0xa7, 0xe9, 0x09, 0x6c, 0x6d, 0xd3, 0xf0, 0xb6, 0x3a, 0x1b, 0x53, 0x4d, 0xab, 0x3d, 0xbd, - 0xe9, 0x68, 0xdb, 0x86, 0xa7, 0x79, 0x86, 0x65, 0x6a, 0xad, 0x69, 0x8f, 0xb6, 0xa8, 0x6d, 0x39, - 0xde, 0xb4, 0x66, 0x1b, 0xd3, 0xde, 0xae, 0x4d, 0xdd, 0xe9, 0x1d, 0x47, 0xb3, 0x6d, 0xea, 0x84, - 0xff, 0x70, 0x21, 0xea, 0x57, 0xb3, 0xa8, 0x74, 0x87, 0x7a, 0x9a, 0xae, 0x79, 0x1a, 0x3e, 0x8f, - 0xf2, 0x75, 0x53, 0xa7, 0x0f, 0x94, 0xcc, 0xc5, 0xcc, 0x95, 0xdc, 0x6c, 0xa1, 0xd7, 0xad, 0x64, - 0xa9, 0x41, 0x38, 0x10, 0x3f, 0x87, 0x06, 0xd7, 0x77, 0x6d, 0xaa, 0x64, 0x2f, 0x66, 0xae, 0x0c, - 0xcd, 0x0e, 0xf5, 0xba, 0x95, 0x3c, 0xbc, 0x1e, 0x01, 0x30, 0x7e, 0x1e, 0x65, 0xeb, 0x73, 0x4a, - 0x0e, 0x90, 0x13, 0xbd, 0x6e, 0x65, 0xa4, 0x63, 0xe8, 0xd7, 0xac, 0xb6, 0xe1, 0xd1, 0xb6, 0xed, - 0xed, 0x92, 0x6c, 0x7d, 0x0e, 0x5f, 0x46, 0x83, 0x35, 0x4b, 0xa7, 0xca, 0x20, 0x10, 0xe1, 0x5e, - 0xb7, 0x32, 0xda, 0xb4, 0x74, 0x2a, 0x51, 0x01, 0x1e, 0xdf, 0x46, 0x83, 0xeb, 0x46, 0x9b, 0x2a, - 0xf9, 0x8b, 0x99, 0x2b, 0xe5, 0x99, 0x73, 0x53, 0x5c, 0x0d, 0x53, 0xbe, 0x1a, 0xa6, 0xd6, 0x7d, - 0x3d, 0xcd, 0x8e, 0x7f, 0xd0, 0xad, 0x0c, 0xf4, 0xba, 0x95, 0x41, 0xa6, 0xba, 0x5f, 0xfc, 0x51, - 0x25, 0x43, 0x80, 0x13, 0xbf, 0x86, 0xca, 0xb5, 0x56, 0xc7, 0xf5, 0xa8, 0xb3, 0xac, 0xb5, 0xa9, - 0x52, 0x80, 0x06, 0xcf, 0xf5, 0xba, 0x95, 0x67, 0x9a, 0x1c, 0xdc, 0x30, 0xb5, 0xb6, 0xdc, 0xb0, - 0x4c, 0xae, 0x7e, 0x01, 0x8d, 0xad, 0x51, 0xd7, 0x35, 0x2c, 0x33, 0x50, 0xcd, 0x47, 0xd1, 0x90, - 0x00, 0xd5, 0xe7, 0x40, 0x3d, 0x43, 0xb3, 0xc5, 0x5e, 0xb7, 0x92, 0x73, 0x0d, 0x9d, 0x84, 0x18, - 0xfc, 0x71, 0x54, 0x7c, 0xd7, 0xf0, 0xb6, 0xee, 0x2c, 0x54, 0x85, 0x9a, 0x9e, 0xe9, 0x75, 0x2b, - 0x78, 0xc7, 0xf0, 0xb6, 0x1a, 0xed, 0x7b, 0x9a, 0xd4, 0x9e, 0x4f, 0xa6, 0xfe, 0x46, 0x16, 0x0d, - 0xdf, 0x75, 0xa9, 0x13, 0xb4, 0x74, 0x19, 0x0d, 0xb2, 0xdf, 0xa2, 0x11, 0x50, 0x52, 0xc7, 0xa5, - 0x8e, 0xac, 0x24, 0x86, 0xc7, 0x57, 0x51, 0x7e, 0xc9, 0xda, 0x34, 0x4c, 0xd1, 0xd0, 0xa9, 0x5e, - 0xb7, 0x32, 0xd6, 0x62, 0x00, 0x89, 0x92, 0x53, 0xe0, 0x4f, 0xa3, 0xe1, 0x7a, 0x9b, 0x75, 0xba, - 0x65, 0x6a, 0x9e, 0xe5, 0x88, 0x4e, 0x02, 0x75, 0x18, 0x12, 0x5c, 0x62, 0x8c, 0xd0, 0xe3, 0x57, - 0x11, 0xaa, 0xbe, 0xbb, 0x46, 0xac, 0x16, 0xad, 0x92, 0x65, 0xd1, 0x7b, 0xc0, 0xad, 0xed, 0xb8, - 0x0d, 0xc7, 0x6a, 0xd1, 0x86, 0xe6, 0xc8, 0xcd, 0x4a, 0xd4, 0x78, 0x1e, 0x8d, 0x56, 0x9b, 0x4d, - 0xea, 0xba, 0x84, 0x7e, 0xb1, 0x43, 0x5d, 0xcf, 0x55, 0xf2, 0x17, 0x73, 0x57, 0x86, 0x66, 0x9f, - 0xeb, 0x75, 0x2b, 0x67, 0x35, 0xc0, 0x34, 0x1c, 0x81, 0x92, 0x44, 0xc4, 0x98, 0xd4, 0xdf, 0xcd, - 0xa1, 0xd1, 0x35, 0xea, 0x6c, 0x4b, 0x8a, 0xaa, 0xb2, 0x5e, 0x62, 0x10, 0xd6, 0x67, 0xae, 0xad, - 0x35, 0xa9, 0xd0, 0xd9, 0x99, 0x5e, 0xb7, 0x72, 0xca, 0xf4, 0x81, 0x92, 0xd0, 0x38, 0x3d, 0xbe, - 0x8a, 0x4a, 0x1c, 0x54, 0x9f, 0x13, 0x6a, 0x1c, 0xe9, 0x75, 0x2b, 0x43, 0x2e, 0xc0, 0x1a, 0x86, - 0x4e, 0x02, 0x34, 0x7b, 0x0f, 0xfe, 0xff, 0xa2, 0xe5, 0x7a, 0x4c, 0xb8, 0xd0, 0x22, 0xbc, 0x87, - 0x60, 0xd8, 0x12, 0x28, 0xf9, 0x3d, 0xa2, 0x4c, 0xf8, 0x93, 0x08, 0x71, 0x48, 0x55, 0xd7, 0x1d, - 0xa1, 0xca, 0xb3, 0xbd, 0x6e, 0xe5, 0xb4, 0x10, 0xa1, 0xe9, 0xba, 0xdc, 0x0f, 0x12, 0x31, 0x6e, - 0xa3, 0x61, 0xfe, 0x6b, 0x49, 0xdb, 0xa0, 0x2d, 0xae, 0xc7, 0xf2, 0xcc, 0x95, 0x29, 0xb1, 0xe2, - 0x44, 0xb5, 0x33, 0x25, 0x93, 0xce, 0x9b, 0x9e, 0xb3, 0x3b, 0x5b, 0x11, 0x73, 0xe5, 0x8c, 0x68, - 0xaa, 0x05, 0x38, 0xb9, 0xd3, 0x65, 0x9e, 0x73, 0x6f, 0xa0, 0x89, 0x84, 0x0c, 0x3c, 0x8e, 0x72, - 0xf7, 0xe9, 0x2e, 0xd7, 0x33, 0x61, 0xff, 0xe2, 0x49, 0x94, 0xdf, 0xd6, 0x5a, 0x1d, 0xb1, 0x2c, - 0x10, 0xfe, 0xe3, 0xd5, 0xec, 0x27, 0x32, 0xea, 0x1f, 0x66, 0x10, 0xae, 0x59, 0xa6, 0x49, 0x9b, - 0x9e, 0x3c, 0x93, 0x5e, 0x41, 0x43, 0x4b, 0x56, 0x53, 0x6b, 0x81, 0x02, 0x78, 0x87, 0x29, 0xbd, - 0x6e, 0x65, 0x92, 0xbd, 0xf9, 0x54, 0x8b, 0x61, 0xa4, 0x47, 0x0a, 0x49, 0x99, 0xe6, 0x08, 0x6d, - 0x5b, 0x1e, 0x05, 0xc6, 0x6c, 0xa8, 0x39, 0x60, 0x74, 0x00, 0x25, 0x6b, 0x2e, 0x24, 0xc6, 0xd3, - 0xa8, 0xb4, 0xca, 0xd6, 0x8e, 0xa6, 0xd5, 0x12, 0xbd, 0x06, 0xb3, 0x05, 0xd6, 0x13, 0x89, 0x25, - 0x20, 0x52, 0xff, 0x5f, 0x16, 0x9d, 0x7d, 0xab, 0xb3, 0x41, 0x1d, 0x93, 0x7a, 0xd4, 0x15, 0x4b, - 0x43, 0xf0, 0x06, 0xcb, 0x68, 0x22, 0x81, 0x14, 0x6f, 0x72, 0xb1, 0xd7, 0xad, 0x9c, 0xbf, 0x1f, - 0x20, 0x1b, 0x62, 0xb5, 0x91, 0x1a, 0x49, 0xb2, 0xe2, 0x45, 0x34, 0x16, 0x02, 0xd9, 0xdc, 0x76, - 0x95, 0x2c, 0xcc, 0x91, 0x0b, 0xbd, 0x6e, 0xe5, 0x9c, 0x24, 0x8d, 0xad, 0x03, 0x72, 0x87, 0xc5, - 0xd9, 0xf0, 0x5b, 0x68, 0x3c, 0x04, 0xbd, 0xe9, 0x58, 0x1d, 0xdb, 0x55, 0x72, 0x20, 0xaa, 0xd2, - 0xeb, 0x56, 0x9e, 0x95, 0x44, 0x6d, 0x02, 0x52, 0x92, 0x95, 0x60, 0x54, 0xff, 0x39, 0x87, 0x4e, - 0x87, 0xc0, 0x55, 0x4b, 0x0f, 0x14, 0xb0, 0x22, 0x2b, 0x60, 0xd5, 0xd2, 0x61, 0x8d, 0xe5, 0x0a, - 0x78, 0xbe, 0xd7, 0xad, 0x3c, 0x27, 0xb5, 0x63, 0x5b, 0x7a, 0x7c, 0xa9, 0x4d, 0xf2, 0xe2, 0xf7, - 0xd0, 0x33, 0x09, 0x20, 0x9f, 0xd1, 0xbc, 0x9f, 0x2f, 0xf7, 0xba, 0x15, 0x35, 0x45, 0x6a, 0x7c, - 0x82, 0xf7, 0x91, 0x82, 0x35, 0x74, 0x46, 0x52, 0xbb, 0x65, 0x7a, 0x9a, 0x61, 0x8a, 0xad, 0x81, - 0x8f, 0x87, 0x17, 0x7a, 0xdd, 0xca, 0x25, 0xb9, 0xdf, 0x7c, 0x9a, 0xf8, 0xc3, 0xf7, 0x93, 0x83, - 0x75, 0xa4, 0xa4, 0xa0, 0xea, 0x6d, 0x6d, 0xd3, 0xdf, 0xef, 0xae, 0xf4, 0xba, 0x95, 0x8f, 0xa4, - 0xb6, 0x61, 0x30, 0x2a, 0xa9, 0x91, 0xbe, 0x92, 0x30, 0x41, 0x38, 0xc4, 0x2d, 0x5b, 0x3a, 0x85, - 0x77, 0xc8, 0x83, 0x7c, 0xb5, 0xd7, 0xad, 0x5c, 0x90, 0xe4, 0x9b, 0x96, 0x4e, 0xe3, 0x8f, 0x9f, - 0xc2, 0xad, 0xfe, 0x28, 0xcf, 0x16, 0x16, 0xd8, 0xc1, 0xd6, 0x3c, 0xcd, 0xf1, 0xf0, 0xab, 0xa1, - 0x49, 0x00, 0xbd, 0x5a, 0x9e, 0x19, 0xf7, 0x17, 0x19, 0x1f, 0x3e, 0x3b, 0xcc, 0x16, 0x93, 0xef, - 0x77, 0x2b, 0x99, 0x5e, 0xb7, 0x32, 0x40, 0x4a, 0xd2, 0xec, 0xe6, 0xbb, 0x57, 0x16, 0xf8, 0x26, - 0x7d, 0x3e, 0x79, 0x87, 0x8b, 0xf1, 0xf2, 0xdd, 0xec, 0x0d, 0x54, 0x14, 0xcf, 0x00, 0x3d, 0x52, - 0x9e, 0x39, 0x13, 0xae, 0x6b, 0x91, 0x9d, 0x38, 0xc6, 0xed, 0x73, 0xe1, 0xd7, 0x50, 0x81, 0x2f, - 0x57, 0xa0, 0xed, 0xf2, 0xcc, 0x33, 0xe9, 0xeb, 0x62, 0x8c, 0x5d, 0xf0, 0xe0, 0x45, 0x84, 0xc2, - 0xa5, 0x2a, 0xb0, 0x3b, 0x84, 0x84, 0xe4, 0x22, 0x16, 0x93, 0x22, 0xf1, 0xe2, 0x57, 0xd0, 0xf0, - 0x3a, 0x75, 0xda, 0x86, 0xa9, 0xb5, 0xd6, 0x8c, 0xf7, 0x7d, 0xd3, 0x03, 0xb6, 0x71, 0xd7, 0x78, - 0x5f, 0xee, 0x8b, 0x08, 0x1d, 0xfe, 0x7c, 0xda, 0xa2, 0x52, 0x84, 0x07, 0x79, 0xde, 0x7f, 0x90, - 0xbe, 0x4b, 0x52, 0xec, 0x79, 0x52, 0xd6, 0x98, 0xb7, 0xd1, 0x48, 0x64, 0x6e, 0x28, 0x25, 0x10, - 0xfd, 0x5c, 0x52, 0xb4, 0x34, 0xd1, 0x63, 0x62, 0xa3, 0x12, 0xd8, 0x8e, 0x58, 0x37, 0x0d, 0xcf, - 0xd0, 0x5a, 0x35, 0xab, 0xdd, 0xd6, 0x4c, 0x5d, 0x19, 0x0a, 0x77, 0x76, 0x83, 0x63, 0x1a, 0x4d, - 0x8e, 0x92, 0x77, 0xc4, 0x28, 0x13, 0x5b, 0xb3, 0x44, 0x1f, 0x12, 0xda, 0xb4, 0x1c, 0xdd, 0x30, - 0x37, 0x15, 0x04, 0x4a, 0x83, 0x35, 0xcb, 0xe5, 0xb8, 0x86, 0xe3, 0x23, 0xe5, 0x35, 0x2b, 0xce, - 0xf8, 0x99, 0xc1, 0x52, 0x79, 0x7c, 0x38, 0x61, 0x3c, 0xfc, 0x76, 0x0e, 0x95, 0x05, 0xe9, 0x67, - 0x2c, 0xc3, 0x3c, 0x19, 0xe0, 0x07, 0x19, 0xe0, 0xa9, 0x03, 0xb5, 0x70, 0x58, 0x03, 0x55, 0xfd, - 0x4a, 0x36, 0x58, 0x8d, 0x56, 0x1d, 0xc3, 0x3c, 0xd8, 0x6a, 0x74, 0x19, 0xa1, 0xda, 0x56, 0xc7, - 0xbc, 0xcf, 0x4f, 0x35, 0xd9, 0xf0, 0x54, 0xd3, 0x34, 0x88, 0x84, 0x61, 0x47, 0x9b, 0x39, 0x26, - 0x9f, 0xf5, 0xcc, 0xf0, 0xec, 0xd0, 0x07, 0x5c, 0x52, 0xe6, 0x25, 0x02, 0x60, 0x5c, 0x41, 0xf9, - 0xd9, 0x5d, 0x8f, 0xba, 0xa0, 0xf9, 0x1c, 0x3f, 0xfa, 0x6c, 0x30, 0x00, 0xe1, 0x70, 0x7c, 0x13, - 0x4d, 0xcc, 0xd1, 0x96, 0xb6, 0x7b, 0xc7, 0x68, 0xb5, 0x0c, 0x97, 0x36, 0x2d, 0x53, 0x77, 0x41, - 0xc9, 0xa2, 0xb9, 0xb6, 0x4b, 0x92, 0x04, 0x58, 0x45, 0x85, 0x95, 0x7b, 0xf7, 0x5c, 0xea, 0x81, - 0xfa, 0x72, 0xb3, 0xa8, 0xd7, 0xad, 0x14, 0x2c, 0x80, 0x10, 0x81, 0x51, 0xbf, 0x99, 0x41, 0xe3, - 0x73, 0xd4, 0xbd, 0xef, 0x59, 0x76, 0x30, 0xca, 0x0f, 0xa4, 0x92, 0xab, 0xa8, 0x78, 0x87, 0xba, - 0x2e, 0xdb, 0x96, 0xb2, 0xf0, 0xb6, 0x63, 0xe2, 0x6d, 0x8b, 0x6d, 0x0e, 0x26, 0x3e, 0x3e, 0xfd, - 0xad, 0x72, 0x0f, 0x79, 0x2b, 0xf5, 0x27, 0x59, 0x74, 0x46, 0x3c, 0x71, 0xad, 0x65, 0xd8, 0x1b, - 0x96, 0xe6, 0xe8, 0x84, 0x36, 0xa9, 0xb1, 0x4d, 0x8f, 0xe7, 0xc4, 0x8b, 0x4e, 0x9d, 0xc1, 0x03, - 0x4c, 0x9d, 0x19, 0x54, 0x16, 0x9a, 0x01, 0x1b, 0x96, 0x6f, 0xdb, 0xe3, 0xbd, 0x6e, 0x65, 0x58, - 0xe7, 0x60, 0x30, 0xff, 0x89, 0x4c, 0xc4, 0x06, 0xc9, 0x12, 0x35, 0x37, 0xbd, 0x2d, 0x18, 0x24, - 0x79, 0x3e, 0x48, 0x5a, 0x00, 0x21, 0x02, 0xa3, 0xfe, 0x6b, 0x16, 0x4d, 0xc6, 0x55, 0xbe, 0x46, - 0x4d, 0xfd, 0x44, 0xdf, 0x1f, 0x8e, 0xbe, 0xff, 0x26, 0x8b, 0x46, 0x82, 0xad, 0xe7, 0x0b, 0xb4, - 0x79, 0x34, 0x26, 0x53, 0xb8, 0x21, 0xe4, 0x0e, 0xbc, 0x21, 0x1c, 0x44, 0xcb, 0x2a, 0x2a, 0x10, - 0xaa, 0xb9, 0x62, 0x5b, 0x19, 0xe2, 0x1a, 0x73, 0x00, 0x42, 0x04, 0x06, 0x3f, 0x8f, 0x8a, 0x77, - 0xb4, 0x07, 0x46, 0xbb, 0xd3, 0x16, 0x6b, 0x1d, 0x38, 0x4f, 0xda, 0xda, 0x03, 0xe2, 0xc3, 0xd5, - 0xbf, 0xcd, 0xb0, 0x13, 0x36, 0x28, 0x55, 0x08, 0x3f, 0x90, 0x56, 0x43, 0xed, 0x64, 0x0f, 0xac, - 0x9d, 0xdc, 0xe3, 0x6b, 0x47, 0xfd, 0xe6, 0x20, 0x53, 0x0f, 0x33, 0xfd, 0x9e, 0xf6, 0xd9, 0x18, - 0xf6, 0x48, 0xfe, 0x31, 0x7a, 0xe4, 0xa9, 0xb1, 0xab, 0xd5, 0xff, 0x28, 0x22, 0x24, 0xb4, 0x3f, - 0x7f, 0xb2, 0x86, 0x1f, 0x6c, 0xd4, 0xcc, 0xa1, 0x89, 0x79, 0x73, 0x4b, 0x33, 0x9b, 0x54, 0x0f, - 0x4f, 0x17, 0x6c, 0xe8, 0x94, 0xb8, 0x67, 0x96, 0x0a, 0x64, 0x78, 0xbc, 0x20, 0x49, 0x06, 0x7c, - 0x1d, 0x95, 0xeb, 0xa6, 0x47, 0x1d, 0xad, 0xe9, 0x19, 0xdb, 0x14, 0x46, 0x4f, 0x69, 0x76, 0xac, - 0xd7, 0xad, 0x94, 0x8d, 0x10, 0x4c, 0x64, 0x1a, 0x7c, 0x13, 0x0d, 0xaf, 0x6a, 0x8e, 0x67, 0x34, - 0x0d, 0x5b, 0x33, 0x3d, 0x57, 0x29, 0xc1, 0xd1, 0x08, 0xf6, 0x1e, 0x5b, 0x82, 0x93, 0x08, 0x15, - 0xfe, 0x3c, 0x1a, 0x82, 0x23, 0x38, 0x78, 0xbf, 0x87, 0x1e, 0xea, 0xfd, 0xbe, 0x14, 0x7a, 0xf4, - 0xf8, 0x21, 0xc9, 0x65, 0xcc, 0xe1, 0x54, 0x00, 0x87, 0x78, 0x28, 0x11, 0x7f, 0x16, 0x15, 0xe7, - 0x4d, 0x1d, 0x84, 0xa3, 0x87, 0x0a, 0x57, 0x85, 0xf0, 0x67, 0x42, 0xe1, 0x96, 0x1d, 0x93, 0xed, - 0x8b, 0x4b, 0x9f, 0x65, 0xe5, 0x0f, 0x6f, 0x96, 0x0d, 0x7f, 0x08, 0xa7, 0xd7, 0x91, 0xc3, 0x3a, - 0xbd, 0x8e, 0x3e, 0xe6, 0xe9, 0x55, 0x7d, 0x1f, 0x95, 0x67, 0x57, 0x17, 0x82, 0xd9, 0x7b, 0x16, - 0xe5, 0x56, 0xc5, 0x6d, 0xc3, 0x20, 0xdf, 0x30, 0x6d, 0x43, 0x27, 0x0c, 0x86, 0xaf, 0xa2, 0x52, - 0x0d, 0x5c, 0x78, 0xc2, 0x71, 0x3d, 0xc8, 0x1d, 0xd7, 0x4d, 0x80, 0x81, 0xe3, 0xda, 0x47, 0xe3, - 0x8f, 0xa2, 0xe2, 0xaa, 0x63, 0x6d, 0x3a, 0x5a, 0x5b, 0xf8, 0xba, 0xca, 0xcc, 0xd8, 0xb7, 0x39, - 0x88, 0xf8, 0x38, 0xf5, 0x97, 0x32, 0xa8, 0xb0, 0xe6, 0x69, 0x5e, 0xc7, 0x65, 0x1c, 0x6b, 0x1d, - 0x38, 0x41, 0x43, 0xdb, 0x25, 0xce, 0xe1, 0x72, 0x10, 0xf1, 0x71, 0xf8, 0x2a, 0xca, 0xcf, 0x3b, - 0x8e, 0xe5, 0xc8, 0x17, 0x10, 0x94, 0x01, 0xe4, 0x0b, 0x08, 0xa0, 0xc0, 0xb7, 0x50, 0x99, 0xaf, - 0x39, 0xfc, 0xe0, 0xc1, 0x9f, 0xe3, 0x74, 0xaf, 0x5b, 0x99, 0x10, 0x87, 0x0e, 0xf9, 0x26, 0x46, - 0xa2, 0x54, 0xbf, 0x9d, 0x93, 0x8c, 0x02, 0xae, 0xf1, 0xa7, 0xf0, 0xf0, 0x7e, 0x03, 0xe5, 0x66, - 0x57, 0x17, 0xc4, 0x02, 0x78, 0xca, 0x67, 0x95, 0x86, 0x4a, 0x8c, 0x8f, 0x51, 0xe3, 0xf3, 0x68, - 0x70, 0x95, 0x0d, 0x9f, 0x02, 0x0c, 0x8f, 0x52, 0xaf, 0x5b, 0x19, 0xb4, 0xd9, 0xf8, 0x01, 0x28, - 0x60, 0x35, 0x6f, 0x0b, 0xd6, 0xb2, 0x21, 0x81, 0xd5, 0xbc, 0x2d, 0x02, 0x50, 0x86, 0xad, 0x3a, - 0x9b, 0xdb, 0x62, 0xd5, 0x02, 0xac, 0xe6, 0x6c, 0x6e, 0x13, 0x80, 0xe2, 0x69, 0x84, 0x08, 0xf5, - 0x3a, 0x8e, 0x09, 0x97, 0x79, 0x43, 0x60, 0x26, 0xc3, 0x6a, 0xe8, 0x00, 0xb4, 0xd1, 0xb4, 0x74, - 0x4a, 0x24, 0x12, 0xf5, 0x37, 0x43, 0xff, 0xcb, 0x9c, 0xe1, 0xde, 0x3f, 0xe9, 0xc2, 0x7d, 0x74, - 0xa1, 0x26, 0x4e, 0x22, 0xc9, 0x4e, 0xaa, 0xa0, 0xfc, 0x42, 0x4b, 0xdb, 0x74, 0xa1, 0x0f, 0xf3, - 0xdc, 0x2b, 0x71, 0x8f, 0x01, 0x08, 0x87, 0xc7, 0xfa, 0xa9, 0xf4, 0xf0, 0x7e, 0xfa, 0xe5, 0x7c, - 0x30, 0xdb, 0x96, 0xa9, 0xb7, 0x63, 0x39, 0x27, 0x5d, 0xf5, 0xa8, 0x5d, 0x75, 0x19, 0x15, 0xd7, - 0x9c, 0x26, 0x1c, 0x33, 0x79, 0x6f, 0x0d, 0xf7, 0xba, 0x95, 0x92, 0xeb, 0x34, 0xf9, 0x11, 0xd3, - 0x47, 0x32, 0xba, 0x39, 0xd7, 0x03, 0xba, 0x62, 0x48, 0xa7, 0xbb, 0x9e, 0xa0, 0x13, 0x48, 0x41, - 0xb7, 0x6a, 0x39, 0x9e, 0xe8, 0xb8, 0x80, 0xce, 0xb6, 0x1c, 0x8f, 0xf8, 0x48, 0xfc, 0x22, 0x42, - 0xeb, 0xb5, 0xd5, 0x77, 0xa8, 0x03, 0xea, 0xe2, 0x73, 0x11, 0x96, 0xeb, 0x6d, 0x0e, 0x22, 0x12, - 0x1a, 0xaf, 0xa3, 0xa1, 0x15, 0x9b, 0x3a, 0x10, 0x26, 0x00, 0x16, 0xc0, 0xe8, 0xcc, 0x0b, 0x31, - 0xd5, 0x8a, 0x7e, 0x9f, 0x12, 0x7f, 0x03, 0x72, 0xbe, 0xbf, 0x58, 0xfe, 0x4f, 0x12, 0x0a, 0xc2, - 0xb7, 0x50, 0xa1, 0xca, 0xed, 0xbc, 0x32, 0x88, 0x0c, 0x54, 0x36, 0xcf, 0xfe, 0x70, 0x14, 0x3f, - 0x14, 0x6a, 0xf0, 0x3f, 0x11, 0xe4, 0xea, 0x55, 0x34, 0x1e, 0x6f, 0x06, 0x97, 0x51, 0xb1, 0xb6, - 0xb2, 0xbc, 0x3c, 0x5f, 0x5b, 0x1f, 0x1f, 0xc0, 0x25, 0x34, 0xb8, 0x36, 0xbf, 0x3c, 0x37, 0x9e, - 0x51, 0xbf, 0x21, 0xad, 0x20, 0x6c, 0x68, 0x9d, 0x78, 0x70, 0x0f, 0xe4, 0x16, 0x19, 0x07, 0xb7, - 0xe5, 0xba, 0xa3, 0x99, 0x6e, 0xdb, 0xf0, 0x3c, 0xaa, 0x8b, 0x5d, 0x02, 0xdc, 0x7a, 0xde, 0x03, - 0x92, 0xc0, 0xe3, 0x6b, 0x68, 0x04, 0x60, 0xc2, 0x93, 0xa7, 0xc3, 0xe8, 0x15, 0x0c, 0xce, 0x03, - 0x12, 0x45, 0xaa, 0x7f, 0x19, 0x3a, 0x71, 0x97, 0xa8, 0x76, 0x5c, 0x1d, 0x7f, 0xff, 0x4b, 0xfa, - 0x4b, 0xfd, 0xf5, 0x1c, 0x1a, 0x62, 0x6f, 0xc4, 0x83, 0x39, 0x8e, 0x42, 0x95, 0x37, 0x7d, 0xdb, - 0x50, 0x68, 0x72, 0x34, 0xd0, 0x04, 0x40, 0x13, 0x1a, 0xe0, 0x76, 0xe4, 0x35, 0x54, 0xb8, 0x43, - 0xbd, 0x2d, 0x4b, 0x17, 0x17, 0xa0, 0x93, 0xbd, 0x6e, 0x65, 0xbc, 0x0d, 0x10, 0xc9, 0xde, 0x13, - 0x34, 0xf8, 0x3e, 0xc2, 0x75, 0x9d, 0x9a, 0x9e, 0xe1, 0xed, 0x56, 0x3d, 0xcf, 0x31, 0x36, 0x3a, - 0x1e, 0x75, 0x85, 0xde, 0xce, 0x24, 0xce, 0x29, 0x6b, 0x10, 0x09, 0x05, 0x77, 0x9e, 0x93, 0x5a, - 0x40, 0x1e, 0x8a, 0xfd, 0xaf, 0x6e, 0xa5, 0xc0, 0x69, 0x48, 0x8a, 0x58, 0xfc, 0x36, 0x1a, 0xba, - 0xb3, 0x50, 0x9d, 0xa3, 0xdb, 0x46, 0x93, 0x8a, 0xcb, 0x8b, 0xb3, 0x81, 0x16, 0x7d, 0x44, 0xa0, - 0x12, 0x88, 0x4f, 0x68, 0xdf, 0xd3, 0x1a, 0x3a, 0xc0, 0xe5, 0xf8, 0x84, 0x80, 0x58, 0xfd, 0x97, - 0x0c, 0x1a, 0x27, 0xd4, 0xb5, 0x3a, 0x4e, 0xc8, 0x89, 0x2f, 0xa3, 0x41, 0xe9, 0x72, 0x1c, 0xbc, - 0x15, 0xb1, 0x1b, 0x59, 0xc0, 0xe3, 0x35, 0x54, 0x9c, 0x7f, 0x60, 0x1b, 0x0e, 0x75, 0x45, 0xdf, - 0xec, 0x75, 0x32, 0x7b, 0x4e, 0x9c, 0xcc, 0x26, 0x28, 0x67, 0x49, 0x1c, 0xca, 0x38, 0x18, 0xbf, - 0x82, 0x86, 0xee, 0xda, 0xba, 0xe6, 0x51, 0x7d, 0x76, 0x57, 0xd8, 0xdc, 0xf0, 0x26, 0x1d, 0x0e, - 0x6c, 0x6c, 0xec, 0xca, 0x6f, 0x12, 0x90, 0xe2, 0x4b, 0x28, 0xb7, 0xbe, 0xbe, 0x24, 0x3a, 0x0d, - 0x42, 0xb9, 0x3c, 0x4f, 0x0e, 0xca, 0x60, 0x58, 0xf5, 0xab, 0x59, 0x84, 0xd8, 0xd8, 0xa8, 0x39, - 0x54, 0xf3, 0x8e, 0x66, 0x82, 0xcf, 0xa2, 0x92, 0xaf, 0x70, 0x31, 0x2e, 0x15, 0x9f, 0x37, 0xde, - 0x11, 0xf1, 0xb6, 0x7d, 0x3c, 0x33, 0xa2, 0x88, 0xd5, 0x82, 0xab, 0x9d, 0x9c, 0x1f, 0xd5, 0xe6, - 0x30, 0x00, 0xe1, 0x70, 0xfc, 0x22, 0x1a, 0x12, 0x53, 0xd1, 0xf2, 0x3d, 0xc8, 0xfc, 0xa8, 0xe5, - 0x03, 0x49, 0x88, 0x57, 0xff, 0x2c, 0xc3, 0x95, 0x32, 0x47, 0x5b, 0xf4, 0xf8, 0x2a, 0x45, 0xfd, - 0x52, 0x06, 0x61, 0x26, 0x6c, 0x55, 0x73, 0xdd, 0x1d, 0xcb, 0xd1, 0x6b, 0x5b, 0x9a, 0xb9, 0x79, - 0x24, 0xaf, 0xa3, 0x7e, 0x39, 0x8f, 0x4e, 0x45, 0x6e, 0x73, 0x8f, 0xf9, 0x78, 0xbb, 0x1a, 0x1d, - 0x6f, 0x70, 0x68, 0x86, 0xf1, 0x26, 0x1f, 0x9a, 0xf9, 0xc8, 0xfb, 0x08, 0x1a, 0x12, 0xef, 0x5c, - 0x9f, 0x13, 0x23, 0x0f, 0xb6, 0x5b, 0x43, 0x27, 0x21, 0x02, 0xbf, 0x84, 0x86, 0xc5, 0x0f, 0xb6, - 0xea, 0xfa, 0x7e, 0x51, 0x18, 0xc7, 0x2e, 0x03, 0x90, 0x08, 0x1a, 0xbf, 0x8c, 0x86, 0xd8, 0xe0, - 0xdc, 0x84, 0x38, 0xc0, 0x62, 0x18, 0x2e, 0xa7, 0xfb, 0x40, 0x79, 0x49, 0x08, 0x28, 0xd9, 0x52, - 0x2e, 0x7c, 0xfc, 0xa5, 0x70, 0x29, 0xe7, 0x3e, 0x7e, 0x79, 0x29, 0x17, 0xde, 0xfe, 0xf7, 0x50, - 0xb9, 0x6a, 0x9a, 0x16, 0x8f, 0x47, 0x75, 0x85, 0x23, 0xab, 0xef, 0x1a, 0x7e, 0x09, 0x82, 0xb8, - 0x42, 0xfa, 0xd4, 0x45, 0x5c, 0x16, 0x88, 0x67, 0x58, 0x47, 0x6c, 0x1b, 0x74, 0x87, 0x3a, 0x22, - 0x54, 0x00, 0x9c, 0x79, 0x8e, 0x80, 0xc9, 0x21, 0x5d, 0x3e, 0x1d, 0x9e, 0x45, 0x23, 0xab, 0x8e, - 0x65, 0x5b, 0x2e, 0xd5, 0xb9, 0xa2, 0xca, 0xc0, 0x78, 0xbe, 0xd7, 0xad, 0x28, 0xb6, 0x40, 0x34, - 0x40, 0x63, 0x12, 0x7b, 0x94, 0x45, 0xfd, 0x66, 0x26, 0x36, 0x18, 0x8f, 0x70, 0x9e, 0x47, 0x46, - 0x47, 0xae, 0xcf, 0xe8, 0x50, 0xbf, 0x96, 0x45, 0x65, 0x76, 0x4e, 0x58, 0xb0, 0x9c, 0x1d, 0xcd, - 0x39, 0x1a, 0xe7, 0xc9, 0xa1, 0xdd, 0xa5, 0x48, 0x66, 0xc8, 0xe0, 0x3e, 0xcc, 0x90, 0xf3, 0x68, - 0x50, 0xba, 0xfe, 0xe3, 0xce, 0x0c, 0x76, 0xd6, 0x02, 0xa8, 0xfa, 0x7f, 0xb2, 0x08, 0x7d, 0xf6, - 0xfa, 0xf5, 0xa7, 0x58, 0x41, 0xea, 0xaf, 0x66, 0xd0, 0x98, 0xf0, 0xae, 0x49, 0xf1, 0xce, 0x45, - 0xdf, 0x2f, 0x9a, 0x09, 0xbd, 0x86, 0xc2, 0x1f, 0x4a, 0x7c, 0x1c, 0x9b, 0x89, 0xf3, 0x0f, 0x0c, - 0x0f, 0x1c, 0x0c, 0x52, 0xc0, 0x33, 0x15, 0x30, 0x79, 0x26, 0xfa, 0x74, 0xf8, 0x25, 0xdf, 0x6f, - 0x98, 0x0b, 0x97, 0x1f, 0xc6, 0x30, 0x9f, 0xea, 0x3b, 0x54, 0xbf, 0x35, 0x88, 0x06, 0xe7, 0x1f, - 0xd0, 0xe6, 0x31, 0xef, 0x1a, 0xe9, 0x34, 0x32, 0x78, 0xc0, 0xd3, 0xc8, 0xe3, 0x5c, 0x84, 0xbc, - 0x11, 0xf6, 0x67, 0x21, 0xda, 0x7c, 0xac, 0xe7, 0xe3, 0xcd, 0xfb, 0x3d, 0x7d, 0xfc, 0xee, 0xd1, - 0xfe, 0x3c, 0x87, 0x72, 0x6b, 0xb5, 0xd5, 0x93, 0x71, 0x73, 0xa4, 0xe3, 0x66, 0x6f, 0x47, 0xb3, - 0x1a, 0xf8, 0x8e, 0x4a, 0x61, 0xec, 0x40, 0xcc, 0x4d, 0xf4, 0x95, 0x2c, 0x1a, 0x5a, 0xeb, 0x6c, - 0xb8, 0xbb, 0xae, 0x47, 0xdb, 0xc7, 0xbc, 0x37, 0xcf, 0x8b, 0xf3, 0xe0, 0x60, 0xa8, 0x0d, 0x76, - 0x1e, 0x14, 0xa7, 0xc0, 0x4b, 0xfe, 0xca, 0x28, 0x9d, 0x33, 0x82, 0x95, 0xd1, 0x5f, 0x0f, 0x7f, - 0x3f, 0x8b, 0xc6, 0x6b, 0x2d, 0x83, 0x9a, 0xde, 0x9c, 0xe1, 0x36, 0x0f, 0x21, 0x52, 0xe2, 0xe8, - 0xb5, 0x72, 0x30, 0x47, 0xcb, 0x23, 0xc4, 0x9f, 0xa8, 0xff, 0x37, 0x8b, 0xca, 0xd5, 0x8e, 0xb7, - 0x55, 0xf5, 0x60, 0x73, 0x79, 0x2a, 0xb7, 0xf9, 0xef, 0x64, 0xd0, 0x18, 0x7b, 0x90, 0x75, 0xeb, - 0x3e, 0x35, 0x0f, 0xe1, 0x18, 0x25, 0x1f, 0x87, 0xb2, 0x8f, 0x79, 0x1c, 0xf2, 0x75, 0x99, 0xdb, - 0xe7, 0xb1, 0x90, 0x1d, 0xb4, 0xd9, 0x29, 0xe9, 0x09, 0x79, 0x8d, 0x43, 0x38, 0x47, 0x1c, 0xe5, - 0x6b, 0x7c, 0x2f, 0x83, 0x26, 0xd7, 0x1d, 0xb6, 0x91, 0xeb, 0x62, 0x3f, 0x3f, 0xe6, 0xfd, 0x92, - 0x7c, 0xa1, 0x63, 0xde, 0x43, 0x3f, 0xc8, 0xa0, 0xb3, 0xd1, 0x17, 0x7a, 0x12, 0x56, 0x81, 0xbf, - 0xca, 0xa0, 0xd3, 0x6f, 0x42, 0x12, 0x6c, 0xe0, 0x82, 0x7b, 0xf2, 0xde, 0xe8, 0x98, 0x8f, 0xbc, - 0xef, 0x66, 0xd0, 0xa9, 0x95, 0xfa, 0x5c, 0xed, 0x49, 0xe9, 0xa1, 0xc4, 0xfb, 0x3c, 0x01, 0xfd, - 0xb3, 0x56, 0xbd, 0xb3, 0xf4, 0x24, 0xf5, 0x4f, 0xe4, 0x7d, 0x8e, 0x79, 0xff, 0xfc, 0xff, 0x02, - 0x2a, 0xb3, 0x73, 0xad, 0xf0, 0xe9, 0x3d, 0xd5, 0x96, 0xfe, 0x0c, 0x2a, 0x0b, 0x35, 0xc0, 0x91, - 0x52, 0x8a, 0xe7, 0x17, 0x19, 0xe4, 0x0d, 0x38, 0x5a, 0xca, 0x44, 0xec, 0xc4, 0xf5, 0x0e, 0x75, - 0x36, 0xe4, 0x18, 0x9a, 0x6d, 0xea, 0x6c, 0x10, 0x80, 0xe2, 0xa5, 0xf0, 0xce, 0xae, 0xba, 0x5a, - 0x87, 0xc4, 0x57, 0x71, 0x52, 0x85, 0x4c, 0x5e, 0x47, 0xe0, 0x1a, 0x9a, 0x6d, 0xf0, 0x94, 0x59, - 0x39, 0x7e, 0x2f, 0xce, 0x89, 0x97, 0xd1, 0x84, 0x0f, 0x0b, 0x33, 0x58, 0x4b, 0x29, 0xe2, 0xd2, - 0x72, 0x57, 0x93, 0xac, 0xf8, 0x0d, 0x34, 0xec, 0x03, 0xdf, 0x32, 0x20, 0xbf, 0x8e, 0x89, 0x7a, - 0xb6, 0xd7, 0xad, 0x9c, 0x09, 0x44, 0xdd, 0x37, 0x22, 0xf1, 0x89, 0x11, 0x06, 0x59, 0x00, 0x1c, - 0x3b, 0x51, 0x8a, 0x80, 0xd8, 0x7d, 0x64, 0x84, 0x01, 0xbf, 0x0c, 0x02, 0x6c, 0xcb, 0x74, 0x29, - 0xf8, 0xf8, 0xca, 0x10, 0x60, 0x02, 0x77, 0x82, 0x8e, 0x80, 0xf3, 0x30, 0xa2, 0x08, 0x19, 0x5e, - 0x41, 0x28, 0xf4, 0xc5, 0x88, 0x60, 0xcd, 0x7d, 0x7b, 0x89, 0x24, 0x11, 0xea, 0x5f, 0xb3, 0xf3, - 0x9b, 0x6d, 0x07, 0x23, 0xf9, 0x25, 0x54, 0xa8, 0xda, 0xf6, 0x5d, 0x52, 0x17, 0xde, 0x49, 0x88, - 0x25, 0xd4, 0x6c, 0xbb, 0xd1, 0x71, 0x0c, 0xf9, 0x42, 0x82, 0x13, 0xe1, 0x1a, 0x1a, 0xa9, 0xda, - 0xf6, 0x6a, 0x67, 0xa3, 0x65, 0x34, 0xa5, 0xf4, 0x71, 0x5e, 0x83, 0xc0, 0xb6, 0x1b, 0x36, 0x60, - 0xe2, 0xc9, 0xf7, 0x51, 0x1e, 0xfc, 0x1e, 0x1a, 0xaa, 0xda, 0xb6, 0x48, 0xbe, 0xcf, 0x41, 0xf2, - 0xbd, 0xea, 0xbf, 0x93, 0xf4, 0x6c, 0x53, 0x01, 0x11, 0x4f, 0xbb, 0x3f, 0x2f, 0x6e, 0x6b, 0x27, - 0x59, 0x43, 0x89, 0x9c, 0xfb, 0x50, 0x24, 0xfe, 0x38, 0x2a, 0x56, 0x6d, 0x5b, 0x72, 0x0f, 0x80, - 0x2b, 0x95, 0x71, 0xc5, 0xba, 0xc8, 0x27, 0x3b, 0xf7, 0x1a, 0x1a, 0x8d, 0x36, 0xb6, 0xaf, 0xfc, - 0xfc, 0x9f, 0x66, 0xe0, 0x85, 0x8e, 0xf9, 0x85, 0xda, 0x0d, 0x94, 0xab, 0xda, 0xb6, 0x58, 0x4e, - 0x4e, 0xa5, 0xf4, 0x47, 0x3c, 0x5a, 0xab, 0x6a, 0xdb, 0xfe, 0xab, 0xf3, 0x2b, 0xef, 0xa7, 0xeb, - 0xd5, 0xbf, 0xcd, 0x5f, 0xfd, 0x98, 0xdf, 0x50, 0x7f, 0x2b, 0x87, 0xc6, 0xaa, 0xb6, 0x7d, 0x92, - 0xb6, 0x7e, 0x58, 0x31, 0x61, 0xd7, 0x11, 0x92, 0x96, 0xc7, 0x62, 0x10, 0xfa, 0x51, 0x96, 0x96, - 0x46, 0x25, 0x43, 0x24, 0x22, 0x7f, 0xf8, 0x95, 0xf6, 0x35, 0xfc, 0xfe, 0x24, 0xd2, 0x71, 0x90, - 0x82, 0x7b, 0xd2, 0x71, 0xf9, 0x03, 0x59, 0x54, 0xa3, 0xb2, 0x32, 0x45, 0xc0, 0xb7, 0xb8, 0xa1, - 0xf7, 0xd3, 0x0f, 0x9a, 0x0c, 0xd5, 0x30, 0x74, 0x12, 0xa3, 0xf5, 0xfb, 0xb0, 0xb8, 0xaf, 0x3e, - 0xfc, 0x7a, 0x16, 0x4d, 0x84, 0x7d, 0x78, 0x18, 0x86, 0xe9, 0x34, 0x42, 0xdc, 0x49, 0x19, 0x5c, - 0x24, 0x8e, 0xf0, 0x48, 0x65, 0x17, 0xa0, 0x22, 0x52, 0x39, 0x24, 0x09, 0x6e, 0x15, 0x72, 0xa9, - 0xb7, 0x0a, 0x57, 0x51, 0x89, 0x68, 0x3b, 0x6f, 0x77, 0xa8, 0xb3, 0x2b, 0xb6, 0x52, 0x70, 0xa5, - 0x3b, 0xda, 0x4e, 0xe3, 0x8b, 0x0c, 0x48, 0x02, 0x34, 0x56, 0x83, 0x18, 0x35, 0xc9, 0x79, 0xcc, - 0x63, 0xd4, 0x82, 0xc8, 0x34, 0xa1, 0xa4, 0xc2, 0xbe, 0x94, 0xf4, 0x83, 0x02, 0x1a, 0x9f, 0xd3, - 0x3c, 0x6d, 0x43, 0x73, 0xa9, 0x74, 0x90, 0x18, 0xf3, 0x61, 0x6c, 0x20, 0x18, 0x41, 0xc9, 0x22, - 0x88, 0xcb, 0xd2, 0x37, 0x1a, 0x2e, 0x87, 0xca, 0x35, 0x5e, 0x62, 0x0c, 0xf8, 0x53, 0xa1, 0xdc, - 0xa0, 0xa8, 0x0d, 0x37, 0x67, 0x40, 0x63, 0xfa, 0x46, 0xc3, 0x16, 0x60, 0x92, 0x20, 0xc4, 0xd7, - 0x50, 0xd9, 0x87, 0x31, 0xe3, 0x29, 0x17, 0xbe, 0xb3, 0xbe, 0xc1, 0x6c, 0x27, 0x22, 0xa3, 0xf1, - 0x27, 0xd1, 0xb0, 0xff, 0x53, 0x32, 0x4b, 0xc0, 0xd6, 0xd2, 0x37, 0x12, 0x86, 0xa3, 0x4c, 0x2a, - 0xb3, 0xc2, 0xfc, 0xcc, 0x47, 0x58, 0x63, 0x05, 0xad, 0x22, 0xa4, 0xf8, 0x8b, 0x68, 0xd4, 0xff, - 0x2d, 0x8c, 0xad, 0x02, 0x18, 0x5b, 0xd7, 0x7c, 0xcd, 0xc7, 0xd5, 0x3a, 0x15, 0x25, 0xe7, 0x66, - 0xd7, 0xb3, 0xc2, 0xec, 0x3a, 0xa5, 0x6f, 0x24, 0xad, 0xae, 0x58, 0x03, 0xb8, 0x8e, 0x26, 0x7c, - 0x48, 0xf5, 0xdd, 0x35, 0x42, 0x37, 0xd9, 0xac, 0x2c, 0x86, 0xc6, 0xb2, 0xbe, 0xd1, 0x80, 0x52, - 0x57, 0x80, 0x90, 0x6d, 0xf6, 0x04, 0x17, 0x6e, 0xa1, 0xf3, 0x11, 0xa0, 0xee, 0x6e, 0x19, 0xf7, - 0x3c, 0x61, 0xe9, 0xd6, 0xe7, 0xc4, 0x71, 0x00, 0x6a, 0xc1, 0x04, 0x52, 0x39, 0x8d, 0x5f, 0x2c, - 0xa8, 0x11, 0xa9, 0x9b, 0xb6, 0xa7, 0x34, 0xbc, 0x86, 0x26, 0x7d, 0xfc, 0x9b, 0xb5, 0xd5, 0x55, - 0xc7, 0xfa, 0x02, 0x6d, 0x7a, 0xf5, 0x39, 0x71, 0x52, 0x80, 0x14, 0x24, 0x7d, 0xa3, 0xb1, 0xd9, - 0xb4, 0xd9, 0xa0, 0x60, 0xb8, 0xa8, 0xf0, 0x54, 0x66, 0xfc, 0x0e, 0x3a, 0x2d, 0xc1, 0xeb, 0xa6, - 0xeb, 0x69, 0x66, 0x93, 0xd6, 0xe7, 0xc4, 0xf1, 0x01, 0x8e, 0x32, 0x42, 0xaa, 0x21, 0x90, 0x51, - 0xb1, 0xe9, 0xec, 0xe7, 0xaa, 0xe8, 0x54, 0x4a, 0x4f, 0xed, 0xcb, 0x66, 0xfd, 0x4a, 0x36, 0x1c, - 0x1c, 0xc7, 0xdc, 0x70, 0x9d, 0x45, 0x25, 0xff, 0x4d, 0xc4, 0x16, 0xa2, 0xf4, 0x1b, 0xe0, 0x71, - 0x19, 0x3e, 0x3e, 0xa2, 0x8e, 0x63, 0x6e, 0xcc, 0x1e, 0x86, 0x3a, 0x3e, 0xc8, 0x84, 0xea, 0x38, - 0xe6, 0x06, 0xee, 0x77, 0x73, 0xe1, 0xcc, 0x3e, 0xb1, 0x72, 0x0f, 0xcb, 0x58, 0x0a, 0x2f, 0x4e, - 0x0b, 0xfb, 0x08, 0x20, 0x93, 0x87, 0x66, 0xf1, 0x31, 0x87, 0xe6, 0x0f, 0x93, 0xfd, 0xc9, 0x0d, - 0x90, 0x63, 0xd9, 0x9f, 0x87, 0x30, 0x59, 0xf1, 0x0c, 0x1a, 0xf1, 0xff, 0xe7, 0x96, 0x5a, 0x5e, - 0xca, 0x87, 0xda, 0x10, 0x86, 0x5a, 0x94, 0x04, 0x7f, 0x0e, 0x9d, 0x89, 0x00, 0x56, 0x35, 0x47, - 0x6b, 0x53, 0x8f, 0x3a, 0xdc, 0x46, 0x10, 0xe5, 0xe7, 0x7c, 0xee, 0x86, 0x1d, 0xa0, 0xe5, 0x0a, - 0x6e, 0x7d, 0x24, 0x48, 0x83, 0xa3, 0xb8, 0x8f, 0x5b, 0xf5, 0x7f, 0xca, 0xa2, 0x91, 0x55, 0xcb, - 0xf5, 0x36, 0x1d, 0xea, 0xae, 0x6a, 0x8e, 0x4b, 0x9f, 0xde, 0x1e, 0xfd, 0x04, 0x1a, 0x81, 0x38, - 0xd9, 0x36, 0x35, 0x3d, 0xa9, 0x2e, 0x1d, 0xaf, 0xd1, 0xe0, 0x23, 0xc0, 0x6c, 0x24, 0x51, 0x42, - 0x5c, 0x41, 0x79, 0x3e, 0x06, 0xa4, 0xe8, 0x65, 0x3e, 0x00, 0x38, 0x5c, 0xfd, 0x7a, 0x0e, 0x0d, - 0xfb, 0x5a, 0x9e, 0x35, 0x8e, 0x6b, 0x16, 0xf0, 0xd1, 0x2a, 0x79, 0x1a, 0xa1, 0x55, 0xcb, 0xf1, - 0xb4, 0x96, 0x54, 0x12, 0x17, 0x8e, 0x0c, 0x36, 0x40, 0x39, 0x8f, 0x44, 0x82, 0xa7, 0x10, 0x92, - 0x26, 0x58, 0x11, 0x26, 0xd8, 0x68, 0xaf, 0x5b, 0x41, 0xe1, 0xbc, 0x22, 0x12, 0x85, 0xfa, 0x47, - 0x59, 0x34, 0xe6, 0x77, 0xd2, 0xfc, 0x03, 0xda, 0xec, 0x78, 0x4f, 0xf1, 0x64, 0x88, 0x6a, 0x3b, - 0xff, 0x50, 0x6d, 0xab, 0xff, 0x26, 0x2d, 0x24, 0xb5, 0x96, 0x75, 0xb2, 0x90, 0xfc, 0x4f, 0x8c, - 0x71, 0xf5, 0x67, 0x72, 0x68, 0xd2, 0xd7, 0xfa, 0x42, 0xc7, 0x04, 0x33, 0xa1, 0xa6, 0xb5, 0x5a, - 0x4f, 0xf3, 0xbe, 0x5c, 0xf6, 0x15, 0xb1, 0x22, 0x12, 0x4f, 0x46, 0xf8, 0x25, 0xdb, 0x3d, 0x01, - 0x6e, 0x58, 0x86, 0x4e, 0x64, 0x22, 0xfc, 0x06, 0x1a, 0xf6, 0x7f, 0x56, 0x9d, 0x4d, 0x7f, 0x33, - 0x86, 0xa3, 0x73, 0xc0, 0xa4, 0x39, 0x9b, 0x91, 0x62, 0xc3, 0x32, 0x83, 0xfa, 0xb5, 0x02, 0x3a, - 0xf7, 0xae, 0x61, 0xea, 0xd6, 0x8e, 0x2b, 0x8a, 0x71, 0x1d, 0x7f, 0xa3, 0xf7, 0xf0, 0x6a, 0xe0, - 0x84, 0x96, 0x49, 0x7e, 0x1f, 0x66, 0xeb, 0xdb, 0xe8, 0x74, 0x5c, 0xa5, 0x4e, 0x90, 0xef, 0x28, - 0x7a, 0x67, 0x87, 0x13, 0x34, 0xfc, 0x7a, 0x68, 0xc2, 0xff, 0x44, 0xd2, 0x39, 0xe3, 0x05, 0xd5, - 0x8a, 0x8f, 0x52, 0x50, 0xed, 0x63, 0xa8, 0x30, 0x67, 0xb5, 0x35, 0xc3, 0x0f, 0xf1, 0x85, 0x59, - 0x1c, 0xb4, 0x0b, 0x18, 0x22, 0x28, 0x98, 0x7c, 0xd1, 0x30, 0x74, 0xd9, 0x50, 0x28, 0xdf, 0x67, - 0xe8, 0xb8, 0xd4, 0x21, 0x32, 0x11, 0xb6, 0xd0, 0x88, 0x68, 0x4e, 0x78, 0x8b, 0x10, 0x78, 0x8b, - 0x5e, 0xf6, 0x75, 0xd4, 0x7f, 0x58, 0x4d, 0x45, 0xf8, 0xb8, 0xdb, 0x08, 0x9e, 0xce, 0x7f, 0x19, - 0xee, 0x37, 0x22, 0x51, 0xf9, 0x92, 0x12, 0x60, 0x91, 0x29, 0x27, 0x95, 0x00, 0xab, 0x8c, 0x4c, - 0x74, 0xee, 0x36, 0xc2, 0xc9, 0xc6, 0xf6, 0xe5, 0xf9, 0xf8, 0xf9, 0x2c, 0xc2, 0xb1, 0x03, 0xc4, - 0xfc, 0x53, 0x6c, 0x07, 0xa9, 0xbf, 0x93, 0x41, 0x13, 0x89, 0x4c, 0x5d, 0x7c, 0x03, 0x21, 0x0e, - 0x91, 0xb2, 0x6e, 0x21, 0xc7, 0x2e, 0xcc, 0xde, 0x15, 0x7b, 0x40, 0x48, 0x86, 0xa7, 0x51, 0x89, - 0xff, 0x0a, 0xaa, 0xc0, 0xc7, 0x59, 0x3a, 0x1d, 0x43, 0x27, 0x01, 0x51, 0xd8, 0x0a, 0x7c, 0x0f, - 0x21, 0x97, 0xca, 0xe2, 0xed, 0xda, 0x41, 0x2b, 0x8c, 0x4c, 0xfd, 0x76, 0x06, 0x0d, 0x07, 0x0f, - 0x5c, 0xd5, 0x8f, 0xaa, 0xeb, 0x0a, 0x22, 0xe9, 0x39, 0xf7, 0xb0, 0xa4, 0xe7, 0xd8, 0xa2, 0x22, - 0xb2, 0x9c, 0xff, 0x22, 0x83, 0xc6, 0x02, 0xda, 0x23, 0xf4, 0xb1, 0x1c, 0xf8, 0x45, 0x7e, 0x21, - 0x83, 0x94, 0x59, 0xa3, 0xd5, 0x32, 0xcc, 0xcd, 0xba, 0x79, 0xcf, 0x72, 0xda, 0x90, 0x5b, 0x78, - 0x74, 0x4e, 0x34, 0xf5, 0xe7, 0x32, 0x68, 0x42, 0x3c, 0x50, 0x4d, 0x73, 0xf4, 0xa3, 0xf3, 0x6e, - 0xc6, 0x9f, 0xe4, 0xe8, 0x7a, 0x19, 0xe2, 0xa3, 0x97, 0xac, 0xe6, 0xfd, 0x27, 0x20, 0xcc, 0x9b, - 0xbd, 0xc6, 0x31, 0x0f, 0x45, 0xfb, 0x72, 0x06, 0x4d, 0x12, 0xda, 0xb4, 0xb6, 0xa9, 0xb3, 0x5b, - 0xb3, 0x74, 0xfa, 0x26, 0x35, 0xa9, 0x73, 0x54, 0x83, 0xf4, 0x8f, 0xa1, 0xdc, 0x42, 0xf8, 0x30, - 0x77, 0x5d, 0xaa, 0x1f, 0x9f, 0xda, 0x18, 0xea, 0x1f, 0x14, 0x91, 0x92, 0x6a, 0x99, 0x1c, 0xdb, - 0x4d, 0xbd, 0xaf, 0xb9, 0x39, 0x78, 0x58, 0xe6, 0x66, 0x7e, 0x7f, 0xe6, 0x66, 0x61, 0xbf, 0xe6, - 0x66, 0xf1, 0x51, 0xcc, 0xcd, 0x76, 0xdc, 0xdc, 0x2c, 0x81, 0xb9, 0x79, 0x63, 0x4f, 0x73, 0x73, - 0xde, 0xd4, 0x1f, 0xd3, 0xd8, 0x3c, 0xb6, 0x15, 0x21, 0x1f, 0xc3, 0x4a, 0xc6, 0x57, 0xd8, 0xe2, - 0xd6, 0xb4, 0x1c, 0x9d, 0xf2, 0x0a, 0x8f, 0x25, 0xee, 0x0d, 0x76, 0x04, 0x8c, 0x04, 0xd8, 0x44, - 0x79, 0xcd, 0x91, 0x47, 0x29, 0xaf, 0x79, 0x08, 0x56, 0xf8, 0xf7, 0x32, 0x68, 0xa2, 0x46, 0x1d, - 0xcf, 0xb8, 0x67, 0x34, 0x35, 0xef, 0x30, 0xae, 0x20, 0xab, 0x68, 0x4c, 0x12, 0x28, 0x7d, 0x60, - 0x0b, 0xf2, 0xa2, 0x9b, 0xd4, 0xf1, 0xc0, 0x94, 0x94, 0x23, 0x02, 0x62, 0xf4, 0xac, 0x79, 0xbf, - 0xc4, 0x8d, 0x98, 0xbb, 0x41, 0xf3, 0x3e, 0x9c, 0x2b, 0xd2, 0x10, 0xbf, 0x48, 0x40, 0xaf, 0x7e, - 0x23, 0x83, 0x2e, 0x13, 0x6a, 0xd2, 0x1d, 0x6d, 0xa3, 0x45, 0x25, 0xc1, 0x62, 0x6d, 0x67, 0xf3, - 0xde, 0x70, 0xdb, 0x9a, 0xd7, 0xdc, 0x3a, 0xd0, 0x5b, 0x2e, 0x44, 0x3f, 0x72, 0xb5, 0x8f, 0xd5, - 0x29, 0xc2, 0xa7, 0xfe, 0x30, 0x83, 0x8a, 0x77, 0xcd, 0xfb, 0xa6, 0xb5, 0x73, 0xb0, 0x42, 0x48, - 0x37, 0x50, 0x59, 0x88, 0x91, 0x34, 0xce, 0xbf, 0x5a, 0xc6, 0xc1, 0x0d, 0xfe, 0x69, 0x33, 0x99, - 0x0a, 0xbf, 0x16, 0x30, 0x41, 0x98, 0x8a, 0xf4, 0x15, 0x2d, 0x9f, 0x29, 0xf6, 0x35, 0x33, 0x99, - 0x1c, 0x9f, 0x17, 0xdf, 0x18, 0x90, 0x52, 0x3f, 0xd9, 0xa3, 0xf0, 0x4f, 0x0c, 0xa8, 0xbf, 0x72, - 0x0d, 0xe5, 0x57, 0x4c, 0xba, 0x72, 0x0f, 0x5f, 0x97, 0x8a, 0x3d, 0x89, 0xf7, 0x9a, 0x90, 0xf5, - 0x04, 0x88, 0xc5, 0x01, 0x22, 0x95, 0x84, 0xba, 0x29, 0x97, 0xe2, 0x11, 0xba, 0xc5, 0x32, 0x0f, - 0xc7, 0x2c, 0x0e, 0x10, 0xb9, 0x64, 0xcf, 0x4d, 0xb9, 0x56, 0x8d, 0x18, 0x38, 0x11, 0x2e, 0x8e, - 0xf1, 0xb9, 0x84, 0xf1, 0xb2, 0x94, 0x56, 0x1a, 0x26, 0xee, 0xdd, 0x48, 0x52, 0x2c, 0x0e, 0x90, - 0xf4, 0x92, 0x32, 0x91, 0x4f, 0xcf, 0x08, 0xff, 0xc6, 0x64, 0x6c, 0xeb, 0x01, 0xdc, 0xe2, 0x00, - 0x89, 0x7e, 0xa6, 0xe6, 0x56, 0xe4, 0xa3, 0x1e, 0xf1, 0xf0, 0x1c, 0x09, 0xb5, 0x38, 0x40, 0x62, - 0x9f, 0xff, 0x88, 0x7c, 0x61, 0x42, 0x5c, 0xf7, 0xc4, 0x1b, 0x05, 0x9c, 0xd4, 0x28, 0xff, 0x1a, - 0xc5, 0xeb, 0xb1, 0xca, 0xef, 0x22, 0xfc, 0xed, 0x74, 0x8c, 0x99, 0x23, 0x17, 0x07, 0x48, 0xac, - 0x4e, 0xfc, 0x15, 0xbf, 0x18, 0xb8, 0x58, 0xcb, 0x47, 0x25, 0xe3, 0xcd, 0x78, 0x9f, 0x69, 0xc9, - 0x2f, 0x16, 0x7e, 0x53, 0x2e, 0x02, 0x2d, 0x16, 0x67, 0x1c, 0x6b, 0x65, 0xde, 0xd4, 0x59, 0xef, - 0x48, 0x96, 0xc3, 0xed, 0x78, 0xb9, 0x54, 0x51, 0x84, 0xf7, 0x99, 0x18, 0xa7, 0xc0, 0x2e, 0x0e, - 0x90, 0x78, 0x79, 0xd5, 0x5b, 0x91, 0x52, 0x9d, 0x22, 0x76, 0x3b, 0xae, 0x55, 0x86, 0x92, 0xb4, - 0x0a, 0x45, 0x3d, 0x6f, 0xc7, 0x6b, 0x47, 0x2a, 0x23, 0xa9, 0x4d, 0x0b, 0xac, 0xd4, 0xb4, 0x5f, - 0x6b, 0xf2, 0x56, 0xa4, 0xc6, 0x1f, 0x94, 0xd1, 0x4d, 0x69, 0x5a, 0xf3, 0x34, 0xb9, 0x69, 0x5e, - 0x0d, 0x30, 0x52, 0x6d, 0x4e, 0x19, 0x4b, 0xed, 0x50, 0xc0, 0x49, 0x1d, 0xca, 0x2b, 0xd3, 0xdd, - 0x8a, 0x14, 0x48, 0x51, 0xc6, 0xa3, 0x8d, 0x4a, 0x28, 0xd6, 0xa8, 0x5c, 0x4a, 0xe5, 0xa6, 0x5c, - 0x37, 0x44, 0x99, 0x88, 0x76, 0x50, 0x88, 0x61, 0x1d, 0x24, 0xd5, 0x17, 0xa9, 0x40, 0x4d, 0x02, - 0x05, 0x03, 0x79, 0x39, 0x78, 0xc2, 0xda, 0xea, 0xe2, 0x00, 0x81, 0x6a, 0x05, 0x2a, 0xaf, 0x76, - 0xa1, 0x9c, 0x02, 0x8a, 0xe1, 0xa0, 0x96, 0xe2, 0x03, 0xda, 0x5c, 0x1c, 0x20, 0xbc, 0x12, 0xc6, - 0x75, 0x29, 0x21, 0x5e, 0x99, 0x8c, 0x2e, 0x11, 0x01, 0x82, 0x2d, 0x11, 0x61, 0xda, 0xfc, 0x42, - 0x32, 0x69, 0x5c, 0x39, 0x1d, 0x3d, 0x3f, 0xc4, 0xf1, 0x8b, 0x03, 0x24, 0x99, 0x68, 0x7e, 0x2b, - 0x92, 0x47, 0xad, 0x3c, 0x13, 0x8b, 0x89, 0x0b, 0x51, 0x4c, 0x5d, 0x72, 0xc6, 0xf5, 0x4a, 0x6a, - 0x1d, 0x27, 0xe5, 0x0c, 0x08, 0x78, 0x36, 0x10, 0x90, 0x24, 0x59, 0x1c, 0x20, 0xa9, 0x15, 0xa0, - 0x6a, 0x89, 0x6c, 0x66, 0x45, 0x89, 0x1a, 0xae, 0x31, 0xf4, 0xe2, 0x00, 0x49, 0xe4, 0x3f, 0xdf, - 0x94, 0xd3, 0x88, 0x95, 0xb3, 0xd1, 0x4e, 0x0c, 0x31, 0xac, 0x13, 0xa5, 0x74, 0xe3, 0x9b, 0x72, - 0xd6, 0xae, 0x72, 0x2e, 0xc9, 0x15, 0xae, 0x9c, 0x52, 0x76, 0x2f, 0x49, 0x4f, 0x92, 0x55, 0x9e, - 0x05, 0xfe, 0xf3, 0x3e, 0x7f, 0x1a, 0xcd, 0xe2, 0x00, 0x49, 0x4f, 0xb0, 0x25, 0xe9, 0x79, 0xaa, - 0xca, 0xf9, 0xbd, 0x64, 0x06, 0x4f, 0x97, 0x9e, 0xe3, 0xaa, 0xed, 0x91, 0x2a, 0xaa, 0x3c, 0x17, - 0xcd, 0xe5, 0xe8, 0x4b, 0xb8, 0x38, 0x40, 0xf6, 0x48, 0x38, 0xbd, 0xdb, 0x27, 0x6f, 0x53, 0xb9, - 0x10, 0xad, 0xfa, 0x91, 0x4a, 0xb4, 0x38, 0x40, 0xfa, 0x64, 0x7d, 0xde, 0xed, 0x93, 0x3c, 0xa9, - 0x54, 0xf6, 0x14, 0x1b, 0xe8, 0xa3, 0x4f, 0xea, 0xe5, 0x4a, 0x6a, 0x06, 0xa3, 0x72, 0x31, 0x3a, - 0x74, 0x53, 0x48, 0xd8, 0xd0, 0x4d, 0xcb, 0x7d, 0x5c, 0x49, 0x4d, 0x21, 0x54, 0x9e, 0xdf, 0x43, - 0x60, 0xf0, 0x8c, 0xa9, 0xc9, 0x87, 0x2b, 0xa9, 0x39, 0x7c, 0x8a, 0x1a, 0x15, 0x98, 0x42, 0xc2, - 0x04, 0xa6, 0x65, 0xff, 0xad, 0xa4, 0x26, 0xd1, 0x29, 0x97, 0xf6, 0x10, 0x18, 0x3e, 0x61, 0x5a, - 0xfa, 0xdd, 0xad, 0x48, 0x16, 0x9b, 0xf2, 0x91, 0xe8, 0xba, 0x21, 0xa1, 0xd8, 0xba, 0x21, 0xe7, - 0xbb, 0xd5, 0x12, 0x81, 0xfe, 0xca, 0x47, 0xa3, 0xd3, 0x3c, 0x86, 0x66, 0xd3, 0x3c, 0x9e, 0x1a, - 0x50, 0x4b, 0x04, 0x9d, 0x2b, 0x97, 0xfb, 0x09, 0x01, 0x74, 0x54, 0x08, 0x0f, 0x53, 0xaf, 0xa7, - 0x44, 0x3d, 0x2b, 0x2f, 0x44, 0xbd, 0x8f, 0x09, 0x82, 0xc5, 0x01, 0x92, 0x12, 0x2b, 0x4d, 0xd2, - 0x83, 0xbb, 0x94, 0x2b, 0xd1, 0x69, 0x9b, 0x46, 0xc3, 0xa6, 0x6d, 0x6a, 0x60, 0xd8, 0x52, 0xda, - 0xfd, 0x80, 0x72, 0x35, 0x6a, 0x98, 0x25, 0x29, 0x98, 0x61, 0x96, 0x72, 0xaf, 0x40, 0xd2, 0xc3, - 0x95, 0x94, 0x8f, 0xed, 0xf9, 0x84, 0x40, 0x93, 0xf2, 0x84, 0x3c, 0x7a, 0x27, 0xb4, 0x9d, 0xee, - 0xda, 0x2d, 0x4b, 0xd3, 0x95, 0x17, 0x53, 0x6d, 0x27, 0x8e, 0x94, 0x6c, 0x27, 0x0e, 0x60, 0xbb, - 0xbc, 0xec, 0x3f, 0x57, 0xae, 0x45, 0x77, 0x79, 0x19, 0xc7, 0x76, 0xf9, 0x88, 0xaf, 0xbd, 0x96, - 0xf0, 0x5a, 0x2b, 0x2f, 0x45, 0x07, 0x40, 0x0c, 0xcd, 0x06, 0x40, 0xdc, 0xcf, 0xfd, 0x5e, 0x7f, - 0x8f, 0xb1, 0x32, 0x05, 0xd2, 0x2e, 0x06, 0xd5, 0xa2, 0xfb, 0xd0, 0x2d, 0x0e, 0x90, 0xfe, 0x5e, - 0xe7, 0x7a, 0x8a, 0x03, 0x58, 0x99, 0x8e, 0x0e, 0xb0, 0x04, 0x01, 0x1b, 0x60, 0x49, 0xb7, 0x71, - 0x3d, 0xc5, 0x83, 0xab, 0x7c, 0xbc, 0xaf, 0xa8, 0xe0, 0x9d, 0x53, 0xfc, 0xbe, 0x37, 0x65, 0x17, - 0xac, 0x72, 0x3d, 0xba, 0xd9, 0x85, 0x18, 0xb6, 0xd9, 0x49, 0xae, 0xda, 0x9b, 0xb2, 0xc7, 0x53, - 0x99, 0x49, 0x72, 0x85, 0x5b, 0xa4, 0xe4, 0x19, 0x25, 0xe9, 0x0e, 0x46, 0xe5, 0x46, 0x74, 0xd4, - 0xa5, 0xd1, 0xb0, 0x51, 0x97, 0xea, 0x9c, 0x5c, 0x48, 0xfa, 0x09, 0x95, 0x9b, 0x71, 0xcf, 0x69, - 0x14, 0xcf, 0x2c, 0x9f, 0x84, 0x6f, 0xf1, 0x76, 0x3c, 0xf2, 0x58, 0x79, 0x39, 0x6a, 0xdf, 0x46, - 0xb1, 0xcc, 0xbe, 0x8d, 0x45, 0x2a, 0xdf, 0x8e, 0x07, 0xeb, 0x2a, 0xaf, 0xa4, 0x4b, 0x08, 0xc6, - 0x4a, 0x3c, 0xb8, 0xf7, 0x76, 0x3c, 0xbe, 0x55, 0xb9, 0x95, 0x2e, 0x21, 0xd0, 0x6e, 0x3c, 0x1e, - 0xf6, 0xba, 0x94, 0xf3, 0xa7, 0x7c, 0x22, 0x6a, 0x3a, 0x06, 0x08, 0x66, 0x3a, 0x86, 0x99, 0x81, - 0xd7, 0xa5, 0x5c, 0x39, 0xe5, 0x93, 0x09, 0x96, 0xe0, 0x61, 0xa5, 0x8c, 0xba, 0xeb, 0x52, 0x8e, - 0x99, 0xf2, 0x6a, 0x82, 0x25, 0x78, 0x3a, 0x29, 0x13, 0x4d, 0xdf, 0x2b, 0x00, 0x40, 0xf9, 0x14, - 0xc8, 0x50, 0x1f, 0x7e, 0xa7, 0xbb, 0x38, 0x40, 0xf6, 0x0a, 0x24, 0x78, 0xaf, 0xbf, 0xd7, 0x55, - 0x79, 0x2d, 0x3a, 0x85, 0xfb, 0xd1, 0xb1, 0x29, 0xdc, 0xd7, 0x73, 0xfb, 0x7a, 0x2c, 0x18, 0x50, - 0x79, 0x3d, 0xba, 0xc4, 0x45, 0x90, 0x6c, 0x89, 0x8b, 0x87, 0x0e, 0x46, 0xa2, 0xdc, 0x94, 0x4f, - 0x47, 0x97, 0x38, 0x19, 0xc7, 0x96, 0xb8, 0x48, 0x44, 0x5c, 0x2d, 0x11, 0x7c, 0xa5, 0xbc, 0x11, - 0x5d, 0xe2, 0x62, 0x68, 0xb6, 0xc4, 0xc5, 0xc3, 0xb5, 0x5e, 0x8f, 0xc5, 0x20, 0x29, 0xb7, 0xd3, - 0x9f, 0x1f, 0x90, 0xf2, 0xf3, 0xf3, 0x88, 0x25, 0x92, 0x1e, 0x4c, 0xa3, 0x54, 0xa3, 0xf3, 0x37, - 0x8d, 0x86, 0xcd, 0xdf, 0xd4, 0x40, 0x9c, 0x95, 0xd4, 0x9a, 0x9b, 0xca, 0xec, 0x1e, 0x07, 0x87, - 0xd0, 0x14, 0x49, 0xab, 0xd6, 0x79, 0x3b, 0xfe, 0x9d, 0x31, 0xa5, 0xd6, 0xe7, 0x8c, 0xec, 0x1f, - 0x83, 0xe2, 0xdf, 0x25, 0xab, 0xa7, 0x38, 0x01, 0x95, 0xb9, 0xe8, 0xea, 0x9a, 0x20, 0x60, 0xab, - 0x6b, 0xd2, 0x75, 0xb8, 0x90, 0xfc, 0xbc, 0xa3, 0x32, 0x1f, 0xbb, 0x12, 0x8f, 0xe1, 0xd9, 0xea, - 0x94, 0xf8, 0x24, 0x24, 0x49, 0xff, 0x02, 0xa0, 0xb2, 0x10, 0xdb, 0xaf, 0x53, 0x68, 0x60, 0xbf, - 0x4e, 0xfb, 0x7a, 0xe0, 0xe7, 0xfa, 0x7e, 0xc8, 0x51, 0x79, 0x13, 0xc4, 0x56, 0xfa, 0x89, 0x15, - 0x64, 0x8b, 0x03, 0xa4, 0xef, 0xa7, 0x20, 0xef, 0xa2, 0xd3, 0x77, 0x76, 0xd7, 0xde, 0x5e, 0x0a, - 0xe2, 0xb7, 0x56, 0x1d, 0x6a, 0x6b, 0x0e, 0x55, 0x16, 0xa3, 0xb6, 0x7a, 0x2a, 0x11, 0xb3, 0xd5, - 0x53, 0x11, 0x49, 0xb1, 0xfe, 0x5c, 0xa8, 0xef, 0x25, 0x36, 0x9c, 0x11, 0xe9, 0xdc, 0x6c, 0x75, - 0x8a, 0x22, 0x98, 0x82, 0x96, 0x2c, 0x73, 0x13, 0x3c, 0x15, 0x9f, 0x89, 0xae, 0x4e, 0xfd, 0x29, - 0xd9, 0xea, 0xd4, 0x1f, 0xcb, 0x86, 0x7a, 0x14, 0xcb, 0xe7, 0xe0, 0x5b, 0xd1, 0xa1, 0x9e, 0x42, - 0xc2, 0x86, 0x7a, 0x0a, 0x38, 0x29, 0x90, 0x50, 0x97, 0x7a, 0xca, 0xd2, 0x5e, 0x02, 0x81, 0x24, - 0x29, 0x10, 0xc0, 0x49, 0x81, 0x0b, 0xd4, 0x6b, 0x6e, 0x29, 0x77, 0xf6, 0x12, 0x08, 0x24, 0x49, - 0x81, 0x00, 0x66, 0x87, 0xcd, 0x28, 0x78, 0xb6, 0xd3, 0xba, 0xef, 0xf7, 0xd9, 0x72, 0xf4, 0xb0, - 0xd9, 0x97, 0x90, 0x1d, 0x36, 0xfb, 0x22, 0xf1, 0x97, 0x1e, 0xd9, 0xc5, 0xad, 0xac, 0x40, 0x83, - 0x53, 0xa1, 0x5d, 0xf0, 0x28, 0x5c, 0x8b, 0x03, 0xe4, 0x51, 0x5d, 0xe8, 0x2f, 0x06, 0xde, 0x6b, - 0x65, 0x15, 0x9a, 0x1a, 0x0b, 0x7c, 0x15, 0x1c, 0xbc, 0x38, 0x40, 0x7c, 0x8a, 0xd9, 0x22, 0xca, - 0xc3, 0x17, 0x34, 0xd4, 0x5f, 0xcb, 0xa0, 0xe1, 0x35, 0xcf, 0xa1, 0x5a, 0x5b, 0x44, 0x76, 0x9d, - 0x43, 0x25, 0x6e, 0x13, 0x8b, 0xaf, 0x43, 0x0d, 0x91, 0xe0, 0x37, 0xbe, 0x8c, 0x46, 0x97, 0x34, - 0xd7, 0x03, 0x4e, 0xe9, 0xb3, 0xb7, 0x24, 0x06, 0xc5, 0x4b, 0x9c, 0x8e, 0xf3, 0xc1, 0x05, 0x50, - 0xee, 0xa1, 0x17, 0x40, 0xa5, 0x0f, 0xba, 0x95, 0x01, 0xb8, 0xe6, 0x89, 0xf1, 0xaa, 0xbd, 0x0c, - 0x4a, 0x58, 0xeb, 0x8f, 0xef, 0x9d, 0x5f, 0x41, 0x63, 0xb1, 0x4b, 0x47, 0xe1, 0xd4, 0x7e, 0xc4, - 0x3b, 0xc9, 0x38, 0x37, 0xbe, 0x84, 0x72, 0x77, 0xeb, 0x73, 0x72, 0x45, 0xfb, 0x4e, 0x24, 0x61, - 0x8d, 0x61, 0xf1, 0x0b, 0x81, 0xc7, 0xf5, 0x2e, 0x59, 0x12, 0x97, 0x8d, 0xf0, 0xd5, 0xad, 0x8e, - 0xd3, 0x22, 0x12, 0x4a, 0xfd, 0x4e, 0x39, 0xbc, 0x70, 0xc1, 0x97, 0xc5, 0x7d, 0xab, 0x54, 0xe1, - 0x3f, 0x96, 0xdd, 0xc8, 0xef, 0x57, 0x3f, 0x8d, 0x86, 0xeb, 0x6d, 0x9b, 0x3a, 0xae, 0x65, 0x42, - 0xed, 0xed, 0x6c, 0x78, 0x7b, 0x60, 0x48, 0x70, 0x39, 0x42, 0x52, 0xa6, 0x0f, 0x0b, 0x87, 0xe7, - 0x1e, 0x5a, 0x38, 0xfc, 0x2a, 0xca, 0xdf, 0x75, 0xf9, 0x77, 0xe7, 0x03, 0xd2, 0x4e, 0xec, 0x2b, - 0x5b, 0x9c, 0x02, 0x5f, 0x43, 0x05, 0xb8, 0x42, 0x70, 0x95, 0x3c, 0xd0, 0x42, 0xd6, 0x70, 0x0b, - 0x20, 0x72, 0x19, 0x0d, 0x4e, 0x83, 0xdf, 0x42, 0xe3, 0x61, 0x4d, 0x0e, 0x28, 0x79, 0xe2, 0x87, - 0x7a, 0x42, 0xa6, 0xa1, 0xf4, 0xed, 0x79, 0xa8, 0x95, 0x22, 0x8b, 0x48, 0x30, 0xe2, 0x45, 0x34, - 0x16, 0xc2, 0x98, 0x8a, 0xfc, 0x10, 0xf3, 0x0b, 0xbd, 0x6e, 0xe5, 0x9c, 0x24, 0x8b, 0xa9, 0x53, - 0x16, 0x15, 0x67, 0xc3, 0xf5, 0xf0, 0xe3, 0x09, 0xa5, 0x87, 0x8e, 0xe1, 0x53, 0xe2, 0x12, 0xb3, - 0x28, 0x3e, 0x9e, 0x10, 0xfd, 0x64, 0xc2, 0x02, 0x1a, 0x25, 0x56, 0xc7, 0xa3, 0xeb, 0x96, 0x5f, - 0xe2, 0x96, 0xc7, 0x20, 0xc2, 0x33, 0x39, 0x0c, 0xd3, 0xf0, 0x2c, 0x3f, 0x51, 0x53, 0x4e, 0x28, - 0x8d, 0x72, 0xe1, 0xe5, 0xb4, 0x6a, 0xb9, 0x52, 0xfa, 0xa4, 0xf4, 0x7a, 0x49, 0x61, 0x29, 0xe5, - 0x71, 0x7f, 0x36, 0x83, 0x0a, 0xeb, 0x8e, 0x66, 0x78, 0xae, 0x70, 0xe8, 0x9f, 0x9e, 0xda, 0x71, - 0x34, 0x9b, 0x8d, 0x8f, 0x29, 0xb8, 0xcb, 0x7c, 0x47, 0x6b, 0x75, 0xa8, 0x3b, 0xfb, 0x2e, 0x7b, - 0xbb, 0xbf, 0xef, 0x56, 0x3e, 0xb5, 0x09, 0x9e, 0xac, 0xa9, 0xa6, 0xd5, 0x9e, 0xde, 0x74, 0xb4, - 0x6d, 0x83, 0x97, 0x4e, 0xd7, 0x5a, 0xd3, 0x1e, 0x6d, 0x51, 0xdb, 0x72, 0xbc, 0x69, 0xcd, 0x36, - 0xa6, 0xbd, 0x5d, 0x9b, 0xba, 0xd3, 0x81, 0x24, 0xde, 0x02, 0x1b, 0x02, 0x1e, 0xfc, 0x27, 0x0f, - 0x01, 0x8e, 0xc3, 0xcb, 0x08, 0x89, 0x57, 0xad, 0xda, 0xb6, 0xb8, 0x1d, 0x90, 0x5c, 0x9f, 0x3e, - 0x86, 0x0f, 0xec, 0x40, 0x61, 0x9a, 0x2d, 0x17, 0xce, 0x91, 0x24, 0xb0, 0x51, 0xb0, 0x2e, 0x9e, - 0xc8, 0x57, 0xd3, 0x48, 0xa8, 0x71, 0xff, 0x61, 0x53, 0x94, 0x14, 0x67, 0xc3, 0x1b, 0x68, 0x4c, - 0xc8, 0x0d, 0x62, 0x0b, 0x47, 0xa3, 0x8b, 0x46, 0x0c, 0xcd, 0x07, 0x6d, 0xf0, 0x8c, 0xba, 0x00, - 0xcb, 0x6d, 0xc4, 0x38, 0xf0, 0x6c, 0x98, 0xb3, 0x04, 0x55, 0x7a, 0x94, 0x31, 0x18, 0xb1, 0x50, - 0x44, 0xde, 0xe7, 0xe7, 0xc5, 0x7d, 0xe4, 0x32, 0x32, 0x11, 0x16, 0x59, 0x06, 0x1f, 0xf5, 0xe3, - 0x29, 0x32, 0xe2, 0x63, 0x3e, 0xca, 0x82, 0x6b, 0x68, 0x24, 0x70, 0x4e, 0xdc, 0x65, 0x2b, 0xdb, - 0x44, 0x58, 0xcf, 0x26, 0x16, 0xb6, 0x28, 0x0b, 0x89, 0xf0, 0xe0, 0x1b, 0xa8, 0xc4, 0xdd, 0xfb, - 0x75, 0x7e, 0x1f, 0xe1, 0x5f, 0x39, 0x03, 0xac, 0x61, 0xc8, 0x3d, 0x16, 0x10, 0xe2, 0xd7, 0x51, - 0xb9, 0xfa, 0xee, 0x1a, 0x5b, 0x67, 0xaa, 0x64, 0xd9, 0x55, 0x4e, 0x85, 0x81, 0xde, 0x90, 0xca, - 0x6c, 0xb5, 0x68, 0x43, 0x73, 0x22, 0x8b, 0x87, 0x4c, 0x8f, 0xe7, 0x51, 0xec, 0xdb, 0xfc, 0xca, - 0x64, 0xf8, 0xd5, 0x45, 0x0d, 0x30, 0x0d, 0x51, 0xcb, 0x29, 0x92, 0xaf, 0x1d, 0x65, 0x62, 0xa3, - 0x66, 0xce, 0x70, 0xb5, 0x56, 0xcb, 0xda, 0x21, 0xd4, 0x70, 0xdd, 0x0e, 0x85, 0xcb, 0x8c, 0x12, - 0x1f, 0x35, 0xba, 0x40, 0x35, 0x1c, 0x8e, 0x8b, 0x64, 0xd3, 0x47, 0xd9, 0xd4, 0xff, 0xcc, 0xc8, - 0x03, 0x3a, 0xa8, 0xcf, 0x9b, 0x49, 0xad, 0xcf, 0x7b, 0x0d, 0x0d, 0x89, 0x6d, 0x20, 0x88, 0x14, - 0x85, 0x7c, 0x18, 0x3f, 0x1e, 0xc2, 0xd0, 0x49, 0x48, 0x00, 0xb9, 0x08, 0x61, 0x49, 0x8d, 0x9c, - 0x94, 0x8b, 0x10, 0x96, 0xd4, 0x88, 0x14, 0xd4, 0x98, 0x41, 0x65, 0x31, 0x98, 0xa5, 0x6c, 0x7b, - 0x88, 0x69, 0xf0, 0x33, 0xc3, 0x79, 0xc8, 0x84, 0x44, 0x84, 0x5f, 0x45, 0x28, 0xd4, 0xaf, 0xd8, - 0xb4, 0x60, 0xee, 0xc9, 0xdd, 0x21, 0xcf, 0xbd, 0x90, 0x5a, 0xfd, 0xbb, 0x4c, 0x62, 0xca, 0xb0, - 0x67, 0x10, 0xd1, 0x37, 0x92, 0x1e, 0xe0, 0x19, 0x44, 0xac, 0x8e, 0x78, 0x06, 0x89, 0x08, 0x5f, - 0x41, 0xa5, 0x58, 0x25, 0x02, 0x88, 0x36, 0x08, 0xca, 0x10, 0x04, 0x58, 0x3c, 0x83, 0x4a, 0x6c, - 0x00, 0x33, 0x11, 0x42, 0x21, 0x50, 0xe3, 0xa8, 0x23, 0x60, 0xf2, 0x88, 0xf3, 0xe9, 0x18, 0x4f, - 0x24, 0x58, 0x58, 0xf0, 0xa4, 0x4c, 0xd7, 0x30, 0x38, 0xf8, 0xf7, 0xb2, 0x7d, 0x4e, 0x17, 0x4f, - 0x6f, 0x5a, 0x47, 0x90, 0x62, 0x97, 0xef, 0x93, 0x62, 0xf7, 0xef, 0xd9, 0x3e, 0x47, 0xa7, 0xa7, - 0x3a, 0x15, 0x26, 0x50, 0x46, 0x34, 0x15, 0x26, 0xcc, 0x42, 0x32, 0x74, 0x22, 0x13, 0xc5, 0x92, - 0xe6, 0x0a, 0x0f, 0x4d, 0x9a, 0xfb, 0xad, 0xdc, 0x5e, 0x47, 0xcb, 0x13, 0xdd, 0xef, 0x47, 0xf7, - 0x33, 0xa8, 0x1c, 0x68, 0x56, 0x14, 0xc2, 0x19, 0x09, 0x82, 0xc2, 0x38, 0x18, 0x78, 0x24, 0x22, - 0x7c, 0x95, 0x3f, 0x2b, 0x7c, 0x53, 0xbc, 0x08, 0x0c, 0x50, 0x2b, 0x86, 0x3d, 0x5b, 0xc3, 0x35, - 0xde, 0xa7, 0x24, 0x40, 0xab, 0x7f, 0x9a, 0x4d, 0x3d, 0x9f, 0x9f, 0xf4, 0xd1, 0x3e, 0xfa, 0x28, - 0x45, 0x89, 0xdc, 0xb3, 0x70, 0xa2, 0xc4, 0x7d, 0x28, 0xf1, 0x27, 0xd9, 0x54, 0x3f, 0xcc, 0x89, - 0x12, 0xf7, 0xb3, 0x5a, 0x5c, 0x43, 0x43, 0xc4, 0xda, 0x71, 0x6b, 0x56, 0xc7, 0xf4, 0xc4, 0x5a, - 0x01, 0x0b, 0xb5, 0x63, 0xed, 0xb8, 0x8d, 0x26, 0x83, 0x92, 0x90, 0x40, 0xfd, 0x69, 0x76, 0x0f, - 0x4f, 0xd5, 0x89, 0xe2, 0x3f, 0xc4, 0x2d, 0xf2, 0x63, 0x2f, 0xa0, 0xb2, 0xf4, 0x29, 0x59, 0x3c, - 0x8c, 0x4a, 0x2b, 0xb3, 0x6b, 0xf3, 0xe4, 0x9d, 0xf9, 0xb9, 0xf1, 0x01, 0x8c, 0x50, 0x61, 0x6e, - 0x7e, 0xb9, 0x3e, 0x3f, 0x37, 0x9e, 0x99, 0x1d, 0xff, 0xe0, 0x1f, 0x2f, 0x0c, 0x7c, 0xf0, 0xe3, - 0x0b, 0x99, 0xef, 0xff, 0xf8, 0x42, 0xe6, 0x1f, 0x7e, 0x7c, 0x21, 0xb3, 0x51, 0x00, 0x23, 0xf4, - 0xc6, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x7f, 0xeb, 0x35, 0x7a, 0x97, 0x97, 0x00, 0x00, + // 6886 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5f, 0x6c, 0x24, 0xc9, + 0x59, 0xb8, 0x67, 0xc6, 0xf3, 0xc7, 0x35, 0xf6, 0xda, 0xae, 0xf5, 0xee, 0xf6, 0xed, 0xed, 0xdd, + 0xdc, 0xf5, 0x25, 0x7b, 0xbb, 0xb9, 0x3d, 0x3b, 0xbb, 0xb7, 0x77, 0x9b, 0x5c, 0xee, 0x72, 0x3b, + 0x1e, 0xdb, 0xe7, 0xc9, 0x79, 0x6d, 0x5f, 0xd9, 0x7b, 0x17, 0x29, 0xca, 0x8d, 0xda, 0xd3, 0xb5, + 0x76, 0x67, 0x67, 0xa6, 0x3b, 0xdd, 0x3d, 0xf6, 0xfa, 0x9e, 0x7e, 0x3f, 0x40, 0x10, 0x45, 0x01, + 0xa1, 0xf0, 0xc0, 0x03, 0x0f, 0xa0, 0x00, 0x12, 0x48, 0x11, 0x01, 0x84, 0x22, 0x24, 0x78, 0x81, + 0x00, 0xba, 0x08, 0x25, 0x04, 0x82, 0x40, 0xe2, 0x61, 0x02, 0x41, 0xbc, 0x0c, 0x41, 0x8a, 0x04, + 0x12, 0x81, 0x27, 0x54, 0x5f, 0x55, 0x77, 0x57, 0x75, 0xf7, 0x78, 0xd7, 0x6b, 0x1f, 0xc6, 0xbb, + 0x7e, 0xb2, 0xe7, 0xfb, 0x57, 0xdd, 0x5f, 0xfd, 0xfb, 0xea, 0xab, 0xef, 0xfb, 0x1a, 0x8d, 0xd2, + 0x6d, 0xda, 0xf1, 0xbd, 0x69, 0xc7, 0xb5, 0x7d, 0x1b, 0x17, 0xf8, 0xaf, 0xf3, 0x53, 0x9b, 0xf6, + 0xa6, 0x0d, 0xa0, 0x19, 0xf6, 0x1f, 0xc7, 0x9e, 0xaf, 0x6c, 0xda, 0xf6, 0x66, 0x8b, 0xce, 0xc0, + 0xaf, 0x8d, 0xee, 0x9d, 0x19, 0xdf, 0x6a, 0x53, 0xcf, 0x37, 0xda, 0x8e, 0x20, 0xb8, 0x10, 0x27, + 0xf0, 0x7c, 0xb7, 0xdb, 0xf4, 0x05, 0xb6, 0xb6, 0x69, 0xf9, 0x5b, 0xdd, 0x8d, 0xe9, 0xa6, 0xdd, + 0x9e, 0xd9, 0x74, 0x8d, 0x6d, 0xcb, 0x37, 0x7c, 0xcb, 0xee, 0x18, 0xad, 0x19, 0x9f, 0xb6, 0xa8, + 0x63, 0xbb, 0xfe, 0x8c, 0xe1, 0x58, 0x33, 0xfe, 0xae, 0x43, 0xbd, 0x99, 0x1d, 0xd7, 0x70, 0x1c, + 0xea, 0x46, 0xff, 0x70, 0x21, 0xfa, 0x57, 0xb3, 0xa8, 0x74, 0x8b, 0xfa, 0x86, 0x69, 0xf8, 0x06, + 0xbe, 0x80, 0xf2, 0xf5, 0x8e, 0x49, 0xef, 0x69, 0x99, 0x67, 0x32, 0x97, 0x72, 0xb3, 0x85, 0x7e, + 0xaf, 0x92, 0xa5, 0x16, 0xe1, 0x40, 0xfc, 0x14, 0x1a, 0x5e, 0xdf, 0x75, 0xa8, 0x96, 0x7d, 0x26, + 0x73, 0x69, 0x64, 0x76, 0xa4, 0xdf, 0xab, 0xe4, 0xe1, 0xf5, 0x08, 0x80, 0xf1, 0xb3, 0x28, 0x5b, + 0x9f, 0xd3, 0x72, 0x80, 0x9c, 0xec, 0xf7, 0x2a, 0x63, 0x5d, 0xcb, 0xbc, 0x62, 0xb7, 0x2d, 0x9f, + 0xb6, 0x1d, 0x7f, 0x97, 0x64, 0xeb, 0x73, 0xf8, 0x22, 0x1a, 0xae, 0xd9, 0x26, 0xd5, 0x86, 0x81, + 0x08, 0xf7, 0x7b, 0x95, 0x53, 0x4d, 0xdb, 0xa4, 0x12, 0x15, 0xe0, 0xf1, 0x4d, 0x34, 0xbc, 0x6e, + 0xb5, 0xa9, 0x96, 0x7f, 0x26, 0x73, 0xa9, 0x7c, 0xed, 0xfc, 0x34, 0x57, 0xc3, 0x74, 0xa0, 0x86, + 0xe9, 0xf5, 0x40, 0x4f, 0xb3, 0x13, 0x1f, 0xf4, 0x2a, 0x43, 0xfd, 0x5e, 0x65, 0x98, 0xa9, 0xee, + 0x17, 0x7f, 0x50, 0xc9, 0x10, 0xe0, 0xc4, 0xaf, 0xa1, 0x72, 0xad, 0xd5, 0xf5, 0x7c, 0xea, 0x2e, + 0x1b, 0x6d, 0xaa, 0x15, 0xa0, 0xc1, 0xf3, 0xfd, 0x5e, 0xe5, 0x6c, 0x93, 0x83, 0x1b, 0x1d, 0xa3, + 0x2d, 0x37, 0x2c, 0x93, 0xeb, 0x5f, 0x40, 0xe3, 0x6b, 0xd4, 0xf3, 0x2c, 0xbb, 0x13, 0xaa, 0xe6, + 0xa3, 0x68, 0x44, 0x80, 0xea, 0x73, 0xa0, 0x9e, 0x91, 0xd9, 0x62, 0xbf, 0x57, 0xc9, 0x79, 0x96, + 0x49, 0x22, 0x0c, 0xfe, 0x38, 0x2a, 0xbe, 0x6b, 0xf9, 0x5b, 0xb7, 0x16, 0xaa, 0x42, 0x4d, 0x67, + 0xfb, 0xbd, 0x0a, 0xde, 0xb1, 0xfc, 0xad, 0x46, 0xfb, 0x8e, 0x21, 0xb5, 0x17, 0x90, 0xe9, 0xbf, + 0x91, 0x45, 0xa3, 0xb7, 0x3d, 0xea, 0x86, 0x2d, 0x5d, 0x44, 0xc3, 0xec, 0xb7, 0x68, 0x04, 0x94, + 0xd4, 0xf5, 0xa8, 0x2b, 0x2b, 0x89, 0xe1, 0xf1, 0x65, 0x94, 0x5f, 0xb2, 0x37, 0xad, 0x8e, 0x68, + 0xe8, 0x74, 0xbf, 0x57, 0x19, 0x6f, 0x31, 0x80, 0x44, 0xc9, 0x29, 0xf0, 0xa7, 0xd1, 0x68, 0xbd, + 0xcd, 0x3a, 0xdd, 0xee, 0x18, 0xbe, 0xed, 0x8a, 0x4e, 0x02, 0x75, 0x58, 0x12, 0x5c, 0x62, 0x54, + 0xe8, 0xf1, 0xab, 0x08, 0x55, 0xdf, 0x5d, 0x23, 0x76, 0x8b, 0x56, 0xc9, 0xb2, 0xe8, 0x3d, 0xe0, + 0x36, 0x76, 0xbc, 0x86, 0x6b, 0xb7, 0x68, 0xc3, 0x70, 0xe5, 0x66, 0x25, 0x6a, 0x3c, 0x8f, 0x4e, + 0x55, 0x9b, 0x4d, 0xea, 0x79, 0x84, 0x7e, 0xb1, 0x4b, 0x3d, 0xdf, 0xd3, 0xf2, 0xcf, 0xe4, 0x2e, + 0x8d, 0xcc, 0x3e, 0xd5, 0xef, 0x55, 0x9e, 0x30, 0x00, 0xd3, 0x70, 0x05, 0x4a, 0x12, 0x11, 0x63, + 0xd2, 0x7f, 0x37, 0x87, 0x4e, 0xad, 0x51, 0x77, 0x5b, 0x52, 0x54, 0x95, 0xf5, 0x12, 0x83, 0xb0, + 0x3e, 0xf3, 0x1c, 0xa3, 0x49, 0x85, 0xce, 0xce, 0xf5, 0x7b, 0x95, 0xd3, 0x9d, 0x00, 0x28, 0x09, + 0x8d, 0xd3, 0xe3, 0xcb, 0xa8, 0xc4, 0x41, 0xf5, 0x39, 0xa1, 0xc6, 0xb1, 0x7e, 0xaf, 0x32, 0xe2, + 0x01, 0xac, 0x61, 0x99, 0x24, 0x44, 0xb3, 0xf7, 0xe0, 0xff, 0x2f, 0xda, 0x9e, 0xcf, 0x84, 0x0b, + 0x2d, 0xc2, 0x7b, 0x08, 0x86, 0x2d, 0x81, 0x92, 0xdf, 0x43, 0x65, 0xc2, 0x9f, 0x44, 0x88, 0x43, + 0xaa, 0xa6, 0xe9, 0x0a, 0x55, 0x3e, 0xd1, 0xef, 0x55, 0xce, 0x08, 0x11, 0x86, 0x69, 0xca, 0xfd, + 0x20, 0x11, 0xe3, 0x36, 0x1a, 0xe5, 0xbf, 0x96, 0x8c, 0x0d, 0xda, 0xe2, 0x7a, 0x2c, 0x5f, 0xbb, + 0x34, 0x2d, 0x56, 0x1c, 0x55, 0x3b, 0xd3, 0x32, 0xe9, 0x7c, 0xc7, 0x77, 0x77, 0x67, 0x2b, 0x62, + 0xae, 0x9c, 0x13, 0x4d, 0xb5, 0x00, 0x27, 0x77, 0xba, 0xcc, 0x73, 0xfe, 0x0d, 0x34, 0x99, 0x90, + 0x81, 0x27, 0x50, 0xee, 0x2e, 0xdd, 0xe5, 0x7a, 0x26, 0xec, 0x5f, 0x3c, 0x85, 0xf2, 0xdb, 0x46, + 0xab, 0x2b, 0x96, 0x05, 0xc2, 0x7f, 0xbc, 0x9a, 0xfd, 0x44, 0x46, 0xff, 0xc3, 0x0c, 0xc2, 0x35, + 0xbb, 0xd3, 0xa1, 0x4d, 0x5f, 0x9e, 0x49, 0xaf, 0xa0, 0x91, 0x25, 0xbb, 0x69, 0xb4, 0x40, 0x01, + 0xbc, 0xc3, 0xb4, 0x7e, 0xaf, 0x32, 0xc5, 0xde, 0x7c, 0xba, 0xc5, 0x30, 0xd2, 0x23, 0x45, 0xa4, + 0x4c, 0x73, 0x84, 0xb6, 0x6d, 0x9f, 0x02, 0x63, 0x36, 0xd2, 0x1c, 0x30, 0xba, 0x80, 0x92, 0x35, + 0x17, 0x11, 0xe3, 0x19, 0x54, 0x5a, 0x65, 0x6b, 0x47, 0xd3, 0x6e, 0x89, 0x5e, 0x83, 0xd9, 0x02, + 0xeb, 0x89, 0xc4, 0x12, 0x12, 0xe9, 0x3f, 0x95, 0x45, 0x4f, 0xbc, 0xd5, 0xdd, 0xa0, 0x6e, 0x87, + 0xfa, 0xd4, 0x13, 0x4b, 0x43, 0xf8, 0x06, 0xcb, 0x68, 0x32, 0x81, 0x14, 0x6f, 0xf2, 0x4c, 0xbf, + 0x57, 0xb9, 0x70, 0x37, 0x44, 0x36, 0xc4, 0x6a, 0x23, 0x35, 0x92, 0x64, 0xc5, 0x8b, 0x68, 0x3c, + 0x02, 0xb2, 0xb9, 0xed, 0x69, 0x59, 0x98, 0x23, 0x4f, 0xf7, 0x7b, 0x95, 0xf3, 0x92, 0x34, 0xb6, + 0x0e, 0xc8, 0x1d, 0x16, 0x67, 0xc3, 0x6f, 0xa1, 0x89, 0x08, 0xf4, 0xa6, 0x6b, 0x77, 0x1d, 0x4f, + 0xcb, 0x81, 0xa8, 0x4a, 0xbf, 0x57, 0x79, 0x52, 0x12, 0xb5, 0x09, 0x48, 0x49, 0x56, 0x82, 0x51, + 0xff, 0x97, 0x1c, 0x3a, 0x13, 0x01, 0x57, 0x6d, 0x33, 0x54, 0xc0, 0x8a, 0xac, 0x80, 0x55, 0xdb, + 0x84, 0x35, 0x96, 0x2b, 0xe0, 0xd9, 0x7e, 0xaf, 0xf2, 0x94, 0xd4, 0x8e, 0x63, 0x9b, 0xf1, 0xa5, + 0x36, 0xc9, 0x8b, 0xdf, 0x43, 0x67, 0x13, 0x40, 0x3e, 0xa3, 0x79, 0x3f, 0x5f, 0xec, 0xf7, 0x2a, + 0x7a, 0x8a, 0xd4, 0xf8, 0x04, 0x1f, 0x20, 0x05, 0x1b, 0xe8, 0x9c, 0xa4, 0x76, 0xbb, 0xe3, 0x1b, + 0x56, 0x47, 0x6c, 0x0d, 0x7c, 0x3c, 0x3c, 0xdf, 0xef, 0x55, 0x9e, 0x93, 0xfb, 0x2d, 0xa0, 0x89, + 0x3f, 0xfc, 0x20, 0x39, 0xd8, 0x44, 0x5a, 0x0a, 0xaa, 0xde, 0x36, 0x36, 0x83, 0xfd, 0xee, 0x52, + 0xbf, 0x57, 0xf9, 0x48, 0x6a, 0x1b, 0x16, 0xa3, 0x92, 0x1a, 0x19, 0x28, 0x09, 0x13, 0x84, 0x23, + 0xdc, 0xb2, 0x6d, 0x52, 0x78, 0x87, 0x3c, 0xc8, 0xd7, 0xfb, 0xbd, 0xca, 0xd3, 0x92, 0xfc, 0x8e, + 0x6d, 0xd2, 0xf8, 0xe3, 0xa7, 0x70, 0xeb, 0x3f, 0xc8, 0xb3, 0x85, 0x05, 0x76, 0xb0, 0x35, 0xdf, + 0x70, 0x7d, 0xfc, 0x6a, 0x64, 0x12, 0x40, 0xaf, 0x96, 0xaf, 0x4d, 0x04, 0x8b, 0x4c, 0x00, 0x9f, + 0x1d, 0x65, 0x8b, 0xc9, 0xf7, 0x7a, 0x95, 0x4c, 0xbf, 0x57, 0x19, 0x22, 0x25, 0x69, 0x76, 0xf3, + 0xdd, 0x2b, 0x0b, 0x7c, 0x53, 0x01, 0x9f, 0xbc, 0xc3, 0xc5, 0x78, 0xf9, 0x6e, 0xf6, 0x06, 0x2a, + 0x8a, 0x67, 0x80, 0x1e, 0x29, 0x5f, 0x3b, 0x17, 0xad, 0x6b, 0xca, 0x4e, 0x1c, 0xe3, 0x0e, 0xb8, + 0xf0, 0x6b, 0xa8, 0xc0, 0x97, 0x2b, 0xd0, 0x76, 0xf9, 0xda, 0xd9, 0xf4, 0x75, 0x31, 0xc6, 0x2e, + 0x78, 0xf0, 0x22, 0x42, 0xd1, 0x52, 0x15, 0xda, 0x1d, 0x42, 0x42, 0x72, 0x11, 0x8b, 0x49, 0x91, + 0x78, 0xf1, 0x2b, 0x68, 0x74, 0x9d, 0xba, 0x6d, 0xab, 0x63, 0xb4, 0xd6, 0xac, 0xf7, 0x03, 0xd3, + 0x03, 0xb6, 0x71, 0xcf, 0x7a, 0x5f, 0xee, 0x0b, 0x85, 0x0e, 0x7f, 0x3e, 0x6d, 0x51, 0x29, 0xc2, + 0x83, 0x3c, 0x1b, 0x3c, 0xc8, 0xc0, 0x25, 0x29, 0xf6, 0x3c, 0x29, 0x6b, 0xcc, 0xdb, 0x68, 0x4c, + 0x99, 0x1b, 0x5a, 0x09, 0x44, 0x3f, 0x95, 0x14, 0x2d, 0x4d, 0xf4, 0x98, 0x58, 0x55, 0x02, 0xdb, + 0x11, 0xeb, 0x1d, 0xcb, 0xb7, 0x8c, 0x56, 0xcd, 0x6e, 0xb7, 0x8d, 0x8e, 0xa9, 0x8d, 0x44, 0x3b, + 0xbb, 0xc5, 0x31, 0x8d, 0x26, 0x47, 0xc9, 0x3b, 0xa2, 0xca, 0xc4, 0xd6, 0x2c, 0xd1, 0x87, 0x84, + 0x36, 0x6d, 0xd7, 0xb4, 0x3a, 0x9b, 0x1a, 0x02, 0xa5, 0xc1, 0x9a, 0xe5, 0x71, 0x5c, 0xc3, 0x0d, + 0x90, 0xf2, 0x9a, 0x15, 0x67, 0xfc, 0xcc, 0x70, 0xa9, 0x3c, 0x31, 0x9a, 0x30, 0x1e, 0x7e, 0x3b, + 0x87, 0xca, 0x82, 0xf4, 0x33, 0xb6, 0xd5, 0x39, 0x19, 0xe0, 0x07, 0x19, 0xe0, 0xa9, 0x03, 0xb5, + 0x70, 0x58, 0x03, 0x55, 0xff, 0x4a, 0x36, 0x5c, 0x8d, 0x56, 0x5d, 0xab, 0x73, 0xb0, 0xd5, 0xe8, + 0x22, 0x42, 0xb5, 0xad, 0x6e, 0xe7, 0x2e, 0x3f, 0xd5, 0x64, 0xa3, 0x53, 0x4d, 0xd3, 0x22, 0x12, + 0x86, 0x1d, 0x6d, 0xe6, 0x98, 0x7c, 0xd6, 0x33, 0xa3, 0xb3, 0x23, 0x1f, 0x70, 0x49, 0x99, 0x17, + 0x09, 0x80, 0x71, 0x05, 0xe5, 0x67, 0x77, 0x7d, 0xea, 0x81, 0xe6, 0x73, 0xfc, 0xe8, 0xb3, 0xc1, + 0x00, 0x84, 0xc3, 0xf1, 0x75, 0x34, 0x39, 0x47, 0x5b, 0xc6, 0xee, 0x2d, 0xab, 0xd5, 0xb2, 0x3c, + 0xda, 0xb4, 0x3b, 0xa6, 0x07, 0x4a, 0x16, 0xcd, 0xb5, 0x3d, 0x92, 0x24, 0xc0, 0x3a, 0x2a, 0xac, + 0xdc, 0xb9, 0xe3, 0x51, 0x1f, 0xd4, 0x97, 0x9b, 0x45, 0xfd, 0x5e, 0xa5, 0x60, 0x03, 0x84, 0x08, + 0x8c, 0xfe, 0x8d, 0x0c, 0x9a, 0x98, 0xa3, 0xde, 0x5d, 0xdf, 0x76, 0xc2, 0x51, 0x7e, 0x20, 0x95, + 0x5c, 0x46, 0xc5, 0x5b, 0xd4, 0xf3, 0xd8, 0xb6, 0x94, 0x85, 0xb7, 0x1d, 0x17, 0x6f, 0x5b, 0x6c, + 0x73, 0x30, 0x09, 0xf0, 0xe9, 0x6f, 0x95, 0xbb, 0xcf, 0x5b, 0xe9, 0x3f, 0xce, 0xa2, 0x73, 0xe2, + 0x89, 0x6b, 0x2d, 0xcb, 0xd9, 0xb0, 0x0d, 0xd7, 0x24, 0xb4, 0x49, 0xad, 0x6d, 0x7a, 0x3c, 0x27, + 0x9e, 0x3a, 0x75, 0x86, 0x0f, 0x30, 0x75, 0xae, 0xa1, 0xb2, 0xd0, 0x0c, 0xd8, 0xb0, 0x7c, 0xdb, + 0x9e, 0xe8, 0xf7, 0x2a, 0xa3, 0x26, 0x07, 0x83, 0xf9, 0x4f, 0x64, 0x22, 0x36, 0x48, 0x96, 0x68, + 0x67, 0xd3, 0xdf, 0x82, 0x41, 0x92, 0xe7, 0x83, 0xa4, 0x05, 0x10, 0x22, 0x30, 0xfa, 0xbf, 0x65, + 0xd1, 0x54, 0x5c, 0xe5, 0x6b, 0xb4, 0x63, 0x9e, 0xe8, 0xfb, 0xc3, 0xd1, 0xf7, 0xdf, 0x64, 0xd1, + 0x58, 0xb8, 0xf5, 0x7c, 0x81, 0x36, 0x8f, 0xc6, 0x64, 0x8a, 0x36, 0x84, 0xdc, 0x81, 0x37, 0x84, + 0x83, 0x68, 0x59, 0x47, 0x05, 0x42, 0x0d, 0x4f, 0x6c, 0x2b, 0x23, 0x5c, 0x63, 0x2e, 0x40, 0x88, + 0xc0, 0xe0, 0x67, 0x51, 0xf1, 0x96, 0x71, 0xcf, 0x6a, 0x77, 0xdb, 0x62, 0xad, 0x03, 0xe7, 0x49, + 0xdb, 0xb8, 0x47, 0x02, 0xb8, 0xfe, 0xb7, 0x19, 0x76, 0xc2, 0x06, 0xa5, 0x0a, 0xe1, 0x07, 0xd2, + 0x6a, 0xa4, 0x9d, 0xec, 0x81, 0xb5, 0x93, 0x7b, 0x78, 0xed, 0xe8, 0xdf, 0x18, 0x66, 0xea, 0x61, + 0xa6, 0xdf, 0xe3, 0x3e, 0x1b, 0xa3, 0x1e, 0xc9, 0x3f, 0x44, 0x8f, 0x3c, 0x36, 0x76, 0xb5, 0xfe, + 0x9f, 0x45, 0x84, 0x84, 0xf6, 0xe7, 0x4f, 0xd6, 0xf0, 0x83, 0x8d, 0x9a, 0x39, 0x34, 0x39, 0xdf, + 0xd9, 0x32, 0x3a, 0x4d, 0x6a, 0x46, 0xa7, 0x0b, 0x36, 0x74, 0x4a, 0xdc, 0x33, 0x4b, 0x05, 0x32, + 0x3a, 0x5e, 0x90, 0x24, 0x03, 0xbe, 0x8a, 0xca, 0xf5, 0x8e, 0x4f, 0x5d, 0xa3, 0xe9, 0x5b, 0xdb, + 0x14, 0x46, 0x4f, 0x69, 0x76, 0xbc, 0xdf, 0xab, 0x94, 0xad, 0x08, 0x4c, 0x64, 0x1a, 0x7c, 0x1d, + 0x8d, 0xae, 0x1a, 0xae, 0x6f, 0x35, 0x2d, 0xc7, 0xe8, 0xf8, 0x9e, 0x56, 0x82, 0xa3, 0x11, 0xec, + 0x3d, 0x8e, 0x04, 0x27, 0x0a, 0x15, 0xfe, 0x3c, 0x1a, 0x81, 0x23, 0x38, 0x78, 0xbf, 0x47, 0xee, + 0xeb, 0xfd, 0x7e, 0x2e, 0xf2, 0xe8, 0xf1, 0x43, 0x92, 0xc7, 0x98, 0xa3, 0xa9, 0x00, 0x0e, 0xf1, + 0x48, 0x22, 0xfe, 0x2c, 0x2a, 0xce, 0x77, 0x4c, 0x10, 0x8e, 0xee, 0x2b, 0x5c, 0x17, 0xc2, 0xcf, + 0x46, 0xc2, 0x6d, 0x27, 0x26, 0x3b, 0x10, 0x97, 0x3e, 0xcb, 0xca, 0x1f, 0xde, 0x2c, 0x1b, 0xfd, + 0x10, 0x4e, 0xaf, 0x63, 0x87, 0x75, 0x7a, 0x3d, 0xf5, 0x90, 0xa7, 0x57, 0xfd, 0x7d, 0x54, 0x9e, + 0x5d, 0x5d, 0x08, 0x67, 0xef, 0x13, 0x28, 0xb7, 0x2a, 0x6e, 0x1b, 0x86, 0xf9, 0x86, 0xe9, 0x58, + 0x26, 0x61, 0x30, 0x7c, 0x19, 0x95, 0x6a, 0xe0, 0xc2, 0x13, 0x8e, 0xeb, 0x61, 0xee, 0xb8, 0x6e, + 0x02, 0x0c, 0x1c, 0xd7, 0x01, 0x1a, 0x7f, 0x14, 0x15, 0x57, 0x5d, 0x7b, 0xd3, 0x35, 0xda, 0xc2, + 0xd7, 0x55, 0x66, 0xc6, 0xbe, 0xc3, 0x41, 0x24, 0xc0, 0xe9, 0xbf, 0x94, 0x41, 0x85, 0x35, 0xdf, + 0xf0, 0xbb, 0x1e, 0xe3, 0x58, 0xeb, 0xc2, 0x09, 0x1a, 0xda, 0x2e, 0x71, 0x0e, 0x8f, 0x83, 0x48, + 0x80, 0xc3, 0x97, 0x51, 0x7e, 0xde, 0x75, 0x6d, 0x57, 0xbe, 0x80, 0xa0, 0x0c, 0x20, 0x5f, 0x40, + 0x00, 0x05, 0xbe, 0x81, 0xca, 0x7c, 0xcd, 0xe1, 0x07, 0x0f, 0xfe, 0x1c, 0x67, 0xfa, 0xbd, 0xca, + 0xa4, 0x38, 0x74, 0xc8, 0x37, 0x31, 0x12, 0xa5, 0xfe, 0xad, 0x9c, 0x64, 0x14, 0x70, 0x8d, 0x3f, + 0x86, 0x87, 0xf7, 0x97, 0x50, 0x6e, 0x76, 0x75, 0x41, 0x2c, 0x80, 0xa7, 0x03, 0x56, 0x69, 0xa8, + 0xc4, 0xf8, 0x18, 0x35, 0xbe, 0x80, 0x86, 0x57, 0xd9, 0xf0, 0x29, 0xc0, 0xf0, 0x28, 0xf5, 0x7b, + 0x95, 0x61, 0x87, 0x8d, 0x1f, 0x80, 0x02, 0xd6, 0xf0, 0xb7, 0x60, 0x2d, 0x1b, 0x11, 0x58, 0xc3, + 0xdf, 0x22, 0x00, 0x65, 0xd8, 0xaa, 0xbb, 0xb9, 0x2d, 0x56, 0x2d, 0xc0, 0x1a, 0xee, 0xe6, 0x36, + 0x01, 0x28, 0x9e, 0x41, 0x88, 0x50, 0xbf, 0xeb, 0x76, 0xe0, 0x32, 0x6f, 0x04, 0xcc, 0x64, 0x58, + 0x0d, 0x5d, 0x80, 0x36, 0x9a, 0xb6, 0x49, 0x89, 0x44, 0xa2, 0xff, 0x66, 0xe4, 0x7f, 0x99, 0xb3, + 0xbc, 0xbb, 0x27, 0x5d, 0xb8, 0x8f, 0x2e, 0x34, 0xc4, 0x49, 0x24, 0xd9, 0x49, 0x15, 0x94, 0x5f, + 0x68, 0x19, 0x9b, 0x1e, 0xf4, 0x61, 0x9e, 0x7b, 0x25, 0xee, 0x30, 0x00, 0xe1, 0xf0, 0x58, 0x3f, + 0x95, 0xee, 0xdf, 0x4f, 0xbf, 0x9c, 0x0f, 0x67, 0xdb, 0x32, 0xf5, 0x77, 0x6c, 0xf7, 0xa4, 0xab, + 0x1e, 0xb4, 0xab, 0x2e, 0xa2, 0xe2, 0x9a, 0xdb, 0x84, 0x63, 0x26, 0xef, 0xad, 0xd1, 0x7e, 0xaf, + 0x52, 0xf2, 0xdc, 0x26, 0x3f, 0x62, 0x06, 0x48, 0x46, 0x37, 0xe7, 0xf9, 0x40, 0x57, 0x8c, 0xe8, + 0x4c, 0xcf, 0x17, 0x74, 0x02, 0x29, 0xe8, 0x56, 0x6d, 0xd7, 0x17, 0x1d, 0x17, 0xd2, 0x39, 0xb6, + 0xeb, 0x93, 0x00, 0x89, 0x5f, 0x40, 0x68, 0xbd, 0xb6, 0xfa, 0x0e, 0x75, 0x41, 0x5d, 0x7c, 0x2e, + 0xc2, 0x72, 0xbd, 0xcd, 0x41, 0x44, 0x42, 0xe3, 0x75, 0x34, 0xb2, 0xe2, 0x50, 0x17, 0xc2, 0x04, + 0xc0, 0x02, 0x38, 0x75, 0xed, 0xf9, 0x98, 0x6a, 0x45, 0xbf, 0x4f, 0x8b, 0xbf, 0x21, 0x39, 0xdf, + 0x5f, 0xec, 0xe0, 0x27, 0x89, 0x04, 0xe1, 0x1b, 0xa8, 0x50, 0xe5, 0x76, 0x5e, 0x19, 0x44, 0x86, + 0x2a, 0x9b, 0x67, 0x7f, 0x38, 0x8a, 0x1f, 0x0a, 0x0d, 0xf8, 0x9f, 0x08, 0x72, 0xfd, 0x32, 0x9a, + 0x88, 0x37, 0x83, 0xcb, 0xa8, 0x58, 0x5b, 0x59, 0x5e, 0x9e, 0xaf, 0xad, 0x4f, 0x0c, 0xe1, 0x12, + 0x1a, 0x5e, 0x9b, 0x5f, 0x9e, 0x9b, 0xc8, 0xe8, 0x5f, 0x97, 0x56, 0x10, 0x36, 0xb4, 0x4e, 0x3c, + 0xb8, 0x07, 0x72, 0x8b, 0x4c, 0x80, 0xdb, 0x72, 0xdd, 0x35, 0x3a, 0x5e, 0xdb, 0xf2, 0x7d, 0x6a, + 0x8a, 0x5d, 0x02, 0xdc, 0x7a, 0xfe, 0x3d, 0x92, 0xc0, 0xe3, 0x2b, 0x68, 0x0c, 0x60, 0xc2, 0x93, + 0x67, 0xc2, 0xe8, 0x15, 0x0c, 0xee, 0x3d, 0xa2, 0x22, 0xf5, 0xbf, 0x8c, 0x9c, 0xb8, 0x4b, 0xd4, + 0x38, 0xae, 0x8e, 0xbf, 0xff, 0x23, 0xfd, 0xa5, 0xff, 0x7a, 0x0e, 0x8d, 0xb0, 0x37, 0xe2, 0xc1, + 0x1c, 0x47, 0xa1, 0xca, 0xeb, 0x81, 0x6d, 0x28, 0x34, 0x79, 0x2a, 0xd4, 0x04, 0x40, 0x13, 0x1a, + 0xe0, 0x76, 0xe4, 0x15, 0x54, 0xb8, 0x45, 0xfd, 0x2d, 0xdb, 0x14, 0x17, 0xa0, 0x53, 0xfd, 0x5e, + 0x65, 0xa2, 0x0d, 0x10, 0xc9, 0xde, 0x13, 0x34, 0xf8, 0x2e, 0xc2, 0x75, 0x93, 0x76, 0x7c, 0xcb, + 0xdf, 0xad, 0xfa, 0xbe, 0x6b, 0x6d, 0x74, 0x7d, 0xea, 0x09, 0xbd, 0x9d, 0x4b, 0x9c, 0x53, 0xd6, + 0x20, 0x12, 0x0a, 0xee, 0x3c, 0xa7, 0x8c, 0x90, 0x3c, 0x12, 0xfb, 0xdf, 0xbd, 0x4a, 0x81, 0xd3, + 0x90, 0x14, 0xb1, 0xf8, 0x6d, 0x34, 0x72, 0x6b, 0xa1, 0x3a, 0x47, 0xb7, 0xad, 0x26, 0x15, 0x97, + 0x17, 0x4f, 0x84, 0x5a, 0x0c, 0x10, 0xa1, 0x4a, 0x20, 0x3e, 0xa1, 0x7d, 0xc7, 0x68, 0x98, 0x00, + 0x97, 0xe3, 0x13, 0x42, 0x62, 0xfd, 0x47, 0x19, 0x34, 0x41, 0xa8, 0x67, 0x77, 0xdd, 0x88, 0x13, + 0x5f, 0x44, 0xc3, 0xd2, 0xe5, 0x38, 0x78, 0x2b, 0x62, 0x37, 0xb2, 0x80, 0xc7, 0x6b, 0xa8, 0x38, + 0x7f, 0xcf, 0xb1, 0x5c, 0xea, 0x89, 0xbe, 0xd9, 0xeb, 0x64, 0xf6, 0x94, 0x38, 0x99, 0x4d, 0x52, + 0xce, 0x92, 0x38, 0x94, 0x71, 0x30, 0x7e, 0x05, 0x8d, 0xdc, 0x76, 0x4c, 0xc3, 0xa7, 0xe6, 0xec, + 0xae, 0xb0, 0xb9, 0xe1, 0x4d, 0xba, 0x1c, 0xd8, 0xd8, 0xd8, 0x95, 0xdf, 0x24, 0x24, 0xc5, 0xcf, + 0xa1, 0xdc, 0xfa, 0xfa, 0x92, 0xe8, 0x34, 0x08, 0xe5, 0xf2, 0x7d, 0x39, 0x28, 0x83, 0x61, 0xf5, + 0xaf, 0x66, 0x11, 0x62, 0x63, 0xa3, 0xe6, 0x52, 0xc3, 0x3f, 0x9a, 0x09, 0x3e, 0x8b, 0x4a, 0x81, + 0xc2, 0xc5, 0xb8, 0xd4, 0x02, 0xde, 0x78, 0x47, 0xc4, 0xdb, 0x0e, 0xf0, 0xcc, 0x88, 0x22, 0x76, + 0x0b, 0xae, 0x76, 0x72, 0x41, 0x54, 0x9b, 0xcb, 0x00, 0x84, 0xc3, 0xf1, 0x0b, 0x68, 0x44, 0x4c, + 0x45, 0x3b, 0xf0, 0x20, 0xf3, 0xa3, 0x56, 0x00, 0x24, 0x11, 0x5e, 0xff, 0xb3, 0x0c, 0x57, 0xca, + 0x1c, 0x6d, 0xd1, 0xe3, 0xab, 0x14, 0xfd, 0x4b, 0x19, 0x84, 0x99, 0xb0, 0x55, 0xc3, 0xf3, 0x76, + 0x6c, 0xd7, 0xac, 0x6d, 0x19, 0x9d, 0xcd, 0x23, 0x79, 0x1d, 0xfd, 0xcb, 0x79, 0x74, 0x5a, 0xb9, + 0xcd, 0x3d, 0xe6, 0xe3, 0xed, 0xb2, 0x3a, 0xde, 0xe0, 0xd0, 0x0c, 0xe3, 0x4d, 0x3e, 0x34, 0xf3, + 0x91, 0xf7, 0x11, 0x34, 0x22, 0xde, 0xb9, 0x3e, 0x27, 0x46, 0x1e, 0x6c, 0xb7, 0x96, 0x49, 0x22, + 0x04, 0x7e, 0x11, 0x8d, 0x8a, 0x1f, 0x6c, 0xd5, 0x0d, 0xfc, 0xa2, 0x30, 0x8e, 0x3d, 0x06, 0x20, + 0x0a, 0x1a, 0xbf, 0x8c, 0x46, 0xd8, 0xe0, 0xdc, 0x84, 0x38, 0xc0, 0x62, 0x14, 0x2e, 0x67, 0x06, + 0x40, 0x79, 0x49, 0x08, 0x29, 0xd9, 0x52, 0x2e, 0x7c, 0xfc, 0xa5, 0x68, 0x29, 0xe7, 0x3e, 0x7e, + 0x79, 0x29, 0x17, 0xde, 0xfe, 0xf7, 0x50, 0xb9, 0xda, 0xe9, 0xd8, 0x3c, 0x1e, 0xd5, 0x13, 0x8e, + 0xac, 0x81, 0x6b, 0xf8, 0x73, 0x10, 0xc4, 0x15, 0xd1, 0xa7, 0x2e, 0xe2, 0xb2, 0x40, 0x7c, 0x8d, + 0x75, 0xc4, 0xb6, 0x45, 0x77, 0xa8, 0x2b, 0x42, 0x05, 0xc0, 0x99, 0xe7, 0x0a, 0x98, 0x1c, 0xd2, + 0x15, 0xd0, 0xe1, 0x59, 0x34, 0xb6, 0xea, 0xda, 0x8e, 0xed, 0x51, 0x93, 0x2b, 0xaa, 0x0c, 0x8c, + 0x17, 0xfa, 0xbd, 0x8a, 0xe6, 0x08, 0x44, 0x03, 0x34, 0x26, 0xb1, 0xab, 0x2c, 0xfa, 0x37, 0x32, + 0xb1, 0xc1, 0x78, 0x84, 0xf3, 0x5c, 0x19, 0x1d, 0xb9, 0x01, 0xa3, 0x43, 0xff, 0xd5, 0x2c, 0x2a, + 0xb3, 0x73, 0xc2, 0x82, 0xed, 0xee, 0x18, 0xee, 0xd1, 0x38, 0x4f, 0x0e, 0xed, 0x2e, 0x45, 0x32, + 0x43, 0x86, 0xf7, 0x61, 0x86, 0x5c, 0x40, 0xc3, 0xd2, 0xf5, 0x1f, 0x77, 0x66, 0xb0, 0xb3, 0x16, + 0x40, 0xf5, 0xff, 0x97, 0x45, 0xe8, 0xb3, 0x57, 0xaf, 0x3e, 0xc6, 0x0a, 0xd2, 0x7f, 0x25, 0x83, + 0xc6, 0x85, 0x77, 0x4d, 0x8a, 0x77, 0x2e, 0x06, 0x7e, 0xd1, 0x4c, 0xe4, 0x35, 0x14, 0xfe, 0x50, + 0x12, 0xe0, 0xd8, 0x4c, 0x9c, 0xbf, 0x67, 0xf9, 0xe0, 0x60, 0x90, 0x02, 0x9e, 0xa9, 0x80, 0xc9, + 0x33, 0x31, 0xa0, 0xc3, 0x2f, 0x06, 0x7e, 0xc3, 0x5c, 0xb4, 0xfc, 0x30, 0x86, 0xf9, 0x54, 0xdf, + 0xa1, 0xfe, 0xcd, 0x61, 0x34, 0x3c, 0x7f, 0x8f, 0x36, 0x8f, 0x79, 0xd7, 0x48, 0xa7, 0x91, 0xe1, + 0x03, 0x9e, 0x46, 0x1e, 0xe6, 0x22, 0xe4, 0x8d, 0xa8, 0x3f, 0x0b, 0x6a, 0xf3, 0xb1, 0x9e, 0x8f, + 0x37, 0x1f, 0xf4, 0xf4, 0xf1, 0xbb, 0x47, 0xfb, 0xf3, 0x1c, 0xca, 0xad, 0xd5, 0x56, 0x4f, 0xc6, + 0xcd, 0x91, 0x8e, 0x9b, 0xbd, 0x1d, 0xcd, 0x7a, 0xe8, 0x3b, 0x2a, 0x45, 0xb1, 0x03, 0x31, 0x37, + 0xd1, 0x57, 0xb2, 0x68, 0x64, 0xad, 0xbb, 0xe1, 0xed, 0x7a, 0x3e, 0x6d, 0x1f, 0xf3, 0xde, 0xbc, + 0x20, 0xce, 0x83, 0xc3, 0x91, 0x36, 0xd8, 0x79, 0x50, 0x9c, 0x02, 0x9f, 0x0b, 0x56, 0x46, 0xe9, + 0x9c, 0x11, 0xae, 0x8c, 0xc1, 0x7a, 0xf8, 0xfb, 0x59, 0x34, 0x51, 0x6b, 0x59, 0xb4, 0xe3, 0xcf, + 0x59, 0x5e, 0xf3, 0x10, 0x22, 0x25, 0x8e, 0x5e, 0x2b, 0x07, 0x73, 0xb4, 0x3c, 0x40, 0xfc, 0x89, + 0xfe, 0xff, 0xb3, 0xa8, 0x5c, 0xed, 0xfa, 0x5b, 0x55, 0x1f, 0x36, 0x97, 0xc7, 0x72, 0x9b, 0xff, + 0x76, 0x06, 0x8d, 0xb3, 0x07, 0x59, 0xb7, 0xef, 0xd2, 0xce, 0x21, 0x1c, 0xa3, 0xe4, 0xe3, 0x50, + 0xf6, 0x21, 0x8f, 0x43, 0x81, 0x2e, 0x73, 0xfb, 0x3c, 0x16, 0xb2, 0x83, 0x36, 0x3b, 0x25, 0x3d, + 0x22, 0xaf, 0x71, 0x08, 0xe7, 0x88, 0xa3, 0x7c, 0x8d, 0xef, 0x66, 0xd0, 0xd4, 0xba, 0xcb, 0x36, + 0x72, 0x53, 0xec, 0xe7, 0xc7, 0xbc, 0x5f, 0x92, 0x2f, 0x74, 0xcc, 0x7b, 0xe8, 0xfb, 0x19, 0xf4, + 0x84, 0xfa, 0x42, 0x8f, 0xc2, 0x2a, 0xf0, 0x57, 0x19, 0x74, 0xe6, 0x4d, 0x48, 0x82, 0x0d, 0x5d, + 0x70, 0x8f, 0xde, 0x1b, 0x1d, 0xf3, 0x91, 0xf7, 0x9d, 0x0c, 0x3a, 0xbd, 0x52, 0x9f, 0xab, 0x3d, + 0x2a, 0x3d, 0x94, 0x78, 0x9f, 0x47, 0xa0, 0x7f, 0xd6, 0xaa, 0xb7, 0x96, 0x1e, 0xa5, 0xfe, 0x51, + 0xde, 0xe7, 0x98, 0xf7, 0xcf, 0x4f, 0x17, 0x50, 0x99, 0x9d, 0x6b, 0x85, 0x4f, 0xef, 0xb1, 0xb6, + 0xf4, 0xaf, 0xa1, 0xb2, 0x50, 0x03, 0x1c, 0x29, 0xa5, 0x78, 0x7e, 0x91, 0x41, 0xde, 0x80, 0xa3, + 0xa5, 0x4c, 0xc4, 0x4e, 0x5c, 0xef, 0x50, 0x77, 0x43, 0x8e, 0xa1, 0xd9, 0xa6, 0xee, 0x06, 0x01, + 0x28, 0x5e, 0x8a, 0xee, 0xec, 0xaa, 0xab, 0x75, 0x48, 0x7c, 0x15, 0x27, 0x55, 0xc8, 0xe4, 0x75, + 0x05, 0xae, 0x61, 0x38, 0x16, 0x4f, 0x99, 0x95, 0xe3, 0xf7, 0xe2, 0x9c, 0x78, 0x19, 0x4d, 0x06, + 0xb0, 0x28, 0x83, 0xb5, 0x94, 0x22, 0x2e, 0x2d, 0x77, 0x35, 0xc9, 0x8a, 0xdf, 0x40, 0xa3, 0x01, + 0xf0, 0x2d, 0x0b, 0xf2, 0xeb, 0x98, 0xa8, 0x27, 0xfb, 0xbd, 0xca, 0xb9, 0x50, 0xd4, 0x5d, 0x4b, + 0x89, 0x4f, 0x54, 0x18, 0x64, 0x01, 0x70, 0xec, 0x44, 0x29, 0x02, 0x62, 0xf7, 0x91, 0x0a, 0x03, + 0x7e, 0x19, 0x04, 0x38, 0x76, 0xc7, 0xa3, 0xe0, 0xe3, 0x2b, 0x43, 0x80, 0x09, 0xdc, 0x09, 0xba, + 0x02, 0xce, 0xc3, 0x88, 0x14, 0x32, 0xbc, 0x82, 0x50, 0xe4, 0x8b, 0x11, 0xc1, 0x9a, 0xfb, 0xf6, + 0x12, 0x49, 0x22, 0xf4, 0xbf, 0x66, 0xe7, 0x37, 0xc7, 0x09, 0x47, 0xf2, 0x8b, 0xa8, 0x50, 0x75, + 0x9c, 0xdb, 0xa4, 0x2e, 0xbc, 0x93, 0x10, 0x4b, 0x68, 0x38, 0x4e, 0xa3, 0xeb, 0x5a, 0xf2, 0x85, + 0x04, 0x27, 0xc2, 0x35, 0x34, 0x56, 0x75, 0x9c, 0xd5, 0xee, 0x46, 0xcb, 0x6a, 0x4a, 0xe9, 0xe3, + 0xbc, 0x06, 0x81, 0xe3, 0x34, 0x1c, 0xc0, 0xc4, 0x93, 0xef, 0x55, 0x1e, 0xfc, 0x1e, 0x1a, 0xa9, + 0x3a, 0x8e, 0x48, 0xbe, 0xcf, 0x41, 0xf2, 0xbd, 0x1e, 0xbc, 0x93, 0xf4, 0x6c, 0xd3, 0x21, 0x11, + 0x4f, 0xbb, 0xbf, 0x20, 0x6e, 0x6b, 0xa7, 0x58, 0x43, 0x89, 0x9c, 0xfb, 0x48, 0x24, 0xfe, 0x38, + 0x2a, 0x56, 0x1d, 0x47, 0x72, 0x0f, 0x80, 0x2b, 0x95, 0x71, 0xc5, 0xba, 0x28, 0x20, 0x3b, 0xff, + 0x1a, 0x3a, 0xa5, 0x36, 0xb6, 0xaf, 0xfc, 0xfc, 0x9f, 0x64, 0xe0, 0x85, 0x8e, 0xf9, 0x85, 0xda, + 0x4b, 0x28, 0x57, 0x75, 0x1c, 0xb1, 0x9c, 0x9c, 0x4e, 0xe9, 0x8f, 0x78, 0xb4, 0x56, 0xd5, 0x71, + 0x82, 0x57, 0xe7, 0x57, 0xde, 0x8f, 0xd7, 0xab, 0x7f, 0x8b, 0xbf, 0xfa, 0x31, 0xbf, 0xa1, 0xfe, + 0x66, 0x0e, 0x8d, 0x57, 0x1d, 0xe7, 0x24, 0x6d, 0xfd, 0xb0, 0x62, 0xc2, 0xae, 0x22, 0x24, 0x2d, + 0x8f, 0xc5, 0x30, 0xf4, 0xa3, 0x2c, 0x2d, 0x8d, 0x5a, 0x86, 0x48, 0x44, 0xc1, 0xf0, 0x2b, 0xed, + 0x6b, 0xf8, 0xfd, 0x89, 0xd2, 0x71, 0x90, 0x82, 0x7b, 0xd2, 0x71, 0xf9, 0x03, 0x59, 0x54, 0xa7, + 0x64, 0x65, 0x8a, 0x80, 0x6f, 0x71, 0x43, 0x1f, 0xa4, 0x1f, 0x34, 0x19, 0xaa, 0x61, 0x99, 0x24, + 0x46, 0x1b, 0xf4, 0x61, 0x71, 0x5f, 0x7d, 0xf8, 0xb5, 0x2c, 0x9a, 0x8c, 0xfa, 0xf0, 0x30, 0x0c, + 0xd3, 0x19, 0x84, 0xb8, 0x93, 0x32, 0xbc, 0x48, 0x1c, 0xe3, 0x91, 0xca, 0x1e, 0x40, 0x45, 0xa4, + 0x72, 0x44, 0x12, 0xde, 0x2a, 0xe4, 0x52, 0x6f, 0x15, 0x2e, 0xa3, 0x12, 0x31, 0x76, 0xde, 0xee, + 0x52, 0x77, 0x57, 0x6c, 0xa5, 0xe0, 0x4a, 0x77, 0x8d, 0x9d, 0xc6, 0x17, 0x19, 0x90, 0x84, 0x68, + 0xac, 0x87, 0x31, 0x6a, 0x92, 0xf3, 0x98, 0xc7, 0xa8, 0x85, 0x91, 0x69, 0x42, 0x49, 0x85, 0x7d, + 0x29, 0xe9, 0xfb, 0x05, 0x34, 0x31, 0x67, 0xf8, 0xc6, 0x86, 0xe1, 0x51, 0xe9, 0x20, 0x31, 0x1e, + 0xc0, 0xd8, 0x40, 0xb0, 0xc2, 0x92, 0x45, 0x10, 0x97, 0x65, 0x6e, 0x34, 0x3c, 0x0e, 0x95, 0x6b, + 0xbc, 0xc4, 0x18, 0xf0, 0xa7, 0x22, 0xb9, 0x61, 0x51, 0x1b, 0x6e, 0xce, 0x80, 0xc6, 0xcc, 0x8d, + 0x86, 0x23, 0xc0, 0x24, 0x41, 0x88, 0xaf, 0xa0, 0x72, 0x00, 0x63, 0xc6, 0x53, 0x2e, 0x7a, 0x67, + 0x73, 0x83, 0xd9, 0x4e, 0x44, 0x46, 0xe3, 0x4f, 0xa2, 0xd1, 0xe0, 0xa7, 0x64, 0x96, 0x80, 0xad, + 0x65, 0x6e, 0x24, 0x0c, 0x47, 0x99, 0x54, 0x66, 0x85, 0xf9, 0x99, 0x57, 0x58, 0x63, 0x05, 0xad, + 0x14, 0x52, 0xfc, 0x45, 0x74, 0x2a, 0xf8, 0x2d, 0x8c, 0xad, 0x02, 0x18, 0x5b, 0x57, 0x02, 0xcd, + 0xc7, 0xd5, 0x3a, 0xad, 0x92, 0x73, 0xb3, 0xeb, 0x49, 0x61, 0x76, 0x9d, 0x36, 0x37, 0x92, 0x56, + 0x57, 0xac, 0x01, 0x5c, 0x47, 0x93, 0x01, 0xa4, 0xfa, 0xee, 0x1a, 0xa1, 0x9b, 0x6c, 0x56, 0x16, + 0x23, 0x63, 0xd9, 0xdc, 0x68, 0x40, 0xa9, 0x2b, 0x40, 0xc8, 0x36, 0x7b, 0x82, 0x0b, 0xb7, 0xd0, + 0x05, 0x05, 0x68, 0x7a, 0x5b, 0xd6, 0x1d, 0x5f, 0x58, 0xba, 0xf5, 0x39, 0x71, 0x1c, 0x80, 0x5a, + 0x30, 0xa1, 0x54, 0x4e, 0x13, 0x14, 0x0b, 0x6a, 0x28, 0x75, 0xd3, 0xf6, 0x94, 0x86, 0xd7, 0xd0, + 0x54, 0x80, 0x7f, 0xb3, 0xb6, 0xba, 0xea, 0xda, 0x5f, 0xa0, 0x4d, 0xbf, 0x3e, 0x27, 0x4e, 0x0a, + 0x90, 0x82, 0x64, 0x6e, 0x34, 0x36, 0x9b, 0x0e, 0x1b, 0x14, 0x0c, 0xa7, 0x0a, 0x4f, 0x65, 0xc6, + 0xef, 0xa0, 0x33, 0x12, 0xbc, 0xde, 0xf1, 0x7c, 0xa3, 0xd3, 0xa4, 0xf5, 0x39, 0x71, 0x7c, 0x80, + 0xa3, 0x8c, 0x90, 0x6a, 0x09, 0xa4, 0x2a, 0x36, 0x9d, 0xfd, 0x7c, 0x15, 0x9d, 0x4e, 0xe9, 0xa9, + 0x7d, 0xd9, 0xac, 0x5f, 0xc9, 0x46, 0x83, 0xe3, 0x98, 0x1b, 0xae, 0xb3, 0xa8, 0x14, 0xbc, 0x89, + 0xd8, 0x42, 0xb4, 0x41, 0x03, 0x3c, 0x2e, 0x23, 0xc0, 0x2b, 0xea, 0x38, 0xe6, 0xc6, 0xec, 0x61, + 0xa8, 0xe3, 0x83, 0x4c, 0xa4, 0x8e, 0x63, 0x6e, 0xe0, 0x7e, 0x27, 0x17, 0xcd, 0xec, 0x13, 0x2b, + 0xf7, 0xb0, 0x8c, 0xa5, 0xe8, 0xe2, 0xb4, 0xb0, 0x8f, 0x00, 0x32, 0x79, 0x68, 0x16, 0x1f, 0x72, + 0x68, 0xfe, 0x5d, 0xb2, 0x3f, 0xb9, 0x01, 0x72, 0x2c, 0xfb, 0xf3, 0x10, 0x26, 0x2b, 0xbe, 0x86, + 0xc6, 0x82, 0xff, 0xb9, 0xa5, 0x96, 0x97, 0xf2, 0xa1, 0x36, 0x84, 0xa1, 0xa6, 0x92, 0xe0, 0xcf, + 0xa1, 0x73, 0x0a, 0x60, 0xd5, 0x70, 0x8d, 0x36, 0xf5, 0xa9, 0xcb, 0x6d, 0x04, 0x51, 0x7e, 0x2e, + 0xe0, 0x6e, 0x38, 0x21, 0x5a, 0xae, 0xe0, 0x36, 0x40, 0x82, 0x34, 0x38, 0x8a, 0xfb, 0xb8, 0x55, + 0xff, 0xe7, 0x2c, 0x1a, 0x5b, 0xb5, 0x3d, 0x7f, 0xd3, 0xa5, 0xde, 0xaa, 0xe1, 0x7a, 0xf4, 0xf1, + 0xed, 0xd1, 0x4f, 0xa0, 0x31, 0x88, 0x93, 0x6d, 0xd3, 0x8e, 0x2f, 0xd5, 0xa5, 0xe3, 0x35, 0x1a, + 0x02, 0x04, 0x98, 0x8d, 0x44, 0x25, 0xc4, 0x15, 0x94, 0xe7, 0x63, 0x40, 0x8a, 0x5e, 0xe6, 0x03, + 0x80, 0xc3, 0xf5, 0xaf, 0xe5, 0xd0, 0x68, 0xa0, 0xe5, 0x59, 0xeb, 0xb8, 0x66, 0x01, 0x1f, 0xad, + 0x92, 0x67, 0x10, 0x5a, 0xb5, 0x5d, 0xdf, 0x68, 0x49, 0x25, 0x71, 0xe1, 0xc8, 0xe0, 0x00, 0x94, + 0xf3, 0x48, 0x24, 0x78, 0x1a, 0x21, 0x69, 0x82, 0x15, 0x61, 0x82, 0x9d, 0xea, 0xf7, 0x2a, 0x28, + 0x9a, 0x57, 0x44, 0xa2, 0xd0, 0xff, 0x28, 0x8b, 0xc6, 0x83, 0x4e, 0x9a, 0xbf, 0x47, 0x9b, 0x5d, + 0xff, 0x31, 0x9e, 0x0c, 0xaa, 0xb6, 0xf3, 0xf7, 0xd5, 0xb6, 0xfe, 0xef, 0xd2, 0x42, 0x52, 0x6b, + 0xd9, 0x27, 0x0b, 0xc9, 0xff, 0xc6, 0x18, 0xd7, 0x7f, 0x26, 0x87, 0xa6, 0x02, 0xad, 0x2f, 0x74, + 0x3b, 0x60, 0x26, 0xd4, 0x8c, 0x56, 0xeb, 0x71, 0xde, 0x97, 0xcb, 0x81, 0x22, 0x56, 0x44, 0xe2, + 0xc9, 0x18, 0xbf, 0x64, 0xbb, 0x23, 0xc0, 0x0d, 0xdb, 0x32, 0x89, 0x4c, 0x84, 0xdf, 0x40, 0xa3, + 0xc1, 0xcf, 0xaa, 0xbb, 0x19, 0x6c, 0xc6, 0x70, 0x74, 0x0e, 0x99, 0x0c, 0x77, 0x53, 0x29, 0x36, + 0x2c, 0x33, 0xe8, 0xff, 0x9a, 0x47, 0xe7, 0xdf, 0xb5, 0x3a, 0xa6, 0xbd, 0xe3, 0x89, 0x62, 0x5c, + 0xc7, 0xdf, 0xe8, 0x3d, 0xbc, 0x1a, 0x38, 0x91, 0x65, 0x92, 0xdf, 0x87, 0xd9, 0xfa, 0x36, 0x3a, + 0x13, 0x57, 0xa9, 0x1b, 0xe6, 0x3b, 0x8a, 0xde, 0xd9, 0xe1, 0x04, 0x8d, 0xa0, 0x1e, 0x9a, 0xf0, + 0x3f, 0x91, 0x74, 0xce, 0x78, 0x41, 0xb5, 0xe2, 0x83, 0x14, 0x54, 0xfb, 0x18, 0x2a, 0xcc, 0xd9, + 0x6d, 0xc3, 0x0a, 0x42, 0x7c, 0x61, 0x16, 0x87, 0xed, 0x02, 0x86, 0x08, 0x0a, 0x26, 0x5f, 0x34, + 0x0c, 0x5d, 0x36, 0x12, 0xc9, 0x0f, 0x18, 0xba, 0x1e, 0x75, 0x89, 0x4c, 0x84, 0x6d, 0x34, 0x26, + 0x9a, 0x13, 0xde, 0x22, 0x04, 0xde, 0xa2, 0x97, 0x03, 0x1d, 0x0d, 0x1e, 0x56, 0xd3, 0x0a, 0x1f, + 0x77, 0x1b, 0xc1, 0xd3, 0x05, 0x2f, 0xc3, 0xfd, 0x46, 0x44, 0x95, 0x7f, 0xfe, 0x26, 0xc2, 0x49, + 0xc6, 0x7d, 0x79, 0x31, 0x7e, 0x3e, 0x8b, 0x70, 0xec, 0x30, 0x30, 0xff, 0x18, 0xdb, 0x34, 0xfa, + 0xef, 0x64, 0xd0, 0x64, 0x22, 0xeb, 0x16, 0xbf, 0x84, 0x10, 0x87, 0x48, 0x19, 0xb4, 0x90, 0x2f, + 0x17, 0x65, 0xe2, 0x8a, 0xf5, 0x3c, 0x22, 0xc3, 0x33, 0xa8, 0xc4, 0x7f, 0x85, 0x15, 0xdd, 0xe3, + 0x2c, 0xdd, 0xae, 0x65, 0x92, 0x90, 0x28, 0x6a, 0x05, 0xbe, 0x6d, 0x90, 0x4b, 0x65, 0xf1, 0x77, + 0x9d, 0xb0, 0x15, 0x46, 0xa6, 0x7f, 0x2b, 0x83, 0x46, 0xc3, 0x07, 0xae, 0x9a, 0x47, 0xd5, 0x75, + 0x05, 0x91, 0xc0, 0x9c, 0xbb, 0x5f, 0x02, 0x73, 0x6c, 0x81, 0x10, 0x19, 0xcb, 0x7f, 0x91, 0x41, + 0xe3, 0x21, 0xed, 0x11, 0xfa, 0x4b, 0x0e, 0xfc, 0x22, 0xbf, 0x90, 0x41, 0xda, 0xac, 0xd5, 0x6a, + 0x59, 0x9d, 0xcd, 0x7a, 0xe7, 0x8e, 0xed, 0xb6, 0x21, 0x4f, 0xf0, 0xe8, 0x1c, 0x62, 0xfa, 0xcf, + 0x65, 0xd0, 0xa4, 0x78, 0xa0, 0x9a, 0xe1, 0x9a, 0x47, 0xe7, 0xa9, 0x8c, 0x3f, 0xc9, 0xd1, 0xf5, + 0x32, 0xc4, 0x3a, 0x2f, 0xd9, 0xcd, 0xbb, 0x8f, 0x40, 0xc8, 0x36, 0x7b, 0x8d, 0x63, 0x1e, 0x56, + 0xf6, 0xe5, 0x0c, 0x9a, 0x22, 0xb4, 0x69, 0x6f, 0x53, 0x77, 0xb7, 0x66, 0x9b, 0xf4, 0x4d, 0xda, + 0xa1, 0xee, 0x51, 0x0d, 0xd2, 0x3f, 0x86, 0xd2, 0x09, 0xd1, 0xc3, 0xdc, 0xf6, 0xa8, 0x79, 0x7c, + 0xea, 0x5c, 0xe8, 0x7f, 0x50, 0x44, 0x5a, 0xaa, 0x95, 0x71, 0x6c, 0x37, 0xf5, 0x81, 0xa6, 0xe3, + 0xf0, 0x61, 0x99, 0x8e, 0xf9, 0xfd, 0x99, 0x8e, 0x85, 0xfd, 0x9a, 0x8e, 0xc5, 0x07, 0x31, 0x1d, + 0xdb, 0x71, 0xd3, 0xb1, 0x04, 0xa6, 0xe3, 0x4b, 0x7b, 0x9a, 0x8e, 0xf3, 0x1d, 0xf3, 0xe1, 0x0c, + 0xc7, 0xe3, 0x5b, 0xdd, 0x31, 0xea, 0x3b, 0x30, 0xc5, 0xca, 0xc9, 0xbe, 0x03, 0x3b, 0x4c, 0x26, + 0xc2, 0x97, 0xd8, 0xe2, 0xd6, 0xb4, 0x5d, 0x93, 0xf2, 0x6a, 0x8d, 0x25, 0xee, 0xd9, 0x75, 0x05, + 0x8c, 0x84, 0xd8, 0x44, 0xa9, 0xcc, 0xb1, 0x07, 0x29, 0x95, 0x79, 0x08, 0x56, 0xf8, 0x77, 0x33, + 0x68, 0xb2, 0x46, 0x5d, 0xdf, 0xba, 0x63, 0x35, 0x0d, 0xff, 0x30, 0xae, 0x13, 0xab, 0x68, 0x5c, + 0x12, 0x28, 0x7d, 0x2c, 0x0b, 0x72, 0x9c, 0x9b, 0xd4, 0xf5, 0xc1, 0x94, 0x94, 0x6f, 0xf7, 0x63, + 0xf4, 0xac, 0xf9, 0xa0, 0x5c, 0x8d, 0x98, 0xbb, 0x61, 0xf3, 0x01, 0x9c, 0x2b, 0xd2, 0x12, 0xbf, + 0x48, 0x48, 0xaf, 0x7f, 0x3d, 0x83, 0x2e, 0x12, 0xda, 0xa1, 0x3b, 0xc6, 0x46, 0x8b, 0x4a, 0x82, + 0xc5, 0xda, 0xce, 0xe6, 0xbd, 0xe5, 0xb5, 0x0d, 0xbf, 0xb9, 0x75, 0xa0, 0xb7, 0x5c, 0x50, 0x3f, + 0x58, 0xb5, 0x8f, 0xd5, 0x49, 0xe1, 0xd3, 0x7f, 0xf4, 0x02, 0xca, 0xaf, 0x74, 0xe8, 0xca, 0x1d, + 0x7c, 0x55, 0xaa, 0x6f, 0x24, 0x1e, 0x67, 0x52, 0x16, 0x07, 0x88, 0xc5, 0x21, 0x22, 0x55, 0x41, + 0xba, 0x2e, 0x57, 0x9f, 0x11, 0x8f, 0x80, 0x65, 0x1e, 0x8e, 0x59, 0x1c, 0x22, 0x72, 0x95, 0x9a, + 0xeb, 0x72, 0x79, 0x16, 0xa1, 0x5f, 0x85, 0x8b, 0x63, 0x02, 0x2e, 0xb1, 0xc7, 0x2f, 0xa5, 0x55, + 0x43, 0x89, 0x1f, 0xe8, 0x93, 0x14, 0x8b, 0x43, 0x24, 0xbd, 0x8a, 0x8a, 0xf2, 0xb5, 0x15, 0x71, + 0xa4, 0x9f, 0x8a, 0xad, 0xd0, 0x80, 0x5b, 0x1c, 0x22, 0xea, 0x97, 0x59, 0x6e, 0x28, 0xdf, 0xb1, + 0x88, 0x47, 0xa4, 0x48, 0xa8, 0xc5, 0x21, 0x12, 0xfb, 0xe2, 0x85, 0xf2, 0x51, 0x05, 0x71, 0xc3, + 0x11, 0x6f, 0x14, 0x70, 0x52, 0xa3, 0xfc, 0x03, 0x0c, 0xaf, 0xc7, 0x8a, 0x9d, 0x8b, 0x88, 0xaf, + 0x33, 0x31, 0x66, 0x8e, 0x5c, 0x1c, 0x22, 0xb1, 0xd2, 0xe8, 0x97, 0x82, 0xfa, 0xd7, 0x62, 0xc9, + 0x3b, 0x25, 0xd9, 0x38, 0xd6, 0xfb, 0x4c, 0x4b, 0x41, 0x7d, 0xec, 0xeb, 0x72, 0xdd, 0x63, 0xb1, + 0x86, 0xe1, 0x58, 0x2b, 0xf3, 0x1d, 0x93, 0xf5, 0x8e, 0xb4, 0xc1, 0xde, 0x8c, 0x57, 0x08, 0x15, + 0x75, 0x67, 0xcf, 0xc6, 0x38, 0x05, 0x76, 0x71, 0x88, 0xc4, 0x2b, 0x8a, 0xde, 0x50, 0xaa, 0x53, + 0x8a, 0x70, 0xe5, 0xb8, 0x56, 0x19, 0x4a, 0xd2, 0x2a, 0xd4, 0xb1, 0xbc, 0x19, 0x2f, 0x97, 0xa8, + 0x8d, 0xa5, 0x36, 0x2d, 0xb0, 0x52, 0xd3, 0x41, 0x79, 0xc5, 0x1b, 0x4a, 0x59, 0x3b, 0xa8, 0x1c, + 0x9b, 0xd2, 0xb4, 0xe1, 0x1b, 0x72, 0xd3, 0xbc, 0x00, 0x9e, 0x52, 0x60, 0x4d, 0x1b, 0x4f, 0xed, + 0x50, 0xc0, 0x49, 0x1d, 0xca, 0x8b, 0xb1, 0xdd, 0x50, 0x6a, 0x82, 0x68, 0x13, 0x6a, 0xa3, 0x12, + 0x8a, 0x35, 0x2a, 0x57, 0x0f, 0xb9, 0x2e, 0x97, 0xca, 0xd0, 0x26, 0xd5, 0x0e, 0x8a, 0x30, 0xac, + 0x83, 0xa4, 0x92, 0x1a, 0x15, 0x48, 0xc3, 0xd7, 0x30, 0x90, 0x97, 0xc3, 0x27, 0xac, 0xad, 0x2e, + 0x0e, 0x11, 0x48, 0xd0, 0xd7, 0x79, 0x81, 0x07, 0xed, 0x34, 0x50, 0x8c, 0x86, 0xe5, 0x03, 0xef, + 0xd1, 0xe6, 0xe2, 0x10, 0xe1, 0xc5, 0x1f, 0xae, 0x4a, 0x39, 0xe0, 0xda, 0x94, 0xba, 0x44, 0x84, + 0x08, 0xb6, 0x44, 0x44, 0x99, 0xe2, 0x0b, 0xc9, 0x3c, 0x69, 0xed, 0x8c, 0x6a, 0x66, 0xc7, 0xf1, + 0x8b, 0x43, 0x24, 0x99, 0x5b, 0x7d, 0x43, 0x49, 0x1d, 0xd6, 0xce, 0xc6, 0xc2, 0xc0, 0x22, 0x14, + 0x53, 0x97, 0x9c, 0x64, 0xbc, 0x92, 0x5a, 0xba, 0x48, 0x3b, 0x07, 0x02, 0x9e, 0x0c, 0x05, 0x24, + 0x49, 0x16, 0x87, 0x48, 0x6a, 0xd1, 0xa3, 0x5a, 0x22, 0x81, 0x57, 0xd3, 0x54, 0xfb, 0x2e, 0x86, + 0x5e, 0x1c, 0x22, 0x89, 0x94, 0xdf, 0xeb, 0x72, 0xe6, 0xac, 0xf6, 0x84, 0xda, 0x89, 0x11, 0x86, + 0x75, 0xa2, 0x94, 0x61, 0x7b, 0x5d, 0x4e, 0x54, 0xd5, 0xce, 0x27, 0xb9, 0xa2, 0x95, 0x53, 0x4a, + 0x68, 0x25, 0xe9, 0x79, 0xa1, 0xda, 0x93, 0xc0, 0x7f, 0x21, 0xe0, 0x4f, 0xa3, 0x59, 0x1c, 0x22, + 0xe9, 0x39, 0xa5, 0x24, 0x3d, 0x35, 0x53, 0xbb, 0xb0, 0x97, 0xcc, 0xf0, 0xe9, 0xd2, 0xd3, 0x3a, + 0x8d, 0x3d, 0xb2, 0x23, 0xb5, 0xa7, 0xd4, 0xf4, 0x85, 0x81, 0x84, 0x8b, 0x43, 0x64, 0x8f, 0x1c, + 0xcb, 0xdb, 0x03, 0x52, 0x15, 0xb5, 0xa7, 0xd5, 0x42, 0x17, 0xa9, 0x44, 0x8b, 0x43, 0x64, 0x40, + 0xa2, 0xe3, 0xed, 0x01, 0xf9, 0x82, 0x5a, 0x65, 0x4f, 0xb1, 0xa1, 0x3e, 0x06, 0x64, 0x1b, 0xae, + 0xa4, 0x26, 0xed, 0x69, 0xcf, 0xa8, 0x43, 0x37, 0x85, 0x84, 0x0d, 0xdd, 0xb4, 0x74, 0xbf, 0x95, + 0xd4, 0xac, 0x39, 0xed, 0xd9, 0x3d, 0x04, 0x86, 0xcf, 0x98, 0x9a, 0x6f, 0xb7, 0x92, 0x9a, 0xb6, + 0xa6, 0xe9, 0xaa, 0xc0, 0x14, 0x12, 0x26, 0x30, 0x2d, 0xe1, 0x6d, 0x25, 0x35, 0x6f, 0x4c, 0x7b, + 0x6e, 0x0f, 0x81, 0xd1, 0x13, 0xa6, 0x65, 0x9c, 0xdd, 0x50, 0x12, 0xb7, 0xb4, 0x8f, 0xa8, 0xeb, + 0x86, 0x84, 0x62, 0xeb, 0x86, 0x9c, 0xe2, 0x55, 0x4b, 0xc4, 0xb6, 0x6b, 0x1f, 0x55, 0xa7, 0x79, + 0x0c, 0xcd, 0xa6, 0x79, 0x3c, 0x1a, 0xbe, 0x96, 0x88, 0xb3, 0xd6, 0x2e, 0x0e, 0x12, 0x02, 0x68, + 0x55, 0x08, 0x8f, 0xcc, 0xae, 0xa7, 0x04, 0xfa, 0x6a, 0xcf, 0xab, 0x4e, 0xba, 0x04, 0xc1, 0xe2, + 0x10, 0x49, 0x09, 0x0f, 0x26, 0xe9, 0xf1, 0x4c, 0xda, 0x25, 0x75, 0xda, 0xa6, 0xd1, 0xb0, 0x69, + 0x9b, 0x1a, 0x0b, 0xb5, 0x94, 0xe6, 0x46, 0xd7, 0x2e, 0xab, 0x86, 0x59, 0x92, 0x82, 0x19, 0x66, + 0x29, 0xee, 0x77, 0x92, 0x1e, 0xa1, 0xa3, 0x7d, 0x6c, 0xcf, 0x27, 0x04, 0x9a, 0x94, 0x27, 0xe4, + 0x01, 0x2b, 0x91, 0xed, 0x74, 0xdb, 0x69, 0xd9, 0x86, 0xa9, 0xbd, 0x90, 0x6a, 0x3b, 0x71, 0xa4, + 0x64, 0x3b, 0x71, 0x00, 0xdb, 0xe5, 0x65, 0x37, 0xb3, 0x76, 0x45, 0xdd, 0xe5, 0x65, 0x1c, 0xdb, + 0xe5, 0x15, 0x97, 0x74, 0x2d, 0xe1, 0xdc, 0xd5, 0x5e, 0x54, 0x07, 0x40, 0x0c, 0xcd, 0x06, 0x40, + 0xdc, 0x1d, 0xfc, 0xde, 0x60, 0xc7, 0xaa, 0x36, 0x0d, 0xd2, 0x9e, 0x09, 0x0b, 0x24, 0x0f, 0xa0, + 0x5b, 0x1c, 0x22, 0x83, 0x9d, 0xb3, 0xf5, 0x14, 0x3f, 0xa9, 0x36, 0xa3, 0x0e, 0xb0, 0x04, 0x01, + 0x1b, 0x60, 0x49, 0xef, 0x6a, 0x3d, 0xc5, 0xd1, 0xa9, 0x7d, 0x7c, 0xa0, 0xa8, 0xf0, 0x9d, 0x53, + 0xdc, 0xa3, 0xd7, 0x65, 0x4f, 0xa5, 0x76, 0x55, 0xdd, 0xec, 0x22, 0x0c, 0xdb, 0xec, 0x24, 0x8f, + 0xe6, 0x75, 0xd9, 0x31, 0xa8, 0x5d, 0x4b, 0x72, 0x45, 0x5b, 0xa4, 0xe4, 0x40, 0x24, 0xe9, 0x7e, + 0x38, 0xed, 0x25, 0x75, 0xd4, 0xa5, 0xd1, 0xb0, 0x51, 0x97, 0xea, 0xc3, 0x5b, 0x48, 0xba, 0xd3, + 0xb4, 0xeb, 0x71, 0x07, 0xa3, 0x8a, 0x67, 0x96, 0x4f, 0xc2, 0x05, 0x77, 0x33, 0x1e, 0x6c, 0xab, + 0xbd, 0xac, 0xda, 0xb7, 0x2a, 0x96, 0xd9, 0xb7, 0xb1, 0xe0, 0xdc, 0x9b, 0xf1, 0xf8, 0x54, 0xed, + 0x95, 0x74, 0x09, 0xe1, 0x58, 0x89, 0xc7, 0xb3, 0xde, 0x8c, 0x87, 0x74, 0x6a, 0x37, 0xd2, 0x25, + 0x84, 0xda, 0x8d, 0x87, 0x80, 0x5e, 0x95, 0xd2, 0xdc, 0xb4, 0x4f, 0xa8, 0xa6, 0x63, 0x88, 0x60, + 0xa6, 0x63, 0x94, 0x0c, 0x77, 0x55, 0x4a, 0x0f, 0xd3, 0x3e, 0x99, 0x60, 0x09, 0x1f, 0x56, 0x4a, + 0x22, 0xbb, 0x2a, 0xa5, 0x55, 0x69, 0xaf, 0x26, 0x58, 0xc2, 0xa7, 0x93, 0x92, 0xaf, 0xcc, 0xbd, + 0xee, 0xbc, 0xb5, 0x4f, 0x81, 0x0c, 0xfd, 0xfe, 0xd7, 0x98, 0x8b, 0x43, 0x64, 0xaf, 0xbb, 0xf3, + 0xf7, 0x06, 0x3b, 0x27, 0xb5, 0xd7, 0xd4, 0x29, 0x3c, 0x88, 0x8e, 0x4d, 0xe1, 0x81, 0x0e, 0xce, + 0xd7, 0x63, 0xf1, 0x6f, 0xda, 0xeb, 0xea, 0x12, 0xa7, 0x20, 0xd9, 0x12, 0x17, 0x8f, 0x96, 0x53, + 0x02, 0xbb, 0xb4, 0x4f, 0xab, 0x4b, 0x9c, 0x8c, 0x63, 0x4b, 0x9c, 0x12, 0x04, 0x56, 0x4b, 0xc4, + 0x1b, 0x69, 0x6f, 0xa8, 0x4b, 0x5c, 0x0c, 0xcd, 0x96, 0xb8, 0x78, 0x84, 0xd2, 0xeb, 0xb1, 0xb0, + 0x1b, 0xed, 0x66, 0xfa, 0xf3, 0x03, 0x52, 0x7e, 0x7e, 0x1e, 0xa4, 0x43, 0xd2, 0xe3, 0x47, 0xb4, + 0xaa, 0x3a, 0x7f, 0xd3, 0x68, 0xd8, 0xfc, 0x4d, 0x8d, 0x3d, 0x59, 0x49, 0x2d, 0x33, 0xa9, 0xcd, + 0xee, 0x71, 0x70, 0x88, 0x4c, 0x91, 0xb4, 0x02, 0x95, 0x37, 0xe3, 0x9f, 0xd6, 0xd2, 0x6a, 0x03, + 0xce, 0xc8, 0xc1, 0x31, 0x28, 0xfe, 0x29, 0xae, 0x7a, 0x8a, 0xaf, 0x4c, 0x9b, 0x53, 0x57, 0xd7, + 0x04, 0x01, 0x5b, 0x5d, 0x93, 0x1e, 0xb6, 0x85, 0xe4, 0x17, 0x0d, 0xb5, 0xf9, 0xd8, 0xcd, 0x71, + 0x0c, 0xcf, 0x56, 0xa7, 0xc4, 0x57, 0x10, 0x49, 0xfa, 0x47, 0xef, 0xb4, 0x85, 0xd8, 0x7e, 0x9d, + 0x42, 0x03, 0xfb, 0x75, 0xda, 0x07, 0xf3, 0x3e, 0x37, 0xf0, 0xdb, 0x85, 0xda, 0x9b, 0x20, 0xb6, + 0x32, 0x48, 0xac, 0x20, 0x5b, 0x1c, 0x22, 0x03, 0xbf, 0x7e, 0x78, 0x1b, 0x9d, 0xb9, 0xb5, 0xbb, + 0xf6, 0xf6, 0x52, 0x18, 0xb2, 0xb4, 0xea, 0x52, 0xc7, 0x70, 0xa9, 0xb6, 0xa8, 0xda, 0xea, 0xa9, + 0x44, 0xcc, 0x56, 0x4f, 0x45, 0x24, 0xc5, 0x06, 0x73, 0xa1, 0xbe, 0x97, 0xd8, 0x68, 0x46, 0xa4, + 0x73, 0xb3, 0xd5, 0x49, 0x45, 0x30, 0x05, 0x2d, 0xd9, 0x9d, 0x4d, 0xf0, 0x54, 0x7c, 0x46, 0x5d, + 0x9d, 0x06, 0x53, 0xb2, 0xd5, 0x69, 0x30, 0x96, 0x0d, 0x75, 0x15, 0xcb, 0xe7, 0xe0, 0x5b, 0xea, + 0x50, 0x4f, 0x21, 0x61, 0x43, 0x3d, 0x05, 0x9c, 0x14, 0x48, 0xa8, 0x47, 0x7d, 0x6d, 0x69, 0x2f, + 0x81, 0x40, 0x92, 0x14, 0x08, 0xe0, 0xa4, 0xc0, 0x05, 0xea, 0x37, 0xb7, 0xb4, 0x5b, 0x7b, 0x09, + 0x04, 0x92, 0xa4, 0x40, 0x00, 0xb3, 0xc3, 0xa6, 0x0a, 0x9e, 0xed, 0xb6, 0xee, 0x06, 0x7d, 0xb6, + 0xac, 0x1e, 0x36, 0x07, 0x12, 0xb2, 0xc3, 0xe6, 0x40, 0x24, 0xfe, 0xd2, 0x03, 0x7b, 0x82, 0xb5, + 0x15, 0x68, 0x70, 0x3a, 0xb2, 0x0b, 0x1e, 0x84, 0x6b, 0x71, 0x88, 0x3c, 0xa0, 0xfc, 0xd9, 0x22, + 0xca, 0xc3, 0x77, 0x20, 0xf4, 0x5f, 0xcb, 0xa0, 0xd1, 0x35, 0xdf, 0xa5, 0x46, 0x5b, 0xc4, 0x27, + 0x9d, 0x47, 0x25, 0x6e, 0xe6, 0x8a, 0x6f, 0x1c, 0x8d, 0x90, 0xf0, 0x37, 0xbe, 0x88, 0x4e, 0x2d, + 0x19, 0x9e, 0x0f, 0x9c, 0xd2, 0xc7, 0x5b, 0x49, 0x0c, 0x8a, 0x97, 0x38, 0x1d, 0xe7, 0x83, 0xab, + 0x8f, 0xdc, 0x7d, 0xaf, 0x3e, 0x4a, 0x1f, 0xf4, 0x2a, 0x43, 0x70, 0xc1, 0x11, 0xe3, 0xd5, 0xfb, + 0x19, 0x94, 0x30, 0xc0, 0x1f, 0xde, 0x4f, 0xbe, 0x82, 0xc6, 0x63, 0xd7, 0x6d, 0xc2, 0x4f, 0xfd, + 0x80, 0xb7, 0x71, 0x71, 0x6e, 0xfc, 0x1c, 0xca, 0xdd, 0xae, 0xcf, 0xc9, 0x75, 0xd9, 0xbb, 0x4a, + 0xda, 0x15, 0xc3, 0xe2, 0xe7, 0x43, 0x27, 0xea, 0x6d, 0xb2, 0x24, 0xae, 0xd9, 0xe0, 0xdb, 0x51, + 0x5d, 0xb7, 0x45, 0x24, 0x94, 0xfe, 0xed, 0x72, 0x74, 0xd5, 0x80, 0x2f, 0x8a, 0x9b, 0x46, 0xa9, + 0x4e, 0x7d, 0x2c, 0x47, 0x8f, 0xdf, 0x2c, 0x7e, 0x1a, 0x8d, 0xd6, 0xdb, 0x0e, 0x75, 0x3d, 0xbb, + 0x03, 0x15, 0xa4, 0xf9, 0xf5, 0xc6, 0xf9, 0x7e, 0xaf, 0x72, 0xd6, 0x92, 0xe0, 0x72, 0x9c, 0x9f, + 0x4c, 0x1f, 0x95, 0xbf, 0xce, 0xdd, 0xb7, 0xfc, 0xf5, 0x65, 0x94, 0xbf, 0xed, 0xf1, 0xaf, 0xa7, + 0x87, 0xa4, 0xdd, 0xd8, 0xb7, 0xa2, 0x38, 0x05, 0xbe, 0x82, 0x0a, 0x70, 0x2b, 0xe0, 0x69, 0x79, + 0xa0, 0x85, 0xdc, 0xd7, 0x16, 0x40, 0xe4, 0x62, 0x10, 0x9c, 0x26, 0xf5, 0x23, 0xf9, 0x85, 0x87, + 0xfc, 0x48, 0x7e, 0xda, 0xb7, 0xfb, 0x8b, 0x0f, 0xf7, 0xed, 0xfe, 0x7a, 0xf4, 0x09, 0x80, 0xd2, + 0x7d, 0xc7, 0xf0, 0x69, 0x71, 0x7d, 0x57, 0x14, 0x9f, 0x00, 0x50, 0x0b, 0xff, 0x2f, 0xa0, 0x53, + 0xc4, 0xee, 0xfa, 0x74, 0xdd, 0x0e, 0x0a, 0xb5, 0xf2, 0x48, 0x3a, 0x78, 0x26, 0x97, 0x61, 0x1a, + 0xbe, 0x1d, 0xa4, 0x1b, 0xca, 0x69, 0x91, 0x2a, 0x17, 0x5e, 0x4e, 0xab, 0xf9, 0x2a, 0x25, 0x01, + 0xca, 0x1f, 0xb3, 0x4f, 0x08, 0x4b, 0x29, 0xf2, 0xfa, 0xb3, 0x19, 0x54, 0x58, 0x77, 0x0d, 0xcb, + 0xf7, 0x84, 0x8f, 0xfe, 0xcc, 0xf4, 0x8e, 0x6b, 0x38, 0x6c, 0x7c, 0x4c, 0xc3, 0x2d, 0xde, 0x3b, + 0x46, 0xab, 0x4b, 0xbd, 0xd9, 0x77, 0xd9, 0xdb, 0xfd, 0x43, 0xaf, 0xf2, 0xa9, 0x4d, 0x70, 0x4e, + 0x4d, 0x37, 0xed, 0xf6, 0xcc, 0xa6, 0x6b, 0x6c, 0x5b, 0xbc, 0x00, 0xb8, 0xd1, 0x9a, 0xf1, 0x69, + 0x8b, 0x3a, 0xb6, 0xeb, 0xcf, 0x18, 0x8e, 0x35, 0xe3, 0xef, 0x3a, 0xd4, 0x9b, 0x09, 0x25, 0xf1, + 0x16, 0xd8, 0x10, 0xf0, 0xe1, 0x3f, 0x79, 0x08, 0x70, 0x1c, 0x5e, 0x46, 0x48, 0xbc, 0x6a, 0xd5, + 0x71, 0x84, 0xc3, 0x5f, 0xf2, 0x66, 0x06, 0x18, 0x3e, 0xb0, 0x43, 0x85, 0x19, 0x8e, 0x5c, 0xfe, + 0x45, 0x92, 0xc0, 0x46, 0xc1, 0xba, 0x78, 0xa2, 0x40, 0x4d, 0x63, 0x91, 0xc6, 0x83, 0x87, 0x4d, + 0x51, 0x52, 0x9c, 0x0d, 0x6f, 0xa0, 0x71, 0x21, 0x37, 0x8c, 0xaa, 0x3b, 0xa5, 0x2e, 0x1a, 0x31, + 0x34, 0x1f, 0xb4, 0xe1, 0x33, 0x9a, 0x02, 0x2c, 0xb7, 0x11, 0xe3, 0xc0, 0xb3, 0x51, 0xe6, 0x0d, + 0xd4, 0x9a, 0xd1, 0xc6, 0x61, 0xc4, 0x42, 0x29, 0xf4, 0x80, 0x9f, 0x97, 0xa8, 0x91, 0x8b, 0xa1, + 0x28, 0x2c, 0xb2, 0x0c, 0x3e, 0xea, 0x27, 0x52, 0x64, 0xc4, 0xc7, 0xbc, 0xca, 0x82, 0x6b, 0x68, + 0x2c, 0xf4, 0x37, 0xdc, 0x66, 0x2b, 0xdb, 0x64, 0x54, 0x95, 0x25, 0x16, 0xb0, 0x27, 0x0b, 0x51, + 0x78, 0xf0, 0x4b, 0xa8, 0xc4, 0x3d, 0xf6, 0x75, 0x7e, 0xc5, 0x10, 0x5c, 0xb6, 0x02, 0xac, 0x61, + 0xc9, 0x3d, 0x16, 0x12, 0xe2, 0xd7, 0x51, 0xb9, 0xfa, 0xee, 0x1a, 0x5b, 0x67, 0xaa, 0x64, 0xd9, + 0xd3, 0x4e, 0x47, 0xe1, 0xca, 0x90, 0x90, 0x6b, 0xb7, 0x68, 0xc3, 0x70, 0x95, 0xc5, 0x43, 0xa6, + 0xc7, 0xf3, 0x28, 0xf6, 0x85, 0x79, 0x6d, 0x2a, 0xfa, 0x76, 0xa0, 0x01, 0x98, 0x86, 0xa8, 0x48, + 0xa4, 0x64, 0x1d, 0xab, 0x4c, 0x6c, 0xd4, 0xcc, 0x59, 0x9e, 0xd1, 0x6a, 0xd9, 0x3b, 0x84, 0x5a, + 0x9e, 0xd7, 0xa5, 0x70, 0x3f, 0x51, 0xe2, 0xa3, 0xc6, 0x14, 0xa8, 0x86, 0xcb, 0x71, 0x4a, 0x4e, + 0xb8, 0xca, 0xa6, 0xff, 0x57, 0x46, 0x1e, 0xd0, 0x61, 0x95, 0xd9, 0x4c, 0x6a, 0x95, 0xd9, 0x2b, + 0x68, 0x44, 0x6c, 0x03, 0x61, 0x8c, 0x24, 0x64, 0x75, 0x04, 0x91, 0x00, 0x96, 0x49, 0x22, 0x02, + 0x88, 0xa8, 0x8f, 0x0a, 0x43, 0xe4, 0xa4, 0x88, 0xfa, 0xa8, 0x30, 0x84, 0x52, 0x16, 0xe2, 0x1a, + 0x2a, 0x8b, 0xc1, 0x2c, 0xe5, 0x8c, 0xc3, 0x6d, 0x7e, 0x90, 0xdf, 0xcc, 0x83, 0x05, 0x24, 0x22, + 0xfc, 0x2a, 0x42, 0x91, 0x7e, 0xc5, 0xa6, 0x05, 0x73, 0x4f, 0xee, 0x0e, 0x79, 0xee, 0x45, 0xd4, + 0xfa, 0xdf, 0x67, 0x12, 0x53, 0x86, 0x3d, 0x83, 0x88, 0x3b, 0x91, 0xf4, 0x00, 0xcf, 0x20, 0xa2, + 0x54, 0xc4, 0x33, 0x48, 0x44, 0xf8, 0x12, 0x2a, 0xc5, 0xf2, 0xe9, 0xe1, 0x9e, 0x3d, 0x4c, 0xa6, + 0x0f, 0xb1, 0xf8, 0x1a, 0x2a, 0xb1, 0x01, 0xcc, 0x44, 0x08, 0x85, 0x40, 0xa5, 0x9e, 0xae, 0x80, + 0xc9, 0x23, 0x2e, 0xa0, 0x63, 0x3c, 0x4a, 0x98, 0xac, 0xe0, 0x49, 0x99, 0xae, 0x51, 0x58, 0xec, + 0xef, 0x65, 0x07, 0x1c, 0x18, 0x1e, 0xdf, 0xe4, 0x84, 0x30, 0x51, 0x2c, 0x3f, 0x20, 0x51, 0xec, + 0x3f, 0xb2, 0x03, 0x4e, 0x43, 0x8f, 0x75, 0x42, 0x47, 0xa8, 0x0c, 0x35, 0xa1, 0x23, 0xca, 0xa5, + 0xb1, 0x4c, 0x22, 0x13, 0xc5, 0x52, 0xbf, 0x0a, 0xf7, 0x4d, 0xfd, 0xfa, 0xad, 0xdc, 0x5e, 0xa7, + 0xc5, 0x13, 0xdd, 0xef, 0x47, 0xf7, 0xd7, 0x50, 0x39, 0xd4, 0xac, 0x28, 0xe7, 0x32, 0x16, 0x86, + 0x43, 0x71, 0x30, 0xf0, 0x48, 0x44, 0xf8, 0x32, 0x7f, 0x56, 0xf8, 0x32, 0x76, 0x11, 0x18, 0xa0, + 0xe2, 0x09, 0x7b, 0xb6, 0x86, 0x67, 0xbd, 0x4f, 0x49, 0x88, 0xd6, 0xff, 0x34, 0x9b, 0x7a, 0xe4, + 0x3e, 0xe9, 0xa3, 0x7d, 0xf4, 0x51, 0x8a, 0x12, 0xb9, 0xb3, 0xe0, 0x44, 0x89, 0xfb, 0x50, 0xe2, + 0x8f, 0xb3, 0xa9, 0xae, 0x95, 0x13, 0x25, 0xee, 0x67, 0xb5, 0xb8, 0x82, 0x46, 0x88, 0xbd, 0xe3, + 0xd5, 0xec, 0x6e, 0xc7, 0x17, 0x6b, 0x05, 0x2c, 0xd4, 0xae, 0xbd, 0xe3, 0x35, 0x9a, 0x0c, 0x4a, + 0x22, 0x02, 0xfd, 0x27, 0xd9, 0x3d, 0x9c, 0x4f, 0x27, 0x8a, 0xff, 0x10, 0xb7, 0xc8, 0x8f, 0x3d, + 0x8f, 0xca, 0xd2, 0x07, 0x51, 0xf1, 0x28, 0x2a, 0xad, 0xcc, 0xae, 0xcd, 0x93, 0x77, 0xe6, 0xe7, + 0x26, 0x86, 0x30, 0x42, 0x85, 0xb9, 0xf9, 0xe5, 0xfa, 0xfc, 0xdc, 0x44, 0x66, 0x76, 0xe2, 0x83, + 0x7f, 0x7a, 0x7a, 0xe8, 0x83, 0x1f, 0x3e, 0x9d, 0xf9, 0xde, 0x0f, 0x9f, 0xce, 0xfc, 0xe3, 0x0f, + 0x9f, 0xce, 0x6c, 0x14, 0xc0, 0x08, 0x7d, 0xe9, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x99, + 0x5b, 0x81, 0x5d, 0x96, 0x00, 0x00, } func (m *Metadata) Marshal() (dAtA []byte, err error) { @@ -11941,13 +11871,6 @@ func (m *WindowsDesktopSessionStart) MarshalToSizedBuffer(dAtA []byte) (int, err i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - if len(m.DesktopName) > 0 { - i -= len(m.DesktopName) - copy(dAtA[i:], m.DesktopName) - i = encodeVarintEvents(dAtA, i, uint64(len(m.DesktopName))) - i-- - dAtA[i] = 0x5a - } if len(m.DesktopLabels) > 0 { for k := range m.DesktopLabels { v := m.DesktopLabels[k] @@ -12885,64 +12808,6 @@ func (m *RenewableCertificateGenerationMismatch) MarshalToSizedBuffer(dAtA []byt return len(dAtA) - i, nil } -func (m *Unknown) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *Unknown) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *Unknown) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.XXX_unrecognized != nil { - i -= len(m.XXX_unrecognized) - copy(dAtA[i:], m.XXX_unrecognized) - } - if len(m.Data) > 0 { - i -= len(m.Data) - copy(dAtA[i:], m.Data) - i = encodeVarintEvents(dAtA, i, uint64(len(m.Data))) - i-- - dAtA[i] = 0x22 - } - if len(m.UnknownCode) > 0 { - i -= len(m.UnknownCode) - copy(dAtA[i:], m.UnknownCode) - i = encodeVarintEvents(dAtA, i, uint64(len(m.UnknownCode))) - i-- - dAtA[i] = 0x1a - } - if len(m.UnknownType) > 0 { - i -= len(m.UnknownType) - copy(dAtA[i:], m.UnknownType) - i = encodeVarintEvents(dAtA, i, uint64(len(m.UnknownType))) - i-- - dAtA[i] = 0x12 - } - { - size, err := m.Metadata.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintEvents(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - func (m *OneOf) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -14766,29 +14631,6 @@ func (m *OneOf_RenewableCertificateGenerationMismatch) MarshalToSizedBuffer(dAtA } return len(dAtA) - i, nil } -func (m *OneOf_Unknown) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *OneOf_Unknown) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - if m.Unknown != nil { - { - size, err := m.Unknown.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintEvents(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x5 - i-- - dAtA[i] = 0x82 - } - return len(dAtA) - i, nil -} func (m *StreamStatus) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -14813,12 +14655,12 @@ func (m *StreamStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n357, err357 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastUploadTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastUploadTime):]) - if err357 != nil { - return 0, err357 + n355, err355 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastUploadTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastUploadTime):]) + if err355 != nil { + return 0, err355 } - i -= n357 - i = encodeVarintEvents(dAtA, i, uint64(n357)) + i -= n355 + i = encodeVarintEvents(dAtA, i, uint64(n355)) i-- dAtA[i] = 0x1a if m.LastEventIndex != 0 { @@ -15048,12 +14890,12 @@ func (m *Identity) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x4a } - n363, err363 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err363 != nil { - return 0, err363 + n361, err361 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err361 != nil { + return 0, err361 } - i -= n363 - i = encodeVarintEvents(dAtA, i, uint64(n363)) + i -= n361 + i = encodeVarintEvents(dAtA, i, uint64(n361)) i-- dAtA[i] = 0x42 if len(m.KubernetesUsers) > 0 { @@ -17680,10 +17522,6 @@ func (m *WindowsDesktopSessionStart) Size() (n int) { n += mapEntrySize + 1 + sovEvents(uint64(mapEntrySize)) } } - l = len(m.DesktopName) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -17985,32 +17823,6 @@ func (m *RenewableCertificateGenerationMismatch) Size() (n int) { return n } -func (m *Unknown) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Metadata.Size() - n += 1 + l + sovEvents(uint64(l)) - l = len(m.UnknownType) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } - l = len(m.UnknownCode) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } - l = len(m.Data) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } - if m.XXX_unrecognized != nil { - n += len(m.XXX_unrecognized) - } - return n -} - func (m *OneOf) Size() (n int) { if m == nil { return 0 @@ -18974,18 +18786,6 @@ func (m *OneOf_RenewableCertificateGenerationMismatch) Size() (n int) { } return n } -func (m *OneOf_Unknown) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Unknown != nil { - l = m.Unknown.Size() - n += 2 + l + sovEvents(uint64(l)) - } - return n -} func (m *StreamStatus) Size() (n int) { if m == nil { return 0 @@ -35336,38 +35136,6 @@ func (m *WindowsDesktopSessionStart) Unmarshal(dAtA []byte) error { } m.DesktopLabels[mapkey] = mapvalue iNdEx = postIndex - case 11: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DesktopName", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DesktopName = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) @@ -37762,186 +37530,6 @@ func (m *RenewableCertificateGenerationMismatch) Unmarshal(dAtA []byte) error { } return nil } -func (m *Unknown) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Unknown: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Unknown: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UnknownType", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.UnknownType = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UnknownCode", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.UnknownCode = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Data = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipEvents(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthEvents - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *OneOf) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -40736,41 +40324,6 @@ func (m *OneOf) Unmarshal(dAtA []byte) error { } m.Event = &OneOf_RenewableCertificateGenerationMismatch{v} iNdEx = postIndex - case 80: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Unknown", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - v := &Unknown{} - if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - m.Event = &OneOf_Unknown{v} - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/api/types/events/events.proto b/api/types/events/events.proto index 7a1e69e52b463..6cdcd923ddbd2 100644 --- a/api/types/events/events.proto +++ b/api/types/events/events.proto @@ -1573,8 +1573,6 @@ message WindowsDesktopSessionStart { string WindowsUser = 9 [ (gogoproto.jsontag) = "windows_user" ]; // DesktopLabels are the labels on the desktop resource. map DesktopLabels = 10 [ (gogoproto.jsontag) = "desktop_labels" ]; - // DesktopName is the name of the desktop resource. - string DesktopName = 11 [ (gogoproto.jsontag) = "desktop_name" ]; } // DatabaseSessionEnd is emitted when a user ends the database session. @@ -1784,22 +1782,6 @@ message RenewableCertificateGenerationMismatch { [ (gogoproto.nullable) = false, (gogoproto.embed) = true, (gogoproto.jsontag) = "" ]; } -// Unknown is a fallback event used when we don't recognize an event from the backend. -message Unknown { - // Metadata is a common event metadata. - Metadata Metadata = 1 - [ (gogoproto.nullable) = false, (gogoproto.embed) = true, (gogoproto.jsontag) = "" ]; - - // UnknownType is the event type extracted from the unknown event. - string UnknownType = 2 [ (gogoproto.jsontag) = "unknown_event" ]; - - // UnknownCode is the event code extracted from the unknown event. - string UnknownCode = 3 [ (gogoproto.jsontag) = "unknown_code,omitempty" ]; - - // Data is the serialized JSON data of the unknown event. - string Data = 4 [ (gogoproto.jsontag) = "data" ]; -} - // OneOf is a union of one of audit events submitted to the auth service message OneOf { // Event is one of the audit events @@ -1883,7 +1865,6 @@ message OneOf { events.MySQLStatementFetch MySQLStatementFetch = 77; events.MySQLStatementBulkExecute MySQLStatementBulkExecute = 78; events.RenewableCertificateGenerationMismatch RenewableCertificateGenerationMismatch = 79; - events.Unknown Unknown = 80; } } diff --git a/api/types/events/oneof.go b/api/types/events/oneof.go index 8181e7c98bcc0..5d3476d536e9d 100644 --- a/api/types/events/oneof.go +++ b/api/types/events/oneof.go @@ -17,11 +17,9 @@ limitations under the License. package events import ( - "encoding/json" "reflect" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" ) // MustToOneOf converts audit event to OneOf @@ -355,28 +353,8 @@ func ToOneOf(in AuditEvent) (*OneOf, error) { out.Event = &OneOf_RenewableCertificateGenerationMismatch{ RenewableCertificateGenerationMismatch: e, } - case *Unknown: - out.Event = &OneOf_Unknown{ - Unknown: e, - } default: - log.Errorf("Attempted to convert dynamic event of unknown type \"%v\" into protobuf event.", in.GetType()) - unknown := &Unknown{} - unknown.Type = UnknownEvent - unknown.Code = UnknownCode - unknown.Time = in.GetTime() - unknown.ClusterName = in.GetClusterName() - unknown.UnknownType = in.GetType() - unknown.UnknownCode = in.GetCode() - data, err := json.Marshal(in) - if err != nil { - return nil, trace.Wrap(err) - } - - unknown.Data = string(data) - out.Event = &OneOf_Unknown{ - Unknown: unknown, - } + return nil, trace.BadParameter("event type %T is not supported", in) } return &out, nil } diff --git a/api/types/events/unknown.go b/api/types/events/unknown.go deleted file mode 100644 index d2f55cacd24a4..0000000000000 --- a/api/types/events/unknown.go +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2021 Gravitational, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package events - -const ( - // UnknownEvent is any event received that isn't recognized as any other event type. - UnknownEvent = "unknown" - - // UnknownCode is used when an event of unknown type is encountered. - UnknownCode = "TCC00E" -) diff --git a/api/types/kubernetes.go b/api/types/kubernetes.go index 511f527defa29..13e5c7545c946 100644 --- a/api/types/kubernetes.go +++ b/api/types/kubernetes.go @@ -18,7 +18,6 @@ package types import ( "fmt" - "sort" "time" "github.com/gogo/protobuf/proto" @@ -26,30 +25,6 @@ import ( "github.com/gravitational/trace" ) -// KubeCluster represents a kubernetes cluster. -type KubeCluster interface { - // ResourceWithLabels provides common resource methods. - ResourceWithLabels - // GetNamespace returns the kube cluster namespace. - GetNamespace() string - // GetStaticLabels returns the kube cluster static labels. - GetStaticLabels() map[string]string - // SetStaticLabels sets the kube cluster static labels. - SetStaticLabels(map[string]string) - // GetDynamicLabels returns the kube cluster dynamic labels. - GetDynamicLabels() map[string]CommandLabel - // SetDynamicLabels sets the kube cluster dynamic labels. - SetDynamicLabels(map[string]CommandLabel) - // LabelsString returns all labels as a string. - LabelsString() string - // String returns string representation of the kube cluster. - String() string - // GetDescription returns the kube cluster description. - GetDescription() string - // Copy returns a copy of this kube cluster resource. - Copy() *KubernetesClusterV3 -} - // NewKubernetesClusterV3FromLegacyCluster creates a new Kubernetes cluster resource // from the legacy type. func NewKubernetesClusterV3FromLegacyCluster(namespace string, cluster *KubernetesCluster) (*KubernetesClusterV3, error) { @@ -217,76 +192,3 @@ func (k *KubernetesClusterV3) CheckAndSetDefaults() error { return nil } - -// KubeClusters represents a list of kube clusters. -type KubeClusters []KubeCluster - -// Len returns the slice length. -func (s KubeClusters) Len() int { return len(s) } - -// Less compares kube clusters by name. -func (s KubeClusters) Less(i, j int) bool { - return s[i].GetName() < s[j].GetName() -} - -// Swap swaps two kube clusters. -func (s KubeClusters) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// SortByCustom custom sorts by given sort criteria. -func (s KubeClusters) SortByCustom(sortBy SortBy) error { - if sortBy.Field == "" { - return nil - } - - isDesc := sortBy.IsDesc - switch sortBy.Field { - case ResourceMetadataName: - sort.SliceStable(s, func(i, j int) bool { - return stringCompare(s[i].GetName(), s[j].GetName(), isDesc) - }) - default: - return trace.NotImplemented("sorting by field %q for resource %q is not supported", sortBy.Field, KindKubernetesCluster) - } - - return nil -} - -// AsResources returns as type resources with labels. -func (s KubeClusters) AsResources() []ResourceWithLabels { - resources := make([]ResourceWithLabels, 0, len(s)) - for _, cluster := range s { - resources = append(resources, ResourceWithLabels(cluster)) - } - return resources -} - -// GetFieldVals returns list of select field values. -func (s KubeClusters) GetFieldVals(field string) ([]string, error) { - vals := make([]string, 0, len(s)) - switch field { - case ResourceMetadataName: - for _, server := range s { - vals = append(vals, server.GetName()) - } - default: - return nil, trace.NotImplemented("getting field %q for resource %q is not supported", field, KindKubernetesCluster) - } - - return vals, nil -} - -// DeduplicateKubeClusters deduplicates kube clusters by name. -func DeduplicateKubeClusters(kubeclusters []KubeCluster) []KubeCluster { - seen := make(map[string]struct{}) - result := make([]KubeCluster, 0, len(kubeclusters)) - - for _, cluster := range kubeclusters { - if _, ok := seen[cluster.GetName()]; ok { - continue - } - seen[cluster.GetName()] = struct{}{} - result = append(result, cluster) - } - - return result -} diff --git a/api/types/kubernetes_test.go b/api/types/kubernetes_test.go deleted file mode 100644 index 2931ebf626a0f..0000000000000 --- a/api/types/kubernetes_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright 2022 Gravitational, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package types - -import ( - "testing" - - "github.com/gravitational/trace" - "github.com/stretchr/testify/require" -) - -func TestKubeClustersSorter(t *testing.T) { - t.Parallel() - - makeClusters := func(testVals []string, testField string) []KubeCluster { - servers := make([]KubeCluster, len(testVals)) - for i := 0; i < len(testVals); i++ { - var err error - servers[i], err = NewKubernetesClusterV3FromLegacyCluster("_", &KubernetesCluster{ - Name: testVals[i], - }) - require.NoError(t, err) - } - return servers - } - - testValsUnordered := []string{"d", "b", "a", "c"} - - // Test descending. - sortBy := SortBy{Field: ResourceMetadataName, IsDesc: true} - clusters := KubeClusters(makeClusters(testValsUnordered, ResourceMetadataName)) - require.NoError(t, clusters.SortByCustom(sortBy)) - targetVals, err := clusters.GetFieldVals(ResourceMetadataName) - require.NoError(t, err) - require.IsDecreasing(t, targetVals) - - // Test ascending. - sortBy = SortBy{Field: ResourceMetadataName} - clusters = KubeClusters(makeClusters(testValsUnordered, ResourceMetadataName)) - require.NoError(t, clusters.SortByCustom(sortBy)) - targetVals, err = clusters.GetFieldVals(ResourceMetadataName) - require.NoError(t, err) - require.IsIncreasing(t, targetVals) - - // Test error. - sortBy = SortBy{Field: "unsupported"} - clusters = KubeClusters(makeClusters(testValsUnordered, ResourceMetadataName)) - require.True(t, trace.IsNotImplemented(clusters.SortByCustom(sortBy))) -} - -func TestDeduplicateKubeClusters(t *testing.T) { - t.Parallel() - - expected := []KubeCluster{ - &KubernetesClusterV3{Metadata: Metadata{Name: "a"}}, - &KubernetesClusterV3{Metadata: Metadata{Name: "b"}}, - &KubernetesClusterV3{Metadata: Metadata{Name: "c"}}, - } - - extra := []KubeCluster{ - &KubernetesClusterV3{Metadata: Metadata{Name: "a"}}, - &KubernetesClusterV3{Metadata: Metadata{Name: "a"}}, - &KubernetesClusterV3{Metadata: Metadata{Name: "b"}}, - } - - clusters := append(expected, extra...) - - result := DeduplicateKubeClusters(clusters) - require.ElementsMatch(t, result, expected) -} diff --git a/api/types/resource.go b/api/types/resource.go index 65c18167cff4c..ef52ae8ca0f01 100644 --- a/api/types/resource.go +++ b/api/types/resource.go @@ -90,21 +90,14 @@ type ResourceWithLabels interface { // ResourcesWithLabels is a list of labeled resources. type ResourcesWithLabels []ResourceWithLabels -// ResourcesWithLabelsMap is like ResourcesWithLabels, but a map from resource name to its value. -type ResourcesWithLabelsMap map[string]ResourceWithLabels - -// ToMap returns these databases as a map keyed by database name. -func (r ResourcesWithLabels) ToMap() ResourcesWithLabelsMap { - rm := make(ResourcesWithLabelsMap, len(r)) - - // there may be duplicate resources in the input list. - // by iterating from end to start, the first resource of given name wins. - for i := len(r) - 1; i >= 0; i-- { - resource := r[i] - rm[resource.GetName()] = resource +// Find returns resource with the specified name or nil. +func (r ResourcesWithLabels) Find(name string) ResourceWithLabels { + for _, resource := range r { + if resource.GetName() == name { + return resource + } } - - return rm + return nil } // Len returns the slice length. @@ -155,32 +148,6 @@ func (r ResourcesWithLabels) AsDatabaseServers() ([]DatabaseServer, error) { return dbs, nil } -// AsWindowsDesktops converts each resource into type WindowsDesktop. -func (r ResourcesWithLabels) AsWindowsDesktops() ([]WindowsDesktop, error) { - desktops := make([]WindowsDesktop, 0, len(r)) - for _, resource := range r { - desktop, ok := resource.(WindowsDesktop) - if !ok { - return nil, trace.BadParameter("expected types.WindowsDesktop, got: %T", resource) - } - desktops = append(desktops, desktop) - } - return desktops, nil -} - -// AsKubeClusters converts each resource into type KubeCluster. -func (r ResourcesWithLabels) AsKubeClusters() ([]KubeCluster, error) { - clusters := make([]KubeCluster, 0, len(r)) - for _, resource := range r { - cluster, ok := resource.(KubeCluster) - if !ok { - return nil, trace.BadParameter("expected types.KubeCluster, got: %T", resource) - } - clusters = append(clusters, cluster) - } - return clusters, nil -} - // GetVersion returns resource version func (h *ResourceHeader) GetVersion() string { return h.Version diff --git a/api/types/resource_test.go b/api/types/resource_test.go index 9dd7c9e88b80f..b0f9c9bb4146a 100644 --- a/api/types/resource_test.go +++ b/api/types/resource_test.go @@ -322,53 +322,3 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) { }) } } - -func TestResourcesWithLabels_ToMap(t *testing.T) { - mkServerHost := func(name string, hostname string) ResourceWithLabels { - server, err := NewServerWithLabels(name, KindNode, ServerSpecV2{ - Hostname: hostname + ".example.com", - Addr: name + ".example.com", - }, nil) - require.NoError(t, err) - - return server - } - - mkServer := func(name string) ResourceWithLabels { - return mkServerHost(name, name) - } - - tests := []struct { - name string - r ResourcesWithLabels - want ResourcesWithLabelsMap - }{ - { - name: "empty", - r: nil, - want: map[string]ResourceWithLabels{}, - }, - { - name: "simple list", - r: []ResourceWithLabels{mkServer("a"), mkServer("b"), mkServer("c")}, - want: map[string]ResourceWithLabels{ - "a": mkServer("a"), - "b": mkServer("b"), - "c": mkServer("c"), - }, - }, - { - name: "first duplicate wins", - r: []ResourceWithLabels{mkServerHost("a", "a1"), mkServerHost("a", "a2"), mkServerHost("a", "a3")}, - want: map[string]ResourceWithLabels{ - "a": mkServerHost("a", "a1"), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.r.ToMap(), tt.want) - }) - } -} diff --git a/api/types/role.go b/api/types/role.go index 0953b122624a7..2f4d0ad9af991 100644 --- a/api/types/role.go +++ b/api/types/role.go @@ -159,11 +159,11 @@ type Role interface { GetSessionPolicySet() SessionTrackerPolicySet } -// NewRole constructs new standard V5 role. -// This creates a V5 role with V4+ RBAC semantics. +// NewRole constructs new standard V3 role. +// This is mostly a legacy function and will create a role with V3 RBAC semantics. func NewRole(name string, spec RoleSpecV5) (Role, error) { role := RoleV5{ - Version: V5, + Version: V3, Metadata: Metadata{ Name: name, }, @@ -175,11 +175,11 @@ func NewRole(name string, spec RoleSpecV5) (Role, error) { return &role, nil } -// NewRoleV3 constructs new standard V3 role. -// This is mostly a legacy function and will create a role with V3 RBAC semantics. -func NewRoleV3(name string, spec RoleSpecV5) (Role, error) { +// NewRoleV5 constructs new standard V5 role. +// This creates a V5 role with V4+ RBAC semantics. This should be preferred over `NewRole`. +func NewRoleV5(name string, spec RoleSpecV5) (Role, error) { role := RoleV5{ - Version: V3, + Version: V5, Metadata: Metadata{ Name: name, }, @@ -604,8 +604,11 @@ func (r *RoleV5) SetRules(rct RoleConditionType, in []Rule) { // setStaticFields sets static resource header and metadata fields. func (r *RoleV5) setStaticFields() { r.Kind = KindRole - if r.Version != V3 && r.Version != V4 { - r.Version = V5 + // TODO(Joerger/nklaassen) Role should default to V4 + // but shouldn't overwrite V3. For now, this does the + // opposite due to an internal reliance on V3 defaults. + if r.Version != V4 && r.Version != V5 { + r.Version = V3 } } diff --git a/api/types/server_test.go b/api/types/server_test.go index 4b145d7bb0ad2..b5de2617ae0e4 100644 --- a/api/types/server_test.go +++ b/api/types/server_test.go @@ -76,6 +76,8 @@ func TestServerSorter(t *testing.T) { for _, c := range cases { c := c t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) { + t.Parallel() + sortBy := SortBy{Field: c.fieldName, IsDesc: true} servers := Servers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) @@ -85,6 +87,8 @@ func TestServerSorter(t *testing.T) { }) t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) { + t.Parallel() + sortBy := SortBy{Field: c.fieldName} servers := Servers(makeServers(testValsUnordered, c.fieldName)) require.NoError(t, servers.SortByCustom(sortBy)) diff --git a/api/types/types.pb.go b/api/types/types.pb.go index 29ee65f6c47ad..b8185b731b525 100644 --- a/api/types/types.pb.go +++ b/api/types/types.pb.go @@ -8156,9 +8156,7 @@ type WindowsDesktopServiceSpecV3 struct { // Addr is the address that this service can be reached at. Addr string `protobuf:"bytes,1,opt,name=Addr,proto3" json:"addr"` // TeleportVersion is teleport binary version running this service. - TeleportVersion string `protobuf:"bytes,2,opt,name=TeleportVersion,proto3" json:"teleport_version"` - // Hostname is the desktop service hostname. - Hostname string `protobuf:"bytes,3,opt,name=Hostname,proto3" json:"hostname"` + TeleportVersion string `protobuf:"bytes,2,opt,name=TeleportVersion,proto3" json:"teleport_version"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -8197,11 +8195,8 @@ func (m *WindowsDesktopServiceSpecV3) XXX_DiscardUnknown() { var xxx_messageInfo_WindowsDesktopServiceSpecV3 proto.InternalMessageInfo -// WindowsDesktopFilter are filters to apply when searching for windows desktops. type WindowsDesktopFilter struct { - // HostID is the ID of the host the Windows Desktop Service proxying the desktop. - HostID string `protobuf:"bytes,1,opt,name=HostID,proto3" json:"host_id"` - // Name is the name of the desktop. + HostID string `protobuf:"bytes,1,opt,name=HostID,proto3" json:"host_id"` Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"name"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -9005,731 +9000,730 @@ func init() { func init() { proto.RegisterFile("types.proto", fileDescriptor_d938547f84707355) } var fileDescriptor_d938547f84707355 = []byte{ - // 11573 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0xbd, 0x5d, 0x6c, 0x24, 0x49, - 0x72, 0x18, 0x3c, 0xd5, 0xdd, 0x24, 0xbb, 0x83, 0x4d, 0xb2, 0x99, 0x9c, 0x1f, 0xce, 0xec, 0xec, - 0xf6, 0x5c, 0xed, 0xdf, 0xcc, 0xdc, 0xee, 0xf0, 0x86, 0x73, 0x3b, 0xd2, 0xde, 0xfe, 0x5d, 0x37, - 0xc9, 0x99, 0xe1, 0x0e, 0x87, 0xe4, 0x56, 0xf3, 0xe7, 0x4e, 0x77, 0xa7, 0x52, 0xb1, 0x2b, 0x87, - 0xac, 0x65, 0x77, 0x57, 0xab, 0xaa, 0x7a, 0x66, 0x28, 0x7d, 0x82, 0xf4, 0xc1, 0x90, 0x05, 0x41, - 0xd0, 0xfd, 0x08, 0x27, 0x4b, 0x32, 0x64, 0x48, 0x16, 0x2c, 0xd8, 0xb2, 0x71, 0x7e, 0x90, 0x0d, - 0xd8, 0x06, 0x0c, 0x03, 0x02, 0x0c, 0xe1, 0x1e, 0x6c, 0x58, 0x6f, 0x86, 0x64, 0x83, 0xb6, 0xee, - 0xfc, 0x22, 0x02, 0x36, 0x0c, 0xf8, 0x49, 0x67, 0x0b, 0x36, 0x32, 0x32, 0xb3, 0x2a, 0xb3, 0xba, - 0x9a, 0x6c, 0xee, 0xce, 0x02, 0x37, 0xf3, 0x44, 0x76, 0x64, 0x44, 0x54, 0xfe, 0x46, 0x46, 0x64, - 0x46, 0x44, 0xc2, 0x78, 0x74, 0xd0, 0xa5, 0xe1, 0x8d, 0x6e, 0xe0, 0x47, 0x3e, 0x19, 0xc1, 0x1f, - 0x97, 0xce, 0xee, 0xfa, 0xbb, 0x3e, 0x42, 0xe6, 0xd8, 0x7f, 0xbc, 0xf0, 0x52, 0x75, 0xd7, 0xf7, - 0x77, 0x5b, 0x74, 0x0e, 0x7f, 0xed, 0xf4, 0x1e, 0xce, 0x45, 0x5e, 0x9b, 0x86, 0x91, 0xd3, 0xee, - 0x0a, 0x84, 0x85, 0x5d, 0x2f, 0xda, 0xeb, 0xed, 0xdc, 0x68, 0xfa, 0xed, 0xb9, 0xdd, 0xc0, 0x79, - 0xe4, 0x45, 0x4e, 0xe4, 0xf9, 0x1d, 0xa7, 0x35, 0x17, 0xd1, 0x16, 0xed, 0xfa, 0x41, 0x34, 0xe7, - 0x74, 0xbd, 0x39, 0xfc, 0xc6, 0xdc, 0xe3, 0xc0, 0xe9, 0x76, 0x69, 0x90, 0xfc, 0xc3, 0x99, 0x98, - 0x7f, 0x3f, 0x0f, 0xa5, 0xfb, 0x94, 0x76, 0x6b, 0x2d, 0xef, 0x11, 0x25, 0x2f, 0x43, 0x61, 0xd5, - 0x69, 0xd3, 0x59, 0xe3, 0x8a, 0x71, 0xb5, 0x54, 0x9f, 0x3a, 0x3a, 0xac, 0x8e, 0x87, 0x34, 0x78, - 0x44, 0x03, 0xbb, 0xe3, 0xb4, 0xa9, 0x85, 0x85, 0xe4, 0xf3, 0x50, 0x62, 0x7f, 0xc3, 0xae, 0xd3, - 0xa4, 0xb3, 0x39, 0xc4, 0x9c, 0x38, 0x3a, 0xac, 0x96, 0x3a, 0x12, 0x68, 0x25, 0xe5, 0xe4, 0x35, - 0x18, 0x5b, 0xa1, 0x4e, 0x48, 0x97, 0x17, 0x67, 0xf3, 0x57, 0x8c, 0xab, 0xf9, 0x7a, 0xf9, 0xe8, - 0xb0, 0x5a, 0x6c, 0x31, 0x90, 0xed, 0xb9, 0x96, 0x2c, 0x24, 0xcb, 0x30, 0xb6, 0xf4, 0xa4, 0xeb, - 0x05, 0x34, 0x9c, 0x2d, 0x5c, 0x31, 0xae, 0x8e, 0xcf, 0x5f, 0xba, 0xc1, 0xdb, 0x7f, 0x43, 0xb6, - 0xff, 0xc6, 0x86, 0x6c, 0x7f, 0x7d, 0xe6, 0xfb, 0x87, 0xd5, 0x33, 0x47, 0x87, 0xd5, 0x31, 0xca, - 0x49, 0xbe, 0xfd, 0x5f, 0xaa, 0x86, 0x25, 0xe9, 0xc9, 0xbb, 0x50, 0xd8, 0x38, 0xe8, 0xd2, 0xd9, - 0xd2, 0x15, 0xe3, 0xea, 0xe4, 0xfc, 0x4b, 0x37, 0x78, 0x8f, 0xc7, 0x8d, 0x4c, 0xfe, 0x63, 0x58, - 0xf5, 0xe2, 0xd1, 0x61, 0xb5, 0xc0, 0x50, 0x2c, 0xa4, 0x22, 0x6f, 0xc2, 0xe8, 0x3d, 0x3f, 0x8c, - 0x96, 0x17, 0x67, 0x01, 0x9b, 0x76, 0xee, 0xe8, 0xb0, 0x3a, 0xbd, 0xe7, 0x87, 0x91, 0xed, 0xb9, - 0x6f, 0xf8, 0x6d, 0x2f, 0xa2, 0xed, 0x6e, 0x74, 0x60, 0x09, 0x24, 0x73, 0x07, 0x26, 0x34, 0x7e, - 0x64, 0x1c, 0xc6, 0x36, 0x57, 0xef, 0xaf, 0xae, 0x6d, 0xaf, 0x56, 0xce, 0x90, 0x22, 0x14, 0x56, - 0xd7, 0x16, 0x97, 0x2a, 0x06, 0x19, 0x83, 0x7c, 0x6d, 0x7d, 0xbd, 0x92, 0x23, 0x65, 0x28, 0x2e, - 0xd6, 0x36, 0x6a, 0xf5, 0x5a, 0x63, 0xa9, 0x92, 0x27, 0x33, 0x30, 0xb5, 0xbd, 0xbc, 0xba, 0xb8, - 0xb6, 0xdd, 0xb0, 0x17, 0x97, 0x1a, 0xf7, 0x37, 0xd6, 0xd6, 0x2b, 0x05, 0x32, 0x09, 0x70, 0x7f, - 0xb3, 0xbe, 0x64, 0xad, 0x2e, 0x6d, 0x2c, 0x35, 0x2a, 0x23, 0xe6, 0xaf, 0xe4, 0xa1, 0xf8, 0x80, - 0x46, 0x8e, 0xeb, 0x44, 0x0e, 0xb9, 0xac, 0x0d, 0x11, 0xd6, 0x5e, 0x19, 0x9b, 0x97, 0xfb, 0xc7, - 0x66, 0xe4, 0xe8, 0xb0, 0x6a, 0xbc, 0xa9, 0x8e, 0xc9, 0x3b, 0x30, 0xbe, 0x48, 0xc3, 0x66, 0xe0, - 0x75, 0xd9, 0x7c, 0xc1, 0x71, 0x29, 0xd5, 0x2f, 0x1e, 0x1d, 0x56, 0xcf, 0xb9, 0x09, 0x58, 0x69, - 0xab, 0x8a, 0x4d, 0x96, 0x61, 0x74, 0xc5, 0xd9, 0xa1, 0xad, 0x70, 0x76, 0xe4, 0x4a, 0xfe, 0xea, - 0xf8, 0xfc, 0x0b, 0xa2, 0x7f, 0x65, 0x05, 0x6f, 0xf0, 0xd2, 0xa5, 0x4e, 0x14, 0x1c, 0xd4, 0xcf, - 0x1e, 0x1d, 0x56, 0x2b, 0x2d, 0x04, 0xa8, 0x7d, 0xc7, 0x51, 0x48, 0x23, 0x19, 0xf3, 0xd1, 0x13, - 0xc7, 0xfc, 0xc5, 0xef, 0x1f, 0x56, 0x0d, 0x36, 0x16, 0x62, 0xcc, 0x13, 0x7e, 0xfa, 0xe8, 0x5f, - 0x81, 0xdc, 0xf2, 0xe2, 0xec, 0x18, 0xce, 0xb5, 0xca, 0xd1, 0x61, 0xb5, 0xac, 0x0d, 0x5b, 0x6e, - 0x79, 0xf1, 0xd2, 0xdb, 0x30, 0xae, 0xd4, 0x91, 0x54, 0x20, 0xbf, 0x4f, 0x0f, 0x78, 0x7f, 0x5a, - 0xec, 0x5f, 0x72, 0x16, 0x46, 0x1e, 0x39, 0xad, 0x9e, 0xe8, 0x40, 0x8b, 0xff, 0xf8, 0x52, 0xee, - 0x27, 0x0d, 0xf3, 0x37, 0x0a, 0x50, 0xb4, 0x7c, 0xbe, 0xce, 0xc8, 0x35, 0x18, 0x69, 0x44, 0x4e, - 0x24, 0x87, 0x62, 0xe6, 0xe8, 0xb0, 0x3a, 0x15, 0x32, 0x80, 0xf2, 0x3d, 0x8e, 0xc1, 0x50, 0xd7, - 0xf7, 0x9c, 0x50, 0x0e, 0x09, 0xa2, 0x76, 0x19, 0x40, 0x45, 0x45, 0x0c, 0xf2, 0x1a, 0x14, 0x1e, - 0xf8, 0x2e, 0x15, 0xa3, 0x42, 0x8e, 0x0e, 0xab, 0x93, 0x6d, 0xdf, 0x55, 0x11, 0xb1, 0x9c, 0xbc, - 0x01, 0xa5, 0x85, 0x5e, 0x10, 0xd0, 0x0e, 0x9b, 0xaa, 0x05, 0x44, 0x9e, 0x3c, 0x3a, 0xac, 0x42, - 0x93, 0x03, 0xd9, 0xe2, 0x4a, 0x10, 0x58, 0x57, 0x37, 0x22, 0x27, 0x88, 0xa8, 0x3b, 0x3b, 0x32, - 0x54, 0x57, 0xb3, 0xe5, 0x35, 0x1d, 0x72, 0x92, 0x74, 0x57, 0x0b, 0x4e, 0xe4, 0x1e, 0x8c, 0xdf, - 0x0d, 0x9c, 0x26, 0x5d, 0xa7, 0x81, 0xe7, 0xbb, 0x38, 0x86, 0xf9, 0xfa, 0x6b, 0x47, 0x87, 0xd5, - 0xf3, 0xbb, 0x0c, 0x6c, 0x77, 0x11, 0x9e, 0x50, 0xff, 0xe8, 0xb0, 0x5a, 0x5c, 0xec, 0x05, 0xd8, - 0x7b, 0x96, 0x4a, 0x4a, 0x7e, 0x86, 0x0d, 0x49, 0x18, 0x61, 0xd7, 0x52, 0x17, 0x47, 0xef, 0xf8, - 0x2a, 0x9a, 0xa2, 0x8a, 0xe7, 0x5b, 0x4e, 0x18, 0xd9, 0x01, 0xa7, 0x4b, 0xd5, 0x53, 0x65, 0x49, - 0xd6, 0xa0, 0xd8, 0x68, 0xee, 0x51, 0xb7, 0xd7, 0xa2, 0xb3, 0x45, 0x64, 0x7f, 0x41, 0x4c, 0x5c, - 0x39, 0x9e, 0xb2, 0xb8, 0x7e, 0x49, 0xf0, 0x26, 0xa1, 0x80, 0x28, 0x7d, 0x1f, 0x33, 0xf9, 0x52, - 0xf1, 0xb7, 0x7f, 0xbf, 0x7a, 0xe6, 0x97, 0xfe, 0xf3, 0x95, 0x33, 0xe6, 0xbf, 0xc8, 0x41, 0x25, - 0xcd, 0x84, 0x3c, 0x84, 0x89, 0xcd, 0xae, 0xeb, 0x44, 0x74, 0xa1, 0xe5, 0xd1, 0x4e, 0x14, 0xe2, - 0x24, 0x39, 0xbe, 0x4d, 0xaf, 0x88, 0xef, 0xce, 0xf6, 0x90, 0xd0, 0x6e, 0x72, 0xca, 0x54, 0xab, - 0x74, 0xb6, 0xc9, 0x77, 0x1a, 0x28, 0xa7, 0x43, 0x9c, 0x61, 0xa7, 0xfb, 0x0e, 0x97, 0xf0, 0x03, - 0xbe, 0x23, 0xd8, 0x8a, 0x09, 0xd4, 0x71, 0x77, 0x0e, 0x70, 0x66, 0x0e, 0x3f, 0x81, 0x18, 0x49, - 0xc6, 0x04, 0x62, 0x60, 0xf3, 0xbf, 0x19, 0x30, 0x69, 0xd1, 0xd0, 0xef, 0x05, 0x4d, 0x7a, 0x8f, - 0x3a, 0x2e, 0x0d, 0xd8, 0xf4, 0xbf, 0xef, 0x75, 0x5c, 0xb1, 0xa6, 0x70, 0xfa, 0xef, 0x7b, 0x1d, - 0x75, 0x09, 0x63, 0x39, 0xf9, 0x02, 0x8c, 0x35, 0x7a, 0x3b, 0x88, 0xca, 0xd7, 0xd4, 0x79, 0x1c, - 0xb1, 0xde, 0x8e, 0x9d, 0x42, 0x97, 0x68, 0x64, 0x0e, 0xc6, 0xb6, 0x68, 0x10, 0x26, 0x12, 0x0f, - 0x25, 0xfb, 0x23, 0x0e, 0x52, 0x09, 0x04, 0x16, 0xb9, 0x9b, 0x48, 0x5d, 0xb1, 0x27, 0x4d, 0xa5, - 0x64, 0x5d, 0x32, 0x55, 0xda, 0x02, 0xa2, 0x4e, 0x15, 0x89, 0x65, 0x7e, 0x27, 0x07, 0x95, 0x45, - 0x27, 0x72, 0x76, 0x9c, 0x50, 0xf4, 0xe7, 0xd6, 0x2d, 0x26, 0xc7, 0x95, 0x86, 0xa2, 0x1c, 0x67, - 0x35, 0xff, 0xc4, 0xcd, 0x7b, 0x35, 0xdd, 0xbc, 0x71, 0xb6, 0x41, 0x8a, 0xe6, 0x25, 0x8d, 0x7a, - 0xef, 0xe4, 0x46, 0x55, 0x44, 0xa3, 0x8a, 0xb2, 0x51, 0x49, 0x53, 0xc8, 0x7b, 0x50, 0x68, 0x74, - 0x69, 0x53, 0x08, 0x11, 0x29, 0xfb, 0xf5, 0xc6, 0x31, 0x84, 0xad, 0x5b, 0xf5, 0xb2, 0x60, 0x53, - 0x08, 0xbb, 0xb4, 0x69, 0x21, 0x99, 0xb2, 0x68, 0xbe, 0x3b, 0x0a, 0x67, 0xb3, 0xc8, 0xc8, 0x7b, - 0xfa, 0xe6, 0xc4, 0xbb, 0xe7, 0x85, 0x81, 0x9b, 0xd3, 0xac, 0xa1, 0x6f, 0x4f, 0xd7, 0xa1, 0xb8, - 0xce, 0x26, 0x64, 0xd3, 0x6f, 0x89, 0x9e, 0x63, 0x52, 0xb1, 0xd8, 0x95, 0x30, 0xc3, 0x8a, 0xcb, - 0xc9, 0x0b, 0x90, 0xdf, 0xb4, 0x96, 0x45, 0x77, 0x95, 0x8e, 0x0e, 0xab, 0xf9, 0x5e, 0xe0, 0xcd, - 0x1a, 0x16, 0x83, 0x92, 0x39, 0x18, 0x5d, 0xa8, 0x2d, 0xd0, 0x20, 0xc2, 0x6e, 0x2a, 0xd7, 0x2f, - 0xb0, 0xd9, 0xd2, 0x74, 0xec, 0x26, 0x0d, 0x22, 0xed, 0xf3, 0x02, 0x8d, 0x7c, 0x1e, 0xf2, 0xb5, - 0xed, 0x86, 0xe8, 0x19, 0x10, 0x3d, 0x53, 0xdb, 0x6e, 0xd4, 0x27, 0x44, 0x47, 0xe4, 0x9d, 0xc7, - 0x21, 0xe3, 0x5e, 0xdb, 0x6e, 0xa8, 0xa3, 0x35, 0x7a, 0xcc, 0x68, 0x5d, 0x85, 0x22, 0xd3, 0x33, - 0xd8, 0x06, 0x8f, 0x42, 0xb1, 0xc4, 0xd5, 0xa7, 0x3d, 0x01, 0xb3, 0xe2, 0x52, 0xf2, 0x72, 0xac, - 0xb6, 0x14, 0x13, 0x7e, 0x42, 0x6d, 0x91, 0xca, 0x0a, 0x79, 0x02, 0x13, 0x8b, 0x07, 0x1d, 0xa7, - 0xed, 0x35, 0xc5, 0x16, 0x5e, 0xc2, 0x2d, 0xfc, 0xc6, 0x31, 0xc3, 0x78, 0x43, 0x23, 0xe0, 0xbb, - 0xba, 0x14, 0xbe, 0xb3, 0x2e, 0x2f, 0xb3, 0xd3, 0x3b, 0xfc, 0xac, 0x61, 0xe9, 0x1f, 0x62, 0x6b, - 0x49, 0x8a, 0x48, 0xd4, 0xab, 0x92, 0x69, 0x27, 0xc1, 0xc9, 0x5a, 0x0a, 0x04, 0x44, 0x5d, 0x4b, - 0xf1, 0xa6, 0xfb, 0x1e, 0xe4, 0xef, 0x2e, 0xac, 0xcf, 0x8e, 0x23, 0x0f, 0x22, 0x78, 0xdc, 0x5d, - 0x58, 0x5f, 0x68, 0xf9, 0x3d, 0xb7, 0xf1, 0xd1, 0x4a, 0xfd, 0x82, 0x60, 0x33, 0xb1, 0xdb, 0xec, - 0x6a, 0x35, 0x62, 0x74, 0x64, 0x09, 0x8a, 0xb2, 0x95, 0xb3, 0x65, 0xe4, 0x31, 0x9d, 0x6a, 0xfc, - 0xd6, 0x2d, 0xbe, 0xd6, 0x5c, 0xf1, 0x5b, 0xad, 0x85, 0xc4, 0xb9, 0xb4, 0x0d, 0xa4, 0xbf, 0x5f, - 0x32, 0x34, 0x89, 0xcf, 0xab, 0x9a, 0xc4, 0xf8, 0xfc, 0x39, 0xf1, 0xad, 0x05, 0xbf, 0xdd, 0x76, - 0x3a, 0x2e, 0xd2, 0x6e, 0xcd, 0xab, 0x0a, 0x46, 0x0d, 0x26, 0x93, 0x8a, 0xac, 0x78, 0x61, 0x44, - 0xe6, 0xa0, 0x24, 0x21, 0x6c, 0x13, 0xc9, 0x67, 0x56, 0xd9, 0x4a, 0x70, 0xcc, 0x3f, 0xcd, 0x01, - 0x24, 0x25, 0xcf, 0xa8, 0x9c, 0xf9, 0x09, 0x4d, 0xce, 0x9c, 0x4b, 0x4f, 0xd0, 0x81, 0x12, 0x86, - 0x7c, 0x00, 0xa3, 0x4c, 0xe5, 0xea, 0x49, 0x95, 0xf2, 0x42, 0x9a, 0x14, 0x0b, 0xb7, 0x6e, 0xd5, - 0x27, 0x05, 0xf1, 0x68, 0x88, 0x10, 0x4b, 0x90, 0x29, 0x22, 0xea, 0x7f, 0x14, 0x92, 0xc1, 0x10, - 0xc2, 0xe9, 0xaa, 0x22, 0x5d, 0x8c, 0x64, 0x3d, 0x4a, 0xe9, 0xa2, 0xc8, 0x96, 0x8b, 0x5c, 0xb6, - 0xf0, 0x4e, 0x1d, 0x13, 0xb2, 0x25, 0x2d, 0x59, 0x78, 0x07, 0x9e, 0x28, 0x59, 0xba, 0xe9, 0x65, - 0x5b, 0xc0, 0x69, 0x70, 0x35, 0xb3, 0x57, 0xb2, 0x16, 0xec, 0x95, 0x93, 0x16, 0x6c, 0x7a, 0xb9, - 0xde, 0x1a, 0x24, 0xcb, 0xce, 0xc9, 0xd5, 0xe5, 0x3c, 0x56, 0xc9, 0x51, 0xa6, 0xbd, 0xc3, 0x97, - 0xe6, 0xe8, 0xc0, 0xa5, 0x79, 0x2e, 0x73, 0x69, 0xf2, 0x85, 0xf9, 0x0e, 0x8c, 0xd4, 0x7e, 0xae, - 0x17, 0x50, 0xa1, 0xfb, 0x95, 0xe5, 0x37, 0x19, 0x2c, 0x5e, 0xd3, 0x53, 0x0e, 0xfb, 0xa9, 0xea, - 0xcc, 0x58, 0xce, 0xbe, 0xbc, 0xb1, 0xd2, 0x10, 0x7a, 0x1d, 0x49, 0x75, 0xcb, 0xc6, 0x8a, 0x52, - 0xed, 0x48, 0x6b, 0x35, 0xa3, 0x22, 0x73, 0x90, 0xab, 0x2d, 0xa2, 0xb1, 0x38, 0x3e, 0x5f, 0x92, - 0x9f, 0x5d, 0xac, 0x9f, 0x15, 0x24, 0x65, 0x47, 0xb3, 0x1f, 0x6a, 0x8b, 0x9f, 0xdd, 0xe2, 0x6f, - 0x29, 0x6a, 0x82, 0x98, 0xa6, 0xcc, 0x1c, 0x15, 0x93, 0xc5, 0x48, 0x94, 0x96, 0xbe, 0xc9, 0x12, - 0x4f, 0x95, 0x6b, 0x7c, 0xe0, 0x72, 0x7d, 0x03, 0x37, 0xae, 0x6c, 0x42, 0x38, 0x5c, 0xe6, 0x5f, - 0x19, 0x88, 0x4b, 0xde, 0x80, 0x51, 0x8b, 0xee, 0x26, 0x7b, 0x2d, 0xda, 0x6c, 0x01, 0x42, 0xd4, - 0x0f, 0x70, 0x1c, 0x14, 0xe4, 0xd4, 0x0d, 0xf7, 0xbc, 0x87, 0x91, 0xf8, 0x4a, 0x2c, 0xc8, 0x05, - 0x58, 0x11, 0xe4, 0x02, 0xa2, 0x09, 0x72, 0x01, 0x63, 0x53, 0xcc, 0x5a, 0x6c, 0x08, 0x65, 0x52, - 0xd6, 0xd4, 0x5a, 0x54, 0xc6, 0x2a, 0x70, 0xb5, 0xb1, 0xb2, 0x16, 0x1b, 0xe4, 0x36, 0x94, 0x6a, - 0xcd, 0xa6, 0xdf, 0x53, 0x8c, 0x9e, 0xd9, 0xa3, 0xc3, 0xea, 0x59, 0x87, 0x03, 0x75, 0x13, 0x3d, - 0x41, 0x35, 0xeb, 0x49, 0xad, 0x19, 0x8f, 0x85, 0x56, 0x2f, 0x8c, 0x68, 0xb0, 0xbc, 0x28, 0x9a, - 0x8c, 0x3c, 0x9a, 0x1c, 0x98, 0xe2, 0x11, 0xa3, 0x9a, 0xff, 0xc9, 0xc0, 0x1a, 0x93, 0xb7, 0x01, - 0x96, 0x3b, 0x4c, 0xb1, 0x6d, 0xd2, 0x98, 0x01, 0x1a, 0xcf, 0x9e, 0x80, 0xea, 0x1c, 0x14, 0x64, - 0xfd, 0xd3, 0xb9, 0xa1, 0x3f, 0xcd, 0x3e, 0x29, 0xd5, 0x64, 0x71, 0x8e, 0x22, 0x3e, 0x19, 0x08, - 0x68, 0xea, 0x93, 0x09, 0x32, 0x79, 0x0d, 0xc6, 0x96, 0x6b, 0x0f, 0x6a, 0xbd, 0x68, 0x0f, 0xfb, - 0xab, 0xc8, 0x05, 0x96, 0xe7, 0xb4, 0x6d, 0xa7, 0x17, 0xed, 0x59, 0xb2, 0xd0, 0xfc, 0x25, 0x03, - 0xc6, 0x95, 0xb5, 0xca, 0xaa, 0xba, 0x1e, 0xf8, 0x1f, 0xd3, 0x66, 0xa4, 0xf7, 0x52, 0x97, 0x03, - 0x53, 0x55, 0x8d, 0x51, 0x53, 0xbd, 0x93, 0x3b, 0x45, 0xef, 0x98, 0x73, 0x42, 0x04, 0x30, 0x1b, - 0x40, 0x39, 0xe2, 0x40, 0x1b, 0x80, 0xe9, 0x38, 0xaa, 0x0d, 0xc0, 0xca, 0xcd, 0x3f, 0x32, 0xd8, - 0xd2, 0x25, 0x73, 0x00, 0xf7, 0xe9, 0x41, 0xe4, 0xec, 0xdc, 0xf1, 0x5a, 0xda, 0xd1, 0xd5, 0x3e, - 0x42, 0xed, 0x87, 0x5e, 0x8b, 0x5a, 0x0a, 0x0a, 0xb9, 0x05, 0xc5, 0xfb, 0xc1, 0xce, 0x5b, 0x88, - 0x9e, 0x8b, 0x45, 0xf0, 0xcc, 0x7e, 0xb0, 0xf3, 0x16, 0x22, 0xab, 0xf3, 0x55, 0x22, 0x12, 0x13, - 0x46, 0x17, 0xfd, 0xb6, 0xe3, 0xc9, 0x6d, 0x0f, 0xd8, 0xde, 0xe1, 0x22, 0xc4, 0x12, 0x25, 0x4c, - 0xe8, 0x37, 0xd6, 0x57, 0xc5, 0xc4, 0x44, 0xa1, 0x1f, 0x76, 0x3b, 0x16, 0x83, 0x99, 0xdf, 0x33, - 0x60, 0x5c, 0x91, 0x48, 0xe4, 0x8b, 0xc2, 0xcc, 0x37, 0xf0, 0x90, 0xea, 0x7c, 0xbf, 0xcc, 0x62, - 0xa5, 0x7c, 0xbb, 0x66, 0xe6, 0xbf, 0x30, 0xfa, 0x13, 0x69, 0x90, 0x1b, 0x46, 0x1a, 0xbc, 0x0d, - 0xc0, 0x75, 0x39, 0xec, 0x4e, 0x65, 0xde, 0x28, 0x87, 0x7a, 0xea, 0x60, 0x24, 0xc8, 0xe6, 0xff, - 0x9f, 0x83, 0xa2, 0xb0, 0x55, 0xe6, 0x9f, 0x51, 0x1d, 0xe2, 0x2d, 0x4d, 0x87, 0x98, 0x11, 0xa4, - 0x8a, 0x72, 0x3b, 0x7f, 0x82, 0x8d, 0xf2, 0x36, 0x94, 0x65, 0x17, 0xa0, 0x2a, 0x76, 0x0d, 0xc6, - 0xa4, 0x95, 0xcd, 0x15, 0xb1, 0x29, 0x8d, 0xe7, 0xd6, 0xbc, 0x25, 0xcb, 0xcd, 0xef, 0x8c, 0x48, - 0x5a, 0xfe, 0x25, 0xd6, 0x85, 0x35, 0xd7, 0x0d, 0xd4, 0x2e, 0x74, 0x5c, 0x37, 0xb0, 0x10, 0xca, - 0x06, 0x6a, 0xbd, 0xb7, 0xd3, 0xf2, 0x9a, 0x88, 0xa3, 0xac, 0x9a, 0x2e, 0x42, 0x6d, 0x86, 0xaa, - 0x0e, 0x54, 0x82, 0xac, 0x99, 0x08, 0xf9, 0x63, 0x4d, 0x84, 0x9f, 0x86, 0xd2, 0x42, 0xdb, 0xd5, - 0x54, 0x08, 0x33, 0xa3, 0x53, 0x6e, 0xc4, 0x48, 0x5c, 0x79, 0xb8, 0x2c, 0xfa, 0xe8, 0x6c, 0xb3, - 0xed, 0xf6, 0x2b, 0x0e, 0x09, 0x4b, 0x4d, 0xc7, 0x1f, 0xf9, 0x34, 0x3a, 0xfe, 0x6d, 0x28, 0x6d, - 0x86, 0x74, 0xa3, 0xd7, 0xe9, 0xd0, 0x16, 0xaa, 0x13, 0x45, 0x2e, 0x7b, 0x7a, 0x21, 0xb5, 0x23, - 0x84, 0xaa, 0x15, 0x88, 0x51, 0xd5, 0x69, 0x35, 0x76, 0xcc, 0xb4, 0xfa, 0x22, 0x14, 0x6a, 0xdd, - 0xae, 0x34, 0x7e, 0xe2, 0x4d, 0xb2, 0xdb, 0xc5, 0xad, 0x6f, 0xd2, 0xe9, 0x76, 0x75, 0x53, 0x06, - 0xb1, 0x09, 0x05, 0x72, 0xbf, 0xb7, 0x43, 0x83, 0x0e, 0x8d, 0x68, 0x28, 0x44, 0x73, 0x38, 0x0b, - 0xc8, 0x63, 0x56, 0x9e, 0x31, 0xa7, 0x11, 0xd0, 0x70, 0xbd, 0xb0, 0xdf, 0xdb, 0xa1, 0xb6, 0x90, - 0xf1, 0x6a, 0xdf, 0x65, 0x30, 0xbc, 0xd4, 0x80, 0x49, 0xbd, 0xff, 0x9f, 0x82, 0x62, 0xf1, 0x61, - 0xa1, 0x58, 0xac, 0x94, 0xcc, 0x5f, 0xc9, 0xc1, 0x78, 0xad, 0xdb, 0x7d, 0xc6, 0x4f, 0x20, 0x7e, - 0x52, 0x5b, 0xd5, 0xe7, 0x93, 0xd1, 0x3b, 0xc5, 0xe1, 0xc3, 0x5f, 0x1b, 0x30, 0x95, 0xa2, 0x50, - 0x6b, 0x6f, 0x0c, 0x69, 0x91, 0xe7, 0x86, 0xb4, 0xc8, 0xf3, 0x83, 0x2d, 0x72, 0x75, 0xcd, 0x14, - 0x3e, 0xcd, 0x9a, 0x79, 0x1d, 0xf2, 0xb5, 0x6e, 0x57, 0xf4, 0x4a, 0x39, 0xe9, 0x95, 0xad, 0x5b, - 0x7c, 0x23, 0x72, 0xba, 0x5d, 0x8b, 0x61, 0x98, 0x6f, 0x42, 0x09, 0xc1, 0x28, 0xd1, 0xae, 0x88, - 0xa5, 0xc0, 0xc5, 0x99, 0x46, 0xc6, 0xa7, 0xbd, 0xf9, 0xbf, 0x0d, 0x18, 0xc1, 0xdf, 0xcf, 0xe8, - 0x74, 0x99, 0xd7, 0xa6, 0x4b, 0x45, 0x99, 0x2e, 0xc3, 0x4c, 0x94, 0x3f, 0xce, 0x63, 0x6f, 0x89, - 0x29, 0x22, 0x6c, 0x3a, 0x23, 0xc3, 0xa6, 0xfb, 0x14, 0x02, 0x7c, 0x3f, 0x6d, 0xdd, 0xe5, 0x71, - 0x30, 0x5e, 0x4e, 0x57, 0xf5, 0xa9, 0x18, 0x76, 0xf7, 0x80, 0x2c, 0x77, 0x42, 0xda, 0xec, 0x05, - 0xb4, 0xb1, 0xef, 0x75, 0xb7, 0x68, 0xe0, 0x3d, 0x3c, 0x10, 0x9a, 0x21, 0xca, 0x58, 0x4f, 0x94, - 0xda, 0xe1, 0xbe, 0xd7, 0xb5, 0x1f, 0x61, 0xb9, 0x95, 0x41, 0x43, 0x3e, 0x80, 0x31, 0x8b, 0x3e, - 0x0e, 0xbc, 0x88, 0x8a, 0xbe, 0x9d, 0x8c, 0xed, 0x00, 0x84, 0x72, 0xdd, 0x24, 0xe0, 0x3f, 0xd4, - 0xf1, 0x17, 0xe5, 0x9f, 0x9d, 0x19, 0xf5, 0xdd, 0x11, 0x5c, 0x0b, 0x27, 0xdc, 0x94, 0x1d, 0x63, - 0xa0, 0xeb, 0x83, 0x99, 0x3f, 0xcd, 0x60, 0x6e, 0x41, 0x99, 0x99, 0x6e, 0x29, 0x4b, 0xfd, 0x72, - 0x32, 0x96, 0x37, 0xd4, 0xe2, 0xe3, 0x2e, 0xc9, 0x34, 0x3e, 0xc4, 0x4e, 0x4f, 0x12, 0x7e, 0xf9, - 0xf6, 0xa2, 0xc2, 0x38, 0x63, 0x7a, 0xc4, 0xa2, 0xa3, 0xc9, 0x3b, 0xeb, 0xd4, 0x13, 0x63, 0xf4, - 0xd3, 0x4d, 0x8c, 0xb1, 0x4f, 0x32, 0x31, 0xd2, 0xd7, 0x93, 0xc5, 0xd3, 0x5c, 0x4f, 0x5e, 0xfa, - 0x00, 0xa6, 0xfb, 0x7a, 0xf8, 0x34, 0x57, 0x7c, 0x9f, 0xdd, 0xb4, 0xfc, 0x85, 0xb8, 0x5f, 0xc8, - 0x3c, 0x9a, 0xa3, 0x5e, 0x40, 0x9b, 0x11, 0x8a, 0x5e, 0x21, 0x2d, 0x03, 0x01, 0x4b, 0xd9, 0xcb, - 0x08, 0x23, 0xef, 0xc3, 0x18, 0xbf, 0x22, 0x09, 0x67, 0x73, 0x38, 0xf6, 0x13, 0xe2, 0x8b, 0x1c, - 0x2a, 0xee, 0xa9, 0x39, 0x86, 0xda, 0xab, 0x82, 0xc8, 0xbc, 0x0b, 0xa3, 0xe2, 0x8a, 0xe5, 0xf8, - 0x75, 0x51, 0x85, 0x91, 0xad, 0xa4, 0x67, 0xf0, 0x58, 0x9c, 0x37, 0xc2, 0xe2, 0x70, 0xf3, 0xd7, - 0x0c, 0x98, 0xd4, 0x5b, 0x49, 0x6e, 0xc0, 0xa8, 0xb8, 0x03, 0x34, 0xf0, 0x0e, 0x90, 0xb5, 0x66, - 0x94, 0xdf, 0xfe, 0x69, 0x77, 0x7e, 0x02, 0x8b, 0x89, 0x7e, 0xc1, 0x01, 0xdb, 0x22, 0x44, 0xbf, - 0x98, 0xa4, 0x96, 0x2c, 0x63, 0x26, 0x97, 0x45, 0xc3, 0x5e, 0x2b, 0x52, 0x4d, 0xae, 0x00, 0x21, - 0x96, 0x28, 0x31, 0x0f, 0x0d, 0x80, 0x46, 0xe3, 0xde, 0x7d, 0x7a, 0xb0, 0xee, 0x78, 0x01, 0x9a, - 0xad, 0xb8, 0x1a, 0xef, 0x8b, 0xd1, 0x2a, 0x0b, 0xb3, 0x95, 0xaf, 0xdc, 0x7d, 0x7a, 0xa0, 0x99, - 0xad, 0x12, 0x15, 0x97, 0x7c, 0xe0, 0x3d, 0x72, 0x22, 0xca, 0x08, 0x73, 0x48, 0xc8, 0x97, 0x3c, - 0x87, 0xa6, 0x28, 0x15, 0x64, 0xf2, 0x0d, 0x98, 0x4c, 0x7e, 0xa1, 0xe3, 0x41, 0x1e, 0x6d, 0x3a, - 0x39, 0x23, 0xf4, 0xc2, 0xfa, 0x4b, 0x47, 0x87, 0xd5, 0x4b, 0x0a, 0x57, 0x9b, 0x61, 0x29, 0xac, - 0x53, 0xcc, 0xcc, 0x3f, 0x30, 0x00, 0x36, 0x56, 0x1a, 0xb2, 0x81, 0xaf, 0x41, 0x21, 0x3e, 0x0d, - 0x2a, 0x73, 0xdb, 0x38, 0x65, 0xfc, 0x61, 0x39, 0x79, 0x19, 0xf2, 0x49, 0x4b, 0xa6, 0x8f, 0x0e, - 0xab, 0x13, 0x7a, 0x0b, 0x58, 0x29, 0xb9, 0x0b, 0x63, 0x43, 0xd5, 0x19, 0x67, 0x67, 0x46, 0x5d, - 0x25, 0x35, 0x8e, 0xc2, 0x87, 0xdb, 0x1b, 0xcf, 0xef, 0x28, 0x7c, 0x2b, 0x07, 0x53, 0xac, 0x5f, - 0x6b, 0xbd, 0x68, 0xcf, 0x0f, 0xbc, 0xe8, 0xe0, 0x99, 0xb5, 0x8a, 0xdf, 0xd5, 0x14, 0xa2, 0x4b, - 0x52, 0x6c, 0xa9, 0x6d, 0x1b, 0xca, 0x38, 0xfe, 0xcb, 0x31, 0x98, 0xc9, 0xa0, 0x22, 0x6f, 0x08, - 0xef, 0x9b, 0xe4, 0xcc, 0x08, 0xbd, 0x6b, 0x7e, 0x74, 0x58, 0x2d, 0x4b, 0xf4, 0x8d, 0xc4, 0xdb, - 0x66, 0x1e, 0xc6, 0x85, 0xe9, 0xb3, 0x9a, 0x68, 0xd4, 0xe8, 0xb6, 0x21, 0xcf, 0xc4, 0x50, 0x34, - 0xa9, 0x48, 0xa4, 0x06, 0xe5, 0x85, 0x3d, 0xda, 0xdc, 0xf7, 0x3a, 0xbb, 0xf7, 0xe9, 0x01, 0xd7, - 0x97, 0xca, 0xf5, 0x17, 0x99, 0xa5, 0xd5, 0x14, 0x70, 0x36, 0xa4, 0xba, 0x11, 0xa7, 0x91, 0x90, - 0xf7, 0x61, 0xbc, 0xe1, 0xed, 0x76, 0x24, 0x87, 0x02, 0x72, 0xb8, 0x7c, 0x74, 0x58, 0x3d, 0x1f, - 0x72, 0x70, 0x3f, 0x03, 0x95, 0x80, 0x5c, 0x83, 0x11, 0xcb, 0x6f, 0x51, 0xbe, 0x0d, 0x0b, 0x7f, - 0x8e, 0x80, 0x01, 0xd4, 0xb3, 0x69, 0xc4, 0x20, 0xf7, 0x60, 0x8c, 0xfd, 0xf3, 0xc0, 0xe9, 0xce, - 0x8e, 0xa2, 0xdc, 0x26, 0xb1, 0x82, 0x8f, 0xd0, 0xae, 0xd7, 0xd9, 0x55, 0x75, 0xfc, 0x16, 0xb5, - 0xdb, 0x4e, 0x57, 0xdb, 0x17, 0x39, 0x22, 0xd9, 0x82, 0xf1, 0x44, 0x10, 0x84, 0xb3, 0x63, 0xda, - 0x5d, 0x50, 0x52, 0x52, 0xff, 0x9c, 0x60, 0x76, 0x21, 0x6a, 0x85, 0x38, 0xb7, 0xbb, 0x0c, 0x5f, - 0x6f, 0x8c, 0xc2, 0x48, 0xb3, 0x41, 0x8a, 0x83, 0x6d, 0x10, 0xe3, 0x44, 0x1b, 0xc4, 0x05, 0x10, - 0x9d, 0x54, 0x6b, 0xed, 0x0a, 0xf7, 0xab, 0x6b, 0x83, 0x27, 0xd8, 0x8d, 0x04, 0x19, 0xd7, 0x24, - 0x3f, 0x99, 0x12, 0xfd, 0xef, 0xb4, 0x76, 0xb5, 0x93, 0xa9, 0x18, 0x95, 0x75, 0x43, 0x22, 0x6a, - 0xa4, 0x05, 0x2e, 0xbb, 0x21, 0x29, 0x49, 0xba, 0xe1, 0xe3, 0xc7, 0xd1, 0xa0, 0x6e, 0x50, 0x18, - 0x91, 0x55, 0x80, 0x5a, 0x33, 0xf2, 0x1e, 0x51, 0x9c, 0x12, 0xe3, 0x5a, 0x47, 0x2c, 0xd4, 0xee, - 0xd3, 0x83, 0x06, 0x8d, 0x62, 0xcf, 0x86, 0x73, 0x0e, 0xa2, 0xa6, 0xa6, 0x89, 0xa5, 0x70, 0x20, - 0x5d, 0x38, 0x57, 0x73, 0x5d, 0x8f, 0xbb, 0xe4, 0x6d, 0x04, 0x6c, 0xfe, 0xba, 0xc8, 0xba, 0x9c, - 0xcd, 0xfa, 0x9a, 0x60, 0xfd, 0x39, 0x27, 0xa6, 0xb2, 0x23, 0x4e, 0x96, 0xfe, 0x4c, 0x36, 0x63, - 0x73, 0x0d, 0x26, 0xf5, 0x2e, 0xd5, 0x9d, 0xd1, 0xca, 0x50, 0xb4, 0x1a, 0x35, 0xbb, 0x71, 0xaf, - 0x76, 0xb3, 0x62, 0x90, 0x0a, 0x94, 0xc5, 0xaf, 0x79, 0x7b, 0xfe, 0xad, 0xdb, 0x95, 0x9c, 0x06, - 0x79, 0xeb, 0xe6, 0x7c, 0x25, 0x6f, 0xfe, 0xb1, 0x01, 0x45, 0x59, 0x3f, 0x72, 0x1b, 0xf2, 0x8d, - 0xc6, 0xbd, 0xd4, 0x15, 0x64, 0xb2, 0xf5, 0xf2, 0x4d, 0x26, 0x0c, 0xf7, 0xd4, 0x4d, 0xa6, 0xd1, - 0xb8, 0xc7, 0xe8, 0x36, 0x56, 0x1a, 0x42, 0x69, 0xc9, 0x98, 0xae, 0xd3, 0x03, 0xee, 0x65, 0x6e, - 0x43, 0xfe, 0xc3, 0xed, 0x0d, 0x61, 0x0d, 0x65, 0x8c, 0x2f, 0xd2, 0x7d, 0xfc, 0x58, 0xdd, 0xfa, - 0x18, 0x81, 0x69, 0xc1, 0xb8, 0xb2, 0xb4, 0xb8, 0x12, 0xd1, 0xf6, 0x63, 0x37, 0x2d, 0xa1, 0x44, - 0x30, 0x88, 0x25, 0x4a, 0x98, 0xce, 0xb3, 0xe2, 0x37, 0x9d, 0x96, 0xd0, 0x46, 0x50, 0xe7, 0x69, - 0x31, 0x80, 0xc5, 0xe1, 0xe6, 0x9f, 0x18, 0x50, 0x59, 0x0f, 0xfc, 0x47, 0x1e, 0x93, 0xc0, 0x1b, - 0xfe, 0x3e, 0xed, 0x6c, 0xdd, 0x24, 0x6f, 0x4a, 0x21, 0xc0, 0x55, 0xb8, 0x0b, 0x8c, 0x0a, 0x85, - 0xc0, 0x8f, 0x0e, 0xab, 0xd0, 0x38, 0x08, 0x23, 0xda, 0x66, 0xe5, 0x52, 0x10, 0x28, 0xde, 0x6e, - 0xb9, 0xe1, 0x3d, 0x68, 0x4e, 0xf0, 0x76, 0xab, 0xc2, 0x08, 0x56, 0x47, 0x71, 0x62, 0x18, 0x89, - 0x18, 0xc0, 0xe2, 0x70, 0x45, 0x60, 0x7f, 0x27, 0xd7, 0xd7, 0x86, 0xf9, 0xe7, 0xca, 0x0b, 0x45, - 0x6f, 0xdc, 0x50, 0x9b, 0xd8, 0x57, 0xe1, 0x6c, 0xba, 0x4b, 0xf0, 0x5c, 0xa4, 0x06, 0x53, 0x3a, - 0x5c, 0x1e, 0x91, 0x5c, 0xc8, 0xfc, 0xd6, 0xd6, 0xbc, 0x95, 0xc6, 0x37, 0x7f, 0x60, 0x40, 0x09, - 0xff, 0xb5, 0x7a, 0x2d, 0xca, 0x34, 0x9b, 0xda, 0x76, 0x43, 0x5c, 0x48, 0xa9, 0x97, 0x46, 0xce, - 0xe3, 0xd0, 0x16, 0xb7, 0x57, 0x9a, 0x1c, 0x89, 0x91, 0x05, 0x29, 0xbf, 0x7e, 0x0b, 0xc5, 0x0c, - 0x8d, 0x49, 0xf9, 0x3d, 0x5d, 0x98, 0x22, 0x15, 0xc8, 0x6c, 0xfc, 0xd8, 0x2f, 0xbf, 0x25, 0x8f, - 0x86, 0x71, 0xfc, 0x90, 0xce, 0xd7, 0xae, 0x39, 0x24, 0x1a, 0x79, 0x13, 0x46, 0xd9, 0xa7, 0x2d, - 0x79, 0x89, 0x81, 0x56, 0x05, 0xd6, 0x31, 0xd0, 0x6e, 0x03, 0x39, 0x92, 0xf9, 0x2f, 0x73, 0xe9, - 0x0e, 0x14, 0x5a, 0xc0, 0x29, 0xd7, 0xc6, 0x3b, 0x30, 0x52, 0x6b, 0xb5, 0xfc, 0xc7, 0x42, 0x4a, - 0xc8, 0x63, 0x9a, 0xb8, 0xff, 0xf8, 0x0e, 0xeb, 0x30, 0x14, 0xed, 0xf6, 0x97, 0x01, 0xc8, 0x02, - 0x94, 0x6a, 0xdb, 0x8d, 0xe5, 0xe5, 0xc5, 0x8d, 0x8d, 0x15, 0xe1, 0x64, 0xfc, 0xaa, 0xec, 0x1f, - 0xcf, 0x73, 0xed, 0x28, 0x6a, 0x0d, 0xf0, 0x41, 0x4c, 0xe8, 0xc8, 0x7b, 0x00, 0x1f, 0xfa, 0x5e, - 0xe7, 0x01, 0x8d, 0xf6, 0x7c, 0x57, 0x34, 0x9e, 0xa9, 0x14, 0xe3, 0x1f, 0xfb, 0x5e, 0xc7, 0x6e, - 0x23, 0x98, 0xd5, 0x3d, 0x41, 0xb2, 0x94, 0xff, 0x59, 0x4f, 0xd7, 0xfd, 0x08, 0x75, 0x98, 0x91, - 0xa4, 0xa7, 0x77, 0xfc, 0x28, 0x7d, 0xc7, 0x22, 0xd1, 0xcc, 0x5f, 0xcf, 0xc1, 0x24, 0xb7, 0x54, - 0xf9, 0x84, 0x79, 0x66, 0x17, 0xe3, 0x3b, 0xda, 0x62, 0xbc, 0x28, 0x37, 0x06, 0xa5, 0x69, 0x43, - 0x2d, 0xc5, 0x3d, 0x20, 0xfd, 0x34, 0xc4, 0x92, 0xe7, 0x29, 0xc3, 0xac, 0xc2, 0x9b, 0xc9, 0xdd, - 0x71, 0x88, 0x44, 0x36, 0x8a, 0xc2, 0xd0, 0xd2, 0x78, 0x98, 0xbf, 0x96, 0x83, 0x09, 0x45, 0x9f, - 0x7c, 0x66, 0x3b, 0xfe, 0x4b, 0x5a, 0xc7, 0xcb, 0x3b, 0x08, 0xa5, 0x65, 0x43, 0xf5, 0x7b, 0x0f, - 0xa6, 0xfb, 0x48, 0xd2, 0x6a, 0xb9, 0x31, 0x8c, 0x5a, 0xfe, 0x46, 0xff, 0xe5, 0x36, 0x77, 0x48, - 0x8e, 0x2f, 0xb7, 0xd5, 0xdb, 0xf4, 0x6f, 0xe5, 0xe0, 0xac, 0xf8, 0x55, 0xeb, 0xb9, 0x5e, 0xb4, - 0xe0, 0x77, 0x1e, 0x7a, 0xbb, 0xcf, 0xec, 0x58, 0xd4, 0xb4, 0xb1, 0xa8, 0xea, 0x63, 0xa1, 0x34, - 0x70, 0xf0, 0x90, 0x98, 0xff, 0xba, 0x08, 0xb3, 0x83, 0x08, 0x98, 0xd9, 0xaf, 0x58, 0x55, 0x68, - 0xf6, 0xa7, 0x2c, 0x56, 0x6e, 0x4f, 0x25, 0xce, 0x1c, 0xb9, 0x21, 0x9c, 0x39, 0x56, 0xa0, 0x82, - 0x9f, 0x6a, 0xd0, 0x90, 0x75, 0x42, 0x98, 0x78, 0x43, 0x5e, 0x39, 0x3a, 0xac, 0x5e, 0x76, 0x58, - 0x99, 0x1d, 0x8a, 0x42, 0xbb, 0x17, 0x78, 0x0a, 0x8f, 0x3e, 0x4a, 0xf2, 0x07, 0x06, 0x4c, 0x22, - 0x70, 0xe9, 0x11, 0xed, 0x44, 0xc8, 0xac, 0x20, 0x2e, 0x69, 0xe2, 0xa0, 0x93, 0x46, 0x14, 0x78, - 0x9d, 0x5d, 0x3c, 0x48, 0x0a, 0xeb, 0x3b, 0xac, 0x17, 0xfe, 0xe2, 0xb0, 0xfa, 0xee, 0x27, 0x09, - 0x64, 0x11, 0xac, 0x42, 0x66, 0xc8, 0xf3, 0x8a, 0x52, 0xfc, 0x6c, 0xaa, 0x9a, 0xa9, 0x1a, 0x91, - 0x9f, 0x82, 0x0b, 0x4b, 0x1d, 0x67, 0xa7, 0x45, 0x17, 0xfc, 0x4e, 0xe4, 0x75, 0x7a, 0x7e, 0x2f, - 0xac, 0x3b, 0xcd, 0xfd, 0x5e, 0x37, 0x14, 0x87, 0x9d, 0xd8, 0xf2, 0x66, 0x5c, 0x68, 0xef, 0xf0, - 0x52, 0x85, 0xe5, 0x20, 0x06, 0xe4, 0x1e, 0x4c, 0xf3, 0xa2, 0x5a, 0x2f, 0xf2, 0x1b, 0x4d, 0xa7, - 0xe5, 0x75, 0x76, 0xf1, 0x0c, 0xb4, 0x58, 0xbf, 0xc4, 0x6c, 0x4b, 0xa7, 0x17, 0xf9, 0x76, 0xc8, - 0xe1, 0x0a, 0xbf, 0x7e, 0x22, 0xb2, 0x0c, 0x53, 0x16, 0x75, 0xdc, 0x07, 0xce, 0x93, 0x05, 0xa7, - 0xeb, 0x34, 0xbd, 0xe8, 0x00, 0x2d, 0xb3, 0x7c, 0xbd, 0x7a, 0x74, 0x58, 0x7d, 0x21, 0xa0, 0x8e, - 0x6b, 0xb7, 0x9d, 0x27, 0x76, 0x53, 0x14, 0x2a, 0xcc, 0xd2, 0x74, 0x31, 0x2b, 0xaf, 0x13, 0xb3, - 0x2a, 0xa5, 0x59, 0x79, 0x9d, 0xc1, 0xac, 0x12, 0x3a, 0xc9, 0x6a, 0xc3, 0x09, 0x76, 0x69, 0xc4, - 0x0f, 0x09, 0xe1, 0x8a, 0x71, 0xd5, 0x50, 0x58, 0x45, 0x58, 0x66, 0xe3, 0x81, 0x61, 0x9a, 0x95, - 0x42, 0xc7, 0x66, 0xde, 0x76, 0xe0, 0x45, 0x54, 0x6d, 0xe1, 0x38, 0x56, 0x0b, 0xfb, 0x1f, 0x8f, - 0x49, 0x07, 0x35, 0xb1, 0x8f, 0x32, 0xe1, 0xa6, 0x34, 0xb2, 0xdc, 0xc7, 0x2d, 0xbb, 0x95, 0x7d, - 0x94, 0x31, 0x37, 0xb5, 0x9d, 0x13, 0xd8, 0x4e, 0x85, 0xdb, 0x80, 0x86, 0xf6, 0x51, 0x92, 0x55, - 0xd6, 0x69, 0x11, 0xed, 0xb0, 0x19, 0x2d, 0x0e, 0x49, 0x27, 0xb1, 0x6a, 0xaf, 0x08, 0x9b, 0xba, - 0x12, 0xc8, 0x62, 0x3b, 0xe3, 0xc8, 0x34, 0x4d, 0xfc, 0x61, 0xa1, 0x38, 0x52, 0x19, 0xb5, 0x2a, - 0x7c, 0xca, 0x47, 0x6c, 0xe2, 0xa0, 0x2c, 0x36, 0x7f, 0x27, 0x07, 0x17, 0xa5, 0x38, 0xa6, 0xd1, - 0x63, 0x3f, 0xd8, 0xf7, 0x3a, 0xbb, 0xcf, 0xb8, 0x54, 0xbd, 0xa3, 0x49, 0xd5, 0x57, 0x52, 0x3b, - 0x5c, 0xaa, 0x95, 0xc7, 0x88, 0xd6, 0x3f, 0x1f, 0x81, 0x17, 0x8f, 0xa5, 0x22, 0x1f, 0xb1, 0x5d, - 0xd0, 0xa3, 0x9d, 0x68, 0xd9, 0x6d, 0x51, 0x66, 0x86, 0xf9, 0xbd, 0x48, 0x1c, 0x66, 0xbf, 0x7c, - 0x74, 0x58, 0x9d, 0xe1, 0xb1, 0x18, 0xb6, 0xe7, 0xb6, 0xa8, 0x1d, 0xf1, 0x62, 0x6d, 0x98, 0xfa, - 0xa9, 0x19, 0xcb, 0x38, 0x32, 0x6c, 0xb9, 0x13, 0xd1, 0xe0, 0x91, 0xc3, 0x5d, 0xd2, 0x05, 0xcb, - 0x7d, 0x4a, 0xbb, 0xb6, 0xc3, 0x4a, 0x6d, 0x4f, 0x14, 0xeb, 0x2c, 0xfb, 0xa8, 0xc9, 0x1d, 0x85, - 0xe5, 0x02, 0x33, 0x0e, 0x1e, 0x38, 0x4f, 0x84, 0xc6, 0x8b, 0xe7, 0xab, 0x0a, 0x4b, 0xee, 0x0f, - 0xd7, 0x76, 0x9e, 0x58, 0xfd, 0x24, 0xe4, 0x1b, 0x70, 0x4e, 0x08, 0x6e, 0x26, 0xc4, 0x02, 0xbf, - 0x25, 0x5b, 0x5c, 0x40, 0x5e, 0xaf, 0x1f, 0x1d, 0x56, 0x2f, 0x08, 0xb1, 0x6f, 0x37, 0x39, 0x46, - 0x66, 0xab, 0xb3, 0xb9, 0x90, 0x0d, 0xb6, 0x91, 0xa5, 0xba, 0xe3, 0x01, 0x0d, 0x43, 0x67, 0x57, - 0x6a, 0xc7, 0xfc, 0x46, 0x49, 0xe9, 0x4c, 0xbb, 0xcd, 0xcb, 0xad, 0x81, 0x94, 0xe4, 0x1e, 0x4c, - 0x6e, 0xd3, 0x1d, 0x75, 0x7c, 0x46, 0xe3, 0x25, 0x5e, 0x79, 0x4c, 0x77, 0x06, 0x0f, 0x4e, 0x8a, - 0x8e, 0x78, 0x30, 0xbd, 0x1e, 0xf8, 0x4f, 0x0e, 0x98, 0xa9, 0x47, 0x3b, 0x34, 0x40, 0x47, 0xac, - 0x31, 0x3c, 0xae, 0x9a, 0x4d, 0x34, 0x4b, 0xbd, 0xbc, 0xfe, 0xb9, 0xa3, 0xc3, 0xea, 0x8b, 0x5d, - 0x06, 0xb6, 0x5b, 0x02, 0x6e, 0xa7, 0x02, 0xb3, 0xfa, 0xb9, 0x92, 0x9f, 0x81, 0x29, 0xcb, 0xef, - 0x45, 0x5e, 0x67, 0xb7, 0x11, 0x05, 0x4e, 0x44, 0x77, 0xb9, 0x20, 0x4f, 0x3c, 0xbe, 0x52, 0xa5, - 0xfc, 0x60, 0x3a, 0xe0, 0x40, 0x3b, 0x14, 0x50, 0x4d, 0x92, 0xea, 0x04, 0xe6, 0x6f, 0xe5, 0x60, - 0x56, 0x0c, 0x83, 0x45, 0x9b, 0x7e, 0xe0, 0x3e, 0xfb, 0xcb, 0x7e, 0x49, 0x5b, 0xf6, 0x2f, 0xc7, - 0x3e, 0x4a, 0x59, 0x8d, 0x3c, 0x66, 0xd5, 0xff, 0x53, 0x03, 0x2e, 0x1f, 0x47, 0xc4, 0x7a, 0x27, - 0xf6, 0xc1, 0x2b, 0xf5, 0xf9, 0xda, 0x75, 0x61, 0x06, 0xc7, 0x13, 0x0f, 0x8e, 0xc3, 0x7b, 0x7e, - 0x18, 0xe1, 0xe9, 0x5d, 0x4e, 0x73, 0x24, 0xa8, 0xfb, 0x7e, 0x0b, 0xe5, 0x7c, 0xfd, 0x0d, 0x26, - 0xce, 0xff, 0xe2, 0xb0, 0x0a, 0x0c, 0xb4, 0x86, 0x97, 0x91, 0x6c, 0xcf, 0xe7, 0x33, 0x06, 0xcf, - 0xa5, 0x43, 0x1b, 0xbd, 0x3f, 0xf6, 0xe9, 0x41, 0x68, 0x65, 0xb1, 0xc6, 0x13, 0x9a, 0x5a, 0x2f, - 0xda, 0x5b, 0x0f, 0xe8, 0x43, 0x1a, 0xd0, 0x4e, 0x93, 0x3e, 0x67, 0x27, 0x34, 0x7a, 0xe3, 0x86, - 0x32, 0x4f, 0xfe, 0xef, 0x28, 0x9c, 0xcd, 0x22, 0x63, 0xfd, 0xa2, 0x68, 0xc4, 0xe9, 0x28, 0xde, - 0xbf, 0x65, 0x40, 0xb9, 0x41, 0x9b, 0x7e, 0xc7, 0xbd, 0xe3, 0x34, 0x23, 0x5f, 0xba, 0x64, 0xd8, - 0x5c, 0xb2, 0x31, 0xb8, 0xfd, 0x10, 0x0b, 0xb4, 0x93, 0x81, 0x2f, 0x0f, 0xa7, 0x88, 0x36, 0x7d, - 0x74, 0x5a, 0x8d, 0xd8, 0x9c, 0x4c, 0x3e, 0x81, 0xb7, 0x1a, 0xda, 0x47, 0x49, 0x1d, 0x26, 0x16, - 0xfc, 0x4e, 0x87, 0xb2, 0x1f, 0x8a, 0x0b, 0xe6, 0xe5, 0xa3, 0xc3, 0xea, 0x6c, 0x53, 0x16, 0xa4, - 0x4f, 0x08, 0x74, 0x12, 0x72, 0x0b, 0xf2, 0x9b, 0xf3, 0x77, 0xc4, 0x18, 0x48, 0x67, 0xb5, 0xcd, - 0xf9, 0x3b, 0x68, 0xeb, 0x32, 0xfd, 0x61, 0xa2, 0x37, 0xff, 0x50, 0x3d, 0x03, 0xdd, 0x9c, 0xbf, - 0x43, 0xd6, 0x60, 0xda, 0xa2, 0x3f, 0xdb, 0xf3, 0x02, 0x2a, 0x16, 0xc0, 0x83, 0x3b, 0x35, 0x1c, - 0x8b, 0x22, 0x97, 0x63, 0x01, 0x2f, 0x94, 0xba, 0xbd, 0xdd, 0x7e, 0xa8, 0x46, 0xae, 0xf5, 0xd3, - 0x92, 0x5f, 0x84, 0x73, 0x8b, 0x5e, 0x28, 0xea, 0xcc, 0x0f, 0x1f, 0x5d, 0xbc, 0x87, 0x1c, 0x1d, - 0xb0, 0x1c, 0x7e, 0x22, 0x73, 0x39, 0x7c, 0xce, 0x8d, 0x99, 0xd8, 0xfc, 0x64, 0xd3, 0x4d, 0xfb, - 0xae, 0x66, 0x7f, 0x87, 0x7c, 0x0c, 0x93, 0x78, 0xda, 0x83, 0xe7, 0xb1, 0xe8, 0xce, 0x3c, 0x36, - 0xe0, 0xcb, 0x5f, 0xc8, 0xfc, 0xf2, 0x25, 0x3c, 0x3c, 0xb2, 0xf1, 0x54, 0x17, 0x5d, 0x9f, 0x35, - 0x1b, 0x41, 0xe3, 0x4c, 0x3e, 0x84, 0x29, 0xb1, 0xe9, 0xac, 0x3d, 0xdc, 0xd8, 0xa3, 0x8b, 0xce, - 0x81, 0x70, 0x42, 0x40, 0xfd, 0x4f, 0xec, 0x54, 0xb6, 0xff, 0xd0, 0x8e, 0xf6, 0xa8, 0xed, 0x3a, - 0x9a, 0x78, 0x4e, 0x11, 0x92, 0x9f, 0x87, 0xf1, 0x15, 0x1f, 0x2f, 0x9e, 0x50, 0xd4, 0x94, 0x90, - 0xcf, 0x57, 0x31, 0x72, 0x95, 0x83, 0x53, 0x9b, 0xc8, 0x8f, 0x0e, 0xab, 0xef, 0x9c, 0x76, 0x16, - 0x2a, 0x1f, 0xb0, 0xd4, 0xaf, 0x91, 0x05, 0x28, 0x6e, 0xd3, 0x1d, 0xd6, 0xda, 0x74, 0xd4, 0x95, - 0x04, 0x73, 0x79, 0xf1, 0x58, 0xfc, 0x52, 0x6f, 0x75, 0x24, 0x86, 0xf9, 0xaf, 0x0c, 0x9c, 0x81, - 0xe4, 0x3a, 0x3a, 0x82, 0xc5, 0xde, 0xe0, 0x68, 0x59, 0x3a, 0xdd, 0xae, 0xee, 0xcf, 0xcd, 0x51, - 0x98, 0x19, 0x7a, 0xc7, 0x69, 0xd2, 0x48, 0x9e, 0x57, 0x22, 0xf2, 0x43, 0x84, 0xa8, 0x66, 0x28, - 0xc7, 0x21, 0x5f, 0x81, 0xb3, 0x8b, 0xf4, 0x91, 0xd7, 0xa4, 0xb5, 0x28, 0xa2, 0x21, 0x6f, 0xed, - 0x42, 0x8d, 0x5f, 0xec, 0x95, 0xea, 0xaf, 0x1c, 0x1d, 0x56, 0xaf, 0xb8, 0x58, 0x6e, 0x3b, 0x09, - 0x82, 0xdd, 0x74, 0x54, 0x5e, 0x99, 0x1c, 0xcc, 0xff, 0x69, 0x24, 0x3d, 0x40, 0x5e, 0x87, 0x82, - 0xb5, 0x1e, 0xd7, 0x9f, 0xdf, 0xd9, 0xa5, 0xaa, 0x8f, 0x08, 0xe4, 0x6b, 0x70, 0x4e, 0xe1, 0x83, - 0x93, 0x83, 0xba, 0xac, 0x42, 0xbc, 0x31, 0xaf, 0xe2, 0x25, 0x8d, 0x52, 0x13, 0x87, 0x63, 0xa4, - 0x6a, 0x94, 0xcd, 0x83, 0x35, 0x56, 0x29, 0x58, 0xa4, 0x1d, 0x8f, 0xf3, 0x56, 0x1a, 0xab, 0xf2, - 0x76, 0x11, 0x21, 0xdd, 0xd8, 0x2c, 0x0e, 0x1f, 0x16, 0x8a, 0x85, 0xca, 0x88, 0xf9, 0xd7, 0x86, - 0x92, 0x02, 0xe0, 0x19, 0xdd, 0x3d, 0x6e, 0x6b, 0xbb, 0xc7, 0x59, 0x41, 0x1a, 0xb7, 0x8a, 0x95, - 0x65, 0xee, 0xf8, 0x53, 0x30, 0xa1, 0x21, 0xa1, 0xcb, 0xeb, 0x66, 0x48, 0x03, 0x7e, 0x3e, 0xf8, - 0x7c, 0xb9, 0xbc, 0xc6, 0xed, 0x1a, 0xca, 0x93, 0xf1, 0x2f, 0x0d, 0x98, 0x4a, 0x51, 0xb0, 0xde, - 0x60, 0x20, 0xb5, 0x37, 0x7a, 0x21, 0x0d, 0x2c, 0x84, 0x72, 0x07, 0xb9, 0x15, 0xdd, 0x41, 0xae, - 0x65, 0x31, 0x18, 0xf9, 0x32, 0x8c, 0x6c, 0xa2, 0x36, 0xaf, 0xfb, 0x58, 0xc4, 0xfc, 0xb1, 0x90, - 0xaf, 0xb0, 0x1e, 0xfb, 0x57, 0x15, 0x10, 0x58, 0x46, 0x1a, 0x30, 0xb6, 0x10, 0x50, 0x0c, 0xf6, - 0x2f, 0x0c, 0x7f, 0x19, 0xd6, 0xe4, 0x24, 0xe9, 0xcb, 0x30, 0xc1, 0xc9, 0xfc, 0xcd, 0x1c, 0x90, - 0xa4, 0x8d, 0xb4, 0x19, 0xd0, 0x28, 0x7c, 0x66, 0x07, 0xfd, 0x03, 0x6d, 0xd0, 0x5f, 0xec, 0x1b, - 0x74, 0xde, 0xbc, 0xa1, 0xc6, 0xfe, 0x4f, 0x0c, 0x38, 0x9f, 0x4d, 0x48, 0x5e, 0x86, 0xd1, 0xb5, - 0x8d, 0x75, 0xe9, 0xa6, 0x23, 0x9a, 0xe2, 0x77, 0x51, 0x4b, 0xb5, 0x44, 0x11, 0x79, 0x13, 0x46, - 0x3f, 0xb2, 0x16, 0xd8, 0xf6, 0xa5, 0x44, 0x9d, 0xfc, 0x6c, 0x60, 0x37, 0x75, 0xf3, 0x47, 0x20, - 0xa9, 0x63, 0x9b, 0x7f, 0x6a, 0x63, 0xfb, 0xad, 0x1c, 0x4c, 0xd5, 0x9a, 0x4d, 0x1a, 0x86, 0x4c, - 0x39, 0xa1, 0x61, 0xf4, 0xcc, 0x0e, 0x6c, 0xb6, 0x03, 0x8e, 0xd6, 0xb6, 0xa1, 0x46, 0xf5, 0x4f, - 0x0d, 0x38, 0x27, 0xa9, 0x1e, 0x79, 0xf4, 0xf1, 0xc6, 0x5e, 0x40, 0xc3, 0x3d, 0xbf, 0xe5, 0x0e, - 0x1b, 0x3f, 0x85, 0xbb, 0xb4, 0xd7, 0x8a, 0x68, 0xa0, 0x1e, 0x16, 0x3f, 0x44, 0x88, 0xb6, 0x4b, - 0x23, 0x84, 0xcc, 0xc1, 0x58, 0xad, 0xdb, 0x0d, 0xfc, 0x47, 0x7c, 0xd9, 0x4f, 0x88, 0xbb, 0x41, - 0x0e, 0xd2, 0xee, 0x12, 0x39, 0x88, 0x55, 0x63, 0x91, 0x76, 0xb8, 0x77, 0xf1, 0x04, 0xaf, 0x86, - 0x4b, 0x3b, 0xaa, 0xb6, 0x84, 0xe5, 0xe6, 0x37, 0x0b, 0x50, 0x56, 0x1b, 0x42, 0x4c, 0x18, 0xe5, - 0xae, 0x22, 0xea, 0x95, 0xbd, 0x83, 0x10, 0x4b, 0x94, 0x24, 0x1e, 0x38, 0xb9, 0x13, 0x3d, 0x70, - 0xb6, 0x61, 0x62, 0x3d, 0xf0, 0xbb, 0x7e, 0x48, 0x5d, 0x9e, 0xaf, 0x85, 0x4b, 0xad, 0x99, 0xd8, - 0x2d, 0x95, 0xf7, 0x39, 0x2b, 0xe2, 0xaa, 0x79, 0x57, 0x60, 0xdb, 0xe9, 0x6c, 0x2e, 0x3a, 0x1f, - 0x7e, 0xd8, 0xee, 0x84, 0xc2, 0x75, 0x3f, 0x3e, 0x6c, 0x67, 0x10, 0xfd, 0xb0, 0x9d, 0x41, 0xd4, - 0x65, 0x31, 0xf2, 0xb4, 0x96, 0x05, 0xf9, 0x4d, 0x03, 0xc6, 0x6b, 0x9d, 0x8e, 0xf0, 0xc0, 0x91, - 0x41, 0xcf, 0xe7, 0x92, 0x03, 0x77, 0xee, 0xa2, 0xc9, 0xcf, 0xdb, 0xbf, 0x2e, 0xce, 0xdb, 0xdf, - 0xf9, 0x44, 0xe7, 0xed, 0x1b, 0x81, 0xe3, 0x45, 0x21, 0x5e, 0xac, 0x26, 0x1f, 0x54, 0xdd, 0x70, - 0x95, 0x7a, 0x90, 0x77, 0xa0, 0x12, 0xcf, 0xc7, 0xe5, 0x8e, 0x4b, 0x9f, 0x50, 0xee, 0xb0, 0x34, - 0xc1, 0x23, 0xf3, 0xb4, 0x8b, 0x84, 0x34, 0xa2, 0xf9, 0x2d, 0x03, 0xce, 0xab, 0x13, 0xa2, 0xd1, - 0xdb, 0x69, 0x7b, 0x68, 0x8a, 0x90, 0x1b, 0x50, 0x12, 0xe3, 0x15, 0x2b, 0x72, 0xfd, 0x49, 0x7e, - 0x12, 0x14, 0xb2, 0xc4, 0x86, 0x88, 0xf1, 0x10, 0x76, 0xfb, 0x4c, 0x6a, 0xb9, 0xb1, 0xa2, 0xfa, - 0xac, 0xe8, 0xec, 0x4a, 0x80, 0xbf, 0xf5, 0xb1, 0x63, 0x10, 0xf3, 0x7d, 0x98, 0xd6, 0x6b, 0xd9, - 0xa0, 0x18, 0x0e, 0x26, 0x9b, 0x66, 0x64, 0x37, 0x4d, 0x96, 0x9b, 0xdb, 0x40, 0xfa, 0xe8, 0x43, - 0xbc, 0x34, 0xa2, 0x91, 0xbc, 0xd4, 0x94, 0x47, 0x4f, 0x7d, 0x88, 0x71, 0xba, 0xab, 0x71, 0xb5, - 0xbb, 0x91, 0xd4, 0xfc, 0x9b, 0x12, 0xcc, 0x64, 0x88, 0x8e, 0x13, 0xb6, 0xf6, 0xaa, 0xbe, 0x78, - 0x4a, 0xf1, 0xed, 0xbc, 0x5c, 0x32, 0xef, 0xcb, 0xd4, 0x46, 0xc7, 0x2c, 0x95, 0xe3, 0xf2, 0x1d, - 0x7d, 0x16, 0xdb, 0xbb, 0xea, 0x40, 0x33, 0xf2, 0xd4, 0x1c, 0x68, 0xea, 0x30, 0x21, 0x5a, 0x25, - 0x96, 0xf2, 0x68, 0x62, 0xa2, 0x07, 0xbc, 0xc0, 0xee, 0x5b, 0xd2, 0x3a, 0x09, 0xe7, 0x11, 0xfa, - 0xad, 0x47, 0x54, 0xf0, 0x18, 0x53, 0x79, 0x60, 0x41, 0x26, 0x0f, 0x85, 0x84, 0xfc, 0x13, 0x03, - 0x88, 0x80, 0xa8, 0xeb, 0xb9, 0x78, 0xdc, 0x7a, 0x76, 0x9f, 0xce, 0x7a, 0x7e, 0x51, 0xd6, 0x31, - 0x7b, 0x5d, 0x67, 0x54, 0x8b, 0xfc, 0x23, 0x03, 0xa6, 0xb9, 0x17, 0x87, 0x5a, 0xd9, 0xd2, 0x71, - 0x95, 0x6d, 0x3e, 0x9d, 0xca, 0x5e, 0x0e, 0xf1, 0xb3, 0x03, 0xea, 0xda, 0x5f, 0x29, 0xf2, 0x53, - 0x00, 0xf1, 0x8a, 0x92, 0xde, 0x82, 0x97, 0x33, 0xa4, 0x40, 0x8c, 0x94, 0x04, 0x3c, 0x46, 0x31, - 0x9d, 0xea, 0x5f, 0x93, 0x70, 0x23, 0xbf, 0x08, 0x67, 0xd9, 0x7a, 0x89, 0x21, 0xc2, 0xe7, 0x6c, - 0x76, 0x1c, 0xbf, 0xf2, 0xc5, 0xc1, 0x5b, 0xfb, 0x8d, 0x2c, 0x32, 0x1e, 0xb3, 0x91, 0x84, 0xbf, - 0x47, 0x6d, 0xd5, 0xe4, 0xcb, 0xa2, 0x40, 0xe7, 0x52, 0xac, 0x7d, 0x38, 0x5b, 0xc6, 0x6f, 0x66, - 0xca, 0xb7, 0x8b, 0x72, 0x2d, 0x70, 0xf9, 0x16, 0xea, 0x41, 0x17, 0x08, 0x22, 0x1f, 0x01, 0x69, - 0xf4, 0x76, 0x77, 0x69, 0x18, 0x51, 0x97, 0xc3, 0x68, 0x10, 0xce, 0x4e, 0xa0, 0x7c, 0xc0, 0x23, - 0xa3, 0x50, 0x96, 0xda, 0x81, 0x2c, 0x56, 0x27, 0x49, 0x3f, 0xf1, 0xa5, 0x1d, 0xb8, 0x38, 0xb0, - 0x99, 0x19, 0x01, 0x15, 0x73, 0x7a, 0x40, 0xc5, 0xc5, 0x41, 0xe2, 0x30, 0x54, 0x83, 0x2a, 0x7e, - 0xcf, 0x48, 0xc9, 0x3f, 0xa1, 0xac, 0xf0, 0x2c, 0x70, 0x83, 0x36, 0x88, 0x1c, 0x06, 0xc6, 0x73, - 0x09, 0x99, 0x4b, 0x94, 0x24, 0x26, 0x21, 0x55, 0x09, 0x8b, 0xb2, 0xf2, 0x53, 0x8a, 0x42, 0xf3, - 0x9f, 0x1b, 0x40, 0x78, 0x0d, 0x17, 0x9c, 0xae, 0xb3, 0xe3, 0xb5, 0xbc, 0xc8, 0xa3, 0x21, 0xb9, - 0x0f, 0x15, 0xc1, 0xc2, 0xd9, 0x69, 0x51, 0xd5, 0x57, 0x4a, 0x5c, 0xa6, 0xc6, 0x65, 0x76, 0x5a, - 0xad, 0xe9, 0x23, 0x1c, 0x30, 0x78, 0xb9, 0x4f, 0x31, 0x78, 0xe6, 0x0f, 0x0d, 0xb8, 0xd8, 0x5f, - 0x6d, 0xf1, 0xe5, 0xb8, 0xf3, 0x8c, 0x13, 0x3a, 0x2f, 0xab, 0x95, 0x39, 0x3c, 0x86, 0x7c, 0x6a, - 0xad, 0xcc, 0x27, 0xa7, 0x9a, 0xa7, 0x6f, 0xe5, 0xaf, 0xe6, 0xa0, 0xbc, 0xde, 0xea, 0xed, 0x7a, - 0x9d, 0x45, 0x27, 0x72, 0x9e, 0x59, 0x93, 0xe2, 0x6d, 0xcd, 0xa4, 0x88, 0xbd, 0xa3, 0xe2, 0x86, - 0x0d, 0x97, 0x91, 0xcb, 0x80, 0xa9, 0x84, 0x84, 0xaf, 0xd2, 0x7b, 0x50, 0x60, 0x3f, 0x84, 0x86, - 0x72, 0xa5, 0x8f, 0x31, 0x62, 0xdd, 0x88, 0xff, 0x13, 0x4a, 0xbe, 0x9e, 0x07, 0x0d, 0x39, 0x5c, - 0xfa, 0x09, 0x9e, 0xc6, 0xe8, 0xf4, 0x29, 0x17, 0xff, 0x99, 0x01, 0x95, 0x74, 0x4b, 0xc8, 0x7d, - 0x18, 0x63, 0x9c, 0xbc, 0x38, 0x25, 0xd2, 0x2b, 0x03, 0xda, 0x7c, 0x43, 0xa0, 0xf1, 0xea, 0x61, - 0xe7, 0x53, 0x0e, 0xb1, 0x24, 0x87, 0x4b, 0x16, 0x94, 0x55, 0xac, 0x8c, 0xda, 0xbd, 0xa1, 0x8b, - 0xa6, 0xf3, 0xd9, 0xfd, 0xa0, 0xd6, 0xfa, 0x77, 0xb5, 0x5a, 0x0b, 0xa1, 0x34, 0x6c, 0x6e, 0x3b, - 0x0c, 0x0f, 0xe3, 0x19, 0x3c, 0xd4, 0x79, 0x26, 0x93, 0x7d, 0xe8, 0xe1, 0x61, 0x1c, 0xc6, 0x6c, - 0x11, 0xfe, 0x3d, 0x31, 0xcf, 0xd0, 0x16, 0xe9, 0x22, 0x44, 0xd5, 0x67, 0x39, 0x8e, 0xf9, 0xf7, - 0xf2, 0x70, 0x3e, 0xa9, 0x1e, 0xcf, 0xf4, 0xb7, 0xee, 0x04, 0x4e, 0x3b, 0x3c, 0x61, 0x05, 0x5c, - 0xed, 0xab, 0x1a, 0x86, 0x3f, 0xcb, 0xaa, 0x29, 0x15, 0x32, 0x53, 0x15, 0x42, 0x23, 0x8e, 0x57, - 0x48, 0x56, 0x83, 0xdc, 0x87, 0x7c, 0x83, 0x46, 0x22, 0x48, 0xf2, 0xb5, 0xbe, 0x5e, 0x55, 0xeb, - 0x75, 0xa3, 0x41, 0x23, 0x3e, 0x88, 0xdc, 0xcf, 0x9c, 0x6a, 0x7e, 0xdf, 0x4c, 0x1d, 0xdf, 0x86, - 0xd1, 0xa5, 0x27, 0x5d, 0xda, 0x8c, 0x44, 0x6c, 0xe4, 0xb5, 0xe3, 0xf9, 0x71, 0x5c, 0x25, 0x02, - 0x93, 0x22, 0x40, 0xed, 0x2c, 0x8e, 0x72, 0xe9, 0x36, 0x14, 0xe5, 0xc7, 0x4f, 0x15, 0x49, 0xf8, - 0x36, 0x8c, 0x2b, 0x1f, 0x39, 0xd5, 0xa4, 0xff, 0x1b, 0x03, 0x46, 0x99, 0xd0, 0xdb, 0x7a, 0xeb, - 0x19, 0x95, 0x48, 0xb7, 0x34, 0x89, 0x34, 0xad, 0x84, 0xbc, 0xe0, 0xba, 0x7c, 0xeb, 0x04, 0x59, - 0x74, 0x68, 0x00, 0x24, 0xc8, 0xe4, 0x2e, 0x8c, 0xf1, 0x8b, 0x1c, 0x99, 0x46, 0x53, 0x8d, 0xa1, - 0x11, 0x25, 0x89, 0x96, 0xe3, 0x77, 0xd3, 0x6a, 0xa1, 0xa4, 0x26, 0x8b, 0x89, 0x9f, 0xb1, 0x1a, - 0xb4, 0xc9, 0xd8, 0x2c, 0xf8, 0x1d, 0x1e, 0x53, 0x11, 0x2a, 0xe9, 0xa6, 0xb2, 0x1d, 0x8e, 0x6b, - 0xe2, 0x60, 0x23, 0x7f, 0x1c, 0x93, 0xf3, 0x82, 0x49, 0xf6, 0x99, 0xc7, 0xb7, 0xc7, 0x79, 0x94, - 0x82, 0xac, 0xd8, 0x7b, 0x50, 0xbe, 0xe3, 0x07, 0x8f, 0x9d, 0xc0, 0xad, 0xed, 0x52, 0xe1, 0x21, - 0x5e, 0x44, 0x37, 0xef, 0x89, 0x87, 0x1c, 0x6e, 0x3b, 0xac, 0xe0, 0x47, 0x87, 0xd5, 0x42, 0xdd, - 0xf7, 0x5b, 0x96, 0x86, 0x4e, 0xd6, 0x60, 0xe2, 0x81, 0xf3, 0x44, 0xdc, 0xd7, 0x6d, 0x6c, 0xac, - 0x08, 0x3f, 0x93, 0x6b, 0x47, 0x87, 0xd5, 0x8b, 0x6d, 0xe7, 0x49, 0x7c, 0xcf, 0x37, 0xd8, 0x15, - 0x5a, 0xa7, 0x27, 0x1e, 0x4c, 0xae, 0xfb, 0x41, 0x24, 0x3e, 0xc2, 0x74, 0xda, 0xfc, 0x80, 0xeb, - 0xb6, 0xb9, 0xcc, 0xeb, 0xb6, 0x8b, 0x4c, 0x91, 0xb7, 0x1f, 0xc6, 0xe4, 0x5a, 0x68, 0x9d, 0xc6, - 0x98, 0xbc, 0x07, 0xd3, 0x0b, 0x34, 0x88, 0xbc, 0x87, 0x5e, 0xd3, 0x89, 0xe8, 0x1d, 0x3f, 0x68, - 0x3b, 0x91, 0x38, 0x50, 0x41, 0x83, 0xba, 0x49, 0x39, 0xa7, 0xb6, 0x13, 0x59, 0xfd, 0x98, 0xe4, - 0x6b, 0x59, 0x9e, 0x3b, 0x23, 0xd8, 0xfc, 0x37, 0x99, 0x52, 0x90, 0xe1, 0xb9, 0x33, 0xa0, 0x0b, - 0x32, 0x7c, 0x78, 0x76, 0x8f, 0xbb, 0xf6, 0x2c, 0xd6, 0x6f, 0x8a, 0x2b, 0xd8, 0x93, 0xaf, 0x35, - 0xe3, 0x71, 0x1b, 0x70, 0xbd, 0x39, 0x0f, 0xf9, 0xfa, 0xfa, 0x1d, 0x3c, 0x22, 0x11, 0xd7, 0x8c, - 0xb4, 0xb3, 0xe7, 0x74, 0x9a, 0xa8, 0xcb, 0x08, 0xdf, 0x05, 0x55, 0xe0, 0xd5, 0xd7, 0xef, 0x10, - 0x07, 0x66, 0xd6, 0x69, 0xd0, 0xf6, 0xa2, 0xaf, 0xdc, 0xbc, 0xa9, 0x0c, 0x54, 0x11, 0xab, 0x36, - 0x27, 0xaa, 0x56, 0xed, 0x22, 0x8a, 0xfd, 0xe4, 0xe6, 0xcd, 0xcc, 0xe1, 0x88, 0x2b, 0x96, 0xc5, - 0x8b, 0x2c, 0xc1, 0xe4, 0x03, 0xe7, 0x89, 0xb8, 0x90, 0x8e, 0x6d, 0xbc, 0x3c, 0x7a, 0xc6, 0xe3, - 0xc4, 0x6a, 0x26, 0x45, 0xea, 0x10, 0xeb, 0x44, 0xe4, 0x5d, 0x18, 0x4f, 0xa6, 0x57, 0x88, 0x57, - 0x91, 0x79, 0xee, 0x12, 0xa9, 0x4c, 0x4e, 0xed, 0x2c, 0x49, 0x41, 0x27, 0x9b, 0xb1, 0x89, 0xce, - 0x15, 0x52, 0x74, 0x14, 0x2c, 0xd5, 0xe7, 0x54, 0x13, 0xdd, 0xc1, 0x12, 0xad, 0x59, 0x53, 0xb1, - 0x8a, 0xce, 0x3d, 0x65, 0x2c, 0x9d, 0x8b, 0x62, 0xf9, 0xaf, 0x07, 0x7e, 0xbb, 0x1b, 0xa1, 0xc7, - 0x60, 0xca, 0xf2, 0xef, 0x62, 0x49, 0x86, 0xe5, 0xcf, 0x49, 0xb2, 0xef, 0xd9, 0x27, 0x3e, 0xc5, - 0x3d, 0x3b, 0x85, 0xc2, 0x8a, 0xdf, 0xdc, 0x47, 0x17, 0xc1, 0x52, 0xfd, 0x23, 0x26, 0x3f, 0x5a, - 0x7e, 0x73, 0xff, 0xe9, 0xdd, 0x0f, 0x23, 0x7b, 0xb2, 0xca, 0xda, 0xce, 0xa6, 0x95, 0xf8, 0xf4, - 0xec, 0x94, 0x76, 0xd3, 0xa6, 0x95, 0x71, 0x45, 0x85, 0xcf, 0x42, 0xd9, 0x10, 0x4b, 0x27, 0x27, - 0x14, 0x2a, 0x8b, 0x34, 0xdc, 0x8f, 0xfc, 0xee, 0x42, 0xcb, 0xeb, 0xee, 0xf8, 0x4e, 0xe0, 0xce, - 0x56, 0x06, 0x08, 0x8c, 0xd7, 0x33, 0x05, 0xc6, 0xb4, 0xcb, 0xe9, 0xed, 0xa6, 0x64, 0x60, 0xf5, - 0xb1, 0x24, 0x5f, 0x83, 0x49, 0xb6, 0x5a, 0x96, 0x9e, 0x44, 0xb4, 0xc3, 0xa7, 0xd2, 0x34, 0x6e, - 0xf5, 0x67, 0x95, 0x20, 0xc3, 0xb8, 0x90, 0x4f, 0x52, 0x94, 0x1e, 0x34, 0x26, 0x50, 0x27, 0xa9, - 0xce, 0xca, 0xfc, 0xa9, 0x54, 0x9f, 0x90, 0x65, 0x18, 0x13, 0x35, 0x10, 0xbb, 0x4e, 0x7f, 0x5b, - 0x5e, 0xcc, 0x6c, 0xcb, 0x98, 0x68, 0x8b, 0x25, 0xe9, 0xcd, 0x7f, 0x63, 0xc0, 0x84, 0xf6, 0x39, - 0x72, 0x5b, 0x71, 0x5f, 0x49, 0xdc, 0xce, 0x34, 0x9c, 0xcc, 0xf4, 0xf4, 0xb7, 0x85, 0xcf, 0x52, - 0x6e, 0x30, 0x5d, 0x66, 0xe6, 0x30, 0x19, 0xf4, 0x9f, 0x3f, 0x3e, 0xe8, 0xbf, 0x30, 0x20, 0xe8, - 0xff, 0xdb, 0x13, 0x30, 0xa9, 0x6f, 0x70, 0x4c, 0xe3, 0x5c, 0xf1, 0x77, 0xbd, 0x8e, 0xb4, 0x5b, - 0x79, 0x1a, 0x0b, 0x84, 0x68, 0xb9, 0xde, 0x11, 0x42, 0x5e, 0x05, 0x88, 0xaf, 0x66, 0xa5, 0x69, - 0x2a, 0x32, 0xd3, 0x2b, 0x05, 0xe4, 0xa7, 0x01, 0x56, 0x7d, 0x97, 0xc6, 0x99, 0x50, 0x8e, 0x39, - 0x50, 0x7a, 0x5d, 0x1c, 0x28, 0x89, 0x6c, 0xf2, 0x47, 0x87, 0xd5, 0x73, 0x1d, 0xdf, 0xa5, 0xfd, - 0x29, 0x50, 0x14, 0x8e, 0xe4, 0x4b, 0x30, 0x62, 0xf5, 0x5a, 0x54, 0x26, 0xe6, 0x18, 0x97, 0x13, - 0xbe, 0xd7, 0x52, 0xb2, 0x4c, 0x06, 0xbd, 0xf4, 0x3d, 0x02, 0x03, 0x90, 0x0f, 0x00, 0xee, 0xf7, - 0x76, 0xe8, 0xdd, 0xc0, 0xef, 0x75, 0x65, 0xe4, 0x2f, 0x9a, 0xb1, 0xfb, 0x71, 0x1a, 0x27, 0x7b, - 0x17, 0x0b, 0xd5, 0x8f, 0x27, 0x24, 0x64, 0x0d, 0xc6, 0x84, 0xf8, 0x10, 0xe7, 0xf4, 0x2f, 0x65, - 0x9d, 0x10, 0x29, 0x3a, 0x84, 0xc8, 0x94, 0x81, 0x60, 0xfd, 0xd0, 0x86, 0x9b, 0xe1, 0xef, 0x42, - 0x89, 0xb1, 0x67, 0xa6, 0x76, 0x28, 0xf6, 0x0e, 0xf4, 0x1f, 0x54, 0x2a, 0xc4, 0xcc, 0x72, 0x2d, - 0x5f, 0x57, 0x4c, 0x40, 0xbe, 0x86, 0xb9, 0x6d, 0x44, 0x57, 0x1f, 0x7b, 0xd0, 0xf8, 0x5a, 0x5f, - 0x57, 0x9f, 0x75, 0xba, 0xdd, 0x8c, 0x64, 0x60, 0x31, 0x3f, 0xb2, 0x1b, 0xc7, 0xd8, 0xc4, 0xa9, - 0x86, 0x8f, 0xf9, 0xc0, 0xf5, 0xbe, 0x0f, 0xcc, 0xca, 0xb0, 0x91, 0xfe, 0x8c, 0x36, 0x1a, 0x5f, - 0xd2, 0x85, 0x4a, 0x92, 0x46, 0x4b, 0x7c, 0x0b, 0x8e, 0xfb, 0xd6, 0x9b, 0x7d, 0xdf, 0x52, 0x07, - 0xb0, 0xef, 0x73, 0x7d, 0xdc, 0x89, 0x9b, 0xa4, 0x85, 0x15, 0xdf, 0x1b, 0x3f, 0xee, 0x7b, 0xaf, - 0xf6, 0x7d, 0x6f, 0xc6, 0xdd, 0xe9, 0xff, 0x4e, 0x8a, 0x27, 0x79, 0x17, 0x26, 0x24, 0x04, 0xd7, - 0x07, 0x1e, 0xf0, 0x09, 0xfd, 0xde, 0xdd, 0x41, 0xa7, 0x31, 0x3d, 0x9d, 0x8b, 0x8a, 0xac, 0x52, - 0xf3, 0xd9, 0x31, 0xa1, 0x51, 0xa7, 0x67, 0x85, 0x8e, 0x4c, 0xbe, 0x0a, 0xe3, 0xcb, 0x6d, 0xd6, - 0x10, 0xbf, 0xe3, 0x44, 0x14, 0x37, 0xa3, 0xe4, 0xd0, 0x54, 0x29, 0x51, 0xa6, 0x2a, 0xcf, 0xf1, - 0x98, 0x14, 0xa9, 0x9b, 0xb9, 0x42, 0xc1, 0x3a, 0x8f, 0x1f, 0xbf, 0x88, 0x39, 0x1c, 0x8a, 0xad, - 0xe7, 0xc5, 0x8c, 0x83, 0x4b, 0x85, 0x3d, 0xca, 0x72, 0x7e, 0xaa, 0x63, 0x8b, 0x05, 0xa1, 0x75, - 0x9e, 0xce, 0x93, 0xbc, 0x07, 0xe3, 0x22, 0xa2, 0xb1, 0x66, 0xad, 0x86, 0xb3, 0x15, 0x6c, 0x3c, - 0xe6, 0x62, 0x93, 0xc1, 0x8f, 0xb6, 0x13, 0xa4, 0x6e, 0xaf, 0x12, 0x7c, 0xf2, 0x15, 0x38, 0xbb, - 0xed, 0x75, 0x5c, 0xff, 0x71, 0x28, 0x04, 0xb8, 0x10, 0x74, 0xd3, 0x89, 0x8f, 0xce, 0x63, 0x5e, - 0x6e, 0xcb, 0x6d, 0xab, 0x4f, 0xf0, 0x65, 0x72, 0x20, 0xbf, 0xd0, 0xc7, 0x99, 0xcf, 0x20, 0x72, - 0xdc, 0x0c, 0x9a, 0xef, 0x9b, 0x41, 0xfd, 0x9f, 0x4f, 0x4f, 0xa7, 0xcc, 0xcf, 0x10, 0x1f, 0x88, - 0xae, 0x73, 0x7c, 0xe8, 0x7b, 0x9d, 0xd9, 0x19, 0xed, 0x21, 0x8f, 0xd8, 0x65, 0x16, 0xf1, 0xd6, - 0xfd, 0x96, 0xd7, 0x3c, 0xa8, 0x9b, 0x47, 0x87, 0xd5, 0x97, 0xd2, 0xda, 0xcc, 0xc7, 0xbe, 0x76, - 0xb8, 0x90, 0xc1, 0x9a, 0x7c, 0x15, 0xca, 0xec, 0x6f, 0xac, 0xfa, 0x9d, 0xd5, 0xae, 0xba, 0x14, - 0x4c, 0xf1, 0x1d, 0x1c, 0x23, 0x0c, 0xb9, 0xcc, 0xd0, 0x0a, 0x35, 0x56, 0xe6, 0x0f, 0x0d, 0x38, - 0x9b, 0x55, 0xd7, 0x13, 0xf2, 0xdb, 0x98, 0xa9, 0x4b, 0x6f, 0x3c, 0x97, 0xe0, 0x97, 0xde, 0xf1, - 0x55, 0x77, 0x15, 0x46, 0x98, 0xad, 0x2c, 0x9d, 0xb2, 0x70, 0x3b, 0x64, 0xf6, 0x74, 0x68, 0x71, - 0x38, 0x43, 0x40, 0x67, 0x7a, 0xdc, 0x2f, 0x47, 0x38, 0x02, 0x7a, 0xdc, 0x5b, 0x1c, 0xce, 0x10, - 0xd8, 0xb6, 0x2b, 0xb7, 0x09, 0x44, 0x60, 0xbb, 0x71, 0x68, 0x71, 0x38, 0x79, 0x0d, 0xc6, 0xd6, - 0x3a, 0x2b, 0xd4, 0x79, 0x44, 0xc5, 0x8d, 0x13, 0x9e, 0xa3, 0xf8, 0x1d, 0xbb, 0xc5, 0x60, 0x96, - 0x2c, 0x34, 0xbf, 0x6b, 0xc0, 0x74, 0x5f, 0x37, 0x9d, 0x9c, 0xc2, 0xe7, 0xf8, 0xeb, 0xbd, 0x61, - 0xda, 0xc7, 0xab, 0x5f, 0xc8, 0xae, 0xbe, 0xf9, 0x57, 0x79, 0xb8, 0x30, 0x60, 0xd7, 0x4a, 0xae, - 0xe6, 0x8d, 0x13, 0xaf, 0xe6, 0xbf, 0xce, 0x76, 0x09, 0xc7, 0x6b, 0x87, 0x1b, 0x7e, 0x52, 0xe3, - 0xe4, 0x16, 0x03, 0xcb, 0x64, 0x8e, 0x0c, 0x99, 0xcf, 0xe1, 0x62, 0x13, 0x29, 0xec, 0xc8, 0xef, - 0x3b, 0x33, 0xd6, 0x99, 0xf5, 0x5d, 0x8e, 0xe7, 0x7f, 0x4c, 0x2e, 0xc7, 0xf5, 0x2b, 0xa9, 0xc2, - 0x53, 0xbd, 0x92, 0xca, 0x3e, 0x24, 0x1f, 0xf9, 0x34, 0x57, 0x01, 0xff, 0x2e, 0x75, 0x1d, 0xff, - 0xe3, 0x38, 0xd4, 0xd7, 0x60, 0x64, 0x7b, 0x8f, 0x06, 0x52, 0xbf, 0xc5, 0x8a, 0x3c, 0x66, 0x00, - 0xb5, 0x22, 0x88, 0x61, 0xfe, 0x3c, 0x94, 0xd5, 0x8f, 0xe1, 0x5a, 0x66, 0xbf, 0xc5, 0x62, 0xe2, - 0x6b, 0x99, 0x01, 0x2c, 0x0e, 0x3f, 0x31, 0x23, 0x56, 0xd2, 0x0b, 0xf9, 0x93, 0x7a, 0xc1, 0xfc, - 0xb7, 0x06, 0x14, 0x30, 0x21, 0xc0, 0x5b, 0x50, 0x92, 0x47, 0xa5, 0x6a, 0x90, 0xfc, 0x8c, 0x3c, - 0x49, 0x0d, 0x75, 0x7f, 0x06, 0x01, 0x64, 0x9f, 0xda, 0xa2, 0xc1, 0x8e, 0xe6, 0xf6, 0xf2, 0x88, - 0x01, 0xd4, 0x4f, 0x21, 0xc6, 0x29, 0xba, 0x04, 0x5d, 0x7b, 0x84, 0x7d, 0xcf, 0x17, 0x3c, 0x77, - 0xed, 0xe9, 0xb3, 0xeb, 0x25, 0x96, 0xf9, 0xdb, 0x06, 0x9c, 0xcb, 0xd4, 0x03, 0xd8, 0x57, 0xb9, - 0xc2, 0xa1, 0xcc, 0x88, 0xb4, 0xb6, 0xc1, 0x31, 0x4e, 0xe3, 0xc2, 0x73, 0x8a, 0xe1, 0xfd, 0x1c, - 0x94, 0x62, 0xfb, 0x8c, 0x9c, 0x95, 0x43, 0x87, 0xe7, 0x69, 0xd2, 0x98, 0xf9, 0x1b, 0x03, 0x46, - 0x59, 0x15, 0x9e, 0xd9, 0xe8, 0x8a, 0xec, 0xd3, 0x55, 0xd6, 0xa4, 0xa1, 0x62, 0x2a, 0xfe, 0x60, - 0x14, 0x20, 0x41, 0x26, 0x3b, 0x30, 0xb9, 0xb6, 0xbc, 0xb8, 0xb0, 0xec, 0xd2, 0x4e, 0x84, 0xb7, - 0x7c, 0xa9, 0x28, 0x7b, 0x66, 0x58, 0x06, 0x1d, 0xa7, 0x25, 0x10, 0x0e, 0x92, 0xe5, 0xe9, 0x7b, - 0x6e, 0xd3, 0xf6, 0x62, 0x3a, 0x55, 0x21, 0xd3, 0x39, 0xb2, 0x6f, 0x34, 0x6a, 0x0f, 0x56, 0x94, - 0x6f, 0xe4, 0x86, 0xfc, 0x46, 0xe8, 0xb4, 0x5b, 0x03, 0xbe, 0xa1, 0x73, 0x24, 0x7b, 0x50, 0xb9, - 0x8b, 0xb2, 0x5b, 0xf9, 0x4a, 0xfe, 0xf8, 0xaf, 0xbc, 0x2c, 0xbe, 0xf2, 0x02, 0x17, 0xfa, 0xd9, - 0xdf, 0xe9, 0xe3, 0x9a, 0xcc, 0xdc, 0xc2, 0x89, 0x33, 0xf7, 0x6f, 0x1b, 0x30, 0xca, 0x37, 0x87, - 0xf8, 0x2d, 0x8b, 0xcc, 0xed, 0x67, 0xfb, 0xe9, 0x6c, 0x3f, 0x95, 0x08, 0xff, 0x53, 0x0d, 0x70, - 0x5e, 0x46, 0x16, 0x53, 0x0f, 0x63, 0xc8, 0x23, 0x74, 0x54, 0x4c, 0x79, 0x49, 0xe2, 0x08, 0xc5, - 0xdf, 0xc4, 0x50, 0xb9, 0x70, 0x0c, 0xf5, 0x99, 0xbe, 0xb1, 0x4f, 0xf9, 0x4c, 0xdf, 0x0a, 0x94, - 0x84, 0x67, 0x4f, 0xfd, 0x40, 0x98, 0x9f, 0xf2, 0x80, 0x25, 0x86, 0x2b, 0xc9, 0xa7, 0x39, 0xc8, - 0xde, 0xd1, 0x52, 0xc7, 0xc5, 0x88, 0x64, 0x0d, 0x4a, 0x49, 0x68, 0x48, 0x49, 0xbb, 0x07, 0x8d, - 0xe1, 0xc2, 0xf5, 0x95, 0x47, 0x1f, 0x66, 0x46, 0x82, 0x24, 0x3c, 0xcc, 0x6f, 0x1a, 0x50, 0x49, - 0xcf, 0x17, 0xf2, 0x2e, 0x8c, 0xc7, 0xd1, 0x39, 0xb1, 0x7f, 0x01, 0x1e, 0x64, 0x26, 0xe1, 0x3c, - 0x9a, 0xa7, 0x81, 0x8a, 0x4e, 0xe6, 0xa1, 0xc8, 0x96, 0x9d, 0x92, 0x3b, 0x18, 0xe5, 0x49, 0x4f, - 0xc0, 0xd4, 0x7b, 0x3d, 0x89, 0xa7, 0xac, 0xda, 0xff, 0x90, 0x87, 0x71, 0x65, 0xb0, 0xc8, 0x35, - 0x28, 0x2e, 0x87, 0x2b, 0x7e, 0x73, 0x9f, 0xba, 0xe2, 0xba, 0x00, 0x5f, 0x61, 0xf4, 0x42, 0xbb, - 0x85, 0x40, 0x2b, 0x2e, 0x26, 0x75, 0x98, 0xe0, 0xff, 0xc9, 0x28, 0xcc, 0x5c, 0x72, 0xd4, 0xc9, - 0x91, 0x65, 0xfc, 0xa5, 0xba, 0xc3, 0x6a, 0x24, 0xe4, 0x1b, 0x00, 0x1c, 0xc0, 0xc6, 0x77, 0x08, - 0xc7, 0x5e, 0xb9, 0x80, 0xcf, 0x89, 0x0f, 0x44, 0x9e, 0xda, 0x42, 0x9c, 0x0a, 0x0a, 0x43, 0x7c, - 0x01, 0xce, 0x6f, 0xee, 0x0f, 0xff, 0x06, 0x64, 0xf2, 0x02, 0x9c, 0xdf, 0xdc, 0xb7, 0xb3, 0xbd, - 0xbc, 0x54, 0x96, 0xe4, 0x5b, 0x06, 0x5c, 0xb2, 0x68, 0xd3, 0x7f, 0x44, 0x83, 0x83, 0x5a, 0x84, - 0x58, 0xea, 0x17, 0x4f, 0x76, 0x29, 0xbb, 0x25, 0xbe, 0xf8, 0x7a, 0x20, 0xb8, 0x60, 0x38, 0x4a, - 0xbb, 0x1b, 0xd9, 0xc7, 0x54, 0xe1, 0x98, 0x4f, 0x9a, 0x7f, 0x6e, 0x28, 0x4b, 0x80, 0xac, 0x42, - 0x29, 0x9e, 0x2c, 0xe2, 0xc0, 0x31, 0x56, 0x8e, 0x24, 0xdc, 0xa2, 0x0f, 0xeb, 0x2f, 0x88, 0x93, - 0xfd, 0x99, 0x78, 0xca, 0x69, 0x2b, 0x42, 0x02, 0xc9, 0x97, 0xa1, 0x80, 0x43, 0x75, 0x72, 0xb2, - 0x29, 0xb9, 0xd5, 0x14, 0xd8, 0x18, 0x61, 0xad, 0x91, 0x92, 0x7c, 0x41, 0x78, 0x79, 0xe4, 0xb5, - 0x34, 0xae, 0x0c, 0xc4, 0xea, 0x11, 0xef, 0x31, 0x89, 0x63, 0xa1, 0x32, 0x5b, 0xff, 0x4e, 0x0e, - 0x2a, 0xe9, 0x85, 0x47, 0x3e, 0x80, 0xf2, 0xba, 0x13, 0x86, 0x8f, 0xfd, 0xc0, 0xbd, 0xe7, 0x84, - 0x7b, 0x22, 0x35, 0x24, 0xda, 0x7c, 0x5d, 0x01, 0xb7, 0xf7, 0x1c, 0x2d, 0x85, 0x98, 0x46, 0xc0, - 0x36, 0xe4, 0x0d, 0xe1, 0xaf, 0xae, 0x2c, 0xa0, 0xc8, 0x8f, 0xba, 0xa9, 0xd4, 0x90, 0x12, 0x8d, - 0xbc, 0x05, 0x79, 0x1e, 0xfb, 0xa6, 0xe6, 0x15, 0x7a, 0x70, 0xa7, 0xc6, 0xc3, 0x85, 0xf8, 0x65, - 0xb2, 0x7e, 0x2a, 0xcf, 0xf0, 0xc9, 0x8a, 0x12, 0x39, 0x35, 0xaa, 0xe5, 0x57, 0x91, 0xe0, 0xb8, - 0x71, 0x27, 0x87, 0x50, 0x7d, 0x58, 0x28, 0xe6, 0x2b, 0x05, 0x11, 0x9f, 0xf3, 0x47, 0x79, 0x28, - 0xc5, 0xdf, 0x27, 0x04, 0x50, 0xdf, 0x10, 0xb7, 0xc2, 0xf8, 0x3f, 0xb9, 0x08, 0x45, 0xa9, 0x62, - 0x88, 0x9b, 0xe1, 0xb1, 0x50, 0xa8, 0x17, 0xb3, 0x20, 0x75, 0x09, 0xae, 0x5e, 0x58, 0xf2, 0x27, - 0xb9, 0x09, 0xb1, 0xa2, 0x30, 0x48, 0xa3, 0x28, 0xb0, 0x01, 0xb3, 0x62, 0x34, 0x32, 0x09, 0x39, - 0x8f, 0xfb, 0x22, 0x97, 0xac, 0x9c, 0xe7, 0x92, 0x0f, 0xa0, 0xe8, 0xb8, 0x2e, 0x75, 0x6d, 0x27, - 0x1a, 0xe2, 0x3d, 0xce, 0x22, 0xe3, 0xc6, 0x25, 0x3a, 0x52, 0xd5, 0x22, 0x52, 0x83, 0x12, 0x3e, - 0xc7, 0xd8, 0x0b, 0x87, 0x7a, 0xc3, 0x31, 0xe1, 0x50, 0x64, 0x64, 0x9b, 0x21, 0x75, 0xc9, 0xeb, - 0x50, 0x60, 0xa3, 0x29, 0xf6, 0x83, 0x38, 0x5b, 0xdc, 0xda, 0xc6, 0x3a, 0xef, 0xb0, 0x7b, 0x67, - 0x2c, 0x44, 0x20, 0xaf, 0x40, 0xbe, 0x37, 0xff, 0x50, 0x48, 0xfa, 0x4a, 0x12, 0x16, 0x19, 0xa3, - 0xb1, 0x62, 0x72, 0x0b, 0x8a, 0x8f, 0xf5, 0x00, 0xb8, 0x73, 0xa9, 0x61, 0x8c, 0xf1, 0x63, 0xc4, - 0x7a, 0x11, 0x46, 0x79, 0xb8, 0x99, 0xf9, 0x12, 0x40, 0xf2, 0xe9, 0xfe, 0x0b, 0x7c, 0xf3, 0x1b, - 0x50, 0x8a, 0x3f, 0x49, 0x5e, 0x04, 0xd8, 0xa7, 0x07, 0xf6, 0x9e, 0xd3, 0x71, 0xc5, 0x33, 0x24, - 0x65, 0xab, 0xb4, 0x4f, 0x0f, 0xee, 0x21, 0x80, 0x5c, 0x80, 0xb1, 0x2e, 0x1b, 0x55, 0x99, 0xd8, - 0xd4, 0x1a, 0xed, 0xf6, 0x76, 0xd8, 0x0c, 0x9d, 0x85, 0x31, 0x3c, 0x3a, 0x10, 0x0b, 0x6d, 0xc2, - 0x92, 0x3f, 0xcd, 0xdf, 0xcb, 0x61, 0xc8, 0xbb, 0x52, 0x4f, 0xf2, 0x32, 0x4c, 0x34, 0x03, 0x8a, - 0xdb, 0x91, 0xc3, 0xd4, 0x22, 0xf1, 0x9d, 0x72, 0x02, 0x5c, 0x76, 0xc9, 0x6b, 0x30, 0x95, 0x64, - 0x5a, 0xb5, 0x9b, 0x3b, 0x22, 0xfc, 0xb5, 0x6c, 0x4d, 0x74, 0x65, 0xaa, 0xd5, 0x85, 0x1d, 0xf4, - 0xa1, 0xaf, 0xa8, 0xa1, 0x66, 0x91, 0xcc, 0x9a, 0x5a, 0xb2, 0xa6, 0x14, 0x38, 0x5e, 0x3b, 0x9c, - 0x87, 0x51, 0xc7, 0xd9, 0xed, 0x79, 0xdc, 0x9f, 0xb7, 0x6c, 0x89, 0x5f, 0xe4, 0xf3, 0x30, 0x1d, - 0x7a, 0xbb, 0x1d, 0x27, 0xea, 0x05, 0x22, 0xe7, 0x00, 0x0d, 0x70, 0x4a, 0x4d, 0x58, 0x95, 0xb8, - 0x60, 0x81, 0xc3, 0xc9, 0x9b, 0x40, 0xd4, 0xef, 0xf9, 0x3b, 0x1f, 0xd3, 0x26, 0x9f, 0x6a, 0x65, - 0x6b, 0x5a, 0x29, 0x59, 0xc3, 0x02, 0xf2, 0x39, 0x28, 0x07, 0x34, 0x44, 0x95, 0x0c, 0xbb, 0x0d, - 0x33, 0xa9, 0x58, 0xe3, 0x12, 0x76, 0x9f, 0x1e, 0x98, 0x75, 0x98, 0xee, 0x5b, 0x8f, 0xe4, 0x4d, - 0xae, 0xdd, 0x8b, 0xfd, 0xb9, 0xcc, 0x8d, 0x19, 0x26, 0xa4, 0x52, 0x2f, 0xf8, 0x72, 0x24, 0xb3, - 0x03, 0x65, 0x55, 0xbe, 0x9e, 0x10, 0x58, 0x7c, 0x1e, 0x1d, 0x0b, 0xb9, 0xf0, 0x19, 0x3d, 0x3a, - 0xac, 0xe6, 0x3c, 0x17, 0xdd, 0x09, 0xaf, 0x42, 0x51, 0x6a, 0x09, 0xea, 0x33, 0x1c, 0x42, 0xa1, - 0x3c, 0xb0, 0xe2, 0x52, 0xf3, 0x75, 0x18, 0x13, 0x22, 0xf4, 0xf8, 0x63, 0x1c, 0xf3, 0x97, 0x73, - 0x30, 0x65, 0x51, 0xb6, 0xc0, 0xc5, 0x03, 0x17, 0xcf, 0x59, 0xce, 0x59, 0xad, 0x6d, 0xc7, 0xc4, - 0xf1, 0x7f, 0xcf, 0x80, 0x99, 0x0c, 0xdc, 0x4f, 0x94, 0xa4, 0xea, 0x36, 0x94, 0x16, 0x3d, 0xa7, - 0x55, 0x73, 0xdd, 0xd8, 0x41, 0x12, 0xb5, 0x41, 0x97, 0x2d, 0x27, 0x87, 0x41, 0xd5, 0xcd, 0x34, - 0x46, 0x25, 0xd7, 0xc5, 0xa4, 0x48, 0xd2, 0xe8, 0xc9, 0xac, 0xb6, 0xc0, 0xeb, 0x94, 0xe4, 0xb4, - 0xc5, 0x30, 0x34, 0x0e, 0x4c, 0xee, 0xc0, 0x9f, 0xd9, 0xa1, 0xcb, 0x0e, 0x43, 0x4b, 0x37, 0x6f, - 0x28, 0xb3, 0xf3, 0x9b, 0x39, 0x38, 0x9f, 0x4d, 0xf8, 0x49, 0xf3, 0x8d, 0x61, 0x12, 0x05, 0x25, - 0x71, 0x30, 0xe6, 0x1b, 0xe3, 0x19, 0x17, 0x10, 0x3f, 0x41, 0x20, 0x0f, 0x61, 0x62, 0xc5, 0x09, - 0xa3, 0x7b, 0xd4, 0x09, 0xa2, 0x1d, 0xea, 0x44, 0x43, 0x68, 0xb0, 0xf1, 0x3b, 0xb9, 0xb8, 0xa9, - 0xed, 0x49, 0xca, 0xf4, 0x3b, 0xb9, 0x1a, 0xdb, 0x78, 0xa2, 0x14, 0x86, 0x98, 0x28, 0x3f, 0x0b, - 0x53, 0x0d, 0xda, 0x76, 0xba, 0x7b, 0x7e, 0x40, 0xc5, 0xc9, 0xf3, 0x0d, 0x98, 0x88, 0x41, 0x99, - 0xb3, 0x45, 0x2f, 0xd6, 0xf0, 0x95, 0x8e, 0x48, 0x44, 0x89, 0x5e, 0x6c, 0xfe, 0x4e, 0x0e, 0x2e, - 0xd4, 0x9a, 0xe2, 0x98, 0x5e, 0x14, 0xc8, 0xdb, 0xc4, 0xcf, 0xf8, 0xdb, 0x64, 0x0e, 0x4a, 0x0f, - 0x9c, 0x27, 0xf8, 0xe0, 0x7b, 0x28, 0xb2, 0xd6, 0x70, 0xf5, 0xcb, 0x79, 0x62, 0xc7, 0xc7, 0x5e, - 0x56, 0x82, 0xf3, 0x34, 0xdf, 0x84, 0x37, 0x61, 0xf4, 0x9e, 0xdf, 0x72, 0xc5, 0xe6, 0x24, 0x4e, - 0xfd, 0xf7, 0x10, 0x62, 0x89, 0x12, 0xf3, 0x87, 0x06, 0x4c, 0xc6, 0x35, 0xc6, 0x2a, 0x7c, 0xe6, - 0x5d, 0x92, 0x7a, 0x1d, 0xbf, 0x34, 0xc4, 0xeb, 0xf8, 0x23, 0x9f, 0xae, 0x27, 0xcc, 0x7f, 0x8c, - 0x17, 0x0a, 0x6a, 0x2b, 0xd9, 0x4e, 0xa4, 0x54, 0xc4, 0x18, 0xb2, 0x22, 0xb9, 0xa7, 0x36, 0x24, - 0xf9, 0x81, 0x43, 0xf2, 0x2b, 0x39, 0x18, 0x8f, 0x2b, 0xfb, 0x9c, 0xc5, 0x6f, 0xc7, 0xed, 0x1a, - 0xca, 0x3b, 0xbb, 0xa1, 0xc8, 0x0a, 0xe1, 0x04, 0xfd, 0x65, 0x18, 0x15, 0x8b, 0xc9, 0x48, 0xdd, - 0xaa, 0xa5, 0x46, 0x37, 0x79, 0xeb, 0x14, 0x07, 0x34, 0xb4, 0x04, 0x1d, 0xba, 0xbf, 0x6f, 0xd3, - 0x1d, 0x71, 0xbf, 0xf4, 0xcc, 0xee, 0x51, 0xd9, 0xee, 0xef, 0x49, 0xc3, 0x86, 0xda, 0x9d, 0xfe, - 0x41, 0x01, 0x2a, 0x69, 0x92, 0x93, 0x23, 0xe4, 0xd7, 0x7b, 0x3b, 0xe2, 0x9d, 0x02, 0x8c, 0x90, - 0xef, 0xf6, 0x76, 0x2c, 0x06, 0x23, 0xaf, 0x41, 0x61, 0x3d, 0xf0, 0x1e, 0x61, 0xab, 0xc5, 0x33, - 0x0d, 0xdd, 0xc0, 0x7b, 0xa4, 0xfa, 0x81, 0xb2, 0x72, 0x34, 0x68, 0x57, 0x1a, 0xca, 0x33, 0xd3, - 0xdc, 0xa0, 0x6d, 0x85, 0xe9, 0xb4, 0x28, 0x12, 0x8d, 0x6d, 0x95, 0x75, 0xea, 0x04, 0x22, 0x9a, - 0x5b, 0x88, 0x33, 0xdc, 0x2a, 0x77, 0x10, 0xcc, 0x73, 0x9e, 0x5a, 0x2a, 0x12, 0x69, 0x01, 0x51, - 0x7e, 0xca, 0x05, 0x7c, 0xb2, 0x8d, 0x27, 0x9f, 0x17, 0x3a, 0xab, 0xb2, 0xb6, 0xd5, 0xd5, 0x9c, - 0xc1, 0xf7, 0x69, 0x9e, 0x11, 0xae, 0x43, 0x09, 0x8f, 0xbc, 0xf0, 0x20, 0xa3, 0x78, 0x22, 0x33, - 0xe9, 0x73, 0x0b, 0x78, 0x1b, 0x6f, 0xc7, 0xc7, 0x19, 0x09, 0x13, 0xf2, 0x3e, 0x8c, 0xab, 0x8e, - 0xa2, 0xdc, 0x9d, 0xf1, 0x32, 0x8f, 0x10, 0x1a, 0x90, 0x3e, 0x4c, 0x25, 0x30, 0xbf, 0xa0, 0xce, - 0x12, 0xb1, 0x69, 0x1f, 0x3b, 0x4b, 0xcc, 0xdf, 0x42, 0x35, 0xbe, 0xed, 0x47, 0x54, 0x68, 0x2f, - 0xcf, 0xac, 0x1c, 0x4b, 0x8e, 0x90, 0x47, 0x34, 0x8f, 0x10, 0xad, 0x75, 0xa7, 0x78, 0x60, 0xf9, - 0x0f, 0x0d, 0x38, 0x97, 0x49, 0x4b, 0x6e, 0x00, 0x24, 0x3a, 0xa2, 0xe8, 0x25, 0x9e, 0x4c, 0x36, - 0x86, 0x5a, 0x0a, 0x06, 0xf9, 0x7a, 0x5a, 0xbb, 0x3b, 0x79, 0x73, 0x92, 0x4f, 0x2e, 0x4c, 0xea, - 0xda, 0x5d, 0x86, 0x4e, 0x67, 0x7e, 0x2f, 0x0f, 0xd3, 0x7d, 0x4f, 0xf5, 0x9d, 0x70, 0x07, 0xbf, - 0x9f, 0x7a, 0x08, 0x8a, 0x5f, 0x77, 0x5c, 0x1f, 0xf4, 0x50, 0x60, 0xc6, 0xb3, 0x50, 0x78, 0x2c, - 0x26, 0xf2, 0x18, 0x9f, 0xf0, 0x3a, 0x54, 0x98, 0xfd, 0x84, 0xd8, 0xe7, 0x07, 0x7e, 0xed, 0x29, - 0x3c, 0x25, 0xf6, 0x63, 0xfc, 0xd2, 0xd2, 0x6f, 0xe5, 0x60, 0xa6, 0xaf, 0xcd, 0xcf, 0xec, 0xaa, - 0xfb, 0xb2, 0xb6, 0xbb, 0xbd, 0x34, 0x68, 0x4c, 0x87, 0xd2, 0x22, 0xfe, 0xbb, 0x01, 0x17, 0x06, - 0x50, 0x92, 0x83, 0xf4, 0x24, 0xe2, 0x5a, 0xc5, 0xcd, 0xe3, 0x3f, 0xf8, 0x54, 0xa6, 0xd2, 0x67, - 0x36, 0x13, 0x7e, 0x39, 0x07, 0xb0, 0x4d, 0x77, 0x9e, 0xed, 0xf4, 0x3f, 0xd9, 0x6f, 0xe1, 0xcb, - 0x66, 0x0d, 0x35, 0xee, 0x6b, 0x78, 0x90, 0x38, 0x7c, 0xee, 0x9f, 0xf8, 0x59, 0x89, 0x5c, 0xf6, - 0xb3, 0x12, 0xe6, 0x0e, 0x9c, 0xbd, 0x4b, 0xa3, 0x64, 0x27, 0x94, 0x36, 0xe4, 0xf1, 0x6c, 0xdf, - 0x80, 0x92, 0xc0, 0xd7, 0x53, 0x84, 0x4b, 0x87, 0x32, 0xcf, 0xb5, 0x12, 0x04, 0x93, 0xc2, 0x85, - 0x45, 0xda, 0xa2, 0x11, 0xfd, 0x6c, 0x3f, 0xd3, 0x00, 0xc2, 0x9b, 0xc2, 0x5f, 0x1b, 0x18, 0xea, - 0x0b, 0x27, 0xf6, 0xcf, 0x16, 0x9c, 0x8b, 0xeb, 0xfe, 0x34, 0xf9, 0xce, 0x31, 0x5d, 0x42, 0xc4, - 0xda, 0x25, 0x1c, 0x8f, 0x39, 0x44, 0x7c, 0x02, 0x97, 0x24, 0xc1, 0xb6, 0x17, 0xdf, 0xc4, 0x0c, - 0x45, 0x4b, 0xde, 0x85, 0x71, 0x85, 0x46, 0x04, 0xee, 0xe2, 0x6d, 0xe7, 0x63, 0x2f, 0xda, 0xb3, - 0x43, 0x0e, 0x57, 0x6f, 0x3b, 0x15, 0x74, 0xf3, 0x6b, 0xf0, 0x42, 0xec, 0xb7, 0x92, 0xf1, 0xe9, - 0x14, 0x73, 0xe3, 0x74, 0xcc, 0x57, 0x93, 0x66, 0x2d, 0x77, 0x62, 0xff, 0x71, 0xc9, 0x9b, 0xa8, - 0xcd, 0x12, 0x8d, 0xb9, 0xac, 0xa4, 0x45, 0x13, 0x7b, 0x51, 0x02, 0x30, 0xdf, 0x51, 0x2a, 0x9b, - 0xc1, 0x50, 0x23, 0x36, 0xd2, 0xc4, 0xbf, 0x9c, 0x83, 0xa9, 0xb5, 0xe5, 0xc5, 0x85, 0xf8, 0x18, - 0xf9, 0x39, 0xcb, 0x4d, 0xa4, 0xb5, 0x6d, 0xb0, 0xbc, 0x31, 0x37, 0x61, 0x26, 0xd5, 0x0d, 0xf8, - 0x98, 0xca, 0xfb, 0xdc, 0xbf, 0x24, 0x06, 0xcb, 0x9d, 0xe5, 0x7c, 0x16, 0xfb, 0xad, 0x5b, 0x56, - 0x0a, 0xdb, 0xfc, 0xde, 0x68, 0x8a, 0xaf, 0x10, 0x61, 0x6f, 0x40, 0x69, 0x39, 0x0c, 0x7b, 0x34, - 0xd8, 0xb4, 0x56, 0x54, 0x1d, 0xd1, 0x43, 0xa0, 0xdd, 0x0b, 0x5a, 0x56, 0x82, 0x40, 0xae, 0x41, - 0x51, 0xc4, 0x77, 0x49, 0x99, 0x80, 0xd7, 0xe5, 0x71, 0x78, 0x98, 0x15, 0x17, 0x93, 0xb7, 0xa0, - 0xcc, 0xff, 0xe7, 0xb3, 0x4d, 0x74, 0x38, 0x9e, 0x55, 0x09, 0x74, 0x3e, 0x3b, 0x2d, 0x0d, 0x8d, - 0x59, 0x66, 0xf2, 0xb5, 0x46, 0x56, 0xa3, 0x42, 0x62, 0x99, 0xc9, 0x87, 0x1d, 0xb1, 0x4e, 0x2a, - 0x12, 0xb9, 0x0e, 0xf9, 0xda, 0x82, 0xa5, 0x66, 0x45, 0x76, 0x9a, 0x01, 0xcf, 0x2a, 0xae, 0x3d, - 0x88, 0x54, 0x5b, 0xb0, 0xc8, 0x3c, 0x14, 0xf1, 0xc1, 0x0b, 0x97, 0x06, 0xc2, 0x67, 0x14, 0x67, - 0x4d, 0x57, 0xc0, 0xd4, 0x9b, 0x47, 0x89, 0x47, 0xe6, 0x60, 0x6c, 0xd1, 0x0b, 0xbb, 0x2d, 0xe7, - 0x40, 0x24, 0x25, 0xc1, 0xcb, 0x10, 0x97, 0x83, 0xd4, 0x79, 0x26, 0xb0, 0xc8, 0x35, 0x18, 0x69, - 0x34, 0xfd, 0x2e, 0xb3, 0xb6, 0x62, 0xd7, 0x96, 0x90, 0x01, 0xb4, 0xcc, 0x06, 0x0c, 0x80, 0x21, - 0xc7, 0x3c, 0x72, 0xaa, 0xa4, 0x84, 0x1c, 0xa7, 0x23, 0xa6, 0x04, 0x4e, 0xbf, 0xff, 0x1f, 0x3c, - 0x4d, 0xff, 0xbf, 0x1d, 0xb8, 0x70, 0x17, 0x55, 0xfd, 0x06, 0x0d, 0x30, 0x0f, 0x24, 0x7f, 0x3c, - 0x67, 0xd3, 0x5a, 0x16, 0xd1, 0x62, 0x57, 0x8f, 0x0e, 0xab, 0xaf, 0x70, 0x6b, 0xc0, 0x0e, 0x39, - 0x8e, 0x7c, 0x77, 0x27, 0xf5, 0x62, 0xc0, 0x20, 0x46, 0xe4, 0x2b, 0x70, 0x36, 0xab, 0x48, 0xc4, - 0x8d, 0xa1, 0x57, 0x78, 0xf6, 0x07, 0x54, 0xb7, 0xec, 0x2c, 0x0e, 0x64, 0x05, 0x2a, 0x1c, 0x5e, - 0x73, 0xdb, 0x5e, 0x67, 0xa9, 0xed, 0x78, 0x2d, 0x8c, 0x22, 0x13, 0xa1, 0x80, 0x82, 0xab, 0xc3, - 0x0a, 0x6d, 0xca, 0x4a, 0x35, 0xef, 0xa4, 0x14, 0x25, 0x8a, 0xa3, 0x46, 0xed, 0xc1, 0x4a, 0xb2, - 0xa6, 0x9e, 0xaf, 0x7b, 0x23, 0xad, 0x6d, 0xc7, 0xdc, 0x1b, 0x6d, 0xc2, 0x4c, 0xaa, 0x1b, 0xa4, - 0x38, 0xd2, 0xc0, 0x69, 0x71, 0x94, 0xa2, 0xb1, 0x52, 0xd8, 0xe6, 0x7f, 0x1c, 0x4d, 0xf1, 0x15, - 0x67, 0x45, 0x26, 0x8c, 0x72, 0x69, 0xa3, 0x66, 0x2d, 0xe3, 0xb2, 0xc8, 0x12, 0x25, 0xe4, 0x22, - 0xe4, 0x1b, 0x8d, 0x35, 0x35, 0xa7, 0x62, 0x18, 0xfa, 0x16, 0x83, 0xb1, 0x11, 0xc2, 0x63, 0x20, - 0x25, 0x40, 0xab, 0x49, 0x83, 0x48, 0x3c, 0xe7, 0xf9, 0x6a, 0xb2, 0x8e, 0x0b, 0x49, 0x7f, 0x8b, - 0x75, 0x9c, 0xac, 0xde, 0x05, 0x98, 0xad, 0x85, 0x21, 0x0d, 0x22, 0x9e, 0x94, 0x3d, 0xec, 0xb5, - 0x69, 0x20, 0xe6, 0x9a, 0x90, 0x31, 0xfc, 0x31, 0xf0, 0x66, 0x68, 0x0d, 0x44, 0x24, 0x57, 0xa1, - 0x58, 0xeb, 0xb9, 0x1e, 0xed, 0x34, 0x35, 0xdf, 0x74, 0x47, 0xc0, 0xac, 0xb8, 0x94, 0x7c, 0x04, - 0xe7, 0x04, 0x91, 0x14, 0x38, 0xa2, 0x07, 0xb8, 0xac, 0xe1, 0x16, 0xac, 0x58, 0x0b, 0x52, 0x4c, - 0xd9, 0xa2, 0x4b, 0xb2, 0x29, 0x49, 0x0d, 0x2a, 0x4b, 0x78, 0x4f, 0x2a, 0x1f, 0xf5, 0xf5, 0x03, - 0x91, 0x7c, 0x17, 0x25, 0x17, 0xbf, 0x43, 0xb5, 0xdd, 0xb8, 0xd0, 0xea, 0x43, 0x27, 0xf7, 0x61, - 0x26, 0x0d, 0x63, 0xf2, 0xb8, 0x94, 0x3c, 0xba, 0xd5, 0xc7, 0x05, 0x05, 0x73, 0x16, 0x15, 0xd9, - 0x81, 0xe9, 0x5a, 0x14, 0x05, 0xde, 0x4e, 0x2f, 0xa2, 0x29, 0xd1, 0x25, 0x0f, 0x1a, 0xe3, 0x72, - 0x29, 0xbe, 0x5e, 0x10, 0x93, 0x71, 0xc6, 0x89, 0x29, 0x63, 0x11, 0x66, 0xf5, 0xb3, 0x23, 0x6e, - 0xfc, 0x6e, 0x9f, 0x78, 0xdb, 0x4e, 0x04, 0x14, 0xc9, 0x03, 0xdd, 0x5a, 0x78, 0xd0, 0x6e, 0xd3, - 0x28, 0xc0, 0x9b, 0x7b, 0x7c, 0xfb, 0xce, 0x14, 0x3e, 0x40, 0x97, 0x94, 0xe7, 0x2a, 0xf1, 0x7d, - 0x43, 0xcd, 0x3d, 0x52, 0xe3, 0xa9, 0x6d, 0x1f, 0xe5, 0x21, 0xb7, 0x8f, 0x16, 0x4c, 0x2f, 0x75, - 0x9a, 0xc1, 0x01, 0x46, 0x36, 0xca, 0xca, 0x4d, 0x9c, 0x50, 0x39, 0xf9, 0xb0, 0xc5, 0x65, 0x47, - 0xce, 0xb0, 0xac, 0xea, 0xf5, 0x33, 0x36, 0xff, 0x3f, 0xa8, 0xa4, 0xfb, 0xf2, 0x53, 0x3e, 0x56, - 0x7c, 0x1a, 0xd7, 0x6c, 0x36, 0xd2, 0xe9, 0xb6, 0x90, 0x39, 0xed, 0x45, 0x5a, 0x23, 0x89, 0x4a, - 0x57, 0xde, 0x8e, 0xd5, 0xde, 0xa1, 0x95, 0xcb, 0x38, 0x97, 0xb5, 0x8c, 0xcd, 0x5f, 0xcd, 0xc1, - 0x34, 0xf7, 0x26, 0x7d, 0xf6, 0x75, 0xc5, 0xf7, 0x35, 0xe1, 0x2c, 0xcf, 0x02, 0x53, 0xad, 0x3b, - 0x46, 0x5b, 0xfc, 0x06, 0x9c, 0xeb, 0xeb, 0x0a, 0x14, 0xd0, 0x8b, 0xd2, 0x8f, 0xb7, 0x4f, 0x44, - 0xcf, 0x66, 0x7f, 0x64, 0xeb, 0x96, 0xd5, 0x47, 0x61, 0xfe, 0xc3, 0x5c, 0x1f, 0x7f, 0xa1, 0x37, - 0xaa, 0x9a, 0xa0, 0x71, 0x3a, 0x4d, 0x30, 0xf7, 0x89, 0x34, 0xc1, 0xfc, 0x30, 0x9a, 0xe0, 0x47, - 0x30, 0xb1, 0x41, 0x1d, 0xa6, 0xd1, 0x88, 0x60, 0xb3, 0x82, 0xf6, 0x5a, 0x2c, 0x2b, 0x93, 0xf2, - 0x25, 0x0e, 0x54, 0x8d, 0x18, 0x01, 0x13, 0x2d, 0x3c, 0xfa, 0xcc, 0xd2, 0x39, 0xa8, 0x9b, 0xc6, - 0xc8, 0xe0, 0x4d, 0xc3, 0xfc, 0x66, 0x0e, 0xc6, 0x15, 0xf6, 0xe4, 0x8b, 0x50, 0x5e, 0x0b, 0x76, - 0x9d, 0x8e, 0xf7, 0x73, 0x8e, 0x72, 0xfc, 0x8a, 0xd5, 0xf7, 0x15, 0xb8, 0xa5, 0x61, 0xa1, 0xdb, - 0x0c, 0x75, 0xda, 0xea, 0xc4, 0x67, 0xd5, 0xb3, 0x10, 0xaa, 0x04, 0x0b, 0xe7, 0x87, 0x08, 0x16, - 0xd6, 0x23, 0x6d, 0x0b, 0xa7, 0x8f, 0xb4, 0xd5, 0x02, 0x63, 0x47, 0x4e, 0x19, 0x18, 0x6b, 0xfe, - 0x7a, 0x0e, 0x2a, 0xe2, 0x5d, 0x55, 0x79, 0x78, 0xf8, 0x7c, 0xbd, 0xc3, 0xa0, 0x37, 0xee, 0x98, - 0xeb, 0xb1, 0xc2, 0x6f, 0xff, 0x7e, 0x15, 0x5f, 0xc9, 0x4c, 0x77, 0x87, 0x7c, 0x25, 0x53, 0x87, - 0xa7, 0x23, 0x07, 0xd2, 0x54, 0x56, 0x1a, 0xdf, 0xfc, 0xb3, 0x5c, 0x9a, 0xb7, 0xd0, 0xa6, 0x5e, - 0x85, 0x31, 0xfe, 0x2c, 0x96, 0x74, 0x6e, 0x16, 0xb9, 0x9b, 0x10, 0x64, 0xc9, 0xb2, 0xd3, 0xc4, - 0x90, 0x9c, 0xf4, 0x54, 0x2a, 0xb9, 0x0d, 0x65, 0xf4, 0x17, 0xa9, 0xb9, 0x6e, 0x40, 0xc3, 0x50, - 0x28, 0x5a, 0x78, 0x77, 0xf7, 0x98, 0xee, 0xd8, 0xdc, 0xaf, 0xc4, 0x71, 0xdd, 0xc0, 0xd2, 0xf0, - 0xc8, 0x02, 0x9c, 0xd5, 0xdc, 0x93, 0x24, 0xfd, 0x48, 0xb2, 0x5b, 0x44, 0x58, 0xc0, 0x89, 0x33, - 0x91, 0x9f, 0xde, 0x33, 0xd1, 0xe6, 0xff, 0x32, 0xd8, 0x5a, 0x6b, 0xee, 0x3f, 0x67, 0xd1, 0x2d, - 0xac, 0x49, 0xc7, 0x28, 0xfb, 0xff, 0xde, 0xe0, 0xfe, 0xe9, 0x62, 0xfa, 0xbc, 0x0d, 0xa3, 0xfc, - 0x11, 0x2e, 0xe1, 0x49, 0xad, 0x72, 0xe1, 0x05, 0xc9, 0xfd, 0x14, 0x7f, 0xca, 0xcb, 0x12, 0x04, - 0xcc, 0x64, 0xd6, 0xdd, 0xe4, 0x51, 0xf1, 0xec, 0xf7, 0x8f, 0x97, 0x58, 0x6a, 0x5e, 0xd2, 0xe1, - 0xf2, 0x5d, 0x1b, 0x27, 0xe7, 0x25, 0x35, 0xff, 0x4f, 0x8e, 0xb7, 0x47, 0x54, 0x6a, 0xd8, 0x84, - 0x7b, 0xaf, 0x41, 0x01, 0x9f, 0x7b, 0x55, 0xb2, 0x1a, 0xa6, 0x9e, 0x7a, 0xc5, 0x72, 0xb6, 0x6e, - 0x50, 0xd6, 0xaa, 0x01, 0x55, 0x28, 0x8e, 0xd5, 0x75, 0x83, 0x18, 0x98, 0x4d, 0xda, 0x77, 0xa9, - 0xba, 0x1c, 0x3a, 0x7a, 0xe2, 0x6f, 0x2c, 0x27, 0xb7, 0x15, 0xbf, 0x66, 0xf5, 0x40, 0xa3, 0xfd, - 0xd0, 0xb1, 0xb9, 0x3f, 0xad, 0x2a, 0x6d, 0x13, 0x17, 0xe8, 0x25, 0x98, 0xd4, 0x63, 0x95, 0x85, - 0xd1, 0x81, 0x21, 0xdf, 0xa9, 0x38, 0x67, 0x55, 0xbd, 0xd5, 0x89, 0x48, 0x1d, 0x26, 0xb4, 0x80, - 0x54, 0x35, 0x09, 0x2b, 0xcf, 0x0e, 0x63, 0xf7, 0x67, 0x52, 0xd0, 0x49, 0x94, 0x03, 0xf3, 0x2f, - 0x40, 0x45, 0xac, 0xcc, 0x38, 0xb6, 0x0d, 0x55, 0xbb, 0xe5, 0x45, 0x4b, 0x5d, 0x4d, 0x4d, 0xcf, - 0x0d, 0x2c, 0x84, 0x9a, 0xdf, 0x35, 0xe0, 0xa2, 0x78, 0x5c, 0xcc, 0xa2, 0x21, 0xd3, 0x21, 0x31, - 0x20, 0x0e, 0xe7, 0xe3, 0x17, 0xc9, 0xbb, 0x32, 0xf1, 0x94, 0x2e, 0x20, 0xd3, 0xdf, 0xa8, 0x4f, - 0x88, 0x49, 0x39, 0x82, 0xa9, 0xa7, 0x64, 0xc2, 0xa9, 0xb7, 0x45, 0xc2, 0xa9, 0xdc, 0xf1, 0xc4, - 0xf1, 0xba, 0x70, 0x69, 0x47, 0x26, 0x9a, 0xfa, 0x4e, 0x0e, 0xce, 0x65, 0x54, 0x6b, 0xeb, 0x8b, - 0xcf, 0xa8, 0x70, 0xa8, 0x6b, 0xc2, 0x41, 0x66, 0x24, 0x1c, 0xd8, 0xf1, 0x99, 0xb2, 0xe2, 0x77, - 0x0d, 0xb8, 0xa0, 0xcf, 0x1e, 0x61, 0x8b, 0x6e, 0xdd, 0x22, 0xef, 0xc0, 0xe8, 0x3d, 0xea, 0xb8, - 0x54, 0x86, 0x60, 0xc4, 0xd9, 0xbd, 0xc4, 0xe9, 0x30, 0x2f, 0xe4, 0x6c, 0xff, 0x8c, 0x2f, 0xe5, - 0x33, 0x96, 0x20, 0x21, 0x8b, 0xa2, 0x72, 0xfc, 0x7a, 0xca, 0x94, 0x37, 0x35, 0x59, 0x9f, 0x3a, - 0x46, 0x31, 0xfe, 0x43, 0x03, 0x5e, 0x38, 0x86, 0x86, 0x0d, 0x1c, 0x1b, 0x7a, 0x75, 0xe0, 0x70, - 0x63, 0x41, 0x28, 0x79, 0x1f, 0xa6, 0x36, 0x44, 0xa4, 0x98, 0x1c, 0x0e, 0x25, 0xbb, 0xbb, 0x0c, - 0x22, 0xb3, 0xe5, 0xb8, 0xa4, 0x91, 0x99, 0xf5, 0x7f, 0xcf, 0x0f, 0xa3, 0x4e, 0x92, 0x2c, 0x06, - 0xad, 0xff, 0x3d, 0x01, 0xb3, 0xe2, 0x52, 0xa6, 0x16, 0xe8, 0xd5, 0x14, 0xee, 0x10, 0x2f, 0xc3, - 0x28, 0xc3, 0x89, 0xb5, 0x6b, 0x9c, 0x07, 0xf8, 0xf4, 0x95, 0xe7, 0x5a, 0xa2, 0x28, 0xb6, 0xeb, - 0x72, 0x99, 0xb7, 0x16, 0xdf, 0x34, 0xa0, 0xa2, 0xf3, 0xfe, 0xb4, 0x43, 0xf3, 0x9e, 0x36, 0x34, - 0x2f, 0x64, 0x0f, 0xcd, 0xe0, 0x31, 0xe9, 0xcb, 0xdb, 0x30, 0xd4, 0x58, 0x98, 0x30, 0xba, 0xe8, - 0xb7, 0x1d, 0xaf, 0xa3, 0xe6, 0x1a, 0x70, 0x11, 0x62, 0x89, 0x12, 0xa5, 0xb7, 0xf2, 0x03, 0x7b, - 0xcb, 0xfc, 0x76, 0x01, 0x2e, 0x5a, 0x74, 0xd7, 0x63, 0x0a, 0xd2, 0x66, 0xe8, 0x75, 0x76, 0xb5, - 0x3b, 0x25, 0x33, 0xd5, 0xe1, 0xc2, 0x93, 0x8e, 0x41, 0xe2, 0xfe, 0xbe, 0x06, 0x45, 0x26, 0xa5, - 0x95, 0x3e, 0x47, 0xa3, 0x07, 0x33, 0xe6, 0xf0, 0x71, 0x95, 0xc5, 0xe4, 0xba, 0xd8, 0x43, 0x14, - 0x5f, 0x67, 0xb6, 0x87, 0xa4, 0x5e, 0xee, 0xe6, 0xfb, 0x48, 0xac, 0x54, 0x15, 0x06, 0x28, 0x55, - 0x0f, 0xe0, 0x6c, 0xcd, 0xe5, 0xf2, 0xc9, 0x69, 0xad, 0x07, 0x5e, 0xa7, 0xe9, 0x75, 0x9d, 0x96, - 0x54, 0xca, 0xf9, 0x3b, 0xe6, 0x71, 0xb9, 0xdd, 0x8d, 0x11, 0xac, 0x4c, 0x32, 0xd6, 0x8c, 0xc5, - 0xd5, 0x06, 0x4f, 0x88, 0x32, 0x8a, 0x2c, 0xb0, 0x19, 0x6e, 0x27, 0xe4, 0x19, 0x51, 0xac, 0xb8, - 0x18, 0xd5, 0x39, 0x0c, 0x77, 0xd8, 0x58, 0x69, 0xdc, 0x17, 0xe1, 0x03, 0xd2, 0x15, 0x8b, 0x47, - 0x47, 0x44, 0xad, 0x10, 0xed, 0x77, 0x0d, 0x2f, 0xa1, 0x6b, 0x34, 0xee, 0x31, 0xba, 0x62, 0x1f, - 0x5d, 0x18, 0xee, 0xa9, 0x74, 0x1c, 0x8f, 0xcc, 0x01, 0x70, 0x67, 0x16, 0x9c, 0x10, 0xa5, 0x44, - 0xf9, 0x0b, 0x10, 0xca, 0x95, 0x3f, 0x05, 0x85, 0xbc, 0x0b, 0x33, 0x4b, 0x0b, 0xf3, 0x32, 0x2e, - 0x60, 0xd1, 0x6f, 0xf6, 0xda, 0xb4, 0x13, 0x61, 0x9c, 0x4a, 0x99, 0x8f, 0x21, 0x6d, 0xce, 0xb3, - 0x59, 0x90, 0x85, 0x26, 0xa2, 0x03, 0x78, 0x6c, 0xd9, 0x82, 0xef, 0xd2, 0x70, 0xeb, 0xe6, 0x73, - 0x16, 0x1d, 0xa0, 0xb4, 0x0d, 0x57, 0xdb, 0xcd, 0xcc, 0x95, 0xf9, 0x77, 0x31, 0x3a, 0xa0, 0x0f, - 0x97, 0xfc, 0x24, 0x8c, 0xe0, 0x4f, 0xb1, 0xe3, 0xce, 0x64, 0xb0, 0x4d, 0x76, 0xdb, 0x26, 0xcf, - 0x6d, 0x81, 0x04, 0x64, 0x39, 0x49, 0x5e, 0x7f, 0x0a, 0x1f, 0x57, 0x11, 0xa0, 0xaa, 0xbf, 0x5a, - 0xe2, 0x42, 0x59, 0xfd, 0x20, 0x9b, 0x23, 0xf7, 0x9c, 0x70, 0x8f, 0xba, 0x0b, 0xf2, 0xdd, 0xc1, - 0x32, 0x9f, 0x23, 0x7b, 0x08, 0xc5, 0x17, 0x55, 0x2c, 0x05, 0x85, 0x49, 0x87, 0xe5, 0x70, 0x33, - 0x14, 0x55, 0x11, 0x56, 0x90, 0x87, 0xd6, 0xab, 0x6b, 0x89, 0x22, 0x94, 0x96, 0x32, 0x77, 0x63, - 0xe0, 0x34, 0xf7, 0x69, 0xb0, 0x75, 0xf3, 0xb3, 0x90, 0x96, 0xfa, 0x37, 0x8e, 0x19, 0x93, 0xdf, - 0x28, 0xc6, 0xa9, 0x59, 0x34, 0x64, 0xa6, 0x23, 0x26, 0x37, 0xf3, 0x46, 0xa2, 0x23, 0x26, 0x37, - 0xf3, 0xaa, 0x8e, 0x18, 0xa3, 0xc6, 0x99, 0x73, 0x73, 0x27, 0x64, 0xce, 0x1d, 0x90, 0xac, 0x5b, - 0x3a, 0x75, 0x3e, 0x47, 0xef, 0x16, 0x7c, 0x09, 0xca, 0xb5, 0x28, 0x72, 0x9a, 0x7b, 0xd4, 0xc5, - 0x0c, 0xcd, 0xca, 0x85, 0xa0, 0x23, 0xe0, 0xaa, 0xbb, 0x98, 0x8a, 0xab, 0xbc, 0x5b, 0x32, 0x36, - 0xc4, 0xbb, 0x25, 0x73, 0x30, 0xb6, 0xdc, 0x79, 0xe4, 0xb1, 0x3e, 0x29, 0x26, 0xc9, 0x21, 0x3c, - 0x0e, 0xd2, 0x1f, 0xbb, 0x40, 0x10, 0x79, 0x5b, 0xd1, 0x20, 0x4a, 0x89, 0x2a, 0x2f, 0x5e, 0x4c, - 0x96, 0x8a, 0x84, 0x7a, 0xde, 0x2c, 0xd1, 0xc9, 0x6d, 0x18, 0x93, 0xd6, 0x33, 0x24, 0xea, 0xbb, - 0xa0, 0x74, 0x78, 0x89, 0x96, 0x8f, 0x42, 0x58, 0xcf, 0xef, 0xea, 0xf1, 0x23, 0xe3, 0x4a, 0x5c, - 0xb6, 0x12, 0x3f, 0xa2, 0xc5, 0x65, 0x2b, 0x91, 0x24, 0xb1, 0x31, 0x54, 0x3e, 0xd1, 0x18, 0xda, - 0x82, 0xf2, 0xba, 0x13, 0x44, 0x1e, 0xdb, 0x8e, 0x3a, 0x11, 0x4f, 0xab, 0x95, 0xd8, 0xea, 0x4a, - 0x51, 0xfd, 0x25, 0x19, 0x9f, 0xdc, 0x55, 0xf0, 0xf5, 0xc0, 0xd6, 0x04, 0x4e, 0x56, 0x33, 0x3c, - 0x0c, 0x45, 0x12, 0x48, 0xbc, 0x02, 0x54, 0x0e, 0xae, 0x44, 0x8b, 0xd4, 0xa3, 0xf4, 0x7e, 0xe7, - 0xc4, 0x5b, 0x7c, 0x0c, 0xd0, 0x66, 0x9c, 0x42, 0x36, 0x98, 0x5d, 0x04, 0xf5, 0x8a, 0x94, 0xe1, - 0x18, 0x23, 0x92, 0xaf, 0x43, 0x99, 0xfd, 0x8f, 0x39, 0x86, 0x3c, 0xca, 0xd3, 0x66, 0x25, 0x1e, - 0x67, 0xfa, 0x82, 0xe6, 0x89, 0x88, 0x1a, 0x34, 0xe2, 0x0b, 0x18, 0x19, 0xa7, 0x0f, 0x5e, 0x34, - 0x6e, 0xe6, 0x0f, 0x0d, 0xb8, 0x30, 0x80, 0xc7, 0xd0, 0x2f, 0x16, 0xcd, 0x25, 0x1b, 0x92, 0x62, - 0x9b, 0x8b, 0x0d, 0x49, 0x9d, 0x18, 0x72, 0x6b, 0xca, 0x4e, 0x78, 0x95, 0xff, 0xcc, 0x12, 0x5e, - 0x99, 0x87, 0x06, 0x8c, 0x2b, 0x23, 0xfb, 0x14, 0x1f, 0x22, 0x78, 0x4d, 0x64, 0x7e, 0xcc, 0x27, - 0x78, 0xa9, 0xf7, 0x87, 0x79, 0xa6, 0xc7, 0x6f, 0x00, 0xac, 0x38, 0x61, 0x54, 0x6b, 0x46, 0xde, - 0x23, 0x3a, 0x84, 0x18, 0x4b, 0x02, 0xf5, 0x1d, 0x4c, 0xa4, 0xca, 0xc8, 0xfa, 0x02, 0xf5, 0x63, - 0x86, 0xe6, 0x2a, 0x8c, 0x36, 0xfc, 0x20, 0xaa, 0x1f, 0xf0, 0xbd, 0x69, 0x91, 0x86, 0x4d, 0xf5, - 0x84, 0xce, 0x43, 0x5b, 0xbd, 0x69, 0x89, 0x22, 0xa6, 0x20, 0xde, 0xf1, 0x68, 0xcb, 0x55, 0x6f, - 0x68, 0x1e, 0x32, 0x80, 0xc5, 0xe1, 0xd7, 0x3f, 0x80, 0x29, 0x99, 0x7c, 0x6e, 0x63, 0xa5, 0x81, - 0x2d, 0x98, 0x82, 0xf1, 0xad, 0x25, 0x6b, 0xf9, 0xce, 0x57, 0xed, 0x3b, 0x9b, 0x2b, 0x2b, 0x95, - 0x33, 0x64, 0x02, 0x4a, 0x02, 0xb0, 0x50, 0xab, 0x18, 0xa4, 0x0c, 0xc5, 0xe5, 0xd5, 0xc6, 0xd2, - 0xc2, 0xa6, 0xb5, 0x54, 0xc9, 0x5d, 0x7f, 0x15, 0x26, 0x93, 0xfb, 0x17, 0x8c, 0xd1, 0x1c, 0x83, - 0xbc, 0x55, 0xdb, 0xae, 0x9c, 0x21, 0x00, 0xa3, 0xeb, 0xf7, 0x17, 0x1a, 0x37, 0x6f, 0x56, 0x8c, - 0xeb, 0x5f, 0xc8, 0x78, 0xf4, 0x99, 0x71, 0x6a, 0xd0, 0xae, 0x13, 0x38, 0x11, 0xe5, 0x9f, 0x79, - 0xd0, 0x6b, 0x45, 0x5e, 0xb7, 0x45, 0x9f, 0x54, 0x8c, 0xeb, 0x6f, 0xf7, 0xbd, 0xdd, 0x4c, 0xce, - 0xc1, 0xf4, 0xe6, 0x6a, 0xed, 0x41, 0x7d, 0xf9, 0xee, 0xe6, 0xda, 0x66, 0xc3, 0x7e, 0x50, 0xdb, - 0x58, 0xb8, 0x57, 0x39, 0xc3, 0x2a, 0xfc, 0x60, 0xad, 0xb1, 0x61, 0x5b, 0x4b, 0x0b, 0x4b, 0xab, - 0x1b, 0x15, 0xe3, 0xfa, 0xaf, 0x19, 0x30, 0xa9, 0xbf, 0x86, 0x47, 0xae, 0xc0, 0xe5, 0xcd, 0xc6, - 0x92, 0x65, 0x6f, 0xac, 0xdd, 0x5f, 0x5a, 0xb5, 0x37, 0x1b, 0xb5, 0xbb, 0x4b, 0xf6, 0xe6, 0x6a, - 0x63, 0x7d, 0x69, 0x61, 0xf9, 0xce, 0xf2, 0xd2, 0x62, 0xe5, 0x0c, 0xa9, 0xc2, 0x0b, 0x0a, 0x86, - 0xb5, 0xb4, 0xb0, 0xb6, 0xb5, 0x64, 0xd9, 0xeb, 0xb5, 0x46, 0x63, 0x7b, 0xcd, 0x5a, 0xac, 0x18, - 0xe4, 0x12, 0x9c, 0xcf, 0x40, 0x78, 0x70, 0xa7, 0x56, 0xc9, 0xf5, 0x95, 0xad, 0x2e, 0x6d, 0xd7, - 0x56, 0xec, 0xfa, 0xda, 0x46, 0x25, 0x7f, 0xfd, 0x03, 0xa6, 0x85, 0x24, 0xcf, 0x55, 0x90, 0x22, - 0x14, 0x56, 0xd7, 0x56, 0x97, 0x2a, 0x67, 0xc8, 0x38, 0x8c, 0xad, 0x2f, 0xad, 0x2e, 0x2e, 0xaf, - 0xde, 0xe5, 0xdd, 0x5a, 0x5b, 0x5f, 0xb7, 0xd6, 0xb6, 0x96, 0x16, 0x2b, 0x39, 0xd6, 0x77, 0x8b, - 0x4b, 0xab, 0xac, 0x66, 0xf9, 0xeb, 0x26, 0x4f, 0xd1, 0xac, 0x65, 0x18, 0x65, 0xbd, 0xb5, 0xf4, - 0x95, 0x8d, 0xa5, 0xd5, 0xc6, 0xf2, 0xda, 0x6a, 0xe5, 0xcc, 0xf5, 0xcb, 0x29, 0x1c, 0x39, 0x12, - 0x8d, 0xc6, 0xbd, 0xca, 0x99, 0xeb, 0x5f, 0x87, 0xb2, 0xba, 0x09, 0x93, 0x0b, 0x30, 0xa3, 0xfe, - 0x5e, 0xa7, 0x1d, 0xd7, 0xeb, 0xec, 0x56, 0xce, 0xa4, 0x0b, 0xac, 0x5e, 0xa7, 0xc3, 0x0a, 0xb0, - 0xf1, 0x6a, 0xc1, 0x06, 0x0d, 0xda, 0x5e, 0x87, 0xed, 0xaf, 0x95, 0x5c, 0xbd, 0xf2, 0xfd, 0xbf, - 0x7c, 0xe9, 0xcc, 0xf7, 0x7f, 0xf0, 0x92, 0xf1, 0x67, 0x3f, 0x78, 0xc9, 0xf8, 0xaf, 0x3f, 0x78, - 0xc9, 0xd8, 0x19, 0xc5, 0x89, 0x7e, 0xeb, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x67, 0x6d, 0x41, - 0x27, 0x6f, 0xb5, 0x00, 0x00, + // 11557 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x7d, 0x6d, 0x6c, 0x1c, 0x49, + 0x76, 0x98, 0x7a, 0x66, 0x48, 0xce, 0x3c, 0x0e, 0xc9, 0x61, 0x51, 0x1f, 0x94, 0x56, 0xbb, 0xa3, + 0xeb, 0xfd, 0x92, 0x74, 0xbb, 0xe2, 0x89, 0xba, 0x95, 0xbd, 0xb7, 0x5f, 0x37, 0x43, 0x52, 0x12, + 0x57, 0x14, 0xc9, 0xed, 0xe1, 0xc7, 0x9d, 0xef, 0xce, 0xed, 0xe6, 0x74, 0x89, 0xec, 0xe5, 0xcc, + 0xf4, 0xb8, 0xbb, 0x47, 0x12, 0xed, 0x18, 0x76, 0x10, 0x38, 0x86, 0x61, 0xf8, 0x3e, 0x8c, 0x73, + 0x6c, 0x07, 0x0e, 0xec, 0x18, 0x09, 0x12, 0x27, 0xb8, 0xfc, 0x70, 0x02, 0x24, 0x01, 0x82, 0x00, + 0x06, 0x02, 0xe3, 0x7e, 0x24, 0x88, 0xff, 0x05, 0x76, 0x02, 0x26, 0xbe, 0xcb, 0x1f, 0x13, 0x48, + 0x10, 0x20, 0xbf, 0x7c, 0x89, 0x91, 0xa0, 0x5e, 0x55, 0x75, 0x57, 0xf5, 0xf4, 0x90, 0xc3, 0x5d, + 0x2d, 0x70, 0xd2, 0x2f, 0x72, 0x5e, 0xbd, 0xf7, 0xba, 0x3e, 0x5f, 0xbd, 0x57, 0xf5, 0xde, 0x2b, + 0x18, 0x8f, 0x0e, 0xba, 0x34, 0xbc, 0xd1, 0x0d, 0xfc, 0xc8, 0x27, 0x23, 0xf8, 0xe3, 0xd2, 0xd9, + 0x5d, 0x7f, 0xd7, 0x47, 0xc8, 0x1c, 0xfb, 0x8f, 0x17, 0x5e, 0xaa, 0xee, 0xfa, 0xfe, 0x6e, 0x8b, + 0xce, 0xe1, 0xaf, 0x9d, 0xde, 0xc3, 0xb9, 0xc8, 0x6b, 0xd3, 0x30, 0x72, 0xda, 0x5d, 0x81, 0xb0, + 0xb0, 0xeb, 0x45, 0x7b, 0xbd, 0x9d, 0x1b, 0x4d, 0xbf, 0x3d, 0xb7, 0x1b, 0x38, 0x8f, 0xbc, 0xc8, + 0x89, 0x3c, 0xbf, 0xe3, 0xb4, 0xe6, 0x22, 0xda, 0xa2, 0x5d, 0x3f, 0x88, 0xe6, 0x9c, 0xae, 0x37, + 0x87, 0xdf, 0x98, 0x7b, 0x1c, 0x38, 0xdd, 0x2e, 0x0d, 0x92, 0x7f, 0x38, 0x13, 0xf3, 0xef, 0xe7, + 0xa1, 0x74, 0x9f, 0xd2, 0x6e, 0xad, 0xe5, 0x3d, 0xa2, 0xe4, 0x65, 0x28, 0xac, 0x3a, 0x6d, 0x3a, + 0x6b, 0x5c, 0x31, 0xae, 0x96, 0xea, 0x53, 0x47, 0x87, 0xd5, 0xf1, 0x90, 0x06, 0x8f, 0x68, 0x60, + 0x77, 0x9c, 0x36, 0xb5, 0xb0, 0x90, 0x7c, 0x1e, 0x4a, 0xec, 0x6f, 0xd8, 0x75, 0x9a, 0x74, 0x36, + 0x87, 0x98, 0x13, 0x47, 0x87, 0xd5, 0x52, 0x47, 0x02, 0xad, 0xa4, 0x9c, 0xbc, 0x06, 0x63, 0x2b, + 0xd4, 0x09, 0xe9, 0xf2, 0xe2, 0x6c, 0xfe, 0x8a, 0x71, 0x35, 0x5f, 0x2f, 0x1f, 0x1d, 0x56, 0x8b, + 0x2d, 0x06, 0xb2, 0x3d, 0xd7, 0x92, 0x85, 0x64, 0x19, 0xc6, 0x96, 0x9e, 0x74, 0xbd, 0x80, 0x86, + 0xb3, 0x85, 0x2b, 0xc6, 0xd5, 0xf1, 0xf9, 0x4b, 0x37, 0x78, 0xfb, 0x6f, 0xc8, 0xf6, 0xdf, 0xd8, + 0x90, 0xed, 0xaf, 0xcf, 0x7c, 0xff, 0xb0, 0x7a, 0xe6, 0xe8, 0xb0, 0x3a, 0x46, 0x39, 0xc9, 0xb7, + 0xff, 0x6b, 0xd5, 0xb0, 0x24, 0x3d, 0x79, 0x17, 0x0a, 0x1b, 0x07, 0x5d, 0x3a, 0x5b, 0xba, 0x62, + 0x5c, 0x9d, 0x9c, 0x7f, 0xe9, 0x06, 0xef, 0xf1, 0xb8, 0x91, 0xc9, 0x7f, 0x0c, 0xab, 0x5e, 0x3c, + 0x3a, 0xac, 0x16, 0x18, 0x8a, 0x85, 0x54, 0xe4, 0x4d, 0x18, 0xbd, 0xe7, 0x87, 0xd1, 0xf2, 0xe2, + 0x2c, 0x60, 0xd3, 0xce, 0x1d, 0x1d, 0x56, 0xa7, 0xf7, 0xfc, 0x30, 0xb2, 0x3d, 0xf7, 0x0d, 0xbf, + 0xed, 0x45, 0xb4, 0xdd, 0x8d, 0x0e, 0x2c, 0x81, 0x64, 0xee, 0xc0, 0x84, 0xc6, 0x8f, 0x8c, 0xc3, + 0xd8, 0xe6, 0xea, 0xfd, 0xd5, 0xb5, 0xed, 0xd5, 0xca, 0x19, 0x52, 0x84, 0xc2, 0xea, 0xda, 0xe2, + 0x52, 0xc5, 0x20, 0x63, 0x90, 0xaf, 0xad, 0xaf, 0x57, 0x72, 0xa4, 0x0c, 0xc5, 0xc5, 0xda, 0x46, + 0xad, 0x5e, 0x6b, 0x2c, 0x55, 0xf2, 0x64, 0x06, 0xa6, 0xb6, 0x97, 0x57, 0x17, 0xd7, 0xb6, 0x1b, + 0xf6, 0xe2, 0x52, 0xe3, 0xfe, 0xc6, 0xda, 0x7a, 0xa5, 0x40, 0x26, 0x01, 0xee, 0x6f, 0xd6, 0x97, + 0xac, 0xd5, 0xa5, 0x8d, 0xa5, 0x46, 0x65, 0xc4, 0xfc, 0x95, 0x3c, 0x14, 0x1f, 0xd0, 0xc8, 0x71, + 0x9d, 0xc8, 0x21, 0x97, 0xb5, 0x21, 0xc2, 0xda, 0x2b, 0x63, 0xf3, 0x72, 0xff, 0xd8, 0x8c, 0x1c, + 0x1d, 0x56, 0x8d, 0x37, 0xd5, 0x31, 0x79, 0x07, 0xc6, 0x17, 0x69, 0xd8, 0x0c, 0xbc, 0x2e, 0x9b, + 0x2f, 0x38, 0x2e, 0xa5, 0xfa, 0xc5, 0xa3, 0xc3, 0xea, 0x39, 0x37, 0x01, 0x2b, 0x6d, 0x55, 0xb1, + 0xc9, 0x32, 0x8c, 0xae, 0x38, 0x3b, 0xb4, 0x15, 0xce, 0x8e, 0x5c, 0xc9, 0x5f, 0x1d, 0x9f, 0x7f, + 0x41, 0xf4, 0xaf, 0xac, 0xe0, 0x0d, 0x5e, 0xba, 0xd4, 0x89, 0x82, 0x83, 0xfa, 0xd9, 0xa3, 0xc3, + 0x6a, 0xa5, 0x85, 0x00, 0xb5, 0xef, 0x38, 0x0a, 0x69, 0x24, 0x63, 0x3e, 0x7a, 0xe2, 0x98, 0xbf, + 0xf8, 0xfd, 0xc3, 0xaa, 0xc1, 0xc6, 0x42, 0x8c, 0x79, 0xc2, 0x4f, 0x1f, 0xfd, 0x2b, 0x90, 0x5b, + 0x5e, 0x9c, 0x1d, 0xc3, 0xb9, 0x56, 0x39, 0x3a, 0xac, 0x96, 0xb5, 0x61, 0xcb, 0x2d, 0x2f, 0x5e, + 0x7a, 0x1b, 0xc6, 0x95, 0x3a, 0x92, 0x0a, 0xe4, 0xf7, 0xe9, 0x01, 0xef, 0x4f, 0x8b, 0xfd, 0x4b, + 0xce, 0xc2, 0xc8, 0x23, 0xa7, 0xd5, 0x13, 0x1d, 0x68, 0xf1, 0x1f, 0x5f, 0xca, 0xfd, 0xa4, 0x61, + 0xfe, 0x46, 0x01, 0x8a, 0x96, 0xcf, 0xd7, 0x19, 0xb9, 0x06, 0x23, 0x8d, 0xc8, 0x89, 0xe4, 0x50, + 0xcc, 0x1c, 0x1d, 0x56, 0xa7, 0x42, 0x06, 0x50, 0xbe, 0xc7, 0x31, 0x18, 0xea, 0xfa, 0x9e, 0x13, + 0xca, 0x21, 0x41, 0xd4, 0x2e, 0x03, 0xa8, 0xa8, 0x88, 0x41, 0x5e, 0x83, 0xc2, 0x03, 0xdf, 0xa5, + 0x62, 0x54, 0xc8, 0xd1, 0x61, 0x75, 0xb2, 0xed, 0xbb, 0x2a, 0x22, 0x96, 0x93, 0x37, 0xa0, 0xb4, + 0xd0, 0x0b, 0x02, 0xda, 0x61, 0x53, 0xb5, 0x80, 0xc8, 0x93, 0x47, 0x87, 0x55, 0x68, 0x72, 0x20, + 0x5b, 0x5c, 0x09, 0x02, 0xeb, 0xea, 0x46, 0xe4, 0x04, 0x11, 0x75, 0x67, 0x47, 0x86, 0xea, 0x6a, + 0xb6, 0xbc, 0xa6, 0x43, 0x4e, 0x92, 0xee, 0x6a, 0xc1, 0x89, 0xdc, 0x83, 0xf1, 0xbb, 0x81, 0xd3, + 0xa4, 0xeb, 0x34, 0xf0, 0x7c, 0x17, 0xc7, 0x30, 0x5f, 0x7f, 0xed, 0xe8, 0xb0, 0x7a, 0x7e, 0x97, + 0x81, 0xed, 0x2e, 0xc2, 0x13, 0xea, 0x1f, 0x1d, 0x56, 0x8b, 0x8b, 0xbd, 0x00, 0x7b, 0xcf, 0x52, + 0x49, 0xc9, 0xcf, 0xb0, 0x21, 0x09, 0x23, 0xec, 0x5a, 0xea, 0xe2, 0xe8, 0x1d, 0x5f, 0x45, 0x53, + 0x54, 0xf1, 0x7c, 0xcb, 0x09, 0x23, 0x3b, 0xe0, 0x74, 0xa9, 0x7a, 0xaa, 0x2c, 0xc9, 0x1a, 0x14, + 0x1b, 0xcd, 0x3d, 0xea, 0xf6, 0x5a, 0x74, 0xb6, 0x88, 0xec, 0x2f, 0x88, 0x89, 0x2b, 0xc7, 0x53, + 0x16, 0xd7, 0x2f, 0x09, 0xde, 0x24, 0x14, 0x10, 0xa5, 0xef, 0x63, 0x26, 0x5f, 0x2a, 0xfe, 0xf6, + 0xef, 0x57, 0xcf, 0xfc, 0xd2, 0x7f, 0xb9, 0x72, 0xc6, 0xfc, 0x97, 0x39, 0xa8, 0xa4, 0x99, 0x90, + 0x87, 0x30, 0xb1, 0xd9, 0x75, 0x9d, 0x88, 0x2e, 0xb4, 0x3c, 0xda, 0x89, 0x42, 0x9c, 0x24, 0xc7, + 0xb7, 0xe9, 0x15, 0xf1, 0xdd, 0xd9, 0x1e, 0x12, 0xda, 0x4d, 0x4e, 0x99, 0x6a, 0x95, 0xce, 0x36, + 0xf9, 0x4e, 0x03, 0xe5, 0x74, 0x88, 0x33, 0xec, 0x74, 0xdf, 0xe1, 0x12, 0x7e, 0xc0, 0x77, 0x04, + 0x5b, 0x31, 0x81, 0x3a, 0xee, 0xce, 0x01, 0xce, 0xcc, 0xe1, 0x27, 0x10, 0x23, 0xc9, 0x98, 0x40, + 0x0c, 0x6c, 0xfe, 0x77, 0x03, 0x26, 0x2d, 0x1a, 0xfa, 0xbd, 0xa0, 0x49, 0xef, 0x51, 0xc7, 0xa5, + 0x01, 0x9b, 0xfe, 0xf7, 0xbd, 0x8e, 0x2b, 0xd6, 0x14, 0x4e, 0xff, 0x7d, 0xaf, 0xa3, 0x2e, 0x61, + 0x2c, 0x27, 0x5f, 0x80, 0xb1, 0x46, 0x6f, 0x07, 0x51, 0xf9, 0x9a, 0x3a, 0x8f, 0x23, 0xd6, 0xdb, + 0xb1, 0x53, 0xe8, 0x12, 0x8d, 0xcc, 0xc1, 0xd8, 0x16, 0x0d, 0xc2, 0x44, 0xe2, 0xa1, 0x64, 0x7f, + 0xc4, 0x41, 0x2a, 0x81, 0xc0, 0x22, 0x77, 0x13, 0xa9, 0x2b, 0xf6, 0xa4, 0xa9, 0x94, 0xac, 0x4b, + 0xa6, 0x4a, 0x5b, 0x40, 0xd4, 0xa9, 0x22, 0xb1, 0xcc, 0xef, 0xe4, 0xa0, 0xb2, 0xe8, 0x44, 0xce, + 0x8e, 0x13, 0x8a, 0xfe, 0xdc, 0xba, 0xc5, 0xe4, 0xb8, 0xd2, 0x50, 0x94, 0xe3, 0xac, 0xe6, 0x9f, + 0xb8, 0x79, 0xaf, 0xa6, 0x9b, 0x37, 0xce, 0x36, 0x48, 0xd1, 0xbc, 0xa4, 0x51, 0xef, 0x9d, 0xdc, + 0xa8, 0x8a, 0x68, 0x54, 0x51, 0x36, 0x2a, 0x69, 0x0a, 0x79, 0x0f, 0x0a, 0x8d, 0x2e, 0x6d, 0x0a, + 0x21, 0x22, 0x65, 0xbf, 0xde, 0x38, 0x86, 0xb0, 0x75, 0xab, 0x5e, 0x16, 0x6c, 0x0a, 0x61, 0x97, + 0x36, 0x2d, 0x24, 0x53, 0x16, 0xcd, 0x77, 0x47, 0xe1, 0x6c, 0x16, 0x19, 0x79, 0x4f, 0xdf, 0x9c, + 0x78, 0xf7, 0xbc, 0x30, 0x70, 0x73, 0x9a, 0x35, 0xf4, 0xed, 0xe9, 0x3a, 0x14, 0xd7, 0xd9, 0x84, + 0x6c, 0xfa, 0x2d, 0xd1, 0x73, 0x4c, 0x2a, 0x16, 0xbb, 0x12, 0x66, 0x58, 0x71, 0x39, 0x79, 0x01, + 0xf2, 0x9b, 0xd6, 0xb2, 0xe8, 0xae, 0xd2, 0xd1, 0x61, 0x35, 0xdf, 0x0b, 0xbc, 0x59, 0xc3, 0x62, + 0x50, 0x32, 0x07, 0xa3, 0x0b, 0xb5, 0x05, 0x1a, 0x44, 0xd8, 0x4d, 0xe5, 0xfa, 0x05, 0x36, 0x5b, + 0x9a, 0x8e, 0xdd, 0xa4, 0x41, 0xa4, 0x7d, 0x5e, 0xa0, 0x91, 0xcf, 0x43, 0xbe, 0xb6, 0xdd, 0x10, + 0x3d, 0x03, 0xa2, 0x67, 0x6a, 0xdb, 0x8d, 0xfa, 0x84, 0xe8, 0x88, 0xbc, 0xf3, 0x38, 0x64, 0xdc, + 0x6b, 0xdb, 0x0d, 0x75, 0xb4, 0x46, 0x8f, 0x19, 0xad, 0xab, 0x50, 0x64, 0x7a, 0x06, 0xdb, 0xe0, + 0x51, 0x28, 0x96, 0xb8, 0xfa, 0xb4, 0x27, 0x60, 0x56, 0x5c, 0x4a, 0x5e, 0x8e, 0xd5, 0x96, 0x62, + 0xc2, 0x4f, 0xa8, 0x2d, 0x52, 0x59, 0x21, 0x4f, 0x60, 0x62, 0xf1, 0xa0, 0xe3, 0xb4, 0xbd, 0xa6, + 0xd8, 0xc2, 0x4b, 0xb8, 0x85, 0xdf, 0x38, 0x66, 0x18, 0x6f, 0x68, 0x04, 0x7c, 0x57, 0x97, 0xc2, + 0x77, 0xd6, 0xe5, 0x65, 0x76, 0x7a, 0x87, 0x9f, 0x35, 0x2c, 0xfd, 0x43, 0x6c, 0x2d, 0x49, 0x11, + 0x89, 0x7a, 0x55, 0x32, 0xed, 0x24, 0x38, 0x59, 0x4b, 0x81, 0x80, 0xa8, 0x6b, 0x29, 0xde, 0x74, + 0xdf, 0x83, 0xfc, 0xdd, 0x85, 0xf5, 0xd9, 0x71, 0xe4, 0x41, 0x04, 0x8f, 0xbb, 0x0b, 0xeb, 0x0b, + 0x2d, 0xbf, 0xe7, 0x36, 0x3e, 0x5a, 0xa9, 0x5f, 0x10, 0x6c, 0x26, 0x76, 0x9b, 0x5d, 0xad, 0x46, + 0x8c, 0x8e, 0x2c, 0x41, 0x51, 0xb6, 0x72, 0xb6, 0x8c, 0x3c, 0xa6, 0x53, 0x8d, 0xdf, 0xba, 0xc5, + 0xd7, 0x9a, 0x2b, 0x7e, 0xab, 0xb5, 0x90, 0x38, 0x97, 0xb6, 0x81, 0xf4, 0xf7, 0x4b, 0x86, 0x26, + 0xf1, 0x79, 0x55, 0x93, 0x18, 0x9f, 0x3f, 0x27, 0xbe, 0xb5, 0xe0, 0xb7, 0xdb, 0x4e, 0xc7, 0x45, + 0xda, 0xad, 0x79, 0x55, 0xc1, 0xa8, 0xc1, 0x64, 0x52, 0x91, 0x15, 0x2f, 0x8c, 0xc8, 0x1c, 0x94, + 0x24, 0x84, 0x6d, 0x22, 0xf9, 0xcc, 0x2a, 0x5b, 0x09, 0x8e, 0xf9, 0x27, 0x39, 0x80, 0xa4, 0xe4, + 0x19, 0x95, 0x33, 0x3f, 0xa1, 0xc9, 0x99, 0x73, 0xe9, 0x09, 0x3a, 0x50, 0xc2, 0x90, 0x0f, 0x60, + 0x94, 0xa9, 0x5c, 0x3d, 0xa9, 0x52, 0x5e, 0x48, 0x93, 0x62, 0xe1, 0xd6, 0xad, 0xfa, 0xa4, 0x20, + 0x1e, 0x0d, 0x11, 0x62, 0x09, 0x32, 0x45, 0x44, 0xfd, 0xcf, 0x42, 0x32, 0x18, 0x42, 0x38, 0x5d, + 0x55, 0xa4, 0x8b, 0x91, 0xac, 0x47, 0x29, 0x5d, 0x14, 0xd9, 0x72, 0x91, 0xcb, 0x16, 0xde, 0xa9, + 0x63, 0x42, 0xb6, 0xa4, 0x25, 0x0b, 0xef, 0xc0, 0x13, 0x25, 0x4b, 0x37, 0xbd, 0x6c, 0x0b, 0x38, + 0x0d, 0xae, 0x66, 0xf6, 0x4a, 0xd6, 0x82, 0xbd, 0x72, 0xd2, 0x82, 0x4d, 0x2f, 0xd7, 0x5b, 0x83, + 0x64, 0xd9, 0x39, 0xb9, 0xba, 0x9c, 0xc7, 0x2a, 0x39, 0xca, 0xb4, 0x77, 0xf8, 0xd2, 0x1c, 0x1d, + 0xb8, 0x34, 0xcf, 0x65, 0x2e, 0x4d, 0xbe, 0x30, 0xdf, 0x81, 0x91, 0xda, 0xcf, 0xf5, 0x02, 0x2a, + 0x74, 0xbf, 0xb2, 0xfc, 0x26, 0x83, 0xc5, 0x6b, 0x7a, 0xca, 0x61, 0x3f, 0x55, 0x9d, 0x19, 0xcb, + 0xd9, 0x97, 0x37, 0x56, 0x1a, 0x42, 0xaf, 0x23, 0xa9, 0x6e, 0xd9, 0x58, 0x51, 0xaa, 0x1d, 0x69, + 0xad, 0x66, 0x54, 0x64, 0x0e, 0x72, 0xb5, 0x45, 0x34, 0x16, 0xc7, 0xe7, 0x4b, 0xf2, 0xb3, 0x8b, + 0xf5, 0xb3, 0x82, 0xa4, 0xec, 0x68, 0xf6, 0x43, 0x6d, 0xf1, 0xb3, 0x5b, 0xfc, 0x2d, 0x45, 0x4d, + 0x10, 0xd3, 0x94, 0x99, 0xa3, 0x62, 0xb2, 0x18, 0x89, 0xd2, 0xd2, 0x37, 0x59, 0xe2, 0xa9, 0x72, + 0x8d, 0x0f, 0x5c, 0xae, 0x6f, 0xe0, 0xc6, 0x95, 0x4d, 0x08, 0x87, 0xcb, 0xfc, 0x4b, 0x03, 0x71, + 0xc9, 0x1b, 0x30, 0x6a, 0xd1, 0xdd, 0x64, 0xaf, 0x45, 0x9b, 0x2d, 0x40, 0x88, 0xfa, 0x01, 0x8e, + 0x83, 0x82, 0x9c, 0xba, 0xe1, 0x9e, 0xf7, 0x30, 0x12, 0x5f, 0x89, 0x05, 0xb9, 0x00, 0x2b, 0x82, + 0x5c, 0x40, 0x34, 0x41, 0x2e, 0x60, 0x6c, 0x8a, 0x59, 0x8b, 0x0d, 0xa1, 0x4c, 0xca, 0x9a, 0x5a, + 0x8b, 0xca, 0x58, 0x05, 0xae, 0x36, 0x56, 0xd6, 0x62, 0x83, 0xdc, 0x86, 0x52, 0xad, 0xd9, 0xf4, + 0x7b, 0x8a, 0xd1, 0x33, 0x7b, 0x74, 0x58, 0x3d, 0xeb, 0x70, 0xa0, 0x6e, 0xa2, 0x27, 0xa8, 0x66, + 0x3d, 0xa9, 0x35, 0xe3, 0xb1, 0xd0, 0xea, 0x85, 0x11, 0x0d, 0x96, 0x17, 0x45, 0x93, 0x91, 0x47, + 0x93, 0x03, 0x53, 0x3c, 0x62, 0x54, 0xf3, 0x3f, 0x1b, 0x58, 0x63, 0xf2, 0x36, 0xc0, 0x72, 0x87, + 0x29, 0xb6, 0x4d, 0x1a, 0x33, 0x40, 0xe3, 0xd9, 0x13, 0x50, 0x9d, 0x83, 0x82, 0xac, 0x7f, 0x3a, + 0x37, 0xf4, 0xa7, 0xd9, 0x27, 0xa5, 0x9a, 0x2c, 0xce, 0x51, 0xc4, 0x27, 0x03, 0x01, 0x4d, 0x7d, + 0x32, 0x41, 0x26, 0xaf, 0xc1, 0xd8, 0x72, 0xed, 0x41, 0xad, 0x17, 0xed, 0x61, 0x7f, 0x15, 0xb9, + 0xc0, 0xf2, 0x9c, 0xb6, 0xed, 0xf4, 0xa2, 0x3d, 0x4b, 0x16, 0x9a, 0xbf, 0x64, 0xc0, 0xb8, 0xb2, + 0x56, 0x59, 0x55, 0xd7, 0x03, 0xff, 0x63, 0xda, 0x8c, 0xf4, 0x5e, 0xea, 0x72, 0x60, 0xaa, 0xaa, + 0x31, 0x6a, 0xaa, 0x77, 0x72, 0xa7, 0xe8, 0x1d, 0x73, 0x4e, 0x88, 0x00, 0x66, 0x03, 0x28, 0x47, + 0x1c, 0x68, 0x03, 0x30, 0x1d, 0x47, 0xb5, 0x01, 0x58, 0xb9, 0xf9, 0x87, 0x06, 0x5b, 0xba, 0x64, + 0x0e, 0xe0, 0x3e, 0x3d, 0x88, 0x9c, 0x9d, 0x3b, 0x5e, 0x4b, 0x3b, 0xba, 0xda, 0x47, 0xa8, 0xfd, + 0xd0, 0x6b, 0x51, 0x4b, 0x41, 0x21, 0xb7, 0xa0, 0x78, 0x3f, 0xd8, 0x79, 0x0b, 0xd1, 0x73, 0xb1, + 0x08, 0x9e, 0xd9, 0x0f, 0x76, 0xde, 0x42, 0x64, 0x75, 0xbe, 0x4a, 0x44, 0x62, 0xc2, 0xe8, 0xa2, + 0xdf, 0x76, 0x3c, 0xb9, 0xed, 0x01, 0xdb, 0x3b, 0x5c, 0x84, 0x58, 0xa2, 0x84, 0x09, 0xfd, 0xc6, + 0xfa, 0xaa, 0x98, 0x98, 0x28, 0xf4, 0xc3, 0x6e, 0xc7, 0x62, 0x30, 0xf3, 0x7b, 0x06, 0x8c, 0x2b, + 0x12, 0x89, 0x7c, 0x51, 0x98, 0xf9, 0x06, 0x1e, 0x52, 0x9d, 0xef, 0x97, 0x59, 0xac, 0x94, 0x6f, + 0xd7, 0xcc, 0xfc, 0x17, 0x46, 0x7f, 0x22, 0x0d, 0x72, 0xc3, 0x48, 0x83, 0xb7, 0x01, 0xb8, 0x2e, + 0x87, 0xdd, 0xa9, 0xcc, 0x1b, 0xe5, 0x50, 0x4f, 0x1d, 0x8c, 0x04, 0xd9, 0xfc, 0x9b, 0x39, 0x28, + 0x0a, 0x5b, 0x65, 0xfe, 0x19, 0xd5, 0x21, 0xde, 0xd2, 0x74, 0x88, 0x19, 0x41, 0xaa, 0x28, 0xb7, + 0xf3, 0x27, 0xd8, 0x28, 0x6f, 0x43, 0x59, 0x76, 0x01, 0xaa, 0x62, 0xd7, 0x60, 0x4c, 0x5a, 0xd9, + 0x5c, 0x11, 0x9b, 0xd2, 0x78, 0x6e, 0xcd, 0x5b, 0xb2, 0xdc, 0xfc, 0xce, 0x88, 0xa4, 0xe5, 0x5f, + 0x62, 0x5d, 0x58, 0x73, 0xdd, 0x40, 0xed, 0x42, 0xc7, 0x75, 0x03, 0x0b, 0xa1, 0x6c, 0xa0, 0xd6, + 0x7b, 0x3b, 0x2d, 0xaf, 0x89, 0x38, 0xca, 0xaa, 0xe9, 0x22, 0xd4, 0x66, 0xa8, 0xea, 0x40, 0x25, + 0xc8, 0x9a, 0x89, 0x90, 0x3f, 0xd6, 0x44, 0xf8, 0x69, 0x28, 0x2d, 0xb4, 0x5d, 0x4d, 0x85, 0x30, + 0x33, 0x3a, 0xe5, 0x46, 0x8c, 0xc4, 0x95, 0x87, 0xcb, 0xa2, 0x8f, 0xce, 0x36, 0xdb, 0x6e, 0xbf, + 0xe2, 0x90, 0xb0, 0xd4, 0x74, 0xfc, 0x91, 0x4f, 0xa3, 0xe3, 0xdf, 0x86, 0xd2, 0x66, 0x48, 0x37, + 0x7a, 0x9d, 0x0e, 0x6d, 0xa1, 0x3a, 0x51, 0xe4, 0xb2, 0xa7, 0x17, 0x52, 0x3b, 0x42, 0xa8, 0x5a, + 0x81, 0x18, 0x55, 0x9d, 0x56, 0x63, 0xc7, 0x4c, 0xab, 0x2f, 0x42, 0xa1, 0xd6, 0xed, 0x4a, 0xe3, + 0x27, 0xde, 0x24, 0xbb, 0x5d, 0xdc, 0xfa, 0x26, 0x9d, 0x6e, 0x57, 0x37, 0x65, 0x10, 0x9b, 0x50, + 0x20, 0xf7, 0x7b, 0x3b, 0x34, 0xe8, 0xd0, 0x88, 0x86, 0x42, 0x34, 0x87, 0xb3, 0x80, 0x3c, 0x66, + 0xe5, 0x19, 0x73, 0x1a, 0x01, 0x0d, 0xd7, 0x0b, 0xfb, 0xbd, 0x1d, 0x6a, 0x0b, 0x19, 0xaf, 0xf6, + 0x5d, 0x06, 0xc3, 0x4b, 0x0d, 0x98, 0xd4, 0xfb, 0xff, 0x29, 0x28, 0x16, 0x1f, 0x16, 0x8a, 0xc5, + 0x4a, 0xc9, 0xfc, 0x95, 0x1c, 0x8c, 0xd7, 0xba, 0xdd, 0x67, 0xfc, 0x04, 0xe2, 0x27, 0xb5, 0x55, + 0x7d, 0x3e, 0x19, 0xbd, 0x53, 0x1c, 0x3e, 0xfc, 0x95, 0x01, 0x53, 0x29, 0x0a, 0xb5, 0xf6, 0xc6, + 0x90, 0x16, 0x79, 0x6e, 0x48, 0x8b, 0x3c, 0x3f, 0xd8, 0x22, 0x57, 0xd7, 0x4c, 0xe1, 0xd3, 0xac, + 0x99, 0xd7, 0x21, 0x5f, 0xeb, 0x76, 0x45, 0xaf, 0x94, 0x93, 0x5e, 0xd9, 0xba, 0xc5, 0x37, 0x22, + 0xa7, 0xdb, 0xb5, 0x18, 0x86, 0xf9, 0x26, 0x94, 0x10, 0x8c, 0x12, 0xed, 0x8a, 0x58, 0x0a, 0x5c, + 0x9c, 0x69, 0x64, 0x7c, 0xda, 0x9b, 0xff, 0xc7, 0x80, 0x11, 0xfc, 0xfd, 0x8c, 0x4e, 0x97, 0x79, + 0x6d, 0xba, 0x54, 0x94, 0xe9, 0x32, 0xcc, 0x44, 0xf9, 0xa3, 0x3c, 0xf6, 0x96, 0x98, 0x22, 0xc2, + 0xa6, 0x33, 0x32, 0x6c, 0xba, 0x4f, 0x21, 0xc0, 0xf7, 0xd3, 0xd6, 0x5d, 0x1e, 0x07, 0xe3, 0xe5, + 0x74, 0x55, 0x9f, 0x8a, 0x61, 0x77, 0x0f, 0xc8, 0x72, 0x27, 0xa4, 0xcd, 0x5e, 0x40, 0x1b, 0xfb, + 0x5e, 0x77, 0x8b, 0x06, 0xde, 0xc3, 0x03, 0xa1, 0x19, 0xa2, 0x8c, 0xf5, 0x44, 0xa9, 0x1d, 0xee, + 0x7b, 0x5d, 0xfb, 0x11, 0x96, 0x5b, 0x19, 0x34, 0xe4, 0x03, 0x18, 0xb3, 0xe8, 0xe3, 0xc0, 0x8b, + 0xa8, 0xe8, 0xdb, 0xc9, 0xd8, 0x0e, 0x40, 0x28, 0xd7, 0x4d, 0x02, 0xfe, 0x43, 0x1d, 0x7f, 0x51, + 0xfe, 0xd9, 0x99, 0x51, 0xdf, 0x1d, 0xc1, 0xb5, 0x70, 0xc2, 0x4d, 0xd9, 0x31, 0x06, 0xba, 0x3e, + 0x98, 0xf9, 0xd3, 0x0c, 0xe6, 0x16, 0x94, 0x99, 0xe9, 0x96, 0xb2, 0xd4, 0x2f, 0x27, 0x63, 0x79, + 0x43, 0x2d, 0x3e, 0xee, 0x92, 0x4c, 0xe3, 0x43, 0xec, 0xf4, 0x24, 0xe1, 0x97, 0x6f, 0x2f, 0x2a, + 0x8c, 0x33, 0xa6, 0x47, 0x2c, 0x3a, 0x9a, 0xbc, 0xb3, 0x4e, 0x3d, 0x31, 0x46, 0x3f, 0xdd, 0xc4, + 0x18, 0xfb, 0x24, 0x13, 0x23, 0x7d, 0x3d, 0x59, 0x3c, 0xcd, 0xf5, 0xe4, 0xa5, 0x0f, 0x60, 0xba, + 0xaf, 0x87, 0x4f, 0x73, 0xc5, 0xf7, 0xd9, 0x4d, 0xcb, 0x5f, 0x88, 0xfb, 0x85, 0xcc, 0xa3, 0x39, + 0xea, 0x05, 0xb4, 0x19, 0xa1, 0xe8, 0x15, 0xd2, 0x32, 0x10, 0xb0, 0x94, 0xbd, 0x8c, 0x30, 0xf2, + 0x3e, 0x8c, 0xf1, 0x2b, 0x92, 0x70, 0x36, 0x87, 0x63, 0x3f, 0x21, 0xbe, 0xc8, 0xa1, 0xe2, 0x9e, + 0x9a, 0x63, 0xa8, 0xbd, 0x2a, 0x88, 0xcc, 0xbb, 0x30, 0x2a, 0xae, 0x58, 0x8e, 0x5f, 0x17, 0x55, + 0x18, 0xd9, 0x4a, 0x7a, 0x06, 0x8f, 0xc5, 0x79, 0x23, 0x2c, 0x0e, 0x37, 0x7f, 0xcd, 0x80, 0x49, + 0xbd, 0x95, 0xe4, 0x06, 0x8c, 0x8a, 0x3b, 0x40, 0x03, 0xef, 0x00, 0x59, 0x6b, 0x46, 0xf9, 0xed, + 0x9f, 0x76, 0xe7, 0x27, 0xb0, 0x98, 0xe8, 0x17, 0x1c, 0xb0, 0x2d, 0x42, 0xf4, 0x8b, 0x49, 0x6a, + 0xc9, 0x32, 0x66, 0x72, 0x59, 0x34, 0xec, 0xb5, 0x22, 0xd5, 0xe4, 0x0a, 0x10, 0x62, 0x89, 0x12, + 0xf3, 0xd0, 0x00, 0x68, 0x34, 0xee, 0xdd, 0xa7, 0x07, 0xeb, 0x8e, 0x17, 0xa0, 0xd9, 0x8a, 0xab, + 0xf1, 0xbe, 0x18, 0xad, 0xb2, 0x30, 0x5b, 0xf9, 0xca, 0xdd, 0xa7, 0x07, 0x9a, 0xd9, 0x2a, 0x51, + 0x71, 0xc9, 0x07, 0xde, 0x23, 0x27, 0xa2, 0x8c, 0x30, 0x87, 0x84, 0x7c, 0xc9, 0x73, 0x68, 0x8a, + 0x52, 0x41, 0x26, 0xdf, 0x80, 0xc9, 0xe4, 0x17, 0x3a, 0x1e, 0xe4, 0xd1, 0xa6, 0x93, 0x33, 0x42, + 0x2f, 0xac, 0xbf, 0x74, 0x74, 0x58, 0xbd, 0xa4, 0x70, 0xb5, 0x19, 0x96, 0xc2, 0x3a, 0xc5, 0xcc, + 0xfc, 0x03, 0x03, 0x60, 0x63, 0xa5, 0x21, 0x1b, 0xf8, 0x1a, 0x14, 0xe2, 0xd3, 0xa0, 0x32, 0xb7, + 0x8d, 0x53, 0xc6, 0x1f, 0x96, 0x93, 0x97, 0x21, 0x9f, 0xb4, 0x64, 0xfa, 0xe8, 0xb0, 0x3a, 0xa1, + 0xb7, 0x80, 0x95, 0x92, 0xbb, 0x30, 0x36, 0x54, 0x9d, 0x71, 0x76, 0x66, 0xd4, 0x55, 0x52, 0xe3, + 0x28, 0x7c, 0xb8, 0xbd, 0xf1, 0xfc, 0x8e, 0xc2, 0xb7, 0x72, 0x30, 0xc5, 0xfa, 0xb5, 0xd6, 0x8b, + 0xf6, 0xfc, 0xc0, 0x8b, 0x0e, 0x9e, 0x59, 0xab, 0xf8, 0x5d, 0x4d, 0x21, 0xba, 0x24, 0xc5, 0x96, + 0xda, 0xb6, 0xa1, 0x8c, 0xe3, 0xbf, 0x18, 0x83, 0x99, 0x0c, 0x2a, 0xf2, 0x86, 0xf0, 0xbe, 0x49, + 0xce, 0x8c, 0xd0, 0xbb, 0xe6, 0x47, 0x87, 0xd5, 0xb2, 0x44, 0xdf, 0x48, 0xbc, 0x6d, 0xe6, 0x61, + 0x5c, 0x98, 0x3e, 0xab, 0x89, 0x46, 0x8d, 0x6e, 0x1b, 0xf2, 0x4c, 0x0c, 0x45, 0x93, 0x8a, 0x44, + 0x6a, 0x50, 0x5e, 0xd8, 0xa3, 0xcd, 0x7d, 0xaf, 0xb3, 0x7b, 0x9f, 0x1e, 0x70, 0x7d, 0xa9, 0x5c, + 0x7f, 0x91, 0x59, 0x5a, 0x4d, 0x01, 0x67, 0x43, 0xaa, 0x1b, 0x71, 0x1a, 0x09, 0x79, 0x1f, 0xc6, + 0x1b, 0xde, 0x6e, 0x47, 0x72, 0x28, 0x20, 0x87, 0xcb, 0x47, 0x87, 0xd5, 0xf3, 0x21, 0x07, 0xf7, + 0x33, 0x50, 0x09, 0xc8, 0x35, 0x18, 0xb1, 0xfc, 0x16, 0xe5, 0xdb, 0xb0, 0xf0, 0xe7, 0x08, 0x18, + 0x40, 0x3d, 0x9b, 0x46, 0x0c, 0x72, 0x0f, 0xc6, 0xd8, 0x3f, 0x0f, 0x9c, 0xee, 0xec, 0x28, 0xca, + 0x6d, 0x12, 0x2b, 0xf8, 0x08, 0xed, 0x7a, 0x9d, 0x5d, 0x55, 0xc7, 0x6f, 0x51, 0xbb, 0xed, 0x74, + 0xb5, 0x7d, 0x91, 0x23, 0x92, 0x2d, 0x18, 0x4f, 0x04, 0x41, 0x38, 0x3b, 0xa6, 0xdd, 0x05, 0x25, + 0x25, 0xf5, 0xcf, 0x09, 0x66, 0x17, 0xa2, 0x56, 0x88, 0x73, 0xbb, 0xcb, 0xf0, 0xf5, 0xc6, 0x28, + 0x8c, 0x34, 0x1b, 0xa4, 0x38, 0xd8, 0x06, 0x31, 0x4e, 0xb4, 0x41, 0x5c, 0x00, 0xd1, 0x49, 0xb5, + 0xd6, 0xae, 0x70, 0xbf, 0xba, 0x36, 0x78, 0x82, 0xdd, 0x48, 0x90, 0x71, 0x4d, 0xf2, 0x93, 0x29, + 0xd1, 0xff, 0x4e, 0x6b, 0x57, 0x3b, 0x99, 0x8a, 0x51, 0x59, 0x37, 0x24, 0xa2, 0x46, 0x5a, 0xe0, + 0xb2, 0x1b, 0x92, 0x92, 0xa4, 0x1b, 0x3e, 0x7e, 0x1c, 0x0d, 0xea, 0x06, 0x85, 0x11, 0x59, 0x05, + 0xa8, 0x35, 0x23, 0xef, 0x11, 0xc5, 0x29, 0x31, 0xae, 0x75, 0xc4, 0x42, 0xed, 0x3e, 0x3d, 0x68, + 0xd0, 0x28, 0xf6, 0x6c, 0x38, 0xe7, 0x20, 0x6a, 0x6a, 0x9a, 0x58, 0x0a, 0x07, 0xd2, 0x85, 0x73, + 0x35, 0xd7, 0xf5, 0xb8, 0x4b, 0xde, 0x46, 0xc0, 0xe6, 0xaf, 0x8b, 0xac, 0xcb, 0xd9, 0xac, 0xaf, + 0x09, 0xd6, 0x9f, 0x73, 0x62, 0x2a, 0x3b, 0xe2, 0x64, 0xe9, 0xcf, 0x64, 0x33, 0x36, 0xd7, 0x60, + 0x52, 0xef, 0x52, 0xdd, 0x19, 0xad, 0x0c, 0x45, 0xab, 0x51, 0xb3, 0x1b, 0xf7, 0x6a, 0x37, 0x2b, + 0x06, 0xa9, 0x40, 0x59, 0xfc, 0x9a, 0xb7, 0xe7, 0xdf, 0xba, 0x5d, 0xc9, 0x69, 0x90, 0xb7, 0x6e, + 0xce, 0x57, 0xf2, 0xe6, 0x1f, 0x19, 0x50, 0x94, 0xf5, 0x23, 0xb7, 0x21, 0xdf, 0x68, 0xdc, 0x4b, + 0x5d, 0x41, 0x26, 0x5b, 0x2f, 0xdf, 0x64, 0xc2, 0x70, 0x4f, 0xdd, 0x64, 0x1a, 0x8d, 0x7b, 0x8c, + 0x6e, 0x63, 0xa5, 0x21, 0x94, 0x96, 0x8c, 0xe9, 0x3a, 0x3d, 0xe0, 0x5e, 0xe6, 0x36, 0xe4, 0x3f, + 0xdc, 0xde, 0x10, 0xd6, 0x50, 0xc6, 0xf8, 0x22, 0xdd, 0xc7, 0x8f, 0xd5, 0xad, 0x8f, 0x11, 0x98, + 0x16, 0x8c, 0x2b, 0x4b, 0x8b, 0x2b, 0x11, 0x6d, 0x3f, 0x76, 0xd3, 0x12, 0x4a, 0x04, 0x83, 0x58, + 0xa2, 0x84, 0xe9, 0x3c, 0x2b, 0x7e, 0xd3, 0x69, 0x09, 0x6d, 0x04, 0x75, 0x9e, 0x16, 0x03, 0x58, + 0x1c, 0x6e, 0xfe, 0xb1, 0x01, 0x95, 0xf5, 0xc0, 0x7f, 0xe4, 0x31, 0x09, 0xbc, 0xe1, 0xef, 0xd3, + 0xce, 0xd6, 0x4d, 0xf2, 0xa6, 0x14, 0x02, 0x5c, 0x85, 0xbb, 0xc0, 0xa8, 0x50, 0x08, 0xfc, 0xe8, + 0xb0, 0x0a, 0x8d, 0x83, 0x30, 0xa2, 0x6d, 0x56, 0x2e, 0x05, 0x81, 0xe2, 0xed, 0x96, 0x1b, 0xde, + 0x83, 0xe6, 0x04, 0x6f, 0xb7, 0x2a, 0x8c, 0x60, 0x75, 0x14, 0x27, 0x86, 0x91, 0x88, 0x01, 0x2c, + 0x0e, 0x57, 0x04, 0xf6, 0x77, 0x72, 0x7d, 0x6d, 0x98, 0x7f, 0xae, 0xbc, 0x50, 0xf4, 0xc6, 0x0d, + 0xb5, 0x89, 0x7d, 0x15, 0xce, 0xa6, 0xbb, 0x04, 0xcf, 0x45, 0x6a, 0x30, 0xa5, 0xc3, 0xe5, 0x11, + 0xc9, 0x85, 0xcc, 0x6f, 0x6d, 0xcd, 0x5b, 0x69, 0x7c, 0xf3, 0x07, 0x06, 0x94, 0xf0, 0x5f, 0xab, + 0xd7, 0xa2, 0x4c, 0xb3, 0xa9, 0x6d, 0x37, 0xc4, 0x85, 0x94, 0x7a, 0x69, 0xe4, 0x3c, 0x0e, 0x6d, + 0x71, 0x7b, 0xa5, 0xc9, 0x91, 0x18, 0x59, 0x90, 0xf2, 0xeb, 0xb7, 0x50, 0xcc, 0xd0, 0x98, 0x94, + 0xdf, 0xd3, 0x85, 0x29, 0x52, 0x81, 0xcc, 0xc6, 0x8f, 0xfd, 0xf2, 0x5b, 0xf2, 0x68, 0x18, 0xc7, + 0x0f, 0xe9, 0x7c, 0xed, 0x9a, 0x43, 0xa2, 0x91, 0x37, 0x61, 0x94, 0x7d, 0xda, 0x92, 0x97, 0x18, + 0x68, 0x55, 0x60, 0x1d, 0x03, 0xed, 0x36, 0x90, 0x23, 0x99, 0xff, 0x2a, 0x97, 0xee, 0x40, 0xa1, + 0x05, 0x9c, 0x72, 0x6d, 0xbc, 0x03, 0x23, 0xb5, 0x56, 0xcb, 0x7f, 0x2c, 0xa4, 0x84, 0x3c, 0xa6, + 0x89, 0xfb, 0x8f, 0xef, 0xb0, 0x0e, 0x43, 0xd1, 0x6e, 0x7f, 0x19, 0x80, 0x2c, 0x40, 0xa9, 0xb6, + 0xdd, 0x58, 0x5e, 0x5e, 0xdc, 0xd8, 0x58, 0x11, 0x4e, 0xc6, 0xaf, 0xca, 0xfe, 0xf1, 0x3c, 0xd7, + 0x8e, 0xa2, 0xd6, 0x00, 0x1f, 0xc4, 0x84, 0x8e, 0xbc, 0x07, 0xf0, 0xa1, 0xef, 0x75, 0x1e, 0xd0, + 0x68, 0xcf, 0x77, 0x45, 0xe3, 0x99, 0x4a, 0x31, 0xfe, 0xb1, 0xef, 0x75, 0xec, 0x36, 0x82, 0x59, + 0xdd, 0x13, 0x24, 0x4b, 0xf9, 0x9f, 0xf5, 0x74, 0xdd, 0x8f, 0x50, 0x87, 0x19, 0x49, 0x7a, 0x7a, + 0xc7, 0x8f, 0xd2, 0x77, 0x2c, 0x12, 0xcd, 0xfc, 0xf5, 0x1c, 0x4c, 0x72, 0x4b, 0x95, 0x4f, 0x98, + 0x67, 0x76, 0x31, 0xbe, 0xa3, 0x2d, 0xc6, 0x8b, 0x72, 0x63, 0x50, 0x9a, 0x36, 0xd4, 0x52, 0xdc, + 0x03, 0xd2, 0x4f, 0x43, 0x2c, 0x79, 0x9e, 0x32, 0xcc, 0x2a, 0xbc, 0x99, 0xdc, 0x1d, 0x87, 0x48, + 0x64, 0xa3, 0x28, 0x0c, 0x2d, 0x8d, 0x87, 0xf9, 0x6b, 0x39, 0x98, 0x50, 0xf4, 0xc9, 0x67, 0xb6, + 0xe3, 0xbf, 0xa4, 0x75, 0xbc, 0xbc, 0x83, 0x50, 0x5a, 0x36, 0x54, 0xbf, 0xf7, 0x60, 0xba, 0x8f, + 0x24, 0xad, 0x96, 0x1b, 0xc3, 0xa8, 0xe5, 0x6f, 0xf4, 0x5f, 0x6e, 0x73, 0x87, 0xe4, 0xf8, 0x72, + 0x5b, 0xbd, 0x4d, 0xff, 0x56, 0x0e, 0xce, 0x8a, 0x5f, 0xb5, 0x9e, 0xeb, 0x45, 0x0b, 0x7e, 0xe7, + 0xa1, 0xb7, 0xfb, 0xcc, 0x8e, 0x45, 0x4d, 0x1b, 0x8b, 0xaa, 0x3e, 0x16, 0x4a, 0x03, 0x07, 0x0f, + 0x89, 0xf9, 0x6f, 0x8a, 0x30, 0x3b, 0x88, 0x80, 0x99, 0xfd, 0x8a, 0x55, 0x85, 0x66, 0x7f, 0xca, + 0x62, 0xe5, 0xf6, 0x54, 0xe2, 0xcc, 0x91, 0x1b, 0xc2, 0x99, 0x63, 0x05, 0x2a, 0xf8, 0xa9, 0x06, + 0x0d, 0x59, 0x27, 0x84, 0x89, 0x37, 0xe4, 0x95, 0xa3, 0xc3, 0xea, 0x65, 0x87, 0x95, 0xd9, 0xa1, + 0x28, 0xb4, 0x7b, 0x81, 0xa7, 0xf0, 0xe8, 0xa3, 0x24, 0x7f, 0x60, 0xc0, 0x24, 0x02, 0x97, 0x1e, + 0xd1, 0x4e, 0x84, 0xcc, 0x0a, 0xe2, 0x92, 0x26, 0x0e, 0x3a, 0x69, 0x44, 0x81, 0xd7, 0xd9, 0xc5, + 0x83, 0xa4, 0xb0, 0xbe, 0xc3, 0x7a, 0xe1, 0xcf, 0x0f, 0xab, 0xef, 0x7e, 0x92, 0x40, 0x16, 0xc1, + 0x2a, 0x64, 0x86, 0x3c, 0xaf, 0x28, 0xc5, 0xcf, 0xa6, 0xaa, 0x99, 0xaa, 0x11, 0xf9, 0x29, 0xb8, + 0xb0, 0xd4, 0x71, 0x76, 0x5a, 0x74, 0xc1, 0xef, 0x44, 0x5e, 0xa7, 0xe7, 0xf7, 0xc2, 0xba, 0xd3, + 0xdc, 0xef, 0x75, 0x43, 0x71, 0xd8, 0x89, 0x2d, 0x6f, 0xc6, 0x85, 0xf6, 0x0e, 0x2f, 0x55, 0x58, + 0x0e, 0x62, 0x40, 0xee, 0xc1, 0x34, 0x2f, 0xaa, 0xf5, 0x22, 0xbf, 0xd1, 0x74, 0x5a, 0x5e, 0x67, + 0x17, 0xcf, 0x40, 0x8b, 0xf5, 0x4b, 0xcc, 0xb6, 0x74, 0x7a, 0x91, 0x6f, 0x87, 0x1c, 0xae, 0xf0, + 0xeb, 0x27, 0x22, 0xcb, 0x30, 0x65, 0x51, 0xc7, 0x7d, 0xe0, 0x3c, 0x59, 0x70, 0xba, 0x4e, 0xd3, + 0x8b, 0x0e, 0xd0, 0x32, 0xcb, 0xd7, 0xab, 0x47, 0x87, 0xd5, 0x17, 0x02, 0xea, 0xb8, 0x76, 0xdb, + 0x79, 0x62, 0x37, 0x45, 0xa1, 0xc2, 0x2c, 0x4d, 0x17, 0xb3, 0xf2, 0x3a, 0x31, 0xab, 0x52, 0x9a, + 0x95, 0xd7, 0x19, 0xcc, 0x2a, 0xa1, 0x93, 0xac, 0x36, 0x9c, 0x60, 0x97, 0x46, 0xfc, 0x90, 0x10, + 0xae, 0x18, 0x57, 0x0d, 0x85, 0x55, 0x84, 0x65, 0x36, 0x1e, 0x18, 0xa6, 0x59, 0x29, 0x74, 0x6c, + 0xe6, 0x6d, 0x07, 0x5e, 0x44, 0xd5, 0x16, 0x8e, 0x63, 0xb5, 0xb0, 0xff, 0xf1, 0x98, 0x74, 0x50, + 0x13, 0xfb, 0x28, 0x13, 0x6e, 0x4a, 0x23, 0xcb, 0x7d, 0xdc, 0xb2, 0x5b, 0xd9, 0x47, 0x19, 0x73, + 0x53, 0xdb, 0x39, 0x81, 0xed, 0x54, 0xb8, 0x0d, 0x68, 0x68, 0x1f, 0x25, 0x59, 0x65, 0x9d, 0x16, + 0xd1, 0x0e, 0x9b, 0xd1, 0xe2, 0x90, 0x74, 0x12, 0xab, 0xf6, 0x8a, 0xb0, 0xa9, 0x2b, 0x81, 0x2c, + 0xb6, 0x33, 0x8e, 0x4c, 0xd3, 0xc4, 0x1f, 0x16, 0x8a, 0x23, 0x95, 0x51, 0xab, 0xc2, 0xa7, 0x7c, + 0xc4, 0x26, 0x0e, 0xca, 0x62, 0xf3, 0x77, 0x72, 0x70, 0x51, 0x8a, 0x63, 0x1a, 0x3d, 0xf6, 0x83, + 0x7d, 0xaf, 0xb3, 0xfb, 0x8c, 0x4b, 0xd5, 0x3b, 0x9a, 0x54, 0x7d, 0x25, 0xb5, 0xc3, 0xa5, 0x5a, + 0x79, 0x8c, 0x68, 0xfd, 0xb3, 0x11, 0x78, 0xf1, 0x58, 0x2a, 0xf2, 0x11, 0xdb, 0x05, 0x3d, 0xda, + 0x89, 0x96, 0xdd, 0x16, 0x65, 0x66, 0x98, 0xdf, 0x8b, 0xc4, 0x61, 0xf6, 0xcb, 0x47, 0x87, 0xd5, + 0x19, 0x1e, 0x8b, 0x61, 0x7b, 0x6e, 0x8b, 0xda, 0x11, 0x2f, 0xd6, 0x86, 0xa9, 0x9f, 0x9a, 0xb1, + 0x8c, 0x23, 0xc3, 0x96, 0x3b, 0x11, 0x0d, 0x1e, 0x39, 0xdc, 0x25, 0x5d, 0xb0, 0xdc, 0xa7, 0xb4, + 0x6b, 0x3b, 0xac, 0xd4, 0xf6, 0x44, 0xb1, 0xce, 0xb2, 0x8f, 0x9a, 0xdc, 0x51, 0x58, 0x2e, 0x30, + 0xe3, 0xe0, 0x81, 0xf3, 0x44, 0x68, 0xbc, 0x78, 0xbe, 0xaa, 0xb0, 0xe4, 0xfe, 0x70, 0x6d, 0xe7, + 0x89, 0xd5, 0x4f, 0x42, 0xbe, 0x01, 0xe7, 0x84, 0xe0, 0x66, 0x42, 0x2c, 0xf0, 0x5b, 0xb2, 0xc5, + 0x05, 0xe4, 0xf5, 0xfa, 0xd1, 0x61, 0xf5, 0x82, 0x10, 0xfb, 0x76, 0x93, 0x63, 0x64, 0xb6, 0x3a, + 0x9b, 0x0b, 0xd9, 0x60, 0x1b, 0x59, 0xaa, 0x3b, 0x1e, 0xd0, 0x30, 0x74, 0x76, 0xa5, 0x76, 0xcc, + 0x6f, 0x94, 0x94, 0xce, 0xb4, 0xdb, 0xbc, 0xdc, 0x1a, 0x48, 0x49, 0xee, 0xc1, 0xe4, 0x36, 0xdd, + 0x51, 0xc7, 0x67, 0x34, 0x5e, 0xe2, 0x95, 0xc7, 0x74, 0x67, 0xf0, 0xe0, 0xa4, 0xe8, 0x88, 0x07, + 0xd3, 0xeb, 0x81, 0xff, 0xe4, 0x80, 0x99, 0x7a, 0xb4, 0x43, 0x03, 0x74, 0xc4, 0x1a, 0xc3, 0xe3, + 0xaa, 0xd9, 0x44, 0xb3, 0xd4, 0xcb, 0xeb, 0x9f, 0x3b, 0x3a, 0xac, 0xbe, 0xd8, 0x65, 0x60, 0xbb, + 0x25, 0xe0, 0x76, 0x2a, 0x30, 0xab, 0x9f, 0x2b, 0xf9, 0x19, 0x98, 0xb2, 0xfc, 0x5e, 0xe4, 0x75, + 0x76, 0x1b, 0x51, 0xe0, 0x44, 0x74, 0x97, 0x0b, 0xf2, 0xc4, 0xe3, 0x2b, 0x55, 0xca, 0x0f, 0xa6, + 0x03, 0x0e, 0xb4, 0x43, 0x01, 0xd5, 0x24, 0xa9, 0x4e, 0x60, 0xfe, 0x56, 0x0e, 0x66, 0xc5, 0x30, + 0x58, 0xb4, 0xe9, 0x07, 0xee, 0xb3, 0xbf, 0xec, 0x97, 0xb4, 0x65, 0xff, 0x72, 0xec, 0xa3, 0x94, + 0xd5, 0xc8, 0x63, 0x56, 0xfd, 0x3f, 0x33, 0xe0, 0xf2, 0x71, 0x44, 0xac, 0x77, 0x62, 0x1f, 0xbc, + 0x52, 0x9f, 0xaf, 0x5d, 0x17, 0x66, 0x70, 0x3c, 0xf1, 0xe0, 0x38, 0xbc, 0xe7, 0x87, 0x11, 0x9e, + 0xde, 0xe5, 0x34, 0x47, 0x82, 0xba, 0xef, 0xb7, 0x50, 0xce, 0xd7, 0xdf, 0x60, 0xe2, 0xfc, 0xcf, + 0x0f, 0xab, 0xc0, 0x40, 0x6b, 0x78, 0x19, 0xc9, 0xf6, 0x7c, 0x3e, 0x63, 0xf0, 0x5c, 0x3a, 0xb4, + 0xd1, 0xfb, 0x63, 0x9f, 0x1e, 0x84, 0x56, 0x16, 0x6b, 0x3c, 0xa1, 0xa9, 0xf5, 0xa2, 0xbd, 0xf5, + 0x80, 0x3e, 0xa4, 0x01, 0xed, 0x34, 0xe9, 0x73, 0x76, 0x42, 0xa3, 0x37, 0x6e, 0x28, 0xf3, 0xe4, + 0xff, 0x8d, 0xc2, 0xd9, 0x2c, 0x32, 0xd6, 0x2f, 0x8a, 0x46, 0x9c, 0x8e, 0xe2, 0xfd, 0x5b, 0x06, + 0x94, 0x1b, 0xb4, 0xe9, 0x77, 0xdc, 0x3b, 0x4e, 0x33, 0xf2, 0xa5, 0x4b, 0x86, 0xcd, 0x25, 0x1b, + 0x83, 0xdb, 0x0f, 0xb1, 0x40, 0x3b, 0x19, 0xf8, 0xf2, 0x70, 0x8a, 0x68, 0xd3, 0x47, 0xa7, 0xd5, + 0x88, 0xcd, 0xc9, 0xe4, 0x13, 0x78, 0xab, 0xa1, 0x7d, 0x94, 0xd4, 0x61, 0x62, 0xc1, 0xef, 0x74, + 0x28, 0xfb, 0xa1, 0xb8, 0x60, 0x5e, 0x3e, 0x3a, 0xac, 0xce, 0x36, 0x65, 0x41, 0xfa, 0x84, 0x40, + 0x27, 0x21, 0xb7, 0x20, 0xbf, 0x39, 0x7f, 0x47, 0x8c, 0x81, 0x74, 0x56, 0xdb, 0x9c, 0xbf, 0x83, + 0xb6, 0x2e, 0xd3, 0x1f, 0x26, 0x7a, 0xf3, 0x0f, 0xd5, 0x33, 0xd0, 0xcd, 0xf9, 0x3b, 0x64, 0x0d, + 0xa6, 0x2d, 0xfa, 0xb3, 0x3d, 0x2f, 0xa0, 0x62, 0x01, 0x3c, 0xb8, 0x53, 0xc3, 0xb1, 0x28, 0x72, + 0x39, 0x16, 0xf0, 0x42, 0xa9, 0xdb, 0xdb, 0xed, 0x87, 0x6a, 0xe4, 0x5a, 0x3f, 0x2d, 0xf9, 0x45, + 0x38, 0xb7, 0xe8, 0x85, 0xa2, 0xce, 0xfc, 0xf0, 0xd1, 0xc5, 0x7b, 0xc8, 0xd1, 0x01, 0xcb, 0xe1, + 0x27, 0x32, 0x97, 0xc3, 0xe7, 0xdc, 0x98, 0x89, 0xcd, 0x4f, 0x36, 0xdd, 0xb4, 0xef, 0x6a, 0xf6, + 0x77, 0xc8, 0xc7, 0x30, 0x89, 0xa7, 0x3d, 0x78, 0x1e, 0x8b, 0xee, 0xcc, 0x63, 0x03, 0xbe, 0xfc, + 0x85, 0xcc, 0x2f, 0x5f, 0xc2, 0xc3, 0x23, 0x1b, 0x4f, 0x75, 0xd1, 0xf5, 0x59, 0xb3, 0x11, 0x34, + 0xce, 0xe4, 0x43, 0x98, 0x12, 0x9b, 0xce, 0xda, 0xc3, 0x8d, 0x3d, 0xba, 0xe8, 0x1c, 0x08, 0x27, + 0x04, 0xd4, 0xff, 0xc4, 0x4e, 0x65, 0xfb, 0x0f, 0xed, 0x68, 0x8f, 0xda, 0xae, 0xa3, 0x89, 0xe7, + 0x14, 0x21, 0xf9, 0x79, 0x18, 0x5f, 0xf1, 0xf1, 0xe2, 0x09, 0x45, 0x4d, 0x09, 0xf9, 0x7c, 0x15, + 0x23, 0x57, 0x39, 0x38, 0xb5, 0x89, 0xfc, 0xe8, 0xb0, 0xfa, 0xce, 0x69, 0x67, 0xa1, 0xf2, 0x01, + 0x4b, 0xfd, 0x1a, 0x59, 0x80, 0xe2, 0x36, 0xdd, 0x61, 0xad, 0x4d, 0x47, 0x5d, 0x49, 0x30, 0x97, + 0x17, 0x8f, 0xc5, 0x2f, 0xf5, 0x56, 0x47, 0x62, 0x98, 0xff, 0xda, 0xc0, 0x19, 0x48, 0xae, 0xa3, + 0x23, 0x58, 0xec, 0x0d, 0x8e, 0x96, 0xa5, 0xd3, 0xed, 0xea, 0xfe, 0xdc, 0x1c, 0x85, 0x99, 0xa1, + 0x77, 0x9c, 0x26, 0x8d, 0xe4, 0x79, 0x25, 0x22, 0x3f, 0x44, 0x88, 0x6a, 0x86, 0x72, 0x1c, 0xf2, + 0x15, 0x38, 0xbb, 0x48, 0x1f, 0x79, 0x4d, 0x5a, 0x8b, 0x22, 0x1a, 0xf2, 0xd6, 0x2e, 0xd4, 0xf8, + 0xc5, 0x5e, 0xa9, 0xfe, 0xca, 0xd1, 0x61, 0xf5, 0x8a, 0x8b, 0xe5, 0xb6, 0x93, 0x20, 0xd8, 0x4d, + 0x47, 0xe5, 0x95, 0xc9, 0xc1, 0xfc, 0x5f, 0x46, 0xd2, 0x03, 0xe4, 0x75, 0x28, 0x58, 0xeb, 0x71, + 0xfd, 0xf9, 0x9d, 0x5d, 0xaa, 0xfa, 0x88, 0x40, 0xbe, 0x06, 0xe7, 0x14, 0x3e, 0x38, 0x39, 0xa8, + 0xcb, 0x2a, 0xc4, 0x1b, 0xf3, 0x2a, 0x5e, 0xd2, 0x28, 0x35, 0x71, 0x38, 0x46, 0xaa, 0x46, 0xd9, + 0x3c, 0x58, 0x63, 0x95, 0x82, 0x45, 0xda, 0xf1, 0x38, 0x6f, 0xa5, 0xb1, 0x2a, 0x6f, 0x17, 0x11, + 0xd2, 0x8d, 0xcd, 0xe2, 0xf0, 0x61, 0xa1, 0x58, 0xa8, 0x8c, 0x98, 0x7f, 0x65, 0x28, 0x29, 0x00, + 0x9e, 0xd1, 0xdd, 0xe3, 0xb6, 0xb6, 0x7b, 0x9c, 0x15, 0xa4, 0x71, 0xab, 0x58, 0x59, 0xe6, 0x8e, + 0x3f, 0x05, 0x13, 0x1a, 0x12, 0xba, 0xbc, 0x6e, 0x86, 0x34, 0xe0, 0xe7, 0x83, 0xcf, 0x97, 0xcb, + 0x6b, 0xdc, 0xae, 0xa1, 0x3c, 0x19, 0xff, 0xc2, 0x80, 0xa9, 0x14, 0x05, 0xeb, 0x0d, 0x06, 0x52, + 0x7b, 0xa3, 0x17, 0xd2, 0xc0, 0x42, 0x28, 0x77, 0x90, 0x5b, 0xd1, 0x1d, 0xe4, 0x5a, 0x16, 0x83, + 0x91, 0x2f, 0xc3, 0xc8, 0x26, 0x6a, 0xf3, 0xba, 0x8f, 0x45, 0xcc, 0x1f, 0x0b, 0xf9, 0x0a, 0xeb, + 0xb1, 0x7f, 0x55, 0x01, 0x81, 0x65, 0xa4, 0x01, 0x63, 0x0b, 0x01, 0xc5, 0x60, 0xff, 0xc2, 0xf0, + 0x97, 0x61, 0x4d, 0x4e, 0x92, 0xbe, 0x0c, 0x13, 0x9c, 0xcc, 0xdf, 0xcc, 0x01, 0x49, 0xda, 0x48, + 0x9b, 0x01, 0x8d, 0xc2, 0x67, 0x76, 0xd0, 0x3f, 0xd0, 0x06, 0xfd, 0xc5, 0xbe, 0x41, 0xe7, 0xcd, + 0x1b, 0x6a, 0xec, 0xff, 0xd8, 0x80, 0xf3, 0xd9, 0x84, 0xe4, 0x65, 0x18, 0x5d, 0xdb, 0x58, 0x97, + 0x6e, 0x3a, 0xa2, 0x29, 0x7e, 0x17, 0xb5, 0x54, 0x4b, 0x14, 0x91, 0x37, 0x61, 0xf4, 0x23, 0x6b, + 0x81, 0x6d, 0x5f, 0x4a, 0xd4, 0xc9, 0xcf, 0x06, 0x76, 0x53, 0x37, 0x7f, 0x04, 0x92, 0x3a, 0xb6, + 0xf9, 0xa7, 0x36, 0xb6, 0xdf, 0xca, 0xc1, 0x54, 0xad, 0xd9, 0xa4, 0x61, 0xc8, 0x94, 0x13, 0x1a, + 0x46, 0xcf, 0xec, 0xc0, 0x66, 0x3b, 0xe0, 0x68, 0x6d, 0x1b, 0x6a, 0x54, 0xff, 0xc4, 0x80, 0x73, + 0x92, 0xea, 0x91, 0x47, 0x1f, 0x6f, 0xec, 0x05, 0x34, 0xdc, 0xf3, 0x5b, 0xee, 0xb0, 0xf1, 0x53, + 0xb8, 0x4b, 0x7b, 0xad, 0x88, 0x06, 0xea, 0x61, 0xf1, 0x43, 0x84, 0x68, 0xbb, 0x34, 0x42, 0xc8, + 0x1c, 0x8c, 0xd5, 0xba, 0xdd, 0xc0, 0x7f, 0xc4, 0x97, 0xfd, 0x84, 0xb8, 0x1b, 0xe4, 0x20, 0xed, + 0x2e, 0x91, 0x83, 0x58, 0x35, 0x16, 0x69, 0x87, 0x7b, 0x17, 0x4f, 0xf0, 0x6a, 0xb8, 0xb4, 0xa3, + 0x6a, 0x4b, 0x58, 0x6e, 0x7e, 0xb3, 0x00, 0x65, 0xb5, 0x21, 0xc4, 0x84, 0x51, 0xee, 0x2a, 0xa2, + 0x5e, 0xd9, 0x3b, 0x08, 0xb1, 0x44, 0x49, 0xe2, 0x81, 0x93, 0x3b, 0xd1, 0x03, 0x67, 0x1b, 0x26, + 0xd6, 0x03, 0xbf, 0xeb, 0x87, 0xd4, 0xe5, 0xf9, 0x5a, 0xb8, 0xd4, 0x9a, 0x89, 0xdd, 0x52, 0x79, + 0x9f, 0xb3, 0x22, 0xae, 0x9a, 0x77, 0x05, 0xb6, 0x9d, 0xce, 0xe6, 0xa2, 0xf3, 0xe1, 0x87, 0xed, + 0x4e, 0x28, 0x5c, 0xf7, 0xe3, 0xc3, 0x76, 0x06, 0xd1, 0x0f, 0xdb, 0x19, 0x44, 0x5d, 0x16, 0x23, + 0x4f, 0x6b, 0x59, 0x90, 0xdf, 0x34, 0x60, 0xbc, 0xd6, 0xe9, 0x08, 0x0f, 0x1c, 0x19, 0xf4, 0x7c, + 0x2e, 0x39, 0x70, 0xe7, 0x2e, 0x9a, 0xfc, 0xbc, 0xfd, 0xeb, 0xe2, 0xbc, 0xfd, 0x9d, 0x4f, 0x74, + 0xde, 0xbe, 0x11, 0x38, 0x5e, 0x14, 0xe2, 0xc5, 0x6a, 0xf2, 0x41, 0xd5, 0x0d, 0x57, 0xa9, 0x07, + 0x79, 0x07, 0x2a, 0xf1, 0x7c, 0x5c, 0xee, 0xb8, 0xf4, 0x09, 0xe5, 0x0e, 0x4b, 0x13, 0x3c, 0x32, + 0x4f, 0xbb, 0x48, 0x48, 0x23, 0x9a, 0xdf, 0x32, 0xe0, 0xbc, 0x3a, 0x21, 0x1a, 0xbd, 0x9d, 0xb6, + 0x87, 0xa6, 0x08, 0xb9, 0x01, 0x25, 0x31, 0x5e, 0xb1, 0x22, 0xd7, 0x9f, 0xe4, 0x27, 0x41, 0x21, + 0x4b, 0x6c, 0x88, 0x18, 0x0f, 0x61, 0xb7, 0xcf, 0xa4, 0x96, 0x1b, 0x2b, 0xaa, 0xcf, 0x8a, 0xce, + 0xae, 0x04, 0xf8, 0x5b, 0x1f, 0x3b, 0x06, 0x31, 0xdf, 0x87, 0x69, 0xbd, 0x96, 0x0d, 0x8a, 0xe1, + 0x60, 0xb2, 0x69, 0x46, 0x76, 0xd3, 0x64, 0xb9, 0xb9, 0x0d, 0xa4, 0x8f, 0x3e, 0xc4, 0x4b, 0x23, + 0x1a, 0xc9, 0x4b, 0x4d, 0x79, 0xf4, 0xd4, 0x87, 0x18, 0xa7, 0xbb, 0x1a, 0x57, 0xbb, 0x1b, 0x49, + 0xcd, 0xbf, 0x2e, 0xc1, 0x4c, 0x86, 0xe8, 0x38, 0x61, 0x6b, 0xaf, 0xea, 0x8b, 0xa7, 0x14, 0xdf, + 0xce, 0xcb, 0x25, 0xf3, 0xbe, 0x4c, 0x6d, 0x74, 0xcc, 0x52, 0x39, 0x2e, 0xdf, 0xd1, 0x67, 0xb1, + 0xbd, 0xab, 0x0e, 0x34, 0x23, 0x4f, 0xcd, 0x81, 0xa6, 0x0e, 0x13, 0xa2, 0x55, 0x62, 0x29, 0x8f, + 0x26, 0x26, 0x7a, 0xc0, 0x0b, 0xec, 0xbe, 0x25, 0xad, 0x93, 0x70, 0x1e, 0xa1, 0xdf, 0x7a, 0x44, + 0x05, 0x8f, 0x31, 0x95, 0x07, 0x16, 0x64, 0xf2, 0x50, 0x48, 0xc8, 0x3f, 0x35, 0x80, 0x08, 0x88, + 0xba, 0x9e, 0x8b, 0xc7, 0xad, 0x67, 0xf7, 0xe9, 0xac, 0xe7, 0x17, 0x65, 0x1d, 0xb3, 0xd7, 0x75, + 0x46, 0xb5, 0xc8, 0x3f, 0x36, 0x60, 0x9a, 0x7b, 0x71, 0xa8, 0x95, 0x2d, 0x1d, 0x57, 0xd9, 0xe6, + 0xd3, 0xa9, 0xec, 0xe5, 0x10, 0x3f, 0x3b, 0xa0, 0xae, 0xfd, 0x95, 0x22, 0x3f, 0x05, 0x10, 0xaf, + 0x28, 0xe9, 0x2d, 0x78, 0x39, 0x43, 0x0a, 0xc4, 0x48, 0x49, 0xc0, 0x63, 0x14, 0xd3, 0xa9, 0xfe, + 0x35, 0x09, 0x37, 0xf2, 0x8b, 0x70, 0x96, 0xad, 0x97, 0x18, 0x22, 0x7c, 0xce, 0x66, 0xc7, 0xf1, + 0x2b, 0x5f, 0x1c, 0xbc, 0xb5, 0xdf, 0xc8, 0x22, 0xe3, 0x31, 0x1b, 0x49, 0xf8, 0x7b, 0xd4, 0x56, + 0x4d, 0xbe, 0x2c, 0x0a, 0x74, 0x2e, 0xc5, 0xda, 0x87, 0xb3, 0x65, 0xfc, 0x66, 0xa6, 0x7c, 0xbb, + 0x28, 0xd7, 0x02, 0x97, 0x6f, 0xa1, 0x1e, 0x74, 0x81, 0x20, 0xf2, 0x11, 0x90, 0x46, 0x6f, 0x77, + 0x97, 0x86, 0x11, 0x75, 0x39, 0x8c, 0x06, 0xe1, 0xec, 0x04, 0xca, 0x07, 0x3c, 0x32, 0x0a, 0x65, + 0xa9, 0x1d, 0xc8, 0x62, 0x75, 0x92, 0xf4, 0x13, 0x5f, 0xda, 0x81, 0x8b, 0x03, 0x9b, 0x99, 0x11, + 0x50, 0x31, 0xa7, 0x07, 0x54, 0x5c, 0x1c, 0x24, 0x0e, 0x43, 0x35, 0xa8, 0xe2, 0xf7, 0x8c, 0x94, + 0xfc, 0x13, 0xca, 0x0a, 0xcf, 0x02, 0x37, 0x68, 0x83, 0xc8, 0x61, 0x60, 0x3c, 0x97, 0x90, 0xb9, + 0x44, 0x49, 0x62, 0x12, 0x52, 0x95, 0xb0, 0x28, 0x2b, 0x3f, 0xa5, 0x28, 0x34, 0xff, 0x85, 0x01, + 0x84, 0xd7, 0x70, 0xc1, 0xe9, 0x3a, 0x3b, 0x5e, 0xcb, 0x8b, 0x3c, 0x1a, 0x92, 0xfb, 0x50, 0x11, + 0x2c, 0x9c, 0x9d, 0x16, 0x55, 0x7d, 0xa5, 0xc4, 0x65, 0x6a, 0x5c, 0x66, 0xa7, 0xd5, 0x9a, 0x3e, + 0xc2, 0x01, 0x83, 0x97, 0xfb, 0x14, 0x83, 0x67, 0xfe, 0xd0, 0x80, 0x8b, 0xfd, 0xd5, 0x16, 0x5f, + 0x8e, 0x3b, 0xcf, 0x38, 0xa1, 0xf3, 0xb2, 0x5a, 0x99, 0xc3, 0x63, 0xc8, 0xa7, 0xd6, 0xca, 0x7c, + 0x72, 0xaa, 0x79, 0xfa, 0x56, 0xfe, 0x6a, 0x0e, 0xca, 0xeb, 0xad, 0xde, 0xae, 0xd7, 0x59, 0x74, + 0x22, 0xe7, 0x99, 0x35, 0x29, 0xde, 0xd6, 0x4c, 0x8a, 0xd8, 0x3b, 0x2a, 0x6e, 0xd8, 0x70, 0x19, + 0xb9, 0x0c, 0x98, 0x4a, 0x48, 0xf8, 0x2a, 0xbd, 0x07, 0x05, 0xf6, 0x43, 0x68, 0x28, 0x57, 0xfa, + 0x18, 0x23, 0xd6, 0x8d, 0xf8, 0x3f, 0xa1, 0xe4, 0xeb, 0x79, 0xd0, 0x90, 0xc3, 0xa5, 0x9f, 0xe0, + 0x69, 0x8c, 0x4e, 0x9f, 0x72, 0xf1, 0x9f, 0x1b, 0x50, 0x49, 0xb7, 0x84, 0xdc, 0x87, 0x31, 0xc6, + 0xc9, 0x8b, 0x53, 0x22, 0xbd, 0x32, 0xa0, 0xcd, 0x37, 0x04, 0x1a, 0xaf, 0x1e, 0x76, 0x3e, 0xe5, + 0x10, 0x4b, 0x72, 0xb8, 0x64, 0x41, 0x59, 0xc5, 0xca, 0xa8, 0xdd, 0x1b, 0xba, 0x68, 0x3a, 0x9f, + 0xdd, 0x0f, 0x6a, 0xad, 0x7f, 0x57, 0xab, 0xb5, 0x10, 0x4a, 0xc3, 0xe6, 0xb6, 0xc3, 0xf0, 0x30, + 0x9e, 0xc1, 0x43, 0x9d, 0x67, 0x32, 0xd9, 0x87, 0x1e, 0x1e, 0xc6, 0x61, 0xcc, 0x16, 0xe1, 0xdf, + 0x13, 0xf3, 0x0c, 0x6d, 0x91, 0x2e, 0x42, 0x54, 0x7d, 0x96, 0xe3, 0x98, 0x7f, 0x2f, 0x0f, 0xe7, + 0x93, 0xea, 0xf1, 0x4c, 0x7f, 0xeb, 0x4e, 0xe0, 0xb4, 0xc3, 0x13, 0x56, 0xc0, 0xd5, 0xbe, 0xaa, + 0x61, 0xf8, 0xb3, 0xac, 0x9a, 0x52, 0x21, 0x33, 0x55, 0x21, 0x34, 0xe2, 0x78, 0x85, 0x64, 0x35, + 0xc8, 0x7d, 0xc8, 0x37, 0x68, 0x24, 0x82, 0x24, 0x5f, 0xeb, 0xeb, 0x55, 0xb5, 0x5e, 0x37, 0x1a, + 0x34, 0xe2, 0x83, 0xc8, 0xfd, 0xcc, 0xa9, 0xe6, 0xf7, 0xcd, 0xd4, 0xf1, 0x6d, 0x18, 0x5d, 0x7a, + 0xd2, 0xa5, 0xcd, 0x48, 0xc4, 0x46, 0x5e, 0x3b, 0x9e, 0x1f, 0xc7, 0x55, 0x22, 0x30, 0x29, 0x02, + 0xd4, 0xce, 0xe2, 0x28, 0x97, 0x6e, 0x43, 0x51, 0x7e, 0xfc, 0x54, 0x91, 0x84, 0x6f, 0xc3, 0xb8, + 0xf2, 0x91, 0x53, 0x4d, 0xfa, 0xbf, 0x36, 0x60, 0x94, 0x09, 0xbd, 0xad, 0xb7, 0x9e, 0x51, 0x89, + 0x74, 0x4b, 0x93, 0x48, 0xd3, 0x4a, 0xc8, 0x0b, 0xae, 0xcb, 0xb7, 0x4e, 0x90, 0x45, 0x87, 0x06, + 0x40, 0x82, 0x4c, 0xee, 0xc2, 0x18, 0xbf, 0xc8, 0x91, 0x69, 0x34, 0xd5, 0x18, 0x1a, 0x51, 0x92, + 0x68, 0x39, 0x7e, 0x37, 0xad, 0x16, 0x4a, 0x6a, 0xb2, 0x98, 0xf8, 0x19, 0xab, 0x41, 0x9b, 0x8c, + 0xcd, 0x82, 0xdf, 0xe1, 0x31, 0x15, 0xa1, 0x92, 0x6e, 0x2a, 0xdb, 0xe1, 0xb8, 0x26, 0x0e, 0x36, + 0xf2, 0xc7, 0x31, 0x39, 0x2f, 0x98, 0x64, 0x9f, 0x79, 0x7c, 0x7b, 0x9c, 0x47, 0x29, 0xc8, 0x8a, + 0xbd, 0x07, 0xe5, 0x3b, 0x7e, 0xf0, 0xd8, 0x09, 0xdc, 0xda, 0x2e, 0x15, 0x1e, 0xe2, 0x45, 0x74, + 0xf3, 0x9e, 0x78, 0xc8, 0xe1, 0xb6, 0xc3, 0x0a, 0x7e, 0x74, 0x58, 0x2d, 0xd4, 0x7d, 0xbf, 0x65, + 0x69, 0xe8, 0x64, 0x0d, 0x26, 0x1e, 0x38, 0x4f, 0xc4, 0x7d, 0xdd, 0xc6, 0xc6, 0x8a, 0xf0, 0x33, + 0xb9, 0x76, 0x74, 0x58, 0xbd, 0xd8, 0x76, 0x9e, 0xc4, 0xf7, 0x7c, 0x83, 0x5d, 0xa1, 0x75, 0x7a, + 0xe2, 0xc1, 0xe4, 0xba, 0x1f, 0x44, 0xe2, 0x23, 0x4c, 0xa7, 0xcd, 0x0f, 0xb8, 0x6e, 0x9b, 0xcb, + 0xbc, 0x6e, 0xbb, 0xc8, 0x14, 0x79, 0xfb, 0x61, 0x4c, 0xae, 0x85, 0xd6, 0x69, 0x8c, 0xc9, 0x7b, + 0x30, 0xbd, 0x40, 0x83, 0xc8, 0x7b, 0xe8, 0x35, 0x9d, 0x88, 0xde, 0xf1, 0x83, 0xb6, 0x13, 0x89, + 0x03, 0x15, 0x34, 0xa8, 0x9b, 0x94, 0x73, 0x6a, 0x3b, 0x91, 0xd5, 0x8f, 0x49, 0xbe, 0x96, 0xe5, + 0xb9, 0x33, 0x82, 0xcd, 0x7f, 0x93, 0x29, 0x05, 0x19, 0x9e, 0x3b, 0x03, 0xba, 0x20, 0xc3, 0x87, + 0x67, 0xf7, 0xb8, 0x6b, 0xcf, 0x62, 0xfd, 0xa6, 0xb8, 0x82, 0x3d, 0xf9, 0x5a, 0x33, 0x1e, 0xb7, + 0x01, 0xd7, 0x9b, 0xf3, 0x90, 0xaf, 0xaf, 0xdf, 0xc1, 0x23, 0x12, 0x71, 0xcd, 0x48, 0x3b, 0x7b, + 0x4e, 0xa7, 0x89, 0xba, 0x8c, 0xf0, 0x5d, 0x50, 0x05, 0x5e, 0x7d, 0xfd, 0x0e, 0x71, 0x60, 0x66, + 0x9d, 0x06, 0x6d, 0x2f, 0xfa, 0xca, 0xcd, 0x9b, 0xca, 0x40, 0x15, 0xb1, 0x6a, 0x73, 0xa2, 0x6a, + 0xd5, 0x2e, 0xa2, 0xd8, 0x4f, 0x6e, 0xde, 0xcc, 0x1c, 0x8e, 0xb8, 0x62, 0x59, 0xbc, 0xc8, 0x12, + 0x4c, 0x3e, 0x70, 0x9e, 0x88, 0x0b, 0xe9, 0xd8, 0xc6, 0xcb, 0xa3, 0x67, 0x3c, 0x4e, 0xac, 0x66, + 0x52, 0xa4, 0x0e, 0xb1, 0x4e, 0x44, 0xde, 0x85, 0xf1, 0x64, 0x7a, 0x85, 0x78, 0x15, 0x99, 0xe7, + 0x2e, 0x91, 0xca, 0xe4, 0xd4, 0xce, 0x92, 0x14, 0x74, 0xb2, 0x19, 0x9b, 0xe8, 0x5c, 0x21, 0x45, + 0x47, 0xc1, 0x52, 0x7d, 0x4e, 0x35, 0xd1, 0x1d, 0x2c, 0xd1, 0x9a, 0x35, 0x15, 0xab, 0xe8, 0xdc, + 0x53, 0xc6, 0xd2, 0xb9, 0x28, 0x96, 0xff, 0x7a, 0xe0, 0xb7, 0xbb, 0x11, 0x7a, 0x0c, 0xa6, 0x2c, + 0xff, 0x2e, 0x96, 0x64, 0x58, 0xfe, 0x9c, 0x24, 0xfb, 0x9e, 0x7d, 0xe2, 0x53, 0xdc, 0xb3, 0x53, + 0x28, 0xac, 0xf8, 0xcd, 0x7d, 0x74, 0x11, 0x2c, 0xd5, 0x3f, 0x62, 0xf2, 0xa3, 0xe5, 0x37, 0xf7, + 0x9f, 0xde, 0xfd, 0x30, 0xb2, 0x27, 0xab, 0xac, 0xed, 0x6c, 0x5a, 0x89, 0x4f, 0xcf, 0x4e, 0x69, + 0x37, 0x6d, 0x5a, 0x19, 0x57, 0x54, 0xf8, 0x2c, 0x94, 0x0d, 0xb1, 0x74, 0x72, 0x42, 0xa1, 0xb2, + 0x48, 0xc3, 0xfd, 0xc8, 0xef, 0x2e, 0xb4, 0xbc, 0xee, 0x8e, 0xef, 0x04, 0xee, 0x6c, 0x65, 0x80, + 0xc0, 0x78, 0x3d, 0x53, 0x60, 0x4c, 0xbb, 0x9c, 0xde, 0x6e, 0x4a, 0x06, 0x56, 0x1f, 0x4b, 0xf2, + 0x35, 0x98, 0x64, 0xab, 0x65, 0xe9, 0x49, 0x44, 0x3b, 0x7c, 0x2a, 0x4d, 0xe3, 0x56, 0x7f, 0x56, + 0x09, 0x32, 0x8c, 0x0b, 0xf9, 0x24, 0x45, 0xe9, 0x41, 0x63, 0x02, 0x75, 0x92, 0xea, 0xac, 0xcc, + 0x9f, 0x4a, 0xf5, 0x09, 0x59, 0x86, 0x31, 0x51, 0x03, 0xb1, 0xeb, 0xf4, 0xb7, 0xe5, 0xc5, 0xcc, + 0xb6, 0x8c, 0x89, 0xb6, 0x58, 0x92, 0xde, 0xfc, 0xb7, 0x06, 0x4c, 0x68, 0x9f, 0x23, 0xb7, 0x15, + 0xf7, 0x95, 0xc4, 0xed, 0x4c, 0xc3, 0xc9, 0x4c, 0x4f, 0x7f, 0x5b, 0xf8, 0x2c, 0xe5, 0x06, 0xd3, + 0x65, 0x66, 0x0e, 0x93, 0x41, 0xff, 0xf9, 0xe3, 0x83, 0xfe, 0x0b, 0x03, 0x82, 0xfe, 0xbf, 0x3d, + 0x01, 0x93, 0xfa, 0x06, 0xc7, 0x34, 0xce, 0x15, 0x7f, 0xd7, 0xeb, 0x48, 0xbb, 0x95, 0xa7, 0xb1, + 0x40, 0x88, 0x96, 0xeb, 0x1d, 0x21, 0xe4, 0x55, 0x80, 0xf8, 0x6a, 0x56, 0x9a, 0xa6, 0x22, 0x33, + 0xbd, 0x52, 0x40, 0x7e, 0x1a, 0x60, 0xd5, 0x77, 0x69, 0x9c, 0x09, 0xe5, 0x98, 0x03, 0xa5, 0xd7, + 0xc5, 0x81, 0x92, 0xc8, 0x26, 0x7f, 0x74, 0x58, 0x3d, 0xd7, 0xf1, 0x5d, 0xda, 0x9f, 0x02, 0x45, + 0xe1, 0x48, 0xbe, 0x04, 0x23, 0x56, 0xaf, 0x45, 0x65, 0x62, 0x8e, 0x71, 0x39, 0xe1, 0x7b, 0x2d, + 0x25, 0xcb, 0x64, 0xd0, 0x4b, 0xdf, 0x23, 0x30, 0x00, 0xf9, 0x00, 0xe0, 0x7e, 0x6f, 0x87, 0xde, + 0x0d, 0xfc, 0x5e, 0x57, 0x46, 0xfe, 0xa2, 0x19, 0xbb, 0x1f, 0xa7, 0x71, 0xb2, 0x77, 0xb1, 0x50, + 0xfd, 0x78, 0x42, 0x42, 0xd6, 0x60, 0x4c, 0x88, 0x0f, 0x71, 0x4e, 0xff, 0x52, 0xd6, 0x09, 0x91, + 0xa2, 0x43, 0x88, 0x4c, 0x19, 0x08, 0xd6, 0x0f, 0x6d, 0xb8, 0x19, 0xfe, 0x2e, 0x94, 0x18, 0x7b, + 0x66, 0x6a, 0x87, 0x62, 0xef, 0x40, 0xff, 0x41, 0xa5, 0x42, 0xcc, 0x2c, 0xd7, 0xf2, 0x75, 0xc5, + 0x04, 0xe4, 0x6b, 0x98, 0xdb, 0x46, 0x74, 0xf5, 0xb1, 0x07, 0x8d, 0xaf, 0xf5, 0x75, 0xf5, 0x59, + 0xa7, 0xdb, 0xcd, 0x48, 0x06, 0x16, 0xf3, 0x23, 0xbb, 0x71, 0x8c, 0x4d, 0x9c, 0x6a, 0xf8, 0x98, + 0x0f, 0x5c, 0xef, 0xfb, 0xc0, 0xac, 0x0c, 0x1b, 0xe9, 0xcf, 0x68, 0xa3, 0xf1, 0x25, 0x5d, 0xa8, + 0x24, 0x69, 0xb4, 0xc4, 0xb7, 0xe0, 0xb8, 0x6f, 0xbd, 0xd9, 0xf7, 0x2d, 0x75, 0x00, 0xfb, 0x3e, + 0xd7, 0xc7, 0x9d, 0xb8, 0x49, 0x5a, 0x58, 0xf1, 0xbd, 0xf1, 0xe3, 0xbe, 0xf7, 0x6a, 0xdf, 0xf7, + 0x66, 0xdc, 0x9d, 0xfe, 0xef, 0xa4, 0x78, 0x92, 0x77, 0x61, 0x42, 0x42, 0x70, 0x7d, 0xe0, 0x01, + 0x9f, 0xd0, 0xef, 0xdd, 0x1d, 0x74, 0x1a, 0xd3, 0xd3, 0xb9, 0xa8, 0xc8, 0x2a, 0x35, 0x9f, 0x1d, + 0x13, 0x1a, 0x75, 0x7a, 0x56, 0xe8, 0xc8, 0xe4, 0xab, 0x30, 0xbe, 0xdc, 0x66, 0x0d, 0xf1, 0x3b, + 0x4e, 0x44, 0x71, 0x33, 0x4a, 0x0e, 0x4d, 0x95, 0x12, 0x65, 0xaa, 0xf2, 0x1c, 0x8f, 0x49, 0x91, + 0xba, 0x99, 0x2b, 0x14, 0xac, 0xf3, 0xf8, 0xf1, 0x8b, 0x98, 0xc3, 0xa1, 0xd8, 0x7a, 0x5e, 0xcc, + 0x38, 0xb8, 0x54, 0xd8, 0xa3, 0x2c, 0xe7, 0xa7, 0x3a, 0xb6, 0x58, 0x10, 0x5a, 0xe7, 0xe9, 0x3c, + 0xc9, 0x7b, 0x30, 0x2e, 0x22, 0x1a, 0x6b, 0xd6, 0x6a, 0x38, 0x5b, 0xc1, 0xc6, 0x63, 0x2e, 0x36, + 0x19, 0xfc, 0x68, 0x3b, 0x41, 0xea, 0xf6, 0x2a, 0xc1, 0x27, 0x5f, 0x81, 0xb3, 0xdb, 0x5e, 0xc7, + 0xf5, 0x1f, 0x87, 0x42, 0x80, 0x0b, 0x41, 0x37, 0x9d, 0xf8, 0xe8, 0x3c, 0xe6, 0xe5, 0xb6, 0xdc, + 0xb6, 0xfa, 0x04, 0x5f, 0x26, 0x07, 0xf2, 0x0b, 0x7d, 0x9c, 0xf9, 0x0c, 0x22, 0xc7, 0xcd, 0xa0, + 0xf9, 0xbe, 0x19, 0xd4, 0xff, 0xf9, 0xf4, 0x74, 0xca, 0xfc, 0x0c, 0xf1, 0x81, 0xe8, 0x3a, 0xc7, + 0x87, 0xbe, 0xd7, 0x99, 0x9d, 0xd1, 0x1e, 0xf2, 0x88, 0x5d, 0x66, 0x11, 0x6f, 0xdd, 0x6f, 0x79, + 0xcd, 0x83, 0xba, 0x79, 0x74, 0x58, 0x7d, 0x29, 0xad, 0xcd, 0x7c, 0xec, 0x6b, 0x87, 0x0b, 0x19, + 0xac, 0xc9, 0x57, 0xa1, 0xcc, 0xfe, 0xc6, 0xaa, 0xdf, 0x59, 0xed, 0xaa, 0x4b, 0xc1, 0x14, 0xdf, + 0xc1, 0x31, 0xc2, 0x90, 0xcb, 0x0c, 0xad, 0x50, 0x63, 0x65, 0xfe, 0xd0, 0x80, 0xb3, 0x59, 0x75, + 0x3d, 0x21, 0xbf, 0x8d, 0x99, 0xba, 0xf4, 0xc6, 0x73, 0x09, 0x7e, 0xe9, 0x1d, 0x5f, 0x75, 0x57, + 0x61, 0x84, 0xd9, 0xca, 0xd2, 0x29, 0x0b, 0xb7, 0x43, 0x66, 0x4f, 0x87, 0x16, 0x87, 0x33, 0x04, + 0x74, 0xa6, 0xc7, 0xfd, 0x72, 0x84, 0x23, 0xa0, 0xc7, 0xbd, 0xc5, 0xe1, 0x0c, 0x81, 0x6d, 0xbb, + 0x72, 0x9b, 0x40, 0x04, 0xb6, 0x1b, 0x87, 0x16, 0x87, 0x93, 0xd7, 0x60, 0x6c, 0xad, 0xb3, 0x42, + 0x9d, 0x47, 0x54, 0xdc, 0x38, 0xe1, 0x39, 0x8a, 0xdf, 0xb1, 0x5b, 0x0c, 0x66, 0xc9, 0x42, 0xf3, + 0xbb, 0x06, 0x4c, 0xf7, 0x75, 0xd3, 0xc9, 0x29, 0x7c, 0x8e, 0xbf, 0xde, 0x1b, 0xa6, 0x7d, 0xbc, + 0xfa, 0x85, 0xec, 0xea, 0x9b, 0x7f, 0x99, 0x87, 0x0b, 0x03, 0x76, 0xad, 0xe4, 0x6a, 0xde, 0x38, + 0xf1, 0x6a, 0xfe, 0xeb, 0x6c, 0x97, 0x70, 0xbc, 0x76, 0xb8, 0xe1, 0x27, 0x35, 0x4e, 0x6e, 0x31, + 0xb0, 0x4c, 0xe6, 0xc8, 0x90, 0xf9, 0x1c, 0x2e, 0x36, 0x91, 0xc2, 0x8e, 0xfc, 0xbe, 0x33, 0x63, + 0x9d, 0x59, 0xdf, 0xe5, 0x78, 0xfe, 0xc7, 0xe4, 0x72, 0x5c, 0xbf, 0x92, 0x2a, 0x3c, 0xd5, 0x2b, + 0xa9, 0xec, 0x43, 0xf2, 0x91, 0x4f, 0x73, 0x15, 0xf0, 0xef, 0x53, 0xd7, 0xf1, 0x3f, 0x8e, 0x43, + 0x7d, 0x0d, 0x46, 0xb6, 0xf7, 0x68, 0x20, 0xf5, 0x5b, 0xac, 0xc8, 0x63, 0x06, 0x50, 0x2b, 0x82, + 0x18, 0xe6, 0xcf, 0x43, 0x59, 0xfd, 0x18, 0xae, 0x65, 0xf6, 0x5b, 0x2c, 0x26, 0xbe, 0x96, 0x19, + 0xc0, 0xe2, 0xf0, 0x13, 0x33, 0x62, 0x25, 0xbd, 0x90, 0x3f, 0xa9, 0x17, 0xcc, 0x7f, 0x67, 0x40, + 0x01, 0x13, 0x02, 0xbc, 0x05, 0x25, 0x79, 0x54, 0xaa, 0x06, 0xc9, 0xcf, 0xc8, 0x93, 0xd4, 0x50, + 0xf7, 0x67, 0x10, 0x40, 0xf6, 0xa9, 0x2d, 0x1a, 0xec, 0x68, 0x6e, 0x2f, 0x8f, 0x18, 0x40, 0xfd, + 0x14, 0x62, 0x9c, 0xa2, 0x4b, 0xd0, 0xb5, 0x47, 0xd8, 0xf7, 0x7c, 0xc1, 0x73, 0xd7, 0x9e, 0x3e, + 0xbb, 0x5e, 0x62, 0x99, 0xbf, 0x6d, 0xc0, 0xb9, 0x4c, 0x3d, 0x80, 0x7d, 0x95, 0x2b, 0x1c, 0xca, + 0x8c, 0x48, 0x6b, 0x1b, 0x1c, 0xe3, 0x34, 0x2e, 0x3c, 0xa7, 0x18, 0xde, 0xcf, 0x41, 0x29, 0xb6, + 0xcf, 0xc8, 0x59, 0x39, 0x74, 0x78, 0x9e, 0x26, 0x8d, 0x99, 0xbf, 0x36, 0x60, 0x94, 0x55, 0xe1, + 0x99, 0x8d, 0xae, 0xc8, 0x3e, 0x5d, 0x65, 0x4d, 0x1a, 0x2a, 0xa6, 0xe2, 0x0f, 0x46, 0x01, 0x12, + 0x64, 0xb2, 0x03, 0x93, 0x6b, 0xcb, 0x8b, 0x0b, 0xcb, 0x2e, 0xed, 0x44, 0x78, 0xcb, 0x97, 0x8a, + 0xb2, 0x67, 0x86, 0x65, 0xd0, 0x71, 0x5a, 0x02, 0xe1, 0x20, 0x59, 0x9e, 0xbe, 0xe7, 0x36, 0x6d, + 0x2f, 0xa6, 0x53, 0x15, 0x32, 0x9d, 0x23, 0xfb, 0x46, 0xa3, 0xf6, 0x60, 0x45, 0xf9, 0x46, 0x6e, + 0xc8, 0x6f, 0x84, 0x4e, 0xbb, 0x35, 0xe0, 0x1b, 0x3a, 0x47, 0xb2, 0x07, 0x95, 0xbb, 0x28, 0xbb, + 0x95, 0xaf, 0xe4, 0x8f, 0xff, 0xca, 0xcb, 0xe2, 0x2b, 0x2f, 0x70, 0xa1, 0x9f, 0xfd, 0x9d, 0x3e, + 0xae, 0xc9, 0xcc, 0x2d, 0x9c, 0x38, 0x73, 0xff, 0xb6, 0x01, 0xa3, 0x7c, 0x73, 0x88, 0xdf, 0xb2, + 0xc8, 0xdc, 0x7e, 0xb6, 0x9f, 0xce, 0xf6, 0x53, 0x89, 0xf0, 0x3f, 0xd5, 0x00, 0xe7, 0x65, 0x64, + 0x31, 0xf5, 0x30, 0x86, 0x3c, 0x42, 0x47, 0xc5, 0x94, 0x97, 0x24, 0x8e, 0x50, 0xfc, 0x4d, 0x0c, + 0x95, 0x0b, 0xc7, 0x50, 0x9f, 0xe9, 0x1b, 0xfb, 0x94, 0xcf, 0xf4, 0xad, 0x40, 0x49, 0x78, 0xf6, + 0xd4, 0x0f, 0x84, 0xf9, 0x29, 0x0f, 0x58, 0x62, 0xb8, 0x92, 0x7c, 0x9a, 0x83, 0xec, 0x1d, 0x2d, + 0x75, 0x5c, 0x8c, 0x48, 0xd6, 0xa0, 0x94, 0x84, 0x86, 0x94, 0xb4, 0x7b, 0xd0, 0x18, 0x2e, 0x5c, + 0x5f, 0x79, 0xf4, 0x61, 0x66, 0x24, 0x48, 0xc2, 0xc3, 0xfc, 0xa6, 0x01, 0x95, 0xf4, 0x7c, 0x21, + 0xef, 0xc2, 0x78, 0x1c, 0x9d, 0x13, 0xfb, 0x17, 0xe0, 0x41, 0x66, 0x12, 0xce, 0xa3, 0x79, 0x1a, + 0xa8, 0xe8, 0x64, 0x1e, 0x8a, 0x6c, 0xd9, 0x29, 0xb9, 0x83, 0x51, 0x9e, 0xf4, 0x04, 0x4c, 0xbd, + 0xd7, 0x93, 0x78, 0xca, 0xaa, 0xfd, 0x8f, 0x79, 0x18, 0x57, 0x06, 0x8b, 0x5c, 0x83, 0xe2, 0x72, + 0xb8, 0xe2, 0x37, 0xf7, 0xa9, 0x2b, 0xae, 0x0b, 0xf0, 0x15, 0x46, 0x2f, 0xb4, 0x5b, 0x08, 0xb4, + 0xe2, 0x62, 0x52, 0x87, 0x09, 0xfe, 0x9f, 0x8c, 0xc2, 0xcc, 0x25, 0x47, 0x9d, 0x1c, 0x59, 0xc6, + 0x5f, 0xaa, 0x3b, 0xac, 0x46, 0x42, 0xbe, 0x01, 0xc0, 0x01, 0x6c, 0x7c, 0x87, 0x70, 0xec, 0x95, + 0x0b, 0xf8, 0x9c, 0xf8, 0x40, 0xe4, 0xa9, 0x2d, 0xc4, 0xa9, 0xa0, 0x30, 0xc4, 0x17, 0xe0, 0xfc, + 0xe6, 0xfe, 0xf0, 0x6f, 0x40, 0x26, 0x2f, 0xc0, 0xf9, 0xcd, 0x7d, 0x3b, 0xdb, 0xcb, 0x4b, 0x65, + 0x49, 0xbe, 0x65, 0xc0, 0x25, 0x8b, 0x36, 0xfd, 0x47, 0x34, 0x38, 0xa8, 0x45, 0x88, 0xa5, 0x7e, + 0xf1, 0x64, 0x97, 0xb2, 0x5b, 0xe2, 0x8b, 0xaf, 0x07, 0x82, 0x0b, 0x86, 0xa3, 0xb4, 0xbb, 0x91, + 0x7d, 0x4c, 0x15, 0x8e, 0xf9, 0xa4, 0xf9, 0x67, 0x86, 0xb2, 0x04, 0xc8, 0x2a, 0x94, 0xe2, 0xc9, + 0x22, 0x0e, 0x1c, 0x63, 0xe5, 0x48, 0xc2, 0x2d, 0xfa, 0xb0, 0xfe, 0x82, 0x38, 0xd9, 0x9f, 0x89, + 0xa7, 0x9c, 0xb6, 0x22, 0x24, 0x90, 0x7c, 0x19, 0x0a, 0x38, 0x54, 0x27, 0x27, 0x9b, 0x92, 0x5b, + 0x4d, 0x81, 0x8d, 0x11, 0xd6, 0x1a, 0x29, 0xc9, 0x17, 0x84, 0x97, 0x47, 0x5e, 0x4b, 0xe3, 0xca, + 0x40, 0xac, 0x1e, 0xf1, 0x1e, 0x93, 0x38, 0x16, 0x2a, 0xb3, 0xf5, 0xef, 0xe4, 0xa0, 0x92, 0x5e, + 0x78, 0xe4, 0x03, 0x28, 0xaf, 0x3b, 0x61, 0xf8, 0xd8, 0x0f, 0xdc, 0x7b, 0x4e, 0xb8, 0x27, 0x52, + 0x43, 0xa2, 0xcd, 0xd7, 0x15, 0x70, 0x7b, 0xcf, 0xd1, 0x52, 0x88, 0x69, 0x04, 0x6c, 0x43, 0xde, + 0x10, 0xfe, 0xea, 0xca, 0x02, 0x8a, 0xfc, 0xa8, 0x9b, 0x4a, 0x0d, 0x29, 0xd1, 0xc8, 0x5b, 0x90, + 0xe7, 0xb1, 0x6f, 0x6a, 0x5e, 0xa1, 0x07, 0x77, 0x6a, 0x3c, 0x5c, 0x88, 0x5f, 0x26, 0xeb, 0xa7, + 0xf2, 0x0c, 0x9f, 0xac, 0x28, 0x91, 0x53, 0xa3, 0x5a, 0x7e, 0x15, 0x09, 0x8e, 0x1b, 0x77, 0x72, + 0x08, 0xd5, 0x87, 0x85, 0x62, 0xbe, 0x52, 0x10, 0xf1, 0x39, 0x7f, 0x98, 0x87, 0x52, 0xfc, 0x7d, + 0x42, 0x00, 0xf5, 0x0d, 0x71, 0x2b, 0x8c, 0xff, 0x93, 0x8b, 0x50, 0x94, 0x2a, 0x86, 0xb8, 0x19, + 0x1e, 0x0b, 0x85, 0x7a, 0x31, 0x0b, 0x52, 0x97, 0xe0, 0xea, 0x85, 0x25, 0x7f, 0x92, 0x9b, 0x10, + 0x2b, 0x0a, 0x83, 0x34, 0x8a, 0x02, 0x1b, 0x30, 0x2b, 0x46, 0x23, 0x93, 0x90, 0xf3, 0xb8, 0x2f, + 0x72, 0xc9, 0xca, 0x79, 0x2e, 0xf9, 0x00, 0x8a, 0x8e, 0xeb, 0x52, 0xd7, 0x76, 0xa2, 0x21, 0xde, + 0xe3, 0x2c, 0x32, 0x6e, 0x5c, 0xa2, 0x23, 0x55, 0x2d, 0x22, 0x35, 0x28, 0xe1, 0x73, 0x8c, 0xbd, + 0x70, 0xa8, 0x37, 0x1c, 0x13, 0x0e, 0x45, 0x46, 0xb6, 0x19, 0x52, 0x97, 0xbc, 0x0e, 0x05, 0x36, + 0x9a, 0x62, 0x3f, 0x88, 0xb3, 0xc5, 0xad, 0x6d, 0xac, 0xf3, 0x0e, 0xbb, 0x77, 0xc6, 0x42, 0x04, + 0xf2, 0x0a, 0xe4, 0x7b, 0xf3, 0x0f, 0x85, 0xa4, 0xaf, 0x24, 0x61, 0x91, 0x31, 0x1a, 0x2b, 0x26, + 0xb7, 0xa0, 0xf8, 0x58, 0x0f, 0x80, 0x3b, 0x97, 0x1a, 0xc6, 0x18, 0x3f, 0x46, 0xac, 0x17, 0x61, + 0x94, 0x87, 0x9b, 0x99, 0x2f, 0x01, 0x24, 0x9f, 0xee, 0xbf, 0xc0, 0x37, 0xbf, 0x01, 0xa5, 0xf8, + 0x93, 0xe4, 0x45, 0x80, 0x7d, 0x7a, 0x60, 0xef, 0x39, 0x1d, 0x57, 0x3c, 0x43, 0x52, 0xb6, 0x4a, + 0xfb, 0xf4, 0xe0, 0x1e, 0x02, 0xc8, 0x05, 0x18, 0xeb, 0xb2, 0x51, 0x95, 0x89, 0x4d, 0xad, 0xd1, + 0x6e, 0x6f, 0x87, 0xcd, 0xd0, 0x59, 0x18, 0xc3, 0xa3, 0x03, 0xb1, 0xd0, 0x26, 0x2c, 0xf9, 0xd3, + 0xfc, 0xbd, 0x1c, 0x86, 0xbc, 0x2b, 0xf5, 0x24, 0x2f, 0xc3, 0x44, 0x33, 0xa0, 0xb8, 0x1d, 0x39, + 0x4c, 0x2d, 0x12, 0xdf, 0x29, 0x27, 0xc0, 0x65, 0x97, 0xbc, 0x06, 0x53, 0x49, 0xa6, 0x55, 0xbb, + 0xb9, 0x23, 0xc2, 0x5f, 0xcb, 0xd6, 0x44, 0x57, 0xa6, 0x5a, 0x5d, 0xd8, 0x41, 0x1f, 0xfa, 0x8a, + 0x1a, 0x6a, 0x16, 0xc9, 0xac, 0xa9, 0x25, 0x6b, 0x4a, 0x81, 0xe3, 0xb5, 0xc3, 0x79, 0x18, 0x75, + 0x9c, 0xdd, 0x9e, 0xc7, 0xfd, 0x79, 0xcb, 0x96, 0xf8, 0x45, 0x3e, 0x0f, 0xd3, 0xa1, 0xb7, 0xdb, + 0x71, 0xa2, 0x5e, 0x20, 0x72, 0x0e, 0xd0, 0x00, 0xa7, 0xd4, 0x84, 0x55, 0x89, 0x0b, 0x16, 0x38, + 0x9c, 0xbc, 0x09, 0x44, 0xfd, 0x9e, 0xbf, 0xf3, 0x31, 0x6d, 0xf2, 0xa9, 0x56, 0xb6, 0xa6, 0x95, + 0x92, 0x35, 0x2c, 0x20, 0x9f, 0x83, 0x72, 0x40, 0x43, 0x54, 0xc9, 0xb0, 0xdb, 0x30, 0x93, 0x8a, + 0x35, 0x2e, 0x61, 0xf7, 0xe9, 0x81, 0x59, 0x87, 0xe9, 0xbe, 0xf5, 0x48, 0xde, 0xe4, 0xda, 0xbd, + 0xd8, 0x9f, 0xcb, 0xdc, 0x98, 0x61, 0x42, 0x2a, 0xf5, 0x82, 0x2f, 0x47, 0x32, 0x3b, 0x50, 0x56, + 0xe5, 0xeb, 0x09, 0x81, 0xc5, 0xe7, 0xd1, 0xb1, 0x90, 0x0b, 0x9f, 0xd1, 0xa3, 0xc3, 0x6a, 0xce, + 0x73, 0xd1, 0x9d, 0xf0, 0x2a, 0x14, 0xa5, 0x96, 0xa0, 0x3e, 0xc3, 0x21, 0x14, 0xca, 0x03, 0x2b, + 0x2e, 0x35, 0x5f, 0x87, 0x31, 0x21, 0x42, 0x8f, 0x3f, 0xc6, 0x31, 0x7f, 0x39, 0x07, 0x53, 0x16, + 0x65, 0x0b, 0x5c, 0x3c, 0x70, 0xf1, 0x9c, 0xe5, 0x9c, 0xd5, 0xda, 0x76, 0x4c, 0x1c, 0xff, 0xf7, + 0x0c, 0x98, 0xc9, 0xc0, 0xfd, 0x44, 0x49, 0xaa, 0x6e, 0x43, 0x69, 0xd1, 0x73, 0x5a, 0x35, 0xd7, + 0x8d, 0x1d, 0x24, 0x51, 0x1b, 0x74, 0xd9, 0x72, 0x72, 0x18, 0x54, 0xdd, 0x4c, 0x63, 0x54, 0x72, + 0x5d, 0x4c, 0x8a, 0x24, 0x8d, 0x9e, 0xcc, 0x6a, 0x0b, 0xbc, 0x4e, 0x49, 0x4e, 0x5b, 0x0c, 0x43, + 0xe3, 0xc0, 0xe4, 0x0e, 0xfc, 0x99, 0x1d, 0xba, 0xec, 0x30, 0xb4, 0x74, 0xf3, 0x86, 0x32, 0x3b, + 0xbf, 0x99, 0x83, 0xf3, 0xd9, 0x84, 0x9f, 0x34, 0xdf, 0x18, 0x26, 0x51, 0x50, 0x12, 0x07, 0x63, + 0xbe, 0x31, 0x9e, 0x71, 0x01, 0xf1, 0x13, 0x04, 0xf2, 0x10, 0x26, 0x56, 0x9c, 0x30, 0xba, 0x47, + 0x9d, 0x20, 0xda, 0xa1, 0x4e, 0x34, 0x84, 0x06, 0x1b, 0xbf, 0x93, 0x8b, 0x9b, 0xda, 0x9e, 0xa4, + 0x4c, 0xbf, 0x93, 0xab, 0xb1, 0x8d, 0x27, 0x4a, 0x61, 0x88, 0x89, 0xf2, 0xb3, 0x30, 0xd5, 0xa0, + 0x6d, 0xa7, 0xbb, 0xe7, 0x07, 0x54, 0x9c, 0x3c, 0xdf, 0x80, 0x89, 0x18, 0x94, 0x39, 0x5b, 0xf4, + 0x62, 0x0d, 0x5f, 0xe9, 0x88, 0x44, 0x94, 0xe8, 0xc5, 0xe6, 0xef, 0xe4, 0xe0, 0x42, 0xad, 0x29, + 0x8e, 0xe9, 0x45, 0x81, 0xbc, 0x4d, 0xfc, 0x8c, 0xbf, 0x4d, 0xe6, 0xa0, 0xf4, 0xc0, 0x79, 0x82, + 0x0f, 0xbe, 0x87, 0x22, 0x6b, 0x0d, 0x57, 0xbf, 0x9c, 0x27, 0x76, 0x7c, 0xec, 0x65, 0x25, 0x38, + 0x4f, 0xf3, 0x4d, 0x78, 0x13, 0x46, 0xef, 0xf9, 0x2d, 0x57, 0x6c, 0x4e, 0xe2, 0xd4, 0x7f, 0x0f, + 0x21, 0x96, 0x28, 0x31, 0x7f, 0x68, 0xc0, 0x64, 0x5c, 0x63, 0xac, 0xc2, 0x67, 0xde, 0x25, 0xa9, + 0xd7, 0xf1, 0x4b, 0x43, 0xbc, 0x8e, 0x3f, 0xf2, 0xe9, 0x7a, 0xc2, 0xfc, 0x27, 0x78, 0xa1, 0xa0, + 0xb6, 0x92, 0xed, 0x44, 0x4a, 0x45, 0x8c, 0x21, 0x2b, 0x92, 0x7b, 0x6a, 0x43, 0x92, 0x1f, 0x38, + 0x24, 0xbf, 0x92, 0x83, 0xf1, 0xb8, 0xb2, 0xcf, 0x59, 0xfc, 0x76, 0xdc, 0xae, 0xa1, 0xbc, 0xb3, + 0x1b, 0x8a, 0xac, 0x10, 0x4e, 0xd0, 0x5f, 0x86, 0x51, 0xb1, 0x98, 0x8c, 0xd4, 0xad, 0x5a, 0x6a, + 0x74, 0x93, 0xb7, 0x4e, 0x71, 0x40, 0x43, 0x4b, 0xd0, 0xa1, 0xfb, 0xfb, 0x36, 0xdd, 0x11, 0xf7, + 0x4b, 0xcf, 0xec, 0x1e, 0x95, 0xed, 0xfe, 0x9e, 0x34, 0x6c, 0xa8, 0xdd, 0xe9, 0x1f, 0x14, 0xa0, + 0x92, 0x26, 0x39, 0x39, 0x42, 0x7e, 0xbd, 0xb7, 0x23, 0xde, 0x29, 0xc0, 0x08, 0xf9, 0x6e, 0x6f, + 0xc7, 0x62, 0x30, 0xf2, 0x1a, 0x14, 0xd6, 0x03, 0xef, 0x11, 0xb6, 0x5a, 0x3c, 0xd3, 0xd0, 0x0d, + 0xbc, 0x47, 0xaa, 0x1f, 0x28, 0x2b, 0x47, 0x83, 0x76, 0xa5, 0xa1, 0x3c, 0x33, 0xcd, 0x0d, 0xda, + 0x56, 0x98, 0x4e, 0x8b, 0x22, 0xd1, 0xd8, 0x56, 0x59, 0xa7, 0x4e, 0x20, 0xa2, 0xb9, 0x85, 0x38, + 0xc3, 0xad, 0x72, 0x07, 0xc1, 0x3c, 0xe7, 0xa9, 0xa5, 0x22, 0x91, 0x16, 0x10, 0xe5, 0xa7, 0x5c, + 0xc0, 0x27, 0xdb, 0x78, 0xf2, 0x79, 0xa1, 0xb3, 0x2a, 0x6b, 0x5b, 0x5d, 0xcd, 0x19, 0x7c, 0x9f, + 0xe6, 0x19, 0xe1, 0x3a, 0x94, 0xf0, 0xc8, 0x0b, 0x0f, 0x32, 0x8a, 0x27, 0x32, 0x93, 0x3e, 0xb7, + 0x80, 0xb7, 0xf1, 0x76, 0x7c, 0x9c, 0x91, 0x30, 0x21, 0xef, 0xc3, 0xb8, 0xea, 0x28, 0xca, 0xdd, + 0x19, 0x2f, 0xf3, 0x08, 0xa1, 0x01, 0xe9, 0xc3, 0x54, 0x02, 0xf3, 0x0b, 0xea, 0x2c, 0x11, 0x9b, + 0xf6, 0xb1, 0xb3, 0xc4, 0xfc, 0x2d, 0x54, 0xe3, 0xdb, 0x7e, 0x44, 0x85, 0xf6, 0xf2, 0xcc, 0xca, + 0xb1, 0xe4, 0x08, 0x79, 0x44, 0xf3, 0x08, 0xd1, 0x5a, 0x77, 0x8a, 0x07, 0x96, 0xff, 0xa1, 0x01, + 0xe7, 0x32, 0x69, 0xc9, 0x0d, 0x80, 0x44, 0x47, 0x14, 0xbd, 0xc4, 0x93, 0xc9, 0xc6, 0x50, 0x4b, + 0xc1, 0x20, 0x5f, 0x4f, 0x6b, 0x77, 0x27, 0x6f, 0x4e, 0xf2, 0xc9, 0x85, 0x49, 0x5d, 0xbb, 0xcb, + 0xd0, 0xe9, 0xcc, 0xef, 0xe5, 0x61, 0xba, 0xef, 0xa9, 0xbe, 0x13, 0xee, 0xe0, 0xf7, 0x53, 0x0f, + 0x41, 0xf1, 0xeb, 0x8e, 0xeb, 0x83, 0x1e, 0x0a, 0xcc, 0x78, 0x16, 0x0a, 0x8f, 0xc5, 0x44, 0x1e, + 0xe3, 0x13, 0x5e, 0x87, 0x0a, 0xb3, 0x9f, 0x10, 0xfb, 0xfc, 0xc0, 0xaf, 0x3d, 0x85, 0xa7, 0xc4, + 0x7e, 0x8c, 0x5f, 0x5a, 0xfa, 0xad, 0x1c, 0xcc, 0xf4, 0xb5, 0xf9, 0x99, 0x5d, 0x75, 0x5f, 0xd6, + 0x76, 0xb7, 0x97, 0x06, 0x8d, 0xe9, 0x50, 0x5a, 0xc4, 0xff, 0x30, 0xe0, 0xc2, 0x00, 0x4a, 0x72, + 0x90, 0x9e, 0x44, 0x5c, 0xab, 0xb8, 0x79, 0xfc, 0x07, 0x9f, 0xca, 0x54, 0xfa, 0xcc, 0x66, 0xc2, + 0x2f, 0xe7, 0x00, 0xb6, 0xe9, 0xce, 0xb3, 0x9d, 0xfe, 0x27, 0xfb, 0x2d, 0x7c, 0xd9, 0xac, 0xa1, + 0xc6, 0x7d, 0x0d, 0x0f, 0x12, 0x87, 0xcf, 0xfd, 0x13, 0x3f, 0x2b, 0x91, 0xcb, 0x7e, 0x56, 0xc2, + 0xdc, 0x81, 0xb3, 0x77, 0x69, 0x94, 0xec, 0x84, 0xd2, 0x86, 0x3c, 0x9e, 0xed, 0x1b, 0x50, 0x12, + 0xf8, 0x7a, 0x8a, 0x70, 0xe9, 0x50, 0xe6, 0xb9, 0x56, 0x82, 0x60, 0x52, 0xb8, 0xb0, 0x48, 0x5b, + 0x34, 0xa2, 0x9f, 0xed, 0x67, 0x1a, 0x40, 0x78, 0x53, 0xf8, 0x6b, 0x03, 0x43, 0x7d, 0xe1, 0xc4, + 0xfe, 0xd9, 0x82, 0x73, 0x71, 0xdd, 0x9f, 0x26, 0xdf, 0x39, 0xa6, 0x4b, 0x88, 0x58, 0xbb, 0x84, + 0xe3, 0x31, 0x87, 0x88, 0x4f, 0xe0, 0x92, 0x24, 0xd8, 0xf6, 0xe2, 0x9b, 0x98, 0xa1, 0x68, 0xc9, + 0xbb, 0x30, 0xae, 0xd0, 0x88, 0xc0, 0x5d, 0xbc, 0xed, 0x7c, 0xec, 0x45, 0x7b, 0x76, 0xc8, 0xe1, + 0xea, 0x6d, 0xa7, 0x82, 0x6e, 0x7e, 0x0d, 0x5e, 0x88, 0xfd, 0x56, 0x32, 0x3e, 0x9d, 0x62, 0x6e, + 0x9c, 0x8e, 0xf9, 0x6a, 0xd2, 0xac, 0xe5, 0x4e, 0xec, 0x3f, 0x2e, 0x79, 0x13, 0xb5, 0x59, 0xa2, + 0x31, 0x97, 0x95, 0xb4, 0x68, 0x62, 0x2f, 0x4a, 0x00, 0xe6, 0x3b, 0x4a, 0x65, 0x33, 0x18, 0x6a, + 0xc4, 0x46, 0x9a, 0xf8, 0x97, 0x73, 0x30, 0xb5, 0xb6, 0xbc, 0xb8, 0x10, 0x1f, 0x23, 0x3f, 0x67, + 0xb9, 0x89, 0xb4, 0xb6, 0x0d, 0x96, 0x37, 0xe6, 0x26, 0xcc, 0xa4, 0xba, 0x01, 0x1f, 0x53, 0x79, + 0x9f, 0xfb, 0x97, 0xc4, 0x60, 0xb9, 0xb3, 0x9c, 0xcf, 0x62, 0xbf, 0x75, 0xcb, 0x4a, 0x61, 0x9b, + 0xdf, 0x1b, 0x4d, 0xf1, 0x15, 0x22, 0xec, 0x0d, 0x28, 0x2d, 0x87, 0x61, 0x8f, 0x06, 0x9b, 0xd6, + 0x8a, 0xaa, 0x23, 0x7a, 0x08, 0xb4, 0x7b, 0x41, 0xcb, 0x4a, 0x10, 0xc8, 0x35, 0x28, 0x8a, 0xf8, + 0x2e, 0x29, 0x13, 0xf0, 0xba, 0x3c, 0x0e, 0x0f, 0xb3, 0xe2, 0x62, 0xf2, 0x16, 0x94, 0xf9, 0xff, + 0x7c, 0xb6, 0x89, 0x0e, 0xc7, 0xb3, 0x2a, 0x81, 0xce, 0x67, 0xa7, 0xa5, 0xa1, 0x31, 0xcb, 0x4c, + 0xbe, 0xd6, 0xc8, 0x6a, 0x54, 0x48, 0x2c, 0x33, 0xf9, 0xb0, 0x23, 0xd6, 0x49, 0x45, 0x22, 0xd7, + 0x21, 0x5f, 0x5b, 0xb0, 0xd4, 0xac, 0xc8, 0x4e, 0x33, 0xe0, 0x59, 0xc5, 0xb5, 0x07, 0x91, 0x6a, + 0x0b, 0x16, 0x99, 0x87, 0x22, 0x3e, 0x78, 0xe1, 0xd2, 0x40, 0xf8, 0x8c, 0xe2, 0xac, 0xe9, 0x0a, + 0x98, 0x7a, 0xf3, 0x28, 0xf1, 0xc8, 0x1c, 0x8c, 0x2d, 0x7a, 0x61, 0xb7, 0xe5, 0x1c, 0x88, 0xa4, + 0x24, 0x78, 0x19, 0xe2, 0x72, 0x90, 0x3a, 0xcf, 0x04, 0x16, 0xb9, 0x06, 0x23, 0x8d, 0xa6, 0xdf, + 0x65, 0xd6, 0x56, 0xec, 0xda, 0x12, 0x32, 0x80, 0x96, 0xd9, 0x80, 0x01, 0x30, 0xe4, 0x98, 0x47, + 0x4e, 0x95, 0x94, 0x90, 0xe3, 0x74, 0xc4, 0x94, 0xc0, 0xe9, 0xf7, 0xff, 0x83, 0xa7, 0xe9, 0xff, + 0xb7, 0x03, 0x17, 0xee, 0xa2, 0xaa, 0xdf, 0xa0, 0x01, 0xe6, 0x81, 0xe4, 0x8f, 0xe7, 0x6c, 0x5a, + 0xcb, 0x22, 0x5a, 0xec, 0xea, 0xd1, 0x61, 0xf5, 0x15, 0x6e, 0x0d, 0xd8, 0x21, 0xc7, 0x91, 0xef, + 0xee, 0xa4, 0x5e, 0x0c, 0x18, 0xc4, 0x88, 0x7c, 0x05, 0xce, 0x66, 0x15, 0x89, 0xb8, 0x31, 0xf4, + 0x0a, 0xcf, 0xfe, 0x80, 0xea, 0x96, 0x9d, 0xc5, 0x81, 0xac, 0x40, 0x85, 0xc3, 0x6b, 0x6e, 0xdb, + 0xeb, 0x2c, 0xb5, 0x1d, 0xaf, 0x85, 0x51, 0x64, 0x22, 0x14, 0x50, 0x70, 0x75, 0x58, 0xa1, 0x4d, + 0x59, 0xa9, 0xe6, 0x9d, 0x94, 0xa2, 0x44, 0x71, 0xd4, 0xa8, 0x3d, 0x58, 0x49, 0xd6, 0xd4, 0xf3, + 0x75, 0x6f, 0xa4, 0xb5, 0xed, 0x98, 0x7b, 0xa3, 0x4d, 0x98, 0x49, 0x75, 0x83, 0x14, 0x47, 0x1a, + 0x38, 0x2d, 0x8e, 0x52, 0x34, 0x56, 0x0a, 0xdb, 0xfc, 0x4f, 0xa3, 0x29, 0xbe, 0xe2, 0xac, 0xc8, + 0x84, 0x51, 0x2e, 0x6d, 0xd4, 0xac, 0x65, 0x5c, 0x16, 0x59, 0xa2, 0x84, 0x5c, 0x84, 0x7c, 0xa3, + 0xb1, 0xa6, 0xe6, 0x54, 0x0c, 0x43, 0xdf, 0x62, 0x30, 0x36, 0x42, 0x78, 0x0c, 0xa4, 0x04, 0x68, + 0x35, 0x69, 0x10, 0x89, 0xe7, 0x3c, 0x5f, 0x4d, 0xd6, 0x71, 0x21, 0xe9, 0x6f, 0xb1, 0x8e, 0x93, + 0xd5, 0xbb, 0x00, 0xb3, 0xb5, 0x30, 0xa4, 0x41, 0xc4, 0x93, 0xb2, 0x87, 0xbd, 0x36, 0x0d, 0xc4, + 0x5c, 0x13, 0x32, 0x86, 0x3f, 0x06, 0xde, 0x0c, 0xad, 0x81, 0x88, 0xe4, 0x2a, 0x14, 0x6b, 0x3d, + 0xd7, 0xa3, 0x9d, 0xa6, 0xe6, 0x9b, 0xee, 0x08, 0x98, 0x15, 0x97, 0x92, 0x8f, 0xe0, 0x9c, 0x20, + 0x92, 0x02, 0x47, 0xf4, 0x00, 0x97, 0x35, 0xdc, 0x82, 0x15, 0x6b, 0x41, 0x8a, 0x29, 0x5b, 0x74, + 0x49, 0x36, 0x25, 0xa9, 0x41, 0x65, 0x09, 0xef, 0x49, 0xe5, 0xa3, 0xbe, 0x7e, 0x20, 0x92, 0xef, + 0xa2, 0xe4, 0xe2, 0x77, 0xa8, 0xb6, 0x1b, 0x17, 0x5a, 0x7d, 0xe8, 0xe4, 0x3e, 0xcc, 0xa4, 0x61, + 0x4c, 0x1e, 0x97, 0x92, 0x47, 0xb7, 0xfa, 0xb8, 0xa0, 0x60, 0xce, 0xa2, 0x22, 0x3b, 0x30, 0x5d, + 0x8b, 0xa2, 0xc0, 0xdb, 0xe9, 0x45, 0x34, 0x25, 0xba, 0xe4, 0x41, 0x63, 0x5c, 0x2e, 0xc5, 0xd7, + 0x0b, 0x62, 0x32, 0xce, 0x38, 0x31, 0x65, 0x2c, 0xc2, 0xac, 0x7e, 0x76, 0xc4, 0x8d, 0xdf, 0xed, + 0x13, 0x6f, 0xdb, 0x89, 0x80, 0x22, 0x79, 0xa0, 0x5b, 0x0b, 0x0f, 0xda, 0x6d, 0x1a, 0x05, 0x78, + 0x73, 0x8f, 0x6f, 0xdf, 0x99, 0xc2, 0x07, 0xe8, 0x92, 0xf2, 0x5c, 0x25, 0xbe, 0x6f, 0xa8, 0xb9, + 0x47, 0x6a, 0x3c, 0xb5, 0xed, 0xa3, 0x3c, 0xe4, 0xf6, 0xd1, 0x82, 0xe9, 0xa5, 0x4e, 0x33, 0x38, + 0xc0, 0xc8, 0x46, 0x59, 0xb9, 0x89, 0x13, 0x2a, 0x27, 0x1f, 0xb6, 0xb8, 0xec, 0xc8, 0x19, 0x96, + 0x55, 0xbd, 0x7e, 0xc6, 0xe6, 0xdf, 0x80, 0x4a, 0xba, 0x2f, 0x3f, 0xe5, 0x63, 0xc5, 0xa7, 0x71, + 0xcd, 0x66, 0x23, 0x9d, 0x6e, 0x0b, 0x99, 0xd3, 0x5e, 0xa4, 0x35, 0x92, 0xa8, 0x74, 0xe5, 0xed, + 0x58, 0xed, 0x1d, 0x5a, 0xb9, 0x8c, 0x73, 0x59, 0xcb, 0xd8, 0xfc, 0xd5, 0x1c, 0x4c, 0x73, 0x6f, + 0xd2, 0x67, 0x5f, 0x57, 0x7c, 0x5f, 0x13, 0xce, 0xf2, 0x2c, 0x30, 0xd5, 0xba, 0x63, 0xb4, 0xc5, + 0x6f, 0xc0, 0xb9, 0xbe, 0xae, 0x40, 0x01, 0xbd, 0x28, 0xfd, 0x78, 0xfb, 0x44, 0xf4, 0x6c, 0xf6, + 0x47, 0xb6, 0x6e, 0x59, 0x7d, 0x14, 0xe6, 0x3f, 0xca, 0xf5, 0xf1, 0x17, 0x7a, 0xa3, 0xaa, 0x09, + 0x1a, 0xa7, 0xd3, 0x04, 0x73, 0x9f, 0x48, 0x13, 0xcc, 0x0f, 0xa3, 0x09, 0x7e, 0x04, 0x13, 0x1b, + 0xd4, 0x61, 0x1a, 0x8d, 0x08, 0x36, 0x2b, 0x68, 0xaf, 0xc5, 0xb2, 0x32, 0x29, 0x5f, 0xe2, 0x40, + 0xd5, 0x88, 0x11, 0x30, 0xd1, 0xc2, 0xa3, 0xcf, 0x2c, 0x9d, 0x83, 0xba, 0x69, 0x8c, 0x0c, 0xde, + 0x34, 0xcc, 0x6f, 0xe6, 0x60, 0x5c, 0x61, 0x4f, 0xbe, 0x08, 0xe5, 0xb5, 0x60, 0xd7, 0xe9, 0x78, + 0x3f, 0xe7, 0x28, 0xc7, 0xaf, 0x58, 0x7d, 0x5f, 0x81, 0x5b, 0x1a, 0x16, 0xba, 0xcd, 0x50, 0xa7, + 0xad, 0x4e, 0x7c, 0x56, 0x3d, 0x0b, 0xa1, 0x4a, 0xb0, 0x70, 0x7e, 0x88, 0x60, 0x61, 0x3d, 0xd2, + 0xb6, 0x70, 0xfa, 0x48, 0x5b, 0x2d, 0x30, 0x76, 0xe4, 0x94, 0x81, 0xb1, 0xe6, 0xaf, 0xe7, 0xa0, + 0x22, 0xde, 0x55, 0x95, 0x87, 0x87, 0xcf, 0xd7, 0x3b, 0x0c, 0x7a, 0xe3, 0x8e, 0xb9, 0x1e, 0x2b, + 0xfc, 0xf6, 0xef, 0x57, 0xf1, 0x95, 0xcc, 0x74, 0x77, 0xc8, 0x57, 0x32, 0x75, 0x78, 0x3a, 0x72, + 0x20, 0x4d, 0x65, 0xa5, 0xf1, 0xcd, 0x3f, 0xcd, 0xa5, 0x79, 0x0b, 0x6d, 0xea, 0x55, 0x18, 0xe3, + 0xcf, 0x62, 0x49, 0xe7, 0x66, 0x91, 0xbb, 0x09, 0x41, 0x96, 0x2c, 0x3b, 0x4d, 0x0c, 0xc9, 0x49, + 0x4f, 0xa5, 0x92, 0xdb, 0x50, 0x46, 0x7f, 0x91, 0x9a, 0xeb, 0x06, 0x34, 0x0c, 0x85, 0xa2, 0x85, + 0x77, 0x77, 0x8f, 0xe9, 0x8e, 0xcd, 0xfd, 0x4a, 0x1c, 0xd7, 0x0d, 0x2c, 0x0d, 0x8f, 0x2c, 0xc0, + 0x59, 0xcd, 0x3d, 0x49, 0xd2, 0x8f, 0x24, 0xbb, 0x45, 0x84, 0x05, 0x9c, 0x38, 0x13, 0xf9, 0xe9, + 0x3d, 0x13, 0x6d, 0xfe, 0x6f, 0x83, 0xad, 0xb5, 0xe6, 0xfe, 0x73, 0x16, 0xdd, 0xc2, 0x9a, 0x74, + 0x8c, 0xb2, 0xff, 0x1f, 0x0c, 0xee, 0x9f, 0x2e, 0xa6, 0xcf, 0xdb, 0x30, 0xca, 0x1f, 0xe1, 0x12, + 0x9e, 0xd4, 0x2a, 0x17, 0x5e, 0x90, 0xdc, 0x4f, 0xf1, 0xa7, 0xbc, 0x2c, 0x41, 0xc0, 0x4c, 0x66, + 0xdd, 0x4d, 0x1e, 0x15, 0xcf, 0x7e, 0xff, 0x78, 0x89, 0xa5, 0xe6, 0x25, 0x1d, 0x2e, 0xdf, 0xb5, + 0x71, 0x72, 0x5e, 0x52, 0xf3, 0xff, 0xe6, 0x78, 0x7b, 0x44, 0xa5, 0x86, 0x4d, 0xb8, 0xf7, 0x1a, + 0x14, 0xf0, 0xb9, 0x57, 0x25, 0xab, 0x61, 0xea, 0xa9, 0x57, 0x2c, 0x67, 0xeb, 0x06, 0x65, 0xad, + 0x1a, 0x50, 0x85, 0xe2, 0x58, 0x5d, 0x37, 0x88, 0x81, 0xd9, 0xa4, 0x7d, 0x97, 0xaa, 0xcb, 0xa1, + 0xa3, 0x27, 0xfe, 0xc6, 0x72, 0x72, 0x5b, 0xf1, 0x6b, 0x56, 0x0f, 0x34, 0xda, 0x0f, 0x1d, 0x9b, + 0xfb, 0xd3, 0xaa, 0xd2, 0x36, 0x71, 0x81, 0x5e, 0x82, 0x49, 0x3d, 0x56, 0x59, 0x18, 0x1d, 0x18, + 0xf2, 0x9d, 0x8a, 0x73, 0x56, 0xd5, 0x5b, 0x9d, 0x88, 0xd4, 0x61, 0x42, 0x0b, 0x48, 0x55, 0x93, + 0xb0, 0xf2, 0xec, 0x30, 0x76, 0x7f, 0x26, 0x05, 0x9d, 0x44, 0x39, 0x30, 0xff, 0x02, 0x54, 0xc4, + 0xca, 0x8c, 0x63, 0xdb, 0x50, 0xb5, 0x5b, 0x5e, 0xb4, 0xd4, 0xd5, 0xd4, 0xf4, 0xdc, 0xc0, 0x42, + 0xa8, 0xf9, 0x5d, 0x03, 0x2e, 0x8a, 0xc7, 0xc5, 0x2c, 0x1a, 0x32, 0x1d, 0x12, 0x03, 0xe2, 0x70, + 0x3e, 0x7e, 0x91, 0xbc, 0x2b, 0x13, 0x4f, 0xe9, 0x02, 0x32, 0xfd, 0x8d, 0xfa, 0x84, 0x98, 0x94, + 0x23, 0x98, 0x7a, 0x4a, 0x26, 0x9c, 0x7a, 0x5b, 0x24, 0x9c, 0xca, 0x1d, 0x4f, 0x1c, 0xaf, 0x0b, + 0x97, 0x76, 0x64, 0xa2, 0xa9, 0xef, 0xe4, 0xe0, 0x5c, 0x46, 0xb5, 0xb6, 0xbe, 0xf8, 0x8c, 0x0a, + 0x87, 0xba, 0x26, 0x1c, 0x64, 0x46, 0xc2, 0x81, 0x1d, 0x9f, 0x29, 0x2b, 0x7e, 0xd7, 0x80, 0x0b, + 0xfa, 0xec, 0x11, 0xb6, 0xe8, 0xd6, 0x2d, 0xf2, 0x0e, 0x8c, 0xde, 0xa3, 0x8e, 0x4b, 0x65, 0x08, + 0x46, 0x9c, 0xdd, 0x4b, 0x9c, 0x0e, 0xf3, 0x42, 0xce, 0xf6, 0x4f, 0xf9, 0x52, 0x3e, 0x63, 0x09, + 0x12, 0xb2, 0x28, 0x2a, 0xc7, 0xaf, 0xa7, 0x4c, 0x79, 0x53, 0x93, 0xf5, 0xa9, 0x63, 0x14, 0xe3, + 0x9f, 0x87, 0x17, 0x8e, 0x21, 0x61, 0xe3, 0xc6, 0x46, 0x5e, 0x1d, 0x37, 0xdc, 0x57, 0x10, 0x4a, + 0xde, 0x87, 0xa9, 0x0d, 0x11, 0x28, 0x26, 0x47, 0x43, 0x49, 0xee, 0x2e, 0x63, 0xc8, 0x6c, 0x39, + 0x2c, 0x69, 0x64, 0xb6, 0xd7, 0xeb, 0x1f, 0x17, 0x3e, 0x0e, 0x2f, 0xc3, 0xe8, 0x3d, 0x5f, 0xc9, + 0xba, 0x8d, 0x83, 0x8b, 0xef, 0x59, 0x79, 0xae, 0x25, 0x8a, 0x62, 0x63, 0x2d, 0x97, 0x79, 0x15, + 0xf1, 0x4d, 0x03, 0x2a, 0x3a, 0xef, 0x4f, 0xdb, 0xdf, 0xef, 0x69, 0xfd, 0xfd, 0x42, 0x76, 0x7f, + 0x0f, 0xee, 0xe8, 0xbe, 0x64, 0x0c, 0x43, 0xf5, 0xb0, 0x09, 0xa3, 0x8b, 0x7e, 0xdb, 0xf1, 0x3a, + 0x6a, 0x02, 0x01, 0x17, 0x21, 0x96, 0x28, 0x51, 0x7a, 0x2b, 0x3f, 0xb0, 0xb7, 0xcc, 0x6f, 0x17, + 0xe0, 0xa2, 0x45, 0x77, 0x3d, 0xa6, 0xf5, 0x6c, 0x86, 0x5e, 0x67, 0x57, 0xbb, 0x28, 0x32, 0x53, + 0x1d, 0x2e, 0xdc, 0xe3, 0x18, 0x24, 0xee, 0xef, 0x6b, 0x50, 0x64, 0xa2, 0x57, 0xe9, 0x73, 0xb4, + 0x64, 0x30, 0x0d, 0x0e, 0x76, 0x7c, 0x5c, 0x4c, 0xae, 0x8b, 0x8d, 0x41, 0x71, 0x60, 0x66, 0x1b, + 0x43, 0xea, 0x39, 0x6e, 0xbe, 0x39, 0xc4, 0x9a, 0x52, 0x61, 0x80, 0xa6, 0xf4, 0x00, 0xce, 0xd6, + 0x5c, 0x2e, 0x74, 0x9c, 0xd6, 0x7a, 0xe0, 0x75, 0x9a, 0x5e, 0xd7, 0x69, 0x49, 0x4d, 0x9b, 0x3f, + 0x4e, 0x1e, 0x97, 0xdb, 0xdd, 0x18, 0xc1, 0xca, 0x24, 0x63, 0xcd, 0x58, 0x5c, 0x6d, 0xf0, 0x2c, + 0x27, 0xa3, 0xc8, 0x02, 0x9b, 0xe1, 0x76, 0x42, 0x9e, 0xe6, 0xc4, 0x8a, 0x8b, 0x51, 0x47, 0xc3, + 0x18, 0x86, 0x8d, 0x95, 0xc6, 0x7d, 0x11, 0x13, 0x20, 0xfd, 0xab, 0x78, 0xc8, 0x43, 0xd4, 0x0a, + 0xd1, 0x28, 0xd7, 0xf0, 0x12, 0xba, 0x46, 0xe3, 0x1e, 0xa3, 0x2b, 0xf6, 0xd1, 0x85, 0xe1, 0x9e, + 0x4a, 0xc7, 0xf1, 0x98, 0xfd, 0xcf, 0x3d, 0x54, 0x70, 0x42, 0x94, 0x12, 0x8d, 0x2e, 0x40, 0x28, + 0xd7, 0xe8, 0x14, 0x14, 0xf2, 0x2e, 0xcc, 0x2c, 0x2d, 0xcc, 0x4b, 0x67, 0xff, 0x45, 0xbf, 0xd9, + 0x6b, 0xd3, 0x4e, 0x84, 0xc1, 0x27, 0x65, 0x3e, 0x86, 0xb4, 0x39, 0xcf, 0x66, 0x41, 0x16, 0x9a, + 0x70, 0xf9, 0xe7, 0x01, 0x63, 0x0b, 0xbe, 0x4b, 0xc3, 0xad, 0x9b, 0xcf, 0x99, 0xcb, 0xbf, 0xd2, + 0x36, 0x5c, 0x6d, 0x37, 0x33, 0x57, 0xe6, 0xdf, 0x45, 0x97, 0xff, 0x3e, 0x5c, 0xf2, 0x93, 0x30, + 0x82, 0x3f, 0xc5, 0x36, 0x3a, 0x93, 0xc1, 0x36, 0xd9, 0x42, 0x9b, 0x3c, 0x61, 0x05, 0x12, 0x90, + 0xe5, 0x24, 0x23, 0xfd, 0x29, 0x1c, 0x57, 0x45, 0xd4, 0xa9, 0xfe, 0x14, 0x89, 0x0b, 0x65, 0xf5, + 0x83, 0x6c, 0x8e, 0xdc, 0x73, 0xc2, 0x3d, 0xea, 0x2e, 0xc8, 0xc7, 0x04, 0xcb, 0x7c, 0x8e, 0xec, + 0x21, 0x14, 0x9f, 0x49, 0xb1, 0x14, 0x14, 0x26, 0x1d, 0x96, 0xc3, 0xcd, 0x50, 0x54, 0x45, 0x98, + 0x36, 0x1e, 0x9a, 0xa4, 0xae, 0x25, 0x8a, 0x50, 0x5a, 0xca, 0x84, 0x8c, 0x81, 0xd3, 0xdc, 0xa7, + 0xc1, 0xd6, 0xcd, 0xcf, 0x42, 0x5a, 0xea, 0xdf, 0x38, 0x66, 0x4c, 0x7e, 0xa3, 0x18, 0xe7, 0x5b, + 0xd1, 0x90, 0x99, 0xe2, 0x97, 0x5c, 0xb7, 0x1b, 0x89, 0xe2, 0x97, 0x5c, 0xb7, 0xab, 0x8a, 0x5f, + 0x8c, 0x1a, 0xa7, 0xc3, 0xcd, 0x9d, 0x90, 0x0e, 0x77, 0x40, 0x06, 0x6e, 0xe9, 0xa9, 0xf9, 0x1c, + 0x3d, 0x46, 0xf0, 0x25, 0x28, 0xd7, 0xa2, 0xc8, 0x69, 0xee, 0x51, 0x17, 0xd3, 0x2e, 0x2b, 0xb7, + 0x7c, 0x8e, 0x80, 0xab, 0x3e, 0x60, 0x2a, 0xae, 0xf2, 0x18, 0xc9, 0xd8, 0x10, 0x8f, 0x91, 0xcc, + 0xc1, 0xd8, 0x72, 0xe7, 0x91, 0xc7, 0xfa, 0xa4, 0x98, 0x64, 0x7c, 0xf0, 0x38, 0x48, 0x7f, 0xc1, + 0x02, 0x41, 0xe4, 0x6d, 0x28, 0xb2, 0x4d, 0x07, 0x63, 0x97, 0x4b, 0x89, 0x7e, 0x2e, 0x9e, 0x41, + 0xde, 0x13, 0x45, 0xea, 0x21, 0xb2, 0x44, 0x27, 0xb7, 0x61, 0x4c, 0x9a, 0xc4, 0x90, 0xe8, 0xe4, + 0x82, 0xd2, 0xe1, 0x25, 0x5a, 0x92, 0x09, 0x61, 0x12, 0xbf, 0xab, 0x07, 0x85, 0x8c, 0x2b, 0xc1, + 0xd6, 0x4a, 0x50, 0x88, 0x16, 0x6c, 0xad, 0x84, 0x87, 0xc4, 0x16, 0x4e, 0xf9, 0x44, 0x0b, 0x67, + 0x0b, 0xca, 0xeb, 0x4e, 0x10, 0x79, 0x6c, 0x3b, 0xea, 0x44, 0x3c, 0x57, 0x56, 0x62, 0x80, 0x2b, + 0x45, 0xf5, 0x97, 0x64, 0xd0, 0x71, 0x57, 0xc1, 0xd7, 0xa3, 0x55, 0x13, 0x38, 0x59, 0xcd, 0x70, + 0x1b, 0x14, 0x99, 0x1d, 0xf1, 0x5e, 0x4f, 0x39, 0x8d, 0x12, 0x2d, 0x52, 0xcf, 0xc7, 0xfb, 0x3d, + 0x0e, 0x6f, 0xf1, 0x31, 0x40, 0x43, 0x70, 0x0a, 0xd9, 0x60, 0xca, 0x10, 0xd4, 0x2b, 0x52, 0xd6, + 0x60, 0x8c, 0x48, 0xbe, 0x0e, 0x65, 0xf6, 0x3f, 0x26, 0x0e, 0xf2, 0x28, 0xcf, 0x85, 0x95, 0xb8, + 0x91, 0xe9, 0x0b, 0x9a, 0x67, 0x17, 0x6a, 0xd0, 0x88, 0x2f, 0x60, 0x64, 0x9c, 0x3e, 0x4d, 0xd1, + 0xb8, 0x99, 0x3f, 0x34, 0xe0, 0xc2, 0x00, 0x1e, 0x43, 0x3f, 0x43, 0x34, 0x97, 0x6c, 0x48, 0x8a, + 0xc1, 0x2d, 0x36, 0x24, 0x75, 0x62, 0xc8, 0xad, 0x29, 0x3b, 0x8b, 0x55, 0xfe, 0x33, 0xcb, 0x62, + 0x65, 0x1e, 0x1a, 0x30, 0xae, 0x8c, 0xec, 0x53, 0x7c, 0x5d, 0xe0, 0x35, 0x91, 0xce, 0x31, 0x9f, + 0xe0, 0xa5, 0x1e, 0x15, 0xe6, 0xe9, 0x1b, 0xbf, 0x01, 0xb0, 0xe2, 0x84, 0x51, 0xad, 0x19, 0x79, + 0x8f, 0xe8, 0x10, 0x62, 0x2c, 0x89, 0xbe, 0x77, 0x30, 0x3b, 0x2a, 0x23, 0xeb, 0x8b, 0xbe, 0x8f, + 0x19, 0x9a, 0xab, 0x30, 0xda, 0xf0, 0x83, 0xa8, 0x7e, 0xc0, 0xf7, 0xa6, 0x45, 0x1a, 0x36, 0xd5, + 0x63, 0x37, 0x0f, 0x0d, 0xf0, 0xa6, 0x25, 0x8a, 0x98, 0x82, 0x78, 0xc7, 0xa3, 0x2d, 0x57, 0xbd, + 0x76, 0x79, 0xc8, 0x00, 0x16, 0x87, 0x5f, 0xff, 0x00, 0xa6, 0x64, 0x46, 0xb9, 0x8d, 0x95, 0x06, + 0xb6, 0x60, 0x0a, 0xc6, 0xb7, 0x96, 0xac, 0xe5, 0x3b, 0x5f, 0xb5, 0xef, 0x6c, 0xae, 0xac, 0x54, + 0xce, 0x90, 0x09, 0x28, 0x09, 0xc0, 0x42, 0xad, 0x62, 0x90, 0x32, 0x14, 0x97, 0x57, 0x1b, 0x4b, + 0x0b, 0x9b, 0xd6, 0x52, 0x25, 0x77, 0xfd, 0x55, 0x98, 0x4c, 0x2e, 0x55, 0x30, 0xf0, 0x72, 0x0c, + 0xf2, 0x56, 0x6d, 0xbb, 0x72, 0x86, 0x00, 0x8c, 0xae, 0xdf, 0x5f, 0x68, 0xdc, 0xbc, 0x59, 0x31, + 0xae, 0x7f, 0x21, 0xe3, 0x25, 0x67, 0xc6, 0xa9, 0x41, 0xbb, 0x4e, 0xe0, 0x44, 0x94, 0x7f, 0xe6, + 0x41, 0xaf, 0x15, 0x79, 0xdd, 0x16, 0x7d, 0x52, 0x31, 0xae, 0xbf, 0xdd, 0xf7, 0x20, 0x33, 0x39, + 0x07, 0xd3, 0x9b, 0xab, 0xb5, 0x07, 0xf5, 0xe5, 0xbb, 0x9b, 0x6b, 0x9b, 0x0d, 0xfb, 0x41, 0x6d, + 0x63, 0xe1, 0x5e, 0xe5, 0x0c, 0xab, 0xf0, 0x83, 0xb5, 0xc6, 0x86, 0x6d, 0x2d, 0x2d, 0x2c, 0xad, + 0x6e, 0x54, 0x8c, 0xeb, 0xbf, 0x66, 0xc0, 0xa4, 0xfe, 0xc4, 0x1d, 0xb9, 0x02, 0x97, 0x37, 0x1b, + 0x4b, 0x96, 0xbd, 0xb1, 0x76, 0x7f, 0x69, 0xd5, 0xde, 0x6c, 0xd4, 0xee, 0x2e, 0xd9, 0x9b, 0xab, + 0x8d, 0xf5, 0xa5, 0x85, 0xe5, 0x3b, 0xcb, 0x4b, 0x8b, 0x95, 0x33, 0xa4, 0x0a, 0x2f, 0x28, 0x18, + 0xd6, 0xd2, 0xc2, 0xda, 0xd6, 0x92, 0x65, 0xaf, 0xd7, 0x1a, 0x8d, 0xed, 0x35, 0x6b, 0xb1, 0x62, + 0x90, 0x4b, 0x70, 0x3e, 0x03, 0xe1, 0xc1, 0x9d, 0x5a, 0x25, 0xd7, 0x57, 0xb6, 0xba, 0xb4, 0x5d, + 0x5b, 0xb1, 0xeb, 0x6b, 0x1b, 0x95, 0xfc, 0xf5, 0x0f, 0x98, 0x16, 0x92, 0xbc, 0x41, 0x41, 0x8a, + 0x50, 0x58, 0x5d, 0x5b, 0x5d, 0xaa, 0x9c, 0x21, 0xe3, 0x30, 0xb6, 0xbe, 0xb4, 0xba, 0xb8, 0xbc, + 0x7a, 0x97, 0x77, 0x6b, 0x6d, 0x7d, 0xdd, 0x5a, 0xdb, 0x5a, 0x5a, 0xac, 0xe4, 0x58, 0xdf, 0x2d, + 0x2e, 0xad, 0xb2, 0x9a, 0xe5, 0xaf, 0x9b, 0x3c, 0xef, 0xb2, 0x96, 0x36, 0x94, 0xf5, 0xd6, 0xd2, + 0x57, 0x36, 0x96, 0x56, 0x1b, 0xcb, 0x6b, 0xab, 0x95, 0x33, 0xd7, 0x2f, 0xa7, 0x70, 0xe4, 0x48, + 0x34, 0x1a, 0xf7, 0x2a, 0x67, 0xae, 0x7f, 0x1d, 0xca, 0xea, 0x26, 0x4c, 0x2e, 0xc0, 0x8c, 0xfa, + 0x7b, 0x9d, 0x76, 0x5c, 0xaf, 0xb3, 0x5b, 0x39, 0x93, 0x2e, 0xb0, 0x7a, 0x9d, 0x0e, 0x2b, 0xc0, + 0xc6, 0xab, 0x05, 0x1b, 0x34, 0x68, 0x7b, 0x1d, 0xb6, 0xbf, 0x56, 0x72, 0xf5, 0xca, 0xf7, 0xff, + 0xe2, 0xa5, 0x33, 0xdf, 0xff, 0xc1, 0x4b, 0xc6, 0x9f, 0xfe, 0xe0, 0x25, 0xe3, 0xbf, 0xfd, 0xe0, + 0x25, 0x63, 0x67, 0x14, 0x27, 0xfa, 0xad, 0xff, 0x1f, 0x00, 0x00, 0xff, 0xff, 0x0e, 0x77, 0x83, + 0xa8, 0x44, 0xb5, 0x00, 0x00, } func (m *KeepAlive) Marshal() (dAtA []byte, err error) { @@ -19622,13 +19616,6 @@ func (m *WindowsDesktopServiceSpecV3) MarshalToSizedBuffer(dAtA []byte) (int, er i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - if len(m.Hostname) > 0 { - i -= len(m.Hostname) - copy(dAtA[i:], m.Hostname) - i = encodeVarintTypes(dAtA, i, uint64(len(m.Hostname))) - i-- - dAtA[i] = 0x1a - } if len(m.TeleportVersion) > 0 { i -= len(m.TeleportVersion) copy(dAtA[i:], m.TeleportVersion) @@ -24843,10 +24830,6 @@ func (m *WindowsDesktopServiceSpecV3) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } - l = len(m.Hostname) - if l > 0 { - n += 1 + l + sovTypes(uint64(l)) - } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -55100,38 +55083,6 @@ func (m *WindowsDesktopServiceSpecV3) Unmarshal(dAtA []byte) error { } m.TeleportVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hostname", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Hostname = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/api/types/types.proto b/api/types/types.proto index c123c47bd1585..058fa01a12616 100644 --- a/api/types/types.proto +++ b/api/types/types.proto @@ -2776,15 +2776,10 @@ message WindowsDesktopServiceSpecV3 { string Addr = 1 [ (gogoproto.jsontag) = "addr" ]; // TeleportVersion is teleport binary version running this service. string TeleportVersion = 2 [ (gogoproto.jsontag) = "teleport_version" ]; - // Hostname is the desktop service hostname. - string Hostname = 3 [ (gogoproto.jsontag) = "hostname" ]; } -// WindowsDesktopFilter are filters to apply when searching for windows desktops. message WindowsDesktopFilter { - // HostID is the ID of the host the Windows Desktop Service proxying the desktop. string HostID = 1 [ (gogoproto.jsontag) = "host_id" ]; - // Name is the name of the desktop. string Name = 2 [ (gogoproto.jsontag) = "name" ]; } diff --git a/assets/aws/Makefile b/assets/aws/Makefile index 2de8319b906c5..d2c2c39a42fc6 100644 --- a/assets/aws/Makefile +++ b/assets/aws/Makefile @@ -14,7 +14,7 @@ AWS_REGION ?= us-west-2 # This must be a _released_ version of Teleport, i.e. one which has binaries # available for download on https://gravitational.com/teleport/download # Unreleased versions will fail to build. -TELEPORT_VERSION ?= 9.0.1 +TELEPORT_VERSION ?= 8.3.1 # Teleport UID is the UID of a non-privileged 'teleport' user TELEPORT_UID ?= 1007 diff --git a/assets/backport/main.go b/assets/backport/main.go index 804bca8d3aabc..f8bd1eca3b400 100644 --- a/assets/backport/main.go +++ b/assets/backport/main.go @@ -20,6 +20,7 @@ import ( "context" "flag" "fmt" + "io/ioutil" "log" "os" "path/filepath" @@ -99,7 +100,7 @@ func getGithubToken() (string, error) { return "", trace.Wrap(err) } ghConfigPath := filepath.Join(dirname, githubConfigPath) - yamlFile, err := os.ReadFile(ghConfigPath) + yamlFile, err := ioutil.ReadFile(ghConfigPath) if err != nil { return "", trace.Wrap(err) } diff --git a/assets/loadtest/k8s/Makefile b/assets/loadtest/k8s/Makefile index 71f785b0b3260..915636397cc06 100644 --- a/assets/loadtest/k8s/Makefile +++ b/assets/loadtest/k8s/Makefile @@ -115,7 +115,7 @@ install-teleport: install-auth install-proxy install-node install-iot-node .PHONY: delete-teleport delete-teleport: delete-tc delete-nodes delete-proxy delete-auth -# installs grafana, influxdb, and prometheus +# installs grafana and influxdb .PHONY: install-monitor install-monitor: kubectl create configmap grafana-config -n loadtest \ @@ -128,16 +128,13 @@ install-monitor: kubectl apply -f influxdb.yaml @make expand-yaml FILENAME=grafana kubectl apply -f grafana-gen.yaml - @make expand-yaml FILENAME=prometheus - kubectl apply -f prometheus-gen.yaml -# deletes grafana, influxdb, and prometheus deployments, services and configmaps +# deletes grafana and influxdb deployments, services and configmaps .PHONY: delete-monitor delete-monitor: kubectl delete -f influxdb.yaml --ignore-not-found kubectl delete -f grafana-gen.yaml --ignore-not-found kubectl delete configmap grafana-config -n loadtest --ignore-not-found - kubectl delete -f prometheus-gen.yaml --ignore-not-found # installs an etcd cluster .PHONY: install-etcd @@ -463,4 +460,4 @@ fetch-profiles: # output file will be named the same with a -gen suffix, i.e input = test then output will be test-gen.yaml .PHONY: expand-yaml expand-yaml: - @bash -c "set -a && source ./secrets/secrets.env && set +a && envsubst < $(FILENAME).yaml > $(FILENAME)-gen.yaml" + @bash -c "set -a && source ./secrets/secrets.env && set +a && envsubst < $(FILENAME).yaml > $(FILENAME)-gen.yaml" \ No newline at end of file diff --git a/assets/loadtest/k8s/auth-etcd.yaml b/assets/loadtest/k8s/auth-etcd.yaml index 049df023b70de..15d648055ec69 100644 --- a/assets/loadtest/k8s/auth-etcd.yaml +++ b/assets/loadtest/k8s/auth-etcd.yaml @@ -14,9 +14,6 @@ spec: metadata: labels: teleport-role: auth - backend: etcd - prometheus.io/scrape: "true" - prometheus.io/port: "3434" spec: volumes: - name: config diff --git a/assets/loadtest/k8s/auth-firestore.yaml b/assets/loadtest/k8s/auth-firestore.yaml index 3b5c3c3affbd5..a3acd6d26cfdb 100644 --- a/assets/loadtest/k8s/auth-firestore.yaml +++ b/assets/loadtest/k8s/auth-firestore.yaml @@ -14,9 +14,6 @@ spec: metadata: labels: teleport-role: auth - backend: firestore - prometheus.io/scrape: "true" - prometheus.io/port: "3434" spec: volumes: - name: config diff --git a/assets/loadtest/k8s/prometheus.yaml b/assets/loadtest/k8s/prometheus.yaml deleted file mode 100644 index 3649f3f7f92d0..0000000000000 --- a/assets/loadtest/k8s/prometheus.yaml +++ /dev/null @@ -1,291 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: prometheus-loadtest ---- -# Source: prometheus/templates/server/serviceaccount.yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - component: "server" - app: prometheus - release: prometheus - chart: prometheus-15.4.0 - heritage: Helm - name: prometheus-server - namespace: prometheus-loadtest - annotations: - {} ---- -# Source: prometheus/templates/server/cm.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - labels: - component: "server" - app: prometheus - release: prometheus - chart: prometheus-15.4.0 - heritage: Helm - name: prometheus-server - namespace: prometheus-loadtest -data: - alerting_rules.yml: | - {} - alerts: | - {} - prometheus.yml: | - global: - evaluation_interval: 1m - scrape_interval: 1m - scrape_timeout: 10s - remote_write: - - url: ${PROM_REMOTE_URL} - basic_auth: - username: ${PROM_USER} - password: ${PROM_PASSWORD} - rule_files: - - /etc/config/recording_rules.yml - - /etc/config/alerting_rules.yml - - /etc/config/rules - - /etc/config/alerts - scrape_configs: - - job_name: kubernetes-pods - kubernetes_sd_configs: - - role: pod - relabel_configs: - - action: keep - regex: true - source_labels: - - __meta_kubernetes_pod_annotation_prometheus_io_scrape - - action: drop - regex: true - source_labels: - - __meta_kubernetes_pod_annotation_prometheus_io_scrape_slow - - action: replace - regex: (https?) - source_labels: - - __meta_kubernetes_pod_annotation_prometheus_io_scheme - target_label: __scheme__ - - action: replace - regex: (.+) - source_labels: - - __meta_kubernetes_pod_annotation_prometheus_io_path - target_label: __metrics_path__ - - action: replace - regex: ([^:]+)(?::\d+)?;(\d+) - replacement: $1:$2 - source_labels: - - __address__ - - __meta_kubernetes_pod_annotation_prometheus_io_port - target_label: __address__ - - action: labelmap - regex: __meta_kubernetes_pod_annotation_prometheus_io_param_(.+) - replacement: __param_$1 - - action: labelmap - regex: __meta_kubernetes_pod_label_(.+) - - action: replace - source_labels: - - __meta_kubernetes_namespace - target_label: namespace - - action: replace - source_labels: - - __meta_kubernetes_pod_name - target_label: pod - - action: drop - regex: Pending|Succeeded|Failed|Completed - source_labels: - - __meta_kubernetes_pod_phase - recording_rules.yml: | - {} - rules: | - {} ---- -# Source: prometheus/templates/server/clusterrole.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - component: "server" - app: prometheus - release: prometheus - chart: prometheus-15.4.0 - heritage: Helm - name: prometheus-server -rules: - - apiGroups: - - "" - resources: - - nodes - - nodes/proxy - - nodes/metrics - - services - - endpoints - - pods - - ingresses - - configmaps - verbs: - - get - - list - - watch - - apiGroups: - - "extensions" - - "networking.k8s.io" - resources: - - ingresses/status - - ingresses - verbs: - - get - - list - - watch - - nonResourceURLs: - - "/metrics" - verbs: - - get ---- -# Source: prometheus/templates/server/clusterrolebinding.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - component: "server" - app: prometheus - release: prometheus - chart: prometheus-15.4.0 - heritage: Helm - name: prometheus-server -subjects: - - kind: ServiceAccount - name: prometheus-server - namespace: prometheus-loadtest -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: prometheus-server ---- -# Source: prometheus/templates/server/service.yaml -apiVersion: v1 -kind: Service -metadata: - labels: - component: "server" - app: prometheus - release: prometheus - chart: prometheus-15.4.0 - heritage: Helm - name: prometheus-server - namespace: prometheus-loadtest -spec: - ports: - - name: http - port: 80 - protocol: TCP - targetPort: 9090 - selector: - component: "server" - app: prometheus - release: prometheus - sessionAffinity: None - type: "ClusterIP" ---- -# Source: prometheus/templates/server/deploy.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - component: "server" - app: prometheus - release: prometheus - chart: prometheus-15.4.0 - heritage: Helm - name: prometheus-server - namespace: prometheus-loadtest -spec: - selector: - matchLabels: - component: "server" - app: prometheus - release: prometheus - replicas: 1 - template: - metadata: - labels: - component: "server" - app: prometheus - release: prometheus - chart: prometheus-15.4.0 - heritage: Helm - spec: - enableServiceLinks: true - serviceAccountName: prometheus-server - containers: - - name: prometheus-server-configmap-reload - image: "jimmidyson/configmap-reload:v0.5.0" - imagePullPolicy: "IfNotPresent" - args: - - --volume-dir=/etc/config - - --webhook-url=http://127.0.0.1:9090/-/reload - resources: - {} - volumeMounts: - - name: config-volume - mountPath: /etc/config - readOnly: true - - - name: prometheus-server - image: "quay.io/prometheus/prometheus:v2.31.1" - imagePullPolicy: "IfNotPresent" - args: - - --storage.tsdb.retention.time=15d - - --config.file=/etc/config/prometheus.yml - - --storage.tsdb.path=/data - - --web.console.libraries=/etc/prometheus/console_libraries - - --web.console.templates=/etc/prometheus/consoles - - --web.enable-lifecycle - ports: - - containerPort: 9090 - readinessProbe: - httpGet: - path: /-/ready - port: 9090 - scheme: HTTP - initialDelaySeconds: 30 - periodSeconds: 5 - timeoutSeconds: 4 - failureThreshold: 3 - successThreshold: 1 - livenessProbe: - httpGet: - path: /-/healthy - port: 9090 - scheme: HTTP - initialDelaySeconds: 30 - periodSeconds: 15 - timeoutSeconds: 10 - failureThreshold: 3 - successThreshold: 1 - resources: - {} - volumeMounts: - - name: config-volume - mountPath: /etc/config - - name: storage-volume - mountPath: /data - subPath: "" - hostNetwork: false - dnsPolicy: ClusterFirst - securityContext: - fsGroup: 65534 - runAsGroup: 65534 - runAsNonRoot: true - runAsUser: 65534 - terminationGracePeriodSeconds: 300 - volumes: - - name: config-volume - configMap: - name: prometheus-server - - name: storage-volume - emptyDir: - {} diff --git a/assets/loadtest/k8s/proxy.yaml b/assets/loadtest/k8s/proxy.yaml index 4fd0dac33816e..4ef2396e8cb64 100644 --- a/assets/loadtest/k8s/proxy.yaml +++ b/assets/loadtest/k8s/proxy.yaml @@ -14,8 +14,6 @@ spec: metadata: labels: teleport-role: proxy - prometheus.io/scrape: "true" - prometheus.io/port: "3434" spec: volumes: - name: config @@ -102,4 +100,4 @@ spec: targetPort: 3036 protocol: TCP selector: - teleport-role: proxy + teleport-role: proxy \ No newline at end of file diff --git a/assets/loadtest/k8s/secrets/Makefile b/assets/loadtest/k8s/secrets/Makefile index 6151d28f801d0..c0110552f208e 100644 --- a/assets/loadtest/k8s/secrets/Makefile +++ b/assets/loadtest/k8s/secrets/Makefile @@ -23,21 +23,6 @@ env: exit 1; \ fi - @if [ -z ${PROM_REMOTE_URL} ]; then \ - echo "PROM_REMOTE_URL is not set, cannot apply cluster."; \ - exit 1; \ - fi - - @if [ -z ${PROM_USER} ]; then \ - echo "PROM_USER is not set, cannot apply cluster."; \ - exit 1; \ - fi - - @if [ -z ${PROM_PASSWORD} ]; then \ - echo "PROM_PASSWORD is not set, cannot apply cluster."; \ - exit 1; \ - fi - @echo PROXY_IP=$(shell make -C ../../network get-proxy-ip) > secrets.env @echo PROXY_HOST=${PROXY_HOST} >> secrets.env @echo GRAFANA_IP=$(shell make -C ../../network get-grafana-ip) >> secrets.env @@ -46,9 +31,6 @@ env: @echo PROXY_TOKEN=$(shell cat proxy-token) >> secrets.env @echo TC_TOKEN=$(shell cat tc-token) >> secrets.env @echo GCP_PROJECT=$(shell make -C ../../cluster get-project) >> secrets.env - @echo PROM_REMOTE_URL=${PROM_REMOTE_URL} >> secrets.env - @echo PROM_USER=${PROM_USER} >> secrets.env - @echo PROM_PASSWORD=${PROM_PASSWORD} >> secrets.env grafana-pass: openssl rand -base64 32 | tr -d '\n' > grafana-pass @@ -74,4 +56,4 @@ join-tokens: node-token proxy-token tc-token # removes everything .PHONY:clean clean: - rm -rf *-pass *-token *-auth *.env + rm -rf *-pass *-token *-auth *.env \ No newline at end of file diff --git a/build.assets/Dockerfile b/build.assets/Dockerfile index 171abbb1b11a9..8158548290341 100644 --- a/build.assets/Dockerfile +++ b/build.assets/Dockerfile @@ -114,11 +114,6 @@ RUN (curl -L https://github.com/golangci/golangci-lint/releases/download/v1.44.0 RUN (mkdir -p helm-tarball && curl -L https://get.helm.sh/helm-v3.5.2-$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C helm-tarball -xz && \ cp helm-tarball/$(go env GOOS)-$(go env GOARCH)/helm /bin/ && \ rm -r helm-tarball*) -RUN helm plugin install https://github.com/vbehar/helm3-unittest && \ - mkdir -p /home/ci/.local/share/helm && \ - cp -r /root/.local/share/helm/plugins /home/ci/.local/share/helm && \ - chown -R ci /home/ci/.local/share/helm && \ - HELM_PLUGINS=/home/ci/.local/share/helm/plugins helm plugin list # Install bats. RUN (curl -L https://github.com/bats-core/bats-core/archive/v1.2.1.tar.gz | tar -xz && \ diff --git a/build.assets/Makefile b/build.assets/Makefile index b6f1659ef0f50..587054299b88c 100644 --- a/build.assets/Makefile +++ b/build.assets/Makefile @@ -247,16 +247,6 @@ test-sh: buildbox docker run $(DOCKERFLAGS) $(NOROOT) -t $(BUILDBOX) \ /bin/bash -c "make -C $(SRCDIR) BATSFLAGS=$(BATSFLAGS) test-sh" -.PHONY:test-helm -test-helm: buildbox - docker run $(DOCKERFLAGS) $(NOROOT) -t $(BUILDBOX) \ - /bin/bash -c "make -C $(SRCDIR) test-helm" - -.PHONY:test-helm-update-snapshots -test-helm-update-snapshots: - docker run $(DOCKERFLAGS) $(NOROOT) -t $(BUILDBOX) \ - /bin/bash -c "make -C $(SRCDIR) test-helm-update-snapshots" - .PHONY:integration integration: buildbox docker run \ diff --git a/build.assets/build-package.sh b/build.assets/build-package.sh index 22d90fd713d09..17e1bfbc52eb6 100755 --- a/build.assets/build-package.sh +++ b/build.assets/build-package.sh @@ -232,7 +232,7 @@ if [[ "${PACKAGE_TYPE}" == "pkg" ]]; then BUNDLE_ID="com.gravitational.teleport.tsh" PKG_FILENAME="tsh-${TELEPORT_VERSION}.${PACKAGE_TYPE}" else - FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport ${TAR_PATH}/tbot" + FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport" BUNDLE_ID="com.gravitational.teleport" if [[ "${TELEPORT_TYPE}" == "ent" ]]; then PKG_FILENAME="teleport-ent-${TELEPORT_VERSION}.${PACKAGE_TYPE}" @@ -241,8 +241,8 @@ if [[ "${PACKAGE_TYPE}" == "pkg" ]]; then fi fi else - FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport ${TAR_PATH}/tbot ${TAR_PATH}/examples/systemd/teleport.service" - LINUX_BINARY_FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/tbot ${TAR_PATH}/teleport" + FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport ${TAR_PATH}/examples/systemd/teleport.service" + LINUX_BINARY_FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport" LINUX_SYSTEMD_FILE_LIST="${TAR_PATH}/examples/systemd/teleport.service" EXTRA_DOCKER_OPTIONS="" RPM_SIGN_STANZA="" diff --git a/build.assets/gomod/update-api-import-path/main.go b/build.assets/gomod/update-api-import-path/main.go index 0163e9c66ceaf..f89054b2976bc 100644 --- a/build.assets/gomod/update-api-import-path/main.go +++ b/build.assets/gomod/update-api-import-path/main.go @@ -52,17 +52,10 @@ func main() { buildFlags = os.Args[1:] } - newVersion, err := semver.NewVersion(api.Version) - if err != nil { - exitWithError(trace.Wrap(err, "invalid semver version"), nil) - } - - // check the version's prelease section, and exit if it is a - // non beta/alpha pre release. - switch newVersion.PreRelease.Slice()[0] { - case "", "alpha", "beta": - default: - exitWithMessage("the current API version (%v) is not a release or alpha/beta pre-release, continue without updating", newVersion) + // the api module import path should only be updated on releases + newVersion := semver.New(api.Version) + if newVersion.PreRelease != "" { + exitWithMessage("the current API version (%v) is not a release, continue without updating", newVersion) } // get the current api module import path diff --git a/build.assets/install b/build.assets/install index dc6ff9d0cd39f..cc3bed422ccdb 100755 --- a/build.assets/install +++ b/build.assets/install @@ -13,7 +13,7 @@ VARDIR=/var/lib/teleport [ ! $(id -u) != "0" ] || { echo "ERROR: You must be root"; exit 1; } cd $(dirname $0) mkdir -p $VARDIR $BINDIR -cp -f teleport tctl tsh tbot $BINDIR/ || exit 1 +cp -f teleport tctl tsh $BINDIR/ || exit 1 # # What operating system is the user running? diff --git a/build.assets/tooling/cmd/check/main.go b/build.assets/tooling/cmd/check/main.go index ac1b8fe3172b6..16fb6dca2f6af 100644 --- a/build.assets/tooling/cmd/check/main.go +++ b/build.assets/tooling/cmd/check/main.go @@ -90,12 +90,6 @@ func checkLatest(ctx context.Context, tag string, gh github.GitHub) error { if r.GetDraft() { continue } - // Because pre-releases are not published to apt, we do not want to - // consider them when making apt publishing decisions. - // see: https://github.com/gravitational/teleport/issues/10800 - if semver.Prerelease(r.GetTagName()) != "" { - continue - } tags = append(tags, r.GetTagName()) } diff --git a/build.assets/tooling/cmd/check/main_test.go b/build.assets/tooling/cmd/check/main_test.go index 3ca92aab1b9b2..4c2ab57368c7f 100644 --- a/build.assets/tooling/cmd/check/main_test.go +++ b/build.assets/tooling/cmd/check/main_test.go @@ -105,15 +105,6 @@ func TestCheckLatest(t *testing.T) { }, wantErr: require.NoError, }, - { // see https://github.com/gravitational/teleport/issues/10800 - desc: "pass-pre-release", - tag: "v8.3.3", - releases: []string{ - "v9.0.0-beta.1", - "v8.3.2", - }, - wantErr: require.NoError, - }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { diff --git a/constants.go b/constants.go index 1ab18552fc5ab..13bf738f230b4 100644 --- a/constants.go +++ b/constants.go @@ -631,10 +631,6 @@ const ( // EnvSSHSessionInvited is an environment variable listning people invited to a session. EnvSSHSessionInvited = "TELEPORT_SESSION_JOIN_MODE" - - // EnvSSHSessionDisplayParticipantRequirements is set to true or false to indicate if participant - // requirement information should be printed. - EnvSSHSessionDisplayParticipantRequirements = "TELEPORT_SESSION_PARTICIPANT_REQUIREMENTS" ) const ( diff --git a/docker/teleport-ent-quickstart.yml b/docker/teleport-ent-quickstart.yml index f6b25dde811d7..b68f11aa837da 100644 --- a/docker/teleport-ent-quickstart.yml +++ b/docker/teleport-ent-quickstart.yml @@ -3,7 +3,7 @@ services: # The configure container starts, generates a config, writes it to # /etc/teleport/teleport.yaml and then immediately exits. configure: - image: quay.io/gravitational/teleport-ent:9 + image: quay.io/gravitational/teleport-ent:8 container_name: teleport-configure entrypoint: /bin/sh hostname: localhost @@ -14,7 +14,7 @@ services: # This container depends on the config written by the configure container above, so it # sleeps for a second on startup to allow the configure container to run first. teleport: - image: quay.io/gravitational/teleport-ent:9 + image: quay.io/gravitational/teleport-ent:8 container_name: teleport entrypoint: /bin/sh hostname: localhost diff --git a/docker/teleport-lab.yml b/docker/teleport-lab.yml index 3298d11034d5e..46655bd0304ed 100644 --- a/docker/teleport-lab.yml +++ b/docker/teleport-lab.yml @@ -3,7 +3,7 @@ services: # This container depends on the config written by the configure container above, so it # sleeps for a second on startup to allow the configure container to run first. teleport: - image: quay.io/gravitational/teleport-lab:9 + image: quay.io/gravitational/teleport-lab:8 container_name: teleport entrypoint: /bin/sh hostname: luna.teleport @@ -24,7 +24,7 @@ services: # The bootstrap container generates certificates and then immediately exits. bootstrap: - image: quay.io/gravitational/teleport-lab:9 + image: quay.io/gravitational/teleport-lab:8 container_name: teleport-bootstrap entrypoint: /bin/sh command: -c "/etc/teleport.d/scripts/generate-certs.sh" @@ -41,7 +41,7 @@ services: # openssh is a demo of openssh node # openssh: - image: quay.io/gravitational/teleport-lab:9 + image: quay.io/gravitational/teleport-lab:8 container_name: openssh hostname: mars.openssh.teleport entrypoint: /bin/sh @@ -60,7 +60,7 @@ services: # term is a container with a terminal to try things out # term: - image: quay.io/gravitational/teleport-lab:9 + image: quay.io/gravitational/teleport-lab:8 hostname: term container_name: term entrypoint: /bin/sh diff --git a/docker/teleport-quickstart.yml b/docker/teleport-quickstart.yml index a8e1a7a7075d7..a78626201ac0e 100644 --- a/docker/teleport-quickstart.yml +++ b/docker/teleport-quickstart.yml @@ -3,7 +3,7 @@ services: # The configure container starts, generates a config, writes it to # /etc/teleport/teleport.yaml and then immediately exits. configure: - image: quay.io/gravitational/teleport:9 + image: quay.io/gravitational/teleport:8 container_name: teleport-configure entrypoint: /bin/sh hostname: localhost @@ -14,7 +14,7 @@ services: # This container depends on the config written by the configure container above, so it # sleeps for a second on startup to allow the configure container to run first. teleport: - image: quay.io/gravitational/teleport:9 + image: quay.io/gravitational/teleport:8 container_name: teleport entrypoint: /bin/sh hostname: localhost diff --git a/docs/config.json b/docs/config.json index 3c409cf64aedd..d57ae33b2a23d 100644 --- a/docs/config.json +++ b/docs/config.json @@ -278,6 +278,10 @@ "title": "Guides", "slug": "/server-access/guides/", "entries": [ + { + "title": "Ansible", + "slug": "/server-access/guides/ansible/" + }, { "title": "Using Teleport with PAM", "slug": "/server-access/guides/ssh-pam/" @@ -344,6 +348,10 @@ "title": "Federation", "slug": "/kubernetes-access/guides/federation/" }, + { + "title": "Migration", + "slug": "/kubernetes-access/guides/migration/" + }, { "title": "Standalone", "slug": "/kubernetes-access/guides/standalone-teleport/" @@ -378,17 +386,7 @@ }, { "title": "Helm Chart Reference", - "slug": "/kubernetes-access/helm/reference/", - "entries": [ - { - "title": "teleport-cluster", - "slug": "/kubernetes-access/helm/reference/teleport-cluster/" - }, - { - "title": "teleport-kube-agent", - "slug": "/kubernetes-access/helm/reference/teleport-kube-agent/" - } - ] + "slug": "/kubernetes-access/helm/reference/" }, { "title": "Access Controls", @@ -452,10 +450,6 @@ "title": "Self-Hosted CockroachDB", "slug": "/database-access/guides/cockroachdb-self-hosted/" }, - { - "title": "Self-Hosted Redis", - "slug": "/database-access/guides/redis/" - }, { "title": "SQL Server (Preview)", "slug": "/database-access/guides/sql-server-ad/" @@ -523,34 +517,8 @@ "slug": "/desktop-access/getting-started/" }, { - "title": "Access Controls", - "slug": "/desktop-access/rbac/" - }, - { - "title": "Reference", - "slug": "/desktop-access/reference/", - "entries": [ - { - "title": "Configuration", - "slug": "/desktop-access/reference/configuration/" - }, - { - "title": "Audit Events", - "slug": "/desktop-access/reference/audit/" - }, - { - "title": "Clipboard Sharing", - "slug": "/desktop-access/reference/clipboard/" - }, - { - "title": "Session Recording", - "slug": "/desktop-access/reference/sessions/" - }, - { - "title": "CLI", - "slug": "/desktop-access/reference/cli/" - } - ] + "title": "Configuration", + "slug": "/desktop-access/reference/configuration/" }, { "title": "Troubleshooting", @@ -558,44 +526,6 @@ } ] }, - { - "icon": "wand", - "title": "Machine ID", - "entries": [ - { - "title": "Introduction", - "slug": "/machine-id/introduction/" - }, - { - "title": "Getting Started", - "slug": "/machine-id/getting-started/" - }, - { - "title": "Reference", - "slug": "/machine-id/reference/", - "entries": [ - { - "title": "Configuration", - "slug": "/machine-id/reference/configuration/" - }, - { - "title": "CLI", - "slug": "/machine-id/reference/cli/" - } - ] - }, - { - "title": "Guides", - "slug": "/machine-id/guides/", - "entries": [ - { - "title": "Machine ID with Ansible", - "slug": "/machine-id/guides/ansible/" - } - ] - } - ] - }, { "icon": "lock", "title": "Access Controls", @@ -1092,11 +1022,6 @@ "source": "/docs/best-practices/", "destination": "/contributing/documentation/", "permanent": true - }, - { - "source": "/kubernetes-access/guides/migration/", - "destination": "/kubernetes-access/introduction/", - "permanent": true } ] -} +} \ No newline at end of file diff --git a/docs/img/database-access/guides/redis/redisinsight-add-config.png b/docs/img/database-access/guides/redis/redisinsight-add-config.png deleted file mode 100644 index da0298b6c6f02c69fd774aaaf84ec64cce2bcda5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 228417 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 z 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

    f6C(*V~Az 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ǯiW!!6N+b|7);_=YEM<%kY(P=|@co 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 z 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 diff --git a/docs/img/database-access/guides/redis/redisinsight-connected.png b/docs/img/database-access/guides/redis/redisinsight-connected.png deleted file mode 100644 index f0f54ce2f1d26a7363bc62588c299c920470e86e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181286 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

    FHbMw^XYXIk0>Et)9=V2xwr!5n1!nHqAjw>f`F7 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 !bHOi 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%0A1;2O0VGz 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

    n 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
      @@ -49,20 +40,6 @@ their on-disk Teleport certificates. https://developers.yubico.com/WebAuthn/WebAuthn_Browser_Support/) (if using SSH from the Teleport Web UI) - -Teleport FIPS builds disable local users. To configure WebAuthn in order to use -per-session MFA with FIPS builds, provide the following in your `teleport.yaml`: - -```yaml -teleport: - auth_service: - local_auth: false - second_factor: optional - webauthn: - rp_id: teleport.example.com -``` - - ## Configuration Per-session MFA can be enforced cluster-wide or only for some specific roles. @@ -98,8 +75,8 @@ spec: ... ``` -Role-specific enforcement only applies when accessing resources matching a -role's `allow` section. +Role-specific enforcement only applies when accessing SSH nodes or Kubernetes +clusters matching that role's `allow` section. #### Roles example @@ -179,6 +156,7 @@ $ tsh ssh prod3.example.com If per-session MFA was enabled cluster-wide, Jerry would be prompted for MFA even when logging into `dev1.example.com`. + ## Database access MFA Database access supports per-connection MFA. When Jerry connects to the database @@ -217,5 +195,3 @@ Current limitations for this feature are: If you enable per-session MFA checks cluster-wide, you will not be able to use Application access. We're working on integrating per-session MFA checks for these clients. -- For Desktop Access, only WebAuthn devices are supported. Teleport does not - support U2F devices for Desktop Access MFA. diff --git a/docs/pages/access-controls/guides/u2f.mdx b/docs/pages/access-controls/guides/u2f.mdx index 243f4a6e17602..4412c796fd36a 100644 --- a/docs/pages/access-controls/guides/u2f.mdx +++ b/docs/pages/access-controls/guides/u2f.mdx @@ -128,7 +128,7 @@ $ tsh login --proxy=example.com U2F for logging into Teleport is only required for [local -users](../../setup/reference/authentication.mdx#local-no-authentication-connector). SSO users should configure +users](../../setup/reference/authentication.mdx#local). SSO users should configure multi-factor authentication in their SSO provider. diff --git a/docs/pages/access-controls/guides/webauthn.mdx b/docs/pages/access-controls/guides/webauthn.mdx index 01bf5ed02f2b0..014b15e05a362 100644 --- a/docs/pages/access-controls/guides/webauthn.mdx +++ b/docs/pages/access-controls/guides/webauthn.mdx @@ -142,7 +142,7 @@ $ tsh login --proxy=example.com WebAuthn for logging into Teleport is only required for [local users]( - ../../setup/reference/authentication.mdx#local-no-authentication-connector). SSO users should configure + ../../setup/reference/authentication.mdx#local). SSO users should configure multi-factor authentication in their SSO provider. diff --git a/docs/pages/access-controls/reference.mdx b/docs/pages/access-controls/reference.mdx index e45a1c4b63ad7..b8b00518c920a 100644 --- a/docs/pages/access-controls/reference.mdx +++ b/docs/pages/access-controls/reference.mdx @@ -243,12 +243,12 @@ that are more appropriately scoped. ### Role versions -There are currently three supported role versions: `v3`, `v4` and `v5`. `v4` and `v5` roles are +There are currently two supported role versions: `v3` and `v5`. `v5` roles are completely backwards-compatible with `v3`, the only difference lies in the default allow labels which will be applied to the role if they are not -explicitly set. Additionally, `v5` is required to use [Moderated Sessions](./guides/moderated-sessions.mdx). +explicitly set. -Label | `v3` Default | `v4` and `v5` Default +Label | `v3` Default | `v5` Default ------------------ | -------------- | --------------- `node_labels` | `[{"*": "*"}]` if the role has any logins, else `[]` | `[]` `app_labels` | `[{"*": "*"}]` | `[]` @@ -435,17 +435,3 @@ spec: Refer to the [Second Factor - U2F guide](./guides/u2f.mdx) if you have a cluster using the legacy U2F support. - -## Filter fields - -Here is an explanation of the fields used in the `where` and `filter` conditions within this guide. - -| Field | Description | -|----------------------------|-----------------------------------------------------| -| `user.roles` | The list of roles assigned to a user | -| `session.participants` | The list of participants from a session recording | -| `ssh_session.participants` | The list of participants from an SSH session | -| `user.metadata.name` | The user's name | - -Check out our [predicate language](../setup/reference/predicate-language.mdx#scoping-allowdeny-rules-in-role-resources) -guide for a more in depth explanation of the language. diff --git a/docs/pages/api/getting-started.mdx b/docs/pages/api/getting-started.mdx index 9ba9a4d7a162a..57fdeed48436b 100644 --- a/docs/pages/api/getting-started.mdx +++ b/docs/pages/api/getting-started.mdx @@ -16,7 +16,7 @@ Here are the steps we'll walkthrough: ## Prerequisites - Install [Go](https://golang.org/doc/install) (=teleport.golang=)+ and Go development environment. -- Set up Teleport through one of our [getting started guides](../getting-started.mdx). You can also begin a [free trial](https://goteleport.com/signup/) of Teleport Cloud. +- Set up Teleport through the [Getting Started Guide](../getting-started.mdx) ## Step 1/3. Create a user @@ -29,8 +29,8 @@ Here are the steps we'll walkthrough: Create a user `api-admin` with the built-in role `editor`: ```code -# Run this directly on your Auth Server unless you are using Teleport Cloud -# Add a user and login via the Proxy Service +# Run this directly on your auth server +# Add user and login via web proxy $ sudo tctl users add api-admin --roles=editor ``` @@ -56,9 +56,10 @@ $ go mod init client-demo $ go get github.com/gravitational/teleport/api/client ``` -Create a file called `main.go`, modifying the `Addrs` strings as needed: +Create a file `main.go` with the following command, modifying the `Addrs` strings as needed: -```go +```bash +cat > main.go << 'EOF' package main import ( @@ -73,7 +74,6 @@ func main() { clt, err := client.New(ctx, client.Config{ Addrs: []string{ - // Teleport Cloud customers should use .teleport.sh "tele.example.com:443", "tele.example.com:3025", "tele.example.com:3024", @@ -101,7 +101,7 @@ func main() { EOF ``` -Now you can run the program and connect the client to the Teleport Auth Server to fetch the server version. +Now you can run the program and connect the client to the Teleport Auth server to fetch the server version. ```code $ go run main.go diff --git a/docs/pages/application-access/controls.mdx b/docs/pages/application-access/controls.mdx index 5691249eef7f9..b94f9ab26ff2c 100644 --- a/docs/pages/application-access/controls.mdx +++ b/docs/pages/application-access/controls.mdx @@ -79,6 +79,6 @@ allow: - [OIDC](../enterprise/sso/oidc.mdx) - [ADFS](../enterprise/sso/adfs.mdx) - [Azure AD](../enterprise/sso/azuread.mdx) - - [Google Workspace](../enterprise/sso/google-workspace.mdx) + - [GSuite](../enterprise/sso/azuread.mdx) - [Onelogin](../enterprise/sso/one-login.mdx) - [Okta](../enterprise/sso/okta.mdx) diff --git a/docs/pages/application-access/getting-started.mdx b/docs/pages/application-access/getting-started.mdx index 64f5f84ab118d..9b107f121a734 100644 --- a/docs/pages/application-access/getting-started.mdx +++ b/docs/pages/application-access/getting-started.mdx @@ -9,24 +9,13 @@ videoBanner: 5Uwhp3IQMHY Let's connect to Grafana using Teleport Application Access in three steps: - Launch Grafana in a Docker container. -- Install the Teleport Application Service on a node and configure it to proxy Grafana. +- Install Teleport and configure it to proxy Grafana. - Access Grafana through Teleport. ## Prerequisites -- The Teleport Auth Service and Proxy Service, deployed on your own infrastructure or via Teleport Cloud. -- A Docker installation, which we will use to launch Grafana in a container. Alternatively, if you have another web application you'd like to protect with Application Access, you can use that instead. -- A host where you will run the Teleport Application Service. - - -If you have not yet deployed the Auth Service and Proxy Service, you should follow one of our [getting started guides](../getting-started.mdx). - - -We will assume your Teleport cluster is accessible at `teleport.example.com` and `*.teleport.example.com`. You can substitute the address of your Teleport Proxy Service. (For Teleport Cloud customers, this will be similar to `mytenant.teleport.sh`.) - - -(!docs/pages/includes/dns-app-access.mdx!) - +- We will use Docker to launch Grafana in a container. Alternatively, if you have another web application you'd like to protect with App Access, you can use that instead. +- We will assume your Teleport cluster is accessible at `teleport.example.com` and `*.teleport.example.com`. Configured DNS records are required to automatically fetch a [Let's Encrypt](https://letsencrypt.org) certificate. ## Step 1/3. Start Grafana @@ -34,54 +23,51 @@ We've picked Grafana for this tutorial since it's very easy to run with zero configuration required. If you have another web application you'd like to expose, skip over to **Step 2**. -Grafana can be launched in a Docker container with a single command: +Grafana can be launched in a [Docker container](https://grafana.com/docs/grafana/latest/installation/docker/) +with a single command: ```code $ docker run -d -p 3000:3000 grafana/grafana ``` ## Step 2/3. Install and configure Teleport -(!docs/pages/includes/permission-warning.mdx!) -On your Application Service host, download the latest version of Teleport for -your platform from our +Download the latest version of Teleport for your platform from our [downloads page](https://goteleport.com/teleport/download). -### Generate a token +Teleport requires a valid TLS certificate to operate and can fetch one automatically +using Let's Encrypt [ACME](https://letsencrypt.org/how-it-works/) protocol. + +We will assume that you have configured DNS records for `teleport.example.com` +and `*.teleport.example.com` to point to the Teleport node. + +(!docs/pages/includes/permission-warning.mdx!) -A join token is required to authorize a Teleport Application Service agent to -join the cluster. Generate a short-lived join token and save it, for example, -in `/tmp/token` on your Teleport Application Service host: +Let's generate a Teleport config with ACME enabled: ```code -$ tctl tokens add \ - --type=app \ - --app-name=grafana \ - --app-uri=http://localhost:3000 +$ sudo teleport configure --cluster-name=teleport.example.com --acme --acme-email=alice@example.com -o file ``` -### Start Teleport - -On the host where you will run the Teleport Application Service, download the latest version of Teleport for your platform from our -[downloads page](https://goteleport.com/teleport/download). + + Teleport uses [TLS-ALPN-01](https://letsencrypt.org/docs/challenge-types/#tls-alpn-01) + ACME challenge to validate certificate requests which only works on port `443`. Make sure your Teleport proxy is accessible on port `443` when using ACME for certificate management. + Now start Teleport and point it to the application endpoint: ```code -$ sudo teleport app start \ - --name=grafana \ - --token=/tmp/token \ - --uri=http://localhost:3000 \ - --auth-server=https://teleport.example.com:3080 +$ sudo teleport start \ + --roles=proxy,auth,app \ + --app-name=grafana \ + --app-uri=http://localhost:3000 ``` -Change `https://teleport.example.com:3080` to the address and port of your Teleport Proxy Server. If you are a Teleport Cloud cluster, use your tenant's subdomain, e.g., `mytenant.teleport.sh`. - -Make sure to update `--app-name` and `--app-uri` accordingly if you're using your own web application. - -The `--token` flag points to the file on the Application Service host where we stored the token that we generated earlier. - -### Create a user +Make sure to update `--app-name` and `--app-uri` accordingly if you're using +your own web application. Next, let's create a user to access the application we've just connected. Teleport has a built-in role called `access` that allows users to access cluster resources. Create a local user assigned this role: @@ -89,23 +75,19 @@ Next, let's create a user to access the application we've just connected. Telepo $ tctl users add --roles=access alice ``` -The command will output a signup link. Use it to choose a password and set up a second factor. After that, it will take you to the Teleport Web UI. +The command will output a signup link. Use it to choose a password and set up a second factor. After that, it will take you to the Teleport web UI. ## Step 3/3. Access the application There are a couple of ways to access the proxied application. -Every application is assigned a public address that you can use to navigate to +Every application is assigned a public address which you use to navigate to the application directly. In our sample Grafana application we have provided a public address with the `--app-public-addr` flag, so go to `https://grafana.teleport.example.com` -to access the app. - -Replace `grafana` with the value of the `--app-name` flag you used when starting the Teleport Application Service and `teleport.example.com` with the address of your Proxy Service. - -If you're not logged into Teleport, +(replace with your app public address) to access the app. If you're not logged into Teleport, you will need to authenticate before the application will show. -Alternatively, log in to the Teleport Web Interface at `https://teleport.example.com` (replace with your Proxy Service's public address). All available applications are displayed on the Applications tab. Click on the Grafana application tile to access it. +Alternatively, log into the Teleport Web Interface at `https://teleport.example.com` (replace with your proxy public address). All available applications are displayed on the Applications tab. Click on the Grafana application tile to access it. ## Next steps @@ -115,4 +97,3 @@ Dive deeper into the topics relevant to your Application Access use-case: - Learn about integrating with [JWT tokens](./guides/jwt.mdx) for auth. - Learn how to use Application Access with [RESTful APIs](./guides/api-access.mdx). - See full configuration and CLI [reference](./reference.mdx). -- Read about how Let's Encrypt uses the [ACME protocol](https://letsencrypt.org/how-it-works/). \ No newline at end of file diff --git a/docs/pages/application-access/guides/api-access.mdx b/docs/pages/application-access/guides/api-access.mdx index 7d45468932a50..489030739557b 100644 --- a/docs/pages/application-access/guides/api-access.mdx +++ b/docs/pages/application-access/guides/api-access.mdx @@ -10,7 +10,6 @@ tools like [curl](https://man7.org/linux/man-pages/man1/curl.1.html) or Postman. ## Prerequisites -You will need a running Teleport cluster, either self hosted or in Teleport Cloud. We'll assume that you followed the [Getting Started](../getting-started.mdx) guide or the general [App Access Usage](./connecting-apps.mdx) guide to connect the web application providing an API to Teleport. diff --git a/docs/pages/application-access/guides/aws-console.mdx b/docs/pages/application-access/guides/aws-console.mdx index a5f81b35aad81..8ffa0ee598a93 100644 --- a/docs/pages/application-access/guides/aws-console.mdx +++ b/docs/pages/application-access/guides/aws-console.mdx @@ -6,21 +6,19 @@ videoBanner: GVcy_rffxQw # AWS Management Console Access -Teleport can automatically sign your users into the AWS management console with +Teleport can automatically sign your users into AWS management console with appropriate IAM roles. This guide will explain how to: -- Connect your AWS account(s) to Teleport. -- Set up example AWS IAM Read Only and Power User roles. +- Connect your AWS account(-s) to Teleport. +- Setup example AWS IAM Read Only and Power User roles. - Use Teleport's role-based access control with AWS IAM roles. - View Teleport users' AWS console activity in CloudTrail. - Access the AWS Command Line Interface (CLI) through Teleport. - ## Prerequisites -- A running Teleport cluster, either self hosted or in Teleport Cloud. -- A Teleport Node with Application Access enabled. Follow the [Getting Started](../getting-started.mdx) +- Teleport with Application Access. Follow [Getting Started](../getting-started.mdx) or [Connecting Apps](./connecting-apps.mdx) guides to get it running. - IAM permissions in the AWS account you want to connect. - AWS EC2 or other instance where you can assign a IAM Security Role for the Teleport Agent. @@ -36,7 +34,7 @@ Otherwise, you will receive "400 Bad Request" errors from AWS. ## Step 1. [Optional] Configure Read Only and Power User roles -AWS provides the `ReadOnlyAccess` and `PowerUserAccess` IAM policies that can be incorporated into roles. **Skip this step** if you already have the roles you want to provide access to. +AWS provides the `ReadOnlyAccess` and `PowerUserAccess` IAM policies that can be incorporated into roles. Skip this step if you already have the roles you want to provide access to. These policies may provide too much or not enough access for your intentions. Validate these meet your expectations if you plan on using them. @@ -69,15 +67,16 @@ Enter a role name and press create role. Follow the same steps and select `PowerUserAccess` IAM Policy to create a `ExamplePowerUser` role. -## Step 2. Update IAM role trust relationships +## Step 2. Update IAM roles trust relationships -This step is only required if you are allowing access from another account. The trust relationship will already exist for the same account. +This step is only required if you are allowing access from another account. The trust relationship will already exist for the same account. -Teleport uses AWS [federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html) to generate sign-in URLs for users, which relies on the `AssumeRole` API -for getting temporary security credentials. -You will need to update your IAM roles' "Trusted entities" to include your AWS account ID. +Teleport uses AWS [Federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html) +service to generate sign-in URLs for users, which relies on the `AssumeRole` API +for getting temporary security credentials. As such, you would first need to +update your IAM roles' "Trusted entities" to include AWS account ID. Go to the [Roles](https://console.aws.amazon.com/iamv2/home#/roles) list, pick a role and create the following trust policy for it by clicking on "Edit trust @@ -100,9 +99,8 @@ relationship" button on the "Trust relationships" tab: See [How to use trust policies with IAM roles](https://aws.amazon.com/blogs/security/how-to-use-trust-policies-with-iam-roles/) for more details. After saving the trust policy, the account will show as a -trusted entity. - -From the EC2 dashboard select Actions -> Security -> Modify IAM Role. +trusted entity: +From the EC2 dashboard select Actions -> Security -> Modify IAM Role ![AWS trusted entities](../../../img/application-access/aws-trusted-entities@2x.png) Do this for each IAM role your Teleport users will need to assume. @@ -138,7 +136,7 @@ is using. The next step is to give your Teleport users permissions to assume IAM roles. -You can do this by creating a role with the `aws_role_arns` field listing all IAM +You can do this by creating a role with `aws_role_arns` field listing all IAM role ARNs this particular role permits its users to assume: ```yaml @@ -234,8 +232,8 @@ an IAM role selector: ![IAM role selector](../../../img/application-access/iam-role-selector.png) -Click on the role you want to assume and you will get redirected to the AWS -management console, signed in with the selected role. +Click on the role you want to assume and you will get redirected to AWS +management console signed in with the selected role. In the console's top-right corner you should see that you're logged in through federated login and the name of your assumed IAM role: @@ -254,7 +252,7 @@ Note that your federated login session is marked with your Teleport username. To view CloudTrail events for your federated sessions, navigate to the CloudTrail [dashboard](https://console.aws.amazon.com/cloudtrail/home) and go to "Event history". -Each Teleport federated login session uses a Teleport username as the federated +Each Teleport federated login session uses Teleport username as the federated username which you can search for to get the events history: ![CloudTrail](../../../img/application-access/cloud-trail.png) @@ -272,7 +270,7 @@ Logged into AWS app awsconsole-test. Example AWS CLI command: $ tsh aws s3 ls ``` -The `--aws-role` flag allows you to specify the AWS IAM role to assume when accessing AWS API. You can either +The `--aws-role` flag allows to specify the AWS IAM role to assume when accessing AWS API. You can either provide a role name like `--aws-role ExamplePowerUser` or a full role ARN `arn:aws:iam::1234567890:role/ExamplePowerUser` Now you can use the `tsh aws` command like the native `aws` command-line tool: diff --git a/docs/pages/application-access/guides/connecting-apps.mdx b/docs/pages/application-access/guides/connecting-apps.mdx index d3c74ae36b496..4468d6544d894 100644 --- a/docs/pages/application-access/guides/connecting-apps.mdx +++ b/docs/pages/application-access/guides/connecting-apps.mdx @@ -54,22 +54,36 @@ applications. When setting up Teleport, the minimum requirement is a certificate for the proxy and a wildcard certificate for its sub-domain. This is where everyone will log into Teleport. - -(!docs/pages/includes/dns-app-access.mdx!) - - In our example: - `teleport.example.com` will host the Access Plane. - `*.teleport.example.com` will host all of the applications e.g. `grafana.teleport.example.com`. -You can either configure Teleport to obtain a TLS certificate via Let's Encrypt or use an existing certificate and private key (e.g. using [certbot](https://certbot.eff.org/)). - - -(!docs/pages/includes/acme.mdx!) - - -If you have obtained certificate/key pairs for your domain they can be provided directly +Teleport can obtain a certificate automatically from Let's Encrypt using +[ACME](https://letsencrypt.org/how-it-works/) protocol. + +Enable ACME in your proxy config: + +```yaml +proxy_service: + enabled: "yes" + web_listen_addr: "0.0.0.0:443" + public_addr: "teleport.example.com:443" + acme: + enabled: "yes" + email: alice@example.com +``` + + + Teleport uses [TLS-ALPN-01](https://letsencrypt.org/docs/challenge-types/#tls-alpn-01) + ACME challenge to validate certificate requests which only works on port `443`. Make sure your Teleport proxy is accessible on port `443` when using ACME for certificate management. + + +Alternatively, if you have obtained certificate/key pairs for your domain +(e.g. using [certbot](https://certbot.eff.org/)), they can be provided directly to the proxy service: ```yaml @@ -83,8 +97,6 @@ proxy_service: - key_file: "/etc/letsencrypt/live/*.teleport.example.com/privkey.pem" cert_file: "/etc/letsencrypt/live/*.teleport.example.com/fullchain.pem" ``` - - ### Create a user diff --git a/docs/pages/cloud/faq.mdx b/docs/pages/cloud/faq.mdx index fc28f895a16cc..327a5199c7172 100644 --- a/docs/pages/cloud/faq.mdx +++ b/docs/pages/cloud/faq.mdx @@ -29,7 +29,7 @@ Currently there is no way to provide your own bucket. ## How do I add nodes to Teleport Cloud? You can connect servers, kubernetes clusters, databases and applications -using [reverse tunnels](../setup/admin/adding-nodes.mdx). +using [reverse tunnels](../setup/admin/adding-nodes.mdx#adding-a-node-located-behind-nat). There is no need to open any ports on your infrastructure for inbound traffic. @@ -56,7 +56,7 @@ $ tctl tokens add --type=node ## Are dynamic node tokens available? After [connecting](#how-can-i-access-the-tctl-admin-tool) `tctl` to Teleport Cloud, users can generate -[dynamic tokens](../setup/admin/adding-nodes.mdx): +[dynamic tokens](../setup/admin/adding-nodes.mdx#short-lived-dynamic-tokens): ```code $ tctl nodes add --ttl=5m --roles=node,proxy --token=$(uuid) diff --git a/docs/pages/database-access/getting-started.mdx b/docs/pages/database-access/getting-started.mdx index 736f67050c1a5..51e4f6ff3cc42 100644 --- a/docs/pages/database-access/getting-started.mdx +++ b/docs/pages/database-access/getting-started.mdx @@ -14,7 +14,7 @@ Here's an overview of what we will do: 2. Download and install Teleport (=teleport.version=) and connect it to the Aurora database. 3. Connect to the Aurora database via Teleport. -## Step 1/3. Set up Aurora +## Step 1/3. Setup Aurora In order to allow Teleport connections to an Aurora instance, it needs to support IAM authentication. @@ -73,28 +73,33 @@ GRANT rds_iam TO alice; For more information about connecting to the PostgreSQL instance directly, see Amazon [documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ConnectToPostgreSQLInstance.html). -## Step 2/3. Set up Teleport +## Step 2/3. Setup Teleport Teleport Database Access is available starting from `6.0.0` release. Download the appropriate version of Teleport for your platform from our [downloads page](https://goteleport.com/teleport/download). - -### Configure TLS - Teleport requires a valid TLS certificate to operate and can fetch one automatically -using Let's Encrypt. +using Let's Encrypt [ACME](https://letsencrypt.org/how-it-works/) protocol. -(!docs/pages/includes/acme.mdx!) +We will assume that you have configured DNS records for `teleport.example.com` and +`*.teleport.example.com` to point to the node where you're launching Teleport. -We will assume that you have configured a DNS record for `teleport.example.com` to point to the node where you're launching Teleport. +Let's generate a Teleport config with ACME enabled: -
      -(!docs/pages/includes/dns-app-access.mdx!) -
      +```code +$ teleport configure --cluster-name=teleport.example.com --acme --acme-email=alice@example.com > /tmp/teleport.yaml +``` -### Start Teleport + + Teleport's ACME protocol integration currently requires web proxy to run on + port 443 so open /tmp/teleport.yaml and update `proxy_service.web_listen_addr` + and `proxy_service.public_addr` to use port 443 instead of the default 3080. + Now start Teleport and point it to your Aurora database instance. Make sure to update the database endpoint and region appropriately. @@ -113,11 +118,9 @@ $ sudo teleport start --config=/tmp/teleport.yaml \ title="AWS Credentials" > The node that connects to the database should have AWS credentials configured - with the policy from [step 1](#step-13-set-up-aurora). + with the policy from [step 1](#step-13-setup-aurora).
      -### Create a user and role - Create the role that will allow a user to connect to any database using any database account: diff --git a/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx b/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx index fd06d0620a521..45bd48fa4fd67 100644 --- a/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx +++ b/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx @@ -36,7 +36,7 @@ This guide will help you to: (!docs/pages/includes/database-access/token.mdx!) - + Start the Teleport Database Service, pointing the `--auth-server` flag to the address of your Teleport Proxy Service: ```code @@ -142,7 +142,7 @@ Log into your Teleport cluster. Your CockroachDB cluster should appear in the list of available databases: - + ```code $ tsh login --proxy=teleport.example.com --user=alice $ tsh db ls diff --git a/docs/pages/database-access/guides/gui-clients.mdx b/docs/pages/database-access/guides/gui-clients.mdx index 9e881f6b6bb51..eae7b53cd1831 100644 --- a/docs/pages/database-access/guides/gui-clients.mdx +++ b/docs/pages/database-access/guides/gui-clients.mdx @@ -192,34 +192,3 @@ keep the Password field empty: ![DataGrip connection options](../../../img/database-access/guides/sqlserver/datagrip-connection@2x.png) Click OK to connect. - -## Redis Insight - - - Teleport's Redis Insight integration only supports Redis standalone instances. - - -After opening Redis Insight click `ADD REDIS DATABASE`. - -![Redis Insight Startup Screen](../../../img/database-access/guides/redis/redisinsight-startup.png) - -Log in to your Redis instance with a Redis user first by using: - -`tsh db login --db-user=alice redis-db-name`. - -Click `Add Database Manually`. Use `127.0.0.1` as the `Host` and port printed by `tsh proxy db` as described [here](#get-connection-information). - -Provide your Redis username as `Username` and password as `Password`. - -![Redis Insight Configuration](../../../img/database-access/guides/redis/redisinsight-add-config.png) - -Next, check the `Use TLS` and `Verify TLS Certificates` boxes and copy the CA certificate returned by `tsh proxy db`. -Copy the private key and certificate to corresponding fields. - -Click `Add Redis Database`. - -![Redis Insight TLS Configuration](../../../img/database-access/guides/redis/redisinsight-tls-config.png) - -Congratulations! You have just connected to your Redis instance. - -![Redis Insight Connected](../../../img/database-access/guides/redis/redisinsight-connected.png) diff --git a/docs/pages/database-access/guides/mongodb-atlas.mdx b/docs/pages/database-access/guides/mongodb-atlas.mdx index d674516142244..96f900cc33837 100644 --- a/docs/pages/database-access/guides/mongodb-atlas.mdx +++ b/docs/pages/database-access/guides/mongodb-atlas.mdx @@ -28,7 +28,7 @@ In this guide you will: (!docs/pages/includes/database-access/token.mdx!) - + Start the Teleport Database Service, pointing the `--auth-server` flag at the address of your Teleport Proxy Service: ```code @@ -74,7 +74,7 @@ If you're starting the Database Agent with a YAML configuration instead of CLI f the following config is equivalent to the `teleport db start` command shown earlier: - + ```yaml teleport: auth_token: "/tmp/token" @@ -193,7 +193,7 @@ certificate with `CN=alice` subject. Log into your Teleport cluster and see available databases: - + ```code $ tsh login --proxy=teleport.example.com --user=alice diff --git a/docs/pages/database-access/guides/mysql-self-hosted.mdx b/docs/pages/database-access/guides/mysql-self-hosted.mdx index 63b4eda176abe..d4a9788beb88b 100644 --- a/docs/pages/database-access/guides/mysql-self-hosted.mdx +++ b/docs/pages/database-access/guides/mysql-self-hosted.mdx @@ -131,17 +131,48 @@ tunnel. ### Start Database Service with Config File -Generate a configuration file at `/etc/teleport.yaml` for the Database Service: - -```code -$ teleport db configure create \ - -o file \ - --token=/tmp/token \ - --proxy=teleport.example.com:3080 \ - --name=test \ - --protocol=mysql \ - --uri=mysql.example.com:3306 \ - --labels=env=dev +Below is an example of a database service configuration file that proxies +a single self-hosted MySQL database: + +```yaml +teleport: + # The data_dir should be a different location if running on the same + # machine as Teleport auth and proxy. + data_dir: /var/lib/teleport-db + nodename: teleport-db-instance + # Teleport invitation token used to join a cluster. + # can also be passed on start using --token flag + auth_token: /tmp/token + # Proxy address to connect to. Note that it has to be the proxy address + # because database service always connects to the cluster over reverse + # tunnel. + auth_servers: + - teleport.example.com:3080 +db_service: + enabled: "yes" + # This section contains definitions of all databases proxied by this + # service, can contain multiple items. + databases: + # Name of the database proxy instance, used to reference in CLI. + - name: "example" + # Free-form description of the database proxy instance. + description: "Example MySQL" + # Database protocol. + protocol: "mysql" + # Database address, MySQL/MariaDB server endpoint in this case. + # + # Note: this URI's hostname must match the host name specified via --host + # flag to tctl auth sign command. + uri: "mysql.example.com:3306" + # Labels to assign to the database, used in RBAC. + static_labels: + env: dev +auth_service: + enabled: "no" +ssh_service: + enabled: "no" +proxy_service: + enabled: "no" ``` + + Use this policy if your Teleport database agent runs as an IAM user (for + example, uses AWS credentials file). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "redshift:DescribeClusters", + "iam:GetUserPolicy", + "iam:PutUserPolicy", + "iam:DeleteUserPolicy" + ], + "Resource": "*" + } + ] + } + ``` + + + Use this policy if your Teleport database agent runs as an IAM role (for + example, on an EC2 instance with attached IAM role). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "redshift:DescribeClusters", + "iam:GetRolePolicy", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy" + ], + "Resource": "*" + } + ] + } + ``` + + + +### Create an IAM permission boundary for Teleport +Since Teleport will be managing its own IAM policies for access to Redshift +databases, you need to create a permission boundary to limit its effective +range of permissions. + +Create another managed policy that will serve as a permission boundary on the +same [Policies](https://console.aws.amazon.com/iamv2/home#/policies) page of +the AWS Management Console. + +In addition to the set of permissions you created above, the boundary should +also include `redshift:GetClusterCredentials`, which will grant your Teleport +agent the permission to generate temporary credentials to authenticate with +Redshift databases. + +Similar to the permission polices you created above, the exact set of required +permissions for the permission boundary depends on the IAM identity your +Teleport database agent will be using (IAM user or IAM role). + + + + Use this permission boundary if your Teleport database agent runs as an IAM + user (for example, uses AWS credentials file). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "redshift:DescribeClusters", + "redshift:GetClusterCredentials", + "iam:GetUserPolicy", + "iam:PutUserPolicy", + "iam:DeleteUserPolicy" + ], + "Resource": "*" + } + ] + } + ``` + + + Use this permission boundary if your Teleport database agent runs as an IAM + role (for example, on an EC2 instance with attached IAM role). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "redshift:DescribeClusters", + "redshift:GetClusterCredentials", + "iam:GetRolePolicy", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy" + ], + "Resource": "*" + } + ] + } + ``` + + + +### Attach the policy and boundary to an IAM identity +(!docs/pages/includes/database-access/attach-iam-policies.mdx!) + + + If you prefer to self-manage IAM for your Redshift databases, see [AWS + reference](../reference/aws.mdx) for details. + + +## Step 4/5. Start the database agent +(!docs/pages/includes/database-access/token.mdx!) + +Create the database agent configuration e.g. in `/etc/teleport.yaml`: + +```yaml +teleport: + data_dir: /var/lib/teleport + auth_token: /tmp/token + auth_servers: + - teleport.example.com:443 # Teleport proxy address to connect to +auth_service: + enabled: "no" +proxy_service: + enabled: "no" +db_service: + enabled: "yes" + aws: # Matchers for registering AWS-hosted databases. + - types: ["redshift"] + regions: ["us-west-1"] # AWS regions to fetch databases from + tags: # AWS database resource tags to match + "*": "*" +``` -## Step 5/6. Start the database agent +Start the database agent: ```code $ teleport start --config=/etc/teleport.yaml @@ -64,7 +195,7 @@ may not propagate immediately and can take a few minutes to come into effect. for more information. -## Step 6/6. Connect +## Step 5/5. Connect Once the database agent has started and joined the cluster, log in to see the registered databases. Replace `--proxy` with the address of your Teleport Proxy Service, diff --git a/docs/pages/database-access/guides/postgres-self-hosted.mdx b/docs/pages/database-access/guides/postgres-self-hosted.mdx index e7c2a818ae1ca..162873d4190aa 100644 --- a/docs/pages/database-access/guides/postgres-self-hosted.mdx +++ b/docs/pages/database-access/guides/postgres-self-hosted.mdx @@ -111,17 +111,43 @@ tunnel. ### Start Database service with config file -Generate a configuration file at `/etc/teleport.yaml` for the Database Service: - -```code -$ teleport db configure create \ - -o file \ - --token=/tmp/token \ - --proxy=teleport.example.com:3080 \ - --name=test \ - --protocol=postgres \ - --uri=postgres.example.com:5432 \ - --labels=env=dev +Below is an example of a database service configuration file that proxies +a single self-hosted PostgreSQL database: + +```yaml +teleport: + data_dir: /var/lib/teleport-db + nodename: test + # Proxy address to connect to. Note that it has to be the proxy address + # because database service always connects to the cluster over reverse + # tunnel. + auth_servers: + - teleport.example.com:3080 +db_service: + enabled: "yes" + # This section contains definitions of all databases proxied by this + # service, can contain multiple items. + databases: + # Name of the database proxy instance, used to reference in CLI. + - name: "example" + # Free-form description of the database proxy instance. + description: "Example PostgreSQL" + # Database protocol. + protocol: "postgres" + # Database address, PostgreSQL server endpoint in this case. + # + # Note: this URI's hostname must match the host name specified via --host + # flag to tctl auth sign command. + uri: "postgres.example.com:5432" + # Labels to assign to the database, used in RBAC. + static_labels: + env: dev +auth_service: + enabled: "no" +ssh_service: + enabled: "no" +proxy_service: + enabled: "no" ``` diff --git a/docs/pages/database-access/guides/rds.mdx b/docs/pages/database-access/guides/rds.mdx index 8b64256228a8c..fba667601e2d2 100644 --- a/docs/pages/database-access/guides/rds.mdx +++ b/docs/pages/database-access/guides/rds.mdx @@ -1,7 +1,6 @@ --- -title: Database Access with AWS RDS and Aurora -h1: Database Access with AWS RDS and Aurora for PostgreSQL, MySQL and MariaDB -description: How to configure Teleport Database Access with AWS RDS and Aurora for PostgreSQL, MySQL and MariaDB. +title: Database Access with AWS RDS and Aurora for PostgreSQL and MySQL +description: How to configure Teleport Database Access with AWS RDS and Aurora for PostgreSQL and MySQL. --- This guide will help you to: @@ -10,10 +9,9 @@ This guide will help you to: - Set up Teleport to access your RDS instances and Aurora clusters. - Connect to your databases through Teleport. - - The following products are not compatible with Database Access as they don't support IAM authentication: - - Aurora Serverless. - - RDS MariaDB versions lower than 10.6. + + Aurora Serverless does not support IAM authentication at the time of this + writing so it can't be used with Database Access. ## Prerequisites @@ -22,42 +20,268 @@ This guide will help you to: - AWS account with RDS and Aurora databases and permissions to create and attach IAM policies. -## Step 1/7. Install Teleport +## Step 1/6. Install Teleport (!docs/pages/includes/database-access/start-auth-proxy.mdx!) -## Step 2/7. Create a Teleport user +## Step 2/6. Create Teleport user (!docs/pages/includes/database-access/create-user.mdx!) -## Step 3/7. Create a database agent configuration +## Step 3/6. Configure IAM -(!docs/pages/includes/database-access/token.mdx!) +### Create IAM policy for Teleport -Create the Database Service configuration: +Teleport needs AWS IAM permissions to be able to: -```code -$ teleport db configure create \ - -o file \ - --proxy=teleport.example.com:3080 \ - --token=/tmp/token \ - --rds-discovery=us-west-1 -``` +- Discover and register RDS instances and Aurora clusters. +- Configure IAM authentication for them. -The command will generate a database agent configuration with RDS/Aurora -database auto-discovery enabled on the `us-west-1` region and place it at the -`/etc/teleport.yaml` location. +Go to the [Policies](https://console.aws.amazon.com/iamv2/home#/policies) page +and create a managed IAM policy for the database agent. -## Step 4/7. Create an IAM policy for Teleport +The exact set of required permissions depends on whether you're connecting to +RDS instances or Aurora clusters (or both), as well as the IAM identity your +Teleport database agent will be using (user or role). -Teleport needs AWS IAM permissions to be able to: + + + Use this policy if you're connecting to RDS instances and your Teleport + database agent runs as IAM user (for example, uses AWS credentials file). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBInstances", + "rds:ModifyDBInstance", + "iam:GetUserPolicy", + "iam:PutUserPolicy", + "iam:DeleteUserPolicy" + ], + "Resource": "*" + } + ] + } + ``` + + + Use this policy if you're connecting to RDS instances and your Teleport + database agent runs as IAM role (for example, on an EC2 instance with + attached IAM role). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBInstances", + "rds:ModifyDBInstance", + "iam:GetRolePolicy", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy" + ], + "Resource": "*" + } + ] + } + ``` + + + Use this policy if you're connecting to Aurora clusters and your Teleport + database agent runs as IAM user (for example, uses AWS credentials file). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBClusters", + "rds:ModifyDBCluster", + "iam:GetUserPolicy", + "iam:PutUserPolicy", + "iam:DeleteUserPolicy" + ], + "Resource": "*" + } + ] + } + ``` + + + Use this policy if you're connecting to Aurora clusters and your Teleport + database agent runs as IAM role (for example, on an EC2 instance with + attached IAM role). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBClusters", + "rds:ModifyDBCluster", + "iam:GetRolePolicy", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy" + ], + "Resource": "*" + } + ] + } + ``` + + -- Discover and register RDS instances and Aurora clusters. -- Configure IAM authentication for them. +### Create IAM permission boundary for Teleport + +Since Teleport will be managing its own IAM policies for access to RDS and +Aurora databases, you need to create a permission boundary to limit its +effective range of permissions. -(!docs/pages/includes/database-access/aws-bootstrap.mdx!) +Create another managed policy that will serve as a permission boundary on the +same [Policies](https://console.aws.amazon.com/iamv2/home#/policies) page. -## Step 5/7. Start the database agent +The boundary should have the same set of permissions as the IAM policy you +created above, plus `rds-db:connect`. + + + + Use this permission boundary policy if you're connecting to RDS instances and + your Teleport database agent runs as IAM user (for example, uses AWS + credentials file). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBInstances", + "rds:ModifyDBInstance", + "iam:GetUserPolicy", + "iam:PutUserPolicy", + "iam:DeleteUserPolicy", + "rds-db:connect" + ], + "Resource": "*" + } + ] + } + ``` + + + Use this permission boundary policy if you're connecting to RDS instances and + your Teleport database agent runs as IAM role (for example, on an EC2 instance + with attached IAM role). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBInstances", + "rds:ModifyDBInstance", + "iam:GetRolePolicy", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy", + "rds-db:connect" + ], + "Resource": "*" + } + ] + } + ``` + + + Use this permission boundary policy if you're connecting to Aurora clusters + and your Teleport database agent runs as IAM user (for example, uses AWS + credentials file). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBClusters", + "rds:ModifyDBCluster", + "iam:GetUserPolicy", + "iam:PutUserPolicy", + "iam:DeleteUserPolicy", + "rds-db:connect" + ], + "Resource": "*" + } + ] + } + ``` + + + Use this permission boundary policy if you're connecting to Aurora clusters + and your Teleport database agent runs as IAM role (for example, on an EC2 + instance with attached IAM role). + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBClusters", + "rds:ModifyDBCluster", + "iam:GetRolePolicy", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy", + "rds-db:connect" + ], + "Resource": "*" + } + ] + } + ``` + + + +### Attach the policy and boundary to an IAM identity +(!docs/pages/includes/database-access/attach-iam-policies.mdx!) + + + If you prefer to self-manage IAM for your RDS databases, take a look at + [AWS reference](../reference/aws.mdx) for details. + + +## Step 4/6. Start the database agent + +(!docs/pages/includes/database-access/token.mdx!) + +Create the database agent configuration e.g. in `/etc/teleport.yaml`: + +```yaml +teleport: + data_dir: /var/lib/teleport + auth_token: /tmp/token + auth_servers: + # Teleport proxy address to connect to. + # For Teleport Cloud users, this will resemble mytenant.teleport.sh + - teleport.example.com:3080 +auth_service: + enabled: "no" +proxy_service: + enabled: "no" +db_service: + enabled: "yes" + aws: + - types: ["rds"] + regions: ["us-west-1"] # AWS regions to fetch databases from + tags: # AWS database resource tags to match + "*": "*" +``` Start the database agent: @@ -75,11 +299,12 @@ policies for the discovered databases. Keep in mind that AWS IAM changes may not propagate immediately and can take a few minutes to come into effect. - The Teleport Database Service uses the default - credential provider chain to find AWS credentials. See [Specifying Credentials](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) for more information. + The Teleport database agent uses the default credential provider chain to find AWS + credentials. See [Specifying Credentials](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) + for more information. -## Step 6/7. Create a database IAM user +## Step 5/6. Create database IAM user Database users must allow IAM authentication in order to be used with Database Access for RDS. See below how to enable it for your database engine. @@ -93,8 +318,8 @@ Access for RDS. See below how to enable it for your database engine. GRANT rds_iam TO alice; ``` - - MySQL and MariaDB users must have the RDS authentication plugin enabled: + + MySQL users must have RDS authentication plugin enabled: ```sql CREATE USER alice IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS'; @@ -112,7 +337,7 @@ Access for RDS. See below how to enable it for your database engine. See [Creating a database account using IAM authentication](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.DBAccounts.html) for more information. -## Step 7/7. Connect +## Step 6/6. Connect Once the database agent has started and joined the cluster, login to see the registered databases: diff --git a/docs/pages/database-access/guides/redis.mdx b/docs/pages/database-access/guides/redis.mdx deleted file mode 100644 index c062781a4520e..0000000000000 --- a/docs/pages/database-access/guides/redis.mdx +++ /dev/null @@ -1,375 +0,0 @@ ---- -title: Database Access with Redis -description: How to configure Teleport Database Access with Redis. ---- - -
      - Database access for Redis is available starting from Teleport `9.0`. -
      - -This guide will help you to: - -- Install and configure Teleport. -- Configure mutual TLS authentication between Teleport and Redis. -- Connect to Redis through Teleport. - -## Prerequisites - -- Teleport version `9.0` or newer. -- Redis (standalone or cluster) version `6.0` or newer. -- `redis-cli` installed and added to your system's `PATH` environment variable. - - - Redis `7.0` and RESP3 (REdis Serialization Protocol) are currently not supported. - - -## Step 1/5. Set up Teleport Auth and Proxy - -(!docs/pages/includes/database-access/start-auth-proxy.mdx!) - -## Step 2/5. Create a Teleport user - -(!docs/pages/includes/database-access/create-user.mdx!) - -## Step 3/5. Create Redis users - -Each Redis user must be protected by a strong password. We recommend using OpenSSL to generate passwords: - -```shell -openssl rand -hex 32 -``` - - -If you have access to Redis you can also generate a password by using the below command from the Redis console: - -``` -ACL GENPASS -``` - - -Create a `users.acl` file, which defines users for your Redis deployment, passwords required to log in as a given user, - and sets of ACL rules. Redis allows you to provide passwords in plaintext or an SHA256 hash. -We strongly recommend using an SHA256 hash instead of plaintext passwords. - -You can use the command below to generate an SHA256 hash from a password. -```shell -echo -n STRONG_GENERATED_PASSWORD | sha256sum -``` - - - - ```text - user alice on #57639ed88a85996453555f22f5aa4147b4c9614056585d931e5d976f610651e9 allcommands allkeys - user default off - ``` - - For more ACL examples refer to the [Redis documentation](https://redis.io/docs/manual/security/acl/). - - -Redis in cluster mode requires a special user created on leader nodes to enable leader-follower communication. - ```text - user alice on #57639ed88a85996453555f22f5aa4147b4c9614056585d931e5d976f610651e9 allcommands allkeys - user replica-user on #42a9798b99d4afcec9995e47a1d246b98ebc96be7a732323eee39d924006ee1d &* -@all +replconf +ping +psync - user default off - ``` - - For more ACL examples refer to the Redis documentation: [ACL](https://redis.io/docs/manual/security/acl/), [ACL rules for replicas](https://redis.io/docs/manual/security/acl/#acl-rules-for-sentinel-and-replicas). - - - - - It's very important to either disable or protect with a password the `default` user. Otherwise, everyone with access - to the database can log in as the `default` user, which by default has administrator privileges. - - -## Step 4/5. Set up mutual TLS - -(!docs/pages/includes/database-access/tctl-auth-sign.mdx!) - -Create the secrets: - - - - When connecting to standalone Redis, sign the certificate for the hostname over - which Teleport will be connecting to it. - - For example, if your Redis server is accessible at `redis.example.com`, - run: - - ```code - $ tctl auth sign --format=redis --host=redis.example.com --out=server --ttl=2190h - ``` - - (!docs/pages/includes/database-access/ttl-note.mdx!) - - The command will create three files: - - `server.cas` with Teleport's certificate authority - - `server.key` with a generated private key - - `server.crt` with a generated user certificate - -You will need these files to enable mutual TLS on your Redis server. - - - When connecting to Redis Cluster, sign certificates for each member - using their hostnames and IP addresses. - - For example, if the first member is accessible at `redis1.example.com` with IP `10.0.0.1` and - the second at `redis2.example.com` with IP `10.0.0.2`, run: - - ```code - $ tctl auth sign --format=redis --host=redis1.example.com,10.0.0.1 --out=redis1 --ttl=2190h - $ tctl auth sign --format=redis --host=redis2.example.com,10.0.0.2 --out=redis2 --ttl=2190h - ``` - - (!docs/pages/includes/database-access/ttl-note.mdx!) - - The command will create three files: - - `server.cas` with Teleport's certificate authority - - `server.key` with a generated private key - - `server.crt` with a generated user certificate - -You will need these files to enable mutual TLS on your Redis server. - - - -(!docs/pages/includes/database-access/rotation-note.mdx!) - -Use the generated secrets to enable mutual TLS in your `redis.conf` configuration -file and restart the database: - - - - ```ini - tls-port 6379 - port 0 - - aclfile /path/to/users.acl - - tls-ca-cert-file /path/to/server.cas - tls-cert-file /path/to/server.crt - tls-key-file /path/to/server.key - tls-protocols "TLSv1.2 TLSv1.3" - ``` - - - ```ini - tls-port 7001 - port 0 - - cluster-enabled yes - - tls-replication yes - tls-cluster yes - - aclfile /path/to/users.acl - - masterauth GENERATED_STRONG_PASSWORD - masteruser replica-user - - tls-cert-file /usr/local/etc/redis/certs/server.crt - tls-key-file /usr/local/etc/redis/certs/server.key - tls-ca-cert-file /usr/local/etc/redis/certs/server.cas - tls-protocols "TLSv1.2 TLSv1.3" - ``` - - - -Once mutual TLS has been enabled, you will no longer be able to connect to -the cluster without providing a valid client certificate. You can use the -`tls-auth-clients optional` setting to allow connections -from clients that do not present a certificate. - -See [TLS Support](https://redis.io/topics/encryption) -in the Redis documentation for more details. - - - When you're configuring Redis Cluster you need to create the cluster using `redis-cli`. - - Use the following command to create the cluster. Please note `redis-cli --cluster create` accepts only IP addresses. - - ```shell - export REDISCLI_AUTH=STRONG_GENERATED_PASSWORD - export CERTS_DIR=/path/to/certs/ - export IP1=10.0.0.1 # update with the real node 1 IP - export IP2=10.0.0.2 # update with the real node 2 IP - export IP3=10.0.0.3 # update with the real node 3 IP - export IP4=10.0.0.4 # update with the real node 4 IP - export IP5=10.0.0.5 # update with the real node 5 IP - export IP6=10.0.0.6 # update with the real node 6 IP - - redis-cli --user alice --cluster-replicas 1 --tls --cluster-yes \ - --cluster create ${IP1}:7001 ${IP2}:7002 ${IP3}:7003 ${IP4}:7004 ${IP5}:7005 ${IP6}:7006 \ - --cacert ${CERTS_DIR}/server.cas --key ${CERTS_DIR}/server.key --cert ${CERTS_DIR}/server.crt - ``` - - - - - - To enable Redis cluster mode in Teleport, add the `mode=cluster` parameter to the connection URI in - your Teleport Database Service config file. - - ```yaml - databases: - - name: "redis-cluster" - uri: "rediss://redis.example.com:6379?mode=cluster" - ``` - - - -## Step 5/5. Connect - -Log into your Teleport cluster and see available databases: - - - - ```code - $ tsh login --proxy=teleport.example.com --user=alice - $ tsh db ls - # Name Description Labels - # ------------- --------------- -------- - # example-redis Example Redis env=dev - ``` - - - ```code - $ tsh login --proxy=mytenant.teleport.sh --user=alice - $ tsh db ls - # Name Description Labels - # ------------- --------------- -------- - # example-redis Example Redis env=dev - ``` - - - -To connect to a particular database instance, first retrieve its certificate -using the `tsh db login` command: - -```code -$ tsh db login example-redis -``` - - - You can be logged into multiple databases simultaneously. - - -You can optionally specify the database name and the user to use by default -when connecting to the database instance: - -```code -$ tsh db login --db-user=alice example-redis -``` - - - If flag `--db-user` is not provided, Teleport logs in as the `default` user. - - -Once logged in, connect to the database: - -```code -$ tsh db connect example-redis -``` - - - The `redis-cli` command-line client should be available in the system `PATH` in order to be - able to connect. - - -Now you can log in as the previously created user using the below command: - -``` -AUTH alice STRONG_GENERATED_PASSWORD -``` - -To log out of the database and remove credentials: - -```code -# Remove credentials for a particular database instance. -$ tsh db logout example-redis -# Remove credentials for all database instances. -$ tsh db logout -``` - -### Supported Redis commands - - - - - Redis in standalone mode doesn't support the commands below. If one of the listed commands is called Teleport - returns the `ERR Teleport: not supported by Teleport` error. - - - `HELLO` - - `PUNSUBSCRIBE` - - `SSUBSCRIBE` - - `SUNSUBSCRIBE` - - - - - Redis in cluster mode doesn't support below commands. If one of the listed commands above is called Teleport - returns the `ERR Teleport: command not supported` error. - - - `ACL` - - `ASKING` - - `CLIENT` - - `CLUSTER` - - `CONFIG` - - `DEBUG` - - `EXEC` - - `HELLO` - - `INFO` - - `LATENCY` - - `MEMORY` - - `MIGRATE` - - `MODULE` - - `MONITOR` - - `MULTI` - - `PFDEBUG` - - `PFSELFTEST` - - `PSUBSCRIBE` - - `PSYNC` - - `PUNSUBSCRIBE` - - `PUNSUBSCRIBE` - - `READONLY` - - `READWRITE` - - `REPLCONF` - - `REPLICAOF` - - `ROLE` - - `SCAN` - - `SCRIPT DEBUG` - - `SCRIPT KILL` - - `SHUTDOWN` - - `SLAVEOF` - - `SLOWLOG` - - `SSUBSCRIBE` - - `SUNSUBSCRIBE` - - `SYNC` - - `TIME` - - `WAIT` - - `WATCH` - - Teleport also adds additional processing to: - -| Command | Description | -|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `DBSIZE` | Sends the query to all nodes and returns the number of keys in the whole cluster. | -| `KEYS` | Sends the query to all nodes and returns a list of all keys in the whole cluster. | -| `MGET` | Translates the commands to multiple `GET`s and sends them to multiple nodes. Result is merged in Teleport and returned back to the client. If Teleport fails to fetch at least one key an error is returned. | -| `FLUSHDB` | Sends the query to all nodes. | -| `FLUSHALL` | Works the same as `FLUSHDB`. | -| `SCRIPT EXISTS` | Sends the query to all nodes. `1` is returned only if script exists on all nodes. | -| `SCRIPT LOAD` | Sends the script to all nodes. | -| `SCRIPT FLUSH` | Sends the query to all nodes. `ASYNC` parameter is ignored. | - - - - -## Next steps - -(!docs/pages/includes/database-access/guides-next-steps.mdx!) diff --git a/docs/pages/database-access/reference/aws.mdx b/docs/pages/database-access/reference/aws.mdx index 9bf906be356eb..6d862d2774300 100644 --- a/docs/pages/database-access/reference/aws.mdx +++ b/docs/pages/database-access/reference/aws.mdx @@ -3,224 +3,17 @@ title: Database Access AWS IAM Reference description: AWS IAM policies for Teleport database access. --- -## Auto-discovery -With the appropriate IAM permissions, Teleport automatically discovers and -configures IAM policies for Amazon RDS and Redshift. +Teleport automatically discovers and configures IAM for RDS and Redshift given +proper IAM permissions as described in the [AWS RDS & Aurora +guide](../guides/rds.mdx) and the [AWS Redshift +guide](../guides/postgres-redshift.mdx). -Teleport also requires permission to update database configurations, for example, to -enable IAM authentication on RDS databases. - -You can generate and manage the permissions with the [`teleport db configure -bootstrap`](../../database-access/reference/cli.mdx#teleport-db-configure-bootstrap) -command. For example, the following command would generate and print the -IAM policies: - -```code -$ teleport db configure bootstrap --manual -``` - -Or if you prefer, you manage the IAM permissions yourself. Examples of policies -for each discovery type are shown below. - -### Aurora/RDS - - - - Use this policy if you're connecting to RDS instances and your Teleport - database agent runs as an IAM user (for example, uses an AWS credentials file). - - Replace `{account-id}` with your AWS Account ID. - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBInstances", - "rds:ModifyDBInstance" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:GetUserPolicy", - "iam:PutUserPolicy", - "iam:DeleteUserPolicy" - ], - "Resource": "arn:aws:iam::{account-id}:user/sample-user" - } - ] - } - ``` - - - Use this policy if you're connecting to RDS instances and your Teleport - database agent runs as an IAM role (for example, on an EC2 instance with - an attached IAM role). - - Replace `{account-id}` with your AWS Account ID. - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBInstances", - "rds:ModifyDBInstance" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:GetRolePolicy", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy" - ], - "Resource": "arn:aws:iam::{account-id}:role/sample-role" - } - ] - } - ``` - - - Use this policy if you're connecting to Aurora clusters and your Teleport - database agent runs as an IAM user (for example, uses an AWS credentials file). - - Replace `{account-id}` with your AWS Account ID. - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBClusters", - "rds:ModifyDBCluster" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:GetUserPolicy", - "iam:PutUserPolicy", - "iam:DeleteUserPolicy" - ], - "Resource": "arn:aws:iam::{account-id}:user/sample-user" - } - ] - } - ``` - - - Use this policy if you're connecting to Aurora clusters and your Teleport - database agent runs as an IAM role (for example, on an EC2 instance with - an attached IAM role). - - Replace `{account-id}` with your AWS Account ID. - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds:DescribeDBClusters", - "rds:ModifyDBCluster" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:GetRolePolicy", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy" - ], - "Resource": "arn:aws:iam::{account-id}:role/sample-role" - } - ] - } - ``` - - - -### Redshift - - - - Use this permission boundary if your Teleport database agent runs as an IAM - user (for example, it uses an AWS credentials file). - - Replace `{account-id}` with your AWS Account ID. - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "redshift:DescribeClusters", - "redshift:GetClusterCredentials" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:GetUserPolicy", - "iam:PutUserPolicy", - "iam:DeleteUserPolicy" - ], - "Resource": "arn:aws:iam::{account-id}:user/sample-user" - } - ] - } - ``` - - - Use this permission boundary if your Teleport database agent runs as an IAM - role (for example, on an EC2 instance with an attached IAM role). - - Replace `{account-id}` with your AWS Account ID. - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "redshift:DescribeClusters", - "redshift:GetClusterCredentials", - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:GetRolePolicy", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy" - ], - "Resource": "arn:aws:iam::{account-id}:role/sample-role" - } - ] - } - ``` - - - -## Manual registration If you prefer to register RDS or Redshift databases manually using a [static configuration](./configuration.mdx) or [`tctl`](../guides/dynamic-registration.mdx) and manage IAM yourself, example IAM policies with the required permissions are shown below. -### RDS or Aurora policy +## RDS or Aurora policy To connect to an RDS database, the database agent's IAM identity needs to have `rds-db:connect` permissions for it: @@ -263,7 +56,7 @@ arn:aws:rds-db:::dbuser:/ See [Creating and using an IAM policy for IAM database access](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.IAMPolicy.html) for more information. -### Redshift policy +## Redshift policy Teleport uses temporary credentials generated by AWS to authenticate with Redshift databases. diff --git a/docs/pages/database-access/reference/cli.mdx b/docs/pages/database-access/reference/cli.mdx index d66839001b05a..dc77a23ce3721 100644 --- a/docs/pages/database-access/reference/cli.mdx +++ b/docs/pages/database-access/reference/cli.mdx @@ -38,53 +38,6 @@ $ teleport db start \ | `--gcp-project-id` | (Only for Cloud SQL) GCP Cloud SQL project identifier. | | `--gcp-instance-id` | (Only for Cloud SQL) GCP Cloud SQL instance identifier.| -## teleport db configure create - -Creates a sample Database Service configuration. - -```code -$ teleport db configure create --rds-discovery=us-west-1 --rds-discovery=us-west-2 -$ teleport db configure create \ - --token=/tmp/token \ - --proxy=proxy.example.com:3080 \ - --name=example \ - --protocol=postgres \ - --uri=postgres://postgres.example.com:5432 \ - --labels=env=prod -``` - -| Flag | Description | -| - | - | -| `--proxy` | Teleport Proxy Service address to connect to. Default: `0.0.0.0:3080`. | -| `--token` | Invitation token to register with the Auth Service. Default: none. | -| `--rds-discovery` | List of AWS regions the agent will discover for RDS/Aurora instances. | -| `--redshift-discovery` | List of AWS regions the agent will discover for Redshift instances. | -| `--ca-pin` | CA pin to validate the Auth Service (can be repeated for multiple pins). | -| `--name` | Name of the proxied database. | -| `--protocol` | Proxied database protocol. Supported are: [postgres mysql mongodb cockroachdb redis sqlserver]. | -| `--uri` | Address the proxied database is reachable at. | -| `--labels` | Comma-separated list of labels for the database, for example env=dev,dept=it | -| `-o/--output` | Write to stdout with `-o=stdout`, the default config file with `-o=file`, or a custom path with `-o=file:///path` | - -## teleport db configure bootstrap - -Bootstrap the necessary configuration for the database agent. It reads the provided agent configuration to determine what will be bootstrapped. - -```code -$ teleport db configure bootstrap -c /etc/teleport.yaml --attach-to-user TeleportUser -$ teleport db configure bootstrap -c /etc/teleport.yaml --attach-to-role TeleportRole -$ teleport db configure bootstrap -c /etc/teleport.yaml --manual -``` - -| Flag | Description | -| - | - | -| `-c/--config` | Path to a configuration file. Default: `/etc/teleport.yaml`. | -| `--manual` | When executed in "manual" mode, this command will print the instructions to complete the configuration instead of applying them directly. | -| `--policy-name` | Name of the Teleport Database Service policy. Default: "DatabaseAccess" | -| `--confirm` | Do not prompt the user and auto-confirm all actions. | -| `--attach-to-role` | Role name to attach the policy to. Mutually exclusive with `--attach-to-user`. If none of the attach-to flags is provided, the command will try to attach the policy to the current user/role based on the credentials. | -| `--attach-to-user` | User name to attach the policy to. Mutually exclusive with `--attach-to-role`. If none of the attach-to flags is provided, the command will try to attach the policy to the current user/role based on the credentials. | - ## tctl auth sign When invoked with a `--format=db` (or `--format=mongodb` for MongoDB) flag, diff --git a/docs/pages/desktop-access/getting-started.mdx b/docs/pages/desktop-access/getting-started.mdx index caa1df9757a00..17191b3b721c4 100644 --- a/docs/pages/desktop-access/getting-started.mdx +++ b/docs/pages/desktop-access/getting-started.mdx @@ -156,7 +156,7 @@ certificate-based smart card authentication, and ensuring RDP is enabled. 1. Get the Teleport user CA certificate by running: -```code +``` $ tctl auth export --type=windows > user-ca.cer ``` @@ -237,16 +237,16 @@ access. 1. Publish the CA to LDAP: -```powershell -certutil –dspublish –f NTAuthCA +``` +$ certutil –dspublish –f NTAuthCA ``` 2. Force the retrieval of the CA from LDAP. While this step is not required, it speeds up the process and allows you to proceed to the next steps without waiting for the certificate to propagate. -```powershell -certutil -pulse +``` +$ certutil -pulse ``` ### Enable the Smart Card service @@ -384,7 +384,7 @@ providing a join token. First, generate a join token with the following command: -```code +``` $ tctl tokens add --type=windowsdesktop ``` diff --git a/docs/pages/desktop-access/reference.mdx b/docs/pages/desktop-access/reference.mdx deleted file mode 100644 index 156ff29aeecd6..0000000000000 --- a/docs/pages/desktop-access/reference.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Desktop Access Reference -layout: tocless-doc ---- - - - - Configure Teleport Desktop Access. - - - Desktop Access audit events. - - - Share your clipboard with a remote desktop. - - - Desktop session recording and playback - - \ No newline at end of file diff --git a/docs/pages/desktop-access/reference/clipboard.mdx b/docs/pages/desktop-access/reference/clipboard.mdx deleted file mode 100644 index 96f284d94d73d..0000000000000 --- a/docs/pages/desktop-access/reference/clipboard.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Clipboard Sharing -description: Using Clipboard Sharing with Teleport Desktop Access. ---- - -Teleport Desktop Access supports copying and pasting text between your browser -and a remote Windows Desktop. - - -This feature requires a Chromium-based browser such as Google Chrome, Brave, -or Microsoft Edge. To use Desktop Access on other browsers, disable clipboard -sharing. - - -Clipboard sharing is configured via Teleport's RBAC system. It is enabled by default, -but can be disabled by setting the following role option on one or more of a user's -roles: - -```yaml -kind: role -version: v4 -metadata: - name: no_clipboard -spec: - options: - desktop_clipboard: false -``` - -If a clipboard sharing is enabled for a user, the browser will prompt for access -to the system clipboard when a desktop session is started. Grant this access to -proceed. - -{ - /* screenshot of prompt */ -} - -If you mistakenly deny clipboard permissions, you can change this setting by -clicking the lock on the left side of your address bar, toggling the clipboard -permission, and refreshing the page. - -When a desktop session is active, Teleport will indicate whether clipboard sharing -is enabled in the menu bar at the top of the screen. - -{ - /* screenshots for enabled / disabled */ -} \ No newline at end of file diff --git a/docs/pages/desktop-access/reference/sessions.mdx b/docs/pages/desktop-access/reference/sessions.mdx deleted file mode 100644 index 0afbb767fae91..0000000000000 --- a/docs/pages/desktop-access/reference/sessions.mdx +++ /dev/null @@ -1,86 +0,0 @@ ---- -title: Session Recording and Playback -description: Recording and Playing Back Teleport Desktop Access Sessions. ---- - -Teleport Desktop Access supports recording and playback of desktop sessions. - -## Disabling session recording - -Session recording is enabled by default, and can be disabled in one of two ways: - -### Disable session recording at the cluster level - -To disable session recording at the cluster level, edit your `teleport.yaml` -configuration file. - -```yaml -teleport: - auth_service: - session_recording: off -``` - - -Disabling session recording at the cluster level applies to all types of -recordings. For example, this configuration would prevent Teleport from -recording SSH sessions as well as desktop sessions. - - -The `session_recording` property can also be configured dynamically by using -`tctl` to edit the cluster's `session_recording_config` resource. This is the -required approach for Teleport Cloud users. - -### Disable session recording via RBAC - -Teleport's RBAC system allows more fine-grained control over which sessions are -recorded. By default, desktop session recording is enabled. To disable desktop -session recording for a particular user, add the following role option to all of -the user's roles: - -```yaml -kind: role -version: v4 -metadata: - name: no_desktop_recording -spec: - options: - record_session: - desktop: false -``` - -Because recording can be important for auditing and compliance concerns, the -presence of a single role with recording enabled will result in the session -being recorded. In other words, *all* of the roles applied to a user must -explicitly disable recording to prevent the session from being recorded. - -## Recording - -When a desktop session is active, Teleport will indicate whether the session is -being recorded in the menu bar at the top of the screen. - -{ - /* screenshots for recording, not recording */ -} - -## Playback - -Recorded sessions can be viewed in the *Session Recordings* page. Desktop -recordings show a desktop icon in the first column to distinguish them from -SSH recordings. - -{ - /* screenshot of recordings table */ -} - - -Since Teleport 8.1, sessions can be protected via RBAC. In order for a user to -see desktop sessions in this list, their roles must permit access to the -sessions resource. - - -Click the play button to open the player in a new tab. The desktop session -player supports toggling between play and pause, but does not support seeking to -a specific point in the stream, rewinding, or restarting playback when the end -of the stream is reached. To replay a session, refresh the page. - - diff --git a/docs/pages/enterprise/hsm.mdx b/docs/pages/enterprise/hsm.mdx index f018bc6f26c5f..b1edac7522235 100644 --- a/docs/pages/enterprise/hsm.mdx +++ b/docs/pages/enterprise/hsm.mdx @@ -12,7 +12,7 @@ This guide will show you how to set up the Teleport Auth Server to use a hardwar - An HSM reachable from your Teleport auth server. - The PKCS#11 module for your HSM. -
      +
      Teleport Cloud and Teleport Open Source do not currently support HSM.
      diff --git a/docs/pages/enterprise/sso/adfs.mdx b/docs/pages/enterprise/sso/adfs.mdx index 0e08fd0ea8fbc..6bc68d1378f6d 100644 --- a/docs/pages/enterprise/sso/adfs.mdx +++ b/docs/pages/enterprise/sso/adfs.mdx @@ -1,14 +1,16 @@ --- -title: SSO with Active Directory Federation Services -description: How to configure SSH access with Active Directory Federation Services using Teleport -h1: Single Sign-On with Active Directory Federation Services +title: SSH Authentication With ADFS How To +description: How to configure SSH access with Active Directory (ADFS) using Teleport +h1: SSH Authentication with ADFS --- +## Active Directory as an SSO provider for SSH authentication + This guide will cover how to configure Active Directory Federation Services -([ADFS](https://en.wikipedia.org/wiki/Active_Directory_Federation_Services)) to be +[ADFS](https://en.wikipedia.org/wiki/Active_Directory_Federation_Services) to be a single sign-on (SSO) provider to issue SSH credentials to specific groups of users. When used in combination with role -based access control (RBAC), it allows SSH administrators to define policies +based access control (RBAC) it allows SSH administrators to define policies like: - Only members of "DBA" group can SSH into machines running PostgreSQL. @@ -22,15 +24,25 @@ like: This guide requires a commercial edition of Teleport. -(!docs/pages/includes/enterprise/samlauthentication.mdx!) +## Enable ADFS Authentication + +First, configure Teleport auth server to use ADFS authentication instead of the local +user database. Update `/etc/teleport.yaml` as shown below and restart the +teleport daemon. + +```yaml +auth_service: + authentication: + type: saml +``` ## Configure ADFS You'll need to configure ADFS to export claims about a user (Claims Provider -Trust in ADFS terminology) and you'll need to configure ADFS to trust +Trust in ADFS terminology) and you'll need to configure AD FS to trust Teleport (a Relying Party Trust in ADFS terminology). -For Claims Provider Trust configuration, you'll need to specify at least the +For Claims Provider Trust configuration you'll need to specify at least the following two incoming claims: `Name ID` and `Group`. `Name ID` should be a mapping of the LDAP Attribute `E-Mail-Addresses` to `Name ID`. A group membership claim should be used to map users to roles (for example to @@ -39,29 +51,29 @@ separate normal users and admins). ![Name ID Configuration](../../../img/adfs-1.png) ![Group Configuration](../../../img/adfs-2.png) -In addition, if you are using dynamic roles (see below), it may be useful to map +In addition if you are using dynamic roles (see below), it may be useful to map the LDAP Attribute `SAM-Account-Name` to `Windows account name` and create another mapping of `E-Mail-Addresses` to `UPN`. ![WAN Configuration](../../../img/adfs-3.png) ![UPN Configuration](../../../img/adfs-4.png) -You'll also need to create a Relying Party Trust. Use the below information to -help guide you through the Wizard. Note that for development purposes we recommend +You'll also need to create a Relying Party Trust, use the below information to +help guide you through the Wizard. Note, for development purposes we recommend using `https://localhost:3080/v1/webapi/saml/acs` as the Assertion Consumer Service (ACS) URL, but for production you'll want to change this to a domain that can be accessed by other users as well. - Create a claims aware trust. - Enter data about the relying party manually. -- Set the display name to something along the lines of `Teleport`. +- Set the display name to something along the lines of "Teleport". - Skip the token encryption certificate. - Select *"Enable support for SAML 2.0 Web SSO protocol"* and set the URL to `https://localhost:3080/v1/webapi/saml/acs`. - Set the relying party trust identifier to `https://localhost:3080/v1/webapi/saml/acs` as well. - For access control policy select *"Permit everyone"*. Once the Relying Party Trust has been created, update the Claim Issuance Policy -for it. Like before, make sure you send at least `Name ID` and `Group` claims to the +for it. Like before make sure you send at least `Name ID` and `Group` claims to the relying party (Teleport). If you are using dynamic roles, it may be useful to map the LDAP Attribute `SAM-Account-Name` to *"Windows account name"* and create another mapping of `E-Mail-Addresses` to *"UPN"*. @@ -71,10 +83,10 @@ associated with it. To check this open Server Manager then *"Tools -> Active Directory Users and Computers"* and select the user and right click and open properties. Make sure the email address field is filled out. -## Create Teleport roles +## Create Teleport Roles -Let's create two Teleport roles: one for administrators and the other for -normal users. You can create them using the `tctl create {file name}` CLI command +Lets create two Teleport roles: one for administrators and the other is for +normal users. You can create them using `tctl create {file name}` CLI command or via the Web UI. ```yaml @@ -114,18 +126,13 @@ spec: This role declares: -- Devs are only allowed to log in to nodes labeled `access: relaxed`. -- Developers can log in as the `ubuntu` user. -- Developers will not be able to see or replay past sessions or - re-configure the Teleport cluster. - -The login -`{{external["http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]}}` -configures Teleport to look at the -`http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname` -ADFS claim and use that field as an allowed login for each user. Note the -double quotes (`"`) and square brackets (`[]`) around the claim name—these are -important. +- Devs are only allowed to login to nodes labelled with `access: relaxed` label. +- Developers can log in as `ubuntu` user +- Notice `{{external["http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]}}` login. It configures Teleport to look at + *"[http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"](http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname")* ADFS claim and use that field as an allowed login for each user. + Also note the double quotes (`"`) and square brackets (`[]`) around the claim name - these are important. +- Developers also do not have any "allow rules" i.e. they will not be able to + see/replay past sessions or re-configure the Teleport cluster. Next, create a SAML connector [resource](../../setup/reference/resources.mdx): @@ -142,7 +149,7 @@ name is `http://schemas.xmlsoap.org/claims/Group` with a value of *"admins"* to the *"admin"* role. Groups with the value *"users"* is being mapped to the *"users"* role. -## Export the signing key +## Export the Signing Key For the last step, you'll need to export the signing key: @@ -163,8 +170,8 @@ the same as before: $ tsh --proxy=proxy.example.com login ``` -This command will print the SSO login URL and try to open it -automatically in a browser. +This command will print the SSO login URL (and will try to open it +automatically in a browser). - Teleport only supports sending party-initiated flows for SAML 2.0. This means - that you cannot initiate login from your identity provider, and must do so - from either the Teleport Web UI or CLI. - + Teleport only supports sending party initiated flows for SAML 2.0. This + means you can not initiate login from your identity provider, you have to + initiate login from either the Teleport Web UI or CLI. + ## Troubleshooting -If you get "access denied" errors, the number one place to check is the audit -log on the Teleport Auth Server. It is located in `/var/lib/teleport/log` by +If you get "access denied errors" the number one place to check is the audit +log on the Teleport auth server. It is located in `/var/lib/teleport/log` by default and it will contain the detailed reason why a user's login was denied. Some errors (like filesystem permissions or misconfigured network) can be @@ -197,4 +204,4 @@ $ sudo journalctl -fu teleport ``` If you wish to increase the verbosity of Teleport's syslog, you can pass -`--debug` flag to the `teleport start` command. +`--debug` flag to `teleport start` command. diff --git a/docs/pages/enterprise/sso/azuread.mdx b/docs/pages/enterprise/sso/azuread.mdx index 7a4a56330b187..c50e0dd4f49e7 100644 --- a/docs/pages/enterprise/sso/azuread.mdx +++ b/docs/pages/enterprise/sso/azuread.mdx @@ -28,8 +28,6 @@ Before you get started you’ll need: - To register one or more users in the directory - To create at least two security groups in Azure AD and assign one or more users to each group -(!docs/pages/includes/enterprise/samlauthentication.mdx!) - ## Configure Azure AD 1. Select **Azure AD -> Enterprise Applications** @@ -170,6 +168,34 @@ $ tctl create dev.yaml ## Testing +Update your Teleport configuration to make SAML the default authentication method. + + + +In your Teleport configuration file, include the following: + +```yaml +auth_service: + authentication: + type: saml +``` + + +Apply the following `cluster_auth_preference` resource via `tctl create`. + +```yaml + kind: cluster_auth_preference + version: v2 + metadata: + name: cluster-auth-preference + spec: + type: "saml" +``` + + + +The Web UI will now present the option to log in via Microsoft. + ![Login with Microsoft](../../../img/azuread/azure-11-loginwithmsft.png) diff --git a/docs/pages/enterprise/sso/gitlab.mdx b/docs/pages/enterprise/sso/gitlab.mdx index c3b74696f4174..9ffd4523b7463 100644 --- a/docs/pages/enterprise/sso/gitlab.mdx +++ b/docs/pages/enterprise/sso/gitlab.mdx @@ -22,7 +22,17 @@ like: This guide requires a commercial edition of Teleport. -(!docs/pages/includes/enterprise/oidcauthentication.mdx!) +## Enable OIDC Authentication + +First, configure Teleport auth server to use OIDC authentication instead of the local +user database. Update `/etc/teleport.yaml` as shown below and restart the +teleport daemon. + +```yaml +auth_service: + authentication: + type: oidc +``` ## Configure GitLab diff --git a/docs/pages/enterprise/sso/google-workspace.mdx b/docs/pages/enterprise/sso/google-workspace.mdx index f2325d9569da9..86b977b3ff770 100644 --- a/docs/pages/enterprise/sso/google-workspace.mdx +++ b/docs/pages/enterprise/sso/google-workspace.mdx @@ -32,8 +32,6 @@ Before you get started you’ll need: - Ability to create a Google Cloud project, which requires signing up for Google Cloud. Note that this guide will not require using any paid Google Cloud services. - Ability to set up Google Workspace groups. -(!docs/pages/includes/enterprise/oidcauthentication.mdx!) - ## Configuring Google Workspace The setup will consist of creating a new project on Google Cloud Platform, diff --git a/docs/pages/enterprise/sso/oidc.mdx b/docs/pages/enterprise/sso/oidc.mdx index 59ab7f1426193..ff77208d90b84 100644 --- a/docs/pages/enterprise/sso/oidc.mdx +++ b/docs/pages/enterprise/sso/oidc.mdx @@ -20,7 +20,17 @@ administrators to define policies like: This guide requires an Enterprise edition of Teleport. -(!docs/pages/includes/enterprise/oidcauthentication.mdx!) +## Enable OIDC Authentication + +First, configure Teleport auth server to use OIDC authentication instead of the local +user database. Update `/etc/teleport.yaml` as show below and restart the +teleport daemon. + +```yaml +auth_service: + authentication: + type: oidc +``` ## Identity Providers diff --git a/docs/pages/enterprise/sso/okta.mdx b/docs/pages/enterprise/sso/okta.mdx index 318891f41f802..f5d374e9ea70a 100644 --- a/docs/pages/enterprise/sso/okta.mdx +++ b/docs/pages/enterprise/sso/okta.mdx @@ -22,7 +22,17 @@ like: This guide requires a commercial edition of Teleport. -(!docs/pages/includes/enterprise/samlauthentication.mdx!) +## Enable SAML Authentication + +First, configure Teleport auth server to use SAML authentication instead of the local +user database. Update `/etc/teleport.yaml` as shown below and restart the +teleport daemon. + +```yaml +auth_service: + authentication: + type: saml +``` ## Configure Okta diff --git a/docs/pages/enterprise/sso/one-login.mdx b/docs/pages/enterprise/sso/one-login.mdx index 37e0f863148c6..a0222e3709c3a 100644 --- a/docs/pages/enterprise/sso/one-login.mdx +++ b/docs/pages/enterprise/sso/one-login.mdx @@ -22,7 +22,17 @@ like: This guide requires an Enterprise edition of Teleport. -(!docs/pages/includes/enterprise/samlauthentication.mdx!) +## Enable SAML Authentication + +Configure Teleport auth server to use SAML authentication instead of the local +user database. Update `/etc/teleport.yaml` as shown below and restart the +teleport daemon. + +```yaml +auth_service: + authentication: + type: saml +``` ## Configure Application diff --git a/docs/pages/enterprise/workflow/ssh-approval-jira-cloud.mdx b/docs/pages/enterprise/workflow/ssh-approval-jira-cloud.mdx index fbdaa0e959ecc..fe5ce015141c2 100644 --- a/docs/pages/enterprise/workflow/ssh-approval-jira-cloud.mdx +++ b/docs/pages/enterprise/workflow/ssh-approval-jira-cloud.mdx @@ -4,7 +4,9 @@ description: How to configure SSH login approval using Jira and Teleport h1: SSH login approvals using Jira --- -This guide will talk through how to setup Teleport with Jira. Teleport's Jira integration allows you to treat Teleport access and permission requests using Jira tickets. +## Teleport Jira Plugin Setup + +This guide will talk through how to setup Teleport with Jira. Teleport to Jira integration allows you to treat Teleport access and permission requests using Jira tickets. ## Setup @@ -14,20 +16,62 @@ This guide assumes that you have: - Admin privileges with access to `tctl` - Jira Server or Jira Cloud installation with an owner privileges, specifically to setup webhooks, issue types, and workflows -Teleport Cloud requires that plugins connect through the Proxy Service (`mytenant.teleport.sh:443`). Open Source and Enterprise installations can connect to the Auth Service (`auth.example.com:3025`) directly. +### Create an access-plugin role and user within Teleport +First off, using an existing Teleport Cluster, we are going to create a new Teleport User and Role to access Teleport. -### Create a user and role for access +#### Create User and Role for access -(!docs/pages/includes/plugins/rbac.mdx!) +Log into Teleport Authentication Server, this is where you normally run `tctl`. Create a +new user and role that only has API access to the `access_request` API. The below script +will create a yaml resource file for a new user and role. -### Export the access-plugin certificate +```bash +cat > rscs.yaml < + By default, [`tctl auth sign`](../../setup/reference/cli.mdx#tctl-auth-sign) produces certificates with a relatively short lifetime. For production deployments, the `--ttl` flag can be used to ensure a more practical certificate lifetime. `--ttl=8760h` exports a 1 year token + + +## Setting up your Jira Project ### Creating the permission management project @@ -43,11 +87,11 @@ Create a new board for tasks in the permission management project. The board has 2. Approved 3. Denied -Teleport's Jira plugin will create a new issue for each new permission request in the first available column on the board. When you drag the request task to the Approved column in Jira, the request will be approved. If you drag the request task to the Denied column in Jira, the request will be denied. +Teleport Jira Plugin will create a new issue for each new permission request in the first available column on the board. When you drag the request task to Approved column on Jira, the request will be approved. If you drag the request task to the Denied column in Jira, the request will be denied. -### Setting up a request ID field on Jira +### Setting up Request ID field on Jira -The Teleport Jira plugin requires a custom issue field to be created. +Teleport Jira Plugin requires a custom issue field to be created. Go to your Jira Project settings → Issue Types → Select type `Task` → add a new Short Text field named `TeleportAccessRequestId`. @@ -59,30 +103,30 @@ If you're using Jira Cloud, navigate to [Account Settings → Security → API T For Jira Server, the URL of the API tokens page will be different depending on your installation. -### Setting up Jira webhooks +### Setting up Jira Webhooks -Go to Settings → General → System → Webhooks and create a new webhook for Jira to tell the Teleport plugin about updates. +Go to Settings → General → System → Webhooks and create a new Webhook for Jira to tell the Teleport Plugin about updates. -For the webhook URL, use the URL that you'll run the plugin on. It needs to be a publicly accessible URL (we will show you how to set this up later). Jira requires the webhook listener to run over HTTPS. +For the webhook URL, use the URL that you'll run the plugin on. It needs to be a publicly accessible URL that we'll set up later. Jira requires the webhook listener to run over HTTPS. -The webhook needs to be notified only about new issues being created, issues being updated, or deleted. You can leave all the other boxes empty. +The Teleport Jira plugin webhook needs to be notified only about new issues being created, issues being updated, or deleted. You can leave all the other boxes empty. - Jira will send updates about any issues in any projects in your Jira installation to the webhook. We suggest that you use JQL filters to limit which issues are being sent to the plugin. + Jira Webhook will send updates about any issues in any projects in your Jira installation to the webhook. We suggest that you use JQL filters to limit which issues are being sent to the plugin. - The plugin's web server will run with TLS, but you can disable it with `--insecure-no-tls` to test things out in a dev environment. + The Plugin's web server will run with TLS, but you can disable it with `--insecure-no-tls` to test things out in a dev environment. -In the webhook settings page, make sure that the webhook will only send Issue Updated updates. It's not critical if anything else gets sent, since the plugin will just ignore everything else. +In the webhook settings page, make sure that the webhook will only send Issue Updated updates. It's not critical if anything else gets sent, the plugin will just ignore everything else. ## Installing -We recommend installing Teleport plugins alongside the Teleport Proxy. This is an ideal +We recommend installing the Teleport Plugins alongside the Teleport Proxy. This is an ideal location as plugins have a low memory footprint, and will require both public internet access -and Teleport Auth access. We currently only provide linux-amd64 binaries, you can also +and Teleport Auth access. We currently only provide linux-amd64 binaries, you can also compile these plugins from [source](https://github.com/gravitational/teleport-plugins/tree/master/access/jira). ```code @@ -98,7 +142,7 @@ Run `./install` in from 'teleport-jira' or place the executable in the appropria ### Configuration file -The Teleport Jira plugin uses a config file in TOML format. Generate a boilerplate config by +Teleport Jira Plugin uses a config file in TOML format. Generate a boilerplate config by running the following command: ```code @@ -106,20 +150,11 @@ $ teleport-jira configure > teleport-jira.toml $ sudo mv teleport-jira.toml /etc ``` -By default, the Jira Teleport plugin will use a config in `/etc/teleport-jira.toml`, and you can override it with `-c config/file/path.toml` flag. +By default, Jira Teleport Plugin will use a config in `/etc/teleport-jira.toml`, and you can override it with `-c config/file/path.toml` flag. - - -```toml -(!examples/resources/plugins/teleport-jira-self-hosted.toml!) -``` - - ```toml -(!examples/resources/plugins/teleport-jira-cloud.toml!) +(!examples/resources/plugins/teleport-jira.toml!) ``` - - The `[teleport]` section describes where the teleport service running, and what keys should the plugin use to authenticate itself. Use the keys that you've generated. @@ -130,10 +165,10 @@ The `[jira]` section requires a few things: 3. Your Jira API token that you've created above. 4. A Jira Project key, available in Project settings. -The `[http]` setting block describes how the plugin's HTTP server works. The HTTP server is responsible for listening for updates from Jira, and processing updates, like when someone drags a task from Inbox to Approved column. +`[http]` setting block describes how the Plugin's HTTP server works. The HTTP server is responsible for listening for updates from Jira, and processing updates, like when someone drags a task from Inbox to Approved column. You must provide an address the server should listen on, and a certificate to use. It's possible to -run the Jira plugin on the same server as the Teleport Proxy, so you can use the same TLS certificate. +setup on the same server as the Teleport Proxy, so you can use the same TLS certificate. ## Testing @@ -151,20 +186,20 @@ Go ahead and test it: $ tsh login --request-roles=admin ``` -That should create a new permission request on Teleport (you can test if it did with `tctl request ls`), and you should see a new task on your Jira project board. +That should create a new permission request on Teleport (you can test if it did with `tctl request ls` ), and you should see a new task on your Jira project board. -### Set up systemd +### Setup with SystemD -In production, we recommend starting the Teleport plugin daemon via an init system like systemd. -Here's the recommended Teleport plugin service unit file for systemd: +In production, we recommend starting teleport plugin daemon via an init system like systemd . +Here's the recommended Teleport Plugin service unit file for systemd: ```txt (!examples/systemd/plugins/teleport-jira.service!) ``` -Save this as `teleport-jira.service`. Make sure the `teleport-jira start` command includes a `--config` flag that refers to the configuration file you created earlier. +Save this as `teleport-jira.service`. -## Audit log +## Audit Log The plugin will let anyone with access to the Jira board approve/deny requests so it's important to review Teleport's audit log. diff --git a/docs/pages/enterprise/workflow/ssh-approval-jira-server.mdx b/docs/pages/enterprise/workflow/ssh-approval-jira-server.mdx index 5bf61b69dee15..1357bd2971e90 100644 --- a/docs/pages/enterprise/workflow/ssh-approval-jira-server.mdx +++ b/docs/pages/enterprise/workflow/ssh-approval-jira-server.mdx @@ -4,7 +4,9 @@ description: How to configure SSH login approval using Jira Server and Teleport h1: SSH login approvals using Jira Server --- -This guide will talk through how to set up Teleport with Jira Server. Teleport's integration with Jira Server allows you to treat Teleport access and permission requests as Jira tasks. +## Teleport Jira Server Plugin Setup + +This guide will talk through how to setup Teleport with Jira Server. Teleport to Jira Server integration allows you to treat Teleport access and permission requests as Jira Tasks. Teleport's tsh request workflow is synchronous and needs to be approved within 1 hour of the request. @@ -30,71 +32,114 @@ This guide will talk through how to set up Teleport with Jira Server. Teleport's - A running Teleport Cluster - Admin Privileges with access and control of [`tctl`](../../setup/reference/cli.mdx#tctl) -- A Jira Server installation with owner privileges, specifically to set up webhooks, issue types, and workflows. This plugin has been tested with Jira Software 8.8.0 +- Jira Server installation with owner privileges, specifically to setup webhooks, issue types, and workflows. This plugin has been tested with Jira Software 8.8.0 + +### Create an access-plugin role and user within Teleport + +First off, using an existing Teleport Cluster, we are going to create a new Teleport User and Role to access Teleport. + +#### Create User and Role for access -Teleport Cloud requires that plugins connect through the Proxy Service (`mytenant.teleport.sh:443`). Open Source and Enterprise installations can connect to the Auth Service (`auth.example.com:3025`) directly. +Log into Teleport Authentication Server, this is where you normally run `tctl`. Create a +new user and role that only has API access to the `access_request` API. The below script +will create a yaml resource file for a new user and role. + +```bash +cat > rscs.yaml < + By default, [`tctl auth sign`](../../setup/reference/cli.mdx#tctl-auth-sign) produces certificates with a relatively short lifetime. For production deployments, the `--ttl` flag can be used to ensure a more practical certificate lifetime. `--ttl=8760h` exports a 1 year token + ### Setting up your Jira Server instance -#### Creating a project +#### Creating a Project -The Teleport Jira plugin relies on your Jira project having a board with at least three statuses (columns): Pending, Approved, and Denied. It's therefore the easiest scenario to create a new Jira project for Teleport to use. +Teleport Jira Plugin relies on your Jira project having a board with at least three statuses (columns): Pending, Approved, and Denied. It's therefore the easiest scenario to create a new Jira project for Teleport to use. -The specific type of project you choose when you create it doesn't matter, as long as you can setup a Kanban Board for it, but we recommend that you go with Kanban Software Development. This will reduce the amount of setup work you'll have to do and provide the board out of the box. +The specific type of project you choose when you create it doesn't matter, as long as you can setup a Kanban Board for it, but we recommend that you go with Kanban Software Development — this will reduce the amount of setup work you'll have to do and provide the board out of the box. You'll need the project key for the Teleport plugin settings later on. It's usually a 3 character code for the project. -#### Setting up a request ID field on Jira +#### Setting up Request ID field on Jira -Teleport stores the request metadata in a special Jira custom field that must be named `teleportAccessRequestId`. To create that field, go to Administration -> Issues -> Custom Fields -> Add Custom Field. +Teleport stores the request metadata in a special Jira custom field that must be named teleportAccessRequestId. To create that field, go to Administration -> Issues -> Custom Fields -> Add Custom Field. -Name the field `teleportAccessRequestId`, and choose Text Field (single line) as the field type. Assign the field to your project, or make it global. Teleport Access Request ID is an internal field and it's not supposed to be edited by users, so you can leave the Screens section blank. That means that the field won't show up in Jira's UI. +Name the field `teleportAccessRequestId`, and choose Text Field (single line) as the field type. Assign the field to your project, or make it global. Teleport Access Request ID is an internal field and it's not supposed to be edited by users, so you can leave the Screens section blank. That means that the field won't show up in Jira UI. Go to Project Settings -> Fields and make sure that the `teleportAccessRequestId` field shows up on the list of fields available in this project. #### Setting up the status board -The default Jira Software workflow has a different board setup than what Teleport needs, so we'll set up another workflow and assign that workflow to the project board. +The default Jira Software workflow has a different board setup from what Teleport needs, so we'll setup another workflow and assign that workflow to the project board. -Go to Administration -> Workflows. You can choose to add a new workflow (recommended), or edit the existing workflow. It will be called Software Simplified Workflow for Project NAME by default. It's only used in your single project, so it's safe to edit it. +Go to Administration -> Workflows. You can choose to add a new workflow (recommended), or edit the existing workflow, it'll be called Software Simplified Workflow for Project NAME by default. It's only used in your single project, so it's safe to edit it. Edit the workflow to have these three states: 1. Pending 2. Approved 3. Denied - -The rules of the workflow must meet these requirements: + The rules of the workflow must meet these requirements: - New created issues should be in Pending state. - It should be possible to move from Pending to Approved - It should be possible to move from Pending to Declined. - You can choose to make the workflow strict and restrict moving requests from Approved state to Declined state and vice versa, or leave that flexible. Teleport will only change the request status once, i.e. the first time the request is approved or denied on your Jira board. -With the Jira workflow editor, you can set up who can approve or deny an access reuqest based on their Jira user permissions. We won't cover that in this guide as it mostly relates to Jira settings. By default Teleport will allow anyone who can use the workflow to approve or deny the request. +With Workflow editor you can setup who can approve or deny the request based on their Jira user permissions. We won't cover that in this guide as it mostly relates to Jira settings. By default Teleport will allow anyone who can use the workflow to approve or deny the request. -Go to your Project Settings -> Workflows, and make sure that the workflow that you just created or edited is applied to the project you'll use for Teleport integration. +Go to your Project Settings -> Workflows, and make sure that your workflow that you just created or edited is applied to the project you'll use for Teleport integration. ### Setting up the webhook -Teleport Jira Plugin will listen for a webhook that Jira Server sends when a request is approved or denied. Go to Settings -> System -> Webhooks to set up the webhook. The webhook needs to be sent when issues are updated or deleted. +Teleport Jira Plugin will listen for a webhook that Jira Server sends when a request is approved or denied. Go to Settings -> System -> Webhooks to setup the webhook. The webhook needs to be sent when issues are updated or deleted. +#### Configuring the Teleport Jira Plugin for Jira Server ## Installing -We recommend installing Teleport plugins alongside the Teleport Proxy. This is an ideal +We recommend installing the Teleport Plugins alongside the Teleport Proxy. This is an ideal location as plugins have a low memory footprint, and will require both public internet access -and Teleport Auth access. We currently only provide linux-amd64 binaries, you can also +and Teleport Auth access. We currently only provide linux-amd64 binaries, you can also compile these plugins from [source](https://github.com/gravitational/teleport-plugins/tree/master/access/jira). ```code @@ -108,10 +153,9 @@ $ which teleport-jira # /usr/local/bin/teleport-jira ``` -Run `sudo ./install` from 'teleport-jira' or place the executable in the appropriate `/usr/bin` or `/usr/local/bin` on the server installation. +Run `sudo ./install` in from 'teleport-jira' or place the executable in the appropriate `/usr/bin` or `/usr/local/bin` on the server installation. - -## Configuration file +### Configuration file Teleport Jira Plugin uses a config file in TOML format. Generate a boilerplate config by running the following command: @@ -121,31 +165,22 @@ $ teleport-jira configure > teleport-jira.toml $ sudo mv teleport-jira.toml /etc ``` -By default, the Jira Teleport plugin will use a config in `/etc/teleport-jira.toml`, and you can override it with `-c config/file/path.toml` flag. +By default, Jira Teleport Plugin will use a config in `/etc/teleport-jira.toml`, and you can override it with `-c config/file/path.toml` flag. - - -```toml -(!examples/resources/plugins/teleport-jira-self-hosted.toml!) -``` - - ```toml -(!examples/resources/plugins/teleport-jira-cloud.toml!) +(!examples/resources/plugins/teleport-jira.toml!) ``` - - -The `[teleport]` section describes where the Teleport service is running, and what keys the plugin should use to authenticate itself. Use the keys that you've generated [above](#export-access-plugin-certificate). +The `[teleport]` section describes where is the teleport service running, and what keys should the plugin use to authenticate itself. Use the keys that you've generated [above in exporting your Certificate section](#export-access-plugin-certificate). The `[jira]` section requires a few things: -1. Your Jira Cloud or Jira Server URL. For Jira Cloud, it looks something like `yourcompany.atlassian.net`. +1. Your Jira Cloud or Jira Server URL. For Jira Cloud, it looks something like yourcompany.atlassian.net. 2. Your username on Jira, i.e. benarent **Note: Not your email address.** -3. Your Jira API token. **For Jira Server, this is a password. It's a good idea to create a separate user record with permissions limited to accessing this particular project board, and use this with the bot.** +3. Your Jira API token. **For Jira Server, this is a password. it's a good idea to create a separate user record with permissions limited to accessing this particular project board, and use this with the bot.** 4. And the Jira Project key, available in Project settings. -The `[http]` setting block describes how the Plugin's HTTP server works. The HTTP server is responsible for listening for updates from Jira, and processing updates, like when someone drags a task from Inbox to Approved column. +`[http]` setting block describes how the Plugin's HTTP server works. The HTTP server is responsible for listening for updates from Jira, and processing updates, like when someone drags a task from Inbox to Approved column. You must provide an address the server should listen on, and a certificate to use, unless you plan on running with `--insecure-no-tls`, which we don't recommend in production. @@ -166,7 +201,7 @@ $ teleport-jira start # DEBU Watcher connected access/service_job.go:62 ``` -The log output should look familiar to what the Teleport service logs. You should see that it connected to Teleport and is listening for new Teleport requests and Jira webhooks. +The log output should look familiar to what Teleport service logs. You should see that it connected to Teleport, and is listening for new Teleport requests and Jira webhooks. Go ahead and test it: @@ -176,10 +211,10 @@ $ tsh login --request-roles=admin That should create a new permission request on Teleport (you can test if it did with `tctl request ls` ), and you should see a new task on your Jira project board. -### Set up systemd +### Setup with SystemD -In production, we recommend starting the Teleport plugin daemon via an init system like systemd. -Here's the recommended Teleport plugin service unit file for systemd: +In production, we recommend starting teleport plugin daemon via an init system like systemd . +Here's the recommended Teleport Plugin service unit file for systemd: ```ini (!examples/systemd/plugins/teleport-jira.service!) @@ -187,7 +222,7 @@ Here's the recommended Teleport plugin service unit file for systemd: Save this as `teleport-jira.service`. -## Audit log +## Audit Log The plugin will let anyone with access to the Jira board approve or deny requests, so it's important to review Teleport's audit log. diff --git a/docs/pages/enterprise/workflow/ssh-approval-mattermost.mdx b/docs/pages/enterprise/workflow/ssh-approval-mattermost.mdx index 48439b8c0f9f6..cc2ceff979154 100644 --- a/docs/pages/enterprise/workflow/ssh-approval-mattermost.mdx +++ b/docs/pages/enterprise/workflow/ssh-approval-mattermost.mdx @@ -4,9 +4,11 @@ description: This guide explains how to setup a Mattermost plugin for Teleport f h1: Teleport Mattermost Plugin Setup --- -This guide will explain how to set up Teleport with Mattermost, an open source messaging platform. Teleport's Mattermost integration allows teams to approve or deny Teleport Access Requests using Mattermost. +This guide will talk through how to setup Teleport with Mattermost. Teleport to +Mattermost integration allows teams to approve or deny Teleport access requests +using [Mattermost](https://mattermost.com/) an open source messaging platform. -#### Example Mattermost request +#### Example Mattermost Request

      SBL52_{mHXjQv!h-0m^$~5Sb^G<<3 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 diff --git a/docs/pages/access-controls/getting-started.mdx b/docs/pages/access-controls/getting-started.mdx index 1b3ecb7b07dfd..41c19b7bc496a 100644 --- a/docs/pages/access-controls/getting-started.mdx +++ b/docs/pages/access-controls/getting-started.mdx @@ -90,7 +90,7 @@ We're now going to set up a GitHub connector for Teleport Open Source Edition an ``` - + Follow [SAML Okta Guide](../enterprise/sso/okta.mdx#configure-okta) to create a SAML app. Check out [OIDC guides](../enterprise/sso/oidc.mdx#identity-providers) for OpenID Connect apps. Save the file below as `okta.yaml` and update the `acs` field. diff --git a/docs/pages/access-controls/guides/dual-authz.mdx b/docs/pages/access-controls/guides/dual-authz.mdx index 8aa899de8d0bc..92b640334b37a 100644 --- a/docs/pages/access-controls/guides/dual-authz.mdx +++ b/docs/pages/access-controls/guides/dual-authz.mdx @@ -4,6 +4,8 @@ description: Dual Authorization for SSH and Kubernetes. videoBanner: b_iqJm_o15I --- +# Dual Authorization + You can set up Teleport to require require the approval of multiple team members to perform some critical actions. Here are the most common scenarios: @@ -28,8 +30,8 @@ two team members for a privileged role `dbadmin`. ## Prerequisites -- Installed [Teleport Enterprise](../../enterprise/introduction.mdx) or [Teleport Cloud](../../cloud/introduction.mdx) >= 7.0.0 -- [Tctl enterprise admin tool](https://goteleport.com/teleport/download) >= 7.0.0 +- Installed [Teleport Enterprise](../../enterprise/introduction.mdx) or [Teleport Cloud](../../cloud/introduction.mdx) >= (=teleport.version=) +- [Tctl enterprise admin tool](https://goteleport.com/teleport/download) >= (=teleport.version=) - Mattermost installed. (!docs/pages/includes/tctl.mdx!) ## Set up Teleport bot -### Create a bot within Mattermost + + + + Enable bot account creation in "System Console -> Integrations". + + Toggle `Enable Bot Account Creation`. -Enable bot account creation in "System Console -> Integrations". + ![Enable bots](../../../img/access-controls/dual-authz/mattermost-0-enable.png) -Toggle `Enable Bot Account Creation`. + Go back to your team settings, navigate to "Integrations -> Bot Accounts". Press "Add Bot Account". -![Enable bots](../../../img/access-controls/dual-authz/mattermost-0-enable.png) + ![Enable bots](../../../img/access-controls/dual-authz/mattermost-1-bot.png) -Go back to your team settings, navigate to "Integrations -> Bot Accounts". Press "Add Bot Account". + Add the "Post All" permission on the new account. -![Enable bots](../../../img/access-controls/dual-authz/mattermost-1-bot.png) + ![Enable bots](../../../img/access-controls/dual-authz/mattermost-2-all-permissions@2x.png) + + Create the bot and save the access token. + + + -Add the "Post All" permission on the new account. +Create a non-interactive bot `access-plugin` user and role. + +```yaml +kind: user +metadata: + name: access-plugin +spec: + roles: ['access-plugin'] +version: v2 +--- +kind: role +version: v5 +metadata: + name: access-plugin +spec: + allow: + rules: + - resources: ['access_request'] + verbs: ['list', 'read'] + - resources: ['access_plugin_data'] + verbs: ['update'] + # teleport currently refuses to issue certs for a user with 0 logins, + # this restriction may be lifted in future versions. + logins: ['access-plugin-not-used'] +``` -![Enable bots](../../../img/access-controls/dual-authz/mattermost-2-all-permissions@2x.png) + + Here and below follow along and create yaml resources using `tctl create -f`: -Create the bot and save the access token. + ```code + $ tctl create -f access.yaml + ``` + -### Set up RBAC for the plugin -(!docs/pages/includes/plugins/rbac.mdx!) +**Export access-plugin cert** -### Export the access-plugin identity files +Teleport Plugin uses the `access-plugin` role and user to perform the approval. +We export the identify files, using `tctl auth sign`. -(!docs/pages/includes/plugins/identity-export.mdx!) +```code +$ tctl auth sign --format=tls --user=access-plugin --out=auth --ttl=720h +``` -We'll reference the exported file(s) later when configuring the plugin. +Teleport will generate `auth.crt`, `auth.key`, and `auth.cas` - a certificate, a private key, and a set of CA certs respectively. -### Install the plugin +**Install the plugin** @@ -102,20 +146,11 @@ We'll reference the exported file(s) later when configuring the plugin. $ teleport-mattermost configure > /etc/teleport-mattermost.toml ``` -Update the config with the Teleport address, Mattermost URL, and a bot token. +Update the config with Teleport address, Mattermost URL, and a bot token. - - -```yaml -(!examples/resources/plugins/teleport-mattermost-self.toml!) -``` - - -```yaml -(!examples/resources/plugins/teleport-mattermost-cloud.toml!) +```toml +(!examples/resources/plugins/teleport-mattermost.toml!) ``` - - ## Dual authorization @@ -217,7 +252,7 @@ Bob can also assume granted access request roles using Web UI: ## Troubleshooting -### Cert errors in self-hosted deployments +**Cert errors** You may be getting certificate errors if Teleport's auth server is missing an address in the server certificate: diff --git a/docs/pages/access-controls/guides/impersonation.mdx b/docs/pages/access-controls/guides/impersonation.mdx index cc722ffa3a437..38c4117fc9703 100644 --- a/docs/pages/access-controls/guides/impersonation.mdx +++ b/docs/pages/access-controls/guides/impersonation.mdx @@ -312,15 +312,23 @@ $ tsh login --proxy=teleport.example.com --user=alice --auth=local $ tctl auth sign --user=security-scanner --format=openssh --out=security-scanner --ttl=10h ``` -### Filter fields - -Here is an explanation of the fields used in the `where` conditions within this guide. - -| Field | Description | -|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| -| `user.spec.traits["group"]` | The list of traits from a local or SSO user where the `group` trait typically comes from an external identity provider | -| `impersonate_role.metadata.labels["

    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ŕ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%0DIbRYd&yBpDvAN&#ro5@_ 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 h86R^7_Gt}^4b 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 z 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 vtCHs=4vIo$oVOk(96m4oU1QM?VcIrcC*f# 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 GRluk)C$IT0lo;TH7Wz3q>hwR|h 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 Ypl$Vw{iu6A1pGRF)b&WkVV}$Y2X#Zw6#xJL diff --git a/docs/img/database-access/guides/redis/redisinsight-startup.png b/docs/img/database-access/guides/redis/redisinsight-startup.png deleted file mode 100644 index 3102842af9d8e7d9d9dfeae8f133d9914a08e8d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 453447 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 gi^OiZ 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

    SBL52_{mHXjQv!h-0m^$~5Sb^G<<3 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 literal 0 HcmV?d00001 diff --git a/docs/pages/access-controls/getting-started.mdx b/docs/pages/access-controls/getting-started.mdx index 41c19b7bc496a..1b3ecb7b07dfd 100644 --- a/docs/pages/access-controls/getting-started.mdx +++ b/docs/pages/access-controls/getting-started.mdx @@ -90,7 +90,7 @@ We're now going to set up a GitHub connector for Teleport Open Source Edition an ``` - + Follow [SAML Okta Guide](../enterprise/sso/okta.mdx#configure-okta) to create a SAML app. Check out [OIDC guides](../enterprise/sso/oidc.mdx#identity-providers) for OpenID Connect apps. Save the file below as `okta.yaml` and update the `acs` field. diff --git a/docs/pages/access-controls/guides/dual-authz.mdx b/docs/pages/access-controls/guides/dual-authz.mdx index 92b640334b37a..8aa899de8d0bc 100644 --- a/docs/pages/access-controls/guides/dual-authz.mdx +++ b/docs/pages/access-controls/guides/dual-authz.mdx @@ -4,8 +4,6 @@ description: Dual Authorization for SSH and Kubernetes. videoBanner: b_iqJm_o15I --- -# Dual Authorization - You can set up Teleport to require require the approval of multiple team members to perform some critical actions. Here are the most common scenarios: @@ -30,8 +28,8 @@ two team members for a privileged role `dbadmin`. ## Prerequisites -- Installed [Teleport Enterprise](../../enterprise/introduction.mdx) or [Teleport Cloud](../../cloud/introduction.mdx) >= (=teleport.version=) -- [Tctl enterprise admin tool](https://goteleport.com/teleport/download) >= (=teleport.version=) +- Installed [Teleport Enterprise](../../enterprise/introduction.mdx) or [Teleport Cloud](../../cloud/introduction.mdx) >= 7.0.0 +- [Tctl enterprise admin tool](https://goteleport.com/teleport/download) >= 7.0.0 - Mattermost installed. (!docs/pages/includes/tctl.mdx!) ## Set up Teleport bot - - - - Enable bot account creation in "System Console -> Integrations". - - Toggle `Enable Bot Account Creation`. +### Create a bot within Mattermost - ![Enable bots](../../../img/access-controls/dual-authz/mattermost-0-enable.png) +Enable bot account creation in "System Console -> Integrations". - Go back to your team settings, navigate to "Integrations -> Bot Accounts". Press "Add Bot Account". +Toggle `Enable Bot Account Creation`. - ![Enable bots](../../../img/access-controls/dual-authz/mattermost-1-bot.png) +![Enable bots](../../../img/access-controls/dual-authz/mattermost-0-enable.png) - Add the "Post All" permission on the new account. +Go back to your team settings, navigate to "Integrations -> Bot Accounts". Press "Add Bot Account". - ![Enable bots](../../../img/access-controls/dual-authz/mattermost-2-all-permissions@2x.png) - - Create the bot and save the access token. - - - +![Enable bots](../../../img/access-controls/dual-authz/mattermost-1-bot.png) -Create a non-interactive bot `access-plugin` user and role. - -```yaml -kind: user -metadata: - name: access-plugin -spec: - roles: ['access-plugin'] -version: v2 ---- -kind: role -version: v5 -metadata: - name: access-plugin -spec: - allow: - rules: - - resources: ['access_request'] - verbs: ['list', 'read'] - - resources: ['access_plugin_data'] - verbs: ['update'] - # teleport currently refuses to issue certs for a user with 0 logins, - # this restriction may be lifted in future versions. - logins: ['access-plugin-not-used'] -``` +Add the "Post All" permission on the new account. - - Here and below follow along and create yaml resources using `tctl create -f`: +![Enable bots](../../../img/access-controls/dual-authz/mattermost-2-all-permissions@2x.png) - ```code - $ tctl create -f access.yaml - ``` - +Create the bot and save the access token. +### Set up RBAC for the plugin -**Export access-plugin cert** +(!docs/pages/includes/plugins/rbac.mdx!) -Teleport Plugin uses the `access-plugin` role and user to perform the approval. -We export the identify files, using `tctl auth sign`. +### Export the access-plugin identity files -```code -$ tctl auth sign --format=tls --user=access-plugin --out=auth --ttl=720h -``` +(!docs/pages/includes/plugins/identity-export.mdx!) -Teleport will generate `auth.crt`, `auth.key`, and `auth.cas` - a certificate, a private key, and a set of CA certs respectively. +We'll reference the exported file(s) later when configuring the plugin. -**Install the plugin** +### Install the plugin @@ -146,11 +102,20 @@ Teleport will generate `auth.crt`, `auth.key`, and `auth.cas` - a certificate, a $ teleport-mattermost configure > /etc/teleport-mattermost.toml ``` -Update the config with Teleport address, Mattermost URL, and a bot token. +Update the config with the Teleport address, Mattermost URL, and a bot token. -```toml -(!examples/resources/plugins/teleport-mattermost.toml!) + + +```yaml +(!examples/resources/plugins/teleport-mattermost-self.toml!) +``` + + +```yaml +(!examples/resources/plugins/teleport-mattermost-cloud.toml!) ``` + + ## Dual authorization @@ -252,7 +217,7 @@ Bob can also assume granted access request roles using Web UI: ## Troubleshooting -**Cert errors** +### Cert errors in self-hosted deployments You may be getting certificate errors if Teleport's auth server is missing an address in the server certificate: diff --git a/docs/pages/access-controls/guides/impersonation.mdx b/docs/pages/access-controls/guides/impersonation.mdx index 38c4117fc9703..cc722ffa3a437 100644 --- a/docs/pages/access-controls/guides/impersonation.mdx +++ b/docs/pages/access-controls/guides/impersonation.mdx @@ -312,23 +312,15 @@ $ tsh login --proxy=teleport.example.com --user=alice --auth=local $ tctl auth sign --user=security-scanner --format=openssh --out=security-scanner --ttl=10h ``` -Here is a summary of variables and functions we used in this guide: - - - - - - - - - - - - - - - - - - -
    Variable or FunctionDescription
    `user.spec.traits`Access traits of local or SSO user.
    `contains(list, var)`Checks whether list contains variable
    `equals(var, var)`Checks whether one variable is equal another
    +### Filter fields + +Here is an explanation of the fields used in the `where` conditions within this guide. + +| Field | Description | +|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| `user.spec.traits["group"]` | The list of traits from a local or SSO user where the `group` trait typically comes from an external identity provider | +| `impersonate_role.metadata.labels["

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ŕ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%0DIbRYd&yBpDvAN&#ro5@_ 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