2016-12-20 18:35:22 +00:00
|
|
|
package mysql
|
|
|
|
|
|
2017-01-18 15:40:51 +00:00
|
|
|
import (
|
2025-09-22 17:12:16 +00:00
|
|
|
"database/sql/driver"
|
2024-04-16 12:52:03 +00:00
|
|
|
"errors"
|
2017-01-18 15:40:51 +00:00
|
|
|
"fmt"
|
2025-09-22 17:12:16 +00:00
|
|
|
"io"
|
2024-04-16 12:52:03 +00:00
|
|
|
"net/http"
|
2025-09-22 17:12:16 +00:00
|
|
|
"os"
|
2021-11-24 17:16:42 +00:00
|
|
|
"strconv"
|
2025-09-22 17:12:16 +00:00
|
|
|
"syscall"
|
2017-01-18 15:40:51 +00:00
|
|
|
|
|
|
|
|
"github.com/VividCortex/mysqlerr"
|
2021-11-15 14:11:38 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
2025-02-18 21:28:54 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/datastore/mysql/common_mysql"
|
2022-01-18 01:52:09 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
2017-01-18 15:40:51 +00:00
|
|
|
"github.com/go-sql-driver/mysql"
|
|
|
|
|
)
|
2016-12-20 18:35:22 +00:00
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
func notFound(kind string) *common_mysql.NotFoundError {
|
|
|
|
|
return &common_mysql.NotFoundError{
|
2016-12-20 18:35:22 +00:00
|
|
|
ResourceType: kind,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type existsError struct {
|
2021-04-05 20:28:43 +00:00
|
|
|
Identifier interface{}
|
2016-12-20 18:35:22 +00:00
|
|
|
ResourceType string
|
2023-02-14 15:12:18 +00:00
|
|
|
TeamID *uint
|
Add UUID to Fleet errors and clean up error msgs (#10411)
#8129
Apart from fixing the issue in #8129, this change also introduces UUIDs
to Fleet errors. To be able to match a returned error from the API to a
error in the Fleet logs. See
https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for
more context.
Samples with the changes in this PR:
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d ''
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "Expected JSON Body"
}
],
"uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0"
}
```
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd'
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "json decoder error"
}
],
"uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d"
}
```
```
curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams"
{
"message": "Authentication required",
"errors": [
{
"name": "base",
"reason": "Authentication required"
}
],
"uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Authorization header required",
"errors": [
{
"name": "base",
"reason": "Authorization header required"
}
],
"uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Permission Denied",
"uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06"
}
```
- [X] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [X] Documented any API changes (docs/Using-Fleet/REST-API.md or
docs/Contributing/API-for-contributors.md)
- ~[ ] Documented any permissions changes~
- ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)~
- ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for
new osquery data ingestion features.~
- [X] Added/updated tests
- [X] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [X] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- ~[ ] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
2023-03-13 16:44:06 +00:00
|
|
|
|
|
|
|
|
fleet.ErrorWithUUID
|
2016-12-20 18:35:22 +00:00
|
|
|
}
|
|
|
|
|
|
2021-04-05 20:28:43 +00:00
|
|
|
func alreadyExists(kind string, identifier interface{}) error {
|
2021-11-24 17:16:42 +00:00
|
|
|
if s, ok := identifier.(string); ok {
|
|
|
|
|
identifier = strconv.Quote(s)
|
|
|
|
|
}
|
2016-12-20 18:35:22 +00:00
|
|
|
return &existsError{
|
2021-04-05 20:28:43 +00:00
|
|
|
Identifier: identifier,
|
2016-12-20 18:35:22 +00:00
|
|
|
ResourceType: kind,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-14 15:12:18 +00:00
|
|
|
func (e *existsError) WithTeamID(teamID uint) error {
|
|
|
|
|
e.TeamID = &teamID
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-20 18:35:22 +00:00
|
|
|
func (e *existsError) Error() string {
|
2024-10-29 19:17:51 +00:00
|
|
|
msg := e.ResourceType
|
2023-08-07 14:43:39 +00:00
|
|
|
if e.Identifier != nil {
|
|
|
|
|
msg += fmt.Sprintf(" %v", e.Identifier)
|
|
|
|
|
}
|
|
|
|
|
msg += " already exists"
|
2023-02-14 15:12:18 +00:00
|
|
|
if e.TeamID != nil {
|
|
|
|
|
msg += fmt.Sprintf(" with TeamID %d", *e.TeamID)
|
|
|
|
|
}
|
|
|
|
|
return msg
|
2016-12-20 18:35:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *existsError) IsExists() bool {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2017-01-18 15:40:51 +00:00
|
|
|
|
2024-04-15 21:50:14 +00:00
|
|
|
func (e *existsError) Resource() string {
|
|
|
|
|
return e.ResourceType
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 21:18:42 +00:00
|
|
|
func IsDuplicate(err error) bool {
|
2021-11-15 14:11:38 +00:00
|
|
|
err = ctxerr.Cause(err)
|
2017-01-18 15:40:51 +00:00
|
|
|
if driverErr, ok := err.(*mysql.MySQLError); ok {
|
|
|
|
|
if driverErr.Number == mysqlerr.ER_DUP_ENTRY {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
2018-06-15 14:13:11 +00:00
|
|
|
|
|
|
|
|
type foreignKeyError struct {
|
|
|
|
|
Name string
|
|
|
|
|
ResourceType string
|
Add UUID to Fleet errors and clean up error msgs (#10411)
#8129
Apart from fixing the issue in #8129, this change also introduces UUIDs
to Fleet errors. To be able to match a returned error from the API to a
error in the Fleet logs. See
https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for
more context.
Samples with the changes in this PR:
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d ''
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "Expected JSON Body"
}
],
"uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0"
}
```
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd'
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "json decoder error"
}
],
"uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d"
}
```
```
curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams"
{
"message": "Authentication required",
"errors": [
{
"name": "base",
"reason": "Authentication required"
}
],
"uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Authorization header required",
"errors": [
{
"name": "base",
"reason": "Authorization header required"
}
],
"uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Permission Denied",
"uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06"
}
```
- [X] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [X] Documented any API changes (docs/Using-Fleet/REST-API.md or
docs/Contributing/API-for-contributors.md)
- ~[ ] Documented any permissions changes~
- ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)~
- ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for
new osquery data ingestion features.~
- [X] Added/updated tests
- [X] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [X] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- ~[ ] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
2023-03-13 16:44:06 +00:00
|
|
|
|
|
|
|
|
fleet.ErrorWithUUID
|
2018-06-15 14:13:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func foreignKey(kind string, name string) error {
|
|
|
|
|
return &foreignKeyError{
|
|
|
|
|
Name: name,
|
|
|
|
|
ResourceType: kind,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *foreignKeyError) Error() string {
|
|
|
|
|
return fmt.Sprintf("the operation violates a foreign key constraint on %s: %s", e.ResourceType, e.Name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *foreignKeyError) IsForeignKey() bool {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func isMySQLForeignKey(err error) bool {
|
2021-11-15 14:11:38 +00:00
|
|
|
err = ctxerr.Cause(err)
|
2018-06-15 14:13:11 +00:00
|
|
|
if driverErr, ok := err.(*mysql.MySQLError); ok {
|
|
|
|
|
if driverErr.Number == mysqlerr.ER_ROW_IS_REFERENCED_2 {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
2024-04-16 12:52:03 +00:00
|
|
|
|
|
|
|
|
// accessDeniedError is an error that implements StatusCode and Internal
|
|
|
|
|
type accessDeniedError struct {
|
|
|
|
|
Message string
|
|
|
|
|
InternalErr error
|
|
|
|
|
Code int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Error returns the error message.
|
|
|
|
|
func (e *accessDeniedError) Error() string {
|
|
|
|
|
return e.Message
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e accessDeniedError) Internal() string {
|
|
|
|
|
if e.InternalErr == nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
return e.InternalErr.Error()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *accessDeniedError) StatusCode() int {
|
|
|
|
|
if e.Code == 0 {
|
|
|
|
|
return http.StatusUnprocessableEntity
|
|
|
|
|
}
|
|
|
|
|
return e.Code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func isMySQLAccessDenied(err error) bool {
|
|
|
|
|
err = ctxerr.Cause(err)
|
|
|
|
|
var mySQLErr *mysql.MySQLError
|
|
|
|
|
if errors.As(
|
|
|
|
|
err, &mySQLErr,
|
|
|
|
|
) && (mySQLErr.Number == mysqlerr.ER_SPECIFIC_ACCESS_DENIED_ERROR || mySQLErr.Number == mysqlerr.ER_TABLEACCESS_DENIED_ERROR) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
2024-05-28 15:10:32 +00:00
|
|
|
|
2025-09-22 17:12:16 +00:00
|
|
|
// isBadConnection checks if the error is a connection-level error that
|
|
|
|
|
// justifies retrying the operation on a new connection.
|
|
|
|
|
func isBadConnection(err error) bool {
|
|
|
|
|
if err == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if errors.Is(err, driver.ErrBadConn) ||
|
|
|
|
|
errors.Is(err, mysql.ErrInvalidConn) ||
|
|
|
|
|
errors.Is(err, io.ErrUnexpectedEOF) ||
|
|
|
|
|
errors.Is(err, io.EOF) ||
|
|
|
|
|
errors.Is(err, syscall.ECONNREFUSED) ||
|
|
|
|
|
errors.Is(err, syscall.ECONNRESET) ||
|
|
|
|
|
errors.Is(err, syscall.ENETUNREACH) ||
|
|
|
|
|
errors.Is(err, syscall.ETIMEDOUT) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-09 14:59:24 +00:00
|
|
|
var mySQLErr *mysql.MySQLError
|
2025-09-22 17:12:16 +00:00
|
|
|
if errors.As(err, &mySQLErr) {
|
|
|
|
|
switch mySQLErr.Number {
|
|
|
|
|
case 1243, // ER_UNKNOWN_STMT_HANDLER
|
|
|
|
|
2006, // ER_SERVER_GONE_ERROR
|
|
|
|
|
1053: // ER_SERVER_SHUTDOWN
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for underlying network errors.
|
|
|
|
|
var se *os.SyscallError
|
|
|
|
|
if errors.As(err, &se) {
|
|
|
|
|
return errors.Is(se.Err, syscall.ECONNRESET) || errors.Is(se.Err, syscall.EPIPE)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
2024-08-09 14:59:24 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-28 15:10:32 +00:00
|
|
|
// ErrPartialResult indicates that a batch operation was completed,
|
|
|
|
|
// but some of the results are missing or incomplete.
|
|
|
|
|
var ErrPartialResult = errors.New("batch operation completed with partial results")
|