diff --git a/.gitignore b/.gitignore index 4be5802a..e94d0693 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *.exe /*-poc /*.sqlite + +/flamenco-worker.yaml +/flamenco-worker-credentials.yaml diff --git a/cmd/flamenco-manager-poc/main.go b/cmd/flamenco-manager-poc/main.go index 30048b90..d6e1275c 100644 --- a/cmd/flamenco-manager-poc/main.go +++ b/cmd/flamenco-manager-poc/main.go @@ -71,14 +71,14 @@ func main() { log.Fatal().Err(err).Msg("error loading job compilers") } flamenco := api_impl.NewFlamenco(compiler, persist) - e := buildWebService(flamenco) + e := buildWebService(flamenco, persist) // Start the web server. finalErr := e.Start(listen) log.Warn().Err(finalErr).Msg("shutting down") } -func buildWebService(flamenco api.ServerInterface) *echo.Echo { +func buildWebService(flamenco api.ServerInterface, persist api_impl.PersistenceService) *echo.Echo { e := echo.New() e.HideBanner = true @@ -99,7 +99,9 @@ func buildWebService(flamenco api.ServerInterface) *echo.Echo { validator := oapi_middle.OapiRequestValidatorWithOptions(swagger, &oapi_middle.Options{ Options: openapi3filter.Options{ - AuthenticationFunc: authenticator, + AuthenticationFunc: func(ctx context.Context, authInfo *openapi3filter.AuthenticationInput) error { + return api_impl.WorkerAuth(ctx, authInfo, persist) + }, }, // Skip OAPI validation when the request is not for the OAPI interface. diff --git a/flamenco-worker-credentials.yaml b/flamenco-worker-credentials.yaml deleted file mode 100644 index f829954f..00000000 --- a/flamenco-worker-credentials.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Credentials file for Flamenco Worker. -# For an explanation of the fields, refer to flamenco-worker-example.yaml -# -# NOTE: this file can be overwritten by Flamenco Worker. -# -# This file was written on 2022-01-31 14:49:14 +01:00 - -worker_id: 9b41b767-74de-4cac-9604-f3521821d68d -worker_secret: 9b4d6b672181d29dfc65ceb59ff7aba3aab3e5bd9cac5d50342eb3a7217b0085 diff --git a/flamenco-worker.yaml b/flamenco-worker.yaml deleted file mode 100644 index 68be88d9..00000000 --- a/flamenco-worker.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Configuration file for Flamenco Worker. -# For an explanation of the fields, refer to flamenco-worker-example.yaml -# -# NOTE: this file can be overwritten by Flamenco Worker. -# -# This file was written on 2022-01-31 14:45:36 +01:00 - -manager_url: "http://localhost:8080/" -task_types: -- sleep -- blender-render -- file-management -- exr-merge -- debug diff --git a/go.mod b/go.mod index b79c3e43..96ef5a82 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/shopspring/decimal v1.2.0 // indirect github.com/stretchr/testify v1.7.0 github.com/ziflex/lecho/v3 v3.1.0 + golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e golang.org/x/net v0.0.0-20211013171255-e13a2654a71e gopkg.in/yaml.v2 v2.4.0 gorm.io/driver/postgres v1.0.8 diff --git a/internal/manager/api_impl/logging.go b/internal/manager/api_impl/logging.go index e6d2e246..0e59d8cd 100644 --- a/internal/manager/api_impl/logging.go +++ b/internal/manager/api_impl/logging.go @@ -1,5 +1,25 @@ package api_impl +/* ***** BEGIN GPL LICENSE BLOCK ***** + * + * Original Code Copyright (C) 2022 Blender Foundation. + * + * This file is part of Flamenco. + * + * Flamenco is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Flamenco. If not, see . + * + * ***** END GPL LICENSE BLOCK ***** */ + import ( "context" diff --git a/internal/manager/api_impl/worker_auth.go b/internal/manager/api_impl/worker_auth.go new file mode 100644 index 00000000..54a5375a --- /dev/null +++ b/internal/manager/api_impl/worker_auth.go @@ -0,0 +1,79 @@ +package api_impl + +/* ***** BEGIN GPL LICENSE BLOCK ***** + * + * Original Code Copyright (C) 2022 Blender Foundation. + * + * This file is part of Flamenco. + * + * Flamenco is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Flamenco. If not, see . + * + * ***** END GPL LICENSE BLOCK ***** */ + +import ( + "context" + "errors" + + oapi_middle "github.com/deepmap/oapi-codegen/pkg/middleware" + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/labstack/echo/v4" + "golang.org/x/crypto/bcrypt" +) + +type workerContextKey string + +const ( + workerKey = workerContextKey("worker") +) + +var ( + errAuthBad = errors.New("no such worker known") +) + +// OpenAPI authentication function for authing workers. +// The worker will be fetched from the database and stored in the request context. +func WorkerAuth(ctx context.Context, authInfo *openapi3filter.AuthenticationInput, persist PersistenceService) error { + echo := ctx.Value(oapi_middle.EchoContextKey).(echo.Context) + req := echo.Request() + logger := requestLogger(echo) + + // Fetch username & password from the HTTP header. + u, p, ok := req.BasicAuth() + logger.Debug().Interface("scheme", authInfo.SecuritySchemeName).Str("user", u).Msg("authenticator") + if !ok { + return authInfo.NewError(errors.New("no auth header found")) + } + + // Fetch the Worker that has this username, making sure there is always _some_ + // secret to check. This helps in making this a constant-time operation. + var hashedSecret string + w, err := persist.FetchWorker(ctx, u) + if err == nil { + hashedSecret = w.Secret + } else { + hashedSecret = "this is not a BCrypt hash, so it'll fail" + } + + // Check the password. + err = bcrypt.CompareHashAndPassword([]byte(hashedSecret), []byte(p)) + if err != nil { + logger.Warn().Str("username", u).Msg("authentication error") + return authInfo.NewError(errAuthBad) + } + + // Store the Worker in the request context, so that it doesn't need to be fetched again later. + reqCtx := context.WithValue(req.Context(), workerKey, w) + echo.SetRequest(req.WithContext(reqCtx)) + + return nil +} diff --git a/internal/manager/api_impl/workers.go b/internal/manager/api_impl/workers.go index 82e22dda..e11cbfcc 100644 --- a/internal/manager/api_impl/workers.go +++ b/internal/manager/api_impl/workers.go @@ -26,6 +26,7 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v4" + "golang.org/x/crypto/bcrypt" "gitlab.com/blender/flamenco-ng-poc/internal/manager/persistence" "gitlab.com/blender/flamenco-ng-poc/pkg/api" @@ -46,10 +47,16 @@ func (f *Flamenco) RegisterWorker(e echo.Context) error { logger.Info().Str("nickname", req.Nickname).Msg("registering new worker") + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Secret), bcrypt.DefaultCost) + if err != nil { + logger.Warn().Err(err).Msg("error hashing worker password") + return sendAPIError(e, http.StatusBadRequest, "error hashing password") + } + dbWorker := persistence.Worker{ UUID: uuid.New().String(), Name: req.Nickname, - Secret: req.Secret, + Secret: string(hashedPassword), Platform: req.Platform, Address: e.RealIP(), SupportedTaskTypes: strings.Join(req.SupportedTaskTypes, ","),