diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79c4020 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +SOURCES/grafana-7.5.11.tar.gz +SOURCES/grafana-vendor-7.5.11-1.tar.xz +SOURCES/grafana-webpack-7.5.11-1.tar.gz diff --git a/.grafana.metadata b/.grafana.metadata new file mode 100644 index 0000000..d33b818 --- /dev/null +++ b/.grafana.metadata @@ -0,0 +1,3 @@ +cd7bfb63dd91361c1bc9c46d1f889b1f54f7758a SOURCES/grafana-7.5.11.tar.gz +d55ac0b3a8fb3a0ce772442923e2ca3cba1af78f SOURCES/grafana-vendor-7.5.11-1.tar.xz +db79c330e9a56dac2cdcae9b7c07c86112a66237 SOURCES/grafana-webpack-7.5.11-1.tar.gz diff --git a/SOURCES/001-wrappers-grafana-cli.patch b/SOURCES/001-wrappers-grafana-cli.patch new file mode 100644 index 0000000..01fe90e --- /dev/null +++ b/SOURCES/001-wrappers-grafana-cli.patch @@ -0,0 +1,49 @@ +diff --git a/packaging/wrappers/grafana-cli b/packaging/wrappers/grafana-cli +index 9cad151c0d..a786edc596 100755 +--- a/packaging/wrappers/grafana-cli ++++ b/packaging/wrappers/grafana-cli +@@ -5,18 +5,19 @@ + # the system-wide Grafana configuration that was bundled with the package as we + # use the binary. + +-DEFAULT=/etc/default/grafana ++DEFAULT=/etc/sysconfig/grafana-server + + GRAFANA_HOME=/usr/share/grafana + CONF_DIR=/etc/grafana + DATA_DIR=/var/lib/grafana + PLUGINS_DIR=/var/lib/grafana/plugins + LOG_DIR=/var/log/grafana ++LIBEXEC_DIR=/usr/libexec/grafana + + CONF_FILE=$CONF_DIR/grafana.ini + PROVISIONING_CFG_DIR=$CONF_DIR/provisioning + +-EXECUTABLE=$GRAFANA_HOME/bin/grafana-cli ++EXECUTABLE=$LIBEXEC_DIR/grafana-cli + + if [ ! -x $EXECUTABLE ]; then + echo "Program not installed or not executable" +@@ -24,6 +25,7 @@ if [ ! -x $EXECUTABLE ]; then + fi + + # overwrite settings from default file ++#shellcheck disable=SC1090 + if [ -f "$DEFAULT" ]; then + . "$DEFAULT" + fi +@@ -36,4 +38,13 @@ OPTS="--homepath=${GRAFANA_HOME} \ + cfg:default.paths.logs=${LOG_DIR} \ + cfg:default.paths.plugins=${PLUGINS_DIR}'" + +-eval $EXECUTABLE "$OPTS" "$@" ++if [ "$(id -u)" -eq 0 ]; then ++ cd "${GRAFANA_HOME}" ++ exec runuser -u "${GRAFANA_USER}" -- "$EXECUTABLE" "$OPTS" "$@" ++elif [ "$(id -u -n)" = "${GRAFANA_USER}" ]; then ++ cd "${GRAFANA_HOME}" ++ exec "$EXECUTABLE" "$OPTS" "$@" ++else ++ echo "$0: please run this script as user \"${GRAFANA_USER}\" or root." ++ exit 5 ++fi diff --git a/SOURCES/002-manpages.patch b/SOURCES/002-manpages.patch new file mode 100644 index 0000000..36ca294 --- /dev/null +++ b/SOURCES/002-manpages.patch @@ -0,0 +1,144 @@ +diff --git a/docs/man/man1/grafana-cli.1 b/docs/man/man1/grafana-cli.1 +new file mode 100644 +index 0000000000..7ac2af882c +--- /dev/null ++++ b/docs/man/man1/grafana-cli.1 +@@ -0,0 +1,60 @@ ++.TH GRAFANA "1" "October 2021" "Grafana cli version 7.5.11" "User Commands" ++.SH NAME ++grafana-cli \- command line administration for the Grafana metrics dashboard and graph editor ++.SH DESCRIPTION ++.SS "NAME:" ++.IP ++grafana-cli ++.SS "USAGE:" ++.IP ++\fBgrafana\-cli\fP [\fIglobal options\fP] \fIcommand\fP [\fIcommand options\fP] [\fIarguments\fP...] ++.SS "COMMANDS:" ++.TP ++plugins ++Manage plugins for grafana ++.TP ++admin ++Grafana admin commands ++.TP ++help, h ++Shows a list of commands or help for one command ++.SS "GLOBAL OPTIONS:" ++.TP ++\fB\-\-pluginsDir\fR value ++path to the grafana plugin directory (default: "/var/lib/grafana/plugins") [$GF_PLUGIN_DIR] ++.TP ++\fB\-\-repo\fR value ++url to the plugin repository (default: "https://grafana.com/api/plugins") [$GF_PLUGIN_REPO] ++.TP ++\fB\-\-pluginUrl\fR value ++Full url to the plugin zip file instead of downloading the plugin from grafana.com/api [$GF_PLUGIN_URL] ++.TP ++\fB\-\-insecure\fR ++Skip TLS verification (insecure) ++.TP ++\fB\-\-debug\fR, \fB\-d\fR ++enable debug logging ++.TP ++\fB\-\-configOverrides\fR value ++Configuration options to override defaults as a string. e.g. cfg:default.paths.log=/dev/null ++.TP ++\fB\-\-homepath\fR value ++Path to Grafana install/home path, defaults to working directory ++.TP ++\fB\-\-config\fR value ++Path to config file ++.TP ++\fB\-\-help\fR, \fB\-h\fR ++show help ++.TP ++\fB\-\-version\fR, \fB\-v\fR ++print the version ++.SH "SEE ALSO" ++Additional documentation for ++.B grafana-cli ++is available on-line at ++.BR http://docs.grafana.org/administration/cli/ . ++The full documentation for ++.B Grafana ++is available on-line at ++.BR http://docs.grafana.org/ . +diff --git a/docs/man/man1/grafana-server.1 b/docs/man/man1/grafana-server.1 +new file mode 100644 +index 0000000000..c616268b31 +--- /dev/null ++++ b/docs/man/man1/grafana-server.1 +@@ -0,0 +1,72 @@ ++.TH VERSION "1" "October 2021" "Version 7.5.11" "User Commands" ++.SH NAME ++grafana-server \- back-end server for the Grafana metrics dashboard and graph editor ++.SH DESCRIPTION ++.B grafana-server ++is the back-end server for the Grafana metrics dashboard and graph editor. ++The ++.B grafana-server ++program should not normally be run from the command line, ++except when testing or for development purposes. ++Rather it should be managed by ++.BR systemd . ++After installing Grafana, the systemd service should be enabled and started as follows: ++.P ++.in 1i ++.B systemctl daemon-reload ++.br ++.B systemctl enable grafana-server.service ++.br ++.B systemctl start grafana-server.service ++.in ++.P ++.SH OPTIONS ++The ++.B gafana-server ++configuration is specified in ++.BR /etc/grafana/grafana.ini ++and is well documented with comments. ++The command-line options listed below override options of ++the same (or similar) name in the configuration file. ++.P ++.HP ++\fB\-config\fR string ++.IP ++path to config file ++.HP ++\fB\-homepath\fR string ++.IP ++path to grafana install/home path, defaults to working directory ++.HP ++\fB\-packaging\fR string ++.IP ++describes the way Grafana was installed (default "unknown") ++.HP ++\fB\-pidfile\fR string ++.IP ++path to pid file ++.HP ++\fB\-profile\fR ++.IP ++Turn on pprof profiling ++.HP ++\fB\-profile\-port\fR uint ++.IP ++Define custom port for profiling (default 6060) ++.HP ++\fB\-tracing\fR ++.IP ++Turn on tracing ++.HP ++\fB\-tracing\-file\fR string ++.IP ++Define tracing output file (default "trace.out") ++.TP ++\fB\-v\fR ++.IP ++prints current version and exits ++.SH "SEE ALSO" ++The full documentation for ++.B Grafana ++is available on-line at ++.BR http://docs.grafana.org/ . diff --git a/SOURCES/003-fix-dashboard-abspath-test.patch b/SOURCES/003-fix-dashboard-abspath-test.patch new file mode 100644 index 0000000..ad7e5bf --- /dev/null +++ b/SOURCES/003-fix-dashboard-abspath-test.patch @@ -0,0 +1,24 @@ +diff --git a/pkg/services/provisioning/dashboards/file_reader_linux_test.go b/pkg/services/provisioning/dashboards/file_reader_linux_test.go +index 3584bbc242..1a89767b69 100644 +--- a/pkg/services/provisioning/dashboards/file_reader_linux_test.go ++++ b/pkg/services/provisioning/dashboards/file_reader_linux_test.go +@@ -28,6 +28,7 @@ func TestProvisionedSymlinkedFolder(t *testing.T) { + } + + want, err := filepath.Abs(containingID) ++ want, err = filepath.EvalSymlinks(want) + + if err != nil { + t.Errorf("expected err to be nil") +diff --git a/pkg/services/provisioning/dashboards/file_reader_test.go b/pkg/services/provisioning/dashboards/file_reader_test.go +index 946d487d5f..2acef40eed 100644 +--- a/pkg/services/provisioning/dashboards/file_reader_test.go ++++ b/pkg/services/provisioning/dashboards/file_reader_test.go +@@ -318,6 +318,7 @@ func TestDashboardFileReader(t *testing.T) { + } + + absPath1, err := filepath.Abs(unprovision + "/dashboard1.json") ++ absPath1, err = filepath.EvalSymlinks(absPath1) + So(err, ShouldBeNil) + // This one does not exist on disk, simulating a deleted file + absPath2, err := filepath.Abs(unprovision + "/dashboard2.json") diff --git a/SOURCES/004-skip-x86-goldenfiles-tests.patch b/SOURCES/004-skip-x86-goldenfiles-tests.patch new file mode 100644 index 0000000..bb61e0b --- /dev/null +++ b/SOURCES/004-skip-x86-goldenfiles-tests.patch @@ -0,0 +1,69 @@ +diff --git a/packages/grafana-data/src/dataframe/ArrowDataFrame.test.ts b/packages/grafana-data/src/dataframe/ArrowDataFrame.test.ts +index 96efaccfce..bcdd98144f 100644 +--- a/packages/grafana-data/src/dataframe/ArrowDataFrame.test.ts ++++ b/packages/grafana-data/src/dataframe/ArrowDataFrame.test.ts +@@ -52,7 +52,7 @@ describe('Read/Write arrow Table to DataFrame', () => { + expect(after).toEqual(before); + }); + +- test('should read all types', () => { ++ test.skip('should read all types', () => { + const fullpath = path.resolve(__dirname, './__snapshots__/all_types.golden.arrow'); + const arrow = fs.readFileSync(fullpath); + const table = Table.from([arrow]); +diff --git a/packages/grafana-runtime/src/utils/queryResponse.test.ts b/packages/grafana-runtime/src/utils/queryResponse.test.ts +index 0adb915d2c..8985d7beab 100644 +--- a/packages/grafana-runtime/src/utils/queryResponse.test.ts ++++ b/packages/grafana-runtime/src/utils/queryResponse.test.ts +@@ -47,7 +47,7 @@ const emptyResults = { + /* eslint-enable */ + + describe('Query Response parser', () => { +- test('should parse output with dataframe', () => { ++ test.skip('should parse output with dataframe', () => { + const res = toDataQueryResponse(resp); + const frames = res.data; + expect(frames).toHaveLength(2); +@@ -131,7 +131,7 @@ describe('Query Response parser', () => { + `); + }); + +- test('should parse output with dataframe in order of queries', () => { ++ test.skip('should parse output with dataframe in order of queries', () => { + const queries: DataQuery[] = [{ refId: 'B' }, { refId: 'A' }]; + const res = toDataQueryResponse(resp, queries); + const frames = res.data; +@@ -250,7 +250,7 @@ describe('Query Response parser', () => { + expect(ids).toEqual(['A', 'B', 'X']); + }); + +- test('resultWithError', () => { ++ test.skip('resultWithError', () => { + // Generated from: + // qdr.Responses[q.GetRefID()] = backend.DataResponse{ + // Error: fmt.Errorf("an Error: %w", fmt.Errorf("another error")), +diff --git a/pkg/tsdb/influxdb/flux/executor_test.go b/pkg/tsdb/influxdb/flux/executor_test.go +index 7cfc8bd20a..add6b5f3b8 100644 +--- a/pkg/tsdb/influxdb/flux/executor_test.go ++++ b/pkg/tsdb/influxdb/flux/executor_test.go +@@ -68,6 +68,7 @@ func executeMockedQuery(t *testing.T, name string, query queryModel) *backend.Da + } + + func verifyGoldenResponse(t *testing.T, name string) *backend.DataResponse { ++ t.Skip("x86 memory dump is not compatible with other architectures") + dr := executeMockedQuery(t, name, queryModel{MaxDataPoints: 100}) + + err := experimental.CheckGoldenDataResponse(filepath.Join("testdata", fmt.Sprintf("%s.golden.txt", name)), +diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts +index afc8ba357b..587092a58d 100644 +--- a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts ++++ b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts +@@ -78,7 +78,7 @@ describe('CloudWatchDatasource', () => { + }); + + describe('When getting log groups', () => { +- it('should return log groups as an array of strings', async () => { ++ it.skip('should return log groups as an array of strings', async () => { + const response = { + results: { + A: { diff --git a/SOURCES/005-remove-unused-dependencies.patch b/SOURCES/005-remove-unused-dependencies.patch new file mode 100644 index 0000000..19d72f0 --- /dev/null +++ b/SOURCES/005-remove-unused-dependencies.patch @@ -0,0 +1,63 @@ +diff --git a/go.mod b/go.mod +index 426b70ab7a..dc0c9a61ef 100644 +--- a/go.mod ++++ b/go.mod +@@ -21,7 +21,6 @@ require ( + github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b + github.com/centrifugal/centrifuge v0.13.0 + github.com/cortexproject/cortex v1.4.1-0.20201022071705-85942c5703cf +- github.com/crewjam/saml v0.4.6-0.20201227203850-bca570abb2ce + github.com/davecgh/go-spew v1.1.1 + github.com/denisenkom/go-mssqldb v0.0.0-20200910202707-1e08a3fab204 + github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect +@@ -57,7 +56,6 @@ require ( + github.com/jmespath/go-jmespath v0.4.0 + github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/json-iterator/go v1.1.10 +- github.com/jung-kurt/gofpdf v1.16.2 + github.com/lib/pq v1.9.0 + github.com/linkedin/goavro/v2 v2.10.0 + github.com/magefile/mage v1.11.0 +diff --git a/go.sum b/go.sum +index 98874d6a7c..03243066ac 100644 +--- a/go.sum ++++ b/go.sum +@@ -282,8 +282,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr + github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= + github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= + github.com/crewjam/httperr v0.0.0-20190612203328-a946449404da/go.mod h1:+rmNIXRvYMqLQeR4DHyTvs6y0MEMymTz4vyFpFkKTPs= +-github.com/crewjam/saml v0.4.6-0.20201227203850-bca570abb2ce h1:pAuTpLhCqC20s2RLhUirfw606jReW+8z2U5EvG+0S7E= +-github.com/crewjam/saml v0.4.6-0.20201227203850-bca570abb2ce/go.mod h1:/gCaeLf13J8/621RNZ6TaExji/8xCWcn6UmdJ57wURQ= + github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ= + github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= + github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= +@@ -914,10 +912,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 + github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= + github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= + github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +-github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +-github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +-github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc= +-github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= + github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= + github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= + github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go +index 24031ace2e..081475fc89 100644 +--- a/pkg/extensions/main.go ++++ b/pkg/extensions/main.go +@@ -6,14 +6,12 @@ import ( + + _ "github.com/beevik/etree" + _ "github.com/cortexproject/cortex/pkg/util" +- _ "github.com/crewjam/saml" + _ "github.com/gobwas/glob" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/licensing" + "github.com/grafana/grafana/pkg/services/validations" + _ "github.com/grafana/loki/pkg/logproto" + _ "github.com/grpc-ecosystem/go-grpc-middleware" +- _ "github.com/jung-kurt/gofpdf" + _ "github.com/linkedin/goavro/v2" + _ "github.com/pkg/errors" + _ "github.com/robfig/cron" diff --git a/SOURCES/006-fix-gtime-test-32bit.patch b/SOURCES/006-fix-gtime-test-32bit.patch new file mode 100644 index 0000000..c38a50f --- /dev/null +++ b/SOURCES/006-fix-gtime-test-32bit.patch @@ -0,0 +1,17 @@ +diff --git a/pkg/components/gtime/gtime_test.go b/pkg/components/gtime/gtime_test.go +index 0b1b23a1db..eb9fe718c7 100644 +--- a/pkg/components/gtime/gtime_test.go ++++ b/pkg/components/gtime/gtime_test.go +@@ -20,9 +20,9 @@ func TestParseInterval(t *testing.T) { + {inp: "1d", duration: 24 * time.Hour}, + {inp: "1w", duration: 168 * time.Hour}, + {inp: "2w", duration: 2 * 168 * time.Hour}, +- {inp: "1M", duration: time.Duration(daysInMonth * 24 * int(time.Hour))}, +- {inp: "1y", duration: time.Duration(daysInYear * 24 * int(time.Hour))}, +- {inp: "5y", duration: time.Duration(calculateDays5y() * 24 * int(time.Hour))}, ++ {inp: "1M", duration: time.Duration(int64(daysInMonth) * 24 * int64(time.Hour))}, ++ {inp: "1y", duration: time.Duration(int64(daysInYear) * 24 * int64(time.Hour))}, ++ {inp: "5y", duration: time.Duration(int64(calculateDays5y()) * 24 * int64(time.Hour))}, + {inp: "invalid-duration", err: regexp.MustCompile(`^time: invalid duration "?invalid-duration"?$`)}, + } + for i, tc := range tcs { diff --git a/SOURCES/008-remove-unused-frontend-crypto.patch b/SOURCES/008-remove-unused-frontend-crypto.patch new file mode 100644 index 0000000..2409e23 --- /dev/null +++ b/SOURCES/008-remove-unused-frontend-crypto.patch @@ -0,0 +1,26 @@ +diff --git a/package.json b/package.json +index 9c5a2d93e2..7f65949ea4 100644 +--- a/package.json ++++ b/package.json +@@ -294,6 +294,9 @@ + "whatwg-fetch": "3.1.0" + }, + "resolutions": { ++ "crypto-browserify": "https://registry.yarnpkg.com/@favware/skip-dependency/-/skip-dependency-1.1.1.tgz", ++ "selfsigned": "https://registry.yarnpkg.com/@favware/skip-dependency/-/skip-dependency-1.1.1.tgz", ++ "http-signature": "https://registry.yarnpkg.com/@favware/skip-dependency/-/skip-dependency-1.1.1.tgz", + "caniuse-db": "1.0.30000772", + "react-use-measure": "https://github.com/mckn/react-use-measure.git#remove-cjs-export" + }, +diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js +index 3e56d31c37..a03ed1a67a 100644 +--- a/scripts/webpack/webpack.common.js ++++ b/scripts/webpack/webpack.common.js +@@ -66,6 +66,7 @@ module.exports = { + }, + node: { + fs: 'empty', ++ crypto: false, + }, + plugins: [ + new MonacoWebpackPlugin({ diff --git a/SOURCES/009-patch-unused-backend-crypto.patch b/SOURCES/009-patch-unused-backend-crypto.patch new file mode 100644 index 0000000..12be571 --- /dev/null +++ b/SOURCES/009-patch-unused-backend-crypto.patch @@ -0,0 +1,168 @@ +diff --git a/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go b/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go +new file mode 100644 +index 0000000..871e612 +--- /dev/null ++++ b/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go +@@ -0,0 +1,25 @@ ++package elgamal ++ ++import ( ++ "io" ++ "math/big" ++) ++ ++// PublicKey represents an ElGamal public key. ++type PublicKey struct { ++ G, P, Y *big.Int ++} ++ ++// PrivateKey represents an ElGamal private key. ++type PrivateKey struct { ++ PublicKey ++ X *big.Int ++} ++ ++func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err error) { ++ panic("ElGamal encryption not available") ++} ++ ++func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) { ++ panic("ElGamal encryption not available") ++} +diff --git a/vendor/golang.org/x/crypto/openpgp/packet/packet.go b/vendor/golang.org/x/crypto/openpgp/packet/packet.go +index 9728d61..9f04c2d 100644 +--- a/vendor/golang.org/x/crypto/openpgp/packet/packet.go ++++ b/vendor/golang.org/x/crypto/openpgp/packet/packet.go +@@ -16,7 +16,6 @@ import ( + "math/big" + "math/bits" + +- "golang.org/x/crypto/cast5" + "golang.org/x/crypto/openpgp/errors" + ) + +@@ -487,7 +486,7 @@ func (cipher CipherFunction) KeySize() int { + case Cipher3DES: + return 24 + case CipherCAST5: +- return cast5.KeySize ++ panic("cast5 cipher not available") + case CipherAES128: + return 16 + case CipherAES192: +@@ -517,7 +516,7 @@ func (cipher CipherFunction) new(key []byte) (block cipher.Block) { + case Cipher3DES: + block, _ = des.NewTripleDESCipher(key) + case CipherCAST5: +- block, _ = cast5.NewCipher(key) ++ panic("cast5 cipher not available") + case CipherAES128, CipherAES192, CipherAES256: + block, _ = aes.NewCipher(key) + } +diff --git a/vendor/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go b/vendor/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go +index 6126030..3a54c5f 100644 +--- a/vendor/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go ++++ b/vendor/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go +@@ -5,13 +5,12 @@ + package packet + + import ( +- "crypto/cipher" + "crypto/sha1" + "crypto/subtle" +- "golang.org/x/crypto/openpgp/errors" + "hash" + "io" +- "strconv" ++ ++ "golang.org/x/crypto/openpgp/errors" + ) + + // SymmetricallyEncrypted represents a symmetrically encrypted byte string. The +@@ -45,46 +44,7 @@ func (se *SymmetricallyEncrypted) parse(r io.Reader) error { + // packet can be read. An incorrect key can, with high probability, be detected + // immediately and this will result in a KeyIncorrect error being returned. + func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) { +- keySize := c.KeySize() +- if keySize == 0 { +- return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c))) +- } +- if len(key) != keySize { +- return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length") +- } +- +- if se.prefix == nil { +- se.prefix = make([]byte, c.blockSize()+2) +- _, err := readFull(se.contents, se.prefix) +- if err != nil { +- return nil, err +- } +- } else if len(se.prefix) != c.blockSize()+2 { +- return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths") +- } +- +- ocfbResync := OCFBResync +- if se.MDC { +- // MDC packets use a different form of OCFB mode. +- ocfbResync = OCFBNoResync +- } +- +- s := NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync) +- if s == nil { +- return nil, errors.ErrKeyIncorrect +- } +- +- plaintext := cipher.StreamReader{S: s, R: se.contents} +- +- if se.MDC { +- // MDC packets have an embedded hash that we need to check. +- h := sha1.New() +- h.Write(se.prefix) +- return &seMDCReader{in: plaintext, h: h}, nil +- } +- +- // Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser. +- return seReader{plaintext}, nil ++ panic("OCFB cipher not available") + } + + // seReader wraps an io.Reader with a no-op Close method. +@@ -254,37 +214,5 @@ func (c noOpCloser) Close() error { + // written. + // If config is nil, sensible defaults will be used. + func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, key []byte, config *Config) (contents io.WriteCloser, err error) { +- if c.KeySize() != len(key) { +- return nil, errors.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length") +- } +- writeCloser := noOpCloser{w} +- ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC) +- if err != nil { +- return +- } +- +- _, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion}) +- if err != nil { +- return +- } +- +- block := c.new(key) +- blockSize := block.BlockSize() +- iv := make([]byte, blockSize) +- _, err = config.Random().Read(iv) +- if err != nil { +- return +- } +- s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync) +- _, err = ciphertext.Write(prefix) +- if err != nil { +- return +- } +- plaintext := cipher.StreamWriter{S: s, W: ciphertext} +- +- h := sha1.New() +- h.Write(iv) +- h.Write(iv[blockSize-2:]) +- contents = &seMDCWriter{w: plaintext, h: h} +- return ++ panic("OCFB cipher not available") + } diff --git a/SOURCES/010-fips.patch b/SOURCES/010-fips.patch new file mode 100644 index 0000000..f9adee9 --- /dev/null +++ b/SOURCES/010-fips.patch @@ -0,0 +1,140 @@ +diff --git a/vendor/golang.org/x/crypto/internal/boring/boring.go b/vendor/golang.org/x/crypto/internal/boring/boring.go +new file mode 100644 +index 0000000..a9c550e +--- /dev/null ++++ b/vendor/golang.org/x/crypto/internal/boring/boring.go +@@ -0,0 +1,74 @@ ++// Copyright 2017 The Go Authors. All rights reserved. ++// Copyright 2021 Red Hat. ++// Use of this source code is governed by a BSD-style ++// license that can be found in the LICENSE file. ++ ++// +build linux ++// +build !android ++// +build !no_openssl ++// +build !cmd_go_bootstrap ++// +build !msan ++ ++package boring ++ ++// #include "openssl_pbkdf2.h" ++// #cgo LDFLAGS: -ldl ++import "C" ++import ( ++ "bytes" ++ "crypto/sha1" ++ "crypto/sha256" ++ "hash" ++ "unsafe" ++) ++ ++var ( ++ emptySha1 = sha1.Sum([]byte{}) ++ emptySha256 = sha256.Sum256([]byte{}) ++) ++ ++func hashToMD(h hash.Hash) *C.GO_EVP_MD { ++ emptyHash := h.Sum([]byte{}) ++ ++ switch { ++ case bytes.Equal(emptyHash, emptySha1[:]): ++ return C._goboringcrypto_EVP_sha1() ++ case bytes.Equal(emptyHash, emptySha256[:]): ++ return C._goboringcrypto_EVP_sha256() ++ } ++ return nil ++} ++ ++// charptr returns the address of the underlying array in b, ++// being careful not to panic when b has zero length. ++func charptr(b []byte) *C.char { ++ if len(b) == 0 { ++ return nil ++ } ++ return (*C.char)(unsafe.Pointer(&b[0])) ++} ++ ++// ucharptr returns the address of the underlying array in b, ++// being careful not to panic when b has zero length. ++func ucharptr(b []byte) *C.uchar { ++ if len(b) == 0 { ++ return nil ++ } ++ return (*C.uchar)(unsafe.Pointer(&b[0])) ++} ++ ++func Pbkdf2Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { ++ // println("[debug] using pbkdf2 from OpenSSL") ++ ch := h() ++ md := hashToMD(ch) ++ if md == nil { ++ return nil ++ } ++ ++ out := make([]byte, keyLen) ++ ok := C._goboringcrypto_PKCS5_PBKDF2_HMAC(charptr(password), C.int(len(password)), ucharptr(salt), C.int(len(salt)), C.int(iter), md, C.int(keyLen), ucharptr(out)) ++ if ok != 1 { ++ panic("boringcrypto: PKCS5_PBKDF2_HMAC failed") ++ } ++ return out ++} +diff --git a/vendor/golang.org/x/crypto/internal/boring/notboring.go b/vendor/golang.org/x/crypto/internal/boring/notboring.go +new file mode 100644 +index 0000000..e244fb5 +--- /dev/null ++++ b/vendor/golang.org/x/crypto/internal/boring/notboring.go +@@ -0,0 +1,16 @@ ++// Copyright 2017 The Go Authors. All rights reserved. ++// Copyright 2021 Red Hat. ++// Use of this source code is governed by a BSD-style ++// license that can be found in the LICENSE file. ++ ++// +build !linux !cgo android cmd_go_bootstrap msan no_openssl ++ ++package boring ++ ++import ( ++ "hash" ++) ++ ++func Pbkdf2Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { ++ panic("boringcrypto: not available") ++} +diff --git a/vendor/golang.org/x/crypto/internal/boring/openssl_pbkdf2.h b/vendor/golang.org/x/crypto/internal/boring/openssl_pbkdf2.h +new file mode 100644 +index 0000000..6dfdf10 +--- /dev/null ++++ b/vendor/golang.org/x/crypto/internal/boring/openssl_pbkdf2.h +@@ -0,0 +1,5 @@ ++#include "/usr/lib/golang/src/crypto/internal/boring/goboringcrypto.h" ++ ++DEFINEFUNC(int, PKCS5_PBKDF2_HMAC, ++ (const char *pass, int passlen, const unsigned char *salt, int saltlen, int iter, EVP_MD *digest, int keylen, unsigned char *out), ++ (pass, passlen, salt, saltlen, iter, digest, keylen, out)) +diff --git a/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go b/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go +index 593f653..799a611 100644 +--- a/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go ++++ b/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go +@@ -19,8 +19,11 @@ pbkdf2.Key. + package pbkdf2 // import "golang.org/x/crypto/pbkdf2" + + import ( ++ "crypto/boring" + "crypto/hmac" + "hash" ++ ++ xboring "golang.org/x/crypto/internal/boring" + ) + + // Key derives a key from the password, salt and iteration count, returning a +@@ -40,6 +43,10 @@ import ( + // Using a higher iteration count will increase the cost of an exhaustive + // search but will also make derivation proportionally slower. + func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { ++ if boring.Enabled() { ++ return xboring.Pbkdf2Key(password, salt, iter, keyLen, h) ++ } ++ + prf := hmac.New(h, password) + hashLen := prf.Size() + numBlocks := (keyLen + hashLen - 1) / hashLen diff --git a/SOURCES/011-CVE-2021-43813.patch b/SOURCES/011-CVE-2021-43813.patch new file mode 100644 index 0000000..375b364 --- /dev/null +++ b/SOURCES/011-CVE-2021-43813.patch @@ -0,0 +1,52 @@ +commit ea77415cfe2cefe46ffce233076a1409abaa8df7 +Author: Will Browne +Date: Fri Dec 10 11:29:12 2021 +0000 + + apply fix (#42969) + +diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go +index e6370a29e7..c7199c716e 100644 +--- a/pkg/plugins/plugins.go ++++ b/pkg/plugins/plugins.go +@@ -491,15 +491,15 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { + } + + // nolint:gosec +- // We can ignore the gosec G304 warning on this one because `plug.PluginDir` is based +- // on plugin the folder structure on disk and not user input. +- path := filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToUpper(name))) ++ // We can ignore the gosec G304 warning since we have cleaned the requested file path and subsequently ++ // use this with a prefix of the plugin's directory, which is set during plugin loading ++ path := filepath.Join(plug.PluginDir, mdFilepath(strings.ToUpper(name))) + exists, err := fs.Exists(path) + if err != nil { + return nil, err + } + if !exists { +- path = filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToLower(name))) ++ path = filepath.Join(plug.PluginDir, mdFilepath(strings.ToLower(name))) + } + + exists, err = fs.Exists(path) +@@ -511,8 +511,8 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { + } + + // nolint:gosec +- // We can ignore the gosec G304 warning on this one because `plug.PluginDir` is based +- // on plugin the folder structure on disk and not user input. ++ // We can ignore the gosec G304 warning since we have cleaned the requested file path and subsequently ++ // use this with a prefix of the plugin's directory, which is set during plugin loading + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err +@@ -520,6 +520,10 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { + return data, nil + } + ++func mdFilepath(mdFilename string) string { ++ return filepath.Clean(filepath.Join("/", fmt.Sprintf("%s.md", mdFilename))) ++} ++ + // gets plugin filenames that require verification for plugin signing + func collectPluginFilesWithin(rootDir string) ([]string, error) { + var files []string diff --git a/SOURCES/012-use-hmac-sha-256-for-password-reset-tokens.patch b/SOURCES/012-use-hmac-sha-256-for-password-reset-tokens.patch new file mode 100644 index 0000000..91b6b46 --- /dev/null +++ b/SOURCES/012-use-hmac-sha-256-for-password-reset-tokens.patch @@ -0,0 +1,353 @@ +commit f13c08e9f45d7776cb264b17ec41bc4ff51fc0b9 +Author: Andreas Gerstmayr +Date: Thu Nov 25 18:49:52 2021 +0100 + + notifications: use HMAC-SHA256 to generate time limit codes + + * changes the time limit code generation function to use HMAC-SHA256 + instead of SHA-1 + * multiple new testcases + +diff --git a/pkg/services/notifications/codes.go b/pkg/services/notifications/codes.go +index ea9beb30cc..1ddf05dc69 100644 +--- a/pkg/services/notifications/codes.go ++++ b/pkg/services/notifications/codes.go +@@ -1,48 +1,53 @@ + package notifications + + import ( +- "crypto/sha1" // #nosec ++ "crypto/hmac" ++ "crypto/sha256" + "encoding/hex" + "fmt" ++ "strconv" + "time" + +- "github.com/unknwon/com" +- + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + ) + +-const timeLimitCodeLength = 12 + 6 + 40 ++const timeLimitStartDateLength = 12 ++const timeLimitMinutesLength = 6 ++const timeLimitHmacLength = 64 ++const timeLimitCodeLength = timeLimitStartDateLength + timeLimitMinutesLength + timeLimitHmacLength + + // create a time limit code +-// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string +-func createTimeLimitCode(data string, minutes int, startInf interface{}) (string, error) { ++// code format: 12 length date time string + 6 minutes string + 64 HMAC-SHA256 encoded string ++func createTimeLimitCode(payload string, minutes int, startStr string) (string, error) { + format := "200601021504" + + var start, end time.Time +- var startStr, endStr string ++ var endStr string + +- if startInf == nil { ++ if startStr == "" { + // Use now time create code + start = time.Now() + startStr = start.Format(format) + } else { + // use start string create code +- startStr = startInf.(string) +- start, _ = time.ParseInLocation(format, startStr, time.Local) +- startStr = start.Format(format) ++ var err error ++ start, err = time.ParseInLocation(format, startStr, time.Local) ++ if err != nil { ++ return "", err ++ } + } + + end = start.Add(time.Minute * time.Duration(minutes)) + endStr = end.Format(format) + +- // create sha1 encode string +- sh := sha1.New() +- if _, err := sh.Write([]byte(data + setting.SecretKey + startStr + endStr + +- com.ToStr(minutes))); err != nil { +- return "", err ++ // create HMAC-SHA256 encoded string ++ key := []byte(setting.SecretKey) ++ h := hmac.New(sha256.New, key) ++ if _, err := h.Write([]byte(payload + startStr + endStr)); err != nil { ++ return "", fmt.Errorf("cannot create hmac: %v", err) + } +- encoded := hex.EncodeToString(sh.Sum(nil)) ++ encoded := hex.EncodeToString(h.Sum(nil)) + + code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded) + return code, nil +@@ -50,30 +55,29 @@ func createTimeLimitCode(data string, minutes int, startInf interface{}) (string + + // verify time limit code + func validateUserEmailCode(user *models.User, code string) (bool, error) { +- if len(code) <= 18 { ++ if len(code) < timeLimitCodeLength { + return false, nil + } + +- minutes := setting.EmailCodeValidMinutes + code = code[:timeLimitCodeLength] + + // split code +- start := code[:12] +- lives := code[12:18] +- if d, err := com.StrTo(lives).Int(); err == nil { +- minutes = d ++ startStr := code[:timeLimitStartDateLength] ++ minutesStr := code[timeLimitStartDateLength : timeLimitStartDateLength+timeLimitMinutesLength] ++ minutes, err := strconv.Atoi(minutesStr) ++ if err != nil { ++ return false, fmt.Errorf("invalid time limit code: %v", err) + } + +- // right active code +- data := com.ToStr(user.Id) + user.Email + user.Login + user.Password + user.Rands +- retCode, err := createTimeLimitCode(data, minutes, start) ++ // verify code ++ payload := strconv.FormatInt(user.Id, 10) + user.Email + user.Login + user.Password + user.Rands ++ expectedCode, err := createTimeLimitCode(payload, minutes, startStr) + if err != nil { + return false, err + } +- fmt.Printf("code : %s\ncode2: %s", retCode, code) +- if retCode == code && minutes > 0 { ++ if hmac.Equal([]byte(code), []byte(expectedCode)) && minutes > 0 { + // check time is expired or not +- before, _ := time.ParseInLocation("200601021504", start, time.Local) ++ before, _ := time.ParseInLocation("200601021504", startStr, time.Local) + now := time.Now() + if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() { + return true, nil +@@ -94,15 +98,15 @@ func getLoginForEmailCode(code string) string { + return string(b) + } + +-func createUserEmailCode(u *models.User, startInf interface{}) (string, error) { ++func createUserEmailCode(user *models.User, startStr string) (string, error) { + minutes := setting.EmailCodeValidMinutes +- data := com.ToStr(u.Id) + u.Email + u.Login + u.Password + u.Rands +- code, err := createTimeLimitCode(data, minutes, startInf) ++ payload := strconv.FormatInt(user.Id, 10) + user.Email + user.Login + user.Password + user.Rands ++ code, err := createTimeLimitCode(payload, minutes, startStr) + if err != nil { + return "", err + } + + // add tail hex username +- code += hex.EncodeToString([]byte(u.Login)) ++ code += hex.EncodeToString([]byte(user.Login)) + return code, nil + } +diff --git a/pkg/services/notifications/codes_test.go b/pkg/services/notifications/codes_test.go +index d2b1f3a617..bea88e0bf5 100644 +--- a/pkg/services/notifications/codes_test.go ++++ b/pkg/services/notifications/codes_test.go +@@ -1,19 +1,129 @@ + package notifications + + import ( ++ "fmt" ++ "strconv" + "testing" ++ "time" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + . "github.com/smartystreets/goconvey/convey" ++ "github.com/stretchr/testify/require" + ) + ++func TestTimeLimitCodes(t *testing.T) { ++ user := &models.User{Id: 10, Email: "t@a.com", Login: "asd", Password: "1", Rands: "2"} ++ ++ format := "200601021504" ++ mailPayload := strconv.FormatInt(user.Id, 10) + user.Email + user.Login + user.Password + user.Rands ++ tenMinutesAgo := time.Now().Add(-time.Minute * 10) ++ ++ tests := []struct { ++ desc string ++ payload string ++ start time.Time ++ minutes int ++ valid bool ++ }{ ++ { ++ desc: "code generated 10 minutes ago, 5 minutes valid", ++ payload: mailPayload, ++ start: tenMinutesAgo, ++ minutes: 5, ++ valid: false, ++ }, ++ { ++ desc: "code generated 10 minutes ago, 9 minutes valid", ++ payload: mailPayload, ++ start: tenMinutesAgo, ++ minutes: 9, ++ valid: false, ++ }, ++ { ++ desc: "code generated 10 minutes ago, 10 minutes valid", ++ payload: mailPayload, ++ start: tenMinutesAgo, ++ minutes: 10, ++ // code was valid exactly 10 minutes since evaluating the tenMinutesAgo assignment ++ // by the time this test is run the code is already expired ++ valid: false, ++ }, ++ { ++ desc: "code generated 10 minutes ago, 11 minutes valid", ++ payload: mailPayload, ++ start: tenMinutesAgo, ++ minutes: 11, ++ valid: true, ++ }, ++ { ++ desc: "code generated 10 minutes ago, 20 minutes valid", ++ payload: mailPayload, ++ start: tenMinutesAgo, ++ minutes: 20, ++ valid: true, ++ }, ++ { ++ desc: "code generated 10 minutes ago, 20 minutes valid, tampered payload", ++ payload: mailPayload[:len(mailPayload)-1] + "x", ++ start: tenMinutesAgo, ++ minutes: 20, ++ valid: false, ++ }, ++ } ++ ++ for _, test := range tests { ++ t.Run(test.desc, func(t *testing.T) { ++ code, err := createTimeLimitCode(test.payload, test.minutes, test.start.Format(format)) ++ require.NoError(t, err) ++ ++ isValid, err := validateUserEmailCode(user, code) ++ require.NoError(t, err) ++ require.Equal(t, test.valid, isValid) ++ }) ++ } ++ ++ t.Run("tampered minutes", func(t *testing.T) { ++ code, err := createTimeLimitCode(mailPayload, 5, tenMinutesAgo.Format(format)) ++ require.NoError(t, err) ++ ++ // code is expired ++ isValid, err := validateUserEmailCode(user, code) ++ require.NoError(t, err) ++ require.Equal(t, false, isValid) ++ ++ // let's try to extend the code by tampering the minutes ++ code = code[:12] + fmt.Sprintf("%06d", 20) + code[18:] ++ isValid, err = validateUserEmailCode(user, code) ++ require.NoError(t, err) ++ require.Equal(t, false, isValid) ++ }) ++ ++ t.Run("tampered start string", func(t *testing.T) { ++ code, err := createTimeLimitCode(mailPayload, 5, tenMinutesAgo.Format(format)) ++ require.NoError(t, err) ++ ++ // code is expired ++ isValid, err := validateUserEmailCode(user, code) ++ require.NoError(t, err) ++ require.Equal(t, false, isValid) ++ ++ // let's try to extend the code by tampering the start string ++ oneMinuteAgo := time.Now().Add(-time.Minute) ++ ++ code = oneMinuteAgo.Format(format) + code[12:] ++ isValid, err = validateUserEmailCode(user, code) ++ require.NoError(t, err) ++ require.Equal(t, false, isValid) ++ }) ++} ++ + func TestEmailCodes(t *testing.T) { + Convey("When generating code", t, func() { + setting.EmailCodeValidMinutes = 120 + + user := &models.User{Id: 10, Email: "t@a.com", Login: "asd", Password: "1", Rands: "2"} +- code, err := createUserEmailCode(user, nil) ++ code, err := createUserEmailCode(user, "") + So(err, ShouldBeNil) + + Convey("getLoginForCode should return login", func() { +@@ -27,7 +137,7 @@ func TestEmailCodes(t *testing.T) { + So(isValid, ShouldBeTrue) + }) + +- Convey("Cannot verify in-valid code", func() { ++ Convey("Cannot verify invalid code", func() { + code = "ASD" + isValid, err := validateUserEmailCode(user, code) + So(err, ShouldBeNil) +diff --git a/pkg/services/notifications/notifications.go b/pkg/services/notifications/notifications.go +index beea82f43e..5a575d1415 100644 +--- a/pkg/services/notifications/notifications.go ++++ b/pkg/services/notifications/notifications.go +@@ -149,7 +149,7 @@ func (ns *NotificationService) sendEmailCommandHandler(cmd *models.SendEmailComm + } + + func (ns *NotificationService) sendResetPasswordEmail(cmd *models.SendResetPasswordEmailCommand) error { +- code, err := createUserEmailCode(cmd.User, nil) ++ code, err := createUserEmailCode(cmd.User, "") + if err != nil { + return err + } +diff --git a/pkg/services/notifications/notifications_test.go b/pkg/services/notifications/notifications_test.go +index e7680c3943..fb73e332ea 100644 +--- a/pkg/services/notifications/notifications_test.go ++++ b/pkg/services/notifications/notifications_test.go +@@ -1,12 +1,14 @@ + package notifications + + import ( ++ "regexp" + "testing" + + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + . "github.com/smartystreets/goconvey/convey" ++ "github.com/stretchr/testify/require" + ) + + func TestNotifications(t *testing.T) { +@@ -25,13 +27,28 @@ func TestNotifications(t *testing.T) { + So(err, ShouldBeNil) + + Convey("When sending reset email password", func() { +- err := ns.sendResetPasswordEmail(&models.SendResetPasswordEmailCommand{User: &models.User{Email: "asd@asd.com"}}) ++ user := models.User{Email: "asd@asd.com", Login: "asd@asd.com"} ++ err := ns.sendResetPasswordEmail(&models.SendResetPasswordEmailCommand{User: &user}) + So(err, ShouldBeNil) + + sentMsg := <-ns.mailQueue + So(sentMsg.Body, ShouldContainSubstring, "body") + So(sentMsg.Subject, ShouldEqual, "Reset your Grafana password - asd@asd.com") + So(sentMsg.Body, ShouldNotContainSubstring, "Subject") ++ ++ // find code in mail ++ r, _ := regexp.Compile(`code=(\w+)`) ++ match := r.FindString(sentMsg.Body) ++ code := match[len("code="):] ++ ++ // verify code ++ bus.AddHandler("test", func(query *models.GetUserByLoginQuery) error { ++ query.Result = &user ++ return nil ++ }) ++ query := models.ValidateResetPasswordCodeQuery{Code: code} ++ err = ns.validateResetPasswordCode(&query) ++ require.NoError(t, err) + }) + }) + } diff --git a/SOURCES/Makefile b/SOURCES/Makefile new file mode 100644 index 0000000..dab531d --- /dev/null +++ b/SOURCES/Makefile @@ -0,0 +1,77 @@ +VERSION := $(shell rpm --specfile *.spec --qf '%{VERSION}\n' | head -1) +RELEASE := $(shell rpm --specfile *.spec --qf '%{RELEASE}\n' | head -1 | cut -d. -f1) + +NAME := grafana +RPM_NAME := $(NAME) +SOURCE_DIR := $(NAME)-$(VERSION) +SOURCE_TAR := $(NAME)-$(VERSION).tar.gz +VENDOR_TAR := $(RPM_NAME)-vendor-$(VERSION)-$(RELEASE).tar.xz +WEBPACK_TAR := $(RPM_NAME)-webpack-$(VERSION)-$(RELEASE).tar.gz + +# patches which must be applied before creating the vendor tarball, for example: +# - changes in dependency versions +# - changes in Go module imports (which affect the vendored Go modules) +PATCHES_PRE_VENDOR := \ + 005-remove-unused-dependencies.patch \ + 008-remove-unused-frontend-crypto.patch + +# patches which must be applied before creating the webpack, for example: +# - changes in Node.js sources or vendored dependencies +PATCHES_PRE_WEBPACK := + + +all: $(SOURCE_TAR) $(VENDOR_TAR) $(WEBPACK_TAR) + +$(SOURCE_TAR): + spectool -g $(RPM_NAME).spec + +$(VENDOR_TAR): $(SOURCE_TAR) + # start with a clean state + rm -rf $(SOURCE_DIR) + tar xf $(SOURCE_TAR) + + # Patches to apply before vendoring + for patch in $(PATCHES_PRE_VENDOR); do echo applying $$patch ...; patch -d $(SOURCE_DIR) -p1 --fuzz=0 < $$patch; done + + # Go + cd $(SOURCE_DIR) && go mod vendor -v + # Remove unused crypto + rm $(SOURCE_DIR)/vendor/golang.org/x/crypto/cast5/cast5.go + rm $(SOURCE_DIR)/vendor/golang.org/x/crypto/ed25519/ed25519.go + rm $(SOURCE_DIR)/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go + rm $(SOURCE_DIR)/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go + rm $(SOURCE_DIR)/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go + rm $(SOURCE_DIR)/vendor/golang.org/x/crypto/openpgp/packet/ocfb.go + awk '$$2~/^v/ && $$4 != "indirect" {print "Provides: bundled(golang(" $$1 ")) = " substr($$2, 2)}' $(SOURCE_DIR)/go.mod | \ + sed -E 's/=(.*)-(.*)-(.*)/=\1-\2.\3/g' > $@.manifest + + # Node.js + cd $(SOURCE_DIR) && yarn install --pure-lockfile + # Remove files with licensing issues + find $(SOURCE_DIR) -type d -name 'node-notifier' -prune -exec rm -r {} \; + find $(SOURCE_DIR) -type d -name 'property-information' -prune -exec rm -r {} \; + find $(SOURCE_DIR) -type f -name '*.exe' -delete + rm -r $(SOURCE_DIR)/node_modules/visjs-network/examples + ./list_bundled_nodejs_packages.py $(SOURCE_DIR) >> $@.manifest + + # Create tarball + XZ_OPT=-9 time -p tar cJf $@ \ + $(SOURCE_DIR)/vendor \ + $$(find $(SOURCE_DIR) -type d -name "node_modules" -prune) + +$(WEBPACK_TAR): $(VENDOR_TAR) + # start with a clean state + rm -rf $(SOURCE_DIR) + tar xf $(SOURCE_TAR) + tar xf $(VENDOR_TAR) + + # Patches to apply before creating the webpack + for patch in $(PATCHES_PRE_WEBPACK); do echo applying $$patch ...; patch -d $(SOURCE_DIR) -p1 --fuzz=0 < $$patch; done + + cd $(SOURCE_DIR) && \ + ../build_frontend.sh + + tar cfz $@ $(SOURCE_DIR)/public/build $(SOURCE_DIR)/public/views $(SOURCE_DIR)/plugins-bundled + +clean: + rm -rf *.tar.gz *.tar.xz *.manifest *.rpm $(NAME)-*/ diff --git a/SOURCES/build_frontend.sh b/SOURCES/build_frontend.sh new file mode 100755 index 0000000..fa0fb8e --- /dev/null +++ b/SOURCES/build_frontend.sh @@ -0,0 +1,17 @@ +#!/bin/bash -eu + +# Build the frontend +yarn run build + +# Build the bundled plugins +mkdir plugins-bundled/external +yarn run plugins:build-bundled +for plugin in plugins-bundled/internal/input-datasource; do + mv $plugin $plugin.tmp + mv $plugin.tmp/dist $plugin + rm -rf $plugin.tmp +done +rm plugins-bundled/README.md plugins-bundled/.gitignore plugins-bundled/external.json + +# Fix permissions (webpack sometimes outputs files with mode = 666 due to reasons unknown (race condition/umask issue afaics)) +chmod -R g-w,o-w public/build plugins-bundled diff --git a/SOURCES/distro-defaults.ini b/SOURCES/distro-defaults.ini new file mode 100644 index 0000000..daa0679 --- /dev/null +++ b/SOURCES/distro-defaults.ini @@ -0,0 +1,942 @@ +##################### Grafana Configuration Defaults ##################### +# +# Do not modify this file in grafana installs +# + +# possible values : production, development +app_mode = production + +# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty +instance_name = ${HOSTNAME} + +#################################### Paths ############################### +[paths] +# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) +data = /var/lib/grafana + +# Temporary files in `data` directory older than given duration will be removed +temp_data_lifetime = 24h + +# Directory where grafana can store logs +logs = /var/log/grafana + +# Directory where grafana will automatically scan and look for plugins +plugins = /var/lib/grafana/plugins + +# folder that contains provisioning config files that grafana will apply on startup and while running. +provisioning = /etc/grafana/provisioning + +#################################### Server ############################## +[server] +# Protocol (http, https, h2, socket) +protocol = http + +# The ip address to bind to, empty will bind to all interfaces +http_addr = + +# The http port to use +http_port = 3000 + +# The public facing domain name used to access grafana from a browser +domain = localhost + +# Redirect to correct domain if host header does not match domain +# Prevents DNS rebinding attacks +enforce_domain = false + +# The full public facing url +root_url = %(protocol)s://%(domain)s:%(http_port)s/ + +# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons. +serve_from_sub_path = false + +# Log web requests +router_logging = false + +# the path relative working path +static_root_path = public + +# enable gzip +enable_gzip = false + +# https certs & key file +cert_file = +cert_key = + +# Unix socket path +socket = /tmp/grafana.sock + +# CDN Url +cdn_url = + +# Sets the maximum time in minutes before timing out read of an incoming request and closing idle connections. +# `0` means there is no timeout for reading the request. +read_timeout = 0 + +#################################### Database ############################ +[database] +# You can configure the database connection by specifying type, host, name, user and password +# as separate properties or as on string using the url property. + +# Either "mysql", "postgres" or "sqlite3", it's your choice +type = sqlite3 +host = 127.0.0.1:3306 +name = grafana +user = root +# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;""" +password = +# Use either URL or the previous fields to configure the database +# Example: mysql://user:secret@host:port/database +url = + +# Max idle conn setting default is 2 +max_idle_conn = 2 + +# Max conn setting default is 0 (mean not set) +max_open_conn = + +# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours) +conn_max_lifetime = 14400 + +# Set to true to log the sql calls and execution times. +log_queries = + +# For "postgres", use either "disable", "require" or "verify-full" +# For "mysql", use either "true", "false", or "skip-verify". +ssl_mode = disable + +# Database drivers may support different transaction isolation levels. +# Currently, only "mysql" driver supports isolation levels. +# If the value is empty - driver's default isolation level is applied. +# For "mysql" use "READ-UNCOMMITTED", "READ-COMMITTED", "REPEATABLE-READ" or "SERIALIZABLE". +isolation_level = + +ca_cert_path = +client_key_path = +client_cert_path = +server_cert_name = + +# For "sqlite3" only, path relative to data_path setting +path = grafana.db + +# For "sqlite3" only. cache mode setting used for connecting to the database +cache_mode = private + +#################################### Cache server ############################# +[remote_cache] +# Either "redis", "memcached" or "database" default is "database" +type = database + +# cache connectionstring options +# database: will use Grafana primary database. +# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'. +# memcache: 127.0.0.1:11211 +connstr = + +#################################### Data proxy ########################### +[dataproxy] + +# This enables data proxy logging, default is false +logging = false + +# How long the data proxy waits before timing out, default is 30 seconds. +# This setting also applies to core backend HTTP data sources where query requests use an HTTP client with timeout set. +timeout = 30 + +# How many seconds the data proxy waits before sending a keepalive request. +keep_alive_seconds = 30 + +# How many seconds the data proxy waits for a successful TLS Handshake before timing out. +tls_handshake_timeout_seconds = 10 + +# How many seconds the data proxy will wait for a server's first response headers after +# fully writing the request headers if the request has an "Expect: 100-continue" +# header. A value of 0 will result in the body being sent immediately, without +# waiting for the server to approve. +expect_continue_timeout_seconds = 1 + +# Optionally limits the total number of connections per host, including connections in the dialing, +# active, and idle states. On limit violation, dials will block. +# A value of zero (0) means no limit. +max_conns_per_host = 0 + +# The maximum number of idle connections that Grafana will keep alive. +max_idle_connections = 100 + +# The maximum number of idle connections per host that Grafana will keep alive. +max_idle_connections_per_host = 2 + +# How many seconds the data proxy keeps an idle connection open before timing out. +idle_conn_timeout_seconds = 90 + +# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request. +send_user_header = false + +#################################### Analytics ########################### +[analytics] +# Server reporting, sends usage counters to stats.grafana.org every 24 hours. +# No ip addresses are being tracked, only simple counters to track +# running instances, dashboard and error counts. It is very helpful to us. +# Change this option to false to disable reporting. +reporting_enabled = false + +# The name of the distributor of the Grafana instance. Ex hosted-grafana, grafana-labs +reporting_distributor = grafana-labs + +# Set to false to disable all checks to https://grafana.com +# for new versions (grafana itself and plugins), check is used +# in some UI views to notify that grafana or plugin update exists +# This option does not cause any auto updates, nor send any information +# only a GET request to https://grafana.com to get latest versions +check_for_updates = false + +# Google Analytics universal tracking code, only enabled if you specify an id here +google_analytics_ua_id = + +# Google Tag Manager ID, only enabled if you specify an id here +google_tag_manager_id = + +#################################### Security ############################ +[security] +# disable creation of admin user on first start of grafana +disable_initial_admin_creation = false + +# default admin user, created on startup +admin_user = admin + +# default admin password, can be changed before first start of grafana, or in profile settings +admin_password = admin + +# used for signing +secret_key = SW2YcwTIb9zpOOhoPsMm + +# disable gravatar profile images +disable_gravatar = false + +# data source proxy whitelist (ip_or_domain:port separated by spaces) +data_source_proxy_whitelist = + +# disable protection against brute force login attempts +disable_brute_force_login_protection = false + +# set to true if you host Grafana behind HTTPS. default is false. +cookie_secure = false + +# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled" +cookie_samesite = lax + +# set to true if you want to allow browsers to render Grafana in a ,