Skip to content

Commit

Permalink
Document how to use ExDoc with encrypted debug info
Browse files Browse the repository at this point in the history
Closes #1928.

- Update documentation
- Update test helper so that debug info options are forwarded to the compiler
- Add basic tests
- Update changelog
- Use "CI" environment variable to skip test
  • Loading branch information
wojtekmach authored and voughtdq committed Sep 11, 2024
1 parent 8948c6a commit 5599dd1
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 60 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
runs-on: ubuntu-20.04
env:
MIX_ENV: test
CI: true
strategy:
fail-fast: false
matrix:
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Improve warning when referencing type from a private module
* Rename "Search HexDocs package" modal to "Go to package docs". Support built-in Erlang/OTP
apps.
* Support modules with encrypted debug info
* Document how to use `.erlang.crypt` with ExDoc

* Bug fixes
* Switch anchor `title` to `aria-label`
Expand Down
62 changes: 4 additions & 58 deletions lib/mix/tasks/docs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -213,64 +213,10 @@ defmodule Mix.Tasks.Docs do
## Encrypted debug info
If a module is compiled with [encrypted debug info](`:compile.file/2`), ExDoc will not be able to
extract its documentation without first setting a decryption function or utilizing
`.erlang.crypt` as prescribed by `m::beam_lib#module-encrypted-debug-information`. Two
convenience options (see below) are provided to avoid having to call `:beam_lib.crypto_key_fun/1`
out-of-band and/or to avoid using `.erlang.crypt`.
If you prefer to set the key out-of-band, follow the instructions provided in the
`m::beam_lib#module-encrypted-debug-information` module documentation.
> ### Key exposure {: .warning}
>
> Avoid adding keys directly to your `mix.exs` file. Instead, use an environment variable, an
> external documentation config file, or a
> [closure](https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/sensitive_data#wrapping).
### `:debug_info_key`
This option can be provided if you only have one key for all encrypted modules. A `t:charlist/0`,
`t:String.t/0`, or tuple of `{:des3_cbc, charlist() | String.t()}` can be used.
### `:debug_info_fn` / `:debug_info_fun`
This option can be provided if you have multiple keys, want more control over key retrieval, or
would like to wrap your key(s) in a closure. `:debug_info_key` will be ignored if this option is
also present. `:debug_info_fun` will be ignored if `:debug_info_fn` is already present.
While a module can be encrypted using a tuple key such as `{:des3_cbc, ~c"secret"}`, the function
that provides the key must return a regular charlist. In other words, the function should return
`~c"secret"`, not `{:des3_cbc, ~c"secret"}`.
A basic function that provides the decryption key `SECRET`:
<!-- tabs-open -->
### Elixir
⚠️ The key returned must be a `t:charlist/0`!
```elixir
fn
:init -> :ok,
{:debug_info, _mode, _module, _filename} -> ~c"SECRET"
:clear -> :ok
end
```
### Erlang
```erlang
fun
(init) -> ok;
({debug_info, _Mode, _Module, _Filename}) -> "SECRET";
(clear) -> ok
end.
```
<!-- tabs-close -->
See `:beam_lib.crypto_key_fun/1` for more information.
extract its documentation without preparation. ExDoc supports using `.erlang.crypt` to decrypt
debug information. Consult the
[`.erlang.crypt` section in the `:beam_lib` documentation](`m::beam_lib#module-erlang-crypt`)
for more information.
## Groups
Expand Down
55 changes: 55 additions & 0 deletions test/ex_doc/retriever/erlang_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,61 @@ defmodule ExDoc.Retriever.ErlangTest do
|> Erlang.autolink_spec(current_module: :mod, current_kfa: {:type, :type, 0}) ==
"type() :: #a{a :: <a href=\"https://www.erlang.org/doc/apps/erts/erlang.html#t:pos_integer/0\">pos_integer</a>(), b :: <a href=\"https://www.erlang.org/doc/apps/erts/erlang.html#t:non_neg_integer/0\">non_neg_integer</a>(), c :: <a href=\"https://www.erlang.org/doc/apps/erts/erlang.html#t:atom/0\">atom</a>(), d :: <a href=\"https://www.erlang.org/doc/apps/erts/erlang.html#t:term/0\">term</a>(), e :: <a href=\"https://www.erlang.org/doc/apps/erts/erlang.html#t:term/0\">term</a>()}."
end

@tag :ci
test "modules with encrypted debug info", c do

Check failure on line 415 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.17, 27, lint)

test docs_from_modules/2 modules with encrypted debug info (ExDoc.Retriever.ErlangTest)
File.cp!("test/fixtures/.erlang.crypt", ".erlang.crypt")

erlc(
c,
:debug_info_mod,
~S"""
-module(debug_info_mod).
-moduledoc("mod docs.").
-export([function1/0]).
-export_type([foo/0]).
-doc("foo/0 docs.").
-type foo() :: atom().
-doc("function1/0 docs.").
-spec function1() -> atom().
function1() -> ok.
""",
debug_info_key: ~c"SECRET"
)

{[mod], []} = Retriever.docs_from_modules([:debug_info_mod], %ExDoc.Config{})

assert %ExDoc.ModuleNode{
moduledoc_file: moduledoc_file,
docs: [function1],
id: "debug_info_mod",
module: :debug_info_mod,
title: "debug_info_mod",
typespecs: [foo]
} = mod

assert DocAST.to_string(mod.doc) =~ "mod docs."
assert DocAST.to_string(function1.doc) =~ "function1/0 docs."
assert DocAST.to_string(foo.doc) =~ "foo/0 docs."
assert moduledoc_file =~ "debug_info_mod.erl"

erlc(
c,
:debug_info_mod2,
~S"""
-module(debug_info_mod2).
-moduledoc("mod docs.").
""",
debug_info_key: {:des3_cbc, ~c"PASSWORD"}
)

assert {[%ExDoc.ModuleNode{module: :debug_info_mod2}], []} =
Retriever.docs_from_modules([:debug_info_mod2], %ExDoc.Config{})

File.rm!(".erlang.crypt")
end
end

describe "docs_from_modules/2 edoc" do
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/.erlang.crypt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[{debug_info, des3_cbc, debug_info_mod, "SECRET"},
{debug_info, des3_cbc, [], "PASSWORD"}].
4 changes: 3 additions & 1 deletion test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
otp_eep48? = Code.ensure_loaded?(:edoc_doclet_chunks)
otp_eep59? = Code.ensure_loaded?(:beam_doc)
ci? = System.get_env("CI") == "true"

exclude = [
otp_eep48: not otp_eep48?,
otp_eep59: not otp_eep59?,
otp_has_docs: not match?({:docs_v1, _, _, _, _, _, _}, Code.fetch_docs(:array))
otp_has_docs: not match?({:docs_v1, _, _, _, _, _, _}, Code.fetch_docs(:array)),
ci: not ci?
]

ExUnit.start(exclude: Enum.filter(exclude, &elem(&1, 1)))
Expand Down

0 comments on commit 5599dd1

Please sign in to comment.