Skip to content

Commit

Permalink
Evaluate operations against a policy enforcer over NATS (#442)
Browse files Browse the repository at this point in the history
* initial scaffold of policy check

Signed-off-by: Brooks Townsend <[email protected]>

* initial implementation of policy for start_actor

Signed-off-by: Brooks Townsend <[email protected]>

* began process of validating policy with actor

Signed-off-by: Brooks Townsend <[email protected]>

* added perform_invocation, policy timeout

Signed-off-by: Brooks Townsend <[email protected]>

* added quick check to allow if policy is disabled

Signed-off-by: Brooks Townsend <[email protected]>

* implemented invalidation of policy

Signed-off-by: Brooks Townsend <[email protected]>

* added logic to invalidate based on request

Signed-off-by: Brooks Townsend <[email protected]>

* cleaned up warnings, used module constants

Signed-off-by: Brooks Townsend <[email protected]>

* ensured policy actions deny by default

Signed-off-by: Brooks Townsend <[email protected]>

* last second cleanup

Signed-off-by: Brooks Townsend <[email protected]>

* corrected policy test for new behavior

Signed-off-by: Brooks Townsend <[email protected]>

* refactored policy to use camelCase for compatibility

Signed-off-by: Brooks Townsend <[email protected]>

* converted date time to unix time

Signed-off-by: Brooks Townsend <[email protected]>

* updated policy actor with requestId

Signed-off-by: Brooks Townsend <[email protected]>

* fixed match for request ID

Signed-off-by: Brooks Townsend <[email protected]>

Signed-off-by: Brooks Townsend <[email protected]>
  • Loading branch information
brooksmtownsend authored Aug 17, 2022
1 parent c164e1f commit c90370e
Show file tree
Hide file tree
Showing 14 changed files with 619 additions and 27 deletions.
3 changes: 2 additions & 1 deletion host_core/lib/host_core.ex
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ defmodule HostCore do
{HostCore.Host, config},
{HostCore.HeartbeatEmitter, config},
{HostCore.Jetstream.Client, config}
]
] ++
HostCore.Policy.Manager.spec()
end

defp post_process_config(config) do
Expand Down
79 changes: 70 additions & 9 deletions host_core/lib/host_core/actors/actor_module.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule HostCore.Actors.ActorModule do

@chunk_threshold 900 * 1024
@thirty_seconds 30_000
@perform_invocation "perform_invocation"

require Logger
alias HostCore.WebAssembly.Imports
Expand Down Expand Up @@ -242,6 +243,7 @@ defmodule HostCore.Actors.ActorModule do
inv["origin"]["link_name"],
inv["origin"]["contract_id"]
)
|> policy_check_invocation(inv["origin"], inv["target"])
|> perform_invocation(
inv["operation"],
check_dechunk_inv(
Expand Down Expand Up @@ -405,7 +407,74 @@ defmodule HostCore.Actors.ActorModule do
{agent, Enum.member?(caps, contract_id)}
end

defp perform_invocation({agent, true}, operation, payload) do
defp policy_check_invocation({agent, false}, _, _), do: {{agent, false}, %{}}
# Returns a tuple in the form of {{agent, allowed?}, policy_result}
defp policy_check_invocation({agent, true}, source, target) do
with {:ok, _topic} <- HostCore.Policy.Manager.policy_topic(),
{:ok, {_pk, source_claims}} <-
HostCore.Claims.Manager.lookup_claims(source["public_key"]),
{:ok, {_pk, target_claims}} <-
HostCore.Claims.Manager.lookup_claims(target["public_key"]) do
{expires_at, expired} =
case source_claims[:exp] do
nil -> {nil, false}
# If the current UTC time is greater than the expiration time, it's expired
time -> {time, DateTime.utc_now() > time}
end

{{agent, true},
HostCore.Policy.Manager.evaluate_action(
%{
publicKey: source["public_key"],
contractId: source["contract_id"],
linkName: source["link_name"],
capabilities: source_claims[:caps],
issuer: source_claims[:iss],
issuedOn: source_claims[:iat],
expiresAt: expires_at,
expired: expired
},
%{
publicKey: target["public_key"],
contractId: target["contract_id"],
linkName: target["link_name"],
issuer: target_claims[:iss]
},
@perform_invocation
)}
else
# Failed to check claims for source or target, denying
:policy_eval_disabled -> {{agent, true}, %{permitted: true}}
:error -> {{agent, true}, %{permitted: false}}
end
end

# Deny invocation if actor is missing capability claims
defp perform_invocation({{_agent, false}, _policy_res}, operation, _payload) do
Logger.error("Actor does not have proper capabilities to receive this invocation",
operation: operation
)

{:error, "actor is missing capability claims"}
end

# Deny invocation if policy enforcer does not permit it
defp perform_invocation(
{{_agent, true}, %{permitted: false} = policy_res},
_operation,
_payload
) do
message = Map.get(policy_res, :message, "reason not specified")

Logger.error("Policy denied invocation:",
message: message
)

{:error, "Policy denied invocation: #{message}"}
end

# Perform invocation if actor is allowed and policy enforcer permits
defp perform_invocation({{agent, true}, %{permitted: true}}, operation, payload) do
raw_state = Agent.get(agent, fn content -> content end)

Tracer.with_span "Wasm Guest Call", kind: :client do
Expand Down Expand Up @@ -449,14 +518,6 @@ defmodule HostCore.Actors.ActorModule do
end
end

defp perform_invocation({_agent, false}, operation, _payload) do
Logger.error("Actor does not have proper capabilities to receive this invocation",
operation: operation
)

{:error, "actor is missing capability claims"}
end

defp to_guest_call_result({:ok, [res]}, agent) do
state = Agent.get(agent, fn content -> content end)

Expand Down
38 changes: 32 additions & 6 deletions host_core/lib/host_core/actors/actor_supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule HostCore.Actors.ActorSupervisor do
require OpenTelemetry.Tracer, as: Tracer
require Logger

@start_actor "start_actor"
def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
Expand Down Expand Up @@ -34,12 +35,27 @@ defmodule HostCore.Actors.ActorSupervisor do
{:error, err}

{:ok, claims} ->
if other_oci_already_running?(oci, claims.public_key) do
Tracer.set_status(:error, "Already running")

{:error,
"Cannot start new instance of #{claims.public_key} from OCI '#{oci}', it is already running with different OCI reference. To upgrade an actor, use live update."}
else
with %{permitted: true} <-
HostCore.Policy.Manager.evaluate_action(
%{
publicKey: "",
contractId: "",
linkName: "",
capabilities: [],
issuer: "",
issuedOn: "",
expiresAt: DateTime.utc_now() |> DateTime.add(60) |> DateTime.to_unix(),
expired: false
},
%{
publicKey: claims.public_key,
issuer: claims.issuer,
contractId: nil,
linkName: nil
},
@start_actor
),
false <- other_oci_already_running?(oci, claims.public_key) do
# Start `count` instances of this actor
case 1..count
|> Enum.reduce_while([], fn _count, pids ->
Expand Down Expand Up @@ -69,6 +85,16 @@ defmodule HostCore.Actors.ActorSupervisor do
Tracer.set_status(:ok, "")
{:ok, pids}
end
else
true ->
Tracer.set_status(:error, "Already running")

{:error,
"Cannot start new instance of #{claims.public_key} from OCI '#{oci}', it is already running with different OCI reference. To upgrade an actor, use live update."}

%{permitted: false, message: message, requestId: request_id} ->
Tracer.set_status(:error, "Policy denied starting actor, request: #{request_id}")
{:error, "Starting actor #{claims.public_key} denied: #{message}"}
end
end
end
Expand Down
11 changes: 9 additions & 2 deletions host_core/lib/host_core/config_plan.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ defmodule HostCore.ConfigPlan do
{:enable_structured_logging, "WASMCLOUD_STRUCTURED_LOGGING_ENABLED", required: false},
{:structured_log_level, "WASMCLOUD_STRUCTURED_LOG_LEVEL",
required: false, map: &string_to_loglevel/1},
{:enable_ipv6, "WASMCLOUD_ENABLE_IPV6", required: false, map: &String.to_integer/1}
{:enable_ipv6, "WASMCLOUD_ENABLE_IPV6", required: false, map: &String.to_integer/1},
{:policy_topic, "WASMCLOUD_POLICY_TOPIC", required: false},
{:policy_changes_topic, "WASMCLOUD_POLICY_CHANGES_TOPIC", required: false},
{:policy_timeout, "WASMCLOUD_POLICY_TIMEOUT",
required: false, map: &String.to_integer/1}
]
}
]
Expand Down Expand Up @@ -90,7 +94,10 @@ defmodule HostCore.ConfigPlan do
{:config_service_enabled, "config_service_enabled", required: false, default: ""},
{:enable_structured_logging, "structured_logging_enabled", required: false, default: false},
{:structured_log_level, "structured_log_level", required: false, default: :info},
{:enable_ipv6, "enable_ipv6", required: false, default: 0}
{:enable_ipv6, "enable_ipv6", required: false, default: 0},
{:policy_topic, "policy_topic", required: false},
{:policy_changes_topic, "policy_changes_topic", required: false},
{:policy_timeout, "policy_timeout", required: false, default: 1_000}
]
end

Expand Down
1 change: 1 addition & 0 deletions host_core/lib/host_core/control_interface/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ defmodule HostCore.ControlInterface.Server do

{:error, e} ->
Tracer.set_status(:error, inspect(e))
Logger.error("#{inspect(e)}")
publish_provider_start_failed(start_provider_command, inspect(e))
end
end
Expand Down
1 change: 1 addition & 0 deletions host_core/lib/host_core/host.ex
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ defmodule HostCore.Host do
:ets.new(:refmap_table, [:named_table, :set, :public])
:ets.new(:callalias_table, [:named_table, :set, :public])
:ets.new(:config_table, [:named_table, :set, :public])
:ets.new(:policy_table, [:named_table, :set, :public])
end

def set_credsmap(credsmap) do
Expand Down
Loading

0 comments on commit c90370e

Please sign in to comment.