diff --git a/cmd/vetinari-server/main.go b/cmd/vetinari-server/main.go index 967bc8348c..ab6cf8e233 100644 --- a/cmd/vetinari-server/main.go +++ b/cmd/vetinari-server/main.go @@ -6,6 +6,8 @@ import ( "log" "net/http" "os" + "os/signal" + "syscall" "golang.org/x/net/context" @@ -32,15 +34,41 @@ func main() { go debugServer(DebugAddress) } - conf, err := config.Load(configFile) - if err != nil { - // TODO: log and exit + ctx := context.Background() + + var cancel context.CancelFunc + cancelable := func() { + var childCtx context.Context + childCtx, cancel = context.WithCancel(ctx) + conf, err := config.Load(configFile) + if err != nil { + // TODO: log and exit + } + + server.Run(childCtx, conf) } - ctx := context.Background() - childCtx, _ := context.WithCancel(ctx) - server.Run(childCtx, conf) + sigHup := make(chan os.Signal) + sigKill := make(chan os.Signal) + signal.Notify(sigHup, syscall.SIGHUP) + signal.Notify(sigKill, syscall.SIGKILL) + + for { + go cancelable() + + select { + // On a sighup we cancel and restart a new server + // with updated config + case <-sigHup: + cancel() + continue + // On sigkill we cancel and shutdown + case <-sigKill: + cancel() + os.Exit(0) + } + } } func usage() { diff --git a/server/handlers/default.go b/server/handlers/default.go index 581d97f4a3..1e469f5dd0 100644 --- a/server/handlers/default.go +++ b/server/handlers/default.go @@ -53,7 +53,7 @@ func AddHandler(ctx utils.IContext, w http.ResponseWriter, r *http.Request) *err } // add to targets local.AddBlob(vars["tag"], meta) - tufRepo, err := repo.NewRepo(local, "sha256", "sha512") + tufRepo, err := repo.NewRepo(ctx.Signer(), local, "sha256", "sha512") if err != nil { return &errors.HTTPError{ HTTPStatus: http.StatusInternalServerError, @@ -106,7 +106,7 @@ func RemoveHandler(ctx utils.IContext, w http.ResponseWriter, r *http.Request) * vars := mux.Vars(r) local := store.DBStore(db, vars["imageName"]) local.RemoveBlob(vars["tag"]) - tufRepo, err := repo.NewRepo(local, "sha256", "sha512") + tufRepo, err := repo.NewRepo(ctx.Signer(), local, "sha256", "sha512") if err != nil { return &errors.HTTPError{ HTTPStatus: http.StatusInternalServerError, @@ -170,7 +170,7 @@ func GenKeysHandler(ctx utils.IContext, w http.ResponseWriter, r *http.Request) // remove tag from tagets list vars := mux.Vars(r) local := store.DBStore(db, vars["imageName"]) - tufRepo, err := repo.NewRepo(local, "sha256", "sha512") + tufRepo, err := repo.NewRepo(ctx.Signer(), local, "sha256", "sha512") if err != nil { return &errors.HTTPError{ HTTPStatus: http.StatusInternalServerError, diff --git a/server/server.go b/server/server.go index 4f549891b2..e9436e330c 100644 --- a/server/server.go +++ b/server/server.go @@ -52,6 +52,9 @@ func Run(ctx context.Context, conf *config.Configuration) error { } tlsLsnr := tls.NewListener(lsnr, tlsConfig) + // This is a basic way to shutdown the running listeners. + // A more complete implementation would ensure each individual connection + // gets cleaned up. go func() { doneChan := ctx.Done() <-doneChan diff --git a/utils/auth.go b/utils/auth.go index 17702836d0..e7a2d4a382 100644 --- a/utils/auth.go +++ b/utils/auth.go @@ -1,5 +1,9 @@ package utils +import ( + "errors" +) + // IScope is an identifier scope type IScope interface { ID() string @@ -50,6 +54,15 @@ func (authzn *InsecureAuthorization) HasScope(scope IScope) bool { // ### END INSECURE AUTHORIZATION TOOLS ### +// NoAuthorizer is a factory for NoAuthorization object +type NoAuthorizer struct{} + +// Authorize implements the IAuthorizer interface +func (auth *NoAuthorizer) Authorize(ctx IContext, scopes ...IScope) error { + ctx.SetAuthorization(&NoAuthorization{}) + return errors.New("User not authorized") +} + // NoAuthorization is an implementation of IAuthorization // which never allows a scope to be valid. type NoAuthorization struct{} diff --git a/utils/auth_test.go b/utils/auth_test.go index 70283caaf7..9231d1acb5 100644 --- a/utils/auth_test.go +++ b/utils/auth_test.go @@ -4,6 +4,19 @@ import ( "testing" ) +func TestInsecureAuthorization(t *testing.T) { + auther := InsecureAuthorizer{} + ctx := Context{} + err := auther.Authorize(&ctx, SSNoAuth) + if err != nil { + t.Fatalf("Failed to authorize with InsecureAuthorizer") + } + if !ctx.Authorization().HasScope(SSCreate) { + t.Fatalf("InsecureAuthorization failed to approve a scope") + } + +} + func TestNoAuthorization(t *testing.T) { auth := NoAuthorization{} if auth.HasScope(SSCreate) { diff --git a/utils/context.go b/utils/context.go index 6244b971f5..558334d376 100644 --- a/utils/context.go +++ b/utils/context.go @@ -1,6 +1,7 @@ package utils import ( + "github.com/endophage/go-tuf/signed" "net/http" ) @@ -23,6 +24,8 @@ type IContext interface { // SetAuthStatus should be called to change the authorization // status of the context (and therefore the request) SetAuthorization(IAuthorization) + + Signer() *signed.Signer } // IContextFactory creates a IContext from an http request. @@ -58,3 +61,8 @@ func (ctx *Context) Authorization() IAuthorization { func (ctx *Context) SetAuthorization(authzn IAuthorization) { ctx.authorization = authzn } + +// Signer returns the instantiated signer for the context +func (ctx *Context) Signer() *signed.Signer { + return nil +} diff --git a/utils/context_test.go b/utils/context_test.go index fdb6eb0635..2ed69cbe3b 100644 --- a/utils/context_test.go +++ b/utils/context_test.go @@ -16,3 +16,11 @@ func TestContextFactory(t *testing.T) { t.Fatalf("Context has incorrect resource") } } + +func TestContext(t *testing.T) { + ctx := Context{} + + if ctx.Signer() != nil { + t.Fatalf("Update this test now that Signer has been implemented") + } +} diff --git a/utils/http_test.go b/utils/http_test.go new file mode 100644 index 0000000000..fe5fc6a4d3 --- /dev/null +++ b/utils/http_test.go @@ -0,0 +1,76 @@ +package utils + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/docker/vetinari/errors" +) + +func MockBetterHandler(ctx IContext, w http.ResponseWriter, r *http.Request) *errors.HTTPError { + return nil +} + +func MockBetterErrorHandler(ctx IContext, w http.ResponseWriter, r *http.Request) *errors.HTTPError { + return &errors.HTTPError{http.StatusInternalServerError, 9999, fmt.Errorf("TestError")} +} + +func TestRootHandlerFactory(t *testing.T) { + hand := RootHandlerFactory(&InsecureAuthorizer{}, ContextFactory) + handler := hand(MockBetterHandler) + if _, ok := interface{}(handler).(http.Handler); !ok { + t.Fatalf("A RootHandler must implement the http.Handler interface") + } + + ts := httptest.NewServer(handler) + defer ts.Close() + + res, err := http.Get(ts.URL) + if err != nil { + t.Fatal(err) + } + if res.StatusCode != http.StatusOK { + t.Fatalf("Expected 200, received %d", res.StatusCode) + } +} + +func TestRootHandlerUnauthorized(t *testing.T) { + hand := RootHandlerFactory(&NoAuthorizer{}, ContextFactory) + handler := hand(MockBetterHandler) + + ts := httptest.NewServer(handler) + defer ts.Close() + + res, err := http.Get(ts.URL) + if err != nil { + t.Fatal(err) + } + if res.StatusCode != http.StatusUnauthorized { + t.Fatalf("Expected 401, received %d", res.StatusCode) + } +} + +func TestRootHandlerError(t *testing.T) { + hand := RootHandlerFactory(&InsecureAuthorizer{}, ContextFactory) + handler := hand(MockBetterErrorHandler) + + ts := httptest.NewServer(handler) + defer ts.Close() + + res, err := http.Get(ts.URL) + if res.StatusCode != http.StatusInternalServerError { + t.Fatalf("Expected 500, received %d", res.StatusCode) + } + content, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + contentStr := strings.Trim(string(content), "\r\n\t ") + if contentStr != "9999: TestError" { + t.Fatalf("Error Body Incorrect: `%s`", content) + } +}