summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrew Dunham <andrew@du.nham.ca>2025-03-10 16:38:44 -0400
committerAndrew Dunham <andrew@du.nham.ca>2025-03-10 16:38:44 -0400
commit5869f14e748f2b217a82f04e3fbb6cf089bff8e3 (patch)
tree911ad8d93ea41aa0dd54d340db914a28fecbc782
parent69b27d2fcfeaa745de072f96dd6c30f4f085ecd9 (diff)
downloadtailscale-andrew/current-time.tar.xz
tailscale-andrew/current-time.zip
net/{currenttime,tlsdial}: add minimum possible time for TLSandrew/current-time
This adds a new package, net/currenttime, which is a thin wrapper around time.Now. If the value returned by time.Now is before a hard-coded value baked into the binary, that hard-coded value will be returned instead. In the case where the system has a buggy, malfunctioning, or nonexistent RTC, this can improve the likelihood that Tailscale will be able to establish a connection to the control plane (via TLS) and fetch the server certificate. As a future TODO: we should cache this value on-disk between process starts (possibly in the state file?) so that we succeed even if the Tailscale server certificate has already expired from the perspective of the minimum time. Additionally, add a GitHub workflow that bumps the current time to a new value every 14 days, so that the value stays reasonably up-to-date in our repository without introducing impurities into the build process. Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: If63cf28c4f188993894d3de589fd65ad447def6f
-rw-r--r--.github/workflows/update-current-time.yml44
-rw-r--r--cmd/derper/depaware.txt1
-rw-r--r--cmd/k8s-operator/depaware.txt1
-rw-r--r--cmd/tailscale/depaware.txt1
-rw-r--r--cmd/tailscaled/depaware.txt1
-rw-r--r--net/currenttime/currenttime.go42
-rw-r--r--net/currenttime/currenttime_test.go14
-rw-r--r--net/currenttime/mintime.txt1
-rw-r--r--net/currenttime/update-current-time.go20
-rw-r--r--net/tlsdial/tlsdial.go2
10 files changed, 127 insertions, 0 deletions
diff --git a/.github/workflows/update-current-time.yml b/.github/workflows/update-current-time.yml
new file mode 100644
index 000000000..f1c86c0be
--- /dev/null
+++ b/.github/workflows/update-current-time.yml
@@ -0,0 +1,44 @@
+name: update-current-time
+
+on:
+ # allow manual execution
+ workflow_dispatch:
+
+ # run every 14 days
+ schedule:
+ - cron: "0 0 */14 * *"
+
+concurrency:
+ group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+ update-flake:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Get access token
+ uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0
+ id: generate-token
+ with:
+ app_id: ${{ secrets.LICENSING_APP_ID }}
+ installation_retrieval_mode: "id"
+ installation_retrieval_payload: ${{ secrets.LICENSING_APP_INSTALLATION_ID }}
+ private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
+
+ - name: Send pull request
+ uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 #v7.0.7
+ with:
+ token: ${{ steps.generate-token.outputs.token }}
+ author: Time Updater <noreply+time-updater@tailscale.com>
+ committer: Time Updater <noreply+time-updater@tailscale.com>
+ branch: time-updates
+ commit-message: "net/currentime: update minimum time"
+ title: "net/currentime: update minimum time"
+ body: Triggered by ${{ github.repository }}@${{ github.sha }}
+ signoff: true
+ delete-branch: true
+ reviewers: andrew-d
diff --git a/cmd/derper/depaware.txt b/cmd/derper/depaware.txt
index 1812a1a8d..d6ce74d72 100644
--- a/cmd/derper/depaware.txt
+++ b/cmd/derper/depaware.txt
@@ -103,6 +103,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/kube/kubetypes from tailscale.com/envknob
tailscale.com/metrics from tailscale.com/cmd/derper+
tailscale.com/net/bakedroots from tailscale.com/net/tlsdial
+ tailscale.com/net/currenttime from tailscale.com/net/tlsdial
tailscale.com/net/dnscache from tailscale.com/derp/derphttp
tailscale.com/net/ktimeout from tailscale.com/cmd/derper
tailscale.com/net/netaddr from tailscale.com/ipn+
diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt
index 54d9bd248..c3bbf043b 100644
--- a/cmd/k8s-operator/depaware.txt
+++ b/cmd/k8s-operator/depaware.txt
@@ -847,6 +847,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/net/bakedroots from tailscale.com/net/tlsdial+
tailscale.com/net/captivedetection from tailscale.com/ipn/ipnlocal+
tailscale.com/net/connstats from tailscale.com/net/tstun+
+ tailscale.com/net/currenttime from tailscale.com/net/tlsdial
tailscale.com/net/dns from tailscale.com/ipn/ipnlocal+
tailscale.com/net/dns/publicdns from tailscale.com/net/dns+
tailscale.com/net/dns/recursive from tailscale.com/net/dnsfallback
diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt
index afe62165c..7814ea677 100644
--- a/cmd/tailscale/depaware.txt
+++ b/cmd/tailscale/depaware.txt
@@ -102,6 +102,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/metrics from tailscale.com/derp+
tailscale.com/net/bakedroots from tailscale.com/net/tlsdial
tailscale.com/net/captivedetection from tailscale.com/net/netcheck
+ tailscale.com/net/currenttime from tailscale.com/net/tlsdial
tailscale.com/net/dns/recursive from tailscale.com/net/dnsfallback
tailscale.com/net/dnscache from tailscale.com/control/controlhttp+
tailscale.com/net/dnsfallback from tailscale.com/control/controlhttp+
diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt
index c0f592ea1..2e82b398c 100644
--- a/cmd/tailscaled/depaware.txt
+++ b/cmd/tailscaled/depaware.txt
@@ -297,6 +297,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/net/bakedroots from tailscale.com/net/tlsdial+
tailscale.com/net/captivedetection from tailscale.com/ipn/ipnlocal+
tailscale.com/net/connstats from tailscale.com/net/tstun+
+ tailscale.com/net/currenttime from tailscale.com/net/tlsdial
tailscale.com/net/dns from tailscale.com/cmd/tailscaled+
tailscale.com/net/dns/publicdns from tailscale.com/net/dns+
tailscale.com/net/dns/recursive from tailscale.com/net/dnsfallback
diff --git a/net/currenttime/currenttime.go b/net/currenttime/currenttime.go
new file mode 100644
index 000000000..7ef2a1c56
--- /dev/null
+++ b/net/currenttime/currenttime.go
@@ -0,0 +1,42 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package currenttime provides a fallback "current time" that can be used as
+// the minimum possible time for things like TLS certificate verification.
+//
+// This ensures that if a Tailscale client's clock is wrong, it can still
+// verify TLS certificates, assuming that the server certificate hasn't already
+// expired from the point of view of the minimum time.
+//
+// In the future, we may want to consider caching the last known current time
+// on-disk to improve the accuracy of this fallback.
+package currenttime
+
+import (
+ _ "embed"
+ "strconv"
+ "time"
+)
+
+//go:embed mintime.txt
+var minTimeUnixMs string
+
+var minCurrentTime time.Time
+
+func init() {
+ ms, err := strconv.ParseInt(minTimeUnixMs, 10, 64)
+ if err != nil {
+ panic(err)
+ }
+ minCurrentTime = time.UnixMilli(int64(ms))
+}
+
+// Now returns the current time as per [time.Now], except that if it is before
+// the baked-in "minimum current time", that value will be returned instead.
+func Now() time.Time {
+ now := time.Now()
+ if now.Before(minCurrentTime) {
+ return minCurrentTime
+ }
+ return now
+}
diff --git a/net/currenttime/currenttime_test.go b/net/currenttime/currenttime_test.go
new file mode 100644
index 000000000..958a8436e
--- /dev/null
+++ b/net/currenttime/currenttime_test.go
@@ -0,0 +1,14 @@
+package currenttime
+
+import (
+ "testing"
+ "time"
+)
+
+func TestMinTime(t *testing.T) {
+ // The baked-in time should always be before the current time.
+ now := time.Now()
+ if !minCurrentTime.Before(now) {
+ t.Fatalf("minCurrentTime is not before the current time: %v >= %v", minCurrentTime, now)
+ }
+}
diff --git a/net/currenttime/mintime.txt b/net/currenttime/mintime.txt
new file mode 100644
index 000000000..5da66af78
--- /dev/null
+++ b/net/currenttime/mintime.txt
@@ -0,0 +1 @@
+1741638824797 \ No newline at end of file
diff --git a/net/currenttime/update-current-time.go b/net/currenttime/update-current-time.go
new file mode 100644
index 000000000..717a984ce
--- /dev/null
+++ b/net/currenttime/update-current-time.go
@@ -0,0 +1,20 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build ignore
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "time"
+)
+
+func main() {
+ contents := fmt.Sprintf(`%d`, time.Now().UnixMilli())
+ if err := os.WriteFile("mintime.txt", []byte(contents), 0644); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/net/tlsdial/tlsdial.go b/net/tlsdial/tlsdial.go
index 4d22383ef..832a3e67a 100644
--- a/net/tlsdial/tlsdial.go
+++ b/net/tlsdial/tlsdial.go
@@ -29,6 +29,7 @@ import (
"tailscale.com/health"
"tailscale.com/hostinfo"
"tailscale.com/net/bakedroots"
+ "tailscale.com/net/currenttime"
"tailscale.com/net/tlsdial/blockblame"
)
@@ -144,6 +145,7 @@ func Config(host string, ht *health.Tracker, base *tls.Config) *tls.Config {
opts := x509.VerifyOptions{
DNSName: cs.ServerName,
Intermediates: x509.NewCertPool(),
+ CurrentTime: currenttime.Now(), // helps if the system clock is wrong
}
for _, cert := range cs.PeerCertificates[1:] {
opts.Intermediates.AddCert(cert)