diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 39c59682e..eba958ea4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,6 +27,18 @@ jobs: echo "::set-output name=product-version::$(make version)" echo "::set-output name=product-base-version::${BASE_VERSION}" + get-build-date: + runs-on: ubuntu-latest + outputs: + build-date: ${{ steps.get-build-date.outputs.build-date }} + steps: + - uses: actions/checkout@v2 + - name: get build date + id: get-build-date + run: | + make build-date + echo "::set-output name=build-date::$(make build-date)" + generate-metadata-file: needs: get-product-version runs-on: ubuntu-latest @@ -48,7 +60,7 @@ jobs: path: ${{ steps.generate-metadata-file.outputs.filepath }} build-other: - needs: get-product-version + needs: [ get-product-version, get-build-date ] runs-on: ubuntu-latest strategy: matrix: @@ -92,7 +104,7 @@ jobs: CGO_ENABLED: 0 run: | mkdir dist out - GO_TAGS="${{ env.GO_TAGS }}" VAULT_VERSION=${{ needs.get-product-version.outputs.product-base-version }} VAULT_REVISION="$(git rev-parse HEAD)" make build + GO_TAGS="${{ env.GO_TAGS }}" VAULT_VERSION=${{ needs.get-product-version.outputs.product-base-version }} VAULT_REVISION="$(git rev-parse HEAD)" VAULT_BUILD_DATE="${{ needs.get-build-date.outputs.build-date }}" make build zip -r -j out/${{ env.PKG_NAME }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip dist/ - uses: actions/upload-artifact@v2 with: @@ -100,7 +112,7 @@ jobs: path: out/${{ env.PKG_NAME }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip build-linux: - needs: get-product-version + needs: [ get-product-version, get-build-date ] runs-on: ubuntu-latest strategy: matrix: @@ -138,7 +150,7 @@ jobs: CGO_ENABLED: 0 run: | mkdir dist out - GO_TAGS="${{ env.GO_TAGS }}" VAULT_VERSION=${{ needs.get-product-version.outputs.product-base-version }} VAULT_REVISION="$(git rev-parse HEAD)" make build + GO_TAGS="${{ env.GO_TAGS }}" VAULT_VERSION=${{ needs.get-product-version.outputs.product-base-version }} VAULT_REVISION="$(git rev-parse HEAD)" VAULT_BUILD_DATE="${{ needs.get-build-date.outputs.build-date }}" make build zip -r -j out/${{ env.PKG_NAME }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip dist/ - uses: actions/upload-artifact@v2 with: @@ -177,7 +189,7 @@ jobs: path: out/${{ env.DEB_PACKAGE }} build-darwin: - needs: get-product-version + needs: [ get-product-version, get-build-date ] runs-on: macos-latest strategy: matrix: @@ -214,7 +226,7 @@ jobs: CGO_ENABLED: 0 run: | mkdir dist out - GO_TAGS="${{ env.GO_TAGS }}" VAULT_VERSION=${{ needs.get-product-version.outputs.product-base-version }} VAULT_REVISION="$(git rev-parse HEAD)" make build + GO_TAGS="${{ env.GO_TAGS }}" VAULT_VERSION=${{ needs.get-product-version.outputs.product-base-version }} VAULT_REVISION="$(git rev-parse HEAD)" VAULT_BUILD_DATE="${{ needs.get-build-date.outputs.build-date }}" make build zip -r -j out/${{ env.PKG_NAME }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip dist/ - uses: actions/upload-artifact@v2 with: diff --git a/Makefile b/Makefile index fedd01643..60df6e047 100644 --- a/Makefile +++ b/Makefile @@ -267,9 +267,14 @@ ci-verify: # This is used for release builds by .github/workflows/build.yml build: @echo "--> Building Vault $(VAULT_VERSION)" - @go build -v -tags "$(GO_TAGS)" -ldflags " -X github.com/hashicorp/vault/sdk/version.Version=$(VAULT_VERSION) -X github.com/hashicorp/vault/sdk/version.GitCommit=$(VAULT_REVISION)" -o dist/ + @go build -v -tags "$(GO_TAGS)" -ldflags " -X github.com/hashicorp/vault/sdk/version.Version=$(VAULT_VERSION) -X github.com/hashicorp/vault/sdk/version.GitCommit=$(VAULT_REVISION) -X github.com/hashicorp/vault/sdk/version.BuildDate=$(VAULT_BUILD_DATE)" -o dist/ .PHONY: version # This is used for release builds by .github/workflows/build.yml version: @$(CURDIR)/scripts/version.sh sdk/version/version_base.go + +.PHONY: build-date +# This is used for release builds by .github/workflows/build.yml +build-date: + @$(CURDIR)/scripts/build_date.sh diff --git a/api/sys_seal.go b/api/sys_seal.go index dcd8d32a5..189d61469 100644 --- a/api/sys_seal.go +++ b/api/sys_seal.go @@ -101,6 +101,7 @@ type SealStatusResponse struct { Progress int `json:"progress"` Nonce string `json:"nonce"` Version string `json:"version"` + BuildDate string `json:"build_date"` Migration bool `json:"migration"` ClusterName string `json:"cluster_name,omitempty"` ClusterID string `json:"cluster_id,omitempty"` diff --git a/changelog/14957.txt b/changelog/14957.txt new file mode 100644 index 000000000..96f0c0494 --- /dev/null +++ b/changelog/14957.txt @@ -0,0 +1,3 @@ +```release-note:improvement +core: Include build date in `sys/seal-status` and `sys/version-history` endpoints. +``` diff --git a/command/format.go b/command/format.go index 192b1aa8c..314090136 100644 --- a/command/format.go +++ b/command/format.go @@ -259,6 +259,7 @@ func (t TableFormatter) OutputSealStatusStruct(ui cli.Ui, secret *api.Secret, da } out = append(out, fmt.Sprintf("Version | %s", status.Version)) + out = append(out, fmt.Sprintf("Build Date | %s", status.BuildDate)) out = append(out, fmt.Sprintf("Storage Type | %s", status.StorageType)) if status.ClusterName != "" && status.ClusterID != "" { diff --git a/command/format_test.go b/command/format_test.go index f51bc5db0..3d1a3333c 100644 --- a/command/format_test.go +++ b/command/format_test.go @@ -111,6 +111,7 @@ Unseal Progress 3/1 Unseal Nonce nonce Seal Migration in Progress true Version version +Build Date build date Storage Type storage type Cluster Name cluster name Cluster ID cluster id @@ -141,6 +142,7 @@ Unseal Progress 3/1 Unseal Nonce nonce Seal Migration in Progress true Version version +Build Date build date Storage Type n/a HA Enabled false` @@ -166,6 +168,7 @@ func getMockStatusData(emptyFields bool) SealStatusOutput { Progress: 3, Nonce: "nonce", Version: "version", + BuildDate: "build date", Migration: true, ClusterName: "cluster name", ClusterID: "cluster id", @@ -197,6 +200,7 @@ func getMockStatusData(emptyFields bool) SealStatusOutput { Progress: 3, Nonce: "nonce", Version: "version", + BuildDate: "build date", Migration: true, ClusterName: "", ClusterID: "", diff --git a/command/version_history.go b/command/version_history.go index 1a4c73046..56d3deb96 100644 --- a/command/version_history.go +++ b/command/version_history.go @@ -100,7 +100,7 @@ func (c *VersionHistoryCommand) Run(args []string) int { return 2 } - table := []string{"Version | Installation Time"} + table := []string{"Version | Installation Time | Build Date"} columnConfig := columnize.DefaultConfig() for _, versionRaw := range keys { @@ -119,7 +119,7 @@ func (c *VersionHistoryCommand) Run(args []string) int { return 2 } - table = append(table, fmt.Sprintf("%s | %s", version, versionInfo["timestamp_installed"])) + table = append(table, fmt.Sprintf("%s | %s | %s", version, versionInfo["timestamp_installed"], versionInfo["build_date"])) } c.UI.Warn("") diff --git a/http/sys_seal_test.go b/http/sys_seal_test.go index a888039a6..e991f4416 100644 --- a/http/sys_seal_test.go +++ b/http/sys_seal_test.go @@ -11,6 +11,7 @@ import ( "github.com/go-test/deep" "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/sdk/logical" + "github.com/hashicorp/vault/sdk/version" "github.com/hashicorp/vault/vault" ) @@ -36,6 +37,7 @@ func TestSysSealStatus(t *testing.T) { "recovery_seal": false, "initialized": true, "migration": false, + "build_date": version.BuildDate, } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) @@ -120,6 +122,7 @@ func TestSysUnseal(t *testing.T) { "recovery_seal": false, "initialized": true, "migration": false, + "build_date": version.BuildDate, } if i == len(keys)-1 { expected["sealed"] = false @@ -201,6 +204,7 @@ func TestSysUnseal_Reset(t *testing.T) { "recovery_seal": false, "initialized": true, "migration": false, + "build_date": version.BuildDate, } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) @@ -240,6 +244,7 @@ func TestSysUnseal_Reset(t *testing.T) { "type": "shamir", "recovery_seal": false, "initialized": true, + "build_date": version.BuildDate, "migration": false, } testResponseStatus(t, resp, 200) diff --git a/scripts/build.sh b/scripts/build.sh index da7961fd2..14261537a 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -7,8 +7,9 @@ GO_CMD=${GO_CMD:-go} # Get the parent directory of where this script is. SOURCE="${BASH_SOURCE[0]}" +SOURCE_DIR=$( dirname "$SOURCE" ) while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done -DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" +DIR="$( cd -P "$SOURCE_DIR/.." && pwd )" # Change into that directory cd "$DIR" @@ -20,6 +21,8 @@ BUILD_TAGS="${BUILD_TAGS:-"vault"}" GIT_COMMIT="$(git rev-parse HEAD)" GIT_DIRTY="$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)" +BUILD_DATE=$("$SOURCE_DIR"/build_date.sh) + # If its dev mode, only build for ourself if [ "${VAULT_DEV_BUILD}x" != "x" ] && [ "${XC_OSARCH}x" == "x" ]; then XC_OS=$(${GO_CMD} env GOOS) @@ -54,7 +57,7 @@ echo "==> Building..." gox \ -osarch="${XC_OSARCH}" \ -gcflags "${GCFLAGS}" \ - -ldflags "${LD_FLAGS}-X github.com/hashicorp/vault/sdk/version.GitCommit='${GIT_COMMIT}${GIT_DIRTY}'" \ + -ldflags "${LD_FLAGS}-X github.com/hashicorp/vault/sdk/version.GitCommit='${GIT_COMMIT}${GIT_DIRTY}' -X github.com/hashicorp/vault/sdk/version.BuildDate=${BUILD_DATE}" \ -output "pkg/{{.OS}}_{{.Arch}}/vault" \ ${GOX_PARALLEL_BUILDS+-parallel="${GOX_PARALLEL_BUILDS}"} \ -tags="${BUILD_TAGS}" \ diff --git a/scripts/build_date.sh b/scripts/build_date.sh new file mode 100755 index 000000000..6fc93ddfd --- /dev/null +++ b/scripts/build_date.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# it's tricky to do an RFC3339 format in a cross platform way, so we hardcode UTC +DATE_FORMAT="%Y-%m-%dT%H:%M:%SZ" + +# we're using this for build date because it's stable across platform builds +git show -s --format=%cd --date=format:"$DATE_FORMAT" HEAD diff --git a/scripts/windows/build.bat b/scripts/windows/build.bat index 524fbacb2..3b4ee3a4c 100644 --- a/scripts/windows/build.bat +++ b/scripts/windows/build.bat @@ -13,6 +13,7 @@ md bin 2>nul :: Get the git commit set _GIT_COMMIT_FILE=%TEMP%\vault-git_commit.txt set _GIT_DIRTY_FILE=%TEMP%\vault-git_dirty.txt +set _GIT_COMMIT_DATE_FILE=%TEMP%\vault-git_commit_date.txt set _NUL_CMP_FILE=%TEMP%\vault-nul_cmp.txt type nul >%_NUL_CMP_FILE% @@ -21,6 +22,10 @@ git rev-parse HEAD >"%_GIT_COMMIT_FILE%" set /p _GIT_COMMIT=<"%_GIT_COMMIT_FILE%" del /f "%_GIT_COMMIT_FILE%" 2>nul +git show -s --format=%cd --date=format:"%Y-%m-%dT%H:%M:%SZ" HEAD >"%_GIT_COMMIT__DATE_FILE%" +set /p _BUILD_DATE=<"%_GIT_COMMIT_DATE_FILE%" +del /f "%_GIT_COMMIT_DATE_FILE%" 2>nul + set _GIT_DIRTY= git status --porcelain >"%_GIT_DIRTY_FILE%" fc "%_GIT_DIRTY_FILE%" "%_NUL_CMP_FILE%" >nul @@ -60,7 +65,7 @@ echo ==^> Building... gox^ -os="%_XC_OS%"^ -arch="%_XC_ARCH%"^ - -ldflags "-X github.com/hashicorp/vault/sdk/version.GitCommit=%_GIT_COMMIT%%_GIT_DIRTY%"^ + -ldflags "-X github.com/hashicorp/vault/sdk/version.GitCommit=%_GIT_COMMIT%%_GIT_DIRTY% -X github.com/hashicorp/vault/sdk/version.BuildDate=%_BUILD_DATE%"^ -output "pkg/{{.OS}}_{{.Arch}}/vault"^ . diff --git a/sdk/version/version.go b/sdk/version/version.go index 1a4521ae6..78b8eb829 100644 --- a/sdk/version/version.go +++ b/sdk/version/version.go @@ -11,6 +11,7 @@ type VersionInfo struct { Version string `json:"version,omitempty"` VersionPrerelease string `json:"version_prerelease,omitempty"` VersionMetadata string `json:"version_metadata,omitempty"` + BuildDate string `json:"build_date,omitempty"` } func GetVersion() *VersionInfo { @@ -29,6 +30,7 @@ func GetVersion() *VersionInfo { Version: ver, VersionPrerelease: rel, VersionMetadata: md, + BuildDate: BuildDate, } } @@ -70,5 +72,9 @@ func (c *VersionInfo) FullVersionNumber(rev bool) string { fmt.Fprintf(&versionString, " (%s)", c.Revision) } + if c.BuildDate != "" { + fmt.Fprintf(&versionString, ", built %s", c.BuildDate) + } + return versionString.String() } diff --git a/sdk/version/version_base.go b/sdk/version/version_base.go index e10edfeaf..2a21d313d 100644 --- a/sdk/version/version_base.go +++ b/sdk/version/version_base.go @@ -5,6 +5,9 @@ var ( GitCommit string GitDescribe string + // The compilation date. This will be filled in by the compiler. + BuildDate string + // Whether cgo is enabled or not; set at build time CgoEnabled bool diff --git a/vault/core.go b/vault/core.go index 169cecc0d..cc74ed247 100644 --- a/vault/core.go +++ b/vault/core.go @@ -611,11 +611,12 @@ type Core struct { // disableSSCTokens is used to disable server side consistent token creation/usage disableSSCTokens bool - // versionTimestamps is a map of vault versions to timestamps when the version + // versionHistory is a map of vault versions to VaultVersion. The + // VaultVersion.TimestampInstalled when the version will denote when the version // was first run. Note that because perf standbys should be upgraded first, and // only the active node will actually write the new version timestamp, a perf // standby shouldn't rely on the stored version timestamps being present. - versionTimestamps map[string]time.Time + versionHistory map[string]VaultVersion } func (c *Core) HAState() consts.HAState { @@ -1112,9 +1113,9 @@ func NewCore(conf *CoreConfig) (*Core, error) { return nil, err } - if c.versionTimestamps == nil { - c.logger.Info("Initializing versionTimestamps for core") - c.versionTimestamps = make(map[string]time.Time) + if c.versionHistory == nil { + c.logger.Info("Initializing version history cache for core") + c.versionHistory = make(map[string]VaultVersion) } return c, nil @@ -1124,15 +1125,22 @@ func NewCore(conf *CoreConfig) (*Core, error) { // storage, and then loads all versions and upgrade timestamps out from storage. func (c *Core) handleVersionTimeStamps(ctx context.Context) error { currentTime := time.Now().UTC() - isUpdated, err := c.storeVersionTimestamp(ctx, version.Version, currentTime, false) + + vaultVersion := &VaultVersion{ + TimestampInstalled: currentTime, + Version: version.Version, + BuildDate: version.BuildDate, + } + + isUpdated, err := c.storeVersionEntry(ctx, vaultVersion, false) if err != nil { return fmt.Errorf("error storing vault version: %w", err) } if isUpdated { - c.logger.Info("Recorded vault version", "vault version", version.Version, "upgrade time", currentTime) + c.logger.Info("Recorded vault version", "vault version", version.Version, "upgrade time", currentTime, "build date", version.BuildDate) } - // Finally, load the versions into core fields - err = c.loadVersionTimestamps(ctx) + // Finally, populate the version history cache + err = c.loadVersionHistory(ctx) if err != nil { return err } diff --git a/vault/core_test.go b/vault/core_test.go index 7f2224750..31d427b33 100644 --- a/vault/core_test.go +++ b/vault/core_test.go @@ -56,17 +56,20 @@ func TestSealConfig_Invalid(t *testing.T) { } } -// TestCore_HasVaultVersion checks that versionTimestamps are correct and initialized +// TestCore_HasVaultVersion checks that versionHistory is correct and initialized // after a core has been unsealed. func TestCore_HasVaultVersion(t *testing.T) { c, _, _ := TestCoreUnsealed(t) - if c.versionTimestamps == nil { + if c.versionHistory == nil { t.Fatalf("Version timestamps for core were not initialized for a new core") } - upgradeTime, ok := c.versionTimestamps[version.Version] + versionEntry, ok := c.versionHistory[version.Version] if !ok { t.Fatalf("%s upgrade time not found", version.Version) } + + upgradeTime := versionEntry.TimestampInstalled + if upgradeTime.After(time.Now()) || upgradeTime.Before(time.Now().Add(-1*time.Hour)) { t.Fatalf("upgrade time isn't within reasonable bounds of new core initialization. " + fmt.Sprintf("time is: %+v, upgrade time is %+v", time.Now(), upgradeTime)) @@ -408,22 +411,19 @@ func TestCore_PreOneTen_BatchTokens(t *testing.T) { // load up some versions and ensure that 1.9 is the most recent one by timestamp (even though this isn't realistic) upgradeTimePlusEpsilon := time.Now().UTC() - versionEntries := []struct { - version string - ts time.Time - }{ - {"1.10.1", upgradeTimePlusEpsilon.Add(-4 * time.Hour)}, - {"1.9.2", upgradeTimePlusEpsilon.Add(2 * time.Hour)}, + versionEntries := []VaultVersion{ + {Version: "1.10.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)}, + {Version: "1.9.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)}, } for _, entry := range versionEntries { - _, err := c.storeVersionTimestamp(context.Background(), entry.version, entry.ts, false) + _, err := c.storeVersionEntry(context.Background(), &entry, false) if err != nil { t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error()) } } - err := c.loadVersionTimestamps(c.activeContext) + err := c.loadVersionHistory(c.activeContext) if err != nil { t.Fatalf("failed to populate version history cache, err: %s", err.Error()) } @@ -461,22 +461,19 @@ func TestCore_OneTenPlus_BatchTokens(t *testing.T) { // load up some versions and ensure that 1.10 is the most recent version upgradeTimePlusEpsilon := time.Now().UTC() - versionEntries := []struct { - version string - ts time.Time - }{ - {"1.9.2", upgradeTimePlusEpsilon.Add(-4 * time.Hour)}, - {"1.10.1", upgradeTimePlusEpsilon.Add(2 * time.Hour)}, + versionEntries := []VaultVersion{ + {Version: "1.9.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)}, + {Version: "1.10.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)}, } for _, entry := range versionEntries { - _, err := c.storeVersionTimestamp(context.Background(), entry.version, entry.ts, false) + _, err := c.storeVersionEntry(context.Background(), &entry, false) if err != nil { t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error()) } } - err := c.loadVersionTimestamps(c.activeContext) + err := c.loadVersionHistory(c.activeContext) if err != nil { t.Fatalf("failed to populate version history cache, err: %s", err.Error()) } diff --git a/vault/logical_system.go b/vault/logical_system.go index b0b843b40..777296216 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -4159,6 +4159,7 @@ type SealStatusResponse struct { Progress int `json:"progress"` Nonce string `json:"nonce"` Version string `json:"version"` + BuildDate string `json:"build_date"` Migration bool `json:"migration"` ClusterName string `json:"cluster_name,omitempty"` ClusterID string `json:"cluster_id,omitempty"` @@ -4192,6 +4193,7 @@ func (core *Core) GetSealStatus(ctx context.Context) (*SealStatusResponse, error RecoverySeal: core.SealAccess().RecoveryKeySupported(), StorageType: core.StorageType(), Version: version.GetVersion().VersionNumber(), + BuildDate: version.BuildDate, }, nil } @@ -4220,6 +4222,7 @@ func (core *Core) GetSealStatus(ctx context.Context) (*SealStatusResponse, error Progress: progress, Nonce: nonce, Version: version.GetVersion().VersionNumber(), + BuildDate: version.BuildDate, Migration: core.IsInSealMigrationMode() && !core.IsSealMigrated(), ClusterName: clusterName, ClusterID: clusterID, @@ -4401,11 +4404,8 @@ func (b *SystemBackend) handleVersionHistoryList(ctx context.Context, req *logic versions := make([]VaultVersion, 0) respKeys := make([]string, 0) - for versionString, ts := range b.Core.versionTimestamps { - versions = append(versions, VaultVersion{ - Version: versionString, - TimestampInstalled: ts, - }) + for _, versionEntry := range b.Core.versionHistory { + versions = append(versions, versionEntry) } sort.Slice(versions, func(i, j int) bool { @@ -4419,6 +4419,7 @@ func (b *SystemBackend) handleVersionHistoryList(ctx context.Context, req *logic entry := map[string]interface{}{ "timestamp_installed": v.TimestampInstalled.Format(time.RFC3339), + "build_date": v.BuildDate, "previous_version": nil, } diff --git a/vault/vault_version_time.go b/vault/vault_version_time.go index 40f3f2813..4bcf3a97a 100644 --- a/vault/vault_version_time.go +++ b/vault/vault_version_time.go @@ -5,4 +5,5 @@ import "time" type VaultVersion struct { TimestampInstalled time.Time Version string + BuildDate string } diff --git a/vault/version_store.go b/vault/version_store.go index 5ef7eddeb..5d78b0b53 100644 --- a/vault/version_store.go +++ b/vault/version_store.go @@ -15,17 +15,16 @@ const ( vaultVersionPath string = "core/versions/" ) -// storeVersionTimestamp will store the version and timestamp pair to storage +// storeVersionEntry will store the version, timestamp, and build date to storage // only if no entry for that version already exists in storage. Version // timestamps were initially stored in local time. UTC should be used. Existing // entries can be overwritten via the force flag. A bool will be returned // denoting whether the entry was updated -func (c *Core) storeVersionTimestamp(ctx context.Context, version string, timestampInstalled time.Time, force bool) (bool, error) { - key := vaultVersionPath + version +func (c *Core) storeVersionEntry(ctx context.Context, vaultVersion *VaultVersion, force bool) (bool, error) { + key := vaultVersionPath + vaultVersion.Version - vaultVersion := VaultVersion{ - TimestampInstalled: timestampInstalled.UTC(), - Version: version, + if vaultVersion.TimestampInstalled.Location() != time.UTC { + vaultVersion.TimestampInstalled = vaultVersion.TimestampInstalled.UTC() } marshalledVaultVersion, err := json.Marshal(vaultVersion) @@ -70,44 +69,44 @@ func (c *Core) storeVersionTimestamp(ctx context.Context, version string, timest // FindOldestVersionTimestamp searches for the vault version with the oldest // upgrade timestamp from storage. The earliest version this can be is 1.9.0. func (c *Core) FindOldestVersionTimestamp() (string, time.Time, error) { - if c.versionTimestamps == nil { - return "", time.Time{}, fmt.Errorf("version timestamps are not initialized") + if c.versionHistory == nil { + return "", time.Time{}, fmt.Errorf("version history is not initialized") } oldestUpgradeTime := time.Now().UTC() var oldestVersion string - for version, upgradeTime := range c.versionTimestamps { - if upgradeTime.Before(oldestUpgradeTime) { - oldestVersion = version - oldestUpgradeTime = upgradeTime + for versionStr, versionEntry := range c.versionHistory { + if versionEntry.TimestampInstalled.Before(oldestUpgradeTime) { + oldestVersion = versionStr + oldestUpgradeTime = versionEntry.TimestampInstalled } } return oldestVersion, oldestUpgradeTime, nil } func (c *Core) FindNewestVersionTimestamp() (string, time.Time, error) { - if c.versionTimestamps == nil { - return "", time.Time{}, fmt.Errorf("version timestamps are not initialized") + if c.versionHistory == nil { + return "", time.Time{}, fmt.Errorf("version history is not initialized") } var newestUpgradeTime time.Time var newestVersion string - for version, upgradeTime := range c.versionTimestamps { - if upgradeTime.After(newestUpgradeTime) { - newestVersion = version - newestUpgradeTime = upgradeTime + for versionStr, versionEntry := range c.versionHistory { + if versionEntry.TimestampInstalled.After(newestUpgradeTime) { + newestVersion = versionStr + newestUpgradeTime = versionEntry.TimestampInstalled } } return newestVersion, newestUpgradeTime, nil } -// loadVersionTimestamps loads all the vault versions and associated upgrade -// timestamps from storage. Version timestamps were originally stored in local -// time. A timestamp that is not in UTC will be rewritten to storage as UTC. -func (c *Core) loadVersionTimestamps(ctx context.Context) error { +// loadVersionHistory loads all the vault versions entries from storage. +// Version timestamps were originally stored in local time. A timestamp +// that is not in UTC will be rewritten to storage as UTC. +func (c *Core) loadVersionHistory(ctx context.Context) error { vaultVersions, err := c.barrier.List(ctx, vaultVersionPath) if err != nil { return fmt.Errorf("unable to retrieve vault versions from storage: %w", err) @@ -130,23 +129,22 @@ func (c *Core) loadVersionTimestamps(ctx context.Context) error { return fmt.Errorf("found empty serialized vault version at path %s", versionPath) } - timestampInstalled := vaultVersion.TimestampInstalled - // self-heal entries that were not stored in UTC - if timestampInstalled.Location() != time.UTC { - timestampInstalled = timestampInstalled.UTC() - isUpdated, err := c.storeVersionTimestamp(ctx, vaultVersion.Version, timestampInstalled, true) + if vaultVersion.TimestampInstalled.Location() != time.UTC { + vaultVersion.TimestampInstalled = vaultVersion.TimestampInstalled.UTC() + + isUpdated, err := c.storeVersionEntry(ctx, &vaultVersion, true) if err != nil { c.logger.Warn("failed to rewrite vault version timestamp as UTC", "error", err) } if isUpdated { c.logger.Info("self-healed pre-existing vault version in UTC", - "vault version", vaultVersion.Version, "UTC time", timestampInstalled) + "vault version", vaultVersion.Version, "UTC time", vaultVersion.TimestampInstalled) } } - c.versionTimestamps[vaultVersion.Version] = timestampInstalled + c.versionHistory[vaultVersion.Version] = vaultVersion } return nil } diff --git a/vault/version_store_test.go b/vault/version_store_test.go index 28dc8668d..823301c2b 100644 --- a/vault/version_store_test.go +++ b/vault/version_store_test.go @@ -13,16 +13,20 @@ import ( func TestVersionStore_StoreMultipleVaultVersions(t *testing.T) { c, _, _ := TestCoreUnsealed(t) upgradeTimePlusEpsilon := time.Now().UTC() - wasStored, err := c.storeVersionTimestamp(context.Background(), version.Version, upgradeTimePlusEpsilon.Add(30*time.Hour), false) + vaultVersion := &VaultVersion{ + Version: version.Version, + TimestampInstalled: upgradeTimePlusEpsilon.Add(30 * time.Hour), + } + wasStored, err := c.storeVersionEntry(context.Background(), vaultVersion, false) if err != nil || wasStored { t.Fatalf("vault version was re-stored: %v, err is: %s", wasStored, err.Error()) } - upgradeTime, ok := c.versionTimestamps[version.Version] + versionEntry, ok := c.versionHistory[version.Version] if !ok { t.Fatalf("no %s version timestamp found", version.Version) } - if upgradeTime.After(upgradeTimePlusEpsilon) { - t.Fatalf("upgrade time for %s is incorrect: got %+v, expected less than %+v", version.Version, upgradeTime, upgradeTimePlusEpsilon) + if versionEntry.TimestampInstalled.After(upgradeTimePlusEpsilon) { + t.Fatalf("upgrade time for %s is incorrect: got %+v, expected less than %+v", version.Version, versionEntry.TimestampInstalled, upgradeTimePlusEpsilon) } } @@ -33,28 +37,25 @@ func TestVersionStore_GetOldestVersion(t *testing.T) { upgradeTimePlusEpsilon := time.Now().UTC() // 1.6.2 is stored before 1.6.1, so even though it is a higher number, it should be returned. - versionEntries := []struct { - version string - ts time.Time - }{ - {"1.6.2", upgradeTimePlusEpsilon.Add(-4 * time.Hour)}, - {"1.6.1", upgradeTimePlusEpsilon.Add(2 * time.Hour)}, + versionEntries := []VaultVersion{ + {Version: "1.6.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)}, + {Version: "1.6.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)}, } for _, entry := range versionEntries { - _, err := c.storeVersionTimestamp(context.Background(), entry.version, entry.ts, false) + _, err := c.storeVersionEntry(context.Background(), &entry, false) if err != nil { t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error()) } } - err := c.loadVersionTimestamps(c.activeContext) + err := c.loadVersionHistory(c.activeContext) if err != nil { t.Fatalf("failed to populate version history cache, err: %s", err.Error()) } - if len(c.versionTimestamps) != 3 { - t.Fatalf("expected 3 entries in timestamps map after refresh, found: %d", len(c.versionTimestamps)) + if len(c.versionHistory) != 3 { + t.Fatalf("expected 3 entries in timestamps map after refresh, found: %d", len(c.versionHistory)) } v, tm, err := c.FindOldestVersionTimestamp() if err != nil { @@ -75,28 +76,25 @@ func TestVersionStore_GetNewestVersion(t *testing.T) { upgradeTimePlusEpsilon := time.Now().UTC() // 1.6.1 is stored after 1.6.2, so even though it is a lower number, it should be returned. - versionEntries := []struct { - version string - ts time.Time - }{ - {"1.6.2", upgradeTimePlusEpsilon.Add(-4 * time.Hour)}, - {"1.6.1", upgradeTimePlusEpsilon.Add(2 * time.Hour)}, + versionEntries := []VaultVersion{ + {Version: "1.6.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)}, + {Version: "1.6.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)}, } for _, entry := range versionEntries { - _, err := c.storeVersionTimestamp(context.Background(), entry.version, entry.ts, false) + _, err := c.storeVersionEntry(context.Background(), &entry, false) if err != nil { t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error()) } } - err := c.loadVersionTimestamps(c.activeContext) + err := c.loadVersionHistory(c.activeContext) if err != nil { t.Fatalf("failed to populate version history cache, err: %s", err.Error()) } - if len(c.versionTimestamps) != 3 { - t.Fatalf("expected 3 entries in timestamps map after refresh, found: %d", len(c.versionTimestamps)) + if len(c.versionHistory) != 3 { + t.Fatalf("expected 3 entries in timestamps map after refresh, found: %d", len(c.versionHistory)) } v, tm, err := c.FindNewestVersionTimestamp() if err != nil { @@ -119,29 +117,26 @@ func TestVersionStore_SelfHealUTC(t *testing.T) { nowEST := time.Now().In(estLoc) - versionEntries := []struct { - version string - ts time.Time - }{ - {"1.9.0", nowEST.Add(24 * time.Hour)}, - {"1.9.1", nowEST.Add(48 * time.Hour)}, + versionEntries := []VaultVersion{ + {Version: "1.9.0", TimestampInstalled: nowEST.Add(24 * time.Hour)}, + {Version: "1.9.1", TimestampInstalled: nowEST.Add(48 * time.Hour)}, } for _, entry := range versionEntries { - _, err := c.storeVersionTimestamp(context.Background(), entry.version, entry.ts, false) + _, err := c.storeVersionEntry(context.Background(), &entry, false) if err != nil { t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error()) } } - err = c.loadVersionTimestamps(c.activeContext) + err = c.loadVersionHistory(c.activeContext) if err != nil { t.Fatalf("failed to load version timestamps, err: %s", err.Error()) } - for versionStr, ts := range c.versionTimestamps { - if ts.Location() != time.UTC { - t.Fatalf("failed to convert %s timestamp %s to UTC", versionStr, ts) + for _, entry := range c.versionHistory { + if entry.TimestampInstalled.Location() != time.UTC { + t.Fatalf("failed to convert %s timestamp %s to UTC", entry.Version, entry.TimestampInstalled) } } }