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 + committer: Time Updater + 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)