From dfbaacb6f3ec319a4af51636dbb022ac75040b92 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Fri, 6 Feb 2026 15:17:38 +1100 Subject: [PATCH] [ci skip] more codex 5.3 improvements --- README.md | 19 ++++-- internal/secrets/secrets_test.go | 27 ++++++++ internal/utils/certOperations.go | 36 +++++----- main.go | 24 ++++++- main_test.go | 112 +++++++++++++++++++++++++++++++ server/handler/encryptData.go | 91 ++++++++++++------------- server/handler/legacy_gate.go | 23 +++++++ server/handler/updateCleanup.go | 4 ++ server/handler/vcCleanup.go | 4 ++ server/handler/vmCreateEvent.go | 4 ++ server/handler/vmDeleteEvent.go | 4 ++ server/handler/vmModifyEvent.go | 4 ++ server/handler/vmMoveEvent.go | 4 ++ server/router/router.go | 2 +- src/vctp.default | 8 ++- src/vctp.yml | 6 +- 16 files changed, 297 insertions(+), 75 deletions(-) create mode 100644 internal/secrets/secrets_test.go create mode 100644 main_test.go create mode 100644 server/handler/legacy_gate.go diff --git a/README.md b/README.md index f36ca56..ac603c6 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,12 @@ vctp -settings /path/to/vctp.yml -db-cleanup ``` ## Database Configuration -By default the app uses SQLite and creates/opens `db.sqlite3`. You can opt into PostgreSQL -by updating the settings file: +By default the app uses SQLite and creates/opens `db.sqlite3`. -- `settings.database_driver`: `sqlite` (default) or `postgres` +PostgreSQL support is currently **experimental** and not a production target. To enable it, +set `VCTP_ENABLE_EXPERIMENTAL_POSTGRES=1` and update the settings file: + +- `settings.database_driver`: `sqlite` (default) or `postgres` (experimental) - `settings.database_url`: SQLite file path/DSN or PostgreSQL DSN Examples: @@ -104,14 +106,19 @@ HTTP/TLS: vCenter: - `settings.vcenter_username`: vCenter username -- `settings.vcenter_password`: vCenter password (encrypted at startup) +- `settings.vcenter_password`: vCenter password (auto-encrypted on startup if plaintext length > 2) - `settings.vcenter_insecure`: `true` to skip TLS verification -- `settings.vcenter_event_polling_seconds`: event polling interval (0 disables) -- `settings.vcenter_inventory_polling_seconds`: inventory polling interval (0 disables) +- `settings.vcenter_event_polling_seconds`: deprecated and ignored +- `settings.vcenter_inventory_polling_seconds`: deprecated and ignored - `settings.vcenter_inventory_snapshot_seconds`: hourly snapshot cadence (seconds) - `settings.vcenter_inventory_aggregate_seconds`: daily aggregation cadence (seconds) - `settings.vcenter_addresses`: list of vCenter SDK URLs to monitor +Credential encryption: +- `VCTP_ENCRYPTION_KEY`: optional environment variable used to derive the encryption key. + If unset, vCTP derives a host key from hardware/host identity. +- New encrypted values are written with `enc:v1:` prefix. + Snapshots: - `settings.hourly_snapshot_concurrency`: max concurrent vCenter snapshots (0 = unlimited) - `settings.hourly_snapshot_max_age_days`: retention for hourly tables diff --git a/internal/secrets/secrets_test.go b/internal/secrets/secrets_test.go new file mode 100644 index 0000000..688ed70 --- /dev/null +++ b/internal/secrets/secrets_test.go @@ -0,0 +1,27 @@ +package secrets + +import ( + "encoding/base64" + "io" + "log/slog" + "strings" + "testing" +) + +func testLogger() *slog.Logger { + return slog.New(slog.NewTextHandler(io.Discard, nil)) +} + +func TestDecryptRejectsShortCiphertext(t *testing.T) { + key := []byte("0123456789abcdef0123456789abcdef") + s := New(testLogger(), key) + + encoded := base64.StdEncoding.EncodeToString([]byte{1, 2, 3}) + _, err := s.Decrypt(encoded) + if err == nil { + t.Fatal("expected error for short ciphertext, got nil") + } + if !strings.Contains(err.Error(), "ciphertext is too short") { + t.Fatalf("unexpected error: %v", err) + } +} diff --git a/internal/utils/certOperations.go b/internal/utils/certOperations.go index 7455992..6ca1813 100644 --- a/internal/utils/certOperations.go +++ b/internal/utils/certOperations.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "fmt" "log" "math/big" "net" @@ -14,7 +15,7 @@ import ( "time" ) -func GenerateCerts(tlsCert string, tlsKey string) { +func GenerateCerts(tlsCert string, tlsKey string) error { // @see https://shaneutt.com/blog/golang-ca-and-signed-cert-go/ // @see https://golang.org/src/crypto/tls/generate_cert.go validFrom := "" @@ -24,7 +25,7 @@ func GenerateCerts(tlsCert string, tlsKey string) { // Get the hostname hostname, err := os.Hostname() if err != nil { - panic(err) + return fmt.Errorf("failed to lookup hostname: %w", err) } // Check that the directory exists @@ -33,13 +34,15 @@ func GenerateCerts(tlsCert string, tlsKey string) { _, err = os.Stat(relativePath) if os.IsNotExist(err) { log.Printf("Certificate path does not exist, creating %s before generating certificate\n", relativePath) - os.MkdirAll(relativePath, os.ModePerm) + if mkErr := os.MkdirAll(relativePath, os.ModePerm); mkErr != nil { + return fmt.Errorf("failed to create certificate directory %s: %w", relativePath, mkErr) + } } // Generate a private key priv, err := rsa.GenerateKey(rand.Reader, rsaBits) if err != nil { - log.Fatalf("Failed to generate private key: %v", err) + return fmt.Errorf("failed to generate private key: %w", err) } var notBefore time.Time @@ -48,7 +51,7 @@ func GenerateCerts(tlsCert string, tlsKey string) { } else { notBefore, err = time.Parse("Jan 2 15:04:05 2006", validFrom) if err != nil { - log.Fatalf("Failed to parse creation date: %v", err) + return fmt.Errorf("failed to parse creation date: %w", err) } } @@ -57,7 +60,7 @@ func GenerateCerts(tlsCert string, tlsKey string) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { - log.Fatalf("Failed to generate serial number: %v", err) + return fmt.Errorf("failed to generate serial number: %w", err) } template := x509.Certificate{ @@ -105,35 +108,38 @@ func GenerateCerts(tlsCert string, tlsKey string) { derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { - log.Fatalf("Failed to create certificate: %v", err) + return fmt.Errorf("failed to create certificate: %w", err) } certOut, err := os.Create(tlsCert) if err != nil { - log.Fatalf("Failed to open %s for writing: %v", tlsCert, err) + return fmt.Errorf("failed to open %s for writing: %w", tlsCert, err) } if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - log.Fatalf("Failed to write data to %s: %v", tlsCert, err) + _ = certOut.Close() + return fmt.Errorf("failed to write certificate data to %s: %w", tlsCert, err) } if err := certOut.Close(); err != nil { - log.Fatalf("Error closing %s: %v", tlsCert, err) + return fmt.Errorf("failed to close certificate file %s: %w", tlsCert, err) } log.Printf("wrote %s\n", tlsCert) keyOut, err := os.OpenFile(tlsKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { - log.Fatalf("Failed to open %s for writing: %v", tlsKey, err) - return + return fmt.Errorf("failed to open %s for writing: %w", tlsKey, err) } privBytes, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { - log.Fatalf("Unable to marshal private key: %v", err) + _ = keyOut.Close() + return fmt.Errorf("unable to marshal private key: %w", err) } if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { - log.Fatalf("Failed to write data to %s: %v", tlsKey, err) + _ = keyOut.Close() + return fmt.Errorf("failed to write private key data to %s: %w", tlsKey, err) } if err := keyOut.Close(); err != nil { - log.Fatalf("Error closing %s: %v", tlsKey, err) + return fmt.Errorf("failed to close private key file %s: %w", tlsKey, err) } log.Printf("wrote %s\n", tlsKey) + return nil } diff --git a/main.go b/main.go index 169ca6b..e5526a7 100644 --- a/main.go +++ b/main.go @@ -29,8 +29,6 @@ var ( bindDisableTls bool sha1ver string // sha1 revision used to build the program buildTime string // when the executable was built - cronFrequency time.Duration - cronInvFrequency time.Duration cronSnapshotFrequency time.Duration cronAggregateFrequency time.Duration ) @@ -66,6 +64,7 @@ func main() { s.Logger = logger logger.Info("vCTP starting", "build_time", buildTime, "sha1_version", sha1ver, "go_version", runtime.Version(), "settings_file", *settingsPath) + warnDeprecatedPollingSettings(logger, s.Values) // Configure database dbDriver := strings.TrimSpace(s.Values.Settings.DatabaseDriver) @@ -137,7 +136,10 @@ func main() { // Generate certificate if required if !(utils.FileExists(tlsCertFilename) && utils.FileExists(tlsKeyFilename)) { logger.Warn("Specified TLS certificate or private key do not exist", "certificate", tlsCertFilename, "tls-key", tlsKeyFilename) - utils.GenerateCerts(tlsCertFilename, tlsKeyFilename) + if err := utils.GenerateCerts(tlsCertFilename, tlsKeyFilename); err != nil { + logger.Error("failed to generate TLS cert/key", "error", err) + os.Exit(1) + } } // Load vcenter credentials from settings, decrypt if required. @@ -338,6 +340,22 @@ func alignStart(now time.Time, freq time.Duration) time.Time { return now.Add(freq) } +func warnDeprecatedPollingSettings(logger *slog.Logger, cfg *settings.SettingsYML) { + if cfg == nil { + return + } + if cfg.Settings.VcenterEventPollingSeconds > 0 { + logger.Warn("vcenter_event_polling_seconds is deprecated and ignored; snapshot lifecycle processing is used instead", + "value", cfg.Settings.VcenterEventPollingSeconds, + ) + } + if cfg.Settings.VcenterInventoryPollingSeconds > 0 { + logger.Warn("vcenter_inventory_polling_seconds is deprecated and ignored; hourly snapshot jobs are used instead", + "value", cfg.Settings.VcenterInventoryPollingSeconds, + ) + } +} + func durationFromSeconds(value int, fallback int) time.Duration { if value <= 0 { return time.Second * time.Duration(fallback) diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..4267dfb --- /dev/null +++ b/main_test.go @@ -0,0 +1,112 @@ +package main + +import ( + "io" + "log/slog" + "strings" + "testing" + "vctp/internal/secrets" +) + +func testLogger() *slog.Logger { + return slog.New(slog.NewTextHandler(io.Discard, nil)) +} + +func mustEncrypt(t *testing.T, s *secrets.Secrets, plain string) string { + t.Helper() + enc, err := s.Encrypt([]byte(plain)) + if err != nil { + t.Fatalf("encrypt failed: %v", err) + } + return enc +} + +func TestResolveVcenterPasswordPlaintextRewrite(t *testing.T) { + logger := testLogger() + key := []byte("0123456789abcdef0123456789abcdef") + cipher := secrets.New(logger, key) + + pass, rewritten, err := resolveVcenterPassword(logger, cipher, nil, "my-password") + if err != nil { + t.Fatalf("resolve failed: %v", err) + } + if string(pass) != "my-password" { + t.Fatalf("unexpected plaintext returned: %q", string(pass)) + } + if !strings.HasPrefix(rewritten, encryptedVcenterPasswordPrefix) { + t.Fatalf("expected rewritten prefixed credential, got: %q", rewritten) + } +} + +func TestResolveVcenterPasswordUnprefixedLegacyCiphertextRewrite(t *testing.T) { + logger := testLogger() + activeKey := []byte("0123456789abcdef0123456789abcdef") + legacyKey := []byte("abcdef0123456789abcdef0123456789") + activeCipher := secrets.New(logger, activeKey) + legacyCipher := secrets.New(logger, legacyKey) + + legacyCiphertext := mustEncrypt(t, legacyCipher, "legacy-secret") + + pass, rewritten, err := resolveVcenterPassword(logger, activeCipher, [][]byte{legacyKey}, legacyCiphertext) + if err != nil { + t.Fatalf("resolve failed: %v", err) + } + if string(pass) != "legacy-secret" { + t.Fatalf("unexpected plaintext returned: %q", string(pass)) + } + if !strings.HasPrefix(rewritten, encryptedVcenterPasswordPrefix) { + t.Fatalf("expected rewritten prefixed credential, got: %q", rewritten) + } + + decoded, err := activeCipher.Decrypt(strings.TrimPrefix(rewritten, encryptedVcenterPasswordPrefix)) + if err != nil { + t.Fatalf("rewritten ciphertext did not decrypt with active key: %v", err) + } + if string(decoded) != "legacy-secret" { + t.Fatalf("unexpected rewritten decrypt value: %q", string(decoded)) + } +} + +func TestResolveVcenterPasswordPrefixedLegacyCiphertextRewrite(t *testing.T) { + logger := testLogger() + activeKey := []byte("0123456789abcdef0123456789abcdef") + legacyKey := []byte("abcdef0123456789abcdef0123456789") + activeCipher := secrets.New(logger, activeKey) + legacyCipher := secrets.New(logger, legacyKey) + + legacyCiphertext := mustEncrypt(t, legacyCipher, "legacy-prefixed-secret") + raw := encryptedVcenterPasswordPrefix + legacyCiphertext + + pass, rewritten, err := resolveVcenterPassword(logger, activeCipher, [][]byte{legacyKey}, raw) + if err != nil { + t.Fatalf("resolve failed: %v", err) + } + if string(pass) != "legacy-prefixed-secret" { + t.Fatalf("unexpected plaintext returned: %q", string(pass)) + } + if !strings.HasPrefix(rewritten, encryptedVcenterPasswordPrefix) { + t.Fatalf("expected rewritten prefixed credential, got: %q", rewritten) + } + + decoded, err := activeCipher.Decrypt(strings.TrimPrefix(rewritten, encryptedVcenterPasswordPrefix)) + if err != nil { + t.Fatalf("rewritten ciphertext did not decrypt with active key: %v", err) + } + if string(decoded) != "legacy-prefixed-secret" { + t.Fatalf("unexpected rewritten decrypt value: %q", string(decoded)) + } +} + +func TestResolveVcenterPasswordShortPlaintextRejected(t *testing.T) { + logger := testLogger() + key := []byte("0123456789abcdef0123456789abcdef") + cipher := secrets.New(logger, key) + + _, _, err := resolveVcenterPassword(logger, cipher, nil, "ab") + if err == nil { + t.Fatal("expected short plaintext error, got nil") + } + if !strings.Contains(err.Error(), "too short") { + t.Fatalf("unexpected error: %v", err) + } +} diff --git a/server/handler/encryptData.go b/server/handler/encryptData.go index b2ab9b1..7b9b613 100644 --- a/server/handler/encryptData.go +++ b/server/handler/encryptData.go @@ -2,11 +2,18 @@ package handler import ( "encoding/json" - "fmt" - "io" "net/http" + "strings" ) +const encryptedValuePrefixV1 = "enc:v1:" + +type encryptRequest struct { + Plaintext string `json:"plaintext"` + Value string `json:"value"` + Message string `json:"message"` +} + // EncryptData encrypts a plaintext value and returns the ciphertext. // @Summary Encrypt data // @Description Encrypts a plaintext value and returns the ciphertext. @@ -15,57 +22,47 @@ import ( // @Produce json // @Param payload body map[string]string true "Plaintext payload" // @Success 200 {object} models.StatusMessageResponse "Ciphertext response" +// @Failure 400 {object} models.ErrorResponse "Invalid request" // @Failure 500 {object} models.ErrorResponse "Server error" // @Router /api/encrypt [post] func (h *Handler) EncryptData(w http.ResponseWriter, r *http.Request) { - //ctx := context.Background() - var cipherText string + if r.Method != http.MethodPost { + writeJSONError(w, http.StatusMethodNotAllowed, "method not allowed") + return + } - reqBody, err := io.ReadAll(r.Body) + var req encryptRequest + if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4096)).Decode(&req); err != nil { + h.Logger.Error("unable to decode encrypt request", "error", err) + writeJSONError(w, http.StatusBadRequest, "invalid JSON body") + return + } + plaintext := strings.TrimSpace(req.Plaintext) + if plaintext == "" { + plaintext = strings.TrimSpace(req.Value) + } + if plaintext == "" { + plaintext = strings.TrimSpace(req.Message) + } + if plaintext == "" { + writeJSONError(w, http.StatusBadRequest, "plaintext is required (accepted keys: plaintext, value, message)") + return + } + + cipherText, err := h.Secret.Encrypt([]byte(plaintext)) if err != nil { - h.Logger.Error("Invalid data received", "error", err) - fmt.Fprintf(w, "Invalid data received") - w.WriteHeader(http.StatusInternalServerError) + h.Logger.Error("unable to encrypt payload", "error", err) + writeJSONError(w, http.StatusInternalServerError, "encryption failed") return - } else { - h.Logger.Debug("received input data", "length", len(reqBody)) } - // get the json input - var input map[string]string - if err := json.Unmarshal(reqBody, &input); err != nil { - h.Logger.Error("unable to unmarshal json", "error", err) - prettyPrint(reqBody) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(map[string]string{ - "status": "ERROR", - "message": fmt.Sprintf("Unable to unmarshal JSON in request body: '%s'", err), - }) - return - } else { - h.Logger.Debug("successfully decoded JSON") - //prettyPrint(input) - } - - //cipher, err := h.Secret.Encrypt() - for k := range input { - //h.Logger.Debug("foo", "key", k, "value", input[k]) - cipherText, err = h.Secret.Encrypt([]byte(input[k])) - if err != nil { - h.Logger.Error("Unable to encrypt", "error", err) - } else { - h.Logger.Debug("Encrypted plaintext", "length", len(input[k]), "ciphertext", cipherText) - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(map[string]string{ - "status": "OK", - "message": cipherText, - }) - return - } - - } - - // return the result - + h.Logger.Debug("encrypted plaintext payload", "input_length", len(plaintext)) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(map[string]string{ + "status": "OK", + "message": cipherText, + "prefixed": encryptedValuePrefixV1 + cipherText, + "ciphertext": cipherText, + }) } diff --git a/server/handler/legacy_gate.go b/server/handler/legacy_gate.go new file mode 100644 index 0000000..15830c9 --- /dev/null +++ b/server/handler/legacy_gate.go @@ -0,0 +1,23 @@ +package handler + +import ( + "fmt" + "net/http" + "os" + "strings" +) + +const legacyAPIEnvVar = "VCTP_ENABLE_LEGACY_API" + +func legacyAPIEnabled() bool { + return strings.TrimSpace(os.Getenv(legacyAPIEnvVar)) == "1" +} + +func (h *Handler) denyLegacyAPI(w http.ResponseWriter, endpoint string) bool { + if legacyAPIEnabled() { + return false + } + h.Logger.Warn("legacy endpoint request blocked", "endpoint", endpoint, "env_var", legacyAPIEnvVar) + writeJSONError(w, http.StatusGone, fmt.Sprintf("%s is deprecated and disabled; set %s=1 to temporarily re-enable", endpoint, legacyAPIEnvVar)) + return true +} diff --git a/server/handler/updateCleanup.go b/server/handler/updateCleanup.go index 02f48cd..d709c23 100644 --- a/server/handler/updateCleanup.go +++ b/server/handler/updateCleanup.go @@ -16,6 +16,10 @@ import ( // @Failure 500 {string} string "Server error" // @Router /api/cleanup/updates [delete] func (h *Handler) UpdateCleanup(w http.ResponseWriter, r *http.Request) { + if h.denyLegacyAPI(w, "/api/cleanup/updates") { + return + } + /* // Get the current time now := time.Now() diff --git a/server/handler/vcCleanup.go b/server/handler/vcCleanup.go index de43642..ef849f0 100644 --- a/server/handler/vcCleanup.go +++ b/server/handler/vcCleanup.go @@ -20,6 +20,10 @@ import ( // @Failure 400 {object} models.ErrorResponse "Invalid request" // @Router /api/cleanup/vcenter [delete] func (h *Handler) VcCleanup(w http.ResponseWriter, r *http.Request) { + if h.denyLegacyAPI(w, "/api/cleanup/vcenter") { + return + } + ctx := context.Background() // Get the parameters diff --git a/server/handler/vmCreateEvent.go b/server/handler/vmCreateEvent.go index a8c85a7..f0b7fcc 100644 --- a/server/handler/vmCreateEvent.go +++ b/server/handler/vmCreateEvent.go @@ -27,6 +27,10 @@ import ( // @Failure 500 {string} string "Server error" // @Router /api/event/vm/create [post] func (h *Handler) VmCreateEvent(w http.ResponseWriter, r *http.Request) { + if h.denyLegacyAPI(w, "/api/event/vm/create") { + return + } + var ( unixTimestamp int64 //numVcpus int32 diff --git a/server/handler/vmDeleteEvent.go b/server/handler/vmDeleteEvent.go index 234f93c..a72b58e 100644 --- a/server/handler/vmDeleteEvent.go +++ b/server/handler/vmDeleteEvent.go @@ -25,6 +25,10 @@ import ( // @Failure 500 {string} string "Server error" // @Router /api/event/vm/delete [post] func (h *Handler) VmDeleteEvent(w http.ResponseWriter, r *http.Request) { + if h.denyLegacyAPI(w, "/api/event/vm/delete") { + return + } + var ( deletedTimestamp int64 ) diff --git a/server/handler/vmModifyEvent.go b/server/handler/vmModifyEvent.go index 91fd76e..57d3736 100644 --- a/server/handler/vmModifyEvent.go +++ b/server/handler/vmModifyEvent.go @@ -32,6 +32,10 @@ import ( // @Failure 500 {object} models.ErrorResponse "Server error" // @Router /api/event/vm/modify [post] func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) { + if h.denyLegacyAPI(w, "/api/event/vm/modify") { + return + } + var configChanges []map[string]string params := queries.CreateUpdateParams{} var unixTimestamp int64 diff --git a/server/handler/vmMoveEvent.go b/server/handler/vmMoveEvent.go index 626b8a2..3174b57 100644 --- a/server/handler/vmMoveEvent.go +++ b/server/handler/vmMoveEvent.go @@ -27,6 +27,10 @@ import ( // @Failure 500 {object} models.ErrorResponse "Server error" // @Router /api/event/vm/move [post] func (h *Handler) VmMoveEvent(w http.ResponseWriter, r *http.Request) { + if h.denyLegacyAPI(w, "/api/event/vm/move") { + return + } + params := queries.CreateUpdateParams{} var unixTimestamp int64 diff --git a/server/router/router.go b/server/router/router.go index e379b55..67ba361 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -55,7 +55,7 @@ func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver st // add missing data to VMs //mux.HandleFunc("/api/inventory/vm/update", h.VmUpdateDetails) - // temporary endpoint + // Legacy/maintenance endpoints are gated by VCTP_ENABLE_LEGACY_API. mux.HandleFunc("/api/cleanup/updates", h.UpdateCleanup) //mux.HandleFunc("/api/cleanup/vcenter", h.VcCleanup) diff --git a/src/vctp.default b/src/vctp.default index 2121968..111e69e 100644 --- a/src/vctp.default +++ b/src/vctp.default @@ -1,3 +1,9 @@ CPE_OPTS='-settings /etc/dtms/vctp.yml' MONTHLY_AGG_GO=0 -DAILY_AGG_GO=0 \ No newline at end of file +DAILY_AGG_GO=0 +# Optional explicit encryption key source (recommended for stable credential decryption across host changes): +# VCTP_ENCRYPTION_KEY='' +# PostgreSQL is experimental and disabled by default: +# VCTP_ENABLE_EXPERIMENTAL_POSTGRES=0 +# Deprecated API endpoints are disabled by default: +# VCTP_ENABLE_LEGACY_API=0 diff --git a/src/vctp.yml b/src/vctp.yml index 926b533..dad4b68 100644 --- a/src/vctp.yml +++ b/src/vctp.yml @@ -12,8 +12,10 @@ settings: vcenter_username: "" vcenter_password: "" vcenter_insecure: false - vcenter_event_polling_seconds: 60 - vcenter_inventory_polling_seconds: 7200 + # Deprecated (ignored): legacy event poller + vcenter_event_polling_seconds: 0 + # Deprecated (ignored): legacy inventory poller + vcenter_inventory_polling_seconds: 0 vcenter_inventory_snapshot_seconds: 3600 vcenter_inventory_aggregate_seconds: 86400 hourly_snapshot_concurrency: 0