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:
- authenticate the user
- 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
type Option ¶
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 ¶
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 ¶
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 (*Proxy) ListenAndServe ¶
ListenAndServe is used to create a listener and serve on it