config

package
v2.9.3+incompatible Latest Latest
Warning

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

Go to latest
Published: Feb 2, 2020 License: MPL-2.0 Imports: 14 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	Default = Config{
		ListenPort:     8080,
		Secret:         "352d20ee67be67f6340b4c0605b044b7",
		TemplatePath:   "templates",
		MiddlewarePath: "middleware",
		AppPath:        "apps/",
		Storage: StorageOptionsConf{
			Type:    "redis",
			Host:    "localhost",
			MaxIdle: 100,
			Port:    6379,
		},
		AnalyticsConfig: AnalyticsConfigConfig{
			IgnoredIPs: make([]string, 0),
		},
		DnsCache: DnsCacheConfig{
			Enabled:                   false,
			TTL:                       dnsCacheDefaultTtl,
			CheckInterval:             dnsCacheDefaultCheckInterval,
			MultipleIPsHandleStrategy: NoCacheStrategy,
		},
	}
)

Functions

func Load

func Load(paths []string, conf *Config) error

Load will load a configuration file, trying each of the paths given and using the first one that is a regular file and can be opened.

If none exists, a default config will be written to the first path in the list.

An error will be returned only if any of the paths existed but was not a valid config file.

func SetGlobal

func SetGlobal(conf Config)

func WriteConf

func WriteConf(path string, conf *Config) error

func WriteDefault

func WriteDefault(path string, conf *Config) error

writeDefault will set conf to the default config and write it to disk in path, if the path is non-empty.

Types

type AnalyticsConfigConfig

type AnalyticsConfigConfig struct {
	Type                    string              `json:"type"`
	IgnoredIPs              []string            `json:"ignored_ips"`
	EnableDetailedRecording bool                `json:"enable_detailed_recording"`
	EnableGeoIP             bool                `json:"enable_geo_ip"`
	GeoIPDBLocation         string              `json:"geo_ip_db_path"`
	NormaliseUrls           NormalisedURLConfig `json:"normalise_urls"`
	PoolSize                int                 `json:"pool_size"`
	RecordsBufferSize       uint64              `json:"records_buffer_size"`
	StorageExpirationTime   int                 `json:"storage_expiration_time"`
	// contains filtered or unexported fields
}

type AuthOverrideConf

type AuthOverrideConf struct {
	ForceAuthProvider    bool                       `json:"force_auth_provider"`
	AuthProvider         apidef.AuthProviderMeta    `json:"auth_provider"`
	ForceSessionProvider bool                       `json:"force_session_provider"`
	SessionProvider      apidef.SessionProviderMeta `json:"session_provider"`
}

type CertData

type CertData struct {
	Name     string `json:"domain_name"`
	CertFile string `json:"cert_file"`
	KeyFile  string `json:"key_file"`
}

type CertificatesConfig

type CertificatesConfig struct {
	API        []string          `json:"apis"`
	Upstream   map[string]string `json:"upstream"`
	ControlAPI []string          `json:"control_api"`
	Dashboard  []string          `json:"dashboard_api"`
	MDCB       []string          `json:"mdcb_api"`
}

type CoProcessConfig

type CoProcessConfig struct {
	EnableCoProcess     bool   `json:"enable_coprocess"`
	CoProcessGRPCServer string `json:"coprocess_grpc_server"`
	GRPCRecvMaxSize     int    `json:"grpc_recv_max_size"`
	GRPCSendMaxSize     int    `json:"grpc_send_max_size"`
	PythonPathPrefix    string `json:"python_path_prefix"`
	PythonVersion       string `json:"python_version"`
}

type Config

type Config struct {
	// OriginalPath is the path to the config file that was read. If
	// none was found, it's the path to the default config file that
	// was written.
	OriginalPath string `json:"-"`

	HostName                  string                  `json:"hostname"`
	ListenAddress             string                  `json:"listen_address"`
	ListenPort                int                     `json:"listen_port"`
	ControlAPIHostname        string                  `json:"control_api_hostname"`
	ControlAPIPort            int                     `json:"control_api_port"`
	Secret                    string                  `json:"secret"`
	NodeSecret                string                  `json:"node_secret"`
	PIDFileLocation           string                  `json:"pid_file_location"`
	AllowInsecureConfigs      bool                    `json:"allow_insecure_configs"`
	PublicKeyPath             string                  `json:"public_key_path"`
	AllowRemoteConfig         bool                    `bson:"allow_remote_config" json:"allow_remote_config"`
	Security                  SecurityConfig          `json:"security"`
	HttpServerOptions         HttpServerOptionsConfig `json:"http_server_options"`
	ReloadWaitTime            int                     `bson:"reload_wait_time" json:"reload_wait_time"`
	VersionHeader             string                  `json:"version_header"`
	UseAsyncSessionWrite      bool                    `json:"optimisations_use_async_session_write"`
	SuppressRedisSignalReload bool                    `json:"suppress_redis_signal_reload"`

	// Gateway Security Policies
	HashKeys                bool           `json:"hash_keys"`
	HashKeyFunction         string         `json:"hash_key_function"`
	EnableHashedKeysListing bool           `json:"enable_hashed_keys_listing"`
	MinTokenLength          int            `json:"min_token_length"`
	EnableAPISegregation    bool           `json:"enable_api_segregation"`
	TemplatePath            string         `json:"template_path"`
	Policies                PoliciesConfig `json:"policies"`
	DisablePortWhiteList    bool           `json:"disable_ports_whitelist"`
	// Defines the ports that will be available for the api services to bind to.
	// This is a map of protocol to PortWhiteList. This allows per protocol
	// configurations.
	PortWhiteList map[string]PortWhiteList `json:"ports_whitelist"`

	// CE Configurations
	AppPath string `json:"app_path"`

	// Dashboard Configurations
	UseDBAppConfigs          bool                   `json:"use_db_app_configs"`
	DBAppConfOptions         DBAppConfOptionsConfig `json:"db_app_conf_options"`
	Storage                  StorageOptionsConf     `json:"storage"`
	DisableDashboardZeroConf bool                   `json:"disable_dashboard_zeroconf"`

	// Slave Configurations
	SlaveOptions   SlaveOptionsConfig `json:"slave_options"`
	ManagementNode bool               `json:"management_node"`
	AuthOverride   AuthOverrideConf   `json:"auth_override"`

	// Rate Limiting Strategy
	EnableNonTransactionalRateLimiter bool    `json:"enable_non_transactional_rate_limiter"`
	EnableSentinelRateLimiter         bool    `json:"enable_sentinel_rate_limiter"`
	EnableRedisRollingLimiter         bool    `json:"enable_redis_rolling_limiter"`
	DRLNotificationFrequency          int     `json:"drl_notification_frequency"`
	DRLThreshold                      float64 `json:"drl_threshold"`

	// Organization configurations
	EnforceOrgDataAge               bool          `json:"enforce_org_data_age"`
	EnforceOrgDataDetailLogging     bool          `json:"enforce_org_data_detail_logging"`
	EnforceOrgQuotas                bool          `json:"enforce_org_quotas"`
	ExperimentalProcessOrgOffThread bool          `json:"experimental_process_org_off_thread"`
	Monitor                         MonitorConfig `json:"monitor"`

	// Client-Gateway Configuration
	MaxIdleConns         int   `bson:"max_idle_connections" json:"max_idle_connections"`
	MaxIdleConnsPerHost  int   `bson:"max_idle_connections_per_host" json:"max_idle_connections_per_host"`
	MaxConnTime          int64 `json:"max_conn_time"`
	CloseIdleConnections bool  `json:"close_idle_connections"`
	CloseConnections     bool  `json:"close_connections"`
	EnableCustomDomains  bool  `json:"enable_custom_domains"`
	// If AllowMasterKeys is set to true, session objects (key definitions) that do not have explicit access rights set
	// will be allowed by Tyk. This means that keys that are created have access to ALL APIs, which in many cases is
	// unwanted behaviour unless you are sure about what you are doing.
	AllowMasterKeys bool `json:"allow_master_keys"`

	// Gateway-Service Configuration
	ServiceDiscovery              ServiceDiscoveryConf `json:"service_discovery"`
	ProxySSLInsecureSkipVerify    bool                 `json:"proxy_ssl_insecure_skip_verify"`
	ProxyEnableHttp2              bool                 `json:"proxy_enable_http2"`
	ProxySSLMinVersion            uint16               `json:"proxy_ssl_min_version"`
	ProxySSLCipherSuites          []string             `json:"proxy_ssl_ciphers"`
	ProxyDefaultTimeout           float64              `json:"proxy_default_timeout"`
	ProxySSLDisableRenegotiation  bool                 `json:"proxy_ssl_disable_renegotiation"`
	ProxyCloseConnections         bool                 `json:"proxy_close_connections"`
	UptimeTests                   UptimeTestsConfig    `json:"uptime_tests"`
	HealthCheck                   HealthCheckConfig    `json:"health_check"`
	OauthRefreshExpire            int64                `json:"oauth_refresh_token_expire"`
	OauthTokenExpire              int32                `json:"oauth_token_expire"`
	OauthTokenExpiredRetainPeriod int32                `json:"oauth_token_expired_retain_period"`
	OauthRedirectUriSeparator     string               `json:"oauth_redirect_uri_separator"`
	OauthErrorStatusCode          int                  `json:"oauth_error_status_code"`
	EnableKeyLogging              bool                 `json:"enable_key_logging"`
	SSLForceCommonNameCheck       bool                 `json:"ssl_force_common_name_check"`

	// Proxy analytics configuration
	EnableAnalytics bool                  `json:"enable_analytics"`
	AnalyticsConfig AnalyticsConfigConfig `json:"analytics_config"`

	// Cache
	DnsCache                 DnsCacheConfig        `json:"dns_cache"`
	DisableRegexpCache       bool                  `json:"disable_regexp_cache"`
	RegexpCacheExpire        int32                 `json:"regexp_cache_expire"`
	LocalSessionCache        LocalSessionCacheConf `json:"local_session_cache"`
	EnableSeperateCacheStore bool                  `json:"enable_separate_cache_store"`
	CacheStorage             StorageOptionsConf    `json:"cache_storage"`

	// Middleware/Plugin Configuration
	EnableBundleDownloader  bool            `bson:"enable_bundle_downloader" json:"enable_bundle_downloader"`
	BundleBaseURL           string          `bson:"bundle_base_url" json:"bundle_base_url"`
	EnableJSVM              bool            `json:"enable_jsvm"`
	JSVMTimeout             int             `json:"jsvm_timeout"`
	DisableVirtualPathBlobs bool            `json:"disable_virtual_path_blobs"`
	TykJSPath               string          `json:"tyk_js_path"`
	MiddlewarePath          string          `json:"middleware_path"`
	CoProcessOptions        CoProcessConfig `json:"coprocess_options"`

	// Monitoring, Logging & Profiling
	LogLevel                string         `json:"log_level"`
	HealthCheckEndpointName string         `json:"health_check_endpoint_name"`
	Tracer                  Tracer         `json:"tracing"`
	NewRelic                NewRelicConfig `json:"newrelic"`
	HTTPProfile             bool           `json:"enable_http_profiler"`
	UseRedisLog             bool           `json:"use_redis_log"`
	SentryCode              string         `json:"sentry_code"`
	UseSentry               bool           `json:"use_sentry"`
	UseSyslog               bool           `json:"use_syslog"`
	UseGraylog              bool           `json:"use_graylog"`
	UseLogstash             bool           `json:"use_logstash"`
	GraylogNetworkAddr      string         `json:"graylog_network_addr"`
	LogstashNetworkAddr     string         `json:"logstash_network_addr"`
	SyslogTransport         string         `json:"syslog_transport"`
	LogstashTransport       string         `json:"logstash_transport"`
	SyslogNetworkAddr       string         `json:"syslog_network_addr"`
	StatsdConnectionString  string         `json:"statsd_connection_string"`
	StatsdPrefix            string         `json:"statsd_prefix"`

	// Event System
	EventHandlers        apidef.EventHandlerMetaConfig         `json:"event_handlers"`
	EventTriggers        map[apidef.TykEvent][]TykEventHandler `json:"event_trigers_defunct"`  // Deprecated: Config.GetEventTriggers instead.
	EventTriggersDefunct map[apidef.TykEvent][]TykEventHandler `json:"event_triggers_defunct"` // Deprecated: Config.GetEventTriggers instead.

	// TODO: These config options are not documented - What do they do?
	SessionUpdatePoolSize          int   `json:"session_update_pool_size"`
	SessionUpdateBufferSize        int   `json:"session_update_buffer_size"`
	SupressDefaultOrgStore         bool  `json:"suppress_default_org_store"`
	LegacyEnableAllowanceCountdown bool  `bson:"legacy_enable_allowance_countdown" json:"legacy_enable_allowance_countdown"`
	GlobalSessionLifetime          int64 `bson:"global_session_lifetime" json:"global_session_lifetime"`
	ForceGlobalSessionLifetime     bool  `bson:"force_global_session_lifetime" json:"force_global_session_lifetime"`
	HideGeneratorHeader            bool  `json:"hide_generator_header"`
	KV                             struct {
		Consul ConsulConfig `json:"consul"`
		Vault  VaultConfig  `json:"vault"`
	} `json:"kv"`

	// Secrets are key-value pairs that can be accessed in the dashboard via "secrets://"
	Secrets map[string]string `json:"secrets"`
}

Config is the configuration object used by tyk to set up various parameters.

func Global

func Global() Config

func (Config) GetEventTriggers

func (c Config) GetEventTriggers() map[apidef.TykEvent][]TykEventHandler

GetEventTriggers returns event triggers. There was a typo in the json tag. To maintain backward compatibility, this solution is chosen.

func (*Config) LoadIgnoredIPs

func (c *Config) LoadIgnoredIPs()

func (*Config) SetEventTriggers

func (c *Config) SetEventTriggers(eventTriggers map[apidef.TykEvent][]TykEventHandler)

func (*Config) StoreAnalytics

func (c *Config) StoreAnalytics(ip string) bool

type ConsulConfig

type ConsulConfig struct {
	// Address is the address of the Consul server
	Address string `json:"address"`

	// Scheme is the URI scheme for the Consul server
	Scheme string `json:"scheme"`

	// Datacenter to use. If not provided, the default agent datacenter is used.
	Datacenter string `json:"datacenter"`

	// HttpAuth is the auth info to use for http access.
	HttpAuth struct {
		// Username to use for HTTP Basic Authentication
		Username string `json:"username"`

		// Password to use for HTTP Basic Authentication
		Password string `json:"password"`
	} `json:"http_auth"`

	// WaitTime limits how long a Watch will block. If not provided,
	// the agent default values will be used.
	WaitTime time.Duration `json:"wait_time"`

	// Token is used to provide a per-request ACL token
	// which overrides the agent's default token.
	Token string `json:"token"`

	TLSConfig struct {
		Address string `json:"address"`

		CAFile string `json:"ca_file"`

		CAPath string `json:"ca_path"`

		CertFile string `json:"cert_file"`

		KeyFile string `json:"key_file"`

		InsecureSkipVerify bool `json:"insecure_skip_verify"`
	} `json:"tls_config"`
}

ConsulConfig is used to configure the creation of a client This is a stripped down version of the Config struct in consul's API client

type DBAppConfOptionsConfig

type DBAppConfOptionsConfig struct {
	ConnectionString string   `json:"connection_string"`
	NodeIsSegmented  bool     `json:"node_is_segmented"`
	Tags             []string `json:"tags"`
}

type DnsCacheConfig

type DnsCacheConfig struct {
	Enabled                   bool              `json:"enabled"`
	TTL                       int64             `json:"ttl"`
	CheckInterval             int64             `json:"-" ignored:"true"` //controls cache cleanup interval. By convention shouldn't be exposed to config or env_variable_setup
	MultipleIPsHandleStrategy IPsHandleStrategy `json:"multiple_ips_handle_strategy"`
}

type EventMessage

type EventMessage struct {
	Type      apidef.TykEvent
	Meta      interface{}
	TimeStamp string
}

EventMessage is a standard form to send event data to handlers

type HealthCheckConfig

type HealthCheckConfig struct {
	EnableHealthChecks      bool  `json:"enable_health_checks"`
	HealthCheckValueTimeout int64 `json:"health_check_value_timeouts"`
}

type HttpServerOptionsConfig

type HttpServerOptionsConfig struct {
	OverrideDefaults       bool       `json:"override_defaults"`
	ReadTimeout            int        `json:"read_timeout"`
	WriteTimeout           int        `json:"write_timeout"`
	UseSSL                 bool       `json:"use_ssl"`
	UseLE_SSL              bool       `json:"use_ssl_le"`
	EnableHttp2            bool       `json:"enable_http2"`
	SSLInsecureSkipVerify  bool       `json:"ssl_insecure_skip_verify"`
	EnableWebSockets       bool       `json:"enable_websockets"`
	Certificates           []CertData `json:"certificates"`
	SSLCertificates        []string   `json:"ssl_certificates"`
	ServerName             string     `json:"server_name"`
	MinVersion             uint16     `json:"min_version"`
	FlushInterval          int        `json:"flush_interval"`
	SkipURLCleaning        bool       `json:"skip_url_cleaning"`
	SkipTargetPathEscaping bool       `json:"skip_target_path_escaping"`
	Ciphers                []string   `json:"ssl_ciphers"`
}

type IPsHandleStrategy

type IPsHandleStrategy string
const (
	PickFirstStrategy IPsHandleStrategy = "pick_first"
	RandomStrategy    IPsHandleStrategy = "random"
	NoCacheStrategy   IPsHandleStrategy = "no_cache"

	DefaultDashPolicySource     = "service"
	DefaultDashPolicyRecordName = "tyk_policies"
)

type LocalSessionCacheConf

type LocalSessionCacheConf struct {
	DisableCacheSessionState bool `json:"disable_cached_session_state"`
	CachedSessionTimeout     int  `json:"cached_session_timeout"`
	CacheSessionEviction     int  `json:"cached_session_eviction"`
}

type MonitorConfig

type MonitorConfig struct {
	EnableTriggerMonitors bool               `json:"enable_trigger_monitors"`
	Config                WebHookHandlerConf `json:"configuration"`
	GlobalTriggerLimit    float64            `json:"global_trigger_limit"`
	MonitorUserKeys       bool               `json:"monitor_user_keys"`
	MonitorOrgKeys        bool               `json:"monitor_org_keys"`
}

type NewRelicConfig

type NewRelicConfig struct {
	AppName    string `json:"app_name"`
	LicenseKey string `json:"license_key"`
}

type NormaliseURLPatterns

type NormaliseURLPatterns struct {
	UUIDs  *regexp.Regexp
	IDs    *regexp.Regexp
	Custom []*regexp.Regexp
}

type NormalisedURLConfig

type NormalisedURLConfig struct {
	Enabled            bool                 `json:"enabled"`
	NormaliseUUIDs     bool                 `json:"normalise_uuids"`
	NormaliseNumbers   bool                 `json:"normalise_numbers"`
	Custom             []string             `json:"custom_patterns"`
	CompiledPatternSet NormaliseURLPatterns `json:"-"` // see analytics.go
}

type PoliciesConfig

type PoliciesConfig struct {
	PolicySource           string `json:"policy_source"`
	PolicyConnectionString string `json:"policy_connection_string"`
	PolicyRecordName       string `json:"policy_record_name"`
	AllowExplicitPolicyID  bool   `json:"allow_explicit_policy_id"`
}

type PortRange

type PortRange struct {
	From int `json:"from"`
	To   int `json:"to"`
}

PortRange defines a range of ports inclusively.

func (PortRange) Match

func (r PortRange) Match(port int) bool

Match returns true if port is within the range

type PortWhiteList

type PortWhiteList struct {
	Ranges []PortRange `json:"ranges,omitempty"`
	Ports  []int       `json:"ports,omitempty"`
}

PortWhiteList defines ports that will be allowed by the gateway.

func (PortWhiteList) Match

func (p PortWhiteList) Match(port int) bool

Match returns true if port is acceptable from the PortWhiteList.

type SecurityConfig

type SecurityConfig struct {
	PrivateCertificateEncodingSecret string             `json:"private_certificate_encoding_secret"`
	ControlAPIUseMutualTLS           bool               `json:"control_api_use_mutual_tls"`
	PinnedPublicKeys                 map[string]string  `json:"pinned_public_keys"`
	Certificates                     CertificatesConfig `json:"certificates"`
}

type ServiceDiscoveryConf

type ServiceDiscoveryConf struct {
	DefaultCacheTimeout int `json:"default_cache_timeout"`
}

type ServicePort

type ServicePort struct {
	Protocol string `json:"protocol"`
	Port     int    `json:"port"`
}

ServicePort defines a protocol and port on which a service can bind to

type SlaveOptionsConfig

type SlaveOptionsConfig struct {
	UseRPC                          bool   `json:"use_rpc"`
	UseSSL                          bool   `json:"use_ssl"`
	SSLInsecureSkipVerify           bool   `json:"ssl_insecure_skip_verify"`
	ConnectionString                string `json:"connection_string"`
	RPCKey                          string `json:"rpc_key"`
	APIKey                          string `json:"api_key"`
	EnableRPCCache                  bool   `json:"enable_rpc_cache"`
	BindToSlugsInsteadOfListenPaths bool   `json:"bind_to_slugs"`
	DisableKeySpaceSync             bool   `json:"disable_keyspace_sync"`
	GroupID                         string `json:"group_id"`
	CallTimeout                     int    `json:"call_timeout"`
	PingTimeout                     int    `json:"ping_timeout"`
	RPCPoolSize                     int    `json:"rpc_pool_size"`
}

type StorageOptionsConf

type StorageOptionsConf struct {
	Type                  string            `json:"type"`
	Host                  string            `json:"host"`
	Port                  int               `json:"port"`
	Hosts                 map[string]string `json:"hosts"` // Deprecated: Addrs instead.
	Addrs                 []string          `json:"addrs"`
	MasterName            string            `json:"master_name"`
	Username              string            `json:"username"`
	Password              string            `json:"password"`
	Database              int               `json:"database"`
	MaxIdle               int               `json:"optimisation_max_idle"`
	MaxActive             int               `json:"optimisation_max_active"`
	Timeout               int               `json:"timeout"`
	EnableCluster         bool              `json:"enable_cluster"`
	UseSSL                bool              `json:"use_ssl"`
	SSLInsecureSkipVerify bool              `json:"ssl_insecure_skip_verify"`
}

type Tracer

type Tracer struct {
	// The name of the tracer to initialize. For instance appdash, to use appdash
	// tracer
	Name string `json:"name"`

	// If true then this tracer will be activated and all tracing data will be sent
	// to this tracer.NoOp tracer is used otherwise which collects traces but
	// discard them.
	Enabled bool `json:"enabled"`

	// Key value pairs used to initialize the tracer. These are tracer specific,
	// each tracer requires different options to operate. Please see trace package
	// for options required by supported tracer implementation.
	Options map[string]interface{} `json:"options"`
}

type TykEventHandler

type TykEventHandler interface {
	Init(interface{}) error
	HandleEvent(EventMessage)
}

TykEventHandler defines an event handler, e.g. LogMessageEventHandler will handle an event by logging it to stdout.

type UptimeTestsConfig

type UptimeTestsConfig struct {
	Disable bool                    `json:"disable"`
	Config  UptimeTestsConfigDetail `json:"config"`
}

type UptimeTestsConfigDetail

type UptimeTestsConfigDetail struct {
	FailureTriggerSampleSize int  `json:"failure_trigger_sample_size"`
	TimeWait                 int  `json:"time_wait"`
	CheckerPoolSize          int  `json:"checker_pool_size"`
	EnableUptimeAnalytics    bool `json:"enable_uptime_analytics"`
}

type VaultConfig

type VaultConfig struct {
	// Address is the address of the Vault server. This should be a complete
	// URL such as "http://vault.example.com".
	Address string `json:"address"`

	// AgentAddress is the address of the local Vault agent. This should be a
	// complete URL such as "http://vault.example.com".
	AgentAddress string `json:"agent_address"`

	// MaxRetries controls the maximum number of times to retry when a vault
	// serer occurs
	MaxRetries int `json:"max_retries"`

	Timeout time.Duration `json:"timeout"`

	// Token is the vault root token
	Token string `json:"token"`

	// KVVersion is the version number of Vault. Usually defaults to 2
	KVVersion int `json:"kv_version"`
}

VaultConfig is used to configure the creation of a client This is a stripped down version of the Config struct in vault's API client

type WebHookHandlerConf

type WebHookHandlerConf struct {
	Method       string            `bson:"method" json:"method"`
	TargetPath   string            `bson:"target_path" json:"target_path"`
	TemplatePath string            `bson:"template_path" json:"template_path"`
	HeaderList   map[string]string `bson:"header_map" json:"header_map"`
	EventTimeout int64             `bson:"event_timeout" json:"event_timeout"`
}

Jump to

Keyboard shortcuts

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