diff --git a/api/client/README.md b/api/client/README.md new file mode 100644 index 00000000000..4f2a4be0194 --- /dev/null +++ b/api/client/README.md @@ -0,0 +1,10 @@ +This package is documented using a combination of [pkg.go.dev](https://pkg.go.dev/github.com/gravitational/teleport/api/client) and Teleport [Docs](https://goteleport.com/docs/). + +## Reference + +- [Introduction](https:/goteleport.com/docs/reference/api/introduction/) +- [Getting Started](https:/goteleport.com/docs/reference/api/getting-started/) +- [Architecture](https:/goteleport.com/docs/reference/api/architecture/) +- [pkg.go.dev](https://pkg.go.dev/github.com/gravitational/teleport/api/client/) + - [Using the client](https://pkg.go.dev/github.com/gravitational/teleport/api/client#Client/) + - [Working with credentials](https://pkg.go.dev/github.com/gravitational/teleport/api/client#Credentials/) \ No newline at end of file diff --git a/api/client/client.go b/api/client/client.go index 7984b7112e8..48f81d6037a 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package client holds the implementation of the Teleport gRPC api client. package client import ( @@ -28,10 +27,12 @@ import ( "time" "github.com/gravitational/teleport/api/client/proto" + "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/api/utils" "github.com/golang/protobuf/ptypes/empty" "github.com/gravitational/trace" @@ -50,7 +51,12 @@ func init() { } } -// Client is a gRPC Client that connects to a teleport server through TLS. +// Client is a gRPC Client that connects to a Teleport Auth server either +// locally or over ssh through a Teleport web proxy or tunnel proxy. +// +// This client can be used to cover a variety of Teleport use cases, +// such as programmatically handling access requests, integrating +// with external tools, or dynamically configuring Teleport. type Client struct { // c contains configuration values for the client. c Config @@ -71,30 +77,22 @@ type Client struct { // New creates a new API client with an open connection to a Teleport server. // -// The server can be an auth server, web proxy, or tunnel proxy. Multiple server -// addresses can be specified in cfg. +// New will try to open a connection with all combinations of addresses and credentials. +// The first successful connection to a server will be used, or an aggregated error will +// be returned if all combinations fail. // -// TLS credentials are required in cfg, and can be generated by a number of -// convenience functions that pull certificates that can be easily generated with tsh. -// SSH credentials are required to connect to web/tunnel proxies and can only be -// provided through the identity file credential loader. +// cfg.Credentials must be non-empty. One of cfg.Addrs and cfg.Dialer must be non-empty, +// unless LoadProfile is used to fetch Credentials and load a web proxy dialer. // -// LoadIdentityFile("identity-file-path") -// -// New will try to open a connection with all combinations of addresses, -// server types, and credentials. The first successful connection to a server -// will be used, or an aggregated error will be returned if all combinations fail. -// -// If cfg.DialInBackground is true, New will only use the first credentials listed. -// A predefined dialer must be provided in cfg, or the first addr must be to an auth server. -// The connection will be dialed in the background, so the connection is not guaranteed -// to be open. This option is primarily meant for internal use where the client has -// direct access to server values that guarantee a successful connection. +// See the example below for usage. func New(ctx context.Context, cfg Config) (clt *Client, err error) { if err = cfg.CheckAndSetDefaults(); err != nil { return nil, trace.Wrap(err) } + // If cfg.DialInBackground is true, only a single connection is attempted. + // This option is primarily meant for internal use where the client has + // direct access to server values that guarantee a successful connection. if cfg.DialInBackground { return connectInBackground(ctx, cfg) } @@ -127,7 +125,7 @@ func connectInBackground(ctx context.Context, cfg Config) (*Client, error) { return nil, trace.BadParameter("must have a Dialer or Addrs in config") } if dialer == nil { - dialer = NewDialer(cfg.KeepAlivePeriod, cfg.DialTimeout) + dialer = NewDirectDialer(cfg.KeepAlivePeriod, cfg.DialTimeout) addr = cfg.Addrs[0] } @@ -215,7 +213,7 @@ func connect(ctx context.Context, cfg Config) (*Client, error) { for _, addr := range cfg.Addrs { // Connect to auth. - dialer := NewDialer(cfg.KeepAlivePeriod, cfg.DialTimeout) + dialer := NewDirectDialer(cfg.KeepAlivePeriod, cfg.DialTimeout) clt := newClient(cfg, dialer, tlsConfig) syncConnect(clt, addr) @@ -225,10 +223,10 @@ func connect(ctx context.Context, cfg Config) (*Client, error) { go func(addr string) { defer wg.Done() // Try connecting to web proxy to retrieve tunnel address. - if pr, err := Find(ctx, addr, cfg.InsecureAddressDiscovery, nil); err == nil { + if pr, err := webclient.Find(ctx, addr, cfg.InsecureAddressDiscovery, nil); err == nil { addr = pr.Proxy.SSH.TunnelPublicAddr } - dialer := NewTunnelDialer(*sshConfig, cfg.KeepAlivePeriod, cfg.DialTimeout) + dialer := newTunnelDialer(*sshConfig, cfg.KeepAlivePeriod, cfg.DialTimeout) clt := newClient(cfg, dialer, tlsConfig) syncConnect(clt, addr) }(addr) @@ -310,7 +308,7 @@ type Config struct { // Addrs is a list of teleport auth/proxy server addresses to dial. Addrs []string // Credentials are a list of credentials to use when attempting - // to form a connection from client to server. + // to connect to the server. Credentials []Credentials // Dialer is a custom dialer used to dial a server. If set, Dialer // takes precedence over all other connection options. @@ -605,7 +603,7 @@ func (c *Client) SetAccessRequestState(ctx context.Context, params types.AccessR Annotations: params.Annotations, Roles: params.Roles, } - if d := GetDelegator(ctx); d != "" { + if d := utils.GetDelegator(ctx); d != "" { setter.Delegator = d } _, err := c.grpc.SetAccessRequestState(ctx, &setter, c.callOpts...) diff --git a/api/client/contextdialer.go b/api/client/contextdialer.go index dec60bc4884..e901396fdc3 100644 --- a/api/client/contextdialer.go +++ b/api/client/contextdialer.go @@ -21,6 +21,7 @@ import ( "net" "time" + "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/utils/sshutils" @@ -34,7 +35,7 @@ type ContextDialer interface { DialContext(ctx context.Context, network, addr string) (net.Conn, error) } -// ContextDialerFunc is a function wrapper that implements the ContextDialer interface +// ContextDialerFunc is a function wrapper that implements the ContextDialer interface. type ContextDialerFunc func(ctx context.Context, network, addr string) (net.Conn, error) // DialContext is a function that dials to the specified address @@ -42,17 +43,36 @@ func (f ContextDialerFunc) DialContext(ctx context.Context, network, addr string return f(ctx, network, addr) } -// NewDialer makes a new dialer. -func NewDialer(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, } } -// NewTunnelDialer make a new ssh tunnel dialer. -func NewTunnelDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Duration) ContextDialer { - dialer := NewDialer(keepAlivePeriod, dialTimeout) +// 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 { + dialer := newTunnelDialer(ssh, keepAlivePeriod, dialTimeout) + return ContextDialerFunc(func(ctx context.Context, network, _ string) (conn net.Conn, err error) { + // Ping web proxy to retrieve tunnel proxy address. + pr, err := webclient.Find(ctx, discoveryAddr, insecure, nil) + if err != nil { + return nil, trace.Wrap(err) + } + + conn, err = dialer.DialContext(ctx, network, pr.Proxy.SSH.TunnelPublicAddr) + if err != nil { + return nil, trace.Wrap(err) + } + return conn, nil + }) +} + +// 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) return ContextDialerFunc(func(ctx context.Context, network, addr string) (conn net.Conn, err error) { conn, err = dialer.DialContext(ctx, network, addr) if err != nil { diff --git a/api/client/credentials.go b/api/client/credentials.go index 1ca13dd6fe4..7cd06d85dac 100644 --- a/api/client/credentials.go +++ b/api/client/credentials.go @@ -17,84 +17,98 @@ limitations under the License. package client import ( - "context" "crypto/tls" "crypto/x509" "io/ioutil" - "net" "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/identityfile" + "github.com/gravitational/teleport/api/profile" "github.com/gravitational/trace" "golang.org/x/crypto/ssh" "golang.org/x/net/http2" ) -// Credentials are used to authenticate to Auth. +// Credentials are used to authenticate the API auth client. Some Credentials +// also provide other functionality, such as automatic address discovery and +// ssh connectivity. +// +// See the examples below for an example of each loader. type Credentials interface { - // Dialer is used to create a dialer used to connect to Auth. + // Dialer is used to create a dialer used to connect to the Auth server. Dialer(cfg Config) (ContextDialer, error) - // TLSConfig returns TLS configuration used to connect to Auth. + // TLSConfig returns TLS configuration used to authenticate the client. TLSConfig() (*tls.Config, error) - // SSHClientConfig returns SSH configuration used to connect to Proxy through tunnel. + // SSHClientConfig returns SSH configuration used to connect to the + // Auth server through a reverse tunnel. SSHClientConfig() (*ssh.ClientConfig, error) } -// LoadTLS is used to load credentials directly from another *tls.Config. -func LoadTLS(tlsConfig *tls.Config) *TLSConfigCreds { - return &TLSConfigCreds{ +// LoadTLS is used to load Credentials directly from a *tls.Config. +// +// TLS creds can only be used to connect directly to a Teleport Auth server. +func LoadTLS(tlsConfig *tls.Config) Credentials { + return &tlsConfigCreds{ tlsConfig: tlsConfig, } } -// TLSConfigCreds are used to authenticate the client -// with a predefined *tls.Config. -type TLSConfigCreds struct { +// tlsConfigCreds use a defined *tls.Config to provide client credentials. +type tlsConfigCreds struct { tlsConfig *tls.Config } -// Dialer is used to dial a connection to Auth. -func (c *TLSConfigCreds) Dialer(cfg Config) (ContextDialer, error) { +// Dialer is used to dial a connection to an Auth server. +func (c *tlsConfigCreds) Dialer(cfg Config) (ContextDialer, error) { return nil, trace.NotImplemented("no dialer") } -// TLSConfig returns TLS configuration used to connect to Auth. -func (c *TLSConfigCreds) TLSConfig() (*tls.Config, error) { +// TLSConfig returns TLS configuration. +func (c *tlsConfigCreds) TLSConfig() (*tls.Config, error) { if c.tlsConfig == nil { return nil, trace.BadParameter("tls config is nil") } - return configure(c.tlsConfig), nil + return configureTLS(c.tlsConfig), nil } -// SSHClientConfig returns SSH configuration used to connect to Proxy. -func (c *TLSConfigCreds) SSHClientConfig() (*ssh.ClientConfig, error) { +// SSHClientConfig returns SSH configuration. +func (c *tlsConfigCreds) SSHClientConfig() (*ssh.ClientConfig, error) { return nil, trace.NotImplemented("no ssh config") } -// LoadKeyPair is used to load credentials from files on disk. -func LoadKeyPair(certFile string, keyFile string, caFile string) *KeyPairCreds { - return &KeyPairCreds{ +// LoadKeyPair is used to load Credentials from a certicate keypair on disk. +// +// KeyPair Credentials can only be used to connect directly to a Teleport Auth server. +// +// New KeyPair files can be generated with tsh or tctl. +// $ tctl auth sign --format=tls --user=api-user --out=path/to/certs +// +// The certificates' time to live can be specified with --ttl. +// +// See the example below for usage. +func LoadKeyPair(certFile, keyFile, caFile string) Credentials { + return &keypairCreds{ certFile: certFile, keyFile: keyFile, caFile: caFile, } } -// KeyPairCreds are used to authenticate the client -// with certificates generated in the given file paths. -type KeyPairCreds struct { +// keypairCreds use keypair certificates to provide client credentials. +type keypairCreds struct { certFile string keyFile string caFile string } -// Dialer is used to dial a connection to Auth. -func (c *KeyPairCreds) Dialer(cfg Config) (ContextDialer, error) { +// Dialer is used to dial a connection to an Auth server. +func (c *keypairCreds) Dialer(cfg Config) (ContextDialer, error) { return nil, trace.NotImplemented("no dialer") } -// TLSConfig returns TLS configuration used to connect to Auth. -func (c *KeyPairCreds) TLSConfig() (*tls.Config, error) { +// TLSConfig returns TLS configuration. +func (c *keypairCreds) TLSConfig() (*tls.Config, error) { cert, err := tls.LoadX509KeyPair(c.certFile, c.keyFile) if err != nil { return nil, trace.Wrap(err) @@ -110,38 +124,48 @@ func (c *KeyPairCreds) TLSConfig() (*tls.Config, error) { return nil, trace.BadParameter("invalid TLS CA cert PEM") } - return configure(&tls.Config{ + return configureTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: pool, }), nil } -// SSHClientConfig returns SSH configuration used to connect to Proxy. -func (c *KeyPairCreds) SSHClientConfig() (*ssh.ClientConfig, error) { +// SSHClientConfig returns SSH configuration. +func (c *keypairCreds) SSHClientConfig() (*ssh.ClientConfig, error) { return nil, trace.NotImplemented("no ssh config") } -// LoadIdentityFile is used to load credentials from an identity file on disk. -func LoadIdentityFile(path string) *IdentityCreds { - return &IdentityCreds{ +// LoadIdentityFile is used to load Credentials from an identity file on disk. +// +// Identity Credentials can be used to connect to an auth server directly +// or through a reverse tunnel. +// +// A new identity file can be generated with tsh or tctl. +// $ tsh login --user=api-user --out=identity-file-path +// $ tctl auth sign --user=api-user --out=identity-file-path +// +// The identity file's time to live can be specified with --ttl. +// +// See the example below for usage. +func LoadIdentityFile(path string) Credentials { + return &identityCreds{ path: path, } } -// IdentityCreds are used to authenticate the client -// with an identity file generated in the given file path. -type IdentityCreds struct { +// identityCreds use an identity file to provide client credentials. +type identityCreds struct { path string - identityFile *IdentityFile + identityFile *identityfile.IdentityFile } -// Dialer is used to dial a connection to Auth. -func (c *IdentityCreds) Dialer(cfg Config) (ContextDialer, error) { +// Dialer is used to dial a connection to an Auth server. +func (c *identityCreds) Dialer(cfg Config) (ContextDialer, error) { return nil, trace.NotImplemented("no dialer") } -// TLSConfig returns TLS configuration used to connect to Auth. -func (c *IdentityCreds) TLSConfig() (*tls.Config, error) { +// TLSConfig returns TLS configuration. +func (c *identityCreds) TLSConfig() (*tls.Config, error) { if err := c.load(); err != nil { return nil, trace.Wrap(err) } @@ -151,11 +175,11 @@ func (c *IdentityCreds) TLSConfig() (*tls.Config, error) { return nil, trace.Wrap(err) } - return configure(tlsConfig), nil + return configureTLS(tlsConfig), nil } -// SSHClientConfig returns SSH configuration used to connect to Proxy. -func (c *IdentityCreds) SSHClientConfig() (*ssh.ClientConfig, error) { +// SSHClientConfig returns SSH configuration. +func (c *identityCreds) SSHClientConfig() (*ssh.ClientConfig, error) { if err := c.load(); err != nil { return nil, trace.Wrap(err) } @@ -170,61 +194,63 @@ func (c *IdentityCreds) SSHClientConfig() (*ssh.ClientConfig, error) { // load is used to lazy load the identity file from persistent storage. // This allows LoadIdentity to avoid possible errors for UX purposes. -func (c *IdentityCreds) load() error { +func (c *identityCreds) load() error { if c.identityFile != nil { return nil } var err error - if c.identityFile, err = ReadIdentityFile(c.path); err != nil { + if c.identityFile, err = identityfile.Read(c.path); err != nil { return trace.BadParameter("identity file could not be decoded: %v", err) } return nil } -// LoadProfile is used to load credentials from a tsh Profile. -// If dir is not specified, the default profile path will be used. -// If name is not specified, the current profile name will be used. -func LoadProfile(dir, name string) *ProfileCreds { - return &ProfileCreds{ +// LoadProfile is used to load Credentials from a tsh profile on disk. +// +// dir is the profile directory. It will defaults to "~/.tsh". +// +// name is the profile name. It will default to the currently active tsh profile. +// +// Profile Credentials can be used to connect to an auth server directly +// or through a reverse tunnel. +// +// Profile Credentials will automatically attempt to find your reverse +// tunnel address and make a connection through it. +// +// A new profile can be generated with tsh. +// $ tsh login --user=api-user +func LoadProfile(dir, name string) Credentials { + return &profileCreds{ dir: dir, name: name, } } -// ProfileCreds are used to authenticate the client -// with a tsh profile with the given directory and name. -type ProfileCreds struct { +// profileCreds use a tsh profile to provide client credentials. +type profileCreds struct { dir string name string - profile *Profile + profile *profile.Profile } -// Dialer is used to dial a connection to Auth. -func (c *ProfileCreds) Dialer(cfg Config) (ContextDialer, error) { +// Dialer is used to dial a connection to an Auth server. +func (c *profileCreds) Dialer(cfg Config) (ContextDialer, error) { sshConfig, err := c.SSHClientConfig() if err != nil { return nil, trace.Wrap(err) } - dialer := NewTunnelDialer(*sshConfig, cfg.KeepAlivePeriod, cfg.DialTimeout) - return ContextDialerFunc(func(ctx context.Context, network, _ string) (conn net.Conn, err error) { - // Ping web proxy to retrieve tunnel proxy address. - pr, err := Find(ctx, c.profile.WebProxyAddr, cfg.InsecureAddressDiscovery, nil) - if err != nil { - return nil, trace.Wrap(err) - } - - conn, err = dialer.DialContext(ctx, network, pr.Proxy.SSH.TunnelPublicAddr) - if err != nil { - // not wrapping on purpose to preserve the original error - return nil, err - } - return conn, nil - }), nil + return NewProxyDialer( + *sshConfig, + cfg.KeepAlivePeriod, + cfg.DialTimeout, + c.profile.WebProxyAddr, + cfg.InsecureAddressDiscovery, + ), nil } -// TLSConfig returns TLS configuration used to connect to Auth. -func (c *ProfileCreds) TLSConfig() (*tls.Config, error) { +// TLSConfig returns TLS configuration. +func (c *profileCreds) TLSConfig() (*tls.Config, error) { if err := c.load(); err != nil { return nil, trace.Wrap(err) } @@ -234,11 +260,11 @@ func (c *ProfileCreds) TLSConfig() (*tls.Config, error) { return nil, trace.Wrap(err) } - return configure(tlsConfig), nil + return configureTLS(tlsConfig), nil } -// SSHClientConfig returns SSH configuration used to connect to Proxy. -func (c *ProfileCreds) SSHClientConfig() (*ssh.ClientConfig, error) { +// SSHClientConfig returns SSH configuration. +func (c *profileCreds) SSHClientConfig() (*ssh.ClientConfig, error) { if err := c.load(); err != nil { return nil, trace.Wrap(err) } @@ -253,18 +279,18 @@ func (c *ProfileCreds) SSHClientConfig() (*ssh.ClientConfig, error) { // load is used to lazy load the profile from persistent storage. // This allows LoadProfile to avoid possible errors for UX purposes. -func (c *ProfileCreds) load() error { +func (c *profileCreds) load() error { if c.profile != nil { return nil } var err error - if c.profile, err = ProfileFromDir(c.dir, c.name); err != nil { + if c.profile, err = profile.FromDir(c.dir, c.name); err != nil { return trace.BadParameter("profile could not be decoded: %v", err) } return nil } -func configure(c *tls.Config) *tls.Config { +func configureTLS(c *tls.Config) *tls.Config { tlsConfig := c.Clone() tlsConfig.NextProtos = []string{http2.NextProtoTLS} diff --git a/api/client/credentials_test.go b/api/client/credentials_test.go index 6c832ac6b3c..5c964780807 100644 --- a/api/client/credentials_test.go +++ b/api/client/credentials_test.go @@ -26,6 +26,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/gravitational/teleport/api/identityfile" + "github.com/gravitational/teleport/api/profile" "github.com/gravitational/teleport/api/utils/sshutils" "github.com/stretchr/testify/require" @@ -61,18 +63,18 @@ func TestLoadIdentityFile(t *testing.T) { // Write identity file to disk. path := filepath.Join(t.TempDir(), "file") - idFile := &IdentityFile{ + idFile := &identityfile.IdentityFile{ PrivateKey: keyPEM, - Certs: Certs{ + Certs: identityfile.Certs{ TLS: tlsCert, SSH: sshCert, }, - CACerts: CACerts{ + CACerts: identityfile.CACerts{ TLS: [][]byte{tlsCACert}, SSH: [][]byte{sshCACert}, }, } - err := WriteIdentityFile(idFile, path) + err := identityfile.Write(idFile, path) require.NoError(t, err) // Load identity file from disk. @@ -134,7 +136,7 @@ func TestLoadProfile(t *testing.T) { // Write identity file to disk. dir := t.TempDir() name := "proxy.example.com" - p := &Profile{ + p := &profile.Profile{ WebProxyAddr: "proxy.example.com:3080", SiteName: "example.com", Username: "testUser", @@ -143,14 +145,14 @@ func TestLoadProfile(t *testing.T) { // Save profile and keys to disk. require.NoError(t, p.SaveToDir(dir, true)) - require.NoError(t, os.MkdirAll(p.keyDir(), 0700)) - require.NoError(t, os.MkdirAll(p.userKeyDir(), 0700)) - require.NoError(t, os.MkdirAll(p.sshDir(), 0700)) - require.NoError(t, ioutil.WriteFile(p.keyPath(), keyPEM, 0600)) - require.NoError(t, ioutil.WriteFile(p.tlsCertPath(), tlsCert, 0600)) - require.NoError(t, ioutil.WriteFile(p.tlsCasPath(), tlsCACert, 0600)) - require.NoError(t, ioutil.WriteFile(p.sshCertPath(), sshCert, 0600)) - require.NoError(t, ioutil.WriteFile(p.sshCasPath(), sshCACert, 0600)) + require.NoError(t, os.MkdirAll(p.KeyDir(), 0700)) + require.NoError(t, os.MkdirAll(p.UserKeyDir(), 0700)) + require.NoError(t, os.MkdirAll(p.SSHDir(), 0700)) + require.NoError(t, ioutil.WriteFile(p.KeyPath(), keyPEM, 0600)) + require.NoError(t, ioutil.WriteFile(p.TLSCertPath(), tlsCert, 0600)) + require.NoError(t, ioutil.WriteFile(p.TLSCAsPath(), tlsCACert, 0600)) + require.NoError(t, ioutil.WriteFile(p.SSHCertPath(), sshCert, 0600)) + require.NoError(t, ioutil.WriteFile(p.SSHCAsPath(), sshCACert, 0600)) // Load profile from disk. creds := LoadProfile(dir, name) @@ -183,7 +185,7 @@ func getExpectedTLSConfig(t *testing.T) *tls.Config { pool := x509.NewCertPool() require.True(t, pool.AppendCertsFromPEM(tlsCACert)) - return configure(&tls.Config{ + return configureTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: pool, }) diff --git a/api/client/doc.go b/api/client/doc.go new file mode 100644 index 00000000000..ec6e8cbb3e2 --- /dev/null +++ b/api/client/doc.go @@ -0,0 +1,3 @@ +// Package client provides a gRPC implementation of the Teleport Auth client. +// This client can be used to programatically interact with a Teleport Auth server. +package client diff --git a/api/client/doc_test.go b/api/client/doc_test.go new file mode 100644 index 00000000000..9f710374cf1 --- /dev/null +++ b/api/client/doc_test.go @@ -0,0 +1,160 @@ +package client_test + +// this package adds godoc examples for several Client types and functions +// See https://pkg.go.dev/github.com/fluhus/godoc-tricks#Examples + +import ( + "context" + "log" + "time" + + "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/types" +) + +// Below is an example of creating a new Teleport Auth client with Profile credentials, +// and using that client to create, get, and delete a Role resource object. +// +// Make sure to look at the Getting Started guide before attempting to run this example. +func ExampleClient_roleCRUD() { + ctx := context.Background() + + // Create a new client in your go file. + clt, err := client.New(ctx, client.Config{ + Credentials: []client.Credentials{ + client.LoadProfile("", ""), + }, + // set to true if your Teleport web proxy doesn't have HTTP/TLS certificate + // configured yet (never use this in production). + InsecureAddressDiscovery: false, + }) + if err != nil { + log.Fatalf("failed to create client: %v", err) + } + defer clt.Close() + + // Resource Spec structs reflect their Resource's yaml definition. + roleSpec := types.RoleSpecV3{ + Options: types.RoleOptions{ + MaxSessionTTL: types.Duration(time.Hour), + }, + Allow: types.RoleConditions{ + Logins: []string{"role1"}, + Rules: []types.Rule{ + types.NewRule(types.KindAccessRequest, []string{types.VerbList, types.VerbRead}), + }, + }, + Deny: types.RoleConditions{ + NodeLabels: types.Labels{"*": []string{"*"}}, + }, + } + + // There are helper functions for creating Teleport resources. + role, err := types.NewRole("role1", roleSpec) + if err != nil { + log.Fatalf("failed to get role: %v", err) + } + + // Getters and setters can be used to alter specs. + role.SetLogins(types.Allow, []string{"root"}) + + // Upsert overwrites the resource if it exists. Use this to create/update resources. + // Equivalent to `tctl create -f role1.yaml`. + err = clt.UpsertRole(ctx, role) + if err != nil { + log.Fatalf("failed to create role: %v", err) + } + + // Equivalent to `tctl get role/role1`. + role, err = clt.GetRole(ctx, "role1") + if err != nil { + log.Fatalf("failed to get role: %v", err) + } + + // Equivalent to `tctl rm role/role1`. + err = clt.DeleteRole(ctx, "role1") + if err != nil { + log.Fatalf("failed to delete role: %v", err) + } +} +func ExampleNew() { + ctx := context.Background() + clt, err := client.New(ctx, client.Config{ + // Multiple Addresses can be provided to attempt to + // connect to the auth server. At least one address + // must be provided, except when using the ProfileCreds. + Addrs: []string{ + // The Auth server address can be provided to connect locally. + "auth.example.com:3025", + // The tunnel proxy address can be provided + // to connect to the Auth server over SSH. + "proxy.example.com:3024", + // The web proxy address can be provided to automatically + // find the tunnel proxy address and connect using it. + "proxy.example.com:3080", + }, + // Multiple Credentials can be provided to attempt to authenticate + // the client. At least one Credentials object must be provided. + Credentials: []client.Credentials{ + client.LoadProfile("", ""), + client.LoadIdentityFile("identity-path"), + client.LoadKeyPair("cert.crt", "cert.key", "cert.cas"), + }, + // set to true if your web proxy doesn't have HTTP/TLS certificate + // configured yet (never use this in production). + InsecureAddressDiscovery: false, + }) + if err != nil { + log.Fatal(err) + } + defer clt.Close() + + clt.Ping(ctx) +} + +// Generate tsh profile with tsh. +// $ tsh login --user=api-user +// Load credentials from the default directory and current profile, or specify the directory and profile. +func ExampleCredentials_loadProfile() { + client.LoadProfile("", "") + client.LoadProfile("profile-directory", "api-user") +} + +// Load credentials from the default directory and current profile, or specify the directory and profile. +func ExampleLoadProfile() { + client.LoadProfile("", "") + client.LoadProfile("profile-directory", "api-user") +} + +// Generate identity file with tsh or tctl. +// $ tsh login --user=api-user --out=identity-file-path +// $ tctl auth sign --user=api-user --out=identity-file-path +// Load credentials from the specified identity file. +func ExampleCredentials_loadIdentity() { + client.LoadIdentityFile("identity-file-path") +} + +// Load credentials from the specified identity file. +func ExampleLoadIdentityFile() { + client.LoadIdentityFile("identity-file-path") +} + +// Generate certificate key pair with tctl. +// $ tctl auth sign --format=tls --user=api-user --out=path/to/certs +// Load credentials from the specified certificate files. +func ExampleCredentials_loadKeyPair() { + client.LoadKeyPair( + "path/to/certs.crt", + "path/to/certs.key", + "path/to/certs.cas", + ) +} + +// Load credentials from the specified certificate files. +func ExampleLoadKeyPair() { + client.LoadKeyPair( + "path/to/certs.crt", + "path/to/certs.key", + "path/to/certs.cas", + ) +} diff --git a/api/client/events.go b/api/client/events.go deleted file mode 100644 index 743e38edec5..00000000000 --- a/api/client/events.go +++ /dev/null @@ -1,217 +0,0 @@ -/* -Copyright 2020 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 ( - "github.com/gravitational/teleport/api/client/proto" - "github.com/gravitational/teleport/api/types" - - "github.com/gravitational/trace" -) - -// EventToGRPC converts a types.Event to an proto.Event -func EventToGRPC(in types.Event) (*proto.Event, error) { - eventType, err := eventTypeToGRPC(in.Type) - if err != nil { - return nil, trace.Wrap(err) - } - out := proto.Event{ - Type: eventType, - } - if in.Type == types.OpInit { - return &out, nil - } - switch r := in.Resource.(type) { - case *types.ResourceHeader: - out.Resource = &proto.Event_ResourceHeader{ - ResourceHeader: r, - } - case *types.CertAuthorityV2: - out.Resource = &proto.Event_CertAuthority{ - CertAuthority: r, - } - case *types.StaticTokensV2: - out.Resource = &proto.Event_StaticTokens{ - StaticTokens: r, - } - case *types.ProvisionTokenV2: - out.Resource = &proto.Event_ProvisionToken{ - ProvisionToken: r, - } - case *types.ClusterNameV2: - out.Resource = &proto.Event_ClusterName{ - ClusterName: r, - } - case *types.ClusterConfigV3: - out.Resource = &proto.Event_ClusterConfig{ - ClusterConfig: r, - } - case *types.UserV2: - out.Resource = &proto.Event_User{ - User: r, - } - case *types.RoleV3: - out.Resource = &proto.Event_Role{ - Role: r, - } - case *types.Namespace: - out.Resource = &proto.Event_Namespace{ - Namespace: r, - } - case *types.ServerV2: - out.Resource = &proto.Event_Server{ - Server: r, - } - case *types.ReverseTunnelV2: - out.Resource = &proto.Event_ReverseTunnel{ - ReverseTunnel: r, - } - case *types.TunnelConnectionV2: - out.Resource = &proto.Event_TunnelConnection{ - TunnelConnection: r, - } - case *types.AccessRequestV3: - out.Resource = &proto.Event_AccessRequest{ - AccessRequest: r, - } - case *types.WebSessionV2: - switch r.GetSubKind() { - case types.KindAppSession: - out.Resource = &proto.Event_AppSession{ - AppSession: r, - } - case types.KindWebSession: - out.Resource = &proto.Event_WebSession{ - WebSession: r, - } - default: - return nil, trace.BadParameter("only %q supported", types.WebSessionSubKinds) - } - case *types.WebTokenV3: - out.Resource = &proto.Event_WebToken{ - WebToken: r, - } - case *types.RemoteClusterV3: - out.Resource = &proto.Event_RemoteCluster{ - RemoteCluster: r, - } - case *types.DatabaseServerV3: - out.Resource = &proto.Event_DatabaseServer{ - DatabaseServer: r, - } - default: - return nil, trace.BadParameter("resource type %T is not supported", in.Resource) - } - return &out, nil -} - -func eventTypeToGRPC(in types.OpType) (proto.Operation, error) { - switch in { - case types.OpInit: - return proto.Operation_INIT, nil - case types.OpPut: - return proto.Operation_PUT, nil - case types.OpDelete: - return proto.Operation_DELETE, nil - default: - return -1, trace.BadParameter("event type %v is not supported", in) - } -} - -// EventFromGRPC converts an proto.Event to a types.Event -func EventFromGRPC(in proto.Event) (*types.Event, error) { - eventType, err := eventTypeFromGRPC(in.Type) - if err != nil { - return nil, trace.Wrap(err) - } - out := types.Event{ - Type: eventType, - } - if eventType == types.OpInit { - return &out, nil - } - if r := in.GetResourceHeader(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetCertAuthority(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetStaticTokens(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetProvisionToken(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetClusterName(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetClusterConfig(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetUser(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetRole(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetNamespace(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetServer(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetReverseTunnel(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetTunnelConnection(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetAccessRequest(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetAppSession(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetWebSession(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetWebToken(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetRemoteCluster(); r != nil { - out.Resource = r - return &out, nil - } else if r := in.GetDatabaseServer(); r != nil { - out.Resource = r - return &out, nil - } else { - return nil, trace.BadParameter("received unsupported resource %T", in.Resource) - } -} - -func eventTypeFromGRPC(in proto.Operation) (types.OpType, error) { - switch in { - case proto.Operation_INIT: - return types.OpInit, nil - case proto.Operation_PUT: - return types.OpPut, nil - case proto.Operation_DELETE: - return types.OpDelete, nil - default: - return types.OpInvalid, trace.BadParameter("unsupported operation type: %v", in) - } -} diff --git a/api/client/streamwatcher.go b/api/client/streamwatcher.go index 6d0669f8d4d..7b5e7a16e10 100644 --- a/api/client/streamwatcher.go +++ b/api/client/streamwatcher.go @@ -87,7 +87,7 @@ func (w *streamWatcher) receiveEvents() { w.closeWithError(trail.FromGRPC(err)) return } - out, err := EventFromGRPC(*event) + out, err := eventFromGRPC(*event) if err != nil { w.closeWithError(trail.FromGRPC(err)) return @@ -100,6 +100,90 @@ func (w *streamWatcher) receiveEvents() { } } +// eventFromGRPC converts an proto.Event to a types.Event +func eventFromGRPC(in proto.Event) (*types.Event, error) { + eventType, err := eventTypeFromGRPC(in.Type) + if err != nil { + return nil, trace.Wrap(err) + } + out := types.Event{ + Type: eventType, + } + if eventType == types.OpInit { + return &out, nil + } + if r := in.GetResourceHeader(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetCertAuthority(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetStaticTokens(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetProvisionToken(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetClusterName(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetClusterConfig(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetUser(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetRole(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetNamespace(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetServer(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetReverseTunnel(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetTunnelConnection(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetAccessRequest(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetAppSession(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetWebSession(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetWebToken(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetRemoteCluster(); r != nil { + out.Resource = r + return &out, nil + } else if r := in.GetDatabaseServer(); r != nil { + out.Resource = r + return &out, nil + } else { + return nil, trace.BadParameter("received unsupported resource %T", in.Resource) + } +} + +func eventTypeFromGRPC(in proto.Operation) (types.OpType, error) { + switch in { + case proto.Operation_INIT: + return types.OpInit, nil + case proto.Operation_PUT: + return types.OpPut, nil + case proto.Operation_DELETE: + return types.OpDelete, nil + default: + return types.OpInvalid, trace.BadParameter("unsupported operation type: %v", in) + } +} + // Done returns a channel that closes once the streamWatcher is Closed func (w *streamWatcher) Done() <-chan struct{} { return w.ctx.Done() diff --git a/api/client/webclient.go b/api/client/webclient/webclient.go similarity index 98% rename from api/client/webclient.go rename to api/client/webclient/webclient.go index 3cdb540cb1e..70fd2d14835 100644 --- a/api/client/webclient.go +++ b/api/client/webclient/webclient.go @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -package client +// Package webclient provides a client for the Teleport Proxy API endpoints. +package webclient import ( "context" diff --git a/api/client/identityfile.go b/api/identityfile/identityfile.go similarity index 91% rename from api/client/identityfile.go rename to api/identityfile/identityfile.go index 4e895aedb31..edd60ce0112 100644 --- a/api/client/identityfile.go +++ b/api/identityfile/identityfile.go @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -package client +// Package identityfile implements parsing and serialization of Teleport identity files. +package identityfile import ( "bufio" @@ -27,15 +28,15 @@ import ( "os" "github.com/gravitational/teleport/api/constants" - sshutils "github.com/gravitational/teleport/api/utils/sshutils" + "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/trace" "golang.org/x/crypto/ssh" ) const ( - // IdentityFilePermissions defines file permissions for identity files. - IdentityFilePermissions = 0600 + // FilePermissions defines file permissions for identity files. + FilePermissions = 0600 ) // IdentityFile represents the basic components of an identity file. @@ -94,20 +95,20 @@ func (i *IdentityFile) SSHClientConfig() (*ssh.ClientConfig, error) { return ssh, nil } -// WriteIdentityFile writes the given identityFile to the specified path. -func WriteIdentityFile(idFile *IdentityFile, path string) error { +// Write writes the given identityFile to the specified path. +func Write(idFile *IdentityFile, path string) error { buf := new(bytes.Buffer) if err := encodeIdentityFile(buf, idFile); err != nil { return trace.Wrap(err) } - if err := ioutil.WriteFile(path, buf.Bytes(), IdentityFilePermissions); err != nil { + if err := ioutil.WriteFile(path, buf.Bytes(), FilePermissions); err != nil { return trace.ConvertSystemError(err) } return nil } -// ReadIdentityFile reads an identity file from the given path. -func ReadIdentityFile(path string) (*IdentityFile, error) { +// Read reads an identity file from the given path. +func Read(path string) (*IdentityFile, error) { r, err := os.Open(path) if err != nil { return nil, trace.Wrap(err) diff --git a/api/client/identityfile_test.go b/api/identityfile/identityfile_test.go similarity index 83% rename from api/client/identityfile_test.go rename to api/identityfile/identityfile_test.go index 30a2dcfece3..97f391fe44c 100644 --- a/api/client/identityfile_test.go +++ b/api/identityfile/identityfile_test.go @@ -15,12 +15,13 @@ limitations under the License. */ -package client +package identityfile_test import ( "path/filepath" "testing" + "github.com/gravitational/teleport/api/identityfile" "github.com/stretchr/testify/require" ) @@ -29,24 +30,24 @@ import ( func TestIdentityFileBasics(t *testing.T) { t.Parallel() path := filepath.Join(t.TempDir(), "file") - writeIDFile := &IdentityFile{ + writeIDFile := &identityfile.IdentityFile{ PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----\nkey\n-----END RSA PRIVATE KEY-----\n"), - Certs: Certs{ + Certs: identityfile.Certs{ SSH: []byte("ssh ssh-cert"), TLS: []byte("-----BEGIN CERTIFICATE-----\ntls-cert\n-----END CERTIFICATE-----\n"), }, - CACerts: CACerts{ + CACerts: identityfile.CACerts{ SSH: [][]byte{[]byte("@cert-authority ssh-cacerts")}, TLS: [][]byte{[]byte("-----BEGIN CERTIFICATE-----\ntls-cacerts\n-----END CERTIFICATE-----\n")}, }, } // Write identity file - err := WriteIdentityFile(writeIDFile, path) + err := identityfile.Write(writeIDFile, path) require.NoError(t, err) // Read identity file - readIDFile, err := ReadIdentityFile(path) + readIDFile, err := identityfile.Read(path) require.NoError(t, err) // Check that read and write values are equal diff --git a/api/client/profile.go b/api/profile/profile.go similarity index 80% rename from api/client/profile.go rename to api/profile/profile.go index 3dabda805fe..f344ba34cde 100644 --- a/api/client/profile.go +++ b/api/profile/profile.go @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -package client +// Package profile handles management of the Teleport profile directory (~/.tsh). +package profile import ( "crypto/tls" @@ -93,12 +94,12 @@ func (p *Profile) Name() string { // TLSConfig returns the profile's associated TLSConfig. func (p *Profile) TLSConfig() (*tls.Config, error) { - cert, err := tls.LoadX509KeyPair(p.tlsCertPath(), p.keyPath()) + cert, err := tls.LoadX509KeyPair(p.TLSCertPath(), p.KeyPath()) if err != nil { return nil, trace.Wrap(err) } - caCerts, err := ioutil.ReadFile(p.tlsCasPath()) + caCerts, err := ioutil.ReadFile(p.TLSCAsPath()) if err != nil { return nil, trace.Wrap(err) } @@ -116,17 +117,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 := ioutil.ReadFile(p.SSHCertPath()) if err != nil { return nil, trace.Wrap(err) } - key, err := ioutil.ReadFile(p.keyPath()) + key, err := ioutil.ReadFile(p.KeyPath()) if err != nil { return nil, trace.Wrap(err) } - caCerts, err := ioutil.ReadFile(p.sshCasPath()) + caCerts, err := ioutil.ReadFile(p.SSHCAsPath()) if err != nil { return nil, trace.Wrap(err) } @@ -216,11 +217,10 @@ func defaultProfilePath() string { return filepath.Join(home, profileDir) } -// ProfileFromDir reads the user (yaml) profile from a given directory. If -// dir is empty, this function defaults to the default tsh profile directory. -// If name is empty, this function defaults to loading the currently active -// profile (if any). -func ProfileFromDir(dir string, name string) (*Profile, error) { +// FromDir reads the user profile from a given directory. If dir is empty, +// this function defaults to the default tsh profile directory. If name is empty, +// this function defaults to loading the currently active profile (if any). +func FromDir(dir string, name string) (*Profile, error) { dir = FullProfilePath(dir) var err error if name == "" { @@ -284,34 +284,42 @@ func (p *Profile) saveToFile(filepath string) error { return nil } -func (p *Profile) keyDir() string { +// KeyDir returns the path to the profile's directory. +func (p *Profile) KeyDir() string { return filepath.Join(p.Dir, constants.SessionKeyDir) } -func (p *Profile) userKeyDir() string { - return filepath.Join(p.keyDir(), p.Name()) +// UserKeyDir returns the path to the profile's key directory. +func (p *Profile) UserKeyDir() string { + return filepath.Join(p.KeyDir(), p.Name()) } -func (p *Profile) keyPath() string { - return filepath.Join(p.userKeyDir(), p.Username) +// KeyPath returns the path to the profile's private key. +func (p *Profile) KeyPath() string { + return filepath.Join(p.UserKeyDir(), p.Username) } -func (p *Profile) tlsCertPath() string { - return filepath.Join(p.userKeyDir(), p.Username+constants.FileExtTLSCert) +// TLSCertPath returns the path to the profile's TLS certificate. +func (p *Profile) TLSCertPath() string { + return filepath.Join(p.UserKeyDir(), p.Username+constants.FileExtTLSCert) } -func (p *Profile) tlsCasPath() string { - return filepath.Join(p.userKeyDir(), constants.FileNameTLSCerts) +// TLSCAsPath returns the path to the profile's TLS certificate authorities. +func (p *Profile) TLSCAsPath() string { + return filepath.Join(p.UserKeyDir(), constants.FileNameTLSCerts) } -func (p *Profile) sshDir() string { - return filepath.Join(p.userKeyDir(), p.Username+constants.SSHDirSuffix) +// SSHDir returns the path to the profile's ssh directory. +func (p *Profile) SSHDir() string { + return filepath.Join(p.UserKeyDir(), p.Username+constants.SSHDirSuffix) } -func (p *Profile) sshCertPath() string { - return filepath.Join(p.sshDir(), p.SiteName+constants.FileExtSSHCert) +// SSHCertPath returns the path to the profile's ssh certificate. +func (p *Profile) SSHCertPath() string { + return filepath.Join(p.SSHDir(), p.SiteName+constants.FileExtSSHCert) } -func (p *Profile) sshCasPath() string { +// SSHCAsPath returns the path to the profile's ssh certificate authorities. +func (p *Profile) SSHCAsPath() string { return filepath.Join(p.Dir, constants.FileNameKnownHosts) } diff --git a/api/client/profile_test.go b/api/profile/profile_test.go similarity index 89% rename from api/client/profile_test.go rename to api/profile/profile_test.go index 7c3ace18220..affbc8d5dcc 100644 --- a/api/client/profile_test.go +++ b/api/profile/profile_test.go @@ -15,7 +15,7 @@ limitations under the License. */ -package client +package profile_test import ( "io/ioutil" @@ -23,6 +23,7 @@ import ( "path/filepath" "testing" + "github.com/gravitational/teleport/api/profile" "github.com/gravitational/trace" "github.com/stretchr/testify/require" @@ -37,7 +38,7 @@ func TestProfileBasics(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(dir) - p := &Profile{ + p := &profile.Profile{ WebProxyAddr: "proxy:3088", SSHProxyAddr: "proxy:3023", Username: "testuser", @@ -63,7 +64,7 @@ func TestProfileBasics(t *testing.T) { require.Error(t, err) // make sure current profile was not set - _, err = GetCurrentProfileName(dir) + _, err = profile.GetCurrentProfileName(dir) require.True(t, trace.IsNotFound(err)) // save again, this time also making current @@ -71,17 +72,17 @@ func TestProfileBasics(t *testing.T) { require.NoError(t, err) // verify that current profile is set and matches this profile - name, err := GetCurrentProfileName(dir) + name, err := profile.GetCurrentProfileName(dir) require.NoError(t, err) require.Equal(t, p.Name(), name) // load and verify current profile - clone, err := ProfileFromDir(dir, "") + clone, err := profile.FromDir(dir, "") require.NoError(t, err) require.Equal(t, *p, *clone) // load and verify directly - clone, err = ProfileFromDir(dir, p.Name()) + clone, err = profile.FromDir(dir, p.Name()) require.NoError(t, err) require.Equal(t, *p, *clone) } diff --git a/api/client/delegator.go b/api/utils/delegator.go similarity index 98% rename from api/client/delegator.go rename to api/utils/delegator.go index 022cefd7a79..194a6ab615c 100644 --- a/api/client/delegator.go +++ b/api/utils/delegator.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package client +package utils import "context" diff --git a/docs/config.json b/docs/config.json index 27dca8d4763..5663c180a66 100644 --- a/docs/config.json +++ b/docs/config.json @@ -171,8 +171,16 @@ "entries": [ { "title": "YAML", "slug": "/config-reference/" }, { "title": "CLI", "slug": "/cli-docs/" }, - { "title": "API", "slug": "/api-reference/" }, - { "title": "Metrics", "slug": "/metrics-logs-reference/" } + { "title": "Metrics", "slug": "/metrics-logs-reference/" }, + { + "title": "API", + "slug": "/reference/api/", + "entries": [ + {"title": "Introduction", "slug": "/reference/api/introduction/"}, + {"title": "Getting Started", "slug": "/reference/api/getting-started/"}, + {"title": "Architecture", "slug": "/reference/api/architecture/"} + ] + } ] } ], diff --git a/docs/pages/api-reference.mdx b/docs/pages/api-reference.mdx deleted file mode 100644 index 222570d2e86..00000000000 --- a/docs/pages/api-reference.mdx +++ /dev/null @@ -1,1132 +0,0 @@ ---- -title: Teleport API Reference -description: The detailed guide to Teleport API ---- - -In most cases, you can interact with Teleport using our CLI tools, [tsh](cli-docs.mdx#tsh) and [tctl](cli-docs.mdx#tctl). However, there are some scenarios where you may need to interact with Teleport programmatically. For this purpose, you can directly use the same API that `tctl` and `tsh` use. - - - We are currently working on improving our API Documentation. If you have an API suggestion, [please complete our survey](https://docs.google.com/forms/d/1HPQu5Asg3lR0cu5crnLDhlvovGpFVIIbDMRvqclPhQg/viewform). - - -## Go Examples - - - The Go examples below depend on some features and changes that are released in Teleport 5.0. - - -Below are some code examples that can be used with Teleport to perform a few key tasks. - -Before you begin: - -- Install [Go](https://golang.org/doc/install) 1.15+ and Setup Go Dev Environment -- Set up Teleport with ([Getting Started Guide](getting-started.mdx)) - -The easiest way to get started with the Teleport API is to clone the [Go Client Example](https://github.com/gravitational/teleport/tree/master/examples/go-client) in our github repo. Follow the README there to quickly authenticate the API with your Teleport Auth Server. - -Or if you prefer, follow the Authentication, Go Client, and Go Packages sections below to add the necessary files to a new directory called `/api-examples`. At the end, you should have this file structure: - -``` -api-examples -+-- api-admin.yaml -+-- certs -| +-- api-admin.cas -| +-- api-admin.crt -| +-- api-admin.key -+-- client.go -+-- go.mod -+-- go.sum -+-- main.go -``` - -## Authentication - -In order to interact with the API, you will need to provision appropriate TLS certificates. In order to provision certificates, you will need to create a user with appropriate permissions. You should only give the API user permissions for what it actually needs. - -To quickly get started with the API, you can use this api-admin user. However in production usage, make sure to have stringent permissions in place. - -```bash -# Copy and Paste the below and run on the Teleport Auth server. -$ cat > api-admin.yaml < - By default, `tctl auth sign` produces certificates with a relatively short lifetime. See our [Kubernetes Section](./kubernetes-access/guides/cicd.mdx) for more information on automating the signing process for short lived certificates. - - While we encourage you to use short lived certificates, we understand you may not have all the infrastructure to issues and obtain them at the onset. You can use the --ttl flag to extend the lifetime of a certificate in these cases but understand this reduces your security posture - - -## Go Client - -The client below interfaces with the Teleport gRPC API, and relies on the `certs` generated above for TLS. - -Add `client.go` into your `/api-examples` folder. - -**client.go** - -```go -package main - -import ( - "crypto/tls" - "crypto/x509" - "fmt" - "io/ioutil" - "os" - - "github.com/gravitational/teleport/api/client" - "github.com/gravitational/teleport/lib/auth" -) - -// connectClient establishes a gRPC connection to an auth server. -func connectClient() (*auth.Client, error) { - tlsConfig, err := LoadTLSConfig("certs/api-admin.crt", "certs/api-admin.key", "certs/api-admin.cas") - if err != nil { - return nil, fmt.Errorf("Failed to setup TLS config: %v", err) - } - - // replace 127.0.0.1:3025 (default) with your auth server address - config := client.Config{Addrs: []string{"127.0.0.1:3025"}, TLS: tlsConfig} - return auth.NewClient(config) -} - -// LoadTLSConfig loads and sets up client TLS config for authentication -func LoadTLSConfig(certPath, keyPath, rootCAsPath string) (*tls.Config, error) { - cert, err := tls.LoadX509KeyPair(certPath, keyPath) - if err != nil { - return nil, err - } - caPool, err := LoadTLSCertPool(rootCAsPath) - if err != nil { - return nil, err - } - conf := &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: caPool, - } - return conf, nil -} - -// LoadTLSCertPool is used to load root CA certs from file path. -func LoadTLSCertPool(path string) (*x509.CertPool, error) { - caFile, err := os.Open(path) - if err != nil { - return nil, err - } - caCerts, err := ioutil.ReadAll(caFile) - if err != nil { - return nil, err - } - pool := x509.NewCertPool() - if ok := pool.AppendCertsFromPEM(caCerts); !ok { - return nil, fmt.Errorf("invalid CA cert PEM") - } - return pool, nil -} -``` - -## Go Packages - -Copy the Teleport module's go.mod below into `/api-examples` and then run `go mod tidy` to slim it down to only what's needed for these api examples. - -``` -module github.com/gravitational/teleport - -go 1.15 - -require ( - cloud.google.com/go/firestore v1.1.1 - cloud.google.com/go/pubsub v1.2.0 // indirect - cloud.google.com/go/storage v1.5.0 - github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect - github.com/HdrHistogram/hdrhistogram-go v0.9.1-0.20201006155429-aada4ab574ea - github.com/Microsoft/go-winio v0.4.9 - github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect - github.com/alecthomas/colour v0.1.0 // indirect - github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c // indirect - github.com/armon/go-radix v1.0.0 - github.com/aws/aws-sdk-go v1.35.19 - github.com/beevik/etree v1.1.0 - github.com/boombuler/barcode v0.0.0-20161226211916-fe0f26ff6d26 // indirect - github.com/cjbassi/drawille-go v0.1.0 // indirect - github.com/coreos/go-oidc v0.0.3 - github.com/coreos/go-semver v0.3.0 - github.com/davecgh/go-spew v1.1.1 - github.com/docker/docker v17.12.0-ce-rc1.0.20180721085148-1ef1cc838816+incompatible - github.com/docker/spdystream v0.0.0-20170912183627-bc6354cbbc29 // indirect - github.com/dustin/go-humanize v1.0.0 - github.com/fsouza/fake-gcs-server v1.11.6 - github.com/ghodss/yaml v1.0.0 - github.com/gizak/termui v0.0.0-20190224181052-63c2a0d70943 - github.com/gogo/protobuf v1.3.1 - github.com/gokyle/hotp v0.0.0-20160218004637-c180d57d286b - github.com/golang/protobuf v1.4.2 - github.com/google/btree v1.0.0 - github.com/google/go-cmp v0.5.2 - github.com/google/gops v0.3.1 - github.com/gravitational/configure v0.0.0-20160909185025-1db4b84fe9db - github.com/gravitational/form v0.0.0-20151109031454-c4048f792f70 - github.com/gravitational/kingpin v2.1.11-0.20190130013101-742f2714c145+incompatible - github.com/gravitational/license v0.0.0-20180912170534-4f189e3bd6e3 - github.com/gravitational/oxy v0.0.0-20200916204440-3eb06d921a1d - github.com/gravitational/reporting v0.0.0-20180907002058-ac7b85c75c4c - github.com/gravitational/roundtrip v1.0.0 - github.com/gravitational/trace v1.1.6 - github.com/gravitational/ttlmap v0.0.0-20171116003245-91fd36b9004c - github.com/hashicorp/golang-lru v0.5.4 - github.com/iovisor/gobpf v0.0.1 - github.com/johannesboyne/gofakes3 v0.0.0-20191228161223-9aee1c78a252 - github.com/jonboulle/clockwork v0.2.1 - github.com/json-iterator/go v1.1.10 - github.com/julienschmidt/httprouter v1.2.0 - github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 - github.com/kr/pty v1.1.1 - github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb - github.com/mailgun/lemma v0.0.0-20160211003854-e8b0cd607f58 - github.com/mailgun/metrics v0.0.0-20150124003306-2b3c4565aafd // indirect - github.com/mailgun/minheap v0.0.0-20131208021033-7c28d80e2ada // indirect - github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 - github.com/mailgun/ttlmap v0.0.0-20150816203249-16b258d86efc - github.com/mattn/go-runewidth v0.0.4 // indirect - github.com/mattn/go-sqlite3 v1.10.0 - github.com/mdp/rsc v0.0.0-20160131164516-90f07065088d // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // indirect - github.com/pborman/uuid v1.2.0 - github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e - github.com/prometheus/client_golang v1.1.0 - github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 - github.com/prometheus/common v0.6.0 - github.com/prometheus/procfs v0.0.4 // indirect - github.com/russellhaering/gosaml2 v0.6.0 - github.com/russellhaering/goxmldsig v1.1.0 - github.com/sergi/go-diff v1.1.0 // indirect - github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect - github.com/sirupsen/logrus v1.6.0 - github.com/stretchr/testify v1.6.1 - github.com/tstranex/u2f v0.0.0-20160508205855-eb799ce68da4 - github.com/vulcand/predicate v1.1.0 - github.com/xeipuuv/gojsonpointer v0.0.0-20151027082146-e0fe6f683076 // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c // indirect - github.com/xeipuuv/gojsonschema v0.0.0-20151204154511-3988ac14d6f6 // indirect - go.etcd.io/etcd v0.5.0-alpha.5.0.20200306183522-221f0cc107cb - go.opencensus.io v0.22.4 // indirect - go.uber.org/atomic v1.4.0 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect - golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect - golang.org/x/net v0.0.0-20200707034311-ab3426394381 - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d - golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 - golang.org/x/text v0.3.3 - google.golang.org/api v0.22.0 - google.golang.org/appengine v1.6.6 // indirect - google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 - google.golang.org/grpc v1.27.0 - google.golang.org/protobuf v1.25.0 - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f - gopkg.in/square/go-jose.v2 v2.5.1 - gopkg.in/yaml.v2 v2.3.0 - gotest.tools v2.2.0+incompatible // indirect - k8s.io/api v0.0.0-20200821051526-051d027c14e1 - k8s.io/apimachinery v0.20.0-alpha.1.0.20200922235617-829ed199f4e0 - k8s.io/client-go v0.0.0-20200827131824-5d33118d4742 - launchpad.net/gocheck v0.0.0-20140225173054-000000000087 // indirect -) - -replace ( - github.com/coreos/go-oidc => github.com/gravitational/go-oidc v0.0.3 - github.com/iovisor/gobpf => github.com/gravitational/gobpf v0.0.1 - github.com/sirupsen/logrus => github.com/gravitational/logrus v0.10.1-0.20171120195323-8ab1e1b91d5f -) -``` - -## Main file - -Add this main file to your `/api-examples` folder. Now you can simply plug in the examples below and then run `go run .` to see them in action. - -**main.go** - -```go -package main - -import ( - "fmt" - "log" -) - -func main() { - log.Printf("Starting Teleport client...") - client, err := connectClient() - if err != nil { - log.Fatalf("Failed to create client: %v", err) - } -} -``` - -## Object Resource - -All Teleport resources, such as `Roles` and `Tokens`, share several fields for database management. To keep the documentation below clear, we will refer to these fields as `Resource Fields`. - -```go -// Resource is a group of fields that all Teleport resources have -type Resource struct { - // Kind is a resource kind - Kind string - // SubKind is an optional resource sub kind, used in some resources - SubKind string - // Version is version - Version string - // Metadata is User metadata - Metadata struct { - // Name is an object name - Name string - // Namespace is object namespace. - Namespace string - // Description is object description - Description string - // Labels is a set of labels - Labels map[string]string - // Expires is a global expiry time header can be set on any resource in the system. - Expires *time.Time - // ID is a record ID - ID int64 - } -} -``` - -## Roles - -Every user in Teleport is assigned a set of [roles](./access-controls/reference.mdx#roles). A user's roles defines what actions or resources the user is allowed or denied to access. We offer a wide array of permissions, allowing you to safely and precisely give developers access to the resources they need. - -Some of the permissions a role could define include: - -- Which SSH nodes a user can or cannot access. -- Ability to replay recorded sessions. -- Ability to update cluster configuration. -- Which UNIX logins a user is allowed to use when logging into servers. - - - The open source edition of Teleport automatically assigns every user to the built-in `admin` role, but Teleport Enterprise allows administrators to define their own roles with far greater control over the user permissions. - - -You can manage roles with the Teleport CLI tool [tctl](cli-docs.mdx#tctl), or programmatically with the RPC calls documented below. - -You may want to use the API to manage roles if: - -- You want to write a program that can always ensure certain roles exist on your system and do not want to orchestrate `tctl` to do this. -- You want to dynamically create short lived roles. -- You want to dynamically create roles with fields filled that Teleport currently does not support. - -### The Role Object - -A Teleport `role` is defined by its `Allow` rules, `Deny` rules, and OpenSSH `Options`. We'll break these down piece by piece below. - -To see a role example in `yaml` form, look at the the admin role in the [RBAC documentation](./access-controls/reference.mdx#roles). You'll notice that the role object below has the same exact nested structure as its `yaml` counterpart. - -```go -// RoleV3 represents role resource specification -type RoleV3 struct { - // Resource Fields are fields that all Teleport resources have - see above - Resource Fields - // Spec is the role specification - Spec RoleSpecV3 struct { - // Options is for OpenSSH options like agent forwarding. - Options RoleOptions - // Allow is the set of conditions evaluated to grant access. - Allow RoleConditions - // Deny is the set of conditions evaluated to deny access. Deny takes priority over allow. - Deny RoleConditions - } -} -``` - -**Role Options** - -The `RoleOptions` struct defines what OpenSSH actions a user is allowed to use. - -```go -// RoleOptions is a set of role options -type RoleOptions struct { - // ForwardAgent is SSH agent forwarding. default true. - ForwardAgent Bool - // MaxSessionTTL defines how long a SSH session can last for. - MaxSessionTTL Duration - // PortForwarding defines if the certificate will have "permit-port-forwarding" - // in the certificate. PortForwarding is true if not set. - PortForwarding *BoolOption // struct { Value bool } - Nullable boolean - // CertificateFormat defines the format of the user certificate to allow - // compatibility with older versions of OpenSSH. - CertificateFormat string - // ClientIdleTimeout sets disconnect clients on idle timeout behavior, - // if set to 0 means do not disconnect, otherwise is set to the idle duration. - ClientIdleTimeout Duration - // DisconnectExpiredCert sets disconnect clients on expired certificates. - DisconnectExpiredCert Bool - // BPF defines what events to record for the BPF-based session recorder. - BPF []string - // PermitX11Forwarding authorizes use of X11 forwarding. - PermitX11Forwarding Bool - // MaxConnections defines the maximum number of - // concurrent connections a user may hold. - MaxConnections int64 - // MaxSessions defines the maximum number of - // concurrent sessions per connection. - MaxSessions int64 -} -``` - -**Role Conditions** - -The `RoleConditions` struct allows for precise permission combinations between a role's `Allow` and `Deny` fields. - -`Deny` conditions are evaluated first and logically OR'ed. That means that when a user attempts an action, if any of their roles has a deny section matching that action, the user will be denied access. If the user is not denied access, then the `Allow` conditions are evaluated and logically AND'ed. So if a user has any roles with an allow section matching the action, the action will be permitted. - -However, this also allows you to take a modular approach to defining roles. Splitting roles up into logical parts will allow you to manage roles for many developers more effectively. It may also be effective to keep your deny and allow conditions in separate roles so that conflicting roles are obvious. - -```go -// RoleConditions is a set of conditions that must all match to be allowed or denied access. -type RoleConditions struct { - // Logins is a list of *nix system logins, e.g. "root". - Logins []string - // Namespaces is a list of namespaces (used to partition a cluster). - Namespaces []string - // NodeLabels is a map of node labels (used to dynamically grant access to nodes). - NodeLabels Labels - // Rules is a list of rules and their access levels. Rules represents allow or deny rule - // that is executed to check if user or service have access to resource - Rules []Rule - // KubeGroups is a list of kubernetes groups that Teleport users with this role will be - KubeGroups []string - // A list of roles that this role can request access to - Request *AccessRequestConditions // type AccessRequestConditions struct { Roles []string } - // KubeUsers is an optional list of kubernetes users that Teleport users with this role will be - KubeUsers []string - // AppLabels is a map of labels used as part of the RBAC system. - AppLabels Labels - // ClusterLabels is a map of node labels (used to dynamically grant access to clusters). - ClusterLabels Labels -} -``` - -**Labels** - -```go -type Label map[string]utils.Strings -``` - -Labels are arbitrary key-value pairs that can be used to differentiate nodes, apps, or leaf clusters by key attributes. For example, `NodeLabels` might have the key `environment`, with its value set to `development`, `staging`, or `production`, according to the node's location. - -```go -services.Labels{"environment": utils.Strings{"development", "staging"}} -``` - -Depending on which field you put these labels in, you can allow/deny access to any nodes, apps, or leaf clusters with the given labels. These labels can be very useful in systems where you need to carefully manage access across many clusters, e.g. if you are managing clusters for several outside groups. - -**Rules** - -The primary building blocks of a rule are its resources and verbs. The optional `Where` and `Actions` fields can be used for more advanced rules. - -```go -type Rule struct { - // Resources is a list of resources - Resources []string - // Verbs is a list of verbs - Verbs []string - // Where specifies optional advanced matcher - Where string - // Actions specifies optional actions taken when this rule matches - Actions []string -} - -``` - -Here's an example of a rule describing `read only` verbs applied to the SSH `session` resource. Depending on if it's under `Allow` or `Deny`, it means "allow/deny users of this role the ability to read or list active SSH sessions". - -```go -services.NewRule( - services.KindSession, - services.RO(), // helper function to get 'read only' verbs ("list" and "read") -) -``` - -**Resources** - -Resources include values like the ones below, and much more. The rest of the Teleport resources can be found in the `services` package. - -```go -KindRole = "role" -KindAccessRequest = "access_request" -KindToken = "token" -KindCertAuthority = "cert_authority" -``` - -**Verbs** - -These are all of the possible resource values, which can be found in the `services` package. - -```go -VerbList = "list" -VerbCreate = "create" -VerbRead = "read" -// readnosecrets prevents secrets on some resources from being read. -// For example, retrieving the Certificate Authority will return it without its private keys. -VerbReadNoSecrets = "readnosecrets" -VerbUpdate = "update" -VerbDelete = "delete" -VerbRotate = "rotate" -``` - -There are also helper functions `RW()`, `RO()`, and `ReadNoSecrets()` in the `services` package to quickly get read/write verbs, read only verbs, and read only verbs with `readnosecrets` respectively. - -### Retrieve Role - -This is the equivalent of `tctl get role/admin`. - -```go -role, err := client.GetRole("admin") -if err != nil { - return err -} -``` - -### Create Role - -You can use the `UpsertRole` RPC to programmatically create a new role. This is the equivalent of `tctl create -f auditor-role.yaml`, where the `-f` flag signals to overwrite the auditor role if it exists already. - -Suppose you wanted to create a role for an auditor that could view all sessions, but could not access any servers. Using `tctl`, you would first create a role that allows reading and listing of the session resource like below. In addition, the user is explicitly denied access to all nodes in the deny block. - -``` -$ cat << EOF > /tmp/auditor-role.yaml -kind: role -version: v3 -metadata: - name: auditor -spec: - options: - max_session_ttl: 8h - allow: - rules: - - resources: [session] - verbs: [list, read] - deny: {} - node_labels: '*': '*' -EOF -$ tctl create -f /tmp/auditor-role.yaml -``` - -To do something similar with the API: - -```go -role, err := services.NewRole("auditor", services.RoleSpecV3{ - Options: services.RoleOptions{ - MaxSessionTTL: services.Duration(time.Hour), - }, - Allow: services.RoleConditions{ - Logins: []string{"auditor"}, - Rules: []services.Rule{ - services.NewRule(services.KindSession, services.RO()), - }, - }, - Deny: services.RoleConditions{ - NodeLabels: services.Labels{"*": []string{"*"}}, - }, -}) -if err != nil { - return err -} -if err = client.UpsertRole(ctx, role); err != nil { - return err -} -``` - -### Update Role - -The `UpsertRole` RPC can also be used to update an existing role. You can change a role's field with the setter functions available, or directly. - -```go -// retrieve role -role, err := client.GetRole("auditor") -if err != nil { - return err -} - -// update the auditor role to be expired -role.SetExpiry(time.Now()) -if err := client.UpsertRole(ctx, role); err != nil { - return err -} -``` - -### Delete Role - -This is the equivalent of `tctl rm auditor-role.yaml`. - -```go -if err := client.DeleteRole(ctx, "auditor"); err != nil { - return err -} -``` - -## Tokens - -Teleport is a "clustered" system, meaning it only allows access to hosts that had been previously granted cluster membership. To achieve this, a cluster has "join tokens" which can be shared to extend trust. - -A remote host can exchange one of these tokens with the cluster's auth server to receive signed certificates and become a trusted Teleport host (auth, node, proxy, app, or kubernetes server). Likewise, a remote Teleport cluster can exchange a token to become a leaf cluster in a [trusted cluster](admin-guide.mdx#trusted-clusters). - -These tokens can be predefined static tokens, or dynamic tokens with a short life time. The latter can be generated by `tctl` or this API, and is more secure. - -You may want to use this API to manage tokens if: - -- You have a program that dynamically adds new hosts to clusters -- You want to programmatically add leaf clusters to a trusted cluster - -### The Token Object - -The Token has a Roles field, which defines what roles this token provides in the root cluster. - -```go -type ProvisionTokenV2 struct { - // Resource Fields are fields that all Teleport resources have - see above - Resource Fields - // Spec is the token specification - Spec ProvisionTokenSpecV2 struct { - // Roles is a list of roles associated with the token - Roles []teleport.Role // teleport.Role is a custom string type - } -} -``` - -**Roles** - -Not to be confused with [RBAC Roles](api-reference.mdx#roles), the `Roles` field on a Token determines what server roles a new host can take on in a cluster. - -These are all of the possible role values, which can be found in the `teleport` package. - -```go -RoleAuth Role = "Auth" -RoleWeb Role = "Web" -RoleNode Role = "Node" -RoleProxy Role = "Proxy" -RoleAdmin Role = "Admin" -RoleProvisionToken Role = "ProvisionToken" -RoleTrustedCluster Role = "Trusted_cluster" -RoleSignup Role = "Signup" -RoleNop Role = "Nop" -RoleRemoteProxy Role = "RemoteProxy" -RoleKube Role = "Kube" -RoleApp Role = "App" -``` - -### Retrieve Token - -The closest equivalent to this is `tctl tokens ls`. - -```go -token, err := client.GetToken(tokenString) -if err != nil { - return err -} -``` - -### Create Token - -You can use the `GenerateToken` RPC to programmatically create a new token. This is the equivalent of `tctl tokens add --type=[Roles] --value=[Token] --ttl=[TTL]`. - -By default, Teleport will create a random 16 byte string using the `CryptoRandomHex` function in our `utils` package. If you want to customize this yourself, simply provide the `Token` field, though we strongly recommend utilizing best security practices. - -You can also set `TTL` to a maximum of 48 hours. The shorter the lifetime, the more secure your cluster will be. - -```go -// generate a token for adding a new proxy host to a cluster -tokenString, err := client.GenerateToken(ctx, auth.GenerateTokenRequest{ - Roles: teleport.Roles{teleport.RoleProxy}, - // Token will be a randomly generated 16 byte hex string - // TTL will default to 30 minutes -}) -if err != nil { - return err -} - -// generate a token for adding a remote cluster to a trusted cluster -tokenString, err := client.GenerateToken(ctx, auth.GenerateTokenRequest{ - Token: "this-is-a-secure-token-string", - Roles: teleport.Roles{teleport.RoleTrustedCluster}, - TTL: time.Minute, -}) -if err != nil { - return err -} -``` - -### Update Token - -`UpsertToken` is essentially the same as `GenerateToken` but without default values, so it is best used only for updating existing tokens. - -```go -token, err := client.GetToken(tokenString) -if err != nil { - return err -} - -// update the token to be expired -token.SetExpiry(time.Now()) -if err := client.UpsertToken(token); err != nil { - return err -} -``` - -### Delete Token - -This is equivalent to `tctl tokens rm [tokenString]`. - -```go -if err := client.DeleteToken(tokenString); err != nil { - return err -} -``` - -## Cluster Labels - -Cluster Labels can be used to differentiate between leaf clusters in a [Trusted Cluster](trustedclusters.mdx). These can be useful when defining [roles](./access-controls/reference.mdx#roles) within a trusted cluster, where each cluster has its own requirements for access. - -For example, if each cluster corresponds to a different product that should only be accessed by its product team, you can give each cluster a label like `product: A`. Then create a role for each product which allows access to clusters with its respective product label. - -You may want to use the following RPCs to manage your cluster labels if: - -- You want to programmatically manage cluster labels from your root cluster -- You have a complex cluster labeling system that would benefit from automation with the API -- You have a large distributed trusted cluster where explicit cluster based access control is crucial - -### Create a Leaf Cluster Join Token with Labels - -To create a leaf cluster with cluster labels, you can create a token with the desired labels, and use that token to add the leaf cluster. Check the [Tokens](api-reference.mdx#tokens) section of this page for more information on tokens. - -```go -tokenString, err := client.GenerateToken(ctx, auth.GenerateTokenRequest{ - Roles: teleport.Roles{teleport.RoleTrustedCluster}, - // Leaf clusters added with this token will inherit these labels - Labels: map[string]string{ - "env": "staging", - }, -}) -``` - -This is the equivalent of `tctl tokens add --type=trusted_cluster --labels=env=staging`. - - - Currently, it is not straightforward to add new leaf clusters with the API, but it is possible with the `RegisterUsingToken` RPC. Until we properly document this, follow the trusted cluster [join token docs](trustedclusters.mdx#join-tokens) to create a leaf cluster with this token using `tctl`. - - -### Update a Leaf Cluster's labels - -You can also update an existing leaf cluster's labels from the root cluster using the `UpdateRemoteCluster` RPC. - -This is the equivalent of `tctl update rc/[leafClusterName] --set-labels=env=prod`. - -```go -rc, err := client.GetRemoteCluster("leafClusterName") -if err != nil { - return err -} - -md := rc.GetMetadata() -md.Labels = map[string]string{"env": "prod"} -rc.SetMetadata(md) - -if err = client.UpdateRemoteCluster(ctx, rc); err != nil { - return err -} - -``` - -## Access Workflows - -[Access Workflows](enterprise/workflow/index.mdx) can be used by Teleport users to request one or more additional roles on the fly. These requests can be partially or fully approved or denied by a Teleport Administrator. - -You may want to use manage Access Workflows using the API if: - -- You want to automatically administer the scaling up and down of permissions for developers depending on their task -- You want to utilize our supported [external tools](enterprise/workflow/index.mdx#integrating-with-an-external-tool) or other third party tools to control the flow of access - -For example, you could have a team of contractors which need database access for some tasks, but should not have it permanently. To do this, you can give them the `contractor` role below, which allows them to request the `dba` role. - -```yaml -kind: role -metadata: - name: contractor -spec: - options: - # ... - allow: - request: - roles: ['dba'] - # ... - deny: - # ... -``` - -```yaml -kind: role -metadata: - name: dba -spec: - options: - # ... - # Only allows the contractor to use this role for 1 hour from time of request. - max_session_ttl: 1h - allow: - # ... - deny: - # ... -``` - -Now if a contractor has a task requiring `dba` access, they can request `dba` access. To approve the request, you need an administrator with read and write permissions to access requests. - -```yaml -kind: role -metadata: - name: request-admin -spec: - options: - # ... - allow: - rules: - - resources: [access_request] - verbs: [list, read, update, delete] - deny: - # ... -``` - -A `request-admin` can list all current requests, resolve them, and delete them. Notice that `request-admin` might not be a great job to handle manually. - -With the API, you can automatically manage the requesting and or resolution of requests in order to streamline this process. Better yet, this opens up the ability to leverage external identity providers by attaching relevant information as `Annotations` to requests, such as `ticket_id` or `task_id`. You can also use our custom [integrations with external tools](enterprise/workflow/index.mdx#integrating-with-an-external-tool), such as Slack, to manage requests according to your custom configuration. - -### The Access Request Object - -An `AccessRequest` is made by a `User` for a set of `Roles`. Once its `State` is resolved to "approved", this user has access to the permissions in those roles until the `Expiry` time, which can be set upon resolution. - -There are also optional `Reasons` and `Annotations` which can be used for audit logs, to integrate external identity information into requests, and other custom usages you may have. - -```go -// AccessRequest represents an access request resource specification -type AccessRequestV3 struct { - // Resource Fields are fields that all Teleport resources have - see above - Resource Fields - // Spec is an AccessRequest specification - Spec AccessRequestSpecV3 struct { - // User is the name of the user to whom the roles will be applied. - User string - // Roles is a list of the roles being requested. - Roles []string - // State is the current state of this access request. Possible values are pending, approved, and denied. - State RequestState - // Created encodes the time at which the request was registered with the auth server. - Created time.Time - // Expires constrains the maximum lifetime of any login session for which this request is active. - Expires time.Time - // RequestReason is an optional message explaining the reason for the request. - RequestReason string - // ResolveReason is an optional message explaining the reason for the resolution - // of the request (approval, denial, etc...). - ResolveReason string - // ResolveAnnotations is a set of arbitrary values received from plugins or other resolving parties during approval/denial. Importantly, these annotations are included in the access_request.update event, allowing plugins to propagate arbitrary structured data to the audit log. - ResolveAnnotations wrappers.Traits - // SystemAnnotations is a set of programmatically generated annotations attached to pending access requests by teleport. These annotations serve as a mechanism for administrators to pass extra information to plugins when they process pending access requests. - SystemAnnotations wrappers.Traits - } -} -``` - -**State** - -These are the RequestState constants, which can be found in the services package. - -```go -// NONE variant exists to allow RequestState to be explicitly omitted -// in certain circumstances (e.g. in an AccessRequestFilter). -RequestState_NONE RequestState = 0 -// PENDING variant is the default for newly created requests. -RequestState_PENDING RequestState = 1 -// APPROVED variant indicates that a request has been accepted by -// an administrating party. -RequestState_APPROVED RequestState = 2 -// DENIED variant indicates that a request has been rejected by -// an administrating party. -RequestState_DENIED RequestState = 3 -``` - -### Retrieve Access Requests - -The closest equivalent to this is `tctl request ls`, which does not have the filter functionality. - -```go -// retrieve all pending access requests -filter := services.AccessRequestFilter{State: services.RequestState_PENDING} -ars, err := client.GetAccessRequests(ctx, filter) -if err != nil { - return err -} -``` - -**AccessRequestFilter** - -The `AccessRequestFilter` struct allows you to filter by `ID`, `User`, and `State`. - -```go -type AccessRequestFilter struct { - // ID specifies a request ID if set. - ID string - // User specifies a username if set. - User string - // RequestState filters for requests in a specific state. - State RequestState -} -``` - -### Create Access Request - -This is equivalent to `tctl request create contractor --roles=dba --reason="I need more power"`. - -However with the RPC below, you can also set other useful fields. For example, `SystemAnnotations` can be used to store relevant information for external tools, such as a ticket id from a ticket management system. - -```go -// create a new access request for contractor to temporarily use the dba role in the cluster -ar, err := services.NewAccessRequest("contractor", "dba") -if err != nil { - return err -} - -// use AccessRequest setters to set optional fields -accessReq.SetRequestReason("I need more power.") -accessReq.SetAccessExpiry(time.Now().Add(time.Hour)) -accessReq.SetSystemAnnotations(map[string][]string{ - "ticket": []string{"137"}, -}) - -if err = client.CreateAccessRequest(ctx, accessReq); err != nil { - return err -} -``` - -### Approve Access Request - -This is equivalent to `tctl request approve [accessReqID] --roles=dba1 --reason="dba2 is not for you"`. - -You can approve a subset of the roles in the request with the `Roles` field. - -```go -aruApprove := services.AccessRequestUpdate{ - RequestID: accessReqID, - State: services.RequestState_APPROVED, - Reason: "dba2 is not for you", - Roles: []string{"dba1"}, -} -if err := client.SetAccessRequestState(ctx, aruApprove); err != nil { - return err -} -``` - -### Deny Access Request - -This is equivalent to `tctl request deny [accessReqID] --reason="Not today"`. - -```go -aruDeny := services.AccessRequestUpdate{ - RequestID: accessReqID, - State: services.RequestState_DENIED, - Reason: "Not today", -} -if err := client.SetAccessRequestState(ctx, aruDeny); err != nil { - return err -} -``` - -### Delete Access Request - -This is equivalent to `tctl request rm [accessReqID]`. - -```go -if err := client.DeleteAccessRequest(ctx, accessReqID); err != nil { - return err -} -``` - -## Certificate Authority - -Teleport uses SSH Certificates to securely connect servers. To achieve this, the Auth server of a Teleport cluster acts as the [Certificate Authority](architecture/authentication.mdx#ssh-certificates) (CA), which signs SSH certificates for users and hosts in the cluster. The auth server uses separate CAs for users and hosts. - -You may want to use this API to manage your CA if: - -- You need to access CA information from within the API for some use case -- You cannot [use tctl to rotate certificates](admin-guide.mdx#certificate-rotation) for some reason -- You want to set up a custom auto schedule for rotating certificates for more security and stability -- You want to implement a robust manual rotation solution, which automatically triggers each rotation phase according to your specification, such as by catching an event or webhook - -### Certificate Rotation - -To maintain security across your cluster, it is a good idea to set up automatic [certificate rotation](architecture/authentication.mdx#certificate-rotation). - -You can use Teleport's `auto` rotation mode to rotate the CA with a default or custom schedule, or you can use `manual` mode to create a custom automated solution that manually triggers each phase. - -A carefully implemented `manual` mode solution has the potential to be more fault tolerant and faster, due to the arbitrary nature of rotation schedules and grace periods (explained below). - -**Rotation Phases** - -A certificate rotation occurs in a series of phases, either triggered automatically or manually. - -1. `Standby`: No rotation operations underway. This is the beginning and end state of every CA rotation. If a CA's rotation is not in the standby state, a new rotation cannot begin. -2. `Init`: New Certificate Authority is issued, but it remains unused while users and servers get updated certificates. -3. `Update Clients`: Client credentials will have to be updated and reloaded, but servers will still use and respond with old credentials (grace period). -4. `Update Servers`: Servers will have to reload and should start serving TLS and SSH certificates signed by new CA (retrieved in previous phase). -5. `Rollback`: Rollback moves back both clients and servers to use the old credentials, but will continue to trust new credentials as well. Must be triggered Manually. - -The phases must occur in the order `Init -> Update Clients -> Update Servers` with the beginning and ending resting state being `Standby`. `Rollback` can occur after any phase. - -**Automated Rotation** - -You can use `tctl auth rotate --type=user --grace-period=10h` or the following RPC to start the rotation in `auto` mode. - -```go -// This will start an automatic rotation that schedules each phase of the rotation in equal increments (each 1/3 of the grace period). -gracePeriod := time.Hour * 24 -req := auth.RotateRequest{ - Mode: services.RotationModeAuto, - GracePeriod: &gracePeriod, // defaults to 48 hours - Type: services.UserCA, // Leave empty to target UserCA and HostCA -} -if err := client.RotateCertAuthority(req); err != nil { - return err -} -``` - -The grace period should be set to 2-3 times the expected time to rotate the CA to ensure completion, while minimizing the grace period. The `type` flag is useful if you want to rotate both user and host CAs with different strategies or frequencies. - -**Automated Rotation with a Custom Schedule** - -You can also rotate certificates with a custom schedule. Using a custom certificate rotation schedule will allow you to target specific phase(s) and extend their length without having to use an unnecessarily long grace period, since long grace periods present a possible vulnerability. - -```go -// This will automate your CA rotation with the custom schedule. Use with caution, each phase should have extra time to ensure they complete in less than optimal situations. -if err := client.RotateCertAuthority(auth.RotateRequest{ - Mode: services.RotationModeAuto, - Schedule: &services.RotationSchedule{ - // 1 hour for Init - UpdateClients: time.Now().UTC().Add(time.Hour), - // 4 hours for UpdateClients - UpdateServers: time.Now().UTC().Add(time.Hour * 5), - // 2 hours for UpdateServers - Standby: time.Now().UTC().Add(time.Hour * 7), - }, -}); err := client.RotateCertAuthority(req); err != nil { - return err -} -``` - -**Manual Rotation** (custom automated solution) - -You can set up a custom system for rotation by triggering each phase manually with `manual` mode. This can be done with `tctl auth rotate --phase=update_clients` or the following RPC. - -```go -// You can run this RPC to start the Update Servers phase -if err := client.RotateCertAuthority(auth.RotateRequest{ - Mode: services.RotationModeManual, - TargetPhase: services.RotationPhaseUpdateServers, -}); err != nil { - return err -} -``` - -This can be used to make rotations independent of an arbitrary rotation schedule or grace period. For example, you might be able to set up a custom automated solution where each phase is triggered by the event of the prior phase completing. This is possible in theory, though it may be complicated to set up. - -However there are multiple upsides if you make it work: - -- it would be quicker than `auto` mode since it wouldn't wait for a phase that is already complete -- it would be more error proof in cases where the servers miss their chance to update credentials, whether due to time constraints or from going offline during the rotation -- you would not need to worry about scaling the grace period with the size of your clusters -- you could catch rotation errors and automatically trigger the `Rollback` phase to try again (though this shouldn't be necessary outside of specific scenarios, such as servers going offline for long periods of time) - - - See our [TestRotateSuccess](https://github.com/gravitational/teleport/blob/645ac573c59240974a1306d28d79d1df3b2d9845/integration/integration_test.go#L3243) integration test to see how you might get started with implementing a manual rotation solution. - - -### Retrieve Certificate Authority - -If you need to access specific information on your CA in a program, you can use the following RPC to retrieve the CA and view its keys, certificates, and more. This can also be done using `tctl auth export`. - -```go -// retrieve the cluster's Certificate Authority for Hosts -ca, err := client.GetCertAuthority( - services.CertAuthID{ - DomainName: clusterName, - Type: services.HostCA, - }, - false, -) -if err != nil { - return err -} - -// use the CA getter methods to retrieve info about the CA -// For example, you can use GetTLSKeyPairs to get and decode the CA's certificates for use in your program -for _, k := range ca.GetTLSKeyPairs() { - block, _ := pem.Decode(k.Cert) - if block == nil { - return fmt.Errorf("error decoding pem block") - } - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return err - } - - // use cert -} -``` diff --git a/docs/pages/application-access/guides/api-access.mdx b/docs/pages/application-access/guides/api-access.mdx index 613911d5f7e..c99373cf653 100644 --- a/docs/pages/application-access/guides/api-access.mdx +++ b/docs/pages/application-access/guides/api-access.mdx @@ -7,7 +7,7 @@ description: How to access RESTful APIs using Teleport Application Access Application Access can be used to access applications' RESTful APIs with tools like [curl](https://man7.org/linux/man-pages/man1/curl.1.html) or -[Postman](https://www.postman.com/). +Postman. ## Prerequisites diff --git a/docs/pages/kubernetes-access/guides/cicd.mdx b/docs/pages/kubernetes-access/guides/cicd.mdx index 2e8329d34b2..b17f4b3e31a 100644 --- a/docs/pages/kubernetes-access/guides/cicd.mdx +++ b/docs/pages/kubernetes-access/guides/cicd.mdx @@ -62,7 +62,7 @@ $ kubectl --kubeconfig /path/to/kubeconfig get pods > Short lived certificates expire in hours or minutes. You don't have to revoke them if the host gets compromised. - Generate a new kubeconfig every hour using `tctl` or [API](../../api-reference.mdx) + Generate a new kubeconfig every hour using `tctl` or [API](../../reference/api/introduction.mdx) and publish it to secret storage, like [AWS](https://aws.amazon.com/secrets-manager/) or [GCP](https://cloud.google.com/secret-manager) secret managers. diff --git a/docs/pages/reference/api.mdx b/docs/pages/reference/api.mdx new file mode 100644 index 00000000000..8aec9c32e5c --- /dev/null +++ b/docs/pages/reference/api.mdx @@ -0,0 +1,15 @@ +--- +title: Teleport API Reference +description: Reference for the Teleport API +--- + +# Reference + +- [Introduction](./api/introduction.mdx) +- [Getting Started](./api/getting-started.mdx) +- [Architecture](./api/architecture.mdx) +- [pkg.go.dev](https://pkg.go.dev/github.com/gravitational/teleport/api/client) + - [Using the client](https://pkg.go.dev/github.com/gravitational/teleport/api/client#Client) + - [Working with credentials](https://pkg.go.dev/github.com/gravitational/teleport/api/client#Credentials) + + diff --git a/docs/pages/reference/api/architecture.mdx b/docs/pages/reference/api/architecture.mdx new file mode 100644 index 00000000000..712f3a152c4 --- /dev/null +++ b/docs/pages/reference/api/architecture.mdx @@ -0,0 +1,96 @@ +--- +title: API Architecture +description: Architecture of the Teleport API +--- + +## Authentication + +The Auth API uses mTLS to authenticate a client-server connection. Therefore, the client must provide +TLS certificates signed by the Auth server to access the API. These are easy to create and provide using +[Credential loaders](#credentials). + +## Authorization + +The client certificates signed by the Auth server will be associated with a specific user. This user will +be used to authorize API requests made by the client. + +It is recommended to make a new user and role for each client. This makes it easier to track client actions, +as well as carefully control client permissions. + +For example, if your client needs to use `client.GetRole()`, the user must have permission to perform the `read` +action on the `role` resource. You should create a user and role with the minimum permissions required. + +```bash +# Copy and Paste the below and run on the Teleport Auth server. +cat > api-role.yaml < + While all Credential loaders support mTLS connections, only some support SSH connections (see the chart above). + + +Take a look at this client constructor [example](https://pkg.go.dev/github.com/gravitational/teleport/api/client#example-New) +to see what these connection options look like. \ No newline at end of file diff --git a/docs/pages/reference/api/getting-started.mdx b/docs/pages/reference/api/getting-started.mdx new file mode 100644 index 00000000000..db25d5a7c81 --- /dev/null +++ b/docs/pages/reference/api/getting-started.mdx @@ -0,0 +1,99 @@ +--- +title: API Getting Started Guide +description: Getting started with the Teleport API +--- + +# Getting Started + +In this getting started guide we will use the Teleport API Go Client to connect to the Teleport Auth server. + +Here are the steps we'll walk through: + 1. Create an API user + 2. Generate credentials + 3. Create a Go program to demo the client + +## Prerequisites + +- Install [Go](https://golang.org/doc/install) (=teleport.golang=)+ and Setup Go Dev Environment +- Set up Teleport with the [Getting Started Guide](../../getting-started.mdx) + +## 1/3 Create a User + +Create a new user for the client to impersonate. + +```bash +# Run this directly on your auth server +# Add user and login via web proxy +tctl users add api-user --roles=admin +``` + + + It is generally best practice to create custom roles for each client user. See [API authorization](./architecture.mdx#authorization). + + +## 2/3 Generate Client Credentials + +Login as the newly created user with `tsh`. + +```bash +# generate tsh profile +tsh login --user=api-user +``` + +The [profile Credentials loader](https://pkg.go.dev/github.com/gravitational/teleport/api/client#LoadProfile) +will automatically retrieve Credentials from the current profile in the next step. + +## Step 3/3 Create a go project + +Set up a new [Go module](https://golang.org/doc/tutorial/create-module) and import the `client` package: + +```bash +mkdir client-demo && cd client-demo +go mod init client-demo +go get github.com/gravitational/teleport/api/client +``` + +Add the following code to a new `main.go` file. + +```go +package main + +import ( + "context" + "log" + + "github.com/gravitational/teleport/api/client" +) + +func main() { + ctx := context.Background() + + clt, err := client.New(ctx, client.Config{ + Credentials: []client.Credentials{ + client.LoadProfile("", ""), + }, + }) + if err != nil { + log.Fatalf("failed to create client: %v", err) + } + defer clt.Close() + + resp, err := clt.Ping(ctx) + if err != nil { + log.Fatalf("failed to ping server: %v", err) + } + log.Printf("server version: %s", resp.ServerVersion) +} +``` + +Now you can run the program which will connect the client to the Teleport Auth server and fetch the server version. + +```bash +go run main.go +``` + +## Next Steps + +- Read about the [API architecture](./architecture.mdx) for a more in depth look at the API client. +- Visit the `client` package's [pkg.go.dev](https://pkg.go.dev/github.com/gravitational/teleport/api/client) for easy to navigate code oriented documentation. +- Familiarize yourself with the [admin manual](../../admin-guide.mdx) to make the best use of the API. \ No newline at end of file diff --git a/docs/pages/reference/api/introduction.mdx b/docs/pages/reference/api/introduction.mdx new file mode 100644 index 00000000000..e2d9d5ef3f5 --- /dev/null +++ b/docs/pages/reference/api/introduction.mdx @@ -0,0 +1,35 @@ +--- +title: Teleport API Introduction +description: Introduction for the Teleport API +--- + +The Teleport Auth API provides a gRPC API for remotely interacting with a Teleport Auth server. + +Teleport has a public [Go client](https://pkg.go.dev/github.com/gravitational/teleport/api/client) +to programatically interact with the API. [tsh and tctl](../../cli-docs.mdx/) use the same API. + +## Go Client + +Here is what you can do with the Go Client: + - Integrating with external tools, which we have already done + for [several tools](../../enterprise/workflow/index.mdx#integrating-with-an-external-tool), + such as Slack, Jira, and Mattermost. + - Writing a program/bot to manage access requests automatically, based on your use case. One idea + is to allow/deny developer requests based on their currently assigned tasks. + - Performing CRUD actions on resources, such as `roles`, `auth connectors`, and `provisioning tokens`. + - Dynamically configuring Teleport. + + + + Not all endpoints are supported by the new public API client yet. See this + [github issue](https://github.com/gravitational/teleport/issues/6394). + + + + We are currently working on improving our API and its documentation. If you have an API suggestion, + [please complete our survey](https://docs.google.com/forms/d/1HPQu5Asg3lR0cu5crnLDhlvovGpFVIIbDMRvqclPhQg/viewform). + + +## Get Started + +Create an API client in 3 minutes with the [Getting Started](./getting-started.mdx) Guide. \ No newline at end of file diff --git a/examples/go-client/.gitignore b/examples/go-client/.gitignore deleted file mode 100644 index 1503cc8a556..00000000000 --- a/examples/go-client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -certs \ No newline at end of file diff --git a/examples/go-client/README.md b/examples/go-client/README.md index cc403606021..4607bacfbc0 100644 --- a/examples/go-client/README.md +++ b/examples/go-client/README.md @@ -1,27 +1,10 @@ ## Teleport Auth Go Client -### Introduction - This program demonstrates how to... 1. Authenticate the client using credential loaders. 2. Authorize API calls using an independent user and role. -3. Create a new client and make API calls. - -### Authentication - -Auth API clients must perform two-way authentication using x509 certificates to: - -1. Validate the auth server x509 certificate to make sure the API endpoint can be trusted. -2. Offer their x509 certificate, which has been previously issued by the auth sever. - -Credential loaders from the `github.com/gravitataional/teleport/api/client` package can be used to find and load certificates generated by `tctl` and `tsh` commands. - -### Authorization - -The server will authorize requests for the user associated with the certificates used to authenticate the client. Therefore, to use the API client, you need to create a user and any roles it may need for your use case. - -The client will act on behalf of that user and have access as defined by the user's role(s). It is recommended to create an independent user and role to manage API access control. +3. Create a new client and make API calls to the Auth server. ### Demo @@ -38,14 +21,14 @@ $ tctl users add access-admin --roles=access-admin ##### Generate Credentials -This demo uses the Profile credential loader. Login with `tsh` and the Client will use the credentials from the profile directory (`~/.tsh`). +Login with `tsh` to generate Profile credentials. ```bash # login and automatically generate keys $ tsh login --user=access-admin ``` -NOTE: You can pass the `InsecureAddressDiscovery` in `client.Config` field to skip verification of the TLS certificate of the proxy. This is not recommended for production clients. +NOTE: You can pass the `InsecureAddressDiscovery` in `client.Config` field to skip verification of the TLS certificate of the proxy. Don't do this for production clients. ##### Run @@ -53,4 +36,13 @@ NOTE: You can pass the `InsecureAddressDiscovery` in `client.Config` field to sk $ go run main.go ``` -To see more information on the Go Client and how to use it, visit our [API Documentation](https://goteleport.com/teleport/docs/api-reference/). +### Reference + +To see more information on the Go Client and how to use it, visit our API documentation: + +- [Introduction](https:/goteleport.com/docs/reference/api/introduction) +- [Getting Started](https:/goteleport.com/docs/reference/api/getting-started) +- [Architecture](https:/goteleport.com/docs/reference/api/architecture) +- [pkg.go.dev](https://pkg.go.dev/github.com/gravitational/teleport/api/client) + - [Client type](https://pkg.go.dev/github.com/gravitational/teleport/api/client#Client) + - [Credentials type](https://pkg.go.dev/github.com/gravitational/teleport/api/client#Credentials) \ No newline at end of file diff --git a/integration/integration_test.go b/integration/integration_test.go index a15bdee00d6..17126177989 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -44,7 +44,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" - apiclient "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/profile" "github.com/gravitational/teleport/lib" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/testauthority" @@ -141,7 +141,7 @@ func (s *IntSuite) TearDownSuite(c *check.C) { } func (s *IntSuite) SetUpTest(c *check.C) { - os.RemoveAll(apiclient.FullProfilePath("")) + os.RemoveAll(profile.FullProfilePath("")) } // setUpTest configures the specific test identified with the given c. diff --git a/lib/auth/auth.go b/lib/auth/auth.go index f2c65af5c40..1fbe0f8219c 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -38,11 +38,11 @@ import ( "time" "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/wrappers" + apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/auth/u2f" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/defaults" @@ -1853,7 +1853,7 @@ func (a *Server) SetAccessRequestState(ctx context.Context, params services.Acce Roles: params.Roles, } - if delegator := client.GetDelegator(ctx); delegator != "" { + if delegator := apiutils.GetDelegator(ctx); delegator != "" { event.Delegator = delegator } diff --git a/lib/auth/clt.go b/lib/auth/clt.go index 182649307f2..6ea0bb78a46 100644 --- a/lib/auth/clt.go +++ b/lib/auth/clt.go @@ -132,7 +132,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.NewDialer(cfg.KeepAlivePeriod, cfg.DialTimeout) + contextDialer := client.NewDirectDialer(cfg.KeepAlivePeriod, cfg.DialTimeout) dialer = 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/auth/grpcserver.go b/lib/auth/grpcserver.go index 8ac67b77667..f46e83bb024 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -24,7 +24,6 @@ import ( "time" "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" @@ -282,7 +281,7 @@ func (g *GRPCServer) WatchEvents(watch *proto.Watch, stream proto.AuthService_Wa case <-watcher.Done(): return trail.ToGRPC(watcher.Error()) case event := <-watcher.Events(): - out, err := client.EventToGRPC(event) + out, err := eventToGRPC(event) if err != nil { return trail.ToGRPC(err) } @@ -293,6 +292,115 @@ func (g *GRPCServer) WatchEvents(watch *proto.Watch, stream proto.AuthService_Wa } } +// eventToGRPC converts a types.Event to an proto.Event +func eventToGRPC(in types.Event) (*proto.Event, error) { + eventType, err := eventTypeToGRPC(in.Type) + if err != nil { + return nil, trace.Wrap(err) + } + out := proto.Event{ + Type: eventType, + } + if in.Type == types.OpInit { + return &out, nil + } + switch r := in.Resource.(type) { + case *types.ResourceHeader: + out.Resource = &proto.Event_ResourceHeader{ + ResourceHeader: r, + } + case *types.CertAuthorityV2: + out.Resource = &proto.Event_CertAuthority{ + CertAuthority: r, + } + case *types.StaticTokensV2: + out.Resource = &proto.Event_StaticTokens{ + StaticTokens: r, + } + case *types.ProvisionTokenV2: + out.Resource = &proto.Event_ProvisionToken{ + ProvisionToken: r, + } + case *types.ClusterNameV2: + out.Resource = &proto.Event_ClusterName{ + ClusterName: r, + } + case *types.ClusterConfigV3: + out.Resource = &proto.Event_ClusterConfig{ + ClusterConfig: r, + } + case *types.UserV2: + out.Resource = &proto.Event_User{ + User: r, + } + case *types.RoleV3: + out.Resource = &proto.Event_Role{ + Role: r, + } + case *types.Namespace: + out.Resource = &proto.Event_Namespace{ + Namespace: r, + } + case *types.ServerV2: + out.Resource = &proto.Event_Server{ + Server: r, + } + case *types.ReverseTunnelV2: + out.Resource = &proto.Event_ReverseTunnel{ + ReverseTunnel: r, + } + case *types.TunnelConnectionV2: + out.Resource = &proto.Event_TunnelConnection{ + TunnelConnection: r, + } + case *types.AccessRequestV3: + out.Resource = &proto.Event_AccessRequest{ + AccessRequest: r, + } + case *types.WebSessionV2: + switch r.GetSubKind() { + case types.KindAppSession: + out.Resource = &proto.Event_AppSession{ + AppSession: r, + } + case types.KindWebSession: + out.Resource = &proto.Event_WebSession{ + WebSession: r, + } + default: + return nil, trace.BadParameter("only %q supported", types.WebSessionSubKinds) + } + case *types.WebTokenV3: + out.Resource = &proto.Event_WebToken{ + WebToken: r, + } + case *types.RemoteClusterV3: + out.Resource = &proto.Event_RemoteCluster{ + RemoteCluster: r, + } + case *types.DatabaseServerV3: + out.Resource = &proto.Event_DatabaseServer{ + DatabaseServer: r, + } + default: + return nil, trace.BadParameter("resource type %T is not supported", in.Resource) + } + return &out, nil +} + +func eventTypeToGRPC(in types.OpType) (proto.Operation, error) { + switch in { + case types.OpInit: + return proto.Operation_INIT, nil + case types.OpPut: + return proto.Operation_PUT, nil + case types.OpDelete: + return proto.Operation_DELETE, nil + default: + return -1, trace.BadParameter("event type %v is not supported", in) + } +} + // UpsertNode upserts node func (g *GRPCServer) UpsertNode(ctx context.Context, server *services.ServerV2) (*services.KeepAlive, error) { auth, err := g.authenticate(ctx) diff --git a/lib/auth/permissions.go b/lib/auth/permissions.go index f2a6606778a..16a76fabb79 100644 --- a/lib/auth/permissions.go +++ b/lib/auth/permissions.go @@ -23,9 +23,9 @@ import ( "time" "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/wrappers" + "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/tlsca" @@ -620,7 +620,7 @@ const ( ) // WithDelegator alias for backwards compatibility -var WithDelegator = client.WithDelegator +var WithDelegator = utils.WithDelegator // ClientUsername returns the username of a remote HTTP client making the call. // If ctx didn't pass through auth middleware or did not come from an HTTP diff --git a/lib/benchmark/benchmark.go b/lib/benchmark/benchmark.go index afccdee211a..5b1d728b1e8 100644 --- a/lib/benchmark/benchmark.go +++ b/lib/benchmark/benchmark.go @@ -29,7 +29,7 @@ import ( "syscall" "time" - apiclient "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/profile" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/utils" @@ -272,7 +272,7 @@ func execute(m benchMeasure) error { // makeTeleportClient creates an instance of a teleport client func makeTeleportClient(host, login, proxy string) (*client.TeleportClient, error) { c := client.Config{Host: host} - path := apiclient.FullProfilePath("") + path := profile.FullProfilePath("") if login != "" { c.HostLogin = login c.Username = login diff --git a/lib/client/api.go b/lib/client/api.go index 4838fac3efb..830022b2e01 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -46,9 +46,10 @@ import ( "golang.org/x/term" "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" + "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/profile" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/lib/auth" @@ -484,7 +485,7 @@ func readProfile(profileDir string, profileName string) (*ProfileStatus, error) } // Read in the profile for this proxy. - profile, err := client.ProfileFromDir(profileDir, profileName) + profile, err := profile.FromDir(profileDir, profileName) if err != nil { return nil, trace.Wrap(err) } @@ -650,7 +651,7 @@ func StatusFor(profileDir, proxyHost, username string) (*ProfileStatus, error) { // If no profile is active, Status returns a nil error and nil profile. func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, error) { var err error - var profile *ProfileStatus + var profileStatus *ProfileStatus var others []*ProfileStatus // remove ports from proxy host, because profile name is stored @@ -663,7 +664,7 @@ func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, err } // Construct the full path to the profile requested and make sure it exists. - profileDir = client.FullProfilePath(profileDir) + profileDir = profile.FullProfilePath(profileDir) stat, err := os.Stat(profileDir) if err != nil { log.Debugf("Failed to stat file: %v.", err) @@ -683,7 +684,7 @@ func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, err // no proxyHost was supplied. profileName := proxyHost if profileName == "" { - profileName, err = client.GetCurrentProfileName(profileDir) + profileName, err = profile.GetCurrentProfileName(profileDir) if err != nil { if trace.IsNotFound(err) { return nil, nil, trace.NotFound("not logged in") @@ -695,7 +696,7 @@ func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, err // Read in the target profile first. If readProfile returns trace.NotFound, // that means the profile may have been corrupted (for example keys were // deleted but profile exists), treat this as the user not being logged in. - profile, err = readProfile(profileDir, profileName) + profileStatus, err = readProfile(profileDir, profileName) if err != nil { log.Debug(err) if !trace.IsNotFound(err) { @@ -703,11 +704,11 @@ func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, err } // Make sure the profile is nil, which tsh uses to detect that no // active profile exists. - profile = nil + profileStatus = nil } // load the rest of the profiles - profiles, err := client.ListProfileNames(profileDir) + profiles, err := profile.ListProfileNames(profileDir) if err != nil { return nil, nil, trace.Wrap(err) } @@ -729,7 +730,7 @@ func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, err others = append(others, ps) } - return profile, others, nil + return profileStatus, others, nil } // LoadProfile populates Config with the values stored in the given @@ -737,7 +738,7 @@ func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, err // directory ~/.tsh is used. func (c *Config) LoadProfile(profileDir string, proxyName string) error { // read the profile: - cp, err := client.ProfileFromDir(profileDir, ProxyHost(proxyName)) + cp, err := profile.FromDir(profileDir, ProxyHost(proxyName)) if err != nil { if trace.IsNotFound(err) { return nil @@ -772,9 +773,9 @@ func (c *Config) SaveProfile(dir string, makeCurrent bool) error { return nil } - dir = client.FullProfilePath(dir) + dir = profile.FullProfilePath(dir) - var cp client.Profile + var cp profile.Profile cp.Username = c.Username cp.WebProxyAddr = c.WebProxyAddr cp.SSHProxyAddr = c.SSHProxyAddr @@ -849,7 +850,7 @@ func (c *Config) KubeProxyHostPort() (string, int) { } // KubeClusterAddr returns a public HTTPS address of the proxy for use by -// Kubernetes clients. +// Kubernetes client. func (c *Config) KubeClusterAddr() string { host, port := c.KubeProxyHostPort() return fmt.Sprintf("https://%s:%d", host, port) @@ -928,7 +929,7 @@ type TeleportClient struct { // Note: there's no mutex guarding this or localAgent, making // TeleportClient NOT safe for concurrent use. - lastPing *client.PingResponse + lastPing *webclient.PingResponse } // ShellCreatedCallback can be supplied for every teleport client. It will @@ -2251,14 +2252,14 @@ func (tc *TeleportClient) ActivateKey(ctx context.Context, key *Key) error { // // Ping can be called for its side-effect of applying the proxy-provided // settings (such as various listening addresses). -func (tc *TeleportClient) Ping(ctx context.Context) (*client.PingResponse, error) { +func (tc *TeleportClient) Ping(ctx context.Context) (*webclient.PingResponse, error) { // If, at some point, there's a need to bypass this caching, consider // adding a bool argument. At the time of writing this we always want to // cache. if tc.lastPing != nil { return tc.lastPing, nil } - pr, err := client.Ping( + pr, err := webclient.Ping( ctx, tc.WebProxyAddr, tc.InsecureSkipVerify, @@ -2339,7 +2340,7 @@ func (tc *TeleportClient) UpdateTrustedCA(ctx context.Context, clusterName strin // applyProxySettings updates configuration changes based on the advertised // proxy settings, overriding existing fields in tc. -func (tc *TeleportClient) applyProxySettings(proxySettings client.ProxySettings) error { +func (tc *TeleportClient) applyProxySettings(proxySettings webclient.ProxySettings) error { // Kubernetes proxy settings. if proxySettings.Kube.Enabled { switch { diff --git a/lib/client/identityfile/identity.go b/lib/client/identityfile/identity.go index fa4b0a7562f..c7c120aa364 100644 --- a/lib/client/identityfile/identity.go +++ b/lib/client/identityfile/identity.go @@ -23,8 +23,8 @@ import ( "os" "strings" - apiclient "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/identityfile" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/kube/kubeconfig" "github.com/gravitational/teleport/lib/sshutils" @@ -97,9 +97,9 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) { return nil, trace.Wrap(err) } - idFile := &apiclient.IdentityFile{ + idFile := &identityfile.IdentityFile{ PrivateKey: cfg.Key.Priv, - Certs: apiclient.Certs{ + Certs: identityfile.Certs{ SSH: cfg.Key.Cert, TLS: cfg.Key.TLSCert, }, @@ -119,7 +119,7 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) { idFile.CACerts.TLS = append(idFile.CACerts.TLS, ca.TLSCertificates...) } - if err := apiclient.WriteIdentityFile(idFile, cfg.OutputPath); err != nil { + if err := identityfile.Write(idFile, cfg.OutputPath); err != nil { return nil, trace.Wrap(err) } @@ -132,12 +132,12 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) { return nil, trace.Wrap(err) } - err = ioutil.WriteFile(certPath, cfg.Key.Cert, apiclient.IdentityFilePermissions) + err = ioutil.WriteFile(certPath, cfg.Key.Cert, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } - err = ioutil.WriteFile(keyPath, cfg.Key.Priv, apiclient.IdentityFilePermissions) + err = ioutil.WriteFile(keyPath, cfg.Key.Priv, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } @@ -151,12 +151,12 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) { return nil, trace.Wrap(err) } - err = ioutil.WriteFile(certPath, cfg.Key.TLSCert, apiclient.IdentityFilePermissions) + err = ioutil.WriteFile(certPath, cfg.Key.TLSCert, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } - err = ioutil.WriteFile(keyPath, cfg.Key.Priv, apiclient.IdentityFilePermissions) + err = ioutil.WriteFile(keyPath, cfg.Key.Priv, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } @@ -166,7 +166,7 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) { caCerts = append(caCerts, cert...) } } - err = ioutil.WriteFile(casPath, caCerts, apiclient.IdentityFilePermissions) + err = ioutil.WriteFile(casPath, caCerts, identityfile.FilePermissions) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/client/interfaces.go b/lib/client/interfaces.go index f6671c871d8..3d32dc5a2cf 100644 --- a/lib/client/interfaces.go +++ b/lib/client/interfaces.go @@ -22,7 +22,7 @@ import ( "time" "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/identityfile" "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/native" @@ -107,7 +107,7 @@ func NewKey() (key *Key, err error) { // KeyFromIdentityFile loads the private key + certificate // from an identity file into a Key. func KeyFromIdentityFile(path string) (*Key, error) { - ident, err := client.ReadIdentityFile(path) + ident, err := identityfile.Read(path) if err != nil { return nil, trace.Wrap(err, "failed to parse identity file") } diff --git a/lib/client/keystore.go b/lib/client/keystore.go index 7c91741fcd0..91b0f1a0b2d 100644 --- a/lib/client/keystore.go +++ b/lib/client/keystore.go @@ -29,8 +29,8 @@ import ( "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/profile" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" @@ -147,7 +147,7 @@ func NewFSLocalKeyStore(dirPath string) (s *FSLocalKeyStore, err error) { // initKeysDir initializes the keystore root directory, usually `~/.tsh`. func initKeysDir(dirPath string) (string, error) { - dirPath = client.FullProfilePath(dirPath) + dirPath = profile.FullProfilePath(dirPath) if err := os.MkdirAll(dirPath, os.ModeDir|profileDirPerms); err != nil { return "", trace.ConvertSystemError(err) } diff --git a/lib/service/connect.go b/lib/service/connect.go index f010dbcac0d..a84f0d48b08 100644 --- a/lib/service/connect.go +++ b/lib/service/connect.go @@ -26,6 +26,7 @@ import ( "github.com/gravitational/teleport" apiclient "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/lib" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/backend" @@ -850,7 +851,7 @@ func (process *TeleportProcess) findReverseTunnel(addrs []utils.NetAddr) (string 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. - resp, err := apiclient.Find(process.ExitContext(), + resp, err := webclient.Find(process.ExitContext(), addr.String(), lib.IsInsecureDevMode(), nil) @@ -867,7 +868,7 @@ func (process *TeleportProcess) findReverseTunnel(addrs []utils.NetAddr) (string // 2. SSH Proxy Public Address. // 3. HTTP Proxy Public Address. // 4. Tunnel Listen Address. -func tunnelAddr(settings apiclient.ProxySettings) (string, error) { +func tunnelAddr(settings webclient.ProxySettings) (string, error) { // Extract the port the tunnel server is listening on. netAddr, err := utils.ParseHostPortAddr(settings.SSH.TunnelListenAddr, defaults.SSHProxyTunnelListenPort) if err != nil { diff --git a/lib/service/service.go b/lib/service/service.go index c896ed5ccc8..b6ca495c5b5 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -47,7 +47,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/native" "github.com/gravitational/teleport/lib/backend" @@ -2486,11 +2486,11 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { var webServer *http.Server var webHandler *web.RewritingHandler if !process.Config.Proxy.DisableWebService { - proxySettings := client.ProxySettings{ - Kube: client.KubeProxySettings{ + proxySettings := webclient.ProxySettings{ + Kube: webclient.KubeProxySettings{ Enabled: cfg.Proxy.Kube.Enabled, }, - SSH: client.SSHProxySettings{ + SSH: webclient.SSHProxySettings{ ListenAddr: proxySSHAddr.Addr, TunnelListenAddr: cfg.Proxy.ReverseTunnelListenAddr.String(), }, diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 69e7ac47921..ee831bff20f 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -36,7 +36,7 @@ import ( "time" "github.com/gravitational/teleport" - apiclient "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" @@ -131,7 +131,7 @@ type Config struct { CipherSuites []uint16 // ProxySettings is a settings communicated to proxy - ProxySettings apiclient.ProxySettings + ProxySettings webclient.ProxySettings // FIPS mode means Teleport started in a FedRAMP/FIPS 140-2 compliant // configuration. @@ -524,8 +524,8 @@ func (h *Handler) getUserContext(w http.ResponseWriter, r *http.Request, p httpr return userContext, nil } -func localSettings(authClient auth.ClientI, cap services.AuthPreference) (apiclient.AuthenticationSettings, error) { - as := apiclient.AuthenticationSettings{ +func localSettings(authClient auth.ClientI, cap services.AuthPreference) (webclient.AuthenticationSettings, error) { + as := webclient.AuthenticationSettings{ Type: teleport.Local, SecondFactor: cap.GetSecondFactor(), } @@ -537,17 +537,17 @@ func localSettings(authClient auth.ClientI, cap services.AuthPreference) (apicli return as, nil } if err != nil { - return apiclient.AuthenticationSettings{}, trace.Wrap(err) + return webclient.AuthenticationSettings{}, trace.Wrap(err) } - as.U2F = &apiclient.U2FSettings{AppID: u2fs.AppID} + as.U2F = &webclient.U2FSettings{AppID: u2fs.AppID} return as, nil } -func oidcSettings(connector services.OIDCConnector, cap services.AuthPreference) apiclient.AuthenticationSettings { - return apiclient.AuthenticationSettings{ +func oidcSettings(connector services.OIDCConnector, cap services.AuthPreference) webclient.AuthenticationSettings { + return webclient.AuthenticationSettings{ Type: teleport.OIDC, - OIDC: &apiclient.OIDCSettings{ + OIDC: &webclient.OIDCSettings{ Name: connector.GetName(), Display: connector.GetDisplay(), }, @@ -556,10 +556,10 @@ func oidcSettings(connector services.OIDCConnector, cap services.AuthPreference) } } -func samlSettings(connector services.SAMLConnector, cap services.AuthPreference) apiclient.AuthenticationSettings { - return apiclient.AuthenticationSettings{ +func samlSettings(connector services.SAMLConnector, cap services.AuthPreference) webclient.AuthenticationSettings { + return webclient.AuthenticationSettings{ Type: teleport.SAML, - SAML: &apiclient.SAMLSettings{ + SAML: &webclient.SAMLSettings{ Name: connector.GetName(), Display: connector.GetDisplay(), }, @@ -568,10 +568,10 @@ func samlSettings(connector services.SAMLConnector, cap services.AuthPreference) } } -func githubSettings(connector services.GithubConnector, cap services.AuthPreference) apiclient.AuthenticationSettings { - return apiclient.AuthenticationSettings{ +func githubSettings(connector services.GithubConnector, cap services.AuthPreference) webclient.AuthenticationSettings { + return webclient.AuthenticationSettings{ Type: teleport.Github, - Github: &apiclient.GithubSettings{ + Github: &webclient.GithubSettings{ Name: connector.GetName(), Display: connector.GetDisplay(), }, @@ -579,35 +579,35 @@ func githubSettings(connector services.GithubConnector, cap services.AuthPrefere } } -func defaultAuthenticationSettings(ctx context.Context, authClient auth.ClientI) (apiclient.AuthenticationSettings, error) { +func defaultAuthenticationSettings(ctx context.Context, authClient auth.ClientI) (webclient.AuthenticationSettings, error) { cap, err := authClient.GetAuthPreference() if err != nil { - return apiclient.AuthenticationSettings{}, trace.Wrap(err) + return webclient.AuthenticationSettings{}, trace.Wrap(err) } - var as apiclient.AuthenticationSettings + var as webclient.AuthenticationSettings switch cap.GetType() { case teleport.Local: as, err = localSettings(authClient, cap) if err != nil { - return apiclient.AuthenticationSettings{}, trace.Wrap(err) + return webclient.AuthenticationSettings{}, trace.Wrap(err) } case teleport.OIDC: if cap.GetConnectorName() != "" { oidcConnector, err := authClient.GetOIDCConnector(ctx, cap.GetConnectorName(), false) if err != nil { - return apiclient.AuthenticationSettings{}, trace.Wrap(err) + return webclient.AuthenticationSettings{}, trace.Wrap(err) } as = oidcSettings(oidcConnector, cap) } else { oidcConnectors, err := authClient.GetOIDCConnectors(ctx, false) if err != nil { - return apiclient.AuthenticationSettings{}, trace.Wrap(err) + return webclient.AuthenticationSettings{}, trace.Wrap(err) } if len(oidcConnectors) == 0 { - return apiclient.AuthenticationSettings{}, trace.BadParameter("no oidc connectors found") + return webclient.AuthenticationSettings{}, trace.BadParameter("no oidc connectors found") } as = oidcSettings(oidcConnectors[0], cap) @@ -616,17 +616,17 @@ func defaultAuthenticationSettings(ctx context.Context, authClient auth.ClientI) if cap.GetConnectorName() != "" { samlConnector, err := authClient.GetSAMLConnector(ctx, cap.GetConnectorName(), false) if err != nil { - return apiclient.AuthenticationSettings{}, trace.Wrap(err) + return webclient.AuthenticationSettings{}, trace.Wrap(err) } as = samlSettings(samlConnector, cap) } else { samlConnectors, err := authClient.GetSAMLConnectors(ctx, false) if err != nil { - return apiclient.AuthenticationSettings{}, trace.Wrap(err) + return webclient.AuthenticationSettings{}, trace.Wrap(err) } if len(samlConnectors) == 0 { - return apiclient.AuthenticationSettings{}, trace.BadParameter("no saml connectors found") + return webclient.AuthenticationSettings{}, trace.BadParameter("no saml connectors found") } as = samlSettings(samlConnectors[0], cap) @@ -635,21 +635,21 @@ func defaultAuthenticationSettings(ctx context.Context, authClient auth.ClientI) if cap.GetConnectorName() != "" { githubConnector, err := authClient.GetGithubConnector(ctx, cap.GetConnectorName(), false) if err != nil { - return apiclient.AuthenticationSettings{}, trace.Wrap(err) + return webclient.AuthenticationSettings{}, trace.Wrap(err) } as = githubSettings(githubConnector, cap) } else { githubConnectors, err := authClient.GetGithubConnectors(ctx, false) if err != nil { - return apiclient.AuthenticationSettings{}, trace.Wrap(err) + return webclient.AuthenticationSettings{}, trace.Wrap(err) } if len(githubConnectors) == 0 { - return apiclient.AuthenticationSettings{}, trace.BadParameter("no github connectors found") + return webclient.AuthenticationSettings{}, trace.BadParameter("no github connectors found") } as = githubSettings(githubConnectors[0], cap) } default: - return apiclient.AuthenticationSettings{}, trace.BadParameter("unknown type %v", cap.GetType()) + return webclient.AuthenticationSettings{}, trace.BadParameter("unknown type %v", cap.GetType()) } return as, nil @@ -663,7 +663,7 @@ func (h *Handler) ping(w http.ResponseWriter, r *http.Request, p httprouter.Para return nil, trace.Wrap(err) } - return apiclient.PingResponse{ + return webclient.PingResponse{ Auth: defaultSettings, Proxy: h.cfg.ProxySettings, ServerVersion: teleport.Version, @@ -672,7 +672,7 @@ func (h *Handler) ping(w http.ResponseWriter, r *http.Request, p httprouter.Para } func (h *Handler) find(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { - return apiclient.PingResponse{ + return webclient.PingResponse{ Proxy: h.cfg.ProxySettings, ServerVersion: teleport.Version, MinClientVersion: teleport.MinClientVersion, @@ -688,7 +688,7 @@ func (h *Handler) pingWithConnector(w http.ResponseWriter, r *http.Request, p ht return nil, trace.Wrap(err) } - response := &apiclient.PingResponse{ + response := &webclient.PingResponse{ Proxy: h.cfg.ProxySettings, ServerVersion: teleport.Version, } diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index 6be98f407a6..e0b27371434 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -44,7 +44,7 @@ import ( "golang.org/x/text/encoding/unicode" "github.com/gravitational/teleport" - apiclient "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" @@ -1547,7 +1547,7 @@ func (s *WebSuite) TestPing(c *C) { re, err := wc.Get(context.Background(), wc.Endpoint("webapi", "ping"), url.Values{}) c.Assert(err, IsNil) - var out *apiclient.PingResponse + var out *webclient.PingResponse c.Assert(json.Unmarshal(re.Bytes(), &out), IsNil) preference, err := s.server.Auth().GetAuthPreference() @@ -1593,7 +1593,7 @@ func (s *WebSuite) TestMultipleConnectors(c *C) { // hit the ping endpoint to get the auth type and connector name re, err := wc.Get(ctx, wc.Endpoint("webapi", "ping"), url.Values{}) c.Assert(err, IsNil) - var out *apiclient.PingResponse + var out *webclient.PingResponse c.Assert(json.Unmarshal(re.Bytes(), &out), IsNil) // make sure the connector name we got back was the first connector diff --git a/tool/tctl/common/tctl.go b/tool/tctl/common/tctl.go index a9ee4f82fd8..ba8fc815ff9 100644 --- a/tool/tctl/common/tctl.go +++ b/tool/tctl/common/tctl.go @@ -27,6 +27,7 @@ import ( "github.com/gravitational/teleport" apiclient "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/config" @@ -259,7 +260,7 @@ func findReverseTunnel(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. - resp, err := apiclient.Find(ctx, addr.String(), insecureTLS, nil) + resp, err := webclient.Find(ctx, addr.String(), insecureTLS, nil) if err == nil { return tunnelAddr(addr, resp.Proxy) } @@ -273,7 +274,7 @@ func findReverseTunnel(ctx context.Context, addrs []utils.NetAddr, insecureTLS b // 2. SSH Proxy Public Address. // 3. HTTP Proxy Public Address. // 4. Tunnel Listen Address. -func tunnelAddr(webAddr utils.NetAddr, settings apiclient.ProxySettings) (string, error) { +func tunnelAddr(webAddr utils.NetAddr, settings webclient.ProxySettings) (string, error) { // Extract the port the tunnel server is listening on. netAddr, err := utils.ParseHostPortAddr(settings.SSH.TunnelListenAddr, defaults.SSHProxyTunnelListenPort) if err != nil { diff --git a/tool/tsh/tsh_test.go b/tool/tsh/tsh_test.go index b59b74d8bca..afac0c5907d 100644 --- a/tool/tsh/tsh_test.go +++ b/tool/tsh/tsh_test.go @@ -30,7 +30,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" - apiclient "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/profile" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/backend" @@ -85,9 +85,9 @@ func (p *cliModules) IsBoringBinary() bool { } func TestFailedLogin(t *testing.T) { - os.RemoveAll(apiclient.FullProfilePath("")) + os.RemoveAll(profile.FullProfilePath("")) t.Cleanup(func() { - os.RemoveAll(apiclient.FullProfilePath("")) + os.RemoveAll(profile.FullProfilePath("")) }) connector := mockConnector(t) @@ -117,9 +117,9 @@ func TestFailedLogin(t *testing.T) { } func TestOIDCLogin(t *testing.T) { - os.RemoveAll(apiclient.FullProfilePath("")) + os.RemoveAll(profile.FullProfilePath("")) t.Cleanup(func() { - os.RemoveAll(apiclient.FullProfilePath("")) + os.RemoveAll(profile.FullProfilePath("")) }) modules.SetModules(&cliModules{}) @@ -213,9 +213,9 @@ func TestOIDCLogin(t *testing.T) { } func TestRelogin(t *testing.T) { - os.RemoveAll(apiclient.FullProfilePath("")) + os.RemoveAll(profile.FullProfilePath("")) t.Cleanup(func() { - os.RemoveAll(apiclient.FullProfilePath("")) + os.RemoveAll(profile.FullProfilePath("")) }) connector := mockConnector(t) @@ -271,9 +271,9 @@ func TestRelogin(t *testing.T) { } func TestMakeClient(t *testing.T) { - os.RemoveAll(apiclient.FullProfilePath("")) + os.RemoveAll(profile.FullProfilePath("")) t.Cleanup(func() { - os.RemoveAll(apiclient.FullProfilePath("")) + os.RemoveAll(profile.FullProfilePath("")) }) var conf CLIConf