pages/cheatsheet.cheatmd

# Cheatsheet

## Creating a Wrapper

```
defmodule MySecretData do
  use SensitiveData.Wrapper
end
```

## Getting Data into a Wrapper
{:.col-2}

### SensitiveData `exec/2`

```
SensitiveData.exec(
  fn -> System.fetch_env!("DB_URI") end,
  into: MySecretData)
```

### Wrapper `from/2`

```
MySecretData.from(fn -> System.fetch_env!("DB_URI") end)
```

### From `:stdio`

```
SensitiveData.gets_sensitive("Your password: ",
  into: MySecretData)
```

or alternatively

```
MySecretData.from(fn ->
  SensitiveData.gets_sensitive("Your password: ")
end)
```

## Interacting with Wrapped Data
{:.col-2}

### `map/3`

The sensitive data within a container can easily be modified using `c:SensitiveData.Wrapper.map/3`:

```
# get authentication data in Basic Authentication format
basic_credentials = MySecretData.from(fn ->
  System.fetch_env!("BASIC_AUTH") end)

# later, these credentials need to be converted
# to username & password
map_credentials =
  MySecretData.map(basic_credentials, fn basic ->
    [username, password] =
      basic
      |> Base.decode64!()
      |> String.split(":")
  end)
```

### `exec/3`

Executing functions requiring sensitive data located in a wrapper can be
accomplished with `c:SensitiveData.Wrapper.exec/3`:

```
# get authentication data
credentials = MySecretData.from(fn ->
  System.fetch_env!("BASIC_AUTH") end)

# later, make an API request using these credentials
{:ok, data} =
  MySecretData.exec(credentials, fn basic_auth ->
    get_api_results("/some/endpoint", basic_auth)
  end)
```

## Identifying Wrapper Contents

It is often useful to be able to determine some information about the sensitive
data contained within wrappers, both for programmatic and human debuggability
reasons.

### Guards and Utilities

Functions from the `SensitiveData.Guards` module and utility functions from
the `SensitiveData.Wrapper` module can help with branching logic depending
(somewhat) on the sensitive data within the wrappers:

```
require SensitiveData.Guards

alias SensitiveData.Guards
alias SensitiveData.Wrapper

data = MySecretData.from(fn ->
  %{foo: :yes, bar: :no} end)

case data do
  map when Guards.is_sensitive_map(map) ->
    IO.puts("It's a map with #{Wrapper.sensitive_map_size(map)} items")

  _ ->
    IO.puts("It's not a map")
end
```

### Redaction

It can be useful to have a redacted version of sensitive data, which can be defined at the module
level with the `redactor` option given to `use SensitiveData.Wrapper`, which can be given as:

- an atom - the name of the redactor function located in the same module
- a `{Module, func}` tuple - the redactor function `func` from module `Module` will be used for redaction

In either case, the redaction function will be called with a single argument: the sensitive term.

⚠️️ **BEWARE** ⚠️ If you use a custom redaction strategy, you must ensure it won't leak any
sensitive data under any circumstances.


```
defmodule CreditCard do
  use SensitiveData.Wrapper, redactor: :redactor

  def redactor(card_number) when is_binary(card_number) do
    {<<first_number::binary-1, to_mask::binary>>, last_four} = String.split_at(card_number, -4)

    IO.iodata_to_binary([first_number, List.duplicate("*", String.length(to_mask)), last_four])
  end
end
```

Alternatively, the same result can be achieved with:

```
defmodule MyApp.Redaction do
  def redact_credit_card(card_number) when is_binary(card_number) do
    {<<first_number::binary-1, to_mask::binary>>, last_four} = String.split_at(card_number, -4)

    IO.iodata_to_binary([first_number, List.duplicate("*", String.length(to_mask)), last_four])
  end
end

defmodule CreditCard do
  use SensitiveData.Wrapper, redactor: {MyApp.Redaction, :redact_credit_card}
end
```

In use (either implementation):

```shell
iex(1)> cc = CreditCard.from(fn -> "5105105105105100" end)
#CreditCard<redacted: "5***********5100", ...>
iex(2)> cc.redacted
"5***********5100"
```

### Labeling

It can be useful to label sensitive data, for example to give scope to the given data. Labeling
must be explicitly enabled via the `allow_label: true` option given to `use SensitiveData.Wrapper`.

Once enabled at the module level, each instance of sensitive data can be labeled via the `:label` option.

⚠️ **BEWARE** ⚠️ If you allow labels, you must ensure call sites aren't leaking
sensitive data via label values.


```
defmodule DatabaseCredentials do
  use SensitiveData.Wrapper, allow_label: true
end
```

In use:

```
iex(1)> db = DatabaseCredentials.from(fn -> System.fetch_env("PROD_DB_CREDS") end, label: :prod)
#DatabaseCredentials<label: :prod, ...>
iex(2)> db.label
:prod
iex(3)> DatabaseCredentials.from(fn -> System.fetch_env("PROD_CI_CREDS") end, label: :integration)
#DatabaseCredentials<label: :integration, ...>
```

If labels aren't allowed but one is passed as an option, the label will be ignored
and a warning will be logged.

## One-Off Executions

In certain cases, there's is only a transient need for sensitive data, such as
when connecting to a system: once the connection is made, the credentials
aren't needed anymore.

```
{:ok, pid} =
  SensitiveData.exec(fn ->
    "DATABASE_CONNECTION_URI"
    |> System.fetch_env!()
    |> parse_postgres_uri()
    |> Postgrex.start_link()
  end)
```

## Multiple Wrapper Modules

It can be useful to use several module implementing the `SensitiveData.Wrapper`
behaviour, for example to benefit from different redaction capabilities.

Let's take as an example a credit card number which will be converted into a
payment token which can then be used to charge the user. It would be useful
to be able to "peek" at non-sensitive data (to display to the user in the UI,
for debugging, and so on): this can be achieved via redaction. Naturally, as
the credit card and associated token are different, they'll require different
redaction implementations:

```elixir
defmodule CreditCard do
  use SensitiveData.Wrapper, redactor: :redactor

  def redactor(card_number) when is_binary(card_number) do
    {<<first_number::binary-1, to_mask::binary>>, last_four} = String.split_at(card_number, -4)

    IO.iodata_to_binary([first_number, List.duplicate("*", String.length(to_mask)), last_four])
  end
end

defmodule PaymentToken do
  use SensitiveData.Wrapper, redactor: :redactor

  def redactor(token) when is_binary(token) do
    # the first 10 characters aren't considered sensitive
    {not_sensitive, rest} = String.split_at(token, 10)

    IO.iodata_to_binary([not_sensitive, List.duplicate("*", String.length(rest))])
  end
end
```

For completeness, let's use the following for tokenization logic:

```elixir
defmodule Tokenizer do
  def tokenize(card_number) when is_binary(card_number) do
    "TOK_" <> random_string(20)
  end

  defp random_string(length) do
    :crypto.strong_rand_bytes(length)
    |> Base.url_encode64
    |> binary_part(0, length)
  end
end
```

Note how the `Tokenizer` module contains only "normal" code: it has no knowledge
of the existence of any wrapped values.

We can now easily do the following:

1. obtain sensitive data from the user (their credit card number)
1. store the sensitive data in a wrapper that still allows us to know
    enough about the wrapped value
1. convert the sensitive data to a separate type (and different wrapper)
    while retaining the ability to inspect non-sensitive portions

```elixir
    "Please enter your credit card number: "
    SensitiveData.gets_sensitive(into: CreditCard)
    |> IO.inspect()
    |> CreditCard.exec(&Tokenizer.tokenize/1, into: PaymentToken)
```

Which will output:

```elixir
#CreditCard<redacted: "5***********5100", ...>
#PaymentToken<redacted: "TOK_QTzY5m**************", ...>
```

And of course we can use guards in our code if we want to ensure only wrapped
data of the proper type is used:

```elixir
import SensitiveData.Guards, only: [is_sensitive: 2]

def pay(token) when is_sensitive(token, PaymentToken) do
  # make the payment
end

def pay(credit_card) when is_sensitive(credit_card, CreditCard) do
  credit_card
  |> CreditCard.exec(& &1, into: PaymentToken)
  |> pay()
end
```