Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ py_test(
".bazelrc",
".forgejo/workflows/elixir-integration-sr-core.yml",
".forgejo/workflows/main.yml",
".github/workflows/elixir-integration-sr-core.yml",
"Makefile",
"buildbuddy.yaml",
"buildbuddy_setup_fixture_env.sh",
Expand Down
47 changes: 47 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,50 @@
# ServiceRadar v1.4.33

Device risk scoring from NVD + KEV + CWE, Findings UI with KEV floor, and
an EventWriter pull-inflight fix so traces and OTLP metrics stop going
silent after a long-lived core. Also ships multi-arch web-ng/core-elx,
moves lint/check CI onto GitHub ARC, and points Settings first-party
plugin/add-on and agent-release import at GitHub Releases instead of
Forgejo.

## Whats New

**1.4.33**
- Inventory risk: compose NVD and CISA/VulnCheck KEV into one CVE finding
with CWE context, a KEV floor of 90 on the Findings overlay, and a
periodic DeviceRiskAssessmentWorker that writes `ocsf_devices.risk_score`
through DeviceRiskReducer only. Endpoint inventory scores KEV at 90 per
payload; device-level Score.compute floors KEV at 70 and folds CWE.
IOC exposure is listed on the device. (PRs #5008, #3591)
- EventWriter: expire stale JetStream pull inflight so a hung fetch no
longer parks the otel-traces / otel-metrics / logs durables (last
delivery ages out, unprocessed piles up, Observability goes empty while
the log-collector is still receiving OTLP). (PR #3764)
- Egress: Finch HTTP (Bumblebee, advisory feeds, and other Req clients)
goes through SERVICERADAR_EGRESS_PROXY. The default Finch connect
options no longer crash the Bumblebee s7 integration shard. (PRs
#5003, #5007)
- Images: web-ng and core-elx now publish multi-arch OCI indexes. (PR
#5001)
- Build: Rust crates vendor as .crate archives; OpenSSL is built
hermetically in Bazel. The native add-on version gate no longer
requires a MODULE.bazel.lock hash that rules_rs cannot satisfy. (PR
#5002)
- CI: lint and check workflows run on GitHub ARC. SRQL fixture TLS is
issued with cert-manager and a live CA. Bootstrap scratch DROP
cancellation no longer fails core s7. The cache-proxy config test now
ships the GitHub integration workflow in its runfiles. (PRs #5010,
#3768, #5005)
- Plugins: Settings first-party Wasm plugin and native add-on import
now reads GitHub Releases (`github.com/carverauto/serviceradar`) plus
Harbor OCI, not Forgejo. `ForgejoOciClient` is gone; the shared
transport is `FirstPartyReleaseClient`. `GitHubImporter` is unchanged
and still fetches source-tree `plugin.yaml`/`plugin.wasm`. Agent
release import (`ReleaseSourceImporter` and Settings → Agents) now
uses the same GitHub Releases API; Forgejo URLs are rejected.
- Web UI: boolean device-fact keys in composite checks are a combobox,
not free text. (PR #4997)

# ServiceRadar v1.4.32

Stops core from OOM-reloading leftover provider CIDR snapshots (the demo
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.4.32
1.4.33
2 changes: 1 addition & 1 deletion docs/docs/cloud-quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ automatically — there is no separate certificate step.

Release assets (DEB, RPM, and signed agent runtime artifacts):

**[https://code.carverauto.dev/carverauto/serviceradar/releases](https://code.carverauto.dev/carverauto/serviceradar/releases)**
**[https://github.com/carverauto/serviceradar/releases](https://github.com/carverauto/serviceradar/releases)**

The product UI links the same page from **Settings → Agent Deploy**. Prefer the
latest stable release train for production.
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/edge-agent-onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: Edge Agent Onboarding

Edge onboarding is intentionally simple:

1. Install `serviceradar-agent` on the host (RPM/DEB from the [releases page](https://code.carverauto.dev/carverauto/serviceradar/releases)).
1. Install `serviceradar-agent` on the host (RPM/DEB from the [releases page](https://github.com/carverauto/serviceradar/releases)).
2. In the UI, create an agent package.
3. Copy/paste the enroll command on the host.

Expand Down Expand Up @@ -59,7 +59,7 @@ That is it. The agent enrolls, receives config, and starts streaming results.

Download the latest `serviceradar-agent` package from the ServiceRadar releases page and install it on the target host:

- Releases: [code.carverauto.dev/carverauto/serviceradar/releases](https://code.carverauto.dev/carverauto/serviceradar/releases)
- Releases: [github.com/carverauto/serviceradar/releases](https://github.com/carverauto/serviceradar/releases)
- Debian/Ubuntu: install the `.deb`
- RHEL/Alma/Rocky: install the `.rpm`

Expand Down
2 changes: 1 addition & 1 deletion elixir/web-ng/.dialyzer_ignore.exs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"The pattern variable _ can never match the type, because it is covered by previous clauses."},
{"lib/serviceradar_web_ng/dashboards/frame_runner.ex", :pattern_match_cov},
{"lib/serviceradar_web_ng/plugins/first_party_importer.ex", "The guard clause can never succeed."},
{"lib/serviceradar_web_ng/plugins/forgejo_oci_client.ex", "The guard clause can never succeed."},
{"lib/serviceradar_web_ng/plugins/first_party_release_client.ex", "The guard clause can never succeed."},
{"lib/serviceradar_web_ng/plugins/github_importer.ex",
"The pattern can never match the type {:error, {:invalid_manifest, [binary()]}}."},
{"lib/serviceradar_web_ng/plugins/github_importer.ex", "Function dashboard_renderer_path/2 will never be called."},
Expand Down
4 changes: 2 additions & 2 deletions elixir/web-ng/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ config :serviceradar_web_ng, :client_ip,
trusted_proxy_cidrs: []

config :serviceradar_web_ng, :first_party_plugin_import,
repo_url: "https://code.carverauto.dev/carverauto/serviceradar",
repo_url: "https://github.com/carverauto/serviceradar",
index_asset_name: "serviceradar-wasm-plugin-index.json",
auto_sync_enabled: false,
sync_release_limit: 10,
Expand All @@ -259,7 +259,7 @@ config :serviceradar_web_ng, :first_party_plugin_import,
config :serviceradar_web_ng, :god_view_enabled, false

config :serviceradar_web_ng, :native_addon_import,
repo_url: "https://code.carverauto.dev/carverauto/serviceradar",
repo_url: "https://github.com/carverauto/serviceradar",
index_asset_name: "serviceradar-native-addon-index.json",
auto_sync_enabled: false,
sync_release_limit: 10,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@ defmodule ServiceRadarWebNG.Edge.ReleaseSourceImporter do
@default_recent_release_limit 5
@max_recent_release_scan_limit 50
@max_asset_redirects 5
@default_provider "forgejo"
@forgejo_host "code.carverauto.dev"
@default_provider "github"
@github_host "github.com"
@github_api_host "api.github.com"
@default_repo_url "https://github.com/carverauto/serviceradar"
@github_asset_hosts [
@github_host,
@github_api_host,
"objects.githubusercontent.com",
"release-assets.githubusercontent.com",
"github-releases.githubusercontent.com"
]
@type import_attrs :: %{optional(:provider) => String.t(), optional(String.t()) => String.t()}

@spec default_manifest_asset_name() :: String.t()
Expand All @@ -20,6 +29,9 @@ defmodule ServiceRadarWebNG.Edge.ReleaseSourceImporter do
@spec default_signature_asset_name() :: String.t()
def default_signature_asset_name, do: @default_signature_asset_name

@spec default_repo_url() :: String.t()
def default_repo_url, do: @default_repo_url

@spec list_recent_releases(import_attrs(), pos_integer()) ::
{:ok, [map()]} | {:error, String.t()}
def list_recent_releases(attrs, limit \\ @default_recent_release_limit)
Expand Down Expand Up @@ -94,14 +106,12 @@ defmodule ServiceRadarWebNG.Edge.ReleaseSourceImporter do
def import(_attrs), do: {:error, "Release import settings are invalid"}

defp selected_provider(attrs) when is_map(attrs) do
attrs
|> Map.get("provider", Map.get(attrs, :provider))
|> normalize_provider()
|> case do
nil -> @default_provider
provider -> provider
raw = Map.get(attrs, "provider") || Map.get(attrs, :provider) || @default_provider

case normalize_provider(raw) do
"github" -> {:ok, "github"}
_other -> {:error, "GitHub is the only supported release provider"}
end
|> validate_provider()
end

defp import_repo(attrs) when is_map(attrs) do
Expand All @@ -112,24 +122,20 @@ defmodule ServiceRadarWebNG.Edge.ReleaseSourceImporter do
end
end

defp validate_provider("forgejo"), do: {:ok, "forgejo"}

defp validate_provider(_provider), do: {:error, "Forgejo is the only supported release provider"}

defp parse_repo_url("forgejo", url) do
with {:ok, %URI{scheme: "https", host: @forgejo_host} = uri} <- parse_uri(url),
defp parse_repo_url("github", url) do
with {:ok, %URI{scheme: "https", host: @github_host} = uri} <- parse_uri(url),
{:ok, owner, repo} <- repo_owner_and_name(uri.path) do
{:ok,
%{
provider: "forgejo",
provider: "github",
repo_url: "https://#{host_port(uri)}/#{owner}/#{repo}",
api_base_url: "https://#{host_port(uri)}/api/v1",
api_base_url: "https://#{@github_api_host}",
owner: owner,
repo: repo
}}
else
_ ->
{:error, "Forgejo repository URL must look like https://code.carverauto.dev/<owner>/<repo>"}
{:error, "GitHub repository URL must look like https://github.com/<owner>/<repo>"}
end
end

Expand Down Expand Up @@ -343,9 +349,16 @@ defmodule ServiceRadarWebNG.Edge.ReleaseSourceImporter do
end
end

defp normalize_provider(value) when is_binary(value), do: value |> String.trim() |> String.downcase()
defp normalize_provider(value) when is_binary(value) do
case value |> String.trim() |> String.downcase() do
"" -> nil
normalized -> normalized
end
end

defp normalize_provider(value) when is_atom(value), do: value |> Atom.to_string() |> normalize_provider()
defp normalize_provider(value) when is_atom(value) and value not in [nil, false, true] do
value |> Atom.to_string() |> normalize_provider()
end

defp normalize_provider(_value), do: nil

Expand All @@ -358,8 +371,8 @@ defmodule ServiceRadarWebNG.Edge.ReleaseSourceImporter do

defp normalize_string(value), do: value |> to_string() |> normalize_string()

defp api_headers("forgejo") do
[{"user-agent", "serviceradar"}, {"accept", "application/json"} | auth_headers("forgejo")]
defp api_headers("github") do
[{"user-agent", "serviceradar"}, {"accept", "application/vnd.github+json"} | auth_headers("github")]
end

defp asset_headers(provider, url) do
Expand All @@ -372,11 +385,12 @@ defmodule ServiceRadarWebNG.Edge.ReleaseSourceImporter do
end
end

defp auth_headers("forgejo") do
case Application.get_env(:serviceradar_web_ng, :agent_release_import_forgejo_token) ||
System.get_env("FORGEJO_TOKEN") do
defp auth_headers("github") do
case Application.get_env(:serviceradar_web_ng, :agent_release_import_github_token) ||
System.get_env("GITHUB_TOKEN") ||
System.get_env("GH_TOKEN") do
nil -> []
token -> [{"authorization", "token #{token}"}]
token -> [{"authorization", "Bearer #{token}"}]
end
end

Expand Down Expand Up @@ -414,24 +428,30 @@ defmodule ServiceRadarWebNG.Edge.ReleaseSourceImporter do
end

defp validate_url(url) do
case ReleaseFetchPolicy.validate(url) do
{:ok, %URI{scheme: "https"} = uri} -> {:ok, uri}
{:error, _reason} = error -> error
_ -> {:error, :disallowed_url}
case URI.parse(String.trim(to_string(url))) do
%URI{scheme: "https", host: host} = uri when host in @github_asset_hosts ->
{:ok, uri}

_ ->
case ReleaseFetchPolicy.validate(url) do
{:ok, %URI{scheme: "https"} = uri} -> {:ok, uri}
{:error, _reason} = error -> error
_ -> {:error, :disallowed_url}
end
end
end

defp trusted_api_host?("forgejo", host), do: host == @forgejo_host
defp trusted_api_host?("github", host), do: host == @github_api_host
defp trusted_api_host?(_, _host), do: false

defp trusted_asset_host?("forgejo", host), do: host == @forgejo_host
defp trusted_asset_host?("github", host), do: host in @github_asset_hosts
defp trusted_asset_host?(_, _host), do: false

defp auth_host?(provider, url) do
case URI.parse(url) do
%URI{host: host} when is_binary(host) ->
case provider do
"forgejo" -> host == @forgejo_host
"github" -> host in [@github_host, @github_api_host]
_ -> false
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
defmodule ServiceRadarWebNG.Plugins.FirstPartyImporter do
@moduledoc """
Discovers and imports first-party Wasm plugin bundles from Forgejo releases.
Discovers and imports first-party Wasm plugin bundles from GitHub Releases.

HTTP/OCI/Cosign/URL transport is the shared `ForgejoOciClient` (imported below);
this module keeps only the Wasm-bundle-specific discovery, verification, and
result-shaping logic. The `default_repo_url/0` import is excepted so this module
can re-export it as its own public accessor.
HTTP/OCI/Cosign/URL transport is the shared `FirstPartyReleaseClient`
(imported below); this module keeps only the Wasm-bundle-specific discovery,
verification, and result-shaping logic. The `default_repo_url/0` import is
excepted so this module can re-export it as its own public accessor.
"""

import ServiceRadarWebNG.Plugins.ForgejoOciClient, except: [default_repo_url: 0]
import ServiceRadarWebNG.Plugins.FirstPartyReleaseClient, except: [default_repo_url: 0]

alias ServiceRadar.Plugins.DisplayContract
alias ServiceRadar.Plugins.Manifest
alias ServiceRadarWebNG.Plugins.ForgejoOciClient
alias ServiceRadarWebNG.Plugins.FirstPartyReleaseClient
alias ServiceRadarWebNG.Plugins.Storage
alias ServiceRadarWebNG.Plugins.UploadSignature

Expand All @@ -32,7 +32,7 @@ defmodule ServiceRadarWebNG.Plugins.FirstPartyImporter do
def default_index_asset_name, do: @default_index_asset_name

@spec default_repo_url() :: String.t()
def default_repo_url, do: ForgejoOciClient.default_repo_url()
def default_repo_url, do: FirstPartyReleaseClient.default_repo_url()

@spec list_recent_plugins(map(), pos_integer()) :: {:ok, [map()]} | {:error, String.t()}
def list_recent_plugins(attrs \\ %{}, limit \\ @default_recent_release_limit)
Expand Down Expand Up @@ -85,11 +85,11 @@ defmodule ServiceRadarWebNG.Plugins.FirstPartyImporter do
def list_recent_plugins_with_summary(_attrs, _limit), do: {:error, "Plugin import settings are invalid"}

@doc """
Lists Wasm plugins from one exact Forgejo release.
Lists Wasm plugins from one exact GitHub release.

Automatic synchronization uses this path so the catalog is anchored to the
immutable ServiceRadar release currently running, rather than depending on the
ordering or completeness of Forgejo's recent-release feed.
ordering or completeness of GitHub's recent-release feed.
"""
@spec list_release_plugins(map(), String.t()) :: {:ok, [map()]} | {:error, term()}
def list_release_plugins(attrs, release_tag) when is_map(attrs) do
Expand Down Expand Up @@ -535,7 +535,7 @@ defmodule ServiceRadarWebNG.Plugins.FirstPartyImporter do

defp source_metadata(repo, release, entry, fetched, now) do
%{
"source" => "first_party_forgejo_release",
"source" => "first_party_github_release",
"repo_url" => repo.repo_url,
"release_tag" => normalize_string(Map.get(release, "tag_name")),
"release_name" => normalize_string(Map.get(release, "name")),
Expand All @@ -561,7 +561,7 @@ defmodule ServiceRadarWebNG.Plugins.FirstPartyImporter do

defp configured_repo_url do
config = Application.get_env(:serviceradar_web_ng, :first_party_plugin_import, [])
Keyword.get(config, :repo_url, ForgejoOciClient.default_repo_url())
Keyword.get(config, :repo_url, FirstPartyReleaseClient.default_repo_url())
end

defp index_asset_name(attrs) do
Expand Down
Loading
Loading