feat: establish secure update foundation
Container-Image veröffentlichen / container (push) v1.4.0 wurde manuell veröffentlicht und verifiziert
Container-Image veröffentlichen / container (push) v1.4.0 wurde manuell veröffentlicht und verifiziert
This commit is contained in:
1 parent
f02ec9977c
commit
bb60c06c0e
28 files changed
+1403
-280
No files matched your search
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/buildinfo"
|
||||
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/config"
|
||||
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/model"
|
||||
)
|
||||
@@ -128,6 +129,13 @@ func (s *Store) migrate(ctx context.Context) error {
|
||||
300, mustJSON([]string{config.DefaultIPv4Primary, config.DefaultIPv4Fallback}), mustJSON([]string{config.DefaultIPv6Primary, config.DefaultIPv6Fallback}), now()); err != nil {
|
||||
return err
|
||||
}
|
||||
var appliedMigrations int
|
||||
if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM schema_migrations WHERE version BETWEEN 1 AND ?`, buildinfo.CurrentSchemaVersion).Scan(&appliedMigrations); err != nil {
|
||||
return err
|
||||
}
|
||||
if appliedMigrations != buildinfo.CurrentSchemaVersion {
|
||||
return fmt.Errorf("database migration chain is incomplete: have %d of %d", appliedMigrations, buildinfo.CurrentSchemaVersion)
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
@@ -861,6 +869,11 @@ func (s *Store) SetDesiredSoftwareVersion(ctx context.Context, version string) e
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) ClearDesiredSoftwareVersion(ctx context.Context, expected string) error {
|
||||
_, err := s.db.ExecContext(ctx, `UPDATE software_update_settings SET desired_version = '', updated_at = ? WHERE id = 1 AND desired_version = ?`, now(), expected)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) UpdateRunState(ctx context.Context, ipv4, ipv6, status string) error {
|
||||
_, err := s.db.ExecContext(ctx, `UPDATE settings SET
|
||||
last_ipv4 = COALESCE(NULLIF(?, ''), last_ipv4),
|
||||
|
||||
@@ -2,16 +2,60 @@ package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/buildinfo"
|
||||
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/model"
|
||||
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/secure"
|
||||
)
|
||||
|
||||
func TestMigrateAppliesEveryIntermediateSchemaFromV1(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
databasePath := filepath.Join(t.TempDir(), "legacy.db")
|
||||
legacy, err := sql.Open("sqlite3", databasePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := legacy.ExecContext(ctx, `CREATE TABLE schema_migrations (version INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL)`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := legacy.ExecContext(ctx, schemaV1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := legacy.ExecContext(ctx, `INSERT INTO schema_migrations(version, applied_at) VALUES(1, 1)`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := legacy.ExecContext(ctx, `INSERT INTO admins(id, password_hash, created_at, updated_at) VALUES(1, 'legacy-password-hash', 1, 1)`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := legacy.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
database, err := Open(ctx, databasePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = database.Close() })
|
||||
var applied int
|
||||
if err := database.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM schema_migrations WHERE version BETWEEN 1 AND ?`, buildinfo.CurrentSchemaVersion).Scan(&applied); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if applied != buildinfo.CurrentSchemaVersion {
|
||||
t.Fatalf("direct migration applied %d of %d schemas", applied, buildinfo.CurrentSchemaVersion)
|
||||
}
|
||||
admin, _, err := database.GetUserByUsername(ctx, "admin")
|
||||
if err != nil || admin.Role != "admin" || !admin.Active {
|
||||
t.Fatalf("legacy administrator was not retained: %#v, %v", admin, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenHardensDataDirectoryAndDatabasePermissions(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
@@ -280,6 +324,20 @@ func TestSoftwareUpdateStateRetainsKnownReleaseOnCheckFailure(t *testing.T) {
|
||||
if settings.RegistryUsername != "registry-user" || string(settings.TokenCiphertext) != "encrypted-token" || settings.LatestVersion != "v1.2.3" || settings.DesiredVersion != "v1.2.3" || settings.LastError != "temporarily unavailable" {
|
||||
t.Fatalf("software update state was not persisted safely: %#v", settings)
|
||||
}
|
||||
if err := database.ClearDesiredSoftwareVersion(ctx, "v9.9.9"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
settings, err = database.GetSoftwareUpdateSettings(ctx)
|
||||
if err != nil || settings.DesiredVersion != "v1.2.3" {
|
||||
t.Fatalf("unrelated update cleared the pending target: %#v, %v", settings, err)
|
||||
}
|
||||
if err := database.ClearDesiredSoftwareVersion(ctx, "v1.2.3"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
settings, err = database.GetSoftwareUpdateSettings(ctx)
|
||||
if err != nil || settings.DesiredVersion != "" {
|
||||
t.Fatalf("matching completed target remained pending: %#v, %v", settings, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPiHoleIntegrationsPersistEncryptedCredentialsAndStatus(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user