2025-02-13 20:32:19 +00:00
|
|
|
package testing_utils
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
2026-02-19 14:27:24 +00:00
|
|
|
"log/slog"
|
2025-02-13 20:32:19 +00:00
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
2026-01-26 22:02:12 +00:00
|
|
|
"path/filepath"
|
2025-02-13 20:32:19 +00:00
|
|
|
"runtime"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
|
2026-01-08 19:17:19 +00:00
|
|
|
common_mysql "github.com/fleetdm/fleet/v4/server/platform/mysql"
|
2025-02-13 20:32:19 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2025-02-18 21:28:54 +00:00
|
|
|
TestUsername = "root"
|
|
|
|
|
TestPassword = "toor"
|
|
|
|
|
TestReplicaDatabaseSuffix = "_replica"
|
2025-02-13 20:32:19 +00:00
|
|
|
)
|
|
|
|
|
|
2026-03-18 18:58:58 +00:00
|
|
|
var TestReplicaAddress = getTestReplicaAddress()
|
|
|
|
|
|
|
|
|
|
// getTestReplicaAddress returns the MySQL replica test server address from environment variable
|
|
|
|
|
// FLEET_MYSQL_REPLICA_TEST_PORT or defaults to localhost:3310
|
|
|
|
|
func getTestReplicaAddress() string {
|
|
|
|
|
if port := os.Getenv("FLEET_MYSQL_REPLICA_TEST_PORT"); port != "" {
|
|
|
|
|
return "localhost:" + port
|
|
|
|
|
}
|
|
|
|
|
return "localhost:3310"
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 15:58:47 +00:00
|
|
|
var TestAddress = getTestAddress()
|
2025-08-12 13:45:43 +00:00
|
|
|
|
|
|
|
|
// getTestAddress returns the MySQL test server address from environment variable
|
|
|
|
|
// FLEET_MYSQL_TEST_PORT or defaults to localhost:3307
|
|
|
|
|
func getTestAddress() string {
|
|
|
|
|
if port := os.Getenv("FLEET_MYSQL_TEST_PORT"); port != "" {
|
|
|
|
|
return "localhost:" + port
|
|
|
|
|
}
|
|
|
|
|
return "localhost:3307"
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
// TruncateTables truncates the specified tables, in order, using ds.writer.
|
|
|
|
|
// Note that the order is typically not important because FK checks are
|
|
|
|
|
// disabled while truncating. If no table is provided, all tables (except
|
|
|
|
|
// those that are seeded by the SQL schema file) are truncated.
|
2026-02-19 14:27:24 +00:00
|
|
|
func TruncateTables(t testing.TB, db *sqlx.DB, logger *slog.Logger, nonEmptyTables map[string]bool, tables ...string) {
|
2025-02-18 21:28:54 +00:00
|
|
|
// By setting DISABLE_TRUNCATE_TABLES a developer can troubleshoot tests
|
|
|
|
|
// by inspecting mysql tables.
|
|
|
|
|
if os.Getenv("DISABLE_TRUNCATE_TABLES") != "" {
|
|
|
|
|
return
|
2025-02-13 20:32:19 +00:00
|
|
|
}
|
|
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
ctx := context.Background()
|
2025-02-13 20:32:19 +00:00
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
require.NoError(t, common_mysql.WithTxx(ctx, db, func(tx sqlx.ExtContext) error {
|
|
|
|
|
var skipSeeded bool
|
2025-02-13 20:32:19 +00:00
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
if len(tables) == 0 {
|
|
|
|
|
skipSeeded = true
|
|
|
|
|
sql := `
|
|
|
|
|
SELECT
|
|
|
|
|
table_name
|
|
|
|
|
FROM
|
|
|
|
|
information_schema.tables
|
|
|
|
|
WHERE
|
|
|
|
|
table_schema = database() AND
|
|
|
|
|
table_type = 'BASE TABLE'
|
|
|
|
|
`
|
|
|
|
|
if err := sqlx.SelectContext(ctx, tx, &tables, sql); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2025-02-13 20:32:19 +00:00
|
|
|
}
|
|
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
if _, err := tx.ExecContext(ctx, `SET FOREIGN_KEY_CHECKS=0`); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
for _, tbl := range tables {
|
|
|
|
|
if nonEmptyTables[tbl] {
|
|
|
|
|
if skipSeeded {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("cannot truncate table %s, it contains seed data from schema.sql", tbl)
|
|
|
|
|
}
|
|
|
|
|
if _, err := tx.ExecContext(ctx, "TRUNCATE TABLE "+tbl); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if _, err := tx.ExecContext(ctx, `SET FOREIGN_KEY_CHECKS=1`); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}, logger))
|
2025-02-13 20:32:19 +00:00
|
|
|
}
|
|
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
// DatastoreTestOptions configures how the test datastore is created
|
|
|
|
|
// by CreateMySQLDSWithOptions.
|
|
|
|
|
type DatastoreTestOptions struct {
|
|
|
|
|
// DummyReplica indicates that a read replica test database should be created.
|
|
|
|
|
DummyReplica bool
|
2025-02-13 20:32:19 +00:00
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
// RunReplication is the function to call to execute the replication of all
|
|
|
|
|
// missing changes from the primary to the replica. The function is created
|
|
|
|
|
// and set automatically by CreateMySQLDSWithOptions. The test is in full
|
|
|
|
|
// control of when the replication is executed. Only applies to DummyReplica.
|
|
|
|
|
// Note that not all changes to data show up in the information_schema
|
|
|
|
|
// update_time timestamp, so to work around that limitation, explicit table
|
|
|
|
|
// names can be provided to force their replication.
|
|
|
|
|
RunReplication func(forceTables ...string)
|
2025-02-13 20:32:19 +00:00
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
// RealReplica indicates that the replica should be a real DB replica, with a dedicated connection.
|
|
|
|
|
RealReplica bool
|
Add SCEP endpoint for host identity. (#30589)
Fixes #30458
Contributor docs PR: https://github.com/fleetdm/fleet/pull/30651
# Checklist for submitter
- We will add changes file later.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] If database migrations are included, checked table schema to
confirm autoupdate
- For database migrations:
- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [x] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).
- [x] Added/updated automated tests
- Did not do manual QA since the SCEP client I have doesn't support ECC.
Will rely on next subtasks for manual QA.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Introduced Host Identity SCEP (Simple Certificate Enrollment Protocol)
support, enabling secure host identity certificate enrollment and
management.
* Added new API endpoints for Host Identity SCEP, including certificate
issuance and retrieval.
* Implemented MySQL-backed storage and management for host identity SCEP
certificates and serials.
* Added new database tables for storing host identity SCEP certificates
and serial numbers.
* Provided utilities for encoding certificates and keys, and handling
ECDSA public keys.
* **Bug Fixes**
* None.
* **Tests**
* Added comprehensive integration and unit tests for Host Identity SCEP
functionality, including certificate issuance, validation, and error
scenarios.
* **Chores**
* Updated test utilities to support unique test names and new SCEP
storage options.
* Extended mock datastore and interfaces for new host identity
certificate methods.
* **Documentation**
* Added comments and documentation for new SCEP-related interfaces,
methods, and database schema changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-11 14:44:07 +00:00
|
|
|
|
|
|
|
|
UniqueTestName string
|
2025-02-13 20:32:19 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 22:02:12 +00:00
|
|
|
// LoadDefaultSchema loads the default database schema for testing.
|
|
|
|
|
func LoadDefaultSchema(t testing.TB, testName string, opts *DatastoreTestOptions) {
|
|
|
|
|
_, thisFile, _, _ := runtime.Caller(0)
|
|
|
|
|
schemaPath := filepath.Join(filepath.Dir(thisFile), "../../../datastore/mysql/schema.sql")
|
|
|
|
|
LoadSchema(t, testName, opts, schemaPath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LoadSchema loads a database schema from the specified path for testing.
|
2025-02-18 21:28:54 +00:00
|
|
|
func LoadSchema(t testing.TB, testName string, opts *DatastoreTestOptions, schemaPath string) {
|
|
|
|
|
schema, err := os.ReadFile(schemaPath)
|
2025-02-13 20:32:19 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
t.FailNow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// execute the schema for the test db, and once more for the replica db if
|
|
|
|
|
// that option is set.
|
|
|
|
|
dbs := []string{testName}
|
|
|
|
|
if opts.DummyReplica {
|
2025-02-18 21:28:54 +00:00
|
|
|
dbs = append(dbs, testName+TestReplicaDatabaseSuffix)
|
2025-02-13 20:32:19 +00:00
|
|
|
}
|
|
|
|
|
for _, dbName := range dbs {
|
|
|
|
|
// Load schema from dumpfile
|
|
|
|
|
sqlCommands := fmt.Sprintf(
|
|
|
|
|
"DROP DATABASE IF EXISTS %s; CREATE DATABASE %s; USE %s; SET FOREIGN_KEY_CHECKS=0; %s;",
|
|
|
|
|
dbName, dbName, dbName, schema,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
cmd := exec.Command(
|
|
|
|
|
"docker", "compose", "exec", "-T", "mysql_test",
|
|
|
|
|
// Command run inside container
|
|
|
|
|
"mysql",
|
2025-02-18 21:28:54 +00:00
|
|
|
"-u"+TestUsername, "-p"+TestPassword,
|
2025-02-13 20:32:19 +00:00
|
|
|
)
|
|
|
|
|
cmd.Stdin = strings.NewReader(sqlCommands)
|
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
t.Error(string(out))
|
|
|
|
|
t.FailNow()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if opts.RealReplica {
|
|
|
|
|
// Load schema from dumpfile
|
|
|
|
|
sqlCommands := fmt.Sprintf(
|
|
|
|
|
"DROP DATABASE IF EXISTS %s; CREATE DATABASE %s; USE %s; SET FOREIGN_KEY_CHECKS=0; %s;",
|
|
|
|
|
testName, testName, testName, schema,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
cmd := exec.Command(
|
|
|
|
|
"docker", "compose", "exec", "-T", "mysql_replica_test",
|
|
|
|
|
// Command run inside container
|
|
|
|
|
"mysql",
|
2025-02-18 21:28:54 +00:00
|
|
|
"-u"+TestUsername, "-p"+TestPassword,
|
2025-02-13 20:32:19 +00:00
|
|
|
)
|
|
|
|
|
cmd.Stdin = strings.NewReader(sqlCommands)
|
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
t.Error(string(out))
|
|
|
|
|
t.FailNow()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 22:26:44 +00:00
|
|
|
func MysqlTestConfig(testName string) *common_mysql.MysqlConfig {
|
|
|
|
|
return &common_mysql.MysqlConfig{
|
2025-02-18 21:28:54 +00:00
|
|
|
Username: TestUsername,
|
|
|
|
|
Password: TestPassword,
|
|
|
|
|
Database: testName,
|
|
|
|
|
Address: TestAddress,
|
|
|
|
|
}
|
2025-02-13 20:32:19 +00:00
|
|
|
}
|
|
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
func ProcessOptions(t testing.TB, opts *DatastoreTestOptions) (string, *DatastoreTestOptions) {
|
2025-02-13 20:32:19 +00:00
|
|
|
if _, ok := os.LookupEnv("MYSQL_TEST"); !ok {
|
|
|
|
|
t.Skip("MySQL tests are disabled")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if opts == nil {
|
|
|
|
|
// so it is never nil in internal helper functions
|
|
|
|
|
opts = new(DatastoreTestOptions)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tt, ok := t.(*testing.T); ok && !opts.RealReplica {
|
|
|
|
|
tt.Parallel()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if opts.RealReplica {
|
|
|
|
|
if _, ok := os.LookupEnv("MYSQL_REPLICA_TEST"); !ok {
|
|
|
|
|
t.Skip("MySQL replica tests are disabled. Set env var MYSQL_REPLICA_TEST=1 to enable.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Add SCEP endpoint for host identity. (#30589)
Fixes #30458
Contributor docs PR: https://github.com/fleetdm/fleet/pull/30651
# Checklist for submitter
- We will add changes file later.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] If database migrations are included, checked table schema to
confirm autoupdate
- For database migrations:
- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [x] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).
- [x] Added/updated automated tests
- Did not do manual QA since the SCEP client I have doesn't support ECC.
Will rely on next subtasks for manual QA.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Introduced Host Identity SCEP (Simple Certificate Enrollment Protocol)
support, enabling secure host identity certificate enrollment and
management.
* Added new API endpoints for Host Identity SCEP, including certificate
issuance and retrieval.
* Implemented MySQL-backed storage and management for host identity SCEP
certificates and serials.
* Added new database tables for storing host identity SCEP certificates
and serial numbers.
* Provided utilities for encoding certificates and keys, and handling
ECDSA public keys.
* **Bug Fixes**
* None.
* **Tests**
* Added comprehensive integration and unit tests for Host Identity SCEP
functionality, including certificate issuance, validation, and error
scenarios.
* **Chores**
* Updated test utilities to support unique test names and new SCEP
storage options.
* Extended mock datastore and interfaces for new host identity
certificate methods.
* **Documentation**
* Added comments and documentation for new SCEP-related interfaces,
methods, and database schema changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-11 14:44:07 +00:00
|
|
|
var cleanTestName string
|
|
|
|
|
if opts.UniqueTestName != "" {
|
|
|
|
|
cleanTestName = opts.UniqueTestName
|
|
|
|
|
} else {
|
|
|
|
|
const numberOfStackFramesFromTest = 3
|
|
|
|
|
pc, _, _, ok := runtime.Caller(numberOfStackFramesFromTest)
|
|
|
|
|
details := runtime.FuncForPC(pc)
|
|
|
|
|
if !ok || details == nil {
|
|
|
|
|
t.FailNow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanTestName = strings.ReplaceAll(
|
|
|
|
|
strings.TrimPrefix(details.Name(), "github.com/fleetdm/fleet/v4/"), "/", "_",
|
|
|
|
|
)
|
2025-02-13 20:32:19 +00:00
|
|
|
}
|
|
|
|
|
|
2025-02-18 21:28:54 +00:00
|
|
|
cleanTestName = strings.ReplaceAll(cleanTestName, ".", "_")
|
|
|
|
|
if len(cleanTestName) > 60 {
|
2025-02-13 20:32:19 +00:00
|
|
|
// the later parts are more unique than the start, with the package names,
|
|
|
|
|
// so trim from the start.
|
2025-02-18 21:28:54 +00:00
|
|
|
cleanTestName = cleanTestName[len(cleanTestName)-60:]
|
2025-02-13 20:32:19 +00:00
|
|
|
}
|
2025-02-18 21:28:54 +00:00
|
|
|
return cleanTestName, opts
|
2025-02-13 20:32:19 +00:00
|
|
|
}
|