Repository: bacalhau-project/bacalhau (language: python)

Issue:
V1 Client APIs do not support authentication
All of the CLIs using the v1 client do not support authentication. We can either push auth support into the V1 Client/Server implementations or migrate away from the V1 Client/Server methods.


Discussion / hints:
> We can either push auth support into the V1 Client/Server implementations or migrate away from the V1 Client/Server methods.

I'm going to do the former as it should be as simple as just attaching auth tokens to the client requests. These methods will continue to work unauthenticated if the server has not been configured (as the default policies encode the existing v1 behaviours), and will still require signing of payloads where necessary, so this isn't a breaking change.

Fix patch:
diff --git a/cmd/util/api.go b/cmd/util/api.go
index fee98dfd11..30a51583a9 100644
--- a/cmd/util/api.go
+++ b/cmd/util/api.go
@@ -14,7 +14,15 @@ import (
 
 func GetAPIClient(ctx context.Context) *client.APIClient {
 	legacyTLS := client.LegacyTLSSupport(config.ClientTLSConfig())
-	return client.NewAPIClient(legacyTLS, config.ClientAPIHost(), config.ClientAPIPort())
+	apiClient := client.NewAPIClient(legacyTLS, config.ClientAPIHost(), config.ClientAPIPort())
+
+	if token, err := getAuthToken(config.ClientAPIBase()); err != nil {
+		log.Warn().Err(err).Msg("Failed to read access tokens – API calls will be without authorization")
+	} else if token != nil {
+		apiClient.DefaultHeaders["Authorization"] = token.String()
+	}
+
+	return apiClient
 }
 
 func GetAPIClientV2() *clientv2.Client {
@@ -37,17 +45,23 @@ func GetAPIClientV2() *clientv2.Client {
 		clientv2.WithHeaders(headers),
 	}
 
-	token, err := ReadToken(base)
-	if err != nil {
+	if token, err := getAuthToken(base); err != nil {
 		log.Warn().Err(err).Msg("Failed to read access tokens – API calls will be without authorization")
+	} else if token != nil {
+		opts = append(opts, clientv2.WithHTTPAuth(token))
 	}
 
+	return clientv2.New(base, opts...)
+}
+
+func getAuthToken(baseURL string) (*apimodels.HTTPCredential, error) {
+	token, err := ReadToken(baseURL)
 	if token != "" {
-		opts = append(opts, clientv2.WithHTTPAuth(&apimodels.HTTPCredential{
+		return &apimodels.HTTPCredential{
 			Scheme: "Bearer",
 			Value:  token,
-		}))
+		}, err
 	}
 
-	return clientv2.New(base, opts...)
+	return nil, err
 }
diff --git a/cmd/util/hook/version.go b/cmd/util/hook/version.go
index 09c44b1045..15f164d1cd 100644
--- a/cmd/util/hook/version.go
+++ b/cmd/util/hook/version.go
@@ -4,8 +4,8 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/bacalhau-project/bacalhau/pkg/config"
-	"github.com/bacalhau-project/bacalhau/pkg/publicapi/client"
+	"github.com/bacalhau-project/bacalhau/cmd/util"
+	"github.com/bacalhau-project/bacalhau/pkg/models"
 	"github.com/bacalhau-project/bacalhau/pkg/version"
 	"github.com/spf13/cobra"
 )
@@ -16,11 +16,17 @@ var printMessage *string = nil
 // background. There should be no output if the check fails or the context is
 // cancelled before the check can complete.
 func StartUpdateCheck(cmd *cobra.Command, args []string) {
-	legacyTLS := client.LegacyTLSSupport(config.ClientTLSConfig())
-
 	version.RunUpdateChecker(
 		cmd.Context(),
-		client.NewAPIClient(legacyTLS, config.ClientAPIHost(), config.ClientAPIPort()).Version,
+		func(ctx context.Context) (*models.BuildVersionInfo, error) {
+			if response, err := util.GetAPIClientV2().Agent().Version(ctx); err != nil {
+				return nil, err
+			} else if response != nil {
+				return response.BuildVersionInfo, nil
+			} else {
+				return nil, nil
+			}
+		},
 		func(_ context.Context, ucr *version.UpdateCheckResponse) { printMessage = &ucr.Message },
 	)
 }
diff --git a/pkg/authz/policies/policy_ns_anon.rego b/pkg/authz/policies/policy_ns_anon.rego
index c11404d4a1..2870708816 100644
--- a/pkg/authz/policies/policy_ns_anon.rego
+++ b/pkg/authz/policies/policy_ns_anon.rego
@@ -10,6 +10,10 @@ http_safe_methods := ["GET", "HEAD", "OPTIONS"]
 http_unsafe_methods := ["PUT", "DELETE", "POST"]
 
 
+is_legacy_api if {
+    input.http.path[2] == "requester"
+}
+
 # Allow writing jobs if the access token has namespace write access
 allow if {
     input.http.path == job_endpoint
@@ -29,9 +33,24 @@ allow if {
 # Allow reading all other endpoints, inclduing by users who don't have a token
 allow if {
     input.http.path != job_endpoint
+    not is_legacy_api
     input.http.method in http_safe_methods
 }
 
+# Allow access to legacy job APIs which will do authz internally
+allow if {
+    is_legacy_api
+    input.http.path[3] in ["submit", "cancel"]
+
+    token_namespaces
+}
+
+# Allow reading other non-job V1 APIs without a token
+allow if {
+    is_legacy_api
+    not input.http.path[3] in ["submit", "cancel"]
+}
+
 # Allow posting to auth endpoints, neccessary to get a token in the first place
 allow if {
     input.http.path[2] == "auth"
diff --git a/pkg/publicapi/client/client.go b/pkg/publicapi/client/client.go
index 4665353079..133b83bb81 100644
--- a/pkg/publicapi/client/client.go
+++ b/pkg/publicapi/client/client.go
@@ -183,9 +183,11 @@ func (apiClient *APIClient) do(ctx context.Context, req *http.Request, resData a
 				string(responseBody)))
 		}
 
-		if !reflect.DeepEqual(serverError, bacerrors.BacalhauErrorInterface(nil)) {
+		if !reflect.DeepEqual(serverError, &bacerrors.ErrorResponse{}) {
 			return serverError
 		}
+
+		return errors.New(string(responseBody))
 	}
 
 	err = json.NewDecoder(res.Body).Decode(resData)
diff --git a/pkg/publicapi/client/legacy_apis.go b/pkg/publicapi/client/legacy_apis.go
index 28793a5974..d7507ea6bf 100644
--- a/pkg/publicapi/client/legacy_apis.go
+++ b/pkg/publicapi/client/legacy_apis.go
@@ -53,8 +53,7 @@ func (apiClient *APIClient) List(
 
 	var res legacymodels.ListResponse
 	if err := apiClient.DoPost(ctx, "/api/v1/requester/list", req, &res); err != nil {
-		e := err
-		return nil, e
+		return nil, err
 	}
 
 	return res.Jobs, nil
diff --git a/pkg/publicapi/middleware/authz.go b/pkg/publicapi/middleware/authz.go
index 2cc5864a87..6a03e2b37e 100644
--- a/pkg/publicapi/middleware/authz.go
+++ b/pkg/publicapi/middleware/authz.go
@@ -15,7 +15,7 @@ func Authorize(authorizer authz.Authorizer) echo.MiddlewareFunc {
 			if result, err := authorizer.Authorize(c.Request()); err != nil {
 				return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
 			} else if !result.Approved {
-				return echo.NewHTTPError(http.StatusForbidden, result.Reason)
+				return echo.NewHTTPError(http.StatusForbidden, "unauthorized. "+result.Reason)
 			} else {
 				return next(c)
 			}


Test patch:
diff --git a/test/secret_auth.sh b/test/secret_auth.sh
index 435d2312dc..f7990d676d 100755
--- a/test/secret_auth.sh
+++ b/test/secret_auth.sh
@@ -22,6 +22,14 @@ testcase_valid_token_is_accepted() {
     assert_equal '[]' $stdout
 }
 
+testcase_token_works_for_v1_api() {
+    setup
+
+    subject "echo insert a secret string here | bacalhau list --output=json"
+    assert_equal 0 $status
+    assert_equal '[]' $stdout
+}
+
 testcase_invalid_token_is_rejected_and_not_persisted() {
     setup