diff --git a/cmd/vetinari-server/main.go b/cmd/vetinari-server/main.go index e4db56ce3f..5f4cc1f249 100644 --- a/cmd/vetinari-server/main.go +++ b/cmd/vetinari-server/main.go @@ -15,8 +15,11 @@ import ( "github.com/gorilla/mux" ) -const ADDR = ":4443" -const DEBUG_ADDR = "localhost:8080" +// ServerAddress is the secure server address to listen on +const ServerAddress = ":4443" + +// DebugAddress is the debug server address to listen on +const DebugAddress = "localhost:8080" var debug bool var certFile, keyFile string @@ -31,8 +34,8 @@ func main() { flag.Usage = usage flag.Parse() - if DEBUG_ADDR != "" { - go debugServer(DEBUG_ADDR) + if DebugAddress != "" { + go debugServer(DebugAddress) } if certFile == "" || keyFile == "" { @@ -65,13 +68,13 @@ func main() { r.Methods("POST").Path("/{imageName}/{tag}").Handler(hand(handlers.AddHandler, utils.SSUpdate)) server := http.Server{ - Addr: ADDR, + Addr: ServerAddress, Handler: r, TLSConfig: tlsConfig, } if debug { - log.Println("[Vetinari Server] : Listening on", ADDR) + log.Println("[Vetinari Server] : Listening on", ServerAddress) } err := server.ListenAndServeTLS(certFile, keyFile) @@ -81,7 +84,7 @@ func main() { } func usage() { - log.Println(os.Stderr, "usage:", os.Args[0], "") + log.Println("usage:", os.Args[0], "") flag.PrintDefaults() } diff --git a/errors/errors.go b/errors/errors.go index 223d4cb8b7..254ffe033a 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -4,6 +4,8 @@ import ( "fmt" ) +// HTTPError represents an application error which will map to +// an HTTP status code and returned error object. type HTTPError struct { HTTPStatus int Code int diff --git a/server/handlers/default.go b/server/handlers/default.go index 6f14e986ea..e58b233251 100644 --- a/server/handlers/default.go +++ b/server/handlers/default.go @@ -16,6 +16,7 @@ import ( var db = util.GetSqliteDB() +// MainHandler is the default handler for the server func MainHandler(ctx utils.IContext, w http.ResponseWriter, r *http.Request) *errors.HTTPError { if r.Method == "GET" { err := json.NewEncoder(w).Encode("{}") @@ -24,7 +25,11 @@ func MainHandler(ctx utils.IContext, w http.ResponseWriter, r *http.Request) *er } } else { w.WriteHeader(http.StatusNotFound) - return &errors.HTTPError{http.StatusNotFound, 9999, nil} + return &errors.HTTPError{ + HTTPStatus: http.StatusNotFound, + Code: 9999, + Err: nil, + } } return nil } @@ -40,32 +45,56 @@ func AddHandler(ctx utils.IContext, w http.ResponseWriter, r *http.Request) *err err := decoder.Decode(&meta) defer r.Body.Close() if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } // add to targets local.AddBlob(vars["tag"], meta) tufRepo, err := repo.NewRepo(local, "sha256", "sha512") if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } _ = tufRepo.Init(true) err = tufRepo.AddTarget(vars["tag"], json.RawMessage{}) if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } err = tufRepo.Sign("targets.json") if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } tufRepo.Snapshot(repo.CompressionTypeNone) err = tufRepo.Sign("snapshot.json") if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } tufRepo.Timestamp() err = tufRepo.Sign("timestamp.json") if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } return nil } @@ -79,23 +108,39 @@ func RemoveHandler(ctx utils.IContext, w http.ResponseWriter, r *http.Request) * local.RemoveBlob(vars["tag"]) tufRepo, err := repo.NewRepo(local, "sha256", "sha512") if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } _ = tufRepo.Init(true) tufRepo.RemoveTarget(vars["tag"]) err = tufRepo.Sign("targets.json") if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } tufRepo.Snapshot(repo.CompressionTypeNone) err = tufRepo.Sign("snapshot.json") if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } tufRepo.Timestamp() err = tufRepo.Sign("timestamp.json") if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } return nil } @@ -109,12 +154,17 @@ func GetHandler(ctx utils.IContext, w http.ResponseWriter, r *http.Request) *err meta, err := local.GetMeta() if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } w.Write(meta[vars["tufFile"]]) return nil } +// GenKeysHandler is the handler for generate keys endpoint func GenKeysHandler(ctx utils.IContext, w http.ResponseWriter, r *http.Request) *errors.HTTPError { log.Printf("GenKeysHandler") // remove tag from tagets list @@ -122,7 +172,11 @@ func GenKeysHandler(ctx utils.IContext, w http.ResponseWriter, r *http.Request) local := store.DBStore(db, vars["imageName"]) tufRepo, err := repo.NewRepo(local, "sha256", "sha512") if err != nil { - return &errors.HTTPError{http.StatusInternalServerError, 9999, err} + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } } tufRepo.GenKey("root") tufRepo.GenKey("targets") diff --git a/utils/auth.go b/utils/auth.go index 6aee512031..17702836d0 100644 --- a/utils/auth.go +++ b/utils/auth.go @@ -1,10 +1,12 @@ package utils +// IScope is an identifier scope type IScope interface { ID() string Compare(IScope) bool } +// IAuthorizer is an interfaces to authorize a scope type IAuthorizer interface { // Authorize is expected to set the Authorization on the Context. If // Authorization fails, an error should be returned, but additionally, @@ -13,6 +15,8 @@ type IAuthorizer interface { Authorize(IContext, ...IScope) error } +// IAuthorization is an interface to determine whether +// an object has a scope type IAuthorization interface { HasScope(IScope) bool } @@ -21,46 +25,66 @@ type IAuthorization interface { // THESE ARE FOR DEV PURPOSES ONLY, DO NOT USE IN // PRODUCTION -// DON'T USE THIS FOR ANYTHING, IT'S VERY INSECURE +// InsecureAuthorizer is an insecure implementation of IAuthorizer. +// WARNING: DON'T USE THIS FOR ANYTHING, IT'S VERY INSECURE type InsecureAuthorizer struct{} -// LIKE I SAID, VERY INSECURE +// Authorize authorizes any scope +// WARNING: LIKE I SAID, VERY INSECURE func (auth *InsecureAuthorizer) Authorize(ctx IContext, scopes ...IScope) error { ctx.SetAuthorization(&InsecureAuthorization{}) return nil } -// ALSO DON'T USE THIS, IT'S ALSO VERY INSECURE +// InsecureAuthorization is an implementation of IAuthorization +// which will consider any scope authorized. +// WARNING: ALSO DON'T USE THIS, IT'S ALSO VERY INSECURE type InsecureAuthorization struct { } -// THIS IS JUST INCREDIBLY INSECURE +// HasScope always returns true for any scope +// WARNING: THIS IS JUST INCREDIBLY INSECURE func (authzn *InsecureAuthorization) HasScope(scope IScope) bool { return true } // ### END INSECURE AUTHORIZATION TOOLS ### +// NoAuthorization is an implementation of IAuthorization +// which never allows a scope to be valid. type NoAuthorization struct{} +// HasScope returns false for any scope func (authzn *NoAuthorization) HasScope(scope IScope) bool { return false } +// SimpleScope is a simple scope represented by a string. type SimpleScope string +// ID returns the string representing the scope. func (ss SimpleScope) ID() string { return string(ss) } +// Compare compares to the given scope for equality. func (ss SimpleScope) Compare(toCompare IScope) bool { return ss.ID() == toCompare.ID() } const ( + // SSNoAuth is the simple scope "NoAuth" SSNoAuth SimpleScope = SimpleScope("NoAuth") - SSCreate = SimpleScope("Create") - SSRead = SimpleScope("Read") - SSUpdate = SimpleScope("Update") - SSDelete = SimpleScope("Delete") + + // SSCreate is the simple scope "Create" + SSCreate = SimpleScope("Create") + + // SSRead is the simple scope "Read" + SSRead = SimpleScope("Read") + + // SSUpdate is the simple scope "Update" + SSUpdate = SimpleScope("Update") + + // SSDelete is the simple scope "Delete" + SSDelete = SimpleScope("Delete") ) diff --git a/utils/context.go b/utils/context.go index 92818b11a7..6244b971f5 100644 --- a/utils/context.go +++ b/utils/context.go @@ -4,6 +4,7 @@ import ( "net/http" ) +// IContext defines an interface for managing authorizations. type IContext interface { // TODO: define a set of standard getters. Using getters // will allow us to easily and transparently cache @@ -24,27 +25,36 @@ type IContext interface { SetAuthorization(IAuthorization) } +// IContextFactory creates a IContext from an http request. type IContextFactory func(*http.Request) IContext +// Context represents an authorization context for a resource. type Context struct { resource string authorization IAuthorization } +// ContextFactory creates a new authorization context with the +// given HTTP request path as the resource. func ContextFactory(r *http.Request) IContext { return &Context{ resource: r.URL.Path, } } +// Resource returns the resource value for the context. func (ctx *Context) Resource() string { return ctx.resource } +// Authorization returns an IAuthorization implementation for +// the context. func (ctx *Context) Authorization() IAuthorization { return ctx.authorization } +// SetAuthorization allows setting an IAuthorization for +// the context. func (ctx *Context) SetAuthorization(authzn IAuthorization) { ctx.authorization = authzn } diff --git a/utils/http.go b/utils/http.go index 41a7b0093a..f33e7e288f 100644 --- a/utils/http.go +++ b/utils/http.go @@ -1,4 +1,3 @@ -// http.go contains useful http utilities. package utils import ( @@ -7,8 +6,12 @@ import ( "github.com/docker/vetinari/errors" ) +// BetterHandler defines an alterate HTTP handler interface which takes in +// a context for authorization and returns an HTTP application error. type BetterHandler func(ctx IContext, w http.ResponseWriter, r *http.Request) *errors.HTTPError +// RootHandler is an implementation of an HTTP request handler which handles +// authorization and calling out to the defined alternate http handler. type RootHandler struct { handler BetterHandler auth IAuthorizer @@ -16,12 +19,17 @@ type RootHandler struct { context IContextFactory } +// RootHandlerFactory creates a new RootHandler factory using the given +// Context creator and authorizer. The returned factory allows creating +// new RootHandlers from the alternate http handler BetterHandler and +// a scope. func RootHandlerFactory(auth IAuthorizer, ctxFac IContextFactory) func(BetterHandler, ...IScope) *RootHandler { return func(handler BetterHandler, scopes ...IScope) *RootHandler { return &RootHandler{handler, auth, scopes, ctxFac} } } +// ServeHTTP serves an HTTP request and implements the http.Handler interface. func (root *RootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := root.context(r) if err := root.auth.Authorize(ctx, root.scopes...); err != nil {