diff --git a/0015-Fix-CVE-2026-27877.patch b/0015-Fix-CVE-2026-27877.patch new file mode 100644 index 0000000..51c49a0 --- /dev/null +++ b/0015-Fix-CVE-2026-27877.patch @@ -0,0 +1,123 @@ +diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go +index 215714b6f83..36949e126b9 100644 +--- a/pkg/api/frontendsettings.go ++++ b/pkg/api/frontendsettings.go +@@ -1,6 +1,7 @@ + package api + + import ( ++ "bytes" + "context" + "fmt" + "net/http" +@@ -18,6 +19,7 @@ import ( + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" + "github.com/grafana/grafana/pkg/services/secrets/kvstore" ++ dashboardkind "github.com/grafana/grafana/pkg/services/store/kind/dashboard" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tsdb/grafanads" + "github.com/grafana/grafana/pkg/util" +@@ -297,7 +299,12 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug + + if c.IsPublicDashboardView() { + // If RBAC is enabled, it will filter out all datasources for a public user, so we need to skip it +- orgDataSources = dataSources ++ // But we can filter to only include datasources actually used by the dashboard ++ filtered, err := hs.publicDashFilterUsedDataSources(c, dataSources) ++ if err != nil { ++ return nil, err ++ } ++ orgDataSources = filtered + } else { + filtered, err := hs.dsGuardian.New(c.SignedInUser.OrgID, c.SignedInUser).FilterDatasourcesByQueryPermissions(dataSources) + if err != nil { +@@ -633,3 +640,36 @@ func (hs *HTTPServer) getEnabledOAuthProviders() map[string]any { + } + return providers + } ++ ++func (hs *HTTPServer) publicDashFilterUsedDataSources(c *contextmodel.ReqContext, allDataSources []*datasources.DataSource) ([]*datasources.DataSource, error) { ++ _, dash, err := hs.PublicDashboardsApi.PublicDashboardService.FindPublicDashboardAndDashboardByAccessToken(c.Req.Context(), c.PublicDashboardAccessToken) ++ if err != nil { ++ return nil, err ++ } ++ ++ payload, err := dash.Data.Encode() ++ if err != nil { ++ return nil, fmt.Errorf("failed to encode dashboard data: %w", err) ++ } ++ ++ lookup := dashboardkind.CreateDatasourceLookup([]*dashboardkind.DatasourceQueryResult{}) ++ ++ usedDSRefs, err := dashboardkind.ReadDashboard(bytes.NewReader(payload), lookup) ++ if err != nil { ++ return nil, fmt.Errorf("failed to parse dashboard: %w", err) ++ } ++ ++ usedUIDs := make(map[string]struct{}, len(usedDSRefs)) ++ for _, ds := range usedDSRefs { ++ usedUIDs[ds.UID] = struct{}{} ++ } ++ ++ filtered := make([]*datasources.DataSource, 0) ++ for _, ds := range allDataSources { ++ if _, ok := usedUIDs[ds.UID]; ok { ++ filtered = append(filtered, ds) ++ } ++ } ++ ++ return filtered, nil ++} +diff --git a/pkg/services/store/kind/dashboard/dashboard.go b/pkg/services/store/kind/dashboard/dashboard.go +index b325acd8c57..054dcafe8ca 100644 +--- a/pkg/services/store/kind/dashboard/dashboard.go ++++ b/pkg/services/store/kind/dashboard/dashboard.go +@@ -112,8 +112,17 @@ func newDatasourceVariableLookup(dsLookup DatasourceLookup) *datasourceVariableL + } + } + ++// ReadDashboard parses dashboard JSON and returns the data sources used in the dashboard. ++func ReadDashboard(stream io.Reader, lookup DatasourceLookup) ([]DataSourceRef, error) { ++ info, err := readDashboard(stream, lookup) ++ if err != nil { ++ return nil, err ++ } ++ return info.Datasource, nil ++} ++ + // nolint:gocyclo +-// ReadDashboard will take a byte stream and return dashboard info ++// readDashboard will take a byte stream and return dashboard info + func readDashboard(stream io.Reader, lookup DatasourceLookup) (*dashboardInfo, error) { + dash := &dashboardInfo{} + +diff --git a/pkg/services/store/kind/dashboard/ds_lookup.go b/pkg/services/store/kind/dashboard/ds_lookup.go +index 5d132d569be..7330e598409 100644 +--- a/pkg/services/store/kind/dashboard/ds_lookup.go ++++ b/pkg/services/store/kind/dashboard/ds_lookup.go +@@ -100,6 +100,9 @@ func (d *DsLookup) ByRef(ref *DataSourceRef) *DataSourceRef { + if ref == nil { + return d.defaultDS + } ++ if ref.UID == "default" && ref.Type == "" { ++ return d.defaultDS ++ } + + key := "" + if ref.UID != "" { +@@ -117,7 +120,13 @@ func (d *DsLookup) ByRef(ref *DataSourceRef) *DataSourceRef { + return ds + } + +- return d.byName[key] ++ ds, ok = d.byName[key] ++ if ok { ++ return ds ++ } ++ ++ // With nothing was found (or configured), use the original reference ++ return ref + } + + func (d *DsLookup) ByType(dsType string) []DataSourceRef { diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..85a399d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,70 @@ +# Grafana CentOS Packaging Workflow + +This file documents the standard workflow for working with the grafana centpkg repository. Claude Code will follow these steps autonomously. + +## Workflow Steps + +1. Fork the repository: + ``` + centpkg fork + ``` + +2. Fetch all remotes: + ``` + git fetch --all + ``` + +3. Create a new branch named after the Jira ticket, branching off the appropriate version branch. + Ask the user which version (c8s, c9s, c10s) and which Jira ticket before running this step. + The branch name is the ticket number lowercased with an `r` prefix (e.g. RHEL-161792 → r161792). + ``` + git checkout -b r origin/ + ``` + +4. Bump the release number in `grafana.spec` (the `Release:` field) by 1. + +5. Add a new changelog entry at the top of `%changelog` in `grafana.spec` following this format: + ``` + *
<> - + - Resolves : + - Resolves : + ... + ``` + Ask the user for: + - The primary Jira ticket (used for the branch name) + - Any additional Jira tickets being fixed in this build (there may be multiple) + - Their Atlassian account email (or check if it is stored in an environment variable) + - Their Atlassian API token variable name (or check if it is stored in an environment variable) + + Fetch the CVE for each ticket from Jira simultaneously: + ``` + curl -s -H "Authorization: Basic $(echo -n ":$" | base64 -w 0)" \ + "https://redhat.atlassian.net/rest/api/2/issue/?fields=summary" + ``` + Extract the CVE identifier (e.g. `CVE-XXXX-XXXXX`) from the `summary` field of each response. + Add one `- Resolves` line per ticket. + +6. Build the source RPM: + ``` + centpkg srpm + ``` + +7. Stage all modified and new files: + ``` + git add grafana.spec CLAUDE.md + ``` + +8. Commit using `centpkg commit -m` with each ticket on its own line: + ``` + centpkg commit -m "$(cat <<'EOF' + Resolves: RHEL-XXXXXX + Resolves: RHEL-XXXXXX + EOF + )" + ``` + +9. Push the branch to the fork. The fork remote name can be found by running `git remote -v` + and identifying the remote that points to the user's personal fork (not `origin`). + ``` + git push + ``` diff --git a/grafana.spec b/grafana.spec index 7792c7f..b5403e2 100644 --- a/grafana.spec +++ b/grafana.spec @@ -26,7 +26,7 @@ end} Name: grafana Version: 10.2.6 -Release: 25%{?dist} +Release: 26%{?dist} Summary: Metrics dashboard and graph editor License: AGPL-3.0-only URL: https://grafana.org @@ -81,6 +81,7 @@ Patch11: 0011-fix-dompurify-CVE.patch Patch12: 0012-fix-jwt-CVE.patch Patch13: 0013-fix-CVE-2025-4123.patch Patch14: 0014-Fix-CVE-2026-21721.patch +Patch15: 0015-Fix-CVE-2026-27877.patch # Patches affecting the vendor tarball Patch1001: 1001-vendor-patch-removed-backend-crypto.patch @@ -775,6 +776,7 @@ rm -r plugins-bundled %patch -P 12 -p1 %patch -P 13 -p1 %patch -P 14 -p1 +%patch -P 15 -p1 %patch -P 1001 -p1 %if %{enable_fips_mode} @@ -1019,6 +1021,13 @@ done %ghost %verify(not md5 size mode mtime) %{_sharedstatedir}/selinux/*/active/modules/200/grafana %changelog +* Tue Apr 21 2026 Sam Feifer 10.2.6-26 +- Resolves RHEL-161792: CVE-2026-27877 +- Resolves RHEL-166318: CVE-2026-33810 +- Resolves RHEL-166490: CVE-2026-32282 +- Resolves RHEL-167360: CVE-2026-32280 +- Resolves RHEL-167516: CVE-2026-32283 + * Wed Apr 1 2026 Sam Feifer 10.2.6-25 - Resolves RHEL-156596: CVE-2026-25679