common

package
v1.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 25, 2025 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Index

Constants

View Source
const (
	EdgeBinding   = "edge"
	TunnelBinding = "tunnel"
)
View Source
const (
	ClaimClientIdOpenZiti = "openziti"
	ClaimAudienceOpenZiti = "openziti"

	//ClaimLegacyNative - to remove after SDKs stop using this as a client id
	ClaimLegacyNative = "native"

	CustomClaimApiSessionId      = "z_asid"
	CustomClaimExternalId        = "z_eid"
	CustomClaimIsAdmin           = "z_ia"
	CustomClaimsConfigTypes      = "z_ct"
	CustomClaimsCertFingerprints = "z_cfs"

	// CustomClaimsTokenType and other constants below may not appear as referenced, but are used in `json: ""` tags. Provided here for external use.
	CustomClaimsTokenType       = "z_t"
	CustomClaimServiceId        = "z_sid"
	CustomClaimIdentityId       = "z_iid"
	CustomClaimServiceType      = "z_st"
	CustomClaimRemoteAddress    = "z_ra"
	CustomClaimIsCertExtendable = "z_ice"

	DefaultAccessTokenDuration  = 30 * time.Minute
	DefaultIdTokenDuration      = 30 * time.Minute
	DefaultRefreshTokenDuration = 24 * time.Hour

	TokenTypeAccess        = "a"
	TokenTypeRefresh       = "r"
	TokenTypeServiceAccess = "s"
)
View Source
const (
	DiffTypeAdd = "added"
	DiffTypeMod = "modified"
	DiffTypeSub = "removed"
)
View Source
const (
	EventAccessGained  ServiceEventType = 1
	EventUpdated       ServiceEventType = 2
	EventAccessRemoved ServiceEventType = 3

	EventFullState            IdentityEventType = 4
	EventIdentityUpdated      IdentityEventType = 5
	EventPostureChecksUpdated IdentityEventType = 6
	EventIdentityDeleted      IdentityEventType = 7
)

Variables

This section is empty.

Functions

func CMapToMap added in v1.3.0

func CMapToMap[T any](m cmap.ConcurrentMap[string, T]) map[string]T

func CloneMap added in v1.1.8

func CloneMap[V any](m cmap.ConcurrentMap[string, V]) cmap.ConcurrentMap[string, V]

Types

type AccessClaims added in v0.34.0

type AccessClaims struct {
	oidc.AccessTokenClaims
	CustomClaims
}

func (*AccessClaims) ConfigTypesAsMap added in v0.34.0

func (r *AccessClaims) ConfigTypesAsMap() map[string]struct{}

func (*AccessClaims) GetAudience added in v0.34.0

func (r *AccessClaims) GetAudience() (jwt.ClaimStrings, error)

func (*AccessClaims) GetExpirationTime added in v0.34.0

func (r *AccessClaims) GetExpirationTime() (*jwt.NumericDate, error)

func (*AccessClaims) GetIssuedAt added in v0.34.0

func (r *AccessClaims) GetIssuedAt() (*jwt.NumericDate, error)

func (*AccessClaims) GetIssuer added in v0.34.0

func (r *AccessClaims) GetIssuer() (string, error)

func (*AccessClaims) GetNotBefore added in v0.34.0

func (r *AccessClaims) GetNotBefore() (*jwt.NumericDate, error)

func (*AccessClaims) GetSubject added in v0.34.0

func (r *AccessClaims) GetSubject() (string, error)

func (*AccessClaims) HasAudience added in v0.34.0

func (c *AccessClaims) HasAudience(targetAud string) bool

func (*AccessClaims) TotpComplete added in v0.34.0

func (c *AccessClaims) TotpComplete() bool

func (*AccessClaims) UnmarshalJSON added in v0.34.0

func (r *AccessClaims) UnmarshalJSON(raw []byte) error

type AccessPolicies added in v0.34.0

type AccessPolicies struct {
	Identity      *Identity
	Service       *Service
	Policies      []*ServicePolicy
	PostureChecks map[string]*edge_ctrl_pb.DataState_PostureCheck
}

AccessPolicies represents the Identity's access to a Service through many Policies. The PostureChecks provided are referenced by the granting Policies. The PostureChecks for each of the Policies may be evaluated to determine a valid policy and posture access path.

type Config added in v1.1.8

type Config struct {
	*DataStateConfig
	// contains filtered or unexported fields
}

type ConfigType added in v1.1.8

type ConfigType struct {
	*DataStateConfigType
	// contains filtered or unexported fields
}

type CustomClaims added in v0.34.0

type CustomClaims struct {
	ApiSessionId     string              `json:"z_asid,omitempty"`
	ExternalId       string              `json:"z_eid,omitempty"`
	IsAdmin          bool                `json:"z_ia,omitempty"`
	ConfigTypes      []string            `json:"z_ct,omitempty"`
	ApplicationId    string              `json:"z_aid,omitempty"`
	Type             string              `json:"z_t"`
	CertFingerprints []string            `json:"z_cfs"`
	Scopes           []string            `json:"scopes,omitempty"`
	SdkInfo          *rest_model.SdkInfo `json:"z_sdk"`
	EnvInfo          *rest_model.EnvInfo `json:"z_env"`
	RemoteAddress    string              `json:"z_ra"`
	IsCertExtendable bool                `json:"z_ice"`
}

func (*CustomClaims) ToMap added in v0.34.0

func (c *CustomClaims) ToMap() (map[string]any, error)

type DataStateConfig added in v1.1.8

type DataStateConfig = edge_ctrl_pb.DataState_Config

type DataStateConfigType added in v1.1.8

type DataStateConfigType = edge_ctrl_pb.DataState_ConfigType

type DataStateIdentity added in v1.1.1

type DataStateIdentity = edge_ctrl_pb.DataState_Identity

type DataStatePostureCheck added in v1.1.8

type DataStatePostureCheck = edge_ctrl_pb.DataState_PostureCheck

type DataStateService added in v1.1.8

type DataStateService = edge_ctrl_pb.DataState_Service

type DataStateServicePolicy added in v1.1.1

type DataStateServicePolicy = edge_ctrl_pb.DataState_ServicePolicy

type DiffSink added in v1.3.0

type DiffSink func(entityType string, id string, diffType DiffType, detail string)

type DiffType added in v1.3.0

type DiffType string

type EventCache added in v0.34.0

type EventCache interface {
	// Store allows storage of an event and execution of an onSuccess callback while the event cache remains locked.
	// onSuccess may be nil. This function is blocking.
	Store(event *edge_ctrl_pb.DataState_ChangeSet, onSuccess OnStoreSuccess) error

	// CurrentIndex returns the latest event index applied. This function is blocking.
	CurrentIndex() (uint64, bool)

	// ReplayFrom returns an array of events from startIndex and true if the replay may be facilitated.
	// An empty slice and true is returned in cases where the requested startIndex is greater than the current index.
	// An empty slice and false is returned in cases where the replay cannot be facilitated.
	// This function is blocking.
	ReplayFrom(startIndex uint64) ([]*edge_ctrl_pb.DataState_ChangeSet, bool)

	// WhileLocked allows the execution of arbitrary functionality while the event cache is locked. This function
	// is blocking.
	WhileLocked(func(uint64, bool))

	// SetCurrentIndex sets the current index to the supplied value. All event log history may be lost.
	SetCurrentIndex(uint64)
}

type ForgetfulEventCache added in v0.34.0

type ForgetfulEventCache struct {
	// contains filtered or unexported fields
}

ForgetfulEventCache does not store events or support replaying. It tracks the event index and that is it. It is a stand in for LoggingEventCache when replaying events is not expected (i.e. in routers)

func NewForgetfulEventCache added in v0.34.0

func NewForgetfulEventCache() *ForgetfulEventCache

func (*ForgetfulEventCache) CurrentIndex added in v0.34.0

func (cache *ForgetfulEventCache) CurrentIndex() (uint64, bool)

func (*ForgetfulEventCache) ReplayFrom added in v0.34.0

func (cache *ForgetfulEventCache) ReplayFrom(_ uint64) ([]*edge_ctrl_pb.DataState_ChangeSet, bool)

func (*ForgetfulEventCache) SetCurrentIndex added in v0.34.0

func (cache *ForgetfulEventCache) SetCurrentIndex(index uint64)

func (*ForgetfulEventCache) Store added in v0.34.0

func (*ForgetfulEventCache) WhileLocked added in v0.34.0

func (cache *ForgetfulEventCache) WhileLocked(callback func(uint64, bool))

type IdTokenClaims added in v0.34.0

type IdTokenClaims struct {
	oidc.IDTokenClaims
	CustomClaims
}

func (*IdTokenClaims) GetAudience added in v1.1.1

func (r *IdTokenClaims) GetAudience() (jwt.ClaimStrings, error)

func (*IdTokenClaims) GetExpirationTime added in v1.1.1

func (r *IdTokenClaims) GetExpirationTime() (*jwt.NumericDate, error)

func (*IdTokenClaims) GetIssuedAt added in v1.1.1

func (r *IdTokenClaims) GetIssuedAt() (*jwt.NumericDate, error)

func (*IdTokenClaims) GetIssuer added in v1.1.1

func (r *IdTokenClaims) GetIssuer() (string, error)

func (*IdTokenClaims) GetNotBefore added in v1.1.1

func (r *IdTokenClaims) GetNotBefore() (*jwt.NumericDate, error)

func (*IdTokenClaims) GetSubject added in v1.1.1

func (r *IdTokenClaims) GetSubject() (string, error)

func (*IdTokenClaims) TotpComplete added in v0.34.0

func (c *IdTokenClaims) TotpComplete() bool

type Identity added in v1.1.1

type Identity struct {
	*DataStateIdentity
	ServicePolicies cmap.ConcurrentMap[string, struct{}] `json:"servicePolicies"`
	// contains filtered or unexported fields
}

type IdentityConfig added in v1.1.8

type IdentityConfig struct {
	Config     *Config
	ConfigType *ConfigType
}

type IdentityEventSubscriber added in v1.1.8

type IdentityEventSubscriber interface {
	NotifyIdentityEvent(state *IdentityState, eventType IdentityEventType)
	NotifyServiceChange(state *IdentityState, service *IdentityService, eventType ServiceEventType)
}

type IdentityEventType added in v1.1.8

type IdentityEventType byte

type IdentityService added in v1.1.8

type IdentityService struct {
	Service     *Service
	Checks      map[string]struct{}
	Configs     map[string]*IdentityConfig
	DialAllowed bool
	BindAllowed bool
}

func (*IdentityService) Equals added in v1.1.8

func (self *IdentityService) Equals(other *IdentityService) bool

type IdentityState added in v1.1.8

type IdentityState struct {
	Identity      *Identity
	PostureChecks map[string]*PostureCheck
	Services      map[string]*IdentityService
}

type IdentitySubscription added in v1.1.8

type IdentitySubscription struct {
	IdentityId string
	Identity   *Identity
	Services   map[string]*IdentityService
	Checks     map[string]*PostureCheck

	sync.Mutex
	// contains filtered or unexported fields
}

func (*IdentitySubscription) Diff added in v1.3.0

func (self *IdentitySubscription) Diff(rdm *RouterDataModel, sink DiffSink)

type LoggingEventCache added in v0.34.0

type LoggingEventCache struct {
	HeadLogIndex uint64                                       `json:"-"`
	LogSize      uint64                                       `json:"-"`
	Log          []uint64                                     `json:"-"`
	Events       map[uint64]*edge_ctrl_pb.DataState_ChangeSet `json:"-"`
	// contains filtered or unexported fields
}

LoggingEventCache stores events in order to support replaying (i.e. in controllers).

func NewLoggingEventCache added in v0.34.0

func NewLoggingEventCache(logSize uint64) *LoggingEventCache

func (*LoggingEventCache) CurrentIndex added in v0.34.0

func (cache *LoggingEventCache) CurrentIndex() (uint64, bool)

func (*LoggingEventCache) ReplayFrom added in v0.34.0

func (cache *LoggingEventCache) ReplayFrom(startIndex uint64) ([]*edge_ctrl_pb.DataState_ChangeSet, bool)

func (*LoggingEventCache) SetCurrentIndex added in v0.34.0

func (cache *LoggingEventCache) SetCurrentIndex(index uint64)

func (*LoggingEventCache) Store added in v0.34.0

func (cache *LoggingEventCache) Store(event *edge_ctrl_pb.DataState_ChangeSet, onSuccess OnStoreSuccess) error

func (*LoggingEventCache) WhileLocked added in v0.34.0

func (cache *LoggingEventCache) WhileLocked(callback func(uint64, bool))

type OnStoreSuccess added in v0.34.0

type OnStoreSuccess func(index uint64, event *edge_ctrl_pb.DataState_ChangeSet)

type PostureCheck added in v1.1.8

type PostureCheck struct {
	*DataStatePostureCheck
	// contains filtered or unexported fields
}

type RefreshClaims added in v0.34.0

type RefreshClaims struct {
	oidc.IDTokenClaims
	CustomClaims
}

func (*RefreshClaims) GetAudience added in v0.34.0

func (r *RefreshClaims) GetAudience() (jwt.ClaimStrings, error)

func (*RefreshClaims) GetExpirationTime added in v0.34.0

func (r *RefreshClaims) GetExpirationTime() (*jwt.NumericDate, error)

func (*RefreshClaims) GetIssuedAt added in v0.34.0

func (r *RefreshClaims) GetIssuedAt() (*jwt.NumericDate, error)

func (*RefreshClaims) GetIssuer added in v0.34.0

func (r *RefreshClaims) GetIssuer() (string, error)

func (*RefreshClaims) GetNotBefore added in v0.34.0

func (r *RefreshClaims) GetNotBefore() (*jwt.NumericDate, error)

func (*RefreshClaims) GetSubject added in v0.34.0

func (r *RefreshClaims) GetSubject() (string, error)

func (*RefreshClaims) MarshalJSON added in v0.34.0

func (c *RefreshClaims) MarshalJSON() ([]byte, error)

func (*RefreshClaims) UnmarshalJSON added in v0.34.0

func (c *RefreshClaims) UnmarshalJSON(data []byte) error

type RouterDataModel added in v0.34.0

type RouterDataModel struct {
	EventCache

	ConfigTypes     cmap.ConcurrentMap[string, *ConfigType]                        `json:"configTypes"`
	Configs         cmap.ConcurrentMap[string, *Config]                            `json:"configs"`
	Identities      cmap.ConcurrentMap[string, *Identity]                          `json:"identities"`
	Services        cmap.ConcurrentMap[string, *Service]                           `json:"services"`
	ServicePolicies cmap.ConcurrentMap[string, *ServicePolicy]                     `json:"servicePolicies"`
	PostureChecks   cmap.ConcurrentMap[string, *PostureCheck]                      `json:"postureChecks"`
	PublicKeys      cmap.ConcurrentMap[string, *edge_ctrl_pb.DataState_PublicKey]  `json:"publicKeys"`
	Revocations     cmap.ConcurrentMap[string, *edge_ctrl_pb.DataState_Revocation] `json:"revocations"`
	// contains filtered or unexported fields
}

RouterDataModel represents a sub-set of a controller's data model. Enough to validate an identities access to dial/bind a service through policies and posture checks. RouterDataModel can operate in two modes: sender (controller) and receiver (router). Sender mode allows a controller support an event cache that supports replays for routers connecting for the first time/after disconnects. Receive mode does not maintain an event cache and does not support replays. It instead is used as a reference data structure for authorization computations.

func NewBareRouterDataModel added in v1.3.0

func NewBareRouterDataModel() *RouterDataModel

NewBareRouterDataModel creates a new RouterDataModel that is expected to have no buffers, listeners or subscriptions

func NewReceiverRouterDataModel added in v0.34.0

func NewReceiverRouterDataModel(listenerBufferSize uint, closeNotify <-chan struct{}) *RouterDataModel

NewReceiverRouterDataModel creates a new RouterDataModel that does not store events. listenerBufferSize affects the buffer size of channels returned to listeners of the data model.

func NewReceiverRouterDataModelFromDataState added in v1.3.0

func NewReceiverRouterDataModelFromDataState(dataState *edge_ctrl_pb.DataState, listenerBufferSize uint, closeNotify <-chan struct{}) *RouterDataModel

NewReceiverRouterDataModelFromDataState creates a new RouterDataModel that does not store events. listenerBufferSize affects the buffer size of channels returned to listeners of the data model.

func NewReceiverRouterDataModelFromExisting added in v1.3.0

func NewReceiverRouterDataModelFromExisting(existing *RouterDataModel, listenerBufferSize uint, closeNotify <-chan struct{}) *RouterDataModel

NewReceiverRouterDataModel creates a new RouterDataModel that does not store events. listenerBufferSize affects the buffer size of channels returned to listeners of the data model.

func NewReceiverRouterDataModelFromFile added in v0.34.0

func NewReceiverRouterDataModelFromFile(path string, listenerBufferSize uint, closeNotify <-chan struct{}) (*RouterDataModel, error)

NewReceiverRouterDataModelFromFile creates a new RouterDataModel that does not store events and is initialized from a file backup. listenerBufferSize affects the buffer size of channels returned to listeners of the data model.

func NewSenderRouterDataModel added in v0.34.0

func NewSenderRouterDataModel(logSize uint64, listenerBufferSize uint) *RouterDataModel

NewSenderRouterDataModel creates a new RouterDataModel that will store events in a circular buffer of logSize. listenerBufferSize affects the buffer size of channels returned to listeners of the data model.

func (*RouterDataModel) ApplyChangeSet added in v1.1.1

func (rdm *RouterDataModel) ApplyChangeSet(change *edge_ctrl_pb.DataState_ChangeSet)

ApplyChangeSet applies the given even to the router data model.

func (*RouterDataModel) Diff added in v1.3.0

func (rdm *RouterDataModel) Diff(o *RouterDataModel, sink DiffSink)

func (*RouterDataModel) GetDataState added in v0.34.0

func (rdm *RouterDataModel) GetDataState() *edge_ctrl_pb.DataState

func (*RouterDataModel) GetEntityCounts added in v1.3.0

func (rdm *RouterDataModel) GetEntityCounts() map[string]uint32

func (*RouterDataModel) GetPublicKeys added in v0.34.2

func (rdm *RouterDataModel) GetPublicKeys() map[string]crypto.PublicKey

func (*RouterDataModel) GetServiceAccessPolicies added in v0.34.0

func (rdm *RouterDataModel) GetServiceAccessPolicies(identityId string, serviceId string, policyType edge_ctrl_pb.PolicyType) (*AccessPolicies, error)

GetServiceAccessPolicies returns an AccessPolicies instance for an identity attempting to access a service.

func (*RouterDataModel) Handle added in v0.34.0

func (rdm *RouterDataModel) Handle(index uint64, event *edge_ctrl_pb.DataState_Event) bool

func (*RouterDataModel) HandleConfigEvent added in v1.1.8

func (rdm *RouterDataModel) HandleConfigEvent(index uint64, event *edge_ctrl_pb.DataState_Event, model *edge_ctrl_pb.DataState_Event_Config)

HandleConfigEvent will apply the delta event to the router data model. It is not restricted by index calculations. Use ApplyConfigEvent for event logged event handling. This method is generally meant for bulk loading of data during startup.

func (*RouterDataModel) HandleConfigTypeEvent added in v1.1.8

func (rdm *RouterDataModel) HandleConfigTypeEvent(index uint64, event *edge_ctrl_pb.DataState_Event, model *edge_ctrl_pb.DataState_Event_ConfigType)

HandleConfigTypeEvent will apply the delta event to the router data model. It is not restricted by index calculations. Use ApplyConfigTypeEvent for event logged event handling. This method is generally meant for bulk loading of data during startup.

func (*RouterDataModel) HandleIdentityEvent added in v0.34.0

func (rdm *RouterDataModel) HandleIdentityEvent(index uint64, event *edge_ctrl_pb.DataState_Event, model *edge_ctrl_pb.DataState_Event_Identity)

HandleIdentityEvent will apply the delta event to the router data model. It is not restricted by index calculations. Use ApplyIdentityEvent for event logged event handling. This method is generally meant for bulk loading of data during startup.

func (*RouterDataModel) HandlePostureCheckEvent added in v0.34.0

func (rdm *RouterDataModel) HandlePostureCheckEvent(index uint64, event *edge_ctrl_pb.DataState_Event, model *edge_ctrl_pb.DataState_Event_PostureCheck)

HandlePostureCheckEvent will apply the delta event to the router data model. It is not restricted by index calculations. Use ApplyPostureCheckEvent for event logged event handling. This method is generally meant for bulk loading of data during startup.

func (*RouterDataModel) HandlePublicKeyEvent added in v0.34.0

func (rdm *RouterDataModel) HandlePublicKeyEvent(event *edge_ctrl_pb.DataState_Event, model *edge_ctrl_pb.DataState_Event_PublicKey)

HandlePublicKeyEvent will apply the delta event to the router data model. It is not restricted by index calculations. Use ApplyPublicKeyEvent for event logged event handling. This method is generally meant for bulk loading of data during startup.

func (*RouterDataModel) HandleRevocationEvent added in v0.34.0

func (rdm *RouterDataModel) HandleRevocationEvent(event *edge_ctrl_pb.DataState_Event, model *edge_ctrl_pb.DataState_Event_Revocation)

HandleRevocationEvent will apply the delta event to the router data model. It is not restricted by index calculations. Use ApplyRevocationEvent for event logged event handling. This method is generally meant for bulk loading of data during startup.

func (*RouterDataModel) HandleServiceEvent added in v0.34.0

func (rdm *RouterDataModel) HandleServiceEvent(index uint64, event *edge_ctrl_pb.DataState_Event, model *edge_ctrl_pb.DataState_Event_Service)

HandleServiceEvent will apply the delta event to the router data model. It is not restricted by index calculations. Use ApplyServiceEvent for event logged event handling. This method is generally meant for bulk loading of data during startup.

func (*RouterDataModel) HandleServicePolicyChange added in v1.1.1

func (rdm *RouterDataModel) HandleServicePolicyChange(index uint64, model *edge_ctrl_pb.DataState_ServicePolicyChange)

func (*RouterDataModel) HandleServicePolicyEvent added in v0.34.0

func (rdm *RouterDataModel) HandleServicePolicyEvent(event *edge_ctrl_pb.DataState_Event, model *edge_ctrl_pb.DataState_Event_ServicePolicy)

HandleServicePolicyEvent will apply the delta event to the router data model. It is not restricted by index calculations. Use ApplyServicePolicyEvent for event logged event handling. This method is generally meant for bulk loading of data during startup.

func (*RouterDataModel) InheritSubscribers added in v1.1.8

func (rdm *RouterDataModel) InheritSubscribers(other *RouterDataModel)

func (*RouterDataModel) NewListener added in v0.34.0

func (rdm *RouterDataModel) NewListener() <-chan *edge_ctrl_pb.DataState_ChangeSet

NewListener returns a channel that will receive the events applied to this data model.

func (*RouterDataModel) Save added in v0.34.0

func (rdm *RouterDataModel) Save(path string)

func (*RouterDataModel) Stop added in v1.1.8

func (rdm *RouterDataModel) Stop()

func (*RouterDataModel) SubscribeToIdentityChanges added in v1.1.8

func (rdm *RouterDataModel) SubscribeToIdentityChanges(identityId string, subscriber IdentityEventSubscriber, isRouterIdentity bool) error

func (*RouterDataModel) SyncAllSubscribers added in v1.1.8

func (rdm *RouterDataModel) SyncAllSubscribers()

func (*RouterDataModel) Validate added in v1.3.0

func (rdm *RouterDataModel) Validate(correct *RouterDataModel, sink DiffSink)

type RouterDataModelConfig added in v1.3.0

type RouterDataModelConfig struct {
	Enabled            bool
	LogSize            uint64
	ListenerBufferSize uint
}

RouterDataModelConfig contains the configuration values for a RouterDataModel

type Service added in v1.1.8

type Service struct {
	*DataStateService
	// contains filtered or unexported fields
}

type ServiceAccessClaims added in v0.34.0

type ServiceAccessClaims struct {
	jwt.RegisteredClaims
	ApiSessionId string `json:"z_asid"`
	IdentityId   string `json:"z_iid"`
	TokenType    string `json:"z_t"`
	Type         string `json:"z_st"`
}

func (*ServiceAccessClaims) HasAudience added in v0.34.0

func (c *ServiceAccessClaims) HasAudience(targetAud string) bool

type ServiceEventType added in v1.1.8

type ServiceEventType byte

type ServicePolicy added in v1.1.1

type ServicePolicy struct {
	*DataStateServicePolicy
	Services      cmap.ConcurrentMap[string, struct{}] `json:"services"`
	PostureChecks cmap.ConcurrentMap[string, struct{}] `json:"postureChecks"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL