Compare commits

...

6 Commits

Author SHA1 Message Date
Sofia Papagiannaki 1cbaaefb60 Fix static handler redirect logic to ensure proper clean up URLs before redirection. 2025-05-06 07:30:31 -05:00
Kevin Minehart 4a01194a7d CI: Use docker creds from ci/common (#104827)
Use docker creds from ci/common

(cherry picked from commit fd4afdbd2c)
2025-05-02 16:37:01 -06:00
Kevin Minehart 002dbf1542 CI: move grafana-delivery-bot path in Drone (#104886)
* move delivery bot creds to vault

* format-drone

(cherry picked from commit ec35e861e0)
2025-05-02 16:36:52 -06:00
github-actions[bot] ae23ead4d9 apply security patch: release-11.6.1/394-202504041254.patch
commit 16b732e9ffe6034f828b3f8e10aa17232da022ef
Author: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
Date:   Fri Apr 4 13:50:50 2025 +0100

    APIs: Remove dashboard and folder registration
2025-04-18 22:35:57 +00:00
github-actions[bot] a29981b6b7 apply security patch: release-11.6.1/380-202504030832.patch
commit cbcd7edd6d2b2cf29b42c96a56f669e0d8e5209f
Author: nmarrs <nathanielmarrs@gmail.com>
Date:   Thu Apr 3 09:28:43 2025 +0100

    backport change
2025-04-18 22:35:57 +00:00
github-actions[bot] e471a96595 apply security patch: release-11.6.1/364-202504020728.patch
commit e555e18f4974f539e8210f3ce965a3eb56f5a406
Author: Andres Martinez Gotor <andres.martinez@grafana.com>
Date:   Mon Mar 31 12:15:52 2025 +0200

    Sanitize paths before evaluating access to route
2025-04-18 22:35:57 +00:00
10 changed files with 226 additions and 66 deletions
+13 -33
View File
@@ -738,10 +738,8 @@ steps:
from_secret: docker_password
DOCKER_USER:
from_secret: docker_username
GITHUB_APP_ID:
from_secret: delivery-bot-app-id
GITHUB_APP_INSTALLATION_ID:
from_secret: delivery-bot-app-installation-id
GITHUB_APP_ID: "329617"
GITHUB_APP_INSTALLATION_ID: "37346161"
GITHUB_APP_PRIVATE_KEY:
from_secret: delivery-bot-app-private-key
failure: ignore
@@ -2160,10 +2158,8 @@ steps:
from_secret: docker_username
GCP_KEY:
from_secret: gcp_grafanauploads
GITHUB_APP_ID:
from_secret: delivery-bot-app-id
GITHUB_APP_INSTALLATION_ID:
from_secret: delivery-bot-app-installation-id
GITHUB_APP_ID: "329617"
GITHUB_APP_INSTALLATION_ID: "37346161"
GITHUB_APP_PRIVATE_KEY:
from_secret: delivery-bot-app-private-key
image: google/cloud-sdk:431.0.0
@@ -2464,10 +2460,8 @@ steps:
from_secret: docker_username
GCP_KEY:
from_secret: gcp_grafanauploads
GITHUB_APP_ID:
from_secret: delivery-bot-app-id
GITHUB_APP_INSTALLATION_ID:
from_secret: delivery-bot-app-installation-id
GITHUB_APP_ID: "329617"
GITHUB_APP_INSTALLATION_ID: "37346161"
GITHUB_APP_PRIVATE_KEY:
from_secret: delivery-bot-app-private-key
image: google/cloud-sdk:431.0.0
@@ -3463,10 +3457,8 @@ steps:
from_secret: docker_username
GCP_KEY:
from_secret: gcp_grafanauploads
GITHUB_APP_ID:
from_secret: delivery-bot-app-id
GITHUB_APP_INSTALLATION_ID:
from_secret: delivery-bot-app-installation-id
GITHUB_APP_ID: "329617"
GITHUB_APP_INSTALLATION_ID: "37346161"
GITHUB_APP_PRIVATE_KEY:
from_secret: delivery-bot-app-private-key
image: google/cloud-sdk:431.0.0
@@ -5415,13 +5407,13 @@ name: prerelease_bucket
---
get:
name: username
path: infra/data/ci/grafanaci-docker-hub
path: ci/data/common/dockerhub
kind: secret
name: docker_username
---
get:
name: password
path: infra/data/ci/grafanaci-docker-hub
path: ci/data/common/dockerhub
kind: secret
name: docker_password
---
@@ -5540,20 +5532,8 @@ kind: secret
name: dagger_token
---
get:
name: app-id
path: infra/data/ci/grafana-release-eng/grafana-delivery-bot
kind: secret
name: delivery-bot-app-id
---
get:
name: app-installation-id
path: infra/data/ci/grafana-release-eng/grafana-delivery-bot
kind: secret
name: delivery-bot-app-installation-id
---
get:
name: app-private-key
path: infra/data/ci/grafana-release-eng/grafana-delivery-bot
name: PRIVATE_KEY
path: ci/data/repo/grafana/grafana/delivery-bot-app
kind: secret
name: delivery-bot-app-private-key
---
@@ -5564,6 +5544,6 @@ kind: secret
name: gcr_credentials
---
kind: signature
hmac: 3e9721305edd682042fed2e6469bf4c83e0ea6406cf92bd9657b1167afb6d13d
hmac: c0e27d237198566806eb4b6309a0f017f88a3733c57c40651536cad5bf92c8fc
...
+9 -1
View File
@@ -302,7 +302,15 @@ func (proxy *DataSourceProxy) validateRequest() error {
}
// route match
if !strings.HasPrefix(proxy.proxyPath, route.Path) {
r1, err := util.CleanRelativePath(proxy.proxyPath)
if err != nil {
return err
}
r2, err := util.CleanRelativePath(route.Path)
if err != nil {
return err
}
if !strings.HasPrefix(r1, r2) {
continue
}
+8
View File
@@ -274,6 +274,14 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
err = proxy.validateRequest()
require.NoError(t, err)
})
t.Run("path with slashes and user is editor", func(t *testing.T) {
ctx, _ := setUp()
proxy, err := setupDSProxyTest(t, ctx, ds, routes, "//api//admin")
require.NoError(t, err)
err = proxy.validateRequest()
require.Error(t, err)
})
})
t.Run("plugin route with RBAC protection user is allowed", func(t *testing.T) {
+6 -5
View File
@@ -159,16 +159,17 @@ func staticHandler(ctx *web.Context, log log.Logger, opt StaticOptions) bool {
if fi.IsDir() {
// Redirect if missing trailing slash.
if !strings.HasSuffix(ctx.Req.URL.Path, "/") {
path := fmt.Sprintf("%s/", ctx.Req.URL.Path)
if !strings.HasPrefix(path, "/") {
redirectPath := path.Clean(ctx.Req.URL.Path)
redirectPath = fmt.Sprintf("%s/", redirectPath)
if !strings.HasPrefix(redirectPath, "/") {
// Disambiguate that it's a path relative to this server
path = fmt.Sprintf("/%s", path)
redirectPath = fmt.Sprintf("/%s", redirectPath)
} else {
// A string starting with // or /\ is interpreted by browsers as a URL, and not a server relative path
rePrefix := regexp.MustCompile(`^(?:/\\|/+)`)
path = rePrefix.ReplaceAllString(path, "/")
redirectPath = rePrefix.ReplaceAllString(redirectPath, "/")
}
http.Redirect(ctx.Resp, ctx.Req, path, http.StatusFound)
http.Redirect(ctx.Resp, ctx.Req, redirectPath, http.StatusFound)
return true
}
+174
View File
@@ -0,0 +1,174 @@
package httpstatic
import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/models/usertoken"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStatic(t *testing.T) {
// Create a temporary directory for test files
tmpDir, err := os.MkdirTemp("", "static-test")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
// Create test files
testFiles := map[string]string{
"test.txt": "Test content",
"subdir/test.txt": "Subdir content",
}
for path, content := range testFiles {
fullPath := filepath.Join(tmpDir, path)
err := os.MkdirAll(filepath.Dir(fullPath), 0o755)
require.NoError(t, err)
err = os.WriteFile(fullPath, []byte(content), 0o644)
require.NoError(t, err)
}
tests := []struct {
dir string
name string
path string
options StaticOptions
expectedStatus int
expectedBody string
expectedLocation string
}{
{
name: "should serve existing file",
path: "/test.txt",
expectedStatus: http.StatusOK,
expectedBody: "Test content",
dir: tmpDir,
},
{
name: "should serve file from subdirectory",
path: "/subdir/test.txt",
expectedStatus: http.StatusOK,
expectedBody: "Subdir content",
dir: tmpDir,
},
{
name: "should redirect directory without trailing slash",
path: "/subdir",
expectedStatus: http.StatusFound,
expectedLocation: "/subdir/",
dir: tmpDir,
},
{
name: "should handle prefix",
path: "/static/test.txt",
options: StaticOptions{Prefix: "/static"},
expectedStatus: http.StatusOK,
expectedBody: "Test content",
dir: tmpDir,
},
{
name: "should handle excluded path",
path: "/test.txt",
options: StaticOptions{Exclude: []string{"/test.txt"}},
expectedStatus: http.StatusNotFound,
dir: tmpDir,
},
{
name: "should add custom headers",
path: "/test.txt",
options: StaticOptions{AddHeaders: func(ctx *web.Context) { ctx.Resp.Header().Set("X-Test", "test") }},
expectedStatus: http.StatusOK,
expectedBody: "Test content",
dir: tmpDir,
},
{
name: "should clean up path before redirecting",
path: "/subdir/..%2F%5C127.0.0.1:80%2F%3F%2F..%2F..",
options: StaticOptions{Prefix: "subdir"},
expectedStatus: http.StatusFound,
expectedLocation: "/",
dir: tmpDir,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sc := setupScenarioContext(t, "")
sc.m.Use(Static(tt.dir, tt.options))
// Create a test request
req := httptest.NewRequest("GET", tt.path, nil)
w := httptest.NewRecorder()
// Execute the handler
sc.m.ServeHTTP(w, req)
// Verify the response
resp := w.Result()
require.Equal(t, tt.expectedStatus, resp.StatusCode)
if tt.expectedBody != "" {
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, tt.expectedBody, string(body))
}
if tt.options.AddHeaders != nil {
assert.Equal(t, "test", resp.Header.Get("X-Test"))
}
if tt.expectedLocation != "" {
assert.Equal(t, tt.expectedLocation, resp.Header.Get("Location"))
}
})
}
}
type scenarioContext struct {
t *testing.T
cfg *setting.Cfg
m *web.Mux
ctxHdlr *contexthandler.ContextHandler
}
func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHandler {
t.Helper()
if cfg == nil {
cfg = setting.NewCfg()
}
return contexthandler.ProvideService(
cfg,
&authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: "0", Type: claims.TypeAnonymous, SessionToken: &usertoken.UserToken{}}},
featuremgmt.WithFeatures(),
)
}
func setupScenarioContext(t *testing.T, url string) *scenarioContext {
cfg := setting.NewCfg()
ctxHdlr := getContextHandler(t, cfg)
sc := &scenarioContext{
t: t,
cfg: cfg,
ctxHdlr: ctxHdlr,
}
sc.m = web.New()
sc.m.Use(ctxHdlr.Middleware)
return sc
}
+4 -6
View File
@@ -30,17 +30,13 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
@@ -85,7 +81,8 @@ func RegisterAPIService(
dual dualwrite.Service,
sorter sort.Service,
) *DashboardsAPIBuilder {
softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore)
// disable dashboard api in 11.6
/* softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore)
dbp := legacysql.NewDatabaseProvider(sql)
namespacer := request.GetNamespaceMapper(cfg)
legacyDashboardSearcher := legacysearcher.NewDashboardSearchClient(dashStore, sorter)
@@ -105,7 +102,8 @@ func RegisterAPIService(
reg: reg,
}
apiregistration.RegisterAPI(builder)
return builder
return builder*/
return nil
}
func (b *DashboardsAPIBuilder) GetGroupVersions() []schema.GroupVersion {
+2 -1
View File
@@ -67,7 +67,8 @@ func RegisterAPIService(cfg *setting.Cfg,
registerer prometheus.Registerer,
unified resource.ResourceClient,
) *FolderAPIBuilder {
if !featuremgmt.AnyEnabled(features,
// disable api in 11.6
if true || !featuremgmt.AnyEnabled(features,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
featuremgmt.FlagProvisioning) {
+2 -1
View File
@@ -641,7 +641,8 @@ function fieldValueColors(f: Field, theme: GrafanaTheme2): FieldColorValues {
let lasti = steps.length - 1;
for (let i = lasti; i > 0; i--) {
conds += `v >= ${steps[i].value} ? ${i} : `;
let rhs = Number(steps[i].value);
conds += `v >= ${rhs} ? ${i} : `;
}
conds += '0';
+4 -4
View File
@@ -976,8 +976,8 @@ def publish_images_step(ver_mode, docker_repo, trigger = None, depends_on = ["rg
"GCP_KEY": from_secret(gcp_grafanauploads),
"DOCKER_USER": from_secret("docker_username"),
"DOCKER_PASSWORD": from_secret("docker_password"),
"GITHUB_APP_ID": from_secret("delivery-bot-app-id"),
"GITHUB_APP_INSTALLATION_ID": from_secret("delivery-bot-app-installation-id"),
"GITHUB_APP_ID": "329617",
"GITHUB_APP_INSTALLATION_ID": "37346161",
"GITHUB_APP_PRIVATE_KEY": from_secret("delivery-bot-app-private-key"),
}
@@ -994,8 +994,8 @@ def publish_images_step(ver_mode, docker_repo, trigger = None, depends_on = ["rg
environment = {
"DOCKER_USER": from_secret("docker_username"),
"DOCKER_PASSWORD": from_secret("docker_password"),
"GITHUB_APP_ID": from_secret("delivery-bot-app-id"),
"GITHUB_APP_INSTALLATION_ID": from_secret("delivery-bot-app-installation-id"),
"GITHUB_APP_ID": "329617",
"GITHUB_APP_INSTALLATION_ID": "37346161",
"GITHUB_APP_PRIVATE_KEY": from_secret("delivery-bot-app-private-key"),
}
+4 -15
View File
@@ -55,8 +55,8 @@ def secrets():
vault_secret(gar_pull_secret, "secret/data/common/gar", ".dockerconfigjson"),
vault_secret(drone_token, "infra/data/ci/drone", "machine-user-token"),
vault_secret(prerelease_bucket, "infra/data/ci/grafana/prerelease", "bucket"),
vault_secret(docker_username, "infra/data/ci/grafanaci-docker-hub", "username"),
vault_secret(docker_password, "infra/data/ci/grafanaci-docker-hub", "password"),
vault_secret(docker_username, "ci/data/common/dockerhub", "username"),
vault_secret(docker_password, "ci/data/common/dockerhub", "password"),
vault_secret(
gcp_upload_artifacts_key,
"infra/data/ci/grafana/releng/artifacts-uploader-service-account",
@@ -153,21 +153,10 @@ def secrets():
"infra/data/ci/grafana-release-eng/rgm",
"dagger_token",
),
# grafana-delivery-bot secrets
vault_secret(
"delivery-bot-app-id",
"infra/data/ci/grafana-release-eng/grafana-delivery-bot",
"app-id",
),
vault_secret(
"delivery-bot-app-installation-id",
"infra/data/ci/grafana-release-eng/grafana-delivery-bot",
"app-installation-id",
),
vault_secret(
"delivery-bot-app-private-key",
"infra/data/ci/grafana-release-eng/grafana-delivery-bot",
"app-private-key",
"ci/data/repo/grafana/grafana/delivery-bot-app",
"PRIVATE_KEY",
),
vault_secret(
"gcr_credentials",