teleport/rfd/0010-api.md
Brian Joerger a5947278a0 Update rfd/0010-api.md
Co-authored-by: Gus Luxton <gus@gravitational.com>
2021-02-02 18:07:29 -08:00

6.9 KiB

authors state
Alexander Klizhentas (sasha@gravitational.com), Brian Joerger (bjoerger@gravitational.com) draft

RFD 10 - API and Client Libraries

What

  • Unify Golang Client experience.
  • Automatically generate API documentation using protoc-gen-doc.

Why

There are several problems with Go client libraries:

  • Go library example pulls entire teleport dependency including lib/auth and lib/services.
  • There is no separate client library with guarantees of compatibility with specific version of Teleport. See examples here.
  • It's impossible to generate library in any other language other than Go due to unclear API surface.
  • Some client logic is residing in plugins, while other is in lib/auth.
  • Code in lib/auth uses some concepts unfamiliar to Go users:
// 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)
    }

    // must parse addr list panics and is not necessary
    authServerAddr := utils.MustParseAddrList("127.0.0.1:3025")
    clientConfig := auth.ClientConfig{Addrs: authServerAddr, TLS: tlsConfig}

    // TLS client is the only client
    return auth.NewTLSClient(clientConfig)

Details

Approach

Keep Teleport's API layer unified, avoid having internal API vs external API layer. The access to the API surface is limited by role based access control.

Avoid large refactors, instead use Go's type aliases to move things around while keeping things backwards compatible.

// Preserve full backwards compatibility
type services.Role = types.Role
type auth.Service = proto.Service

Move protobuf types and services and generate docs

Point protobuf generators to generate types to another folder and use a system of go submodules.

Use protoc-gen-doc to generate gRPC API markdown documentation during the protoc build process. This will require us to improve our proto files with user-focused comments.

$ ls github.com/gravitational/teleport/api
# root folder with API specs and is a go submodule
api/
  # open API spec used for swagger, see for example K8s Swagger spec
  # https://github.com/kubernetes/kubernetes/tree/master/api/openapi-spec
  # These docs can be copied to our doc engine before each doc release, optionally with custom templates.
  docs/
    types.md
    wrappers.md
    client.md
    # move generated services types here, while keeping the original proto files
    # in place in lib/services/services.proto
  types/
    types.pb.go
    wrappers.pb.go
  # generate auth service lib/auth/proto here
  client/
    authservice.pb.go

Move client implementation to api/client

Move relevant lib/client.*Client code to api/client. Track dependencies, make sure that new api/client module has no dependencies on the outside modules anywhere in lib.

Track dependencies

Track dependencies to be limited to:

  • gravitational/roundtrip, gravitational/trace
  • grpc google package and its dependencies
  • protobuf generated code
  • Keep some auth service specific code like utils.Addr out of the api/client

Go native user experience

Achieve the user experience in Client with a simplified client constructor. This should also transparently handle dialing via a proxy.

import (
   "github.com/gravitational/teleport/api/client"
)

func main() {
   // TLS client is the only TLS client supported in teleport
   client, err := client.New(client.Config{
     Addrs: []string{"localhost:3025"},
     // ContextDialer is optional context dialer
     ContextDialer: net.DialContext,
     // direct TLS credentials config
     Credentials: client.TLSCreds(tls.Config)
   })
   ...
   defer client.Close()

   ping, err := client.Ping()
   ...
   fmt.Printf("Ping: %v\n", ping.Version)
}
$ go mod init
$ go run main.go

Credential providers

Support multiple credential providers with chaining

  • client.TLSCreds(*tls.Config) loads creds from TLS config
  • client.PathCreds("path") loads mounted creds from path, detects reloads and updates the grpc transport

Support convenience method that supports and understands ~/.tsh/profile, so users can try testing using credentials they got from tsh login

// for testing, use client spec that loads dialer, and credentials from profile
cfg, err := client.LoadTSHConfig()
// cfg.ContextDialer sets up dialer using tunnel dialer in tctl
// TLS creds are loaded from key store, similar how tctl does it in 5.0
client, err := client.New(cfg)
$ tsh login
# try client
$ go run main.go

Auto version checks

Client should verify that its implementation is compatible with Teleport's server version by calling Ping and checking the version similar to tsh logic.

Move Access requests code into client

Move access workflows code into client subpackage with new name workflows:

https://github.com/gravitational/teleport-plugins/tree/master/access

Instead of a separate client, workflows becomes a subpackage of the api client:

import (
   "github.com/gravitational/teleport/api/client"
   "github.com/gravitational/teleport/api/client/workflows"
)

func main() {
   client, err := client.New(...)
   watcher, err := workflows.NewWatcher(ctx, client, workflows.Watch{State: ...})
   ...
   defer watcher.Close()

Provide simplified high level access hooks

Access hooks are too low level, add simple handler similar to http server design with a router:


func myTeamRequest(ctx context.Context, req access.Request) (access.Request, error) {
  return req, trace.AccessDenied("access denied")
}


func main() {
router := access.Router()
  // handle access requests from my team only
  router.HandleFunc(router.Match{Traits: Trait("team", "myteam")}
  srv := access.Server{
     Client: clt,
     Handler: router,
  }

Improve API documentation user experience

Integrate protoc-gen-doc generated docs into mkdocs` using mkdocs plugin.

Add Python Client

Generate a python version of the Teleport client as a proof of concept. This could be any language, and the choice to use python first is arbitrary.

ls github.com/gravitational/teleport
api/
python/

$ pip install teleport-client
import teleport

client = teleport.client(addr=...)
client.tokens()

Port router code

@access.handle(clt, path=access.trait("team")):
def handle(req):
   if req.user == "bob":
      raise access.Denied("access denied")

Update docs to include samples in both languages, Python and Go.