teleport/lib/services/session.go
Sasha Klizhentas 8356ae6a74 Use in-memory cache for the auth server API.
This commit expands the usage of the caching layer
for auth server API:

* Introduces in-memory cache that is used to serve all
Auth server API requests. This is done to achieve scalability
on 10K+ node clusters, where each node fetches certificate authorities,
roles, users and join tokens. It is not possible to scale
DynamoDB backend or other backends on 10K reads per seconds
on a single shard or partition. The solution is to introduce
an in-memory cache of the backend state that is always used
for reads.

* In-memory cache has been expanded to support all resources
required by the auth server.

* Experimental `tctl top` command has been introduced to display
common single node metrics.

Replace SQLite Memory Backend with BTree

SQLite in memory backend was suffering from
high tail latencies under load (up to 8 seconds
in 99.9%-ile on load configurations).

This commit replaces the SQLite memory caching
backend with in-memory BTree backend that
brought down tail latencies to 2 seconds (99.9%-ile)
and brought overall performance improvement.
2019-04-12 14:23:09 -07:00

485 lines
14 KiB
Go

/*
Copyright 2015-2019 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package services
import (
"encoding/json"
"fmt"
"time"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
)
// WebSession stores key and value used to authenticate with SSH
// notes on behalf of user
type WebSession interface {
GetMetadata() Metadata
// GetShortName returns visible short name used in logging
GetShortName() string
// GetName returns session name
GetName() string
// GetUser returns the user this session is associated with
GetUser() string
// SetName sets session name
SetName(string)
// SetUser sets user associated with this session
SetUser(string)
// GetPub is returns public certificate signed by auth server
GetPub() []byte
// GetPriv returns private OpenSSH key used to auth with SSH nodes
GetPriv() []byte
// SetPriv sets private key
SetPriv([]byte)
// GetTLSCert returns PEM encoded TLS certificate associated with session
GetTLSCert() []byte
// BearerToken is a special bearer token used for additional
// bearer authentication
GetBearerToken() string
// SetBearerTokenExpiryTime sets bearer token expiry time
SetBearerTokenExpiryTime(time.Time)
// SetExpiryTime sets session expiry time
SetExpiryTime(time.Time)
// GetBearerTokenExpiryTime - absolute time when token expires
GetBearerTokenExpiryTime() time.Time
// GetExpiryTime - absolute time when web session expires
GetExpiryTime() time.Time
// V1 returns V1 version of the resource
V1() *WebSessionV1
// V2 returns V2 version of the resource
V2() *WebSessionV2
// WithoutSecrets returns copy of the web session but without private keys
WithoutSecrets() WebSession
// CheckAndSetDefaults checks and set default values for any missing fields.
CheckAndSetDefaults() error
}
// NewWebSession returns new instance of the web session based on the V2 spec
func NewWebSession(name string, spec WebSessionSpecV2) WebSession {
return &WebSessionV2{
Kind: KindWebSession,
Version: V2,
Metadata: Metadata{
Name: name,
Namespace: defaults.Namespace,
},
Spec: spec,
}
}
// WebSessionV2 is version 2 spec for session
type WebSessionV2 struct {
// Kind is a resource kind
Kind string `json:"kind"`
// Version is version
Version string `json:"version"`
// Metadata is connector metadata
Metadata Metadata `json:"metadata"`
// Spec contains cert authority specification
Spec WebSessionSpecV2 `json:"spec"`
}
// WebSessionSpecV2 is a spec for V2 session
type WebSessionSpecV2 struct {
// User is a user this web session belongs to
User string `json:"user"`
// Pub is a public certificate signed by auth server
Pub []byte `json:"pub"`
// Priv is a private OpenSSH key used to auth with SSH nodes
Priv []byte `json:"priv,omitempty"`
// TLSCert is a TLS certificate used to auth with auth server
TLSCert []byte `json:"tls_cert,omitempty"`
// BearerToken is a special bearer token used for additional
// bearer authentication
BearerToken string `json:"bearer_token"`
// BearerTokenExpires - absolute time when token expires
BearerTokenExpires time.Time `json:"bearer_token_expires"`
// Expires - absolute time when session expires
Expires time.Time `json:"expires"`
}
// GetMetadata returns metadata
func (ws *WebSessionV2) GetMetadata() Metadata {
return ws.Metadata
}
// WithoutSecrets returns copy of the object but without secrets
func (ws *WebSessionV2) WithoutSecrets() WebSession {
v2 := ws.V2()
v2.Spec.Priv = nil
return v2
}
// CheckAndSetDefaults checks and set default values for any missing fields.
func (ws *WebSessionV2) CheckAndSetDefaults() error {
err := ws.Metadata.CheckAndSetDefaults()
if err != nil {
return trace.Wrap(err)
}
return nil
}
// SetName sets session name
func (ws *WebSessionV2) SetName(name string) {
ws.Metadata.Name = name
}
// SetUser sets user associated with this session
func (ws *WebSessionV2) SetUser(u string) {
ws.Spec.User = u
}
// GetUser returns the user this session is associated with
func (ws *WebSessionV2) GetUser() string {
return ws.Spec.User
}
// GetShortName returns visible short name used in logging
func (ws *WebSessionV2) GetShortName() string {
if len(ws.Metadata.Name) < 4 {
return "<undefined>"
}
return ws.Metadata.Name[:4]
}
// GetName returns session name
func (ws *WebSessionV2) GetName() string {
return ws.Metadata.Name
}
// GetTLSCert returns PEM encoded TLS certificate associated with session
func (ws *WebSessionV2) GetTLSCert() []byte {
return ws.Spec.TLSCert
}
// GetPub is returns public certificate signed by auth server
func (ws *WebSessionV2) GetPub() []byte {
return ws.Spec.Pub
}
// GetPriv returns private OpenSSH key used to auth with SSH nodes
func (ws *WebSessionV2) GetPriv() []byte {
return ws.Spec.Priv
}
// SetPriv sets private key
func (ws *WebSessionV2) SetPriv(priv []byte) {
ws.Spec.Priv = priv
}
// BearerToken is a special bearer token used for additional
// bearer authentication
func (ws *WebSessionV2) GetBearerToken() string {
return ws.Spec.BearerToken
}
// SetBearerTokenExpiryTime sets bearer token expiry time
func (ws *WebSessionV2) SetBearerTokenExpiryTime(tm time.Time) {
ws.Spec.BearerTokenExpires = tm
}
// SetExpiryTime sets session expiry time
func (ws *WebSessionV2) SetExpiryTime(tm time.Time) {
ws.Spec.Expires = tm
}
// GetBearerTokenExpiryTime - absolute time when token expires
func (ws *WebSessionV2) GetBearerTokenExpiryTime() time.Time {
return ws.Spec.BearerTokenExpires
}
// GetExpiryTime - absolute time when web session expires
func (ws *WebSessionV2) GetExpiryTime() time.Time {
return ws.Spec.Expires
}
// V2 returns V2 version of the resource
func (ws *WebSessionV2) V2() *WebSessionV2 {
return ws
}
// V1 returns V1 version of the object
func (ws *WebSessionV2) V1() *WebSessionV1 {
return &WebSessionV1{
ID: ws.Metadata.Name,
Priv: ws.Spec.Priv,
Pub: ws.Spec.Pub,
BearerToken: ws.Spec.BearerToken,
Expires: ws.Spec.Expires,
}
}
// WebSessionSpecV2Schema is JSON schema for cert authority V2
const WebSessionSpecV2Schema = `{
"type": "object",
"additionalProperties": false,
"required": ["pub", "bearer_token", "bearer_token_expires", "expires", "user"],
"properties": {
"user": {"type": "string"},
"pub": {"type": "string"},
"priv": {"type": "string"},
"tls_cert": {"type": "string"},
"bearer_token": {"type": "string"},
"bearer_token_expires": {"type": "string"},
"expires": {"type": "string"}%v
}
}`
// WebSession stores key and value used to authenticate with SSH
// nodes on behalf of user
type WebSessionV1 struct {
// ID is session ID
ID string `json:"id"`
// User is a user this web session is associated with
User string `json:"user"`
// Pub is a public certificate signed by auth server
Pub []byte `json:"pub"`
// Priv is a private OpenSSH key used to auth with SSH nodes
Priv []byte `json:"priv,omitempty"`
// BearerToken is a special bearer token used for additional
// bearer authentication
BearerToken string `json:"bearer_token"`
// Expires - absolute time when token expires
Expires time.Time `json:"expires"`
}
// V1 returns V1 version of the resource
func (s *WebSessionV1) V1() *WebSessionV1 {
return s
}
// V2 returns V2 version of the resource
func (s *WebSessionV1) V2() *WebSessionV2 {
return &WebSessionV2{
Kind: KindWebSession,
Version: V2,
Metadata: Metadata{
Name: s.ID,
Namespace: defaults.Namespace,
},
Spec: WebSessionSpecV2{
Pub: s.Pub,
Priv: s.Priv,
BearerToken: s.BearerToken,
Expires: s.Expires,
BearerTokenExpires: s.Expires,
},
}
}
// WithoutSecrets returns copy of the web session but without private keys
func (ws *WebSessionV1) WithoutSecrets() WebSession {
v2 := ws.V2()
v2.Spec.Priv = nil
return nil
}
// SetName sets session name
func (ws *WebSessionV1) SetName(name string) {
ws.ID = name
}
// SetUser sets user associated with this session
func (ws *WebSessionV1) SetUser(u string) {
ws.User = u
}
// GetUser returns the user this session is associated with
func (ws *WebSessionV1) GetUser() string {
return ws.User
}
// GetShortName returns visible short name used in logging
func (ws *WebSessionV1) GetShortName() string {
if len(ws.ID) < 4 {
return "<undefined>"
}
return ws.ID[:4]
}
// GetName returns session name
func (ws *WebSessionV1) GetName() string {
return ws.ID
}
// GetPub is returns public certificate signed by auth server
func (ws *WebSessionV1) GetPub() []byte {
return ws.Pub
}
// GetPriv returns private OpenSSH key used to auth with SSH nodes
func (ws *WebSessionV1) GetPriv() []byte {
return ws.Priv
}
// BearerToken is a special bearer token used for additional
// bearer authentication
func (ws *WebSessionV1) GetBearerToken() string {
return ws.BearerToken
}
// Expires - absolute time when token expires
func (ws *WebSessionV1) GetExpiryTime() time.Time {
return ws.Expires
}
// SetExpiryTime sets session expiry time
func (ws *WebSessionV1) SetExpiryTime(tm time.Time) {
ws.Expires = tm
}
// GetBearerRoken - absolute time when token expires
func (ws *WebSessionV1) GetBearerTokenExpiryTime() time.Time {
return ws.Expires
}
// SetBearerTokenExpiryTime sets session expiry time
func (ws *WebSessionV1) SetBearerTokenExpiryTime(tm time.Time) {
ws.Expires = tm
}
var webSessionMarshaler WebSessionMarshaler = &TeleportWebSessionMarshaler{}
// SetWebSessionMarshaler sets global user marshaler
func SetWebSessionMarshaler(u WebSessionMarshaler) {
marshalerMutex.Lock()
defer marshalerMutex.Unlock()
webSessionMarshaler = u
}
// GetWebSessionMarshaler returns currently set user marshaler
func GetWebSessionMarshaler() WebSessionMarshaler {
marshalerMutex.RLock()
defer marshalerMutex.RUnlock()
return webSessionMarshaler
}
// WebSessionMarshaler implements marshal/unmarshal of User implementations
// mostly adds support for extended versions
type WebSessionMarshaler interface {
// UnmarshalWebSession unmarhsals cert authority from binary representation
UnmarshalWebSession(bytes []byte) (WebSession, error)
// MarshalWebSession to binary representation
MarshalWebSession(c WebSession, opts ...MarshalOption) ([]byte, error)
// GenerateWebSession generates new web session and is used to
// inject additional data in extenstions
GenerateWebSession(WebSession) (WebSession, error)
// ExtendWebSession extends web session and is used to
// inject additional data in extenstions when session is getting renewed
ExtendWebSession(WebSession) (WebSession, error)
}
// GetWebSessionSchema returns JSON Schema for web session
func GetWebSessionSchema() string {
return GetWebSessionSchemaWithExtensions("")
}
// GetWebSessionSchemaWithExtensions returns JSON Schema for web session with user-supplied extensions
func GetWebSessionSchemaWithExtensions(extension string) string {
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, fmt.Sprintf(WebSessionSpecV2Schema, extension), DefaultDefinitions)
}
type TeleportWebSessionMarshaler struct{}
// GenerateWebSession generates new web session and is used to
// inject additional data in extenstions
func (*TeleportWebSessionMarshaler) GenerateWebSession(ws WebSession) (WebSession, error) {
return ws, nil
}
// ExtendWebSession renews web session and is used to
// inject additional data in extenstions when session is getting renewed
func (*TeleportWebSessionMarshaler) ExtendWebSession(ws WebSession) (WebSession, error) {
return ws, nil
}
// UnmarshalWebSession unmarshals web session from on-disk byte format
func (*TeleportWebSessionMarshaler) UnmarshalWebSession(bytes []byte) (WebSession, error) {
var h ResourceHeader
err := json.Unmarshal(bytes, &h)
if err != nil {
return nil, trace.Wrap(err)
}
switch h.Version {
case "":
var ws WebSessionV1
err := json.Unmarshal(bytes, &ws)
if err != nil {
return nil, trace.Wrap(err)
}
utils.UTC(&ws.Expires)
return ws.V2(), nil
case V2:
var ws WebSessionV2
if err := utils.UnmarshalWithSchema(GetWebSessionSchema(), &ws, bytes); err != nil {
return nil, trace.BadParameter(err.Error())
}
utils.UTC(&ws.Spec.BearerTokenExpires)
utils.UTC(&ws.Spec.Expires)
if err := ws.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
return &ws, nil
}
return nil, trace.BadParameter("web session resource version %v is not supported", h.Version)
}
// MarshalWebSession marshals web session into on-disk representation
func (*TeleportWebSessionMarshaler) MarshalWebSession(ws WebSession, opts ...MarshalOption) ([]byte, error) {
cfg, err := collectOptions(opts)
if err != nil {
return nil, trace.Wrap(err)
}
type ws1 interface {
V1() *WebSessionV1
}
type ws2 interface {
V2() *WebSessionV2
}
version := cfg.GetVersion()
switch version {
case V1:
v, ok := ws.(ws1)
if !ok {
return nil, trace.BadParameter("don't know how to marshal session %v", V1)
}
return json.Marshal(v.V1())
case V2:
v, ok := ws.(ws2)
if !ok {
return nil, trace.BadParameter("don't know how to marshal session %v", V2)
}
v2 := v.V2()
if !cfg.PreserveResourceID {
// avoid modifying the original object
// to prevent unexpected data races
copy := *v2
copy.Metadata.ID = 0
v2 = &copy
}
return utils.FastMarshal(v2)
default:
return nil, trace.BadParameter("version %v is not supported", version)
}
}