spice

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Feb 15, 2018 License: MIT Imports: 17 Imported by: 0

README

go-spice GoDoc Go Report Card

Package spice attempts to implement a SPICE proxy. It can be used to proxy virt-viewer/remote-viewer traffic to destination qemu instances. Using this proxy over a HTML5 based web viewer has many advantages. One being, the native remote-viewer client can be used through this proxy. This allows (for example) USB redirection, sound playback and recording and clipboard sharing to function.

This package is mostly finished except for the below mentioned todo's. The API should be stable. Vendoring this package is still advised in any case.

TODO:

  • implement proper auth capability handling
  • implement SASL authentication (Not planned, but nice to have)

See example for an example including an Authenticator

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthContext

type AuthContext interface {
	LoadToken() string
	SaveToken(string)
	LoadAddress() string
	SaveAddress(string)
}

AuthContext is used to pass either a spiceAuthContext or a saslAuthContext to the Authenticator

type AuthSASLContext

type AuthSASLContext interface {
	AuthContext
	// contains filtered or unexported methods
}

AuthSASLContext is the interface for SASL authentication. This is not yet implemented

type AuthSpiceContext

type AuthSpiceContext interface {
	Token() (string, error)
	AuthContext
}

AuthSpiceContext is the interface for token based (Spice) authentication.

type Authenticator

type Authenticator interface {
	// Next starts the authentication procedure for the tenant connection
	// It should only ever return an error when there is a issue performing the
	// authentication. A non-existant user/token or a bad password/token is not
	// considered an error.
	// Errors result in a connection being dropped instantly, whereas
	// `accessGranted = false` results in the connection being dropped, after
	// an 'access denied' message is returned. `accessGranted = false` is also
	// not logged by the proxy, where an error is.
	Next(AuthContext) (accessGranted bool, computeDestination string, err error)

	// Method is used to retrieve the type of authentication this
	// Authenticator supports
	Method() red.AuthMethod

	// Init is called once during configuration and can be used to do any
	// initialisation this Authenticator might need. If an error is
	// returned, the Authenticator is not used.
	Init() error
}

Authenticator is the interface used for creating a tenant authentication It is used by the proxy to do two things:

  1. authenticate the user
  2. return the compute node to forward the tenant user to

When creating your own authentication you should probably use one-time tokens for the tenant authentication. Using a method based on the below sequence of events:

a) Tenant authenticates using token '123e4567:secretpw'
b) The Authenticator looks up the token '123e4567' in a shared store
   (kv store or database)
c) The value of token 123e4567 is an encrypted compute node computeAddress.
   Attempt to decrypt the computeAddress using 'secretpw'. If this results
   in a valid compute node computeAddress, the user is granted access, and
   de compute destination is set to the decrypted node computeAddress.
   In the same transaction, a new token+secret should be generated, and the
   old one destroyed

type Logger

type Logger interface {
	// Debug logs debugging messages.
	Debug(...interface{})
	// Info logs informational messages.
	Info(...interface{})
	// Error logs error messages.
	Error(...interface{})
	// WithFields creates a new Logger with the fields embedded
	WithFields(keyvals ...interface{}) Logger
	// WithError creates a new Logger with the error embedded
	WithError(err error) Logger
}

Logger is a logging adapter interface

func Adapt

func Adapt(l *logrus.Entry) Logger

Adapt creates a Logger backed from a logrus Entry.

type Option

type Option func(*Proxy) error

Option is a functional option handler for Server.

func WithAuthenticator

func WithAuthenticator(a Authenticator) Option

WithAuthenticator can be provided to implement custom authentication By default, "auth-less" no-op mode is enabled.

func WithDialer

func WithDialer(dial func(ctx context.Context, network, addr string) (net.Conn, error)) Option

WithDialer can be used to provide a custom dialer to reach compute nodes the network is always of type 'tcp' and the computeAddress is the compute node computeAddress that is return by an Authenticator.

func WithLogger

func WithLogger(log Logger) Option

WithLogger can be used to provide a custom logger. Defaults to a logrus implementation.

type Proxy

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

Proxy is the server object for this spice proxy.

Example
package main

import (
	"fmt"

	"github.com/jsimonetti/go-spice"
	"github.com/jsimonetti/go-spice/red"
	"github.com/sirupsen/logrus"
)

func main() {
	// create a new logger to be used for the proxy and the authenticator
	log := logrus.New()
	log.SetLevel(logrus.DebugLevel)

	// create a new instance of the sample authenticator
	authSpice := &AuthSpice{
		log: log.WithField("component", "authenticator"),
	}

	// create the proxy using the logger and authenticator
	logger := spice.Adapt(log.WithField("component", "proxy"))
	proxy, err := spice.New(spice.WithLogger(logger),
		spice.WithAuthenticator(authSpice))
	if err != nil {
		log.Fatalf("error: %s", err)
	}

	// start listening for tenant connections
	log.Fatal(proxy.ListenAndServe("tcp", "127.0.0.1:5901"))
}

// AuthSpice is an example implementation of a spice Authenticator
type AuthSpice struct {
	log *logrus.Entry

	computeMap map[string]string
}

// Next will check the supplied token and return authorisation information
func (a *AuthSpice) Next(c spice.AuthContext) (bool, string, error) {
	// convert the AuthContext into an AuthSpiceContext, since we do that
	var ctx spice.AuthSpiceContext
	var ok bool
	if ctx, ok = c.(spice.AuthSpiceContext); !ok {
		return false, "", fmt.Errorf("invalid auth method")
	}

	// retrieve the token sent by the tenant
	token, err := ctx.Token()
	if err != nil {
		return false, "", err
	}

	// is the previously saved token is set and matches the token
	// sent by the tenant we return the previously saved compute address
	if ctx.LoadToken() != "" && ctx.LoadToken() == token {
		a.log.Debug("LoadToken found and matches password")
		return true, ctx.LoadAddress(), nil
	}

	// find the compute node for this token
	if destination, ok := a.resolveComputeAddress(token); ok {
		a.log.Debugf("Ticket validated, compute node at %s", destination)
		// save the token and compute address into the context
		// so it can be saved into the session table by the proxy
		ctx.SaveToken(token)
		ctx.SaveAddress(destination)
		return true, ctx.LoadAddress(), nil
	}

	a.log.Warn("authentication failed")
	return false, "", nil
}

// Method returns the Spice auth method
func (a *AuthSpice) Method() red.AuthMethod {
	return red.AuthMethodSpice
}

// resolveComputeAddress is a custom function that checks the token and returns
// a compute node address
func (a *AuthSpice) resolveComputeAddress(token string) (string, bool) {
	// this is just an example, lookup your token somewhere and resolve it
	// to a compute node. When creating your own authentication you should
	// probably use one-time tokens for the tenant authentication.
	// Using a method based on the below sequence of events:
	//
	//  a) Tenant authenticates using token '123e4567:secretpw'
	//  b) The Authenticator looks up the token '123e4567' in a shared store
	//     (kv store or database)
	//  c) The value of token 123e4567 is an encrypted compute
	//     node computeAddress.
	//     Attempt to decrypt the computeAddress using 'secretpw'.
	//     If this results in a valid compute node computeAddress,
	//     the user is granted access, and de compute destination
	//     is set to the decrypted node computeAddress. In the same transaction,
	//     a new token+secret should be generated, and the old one destroyed
	if compute, ok := a.computeMap[token]; ok {
		a.log.Warn("bogus token check and compute node")
		return compute, true
	}
	return "", false
}

// Init initialises this authenticator
func (a *AuthSpice) Init() error {
	// fill in some compute nodes
	a.computeMap = map[string]string{
		"test":  "127.0.0.1:5900",
		"test2": "127.0.0.1:5902",
	}
	a.log.Debug("AuthSpice initialised")
	return nil
}
Output:

func New

func New(options ...Option) (*Proxy, error)

New returns a new *Proxy with the options applied

func (*Proxy) ListenAndServe

func (p *Proxy) ListenAndServe(network, addr string) error

ListenAndServe is used to create a listener and serve on it

func (*Proxy) Serve

func (p *Proxy) Serve(l net.Listener) error

Serve is used to serve connections from a listener

func (*Proxy) ServeConn

func (p *Proxy) ServeConn(tenant net.Conn) error

ServeConn is used to serve a single connection.

func (*Proxy) SetOption

func (p *Proxy) SetOption(option Option) error

SetOption runs a functional option against the server.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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