Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion managed/cmd/pmm-encryption-rotation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ func main() {
}

statusCode, err := encryptionService.RotateEncryptionKey(sqlDB, "pmm-managed")
sqlDB.Close() //nolint:errcheck
closeErr := sqlDB.Close()
if closeErr != nil {
logrus.Error(closeErr)
}
if err != nil {
logrus.Error(err)
os.Exit(statusCode)
Expand Down
30 changes: 20 additions & 10 deletions managed/cmd/pmm-managed/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,13 @@ const (
cleanOlderThan = 30 * time.Minute

defaultContextTimeout = 10 * time.Second
pProfProfileDuration = 30 * time.Second
pProfTraceDuration = 10 * time.Second
// When ReadHeaderTimeout is not configured, a malicious client can open
// a connection and send headers extremely slowly (or not at all),
// keeping the connection open indefinitely. This "Slowloris" attack can eventually
// exhaust the server's connection pool, leading to a Denial of Service (DoS).
readHeaderTimeout = 10 * time.Second
pProfProfileDuration = 30 * time.Second
pProfTraceDuration = 10 * time.Second

clickhouseMaxIdleConns = 5
clickhouseMaxOpenConns = 10
Expand Down Expand Up @@ -439,10 +444,11 @@ func runHTTP1Server(ctx context.Context, deps *http1ServerDeps) {
mux.Handle("/v1/users/current", deps.currentUserHandler)
mux.Handle("/", proxyMux)

server := &http.Server{ //nolint:gosec
Addr: http1Addr,
ErrorLog: log.New(os.Stderr, "runJSONServer: ", 0),
Handler: mux,
server := &http.Server{
Addr: http1Addr,
ErrorLog: log.New(os.Stderr, "runJSONServer: ", 0),
Handler: mux,
ReadHeaderTimeout: readHeaderTimeout,
}
go func() {
err := server.ListenAndServe()
Expand Down Expand Up @@ -499,13 +505,17 @@ func runDebugServer(ctx context.Context) {
l.Fatal(err)
}
http.HandleFunc("/debug", func(rw http.ResponseWriter, _ *http.Request) {
rw.Write(buf.Bytes()) //nolint:errcheck
_, wErr := rw.Write(buf.Bytes())
if wErr != nil {
l.Errorf("Failed to write debug page: %v", wErr)
}
})
l.Infof("Starting server on http://%s/debug\nRegistered handlers:\n\t%s", debugAddr, strings.Join(handlers, "\n\t"))

server := &http.Server{ //nolint:gosec
Addr: debugAddr,
ErrorLog: log.New(os.Stderr, "runDebugServer: ", 0),
server := &http.Server{
Addr: debugAddr,
ErrorLog: log.New(os.Stderr, "runDebugServer: ", 0),
ReadHeaderTimeout: readHeaderTimeout,
}
go func() {
err := server.ListenAndServe()
Expand Down
6 changes: 2 additions & 4 deletions managed/models/artifact_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,10 +330,8 @@ func DeleteArtifact(q *reform.Querier, id string) error {

// MetadataRemoveFirstN removes first N records from artifact metadata list.
func (s *Artifact) MetadataRemoveFirstN(q *reform.Querier, n uint32) error {
if n > uint32(len(s.MetadataList)) {
n = uint32(len(s.MetadataList))
}
s.MetadataList = s.MetadataList[n:]
idx := min(int(n), len(s.MetadataList))
s.MetadataList = s.MetadataList[idx:]
err := q.Update(s)
if err != nil {
return fmt.Errorf("failed to remove artifact metadata records: %w", err)
Expand Down
138 changes: 132 additions & 6 deletions managed/models/artifact_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func TestArtifacts(t *testing.T) {
assert.Equal(t, createParams.DataModel, a.DataModel)
assert.Equal(t, createParams.Status, a.Status)
assert.Equal(t, createParams.Folder, a.Folder)
assert.Equal(t, models.OnDemandArtifactType, a.Type)
assert.Less(t, time.Now().UTC().Unix()-a.CreatedAt.Unix(), int64(5))

updateParams := models.UpdateArtifactParams{
Expand All @@ -135,9 +136,108 @@ func TestArtifacts(t *testing.T) {
assert.Equal(t, *updateParams.ServiceID, a.ServiceID)
assert.Equal(t, updateParams.IsShardedCluster, a.IsShardedCluster)
assert.Less(t, time.Now().UTC().Unix()-a.UpdatedAt.Unix(), int64(5))

t.Run("scheduled type", func(t *testing.T) {
createParams.Name = "scheduled_backup"
createParams.ScheduleID = "some_schedule"
sa, err := models.CreateArtifact(q, createParams)
require.NoError(t, err)
assert.Equal(t, models.ScheduledArtifactType, sa.Type)
assert.Equal(t, "some_schedule", sa.ScheduleID)
})
})

t.Run("unique name", func(t *testing.T) {
tx, err := db.Begin()
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, tx.Rollback())
})

q := tx.Querier
prepareLocationsAndService(q)

params := models.CreateArtifactParams{
Name: "unique_name",
Vendor: "MySQL",
LocationID: locationID1,
ServiceID: serviceID1,
DataModel: models.PhysicalDataModel,
Status: models.PendingBackupStatus,
Mode: models.Snapshot,
}

_, err = models.CreateArtifact(q, params)
require.NoError(t, err)

_, err = models.CreateArtifact(q, params)
require.Error(t, err)
assert.Contains(t, err.Error(), `Artifact with name "unique_name" already exists.`)
})

t.Run("find by ID and name", func(t *testing.T) {
tx, err := db.Begin()
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, tx.Rollback())
})

q := tx.Querier
prepareLocationsAndService(q)

a, err := models.CreateArtifact(q, models.CreateArtifactParams{
Name: "find_me",
Vendor: "MySQL",
LocationID: locationID1,
ServiceID: serviceID1,
DataModel: models.PhysicalDataModel,
Status: models.PendingBackupStatus,
Mode: models.Snapshot,
})
require.NoError(t, err)

// Find by ID
found, err := models.FindArtifactByID(q, a.ID)
require.NoError(t, err)
assert.Equal(t, a.Name, found.Name)

_, err = models.FindArtifactByID(q, "non-existent")
require.ErrorIs(t, err, models.ErrNotFound)

// Find by Name
found, err = models.FindArtifactByName(q, "find_me")
require.NoError(t, err)
assert.Equal(t, a.ID, found.ID)

_, err = models.FindArtifactByName(q, "unknown")
assert.ErrorIs(t, err, models.ErrNotFound)
})

t.Run("list", func(t *testing.T) {
t.Run("find by IDs", func(t *testing.T) {
tx, err := db.Begin()
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, tx.Rollback())
})

q := tx.Querier
prepareLocationsAndService(q)

a1, _ := models.CreateArtifact(q, models.CreateArtifactParams{Name: "a1", Vendor: "V", LocationID: locationID1, ServiceID: serviceID1, DataModel: models.PhysicalDataModel, Status: models.PendingBackupStatus, Mode: models.Snapshot})
a2, _ := models.CreateArtifact(q, models.CreateArtifactParams{Name: "a2", Vendor: "V", LocationID: locationID1, ServiceID: serviceID1, DataModel: models.PhysicalDataModel, Status: models.PendingBackupStatus, Mode: models.Snapshot})
Comment on lines +226 to +227

res, err := models.FindArtifactsByIDs(q, []string{a1.ID, a2.ID, "unknown"})
require.NoError(t, err)
assert.Len(t, res, 2)
assert.Contains(t, res, a1.ID)
assert.Contains(t, res, a2.ID)

emptyRes, err := models.FindArtifactsByIDs(q, []string{})
require.NoError(t, err)
assert.Empty(t, emptyRes)
})

t.Run("list and filters", func(t *testing.T) {
tx, err := db.Begin()
require.NoError(t, err)
t.Cleanup(func() {
Expand Down Expand Up @@ -206,13 +306,23 @@ func TestArtifacts(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, []*models.Artifact{a3}, actual2)

actual3, err := models.FindArtifacts(q, models.ArtifactFilters{})
// Check filters by status and service.
actual3, err := models.FindArtifacts(q, models.ArtifactFilters{Status: models.PausedBackupStatus, ServiceID: serviceID2})
require.NoError(t, err)
require.Len(t, actual3, 3)
assert.Len(t, actual3, 1)
assert.Equal(t, a2.ID, actual3[0].ID)

for _, a := range actual3 {
assert.Contains(t, []models.Artifact{*a1, *a2, *a3}, *a)
}
// Check non-existent location error.
_, err = models.FindArtifacts(q, models.ArtifactFilters{LocationID: "invalid_loc"})
require.Error(t, err)

// Check sorting (latest first).
all, err := models.FindArtifacts(q, models.ArtifactFilters{})
require.NoError(t, err)
require.Len(t, all, 3)
assert.Equal(t, a3.ID, all[0].ID)
assert.Equal(t, a2.ID, all[1].ID)
assert.Equal(t, a1.ID, all[2].ID)
})

t.Run("remove", func(t *testing.T) {
Expand Down Expand Up @@ -446,3 +556,19 @@ func TestArtifactValidation(t *testing.T) {
})
}
}

func TestIsArtifactFinalStatus(t *testing.T) {
testCases := []struct {
status models.BackupStatus
expected bool
}{
{models.SuccessBackupStatus, true},
{models.ErrorBackupStatus, true},
{models.FailedToDeleteBackupStatus, true},
{models.PendingBackupStatus, false},
{models.PausedBackupStatus, false},
}
for _, tc := range testCases {
assert.Equal(t, tc.expected, models.IsArtifactFinalStatus(tc.status), "Status: %s", tc.status)
}
}
17 changes: 16 additions & 1 deletion managed/models/encryption_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ func awsOptionsHandler(val any, handler func(string) (string, error)) (any, erro
return nil, err
}

// The gosec G117 warning is triggered when sensitive fields (like ClientSecret, AWSSecretKey, or TLSKey)
// are part of a structure being marshaled to JSON.
// In the context of this file, these operations are intentional.
res, err := json.Marshal(o)
if err != nil {
return nil, err
Expand Down Expand Up @@ -205,7 +208,10 @@ func azureOptionsHandler(val any, handler func(string) (string, error)) (any, er
return nil, err
}

res, err := json.Marshal(o)
// The gosec G117 warning is triggered when sensitive fields (like ClientSecret, AWSSecretKey, or TLSKey)
// are part of a structure being marshaled to JSON.
// In the context of this file, these operations are intentional.
res, err := json.Marshal(o) //nolint:gosec
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -244,6 +250,9 @@ func mongoDBOptionsHandler(val any, handler func(string) (string, error)) (any,
return nil, err
}

// The gosec G117 warning is triggered when sensitive fields (like ClientSecret, AWSSecretKey, or TLSKey)
// are part of a structure being marshaled to JSON.
// In the context of this file, these operations are intentional.
res, err := json.Marshal(o)
if err != nil {
return nil, err
Expand Down Expand Up @@ -283,6 +292,9 @@ func mySQLOptionsHandler(val any, handler func(string) (string, error)) (any, er
return nil, err
}

// The gosec G117 warning is triggered when sensitive fields (like ClientSecret, AWSSecretKey, or TLSKey)
// are part of a structure being marshaled to JSON.
// In the context of this file, these operations are intentional.
res, err := json.Marshal(o)
if err != nil {
return nil, err
Expand Down Expand Up @@ -322,6 +334,9 @@ func postgreSQLOptionsHandler(val any, handler func(string) (string, error)) (an
return nil, err
}

// The gosec G117 warning is triggered when sensitive fields (like ClientSecret, AWSSecretKey, or TLSKey)
// are part of a structure being marshaled to JSON.
// In the context of this file, these operations are intentional.
res, err := json.Marshal(o)
if err != nil {
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions managed/models/role_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package models
import (
"errors"
"fmt"
"math"
"strings"

"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -51,6 +52,9 @@ func AssignRoles(tx *reform.TX, userID int, roleIDs []int) error {
return err
}

if roleID < 0 || roleID > math.MaxUint32 {
return fmt.Errorf("role ID %d is out of range for uint32", roleID)
}
var userRole UserRoles
userRole.UserID = userID
userRole.RoleID = uint32(roleID)
Expand Down
2 changes: 1 addition & 1 deletion managed/services/agents/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (s *ActionsService) StartMySQLExplainAction(
return err
}

if res.PlaceholdersCount != uint32(len(placeholders)) {
if res.PlaceholdersCount != uint32(len(placeholders)) { //nolint:gosec // placeholders count is not expected to exceed uint32
return status.Error(codes.FailedPrecondition, "placeholders count is not correct")
}
q = res.ExplainFingerprint
Expand Down
5 changes: 4 additions & 1 deletion managed/services/agents/service_info_broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package agents
import (
"context"
"fmt"
"math"
"time"

"github.com/AlekSi/pointer"
Expand Down Expand Up @@ -213,7 +214,9 @@ func (c *ServiceInfoBroker) GetInfoFromService(ctx context.Context, q *reform.Qu
}
}
agent.PostgreSQLOptions.PGSMVersion = sInfo.PgsmVersion
agent.PostgreSQLOptions.DatabaseCount = int32(databaseCount - excludedDatabaseCount)

dbCount := min(max(databaseCount-excludedDatabaseCount, 0), math.MaxInt32)
agent.PostgreSQLOptions.DatabaseCount = int32(dbCount)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
G115: integer overflow conversion int -> int32 (gosec)


l.Debugf("Updating PostgreSQL options, database count: %d.", agent.PostgreSQLOptions.DatabaseCount)
err = q.Update(new(models.EncryptAgent(*agent)))
Expand Down
Loading
Loading