vulnerabilidades top seguridad que metodologia erlang aop elixir

erlang - top - Inyección de código en tiempo de compilación Elixir/AOP



owasp seguridad (4)

Me gustaría proponer un enfoque completamente diferente al problema: GenEvent .

La forma ExUnit funciona para ExUnit porque es un marco de prueba y puede imponer restricciones sobre cómo se ejecuta la prueba y, por lo tanto, cómo se debe escribir su código. Para su aplicación actual, que incluye el registro y otras cosas, un sistema basado en eventos parece ser una solución mucho más sólida que también puede beneficiarse fácilmente de la concurrencia.

La idea es que inicie GenEvent y le envíe eventos. Su registrador será un controlador instalado en GenEvent. Puede elegir que los eventos publicados sean sincronizados o asincrónicos. El ejemplo en nuestra documentación cubre exactamente este caso.

Anteriormente utilicé el código de estilo AOP para separar Logic de Logging y estoy muy satisfecho con los resultados. Reconozco que las opiniones sobre AOP varían, pero me gustaría encontrar una solución en Elixir, incluso si no termino usándola en prod.

El ejemplo más cercano que he visto es la devolución de llamada de configuración dentro de ExUnit, que permite la ejecución del código antes de ejecutar cada prueba; Me gustaría hacer algo similar, pero no he podido confundir la fuente ExUnit para comprender las intuiciones allí.

En forma de código:

defmodule Project.Logic do LoggingInjection.inject Project.Logging def work_do_stuff(arg) do #... #returns some_result end end

en un archivo de código separado:

defmodule Project.Logging do #called just before Project.Logic.work_do_stuff with the same args def before_work_do_stuff(arg) do Log.write("about to work_do_stuff with #{inspect arg}") end # def after_work_do_stuff(some_result) implicitly defined as no-op, # but could be overridden. end

y finalmente, la verdadera pregunta: ¿Cuál es el código para habilitar esta magia?

defmodule LoggingInjection do defmacro inject(logging_module) do #What goes here? end end


No es AOP, pero si está interesado en observar las llamadas a funciones en tiempo de ejecución, puede considerar buscar en el trazo de Erlang.

Por ejemplo, puede usar :dbg para configurar huellas dinámicas de llamadas a funciones. A continuación, le mostramos cómo rastrear todas las llamadas a IO :

iex> :dbg.tracer iex> :dbg.p(:all, [:call]) iex> :dbg.tp(IO, [{:_, [], [{:return_trace}]}]) (<0.53.0>) call ''Elixir.IO'':puts(stdio,<<"/e[33m{:ok, [{:matched, :nonode@nohost, 28}, {:saved, 1}]}/e[0m">>) (<0.53.0>) returned from ''Elixir.IO'':puts/2 -> ok (<0.59.0>) call ''Elixir.IO'':gets(stdio,<<"iex(4)> ">>)

De vez en cuando uso esta característica para conectarme a un nodo BEAM en ejecución y analizar el sistema en ejecución. Asegúrese de detener los rastros con :dbg.stop_clear una vez que haya terminado.

Si desea manejar manualmente los mensajes de seguimiento y hacer algo específico sobre ellos (por ejemplo, registrarlos en un archivo), puede usar :erlang.trace . Aquí hay un gen_server simple que rastrea las llamadas a varios módulos:

defmodule Tracer do use GenServer def start(modules), do: GenServer.start(__MODULE__, modules) def init(modules) do :erlang.trace(:all, true, [:call]) for module <- modules do :erlang.trace_pattern({module, :_, :_}, [{:_, [], [{:return_trace}]}]) end {:ok, nil} end def handle_info({:trace, _, :call, {mod, fun, args}}, state) do IO.puts "called #{mod}.#{fun}(#{Enum.join(Enum.map(args, &inspect/1), ",")})" {:noreply, state} end def handle_info({:trace, _, :return_from, {mod, fun, arity}, res}, state) do IO.puts "#{mod}.#{fun}/#{arity} returned #{res}" {:noreply, state} end def handle_info(_, state), do: {:noreply, state} end

Para usarlo, simplemente inícielo y proporcione la lista de módulos que desea rastrear:

iex(2)> Tracer.start([IO]) {:ok, #PID<0.84.0>} called Elixir.IO.puts(:stdio,"/e[33m{:ok, #PID<0.84.0>}/e[0m") Elixir.IO.puts/2 returned ok call Elixir.IO.gets(:stdio,"iex(3)> ")

El rastreo es muy poderoso y puedes hacer todo tipo de cosas con él. No lo utilicé para un sistema de registro, así que no puedo decir qué tan problemático será, así que si tomas este camino, te aconsejo que seas cuidadoso y observes el rendimiento, ya que un exceso de rastreo podría sobrecargar su sistema


Si no te importa decorar tus funciones con atributos, puedes utilizar: https://github.com/arjan/decorator

Es bastante discreto. Puede hacer un LogDecorator así:

defmodule LogDecorator do use Decorator.Define, [log: 0] require Logger def log(body, context) do quote do self = unquote(__MODULE__) data = unquote(Macro.escape(context)) |> Map.delete(:__struct__) |> Map.merge(%{ args: self.listify(unquote(context.args)), returned: unquote(body) }) self.trace_function(data) data.returned end end def listify(arg) when is_list(arg), do: arg def listify(arg) when is_map(arg), do: Enum.into(arg, []) def listify(arg), do: [arg] def trace_function(ctx) do Logger.info("Function Invocation #{inspect ctx}") end end

Para usarlo colocaría atributos en las funciones que desea rastrear de esta manera:

defmodule DecoratorSpike do use LogDecorator @decorate log() def square(a) do a * a end @decorate log() def add(a, b) do a + b end end


Me encontré con esta pregunta con la misma necesidad. No es mi solución, ponerlo aquí para cualquier referencia, encontré una excelente serie de tutoriales sobre macros y cómo extraer la información de la función por Saša Jurić y una forma de anular la macro ''def'' también por él.

Básicamente:

  1. Para decorar declaración de función, puede excluir la importación predeterminada de la macro ''def'' desde el módulo Kernel.

    import Kernel, except: [def: 2]

  2. Proporcione una implementación personalizada de la macro ''def'' que extraerá la información de declaración de función para el nodo AST - nombre y argumentos - y generará un código que haga el ''Kernel.def'' real, dentro de él haga el registro, por ejemplo, y haga la código original para la función.

    defmacro def(fn_call_ast, fn_opts_ast) do result_fn_call_ast = process_call_ast fn_call_ast result_fn_opts_ast = process_opts_ast fn_opts_ast quote do Kernel.def( unquote(result_fn_call_ast), unquote(result_fn_opts_ast)) end end

He reunido esas dos soluciones y he creado un paquete para implementarlas. Destinado solo para experimentar Entonces puedes hacer:

defmodule User do use FunctionDecorating decorate_fn_with LogDecorator def say(word) do word end end iex>User.say("hello") #PID<0.86.0> [x] Elixir.User.say(["hello"]) -> "hello" "hello"