multipass

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 21, 2016 License: BSD-3-Clause Imports: 19 Imported by: 0

README

mutipass preview

Multipass

Multipass is a remote proxy which can be used to protect web resources and services using user access control. Users are authenticated using a challenge; prove they are the owner of the registered email address by following a login link.

Multipass implements the idea to authenticate users based on something they own instead of something they know. This is better known as the second factor of Two-factor Authentication.

Login links are encoded JSON Web Tokens containing information about the user and their accessible resources. These tokens are used as access tokens with an expiration date and are signed using an on startup generated RSA key pair.

Goal

Protect internet exposed web resources and services with automatic HTTPS (TLS) and provide user friendly authentication.

Motivation

Many private web resources and services end up exposed on the internet, accessible by anyone. Think IP video cameras, Key-value stores, analytic applications and many more. Using Multipass, these web resources and services can be protected using automatic HTTPS (TLS) and access can be granted on an individual basis.

What's here?


Installation

Download the binary from the releases page. If your platform isn't listed please submit a PR.

Build

Building the Multipass command.

  1. Get the Caddy web server source code:

    $ go get github.com/mholt/caddy
    
  2. Register Multipass as a caddy plugin by adding multipass to the caddy directive:

    Open $GOPATH/src/github.com/mholt/caddy/caddyhttp/httpserver/plugin.go in your favorite editor and make the following changes.

    var directives = []string{
    	...
     	"expvar",
    	"multipass", // <- insert this line somewhere before "proxy"
    	"proxy",
    	...
    }
    
  3. Get the Multipass source code and build the command:

    $ go get github.com/namsral/multipass
    $ go install github.com/namsral/multipass/cmd/multipass
    

The next thing is to create a configuration file and run the multipass command.

Configuration

In the following example, the service running on localhost:2016 is proxied and protected to allow only users with handles leeloo@dallas and korben@dallas to access the /fhloston and /paradise resources.

example.com {
	bind 0.0.0.0
	multipass {
		resources /fhloston /paradise
		handles leeloo@dallas korben@dallas
		basepath /multipass
		expires 24h
		smtp_addr localhost:2525
		mail_from "Multipass <no-reply@dallas>"
	}
	proxy / localhost:2016
	log stdout
}
  • resources: path of resources to protect. Default: /
  • handles: the handles which identify the users. Required
  • basepath: path to the log-in and sign-out page. Default: /multipass
  • expires: The time duration after which the token expires. Any time duration Go can parse. Default: 24h
  • smtp_addr: Mailserver address used for sending login links. Default: localhost:25
  • smtp_user: Mailserver username used for authentication.
  • smtp_pass: Mailserver password used for authentication.
  • mail_from: From address used in email messages sent to users. Required

How it works

Multipass works by sending the user a login link with an embedded access token. When the user follows the login link the access token is stored in the browser session and used to authenticate the user on successive requests. The access token is a JSON web token containing claims specific to Multipass and signed with a RSA key pair.

User flow:

  1. User visits protected resource
  2. User is redirected to log-in page and enters a known handle, e.g. email address
  3. An user access token is sent to user in the form of a login link
  4. User follows the login link and is granted access the protected resource

JWT

Access tokens are signed JSON Web Tokens with specific claims like user handle and expire date. The tokens are embedded in login links which are sent to user.

RSA key pairs

A RSA key pair is used to sign user access tokens. These access tokens and other signatures can be verified by others using the public key made available at the url [siteaddr][basepath]/pub.cer when Multipass is running.

You can set your own private RSA key in the MULTIPASS_RSA_PRIVATE_KEY environment variable; make sure to PEM encode the private key.

When no private key is set, the MULTIPASS_RSA_PRIVATE_KEY environment variable is empty, a RSA key pair is randomly generated and stored in the environment. This ensures signatures still validate after Multipass reloads during a configuration reload.

Automatic HTTPS

Multipass piggybacks on the Caddy web server which comes with automatic HTTPS using Let's Encrypt and many more features and plugins.

Reverse Proxy

The user handle which was used to authenticate the user is passed down to the protected web services as a HTTP header:

Multipass-Handle: <user handle>

Extending

Exting Multipass by implementing the UserService interface.

The current something they own is the users email address and access tokens are sent to this address. But the something they own can also be a mobile number which can receive SMS messages, or a connected device which can receive Push notifications, chat messages and many more.
By implementing the UserService, shown below, Multipass can be extended to support other user handles which can identify and notify users.

// A UserService is an interface used by a Multipass instance to register,
// list user handles and notify users about requested access tokens.
// A handle is a unique user identifier, e.g. email address.
type UserService interface {
	// Register returns nil when the given handle is accepted for
	// registration with the service.
	// The handle is passed on by the Multipass instance and can represent
	// an username, email address or even an URI representing a connection to
	// a datastore. The latter allows the UserService to be associated
	// with a RDBMS from which to verify listed users.
	Register(handle string) error

	// Listed returns true when the given handle is listed with the
	// service.
	Listed(handle string) bool

	// Authorized returns true when the user identified by the given handle is
	// authorized to access the given resource at rawurl.
	Authorized(handle, rawurl string) bool

	// Notify returns nil when the given login URL is successfully
	// communicated to the given handle.
	Notify(handle, loginurl string) error

	// Close closes any open connections.
	Close() error
}

Contributing

Contributing is easy:

  1. Fork this repo
  2. Checkout a new branch
  3. Submit a pull-request

Or follow GiHub's guide to using-pull-requests.

Documentation

Overview

Package multipass implements an authentication service which can be used to wrap any http.Handler(Func).

Multipass implements the concept to authenticate users based on something they own instead of something they know. This is better known as the second factor of Two-factor Authentication.

Quick Start

Wrap any http.Handler or http.HandlerFunc to provide user authentication. In the example below, the appHandler function is wrapped using the AuthHandler wrapper. It assumes you have a SMTP service running on `localhost:2525` and user identified by email address leeloo@dallas has access to the resource at `/private`.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/namsral/multipass"
	"github.com/namsral/multipass/services/email"
)

func appHandler(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/":
		fmt.Fprintf(w, "this is the public page")
		return
	case "/private":
		fmt.Fprintf(w, "this is the private page")
		return
	}
	http.NotFound(w, r)
}

func main() {
	options := &email.Options{
		Addr:     "localhost:2525", // SMTP address
		FromAddr: "Multipass Bot <noreply@dallas>",
	}
	service, err := email.NewUserService(options)
	if err != nil {
		log.Fatal(err)
	}
	service.AddPattern("/private")    // Accessible to authenticated users
	service.Register("leeloo@dallas") // Only registered users are granted access

	addr := "localhost:6080"
	siteaddr := "http://" + addr
	m, err := multipass.NewMultipass(siteaddr)
	if err != nil {
		log.Fatal(err)
	}
	m.SetUserService(service) // Override the default UserService

	h := multipass.AuthHandler(http.HandlerFunc(appHandler), m)
	log.Fatal(http.ListenAndServe(addr, h))
}

The package consist of three major components; Multipass, ResourceHandler and UserService.

Multipass

Multipass is a http.Handler which issues and signs user tokens and validates their signature.

NewMultipass(siteaddr string) (*Multipass, error)

Multipass has it's own web UI which is available at the configurable basepath. From the web UI users can request a login url to gain access to private resources.

Multipass.SetBasePath(basepath string)

UserService

User authorization is offloaded to the UserService service. Because the UserService is an interface custom UserService's can be developed and plugged into the Multipass instance. Allowing other means of authentication and authorization besides the built-in email UserService.

// A UserService is an interface used by a Multipass instance to register,
// list user handles and notify users about requested access tokens.
// A handle is a unique user identifier, e.g. email address.
type UserService interface {
	// Register returns nil when the given handle is accepted for
	// registration with the service.
	// The handle is passed on by the Multipass instance and can represent
	// an username, email address or even an URI representing a connection to
	// a datastore. The latter allows the UserService to be associated
	// with a RDBMS from which to verify listed users.
	Register(handle string) error

	// Listed returns true when the given handle is listed with the
	// service.
	Listed(handle string) bool

	// Authorized returns true when the user identified by the given handle is
	// authorized to access the given resource at rawurl.
	Authorized(handle, rawurl string) bool

	// Notify returns nil when the given login URL is successfully
	// communicated to the given handle.
	Notify(handle, loginurl string) error

	// Close closes any open connections.
	Close() error
}

An implementation can be found in the `services/email` package. This implementations identifies users by their email address.

ResourceHandler

ResourceHandler accepts a http.ResponseWriter and http.Request and determines if the request is from an authenticated user and if this user is authorized to access the requested resource according to the UserService. The ResourceHandler extracts any user token from the header, cookie header or query parameters and validates the user tokens signature with preset or pre-generated key pairs.

ResourceHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error)

Index

Constants

View Source
const (
	PKENV = "MULTIPASS_RSA_PRIVATE_KEY"
)

Portable constants

Variables

View Source
var (
	ErrInvalidToken = errors.New("invalid token")
	ErrForbidden    = errors.New(http.StatusText(http.StatusForbidden))
)

Portable errors

View Source
var DefaultUserService = io.NewUserService(os.Stdout)

DefaultUserService is the default UserService used by Multipass.

Functions

func AuthHandler added in v0.3.0

func AuthHandler(next http.Handler, m *Multipass) http.Handler

AuthHandler wraps any http.Handler to provide authentication using the given Multipass instance. Handlers from other http routers can be wrapped with little effort by copying the AuthHandler and make minor changes.

func NewLoginURL

func NewLoginURL(siteaddr, basepath, token string, v url.Values) (*url.URL, error)

NewLoginURL returns a login url which can be used as a time limited login. Optional values will be encoded in the login URL.

func ResourceHandler added in v0.3.0

func ResourceHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error)

ResourceHandler validates the token in the request before it writes the response.

Types

type Claims

type Claims struct {
	Handle  string `json:"handle"`
	Expires int64  `json:"exp"`
}

Claims are part of the JSON web token

type Multipass

type Multipass struct {
	Expires time.Duration
	// contains filtered or unexported fields
}

Multipass implements the http.Handler interface which can handle authentication and authorization of users and resources using signed JWT.

func NewMultipass

func NewMultipass(siteaddr string) (*Multipass, error)

NewMultipass returns a new instance of Multipass with reasonalble defaults: 2048 bit RSA key pair, `/multipass` basepath a token expiration time of 24 hours.

func (*Multipass) AccessToken

func (m *Multipass) AccessToken(handle string) (tokenStr string, err error)

AccessToken returns a new signed and serialized token with the given handle as a claim.

func (*Multipass) BasePath added in v0.3.0

func (m *Multipass) BasePath() string

BasePath return the base path.

func (*Multipass) ServeHTTP

func (m *Multipass) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP satisfies the ServeHTTP interface

func (*Multipass) SetBasePath added in v0.3.0

func (m *Multipass) SetBasePath(basepath string)

SetBasePath overrides the default base path of `/multipass`. The given basepath is made absolute before it is set.

func (*Multipass) SetUserService added in v0.3.0

func (m *Multipass) SetUserService(s UserService)

SetUserService overrides the default UserService.

type UserService added in v0.3.0

type UserService interface {
	// Register returns nil when the given handle is accepted for
	// registration with the service.
	// The handle is passed on by the Multipass instance and can represent
	// an username, email address or even an URI representing a connection to
	// a datastore. The latter allows the UserService to be associated
	// with a RDBMS from which to verify listed users.
	Register(handle string) error

	// Listed returns true when the given handle is listed with the
	// service.
	Listed(handle string) bool

	// Authorized returns true when the user identified by the given handle is
	// authorized to access the given resource at rawurl.
	Authorized(handle, rawurl string) bool

	// Notify returns nil when the given login URL is successfully
	// communicated to the given handle.
	Notify(handle, loginurl string) error

	// Close closes any open connections.
	Close() error
}

A UserService is an interface used by a Multipass instance to register, list user handles and notify users about requested access tokens. A handle is a unique user identifier, e.g. email address.

Directories

Path Synopsis
cmd
services
io

Jump to

Keyboard shortcuts

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