diff --git a/agent/grpc-external/services/resource/write_status.go b/agent/grpc-external/services/resource/write_status.go index 1c41da4b4..ec7c0da2f 100644 --- a/agent/grpc-external/services/resource/write_status.go +++ b/agent/grpc-external/services/resource/write_status.go @@ -13,16 +13,31 @@ import ( "github.com/oklog/ulid/v2" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/internal/storage" "github.com/hashicorp/consul/proto-public/pbresource" ) func (s *Server) WriteStatus(ctx context.Context, req *pbresource.WriteStatusRequest) (*pbresource.WriteStatusResponse, error) { + authz, err := s.getAuthorizer(tokenFromContext(ctx)) + if err != nil { + return nil, err + } + + // check acls + err = authz.ToAllowAuthorizer().OperatorWriteAllowed(&acl.AuthorizerContext{}) + switch { + case acl.IsErrPermissionDenied(err): + return nil, status.Error(codes.PermissionDenied, err.Error()) + case err != nil: + return nil, status.Errorf(codes.Internal, "failed operator:write allowed acl: %v", err) + } + if err := validateWriteStatusRequest(req); err != nil { return nil, err } - _, err := s.resolveType(req.Id.Type) + _, err = s.resolveType(req.Id.Type) if err != nil { return nil, err } diff --git a/agent/grpc-external/services/resource/write_status_test.go b/agent/grpc-external/services/resource/write_status_test.go index ef527bbdd..35eb26385 100644 --- a/agent/grpc-external/services/resource/write_status_test.go +++ b/agent/grpc-external/services/resource/write_status_test.go @@ -8,15 +8,63 @@ import ( "testing" "github.com/oklog/ulid/v2" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "github.com/hashicorp/consul/acl/resolver" "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource/demo" "github.com/hashicorp/consul/proto-public/pbresource" ) +func TestWriteStatus_ACL(t *testing.T) { + type testCase struct { + authz resolver.Result + assertErrFn func(error) + } + testcases := map[string]testCase{ + "denied": { + authz: AuthorizerFrom(t, demo.ArtistV2WritePolicy), + assertErrFn: func(err error) { + require.Error(t, err) + require.Equal(t, codes.PermissionDenied.String(), status.Code(err).String()) + }, + }, + "allowed": { + authz: AuthorizerFrom(t, demo.ArtistV2WritePolicy, `operator = "write"`), + assertErrFn: func(err error) { + require.NoError(t, err) + }, + }, + } + + for desc, tc := range testcases { + t.Run(desc, func(t *testing.T) { + server := testServer(t) + client := testClient(t, server) + + mockACLResolver := &MockACLResolver{} + mockACLResolver.On("ResolveTokenAndDefaultMeta", mock.Anything, mock.Anything, mock.Anything). + Return(tc.authz, nil) + server.ACLResolver = mockACLResolver + demo.Register(server.Registry) + + artist, err := demo.GenerateV2Artist() + require.NoError(t, err) + + rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: artist}) + require.NoError(t, err) + artist = rsp.Resource + + // exercise ACL + _, err = client.WriteStatus(testContext(t), validWriteStatusRequest(t, artist)) + tc.assertErrFn(err) + }) + } +} + func TestWriteStatus_InputValidation(t *testing.T) { server := testServer(t) client := testClient(t, server)