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