Home Download Docs Code Community
     1	/*
     2	Copyright 2011 The Perkeep Authors
     3	
     4	Licensed under the Apache License, Version 2.0 (the "License");
     5	you may not use this file except in compliance with the License.
     6	You may obtain a copy of the License at
     7	
     8	     http://www.apache.org/licenses/LICENSE-2.0
     9	
    10	Unless required by applicable law or agreed to in writing, software
    11	distributed under the License is distributed on an "AS IS" BASIS,
    12	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13	See the License for the specific language governing permissions and
    14	limitations under the License.
    15	*/
    16	
    17	// Package auth implements Camlistore authentication.
    18	package auth // import "perkeep.org/pkg/auth"
    19	
    20	import (
    21		"crypto/rand"
    22		"crypto/subtle"
    23		"errors"
    24		"fmt"
    25		"net/http"
    26		"os"
    27		"regexp"
    28		"strings"
    29		"sync"
    30	
    31		"perkeep.org/internal/httputil"
    32	)
    33	
    34	// Operation represents a bitmask of operations. See the OpX constants.
    35	type Operation int
    36	
    37	const (
    38		OpUpload Operation = 1 << iota
    39		OpStat
    40		OpGet
    41		OpEnumerate
    42		OpRemove
    43		OpSign
    44		// OpDiscovery note: since we disclose an OpAll token in the discovery
    45		// response, please bear in mind that granting OpDiscovery is in effect the
    46		// same as granting OpAll for now.
    47		OpDiscovery
    48		OpRead   = OpEnumerate | OpStat | OpGet | OpDiscovery
    49		OpRW     = OpUpload | OpEnumerate | OpStat | OpGet // Not Remove
    50		OpVivify = OpUpload | OpStat | OpGet | OpDiscovery
    51		OpAll    = OpUpload | OpEnumerate | OpStat | OpRemove | OpGet | OpSign | OpDiscovery
    52	)
    53	
    54	const OmitAuthToken = "OmitAuthToken"
    55	
    56	var (
    57		// Each mode defines an auth logic which depends on the chosen auth mechanism.
    58		// Access is allowed if any of the modes allows it.
    59		// No need to guard for now as all the writes are done sequentially during setup.
    60		modes []AuthMode
    61	)
    62	
    63	// An AuthMode is the interface implemented by diffent authentication
    64	// schemes.
    65	type AuthMode interface {
    66		// AllowedAccess returns a bitmask of all operations
    67		// this user/request is allowed to do.
    68		AllowedAccess(req *http.Request) Operation
    69		// AddAuthHeader inserts in req the credentials needed
    70		// for a client to authenticate.
    71		AddAuthHeader(req *http.Request)
    72	}
    73	
    74	// UnauthorizedSender may be implemented by AuthModes which want to
    75	// handle sending unauthorized.
    76	type UnauthorizedSender interface {
    77		// SendUnauthorized sends an unauthorized response,
    78		// and returns whether it handled it.
    79		SendUnauthorized(http.ResponseWriter, *http.Request) (handled bool)
    80	}
    81	
    82	func FromEnv() (AuthMode, error) {
    83		return FromConfig(os.Getenv("CAMLI_AUTH"))
    84	}
    85	
    86	// An AuthConfigParser parses a registered authentication type's option
    87	// and returns an AuthMode.
    88	type AuthConfigParser func(arg string) (AuthMode, error)
    89	
    90	var authConstructor = map[string]AuthConfigParser{
    91		"none":      newNoneAuth,
    92		"localhost": newLocalhostAuth,
    93		"userpass":  newUserPassAuth,
    94		"token":     NewTokenAuth,
    95		"devauth":   newDevAuth,
    96		"basic":     newBasicAuth,
    97		"tailscale": newTailscaleAuth,
    98	}
    99	
   100	// RegisterAuth registers a new authentication scheme.
   101	func RegisterAuth(name string, ctor AuthConfigParser) {
   102		if _, dup := authConstructor[name]; dup {
   103			panic("Dup registration of auth mode " + name)
   104		}
   105		authConstructor[name] = ctor
   106	}
   107	
   108	func newNoneAuth(string) (AuthMode, error) {
   109		return None{}, nil
   110	}
   111	
   112	func newLocalhostAuth(string) (AuthMode, error) {
   113		return Localhost{}, nil
   114	}
   115	
   116	func newDevAuth(pw string) (AuthMode, error) {
   117		// the vivify mode password is automatically set to "vivi" + Password
   118		vp := "vivi" + pw
   119		return &DevAuth{
   120			Password:   pw,
   121			VivifyPass: &vp,
   122		}, nil
   123	}
   124	
   125	func newUserPassAuth(arg string) (AuthMode, error) {
   126		pieces := strings.Split(arg, ":")
   127		if len(pieces) < 2 {
   128			return nil, fmt.Errorf("Wrong userpass auth string; needs to be \"user:password\"")
   129		}
   130		username := pieces[0]
   131		password := pieces[1]
   132		mode := &UserPass{Username: username, Password: password}
   133		for _, opt := range pieces[2:] {
   134			switch {
   135			case opt == "+localhost":
   136				mode.OrLocalhost = true
   137			case strings.HasPrefix(opt, "vivify="):
   138				// optional vivify mode password: "userpass:joe:ponies:vivify=rainbowdash"
   139				vp := strings.Replace(opt, "vivify=", "", -1)
   140				mode.VivifyPass = &vp
   141			default:
   142				return nil, fmt.Errorf("Unknown userpass option %q", opt)
   143			}
   144		}
   145		return mode, nil
   146	}
   147	
   148	func newBasicAuth(arg string) (AuthMode, error) {
   149		pieces := strings.Split(arg, ":")
   150		if len(pieces) != 2 {
   151			return nil, fmt.Errorf("invalid basic auth syntax. got %q, want \"username:password\"", arg)
   152		}
   153		return NewBasicAuth(pieces[0], pieces[1]), nil
   154	}
   155	
   156	// NewBasicAuth returns a UserPass Authmode, adequate to support HTTP
   157	// basic authentication.
   158	func NewBasicAuth(username, password string) AuthMode {
   159		return &UserPass{
   160			Username: username,
   161			Password: password,
   162		}
   163	}
   164	
   165	// NewTokenAuth returns an Authmode similar to HTTP Basic Auth, which is only
   166	// relying on token instead of a username and password.
   167	func NewTokenAuth(token string) (AuthMode, error) {
   168		return &tokenAuth{token: token}, nil
   169	}
   170	
   171	// ErrNoAuth is returned when there is no configured authentication.
   172	var ErrNoAuth = errors.New("auth: no configured authentication")
   173	
   174	// FromConfig parses authConfig and accordingly sets up the AuthMode
   175	// that will be used for all upcoming authentication exchanges. The
   176	// supported modes are UserPass and DevAuth. UserPass requires an authConfig
   177	// of the kind "userpass:joe:ponies".
   178	//
   179	// If the input string is empty, the error will be ErrNoAuth.
   180	func FromConfig(authConfig string) (AuthMode, error) {
   181		if authConfig == "" {
   182			return nil, ErrNoAuth
   183		}
   184		pieces := strings.SplitN(authConfig, ":", 2)
   185		if len(pieces) < 1 {
   186			return nil, fmt.Errorf("Invalid auth string: %q", authConfig)
   187		}
   188		authType := pieces[0]
   189	
   190		if fn, ok := authConstructor[authType]; ok {
   191			arg := ""
   192			if len(pieces) == 2 {
   193				arg = pieces[1]
   194			}
   195			return fn(arg)
   196		}
   197		return nil, fmt.Errorf("Unknown auth type: %q", authType)
   198	}
   199	
   200	// SetMode sets the given authentication mode as the only allowed one for
   201	// future requests. That is, it replaces all modes that were previously added.
   202	func SetMode(m AuthMode) {
   203		modes = []AuthMode{m}
   204	}
   205	
   206	// AddMode adds the given authentication mode to the list of modes that future
   207	// requests can authenticate against.
   208	func AddMode(am AuthMode) {
   209		modes = append(modes, am)
   210	}
   211	
   212	type tokenAuth struct {
   213		token string
   214	}
   215	
   216	func (t *tokenAuth) AllowedAccess(r *http.Request) Operation {
   217		if authTokenHeaderMatches(r) {
   218			return OpAll
   219		}
   220		if websocketTokenMatches(r) {
   221			return OpAll
   222		}
   223		return 0
   224	}
   225	
   226	func (t *tokenAuth) AddAuthHeader(r *http.Request) {
   227		r.Header.Set("Authorization", "Token "+t.token)
   228	}
   229	
   230	// UserPass is used when the auth string provided in the config
   231	// is of the kind "userpass:username:pass"
   232	// Possible options appended to the config string are
   233	// "+localhost" and "vivify=pass", where pass will be the
   234	// alternative password which only allows the vivify operation.
   235	type UserPass struct {
   236		Username, Password string
   237		OrLocalhost        bool // if true, allow localhost ident auth too
   238	
   239		// VivifyPass, if not nil, is the alternative password used (only) for the vivify operation.
   240		// It is checked when uploading, but Password takes precedence.
   241		VivifyPass *string
   242	}
   243	
   244	func (up *UserPass) AllowedAccess(req *http.Request) Operation {
   245		user, pass, err := httputil.BasicAuth(req)
   246		if err == nil {
   247			if subtle.ConstantTimeCompare([]byte(user), []byte(up.Username)) == 1 {
   248				if subtle.ConstantTimeCompare([]byte(pass), []byte(up.Password)) == 1 {
   249					return OpAll
   250				}
   251				if up.VivifyPass != nil && subtle.ConstantTimeCompare([]byte(pass), []byte(*up.VivifyPass)) == 1 {
   252					return OpVivify
   253				}
   254			}
   255		}
   256	
   257		if authTokenHeaderMatches(req) {
   258			return OpAll
   259		}
   260		if websocketTokenMatches(req) {
   261			return OpAll
   262		}
   263		if up.OrLocalhost && httputil.IsLocalhost(req) {
   264			return OpAll
   265		}
   266	
   267		return 0
   268	}
   269	
   270	func (up *UserPass) AddAuthHeader(req *http.Request) {
   271		req.SetBasicAuth(up.Username, up.Password)
   272	}
   273	
   274	type None struct{}
   275	
   276	func (None) AllowedAccess(req *http.Request) Operation {
   277		return OpAll
   278	}
   279	
   280	func (None) AddAuthHeader(req *http.Request) {
   281		// Nothing.
   282	}
   283	
   284	type Localhost struct {
   285		None
   286	}
   287	
   288	func (Localhost) AllowedAccess(req *http.Request) (out Operation) {
   289		if httputil.IsLocalhost(req) {
   290			return OpAll
   291		}
   292		return 0
   293	}
   294	
   295	// DevAuth is used for development.  It has one password and one vivify password, but
   296	// also accepts all passwords from localhost. Usernames are ignored.
   297	type DevAuth struct {
   298		Password string
   299		// Password for the vivify mode, automatically set to "vivi" + Password
   300		VivifyPass *string
   301	}
   302	
   303	func (da *DevAuth) AllowedAccess(req *http.Request) Operation {
   304		_, pass, err := httputil.BasicAuth(req)
   305		if err == nil {
   306			if pass == da.Password {
   307				return OpAll
   308			}
   309			if da.VivifyPass != nil && pass == *da.VivifyPass {
   310				return OpVivify
   311			}
   312		}
   313	
   314		if authTokenHeaderMatches(req) {
   315			return OpAll
   316		}
   317		if websocketTokenMatches(req) {
   318			return OpAll
   319		}
   320	
   321		// See if the local TCP port is owned by the same non-root user as this
   322		// server.  This check performed last as it may require reading from the
   323		// kernel or exec'ing a program.
   324		if httputil.IsLocalhost(req) {
   325			return OpAll
   326		}
   327	
   328		return 0
   329	}
   330	
   331	func (da *DevAuth) AddAuthHeader(req *http.Request) {
   332		req.SetBasicAuth("", da.Password)
   333	}
   334	
   335	func IsLocalhost(req *http.Request) bool {
   336		return httputil.IsLocalhost(req)
   337	}
   338	
   339	// AllowedWithAuth returns whether the given request
   340	// has access to perform all the operations in op
   341	// against am.
   342	func AllowedWithAuth(am AuthMode, req *http.Request, op Operation) bool {
   343		if op&OpUpload != 0 {
   344			// upload (at least from pk-put) requires stat and get too
   345			op = op | OpVivify
   346		}
   347		return am.AllowedAccess(req)&op == op
   348	}
   349	
   350	// Allowed returns whether the given request
   351	// has access to perform all the operations in op.
   352	func Allowed(req *http.Request, op Operation) bool {
   353		for _, m := range modes {
   354			if AllowedWithAuth(m, req, op) {
   355				return true
   356			}
   357		}
   358		return false
   359	}
   360	
   361	var uiTokenPattern = regexp.MustCompile(`^Token ([a-zA-Z0-9]+)`)
   362	
   363	func authTokenHeaderMatches(req *http.Request) bool {
   364		authHeader := req.Header.Get("Authorization")
   365		if authHeader == "" {
   366			return false
   367		}
   368		matches := uiTokenPattern.FindStringSubmatch(authHeader)
   369		if len(matches) != 2 {
   370			return false
   371		}
   372		return matches[1] == Token()
   373	}
   374	
   375	func websocketTokenMatches(req *http.Request) bool {
   376		return req.Method == "GET" &&
   377			req.Header.Get("Upgrade") == "websocket" &&
   378			req.FormValue("authtoken") == Token()
   379	}
   380	
   381	func TriedAuthorization(req *http.Request) bool {
   382		// Currently a simple test just using HTTP basic auth
   383		// (presumably over https); may expand.
   384		return req.Header.Get("Authorization") != ""
   385	}
   386	
   387	func SendUnauthorized(rw http.ResponseWriter, req *http.Request) {
   388		for _, m := range modes {
   389			if us, ok := m.(UnauthorizedSender); ok {
   390				if us.SendUnauthorized(rw, req) {
   391					return
   392				}
   393			}
   394		}
   395		var realm string
   396		hasDevAuth := func() (*DevAuth, bool) {
   397			for _, m := range modes {
   398				if devAuth, ok := m.(*DevAuth); ok {
   399					return devAuth, ok
   400				}
   401			}
   402			return nil, false
   403		}
   404		if devAuth, ok := hasDevAuth(); ok {
   405			realm = "Any username, password is: " + devAuth.Password
   406		}
   407		// From what I've tested, it looks like sending just "Basic" would be ok,
   408		// but RFC 2617 says realm is mandatory, so probably better to send an empty one.
   409		rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm))
   410		rw.WriteHeader(http.StatusUnauthorized)
   411		fmt.Fprintf(rw, "<html><body><h1>Unauthorized</h1>")
   412	}
   413	
   414	type Handler struct {
   415		http.Handler
   416	}
   417	
   418	// ServeHTTP serves only if this request and auth mode are allowed all Operations.
   419	func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   420		h.serveHTTPForOp(w, r, OpAll)
   421	}
   422	
   423	// serveHTTPForOp serves only if op is allowed for this request and auth mode.
   424	func (h Handler) serveHTTPForOp(w http.ResponseWriter, r *http.Request, op Operation) {
   425		if Allowed(r, op) {
   426			h.Handler.ServeHTTP(w, r)
   427		} else {
   428			SendUnauthorized(w, r)
   429		}
   430	}
   431	
   432	// RequireAuth wraps a function with another function that enforces
   433	// HTTP Basic Auth and checks if the operations in op are all permitted.
   434	func RequireAuth(h http.Handler, op Operation) http.Handler {
   435		return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   436			if Allowed(req, op) {
   437				h.ServeHTTP(rw, req)
   438			} else {
   439				SendUnauthorized(rw, req)
   440			}
   441		})
   442	}
   443	
   444	var (
   445		processRand     string
   446		processRandOnce sync.Once
   447	)
   448	
   449	// Token returns a 20 byte token generated (only once, then cached) with RandToken.
   450	// This token is used for authentication by the Camlistore web UI and its
   451	// websockets, therefore it should be handled with care.
   452	func Token() string {
   453		processRandOnce.Do(genProcessRand)
   454		return processRand
   455	}
   456	
   457	// DiscoveryToken returns OmitAuthToken if the first registered auth mode is of
   458	// type None, and Token() otherwise.
   459	func DiscoveryToken() string {
   460		if len(modes) == 0 {
   461			return Token()
   462		}
   463		if _, ok := modes[0].(None); ok {
   464			return OmitAuthToken
   465		}
   466		return Token()
   467	}
   468	
   469	// TokenOrNone returns a token auth mode if token is not OmitAuthToken, and
   470	// otherwise a None auth mode.
   471	func TokenOrNone(token string) (AuthMode, error) {
   472		if token == OmitAuthToken {
   473			return None{}, nil
   474		}
   475		return NewTokenAuth(token)
   476	}
   477	
   478	func genProcessRand() {
   479		processRand = RandToken(20)
   480	}
   481	
   482	// RandToken genererates (with crypto/rand.Read) and returns a token
   483	// that is the hex version (2x size) of size bytes of randomness.
   484	func RandToken(size int) string {
   485		buf := make([]byte, size)
   486		if n, err := rand.Read(buf); err != nil || n != len(buf) {
   487			panic("failed to get random: " + err.Error())
   488		}
   489		return fmt.Sprintf("%x", buf)
   490	}
Website layout inspired by memcached.
Content by the authors.