AshAuthentication.AddOn.AuditLog.IpPrivacy (ash_authentication v4.15.0)

View Source

Provides IP address privacy transformations for audit logging.

The audit log add-on transforms client IP addresses with this module before it writes them. Select the transformation with the ip_privacy_mode option of the audit_log add-on.

Modes

  • :none - store the address unchanged. This is the default.
  • :truncate - keep the network prefix of the address and drop the host part.
  • :exclude - do not store the address at all.
  • :hash - store a keyed digest of the address.

The :hash mode

:hash computes an HMAC-SHA256 of the address under a salt which you must configure, then keeps the first 64 bits of the result:

config :my_app, audit_log_ip_salt: System.fetch_env!("AUDIT_LOG_IP_SALT")

The salt can be a string, or a {module, function, arguments} tuple which returns a string. The add-on reads it from the application which owns the resource being audited, so each application in an umbrella has its own salt.

Deprecated salt locations

Earlier versions read the salt from this library's own application name. Both config :ash_authentication, audit_log_ip_salt: ... and config :ash_authentication, secret: ... still work, and are used when the owning application configures nothing. Both are deprecated, both warn at start up, and both will be removed in a future release.

Run mix ash_authentication.upgrade to move the setting. Keep the value identical: a different salt changes every stored digest, so entries written before the move stop correlating with entries written after it.

The salt must be secret and it must have high entropy. IPv4 has only 2^32 addresses, so anybody who knows the salt can compute the digest of every address and reverse the stored values. There is no salt which is safe to share between deployments, and there is no safe default. :hash therefore fails closed: AshAuthentication.Supervisor refuses to start when a resource selects :hash without a salt, and hash_ip/1 raises for the same reason.

A digest still identifies one address, which lets you count the events which come from it. Use :truncate or :exclude when you do not need that. Neither depends on a secret, so neither can fail in this way.

Hashing is not anonymisation. A digest of an IP address remains personal data under the GDPR and similar laws, because it still singles out one subscriber. Protect the stored digests the same way you would protect the raw addresses.

Summary

Functions

Apply privacy transformation to an IP address string.

Apply privacy transformation to request data containing IP addresses.

Hash an IP address with the configured salt.

Truncate an IP address to a network prefix.

Check the given resources for an audit log add-on which hashes IP addresses without a configured salt.

Functions

apply_privacy(ip, arg2, opts)

@spec apply_privacy(String.t() | nil, atom(), map()) :: String.t() | nil

Apply privacy transformation to an IP address string.

Options

  • :mode - The privacy mode (:none, :hash, :truncate, :exclude)
  • :truncation_masks - Map with :ipv4 and :ipv6 keys for truncation bits
  • :otp_app - The application whose configuration holds the hash salt. Only used by :hash.

apply_to_request(request, mode, opts)

@spec apply_to_request(map(), atom(), map()) :: map()

Apply privacy transformation to request data containing IP addresses.

Transforms the following fields:

  • remote_ip
  • x_forwarded_for (list of IPs)
  • forwarded (list of forwarded headers)

hash_ip(ip, otp_app \\ nil)

@spec hash_ip(String.t(), atom() | nil) :: String.t() | nil

Hash an IP address with the configured salt.

Computes an HMAC-SHA256 of the address and keeps the first 64 bits of the digest.

Raises when no salt is configured. See the module documentation for the reason and for the configuration keys.

truncate_ip(ip, masks)

@spec truncate_ip(String.t(), map()) :: String.t() | nil

Truncate an IP address to a network prefix.

For IPv4: Applies a subnet mask (e.g., /24 keeps first 3 octets) For IPv6: Applies a prefix length (e.g., /48 keeps first 3 hextets)

verify_hash_salt!(otp_app, resources)

@spec verify_hash_salt!(atom() | nil, [Ash.Resource.t()]) :: :ok

Check the given resources for an audit log add-on which hashes IP addresses without a configured salt.

Raises when it finds one. AshAuthentication.Supervisor calls this when it starts, so a deployment which is missing the salt fails to boot instead of writing reversible digests.

Warns when the salt only resolves from the deprecated :ash_authentication configuration.