Skip to content
This repository was archived by the owner on Sep 10, 2022. It is now read-only.

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

10 Commits

Repository files navigation

okay: abstract cross-package auth[nz]

okay is a package that allows consumers to pass authentication and authorization checks across package boundaries.

Consumers should start with the base OK, which denies everyone to everything, and add permissions as required with the Allow, Validate, and Verify functions.

Overview

Often there is some resource to which we want to restrict access; databases (or columns, or rows), file systems, URL endpoints, or even specific functions or methods.

Say we have a file system type:

package fs
import (
"io""os""path/filepath"
)
typeFileSystemstruct {
Rootstring
}
func (fsFileSystem) Open(pathstring) (io.ReadCloser, error) {
returnos.Open(filepath.Join(fs.Root, path))
}

This could be used to e.g. serve the file via HTTP, but it provides no access controls. We could add this by modifying Open to take a Context which contains the appropriate credentials:

import (
"context""io""os""path/filepath"
)
func (fsFileSystem) Open(ctx context.Context, pathstring) (io.ReadCloser, error) {
// TODO: check context somehowreturnos.Open(filepath.Join(fs.Root, path))
}

How should we check the context? If we're serving this via HTTP, we could provide simple authentication by wrapping HTTP basic authentication into a context:

package server
import (
"context""net/http"
)
typeauthTypestringfuncgetContext(r*http.Request) context.Context {
ctx:=r.Context()
ifuser, pass, ok:=r.BasicAuth(); ok {
ctx=context.WithValue(ctx, authType("user"), user)
ctx=context.WithValue(ctx, authType("pass"), pass)
}
returnctx
}
funchandleRequest(rw http.ResponseWriter, r*http.Request) {
ctx:=getContext(r)
doTheThing(ctx, rw)
}

...and then providing a facility to verify that context:

package server
import"context"func (s*Server) Authenticate(ctx context.Context) (bool, error) {
user, ok:=ctx.Value(authType("user")).(string)
if!ok {
returnfalse, nil
}
pass, ok:=ctx.Value(authType("pass")).(string)
if!ok {
returnfalse, nil
}
returns.checkCredentials(user, pass)
}

Now we know how to check the context and can successfully gate access to our files.

import (
"context""fmt""io""os""path/filepath""server"
)
typeFileSystemstruct {
Server server.ServerRootstring
}
func (fs*FileSystem) Open(ctx context.Context, pathstring) (io.ReadCloser, error) {
ok, err:=fs.Server.Authenticate(ctx)
iferr!=nil {
returnnil, err
}
if!ok {
returnnil, fmt.Errorf("%s: permission denied", path)
}
returnos.Open(filepath.Join(fs.Root, path))
}

Unfortunately, this very tightly couples packages fs and server. If fs wants to grant users via another scheme, such as OAuth2, or to certain administrator users, or in some other way, all the methods which check for authentication must be updated. It may be that server is not something we can modify; it may be that fs isn't something we can modify.

This can be fixed by having fs accept OK types:

import (
"context""fmt""io""okay""os""path/filepath"
)
typeFileSystemstruct {
Auths []okay.OKRootstring
}
func (fsFileSystem) Open(ctx context.Context, pathstring) (io.ReadCloser, error) {
ok, err:=okay.Check(ctx, path, fs.Auths...)
iferr!=nil {
returnnil, err
}
if!ok {
returnnil, fmt.Errorf("%s: permission denied", path)
}
returnos.Open(filepath.Join(fs.Root, path))
}

Now fs and server are distinct packages coupled only by the OK interface. FileSystem.Auths can be extended at will by fs or any consumer of fs, and it will gate the guarded resources appropriately.

Use

Packages using okay should begin with the base type, and add authentication (Verify()), authorization (Allows()), and validation (Valid()) checks as needed.

The base type is always valid, and authenticates nobody and authorizes nothing.

Validation

OKs are valid until they are not, and are thereafter never valid. If a consumer wishes to create an access grant that expires (for example, to allow access to a file for only 24 hours), they should do so by creating an OK that expires after that period of time. The okay package has several helper functions for this:

ok:=okay.New()
ok=okay.WithTimeout(ok, 24*time.Hour)

Or, for an OK that invalidates itself when a Context expires or is canceled:

ok:=okay.New()
ok=okay.WithContext(ok, ctx)

However, consumers can implement more sophisticated behavior by implementing their own validation functions:

import (
"os""os/signal""sync/atomic""okay"
)
funcSigintCancel() OK {
ch:=make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
varvint32gofunc() {
<-chatomic.StoreInt32(&v, 1)
}()
ok:=okay.New()
ok=okay.Validate(ok, func() bool {
returnatomic.LoadInt32(&v) ==0
})
returnok
}

Authentication

Consumers are expected to provide custom authentication with the Verify() function.

Here is a simple package that provides token-based authentication:

package authtoken
import (
"context""okay"
)
typeauthTokenstruct{}
// WithToken returns a context that contains the given auth token.funcWithToken(ctx context.Context, tokenstring) context.Context {
returncontext.WithValue(ctx, authToken{}, token)
}
// TokenOK returns an OK that verifies the given token.funcTokenOK(ok okay.OK, tokenstring) okay.OK {
returnokay.Verify(ok, func(ctx context.Context) (bool, error) {
tok, ok:=ctx.Value(authToken{}).(string)
if!ok {
returnfalse, nil
}
returntok==token, nil
})
}

Authorization

Authorization is provided via the Allow() function, which allows access to resources.

The fs package (or consumers of it) might implement the following:

package fs
import (
"strings""okay"
)
funcAllowFiles(ok okay.OK, file...string) OK {
check:=make(map[string]bool)
for_, f:=rangefile {
check[f] =true
}
returnokay.Allow(ok, func(iinterface{}) (bool, error) {
f, ok:=i.(string)
if!ok {
returnfalse, nil
}
returncheck[f], nil
})
}
funcAllowPrefix(ok okay.OK, pfxstring) OK {
returnokay.Allow(ok, func(iinterface{}) (bool, error) {
f, ok:=i.(string)
if!ok {
returnfalse, nil
}
returnstrings.HasPrefix(f, pfx), nil
})
}

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

22 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages