Add build date (#14957)

* add BuildDate to version base

* populate BuildDate with ldflags

* include BuildDate in FullVersionNumber

* add BuildDate to seal-status and associated status cmd

* extend core/versions entries to include BuildDate

* include BuildDate in version-history API and CLI

* fix version history tests

* fix sys status tests

* fix TestStatusFormat

* remove extraneous LD_FLAGS from build.sh

* add BuildDate to build.bat

* fix TestSysUnseal_Reset

* attempt to add build-date to release builds

* add branch to github build workflow

* add get-build-date to build-* job needs

* fix release build command vars

* add missing quote in release build command

* Revert "add branch to github build workflow"

This reverts commit b835699ecb7c2c632757fa5fe64b3d5f60d2a886.

* add changelog entry
This commit is contained in:
Chris Capurso
2022-04-19 14:28:08 -04:00
committed by GitHub
parent 4c1e2f6aef
commit 203b1ad789
19 changed files with 163 additions and 109 deletions

View File

@@ -27,6 +27,18 @@ jobs:
echo "::set-output name=product-version::$(make version)" echo "::set-output name=product-version::$(make version)"
echo "::set-output name=product-base-version::${BASE_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: generate-metadata-file:
needs: get-product-version needs: get-product-version
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -48,7 +60,7 @@ jobs:
path: ${{ steps.generate-metadata-file.outputs.filepath }} path: ${{ steps.generate-metadata-file.outputs.filepath }}
build-other: build-other:
needs: get-product-version needs: [ get-product-version, get-build-date ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@@ -92,7 +104,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
run: | run: |
mkdir dist out 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/ 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 - uses: actions/upload-artifact@v2
with: with:
@@ -100,7 +112,7 @@ jobs:
path: out/${{ env.PKG_NAME }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip path: out/${{ env.PKG_NAME }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip
build-linux: build-linux:
needs: get-product-version needs: [ get-product-version, get-build-date ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@@ -138,7 +150,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
run: | run: |
mkdir dist out 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/ 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 - uses: actions/upload-artifact@v2
with: with:
@@ -177,7 +189,7 @@ jobs:
path: out/${{ env.DEB_PACKAGE }} path: out/${{ env.DEB_PACKAGE }}
build-darwin: build-darwin:
needs: get-product-version needs: [ get-product-version, get-build-date ]
runs-on: macos-latest runs-on: macos-latest
strategy: strategy:
matrix: matrix:
@@ -214,7 +226,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
run: | run: |
mkdir dist out 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/ 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 - uses: actions/upload-artifact@v2
with: with:

View File

@@ -267,9 +267,14 @@ ci-verify:
# This is used for release builds by .github/workflows/build.yml # This is used for release builds by .github/workflows/build.yml
build: build:
@echo "--> Building Vault $(VAULT_VERSION)" @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 .PHONY: version
# This is used for release builds by .github/workflows/build.yml # This is used for release builds by .github/workflows/build.yml
version: version:
@$(CURDIR)/scripts/version.sh sdk/version/version_base.go @$(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

View File

@@ -101,6 +101,7 @@ type SealStatusResponse struct {
Progress int `json:"progress"` Progress int `json:"progress"`
Nonce string `json:"nonce"` Nonce string `json:"nonce"`
Version string `json:"version"` Version string `json:"version"`
BuildDate string `json:"build_date"`
Migration bool `json:"migration"` Migration bool `json:"migration"`
ClusterName string `json:"cluster_name,omitempty"` ClusterName string `json:"cluster_name,omitempty"`
ClusterID string `json:"cluster_id,omitempty"` ClusterID string `json:"cluster_id,omitempty"`

3
changelog/14957.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
core: Include build date in `sys/seal-status` and `sys/version-history` endpoints.
```

View File

@@ -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("Version | %s", status.Version))
out = append(out, fmt.Sprintf("Build Date | %s", status.BuildDate))
out = append(out, fmt.Sprintf("Storage Type | %s", status.StorageType)) out = append(out, fmt.Sprintf("Storage Type | %s", status.StorageType))
if status.ClusterName != "" && status.ClusterID != "" { if status.ClusterName != "" && status.ClusterID != "" {

View File

@@ -111,6 +111,7 @@ Unseal Progress 3/1
Unseal Nonce nonce Unseal Nonce nonce
Seal Migration in Progress true Seal Migration in Progress true
Version version Version version
Build Date build date
Storage Type storage type Storage Type storage type
Cluster Name cluster name Cluster Name cluster name
Cluster ID cluster id Cluster ID cluster id
@@ -141,6 +142,7 @@ Unseal Progress 3/1
Unseal Nonce nonce Unseal Nonce nonce
Seal Migration in Progress true Seal Migration in Progress true
Version version Version version
Build Date build date
Storage Type n/a Storage Type n/a
HA Enabled false` HA Enabled false`
@@ -166,6 +168,7 @@ func getMockStatusData(emptyFields bool) SealStatusOutput {
Progress: 3, Progress: 3,
Nonce: "nonce", Nonce: "nonce",
Version: "version", Version: "version",
BuildDate: "build date",
Migration: true, Migration: true,
ClusterName: "cluster name", ClusterName: "cluster name",
ClusterID: "cluster id", ClusterID: "cluster id",
@@ -197,6 +200,7 @@ func getMockStatusData(emptyFields bool) SealStatusOutput {
Progress: 3, Progress: 3,
Nonce: "nonce", Nonce: "nonce",
Version: "version", Version: "version",
BuildDate: "build date",
Migration: true, Migration: true,
ClusterName: "", ClusterName: "",
ClusterID: "", ClusterID: "",

View File

@@ -100,7 +100,7 @@ func (c *VersionHistoryCommand) Run(args []string) int {
return 2 return 2
} }
table := []string{"Version | Installation Time"} table := []string{"Version | Installation Time | Build Date"}
columnConfig := columnize.DefaultConfig() columnConfig := columnize.DefaultConfig()
for _, versionRaw := range keys { for _, versionRaw := range keys {
@@ -119,7 +119,7 @@ func (c *VersionHistoryCommand) Run(args []string) int {
return 2 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("") c.UI.Warn("")

View File

@@ -11,6 +11,7 @@ import (
"github.com/go-test/deep" "github.com/go-test/deep"
"github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/version"
"github.com/hashicorp/vault/vault" "github.com/hashicorp/vault/vault"
) )
@@ -36,6 +37,7 @@ func TestSysSealStatus(t *testing.T) {
"recovery_seal": false, "recovery_seal": false,
"initialized": true, "initialized": true,
"migration": false, "migration": false,
"build_date": version.BuildDate,
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual) testResponseBody(t, resp, &actual)
@@ -120,6 +122,7 @@ func TestSysUnseal(t *testing.T) {
"recovery_seal": false, "recovery_seal": false,
"initialized": true, "initialized": true,
"migration": false, "migration": false,
"build_date": version.BuildDate,
} }
if i == len(keys)-1 { if i == len(keys)-1 {
expected["sealed"] = false expected["sealed"] = false
@@ -201,6 +204,7 @@ func TestSysUnseal_Reset(t *testing.T) {
"recovery_seal": false, "recovery_seal": false,
"initialized": true, "initialized": true,
"migration": false, "migration": false,
"build_date": version.BuildDate,
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual) testResponseBody(t, resp, &actual)
@@ -240,6 +244,7 @@ func TestSysUnseal_Reset(t *testing.T) {
"type": "shamir", "type": "shamir",
"recovery_seal": false, "recovery_seal": false,
"initialized": true, "initialized": true,
"build_date": version.BuildDate,
"migration": false, "migration": false,
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)

View File

@@ -7,8 +7,9 @@ GO_CMD=${GO_CMD:-go}
# Get the parent directory of where this script is. # Get the parent directory of where this script is.
SOURCE="${BASH_SOURCE[0]}" SOURCE="${BASH_SOURCE[0]}"
SOURCE_DIR=$( dirname "$SOURCE" )
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" DIR="$( cd -P "$SOURCE_DIR/.." && pwd )"
# Change into that directory # Change into that directory
cd "$DIR" cd "$DIR"
@@ -20,6 +21,8 @@ BUILD_TAGS="${BUILD_TAGS:-"vault"}"
GIT_COMMIT="$(git rev-parse HEAD)" GIT_COMMIT="$(git rev-parse HEAD)"
GIT_DIRTY="$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)" 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 its dev mode, only build for ourself
if [ "${VAULT_DEV_BUILD}x" != "x" ] && [ "${XC_OSARCH}x" == "x" ]; then if [ "${VAULT_DEV_BUILD}x" != "x" ] && [ "${XC_OSARCH}x" == "x" ]; then
XC_OS=$(${GO_CMD} env GOOS) XC_OS=$(${GO_CMD} env GOOS)
@@ -54,7 +57,7 @@ echo "==> Building..."
gox \ gox \
-osarch="${XC_OSARCH}" \ -osarch="${XC_OSARCH}" \
-gcflags "${GCFLAGS}" \ -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" \ -output "pkg/{{.OS}}_{{.Arch}}/vault" \
${GOX_PARALLEL_BUILDS+-parallel="${GOX_PARALLEL_BUILDS}"} \ ${GOX_PARALLEL_BUILDS+-parallel="${GOX_PARALLEL_BUILDS}"} \
-tags="${BUILD_TAGS}" \ -tags="${BUILD_TAGS}" \

6
scripts/build_date.sh Executable file
View File

@@ -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

View File

@@ -13,6 +13,7 @@ md bin 2>nul
:: Get the git commit :: Get the git commit
set _GIT_COMMIT_FILE=%TEMP%\vault-git_commit.txt set _GIT_COMMIT_FILE=%TEMP%\vault-git_commit.txt
set _GIT_DIRTY_FILE=%TEMP%\vault-git_dirty.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 set _NUL_CMP_FILE=%TEMP%\vault-nul_cmp.txt
type nul >%_NUL_CMP_FILE% type nul >%_NUL_CMP_FILE%
@@ -21,6 +22,10 @@ git rev-parse HEAD >"%_GIT_COMMIT_FILE%"
set /p _GIT_COMMIT=<"%_GIT_COMMIT_FILE%" set /p _GIT_COMMIT=<"%_GIT_COMMIT_FILE%"
del /f "%_GIT_COMMIT_FILE%" 2>nul 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= set _GIT_DIRTY=
git status --porcelain >"%_GIT_DIRTY_FILE%" git status --porcelain >"%_GIT_DIRTY_FILE%"
fc "%_GIT_DIRTY_FILE%" "%_NUL_CMP_FILE%" >nul fc "%_GIT_DIRTY_FILE%" "%_NUL_CMP_FILE%" >nul
@@ -60,7 +65,7 @@ echo ==^> Building...
gox^ gox^
-os="%_XC_OS%"^ -os="%_XC_OS%"^
-arch="%_XC_ARCH%"^ -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"^ -output "pkg/{{.OS}}_{{.Arch}}/vault"^
. .

View File

@@ -11,6 +11,7 @@ type VersionInfo struct {
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
VersionPrerelease string `json:"version_prerelease,omitempty"` VersionPrerelease string `json:"version_prerelease,omitempty"`
VersionMetadata string `json:"version_metadata,omitempty"` VersionMetadata string `json:"version_metadata,omitempty"`
BuildDate string `json:"build_date,omitempty"`
} }
func GetVersion() *VersionInfo { func GetVersion() *VersionInfo {
@@ -29,6 +30,7 @@ func GetVersion() *VersionInfo {
Version: ver, Version: ver,
VersionPrerelease: rel, VersionPrerelease: rel,
VersionMetadata: md, VersionMetadata: md,
BuildDate: BuildDate,
} }
} }
@@ -70,5 +72,9 @@ func (c *VersionInfo) FullVersionNumber(rev bool) string {
fmt.Fprintf(&versionString, " (%s)", c.Revision) fmt.Fprintf(&versionString, " (%s)", c.Revision)
} }
if c.BuildDate != "" {
fmt.Fprintf(&versionString, ", built %s", c.BuildDate)
}
return versionString.String() return versionString.String()
} }

View File

@@ -5,6 +5,9 @@ var (
GitCommit string GitCommit string
GitDescribe 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 // Whether cgo is enabled or not; set at build time
CgoEnabled bool CgoEnabled bool

View File

@@ -611,11 +611,12 @@ type Core struct {
// disableSSCTokens is used to disable server side consistent token creation/usage // disableSSCTokens is used to disable server side consistent token creation/usage
disableSSCTokens bool 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 // 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 // only the active node will actually write the new version timestamp, a perf
// standby shouldn't rely on the stored version timestamps being present. // 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 { func (c *Core) HAState() consts.HAState {
@@ -1112,9 +1113,9 @@ func NewCore(conf *CoreConfig) (*Core, error) {
return nil, err return nil, err
} }
if c.versionTimestamps == nil { if c.versionHistory == nil {
c.logger.Info("Initializing versionTimestamps for core") c.logger.Info("Initializing version history cache for core")
c.versionTimestamps = make(map[string]time.Time) c.versionHistory = make(map[string]VaultVersion)
} }
return c, nil 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. // storage, and then loads all versions and upgrade timestamps out from storage.
func (c *Core) handleVersionTimeStamps(ctx context.Context) error { func (c *Core) handleVersionTimeStamps(ctx context.Context) error {
currentTime := time.Now().UTC() 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 { if err != nil {
return fmt.Errorf("error storing vault version: %w", err) return fmt.Errorf("error storing vault version: %w", err)
} }
if isUpdated { 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 // Finally, populate the version history cache
err = c.loadVersionTimestamps(ctx) err = c.loadVersionHistory(ctx)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -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. // after a core has been unsealed.
func TestCore_HasVaultVersion(t *testing.T) { func TestCore_HasVaultVersion(t *testing.T) {
c, _, _ := TestCoreUnsealed(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") 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 { if !ok {
t.Fatalf("%s upgrade time not found", version.Version) 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)) { 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. " + 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)) 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) // 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() upgradeTimePlusEpsilon := time.Now().UTC()
versionEntries := []struct { versionEntries := []VaultVersion{
version string {Version: "1.10.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
ts time.Time {Version: "1.9.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)},
}{
{"1.10.1", upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
{"1.9.2", upgradeTimePlusEpsilon.Add(2 * time.Hour)},
} }
for _, entry := range versionEntries { for _, entry := range versionEntries {
_, err := c.storeVersionTimestamp(context.Background(), entry.version, entry.ts, false) _, err := c.storeVersionEntry(context.Background(), &entry, false)
if err != nil { if err != nil {
t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error()) 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 { if err != nil {
t.Fatalf("failed to populate version history cache, err: %s", err.Error()) 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 // load up some versions and ensure that 1.10 is the most recent version
upgradeTimePlusEpsilon := time.Now().UTC() upgradeTimePlusEpsilon := time.Now().UTC()
versionEntries := []struct { versionEntries := []VaultVersion{
version string {Version: "1.9.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
ts time.Time {Version: "1.10.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)},
}{
{"1.9.2", upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
{"1.10.1", upgradeTimePlusEpsilon.Add(2 * time.Hour)},
} }
for _, entry := range versionEntries { for _, entry := range versionEntries {
_, err := c.storeVersionTimestamp(context.Background(), entry.version, entry.ts, false) _, err := c.storeVersionEntry(context.Background(), &entry, false)
if err != nil { if err != nil {
t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error()) 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 { if err != nil {
t.Fatalf("failed to populate version history cache, err: %s", err.Error()) t.Fatalf("failed to populate version history cache, err: %s", err.Error())
} }

View File

@@ -4159,6 +4159,7 @@ type SealStatusResponse struct {
Progress int `json:"progress"` Progress int `json:"progress"`
Nonce string `json:"nonce"` Nonce string `json:"nonce"`
Version string `json:"version"` Version string `json:"version"`
BuildDate string `json:"build_date"`
Migration bool `json:"migration"` Migration bool `json:"migration"`
ClusterName string `json:"cluster_name,omitempty"` ClusterName string `json:"cluster_name,omitempty"`
ClusterID string `json:"cluster_id,omitempty"` ClusterID string `json:"cluster_id,omitempty"`
@@ -4192,6 +4193,7 @@ func (core *Core) GetSealStatus(ctx context.Context) (*SealStatusResponse, error
RecoverySeal: core.SealAccess().RecoveryKeySupported(), RecoverySeal: core.SealAccess().RecoveryKeySupported(),
StorageType: core.StorageType(), StorageType: core.StorageType(),
Version: version.GetVersion().VersionNumber(), Version: version.GetVersion().VersionNumber(),
BuildDate: version.BuildDate,
}, nil }, nil
} }
@@ -4220,6 +4222,7 @@ func (core *Core) GetSealStatus(ctx context.Context) (*SealStatusResponse, error
Progress: progress, Progress: progress,
Nonce: nonce, Nonce: nonce,
Version: version.GetVersion().VersionNumber(), Version: version.GetVersion().VersionNumber(),
BuildDate: version.BuildDate,
Migration: core.IsInSealMigrationMode() && !core.IsSealMigrated(), Migration: core.IsInSealMigrationMode() && !core.IsSealMigrated(),
ClusterName: clusterName, ClusterName: clusterName,
ClusterID: clusterID, ClusterID: clusterID,
@@ -4401,11 +4404,8 @@ func (b *SystemBackend) handleVersionHistoryList(ctx context.Context, req *logic
versions := make([]VaultVersion, 0) versions := make([]VaultVersion, 0)
respKeys := make([]string, 0) respKeys := make([]string, 0)
for versionString, ts := range b.Core.versionTimestamps { for _, versionEntry := range b.Core.versionHistory {
versions = append(versions, VaultVersion{ versions = append(versions, versionEntry)
Version: versionString,
TimestampInstalled: ts,
})
} }
sort.Slice(versions, func(i, j int) bool { 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{}{ entry := map[string]interface{}{
"timestamp_installed": v.TimestampInstalled.Format(time.RFC3339), "timestamp_installed": v.TimestampInstalled.Format(time.RFC3339),
"build_date": v.BuildDate,
"previous_version": nil, "previous_version": nil,
} }

View File

@@ -5,4 +5,5 @@ import "time"
type VaultVersion struct { type VaultVersion struct {
TimestampInstalled time.Time TimestampInstalled time.Time
Version string Version string
BuildDate string
} }

View File

@@ -15,17 +15,16 @@ const (
vaultVersionPath string = "core/versions/" 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 // only if no entry for that version already exists in storage. Version
// timestamps were initially stored in local time. UTC should be used. Existing // 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 // entries can be overwritten via the force flag. A bool will be returned
// denoting whether the entry was updated // denoting whether the entry was updated
func (c *Core) storeVersionTimestamp(ctx context.Context, version string, timestampInstalled time.Time, force bool) (bool, error) { func (c *Core) storeVersionEntry(ctx context.Context, vaultVersion *VaultVersion, force bool) (bool, error) {
key := vaultVersionPath + version key := vaultVersionPath + vaultVersion.Version
vaultVersion := VaultVersion{ if vaultVersion.TimestampInstalled.Location() != time.UTC {
TimestampInstalled: timestampInstalled.UTC(), vaultVersion.TimestampInstalled = vaultVersion.TimestampInstalled.UTC()
Version: version,
} }
marshalledVaultVersion, err := json.Marshal(vaultVersion) 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 // FindOldestVersionTimestamp searches for the vault version with the oldest
// upgrade timestamp from storage. The earliest version this can be is 1.9.0. // upgrade timestamp from storage. The earliest version this can be is 1.9.0.
func (c *Core) FindOldestVersionTimestamp() (string, time.Time, error) { func (c *Core) FindOldestVersionTimestamp() (string, time.Time, error) {
if c.versionTimestamps == nil { if c.versionHistory == nil {
return "", time.Time{}, fmt.Errorf("version timestamps are not initialized") return "", time.Time{}, fmt.Errorf("version history is not initialized")
} }
oldestUpgradeTime := time.Now().UTC() oldestUpgradeTime := time.Now().UTC()
var oldestVersion string var oldestVersion string
for version, upgradeTime := range c.versionTimestamps { for versionStr, versionEntry := range c.versionHistory {
if upgradeTime.Before(oldestUpgradeTime) { if versionEntry.TimestampInstalled.Before(oldestUpgradeTime) {
oldestVersion = version oldestVersion = versionStr
oldestUpgradeTime = upgradeTime oldestUpgradeTime = versionEntry.TimestampInstalled
} }
} }
return oldestVersion, oldestUpgradeTime, nil return oldestVersion, oldestUpgradeTime, nil
} }
func (c *Core) FindNewestVersionTimestamp() (string, time.Time, error) { func (c *Core) FindNewestVersionTimestamp() (string, time.Time, error) {
if c.versionTimestamps == nil { if c.versionHistory == nil {
return "", time.Time{}, fmt.Errorf("version timestamps are not initialized") return "", time.Time{}, fmt.Errorf("version history is not initialized")
} }
var newestUpgradeTime time.Time var newestUpgradeTime time.Time
var newestVersion string var newestVersion string
for version, upgradeTime := range c.versionTimestamps { for versionStr, versionEntry := range c.versionHistory {
if upgradeTime.After(newestUpgradeTime) { if versionEntry.TimestampInstalled.After(newestUpgradeTime) {
newestVersion = version newestVersion = versionStr
newestUpgradeTime = upgradeTime newestUpgradeTime = versionEntry.TimestampInstalled
} }
} }
return newestVersion, newestUpgradeTime, nil return newestVersion, newestUpgradeTime, nil
} }
// loadVersionTimestamps loads all the vault versions and associated upgrade // loadVersionHistory loads all the vault versions entries from storage.
// timestamps from storage. Version timestamps were originally stored in local // Version timestamps were originally stored in local time. A timestamp
// time. A timestamp that is not in UTC will be rewritten to storage as UTC. // that is not in UTC will be rewritten to storage as UTC.
func (c *Core) loadVersionTimestamps(ctx context.Context) error { func (c *Core) loadVersionHistory(ctx context.Context) error {
vaultVersions, err := c.barrier.List(ctx, vaultVersionPath) vaultVersions, err := c.barrier.List(ctx, vaultVersionPath)
if err != nil { if err != nil {
return fmt.Errorf("unable to retrieve vault versions from storage: %w", err) 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) return fmt.Errorf("found empty serialized vault version at path %s", versionPath)
} }
timestampInstalled := vaultVersion.TimestampInstalled
// self-heal entries that were not stored in UTC // self-heal entries that were not stored in UTC
if timestampInstalled.Location() != time.UTC { if vaultVersion.TimestampInstalled.Location() != time.UTC {
timestampInstalled = timestampInstalled.UTC() vaultVersion.TimestampInstalled = vaultVersion.TimestampInstalled.UTC()
isUpdated, err := c.storeVersionTimestamp(ctx, vaultVersion.Version, timestampInstalled, true)
isUpdated, err := c.storeVersionEntry(ctx, &vaultVersion, true)
if err != nil { if err != nil {
c.logger.Warn("failed to rewrite vault version timestamp as UTC", "error", err) c.logger.Warn("failed to rewrite vault version timestamp as UTC", "error", err)
} }
if isUpdated { if isUpdated {
c.logger.Info("self-healed pre-existing vault version in UTC", 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 return nil
} }

View File

@@ -13,16 +13,20 @@ import (
func TestVersionStore_StoreMultipleVaultVersions(t *testing.T) { func TestVersionStore_StoreMultipleVaultVersions(t *testing.T) {
c, _, _ := TestCoreUnsealed(t) c, _, _ := TestCoreUnsealed(t)
upgradeTimePlusEpsilon := time.Now().UTC() 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 { if err != nil || wasStored {
t.Fatalf("vault version was re-stored: %v, err is: %s", wasStored, err.Error()) 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 { if !ok {
t.Fatalf("no %s version timestamp found", version.Version) t.Fatalf("no %s version timestamp found", version.Version)
} }
if upgradeTime.After(upgradeTimePlusEpsilon) { if versionEntry.TimestampInstalled.After(upgradeTimePlusEpsilon) {
t.Fatalf("upgrade time for %s is incorrect: got %+v, expected less than %+v", version.Version, upgradeTime, 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() 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. // 1.6.2 is stored before 1.6.1, so even though it is a higher number, it should be returned.
versionEntries := []struct { versionEntries := []VaultVersion{
version string {Version: "1.6.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
ts time.Time {Version: "1.6.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)},
}{
{"1.6.2", upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
{"1.6.1", upgradeTimePlusEpsilon.Add(2 * time.Hour)},
} }
for _, entry := range versionEntries { for _, entry := range versionEntries {
_, err := c.storeVersionTimestamp(context.Background(), entry.version, entry.ts, false) _, err := c.storeVersionEntry(context.Background(), &entry, false)
if err != nil { if err != nil {
t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error()) 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 { if err != nil {
t.Fatalf("failed to populate version history cache, err: %s", err.Error()) t.Fatalf("failed to populate version history cache, err: %s", err.Error())
} }
if len(c.versionTimestamps) != 3 { if len(c.versionHistory) != 3 {
t.Fatalf("expected 3 entries in timestamps map after refresh, found: %d", len(c.versionTimestamps)) t.Fatalf("expected 3 entries in timestamps map after refresh, found: %d", len(c.versionHistory))
} }
v, tm, err := c.FindOldestVersionTimestamp() v, tm, err := c.FindOldestVersionTimestamp()
if err != nil { if err != nil {
@@ -75,28 +76,25 @@ func TestVersionStore_GetNewestVersion(t *testing.T) {
upgradeTimePlusEpsilon := time.Now().UTC() 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. // 1.6.1 is stored after 1.6.2, so even though it is a lower number, it should be returned.
versionEntries := []struct { versionEntries := []VaultVersion{
version string {Version: "1.6.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
ts time.Time {Version: "1.6.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)},
}{
{"1.6.2", upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
{"1.6.1", upgradeTimePlusEpsilon.Add(2 * time.Hour)},
} }
for _, entry := range versionEntries { for _, entry := range versionEntries {
_, err := c.storeVersionTimestamp(context.Background(), entry.version, entry.ts, false) _, err := c.storeVersionEntry(context.Background(), &entry, false)
if err != nil { if err != nil {
t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error()) 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 { if err != nil {
t.Fatalf("failed to populate version history cache, err: %s", err.Error()) t.Fatalf("failed to populate version history cache, err: %s", err.Error())
} }
if len(c.versionTimestamps) != 3 { if len(c.versionHistory) != 3 {
t.Fatalf("expected 3 entries in timestamps map after refresh, found: %d", len(c.versionTimestamps)) t.Fatalf("expected 3 entries in timestamps map after refresh, found: %d", len(c.versionHistory))
} }
v, tm, err := c.FindNewestVersionTimestamp() v, tm, err := c.FindNewestVersionTimestamp()
if err != nil { if err != nil {
@@ -119,29 +117,26 @@ func TestVersionStore_SelfHealUTC(t *testing.T) {
nowEST := time.Now().In(estLoc) nowEST := time.Now().In(estLoc)
versionEntries := []struct { versionEntries := []VaultVersion{
version string {Version: "1.9.0", TimestampInstalled: nowEST.Add(24 * time.Hour)},
ts time.Time {Version: "1.9.1", TimestampInstalled: nowEST.Add(48 * time.Hour)},
}{
{"1.9.0", nowEST.Add(24 * time.Hour)},
{"1.9.1", nowEST.Add(48 * time.Hour)},
} }
for _, entry := range versionEntries { for _, entry := range versionEntries {
_, err := c.storeVersionTimestamp(context.Background(), entry.version, entry.ts, false) _, err := c.storeVersionEntry(context.Background(), &entry, false)
if err != nil { if err != nil {
t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error()) 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 { if err != nil {
t.Fatalf("failed to load version timestamps, err: %s", err.Error()) t.Fatalf("failed to load version timestamps, err: %s", err.Error())
} }
for versionStr, ts := range c.versionTimestamps { for _, entry := range c.versionHistory {
if ts.Location() != time.UTC { if entry.TimestampInstalled.Location() != time.UTC {
t.Fatalf("failed to convert %s timestamp %s to UTC", versionStr, ts) t.Fatalf("failed to convert %s timestamp %s to UTC", entry.Version, entry.TimestampInstalled)
} }
} }
} }