Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ MirrorNeuron Core includes protobuf definitions and generated Elixir modules for

Generated modules live under `lib/mirror_neuron_grpc/`.

`JobService.ClearJobs` is a destructive administrative RPC. It is denied unless the server has `MIRROR_NEURON_GRPC_ADMIN_TOKEN` set and the request includes the same value in `admin_token`.

The separate REST API package is maintained in [`mn-api`](https://github.com/MirrorNeuronLab/mn-api). The Python SDK is maintained in [`mn-python-sdk`](https://github.com/MirrorNeuronLab/mn-python-sdk).

## Project Structure
Expand Down
2 changes: 2 additions & 0 deletions lib/mirror_neuron_grpc/job.pb.ex
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ defmodule Mirrorneuron.Job.V1.ClearJobsRequest do
full_name: "mirrorneuron.job.v1.ClearJobsRequest",
protoc_gen_elixir_version: "0.16.0",
syntax: :proto3

field(:admin_token, 1, type: :string, json_name: "adminToken")
end

defmodule Mirrorneuron.Job.V1.ClearJobsResponse do
Expand Down
25 changes: 24 additions & 1 deletion lib/mirror_neuron_grpc/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ defmodule MirrorNeuron.Grpc.JobServer do
end
end

def clear_jobs(_request, _stream) do
def clear_jobs(request, _stream) do
authorize_clear_jobs!(request)

case MirrorNeuron.Monitor.clear_jobs() do
{:ok, count} ->
%ClearJobsResponse{cleared_count: count}
Expand All @@ -160,6 +162,27 @@ defmodule MirrorNeuron.Grpc.JobServer do
raise GRPC.RPCError, status: GRPC.Status.internal(), message: reason
end
end

defp authorize_clear_jobs!(request) do
configured_token = System.get_env("MIRROR_NEURON_GRPC_ADMIN_TOKEN")
request_token = Map.get(request, :admin_token, "")

unless valid_admin_token?(configured_token, request_token) do
raise GRPC.RPCError,
status: GRPC.Status.permission_denied(),
message: "ClearJobs requires MIRROR_NEURON_GRPC_ADMIN_TOKEN"
end

:ok
end

defp valid_admin_token?(configured_token, request_token)
when is_binary(configured_token) and byte_size(configured_token) > 0 and
is_binary(request_token) do
configured_token == request_token
end

defp valid_admin_token?(_configured_token, _request_token), do: false
end

defmodule MirrorNeuron.Grpc.ClusterServer do
Expand Down
1 change: 1 addition & 0 deletions proto/job.proto
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ message ResumeJobResponse {
}

message ClearJobsRequest {
string admin_token = 1;
}

message ClearJobsResponse {
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/grpc_job_server_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule MirrorNeuron.Grpc.JobServerTest do
use ExUnit.Case, async: false

alias MirrorNeuron.Grpc.JobServer
alias Mirrorneuron.Job.V1.ClearJobsRequest

setup do
old_token = System.get_env("MIRROR_NEURON_GRPC_ADMIN_TOKEN")
System.delete_env("MIRROR_NEURON_GRPC_ADMIN_TOKEN")

on_exit(fn ->
if is_nil(old_token) do
System.delete_env("MIRROR_NEURON_GRPC_ADMIN_TOKEN")
else
System.put_env("MIRROR_NEURON_GRPC_ADMIN_TOKEN", old_token)
end
end)
end

test "clear_jobs rejects unauthenticated requests before deleting jobs" do
error =
assert_raise GRPC.RPCError, fn ->
JobServer.clear_jobs(%ClearJobsRequest{}, nil)
end

assert Exception.message(error) =~ "ClearJobs requires MIRROR_NEURON_GRPC_ADMIN_TOKEN"
end
end
Loading