Skip to content

Commit b245431

Browse files
authored
Create audit log when archive is sent to device (#1847)
If an archive is sent to a device on connect or from an archive update, an audit log is created specifying device, deployment, and archive details.
1 parent a35d0b0 commit b245431

File tree

3 files changed

+119
-3
lines changed

3 files changed

+119
-3
lines changed

lib/nerves_hub/audit_logs/templates/device_templates.ex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ defmodule NervesHub.AuditLogs.DeviceTemplates do
22
@moduledoc """
33
Templates for and handling of audit logging for device operations.
44
"""
5+
56
alias NervesHub.Accounts.User
7+
alias NervesHub.Archives.Archive
68
alias NervesHub.AuditLogs
79
alias NervesHub.Deployments.Deployment
810
alias NervesHub.Devices.Device
@@ -107,4 +109,18 @@ defmodule NervesHub.AuditLogs.DeviceTemplates do
107109
"Multiple matching deployments found, updating #{device.identifier}'s deployment to #{deployment.name}"
108110
)
109111
end
112+
113+
@spec audit_device_archive_update_triggered(Device.t(), Archive.t(), UUIDv7.t()) :: :ok
114+
def audit_device_archive_update_triggered(
115+
device,
116+
archive,
117+
reference_id
118+
) do
119+
description =
120+
"Archive update triggered for #{device.identifier}. Sending archive #{archive.uuid}."
121+
122+
AuditLogs.audit_with_ref!(device, device, description, reference_id)
123+
124+
:ok
125+
end
110126
end

lib/nerves_hub_web/channels/device_channel.ex

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ defmodule NervesHubWeb.DeviceChannel do
160160
end
161161

162162
def handle_info(%Broadcast{event: "archives/updated"}, socket) do
163-
{:noreply, maybe_send_archive(socket)}
163+
{:noreply, maybe_send_archive(socket, audit_log: true)}
164164
end
165165

166166
def handle_info(%Broadcast{event: "moved"}, %{assigns: %{device: device}} = socket) do
@@ -203,7 +203,7 @@ defmodule NervesHubWeb.DeviceChannel do
203203
socket
204204
|> update_device(device)
205205
|> maybe_start_penalty_timer()
206-
|> maybe_send_archive()
206+
|> maybe_send_archive(audit_log: true)
207207

208208
{:noreply, socket}
209209
end
@@ -485,12 +485,21 @@ defmodule NervesHubWeb.DeviceChannel do
485485
end
486486
end
487487

488-
defp maybe_send_archive(%{assigns: %{device: device}} = socket) do
488+
defp maybe_send_archive(%{assigns: %{device: device}} = socket, opts \\ []) do
489+
opts = Keyword.validate!(opts, audit_log: false)
489490
updates_enabled = device.updates_enabled && !Devices.device_in_penalty_box?(device)
490491
version_match = Version.match?(socket.assigns.device_api_version, ">= 2.0.0")
491492

492493
if updates_enabled && version_match do
493494
if archive = Archives.archive_for_deployment(device.deployment_id) do
495+
if opts[:audit_log],
496+
do:
497+
DeviceTemplates.audit_device_archive_update_triggered(
498+
device,
499+
archive,
500+
socket.assigns.reference_id
501+
)
502+
494503
push(socket, "archive", %{
495504
size: archive.size,
496505
uuid: archive.uuid,

test/nerves_hub_web/channels/device_channel_test.exs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ defmodule NervesHubWeb.DeviceChannelTest do
22
use NervesHubWeb.ChannelCase
33
use DefaultMocks
44

5+
import Ecto.Query
56
import TrackerHelper
67

78
alias NervesHub.Devices
9+
alias NervesHub.AuditLogs.AuditLog
810
alias NervesHub.Fixtures
11+
alias NervesHub.Repo
912
alias NervesHubWeb.DeviceChannel
1013
alias NervesHubWeb.DeviceSocket
1114
alias NervesHubWeb.ExtensionsChannel
@@ -91,6 +94,68 @@ defmodule NervesHubWeb.DeviceChannelTest do
9194
assert_push("archive_public_keys", %{keys: [_]})
9295
end
9396

97+
test "if archive is sent on connect an audit log is not created" do
98+
%{certificate: certificate, params: params, archive_uuid: archive_uuid} =
99+
archive_setup()
100+
101+
{:ok, socket} =
102+
connect(DeviceSocket, %{}, connect_info: %{peer_data: %{ssl_cert: certificate.der}})
103+
104+
{:ok, %{}, _socket} = subscribe_and_join(socket, DeviceChannel, "device", params)
105+
106+
audit_log_count_before = Repo.aggregate(AuditLog, :count)
107+
108+
assert_push("archive", %{uuid: ^archive_uuid})
109+
110+
assert audit_log_count_before == Repo.aggregate(AuditLog, :count)
111+
end
112+
113+
test "if archive is sent when an archive updates an audit log is created" do
114+
%{device: device, certificate: certificate, params: params} = archive_setup()
115+
116+
{:ok, socket} =
117+
connect(DeviceSocket, %{}, connect_info: %{peer_data: %{ssl_cert: certificate.der}})
118+
119+
{:ok, %{}, socket} = subscribe_and_join(socket, DeviceChannel, "device", params)
120+
121+
Phoenix.PubSub.broadcast(
122+
NervesHub.PubSub,
123+
"device:#{device.id}",
124+
%Phoenix.Socket.Broadcast{event: "archives/updated"}
125+
)
126+
127+
_ = :sys.get_state(socket.channel_pid)
128+
129+
assert Repo.exists?(
130+
from(al in AuditLog,
131+
where: like(al.description, "Archive update triggered for%")
132+
)
133+
)
134+
end
135+
136+
test "if archive is sent when a device updates an audit log is created" do
137+
%{device: device, certificate: certificate, params: params} = archive_setup()
138+
139+
{:ok, socket} =
140+
connect(DeviceSocket, %{}, connect_info: %{peer_data: %{ssl_cert: certificate.der}})
141+
142+
{:ok, %{}, socket} = subscribe_and_join(socket, DeviceChannel, "device", params)
143+
144+
Phoenix.PubSub.broadcast(
145+
NervesHub.PubSub,
146+
"device:#{device.id}",
147+
%Phoenix.Socket.Broadcast{event: "devices/updated"}
148+
)
149+
150+
_ = :sys.get_state(socket.channel_pid)
151+
152+
assert Repo.exists?(
153+
from(al in AuditLog,
154+
where: like(al.description, "Archive update triggered for%")
155+
)
156+
)
157+
end
158+
94159
test "the first fwup_progress marks an update as happening" do
95160
user = Fixtures.user_fixture()
96161
{device, _firmware, _deployment} = device_fixture(user, %{identifier: "123"})
@@ -247,4 +312,30 @@ defmodule NervesHubWeb.DeviceChannelTest do
247312

248313
{device, firmware, deployment}
249314
end
315+
316+
defp archive_setup() do
317+
user = Fixtures.user_fixture()
318+
org = Fixtures.org_fixture(user, %{name: "BigOrg2022"})
319+
product = Fixtures.product_fixture(user, org, %{name: "Hop"})
320+
org_key = Fixtures.org_key_fixture(org, user)
321+
archive = %{uuid: archive_uuid} = Fixtures.archive_fixture(org_key, product)
322+
firmware = Fixtures.firmware_fixture(org_key, product, %{dir: System.tmp_dir()})
323+
deployment = Fixtures.deployment_fixture(org, firmware, %{archive_id: archive.id})
324+
325+
{device, _firmware, _deployment} =
326+
device_fixture(user, %{identifier: "123", deployment_id: deployment.id})
327+
328+
%{db_cert: certificate} = Fixtures.device_certificate_fixture(device)
329+
330+
params =
331+
for {k, v} <- Map.from_struct(device.firmware_metadata),
332+
into: %{"device_api_version" => "2.0.1"} do
333+
case k do
334+
:uuid -> {"nerves_fw_uuid", Ecto.UUID.generate()}
335+
_ -> {"nerves_fw_#{k}", v}
336+
end
337+
end
338+
339+
%{device: device, certificate: certificate, params: params, archive_uuid: archive_uuid}
340+
end
250341
end

0 commit comments

Comments
 (0)