From d812c2a4326eb8967f46338e99d4401b13b210d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Tue, 1 Oct 2024 07:41:26 +0200 Subject: [PATCH 01/15] PMM-13315 Remove comment about SA max length. --- managed/services/grafana/client.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go index 707a5df63e..ede6725249 100644 --- a/managed/services/grafana/client.go +++ b/managed/services/grafana/client.go @@ -677,8 +677,6 @@ func (c *Client) createServiceAccount(ctx context.Context, role role, nodeName s return 0, errors.New("you cannot create service account with empty role") } - // Max length of service account name is 190 chars (default limit in Grafana). In reality it is 187. - // Some tests could fail if you pass a name longer than 187 chars. serviceAccountName := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName) b, err := json.Marshal(serviceAccount{Name: serviceAccountName, Role: role.String(), Force: reregister}) if err != nil { From 36dcfd070ca8091446aee965bc4195e3bbf0879a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Tue, 1 Oct 2024 07:47:00 +0200 Subject: [PATCH 02/15] PMM-13315 Long postfix for tests. --- api-tests/server/auth_test.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/api-tests/server/auth_test.go b/api-tests/server/auth_test.go index a51d60b3dd..7ece320c4a 100644 --- a/api-tests/server/auth_test.go +++ b/api-tests/server/auth_test.go @@ -17,6 +17,8 @@ package server import ( "bytes" + "crypto/rand" + "encoding/base64" "encoding/json" "fmt" "io" @@ -357,12 +359,23 @@ func setRole(t *testing.T, userID int, role string) { require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to set role for user, response: %s", b) } +func generateRandomString(length int) (string, error) { + buffer := make([]byte, length) + _, err := rand.Read(buffer) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(buffer)[:length], nil +} + func TestServiceAccountPermissions(t *testing.T) { // service account role options: viewer, editor, admin // service token role options: editor, admin // basic auth format is skipped, endpoint /auth/serviceaccount (to get info about currently used token in request) requires Bearer authorization // service_token:token format could be used in pmm-agent and pmm-admin (its transformed into Bearer authorization) - nodeName := "test-node" + postfix, err := generateRandomString(256) + require.NoError(t, err) + nodeName := fmt.Sprintf("test-node-%s", postfix) viewerNodeName := fmt.Sprintf("%s-viewer", nodeName) viewerAccountID := createServiceAccountWithRole(t, "Viewer", viewerNodeName) From 2b58b350806d68597ca8aa8d05e8b313b9fee9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Tue, 1 Oct 2024 11:52:35 +0200 Subject: [PATCH 03/15] trigger From f600dc88329b6f3897f1b8b07e6197df84449a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Tue, 1 Oct 2024 13:22:54 +0200 Subject: [PATCH 04/15] Revert "PMM-13315 Remove comment about SA max length." This reverts commit d812c2a4326eb8967f46338e99d4401b13b210d6. --- managed/services/grafana/client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go index ede6725249..707a5df63e 100644 --- a/managed/services/grafana/client.go +++ b/managed/services/grafana/client.go @@ -677,6 +677,8 @@ func (c *Client) createServiceAccount(ctx context.Context, role role, nodeName s return 0, errors.New("you cannot create service account with empty role") } + // Max length of service account name is 190 chars (default limit in Grafana). In reality it is 187. + // Some tests could fail if you pass a name longer than 187 chars. serviceAccountName := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName) b, err := json.Marshal(serviceAccount{Name: serviceAccountName, Role: role.String(), Force: reregister}) if err != nil { From f0c93c5147ef67d5703a5ada10e687913715b8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Wed, 2 Oct 2024 10:05:15 +0200 Subject: [PATCH 05/15] PMM-13315 Long SA names hashing. --- api-tests/server/auth_test.go | 13 +++++++++++-- managed/services/grafana/client.go | 26 +++++++++++++++++-------- managed/services/grafana/client_test.go | 11 +++++++++++ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/api-tests/server/auth_test.go b/api-tests/server/auth_test.go index 7ece320c4a..948e7d2131 100644 --- a/api-tests/server/auth_test.go +++ b/api-tests/server/auth_test.go @@ -17,6 +17,7 @@ package server import ( "bytes" + "crypto/md5" "crypto/rand" "encoding/base64" "encoding/json" @@ -533,7 +534,7 @@ func createServiceAccountWithRole(t *testing.T, role, nodeName string) int { name := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName) data, err := json.Marshal(map[string]string{ - "name": name, + "name": sanitizeSAName(name), "role": role, }) require.NoError(t, err) @@ -595,7 +596,7 @@ func createServiceToken(t *testing.T, serviceAccountID int, nodeName string) (in name := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName) data, err := json.Marshal(map[string]string{ - "name": name, + "name": sanitizeSAName(name), }) require.NoError(t, err) @@ -630,3 +631,11 @@ func deleteServiceToken(t *testing.T, serviceAccountID, serviceTokenID int) { require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to delete service token, status code: %d, response: %s", resp.StatusCode, b) } + +func sanitizeSAName(name string) string { + if len(name) <= 190 { + return name + } + + return fmt.Sprintf("%s%x", name[:158], md5.Sum([]byte(name[158:]))) +} diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go index 707a5df63e..cc989cb943 100644 --- a/managed/services/grafana/client.go +++ b/managed/services/grafana/client.go @@ -19,6 +19,7 @@ package grafana import ( "bytes" "context" + "crypto/md5" "encoding/json" "fmt" "io" @@ -350,7 +351,7 @@ type serviceAccountSearch struct { func (c *Client) getServiceAccountIDFromName(ctx context.Context, nodeName string, authHeaders http.Header) (int, error) { var res serviceAccountSearch - serviceAccountName := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName) + serviceAccountName := sanitizeSAName(fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName)) if err := c.do(ctx, http.MethodGet, "/api/serviceaccounts/search", fmt.Sprintf("query=%s", serviceAccountName), authHeaders, nil, &res); err != nil { return 0, err } @@ -383,7 +384,7 @@ func (c *Client) getNotPMMAgentTokenCountForServiceAccount(ctx context.Context, count := 0 for _, token := range tokens { serviceTokenName := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName) - if !strings.HasPrefix(token.Name, serviceTokenName) { + if !strings.HasPrefix(token.Name, sanitizeSAName(serviceTokenName)) { count++ } } @@ -672,15 +673,24 @@ type serviceToken struct { Role string `json:"role"` } +// Max length of service account name is 190 chars (default limit in Grafana Postgres DB). +// Sanitizing, ensure its length by hashing postfix when length is exceeded. +// MD5 is used because it has fixed length 32 chars. +func sanitizeSAName(name string) string { + if len(name) <= 190 { + return name + } + + return fmt.Sprintf("%s%x", name[:158], md5.Sum([]byte(name[158:]))) +} + func (c *Client) createServiceAccount(ctx context.Context, role role, nodeName string, reregister bool, authHeaders http.Header) (int, error) { if role == none { return 0, errors.New("you cannot create service account with empty role") } - // Max length of service account name is 190 chars (default limit in Grafana). In reality it is 187. - // Some tests could fail if you pass a name longer than 187 chars. serviceAccountName := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName) - b, err := json.Marshal(serviceAccount{Name: serviceAccountName, Role: role.String(), Force: reregister}) + b, err := json.Marshal(serviceAccount{Name: sanitizeSAName(serviceAccountName), Role: role.String(), Force: reregister}) if err != nil { return 0, errors.WithStack(err) } @@ -714,7 +724,7 @@ func (c *Client) createServiceToken(ctx context.Context, serviceAccountID int, n } } - b, err := json.Marshal(serviceToken{Name: serviceTokenName, Role: admin.String()}) + b, err := json.Marshal(serviceToken{Name: sanitizeSAName(serviceTokenName), Role: admin.String()}) if err != nil { return 0, "", errors.WithStack(err) } @@ -737,7 +747,7 @@ func (c *Client) serviceTokenExists(ctx context.Context, serviceAccountID int, n serviceTokenName := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName) for _, token := range tokens { - if !strings.HasPrefix(token.Name, serviceTokenName) { + if !strings.HasPrefix(token.Name, sanitizeSAName(serviceTokenName)) { continue } @@ -755,7 +765,7 @@ func (c *Client) deletePMMAgentServiceToken(ctx context.Context, serviceAccountI serviceTokenName := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName) for _, token := range tokens { - if strings.HasPrefix(token.Name, serviceTokenName) { + if strings.HasPrefix(token.Name, sanitizeSAName(serviceTokenName)) { if err := c.do(ctx, "DELETE", fmt.Sprintf("/api/serviceaccounts/%d/tokens/%d", serviceAccountID, token.ID), "", authHeaders, nil, nil); err != nil { return err } diff --git a/managed/services/grafana/client_test.go b/managed/services/grafana/client_test.go index 2b315a7275..5f382b225b 100644 --- a/managed/services/grafana/client_test.go +++ b/managed/services/grafana/client_test.go @@ -234,3 +234,14 @@ func TestClient(t *testing.T) { require.NoError(t, err) }) } + +func Test_sanitizeNodeName(t *testing.T) { + // max possible length without hashing + len190 := "verylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongn" + require.Equal(t, len190, sanitizeSAName(len190)) + + // too long length - postfix hashed + len200 := fmt.Sprintf("%s1234567890", len190) + len200sanitized := sanitizeSAName(len200) + require.Equal(t, fmt.Sprintf("%s%s", len200[:158], len200sanitized[158:]), len200sanitized) +} From 15e11f93971afa3f9cfc3c6f564fcc7724d2ce61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Wed, 2 Oct 2024 10:12:24 +0200 Subject: [PATCH 06/15] PMM-13315 Lint. --- api-tests/server/auth_test.go | 4 ++-- managed/services/grafana/client.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api-tests/server/auth_test.go b/api-tests/server/auth_test.go index 948e7d2131..4b9308c826 100644 --- a/api-tests/server/auth_test.go +++ b/api-tests/server/auth_test.go @@ -17,7 +17,7 @@ package server import ( "bytes" - "crypto/md5" + "crypto/md5" //nolint:gosec "crypto/rand" "encoding/base64" "encoding/json" @@ -637,5 +637,5 @@ func sanitizeSAName(name string) string { return name } - return fmt.Sprintf("%s%x", name[:158], md5.Sum([]byte(name[158:]))) + return fmt.Sprintf("%s%x", name[:158], md5.Sum([]byte(name[158:]))) //nolint:gosec } diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go index cc989cb943..d0cf2e8413 100644 --- a/managed/services/grafana/client.go +++ b/managed/services/grafana/client.go @@ -19,7 +19,7 @@ package grafana import ( "bytes" "context" - "crypto/md5" + "crypto/md5" //nolint:gosec "encoding/json" "fmt" "io" @@ -681,7 +681,7 @@ func sanitizeSAName(name string) string { return name } - return fmt.Sprintf("%s%x", name[:158], md5.Sum([]byte(name[158:]))) + return fmt.Sprintf("%s%x", name[:158], md5.Sum([]byte(name[158:]))) //nolint:gosec } func (c *Client) createServiceAccount(ctx context.Context, role role, nodeName string, reregister bool, authHeaders http.Header) (int, error) { From 1a70691ed883d0f6b8de81d1a9c129265298ee77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Wed, 2 Oct 2024 11:34:29 +0200 Subject: [PATCH 07/15] PMM-11315 Changes, refactor. --- api-tests/server/auth_test.go | 22 ++++++----------- managed/services/grafana/client.go | 7 +++--- managed/services/grafana/client_test.go | 18 +++++++++----- managed/utils/strings/generate.go | 32 +++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 managed/utils/strings/generate.go diff --git a/api-tests/server/auth_test.go b/api-tests/server/auth_test.go index 4b9308c826..8d8dcad511 100644 --- a/api-tests/server/auth_test.go +++ b/api-tests/server/auth_test.go @@ -18,8 +18,6 @@ package server import ( "bytes" "crypto/md5" //nolint:gosec - "crypto/rand" - "encoding/base64" "encoding/json" "fmt" "io" @@ -38,6 +36,7 @@ import ( pmmapitests "github.com/percona/pmm/api-tests" serverClient "github.com/percona/pmm/api/server/v1/json/client" server "github.com/percona/pmm/api/server/v1/json/client/server_service" + stringsgen "github.com/percona/pmm/managed/utils/strings" ) const ( @@ -360,23 +359,13 @@ func setRole(t *testing.T, userID int, role string) { require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to set role for user, response: %s", b) } -func generateRandomString(length int) (string, error) { - buffer := make([]byte, length) - _, err := rand.Read(buffer) - if err != nil { - return "", err - } - return base64.URLEncoding.EncodeToString(buffer)[:length], nil -} - func TestServiceAccountPermissions(t *testing.T) { // service account role options: viewer, editor, admin // service token role options: editor, admin // basic auth format is skipped, endpoint /auth/serviceaccount (to get info about currently used token in request) requires Bearer authorization // service_token:token format could be used in pmm-agent and pmm-admin (its transformed into Bearer authorization) - postfix, err := generateRandomString(256) + nodeName, err := stringsgen.GenerateRandomString(256) require.NoError(t, err) - nodeName := fmt.Sprintf("test-node-%s", postfix) viewerNodeName := fmt.Sprintf("%s-viewer", nodeName) viewerAccountID := createServiceAccountWithRole(t, "Viewer", viewerNodeName) @@ -633,9 +622,12 @@ func deleteServiceToken(t *testing.T, serviceAccountID, serviceTokenID int) { } func sanitizeSAName(name string) string { - if len(name) <= 190 { + fmt.Println(name) + if len(name) <= 185 { return name } - return fmt.Sprintf("%s%x", name[:158], md5.Sum([]byte(name[158:]))) //nolint:gosec + res := fmt.Sprintf("%s%x", name[:153], md5.Sum([]byte(name[153:]))) //nolint:gosec + fmt.Println(res) + return res } diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go index d0cf2e8413..6a6f65a9a0 100644 --- a/managed/services/grafana/client.go +++ b/managed/services/grafana/client.go @@ -673,15 +673,15 @@ type serviceToken struct { Role string `json:"role"` } -// Max length of service account name is 190 chars (default limit in Grafana Postgres DB). +// Max length of service account name is 185 chars (limit in Grafana Postgres DB for 190 chars). // Sanitizing, ensure its length by hashing postfix when length is exceeded. // MD5 is used because it has fixed length 32 chars. func sanitizeSAName(name string) string { - if len(name) <= 190 { + if len(name) <= 185 { return name } - return fmt.Sprintf("%s%x", name[:158], md5.Sum([]byte(name[158:]))) //nolint:gosec + return fmt.Sprintf("%s%x", name[:153], md5.Sum([]byte(name[153:]))) //nolint:gosec } func (c *Client) createServiceAccount(ctx context.Context, role role, nodeName string, reregister bool, authHeaders http.Header) (int, error) { @@ -690,6 +690,7 @@ func (c *Client) createServiceAccount(ctx context.Context, role role, nodeName s } serviceAccountName := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName) + fmt.Println(sanitizeSAName(serviceAccountName)) b, err := json.Marshal(serviceAccount{Name: sanitizeSAName(serviceAccountName), Role: role.String(), Force: reregister}) if err != nil { return 0, errors.WithStack(err) diff --git a/managed/services/grafana/client_test.go b/managed/services/grafana/client_test.go index 5f382b225b..0ee38595fa 100644 --- a/managed/services/grafana/client_test.go +++ b/managed/services/grafana/client_test.go @@ -23,6 +23,7 @@ import ( "testing" "time" + stringsgen "github.com/percona/pmm/managed/utils/strings" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -144,8 +145,11 @@ func TestClient(t *testing.T) { }) t.Run(fmt.Sprintf("Service token auth %s", role.String()), func(t *testing.T) { - nodeName := fmt.Sprintf("test-node-%s", role) + name, err := stringsgen.GenerateRandomString(256) + require.NoError(t, err) + nodeName := fmt.Sprintf("%s-%s", name, role) serviceAccountID, err := c.createServiceAccount(ctx, role, nodeName, true, authHeaders) + fmt.Println(serviceAccountID) require.NoError(t, err) defer func() { err := c.deleteServiceAccount(ctx, serviceAccountID, authHeaders) @@ -235,13 +239,15 @@ func TestClient(t *testing.T) { }) } -func Test_sanitizeNodeName(t *testing.T) { +func Test_sanitizeSAName(t *testing.T) { // max possible length without hashing - len190 := "verylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongnodenameverylongn" - require.Equal(t, len190, sanitizeSAName(len190)) + len185, err := stringsgen.GenerateRandomString(185) + require.NoError(t, err) + require.Equal(t, len185, sanitizeSAName(len185)) // too long length - postfix hashed - len200 := fmt.Sprintf("%s1234567890", len190) + len200, err := stringsgen.GenerateRandomString(200) + require.NoError(t, err) len200sanitized := sanitizeSAName(len200) - require.Equal(t, fmt.Sprintf("%s%s", len200[:158], len200sanitized[158:]), len200sanitized) + require.Equal(t, fmt.Sprintf("%s%s", len200[:153], len200sanitized[153:]), len200sanitized) } diff --git a/managed/utils/strings/generate.go b/managed/utils/strings/generate.go new file mode 100644 index 0000000000..81a62f0f9b --- /dev/null +++ b/managed/utils/strings/generate.go @@ -0,0 +1,32 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package stringsgen provides functions to generate random strings. +package stringsgen + +import ( + "crypto/rand" + "encoding/base64" +) + +// GenerateRandomString returns random string with given length. +func GenerateRandomString(length int) (string, error) { + buffer := make([]byte, length) + _, err := rand.Read(buffer) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(buffer)[:length], nil +} From 278417a01d6401d34bbc7abdc833bde15b888d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Wed, 2 Oct 2024 11:38:57 +0200 Subject: [PATCH 08/15] PMM-13315 Format. --- managed/services/grafana/client_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/managed/services/grafana/client_test.go b/managed/services/grafana/client_test.go index 0ee38595fa..e78a152a72 100644 --- a/managed/services/grafana/client_test.go +++ b/managed/services/grafana/client_test.go @@ -23,10 +23,11 @@ import ( "testing" "time" - stringsgen "github.com/percona/pmm/managed/utils/strings" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + stringsgen "github.com/percona/pmm/managed/utils/strings" ) func TestClient(t *testing.T) { From eac0660399f7e5379b6400cd998b5329054309b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Wed, 2 Oct 2024 11:48:03 +0200 Subject: [PATCH 09/15] PMM-13315 Remove forgotten prints. --- api-tests/server/auth_test.go | 1 - managed/services/grafana/client.go | 1 - managed/services/grafana/client_test.go | 1 - 3 files changed, 3 deletions(-) diff --git a/api-tests/server/auth_test.go b/api-tests/server/auth_test.go index 8d8dcad511..12cf3484a1 100644 --- a/api-tests/server/auth_test.go +++ b/api-tests/server/auth_test.go @@ -622,7 +622,6 @@ func deleteServiceToken(t *testing.T, serviceAccountID, serviceTokenID int) { } func sanitizeSAName(name string) string { - fmt.Println(name) if len(name) <= 185 { return name } diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go index 6a6f65a9a0..652a19fd8b 100644 --- a/managed/services/grafana/client.go +++ b/managed/services/grafana/client.go @@ -690,7 +690,6 @@ func (c *Client) createServiceAccount(ctx context.Context, role role, nodeName s } serviceAccountName := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName) - fmt.Println(sanitizeSAName(serviceAccountName)) b, err := json.Marshal(serviceAccount{Name: sanitizeSAName(serviceAccountName), Role: role.String(), Force: reregister}) if err != nil { return 0, errors.WithStack(err) diff --git a/managed/services/grafana/client_test.go b/managed/services/grafana/client_test.go index e78a152a72..d1e0eef535 100644 --- a/managed/services/grafana/client_test.go +++ b/managed/services/grafana/client_test.go @@ -150,7 +150,6 @@ func TestClient(t *testing.T) { require.NoError(t, err) nodeName := fmt.Sprintf("%s-%s", name, role) serviceAccountID, err := c.createServiceAccount(ctx, role, nodeName, true, authHeaders) - fmt.Println(serviceAccountID) require.NoError(t, err) defer func() { err := c.deleteServiceAccount(ctx, serviceAccountID, authHeaders) From ac7ea22a31860eec783922f64e3b985018206b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Wed, 2 Oct 2024 11:52:03 +0200 Subject: [PATCH 10/15] PMM-13315 Remove another print. --- api-tests/server/auth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-tests/server/auth_test.go b/api-tests/server/auth_test.go index 12cf3484a1..878873dcfa 100644 --- a/api-tests/server/auth_test.go +++ b/api-tests/server/auth_test.go @@ -627,6 +627,6 @@ func sanitizeSAName(name string) string { } res := fmt.Sprintf("%s%x", name[:153], md5.Sum([]byte(name[153:]))) //nolint:gosec - fmt.Println(res) + return res } From 5c45652a7a5ac65b2b01e680f0b78bd0ebedb3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Thu, 3 Oct 2024 12:15:44 +0200 Subject: [PATCH 11/15] PMM-13315 Fix problem with big orgIDs. --- api-tests/server/auth_test.go | 6 ++---- managed/services/grafana/client.go | 9 ++++++--- managed/services/grafana/client_test.go | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/api-tests/server/auth_test.go b/api-tests/server/auth_test.go index 878873dcfa..ee475f747c 100644 --- a/api-tests/server/auth_test.go +++ b/api-tests/server/auth_test.go @@ -622,11 +622,9 @@ func deleteServiceToken(t *testing.T, serviceAccountID, serviceTokenID int) { } func sanitizeSAName(name string) string { - if len(name) <= 185 { + if len(name) <= 180 { return name } - res := fmt.Sprintf("%s%x", name[:153], md5.Sum([]byte(name[153:]))) //nolint:gosec - - return res + return fmt.Sprintf("%s%x", name[:148], md5.Sum([]byte(name[148:]))) //nolint:gosec } diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go index 652a19fd8b..3961d3db38 100644 --- a/managed/services/grafana/client.go +++ b/managed/services/grafana/client.go @@ -673,15 +673,18 @@ type serviceToken struct { Role string `json:"role"` } -// Max length of service account name is 185 chars (limit in Grafana Postgres DB for 190 chars). +// Max length of service account name is 190 chars (limit in Grafana Postgres DB). +// However, prefix added by grafana is counted too. Prefix is sa-{orgID}-. +// Bare minimum is 5 chars reserved (orgID is <10, like sa-1-) and could be more depends +// on orgID number. Let's reserve 10 chars. It will cover almost one million orgIDs. // Sanitizing, ensure its length by hashing postfix when length is exceeded. // MD5 is used because it has fixed length 32 chars. func sanitizeSAName(name string) string { - if len(name) <= 185 { + if len(name) <= 180 { return name } - return fmt.Sprintf("%s%x", name[:153], md5.Sum([]byte(name[153:]))) //nolint:gosec + return fmt.Sprintf("%s%x", name[:148], md5.Sum([]byte(name[148:]))) //nolint:gosec } func (c *Client) createServiceAccount(ctx context.Context, role role, nodeName string, reregister bool, authHeaders http.Header) (int, error) { diff --git a/managed/services/grafana/client_test.go b/managed/services/grafana/client_test.go index d1e0eef535..b6aa33ec7f 100644 --- a/managed/services/grafana/client_test.go +++ b/managed/services/grafana/client_test.go @@ -241,13 +241,13 @@ func TestClient(t *testing.T) { func Test_sanitizeSAName(t *testing.T) { // max possible length without hashing - len185, err := stringsgen.GenerateRandomString(185) + len180, err := stringsgen.GenerateRandomString(180) require.NoError(t, err) - require.Equal(t, len185, sanitizeSAName(len185)) + require.Equal(t, len180, sanitizeSAName(len180)) // too long length - postfix hashed len200, err := stringsgen.GenerateRandomString(200) require.NoError(t, err) len200sanitized := sanitizeSAName(len200) - require.Equal(t, fmt.Sprintf("%s%s", len200[:153], len200sanitized[153:]), len200sanitized) + require.Equal(t, fmt.Sprintf("%s%s", len200[:148], len200sanitized[148:]), len200sanitized) } From c646f675eec7ec7b202810360eb6311e537ebae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Tue, 8 Oct 2024 16:12:42 +0200 Subject: [PATCH 12/15] PMM-13315 Move SanitizeSAName to global utils. --- api-tests/server/auth_test.go | 18 ++++-------- managed/services/grafana/client.go | 30 ++++++-------------- managed/services/grafana/client_test.go | 15 +--------- utils/grafana/serviceaccounts.go | 21 ++++++++++++++ utils/grafana/serviceaccounts_test.go | 23 +++++++++++++++ {managed/utils => utils}/strings/generate.go | 0 6 files changed, 58 insertions(+), 49 deletions(-) create mode 100644 utils/grafana/serviceaccounts.go create mode 100644 utils/grafana/serviceaccounts_test.go rename {managed/utils => utils}/strings/generate.go (100%) diff --git a/api-tests/server/auth_test.go b/api-tests/server/auth_test.go index ee475f747c..bfafe21ef0 100644 --- a/api-tests/server/auth_test.go +++ b/api-tests/server/auth_test.go @@ -16,8 +16,7 @@ package server import ( - "bytes" - "crypto/md5" //nolint:gosec + "bytes" //nolint:gosec "encoding/json" "fmt" "io" @@ -36,7 +35,8 @@ import ( pmmapitests "github.com/percona/pmm/api-tests" serverClient "github.com/percona/pmm/api/server/v1/json/client" server "github.com/percona/pmm/api/server/v1/json/client/server_service" - stringsgen "github.com/percona/pmm/managed/utils/strings" + "github.com/percona/pmm/utils/grafana" + stringsgen "github.com/percona/pmm/utils/strings" ) const ( @@ -523,7 +523,7 @@ func createServiceAccountWithRole(t *testing.T, role, nodeName string) int { name := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName) data, err := json.Marshal(map[string]string{ - "name": sanitizeSAName(name), + "name": grafana.SanitizeSAName(name), "role": role, }) require.NoError(t, err) @@ -585,7 +585,7 @@ func createServiceToken(t *testing.T, serviceAccountID int, nodeName string) (in name := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName) data, err := json.Marshal(map[string]string{ - "name": sanitizeSAName(name), + "name": grafana.SanitizeSAName(name), }) require.NoError(t, err) @@ -620,11 +620,3 @@ func deleteServiceToken(t *testing.T, serviceAccountID, serviceTokenID int) { require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to delete service token, status code: %d, response: %s", resp.StatusCode, b) } - -func sanitizeSAName(name string) string { - if len(name) <= 180 { - return name - } - - return fmt.Sprintf("%s%x", name[:148], md5.Sum([]byte(name[148:]))) //nolint:gosec -} diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go index 3961d3db38..96607d3048 100644 --- a/managed/services/grafana/client.go +++ b/managed/services/grafana/client.go @@ -18,8 +18,7 @@ package grafana import ( "bytes" - "context" - "crypto/md5" //nolint:gosec + "context" //nolint:gosec "encoding/json" "fmt" "io" @@ -41,6 +40,7 @@ import ( "github.com/percona/pmm/managed/services" "github.com/percona/pmm/managed/utils/auth" "github.com/percona/pmm/managed/utils/irt" + "github.com/percona/pmm/utils/grafana" ) // ErrFailedToGetToken means it failed to get user's token. Most likely due to the fact user is not logged in using Percona Account. @@ -351,7 +351,7 @@ type serviceAccountSearch struct { func (c *Client) getServiceAccountIDFromName(ctx context.Context, nodeName string, authHeaders http.Header) (int, error) { var res serviceAccountSearch - serviceAccountName := sanitizeSAName(fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName)) + serviceAccountName := grafana.SanitizeSAName(fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName)) if err := c.do(ctx, http.MethodGet, "/api/serviceaccounts/search", fmt.Sprintf("query=%s", serviceAccountName), authHeaders, nil, &res); err != nil { return 0, err } @@ -384,7 +384,7 @@ func (c *Client) getNotPMMAgentTokenCountForServiceAccount(ctx context.Context, count := 0 for _, token := range tokens { serviceTokenName := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName) - if !strings.HasPrefix(token.Name, sanitizeSAName(serviceTokenName)) { + if !strings.HasPrefix(token.Name, grafana.SanitizeSAName(serviceTokenName)) { count++ } } @@ -673,27 +673,13 @@ type serviceToken struct { Role string `json:"role"` } -// Max length of service account name is 190 chars (limit in Grafana Postgres DB). -// However, prefix added by grafana is counted too. Prefix is sa-{orgID}-. -// Bare minimum is 5 chars reserved (orgID is <10, like sa-1-) and could be more depends -// on orgID number. Let's reserve 10 chars. It will cover almost one million orgIDs. -// Sanitizing, ensure its length by hashing postfix when length is exceeded. -// MD5 is used because it has fixed length 32 chars. -func sanitizeSAName(name string) string { - if len(name) <= 180 { - return name - } - - return fmt.Sprintf("%s%x", name[:148], md5.Sum([]byte(name[148:]))) //nolint:gosec -} - func (c *Client) createServiceAccount(ctx context.Context, role role, nodeName string, reregister bool, authHeaders http.Header) (int, error) { if role == none { return 0, errors.New("you cannot create service account with empty role") } serviceAccountName := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName) - b, err := json.Marshal(serviceAccount{Name: sanitizeSAName(serviceAccountName), Role: role.String(), Force: reregister}) + b, err := json.Marshal(serviceAccount{Name: grafana.SanitizeSAName(serviceAccountName), Role: role.String(), Force: reregister}) if err != nil { return 0, errors.WithStack(err) } @@ -727,7 +713,7 @@ func (c *Client) createServiceToken(ctx context.Context, serviceAccountID int, n } } - b, err := json.Marshal(serviceToken{Name: sanitizeSAName(serviceTokenName), Role: admin.String()}) + b, err := json.Marshal(serviceToken{Name: grafana.SanitizeSAName(serviceTokenName), Role: admin.String()}) if err != nil { return 0, "", errors.WithStack(err) } @@ -750,7 +736,7 @@ func (c *Client) serviceTokenExists(ctx context.Context, serviceAccountID int, n serviceTokenName := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName) for _, token := range tokens { - if !strings.HasPrefix(token.Name, sanitizeSAName(serviceTokenName)) { + if !strings.HasPrefix(token.Name, grafana.SanitizeSAName(serviceTokenName)) { continue } @@ -768,7 +754,7 @@ func (c *Client) deletePMMAgentServiceToken(ctx context.Context, serviceAccountI serviceTokenName := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName) for _, token := range tokens { - if strings.HasPrefix(token.Name, sanitizeSAName(serviceTokenName)) { + if strings.HasPrefix(token.Name, grafana.SanitizeSAName(serviceTokenName)) { if err := c.do(ctx, "DELETE", fmt.Sprintf("/api/serviceaccounts/%d/tokens/%d", serviceAccountID, token.ID), "", authHeaders, nil, nil); err != nil { return err } diff --git a/managed/services/grafana/client_test.go b/managed/services/grafana/client_test.go index b6aa33ec7f..50d28ed1a5 100644 --- a/managed/services/grafana/client_test.go +++ b/managed/services/grafana/client_test.go @@ -27,7 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - stringsgen "github.com/percona/pmm/managed/utils/strings" + stringsgen "github.com/percona/pmm/utils/strings" ) func TestClient(t *testing.T) { @@ -238,16 +238,3 @@ func TestClient(t *testing.T) { require.NoError(t, err) }) } - -func Test_sanitizeSAName(t *testing.T) { - // max possible length without hashing - len180, err := stringsgen.GenerateRandomString(180) - require.NoError(t, err) - require.Equal(t, len180, sanitizeSAName(len180)) - - // too long length - postfix hashed - len200, err := stringsgen.GenerateRandomString(200) - require.NoError(t, err) - len200sanitized := sanitizeSAName(len200) - require.Equal(t, fmt.Sprintf("%s%s", len200[:148], len200sanitized[148:]), len200sanitized) -} diff --git a/utils/grafana/serviceaccounts.go b/utils/grafana/serviceaccounts.go new file mode 100644 index 0000000000..2f2bc46482 --- /dev/null +++ b/utils/grafana/serviceaccounts.go @@ -0,0 +1,21 @@ +package grafana + +import ( + "crypto/md5" + "fmt" +) + +// SanitizeSAName is used for sanitize name and it's length for service accounts. +// Max length of service account name is 190 chars (limit in Grafana Postgres DB). +// However, prefix added by grafana is counted too. Prefix is sa-{orgID}-. +// Bare minimum is 5 chars reserved (orgID is <10, like sa-1-) and could be more depends +// on orgID number. Let's reserve 10 chars. It will cover almost one million orgIDs. +// Sanitizing, ensure its length by hashing postfix when length is exceeded. +// MD5 is used because it has fixed length 32 chars. +func SanitizeSAName(name string) string { + if len(name) <= 180 { + return name + } + + return fmt.Sprintf("%s%x", name[:148], md5.Sum([]byte(name[148:]))) //nolint:gosec +} diff --git a/utils/grafana/serviceaccounts_test.go b/utils/grafana/serviceaccounts_test.go new file mode 100644 index 0000000000..c1b3727164 --- /dev/null +++ b/utils/grafana/serviceaccounts_test.go @@ -0,0 +1,23 @@ +package grafana + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + stringsgen "github.com/percona/pmm/utils/strings" +) + +func Test_sanitizeSAName(t *testing.T) { + // max possible length without hashing + len180, err := stringsgen.GenerateRandomString(180) + require.NoError(t, err) + require.Equal(t, len180, SanitizeSAName(len180)) + + // too long length - postfix hashed + len200, err := stringsgen.GenerateRandomString(200) + require.NoError(t, err) + len200sanitized := SanitizeSAName(len200) + require.Equal(t, fmt.Sprintf("%s%s", len200[:148], len200sanitized[148:]), len200sanitized) +} diff --git a/managed/utils/strings/generate.go b/utils/strings/generate.go similarity index 100% rename from managed/utils/strings/generate.go rename to utils/strings/generate.go From 59059319a72596a80c95c487d35eb14b42328634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Tue, 8 Oct 2024 16:35:19 +0200 Subject: [PATCH 13/15] PMM-13315 Headers. --- utils/grafana/serviceaccounts.go | 16 ++++++++++++++++ utils/grafana/serviceaccounts_test.go | 14 ++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/utils/grafana/serviceaccounts.go b/utils/grafana/serviceaccounts.go index 2f2bc46482..2d0cdedbf3 100644 --- a/utils/grafana/serviceaccounts.go +++ b/utils/grafana/serviceaccounts.go @@ -1,3 +1,19 @@ +// Copyright (C) 2024 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package grafana provides util functions related to grafana functionality. package grafana import ( diff --git a/utils/grafana/serviceaccounts_test.go b/utils/grafana/serviceaccounts_test.go index c1b3727164..e3a5871c2a 100644 --- a/utils/grafana/serviceaccounts_test.go +++ b/utils/grafana/serviceaccounts_test.go @@ -1,3 +1,17 @@ +// Copyright (C) 2024 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . package grafana import ( From 0dc0c12ac3ef5034540bace265450b28b0192264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Tue, 8 Oct 2024 16:46:46 +0200 Subject: [PATCH 14/15] PMM-13315 Licence year. --- utils/grafana/serviceaccounts.go | 2 +- utils/grafana/serviceaccounts_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/grafana/serviceaccounts.go b/utils/grafana/serviceaccounts.go index 2d0cdedbf3..65556dedd5 100644 --- a/utils/grafana/serviceaccounts.go +++ b/utils/grafana/serviceaccounts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Percona LLC +// Copyright (C) 2023 Percona LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by diff --git a/utils/grafana/serviceaccounts_test.go b/utils/grafana/serviceaccounts_test.go index e3a5871c2a..ddea7f833e 100644 --- a/utils/grafana/serviceaccounts_test.go +++ b/utils/grafana/serviceaccounts_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Percona LLC +// Copyright (C) 2023 Percona LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by From af2bc914c11d0644aaabae135de57d69903a3d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Tue, 8 Oct 2024 16:58:07 +0200 Subject: [PATCH 15/15] PMM-13315 Lint. --- api-tests/server/auth_test.go | 2 +- managed/services/grafana/client.go | 2 +- utils/grafana/serviceaccounts.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api-tests/server/auth_test.go b/api-tests/server/auth_test.go index bfafe21ef0..7529f89538 100644 --- a/api-tests/server/auth_test.go +++ b/api-tests/server/auth_test.go @@ -16,7 +16,7 @@ package server import ( - "bytes" //nolint:gosec + "bytes" "encoding/json" "fmt" "io" diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go index 96607d3048..791c24a7ab 100644 --- a/managed/services/grafana/client.go +++ b/managed/services/grafana/client.go @@ -18,7 +18,7 @@ package grafana import ( "bytes" - "context" //nolint:gosec + "context" "encoding/json" "fmt" "io" diff --git a/utils/grafana/serviceaccounts.go b/utils/grafana/serviceaccounts.go index 65556dedd5..4bcbe0570e 100644 --- a/utils/grafana/serviceaccounts.go +++ b/utils/grafana/serviceaccounts.go @@ -17,7 +17,7 @@ package grafana import ( - "crypto/md5" + "crypto/md5" //nolint:gosec "fmt" )