mirror of https://github.com/grafana/grafana
commit
44a134f72b
@ -1,39 +1,26 @@ |
|||||||
name: Auto-milestone |
name: Auto-milestone |
||||||
on: |
on: |
||||||
pull_request: |
pull_request_target: |
||||||
types: |
types: |
||||||
- opened |
- opened |
||||||
- reopened |
- reopened |
||||||
- closed |
- closed |
||||||
|
- ready_for_review |
||||||
|
|
||||||
jobs: |
permissions: |
||||||
config: |
pull-requests: write |
||||||
runs-on: "ubuntu-latest" |
|
||||||
outputs: |
|
||||||
has-secrets: ${{ steps.check.outputs.has-secrets }} |
|
||||||
steps: |
|
||||||
- name: "Check for secrets" |
|
||||||
id: check |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
if [ -n "${{ (secrets.GRAFANA_DELIVERY_BOT_APP_ID != '' && secrets.GRAFANA_DELIVERY_BOT_APP_PEM != '') || '' }}" ]; then |
|
||||||
echo "has-secrets=1" >> "$GITHUB_OUTPUT" |
|
||||||
fi |
|
||||||
|
|
||||||
|
# Note: this action runs with write permissions on GITHUB_TOKEN even from forks |
||||||
|
# so it must not run untrusted code (such as checking out the pull request) |
||||||
|
jobs: |
||||||
main: |
main: |
||||||
needs: config |
|
||||||
if: needs.config.outputs.has-secrets |
|
||||||
runs-on: ubuntu-latest |
runs-on: ubuntu-latest |
||||||
|
if: github.event.pull_request.draft == false |
||||||
steps: |
steps: |
||||||
- name: "Generate token" |
# Note: Github will not trigger other actions from this because it uses |
||||||
id: generate_token |
# the GITHUB_TOKEN token |
||||||
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 |
|
||||||
with: |
|
||||||
app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} |
|
||||||
private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} |
|
||||||
|
|
||||||
- name: Run auto-milestone |
- name: Run auto-milestone |
||||||
uses: grafana/grafana-github-actions-go/auto-milestone@main |
uses: grafana/grafana-github-actions-go/auto-milestone@main |
||||||
with: |
with: |
||||||
pr: ${{ github.event.pull_request.number }} |
pr: ${{ github.event.pull_request.number }} |
||||||
token: ${{ steps.generate_token.outputs.token }} |
token: ${{ secrets.GITHUB_TOKEN }} |
||||||
|
@ -0,0 +1,16 @@ |
|||||||
|
route: |
||||||
|
group_by: ['alertname'] |
||||||
|
group_wait: 30s |
||||||
|
group_interval: 5m |
||||||
|
repeat_interval: 1h |
||||||
|
receiver: 'web.hook' |
||||||
|
receivers: |
||||||
|
- name: 'web.hook' |
||||||
|
webhook_configs: |
||||||
|
- url: 'http://127.0.0.1:5001/' |
||||||
|
inhibit_rules: |
||||||
|
- source_match: |
||||||
|
severity: 'critical' |
||||||
|
target_match: |
||||||
|
severity: 'warning' |
||||||
|
equal: ['alertname', 'dev', 'instance'] |
@ -0,0 +1,39 @@ |
|||||||
|
import * as e2e from '@grafana/e2e-selectors'; |
||||||
|
import { expect, test } from '@grafana/plugin-e2e'; |
||||||
|
|
||||||
|
test('should evaluate to false if entire request returns 500', async ({ page, alertRuleEditPage, selectors }) => { |
||||||
|
await alertRuleEditPage.alertRuleNameField.fill('Test Alert Rule'); |
||||||
|
|
||||||
|
// remove the default query
|
||||||
|
const queryA = alertRuleEditPage.getAlertRuleQueryRow('A'); |
||||||
|
await alertRuleEditPage |
||||||
|
.getByGrafanaSelector(selectors.components.QueryEditorRow.actionButton('Remove query'), { |
||||||
|
root: queryA.locator, |
||||||
|
}) |
||||||
|
.click(); |
||||||
|
await expect(alertRuleEditPage.evaluate()).not.toBeOK(); |
||||||
|
}); |
||||||
|
|
||||||
|
test('should evaluate to false if entire request returns 200 but partial query result is invalid', async ({ |
||||||
|
page, |
||||||
|
alertRuleEditPage, |
||||||
|
}) => { |
||||||
|
await alertRuleEditPage.alertRuleNameField.fill('Test Alert Rule'); |
||||||
|
|
||||||
|
//add working query
|
||||||
|
const queryA = alertRuleEditPage.getAlertRuleQueryRow('A'); |
||||||
|
await queryA.datasource.set('gdev-prometheus'); |
||||||
|
await queryA.locator.getByLabel('Code').click(); |
||||||
|
await page.waitForFunction(() => window.monaco); |
||||||
|
await queryA.getByGrafanaSelector(e2e.selectors.components.QueryField.container).click(); |
||||||
|
await page.keyboard.insertText('topk(5, max(scrape_duration_seconds) by (job))'); |
||||||
|
|
||||||
|
//add broken query
|
||||||
|
const newQuery = await alertRuleEditPage.clickAddQueryRow(); |
||||||
|
await newQuery.datasource.set('gdev-prometheus'); |
||||||
|
await newQuery.locator.getByLabel('Code').click(); |
||||||
|
await newQuery.getByGrafanaSelector(e2e.selectors.components.QueryField.container).click(); |
||||||
|
await page.keyboard.insertText('topk(5,'); |
||||||
|
|
||||||
|
await expect(alertRuleEditPage.evaluate()).not.toBeOK(); |
||||||
|
}); |
@ -1,7 +1,7 @@ |
|||||||
import testDashboard from '../dashboards/TestDashboard.json'; |
import testDashboard from '../dashboards/TestDashboard.json'; |
||||||
import { e2e } from '../utils'; |
import { e2e } from '../utils'; |
||||||
|
// Skipping due to race conditions with same old arch test e2e/dashboards-suite/dashboard-browse.spec.ts
|
||||||
describe('Dashboard browse', () => { |
describe.skip('Dashboard browse', () => { |
||||||
beforeEach(() => { |
beforeEach(() => { |
||||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); |
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); |
||||||
}); |
}); |
@ -1,6 +1,6 @@ |
|||||||
import React, { useEffect, useState } from 'react'; |
import React, { useEffect, useState } from 'react'; |
||||||
|
|
||||||
import { store } from '../store'; |
import { store } from './store'; |
||||||
|
|
||||||
export interface Props<T> { |
export interface Props<T> { |
||||||
storageKey: string; |
storageKey: string; |
@ -1,6 +0,0 @@ |
|||||||
// reset font file paths so storybook loads them based on |
|
||||||
// staticDirs defined in packages/grafana-ui/.storybook/main.ts |
|
||||||
$font-file-path: './public/fonts'; |
|
||||||
$fa-font-path: $font-file-path; |
|
||||||
|
|
||||||
@import '../../../public/sass/grafana.dark.scss'; |
|
@ -1,6 +0,0 @@ |
|||||||
// reset font file paths so storybook loads them based on |
|
||||||
// staticDirs defined in packages/grafana-ui/.storybook/main.ts |
|
||||||
$font-file-path: './public/fonts'; |
|
||||||
$fa-font-path: $font-file-path; |
|
||||||
|
|
||||||
@import '../../../public/sass/grafana.light.scss'; |
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,54 @@ |
|||||||
|
package util |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"net/url" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
const masking = "hidden" |
||||||
|
|
||||||
|
var sensitiveQueryChecks = map[string]func(key string, urlValues url.Values) bool{ |
||||||
|
"auth_token": func(key string, urlValues url.Values) bool { |
||||||
|
return true |
||||||
|
}, |
||||||
|
"x-amz-signature": func(key string, urlValues url.Values) bool { |
||||||
|
return true |
||||||
|
}, |
||||||
|
"x-goog-signature": func(key string, urlValues url.Values) bool { |
||||||
|
return true |
||||||
|
}, |
||||||
|
"sig": func(key string, urlValues url.Values) bool { |
||||||
|
for k := range urlValues { |
||||||
|
if strings.ToLower(k) == "sv" { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
func SanitizeURI(s string) (string, error) { |
||||||
|
if s == "" { |
||||||
|
return s, nil |
||||||
|
} |
||||||
|
|
||||||
|
u, err := url.ParseRequestURI(s) |
||||||
|
if err != nil { |
||||||
|
return "", fmt.Errorf("failed to sanitize URL") |
||||||
|
} |
||||||
|
|
||||||
|
// strip out sensitive query strings
|
||||||
|
urlValues := u.Query() |
||||||
|
for key := range urlValues { |
||||||
|
lk := strings.ToLower(key) |
||||||
|
if checker, ok := sensitiveQueryChecks[lk]; ok { |
||||||
|
if checker(key, urlValues) { |
||||||
|
urlValues.Set(key, masking) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
u.RawQuery = urlValues.Encode() |
||||||
|
|
||||||
|
return u.String(), nil |
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
package util |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert" |
||||||
|
) |
||||||
|
|
||||||
|
func Test_sanitizeURI(t *testing.T) { |
||||||
|
tests := []struct { |
||||||
|
name string |
||||||
|
input string |
||||||
|
want string |
||||||
|
expectError bool |
||||||
|
}{ |
||||||
|
{ |
||||||
|
name: "Receiving empty string should return it", |
||||||
|
input: "", |
||||||
|
want: "", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "Receiving URL with auth_token should remove it", |
||||||
|
input: "https://grafana.com/?auth_token=secret-token&q=1234", |
||||||
|
want: "https://grafana.com/?auth_token=hidden&q=1234", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "Receiving presigned URL from AWS should remove signature", |
||||||
|
input: "https://s3.amazonaws.com/finance-department-bucket/2022/tax-certificate.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA3SGQVQG7FGA6KKA6%2F20221104%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221104T140227Z&X-Amz-Expires=3600&X-Amz-Signature=b22&X-Amz-SignedHeaders=host", |
||||||
|
want: "https://s3.amazonaws.com/finance-department-bucket/2022/tax-certificate.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA3SGQVQG7FGA6KKA6%2F20221104%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221104T140227Z&X-Amz-Expires=3600&X-Amz-Signature=hidden&X-Amz-SignedHeaders=host", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "Receiving presigned URL from GCP should remove signature", |
||||||
|
input: "https://storage.googleapis.com/example-bucket/cat.jpeg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=example%40example-project.iam.gserviceaccount.com%2F20181026%2Fus-central1%2Fstorage%2Fgoog4_request&X-Goog-Date=20181026T181309Z&X-Goog-Expires=900&X-Goog-Signature=247a&X-Goog-SignedHeaders=host", |
||||||
|
want: "https://storage.googleapis.com/example-bucket/cat.jpeg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=example%40example-project.iam.gserviceaccount.com%2F20181026%2Fus-central1%2Fstorage%2Fgoog4_request&X-Goog-Date=20181026T181309Z&X-Goog-Expires=900&X-Goog-Signature=hidden&X-Goog-SignedHeaders=host", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "Receiving presigned URL with lower case query params from GCP should remove signature", |
||||||
|
input: "https://storage.googleapis.com/example-bucket/cat.jpeg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=example%40example-project.iam.gserviceaccount.com%2F20181026%2Fus-central1%2Fstorage%2Fgoog4_request&X-Goog-Date=20181026T181309Z&X-Goog-Expires=900&x-goog-signature=247a&X-Goog-SignedHeaders=host", |
||||||
|
want: "https://storage.googleapis.com/example-bucket/cat.jpeg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=example%40example-project.iam.gserviceaccount.com%2F20181026%2Fus-central1%2Fstorage%2Fgoog4_request&X-Goog-Date=20181026T181309Z&X-Goog-Expires=900&X-Goog-SignedHeaders=host&x-goog-signature=hidden", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "Receiving presigned URL from Azure should remove signature", |
||||||
|
input: "https://myaccount.queue.core.windows.net/myqueue/messages?se=2015-07-02T08%3A49Z&si=YWJjZGVmZw%3D%3D&sig=jDrr6cna7JPwIaxWfdH0tT5v9dc%3d&sp=p&st=2015-07-01T08%3A49Z&sv=2015-02-21&visibilitytimeout=120", |
||||||
|
want: "https://myaccount.queue.core.windows.net/myqueue/messages?se=2015-07-02T08%3A49Z&si=YWJjZGVmZw%3D%3D&sig=hidden&sp=p&st=2015-07-01T08%3A49Z&sv=2015-02-21&visibilitytimeout=120", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "Receiving presigned URL from Azure with upper case query values should remove signature", |
||||||
|
input: "https://myaccount.queue.core.windows.net/myqueue/messages?se=2015-07-02T08%3A49Z&si=YWJjZGVmZw%3D%3D&SIG=jDrr6cna7JPwIaxWfdH0tT5v9dc%3d&sp=p&st=2015-07-01T08%3A49Z&SV=2015-02-21&visibilitytimeout=120", |
||||||
|
want: "https://myaccount.queue.core.windows.net/myqueue/messages?SIG=hidden&SV=2015-02-21&se=2015-07-02T08%3A49Z&si=YWJjZGVmZw%3D%3D&sp=p&st=2015-07-01T08%3A49Z&visibilitytimeout=120", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "Receiving valid URL string should return it parsed", |
||||||
|
input: "https://grafana.com/?sig=testing-a-generic-parameter", |
||||||
|
want: "https://grafana.com/?sig=testing-a-generic-parameter", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "Receiving invalid URL string should return empty string", |
||||||
|
input: "this is not a valid URL", |
||||||
|
want: "", |
||||||
|
expectError: true, |
||||||
|
}, |
||||||
|
} |
||||||
|
for _, tt := range tests { |
||||||
|
t.Run(tt.name, func(t *testing.T) { |
||||||
|
url, err := SanitizeURI(tt.input) |
||||||
|
if tt.expectError { |
||||||
|
assert.Error(t, err) |
||||||
|
} else { |
||||||
|
assert.NoError(t, err) |
||||||
|
} |
||||||
|
assert.Equalf(t, tt.want, url, "SanitizeURI(%v)", tt.input) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue