logical

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Mar 16, 2017 License: MPL-2.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// The operations below are called per path
	CreateOperation Operation = "create"
	ReadOperation             = "read"
	UpdateOperation           = "update"
	DeleteOperation           = "delete"
	ListOperation             = "list"
	HelpOperation             = "help"

	// The operations below are called globally, the path is less relevant.
	RevokeOperation   Operation = "revoke"
	RenewOperation              = "renew"
	RollbackOperation           = "rollback"
)
View Source
const (
	// HTTPContentType can be specified in the Data field of a Response
	// so that the HTTP front end can specify a custom Content-Type associated
	// with the HTTPRawBody. This can only be used for non-secrets, and should
	// be avoided unless absolutely necessary, such as implementing a specification.
	// The value must be a string.
	HTTPContentType = "http_content_type"

	// HTTPRawBody is the raw content of the HTTP body that goes with the HTTPContentType.
	// This can only be specified for non-secrets, and should should be similarly
	// avoided like the HTTPContentType. The value must be a byte slice.
	HTTPRawBody = "http_raw_body"

	// HTTPStatusCode is the response code of the HTTP body that goes with the HTTPContentType.
	// This can only be specified for non-secrets, and should should be similarly
	// avoided like the HTTPContentType. The value must be an integer.
	HTTPStatusCode = "http_status_code"
)

Variables

View Source
var (
	// ErrUnsupportedOperation is returned if the operation is not supported
	// by the logical backend.
	ErrUnsupportedOperation = errors.New("unsupported operation")

	// ErrUnsupportedPath is returned if the path is not supported
	// by the logical backend.
	ErrUnsupportedPath = errors.New("unsupported path")

	// ErrInvalidRequest is returned if the request is invalid
	ErrInvalidRequest = errors.New("invalid request")

	// ErrPermissionDenied is returned if the client is not authorized
	ErrPermissionDenied = errors.New("permission denied")
)
View Source
var ErrReadOnly = errors.New("Cannot write to readonly storage")

ErrReadOnly is returned when a backend does not support writing. This can be caused by a read-only replica or secondary cluster operation.

Functions

func AdjustErrorStatusCode added in v0.7.0

func AdjustErrorStatusCode(status *int, err error)

AdjustErrorStatusCode adjusts the status that will be sent in error conditions in a way that can be shared across http's respondError and other locations.

func ClearView added in v0.6.5

func ClearView(view ClearableView) error

ClearView is used to delete all the keys in a view

func CollectKeys added in v0.6.5

func CollectKeys(view ClearableView) ([]string, error)

CollectKeys is used to collect all the keys in a view

func RespondErrorCommon added in v0.7.0

func RespondErrorCommon(req *Request, resp *Response, err error) (int, error)

RespondErrorCommon pulls most of the functionality from http's respondErrorCommon and some of http's handleLogical and makes it available to both the http package and elsewhere.

func ScanView added in v0.6.5

func ScanView(view ClearableView, cb func(path string)) error

ScanView is used to scan all the keys in a view iteratively

func TestStorage

func TestStorage(t *testing.T, s Storage)

TestStorage is a helper that can be used from unit tests to verify the behavior of a Storage impl.

Types

type Auth

type Auth struct {
	LeaseOptions

	// InternalData is JSON-encodable data that is stored with the auth struct.
	// This will be sent back during a Renew/Revoke for storing internal data
	// used for those operations.
	InternalData map[string]interface{} `json:"internal_data" mapstructure:"internal_data" structs:"internal_data"`

	// DisplayName is a non-security sensitive identifier that is
	// applicable to this Auth. It is used for logging and prefixing
	// of dynamic secrets. For example, DisplayName may be "armon" for
	// the github credential backend. If the client token is used to
	// generate a SQL credential, the user may be "github-armon-uuid".
	// This is to help identify the source without using audit tables.
	DisplayName string `json:"display_name" mapstructure:"display_name" structs:"display_name"`

	// Policies is the list of policies that the authenticated user
	// is associated with.
	Policies []string `json:"policies" mapstructure:"policies" structs:"policies"`

	// Metadata is used to attach arbitrary string-type metadata to
	// an authenticated user. This metadata will be outputted into the
	// audit log.
	Metadata map[string]string `json:"metadata" mapstructure:"metadata" structs:"metadata"`

	// ClientToken is the token that is generated for the authentication.
	// This will be filled in by Vault core when an auth structure is
	// returned. Setting this manually will have no effect.
	ClientToken string `json:"client_token" mapstructure:"client_token" structs:"client_token"`

	// Accessor is the identifier for the ClientToken. This can be used
	// to perform management functionalities (especially revocation) when
	// ClientToken in the audit logs are obfuscated. Accessor can be used
	// to revoke a ClientToken and to lookup the capabilities of the ClientToken,
	// both without actually knowing the ClientToken.
	Accessor string `json:"accessor" mapstructure:"accessor" structs:"accessor"`

	// Period indicates that the token generated using this Auth object
	// should never expire. The token should be renewed within the duration
	// specified by this period.
	Period time.Duration `json:"period" mapstructure:"period" structs:"period"`

	// Number of allowed uses of the issued token
	NumUses int `json:"num_uses" mapstructure:"num_uses" structs:"num_uses"`
}

Auth is the resulting authentication information that is part of Response for credential backends.

func (*Auth) GoString

func (a *Auth) GoString() string

type Backend

type Backend interface {
	// HandleRequest is used to handle a request and generate a response.
	// The backends must check the operation type and handle appropriately.
	HandleRequest(*Request) (*Response, error)

	// SpecialPaths is a list of paths that are special in some way.
	// See PathType for the types of special paths. The key is the type
	// of the special path, and the value is a list of paths for this type.
	// This is not a regular expression but is an exact match. If the path
	// ends in '*' then it is a prefix-based match. The '*' can only appear
	// at the end.
	SpecialPaths() *Paths

	// System provides an interface to access certain system configuration
	// information, such as globally configured default and max lease TTLs.
	System() SystemView

	// HandleExistenceCheck is used to handle a request and generate a response
	// indicating whether the given path exists or not; this is used to
	// understand whether the request must have a Create or Update capability
	// ACL applied. The first bool indicates whether an existence check
	// function was found for the backend; the second indicates whether, if an
	// existence check function was found, the item exists or not.
	HandleExistenceCheck(*Request) (bool, bool, error)

	// Cleanup is invoked during an unmount of a backend to allow it to
	// handle any cleanup like connection closing or releasing of file handles.
	Cleanup()

	// Initialize is invoked after a backend is created. It is the place to run
	// any operations requiring storage; these should not be in the factory.
	Initialize() error

	// InvalidateKey may be invoked when an object is modified that belongs
	// to the backend. The backend can use this to clear any caches or reset
	// internal state as needed.
	InvalidateKey(key string)
}

Backend interface must be implemented to be "mountable" at a given path. Requests flow through a router which has various mount points that flow to a logical backend. The logic of each backend is flexible, and this is what allows materialized keys to function. There can be specialized logical backends for various upstreams (Consul, PostgreSQL, MySQL, etc) that can interact with remote APIs to generate keys dynamically. This interface also allows for a "procfs" like interaction, as internal state can be exposed by acting like a logical backend and being mounted.

type BackendConfig added in v0.2.0

type BackendConfig struct {
	// View should not be stored, and should only be used for initialization
	StorageView Storage

	// The backend should use this logger. The log should not contain any secrets.
	Logger log.Logger

	// System provides a view into a subset of safe system information that
	// is useful for backends, such as the default/max lease TTLs
	System SystemView

	// Config is the opaque user configuration provided when mounting
	Config map[string]string
}

BackendConfig is provided to the factory to initialize the backend

func TestBackendConfig added in v0.4.0

func TestBackendConfig() *BackendConfig

type ClearableView added in v0.6.5

type ClearableView interface {
	List(string) ([]string, error)
	Delete(string) error
}

type Connection

type Connection struct {
	// RemoteAddr is the network address that sent the request.
	RemoteAddr string `json:"remote_addr"`

	// ConnState is the TLS connection state if applicable.
	ConnState *tls.ConnectionState
}

Connection represents the connection information for a request. This is present on the Request structure for credential backends.

type Factory

type Factory func(*BackendConfig) (Backend, error)

Factory is the factory function to create a logical backend.

type HTTPAuth added in v0.6.0

type HTTPAuth struct {
	ClientToken   string            `json:"client_token"`
	Accessor      string            `json:"accessor"`
	Policies      []string          `json:"policies"`
	Metadata      map[string]string `json:"metadata"`
	LeaseDuration int               `json:"lease_duration"`
	Renewable     bool              `json:"renewable"`
}

type HTTPCodedError added in v0.3.0

type HTTPCodedError interface {
	Error() string
	Code() int
}

func CodedError added in v0.3.0

func CodedError(c int, s string) HTTPCodedError

type HTTPResponse added in v0.6.0

type HTTPResponse struct {
	RequestID     string                 `json:"request_id"`
	LeaseID       string                 `json:"lease_id"`
	Renewable     bool                   `json:"renewable"`
	LeaseDuration int                    `json:"lease_duration"`
	Data          map[string]interface{} `json:"data"`
	WrapInfo      *HTTPWrapInfo          `json:"wrap_info"`
	Warnings      []string               `json:"warnings"`
	Auth          *HTTPAuth              `json:"auth"`
}

func LogicalResponseToHTTPResponse added in v0.6.2

func LogicalResponseToHTTPResponse(input *Response) *HTTPResponse

This logic was pulled from the http package so that it can be used for encoding wrapped responses as well. It simply translates the logical request to an http response, with the values we want and omitting the values we don't.

type HTTPSysInjector added in v0.6.1

type HTTPSysInjector struct {
	Response *HTTPResponse
}

func (HTTPSysInjector) MarshalJSON added in v0.6.1

func (h HTTPSysInjector) MarshalJSON() ([]byte, error)

type HTTPWrapInfo added in v0.6.0

type HTTPWrapInfo struct {
	Token           string `json:"token"`
	TTL             int    `json:"ttl"`
	CreationTime    string `json:"creation_time"`
	WrappedAccessor string `json:"wrapped_accessor,omitempty"`
}

type InmemStorage

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

InmemStorage implements Storage and stores all data in memory.

func (*InmemStorage) Delete

func (s *InmemStorage) Delete(k string) error

func (*InmemStorage) Get

func (s *InmemStorage) Get(key string) (*StorageEntry, error)

func (*InmemStorage) List

func (s *InmemStorage) List(prefix string) ([]string, error)

func (*InmemStorage) Put

func (s *InmemStorage) Put(entry *StorageEntry) error

type LeaseOptions

type LeaseOptions struct {
	// Lease is the duration that this secret is valid for. Vault
	// will automatically revoke it after the duration.
	TTL time.Duration `json:"lease"`

	// Renewable, if true, means that this secret can be renewed.
	Renewable bool `json:"renewable"`

	// Increment will be the lease increment that the user requested.
	// This is only available on a Renew operation and has no effect
	// when returning a response.
	Increment time.Duration `json:"-"`

	// IssueTime is the time of issue for the original lease. This is
	// only available on a Renew operation and has no effect when returning
	// a response. It can be used to enforce maximum lease periods by
	// a logical backend.
	IssueTime time.Time `json:"-"`
}

LeaseOptions is an embeddable struct to capture common lease settings between a Secret and Auth

func (*LeaseOptions) ExpirationTime

func (l *LeaseOptions) ExpirationTime() time.Time

ExpirationTime computes the time until expiration including the grace period

func (*LeaseOptions) LeaseEnabled

func (l *LeaseOptions) LeaseEnabled() bool

LeaseEnabled checks if leasing is enabled

func (*LeaseOptions) LeaseTotal

func (l *LeaseOptions) LeaseTotal() time.Duration

LeaseTotal is the lease duration with a guard against a negative TTL

type Operation

type Operation string

Operation is an enum that is used to specify the type of request being made

type Paths

type Paths struct {
	// Root are the paths that require a root token to access
	Root []string

	// Unauthenticated are the paths that can be accessed without any auth.
	Unauthenticated []string

	// LocalStorage are paths (prefixes) that are local to this instance; this
	// indicates that these paths should not be replicated
	LocalStorage []string
}

Paths is the structure of special paths that is used for SpecialPaths.

type ReplicationCodedError added in v0.7.0

type ReplicationCodedError struct {
	Msg  string
	Code int
}

This is a new type declared to not cause potential compatibility problems if the logic around the HTTPCodedError interface changes; in particular for logical request paths it is basically ignored, and changing that behavior might cause unforseen issues.

func (*ReplicationCodedError) Error added in v0.7.0

func (r *ReplicationCodedError) Error() string

type Request

type Request struct {
	// Id is the uuid associated with each request
	ID string `json:"id" structs:"id" mapstructure:"id"`

	// If set, the name given to the replication secondary where this request
	// originated
	ReplicationCluster string `json:"replication_cluster" structs:"replication_cluster", mapstructure:"replication_cluster"`

	// Operation is the requested operation type
	Operation Operation `json:"operation" structs:"operation" mapstructure:"operation"`

	// Path is the part of the request path not consumed by the
	// routing. As an example, if the original request path is "prod/aws/foo"
	// and the AWS logical backend is mounted at "prod/aws/", then the
	// final path is "foo" since the mount prefix is trimmed.
	Path string `json:"path" structs:"path" mapstructure:"path"`

	// Request data is an opaque map that must have string keys.
	Data map[string]interface{} `json:"map" structs:"data" mapstructure:"data"`

	// Storage can be used to durably store and retrieve state.
	Storage Storage `json:"-"`

	// Secret will be non-nil only for Revoke and Renew operations
	// to represent the secret that was returned prior.
	Secret *Secret `json:"secret" structs:"secret" mapstructure:"secret"`

	// Auth will be non-nil only for Renew operations
	// to represent the auth that was returned prior.
	Auth *Auth `json:"auth" structs:"auth" mapstructure:"auth"`

	// Headers will contain the http headers from the request. This value will
	// be used in the audit broker to ensure we are auditing only the allowed
	// headers.
	Headers map[string][]string `json:"headers" structs:"headers" mapstructure:"headers"`

	// Connection will be non-nil only for credential providers to
	// inspect the connection information and potentially use it for
	// authentication/protection.
	Connection *Connection `json:"connection" structs:"connection" mapstructure:"connection"`

	// ClientToken is provided to the core so that the identity
	// can be verified and ACLs applied. This value is passed
	// through to the logical backends but after being salted and
	// hashed.
	ClientToken string `json:"client_token" structs:"client_token" mapstructure:"client_token"`

	// ClientTokenAccessor is provided to the core so that the it can get
	// logged as part of request audit logging.
	ClientTokenAccessor string `json:"client_token_accessor" structs:"client_token_accessor" mapstructure:"client_token_accessor"`

	// DisplayName is provided to the logical backend to help associate
	// dynamic secrets with the source entity. This is not a sensitive
	// name, but is useful for operators.
	DisplayName string `json:"display_name" structs:"display_name" mapstructure:"display_name"`

	// MountPoint is provided so that a logical backend can generate
	// paths relative to itself. The `Path` is effectively the client
	// request path with the MountPoint trimmed off.
	MountPoint string `json:"mount_point" structs:"mount_point" mapstructure:"mount_point"`

	// WrapInfo contains requested response wrapping parameters
	WrapInfo *RequestWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info"`

	// ClientTokenRemainingUses represents the allowed number of uses left on the
	// token supplied
	ClientTokenRemainingUses int `json:"client_token_remaining_uses" structs:"client_token_remaining_uses" mapstructure:"client_token_remaining_uses"`
	// contains filtered or unexported fields
}

Request is a struct that stores the parameters and context of a request being made to Vault. It is used to abstract the details of the higher level request protocol from the handlers.

func RenewAuthRequest

func RenewAuthRequest(
	path string, auth *Auth, data map[string]interface{}) *Request

RenewAuthRequest creates the structure of the renew request for an auth.

func RenewRequest

func RenewRequest(
	path string, secret *Secret, data map[string]interface{}) *Request

RenewRequest creates the structure of the renew request.

func RevokeRequest

func RevokeRequest(
	path string, secret *Secret, data map[string]interface{}) *Request

RevokeRequest creates the structure of the revoke request.

func RollbackRequest

func RollbackRequest(path string) *Request

RollbackRequest creates the structure of the revoke request.

func TestRequest

func TestRequest(t *testing.T, op Operation, path string) *Request

TestRequest is a helper to create a purely in-memory Request struct.

func (*Request) Get

func (r *Request) Get(key string) interface{}

Get returns a data field and guards for nil Data

func (*Request) GetString

func (r *Request) GetString(key string) string

GetString returns a data field as a string

func (*Request) GoString

func (r *Request) GoString() string

func (*Request) LastRemoteWAL added in v0.7.0

func (r *Request) LastRemoteWAL() uint64

func (*Request) SetLastRemoteWAL added in v0.7.0

func (r *Request) SetLastRemoteWAL(last uint64)

type RequestWrapInfo added in v0.6.5

type RequestWrapInfo struct {
	// Setting to non-zero specifies that the response should be wrapped.
	// Specifies the desired TTL of the wrapping token.
	TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"`

	// The format to use for the wrapped response; if not specified it's a bare
	// token
	Format string `json:"format" structs:"format" mapstructure:"format"`
}

RequestWrapInfo is a struct that stores information about desired response wrapping behavior

type Response

type Response struct {
	// Secret, if not nil, denotes that this response represents a secret.
	Secret *Secret `json:"secret" structs:"secret" mapstructure:"secret"`

	// Auth, if not nil, contains the authentication information for
	// this response. This is only checked and means something for
	// credential backends.
	Auth *Auth `json:"auth" structs:"auth" mapstructure:"auth"`

	// Response data is an opaque map that must have string keys. For
	// secrets, this data is sent down to the user as-is. To store internal
	// data that you don't want the user to see, store it in
	// Secret.InternalData.
	Data map[string]interface{} `json:"data" structs:"data" mapstructure:"data"`

	// Redirect is an HTTP URL to redirect to for further authentication.
	// This is only valid for credential backends. This will be blanked
	// for any logical backend and ignored.
	Redirect string `json:"redirect" structs:"redirect" mapstructure:"redirect"`

	// Information for wrapping the response in a cubbyhole
	WrapInfo *ResponseWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info"`
	// contains filtered or unexported fields
}

Response is a struct that stores the response of a request. It is used to abstract the details of the higher level request protocol.

func ErrorResponse

func ErrorResponse(text string) *Response

ErrorResponse is used to format an error response

func HTTPResponseToLogicalResponse added in v0.6.2

func HTTPResponseToLogicalResponse(input *HTTPResponse) *Response

func HelpResponse

func HelpResponse(text string, seeAlso []string) *Response

HelpResponse is used to format a help response

func ListResponse

func ListResponse(keys []string) *Response

ListResponse is used to format a response to a list operation.

func (*Response) AddWarning added in v0.4.0

func (r *Response) AddWarning(warning string)

AddWarning adds a warning into the response's warning list

func (*Response) ClearWarnings added in v0.4.0

func (r *Response) ClearWarnings()

ClearWarnings clears the response's warning list

func (*Response) CloneWarnings added in v0.6.0

func (r *Response) CloneWarnings(other *Response)

Copies the warnings from the other response to this one

func (*Response) Error added in v0.6.1

func (r *Response) Error() error

func (*Response) IsError

func (r *Response) IsError() bool

IsError returns true if this response seems to indicate an error.

func (*Response) Warnings added in v0.4.0

func (r *Response) Warnings() []string

Warnings returns the list of warnings set on the response

type ResponseWrapInfo added in v0.6.5

type ResponseWrapInfo struct {
	// Setting to non-zero specifies that the response should be wrapped.
	// Specifies the desired TTL of the wrapping token.
	TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"`

	// The token containing the wrapped response
	Token string `json:"token" structs:"token" mapstructure:"token"`

	// The creation time. This can be used with the TTL to figure out an
	// expected expiration.
	CreationTime time.Time `json:"creation_time" structs:"creation_time" mapstructure:"cration_time"`

	// If the contained response is the output of a token creation call, the
	// created token's accessor will be accessible here
	WrappedAccessor string `json:"wrapped_accessor" structs:"wrapped_accessor" mapstructure:"wrapped_accessor"`

	// The format to use. This doesn't get returned, it's only internal.
	Format string `json:"format" structs:"format" mapstructure:"format"`
}

type Secret

type Secret struct {
	LeaseOptions

	// InternalData is JSON-encodable data that is stored with the secret.
	// This will be sent back during a Renew/Revoke for storing internal data
	// used for those operations.
	InternalData map[string]interface{} `json:"internal_data"`

	// LeaseID is the ID returned to the user to manage this secret.
	// This is generated by Vault core. Any set value will be ignored.
	// For requests, this will always be blank.
	LeaseID string
}

Secret represents the secret part of a response.

func (*Secret) GoString

func (s *Secret) GoString() string

func (*Secret) Validate

func (s *Secret) Validate() error

type StaticSystemView added in v0.3.0

type StaticSystemView struct {
	DefaultLeaseTTLVal  time.Duration
	MaxLeaseTTLVal      time.Duration
	SudoPrivilegeVal    bool
	TaintedVal          bool
	CachingDisabledVal  bool
	Primary             bool
	ReplicationStateVal consts.ReplicationState
}

func TestSystemView added in v0.4.0

func TestSystemView() *StaticSystemView

func (StaticSystemView) CachingDisabled added in v0.6.0

func (d StaticSystemView) CachingDisabled() bool

func (StaticSystemView) DefaultLeaseTTL added in v0.3.0

func (d StaticSystemView) DefaultLeaseTTL() time.Duration

func (StaticSystemView) MaxLeaseTTL added in v0.3.0

func (d StaticSystemView) MaxLeaseTTL() time.Duration

func (StaticSystemView) ReplicationState added in v0.6.5

func (d StaticSystemView) ReplicationState() consts.ReplicationState

func (StaticSystemView) SudoPrivilege added in v0.3.0

func (d StaticSystemView) SudoPrivilege(path string, token string) bool

func (StaticSystemView) Tainted added in v0.5.0

func (d StaticSystemView) Tainted() bool

type StatusBadRequest added in v0.7.0

type StatusBadRequest struct {
	Err string
}

Struct to identify user input errors. This is helpful in responding the appropriate status codes to clients from the HTTP endpoints.

func (*StatusBadRequest) Error added in v0.7.0

func (s *StatusBadRequest) Error() string

Implementing error interface

type Storage

type Storage interface {
	List(prefix string) ([]string, error)
	Get(string) (*StorageEntry, error)
	Put(*StorageEntry) error
	Delete(string) error
}

Storage is the way that logical backends are able read/write data.

type StorageEntry

type StorageEntry struct {
	Key   string
	Value []byte
}

StorageEntry is the entry for an item in a Storage implementation.

func StorageEntryJSON

func StorageEntryJSON(k string, v interface{}) (*StorageEntry, error)

StorageEntryJSON creates a StorageEntry with a JSON-encoded value.

func (*StorageEntry) DecodeJSON

func (e *StorageEntry) DecodeJSON(out interface{}) error

DecodeJSON decodes the 'Value' present in StorageEntry.

type SystemView added in v0.3.0

type SystemView interface {
	// DefaultLeaseTTL returns the default lease TTL set in Vault configuration
	DefaultLeaseTTL() time.Duration

	// MaxLeaseTTL returns the max lease TTL set in Vault configuration; backend
	// authors should take care not to issue credentials that last longer than
	// this value, as Vault will revoke them
	MaxLeaseTTL() time.Duration

	// SudoPrivilege returns true if given path has sudo privileges
	// for the given client token
	SudoPrivilege(path string, token string) bool

	// Returns true if the mount is tainted. A mount is tainted if it is in the
	// process of being unmounted. This should only be used in special
	// circumstances; a primary use-case is as a guard in revocation functions.
	// If revocation of a backend's leases fails it can keep the unmounting
	// process from being successful. If the reason for this failure is not
	// relevant when the mount is tainted (for instance, saving a CRL to disk
	// when the stored CRL will be removed during the unmounting process
	// anyways), we can ignore the errors to allow unmounting to complete.
	Tainted() bool

	// Returns true if caching is disabled. If true, no caches should be used,
	// despite known slowdowns.
	CachingDisabled() bool

	// ReplicationState indicates the state of cluster replication
	ReplicationState() consts.ReplicationState
}

SystemView exposes system configuration information in a safe way for logical backends to consume

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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